@trydreamstate/cli 1.3.2 → 1.3.4
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 +36 -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 +3 -1
- package/agent-plugin/skills/dreamstate/SKILL.md +15 -5
- package/agent-plugin/skills/first-win/SKILL.md +2 -2
- package/agent-plugin/skills/inbox-replies/SKILL.md +3 -1
- package/agent-plugin/skills/knowledge/SKILL.md +3 -5
- package/agent-plugin/skills/linkedin-posts/SKILL.md +3 -1
- package/agent-plugin/skills/linkedin-warm-signals/SKILL.md +2 -2
- package/agent-plugin/skills/list-building/SKILL.md +5 -10
- package/agent-plugin/skills/list-building/column-authoring.md +1 -1
- package/agent-plugin/skills/list-building/sourcing.md +2 -2
- package/agent-plugin/skills/outreach/SKILL.md +2 -2
- package/agent-plugin/skills/outreach/steps-and-timing.md +1 -1
- package/agent-plugin/skills/plays/SKILL.md +3 -3
- package/agent-plugin/skills/reddit-posts/SKILL.md +3 -1
- package/agent-plugin/skills/skill-authoring/SKILL.md +3 -1
- package/agent-plugin/skills/social/SKILL.md +4 -6
- package/agent-plugin/skills/visibility/SKILL.md +2 -6
- package/agent-plugin/skills/workflows/SKILL.md +3 -3
- package/agent-plugin/skills/workflows/lifecycle-and-enrollment.md +1 -1
- package/agent-plugin/skills/worksheet-tidy/SKILL.md +3 -1
- package/agent-plugin/skills/writing/SKILL.md +3 -5
- package/agent-plugin/skills/x-posts/SKILL.md +3 -1
- package/dist/agentGuidance.generated.json +35 -0
- package/dist/capabilityPosture.d.ts +12 -0
- package/dist/capabilityPosture.d.ts.map +1 -0
- package/dist/capabilityPosture.js +39 -0
- package/dist/client.d.ts +33 -2
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +155 -64
- 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 +3 -2
- package/dist/commands/sequences.d.ts.map +1 -1
- package/dist/commands/sequences.js +17 -34
- package/dist/commands/shared.d.ts +4 -26
- package/dist/commands/shared.d.ts.map +1 -1
- package/dist/commands/shared.js +136 -158
- package/dist/commands/workflows.d.ts +3 -2
- package/dist/commands/workflows.d.ts.map +1 -1
- package/dist/commands/workflows.js +17 -39
- package/dist/errors.d.ts +74 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +84 -0
- package/dist/gatedRun.d.ts +79 -0
- package/dist/gatedRun.d.ts.map +1 -0
- package/dist/gatedRun.js +346 -0
- package/dist/generated/packVerbs.generated.json +492 -0
- package/dist/generated/runContract.generated.d.ts +4 -0
- package/dist/generated/runContract.generated.d.ts.map +1 -0
- package/dist/generated/runContract.generated.js +7 -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 +22 -35
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +405 -378
- package/dist/registryManifest.d.ts +6 -0
- package/dist/registryManifest.d.ts.map +1 -0
- package/dist/registryManifest.js +66 -0
- package/dist/runContract.d.ts +10 -0
- package/dist/runContract.d.ts.map +1 -0
- package/dist/runContract.js +13 -0
- package/dist/runWait.d.ts +2 -2
- package/dist/runWait.d.ts.map +1 -1
- package/dist/runWait.js +3 -14
- 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 +6 -3
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.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,19 @@ 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
|
+
const tablesImport_1 = require("./tablesImport");
|
|
31
|
+
const commandTable_1 = require("./commandTable");
|
|
26
32
|
const skillInstaller_1 = require("./skillInstaller");
|
|
27
|
-
|
|
28
|
-
const
|
|
33
|
+
const runContract_1 = require("./runContract");
|
|
34
|
+
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
35
|
const USAGE = `Dreamstate CLI
|
|
30
36
|
|
|
31
37
|
Usage:
|
|
@@ -53,26 +59,22 @@ Usage:
|
|
|
53
59
|
dreamstate commands search <term> [--json-output|--json]
|
|
54
60
|
dreamstate tools search --goal <goal> [--terms <csv>] [--limit <n>] [--toolset <pack_id>] [--json-output|--json]
|
|
55
61
|
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>) [--
|
|
62
|
+
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>] [--max-live-sends <n>] [--quote-hash <sha256:...>] [--idempotency-key <key>] [--wait-timeout <seconds>] [--no-wait] [--open] [--json-output]
|
|
57
63
|
dreamstate sequences <verb> [args] [flags]
|
|
58
64
|
dreamstate workflows <verb> [args] [flags]
|
|
59
|
-
dreamstate tables import --table-id <uuid> --file <path> [--format <csv|json|jsonl|xlsx>] [--preview] [--upsert-key <header>] [--
|
|
65
|
+
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
66
|
dreamstate runs list [--limit <n>] [--cursor <cursor>] [--json <query>] [--json-output|--json]
|
|
61
67
|
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
68
|
dreamstate runs events <run_id> [--json-output|--json]
|
|
64
69
|
dreamstate runs cancel <run_id> [--state-version <n>] [--reason <text>] [--json <body>] [--idempotency-key <key>] [--json-output|--json]
|
|
65
70
|
|
|
66
71
|
All catalog discovery reads live API records. Set DREAMSTATE_API_URL and DREAMSTATE_API_KEY.
|
|
67
72
|
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}.
|
|
73
|
+
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 repair.kind ask_user, 8 CLI update required, 9 refused, 10 transport, 11 timeout.
|
|
68
74
|
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
75
|
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
76
|
logout deletes the stored credentials file. --agents takes space-separated, comma-separated or repeated values.
|
|
71
77
|
`;
|
|
72
|
-
class CliUsageError extends Error {
|
|
73
|
-
constructor(message) { super(message); this.name = 'CliUsageError'; }
|
|
74
|
-
}
|
|
75
|
-
exports.CliUsageError = CliUsageError;
|
|
76
78
|
function outputOption(values) {
|
|
77
79
|
return values['json-output'] ? { jsonOutput: true } : {};
|
|
78
80
|
}
|
|
@@ -84,35 +86,41 @@ function parseJsonObject(raw, flag = '--json') {
|
|
|
84
86
|
return value;
|
|
85
87
|
}
|
|
86
88
|
catch (error) {
|
|
87
|
-
throw new CliUsageError(`${flag} ${error instanceof Error ? error.message : 'is invalid'}`);
|
|
89
|
+
throw new errors_1.CliUsageError(`${flag} ${error instanceof Error ? error.message : 'is invalid'}`);
|
|
88
90
|
}
|
|
89
91
|
}
|
|
90
92
|
function requireOne(positionals, usage) {
|
|
91
93
|
if (positionals.length !== 2 || !positionals[1])
|
|
92
|
-
throw new CliUsageError(`Usage: ${usage}`);
|
|
94
|
+
throw new errors_1.CliUsageError(`Usage: ${usage}`);
|
|
93
95
|
return positionals[1];
|
|
94
96
|
}
|
|
95
97
|
function csv(raw, flag = '--terms') {
|
|
96
98
|
const values = raw.split(',').map((value) => value.trim()).filter(Boolean);
|
|
97
99
|
if (!values.length)
|
|
98
|
-
throw new CliUsageError(`${flag} must contain at least one value`);
|
|
100
|
+
throw new errors_1.CliUsageError(`${flag} must contain at least one value`);
|
|
99
101
|
return [...new Set(values)];
|
|
100
102
|
}
|
|
101
103
|
function requireToolsRunMode(body) {
|
|
102
|
-
if (
|
|
104
|
+
if (runContract_1.RUN_MODES.includes(body.mode))
|
|
103
105
|
return;
|
|
104
|
-
throw new CliUsageError('mode must be one of: smoke_test, dry_run, live.');
|
|
106
|
+
throw new errors_1.CliUsageError('mode must be one of: smoke_test, dry_run, live.');
|
|
105
107
|
}
|
|
106
108
|
function nonNegativeNumber(raw, flag) {
|
|
107
109
|
const value = Number(raw);
|
|
108
110
|
if (!Number.isFinite(value) || value < 0)
|
|
109
|
-
throw new CliUsageError(`${flag} must be a non-negative number`);
|
|
111
|
+
throw new errors_1.CliUsageError(`${flag} must be a non-negative number`);
|
|
110
112
|
return value;
|
|
111
113
|
}
|
|
112
114
|
function nonNegativeInteger(raw, flag) {
|
|
113
115
|
const value = Number(raw);
|
|
114
116
|
if (!Number.isSafeInteger(value) || value < 0)
|
|
115
|
-
throw new CliUsageError(`${flag} must be a non-negative integer`);
|
|
117
|
+
throw new errors_1.CliUsageError(`${flag} must be a non-negative integer`);
|
|
118
|
+
return value;
|
|
119
|
+
}
|
|
120
|
+
function positiveInteger(raw, flag) {
|
|
121
|
+
const value = Number(raw);
|
|
122
|
+
if (!Number.isSafeInteger(value) || value < 1)
|
|
123
|
+
throw new errors_1.CliUsageError(`${flag} must be an integer greater than or equal to 1`);
|
|
116
124
|
return value;
|
|
117
125
|
}
|
|
118
126
|
// runs.get returns { data: { run: { state_version } } }. Cancel posts
|
|
@@ -141,7 +149,7 @@ function parsePacks(argv) {
|
|
|
141
149
|
return { command: 'packs_get', packId: requireOne(positionals, 'dreamstate packs get <pack_id>'), ...outputOption(values) };
|
|
142
150
|
if (positionals.length === 1 && positionals[0] === 'route' && values.goal?.trim())
|
|
143
151
|
return { command: 'packs_route', goal: values.goal.trim(), ...outputOption(values) };
|
|
144
|
-
throw new CliUsageError('Usage: dreamstate packs <list|get|route>');
|
|
152
|
+
throw new errors_1.CliUsageError('Usage: dreamstate packs <list|get|route>');
|
|
145
153
|
}
|
|
146
154
|
/** Rewrites `--agents a b c` into `--agents=a --agents=b --agents=c` so parseArgs sees every value. */
|
|
147
155
|
function spaceSeparatedAgents(argv) {
|
|
@@ -172,7 +180,7 @@ function parseSkills(argv) {
|
|
|
172
180
|
const requested = [...new Set(values.agents.flatMap((raw) => csv(raw, '--agents')))];
|
|
173
181
|
const invalid = requested.filter((agent) => !skillInstaller_1.SKILL_INSTALL_AGENTS.includes(agent));
|
|
174
182
|
if (invalid.length)
|
|
175
|
-
throw new CliUsageError(`--agents values must be one of: ${skillInstaller_1.SKILL_INSTALL_AGENTS.join(', ')} (got: ${invalid.join(', ')})`);
|
|
183
|
+
throw new errors_1.CliUsageError(`--agents values must be one of: ${skillInstaller_1.SKILL_INSTALL_AGENTS.join(', ')} (got: ${invalid.join(', ')})`);
|
|
176
184
|
return requested;
|
|
177
185
|
};
|
|
178
186
|
if (positionals.length === 1 && positionals[0] === 'list') {
|
|
@@ -195,14 +203,14 @@ function parseSkills(argv) {
|
|
|
195
203
|
return { command: 'skills_install', skillId: values.skill?.trim() || '*', agents: agents(), ...(values.project ? { project: true } : {}), ...(values.copy ? { copy: true } : {}), ...outputOption(values) };
|
|
196
204
|
if (positionals.length === 1 && positionals[0] === 'doctor')
|
|
197
205
|
return { command: 'skills_doctor', agents: agents(), ...(values.project ? { project: true } : {}), ...outputOption(values) };
|
|
198
|
-
throw new CliUsageError('Usage: dreamstate skills <list|get|search|install|doctor>');
|
|
206
|
+
throw new errors_1.CliUsageError('Usage: dreamstate skills <list|get|search|install|doctor>');
|
|
199
207
|
}
|
|
200
208
|
function parseCommands(argv) {
|
|
201
209
|
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
210
|
if (values.help)
|
|
203
211
|
return { command: 'help' };
|
|
204
212
|
if (positionals[0] !== 'search' || positionals.length < 2)
|
|
205
|
-
throw new CliUsageError('Usage: dreamstate commands search <term>');
|
|
213
|
+
throw new errors_1.CliUsageError('Usage: dreamstate commands search <term>');
|
|
206
214
|
return { command: 'commands_search', term: positionals.slice(1).join(' '), ...outputOption(values) };
|
|
207
215
|
}
|
|
208
216
|
/**
|
|
@@ -210,10 +218,8 @@ function parseCommands(argv) {
|
|
|
210
218
|
* are mutually exclusive ways of saying the same thing, and disagreeing ones are a
|
|
211
219
|
* usage error rather than a silent winner.
|
|
212
220
|
*
|
|
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.
|
|
221
|
+
* --approved is deliberately NOT here: it is not a mode. It rides on a live run
|
|
222
|
+
* as the caller's assertion that it asked a person (see parseTools).
|
|
217
223
|
*/
|
|
218
224
|
function toolsRunMode(values) {
|
|
219
225
|
const chosen = new Set();
|
|
@@ -223,13 +229,13 @@ function toolsRunMode(values) {
|
|
|
223
229
|
chosen.add('live');
|
|
224
230
|
if (values.mode !== undefined) {
|
|
225
231
|
const mode = values.mode.trim();
|
|
226
|
-
if (!
|
|
227
|
-
throw new CliUsageError(`--mode must be one of: ${
|
|
232
|
+
if (!runContract_1.RUN_MODES.includes(mode)) {
|
|
233
|
+
throw new errors_1.CliUsageError(`--mode must be one of: ${runContract_1.RUN_MODES.join(', ')}`);
|
|
228
234
|
}
|
|
229
235
|
chosen.add(mode);
|
|
230
236
|
}
|
|
231
237
|
if (chosen.size > 1) {
|
|
232
|
-
throw new CliUsageError(`Conflicting execution modes requested: ${[...chosen].sort().join(', ')}. Pass exactly one of --live, --dry-run, --mode.`);
|
|
238
|
+
throw new errors_1.CliUsageError(`Conflicting execution modes requested: ${[...chosen].sort().join(', ')}. Pass exactly one of --live, --dry-run, --mode.`);
|
|
233
239
|
}
|
|
234
240
|
return [...chosen][0];
|
|
235
241
|
}
|
|
@@ -239,8 +245,9 @@ function parseTools(argv) {
|
|
|
239
245
|
limit: { type: 'string' }, toolset: { type: 'string' },
|
|
240
246
|
'idempotency-key': { type: 'string' }, 'json-output': { type: 'boolean' }, open: { type: 'boolean' },
|
|
241
247
|
'dry-run': { type: 'boolean' }, live: { type: 'boolean' }, mode: { type: 'string' },
|
|
242
|
-
|
|
243
|
-
'
|
|
248
|
+
approved: { type: 'boolean' }, 'approved-effect-unknown': { type: 'boolean' },
|
|
249
|
+
'quote-hash': { type: 'string' }, 'approval-id': { type: 'string' },
|
|
250
|
+
'max-credits': { type: 'string' }, 'max-live-sends': { type: 'string' }, 'turn-token': { type: 'string' }, 'wait-timeout': { type: 'string' }, 'no-wait': { type: 'boolean' }, help: { type: 'boolean', short: 'h' },
|
|
244
251
|
}, allowPositionals: true, strict: true });
|
|
245
252
|
if (values.help)
|
|
246
253
|
return { command: 'help' };
|
|
@@ -256,7 +263,7 @@ function parseTools(argv) {
|
|
|
256
263
|
if (values.toolset !== undefined)
|
|
257
264
|
query.toolset = values.toolset;
|
|
258
265
|
if (typeof query.goal !== 'string' && !Array.isArray(query.query_terms))
|
|
259
|
-
throw new CliUsageError('tools search requires --goal, --terms, or --json query fields');
|
|
266
|
+
throw new errors_1.CliUsageError('tools search requires --goal, --terms, or --json query fields');
|
|
260
267
|
return { command: 'tools_search', query, ...output };
|
|
261
268
|
}
|
|
262
269
|
if (positionals[0] === 'get')
|
|
@@ -264,11 +271,11 @@ function parseTools(argv) {
|
|
|
264
271
|
if (positionals[0] === 'run') {
|
|
265
272
|
const capabilityId = requireOne(positionals, 'dreamstate tools run <capability_id> [--params <object>|--json <body>]');
|
|
266
273
|
if (values.json && values.params)
|
|
267
|
-
throw new CliUsageError('Use either --params for tool parameters or --json for the complete request, not both.');
|
|
274
|
+
throw new errors_1.CliUsageError('Use either --params for tool parameters or --json for the complete request, not both.');
|
|
268
275
|
if (!values.json && !values.params)
|
|
269
|
-
throw new CliUsageError('tools run requires --params <object> or --json <body>; fetch live schema first with tools get');
|
|
276
|
+
throw new errors_1.CliUsageError('tools run requires --params <object> or --json <body>; fetch live schema first with tools get');
|
|
270
277
|
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)
|
|
278
|
+
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
279
|
? json
|
|
273
280
|
: {};
|
|
274
281
|
if (values.params)
|
|
@@ -277,37 +284,75 @@ function parseTools(argv) {
|
|
|
277
284
|
body.params = json;
|
|
278
285
|
// The server never defaults a mode, so a mutating run with none is refused
|
|
279
286
|
// 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
287
|
const chosenMode = toolsRunMode(values);
|
|
283
288
|
if (chosenMode)
|
|
284
289
|
body.mode = chosenMode;
|
|
285
|
-
|
|
286
|
-
|
|
290
|
+
// agent_session_id selects the in-app Architect surface and the server refuses it for every
|
|
291
|
+
// other caller (an API key may name a session only while an Architect turn is running on it,
|
|
292
|
+
// which a CLI user never has). There is no flag for it, and a full --json body must not smuggle it.
|
|
293
|
+
if (body.agent_session_id !== undefined) {
|
|
294
|
+
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.');
|
|
295
|
+
}
|
|
287
296
|
if (values['approved-effect-unknown'])
|
|
288
297
|
body.approved_effect_unknown = true;
|
|
289
298
|
if (values['max-credits'] !== undefined)
|
|
290
299
|
body.max_credits = nonNegativeNumber(values['max-credits'], '--max-credits');
|
|
300
|
+
if (body.max_live_sends !== undefined) {
|
|
301
|
+
throw new errors_1.CliUsageError('max_live_sends is a capability parameter. Put it in params.max_live_sends or pass --max-live-sends.');
|
|
302
|
+
}
|
|
303
|
+
if (values['max-live-sends'] !== undefined) {
|
|
304
|
+
const params = body.params;
|
|
305
|
+
if (!params || typeof params !== 'object' || Array.isArray(params))
|
|
306
|
+
throw new errors_1.CliUsageError('tools run params must be an object');
|
|
307
|
+
params.max_live_sends = positiveInteger(values['max-live-sends'], '--max-live-sends');
|
|
308
|
+
}
|
|
309
|
+
if (values['quote-hash'] !== undefined) {
|
|
310
|
+
if (!runContract_1.QUOTE_HASH_PATTERN.test(values['quote-hash']))
|
|
311
|
+
throw new errors_1.CliUsageError('--quote-hash must be the sha256:<64 hex> quote_hash a dry run returned');
|
|
312
|
+
body.quote_hash = values['quote-hash'];
|
|
313
|
+
}
|
|
314
|
+
// --approved is the caller's assertion that it showed a person the dry run's
|
|
315
|
+
// rows and cost and they agreed. A paid run above the size or dollar
|
|
316
|
+
// threshold is refused without it. It only means something on a live run,
|
|
317
|
+
// so any other mode never sends it.
|
|
318
|
+
if (values.approved && body.mode === 'live')
|
|
319
|
+
body.approved = true;
|
|
320
|
+
// --approval-id names a recorded approval (a uuid the approvals capabilities returned)
|
|
321
|
+
// the door should honour for this run. It is a door input, never a param.
|
|
322
|
+
if (values['approval-id'] !== undefined) {
|
|
323
|
+
if (!UUID_PATTERN.test(values['approval-id']))
|
|
324
|
+
throw new errors_1.CliUsageError('--approval-id must be the uuid of a recorded approval');
|
|
325
|
+
body.approval_id = values['approval-id'];
|
|
326
|
+
}
|
|
291
327
|
// No client-side mode requirement: the server owns that refusal and answers
|
|
292
328
|
// mode_required with a repair, and the parse grammar is expected to accept a
|
|
293
329
|
// run without one. What was missing was any way to ASK for live.
|
|
294
330
|
if (body.mode !== undefined)
|
|
295
331
|
requireToolsRunMode(body);
|
|
296
332
|
const waitTimeout = values['wait-timeout'] === undefined ? 120 : nonNegativeNumber(values['wait-timeout'], '--wait-timeout');
|
|
333
|
+
if (values['idempotency-key'] !== undefined && values['idempotency-key'].length > runContract_1.IDEMPOTENCY_KEY_MAX_LENGTH) {
|
|
334
|
+
throw new errors_1.CliUsageError(`--idempotency-key must be at most ${runContract_1.IDEMPOTENCY_KEY_MAX_LENGTH} characters`);
|
|
335
|
+
}
|
|
297
336
|
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
337
|
}
|
|
299
|
-
throw new CliUsageError('Usage: dreamstate tools <search|get|run>');
|
|
338
|
+
throw new errors_1.CliUsageError('Usage: dreamstate tools <search|get|run>');
|
|
300
339
|
}
|
|
301
340
|
function parseTables(argv) {
|
|
341
|
+
// Servers before this release put --background on the import command they hand out. It
|
|
342
|
+
// never did anything (the import always ran to completion in this process), so 1.3.3
|
|
343
|
+
// dropped it; say so plainly instead of the generic "unknown option" error.
|
|
344
|
+
if (argv.includes('--background')) {
|
|
345
|
+
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.');
|
|
346
|
+
}
|
|
302
347
|
const { values, positionals } = (0, node_util_1.parseArgs)({ args: argv, options: {
|
|
303
|
-
'table-id': { type: 'string' }, file: { type: 'string' }, format: { type: 'string' },
|
|
348
|
+
'table-id': { type: 'string' }, file: { type: 'string' }, format: { type: 'string' },
|
|
304
349
|
preview: { type: 'boolean' }, 'upsert-key': { type: 'string' }, 'row-kind': { type: 'string' }, 'keep-unmapped-columns': { type: 'string' },
|
|
305
350
|
'json-output': { type: 'boolean' }, help: { type: 'boolean', short: 'h' },
|
|
306
351
|
}, allowPositionals: true, strict: true });
|
|
307
352
|
if (values.help)
|
|
308
353
|
return { command: 'help' };
|
|
309
354
|
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>]
|
|
355
|
+
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
356
|
}
|
|
312
357
|
// --format is an override, not a requirement. Requiring it made the caller restate
|
|
313
358
|
// what the file path already says, and an omitted flag produced a usage error rather
|
|
@@ -315,16 +360,16 @@ function parseTables(argv) {
|
|
|
315
360
|
// the kind from the file it receives.
|
|
316
361
|
const format = values.format;
|
|
317
362
|
if (format !== undefined && !['csv', 'json', 'jsonl', 'xlsx'].includes(format)) {
|
|
318
|
-
throw new CliUsageError('--format must be one of csv, json, jsonl, xlsx');
|
|
363
|
+
throw new errors_1.CliUsageError('--format must be one of csv, json, jsonl, xlsx');
|
|
319
364
|
}
|
|
320
365
|
const upsertKey = values['upsert-key']?.trim();
|
|
321
366
|
const rowKind = values['row-kind'];
|
|
322
367
|
if (rowKind !== undefined && rowKind !== 'person' && rowKind !== 'company') {
|
|
323
|
-
throw new CliUsageError('--row-kind must be person or company');
|
|
368
|
+
throw new errors_1.CliUsageError('--row-kind must be person or company');
|
|
324
369
|
}
|
|
325
370
|
const keepUnmappedColumns = values['keep-unmapped-columns'];
|
|
326
371
|
if (keepUnmappedColumns !== undefined && keepUnmappedColumns !== 'true' && keepUnmappedColumns !== 'false') {
|
|
327
|
-
throw new CliUsageError('--keep-unmapped-columns must be true or false');
|
|
372
|
+
throw new errors_1.CliUsageError('--keep-unmapped-columns must be true or false');
|
|
328
373
|
}
|
|
329
374
|
return {
|
|
330
375
|
command: 'tables_import',
|
|
@@ -335,14 +380,13 @@ function parseTables(argv) {
|
|
|
335
380
|
...(upsertKey ? { upsertKey } : {}),
|
|
336
381
|
...(rowKind ? { rowKind } : {}),
|
|
337
382
|
...(keepUnmappedColumns === 'false' ? { keepUnmappedColumns: false } : {}),
|
|
338
|
-
background: Boolean(values.background),
|
|
339
383
|
...outputOption(values),
|
|
340
384
|
};
|
|
341
385
|
}
|
|
342
386
|
function runsListLimit(raw) {
|
|
343
387
|
const value = nonNegativeInteger(raw, '--limit');
|
|
344
388
|
if (value < 1 || value > 500)
|
|
345
|
-
throw new CliUsageError('--limit must be an integer from 1 to 500');
|
|
389
|
+
throw new errors_1.CliUsageError('--limit must be an integer from 1 to 500');
|
|
346
390
|
return value;
|
|
347
391
|
}
|
|
348
392
|
function parseRuns(argv) {
|
|
@@ -357,30 +401,28 @@ function parseRuns(argv) {
|
|
|
357
401
|
if (values.cursor !== undefined) {
|
|
358
402
|
const cursor = values.cursor.trim();
|
|
359
403
|
if (!cursor)
|
|
360
|
-
throw new CliUsageError('--cursor must be a non-empty string');
|
|
404
|
+
throw new errors_1.CliUsageError('--cursor must be a non-empty string');
|
|
361
405
|
query.cursor = cursor;
|
|
362
406
|
}
|
|
363
407
|
return { command: 'runs_list', query, ...output };
|
|
364
408
|
}
|
|
365
409
|
if (values.limit !== undefined || values.cursor !== undefined) {
|
|
366
|
-
throw new CliUsageError('--limit and --cursor are only valid on runs list');
|
|
410
|
+
throw new errors_1.CliUsageError('--limit and --cursor are only valid on runs list');
|
|
411
|
+
}
|
|
412
|
+
if (positionals[0] === 'resume') {
|
|
413
|
+
// The server retired generic run resume (verifyExecutionDoorCollapse.ts blocks the
|
|
414
|
+
// route), so there is nothing to call. Say where the replacement is.
|
|
415
|
+
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
416
|
}
|
|
368
417
|
const runId = requireOne(positionals, `dreamstate runs ${positionals[0] ?? '<get|events|cancel>'} <run_id>`);
|
|
369
418
|
if (positionals[0] === 'get')
|
|
370
419
|
return { command: 'runs_get', runId, ...output };
|
|
371
420
|
if (positionals[0] === 'events')
|
|
372
421
|
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
422
|
if (positionals[0] === 'cancel') {
|
|
381
423
|
const idempotency = values['idempotency-key'] ? { idempotencyKey: values['idempotency-key'] } : {};
|
|
382
424
|
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.');
|
|
425
|
+
throw new errors_1.CliUsageError('Use either --json <body> or --state-version and --reason with runs cancel, not both.');
|
|
384
426
|
}
|
|
385
427
|
if (values.json)
|
|
386
428
|
return { command: 'runs_cancel', runId, body: parseJsonObject(values.json), ...idempotency, ...output };
|
|
@@ -393,7 +435,13 @@ function parseRuns(argv) {
|
|
|
393
435
|
...output,
|
|
394
436
|
};
|
|
395
437
|
}
|
|
396
|
-
throw new CliUsageError('Usage: dreamstate runs <get|events|cancel> <run_id>');
|
|
438
|
+
throw new errors_1.CliUsageError('Usage: dreamstate runs <list|get|events|cancel> <run_id>');
|
|
439
|
+
}
|
|
440
|
+
function sequencesCommands() {
|
|
441
|
+
return require('./commands/sequences');
|
|
442
|
+
}
|
|
443
|
+
function workflowsCommands() {
|
|
444
|
+
return require('./commands/workflows');
|
|
397
445
|
}
|
|
398
446
|
function normalizeJsonOutputAlias(argv) {
|
|
399
447
|
return argv.map((argument, index) => argument === '--json' && (index === argv.length - 1 || argv[index + 1]?.startsWith('-'))
|
|
@@ -412,9 +460,9 @@ function parseCliArgs(argv) {
|
|
|
412
460
|
if (values.help)
|
|
413
461
|
return { command: 'help' };
|
|
414
462
|
if (positionals.length)
|
|
415
|
-
throw new CliUsageError('Usage: dreamstate login [--token <ds_user_...|ds_live_...>]');
|
|
463
|
+
throw new errors_1.CliUsageError('Usage: dreamstate login [--token <ds_user_...|ds_live_...>]');
|
|
416
464
|
if (values.token !== undefined && values['api-key-file'])
|
|
417
|
-
throw new CliUsageError('Use either --token or --api-key-file, not both.');
|
|
465
|
+
throw new errors_1.CliUsageError('Use either --token or --api-key-file, not both.');
|
|
418
466
|
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
467
|
}
|
|
420
468
|
if (group === 'install') {
|
|
@@ -422,11 +470,11 @@ function parseCliArgs(argv) {
|
|
|
422
470
|
if (values.help)
|
|
423
471
|
return { command: 'help' };
|
|
424
472
|
if (positionals.length)
|
|
425
|
-
throw new CliUsageError('Usage: dreamstate install [--agents <claude codex grok>]');
|
|
473
|
+
throw new errors_1.CliUsageError('Usage: dreamstate install [--agents <claude codex grok>]');
|
|
426
474
|
const requested = values.agents ? [...new Set(values.agents.flatMap((raw) => csv(raw, '--agents')))] : [...pluginInstaller_1.PLUGIN_AGENTS];
|
|
427
475
|
const invalid = requested.filter((agent) => !pluginInstaller_1.PLUGIN_AGENTS.includes(agent));
|
|
428
476
|
if (invalid.length)
|
|
429
|
-
throw new CliUsageError(`--agents values must be one of: ${pluginInstaller_1.PLUGIN_AGENTS.join(', ')} (got: ${invalid.join(', ')})`);
|
|
477
|
+
throw new errors_1.CliUsageError(`--agents values must be one of: ${pluginInstaller_1.PLUGIN_AGENTS.join(', ')} (got: ${invalid.join(', ')})`);
|
|
430
478
|
return { command: 'install', agents: requested, ...outputOption(values) };
|
|
431
479
|
}
|
|
432
480
|
if (group === 'logout') {
|
|
@@ -434,14 +482,14 @@ function parseCliArgs(argv) {
|
|
|
434
482
|
if (values.help)
|
|
435
483
|
return { command: 'help' };
|
|
436
484
|
if (positionals.length)
|
|
437
|
-
throw new CliUsageError('Usage: dreamstate logout');
|
|
485
|
+
throw new errors_1.CliUsageError('Usage: dreamstate logout');
|
|
438
486
|
return { command: 'logout', ...outputOption(values) };
|
|
439
487
|
}
|
|
440
488
|
if (group === 'status') {
|
|
441
489
|
const { values, positionals } = (0, node_util_1.parseArgs)({ args: rest, options: { 'json-output': { type: 'boolean' } }, allowPositionals: true, strict: true });
|
|
442
490
|
if (!positionals.length)
|
|
443
491
|
return { command: 'status', ...outputOption(values) };
|
|
444
|
-
throw new CliUsageError('Usage: dreamstate status [--json-output]');
|
|
492
|
+
throw new errors_1.CliUsageError('Usage: dreamstate status [--json-output]');
|
|
445
493
|
}
|
|
446
494
|
if (group === 'profiles') {
|
|
447
495
|
const { values, positionals } = (0, node_util_1.parseArgs)({ args: rest, options: { 'json-output': { type: 'boolean' } }, allowPositionals: true, strict: true });
|
|
@@ -456,13 +504,13 @@ function parseCliArgs(argv) {
|
|
|
456
504
|
return { command: 'profiles_remove', name: (0, config_1.validateProfileName)(positionals[1]), ...output };
|
|
457
505
|
if (positionals.length === 2 && positionals[0] === 'env')
|
|
458
506
|
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>>');
|
|
507
|
+
throw new errors_1.CliUsageError('Usage: dreamstate profiles <list|use <name>|current|remove <name>|env <name>>');
|
|
460
508
|
}
|
|
461
509
|
if (group === 'whoami') {
|
|
462
510
|
const { values, positionals } = (0, node_util_1.parseArgs)({ args: rest, options: { 'json-output': { type: 'boolean' } }, allowPositionals: true, strict: true });
|
|
463
511
|
if (positionals.length === 0)
|
|
464
512
|
return { command: 'whoami', ...outputOption(values) };
|
|
465
|
-
throw new CliUsageError('Usage: dreamstate whoami [--json-output]');
|
|
513
|
+
throw new errors_1.CliUsageError('Usage: dreamstate whoami [--json-output]');
|
|
466
514
|
}
|
|
467
515
|
if (group === 'workspaces') {
|
|
468
516
|
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 +520,7 @@ function parseCliArgs(argv) {
|
|
|
472
520
|
return { command: 'workspaces_list', ...outputOption(values) };
|
|
473
521
|
if (positionals.length === 2 && positionals[0] === 'use' && positionals[1])
|
|
474
522
|
return { command: 'workspaces_use', workspace: positionals[1], ...outputOption(values) };
|
|
475
|
-
throw new CliUsageError('Usage: dreamstate workspaces <list|use <id|slug>>');
|
|
523
|
+
throw new errors_1.CliUsageError('Usage: dreamstate workspaces <list|use <id|slug>>');
|
|
476
524
|
}
|
|
477
525
|
if (group === 'auth') {
|
|
478
526
|
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 +528,7 @@ function parseCliArgs(argv) {
|
|
|
480
528
|
return { command: 'login', ...(values['api-key-file'] ? { apiKeyFile: values['api-key-file'] } : {}), ...(values['no-skills'] ? { noSkills: true } : {}), ...outputOption(values) };
|
|
481
529
|
if (positionals.length === 1 && positionals[0] === 'whoami')
|
|
482
530
|
return { command: 'whoami', ...outputOption(values) };
|
|
483
|
-
throw new CliUsageError('Usage: dreamstate auth <login|whoami>');
|
|
531
|
+
throw new errors_1.CliUsageError('Usage: dreamstate auth <login|whoami>');
|
|
484
532
|
}
|
|
485
533
|
if (group === 'packs')
|
|
486
534
|
return parsePacks(rest);
|
|
@@ -495,13 +543,17 @@ function parseCliArgs(argv) {
|
|
|
495
543
|
if (group === 'runs')
|
|
496
544
|
return parseRuns(rest);
|
|
497
545
|
if (group === 'sequences')
|
|
498
|
-
return
|
|
546
|
+
return sequencesCommands().parseSequencesCommand(rest);
|
|
499
547
|
if (group === 'workflows')
|
|
500
|
-
return
|
|
501
|
-
throw new CliUsageError(`Unknown command: ${group}`);
|
|
548
|
+
return workflowsCommands().parseWorkflowsCommand(rest);
|
|
549
|
+
throw new errors_1.CliUsageError(`Unknown command: ${group}`);
|
|
502
550
|
}
|
|
503
551
|
function print(value, stdout, compact) {
|
|
504
|
-
|
|
552
|
+
// Printed exactly as the server returned it. tool_turn_token used to be stripped
|
|
553
|
+
// here, which made --turn-token impossible to use: a caller could never read the
|
|
554
|
+
// token from `tools get` to pass back. The token is a short-lived fetch fence tied
|
|
555
|
+
// to the caller's own session, not a credential, and MCP and REST return it too.
|
|
556
|
+
stdout.write(`${JSON.stringify(value, null, compact ? 0 : 2)}\n`);
|
|
505
557
|
}
|
|
506
558
|
function printPackRoute(value, stdout) {
|
|
507
559
|
const data = value && typeof value === 'object' && !Array.isArray(value)
|
|
@@ -530,103 +582,32 @@ function printPackRoute(value, stdout) {
|
|
|
530
582
|
stdout.write(`${packId}${typeof reason === 'string' ? `: ${reason}` : ''}\n`);
|
|
531
583
|
}
|
|
532
584
|
}
|
|
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
585
|
/**
|
|
589
|
-
*
|
|
590
|
-
*
|
|
591
|
-
*
|
|
592
|
-
*
|
|
593
|
-
*
|
|
586
|
+
* The refusal envelope, in the shared shape { ok:false, error, meta }. The server's
|
|
587
|
+
* own message, retry decision, repair, details and meta (trace_id, server_version,
|
|
588
|
+
* minimum_cli_version, release_sha) are forwarded unchanged; retry_after and
|
|
589
|
+
* idempotency_key are request receipts, so they stay in meta. Process exit status
|
|
590
|
+
* remains process-level state: ToolEnvelopeMeta intentionally has no exit_code.
|
|
594
591
|
*/
|
|
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
592
|
function refusalEnvelope(error, exitCode) {
|
|
616
593
|
const cliError = error instanceof client_1.CliError ? error : null;
|
|
617
594
|
return {
|
|
618
595
|
ok: false,
|
|
619
|
-
executed: false,
|
|
620
596
|
error: {
|
|
621
|
-
code: code(error) ?? 'invalid_request',
|
|
597
|
+
code: (0, gatedRun_1.code)(error) ?? 'invalid_request',
|
|
622
598
|
message: error instanceof Error ? error.message : String(error),
|
|
623
|
-
...(cliError?.
|
|
599
|
+
...(cliError?.retryable === undefined ? {} : { retryable: cliError.retryable }),
|
|
624
600
|
// The client keeps the server's retry decision and repair; dropping them
|
|
625
601
|
// here left CLI automation without the recovery Architect and MCP get.
|
|
626
|
-
...(cliError?.retryable === undefined ? {} : { retryable: cliError.retryable }),
|
|
627
602
|
...(cliError?.repair === undefined ? {} : { repair: cliError.repair }),
|
|
603
|
+
...(cliError?.details === undefined ? {} : { details: cliError.details }),
|
|
604
|
+
},
|
|
605
|
+
meta: {
|
|
606
|
+
...(cliError?.meta ?? {}),
|
|
607
|
+
...(cliError?.traceId !== undefined && cliError.meta?.trace_id === undefined ? { trace_id: cliError.traceId } : {}),
|
|
608
|
+
...(cliError?.retryAfter !== undefined ? { retry_after: cliError.retryAfter } : {}),
|
|
609
|
+
...(cliError?.idempotencyKey !== undefined ? { idempotency_key: cliError.idempotencyKey } : {}),
|
|
628
610
|
},
|
|
629
|
-
meta: { exit_code: exitCode },
|
|
630
611
|
};
|
|
631
612
|
}
|
|
632
613
|
function openWebUrl(rawUrl, launch = node_child_process_1.spawn) {
|
|
@@ -687,7 +668,7 @@ function discoveredCapabilityId(value) {
|
|
|
687
668
|
.filter((candidate) => typeof candidate === 'string' && candidate.length > 0);
|
|
688
669
|
const ids = [...new Set(candidates)];
|
|
689
670
|
if (ids.length !== 1)
|
|
690
|
-
throw new CliUsageError('Local file import discovery must return exactly one capability.');
|
|
671
|
+
throw new errors_1.CliUsageError('Local file import discovery must return exactly one capability.');
|
|
691
672
|
return ids[0];
|
|
692
673
|
}
|
|
693
674
|
function hostedSkill(value) {
|
|
@@ -705,31 +686,85 @@ function hostedSkill(value) {
|
|
|
705
686
|
const documents = Array.isArray(entry.documents)
|
|
706
687
|
? entry.documents.filter((document) => Boolean(document) && typeof document === 'object' && typeof document.name === 'string' && typeof document.content === 'string')
|
|
707
688
|
: undefined;
|
|
708
|
-
|
|
689
|
+
// The server lists a skill's supporting pages beside the skill (data.files), each read
|
|
690
|
+
// with `skills get <id> --file <path>`; they are not inline in the skill record.
|
|
691
|
+
const files = Array.isArray(data.files)
|
|
692
|
+
? data.files
|
|
693
|
+
.filter((file) => Boolean(file) && typeof file === 'object' && typeof file.path === 'string')
|
|
694
|
+
.map((file) => ({ path: file.path, ...(typeof file.revision_number === 'number' ? { revision_number: file.revision_number } : {}) }))
|
|
695
|
+
: undefined;
|
|
696
|
+
return {
|
|
697
|
+
id: entry.id, name: entry.name, description: entry.description, body: entry.body, frontmatter, documents,
|
|
698
|
+
...(typeof entry.revision_number === 'number' ? { revision_number: entry.revision_number } : {}),
|
|
699
|
+
...(files ? { files } : {}),
|
|
700
|
+
};
|
|
701
|
+
}
|
|
702
|
+
const SAFE_SKILL_ID = /^[a-z0-9][a-z0-9_-]*$/i;
|
|
703
|
+
/**
|
|
704
|
+
* One hosted skill with its supporting pages read in. Reads are one at a time because
|
|
705
|
+
* the workspace's private vault admits two reads at once, and fifteen parallel reads
|
|
706
|
+
* failed in production.
|
|
707
|
+
*/
|
|
708
|
+
async function loadHostedSkill(client, id, withPages) {
|
|
709
|
+
if (!SAFE_SKILL_ID.test(id))
|
|
710
|
+
throw new errors_1.CliUsageError('Dreamstate API returned an invalid skill id.');
|
|
711
|
+
const skill = hostedSkill(await client.getSkill(id));
|
|
712
|
+
if (!SAFE_SKILL_ID.test(skill.id))
|
|
713
|
+
throw new errors_1.CliUsageError('Dreamstate API returned an invalid skill id.');
|
|
714
|
+
if (skill.id !== id)
|
|
715
|
+
throw new client_1.CliError('Dreamstate API returned a mismatched skill id.', 'invalid_response', 200);
|
|
716
|
+
if (!withPages || !skill.files?.length)
|
|
717
|
+
return skill;
|
|
718
|
+
const documents = [];
|
|
719
|
+
for (const file of skill.files) {
|
|
720
|
+
const page = hostedSkill(await client.getSkill(id, { file: file.path }));
|
|
721
|
+
documents.push({ name: file.path, content: page.body });
|
|
722
|
+
}
|
|
723
|
+
return { ...skill, documents };
|
|
724
|
+
}
|
|
725
|
+
/** Ids of every skill the server lists. */
|
|
726
|
+
async function listedSkillIds(client) {
|
|
727
|
+
const listed = await client.listAllSkills();
|
|
728
|
+
const entries = listed && typeof listed === 'object' && listed.data?.skills;
|
|
729
|
+
const ids = Array.isArray(entries) ? entries.map((skill) => skill && typeof skill === 'object' ? skill.id : undefined).filter((id) => typeof id === 'string') : [];
|
|
730
|
+
if (ids.some((id) => !SAFE_SKILL_ID.test(id)))
|
|
731
|
+
throw new errors_1.CliUsageError('Dreamstate API returned an invalid skill id.');
|
|
732
|
+
return ids;
|
|
709
733
|
}
|
|
710
734
|
function exitCodeForError(error) {
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
735
|
+
const errorCode = (0, gatedRun_1.code)(error);
|
|
736
|
+
if (errorCode?.startsWith('ERR_PARSE_ARGS'))
|
|
737
|
+
return errors_1.CLI_EXIT.USAGE;
|
|
738
|
+
if (error instanceof errors_1.CliUsageError || error instanceof config_1.ApiKeyFileError || error instanceof config_1.ApiBaseUrlError || error instanceof config_1.MissingApiUrlError)
|
|
739
|
+
return errors_1.CLI_EXIT.USAGE;
|
|
715
740
|
if (error instanceof config_1.MissingApiKeyError)
|
|
716
|
-
return
|
|
717
|
-
|
|
718
|
-
|
|
741
|
+
return errors_1.CLI_EXIT.AUTH;
|
|
742
|
+
// By code before by status: a gate refusal arrives as 403 or 409 and must still read as
|
|
743
|
+
// "a person has to decide", and an update-required refusal is 426.
|
|
744
|
+
if (errorCode === 'cli_update_required')
|
|
745
|
+
return errors_1.CLI_EXIT.UPDATE_REQUIRED;
|
|
746
|
+
// Local transport failures stamp repair.kind ask_user (read the target back,
|
|
747
|
+
// then ask a person). Their published exits win over that repair.
|
|
748
|
+
if (errorCode === 'request_timeout')
|
|
749
|
+
return errors_1.CLI_EXIT.TIMEOUT;
|
|
750
|
+
if (errorCode === 'network_error')
|
|
751
|
+
return errors_1.CLI_EXIT.TRANSPORT;
|
|
752
|
+
if ((0, errors_1.isLocalRefusalCode)(errorCode))
|
|
753
|
+
return errors_1.CLI_EXIT.REFUSED;
|
|
754
|
+
if ((0, errors_1.isApprovalRepair)(error))
|
|
755
|
+
return errors_1.CLI_EXIT.APPROVAL;
|
|
719
756
|
const status = error instanceof client_1.CliError ? error.status : 0;
|
|
720
757
|
if (status === 401 || status === 403)
|
|
721
|
-
return
|
|
758
|
+
return errors_1.CLI_EXIT.AUTH;
|
|
722
759
|
if (status === 404)
|
|
723
|
-
return
|
|
760
|
+
return errors_1.CLI_EXIT.NOT_FOUND;
|
|
724
761
|
if (status === 429)
|
|
725
|
-
return
|
|
726
|
-
if (
|
|
727
|
-
return
|
|
728
|
-
if (status >= 500 || code(error) === 'invalid_response')
|
|
729
|
-
return exports.CLI_EXIT.SERVER;
|
|
762
|
+
return errors_1.CLI_EXIT.RATE_LIMITED;
|
|
763
|
+
if (status >= 500 || errorCode === 'invalid_response')
|
|
764
|
+
return errors_1.CLI_EXIT.SERVER;
|
|
730
765
|
if (status >= 400)
|
|
731
|
-
return
|
|
732
|
-
return
|
|
766
|
+
return errors_1.CLI_EXIT.REFUSED;
|
|
767
|
+
return errors_1.CLI_EXIT.UNCLASSIFIED;
|
|
733
768
|
}
|
|
734
769
|
async function readLoginKey(command, runtime) {
|
|
735
770
|
if (command.apiKeyFile)
|
|
@@ -747,7 +782,7 @@ async function readLoginKey(command, runtime) {
|
|
|
747
782
|
}
|
|
748
783
|
if (runtime.promptHidden)
|
|
749
784
|
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.');
|
|
785
|
+
throw new errors_1.CliUsageError('No API key provided. Pipe it on stdin, set DREAMSTATE_API_KEY, or pass --api-key-file.');
|
|
751
786
|
}
|
|
752
787
|
function deviceState() {
|
|
753
788
|
return (0, node_crypto_1.randomBytes)(32).toString('base64url');
|
|
@@ -767,14 +802,14 @@ function extractWorkspaceFlag(argv) {
|
|
|
767
802
|
if (argument === '--workspace') {
|
|
768
803
|
const value = argv[index + 1];
|
|
769
804
|
if (value === undefined || value.startsWith('-') || !value.trim())
|
|
770
|
-
throw new CliUsageError('--workspace requires a workspace id or slug.');
|
|
805
|
+
throw new errors_1.CliUsageError('--workspace requires a workspace id or slug.');
|
|
771
806
|
workspace = value.trim();
|
|
772
807
|
index += 1;
|
|
773
808
|
}
|
|
774
809
|
else if (argument.startsWith('--workspace=')) {
|
|
775
810
|
workspace = argument.slice('--workspace='.length).trim();
|
|
776
811
|
if (!workspace)
|
|
777
|
-
throw new CliUsageError('--workspace requires a workspace id or slug.');
|
|
812
|
+
throw new errors_1.CliUsageError('--workspace requires a workspace id or slug.');
|
|
778
813
|
}
|
|
779
814
|
else {
|
|
780
815
|
rest.push(argument);
|
|
@@ -786,25 +821,28 @@ function whoamiData(value) {
|
|
|
786
821
|
const data = value && typeof value === 'object' ? value.data : undefined;
|
|
787
822
|
return data && typeof data === 'object' ? data : {};
|
|
788
823
|
}
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
const
|
|
798
|
-
const
|
|
799
|
-
const
|
|
800
|
-
const compatible = !minimum || minimumMajor < cliMajor || (minimumMajor === cliMajor && (minimumMinor < cliMinor || (minimumMinor === cliMinor && minimumPatch <= cliPatch)));
|
|
824
|
+
/**
|
|
825
|
+
* What `dreamstate status` reports. The release identity comes from the server's
|
|
826
|
+
* own answers (envelope meta and X-Dreamstate-* headers on every response), so
|
|
827
|
+
* skew is measured, not assumed: with a server that publishes nothing the skew is
|
|
828
|
+
* "unknown", never "compatible".
|
|
829
|
+
*/
|
|
830
|
+
function statusData(whoami, health, info) {
|
|
831
|
+
// health.data.version is not the release. server_version comes only from envelope meta.
|
|
832
|
+
const serverVersion = info.server_version;
|
|
833
|
+
const skew = (0, client_1.describeVersionSkew)(info);
|
|
834
|
+
const compatible = skew !== 'cli_below_minimum';
|
|
801
835
|
return {
|
|
802
836
|
cli_version: client_1.CLI_VERSION,
|
|
803
|
-
api_version:
|
|
804
|
-
|
|
805
|
-
|
|
837
|
+
api_version: serverVersion,
|
|
838
|
+
server_version: serverVersion,
|
|
839
|
+
registry_hash: info.registry_hash,
|
|
840
|
+
release_sha: info.release_sha,
|
|
841
|
+
minimum_cli_version: info.minimum_cli_version,
|
|
842
|
+
minimum_cli_version_published: info.minimum_cli_version !== null,
|
|
843
|
+
skew,
|
|
806
844
|
compatible,
|
|
807
|
-
upgrade_hint:
|
|
845
|
+
upgrade_hint: skew === 'cli_below_minimum' || skew === 'cli_behind_server' ? `Run: ${client_1.CLI_UPDATE_COMMAND}` : null,
|
|
808
846
|
health,
|
|
809
847
|
whoami: whoamiData(whoami),
|
|
810
848
|
};
|
|
@@ -816,36 +854,31 @@ function activeWorkspace(data) {
|
|
|
816
854
|
function wait(milliseconds) {
|
|
817
855
|
return new Promise((resolve) => setTimeout(resolve, milliseconds));
|
|
818
856
|
}
|
|
857
|
+
/**
|
|
858
|
+
* The one way the CLI installs skills: every skill from the hosted catalog (the same
|
|
859
|
+
* records MCP skills_get and `skills get` read), with its supporting pages, plus the
|
|
860
|
+
* router. `skills install`, `login` and the single-skill form all come through here, so
|
|
861
|
+
* there is no second source of skill text on this machine.
|
|
862
|
+
*/
|
|
819
863
|
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') : [];
|
|
864
|
+
const ids = options.only ?? await listedSkillIds(client);
|
|
823
865
|
if (!ids.length)
|
|
824
866
|
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
867
|
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
|
-
}
|
|
868
|
+
for (const id of ids)
|
|
869
|
+
skills.push(await loadHostedSkill(client, id, true));
|
|
836
870
|
const packs = await client.listPacks();
|
|
837
871
|
const whoami = await client.getWhoami();
|
|
838
872
|
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)], {
|
|
873
|
+
const all = [...skills, (0, skillInstaller_1.routerSkill)(packs)];
|
|
874
|
+
// Stamped into every file so a stale install is visible against the live skills.
|
|
875
|
+
const installed = await (0, skillInstaller_1.installSkills)(all, {
|
|
843
876
|
agents,
|
|
844
877
|
homeDirectory: runtime.homeDirectory,
|
|
845
878
|
project: Boolean(options.project),
|
|
846
879
|
copy: Boolean(options.copy) || process.platform === 'win32',
|
|
847
880
|
workspaceId,
|
|
848
|
-
indexVersion,
|
|
881
|
+
indexVersion: (0, skillInstaller_1.skillIndexVersion)(all),
|
|
849
882
|
});
|
|
850
883
|
return { files: installed.files, skillIds: [...ids, 'router'] };
|
|
851
884
|
}
|
|
@@ -930,12 +963,12 @@ async function runCli(argv = process.argv.slice(2), runtime = {}) {
|
|
|
930
963
|
if (command.command === 'help' || command.command === 'pack_help') {
|
|
931
964
|
const packHelp = command.command === 'pack_help' ? command : undefined;
|
|
932
965
|
print({ ok: true, data: { usage: packHelp?.usage ?? USAGE }, meta: {} }, stdout, Boolean(packHelp?.jsonOutput));
|
|
933
|
-
process.exitCode =
|
|
966
|
+
process.exitCode = errors_1.CLI_EXIT.SUCCESS;
|
|
934
967
|
return;
|
|
935
968
|
}
|
|
936
969
|
if (command.command === 'version') {
|
|
937
970
|
print({ ok: true, data: { version: client_1.CLI_VERSION }, meta: {} }, stdout, false);
|
|
938
|
-
process.exitCode =
|
|
971
|
+
process.exitCode = errors_1.CLI_EXIT.SUCCESS;
|
|
939
972
|
return;
|
|
940
973
|
}
|
|
941
974
|
if (command.command === 'install') {
|
|
@@ -948,7 +981,7 @@ async function runCli(argv = process.argv.slice(2), runtime = {}) {
|
|
|
948
981
|
const removedSkills = done.includes('claude') ? await (0, pluginInstaller_1.removeClaudeSkillCopies)(runtime.homeDirectory) : [];
|
|
949
982
|
stderr.write(`Dreamstate plugin installed for ${done.join(', ')}. Start a new session; the first Dreamstate tool call asks you to sign in.\n`);
|
|
950
983
|
print({ ok: true, data: { ...installed, removed_skill_copies: removedSkills }, meta: {} }, stdout, Boolean(command.jsonOutput));
|
|
951
|
-
process.exitCode =
|
|
984
|
+
process.exitCode = errors_1.CLI_EXIT.SUCCESS;
|
|
952
985
|
return;
|
|
953
986
|
}
|
|
954
987
|
if (command.command === 'logout') {
|
|
@@ -967,7 +1000,7 @@ async function runCli(argv = process.argv.slice(2), runtime = {}) {
|
|
|
967
1000
|
const message = removed ? `Removed ${path}` : `Already logged out: no credentials at ${path}`;
|
|
968
1001
|
stderr.write(`${message}\n`);
|
|
969
1002
|
print({ ok: true, data: { credentials_path: path, removed }, meta: {} }, stdout, Boolean(command.jsonOutput));
|
|
970
|
-
process.exitCode =
|
|
1003
|
+
process.exitCode = errors_1.CLI_EXIT.SUCCESS;
|
|
971
1004
|
return;
|
|
972
1005
|
}
|
|
973
1006
|
if (command.command === 'profiles_list' || command.command === 'profiles_current' || command.command === 'profiles_use' || command.command === 'profiles_remove' || command.command === 'profiles_env') {
|
|
@@ -992,20 +1025,20 @@ async function runCli(argv = process.argv.slice(2), runtime = {}) {
|
|
|
992
1025
|
else {
|
|
993
1026
|
const profiles = await (0, config_1.listCredentialProfiles)(path);
|
|
994
1027
|
if (!profiles.some((profile) => profile.name === command.name))
|
|
995
|
-
throw new CliUsageError(`Profile not found: ${command.name}.`);
|
|
1028
|
+
throw new errors_1.CliUsageError(`Profile not found: ${command.name}.`);
|
|
996
1029
|
const result = { ok: true, data: { profile: command.name, exports: [`DREAMSTATE_PROFILE=${command.name}`] }, meta: {} };
|
|
997
1030
|
if (command.jsonOutput)
|
|
998
1031
|
print(result, stdout, true);
|
|
999
1032
|
else
|
|
1000
1033
|
stdout.write(`export DREAMSTATE_PROFILE=${command.name}\n`);
|
|
1001
1034
|
}
|
|
1002
|
-
process.exitCode =
|
|
1035
|
+
process.exitCode = errors_1.CLI_EXIT.SUCCESS;
|
|
1003
1036
|
return;
|
|
1004
1037
|
}
|
|
1005
1038
|
if (command.command === 'login' && command.token !== undefined) {
|
|
1006
1039
|
const token = command.token.trim();
|
|
1007
1040
|
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_.');
|
|
1041
|
+
throw new errors_1.CliUsageError('--token must be a Dreamstate session or API key starting with ds_user_ or ds_live_.');
|
|
1009
1042
|
const environment = runtime.env ?? process.env;
|
|
1010
1043
|
const baseUrl = (0, config_1.resolveApiBaseUrl)(environment);
|
|
1011
1044
|
// Only an explicit flag or env picks the workspace here: a stored default belongs to the previous login.
|
|
@@ -1024,7 +1057,7 @@ async function runCli(argv = process.argv.slice(2), runtime = {}) {
|
|
|
1024
1057
|
await (0, config_1.storeApiKey)(token, runtimeCredentialsPath(runtime));
|
|
1025
1058
|
const skills = await installLoginSkills(client, runtime, Boolean(command.noSkills), stderr);
|
|
1026
1059
|
print(loginResult(whoami, skills), stdout, Boolean(command.jsonOutput));
|
|
1027
|
-
process.exitCode =
|
|
1060
|
+
process.exitCode = errors_1.CLI_EXIT.SUCCESS;
|
|
1028
1061
|
return;
|
|
1029
1062
|
}
|
|
1030
1063
|
if (command.command === 'status') {
|
|
@@ -1033,27 +1066,47 @@ async function runCli(argv = process.argv.slice(2), runtime = {}) {
|
|
|
1033
1066
|
const apiKey = await (0, config_1.resolveApiKey)(environment, credentialFilePath);
|
|
1034
1067
|
const workspace = await (0, config_1.resolveWorkspace)(extracted.workspace, environment, credentialFilePath);
|
|
1035
1068
|
const client = new client_1.DreamstateClient({ baseUrl: (0, config_1.resolveApiBaseUrl)(environment), apiKey, ...(workspace ? { workspace } : {}), requestTimeoutMs: runtime.requestTimeoutMs });
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1069
|
+
let whoami = null;
|
|
1070
|
+
let health = null;
|
|
1071
|
+
let refusedAsTooOld = null;
|
|
1072
|
+
try {
|
|
1073
|
+
[whoami, health] = await Promise.all([client.getStatusWhoami(), client.getHealth()]);
|
|
1074
|
+
}
|
|
1075
|
+
catch (error) {
|
|
1076
|
+
// A server that refuses this CLI as too old (426) is the clearest skew report there is:
|
|
1077
|
+
// print it as a status, with the update command, instead of only an error.
|
|
1078
|
+
if (!(error instanceof client_1.CliError) || error.code !== 'cli_update_required')
|
|
1079
|
+
throw error;
|
|
1080
|
+
refusedAsTooOld = error;
|
|
1081
|
+
}
|
|
1082
|
+
const info = client.serverInfo;
|
|
1083
|
+
const refusal = refusedAsTooOld?.details && typeof refusedAsTooOld.details === 'object' ? refusedAsTooOld.details : {};
|
|
1084
|
+
const data = statusData(whoami, health, {
|
|
1085
|
+
...info,
|
|
1086
|
+
minimum_cli_version: info.minimum_cli_version ?? (typeof refusal.minimum_cli_version === 'string' ? refusal.minimum_cli_version : null),
|
|
1087
|
+
});
|
|
1088
|
+
print({ ok: true, data, meta: {} }, stdout, Boolean(command.jsonOutput));
|
|
1089
|
+
process.exitCode = data.skew === 'cli_below_minimum' ? errors_1.CLI_EXIT.UPDATE_REQUIRED : errors_1.CLI_EXIT.SUCCESS;
|
|
1090
|
+
if (process.exitCode === errors_1.CLI_EXIT.UPDATE_REQUIRED)
|
|
1091
|
+
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
1092
|
return;
|
|
1040
1093
|
}
|
|
1041
1094
|
if (command.command === 'login') {
|
|
1042
1095
|
if (command.apiKeyFile || (runtime.env ?? process.env).DREAMSTATE_API_KEY || runtime.promptHidden) {
|
|
1043
1096
|
const key = await readLoginKey(command, runtime);
|
|
1044
1097
|
if (!(0, config_1.isDreamstateToken)(key))
|
|
1045
|
-
throw new CliUsageError('Dreamstate API keys must start with ds_user_ or ds_live_.');
|
|
1098
|
+
throw new errors_1.CliUsageError('Dreamstate API keys must start with ds_user_ or ds_live_.');
|
|
1046
1099
|
const path = command.profile
|
|
1047
1100
|
? await (0, config_1.storeCredentialProfile)(command.profile, key, runtimeCredentialsPath(runtime))
|
|
1048
1101
|
: await (0, config_1.storeApiKey)(key, runtimeCredentialsPath(runtime));
|
|
1049
1102
|
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
1103
|
const skills = await installLoginSkills(client, runtime, Boolean(command.noSkills), stderr);
|
|
1051
1104
|
print({ ok: true, data: { credentials_path: path, skills }, meta: {} }, stdout, Boolean(command.jsonOutput));
|
|
1052
|
-
process.exitCode =
|
|
1105
|
+
process.exitCode = errors_1.CLI_EXIT.SUCCESS;
|
|
1053
1106
|
return;
|
|
1054
1107
|
}
|
|
1055
1108
|
await loginWithDeviceAuthorization(runtime, stdout, stderr, command.profile, Boolean(command.noSkills), Boolean(command.jsonOutput));
|
|
1056
|
-
process.exitCode =
|
|
1109
|
+
process.exitCode = errors_1.CLI_EXIT.SUCCESS;
|
|
1057
1110
|
return;
|
|
1058
1111
|
}
|
|
1059
1112
|
if (command.command === 'skills_doctor') {
|
|
@@ -1065,8 +1118,56 @@ async function runCli(argv = process.argv.slice(2), runtime = {}) {
|
|
|
1065
1118
|
const client = new client_1.DreamstateClient({ baseUrl, apiKey, ...(workspace ? { workspace } : {}), requestTimeoutMs: runtime.requestTimeoutMs });
|
|
1066
1119
|
await client.listSkills({ limit: 1 });
|
|
1067
1120
|
const installers = await (0, skillInstaller_1.inspectSkillInstallers)(command.agents, runtime.homeDirectory, Boolean(command.project));
|
|
1068
|
-
|
|
1069
|
-
|
|
1121
|
+
// Age says nothing about whether the install matches the server. Compare the content
|
|
1122
|
+
// stamp of every installed skill with the live skill's, the way install computes it.
|
|
1123
|
+
const ids = await listedSkillIds(client);
|
|
1124
|
+
const live = [];
|
|
1125
|
+
for (const id of ids)
|
|
1126
|
+
live.push(await loadHostedSkill(client, id, false));
|
|
1127
|
+
live.push((0, skillInstaller_1.routerSkill)(await client.listPacks()));
|
|
1128
|
+
const drift = await (0, skillInstaller_1.compareInstalledSkills)(live, runtime.homeDirectory, Boolean(command.project));
|
|
1129
|
+
const pluginAgents = await (0, pluginInstaller_1.agentsWithDreamstatePlugin)(runtime.homeDirectory);
|
|
1130
|
+
const duplicateSources = pluginAgents.filter((agent) => command.agents.includes(agent) && drift.installed_skill_ids.length > 0);
|
|
1131
|
+
print({ ok: true, data: { index_reachable: true, installers, content: drift, plugin_agents: pluginAgents, ...(duplicateSources.length ? { duplicate_skill_sources: duplicateSources } : {}) }, meta: {} }, stdout, Boolean(command.jsonOutput));
|
|
1132
|
+
process.exitCode = errors_1.CLI_EXIT.SUCCESS;
|
|
1133
|
+
return;
|
|
1134
|
+
}
|
|
1135
|
+
// The CLI's own commands answer from its parser tables, so they need no key;
|
|
1136
|
+
// matching capabilities are added when a key is available.
|
|
1137
|
+
if (command.command === 'commands_search') {
|
|
1138
|
+
const commands = (0, commandTable_1.searchCliCommands)(command.term, USAGE);
|
|
1139
|
+
let capabilities = null;
|
|
1140
|
+
let capabilitiesError = null;
|
|
1141
|
+
try {
|
|
1142
|
+
const environment = runtime.env ?? process.env;
|
|
1143
|
+
const credentialFilePath = runtimeCredentialsPath(runtime);
|
|
1144
|
+
const apiKey = await (0, config_1.resolveApiKey)(environment, credentialFilePath);
|
|
1145
|
+
const workspace = await (0, config_1.resolveWorkspace)(extracted.workspace, environment, credentialFilePath);
|
|
1146
|
+
const client = new client_1.DreamstateClient({
|
|
1147
|
+
baseUrl: (0, config_1.resolveApiBaseUrl)(environment),
|
|
1148
|
+
apiKey,
|
|
1149
|
+
...(workspace ? { workspace } : {}),
|
|
1150
|
+
requestTimeoutMs: runtime.requestTimeoutMs,
|
|
1151
|
+
onVersionWarning: (message) => stderr.write(`${message}\n`),
|
|
1152
|
+
});
|
|
1153
|
+
capabilities = await client.listTools({ goal: command.term });
|
|
1154
|
+
}
|
|
1155
|
+
catch (error) {
|
|
1156
|
+
if (!commands.length || (error instanceof client_1.CliError && error.code === 'cli_update_required'))
|
|
1157
|
+
throw error;
|
|
1158
|
+
capabilitiesError = { code: (0, gatedRun_1.code)(error) ?? 'request_failed', message: error instanceof Error ? error.message : String(error) };
|
|
1159
|
+
}
|
|
1160
|
+
print({
|
|
1161
|
+
ok: true,
|
|
1162
|
+
data: {
|
|
1163
|
+
commands,
|
|
1164
|
+
// Catalog capabilities that match the same words; run one with `dreamstate tools run <capability_id>`.
|
|
1165
|
+
capabilities: capabilities?.data ?? null,
|
|
1166
|
+
...(capabilitiesError ? { capabilities_error: capabilitiesError } : {}),
|
|
1167
|
+
},
|
|
1168
|
+
meta: capabilities?.meta && typeof capabilities.meta === 'object' ? capabilities.meta : {},
|
|
1169
|
+
}, stdout, Boolean(command.jsonOutput));
|
|
1170
|
+
process.exitCode = errors_1.CLI_EXIT.SUCCESS;
|
|
1070
1171
|
return;
|
|
1071
1172
|
}
|
|
1072
1173
|
// Preview parses the local file and writes nothing, so it must not require a key.
|
|
@@ -1081,23 +1182,21 @@ async function runCli(argv = process.argv.slice(2), runtime = {}) {
|
|
|
1081
1182
|
}, stdout, Boolean(command.jsonOutput));
|
|
1082
1183
|
}
|
|
1083
1184
|
catch (error) {
|
|
1084
|
-
throw new CliUsageError(error instanceof CliUsageError ? error.message : error instanceof Error ? error.message : String(error));
|
|
1185
|
+
throw new errors_1.CliUsageError(error instanceof errors_1.CliUsageError ? error.message : error instanceof Error ? error.message : String(error));
|
|
1085
1186
|
}
|
|
1086
|
-
process.exitCode =
|
|
1187
|
+
process.exitCode = errors_1.CLI_EXIT.SUCCESS;
|
|
1087
1188
|
return;
|
|
1088
1189
|
}
|
|
1089
1190
|
// init writes a local scaffold and calls no API, so it must not require a key.
|
|
1090
1191
|
// Resolving credentials first would refuse it with not_authenticated.
|
|
1091
1192
|
if (command.command === 'pack' && command.verb === 'init') {
|
|
1092
1193
|
if (command.pack === 'sequences') {
|
|
1093
|
-
await
|
|
1094
|
-
.runSequencesCommand(undefined, command, stdout);
|
|
1194
|
+
await sequencesCommands().runSequencesCommand(undefined, command, stdout);
|
|
1095
1195
|
}
|
|
1096
1196
|
else {
|
|
1097
|
-
await
|
|
1098
|
-
.runWorkflowsCommand(undefined, command, stdout);
|
|
1197
|
+
await workflowsCommands().runWorkflowsCommand(undefined, command, stdout);
|
|
1099
1198
|
}
|
|
1100
|
-
process.exitCode =
|
|
1199
|
+
process.exitCode = errors_1.CLI_EXIT.SUCCESS;
|
|
1101
1200
|
return;
|
|
1102
1201
|
}
|
|
1103
1202
|
const environment = runtime.env ?? process.env;
|
|
@@ -1117,7 +1216,7 @@ async function runCli(argv = process.argv.slice(2), runtime = {}) {
|
|
|
1117
1216
|
}
|
|
1118
1217
|
if (command.command === 'workspaces_use' && apiKey.startsWith('ds_live_')) {
|
|
1119
1218
|
// 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.');
|
|
1219
|
+
throw new errors_1.CliUsageError('This key is bound to one workspace. Run dreamstate login for a user session that can switch.');
|
|
1121
1220
|
}
|
|
1122
1221
|
const workspace = command.command === 'workspaces_use'
|
|
1123
1222
|
? command.workspace
|
|
@@ -1129,6 +1228,19 @@ async function runCli(argv = process.argv.slice(2), runtime = {}) {
|
|
|
1129
1228
|
requestTimeoutMs: runtime.requestTimeoutMs,
|
|
1130
1229
|
onVersionWarning: (message) => (runtime.stderr ?? process.stderr).write(`${message}\n`),
|
|
1131
1230
|
});
|
|
1231
|
+
const turnsPath = (0, turnCache_1.turnCachePath)(credentialFilePath);
|
|
1232
|
+
const idempotencyPath = (0, idempotency_1.pendingIdempotencyPath)(credentialFilePath);
|
|
1233
|
+
// A turn belongs to one workspace, so the workspace is part of the cache identity.
|
|
1234
|
+
const gatedContext = () => ({
|
|
1235
|
+
client,
|
|
1236
|
+
baseUrl,
|
|
1237
|
+
turnKey: workspace ? `${apiKey}\n${workspace}` : apiKey,
|
|
1238
|
+
turnsPath,
|
|
1239
|
+
idempotencyPath,
|
|
1240
|
+
...(workspace ? { workspace } : {}),
|
|
1241
|
+
stderr,
|
|
1242
|
+
sleep: runtime.sleep ?? wait,
|
|
1243
|
+
});
|
|
1132
1244
|
let result;
|
|
1133
1245
|
switch (command.command) {
|
|
1134
1246
|
case 'whoami': {
|
|
@@ -1149,7 +1261,7 @@ async function runCli(argv = process.argv.slice(2), runtime = {}) {
|
|
|
1149
1261
|
stdout.write(rows.length
|
|
1150
1262
|
? `${rows.map((row) => `${row.id === activeId ? '*' : ' '} ${row.slug ?? '-'} ${row.name} ${row.role ?? '-'} ${row.id}`).join('\n')}\n`
|
|
1151
1263
|
: 'No workspaces. Ask a workspace owner to invite you.\n');
|
|
1152
|
-
process.exitCode =
|
|
1264
|
+
process.exitCode = errors_1.CLI_EXIT.SUCCESS;
|
|
1153
1265
|
return;
|
|
1154
1266
|
}
|
|
1155
1267
|
break;
|
|
@@ -1160,10 +1272,10 @@ async function runCli(argv = process.argv.slice(2), runtime = {}) {
|
|
|
1160
1272
|
const wanted = command.workspace.toLowerCase();
|
|
1161
1273
|
const match = data.workspaces?.find((row) => row.id.toLowerCase() === wanted || row.slug?.toLowerCase() === wanted);
|
|
1162
1274
|
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.`);
|
|
1275
|
+
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
1276
|
const stored = await (0, config_1.readStoredCredentials)(await (0, config_1.selectedCredentialsPath)(credentialFilePath, environment));
|
|
1165
1277
|
if (!stored)
|
|
1166
|
-
throw new CliUsageError('workspaces use saves the default next to a stored login. Run dreamstate login or dreamstate login --token first.');
|
|
1278
|
+
throw new errors_1.CliUsageError('workspaces use saves the default next to a stored login. Run dreamstate login or dreamstate login --token first.');
|
|
1167
1279
|
await (0, config_1.storeApiKey)(stored.token, await (0, config_1.selectedCredentialsPath)(credentialFilePath, environment), match.id);
|
|
1168
1280
|
stderr.write(`Default workspace: ${match.name} (${match.slug})\n`);
|
|
1169
1281
|
result = { ok: true, data: { workspace: { id: match.id, name: match.name, slug: match.slug }, credentials_path: credentialFilePath }, meta: {} };
|
|
@@ -1188,47 +1300,26 @@ async function runCli(argv = process.argv.slice(2), runtime = {}) {
|
|
|
1188
1300
|
result = await client.searchSkillDocuments(command.query);
|
|
1189
1301
|
break;
|
|
1190
1302
|
case 'skills_install': {
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1303
|
+
// An agent that already loads Dreamstate skills from the plugin gets them from there:
|
|
1304
|
+
// installing hosted copies too would load the same skill twice under two names, from
|
|
1305
|
+
// two sources that drift apart. login skips such agents the same way.
|
|
1306
|
+
const withPlugin = await (0, pluginInstaller_1.agentsWithDreamstatePlugin)(runtime.homeDirectory);
|
|
1307
|
+
const targets = command.agents.filter((agent) => !withPlugin.includes(agent));
|
|
1308
|
+
const skipped = command.agents.filter((agent) => !targets.includes(agent)).map((agent) => ({ agent, reason: 'plugin_installed' }));
|
|
1309
|
+
if (skipped.length)
|
|
1310
|
+
stderr.write(`Skipped ${skipped.map((entry) => entry.agent).join(', ')}: the Dreamstate plugin already provides skills there. Update it with dreamstate install.\n`);
|
|
1311
|
+
if (!targets.length) {
|
|
1312
|
+
result = { ok: true, data: { installed_files: [], skill_ids: [], agents: [], skipped_agents: skipped }, meta: {} };
|
|
1194
1313
|
break;
|
|
1195
1314
|
}
|
|
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,
|
|
1315
|
+
const installed = await installHostedSkills(client, targets, runtime, {
|
|
1316
|
+
project: command.project,
|
|
1317
|
+
copy: command.copy,
|
|
1318
|
+
...(command.skillId === '*' ? {} : { only: [command.skillId] }),
|
|
1225
1319
|
});
|
|
1226
|
-
result = { ok: true, data: { installed_files: installed.files, skill_ids:
|
|
1320
|
+
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
1321
|
break;
|
|
1228
1322
|
}
|
|
1229
|
-
case 'commands_search':
|
|
1230
|
-
result = await client.listTools({ goal: command.term });
|
|
1231
|
-
break;
|
|
1232
1323
|
case 'tools_search':
|
|
1233
1324
|
result = await client.listTools(command.query);
|
|
1234
1325
|
break;
|
|
@@ -1239,100 +1330,30 @@ async function runCli(argv = process.argv.slice(2), runtime = {}) {
|
|
|
1239
1330
|
result = await client.getTool(command.capabilityId);
|
|
1240
1331
|
break;
|
|
1241
1332
|
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
|
-
}
|
|
1333
|
+
const outcome = await (0, gatedRun_1.runGated)(gatedContext(), {
|
|
1334
|
+
capabilityId: command.capabilityId,
|
|
1335
|
+
body: command.body,
|
|
1336
|
+
...(command.turnToken ? { turnToken: command.turnToken } : {}),
|
|
1337
|
+
...(command.idempotencyKey ? { idempotencyKey: command.idempotencyKey } : {}),
|
|
1338
|
+
waitTimeout: command.waitTimeout,
|
|
1339
|
+
...(command.noWait ? { noWait: true } : {}),
|
|
1340
|
+
});
|
|
1341
|
+
result = outcome.value;
|
|
1342
|
+
if (outcome.timedOut)
|
|
1343
|
+
process.exitCode = runWait_1.RUN_WAIT_TIMEOUT_EXIT_CODE;
|
|
1313
1344
|
break;
|
|
1314
1345
|
}
|
|
1315
1346
|
case 'tables_import': {
|
|
1316
1347
|
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));
|
|
1348
|
+
const imported = await (0, tablesImport_1.importLocalTable)(gatedContext(), {
|
|
1349
|
+
tableId: command.tableId,
|
|
1350
|
+
file: command.file,
|
|
1351
|
+
...(command.format ? { format: command.format } : {}),
|
|
1352
|
+
...(command.upsertKey ? { upsertKey: command.upsertKey } : {}),
|
|
1353
|
+
...(command.rowKind ? { rowKind: command.rowKind } : {}),
|
|
1354
|
+
...(command.keepUnmappedColumns === false ? { keepUnmappedColumns: false } : {}),
|
|
1355
|
+
});
|
|
1356
|
+
result = { ok: true, data: { import: imported }, meta: {} };
|
|
1336
1357
|
break;
|
|
1337
1358
|
}
|
|
1338
1359
|
case 'runs_list':
|
|
@@ -1341,12 +1362,6 @@ async function runCli(argv = process.argv.slice(2), runtime = {}) {
|
|
|
1341
1362
|
case 'runs_get':
|
|
1342
1363
|
result = await client.getRun(command.runId);
|
|
1343
1364
|
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
1365
|
case 'runs_events':
|
|
1351
1366
|
result = await client.getRunEvents(command.runId);
|
|
1352
1367
|
break;
|
|
@@ -1355,28 +1370,40 @@ async function runCli(argv = process.argv.slice(2), runtime = {}) {
|
|
|
1355
1370
|
expected_state_version: command.stateVersion ?? stateVersionFromRunRead(await client.getRun(command.runId)),
|
|
1356
1371
|
...(command.reason ? { reason: command.reason } : {}),
|
|
1357
1372
|
};
|
|
1358
|
-
|
|
1373
|
+
const identity = { scope: 'runs.cancel', workspace, body: { run_id: command.runId, ...body } };
|
|
1374
|
+
const key = command.idempotencyKey ?? await (0, idempotency_1.pendingIdempotencyKey)(idempotencyPath, identity);
|
|
1375
|
+
try {
|
|
1376
|
+
result = await client.cancelRun(command.runId, body, key);
|
|
1377
|
+
if (!command.idempotencyKey)
|
|
1378
|
+
await (0, idempotency_1.finishIdempotencyAttempt)(idempotencyPath, identity);
|
|
1379
|
+
}
|
|
1380
|
+
catch (error) {
|
|
1381
|
+
const transient = error instanceof client_1.CliError && (error.code === 'request_timeout' || error.code === 'network_error' || error.retryable === true || error.status >= 500);
|
|
1382
|
+
if (!command.idempotencyKey && !transient)
|
|
1383
|
+
await (0, idempotency_1.finishIdempotencyAttempt)(idempotencyPath, identity);
|
|
1384
|
+
throw error;
|
|
1385
|
+
}
|
|
1359
1386
|
break;
|
|
1360
1387
|
}
|
|
1361
1388
|
case 'pack': {
|
|
1362
1389
|
if (command.pack === 'sequences') {
|
|
1363
|
-
await
|
|
1390
|
+
await sequencesCommands().runSequencesCommand(gatedContext(), command, stdout);
|
|
1364
1391
|
}
|
|
1365
1392
|
else {
|
|
1366
|
-
await
|
|
1393
|
+
await workflowsCommands().runWorkflowsCommand(gatedContext(), command, stdout);
|
|
1367
1394
|
}
|
|
1368
1395
|
return;
|
|
1369
1396
|
}
|
|
1370
1397
|
default: return;
|
|
1371
1398
|
}
|
|
1372
|
-
if (
|
|
1399
|
+
if (command.command === 'tools_run' && command.open)
|
|
1373
1400
|
openResult(result, runtime);
|
|
1374
1401
|
if (command.command === 'packs_route' && !command.jsonOutput)
|
|
1375
1402
|
printPackRoute(result, stdout);
|
|
1376
1403
|
else
|
|
1377
1404
|
print(result, stdout, Boolean(command.jsonOutput));
|
|
1378
1405
|
if (process.exitCode !== runWait_1.RUN_WAIT_TIMEOUT_EXIT_CODE)
|
|
1379
|
-
process.exitCode =
|
|
1406
|
+
process.exitCode = errors_1.CLI_EXIT.SUCCESS;
|
|
1380
1407
|
}
|
|
1381
1408
|
catch (error) {
|
|
1382
1409
|
const exitCode = exitCodeForError(error);
|