@trydreamstate/cli 1.3.2 → 1.3.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +29 -16
- package/agent-plugin/.claude-plugin/plugin.json +1 -1
- package/agent-plugin/.codex-plugin/plugin.json +1 -1
- package/agent-plugin/hooks/approve-readonly.sh +15 -0
- package/agent-plugin/hooks/hooks.json +17 -0
- package/agent-plugin/skills/crm/SKILL.md +2 -2
- package/agent-plugin/skills/diagnostics/SKILL.md +1 -1
- package/agent-plugin/skills/dreamstate/SKILL.md +13 -5
- package/agent-plugin/skills/first-win/SKILL.md +2 -2
- package/agent-plugin/skills/inbox-replies/SKILL.md +1 -1
- package/agent-plugin/skills/knowledge/SKILL.md +1 -1
- package/agent-plugin/skills/linkedin-posts/SKILL.md +1 -1
- package/agent-plugin/skills/linkedin-warm-signals/SKILL.md +2 -2
- package/agent-plugin/skills/list-building/SKILL.md +4 -4
- package/agent-plugin/skills/outreach/SKILL.md +1 -1
- package/agent-plugin/skills/outreach/steps-and-timing.md +1 -1
- package/agent-plugin/skills/plays/SKILL.md +2 -2
- package/agent-plugin/skills/reddit-posts/SKILL.md +1 -1
- package/agent-plugin/skills/skill-authoring/SKILL.md +1 -1
- package/agent-plugin/skills/social/SKILL.md +2 -2
- package/agent-plugin/skills/visibility/SKILL.md +2 -2
- package/agent-plugin/skills/workflows/SKILL.md +3 -3
- package/agent-plugin/skills/worksheet-tidy/SKILL.md +1 -1
- package/agent-plugin/skills/writing/SKILL.md +1 -1
- package/agent-plugin/skills/x-posts/SKILL.md +1 -1
- package/dist/agentGuidance.generated.json +35 -0
- package/dist/client.d.ts +31 -2
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +115 -33
- package/dist/commandTable.d.ts +26 -0
- package/dist/commandTable.d.ts.map +1 -0
- package/dist/commandTable.js +54 -0
- package/dist/commands/sequences.d.ts +1 -1
- package/dist/commands/sequences.d.ts.map +1 -1
- package/dist/commands/sequences.js +37 -36
- package/dist/commands/shared.d.ts +4 -26
- package/dist/commands/shared.d.ts.map +1 -1
- package/dist/commands/shared.js +129 -127
- package/dist/commands/workflows.d.ts +1 -1
- package/dist/commands/workflows.d.ts.map +1 -1
- package/dist/commands/workflows.js +35 -41
- package/dist/errors.d.ts +78 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +117 -0
- package/dist/gatedRun.d.ts +81 -0
- package/dist/gatedRun.d.ts.map +1 -0
- package/dist/gatedRun.js +370 -0
- package/dist/idempotency.d.ts +38 -0
- package/dist/idempotency.d.ts.map +1 -0
- package/dist/idempotency.js +106 -0
- package/dist/index.d.ts +21 -35
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +372 -366
- package/dist/runWait.d.ts +2 -1
- package/dist/runWait.d.ts.map +1 -1
- package/dist/runWait.js +3 -1
- package/dist/schemaCoercion.d.ts +20 -0
- package/dist/schemaCoercion.d.ts.map +1 -0
- package/dist/schemaCoercion.js +123 -0
- package/dist/skillInstaller.d.ts +40 -0
- package/dist/skillInstaller.d.ts.map +1 -1
- package/dist/skillInstaller.js +108 -9
- package/dist/tableFilePreview.d.ts +14 -0
- package/dist/tableFilePreview.d.ts.map +1 -1
- package/dist/tableFilePreview.js +19 -8
- package/dist/tableFileReader.d.ts +26 -0
- package/dist/tableFileReader.d.ts.map +1 -0
- package/dist/tableFileReader.js +108 -0
- package/dist/tablesImport.d.ts +57 -0
- package/dist/tablesImport.d.ts.map +1 -0
- package/dist/tablesImport.js +263 -0
- package/dist/turnCache.d.ts +14 -1
- package/dist/turnCache.d.ts.map +1 -1
- package/dist/turnCache.js +25 -8
- package/package.json +3 -2
package/dist/index.js
CHANGED
|
@@ -1,15 +1,13 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
"use strict";
|
|
3
3
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
4
|
-
exports.CliUsageError = exports.CLI_EXIT = void 0;
|
|
4
|
+
exports.toolTurnToken = exports.omitsToolTurnForInlineTableRead = exports.capabilityIdempotencyKey = exports.CliUsageError = exports.CLI_EXIT = void 0;
|
|
5
5
|
exports.parseCliArgs = parseCliArgs;
|
|
6
|
-
exports.toolTurnToken = toolTurnToken;
|
|
7
|
-
exports.omitsToolTurnForInlineTableRead = omitsToolTurnForInlineTableRead;
|
|
8
|
-
exports.capabilityIdempotencyKey = capabilityIdempotencyKey;
|
|
9
6
|
exports.refusalEnvelope = refusalEnvelope;
|
|
10
7
|
exports.openWebUrl = openWebUrl;
|
|
11
8
|
exports.exitCodeForError = exitCodeForError;
|
|
12
9
|
exports.extractWorkspaceFlag = extractWorkspaceFlag;
|
|
10
|
+
exports.statusData = statusData;
|
|
13
11
|
exports.runCli = runCli;
|
|
14
12
|
const node_child_process_1 = require("node:child_process");
|
|
15
13
|
const node_crypto_1 = require("node:crypto");
|
|
@@ -21,11 +19,21 @@ const node_util_1 = require("node:util");
|
|
|
21
19
|
const config_1 = require("./config");
|
|
22
20
|
const pluginInstaller_1 = require("./pluginInstaller");
|
|
23
21
|
const turnCache_1 = require("./turnCache");
|
|
22
|
+
const idempotency_1 = require("./idempotency");
|
|
24
23
|
const runWait_1 = require("./runWait");
|
|
25
24
|
const client_1 = require("./client");
|
|
25
|
+
const errors_1 = require("./errors");
|
|
26
|
+
Object.defineProperty(exports, "CLI_EXIT", { enumerable: true, get: function () { return errors_1.CLI_EXIT; } });
|
|
27
|
+
Object.defineProperty(exports, "CliUsageError", { enumerable: true, get: function () { return errors_1.CliUsageError; } });
|
|
28
|
+
const gatedRun_1 = require("./gatedRun");
|
|
29
|
+
Object.defineProperty(exports, "capabilityIdempotencyKey", { enumerable: true, get: function () { return gatedRun_1.capabilityIdempotencyKey; } });
|
|
30
|
+
Object.defineProperty(exports, "omitsToolTurnForInlineTableRead", { enumerable: true, get: function () { return gatedRun_1.omitsToolTurnForInlineTableRead; } });
|
|
31
|
+
Object.defineProperty(exports, "toolTurnToken", { enumerable: true, get: function () { return gatedRun_1.toolTurnToken; } });
|
|
32
|
+
const tablesImport_1 = require("./tablesImport");
|
|
33
|
+
const commandTable_1 = require("./commandTable");
|
|
26
34
|
const skillInstaller_1 = require("./skillInstaller");
|
|
27
|
-
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 };
|
|
28
35
|
const TOOLS_RUN_MODES = ['smoke_test', 'dry_run', 'live'];
|
|
36
|
+
const UUID_PATTERN = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
29
37
|
const USAGE = `Dreamstate CLI
|
|
30
38
|
|
|
31
39
|
Usage:
|
|
@@ -53,26 +61,22 @@ Usage:
|
|
|
53
61
|
dreamstate commands search <term> [--json-output|--json]
|
|
54
62
|
dreamstate tools search --goal <goal> [--terms <csv>] [--limit <n>] [--toolset <pack_id>] [--json-output|--json]
|
|
55
63
|
dreamstate tools get <capability_id> [--json-output|--json]
|
|
56
|
-
dreamstate tools run <capability_id> [--params <object>|--json <body>] [--turn-token <token>] (--live | --dry-run | --mode <smoke_test|dry_run|live>) [--
|
|
64
|
+
dreamstate tools run <capability_id> [--params <object>|--json <body>] [--turn-token <token>] (--live | --dry-run | --mode <smoke_test|dry_run|live>) [--approved] [--approval-id <uuid>] [--approved-effect-unknown] [--max-credits <n>] [--quote-hash <sha256:...>] [--idempotency-key <key>] [--wait-timeout <seconds>] [--no-wait] [--open] [--json-output|--json]
|
|
57
65
|
dreamstate sequences <verb> [args] [flags]
|
|
58
66
|
dreamstate workflows <verb> [args] [flags]
|
|
59
|
-
dreamstate tables import --table-id <uuid> --file <path> [--format <csv|json|jsonl|xlsx>] [--preview] [--upsert-key <header>] [--
|
|
67
|
+
dreamstate tables import --table-id <uuid> --file <path> [--format <csv|json|jsonl|xlsx>] [--preview] [--upsert-key <header>] [--row-kind <person|company>] [--keep-unmapped-columns <true|false>] [--json-output|--json]
|
|
60
68
|
dreamstate runs list [--limit <n>] [--cursor <cursor>] [--json <query>] [--json-output|--json]
|
|
61
69
|
dreamstate runs get <run_id> [--json-output|--json]
|
|
62
|
-
dreamstate runs resume <run_id> --state-version <n> [--reason <text>] [--idempotency-key <key>] [--open] [--json-output|--json]
|
|
63
70
|
dreamstate runs events <run_id> [--json-output|--json]
|
|
64
71
|
dreamstate runs cancel <run_id> [--state-version <n>] [--reason <text>] [--json <body>] [--idempotency-key <key>] [--json-output|--json]
|
|
65
72
|
|
|
66
73
|
All catalog discovery reads live API records. Set DREAMSTATE_API_URL and DREAMSTATE_API_KEY.
|
|
67
74
|
Run responses wait for terminal run status by default, timeout default is 120 seconds, --no-wait returns immediate response. Timeout prints latest run envelope and exits ${runWait_1.RUN_WAIT_TIMEOUT_EXIT_CODE}.
|
|
75
|
+
A write you re-run unchanged presents the same Idempotency-Key (printed as meta.idempotency_key), so the server replays instead of running it twice; --idempotency-key sets one yourself. Exit codes: 0 ok, 2 usage, 3 auth, 4 not found, 5 server failure, 6 rate limited, 7 approval/consent/spend gate required, 8 CLI update required, 9 refused, 10 transport, 11 timeout.
|
|
68
76
|
login opens browser approval and installs Dreamstate skills for detected agents; --no-skills skips installation. login --token stores a ds_user_ session or ds_live_ key (for SSH or remote machines) after verifying it with whoami.
|
|
69
77
|
Global flag --workspace <id|slug> (any command) or DREAMSTATE_WORKSPACE picks the workspace for one call; workspaces use stores a default. Flag beats env beats stored. A ds_live_ key is bound to one workspace and cannot switch.
|
|
70
78
|
logout deletes the stored credentials file. --agents takes space-separated, comma-separated or repeated values.
|
|
71
79
|
`;
|
|
72
|
-
class CliUsageError extends Error {
|
|
73
|
-
constructor(message) { super(message); this.name = 'CliUsageError'; }
|
|
74
|
-
}
|
|
75
|
-
exports.CliUsageError = CliUsageError;
|
|
76
80
|
function outputOption(values) {
|
|
77
81
|
return values['json-output'] ? { jsonOutput: true } : {};
|
|
78
82
|
}
|
|
@@ -84,35 +88,35 @@ function parseJsonObject(raw, flag = '--json') {
|
|
|
84
88
|
return value;
|
|
85
89
|
}
|
|
86
90
|
catch (error) {
|
|
87
|
-
throw new CliUsageError(`${flag} ${error instanceof Error ? error.message : 'is invalid'}`);
|
|
91
|
+
throw new errors_1.CliUsageError(`${flag} ${error instanceof Error ? error.message : 'is invalid'}`);
|
|
88
92
|
}
|
|
89
93
|
}
|
|
90
94
|
function requireOne(positionals, usage) {
|
|
91
95
|
if (positionals.length !== 2 || !positionals[1])
|
|
92
|
-
throw new CliUsageError(`Usage: ${usage}`);
|
|
96
|
+
throw new errors_1.CliUsageError(`Usage: ${usage}`);
|
|
93
97
|
return positionals[1];
|
|
94
98
|
}
|
|
95
99
|
function csv(raw, flag = '--terms') {
|
|
96
100
|
const values = raw.split(',').map((value) => value.trim()).filter(Boolean);
|
|
97
101
|
if (!values.length)
|
|
98
|
-
throw new CliUsageError(`${flag} must contain at least one value`);
|
|
102
|
+
throw new errors_1.CliUsageError(`${flag} must contain at least one value`);
|
|
99
103
|
return [...new Set(values)];
|
|
100
104
|
}
|
|
101
105
|
function requireToolsRunMode(body) {
|
|
102
106
|
if (TOOLS_RUN_MODES.includes(body.mode))
|
|
103
107
|
return;
|
|
104
|
-
throw new CliUsageError('mode must be one of: smoke_test, dry_run, live.');
|
|
108
|
+
throw new errors_1.CliUsageError('mode must be one of: smoke_test, dry_run, live.');
|
|
105
109
|
}
|
|
106
110
|
function nonNegativeNumber(raw, flag) {
|
|
107
111
|
const value = Number(raw);
|
|
108
112
|
if (!Number.isFinite(value) || value < 0)
|
|
109
|
-
throw new CliUsageError(`${flag} must be a non-negative number`);
|
|
113
|
+
throw new errors_1.CliUsageError(`${flag} must be a non-negative number`);
|
|
110
114
|
return value;
|
|
111
115
|
}
|
|
112
116
|
function nonNegativeInteger(raw, flag) {
|
|
113
117
|
const value = Number(raw);
|
|
114
118
|
if (!Number.isSafeInteger(value) || value < 0)
|
|
115
|
-
throw new CliUsageError(`${flag} must be a non-negative integer`);
|
|
119
|
+
throw new errors_1.CliUsageError(`${flag} must be a non-negative integer`);
|
|
116
120
|
return value;
|
|
117
121
|
}
|
|
118
122
|
// runs.get returns { data: { run: { state_version } } }. Cancel posts
|
|
@@ -141,7 +145,7 @@ function parsePacks(argv) {
|
|
|
141
145
|
return { command: 'packs_get', packId: requireOne(positionals, 'dreamstate packs get <pack_id>'), ...outputOption(values) };
|
|
142
146
|
if (positionals.length === 1 && positionals[0] === 'route' && values.goal?.trim())
|
|
143
147
|
return { command: 'packs_route', goal: values.goal.trim(), ...outputOption(values) };
|
|
144
|
-
throw new CliUsageError('Usage: dreamstate packs <list|get|route>');
|
|
148
|
+
throw new errors_1.CliUsageError('Usage: dreamstate packs <list|get|route>');
|
|
145
149
|
}
|
|
146
150
|
/** Rewrites `--agents a b c` into `--agents=a --agents=b --agents=c` so parseArgs sees every value. */
|
|
147
151
|
function spaceSeparatedAgents(argv) {
|
|
@@ -172,7 +176,7 @@ function parseSkills(argv) {
|
|
|
172
176
|
const requested = [...new Set(values.agents.flatMap((raw) => csv(raw, '--agents')))];
|
|
173
177
|
const invalid = requested.filter((agent) => !skillInstaller_1.SKILL_INSTALL_AGENTS.includes(agent));
|
|
174
178
|
if (invalid.length)
|
|
175
|
-
throw new CliUsageError(`--agents values must be one of: ${skillInstaller_1.SKILL_INSTALL_AGENTS.join(', ')} (got: ${invalid.join(', ')})`);
|
|
179
|
+
throw new errors_1.CliUsageError(`--agents values must be one of: ${skillInstaller_1.SKILL_INSTALL_AGENTS.join(', ')} (got: ${invalid.join(', ')})`);
|
|
176
180
|
return requested;
|
|
177
181
|
};
|
|
178
182
|
if (positionals.length === 1 && positionals[0] === 'list') {
|
|
@@ -195,14 +199,14 @@ function parseSkills(argv) {
|
|
|
195
199
|
return { command: 'skills_install', skillId: values.skill?.trim() || '*', agents: agents(), ...(values.project ? { project: true } : {}), ...(values.copy ? { copy: true } : {}), ...outputOption(values) };
|
|
196
200
|
if (positionals.length === 1 && positionals[0] === 'doctor')
|
|
197
201
|
return { command: 'skills_doctor', agents: agents(), ...(values.project ? { project: true } : {}), ...outputOption(values) };
|
|
198
|
-
throw new CliUsageError('Usage: dreamstate skills <list|get|search|install|doctor>');
|
|
202
|
+
throw new errors_1.CliUsageError('Usage: dreamstate skills <list|get|search|install|doctor>');
|
|
199
203
|
}
|
|
200
204
|
function parseCommands(argv) {
|
|
201
205
|
const { values, positionals } = (0, node_util_1.parseArgs)({ args: argv, options: { 'json-output': { type: 'boolean' }, help: { type: 'boolean', short: 'h' } }, allowPositionals: true, strict: true });
|
|
202
206
|
if (values.help)
|
|
203
207
|
return { command: 'help' };
|
|
204
208
|
if (positionals[0] !== 'search' || positionals.length < 2)
|
|
205
|
-
throw new CliUsageError('Usage: dreamstate commands search <term>');
|
|
209
|
+
throw new errors_1.CliUsageError('Usage: dreamstate commands search <term>');
|
|
206
210
|
return { command: 'commands_search', term: positionals.slice(1).join(' '), ...outputOption(values) };
|
|
207
211
|
}
|
|
208
212
|
/**
|
|
@@ -210,10 +214,8 @@ function parseCommands(argv) {
|
|
|
210
214
|
* are mutually exclusive ways of saying the same thing, and disagreeing ones are a
|
|
211
215
|
* usage error rather than a silent winner.
|
|
212
216
|
*
|
|
213
|
-
* --approved is deliberately NOT here
|
|
214
|
-
*
|
|
215
|
-
* than authorizing anything. Spend authority is max_credits and a human grant
|
|
216
|
-
* arrives as approval_id.
|
|
217
|
+
* --approved is deliberately NOT here: it is not a mode. It rides on a live run
|
|
218
|
+
* as the caller's assertion that it asked a person (see parseTools).
|
|
217
219
|
*/
|
|
218
220
|
function toolsRunMode(values) {
|
|
219
221
|
const chosen = new Set();
|
|
@@ -224,12 +226,12 @@ function toolsRunMode(values) {
|
|
|
224
226
|
if (values.mode !== undefined) {
|
|
225
227
|
const mode = values.mode.trim();
|
|
226
228
|
if (!TOOLS_RUN_MODES.includes(mode)) {
|
|
227
|
-
throw new CliUsageError(`--mode must be one of: ${TOOLS_RUN_MODES.join(', ')}`);
|
|
229
|
+
throw new errors_1.CliUsageError(`--mode must be one of: ${TOOLS_RUN_MODES.join(', ')}`);
|
|
228
230
|
}
|
|
229
231
|
chosen.add(mode);
|
|
230
232
|
}
|
|
231
233
|
if (chosen.size > 1) {
|
|
232
|
-
throw new CliUsageError(`Conflicting execution modes requested: ${[...chosen].sort().join(', ')}. Pass exactly one of --live, --dry-run, --mode.`);
|
|
234
|
+
throw new errors_1.CliUsageError(`Conflicting execution modes requested: ${[...chosen].sort().join(', ')}. Pass exactly one of --live, --dry-run, --mode.`);
|
|
233
235
|
}
|
|
234
236
|
return [...chosen][0];
|
|
235
237
|
}
|
|
@@ -239,7 +241,8 @@ function parseTools(argv) {
|
|
|
239
241
|
limit: { type: 'string' }, toolset: { type: 'string' },
|
|
240
242
|
'idempotency-key': { type: 'string' }, 'json-output': { type: 'boolean' }, open: { type: 'boolean' },
|
|
241
243
|
'dry-run': { type: 'boolean' }, live: { type: 'boolean' }, mode: { type: 'string' },
|
|
242
|
-
|
|
244
|
+
approved: { type: 'boolean' }, 'approved-effect-unknown': { type: 'boolean' },
|
|
245
|
+
'quote-hash': { type: 'string' }, 'approval-id': { type: 'string' },
|
|
243
246
|
'max-credits': { type: 'string' }, 'turn-token': { type: 'string' }, 'wait-timeout': { type: 'string' }, 'no-wait': { type: 'boolean' }, help: { type: 'boolean', short: 'h' },
|
|
244
247
|
}, allowPositionals: true, strict: true });
|
|
245
248
|
if (values.help)
|
|
@@ -256,7 +259,7 @@ function parseTools(argv) {
|
|
|
256
259
|
if (values.toolset !== undefined)
|
|
257
260
|
query.toolset = values.toolset;
|
|
258
261
|
if (typeof query.goal !== 'string' && !Array.isArray(query.query_terms))
|
|
259
|
-
throw new CliUsageError('tools search requires --goal, --terms, or --json query fields');
|
|
262
|
+
throw new errors_1.CliUsageError('tools search requires --goal, --terms, or --json query fields');
|
|
260
263
|
return { command: 'tools_search', query, ...output };
|
|
261
264
|
}
|
|
262
265
|
if (positionals[0] === 'get')
|
|
@@ -264,11 +267,11 @@ function parseTools(argv) {
|
|
|
264
267
|
if (positionals[0] === 'run') {
|
|
265
268
|
const capabilityId = requireOne(positionals, 'dreamstate tools run <capability_id> [--params <object>|--json <body>]');
|
|
266
269
|
if (values.json && values.params)
|
|
267
|
-
throw new CliUsageError('Use either --params for tool parameters or --json for the complete request, not both.');
|
|
270
|
+
throw new errors_1.CliUsageError('Use either --params for tool parameters or --json for the complete request, not both.');
|
|
268
271
|
if (!values.json && !values.params)
|
|
269
|
-
throw new CliUsageError('tools run requires --params <object> or --json <body>; fetch live schema first with tools get');
|
|
272
|
+
throw new errors_1.CliUsageError('tools run requires --params <object> or --json <body>; fetch live schema first with tools get');
|
|
270
273
|
const json = values.json ? parseJsonObject(values.json) : undefined;
|
|
271
|
-
const body = json && ('params' in json || 'mode' in json || 'agent_session_id' in json || 'approved_effect_unknown' in json || 'max_credits' in json)
|
|
274
|
+
const body = json && ('params' in json || 'mode' in json || 'agent_session_id' in json || 'approved_effect_unknown' in json || 'max_credits' in json || 'quote_hash' in json || 'approved' in json || 'approval_id' in json)
|
|
272
275
|
? json
|
|
273
276
|
: {};
|
|
274
277
|
if (values.params)
|
|
@@ -277,17 +280,37 @@ function parseTools(argv) {
|
|
|
277
280
|
body.params = json;
|
|
278
281
|
// The server never defaults a mode, so a mutating run with none is refused
|
|
279
282
|
// mode_required; --live and --mode exist so a caller can actually ask for one.
|
|
280
|
-
// `approved` is no longer sent: the run body schema is strict and has no such
|
|
281
|
-
// key, so passing it failed the whole call instead of authorizing anything.
|
|
282
283
|
const chosenMode = toolsRunMode(values);
|
|
283
284
|
if (chosenMode)
|
|
284
285
|
body.mode = chosenMode;
|
|
285
|
-
|
|
286
|
-
|
|
286
|
+
// agent_session_id selects the in-app Architect surface and the server refuses it for every
|
|
287
|
+
// other caller (an API key may name a session only while an Architect turn is running on it,
|
|
288
|
+
// which a CLI user never has). There is no flag for it, and a full --json body must not smuggle it.
|
|
289
|
+
if (body.agent_session_id !== undefined) {
|
|
290
|
+
throw new errors_1.CliUsageError('agent_session_id is not accepted by the CLI: only the in-app Architect may name an Architect session, and the server refuses it for every other caller. Remove it from the request.');
|
|
291
|
+
}
|
|
287
292
|
if (values['approved-effect-unknown'])
|
|
288
293
|
body.approved_effect_unknown = true;
|
|
289
294
|
if (values['max-credits'] !== undefined)
|
|
290
295
|
body.max_credits = nonNegativeNumber(values['max-credits'], '--max-credits');
|
|
296
|
+
if (values['quote-hash'] !== undefined) {
|
|
297
|
+
if (!/^sha256:[a-f0-9]{64}$/.test(values['quote-hash']))
|
|
298
|
+
throw new errors_1.CliUsageError('--quote-hash must be the sha256:<64 hex> quote_hash a dry run returned');
|
|
299
|
+
body.quote_hash = values['quote-hash'];
|
|
300
|
+
}
|
|
301
|
+
// --approved is the caller's assertion that it showed a person the dry run's
|
|
302
|
+
// rows and cost and they agreed. A paid run above the size or dollar
|
|
303
|
+
// threshold is refused without it. It only means something on a live run,
|
|
304
|
+
// so any other mode never sends it.
|
|
305
|
+
if (values.approved && body.mode === 'live')
|
|
306
|
+
body.approved = true;
|
|
307
|
+
// --approval-id names a recorded approval (a uuid the approvals capabilities returned)
|
|
308
|
+
// the door should honour for this run. It is a door input, never a param.
|
|
309
|
+
if (values['approval-id'] !== undefined) {
|
|
310
|
+
if (!UUID_PATTERN.test(values['approval-id']))
|
|
311
|
+
throw new errors_1.CliUsageError('--approval-id must be the uuid of a recorded approval');
|
|
312
|
+
body.approval_id = values['approval-id'];
|
|
313
|
+
}
|
|
291
314
|
// No client-side mode requirement: the server owns that refusal and answers
|
|
292
315
|
// mode_required with a repair, and the parse grammar is expected to accept a
|
|
293
316
|
// run without one. What was missing was any way to ASK for live.
|
|
@@ -296,18 +319,24 @@ function parseTools(argv) {
|
|
|
296
319
|
const waitTimeout = values['wait-timeout'] === undefined ? 120 : nonNegativeNumber(values['wait-timeout'], '--wait-timeout');
|
|
297
320
|
return { command: 'tools_run', capabilityId, body, waitTimeout, ...(values['no-wait'] ? { noWait: true } : {}), ...(values['turn-token'] ? { turnToken: values['turn-token'] } : {}), ...(values['idempotency-key'] ? { idempotencyKey: values['idempotency-key'] } : {}), ...(values.open ? { open: true } : {}), ...output };
|
|
298
321
|
}
|
|
299
|
-
throw new CliUsageError('Usage: dreamstate tools <search|get|run>');
|
|
322
|
+
throw new errors_1.CliUsageError('Usage: dreamstate tools <search|get|run>');
|
|
300
323
|
}
|
|
301
324
|
function parseTables(argv) {
|
|
325
|
+
// Servers before this release put --background on the import command they hand out. It
|
|
326
|
+
// never did anything (the import always ran to completion in this process), so 1.3.3
|
|
327
|
+
// dropped it; say so plainly instead of the generic "unknown option" error.
|
|
328
|
+
if (argv.includes('--background')) {
|
|
329
|
+
throw new errors_1.CliUsageError('--background was removed in 1.3.3: dreamstate tables import always runs to completion in this process. Run the same command without it.');
|
|
330
|
+
}
|
|
302
331
|
const { values, positionals } = (0, node_util_1.parseArgs)({ args: argv, options: {
|
|
303
|
-
'table-id': { type: 'string' }, file: { type: 'string' }, format: { type: 'string' },
|
|
332
|
+
'table-id': { type: 'string' }, file: { type: 'string' }, format: { type: 'string' },
|
|
304
333
|
preview: { type: 'boolean' }, 'upsert-key': { type: 'string' }, 'row-kind': { type: 'string' }, 'keep-unmapped-columns': { type: 'string' },
|
|
305
334
|
'json-output': { type: 'boolean' }, help: { type: 'boolean', short: 'h' },
|
|
306
335
|
}, allowPositionals: true, strict: true });
|
|
307
336
|
if (values.help)
|
|
308
337
|
return { command: 'help' };
|
|
309
338
|
if (positionals.length !== 1 || positionals[0] !== 'import' || !values['table-id'] || !values.file) {
|
|
310
|
-
throw new CliUsageError('Usage: dreamstate tables import --table-id <uuid> --file <path> [--format <csv|json|jsonl|xlsx>] [--preview] [--upsert-key <header>] [--row-kind <person|company>] [--keep-unmapped-columns <true|false>]
|
|
339
|
+
throw new errors_1.CliUsageError('Usage: dreamstate tables import --table-id <uuid> --file <path> [--format <csv|json|jsonl|xlsx>] [--preview] [--upsert-key <header>] [--row-kind <person|company>] [--keep-unmapped-columns <true|false>]');
|
|
311
340
|
}
|
|
312
341
|
// --format is an override, not a requirement. Requiring it made the caller restate
|
|
313
342
|
// what the file path already says, and an omitted flag produced a usage error rather
|
|
@@ -315,16 +344,16 @@ function parseTables(argv) {
|
|
|
315
344
|
// the kind from the file it receives.
|
|
316
345
|
const format = values.format;
|
|
317
346
|
if (format !== undefined && !['csv', 'json', 'jsonl', 'xlsx'].includes(format)) {
|
|
318
|
-
throw new CliUsageError('--format must be one of csv, json, jsonl, xlsx');
|
|
347
|
+
throw new errors_1.CliUsageError('--format must be one of csv, json, jsonl, xlsx');
|
|
319
348
|
}
|
|
320
349
|
const upsertKey = values['upsert-key']?.trim();
|
|
321
350
|
const rowKind = values['row-kind'];
|
|
322
351
|
if (rowKind !== undefined && rowKind !== 'person' && rowKind !== 'company') {
|
|
323
|
-
throw new CliUsageError('--row-kind must be person or company');
|
|
352
|
+
throw new errors_1.CliUsageError('--row-kind must be person or company');
|
|
324
353
|
}
|
|
325
354
|
const keepUnmappedColumns = values['keep-unmapped-columns'];
|
|
326
355
|
if (keepUnmappedColumns !== undefined && keepUnmappedColumns !== 'true' && keepUnmappedColumns !== 'false') {
|
|
327
|
-
throw new CliUsageError('--keep-unmapped-columns must be true or false');
|
|
356
|
+
throw new errors_1.CliUsageError('--keep-unmapped-columns must be true or false');
|
|
328
357
|
}
|
|
329
358
|
return {
|
|
330
359
|
command: 'tables_import',
|
|
@@ -335,14 +364,13 @@ function parseTables(argv) {
|
|
|
335
364
|
...(upsertKey ? { upsertKey } : {}),
|
|
336
365
|
...(rowKind ? { rowKind } : {}),
|
|
337
366
|
...(keepUnmappedColumns === 'false' ? { keepUnmappedColumns: false } : {}),
|
|
338
|
-
background: Boolean(values.background),
|
|
339
367
|
...outputOption(values),
|
|
340
368
|
};
|
|
341
369
|
}
|
|
342
370
|
function runsListLimit(raw) {
|
|
343
371
|
const value = nonNegativeInteger(raw, '--limit');
|
|
344
372
|
if (value < 1 || value > 500)
|
|
345
|
-
throw new CliUsageError('--limit must be an integer from 1 to 500');
|
|
373
|
+
throw new errors_1.CliUsageError('--limit must be an integer from 1 to 500');
|
|
346
374
|
return value;
|
|
347
375
|
}
|
|
348
376
|
function parseRuns(argv) {
|
|
@@ -357,30 +385,28 @@ function parseRuns(argv) {
|
|
|
357
385
|
if (values.cursor !== undefined) {
|
|
358
386
|
const cursor = values.cursor.trim();
|
|
359
387
|
if (!cursor)
|
|
360
|
-
throw new CliUsageError('--cursor must be a non-empty string');
|
|
388
|
+
throw new errors_1.CliUsageError('--cursor must be a non-empty string');
|
|
361
389
|
query.cursor = cursor;
|
|
362
390
|
}
|
|
363
391
|
return { command: 'runs_list', query, ...output };
|
|
364
392
|
}
|
|
365
393
|
if (values.limit !== undefined || values.cursor !== undefined) {
|
|
366
|
-
throw new CliUsageError('--limit and --cursor are only valid on runs list');
|
|
394
|
+
throw new errors_1.CliUsageError('--limit and --cursor are only valid on runs list');
|
|
395
|
+
}
|
|
396
|
+
if (positionals[0] === 'resume') {
|
|
397
|
+
// The server retired generic run resume (verifyExecutionDoorCollapse.ts blocks the
|
|
398
|
+
// route), so there is nothing to call. Say where the replacement is.
|
|
399
|
+
throw new errors_1.CliUsageError('runs resume was retired: the server has no generic run resume. A table run checkpoint resumes with: dreamstate tools run tables.table_runs.resume --params \'{...}\' --live. Start a new run for anything else.');
|
|
367
400
|
}
|
|
368
401
|
const runId = requireOne(positionals, `dreamstate runs ${positionals[0] ?? '<get|events|cancel>'} <run_id>`);
|
|
369
402
|
if (positionals[0] === 'get')
|
|
370
403
|
return { command: 'runs_get', runId, ...output };
|
|
371
404
|
if (positionals[0] === 'events')
|
|
372
405
|
return { command: 'runs_events', runId, ...output };
|
|
373
|
-
if (positionals[0] === 'resume') {
|
|
374
|
-
if (values.json)
|
|
375
|
-
throw new CliUsageError('Use --state-version and --reason with runs resume, not --json.');
|
|
376
|
-
if (values['state-version'] === undefined)
|
|
377
|
-
throw new CliUsageError('--state-version is required for runs resume.');
|
|
378
|
-
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 };
|
|
379
|
-
}
|
|
380
406
|
if (positionals[0] === 'cancel') {
|
|
381
407
|
const idempotency = values['idempotency-key'] ? { idempotencyKey: values['idempotency-key'] } : {};
|
|
382
408
|
if (values.json && (values['state-version'] !== undefined || values.reason)) {
|
|
383
|
-
throw new CliUsageError('Use either --json <body> or --state-version and --reason with runs cancel, not both.');
|
|
409
|
+
throw new errors_1.CliUsageError('Use either --json <body> or --state-version and --reason with runs cancel, not both.');
|
|
384
410
|
}
|
|
385
411
|
if (values.json)
|
|
386
412
|
return { command: 'runs_cancel', runId, body: parseJsonObject(values.json), ...idempotency, ...output };
|
|
@@ -393,7 +419,7 @@ function parseRuns(argv) {
|
|
|
393
419
|
...output,
|
|
394
420
|
};
|
|
395
421
|
}
|
|
396
|
-
throw new CliUsageError('Usage: dreamstate runs <get|events|cancel> <run_id>');
|
|
422
|
+
throw new errors_1.CliUsageError('Usage: dreamstate runs <list|get|events|cancel> <run_id>');
|
|
397
423
|
}
|
|
398
424
|
function normalizeJsonOutputAlias(argv) {
|
|
399
425
|
return argv.map((argument, index) => argument === '--json' && (index === argv.length - 1 || argv[index + 1]?.startsWith('-'))
|
|
@@ -412,9 +438,9 @@ function parseCliArgs(argv) {
|
|
|
412
438
|
if (values.help)
|
|
413
439
|
return { command: 'help' };
|
|
414
440
|
if (positionals.length)
|
|
415
|
-
throw new CliUsageError('Usage: dreamstate login [--token <ds_user_...|ds_live_...>]');
|
|
441
|
+
throw new errors_1.CliUsageError('Usage: dreamstate login [--token <ds_user_...|ds_live_...>]');
|
|
416
442
|
if (values.token !== undefined && values['api-key-file'])
|
|
417
|
-
throw new CliUsageError('Use either --token or --api-key-file, not both.');
|
|
443
|
+
throw new errors_1.CliUsageError('Use either --token or --api-key-file, not both.');
|
|
418
444
|
return { command: 'login', ...(values.token !== undefined ? { token: values.token } : {}), ...(values['api-key-file'] ? { apiKeyFile: values['api-key-file'] } : {}), ...(values.profile ? { profile: (0, config_1.validateProfileName)(values.profile) } : {}), ...(values['no-skills'] ? { noSkills: true } : {}), ...outputOption(values) };
|
|
419
445
|
}
|
|
420
446
|
if (group === 'install') {
|
|
@@ -422,11 +448,11 @@ function parseCliArgs(argv) {
|
|
|
422
448
|
if (values.help)
|
|
423
449
|
return { command: 'help' };
|
|
424
450
|
if (positionals.length)
|
|
425
|
-
throw new CliUsageError('Usage: dreamstate install [--agents <claude codex grok>]');
|
|
451
|
+
throw new errors_1.CliUsageError('Usage: dreamstate install [--agents <claude codex grok>]');
|
|
426
452
|
const requested = values.agents ? [...new Set(values.agents.flatMap((raw) => csv(raw, '--agents')))] : [...pluginInstaller_1.PLUGIN_AGENTS];
|
|
427
453
|
const invalid = requested.filter((agent) => !pluginInstaller_1.PLUGIN_AGENTS.includes(agent));
|
|
428
454
|
if (invalid.length)
|
|
429
|
-
throw new CliUsageError(`--agents values must be one of: ${pluginInstaller_1.PLUGIN_AGENTS.join(', ')} (got: ${invalid.join(', ')})`);
|
|
455
|
+
throw new errors_1.CliUsageError(`--agents values must be one of: ${pluginInstaller_1.PLUGIN_AGENTS.join(', ')} (got: ${invalid.join(', ')})`);
|
|
430
456
|
return { command: 'install', agents: requested, ...outputOption(values) };
|
|
431
457
|
}
|
|
432
458
|
if (group === 'logout') {
|
|
@@ -434,14 +460,14 @@ function parseCliArgs(argv) {
|
|
|
434
460
|
if (values.help)
|
|
435
461
|
return { command: 'help' };
|
|
436
462
|
if (positionals.length)
|
|
437
|
-
throw new CliUsageError('Usage: dreamstate logout');
|
|
463
|
+
throw new errors_1.CliUsageError('Usage: dreamstate logout');
|
|
438
464
|
return { command: 'logout', ...outputOption(values) };
|
|
439
465
|
}
|
|
440
466
|
if (group === 'status') {
|
|
441
467
|
const { values, positionals } = (0, node_util_1.parseArgs)({ args: rest, options: { 'json-output': { type: 'boolean' } }, allowPositionals: true, strict: true });
|
|
442
468
|
if (!positionals.length)
|
|
443
469
|
return { command: 'status', ...outputOption(values) };
|
|
444
|
-
throw new CliUsageError('Usage: dreamstate status [--json-output]');
|
|
470
|
+
throw new errors_1.CliUsageError('Usage: dreamstate status [--json-output]');
|
|
445
471
|
}
|
|
446
472
|
if (group === 'profiles') {
|
|
447
473
|
const { values, positionals } = (0, node_util_1.parseArgs)({ args: rest, options: { 'json-output': { type: 'boolean' } }, allowPositionals: true, strict: true });
|
|
@@ -456,13 +482,13 @@ function parseCliArgs(argv) {
|
|
|
456
482
|
return { command: 'profiles_remove', name: (0, config_1.validateProfileName)(positionals[1]), ...output };
|
|
457
483
|
if (positionals.length === 2 && positionals[0] === 'env')
|
|
458
484
|
return { command: 'profiles_env', name: (0, config_1.validateProfileName)(positionals[1]), ...output };
|
|
459
|
-
throw new CliUsageError('Usage: dreamstate profiles <list|use <name>|current|remove <name>|env <name>>');
|
|
485
|
+
throw new errors_1.CliUsageError('Usage: dreamstate profiles <list|use <name>|current|remove <name>|env <name>>');
|
|
460
486
|
}
|
|
461
487
|
if (group === 'whoami') {
|
|
462
488
|
const { values, positionals } = (0, node_util_1.parseArgs)({ args: rest, options: { 'json-output': { type: 'boolean' } }, allowPositionals: true, strict: true });
|
|
463
489
|
if (positionals.length === 0)
|
|
464
490
|
return { command: 'whoami', ...outputOption(values) };
|
|
465
|
-
throw new CliUsageError('Usage: dreamstate whoami [--json-output]');
|
|
491
|
+
throw new errors_1.CliUsageError('Usage: dreamstate whoami [--json-output]');
|
|
466
492
|
}
|
|
467
493
|
if (group === 'workspaces') {
|
|
468
494
|
const { values, positionals } = (0, node_util_1.parseArgs)({ args: rest, options: { 'json-output': { type: 'boolean' }, help: { type: 'boolean', short: 'h' } }, allowPositionals: true, strict: true });
|
|
@@ -472,7 +498,7 @@ function parseCliArgs(argv) {
|
|
|
472
498
|
return { command: 'workspaces_list', ...outputOption(values) };
|
|
473
499
|
if (positionals.length === 2 && positionals[0] === 'use' && positionals[1])
|
|
474
500
|
return { command: 'workspaces_use', workspace: positionals[1], ...outputOption(values) };
|
|
475
|
-
throw new CliUsageError('Usage: dreamstate workspaces <list|use <id|slug>>');
|
|
501
|
+
throw new errors_1.CliUsageError('Usage: dreamstate workspaces <list|use <id|slug>>');
|
|
476
502
|
}
|
|
477
503
|
if (group === 'auth') {
|
|
478
504
|
const { values, positionals } = (0, node_util_1.parseArgs)({ args: rest, options: { 'api-key-file': { type: 'string' }, 'no-skills': { type: 'boolean' }, 'json-output': { type: 'boolean' } }, allowPositionals: true, strict: true });
|
|
@@ -480,7 +506,7 @@ function parseCliArgs(argv) {
|
|
|
480
506
|
return { command: 'login', ...(values['api-key-file'] ? { apiKeyFile: values['api-key-file'] } : {}), ...(values['no-skills'] ? { noSkills: true } : {}), ...outputOption(values) };
|
|
481
507
|
if (positionals.length === 1 && positionals[0] === 'whoami')
|
|
482
508
|
return { command: 'whoami', ...outputOption(values) };
|
|
483
|
-
throw new CliUsageError('Usage: dreamstate auth <login|whoami>');
|
|
509
|
+
throw new errors_1.CliUsageError('Usage: dreamstate auth <login|whoami>');
|
|
484
510
|
}
|
|
485
511
|
if (group === 'packs')
|
|
486
512
|
return parsePacks(rest);
|
|
@@ -498,10 +524,14 @@ function parseCliArgs(argv) {
|
|
|
498
524
|
return require('./commands/sequences').parseSequencesCommand(rest);
|
|
499
525
|
if (group === 'workflows')
|
|
500
526
|
return require('./commands/workflows').parseWorkflowsCommand(rest);
|
|
501
|
-
throw new CliUsageError(`Unknown command: ${group}`);
|
|
527
|
+
throw new errors_1.CliUsageError(`Unknown command: ${group}`);
|
|
502
528
|
}
|
|
503
529
|
function print(value, stdout, compact) {
|
|
504
|
-
|
|
530
|
+
// Printed exactly as the server returned it. tool_turn_token used to be stripped
|
|
531
|
+
// here, which made --turn-token impossible to use: a caller could never read the
|
|
532
|
+
// token from `tools get` to pass back. The token is a short-lived fetch fence tied
|
|
533
|
+
// to the caller's own session, not a credential, and MCP and REST return it too.
|
|
534
|
+
stdout.write(`${JSON.stringify(value, null, compact ? 0 : 2)}\n`);
|
|
505
535
|
}
|
|
506
536
|
function printPackRoute(value, stdout) {
|
|
507
537
|
const data = value && typeof value === 'object' && !Array.isArray(value)
|
|
@@ -530,103 +560,32 @@ function printPackRoute(value, stdout) {
|
|
|
530
560
|
stdout.write(`${packId}${typeof reason === 'string' ? `: ${reason}` : ''}\n`);
|
|
531
561
|
}
|
|
532
562
|
}
|
|
533
|
-
function toolTurnToken(value) {
|
|
534
|
-
const root = value && typeof value === 'object' ? value : {};
|
|
535
|
-
const data = root.data && typeof root.data === 'object' ? root.data : {};
|
|
536
|
-
if (typeof data.tool_turn_token === 'string' && data.tool_turn_token.trim())
|
|
537
|
-
return data.tool_turn_token;
|
|
538
|
-
throw new client_1.CliError('tools get did not return a tool_turn_token.', 'invalid_tool_turn_token', 0);
|
|
539
|
-
}
|
|
540
|
-
/**
|
|
541
|
-
* Free sync reads. The server runs these on the inline read path when
|
|
542
|
-
* the body has no tool_turn_token and the request has no Idempotency-Key
|
|
543
|
-
* (isInlineReadEligible). A tools get before the run is a second HTTP the
|
|
544
|
-
* read does not need. Measured 2026-10-04: tables.workbooks.get full live CLI
|
|
545
|
-
* 0.726s against whoami 0.455s, about 0.27s of tools get per harness read-back.
|
|
546
|
-
* knowledge.brain.context.get is the same class (catalog: credit_class free,
|
|
547
|
-
* async_policy.mode sync, mutates not set). Live 2026-10-05 a standalone
|
|
548
|
-
* tools get of that id was 0.400s including node startup, next to whoami
|
|
549
|
-
* 0.422s, so the same-process skip is one HTTP, not a second process.
|
|
550
|
-
* The set name is historical. Writes are not in this set: they stay fetch-first.
|
|
551
|
-
*/
|
|
552
|
-
const INLINE_READ_CAPABILITIES = new Set([
|
|
553
|
-
'tables.workbooks.get',
|
|
554
|
-
'tables.workbooks.overview',
|
|
555
|
-
'tables.workbooks.list',
|
|
556
|
-
'tables.columns.list',
|
|
557
|
-
'tables.columns.stats',
|
|
558
|
-
'tables.rows.query',
|
|
559
|
-
'tables.rows.get',
|
|
560
|
-
'tables.rows.count',
|
|
561
|
-
'knowledge.brain.context.get',
|
|
562
|
-
// Both are stored, free, synchronous inbox reads. Keeping either behind a
|
|
563
|
-
// tools_get turns one list or thread open into two HTTP requests, while the
|
|
564
|
-
// canonical inline-read gate already validates their read-only contract.
|
|
565
|
-
'unibox.inbox.unified_list',
|
|
566
|
-
'unibox.inbox.conversation_get',
|
|
567
|
-
]);
|
|
568
|
-
function omitsToolTurnForInlineTableRead(capabilityId) {
|
|
569
|
-
return INLINE_READ_CAPABILITIES.has(capabilityId);
|
|
570
|
-
}
|
|
571
|
-
function readIdempotencyContract(fetched) {
|
|
572
|
-
if (!fetched || typeof fetched !== 'object' || Array.isArray(fetched))
|
|
573
|
-
return null;
|
|
574
|
-
const root = fetched;
|
|
575
|
-
const data = root.data && typeof root.data === 'object' && !Array.isArray(root.data)
|
|
576
|
-
? root.data
|
|
577
|
-
: null;
|
|
578
|
-
const candidates = [data?.capability, root.capability, data, root];
|
|
579
|
-
for (const candidate of candidates) {
|
|
580
|
-
if (!candidate || typeof candidate !== 'object' || Array.isArray(candidate))
|
|
581
|
-
continue;
|
|
582
|
-
if ('mutates' in candidate || 'idempotency_required' in candidate) {
|
|
583
|
-
return candidate;
|
|
584
|
-
}
|
|
585
|
-
}
|
|
586
|
-
return null;
|
|
587
|
-
}
|
|
588
563
|
/**
|
|
589
|
-
*
|
|
590
|
-
*
|
|
591
|
-
*
|
|
592
|
-
*
|
|
593
|
-
*
|
|
564
|
+
* The refusal envelope, in the shared shape { ok:false, error, meta }. The server's
|
|
565
|
+
* own message, retry decision, repair, details and meta (trace_id, server_version,
|
|
566
|
+
* minimum_cli_version, release_sha) are forwarded unchanged; retry_after and
|
|
567
|
+
* idempotency_key are request receipts, so they stay in meta. Process exit status
|
|
568
|
+
* remains process-level state: ToolEnvelopeMeta intentionally has no exit_code.
|
|
594
569
|
*/
|
|
595
|
-
function capabilityIdempotencyKey(fetched, explicit) {
|
|
596
|
-
if (typeof explicit === 'string' && explicit.length > 0)
|
|
597
|
-
return explicit;
|
|
598
|
-
const contract = readIdempotencyContract(fetched);
|
|
599
|
-
if (contract?.mutates === true || contract?.idempotency_required === true)
|
|
600
|
-
return (0, node_crypto_1.randomUUID)();
|
|
601
|
-
return null;
|
|
602
|
-
}
|
|
603
|
-
function parameterIdempotencyKey(body) {
|
|
604
|
-
const params = body.params;
|
|
605
|
-
if (!params || typeof params !== 'object' || Array.isArray(params))
|
|
606
|
-
return undefined;
|
|
607
|
-
const key = params.idempotency_key;
|
|
608
|
-
return typeof key === 'string' && key.length > 0 ? key : undefined;
|
|
609
|
-
}
|
|
610
|
-
function code(error) {
|
|
611
|
-
return error && typeof error === 'object' && typeof error.code === 'string'
|
|
612
|
-
? error.code
|
|
613
|
-
: undefined;
|
|
614
|
-
}
|
|
615
570
|
function refusalEnvelope(error, exitCode) {
|
|
616
571
|
const cliError = error instanceof client_1.CliError ? error : null;
|
|
617
572
|
return {
|
|
618
573
|
ok: false,
|
|
619
|
-
executed: false,
|
|
620
574
|
error: {
|
|
621
|
-
code: code(error) ?? 'invalid_request',
|
|
575
|
+
code: (0, gatedRun_1.code)(error) ?? 'invalid_request',
|
|
622
576
|
message: error instanceof Error ? error.message : String(error),
|
|
623
|
-
...(cliError?.
|
|
577
|
+
...(cliError?.retryable === undefined ? {} : { retryable: cliError.retryable }),
|
|
624
578
|
// The client keeps the server's retry decision and repair; dropping them
|
|
625
579
|
// here left CLI automation without the recovery Architect and MCP get.
|
|
626
|
-
...(cliError?.retryable === undefined ? {} : { retryable: cliError.retryable }),
|
|
627
580
|
...(cliError?.repair === undefined ? {} : { repair: cliError.repair }),
|
|
581
|
+
...(cliError?.details === undefined ? {} : { details: cliError.details }),
|
|
582
|
+
},
|
|
583
|
+
meta: {
|
|
584
|
+
...(cliError?.meta ?? {}),
|
|
585
|
+
...(cliError?.traceId !== undefined && cliError.meta?.trace_id === undefined ? { trace_id: cliError.traceId } : {}),
|
|
586
|
+
...(cliError?.retryAfter !== undefined ? { retry_after: cliError.retryAfter } : {}),
|
|
587
|
+
...(cliError?.idempotencyKey !== undefined ? { idempotency_key: cliError.idempotencyKey } : {}),
|
|
628
588
|
},
|
|
629
|
-
meta: { exit_code: exitCode },
|
|
630
589
|
};
|
|
631
590
|
}
|
|
632
591
|
function openWebUrl(rawUrl, launch = node_child_process_1.spawn) {
|
|
@@ -687,7 +646,7 @@ function discoveredCapabilityId(value) {
|
|
|
687
646
|
.filter((candidate) => typeof candidate === 'string' && candidate.length > 0);
|
|
688
647
|
const ids = [...new Set(candidates)];
|
|
689
648
|
if (ids.length !== 1)
|
|
690
|
-
throw new CliUsageError('Local file import discovery must return exactly one capability.');
|
|
649
|
+
throw new errors_1.CliUsageError('Local file import discovery must return exactly one capability.');
|
|
691
650
|
return ids[0];
|
|
692
651
|
}
|
|
693
652
|
function hostedSkill(value) {
|
|
@@ -705,31 +664,83 @@ function hostedSkill(value) {
|
|
|
705
664
|
const documents = Array.isArray(entry.documents)
|
|
706
665
|
? entry.documents.filter((document) => Boolean(document) && typeof document === 'object' && typeof document.name === 'string' && typeof document.content === 'string')
|
|
707
666
|
: undefined;
|
|
708
|
-
|
|
667
|
+
// The server lists a skill's supporting pages beside the skill (data.files), each read
|
|
668
|
+
// with `skills get <id> --file <path>`; they are not inline in the skill record.
|
|
669
|
+
const files = Array.isArray(data.files)
|
|
670
|
+
? data.files
|
|
671
|
+
.filter((file) => Boolean(file) && typeof file === 'object' && typeof file.path === 'string')
|
|
672
|
+
.map((file) => ({ path: file.path, ...(typeof file.revision_number === 'number' ? { revision_number: file.revision_number } : {}) }))
|
|
673
|
+
: undefined;
|
|
674
|
+
return {
|
|
675
|
+
id: entry.id, name: entry.name, description: entry.description, body: entry.body, frontmatter, documents,
|
|
676
|
+
...(typeof entry.revision_number === 'number' ? { revision_number: entry.revision_number } : {}),
|
|
677
|
+
...(files ? { files } : {}),
|
|
678
|
+
};
|
|
679
|
+
}
|
|
680
|
+
const SAFE_SKILL_ID = /^[a-z0-9][a-z0-9_-]*$/i;
|
|
681
|
+
/**
|
|
682
|
+
* One hosted skill with its supporting pages read in. Reads are one at a time because
|
|
683
|
+
* the workspace's private vault admits two reads at once, and fifteen parallel reads
|
|
684
|
+
* failed in production.
|
|
685
|
+
*/
|
|
686
|
+
async function loadHostedSkill(client, id, withPages) {
|
|
687
|
+
if (!SAFE_SKILL_ID.test(id))
|
|
688
|
+
throw new errors_1.CliUsageError('Dreamstate API returned an invalid skill id.');
|
|
689
|
+
const skill = hostedSkill(await client.getSkill(id));
|
|
690
|
+
if (!SAFE_SKILL_ID.test(skill.id))
|
|
691
|
+
throw new errors_1.CliUsageError('Dreamstate API returned an invalid skill id.');
|
|
692
|
+
if (skill.id !== id)
|
|
693
|
+
throw new client_1.CliError('Dreamstate API returned a mismatched skill id.', 'invalid_response', 200);
|
|
694
|
+
if (!withPages || !skill.files?.length)
|
|
695
|
+
return skill;
|
|
696
|
+
const documents = [];
|
|
697
|
+
for (const file of skill.files) {
|
|
698
|
+
const page = hostedSkill(await client.getSkill(id, { file: file.path }));
|
|
699
|
+
documents.push({ name: file.path, content: page.body });
|
|
700
|
+
}
|
|
701
|
+
return { ...skill, documents };
|
|
702
|
+
}
|
|
703
|
+
/** Ids of every skill the server lists. */
|
|
704
|
+
async function listedSkillIds(client) {
|
|
705
|
+
const listed = await client.listAllSkills();
|
|
706
|
+
const entries = listed && typeof listed === 'object' && listed.data?.skills;
|
|
707
|
+
const ids = Array.isArray(entries) ? entries.map((skill) => skill && typeof skill === 'object' ? skill.id : undefined).filter((id) => typeof id === 'string') : [];
|
|
708
|
+
if (ids.some((id) => !SAFE_SKILL_ID.test(id)))
|
|
709
|
+
throw new errors_1.CliUsageError('Dreamstate API returned an invalid skill id.');
|
|
710
|
+
return ids;
|
|
709
711
|
}
|
|
710
712
|
function exitCodeForError(error) {
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
713
|
+
const errorCode = (0, gatedRun_1.code)(error);
|
|
714
|
+
if (errorCode?.startsWith('ERR_PARSE_ARGS'))
|
|
715
|
+
return errors_1.CLI_EXIT.USAGE;
|
|
716
|
+
if (error instanceof errors_1.CliUsageError || error instanceof config_1.ApiKeyFileError || error instanceof config_1.ApiBaseUrlError || error instanceof config_1.MissingApiUrlError)
|
|
717
|
+
return errors_1.CLI_EXIT.USAGE;
|
|
715
718
|
if (error instanceof config_1.MissingApiKeyError)
|
|
716
|
-
return
|
|
717
|
-
|
|
718
|
-
|
|
719
|
+
return errors_1.CLI_EXIT.AUTH;
|
|
720
|
+
// By code before by status: a gate refusal arrives as 403 or 409 and must still read as
|
|
721
|
+
// "a person has to decide", and an update-required refusal is 426.
|
|
722
|
+
if (errorCode === 'cli_update_required')
|
|
723
|
+
return errors_1.CLI_EXIT.UPDATE_REQUIRED;
|
|
724
|
+
if ((0, errors_1.isApprovalGateCode)(errorCode))
|
|
725
|
+
return errors_1.CLI_EXIT.APPROVAL;
|
|
726
|
+
if (errorCode === 'request_timeout')
|
|
727
|
+
return errors_1.CLI_EXIT.TIMEOUT;
|
|
728
|
+
if ((0, errors_1.isLocalRefusalCode)(errorCode))
|
|
729
|
+
return errors_1.CLI_EXIT.REFUSED;
|
|
719
730
|
const status = error instanceof client_1.CliError ? error.status : 0;
|
|
720
731
|
if (status === 401 || status === 403)
|
|
721
|
-
return
|
|
732
|
+
return errors_1.CLI_EXIT.AUTH;
|
|
722
733
|
if (status === 404)
|
|
723
|
-
return
|
|
734
|
+
return errors_1.CLI_EXIT.NOT_FOUND;
|
|
724
735
|
if (status === 429)
|
|
725
|
-
return
|
|
726
|
-
if (
|
|
727
|
-
return
|
|
728
|
-
if (status >= 500 ||
|
|
729
|
-
return
|
|
736
|
+
return errors_1.CLI_EXIT.RATE_LIMITED;
|
|
737
|
+
if (errorCode === 'network_error')
|
|
738
|
+
return errors_1.CLI_EXIT.TRANSPORT;
|
|
739
|
+
if (status >= 500 || errorCode === 'invalid_response')
|
|
740
|
+
return errors_1.CLI_EXIT.SERVER;
|
|
730
741
|
if (status >= 400)
|
|
731
|
-
return
|
|
732
|
-
return
|
|
742
|
+
return errors_1.CLI_EXIT.REFUSED;
|
|
743
|
+
return errors_1.CLI_EXIT.UNCLASSIFIED;
|
|
733
744
|
}
|
|
734
745
|
async function readLoginKey(command, runtime) {
|
|
735
746
|
if (command.apiKeyFile)
|
|
@@ -747,7 +758,7 @@ async function readLoginKey(command, runtime) {
|
|
|
747
758
|
}
|
|
748
759
|
if (runtime.promptHidden)
|
|
749
760
|
return runtime.promptHidden('API key (input hidden): ');
|
|
750
|
-
throw new CliUsageError('No API key provided. Pipe it on stdin, set DREAMSTATE_API_KEY, or pass --api-key-file.');
|
|
761
|
+
throw new errors_1.CliUsageError('No API key provided. Pipe it on stdin, set DREAMSTATE_API_KEY, or pass --api-key-file.');
|
|
751
762
|
}
|
|
752
763
|
function deviceState() {
|
|
753
764
|
return (0, node_crypto_1.randomBytes)(32).toString('base64url');
|
|
@@ -767,14 +778,14 @@ function extractWorkspaceFlag(argv) {
|
|
|
767
778
|
if (argument === '--workspace') {
|
|
768
779
|
const value = argv[index + 1];
|
|
769
780
|
if (value === undefined || value.startsWith('-') || !value.trim())
|
|
770
|
-
throw new CliUsageError('--workspace requires a workspace id or slug.');
|
|
781
|
+
throw new errors_1.CliUsageError('--workspace requires a workspace id or slug.');
|
|
771
782
|
workspace = value.trim();
|
|
772
783
|
index += 1;
|
|
773
784
|
}
|
|
774
785
|
else if (argument.startsWith('--workspace=')) {
|
|
775
786
|
workspace = argument.slice('--workspace='.length).trim();
|
|
776
787
|
if (!workspace)
|
|
777
|
-
throw new CliUsageError('--workspace requires a workspace id or slug.');
|
|
788
|
+
throw new errors_1.CliUsageError('--workspace requires a workspace id or slug.');
|
|
778
789
|
}
|
|
779
790
|
else {
|
|
780
791
|
rest.push(argument);
|
|
@@ -786,25 +797,29 @@ function whoamiData(value) {
|
|
|
786
797
|
const data = value && typeof value === 'object' ? value.data : undefined;
|
|
787
798
|
return data && typeof data === 'object' ? data : {};
|
|
788
799
|
}
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
800
|
+
/**
|
|
801
|
+
* What `dreamstate status` reports. The release identity comes from the server's
|
|
802
|
+
* own answers (envelope meta and X-Dreamstate-* headers on every response), so
|
|
803
|
+
* skew is measured, not assumed: with a server that publishes nothing the skew is
|
|
804
|
+
* "unknown", never "compatible".
|
|
805
|
+
*/
|
|
806
|
+
function statusData(whoami, health, info) {
|
|
793
807
|
const healthData = health && typeof health === 'object' ? health.data : null;
|
|
794
808
|
const healthVersion = healthData && typeof healthData === 'object' ? healthData.version : null;
|
|
795
|
-
const
|
|
796
|
-
const
|
|
797
|
-
const
|
|
798
|
-
const
|
|
799
|
-
const [cliMajor = 0, cliMinor = 0, cliPatch = 0] = parts(client_1.CLI_VERSION);
|
|
800
|
-
const compatible = !minimum || minimumMajor < cliMajor || (minimumMajor === cliMajor && (minimumMinor < cliMinor || (minimumMinor === cliMinor && minimumPatch <= cliPatch)));
|
|
809
|
+
const serverVersion = info.server_version ?? (typeof healthVersion === 'string' ? healthVersion : null);
|
|
810
|
+
const resolved = { ...info, server_version: serverVersion };
|
|
811
|
+
const skew = (0, client_1.describeVersionSkew)(resolved);
|
|
812
|
+
const compatible = skew !== 'cli_below_minimum';
|
|
801
813
|
return {
|
|
802
814
|
cli_version: client_1.CLI_VERSION,
|
|
803
|
-
api_version:
|
|
804
|
-
|
|
805
|
-
|
|
815
|
+
api_version: serverVersion,
|
|
816
|
+
server_version: serverVersion,
|
|
817
|
+
release_sha: info.release_sha,
|
|
818
|
+
minimum_cli_version: info.minimum_cli_version,
|
|
819
|
+
minimum_cli_version_published: info.minimum_cli_version !== null,
|
|
820
|
+
skew,
|
|
806
821
|
compatible,
|
|
807
|
-
upgrade_hint:
|
|
822
|
+
upgrade_hint: skew === 'cli_below_minimum' || skew === 'cli_behind_server' ? `Run: ${client_1.CLI_UPDATE_COMMAND}` : null,
|
|
808
823
|
health,
|
|
809
824
|
whoami: whoamiData(whoami),
|
|
810
825
|
};
|
|
@@ -816,36 +831,31 @@ function activeWorkspace(data) {
|
|
|
816
831
|
function wait(milliseconds) {
|
|
817
832
|
return new Promise((resolve) => setTimeout(resolve, milliseconds));
|
|
818
833
|
}
|
|
834
|
+
/**
|
|
835
|
+
* The one way the CLI installs skills: every skill from the hosted catalog (the same
|
|
836
|
+
* records MCP skills_get and `skills get` read), with its supporting pages, plus the
|
|
837
|
+
* router. `skills install`, `login` and the single-skill form all come through here, so
|
|
838
|
+
* there is no second source of skill text on this machine.
|
|
839
|
+
*/
|
|
819
840
|
async function installHostedSkills(client, agents, runtime, options = {}) {
|
|
820
|
-
const
|
|
821
|
-
const entries = listed && typeof listed === 'object' && listed.data?.skills;
|
|
822
|
-
const ids = Array.isArray(entries) ? entries.map((skill) => skill && typeof skill === 'object' ? skill.id : undefined).filter((id) => typeof id === 'string') : [];
|
|
841
|
+
const ids = options.only ?? await listedSkillIds(client);
|
|
823
842
|
if (!ids.length)
|
|
824
843
|
throw new client_1.CliError('Dreamstate API returned no installable skills.', 'invalid_response', 200);
|
|
825
|
-
if (ids.some((id) => !/^[a-z0-9][a-z0-9_-]*$/i.test(id)))
|
|
826
|
-
throw new CliUsageError('Dreamstate API returned an invalid skill id.');
|
|
827
844
|
const skills = [];
|
|
828
|
-
for (const id of ids)
|
|
829
|
-
|
|
830
|
-
if (!/^[a-z0-9][a-z0-9_-]*$/i.test(skill.id))
|
|
831
|
-
throw new CliUsageError('Dreamstate API returned an invalid skill id.');
|
|
832
|
-
if (skill.id !== id)
|
|
833
|
-
throw new client_1.CliError('Dreamstate API returned a mismatched skill id.', 'invalid_response', 200);
|
|
834
|
-
skills.push(skill);
|
|
835
|
-
}
|
|
845
|
+
for (const id of ids)
|
|
846
|
+
skills.push(await loadHostedSkill(client, id, true));
|
|
836
847
|
const packs = await client.listPacks();
|
|
837
848
|
const whoami = await client.getWhoami();
|
|
838
849
|
const workspaceId = typeof whoami?.data?.workspace_id === 'string' ? whoami.data.workspace_id : 'unknown';
|
|
839
|
-
const
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
const installed = await (0, skillInstaller_1.installSkills)([...skills, (0, skillInstaller_1.routerSkill)(packs)], {
|
|
850
|
+
const all = [...skills, (0, skillInstaller_1.routerSkill)(packs)];
|
|
851
|
+
// Stamped into every file so a stale install is visible against the live skills.
|
|
852
|
+
const installed = await (0, skillInstaller_1.installSkills)(all, {
|
|
843
853
|
agents,
|
|
844
854
|
homeDirectory: runtime.homeDirectory,
|
|
845
855
|
project: Boolean(options.project),
|
|
846
856
|
copy: Boolean(options.copy) || process.platform === 'win32',
|
|
847
857
|
workspaceId,
|
|
848
|
-
indexVersion,
|
|
858
|
+
indexVersion: (0, skillInstaller_1.skillIndexVersion)(all),
|
|
849
859
|
});
|
|
850
860
|
return { files: installed.files, skillIds: [...ids, 'router'] };
|
|
851
861
|
}
|
|
@@ -930,12 +940,12 @@ async function runCli(argv = process.argv.slice(2), runtime = {}) {
|
|
|
930
940
|
if (command.command === 'help' || command.command === 'pack_help') {
|
|
931
941
|
const packHelp = command.command === 'pack_help' ? command : undefined;
|
|
932
942
|
print({ ok: true, data: { usage: packHelp?.usage ?? USAGE }, meta: {} }, stdout, Boolean(packHelp?.jsonOutput));
|
|
933
|
-
process.exitCode =
|
|
943
|
+
process.exitCode = errors_1.CLI_EXIT.SUCCESS;
|
|
934
944
|
return;
|
|
935
945
|
}
|
|
936
946
|
if (command.command === 'version') {
|
|
937
947
|
print({ ok: true, data: { version: client_1.CLI_VERSION }, meta: {} }, stdout, false);
|
|
938
|
-
process.exitCode =
|
|
948
|
+
process.exitCode = errors_1.CLI_EXIT.SUCCESS;
|
|
939
949
|
return;
|
|
940
950
|
}
|
|
941
951
|
if (command.command === 'install') {
|
|
@@ -948,7 +958,7 @@ async function runCli(argv = process.argv.slice(2), runtime = {}) {
|
|
|
948
958
|
const removedSkills = done.includes('claude') ? await (0, pluginInstaller_1.removeClaudeSkillCopies)(runtime.homeDirectory) : [];
|
|
949
959
|
stderr.write(`Dreamstate plugin installed for ${done.join(', ')}. Start a new session; the first Dreamstate tool call asks you to sign in.\n`);
|
|
950
960
|
print({ ok: true, data: { ...installed, removed_skill_copies: removedSkills }, meta: {} }, stdout, Boolean(command.jsonOutput));
|
|
951
|
-
process.exitCode =
|
|
961
|
+
process.exitCode = errors_1.CLI_EXIT.SUCCESS;
|
|
952
962
|
return;
|
|
953
963
|
}
|
|
954
964
|
if (command.command === 'logout') {
|
|
@@ -967,7 +977,7 @@ async function runCli(argv = process.argv.slice(2), runtime = {}) {
|
|
|
967
977
|
const message = removed ? `Removed ${path}` : `Already logged out: no credentials at ${path}`;
|
|
968
978
|
stderr.write(`${message}\n`);
|
|
969
979
|
print({ ok: true, data: { credentials_path: path, removed }, meta: {} }, stdout, Boolean(command.jsonOutput));
|
|
970
|
-
process.exitCode =
|
|
980
|
+
process.exitCode = errors_1.CLI_EXIT.SUCCESS;
|
|
971
981
|
return;
|
|
972
982
|
}
|
|
973
983
|
if (command.command === 'profiles_list' || command.command === 'profiles_current' || command.command === 'profiles_use' || command.command === 'profiles_remove' || command.command === 'profiles_env') {
|
|
@@ -992,20 +1002,20 @@ async function runCli(argv = process.argv.slice(2), runtime = {}) {
|
|
|
992
1002
|
else {
|
|
993
1003
|
const profiles = await (0, config_1.listCredentialProfiles)(path);
|
|
994
1004
|
if (!profiles.some((profile) => profile.name === command.name))
|
|
995
|
-
throw new CliUsageError(`Profile not found: ${command.name}.`);
|
|
1005
|
+
throw new errors_1.CliUsageError(`Profile not found: ${command.name}.`);
|
|
996
1006
|
const result = { ok: true, data: { profile: command.name, exports: [`DREAMSTATE_PROFILE=${command.name}`] }, meta: {} };
|
|
997
1007
|
if (command.jsonOutput)
|
|
998
1008
|
print(result, stdout, true);
|
|
999
1009
|
else
|
|
1000
1010
|
stdout.write(`export DREAMSTATE_PROFILE=${command.name}\n`);
|
|
1001
1011
|
}
|
|
1002
|
-
process.exitCode =
|
|
1012
|
+
process.exitCode = errors_1.CLI_EXIT.SUCCESS;
|
|
1003
1013
|
return;
|
|
1004
1014
|
}
|
|
1005
1015
|
if (command.command === 'login' && command.token !== undefined) {
|
|
1006
1016
|
const token = command.token.trim();
|
|
1007
1017
|
if (!(0, config_1.isDreamstateToken)(token))
|
|
1008
|
-
throw new CliUsageError('--token must be a Dreamstate session or API key starting with ds_user_ or ds_live_.');
|
|
1018
|
+
throw new errors_1.CliUsageError('--token must be a Dreamstate session or API key starting with ds_user_ or ds_live_.');
|
|
1009
1019
|
const environment = runtime.env ?? process.env;
|
|
1010
1020
|
const baseUrl = (0, config_1.resolveApiBaseUrl)(environment);
|
|
1011
1021
|
// Only an explicit flag or env picks the workspace here: a stored default belongs to the previous login.
|
|
@@ -1024,7 +1034,7 @@ async function runCli(argv = process.argv.slice(2), runtime = {}) {
|
|
|
1024
1034
|
await (0, config_1.storeApiKey)(token, runtimeCredentialsPath(runtime));
|
|
1025
1035
|
const skills = await installLoginSkills(client, runtime, Boolean(command.noSkills), stderr);
|
|
1026
1036
|
print(loginResult(whoami, skills), stdout, Boolean(command.jsonOutput));
|
|
1027
|
-
process.exitCode =
|
|
1037
|
+
process.exitCode = errors_1.CLI_EXIT.SUCCESS;
|
|
1028
1038
|
return;
|
|
1029
1039
|
}
|
|
1030
1040
|
if (command.command === 'status') {
|
|
@@ -1033,27 +1043,47 @@ async function runCli(argv = process.argv.slice(2), runtime = {}) {
|
|
|
1033
1043
|
const apiKey = await (0, config_1.resolveApiKey)(environment, credentialFilePath);
|
|
1034
1044
|
const workspace = await (0, config_1.resolveWorkspace)(extracted.workspace, environment, credentialFilePath);
|
|
1035
1045
|
const client = new client_1.DreamstateClient({ baseUrl: (0, config_1.resolveApiBaseUrl)(environment), apiKey, ...(workspace ? { workspace } : {}), requestTimeoutMs: runtime.requestTimeoutMs });
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1046
|
+
let whoami = null;
|
|
1047
|
+
let health = null;
|
|
1048
|
+
let refusedAsTooOld = null;
|
|
1049
|
+
try {
|
|
1050
|
+
[whoami, health] = await Promise.all([client.getStatusWhoami(), client.getHealth()]);
|
|
1051
|
+
}
|
|
1052
|
+
catch (error) {
|
|
1053
|
+
// A server that refuses this CLI as too old (426) is the clearest skew report there is:
|
|
1054
|
+
// print it as a status, with the update command, instead of only an error.
|
|
1055
|
+
if (!(error instanceof client_1.CliError) || error.code !== 'cli_update_required')
|
|
1056
|
+
throw error;
|
|
1057
|
+
refusedAsTooOld = error;
|
|
1058
|
+
}
|
|
1059
|
+
const info = client.serverInfo;
|
|
1060
|
+
const refusal = refusedAsTooOld?.details && typeof refusedAsTooOld.details === 'object' ? refusedAsTooOld.details : {};
|
|
1061
|
+
const data = statusData(whoami, health, {
|
|
1062
|
+
...info,
|
|
1063
|
+
minimum_cli_version: info.minimum_cli_version ?? (typeof refusal.minimum_cli_version === 'string' ? refusal.minimum_cli_version : null),
|
|
1064
|
+
});
|
|
1065
|
+
print({ ok: true, data, meta: {} }, stdout, Boolean(command.jsonOutput));
|
|
1066
|
+
process.exitCode = data.skew === 'cli_below_minimum' ? errors_1.CLI_EXIT.UPDATE_REQUIRED : errors_1.CLI_EXIT.SUCCESS;
|
|
1067
|
+
if (process.exitCode === errors_1.CLI_EXIT.UPDATE_REQUIRED)
|
|
1068
|
+
stderr.write(`This CLI (${client_1.CLI_VERSION}) is below the server minimum${data.minimum_cli_version ? ` ${String(data.minimum_cli_version)}` : ''}. ${String(data.upgrade_hint)}\n`);
|
|
1039
1069
|
return;
|
|
1040
1070
|
}
|
|
1041
1071
|
if (command.command === 'login') {
|
|
1042
1072
|
if (command.apiKeyFile || (runtime.env ?? process.env).DREAMSTATE_API_KEY || runtime.promptHidden) {
|
|
1043
1073
|
const key = await readLoginKey(command, runtime);
|
|
1044
1074
|
if (!(0, config_1.isDreamstateToken)(key))
|
|
1045
|
-
throw new CliUsageError('Dreamstate API keys must start with ds_user_ or ds_live_.');
|
|
1075
|
+
throw new errors_1.CliUsageError('Dreamstate API keys must start with ds_user_ or ds_live_.');
|
|
1046
1076
|
const path = command.profile
|
|
1047
1077
|
? await (0, config_1.storeCredentialProfile)(command.profile, key, runtimeCredentialsPath(runtime))
|
|
1048
1078
|
: await (0, config_1.storeApiKey)(key, runtimeCredentialsPath(runtime));
|
|
1049
1079
|
const client = new client_1.DreamstateClient({ baseUrl: (0, config_1.resolveApiBaseUrl)(runtime.env ?? process.env), apiKey: key, requestTimeoutMs: runtime.requestTimeoutMs, onVersionWarning: (message) => stderr.write(`${message}\n`) });
|
|
1050
1080
|
const skills = await installLoginSkills(client, runtime, Boolean(command.noSkills), stderr);
|
|
1051
1081
|
print({ ok: true, data: { credentials_path: path, skills }, meta: {} }, stdout, Boolean(command.jsonOutput));
|
|
1052
|
-
process.exitCode =
|
|
1082
|
+
process.exitCode = errors_1.CLI_EXIT.SUCCESS;
|
|
1053
1083
|
return;
|
|
1054
1084
|
}
|
|
1055
1085
|
await loginWithDeviceAuthorization(runtime, stdout, stderr, command.profile, Boolean(command.noSkills), Boolean(command.jsonOutput));
|
|
1056
|
-
process.exitCode =
|
|
1086
|
+
process.exitCode = errors_1.CLI_EXIT.SUCCESS;
|
|
1057
1087
|
return;
|
|
1058
1088
|
}
|
|
1059
1089
|
if (command.command === 'skills_doctor') {
|
|
@@ -1065,8 +1095,56 @@ async function runCli(argv = process.argv.slice(2), runtime = {}) {
|
|
|
1065
1095
|
const client = new client_1.DreamstateClient({ baseUrl, apiKey, ...(workspace ? { workspace } : {}), requestTimeoutMs: runtime.requestTimeoutMs });
|
|
1066
1096
|
await client.listSkills({ limit: 1 });
|
|
1067
1097
|
const installers = await (0, skillInstaller_1.inspectSkillInstallers)(command.agents, runtime.homeDirectory, Boolean(command.project));
|
|
1068
|
-
|
|
1069
|
-
|
|
1098
|
+
// Age says nothing about whether the install matches the server. Compare the content
|
|
1099
|
+
// stamp of every installed skill with the live skill's, the way install computes it.
|
|
1100
|
+
const ids = await listedSkillIds(client);
|
|
1101
|
+
const live = [];
|
|
1102
|
+
for (const id of ids)
|
|
1103
|
+
live.push(await loadHostedSkill(client, id, false));
|
|
1104
|
+
live.push((0, skillInstaller_1.routerSkill)(await client.listPacks()));
|
|
1105
|
+
const drift = await (0, skillInstaller_1.compareInstalledSkills)(live, runtime.homeDirectory, Boolean(command.project));
|
|
1106
|
+
const pluginAgents = await (0, pluginInstaller_1.agentsWithDreamstatePlugin)(runtime.homeDirectory);
|
|
1107
|
+
const duplicateSources = pluginAgents.filter((agent) => command.agents.includes(agent) && drift.installed_skill_ids.length > 0);
|
|
1108
|
+
print({ ok: true, data: { index_reachable: true, installers, content: drift, plugin_agents: pluginAgents, ...(duplicateSources.length ? { duplicate_skill_sources: duplicateSources } : {}) }, meta: {} }, stdout, Boolean(command.jsonOutput));
|
|
1109
|
+
process.exitCode = errors_1.CLI_EXIT.SUCCESS;
|
|
1110
|
+
return;
|
|
1111
|
+
}
|
|
1112
|
+
// The CLI's own commands answer from its parser tables, so they need no key;
|
|
1113
|
+
// matching capabilities are added when a key is available.
|
|
1114
|
+
if (command.command === 'commands_search') {
|
|
1115
|
+
const commands = (0, commandTable_1.searchCliCommands)(command.term, USAGE);
|
|
1116
|
+
let capabilities = null;
|
|
1117
|
+
let capabilitiesError = null;
|
|
1118
|
+
try {
|
|
1119
|
+
const environment = runtime.env ?? process.env;
|
|
1120
|
+
const credentialFilePath = runtimeCredentialsPath(runtime);
|
|
1121
|
+
const apiKey = await (0, config_1.resolveApiKey)(environment, credentialFilePath);
|
|
1122
|
+
const workspace = await (0, config_1.resolveWorkspace)(extracted.workspace, environment, credentialFilePath);
|
|
1123
|
+
const client = new client_1.DreamstateClient({
|
|
1124
|
+
baseUrl: (0, config_1.resolveApiBaseUrl)(environment),
|
|
1125
|
+
apiKey,
|
|
1126
|
+
...(workspace ? { workspace } : {}),
|
|
1127
|
+
requestTimeoutMs: runtime.requestTimeoutMs,
|
|
1128
|
+
onVersionWarning: (message) => stderr.write(`${message}\n`),
|
|
1129
|
+
});
|
|
1130
|
+
capabilities = await client.listTools({ goal: command.term });
|
|
1131
|
+
}
|
|
1132
|
+
catch (error) {
|
|
1133
|
+
if (!commands.length || (error instanceof client_1.CliError && error.code === 'cli_update_required'))
|
|
1134
|
+
throw error;
|
|
1135
|
+
capabilitiesError = { code: (0, gatedRun_1.code)(error) ?? 'request_failed', message: error instanceof Error ? error.message : String(error) };
|
|
1136
|
+
}
|
|
1137
|
+
print({
|
|
1138
|
+
ok: true,
|
|
1139
|
+
data: {
|
|
1140
|
+
commands,
|
|
1141
|
+
// Catalog capabilities that match the same words; run one with `dreamstate tools run <capability_id>`.
|
|
1142
|
+
capabilities: capabilities?.data ?? null,
|
|
1143
|
+
...(capabilitiesError ? { capabilities_error: capabilitiesError } : {}),
|
|
1144
|
+
},
|
|
1145
|
+
meta: capabilities?.meta && typeof capabilities.meta === 'object' ? capabilities.meta : {},
|
|
1146
|
+
}, stdout, Boolean(command.jsonOutput));
|
|
1147
|
+
process.exitCode = errors_1.CLI_EXIT.SUCCESS;
|
|
1070
1148
|
return;
|
|
1071
1149
|
}
|
|
1072
1150
|
// Preview parses the local file and writes nothing, so it must not require a key.
|
|
@@ -1081,9 +1159,9 @@ async function runCli(argv = process.argv.slice(2), runtime = {}) {
|
|
|
1081
1159
|
}, stdout, Boolean(command.jsonOutput));
|
|
1082
1160
|
}
|
|
1083
1161
|
catch (error) {
|
|
1084
|
-
throw new CliUsageError(error instanceof CliUsageError ? error.message : error instanceof Error ? error.message : String(error));
|
|
1162
|
+
throw new errors_1.CliUsageError(error instanceof errors_1.CliUsageError ? error.message : error instanceof Error ? error.message : String(error));
|
|
1085
1163
|
}
|
|
1086
|
-
process.exitCode =
|
|
1164
|
+
process.exitCode = errors_1.CLI_EXIT.SUCCESS;
|
|
1087
1165
|
return;
|
|
1088
1166
|
}
|
|
1089
1167
|
// init writes a local scaffold and calls no API, so it must not require a key.
|
|
@@ -1097,7 +1175,7 @@ async function runCli(argv = process.argv.slice(2), runtime = {}) {
|
|
|
1097
1175
|
await require('./commands/workflows')
|
|
1098
1176
|
.runWorkflowsCommand(undefined, command, stdout);
|
|
1099
1177
|
}
|
|
1100
|
-
process.exitCode =
|
|
1178
|
+
process.exitCode = errors_1.CLI_EXIT.SUCCESS;
|
|
1101
1179
|
return;
|
|
1102
1180
|
}
|
|
1103
1181
|
const environment = runtime.env ?? process.env;
|
|
@@ -1117,7 +1195,7 @@ async function runCli(argv = process.argv.slice(2), runtime = {}) {
|
|
|
1117
1195
|
}
|
|
1118
1196
|
if (command.command === 'workspaces_use' && apiKey.startsWith('ds_live_')) {
|
|
1119
1197
|
// Decided locally: a workspace key can never switch, so no request is worth making.
|
|
1120
|
-
throw new CliUsageError('This key is bound to one workspace. Run dreamstate login for a user session that can switch.');
|
|
1198
|
+
throw new errors_1.CliUsageError('This key is bound to one workspace. Run dreamstate login for a user session that can switch.');
|
|
1121
1199
|
}
|
|
1122
1200
|
const workspace = command.command === 'workspaces_use'
|
|
1123
1201
|
? command.workspace
|
|
@@ -1129,6 +1207,19 @@ async function runCli(argv = process.argv.slice(2), runtime = {}) {
|
|
|
1129
1207
|
requestTimeoutMs: runtime.requestTimeoutMs,
|
|
1130
1208
|
onVersionWarning: (message) => (runtime.stderr ?? process.stderr).write(`${message}\n`),
|
|
1131
1209
|
});
|
|
1210
|
+
const turnsPath = (0, turnCache_1.turnCachePath)(credentialFilePath);
|
|
1211
|
+
const idempotencyPath = (0, idempotency_1.pendingIdempotencyPath)(credentialFilePath);
|
|
1212
|
+
// A turn belongs to one workspace, so the workspace is part of the cache identity.
|
|
1213
|
+
const gatedContext = () => ({
|
|
1214
|
+
client,
|
|
1215
|
+
baseUrl,
|
|
1216
|
+
turnKey: workspace ? `${apiKey}\n${workspace}` : apiKey,
|
|
1217
|
+
turnsPath,
|
|
1218
|
+
idempotencyPath,
|
|
1219
|
+
...(workspace ? { workspace } : {}),
|
|
1220
|
+
stderr,
|
|
1221
|
+
sleep: runtime.sleep ?? wait,
|
|
1222
|
+
});
|
|
1132
1223
|
let result;
|
|
1133
1224
|
switch (command.command) {
|
|
1134
1225
|
case 'whoami': {
|
|
@@ -1149,7 +1240,7 @@ async function runCli(argv = process.argv.slice(2), runtime = {}) {
|
|
|
1149
1240
|
stdout.write(rows.length
|
|
1150
1241
|
? `${rows.map((row) => `${row.id === activeId ? '*' : ' '} ${row.slug ?? '-'} ${row.name} ${row.role ?? '-'} ${row.id}`).join('\n')}\n`
|
|
1151
1242
|
: 'No workspaces. Ask a workspace owner to invite you.\n');
|
|
1152
|
-
process.exitCode =
|
|
1243
|
+
process.exitCode = errors_1.CLI_EXIT.SUCCESS;
|
|
1153
1244
|
return;
|
|
1154
1245
|
}
|
|
1155
1246
|
break;
|
|
@@ -1160,10 +1251,10 @@ async function runCli(argv = process.argv.slice(2), runtime = {}) {
|
|
|
1160
1251
|
const wanted = command.workspace.toLowerCase();
|
|
1161
1252
|
const match = data.workspaces?.find((row) => row.id.toLowerCase() === wanted || row.slug?.toLowerCase() === wanted);
|
|
1162
1253
|
if (!match)
|
|
1163
|
-
throw new CliUsageError(`You are not a member of workspace "${command.workspace}". Run dreamstate workspaces list to see the workspaces you can use.`);
|
|
1254
|
+
throw new errors_1.CliUsageError(`You are not a member of workspace "${command.workspace}". Run dreamstate workspaces list to see the workspaces you can use.`);
|
|
1164
1255
|
const stored = await (0, config_1.readStoredCredentials)(await (0, config_1.selectedCredentialsPath)(credentialFilePath, environment));
|
|
1165
1256
|
if (!stored)
|
|
1166
|
-
throw new CliUsageError('workspaces use saves the default next to a stored login. Run dreamstate login or dreamstate login --token first.');
|
|
1257
|
+
throw new errors_1.CliUsageError('workspaces use saves the default next to a stored login. Run dreamstate login or dreamstate login --token first.');
|
|
1167
1258
|
await (0, config_1.storeApiKey)(stored.token, await (0, config_1.selectedCredentialsPath)(credentialFilePath, environment), match.id);
|
|
1168
1259
|
stderr.write(`Default workspace: ${match.name} (${match.slug})\n`);
|
|
1169
1260
|
result = { ok: true, data: { workspace: { id: match.id, name: match.name, slug: match.slug }, credentials_path: credentialFilePath }, meta: {} };
|
|
@@ -1188,47 +1279,26 @@ async function runCli(argv = process.argv.slice(2), runtime = {}) {
|
|
|
1188
1279
|
result = await client.searchSkillDocuments(command.query);
|
|
1189
1280
|
break;
|
|
1190
1281
|
case 'skills_install': {
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1282
|
+
// An agent that already loads Dreamstate skills from the plugin gets them from there:
|
|
1283
|
+
// installing hosted copies too would load the same skill twice under two names, from
|
|
1284
|
+
// two sources that drift apart. login skips such agents the same way.
|
|
1285
|
+
const withPlugin = await (0, pluginInstaller_1.agentsWithDreamstatePlugin)(runtime.homeDirectory);
|
|
1286
|
+
const targets = command.agents.filter((agent) => !withPlugin.includes(agent));
|
|
1287
|
+
const skipped = command.agents.filter((agent) => !targets.includes(agent)).map((agent) => ({ agent, reason: 'plugin_installed' }));
|
|
1288
|
+
if (skipped.length)
|
|
1289
|
+
stderr.write(`Skipped ${skipped.map((entry) => entry.agent).join(', ')}: the Dreamstate plugin already provides skills there. Update it with dreamstate install.\n`);
|
|
1290
|
+
if (!targets.length) {
|
|
1291
|
+
result = { ok: true, data: { installed_files: [], skill_ids: [], agents: [], skipped_agents: skipped }, meta: {} };
|
|
1194
1292
|
break;
|
|
1195
1293
|
}
|
|
1196
|
-
const
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
// Read each skill one at a time because the workspace's private vault
|
|
1201
|
-
// admits two reads at once, and fifteen parallel reads failed in production.
|
|
1202
|
-
const skills = [];
|
|
1203
|
-
for (const id of ids) {
|
|
1204
|
-
const skill = hostedSkill(await client.getSkill(id));
|
|
1205
|
-
if (!/^[a-z0-9][a-z0-9_-]*$/i.test(skill.id))
|
|
1206
|
-
throw new CliUsageError('Dreamstate API returned an invalid skill id.');
|
|
1207
|
-
if (skill.id !== id)
|
|
1208
|
-
throw new client_1.CliError('Dreamstate API returned a mismatched skill id.', 'invalid_response', 200);
|
|
1209
|
-
skills.push(skill);
|
|
1210
|
-
}
|
|
1211
|
-
const packs = await client.listPacks();
|
|
1212
|
-
const whoami = await client.getWhoami();
|
|
1213
|
-
const workspaceId = typeof whoami?.data?.workspace_id === 'string' ? whoami.data.workspace_id : 'unknown';
|
|
1214
|
-
// Stamped into every file so a stale install is visible against the live skills.
|
|
1215
|
-
const indexVersion = (0, node_crypto_1.createHash)('sha256')
|
|
1216
|
-
.update(JSON.stringify(skills.map((skill) => [skill.id, skill.description, skill.body, skill.frontmatter ?? {}])))
|
|
1217
|
-
.digest('hex').slice(0, 16);
|
|
1218
|
-
const installed = await (0, skillInstaller_1.installSkills)([...skills, (0, skillInstaller_1.routerSkill)(packs)], {
|
|
1219
|
-
agents: command.agents,
|
|
1220
|
-
homeDirectory: runtime.homeDirectory,
|
|
1221
|
-
project: Boolean(command.project),
|
|
1222
|
-
copy: Boolean(command.copy) || process.platform === 'win32',
|
|
1223
|
-
workspaceId,
|
|
1224
|
-
indexVersion,
|
|
1294
|
+
const installed = await installHostedSkills(client, targets, runtime, {
|
|
1295
|
+
project: command.project,
|
|
1296
|
+
copy: command.copy,
|
|
1297
|
+
...(command.skillId === '*' ? {} : { only: [command.skillId] }),
|
|
1225
1298
|
});
|
|
1226
|
-
result = { ok: true, data: { installed_files: installed.files, skill_ids:
|
|
1299
|
+
result = { ok: true, data: { installed_files: installed.files, skill_ids: installed.skillIds, agents: targets, ...(skipped.length ? { skipped_agents: skipped } : {}), ...(command.project ? { lock_file: 'skills-lock.json' } : {}) }, meta: {} };
|
|
1227
1300
|
break;
|
|
1228
1301
|
}
|
|
1229
|
-
case 'commands_search':
|
|
1230
|
-
result = await client.listTools({ goal: command.term });
|
|
1231
|
-
break;
|
|
1232
1302
|
case 'tools_search':
|
|
1233
1303
|
result = await client.listTools(command.query);
|
|
1234
1304
|
break;
|
|
@@ -1239,100 +1309,30 @@ async function runCli(argv = process.argv.slice(2), runtime = {}) {
|
|
|
1239
1309
|
result = await client.getTool(command.capabilityId);
|
|
1240
1310
|
break;
|
|
1241
1311
|
case 'tools_run': {
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
warnedAboutTurnCache = true;
|
|
1254
|
-
stderr.write('Warning: Dreamstate turn cache unavailable; continuing without turn reuse.\n');
|
|
1255
|
-
};
|
|
1256
|
-
const pending = command.turnToken
|
|
1257
|
-
? null
|
|
1258
|
-
: await (0, turnCache_1.reusableTurnToken)(turns, baseUrl, turnKey, command.capabilityId, Date.now(), warnTurnCache);
|
|
1259
|
-
const inlineRead = !command.turnToken && !pending && !command.idempotencyKey && omitsToolTurnForInlineTableRead(command.capabilityId);
|
|
1260
|
-
const fetchedForRun = command.turnToken || pending || inlineRead ? null : await client.getTool(command.capabilityId);
|
|
1261
|
-
let turnToken = command.turnToken ?? pending ?? (inlineRead ? undefined : toolTurnToken(fetchedForRun));
|
|
1262
|
-
const callerParameterKey = parameterIdempotencyKey(command.body);
|
|
1263
|
-
let idempotencyKey = capabilityIdempotencyKey(fetchedForRun, callerParameterKey ?? command.idempotencyKey);
|
|
1264
|
-
let recoveredInlineRead = false;
|
|
1265
|
-
const run = () => client.runCapability(command.capabilityId, {
|
|
1266
|
-
...command.body,
|
|
1267
|
-
...(turnToken ? { tool_turn_token: turnToken } : {}),
|
|
1268
|
-
}, idempotencyKey);
|
|
1269
|
-
try {
|
|
1270
|
-
try {
|
|
1271
|
-
result = await run();
|
|
1272
|
-
}
|
|
1273
|
-
catch (error) {
|
|
1274
|
-
// The inline skip is only valid while the server still treats this
|
|
1275
|
-
// capability as a free sync read. capability_not_fetched means the
|
|
1276
|
-
// fetch-first fence is on, so retry once on the old get-then-run path.
|
|
1277
|
-
if (inlineRead && !recoveredInlineRead && code(error) === 'capability_not_fetched') {
|
|
1278
|
-
recoveredInlineRead = true;
|
|
1279
|
-
const fetched = await client.getTool(command.capabilityId);
|
|
1280
|
-
turnToken = toolTurnToken(fetched);
|
|
1281
|
-
idempotencyKey = capabilityIdempotencyKey(fetched, callerParameterKey ?? command.idempotencyKey);
|
|
1282
|
-
result = await run();
|
|
1283
|
-
}
|
|
1284
|
-
else if (command.body.mode !== 'live' || code(error) !== 'quote_required') {
|
|
1285
|
-
// A quote_required refusal binds a fresh quote to this turn, so the
|
|
1286
|
-
// identical call admits on the next try. Retry once here instead of
|
|
1287
|
-
// making the caller rerun the same command by hand.
|
|
1288
|
-
throw error;
|
|
1289
|
-
}
|
|
1290
|
-
else {
|
|
1291
|
-
result = await run();
|
|
1292
|
-
}
|
|
1293
|
-
}
|
|
1294
|
-
}
|
|
1295
|
-
catch (error) {
|
|
1296
|
-
if (!command.turnToken && turnToken && code(error) === 'quote_required') {
|
|
1297
|
-
await (0, turnCache_1.rememberTurnToken)(turns, baseUrl, turnKey, command.capabilityId, turnToken, Date.now(), warnTurnCache);
|
|
1298
|
-
}
|
|
1299
|
-
throw error;
|
|
1300
|
-
}
|
|
1301
|
-
if (!command.turnToken) {
|
|
1302
|
-
if (command.body.mode === 'dry_run' && turnToken)
|
|
1303
|
-
await (0, turnCache_1.rememberTurnToken)(turns, baseUrl, turnKey, command.capabilityId, turnToken, Date.now(), warnTurnCache);
|
|
1304
|
-
else if (pending)
|
|
1305
|
-
await (0, turnCache_1.forgetTurnToken)(turns, baseUrl, turnKey, command.capabilityId, warnTurnCache);
|
|
1306
|
-
}
|
|
1307
|
-
if (!command.noWait && !(0, runWait_1.columnRunReturnsRunId)(command.capabilityId)) {
|
|
1308
|
-
const waited = await (0, runWait_1.waitForRun)(client, result, command.waitTimeout, runtime.sleep ?? wait);
|
|
1309
|
-
result = waited.value;
|
|
1310
|
-
if (waited.timedOut)
|
|
1311
|
-
process.exitCode = runWait_1.RUN_WAIT_TIMEOUT_EXIT_CODE;
|
|
1312
|
-
}
|
|
1312
|
+
const outcome = await (0, gatedRun_1.runGated)(gatedContext(), {
|
|
1313
|
+
capabilityId: command.capabilityId,
|
|
1314
|
+
body: command.body,
|
|
1315
|
+
...(command.turnToken ? { turnToken: command.turnToken } : {}),
|
|
1316
|
+
...(command.idempotencyKey ? { idempotencyKey: command.idempotencyKey } : {}),
|
|
1317
|
+
waitTimeout: command.waitTimeout,
|
|
1318
|
+
...(command.noWait ? { noWait: true } : {}),
|
|
1319
|
+
});
|
|
1320
|
+
result = outcome.value;
|
|
1321
|
+
if (outcome.timedOut)
|
|
1322
|
+
process.exitCode = runWait_1.RUN_WAIT_TIMEOUT_EXIT_CODE;
|
|
1313
1323
|
break;
|
|
1314
1324
|
}
|
|
1315
1325
|
case 'tables_import': {
|
|
1316
1326
|
await (0, promises_1.access)(command.file);
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
mode: 'live',
|
|
1327
|
-
params: {
|
|
1328
|
-
table_id: command.tableId,
|
|
1329
|
-
file_path: command.file,
|
|
1330
|
-
...(command.format ? { file_kind: command.format } : {}),
|
|
1331
|
-
...(command.upsertKey ? { upsert_key: command.upsertKey } : {}),
|
|
1332
|
-
...(command.rowKind ? { row_kind: command.rowKind } : {}),
|
|
1333
|
-
...(command.keepUnmappedColumns === false ? { keep_unmapped_columns: false } : {}),
|
|
1334
|
-
},
|
|
1335
|
-
}, capabilityIdempotencyKey(importContract));
|
|
1327
|
+
const imported = await (0, tablesImport_1.importLocalTable)(gatedContext(), {
|
|
1328
|
+
tableId: command.tableId,
|
|
1329
|
+
file: command.file,
|
|
1330
|
+
...(command.format ? { format: command.format } : {}),
|
|
1331
|
+
...(command.upsertKey ? { upsertKey: command.upsertKey } : {}),
|
|
1332
|
+
...(command.rowKind ? { rowKind: command.rowKind } : {}),
|
|
1333
|
+
...(command.keepUnmappedColumns === false ? { keepUnmappedColumns: false } : {}),
|
|
1334
|
+
});
|
|
1335
|
+
result = { ok: true, data: { import: imported }, meta: {} };
|
|
1336
1336
|
break;
|
|
1337
1337
|
}
|
|
1338
1338
|
case 'runs_list':
|
|
@@ -1341,12 +1341,6 @@ async function runCli(argv = process.argv.slice(2), runtime = {}) {
|
|
|
1341
1341
|
case 'runs_get':
|
|
1342
1342
|
result = await client.getRun(command.runId);
|
|
1343
1343
|
break;
|
|
1344
|
-
case 'runs_resume':
|
|
1345
|
-
result = await client.resumeRun(command.runId, {
|
|
1346
|
-
expected_state_version: command.stateVersion,
|
|
1347
|
-
...(command.reason ? { reason: command.reason } : {}),
|
|
1348
|
-
}, command.idempotencyKey);
|
|
1349
|
-
break;
|
|
1350
1344
|
case 'runs_events':
|
|
1351
1345
|
result = await client.getRunEvents(command.runId);
|
|
1352
1346
|
break;
|
|
@@ -1355,28 +1349,40 @@ async function runCli(argv = process.argv.slice(2), runtime = {}) {
|
|
|
1355
1349
|
expected_state_version: command.stateVersion ?? stateVersionFromRunRead(await client.getRun(command.runId)),
|
|
1356
1350
|
...(command.reason ? { reason: command.reason } : {}),
|
|
1357
1351
|
};
|
|
1358
|
-
|
|
1352
|
+
const identity = { scope: 'runs.cancel', workspace, body: { run_id: command.runId, ...body } };
|
|
1353
|
+
const key = command.idempotencyKey ?? await (0, idempotency_1.pendingIdempotencyKey)(idempotencyPath, identity);
|
|
1354
|
+
try {
|
|
1355
|
+
result = await client.cancelRun(command.runId, body, key);
|
|
1356
|
+
if (!command.idempotencyKey)
|
|
1357
|
+
await (0, idempotency_1.finishIdempotencyAttempt)(idempotencyPath, identity);
|
|
1358
|
+
}
|
|
1359
|
+
catch (error) {
|
|
1360
|
+
const transient = error instanceof client_1.CliError && (error.code === 'request_timeout' || error.code === 'network_error' || error.retryable === true || error.status >= 500);
|
|
1361
|
+
if (!command.idempotencyKey && !transient)
|
|
1362
|
+
await (0, idempotency_1.finishIdempotencyAttempt)(idempotencyPath, identity);
|
|
1363
|
+
throw error;
|
|
1364
|
+
}
|
|
1359
1365
|
break;
|
|
1360
1366
|
}
|
|
1361
1367
|
case 'pack': {
|
|
1362
1368
|
if (command.pack === 'sequences') {
|
|
1363
|
-
await require('./commands/sequences').runSequencesCommand(
|
|
1369
|
+
await require('./commands/sequences').runSequencesCommand(gatedContext(), command, stdout);
|
|
1364
1370
|
}
|
|
1365
1371
|
else {
|
|
1366
|
-
await require('./commands/workflows').runWorkflowsCommand(
|
|
1372
|
+
await require('./commands/workflows').runWorkflowsCommand(gatedContext(), command, stdout);
|
|
1367
1373
|
}
|
|
1368
1374
|
return;
|
|
1369
1375
|
}
|
|
1370
1376
|
default: return;
|
|
1371
1377
|
}
|
|
1372
|
-
if (
|
|
1378
|
+
if (command.command === 'tools_run' && command.open)
|
|
1373
1379
|
openResult(result, runtime);
|
|
1374
1380
|
if (command.command === 'packs_route' && !command.jsonOutput)
|
|
1375
1381
|
printPackRoute(result, stdout);
|
|
1376
1382
|
else
|
|
1377
1383
|
print(result, stdout, Boolean(command.jsonOutput));
|
|
1378
1384
|
if (process.exitCode !== runWait_1.RUN_WAIT_TIMEOUT_EXIT_CODE)
|
|
1379
|
-
process.exitCode =
|
|
1385
|
+
process.exitCode = errors_1.CLI_EXIT.SUCCESS;
|
|
1380
1386
|
}
|
|
1381
1387
|
catch (error) {
|
|
1382
1388
|
const exitCode = exitCodeForError(error);
|