@crewx/cli 0.9.0-rc.14 → 0.9.0-rc.141

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 (65) hide show
  1. package/dist/bootstrap/codex-writable-roots.d.ts +13 -0
  2. package/dist/bootstrap/codex-writable-roots.js +25 -0
  3. package/dist/bootstrap/crewx-cli.js +27 -6
  4. package/dist/builtin.d.ts +1 -1
  5. package/dist/builtin.js +7 -2
  6. package/dist/commands/agent.js +1 -107
  7. package/dist/commands/cal.d.ts +15 -0
  8. package/dist/commands/cal.js +44 -0
  9. package/dist/commands/db.d.ts +3 -0
  10. package/dist/commands/db.js +533 -1
  11. package/dist/commands/detached.d.ts +95 -0
  12. package/dist/commands/detached.js +620 -0
  13. package/dist/commands/doctor.d.ts +77 -0
  14. package/dist/commands/doctor.js +475 -22
  15. package/dist/commands/emit-trailer.d.ts +25 -0
  16. package/dist/commands/emit-trailer.js +33 -0
  17. package/dist/commands/execute.d.ts +8 -4
  18. package/dist/commands/execute.js +200 -51
  19. package/dist/commands/hook/command-marker.d.ts +2 -0
  20. package/dist/commands/hook/command-marker.js +5 -0
  21. package/dist/commands/hook/install.d.ts +0 -1
  22. package/dist/commands/hook/install.js +75 -61
  23. package/dist/commands/hook/status.js +3 -3
  24. package/dist/commands/hook/uninstall.js +2 -2
  25. package/dist/commands/init.d.ts +34 -0
  26. package/dist/commands/init.js +132 -59
  27. package/dist/commands/kill.d.ts +2 -8
  28. package/dist/commands/kill.js +6 -47
  29. package/dist/commands/log.js +4 -3
  30. package/dist/commands/parse-common-flags.d.ts +5 -1
  31. package/dist/commands/parse-common-flags.js +35 -4
  32. package/dist/commands/ps.d.ts +2 -2
  33. package/dist/commands/ps.js +25 -15
  34. package/dist/commands/publish.d.ts +1 -0
  35. package/dist/commands/publish.js +303 -0
  36. package/dist/commands/query.d.ts +7 -2
  37. package/dist/commands/query.js +195 -48
  38. package/dist/commands/registry.js +4 -2
  39. package/dist/commands/resolve-prompt.js +23 -1
  40. package/dist/commands/resolve-recipient.d.ts +14 -0
  41. package/dist/commands/resolve-recipient.js +47 -0
  42. package/dist/commands/restart.d.ts +3 -1
  43. package/dist/commands/restart.js +14 -4
  44. package/dist/commands/result.d.ts +8 -3
  45. package/dist/commands/result.js +149 -10
  46. package/dist/commands/shortcut.d.ts +1 -0
  47. package/dist/commands/shortcut.js +267 -0
  48. package/dist/commands/slack.js +2 -1
  49. package/dist/commands/stop.d.ts +8 -0
  50. package/dist/commands/stop.js +119 -0
  51. package/dist/index.d.ts +1 -1
  52. package/dist/index.js +4 -1
  53. package/dist/logging.d.ts +1 -1
  54. package/dist/logging.js +3 -2
  55. package/dist/main.d.ts +3 -2
  56. package/dist/main.js +99 -24
  57. package/dist/utils/env-defaults.d.ts +2 -5
  58. package/dist/utils/env-defaults.js +10 -5
  59. package/dist/utils/inherited-trace.d.ts +24 -0
  60. package/dist/utils/inherited-trace.js +40 -0
  61. package/dist/utils/product-package-root.d.ts +6 -0
  62. package/dist/utils/product-package-root.js +39 -0
  63. package/dist/utils/sdk-compat.d.ts +24 -0
  64. package/dist/utils/sdk-compat.js +120 -0
  65. package/package.json +16 -12
@@ -44,6 +44,9 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
44
44
  return (mod && mod.__esModule) ? mod : { "default": mod };
45
45
  };
46
46
  Object.defineProperty(exports, "__esModule", { value: true });
47
+ exports.buildDefaultAgents = buildDefaultAgents;
48
+ exports.parseInitProvider = parseInitProvider;
49
+ exports.resolveInitProvider = resolveInitProvider;
47
50
  exports.handleInit = handleInit;
48
51
  const fs_1 = require("fs");
49
52
  const path_1 = require("path");
@@ -52,6 +55,7 @@ const git = __importStar(require("isomorphic-git"));
52
55
  const nodeFs = __importStar(require("fs"));
53
56
  const os_1 = __importDefault(require("os"));
54
57
  const repository_1 = require("@crewx/sdk/repository");
58
+ const sdk_1 = require("@crewx/sdk");
55
59
  const install_1 = require("./hook/install");
56
60
  const STATUSLINE_SCRIPT_NAME = 'claude-usage-statusline.js';
57
61
  const STATUSLINE_SCRIPT_MARKER = 'CrewX claude-usage-statusline';
@@ -84,6 +88,11 @@ const CREWX_MARKER = '# CrewX runtime';
84
88
  const CREWX_GITIGNORE = `# CrewX runtime
85
89
  .crewx/
86
90
 
91
+ # Secrets (workspace .env — see docs/manual)
92
+ .env
93
+ .env.*
94
+ !.env.example
95
+
87
96
  # Memory runtime state (regenerable from entries/)
88
97
  memory/*/.dirty-summary
89
98
  memory/*/graph.json
@@ -97,59 +106,66 @@ skills/*/.crewx/
97
106
 
98
107
  # OS
99
108
  .DS_Store`.trim();
100
- const DEFAULT_AGENTS = [
101
- {
102
- id: 'planner',
103
- name: 'Planner',
104
- role: 'Product/Architecture Planner',
105
- team: 'core',
106
- provider: 'acp/claude',
107
- working_directory: './',
108
- description: 'Owns requirements analysis, design, and work breakdown',
109
- options: {
110
- query: { effort: 'high' },
111
- execute: { effort: 'high', mode: 'bypassPermissions' },
112
- },
113
- inline: {
114
- model: 'claude-opus-4-7',
115
- prompt: `You are a planner. Break down user goals into concrete,\ntestable steps. Challenge assumptions. Never write code directly.`,
116
- },
117
- },
118
- {
119
- id: 'developer',
120
- name: 'Developer',
121
- role: 'Implementation Engineer',
122
- team: 'core',
123
- provider: 'acp/claude',
124
- working_directory: './',
125
- description: 'Implements code changes and tests',
126
- options: {
127
- query: { effort: 'high' },
128
- execute: { effort: 'high', mode: 'bypassPermissions' },
129
- },
130
- inline: {
131
- model: 'claude-sonnet-4-6',
132
- prompt: `Implement what the planner specifies. Prefer editing\nexisting files. Run tests before reporting done.`,
109
+ /** Build the three default agents using the selected CLI provider profile. */
110
+ function buildDefaultAgents(provider) {
111
+ const profile = (0, sdk_1.getDefaultAgentProfile)(provider);
112
+ if (!profile) {
113
+ throw new Error(`DEFAULT_AGENT_PROFILE_NOT_FOUND: ${provider}`);
114
+ }
115
+ return [
116
+ {
117
+ id: 'planner',
118
+ name: 'Planner',
119
+ role: 'Product/Architecture Planner',
120
+ team: 'core',
121
+ provider,
122
+ working_directory: './',
123
+ description: 'Owns requirements analysis, design, and work breakdown',
124
+ options: {
125
+ query: profile.planner.query,
126
+ execute: profile.planner.execute,
127
+ },
128
+ inline: {
129
+ model: profile.planner.model,
130
+ prompt: `You are a planner. Break down user goals into concrete,\ntestable steps. Challenge assumptions. Never write code directly.`,
131
+ },
133
132
  },
134
- },
135
- {
136
- id: 'reviewer',
137
- name: 'Reviewer',
138
- role: 'Code Reviewer',
139
- team: 'core',
140
- provider: 'acp/claude',
141
- working_directory: './',
142
- description: 'Reviews code quality, security, and readability',
143
- options: {
144
- query: { effort: 'high' },
145
- execute: { effort: 'high', mode: 'bypassPermissions' },
133
+ {
134
+ id: 'developer',
135
+ name: 'Developer',
136
+ role: 'Implementation Engineer',
137
+ team: 'core',
138
+ provider,
139
+ working_directory: './',
140
+ description: 'Implements code changes and tests',
141
+ options: {
142
+ query: profile.worker.query,
143
+ execute: profile.worker.execute,
144
+ },
145
+ inline: {
146
+ model: profile.worker.model,
147
+ prompt: `Implement what the planner specifies. Prefer editing\nexisting files. Run tests before reporting done.`,
148
+ },
146
149
  },
147
- inline: {
148
- model: 'claude-sonnet-4-6',
149
- prompt: `Review diffs for correctness, security, readability.\nCite file:line. Be blunt, not polite.`,
150
+ {
151
+ id: 'reviewer',
152
+ name: 'Reviewer',
153
+ role: 'Code Reviewer',
154
+ team: 'core',
155
+ provider,
156
+ working_directory: './',
157
+ description: 'Reviews code quality, security, and readability',
158
+ options: {
159
+ query: profile.worker.query,
160
+ execute: profile.worker.execute,
161
+ },
162
+ inline: {
163
+ model: profile.worker.model,
164
+ prompt: `Review diffs for correctness, security, readability.\nCite file:line. Be blunt, not polite.`,
165
+ },
150
166
  },
151
- },
152
- ];
167
+ ];
168
+ }
153
169
  const GOAL_TEMPLATE = `---
154
170
  type: goal
155
171
  description: CrewX Goal — OKR
@@ -370,10 +386,11 @@ function generateDefaultYaml(agents) {
370
386
  description: "${a.description}"
371
387
  options:
372
388
  query:
373
- effort: "high"
389
+ mode: "${a.options.query.mode}"
390
+ effort: "${a.options.query.effort}"
374
391
  execute:
375
- effort: "high"
376
- mode: "bypassPermissions"
392
+ mode: "${a.options.execute.mode}"
393
+ effort: "${a.options.execute.effort}"
377
394
  inline:
378
395
  model: "${a.inline.model}"
379
396
  prompt: |
@@ -386,11 +403,51 @@ agents:
386
403
  ${agentBlocks}
387
404
 
388
405
  # Usage examples:
389
- # crewx query "@planner analyze this codebase"
390
- # crewx execute "@developer implement the feature described above"
406
+ # crewx q --to planner "analyze this codebase"
407
+ # crewx x --to developer "implement the feature described above"
391
408
  # crewx agent ls
392
409
  `;
393
410
  }
411
+ function isInitProvider(value) {
412
+ return value === 'cli/claude' || value === 'cli/codex';
413
+ }
414
+ function invalidProviderError(value) {
415
+ if (value === undefined || value.length === 0) {
416
+ return new Error('Missing --provider value. Expected: cli/claude | cli/codex');
417
+ }
418
+ return new Error(`Invalid --provider value: "${value}". Expected: cli/claude | cli/codex`);
419
+ }
420
+ /** Parse the provider option accepted by `crewx init`. */
421
+ function parseInitProvider(args) {
422
+ for (let index = 0; index < args.length; index += 1) {
423
+ const arg = args[index];
424
+ if (arg === '--provider') {
425
+ const value = args[index + 1];
426
+ if (value === undefined || value.startsWith('--'))
427
+ throw invalidProviderError(undefined);
428
+ if (!isInitProvider(value))
429
+ throw invalidProviderError(value);
430
+ return value;
431
+ }
432
+ if (arg?.startsWith('--provider=')) {
433
+ const value = arg.slice('--provider='.length);
434
+ if (!isInitProvider(value))
435
+ throw invalidProviderError(value);
436
+ return value;
437
+ }
438
+ }
439
+ return undefined;
440
+ }
441
+ /** Resolve an explicit provider or detect Claude, then Codex, using PATH only. */
442
+ function resolveInitProvider(provider, deps = {}) {
443
+ if (provider !== undefined)
444
+ return provider;
445
+ if ((0, sdk_1.isProviderAvailable)('cli/claude', deps))
446
+ return 'cli/claude';
447
+ if ((0, sdk_1.isProviderAvailable)('cli/codex', deps))
448
+ return 'cli/codex';
449
+ return 'cli/claude';
450
+ }
394
451
  /**
395
452
  * Programmatic init — creates crewx.yaml, support dirs, and optionally installs hooks.
396
453
  * Throws on unrecoverable errors (PATH_NOT_FOUND, YAML_CREATE_FAILED).
@@ -399,7 +456,8 @@ ${agentBlocks}
399
456
  * server paths both produce a row in ~/.crewx/crewx.db.
400
457
  */
401
458
  async function handleInit(opts) {
402
- const { path: target, skipHook = false, force = false } = opts;
459
+ const { path: target, skipHook = false, force = false, provider: requestedProvider } = opts;
460
+ const provider = resolveInitProvider(requestedProvider);
403
461
  const errors = [];
404
462
  let yamlCreated = false;
405
463
  let hookInstalled = false;
@@ -417,7 +475,7 @@ async function handleInit(opts) {
417
475
  }
418
476
  if (!skippedReason) {
419
477
  try {
420
- const content = generateDefaultYaml(DEFAULT_AGENTS);
478
+ const content = generateDefaultYaml(buildDefaultAgents(provider));
421
479
  (0, fs_1.writeFileSync)(yamlPath, content, 'utf-8');
422
480
  yamlCreated = true;
423
481
  }
@@ -435,7 +493,7 @@ async function handleInit(opts) {
435
493
  }
436
494
  }
437
495
  // Always create docs dirs and templates, regardless of whether yaml was skipped
438
- for (const dir of ['.crewx/logs', '.claude/commands', 'docs/goal', 'docs/daily', 'docs/wi']) {
496
+ for (const dir of ['.crewx/logs', '.claude/commands', 'docs/goal', 'docs/daily', 'docs/wi', 'workflows']) {
439
497
  try {
440
498
  (0, fs_1.mkdirSync)((0, path_1.join)(target, dir), { recursive: true });
441
499
  }
@@ -456,6 +514,21 @@ async function handleInit(opts) {
456
514
  console.log(`✅ ${t.dir}/ ${action} (${t.file})`);
457
515
  }
458
516
  }
517
+ try {
518
+ const workflowTemplatesDir = (0, sdk_1.resolveWorkflowTemplatesPath)();
519
+ for (const filename of [sdk_1.WI_DEFAULT_FLOW_FILENAME, sdk_1.WI_PLAN_FLOW_FILENAME]) {
520
+ const sourcePath = (0, path_1.join)(workflowTemplatesDir, filename);
521
+ const targetPath = (0, path_1.join)(target, 'workflows', filename);
522
+ if (force || !(0, fs_1.existsSync)(targetPath)) {
523
+ const action = (0, fs_1.existsSync)(targetPath) ? 'updated' : 'created';
524
+ (0, fs_1.copyFileSync)(sourcePath, targetPath);
525
+ console.log(`✅ workflows/ ${action} (${filename})`);
526
+ }
527
+ }
528
+ }
529
+ catch (e) {
530
+ errors.push(`WORKFLOW_TEMPLATE_FAILED:${e.message}`);
531
+ }
459
532
  // Always register workspace in ~/.crewx/crewx.db (best-effort, idempotent)
460
533
  let workspaceId;
461
534
  let slug;
@@ -487,7 +560,7 @@ async function handleInit(opts) {
487
560
  await initGitRepo(target, errors);
488
561
  // Idempotent statusLine merge into ~/.claude/settings.json
489
562
  const statusLineResult = ensureClaudeStatusLine();
490
- return { yamlCreated, hookInstalled, errors, skippedReason, workspaceId, slug, ...statusLineResult };
563
+ return { yamlCreated, hookInstalled, errors, provider, skippedReason, workspaceId, slug, ...statusLineResult };
491
564
  }
492
565
  function ensureClaudeStatusLine() {
493
566
  const home = os_1.default.homedir();
@@ -1,9 +1,3 @@
1
- /**
2
- * crewx kill handler.
3
- * Kills a running task by ID or kills all running tasks.
4
- *
5
- * Usage:
6
- * crewx kill <task-id> Kill a specific running task
7
- * crewx kill --all Kill all running tasks
8
- */
1
+ /** Compatibility entry point for `crewx kill`; the implementation lives in stop.ts. */
2
+ export { handleStop } from './stop';
9
3
  export declare function handleKill(args: string[]): Promise<void>;
@@ -1,52 +1,11 @@
1
1
  "use strict";
2
- /**
3
- * crewx kill handler.
4
- * Kills a running task by ID or kills all running tasks.
5
- *
6
- * Usage:
7
- * crewx kill <task-id> Kill a specific running task
8
- * crewx kill --all Kill all running tasks
9
- */
10
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.handleStop = void 0;
11
4
  exports.handleKill = handleKill;
12
- const repository_1 = require("@crewx/sdk/repository");
5
+ /** Compatibility entry point for `crewx kill`; the implementation lives in stop.ts. */
6
+ var stop_1 = require("./stop");
7
+ Object.defineProperty(exports, "handleStop", { enumerable: true, get: function () { return stop_1.handleStop; } });
8
+ const stop_2 = require("./stop");
13
9
  async function handleKill(args) {
14
- const killAll = args.includes('--all');
15
- const taskId = args.find(a => !a.startsWith('--'));
16
- if (!taskId && !killAll) {
17
- console.error('Error: Please provide a task ID or use --all to kill all running tasks.');
18
- console.error('');
19
- console.error('Usage:');
20
- console.error(' crewx kill <task-id> Kill a running task by ID');
21
- console.error(' crewx kill --all Kill all running tasks');
22
- process.exit(1);
23
- return;
24
- }
25
- const repo = new repository_1.TaskRepository();
26
- if (killAll) {
27
- const running = repo.getRunningTasks();
28
- if (running.length === 0) {
29
- console.log('No running tasks found.');
30
- return;
31
- }
32
- console.log(`Found ${running.length} running task(s). Killing...`);
33
- for (const task of running) {
34
- const r = repo.killTask(task.id);
35
- if (r.killed) {
36
- console.log(`Killed task ${task.id}${r.pid ? ` (PID: ${r.pid})` : ''}`);
37
- }
38
- else {
39
- console.log(`Task ${task.id} could not be killed (may have already finished)`);
40
- }
41
- }
42
- return;
43
- }
44
- const r = repo.killTask(taskId);
45
- if (r.killed) {
46
- console.log(`Killed task ${taskId}${r.pid ? ` (PID: ${r.pid})` : ''}`);
47
- }
48
- else {
49
- console.error(`Error: Task not found or not running: ${taskId}`);
50
- process.exit(1);
51
- }
10
+ return (0, stop_2.handleStop)(args, 'kill');
52
11
  }
@@ -10,6 +10,7 @@
10
10
  */
11
11
  Object.defineProperty(exports, "__esModule", { value: true });
12
12
  exports.handleLog = handleLog;
13
+ const sdk_1 = require("@crewx/sdk");
13
14
  const repository_1 = require("@crewx/sdk/repository");
14
15
  function statusIcon(status) {
15
16
  switch (status) {
@@ -36,10 +37,10 @@ async function handleLog(args) {
36
37
  console.log(`Status: ${statusIcon(task.status)} ${task.status}`);
37
38
  console.log(`Agent: ${task.agent_id ?? '—'}`);
38
39
  console.log(`Mode: ${task.mode ?? '—'}`);
39
- console.log(`Started: ${new Date(task.started_at).toLocaleString()}`);
40
+ console.log(`Started: ${(0, sdk_1.formatDisplayTimestamp)(new Date(task.started_at))}`);
40
41
  if (task.completed_at) {
41
42
  const duration = new Date(task.completed_at).getTime() - new Date(task.started_at).getTime();
42
- console.log(`Completed: ${new Date(task.completed_at).toLocaleString()} (${duration}ms)`);
43
+ console.log(`Completed: ${(0, sdk_1.formatDisplayTimestamp)(new Date(task.completed_at))} (${duration}ms)`);
43
44
  }
44
45
  console.log('='.repeat(60));
45
46
  console.log('');
@@ -78,7 +79,7 @@ async function handleLog(args) {
78
79
  : 'running...';
79
80
  console.log(`${idx + 1}. ${icon} ${task.id}`);
80
81
  console.log(` Agent: ${task.agent_id ?? '—'} Mode: ${task.mode ?? '—'}`);
81
- console.log(` Started: ${new Date(task.started_at).toLocaleString()}`);
82
+ console.log(` Started: ${(0, sdk_1.formatDisplayTimestamp)(new Date(task.started_at))}`);
82
83
  console.log(` Duration: ${duration}`);
83
84
  console.log('');
84
85
  });
@@ -2,7 +2,7 @@
2
2
  * Common flag parser for query/execute commands.
3
3
  *
4
4
  * Supports both `--flag=value` and `--flag value` forms.
5
- * Handles: --thread, --provider, --metadata, --verbose, --config/-c,
5
+ * Handles: --to, --thread, --provider, --model, --metadata, --verbose, --config/-c,
6
6
  * --output-format, --effort, --prompt-file/-f, --overdrive.
7
7
  *
8
8
  * Strict mode: unknown --xxx tokens after known flags are consumed throw an
@@ -12,8 +12,12 @@
12
12
  export interface CommonFlags {
13
13
  /** Thread name for conversation continuity. */
14
14
  thread?: string;
15
+ /** Explicit agent recipient. */
16
+ to?: string;
15
17
  /** Provider override (e.g., cli/claude). */
16
18
  provider?: string;
19
+ /** Model override (e.g., claude-sonnet-5). */
20
+ model?: string;
17
21
  /** Raw metadata JSON string. */
18
22
  metadata?: string;
19
23
  /** Enable verbose/debug output mode. */
@@ -3,7 +3,7 @@
3
3
  * Common flag parser for query/execute commands.
4
4
  *
5
5
  * Supports both `--flag=value` and `--flag value` forms.
6
- * Handles: --thread, --provider, --metadata, --verbose, --config/-c,
6
+ * Handles: --to, --thread, --provider, --model, --metadata, --verbose, --config/-c,
7
7
  * --output-format, --effort, --prompt-file/-f, --overdrive.
8
8
  *
9
9
  * Strict mode: unknown --xxx tokens after known flags are consumed throw an
@@ -61,8 +61,10 @@ exports.UnknownOptionError = UnknownOptionError;
61
61
  * The `--` sentinel itself is NOT included in `rest`.
62
62
  */
63
63
  function parseCommonFlags(args) {
64
+ const { value: to, consumed: toConsumed } = parseToFlag(args);
64
65
  const thread = parseFlag(args, '--thread');
65
66
  const provider = parseFlag(args, '--provider');
67
+ const model = parseFlag(args, '--model');
66
68
  const metadata = parseFlag(args, '--metadata');
67
69
  const config = parseFlag(args, '--config', '-c');
68
70
  const outputFormat = parseFlag(args, '--output-format');
@@ -72,10 +74,11 @@ function parseCommonFlags(args) {
72
74
  const verbose = hasFlag(args, '--verbose');
73
75
  const overdrive = hasFlag(args, '--overdrive');
74
76
  // Collect consumed positions for known flags
75
- const consumed = new Set();
77
+ const consumed = new Set(toConsumed);
76
78
  const flagPairs = [
77
79
  { names: ['--thread'] },
78
80
  { names: ['--provider'] },
81
+ { names: ['--model'] },
79
82
  { names: ['--metadata'] },
80
83
  { names: ['--config', '-c'] },
81
84
  { names: ['--output-format'] },
@@ -147,7 +150,7 @@ function parseCommonFlags(args) {
147
150
  if (!escapeMode && token.startsWith('--')) {
148
151
  throw new UnknownOptionError(`Unknown option: ${token}. Use "--" to pass literal flag-like tokens as message.\n` +
149
152
  ` Did you mean: crewx serve ${token}\n` +
150
- ` or: crewx q -- ${token} <message> (to include in message)`);
153
+ ` or: crewx q --to <agent> -- ${token} <message>`);
151
154
  }
152
155
  // Reject short -x flags that are not numeric (e.g. -f was consumed above)
153
156
  if (!escapeMode && token.startsWith('-') && token.length > 1 && !/^-?\d/.test(token)) {
@@ -155,7 +158,35 @@ function parseCommonFlags(args) {
155
158
  }
156
159
  rest.push(token);
157
160
  }
158
- return { thread, provider, metadata, verbose, config, outputFormat, effort, promptFile, overdrive, vars, out, rest };
161
+ return { to, thread, provider, model, metadata, verbose, config, outputFormat, effort, promptFile, overdrive, vars, out, rest };
162
+ }
163
+ /** Parse the explicit recipient flag without treating tokens after `--` as flags. */
164
+ function parseToFlag(args) {
165
+ let value;
166
+ const consumed = [];
167
+ for (let index = 0; index < args.length; index += 1) {
168
+ const arg = args[index];
169
+ if (arg === '--')
170
+ break;
171
+ if (arg.startsWith('--to=')) {
172
+ const candidate = arg.slice('--to='.length);
173
+ if (!candidate.trim())
174
+ throw new UnknownOptionError('--to requires an agent id');
175
+ value = candidate;
176
+ consumed.push(index);
177
+ continue;
178
+ }
179
+ if (arg !== '--to')
180
+ continue;
181
+ const candidate = args[index + 1];
182
+ if (candidate === undefined || candidate === '--' || candidate.startsWith('--') || !candidate.trim()) {
183
+ throw new UnknownOptionError('--to requires an agent id');
184
+ }
185
+ value = candidate;
186
+ consumed.push(index, index + 1);
187
+ index += 1;
188
+ }
189
+ return { value, consumed };
159
190
  }
160
191
  /**
161
192
  * Parse metadata JSON string from --metadata flag.
@@ -1,9 +1,9 @@
1
1
  /**
2
2
  * crewx ps handler.
3
- * Lists currently running tasks in table format.
3
+ * Lists active tasks (pending or running) in table format.
4
4
  *
5
5
  * Usage:
6
- * crewx ps List all running tasks
6
+ * crewx ps List all pending and running tasks
7
7
  * crewx ps --json Output as JSON array
8
8
  */
9
9
  export declare function handlePs(args: string[]): Promise<void>;
@@ -1,15 +1,16 @@
1
1
  "use strict";
2
2
  /**
3
3
  * crewx ps handler.
4
- * Lists currently running tasks in table format.
4
+ * Lists active tasks (pending or running) in table format.
5
5
  *
6
6
  * Usage:
7
- * crewx ps List all running tasks
7
+ * crewx ps List all pending and running tasks
8
8
  * crewx ps --json Output as JSON array
9
9
  */
10
10
  Object.defineProperty(exports, "__esModule", { value: true });
11
11
  exports.handlePs = handlePs;
12
12
  const repository_1 = require("@crewx/sdk/repository");
13
+ const cal_1 = require("./cal");
13
14
  function formatElapsed(ms) {
14
15
  const s = Math.floor(ms / 1000);
15
16
  if (s < 60)
@@ -34,9 +35,9 @@ function isAgyCommand(codingAgentCommand) {
34
35
  const base = firstToken.split('/').pop() ?? firstToken;
35
36
  return base.startsWith('agy');
36
37
  }
37
- function getLastActive(task) {
38
+ function getLastActive(task, taskRepo) {
38
39
  try {
39
- const entries = task.logs ? JSON.parse(task.logs) : [];
40
+ const entries = taskRepo.readLogsTail(task.id, 1)?.entries ?? [];
40
41
  if (Array.isArray(entries) && entries.length > 0) {
41
42
  const lastEntry = entries[entries.length - 1];
42
43
  const ts = lastEntry?.timestamp;
@@ -59,12 +60,13 @@ function getLastActive(task) {
59
60
  }
60
61
  return { display: '—', isoTimestamp: null, logCapable: true };
61
62
  }
62
- function taskToRow(task) {
63
+ function taskToRow(task, taskRepo) {
63
64
  const elapsed = formatElapsed(Date.now() - new Date(task.started_at).getTime());
64
- const lastActive = getLastActive(task);
65
+ const lastActive = getLastActive(task, taskRepo);
65
66
  return [
66
67
  task.id,
67
68
  task.agent_id ?? '—',
69
+ task.status,
68
70
  task.pid !== null && task.pid !== undefined ? String(task.pid) : '—',
69
71
  elapsed,
70
72
  task.mode ?? '—',
@@ -83,14 +85,14 @@ function renderTable(headers, rows) {
83
85
  }
84
86
  async function handlePs(args) {
85
87
  const repo = new repository_1.TaskRepository();
86
- const tasks = repo.getRunningTasks();
87
- if (tasks.length === 0) {
88
- console.log('No running tasks.');
89
- return;
90
- }
88
+ const reaped = repo.reapOrphanedTasks();
89
+ if (reaped > 0)
90
+ console.error(`Reaped ${reaped} orphaned task(s)`);
91
+ repo.reapRunningWorkflowTasks();
92
+ const tasks = repo.getActiveTasks();
91
93
  if (args.includes('--json')) {
92
94
  const withLastActive = tasks.map((task) => {
93
- const lastActive = getLastActive(task);
95
+ const lastActive = getLastActive(task, repo);
94
96
  return {
95
97
  ...task,
96
98
  last_active_at: lastActive.isoTimestamp,
@@ -100,8 +102,16 @@ async function handlePs(args) {
100
102
  console.log(JSON.stringify(withLastActive, null, 2));
101
103
  return;
102
104
  }
103
- const headers = ['TASK ID', 'AGENT', 'PID', 'ELAPSED', 'MODE', 'LAST ACTIVE'];
104
- const rows = tasks.map(taskToRow);
105
+ if (tasks.length === 0) {
106
+ console.log('No active tasks.');
107
+ return;
108
+ }
109
+ const headers = ['TASK ID', 'AGENT', 'STATUS', 'PID', 'ELAPSED', 'MODE', 'LAST ACTIVE'];
110
+ const rows = tasks.map((task) => taskToRow(task, repo));
105
111
  renderTable(headers, rows);
106
- console.log(`\n ${tasks.length} running task(s)`);
112
+ console.log(`\n ${tasks.length} active task(s)`);
113
+ (0, cal_1.writeCalFooter)({
114
+ commandId: 'crewx.ps',
115
+ params: { taskId: tasks[0].id },
116
+ });
107
117
  }
@@ -0,0 +1 @@
1
+ export declare function handlePublish(args: string[]): Promise<void>;