@trydreamstate/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/dist/index.js ADDED
@@ -0,0 +1,896 @@
1
+ #!/usr/bin/env node
2
+ "use strict";
3
+ Object.defineProperty(exports, "__esModule", { value: true });
4
+ exports.CliUsageError = exports.CLI_EXIT = void 0;
5
+ exports.parseCliArgs = parseCliArgs;
6
+ exports.toolTurnToken = toolTurnToken;
7
+ exports.refusalEnvelope = refusalEnvelope;
8
+ exports.openWebUrl = openWebUrl;
9
+ exports.exitCodeForError = exitCodeForError;
10
+ exports.runCli = runCli;
11
+ const node_child_process_1 = require("node:child_process");
12
+ const node_crypto_1 = require("node:crypto");
13
+ const promises_1 = require("node:fs/promises");
14
+ const tableFilePreview_1 = require("./tableFilePreview");
15
+ const node_os_1 = require("node:os");
16
+ const node_util_1 = require("node:util");
17
+ const config_1 = require("./config");
18
+ const turnCache_1 = require("./turnCache");
19
+ const client_1 = require("./client");
20
+ const skillInstaller_1 = require("./skillInstaller");
21
+ exports.CLI_EXIT = { SUCCESS: 0, UNCLASSIFIED: 1, USAGE: 2, AUTH: 3, NOT_FOUND: 4, SERVER: 5, RATE_LIMITED: 6, APPROVAL: 7, TIMEOUT: 8, REFUSED: 9, TRANSPORT: 10 };
22
+ const TOOLS_RUN_MODES = ['smoke_test', 'dry_run', 'live'];
23
+ const USAGE = `Dreamstate CLI
24
+
25
+ Usage:
26
+ dreamstate --help
27
+ dreamstate --version
28
+ dreamstate login
29
+ dreamstate login --token <ds_live_...>
30
+ dreamstate logout
31
+ dreamstate whoami [--json-output|--json]
32
+ dreamstate auth login [--api-key-file <path>]
33
+ dreamstate auth whoami [--json-output|--json]
34
+ dreamstate packs list [--json-output|--json]
35
+ dreamstate packs get <pack_id> [--json-output|--json]
36
+ dreamstate packs route --goal <text> [--json-output|--json]
37
+ dreamstate skills list [--query <query>] [--limit <n>] [--json-output|--json]
38
+ dreamstate skills get <skill_id> [--json-output|--json]
39
+ dreamstate skills search <query> [--limit <n>] [--json-output|--json]
40
+ dreamstate skills install [--skill <skill_id|*>] [--agents <codex claude-code cursor>] [--project] [--copy] [--json-output|--json]
41
+ dreamstate skills doctor [--agents <codex claude-code cursor>] [--project] [--json-output|--json]
42
+ dreamstate commands search <term> [--json-output|--json]
43
+ dreamstate tools search --goal <goal> [--terms <csv>] [--limit <n>] [--toolset <pack_id>] [--json-output|--json]
44
+ dreamstate tools get <capability_id> [--json-output|--json]
45
+ dreamstate tools run <capability_id> [--params <object>|--json <body>] [--turn-token <token>] (--live | --dry-run | --mode <smoke_test|dry_run|live>) [--agent-session-id <uuid>] [--approved] [--approved-effect-unknown] [--max-credits <n>] [--idempotency-key <key>] [--open] [--json-output|--json]
46
+ dreamstate sequences <verb> [args] [flags]
47
+ dreamstate workflows <verb> [args] [flags]
48
+ dreamstate tables import --table-id <uuid> --file <path> [--format <csv|json|jsonl|xlsx>] [--preview] [--upsert-key <header>] [--background] [--json-output|--json]
49
+ dreamstate runs list [--json <query>] [--json-output|--json]
50
+ dreamstate runs get <run_id> [--json-output|--json]
51
+ dreamstate runs resume <run_id> --state-version <n> [--reason <text>] [--idempotency-key <key>] [--open] [--json-output|--json]
52
+ dreamstate runs events <run_id> [--json-output|--json]
53
+ dreamstate runs cancel <run_id> --json <body> [--idempotency-key <key>] [--json-output|--json]
54
+
55
+ All catalog discovery reads live API records. Set DREAMSTATE_API_URL and DREAMSTATE_API_KEY.
56
+ login opens browser approval; login --token stores a ds_live_ key (for SSH or remote machines) after verifying it with whoami.
57
+ logout deletes the stored credentials file. --agents takes space-separated, comma-separated or repeated values.
58
+ `;
59
+ class CliUsageError extends Error {
60
+ constructor(message) { super(message); this.name = 'CliUsageError'; }
61
+ }
62
+ exports.CliUsageError = CliUsageError;
63
+ function outputOption(values) {
64
+ return values['json-output'] ? { jsonOutput: true } : {};
65
+ }
66
+ function parseJsonObject(raw, flag = '--json') {
67
+ try {
68
+ const value = JSON.parse(raw);
69
+ if (!value || typeof value !== 'object' || Array.isArray(value))
70
+ throw new Error('must contain a JSON object');
71
+ return value;
72
+ }
73
+ catch (error) {
74
+ throw new CliUsageError(`${flag} ${error instanceof Error ? error.message : 'is invalid'}`);
75
+ }
76
+ }
77
+ function requireOne(positionals, usage) {
78
+ if (positionals.length !== 2 || !positionals[1])
79
+ throw new CliUsageError(`Usage: ${usage}`);
80
+ return positionals[1];
81
+ }
82
+ function csv(raw, flag = '--terms') {
83
+ const values = raw.split(',').map((value) => value.trim()).filter(Boolean);
84
+ if (!values.length)
85
+ throw new CliUsageError(`${flag} must contain at least one value`);
86
+ return [...new Set(values)];
87
+ }
88
+ function requireToolsRunMode(body) {
89
+ if (TOOLS_RUN_MODES.includes(body.mode))
90
+ return;
91
+ throw new CliUsageError('mode must be one of: smoke_test, dry_run, live.');
92
+ }
93
+ function nonNegativeNumber(raw, flag) {
94
+ const value = Number(raw);
95
+ if (!Number.isFinite(value) || value < 0)
96
+ throw new CliUsageError(`${flag} must be a non-negative number`);
97
+ return value;
98
+ }
99
+ function nonNegativeInteger(raw, flag) {
100
+ const value = Number(raw);
101
+ if (!Number.isSafeInteger(value) || value < 0)
102
+ throw new CliUsageError(`${flag} must be a non-negative integer`);
103
+ return value;
104
+ }
105
+ function parsePacks(argv) {
106
+ const { values, positionals } = (0, node_util_1.parseArgs)({ args: argv, options: { goal: { type: 'string' }, 'json-output': { type: 'boolean' }, help: { type: 'boolean', short: 'h' } }, allowPositionals: true, strict: true });
107
+ if (values.help)
108
+ return { command: 'help' };
109
+ if (positionals.length === 1 && positionals[0] === 'list')
110
+ return { command: 'packs_list', ...outputOption(values) };
111
+ if (positionals[0] === 'get')
112
+ return { command: 'packs_get', packId: requireOne(positionals, 'dreamstate packs get <pack_id>'), ...outputOption(values) };
113
+ if (positionals.length === 1 && positionals[0] === 'route' && values.goal?.trim())
114
+ return { command: 'packs_route', goal: values.goal.trim(), ...outputOption(values) };
115
+ throw new CliUsageError('Usage: dreamstate packs <list|get|route>');
116
+ }
117
+ /** Rewrites `--agents a b c` into `--agents=a --agents=b --agents=c` so parseArgs sees every value. */
118
+ function spaceSeparatedAgents(argv) {
119
+ const out = [];
120
+ for (let index = 0; index < argv.length; index += 1) {
121
+ if (argv[index] !== '--agents') {
122
+ out.push(argv[index]);
123
+ continue;
124
+ }
125
+ let consumed = 0;
126
+ while (index + 1 < argv.length && !argv[index + 1].startsWith('-')) {
127
+ out.push(`--agents=${argv[index + 1]}`);
128
+ index += 1;
129
+ consumed += 1;
130
+ }
131
+ if (!consumed)
132
+ out.push('--agents');
133
+ }
134
+ return out;
135
+ }
136
+ function parseSkills(argv) {
137
+ const { values, positionals } = (0, node_util_1.parseArgs)({ args: spaceSeparatedAgents(argv), options: { query: { type: 'string' }, limit: { type: 'string' }, skill: { type: 'string' }, agents: { type: 'string', multiple: true }, project: { type: 'boolean' }, copy: { type: 'boolean' }, 'json-output': { type: 'boolean' }, help: { type: 'boolean', short: 'h' } }, allowPositionals: true, strict: true });
138
+ if (values.help)
139
+ return { command: 'help' };
140
+ const agents = () => {
141
+ if (!values.agents)
142
+ return [...skillInstaller_1.SKILL_INSTALL_AGENTS];
143
+ const requested = [...new Set(values.agents.flatMap((raw) => csv(raw, '--agents')))];
144
+ const invalid = requested.filter((agent) => !skillInstaller_1.SKILL_INSTALL_AGENTS.includes(agent));
145
+ if (invalid.length)
146
+ throw new CliUsageError(`--agents values must be one of: ${skillInstaller_1.SKILL_INSTALL_AGENTS.join(', ')} (got: ${invalid.join(', ')})`);
147
+ return requested;
148
+ };
149
+ if (positionals.length === 1 && positionals[0] === 'list') {
150
+ const query = {};
151
+ if (values.query)
152
+ query.query = values.query;
153
+ if (values.limit !== undefined)
154
+ query.limit = nonNegativeInteger(values.limit, '--limit');
155
+ return { command: 'skills_list', query, ...outputOption(values) };
156
+ }
157
+ if (positionals[0] === 'get')
158
+ return { command: 'skills_get', skillId: requireOne(positionals, 'dreamstate skills get <skill_id>'), ...outputOption(values) };
159
+ if (positionals[0] === 'search' && positionals.length > 1) {
160
+ const query = { query: positionals.slice(1).join(' ') };
161
+ if (values.limit !== undefined)
162
+ query.limit = nonNegativeInteger(values.limit, '--limit');
163
+ return { command: 'skills_search', query, ...outputOption(values) };
164
+ }
165
+ if (positionals.length === 1 && positionals[0] === 'install')
166
+ return { command: 'skills_install', skillId: values.skill?.trim() || '*', agents: agents(), ...(values.project ? { project: true } : {}), ...(values.copy ? { copy: true } : {}), ...outputOption(values) };
167
+ if (positionals.length === 1 && positionals[0] === 'doctor')
168
+ return { command: 'skills_doctor', agents: agents(), ...(values.project ? { project: true } : {}), ...outputOption(values) };
169
+ throw new CliUsageError('Usage: dreamstate skills <list|get|search|install|doctor>');
170
+ }
171
+ function parseCommands(argv) {
172
+ const { values, positionals } = (0, node_util_1.parseArgs)({ args: argv, options: { 'json-output': { type: 'boolean' }, help: { type: 'boolean', short: 'h' } }, allowPositionals: true, strict: true });
173
+ if (values.help)
174
+ return { command: 'help' };
175
+ if (positionals[0] !== 'search' || positionals.length < 2)
176
+ throw new CliUsageError('Usage: dreamstate commands search <term>');
177
+ return { command: 'commands_search', term: positionals.slice(1).join(' '), ...outputOption(values) };
178
+ }
179
+ /**
180
+ * One place that turns the mode flags into a mode. --dry-run, --live and --mode
181
+ * are mutually exclusive ways of saying the same thing, and disagreeing ones are a
182
+ * usage error rather than a silent winner.
183
+ *
184
+ * --approved is deliberately NOT here. It is a retained no-op: the run body schema
185
+ * is strict and has no `approved` key, so sending it failed the entire call rather
186
+ * than authorizing anything. Spend authority is max_credits and a human grant
187
+ * arrives as approval_id.
188
+ */
189
+ function toolsRunMode(values) {
190
+ const chosen = new Set();
191
+ if (values['dry-run'])
192
+ chosen.add('dry_run');
193
+ if (values.live)
194
+ chosen.add('live');
195
+ if (values.mode !== undefined) {
196
+ const mode = values.mode.trim();
197
+ if (!TOOLS_RUN_MODES.includes(mode)) {
198
+ throw new CliUsageError(`--mode must be one of: ${TOOLS_RUN_MODES.join(', ')}`);
199
+ }
200
+ chosen.add(mode);
201
+ }
202
+ if (chosen.size > 1) {
203
+ throw new CliUsageError(`Conflicting execution modes requested: ${[...chosen].sort().join(', ')}. Pass exactly one of --live, --dry-run, --mode.`);
204
+ }
205
+ return [...chosen][0];
206
+ }
207
+ function parseTools(argv) {
208
+ const { values, positionals } = (0, node_util_1.parseArgs)({ args: argv, options: {
209
+ json: { type: 'string' }, params: { type: 'string' }, goal: { type: 'string' }, terms: { type: 'string' },
210
+ limit: { type: 'string' }, toolset: { type: 'string' },
211
+ 'idempotency-key': { type: 'string' }, 'json-output': { type: 'boolean' }, open: { type: 'boolean' },
212
+ 'dry-run': { type: 'boolean' }, live: { type: 'boolean' }, mode: { type: 'string' },
213
+ 'agent-session-id': { type: 'string' }, approved: { type: 'boolean' }, 'approved-effect-unknown': { type: 'boolean' },
214
+ 'max-credits': { type: 'string' }, 'turn-token': { type: 'string' }, help: { type: 'boolean', short: 'h' },
215
+ }, allowPositionals: true, strict: true });
216
+ if (values.help)
217
+ return { command: 'help' };
218
+ const output = outputOption(values);
219
+ if (positionals.length === 1 && positionals[0] === 'search') {
220
+ const query = values.json ? parseJsonObject(values.json) : {};
221
+ if (values.goal)
222
+ query.goal = values.goal;
223
+ if (values.terms)
224
+ query.query_terms = csv(values.terms);
225
+ if (values.limit !== undefined)
226
+ query.limit = nonNegativeInteger(values.limit, '--limit');
227
+ if (values.toolset !== undefined)
228
+ query.toolset = values.toolset;
229
+ if (typeof query.goal !== 'string' && !Array.isArray(query.query_terms))
230
+ throw new CliUsageError('tools search requires --goal, --terms, or --json query fields');
231
+ return { command: 'tools_search', query, ...output };
232
+ }
233
+ if (positionals[0] === 'get')
234
+ return { command: 'tools_get', capabilityId: requireOne(positionals, 'dreamstate tools get <capability_id>'), ...output };
235
+ if (positionals[0] === 'run') {
236
+ const capabilityId = requireOne(positionals, 'dreamstate tools run <capability_id> [--params <object>|--json <body>]');
237
+ if (values.json && values.params)
238
+ throw new CliUsageError('Use either --params for tool parameters or --json for the complete request, not both.');
239
+ if (!values.json && !values.params)
240
+ throw new CliUsageError('tools run requires --params <object> or --json <body>; fetch live schema first with tools get');
241
+ const body = values.json ? parseJsonObject(values.json) : {};
242
+ if (values.params)
243
+ body.params = parseJsonObject(values.params, '--params');
244
+ // The server never defaults a mode, so a mutating run with none is refused
245
+ // mode_required; --live and --mode exist so a caller can actually ask for one.
246
+ // `approved` is no longer sent: the run body schema is strict and has no such
247
+ // key, so passing it failed the whole call instead of authorizing anything.
248
+ const chosenMode = toolsRunMode(values);
249
+ if (chosenMode)
250
+ body.mode = chosenMode;
251
+ if (values['agent-session-id'])
252
+ body.agent_session_id = values['agent-session-id'];
253
+ if (values['approved-effect-unknown'])
254
+ body.approved_effect_unknown = true;
255
+ if (values['max-credits'] !== undefined)
256
+ body.max_credits = nonNegativeNumber(values['max-credits'], '--max-credits');
257
+ // No client-side mode requirement: the server owns that refusal and answers
258
+ // mode_required with a repair, and the parse grammar is expected to accept a
259
+ // run without one. What was missing was any way to ASK for live.
260
+ if (body.mode !== undefined)
261
+ requireToolsRunMode(body);
262
+ return { command: 'tools_run', capabilityId, body, ...(values['turn-token'] ? { turnToken: values['turn-token'] } : {}), ...(values['idempotency-key'] ? { idempotencyKey: values['idempotency-key'] } : {}), ...(values.open ? { open: true } : {}), ...output };
263
+ }
264
+ throw new CliUsageError('Usage: dreamstate tools <search|get|run>');
265
+ }
266
+ function parseTables(argv) {
267
+ const { values, positionals } = (0, node_util_1.parseArgs)({ args: argv, options: {
268
+ 'table-id': { type: 'string' }, file: { type: 'string' }, format: { type: 'string' }, background: { type: 'boolean' },
269
+ preview: { type: 'boolean' }, 'upsert-key': { type: 'string' },
270
+ 'json-output': { type: 'boolean' }, help: { type: 'boolean', short: 'h' },
271
+ }, allowPositionals: true, strict: true });
272
+ if (values.help)
273
+ return { command: 'help' };
274
+ if (positionals.length !== 1 || positionals[0] !== 'import' || !values['table-id'] || !values.file) {
275
+ throw new CliUsageError('Usage: dreamstate tables import --table-id <uuid> --file <path> [--format <csv|json|jsonl|xlsx>] [--preview] [--upsert-key <header>] [--background]');
276
+ }
277
+ // --format is an override, not a requirement. Requiring it made the caller restate
278
+ // what the file path already says, and an omitted flag produced a usage error rather
279
+ // than an import. Omitted, the flag is not forwarded at all and the server derives
280
+ // the kind from the file it receives.
281
+ const format = values.format;
282
+ if (format !== undefined && !['csv', 'json', 'jsonl', 'xlsx'].includes(format)) {
283
+ throw new CliUsageError('--format must be one of csv, json, jsonl, xlsx');
284
+ }
285
+ const upsertKey = values['upsert-key']?.trim();
286
+ return {
287
+ command: 'tables_import',
288
+ tableId: values['table-id'],
289
+ file: values.file,
290
+ ...(format ? { format: format } : {}),
291
+ ...(values.preview ? { preview: true } : {}),
292
+ ...(upsertKey ? { upsertKey } : {}),
293
+ background: Boolean(values.background),
294
+ ...outputOption(values),
295
+ };
296
+ }
297
+ function parseRuns(argv) {
298
+ const { values, positionals } = (0, node_util_1.parseArgs)({ args: argv, options: { json: { type: 'string' }, reason: { type: 'string' }, 'state-version': { type: 'string' }, 'idempotency-key': { type: 'string' }, open: { type: 'boolean' }, 'json-output': { type: 'boolean' }, help: { type: 'boolean', short: 'h' } }, allowPositionals: true, strict: true });
299
+ if (values.help)
300
+ return { command: 'help' };
301
+ const output = outputOption(values);
302
+ if (positionals.length === 1 && positionals[0] === 'list')
303
+ return { command: 'runs_list', query: values.json ? parseJsonObject(values.json) : {}, ...output };
304
+ const runId = requireOne(positionals, `dreamstate runs ${positionals[0] ?? '<get|events|cancel>'} <run_id>`);
305
+ if (positionals[0] === 'get')
306
+ return { command: 'runs_get', runId, ...output };
307
+ if (positionals[0] === 'events')
308
+ return { command: 'runs_events', runId, ...output };
309
+ if (positionals[0] === 'resume') {
310
+ if (values.json)
311
+ throw new CliUsageError('Use --state-version and --reason with runs resume, not --json.');
312
+ if (values['state-version'] === undefined)
313
+ throw new CliUsageError('--state-version is required for runs resume.');
314
+ return { command: 'runs_resume', runId, stateVersion: nonNegativeInteger(values['state-version'], '--state-version'), ...(values.reason ? { reason: values.reason } : {}), ...(values['idempotency-key'] ? { idempotencyKey: values['idempotency-key'] } : {}), ...(values.open ? { open: true } : {}), ...output };
315
+ }
316
+ if (positionals[0] === 'cancel') {
317
+ if (!values.json)
318
+ throw new CliUsageError('runs cancel requires --json <body>');
319
+ return { command: 'runs_cancel', runId, body: parseJsonObject(values.json), ...(values['idempotency-key'] ? { idempotencyKey: values['idempotency-key'] } : {}), ...output };
320
+ }
321
+ throw new CliUsageError('Usage: dreamstate runs <get|events|cancel> <run_id>');
322
+ }
323
+ function normalizeJsonOutputAlias(argv) {
324
+ return argv.map((argument, index) => argument === '--json' && (index === argv.length - 1 || argv[index + 1]?.startsWith('-'))
325
+ ? '--json-output'
326
+ : argument);
327
+ }
328
+ function parseCliArgs(argv) {
329
+ argv = normalizeJsonOutputAlias(argv);
330
+ if (!argv.length || argv[0] === '--help' || argv[0] === '-h')
331
+ return { command: 'help' };
332
+ if (argv.length === 1 && (argv[0] === '--version' || argv[0] === '-v'))
333
+ return { command: 'version' };
334
+ const [group, ...rest] = argv;
335
+ if (group === 'login') {
336
+ const { values, positionals } = (0, node_util_1.parseArgs)({ args: rest, options: { token: { type: 'string' }, 'api-key-file': { type: 'string' }, 'json-output': { type: 'boolean' }, help: { type: 'boolean', short: 'h' } }, allowPositionals: true, strict: true });
337
+ if (values.help)
338
+ return { command: 'help' };
339
+ if (positionals.length)
340
+ throw new CliUsageError('Usage: dreamstate login [--token <ds_live_...>]');
341
+ if (values.token !== undefined && values['api-key-file'])
342
+ throw new CliUsageError('Use either --token or --api-key-file, not both.');
343
+ return { command: 'login', ...(values.token !== undefined ? { token: values.token } : {}), ...(values['api-key-file'] ? { apiKeyFile: values['api-key-file'] } : {}), ...outputOption(values) };
344
+ }
345
+ if (group === 'logout') {
346
+ const { values, positionals } = (0, node_util_1.parseArgs)({ args: rest, options: { 'json-output': { type: 'boolean' }, help: { type: 'boolean', short: 'h' } }, allowPositionals: true, strict: true });
347
+ if (values.help)
348
+ return { command: 'help' };
349
+ if (positionals.length)
350
+ throw new CliUsageError('Usage: dreamstate logout');
351
+ return { command: 'logout', ...outputOption(values) };
352
+ }
353
+ if (group === 'whoami') {
354
+ const { values, positionals } = (0, node_util_1.parseArgs)({ args: rest, options: { 'json-output': { type: 'boolean' } }, allowPositionals: true, strict: true });
355
+ if (positionals.length === 0)
356
+ return { command: 'whoami', ...outputOption(values) };
357
+ throw new CliUsageError('Usage: dreamstate whoami [--json-output]');
358
+ }
359
+ if (group === 'auth') {
360
+ const { values, positionals } = (0, node_util_1.parseArgs)({ args: rest, options: { 'api-key-file': { type: 'string' }, 'json-output': { type: 'boolean' } }, allowPositionals: true, strict: true });
361
+ if (positionals.length === 1 && positionals[0] === 'login')
362
+ return { command: 'login', ...(values['api-key-file'] ? { apiKeyFile: values['api-key-file'] } : {}), ...outputOption(values) };
363
+ if (positionals.length === 1 && positionals[0] === 'whoami')
364
+ return { command: 'whoami', ...outputOption(values) };
365
+ throw new CliUsageError('Usage: dreamstate auth <login|whoami>');
366
+ }
367
+ if (group === 'packs')
368
+ return parsePacks(rest);
369
+ if (group === 'skills')
370
+ return parseSkills(rest);
371
+ if (group === 'commands')
372
+ return parseCommands(rest);
373
+ if (group === 'tools')
374
+ return parseTools(rest);
375
+ if (group === 'tables')
376
+ return parseTables(rest);
377
+ if (group === 'runs')
378
+ return parseRuns(rest);
379
+ if (group === 'sequences')
380
+ return require('./commands/sequences').parseSequencesCommand(rest);
381
+ if (group === 'workflows')
382
+ return require('./commands/workflows').parseWorkflowsCommand(rest);
383
+ throw new CliUsageError(`Unknown command: ${group}`);
384
+ }
385
+ function print(value, stdout, compact) {
386
+ stdout.write(`${JSON.stringify(value, (key, nested) => key === 'tool_turn_token' ? undefined : nested, compact ? 0 : 2)}\n`);
387
+ }
388
+ function printPackRoute(value, stdout) {
389
+ const data = value && typeof value === 'object' && !Array.isArray(value)
390
+ ? value.data
391
+ : null;
392
+ const result = data && typeof data === 'object' && !Array.isArray(data)
393
+ ? data
394
+ : {};
395
+ const route = result.route && typeof result.route === 'object' && !Array.isArray(result.route)
396
+ ? result.route
397
+ : null;
398
+ if (route && typeof route.pack_id === 'string' && typeof route.posture === 'string') {
399
+ stdout.write(`Pack: ${route.pack_id}\nPosture: ${route.posture}\nRecommended capability ids:\n`);
400
+ for (const capabilityId of Array.isArray(route.recommended_capability_ids) ? route.recommended_capability_ids : []) {
401
+ if (typeof capabilityId === 'string')
402
+ stdout.write(`${capabilityId}\n`);
403
+ }
404
+ return;
405
+ }
406
+ stdout.write('No matching pack.\nPacks considered:\n');
407
+ for (const alternate of Array.isArray(result.alternates) ? result.alternates : []) {
408
+ if (!alternate || typeof alternate !== 'object' || Array.isArray(alternate))
409
+ continue;
410
+ const { pack_id: packId, reason } = alternate;
411
+ if (typeof packId === 'string')
412
+ stdout.write(`${packId}${typeof reason === 'string' ? `: ${reason}` : ''}\n`);
413
+ }
414
+ }
415
+ function toolTurnToken(value) {
416
+ const root = value && typeof value === 'object' ? value : {};
417
+ const data = root.data && typeof root.data === 'object' ? root.data : {};
418
+ if (typeof data.tool_turn_token === 'string' && data.tool_turn_token.trim())
419
+ return data.tool_turn_token;
420
+ throw new client_1.CliError('tools get did not return a tool_turn_token.', 'invalid_tool_turn_token', 0);
421
+ }
422
+ function code(error) {
423
+ return error && typeof error === 'object' && typeof error.code === 'string'
424
+ ? error.code
425
+ : undefined;
426
+ }
427
+ function refusalEnvelope(error, exitCode) {
428
+ const cliError = error instanceof client_1.CliError ? error : null;
429
+ return {
430
+ ok: false,
431
+ executed: false,
432
+ error: {
433
+ code: code(error) ?? 'invalid_request',
434
+ message: error instanceof Error ? error.message : String(error),
435
+ ...(cliError?.details === undefined ? {} : { details: cliError.details }),
436
+ // The client keeps the server's retry decision and repair; dropping them
437
+ // here left CLI automation without the recovery Architect and MCP get.
438
+ ...(cliError?.retryable === undefined ? {} : { retryable: cliError.retryable }),
439
+ ...(cliError?.repair === undefined ? {} : { repair: cliError.repair }),
440
+ },
441
+ meta: { exit_code: exitCode },
442
+ };
443
+ }
444
+ function openWebUrl(rawUrl, launch = node_child_process_1.spawn) {
445
+ let url;
446
+ try {
447
+ url = new URL(rawUrl);
448
+ }
449
+ catch {
450
+ throw new client_1.CliError('The API returned an invalid web_url.', 'invalid_web_url', 0);
451
+ }
452
+ if (!['https:', 'http:'].includes(url.protocol) || rawUrl.length > 4_096) {
453
+ throw new client_1.CliError('The API returned an unsafe web_url.', 'invalid_web_url', 0);
454
+ }
455
+ const target = url.toString();
456
+ const [command, args] = process.platform === 'darwin'
457
+ ? ['open', [target]]
458
+ : process.platform === 'win32'
459
+ ? ['rundll32', ['url.dll,FileProtocolHandler', target]]
460
+ : ['xdg-open', [target]];
461
+ launch(command, args, { detached: true, stdio: 'ignore', shell: false }).unref();
462
+ }
463
+ function resultWebUrl(value) {
464
+ if (!value || typeof value !== 'object' || Array.isArray(value))
465
+ return null;
466
+ const envelope = value;
467
+ const meta = envelope.meta && typeof envelope.meta === 'object' && !Array.isArray(envelope.meta)
468
+ ? envelope.meta
469
+ : {};
470
+ if (typeof meta.web_url === 'string')
471
+ return meta.web_url;
472
+ const data = envelope.data && typeof envelope.data === 'object' && !Array.isArray(envelope.data)
473
+ ? envelope.data
474
+ : {};
475
+ return typeof data.web_url === 'string' ? data.web_url : null;
476
+ }
477
+ function openResult(value, runtime) {
478
+ const webUrl = resultWebUrl(value);
479
+ if (!webUrl)
480
+ throw new client_1.CliError('The API response did not include a web_url.', 'web_url_unavailable', 0);
481
+ (runtime.openWebUrl ?? openWebUrl)(webUrl);
482
+ }
483
+ function discoveredCapabilityId(value) {
484
+ const root = value && typeof value === 'object' ? value : {};
485
+ const data = root.data && typeof root.data === 'object' ? root.data : {};
486
+ const candidates = ['tools', 'capabilities', 'results'].flatMap((key) => Array.isArray(data[key]) ? data[key] : [])
487
+ .map((candidate) => candidate && typeof candidate === 'object'
488
+ ? candidate.capability_id ?? candidate.id
489
+ : undefined)
490
+ .filter((candidate) => typeof candidate === 'string' && candidate.length > 0);
491
+ const ids = [...new Set(candidates)];
492
+ if (ids.length !== 1)
493
+ throw new CliUsageError('Local file import discovery must return exactly one capability.');
494
+ return ids[0];
495
+ }
496
+ function hostedSkill(value) {
497
+ const root = value && typeof value === 'object' ? value : {};
498
+ const data = root.data && typeof root.data === 'object' ? root.data : {};
499
+ const skill = data.skill;
500
+ if (!skill || typeof skill !== 'object')
501
+ throw new client_1.CliError('Dreamstate API returned an invalid skill document.', 'invalid_response', 200);
502
+ const entry = skill;
503
+ if (typeof entry.id !== 'string' || typeof entry.name !== 'string' || typeof entry.description !== 'string' || typeof entry.body !== 'string') {
504
+ throw new client_1.CliError('Dreamstate API returned an invalid skill document.', 'invalid_response', 200);
505
+ }
506
+ const frontmatter = entry.frontmatter && typeof entry.frontmatter === 'object' && !Array.isArray(entry.frontmatter)
507
+ ? entry.frontmatter : undefined;
508
+ const documents = Array.isArray(entry.documents)
509
+ ? entry.documents.filter((document) => Boolean(document) && typeof document === 'object' && typeof document.name === 'string' && typeof document.content === 'string')
510
+ : undefined;
511
+ return { id: entry.id, name: entry.name, description: entry.description, body: entry.body, frontmatter, documents };
512
+ }
513
+ function exitCodeForError(error) {
514
+ if (code(error)?.startsWith('ERR_PARSE_ARGS'))
515
+ return exports.CLI_EXIT.USAGE;
516
+ if (error instanceof CliUsageError || error instanceof config_1.ApiKeyFileError || error instanceof config_1.ApiBaseUrlError || error instanceof config_1.MissingApiUrlError)
517
+ return exports.CLI_EXIT.USAGE;
518
+ if (error instanceof config_1.MissingApiKeyError)
519
+ return exports.CLI_EXIT.AUTH;
520
+ if (code(error) === 'request_timeout')
521
+ return exports.CLI_EXIT.TIMEOUT;
522
+ const status = error instanceof client_1.CliError ? error.status : 0;
523
+ if (status === 401 || status === 403)
524
+ return exports.CLI_EXIT.AUTH;
525
+ if (status === 404)
526
+ return exports.CLI_EXIT.NOT_FOUND;
527
+ if (status === 429)
528
+ return exports.CLI_EXIT.RATE_LIMITED;
529
+ if (code(error) === 'network_error')
530
+ return exports.CLI_EXIT.TRANSPORT;
531
+ if (status >= 500 || code(error) === 'invalid_response')
532
+ return exports.CLI_EXIT.SERVER;
533
+ if (status >= 400)
534
+ return exports.CLI_EXIT.REFUSED;
535
+ return exports.CLI_EXIT.UNCLASSIFIED;
536
+ }
537
+ async function readLoginKey(command, runtime) {
538
+ if (command.apiKeyFile)
539
+ return (0, config_1.readApiKeyFile)(command.apiKeyFile);
540
+ const fromEnv = (runtime.env ?? process.env).DREAMSTATE_API_KEY?.trim();
541
+ if (fromEnv)
542
+ return fromEnv;
543
+ const stdin = runtime.stdin ?? process.stdin;
544
+ if (!stdin.isTTY) {
545
+ let value = '';
546
+ for await (const part of stdin)
547
+ value += part.toString();
548
+ if (value.trim())
549
+ return value.trim();
550
+ }
551
+ if (runtime.promptHidden)
552
+ return runtime.promptHidden('API key (input hidden): ');
553
+ throw new CliUsageError('No API key provided. Pipe it on stdin, set DREAMSTATE_API_KEY, or pass --api-key-file.');
554
+ }
555
+ function deviceState() {
556
+ return (0, node_crypto_1.randomBytes)(32).toString('base64url');
557
+ }
558
+ function runtimeCredentialsPath(runtime) {
559
+ return runtime.credentialsPath ?? (0, config_1.credentialsPath)(undefined, runtime.env ?? process.env);
560
+ }
561
+ function wait(milliseconds) {
562
+ return new Promise((resolve) => setTimeout(resolve, milliseconds));
563
+ }
564
+ async function loginWithDeviceAuthorization(runtime, stdout, stderr) {
565
+ const environment = runtime.env ?? process.env;
566
+ const baseUrl = (0, config_1.resolveApiBaseUrl)(environment);
567
+ const state = runtime.deviceState ?? deviceState();
568
+ const authorization = await (0, client_1.requestDeviceAuthorization)(baseUrl, {
569
+ state,
570
+ client: { os: process.platform, hostname: (0, node_os_1.hostname)(), cli_version: client_1.CLI_VERSION },
571
+ });
572
+ stderr.write(`Open: ${authorization.verification_uri_complete}\nConfirmation code: ${authorization.user_code}\n`);
573
+ (runtime.openWebUrl ?? openWebUrl)(authorization.verification_uri_complete);
574
+ for (;;) {
575
+ const claim = await (0, client_1.claimDeviceAuthorization)(baseUrl, authorization.device_code);
576
+ if (claim.status === 'ready') {
577
+ await (0, config_1.storeApiKey)(claim.token, runtimeCredentialsPath(runtime));
578
+ const client = new client_1.DreamstateClient({
579
+ baseUrl,
580
+ apiKey: claim.token,
581
+ requestTimeoutMs: runtime.requestTimeoutMs,
582
+ onVersionWarning: (message) => (runtime.stderr ?? process.stderr).write(`${message}\n`),
583
+ });
584
+ print(await client.getWhoami(), stdout, false);
585
+ return;
586
+ }
587
+ await (runtime.sleep ?? wait)(authorization.interval_seconds * 1_000);
588
+ }
589
+ }
590
+ function ignoreBrokenPipe(stdout) {
591
+ // EPIPE is an async stream 'error', not a rejected promise. runCli().catch
592
+ // does not see it. A closed stdout (piped to head) must keep the exit code
593
+ // the command already set and must not dump a stack.
594
+ const emitter = stdout;
595
+ if (typeof emitter.on !== 'function')
596
+ return;
597
+ emitter.on('error', (error) => {
598
+ if (error.code !== 'EPIPE')
599
+ throw error;
600
+ });
601
+ }
602
+ async function runCli(argv = process.argv.slice(2), runtime = {}) {
603
+ const stdout = runtime.stdout ?? process.stdout;
604
+ const stderr = runtime.stderr ?? process.stderr;
605
+ ignoreBrokenPipe(stdout);
606
+ ignoreBrokenPipe(stderr);
607
+ try {
608
+ const command = parseCliArgs(argv);
609
+ if (command.command === 'help') {
610
+ print({ ok: true, data: { usage: USAGE }, meta: {} }, stdout, false);
611
+ process.exitCode = exports.CLI_EXIT.SUCCESS;
612
+ return;
613
+ }
614
+ if (command.command === 'version') {
615
+ print({ ok: true, data: { version: client_1.CLI_VERSION }, meta: {} }, stdout, false);
616
+ process.exitCode = exports.CLI_EXIT.SUCCESS;
617
+ return;
618
+ }
619
+ if (command.command === 'logout') {
620
+ const path = runtimeCredentialsPath(runtime);
621
+ let removed = true;
622
+ try {
623
+ await (0, promises_1.access)(path);
624
+ }
625
+ catch (error) {
626
+ if (error.code !== 'ENOENT')
627
+ throw error;
628
+ removed = false;
629
+ }
630
+ if (removed)
631
+ await (0, promises_1.rm)(path, { force: true });
632
+ const message = removed ? `Removed ${path}` : `Already logged out: no credentials at ${path}`;
633
+ stderr.write(`${message}\n`);
634
+ print({ ok: true, data: { credentials_path: path, removed }, meta: {} }, stdout, Boolean(command.jsonOutput));
635
+ process.exitCode = exports.CLI_EXIT.SUCCESS;
636
+ return;
637
+ }
638
+ if (command.command === 'login' && command.token !== undefined) {
639
+ const token = command.token.trim();
640
+ if (!token.startsWith('ds_live_') || token.length <= 'ds_live_'.length)
641
+ throw new CliUsageError('--token must be a Dreamstate API key starting with ds_live_.');
642
+ const baseUrl = (0, config_1.resolveApiBaseUrl)(runtime.env ?? process.env);
643
+ const client = new client_1.DreamstateClient({
644
+ baseUrl,
645
+ apiKey: token,
646
+ requestTimeoutMs: runtime.requestTimeoutMs,
647
+ onVersionWarning: (message) => stderr.write(`${message}\n`),
648
+ });
649
+ const whoami = await client.getWhoami();
650
+ await (0, config_1.storeApiKey)(token, runtimeCredentialsPath(runtime));
651
+ print(whoami, stdout, Boolean(command.jsonOutput));
652
+ process.exitCode = exports.CLI_EXIT.SUCCESS;
653
+ return;
654
+ }
655
+ if (command.command === 'login') {
656
+ if (command.apiKeyFile || (runtime.env ?? process.env).DREAMSTATE_API_KEY || runtime.promptHidden) {
657
+ const key = await readLoginKey(command, runtime);
658
+ if (!key.startsWith('ds_live_'))
659
+ throw new CliUsageError('Dreamstate API keys must start with ds_live_.');
660
+ const path = await (0, config_1.storeApiKey)(key, runtimeCredentialsPath(runtime));
661
+ print({ ok: true, data: { credentials_path: path }, meta: {} }, stdout, Boolean(command.jsonOutput));
662
+ stderr.write('Run dreamstate skills install to install Dreamstate skills.\n');
663
+ process.exitCode = exports.CLI_EXIT.SUCCESS;
664
+ return;
665
+ }
666
+ await loginWithDeviceAuthorization(runtime, stdout, stderr);
667
+ stderr.write('Run dreamstate skills install to install Dreamstate skills.\n');
668
+ process.exitCode = exports.CLI_EXIT.SUCCESS;
669
+ return;
670
+ }
671
+ if (command.command === 'skills_doctor') {
672
+ const environment = runtime.env ?? process.env;
673
+ const baseUrl = (0, config_1.resolveApiBaseUrl)(environment);
674
+ const credentialFilePath = runtimeCredentialsPath(runtime);
675
+ const apiKey = await (0, config_1.resolveApiKey)(environment, credentialFilePath);
676
+ const client = new client_1.DreamstateClient({ baseUrl, apiKey, requestTimeoutMs: runtime.requestTimeoutMs });
677
+ await client.listSkills({ limit: 1 });
678
+ const installers = await (0, skillInstaller_1.inspectSkillInstallers)(command.agents, runtime.homeDirectory, Boolean(command.project));
679
+ print({ ok: true, data: { index_reachable: true, installers }, meta: {} }, stdout, Boolean(command.jsonOutput));
680
+ process.exitCode = exports.CLI_EXIT.SUCCESS;
681
+ return;
682
+ }
683
+ // Preview parses the local file and writes nothing, so it must not require a key.
684
+ if (command.command === 'tables_import' && command.preview) {
685
+ await (0, promises_1.access)(command.file);
686
+ try {
687
+ const preview = await (0, tableFilePreview_1.previewLocalTableFile)(command.file, command.format);
688
+ print({
689
+ ok: true,
690
+ data: { ...preview, table_id: command.tableId, file_path: command.file, upsert_key: command.upsertKey ?? null },
691
+ meta: {},
692
+ }, stdout, Boolean(command.jsonOutput));
693
+ }
694
+ catch (error) {
695
+ throw new CliUsageError(error instanceof CliUsageError ? error.message : error instanceof Error ? error.message : String(error));
696
+ }
697
+ process.exitCode = exports.CLI_EXIT.SUCCESS;
698
+ return;
699
+ }
700
+ // init writes a local scaffold and calls no API, so it must not require a key.
701
+ // Resolving credentials first would refuse it with not_authenticated.
702
+ if (command.command === 'pack' && command.verb === 'init') {
703
+ if (command.pack === 'sequences') {
704
+ await require('./commands/sequences')
705
+ .runSequencesCommand(undefined, command, stdout);
706
+ }
707
+ else {
708
+ await require('./commands/workflows')
709
+ .runWorkflowsCommand(undefined, command, stdout);
710
+ }
711
+ process.exitCode = exports.CLI_EXIT.SUCCESS;
712
+ return;
713
+ }
714
+ const environment = runtime.env ?? process.env;
715
+ const baseUrl = (0, config_1.resolveApiBaseUrl)(environment);
716
+ const credentialFilePath = runtimeCredentialsPath(runtime);
717
+ let apiKey;
718
+ try {
719
+ apiKey = await (0, config_1.resolveApiKey)(environment, credentialFilePath);
720
+ }
721
+ catch (error) {
722
+ if (command.command === 'skills_install' && error instanceof config_1.MissingApiKeyError) {
723
+ throw new client_1.CliError('Dreamstate skills installation requires authentication.', 'skills_authentication_required', 401, {
724
+ details: { next_step: 'Run dreamstate login, then retry' },
725
+ });
726
+ }
727
+ throw error;
728
+ }
729
+ const client = new client_1.DreamstateClient({
730
+ baseUrl,
731
+ apiKey,
732
+ requestTimeoutMs: runtime.requestTimeoutMs,
733
+ onVersionWarning: (message) => (runtime.stderr ?? process.stderr).write(`${message}\n`),
734
+ });
735
+ let result;
736
+ switch (command.command) {
737
+ case 'whoami':
738
+ result = await client.getWhoami();
739
+ break;
740
+ case 'packs_list':
741
+ result = await client.listPacks();
742
+ break;
743
+ case 'packs_get':
744
+ result = await client.getPack(command.packId);
745
+ break;
746
+ case 'packs_route':
747
+ result = await client.routePack(command.goal);
748
+ break;
749
+ case 'skills_list':
750
+ result = await client.listSkills(command.query);
751
+ break;
752
+ case 'skills_get':
753
+ result = await client.getSkill(command.skillId);
754
+ break;
755
+ case 'skills_search':
756
+ result = await client.searchSkillDocuments(command.query);
757
+ break;
758
+ case 'skills_install': {
759
+ const listed = command.skillId === '*' ? await client.listAllSkills() : null;
760
+ const entries = listed && typeof listed === 'object' && listed.data?.skills;
761
+ const ids = command.skillId === '*'
762
+ ? Array.isArray(entries) ? entries.map((skill) => skill && typeof skill === 'object' ? skill.id : undefined).filter((id) => typeof id === 'string') : []
763
+ : [command.skillId];
764
+ if (!ids.length)
765
+ throw new client_1.CliError('Dreamstate API returned no installable skills.', 'invalid_response', 200);
766
+ // A remote id becomes a directory name; refuse traversal before any request or write.
767
+ if (ids.some((id) => !/^[a-z0-9][a-z0-9_-]*$/i.test(id)))
768
+ throw new CliUsageError('Dreamstate API returned an invalid skill id.');
769
+ const skills = await Promise.all(ids.map(async (id) => {
770
+ const skill = hostedSkill(await client.getSkill(id));
771
+ if (!/^[a-z0-9][a-z0-9_-]*$/i.test(skill.id))
772
+ throw new CliUsageError('Dreamstate API returned an invalid skill id.');
773
+ if (skill.id !== id)
774
+ throw new client_1.CliError('Dreamstate API returned a mismatched skill id.', 'invalid_response', 200);
775
+ return skill;
776
+ }));
777
+ const packs = await client.listPacks();
778
+ const installed = await (0, skillInstaller_1.installSkills)([...skills, (0, skillInstaller_1.routerSkill)(packs)], {
779
+ agents: command.agents,
780
+ homeDirectory: runtime.homeDirectory,
781
+ project: Boolean(command.project),
782
+ copy: Boolean(command.copy) || process.platform === 'win32',
783
+ });
784
+ result = { ok: true, data: { installed_files: installed.files, skill_ids: [...ids, 'router'], agents: command.agents, ...(command.project ? { lock_file: 'skills-lock.json' } : {}) }, meta: {} };
785
+ break;
786
+ }
787
+ case 'commands_search':
788
+ result = await client.listTools({ goal: command.term });
789
+ break;
790
+ case 'tools_search':
791
+ result = await client.listTools(command.query);
792
+ break;
793
+ case 'tools_get':
794
+ result = await client.getTool(command.capabilityId);
795
+ break;
796
+ case 'tools_run': {
797
+ // A quote binds to the turn that asked for it. Reuse a turn only while
798
+ // a quote is pending (after a dry run or a quote_required refusal), and
799
+ // forget it once the paid run is admitted, so every other call keeps
800
+ // fetching a fresh turn exactly as before.
801
+ const turns = (0, turnCache_1.turnCachePath)(credentialFilePath);
802
+ let warnedAboutTurnCache = false;
803
+ const warnTurnCache = () => {
804
+ if (warnedAboutTurnCache)
805
+ return;
806
+ warnedAboutTurnCache = true;
807
+ stderr.write('Warning: Dreamstate turn cache unavailable; continuing without turn reuse.\n');
808
+ };
809
+ const pending = command.turnToken
810
+ ? null
811
+ : await (0, turnCache_1.reusableTurnToken)(turns, baseUrl, apiKey, command.capabilityId, Date.now(), warnTurnCache);
812
+ const turnToken = command.turnToken ?? pending ?? toolTurnToken(await client.getTool(command.capabilityId));
813
+ try {
814
+ result = await client.runCapability(command.capabilityId, { ...command.body, tool_turn_token: turnToken }, command.idempotencyKey);
815
+ }
816
+ catch (error) {
817
+ if (!command.turnToken && code(error) === 'quote_required') {
818
+ await (0, turnCache_1.rememberTurnToken)(turns, baseUrl, apiKey, command.capabilityId, turnToken, Date.now(), warnTurnCache);
819
+ }
820
+ throw error;
821
+ }
822
+ if (!command.turnToken) {
823
+ if (command.body.mode === 'dry_run')
824
+ await (0, turnCache_1.rememberTurnToken)(turns, baseUrl, apiKey, command.capabilityId, turnToken, Date.now(), warnTurnCache);
825
+ else if (pending)
826
+ await (0, turnCache_1.forgetTurnToken)(turns, baseUrl, apiKey, command.capabilityId, warnTurnCache);
827
+ }
828
+ break;
829
+ }
830
+ case 'tables_import': {
831
+ await (0, promises_1.access)(command.file);
832
+ const discovered = await client.listTools({
833
+ goal: 'prepare a local file import into a table',
834
+ query_terms: ['local', 'file', 'import'],
835
+ });
836
+ const importCapabilityId = discoveredCapabilityId(discovered);
837
+ result = await client.runCapability(importCapabilityId, {
838
+ // A mutation run needs the turn token its own get minted; without it
839
+ // an API-key import was refused after the local file work.
840
+ tool_turn_token: toolTurnToken(await client.getTool(importCapabilityId)),
841
+ mode: 'live',
842
+ params: {
843
+ table_id: command.tableId,
844
+ file_path: command.file,
845
+ ...(command.format ? { file_kind: command.format } : {}),
846
+ ...(command.upsertKey ? { upsert_key: command.upsertKey } : {}),
847
+ },
848
+ });
849
+ break;
850
+ }
851
+ case 'runs_list':
852
+ result = await client.listRuns(command.query);
853
+ break;
854
+ case 'runs_get':
855
+ result = await client.getRun(command.runId);
856
+ break;
857
+ case 'runs_resume':
858
+ result = await client.resumeRun(command.runId, {
859
+ expected_state_version: command.stateVersion,
860
+ ...(command.reason ? { reason: command.reason } : {}),
861
+ }, command.idempotencyKey);
862
+ break;
863
+ case 'runs_events':
864
+ result = await client.getRunEvents(command.runId);
865
+ break;
866
+ case 'runs_cancel':
867
+ result = await client.cancelRun(command.runId, command.body, command.idempotencyKey);
868
+ break;
869
+ case 'pack': {
870
+ if (command.pack === 'sequences') {
871
+ await require('./commands/sequences').runSequencesCommand(client, command, stdout);
872
+ }
873
+ else {
874
+ await require('./commands/workflows').runWorkflowsCommand(client, command, stdout);
875
+ }
876
+ return;
877
+ }
878
+ default: return;
879
+ }
880
+ if ((command.command === 'tools_run' || command.command === 'runs_resume') && command.open)
881
+ openResult(result, runtime);
882
+ if (command.command === 'packs_route' && !command.jsonOutput)
883
+ printPackRoute(result, stdout);
884
+ else
885
+ print(result, stdout, Boolean(command.jsonOutput));
886
+ process.exitCode = exports.CLI_EXIT.SUCCESS;
887
+ }
888
+ catch (error) {
889
+ const exitCode = exitCodeForError(error);
890
+ stdout.write(`${JSON.stringify(refusalEnvelope(error, exitCode))}\n`);
891
+ process.exitCode = exitCode;
892
+ throw error;
893
+ }
894
+ }
895
+ if (require.main === module)
896
+ runCli().catch(() => undefined);