@crewx/cli 0.9.0-rc.9 → 0.9.0-rc.91

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 (45) 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 +2 -1
  4. package/dist/builtin.js +1 -0
  5. package/dist/commands/agent.js +0 -58
  6. package/dist/commands/db.d.ts +1 -0
  7. package/dist/commands/db.js +191 -1
  8. package/dist/commands/doctor.d.ts +53 -0
  9. package/dist/commands/doctor.js +391 -21
  10. package/dist/commands/emit-trailer.d.ts +25 -0
  11. package/dist/commands/emit-trailer.js +33 -0
  12. package/dist/commands/execute.d.ts +6 -1
  13. package/dist/commands/execute.js +162 -11
  14. package/dist/commands/hook/command-marker.d.ts +2 -0
  15. package/dist/commands/hook/command-marker.js +5 -0
  16. package/dist/commands/hook/install.d.ts +0 -1
  17. package/dist/commands/hook/install.js +60 -63
  18. package/dist/commands/hook/status.js +3 -3
  19. package/dist/commands/hook/uninstall.js +2 -2
  20. package/dist/commands/init.js +22 -1
  21. package/dist/commands/log.js +4 -3
  22. package/dist/commands/parse-common-flags.d.ts +5 -1
  23. package/dist/commands/parse-common-flags.js +6 -2
  24. package/dist/commands/ps.js +7 -6
  25. package/dist/commands/publish.d.ts +1 -0
  26. package/dist/commands/publish.js +290 -0
  27. package/dist/commands/query.d.ts +3 -1
  28. package/dist/commands/query.js +78 -11
  29. package/dist/commands/registry.js +3 -1
  30. package/dist/commands/result.d.ts +7 -3
  31. package/dist/commands/result.js +41 -6
  32. package/dist/commands/shortcut.d.ts +1 -0
  33. package/dist/commands/shortcut.js +267 -0
  34. package/dist/commands/slack.js +2 -1
  35. package/dist/commands/write-output.d.ts +3 -0
  36. package/dist/commands/write-output.js +24 -0
  37. package/dist/logging.d.ts +1 -1
  38. package/dist/logging.js +3 -2
  39. package/dist/main.d.ts +3 -2
  40. package/dist/main.js +56 -11
  41. package/dist/utils/env-defaults.d.ts +2 -5
  42. package/dist/utils/env-defaults.js +10 -5
  43. package/dist/utils/sdk-compat.d.ts +24 -0
  44. package/dist/utils/sdk-compat.js +120 -0
  45. package/package.json +13 -11
@@ -6,6 +6,7 @@
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
11
  * e.g. --metadata='{"workflow_id":"wf-1"}'
11
12
  * --verbose Debug output mode (default: raw agent response only)
@@ -13,6 +14,9 @@
13
14
  * --output-format <fmt> Output format (json|text|stream-json)
14
15
  * --effort <level> Model effort (high|medium|low)
15
16
  * -f/--prompt-file <path> Read task body from file (bypasses cmd.exe argv truncation)
17
+ * --detach Re-spawn as a detached runner; print task-id and exit 0 immediately.
18
+ * Ignored when CREWX_TRACE_ID is already set (recursive-spawn guard) or
19
+ * on win32 (unsupported — exits with an error).
16
20
  *
17
21
  * Stdin support:
18
22
  * Pipe or redirect content into crewx x to supply the task body via stdin.
@@ -21,25 +25,123 @@
21
25
  */
22
26
  Object.defineProperty(exports, "__esModule", { value: true });
23
27
  exports.handleExecute = handleExecute;
28
+ const child_process_1 = require("child_process");
29
+ const fs_1 = require("fs");
30
+ const path_1 = require("path");
31
+ const os_1 = require("os");
24
32
  const sdk_1 = require("@crewx/sdk");
25
33
  const parse_agent_message_1 = require("./parse-agent-message");
26
34
  const parse_common_flags_1 = require("./parse-common-flags");
27
35
  const resolve_prompt_1 = require("./resolve-prompt");
28
36
  const crewx_cli_1 = require("../bootstrap/crewx-cli");
29
37
  const inherited_trace_1 = require("../utils/inherited-trace");
38
+ const write_output_1 = require("./write-output");
39
+ const emit_trailer_1 = require("./emit-trailer");
40
+ /**
41
+ * Split `--detach` out of argv, respecting the `--` literal-args sentinel
42
+ * (a `--detach` appearing after `--` is message text, not the flag).
43
+ */
44
+ function extractDetachFlag(args) {
45
+ const rest = [];
46
+ let detach = false;
47
+ let escapeMode = false;
48
+ for (const arg of args) {
49
+ if (!escapeMode && arg === '--') {
50
+ escapeMode = true;
51
+ rest.push(arg);
52
+ continue;
53
+ }
54
+ if (!escapeMode && arg === '--detach') {
55
+ detach = true;
56
+ continue;
57
+ }
58
+ rest.push(arg);
59
+ }
60
+ return { detach, rest };
61
+ }
62
+ /**
63
+ * Double-detach: re-spawn this same CLI entry (minus --detach) as a detached
64
+ * process so it survives the parent's exit. The task-id is pre-generated here
65
+ * and injected as CREWX_TRACE_ID so the runner's own task row is created under
66
+ * this id (see handleExecute's `selfTaskId` derivation below) — it doubles as
67
+ * both this task's row id and the root of any further delegation it spawns.
68
+ *
69
+ * stdout contract (script-parseable): task-id only, on the first line.
70
+ * Everything else goes to stderr.
71
+ */
72
+ function runDetached(filteredArgs) {
73
+ const taskId = (0, sdk_1.generateId)('tsk');
74
+ const logDir = (0, path_1.join)((0, os_1.homedir)(), '.crewx', 'logs');
75
+ if (!(0, fs_1.existsSync)(logDir))
76
+ (0, fs_1.mkdirSync)(logDir, { recursive: true });
77
+ const logPath = (0, path_1.join)(logDir, `${taskId}.log`);
78
+ const logFd = (0, fs_1.openSync)(logPath, 'a');
79
+ const entry = process.argv[1];
80
+ const child = (0, child_process_1.spawn)(process.execPath, [entry, 'x', ...filteredArgs], {
81
+ detached: true,
82
+ stdio: ['ignore', logFd, logFd],
83
+ env: { ...process.env, CREWX_TRACE_ID: taskId },
84
+ });
85
+ (0, fs_1.closeSync)(logFd);
86
+ child.on('error', (err) => {
87
+ process.stderr.write(`Failed to spawn detached runner: ${err.message}\n`);
88
+ });
89
+ child.unref();
90
+ console.log(taskId);
91
+ process.stderr.write(`Detached task ${taskId} started (log: ${logPath}).\n`);
92
+ process.stderr.write(`Use \`crewx result ${taskId} --wait=N\` to wait for completion.\n`);
93
+ process.exit(0);
94
+ }
30
95
  /**
31
96
  * Handle `crewx execute <agentRef> <message>` command.
32
97
  *
33
98
  * Default output: raw agent response only (stdout).
34
99
  * --verbose: debug info written to stderr, response to stdout.
35
100
  */
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)
101
+ async function handleExecute(args, command = 'execute', emitState = (0, emit_trailer_1.createDelegationEmitState)()) {
102
+ const { detach, rest: detachFilteredArgs } = extractDetachFlag(args);
103
+ if (detach) {
104
+ // Recursive-spawn guard: a CREWX_TRACE_ID already present means this
105
+ // process is itself running inside a traced context (either the
106
+ // respawned runner, or a delegated sub-call) — never chain a second
107
+ // detach off of it. Silently fall through to normal (synchronous) execution.
108
+ if (process.env['CREWX_TRACE_ID']) {
109
+ process.stderr.write('Note: --detach ignored (already running inside a traced context; CREWX_TRACE_ID is set).\n');
110
+ }
111
+ else if (process.platform === 'win32') {
112
+ console.error('Error: --detach is not supported on win32.');
113
+ (0, emit_trailer_1.emitDelegationResult)({ command, isError: true, state: emitState });
114
+ process.exit(1);
115
+ return;
116
+ }
117
+ else {
118
+ runDetached(detachFilteredArgs);
119
+ return;
120
+ }
121
+ }
122
+ let parsedFlags;
123
+ try {
124
+ parsedFlags = (0, parse_common_flags_1.parseCommonFlags)(detachFilteredArgs);
125
+ }
126
+ catch (err) {
127
+ (0, emit_trailer_1.emitDelegationResult)({ command, isError: true, state: emitState });
128
+ throw err;
129
+ }
130
+ const { thread, provider, model, metadata, verbose, config, outputFormat, effort, promptFile, overdrive, vars, out, rest } = parsedFlags;
131
+ const inheritedTrace = (0, inherited_trace_1.readInheritedTrace)();
132
+ let parsedAgentRef;
133
+ let message;
134
+ let finalMessage;
135
+ try {
136
+ ({ agentRef: parsedAgentRef, message } = (0, parse_agent_message_1.parseAgentMessage)(rest));
137
+ // Resolve final prompt: argv | stdin pipe | --prompt-file (or combination)
138
+ finalMessage = await (0, resolve_prompt_1.resolvePrompt)(message, promptFile);
139
+ }
140
+ catch (err) {
141
+ (0, emit_trailer_1.emitDelegationResult)({ command, trace: inheritedTrace, isError: true, state: emitState });
142
+ throw err;
143
+ }
40
144
  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
145
  if (!finalMessage) {
44
146
  console.error('Usage: crewx execute [@agent] <task> [options]');
45
147
  console.error(' crewx x [@agent] <task> [options]');
@@ -55,6 +157,7 @@ async function handleExecute(args) {
55
157
  console.error('Options:');
56
158
  console.error(' --thread <name> Conversation thread name');
57
159
  console.error(' --provider <cli/xxx> Provider override');
160
+ console.error(' --model <name> Model override (e.g. claude-sonnet-5)');
58
161
  console.error(' --metadata <json> Extra metadata (JSON object, double-quoted).');
59
162
  console.error(' Propagated to events/hooks/tracing.');
60
163
  console.error(' Invalid JSON aborts with exit code 2.');
@@ -62,16 +165,26 @@ async function handleExecute(args) {
62
165
  console.error(' --verbose Debug output mode');
63
166
  console.error(' --config/-c <path> Config file path');
64
167
  console.error(' --output-format <fmt> Output format (json|text|stream-json)');
168
+ console.error(' --out/-o <path> Save result to file (stdout suppressed)');
65
169
  console.error(' --effort <level> Model effort (high|medium|low)');
66
170
  console.error(' -f/--prompt-file <path> Read task body from file');
67
171
  console.error(' --var key=value Template variable (repeatable)');
68
172
  console.error(' --overdrive Activate overdrive (boost) profile for this request');
173
+ (0, emit_trailer_1.emitDelegationResult)({ command, trace: inheritedTrace, isError: true, state: emitState });
69
174
  process.exit(1);
175
+ return;
70
176
  }
71
177
  const configPath = config ?? process.env.CREWX_CONFIG ?? 'crewx.yaml';
72
178
  // Only show exec audit span JSON in verbose mode
73
179
  (0, sdk_1.setAuditVerbose)(verbose);
74
- const crewx = await (0, crewx_cli_1.createCliCrewx)(configPath);
180
+ let crewx;
181
+ try {
182
+ crewx = await (0, crewx_cli_1.createCliCrewx)(configPath);
183
+ }
184
+ catch (err) {
185
+ (0, emit_trailer_1.emitDelegationResult)({ command, trace: inheritedTrace, isError: true, state: emitState });
186
+ throw err;
187
+ }
75
188
  // file:// remote agent delegation is handled transparently inside Crewx.query/execute.
76
189
  if (verbose) {
77
190
  process.stderr.write(`📋 Task: ${finalMessage}\n`);
@@ -80,6 +193,8 @@ async function handleExecute(args) {
80
193
  process.stderr.write(`🔗 Thread: ${thread}\n`);
81
194
  if (provider)
82
195
  process.stderr.write(`🔌 Provider: ${provider}\n`);
196
+ if (model)
197
+ process.stderr.write(`🧠 Model: ${model}\n`);
83
198
  if (outputFormat)
84
199
  process.stderr.write(`📄 Output-format: ${outputFormat}\n`);
85
200
  if (effort)
@@ -95,22 +210,36 @@ async function handleExecute(args) {
95
210
  catch (err) {
96
211
  const msg = err instanceof Error ? err.message : String(err);
97
212
  process.stderr.write(`Error: ${msg}\n`);
213
+ (0, emit_trailer_1.emitDelegationResult)({ command, trace: inheritedTrace, isError: true, state: emitState });
98
214
  process.exit(2);
215
+ return;
99
216
  }
217
+ // A trace with a rootTraceId but no parentTaskId means the id was pre-assigned
218
+ // to *this* task itself (the detach runner's parent injects only CREWX_TRACE_ID,
219
+ // never CREWX_PARENT_TASK_ID — see execute.ts's runDetached), not inherited from
220
+ // an ancestor task in a delegation chain (which always carries both). It doubles
221
+ // as this task's own row id so `crewx result <task-id>` can find it.
222
+ const selfTaskId = inheritedTrace && !inheritedTrace.parentTaskId
223
+ ? (inheritedTrace.rootTraceId || undefined)
224
+ : undefined;
100
225
  let exitCode = 0;
226
+ let result;
101
227
  try {
102
- const result = await crewx.execute(agentRef, finalMessage, {
228
+ result = await crewx.execute(agentRef, finalMessage, {
103
229
  provider,
230
+ model,
104
231
  effort: effort || undefined,
105
232
  overdrive: overdrive || undefined,
106
233
  threadId: thread,
234
+ taskId: selfTaskId,
107
235
  metadata: Object.keys(parsedMetadata).length > 0 ? parsedMetadata : undefined,
108
236
  vars: Object.keys(vars).length > 0 ? vars : undefined,
109
- trace: (0, inherited_trace_1.readInheritedTrace)(),
237
+ trace: inheritedTrace,
110
238
  });
111
239
  if (!result.ok) {
112
240
  const errMsg = result.error?.message ?? 'Execute failed';
113
241
  console.error(errMsg);
242
+ (0, write_output_1.appendError)(out, errMsg);
114
243
  exitCode = 1;
115
244
  }
116
245
  else {
@@ -123,7 +252,7 @@ async function handleExecute(args) {
123
252
  process.stderr.write('\n📄 Response:\n');
124
253
  process.stderr.write('─'.repeat(40) + '\n');
125
254
  }
126
- console.log(result.data);
255
+ (0, write_output_1.writeResult)(out, result.data);
127
256
  if (verbose) {
128
257
  process.stderr.write('\n✅ Execute completed successfully\n');
129
258
  }
@@ -132,11 +261,33 @@ async function handleExecute(args) {
132
261
  catch (err) {
133
262
  const errMsg = err instanceof Error ? err.message : String(err);
134
263
  console.error(`Error: ${errMsg}`);
264
+ (0, write_output_1.appendError)(out, `Error: ${errMsg}`);
135
265
  exitCode = 1;
136
266
  }
137
267
  finally {
138
- await crewx.close();
268
+ try {
269
+ await crewx.close();
270
+ }
271
+ catch (err) {
272
+ (0, emit_trailer_1.emitDelegationResult)({
273
+ command,
274
+ trace: inheritedTrace,
275
+ taskId: result?.meta.taskId,
276
+ agentId: result?.meta.agentId,
277
+ isError: true,
278
+ state: emitState,
279
+ });
280
+ throw err;
281
+ }
139
282
  }
283
+ (0, emit_trailer_1.emitDelegationResult)({
284
+ command,
285
+ trace: inheritedTrace,
286
+ taskId: result?.meta.taskId,
287
+ agentId: result?.meta.agentId,
288
+ isError: exitCode !== 0 || result?.ok === false,
289
+ state: emitState,
290
+ });
140
291
  if (exitCode !== 0)
141
292
  process.exit(exitCode);
142
293
  }
@@ -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;
@@ -52,6 +52,7 @@ const git = __importStar(require("isomorphic-git"));
52
52
  const nodeFs = __importStar(require("fs"));
53
53
  const os_1 = __importDefault(require("os"));
54
54
  const repository_1 = require("@crewx/sdk/repository");
55
+ const sdk_1 = require("@crewx/sdk");
55
56
  const install_1 = require("./hook/install");
56
57
  const STATUSLINE_SCRIPT_NAME = 'claude-usage-statusline.js';
57
58
  const STATUSLINE_SCRIPT_MARKER = 'CrewX claude-usage-statusline';
@@ -84,6 +85,11 @@ const CREWX_MARKER = '# CrewX runtime';
84
85
  const CREWX_GITIGNORE = `# CrewX runtime
85
86
  .crewx/
86
87
 
88
+ # Secrets (workspace .env — see docs/manual)
89
+ .env
90
+ .env.*
91
+ !.env.example
92
+
87
93
  # Memory runtime state (regenerable from entries/)
88
94
  memory/*/.dirty-summary
89
95
  memory/*/graph.json
@@ -435,7 +441,7 @@ async function handleInit(opts) {
435
441
  }
436
442
  }
437
443
  // 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']) {
444
+ for (const dir of ['.crewx/logs', '.claude/commands', 'docs/goal', 'docs/daily', 'docs/wi', 'workflows']) {
439
445
  try {
440
446
  (0, fs_1.mkdirSync)((0, path_1.join)(target, dir), { recursive: true });
441
447
  }
@@ -456,6 +462,21 @@ async function handleInit(opts) {
456
462
  console.log(`✅ ${t.dir}/ ${action} (${t.file})`);
457
463
  }
458
464
  }
465
+ try {
466
+ const workflowTemplatesDir = (0, sdk_1.resolveWorkflowTemplatesPath)();
467
+ for (const filename of [sdk_1.WI_DEFAULT_FLOW_FILENAME, sdk_1.WI_PLAN_FLOW_FILENAME]) {
468
+ const sourcePath = (0, path_1.join)(workflowTemplatesDir, filename);
469
+ const targetPath = (0, path_1.join)(target, 'workflows', filename);
470
+ if (force || !(0, fs_1.existsSync)(targetPath)) {
471
+ const action = (0, fs_1.existsSync)(targetPath) ? 'updated' : 'created';
472
+ (0, fs_1.copyFileSync)(sourcePath, targetPath);
473
+ console.log(`✅ workflows/ ${action} (${filename})`);
474
+ }
475
+ }
476
+ }
477
+ catch (e) {
478
+ errors.push(`WORKFLOW_TEMPLATE_FAILED:${e.message}`);
479
+ }
459
480
  // Always register workspace in ~/.crewx/crewx.db (best-effort, idempotent)
460
481
  let workspaceId;
461
482
  let slug;
@@ -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: --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
@@ -14,6 +14,8 @@ export interface CommonFlags {
14
14
  thread?: string;
15
15
  /** Provider override (e.g., cli/claude). */
16
16
  provider?: string;
17
+ /** Model override (e.g., claude-sonnet-5). */
18
+ model?: string;
17
19
  /** Raw metadata JSON string. */
18
20
  metadata?: string;
19
21
  /** Enable verbose/debug output mode. */
@@ -28,6 +30,8 @@ export interface CommonFlags {
28
30
  promptFile?: string;
29
31
  /** Whether overdrive (boost) is active for this request. */
30
32
  overdrive: boolean;
33
+ /** Output file path for saving result (--out/-o). */
34
+ out?: string;
31
35
  /** Template variables from --var key=value flags. */
32
36
  vars: Record<string, string>;
33
37
  /** Remaining non-flag positional arguments. */
@@ -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: --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
@@ -63,11 +63,13 @@ exports.UnknownOptionError = UnknownOptionError;
63
63
  function parseCommonFlags(args) {
64
64
  const thread = parseFlag(args, '--thread');
65
65
  const provider = parseFlag(args, '--provider');
66
+ const model = parseFlag(args, '--model');
66
67
  const metadata = parseFlag(args, '--metadata');
67
68
  const config = parseFlag(args, '--config', '-c');
68
69
  const outputFormat = parseFlag(args, '--output-format');
69
70
  const effort = parseFlag(args, '--effort');
70
71
  const promptFile = parseFlag(args, '--prompt-file', '-f');
72
+ const out = parseFlag(args, '--out', '-o');
71
73
  const verbose = hasFlag(args, '--verbose');
72
74
  const overdrive = hasFlag(args, '--overdrive');
73
75
  // Collect consumed positions for known flags
@@ -75,11 +77,13 @@ function parseCommonFlags(args) {
75
77
  const flagPairs = [
76
78
  { names: ['--thread'] },
77
79
  { names: ['--provider'] },
80
+ { names: ['--model'] },
78
81
  { names: ['--metadata'] },
79
82
  { names: ['--config', '-c'] },
80
83
  { names: ['--output-format'] },
81
84
  { names: ['--effort'] },
82
85
  { names: ['--prompt-file', '-f'] },
86
+ { names: ['--out', '-o'] },
83
87
  ];
84
88
  // Parse --var key=value flags (multiple allowed; last value wins on duplicate keys)
85
89
  const vars = {};
@@ -153,7 +157,7 @@ function parseCommonFlags(args) {
153
157
  }
154
158
  rest.push(token);
155
159
  }
156
- return { thread, provider, metadata, verbose, config, outputFormat, effort, promptFile, overdrive, vars, rest };
160
+ return { thread, provider, model, metadata, verbose, config, outputFormat, effort, promptFile, overdrive, vars, out, rest };
157
161
  }
158
162
  /**
159
163
  * Parse metadata JSON string from --metadata flag.