@wirecat/zoom-cli 0.1.1 → 0.2.1

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.
Files changed (89) hide show
  1. package/README.md +165 -9
  2. package/dist/agent-skill.d.ts +4 -0
  3. package/dist/agent-skill.d.ts.map +1 -0
  4. package/dist/agent-skill.js +16 -0
  5. package/dist/agent-skill.js.map +1 -0
  6. package/dist/api.d.ts +3 -2
  7. package/dist/api.d.ts.map +1 -1
  8. package/dist/api.js +10 -41
  9. package/dist/api.js.map +1 -1
  10. package/dist/archive-commands.d.ts +32 -0
  11. package/dist/archive-commands.d.ts.map +1 -0
  12. package/dist/archive-commands.js +182 -0
  13. package/dist/archive-commands.js.map +1 -0
  14. package/dist/archive-transcripts.d.ts +33 -0
  15. package/dist/archive-transcripts.d.ts.map +1 -0
  16. package/dist/archive-transcripts.js +131 -0
  17. package/dist/archive-transcripts.js.map +1 -0
  18. package/dist/bin/zm.js +53 -3
  19. package/dist/bin/zm.js.map +1 -1
  20. package/dist/commands.d.ts +12 -0
  21. package/dist/commands.d.ts.map +1 -0
  22. package/dist/commands.js +96 -0
  23. package/dist/commands.js.map +1 -0
  24. package/dist/errors.d.ts +4 -3
  25. package/dist/errors.d.ts.map +1 -1
  26. package/dist/errors.js +11 -4
  27. package/dist/errors.js.map +1 -1
  28. package/dist/execution.d.ts +16 -0
  29. package/dist/execution.d.ts.map +1 -0
  30. package/dist/execution.js +49 -0
  31. package/dist/execution.js.map +1 -0
  32. package/dist/index.d.ts +6 -0
  33. package/dist/index.d.ts.map +1 -1
  34. package/dist/index.js +6 -0
  35. package/dist/index.js.map +1 -1
  36. package/dist/login.d.ts +1 -0
  37. package/dist/login.d.ts.map +1 -1
  38. package/dist/login.js +13 -1
  39. package/dist/login.js.map +1 -1
  40. package/dist/mcp-server.d.ts +16 -0
  41. package/dist/mcp-server.d.ts.map +1 -0
  42. package/dist/mcp-server.js +172 -0
  43. package/dist/mcp-server.js.map +1 -0
  44. package/dist/output-file.d.ts +4 -0
  45. package/dist/output-file.d.ts.map +1 -0
  46. package/dist/output-file.js +55 -0
  47. package/dist/output-file.js.map +1 -0
  48. package/dist/program.d.ts +22 -0
  49. package/dist/program.d.ts.map +1 -1
  50. package/dist/program.js +443 -44
  51. package/dist/program.js.map +1 -1
  52. package/dist/recording-archive.d.ts +62 -0
  53. package/dist/recording-archive.d.ts.map +1 -0
  54. package/dist/recording-archive.js +519 -0
  55. package/dist/recording-archive.js.map +1 -0
  56. package/dist/recording-sync.d.ts +15 -0
  57. package/dist/recording-sync.d.ts.map +1 -0
  58. package/dist/recording-sync.js +49 -0
  59. package/dist/recording-sync.js.map +1 -0
  60. package/dist/recordings.d.ts +47 -0
  61. package/dist/recordings.d.ts.map +1 -0
  62. package/dist/recordings.js +124 -0
  63. package/dist/recordings.js.map +1 -0
  64. package/dist/remote-http.d.ts +23 -0
  65. package/dist/remote-http.d.ts.map +1 -0
  66. package/dist/remote-http.js +113 -0
  67. package/dist/remote-http.js.map +1 -0
  68. package/dist/remote-meetings.d.ts +58 -0
  69. package/dist/remote-meetings.d.ts.map +1 -0
  70. package/dist/remote-meetings.js +151 -0
  71. package/dist/remote-meetings.js.map +1 -0
  72. package/dist/store-connection.d.ts +9 -0
  73. package/dist/store-connection.d.ts.map +1 -0
  74. package/dist/store-connection.js +39 -0
  75. package/dist/store-connection.js.map +1 -0
  76. package/dist/streaming.d.ts +6 -0
  77. package/dist/streaming.d.ts.map +1 -0
  78. package/dist/streaming.js +86 -0
  79. package/dist/streaming.js.map +1 -0
  80. package/dist/tokens.d.ts +6 -4
  81. package/dist/tokens.d.ts.map +1 -1
  82. package/dist/tokens.js +48 -18
  83. package/dist/tokens.js.map +1 -1
  84. package/dist/watch-commands.d.ts +25 -0
  85. package/dist/watch-commands.d.ts.map +1 -0
  86. package/dist/watch-commands.js +52 -0
  87. package/dist/watch-commands.js.map +1 -0
  88. package/package.json +22 -12
  89. package/skills/zoom-cli/SKILL.md +85 -0
package/dist/program.js CHANGED
@@ -1,12 +1,22 @@
1
- import { MeetingError } from "@wirecat/cli-meetings";
1
+ import { CliError, createDeadline, createRenderer, exitCodeFor, fieldsOf, GENERIC_FAILURE, projectFields, realSleep, } from "@wirecat/cli-core";
2
+ import { importFiles, listed, MeetingError, pull } from "@wirecat/cli-meetings";
2
3
  import { addMeetingCommands } from "@wirecat/cli-meetings/cli";
3
4
  import { Command, CommanderError } from "commander";
5
+ import { addZoomSkillCommands } from "./agent-skill.js";
4
6
  import { ZoomApiSource } from "./api.js";
7
+ import { addArchiveCommands, } from "./archive-commands.js";
8
+ import { readArchiveTranscripts, searchArchiveTranscripts } from "./archive-transcripts.js";
9
+ import { addStandardCommands, annotateMeetingCommands } from "./commands.js";
5
10
  import { profileAccountKey, readConfig, setConfig } from "./config.js";
6
- import { ZoomError } from "./errors.js";
11
+ import { checkCancelled, ZoomError } from "./errors.js";
12
+ import { bytes, duration, resetCliOptions, startExecution } from "./execution.js";
7
13
  import { FilesSource, importFile, readDownloadedFiles, validateDownloadedFiles } from "./files.js";
8
14
  import { login } from "./login.js";
15
+ import { getZoomProfile, inspectArchive, inspectArchiveLocks, recoverArchiveLocks, verifyArchive, ZoomRecordings, } from "./recordings.js";
16
+ import { ZoomRemoteMeetings } from "./remote-meetings.js";
17
+ import { emitReceipt } from "./streaming.js";
9
18
  import { VERSION } from "./version.js";
19
+ import { addWatchCommand } from "./watch-commands.js";
10
20
  const unavailable = async () => {
11
21
  throw new ZoomError("configuration_error", "the shared SQLite meeting store is not available yet");
12
22
  };
@@ -25,17 +35,64 @@ const unavailableStore = {
25
35
  cursor: unavailable,
26
36
  setCursor: unavailable,
27
37
  };
28
- const render = (data, format) => {
29
- if (format === "jsonl" && typeof data === "object" && data !== null && "items" in data && Array.isArray(data.items))
30
- return data.items.map((row) => `${JSON.stringify(row)}\n`).join("");
31
- return `${JSON.stringify(data, null, format === "text" ? 2 : undefined)}\n`;
38
+ const render = (value, format, deps, fields) => {
39
+ if (fields)
40
+ value = projectFields(value, fields);
41
+ const chunks = [];
42
+ const renderer = createRenderer({
43
+ format: format === "text" ? (deps.outputIsTTY ? "pretty" : "json") : format,
44
+ color: false,
45
+ streams: { data: (text) => chunks.push(`${text}\n`), diagnostic: deps.diagnostic },
46
+ });
47
+ if (format === "jsonl" &&
48
+ typeof value === "object" &&
49
+ value !== null &&
50
+ "items" in value &&
51
+ Array.isArray(value.items))
52
+ renderer.stream(value.items);
53
+ else
54
+ renderer.result(value);
55
+ return chunks.join("");
32
56
  };
33
57
  export const createProgram = (deps) => {
34
58
  let config;
35
59
  const getConfig = () => (config ??= readConfig(deps.configPath));
36
60
  let connection;
37
61
  let parsing = false;
38
- let meetingOutput = [];
62
+ let execution;
63
+ let fields;
64
+ let activeTokens;
65
+ const currentExecution = () => execution;
66
+ const signal = () => currentExecution()?.deadline.signal ?? deps.signal;
67
+ const fetchWithSignal = (fetcher) => (input, init) => {
68
+ checkCancelled(signal());
69
+ if (program.opts().offline)
70
+ throw new ZoomError("configuration_error", "the command requires network access; remove --offline");
71
+ const operation = signal();
72
+ return fetcher(input, {
73
+ ...init,
74
+ signal: operation && init?.signal ? AbortSignal.any([operation, init.signal]) : (operation ?? init?.signal),
75
+ });
76
+ };
77
+ const tokens = () => (activeTokens ??= deps.tokens.withSignal(signal(), fetchWithSignal(deps.tokens.options.fetch)));
78
+ const buffer = (text) => {
79
+ if (!execution)
80
+ throw new ZoomError("configuration_error", "command execution is not initialized");
81
+ execution.output.data(text);
82
+ };
83
+ const guardedStore = (store) => new Proxy(store, {
84
+ get(target, key, receiver) {
85
+ const method = Reflect.get(target, key, receiver);
86
+ return typeof method === "function"
87
+ ? async (...args) => {
88
+ checkCancelled(signal());
89
+ const result = await method.apply(target, args);
90
+ checkCancelled(signal());
91
+ return result;
92
+ }
93
+ : method;
94
+ },
95
+ });
39
96
  let selectedStore = deps.store;
40
97
  let selectedAccountId = deps.accountId;
41
98
  const requireAccount = () => {
@@ -50,15 +107,47 @@ export const createProgram = (deps) => {
50
107
  .option("--json", "one JSON value")
51
108
  .option("--jsonl", "one JSON object per item")
52
109
  .option("--no-input", "disable interactive login")
110
+ .option("--interactive", "permit browser approval in an interactive terminal")
111
+ .option("--quiet", "suppress non-error diagnostics")
112
+ .option("--fields <paths>", "project result fields, such as id,title")
113
+ .option("--timeout <duration>", "command deadline: reads 30s, login 5m, downloads 1h; watch/MCP have no default", duration)
114
+ .option("--max-output-bytes <bytes>", "bound rendered result bytes (default 4194304; zero is unlimited)", bytes)
115
+ .option("--offline", "refuse network requests")
53
116
  .exitOverride()
54
117
  .configureOutput({ writeOut: deps.data, writeErr: deps.diagnostic, outputError: () => { } });
118
+ const api = () => {
119
+ if (program.opts().offline)
120
+ throw new ZoomError("configuration_error", "the command requires network access; remove --offline");
121
+ if (!getConfig().clientId)
122
+ throw new ZoomError("configuration_error", "run zm config set zoom.clientId <Public Client ID> first");
123
+ };
55
124
  program.hook("preAction", async (_root, action) => {
56
- const config = getConfig();
57
125
  const opts = action.optsWithGlobals();
58
126
  if (opts.json && opts.jsonl)
59
127
  throw new ZoomError("validation_error", "choose --json or --jsonl");
60
- if (action.name() === "login" || action.parent?.name() === "config")
128
+ fields = opts.fields === undefined ? undefined : fieldsOf(opts.fields);
129
+ execution = startExecution(action, { signal: deps.signal, sleep: deps.sleep });
130
+ checkCancelled(signal());
131
+ let family = action;
132
+ while (family.parent && family.parent !== program)
133
+ family = family.parent;
134
+ if ([
135
+ "login",
136
+ "commands",
137
+ "complete",
138
+ "doctor",
139
+ "config",
140
+ "session",
141
+ "recordings",
142
+ "remote-meetings",
143
+ "serve",
144
+ "skill",
145
+ "watch",
146
+ ].includes(family.name()))
61
147
  return;
148
+ const config = getConfig();
149
+ if (opts.offline && action.name() === "pull" && config.source === "api" && !deps.source)
150
+ throw new ZoomError("configuration_error", "API pull requires network access; remove --offline");
62
151
  if (!selectedStore) {
63
152
  if (!deps.connectStore)
64
153
  return unavailable();
@@ -81,7 +170,32 @@ export const createProgram = (deps) => {
81
170
  }
82
171
  requireAccount();
83
172
  });
84
- const output = (value) => deps.data(render(value, program.opts().json ? "json" : program.opts().jsonl ? "jsonl" : "text"));
173
+ const output = (value) => buffer(render(value, program.opts().json ? "json" : program.opts().jsonl ? "jsonl" : "text", deps, fields));
174
+ const outputItems = async (values) => {
175
+ const format = program.opts().jsonl ? "jsonl" : program.opts().json || !deps.outputIsTTY ? "json" : "text";
176
+ const metadata = listed([]);
177
+ delete metadata.items;
178
+ let count = 0;
179
+ if (format === "json")
180
+ buffer('{"items":[');
181
+ for await (const value of values) {
182
+ checkCancelled(signal());
183
+ const projected = fields ? projectFields(listed([value]), fields).items[0] : value;
184
+ if (format === "json") {
185
+ if (count)
186
+ buffer(",");
187
+ buffer(render(projected, "json", deps).trimEnd());
188
+ }
189
+ else
190
+ buffer(render(projected, format, deps));
191
+ count++;
192
+ }
193
+ metadata.limit = count;
194
+ if (format === "json")
195
+ buffer(`],${JSON.stringify(metadata).slice(1)}\n`);
196
+ else if (format === "text")
197
+ buffer(render(count ? metadata : listed([]), "text", deps));
198
+ };
85
199
  const cfg = program.command("config").description("local Zoom configuration");
86
200
  cfg
87
201
  .command("show")
@@ -105,13 +219,25 @@ export const createProgram = (deps) => {
105
219
  const config = getConfig();
106
220
  if (!config.clientId)
107
221
  throw new ZoomError("configuration_error", "run zm config set zoom.clientId <Public Client ID> first");
108
- if (program.opts().input === false)
222
+ const globals = program.opts();
223
+ if (globals.offline)
224
+ throw new ZoomError("configuration_error", "login requires network access; remove --offline");
225
+ const headless = deps.interactive === false ||
226
+ Boolean(deps.env?.CI) ||
227
+ (deps.interactive !== undefined &&
228
+ (globals.json || globals.jsonl || deps.outputIsTTY === false) &&
229
+ !globals.interactive);
230
+ if (globals.input === false || headless)
109
231
  throw new ZoomError("configuration_error", "login requires browser approval; remove --no-input");
110
232
  output(await login({
111
- tokens: deps.tokens,
233
+ tokens: tokens(),
234
+ signal: signal(),
112
235
  openBrowser: (url) => deps.openBrowser(url, opts.browser),
113
236
  readPaste: deps.readPaste,
114
- showUrl: (url) => deps.diagnostic(`Open this URL to approve Zoom login:\n${url}\n`),
237
+ showUrl: (url) => {
238
+ if (!program.opts().quiet)
239
+ deps.diagnostic(`Open this URL to approve Zoom login:\n${url}\n`);
240
+ },
115
241
  }));
116
242
  });
117
243
  let activeSource;
@@ -121,7 +247,7 @@ export const createProgram = (deps) => {
121
247
  activeSource ??= (async () => {
122
248
  const config = getConfig();
123
249
  if (config.source === "api")
124
- return new ZoomApiSource(deps.tokens, deps.fetch);
250
+ return new ZoomApiSource(tokens(), fetchWithSignal(deps.fetch));
125
251
  if (!config.filesFolder)
126
252
  throw new ZoomError("configuration_error", "set files.folder before pulling from files");
127
253
  return new FilesSource(await (deps.readFiles ?? readDownloadedFiles)(config.filesFolder), config.filesTimezone);
@@ -157,9 +283,9 @@ export const createProgram = (deps) => {
157
283
  return (await getSource()).files(id);
158
284
  },
159
285
  };
160
- addMeetingCommands(program, {
286
+ const meetingCommands = {
161
287
  get store() {
162
- return selectedStore ?? unavailableStore;
288
+ return guardedStore(selectedStore ?? unavailableStore);
163
289
  },
164
290
  rootIngestion: true,
165
291
  get accountId() {
@@ -172,24 +298,305 @@ export const createProgram = (deps) => {
172
298
  },
173
299
  now: deps.now,
174
300
  write: (value, format) => {
175
- meetingOutput.push(render(value, format));
301
+ buffer(render(value, format, deps, fields));
302
+ },
303
+ writeFile: deps.writeFile
304
+ ? async (path, content) => {
305
+ checkCancelled(signal());
306
+ await deps.writeFile?.(path, content, { signal: signal() });
307
+ checkCancelled(signal());
308
+ }
309
+ : undefined,
310
+ };
311
+ addMeetingCommands(program, meetingCommands);
312
+ addStandardCommands(program, {
313
+ output,
314
+ discovery: (value) => buffer(render(value, program.opts().json ? "json" : program.opts().jsonl ? "jsonl" : "text", deps)),
315
+ fields: () => fields,
316
+ raw: buffer,
317
+ doctor: () => {
318
+ const config = getConfig();
319
+ return {
320
+ version: VERSION,
321
+ runtime: { node: process.versions.node },
322
+ config: { readable: true, path: deps.configPath, source: config.source },
323
+ dataProfile: config.accountProfile,
324
+ authentication: { configured: config.clientId !== null, state: "not_checked" },
325
+ database: "not_checked",
326
+ };
327
+ },
328
+ sessionStatus: async (check) => {
329
+ const config = getConfig();
330
+ if (!config.clientId)
331
+ return { configured: false, state: "not_configured", dataProfile: config.accountProfile, verified: false };
332
+ if (check) {
333
+ if (program.opts().offline)
334
+ throw new ZoomError("configuration_error", "session verification requires network access; remove --offline");
335
+ api();
336
+ return {
337
+ configured: true,
338
+ state: "verified",
339
+ dataProfile: config.accountProfile,
340
+ verified: true,
341
+ profile: await (deps.checkSession
342
+ ? deps.checkSession({ signal: signal() })
343
+ : getZoomProfile(tokens(), fetchWithSignal(deps.fetch), { signal: signal() })),
344
+ };
345
+ }
346
+ const saved = deps.tokens.load();
347
+ return {
348
+ configured: true,
349
+ state: saved ? (saved.expiresAt > (deps.now?.() ?? Date.now()) ? "saved" : "expired") : "missing",
350
+ dataProfile: config.accountProfile,
351
+ verified: false,
352
+ ...(saved ? { expiresAt: saved.expiresAt, scopes: saved.scopes } : {}),
353
+ };
354
+ },
355
+ });
356
+ let watchConfig;
357
+ let watchSince;
358
+ const watchSleep = deps.sleep;
359
+ const receipt = async (value) => {
360
+ const text = render(value, program.opts().jsonl ? "jsonl" : "text", deps, fields);
361
+ const operation = signal();
362
+ const grace = operation.aborted ? createDeadline({ timeoutMs: 1000 }) : undefined;
363
+ try {
364
+ await emitReceipt(text, program.opts().maxOutputBytes, async (serialized) => {
365
+ if (deps.streamData)
366
+ await deps.streamData(serialized, grace?.signal ?? operation);
367
+ else
368
+ deps.data(serialized);
369
+ });
370
+ }
371
+ finally {
372
+ grace?.dispose();
373
+ }
374
+ };
375
+ addWatchCommand(program, {
376
+ signal: () => signal(),
377
+ receipt,
378
+ outputIsTTY: deps.outputIsTTY,
379
+ sleep: watchSleep ? (ms, abort) => watchSleep(ms, abort, "retry") : undefined,
380
+ prepare: (folder, flags) => {
381
+ const config = { ...getConfig() };
382
+ const source = flags.source ?? (folder !== undefined ? "files" : config.source);
383
+ const plan = {
384
+ source,
385
+ since: flags.since,
386
+ lookbackDays: flags.lookbackDays ?? 0,
387
+ intervalMs: flags.interval,
388
+ folder: folder ?? config.filesFolder ?? undefined,
389
+ };
390
+ if (source === "files") {
391
+ if (flags.since !== undefined || flags.lookbackDays !== undefined)
392
+ throw new ZoomError("validation_error", "since and lookback days apply only to API watching");
393
+ if (!plan.folder?.trim())
394
+ throw new ZoomError("configuration_error", "set files.folder or provide a folder for file watching");
395
+ }
396
+ else {
397
+ if (folder !== undefined)
398
+ throw new ZoomError("validation_error", "API watching does not accept a folder");
399
+ if (program.opts().offline)
400
+ throw new ZoomError("configuration_error", "API watching requires network access; remove --offline");
401
+ if (!deps.source) {
402
+ if (!config.clientId)
403
+ throw new ZoomError("configuration_error", "run zm config set zoom.clientId <Public Client ID> first");
404
+ if (tokens().options.clientId !== config.clientId)
405
+ throw new ZoomError("configuration_error", "the OAuth client changed before watch started; retry with matching configuration");
406
+ }
407
+ }
408
+ if (!deps.store && !config.accountProfile)
409
+ throw new ZoomError("configuration_error", "select a local profile with zm config set account.profile <label>");
410
+ watchConfig = config;
411
+ watchSince = plan.since;
412
+ return plan;
176
413
  },
414
+ ingest: async (plan, operation) => {
415
+ checkCancelled(operation);
416
+ const config = watchConfig;
417
+ if (!config)
418
+ throw new ZoomError("configuration_error", "watch configuration is unavailable");
419
+ let cycle;
420
+ let failure;
421
+ let result;
422
+ try {
423
+ if (!deps.store) {
424
+ if (!deps.connectStore)
425
+ return unavailable();
426
+ if (!config.accountProfile)
427
+ throw new ZoomError("configuration_error", "select a local profile with zm config set account.profile <label>");
428
+ try {
429
+ cycle = await deps.connectStore({
430
+ profile: config.accountProfile,
431
+ externalId: profileAccountKey(config.accountProfile),
432
+ intent: "ingestion",
433
+ });
434
+ }
435
+ catch (error) {
436
+ if (error instanceof CliError || error instanceof MeetingError)
437
+ throw error;
438
+ throw new ZoomError("configuration_error", "could not open the shared meeting store");
439
+ }
440
+ }
441
+ checkCancelled(operation);
442
+ const accountId = cycle?.accountId ?? deps.accountId;
443
+ if (!Number.isSafeInteger(accountId) || !accountId || accountId < 1)
444
+ throw new ZoomError("configuration_error", "the selected meeting account is unavailable");
445
+ const store = guardedStore(cycle?.store ?? deps.store ?? unavailableStore);
446
+ if (plan.source === "files") {
447
+ const files = validateDownloadedFiles(await (deps.readFiles ?? readDownloadedFiles)(plan.folder ?? ""), config.filesTimezone);
448
+ checkCancelled(operation);
449
+ result = await importFiles(files.map((file) => importFile(file, accountId, config.filesTimezone)), store, deps.now?.());
450
+ }
451
+ else {
452
+ const original = deps.source ??
453
+ new ZoomApiSource(tokens(), fetchWithSignal(deps.fetch), {
454
+ now: deps.now,
455
+ sleep: (ms, abort) => (deps.sleep ?? realSleep)(ms, AbortSignal.any([operation, abort]), "retry"),
456
+ });
457
+ let sourceFailure;
458
+ const source = new Proxy(original, {
459
+ get(target, key, receiver) {
460
+ const method = Reflect.get(target, key, receiver);
461
+ return typeof method === "function"
462
+ ? async (...args) => {
463
+ if (sourceFailure)
464
+ throw sourceFailure;
465
+ checkCancelled(operation);
466
+ try {
467
+ const value = await method.apply(target, args);
468
+ checkCancelled(operation);
469
+ return value;
470
+ }
471
+ catch (error) {
472
+ if (error instanceof CliError && error.code === "outcome_unknown") {
473
+ sourceFailure = error;
474
+ throw error;
475
+ }
476
+ checkCancelled(operation);
477
+ throw error;
478
+ }
479
+ }
480
+ : method;
481
+ },
482
+ });
483
+ let cursorAdvanced = false;
484
+ const trackingStore = new Proxy(store, {
485
+ get(target, key, receiver) {
486
+ if (key === "setCursor")
487
+ return async (...args) => {
488
+ await target.setCursor(...args);
489
+ cursorAdvanced = true;
490
+ };
491
+ return Reflect.get(target, key, receiver);
492
+ },
493
+ });
494
+ result = await pull(source, trackingStore, {
495
+ accountId,
496
+ since: watchSince,
497
+ lookbackMs: plan.lookbackDays * 86400000,
498
+ now: deps.now?.(),
499
+ });
500
+ if (sourceFailure)
501
+ throw sourceFailure;
502
+ checkCancelled(operation);
503
+ if (cursorAdvanced)
504
+ watchSince = undefined;
505
+ }
506
+ }
507
+ catch (error) {
508
+ failure = error;
509
+ }
510
+ finally {
511
+ if (cycle) {
512
+ try {
513
+ await cycle.close();
514
+ }
515
+ catch {
516
+ failure ??= new ZoomError("configuration_error", "could not close the shared meeting store");
517
+ }
518
+ }
519
+ }
520
+ if (failure)
521
+ throw failure;
522
+ if (!result)
523
+ return unavailable();
524
+ return result;
525
+ },
526
+ });
527
+ const retrySleep = deps.sleep;
528
+ const remoteOptions = {
529
+ now: deps.now,
530
+ sleep: retrySleep ? (ms, abort) => retrySleep(ms, abort, "retry") : undefined,
531
+ };
532
+ addArchiveCommands(program, {
533
+ output,
534
+ outputItems,
535
+ transcripts: deps.archiveTranscripts ?? { read: readArchiveTranscripts, search: searchArchiveTranscripts },
536
+ receipt,
537
+ outputIsTTY: deps.outputIsTTY,
538
+ api,
539
+ signal,
540
+ now: deps.now ?? Date.now,
541
+ recordings: () => deps.recordings ?? new ZoomRecordings(tokens(), fetchWithSignal(deps.fetch), remoteOptions),
542
+ remoteMeetings: () => deps.remoteMeetings ?? new ZoomRemoteMeetings(tokens(), fetchWithSignal(deps.fetch), remoteOptions),
543
+ archive: deps.archive ?? {
544
+ inspect: inspectArchive,
545
+ verify: verifyArchive,
546
+ locks: inspectArchiveLocks,
547
+ recover: recoverArchiveLocks,
548
+ },
549
+ });
550
+ addZoomSkillCommands(program, () => {
551
+ const renderer = createRenderer({
552
+ format: program.opts().json ? "json" : program.opts().jsonl ? "jsonl" : deps.outputIsTTY ? "pretty" : "json",
553
+ color: false,
554
+ quiet: Boolean(program.opts().quiet),
555
+ streams: { data: buffer, diagnostic: deps.diagnostic },
556
+ });
557
+ return {
558
+ renderer: { ...renderer, result: output, stream: (items) => output({ items: [...items] }) },
559
+ streams: { data: (text) => buffer(`${text}\n`), diagnostic: deps.diagnostic },
560
+ env: deps.env,
561
+ };
177
562
  });
563
+ program
564
+ .command("serve")
565
+ .description("serve read-only meeting tools over MCP stdio")
566
+ .requiredOption("--mcp", "use the MCP JSON-RPC protocol")
567
+ .action(async () => {
568
+ const opts = program.opts();
569
+ if (opts.json || opts.jsonl || opts.fields)
570
+ throw new ZoomError("validation_error", "MCP stdio controls its own output; omit --json, --jsonl and --fields");
571
+ if (!deps.serveMcp)
572
+ throw new ZoomError("configuration_error", "the MCP server adapter is not available");
573
+ await deps.serveMcp({ signal: signal(), maxResultBytes: program.opts().maxOutputBytes });
574
+ });
575
+ annotateMeetingCommands(program);
178
576
  const parseAsync = program.parseAsync.bind(program);
179
577
  program.parseAsync = async (...args) => {
180
578
  if (parsing)
181
579
  throw new ZoomError("validation_error", "concurrent command parsing is unavailable");
580
+ resetCliOptions(program);
182
581
  parsing = true;
183
582
  config = undefined;
184
583
  activeSource = undefined;
185
- meetingOutput = [];
584
+ execution = undefined;
585
+ activeTokens = undefined;
586
+ watchConfig = undefined;
587
+ watchSince = undefined;
588
+ fields = undefined;
186
589
  let result = program;
187
590
  let failed = false;
188
591
  let failure;
592
+ const running = parseAsync(...args);
593
+ void running.catch(() => { });
189
594
  try {
190
- result = await parseAsync(...args);
595
+ const active = currentExecution();
596
+ result = active ? await active.deadline.race(() => running) : await running;
191
597
  }
192
598
  catch (error) {
599
+ await running.catch(() => { });
193
600
  failed = true;
194
601
  failure = error;
195
602
  }
@@ -210,14 +617,23 @@ export const createProgram = (deps) => {
210
617
  }
211
618
  }
212
619
  }
620
+ if (!failed) {
621
+ try {
622
+ checkCancelled(signal());
623
+ }
624
+ catch (error) {
625
+ failed = true;
626
+ failure = error;
627
+ }
628
+ }
629
+ currentExecution()?.deadline.dispose();
213
630
  parsing = false;
214
631
  if (failed) {
215
- meetingOutput = [];
632
+ currentExecution()?.output.discard();
216
633
  throw failure;
217
634
  }
218
- for (const text of meetingOutput)
219
- deps.data(text);
220
- meetingOutput = [];
635
+ currentExecution()?.output.flush(deps.data);
636
+ execution = undefined;
221
637
  return result;
222
638
  };
223
639
  return program;
@@ -233,28 +649,11 @@ export const run = async (args, deps) => {
233
649
  "code" in error &&
234
650
  ["commander.helpDisplayed", "commander.help", "commander.version"].includes(String(error.code)))
235
651
  return 0;
236
- const code = error instanceof ZoomError || error instanceof MeetingError
237
- ? error.code
238
- : error instanceof CommanderError
239
- ? "validation_error"
240
- : "generic_failure";
241
- const message = error instanceof ZoomError || error instanceof MeetingError
242
- ? error.message
243
- : "command failed; use --help for usage";
652
+ const typed = error instanceof CliError || error instanceof MeetingError;
653
+ const code = typed ? error.code : error instanceof CommanderError ? "validation_error" : "generic_failure";
654
+ const message = typed ? error.message : "command failed; use --help for usage";
244
655
  deps.diagnostic(`${JSON.stringify({ error: { code, message } })}\n`);
245
- return code === "configuration_error"
246
- ? 3
247
- : code === "authentication_error"
248
- ? 4
249
- : code === "provider_error"
250
- ? 11
251
- : code === "invalid_response"
252
- ? 13
253
- : code === "not_found"
254
- ? 6
255
- : code === "generic_failure"
256
- ? 1
257
- : 2;
656
+ return code === "generic_failure" ? GENERIC_FAILURE : exitCodeFor(code);
258
657
  }
259
658
  };
260
659
  //# sourceMappingURL=program.js.map