@agent-deck/cli 2.0.0 → 2.1.1

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 (63) hide show
  1. package/dist/agent-harness.d.ts +3 -0
  2. package/dist/agent-harness.d.ts.map +1 -1
  3. package/dist/agent-harness.js +64 -6
  4. package/dist/agent-harness.js.map +1 -1
  5. package/dist/index.d.ts.map +1 -1
  6. package/dist/index.js +10 -2
  7. package/dist/index.js.map +1 -1
  8. package/dist/mcp-bridge.d.ts +83 -1
  9. package/dist/mcp-bridge.d.ts.map +1 -1
  10. package/dist/mcp-bridge.js +252 -7
  11. package/dist/mcp-bridge.js.map +1 -1
  12. package/dist/mcp-config.d.ts +13 -1
  13. package/dist/mcp-config.d.ts.map +1 -1
  14. package/dist/mcp-config.js +49 -1
  15. package/dist/mcp-config.js.map +1 -1
  16. package/dist/muse-skills/agent-deck-playbooks/SKILL.md +23 -0
  17. package/dist/muse-skills/agent-deck-run/SKILL.md +86 -0
  18. package/dist/muse-skills/agent-deck-session/SKILL.md +59 -0
  19. package/dist/muse-skills/agent-deck-setup/SKILL.md +32 -0
  20. package/dist/muse-skills.d.ts +42 -0
  21. package/dist/muse-skills.d.ts.map +1 -0
  22. package/dist/muse-skills.js +149 -0
  23. package/dist/muse-skills.js.map +1 -0
  24. package/dist/run-adapter.d.ts +100 -0
  25. package/dist/run-adapter.d.ts.map +1 -0
  26. package/dist/run-adapter.js +73 -0
  27. package/dist/run-adapter.js.map +1 -0
  28. package/dist/run-claude.d.ts +41 -0
  29. package/dist/run-claude.d.ts.map +1 -0
  30. package/dist/run-claude.js +91 -0
  31. package/dist/run-claude.js.map +1 -0
  32. package/dist/run-codex.d.ts +75 -0
  33. package/dist/run-codex.d.ts.map +1 -0
  34. package/dist/run-codex.js +337 -0
  35. package/dist/run-codex.js.map +1 -0
  36. package/dist/run-cursor.d.ts +79 -0
  37. package/dist/run-cursor.d.ts.map +1 -0
  38. package/dist/run-cursor.js +455 -0
  39. package/dist/run-cursor.js.map +1 -0
  40. package/dist/run-detach.d.ts +36 -0
  41. package/dist/run-detach.d.ts.map +1 -0
  42. package/dist/run-detach.js +233 -0
  43. package/dist/run-detach.js.map +1 -0
  44. package/dist/run-muse.d.ts +127 -0
  45. package/dist/run-muse.d.ts.map +1 -0
  46. package/dist/run-muse.js +499 -0
  47. package/dist/run-muse.js.map +1 -0
  48. package/dist/run-status.d.ts +62 -0
  49. package/dist/run-status.d.ts.map +1 -0
  50. package/dist/run-status.js +552 -0
  51. package/dist/run-status.js.map +1 -0
  52. package/dist/run.d.ts +191 -0
  53. package/dist/run.d.ts.map +1 -0
  54. package/dist/run.js +680 -0
  55. package/dist/run.js.map +1 -0
  56. package/dist/setup.d.ts.map +1 -1
  57. package/dist/setup.js +51 -10
  58. package/dist/setup.js.map +1 -1
  59. package/dist/use.d.ts +1 -1
  60. package/dist/use.d.ts.map +1 -1
  61. package/dist/use.js +16 -15
  62. package/dist/use.js.map +1 -1
  63. package/package.json +5 -4
package/dist/run.js ADDED
@@ -0,0 +1,680 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ var __importDefault = (this && this.__importDefault) || function (mod) {
36
+ return (mod && mod.__esModule) ? mod : { "default": mod };
37
+ };
38
+ Object.defineProperty(exports, "__esModule", { value: true });
39
+ exports.RUN_FAKE_ENV = exports.RUN_CLAUDE_NOT_SIGNED_IN_ERROR = exports.RUN_CLAUDE_NOT_INSTALLED_ERROR = exports.CLAUDE_AUTH_LOGIN_COMMAND = exports.RUN_TOOL_PROFILES = exports.RUN_SETTING_SOURCES = exports.RUN_READONLY_TOOLS = exports.RUN_PERMISSION_MODE = exports.RUN_EDIT_TOOLS = exports.allowedToolsForProfile = void 0;
40
+ exports.parseRunArgs = parseRunArgs;
41
+ exports.findExecutableOnPath = findExecutableOnPath;
42
+ exports.claudeAuthStatusShowsLogin = claudeAuthStatusShowsLogin;
43
+ exports.defaultClaudeClientProbe = defaultClaudeClientProbe;
44
+ exports.loadRunFakeBehavior = loadRunFakeBehavior;
45
+ exports.newRunId = newRunId;
46
+ exports.writeRunMeta = writeRunMeta;
47
+ exports.findReceiptDeckMismatch = findReceiptDeckMismatch;
48
+ exports.validateRunPrereqs = validateRunPrereqs;
49
+ exports.printRunHelp = printRunHelp;
50
+ exports.runRun = runRun;
51
+ exports.runRunCommand = runRunCommand;
52
+ /**
53
+ * `agent-deck run` — start a local agent session bound to a deck (NOT-400, NOT-404).
54
+ *
55
+ * Productizes the throwaway botmancers runner: one command takes a deck, a
56
+ * working directory, and a brief, streams the session to a JSONL log, and
57
+ * records a small meta.json that later tickets (detach, status) can read.
58
+ * Resume continues the same client session id under a new run log.
59
+ *
60
+ * Client-specific launching lives behind the {@link ClientAdapter} interface
61
+ * (`run-adapter.ts`); this file owns arg parsing, the deck binding gate,
62
+ * tool profiles, the JSONL log, and meta.json. New adapters plug in without
63
+ * touching this file's run/log/status paths.
64
+ *
65
+ * `--detach` (NOT-401) re-executes this same command as a background child;
66
+ * status/log/list (NOT-402) poll the run record the child writes.
67
+ */
68
+ const node_child_process_1 = require("node:child_process");
69
+ const node_crypto_1 = require("node:crypto");
70
+ const node_fs_1 = __importDefault(require("node:fs"));
71
+ const node_path_1 = __importDefault(require("node:path"));
72
+ const backend_runtime_1 = require("./backend-runtime");
73
+ const paths_1 = require("./managed/paths");
74
+ const mcp_launcher_1 = require("./mcp-launcher");
75
+ const run_adapter_1 = require("./run-adapter");
76
+ const run_claude_1 = require("./run-claude");
77
+ const run_codex_1 = require("./run-codex");
78
+ const run_cursor_1 = require("./run-cursor");
79
+ const run_muse_1 = require("./run-muse");
80
+ // Tool profiles stay owned by the shared run code (re-exported so existing
81
+ // `from './run'` imports keep working).
82
+ var run_adapter_2 = require("./run-adapter");
83
+ Object.defineProperty(exports, "allowedToolsForProfile", { enumerable: true, get: function () { return run_adapter_2.allowedToolsForProfile; } });
84
+ Object.defineProperty(exports, "RUN_EDIT_TOOLS", { enumerable: true, get: function () { return run_adapter_2.RUN_EDIT_TOOLS; } });
85
+ Object.defineProperty(exports, "RUN_PERMISSION_MODE", { enumerable: true, get: function () { return run_adapter_2.RUN_PERMISSION_MODE; } });
86
+ Object.defineProperty(exports, "RUN_READONLY_TOOLS", { enumerable: true, get: function () { return run_adapter_2.RUN_READONLY_TOOLS; } });
87
+ Object.defineProperty(exports, "RUN_SETTING_SOURCES", { enumerable: true, get: function () { return run_adapter_2.RUN_SETTING_SOURCES; } });
88
+ Object.defineProperty(exports, "RUN_TOOL_PROFILES", { enumerable: true, get: function () { return run_adapter_2.RUN_TOOL_PROFILES; } });
89
+ function parseRunArgs(args) {
90
+ let deckRef;
91
+ let client;
92
+ let cwd;
93
+ let prompt;
94
+ let promptFile;
95
+ let tools = 'readonly';
96
+ let log;
97
+ let resume;
98
+ let detach = false;
99
+ let runIdOverride;
100
+ for (let i = 0; i < args.length; i += 1) {
101
+ const arg = args[i];
102
+ if (arg === '--client') {
103
+ client = args[++i];
104
+ }
105
+ else if (arg === '--cwd') {
106
+ cwd = args[++i];
107
+ }
108
+ else if (arg === '--prompt') {
109
+ prompt = args[++i];
110
+ }
111
+ else if (arg === '--prompt-file') {
112
+ promptFile = args[++i];
113
+ }
114
+ else if (arg === '--tools') {
115
+ tools = args[++i] ?? '';
116
+ }
117
+ else if (arg === '--log') {
118
+ log = args[++i];
119
+ }
120
+ else if (arg === '--resume') {
121
+ resume = args[++i];
122
+ }
123
+ else if (arg === '--detach') {
124
+ detach = true;
125
+ }
126
+ else if (arg === '--_run-id') {
127
+ runIdOverride = args[++i];
128
+ }
129
+ else if (arg === '--help' || arg === '-h') {
130
+ return { error: 'help' };
131
+ }
132
+ else if (arg.startsWith('-')) {
133
+ return { error: `Unknown run option: ${arg}` };
134
+ }
135
+ else if (!deckRef) {
136
+ deckRef = arg;
137
+ }
138
+ else {
139
+ return { error: `Unexpected argument: ${arg}` };
140
+ }
141
+ }
142
+ if (!deckRef) {
143
+ return { error: 'deck name or id is required' };
144
+ }
145
+ if (!client) {
146
+ return { error: (0, run_adapter_1.missingRunClientMessage)() };
147
+ }
148
+ if (client !== 'claude' && client !== 'codex' && client !== 'cursor' && client !== 'muse') {
149
+ return { error: (0, run_adapter_1.runClientMessage)(client) };
150
+ }
151
+ if (!cwd) {
152
+ return { error: '--cwd is required' };
153
+ }
154
+ if (prompt !== undefined && promptFile !== undefined) {
155
+ return { error: 'Pass exactly one of --prompt or --prompt-file' };
156
+ }
157
+ if (prompt === undefined && promptFile === undefined) {
158
+ return { error: 'A brief is required: pass --prompt <text> or --prompt-file <path>' };
159
+ }
160
+ if (tools !== 'readonly' && tools !== 'edit') {
161
+ return { error: `--tools must be one of: readonly, edit (got "${tools}")` };
162
+ }
163
+ return {
164
+ deckRef,
165
+ client,
166
+ cwd: node_path_1.default.resolve(cwd),
167
+ ...(prompt !== undefined ? { prompt } : {}),
168
+ ...(promptFile !== undefined ? { promptFile } : {}),
169
+ tools,
170
+ ...(log ? { log: node_path_1.default.resolve(log) } : {}),
171
+ ...(resume ? { resume } : {}),
172
+ detach,
173
+ ...(runIdOverride ? { runIdOverride } : {}),
174
+ };
175
+ }
176
+ /** The one fix command both preflight remedies name. Verified against `claude auth --help`. */
177
+ exports.CLAUDE_AUTH_LOGIN_COMMAND = 'claude auth login';
178
+ exports.RUN_CLAUDE_NOT_INSTALLED_ERROR = `Claude Code is not installed (no \`claude\` on PATH). Install Claude Code, then run \`${exports.CLAUDE_AUTH_LOGIN_COMMAND}\` to sign in, then retry.`;
179
+ exports.RUN_CLAUDE_NOT_SIGNED_IN_ERROR = `Claude Code is not signed in. Run \`${exports.CLAUDE_AUTH_LOGIN_COMMAND}\` to sign in with your own account, then retry.`;
180
+ /** First executable `name` on `pathEnv`, or null. Pure apart from the fs check. */
181
+ function findExecutableOnPath(name, pathEnv = process.env.PATH ?? '') {
182
+ for (const dir of pathEnv.split(node_path_1.default.delimiter)) {
183
+ if (!dir) {
184
+ continue;
185
+ }
186
+ const candidate = node_path_1.default.join(dir, name);
187
+ try {
188
+ node_fs_1.default.accessSync(candidate, node_fs_1.default.constants.X_OK);
189
+ return candidate;
190
+ }
191
+ catch {
192
+ // Not here; keep scanning.
193
+ }
194
+ }
195
+ return null;
196
+ }
197
+ /**
198
+ * True only when `claude auth status` JSON reports a login. Never throws:
199
+ * garbage, an empty string, or a logged-out report all read as signed out.
200
+ */
201
+ function claudeAuthStatusShowsLogin(stdout) {
202
+ try {
203
+ return JSON.parse(stdout).loggedIn === true;
204
+ }
205
+ catch {
206
+ return false;
207
+ }
208
+ }
209
+ /**
210
+ * Default Claude Code preflight: the `claude` CLI must be on PATH and
211
+ * `claude auth status` must report a login. Local only — no model call.
212
+ * An explicit ANTHROPIC_API_KEY passes without the OAuth check once the
213
+ * `claude` CLI is found on PATH (API-key onboarding stays out of scope;
214
+ * the friend path signs in via `claude auth login`). Any other failure —
215
+ * logged out, unparseable output, a hung or crashed `claude` — reads as
216
+ * not signed in; re-running the login is the remedy in every case.
217
+ */
218
+ async function defaultClaudeClientProbe() {
219
+ const claudePath = findExecutableOnPath('claude');
220
+ if (!claudePath) {
221
+ return { ok: false, reason: 'not-installed' };
222
+ }
223
+ if ((process.env.ANTHROPIC_API_KEY ?? '').trim()) {
224
+ return { ok: true };
225
+ }
226
+ try {
227
+ const out = (0, node_child_process_1.execFileSync)(claudePath, ['auth', 'status'], {
228
+ encoding: 'utf8',
229
+ timeout: 15_000,
230
+ stdio: ['ignore', 'pipe', 'ignore'],
231
+ });
232
+ return claudeAuthStatusShowsLogin(out)
233
+ ? { ok: true }
234
+ : { ok: false, reason: 'not-signed-in' };
235
+ }
236
+ catch (error) {
237
+ // The binary vanished between the PATH scan and the exec.
238
+ if (error?.code === 'ENOENT') {
239
+ return { ok: false, reason: 'not-installed' };
240
+ }
241
+ return { ok: false, reason: 'not-signed-in' };
242
+ }
243
+ }
244
+ /**
245
+ * Test-only fake runner for the `run --detach` integration suite. The detached
246
+ * child is a real process, so dependency injection cannot reach it; instead the
247
+ * suite points AGENT_DECK_RUN_FAKE at a JSON behavior file and both the starter
248
+ * (deck lookup) and the child (deck lookup + model stream) honor it. Explicit
249
+ * `deps` always win, so unit tests are unaffected. Never set in production.
250
+ */
251
+ exports.RUN_FAKE_ENV = 'AGENT_DECK_RUN_FAKE';
252
+ function loadRunFakeBehavior() {
253
+ const file = process.env[exports.RUN_FAKE_ENV]?.trim();
254
+ if (!file) {
255
+ return null;
256
+ }
257
+ try {
258
+ return JSON.parse(node_fs_1.default.readFileSync(file, 'utf8'));
259
+ }
260
+ catch {
261
+ throw new Error(`Cannot read ${exports.RUN_FAKE_ENV} behavior file: ${file}`);
262
+ }
263
+ }
264
+ function sleep(ms) {
265
+ return new Promise((resolve) => setTimeout(resolve, ms));
266
+ }
267
+ function fakeQueryFromBehavior(behavior) {
268
+ const sessionId = behavior.sessionId ?? 'ses_fake';
269
+ return async function* ({ prompt, options }) {
270
+ if (behavior.recordOptionsPath) {
271
+ node_fs_1.default.mkdirSync(node_path_1.default.dirname(behavior.recordOptionsPath), { recursive: true });
272
+ node_fs_1.default.writeFileSync(behavior.recordOptionsPath, `${JSON.stringify({ prompt, options }, null, 2)}\n`, 'utf8');
273
+ }
274
+ if (behavior.mode === 'crash') {
275
+ // A crash leaves no log line behind: sync write, then out with no yield.
276
+ node_fs_1.default.writeSync(2, `${behavior.crashStderr ?? 'fake runner crashed'}\n`);
277
+ process.exit(behavior.exitCode ?? 1);
278
+ }
279
+ if (behavior.lineDelayMs) {
280
+ await sleep(behavior.lineDelayMs);
281
+ }
282
+ if (behavior.mode === 'fail') {
283
+ throw new Error(behavior.error ?? 'fake runner failed');
284
+ }
285
+ yield {
286
+ type: 'assistant',
287
+ session_id: sessionId,
288
+ message: { content: [{ type: 'text', text: behavior.text ?? 'fake runner here' }] },
289
+ };
290
+ if (behavior.endDelayMs) {
291
+ await sleep(behavior.endDelayMs);
292
+ }
293
+ yield { type: 'result', subtype: 'success', session_id: sessionId, result: 'fake ok' };
294
+ };
295
+ }
296
+ function fakeResolveDeckFromBehavior(behavior) {
297
+ return async (ref) => ({
298
+ id: behavior.deckId ?? 'deck-fake',
299
+ name: behavior.deckName ?? ref,
300
+ });
301
+ }
302
+ function defaultResolveDeck(ref) {
303
+ return (0, backend_runtime_1.createCollectionAdmin)().resolveDeck(ref);
304
+ }
305
+ function selectResolveDeck(deps) {
306
+ if (deps.resolveDeck) {
307
+ return deps.resolveDeck;
308
+ }
309
+ const fake = loadRunFakeBehavior();
310
+ return fake ? fakeResolveDeckFromBehavior(fake) : defaultResolveDeck;
311
+ }
312
+ function selectClaudeProbe(deps) {
313
+ if (deps.claudeProbe) {
314
+ return deps.claudeProbe;
315
+ }
316
+ // The fake runner brings its own working client, like it brings its own
317
+ // deck and model stream; the real probe only guards the real transport.
318
+ if (loadRunFakeBehavior()) {
319
+ return async () => ({ ok: true });
320
+ }
321
+ return defaultClaudeClientProbe;
322
+ }
323
+ /**
324
+ * `query()` for the Claude adapter: explicit deps win, then the NOT-401
325
+ * detach-integration fake (the detached child is a real process, so the
326
+ * AGENT_DECK_RUN_FAKE behavior file is its only seam), then undefined so the
327
+ * adapter lazily requires the real Claude Agent SDK.
328
+ */
329
+ function selectClaudeQuery(deps) {
330
+ if (deps.query) {
331
+ return deps.query;
332
+ }
333
+ if (deps.claude?.query) {
334
+ return deps.claude.query;
335
+ }
336
+ const fake = loadRunFakeBehavior();
337
+ return fake ? fakeQueryFromBehavior(fake) : undefined;
338
+ }
339
+ function resolveAdapter(client, deps) {
340
+ if (client === 'codex') {
341
+ return (0, run_codex_1.createCodexAdapter)(deps.codex ?? {});
342
+ }
343
+ if (client === 'cursor') {
344
+ return (0, run_cursor_1.createCursorAdapter)(deps.cursor ?? {});
345
+ }
346
+ if (client === 'muse') {
347
+ return (0, run_muse_1.createMuseAdapter)(deps.muse ?? {});
348
+ }
349
+ const query = selectClaudeQuery(deps);
350
+ return (0, run_claude_1.createClaudeAdapter)({
351
+ ...deps.claude,
352
+ ...(query ? { query } : {}),
353
+ });
354
+ }
355
+ function newRunId() {
356
+ const stamp = new Date().toISOString().replace(/[-:]/g, '').replace(/\..+/, '');
357
+ return `run-${stamp}-${(0, node_crypto_1.randomBytes)(3).toString('hex')}`;
358
+ }
359
+ /**
360
+ * Atomic run-record write: readers (`run status`, detach tests, orchestrators)
361
+ * poll meta.json while the run child rewrites it, and a plain writeFileSync
362
+ * truncates before writing — a concurrent read lands on empty/partial bytes
363
+ * and JSON.parse throws. Writing to a sibling temp file and renaming keeps
364
+ * every observed state a complete document (rename is atomic on POSIX and
365
+ * Windows). The pid suffix keeps the starter's and the child's temp files
366
+ * distinct when both target the same meta.json.
367
+ */
368
+ function writeRunMeta(metaPath, meta) {
369
+ const tmpPath = `${metaPath}.tmp-${process.pid}`;
370
+ node_fs_1.default.writeFileSync(tmpPath, `${JSON.stringify(meta, null, 2)}\n`, 'utf8');
371
+ node_fs_1.default.renameSync(tmpPath, metaPath);
372
+ }
373
+ function writeMeta(metaPath, meta) {
374
+ writeRunMeta(metaPath, meta);
375
+ }
376
+ /**
377
+ * Session receipt check: the agent harness prints the bound deck as one
378
+ * `get_session_context` receipt line rendering `display_summary`
379
+ * (e.g. `◆ dev · 2 MCP · 0 keys · 1 playbooks`). Returns the mismatching deck
380
+ * token when the log positively shows a deck other than the requested one;
381
+ * a missing receipt is not a mismatch (the pre-start assignment gate is the
382
+ * fail-closed check; this is a second net for a mis-bound session).
383
+ * The fragments are joined before scanning: real clients stream token-level
384
+ * deltas, so one receipt line can arrive split across many text events and a
385
+ * per-fragment scan would miss the mismatch.
386
+ */
387
+ function findReceiptDeckMismatch(texts, deck) {
388
+ const joined = texts.join('');
389
+ const pattern = /◆\s*([^·\r\n]+?)\s*·/g;
390
+ for (const match of joined.matchAll(pattern)) {
391
+ const token = match[1].trim();
392
+ if (token && token !== deck.name && token !== deck.id) {
393
+ return token;
394
+ }
395
+ }
396
+ return null;
397
+ }
398
+ /**
399
+ * Everything a run must prove before it may start streaming: a readable brief,
400
+ * an existing cwd, a known deck, and a folder assignment already bound to that
401
+ * deck. Shared by the foreground run and the `--detach` starter so both fail
402
+ * closed on the same inputs with the same messages.
403
+ */
404
+ async function validateRunPrereqs(options, deps = {}) {
405
+ const resolveDeck = selectResolveDeck(deps);
406
+ const resolveLaunchPlan = deps.resolveLaunchPlan ?? mcp_launcher_1.resolveMcpLaunchPlan;
407
+ let promptText;
408
+ if (options.promptFile !== undefined) {
409
+ try {
410
+ promptText = node_fs_1.default.readFileSync(options.promptFile, 'utf8');
411
+ }
412
+ catch {
413
+ return { error: `Cannot read prompt file: ${options.promptFile}` };
414
+ }
415
+ }
416
+ else {
417
+ promptText = options.prompt ?? '';
418
+ }
419
+ if (!promptText.trim()) {
420
+ return { error: 'Prompt is empty: pass a non-empty --prompt or --prompt-file' };
421
+ }
422
+ let cwdStat;
423
+ try {
424
+ cwdStat = node_fs_1.default.statSync(options.cwd);
425
+ }
426
+ catch {
427
+ return { error: `--cwd does not exist: ${options.cwd}` };
428
+ }
429
+ if (!cwdStat.isDirectory()) {
430
+ return { error: `--cwd is not a directory: ${options.cwd}` };
431
+ }
432
+ const deck = await resolveDeck(options.deckRef);
433
+ if (!deck) {
434
+ return { error: `Deck not found: ${options.deckRef}` };
435
+ }
436
+ // Fail closed before any model call: the session's MCP launch must already
437
+ // resolve to the requested deck. Never rewrite the folder assignment here.
438
+ const plan = await resolveLaunchPlan(options.cwd);
439
+ if (plan.deckId !== deck.id) {
440
+ if (!plan.deckId) {
441
+ return {
442
+ error: `No deck assigned in ${options.cwd}: run \`agent-deck use ${options.deckRef}\` there first, then retry. Refusing to start an unbound session.`,
443
+ };
444
+ }
445
+ return {
446
+ error: `Folder ${options.cwd} is bound to "${plan.deckName ?? plan.deckId}" (${plan.deckId}), not "${options.deckRef}". ` +
447
+ `Run \`agent-deck use ${options.deckRef}\` in that folder to rebind it, then retry. Refusing to start a mis-bound session.`,
448
+ };
449
+ }
450
+ // Last and slowest (it shells out to `claude auth status`): for claude runs,
451
+ // the Claude Code client must be installed and signed in before any model
452
+ // call (NOT-405). Other clients (codex) skip this probe; their adapters own
453
+ // their preflight. Shared by the foreground run and the `--detach` starter,
454
+ // so both fail closed with the same one-line remedy instead of an SDK
455
+ // stack trace.
456
+ if (options.client === 'claude') {
457
+ const client = await selectClaudeProbe(deps)();
458
+ if (!client.ok) {
459
+ return {
460
+ error: client.reason === 'not-installed'
461
+ ? exports.RUN_CLAUDE_NOT_INSTALLED_ERROR
462
+ : exports.RUN_CLAUDE_NOT_SIGNED_IN_ERROR,
463
+ };
464
+ }
465
+ }
466
+ return { promptText, deck };
467
+ }
468
+ function printRunHelp() {
469
+ console.log(`Usage:
470
+ agent-deck run <deck> --client claude|codex|cursor|muse --cwd <dir> (--prompt <text>|--prompt-file <path>) [--tools readonly|edit] [--log <path>] [--resume <sessionId>] [--detach]
471
+ agent-deck run status <runId> [--json]
472
+ agent-deck run log <runId> [--tail <n>] [--follow] [--json]
473
+ agent-deck run list [--json] [--limit <n>]
474
+
475
+ Starts a local agent session bound to <deck> and streams a JSONL log.
476
+ The folder's assignment must already name <deck> (agent-deck use <deck>);
477
+ run never rewrites .agent-deck/use.json and fails closed on a mismatch.
478
+ Claude runs require the Claude Code CLI on this machine, signed in
479
+ ('claude auth login'); otherwise run fails before any model call.
480
+ A set ANTHROPIC_API_KEY skips the sign-in check (the SDK accepts the
481
+ key instead); API-key setup is otherwise out of scope for this path.
482
+
483
+ --client claude|codex|cursor|muse
484
+ Required.
485
+ codex maps --tools to -s read-only|workspace-write;
486
+ cursor maps --tools edit to --force (readonly passes
487
+ no write flag); all log lines land when the run ends;
488
+ muse maps readonly to --disable-write --disable-shell
489
+ (edit uses the default bounded permissions).
490
+ --cwd <dir> Required. Session working directory.
491
+ --prompt <text> Brief text (exactly one of --prompt/--prompt-file).
492
+ --prompt-file <path> Brief read from a file.
493
+ --tools readonly|edit Tool profile, default readonly. edit adds Edit, Write
494
+ and git-scoped Bash (diff/add/commit/checkout -b).
495
+ --log <path> Log file. Default: ~/.agent-deck/runs/<runId>/run.jsonl
496
+ (meta.json sits next to the log in both cases).
497
+ --resume <sessionId> Continue a previous session id. The profile
498
+ passed on this invocation applies; nothing is inherited.
499
+ codex resume keeps the thread's original sandbox, so
500
+ --tools edit cannot be resumed and fails closed.
501
+ --detach Start in the background in a new session, so the run
502
+ survives this shell disconnecting. Prints runId, pid,
503
+ and the log path, then exits. Every other flag is
504
+ passed to the run unchanged. Stop it with kill <pid>.
505
+
506
+ Poll a run without parsing files by hand:
507
+
508
+ status <runId> State (running|ended|error|died), sessionId, log path.
509
+ log <runId> One readable line per event; --json prints raw JSONL.
510
+ list Recent runs, newest first (default limit 20).`);
511
+ }
512
+ async function runRun(options, deps = {}) {
513
+ const homeDir = deps.homeDir ?? (0, paths_1.agentDeckHome)();
514
+ const prereq = await validateRunPrereqs(options, deps);
515
+ if ('error' in prereq) {
516
+ console.error(prereq.error);
517
+ return 1;
518
+ }
519
+ const { promptText, deck } = prereq;
520
+ const adapter = resolveAdapter(options.client, deps);
521
+ const preflight = await adapter.preflight();
522
+ if (!preflight.ok) {
523
+ console.error(preflight.remedy);
524
+ return 1;
525
+ }
526
+ const startPlan = adapter.planStart({
527
+ cwd: options.cwd,
528
+ prompt: promptText,
529
+ tools: options.tools,
530
+ ...(options.resume ? { resumeId: options.resume } : {}),
531
+ });
532
+ if (!startPlan.ok) {
533
+ console.error(startPlan.remedy);
534
+ return 1;
535
+ }
536
+ const runId = options.runIdOverride ?? newRunId();
537
+ const logPath = options.log ?? node_path_1.default.join(homeDir, 'runs', runId, 'run.jsonl');
538
+ const metaPath = node_path_1.default.join(node_path_1.default.dirname(logPath), 'meta.json');
539
+ node_fs_1.default.mkdirSync(node_path_1.default.dirname(logPath), { recursive: true });
540
+ const meta = {
541
+ runId,
542
+ deck: deck.id,
543
+ client: options.client,
544
+ tools: options.tools,
545
+ cwd: options.cwd,
546
+ pid: process.pid,
547
+ startedAt: new Date().toISOString(),
548
+ state: 'running',
549
+ ...(startPlan.trustOverride ? { trustOverride: true } : {}),
550
+ };
551
+ writeMeta(metaPath, meta);
552
+ const appendLine = (line) => {
553
+ node_fs_1.default.appendFileSync(logPath, `${JSON.stringify(line)}\n`, 'utf8');
554
+ };
555
+ let sessionId = options.resume ?? null;
556
+ let metaSessionWritten = false;
557
+ const captureSessionId = (value) => {
558
+ if (typeof value === 'string' && value) {
559
+ sessionId = value;
560
+ if (!metaSessionWritten) {
561
+ metaSessionWritten = true;
562
+ meta.sessionId = value;
563
+ writeMeta(metaPath, meta);
564
+ }
565
+ else {
566
+ meta.sessionId = value;
567
+ }
568
+ }
569
+ };
570
+ let lastResultSubtype;
571
+ const seenTexts = [];
572
+ try {
573
+ const stream = adapter.start({
574
+ cwd: options.cwd,
575
+ prompt: promptText,
576
+ tools: options.tools,
577
+ ...(options.resume ? { resumeId: options.resume } : {}),
578
+ });
579
+ for await (const event of stream) {
580
+ if (event.sessionId !== undefined) {
581
+ captureSessionId(event.sessionId);
582
+ }
583
+ if (event.kind === 'text') {
584
+ seenTexts.push(event.text);
585
+ appendLine({ t: 'text', ts: new Date().toISOString(), text: event.text });
586
+ }
587
+ else if (event.kind === 'tool') {
588
+ appendLine({ t: 'tool', ts: new Date().toISOString(), name: event.name });
589
+ }
590
+ else if (event.kind === 'result') {
591
+ lastResultSubtype = event.subtype;
592
+ appendLine({
593
+ t: 'result',
594
+ ts: new Date().toISOString(),
595
+ sessionId,
596
+ subtype: event.subtype,
597
+ result: event.result,
598
+ });
599
+ }
600
+ else {
601
+ appendLine({
602
+ t: 'notice',
603
+ ts: new Date().toISOString(),
604
+ message: event.message,
605
+ sessionId,
606
+ });
607
+ }
608
+ }
609
+ appendLine({ t: 'end', ts: new Date().toISOString(), sessionId });
610
+ }
611
+ catch (error) {
612
+ appendLine({
613
+ t: 'error',
614
+ ts: new Date().toISOString(),
615
+ message: String(error instanceof Error ? error.message : error).slice(0, 500),
616
+ sessionId,
617
+ });
618
+ meta.state = 'error';
619
+ if (sessionId) {
620
+ meta.sessionId = sessionId;
621
+ }
622
+ writeMeta(metaPath, meta);
623
+ console.log(`run ${runId} error: log ${logPath} sessionId ${sessionId ?? 'unknown'}`);
624
+ return 1;
625
+ }
626
+ let success = lastResultSubtype === 'success';
627
+ if (success) {
628
+ const mismatch = findReceiptDeckMismatch(seenTexts, deck);
629
+ if (mismatch) {
630
+ success = false;
631
+ console.error(`Session receipt names deck "${mismatch}", not "${deck.name}": marking run error. ` +
632
+ `Rebind the folder with \`agent-deck use ${options.deckRef}\` and retry.`);
633
+ }
634
+ }
635
+ meta.state = success ? 'ended' : 'error';
636
+ if (sessionId) {
637
+ meta.sessionId = sessionId;
638
+ }
639
+ writeMeta(metaPath, meta);
640
+ console.log(`run ${runId} ${meta.state}: log ${logPath} sessionId ${sessionId ?? 'unknown'}`);
641
+ return success ? 0 : 1;
642
+ }
643
+ async function runRunCommand(args, deps = {}) {
644
+ // Poll subcommands (NOT-402) share the `run` prefix with launch (NOT-400).
645
+ const [head, ...tail] = args;
646
+ if (head === 'status' || head === 'log' || head === 'list') {
647
+ const inspect = await Promise.resolve().then(() => __importStar(require('./run-status')));
648
+ if (head === 'status') {
649
+ return inspect.runRunStatusCommand(tail, { homeDir: deps.homeDir });
650
+ }
651
+ if (head === 'log') {
652
+ return inspect.runRunLogCommand(tail, {
653
+ homeDir: deps.homeDir,
654
+ ...(deps.followPollMs !== undefined ? { followPollMs: deps.followPollMs } : {}),
655
+ });
656
+ }
657
+ return inspect.runRunListCommand(tail, { homeDir: deps.homeDir });
658
+ }
659
+ const parsed = parseRunArgs(args);
660
+ if ('error' in parsed) {
661
+ if (parsed.error === 'help') {
662
+ printRunHelp();
663
+ return 0;
664
+ }
665
+ console.error(parsed.error);
666
+ return 1;
667
+ }
668
+ try {
669
+ if (parsed.detach) {
670
+ const { startDetachedRun } = await Promise.resolve().then(() => __importStar(require('./run-detach')));
671
+ return await startDetachedRun(args, parsed, deps);
672
+ }
673
+ return await runRun(parsed, deps);
674
+ }
675
+ catch (error) {
676
+ console.error(error instanceof Error ? error.message : error);
677
+ return 1;
678
+ }
679
+ }
680
+ //# sourceMappingURL=run.js.map