@wave-av/cli 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +178 -0
- package/dist/index.js +4034 -0
- package/dist/index.js.map +1 -0
- package/package.json +73 -0
- package/templates/api-integration/.env.example +2 -0
- package/templates/api-integration/package.json +17 -0
- package/templates/api-integration/src/index.ts +24 -0
- package/templates/blank/.env.example +1 -0
- package/templates/blank/package.json +17 -0
- package/templates/blank/src/index.ts +16 -0
- package/templates/multi-camera/.env.example +1 -0
- package/templates/multi-camera/package.json +17 -0
- package/templates/multi-camera/src/index.ts +49 -0
- package/templates/podcast/.env.example +1 -0
- package/templates/podcast/package.json +17 -0
- package/templates/podcast/src/index.ts +33 -0
- package/templates/srt-contribution/.env.example +1 -0
- package/templates/srt-contribution/package.json +17 -0
- package/templates/srt-contribution/src/index.ts +29 -0
- package/templates/studio-plugin/.env.example +2 -0
- package/templates/studio-plugin/package.json +17 -0
- package/templates/studio-plugin/src/index.ts +32 -0
- package/templates/webhook-handler/.env.example +3 -0
- package/templates/webhook-handler/package.json +19 -0
- package/templates/webhook-handler/src/index.ts +52 -0
- package/templates/webrtc-demo/.env.example +1 -0
- package/templates/webrtc-demo/package.json +17 -0
- package/templates/webrtc-demo/src/index.ts +31 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,4034 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// src/cli.ts
|
|
4
|
+
import { Command } from "commander";
|
|
5
|
+
import chalk54 from "chalk";
|
|
6
|
+
|
|
7
|
+
// src/commands/auth/index.ts
|
|
8
|
+
import chalk5 from "chalk";
|
|
9
|
+
|
|
10
|
+
// src/lib/errors.ts
|
|
11
|
+
import chalk2 from "chalk";
|
|
12
|
+
import { WaveError, RateLimitError } from "@wave-av/sdk";
|
|
13
|
+
|
|
14
|
+
// src/lib/exit-codes.ts
|
|
15
|
+
var EXIT_CODES = {
|
|
16
|
+
SUCCESS: 0,
|
|
17
|
+
GENERAL_ERROR: 1,
|
|
18
|
+
AUTH_REQUIRED: 2,
|
|
19
|
+
AUTH_EXPIRED: 3,
|
|
20
|
+
NOT_FOUND: 4,
|
|
21
|
+
VALIDATION_ERROR: 5,
|
|
22
|
+
RATE_LIMITED: 6,
|
|
23
|
+
PERMISSION_DENIED: 7,
|
|
24
|
+
NETWORK_ERROR: 8,
|
|
25
|
+
CONFIG_ERROR: 9,
|
|
26
|
+
TIMEOUT: 10
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
// src/lib/environment.ts
|
|
30
|
+
function detectEnvironment() {
|
|
31
|
+
const isCI = Boolean(
|
|
32
|
+
process.env["CI"] || process.env["GITHUB_ACTIONS"] || process.env["VERCEL"] || process.env["BUILDKITE"] || process.env["GITLAB_CI"] || process.env["CIRCLECI"]
|
|
33
|
+
);
|
|
34
|
+
const isAgent = Boolean(
|
|
35
|
+
process.env["WAVE_AGENT"] || process.env["CLAUDE_CODE"] || process.env["CURSOR_SESSION"] || process.env["AIDER_SESSION"] || process.env["CONTINUE_SESSION"]
|
|
36
|
+
);
|
|
37
|
+
const isInteractive = Boolean(process.stdin.isTTY) && !isCI && !isAgent;
|
|
38
|
+
const preferJson = !isInteractive || process.env["WAVE_OUTPUT_FORMAT"] === "json" || isAgent;
|
|
39
|
+
const supportsColor = process.env["WAVE_NO_COLOR"] !== "1" && process.env["NO_COLOR"] === void 0 && (process.env["FORCE_COLOR"] !== void 0 || Boolean(process.stdout.isTTY));
|
|
40
|
+
const agentName = process.env["WAVE_AGENT_NAME"] || (process.env["CLAUDE_CODE"] ? "claude-code" : void 0) || (process.env["CURSOR_SESSION"] ? "cursor" : void 0);
|
|
41
|
+
return { isCI, isAgent, isInteractive, preferJson, supportsColor, agentName };
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// src/lib/suggestions.ts
|
|
45
|
+
import chalk from "chalk";
|
|
46
|
+
var AUTH_SUGGESTIONS = [
|
|
47
|
+
{ message: "Authenticate with WAVE", command: "wave login" },
|
|
48
|
+
{ message: "Use an API key directly", command: "wave login --api-key <your-key>" },
|
|
49
|
+
{ message: "Set env var for CI/CD", command: "export WAVE_API_KEY=wave_live_..." }
|
|
50
|
+
];
|
|
51
|
+
var RATE_LIMIT_SUGGESTIONS = [
|
|
52
|
+
{ message: "Check your current limits", command: "wave billing limits" },
|
|
53
|
+
{ message: "Upgrade your plan", command: "wave billing upgrade" },
|
|
54
|
+
{ message: "Wait and retry (the CLI does this automatically)" }
|
|
55
|
+
];
|
|
56
|
+
function formatSuggestion(suggestion) {
|
|
57
|
+
const parts = [` ${chalk.dim(">")} ${suggestion.message}`];
|
|
58
|
+
if (suggestion.command) {
|
|
59
|
+
parts.push(` ${chalk.cyan("$")} ${chalk.bold(suggestion.command)}`);
|
|
60
|
+
}
|
|
61
|
+
if (suggestion.docs) {
|
|
62
|
+
parts.push(` ${chalk.dim(suggestion.docs)}`);
|
|
63
|
+
}
|
|
64
|
+
return parts.join("\n");
|
|
65
|
+
}
|
|
66
|
+
function getAuthSuggestions() {
|
|
67
|
+
const header = chalk.yellow("Not authenticated. Try one of:");
|
|
68
|
+
const suggestions = AUTH_SUGGESTIONS.map(formatSuggestion).join("\n\n");
|
|
69
|
+
return `${header}
|
|
70
|
+
|
|
71
|
+
${suggestions}`;
|
|
72
|
+
}
|
|
73
|
+
function getRateLimitSuggestions() {
|
|
74
|
+
const header = chalk.yellow("Rate limited. Options:");
|
|
75
|
+
return `${header}
|
|
76
|
+
|
|
77
|
+
${RATE_LIMIT_SUGGESTIONS.map(formatSuggestion).join("\n\n")}`;
|
|
78
|
+
}
|
|
79
|
+
function toStructuredError(code, message, exitCode, suggestions, requestId) {
|
|
80
|
+
return {
|
|
81
|
+
error: {
|
|
82
|
+
code,
|
|
83
|
+
message,
|
|
84
|
+
exit_code: exitCode,
|
|
85
|
+
suggestions,
|
|
86
|
+
request_id: requestId
|
|
87
|
+
}
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// src/lib/errors.ts
|
|
92
|
+
function formatCLIError(error) {
|
|
93
|
+
const env = detectEnvironment();
|
|
94
|
+
if (error instanceof RateLimitError) {
|
|
95
|
+
if (env.preferJson) {
|
|
96
|
+
const structured = toStructuredError(
|
|
97
|
+
"RATE_LIMITED",
|
|
98
|
+
`Rate limit exceeded. Retry after ${error.retryAfter}ms.`,
|
|
99
|
+
EXIT_CODES.RATE_LIMITED,
|
|
100
|
+
[
|
|
101
|
+
{ message: "Check your limits", command: "wave billing limits" },
|
|
102
|
+
{ message: "Upgrade your plan", command: "wave billing upgrade" }
|
|
103
|
+
],
|
|
104
|
+
error.requestId
|
|
105
|
+
);
|
|
106
|
+
return { message: JSON.stringify(structured, null, 2), exitCode: EXIT_CODES.RATE_LIMITED };
|
|
107
|
+
}
|
|
108
|
+
return {
|
|
109
|
+
message: getRateLimitSuggestions(),
|
|
110
|
+
exitCode: EXIT_CODES.RATE_LIMITED
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
if (error instanceof WaveError) {
|
|
114
|
+
const exitCode = error.statusCode === 401 ? EXIT_CODES.AUTH_REQUIRED : error.statusCode === 403 ? EXIT_CODES.PERMISSION_DENIED : error.statusCode === 404 ? EXIT_CODES.NOT_FOUND : error.statusCode === 422 ? EXIT_CODES.VALIDATION_ERROR : error.statusCode === 429 ? EXIT_CODES.RATE_LIMITED : EXIT_CODES.GENERAL_ERROR;
|
|
115
|
+
if (env.preferJson) {
|
|
116
|
+
const suggestions = error.statusCode === 401 ? [{ message: "Authenticate", command: "wave login" }, { message: "Use API key", command: "export WAVE_API_KEY=..." }] : error.statusCode === 404 ? [{ message: "List resources", command: "wave <resource> list" }] : [];
|
|
117
|
+
const structured = toStructuredError(
|
|
118
|
+
error.code,
|
|
119
|
+
error.message,
|
|
120
|
+
exitCode,
|
|
121
|
+
suggestions,
|
|
122
|
+
error.requestId
|
|
123
|
+
);
|
|
124
|
+
return { message: JSON.stringify(structured, null, 2), exitCode };
|
|
125
|
+
}
|
|
126
|
+
if (error.statusCode === 401) {
|
|
127
|
+
return { message: getAuthSuggestions(), exitCode };
|
|
128
|
+
}
|
|
129
|
+
const lines = [
|
|
130
|
+
chalk2.red(error.message),
|
|
131
|
+
chalk2.dim(` Code: ${error.code} | Status: ${error.statusCode}`),
|
|
132
|
+
error.requestId ? chalk2.dim(` Request ID: ${error.requestId}`) : "",
|
|
133
|
+
error.retryable ? chalk2.yellow(" This error is retryable.") : ""
|
|
134
|
+
].filter(Boolean);
|
|
135
|
+
return { message: lines.join("\n"), exitCode };
|
|
136
|
+
}
|
|
137
|
+
if (error instanceof Error) {
|
|
138
|
+
if (env.preferJson) {
|
|
139
|
+
const structured = toStructuredError(
|
|
140
|
+
"CLI_ERROR",
|
|
141
|
+
error.message,
|
|
142
|
+
EXIT_CODES.GENERAL_ERROR,
|
|
143
|
+
[]
|
|
144
|
+
);
|
|
145
|
+
return { message: JSON.stringify(structured, null, 2), exitCode: EXIT_CODES.GENERAL_ERROR };
|
|
146
|
+
}
|
|
147
|
+
return {
|
|
148
|
+
message: chalk2.red(`Error: ${error.message}`),
|
|
149
|
+
exitCode: EXIT_CODES.GENERAL_ERROR
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
return {
|
|
153
|
+
message: chalk2.red(`Unexpected error: ${String(error)}`),
|
|
154
|
+
exitCode: EXIT_CODES.GENERAL_ERROR
|
|
155
|
+
};
|
|
156
|
+
}
|
|
157
|
+
function wrapCommand(fn) {
|
|
158
|
+
return async (...args) => {
|
|
159
|
+
try {
|
|
160
|
+
await fn(...args);
|
|
161
|
+
} catch (error) {
|
|
162
|
+
const { message, exitCode } = formatCLIError(error);
|
|
163
|
+
console.error(message);
|
|
164
|
+
process.exit(exitCode);
|
|
165
|
+
}
|
|
166
|
+
};
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
// src/lib/output/table.ts
|
|
170
|
+
import Table from "cli-table3";
|
|
171
|
+
function formatTable(data, columns) {
|
|
172
|
+
if (data.length === 0) {
|
|
173
|
+
return "No results found.";
|
|
174
|
+
}
|
|
175
|
+
const cols = columns ?? Object.keys(data[0] ?? {});
|
|
176
|
+
const table = new Table({
|
|
177
|
+
head: cols,
|
|
178
|
+
style: { head: ["cyan"] }
|
|
179
|
+
});
|
|
180
|
+
for (const row of data) {
|
|
181
|
+
table.push(
|
|
182
|
+
cols.map((col) => {
|
|
183
|
+
const val = row[col];
|
|
184
|
+
if (val === null || val === void 0) return "";
|
|
185
|
+
if (typeof val === "object") return JSON.stringify(val);
|
|
186
|
+
const str = String(val);
|
|
187
|
+
return str.length > 60 ? str.slice(0, 57) + "..." : str;
|
|
188
|
+
})
|
|
189
|
+
);
|
|
190
|
+
}
|
|
191
|
+
return table.toString();
|
|
192
|
+
}
|
|
193
|
+
function formatDetail(data) {
|
|
194
|
+
const table = new Table({
|
|
195
|
+
style: { head: ["cyan"] }
|
|
196
|
+
});
|
|
197
|
+
for (const [key, value] of Object.entries(data)) {
|
|
198
|
+
const val = value === null || value === void 0 ? "" : typeof value === "object" ? JSON.stringify(value, null, 2) : String(value);
|
|
199
|
+
table.push({ [key]: val });
|
|
200
|
+
}
|
|
201
|
+
return table.toString();
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
// src/lib/output/json.ts
|
|
205
|
+
import chalk3 from "chalk";
|
|
206
|
+
function formatJson(data, colorize = true) {
|
|
207
|
+
const json = JSON.stringify(data, null, 2);
|
|
208
|
+
if (!colorize) return json;
|
|
209
|
+
return json.replace(/("(?:\\.|[^"\\])*")\s*:/g, (_match, key) => `${chalk3.cyan(key)}:`);
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
// src/lib/output/yaml.ts
|
|
213
|
+
import { stringify } from "yaml";
|
|
214
|
+
function formatYaml(data) {
|
|
215
|
+
return stringify(data, { indent: 2 });
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
// src/lib/output/spinner.ts
|
|
219
|
+
import ora from "ora";
|
|
220
|
+
async function withSpinner(message, fn) {
|
|
221
|
+
const spinner = ora(message).start();
|
|
222
|
+
try {
|
|
223
|
+
const result = await fn();
|
|
224
|
+
spinner.succeed();
|
|
225
|
+
return result;
|
|
226
|
+
} catch (error) {
|
|
227
|
+
spinner.fail();
|
|
228
|
+
throw error;
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
// src/lib/output/confirm.ts
|
|
233
|
+
import inquirer from "inquirer";
|
|
234
|
+
async function confirmDestructive(action, resource, opts) {
|
|
235
|
+
if (opts.confirm) return true;
|
|
236
|
+
const { confirmed } = await inquirer.prompt([
|
|
237
|
+
{
|
|
238
|
+
type: "confirm",
|
|
239
|
+
name: "confirmed",
|
|
240
|
+
message: `Are you sure you want to ${action} ${resource}?`,
|
|
241
|
+
default: false
|
|
242
|
+
}
|
|
243
|
+
]);
|
|
244
|
+
return confirmed;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
// src/lib/output/index.ts
|
|
248
|
+
function formatOutput(data, opts = {}) {
|
|
249
|
+
const format = opts.output ?? "table";
|
|
250
|
+
switch (format) {
|
|
251
|
+
case "json":
|
|
252
|
+
console.log(formatJson(data));
|
|
253
|
+
break;
|
|
254
|
+
case "yaml":
|
|
255
|
+
console.log(formatYaml(data));
|
|
256
|
+
break;
|
|
257
|
+
case "table":
|
|
258
|
+
default:
|
|
259
|
+
if (Array.isArray(data)) {
|
|
260
|
+
console.log(formatTable(data));
|
|
261
|
+
} else if (data && typeof data === "object") {
|
|
262
|
+
console.log(formatDetail(data));
|
|
263
|
+
} else {
|
|
264
|
+
console.log(String(data));
|
|
265
|
+
}
|
|
266
|
+
break;
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
// src/lib/auth/keychain.ts
|
|
271
|
+
import { readFile, writeFile, mkdir } from "fs/promises";
|
|
272
|
+
import { existsSync } from "fs";
|
|
273
|
+
import { join } from "path";
|
|
274
|
+
import { homedir } from "os";
|
|
275
|
+
var SERVICE_NAME = "wave-cli";
|
|
276
|
+
var keytarModule = null;
|
|
277
|
+
var keytarChecked = false;
|
|
278
|
+
async function getKeytar() {
|
|
279
|
+
if (keytarChecked) return keytarModule;
|
|
280
|
+
keytarChecked = true;
|
|
281
|
+
try {
|
|
282
|
+
keytarModule = await import(
|
|
283
|
+
/* webpackIgnore: true */
|
|
284
|
+
"keytar"
|
|
285
|
+
);
|
|
286
|
+
return keytarModule;
|
|
287
|
+
} catch {
|
|
288
|
+
return null;
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
async function storeApiKey(project, key) {
|
|
292
|
+
const keytar = await getKeytar();
|
|
293
|
+
if (keytar) {
|
|
294
|
+
await keytar.setPassword(SERVICE_NAME, `apikey:${project}`, key);
|
|
295
|
+
} else {
|
|
296
|
+
await storeToFile(project, key);
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
async function getApiKey(project) {
|
|
300
|
+
const keytar = await getKeytar();
|
|
301
|
+
if (keytar) {
|
|
302
|
+
return keytar.getPassword(SERVICE_NAME, `apikey:${project}`);
|
|
303
|
+
}
|
|
304
|
+
return getFromFile(project);
|
|
305
|
+
}
|
|
306
|
+
async function deleteApiKey(project) {
|
|
307
|
+
const keytar = await getKeytar();
|
|
308
|
+
if (keytar) {
|
|
309
|
+
await keytar.deletePassword(SERVICE_NAME, `apikey:${project}`);
|
|
310
|
+
} else {
|
|
311
|
+
await deleteFromFile(project);
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
async function deleteAllKeys() {
|
|
315
|
+
const keytar = await getKeytar();
|
|
316
|
+
if (keytar) {
|
|
317
|
+
const credentials = await keytar.findCredentials(SERVICE_NAME);
|
|
318
|
+
for (const cred of credentials) {
|
|
319
|
+
await keytar.deletePassword(SERVICE_NAME, cred.account);
|
|
320
|
+
}
|
|
321
|
+
} else {
|
|
322
|
+
await deleteAllFromFile();
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
var CRED_FILE = join(homedir(), ".wave", "credentials.json");
|
|
326
|
+
async function loadCredentials() {
|
|
327
|
+
try {
|
|
328
|
+
if (!existsSync(CRED_FILE)) return {};
|
|
329
|
+
const raw = await readFile(CRED_FILE, "utf-8");
|
|
330
|
+
return JSON.parse(raw);
|
|
331
|
+
} catch {
|
|
332
|
+
return {};
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
async function saveCredentials(creds) {
|
|
336
|
+
const dir = join(homedir(), ".wave");
|
|
337
|
+
if (!existsSync(dir)) {
|
|
338
|
+
await mkdir(dir, { recursive: true });
|
|
339
|
+
}
|
|
340
|
+
await writeFile(CRED_FILE, JSON.stringify(creds, null, 2), {
|
|
341
|
+
mode: 384
|
|
342
|
+
});
|
|
343
|
+
}
|
|
344
|
+
async function storeToFile(project, key) {
|
|
345
|
+
const creds = await loadCredentials();
|
|
346
|
+
creds[project] = key;
|
|
347
|
+
await saveCredentials(creds);
|
|
348
|
+
}
|
|
349
|
+
async function getFromFile(project) {
|
|
350
|
+
const creds = await loadCredentials();
|
|
351
|
+
return creds[project] ?? null;
|
|
352
|
+
}
|
|
353
|
+
async function deleteFromFile(project) {
|
|
354
|
+
const creds = await loadCredentials();
|
|
355
|
+
delete creds[project];
|
|
356
|
+
await saveCredentials(creds);
|
|
357
|
+
}
|
|
358
|
+
async function deleteAllFromFile() {
|
|
359
|
+
await saveCredentials({});
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
// src/lib/config/manager.ts
|
|
363
|
+
import { readFile as readFile2, writeFile as writeFile2, mkdir as mkdir2 } from "fs/promises";
|
|
364
|
+
import { existsSync as existsSync2 } from "fs";
|
|
365
|
+
import { homedir as homedir2 } from "os";
|
|
366
|
+
import { join as join2 } from "path";
|
|
367
|
+
|
|
368
|
+
// src/lib/config/schema.ts
|
|
369
|
+
import { z } from "zod";
|
|
370
|
+
var projectConfigSchema = z.object({
|
|
371
|
+
organizationId: z.string(),
|
|
372
|
+
organizationName: z.string(),
|
|
373
|
+
baseUrl: z.string().url().optional(),
|
|
374
|
+
region: z.string().optional()
|
|
375
|
+
});
|
|
376
|
+
var waveConfigSchema = z.object({
|
|
377
|
+
version: z.string().default("1.0.0"),
|
|
378
|
+
currentProject: z.string().default("default"),
|
|
379
|
+
projects: z.record(z.string(), projectConfigSchema).default({}),
|
|
380
|
+
defaults: z.object({
|
|
381
|
+
outputFormat: z.enum(["table", "json", "yaml"]).default("table"),
|
|
382
|
+
protocol: z.string().optional(),
|
|
383
|
+
color: z.enum(["auto", "on", "off"]).default("auto")
|
|
384
|
+
}).default({}),
|
|
385
|
+
telemetry: z.object({
|
|
386
|
+
enabled: z.boolean().default(false),
|
|
387
|
+
errorReporting: z.boolean().default(false)
|
|
388
|
+
}).default({})
|
|
389
|
+
});
|
|
390
|
+
function getDefaultConfig() {
|
|
391
|
+
return waveConfigSchema.parse({});
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
// src/lib/config/manager.ts
|
|
395
|
+
var CONFIG_DIR = join2(homedir2(), ".wave");
|
|
396
|
+
var CONFIG_FILE = join2(CONFIG_DIR, "config.json");
|
|
397
|
+
function getConfigPath() {
|
|
398
|
+
return CONFIG_FILE;
|
|
399
|
+
}
|
|
400
|
+
async function loadConfig() {
|
|
401
|
+
try {
|
|
402
|
+
if (!existsSync2(CONFIG_FILE)) {
|
|
403
|
+
const defaults = getDefaultConfig();
|
|
404
|
+
await saveConfig(defaults);
|
|
405
|
+
return defaults;
|
|
406
|
+
}
|
|
407
|
+
const raw = await readFile2(CONFIG_FILE, "utf-8");
|
|
408
|
+
const parsed = JSON.parse(raw);
|
|
409
|
+
return waveConfigSchema.parse(parsed);
|
|
410
|
+
} catch {
|
|
411
|
+
const defaults = getDefaultConfig();
|
|
412
|
+
await saveConfig(defaults);
|
|
413
|
+
return defaults;
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
async function saveConfig(config) {
|
|
417
|
+
const validated = waveConfigSchema.parse(config);
|
|
418
|
+
if (!existsSync2(CONFIG_DIR)) {
|
|
419
|
+
await mkdir2(CONFIG_DIR, { recursive: true });
|
|
420
|
+
}
|
|
421
|
+
const tmpPath = `${CONFIG_FILE}.tmp`;
|
|
422
|
+
await writeFile2(tmpPath, JSON.stringify(validated, null, 2) + "\n", "utf-8");
|
|
423
|
+
const { rename } = await import("fs/promises");
|
|
424
|
+
await rename(tmpPath, CONFIG_FILE);
|
|
425
|
+
}
|
|
426
|
+
async function updateConfig(updater) {
|
|
427
|
+
const current = await loadConfig();
|
|
428
|
+
const updated = updater(current);
|
|
429
|
+
await saveConfig(updated);
|
|
430
|
+
return updated;
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
// src/lib/auth/device-flow.ts
|
|
434
|
+
import open from "open";
|
|
435
|
+
import chalk4 from "chalk";
|
|
436
|
+
var POLL_ERROR_AUTHORIZATION_PENDING = "authorization_pending";
|
|
437
|
+
var POLL_ERROR_SLOW_DOWN = "slow_down";
|
|
438
|
+
var POLL_ERROR_EXPIRED_TOKEN = "expired_token";
|
|
439
|
+
var POLL_ERROR_ACCESS_DENIED = "access_denied";
|
|
440
|
+
var SLOW_DOWN_INCREMENT_MS = 5e3;
|
|
441
|
+
async function startDeviceAuth(baseUrl) {
|
|
442
|
+
const url = `${baseUrl}/api/oauth/device/authorize`;
|
|
443
|
+
const response = await fetch(url, {
|
|
444
|
+
method: "POST",
|
|
445
|
+
headers: { "Content-Type": "application/json" },
|
|
446
|
+
body: JSON.stringify({
|
|
447
|
+
client_id: "wave-cli",
|
|
448
|
+
scope: "openid profile email offline_access"
|
|
449
|
+
})
|
|
450
|
+
});
|
|
451
|
+
if (!response.ok) {
|
|
452
|
+
const text = await response.text();
|
|
453
|
+
throw new Error(`Device authorization request failed (${response.status}): ${text}`);
|
|
454
|
+
}
|
|
455
|
+
const data = await response.json();
|
|
456
|
+
console.log();
|
|
457
|
+
console.log(chalk4.bold(" Open this URL in your browser to authenticate:"));
|
|
458
|
+
console.log();
|
|
459
|
+
console.log(` ${chalk4.cyan.underline(data.verification_uri)}`);
|
|
460
|
+
console.log();
|
|
461
|
+
console.log(chalk4.bold(" Enter this code when prompted:"));
|
|
462
|
+
console.log();
|
|
463
|
+
console.log(` ${chalk4.bold.yellow(data.user_code)}`);
|
|
464
|
+
console.log();
|
|
465
|
+
const verificationUrl = data.verification_uri_complete ?? data.verification_uri;
|
|
466
|
+
try {
|
|
467
|
+
await open(verificationUrl);
|
|
468
|
+
console.log(chalk4.gray(" Browser opened automatically."));
|
|
469
|
+
} catch {
|
|
470
|
+
console.log(
|
|
471
|
+
chalk4.gray(" Could not open browser automatically. Please open the URL manually.")
|
|
472
|
+
);
|
|
473
|
+
}
|
|
474
|
+
console.log();
|
|
475
|
+
console.log(chalk4.gray(" Waiting for authentication..."));
|
|
476
|
+
console.log();
|
|
477
|
+
return data;
|
|
478
|
+
}
|
|
479
|
+
async function pollForToken(baseUrl, deviceCode, interval, expiresIn) {
|
|
480
|
+
const url = `${baseUrl}/api/oauth/device/token`;
|
|
481
|
+
const deadline = Date.now() + expiresIn * 1e3;
|
|
482
|
+
let pollIntervalMs = interval * 1e3;
|
|
483
|
+
while (Date.now() < deadline) {
|
|
484
|
+
await sleep(pollIntervalMs);
|
|
485
|
+
const response = await fetch(url, {
|
|
486
|
+
method: "POST",
|
|
487
|
+
headers: { "Content-Type": "application/json" },
|
|
488
|
+
body: JSON.stringify({
|
|
489
|
+
grant_type: "urn:ietf:params:oauth:grant-type:device_code",
|
|
490
|
+
device_code: deviceCode,
|
|
491
|
+
client_id: "wave-cli"
|
|
492
|
+
})
|
|
493
|
+
});
|
|
494
|
+
if (response.ok) {
|
|
495
|
+
const tokenData = await response.json();
|
|
496
|
+
console.log(chalk4.green(" Authentication successful."));
|
|
497
|
+
return tokenData;
|
|
498
|
+
}
|
|
499
|
+
const errorBody = await response.json();
|
|
500
|
+
const errorCode = errorBody.error;
|
|
501
|
+
switch (errorCode) {
|
|
502
|
+
case POLL_ERROR_AUTHORIZATION_PENDING:
|
|
503
|
+
break;
|
|
504
|
+
case POLL_ERROR_SLOW_DOWN:
|
|
505
|
+
pollIntervalMs += SLOW_DOWN_INCREMENT_MS;
|
|
506
|
+
break;
|
|
507
|
+
case POLL_ERROR_EXPIRED_TOKEN:
|
|
508
|
+
throw new Error("Device code has expired. Please run the login command again.");
|
|
509
|
+
case POLL_ERROR_ACCESS_DENIED:
|
|
510
|
+
throw new Error("Authentication was denied. Please try again.");
|
|
511
|
+
default:
|
|
512
|
+
throw new Error(
|
|
513
|
+
`Unexpected error during device flow: ${errorCode}${errorBody.error_description ? ` - ${errorBody.error_description}` : ""}`
|
|
514
|
+
);
|
|
515
|
+
}
|
|
516
|
+
}
|
|
517
|
+
throw new Error("Device code has expired (timeout). Please run the login command again.");
|
|
518
|
+
}
|
|
519
|
+
function sleep(ms) {
|
|
520
|
+
return new Promise((resolve4) => setTimeout(resolve4, ms));
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
// src/commands/auth/index.ts
|
|
524
|
+
function registerAuthCommands(program) {
|
|
525
|
+
const auth = program.command("auth").description("Manage authentication");
|
|
526
|
+
auth.command("login").description("Authenticate with the WAVE platform").option("--api-key <key>", "API key for non-interactive authentication").action(
|
|
527
|
+
wrapCommand(async (opts) => {
|
|
528
|
+
if (opts.apiKey) {
|
|
529
|
+
const config = await loadConfig();
|
|
530
|
+
const project2 = config.currentProject || "default";
|
|
531
|
+
await storeApiKey(project2, opts.apiKey);
|
|
532
|
+
console.log(chalk5.green(`API key stored for project "${project2}".`));
|
|
533
|
+
return;
|
|
534
|
+
}
|
|
535
|
+
const baseUrl = process.env["WAVE_BASE_URL"] ?? "https://wave.online";
|
|
536
|
+
const deviceAuth = await startDeviceAuth(baseUrl);
|
|
537
|
+
const token = await pollForToken(
|
|
538
|
+
baseUrl,
|
|
539
|
+
deviceAuth.device_code,
|
|
540
|
+
deviceAuth.interval,
|
|
541
|
+
deviceAuth.expires_in
|
|
542
|
+
);
|
|
543
|
+
const project = "default";
|
|
544
|
+
await storeApiKey(project, token.access_token);
|
|
545
|
+
await updateConfig((config) => ({
|
|
546
|
+
...config,
|
|
547
|
+
currentProject: project
|
|
548
|
+
}));
|
|
549
|
+
console.log(chalk5.green("\nAuthentication complete. You can now use the WAVE CLI."));
|
|
550
|
+
})
|
|
551
|
+
);
|
|
552
|
+
auth.command("logout").description("Remove stored credentials").option("--all", "Remove credentials for all projects").action(
|
|
553
|
+
wrapCommand(async (opts) => {
|
|
554
|
+
if (opts.all) {
|
|
555
|
+
await deleteAllKeys();
|
|
556
|
+
console.log(chalk5.green("All credentials removed."));
|
|
557
|
+
} else {
|
|
558
|
+
const config = await loadConfig();
|
|
559
|
+
await deleteApiKey(config.currentProject);
|
|
560
|
+
console.log(chalk5.green(`Credentials removed for project "${config.currentProject}".`));
|
|
561
|
+
}
|
|
562
|
+
})
|
|
563
|
+
);
|
|
564
|
+
auth.command("status").description("Show current authentication status").action(
|
|
565
|
+
wrapCommand(async () => {
|
|
566
|
+
const config = await loadConfig();
|
|
567
|
+
const apiKey = await getApiKey(config.currentProject);
|
|
568
|
+
const status = {
|
|
569
|
+
project: config.currentProject,
|
|
570
|
+
authenticated: !!apiKey,
|
|
571
|
+
organization: config.projects[config.currentProject]?.organizationName ?? "N/A",
|
|
572
|
+
organizationId: config.projects[config.currentProject]?.organizationId ?? "N/A"
|
|
573
|
+
};
|
|
574
|
+
formatOutput(status, program.opts());
|
|
575
|
+
})
|
|
576
|
+
);
|
|
577
|
+
program.command("whoami").description("Show the current authenticated user").action(
|
|
578
|
+
wrapCommand(async () => {
|
|
579
|
+
const config = await loadConfig();
|
|
580
|
+
const project = config.currentProject || "default";
|
|
581
|
+
const apiKey = await getApiKey(project);
|
|
582
|
+
if (!apiKey) {
|
|
583
|
+
console.error(chalk5.red("Not authenticated. Run `wave auth login` first."));
|
|
584
|
+
process.exit(1);
|
|
585
|
+
}
|
|
586
|
+
const baseUrl = config.projects[project]?.baseUrl ?? "https://wave.online";
|
|
587
|
+
const res = await fetch(`${baseUrl}/api/v1/me`, {
|
|
588
|
+
headers: { Authorization: `Bearer ${apiKey}` }
|
|
589
|
+
});
|
|
590
|
+
if (!res.ok) {
|
|
591
|
+
console.error(
|
|
592
|
+
chalk5.red(
|
|
593
|
+
"Authentication invalid or expired. Run `wave auth login` to re-authenticate."
|
|
594
|
+
)
|
|
595
|
+
);
|
|
596
|
+
process.exit(1);
|
|
597
|
+
}
|
|
598
|
+
const user = await res.json();
|
|
599
|
+
console.log(chalk5.bold("\n Authenticated as:"));
|
|
600
|
+
if (user.name) console.log(` Name: ${chalk5.cyan(user.name)}`);
|
|
601
|
+
if (user.email) console.log(` Email: ${chalk5.cyan(user.email)}`);
|
|
602
|
+
console.log(
|
|
603
|
+
` Org: ${chalk5.cyan(user.organization ?? config.projects[project]?.organizationName ?? "N/A")}`
|
|
604
|
+
);
|
|
605
|
+
console.log(` Project: ${chalk5.cyan(project)}`);
|
|
606
|
+
console.log("");
|
|
607
|
+
formatOutput(
|
|
608
|
+
{
|
|
609
|
+
project,
|
|
610
|
+
name: user.name ?? "N/A",
|
|
611
|
+
email: user.email ?? "N/A",
|
|
612
|
+
organization: user.organization ?? config.projects[project]?.organizationName ?? "N/A"
|
|
613
|
+
},
|
|
614
|
+
program.opts()
|
|
615
|
+
);
|
|
616
|
+
})
|
|
617
|
+
);
|
|
618
|
+
}
|
|
619
|
+
|
|
620
|
+
// src/commands/org/index.ts
|
|
621
|
+
import chalk6 from "chalk";
|
|
622
|
+
function registerOrgCommands(program) {
|
|
623
|
+
const org = program.command("org").description("Manage organizations");
|
|
624
|
+
org.command("list").description("List available organizations").action(
|
|
625
|
+
wrapCommand(async () => {
|
|
626
|
+
const config = await loadConfig();
|
|
627
|
+
const orgs = Object.entries(config.projects).map(([name, project]) => ({
|
|
628
|
+
name,
|
|
629
|
+
organizationId: project.organizationId,
|
|
630
|
+
organizationName: project.organizationName,
|
|
631
|
+
current: name === config.currentProject ? "*" : ""
|
|
632
|
+
}));
|
|
633
|
+
formatOutput(orgs, program.opts());
|
|
634
|
+
})
|
|
635
|
+
);
|
|
636
|
+
org.command("switch <id>").description("Switch to a different organization").action(
|
|
637
|
+
wrapCommand(async (id) => {
|
|
638
|
+
const config = await loadConfig();
|
|
639
|
+
const match = Object.entries(config.projects).find(
|
|
640
|
+
([name, project]) => name === id || project.organizationId === id
|
|
641
|
+
);
|
|
642
|
+
if (!match) {
|
|
643
|
+
throw new Error(
|
|
644
|
+
`Organization "${id}" not found. Run ${chalk6.bold("wave org list")} to see available organizations.`
|
|
645
|
+
);
|
|
646
|
+
}
|
|
647
|
+
const [projectName] = match;
|
|
648
|
+
await updateConfig((cfg) => ({
|
|
649
|
+
...cfg,
|
|
650
|
+
currentProject: projectName
|
|
651
|
+
}));
|
|
652
|
+
console.log(chalk6.green(`Switched to organization "${projectName}".`));
|
|
653
|
+
})
|
|
654
|
+
);
|
|
655
|
+
org.command("current").description("Show the current organization").action(
|
|
656
|
+
wrapCommand(async () => {
|
|
657
|
+
const config = await loadConfig();
|
|
658
|
+
const project = config.projects[config.currentProject];
|
|
659
|
+
if (!project) {
|
|
660
|
+
console.log(chalk6.yellow("No organization configured. Run `wave login` first."));
|
|
661
|
+
return;
|
|
662
|
+
}
|
|
663
|
+
formatOutput(
|
|
664
|
+
{
|
|
665
|
+
project: config.currentProject,
|
|
666
|
+
organizationId: project.organizationId,
|
|
667
|
+
organizationName: project.organizationName,
|
|
668
|
+
region: project.region ?? "auto"
|
|
669
|
+
},
|
|
670
|
+
program.opts()
|
|
671
|
+
);
|
|
672
|
+
})
|
|
673
|
+
);
|
|
674
|
+
}
|
|
675
|
+
|
|
676
|
+
// src/commands/config/index.ts
|
|
677
|
+
import chalk7 from "chalk";
|
|
678
|
+
function registerConfigCommands(program) {
|
|
679
|
+
const config = program.command("config").description("Manage CLI configuration");
|
|
680
|
+
config.command("list").description("List all configuration values").action(
|
|
681
|
+
wrapCommand(async () => {
|
|
682
|
+
const cfg = await loadConfig();
|
|
683
|
+
console.log(chalk7.gray(`Config file: ${getConfigPath()}
|
|
684
|
+
`));
|
|
685
|
+
formatOutput(cfg, program.opts());
|
|
686
|
+
})
|
|
687
|
+
);
|
|
688
|
+
config.command("get <key>").description("Get a configuration value").action(
|
|
689
|
+
wrapCommand(async (key) => {
|
|
690
|
+
const cfg = await loadConfig();
|
|
691
|
+
const value = getNestedValue(cfg, key);
|
|
692
|
+
if (value === void 0) {
|
|
693
|
+
throw new Error(`Configuration key "${key}" not found.`);
|
|
694
|
+
}
|
|
695
|
+
if (typeof value === "object" && value !== null) {
|
|
696
|
+
formatOutput(value, program.opts());
|
|
697
|
+
} else {
|
|
698
|
+
console.log(String(value));
|
|
699
|
+
}
|
|
700
|
+
})
|
|
701
|
+
);
|
|
702
|
+
config.command("set <key> <value>").description("Set a configuration value").action(
|
|
703
|
+
wrapCommand(async (key, value) => {
|
|
704
|
+
await updateConfig((cfg) => {
|
|
705
|
+
const updated = { ...cfg };
|
|
706
|
+
setNestedValue(updated, key, value);
|
|
707
|
+
return updated;
|
|
708
|
+
});
|
|
709
|
+
console.log(chalk7.green(`Set ${chalk7.bold(key)} = ${value}`));
|
|
710
|
+
})
|
|
711
|
+
);
|
|
712
|
+
config.command("reset").description("Reset configuration to defaults").action(
|
|
713
|
+
wrapCommand(async () => {
|
|
714
|
+
const defaults = getDefaultConfig();
|
|
715
|
+
await updateConfig(() => defaults);
|
|
716
|
+
console.log(chalk7.green("Configuration reset to defaults."));
|
|
717
|
+
})
|
|
718
|
+
);
|
|
719
|
+
}
|
|
720
|
+
function getNestedValue(obj, path) {
|
|
721
|
+
const keys = path.split(".");
|
|
722
|
+
let current = obj;
|
|
723
|
+
for (const key of keys) {
|
|
724
|
+
if (current === null || current === void 0 || typeof current !== "object") {
|
|
725
|
+
return void 0;
|
|
726
|
+
}
|
|
727
|
+
current = current[key];
|
|
728
|
+
}
|
|
729
|
+
return current;
|
|
730
|
+
}
|
|
731
|
+
function setNestedValue(obj, path, value) {
|
|
732
|
+
const keys = path.split(".");
|
|
733
|
+
let current = obj;
|
|
734
|
+
for (let i = 0; i < keys.length - 1; i++) {
|
|
735
|
+
const key = keys[i];
|
|
736
|
+
if (typeof current[key] !== "object" || current[key] === null) {
|
|
737
|
+
current[key] = {};
|
|
738
|
+
}
|
|
739
|
+
current = current[key];
|
|
740
|
+
}
|
|
741
|
+
const lastKey = keys[keys.length - 1];
|
|
742
|
+
if (value === "true") current[lastKey] = true;
|
|
743
|
+
else if (value === "false") current[lastKey] = false;
|
|
744
|
+
else if (!isNaN(Number(value)) && value !== "") current[lastKey] = Number(value);
|
|
745
|
+
else current[lastKey] = value;
|
|
746
|
+
}
|
|
747
|
+
|
|
748
|
+
// src/commands/stream/index.ts
|
|
749
|
+
import chalk9 from "chalk";
|
|
750
|
+
|
|
751
|
+
// src/lib/api-client.ts
|
|
752
|
+
import { Wave } from "@wave-av/sdk";
|
|
753
|
+
import chalk8 from "chalk";
|
|
754
|
+
async function getClient(opts) {
|
|
755
|
+
const envApiKey = process.env["WAVE_API_KEY"];
|
|
756
|
+
const envOrgId = process.env["WAVE_ORG_ID"];
|
|
757
|
+
const envBaseUrl = process.env["WAVE_BASE_URL"];
|
|
758
|
+
if (envApiKey) {
|
|
759
|
+
return new Wave({
|
|
760
|
+
apiKey: envApiKey,
|
|
761
|
+
organizationId: envOrgId ?? opts?.org,
|
|
762
|
+
baseUrl: envBaseUrl
|
|
763
|
+
});
|
|
764
|
+
}
|
|
765
|
+
const config = await loadConfig();
|
|
766
|
+
const projectName = opts?.project ?? process.env["WAVE_PROJECT"] ?? config.currentProject;
|
|
767
|
+
const project = config.projects[projectName];
|
|
768
|
+
if (!project) {
|
|
769
|
+
console.error(
|
|
770
|
+
chalk8.red(
|
|
771
|
+
`No project "${projectName}" configured. Run ${chalk8.bold("wave login")} to authenticate.`
|
|
772
|
+
)
|
|
773
|
+
);
|
|
774
|
+
process.exit(1);
|
|
775
|
+
}
|
|
776
|
+
const apiKey = await getApiKey(projectName);
|
|
777
|
+
if (!apiKey) {
|
|
778
|
+
console.error(
|
|
779
|
+
chalk8.red(
|
|
780
|
+
`No API key found for project "${projectName}". Run ${chalk8.bold("wave login")} to authenticate.`
|
|
781
|
+
)
|
|
782
|
+
);
|
|
783
|
+
process.exit(1);
|
|
784
|
+
}
|
|
785
|
+
const client = new Wave({
|
|
786
|
+
apiKey,
|
|
787
|
+
organizationId: opts?.org ?? project.organizationId,
|
|
788
|
+
baseUrl: project.baseUrl,
|
|
789
|
+
customHeaders: {
|
|
790
|
+
"X-Wave-Source": "cli",
|
|
791
|
+
"X-Wave-CLI-Version": "1.0.0"
|
|
792
|
+
}
|
|
793
|
+
});
|
|
794
|
+
client.client.on("rate_limit.hit", (retryAfter) => {
|
|
795
|
+
console.warn(chalk8.yellow(`Rate limited. Retrying in ${retryAfter}ms...`));
|
|
796
|
+
});
|
|
797
|
+
client.client.on("request.retry", (_url, _method, attempt) => {
|
|
798
|
+
console.warn(chalk8.gray(`Retry ${attempt}/3...`));
|
|
799
|
+
});
|
|
800
|
+
return client;
|
|
801
|
+
}
|
|
802
|
+
|
|
803
|
+
// src/commands/stream/index.ts
|
|
804
|
+
function registerStreamCommands(program) {
|
|
805
|
+
const stream = program.command("stream").description("Manage live streams");
|
|
806
|
+
stream.command("create").description("Create a new live stream").requiredOption("--title <title>", "Stream title").option("--protocol <protocol>", "Streaming protocol (webrtc, srt, rtmp)", "webrtc").action(
|
|
807
|
+
wrapCommand(async (opts) => {
|
|
808
|
+
const client = await getClient(program.opts());
|
|
809
|
+
const result = await client.pipeline.create({
|
|
810
|
+
title: opts.title,
|
|
811
|
+
protocol: opts.protocol
|
|
812
|
+
});
|
|
813
|
+
console.log(chalk9.green(`Stream created: ${result.id}`));
|
|
814
|
+
formatOutput(result, program.opts());
|
|
815
|
+
})
|
|
816
|
+
);
|
|
817
|
+
stream.command("list").description("List all streams").option("--limit <n>", "Maximum results", "20").option("--status <status>", "Filter by status").action(
|
|
818
|
+
wrapCommand(async (opts) => {
|
|
819
|
+
const client = await getClient(program.opts());
|
|
820
|
+
const params = {
|
|
821
|
+
limit: parseInt(opts.limit)
|
|
822
|
+
};
|
|
823
|
+
if (opts.status) params.status = opts.status;
|
|
824
|
+
const result = await client.pipeline.list(params);
|
|
825
|
+
formatOutput(result.data, program.opts());
|
|
826
|
+
})
|
|
827
|
+
);
|
|
828
|
+
stream.command("get <id>").description("Get stream details").action(
|
|
829
|
+
wrapCommand(async (id) => {
|
|
830
|
+
const client = await getClient(program.opts());
|
|
831
|
+
const result = await client.pipeline.get(id);
|
|
832
|
+
formatOutput(result, program.opts());
|
|
833
|
+
})
|
|
834
|
+
);
|
|
835
|
+
stream.command("update <id>").description("Update a stream").option("--title <title>", "New stream title").option("--protocol <protocol>", "New streaming protocol").action(
|
|
836
|
+
wrapCommand(async (id, opts) => {
|
|
837
|
+
const client = await getClient(program.opts());
|
|
838
|
+
const updates = {};
|
|
839
|
+
if (opts.title) updates.title = opts.title;
|
|
840
|
+
if (opts.protocol) updates.protocol = opts.protocol;
|
|
841
|
+
const result = await client.pipeline.update(id, updates);
|
|
842
|
+
console.log(chalk9.green(`Stream ${id} updated.`));
|
|
843
|
+
formatOutput(result, program.opts());
|
|
844
|
+
})
|
|
845
|
+
);
|
|
846
|
+
stream.command("delete <id>").description("Delete a stream").action(
|
|
847
|
+
wrapCommand(async (id) => {
|
|
848
|
+
const confirmed = await confirmDestructive("delete", `stream ${id}`, program.opts());
|
|
849
|
+
if (!confirmed) return;
|
|
850
|
+
const client = await getClient(program.opts());
|
|
851
|
+
await client.pipeline.delete(id);
|
|
852
|
+
console.log(chalk9.green(`Stream ${id} deleted.`));
|
|
853
|
+
})
|
|
854
|
+
);
|
|
855
|
+
stream.command("start <id>").description("Start a live stream").action(
|
|
856
|
+
wrapCommand(async (id) => {
|
|
857
|
+
const client = await getClient(program.opts());
|
|
858
|
+
const result = await client.pipeline.start(id);
|
|
859
|
+
console.log(chalk9.green(`Stream ${id} started.`));
|
|
860
|
+
formatOutput(result, program.opts());
|
|
861
|
+
})
|
|
862
|
+
);
|
|
863
|
+
stream.command("stop <id>").description("Stop a live stream").action(
|
|
864
|
+
wrapCommand(async (id) => {
|
|
865
|
+
const confirmed = await confirmDestructive("stop", `stream ${id}`, program.opts());
|
|
866
|
+
if (!confirmed) return;
|
|
867
|
+
const client = await getClient(program.opts());
|
|
868
|
+
const result = await client.pipeline.stop(id);
|
|
869
|
+
console.log(chalk9.green(`Stream ${id} stopped.`));
|
|
870
|
+
formatOutput(result, program.opts());
|
|
871
|
+
})
|
|
872
|
+
);
|
|
873
|
+
stream.command("restart <id>").description("Restart a live stream").action(
|
|
874
|
+
wrapCommand(async (id) => {
|
|
875
|
+
const client = await getClient(program.opts());
|
|
876
|
+
const result = await client.pipeline.restart(id);
|
|
877
|
+
console.log(chalk9.green(`Stream ${id} restarted.`));
|
|
878
|
+
formatOutput(result, program.opts());
|
|
879
|
+
})
|
|
880
|
+
);
|
|
881
|
+
stream.command("status <id>").description("Get stream status").action(
|
|
882
|
+
wrapCommand(async (id) => {
|
|
883
|
+
const client = await getClient(program.opts());
|
|
884
|
+
const result = await client.pipeline.status(id);
|
|
885
|
+
formatOutput(result, program.opts());
|
|
886
|
+
})
|
|
887
|
+
);
|
|
888
|
+
stream.command("viewers <id>").description("Get viewer count for a stream").action(
|
|
889
|
+
wrapCommand(async (id) => {
|
|
890
|
+
const client = await getClient(program.opts());
|
|
891
|
+
const result = await client.pipeline.viewers(id);
|
|
892
|
+
formatOutput(result, program.opts());
|
|
893
|
+
})
|
|
894
|
+
);
|
|
895
|
+
stream.command("metrics <id>").description("Get stream metrics").action(
|
|
896
|
+
wrapCommand(async (id) => {
|
|
897
|
+
const client = await getClient(program.opts());
|
|
898
|
+
const result = await client.pipeline.metrics(id);
|
|
899
|
+
formatOutput(result, program.opts());
|
|
900
|
+
})
|
|
901
|
+
);
|
|
902
|
+
stream.command("recordings <id>").description("List recordings for a stream").action(
|
|
903
|
+
wrapCommand(async (id) => {
|
|
904
|
+
const client = await getClient(program.opts());
|
|
905
|
+
const result = await client.pipeline.recordings(id);
|
|
906
|
+
formatOutput(result, program.opts());
|
|
907
|
+
})
|
|
908
|
+
);
|
|
909
|
+
}
|
|
910
|
+
|
|
911
|
+
// src/commands/studio/index.ts
|
|
912
|
+
import chalk10 from "chalk";
|
|
913
|
+
function registerStudioCommands(program) {
|
|
914
|
+
const studio = program.command("studio").description("Manage studio productions");
|
|
915
|
+
studio.command("create").description("Create a new studio production").requiredOption("--title <title>", "Production title").action(
|
|
916
|
+
wrapCommand(async (opts) => {
|
|
917
|
+
const client = await getClient(program.opts());
|
|
918
|
+
const result = await client.studio.create({ title: opts.title });
|
|
919
|
+
console.log(chalk10.green(`Studio production created: ${result.id}`));
|
|
920
|
+
formatOutput(result, program.opts());
|
|
921
|
+
})
|
|
922
|
+
);
|
|
923
|
+
studio.command("list").description("List studio productions").option("--limit <n>", "Maximum results", "20").action(
|
|
924
|
+
wrapCommand(async (opts) => {
|
|
925
|
+
const client = await getClient(program.opts());
|
|
926
|
+
const result = await client.studio.list({ limit: parseInt(opts.limit) });
|
|
927
|
+
formatOutput(result.data, program.opts());
|
|
928
|
+
})
|
|
929
|
+
);
|
|
930
|
+
studio.command("get <id>").description("Get studio production details").action(
|
|
931
|
+
wrapCommand(async (id) => {
|
|
932
|
+
const client = await getClient(program.opts());
|
|
933
|
+
const result = await client.studio.get(id);
|
|
934
|
+
formatOutput(result, program.opts());
|
|
935
|
+
})
|
|
936
|
+
);
|
|
937
|
+
studio.command("start <id>").description("Start a studio production").action(
|
|
938
|
+
wrapCommand(async (id) => {
|
|
939
|
+
const client = await getClient(program.opts());
|
|
940
|
+
const result = await client.studio.start(id);
|
|
941
|
+
console.log(chalk10.green(`Studio production ${id} started.`));
|
|
942
|
+
formatOutput(result, program.opts());
|
|
943
|
+
})
|
|
944
|
+
);
|
|
945
|
+
studio.command("stop <id>").description("Stop a studio production").action(
|
|
946
|
+
wrapCommand(async (id) => {
|
|
947
|
+
const client = await getClient(program.opts());
|
|
948
|
+
const result = await client.studio.stop(id);
|
|
949
|
+
console.log(chalk10.green(`Studio production ${id} stopped.`));
|
|
950
|
+
formatOutput(result, program.opts());
|
|
951
|
+
})
|
|
952
|
+
);
|
|
953
|
+
const scene = studio.command("scene").description("Manage studio scenes");
|
|
954
|
+
scene.command("list <productionId>").description("List scenes in a production").action(
|
|
955
|
+
wrapCommand(async (productionId) => {
|
|
956
|
+
const client = await getClient(program.opts());
|
|
957
|
+
const result = await client.studio.scenes.list(productionId);
|
|
958
|
+
formatOutput(result, program.opts());
|
|
959
|
+
})
|
|
960
|
+
);
|
|
961
|
+
scene.command("create <productionId>").description("Create a scene in a production").requiredOption("--name <name>", "Scene name").action(
|
|
962
|
+
wrapCommand(async (productionId, opts) => {
|
|
963
|
+
const client = await getClient(program.opts());
|
|
964
|
+
const result = await client.studio.scenes.create(productionId, {
|
|
965
|
+
name: opts.name
|
|
966
|
+
});
|
|
967
|
+
console.log(chalk10.green(`Scene created: ${result.id}`));
|
|
968
|
+
formatOutput(result, program.opts());
|
|
969
|
+
})
|
|
970
|
+
);
|
|
971
|
+
scene.command("activate <productionId> <sceneId>").description("Activate a scene in a production").action(
|
|
972
|
+
wrapCommand(async (productionId, sceneId) => {
|
|
973
|
+
const client = await getClient(program.opts());
|
|
974
|
+
const result = await client.studio.scenes.activate(productionId, sceneId);
|
|
975
|
+
console.log(chalk10.green(`Scene ${sceneId} activated.`));
|
|
976
|
+
formatOutput(result, program.opts());
|
|
977
|
+
})
|
|
978
|
+
);
|
|
979
|
+
const source = studio.command("source").description("Manage studio sources");
|
|
980
|
+
source.command("list <productionId>").description("List sources in a production").action(
|
|
981
|
+
wrapCommand(async (productionId) => {
|
|
982
|
+
const client = await getClient(program.opts());
|
|
983
|
+
const result = await client.studio.sources.list(productionId);
|
|
984
|
+
formatOutput(result, program.opts());
|
|
985
|
+
})
|
|
986
|
+
);
|
|
987
|
+
source.command("add <productionId>").description("Add a source to a production").requiredOption("--type <type>", "Source type (camera, screen, url)").option("--url <url>", "Source URL for url type").option("--name <name>", "Source display name").action(
|
|
988
|
+
wrapCommand(async (productionId, opts) => {
|
|
989
|
+
const client = await getClient(program.opts());
|
|
990
|
+
const result = await client.studio.sources.add(productionId, {
|
|
991
|
+
type: opts.type,
|
|
992
|
+
url: opts.url,
|
|
993
|
+
name: opts.name
|
|
994
|
+
});
|
|
995
|
+
console.log(chalk10.green(`Source added: ${result.id}`));
|
|
996
|
+
formatOutput(result, program.opts());
|
|
997
|
+
})
|
|
998
|
+
);
|
|
999
|
+
}
|
|
1000
|
+
|
|
1001
|
+
// src/commands/clips/index.ts
|
|
1002
|
+
import chalk11 from "chalk";
|
|
1003
|
+
function registerClipCommands(program) {
|
|
1004
|
+
const clip = program.command("clip").description("Manage stream clips");
|
|
1005
|
+
clip.command("create").description("Create a clip from a stream").requiredOption("--stream-id <streamId>", "Source stream ID").requiredOption("--start <start>", "Start time in seconds").requiredOption("--end <end>", "End time in seconds").option("--title <title>", "Clip title").action(
|
|
1006
|
+
wrapCommand(async (opts) => {
|
|
1007
|
+
const client = await getClient(program.opts());
|
|
1008
|
+
const result = await client.clips.create({
|
|
1009
|
+
streamId: opts.streamId,
|
|
1010
|
+
start: parseFloat(opts.start),
|
|
1011
|
+
end: parseFloat(opts.end),
|
|
1012
|
+
title: opts.title
|
|
1013
|
+
});
|
|
1014
|
+
console.log(chalk11.green(`Clip created: ${result.id}`));
|
|
1015
|
+
formatOutput(result, program.opts());
|
|
1016
|
+
})
|
|
1017
|
+
);
|
|
1018
|
+
clip.command("list").description("List all clips").option("--limit <n>", "Maximum results", "20").action(
|
|
1019
|
+
wrapCommand(async (opts) => {
|
|
1020
|
+
const client = await getClient(program.opts());
|
|
1021
|
+
const result = await client.clips.list({ limit: parseInt(opts.limit) });
|
|
1022
|
+
formatOutput(result.data, program.opts());
|
|
1023
|
+
})
|
|
1024
|
+
);
|
|
1025
|
+
clip.command("get <id>").description("Get clip details").action(
|
|
1026
|
+
wrapCommand(async (id) => {
|
|
1027
|
+
const client = await getClient(program.opts());
|
|
1028
|
+
const result = await client.clips.get(id);
|
|
1029
|
+
formatOutput(result, program.opts());
|
|
1030
|
+
})
|
|
1031
|
+
);
|
|
1032
|
+
clip.command("export <id>").description("Export a clip").option("--format <format>", "Export format (mp4, webm, gif)", "mp4").action(
|
|
1033
|
+
wrapCommand(async (id, opts) => {
|
|
1034
|
+
const client = await getClient(program.opts());
|
|
1035
|
+
const result = await client.clips.export(id, { format: opts.format });
|
|
1036
|
+
console.log(chalk11.green(`Clip ${id} export started.`));
|
|
1037
|
+
formatOutput(result, program.opts());
|
|
1038
|
+
})
|
|
1039
|
+
);
|
|
1040
|
+
clip.command("download <id>").description("Download a clip to local filesystem").option("--output <path>", "Output file path").action(
|
|
1041
|
+
wrapCommand(async (id, opts) => {
|
|
1042
|
+
const client = await getClient(program.opts());
|
|
1043
|
+
const result = await client.clips.download(id, { output: opts.output });
|
|
1044
|
+
console.log(chalk11.green(`Clip ${id} downloaded to ${result.path}`));
|
|
1045
|
+
})
|
|
1046
|
+
);
|
|
1047
|
+
}
|
|
1048
|
+
|
|
1049
|
+
// src/commands/editor/index.ts
|
|
1050
|
+
import chalk12 from "chalk";
|
|
1051
|
+
function registerEditorCommands(program) {
|
|
1052
|
+
const editor = program.command("editor").description("Manage video editor projects");
|
|
1053
|
+
editor.command("create").description("Create a new editor project").requiredOption("--title <title>", "Project title").action(
|
|
1054
|
+
wrapCommand(async (opts) => {
|
|
1055
|
+
const client = await getClient(program.opts());
|
|
1056
|
+
const result = await client.editor.create({ title: opts.title });
|
|
1057
|
+
console.log(chalk12.green(`Editor project created: ${result.id}`));
|
|
1058
|
+
formatOutput(result, program.opts());
|
|
1059
|
+
})
|
|
1060
|
+
);
|
|
1061
|
+
editor.command("list").description("List editor projects").option("--limit <n>", "Maximum results", "20").action(
|
|
1062
|
+
wrapCommand(async (opts) => {
|
|
1063
|
+
const client = await getClient(program.opts());
|
|
1064
|
+
const result = await client.editor.list({ limit: parseInt(opts.limit) });
|
|
1065
|
+
formatOutput(result.data, program.opts());
|
|
1066
|
+
})
|
|
1067
|
+
);
|
|
1068
|
+
editor.command("get <id>").description("Get editor project details").action(
|
|
1069
|
+
wrapCommand(async (id) => {
|
|
1070
|
+
const client = await getClient(program.opts());
|
|
1071
|
+
const result = await client.editor.get(id);
|
|
1072
|
+
formatOutput(result, program.opts());
|
|
1073
|
+
})
|
|
1074
|
+
);
|
|
1075
|
+
editor.command("render <id>").description("Render an editor project").option("--format <format>", "Output format (mp4, webm, mov)", "mp4").action(
|
|
1076
|
+
wrapCommand(async (id, opts) => {
|
|
1077
|
+
const client = await getClient(program.opts());
|
|
1078
|
+
const result = await client.editor.render(id, { format: opts.format });
|
|
1079
|
+
console.log(chalk12.green(`Render started for project ${id}.`));
|
|
1080
|
+
formatOutput(result, program.opts());
|
|
1081
|
+
})
|
|
1082
|
+
);
|
|
1083
|
+
}
|
|
1084
|
+
|
|
1085
|
+
// src/commands/voice/index.ts
|
|
1086
|
+
import chalk13 from "chalk";
|
|
1087
|
+
function registerVoiceCommands(program) {
|
|
1088
|
+
const voice = program.command("voice").description("Voice synthesis and cloning");
|
|
1089
|
+
voice.command("synthesize").description("Synthesize text to speech").requiredOption("--text <text>", "Text to synthesize").requiredOption("--voice-id <voiceId>", "Voice ID to use").option("--output <path>", "Output audio file path").action(
|
|
1090
|
+
wrapCommand(async (opts) => {
|
|
1091
|
+
const client = await getClient(program.opts());
|
|
1092
|
+
const result = await client.voice.synthesize({
|
|
1093
|
+
text: opts.text,
|
|
1094
|
+
voiceId: opts.voiceId,
|
|
1095
|
+
output: opts.output
|
|
1096
|
+
});
|
|
1097
|
+
console.log(chalk13.green("Speech synthesized successfully."));
|
|
1098
|
+
formatOutput(result, program.opts());
|
|
1099
|
+
})
|
|
1100
|
+
);
|
|
1101
|
+
voice.command("clone").description("Clone a voice from a sample").requiredOption("--name <name>", "Name for the cloned voice").option("--sample <path>", "Path to voice sample audio file").action(
|
|
1102
|
+
wrapCommand(async (opts) => {
|
|
1103
|
+
const client = await getClient(program.opts());
|
|
1104
|
+
const result = await client.voice.clone({
|
|
1105
|
+
name: opts.name,
|
|
1106
|
+
samplePath: opts.sample
|
|
1107
|
+
});
|
|
1108
|
+
console.log(chalk13.green(`Voice cloned: ${result.id}`));
|
|
1109
|
+
formatOutput(result, program.opts());
|
|
1110
|
+
})
|
|
1111
|
+
);
|
|
1112
|
+
voice.command("list-voices").description("List available voices").action(
|
|
1113
|
+
wrapCommand(async () => {
|
|
1114
|
+
const client = await getClient(program.opts());
|
|
1115
|
+
const result = await client.voice.list();
|
|
1116
|
+
formatOutput(result.data, program.opts());
|
|
1117
|
+
})
|
|
1118
|
+
);
|
|
1119
|
+
}
|
|
1120
|
+
|
|
1121
|
+
// src/commands/phone/index.ts
|
|
1122
|
+
import chalk14 from "chalk";
|
|
1123
|
+
function registerPhoneCommands(program) {
|
|
1124
|
+
const phone = program.command("phone").description("Telephony and phone services");
|
|
1125
|
+
phone.command("call").description("Initiate a phone call").requiredOption("--to <number>", "Destination phone number").requiredOption("--from <number>", "Source phone number").action(
|
|
1126
|
+
wrapCommand(async (opts) => {
|
|
1127
|
+
const client = await getClient(program.opts());
|
|
1128
|
+
const result = await client.phone.call({
|
|
1129
|
+
to: opts.to,
|
|
1130
|
+
from: opts.from
|
|
1131
|
+
});
|
|
1132
|
+
console.log(chalk14.green(`Call initiated: ${result.id}`));
|
|
1133
|
+
formatOutput(result, program.opts());
|
|
1134
|
+
})
|
|
1135
|
+
);
|
|
1136
|
+
const conference = phone.command("conference").description("Manage conference calls");
|
|
1137
|
+
conference.command("create").description("Create a conference call").option("--name <name>", "Conference name").action(
|
|
1138
|
+
wrapCommand(async (opts) => {
|
|
1139
|
+
const client = await getClient(program.opts());
|
|
1140
|
+
const result = await client.phone.conferences.create({
|
|
1141
|
+
name: opts.name
|
|
1142
|
+
});
|
|
1143
|
+
console.log(chalk14.green(`Conference created: ${result.id}`));
|
|
1144
|
+
formatOutput(result, program.opts());
|
|
1145
|
+
})
|
|
1146
|
+
);
|
|
1147
|
+
conference.command("list").description("List conference calls").action(
|
|
1148
|
+
wrapCommand(async () => {
|
|
1149
|
+
const client = await getClient(program.opts());
|
|
1150
|
+
const result = await client.phone.conferences.list();
|
|
1151
|
+
formatOutput(result.data, program.opts());
|
|
1152
|
+
})
|
|
1153
|
+
);
|
|
1154
|
+
const numbers = phone.command("numbers").description("Manage phone numbers");
|
|
1155
|
+
numbers.command("list").description("List provisioned phone numbers").action(
|
|
1156
|
+
wrapCommand(async () => {
|
|
1157
|
+
const client = await getClient(program.opts());
|
|
1158
|
+
const result = await client.phone.numbers.list();
|
|
1159
|
+
formatOutput(result.data, program.opts());
|
|
1160
|
+
})
|
|
1161
|
+
);
|
|
1162
|
+
numbers.command("provision").description("Provision a new phone number").option("--country <code>", "Country code", "US").option("--area-code <code>", "Area code").action(
|
|
1163
|
+
wrapCommand(async (opts) => {
|
|
1164
|
+
const client = await getClient(program.opts());
|
|
1165
|
+
const result = await client.phone.numbers.provision({
|
|
1166
|
+
country: opts.country,
|
|
1167
|
+
areaCode: opts.areaCode
|
|
1168
|
+
});
|
|
1169
|
+
console.log(chalk14.green(`Number provisioned: ${result.number}`));
|
|
1170
|
+
formatOutput(result, program.opts());
|
|
1171
|
+
})
|
|
1172
|
+
);
|
|
1173
|
+
}
|
|
1174
|
+
|
|
1175
|
+
// src/commands/collab/index.ts
|
|
1176
|
+
import chalk15 from "chalk";
|
|
1177
|
+
function registerCollabCommands(program) {
|
|
1178
|
+
const collab = program.command("collab").description("Real-time collaboration rooms");
|
|
1179
|
+
const room = collab.command("room").description("Manage collaboration rooms");
|
|
1180
|
+
room.command("create").description("Create a collaboration room").option("--name <name>", "Room name").option("--max-participants <n>", "Maximum participants", "50").action(
|
|
1181
|
+
wrapCommand(async (opts) => {
|
|
1182
|
+
const client = await getClient(program.opts());
|
|
1183
|
+
const result = await client.collab.rooms.create({
|
|
1184
|
+
name: opts.name,
|
|
1185
|
+
maxParticipants: parseInt(opts.maxParticipants)
|
|
1186
|
+
});
|
|
1187
|
+
console.log(chalk15.green(`Room created: ${result.id}`));
|
|
1188
|
+
formatOutput(result, program.opts());
|
|
1189
|
+
})
|
|
1190
|
+
);
|
|
1191
|
+
room.command("list").description("List collaboration rooms").option("--limit <n>", "Maximum results", "20").action(
|
|
1192
|
+
wrapCommand(async (opts) => {
|
|
1193
|
+
const client = await getClient(program.opts());
|
|
1194
|
+
const result = await client.collab.rooms.list({
|
|
1195
|
+
limit: parseInt(opts.limit)
|
|
1196
|
+
});
|
|
1197
|
+
formatOutput(result.data, program.opts());
|
|
1198
|
+
})
|
|
1199
|
+
);
|
|
1200
|
+
room.command("join <id>").description("Join a collaboration room").action(
|
|
1201
|
+
wrapCommand(async (id) => {
|
|
1202
|
+
const client = await getClient(program.opts());
|
|
1203
|
+
const result = await client.collab.rooms.join(id);
|
|
1204
|
+
console.log(chalk15.green(`Joined room ${id}.`));
|
|
1205
|
+
formatOutput(result, program.opts());
|
|
1206
|
+
})
|
|
1207
|
+
);
|
|
1208
|
+
collab.command("invite").description("Invite a user to a collaboration room").requiredOption("--room-id <roomId>", "Room ID").requiredOption("--email <email>", "Email address to invite").action(
|
|
1209
|
+
wrapCommand(async (opts) => {
|
|
1210
|
+
const client = await getClient(program.opts());
|
|
1211
|
+
const result = await client.collab.invite({
|
|
1212
|
+
roomId: opts.roomId,
|
|
1213
|
+
email: opts.email
|
|
1214
|
+
});
|
|
1215
|
+
console.log(chalk15.green(`Invitation sent to ${opts.email}.`));
|
|
1216
|
+
formatOutput(result, program.opts());
|
|
1217
|
+
})
|
|
1218
|
+
);
|
|
1219
|
+
}
|
|
1220
|
+
|
|
1221
|
+
// src/commands/captions/index.ts
|
|
1222
|
+
import chalk16 from "chalk";
|
|
1223
|
+
function registerCaptionsCommands(program) {
|
|
1224
|
+
const captions = program.command("captions").description("Live captioning and subtitles");
|
|
1225
|
+
captions.command("generate").description("Generate captions for a stream").requiredOption("--stream-id <streamId>", "Stream ID").option("--language <lang>", "Source language", "en").action(
|
|
1226
|
+
wrapCommand(async (opts) => {
|
|
1227
|
+
const client = await getClient(program.opts());
|
|
1228
|
+
const result = await client.captions.generate({
|
|
1229
|
+
streamId: opts.streamId,
|
|
1230
|
+
language: opts.language
|
|
1231
|
+
});
|
|
1232
|
+
console.log(chalk16.green("Caption generation started."));
|
|
1233
|
+
formatOutput(result, program.opts());
|
|
1234
|
+
})
|
|
1235
|
+
);
|
|
1236
|
+
captions.command("translate").description("Translate captions to another language").requiredOption("--stream-id <streamId>", "Stream ID").requiredOption("--target-language <lang>", "Target language code").action(
|
|
1237
|
+
wrapCommand(async (opts) => {
|
|
1238
|
+
const client = await getClient(program.opts());
|
|
1239
|
+
const result = await client.captions.translate({
|
|
1240
|
+
streamId: opts.streamId,
|
|
1241
|
+
targetLanguage: opts.targetLanguage
|
|
1242
|
+
});
|
|
1243
|
+
console.log(chalk16.green(`Translation to ${opts.targetLanguage} started.`));
|
|
1244
|
+
formatOutput(result, program.opts());
|
|
1245
|
+
})
|
|
1246
|
+
);
|
|
1247
|
+
captions.command("burn-in").description("Burn captions into a recording").requiredOption("--recording-id <recordingId>", "Recording ID").option("--style <style>", "Caption style (default, cinematic, minimal)", "default").action(
|
|
1248
|
+
wrapCommand(async (opts) => {
|
|
1249
|
+
const client = await getClient(program.opts());
|
|
1250
|
+
const result = await client.captions.burnIn({
|
|
1251
|
+
recordingId: opts.recordingId,
|
|
1252
|
+
style: opts.style
|
|
1253
|
+
});
|
|
1254
|
+
console.log(chalk16.green("Caption burn-in started."));
|
|
1255
|
+
formatOutput(result, program.opts());
|
|
1256
|
+
})
|
|
1257
|
+
);
|
|
1258
|
+
}
|
|
1259
|
+
|
|
1260
|
+
// src/commands/chapters/index.ts
|
|
1261
|
+
import chalk17 from "chalk";
|
|
1262
|
+
function registerChaptersCommands(program) {
|
|
1263
|
+
const chapters = program.command("chapters").description("Chapter detection and management");
|
|
1264
|
+
chapters.command("detect").description("Auto-detect chapters in a recording").requiredOption("--recording-id <recordingId>", "Recording ID").action(
|
|
1265
|
+
wrapCommand(async (opts) => {
|
|
1266
|
+
const client = await getClient(program.opts());
|
|
1267
|
+
const result = await client.chapters.detect({
|
|
1268
|
+
recordingId: opts.recordingId
|
|
1269
|
+
});
|
|
1270
|
+
console.log(chalk17.green("Chapter detection started."));
|
|
1271
|
+
formatOutput(result, program.opts());
|
|
1272
|
+
})
|
|
1273
|
+
);
|
|
1274
|
+
chapters.command("create").description("Create a chapter manually").requiredOption("--recording-id <recordingId>", "Recording ID").requiredOption("--title <title>", "Chapter title").requiredOption("--timestamp <timestamp>", "Timestamp in seconds").action(
|
|
1275
|
+
wrapCommand(async (opts) => {
|
|
1276
|
+
const client = await getClient(program.opts());
|
|
1277
|
+
const result = await client.chapters.create({
|
|
1278
|
+
recordingId: opts.recordingId,
|
|
1279
|
+
title: opts.title,
|
|
1280
|
+
timestamp: parseFloat(opts.timestamp)
|
|
1281
|
+
});
|
|
1282
|
+
console.log(chalk17.green(`Chapter created: ${result.id}`));
|
|
1283
|
+
formatOutput(result, program.opts());
|
|
1284
|
+
})
|
|
1285
|
+
);
|
|
1286
|
+
chapters.command("list").description("List chapters for a recording").requiredOption("--recording-id <recordingId>", "Recording ID").action(
|
|
1287
|
+
wrapCommand(async (opts) => {
|
|
1288
|
+
const client = await getClient(program.opts());
|
|
1289
|
+
const result = await client.chapters.list({
|
|
1290
|
+
recordingId: opts.recordingId
|
|
1291
|
+
});
|
|
1292
|
+
formatOutput(result.data, program.opts());
|
|
1293
|
+
})
|
|
1294
|
+
);
|
|
1295
|
+
}
|
|
1296
|
+
|
|
1297
|
+
// src/commands/ai/index.ts
|
|
1298
|
+
import chalk18 from "chalk";
|
|
1299
|
+
function registerAICommands(program) {
|
|
1300
|
+
const ai = program.command("ai").description("AI-powered studio assistant");
|
|
1301
|
+
const assistant = ai.command("assistant").description("Studio AI assistant");
|
|
1302
|
+
assistant.command("start").description("Start the AI assistant for a production").requiredOption("--production-id <productionId>", "Production ID").action(
|
|
1303
|
+
wrapCommand(async (opts) => {
|
|
1304
|
+
const client = await getClient(program.opts());
|
|
1305
|
+
const result = await client.studioAI.start({
|
|
1306
|
+
productionId: opts.productionId
|
|
1307
|
+
});
|
|
1308
|
+
console.log(chalk18.green("AI assistant started."));
|
|
1309
|
+
formatOutput(result, program.opts());
|
|
1310
|
+
})
|
|
1311
|
+
);
|
|
1312
|
+
assistant.command("stop").description("Stop the AI assistant for a production").requiredOption("--production-id <productionId>", "Production ID").action(
|
|
1313
|
+
wrapCommand(async (opts) => {
|
|
1314
|
+
const client = await getClient(program.opts());
|
|
1315
|
+
const result = await client.studioAI.stop({
|
|
1316
|
+
productionId: opts.productionId
|
|
1317
|
+
});
|
|
1318
|
+
console.log(chalk18.green("AI assistant stopped."));
|
|
1319
|
+
formatOutput(result, program.opts());
|
|
1320
|
+
})
|
|
1321
|
+
);
|
|
1322
|
+
ai.command("suggestions").description("Get AI suggestions for the current production").requiredOption("--production-id <productionId>", "Production ID").action(
|
|
1323
|
+
wrapCommand(async (opts) => {
|
|
1324
|
+
const client = await getClient(program.opts());
|
|
1325
|
+
const result = await client.studioAI.suggestions({
|
|
1326
|
+
productionId: opts.productionId
|
|
1327
|
+
});
|
|
1328
|
+
formatOutput(result, program.opts());
|
|
1329
|
+
})
|
|
1330
|
+
);
|
|
1331
|
+
}
|
|
1332
|
+
|
|
1333
|
+
// src/commands/transcribe/index.ts
|
|
1334
|
+
import chalk19 from "chalk";
|
|
1335
|
+
function registerTranscribeCommands(program) {
|
|
1336
|
+
const transcribe = program.command("transcribe").description("Transcription services");
|
|
1337
|
+
transcribe.command("create").description("Start a transcription job").requiredOption("--stream-id <streamId>", "Stream or recording ID").option("--language <lang>", "Source language", "en").action(
|
|
1338
|
+
wrapCommand(async (opts) => {
|
|
1339
|
+
const client = await getClient(program.opts());
|
|
1340
|
+
const result = await client.transcribe.create({
|
|
1341
|
+
streamId: opts.streamId,
|
|
1342
|
+
language: opts.language
|
|
1343
|
+
});
|
|
1344
|
+
console.log(chalk19.green(`Transcription started: ${result.id}`));
|
|
1345
|
+
formatOutput(result, program.opts());
|
|
1346
|
+
})
|
|
1347
|
+
);
|
|
1348
|
+
transcribe.command("get <id>").description("Get transcription status and results").action(
|
|
1349
|
+
wrapCommand(async (id) => {
|
|
1350
|
+
const client = await getClient(program.opts());
|
|
1351
|
+
const result = await client.transcribe.get(id);
|
|
1352
|
+
formatOutput(result, program.opts());
|
|
1353
|
+
})
|
|
1354
|
+
);
|
|
1355
|
+
transcribe.command("export <id>").description("Export a transcription").option("--format <format>", "Export format (srt, vtt, txt, json)", "srt").action(
|
|
1356
|
+
wrapCommand(async (id, opts) => {
|
|
1357
|
+
const client = await getClient(program.opts());
|
|
1358
|
+
const result = await client.transcribe.export(id, {
|
|
1359
|
+
format: opts.format
|
|
1360
|
+
});
|
|
1361
|
+
console.log(chalk19.green(`Transcription exported as ${opts.format}.`));
|
|
1362
|
+
formatOutput(result, program.opts());
|
|
1363
|
+
})
|
|
1364
|
+
);
|
|
1365
|
+
}
|
|
1366
|
+
|
|
1367
|
+
// src/commands/sentiment/index.ts
|
|
1368
|
+
function registerSentimentCommands(program) {
|
|
1369
|
+
const sentiment = program.command("sentiment").description("Sentiment analysis tools");
|
|
1370
|
+
sentiment.command("analyze").description("Analyze sentiment of text").requiredOption("--text <text>", "Text to analyze").action(
|
|
1371
|
+
wrapCommand(async (opts) => {
|
|
1372
|
+
const client = await getClient(program.opts());
|
|
1373
|
+
const result = await client.sentiment.analyze({ text: opts.text });
|
|
1374
|
+
formatOutput(result, program.opts());
|
|
1375
|
+
})
|
|
1376
|
+
);
|
|
1377
|
+
sentiment.command("batch").description("Analyze sentiment of multiple texts from stdin").action(
|
|
1378
|
+
wrapCommand(async () => {
|
|
1379
|
+
const client = await getClient(program.opts());
|
|
1380
|
+
const result = await client.sentiment.batch();
|
|
1381
|
+
formatOutput(result, program.opts());
|
|
1382
|
+
})
|
|
1383
|
+
);
|
|
1384
|
+
sentiment.command("trends").description("View sentiment trends over time").option("--stream-id <streamId>", "Filter by stream ID").option("--period <period>", "Time period (hour, day, week)", "day").action(
|
|
1385
|
+
wrapCommand(async (opts) => {
|
|
1386
|
+
const client = await getClient(program.opts());
|
|
1387
|
+
const result = await client.sentiment.trends({
|
|
1388
|
+
streamId: opts.streamId,
|
|
1389
|
+
period: opts.period
|
|
1390
|
+
});
|
|
1391
|
+
formatOutput(result, program.opts());
|
|
1392
|
+
})
|
|
1393
|
+
);
|
|
1394
|
+
}
|
|
1395
|
+
|
|
1396
|
+
// src/commands/search/index.ts
|
|
1397
|
+
import chalk20 from "chalk";
|
|
1398
|
+
function registerSearchCommands(program) {
|
|
1399
|
+
const search = program.command("search").description("Content search across streams and recordings");
|
|
1400
|
+
search.command("query").description("Search content by text query").requiredOption("--q <query>", "Search query").option("--limit <n>", "Maximum results", "20").action(
|
|
1401
|
+
wrapCommand(async (opts) => {
|
|
1402
|
+
const client = await getClient(program.opts());
|
|
1403
|
+
const result = await client.search.query({
|
|
1404
|
+
q: opts.q,
|
|
1405
|
+
limit: parseInt(opts.limit)
|
|
1406
|
+
});
|
|
1407
|
+
formatOutput(result.data, program.opts());
|
|
1408
|
+
})
|
|
1409
|
+
);
|
|
1410
|
+
search.command("visual").description("Search by visual content").requiredOption("--q <query>", "Visual search query").option("--limit <n>", "Maximum results", "10").action(
|
|
1411
|
+
wrapCommand(async (opts) => {
|
|
1412
|
+
const client = await getClient(program.opts());
|
|
1413
|
+
const result = await client.search.visual({
|
|
1414
|
+
q: opts.q,
|
|
1415
|
+
limit: parseInt(opts.limit)
|
|
1416
|
+
});
|
|
1417
|
+
formatOutput(result.data, program.opts());
|
|
1418
|
+
})
|
|
1419
|
+
);
|
|
1420
|
+
search.command("audio").description("Search by audio content").requiredOption("--q <query>", "Audio search query").option("--limit <n>", "Maximum results", "10").action(
|
|
1421
|
+
wrapCommand(async (opts) => {
|
|
1422
|
+
const client = await getClient(program.opts());
|
|
1423
|
+
const result = await client.search.audio({
|
|
1424
|
+
q: opts.q,
|
|
1425
|
+
limit: parseInt(opts.limit)
|
|
1426
|
+
});
|
|
1427
|
+
formatOutput(result.data, program.opts());
|
|
1428
|
+
})
|
|
1429
|
+
);
|
|
1430
|
+
search.command("index").description("Index a recording for search").requiredOption("--recording-id <recordingId>", "Recording ID to index").action(
|
|
1431
|
+
wrapCommand(async (opts) => {
|
|
1432
|
+
const client = await getClient(program.opts());
|
|
1433
|
+
const result = await client.search.index({
|
|
1434
|
+
recordingId: opts.recordingId
|
|
1435
|
+
});
|
|
1436
|
+
console.log(chalk20.green("Recording indexing started."));
|
|
1437
|
+
formatOutput(result, program.opts());
|
|
1438
|
+
})
|
|
1439
|
+
);
|
|
1440
|
+
}
|
|
1441
|
+
|
|
1442
|
+
// src/commands/scene/index.ts
|
|
1443
|
+
import chalk21 from "chalk";
|
|
1444
|
+
function registerSceneCommands(program) {
|
|
1445
|
+
const scene = program.command("scene").description("Scene detection and analysis");
|
|
1446
|
+
scene.command("detect").description("Detect scenes in a recording").requiredOption("--recording-id <recordingId>", "Recording ID").action(
|
|
1447
|
+
wrapCommand(async (opts) => {
|
|
1448
|
+
const client = await getClient(program.opts());
|
|
1449
|
+
const result = await client.scene.detect({
|
|
1450
|
+
recordingId: opts.recordingId
|
|
1451
|
+
});
|
|
1452
|
+
console.log(chalk21.green("Scene detection started."));
|
|
1453
|
+
formatOutput(result, program.opts());
|
|
1454
|
+
})
|
|
1455
|
+
);
|
|
1456
|
+
scene.command("list").description("List detected scenes").requiredOption("--recording-id <recordingId>", "Recording ID").action(
|
|
1457
|
+
wrapCommand(async (opts) => {
|
|
1458
|
+
const client = await getClient(program.opts());
|
|
1459
|
+
const result = await client.scene.list({
|
|
1460
|
+
recordingId: opts.recordingId
|
|
1461
|
+
});
|
|
1462
|
+
formatOutput(result.data, program.opts());
|
|
1463
|
+
})
|
|
1464
|
+
);
|
|
1465
|
+
scene.command("compare").description("Compare scenes across recordings").requiredOption("--recording-ids <ids>", "Comma-separated recording IDs").action(
|
|
1466
|
+
wrapCommand(async (opts) => {
|
|
1467
|
+
const client = await getClient(program.opts());
|
|
1468
|
+
const recordingIds = opts.recordingIds.split(",");
|
|
1469
|
+
const result = await client.scene.compare({ recordingIds });
|
|
1470
|
+
formatOutput(result, program.opts());
|
|
1471
|
+
})
|
|
1472
|
+
);
|
|
1473
|
+
}
|
|
1474
|
+
|
|
1475
|
+
// src/commands/fleet/index.ts
|
|
1476
|
+
import chalk22 from "chalk";
|
|
1477
|
+
function registerFleetCommands(program) {
|
|
1478
|
+
const fleet = program.command("fleet").description("Device fleet management");
|
|
1479
|
+
fleet.command("list").description("List all devices in the fleet").option("--limit <n>", "Maximum results", "20").option("--status <status>", "Filter by device status").action(
|
|
1480
|
+
wrapCommand(async (opts) => {
|
|
1481
|
+
const client = await getClient(program.opts());
|
|
1482
|
+
const params = {
|
|
1483
|
+
limit: parseInt(opts.limit)
|
|
1484
|
+
};
|
|
1485
|
+
if (opts.status) params.status = opts.status;
|
|
1486
|
+
const result = await client.fleet.list(params);
|
|
1487
|
+
formatOutput(result.data, program.opts());
|
|
1488
|
+
})
|
|
1489
|
+
);
|
|
1490
|
+
fleet.command("get <id>").description("Get device details").action(
|
|
1491
|
+
wrapCommand(async (id) => {
|
|
1492
|
+
const client = await getClient(program.opts());
|
|
1493
|
+
const result = await client.fleet.get(id);
|
|
1494
|
+
formatOutput(result, program.opts());
|
|
1495
|
+
})
|
|
1496
|
+
);
|
|
1497
|
+
fleet.command("command <id>").description("Send a command to a device").requiredOption("--action <action>", "Command action (reboot, update, configure)").option("--payload <json>", "JSON payload for the command").action(
|
|
1498
|
+
wrapCommand(async (id, opts) => {
|
|
1499
|
+
const client = await getClient(program.opts());
|
|
1500
|
+
const payload = opts.payload ? JSON.parse(opts.payload) : void 0;
|
|
1501
|
+
const result = await client.fleet.command(id, {
|
|
1502
|
+
action: opts.action,
|
|
1503
|
+
payload
|
|
1504
|
+
});
|
|
1505
|
+
console.log(chalk22.green(`Command sent to device ${id}.`));
|
|
1506
|
+
formatOutput(result, program.opts());
|
|
1507
|
+
})
|
|
1508
|
+
);
|
|
1509
|
+
fleet.command("health <id>").description("Get device health status").action(
|
|
1510
|
+
wrapCommand(async (id) => {
|
|
1511
|
+
const client = await getClient(program.opts());
|
|
1512
|
+
const result = await client.fleet.health(id);
|
|
1513
|
+
formatOutput(result, program.opts());
|
|
1514
|
+
})
|
|
1515
|
+
);
|
|
1516
|
+
}
|
|
1517
|
+
|
|
1518
|
+
// src/commands/ghost/index.ts
|
|
1519
|
+
import chalk23 from "chalk";
|
|
1520
|
+
function registerGhostCommands(program) {
|
|
1521
|
+
const ghost = program.command("ghost").description("WAVE Autopilot AI director");
|
|
1522
|
+
ghost.command("suggestions").description("Get AI production suggestions").requiredOption("--production-id <productionId>", "Production ID").action(
|
|
1523
|
+
wrapCommand(async (opts) => {
|
|
1524
|
+
const client = await getClient(program.opts());
|
|
1525
|
+
const result = await client.ghost.suggestions({
|
|
1526
|
+
productionId: opts.productionId
|
|
1527
|
+
});
|
|
1528
|
+
formatOutput(result, program.opts());
|
|
1529
|
+
})
|
|
1530
|
+
);
|
|
1531
|
+
ghost.command("apply <id>").description("Apply an autopilot suggestion").action(
|
|
1532
|
+
wrapCommand(async (id) => {
|
|
1533
|
+
const client = await getClient(program.opts());
|
|
1534
|
+
const result = await client.ghost.apply(id);
|
|
1535
|
+
console.log(chalk23.green(`Suggestion ${id} applied.`));
|
|
1536
|
+
formatOutput(result, program.opts());
|
|
1537
|
+
})
|
|
1538
|
+
);
|
|
1539
|
+
ghost.command("history").description("View autopilot action history").option("--production-id <productionId>", "Filter by production ID").option("--limit <n>", "Maximum results", "20").action(
|
|
1540
|
+
wrapCommand(async (opts) => {
|
|
1541
|
+
const client = await getClient(program.opts());
|
|
1542
|
+
const result = await client.ghost.history({
|
|
1543
|
+
productionId: opts.productionId,
|
|
1544
|
+
limit: parseInt(opts.limit)
|
|
1545
|
+
});
|
|
1546
|
+
formatOutput(result.data, program.opts());
|
|
1547
|
+
})
|
|
1548
|
+
);
|
|
1549
|
+
}
|
|
1550
|
+
|
|
1551
|
+
// src/commands/mesh/index.ts
|
|
1552
|
+
import chalk24 from "chalk";
|
|
1553
|
+
function registerMeshCommands(program) {
|
|
1554
|
+
const mesh = program.command("mesh").description("Multi-region mesh network and failover");
|
|
1555
|
+
mesh.command("status").description("Show mesh network status").action(
|
|
1556
|
+
wrapCommand(async () => {
|
|
1557
|
+
const client = await getClient(program.opts());
|
|
1558
|
+
const result = await client.mesh.status();
|
|
1559
|
+
formatOutput(result, program.opts());
|
|
1560
|
+
})
|
|
1561
|
+
);
|
|
1562
|
+
mesh.command("regions").description("List available mesh regions").action(
|
|
1563
|
+
wrapCommand(async () => {
|
|
1564
|
+
const client = await getClient(program.opts());
|
|
1565
|
+
const result = await client.mesh.regions();
|
|
1566
|
+
formatOutput(result.data, program.opts());
|
|
1567
|
+
})
|
|
1568
|
+
);
|
|
1569
|
+
mesh.command("failover").description("Trigger a failover between regions").requiredOption("--from <region>", "Source region").requiredOption("--to <region>", "Target region").action(
|
|
1570
|
+
wrapCommand(async (opts) => {
|
|
1571
|
+
const confirmed = await confirmDestructive(
|
|
1572
|
+
"failover",
|
|
1573
|
+
`from ${opts.from} to ${opts.to}`,
|
|
1574
|
+
program.opts()
|
|
1575
|
+
);
|
|
1576
|
+
if (!confirmed) return;
|
|
1577
|
+
const client = await getClient(program.opts());
|
|
1578
|
+
const result = await client.mesh.failover({
|
|
1579
|
+
from: opts.from,
|
|
1580
|
+
to: opts.to
|
|
1581
|
+
});
|
|
1582
|
+
console.log(chalk24.green(`Failover initiated: ${opts.from} -> ${opts.to}`));
|
|
1583
|
+
formatOutput(result, program.opts());
|
|
1584
|
+
})
|
|
1585
|
+
);
|
|
1586
|
+
}
|
|
1587
|
+
|
|
1588
|
+
// src/commands/edge/index.ts
|
|
1589
|
+
import chalk25 from "chalk";
|
|
1590
|
+
function registerEdgeCommands(program) {
|
|
1591
|
+
const edge = program.command("edge").description("Edge processing and CDN management");
|
|
1592
|
+
const cache = edge.command("cache").description("Manage edge cache");
|
|
1593
|
+
cache.command("status").description("Show edge cache status").action(
|
|
1594
|
+
wrapCommand(async () => {
|
|
1595
|
+
const client = await getClient(program.opts());
|
|
1596
|
+
const result = await client.edge.cache.status();
|
|
1597
|
+
formatOutput(result, program.opts());
|
|
1598
|
+
})
|
|
1599
|
+
);
|
|
1600
|
+
cache.command("purge").description("Purge edge cache").option("--path <path>", "Specific path to purge (purges all if omitted)").action(
|
|
1601
|
+
wrapCommand(async (opts) => {
|
|
1602
|
+
const target = opts.path ?? "all cache entries";
|
|
1603
|
+
const confirmed = await confirmDestructive("purge", target, program.opts());
|
|
1604
|
+
if (!confirmed) return;
|
|
1605
|
+
const client = await getClient(program.opts());
|
|
1606
|
+
const result = await client.edge.cache.purge({ path: opts.path });
|
|
1607
|
+
console.log(chalk25.green("Cache purge initiated."));
|
|
1608
|
+
formatOutput(result, program.opts());
|
|
1609
|
+
})
|
|
1610
|
+
);
|
|
1611
|
+
const workers = edge.command("workers").description("Manage edge workers");
|
|
1612
|
+
workers.command("list").description("List edge workers").action(
|
|
1613
|
+
wrapCommand(async () => {
|
|
1614
|
+
const client = await getClient(program.opts());
|
|
1615
|
+
const result = await client.edge.workers.list();
|
|
1616
|
+
formatOutput(result.data, program.opts());
|
|
1617
|
+
})
|
|
1618
|
+
);
|
|
1619
|
+
const rules = edge.command("rules").description("Manage edge rules");
|
|
1620
|
+
rules.command("list").description("List edge rules").action(
|
|
1621
|
+
wrapCommand(async () => {
|
|
1622
|
+
const client = await getClient(program.opts());
|
|
1623
|
+
const result = await client.edge.rules.list();
|
|
1624
|
+
formatOutput(result.data, program.opts());
|
|
1625
|
+
})
|
|
1626
|
+
);
|
|
1627
|
+
rules.command("create").description("Create an edge rule").requiredOption("--pattern <pattern>", "URL pattern to match").requiredOption("--action <action>", "Rule action (cache, redirect, block)").action(
|
|
1628
|
+
wrapCommand(async (opts) => {
|
|
1629
|
+
const client = await getClient(program.opts());
|
|
1630
|
+
const result = await client.edge.rules.create({
|
|
1631
|
+
pattern: opts.pattern,
|
|
1632
|
+
action: opts.action
|
|
1633
|
+
});
|
|
1634
|
+
console.log(chalk25.green(`Edge rule created: ${result.id}`));
|
|
1635
|
+
formatOutput(result, program.opts());
|
|
1636
|
+
})
|
|
1637
|
+
);
|
|
1638
|
+
}
|
|
1639
|
+
|
|
1640
|
+
// src/commands/analytics/index.ts
|
|
1641
|
+
function registerAnalyticsCommands(program) {
|
|
1642
|
+
const analytics = program.command("analytics").description("Streaming analytics and insights");
|
|
1643
|
+
analytics.command("viewers").description("View audience analytics").option("--stream-id <streamId>", "Filter by stream ID").option("--period <period>", "Time period (hour, day, week, month)", "day").action(
|
|
1644
|
+
wrapCommand(async (opts) => {
|
|
1645
|
+
const client = await getClient(program.opts());
|
|
1646
|
+
const result = await client.pulse.viewers({
|
|
1647
|
+
streamId: opts.streamId,
|
|
1648
|
+
period: opts.period
|
|
1649
|
+
});
|
|
1650
|
+
formatOutput(result, program.opts());
|
|
1651
|
+
})
|
|
1652
|
+
);
|
|
1653
|
+
analytics.command("revenue").description("View revenue analytics").option("--period <period>", "Time period (day, week, month)", "month").action(
|
|
1654
|
+
wrapCommand(async (opts) => {
|
|
1655
|
+
const client = await getClient(program.opts());
|
|
1656
|
+
const result = await client.pulse.revenue({ period: opts.period });
|
|
1657
|
+
formatOutput(result, program.opts());
|
|
1658
|
+
})
|
|
1659
|
+
);
|
|
1660
|
+
analytics.command("export").description("Export analytics data").option("--format <format>", "Export format (csv, json)", "csv").option("--period <period>", "Time period (day, week, month)", "month").option("--stream-id <streamId>", "Filter by stream ID").action(
|
|
1661
|
+
wrapCommand(async (opts) => {
|
|
1662
|
+
const client = await getClient(program.opts());
|
|
1663
|
+
const result = await client.pulse.export({
|
|
1664
|
+
format: opts.format,
|
|
1665
|
+
period: opts.period,
|
|
1666
|
+
streamId: opts.streamId
|
|
1667
|
+
});
|
|
1668
|
+
formatOutput(result, program.opts());
|
|
1669
|
+
})
|
|
1670
|
+
);
|
|
1671
|
+
}
|
|
1672
|
+
|
|
1673
|
+
// src/commands/prism/index.ts
|
|
1674
|
+
import chalk26 from "chalk";
|
|
1675
|
+
function registerPrismCommands(program) {
|
|
1676
|
+
const prism = program.command("prism").description("Camera discovery and PTZ control");
|
|
1677
|
+
prism.command("discover").description("Discover available cameras on the network").option("--timeout <ms>", "Discovery timeout in milliseconds", "5000").action(
|
|
1678
|
+
wrapCommand(async (opts) => {
|
|
1679
|
+
const client = await getClient(program.opts());
|
|
1680
|
+
const result = await client.prism.discover({
|
|
1681
|
+
timeout: parseInt(opts.timeout)
|
|
1682
|
+
});
|
|
1683
|
+
formatOutput(result.data, program.opts());
|
|
1684
|
+
})
|
|
1685
|
+
);
|
|
1686
|
+
prism.command("create").description("Register a camera source").requiredOption("--source-type <type>", "Source type (ndi, srt, rtmp, usb)").option("--name <name>", "Camera display name").option("--url <url>", "Camera source URL").action(
|
|
1687
|
+
wrapCommand(async (opts) => {
|
|
1688
|
+
const client = await getClient(program.opts());
|
|
1689
|
+
const result = await client.prism.create({
|
|
1690
|
+
sourceType: opts.sourceType,
|
|
1691
|
+
name: opts.name,
|
|
1692
|
+
url: opts.url
|
|
1693
|
+
});
|
|
1694
|
+
console.log(chalk26.green(`Camera registered: ${result.id}`));
|
|
1695
|
+
formatOutput(result, program.opts());
|
|
1696
|
+
})
|
|
1697
|
+
);
|
|
1698
|
+
prism.command("ptz").description("Control camera PTZ (Pan-Tilt-Zoom)").requiredOption("--camera-id <cameraId>", "Camera ID").option("--pan <degrees>", "Pan angle in degrees").option("--tilt <degrees>", "Tilt angle in degrees").option("--zoom <level>", "Zoom level (0-100)").action(
|
|
1699
|
+
wrapCommand(async (opts) => {
|
|
1700
|
+
const client = await getClient(program.opts());
|
|
1701
|
+
const params = {
|
|
1702
|
+
cameraId: opts.cameraId
|
|
1703
|
+
};
|
|
1704
|
+
if (opts.pan !== void 0) params.pan = parseFloat(opts.pan);
|
|
1705
|
+
if (opts.tilt !== void 0) params.tilt = parseFloat(opts.tilt);
|
|
1706
|
+
if (opts.zoom !== void 0) params.zoom = parseFloat(opts.zoom);
|
|
1707
|
+
const result = await client.prism.ptz(params);
|
|
1708
|
+
console.log(chalk26.green("PTZ command sent."));
|
|
1709
|
+
formatOutput(result, program.opts());
|
|
1710
|
+
})
|
|
1711
|
+
);
|
|
1712
|
+
}
|
|
1713
|
+
|
|
1714
|
+
// src/commands/zoom/index.ts
|
|
1715
|
+
import chalk27 from "chalk";
|
|
1716
|
+
function registerZoomCommands(program) {
|
|
1717
|
+
const zoom = program.command("zoom").description("Zoom meeting and webinar integration");
|
|
1718
|
+
const meeting = zoom.command("meeting").description("Manage Zoom meetings");
|
|
1719
|
+
meeting.command("create").description("Create a Zoom meeting").requiredOption("--topic <topic>", "Meeting topic").option("--duration <minutes>", "Duration in minutes", "60").action(
|
|
1720
|
+
wrapCommand(async (opts) => {
|
|
1721
|
+
const client = await getClient(program.opts());
|
|
1722
|
+
const result = await client.zoom.meetings.create({
|
|
1723
|
+
topic: opts.topic,
|
|
1724
|
+
duration: parseInt(opts.duration)
|
|
1725
|
+
});
|
|
1726
|
+
console.log(chalk27.green(`Meeting created: ${result.id}`));
|
|
1727
|
+
formatOutput(result, program.opts());
|
|
1728
|
+
})
|
|
1729
|
+
);
|
|
1730
|
+
meeting.command("list").description("List Zoom meetings").option("--limit <n>", "Maximum results", "20").action(
|
|
1731
|
+
wrapCommand(async (opts) => {
|
|
1732
|
+
const client = await getClient(program.opts());
|
|
1733
|
+
const result = await client.zoom.meetings.list({
|
|
1734
|
+
limit: parseInt(opts.limit)
|
|
1735
|
+
});
|
|
1736
|
+
formatOutput(result.data, program.opts());
|
|
1737
|
+
})
|
|
1738
|
+
);
|
|
1739
|
+
meeting.command("get <id>").description("Get Zoom meeting details").action(
|
|
1740
|
+
wrapCommand(async (id) => {
|
|
1741
|
+
const client = await getClient(program.opts());
|
|
1742
|
+
const result = await client.zoom.meetings.get(id);
|
|
1743
|
+
formatOutput(result, program.opts());
|
|
1744
|
+
})
|
|
1745
|
+
);
|
|
1746
|
+
meeting.command("delete <id>").description("Delete a Zoom meeting").action(
|
|
1747
|
+
wrapCommand(async (id) => {
|
|
1748
|
+
const confirmed = await confirmDestructive("delete", `Zoom meeting ${id}`, program.opts());
|
|
1749
|
+
if (!confirmed) return;
|
|
1750
|
+
const client = await getClient(program.opts());
|
|
1751
|
+
await client.zoom.meetings.delete(id);
|
|
1752
|
+
console.log(chalk27.green(`Meeting ${id} deleted.`));
|
|
1753
|
+
})
|
|
1754
|
+
);
|
|
1755
|
+
const recordings = zoom.command("recordings").description("Manage Zoom recordings");
|
|
1756
|
+
recordings.command("list").description("List Zoom recordings").option("--limit <n>", "Maximum results", "20").action(
|
|
1757
|
+
wrapCommand(async (opts) => {
|
|
1758
|
+
const client = await getClient(program.opts());
|
|
1759
|
+
const result = await client.zoom.recordings.list({
|
|
1760
|
+
limit: parseInt(opts.limit)
|
|
1761
|
+
});
|
|
1762
|
+
formatOutput(result.data, program.opts());
|
|
1763
|
+
})
|
|
1764
|
+
);
|
|
1765
|
+
recordings.command("get <id>").description("Get Zoom recording details").action(
|
|
1766
|
+
wrapCommand(async (id) => {
|
|
1767
|
+
const client = await getClient(program.opts());
|
|
1768
|
+
const result = await client.zoom.recordings.get(id);
|
|
1769
|
+
formatOutput(result, program.opts());
|
|
1770
|
+
})
|
|
1771
|
+
);
|
|
1772
|
+
const webinar = zoom.command("webinar").description("Manage Zoom webinars");
|
|
1773
|
+
webinar.command("create").description("Create a Zoom webinar").requiredOption("--topic <topic>", "Webinar topic").option("--duration <minutes>", "Duration in minutes", "60").action(
|
|
1774
|
+
wrapCommand(async (opts) => {
|
|
1775
|
+
const client = await getClient(program.opts());
|
|
1776
|
+
const result = await client.zoom.webinars.create({
|
|
1777
|
+
topic: opts.topic,
|
|
1778
|
+
duration: parseInt(opts.duration)
|
|
1779
|
+
});
|
|
1780
|
+
console.log(chalk27.green(`Webinar created: ${result.id}`));
|
|
1781
|
+
formatOutput(result, program.opts());
|
|
1782
|
+
})
|
|
1783
|
+
);
|
|
1784
|
+
webinar.command("list").description("List Zoom webinars").option("--limit <n>", "Maximum results", "20").action(
|
|
1785
|
+
wrapCommand(async (opts) => {
|
|
1786
|
+
const client = await getClient(program.opts());
|
|
1787
|
+
const result = await client.zoom.webinars.list({
|
|
1788
|
+
limit: parseInt(opts.limit)
|
|
1789
|
+
});
|
|
1790
|
+
formatOutput(result.data, program.opts());
|
|
1791
|
+
})
|
|
1792
|
+
);
|
|
1793
|
+
}
|
|
1794
|
+
|
|
1795
|
+
// src/commands/vault/index.ts
|
|
1796
|
+
import chalk28 from "chalk";
|
|
1797
|
+
function registerVaultCommands(program) {
|
|
1798
|
+
const vault = program.command("vault").description("Recording vault and archive management");
|
|
1799
|
+
const recordings = vault.command("recordings").description("Manage vault recordings");
|
|
1800
|
+
recordings.command("list").description("List recordings in the vault").option("--limit <n>", "Maximum results", "20").option("--status <status>", "Filter by status (active, archived)").action(
|
|
1801
|
+
wrapCommand(async (opts) => {
|
|
1802
|
+
const client = await getClient(program.opts());
|
|
1803
|
+
const params = {
|
|
1804
|
+
limit: parseInt(opts.limit)
|
|
1805
|
+
};
|
|
1806
|
+
if (opts.status) params.status = opts.status;
|
|
1807
|
+
const result = await client.vault.recordings.list(params);
|
|
1808
|
+
formatOutput(result.data, program.opts());
|
|
1809
|
+
})
|
|
1810
|
+
);
|
|
1811
|
+
recordings.command("get <id>").description("Get recording details").action(
|
|
1812
|
+
wrapCommand(async (id) => {
|
|
1813
|
+
const client = await getClient(program.opts());
|
|
1814
|
+
const result = await client.vault.recordings.get(id);
|
|
1815
|
+
formatOutput(result, program.opts());
|
|
1816
|
+
})
|
|
1817
|
+
);
|
|
1818
|
+
recordings.command("delete <id>").description("Delete a recording from the vault").action(
|
|
1819
|
+
wrapCommand(async (id) => {
|
|
1820
|
+
const confirmed = await confirmDestructive(
|
|
1821
|
+
"permanently delete",
|
|
1822
|
+
`recording ${id}`,
|
|
1823
|
+
program.opts()
|
|
1824
|
+
);
|
|
1825
|
+
if (!confirmed) return;
|
|
1826
|
+
const client = await getClient(program.opts());
|
|
1827
|
+
await client.vault.recordings.delete(id);
|
|
1828
|
+
console.log(chalk28.green(`Recording ${id} deleted.`));
|
|
1829
|
+
})
|
|
1830
|
+
);
|
|
1831
|
+
vault.command("archive <id>").description("Archive a recording to cold storage").action(
|
|
1832
|
+
wrapCommand(async (id) => {
|
|
1833
|
+
const client = await getClient(program.opts());
|
|
1834
|
+
const result = await client.vault.archive(id);
|
|
1835
|
+
console.log(chalk28.green(`Recording ${id} archived.`));
|
|
1836
|
+
formatOutput(result, program.opts());
|
|
1837
|
+
})
|
|
1838
|
+
);
|
|
1839
|
+
vault.command("restore <id>").description("Restore an archived recording").action(
|
|
1840
|
+
wrapCommand(async (id) => {
|
|
1841
|
+
const client = await getClient(program.opts());
|
|
1842
|
+
const result = await client.vault.restore(id);
|
|
1843
|
+
console.log(chalk28.green(`Recording ${id} restoration started.`));
|
|
1844
|
+
formatOutput(result, program.opts());
|
|
1845
|
+
})
|
|
1846
|
+
);
|
|
1847
|
+
}
|
|
1848
|
+
|
|
1849
|
+
// src/commands/marketplace/index.ts
|
|
1850
|
+
import chalk29 from "chalk";
|
|
1851
|
+
function registerMarketplaceCommands(program) {
|
|
1852
|
+
const marketplace = program.command("marketplace").description("WAVE marketplace for plugins and extensions");
|
|
1853
|
+
marketplace.command("search").description("Search the marketplace").requiredOption("--q <query>", "Search query").option("--category <category>", "Filter by category").option("--limit <n>", "Maximum results", "20").action(
|
|
1854
|
+
wrapCommand(async (opts) => {
|
|
1855
|
+
const client = await getClient(program.opts());
|
|
1856
|
+
const result = await client.marketplace.search({
|
|
1857
|
+
q: opts.q,
|
|
1858
|
+
category: opts.category,
|
|
1859
|
+
limit: parseInt(opts.limit)
|
|
1860
|
+
});
|
|
1861
|
+
formatOutput(result.data, program.opts());
|
|
1862
|
+
})
|
|
1863
|
+
);
|
|
1864
|
+
marketplace.command("install <id>").description("Install a marketplace extension").action(
|
|
1865
|
+
wrapCommand(async (id) => {
|
|
1866
|
+
const client = await getClient(program.opts());
|
|
1867
|
+
const result = await client.marketplace.install(id);
|
|
1868
|
+
console.log(chalk29.green(`Extension ${id} installed.`));
|
|
1869
|
+
formatOutput(result, program.opts());
|
|
1870
|
+
})
|
|
1871
|
+
);
|
|
1872
|
+
marketplace.command("publish").description("Publish an extension to the marketplace").option("--path <path>", "Path to extension package", ".").action(
|
|
1873
|
+
wrapCommand(async (opts) => {
|
|
1874
|
+
const client = await getClient(program.opts());
|
|
1875
|
+
const result = await client.marketplace.publish({ path: opts.path });
|
|
1876
|
+
console.log(chalk29.green("Extension published successfully."));
|
|
1877
|
+
formatOutput(result, program.opts());
|
|
1878
|
+
})
|
|
1879
|
+
);
|
|
1880
|
+
marketplace.command("list").description("List installed extensions").action(
|
|
1881
|
+
wrapCommand(async () => {
|
|
1882
|
+
const client = await getClient(program.opts());
|
|
1883
|
+
const result = await client.marketplace.list();
|
|
1884
|
+
formatOutput(result.data, program.opts());
|
|
1885
|
+
})
|
|
1886
|
+
);
|
|
1887
|
+
}
|
|
1888
|
+
|
|
1889
|
+
// src/commands/connect/index.ts
|
|
1890
|
+
import chalk30 from "chalk";
|
|
1891
|
+
function registerConnectCommands(program) {
|
|
1892
|
+
const connect = program.command("connect").description("Third-party integrations and connectors");
|
|
1893
|
+
connect.command("integrations").description("List available integrations").option("--limit <n>", "Maximum results", "50").action(
|
|
1894
|
+
wrapCommand(async (opts) => {
|
|
1895
|
+
const client = await getClient(program.opts());
|
|
1896
|
+
const result = await client.connect.integrations({
|
|
1897
|
+
limit: parseInt(opts.limit)
|
|
1898
|
+
});
|
|
1899
|
+
formatOutput(result.data, program.opts());
|
|
1900
|
+
})
|
|
1901
|
+
);
|
|
1902
|
+
connect.command("configure <id>").description("Configure an integration").option("--settings <json>", "JSON settings for the integration").action(
|
|
1903
|
+
wrapCommand(async (id, opts) => {
|
|
1904
|
+
const client = await getClient(program.opts());
|
|
1905
|
+
const settings = opts.settings ? JSON.parse(opts.settings) : void 0;
|
|
1906
|
+
const result = await client.connect.configure(id, { settings });
|
|
1907
|
+
console.log(chalk30.green(`Integration ${id} configured.`));
|
|
1908
|
+
formatOutput(result, program.opts());
|
|
1909
|
+
})
|
|
1910
|
+
);
|
|
1911
|
+
connect.command("status <id>").description("Get integration connection status").action(
|
|
1912
|
+
wrapCommand(async (id) => {
|
|
1913
|
+
const client = await getClient(program.opts());
|
|
1914
|
+
const result = await client.connect.status(id);
|
|
1915
|
+
formatOutput(result, program.opts());
|
|
1916
|
+
})
|
|
1917
|
+
);
|
|
1918
|
+
connect.command("disconnect <id>").description("Disconnect an integration").action(
|
|
1919
|
+
wrapCommand(async (id) => {
|
|
1920
|
+
const confirmed = await confirmDestructive(
|
|
1921
|
+
"disconnect",
|
|
1922
|
+
`integration ${id}`,
|
|
1923
|
+
program.opts()
|
|
1924
|
+
);
|
|
1925
|
+
if (!confirmed) return;
|
|
1926
|
+
const client = await getClient(program.opts());
|
|
1927
|
+
await client.connect.disconnect(id);
|
|
1928
|
+
console.log(chalk30.green(`Integration ${id} disconnected.`));
|
|
1929
|
+
})
|
|
1930
|
+
);
|
|
1931
|
+
}
|
|
1932
|
+
|
|
1933
|
+
// src/commands/distribution/index.ts
|
|
1934
|
+
import chalk31 from "chalk";
|
|
1935
|
+
function registerDistributionCommands(program) {
|
|
1936
|
+
const distribution = program.command("distribution").description("Multi-platform stream distribution");
|
|
1937
|
+
distribution.command("simulcast").description("Set up simulcast for a stream").requiredOption("--stream-id <streamId>", "Stream ID").action(
|
|
1938
|
+
wrapCommand(async (opts) => {
|
|
1939
|
+
const client = await getClient(program.opts());
|
|
1940
|
+
const result = await client.distribution.simulcast({
|
|
1941
|
+
streamId: opts.streamId
|
|
1942
|
+
});
|
|
1943
|
+
console.log(chalk31.green("Simulcast configured."));
|
|
1944
|
+
formatOutput(result, program.opts());
|
|
1945
|
+
})
|
|
1946
|
+
);
|
|
1947
|
+
const destinations = distribution.command("destinations").description("Manage distribution destinations");
|
|
1948
|
+
destinations.command("list").description("List distribution destinations").action(
|
|
1949
|
+
wrapCommand(async () => {
|
|
1950
|
+
const client = await getClient(program.opts());
|
|
1951
|
+
const result = await client.distribution.destinations.list();
|
|
1952
|
+
formatOutput(result.data, program.opts());
|
|
1953
|
+
})
|
|
1954
|
+
);
|
|
1955
|
+
destinations.command("add").description("Add a distribution destination").requiredOption("--platform <platform>", "Platform (youtube, twitch, facebook, custom)").requiredOption("--stream-key <key>", "Stream key for the platform").option("--url <url>", "Custom RTMP URL").action(
|
|
1956
|
+
wrapCommand(async (opts) => {
|
|
1957
|
+
const client = await getClient(program.opts());
|
|
1958
|
+
const result = await client.distribution.destinations.add({
|
|
1959
|
+
platform: opts.platform,
|
|
1960
|
+
streamKey: opts.streamKey,
|
|
1961
|
+
url: opts.url
|
|
1962
|
+
});
|
|
1963
|
+
console.log(chalk31.green(`Destination added: ${result.id}`));
|
|
1964
|
+
formatOutput(result, program.opts());
|
|
1965
|
+
})
|
|
1966
|
+
);
|
|
1967
|
+
destinations.command("remove <id>").description("Remove a distribution destination").action(
|
|
1968
|
+
wrapCommand(async (id) => {
|
|
1969
|
+
const confirmed = await confirmDestructive("remove", `destination ${id}`, program.opts());
|
|
1970
|
+
if (!confirmed) return;
|
|
1971
|
+
const client = await getClient(program.opts());
|
|
1972
|
+
await client.distribution.destinations.remove(id);
|
|
1973
|
+
console.log(chalk31.green(`Destination ${id} removed.`));
|
|
1974
|
+
})
|
|
1975
|
+
);
|
|
1976
|
+
}
|
|
1977
|
+
|
|
1978
|
+
// src/commands/desktop/index.ts
|
|
1979
|
+
import chalk32 from "chalk";
|
|
1980
|
+
function registerDesktopCommands(program) {
|
|
1981
|
+
const desktop = program.command("desktop").description("Desktop node management and pairing");
|
|
1982
|
+
desktop.command("nodes").description("List connected desktop nodes").action(
|
|
1983
|
+
wrapCommand(async () => {
|
|
1984
|
+
const client = await getClient(program.opts());
|
|
1985
|
+
const result = await client.desktop.nodes();
|
|
1986
|
+
formatOutput(result.data, program.opts());
|
|
1987
|
+
})
|
|
1988
|
+
);
|
|
1989
|
+
desktop.command("pair").description("Pair a desktop node").requiredOption("--code <code>", "Pairing code from the desktop application").action(
|
|
1990
|
+
wrapCommand(async (opts) => {
|
|
1991
|
+
const client = await getClient(program.opts());
|
|
1992
|
+
const result = await client.desktop.pair({ code: opts.code });
|
|
1993
|
+
console.log(chalk32.green("Desktop node paired successfully."));
|
|
1994
|
+
formatOutput(result, program.opts());
|
|
1995
|
+
})
|
|
1996
|
+
);
|
|
1997
|
+
desktop.command("status <id>").description("Get desktop node status").action(
|
|
1998
|
+
wrapCommand(async (id) => {
|
|
1999
|
+
const client = await getClient(program.opts());
|
|
2000
|
+
const result = await client.desktop.status(id);
|
|
2001
|
+
formatOutput(result, program.opts());
|
|
2002
|
+
})
|
|
2003
|
+
);
|
|
2004
|
+
}
|
|
2005
|
+
|
|
2006
|
+
// src/commands/signage/index.ts
|
|
2007
|
+
import chalk33 from "chalk";
|
|
2008
|
+
function registerSignageCommands(program) {
|
|
2009
|
+
const signage = program.command("signage").description("Digital signage management");
|
|
2010
|
+
const displays = signage.command("displays").description("Manage signage displays");
|
|
2011
|
+
displays.command("list").description("List registered displays").option("--limit <n>", "Maximum results", "20").action(
|
|
2012
|
+
wrapCommand(async (opts) => {
|
|
2013
|
+
const client = await getClient(program.opts());
|
|
2014
|
+
const result = await client.signage.displays.list({
|
|
2015
|
+
limit: parseInt(opts.limit)
|
|
2016
|
+
});
|
|
2017
|
+
formatOutput(result.data, program.opts());
|
|
2018
|
+
})
|
|
2019
|
+
);
|
|
2020
|
+
displays.command("register").description("Register a new display").requiredOption("--name <name>", "Display name").option("--location <location>", "Physical location").action(
|
|
2021
|
+
wrapCommand(async (opts) => {
|
|
2022
|
+
const client = await getClient(program.opts());
|
|
2023
|
+
const result = await client.signage.displays.register({
|
|
2024
|
+
name: opts.name,
|
|
2025
|
+
location: opts.location
|
|
2026
|
+
});
|
|
2027
|
+
console.log(chalk33.green(`Display registered: ${result.id}`));
|
|
2028
|
+
formatOutput(result, program.opts());
|
|
2029
|
+
})
|
|
2030
|
+
);
|
|
2031
|
+
const content = signage.command("content").description("Manage signage content");
|
|
2032
|
+
content.command("upload").description("Upload content for signage").requiredOption("--file <path>", "Path to content file").option("--name <name>", "Content name").action(
|
|
2033
|
+
wrapCommand(async (opts) => {
|
|
2034
|
+
const client = await getClient(program.opts());
|
|
2035
|
+
const result = await client.signage.content.upload({
|
|
2036
|
+
file: opts.file,
|
|
2037
|
+
name: opts.name
|
|
2038
|
+
});
|
|
2039
|
+
console.log(chalk33.green(`Content uploaded: ${result.id}`));
|
|
2040
|
+
formatOutput(result, program.opts());
|
|
2041
|
+
})
|
|
2042
|
+
);
|
|
2043
|
+
content.command("list").description("List signage content").option("--limit <n>", "Maximum results", "20").action(
|
|
2044
|
+
wrapCommand(async (opts) => {
|
|
2045
|
+
const client = await getClient(program.opts());
|
|
2046
|
+
const result = await client.signage.content.list({
|
|
2047
|
+
limit: parseInt(opts.limit)
|
|
2048
|
+
});
|
|
2049
|
+
formatOutput(result.data, program.opts());
|
|
2050
|
+
})
|
|
2051
|
+
);
|
|
2052
|
+
content.command("delete <id>").description("Delete signage content").action(
|
|
2053
|
+
wrapCommand(async (id) => {
|
|
2054
|
+
const confirmed = await confirmDestructive("delete", `content ${id}`, program.opts());
|
|
2055
|
+
if (!confirmed) return;
|
|
2056
|
+
const client = await getClient(program.opts());
|
|
2057
|
+
await client.signage.content.delete(id);
|
|
2058
|
+
console.log(chalk33.green(`Content ${id} deleted.`));
|
|
2059
|
+
})
|
|
2060
|
+
);
|
|
2061
|
+
const schedule = signage.command("schedule").description("Manage content schedules");
|
|
2062
|
+
schedule.command("create").description("Create a content schedule").requiredOption("--display-id <displayId>", "Target display ID").requiredOption("--content-id <contentId>", "Content ID to schedule").option("--start <datetime>", "Start datetime (ISO 8601)").option("--end <datetime>", "End datetime (ISO 8601)").action(
|
|
2063
|
+
wrapCommand(async (opts) => {
|
|
2064
|
+
const client = await getClient(program.opts());
|
|
2065
|
+
const result = await client.signage.schedules.create({
|
|
2066
|
+
displayId: opts.displayId,
|
|
2067
|
+
contentId: opts.contentId,
|
|
2068
|
+
start: opts.start,
|
|
2069
|
+
end: opts.end
|
|
2070
|
+
});
|
|
2071
|
+
console.log(chalk33.green(`Schedule created: ${result.id}`));
|
|
2072
|
+
formatOutput(result, program.opts());
|
|
2073
|
+
})
|
|
2074
|
+
);
|
|
2075
|
+
schedule.command("list").description("List content schedules").option("--display-id <displayId>", "Filter by display ID").action(
|
|
2076
|
+
wrapCommand(async (opts) => {
|
|
2077
|
+
const client = await getClient(program.opts());
|
|
2078
|
+
const result = await client.signage.schedules.list({
|
|
2079
|
+
displayId: opts.displayId
|
|
2080
|
+
});
|
|
2081
|
+
formatOutput(result.data, program.opts());
|
|
2082
|
+
})
|
|
2083
|
+
);
|
|
2084
|
+
}
|
|
2085
|
+
|
|
2086
|
+
// src/commands/qr/index.ts
|
|
2087
|
+
import chalk34 from "chalk";
|
|
2088
|
+
function registerQrCommands(program) {
|
|
2089
|
+
const qr = program.command("qr").description("QR code generation and scanning");
|
|
2090
|
+
qr.command("create").description("Generate a QR code").requiredOption("--data <data>", "Data to encode in the QR code").option("--format <format>", "Output format (png, svg)", "png").option("--size <size>", "QR code size in pixels", "256").option("--output <path>", "Output file path").action(
|
|
2091
|
+
wrapCommand(async (opts) => {
|
|
2092
|
+
const client = await getClient(program.opts());
|
|
2093
|
+
const result = await client.qr.create({
|
|
2094
|
+
data: opts.data,
|
|
2095
|
+
format: opts.format,
|
|
2096
|
+
size: parseInt(opts.size),
|
|
2097
|
+
output: opts.output
|
|
2098
|
+
});
|
|
2099
|
+
console.log(chalk34.green("QR code generated."));
|
|
2100
|
+
formatOutput(result, program.opts());
|
|
2101
|
+
})
|
|
2102
|
+
);
|
|
2103
|
+
qr.command("scan").description("Scan a QR code from an image").requiredOption("--image-path <path>", "Path to image file containing QR code").action(
|
|
2104
|
+
wrapCommand(async (opts) => {
|
|
2105
|
+
const client = await getClient(program.opts());
|
|
2106
|
+
const result = await client.qr.scan({ imagePath: opts.imagePath });
|
|
2107
|
+
formatOutput(result, program.opts());
|
|
2108
|
+
})
|
|
2109
|
+
);
|
|
2110
|
+
qr.command("list").description("List generated QR codes").option("--limit <n>", "Maximum results", "20").action(
|
|
2111
|
+
wrapCommand(async (opts) => {
|
|
2112
|
+
const client = await getClient(program.opts());
|
|
2113
|
+
const result = await client.qr.list({ limit: parseInt(opts.limit) });
|
|
2114
|
+
formatOutput(result.data, program.opts());
|
|
2115
|
+
})
|
|
2116
|
+
);
|
|
2117
|
+
}
|
|
2118
|
+
|
|
2119
|
+
// src/commands/audience/index.ts
|
|
2120
|
+
import chalk35 from "chalk";
|
|
2121
|
+
function registerAudienceCommands(program) {
|
|
2122
|
+
const audience = program.command("audience").description("Audience engagement tools");
|
|
2123
|
+
const polls = audience.command("polls").description("Manage audience polls");
|
|
2124
|
+
polls.command("create").description("Create an audience poll").requiredOption("--question <question>", "Poll question").requiredOption("--options <options>", "Comma-separated poll options").option("--stream-id <streamId>", "Attach to a stream").action(
|
|
2125
|
+
wrapCommand(async (opts) => {
|
|
2126
|
+
const client = await getClient(program.opts());
|
|
2127
|
+
const options = opts.options.split(",").map((o) => o.trim());
|
|
2128
|
+
const result = await client.audience.polls.create({
|
|
2129
|
+
question: opts.question,
|
|
2130
|
+
options,
|
|
2131
|
+
streamId: opts.streamId
|
|
2132
|
+
});
|
|
2133
|
+
console.log(chalk35.green(`Poll created: ${result.id}`));
|
|
2134
|
+
formatOutput(result, program.opts());
|
|
2135
|
+
})
|
|
2136
|
+
);
|
|
2137
|
+
polls.command("list").description("List polls").option("--stream-id <streamId>", "Filter by stream ID").action(
|
|
2138
|
+
wrapCommand(async (opts) => {
|
|
2139
|
+
const client = await getClient(program.opts());
|
|
2140
|
+
const result = await client.audience.polls.list({
|
|
2141
|
+
streamId: opts.streamId
|
|
2142
|
+
});
|
|
2143
|
+
formatOutput(result.data, program.opts());
|
|
2144
|
+
})
|
|
2145
|
+
);
|
|
2146
|
+
polls.command("results <id>").description("Get poll results").action(
|
|
2147
|
+
wrapCommand(async (id) => {
|
|
2148
|
+
const client = await getClient(program.opts());
|
|
2149
|
+
const result = await client.audience.polls.results(id);
|
|
2150
|
+
formatOutput(result, program.opts());
|
|
2151
|
+
})
|
|
2152
|
+
);
|
|
2153
|
+
polls.command("close <id>").description("Close an active poll").action(
|
|
2154
|
+
wrapCommand(async (id) => {
|
|
2155
|
+
const client = await getClient(program.opts());
|
|
2156
|
+
const result = await client.audience.polls.close(id);
|
|
2157
|
+
console.log(chalk35.green(`Poll ${id} closed.`));
|
|
2158
|
+
formatOutput(result, program.opts());
|
|
2159
|
+
})
|
|
2160
|
+
);
|
|
2161
|
+
const questions = audience.command("questions").description("Manage audience Q&A");
|
|
2162
|
+
questions.command("create").description("Open a Q&A session").option("--stream-id <streamId>", "Attach to a stream").option("--moderated", "Enable moderation", false).action(
|
|
2163
|
+
wrapCommand(async (opts) => {
|
|
2164
|
+
const client = await getClient(program.opts());
|
|
2165
|
+
const result = await client.audience.questions.create({
|
|
2166
|
+
streamId: opts.streamId,
|
|
2167
|
+
moderated: opts.moderated
|
|
2168
|
+
});
|
|
2169
|
+
console.log(chalk35.green(`Q&A session created: ${result.id}`));
|
|
2170
|
+
formatOutput(result, program.opts());
|
|
2171
|
+
})
|
|
2172
|
+
);
|
|
2173
|
+
questions.command("list").description("List questions in a Q&A session").requiredOption("--session-id <sessionId>", "Q&A session ID").action(
|
|
2174
|
+
wrapCommand(async (opts) => {
|
|
2175
|
+
const client = await getClient(program.opts());
|
|
2176
|
+
const result = await client.audience.questions.list({
|
|
2177
|
+
sessionId: opts.sessionId
|
|
2178
|
+
});
|
|
2179
|
+
formatOutput(result.data, program.opts());
|
|
2180
|
+
})
|
|
2181
|
+
);
|
|
2182
|
+
const reactions = audience.command("reactions").description("Manage audience reactions");
|
|
2183
|
+
reactions.command("enable").description("Enable reactions for a stream").requiredOption("--stream-id <streamId>", "Stream ID").action(
|
|
2184
|
+
wrapCommand(async (opts) => {
|
|
2185
|
+
const client = await getClient(program.opts());
|
|
2186
|
+
const result = await client.audience.reactions.enable({
|
|
2187
|
+
streamId: opts.streamId
|
|
2188
|
+
});
|
|
2189
|
+
console.log(chalk35.green("Reactions enabled."));
|
|
2190
|
+
formatOutput(result, program.opts());
|
|
2191
|
+
})
|
|
2192
|
+
);
|
|
2193
|
+
reactions.command("disable").description("Disable reactions for a stream").requiredOption("--stream-id <streamId>", "Stream ID").action(
|
|
2194
|
+
wrapCommand(async (opts) => {
|
|
2195
|
+
const client = await getClient(program.opts());
|
|
2196
|
+
const result = await client.audience.reactions.disable({
|
|
2197
|
+
streamId: opts.streamId
|
|
2198
|
+
});
|
|
2199
|
+
console.log(chalk35.green("Reactions disabled."));
|
|
2200
|
+
formatOutput(result, program.opts());
|
|
2201
|
+
})
|
|
2202
|
+
);
|
|
2203
|
+
}
|
|
2204
|
+
|
|
2205
|
+
// src/commands/creator/index.ts
|
|
2206
|
+
import chalk36 from "chalk";
|
|
2207
|
+
function registerCreatorCommands(program) {
|
|
2208
|
+
const creator = program.command("creator").description("Creator monetization and analytics");
|
|
2209
|
+
creator.command("revenue").description("View revenue summary").option("--period <period>", "Time period (day, week, month)", "month").action(
|
|
2210
|
+
wrapCommand(async (opts) => {
|
|
2211
|
+
const client = await getClient(program.opts());
|
|
2212
|
+
const result = await client.creator.revenue({ period: opts.period });
|
|
2213
|
+
formatOutput(result, program.opts());
|
|
2214
|
+
})
|
|
2215
|
+
);
|
|
2216
|
+
const payouts = creator.command("payouts").description("Manage payouts");
|
|
2217
|
+
payouts.command("list").description("List payout history").option("--limit <n>", "Maximum results", "20").action(
|
|
2218
|
+
wrapCommand(async (opts) => {
|
|
2219
|
+
const client = await getClient(program.opts());
|
|
2220
|
+
const result = await client.creator.payouts.list({
|
|
2221
|
+
limit: parseInt(opts.limit)
|
|
2222
|
+
});
|
|
2223
|
+
formatOutput(result.data, program.opts());
|
|
2224
|
+
})
|
|
2225
|
+
);
|
|
2226
|
+
payouts.command("request").description("Request a payout").option("--amount <amount>", "Payout amount (uses full balance if omitted)").action(
|
|
2227
|
+
wrapCommand(async (opts) => {
|
|
2228
|
+
const client = await getClient(program.opts());
|
|
2229
|
+
const params = {};
|
|
2230
|
+
if (opts.amount) params.amount = parseFloat(opts.amount);
|
|
2231
|
+
const result = await client.creator.payouts.request(params);
|
|
2232
|
+
console.log(chalk36.green("Payout requested."));
|
|
2233
|
+
formatOutput(result, program.opts());
|
|
2234
|
+
})
|
|
2235
|
+
);
|
|
2236
|
+
creator.command("analytics").description("View creator analytics").option("--period <period>", "Time period (day, week, month)", "month").action(
|
|
2237
|
+
wrapCommand(async (opts) => {
|
|
2238
|
+
const client = await getClient(program.opts());
|
|
2239
|
+
const result = await client.creator.analytics({ period: opts.period });
|
|
2240
|
+
formatOutput(result, program.opts());
|
|
2241
|
+
})
|
|
2242
|
+
);
|
|
2243
|
+
}
|
|
2244
|
+
|
|
2245
|
+
// src/commands/podcast/index.ts
|
|
2246
|
+
import chalk37 from "chalk";
|
|
2247
|
+
function registerPodcastCommands(program) {
|
|
2248
|
+
const podcast = program.command("podcast").description("Podcast management");
|
|
2249
|
+
const episodes = podcast.command("episodes").description("Manage podcast episodes");
|
|
2250
|
+
episodes.command("create").description("Create a podcast episode").requiredOption("--title <title>", "Episode title").option("--description <desc>", "Episode description").option("--audio <path>", "Path to audio file").action(
|
|
2251
|
+
wrapCommand(async (opts) => {
|
|
2252
|
+
const client = await getClient(program.opts());
|
|
2253
|
+
const result = await client.podcast.episodes.create({
|
|
2254
|
+
title: opts.title,
|
|
2255
|
+
description: opts.description,
|
|
2256
|
+
audioPath: opts.audio
|
|
2257
|
+
});
|
|
2258
|
+
console.log(chalk37.green(`Episode created: ${result.id}`));
|
|
2259
|
+
formatOutput(result, program.opts());
|
|
2260
|
+
})
|
|
2261
|
+
);
|
|
2262
|
+
episodes.command("list").description("List podcast episodes").option("--limit <n>", "Maximum results", "20").action(
|
|
2263
|
+
wrapCommand(async (opts) => {
|
|
2264
|
+
const client = await getClient(program.opts());
|
|
2265
|
+
const result = await client.podcast.episodes.list({
|
|
2266
|
+
limit: parseInt(opts.limit)
|
|
2267
|
+
});
|
|
2268
|
+
formatOutput(result.data, program.opts());
|
|
2269
|
+
})
|
|
2270
|
+
);
|
|
2271
|
+
episodes.command("get <id>").description("Get episode details").action(
|
|
2272
|
+
wrapCommand(async (id) => {
|
|
2273
|
+
const client = await getClient(program.opts());
|
|
2274
|
+
const result = await client.podcast.episodes.get(id);
|
|
2275
|
+
formatOutput(result, program.opts());
|
|
2276
|
+
})
|
|
2277
|
+
);
|
|
2278
|
+
episodes.command("publish <id>").description("Publish a podcast episode").action(
|
|
2279
|
+
wrapCommand(async (id) => {
|
|
2280
|
+
const client = await getClient(program.opts());
|
|
2281
|
+
const result = await client.podcast.episodes.publish(id);
|
|
2282
|
+
console.log(chalk37.green(`Episode ${id} published.`));
|
|
2283
|
+
formatOutput(result, program.opts());
|
|
2284
|
+
})
|
|
2285
|
+
);
|
|
2286
|
+
episodes.command("delete <id>").description("Delete a podcast episode").action(
|
|
2287
|
+
wrapCommand(async (id) => {
|
|
2288
|
+
const confirmed = await confirmDestructive("delete", `episode ${id}`, program.opts());
|
|
2289
|
+
if (!confirmed) return;
|
|
2290
|
+
const client = await getClient(program.opts());
|
|
2291
|
+
await client.podcast.episodes.delete(id);
|
|
2292
|
+
console.log(chalk37.green(`Episode ${id} deleted.`));
|
|
2293
|
+
})
|
|
2294
|
+
);
|
|
2295
|
+
const rss = podcast.command("rss").description("Manage podcast RSS feed");
|
|
2296
|
+
rss.command("generate").description("Generate an RSS feed").action(
|
|
2297
|
+
wrapCommand(async () => {
|
|
2298
|
+
const client = await getClient(program.opts());
|
|
2299
|
+
const result = await client.podcast.rss.generate();
|
|
2300
|
+
console.log(chalk37.green("RSS feed generated."));
|
|
2301
|
+
formatOutput(result, program.opts());
|
|
2302
|
+
})
|
|
2303
|
+
);
|
|
2304
|
+
rss.command("get").description("Get the current RSS feed URL").action(
|
|
2305
|
+
wrapCommand(async () => {
|
|
2306
|
+
const client = await getClient(program.opts());
|
|
2307
|
+
const result = await client.podcast.rss.get();
|
|
2308
|
+
formatOutput(result, program.opts());
|
|
2309
|
+
})
|
|
2310
|
+
);
|
|
2311
|
+
podcast.command("distribute").description("Distribute podcast to platforms").option("--platforms <platforms>", "Comma-separated platforms (spotify, apple, google)").action(
|
|
2312
|
+
wrapCommand(async (opts) => {
|
|
2313
|
+
const client = await getClient(program.opts());
|
|
2314
|
+
const platforms = opts.platforms ? opts.platforms.split(",").map((p) => p.trim()) : void 0;
|
|
2315
|
+
const result = await client.podcast.distribute({ platforms });
|
|
2316
|
+
console.log(chalk37.green("Distribution initiated."));
|
|
2317
|
+
formatOutput(result, program.opts());
|
|
2318
|
+
})
|
|
2319
|
+
);
|
|
2320
|
+
}
|
|
2321
|
+
|
|
2322
|
+
// src/commands/slides/index.ts
|
|
2323
|
+
import chalk38 from "chalk";
|
|
2324
|
+
function registerSlidesCommands(program) {
|
|
2325
|
+
const slides = program.command("slides").description("Slide presentation tools");
|
|
2326
|
+
slides.command("convert").description("Convert a presentation file for streaming").requiredOption("--input-file <path>", "Path to presentation file (pptx, pdf, keynote)").option("--output <path>", "Output directory").action(
|
|
2327
|
+
wrapCommand(async (opts) => {
|
|
2328
|
+
const client = await getClient(program.opts());
|
|
2329
|
+
const result = await client.slides.convert({
|
|
2330
|
+
inputFile: opts.inputFile,
|
|
2331
|
+
output: opts.output
|
|
2332
|
+
});
|
|
2333
|
+
console.log(chalk38.green("Presentation converted."));
|
|
2334
|
+
formatOutput(result, program.opts());
|
|
2335
|
+
})
|
|
2336
|
+
);
|
|
2337
|
+
slides.command("present").description("Start a slide presentation in a production").requiredOption("--production-id <productionId>", "Production ID").option("--slide-deck <id>", "Slide deck ID").action(
|
|
2338
|
+
wrapCommand(async (opts) => {
|
|
2339
|
+
const client = await getClient(program.opts());
|
|
2340
|
+
const result = await client.slides.present({
|
|
2341
|
+
productionId: opts.productionId,
|
|
2342
|
+
slideDeckId: opts.slideDeck
|
|
2343
|
+
});
|
|
2344
|
+
console.log(chalk38.green("Presentation started."));
|
|
2345
|
+
formatOutput(result, program.opts());
|
|
2346
|
+
})
|
|
2347
|
+
);
|
|
2348
|
+
slides.command("annotate").description("Enable slide annotations").requiredOption("--production-id <productionId>", "Production ID").option("--tool <tool>", "Annotation tool (pen, highlight, pointer)", "pen").action(
|
|
2349
|
+
wrapCommand(async (opts) => {
|
|
2350
|
+
const client = await getClient(program.opts());
|
|
2351
|
+
const result = await client.slides.annotate({
|
|
2352
|
+
productionId: opts.productionId,
|
|
2353
|
+
tool: opts.tool
|
|
2354
|
+
});
|
|
2355
|
+
console.log(chalk38.green("Annotations enabled."));
|
|
2356
|
+
formatOutput(result, program.opts());
|
|
2357
|
+
})
|
|
2358
|
+
);
|
|
2359
|
+
}
|
|
2360
|
+
|
|
2361
|
+
// src/commands/usb/index.ts
|
|
2362
|
+
import chalk39 from "chalk";
|
|
2363
|
+
function registerUsbCommands(program) {
|
|
2364
|
+
const usb = program.command("usb").description("USB device relay and management");
|
|
2365
|
+
const relay = usb.command("relay").description("Manage USB relay");
|
|
2366
|
+
relay.command("start").description("Start USB relay for virtual camera/microphone").option("--device <id>", "Specific device ID").action(
|
|
2367
|
+
wrapCommand(async (opts) => {
|
|
2368
|
+
const client = await getClient(program.opts());
|
|
2369
|
+
const result = await client.usb.relay.start({ deviceId: opts.device });
|
|
2370
|
+
console.log(chalk39.green("USB relay started."));
|
|
2371
|
+
formatOutput(result, program.opts());
|
|
2372
|
+
})
|
|
2373
|
+
);
|
|
2374
|
+
relay.command("stop").description("Stop USB relay").action(
|
|
2375
|
+
wrapCommand(async () => {
|
|
2376
|
+
const client = await getClient(program.opts());
|
|
2377
|
+
const result = await client.usb.relay.stop();
|
|
2378
|
+
console.log(chalk39.green("USB relay stopped."));
|
|
2379
|
+
formatOutput(result, program.opts());
|
|
2380
|
+
})
|
|
2381
|
+
);
|
|
2382
|
+
usb.command("devices").description("List connected USB devices").action(
|
|
2383
|
+
wrapCommand(async () => {
|
|
2384
|
+
const client = await getClient(program.opts());
|
|
2385
|
+
const result = await client.usb.devices.list();
|
|
2386
|
+
formatOutput(result.data, program.opts());
|
|
2387
|
+
})
|
|
2388
|
+
);
|
|
2389
|
+
usb.command("configure <id>").description("Configure a USB device").option("--settings <json>", "JSON settings for the device").action(
|
|
2390
|
+
wrapCommand(async (id, opts) => {
|
|
2391
|
+
const client = await getClient(program.opts());
|
|
2392
|
+
const settings = opts.settings ? JSON.parse(opts.settings) : void 0;
|
|
2393
|
+
const result = await client.usb.configure(id, { settings });
|
|
2394
|
+
console.log(chalk39.green(`Device ${id} configured.`));
|
|
2395
|
+
formatOutput(result, program.opts());
|
|
2396
|
+
})
|
|
2397
|
+
);
|
|
2398
|
+
}
|
|
2399
|
+
|
|
2400
|
+
// src/commands/notify/index.ts
|
|
2401
|
+
import chalk40 from "chalk";
|
|
2402
|
+
function registerNotifyCommands(program) {
|
|
2403
|
+
const notify = program.command("notify").description("Notification management");
|
|
2404
|
+
notify.command("send").description("Send a notification").requiredOption("--to <userId>", "Recipient user ID").requiredOption("--template <templateId>", "Notification template ID").option("--data <json>", "JSON data for template variables").action(
|
|
2405
|
+
wrapCommand(async (opts) => {
|
|
2406
|
+
const client = await getClient(program.opts());
|
|
2407
|
+
const data = opts.data ? JSON.parse(opts.data) : void 0;
|
|
2408
|
+
const result = await client.notifications.send({
|
|
2409
|
+
to: opts.to,
|
|
2410
|
+
template: opts.template,
|
|
2411
|
+
data
|
|
2412
|
+
});
|
|
2413
|
+
console.log(chalk40.green("Notification sent."));
|
|
2414
|
+
formatOutput(result, program.opts());
|
|
2415
|
+
})
|
|
2416
|
+
);
|
|
2417
|
+
const templates = notify.command("templates").description("Manage notification templates");
|
|
2418
|
+
templates.command("list").description("List notification templates").option("--limit <n>", "Maximum results", "20").action(
|
|
2419
|
+
wrapCommand(async (opts) => {
|
|
2420
|
+
const client = await getClient(program.opts());
|
|
2421
|
+
const result = await client.notifications.templates.list({
|
|
2422
|
+
limit: parseInt(opts.limit)
|
|
2423
|
+
});
|
|
2424
|
+
formatOutput(result.data, program.opts());
|
|
2425
|
+
})
|
|
2426
|
+
);
|
|
2427
|
+
templates.command("get <id>").description("Get a notification template").action(
|
|
2428
|
+
wrapCommand(async (id) => {
|
|
2429
|
+
const client = await getClient(program.opts());
|
|
2430
|
+
const result = await client.notifications.templates.get(id);
|
|
2431
|
+
formatOutput(result, program.opts());
|
|
2432
|
+
})
|
|
2433
|
+
);
|
|
2434
|
+
templates.command("create").description("Create a notification template").requiredOption("--name <name>", "Template name").requiredOption("--body <body>", "Template body").option("--subject <subject>", "Template subject (for email)").action(
|
|
2435
|
+
wrapCommand(async (opts) => {
|
|
2436
|
+
const client = await getClient(program.opts());
|
|
2437
|
+
const result = await client.notifications.templates.create({
|
|
2438
|
+
name: opts.name,
|
|
2439
|
+
body: opts.body,
|
|
2440
|
+
subject: opts.subject
|
|
2441
|
+
});
|
|
2442
|
+
console.log(chalk40.green(`Template created: ${result.id}`));
|
|
2443
|
+
formatOutput(result, program.opts());
|
|
2444
|
+
})
|
|
2445
|
+
);
|
|
2446
|
+
const channels = notify.command("channels").description("Manage notification channels");
|
|
2447
|
+
channels.command("list").description("List notification channels").action(
|
|
2448
|
+
wrapCommand(async () => {
|
|
2449
|
+
const client = await getClient(program.opts());
|
|
2450
|
+
const result = await client.notifications.channels.list();
|
|
2451
|
+
formatOutput(result.data, program.opts());
|
|
2452
|
+
})
|
|
2453
|
+
);
|
|
2454
|
+
}
|
|
2455
|
+
|
|
2456
|
+
// src/commands/drm/index.ts
|
|
2457
|
+
import chalk41 from "chalk";
|
|
2458
|
+
function registerDrmCommands(program) {
|
|
2459
|
+
const drm = program.command("drm").description("Digital rights management");
|
|
2460
|
+
const licenses = drm.command("licenses").description("Manage DRM licenses");
|
|
2461
|
+
licenses.command("list").description("List DRM licenses").option("--limit <n>", "Maximum results", "20").action(
|
|
2462
|
+
wrapCommand(async (opts) => {
|
|
2463
|
+
const client = await getClient(program.opts());
|
|
2464
|
+
const result = await client.drm.licenses.list({
|
|
2465
|
+
limit: parseInt(opts.limit)
|
|
2466
|
+
});
|
|
2467
|
+
formatOutput(result.data, program.opts());
|
|
2468
|
+
})
|
|
2469
|
+
);
|
|
2470
|
+
licenses.command("create").description("Create a DRM license").requiredOption("--content-id <contentId>", "Content ID").requiredOption("--policy-id <policyId>", "Policy ID").action(
|
|
2471
|
+
wrapCommand(async (opts) => {
|
|
2472
|
+
const client = await getClient(program.opts());
|
|
2473
|
+
const result = await client.drm.licenses.create({
|
|
2474
|
+
contentId: opts.contentId,
|
|
2475
|
+
policyId: opts.policyId
|
|
2476
|
+
});
|
|
2477
|
+
console.log(chalk41.green(`License created: ${result.id}`));
|
|
2478
|
+
formatOutput(result, program.opts());
|
|
2479
|
+
})
|
|
2480
|
+
);
|
|
2481
|
+
licenses.command("revoke <id>").description("Revoke a DRM license").action(
|
|
2482
|
+
wrapCommand(async (id) => {
|
|
2483
|
+
const confirmed = await confirmDestructive("revoke", `license ${id}`, program.opts());
|
|
2484
|
+
if (!confirmed) return;
|
|
2485
|
+
const client = await getClient(program.opts());
|
|
2486
|
+
await client.drm.licenses.revoke(id);
|
|
2487
|
+
console.log(chalk41.green(`License ${id} revoked.`));
|
|
2488
|
+
})
|
|
2489
|
+
);
|
|
2490
|
+
const policies = drm.command("policies").description("Manage DRM policies");
|
|
2491
|
+
policies.command("list").description("List DRM policies").option("--limit <n>", "Maximum results", "20").action(
|
|
2492
|
+
wrapCommand(async (opts) => {
|
|
2493
|
+
const client = await getClient(program.opts());
|
|
2494
|
+
const result = await client.drm.policies.list({
|
|
2495
|
+
limit: parseInt(opts.limit)
|
|
2496
|
+
});
|
|
2497
|
+
formatOutput(result.data, program.opts());
|
|
2498
|
+
})
|
|
2499
|
+
);
|
|
2500
|
+
policies.command("create").description("Create a DRM policy").requiredOption("--name <name>", "Policy name").option("--rules <json>", "JSON rules for the policy").action(
|
|
2501
|
+
wrapCommand(async (opts) => {
|
|
2502
|
+
const client = await getClient(program.opts());
|
|
2503
|
+
const rules = opts.rules ? JSON.parse(opts.rules) : void 0;
|
|
2504
|
+
const result = await client.drm.policies.create({
|
|
2505
|
+
name: opts.name,
|
|
2506
|
+
rules
|
|
2507
|
+
});
|
|
2508
|
+
console.log(chalk41.green(`Policy created: ${result.id}`));
|
|
2509
|
+
formatOutput(result, program.opts());
|
|
2510
|
+
})
|
|
2511
|
+
);
|
|
2512
|
+
policies.command("update <id>").description("Update a DRM policy").option("--name <name>", "New policy name").option("--rules <json>", "Updated JSON rules").action(
|
|
2513
|
+
wrapCommand(async (id, opts) => {
|
|
2514
|
+
const client = await getClient(program.opts());
|
|
2515
|
+
const updates = {};
|
|
2516
|
+
if (opts.name) updates.name = opts.name;
|
|
2517
|
+
if (opts.rules) updates.rules = JSON.parse(opts.rules);
|
|
2518
|
+
const result = await client.drm.policies.update(id, updates);
|
|
2519
|
+
console.log(chalk41.green(`Policy ${id} updated.`));
|
|
2520
|
+
formatOutput(result, program.opts());
|
|
2521
|
+
})
|
|
2522
|
+
);
|
|
2523
|
+
policies.command("delete <id>").description("Delete a DRM policy").action(
|
|
2524
|
+
wrapCommand(async (id) => {
|
|
2525
|
+
const confirmed = await confirmDestructive("delete", `policy ${id}`, program.opts());
|
|
2526
|
+
if (!confirmed) return;
|
|
2527
|
+
const client = await getClient(program.opts());
|
|
2528
|
+
await client.drm.policies.delete(id);
|
|
2529
|
+
console.log(chalk41.green(`Policy ${id} deleted.`));
|
|
2530
|
+
})
|
|
2531
|
+
);
|
|
2532
|
+
const keys = drm.command("keys").description("Manage encryption keys");
|
|
2533
|
+
keys.command("rotate").description("Rotate encryption keys").option("--content-id <contentId>", "Rotate keys for specific content").action(
|
|
2534
|
+
wrapCommand(async (opts) => {
|
|
2535
|
+
const confirmed = await confirmDestructive(
|
|
2536
|
+
"rotate encryption keys for",
|
|
2537
|
+
opts.contentId ?? "all content",
|
|
2538
|
+
program.opts()
|
|
2539
|
+
);
|
|
2540
|
+
if (!confirmed) return;
|
|
2541
|
+
const client = await getClient(program.opts());
|
|
2542
|
+
const result = await client.drm.keys.rotate({
|
|
2543
|
+
contentId: opts.contentId
|
|
2544
|
+
});
|
|
2545
|
+
console.log(chalk41.green("Key rotation initiated."));
|
|
2546
|
+
formatOutput(result, program.opts());
|
|
2547
|
+
})
|
|
2548
|
+
);
|
|
2549
|
+
}
|
|
2550
|
+
|
|
2551
|
+
// src/commands/billing/index.ts
|
|
2552
|
+
import chalk42 from "chalk";
|
|
2553
|
+
async function billingFetch(path, opts) {
|
|
2554
|
+
const config = await loadConfig();
|
|
2555
|
+
const project = config.projects[config.currentProject];
|
|
2556
|
+
const baseUrl = project?.baseUrl ?? process.env["WAVE_BASE_URL"] ?? "https://wave.online";
|
|
2557
|
+
const apiKey = await getApiKey(config.currentProject);
|
|
2558
|
+
if (!apiKey) {
|
|
2559
|
+
throw new Error(`No API key found. Run ${chalk42.bold("wave login")} to authenticate.`);
|
|
2560
|
+
}
|
|
2561
|
+
const res = await fetch(`${baseUrl}${path}`, {
|
|
2562
|
+
method: opts?.method ?? "GET",
|
|
2563
|
+
headers: {
|
|
2564
|
+
Authorization: `Bearer ${apiKey}`,
|
|
2565
|
+
"Content-Type": "application/json",
|
|
2566
|
+
"X-Wave-Source": "cli"
|
|
2567
|
+
},
|
|
2568
|
+
body: opts?.body ? JSON.stringify(opts.body) : void 0
|
|
2569
|
+
});
|
|
2570
|
+
if (!res.ok) {
|
|
2571
|
+
const error = await res.json().catch(() => ({}));
|
|
2572
|
+
throw new Error(error.message ?? `Billing API error: ${res.status} ${res.statusText}`);
|
|
2573
|
+
}
|
|
2574
|
+
return res.json();
|
|
2575
|
+
}
|
|
2576
|
+
function registerBillingCommands(program) {
|
|
2577
|
+
const billing = program.command("billing").description("Billing, usage, and subscription management");
|
|
2578
|
+
billing.command("status").description("Show current billing status and plan").action(
|
|
2579
|
+
wrapCommand(async () => {
|
|
2580
|
+
const result = await billingFetch("/api/billing/status");
|
|
2581
|
+
formatOutput(result, program.opts());
|
|
2582
|
+
})
|
|
2583
|
+
);
|
|
2584
|
+
billing.command("usage").description("Show current usage metrics").option("--period <period>", "Billing period (current, previous)", "current").action(
|
|
2585
|
+
wrapCommand(async (opts) => {
|
|
2586
|
+
const result = await billingFetch(`/api/billing/usage?period=${opts.period}`);
|
|
2587
|
+
formatOutput(result, program.opts());
|
|
2588
|
+
})
|
|
2589
|
+
);
|
|
2590
|
+
billing.command("invoices").description("List billing invoices").option("--limit <n>", "Maximum results", "10").action(
|
|
2591
|
+
wrapCommand(async (opts) => {
|
|
2592
|
+
const result = await billingFetch(`/api/billing/invoices?limit=${opts.limit}`);
|
|
2593
|
+
formatOutput(result, program.opts());
|
|
2594
|
+
})
|
|
2595
|
+
);
|
|
2596
|
+
billing.command("limits").description("Show current usage limits").action(
|
|
2597
|
+
wrapCommand(async () => {
|
|
2598
|
+
const result = await billingFetch("/api/billing/limits");
|
|
2599
|
+
formatOutput(result, program.opts());
|
|
2600
|
+
})
|
|
2601
|
+
);
|
|
2602
|
+
billing.command("portal").description("Open the billing portal in your browser").action(
|
|
2603
|
+
wrapCommand(async () => {
|
|
2604
|
+
const result = await billingFetch("/api/billing/portal", {
|
|
2605
|
+
method: "POST"
|
|
2606
|
+
});
|
|
2607
|
+
const open2 = (await import("open")).default;
|
|
2608
|
+
await open2(result.url);
|
|
2609
|
+
console.log(chalk42.green("Billing portal opened in your browser."));
|
|
2610
|
+
})
|
|
2611
|
+
);
|
|
2612
|
+
billing.command("upgrade").description("View available upgrade options").action(
|
|
2613
|
+
wrapCommand(async () => {
|
|
2614
|
+
const result = await billingFetch("/api/billing/plans");
|
|
2615
|
+
formatOutput(result, program.opts());
|
|
2616
|
+
console.log(chalk42.gray("\nTo upgrade, visit the billing portal: wave billing portal"));
|
|
2617
|
+
})
|
|
2618
|
+
);
|
|
2619
|
+
}
|
|
2620
|
+
|
|
2621
|
+
// src/commands/listen/index.ts
|
|
2622
|
+
import chalk43 from "chalk";
|
|
2623
|
+
|
|
2624
|
+
// src/lib/sse-client.ts
|
|
2625
|
+
async function connectSSE(url, apiKey, options) {
|
|
2626
|
+
const controller = new AbortController();
|
|
2627
|
+
const maxAttempts = options.maxReconnectAttempts ?? 10;
|
|
2628
|
+
const initialDelay = options.initialReconnectDelay ?? 1e3;
|
|
2629
|
+
const maxDelay = options.maxReconnectDelay ?? 3e4;
|
|
2630
|
+
let reconnectAttempts = 0;
|
|
2631
|
+
let lastEventId;
|
|
2632
|
+
const cleanup = () => {
|
|
2633
|
+
controller.abort();
|
|
2634
|
+
};
|
|
2635
|
+
process.on("SIGINT", cleanup);
|
|
2636
|
+
process.on("SIGTERM", cleanup);
|
|
2637
|
+
async function connect() {
|
|
2638
|
+
if (controller.signal.aborted) return;
|
|
2639
|
+
try {
|
|
2640
|
+
const headers = {
|
|
2641
|
+
Accept: "text/event-stream",
|
|
2642
|
+
"Cache-Control": "no-cache",
|
|
2643
|
+
Authorization: `Bearer ${apiKey}`
|
|
2644
|
+
};
|
|
2645
|
+
if (lastEventId) {
|
|
2646
|
+
headers["Last-Event-ID"] = lastEventId;
|
|
2647
|
+
}
|
|
2648
|
+
const response = await fetch(url, {
|
|
2649
|
+
headers,
|
|
2650
|
+
signal: controller.signal
|
|
2651
|
+
});
|
|
2652
|
+
if (!response.ok) {
|
|
2653
|
+
throw new Error(`SSE connection failed: ${response.status} ${response.statusText}`);
|
|
2654
|
+
}
|
|
2655
|
+
if (!response.body) {
|
|
2656
|
+
throw new Error("SSE response has no body");
|
|
2657
|
+
}
|
|
2658
|
+
reconnectAttempts = 0;
|
|
2659
|
+
options.onConnect?.();
|
|
2660
|
+
const reader = response.body.getReader();
|
|
2661
|
+
const decoder = new TextDecoder();
|
|
2662
|
+
let buffer = "";
|
|
2663
|
+
let currentEvent = {};
|
|
2664
|
+
while (!controller.signal.aborted) {
|
|
2665
|
+
const { done, value } = await reader.read();
|
|
2666
|
+
if (done) break;
|
|
2667
|
+
buffer += decoder.decode(value, { stream: true });
|
|
2668
|
+
const lines = buffer.split("\n");
|
|
2669
|
+
buffer = lines.pop() ?? "";
|
|
2670
|
+
for (const line of lines) {
|
|
2671
|
+
if (line === "") {
|
|
2672
|
+
if (currentEvent.data !== void 0) {
|
|
2673
|
+
const event = {
|
|
2674
|
+
id: currentEvent.id,
|
|
2675
|
+
event: currentEvent.event,
|
|
2676
|
+
data: currentEvent.data,
|
|
2677
|
+
retry: currentEvent.retry
|
|
2678
|
+
};
|
|
2679
|
+
if (event.id) {
|
|
2680
|
+
lastEventId = event.id;
|
|
2681
|
+
}
|
|
2682
|
+
options.onEvent(event);
|
|
2683
|
+
}
|
|
2684
|
+
currentEvent = {};
|
|
2685
|
+
} else if (line.startsWith(":")) {
|
|
2686
|
+
} else if (line.startsWith("data:")) {
|
|
2687
|
+
const value2 = line.slice(5).trimStart();
|
|
2688
|
+
currentEvent.data = currentEvent.data !== void 0 ? `${currentEvent.data}
|
|
2689
|
+
${value2}` : value2;
|
|
2690
|
+
} else if (line.startsWith("event:")) {
|
|
2691
|
+
currentEvent.event = line.slice(6).trimStart();
|
|
2692
|
+
} else if (line.startsWith("id:")) {
|
|
2693
|
+
currentEvent.id = line.slice(3).trimStart();
|
|
2694
|
+
} else if (line.startsWith("retry:")) {
|
|
2695
|
+
const retry = parseInt(line.slice(6).trimStart(), 10);
|
|
2696
|
+
if (!isNaN(retry)) {
|
|
2697
|
+
currentEvent.retry = retry;
|
|
2698
|
+
}
|
|
2699
|
+
}
|
|
2700
|
+
}
|
|
2701
|
+
}
|
|
2702
|
+
} catch (error) {
|
|
2703
|
+
if (controller.signal.aborted) {
|
|
2704
|
+
options.onClose?.();
|
|
2705
|
+
return;
|
|
2706
|
+
}
|
|
2707
|
+
const err = error instanceof Error ? error : new Error(String(error));
|
|
2708
|
+
options.onError?.(err);
|
|
2709
|
+
if (reconnectAttempts < maxAttempts) {
|
|
2710
|
+
reconnectAttempts++;
|
|
2711
|
+
const delay = Math.min(initialDelay * Math.pow(2, reconnectAttempts - 1), maxDelay);
|
|
2712
|
+
await new Promise((resolve4) => setTimeout(resolve4, delay));
|
|
2713
|
+
if (!controller.signal.aborted) {
|
|
2714
|
+
return connect();
|
|
2715
|
+
}
|
|
2716
|
+
} else {
|
|
2717
|
+
options.onError?.(new Error(`Max reconnect attempts (${maxAttempts}) exceeded`));
|
|
2718
|
+
options.onClose?.();
|
|
2719
|
+
}
|
|
2720
|
+
}
|
|
2721
|
+
}
|
|
2722
|
+
connect().catch((error) => {
|
|
2723
|
+
const err = error instanceof Error ? error : new Error(String(error));
|
|
2724
|
+
options.onError?.(err);
|
|
2725
|
+
});
|
|
2726
|
+
controller.signal.addEventListener("abort", () => {
|
|
2727
|
+
process.removeListener("SIGINT", cleanup);
|
|
2728
|
+
process.removeListener("SIGTERM", cleanup);
|
|
2729
|
+
});
|
|
2730
|
+
return controller;
|
|
2731
|
+
}
|
|
2732
|
+
|
|
2733
|
+
// src/commands/listen/index.ts
|
|
2734
|
+
function registerListenCommands(program) {
|
|
2735
|
+
program.command("listen").description("Listen for webhook events from WAVE").option("--forward-to <url>", "URL to forward events to", "http://localhost:3000/webhooks/wave").option("--events <pattern>", "Event pattern to listen for (e.g., stream.*)").action(
|
|
2736
|
+
wrapCommand(async (opts) => {
|
|
2737
|
+
const config = await loadConfig();
|
|
2738
|
+
const apiKey = await getApiKey(config.currentProject);
|
|
2739
|
+
if (!apiKey) {
|
|
2740
|
+
console.error(chalk43.red("Not authenticated. Run `wave login` first."));
|
|
2741
|
+
process.exit(1);
|
|
2742
|
+
}
|
|
2743
|
+
const baseUrl = config.projects[config.currentProject]?.baseUrl ?? "https://wave.online";
|
|
2744
|
+
const params = new URLSearchParams();
|
|
2745
|
+
if (opts.events) params.set("events", opts.events);
|
|
2746
|
+
const url = `${baseUrl}/api/webhooks/listen${params.toString() ? `?${params}` : ""}`;
|
|
2747
|
+
console.log(chalk43.bold("WAVE Event Listener\n"));
|
|
2748
|
+
console.log(` Forward URL: ${chalk43.cyan(opts.forwardTo)}`);
|
|
2749
|
+
if (opts.events) console.log(` Event filter: ${chalk43.cyan(opts.events)}`);
|
|
2750
|
+
console.log("");
|
|
2751
|
+
let eventCount = 0;
|
|
2752
|
+
const controller = await connectSSE(url, apiKey, {
|
|
2753
|
+
onConnect() {
|
|
2754
|
+
console.log(chalk43.green(" Connected to event stream."));
|
|
2755
|
+
console.log(chalk43.gray(" Waiting for events... (Ctrl+C to stop)\n"));
|
|
2756
|
+
},
|
|
2757
|
+
onEvent(event) {
|
|
2758
|
+
if (event.event === "connected" || event.event === "timeout") return;
|
|
2759
|
+
if (event.event === "webhook") {
|
|
2760
|
+
eventCount++;
|
|
2761
|
+
const data = JSON.parse(event.data);
|
|
2762
|
+
const timestamp = (/* @__PURE__ */ new Date()).toISOString().slice(11, 23);
|
|
2763
|
+
const eventType = String(data.type ?? "unknown");
|
|
2764
|
+
console.log(`${chalk43.gray(timestamp)} ${chalk43.cyan(`[${eventType}]`)} ${chalk43.white(JSON.stringify(data).slice(0, 120))}`);
|
|
2765
|
+
fetch(opts.forwardTo, {
|
|
2766
|
+
method: "POST",
|
|
2767
|
+
headers: { "Content-Type": "application/json" },
|
|
2768
|
+
body: JSON.stringify(data)
|
|
2769
|
+
}).catch((err) => {
|
|
2770
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
2771
|
+
console.error(chalk43.red(` Forward failed: ${msg}`));
|
|
2772
|
+
});
|
|
2773
|
+
}
|
|
2774
|
+
},
|
|
2775
|
+
onError(error) {
|
|
2776
|
+
console.error(chalk43.red(` Connection error: ${error.message}`));
|
|
2777
|
+
},
|
|
2778
|
+
onClose() {
|
|
2779
|
+
console.log(chalk43.gray(`
|
|
2780
|
+
Disconnected. ${eventCount} events received.`));
|
|
2781
|
+
}
|
|
2782
|
+
});
|
|
2783
|
+
await new Promise((resolve4) => {
|
|
2784
|
+
controller.signal.addEventListener("abort", () => resolve4());
|
|
2785
|
+
});
|
|
2786
|
+
})
|
|
2787
|
+
);
|
|
2788
|
+
}
|
|
2789
|
+
|
|
2790
|
+
// src/commands/logs/index.ts
|
|
2791
|
+
import chalk44 from "chalk";
|
|
2792
|
+
var LEVEL_COLORS = {
|
|
2793
|
+
debug: chalk44.gray,
|
|
2794
|
+
info: chalk44.white,
|
|
2795
|
+
warn: chalk44.yellow,
|
|
2796
|
+
error: chalk44.red
|
|
2797
|
+
};
|
|
2798
|
+
function registerLogsCommands(program) {
|
|
2799
|
+
const logs = program.command("logs").description("Stream application logs");
|
|
2800
|
+
logs.command("tail").description("Tail logs in real-time").option("--stream <id>", "Filter by stream ID").option("--level <level>", "Minimum log level (debug, info, warn, error)", "info").option("--since <duration>", "Show logs since duration (e.g., 5m, 1h)", "5m").action(
|
|
2801
|
+
wrapCommand(async (opts) => {
|
|
2802
|
+
const config = await loadConfig();
|
|
2803
|
+
const apiKey = await getApiKey(config.currentProject);
|
|
2804
|
+
if (!apiKey) {
|
|
2805
|
+
console.error(
|
|
2806
|
+
chalk44.red("Not authenticated. Run `wave login` first.")
|
|
2807
|
+
);
|
|
2808
|
+
process.exit(1);
|
|
2809
|
+
}
|
|
2810
|
+
const baseUrl = config.projects[config.currentProject]?.baseUrl ?? "https://wave.online";
|
|
2811
|
+
const params = new URLSearchParams();
|
|
2812
|
+
params.set("level", opts.level);
|
|
2813
|
+
if (opts.stream) params.set("stream", opts.stream);
|
|
2814
|
+
if (opts.since) params.set("since", opts.since);
|
|
2815
|
+
const url = `${baseUrl}/api/logs/stream?${params}`;
|
|
2816
|
+
console.log(chalk44.bold("WAVE Log Tail\n"));
|
|
2817
|
+
console.log(` Level: ${chalk44.cyan(opts.level)}`);
|
|
2818
|
+
console.log(` Since: ${chalk44.cyan(opts.since)}`);
|
|
2819
|
+
if (opts.stream) {
|
|
2820
|
+
console.log(` Stream: ${chalk44.cyan(opts.stream)}`);
|
|
2821
|
+
}
|
|
2822
|
+
console.log("");
|
|
2823
|
+
let logCount = 0;
|
|
2824
|
+
const controller = await connectSSE(url, apiKey, {
|
|
2825
|
+
onConnect() {
|
|
2826
|
+
console.log(chalk44.green(" Connected to log stream."));
|
|
2827
|
+
console.log(chalk44.gray(" Tailing logs... (Ctrl+C to stop)\n"));
|
|
2828
|
+
},
|
|
2829
|
+
onEvent(event) {
|
|
2830
|
+
if (event.event === "connected" || event.event === "timeout") {
|
|
2831
|
+
return;
|
|
2832
|
+
}
|
|
2833
|
+
if (event.event === "log") {
|
|
2834
|
+
logCount++;
|
|
2835
|
+
const data = JSON.parse(event.data);
|
|
2836
|
+
const level = String(data.level ?? "info");
|
|
2837
|
+
const timestamp = (/* @__PURE__ */ new Date()).toISOString().slice(11, 23);
|
|
2838
|
+
const message = String(data.message ?? "");
|
|
2839
|
+
const colorFn = LEVEL_COLORS[level] ?? chalk44.white;
|
|
2840
|
+
console.log(
|
|
2841
|
+
`${chalk44.gray(timestamp)} ${colorFn(level.toUpperCase().padEnd(5))} ${message}`
|
|
2842
|
+
);
|
|
2843
|
+
}
|
|
2844
|
+
},
|
|
2845
|
+
onError(error) {
|
|
2846
|
+
console.error(chalk44.red(` Connection error: ${error.message}`));
|
|
2847
|
+
},
|
|
2848
|
+
onClose() {
|
|
2849
|
+
console.log(chalk44.gray(`
|
|
2850
|
+
Disconnected. ${logCount} log entries received.`));
|
|
2851
|
+
}
|
|
2852
|
+
});
|
|
2853
|
+
await new Promise((resolve4) => {
|
|
2854
|
+
controller.signal.addEventListener("abort", () => resolve4());
|
|
2855
|
+
});
|
|
2856
|
+
})
|
|
2857
|
+
);
|
|
2858
|
+
}
|
|
2859
|
+
|
|
2860
|
+
// src/commands/trigger/index.ts
|
|
2861
|
+
import chalk45 from "chalk";
|
|
2862
|
+
function registerTriggerCommands(program) {
|
|
2863
|
+
program.command("trigger <event>").description("Trigger a WAVE event manually").option("--override <pairs...>", "Override event data (key=value pairs)").option("--list", "List available trigger events").action(
|
|
2864
|
+
wrapCommand(async (event, opts) => {
|
|
2865
|
+
const config = await loadConfig();
|
|
2866
|
+
const project = config.projects[config.currentProject];
|
|
2867
|
+
const baseUrl = project?.baseUrl ?? process.env["WAVE_BASE_URL"] ?? "https://wave.online";
|
|
2868
|
+
const apiKey = await getApiKey(config.currentProject);
|
|
2869
|
+
if (!apiKey) {
|
|
2870
|
+
throw new Error(`No API key found. Run ${chalk45.bold("wave login")} to authenticate.`);
|
|
2871
|
+
}
|
|
2872
|
+
if (opts.list) {
|
|
2873
|
+
const res2 = await fetch(`${baseUrl}/api/cli/trigger`, {
|
|
2874
|
+
headers: {
|
|
2875
|
+
Authorization: `Bearer ${apiKey}`,
|
|
2876
|
+
"X-Wave-Source": "cli"
|
|
2877
|
+
}
|
|
2878
|
+
});
|
|
2879
|
+
if (!res2.ok) throw new Error(`Failed to list events: ${res2.statusText}`);
|
|
2880
|
+
const events = await res2.json();
|
|
2881
|
+
formatOutput(events, program.opts());
|
|
2882
|
+
return;
|
|
2883
|
+
}
|
|
2884
|
+
const overrides = {};
|
|
2885
|
+
if (opts.override) {
|
|
2886
|
+
for (const pair of opts.override) {
|
|
2887
|
+
const [key, ...valueParts] = pair.split("=");
|
|
2888
|
+
if (key && valueParts.length > 0) {
|
|
2889
|
+
overrides[key] = valueParts.join("=");
|
|
2890
|
+
}
|
|
2891
|
+
}
|
|
2892
|
+
}
|
|
2893
|
+
const res = await fetch(`${baseUrl}/api/cli/trigger`, {
|
|
2894
|
+
method: "POST",
|
|
2895
|
+
headers: {
|
|
2896
|
+
Authorization: `Bearer ${apiKey}`,
|
|
2897
|
+
"Content-Type": "application/json",
|
|
2898
|
+
"X-Wave-Source": "cli"
|
|
2899
|
+
},
|
|
2900
|
+
body: JSON.stringify({ event, data: overrides })
|
|
2901
|
+
});
|
|
2902
|
+
if (!res.ok) {
|
|
2903
|
+
const error = await res.json().catch(() => ({}));
|
|
2904
|
+
throw new Error(error.message ?? `Failed to trigger event: ${res.statusText}`);
|
|
2905
|
+
}
|
|
2906
|
+
const result = await res.json();
|
|
2907
|
+
console.log(chalk45.green(`Event "${event}" triggered.`));
|
|
2908
|
+
formatOutput(result, program.opts());
|
|
2909
|
+
})
|
|
2910
|
+
);
|
|
2911
|
+
}
|
|
2912
|
+
|
|
2913
|
+
// src/commands/dev/index.ts
|
|
2914
|
+
import chalk46 from "chalk";
|
|
2915
|
+
import { readFile as readFile3 } from "fs/promises";
|
|
2916
|
+
import { existsSync as existsSync3 } from "fs";
|
|
2917
|
+
import { join as join3, resolve } from "path";
|
|
2918
|
+
var LEVEL_COLORS2 = {
|
|
2919
|
+
debug: chalk46.gray,
|
|
2920
|
+
info: chalk46.white,
|
|
2921
|
+
warn: chalk46.yellow,
|
|
2922
|
+
error: chalk46.red
|
|
2923
|
+
};
|
|
2924
|
+
async function loadProjectConfig() {
|
|
2925
|
+
const projectJsonPath = join3(resolve(process.cwd()), ".wave", "project.json");
|
|
2926
|
+
if (!existsSync3(projectJsonPath)) {
|
|
2927
|
+
return null;
|
|
2928
|
+
}
|
|
2929
|
+
try {
|
|
2930
|
+
const raw = await readFile3(projectJsonPath, "utf-8");
|
|
2931
|
+
const parsed = JSON.parse(raw);
|
|
2932
|
+
if (typeof parsed.projectId === "string" && typeof parsed.organizationId === "string" && typeof parsed.projectName === "string") {
|
|
2933
|
+
return parsed;
|
|
2934
|
+
}
|
|
2935
|
+
return null;
|
|
2936
|
+
} catch {
|
|
2937
|
+
return null;
|
|
2938
|
+
}
|
|
2939
|
+
}
|
|
2940
|
+
async function detectLocalPort() {
|
|
2941
|
+
const pkgPath = join3(resolve(process.cwd()), "package.json");
|
|
2942
|
+
if (!existsSync3(pkgPath)) {
|
|
2943
|
+
return 3e3;
|
|
2944
|
+
}
|
|
2945
|
+
try {
|
|
2946
|
+
const raw = await readFile3(pkgPath, "utf-8");
|
|
2947
|
+
const pkg = JSON.parse(raw);
|
|
2948
|
+
const scripts = pkg.scripts;
|
|
2949
|
+
if (!scripts) return 3e3;
|
|
2950
|
+
const devScript = scripts.dev ?? scripts.start ?? "";
|
|
2951
|
+
const portMatch = devScript.match(
|
|
2952
|
+
/(?:--port\s+|(?:^|\s)-p\s+|PORT=|:)(\d{4,5})/
|
|
2953
|
+
);
|
|
2954
|
+
if (portMatch) {
|
|
2955
|
+
const port = parseInt(portMatch[1], 10);
|
|
2956
|
+
if (port > 0 && port < 65536) return port;
|
|
2957
|
+
}
|
|
2958
|
+
return 3e3;
|
|
2959
|
+
} catch {
|
|
2960
|
+
return 3e3;
|
|
2961
|
+
}
|
|
2962
|
+
}
|
|
2963
|
+
function registerDevCommands(program) {
|
|
2964
|
+
program.command("dev").description(
|
|
2965
|
+
"Start unified development proxy (webhook forwarding + log streaming)"
|
|
2966
|
+
).option(
|
|
2967
|
+
"--forward-to <url>",
|
|
2968
|
+
"URL to forward webhook events to (overrides auto-detected port)"
|
|
2969
|
+
).option("--port <number>", "Local server port (overrides auto-detection)").option(
|
|
2970
|
+
"--log-level <level>",
|
|
2971
|
+
"Minimum log level (debug, info, warn, error)",
|
|
2972
|
+
"info"
|
|
2973
|
+
).option("--events <pattern>", "Event pattern to filter (e.g., stream.*)").action(
|
|
2974
|
+
wrapCommand(async (opts) => {
|
|
2975
|
+
const config = await loadConfig();
|
|
2976
|
+
const apiKey = await getApiKey(config.currentProject);
|
|
2977
|
+
if (!apiKey) {
|
|
2978
|
+
console.error(
|
|
2979
|
+
chalk46.red("Not authenticated. Run `wave login` first.")
|
|
2980
|
+
);
|
|
2981
|
+
process.exit(1);
|
|
2982
|
+
}
|
|
2983
|
+
const baseUrl = config.projects[config.currentProject]?.baseUrl ?? "https://wave.online";
|
|
2984
|
+
const projectConfig = await loadProjectConfig();
|
|
2985
|
+
let forwardUrl;
|
|
2986
|
+
if (opts.forwardTo) {
|
|
2987
|
+
forwardUrl = opts.forwardTo;
|
|
2988
|
+
} else {
|
|
2989
|
+
const port = opts.port ? parseInt(opts.port, 10) : await detectLocalPort();
|
|
2990
|
+
forwardUrl = `http://localhost:${port}/webhooks/wave`;
|
|
2991
|
+
}
|
|
2992
|
+
console.log(chalk46.bold("WAVE Development Mode\n"));
|
|
2993
|
+
if (projectConfig) {
|
|
2994
|
+
console.log(
|
|
2995
|
+
` Project: ${chalk46.cyan(projectConfig.projectName)} ${chalk46.dim(`(${projectConfig.projectId})`)}`
|
|
2996
|
+
);
|
|
2997
|
+
} else {
|
|
2998
|
+
console.log(
|
|
2999
|
+
chalk46.dim(
|
|
3000
|
+
" No .wave/project.json found. Run `wave link` to connect a project."
|
|
3001
|
+
)
|
|
3002
|
+
);
|
|
3003
|
+
}
|
|
3004
|
+
console.log(
|
|
3005
|
+
` Webhook forwarding: ${chalk46.cyan(forwardUrl)}`
|
|
3006
|
+
);
|
|
3007
|
+
console.log(
|
|
3008
|
+
` Log level: ${chalk46.cyan(opts.logLevel)}`
|
|
3009
|
+
);
|
|
3010
|
+
if (opts.events) {
|
|
3011
|
+
console.log(
|
|
3012
|
+
` Event filter: ${chalk46.cyan(opts.events)}`
|
|
3013
|
+
);
|
|
3014
|
+
}
|
|
3015
|
+
console.log("");
|
|
3016
|
+
console.log(chalk46.dim(" Starting development proxy...\n"));
|
|
3017
|
+
let webhookCount = 0;
|
|
3018
|
+
let logCount = 0;
|
|
3019
|
+
let errorCount = 0;
|
|
3020
|
+
const webhookParams = new URLSearchParams();
|
|
3021
|
+
if (opts.events) webhookParams.set("events", opts.events);
|
|
3022
|
+
const webhookUrl = `${baseUrl}/api/webhooks/listen${webhookParams.toString() ? `?${webhookParams}` : ""}`;
|
|
3023
|
+
const webhookController = await connectSSE(webhookUrl, apiKey, {
|
|
3024
|
+
onConnect() {
|
|
3025
|
+
console.log(
|
|
3026
|
+
chalk46.green(" [webhook] Connected to event stream")
|
|
3027
|
+
);
|
|
3028
|
+
},
|
|
3029
|
+
onEvent(event) {
|
|
3030
|
+
if (event.event === "connected" || event.event === "timeout")
|
|
3031
|
+
return;
|
|
3032
|
+
if (event.event === "webhook") {
|
|
3033
|
+
webhookCount++;
|
|
3034
|
+
const data = JSON.parse(event.data);
|
|
3035
|
+
const timestamp = (/* @__PURE__ */ new Date()).toISOString().slice(11, 23);
|
|
3036
|
+
const eventType = String(data.type ?? "unknown");
|
|
3037
|
+
console.log(
|
|
3038
|
+
`${chalk46.gray(timestamp)} ${chalk46.magenta("[webhook]")} ${chalk46.cyan(eventType)} ${chalk46.white(JSON.stringify(data).slice(0, 100))}`
|
|
3039
|
+
);
|
|
3040
|
+
fetch(forwardUrl, {
|
|
3041
|
+
method: "POST",
|
|
3042
|
+
headers: { "Content-Type": "application/json" },
|
|
3043
|
+
body: JSON.stringify(data)
|
|
3044
|
+
}).catch((err) => {
|
|
3045
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
3046
|
+
console.error(
|
|
3047
|
+
`${chalk46.gray((/* @__PURE__ */ new Date()).toISOString().slice(11, 23))} ${chalk46.red("[error]")} Forward failed: ${msg}`
|
|
3048
|
+
);
|
|
3049
|
+
errorCount++;
|
|
3050
|
+
});
|
|
3051
|
+
}
|
|
3052
|
+
},
|
|
3053
|
+
onError(error) {
|
|
3054
|
+
console.error(
|
|
3055
|
+
chalk46.red(` [webhook] Error: ${error.message}`)
|
|
3056
|
+
);
|
|
3057
|
+
errorCount++;
|
|
3058
|
+
}
|
|
3059
|
+
});
|
|
3060
|
+
const logParams = new URLSearchParams();
|
|
3061
|
+
logParams.set("level", opts.logLevel);
|
|
3062
|
+
const logUrl = `${baseUrl}/api/logs/stream?${logParams}`;
|
|
3063
|
+
const logController = await connectSSE(logUrl, apiKey, {
|
|
3064
|
+
onConnect() {
|
|
3065
|
+
console.log(
|
|
3066
|
+
chalk46.green(" [log] Connected to log stream")
|
|
3067
|
+
);
|
|
3068
|
+
console.log(chalk46.gray("\n Ready. Ctrl+C to stop.\n"));
|
|
3069
|
+
},
|
|
3070
|
+
onEvent(event) {
|
|
3071
|
+
if (event.event === "connected" || event.event === "timeout")
|
|
3072
|
+
return;
|
|
3073
|
+
if (event.event === "log") {
|
|
3074
|
+
logCount++;
|
|
3075
|
+
const data = JSON.parse(event.data);
|
|
3076
|
+
const level = String(data.level ?? "info");
|
|
3077
|
+
const timestamp = (/* @__PURE__ */ new Date()).toISOString().slice(11, 23);
|
|
3078
|
+
const message = String(data.message ?? "");
|
|
3079
|
+
const colorFn = LEVEL_COLORS2[level] ?? chalk46.white;
|
|
3080
|
+
console.log(
|
|
3081
|
+
`${chalk46.gray(timestamp)} ${chalk46.blue("[log]")} ${colorFn(level.toUpperCase().padEnd(5))} ${message}`
|
|
3082
|
+
);
|
|
3083
|
+
}
|
|
3084
|
+
},
|
|
3085
|
+
onError(error) {
|
|
3086
|
+
console.error(
|
|
3087
|
+
chalk46.red(` [log] Error: ${error.message}`)
|
|
3088
|
+
);
|
|
3089
|
+
errorCount++;
|
|
3090
|
+
}
|
|
3091
|
+
});
|
|
3092
|
+
await new Promise((resolve4) => {
|
|
3093
|
+
const done = () => {
|
|
3094
|
+
webhookController.abort();
|
|
3095
|
+
logController.abort();
|
|
3096
|
+
resolve4();
|
|
3097
|
+
};
|
|
3098
|
+
process.on("SIGINT", done);
|
|
3099
|
+
process.on("SIGTERM", done);
|
|
3100
|
+
});
|
|
3101
|
+
console.log(
|
|
3102
|
+
chalk46.gray(
|
|
3103
|
+
`
|
|
3104
|
+
Session ended. ${webhookCount} webhook events, ${logCount} log entries, ${errorCount} errors.`
|
|
3105
|
+
)
|
|
3106
|
+
);
|
|
3107
|
+
})
|
|
3108
|
+
);
|
|
3109
|
+
}
|
|
3110
|
+
|
|
3111
|
+
// src/commands/open/index.ts
|
|
3112
|
+
import chalk47 from "chalk";
|
|
3113
|
+
var PAGE_MAP = {
|
|
3114
|
+
dashboard: "https://wave.online/dashboard",
|
|
3115
|
+
streams: "https://wave.online/dashboard/streams",
|
|
3116
|
+
studio: "https://wave.online/dashboard/production",
|
|
3117
|
+
settings: "https://wave.online/dashboard/settings",
|
|
3118
|
+
billing: "https://wave.online/dashboard/settings/billing",
|
|
3119
|
+
api: "https://wave.online/dashboard/settings/api-keys",
|
|
3120
|
+
docs: "https://docs.wave.online",
|
|
3121
|
+
status: "https://status.wave.online",
|
|
3122
|
+
marketplace: "https://wave.online/marketplace",
|
|
3123
|
+
fleet: "https://wave.online/dashboard/fleet",
|
|
3124
|
+
analytics: "https://wave.online/dashboard/analytics",
|
|
3125
|
+
recordings: "https://wave.online/dashboard/recordings",
|
|
3126
|
+
integrations: "https://wave.online/dashboard/integrations"
|
|
3127
|
+
};
|
|
3128
|
+
function registerOpenCommands(program) {
|
|
3129
|
+
program.command("open [page]").description("Open WAVE pages in your browser").action(
|
|
3130
|
+
wrapCommand(async (page) => {
|
|
3131
|
+
if (!page) {
|
|
3132
|
+
console.log(chalk47.bold("Available pages:\n"));
|
|
3133
|
+
for (const [name, url2] of Object.entries(PAGE_MAP)) {
|
|
3134
|
+
console.log(` ${chalk47.cyan(name.padEnd(16))} ${chalk47.gray(url2)}`);
|
|
3135
|
+
}
|
|
3136
|
+
console.log(chalk47.gray(`
|
|
3137
|
+
Usage: wave open <page>`));
|
|
3138
|
+
return;
|
|
3139
|
+
}
|
|
3140
|
+
const url = PAGE_MAP[page.toLowerCase()];
|
|
3141
|
+
if (!url) {
|
|
3142
|
+
console.error(
|
|
3143
|
+
chalk47.red(
|
|
3144
|
+
`Unknown page "${page}". Run ${chalk47.bold("wave open")} to see available pages.`
|
|
3145
|
+
)
|
|
3146
|
+
);
|
|
3147
|
+
process.exit(1);
|
|
3148
|
+
}
|
|
3149
|
+
const open2 = (await import("open")).default;
|
|
3150
|
+
await open2(url);
|
|
3151
|
+
console.log(chalk47.green(`Opened ${url}`));
|
|
3152
|
+
})
|
|
3153
|
+
);
|
|
3154
|
+
}
|
|
3155
|
+
|
|
3156
|
+
// src/commands/init/index.ts
|
|
3157
|
+
import chalk48 from "chalk";
|
|
3158
|
+
import { writeFile as writeFile3, mkdir as mkdir3, readFile as readFile4, readdir, copyFile } from "fs/promises";
|
|
3159
|
+
import { existsSync as existsSync4 } from "fs";
|
|
3160
|
+
import { join as join4, dirname, resolve as resolve2 } from "path";
|
|
3161
|
+
import { fileURLToPath } from "url";
|
|
3162
|
+
import { spawnSync } from "child_process";
|
|
3163
|
+
|
|
3164
|
+
// src/lib/prompts.ts
|
|
3165
|
+
import inquirer2 from "inquirer";
|
|
3166
|
+
function isNonInteractive() {
|
|
3167
|
+
return Boolean(process.env["CI"]) || Boolean(process.env["WAVE_NON_INTERACTIVE"]) || !process.stdin.isTTY;
|
|
3168
|
+
}
|
|
3169
|
+
async function promptSelect(message, choices) {
|
|
3170
|
+
if (isNonInteractive()) {
|
|
3171
|
+
const first = choices[0];
|
|
3172
|
+
if (!first) {
|
|
3173
|
+
throw new Error(
|
|
3174
|
+
`Cannot prompt in non-interactive mode and no choices provided for: ${message}`
|
|
3175
|
+
);
|
|
3176
|
+
}
|
|
3177
|
+
return first.value;
|
|
3178
|
+
}
|
|
3179
|
+
const { selection } = await inquirer2.prompt([
|
|
3180
|
+
{
|
|
3181
|
+
type: "list",
|
|
3182
|
+
name: "selection",
|
|
3183
|
+
message,
|
|
3184
|
+
choices
|
|
3185
|
+
}
|
|
3186
|
+
]);
|
|
3187
|
+
return selection;
|
|
3188
|
+
}
|
|
3189
|
+
async function promptInput(message, defaultValue) {
|
|
3190
|
+
if (isNonInteractive()) {
|
|
3191
|
+
if (defaultValue !== void 0) {
|
|
3192
|
+
return defaultValue;
|
|
3193
|
+
}
|
|
3194
|
+
throw new Error(
|
|
3195
|
+
`Cannot prompt in non-interactive mode and no default provided for: ${message}`
|
|
3196
|
+
);
|
|
3197
|
+
}
|
|
3198
|
+
const { value } = await inquirer2.prompt([
|
|
3199
|
+
{
|
|
3200
|
+
type: "input",
|
|
3201
|
+
name: "value",
|
|
3202
|
+
message,
|
|
3203
|
+
default: defaultValue
|
|
3204
|
+
}
|
|
3205
|
+
]);
|
|
3206
|
+
return value;
|
|
3207
|
+
}
|
|
3208
|
+
async function promptConfirm(message, defaultValue) {
|
|
3209
|
+
if (isNonInteractive()) {
|
|
3210
|
+
return defaultValue ?? false;
|
|
3211
|
+
}
|
|
3212
|
+
const { confirmed } = await inquirer2.prompt([
|
|
3213
|
+
{
|
|
3214
|
+
type: "confirm",
|
|
3215
|
+
name: "confirmed",
|
|
3216
|
+
message,
|
|
3217
|
+
default: defaultValue ?? false
|
|
3218
|
+
}
|
|
3219
|
+
]);
|
|
3220
|
+
return confirmed;
|
|
3221
|
+
}
|
|
3222
|
+
|
|
3223
|
+
// src/commands/init/index.ts
|
|
3224
|
+
var TEMPLATES = [
|
|
3225
|
+
{
|
|
3226
|
+
name: "WebRTC Quickstart",
|
|
3227
|
+
description: "Browser-based live streaming with WebRTC",
|
|
3228
|
+
dirName: "webrtc-demo"
|
|
3229
|
+
},
|
|
3230
|
+
{
|
|
3231
|
+
name: "SRT Ingest",
|
|
3232
|
+
description: "Low-latency SRT ingest for professional streaming",
|
|
3233
|
+
dirName: "srt-contribution"
|
|
3234
|
+
},
|
|
3235
|
+
{
|
|
3236
|
+
name: "Webhook Handler",
|
|
3237
|
+
description: "Express server handling WAVE webhooks",
|
|
3238
|
+
dirName: "webhook-handler"
|
|
3239
|
+
},
|
|
3240
|
+
{
|
|
3241
|
+
name: "API Integration",
|
|
3242
|
+
description: "Node.js API integration boilerplate",
|
|
3243
|
+
dirName: "api-integration"
|
|
3244
|
+
},
|
|
3245
|
+
{
|
|
3246
|
+
name: "Studio Plugin",
|
|
3247
|
+
description: "Studio plugin for custom production features",
|
|
3248
|
+
dirName: "studio-plugin"
|
|
3249
|
+
}
|
|
3250
|
+
];
|
|
3251
|
+
function getTemplatesDir() {
|
|
3252
|
+
const thisFile = fileURLToPath(import.meta.url);
|
|
3253
|
+
const packageRoot = resolve2(dirname(thisFile), "..", "..", "..");
|
|
3254
|
+
return join4(packageRoot, "templates");
|
|
3255
|
+
}
|
|
3256
|
+
async function copyDir(src, dest) {
|
|
3257
|
+
await mkdir3(dest, { recursive: true });
|
|
3258
|
+
const entries = await readdir(src, { withFileTypes: true });
|
|
3259
|
+
for (const entry of entries) {
|
|
3260
|
+
const srcPath = join4(src, entry.name);
|
|
3261
|
+
const destPath = join4(dest, entry.name);
|
|
3262
|
+
if (entry.isDirectory()) {
|
|
3263
|
+
await copyDir(srcPath, destPath);
|
|
3264
|
+
} else {
|
|
3265
|
+
await copyFile(srcPath, destPath);
|
|
3266
|
+
}
|
|
3267
|
+
}
|
|
3268
|
+
}
|
|
3269
|
+
async function rewritePackageName(projectDir, projectName) {
|
|
3270
|
+
const pkgPath = join4(projectDir, "package.json");
|
|
3271
|
+
if (!existsSync4(pkgPath)) return;
|
|
3272
|
+
const raw = await readFile4(pkgPath, "utf-8");
|
|
3273
|
+
const pkg = JSON.parse(raw);
|
|
3274
|
+
pkg["name"] = projectName;
|
|
3275
|
+
await writeFile3(pkgPath, JSON.stringify(pkg, null, 2) + "\n", "utf-8");
|
|
3276
|
+
}
|
|
3277
|
+
function registerInitCommands(program) {
|
|
3278
|
+
program.command("init [name]").description("Initialize a new WAVE project").option("--template <template>", "Project template to use (skips interactive picker)").option("--no-install", "Skip dependency installation").action(
|
|
3279
|
+
wrapCommand(async (name, opts) => {
|
|
3280
|
+
let selectedTemplate;
|
|
3281
|
+
if (opts.template) {
|
|
3282
|
+
const match = TEMPLATES.find(
|
|
3283
|
+
(t) => t.dirName === opts.template || t.name.toLowerCase().replace(/\s+/g, "-") === opts.template
|
|
3284
|
+
);
|
|
3285
|
+
if (!match) {
|
|
3286
|
+
const valid = TEMPLATES.map((t) => t.dirName).join(", ");
|
|
3287
|
+
throw new Error(
|
|
3288
|
+
`Unknown template "${opts.template}". Available templates: ${valid}`
|
|
3289
|
+
);
|
|
3290
|
+
}
|
|
3291
|
+
selectedTemplate = match;
|
|
3292
|
+
} else {
|
|
3293
|
+
const choices = TEMPLATES.map((t) => ({
|
|
3294
|
+
name: `${t.name} - ${t.description}`,
|
|
3295
|
+
value: t
|
|
3296
|
+
}));
|
|
3297
|
+
selectedTemplate = await promptSelect(
|
|
3298
|
+
"Choose a template:",
|
|
3299
|
+
choices
|
|
3300
|
+
);
|
|
3301
|
+
}
|
|
3302
|
+
const projectName = name ?? await promptInput("Project name:", "my-wave-project");
|
|
3303
|
+
const dir = join4(process.cwd(), projectName);
|
|
3304
|
+
if (existsSync4(dir)) {
|
|
3305
|
+
throw new Error(
|
|
3306
|
+
`Directory "${projectName}" already exists. Choose a different name or remove the directory.`
|
|
3307
|
+
);
|
|
3308
|
+
}
|
|
3309
|
+
console.log(
|
|
3310
|
+
chalk48.bold(
|
|
3311
|
+
`
|
|
3312
|
+
Creating "${projectName}" with ${selectedTemplate.name} template...
|
|
3313
|
+
`
|
|
3314
|
+
)
|
|
3315
|
+
);
|
|
3316
|
+
const templatesDir = getTemplatesDir();
|
|
3317
|
+
const templateDir = join4(templatesDir, selectedTemplate.dirName);
|
|
3318
|
+
const resolvedTemplateDir = resolve2(templateDir);
|
|
3319
|
+
const resolvedTemplatesRoot = resolve2(templatesDir);
|
|
3320
|
+
if (!resolvedTemplateDir.startsWith(resolvedTemplatesRoot)) {
|
|
3321
|
+
throw new Error("Invalid template path");
|
|
3322
|
+
}
|
|
3323
|
+
const templateExists = existsSync4(resolvedTemplateDir);
|
|
3324
|
+
await withSpinner(`Creating ${projectName}/`, async () => {
|
|
3325
|
+
if (templateExists) {
|
|
3326
|
+
await copyDir(resolvedTemplateDir, dir);
|
|
3327
|
+
} else {
|
|
3328
|
+
await mkdir3(join4(dir, "src"), { recursive: true });
|
|
3329
|
+
}
|
|
3330
|
+
await rewritePackageName(dir, projectName);
|
|
3331
|
+
const configContent = `import { defineConfig } from "@wave-av/sdk";
|
|
3332
|
+
|
|
3333
|
+
export default defineConfig({
|
|
3334
|
+
project: "${projectName}",
|
|
3335
|
+
template: "${selectedTemplate.dirName}",
|
|
3336
|
+
streaming: {
|
|
3337
|
+
protocol: "webrtc",
|
|
3338
|
+
fallback: ["srt", "rtmp"],
|
|
3339
|
+
},
|
|
3340
|
+
});
|
|
3341
|
+
`;
|
|
3342
|
+
await writeFile3(join4(dir, "wave.config.ts"), configContent, "utf-8");
|
|
3343
|
+
const gitignorePath = join4(dir, ".gitignore");
|
|
3344
|
+
const gitignoreContent = `.env.local
|
|
3345
|
+
.wave/
|
|
3346
|
+
node_modules/
|
|
3347
|
+
dist/
|
|
3348
|
+
`;
|
|
3349
|
+
await writeFile3(gitignorePath, gitignoreContent, "utf-8");
|
|
3350
|
+
const readmeContent = `# ${projectName}
|
|
3351
|
+
|
|
3352
|
+
Created with [WAVE CLI](https://docs.wave.online/cli) using the **${selectedTemplate.name}** template.
|
|
3353
|
+
|
|
3354
|
+
## Getting Started
|
|
3355
|
+
|
|
3356
|
+
\`\`\`bash
|
|
3357
|
+
# Set your API key
|
|
3358
|
+
cp .env.example .env.local
|
|
3359
|
+
# Edit .env.local with your WAVE API key from https://wave.online/dashboard/settings/api-keys
|
|
3360
|
+
|
|
3361
|
+
# Install dependencies
|
|
3362
|
+
npm install
|
|
3363
|
+
|
|
3364
|
+
# Run the project
|
|
3365
|
+
npm run dev
|
|
3366
|
+
\`\`\`
|
|
3367
|
+
|
|
3368
|
+
## Learn More
|
|
3369
|
+
|
|
3370
|
+
- [WAVE Documentation](https://docs.wave.online)
|
|
3371
|
+
- [API Reference](https://docs.wave.online/api)
|
|
3372
|
+
- [SDK Reference](https://docs.wave.online/sdk)
|
|
3373
|
+
`;
|
|
3374
|
+
await writeFile3(join4(dir, "README.md"), readmeContent, "utf-8");
|
|
3375
|
+
});
|
|
3376
|
+
console.log(chalk48.green(` Created ${projectName}/`));
|
|
3377
|
+
if (opts.install) {
|
|
3378
|
+
const shouldInstall = await promptConfirm("Install dependencies?", true);
|
|
3379
|
+
if (shouldInstall) {
|
|
3380
|
+
await withSpinner("Installing dependencies", async () => {
|
|
3381
|
+
const result = spawnSync("npm", ["install"], {
|
|
3382
|
+
cwd: dir,
|
|
3383
|
+
stdio: "pipe",
|
|
3384
|
+
shell: false
|
|
3385
|
+
});
|
|
3386
|
+
if (result.status !== 0) {
|
|
3387
|
+
const stderr = result.stderr?.toString() ?? "";
|
|
3388
|
+
throw new Error(`npm install failed: ${stderr}`);
|
|
3389
|
+
}
|
|
3390
|
+
});
|
|
3391
|
+
console.log(chalk48.green(" Installed dependencies"));
|
|
3392
|
+
}
|
|
3393
|
+
}
|
|
3394
|
+
console.log(chalk48.green("\n Ready!"));
|
|
3395
|
+
console.log(`
|
|
3396
|
+
Next steps:`);
|
|
3397
|
+
console.log(` cd ${projectName}`);
|
|
3398
|
+
if (!opts.install) {
|
|
3399
|
+
console.log(` npm install`);
|
|
3400
|
+
}
|
|
3401
|
+
console.log(` wave login`);
|
|
3402
|
+
console.log(` npm run dev
|
|
3403
|
+
`);
|
|
3404
|
+
})
|
|
3405
|
+
);
|
|
3406
|
+
}
|
|
3407
|
+
|
|
3408
|
+
// src/commands/admin/index.ts
|
|
3409
|
+
import chalk49 from "chalk";
|
|
3410
|
+
async function adminFetch(path, opts) {
|
|
3411
|
+
const config = await loadConfig();
|
|
3412
|
+
const project = config.projects[config.currentProject];
|
|
3413
|
+
const baseUrl = project?.baseUrl ?? process.env["WAVE_BASE_URL"] ?? "https://wave.online";
|
|
3414
|
+
const apiKey = await getApiKey(config.currentProject);
|
|
3415
|
+
if (!apiKey) {
|
|
3416
|
+
throw new Error(`No API key found. Run ${chalk49.bold("wave login")} to authenticate.`);
|
|
3417
|
+
}
|
|
3418
|
+
const res = await fetch(`${baseUrl}${path}`, {
|
|
3419
|
+
method: opts?.method ?? "GET",
|
|
3420
|
+
headers: {
|
|
3421
|
+
Authorization: `Bearer ${apiKey}`,
|
|
3422
|
+
"Content-Type": "application/json",
|
|
3423
|
+
"X-Wave-Source": "cli"
|
|
3424
|
+
},
|
|
3425
|
+
body: opts?.body ? JSON.stringify(opts.body) : void 0
|
|
3426
|
+
});
|
|
3427
|
+
if (!res.ok) {
|
|
3428
|
+
const error = await res.json().catch(() => ({}));
|
|
3429
|
+
throw new Error(error.message ?? `Admin API error: ${res.status} ${res.statusText}`);
|
|
3430
|
+
}
|
|
3431
|
+
return res.json();
|
|
3432
|
+
}
|
|
3433
|
+
function registerAdminCommands(program) {
|
|
3434
|
+
const admin = program.command("admin").description("Administrative commands (requires admin role)");
|
|
3435
|
+
const jobs = admin.command("jobs").description("Manage background jobs");
|
|
3436
|
+
jobs.command("list").description("List background job functions").option("--status <status>", "Filter by status (active, paused, failed)").action(
|
|
3437
|
+
wrapCommand(async (opts) => {
|
|
3438
|
+
const params = new URLSearchParams();
|
|
3439
|
+
if (opts.status) params.set("status", opts.status);
|
|
3440
|
+
const query = params.toString();
|
|
3441
|
+
const result = await adminFetch(`/api/admin/jobs${query ? `?${query}` : ""}`);
|
|
3442
|
+
formatOutput(result, program.opts());
|
|
3443
|
+
})
|
|
3444
|
+
);
|
|
3445
|
+
jobs.command("trigger <functionId>").description("Manually trigger a background job function").option("--data <json>", "JSON data payload for the job").action(
|
|
3446
|
+
wrapCommand(async (functionId, opts) => {
|
|
3447
|
+
const data = opts.data ? JSON.parse(opts.data) : void 0;
|
|
3448
|
+
const result = await adminFetch(`/api/admin/jobs/${functionId}/trigger`, {
|
|
3449
|
+
method: "POST",
|
|
3450
|
+
body: data ? { data } : void 0
|
|
3451
|
+
});
|
|
3452
|
+
console.log(chalk49.green(`Job "${functionId}" triggered.`));
|
|
3453
|
+
formatOutput(result, program.opts());
|
|
3454
|
+
})
|
|
3455
|
+
);
|
|
3456
|
+
}
|
|
3457
|
+
|
|
3458
|
+
// src/commands/doctor/index.ts
|
|
3459
|
+
import chalk50 from "chalk";
|
|
3460
|
+
function registerDoctorCommands(program) {
|
|
3461
|
+
program.command("doctor").description("Check your WAVE CLI setup and diagnose issues").action(
|
|
3462
|
+
wrapCommand(async () => {
|
|
3463
|
+
const checks = [];
|
|
3464
|
+
const nodeVersion = process.version;
|
|
3465
|
+
const major = parseInt(nodeVersion.slice(1).split(".")[0] ?? "0");
|
|
3466
|
+
checks.push({
|
|
3467
|
+
name: "Node.js",
|
|
3468
|
+
status: major >= 18 ? "pass" : "fail",
|
|
3469
|
+
message: `${nodeVersion} ${major >= 18 ? "(supported)" : "(requires >=18)"}`,
|
|
3470
|
+
fix: major < 18 ? "Install Node.js 18+: https://nodejs.org" : void 0
|
|
3471
|
+
});
|
|
3472
|
+
try {
|
|
3473
|
+
const config2 = await loadConfig();
|
|
3474
|
+
checks.push({
|
|
3475
|
+
name: "Config",
|
|
3476
|
+
status: "pass",
|
|
3477
|
+
message: `Loaded (project: ${config2.currentProject})`
|
|
3478
|
+
});
|
|
3479
|
+
} catch {
|
|
3480
|
+
checks.push({
|
|
3481
|
+
name: "Config",
|
|
3482
|
+
status: "warn",
|
|
3483
|
+
message: "Could not load config (using defaults)",
|
|
3484
|
+
fix: "wave config list"
|
|
3485
|
+
});
|
|
3486
|
+
}
|
|
3487
|
+
const config = await loadConfig();
|
|
3488
|
+
const apiKey = await getApiKey(config.currentProject);
|
|
3489
|
+
const envKey = process.env["WAVE_API_KEY"];
|
|
3490
|
+
if (envKey) {
|
|
3491
|
+
checks.push({
|
|
3492
|
+
name: "Auth",
|
|
3493
|
+
status: "pass",
|
|
3494
|
+
message: `WAVE_API_KEY env var set (${envKey.slice(0, 12)}...)`
|
|
3495
|
+
});
|
|
3496
|
+
} else if (apiKey) {
|
|
3497
|
+
checks.push({
|
|
3498
|
+
name: "Auth",
|
|
3499
|
+
status: "pass",
|
|
3500
|
+
message: `API key stored for "${config.currentProject}" (${apiKey.slice(0, 12)}...)`
|
|
3501
|
+
});
|
|
3502
|
+
} else {
|
|
3503
|
+
checks.push({
|
|
3504
|
+
name: "Auth",
|
|
3505
|
+
status: "fail",
|
|
3506
|
+
message: "No API key found",
|
|
3507
|
+
fix: "wave login"
|
|
3508
|
+
});
|
|
3509
|
+
}
|
|
3510
|
+
const projectCount = Object.keys(config.projects).length;
|
|
3511
|
+
checks.push({
|
|
3512
|
+
name: "Projects",
|
|
3513
|
+
status: projectCount > 0 ? "pass" : "warn",
|
|
3514
|
+
message: projectCount > 0 ? `${projectCount} project(s) configured` : "No projects configured",
|
|
3515
|
+
fix: projectCount === 0 ? "wave login --project-name production" : void 0
|
|
3516
|
+
});
|
|
3517
|
+
const env = detectEnvironment();
|
|
3518
|
+
checks.push({
|
|
3519
|
+
name: "Environment",
|
|
3520
|
+
status: "pass",
|
|
3521
|
+
message: [
|
|
3522
|
+
env.isCI ? "CI" : env.isAgent ? `Agent (${env.agentName ?? "unknown"})` : "Interactive",
|
|
3523
|
+
env.supportsColor ? "color" : "no-color",
|
|
3524
|
+
env.preferJson ? "json-mode" : "table-mode"
|
|
3525
|
+
].join(", ")
|
|
3526
|
+
});
|
|
3527
|
+
checks.push({
|
|
3528
|
+
name: "Telemetry",
|
|
3529
|
+
status: "pass",
|
|
3530
|
+
message: config.telemetry.enabled ? "Enabled (anonymous)" : "Disabled"
|
|
3531
|
+
});
|
|
3532
|
+
const opts = program.opts();
|
|
3533
|
+
if (opts.output === "json") {
|
|
3534
|
+
formatOutput(
|
|
3535
|
+
checks.map((c) => ({ ...c })),
|
|
3536
|
+
opts
|
|
3537
|
+
);
|
|
3538
|
+
} else {
|
|
3539
|
+
console.log(chalk50.bold("\n WAVE CLI Doctor\n"));
|
|
3540
|
+
for (const check of checks) {
|
|
3541
|
+
const icon = check.status === "pass" ? chalk50.green("\u2713") : check.status === "warn" ? chalk50.yellow("!") : chalk50.red("\u2717");
|
|
3542
|
+
console.log(` ${icon} ${chalk50.bold(check.name)}: ${check.message}`);
|
|
3543
|
+
if (check.fix) {
|
|
3544
|
+
console.log(` ${chalk50.dim("Fix:")} ${chalk50.cyan(check.fix)}`);
|
|
3545
|
+
}
|
|
3546
|
+
}
|
|
3547
|
+
const failures = checks.filter((c) => c.status === "fail");
|
|
3548
|
+
const warnings = checks.filter((c) => c.status === "warn");
|
|
3549
|
+
console.log("");
|
|
3550
|
+
if (failures.length === 0 && warnings.length === 0) {
|
|
3551
|
+
console.log(chalk50.green(" All checks passed! You're ready to go."));
|
|
3552
|
+
} else if (failures.length > 0) {
|
|
3553
|
+
console.log(
|
|
3554
|
+
chalk50.red(` ${failures.length} issue(s) need attention.`)
|
|
3555
|
+
);
|
|
3556
|
+
}
|
|
3557
|
+
console.log("");
|
|
3558
|
+
}
|
|
3559
|
+
})
|
|
3560
|
+
);
|
|
3561
|
+
}
|
|
3562
|
+
|
|
3563
|
+
// src/commands/status/index.ts
|
|
3564
|
+
import chalk51 from "chalk";
|
|
3565
|
+
function registerStatusCommands(program) {
|
|
3566
|
+
program.command("status").description("Show WAVE CLI status (auth, API health, current project)").action(
|
|
3567
|
+
wrapCommand(async () => {
|
|
3568
|
+
const config = await loadConfig();
|
|
3569
|
+
const project = config.currentProject || "default";
|
|
3570
|
+
const apiKey = await getApiKey(project);
|
|
3571
|
+
const baseUrl = config.projects[project]?.baseUrl ?? "https://wave.online";
|
|
3572
|
+
const authenticated = !!apiKey;
|
|
3573
|
+
let apiHealthy = false;
|
|
3574
|
+
let apiLatencyMs = null;
|
|
3575
|
+
try {
|
|
3576
|
+
const start = Date.now();
|
|
3577
|
+
const res = await fetch(`${baseUrl}/api/health`, {
|
|
3578
|
+
signal: AbortSignal.timeout(5e3)
|
|
3579
|
+
});
|
|
3580
|
+
apiLatencyMs = Date.now() - start;
|
|
3581
|
+
apiHealthy = res.ok;
|
|
3582
|
+
} catch {
|
|
3583
|
+
apiHealthy = false;
|
|
3584
|
+
}
|
|
3585
|
+
const status = {
|
|
3586
|
+
project,
|
|
3587
|
+
authenticated,
|
|
3588
|
+
organization: config.projects[project]?.organizationName ?? "N/A",
|
|
3589
|
+
apiEndpoint: baseUrl,
|
|
3590
|
+
apiHealthy,
|
|
3591
|
+
apiLatencyMs
|
|
3592
|
+
};
|
|
3593
|
+
console.log(chalk51.bold("\nWAVE CLI Status\n"));
|
|
3594
|
+
console.log(
|
|
3595
|
+
` Auth: ${authenticated ? chalk51.green("Authenticated") : chalk51.red("Not authenticated")}`
|
|
3596
|
+
);
|
|
3597
|
+
console.log(` Project: ${chalk51.cyan(project)}`);
|
|
3598
|
+
console.log(
|
|
3599
|
+
` Org: ${chalk51.cyan(config.projects[project]?.organizationName ?? "N/A")}`
|
|
3600
|
+
);
|
|
3601
|
+
console.log(` Endpoint: ${chalk51.cyan(baseUrl)}`);
|
|
3602
|
+
console.log(
|
|
3603
|
+
` API: ${apiHealthy ? chalk51.green(`Healthy (${apiLatencyMs}ms)`) : chalk51.red("Unreachable")}`
|
|
3604
|
+
);
|
|
3605
|
+
console.log("");
|
|
3606
|
+
if (!authenticated) {
|
|
3607
|
+
console.log(chalk51.yellow(" Run `wave auth login` to authenticate.\n"));
|
|
3608
|
+
}
|
|
3609
|
+
formatOutput(status, program.opts());
|
|
3610
|
+
})
|
|
3611
|
+
);
|
|
3612
|
+
}
|
|
3613
|
+
|
|
3614
|
+
// src/commands/completion/index.ts
|
|
3615
|
+
function registerCompletionCommands(program) {
|
|
3616
|
+
const completion = program.command("completion").description("Generate shell completion scripts");
|
|
3617
|
+
completion.command("bash").description("Generate bash completion script").action(
|
|
3618
|
+
wrapCommand(async () => {
|
|
3619
|
+
console.log(generateBashCompletion(program));
|
|
3620
|
+
})
|
|
3621
|
+
);
|
|
3622
|
+
completion.command("zsh").description("Generate zsh completion script").action(
|
|
3623
|
+
wrapCommand(async () => {
|
|
3624
|
+
console.log(generateZshCompletion(program));
|
|
3625
|
+
})
|
|
3626
|
+
);
|
|
3627
|
+
completion.command("fish").description("Generate fish completion script").action(
|
|
3628
|
+
wrapCommand(async () => {
|
|
3629
|
+
console.log(generateFishCompletion(program));
|
|
3630
|
+
})
|
|
3631
|
+
);
|
|
3632
|
+
}
|
|
3633
|
+
function getCommandNames(program) {
|
|
3634
|
+
return program.commands.map((cmd) => cmd.name());
|
|
3635
|
+
}
|
|
3636
|
+
function generateBashCompletion(program) {
|
|
3637
|
+
const commands = getCommandNames(program);
|
|
3638
|
+
return `# WAVE CLI bash completion
|
|
3639
|
+
# Add to ~/.bashrc: eval "$(wave completion bash)"
|
|
3640
|
+
|
|
3641
|
+
_wave_completions() {
|
|
3642
|
+
local cur prev commands
|
|
3643
|
+
COMPREPLY=()
|
|
3644
|
+
cur="\${COMP_WORDS[COMP_CWORD]}"
|
|
3645
|
+
prev="\${COMP_WORDS[COMP_CWORD-1]}"
|
|
3646
|
+
|
|
3647
|
+
commands="${commands.join(" ")}"
|
|
3648
|
+
|
|
3649
|
+
if [[ \${COMP_CWORD} -eq 1 ]]; then
|
|
3650
|
+
COMPREPLY=( $(compgen -W "\${commands}" -- "\${cur}") )
|
|
3651
|
+
return 0
|
|
3652
|
+
fi
|
|
3653
|
+
|
|
3654
|
+
# Global options
|
|
3655
|
+
local global_opts="--output --project --org --confirm --color --debug --help --version"
|
|
3656
|
+
COMPREPLY=( $(compgen -W "\${global_opts}" -- "\${cur}") )
|
|
3657
|
+
}
|
|
3658
|
+
|
|
3659
|
+
complete -F _wave_completions wave`;
|
|
3660
|
+
}
|
|
3661
|
+
function generateZshCompletion(program) {
|
|
3662
|
+
const commands = getCommandNames(program);
|
|
3663
|
+
const cmdList = commands.map((name) => {
|
|
3664
|
+
const cmd = program.commands.find((c) => c.name() === name);
|
|
3665
|
+
const desc = cmd?.description() ?? name;
|
|
3666
|
+
return ` '${name}:${desc.replace(/'/g, "'\\''")}'`;
|
|
3667
|
+
}).join("\n");
|
|
3668
|
+
return `#compdef wave
|
|
3669
|
+
# WAVE CLI zsh completion
|
|
3670
|
+
# Add to ~/.zshrc: eval "$(wave completion zsh)"
|
|
3671
|
+
|
|
3672
|
+
_wave() {
|
|
3673
|
+
local -a commands
|
|
3674
|
+
commands=(
|
|
3675
|
+
${cmdList}
|
|
3676
|
+
)
|
|
3677
|
+
|
|
3678
|
+
_arguments -C \\
|
|
3679
|
+
'--output[Output format]:format:(table json yaml)' \\
|
|
3680
|
+
'--project[Project name]:project:' \\
|
|
3681
|
+
'--org[Organization]:org:' \\
|
|
3682
|
+
'--debug[Enable debug output]' \\
|
|
3683
|
+
'--help[Show help]' \\
|
|
3684
|
+
'--version[Show version]' \\
|
|
3685
|
+
'1:command:->command' \\
|
|
3686
|
+
'*::arg:->args'
|
|
3687
|
+
|
|
3688
|
+
case $state in
|
|
3689
|
+
command)
|
|
3690
|
+
_describe 'command' commands
|
|
3691
|
+
;;
|
|
3692
|
+
esac
|
|
3693
|
+
}
|
|
3694
|
+
|
|
3695
|
+
_wave`;
|
|
3696
|
+
}
|
|
3697
|
+
function generateFishCompletion(program) {
|
|
3698
|
+
const commands = getCommandNames(program);
|
|
3699
|
+
const completions = commands.map((name) => {
|
|
3700
|
+
const cmd = program.commands.find((c) => c.name() === name);
|
|
3701
|
+
const desc = cmd?.description() ?? name;
|
|
3702
|
+
return `complete -c wave -n '__fish_use_subcommand' -a '${name}' -d '${desc.replace(/'/g, "\\'")}'`;
|
|
3703
|
+
}).join("\n");
|
|
3704
|
+
return `# WAVE CLI fish completion
|
|
3705
|
+
# Add to ~/.config/fish/completions/wave.fish: wave completion fish | source
|
|
3706
|
+
|
|
3707
|
+
# Disable file completions for wave
|
|
3708
|
+
complete -c wave -f
|
|
3709
|
+
|
|
3710
|
+
# Global options
|
|
3711
|
+
complete -c wave -l output -d 'Output format' -a 'table json yaml'
|
|
3712
|
+
complete -c wave -l project -d 'Project name'
|
|
3713
|
+
complete -c wave -l org -d 'Organization'
|
|
3714
|
+
complete -c wave -l debug -d 'Enable debug output'
|
|
3715
|
+
complete -c wave -l help -d 'Show help'
|
|
3716
|
+
complete -c wave -l version -d 'Show version'
|
|
3717
|
+
|
|
3718
|
+
# Commands
|
|
3719
|
+
${completions}`;
|
|
3720
|
+
}
|
|
3721
|
+
|
|
3722
|
+
// src/commands/api/index.ts
|
|
3723
|
+
import chalk52 from "chalk";
|
|
3724
|
+
function registerApiCommands(program) {
|
|
3725
|
+
program.command("api <method> <path>").description("Make raw API requests (like gh api)").option("-d, --data <json>", "Request body (JSON)").option("-H, --header <header>", "Additional header (key:value)", collectHeaders, []).option("--paginate", "Auto-paginate and collect all results").action(
|
|
3726
|
+
wrapCommand(async (method, path, opts) => {
|
|
3727
|
+
const config = await loadConfig();
|
|
3728
|
+
const project = config.currentProject || "default";
|
|
3729
|
+
const apiKey = await getApiKey(project);
|
|
3730
|
+
if (!apiKey) {
|
|
3731
|
+
console.error(chalk52.red("Not authenticated. Run `wave auth login` first."));
|
|
3732
|
+
process.exit(1);
|
|
3733
|
+
}
|
|
3734
|
+
const baseUrl = config.projects[project]?.baseUrl ?? "https://wave.online";
|
|
3735
|
+
const url = path.startsWith("http") ? path : `${baseUrl}${path.startsWith("/") ? path : `/${path}`}`;
|
|
3736
|
+
const headers = {
|
|
3737
|
+
Authorization: `Bearer ${apiKey}`,
|
|
3738
|
+
"Content-Type": "application/json",
|
|
3739
|
+
"User-Agent": "wave-cli/1.0.0"
|
|
3740
|
+
};
|
|
3741
|
+
for (const h of opts.header) {
|
|
3742
|
+
const [key, ...valueParts] = h.split(":");
|
|
3743
|
+
if (key && valueParts.length > 0) {
|
|
3744
|
+
headers[key.trim()] = valueParts.join(":").trim();
|
|
3745
|
+
}
|
|
3746
|
+
}
|
|
3747
|
+
const fetchOpts = {
|
|
3748
|
+
method: method.toUpperCase(),
|
|
3749
|
+
headers
|
|
3750
|
+
};
|
|
3751
|
+
if (opts.data && ["POST", "PUT", "PATCH"].includes(method.toUpperCase())) {
|
|
3752
|
+
fetchOpts.body = opts.data;
|
|
3753
|
+
}
|
|
3754
|
+
const res = await fetch(url, fetchOpts);
|
|
3755
|
+
const contentType = res.headers.get("content-type") ?? "";
|
|
3756
|
+
if (contentType.includes("application/json")) {
|
|
3757
|
+
const data = await res.json();
|
|
3758
|
+
if (!res.ok) {
|
|
3759
|
+
console.error(chalk52.red(`${res.status} ${res.statusText}`));
|
|
3760
|
+
console.error(JSON.stringify(data, null, 2));
|
|
3761
|
+
process.exit(1);
|
|
3762
|
+
}
|
|
3763
|
+
formatOutput(data, program.opts());
|
|
3764
|
+
} else {
|
|
3765
|
+
const text = await res.text();
|
|
3766
|
+
if (!res.ok) {
|
|
3767
|
+
console.error(chalk52.red(`${res.status} ${res.statusText}`));
|
|
3768
|
+
console.error(text);
|
|
3769
|
+
process.exit(1);
|
|
3770
|
+
}
|
|
3771
|
+
console.log(text);
|
|
3772
|
+
}
|
|
3773
|
+
})
|
|
3774
|
+
);
|
|
3775
|
+
}
|
|
3776
|
+
function collectHeaders(value, previous) {
|
|
3777
|
+
return previous.concat([value]);
|
|
3778
|
+
}
|
|
3779
|
+
|
|
3780
|
+
// src/commands/link/index.ts
|
|
3781
|
+
import chalk53 from "chalk";
|
|
3782
|
+
import { writeFile as writeFile4, mkdir as mkdir4, readFile as readFile5, appendFile } from "fs/promises";
|
|
3783
|
+
import { existsSync as existsSync5 } from "fs";
|
|
3784
|
+
import { join as join5, resolve as resolve3 } from "path";
|
|
3785
|
+
var CREATE_NEW_PROJECT_VALUE = "__create_new__";
|
|
3786
|
+
function registerLinkCommands(program) {
|
|
3787
|
+
program.command("link").description("Link the current directory to a WAVE project").option("--org <id>", "Organization ID (skip org selection prompt)").option("--project <id>", "Project ID (skip project selection prompt)").action(
|
|
3788
|
+
wrapCommand(async (opts) => {
|
|
3789
|
+
const config = await loadConfig();
|
|
3790
|
+
const configProjectName = config.currentProject || "default";
|
|
3791
|
+
const apiKey = await getApiKey(configProjectName);
|
|
3792
|
+
if (!apiKey) {
|
|
3793
|
+
console.error(
|
|
3794
|
+
chalk53.red(
|
|
3795
|
+
`Not authenticated. Run ${chalk53.bold("wave auth login")} first.`
|
|
3796
|
+
)
|
|
3797
|
+
);
|
|
3798
|
+
process.exit(1);
|
|
3799
|
+
}
|
|
3800
|
+
const wave = await getClient({ org: opts.org });
|
|
3801
|
+
const orgs = await withSpinner("Fetching organizations...", async () => {
|
|
3802
|
+
const response = await wave.client.get(
|
|
3803
|
+
"/v1/organizations"
|
|
3804
|
+
);
|
|
3805
|
+
return response.data;
|
|
3806
|
+
});
|
|
3807
|
+
if (!orgs || orgs.length === 0) {
|
|
3808
|
+
console.error(
|
|
3809
|
+
chalk53.red(
|
|
3810
|
+
"No organizations found. Create one at https://wave.online/dashboard/settings/organizations"
|
|
3811
|
+
)
|
|
3812
|
+
);
|
|
3813
|
+
process.exit(1);
|
|
3814
|
+
}
|
|
3815
|
+
let selectedOrg;
|
|
3816
|
+
if (opts.org) {
|
|
3817
|
+
const match = orgs.find((o) => o.id === opts.org);
|
|
3818
|
+
if (!match) {
|
|
3819
|
+
console.error(
|
|
3820
|
+
chalk53.red(
|
|
3821
|
+
`Organization "${opts.org}" not found. Available organizations:
|
|
3822
|
+
` + orgs.map((o) => ` ${o.name} (${o.id})`).join("\n")
|
|
3823
|
+
)
|
|
3824
|
+
);
|
|
3825
|
+
process.exit(1);
|
|
3826
|
+
}
|
|
3827
|
+
selectedOrg = match;
|
|
3828
|
+
} else {
|
|
3829
|
+
const orgId = await promptSelect(
|
|
3830
|
+
"Select organization:",
|
|
3831
|
+
orgs.map((o) => ({
|
|
3832
|
+
name: `${o.name} (${o.id})`,
|
|
3833
|
+
value: o.id
|
|
3834
|
+
}))
|
|
3835
|
+
);
|
|
3836
|
+
selectedOrg = orgs.find((o) => o.id === orgId);
|
|
3837
|
+
}
|
|
3838
|
+
const projects = await withSpinner("Fetching projects...", async () => {
|
|
3839
|
+
const response = await wave.client.get(
|
|
3840
|
+
"/v1/projects",
|
|
3841
|
+
{
|
|
3842
|
+
params: { organization_id: selectedOrg.id }
|
|
3843
|
+
}
|
|
3844
|
+
);
|
|
3845
|
+
return response.data;
|
|
3846
|
+
});
|
|
3847
|
+
let selectedProject;
|
|
3848
|
+
if (opts.project) {
|
|
3849
|
+
const match = projects?.find((p) => p.id === opts.project);
|
|
3850
|
+
if (!match) {
|
|
3851
|
+
console.error(
|
|
3852
|
+
chalk53.red(
|
|
3853
|
+
`Project "${opts.project}" not found in organization "${selectedOrg.name}".`
|
|
3854
|
+
)
|
|
3855
|
+
);
|
|
3856
|
+
process.exit(1);
|
|
3857
|
+
}
|
|
3858
|
+
selectedProject = match;
|
|
3859
|
+
} else {
|
|
3860
|
+
const choices = [
|
|
3861
|
+
...(projects ?? []).map((p) => ({
|
|
3862
|
+
name: `${p.name} (${p.id})`,
|
|
3863
|
+
value: p.id
|
|
3864
|
+
})),
|
|
3865
|
+
{
|
|
3866
|
+
name: chalk53.green("+ Create new project"),
|
|
3867
|
+
value: CREATE_NEW_PROJECT_VALUE
|
|
3868
|
+
}
|
|
3869
|
+
];
|
|
3870
|
+
const projectId = await promptSelect("Select project:", choices);
|
|
3871
|
+
if (projectId === CREATE_NEW_PROJECT_VALUE) {
|
|
3872
|
+
const newName = await promptInput("Project name:");
|
|
3873
|
+
selectedProject = await withSpinner(
|
|
3874
|
+
"Creating project...",
|
|
3875
|
+
async () => {
|
|
3876
|
+
return wave.client.post("/v1/projects", {
|
|
3877
|
+
organization_id: selectedOrg.id,
|
|
3878
|
+
name: newName
|
|
3879
|
+
});
|
|
3880
|
+
}
|
|
3881
|
+
);
|
|
3882
|
+
} else {
|
|
3883
|
+
selectedProject = projects.find((p) => p.id === projectId);
|
|
3884
|
+
}
|
|
3885
|
+
}
|
|
3886
|
+
const cwd = resolve3(process.cwd());
|
|
3887
|
+
const waveDirPath = join5(cwd, ".wave");
|
|
3888
|
+
const projectJsonPath = join5(waveDirPath, "project.json");
|
|
3889
|
+
if (!existsSync5(waveDirPath)) {
|
|
3890
|
+
await mkdir4(waveDirPath, { recursive: true });
|
|
3891
|
+
}
|
|
3892
|
+
const linkConfig = {
|
|
3893
|
+
projectId: selectedProject.id,
|
|
3894
|
+
organizationId: selectedOrg.id,
|
|
3895
|
+
projectName: selectedProject.name
|
|
3896
|
+
};
|
|
3897
|
+
await writeFile4(
|
|
3898
|
+
projectJsonPath,
|
|
3899
|
+
JSON.stringify(linkConfig, null, 2) + "\n",
|
|
3900
|
+
"utf-8"
|
|
3901
|
+
);
|
|
3902
|
+
const gitignorePath = join5(cwd, ".gitignore");
|
|
3903
|
+
await ensureGitignoreEntry(gitignorePath, ".wave/");
|
|
3904
|
+
await updateConfig((cfg) => ({
|
|
3905
|
+
...cfg,
|
|
3906
|
+
projects: {
|
|
3907
|
+
...cfg.projects,
|
|
3908
|
+
[configProjectName]: {
|
|
3909
|
+
...cfg.projects[configProjectName],
|
|
3910
|
+
organizationId: selectedOrg.id,
|
|
3911
|
+
organizationName: selectedOrg.name
|
|
3912
|
+
}
|
|
3913
|
+
}
|
|
3914
|
+
}));
|
|
3915
|
+
console.log("");
|
|
3916
|
+
console.log(
|
|
3917
|
+
chalk53.green(
|
|
3918
|
+
`Linked to "${selectedProject.name}" (${selectedProject.id})`
|
|
3919
|
+
)
|
|
3920
|
+
);
|
|
3921
|
+
console.log(chalk53.dim(`Created ${projectJsonPath}`));
|
|
3922
|
+
console.log("");
|
|
3923
|
+
})
|
|
3924
|
+
);
|
|
3925
|
+
}
|
|
3926
|
+
async function ensureGitignoreEntry(gitignorePath, entry) {
|
|
3927
|
+
if (existsSync5(gitignorePath)) {
|
|
3928
|
+
const content = await readFile5(gitignorePath, "utf-8");
|
|
3929
|
+
const lines = content.split("\n").map((l) => l.trim());
|
|
3930
|
+
if (lines.includes(entry)) {
|
|
3931
|
+
return;
|
|
3932
|
+
}
|
|
3933
|
+
const separator = content.endsWith("\n") ? "" : "\n";
|
|
3934
|
+
await appendFile(gitignorePath, `${separator}${entry}
|
|
3935
|
+
`, "utf-8");
|
|
3936
|
+
} else {
|
|
3937
|
+
await writeFile4(gitignorePath, `${entry}
|
|
3938
|
+
`, "utf-8");
|
|
3939
|
+
}
|
|
3940
|
+
}
|
|
3941
|
+
|
|
3942
|
+
// src/cli.ts
|
|
3943
|
+
function printBanner() {
|
|
3944
|
+
const b = chalk54.hex("#3366FF");
|
|
3945
|
+
const p = chalk54.hex("#7B41E8");
|
|
3946
|
+
const c = chalk54.hex("#33BBCC");
|
|
3947
|
+
const d = chalk54.dim;
|
|
3948
|
+
console.log("");
|
|
3949
|
+
console.log(` ${b("\u2588\u2588\u2557 \u2588\u2588\u2557")} ${p("\u2588\u2588\u2588\u2588\u2588\u2557 ")} ${p("\u2588\u2588\u2557 \u2588\u2588\u2557")} ${c("\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557")}`);
|
|
3950
|
+
console.log(` ${b("\u2588\u2588\u2551 \u2588\u2588\u2551")} ${p("\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557")} ${p("\u2588\u2588\u2551 \u2588\u2588\u2551")} ${c("\u2588\u2588\u2554\u2550\u2550\u2550\u2550\u255D")}`);
|
|
3951
|
+
console.log(` ${b("\u2588\u2588\u2551 \u2588\u2557 \u2588\u2588\u2551")} ${p("\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2551")} ${p("\u2588\u2588\u2551 \u2588\u2588\u2551")} ${c("\u2588\u2588\u2588\u2588\u2588\u2557 ")}`);
|
|
3952
|
+
console.log(` ${b("\u2588\u2588\u2551\u2588\u2588\u2588\u2557\u2588\u2588\u2551")} ${p("\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2551")} ${p("\u255A\u2588\u2588\u2557 \u2588\u2588\u2554\u255D")} ${c("\u2588\u2588\u2554\u2550\u2550\u255D ")}`);
|
|
3953
|
+
console.log(` ${b("\u255A\u2588\u2588\u2588\u2554\u2588\u2588\u2588\u2554\u255D")} ${p("\u2588\u2588\u2551 \u2588\u2588\u2551")} ${p(" \u255A\u2588\u2588\u2588\u2588\u2554\u255D ")} ${c("\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557")}`);
|
|
3954
|
+
console.log(` ${b(" \u255A\u2550\u2550\u255D\u255A\u2550\u2550\u255D ")} ${p("\u255A\u2550\u255D \u255A\u2550\u255D")} ${p(" \u255A\u2550\u2550\u2550\u255D ")} ${c("\u255A\u2550\u2550\u2550\u2550\u2550\u2550\u255D")}`);
|
|
3955
|
+
console.log("");
|
|
3956
|
+
console.log(` ${d("Enterprise Streaming Platform")} ${chalk54.hex("#555")("v1.0.0")}`);
|
|
3957
|
+
console.log(` ${d("\u2500".repeat(45))}`);
|
|
3958
|
+
console.log("");
|
|
3959
|
+
}
|
|
3960
|
+
function createProgram() {
|
|
3961
|
+
const program = new Command();
|
|
3962
|
+
program.name("wave").description("WAVE CLI - Command-line interface for the WAVE streaming platform").version("1.0.0", "-v, --version").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");
|
|
3963
|
+
registerAuthCommands(program);
|
|
3964
|
+
registerOrgCommands(program);
|
|
3965
|
+
registerConfigCommands(program);
|
|
3966
|
+
registerInitCommands(program);
|
|
3967
|
+
registerLinkCommands(program);
|
|
3968
|
+
registerStreamCommands(program);
|
|
3969
|
+
registerStudioCommands(program);
|
|
3970
|
+
registerClipCommands(program);
|
|
3971
|
+
registerEditorCommands(program);
|
|
3972
|
+
registerVoiceCommands(program);
|
|
3973
|
+
registerPhoneCommands(program);
|
|
3974
|
+
registerCollabCommands(program);
|
|
3975
|
+
registerCaptionsCommands(program);
|
|
3976
|
+
registerChaptersCommands(program);
|
|
3977
|
+
registerAICommands(program);
|
|
3978
|
+
registerTranscribeCommands(program);
|
|
3979
|
+
registerSentimentCommands(program);
|
|
3980
|
+
registerSearchCommands(program);
|
|
3981
|
+
registerSceneCommands(program);
|
|
3982
|
+
registerFleetCommands(program);
|
|
3983
|
+
registerGhostCommands(program);
|
|
3984
|
+
registerMeshCommands(program);
|
|
3985
|
+
registerEdgeCommands(program);
|
|
3986
|
+
registerAnalyticsCommands(program);
|
|
3987
|
+
registerPrismCommands(program);
|
|
3988
|
+
registerZoomCommands(program);
|
|
3989
|
+
registerVaultCommands(program);
|
|
3990
|
+
registerMarketplaceCommands(program);
|
|
3991
|
+
registerConnectCommands(program);
|
|
3992
|
+
registerDistributionCommands(program);
|
|
3993
|
+
registerDesktopCommands(program);
|
|
3994
|
+
registerSignageCommands(program);
|
|
3995
|
+
registerQrCommands(program);
|
|
3996
|
+
registerAudienceCommands(program);
|
|
3997
|
+
registerCreatorCommands(program);
|
|
3998
|
+
registerPodcastCommands(program);
|
|
3999
|
+
registerSlidesCommands(program);
|
|
4000
|
+
registerUsbCommands(program);
|
|
4001
|
+
registerNotifyCommands(program);
|
|
4002
|
+
registerDrmCommands(program);
|
|
4003
|
+
registerBillingCommands(program);
|
|
4004
|
+
registerListenCommands(program);
|
|
4005
|
+
registerLogsCommands(program);
|
|
4006
|
+
registerTriggerCommands(program);
|
|
4007
|
+
registerDevCommands(program);
|
|
4008
|
+
registerOpenCommands(program);
|
|
4009
|
+
registerAdminCommands(program);
|
|
4010
|
+
registerDoctorCommands(program);
|
|
4011
|
+
registerStatusCommands(program);
|
|
4012
|
+
registerCompletionCommands(program);
|
|
4013
|
+
registerApiCommands(program);
|
|
4014
|
+
const env = detectEnvironment();
|
|
4015
|
+
if (!env.isAgent && !env.isCI) {
|
|
4016
|
+
const originalHelp = program.helpInformation.bind(program);
|
|
4017
|
+
program.helpInformation = function() {
|
|
4018
|
+
printBanner();
|
|
4019
|
+
return originalHelp();
|
|
4020
|
+
};
|
|
4021
|
+
}
|
|
4022
|
+
return program;
|
|
4023
|
+
}
|
|
4024
|
+
|
|
4025
|
+
// src/index.ts
|
|
4026
|
+
async function main() {
|
|
4027
|
+
const program = createProgram();
|
|
4028
|
+
await program.parseAsync(process.argv);
|
|
4029
|
+
}
|
|
4030
|
+
main().catch((error) => {
|
|
4031
|
+
console.error(error instanceof Error ? error.message : "An unexpected error occurred");
|
|
4032
|
+
process.exit(1);
|
|
4033
|
+
});
|
|
4034
|
+
//# sourceMappingURL=index.js.map
|