@wirecat/zoom-cli 0.12.0 → 0.14.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.
Files changed (220) hide show
  1. package/README.md +5 -5
  2. package/dist/agent-skill.d.ts.map +1 -1
  3. package/dist/agent-skill.js +2 -2
  4. package/dist/agent-skill.js.map +1 -1
  5. package/dist/answer-output.d.ts +23 -0
  6. package/dist/answer-output.d.ts.map +1 -0
  7. package/dist/answer-output.js +52 -0
  8. package/dist/answer-output.js.map +1 -0
  9. package/dist/answer-policy.d.ts +11 -0
  10. package/dist/answer-policy.d.ts.map +1 -0
  11. package/dist/answer-policy.js +8 -0
  12. package/dist/answer-policy.js.map +1 -0
  13. package/dist/answers.d.ts +105 -42
  14. package/dist/answers.d.ts.map +1 -1
  15. package/dist/answers.js +23 -34
  16. package/dist/answers.js.map +1 -1
  17. package/dist/api.d.ts.map +1 -1
  18. package/dist/api.js +20 -19
  19. package/dist/api.js.map +1 -1
  20. package/dist/archive-commands.d.ts +5 -4
  21. package/dist/archive-commands.d.ts.map +1 -1
  22. package/dist/archive-commands.js +32 -31
  23. package/dist/archive-commands.js.map +1 -1
  24. package/dist/archive-import.d.ts +2 -2
  25. package/dist/archive-import.d.ts.map +1 -1
  26. package/dist/archive-import.js +20 -30
  27. package/dist/archive-import.js.map +1 -1
  28. package/dist/archive-transcripts.d.ts.map +1 -1
  29. package/dist/archive-transcripts.js +22 -21
  30. package/dist/archive-transcripts.js.map +1 -1
  31. package/dist/bin/zm.js +1 -2
  32. package/dist/bin/zm.js.map +1 -1
  33. package/dist/command-effects.d.ts +5 -0
  34. package/dist/command-effects.d.ts.map +1 -0
  35. package/dist/command-effects.js +38 -0
  36. package/dist/command-effects.js.map +1 -0
  37. package/dist/commands.d.ts +2 -1
  38. package/dist/commands.d.ts.map +1 -1
  39. package/dist/commands.js +21 -18
  40. package/dist/commands.js.map +1 -1
  41. package/dist/config.d.ts.map +1 -1
  42. package/dist/config.js +54 -37
  43. package/dist/config.js.map +1 -1
  44. package/dist/errors.d.ts +0 -4
  45. package/dist/errors.d.ts.map +1 -1
  46. package/dist/errors.js +2 -7
  47. package/dist/errors.js.map +1 -1
  48. package/dist/execution.d.ts +1 -0
  49. package/dist/execution.d.ts.map +1 -1
  50. package/dist/execution.js +4 -4
  51. package/dist/execution.js.map +1 -1
  52. package/dist/files.d.ts.map +1 -1
  53. package/dist/files.js +9 -9
  54. package/dist/files.js.map +1 -1
  55. package/dist/index.d.ts +2 -1
  56. package/dist/index.d.ts.map +1 -1
  57. package/dist/index.js +2 -1
  58. package/dist/index.js.map +1 -1
  59. package/dist/ingestion-search-commands.d.ts +3 -2
  60. package/dist/ingestion-search-commands.d.ts.map +1 -1
  61. package/dist/ingestion-search-commands.js +9 -8
  62. package/dist/ingestion-search-commands.js.map +1 -1
  63. package/dist/json-fields.d.ts +7 -0
  64. package/dist/json-fields.d.ts.map +1 -0
  65. package/dist/json-fields.js +23 -0
  66. package/dist/json-fields.js.map +1 -0
  67. package/dist/keys.d.ts +1 -1
  68. package/dist/keys.d.ts.map +1 -1
  69. package/dist/keys.js +7 -5
  70. package/dist/keys.js.map +1 -1
  71. package/dist/local-transcription-commands.d.ts +3 -2
  72. package/dist/local-transcription-commands.d.ts.map +1 -1
  73. package/dist/local-transcription-commands.js +7 -7
  74. package/dist/local-transcription-commands.js.map +1 -1
  75. package/dist/local-transcription.d.ts.map +1 -1
  76. package/dist/local-transcription.js +13 -12
  77. package/dist/local-transcription.js.map +1 -1
  78. package/dist/login.d.ts.map +1 -1
  79. package/dist/login.js +8 -7
  80. package/dist/login.js.map +1 -1
  81. package/dist/mcp-server.d.ts +4 -0
  82. package/dist/mcp-server.d.ts.map +1 -1
  83. package/dist/mcp-server.js +40 -48
  84. package/dist/mcp-server.js.map +1 -1
  85. package/dist/meeting-context-commands.d.ts +2 -1
  86. package/dist/meeting-context-commands.d.ts.map +1 -1
  87. package/dist/meeting-context-commands.js +11 -15
  88. package/dist/meeting-context-commands.js.map +1 -1
  89. package/dist/meeting-context.d.ts +6 -6
  90. package/dist/meeting-context.d.ts.map +1 -1
  91. package/dist/meeting-context.js +8 -21
  92. package/dist/meeting-context.js.map +1 -1
  93. package/dist/meeting-integration-commands.d.ts +3 -2
  94. package/dist/meeting-integration-commands.d.ts.map +1 -1
  95. package/dist/meeting-integration-commands.js +7 -7
  96. package/dist/meeting-integration-commands.js.map +1 -1
  97. package/dist/meeting-integration.d.ts.map +1 -1
  98. package/dist/meeting-integration.js +2 -2
  99. package/dist/meeting-integration.js.map +1 -1
  100. package/dist/meeting-mcp-command.d.ts +19 -0
  101. package/dist/meeting-mcp-command.d.ts.map +1 -0
  102. package/dist/meeting-mcp-command.js +41 -0
  103. package/dist/meeting-mcp-command.js.map +1 -0
  104. package/dist/meeting-preparation-mcp.d.ts +131 -0
  105. package/dist/meeting-preparation-mcp.d.ts.map +1 -0
  106. package/dist/meeting-preparation-mcp.js +20 -0
  107. package/dist/meeting-preparation-mcp.js.map +1 -0
  108. package/dist/meeting-preparation.d.ts +22 -0
  109. package/dist/meeting-preparation.d.ts.map +1 -0
  110. package/dist/meeting-preparation.js +29 -0
  111. package/dist/meeting-preparation.js.map +1 -0
  112. package/dist/meeting-project-evidence-commands.d.ts +1 -4
  113. package/dist/meeting-project-evidence-commands.d.ts.map +1 -1
  114. package/dist/meeting-project-evidence-commands.js +2 -6
  115. package/dist/meeting-project-evidence-commands.js.map +1 -1
  116. package/dist/meeting-project-evidence-mcp.d.ts +76 -83
  117. package/dist/meeting-project-evidence-mcp.d.ts.map +1 -1
  118. package/dist/meeting-project-evidence-mcp.js +7 -8
  119. package/dist/meeting-project-evidence-mcp.js.map +1 -1
  120. package/dist/meeting-read-command-bindings.d.ts +10 -0
  121. package/dist/meeting-read-command-bindings.d.ts.map +1 -0
  122. package/dist/meeting-read-command-bindings.js +13 -0
  123. package/dist/meeting-read-command-bindings.js.map +1 -0
  124. package/dist/meeting-search-commands.d.ts +3 -2
  125. package/dist/meeting-search-commands.d.ts.map +1 -1
  126. package/dist/meeting-search-commands.js +47 -38
  127. package/dist/meeting-search-commands.js.map +1 -1
  128. package/dist/meeting-semantic-commands.d.ts +3 -2
  129. package/dist/meeting-semantic-commands.d.ts.map +1 -1
  130. package/dist/meeting-semantic-commands.js +13 -12
  131. package/dist/meeting-semantic-commands.js.map +1 -1
  132. package/dist/meeting-semantic.d.ts.map +1 -1
  133. package/dist/meeting-semantic.js +3 -2
  134. package/dist/meeting-semantic.js.map +1 -1
  135. package/dist/occurrence-schedule.d.ts +3 -0
  136. package/dist/occurrence-schedule.d.ts.map +1 -0
  137. package/dist/occurrence-schedule.js +106 -0
  138. package/dist/occurrence-schedule.js.map +1 -0
  139. package/dist/output-file.js +3 -3
  140. package/dist/output-file.js.map +1 -1
  141. package/dist/program.d.ts +15 -28
  142. package/dist/program.d.ts.map +1 -1
  143. package/dist/program.js +163 -333
  144. package/dist/program.js.map +1 -1
  145. package/dist/recording-archive.d.ts +1 -1
  146. package/dist/recording-archive.d.ts.map +1 -1
  147. package/dist/recording-archive.js +99 -46
  148. package/dist/recording-archive.js.map +1 -1
  149. package/dist/recording-partial-store.d.ts.map +1 -1
  150. package/dist/recording-partial-store.js +11 -10
  151. package/dist/recording-partial-store.js.map +1 -1
  152. package/dist/recording-promotion-store.d.ts.map +1 -1
  153. package/dist/recording-promotion-store.js +4 -4
  154. package/dist/recording-promotion-store.js.map +1 -1
  155. package/dist/recording-schedule-commands.d.ts +3 -2
  156. package/dist/recording-schedule-commands.d.ts.map +1 -1
  157. package/dist/recording-schedule-commands.js +13 -12
  158. package/dist/recording-schedule-commands.js.map +1 -1
  159. package/dist/recording-schedule-store.d.ts.map +1 -1
  160. package/dist/recording-schedule-store.js +15 -14
  161. package/dist/recording-schedule-store.js.map +1 -1
  162. package/dist/recording-schedule-systemd.d.ts.map +1 -1
  163. package/dist/recording-schedule-systemd.js +16 -15
  164. package/dist/recording-schedule-systemd.js.map +1 -1
  165. package/dist/recording-schedule.d.ts +1 -1
  166. package/dist/recording-schedule.d.ts.map +1 -1
  167. package/dist/recording-schedule.js +15 -14
  168. package/dist/recording-schedule.js.map +1 -1
  169. package/dist/recording-sync-store.d.ts.map +1 -1
  170. package/dist/recording-sync-store.js +9 -9
  171. package/dist/recording-sync-store.js.map +1 -1
  172. package/dist/recording-sync.d.ts +1 -1
  173. package/dist/recording-sync.d.ts.map +1 -1
  174. package/dist/recording-sync.js +10 -19
  175. package/dist/recording-sync.js.map +1 -1
  176. package/dist/recordings.d.ts.map +1 -1
  177. package/dist/recordings.js +19 -18
  178. package/dist/recordings.js.map +1 -1
  179. package/dist/remote-http.d.ts +1 -0
  180. package/dist/remote-http.d.ts.map +1 -1
  181. package/dist/remote-http.js +26 -14
  182. package/dist/remote-http.js.map +1 -1
  183. package/dist/remote-meeting-id.d.ts +2 -0
  184. package/dist/remote-meeting-id.d.ts.map +1 -0
  185. package/dist/remote-meeting-id.js +7 -0
  186. package/dist/remote-meeting-id.js.map +1 -0
  187. package/dist/remote-meetings.d.ts.map +1 -1
  188. package/dist/remote-meetings.js +10 -13
  189. package/dist/remote-meetings.js.map +1 -1
  190. package/dist/store-connection.d.ts +1 -7
  191. package/dist/store-connection.d.ts.map +1 -1
  192. package/dist/store-connection.js +95 -176
  193. package/dist/store-connection.js.map +1 -1
  194. package/dist/streaming.js +3 -3
  195. package/dist/streaming.js.map +1 -1
  196. package/dist/tokens.d.ts.map +1 -1
  197. package/dist/tokens.js +10 -9
  198. package/dist/tokens.js.map +1 -1
  199. package/dist/unified-search.d.ts +1 -0
  200. package/dist/unified-search.d.ts.map +1 -1
  201. package/dist/unified-search.js +18 -3
  202. package/dist/unified-search.js.map +1 -1
  203. package/dist/upcoming-meetings.d.ts +10 -0
  204. package/dist/upcoming-meetings.d.ts.map +1 -0
  205. package/dist/upcoming-meetings.js +24 -0
  206. package/dist/upcoming-meetings.js.map +1 -0
  207. package/dist/watch-commands.d.ts +2 -1
  208. package/dist/watch-commands.d.ts.map +1 -1
  209. package/dist/watch-commands.js +9 -9
  210. package/dist/watch-commands.js.map +1 -1
  211. package/package.json +5 -4
  212. package/skills/zoom-cli/SKILL.md +32 -12
  213. package/dist/ordinary-file.d.ts +0 -3
  214. package/dist/ordinary-file.d.ts.map +0 -1
  215. package/dist/ordinary-file.js +0 -16
  216. package/dist/ordinary-file.js.map +0 -1
  217. package/dist/wav-audio.d.ts +0 -2
  218. package/dist/wav-audio.d.ts.map +0 -1
  219. package/dist/wav-audio.js +0 -2
  220. package/dist/wav-audio.js.map +0 -1
package/dist/program.js CHANGED
@@ -1,30 +1,33 @@
1
1
  import { homedir } from "node:os";
2
2
  import { dirname, join } from "node:path";
3
- import { createDeadline, createRenderer, errorObjectOf, errorRecordOf, exitCodeOfError, fieldsOf, isCliError, projectFields, realSleep, renderError, } from "@wirecat/cli-base";
4
- import { failureContext } from "@wirecat/cli-core";
5
- import { importFiles, listed, MeetingError, pull, } from "@wirecat/cli-core/meetings";
3
+ import { CliError, createDeadline, createOutputBuffer, createRenderer, errorObjectOf, errorRecordOf, exitCodeOfError, isCliError, projectFields, realSleep, renderError, } from "@wirecat/cli-base";
4
+ import { metaOf } from "@wirecat/cli-base/commands";
5
+ import { checkJson, itemOf, snakeKeys } from "@wirecat/cli-core/cli";
6
+ import { importFiles, pull, } from "@wirecat/cli-core/meetings";
6
7
  import { addMeetingCommands, addMeetingPassageCommand } from "@wirecat/cli-core/meetings/cli";
7
8
  import { Command, CommanderError } from "commander";
8
9
  import { addZoomSkillCommands } from "./agent-skill.js";
9
- import { meetingAnswer } from "./answers.js";
10
+ import { bodyOf, renderBody, wholeList } from "./answer-output.js";
11
+ import { hostFailureContext } from "./answer-policy.js";
10
12
  import { ZoomApiSource } from "./api.js";
11
13
  import { addArchiveCommands, } from "./archive-commands.js";
12
14
  import { applyArchiveImport, preflightArchiveImport } from "./archive-import.js";
13
15
  import { readArchiveTranscripts, searchArchiveTranscripts } from "./archive-transcripts.js";
14
16
  import { addStandardCommands, annotateMeetingCommands } from "./commands.js";
15
17
  import { profileAccountKey, readConfig, setConfig } from "./config.js";
16
- import { checkCancelled, ZoomError } from "./errors.js";
18
+ import { checkCancelled } from "./errors.js";
17
19
  import { bytes, duration, resetCliOptions, startExecution } from "./execution.js";
18
20
  import { FilesSource, importFile, readDownloadedFiles, validateDownloadedFiles } from "./files.js";
19
21
  import { addIngestionSearchCommands } from "./ingestion-search-commands.js";
20
- import { snakeKeys } from "./keys.js";
22
+ import { liftJsonFields } from "./json-fields.js";
21
23
  import { addLocalTranscriptionCommands } from "./local-transcription-commands.js";
22
24
  import { login } from "./login.js";
23
25
  import { addMeetingContextCommands } from "./meeting-context-commands.js";
24
26
  import { addMeetingIntegrationCommands } from "./meeting-integration-commands.js";
25
- import { addProjectEvidenceCommand } from "./meeting-project-evidence-commands.js";
27
+ import { addZoomMcpCommand } from "./meeting-mcp-command.js";
28
+ import { addMeetingReadBindings } from "./meeting-read-command-bindings.js";
26
29
  import { addMeetingSearchCommands } from "./meeting-search-commands.js";
27
- import { addMeetingEmbeddingCommand, validateMeetingModel, } from "./meeting-semantic-commands.js";
30
+ import { addMeetingEmbeddingCommand } from "./meeting-semantic-commands.js";
28
31
  import { createRecordingScheduleService } from "./recording-schedule.js";
29
32
  import { addRecordingScheduleCommands } from "./recording-schedule-commands.js";
30
33
  import { fileRecordingScheduleStore } from "./recording-schedule-store.js";
@@ -37,7 +40,7 @@ import { runUnifiedSearch } from "./unified-search.js";
37
40
  import { VERSION } from "./version.js";
38
41
  import { addWatchCommand } from "./watch-commands.js";
39
42
  const unavailable = async () => {
40
- throw new ZoomError("configuration_error", "the shared SQLite meeting store is not available yet");
43
+ throw new CliError("configuration_error", "the shared SQLite meeting store is not available yet");
41
44
  };
42
45
  const unavailableStore = {
43
46
  saveMeeting: unavailable,
@@ -55,39 +58,11 @@ const unavailableStore = {
55
58
  cursor: unavailable,
56
59
  setCursor: unavailable,
57
60
  };
58
- const render = (value, format, deps, fields, preserveEnvelope = false, providerKeys = false, seal) => {
59
- if (!providerKeys)
60
- value = snakeKeys(value);
61
- if (seal)
62
- value = meetingAnswer(value, seal());
63
- if (fields) {
64
- if (seal && value && typeof value === "object" && "item" in value)
65
- value = { ...value, item: projectFields(value.item, fields) };
66
- else
67
- value = projectFields(value, fields);
68
- }
69
- const chunks = [];
70
- const renderer = createRenderer({
71
- format: format === "text" ? (deps.outputIsTTY ? "pretty" : "json") : format,
72
- color: false,
73
- streams: { data: (text) => chunks.push(`${text}\n`), diagnostic: deps.diagnostic },
74
- });
75
- if (!preserveEnvelope &&
76
- !seal &&
77
- format === "jsonl" &&
78
- typeof value === "object" &&
79
- value !== null &&
80
- "items" in value &&
81
- Array.isArray(value.items))
82
- renderer.stream(value.items);
83
- else
84
- renderer.result(value);
85
- return chunks.join("");
86
- };
61
+ const answerExitCodes = new WeakMap();
87
62
  export const createProgram = (deps) => {
88
63
  let config;
89
64
  const getConfig = () => (config ??= readConfig(deps.configPath));
90
- let connection;
65
+ let connecting;
91
66
  let parsing = false;
92
67
  let execution;
93
68
  let fields;
@@ -97,7 +72,7 @@ export const createProgram = (deps) => {
97
72
  const fetchWithSignal = (fetcher) => (input, init) => {
98
73
  checkCancelled(signal());
99
74
  if (program.opts().offline)
100
- throw new ZoomError("configuration_error", "the command requires network access; remove --offline");
75
+ throw new CliError("configuration_error", "the command requires network access; remove --offline");
101
76
  const operation = signal();
102
77
  return fetcher(input, {
103
78
  ...init,
@@ -107,7 +82,7 @@ export const createProgram = (deps) => {
107
82
  const tokens = () => (activeTokens ??= deps.tokens.withSignal(signal(), fetchWithSignal(deps.tokens.options.fetch)));
108
83
  const buffer = (text) => {
109
84
  if (!execution)
110
- throw new ZoomError("configuration_error", "command execution is not initialized");
85
+ throw new CliError("configuration_error", "command execution is not initialized");
111
86
  execution.output.data(text);
112
87
  };
113
88
  const guardedStore = (store) => new Proxy(store, {
@@ -127,116 +102,99 @@ export const createProgram = (deps) => {
127
102
  let selectedAccountId = deps.accountId;
128
103
  const requireAccount = () => {
129
104
  if (!Number.isSafeInteger(selectedAccountId) || !selectedAccountId || selectedAccountId < 1)
130
- throw new ZoomError("configuration_error", "the selected meeting account is unavailable");
105
+ throw new CliError("configuration_error", "the selected meeting account is unavailable");
131
106
  return selectedAccountId;
132
107
  };
108
+ const openStore = async (intent) => {
109
+ if (!deps.connectStore)
110
+ return unavailable();
111
+ const profile = getConfig().accountProfile;
112
+ if (!profile)
113
+ throw new CliError("configuration_error", "select a local profile with zm config set account.profile <label>");
114
+ try {
115
+ return await deps.connectStore({ profile, externalId: profileAccountKey(profile), intent });
116
+ }
117
+ catch (error) {
118
+ if (isCliError(error))
119
+ throw error;
120
+ throw new CliError("configuration_error", "could not open the shared meeting store");
121
+ }
122
+ };
123
+ const connected = (intent = "read") => (connecting ??= openStore(intent));
133
124
  const program = new Command()
134
125
  .name("zm")
135
126
  .version(VERSION)
136
127
  .description("Zoom meeting transcripts and summaries")
137
- .option("--json", "one JSON value")
128
+ .option("--json", "one JSON value; --json=id,title keeps only those fields")
138
129
  .option("--jsonl", "one JSON object per item")
139
130
  .option("--no-input", "disable interactive login")
140
131
  .option("--interactive", "permit browser approval in an interactive terminal")
141
132
  .option("--quiet", "suppress non-error diagnostics")
142
- .option("--fields <paths>", "project result fields, such as id,title")
143
133
  .option("--timeout <duration>", "command deadline: reads 30s, login 5m, downloads/transcription 1h; watch/MCP have no default", duration)
144
134
  .option("--max-output-bytes <bytes>", "bound rendered result bytes (default 4194304; zero is unlimited)", bytes)
145
135
  .option("--offline", "refuse network requests")
146
136
  .option("--debug", "on a failure, its whole record — cause and stack — in place of the short line")
147
137
  .exitOverride()
148
138
  .configureOutput({ writeOut: deps.data, writeErr: deps.diagnostic, outputError: () => { } });
139
+ const formatOf = () => program.opts().jsonl ? "jsonl" : program.opts().json || !deps.outputIsTTY ? "json" : "pretty";
140
+ const rendered = (body, format = formatOf()) => {
141
+ if (!execution)
142
+ throw new CliError("configuration_error", "command execution is not initialized");
143
+ const answer = renderBody(execution.action, body, {
144
+ format,
145
+ meta: execution.meta(),
146
+ fields,
147
+ quiet: Boolean(program.opts().quiet),
148
+ diagnostic: deps.diagnostic,
149
+ });
150
+ answerExitCodes.set(program, Math.max(answerExitCodes.get(program) ?? 0, answer.exitCode));
151
+ return answer.text;
152
+ };
149
153
  const api = () => {
150
154
  if (program.opts().offline)
151
- throw new ZoomError("configuration_error", "the command requires network access; remove --offline");
155
+ throw new CliError("configuration_error", "the command requires network access; remove --offline");
152
156
  if (!getConfig().clientId)
153
- throw new ZoomError("configuration_error", "run zm config set zoom.clientId <Public Client ID> first");
157
+ throw new CliError("configuration_error", "run zm config set zoom.client_id <Public Client ID> first");
154
158
  };
155
159
  program.hook("preAction", async (_root, action) => {
156
160
  const opts = action.optsWithGlobals();
157
161
  if (opts.json && opts.jsonl)
158
- throw new ZoomError("validation_error", "choose --json or --jsonl");
159
- fields = opts.fields === undefined ? undefined : fieldsOf(opts.fields);
162
+ throw new CliError("validation_error", "choose --json or --jsonl");
163
+ checkJson(action, { json: opts.json === true, ...(fields ? { fields: fields.join(",") } : {}) }, false);
160
164
  execution = startExecution(action, { signal: deps.signal, sleep: deps.sleep, now: deps.now });
161
165
  checkCancelled(signal());
162
- let family = action;
163
- while (family.parent && family.parent !== program)
164
- family = family.parent;
165
- if ([
166
- "commands",
167
- "complete",
168
- "doctor",
169
- "config",
170
- "session",
171
- "recordings",
172
- "remote-meetings",
173
- "serve",
174
- "skill",
175
- "watch",
176
- "search",
177
- ].includes(family.name()) ||
178
- (family.name() === "meetings" &&
179
- ["passage", "evidence", "context", "task-proposal", "embeddings", "update", "link", "transcribe"].includes(action.name())))
166
+ if (!storeCommands.has(action))
180
167
  return;
181
168
  const config = getConfig();
182
- const ingestion = family.name() === "meetings" && ["sync", "import"].includes(action.name());
169
+ const ingestion = action.parent?.name() === "meetings" && ["sync", "import"].includes(action.name());
183
170
  if (opts.offline && ingestion && action.name() !== "import" && config.source === "api" && !deps.source)
184
- throw new ZoomError("configuration_error", "API sync requires network access; remove --offline");
171
+ throw new CliError("configuration_error", "API sync requires network access; remove --offline");
185
172
  if (!selectedStore) {
186
- if (!deps.connectStore)
187
- return unavailable();
188
- if (!config.accountProfile)
189
- throw new ZoomError("configuration_error", "select a local profile with zm config set account.profile <label>");
190
- try {
191
- connection = await deps.connectStore({
192
- profile: config.accountProfile,
193
- externalId: profileAccountKey(config.accountProfile),
194
- intent: ingestion ? "ingestion" : "read",
195
- });
196
- }
197
- catch (error) {
198
- if (error instanceof ZoomError || error instanceof MeetingError)
199
- throw error;
200
- throw new ZoomError("configuration_error", "could not open the shared meeting store");
201
- }
173
+ const connection = await connected(ingestion ? "ingestion" : "read");
202
174
  selectedStore = connection.store;
203
175
  selectedAccountId = connection.accountId;
204
176
  }
205
177
  requireAccount();
206
178
  });
207
- const output = (value) => buffer(render(value, program.opts().json ? "json" : program.opts().jsonl ? "jsonl" : "text", deps, fields, true, false, execution?.meta));
179
+ const output = (body) => buffer(rendered(body));
208
180
  const outputItems = async (values) => {
209
- const format = program.opts().jsonl ? "jsonl" : program.opts().json || !deps.outputIsTTY ? "json" : "text";
210
- const metadata = listed([]);
211
- delete metadata.items;
212
- let count = 0;
213
- if (format === "json")
214
- buffer('{"items":[');
181
+ const items = [];
182
+ // Fails on the row that crosses --max-output-bytes, so a long iterator stops reading there.
183
+ const measured = createOutputBuffer({ maxOutputBytes: program.opts().maxOutputBytes });
215
184
  for await (const value of values) {
216
185
  checkCancelled(signal());
217
186
  const item = snakeKeys(value);
218
- const projected = fields ? projectFields(listed([item]), fields).items[0] : item;
219
- if (format === "json") {
220
- if (count)
221
- buffer(",");
222
- buffer(render(projected, "json", deps).trimEnd());
223
- }
224
- else
225
- buffer(render(projected, format, deps, undefined, true, false, execution?.meta));
226
- count++;
187
+ measured.data(JSON.stringify(fields ? projectFields(item, fields) : item));
188
+ items.push(item);
227
189
  }
228
- metadata.limit = count;
229
- if (format === "json")
230
- buffer(`],"page":${JSON.stringify({ limit: Math.max(1, count), has_more: false })},"meta":${JSON.stringify(execution?.meta())}}\n`);
231
- else if (format === "text")
232
- buffer(render(count ? metadata : listed([]), "text", deps));
190
+ output(wholeList("list", items));
233
191
  };
234
192
  const cfg = program.command("config").description("local Zoom configuration");
235
193
  cfg
236
194
  .command("show")
237
195
  .description("show local configuration")
238
196
  .action(() => {
239
- output(getConfig());
197
+ output(itemOf(getConfig()));
240
198
  });
241
199
  cfg
242
200
  .command("set")
@@ -244,7 +202,7 @@ export const createProgram = (deps) => {
244
202
  .argument("<key>", "setting name, such as account.profile")
245
203
  .argument("<value>", "new value")
246
204
  .action((key, value) => {
247
- output(setConfig(deps.configPath, key, value));
205
+ output(itemOf(setConfig(deps.configPath, key, value)));
248
206
  });
249
207
  let activeSource;
250
208
  const getSource = () => {
@@ -255,7 +213,7 @@ export const createProgram = (deps) => {
255
213
  if (config.source === "api")
256
214
  return new ZoomApiSource(tokens(), fetchWithSignal(deps.fetch));
257
215
  if (!config.filesFolder)
258
- throw new ZoomError("configuration_error", "set files.folder before pulling from files");
216
+ throw new CliError("configuration_error", "set files.folder before pulling from files");
259
217
  return new FilesSource(await (deps.readFiles ?? readDownloadedFiles)(config.filesFolder), config.filesTimezone);
260
218
  })();
261
219
  return activeSource;
@@ -302,10 +260,7 @@ export const createProgram = (deps) => {
302
260
  return validateDownloadedFiles(await (deps.readFiles ?? readDownloadedFiles)(folder), config.filesTimezone).map((file) => importFile(file, requireAccount(), config.filesTimezone));
303
261
  },
304
262
  now: deps.now,
305
- write: (value, format) => {
306
- const paged = typeof value === "object" && value !== null && "page" in value && typeof value.page === "object";
307
- buffer(render(value, format, deps, fields, paged, true, execution?.meta));
308
- },
263
+ write: (value) => output(bodyOf(value)),
309
264
  writeFile: deps.writeFile
310
265
  ? async (path, content) => {
311
266
  checkCancelled(signal());
@@ -314,10 +269,18 @@ export const createProgram = (deps) => {
314
269
  }
315
270
  : undefined,
316
271
  };
272
+ const before = new Set(program.commands);
317
273
  addMeetingCommands(program, meetingCommands);
274
+ const leaves = (command) => command.commands.length ? command.commands.flatMap(leaves) : [command];
275
+ // The meeting reads cli-core mounts read the store opened before their action; zoom's own commands open it lazily.
276
+ const storeCommands = new Set(program.commands.filter((group) => !before.has(group)).flatMap(leaves));
318
277
  addStandardCommands(program, {
319
278
  output,
320
- discovery: (value) => buffer(render(value, program.opts().json ? "json" : program.opts().jsonl ? "jsonl" : "text", deps, undefined, false, true)),
279
+ discovery: (value) => createRenderer({
280
+ format: formatOf(),
281
+ color: false,
282
+ streams: { data: (text) => buffer(`${text}\n`), diagnostic: deps.diagnostic },
283
+ }).result(value),
321
284
  fields: () => fields,
322
285
  raw: buffer,
323
286
  doctor: () => {
@@ -337,7 +300,7 @@ export const createProgram = (deps) => {
337
300
  return { configured: false, state: "not_configured", dataProfile: config.accountProfile, verified: false };
338
301
  if (check) {
339
302
  if (program.opts().offline)
340
- throw new ZoomError("configuration_error", "session verification requires network access; remove --offline");
303
+ throw new CliError("configuration_error", "session verification requires network access; remove --offline");
341
304
  api();
342
305
  return {
343
306
  configured: true,
@@ -361,7 +324,7 @@ export const createProgram = (deps) => {
361
324
  });
362
325
  const session = program.commands.find((command) => command.name() === "session");
363
326
  if (!session)
364
- throw new ZoomError("configuration_error", "session command group is unavailable");
327
+ throw new CliError("configuration_error", "session command group is unavailable");
365
328
  session
366
329
  .command("start")
367
330
  .description("log in to Zoom through your browser")
@@ -369,18 +332,18 @@ export const createProgram = (deps) => {
369
332
  .action(async (opts) => {
370
333
  const config = getConfig();
371
334
  if (!config.clientId)
372
- throw new ZoomError("configuration_error", "run zm config set zoom.clientId <Public Client ID> first");
335
+ throw new CliError("configuration_error", "run zm config set zoom.client_id <Public Client ID> first");
373
336
  const globals = program.opts();
374
337
  if (globals.offline)
375
- throw new ZoomError("configuration_error", "login requires network access; remove --offline");
338
+ throw new CliError("configuration_error", "login requires network access; remove --offline");
376
339
  const headless = deps.interactive === false ||
377
340
  Boolean(deps.env?.CI) ||
378
341
  (deps.interactive !== undefined &&
379
342
  (globals.json || globals.jsonl || deps.outputIsTTY === false) &&
380
343
  !globals.interactive);
381
344
  if (globals.input === false || headless)
382
- throw new ZoomError("configuration_error", "login requires browser approval; remove --no-input");
383
- output(await login({
345
+ throw new CliError("configuration_error", "login requires browser approval; remove --no-input");
346
+ output(itemOf(await login({
384
347
  tokens: tokens(),
385
348
  signal: signal(),
386
349
  openBrowser: (url) => deps.openBrowser(url, opts.browser),
@@ -389,13 +352,13 @@ export const createProgram = (deps) => {
389
352
  if (!program.opts().quiet)
390
353
  deps.diagnostic(`Open this URL to approve Zoom login:\n${url}\n`);
391
354
  },
392
- }));
355
+ })));
393
356
  });
394
357
  let watchConfig;
395
358
  let watchSince;
396
359
  const watchSleep = deps.sleep;
397
- const receipt = async (value) => {
398
- const text = render(value, program.opts().jsonl ? "jsonl" : program.opts().json ? "json" : "text", deps, fields, true, false, execution?.meta);
360
+ const receipt = async (body) => {
361
+ const text = rendered(body);
399
362
  const operation = signal();
400
363
  const grace = operation.aborted ? createDeadline({ timeoutMs: 1000 }) : undefined;
401
364
  try {
@@ -427,24 +390,24 @@ export const createProgram = (deps) => {
427
390
  };
428
391
  if (source === "files") {
429
392
  if (flags.sinceTime !== undefined || flags.lookbackDays !== undefined)
430
- throw new ZoomError("validation_error", "--since-time and --lookback-days apply only to API watching");
393
+ throw new CliError("validation_error", "--since-time and --lookback-days apply only to API watching");
431
394
  if (!plan.folder?.trim())
432
- throw new ZoomError("configuration_error", "set files.folder or provide a folder for file watching");
395
+ throw new CliError("configuration_error", "set files.folder or provide a folder for file watching");
433
396
  }
434
397
  else {
435
398
  if (folder !== undefined)
436
- throw new ZoomError("validation_error", "API watching does not accept a folder");
399
+ throw new CliError("validation_error", "API watching does not accept a folder");
437
400
  if (program.opts().offline)
438
- throw new ZoomError("configuration_error", "API watching requires network access; remove --offline");
401
+ throw new CliError("configuration_error", "API watching requires network access; remove --offline");
439
402
  if (!deps.source) {
440
403
  if (!config.clientId)
441
- throw new ZoomError("configuration_error", "run zm config set zoom.clientId <Public Client ID> first");
404
+ throw new CliError("configuration_error", "run zm config set zoom.client_id <Public Client ID> first");
442
405
  if (tokens().options.clientId !== config.clientId)
443
- throw new ZoomError("configuration_error", "the OAuth client changed before watch started; retry with matching configuration");
406
+ throw new CliError("configuration_error", "the OAuth client changed before watch started; retry with matching configuration");
444
407
  }
445
408
  }
446
409
  if (!deps.store && !config.accountProfile)
447
- throw new ZoomError("configuration_error", "select a local profile with zm config set account.profile <label>");
410
+ throw new CliError("configuration_error", "select a local profile with zm config set account.profile <label>");
448
411
  watchConfig = config;
449
412
  watchSince = plan.since;
450
413
  return plan;
@@ -453,33 +416,17 @@ export const createProgram = (deps) => {
453
416
  checkCancelled(operation);
454
417
  const config = watchConfig;
455
418
  if (!config)
456
- throw new ZoomError("configuration_error", "watch configuration is unavailable");
419
+ throw new CliError("configuration_error", "watch configuration is unavailable");
457
420
  let cycle;
458
421
  let failure;
459
422
  let result;
460
423
  try {
461
- if (!deps.store) {
462
- if (!deps.connectStore)
463
- return unavailable();
464
- if (!config.accountProfile)
465
- throw new ZoomError("configuration_error", "select a local profile with zm config set account.profile <label>");
466
- try {
467
- cycle = await deps.connectStore({
468
- profile: config.accountProfile,
469
- externalId: profileAccountKey(config.accountProfile),
470
- intent: "ingestion",
471
- });
472
- }
473
- catch (error) {
474
- if (isCliError(error))
475
- throw error;
476
- throw new ZoomError("configuration_error", "could not open the shared meeting store");
477
- }
478
- }
424
+ if (!deps.store)
425
+ cycle = await openStore("ingestion");
479
426
  checkCancelled(operation);
480
427
  const accountId = cycle?.accountId ?? deps.accountId;
481
428
  if (!Number.isSafeInteger(accountId) || !accountId || accountId < 1)
482
- throw new ZoomError("configuration_error", "the selected meeting account is unavailable");
429
+ throw new CliError("configuration_error", "the selected meeting account is unavailable");
483
430
  const store = guardedStore(cycle?.store ?? deps.store ?? unavailableStore);
484
431
  if (plan.source === "files") {
485
432
  const files = validateDownloadedFiles(await (deps.readFiles ?? readDownloadedFiles)(plan.folder ?? ""), config.filesTimezone);
@@ -551,7 +498,7 @@ export const createProgram = (deps) => {
551
498
  await cycle.close();
552
499
  }
553
500
  catch {
554
- failure ??= new ZoomError("configuration_error", "could not close the shared meeting store");
501
+ failure ??= new CliError("configuration_error", "could not close the shared meeting store");
555
502
  }
556
503
  }
557
504
  }
@@ -605,7 +552,7 @@ export const createProgram = (deps) => {
605
552
  if (dirname(deps.configPath) !== job.capture.configDir ||
606
553
  selected.accountProfile !== job.capture.profile ||
607
554
  selected.clientId !== job.capture.clientId)
608
- throw new ZoomError("configuration_error", "archive schedule requires its saved profile and Zoom client id; select those settings before running");
555
+ throw new CliError("configuration_error", "archive schedule requires its saved profile and Zoom client id; select those settings before running");
609
556
  return new ZoomRecordings(new TokenManager({
610
557
  clientId: job.capture.clientId,
611
558
  keyring: deps.tokens.options.keyring,
@@ -623,9 +570,9 @@ export const createProgram = (deps) => {
623
570
  capture: () => {
624
571
  const selected = getConfig();
625
572
  if (!selected.accountProfile || !selected.clientId)
626
- throw new ZoomError("configuration_error", "archive schedules require a configured local profile and Zoom client id");
573
+ throw new CliError("configuration_error", "archive schedules require a configured local profile and Zoom client id");
627
574
  if (!deps.recordingScheduleTarget)
628
- throw new ZoomError("configuration_error", "archive scheduler requires an explicit runtime and CLI path");
575
+ throw new CliError("configuration_error", "archive scheduler requires an explicit runtime and CLI path");
629
576
  return {
630
577
  profile: selected.accountProfile,
631
578
  clientId: selected.clientId,
@@ -642,219 +589,99 @@ export const createProgram = (deps) => {
642
589
  });
643
590
  addZoomSkillCommands(program, () => {
644
591
  const renderer = createRenderer({
645
- format: program.opts().json ? "json" : program.opts().jsonl ? "jsonl" : deps.outputIsTTY ? "pretty" : "json",
592
+ format: formatOf(),
646
593
  color: false,
647
594
  quiet: Boolean(program.opts().quiet),
648
595
  streams: { data: buffer, diagnostic: deps.diagnostic },
649
596
  });
650
597
  return {
651
- renderer: { ...renderer, result: output, stream: (items) => output({ items: [...items] }) },
598
+ renderer: {
599
+ ...renderer,
600
+ result: (value) => output(bodyOf(value)),
601
+ stream: (items) => output(wholeList("skill", [...items])),
602
+ },
652
603
  streams: { data: (text) => buffer(`${text}\n`), diagnostic: deps.diagnostic },
653
604
  env: deps.env,
654
605
  };
655
606
  });
656
- program
657
- .command("serve")
658
- .description("serve read-only meeting tools over MCP stdio")
659
- .requiredOption("--mcp", "use the MCP JSON-RPC protocol")
660
- .option("--unified-search", "include selected-profile meetings-only unified search")
661
- .option("--meeting-context", "include account-fixed evidence, person context and task proposals")
662
- .option("--semantic", "include read-only meeting vector search with an explicit query model")
663
- .option("--model <model>", "query embedding model: an installed one, or one served at --base-url")
664
- .option("--base-url <url>", "explicit remote embedding endpoint for semantic queries")
665
- .option("--dims <n>", "vector size the remote service returns, in dimensions", bytes)
666
- .action(async (options) => {
667
- const opts = program.opts();
668
- if (opts.json || opts.jsonl || opts.fields)
669
- throw new ZoomError("validation_error", "MCP stdio controls its own output; omit --json, --jsonl and --fields");
670
- if (!deps.serveMcp)
671
- throw new ZoomError("configuration_error", "the MCP server adapter is not available");
672
- if (options.semantic)
673
- validateMeetingModel({ model: options.model ?? "", baseUrl: options.baseUrl, dims: options.dims });
674
- else if (options.model !== undefined || options.baseUrl !== undefined || options.dims !== undefined)
675
- throw new ZoomError("validation_error", "MCP model options require --semantic");
676
- if (options.semantic && options.baseUrl !== undefined && opts.offline)
677
- throw new ZoomError("configuration_error", "remote embeddings require network access; remove --offline");
678
- await deps.serveMcp({
679
- signal: signal(),
680
- maxResultBytes: program.opts().maxOutputBytes,
681
- ...(options.unifiedSearch ? { includeUnifiedSearch: true } : {}),
682
- ...(options.meetingContext ? { includeMeetingContext: true } : {}),
683
- ...(options.semantic
684
- ? { semanticModel: { model: options.model ?? "", baseUrl: options.baseUrl, dims: options.dims } }
685
- : {}),
686
- });
687
- });
607
+ const preparationOptions = {
608
+ credentials: () => {
609
+ api();
610
+ return tokens();
611
+ },
612
+ fetch: fetchWithSignal(deps.fetch),
613
+ remoteOptions,
614
+ };
615
+ addZoomMcpCommand(program, { fields: () => fields, signal, serve: deps.serveMcp, preparation: preparationOptions });
688
616
  addIngestionSearchCommands(program, {
689
617
  signal,
690
- output: (value) => buffer(render(value, program.opts().jsonl ? "jsonl" : program.opts().json ? "json" : "text", deps, fields, true, false, execution?.meta)),
618
+ output,
691
619
  outputItems,
692
620
  receipt,
693
621
  preflight: preflightArchiveImport,
694
622
  apply: async function* (plan) {
695
623
  if (plan.occurrences === 0)
696
624
  return;
697
- let store = deps.transcriptStore;
698
- let accountId = selectedAccountId;
699
- if (!store) {
700
- const config = getConfig();
701
- if (!config.accountProfile || !deps.connectStore)
702
- throw new ZoomError("configuration_error", "select a local profile with an available transcript append store");
703
- connection = await deps.connectStore({
704
- profile: config.accountProfile,
705
- externalId: profileAccountKey(config.accountProfile),
706
- intent: "ingestion",
707
- capability: "transcript-append",
708
- });
709
- accountId = connection.accountId;
710
- if (!connection.appendTranscripts && !connection.appendTranscriptsWithReceipt)
711
- throw new ZoomError("configuration_error", "the installed meeting store does not support transcript append");
712
- store = {
713
- appendTranscripts: connection.appendTranscripts,
714
- appendTranscriptsWithReceipt: connection.appendTranscriptsWithReceipt,
715
- };
716
- }
717
- for await (const item of applyArchiveImport(plan, store, {
718
- accountId: accountId ?? 0,
625
+ const connection = await connected("ingestion");
626
+ for await (const item of applyArchiveImport(plan, connection, {
627
+ accountId: connection.accountId,
719
628
  now: deps.now?.(),
720
629
  signal: signal(),
721
630
  }))
722
631
  yield item;
723
632
  },
724
- search: async (request) => {
725
- if (deps.unifiedSearch)
726
- return runUnifiedSearch(deps.unifiedSearch, request);
727
- const config = getConfig();
728
- if (!config.accountProfile || !deps.connectStore)
729
- throw new ZoomError("configuration_error", "select a stored local profile for unified search");
730
- connection = await deps.connectStore({
731
- profile: config.accountProfile,
732
- externalId: profileAccountKey(config.accountProfile),
733
- intent: "read",
734
- });
735
- if (!connection.unifiedSearch)
736
- throw new ZoomError("configuration_error", "unified search is unavailable in this store connector");
737
- return runUnifiedSearch(connection.unifiedSearch, request);
738
- },
633
+ search: async (request) => runUnifiedSearch((await connected()).unifiedSearch, request),
739
634
  });
740
- const integrationConnection = async (capability) => {
741
- const config = getConfig();
742
- if (!config.accountProfile || !deps.connectStore)
743
- throw new ZoomError("configuration_error", "select a stored local profile for meeting integration");
744
- connection = await deps.connectStore({
745
- profile: config.accountProfile,
746
- externalId: profileAccountKey(config.accountProfile),
747
- intent: "read",
748
- capability,
749
- });
750
- return connection;
751
- };
752
- const integrationOutput = (value) => buffer(render(value, program.opts().jsonl ? "jsonl" : program.opts().json ? "json" : "text", deps, fields, true, false, execution?.meta));
753
- addProjectEvidenceCommand(program, {
754
- read: deps.meetingProjectEvidence,
755
- connect: () => integrationConnection("meeting-project-evidence"),
756
- signal,
757
- now: deps.now,
758
- output: integrationOutput,
635
+ addMeetingReadBindings(program, {
636
+ project: {
637
+ read: async (project, request) => (await connected()).meetingProjectEvidence(project, request),
638
+ signal,
639
+ now: deps.now,
640
+ output: (value) => output(bodyOf(value)),
641
+ },
642
+ preparation: {
643
+ ...preparationOptions,
644
+ connect: () => openStore("read"),
645
+ },
759
646
  });
760
647
  addMeetingIntegrationCommands(program, {
761
648
  signal,
762
- output: integrationOutput,
649
+ output,
763
650
  receipt,
764
- integration: async () => {
765
- if (deps.meetingIntegration)
766
- return deps.meetingIntegration;
767
- const connected = await integrationConnection("meeting-integration");
768
- if (!connected.meetingIntegration)
769
- throw new ZoomError("configuration_error", "meeting integration is unavailable in this store connector");
770
- return connected.meetingIntegration;
771
- },
651
+ integration: async () => (await connected()).meetingIntegration,
772
652
  });
773
653
  addLocalTranscriptionCommands(program, {
774
654
  signal,
775
- output: integrationOutput,
655
+ output,
776
656
  receipt,
777
- capabilities: async () => {
778
- if (deps.meetingTranscription)
779
- return deps.meetingTranscription;
780
- const config = getConfig();
781
- if (!config.accountProfile || !deps.connectStore)
782
- throw new ZoomError("configuration_error", "select a stored local profile for transcription");
783
- connection = await deps.connectStore({
784
- profile: config.accountProfile,
785
- externalId: profileAccountKey(config.accountProfile),
786
- intent: "read",
787
- capability: "transcript-append",
788
- });
789
- if (!connection.meetingTranscription)
790
- throw new ZoomError("configuration_error", "local transcription is unavailable in this store connector");
791
- return connection.meetingTranscription;
792
- },
657
+ capabilities: async () => (await connected()).meetingTranscription,
793
658
  });
794
- const meetingContext = async () => {
795
- if (deps.meetingContext)
796
- return deps.meetingContext;
797
- const config = getConfig();
798
- if (!config.accountProfile || !deps.connectStore)
799
- throw new ZoomError("configuration_error", "select a stored local profile for meeting context");
800
- connection = await deps.connectStore({
801
- profile: config.accountProfile,
802
- externalId: profileAccountKey(config.accountProfile),
803
- intent: "read",
804
- });
805
- if (!connection.meetingContext)
806
- throw new ZoomError("configuration_error", "meeting context is unavailable in this store connector");
807
- return connection.meetingContext;
808
- };
659
+ const meetingContext = async () => (await connected()).meetingContext;
809
660
  addMeetingContextCommands(program, {
810
661
  signal,
811
- output: (value) => buffer(render(value, program.opts().jsonl ? "jsonl" : program.opts().json ? "json" : "text", deps, fields, true, false, execution?.meta)),
662
+ output,
812
663
  context: meetingContext,
813
664
  });
814
665
  addMeetingPassageCommand(program, {
815
666
  signal,
816
667
  read: async (reference, options) => {
817
668
  if (fields && options.format !== "json")
818
- throw new ZoomError("validation_error", "passage field selection requires --json or --format json");
819
- const context = await meetingContext();
820
- if (!context.passage)
821
- throw new ZoomError("configuration_error", "meeting passage is unavailable in this store connector");
822
- return context.passage(reference, options);
669
+ throw new CliError("validation_error", "passage field selection requires --json or --format json");
670
+ return (await meetingContext()).passage(reference, options);
823
671
  },
824
672
  output: (result, format) => {
825
673
  if (format === "json")
826
- buffer(render(result.packet, program.opts().jsonl ? "jsonl" : "json", deps, fields, true, false, execution?.meta));
674
+ buffer(rendered(bodyOf(result.packet), program.opts().jsonl ? "jsonl" : "json"));
827
675
  else
828
676
  buffer(result.content);
829
677
  },
830
678
  });
831
- const meetingSemantic = async () => {
832
- if (deps.meetingSemantic)
833
- return deps.meetingSemantic;
834
- const config = getConfig();
835
- if (!config.accountProfile || !deps.connectStore)
836
- throw new ZoomError("configuration_error", "select a stored local profile for meeting vectors");
837
- connection = await deps.connectStore({
838
- profile: config.accountProfile,
839
- externalId: profileAccountKey(config.accountProfile),
840
- intent: "read",
841
- });
842
- if (!connection.meetingSemantic)
843
- throw new ZoomError("configuration_error", "meeting vector capabilities are unavailable in this store connector");
844
- return connection.meetingSemantic;
845
- };
679
+ const meetingSemantic = async () => (await connected()).meetingSemantic;
846
680
  addMeetingSearchCommands(program, {
847
681
  signal,
848
682
  offline: () => Boolean(program.opts().offline),
849
- output: integrationOutput,
850
- words: async () => {
851
- if (deps.meetingSearch)
852
- return deps.meetingSearch;
853
- const connected = await integrationConnection("meeting-search");
854
- if (!connected.meetingSearch)
855
- throw new ZoomError("configuration_error", "meeting search is unavailable in this store connector");
856
- return connected.meetingSearch;
857
- },
683
+ output,
684
+ words: async () => (await connected()).meetingSearch,
858
685
  meaning: meetingSemantic,
859
686
  archive: (deps.archiveTranscripts ?? { search: searchArchiveTranscripts }).search,
860
687
  });
@@ -862,27 +689,29 @@ export const createProgram = (deps) => {
862
689
  signal,
863
690
  offline: () => Boolean(program.opts().offline),
864
691
  receipt,
865
- output: integrationOutput,
692
+ output,
866
693
  capabilities: meetingSemantic,
867
694
  });
868
695
  annotateMeetingCommands(program);
869
696
  const parseAsync = program.parseAsync.bind(program);
870
697
  program.parseAsync = async (...args) => {
871
698
  if (parsing)
872
- throw new ZoomError("validation_error", "concurrent command parsing is unavailable");
699
+ throw new CliError("validation_error", "concurrent command parsing is unavailable");
873
700
  resetCliOptions(program);
874
701
  parsing = true;
702
+ answerExitCodes.set(program, 0);
875
703
  config = undefined;
876
704
  activeSource = undefined;
877
705
  execution = undefined;
878
706
  activeTokens = undefined;
879
707
  watchConfig = undefined;
880
708
  watchSince = undefined;
881
- fields = undefined;
709
+ const lifted = liftJsonFields(program, args);
710
+ fields = lifted.fields;
882
711
  let result = program;
883
712
  let failed = false;
884
713
  let failure;
885
- const running = parseAsync(...args);
714
+ const running = parseAsync(...lifted.args);
886
715
  void running.catch(() => { });
887
716
  try {
888
717
  const active = currentExecution();
@@ -894,18 +723,18 @@ export const createProgram = (deps) => {
894
723
  failure = error;
895
724
  }
896
725
  finally {
897
- if (connection) {
898
- const closing = connection;
899
- connection = undefined;
726
+ if (connecting) {
727
+ const closing = connecting;
728
+ connecting = undefined;
900
729
  selectedStore = deps.store;
901
730
  selectedAccountId = deps.accountId;
902
731
  try {
903
- await closing.close();
732
+ await (await closing.catch(() => undefined))?.close();
904
733
  }
905
734
  catch {
906
735
  if (!failed) {
907
736
  failed = true;
908
- failure = new ZoomError("configuration_error", "could not close the shared meeting store");
737
+ failure = new CliError("configuration_error", "could not close the shared meeting store");
909
738
  }
910
739
  }
911
740
  }
@@ -935,7 +764,7 @@ export const run = async (args, deps) => {
935
764
  const program = createProgram(deps);
936
765
  try {
937
766
  await program.parseAsync(args, { from: "user" });
938
- return 0;
767
+ return answerExitCodes.get(program) ?? 0;
939
768
  }
940
769
  catch (error) {
941
770
  if (typeof error === "object" &&
@@ -947,18 +776,19 @@ export const run = async (args, deps) => {
947
776
  const failure = isCliError(error)
948
777
  ? error
949
778
  : unknownOutcome
950
- ? new ZoomError("outcome_unknown", "operation outcome is unknown; verify stored state before retrying")
779
+ ? new CliError("outcome_unknown", "operation outcome is unknown; verify stored state before retrying")
951
780
  : error instanceof CommanderError
952
- ? new ZoomError("validation_error", "command failed; use --help for usage")
781
+ ? new CliError("validation_error", "command failed; use --help for usage")
953
782
  : error;
954
- return reportFailure(failure, args, deps, commandPath(program, args));
783
+ const selected = commandSelection(program, args);
784
+ return reportFailure(failure, args, deps, selected.path, hostFailureContext(program, metaOf(selected.action).effects));
955
785
  }
956
786
  };
957
- export const reportFailure = (failure, args, deps, command = "zm") => {
787
+ export const reportFailure = (failure, args, deps, command = "zm", context = hostFailureContext()) => {
958
788
  const end = args.indexOf("--");
959
789
  const flags = args.slice(0, end < 0 ? undefined : end);
960
790
  const machine = flags.some((word) => word === "--json" || word === "--jsonl");
961
- const object = errorObjectOf(failure, failureContext());
791
+ const object = errorObjectOf(failure, context);
962
792
  const record = flags.includes("--debug")
963
793
  ? errorRecordOf(failure, object, { command, service: "zm", version: VERSION })
964
794
  : undefined;
@@ -966,7 +796,7 @@ export const reportFailure = (failure, args, deps, command = "zm") => {
966
796
  deps.diagnostic(`${renderError(object, { cli: "zm", interactive, ...(record ? { record } : {}) })}\n`);
967
797
  return exitCodeOfError(object);
968
798
  };
969
- const commandPath = (program, args) => {
799
+ const commandSelection = (program, args) => {
970
800
  const words = [];
971
801
  let current = program;
972
802
  for (const word of args) {
@@ -980,6 +810,6 @@ const commandPath = (program, args) => {
980
810
  words.push(word);
981
811
  current = next;
982
812
  }
983
- return words.length ? words.join(" ") : "zm";
813
+ return { path: words.length ? words.join(" ") : "zm", action: current };
984
814
  };
985
815
  //# sourceMappingURL=program.js.map