Skip to content
Merged
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
5 changes: 5 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
node_modules/
dist/
*.tgz
.env
.env.local
98 changes: 98 additions & 0 deletions RECOVERY.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
# Source recovery — where this code came from, and how you can check

This repository advertised itself as the source of `@wave-av/cli` while containing no source at
all. Eight versions were published to npm between 2026-04-03 and 2026-08-04 from a working copy
that was never committed. Anyone who ran `npm install @wave-av/cli` and followed the `repository`
link the package itself carries arrived at a README and a LICENSE, and **the code executing on
their machine existed in no public repository.**

This directory closes that gap. It is worth being precise about what kind of claim that is.

## The source was recovered, not reconstructed

Every published version of `@wave-av/cli` ships `dist/index.js.map`, and that sourcemap carries
`sourcesContent` — not merely the *names* of the original files but their **complete contents**,
pre-compilation. All 70 TypeScript files under `src/` were extracted from it verbatim. Nothing here
was inferred from the compiled bundle, hand-written to match, or reasoned backwards from types.

## The receipt: it rebuilds byte-for-byte

The claim "this is the source" is checkable, so it was checked, on **every** published version
rather than a sample:

| version | rebuilt `dist/index.js` vs published |
|---|---|
| 1.0.0 | **byte-identical** |
| 1.0.2 | **byte-identical** |
| 1.0.3 | **byte-identical** |
| 1.0.4 | **byte-identical** |
| 1.0.5 | **byte-identical** |
| 1.0.6 | **byte-identical** |
| 1.0.7 | **byte-identical** |
| 1.0.8 | **byte-identical** |

Re-derive any row yourself:

```sh
# 1. fetch what npm actually shipped
# NOTE: --registry does NOT override a scope mapping. If your npm config points @wave-av at a
# private registry, the plain form 404s against the wrong host and reads as "not published".
npm pack @wave-av/cli@1.0.8 --@wave-av:registry=https://registry.npmjs.org
tar -xzf wave-av-cli-1.0.8.tgz

# 2. extract the original sources out of the published sourcemap
node -e '
const m = require("./package/dist/index.js.map"), fs = require("fs"), p = require("path");
m.sources.forEach((s, i) => {
const f = p.join("recovered", s.replace(/^\.\.\//, ""));
fs.mkdirSync(p.dirname(f), { recursive: true });
fs.writeFileSync(f, m.sourcesContent[i]);
});
'

# 3. build and compare
npm ci --include=dev && npx tsup
cmp dist/index.js package/dist/index.js && echo IDENTICAL
Comment on lines +53 to +55

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Remediation recommended

6. Recovery command cannot install 🐞 Bug ⚙ Maintainability

RECOVERY.md instructs auditors to run npm ci, but the recovered repository has no npm lockfile.
The command stops before tsup runs, so the documented byte-comparison procedure is not reproducible
as written.
Agent Prompt
## Issue description
The documented recovery procedure uses `npm ci` without the lockfile that command requires.

## Issue Context
Commit the matching npm lockfile and validate the full commands from a clean checkout. If a lockfile cannot be recovered, document a tested package-manager/version procedure that actually runs and explain its reproducibility limits.

## Fix Focus Areas
- RECOVERY.md[34-58]
- package.json[57-79]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

```

`cmp` exits 0 silently on a match.

## What was authored for this recovery, and is therefore NOT recovered

Two files. The sourcemap contains source, not build configuration, so these were written to make
the tree buildable and are stated here rather than left to look like they came out of the artifact:

- **`tsconfig.json`** — a conventional strict ES2022/ESNext configuration.
- **`tsup.config.ts`** — entry `src/index.ts`, ESM, node18, sourcemap on, shebang banner.

They are not guesses in any loose sense: they are the settings under which the output matches the
published bytes exactly, on all eight versions. A different plausible configuration would have
produced a different bundle and the comparison above would have failed. But they were **written**,
not **recovered**, and conflating the two would be the same class of error this whole exercise
exists to correct.

`package.json` was taken from the published manifest, which npm preserves in full — including
`scripts` and `devDependencies`.

## Safety

The recovered tree was scanned with `gitleaks` before being proposed here: **no leaks found**,
~200KB across 70 files. This mattered more than it looks. The sourcemap has been publicly
downloadable since 2026-04-03, so a hardcoded credential inside it would have been a live
four-month exposure — a fact about the *published package*, not a risk created by recovering it.

## What this does and does not settle

It settles the question *"what source produced the code now running on their machine?"* for every
version of `@wave-av/cli`, with a receipt anyone outside WAVE can reproduce.

It does **not** settle it for `@wave-av/workflow-sdk`, whose seven published versions carry no
sourcemap at all. That package's source is recoverable only as a *reconstruction* — a tree that can
be proven to produce the published bytes, which is a genuinely weaker claim than a tree that did.
The two must not be recorded as the same thing.

## Provenance of the versions themselves

Recovering the source does not retroactively create the tags that never existed. Tagging each
published version against its own `cmp` receipt is tracked separately; until those tags exist, this
file is the record of where the code came from.
80 changes: 80 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
{
"name": "@wave-av/cli",
"version": "1.0.8",
"description": "WAVE CLI \u2014 manage live streams, productions, and video infrastructure from your terminal. 34 command groups.",
"main": "./dist/index.js",
"type": "module",
"bin": {
"wave": "./dist/index.js"
},
"files": [
"dist",
"templates",
"README.md",
"CHANGELOG.md"
],
"scripts": {
"build": "tsup",
"dev": "tsup --watch",
"type-check": "tsc --noEmit",
"test": "vitest run",
"test:watch": "vitest",
"lint": "eslint src/",
"prepublishOnly": "npm run build"
Comment on lines +20 to +23

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Action required

1. lint script missing eslint 📘 Rule violation ✧ Quality

package.json defines lint as eslint src/, but eslint is not listed in dependencies or
devDependencies, so npm run lint will fail in CI on a clean install. This introduces a
deterministic new CI failure risk for the configured lint command.
Agent Prompt
## Issue description
`npm run lint` is configured to run `eslint src/`, but the `eslint` package is not installed via `package.json`, causing lint to fail in CI/clean environments.

## Issue Context
Compliance requires that changed code must not introduce new lint/type/test failures in the existing CI configuration. Since the `lint` script invokes `eslint`, the repo should declare `eslint` (and any required config) so the command can run successfully.

## Fix Focus Areas
- package.json[16-23]
- package.json[71-79]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

},
"keywords": [
"wave",
"cli",
"streaming",
"video",
"broadcast",
"production",
"live",
"webrtc",
"srt",
"rtmp",
"terminal"
],
"author": "WAVE Inc. <sdk@wave.online>",
"license": "MIT",
"repository": {
"type": "git",
"url": "https://github.com/wave-av/cli.git",
"directory": "."
},
"homepage": "https://docs.wave.online/cli",
"bugs": {
"url": "https://github.com/wave-av/cli/issues"
},
"engines": {
"node": ">=18.0.0"
},
"publishConfig": {
"access": "public",
"provenance": false,
"registry": "https://registry.npmjs.org/"
},
"dependencies": {
"@wave-av/sdk": "^2.0.11",
"chalk": "^5.4.1",
"cli-table3": "^0.6.5",
"commander": "^13.1.0",
"conf": "^13.1.0",
"inquirer": "^12.3.2",
"keytar": "^7.9.0",
"open": "^10.1.0",
"ora": "^8.2.0",
"ws": "^8.18.0",
"yaml": "^2.7.0",
"zod": "^3.22.0"
},
"devDependencies": {
"@sentry/node": "^9.4.0",
"@types/inquirer": "^9.0.7",
"@types/node": "^22.13.0",
"@types/ws": "^8.5.14",
"tsup": "^8.0.0",
"typescript": "^5.9.3",
"vitest": "^4.0.16"
}
}
174 changes: 174 additions & 0 deletions src/cli.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,174 @@
import { Command } from "commander";
import chalk from "chalk";
import { registerAuthCommands } from "./commands/auth/index.js";
import { registerOrgCommands } from "./commands/org/index.js";
import { registerConfigCommands } from "./commands/config/index.js";
import { registerStreamCommands } from "./commands/stream/index.js";
import { registerStudioCommands } from "./commands/studio/index.js";
import { registerClipCommands } from "./commands/clips/index.js";
import { registerEditorCommands } from "./commands/editor/index.js";
import { registerVoiceCommands } from "./commands/voice/index.js";
import { registerPhoneCommands } from "./commands/phone/index.js";
import { registerCollabCommands } from "./commands/collab/index.js";
import { registerCaptionsCommands } from "./commands/captions/index.js";
import { registerChaptersCommands } from "./commands/chapters/index.js";
import { registerAICommands } from "./commands/ai/index.js";
import { registerTranscribeCommands } from "./commands/transcribe/index.js";
import { registerSentimentCommands } from "./commands/sentiment/index.js";
import { registerSearchCommands } from "./commands/search/index.js";
import { registerSceneCommands } from "./commands/scene/index.js";
import { registerFleetCommands } from "./commands/fleet/index.js";
import { registerGhostCommands } from "./commands/ghost/index.js";
import { registerMeshCommands } from "./commands/mesh/index.js";
import { registerEdgeCommands } from "./commands/edge/index.js";
import { registerAnalyticsCommands } from "./commands/analytics/index.js";
import { registerPrismCommands } from "./commands/prism/index.js";
import { registerZoomCommands } from "./commands/zoom/index.js";
import { registerVaultCommands } from "./commands/vault/index.js";
import { registerMarketplaceCommands } from "./commands/marketplace/index.js";
import { registerConnectCommands } from "./commands/connect/index.js";
import { registerDistributionCommands } from "./commands/distribution/index.js";
import { registerDesktopCommands } from "./commands/desktop/index.js";
import { registerSignageCommands } from "./commands/signage/index.js";
import { registerQrCommands } from "./commands/qr/index.js";
import { registerAudienceCommands } from "./commands/audience/index.js";
import { registerCreatorCommands } from "./commands/creator/index.js";
import { registerPodcastCommands } from "./commands/podcast/index.js";
import { registerSlidesCommands } from "./commands/slides/index.js";
import { registerUsbCommands } from "./commands/usb/index.js";
import { registerNotifyCommands } from "./commands/notify/index.js";
import { registerDrmCommands } from "./commands/drm/index.js";
import { registerBillingCommands } from "./commands/billing/index.js";
import { registerListenCommands } from "./commands/listen/index.js";
import { registerLogsCommands } from "./commands/logs/index.js";
import { registerTriggerCommands } from "./commands/trigger/index.js";
import { registerDevCommands } from "./commands/dev/index.js";
import { registerOpenCommands } from "./commands/open/index.js";
import { registerInitCommands } from "./commands/init/index.js";
import { registerAdminCommands } from "./commands/admin/index.js";
import { registerDoctorCommands } from "./commands/doctor/index.js";
import { registerStatusCommands } from "./commands/status/index.js";
import { registerCompletionCommands } from "./commands/completion/index.js";
import { registerApiCommands } from "./commands/api/index.js";
import { registerLinkCommands } from "./commands/link/index.js";
import { detectEnvironment } from "./lib/environment.js";

function printBanner(): void {
// WAVE brand gradient: blue (#3366FF) -> purple (#7B41E8) -> cyan (#33BBCC)
const b = chalk.hex("#3366FF"); // primary blue
const p = chalk.hex("#7B41E8"); // secondary purple
const c = chalk.hex("#33BBCC"); // accent cyan
const d = chalk.dim;

console.log("");
console.log(` ${b("██╗ ██╗")} ${p("█████╗ ")} ${p("██╗ ██╗")} ${c("███████╗")}`);
console.log(` ${b("██║ ██║")} ${p("██╔══██╗")} ${p("██║ ██║")} ${c("██╔════╝")}`);
console.log(` ${b("██║ █╗ ██║")} ${p("███████║")} ${p("██║ ██║")} ${c("█████╗ ")}`);
console.log(` ${b("██║███╗██║")} ${p("██╔══██║")} ${p("╚██╗ ██╔╝")} ${c("██╔══╝ ")}`);
console.log(` ${b("╚███╔███╔╝")} ${p("██║ ██║")} ${p(" ╚████╔╝ ")} ${c("███████╗")}`);
console.log(` ${b(" ╚══╝╚══╝ ")} ${p("╚═╝ ╚═╝")} ${p(" ╚═══╝ ")} ${c("╚══════╝")}`);
console.log("");
console.log(` ${d("Enterprise Streaming Platform")} ${chalk.hex("#555")("v1.0.0")}`);
console.log(` ${d("─".repeat(45))}`);
console.log("");
}

export function createProgram(): Command {
const program = new Command();

program
.name("wave")
.description("WAVE CLI - Command-line interface for the WAVE streaming platform")
.version("1.0.0", "-v, --version")

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🟡 The CLI reports the wrong version number

The version reported to users is hardcoded to an old value (.version("1.0.0", "-v, --version") at src/cli.ts:82) while the package being shipped is 1.0.8, so wave --version misinforms anyone checking which release they have.
Impact: Bug reports and support requests will cite a version that does not match the installed release.

Three hardcoded 1.0.0 strings vs package.json 1.0.8

package.json:3 declares "version": "1.0.8". The literal 1.0.0 is hardcoded in the version flag (src/cli.ts:82), the banner (src/cli.ts:71), the raw-request User-Agent (src/commands/api/index.ts:34) and the SDK header X-Wave-CLI-Version (src/lib/api-client.ts:49), so server-side telemetry also attributes all traffic to 1.0.0. Reading the version from the manifest at build time would keep these in sync.

Open in Devin Review

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

.option("-o, --output <format>", "Output format: table, json, yaml", "table")
.option("--project <name>", "Override project context")
.option("--org <id>", "Override organization")
.option("-c, --confirm", "Skip confirmation prompts")
.option("--no-color", "Disable colored output")
.option("--debug", "Verbose debug logging");

// Auth & Config
registerAuthCommands(program);
registerOrgCommands(program);
registerConfigCommands(program);
registerInitCommands(program);
registerLinkCommands(program);

// Core APIs (P1)
registerStreamCommands(program);
registerStudioCommands(program);

// Production (P1)
registerClipCommands(program);
registerEditorCommands(program);
registerVoiceCommands(program);
registerPhoneCommands(program);
registerCollabCommands(program);
registerCaptionsCommands(program);
registerChaptersCommands(program);
registerAICommands(program);
registerTranscribeCommands(program);

// Intelligence (P2)
registerSentimentCommands(program);
registerSearchCommands(program);
registerSceneCommands(program);

// Enterprise (P2)
registerFleetCommands(program);
registerGhostCommands(program);
registerMeshCommands(program);
registerEdgeCommands(program);
registerAnalyticsCommands(program);
registerPrismCommands(program);
registerZoomCommands(program);

// Content & Commerce (P3)
registerVaultCommands(program);
registerMarketplaceCommands(program);
registerConnectCommands(program);
registerDistributionCommands(program);
registerDesktopCommands(program);
registerSignageCommands(program);
registerQrCommands(program);
registerAudienceCommands(program);
registerCreatorCommands(program);

// Specialized (P4)
registerPodcastCommands(program);
registerSlidesCommands(program);
registerUsbCommands(program);

// Cross-cutting
registerNotifyCommands(program);
registerDrmCommands(program);
registerBillingCommands(program);

// Developer tools
registerListenCommands(program);
registerLogsCommands(program);
registerTriggerCommands(program);
registerDevCommands(program);
registerOpenCommands(program);

// Admin
registerAdminCommands(program);

// Diagnostics & utilities
registerDoctorCommands(program);
registerStatusCommands(program);
registerCompletionCommands(program);
registerApiCommands(program);

// Skip banner for AI agents and CI (they prefer clean output)
const env = detectEnvironment();
if (!env.isAgent && !env.isCI) {
const originalHelp = program.helpInformation.bind(program);
program.helpInformation = function () {
printBanner();
return originalHelp();
};
}

return program;
}
Loading
Loading