@crewx/cli 0.9.0-rc.11 → 0.9.0-rc.110

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 (59) 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.js +1 -0
  5. package/dist/commands/agent.js +1 -107
  6. package/dist/commands/cal.d.ts +15 -0
  7. package/dist/commands/cal.js +44 -0
  8. package/dist/commands/db.d.ts +2 -0
  9. package/dist/commands/db.js +381 -1
  10. package/dist/commands/detached.d.ts +89 -0
  11. package/dist/commands/detached.js +574 -0
  12. package/dist/commands/doctor.d.ts +53 -0
  13. package/dist/commands/doctor.js +391 -21
  14. package/dist/commands/emit-trailer.d.ts +25 -0
  15. package/dist/commands/emit-trailer.js +33 -0
  16. package/dist/commands/execute.d.ts +7 -4
  17. package/dist/commands/execute.js +171 -41
  18. package/dist/commands/hook/command-marker.d.ts +2 -0
  19. package/dist/commands/hook/command-marker.js +5 -0
  20. package/dist/commands/hook/install.d.ts +0 -1
  21. package/dist/commands/hook/install.js +60 -63
  22. package/dist/commands/hook/status.js +3 -3
  23. package/dist/commands/hook/uninstall.js +2 -2
  24. package/dist/commands/init.d.ts +34 -0
  25. package/dist/commands/init.js +130 -57
  26. package/dist/commands/kill.d.ts +2 -8
  27. package/dist/commands/kill.js +6 -47
  28. package/dist/commands/log.js +4 -3
  29. package/dist/commands/parse-common-flags.d.ts +5 -1
  30. package/dist/commands/parse-common-flags.js +6 -2
  31. package/dist/commands/ps.d.ts +2 -2
  32. package/dist/commands/ps.js +25 -15
  33. package/dist/commands/publish.d.ts +1 -0
  34. package/dist/commands/publish.js +303 -0
  35. package/dist/commands/query.d.ts +5 -1
  36. package/dist/commands/query.js +165 -37
  37. package/dist/commands/registry.js +4 -2
  38. package/dist/commands/result.d.ts +8 -3
  39. package/dist/commands/result.js +139 -7
  40. package/dist/commands/shortcut.d.ts +1 -0
  41. package/dist/commands/shortcut.js +267 -0
  42. package/dist/commands/slack.js +2 -1
  43. package/dist/commands/stop.d.ts +8 -0
  44. package/dist/commands/stop.js +119 -0
  45. package/dist/commands/write-output.d.ts +3 -0
  46. package/dist/commands/write-output.js +24 -0
  47. package/dist/index.d.ts +1 -1
  48. package/dist/index.js +4 -1
  49. package/dist/logging.d.ts +1 -1
  50. package/dist/logging.js +3 -2
  51. package/dist/main.d.ts +3 -2
  52. package/dist/main.js +84 -18
  53. package/dist/utils/env-defaults.d.ts +2 -5
  54. package/dist/utils/env-defaults.js +10 -5
  55. package/dist/utils/inherited-trace.d.ts +24 -0
  56. package/dist/utils/inherited-trace.js +40 -0
  57. package/dist/utils/sdk-compat.d.ts +24 -0
  58. package/dist/utils/sdk-compat.js +120 -0
  59. package/package.json +15 -12
@@ -6,40 +6,85 @@
6
6
  * Flags:
7
7
  * --thread <name> Conversation thread name
8
8
  * --provider <cli/xxx> Provider override
9
+ * --model <name> Model override (e.g. claude-sonnet-5)
9
10
  * --metadata <json> Extra metadata JSON (double-quoted object). Propagated to events/hooks/tracing.
10
- * e.g. --metadata='{"workflow_id":"wf-1"}'
11
11
  * --verbose Debug output mode (default: raw agent response only)
12
12
  * --config/-c <path> Config file path override
13
13
  * --output-format <fmt> Output format (json|text|stream-json)
14
14
  * --effort <level> Model effort (high|medium|low)
15
- * -f/--prompt-file <path> Read task body from file (bypasses cmd.exe argv truncation)
15
+ * -f/--prompt-file <path> Read task body from file
16
+ * --detach/--async Persist a receipt and launch a detached runner
17
+ * --wait Force synchronous execution
16
18
  *
17
19
  * Stdin support:
18
20
  * Pipe or redirect content into crewx x to supply the task body via stdin.
19
- * e.g. cat task.md | crewx x "@agent label"
20
21
  * Stdin is ignored when running in an interactive TTY.
21
22
  */
22
23
  Object.defineProperty(exports, "__esModule", { value: true });
24
+ exports.runDetachedMiddle = void 0;
23
25
  exports.handleExecute = handleExecute;
24
26
  const sdk_1 = require("@crewx/sdk");
27
+ const repository_1 = require("@crewx/sdk/repository");
25
28
  const parse_agent_message_1 = require("./parse-agent-message");
26
29
  const parse_common_flags_1 = require("./parse-common-flags");
27
30
  const resolve_prompt_1 = require("./resolve-prompt");
28
31
  const crewx_cli_1 = require("../bootstrap/crewx-cli");
29
32
  const inherited_trace_1 = require("../utils/inherited-trace");
33
+ const write_output_1 = require("./write-output");
34
+ const detached_1 = require("./detached");
35
+ const emit_trailer_1 = require("./emit-trailer");
36
+ // Exported for platform-focused tests; the middle hop remains an internal CLI
37
+ // contract and is not routed through the public command registry.
38
+ var detached_2 = require("./detached");
39
+ Object.defineProperty(exports, "runDetachedMiddle", { enumerable: true, get: function () { return detached_2.runDetachedMiddle; } });
30
40
  /**
31
41
  * Handle `crewx execute <agentRef> <message>` command.
32
42
  *
33
43
  * Default output: raw agent response only (stdout).
34
44
  * --verbose: debug info written to stderr, response to stdout.
35
45
  */
36
- async function handleExecute(args) {
37
- const { thread, provider, metadata, verbose, config, outputFormat, effort, promptFile, overdrive, vars, rest } = (0, parse_common_flags_1.parseCommonFlags)(args);
38
- const { agentRef: parsedAgentRef, message } = (0, parse_agent_message_1.parseAgentMessage)(rest);
39
- // No @mention → default to @crewx agent (matches cli-bak behaviour)
46
+ async function handleExecute(args, command = 'execute', emitState = (0, emit_trailer_1.createDelegationEmitState)()) {
47
+ // The private middle-hop marker is consumed before public flag parsing. An
48
+ // invalid direct use remains an unknown option and is rejected normally.
49
+ const middleHop = (0, detached_1.readDetachedMiddleHop)(args);
50
+ if (middleHop) {
51
+ await (0, detached_1.runDetachedMiddle)(command, middleHop);
52
+ return;
53
+ }
54
+ const receiptFlags = (0, detached_1.extractReceiptFlags)(args);
55
+ const receiptMode = (0, detached_1.resolveReceiptMode)({
56
+ detach: receiptFlags.detach,
57
+ wait: receiptFlags.wait,
58
+ detachedRunner: (0, inherited_trace_1.isDetachedRunner)(),
59
+ agentContext: Boolean(process.env['CREWX_AGENT_ID']),
60
+ });
61
+ if (receiptMode.receipt) {
62
+ await (0, detached_1.runDetached)(command, receiptFlags.rest, receiptMode.reason);
63
+ return;
64
+ }
65
+ let parsedFlags;
66
+ try {
67
+ parsedFlags = (0, parse_common_flags_1.parseCommonFlags)(receiptFlags.rest);
68
+ }
69
+ catch (err) {
70
+ (0, emit_trailer_1.emitDelegationResult)({ command, isError: true, state: emitState });
71
+ throw err;
72
+ }
73
+ const { thread, provider, model, metadata, verbose, config, outputFormat, effort, promptFile, overdrive, vars, out, rest } = parsedFlags;
74
+ const inheritedTrace = (0, inherited_trace_1.readInheritedTrace)();
75
+ let parsedAgentRef;
76
+ let message;
77
+ let finalMessage;
78
+ try {
79
+ ({ agentRef: parsedAgentRef, message } = (0, parse_agent_message_1.parseAgentMessage)(rest));
80
+ // Resolve final prompt: argv | stdin pipe | --prompt-file (or combination)
81
+ finalMessage = await (0, resolve_prompt_1.resolvePrompt)(message, promptFile);
82
+ }
83
+ catch (err) {
84
+ (0, emit_trailer_1.emitDelegationResult)({ command, trace: inheritedTrace, isError: true, state: emitState });
85
+ throw err;
86
+ }
40
87
  const agentRef = parsedAgentRef || '@crewx';
41
- // Resolve final prompt: argv | stdin pipe | --prompt-file (or combination)
42
- const finalMessage = await (0, resolve_prompt_1.resolvePrompt)(message, promptFile);
43
88
  if (!finalMessage) {
44
89
  console.error('Usage: crewx execute [@agent] <task> [options]');
45
90
  console.error(' crewx x [@agent] <task> [options]');
@@ -55,6 +100,7 @@ async function handleExecute(args) {
55
100
  console.error('Options:');
56
101
  console.error(' --thread <name> Conversation thread name');
57
102
  console.error(' --provider <cli/xxx> Provider override');
103
+ console.error(' --model <name> Model override (e.g. claude-sonnet-5)');
58
104
  console.error(' --metadata <json> Extra metadata (JSON object, double-quoted).');
59
105
  console.error(' Propagated to events/hooks/tracing.');
60
106
  console.error(' Invalid JSON aborts with exit code 2.');
@@ -62,30 +108,43 @@ async function handleExecute(args) {
62
108
  console.error(' --verbose Debug output mode');
63
109
  console.error(' --config/-c <path> Config file path');
64
110
  console.error(' --output-format <fmt> Output format (json|text|stream-json)');
111
+ console.error(' --out/-o <path> Save result to file (stdout suppressed)');
65
112
  console.error(' --effort <level> Model effort (high|medium|low)');
66
113
  console.error(' -f/--prompt-file <path> Read task body from file');
67
114
  console.error(' --var key=value Template variable (repeatable)');
68
115
  console.error(' --overdrive Activate overdrive (boost) profile for this request');
116
+ (0, emit_trailer_1.emitDelegationResult)({ command, trace: inheritedTrace, isError: true, state: emitState });
69
117
  process.exit(1);
118
+ return;
70
119
  }
71
120
  const configPath = config ?? process.env.CREWX_CONFIG ?? 'crewx.yaml';
72
121
  // Only show exec audit span JSON in verbose mode
73
122
  (0, sdk_1.setAuditVerbose)(verbose);
74
- const crewx = await (0, crewx_cli_1.createCliCrewx)(configPath);
123
+ let crewx;
124
+ try {
125
+ crewx = await (0, crewx_cli_1.createCliCrewx)(configPath);
126
+ }
127
+ catch (err) {
128
+ (0, emit_trailer_1.emitDelegationResult)({ command, trace: inheritedTrace, isError: true, state: emitState });
129
+ throw err;
130
+ }
75
131
  // file:// remote agent delegation is handled transparently inside Crewx.query/execute.
76
132
  if (verbose) {
133
+ process.stderr.write(`mode=sync reason=${receiptMode.reason}\n`);
77
134
  process.stderr.write(`📋 Task: ${finalMessage}\n`);
78
135
  process.stderr.write(`🤖 Agent: ${agentRef}\n`);
79
136
  if (thread)
80
137
  process.stderr.write(`🔗 Thread: ${thread}\n`);
81
138
  if (provider)
82
139
  process.stderr.write(`🔌 Provider: ${provider}\n`);
140
+ if (model)
141
+ process.stderr.write(`🧠 Model: ${model}\n`);
83
142
  if (outputFormat)
84
143
  process.stderr.write(`📄 Output-format: ${outputFormat}\n`);
85
144
  if (effort)
86
145
  process.stderr.write(`⚡ Effort: ${effort}\n`);
87
146
  if (overdrive)
88
- process.stderr.write(`🚀 Overdrive: ON\n`);
147
+ process.stderr.write('🚀 Overdrive: ON\n');
89
148
  process.stderr.write('─'.repeat(60) + '\n');
90
149
  }
91
150
  let parsedMetadata = {};
@@ -95,48 +154,119 @@ async function handleExecute(args) {
95
154
  catch (err) {
96
155
  const msg = err instanceof Error ? err.message : String(err);
97
156
  process.stderr.write(`Error: ${msg}\n`);
157
+ (0, emit_trailer_1.emitDelegationResult)({ command, trace: inheritedTrace, isError: true, state: emitState });
98
158
  process.exit(2);
159
+ return;
160
+ }
161
+ const actor = (0, detached_1.resolveCliTaskActor)(parsedMetadata);
162
+ // Only the detached runner may adopt a pre-issued task row. Trace variables
163
+ // describe the caller chain and are never used as this task's identity.
164
+ const detachedRunner = (0, inherited_trace_1.isDetachedRunner)();
165
+ const assignedTaskId = (0, inherited_trace_1.readRunnerAssignment)();
166
+ let selfTaskId;
167
+ let taskActor;
168
+ if (assignedTaskId !== undefined) {
169
+ const taskRepo = new repository_1.TaskRepository();
170
+ const adoption = (0, detached_1.adoptAssignedTask)(taskRepo, assignedTaskId, detachedRunner);
171
+ if (adoption.kind === 'failed') {
172
+ (0, emit_trailer_1.emitDelegationResult)({ command, trace: inheritedTrace, isError: true, state: emitState });
173
+ process.exit(1);
174
+ return;
175
+ }
176
+ if (adoption.kind === 'adopted') {
177
+ selfTaskId = adoption.taskId;
178
+ taskActor = (0, detached_1.readAssignedTaskActor)(taskRepo, adoption.taskId);
179
+ }
180
+ }
181
+ // A synchronous `crewx x` runs in this same process, but this process is
182
+ // still separate from the embedded server and never sees its in-memory
183
+ // UpdateGateService (WI-CTO-20260919-004 R2 finding #1) — only the durable
184
+ // marker it writes. Reserve-then-check before calling into the SDK below,
185
+ // not after; see admitDurableTask()'s own doc comment. Never checked for
186
+ // an adopted receipt (selfTaskId set): that work was already admitted by
187
+ // whichever process first created its pending DB row.
188
+ let admission;
189
+ if (selfTaskId === undefined) {
190
+ admission = (0, sdk_1.admitDurableTask)(`direct-cli:${command}`);
191
+ if (!admission.ok) {
192
+ (0, detached_1.reportAdmissionBlocked)(admission);
193
+ (0, emit_trailer_1.emitDelegationResult)({ command, trace: inheritedTrace, isError: true, state: emitState });
194
+ process.exit(1);
195
+ return;
196
+ }
99
197
  }
100
198
  let exitCode = 0;
199
+ let result;
101
200
  try {
102
- const result = await crewx.execute(agentRef, finalMessage, {
103
- provider,
104
- effort: effort || undefined,
105
- overdrive: overdrive || undefined,
106
- threadId: thread,
107
- metadata: Object.keys(parsedMetadata).length > 0 ? parsedMetadata : undefined,
108
- vars: Object.keys(vars).length > 0 ? vars : undefined,
109
- trace: (0, inherited_trace_1.readInheritedTrace)(),
110
- });
111
- if (!result.ok) {
112
- const errMsg = result.error?.message ?? 'Execute failed';
113
- console.error(errMsg);
201
+ try {
202
+ result = await crewx.execute(agentRef, finalMessage, {
203
+ provider,
204
+ model,
205
+ effort: effort || undefined,
206
+ overdrive: overdrive || undefined,
207
+ threadId: thread,
208
+ taskId: selfTaskId,
209
+ actor: assignedTaskId === undefined ? actor : taskActor,
210
+ metadata: Object.keys(parsedMetadata).length > 0 ? parsedMetadata : undefined,
211
+ vars: Object.keys(vars).length > 0 ? vars : undefined,
212
+ trace: inheritedTrace,
213
+ });
214
+ if (!result.ok) {
215
+ const errMsg = result.error?.message ?? 'Execute failed';
216
+ console.error(errMsg);
217
+ (0, write_output_1.appendError)(out, errMsg);
218
+ exitCode = 1;
219
+ }
220
+ else {
221
+ if (verbose) {
222
+ process.stderr.write(`\n📊 Results from ${agentRef}:\n`);
223
+ process.stderr.write('═'.repeat(60) + '\n');
224
+ process.stderr.write('🟢 Status: Success\n');
225
+ process.stderr.write(`🤖 Provider: ${result.meta.provider}\n`);
226
+ process.stderr.write(`⏱️ Duration: ${result.meta.durationMs}ms\n`);
227
+ process.stderr.write('\n📄 Response:\n');
228
+ process.stderr.write('─'.repeat(40) + '\n');
229
+ }
230
+ (0, write_output_1.writeResult)(out, result.data);
231
+ if (verbose)
232
+ process.stderr.write('\n✅ Execute completed successfully\n');
233
+ }
234
+ }
235
+ catch (err) {
236
+ const errMsg = err instanceof Error ? err.message : String(err);
237
+ console.error(`Error: ${errMsg}`);
238
+ (0, write_output_1.appendError)(out, `Error: ${errMsg}`);
114
239
  exitCode = 1;
115
240
  }
116
- else {
117
- if (verbose) {
118
- process.stderr.write(`\n📊 Results from ${agentRef}:\n`);
119
- process.stderr.write('═'.repeat(60) + '\n');
120
- process.stderr.write('🟢 Status: Success\n');
121
- process.stderr.write(`🤖 Provider: ${result.meta.provider}\n`);
122
- process.stderr.write(`⏱️ Duration: ${result.meta.durationMs}ms\n`);
123
- process.stderr.write('\n📄 Response:\n');
124
- process.stderr.write('─'.repeat(40) + '\n');
241
+ finally {
242
+ try {
243
+ await crewx.close();
125
244
  }
126
- console.log(result.data);
127
- if (verbose) {
128
- process.stderr.write('\n✅ Execute completed successfully\n');
245
+ catch (err) {
246
+ (0, emit_trailer_1.emitDelegationResult)({
247
+ command,
248
+ trace: inheritedTrace,
249
+ taskId: result?.meta.taskId,
250
+ agentId: result?.meta.agentId,
251
+ isError: true,
252
+ state: emitState,
253
+ });
254
+ throw err;
129
255
  }
130
256
  }
131
257
  }
132
- catch (err) {
133
- const errMsg = err instanceof Error ? err.message : String(err);
134
- console.error(`Error: ${errMsg}`);
135
- exitCode = 1;
136
- }
137
258
  finally {
138
- await crewx.close();
259
+ if (admission?.ok)
260
+ admission.release();
139
261
  }
262
+ (0, emit_trailer_1.emitDelegationResult)({
263
+ command,
264
+ trace: inheritedTrace,
265
+ taskId: result?.meta.taskId,
266
+ agentId: result?.meta.agentId,
267
+ isError: exitCode !== 0 || result?.ok === false,
268
+ state: emitState,
269
+ });
140
270
  if (exitCode !== 0)
141
271
  process.exit(exitCode);
142
272
  }
@@ -0,0 +1,2 @@
1
+ /** Match only commands that invoke CrewX's hook-dispatch subcommand. */
2
+ export declare const CREWX_HOOK_COMMAND_RE: RegExp;
@@ -0,0 +1,5 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.CREWX_HOOK_COMMAND_RE = void 0;
4
+ /** Match only commands that invoke CrewX's hook-dispatch subcommand. */
5
+ exports.CREWX_HOOK_COMMAND_RE = /crewx(?:\.js|\.cmd|\.exe)?['"]?\s+hook-dispatch\b/i;
@@ -10,7 +10,6 @@
10
10
  * - Never traverses parent directories — project root determined by crewx.yaml
11
11
  */
12
12
  import { type HookProvider } from './paths';
13
- export declare const CREWX_HOOK_COMMAND_MARKER = "crewx hook-dispatch";
14
13
  export declare function resolveCrewxBinary(): string;
15
14
  export interface HookInstallOpts {
16
15
  projectRoot: string;
@@ -10,49 +10,14 @@
10
10
  * - Preserves existing user hooks
11
11
  * - Never traverses parent directories — project root determined by crewx.yaml
12
12
  */
13
- var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
14
- if (k2 === undefined) k2 = k;
15
- var desc = Object.getOwnPropertyDescriptor(m, k);
16
- if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
17
- desc = { enumerable: true, get: function() { return m[k]; } };
18
- }
19
- Object.defineProperty(o, k2, desc);
20
- }) : (function(o, m, k, k2) {
21
- if (k2 === undefined) k2 = k;
22
- o[k2] = m[k];
23
- }));
24
- var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
25
- Object.defineProperty(o, "default", { enumerable: true, value: v });
26
- }) : function(o, v) {
27
- o["default"] = v;
28
- });
29
- var __importStar = (this && this.__importStar) || (function () {
30
- var ownKeys = function(o) {
31
- ownKeys = Object.getOwnPropertyNames || function (o) {
32
- var ar = [];
33
- for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
34
- return ar;
35
- };
36
- return ownKeys(o);
37
- };
38
- return function (mod) {
39
- if (mod && mod.__esModule) return mod;
40
- var result = {};
41
- if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
42
- __setModuleDefault(result, mod);
43
- return result;
44
- };
45
- })();
46
13
  Object.defineProperty(exports, "__esModule", { value: true });
47
- exports.CREWX_HOOK_COMMAND_MARKER = void 0;
48
14
  exports.resolveCrewxBinary = resolveCrewxBinary;
49
15
  exports.handleHookInstall = handleHookInstall;
50
16
  const fs_1 = require("fs");
51
- const path_1 = require("path");
52
- const cp = __importStar(require("child_process"));
17
+ const sdk_1 = require("@crewx/sdk");
53
18
  const paths_1 = require("./paths");
54
- exports.CREWX_HOOK_COMMAND_MARKER = 'crewx hook-dispatch';
55
- function getProviderConfigs(projectRoot, providers, crewxBin) {
19
+ const command_marker_1 = require("./command-marker");
20
+ function getProviderConfigs(projectRoot, providers) {
56
21
  return providers.map((provider) => ({
57
22
  settingsPath: provider === 'claude'
58
23
  ? (0, paths_1.getClaudeSettingsPath)(projectRoot)
@@ -63,24 +28,13 @@ function getProviderConfigs(projectRoot, providers, crewxBin) {
63
28
  }));
64
29
  }
65
30
  function resolveCrewxBinary() {
66
- if (process.env.CREWX_CLI)
67
- return process.env.CREWX_CLI;
68
- try {
69
- const which = cp.execSync('which crewx 2>/dev/null', { encoding: 'utf8' }).trim();
70
- const isTmpProxy = which.includes('/T/crewx-proxy-') ||
71
- which.includes('/tmp/crewx-proxy-') ||
72
- /\/[Tt]e?mp\//.test(which);
73
- if (which && !isTmpProxy)
74
- return which;
31
+ const resolution = (0, sdk_1.resolveCrewxExecutable)();
32
+ if (!resolution.ok) {
33
+ throw new Error((0, sdk_1.formatCrewxExecutableFailure)(resolution));
75
34
  }
76
- catch { }
77
- if (process.argv[1]) {
78
- const scriptPath = (0, path_1.resolve)(process.argv[1]);
79
- if ((0, fs_1.existsSync)(scriptPath)) {
80
- return `${process.execPath} ${scriptPath}`;
81
- }
82
- }
83
- return 'crewx';
35
+ const stableEntrypoint = (0, sdk_1.materializeCrewxStableEntrypoint)(resolution);
36
+ const useStable = stableEntrypoint && resolution.argv.some(sdk_1.isRotatingInstallPath);
37
+ return (0, sdk_1.formatCrewxExecutableArgv)(useStable ? [stableEntrypoint] : resolution.argv);
84
38
  }
85
39
  function readSettingsTyped(settingsPath) {
86
40
  return (0, paths_1.readSettings)(settingsPath);
@@ -88,10 +42,19 @@ function readSettingsTyped(settingsPath) {
88
42
  function findCrewxEntry(preToolUse) {
89
43
  if (!preToolUse)
90
44
  return -1;
91
- return preToolUse.findIndex((entry) => entry.hooks?.some((h) => h.command?.includes(exports.CREWX_HOOK_COMMAND_MARKER)));
45
+ return preToolUse.findIndex((entry) => entry.hooks?.some((h) => command_marker_1.CREWX_HOOK_COMMAND_RE.test(h.command)));
46
+ }
47
+ function findCrewxHook(entry) {
48
+ if (!entry.hooks)
49
+ return undefined;
50
+ const index = entry.hooks.findIndex((hook) => command_marker_1.CREWX_HOOK_COMMAND_RE.test(hook.command));
51
+ if (index < 0)
52
+ return undefined;
53
+ return { index, command: entry.hooks[index].command };
92
54
  }
93
55
  function installForProvider(projectRoot, config, crewxBin, yes) {
94
56
  const { settingsPath, provider, matcher, commandArg } = config;
57
+ const command = `${crewxBin} hook-dispatch ${commandArg}`;
95
58
  const settings = readSettingsTyped(settingsPath);
96
59
  if (!settings.hooks)
97
60
  settings.hooks = {};
@@ -99,8 +62,28 @@ function installForProvider(projectRoot, config, crewxBin, yes) {
99
62
  settings.hooks.PreToolUse = [];
100
63
  const existingIdx = findCrewxEntry(settings.hooks.PreToolUse);
101
64
  if (existingIdx >= 0) {
102
- console.log(`[crewx] Hook already installed in ${settingsPath} (${provider})`);
103
- return false;
65
+ const existingEntry = settings.hooks.PreToolUse[existingIdx];
66
+ const existingHook = findCrewxHook(existingEntry);
67
+ if (existingHook?.command === command) {
68
+ console.log(`[crewx] Hook already installed in ${settingsPath} (${provider})`);
69
+ return false;
70
+ }
71
+ if (!yes) {
72
+ console.log(`[crewx] Existing CrewX hook needs an install-path refresh in ${settingsPath} (${provider})`);
73
+ return false;
74
+ }
75
+ if ((0, fs_1.existsSync)(settingsPath)) {
76
+ const backupPath = settingsPath + '.crewx-backup';
77
+ (0, fs_1.copyFileSync)(settingsPath, backupPath);
78
+ console.log(`[crewx] Backup created: ${backupPath}`);
79
+ }
80
+ if (existingHook) {
81
+ existingEntry.hooks[existingHook.index] = { ...existingEntry.hooks[existingHook.index], command };
82
+ }
83
+ (0, fs_1.writeFileSync)(settingsPath, JSON.stringify(settings, null, 2) + '\n', 'utf8');
84
+ console.log(`[crewx] Hook updated (${provider}): ${command}`);
85
+ console.log(`[crewx] Settings: ${settingsPath}`);
86
+ return true;
104
87
  }
105
88
  if (!yes) {
106
89
  console.log(`⚠️ crewx hook install will register crewx-hook-dispatch as a PreToolUse hook.\n` +
@@ -119,7 +102,6 @@ function installForProvider(projectRoot, config, crewxBin, yes) {
119
102
  else {
120
103
  (0, paths_1.ensureCodexHooks)(projectRoot);
121
104
  }
122
- const command = `${crewxBin} hook-dispatch ${commandArg}`;
123
105
  settings.hooks.PreToolUse.push({
124
106
  matcher,
125
107
  hooks: [{ type: 'command', command }],
@@ -133,8 +115,15 @@ async function handleHookInstall(argsOrOpts) {
133
115
  if (!Array.isArray(argsOrOpts)) {
134
116
  const { projectRoot, yes, provider } = argsOrOpts;
135
117
  const providers = (0, paths_1.providersFromFilter)(provider ?? 'all');
136
- const crewxBin = resolveCrewxBinary();
137
- const configs = getProviderConfigs(projectRoot, providers, crewxBin);
118
+ let crewxBin;
119
+ try {
120
+ crewxBin = resolveCrewxBinary();
121
+ }
122
+ catch (error) {
123
+ console.error(`[crewx] ${error instanceof Error ? error.message : String(error)}`);
124
+ return;
125
+ }
126
+ const configs = getProviderConfigs(projectRoot, providers);
138
127
  for (const config of configs) {
139
128
  installForProvider(projectRoot, config, crewxBin, yes);
140
129
  }
@@ -152,8 +141,16 @@ async function handleHookInstall(argsOrOpts) {
152
141
  }
153
142
  const providerFilter = (0, paths_1.parseProviderArg)(args);
154
143
  const providers = (0, paths_1.providersFromFilter)(providerFilter);
155
- const crewxBin = resolveCrewxBinary();
156
- const configs = getProviderConfigs(projectRoot, providers, crewxBin);
144
+ let crewxBin;
145
+ try {
146
+ crewxBin = resolveCrewxBinary();
147
+ }
148
+ catch (error) {
149
+ console.error(`[crewx] ${error instanceof Error ? error.message : String(error)}`);
150
+ process.exitCode = 1;
151
+ return;
152
+ }
153
+ const configs = getProviderConfigs(projectRoot, providers);
157
154
  if (!yes) {
158
155
  const targets = configs.map((c) => ` - ${c.provider}: ${c.settingsPath}`).join('\n');
159
156
  console.log(`⚠️ crewx hook install will register crewx-hook-dispatch as a PreToolUse hook.\n` +
@@ -11,7 +11,7 @@ const fs_1 = require("fs");
11
11
  const path_1 = require("path");
12
12
  const sdk_1 = require("@crewx/sdk");
13
13
  const paths_1 = require("./paths");
14
- const CREWX_HOOK_COMMAND_MARKER = 'crewx hook-dispatch';
14
+ const command_marker_1 = require("./command-marker");
15
15
  function showProviderStatus(projectRoot, provider) {
16
16
  const settingsPath = provider === 'claude'
17
17
  ? (0, paths_1.getClaudeSettingsPath)(projectRoot)
@@ -26,9 +26,9 @@ function showProviderStatus(projectRoot, provider) {
26
26
  try {
27
27
  const settings = JSON.parse((0, fs_1.readFileSync)(settingsPath, 'utf8'));
28
28
  const preHooks = settings.hooks?.PreToolUse ?? [];
29
- const crewxEntry = preHooks.find((entry) => entry.hooks?.some((h) => h.command?.includes(CREWX_HOOK_COMMAND_MARKER)));
29
+ const crewxEntry = preHooks.find((entry) => entry.hooks?.some((h) => command_marker_1.CREWX_HOOK_COMMAND_RE.test(h.command ?? '')));
30
30
  if (crewxEntry) {
31
- const command = crewxEntry.hooks.find((h) => h.command?.includes(CREWX_HOOK_COMMAND_MARKER))?.command;
31
+ const command = crewxEntry.hooks.find((h) => command_marker_1.CREWX_HOOK_COMMAND_RE.test(h.command ?? ''))?.command;
32
32
  console.log(` Status: INSTALLED`);
33
33
  console.log(` Command: ${command}`);
34
34
  }
@@ -10,7 +10,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
10
10
  exports.handleHookUninstall = handleHookUninstall;
11
11
  const fs_1 = require("fs");
12
12
  const paths_1 = require("./paths");
13
- const CREWX_HOOK_COMMAND_MARKER = 'crewx hook-dispatch';
13
+ const command_marker_1 = require("./command-marker");
14
14
  function getSettingsPath(projectRoot, provider) {
15
15
  return provider === 'claude'
16
16
  ? (0, paths_1.getClaudeSettingsPath)(projectRoot)
@@ -35,7 +35,7 @@ function uninstallFromProvider(projectRoot, provider) {
35
35
  return;
36
36
  }
37
37
  const before = settings.hooks.PreToolUse.length;
38
- settings.hooks.PreToolUse = settings.hooks.PreToolUse.filter((entry) => !entry.hooks?.some((h) => h.command?.includes(CREWX_HOOK_COMMAND_MARKER)));
38
+ settings.hooks.PreToolUse = settings.hooks.PreToolUse.filter((entry) => !entry.hooks?.some((h) => command_marker_1.CREWX_HOOK_COMMAND_RE.test(h.command)));
39
39
  const removed = settings.hooks.PreToolUse.length < before;
40
40
  if (settings.hooks.PreToolUse.length === 0) {
41
41
  delete settings.hooks.PreToolUse;
@@ -6,16 +6,48 @@
6
6
  * crewx init Create crewx.yaml in current directory
7
7
  * crewx init --force Overwrite existing configuration
8
8
  */
9
+ import { type ProviderAvailabilityDeps } from '@crewx/sdk';
10
+ export type InitProvider = 'cli/claude' | 'cli/codex';
11
+ type InitAgent = {
12
+ id: 'planner' | 'developer' | 'reviewer';
13
+ name: string;
14
+ role: string;
15
+ team: string;
16
+ provider: InitProvider;
17
+ working_directory: string;
18
+ description: string;
19
+ options: {
20
+ query: {
21
+ mode: string;
22
+ effort: string;
23
+ };
24
+ execute: {
25
+ mode: string;
26
+ effort: string;
27
+ };
28
+ };
29
+ inline: {
30
+ model: string;
31
+ prompt: string;
32
+ };
33
+ };
34
+ /** Build the three default agents using the selected CLI provider profile. */
35
+ export declare function buildDefaultAgents(provider: InitProvider): InitAgent[];
9
36
  export interface InitResult {
10
37
  yamlCreated: boolean;
11
38
  hookInstalled: boolean;
12
39
  errors: string[];
40
+ provider: InitProvider;
13
41
  skippedReason?: 'yaml-exists';
14
42
  workspaceId?: string;
15
43
  slug?: string;
16
44
  statusLineConfigured?: boolean;
17
45
  statusLineSkipped?: boolean;
18
46
  }
47
+ /** Parse the provider option accepted by `crewx init`. */
48
+ export declare function parseInitProvider(args: readonly string[]): InitProvider | undefined;
49
+ /** Resolve an explicit provider or detect Claude, then Codex, using PATH only. */
50
+ export declare function resolveInitProvider(provider?: InitProvider, deps?: ProviderAvailabilityDeps): InitProvider;
19
51
  /**
20
52
  * Programmatic init — creates crewx.yaml, support dirs, and optionally installs hooks.
21
53
  * Throws on unrecoverable errors (PATH_NOT_FOUND, YAML_CREATE_FAILED).
@@ -27,4 +59,6 @@ export declare function handleInit(opts: {
27
59
  path: string;
28
60
  skipHook?: boolean;
29
61
  force?: boolean;
62
+ provider?: InitProvider;
30
63
  }): Promise<InitResult>;
64
+ export {};