@crewx/cli 0.9.0-rc.18 → 0.9.0-rc.181

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 (80) hide show
  1. package/dist/bootstrap/codex-writable-roots.d.ts +20 -1
  2. package/dist/bootstrap/codex-writable-roots.js +1 -88
  3. package/dist/bootstrap/crewx-cli.js +1 -63
  4. package/dist/bootstrap/rate-limit-snapshot.d.ts +15 -0
  5. package/dist/bootstrap/rate-limit-snapshot.js +1 -0
  6. package/dist/builtin.d.ts +1 -1
  7. package/dist/builtin.js +5 -127
  8. package/dist/commands/agent.d.ts +1 -0
  9. package/dist/commands/agent.js +3 -246
  10. package/dist/commands/ai-account.d.ts +4 -0
  11. package/dist/commands/ai-account.js +27 -0
  12. package/dist/commands/cal.d.ts +15 -0
  13. package/dist/commands/cal.js +2 -0
  14. package/dist/commands/command-help.d.ts +26 -0
  15. package/dist/commands/command-help.js +52 -0
  16. package/dist/commands/db.d.ts +3 -0
  17. package/dist/commands/db.js +59 -125
  18. package/dist/commands/detached.d.ts +102 -0
  19. package/dist/commands/detached.js +15 -0
  20. package/dist/commands/doctor.d.ts +77 -0
  21. package/dist/commands/doctor.js +4 -196
  22. package/dist/commands/emit-trailer.d.ts +25 -0
  23. package/dist/commands/emit-trailer.js +2 -0
  24. package/dist/commands/execute.d.ts +8 -4
  25. package/dist/commands/execute.js +15 -146
  26. package/dist/commands/hook/command-marker.d.ts +2 -0
  27. package/dist/commands/hook/command-marker.js +1 -0
  28. package/dist/commands/hook/install.d.ts +0 -1
  29. package/dist/commands/hook/install.js +8 -175
  30. package/dist/commands/hook/paths.js +1 -94
  31. package/dist/commands/hook/status.js +1 -86
  32. package/dist/commands/hook/uninstall.js +1 -71
  33. package/dist/commands/hook-dispatch.js +2 -180
  34. package/dist/commands/init.d.ts +34 -0
  35. package/dist/commands/init.js +38 -371
  36. package/dist/commands/kill.d.ts +2 -8
  37. package/dist/commands/kill.js +1 -52
  38. package/dist/commands/log.js +3 -95
  39. package/dist/commands/parse-agent-message.js +1 -52
  40. package/dist/commands/parse-common-flags.d.ts +5 -1
  41. package/dist/commands/parse-common-flags.js +3 -181
  42. package/dist/commands/ps.d.ts +2 -2
  43. package/dist/commands/ps.js +2 -107
  44. package/dist/commands/publish.d.ts +1 -0
  45. package/dist/commands/publish.js +27 -0
  46. package/dist/commands/query.d.ts +7 -2
  47. package/dist/commands/query.js +16 -146
  48. package/dist/commands/registry.js +1 -31
  49. package/dist/commands/resolve-prompt.js +3 -54
  50. package/dist/commands/resolve-recipient.d.ts +14 -0
  51. package/dist/commands/resolve-recipient.js +3 -0
  52. package/dist/commands/restart.d.ts +3 -1
  53. package/dist/commands/restart.js +18 -162
  54. package/dist/commands/result.d.ts +8 -3
  55. package/dist/commands/result.js +1 -70
  56. package/dist/commands/shortcut.d.ts +1 -0
  57. package/dist/commands/shortcut.js +18 -0
  58. package/dist/commands/slack.js +16 -559
  59. package/dist/commands/stop.d.ts +8 -0
  60. package/dist/commands/stop.js +8 -0
  61. package/dist/commands/write-output.js +1 -24
  62. package/dist/index.d.ts +1 -1
  63. package/dist/index.js +1 -5
  64. package/dist/logging.d.ts +1 -1
  65. package/dist/logging.js +10 -86
  66. package/dist/main.d.ts +3 -2
  67. package/dist/main.js +49 -253
  68. package/dist/register-builtin-tools.js +1 -9
  69. package/dist/slack/file-download.js +1 -134
  70. package/dist/slack/markdown.js +1 -33
  71. package/dist/utils/env-defaults.d.ts +2 -5
  72. package/dist/utils/env-defaults.js +1 -10
  73. package/dist/utils/inherited-trace.d.ts +24 -0
  74. package/dist/utils/inherited-trace.js +1 -25
  75. package/dist/utils/product-package-root.d.ts +6 -0
  76. package/dist/utils/product-package-root.js +1 -0
  77. package/dist/utils/sdk-compat.d.ts +24 -0
  78. package/dist/utils/sdk-compat.js +1 -0
  79. package/dist/utils/version.js +1 -28
  80. package/package.json +16 -12
@@ -0,0 +1,15 @@
1
+ import type { ActionContext } from '@crewx/knowledge-core';
2
+ /** Suggested wait rungs in seconds. 540 is the safe caller-harness cap. */
3
+ export declare const WAIT_LADDER_SECONDS: readonly [60, 300, 540];
4
+ export interface SuggestWaitSecondsOptions {
5
+ startedAt?: string | number | Date | null;
6
+ lastWaitSeconds?: number;
7
+ now?: number;
8
+ }
9
+ /**
10
+ * Select the next safe result wait based on task runtime without stepping
11
+ * down from the wait value the caller just used.
12
+ */
13
+ export declare function suggestWaitSeconds(options: SuggestWaitSecondsOptions): number;
14
+ /** Write a human-only CAL footer without contaminating command stdout. */
15
+ export declare function writeCalFooter(context: ActionContext): void;
@@ -0,0 +1,2 @@
1
+ "use strict";Object.defineProperty(exports,"__esModule",{value:true});exports.WAIT_LADDER_SECONDS=void 0;exports.suggestWaitSeconds=D;exports.writeCalFooter=a;const i=require("@crewx/knowledge-core");exports.WAIT_LADDER_SECONDS=[60,300,540];function d(e){if(e instanceof Date){return Number.isFinite(e.getTime())?e.getTime():void 0}if(typeof e==="number"){return Number.isFinite(e)?e:void 0}if(typeof e==="string"){const t=Date.parse(e);return Number.isFinite(t)?t:void 0}return void 0}function D(e){const t=Number.isFinite(e.now)?e.now:Date.now();const n=d(e.startedAt);const r=n===void 0?0:Math.max(0,(t-n)/1e3);const s=exports.WAIT_LADDER_SECONDS.find(c=>c>r)??exports.WAIT_LADDER_SECONDS[exports.WAIT_LADDER_SECONDS.length-1];const o=Number.isFinite(e.lastWaitSeconds)?Math.max(0,e.lastWaitSeconds):0;return Math.min(exports.WAIT_LADDER_SECONDS[exports.WAIT_LADDER_SECONDS.length-1],Math.max(s,o))}function a(e){const t=(0,i.formatActions)((0,i.generateNextActions)(e));if(t)process.stderr.write(`${t}
2
+ `)}
@@ -0,0 +1,26 @@
1
+ /**
2
+ * `--help` / `-h` routing for commands that change state.
3
+ *
4
+ * `crewx init --help` used to run init: git init, files, and a workspace row.
5
+ * main.ts asks this module before any handler runs, so a help request prints
6
+ * usage and nothing else.
7
+ */
8
+ export declare const INIT_USAGE = "Usage: crewx init [--force] [--provider cli/claude|cli/codex]\n\nCreate crewx.yaml, support folders, and a git repository in the current folder.\n\n --force Overwrite the existing configuration\n --provider cli/claude|cli/codex Provider for the generated agents\n (detected from PATH when omitted)";
9
+ export declare const STOP_USAGE = "Usage:\n crewx stop <task-id> [--grace=<ms>] Stop a running task\n crewx stop --all Stop all running tasks\n crewx kill <task-id> Alias for stop";
10
+ export declare const RESTART_USAGE = "Usage: crewx restart <task-id>\n\nRestart a failed task as a new task, reusing its thread, agent, and provider.";
11
+ export declare const HOOK_USAGE = "Usage: crewx hook install|uninstall|status [options]\n\n install [--yes] [--provider claude|codex] Install the PreToolUse hook in .claude/settings.json\n uninstall [--provider claude|codex] Remove the crewx hook\n status Show hook installation status and plugins";
12
+ export declare const HOOK_DISPATCH_USAGE = "Usage: crewx hook-dispatch --provider <claude|codex>\n\nInternal IPC router: reads one hook payload from stdin and writes the decision to stdout.\nAgent CLI hooks call it; people do not need to run it.";
13
+ export declare const SLACK_USAGE = "Usage: crewx slack:files --thread <thread_ts> [--list | --clean]\n crewx slack --mode <query|execute> --agent <agent-id>\n\nExample:\n crewx slack:files --thread 1234567890.123456";
14
+ export declare const AGENT_USAGE = "Usage: crewx agent [subcommand] [options]\n\n crewx agent # List configured agents (default)\n crewx agent ls # List configured agents\n crewx agent list # Alias for ls\n crewx agent prompt <id> # Inspect rendered agent prompt\n crewx agent interceptor test <id> --phase request|response --text \"<text>\" [--json]\n # Dry-run an agent's interceptor chain on a text (no agent start)\n\nFilter Options (for agent ls):\n --role <value> Filter by agent role (comma-separated for multiple: PM,Dev)\n --team <value> Filter by agent team (comma-separated for multiple)\n --provider <value> Filter by provider (comma-separated, partial match: claude)\n\nExamples:\n crewx agent\n crewx agent ls\n crewx agent ls --role=PM\n crewx agent ls --team=\"CrewX Core Team\"\n crewx agent ls --provider=claude\n crewx agent ls --role=PM --provider=claude\n crewx agent ls --role=PM,general\n CREWX_CONFIG=./crewx.yaml crewx agent list";
15
+ export declare const AGENT_LS_USAGE = "Usage: crewx agent ls [--role <value>] [--team <value>] [--provider <value>]\n\nList configured agents. Filters take comma-separated values; --provider also matches part of a name (claude).";
16
+ export declare const AGENT_PROMPT_USAGE = "Usage: crewx agent prompt <id>\n\nPrint the fully rendered system prompt of one agent.";
17
+ export declare const AGENT_INTERCEPTOR_USAGE = "Usage: crewx agent interceptor test <id> --phase request|response --text \"<text>\" [--json]\n\nRun an agent's interceptor chain once on a text without starting the agent.\nExit 0 when the lane passes; exit 1 when it is blocked or the chain configuration is invalid.";
18
+ /** True when the arguments ask for help. Use only for commands with no free-text message. */
19
+ export declare function isHelpRequest(args: readonly string[]): boolean;
20
+ /**
21
+ * Usage text for `crewx agent … --help`, or undefined when the subcommand should run.
22
+ * The subcommand is `rest[0]`, the same word the agent handler dispatches on.
23
+ */
24
+ export declare function agentHelpUsage(rest: readonly string[]): string | undefined;
25
+ /** Usage text to print instead of running `command`, or undefined when the command should run. */
26
+ export declare function helpUsageFor(command: string, rest: readonly string[]): string | undefined;
@@ -0,0 +1,52 @@
1
+ "use strict";Object.defineProperty(exports,"__esModule",{value:true});exports.AGENT_INTERCEPTOR_USAGE=exports.AGENT_PROMPT_USAGE=exports.AGENT_LS_USAGE=exports.AGENT_USAGE=exports.SLACK_USAGE=exports.HOOK_DISPATCH_USAGE=exports.HOOK_USAGE=exports.RESTART_USAGE=exports.STOP_USAGE=exports.INIT_USAGE=void 0;exports.isHelpRequest=a;exports.agentHelpUsage=r;exports.helpUsageFor=n;exports.INIT_USAGE=`Usage: crewx init [--force] [--provider cli/claude|cli/codex]
2
+
3
+ Create crewx.yaml, support folders, and a git repository in the current folder.
4
+
5
+ --force Overwrite the existing configuration
6
+ --provider cli/claude|cli/codex Provider for the generated agents
7
+ (detected from PATH when omitted)`;exports.STOP_USAGE=`Usage:
8
+ crewx stop <task-id> [--grace=<ms>] Stop a running task
9
+ crewx stop --all Stop all running tasks
10
+ crewx kill <task-id> Alias for stop`;exports.RESTART_USAGE=`Usage: crewx restart <task-id>
11
+
12
+ Restart a failed task as a new task, reusing its thread, agent, and provider.`;exports.HOOK_USAGE=`Usage: crewx hook install|uninstall|status [options]
13
+
14
+ install [--yes] [--provider claude|codex] Install the PreToolUse hook in .claude/settings.json
15
+ uninstall [--provider claude|codex] Remove the crewx hook
16
+ status Show hook installation status and plugins`;exports.HOOK_DISPATCH_USAGE=`Usage: crewx hook-dispatch --provider <claude|codex>
17
+
18
+ Internal IPC router: reads one hook payload from stdin and writes the decision to stdout.
19
+ Agent CLI hooks call it; people do not need to run it.`;exports.SLACK_USAGE=`Usage: crewx slack:files --thread <thread_ts> [--list | --clean]
20
+ crewx slack --mode <query|execute> --agent <agent-id>
21
+
22
+ Example:
23
+ crewx slack:files --thread 1234567890.123456`;exports.AGENT_USAGE=`Usage: crewx agent [subcommand] [options]
24
+
25
+ crewx agent # List configured agents (default)
26
+ crewx agent ls # List configured agents
27
+ crewx agent list # Alias for ls
28
+ crewx agent prompt <id> # Inspect rendered agent prompt
29
+ crewx agent interceptor test <id> --phase request|response --text "<text>" [--json]
30
+ # Dry-run an agent's interceptor chain on a text (no agent start)
31
+
32
+ Filter Options (for agent ls):
33
+ --role <value> Filter by agent role (comma-separated for multiple: PM,Dev)
34
+ --team <value> Filter by agent team (comma-separated for multiple)
35
+ --provider <value> Filter by provider (comma-separated, partial match: claude)
36
+
37
+ Examples:
38
+ crewx agent
39
+ crewx agent ls
40
+ crewx agent ls --role=PM
41
+ crewx agent ls --team="CrewX Core Team"
42
+ crewx agent ls --provider=claude
43
+ crewx agent ls --role=PM --provider=claude
44
+ crewx agent ls --role=PM,general
45
+ CREWX_CONFIG=./crewx.yaml crewx agent list`;exports.AGENT_LS_USAGE=`Usage: crewx agent ls [--role <value>] [--team <value>] [--provider <value>]
46
+
47
+ List configured agents. Filters take comma-separated values; --provider also matches part of a name (claude).`;exports.AGENT_PROMPT_USAGE=`Usage: crewx agent prompt <id>
48
+
49
+ Print the fully rendered system prompt of one agent.`;exports.AGENT_INTERCEPTOR_USAGE=`Usage: crewx agent interceptor test <id> --phase request|response --text "<text>" [--json]
50
+
51
+ Run an agent's interceptor chain once on a text without starting the agent.
52
+ Exit 0 when the lane passes; exit 1 when it is blocked or the chain configuration is invalid.`;function a(e){return e.includes("--help")||e.includes("-h")}function r(e){const t=e.filter((o,s)=>e[s-1]!=="--text");if(!a(t))return void 0;switch((e[0]??"").toLowerCase()){case"ls":case"list":return exports.AGENT_LS_USAGE;case"prompt":case"p":return exports.AGENT_PROMPT_USAGE;case"interceptor":return exports.AGENT_INTERCEPTOR_USAGE;default:return exports.AGENT_USAGE}}function n(e,t){if(!a(t))return void 0;switch(e){case"agent":return r(t);case"init":return exports.INIT_USAGE;case"stop":case"kill":return exports.STOP_USAGE;case"restart":return exports.RESTART_USAGE;case"hook":return exports.HOOK_USAGE;case"hook-dispatch":return exports.HOOK_DISPATCH_USAGE;case"slack":case"slack:files":return exports.SLACK_USAGE;default:return void 0}}
@@ -6,4 +6,7 @@
6
6
  * crewx db push --force Apply without confirmation + reset migration history
7
7
  * crewx db push --dry-run Show preview only, no changes
8
8
  */
9
+ export declare const TASK_LOG_MIGRATION_HELP = "Usage:\n crewx db migrate-task-logs --dry-run [--db PATH]\n crewx db migrate-task-logs --apply [--db PATH] [--batch-tasks N]\n crewx db migrate-task-logs --verify [--db PATH]\n\nModes:\n --dry-run Read and validate legacy blobs without writing or creating a backup.\n --apply Create a consistent SQLite backup, then migrate one task per transaction.\n --verify Check source invariants, event sequences, counts, and orphan rows.\n\nApply policy:\n Only blob tasks with status success, failed, or completed are migrated.\n pending, running, paused, and unknown statuses are deferred for a later run.\n There is no --force or --include-active option. Apply requires the estimated\n database/event growth plus a 10 GiB free-space reserve.\n\nExit codes:\n 0 Completed with no malformed rows or invariant failures.\n 1 Safe tasks completed but a row/invariant failed, or apply was blocked.\n 2 Invalid command-line arguments.\n\nThe command never runs automatically at server startup and never runs VACUUM.";
10
+ export declare const REQUEST_LOG_MIGRATION_HELP = "Usage:\n crewx db migrate-request-logs [--batch N] [--dry-run]\n\nMoves the request log rows still stored in crewx.db into logs.db, the file next\nto it. Each distinct request/response body is stored once in logs.db, keyed by\nits SHA-256; a row points to its bodies by hash, so nothing is dropped.\n\nOptions:\n --batch N Rows per batch (default 250; a batch is also capped at about 8 MB).\n --dry-run Count rows and distinct bodies without writing anything (logs.db is not created).\n\nSafe while crewx is running: each batch is committed to logs.db, verified there,\nand only then deleted from crewx.db in one short transaction. Interrupt it and run\nit again at any time. The database is chosen like everywhere else (CREWX_DB, else\n~/.crewx/crewx.db). No VACUUM and no DROP: crewx.db keeps its size until a later compaction.\n\nExit codes:\n 0 Every row is in logs.db and none is left in crewx.db (or the dry run finished).\n 1 Rows are still in crewx.db, or an error stopped the move (progress is kept).\n 2 Invalid command-line arguments.";
11
+ export declare const USAGE_BACKFILL_HELP = "Usage:\n crewx db backfill-usage [--dry-run|--apply|--verify] [--db PATH] [--backup PATH]\n crewx db backfill-usage --restore SNAPSHOT [--db PATH]\n\nModes:\n --dry-run Show provider/reason recovery plans without changing the database (default).\n --apply Create a row-level undo snapshot, then account one task per transaction.\n --verify Recheck remaining targets and usageAccounting markers without changing the database.\n --restore Restore rows from SNAPSHOT when their current values still match the snapshot after-state.\n\nApply policy:\n Only success, failed, and completed tasks with zero recorded usage are considered.\n Workflow/MCP anchors and plugin/mock rows are excluded. Rows completed within 24 hours\n are not marked unavailable when their logs do not contain usage.\n\nExit codes:\n 0 Completed successfully.\n 1 Apply was blocked or one or more task updates failed.\n 2 Invalid command-line arguments.";
9
12
  export declare function handleDb(args: string[]): Promise<void>;
@@ -1,125 +1,59 @@
1
- "use strict";
2
- /**
3
- * crewx db push handler.
4
- *
5
- * Usage:
6
- * crewx db push Preview changes → confirm → apply
7
- * crewx db push --force Apply without confirmation + reset migration history
8
- * crewx db push --dry-run Show preview only, no changes
9
- */
10
- var __importDefault = (this && this.__importDefault) || function (mod) {
11
- return (mod && mod.__esModule) ? mod : { "default": mod };
12
- };
13
- Object.defineProperty(exports, "__esModule", { value: true });
14
- exports.handleDb = handleDb;
15
- const path_1 = __importDefault(require("path"));
16
- const os_1 = __importDefault(require("os"));
17
- const readline_1 = __importDefault(require("readline"));
18
- const repository_1 = require("@crewx/sdk/repository");
19
- const repository_2 = require("@crewx/sdk/repository");
20
- function defaultDbPath() {
21
- return path_1.default.join(os_1.default.homedir(), '.crewx', 'crewx.db');
22
- }
23
- function formatPreview(result, dbPath) {
24
- const lines = [];
25
- lines.push(`[crewx] Database push — comparing schema vs ${dbPath}`);
26
- lines.push('');
27
- if (result.created.length > 0) {
28
- lines.push(` Tables to create:`);
29
- for (const t of result.created) {
30
- lines.push(` → ${t}`);
31
- }
32
- }
33
- else {
34
- lines.push(' Tables to create: (none)');
35
- }
36
- if (result.altered.length > 0) {
37
- lines.push(' Columns to add:');
38
- for (const { table, column } of result.altered) {
39
- lines.push(` → ${table}.${column}`);
40
- }
41
- }
42
- else {
43
- lines.push(' Columns to add: (none)');
44
- }
45
- if (result.warnings.length > 0) {
46
- lines.push(' Warnings:');
47
- for (const w of result.warnings) {
48
- lines.push(` ⚠ ${w}`);
49
- }
50
- }
51
- if (result.backupPath) {
52
- lines.push('');
53
- lines.push(` Backup: ${result.backupPath}`);
54
- }
55
- return lines.join('\n');
56
- }
57
- function hasChanges(result) {
58
- return result.created.length > 0 || result.altered.length > 0;
59
- }
60
- function prompt(question) {
61
- const rl = readline_1.default.createInterface({ input: process.stdin, output: process.stdout });
62
- return new Promise((resolve) => {
63
- rl.question(question, (answer) => {
64
- rl.close();
65
- resolve(answer);
66
- });
67
- });
68
- }
69
- async function handleDb(args) {
70
- const subcommand = args[0];
71
- if (!subcommand || subcommand === 'push') {
72
- await handleDbPush(args.slice(subcommand === 'push' ? 1 : 0));
73
- return;
74
- }
75
- console.error(`Unknown db subcommand: ${subcommand}`);
76
- console.error('Usage: crewx db push [--force] [--dry-run]');
77
- process.exit(1);
78
- }
79
- async function handleDbPush(args) {
80
- const force = args.includes('--force');
81
- const dryRun = args.includes('--dry-run');
82
- const dbPath = defaultDbPath();
83
- const handle = (0, repository_1.openDrizzleDb)(dbPath);
84
- try {
85
- // 1. Compute preview (dry-run pass — no changes yet)
86
- const preview = (0, repository_2.pushSchema)(handle.db, { force, dbPath, dryRun: true });
87
- console.log(formatPreview(preview, dbPath));
88
- console.log('');
89
- if (dryRun) {
90
- console.log('[crewx] Dry-run mode — no changes applied.');
91
- return;
92
- }
93
- if (!hasChanges(preview)) {
94
- console.log('[crewx] No changes needed. Database is up to date.');
95
- return;
96
- }
97
- // 2. Confirm unless --force
98
- if (!force) {
99
- const answer = await prompt('Apply changes? (y/N): ');
100
- if (answer.trim().toLowerCase() !== 'y') {
101
- console.log('[crewx] Aborted.');
102
- return;
103
- }
104
- }
105
- // 3. Apply
106
- const result = (0, repository_2.pushSchema)(handle.db, { force, dbPath });
107
- console.log('');
108
- console.log('[crewx] Push complete.');
109
- if (result.created.length > 0) {
110
- console.log(` Tables created: ${result.created.join(', ')}`);
111
- }
112
- if (result.altered.length > 0) {
113
- console.log(` Columns added: ${result.altered.map((a) => `${a.table}.${a.column}`).join(', ')}`);
114
- }
115
- if (result.backupPath) {
116
- console.log(` Backup: ${result.backupPath}`);
117
- }
118
- if (result.created.length === 0 && result.altered.length === 0) {
119
- console.log(' No changes applied.');
120
- }
121
- }
122
- finally {
123
- handle.close();
124
- }
125
- }
1
+ "use strict";var g=exports&&exports.__importDefault||function(e){return e&&e.__esModule?e:{"default":e}};Object.defineProperty(exports,"__esModule",{value:true});exports.USAGE_BACKFILL_HELP=exports.REQUEST_LOG_MIGRATION_HELP=exports.TASK_LOG_MIGRATION_HELP=void 0;exports.handleDb=C;const v=g(require("path"));const R=g(require("os"));const T=g(require("readline"));const x=require("./command-help");const _=require("@crewx/sdk/repository");const m=require("@crewx/sdk/repository");const E=require("@crewx/sdk/repository");const p=require("@crewx/sdk/repository");const S=require("@crewx/sdk/repository");exports.TASK_LOG_MIGRATION_HELP=`Usage:
2
+ crewx db migrate-task-logs --dry-run [--db PATH]
3
+ crewx db migrate-task-logs --apply [--db PATH] [--batch-tasks N]
4
+ crewx db migrate-task-logs --verify [--db PATH]
5
+
6
+ Modes:
7
+ --dry-run Read and validate legacy blobs without writing or creating a backup.
8
+ --apply Create a consistent SQLite backup, then migrate one task per transaction.
9
+ --verify Check source invariants, event sequences, counts, and orphan rows.
10
+
11
+ Apply policy:
12
+ Only blob tasks with status success, failed, or completed are migrated.
13
+ pending, running, paused, and unknown statuses are deferred for a later run.
14
+ There is no --force or --include-active option. Apply requires the estimated
15
+ database/event growth plus a 10 GiB free-space reserve.
16
+
17
+ Exit codes:
18
+ 0 Completed with no malformed rows or invariant failures.
19
+ 1 Safe tasks completed but a row/invariant failed, or apply was blocked.
20
+ 2 Invalid command-line arguments.
21
+
22
+ The command never runs automatically at server startup and never runs VACUUM.`;exports.REQUEST_LOG_MIGRATION_HELP=`Usage:
23
+ crewx db migrate-request-logs [--batch N] [--dry-run]
24
+
25
+ Moves the request log rows still stored in crewx.db into logs.db, the file next
26
+ to it. Each distinct request/response body is stored once in logs.db, keyed by
27
+ its SHA-256; a row points to its bodies by hash, so nothing is dropped.
28
+
29
+ Options:
30
+ --batch N Rows per batch (default 250; a batch is also capped at about 8 MB).
31
+ --dry-run Count rows and distinct bodies without writing anything (logs.db is not created).
32
+
33
+ Safe while crewx is running: each batch is committed to logs.db, verified there,
34
+ and only then deleted from crewx.db in one short transaction. Interrupt it and run
35
+ it again at any time. The database is chosen like everywhere else (CREWX_DB, else
36
+ ~/.crewx/crewx.db). No VACUUM and no DROP: crewx.db keeps its size until a later compaction.
37
+
38
+ Exit codes:
39
+ 0 Every row is in logs.db and none is left in crewx.db (or the dry run finished).
40
+ 1 Rows are still in crewx.db, or an error stopped the move (progress is kept).
41
+ 2 Invalid command-line arguments.`;exports.USAGE_BACKFILL_HELP=`Usage:
42
+ crewx db backfill-usage [--dry-run|--apply|--verify] [--db PATH] [--backup PATH]
43
+ crewx db backfill-usage --restore SNAPSHOT [--db PATH]
44
+
45
+ Modes:
46
+ --dry-run Show provider/reason recovery plans without changing the database (default).
47
+ --apply Create a row-level undo snapshot, then account one task per transaction.
48
+ --verify Recheck remaining targets and usageAccounting markers without changing the database.
49
+ --restore Restore rows from SNAPSHOT when their current values still match the snapshot after-state.
50
+
51
+ Apply policy:
52
+ Only success, failed, and completed tasks with zero recorded usage are considered.
53
+ Workflow/MCP anchors and plugin/mock rows are excluded. Rows completed within 24 hours
54
+ are not marked unavailable when their logs do not contain usage.
55
+
56
+ Exit codes:
57
+ 0 Completed successfully.
58
+ 1 Apply was blocked or one or more task updates failed.
59
+ 2 Invalid command-line arguments.`;function A(){return v.default.join(R.default.homedir(),".crewx","crewx.db")}function L(e,o){const s=[];s.push(`[crewx] Database push \u2014 comparing schema vs ${o}`);s.push("");if(e.created.length>0){s.push(` Tables to create:`);for(const n of e.created){s.push(` \u2192 ${n}`)}}else{s.push(" Tables to create: (none)")}if(e.altered.length>0){s.push(" Columns to add:");for(const{table:n,column:r}of e.altered){s.push(` \u2192 ${n}.${r}`)}}else{s.push(" Columns to add: (none)")}if(e.warnings.length>0){s.push(" Warnings:");for(const n of e.warnings){s.push(` \u26A0 ${n}`)}}if(e.backupPath){s.push("");s.push(` Backup: ${e.backupPath}`)}return s.join("\n")}function P(e){return e.created.length>0||e.altered.length>0}function y(e){const o=Object.entries(e??{});return o.length>0?o.map(([s,n])=>`${s}=${n}`).join(", "):"(none)"}function M(e){const o=T.default.createInterface({input:process.stdin,output:process.stdout});return new Promise(s=>{o.question(e,n=>{o.close();s(n)})})}function k(){console.log("Usage: crewx db push [--force] [--dry-run]");console.log(exports.TASK_LOG_MIGRATION_HELP);console.log(exports.REQUEST_LOG_MIGRATION_HELP)}async function C(e){const o=e[0];if(o==="migrate-task-logs"){await q(e.slice(1));return}if(o==="migrate-request-logs"){await D(e.slice(1));return}if(o==="backfill-usage"){await F(e.slice(1));return}if(o==="--help"||o==="-h"){k();return}if(!o||o==="push"){const s=e.slice(o==="push"?1:0);if((0,x.isHelpRequest)(s)){k();return}await W(s);return}console.error(`Unknown db subcommand: ${o}`);console.error("Usage: crewx db push [--force] [--dry-run]");console.error(exports.TASK_LOG_MIGRATION_HELP);process.exitCode=2}class d extends Error{}class u extends Error{}function B(e){let o;let s;let n;const r=a=>{if(o)throw new d("Choose exactly one of --dry-run, --apply, or --verify.");o=a};const t=(a,c,l)=>{const i=a[c+1];if(!i||i.startsWith("--"))throw new d(`${l} requires a value.`);return i};for(let a=0;a<e.length;a+=1){const c=e[a];if(c==="--help"||c==="-h"){throw new d(exports.TASK_LOG_MIGRATION_HELP)}if(c==="--dry-run"){r("dry-run");continue}if(c==="--apply"){r("apply");continue}if(c==="--verify"){r("verify");continue}if(c==="--db"){s=t(e,a,"--db");a+=1;continue}if(c.startsWith("--db=")){s=c.slice("--db=".length);if(!s)throw new d("--db requires a value.");continue}if(c==="--batch-tasks"){const l=t(e,a,"--batch-tasks");const i=Number(l);if(!Number.isSafeInteger(i)||i<1){throw new d("--batch-tasks must be a positive integer.")}n=i;a+=1;continue}if(c.startsWith("--batch-tasks=")){const l=c.slice("--batch-tasks=".length);const i=Number(l);if(!Number.isSafeInteger(i)||i<1){throw new d("--batch-tasks must be a positive integer.")}n=i;continue}throw new d(`Unknown option: ${c}`)}if(!o)throw new d("Choose one of --dry-run, --apply, or --verify.");if(n!==void 0&&o!=="apply"){throw new d("--batch-tasks is only valid with --apply.")}return{mode:o,dbPath:s,batchTasks:n}}function I(e){const o=e.mode==="dry-run"?"Dry-run":e.mode==="apply"?"Apply":"Verify";const s=[`[crewx] Task-log migration \u2014 ${o}`,` Database: ${e.dbPath}`,` Tasks scanned: ${e.taskCount}`,` Blob candidates: ${e.candidateTaskCount}`,` Eligible terminal blobs: ${e.eligibleTaskCount}`,` Status counts: ${y(e.statusCounts)}`,` Deferred blob tasks: ${e.deferredTaskCount}`,` Deferred statuses: ${y(e.deferredStatusCounts)}`,` Entries: ${e.entryCount}`,` Source bytes: ${e.sourceBytes}`,` Estimated free-space need: ${e.estimatedFreeSpaceBytes}`,` Required free space (with reserve): ${e.requiredFreeSpaceBytes}`,` Available free space: ${e.availableFreeSpaceBytes}`,` Free-space gate: ${e.freeSpaceGatePassed?"PASS":"FAIL"}`,` Migrated: ${e.migratedTasks} task(s), ${e.migratedEntries} entr${e.migratedEntries===1?"y":"ies"}`,` Skipped event-source tasks: ${e.skippedTasks}`,` Failed tasks: ${e.failedTasks}`,` Logical source bytes removed: ${e.logicalSourceBytesRemoved}`];if(e.remainingTaskCount!==void 0)s.push(` Remaining blob tasks: ${e.remainingTaskCount}`);if(e.backupPath){s.push(` Backup: ${e.backupPath}`);if(e.backupMethod)s.push(` Backup method: ${e.backupMethod}`)}if(e.verification){s.push(` Event rows checked: ${e.verification.eventCount}`);s.push(` Verification issues: ${e.verification.issues.length}`)}if(e.blockedReason)s.push(` Blocked: ${e.blockedReason}`);for(const n of e.warnings??[])s.push(` Warning: ${n}`);return s.join("\n")}async function q(e){let o;try{o=B(e)}catch(s){const n=s instanceof Error?s.message:String(s);if(n===exports.TASK_LOG_MIGRATION_HELP){console.log(n)}else{console.error(n);console.error(exports.TASK_LOG_MIGRATION_HELP);process.exitCode=2}return}try{const s=await(0,E.runTaskLogMigration)(o);console.log(I(s));if(!s.ok){if(s.blockedReason)console.error(s.blockedReason);const n=s.failures??s.malformedRows??[];for(const r of n){console.error(` ${r.taskId}: ${r.reason}`)}process.exitCode=1}else{process.exitCode=0}}catch(s){console.error(s instanceof Error?s.message:String(s));process.exitCode=1}}class b extends Error{}function O(e){let o;let s=false;const n=r=>{const t=Number(r);if(!r||!Number.isSafeInteger(t)||t<1){throw new b("--batch must be a positive integer.")}return t};for(let r=0;r<e.length;r+=1){const t=e[r];if(t==="--help"||t==="-h")throw new b(exports.REQUEST_LOG_MIGRATION_HELP);if(t==="--dry-run"){s=true;continue}if(t==="--batch"){o=n(e[r+1]);r+=1;continue}if(t.startsWith("--batch=")){o=n(t.slice("--batch=".length));continue}throw new b(`Unknown option: ${t}`)}return{batchRows:o,dryRun:s}}function h(e){if(e<1024)return`${e} B`;const o=["KiB","MiB","GiB","TiB"];let s=e;let n=-1;while(s>=1024&&n<o.length-1){s/=1024;n+=1}return`${s.toFixed(s>=100?0:1)} ${o[n]} (${e} bytes)`}function $(e,o){const s=[...e].sort((n,r)=>n-r);return s[Math.min(s.length-1,Math.floor(o/100*s.length))]}function w(e){return`${(e/1e3).toFixed(1)}s`}const G=1e4;function U(e,o){if(e.dryRun){const s=e.scan;console.log("[crewx] Dry run finished \u2014 nothing was written.");console.log(` Rows scanned: ${s?.rows??0}`);console.log(` Bodies: ${s?.bodies??0} (${h(s?.bodyBytes??0)})`);console.log(` Distinct bodies: ${s?.distinctBodies??0} (${h(s?.distinctBodyBytes??0)}) \u2014 what logs.db would store`);console.log(` Elapsed: ${w(e.elapsedMs)}`);return}console.log("[crewx] Request-log move finished.");console.log(` Moved rows: ${e.movedRows}`);console.log(` Remaining rows in crewx.db: ${o.crewxRows}`);console.log(` logs.db request_logs: ${o.logsRows??0}`);console.log(` logs.db request_log_bodies: ${o.logsBodies??0}`);console.log(` logs.db size: ${h(o.logsFileBytes)} (+ wal ${h(o.logsWalBytes)})`);console.log(` crewx.db freelist pages: ${o.crewxFreelistPages}`);console.log(` Batches: ${e.batches}, bodies added: ${e.bodiesAdded}`);if(e.deleteTxMs.length>0){console.log(` crewx.db delete transaction per batch: p50 ${$(e.deleteTxMs,50).toFixed(1)} ms, p90 ${$(e.deleteTxMs,90).toFixed(1)} ms, max ${e.maxDeleteTxMs.toFixed(1)} ms`);console.log(` Largest crewx.db -wal seen after a batch: ${h(e.maxWalBytes)}`)}console.log(` Elapsed: ${w(e.elapsedMs)}`);if(o.crewxRows>0){console.log(` ${o.crewxRows} row(s) are still in crewx.db (new rows arrived or the run stopped early). Run it again.`)}}async function D(e){let o;try{o=O(e)}catch(s){const n=s instanceof Error?s.message:String(s);if(n===exports.REQUEST_LOG_MIGRATION_HELP){console.log(n)}else{console.error(n);console.error(exports.REQUEST_LOG_MIGRATION_HELP);process.exitCode=2}return}try{const s=(0,p.inspectRequestLogsMove)();console.log(`[crewx] Request logs \u2192 logs.db${o.dryRun?" (dry run)":""}`);console.log(` crewx.db: ${s.crewxDbPath}`);console.log(` logs.db: ${s.logsDbPath}`);console.log(` Rows to move: ${s.crewxRows}`);let n=Date.now();const r=a=>{const c=Date.now();if(c-n<G)return;n=c;const l=o.dryRun?a.scannedRows:a.movedRows;const i=Math.max(0,s.crewxRows-l);console.log(` ${o.dryRun?"scanned":"moved"} ${l} | remaining ~${i} | elapsed ${w(a.elapsedMs)}`)};const t=(0,p.moveRequestLogsToLogsDb)({batchRows:o.batchRows,dryRun:o.dryRun,onProgress:r});U(t,(0,p.inspectRequestLogsMove)());process.exitCode=o.dryRun||t.remainingRows===0?0:1}catch(s){console.error(s instanceof Error?s.message:String(s));process.exitCode=1}}function H(e){let o;let s;let n;let r;const t=l=>{if(o)throw new u("Choose exactly one of --dry-run, --apply, --verify, or --restore.");o=l};const a=(l,i)=>{const f=e[l+1];if(!f||f.startsWith("--"))throw new u(`${i} requires a value.`);return f};for(let l=0;l<e.length;l+=1){const i=e[l];if(i==="--help"||i==="-h")throw new u(exports.USAGE_BACKFILL_HELP);if(i==="--dry-run"){t("dry-run");continue}if(i==="--apply"){t("apply");continue}if(i==="--verify"){t("verify");continue}if(i==="--restore"){t("restore");r=a(l,"--restore");l+=1;continue}if(i.startsWith("--restore=")){t("restore");r=i.slice("--restore=".length);if(!r)throw new u("--restore requires a value.");continue}if(i==="--db"){s=a(l,"--db");l+=1;continue}if(i.startsWith("--db=")){s=i.slice("--db=".length);if(!s)throw new u("--db requires a value.");continue}if(i==="--backup"){n=a(l,"--backup");l+=1;continue}if(i.startsWith("--backup=")){n=i.slice("--backup=".length);if(!n)throw new u("--backup requires a value.");continue}throw new u(`Unknown option: ${i}`)}const c=o??"dry-run";if(n&&c!=="apply"){throw new u("--backup is only valid with --apply.")}if(r&&c!=="restore"){throw new u("--restore is only valid with restore mode.")}return{mode:c,dbPath:s,backupPath:n,restorePath:r}}function N(e){const o=e.mode==="dry-run"?"Dry-run":e.mode==="apply"?"Apply":e.mode==="restore"?"Restore":"Verify";const s=[`[crewx] Usage backfill \u2014 ${o}`,` Database: ${e.dbPath}`,` Tasks scanned: ${e.scannedTasks}`,` Terminal tasks: ${e.terminalTasks}`,` Raw target tasks: ${e.rawTargetTasks}`,` Candidate tasks: ${e.candidateTasks}`,` Recovered: ${e.recoveredTasks}`,` Unavailable: ${e.unavailableTasks}`,` Deferred (within 24h): ${e.deferredTasks}`,` Changed: ${e.changedTasks}`,` Would change: ${e.wouldChangeTasks}`,` Restored: ${e.restored}`,` Skipped conflicts: ${e.skippedConflict}`,` Remaining target tasks: ${e.remainingTargetTasks}`,` Tasks with usageAccounting: ${e.accountingTasks}`," Provider distribution:"];const n=Object.entries(e.providerDistribution);if(n.length===0){s.push(" (none)")}else{for(const[t,a]of n){s.push(` ${t}: candidates=${a.candidates}, recovered=${a.recovered}, unavailable=${a.unavailable}, deferred=${a.deferred}`)}}s.push(" Reason counts:");for(const[t,a]of Object.entries(e.reasonCounts))s.push(` ${t}: ${a}`);s.push(` Estimated free-space need: ${e.estimatedFreeSpaceBytes}`);s.push(` Required free space (with reserve): ${e.requiredFreeSpaceBytes}`);s.push(` Available free space: ${e.availableFreeSpaceBytes}`);s.push(` Free-space gate: ${e.freeSpaceGatePassed?"PASS":"FAIL"}`);const r=e.snapshotPath??e.backupPath;if(r){s.push(` Undo snapshot: ${r}`);if(e.backupMethod)s.push(` Snapshot method: ${e.backupMethod}`)}if(e.blockedReason)s.push(` Blocked: ${e.blockedReason}`);for(const t of e.conflictSamples){s.push(` Conflict sample: ${t.taskId}: ${t.fields.join(", ")}`)}for(const t of e.failures)s.push(` Failure: ${t.taskId}: ${t.reason}`);for(const t of e.warnings)s.push(` Warning: ${t}`);return s.join("\n")}async function F(e){let o;try{o=H(e)}catch(s){const n=s instanceof Error?s.message:String(s);if(n===exports.USAGE_BACKFILL_HELP){console.log(n)}else{console.error(n);console.error(exports.USAGE_BACKFILL_HELP);process.exitCode=2}return}try{const s=await(0,S.runUsageBackfill)(o);console.log(N(s));process.exitCode=s.ok?0:1}catch(s){console.error(s instanceof Error?s.message:String(s));process.exitCode=1}}async function W(e){const o=e.includes("--force");const s=e.includes("--dry-run");const n=A();const r=(0,_.openDrizzleDb)(n);try{const t=(0,m.pushSchema)(r.db,{force:o,dbPath:n,dryRun:true});console.log(L(t,n));console.log("");if(s){console.log("[crewx] Dry-run mode \u2014 no changes applied.");return}if(!P(t)){console.log("[crewx] No changes needed. Database is up to date.");return}if(!o){const c=await M("Apply changes? (y/N): ");if(c.trim().toLowerCase()!=="y"){console.log("[crewx] Aborted.");return}}const a=(0,m.pushSchema)(r.db,{force:o,dbPath:n});console.log("");console.log("[crewx] Push complete.");if(a.created.length>0){console.log(` Tables created: ${a.created.join(", ")}`)}if(a.altered.length>0){console.log(` Columns added: ${a.altered.map(c=>`${c.table}.${c.column}`).join(", ")}`)}if(a.backupPath){console.log(` Backup: ${a.backupPath}`)}if(a.created.length===0&&a.altered.length===0){console.log(" No changes applied.")}}finally{r.close()}}
@@ -0,0 +1,102 @@
1
+ /**
2
+ * Shared receipt-mode implementation for the query and execute commands.
3
+ *
4
+ * A receipt is persisted before a detached runner is started. The runner gets
5
+ * its own identity variables, while the trace variables remain the caller's
6
+ * trace context and are never repurposed as the runner assignment.
7
+ */
8
+ import { TaskRepository } from '@crewx/sdk/repository';
9
+ import type { DurableTaskAdmission, TaskActor } from '@crewx/sdk';
10
+ export type ReceiptActorKind = 'human' | 'agent' | 'system' | 'unknown';
11
+ export interface ReceiptActor {
12
+ kind: ReceiptActorKind;
13
+ id: string | null;
14
+ }
15
+ /** Resolve the CLI actor from metadata and the caller's current environment. */
16
+ export declare function resolveCliTaskActor(metadata: Record<string, unknown>, agentId?: string | undefined): TaskActor;
17
+ /**
18
+ * Resolve the actor at the receipt boundary while the caller's environment is
19
+ * still available. The receipt and synchronous paths share one predicate so
20
+ * cron runners cannot diverge after root context is stripped.
21
+ */
22
+ export declare function resolveReceiptActor(metadata: Record<string, unknown>, agentId?: string | undefined): ReceiptActor;
23
+ /**
24
+ * Report a blocked direct admission (WI-CTO-20260919-004 R2 finding #1) to
25
+ * the operator. Never called for an adopted (already pending) receipt — see
26
+ * admitDurableTask()'s own doc comment.
27
+ */
28
+ export declare function reportAdmissionBlocked(admission: Extract<DurableTaskAdmission, {
29
+ ok: false;
30
+ }>): void;
31
+ /**
32
+ * Remove receipt-selection flags, respecting the `--` literal-args sentinel.
33
+ */
34
+ export declare function extractReceiptFlags(args: string[]): {
35
+ detach: boolean;
36
+ wait: boolean;
37
+ rest: string[];
38
+ };
39
+ export type ReceiptModeReason = 'explicit-detach' | 'agent-context' | 'detached-runner' | 'wait' | 'default';
40
+ export interface ReceiptModeDecision {
41
+ receipt: boolean;
42
+ reason: ReceiptModeReason;
43
+ }
44
+ /** Resolve the receipt/synchronous mode contract shared by q and x. */
45
+ export declare function resolveReceiptMode(options: {
46
+ detach: boolean;
47
+ wait: boolean;
48
+ detachedRunner: boolean;
49
+ agentContext: boolean;
50
+ }): ReceiptModeDecision;
51
+ /**
52
+ * Rebuild runner argv from the parsed request. The resolved prompt is passed
53
+ * as one argv value after `--`, so stdin is never needed by the detached
54
+ * child and prompt text beginning with a dash remains positional text.
55
+ */
56
+ export declare function buildDetachedRunnerArgs(filteredArgs: string[], agentRef: string, finalMessage: string): string[];
57
+ export type AssignedTaskAdoption = {
58
+ kind: 'adopted';
59
+ taskId: string;
60
+ } | {
61
+ kind: 'stale';
62
+ } | {
63
+ kind: 'failed';
64
+ };
65
+ /**
66
+ * Claim a pre-issued receipt without overwriting an already-owned row.
67
+ * Assignment-only leakage is recoverable: discard the stale id and let the
68
+ * normal SDK path create a fresh task. A real detached runner must fail loud
69
+ * because its receipt would otherwise point at no execution row.
70
+ */
71
+ export declare function adoptAssignedTask(taskRepo: Pick<TaskRepository, 'adoptPendingTask'>, taskId: string, detachedRunner: boolean): AssignedTaskAdoption;
72
+ /** Read the actor persisted on the receipt after the runner adopts it. */
73
+ export declare function readAssignedTaskActor(taskRepo: Pick<TaskRepository, 'getTask'>, taskId: string): TaskActor;
74
+ /**
75
+ * Provider account id recorded on the adopted task row. The detached child runs
76
+ * on that account instead of whatever is active when it starts. The value comes
77
+ * from the row, never from process.env. Absent or unreadable metadata means
78
+ * "no pin": the SDK resolves the account as for any other run.
79
+ */
80
+ export declare function readAssignedTaskProviderAccountId(taskRepo: Pick<TaskRepository, 'getTask'>, taskId: string): string | undefined;
81
+ export interface DetachedMiddleHop {
82
+ taskId: string;
83
+ logPath: string;
84
+ runnerArgs: string[];
85
+ }
86
+ /**
87
+ * Read the private Windows middle-hop contract. Invalid direct invocations
88
+ * are left untouched and therefore fail through the normal unknown-option
89
+ * path instead of becoming an undocumented public command.
90
+ */
91
+ export declare function readDetachedMiddleHop(args: string[]): DetachedMiddleHop | undefined;
92
+ /**
93
+ * Windows middle hop: transfer a transient log handle to the final runner,
94
+ * close the middle process's copy, and exit immediately. The parent treats a
95
+ * successful middle-hop exit as normal and makes adoption a database decision.
96
+ */
97
+ export declare function runDetachedMiddle(command: string, hop: DetachedMiddleHop): Promise<void>;
98
+ /**
99
+ * Persist a receipt and launch its detached runner. Unix uses one hop. Windows
100
+ * uses a short-lived middle hop so the final runner has no live parent link.
101
+ */
102
+ export declare function runDetached(command: string, filteredArgs: string[], reason: ReceiptModeReason, invocationArgs?: string[]): Promise<void>;
@@ -0,0 +1,15 @@
1
+ "use strict";Object.defineProperty(exports,"__esModule",{value:true});exports.resolveCliTaskActor=V;exports.resolveReceiptActor=X;exports.reportAdmissionBlocked=B;exports.extractReceiptFlags=he;exports.resolveReceiptMode=_e;exports.buildDetachedRunnerArgs=Y;exports.adoptAssignedTask=we;exports.readAssignedTaskActor=me;exports.readAssignedTaskProviderAccountId=Te;exports.readDetachedMiddleHop=Ee;exports.runDetachedMiddle=xe;exports.runDetached=ye;const F=require("child_process");const l=require("fs");const y=require("path");const ne=require("os");const v=require("@crewx/sdk");const se=require("@crewx/sdk/node");const oe=require("@crewx/sdk/repository");const ie=require("./parse-agent-message");const j=require("./parse-common-flags");const ce=require("./resolve-recipient");const ae=require("./resolve-prompt");const de=require("../utils/version");const G=require("../utils/inherited-trace");const U=require("./cal");const ue=require("./emit-trailer");function b(e){return typeof e==="string"&&e.trim().length>0?e.trim():void 0}function V(e,t=process.env["CREWX_AGENT_ID"]){const n=b(e.schedule_id);if(n)return{actorKind:"system",actorId:`cron:${n}`};const r=e.workflow;const s=r!==null&&typeof r==="object"&&!Array.isArray(r)?r:void 0;const o=b(s?.exec_id);if(o)return{actorKind:"system",actorId:`workflow:${o}`};const i=b(t);if(i)return{actorKind:"agent",actorId:i};if(process.stdin.isTTY===true||process.stderr.isTTY===true){return{actorKind:"human",actorId:(0,se.getLocalActorId)()}}return{actorKind:"unknown",actorId:null}}function X(e,t=process.env["CREWX_AGENT_ID"]){const n=V(e,t);return{kind:n.actorKind,id:n.actorId}}function B(e){process.stderr.write(`${e.message}
2
+ `)}const le=200;const J=3e4;const fe=["--to","--thread","--provider","--model","--metadata","--config","-c","--output-format","--effort","--prompt-file","-f","--out","-o"];const z="--__crewx-detached-middle";const q="--__crewx-detached-task";const C="--__crewx-detached-log";const L="--__crewx-detached-token";const M="CREWX_DETACHED_HOP_TOKEN";function pe(e){return fe.some(t=>e===t||e.startsWith(`${t}=`))}function ge(e){return e==="--prompt-file"||e==="-f"||e.startsWith("--prompt-file=")||e.startsWith("-f=")}function he(e){const t=[];let n=false;let r=false;let s=false;for(const o of e){if(!s&&o==="--"){s=true;t.push(o);continue}if(!s&&(o==="--detach"||o==="--async")){n=true;continue}if(!s&&(o==="--wait"||o.startsWith("--wait="))){r=true;continue}t.push(o)}return{detach:n,wait:r,rest:t}}function _e(e){if(e.wait)return{receipt:false,reason:"wait"};if(e.detachedRunner)return{receipt:false,reason:"detached-runner"};if(e.detach)return{receipt:true,reason:"explicit-detach"};if(e.agentContext)return{receipt:true,reason:"agent-context"};return{receipt:false,reason:"default"}}function h(e){return e instanceof Error?e.message:String(e)}function ke(e,t,n,r={}){return new Promise((s,o)=>{let i=false;let c;let _;const T=()=>{if(c!==void 0)clearInterval(c);if(_!==void 0)clearTimeout(_)};const A=a=>{if(i)return;i=true;T();s(a)};const k=(a,g=false)=>{try{const f=e.getTask(t);if(f&&f.status!=="pending"){A({kind:"adopted"});return}if(a!==void 0){A({kind:"exited",reason:a});return}if(g)A({kind:"timeout"})}catch(f){if(i)return;i=true;T();o(f)}};n.on("error",a=>{k(`Runner failed before adopting task: ${h(a)}`)});n.on("exit",(a,g)=>{if(r.ignoreSuccessfulExit&&a===0&&g===null)return;const f=g?`signal ${g}`:`exit ${a??"unknown"}`;k(`Runner exited before adopting task (${f})`)});c=setInterval(()=>k(),le);_=setTimeout(()=>k(void 0,true),J);k()})}function P(e,t,n){try{const r=e.failPendingTask(t,n);if(r){process.stderr.write(`Detached task ${t} failed: ${n}
3
+ `);return false}const s=e.getTask(t);if(s&&s.status!=="pending")return true}catch(r){n+=`; could not persist failure: ${h(r)}`}process.stderr.write(`Detached task ${t} failed: ${n}
4
+ `);return false}function I(e,t,n,r,s,o,i){let c="pending";try{const T=e.getTask(t);c=T?.status==="pending"?"pending":"running"}catch{}if(o==="json"){process.stdout.write(`${JSON.stringify({taskId:t,status:c,mode:"receipt",logPath:n})}
5
+ `);return}if(s)process.stderr.write(`mode=receipt reason=${r}
6
+ `);process.stdout.write(`${t}
7
+ `);process.stderr.write(`Task ${t} accepted. This ID is the handle (not a PID).
8
+ `);if(i&&s)process.stderr.write(`${i}
9
+ `);const _=(0,U.suggestWaitSeconds)({startedAt:null});(0,U.writeCalFooter)({commandId:"crewx.receipt",params:{taskId:t,wait:String(_)}})}function Y(e,t,n){const r=[];for(let s=0;s<e.length;s+=1){const o=e[s];if(o==="--")break;if(o==="--verbose"||o==="--overdrive"){r.push(o);continue}if(o==="--var"){const i=e[s+1];if(i!==void 0){r.push(o,i);s+=1}continue}if(o.startsWith("--var=")){r.push(o);continue}if(o==="--to"){s+=1;continue}if(o.startsWith("--to="))continue;if(!pe(o))continue;if(ge(o)){if(!o.includes("="))s+=1;continue}r.push(o);if(!o.includes("=")){const i=e[s+1];if(i!==void 0){r.push(i);s+=1}}}return[...r,...t?[t]:[],"--",n]}function Q(e){if(e==="execute")return"x";if(e==="query")return"q";return e}function Z(e){const t={...process.env};delete t[M];if(process.versions.electron){t.ELECTRON_RUN_AS_NODE="1"}t.CREWX_DETACHED_RUNNER="1";t.CREWX_ASSIGNED_TASK_ID=e;return t}function we(e,t,n){if(e.adoptPendingTask(t)){return{kind:"adopted",taskId:t}}const r=`stale assigned id ${t}: expected pending task with pid NULL`;if(n){process.stderr.write(`Detached runner failed to adopt task ${t}: ${r}
10
+ `);return{kind:"failed"}}process.stderr.write(`${r}
11
+ `);return{kind:"stale"}}function me(e,t){const n=e.getTask(t);const r=n?.actor_kind;if(r===null||r===void 0){return{actorKind:"unknown",actorId:null}}if(r==="human"||r==="agent"||r==="system"||r==="unknown"){return{actorKind:r,actorId:n?.actor_id??null}}return{actorKind:"unknown",actorId:n?.actor_id??null}}function Te(e,t){const n=e.getTask(t)?.metadata;if(!n)return void 0;try{const r=JSON.parse(n);if(typeof r!=="object"||r===null||Array.isArray(r))return void 0;const s=r.provider_account_id;return typeof s==="string"&&s.trim().length>0?s.trim():void 0}catch{return void 0}}function Ae(e){const t={...process.env};if(process.versions.electron){t.ELECTRON_RUN_AS_NODE="1"}t[M]=e;return t}function Ee(e){let t=false;let n;let r;let s;let o=false;const i=[];for(const c of e){if(!o&&c==="--"){o=true;i.push(c);continue}if(!o&&c===z){t=true;continue}if(!o&&c.startsWith(`${q}=`)){n=c.slice(`${q}=`.length);continue}if(!o&&c.startsWith(`${C}=`)){r=c.slice(`${C}=`.length);continue}if(!o&&c.startsWith(`${L}=`)){s=c.slice(`${L}=`.length);continue}i.push(c)}if(!t||!n||!r||!s||s!==process.env[M]){return void 0}return{taskId:n,logPath:r,runnerArgs:i}}async function xe(e,t){let n;try{const r=(0,y.dirname)(t.logPath);if(!(0,l.existsSync)(r))(0,l.mkdirSync)(r,{recursive:true});n=(0,l.openSync)(t.logPath,"a");const s=process.argv[1];const o=(0,F.spawn)(process.execPath,[s,Q(e),...t.runnerArgs],{detached:true,windowsHide:true,stdio:["ignore",n,n],env:Z(t.taskId)});(0,l.closeSync)(n);n=void 0;o.unref()}catch(r){if(n!==void 0)(0,l.closeSync)(n);process.stderr.write(`Runner failed before adopting task: ${h(r)}
12
+ `);process.exit(1);return}process.exit(0)}async function ye(e,t,n,r=t){const s=(0,j.parseCommonFlags)(t);const{thread:o,metadata:i,promptFile:c,rest:_,to:T}=s;const{agentRef:A,message:k}=(0,ie.parseAgentMessage)(_);const a=await(0,ae.resolvePrompt)(k,c);let g;try{({agentRef:g}=(0,ce.resolveRecipient)({positionalRef:A,toFlag:T,agentContext:Boolean(process.env["CREWX_AGENT_ID"]),bodyForHint:a,command:e,invocationArgs:r}))}catch(u){const x=h(u);process.stderr.write(`Error: ${x}
13
+ `);(0,ue.emitDelegationResult)({command:e,trace:(0,G.readInheritedTrace)(),isError:true});process.exit(2);return}if(!a){console.error("Usage: crewx q|x --to <agent> <message> [options]");console.error(" @agent is a positional shorthand for selecting a recipient.");console.error(" Task body can also be supplied via stdin pipe or --prompt-file.");process.exit(1);return}let f;try{f=(0,j.parseMetadata)(i)}catch(u){process.stderr.write(`Error: ${h(u)}
14
+ `);process.exit(2);return}const E=(0,v.admitDurableTask)(`direct-cli:${e}`);if(!E.ok){B(E);process.exit(1);return}const d=E.taskId;const ee=new Date().toISOString();const R=process.cwd();const te=(0,l.existsSync)((0,y.join)(R,"crewx.yaml"))||(0,l.existsSync)((0,y.join)(R,"crewx.yml"));const N=(0,G.readInheritedTrace)();const re=N?.rootTraceId||d;const p=new oe.TaskRepository;const O=X(f);try{p.startTask({id:d,agentId:g.replace(/^@/,""),actorKind:O.kind,actorId:O.id,prompt:a,mode:e==="q"||e==="query"?"query":"execute",status:"pending",startedAt:ee,pid:null,parentTaskId:N?.parentTaskId??null,callerAgentId:N?.callerAgentId??null,traceId:re,command:process.argv.join(" "),metadata:Object.keys(f).length>0?JSON.stringify(f):null,workspaceId:te?(0,v.hashWorkspaceId)((0,v.resolveWorkspaceOwnerPath)(R)):null,cwd:(0,v.normalizeWorkspacePath)(R),projectId:null,platform:"cli",crewxVersion:de.CLI_VERSION,threadId:o??null})}catch(u){E.release();process.stderr.write(`Failed to create detached task receipt: ${h(u)}
15
+ `);process.exit(1);return}E.release();const W=Y(t,g,a);const K=Q(e);const S=(0,y.join)((0,ne.homedir)(),".crewx","logs");const w=(0,y.join)(S,`${d}.log`);let m;let D;let H=false;try{if(!(0,l.existsSync)(S))(0,l.mkdirSync)(S,{recursive:true});if(process.platform==="win32"){H=true;const u=(0,v.generateId)("req");D=(0,F.spawn)(process.execPath,[process.argv[1],K,z,`${q}=${d}`,`${C}=${w}`,`${L}=${u}`,...W],{detached:true,windowsHide:true,stdio:"ignore",env:Ae(u)})}else{m=(0,l.openSync)(w,"a");D=(0,F.spawn)(process.execPath,[process.argv[1],K,...W],{detached:true,windowsHide:true,stdio:["ignore",m,m],env:Z(d)});(0,l.closeSync)(m);m=void 0}D.unref()}catch(u){if(m!==void 0)(0,l.closeSync)(m);const x=`Runner failed before adopting task: ${h(u)}`;if(!P(p,d,x))process.exit(1);else{I(p,d,w,n,s.verbose,s.outputFormat);process.exit(0)}return}let $;try{$=await ke(p,d,D,{ignoreSuccessfulExit:H})}catch(u){const x=`Runner adoption check failed: ${h(u)}`;if(!P(p,d,x))process.exit(1);else{I(p,d,w,n,s.verbose,s.outputFormat);process.exit(0)}return}if($.kind==="adopted"){I(p,d,w,n,s.verbose,s.outputFormat);process.exit(0);return}if($.kind==="timeout"){I(p,d,w,n,s.verbose,s.outputFormat,`Warning: runner alive, row not yet adopted after ${J/1e3}s.`);process.exit(0);return}if(!P(p,d,$.reason))process.exit(1);else{I(p,d,w,n,s.verbose,s.outputFormat);process.exit(0)}}
@@ -6,7 +6,84 @@
6
6
  * crewx doctor Run full diagnosis
7
7
  * crewx doctor --config <path> Use specific config file
8
8
  */
9
+ import type { CrewxExecutableResolution, CrewxExecutableStage } from '@crewx/sdk';
10
+ interface DiagnosticResult {
11
+ name: string;
12
+ status: 'success' | 'warning' | 'error';
13
+ message: string;
14
+ details?: string;
15
+ }
16
+ export interface CliProviderInfo {
17
+ cmd: string;
18
+ install: string;
19
+ }
20
+ export interface DoctorArtifact {
21
+ directory: string;
22
+ sizeBytes: number;
23
+ details: string[];
24
+ }
25
+ export interface NpxCacheReport {
26
+ cacheRoot: string;
27
+ zeroByteLockfileDirectories: DoctorArtifact[];
28
+ staleFileCacheDirectories: DoctorArtifact[];
29
+ cleanableDirectories: DoctorArtifact[];
30
+ }
31
+ export interface HookCommandIssue {
32
+ provider: 'claude' | 'codex';
33
+ settingsPath: string;
34
+ command: string;
35
+ reason: 'launcher' | 'non-absolute';
36
+ }
37
+ export interface HookCommandReport {
38
+ inspectedFiles: string[];
39
+ issues: HookCommandIssue[];
40
+ }
41
+ export interface CrewxShimReport {
42
+ shimRoot: string;
43
+ currentFingerprint?: string;
44
+ staleDirectories: Array<DoctorArtifact & {
45
+ fingerprint: string;
46
+ }>;
47
+ }
48
+ export interface CrewxExecutableReport {
49
+ platform: NodeJS.Platform;
50
+ pathCandidates: string[];
51
+ stage?: CrewxExecutableStage;
52
+ argv?: string[];
53
+ electron: boolean;
54
+ requiredEnvKeys: string[];
55
+ binResolution: 'global_first' | 'local_first';
56
+ adoptedPath?: string;
57
+ resolutionError?: string;
58
+ }
59
+ export interface CrewxExecutableInspectionOptions {
60
+ platform?: NodeJS.Platform;
61
+ cwd?: string;
62
+ env?: NodeJS.ProcessEnv;
63
+ binResolution?: 'global_first' | 'local_first';
64
+ pathCandidates?: readonly string[];
65
+ resolution?: CrewxExecutableResolution;
66
+ }
67
+ /** CLI diagnostics keyed by the bare provider name used in PROVIDER_ORDER. */
68
+ export declare const CLI_PROVIDER_INFO: Record<string, CliProviderInfo>;
69
+ /** Format a byte count for diagnostic output. */
70
+ export declare function formatBytes(bytes: number): string;
71
+ /** Inspect npm's persistent `_npx` cache without modifying it. */
72
+ export declare function inspectNpxCache(cacheRoot?: string): NpxCacheReport;
73
+ /** Inspect installed CrewX hooks without rewriting provider settings. */
74
+ export declare function inspectHookCommands(projectRoot: string): HookCommandReport;
75
+ /** Inspect the SDK-selected CrewX executable without printing environment values. */
76
+ export declare function inspectCrewxExecutable(options?: CrewxExecutableInspectionOptions & {
77
+ configPath?: string;
78
+ }): CrewxExecutableReport;
79
+ /** Inspect orphaned CrewX PATH shim directories without modifying them. */
80
+ export declare function inspectCrewxShims(crewxHome?: string, resolvedFingerprint?: string | undefined): CrewxShimReport;
81
+ /**
82
+ * Check CLI provider availability in the canonical PROVIDER_ORDER.
83
+ */
84
+ export declare function checkCliProviders(): DiagnosticResult[];
9
85
  /**
10
86
  * Handle `crewx doctor` command.
11
87
  */
12
88
  export declare function handleDoctor(args: string[]): Promise<void>;
89
+ export {};