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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (65) hide show
  1. package/dist/bootstrap/codex-writable-roots.d.ts +13 -0
  2. package/dist/bootstrap/codex-writable-roots.js +25 -0
  3. package/dist/bootstrap/crewx-cli.js +27 -6
  4. package/dist/builtin.d.ts +1 -1
  5. package/dist/builtin.js +7 -2
  6. package/dist/commands/agent.js +1 -107
  7. package/dist/commands/cal.d.ts +15 -0
  8. package/dist/commands/cal.js +44 -0
  9. package/dist/commands/db.d.ts +3 -0
  10. package/dist/commands/db.js +533 -1
  11. package/dist/commands/detached.d.ts +95 -0
  12. package/dist/commands/detached.js +620 -0
  13. package/dist/commands/doctor.d.ts +77 -0
  14. package/dist/commands/doctor.js +475 -22
  15. package/dist/commands/emit-trailer.d.ts +25 -0
  16. package/dist/commands/emit-trailer.js +33 -0
  17. package/dist/commands/execute.d.ts +8 -4
  18. package/dist/commands/execute.js +200 -51
  19. package/dist/commands/hook/command-marker.d.ts +2 -0
  20. package/dist/commands/hook/command-marker.js +5 -0
  21. package/dist/commands/hook/install.d.ts +0 -1
  22. package/dist/commands/hook/install.js +75 -61
  23. package/dist/commands/hook/status.js +3 -3
  24. package/dist/commands/hook/uninstall.js +2 -2
  25. package/dist/commands/init.d.ts +34 -0
  26. package/dist/commands/init.js +132 -59
  27. package/dist/commands/kill.d.ts +2 -8
  28. package/dist/commands/kill.js +6 -47
  29. package/dist/commands/log.js +4 -3
  30. package/dist/commands/parse-common-flags.d.ts +5 -1
  31. package/dist/commands/parse-common-flags.js +35 -4
  32. package/dist/commands/ps.d.ts +2 -2
  33. package/dist/commands/ps.js +25 -15
  34. package/dist/commands/publish.d.ts +1 -0
  35. package/dist/commands/publish.js +303 -0
  36. package/dist/commands/query.d.ts +7 -2
  37. package/dist/commands/query.js +195 -48
  38. package/dist/commands/registry.js +4 -2
  39. package/dist/commands/resolve-prompt.js +23 -1
  40. package/dist/commands/resolve-recipient.d.ts +14 -0
  41. package/dist/commands/resolve-recipient.js +47 -0
  42. package/dist/commands/restart.d.ts +3 -1
  43. package/dist/commands/restart.js +14 -4
  44. package/dist/commands/result.d.ts +8 -3
  45. package/dist/commands/result.js +149 -10
  46. package/dist/commands/shortcut.d.ts +1 -0
  47. package/dist/commands/shortcut.js +267 -0
  48. package/dist/commands/slack.js +2 -1
  49. package/dist/commands/stop.d.ts +8 -0
  50. package/dist/commands/stop.js +119 -0
  51. package/dist/index.d.ts +1 -1
  52. package/dist/index.js +4 -1
  53. package/dist/logging.d.ts +1 -1
  54. package/dist/logging.js +3 -2
  55. package/dist/main.d.ts +3 -2
  56. package/dist/main.js +99 -24
  57. package/dist/utils/env-defaults.d.ts +2 -5
  58. package/dist/utils/env-defaults.js +10 -5
  59. package/dist/utils/inherited-trace.d.ts +24 -0
  60. package/dist/utils/inherited-trace.js +40 -0
  61. package/dist/utils/product-package-root.d.ts +6 -0
  62. package/dist/utils/product-package-root.js +39 -0
  63. package/dist/utils/sdk-compat.d.ts +24 -0
  64. package/dist/utils/sdk-compat.js +120 -0
  65. package/package.json +16 -12
@@ -8,12 +8,30 @@
8
8
  * crewx doctor --config <path> Use specific config file
9
9
  */
10
10
  Object.defineProperty(exports, "__esModule", { value: true });
11
+ exports.CLI_PROVIDER_INFO = void 0;
12
+ exports.formatBytes = formatBytes;
13
+ exports.inspectNpxCache = inspectNpxCache;
14
+ exports.inspectHookCommands = inspectHookCommands;
15
+ exports.inspectCrewxExecutable = inspectCrewxExecutable;
16
+ exports.inspectCrewxShims = inspectCrewxShims;
17
+ exports.checkCliProviders = checkCliProviders;
11
18
  exports.handleDoctor = handleDoctor;
12
- const fs_1 = require("fs");
13
- const path_1 = require("path");
14
- const child_process_1 = require("child_process");
19
+ const node_child_process_1 = require("node:child_process");
20
+ const node_fs_1 = require("node:fs");
21
+ const node_os_1 = require("node:os");
22
+ const node_path_1 = require("node:path");
15
23
  const sdk_1 = require("@crewx/sdk");
16
24
  const parse_common_flags_1 = require("./parse-common-flags");
25
+ const command_marker_1 = require("./hook/command-marker");
26
+ /** CLI diagnostics keyed by the bare provider name used in PROVIDER_ORDER. */
27
+ exports.CLI_PROVIDER_INFO = {
28
+ codex: { cmd: 'codex', install: 'npm install -g @openai/codex' },
29
+ claude: { cmd: 'claude', install: 'npm install -g @anthropic-ai/claude-code' },
30
+ grok: { cmd: 'grok', install: 'curl -fsSL https://x.ai/cli/install.sh | bash' },
31
+ opencode: { cmd: 'opencode', install: 'npm install -g opencode' },
32
+ antigravity: { cmd: 'antigravity', install: 'npm install -g antigravity' },
33
+ copilot: { cmd: 'gh', install: 'brew install gh # or visit cli.github.com' },
34
+ };
17
35
  function statusIcon(status) {
18
36
  switch (status) {
19
37
  case 'success': return '✅';
@@ -26,18 +44,438 @@ function statusIcon(status) {
26
44
  */
27
45
  function isCommandAvailable(cmd) {
28
46
  try {
29
- (0, child_process_1.execSync)(`which ${cmd}`, { stdio: 'ignore' });
47
+ (0, node_child_process_1.execSync)(`which ${cmd}`, { stdio: 'ignore' });
30
48
  return true;
31
49
  }
32
50
  catch {
33
51
  return false;
34
52
  }
35
53
  }
54
+ /** Format a byte count for diagnostic output. */
55
+ function formatBytes(bytes) {
56
+ if (!Number.isFinite(bytes) || bytes <= 0)
57
+ return '0 B';
58
+ const units = ['B', 'KB', 'MB', 'GB', 'TB'];
59
+ let value = bytes;
60
+ let unitIndex = 0;
61
+ while (value >= 1024 && unitIndex < units.length - 1) {
62
+ value /= 1024;
63
+ unitIndex += 1;
64
+ }
65
+ const rounded = unitIndex === 0 || value >= 10 ? Math.round(value) : value.toFixed(1);
66
+ return `${rounded} ${units[unitIndex]}`;
67
+ }
68
+ function directorySize(path) {
69
+ try {
70
+ const stats = (0, node_fs_1.lstatSync)(path);
71
+ if (!stats.isDirectory())
72
+ return stats.size;
73
+ return (0, node_fs_1.readdirSync)(path, { withFileTypes: true }).reduce((total, entry) => {
74
+ return total + directorySize((0, node_path_1.join)(path, entry.name));
75
+ }, 0);
76
+ }
77
+ catch {
78
+ return 0;
79
+ }
80
+ }
81
+ function immediateDirectories(root) {
82
+ try {
83
+ return (0, node_fs_1.readdirSync)(root, { withFileTypes: true })
84
+ .filter((entry) => entry.isDirectory())
85
+ .map((entry) => (0, node_path_1.join)(root, entry.name))
86
+ .sort();
87
+ }
88
+ catch {
89
+ return [];
90
+ }
91
+ }
92
+ function readJsonObject(filePath) {
93
+ try {
94
+ const parsed = JSON.parse((0, node_fs_1.readFileSync)(filePath, 'utf8'));
95
+ if (typeof parsed !== 'object' || parsed === null || Array.isArray(parsed))
96
+ return undefined;
97
+ return parsed;
98
+ }
99
+ catch {
100
+ return undefined;
101
+ }
102
+ }
103
+ function getNpxCacheRoot(env = process.env) {
104
+ const configuredCache = env.NPM_CONFIG_CACHE ?? env.npm_config_cache;
105
+ const npmCache = configuredCache?.trim() || (0, node_path_1.join)((0, node_os_1.homedir)(), '.npm');
106
+ const resolvedCache = (0, node_path_1.resolve)(npmCache);
107
+ return (0, node_path_1.basename)(resolvedCache) === '_npx'
108
+ ? resolvedCache
109
+ : (0, node_path_1.join)(resolvedCache, '_npx');
110
+ }
111
+ const DEPENDENCY_SECTIONS = [
112
+ 'dependencies',
113
+ 'devDependencies',
114
+ 'optionalDependencies',
115
+ 'peerDependencies',
116
+ ];
117
+ function staleFileDependencies(cacheDirectory) {
118
+ const packageJson = readJsonObject((0, node_path_1.join)(cacheDirectory, 'package.json'));
119
+ if (!packageJson)
120
+ return [];
121
+ const missing = [];
122
+ for (const section of DEPENDENCY_SECTIONS) {
123
+ const dependencies = packageJson[section];
124
+ if (typeof dependencies !== 'object' || dependencies === null || Array.isArray(dependencies))
125
+ continue;
126
+ for (const [name, specifier] of Object.entries(dependencies)) {
127
+ if (typeof specifier !== 'string' || !specifier.startsWith('file:'))
128
+ continue;
129
+ const target = (0, node_path_1.resolve)(cacheDirectory, specifier.slice('file:'.length));
130
+ if (!(0, node_fs_1.existsSync)(target)) {
131
+ missing.push(`${section}.${name}=${specifier} (missing ${target})`);
132
+ }
133
+ }
134
+ }
135
+ return missing;
136
+ }
137
+ function addArtifact(artifacts, directory, details) {
138
+ const existing = artifacts.get(directory);
139
+ if (existing) {
140
+ existing.details.push(...details);
141
+ return existing;
142
+ }
143
+ const artifact = {
144
+ directory,
145
+ sizeBytes: directorySize(directory),
146
+ details: [...details],
147
+ };
148
+ artifacts.set(directory, artifact);
149
+ return artifact;
150
+ }
151
+ /** Inspect npm's persistent `_npx` cache without modifying it. */
152
+ function inspectNpxCache(cacheRoot = getNpxCacheRoot()) {
153
+ const resolvedRoot = (0, node_path_1.resolve)(cacheRoot);
154
+ const zeroByteLockfileDirectories = [];
155
+ const staleFileCacheDirectories = [];
156
+ const cleanable = new Map();
157
+ for (const directory of immediateDirectories(resolvedRoot)) {
158
+ const lockfile = (0, node_path_1.join)(directory, 'package-lock.json');
159
+ try {
160
+ const lockfileStats = (0, node_fs_1.statSync)(lockfile);
161
+ if (lockfileStats.isFile() && lockfileStats.size === 0) {
162
+ const entry = addArtifact(cleanable, directory, [`0-byte lockfile: ${lockfile}`]);
163
+ zeroByteLockfileDirectories.push(entry);
164
+ }
165
+ }
166
+ catch {
167
+ // A cache entry can disappear while doctor is inspecting it.
168
+ }
169
+ const missing = staleFileDependencies(directory);
170
+ if (missing.length > 0) {
171
+ const entry = addArtifact(cleanable, directory, missing);
172
+ staleFileCacheDirectories.push(entry);
173
+ }
174
+ }
175
+ return {
176
+ cacheRoot: resolvedRoot,
177
+ zeroByteLockfileDirectories,
178
+ staleFileCacheDirectories,
179
+ cleanableDirectories: [...cleanable.values()],
180
+ };
181
+ }
182
+ function asRecord(value) {
183
+ if (typeof value !== 'object' || value === null || Array.isArray(value))
184
+ return undefined;
185
+ return value;
186
+ }
187
+ function hookCommandsFromSettings(settings) {
188
+ const hookContainer = asRecord(settings.hooks) ?? settings;
189
+ const preToolUse = hookContainer.PreToolUse;
190
+ if (!Array.isArray(preToolUse))
191
+ return [];
192
+ const commands = [];
193
+ for (const entry of preToolUse) {
194
+ const entryRecord = asRecord(entry);
195
+ const hooks = entryRecord?.hooks;
196
+ if (!Array.isArray(hooks))
197
+ continue;
198
+ for (const hook of hooks) {
199
+ const command = asRecord(hook)?.command;
200
+ if (typeof command === 'string' && command_marker_1.CREWX_HOOK_COMMAND_RE.test(command)) {
201
+ commands.push(command);
202
+ }
203
+ }
204
+ }
205
+ return commands;
206
+ }
207
+ function firstShellToken(command) {
208
+ const value = command.trim();
209
+ if (!value)
210
+ return '';
211
+ if (value.startsWith("'") || value.startsWith('"')) {
212
+ const quote = value[0];
213
+ const closing = value.indexOf(quote, 1);
214
+ return closing === -1 ? value.slice(1) : value.slice(1, closing);
215
+ }
216
+ return value.split(/\s+/, 1)[0];
217
+ }
218
+ function isAbsoluteExecutableToken(token) {
219
+ return (0, node_path_1.isAbsolute)(token) || /^[A-Za-z]:[\\/]/.test(token) || token.startsWith('\\\\');
220
+ }
221
+ function hookCommandIssue(command) {
222
+ const executable = firstShellToken(command);
223
+ if (/^(?:npx|pnpm|yarn)$/i.test(executable))
224
+ return 'launcher';
225
+ if (!isAbsoluteExecutableToken(executable))
226
+ return 'non-absolute';
227
+ return undefined;
228
+ }
229
+ /** Inspect installed CrewX hooks without rewriting provider settings. */
230
+ function inspectHookCommands(projectRoot) {
231
+ const candidates = [
232
+ { provider: 'claude', settingsPath: (0, node_path_1.join)((0, node_path_1.resolve)(projectRoot), '.claude', 'settings.json') },
233
+ { provider: 'codex', settingsPath: (0, node_path_1.join)((0, node_path_1.resolve)(projectRoot), '.codex', 'hooks.json') },
234
+ ];
235
+ const inspectedFiles = [];
236
+ const issues = [];
237
+ for (const candidate of candidates) {
238
+ if (!(0, node_fs_1.existsSync)(candidate.settingsPath))
239
+ continue;
240
+ inspectedFiles.push(candidate.settingsPath);
241
+ const settings = readJsonObject(candidate.settingsPath);
242
+ if (!settings)
243
+ continue;
244
+ for (const command of hookCommandsFromSettings(settings)) {
245
+ const reason = hookCommandIssue(command);
246
+ if (reason) {
247
+ issues.push({
248
+ provider: candidate.provider,
249
+ settingsPath: candidate.settingsPath,
250
+ command,
251
+ reason,
252
+ });
253
+ }
254
+ }
255
+ }
256
+ return { inspectedFiles, issues };
257
+ }
258
+ function configuredBinResolution(configPath, fallback = 'global_first') {
259
+ try {
260
+ const config = (0, sdk_1.loadYamlFile)(configPath);
261
+ const value = config.settings?.bin_resolution;
262
+ return value === 'local_first' || value === 'global_first' ? value : fallback;
263
+ }
264
+ catch {
265
+ return fallback;
266
+ }
267
+ }
268
+ function readWindowsCrewxPathCandidates(cwd, env, platform) {
269
+ if (platform !== 'win32')
270
+ return [];
271
+ try {
272
+ (0, sdk_1.primeWindowsConsoleCodePage)();
273
+ const output = (0, node_child_process_1.execFileSync)('where.exe', ['crewx'], {
274
+ cwd,
275
+ env,
276
+ encoding: null,
277
+ stdio: ['ignore', 'pipe', 'ignore'],
278
+ });
279
+ return (0, sdk_1.decodeConsoleBuffer)(output, { platform })
280
+ .split(/\r?\n/)
281
+ .map((line) => line.trim())
282
+ .filter((line) => line.length > 0);
283
+ }
284
+ catch {
285
+ return [];
286
+ }
287
+ }
288
+ /** Inspect the SDK-selected CrewX executable without printing environment values. */
289
+ function inspectCrewxExecutable(options = {}) {
290
+ const platform = options.platform ?? process.platform;
291
+ const cwd = (0, node_path_1.resolve)(options.cwd ?? process.cwd());
292
+ const env = options.env ?? process.env;
293
+ const configPath = options.configPath ?? (0, node_path_1.join)(cwd, 'crewx.yaml');
294
+ const resolution = options.resolution ?? (0, sdk_1.resolveCrewxExecutable)({ env, cwd, platform });
295
+ const pathCandidates = options.pathCandidates
296
+ ? [...options.pathCandidates]
297
+ : readWindowsCrewxPathCandidates(cwd, env, platform);
298
+ const requiredEnvKeys = resolution.ok
299
+ ? Object.keys(resolution.requiredEnv ?? {})
300
+ .filter((key) => key === 'ELECTRON_RUN_AS_NODE')
301
+ : [];
302
+ return {
303
+ platform,
304
+ pathCandidates,
305
+ ...(resolution.ok
306
+ ? {
307
+ stage: resolution.stage,
308
+ argv: [...resolution.argv],
309
+ adoptedPath: resolution.argv[0],
310
+ }
311
+ : { resolutionError: resolution.reason }),
312
+ electron: requiredEnvKeys.includes('ELECTRON_RUN_AS_NODE'),
313
+ requiredEnvKeys,
314
+ binResolution: options.binResolution ?? configuredBinResolution(configPath),
315
+ };
316
+ }
317
+ function checkCrewxExecutable(report) {
318
+ const details = [
319
+ `platform: ${report.platform}`,
320
+ `stage: ${report.stage ?? '(unresolved)'}`,
321
+ `argv: ${report.argv ? (0, sdk_1.formatCrewxExecutableArgv)(report.argv) : '(unresolved)'}`,
322
+ `adopted path: ${report.adoptedPath ?? '(none)'}`,
323
+ `Electron runtime: ${report.electron ? 'yes' : 'no'}`,
324
+ `required environment keys: ${report.requiredEnvKeys.length > 0 ? report.requiredEnvKeys.join(', ') : '(none)'}`,
325
+ `bin_resolution setting: ${report.binResolution}`,
326
+ ...(report.platform === 'win32'
327
+ ? [`where.exe crewx candidates: ${report.pathCandidates.length > 0 ? report.pathCandidates.join(', ') : '(none)'}`]
328
+ : []),
329
+ ];
330
+ if (report.resolutionError) {
331
+ return {
332
+ name: 'CrewX Executable',
333
+ status: 'warning',
334
+ message: 'Could not resolve the CrewX executable',
335
+ details: `${details.join('\n')}\nResolution failed; no environment values were printed.`,
336
+ };
337
+ }
338
+ return {
339
+ name: 'CrewX Executable',
340
+ status: 'success',
341
+ message: 'SDK child executable contract is available',
342
+ details: details.join('\n'),
343
+ };
344
+ }
345
+ /** Inspect orphaned CrewX PATH shim directories without modifying them. */
346
+ function inspectCrewxShims(crewxHome = (0, sdk_1.getCrewxHome)(), resolvedFingerprint = (() => {
347
+ const resolution = (0, sdk_1.resolveCrewxExecutable)();
348
+ return resolution.ok ? (0, sdk_1.crewxCliShimFingerprint)(resolution) : undefined;
349
+ })()) {
350
+ const shimRoot = (0, node_path_1.join)((0, node_path_1.resolve)(crewxHome), 'bin-shim');
351
+ const staleDirectories = [];
352
+ if (resolvedFingerprint) {
353
+ for (const directory of immediateDirectories(shimRoot)) {
354
+ const fingerprint = directory.slice(shimRoot.length + 1);
355
+ if (fingerprint === resolvedFingerprint)
356
+ continue;
357
+ staleDirectories.push({
358
+ fingerprint,
359
+ directory,
360
+ sizeBytes: directorySize(directory),
361
+ details: [`not current fingerprint (${resolvedFingerprint})`],
362
+ });
363
+ }
364
+ }
365
+ return { shimRoot, currentFingerprint: resolvedFingerprint, staleDirectories };
366
+ }
367
+ function checkNpxCache(report) {
368
+ const corrupt = report.zeroByteLockfileDirectories.length;
369
+ const stale = report.staleFileCacheDirectories.length;
370
+ const cleanable = report.cleanableDirectories.length;
371
+ if (corrupt === 0 && stale === 0) {
372
+ return {
373
+ name: 'npx Cache',
374
+ status: 'success',
375
+ message: `No stale entries found in ${report.cacheRoot}`,
376
+ };
377
+ }
378
+ const details = [];
379
+ if (corrupt > 0) {
380
+ details.push(`0-byte package-lock.json: ${report.zeroByteLockfileDirectories.map((entry) => entry.directory).join(', ')}`);
381
+ }
382
+ if (stale > 0) {
383
+ details.push(`stale file: cache: ${report.staleFileCacheDirectories.map((entry) => entry.directory).join(', ')}`);
384
+ }
385
+ details.push('Run `crewx doctor --clean-npx-cache` to remove these cache directories explicitly.');
386
+ return {
387
+ name: 'npx Cache',
388
+ status: 'warning',
389
+ message: `Found ${cleanable} problematic npx cache director${cleanable === 1 ? 'y' : 'ies'}`,
390
+ details: details.join('\n'),
391
+ };
392
+ }
393
+ function checkHookCommands(report) {
394
+ if (report.issues.length === 0) {
395
+ return {
396
+ name: 'CrewX Hooks',
397
+ status: 'success',
398
+ message: report.inspectedFiles.length > 0
399
+ ? 'Installed CrewX hook commands use absolute entrypoints'
400
+ : 'No installed CrewX hooks found',
401
+ };
402
+ }
403
+ const details = report.issues
404
+ .map((issue) => `${issue.settingsPath}: ${issue.command} (${issue.reason})`)
405
+ .join('\n');
406
+ return {
407
+ name: 'CrewX Hooks',
408
+ status: 'warning',
409
+ message: `Found ${report.issues.length} hook command(s) that need an absolute CrewX entrypoint`,
410
+ details: `${details}\nRun \`crewx hook install\` to refresh the hook after reviewing the change.`,
411
+ };
412
+ }
413
+ function checkCrewxShims(report) {
414
+ if (!report.currentFingerprint) {
415
+ return {
416
+ name: 'CrewX PATH Shims',
417
+ status: 'warning',
418
+ message: 'Could not resolve the current CrewX executable; skipped orphan shim cleanup',
419
+ details: `Shim root: ${report.shimRoot}`,
420
+ };
421
+ }
422
+ if (report.staleDirectories.length === 0) {
423
+ return {
424
+ name: 'CrewX PATH Shims',
425
+ status: 'success',
426
+ message: 'No orphaned CrewX PATH shims found',
427
+ };
428
+ }
429
+ return {
430
+ name: 'CrewX PATH Shims',
431
+ status: 'warning',
432
+ message: `Found ${report.staleDirectories.length} orphaned CrewX PATH shim director${report.staleDirectories.length === 1 ? 'y' : 'ies'}`,
433
+ details: `${report.staleDirectories.map((entry) => entry.directory).join(', ')}\nRun \`crewx doctor --clean-npx-cache\` to remove stale shims explicitly.`,
434
+ };
435
+ }
436
+ function safelyRemoveDirectories(directories, parent) {
437
+ const expectedParent = (0, node_path_1.resolve)(parent);
438
+ let removed = 0;
439
+ const failed = [];
440
+ for (const entry of directories) {
441
+ const directory = (0, node_path_1.resolve)(entry.directory);
442
+ if ((0, node_path_1.dirname)(directory) !== expectedParent) {
443
+ failed.push(`${directory} (unexpected parent)`);
444
+ continue;
445
+ }
446
+ try {
447
+ (0, node_fs_1.rmSync)(directory, { recursive: true, force: true });
448
+ if (!(0, node_fs_1.existsSync)(directory))
449
+ removed += 1;
450
+ else
451
+ failed.push(directory);
452
+ }
453
+ catch {
454
+ failed.push(directory);
455
+ }
456
+ }
457
+ return { removed, failed };
458
+ }
459
+ function cleanDoctorArtifacts(npxCache, shims) {
460
+ const npxTargets = npxCache.cleanableDirectories;
461
+ const shimTargets = shims.staleDirectories;
462
+ const allTargets = [...npxTargets, ...shimTargets];
463
+ const totalBytes = allTargets.reduce((sum, entry) => sum + entry.sizeBytes, 0);
464
+ console.log(`⚠️ --clean-npx-cache: before cleanup, ${npxTargets.length} npx cache director${npxTargets.length === 1 ? 'y' : 'ies'} ` +
465
+ `and ${shimTargets.length} orphan shim director${shimTargets.length === 1 ? 'y' : 'ies'} (${formatBytes(totalBytes)}).`);
466
+ const npxResult = safelyRemoveDirectories(npxTargets, npxCache.cacheRoot);
467
+ const shimResult = safelyRemoveDirectories(shimTargets, shims.shimRoot);
468
+ const failures = [...npxResult.failed, ...shimResult.failed];
469
+ console.log(` Removed ${npxResult.removed + shimResult.removed} director${npxResult.removed + shimResult.removed === 1 ? 'y' : 'ies'}.`);
470
+ if (failures.length > 0) {
471
+ console.log(` Could not remove: ${failures.join(', ')}`);
472
+ }
473
+ }
36
474
  /**
37
475
  * Check crewx.yaml configuration file.
38
476
  */
39
477
  function checkConfig(configPath) {
40
- if (!(0, fs_1.existsSync)(configPath)) {
478
+ if (!(0, node_fs_1.existsSync)(configPath)) {
41
479
  return {
42
480
  name: 'Configuration File',
43
481
  status: 'error',
@@ -83,8 +521,8 @@ function checkConfig(configPath) {
83
521
  * Check .crewx/logs directory.
84
522
  */
85
523
  function checkLogsDir() {
86
- const logsDir = (0, path_1.join)(process.cwd(), '.crewx', 'logs');
87
- if (!(0, fs_1.existsSync)(logsDir)) {
524
+ const logsDir = (0, node_path_1.join)(process.cwd(), '.crewx', 'logs');
525
+ if (!(0, node_fs_1.existsSync)(logsDir)) {
88
526
  return {
89
527
  name: 'Logs Directory',
90
528
  status: 'warning',
@@ -99,20 +537,19 @@ function checkLogsDir() {
99
537
  };
100
538
  }
101
539
  /**
102
- * Check CLI provider availability.
103
- * Providers are checked in PROVIDER_ORDER: codex → claude → opencode → antigravity → copilot.
540
+ * Check CLI provider availability in the canonical PROVIDER_ORDER.
104
541
  */
105
542
  function checkCliProviders() {
106
- // Map each provider (in PROVIDER_ORDER) to its CLI command and install hint.
107
- const providerInfo = {
108
- codex: { cmd: 'codex', install: 'npm install -g @openai/codex' },
109
- claude: { cmd: 'claude', install: 'npm install -g @anthropic-ai/claude-code' },
110
- opencode: { cmd: 'opencode', install: 'npm install -g opencode' },
111
- antigravity: { cmd: 'antigravity', install: 'npm install -g antigravity' },
112
- copilot: { cmd: 'gh', install: 'brew install gh # or visit cli.github.com' },
113
- };
114
543
  return sdk_1.PROVIDER_ORDER.map(provider => {
115
- const info = providerInfo[provider];
544
+ const info = exports.CLI_PROVIDER_INFO[provider];
545
+ if (!info) {
546
+ return {
547
+ name: `${provider.toUpperCase()} CLI`,
548
+ status: 'error',
549
+ message: 'Diagnostic metadata is missing',
550
+ details: `Add ${provider} to CLI_PROVIDER_INFO.`,
551
+ };
552
+ }
116
553
  const displayName = provider === 'copilot' ? 'copilot (gh)' : provider;
117
554
  const available = isCommandAvailable(info.cmd);
118
555
  return {
@@ -138,7 +575,7 @@ function checkEnvVars() {
138
575
  return {
139
576
  name: 'CREWX_CLI',
140
577
  status: 'warning',
141
- message: 'Not set — defaulting to "npx crewx"',
578
+ message: 'Not set — defaulting to "crewx"',
142
579
  details: 'This is auto-set at CLI startup; no action needed.',
143
580
  };
144
581
  }
@@ -161,14 +598,30 @@ function assessHealth(diagnostics) {
161
598
  * Handle `crewx doctor` command.
162
599
  */
163
600
  async function handleDoctor(args) {
164
- const { config } = (0, parse_common_flags_1.parseCommonFlags)(args);
165
- const configPath = config ?? process.env.CREWX_CONFIG ?? (0, path_1.join)(process.cwd(), 'crewx.yaml');
601
+ const cleanNpxCache = args.includes('--clean-npx-cache');
602
+ const commonArgs = args.filter((arg) => arg !== '--clean-npx-cache');
603
+ const { config } = (0, parse_common_flags_1.parseCommonFlags)(commonArgs);
604
+ const configPath = config ?? process.env.CREWX_CONFIG ?? (0, node_path_1.join)(process.cwd(), 'crewx.yaml');
166
605
  console.log('🩺 Starting CrewX system diagnosis...\n');
606
+ let npxCache = inspectNpxCache();
607
+ let shims = inspectCrewxShims();
608
+ const projectRoot = (0, node_path_1.dirname)((0, node_path_1.resolve)(configPath));
609
+ const hooks = inspectHookCommands(projectRoot);
610
+ const executable = inspectCrewxExecutable({ configPath, cwd: process.cwd() });
611
+ if (cleanNpxCache) {
612
+ cleanDoctorArtifacts(npxCache, shims);
613
+ npxCache = inspectNpxCache();
614
+ shims = inspectCrewxShims();
615
+ }
167
616
  const diagnostics = [
168
617
  checkConfig(configPath),
169
618
  checkLogsDir(),
170
619
  checkEnvVars(),
620
+ checkCrewxExecutable(executable),
171
621
  ...checkCliProviders(),
622
+ checkNpxCache(npxCache),
623
+ checkHookCommands(hooks),
624
+ checkCrewxShims(shims),
172
625
  ];
173
626
  // Print diagnostics
174
627
  diagnostics.forEach(d => {
@@ -185,7 +638,7 @@ async function handleDoctor(args) {
185
638
  console.log(`${healthIcon} Overall: ${health.message}`);
186
639
  if (health.status === 'success') {
187
640
  console.log('\nNext steps:');
188
- console.log(' crewx query "@claude hello"');
641
+ console.log(' crewx q --to crewx "hello"');
189
642
  }
190
643
  else {
191
644
  console.log('\nAddress the issues above, then run `crewx doctor` again.');
@@ -0,0 +1,25 @@
1
+ import type { TraceContext } from '@crewx/sdk';
2
+ /** Mutable per-process guard shared by a handler and main()'s error boundary. */
3
+ export interface DelegationEmitState {
4
+ emitted: boolean;
5
+ }
6
+ /** The result fields needed to produce a delegation control line. */
7
+ export interface DelegationEmitOptions {
8
+ command?: string;
9
+ trace?: TraceContext;
10
+ taskId?: string;
11
+ agentId?: string;
12
+ isError: boolean;
13
+ state?: DelegationEmitState;
14
+ }
15
+ export declare function createDelegationEmitState(): DelegationEmitState;
16
+ /**
17
+ * Return true only for an inherited child invocation of q/query/x/execute.
18
+ * Root CLI calls and unrelated built-in commands must remain byte-compatible.
19
+ */
20
+ export declare function shouldEmitDelegationResult(command: string | undefined, trace?: TraceContext | undefined): boolean;
21
+ /**
22
+ * Write one machine-readable result line to stdout when this is a delegated call.
23
+ * The shared SDK formatter owns the wire syntax and validation rules.
24
+ */
25
+ export declare function emitDelegationResult(options: DelegationEmitOptions): boolean;
@@ -0,0 +1,33 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.createDelegationEmitState = createDelegationEmitState;
4
+ exports.shouldEmitDelegationResult = shouldEmitDelegationResult;
5
+ exports.emitDelegationResult = emitDelegationResult;
6
+ const sdk_1 = require("@crewx/sdk");
7
+ const inherited_trace_1 = require("../utils/inherited-trace");
8
+ const DELEGATION_COMMANDS = new Set(['q', 'query', 'x', 'execute']);
9
+ function createDelegationEmitState() {
10
+ return { emitted: false };
11
+ }
12
+ /**
13
+ * Return true only for an inherited child invocation of q/query/x/execute.
14
+ * Root CLI calls and unrelated built-in commands must remain byte-compatible.
15
+ */
16
+ function shouldEmitDelegationResult(command, trace = (0, inherited_trace_1.readInheritedTrace)()) {
17
+ return DELEGATION_COMMANDS.has(command ?? '') && Boolean(trace?.parentTaskId);
18
+ }
19
+ /**
20
+ * Write one machine-readable result line to stdout when this is a delegated call.
21
+ * The shared SDK formatter owns the wire syntax and validation rules.
22
+ */
23
+ function emitDelegationResult(options) {
24
+ const { command, trace, state, taskId, agentId, isError } = options;
25
+ if (state?.emitted || !shouldEmitDelegationResult(command, trace))
26
+ return false;
27
+ const line = (0, sdk_1.formatDelegationResult)({ taskId, agentId, isError });
28
+ // Mark before writing so an EPIPE/error boundary cannot accidentally retry.
29
+ if (state)
30
+ state.emitted = true;
31
+ process.stdout.write(`${line}\n`);
32
+ return true;
33
+ }
@@ -3,25 +3,29 @@
3
3
  * Parses args, loads config via Crewx SDK, and executes the task.
4
4
  *
5
5
  * Flags:
6
+ * --to <agent> Explicit recipient (the positional @agent form is shorthand)
6
7
  * --thread <name> Conversation thread name
7
8
  * --provider <cli/xxx> Provider override
9
+ * --model <name> Model override (e.g. claude-sonnet-5)
8
10
  * --metadata <json> Extra metadata JSON (double-quoted object). Propagated to events/hooks/tracing.
9
- * e.g. --metadata='{"workflow_id":"wf-1"}'
10
11
  * --verbose Debug output mode (default: raw agent response only)
11
12
  * --config/-c <path> Config file path override
12
13
  * --output-format <fmt> Output format (json|text|stream-json)
13
14
  * --effort <level> Model effort (high|medium|low)
14
- * -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
15
18
  *
16
19
  * Stdin support:
17
20
  * Pipe or redirect content into crewx x to supply the task body via stdin.
18
- * e.g. cat task.md | crewx x "@agent label"
19
21
  * Stdin is ignored when running in an interactive TTY.
20
22
  */
23
+ import { type DelegationEmitState } from './emit-trailer';
24
+ export { runDetachedMiddle } from './detached';
21
25
  /**
22
26
  * Handle `crewx execute <agentRef> <message>` command.
23
27
  *
24
28
  * Default output: raw agent response only (stdout).
25
29
  * --verbose: debug info written to stderr, response to stdout.
26
30
  */
27
- export declare function handleExecute(args: string[]): Promise<void>;
31
+ export declare function handleExecute(args: string[], command?: string, emitState?: DelegationEmitState): Promise<void>;