Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@
"@types/inquirer": "^9.0.7",
"@types/node": "^22.13.0",
"@types/ws": "^8.5.14",
"eslint": "^9.17.0",

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changelog was not updated for these user-facing fixes

The repository contract requires the Unreleased section of CHANGELOG.md to be updated for user-facing changes, but this PR changes login behaviour, API request handling and streaming reconnects without adding any entry.
Impact: Users and maintainers get no record of these behaviour changes in the changelog.

Rule reference

AGENTS.md states: "Conventional Commit titles; update CHANGELOG.md (Unreleased) for user-facing changes." The Unreleased section in CHANGELOG.md:7 is still empty.

Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

"tsup": "^8.0.0",
"typescript": "^5.9.3",
"vitest": "^4.0.16"
Expand Down
9 changes: 6 additions & 3 deletions src/commands/api/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,12 @@ export function registerApiCommands(program: Command): void {
}

const baseUrl = config.projects[project]?.baseUrl ?? "https://wave.online";
const url = path.startsWith("http")
? path
: `${baseUrl}${path.startsWith("/") ? path : `/${path}`}`;
const base = new URL(baseUrl);
const requested = new URL(path, `${base.origin}/`);
if (requested.protocol !== "https:" || requested.origin !== base.origin) {
throw new Error("API requests must use the configured WAVE HTTPS host");
}
const url = requested.toString();
Comment on lines +27 to +32

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 API requests lose any path prefix configured for the WAVE host

Request addresses are now rebuilt from only the host part of the configured address (new URL(path, ${base.origin}/) at src/commands/api/index.ts:28) instead of the full configured address, so any folder path in that setting is silently dropped and requests go to the wrong location.
Impact: Users whose configured WAVE address includes a path prefix will have their raw API calls sent to a wrong URL and fail.

Mechanism and secondary effect

Previously the URL was ${baseUrl}${path} (old src/commands/api/index.ts:27-29), preserving any path in baseUrl (the schema allows any URL, src/lib/config/schema.ts:6). Now only base.origin is used, so with baseUrl = https://wave.online/gateway, wave api GET /v1/streams hits https://wave.online/v1/streams. Other commands still concatenate onto the full base URL (e.g. src/commands/auth/index.ts:99), so behaviour is inconsistent.

Additionally, the hard requested.protocol !== "https:" check makes the api command unusable against a non-HTTPS base URL (e.g. a local dev host set via WAVE_BASE_URL), even though login accepts such a value (src/commands/auth/index.ts:27).

Prompt for agents
In src/commands/api/index.ts the request URL is resolved against base.origin, discarding any path component of the configured baseUrl, unlike other commands which append to the full baseUrl. Resolve the path against the full base URL (ensuring a trailing slash for relative resolution, or concatenating for absolute paths) while still validating that the final origin matches the configured origin. Also consider whether the strict https-only requirement should allow the configured base URL's own scheme (e.g. http://localhost for development).
Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.


const headers: Record<string, string> = {
Authorization: `Bearer ${apiKey}`,
Expand Down
20 changes: 20 additions & 0 deletions src/commands/auth/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,18 @@ export function registerAuthCommands(program: Command): void {
const config = await loadConfig();
const project = config.currentProject || "default";
await storeApiKey(project, opts.apiKey);
await updateConfig((config) => ({
...config,
currentProject: project,
projects: {
...config.projects,
[project]: config.projects[project] ?? {
organizationId: "",
organizationName: "",
baseUrl: process.env["WAVE_BASE_URL"] ?? "https://wave.online",
},
},
}));
console.log(chalk.green(`API key stored for project "${project}".`));
return;
}
Expand All @@ -40,6 +52,14 @@ export function registerAuthCommands(program: Command): void {
await updateConfig((config) => ({
...config,
currentProject: project,
projects: {
...config.projects,
[project]: config.projects[project] ?? {
organizationId: "",
organizationName: "",
baseUrl,
},
},
}));

console.log(chalk.green("\nAuthentication complete. You can now use the WAVE CLI."));
Expand Down
5 changes: 3 additions & 2 deletions src/commands/init/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { Command } from "commander";
import chalk from "chalk";
import { writeFile, mkdir, readFile, readdir, copyFile } from "node:fs/promises";
import { existsSync } from "node:fs";
import { join, dirname, resolve } from "node:path";
import { join, dirname, resolve, sep } from "node:path";
import { fileURLToPath } from "node:url";
import { spawnSync } from "node:child_process";
import { wrapCommand } from "../../lib/errors.js";
Expand Down Expand Up @@ -56,7 +56,8 @@ const TEMPLATES: TemplateDefinition[] = [
function getTemplatesDir(): string {
const thisFile = fileURLToPath(import.meta.url);
// Walk up from src/commands/init/ or dist/commands/init/ to package root
const packageRoot = resolve(dirname(thisFile), "..", "..", "..");
const fileDir = dirname(thisFile);
const packageRoot = fileDir.endsWith(`${sep}dist`) ? resolve(fileDir, "..") : resolve(fileDir, "..", "..", "..");
return join(packageRoot, "templates");
}

Expand Down
4 changes: 3 additions & 1 deletion src/lib/sse-client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,9 @@ export async function connectSSE(

while (!controller.signal.aborted) {
const { done, value } = await reader.read();
if (done) break;
if (done) {
throw new Error("SSE connection closed");
}
Comment on lines +96 to +98

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔴 Streaming commands never stop and repeatedly show a connection error when the server ends the stream normally

A normally finished event stream is now turned into a failure (throw new Error("SSE connection closed") at src/lib/sse-client.ts:96-98) instead of a clean finish, so users see repeated error messages and the command keeps reconnecting without ever ending.
Impact: Log/listen/dev streaming commands print spurious errors and hang indefinitely instead of finishing when the server closes the stream.

Why the retry limit never stops the loop

On every successful connection reconnectAttempts is reset to 0 (src/lib/sse-client.ts:84). A server that accepts the connection and then closes it (e.g. periodic SSE timeouts – the log command explicitly handles a timeout event, src/commands/logs/index.ts:60) will therefore always reconnect: connect → done → throw → onError (prints "Connection error: SSE connection closed") → backoff of initialDelay (counter was reset so the exponent is always 1) → reconnect, forever. onClose is never invoked, so consumers never learn the stream ended, and each retry is a nested return connect() call from the catch block, growing the promise chain.

A cleaner approach is to distinguish an intentional server-side end (call onClose) from an unexpected drop, and to not reset the attempt counter unless the connection actually delivered data / stayed up for a meaningful period.

Prompt for agents
In src/lib/sse-client.ts, the read loop now throws "SSE connection closed" when the reader reports done. Because reconnectAttempts is reset to 0 on each successful connection (line 84), a server that closes the stream right after connecting causes an endless reconnect loop with a constant 1s delay, an error message on every cycle, and onClose is never called. Consider tracking whether the connection was up long enough / received events before resetting the attempt counter, and treat a graceful server close distinctly (e.g. reconnect quietly without emitting onError, and still honour maxReconnectAttempts so the loop terminates and onClose fires).
Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

Comment on lines 94 to +98

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔍 SSE reader is never released on error/reconnect

When the loop now throws on stream end (or on any parsing error), the ReadableStreamDefaultReader obtained at src/lib/sse-client.ts:87 is never cancelled or released before connect() recurses. With the new reconnect-on-clean-close behaviour this happens on every cycle, so readers/response bodies accumulate for long-lived sessions. Adding a try/finally around the read loop with reader.cancel()/releaseLock() would bound this.

Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.


buffer += decoder.decode(value, { stream: true });

Expand Down
30 changes: 30 additions & 0 deletions src/types/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
export interface DeviceAuthResponse {
device_code: string;
user_code: string;
verification_uri: string;
verification_uri_complete?: string;
expires_in: number;
interval: number;
}

export interface TokenResponse {
access_token: string;
token_type?: string;
expires_in?: number;
refresh_token?: string;
}

export interface WaveConfig {
version: string;
currentProject: string;
projects: Record<string, {
organizationId: string;
organizationName: string;
baseUrl?: string;
region?: string;
}>;
defaults: { outputFormat: OutputFormat; protocol?: string; color: "auto" | "on" | "off" };
telemetry: { enabled: boolean; errorReporting: boolean };
}

export type OutputFormat = "table" | "json" | "yaml";
2 changes: 1 addition & 1 deletion templates/api-integration/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
"build": "tsc"
},
"dependencies": {
"@wave/sdk": "^2.0.0"
"@wave-av/sdk": "^2.0.0"
},
"devDependencies": {
"tsx": "^4.0.0",
Expand Down
2 changes: 1 addition & 1 deletion templates/api-integration/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Wave } from "@wave/sdk";
import { Wave } from "@wave-av/sdk";

const wave = new Wave({
apiKey: process.env.WAVE_API_KEY!,
Expand Down
2 changes: 1 addition & 1 deletion templates/blank/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
"build": "tsc"
},
"dependencies": {
"@wave/sdk": "^2.0.0"
"@wave-av/sdk": "^2.0.0"
},
"devDependencies": {
"tsx": "^4.0.0",
Expand Down
2 changes: 1 addition & 1 deletion templates/blank/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Wave } from "@wave/sdk";
import { Wave } from "@wave-av/sdk";

const wave = new Wave({
apiKey: process.env.WAVE_API_KEY!,
Expand Down
2 changes: 1 addition & 1 deletion templates/multi-camera/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
"build": "tsc"
},
"dependencies": {
"@wave/sdk": "^2.0.0"
"@wave-av/sdk": "^2.0.0"
},
"devDependencies": {
"tsx": "^4.0.0",
Expand Down
2 changes: 1 addition & 1 deletion templates/multi-camera/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Wave } from "@wave/sdk";
import { Wave } from "@wave-av/sdk";

const wave = new Wave({
apiKey: process.env.WAVE_API_KEY!,
Expand Down
2 changes: 1 addition & 1 deletion templates/podcast/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
"build": "tsc"
},
"dependencies": {
"@wave/sdk": "^2.0.0"
"@wave-av/sdk": "^2.0.0"
},
"devDependencies": {
"tsx": "^4.0.0",
Expand Down
2 changes: 1 addition & 1 deletion templates/podcast/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Wave } from "@wave/sdk";
import { Wave } from "@wave-av/sdk";

const wave = new Wave({
apiKey: process.env.WAVE_API_KEY!,
Expand Down
2 changes: 1 addition & 1 deletion templates/srt-contribution/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
"build": "tsc"
},
"dependencies": {
"@wave/sdk": "^2.0.0"
"@wave-av/sdk": "^2.0.0"
},
"devDependencies": {
"tsx": "^4.0.0",
Expand Down
2 changes: 1 addition & 1 deletion templates/srt-contribution/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Wave } from "@wave/sdk";
import { Wave } from "@wave-av/sdk";

const wave = new Wave({
apiKey: process.env.WAVE_API_KEY!,
Expand Down
2 changes: 1 addition & 1 deletion templates/studio-plugin/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
"build": "tsc"
},
"dependencies": {
"@wave/sdk": "^2.0.0"
"@wave-av/sdk": "^2.0.0"
},
"devDependencies": {
"tsx": "^4.0.0",
Expand Down
2 changes: 1 addition & 1 deletion templates/studio-plugin/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Wave } from "@wave/sdk";
import { Wave } from "@wave-av/sdk";

const wave = new Wave({
apiKey: process.env.WAVE_API_KEY!,
Expand Down
2 changes: 1 addition & 1 deletion templates/webhook-handler/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
"build": "tsc"
},
"dependencies": {
"@wave/sdk": "^2.0.0",
"@wave-av/sdk": "^2.0.0",
"express": "^4.21.0"
},
"devDependencies": {
Expand Down
2 changes: 1 addition & 1 deletion templates/webhook-handler/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import express from "express";
import { Wave } from "@wave/sdk";
import { Wave } from "@wave-av/sdk";

const wave = new Wave({
apiKey: process.env.WAVE_API_KEY!,
Expand Down
2 changes: 1 addition & 1 deletion templates/webrtc-demo/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
"build": "tsc"
},
"dependencies": {
"@wave/sdk": "^2.0.0"
"@wave-av/sdk": "^2.0.0"
},
"devDependencies": {
"tsx": "^4.0.0",
Expand Down
2 changes: 1 addition & 1 deletion templates/webrtc-demo/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Wave } from "@wave/sdk";
import { Wave } from "@wave-av/sdk";

const wave = new Wave({
apiKey: process.env.WAVE_API_KEY!,
Expand Down
Loading