@trydreamstate/cli 1.2.0 → 1.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (77) hide show
  1. package/README.md +11 -1
  2. package/agent-plugin/.agents/plugins/marketplace.json +20 -0
  3. package/agent-plugin/.claude-plugin/marketplace.json +16 -0
  4. package/agent-plugin/.grok-plugin/marketplace.json +16 -0
  5. package/agent-plugin/plugins/dreamstate/.claude-plugin/plugin.json +21 -0
  6. package/agent-plugin/plugins/dreamstate/.codex-plugin/plugin.json +41 -0
  7. package/agent-plugin/plugins/dreamstate/.mcp.json +8 -0
  8. package/agent-plugin/plugins/dreamstate/skills/crm/SKILL.md +75 -0
  9. package/agent-plugin/plugins/dreamstate/skills/diagnostics/SKILL.md +72 -0
  10. package/agent-plugin/plugins/dreamstate/skills/dreamstate/SKILL.md +16 -0
  11. package/agent-plugin/plugins/dreamstate/skills/first-win/SKILL.md +57 -0
  12. package/agent-plugin/plugins/dreamstate/skills/inbox-replies/SKILL.md +61 -0
  13. package/agent-plugin/plugins/dreamstate/skills/knowledge/SKILL.md +48 -0
  14. package/agent-plugin/plugins/dreamstate/skills/linkedin-posts/SKILL.md +79 -0
  15. package/agent-plugin/plugins/dreamstate/skills/linkedin-warm-signals/SKILL.md +65 -0
  16. package/agent-plugin/plugins/dreamstate/skills/list-building/SKILL.md +105 -0
  17. package/agent-plugin/plugins/dreamstate/skills/list-building/column-authoring.md +37 -0
  18. package/agent-plugin/plugins/dreamstate/skills/list-building/readback.md +15 -0
  19. package/agent-plugin/plugins/dreamstate/skills/list-building/sourcing.md +31 -0
  20. package/agent-plugin/plugins/dreamstate/skills/outreach/SKILL.md +77 -0
  21. package/agent-plugin/plugins/dreamstate/skills/outreach/channels.md +5 -0
  22. package/agent-plugin/plugins/dreamstate/skills/outreach/deliverability-and-caps.md +17 -0
  23. package/agent-plugin/plugins/dreamstate/skills/outreach/enrollment.md +17 -0
  24. package/agent-plugin/plugins/dreamstate/skills/outreach/message-copy.md +41 -0
  25. package/agent-plugin/plugins/dreamstate/skills/outreach/replies.md +5 -0
  26. package/agent-plugin/plugins/dreamstate/skills/outreach/steps-and-timing.md +13 -0
  27. package/agent-plugin/plugins/dreamstate/skills/outreach/stop-and-suppression.md +7 -0
  28. package/agent-plugin/plugins/dreamstate/skills/plays/SKILL.md +81 -0
  29. package/agent-plugin/plugins/dreamstate/skills/reddit-posts/SKILL.md +65 -0
  30. package/agent-plugin/plugins/dreamstate/skills/skill-authoring/SKILL.md +65 -0
  31. package/agent-plugin/plugins/dreamstate/skills/social/SKILL.md +69 -0
  32. package/agent-plugin/plugins/dreamstate/skills/visibility/SKILL.md +60 -0
  33. package/agent-plugin/plugins/dreamstate/skills/visibility/probes.md +15 -0
  34. package/agent-plugin/plugins/dreamstate/skills/visibility/query-sets.md +17 -0
  35. package/agent-plugin/plugins/dreamstate/skills/visibility/search-evidence.md +15 -0
  36. package/agent-plugin/plugins/dreamstate/skills/visibility/technical.md +16 -0
  37. package/agent-plugin/plugins/dreamstate/skills/workflows/SKILL.md +43 -0
  38. package/agent-plugin/plugins/dreamstate/skills/workflows/durable-runs.md +19 -0
  39. package/agent-plugin/plugins/dreamstate/skills/workflows/graph-authoring.md +9 -0
  40. package/agent-plugin/plugins/dreamstate/skills/workflows/lifecycle-and-enrollment.md +11 -0
  41. package/agent-plugin/plugins/dreamstate/skills/workflows/live-replies.md +5 -0
  42. package/agent-plugin/plugins/dreamstate/skills/workflows/recipe-routing.md +5 -0
  43. package/agent-plugin/plugins/dreamstate/skills/worksheet-tidy/SKILL.md +61 -0
  44. package/agent-plugin/plugins/dreamstate/skills/writing/SKILL.md +84 -0
  45. package/agent-plugin/plugins/dreamstate/skills/writing/briefs.md +35 -0
  46. package/agent-plugin/plugins/dreamstate/skills/writing/drafting.md +39 -0
  47. package/agent-plugin/plugins/dreamstate/skills/writing/publishing.md +74 -0
  48. package/agent-plugin/plugins/dreamstate/skills/writing/revision-and-voice.md +31 -0
  49. package/agent-plugin/plugins/dreamstate/skills/x-posts/SKILL.md +73 -0
  50. package/dist/client.d.ts +3 -1
  51. package/dist/client.d.ts.map +1 -1
  52. package/dist/client.js +126 -41
  53. package/dist/commands/sequences.d.ts +1 -1
  54. package/dist/commands/sequences.d.ts.map +1 -1
  55. package/dist/commands/sequences.js +3 -3
  56. package/dist/commands/shared.d.ts +2 -1
  57. package/dist/commands/shared.d.ts.map +1 -1
  58. package/dist/commands/shared.js +98 -20
  59. package/dist/commands/workflows.d.ts +1 -1
  60. package/dist/commands/workflows.d.ts.map +1 -1
  61. package/dist/commands/workflows.js +9 -9
  62. package/dist/config.d.ts +10 -0
  63. package/dist/config.d.ts.map +1 -1
  64. package/dist/config.js +95 -2
  65. package/dist/index.d.ts +51 -1
  66. package/dist/index.d.ts.map +1 -1
  67. package/dist/index.js +421 -67
  68. package/dist/pluginInstaller.d.ts +22 -0
  69. package/dist/pluginInstaller.d.ts.map +1 -0
  70. package/dist/pluginInstaller.js +122 -0
  71. package/dist/runWait.d.ts +9 -0
  72. package/dist/runWait.d.ts.map +1 -0
  73. package/dist/runWait.js +76 -0
  74. package/dist/skillInstaller.d.ts +1 -0
  75. package/dist/skillInstaller.d.ts.map +1 -1
  76. package/dist/skillInstaller.js +18 -0
  77. package/package.json +3 -2
package/dist/index.js CHANGED
@@ -4,6 +4,8 @@ Object.defineProperty(exports, "__esModule", { value: true });
4
4
  exports.CliUsageError = exports.CLI_EXIT = void 0;
5
5
  exports.parseCliArgs = parseCliArgs;
6
6
  exports.toolTurnToken = toolTurnToken;
7
+ exports.omitsToolTurnForInlineTableRead = omitsToolTurnForInlineTableRead;
8
+ exports.capabilityIdempotencyKey = capabilityIdempotencyKey;
7
9
  exports.refusalEnvelope = refusalEnvelope;
8
10
  exports.openWebUrl = openWebUrl;
9
11
  exports.exitCodeForError = exitCodeForError;
@@ -14,9 +16,12 @@ const node_crypto_1 = require("node:crypto");
14
16
  const promises_1 = require("node:fs/promises");
15
17
  const tableFilePreview_1 = require("./tableFilePreview");
16
18
  const node_os_1 = require("node:os");
19
+ const node_path_1 = require("node:path");
17
20
  const node_util_1 = require("node:util");
18
21
  const config_1 = require("./config");
22
+ const pluginInstaller_1 = require("./pluginInstaller");
19
23
  const turnCache_1 = require("./turnCache");
24
+ const runWait_1 = require("./runWait");
20
25
  const client_1 = require("./client");
21
26
  const skillInstaller_1 = require("./skillInstaller");
22
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 };
@@ -26,13 +31,16 @@ const USAGE = `Dreamstate CLI
26
31
  Usage:
27
32
  dreamstate --help
28
33
  dreamstate --version
29
- dreamstate login
30
- dreamstate login --token <ds_user_...|ds_live_...>
34
+ dreamstate login [--no-skills]
35
+ dreamstate login [--profile <name>] --token <ds_user_...|ds_live_...> [--no-skills]
31
36
  dreamstate logout
37
+ dreamstate install [--agents <claude codex grok>] [--json-output|--json]
38
+ dreamstate status [--json-output|--json]
39
+ dreamstate profiles <list|use|current|remove|env> [name] [--json-output|--json]
32
40
  dreamstate whoami [--json-output|--json]
33
41
  dreamstate workspaces list [--json-output|--json]
34
42
  dreamstate workspaces use <id|slug>
35
- dreamstate auth login [--api-key-file <path>]
43
+ dreamstate auth login [--api-key-file <path>] [--no-skills]
36
44
  dreamstate auth whoami [--json-output|--json]
37
45
  dreamstate packs list [--json-output|--json]
38
46
  dreamstate packs get <pack_id> [--json-output|--json]
@@ -45,18 +53,19 @@ Usage:
45
53
  dreamstate commands search <term> [--json-output|--json]
46
54
  dreamstate tools search --goal <goal> [--terms <csv>] [--limit <n>] [--toolset <pack_id>] [--json-output|--json]
47
55
  dreamstate tools get <capability_id> [--json-output|--json]
48
- dreamstate tools run <capability_id> [--params <object>|--json <body>] [--turn-token <token>] (--live | --dry-run | --mode <smoke_test|dry_run|live>) [--agent-session-id <uuid>] [--approved] [--approved-effect-unknown] [--max-credits <n>] [--idempotency-key <key>] [--open] [--json-output|--json]
56
+ dreamstate tools run <capability_id> [--params <object>|--json <body>] [--turn-token <token>] (--live | --dry-run | --mode <smoke_test|dry_run|live>) [--agent-session-id <uuid>] [--approved] [--approved-effect-unknown] [--max-credits <n>] [--idempotency-key <key>] [--wait-timeout <seconds>] [--no-wait] [--open] [--json-output|--json]
49
57
  dreamstate sequences <verb> [args] [flags]
50
58
  dreamstate workflows <verb> [args] [flags]
51
59
  dreamstate tables import --table-id <uuid> --file <path> [--format <csv|json|jsonl|xlsx>] [--preview] [--upsert-key <header>] [--background] [--json-output|--json]
52
- dreamstate runs list [--json <query>] [--json-output|--json]
60
+ dreamstate runs list [--limit <n>] [--cursor <cursor>] [--json <query>] [--json-output|--json]
53
61
  dreamstate runs get <run_id> [--json-output|--json]
54
62
  dreamstate runs resume <run_id> --state-version <n> [--reason <text>] [--idempotency-key <key>] [--open] [--json-output|--json]
55
63
  dreamstate runs events <run_id> [--json-output|--json]
56
- dreamstate runs cancel <run_id> --json <body> [--idempotency-key <key>] [--json-output|--json]
64
+ dreamstate runs cancel <run_id> [--state-version <n>] [--reason <text>] [--json <body>] [--idempotency-key <key>] [--json-output|--json]
57
65
 
58
66
  All catalog discovery reads live API records. Set DREAMSTATE_API_URL and DREAMSTATE_API_KEY.
59
- login opens browser approval; login --token stores a ds_user_ session or ds_live_ key (for SSH or remote machines) after verifying it with whoami.
67
+ 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}.
68
+ 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.
60
69
  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.
61
70
  logout deletes the stored credentials file. --agents takes space-separated, comma-separated or repeated values.
62
71
  `;
@@ -106,6 +115,22 @@ function nonNegativeInteger(raw, flag) {
106
115
  throw new CliUsageError(`${flag} must be a non-negative integer`);
107
116
  return value;
108
117
  }
118
+ // runs.get returns { data: { run: { state_version } } }. Cancel posts
119
+ // expected_state_version, and a missing read must not invent 0.
120
+ function stateVersionFromRunRead(payload) {
121
+ const record = payload && typeof payload === 'object' && !Array.isArray(payload) ? payload : null;
122
+ const data = record && record.data && typeof record.data === 'object' && !Array.isArray(record.data)
123
+ ? record.data
124
+ : null;
125
+ const run = data && data.run && typeof data.run === 'object' && !Array.isArray(data.run)
126
+ ? data.run
127
+ : null;
128
+ const version = run?.state_version;
129
+ if (typeof version !== 'number' || !Number.isSafeInteger(version) || version < 0) {
130
+ throw new client_1.CliError('runs cancel could not read state_version from the run. Pass --state-version <n>.', 'invalid_response', 0);
131
+ }
132
+ return version;
133
+ }
109
134
  function parsePacks(argv) {
110
135
  const { values, positionals } = (0, node_util_1.parseArgs)({ args: argv, options: { goal: { type: 'string' }, 'json-output': { type: 'boolean' }, help: { type: 'boolean', short: 'h' } }, allowPositionals: true, strict: true });
111
136
  if (values.help)
@@ -215,7 +240,7 @@ function parseTools(argv) {
215
240
  'idempotency-key': { type: 'string' }, 'json-output': { type: 'boolean' }, open: { type: 'boolean' },
216
241
  'dry-run': { type: 'boolean' }, live: { type: 'boolean' }, mode: { type: 'string' },
217
242
  'agent-session-id': { type: 'string' }, approved: { type: 'boolean' }, 'approved-effect-unknown': { type: 'boolean' },
218
- 'max-credits': { type: 'string' }, 'turn-token': { type: 'string' }, help: { type: 'boolean', short: 'h' },
243
+ 'max-credits': { type: 'string' }, 'turn-token': { type: 'string' }, 'wait-timeout': { type: 'string' }, 'no-wait': { type: 'boolean' }, help: { type: 'boolean', short: 'h' },
219
244
  }, allowPositionals: true, strict: true });
220
245
  if (values.help)
221
246
  return { command: 'help' };
@@ -268,20 +293,21 @@ function parseTools(argv) {
268
293
  // run without one. What was missing was any way to ASK for live.
269
294
  if (body.mode !== undefined)
270
295
  requireToolsRunMode(body);
271
- return { command: 'tools_run', capabilityId, body, ...(values['turn-token'] ? { turnToken: values['turn-token'] } : {}), ...(values['idempotency-key'] ? { idempotencyKey: values['idempotency-key'] } : {}), ...(values.open ? { open: true } : {}), ...output };
296
+ const waitTimeout = values['wait-timeout'] === undefined ? 120 : nonNegativeNumber(values['wait-timeout'], '--wait-timeout');
297
+ 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 };
272
298
  }
273
299
  throw new CliUsageError('Usage: dreamstate tools <search|get|run>');
274
300
  }
275
301
  function parseTables(argv) {
276
302
  const { values, positionals } = (0, node_util_1.parseArgs)({ args: argv, options: {
277
303
  'table-id': { type: 'string' }, file: { type: 'string' }, format: { type: 'string' }, background: { type: 'boolean' },
278
- preview: { type: 'boolean' }, 'upsert-key': { type: 'string' },
304
+ preview: { type: 'boolean' }, 'upsert-key': { type: 'string' }, 'row-kind': { type: 'string' }, 'keep-unmapped-columns': { type: 'string' },
279
305
  'json-output': { type: 'boolean' }, help: { type: 'boolean', short: 'h' },
280
306
  }, allowPositionals: true, strict: true });
281
307
  if (values.help)
282
308
  return { command: 'help' };
283
309
  if (positionals.length !== 1 || positionals[0] !== 'import' || !values['table-id'] || !values.file) {
284
- throw new CliUsageError('Usage: dreamstate tables import --table-id <uuid> --file <path> [--format <csv|json|jsonl|xlsx>] [--preview] [--upsert-key <header>] [--background]');
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>] [--background]');
285
311
  }
286
312
  // --format is an override, not a requirement. Requiring it made the caller restate
287
313
  // what the file path already says, and an omitted flag produced a usage error rather
@@ -292,6 +318,14 @@ function parseTables(argv) {
292
318
  throw new CliUsageError('--format must be one of csv, json, jsonl, xlsx');
293
319
  }
294
320
  const upsertKey = values['upsert-key']?.trim();
321
+ const rowKind = values['row-kind'];
322
+ if (rowKind !== undefined && rowKind !== 'person' && rowKind !== 'company') {
323
+ throw new CliUsageError('--row-kind must be person or company');
324
+ }
325
+ const keepUnmappedColumns = values['keep-unmapped-columns'];
326
+ if (keepUnmappedColumns !== undefined && keepUnmappedColumns !== 'true' && keepUnmappedColumns !== 'false') {
327
+ throw new CliUsageError('--keep-unmapped-columns must be true or false');
328
+ }
295
329
  return {
296
330
  command: 'tables_import',
297
331
  tableId: values['table-id'],
@@ -299,17 +333,38 @@ function parseTables(argv) {
299
333
  ...(format ? { format: format } : {}),
300
334
  ...(values.preview ? { preview: true } : {}),
301
335
  ...(upsertKey ? { upsertKey } : {}),
336
+ ...(rowKind ? { rowKind } : {}),
337
+ ...(keepUnmappedColumns === 'false' ? { keepUnmappedColumns: false } : {}),
302
338
  background: Boolean(values.background),
303
339
  ...outputOption(values),
304
340
  };
305
341
  }
342
+ function runsListLimit(raw) {
343
+ const value = nonNegativeInteger(raw, '--limit');
344
+ if (value < 1 || value > 500)
345
+ throw new CliUsageError('--limit must be an integer from 1 to 500');
346
+ return value;
347
+ }
306
348
  function parseRuns(argv) {
307
- const { values, positionals } = (0, node_util_1.parseArgs)({ args: argv, options: { json: { type: 'string' }, reason: { type: 'string' }, 'state-version': { type: 'string' }, 'idempotency-key': { type: 'string' }, open: { type: 'boolean' }, 'json-output': { type: 'boolean' }, help: { type: 'boolean', short: 'h' } }, allowPositionals: true, strict: true });
349
+ const { values, positionals } = (0, node_util_1.parseArgs)({ args: argv, options: { json: { type: 'string' }, limit: { type: 'string' }, cursor: { type: 'string' }, reason: { type: 'string' }, 'state-version': { type: 'string' }, 'idempotency-key': { type: 'string' }, open: { type: 'boolean' }, 'json-output': { type: 'boolean' }, help: { type: 'boolean', short: 'h' } }, allowPositionals: true, strict: true });
308
350
  if (values.help)
309
351
  return { command: 'help' };
310
352
  const output = outputOption(values);
311
- if (positionals.length === 1 && positionals[0] === 'list')
312
- return { command: 'runs_list', query: values.json ? parseJsonObject(values.json) : {}, ...output };
353
+ if (positionals.length === 1 && positionals[0] === 'list') {
354
+ const query = values.json ? parseJsonObject(values.json) : {};
355
+ if (values.limit !== undefined)
356
+ query.limit = runsListLimit(values.limit);
357
+ if (values.cursor !== undefined) {
358
+ const cursor = values.cursor.trim();
359
+ if (!cursor)
360
+ throw new CliUsageError('--cursor must be a non-empty string');
361
+ query.cursor = cursor;
362
+ }
363
+ return { command: 'runs_list', query, ...output };
364
+ }
365
+ if (values.limit !== undefined || values.cursor !== undefined) {
366
+ throw new CliUsageError('--limit and --cursor are only valid on runs list');
367
+ }
313
368
  const runId = requireOne(positionals, `dreamstate runs ${positionals[0] ?? '<get|events|cancel>'} <run_id>`);
314
369
  if (positionals[0] === 'get')
315
370
  return { command: 'runs_get', runId, ...output };
@@ -323,9 +378,20 @@ function parseRuns(argv) {
323
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 };
324
379
  }
325
380
  if (positionals[0] === 'cancel') {
326
- if (!values.json)
327
- throw new CliUsageError('runs cancel requires --json <body>');
328
- return { command: 'runs_cancel', runId, body: parseJsonObject(values.json), ...(values['idempotency-key'] ? { idempotencyKey: values['idempotency-key'] } : {}), ...output };
381
+ const idempotency = values['idempotency-key'] ? { idempotencyKey: values['idempotency-key'] } : {};
382
+ 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.');
384
+ }
385
+ if (values.json)
386
+ return { command: 'runs_cancel', runId, body: parseJsonObject(values.json), ...idempotency, ...output };
387
+ return {
388
+ command: 'runs_cancel',
389
+ runId,
390
+ ...(values['state-version'] !== undefined ? { stateVersion: nonNegativeInteger(values['state-version'], '--state-version') } : {}),
391
+ ...(values.reason ? { reason: values.reason } : {}),
392
+ ...idempotency,
393
+ ...output,
394
+ };
329
395
  }
330
396
  throw new CliUsageError('Usage: dreamstate runs <get|events|cancel> <run_id>');
331
397
  }
@@ -342,14 +408,26 @@ function parseCliArgs(argv) {
342
408
  return { command: 'version' };
343
409
  const [group, ...rest] = argv;
344
410
  if (group === 'login') {
345
- const { values, positionals } = (0, node_util_1.parseArgs)({ args: rest, options: { token: { type: 'string' }, 'api-key-file': { type: 'string' }, 'json-output': { type: 'boolean' }, help: { type: 'boolean', short: 'h' } }, allowPositionals: true, strict: true });
411
+ const { values, positionals } = (0, node_util_1.parseArgs)({ args: rest, options: { token: { type: 'string' }, profile: { type: 'string' }, 'api-key-file': { type: 'string' }, 'no-skills': { type: 'boolean' }, 'json-output': { type: 'boolean' }, help: { type: 'boolean', short: 'h' } }, allowPositionals: true, strict: true });
346
412
  if (values.help)
347
413
  return { command: 'help' };
348
414
  if (positionals.length)
349
415
  throw new CliUsageError('Usage: dreamstate login [--token <ds_user_...|ds_live_...>]');
350
416
  if (values.token !== undefined && values['api-key-file'])
351
417
  throw new CliUsageError('Use either --token or --api-key-file, not both.');
352
- return { command: 'login', ...(values.token !== undefined ? { token: values.token } : {}), ...(values['api-key-file'] ? { apiKeyFile: values['api-key-file'] } : {}), ...outputOption(values) };
418
+ 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
+ }
420
+ if (group === 'install') {
421
+ const { values, positionals } = (0, node_util_1.parseArgs)({ args: spaceSeparatedAgents(rest), options: { agents: { type: 'string', multiple: true }, 'json-output': { type: 'boolean' }, help: { type: 'boolean', short: 'h' } }, allowPositionals: true, strict: true });
422
+ if (values.help)
423
+ return { command: 'help' };
424
+ if (positionals.length)
425
+ throw new CliUsageError('Usage: dreamstate install [--agents <claude codex grok>]');
426
+ const requested = values.agents ? [...new Set(values.agents.flatMap((raw) => csv(raw, '--agents')))] : [...pluginInstaller_1.PLUGIN_AGENTS];
427
+ const invalid = requested.filter((agent) => !pluginInstaller_1.PLUGIN_AGENTS.includes(agent));
428
+ if (invalid.length)
429
+ throw new CliUsageError(`--agents values must be one of: ${pluginInstaller_1.PLUGIN_AGENTS.join(', ')} (got: ${invalid.join(', ')})`);
430
+ return { command: 'install', agents: requested, ...outputOption(values) };
353
431
  }
354
432
  if (group === 'logout') {
355
433
  const { values, positionals } = (0, node_util_1.parseArgs)({ args: rest, options: { 'json-output': { type: 'boolean' }, help: { type: 'boolean', short: 'h' } }, allowPositionals: true, strict: true });
@@ -359,6 +437,27 @@ function parseCliArgs(argv) {
359
437
  throw new CliUsageError('Usage: dreamstate logout');
360
438
  return { command: 'logout', ...outputOption(values) };
361
439
  }
440
+ if (group === 'status') {
441
+ const { values, positionals } = (0, node_util_1.parseArgs)({ args: rest, options: { 'json-output': { type: 'boolean' } }, allowPositionals: true, strict: true });
442
+ if (!positionals.length)
443
+ return { command: 'status', ...outputOption(values) };
444
+ throw new CliUsageError('Usage: dreamstate status [--json-output]');
445
+ }
446
+ if (group === 'profiles') {
447
+ const { values, positionals } = (0, node_util_1.parseArgs)({ args: rest, options: { 'json-output': { type: 'boolean' } }, allowPositionals: true, strict: true });
448
+ const output = outputOption(values);
449
+ if (positionals.length === 1 && positionals[0] === 'list')
450
+ return { command: 'profiles_list', ...output };
451
+ if (positionals.length === 1 && positionals[0] === 'current')
452
+ return { command: 'profiles_current', ...output };
453
+ if (positionals.length === 2 && positionals[0] === 'use')
454
+ return { command: 'profiles_use', name: (0, config_1.validateProfileName)(positionals[1]), ...output };
455
+ if (positionals.length === 2 && positionals[0] === 'remove')
456
+ return { command: 'profiles_remove', name: (0, config_1.validateProfileName)(positionals[1]), ...output };
457
+ if (positionals.length === 2 && positionals[0] === 'env')
458
+ 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>>');
460
+ }
362
461
  if (group === 'whoami') {
363
462
  const { values, positionals } = (0, node_util_1.parseArgs)({ args: rest, options: { 'json-output': { type: 'boolean' } }, allowPositionals: true, strict: true });
364
463
  if (positionals.length === 0)
@@ -376,9 +475,9 @@ function parseCliArgs(argv) {
376
475
  throw new CliUsageError('Usage: dreamstate workspaces <list|use <id|slug>>');
377
476
  }
378
477
  if (group === 'auth') {
379
- const { values, positionals } = (0, node_util_1.parseArgs)({ args: rest, options: { 'api-key-file': { type: 'string' }, 'json-output': { type: 'boolean' } }, allowPositionals: true, strict: true });
478
+ 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 });
380
479
  if (positionals.length === 1 && positionals[0] === 'login')
381
- return { command: 'login', ...(values['api-key-file'] ? { apiKeyFile: values['api-key-file'] } : {}), ...outputOption(values) };
480
+ return { command: 'login', ...(values['api-key-file'] ? { apiKeyFile: values['api-key-file'] } : {}), ...(values['no-skills'] ? { noSkills: true } : {}), ...outputOption(values) };
382
481
  if (positionals.length === 1 && positionals[0] === 'whoami')
383
482
  return { command: 'whoami', ...outputOption(values) };
384
483
  throw new CliUsageError('Usage: dreamstate auth <login|whoami>');
@@ -438,6 +537,76 @@ function toolTurnToken(value) {
438
537
  return data.tool_turn_token;
439
538
  throw new client_1.CliError('tools get did not return a tool_turn_token.', 'invalid_tool_turn_token', 0);
440
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
+ /**
589
+ * Header value, or null when the request must not carry Idempotency-Key.
590
+ * Any key forces a read onto the durable run path (isInlineReadEligible).
591
+ * Send one only for an explicit key, or when the tools get contract says
592
+ * mutates or idempotency_required. Missing flags omit the key: a read whose
593
+ * contract does not set mutates must not be treated as a write.
594
+ */
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
+ }
441
610
  function code(error) {
442
611
  return error && typeof error === 'object' && typeof error.code === 'string'
443
612
  ? error.code
@@ -617,14 +786,97 @@ function whoamiData(value) {
617
786
  const data = value && typeof value === 'object' ? value.data : undefined;
618
787
  return data && typeof data === 'object' ? data : {};
619
788
  }
789
+ function statusData(whoami, health) {
790
+ const meta = whoami && typeof whoami === 'object' ? whoami.meta : null;
791
+ const serverVersion = meta && typeof meta === 'object' ? meta.server_version : null;
792
+ const minimumCliVersion = meta && typeof meta === 'object' ? meta.minimum_cli_version : null;
793
+ const healthData = health && typeof health === 'object' ? health.data : null;
794
+ const healthVersion = healthData && typeof healthData === 'object' ? healthData.version : null;
795
+ const version = typeof serverVersion === 'string' ? serverVersion : typeof healthVersion === 'string' ? healthVersion : null;
796
+ const minimum = typeof minimumCliVersion === 'string' ? minimumCliVersion : null;
797
+ const parts = (value) => value.replace(/^v/, '').split('-')[0].split('.').map((part) => Number(part));
798
+ const [minimumMajor = 0, minimumMinor = 0, minimumPatch = 0] = minimum ? parts(minimum) : [];
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)));
801
+ return {
802
+ cli_version: client_1.CLI_VERSION,
803
+ api_version: version,
804
+ minimum_cli_version: minimum,
805
+ minimum_cli_version_published: minimum !== null,
806
+ compatible,
807
+ upgrade_hint: compatible ? null : 'Run: npm install -g @trydreamstate/cli@latest',
808
+ health,
809
+ whoami: whoamiData(whoami),
810
+ };
811
+ }
620
812
  function activeWorkspace(data) {
621
- const activeId = data.workspace_id ?? data.active_workspace_id ?? undefined;
813
+ const activeId = data.workspace_id;
622
814
  return activeId ? data.workspaces?.find((workspace) => workspace.id === activeId) : undefined;
623
815
  }
624
816
  function wait(milliseconds) {
625
817
  return new Promise((resolve) => setTimeout(resolve, milliseconds));
626
818
  }
627
- async function loginWithDeviceAuthorization(runtime, stdout, stderr) {
819
+ async function installHostedSkills(client, agents, runtime, options = {}) {
820
+ const listed = await client.listAllSkills();
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') : [];
823
+ if (!ids.length)
824
+ 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
+ const skills = [];
828
+ for (const id of ids) {
829
+ const skill = hostedSkill(await client.getSkill(id));
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
+ }
836
+ const packs = await client.listPacks();
837
+ const whoami = await client.getWhoami();
838
+ const workspaceId = typeof whoami?.data?.workspace_id === 'string' ? whoami.data.workspace_id : 'unknown';
839
+ const indexVersion = (0, node_crypto_1.createHash)('sha256')
840
+ .update(JSON.stringify(skills.map((skill) => [skill.id, skill.description, skill.body, skill.frontmatter ?? {}])))
841
+ .digest('hex').slice(0, 16);
842
+ const installed = await (0, skillInstaller_1.installSkills)([...skills, (0, skillInstaller_1.routerSkill)(packs)], {
843
+ agents,
844
+ homeDirectory: runtime.homeDirectory,
845
+ project: Boolean(options.project),
846
+ copy: Boolean(options.copy) || process.platform === 'win32',
847
+ workspaceId,
848
+ indexVersion,
849
+ });
850
+ return { files: installed.files, skillIds: [...ids, 'router'] };
851
+ }
852
+ async function installLoginSkills(client, runtime, skip, stderr) {
853
+ if (skip)
854
+ return { skipped: 'disabled' };
855
+ const withPlugin = await (0, pluginInstaller_1.agentsWithDreamstatePlugin)(runtime.homeDirectory);
856
+ const agents = (await (0, skillInstaller_1.detectInstalledSkillAgents)(runtime.homeDirectory)).filter((agent) => !withPlugin.includes(agent));
857
+ if (!agents.length && withPlugin.length)
858
+ return { skipped: 'plugin_installed' };
859
+ if (!agents.length) {
860
+ stderr.write('Run dreamstate skills install to install Dreamstate skills.\n');
861
+ return { skipped: 'no_agents_detected' };
862
+ }
863
+ try {
864
+ const installed = await installHostedSkills(client, agents, runtime);
865
+ stderr.write(`Installed Dreamstate skills into: ${agents.join(', ')}.\n`);
866
+ return { installed: agents, files: installed.files.length };
867
+ }
868
+ catch {
869
+ stderr.write('Run dreamstate skills install to install Dreamstate skills.\n');
870
+ return { skipped: 'installation_failed' };
871
+ }
872
+ }
873
+ function loginResult(value, skills) {
874
+ if (!value || typeof value !== 'object')
875
+ return value;
876
+ const envelope = value;
877
+ return { ...envelope, data: { ...(envelope.data && typeof envelope.data === 'object' ? envelope.data : {}), skills } };
878
+ }
879
+ async function loginWithDeviceAuthorization(runtime, stdout, stderr, profile, noSkills = false, jsonOutput = false) {
628
880
  const environment = runtime.env ?? process.env;
629
881
  const baseUrl = (0, config_1.resolveApiBaseUrl)(environment);
630
882
  const state = runtime.deviceState ?? deviceState();
@@ -637,14 +889,19 @@ async function loginWithDeviceAuthorization(runtime, stdout, stderr) {
637
889
  for (;;) {
638
890
  const claim = await (0, client_1.claimDeviceAuthorization)(baseUrl, authorization.device_code);
639
891
  if (claim.status === 'ready') {
640
- await (0, config_1.storeApiKey)(claim.token, runtimeCredentialsPath(runtime), claim.workspace.id);
892
+ if (profile)
893
+ await (0, config_1.storeCredentialProfile)(profile, claim.token, runtimeCredentialsPath(runtime), claim.workspace.id);
894
+ else
895
+ await (0, config_1.storeApiKey)(claim.token, runtimeCredentialsPath(runtime), claim.workspace.id);
641
896
  const client = new client_1.DreamstateClient({
642
897
  baseUrl,
643
898
  apiKey: claim.token,
644
899
  requestTimeoutMs: runtime.requestTimeoutMs,
645
900
  onVersionWarning: (message) => (runtime.stderr ?? process.stderr).write(`${message}\n`),
646
901
  });
647
- print(await client.getWhoami(), stdout, false);
902
+ const whoami = await client.getWhoami();
903
+ const skills = await installLoginSkills(client, runtime, noSkills, stderr);
904
+ print(loginResult(whoami, skills), stdout, jsonOutput);
648
905
  return;
649
906
  }
650
907
  await (runtime.sleep ?? wait)(authorization.interval_seconds * 1_000);
@@ -670,8 +927,9 @@ async function runCli(argv = process.argv.slice(2), runtime = {}) {
670
927
  try {
671
928
  const extracted = extractWorkspaceFlag(argv);
672
929
  const command = parseCliArgs(extracted.argv);
673
- if (command.command === 'help') {
674
- print({ ok: true, data: { usage: USAGE }, meta: {} }, stdout, false);
930
+ if (command.command === 'help' || command.command === 'pack_help') {
931
+ const packHelp = command.command === 'pack_help' ? command : undefined;
932
+ print({ ok: true, data: { usage: packHelp?.usage ?? USAGE }, meta: {} }, stdout, Boolean(packHelp?.jsonOutput));
675
933
  process.exitCode = exports.CLI_EXIT.SUCCESS;
676
934
  return;
677
935
  }
@@ -680,6 +938,19 @@ async function runCli(argv = process.argv.slice(2), runtime = {}) {
680
938
  process.exitCode = exports.CLI_EXIT.SUCCESS;
681
939
  return;
682
940
  }
941
+ if (command.command === 'install') {
942
+ const installed = await (0, pluginInstaller_1.installAgentPlugin)({ agents: command.agents, bundleDirectory: (0, node_path_1.join)(__dirname, '..', 'agent-plugin'), homeDirectory: runtime.homeDirectory, run: runtime.runAgentCommand });
943
+ const done = installed.agents.filter((item) => item.status === 'installed').map((item) => item.agent);
944
+ const failed = installed.agents.filter((item) => item.status === 'failed');
945
+ if (failed.length || !done.length) {
946
+ throw new client_1.CliError(done.length ? 'Dreamstate plugin install failed for some agents.' : 'No supported agent was found. Install Claude Code, Codex or Grok Build, then rerun dreamstate install.', 'plugin_install_failed', 0, { details: installed });
947
+ }
948
+ const removedSkills = done.includes('claude') ? await (0, pluginInstaller_1.removeClaudeSkillCopies)(runtime.homeDirectory) : [];
949
+ stderr.write(`Dreamstate plugin installed for ${done.join(', ')}. Start a new session; the first Dreamstate tool call asks you to sign in.\n`);
950
+ print({ ok: true, data: { ...installed, removed_skill_copies: removedSkills }, meta: {} }, stdout, Boolean(command.jsonOutput));
951
+ process.exitCode = exports.CLI_EXIT.SUCCESS;
952
+ return;
953
+ }
683
954
  if (command.command === 'logout') {
684
955
  const path = runtimeCredentialsPath(runtime);
685
956
  let removed = true;
@@ -699,6 +970,38 @@ async function runCli(argv = process.argv.slice(2), runtime = {}) {
699
970
  process.exitCode = exports.CLI_EXIT.SUCCESS;
700
971
  return;
701
972
  }
973
+ if (command.command === 'profiles_list' || command.command === 'profiles_current' || command.command === 'profiles_use' || command.command === 'profiles_remove' || command.command === 'profiles_env') {
974
+ const path = runtimeCredentialsPath(runtime);
975
+ const environment = runtime.env ?? process.env;
976
+ if (command.command === 'profiles_list') {
977
+ const current = await (0, config_1.currentCredentialProfile)(path, environment);
978
+ const profiles = await (0, config_1.listCredentialProfiles)(path);
979
+ print({ ok: true, data: { current_profile: current, profiles: profiles.map(({ name, workspace }) => ({ name, workspace, active: name === current })) }, meta: {} }, stdout, Boolean(command.jsonOutput));
980
+ }
981
+ else if (command.command === 'profiles_current') {
982
+ print({ ok: true, data: { profile: await (0, config_1.currentCredentialProfile)(path, environment) }, meta: {} }, stdout, Boolean(command.jsonOutput));
983
+ }
984
+ else if (command.command === 'profiles_use') {
985
+ await (0, config_1.useCredentialProfile)(command.name, path);
986
+ print({ ok: true, data: { profile: command.name }, meta: {} }, stdout, Boolean(command.jsonOutput));
987
+ }
988
+ else if (command.command === 'profiles_remove') {
989
+ const removed = await (0, config_1.removeCredentialProfile)(command.name, path);
990
+ print({ ok: true, data: { profile: command.name, removed }, meta: {} }, stdout, Boolean(command.jsonOutput));
991
+ }
992
+ else {
993
+ const profiles = await (0, config_1.listCredentialProfiles)(path);
994
+ if (!profiles.some((profile) => profile.name === command.name))
995
+ throw new CliUsageError(`Profile not found: ${command.name}.`);
996
+ const result = { ok: true, data: { profile: command.name, exports: [`DREAMSTATE_PROFILE=${command.name}`] }, meta: {} };
997
+ if (command.jsonOutput)
998
+ print(result, stdout, true);
999
+ else
1000
+ stdout.write(`export DREAMSTATE_PROFILE=${command.name}\n`);
1001
+ }
1002
+ process.exitCode = exports.CLI_EXIT.SUCCESS;
1003
+ return;
1004
+ }
702
1005
  if (command.command === 'login' && command.token !== undefined) {
703
1006
  const token = command.token.trim();
704
1007
  if (!(0, config_1.isDreamstateToken)(token))
@@ -715,8 +1018,23 @@ async function runCli(argv = process.argv.slice(2), runtime = {}) {
715
1018
  onVersionWarning: (message) => stderr.write(`${message}\n`),
716
1019
  });
717
1020
  const whoami = await client.getWhoami();
718
- await (0, config_1.storeApiKey)(token, runtimeCredentialsPath(runtime));
719
- print(whoami, stdout, Boolean(command.jsonOutput));
1021
+ if (command.profile)
1022
+ await (0, config_1.storeCredentialProfile)(command.profile, token, runtimeCredentialsPath(runtime));
1023
+ else
1024
+ await (0, config_1.storeApiKey)(token, runtimeCredentialsPath(runtime));
1025
+ const skills = await installLoginSkills(client, runtime, Boolean(command.noSkills), stderr);
1026
+ print(loginResult(whoami, skills), stdout, Boolean(command.jsonOutput));
1027
+ process.exitCode = exports.CLI_EXIT.SUCCESS;
1028
+ return;
1029
+ }
1030
+ if (command.command === 'status') {
1031
+ const environment = runtime.env ?? process.env;
1032
+ const credentialFilePath = runtimeCredentialsPath(runtime);
1033
+ const apiKey = await (0, config_1.resolveApiKey)(environment, credentialFilePath);
1034
+ const workspace = await (0, config_1.resolveWorkspace)(extracted.workspace, environment, credentialFilePath);
1035
+ const client = new client_1.DreamstateClient({ baseUrl: (0, config_1.resolveApiBaseUrl)(environment), apiKey, ...(workspace ? { workspace } : {}), requestTimeoutMs: runtime.requestTimeoutMs });
1036
+ const [whoami, health] = await Promise.all([client.getStatusWhoami(), client.getHealth()]);
1037
+ print({ ok: true, data: statusData(whoami, health), meta: {} }, stdout, Boolean(command.jsonOutput));
720
1038
  process.exitCode = exports.CLI_EXIT.SUCCESS;
721
1039
  return;
722
1040
  }
@@ -725,14 +1043,16 @@ async function runCli(argv = process.argv.slice(2), runtime = {}) {
725
1043
  const key = await readLoginKey(command, runtime);
726
1044
  if (!(0, config_1.isDreamstateToken)(key))
727
1045
  throw new CliUsageError('Dreamstate API keys must start with ds_user_ or ds_live_.');
728
- const path = await (0, config_1.storeApiKey)(key, runtimeCredentialsPath(runtime));
729
- print({ ok: true, data: { credentials_path: path }, meta: {} }, stdout, Boolean(command.jsonOutput));
730
- stderr.write('Run dreamstate skills install to install Dreamstate skills.\n');
1046
+ const path = command.profile
1047
+ ? await (0, config_1.storeCredentialProfile)(command.profile, key, runtimeCredentialsPath(runtime))
1048
+ : await (0, config_1.storeApiKey)(key, runtimeCredentialsPath(runtime));
1049
+ 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
+ const skills = await installLoginSkills(client, runtime, Boolean(command.noSkills), stderr);
1051
+ print({ ok: true, data: { credentials_path: path, skills }, meta: {} }, stdout, Boolean(command.jsonOutput));
731
1052
  process.exitCode = exports.CLI_EXIT.SUCCESS;
732
1053
  return;
733
1054
  }
734
- await loginWithDeviceAuthorization(runtime, stdout, stderr);
735
- stderr.write('Run dreamstate skills install to install Dreamstate skills.\n');
1055
+ await loginWithDeviceAuthorization(runtime, stdout, stderr, command.profile, Boolean(command.noSkills), Boolean(command.jsonOutput));
736
1056
  process.exitCode = exports.CLI_EXIT.SUCCESS;
737
1057
  return;
738
1058
  }
@@ -824,7 +1144,7 @@ async function runCli(argv = process.argv.slice(2), runtime = {}) {
824
1144
  result = await client.getWhoami();
825
1145
  if (!command.jsonOutput) {
826
1146
  const data = whoamiData(result);
827
- const activeId = data.workspace_id ?? data.active_workspace_id ?? null;
1147
+ const activeId = data.workspace_id ?? null;
828
1148
  const rows = data.workspaces ?? [];
829
1149
  stdout.write(rows.length
830
1150
  ? `${rows.map((row) => `${row.id === activeId ? '*' : ' '} ${row.slug ?? '-'} ${row.name} ${row.role ?? '-'} ${row.id}`).join('\n')}\n`
@@ -841,10 +1161,10 @@ async function runCli(argv = process.argv.slice(2), runtime = {}) {
841
1161
  const match = data.workspaces?.find((row) => row.id.toLowerCase() === wanted || row.slug?.toLowerCase() === wanted);
842
1162
  if (!match)
843
1163
  throw new CliUsageError(`You are not a member of workspace "${command.workspace}". Run dreamstate workspaces list to see the workspaces you can use.`);
844
- const stored = await (0, config_1.readStoredCredentials)(credentialFilePath);
1164
+ const stored = await (0, config_1.readStoredCredentials)(await (0, config_1.selectedCredentialsPath)(credentialFilePath, environment));
845
1165
  if (!stored)
846
1166
  throw new CliUsageError('workspaces use saves the default next to a stored login. Run dreamstate login or dreamstate login --token first.');
847
- await (0, config_1.storeApiKey)(stored.token, credentialFilePath, match.id);
1167
+ await (0, config_1.storeApiKey)(stored.token, await (0, config_1.selectedCredentialsPath)(credentialFilePath, environment), match.id);
848
1168
  stderr.write(`Default workspace: ${match.name} (${match.slug})\n`);
849
1169
  result = { ok: true, data: { workspace: { id: match.id, name: match.name, slug: match.slug }, credentials_path: credentialFilePath }, meta: {} };
850
1170
  break;
@@ -868,18 +1188,17 @@ async function runCli(argv = process.argv.slice(2), runtime = {}) {
868
1188
  result = await client.searchSkillDocuments(command.query);
869
1189
  break;
870
1190
  case 'skills_install': {
871
- const listed = command.skillId === '*' ? await client.listAllSkills() : null;
872
- const entries = listed && typeof listed === 'object' && listed.data?.skills;
873
- const ids = command.skillId === '*'
874
- ? Array.isArray(entries) ? entries.map((skill) => skill && typeof skill === 'object' ? skill.id : undefined).filter((id) => typeof id === 'string') : []
875
- : [command.skillId];
876
- if (!ids.length)
877
- throw new client_1.CliError('Dreamstate API returned no installable skills.', 'invalid_response', 200);
1191
+ if (command.skillId === '*') {
1192
+ const installed = await installHostedSkills(client, command.agents, runtime, { project: command.project, copy: command.copy });
1193
+ result = { ok: true, data: { installed_files: installed.files, skill_ids: installed.skillIds, agents: command.agents, ...(command.project ? { lock_file: 'skills-lock.json' } : {}) }, meta: {} };
1194
+ break;
1195
+ }
1196
+ const ids = [command.skillId];
878
1197
  // A remote id becomes a directory name; refuse traversal before any request or write.
879
1198
  if (ids.some((id) => !/^[a-z0-9][a-z0-9_-]*$/i.test(id)))
880
1199
  throw new CliUsageError('Dreamstate API returned an invalid skill id.');
881
- // One at a time: each read comes from the workspace's private vault,
882
- // which admits two reads at once, and fifteen in parallel failed on prod.
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.
883
1202
  const skills = [];
884
1203
  for (const id of ids) {
885
1204
  const skill = hostedSkill(await client.getSkill(id));
@@ -937,54 +1256,83 @@ async function runCli(argv = process.argv.slice(2), runtime = {}) {
937
1256
  const pending = command.turnToken
938
1257
  ? null
939
1258
  : await (0, turnCache_1.reusableTurnToken)(turns, baseUrl, turnKey, command.capabilityId, Date.now(), warnTurnCache);
940
- const turnToken = command.turnToken ?? pending ?? toolTurnToken(await client.getTool(command.capabilityId));
941
- const run = () => client.runCapability(command.capabilityId, { ...command.body, tool_turn_token: turnToken }, command.idempotencyKey);
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);
942
1269
  try {
943
1270
  try {
944
1271
  result = await run();
945
1272
  }
946
1273
  catch (error) {
947
- // A quote_required refusal binds a fresh quote to this turn, so the
948
- // identical call admits on the next try. Retry once here instead of
949
- // making the caller rerun the same command by hand.
950
- if (command.body.mode !== 'live' || code(error) !== 'quote_required')
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.
951
1288
  throw error;
952
- result = await run();
1289
+ }
1290
+ else {
1291
+ result = await run();
1292
+ }
953
1293
  }
954
1294
  }
955
1295
  catch (error) {
956
- if (!command.turnToken && code(error) === 'quote_required') {
1296
+ if (!command.turnToken && turnToken && code(error) === 'quote_required') {
957
1297
  await (0, turnCache_1.rememberTurnToken)(turns, baseUrl, turnKey, command.capabilityId, turnToken, Date.now(), warnTurnCache);
958
1298
  }
959
1299
  throw error;
960
1300
  }
961
1301
  if (!command.turnToken) {
962
- if (command.body.mode === 'dry_run')
1302
+ if (command.body.mode === 'dry_run' && turnToken)
963
1303
  await (0, turnCache_1.rememberTurnToken)(turns, baseUrl, turnKey, command.capabilityId, turnToken, Date.now(), warnTurnCache);
964
1304
  else if (pending)
965
1305
  await (0, turnCache_1.forgetTurnToken)(turns, baseUrl, turnKey, command.capabilityId, warnTurnCache);
966
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
+ }
967
1313
  break;
968
1314
  }
969
1315
  case 'tables_import': {
970
1316
  await (0, promises_1.access)(command.file);
971
- const discovered = await client.listTools({
972
- goal: 'prepare a local file import into a table',
973
- query_terms: ['local', 'file', 'import'],
974
- });
975
- const importCapabilityId = discoveredCapabilityId(discovered);
1317
+ // Search ranking is not an import contract. This exact capability owns the
1318
+ // prepare handoff for caller-local files, while nearby upload capabilities
1319
+ // can also match these words and made a real import fail after local work.
1320
+ const importCapabilityId = 'tables.table_sources.csv_import_prepare';
1321
+ const importContract = await client.getTool(importCapabilityId);
976
1322
  result = await client.runCapability(importCapabilityId, {
977
1323
  // A mutation run needs the turn token its own get minted; without it
978
1324
  // an API-key import was refused after the local file work.
979
- tool_turn_token: toolTurnToken(await client.getTool(importCapabilityId)),
1325
+ tool_turn_token: toolTurnToken(importContract),
980
1326
  mode: 'live',
981
1327
  params: {
982
1328
  table_id: command.tableId,
983
1329
  file_path: command.file,
984
1330
  ...(command.format ? { file_kind: command.format } : {}),
985
1331
  ...(command.upsertKey ? { upsert_key: command.upsertKey } : {}),
1332
+ ...(command.rowKind ? { row_kind: command.rowKind } : {}),
1333
+ ...(command.keepUnmappedColumns === false ? { keep_unmapped_columns: false } : {}),
986
1334
  },
987
- });
1335
+ }, capabilityIdempotencyKey(importContract));
988
1336
  break;
989
1337
  }
990
1338
  case 'runs_list':
@@ -1002,15 +1350,20 @@ async function runCli(argv = process.argv.slice(2), runtime = {}) {
1002
1350
  case 'runs_events':
1003
1351
  result = await client.getRunEvents(command.runId);
1004
1352
  break;
1005
- case 'runs_cancel':
1006
- result = await client.cancelRun(command.runId, command.body, command.idempotencyKey);
1353
+ case 'runs_cancel': {
1354
+ const body = command.body ?? {
1355
+ expected_state_version: command.stateVersion ?? stateVersionFromRunRead(await client.getRun(command.runId)),
1356
+ ...(command.reason ? { reason: command.reason } : {}),
1357
+ };
1358
+ result = await client.cancelRun(command.runId, body, command.idempotencyKey);
1007
1359
  break;
1360
+ }
1008
1361
  case 'pack': {
1009
1362
  if (command.pack === 'sequences') {
1010
- await require('./commands/sequences').runSequencesCommand(client, command, stdout);
1363
+ await require('./commands/sequences').runSequencesCommand(client, command, stdout, runtime.sleep ?? wait);
1011
1364
  }
1012
1365
  else {
1013
- await require('./commands/workflows').runWorkflowsCommand(client, command, stdout);
1366
+ await require('./commands/workflows').runWorkflowsCommand(client, command, stdout, runtime.sleep ?? wait);
1014
1367
  }
1015
1368
  return;
1016
1369
  }
@@ -1022,7 +1375,8 @@ async function runCli(argv = process.argv.slice(2), runtime = {}) {
1022
1375
  printPackRoute(result, stdout);
1023
1376
  else
1024
1377
  print(result, stdout, Boolean(command.jsonOutput));
1025
- process.exitCode = exports.CLI_EXIT.SUCCESS;
1378
+ if (process.exitCode !== runWait_1.RUN_WAIT_TIMEOUT_EXIT_CODE)
1379
+ process.exitCode = exports.CLI_EXIT.SUCCESS;
1026
1380
  }
1027
1381
  catch (error) {
1028
1382
  const exitCode = exitCodeForError(error);