@crewx/cli 0.9.0-rc.9 → 0.9.0-rc.90
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.
- package/dist/bootstrap/codex-writable-roots.d.ts +13 -0
- package/dist/bootstrap/codex-writable-roots.js +25 -0
- package/dist/bootstrap/crewx-cli.js +2 -1
- package/dist/builtin.js +1 -0
- package/dist/commands/agent.js +0 -58
- package/dist/commands/db.d.ts +1 -0
- package/dist/commands/db.js +191 -1
- package/dist/commands/doctor.d.ts +53 -0
- package/dist/commands/doctor.js +391 -21
- package/dist/commands/emit-trailer.d.ts +25 -0
- package/dist/commands/emit-trailer.js +33 -0
- package/dist/commands/execute.d.ts +6 -1
- package/dist/commands/execute.js +162 -11
- package/dist/commands/hook/command-marker.d.ts +2 -0
- package/dist/commands/hook/command-marker.js +5 -0
- package/dist/commands/hook/install.d.ts +0 -1
- package/dist/commands/hook/install.js +60 -63
- package/dist/commands/hook/status.js +3 -3
- package/dist/commands/hook/uninstall.js +2 -2
- package/dist/commands/init.js +22 -1
- package/dist/commands/log.js +4 -3
- package/dist/commands/parse-common-flags.d.ts +5 -1
- package/dist/commands/parse-common-flags.js +6 -2
- package/dist/commands/ps.js +7 -6
- package/dist/commands/publish.d.ts +1 -0
- package/dist/commands/publish.js +290 -0
- package/dist/commands/query.d.ts +3 -1
- package/dist/commands/query.js +78 -11
- package/dist/commands/registry.js +3 -1
- package/dist/commands/result.d.ts +7 -3
- package/dist/commands/result.js +41 -6
- package/dist/commands/shortcut.d.ts +1 -0
- package/dist/commands/shortcut.js +267 -0
- package/dist/commands/slack.js +2 -1
- package/dist/commands/write-output.d.ts +3 -0
- package/dist/commands/write-output.js +24 -0
- package/dist/logging.d.ts +1 -1
- package/dist/logging.js +3 -2
- package/dist/main.d.ts +3 -2
- package/dist/main.js +56 -11
- package/dist/utils/env-defaults.d.ts +2 -5
- package/dist/utils/env-defaults.js +10 -5
- package/dist/utils/sdk-compat.d.ts +24 -0
- package/dist/utils/sdk-compat.js +120 -0
- package/package.json +13 -11
package/dist/commands/doctor.js
CHANGED
|
@@ -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.inspectCrewxShims = inspectCrewxShims;
|
|
16
|
+
exports.checkCliProviders = checkCliProviders;
|
|
11
17
|
exports.handleDoctor = handleDoctor;
|
|
12
|
-
const
|
|
13
|
-
const
|
|
14
|
-
const
|
|
18
|
+
const node_crypto_1 = require("node:crypto");
|
|
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,357 @@ function statusIcon(status) {
|
|
|
26
44
|
*/
|
|
27
45
|
function isCommandAvailable(cmd) {
|
|
28
46
|
try {
|
|
29
|
-
(0,
|
|
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 currentShimFingerprint() {
|
|
259
|
+
const resolution = (0, sdk_1.resolveCrewxExecutable)();
|
|
260
|
+
if (!resolution.ok)
|
|
261
|
+
return undefined;
|
|
262
|
+
return (0, node_crypto_1.createHash)('sha256')
|
|
263
|
+
.update(JSON.stringify(resolution.argv))
|
|
264
|
+
.digest('hex')
|
|
265
|
+
.slice(0, 16);
|
|
266
|
+
}
|
|
267
|
+
/** Inspect orphaned CrewX PATH shim directories without modifying them. */
|
|
268
|
+
function inspectCrewxShims(crewxHome = (0, sdk_1.getCrewxHome)(), resolvedFingerprint = currentShimFingerprint()) {
|
|
269
|
+
const shimRoot = (0, node_path_1.join)((0, node_path_1.resolve)(crewxHome), 'bin-shim');
|
|
270
|
+
const staleDirectories = [];
|
|
271
|
+
if (resolvedFingerprint) {
|
|
272
|
+
for (const directory of immediateDirectories(shimRoot)) {
|
|
273
|
+
const fingerprint = directory.slice(shimRoot.length + 1);
|
|
274
|
+
if (fingerprint === resolvedFingerprint)
|
|
275
|
+
continue;
|
|
276
|
+
staleDirectories.push({
|
|
277
|
+
fingerprint,
|
|
278
|
+
directory,
|
|
279
|
+
sizeBytes: directorySize(directory),
|
|
280
|
+
details: [`not current fingerprint (${resolvedFingerprint})`],
|
|
281
|
+
});
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
return { shimRoot, currentFingerprint: resolvedFingerprint, staleDirectories };
|
|
285
|
+
}
|
|
286
|
+
function checkNpxCache(report) {
|
|
287
|
+
const corrupt = report.zeroByteLockfileDirectories.length;
|
|
288
|
+
const stale = report.staleFileCacheDirectories.length;
|
|
289
|
+
const cleanable = report.cleanableDirectories.length;
|
|
290
|
+
if (corrupt === 0 && stale === 0) {
|
|
291
|
+
return {
|
|
292
|
+
name: 'npx Cache',
|
|
293
|
+
status: 'success',
|
|
294
|
+
message: `No stale entries found in ${report.cacheRoot}`,
|
|
295
|
+
};
|
|
296
|
+
}
|
|
297
|
+
const details = [];
|
|
298
|
+
if (corrupt > 0) {
|
|
299
|
+
details.push(`0-byte package-lock.json: ${report.zeroByteLockfileDirectories.map((entry) => entry.directory).join(', ')}`);
|
|
300
|
+
}
|
|
301
|
+
if (stale > 0) {
|
|
302
|
+
details.push(`stale file: cache: ${report.staleFileCacheDirectories.map((entry) => entry.directory).join(', ')}`);
|
|
303
|
+
}
|
|
304
|
+
details.push('Run `crewx doctor --clean-npx-cache` to remove these cache directories explicitly.');
|
|
305
|
+
return {
|
|
306
|
+
name: 'npx Cache',
|
|
307
|
+
status: 'warning',
|
|
308
|
+
message: `Found ${cleanable} problematic npx cache director${cleanable === 1 ? 'y' : 'ies'}`,
|
|
309
|
+
details: details.join('\n'),
|
|
310
|
+
};
|
|
311
|
+
}
|
|
312
|
+
function checkHookCommands(report) {
|
|
313
|
+
if (report.issues.length === 0) {
|
|
314
|
+
return {
|
|
315
|
+
name: 'CrewX Hooks',
|
|
316
|
+
status: 'success',
|
|
317
|
+
message: report.inspectedFiles.length > 0
|
|
318
|
+
? 'Installed CrewX hook commands use absolute entrypoints'
|
|
319
|
+
: 'No installed CrewX hooks found',
|
|
320
|
+
};
|
|
321
|
+
}
|
|
322
|
+
const details = report.issues
|
|
323
|
+
.map((issue) => `${issue.settingsPath}: ${issue.command} (${issue.reason})`)
|
|
324
|
+
.join('\n');
|
|
325
|
+
return {
|
|
326
|
+
name: 'CrewX Hooks',
|
|
327
|
+
status: 'warning',
|
|
328
|
+
message: `Found ${report.issues.length} hook command(s) that need an absolute CrewX entrypoint`,
|
|
329
|
+
details: `${details}\nRun \`crewx hook install\` to refresh the hook after reviewing the change.`,
|
|
330
|
+
};
|
|
331
|
+
}
|
|
332
|
+
function checkCrewxShims(report) {
|
|
333
|
+
if (!report.currentFingerprint) {
|
|
334
|
+
return {
|
|
335
|
+
name: 'CrewX PATH Shims',
|
|
336
|
+
status: 'warning',
|
|
337
|
+
message: 'Could not resolve the current CrewX executable; skipped orphan shim cleanup',
|
|
338
|
+
details: `Shim root: ${report.shimRoot}`,
|
|
339
|
+
};
|
|
340
|
+
}
|
|
341
|
+
if (report.staleDirectories.length === 0) {
|
|
342
|
+
return {
|
|
343
|
+
name: 'CrewX PATH Shims',
|
|
344
|
+
status: 'success',
|
|
345
|
+
message: 'No orphaned CrewX PATH shims found',
|
|
346
|
+
};
|
|
347
|
+
}
|
|
348
|
+
return {
|
|
349
|
+
name: 'CrewX PATH Shims',
|
|
350
|
+
status: 'warning',
|
|
351
|
+
message: `Found ${report.staleDirectories.length} orphaned CrewX PATH shim director${report.staleDirectories.length === 1 ? 'y' : 'ies'}`,
|
|
352
|
+
details: `${report.staleDirectories.map((entry) => entry.directory).join(', ')}\nRun \`crewx doctor --clean-npx-cache\` to remove stale shims explicitly.`,
|
|
353
|
+
};
|
|
354
|
+
}
|
|
355
|
+
function safelyRemoveDirectories(directories, parent) {
|
|
356
|
+
const expectedParent = (0, node_path_1.resolve)(parent);
|
|
357
|
+
let removed = 0;
|
|
358
|
+
const failed = [];
|
|
359
|
+
for (const entry of directories) {
|
|
360
|
+
const directory = (0, node_path_1.resolve)(entry.directory);
|
|
361
|
+
if ((0, node_path_1.dirname)(directory) !== expectedParent) {
|
|
362
|
+
failed.push(`${directory} (unexpected parent)`);
|
|
363
|
+
continue;
|
|
364
|
+
}
|
|
365
|
+
try {
|
|
366
|
+
(0, node_fs_1.rmSync)(directory, { recursive: true, force: true });
|
|
367
|
+
if (!(0, node_fs_1.existsSync)(directory))
|
|
368
|
+
removed += 1;
|
|
369
|
+
else
|
|
370
|
+
failed.push(directory);
|
|
371
|
+
}
|
|
372
|
+
catch {
|
|
373
|
+
failed.push(directory);
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
return { removed, failed };
|
|
377
|
+
}
|
|
378
|
+
function cleanDoctorArtifacts(npxCache, shims) {
|
|
379
|
+
const npxTargets = npxCache.cleanableDirectories;
|
|
380
|
+
const shimTargets = shims.staleDirectories;
|
|
381
|
+
const allTargets = [...npxTargets, ...shimTargets];
|
|
382
|
+
const totalBytes = allTargets.reduce((sum, entry) => sum + entry.sizeBytes, 0);
|
|
383
|
+
console.log(`⚠️ --clean-npx-cache: before cleanup, ${npxTargets.length} npx cache director${npxTargets.length === 1 ? 'y' : 'ies'} ` +
|
|
384
|
+
`and ${shimTargets.length} orphan shim director${shimTargets.length === 1 ? 'y' : 'ies'} (${formatBytes(totalBytes)}).`);
|
|
385
|
+
const npxResult = safelyRemoveDirectories(npxTargets, npxCache.cacheRoot);
|
|
386
|
+
const shimResult = safelyRemoveDirectories(shimTargets, shims.shimRoot);
|
|
387
|
+
const failures = [...npxResult.failed, ...shimResult.failed];
|
|
388
|
+
console.log(` Removed ${npxResult.removed + shimResult.removed} director${npxResult.removed + shimResult.removed === 1 ? 'y' : 'ies'}.`);
|
|
389
|
+
if (failures.length > 0) {
|
|
390
|
+
console.log(` Could not remove: ${failures.join(', ')}`);
|
|
391
|
+
}
|
|
392
|
+
}
|
|
36
393
|
/**
|
|
37
394
|
* Check crewx.yaml configuration file.
|
|
38
395
|
*/
|
|
39
396
|
function checkConfig(configPath) {
|
|
40
|
-
if (!(0,
|
|
397
|
+
if (!(0, node_fs_1.existsSync)(configPath)) {
|
|
41
398
|
return {
|
|
42
399
|
name: 'Configuration File',
|
|
43
400
|
status: 'error',
|
|
@@ -83,8 +440,8 @@ function checkConfig(configPath) {
|
|
|
83
440
|
* Check .crewx/logs directory.
|
|
84
441
|
*/
|
|
85
442
|
function checkLogsDir() {
|
|
86
|
-
const logsDir = (0,
|
|
87
|
-
if (!(0,
|
|
443
|
+
const logsDir = (0, node_path_1.join)(process.cwd(), '.crewx', 'logs');
|
|
444
|
+
if (!(0, node_fs_1.existsSync)(logsDir)) {
|
|
88
445
|
return {
|
|
89
446
|
name: 'Logs Directory',
|
|
90
447
|
status: 'warning',
|
|
@@ -99,20 +456,19 @@ function checkLogsDir() {
|
|
|
99
456
|
};
|
|
100
457
|
}
|
|
101
458
|
/**
|
|
102
|
-
* Check CLI provider availability.
|
|
103
|
-
* Providers are checked in PROVIDER_ORDER: codex → claude → opencode → antigravity → copilot.
|
|
459
|
+
* Check CLI provider availability in the canonical PROVIDER_ORDER.
|
|
104
460
|
*/
|
|
105
461
|
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
462
|
return sdk_1.PROVIDER_ORDER.map(provider => {
|
|
115
|
-
const info =
|
|
463
|
+
const info = exports.CLI_PROVIDER_INFO[provider];
|
|
464
|
+
if (!info) {
|
|
465
|
+
return {
|
|
466
|
+
name: `${provider.toUpperCase()} CLI`,
|
|
467
|
+
status: 'error',
|
|
468
|
+
message: 'Diagnostic metadata is missing',
|
|
469
|
+
details: `Add ${provider} to CLI_PROVIDER_INFO.`,
|
|
470
|
+
};
|
|
471
|
+
}
|
|
116
472
|
const displayName = provider === 'copilot' ? 'copilot (gh)' : provider;
|
|
117
473
|
const available = isCommandAvailable(info.cmd);
|
|
118
474
|
return {
|
|
@@ -138,7 +494,7 @@ function checkEnvVars() {
|
|
|
138
494
|
return {
|
|
139
495
|
name: 'CREWX_CLI',
|
|
140
496
|
status: 'warning',
|
|
141
|
-
message: 'Not set — defaulting to "
|
|
497
|
+
message: 'Not set — defaulting to "crewx"',
|
|
142
498
|
details: 'This is auto-set at CLI startup; no action needed.',
|
|
143
499
|
};
|
|
144
500
|
}
|
|
@@ -161,14 +517,28 @@ function assessHealth(diagnostics) {
|
|
|
161
517
|
* Handle `crewx doctor` command.
|
|
162
518
|
*/
|
|
163
519
|
async function handleDoctor(args) {
|
|
164
|
-
const
|
|
165
|
-
const
|
|
520
|
+
const cleanNpxCache = args.includes('--clean-npx-cache');
|
|
521
|
+
const commonArgs = args.filter((arg) => arg !== '--clean-npx-cache');
|
|
522
|
+
const { config } = (0, parse_common_flags_1.parseCommonFlags)(commonArgs);
|
|
523
|
+
const configPath = config ?? process.env.CREWX_CONFIG ?? (0, node_path_1.join)(process.cwd(), 'crewx.yaml');
|
|
166
524
|
console.log('🩺 Starting CrewX system diagnosis...\n');
|
|
525
|
+
let npxCache = inspectNpxCache();
|
|
526
|
+
let shims = inspectCrewxShims();
|
|
527
|
+
const projectRoot = (0, node_path_1.dirname)((0, node_path_1.resolve)(configPath));
|
|
528
|
+
const hooks = inspectHookCommands(projectRoot);
|
|
529
|
+
if (cleanNpxCache) {
|
|
530
|
+
cleanDoctorArtifacts(npxCache, shims);
|
|
531
|
+
npxCache = inspectNpxCache();
|
|
532
|
+
shims = inspectCrewxShims();
|
|
533
|
+
}
|
|
167
534
|
const diagnostics = [
|
|
168
535
|
checkConfig(configPath),
|
|
169
536
|
checkLogsDir(),
|
|
170
537
|
checkEnvVars(),
|
|
171
538
|
...checkCliProviders(),
|
|
539
|
+
checkNpxCache(npxCache),
|
|
540
|
+
checkHookCommands(hooks),
|
|
541
|
+
checkCrewxShims(shims),
|
|
172
542
|
];
|
|
173
543
|
// Print diagnostics
|
|
174
544
|
diagnostics.forEach(d => {
|
|
@@ -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
|
+
}
|
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
* Flags:
|
|
6
6
|
* --thread <name> Conversation thread name
|
|
7
7
|
* --provider <cli/xxx> Provider override
|
|
8
|
+
* --model <name> Model override (e.g. claude-sonnet-5)
|
|
8
9
|
* --metadata <json> Extra metadata JSON (double-quoted object). Propagated to events/hooks/tracing.
|
|
9
10
|
* e.g. --metadata='{"workflow_id":"wf-1"}'
|
|
10
11
|
* --verbose Debug output mode (default: raw agent response only)
|
|
@@ -12,16 +13,20 @@
|
|
|
12
13
|
* --output-format <fmt> Output format (json|text|stream-json)
|
|
13
14
|
* --effort <level> Model effort (high|medium|low)
|
|
14
15
|
* -f/--prompt-file <path> Read task body from file (bypasses cmd.exe argv truncation)
|
|
16
|
+
* --detach Re-spawn as a detached runner; print task-id and exit 0 immediately.
|
|
17
|
+
* Ignored when CREWX_TRACE_ID is already set (recursive-spawn guard) or
|
|
18
|
+
* on win32 (unsupported — exits with an error).
|
|
15
19
|
*
|
|
16
20
|
* Stdin support:
|
|
17
21
|
* Pipe or redirect content into crewx x to supply the task body via stdin.
|
|
18
22
|
* e.g. cat task.md | crewx x "@agent label"
|
|
19
23
|
* Stdin is ignored when running in an interactive TTY.
|
|
20
24
|
*/
|
|
25
|
+
import { type DelegationEmitState } from './emit-trailer';
|
|
21
26
|
/**
|
|
22
27
|
* Handle `crewx execute <agentRef> <message>` command.
|
|
23
28
|
*
|
|
24
29
|
* Default output: raw agent response only (stdout).
|
|
25
30
|
* --verbose: debug info written to stderr, response to stdout.
|
|
26
31
|
*/
|
|
27
|
-
export declare function handleExecute(args: string[]): Promise<void>;
|
|
32
|
+
export declare function handleExecute(args: string[], command?: string, emitState?: DelegationEmitState): Promise<void>;
|