@crewx/cli 0.9.0-rc.14 → 0.9.0-rc.141
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 +27 -6
- package/dist/builtin.d.ts +1 -1
- package/dist/builtin.js +7 -2
- package/dist/commands/agent.js +1 -107
- package/dist/commands/cal.d.ts +15 -0
- package/dist/commands/cal.js +44 -0
- package/dist/commands/db.d.ts +3 -0
- package/dist/commands/db.js +533 -1
- package/dist/commands/detached.d.ts +95 -0
- package/dist/commands/detached.js +620 -0
- package/dist/commands/doctor.d.ts +77 -0
- package/dist/commands/doctor.js +475 -22
- package/dist/commands/emit-trailer.d.ts +25 -0
- package/dist/commands/emit-trailer.js +33 -0
- package/dist/commands/execute.d.ts +8 -4
- package/dist/commands/execute.js +200 -51
- 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 +75 -61
- package/dist/commands/hook/status.js +3 -3
- package/dist/commands/hook/uninstall.js +2 -2
- package/dist/commands/init.d.ts +34 -0
- package/dist/commands/init.js +132 -59
- package/dist/commands/kill.d.ts +2 -8
- package/dist/commands/kill.js +6 -47
- package/dist/commands/log.js +4 -3
- package/dist/commands/parse-common-flags.d.ts +5 -1
- package/dist/commands/parse-common-flags.js +35 -4
- package/dist/commands/ps.d.ts +2 -2
- package/dist/commands/ps.js +25 -15
- package/dist/commands/publish.d.ts +1 -0
- package/dist/commands/publish.js +303 -0
- package/dist/commands/query.d.ts +7 -2
- package/dist/commands/query.js +195 -48
- package/dist/commands/registry.js +4 -2
- package/dist/commands/resolve-prompt.js +23 -1
- package/dist/commands/resolve-recipient.d.ts +14 -0
- package/dist/commands/resolve-recipient.js +47 -0
- package/dist/commands/restart.d.ts +3 -1
- package/dist/commands/restart.js +14 -4
- package/dist/commands/result.d.ts +8 -3
- package/dist/commands/result.js +149 -10
- 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/stop.d.ts +8 -0
- package/dist/commands/stop.js +119 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.js +4 -1
- package/dist/logging.d.ts +1 -1
- package/dist/logging.js +3 -2
- package/dist/main.d.ts +3 -2
- package/dist/main.js +99 -24
- package/dist/utils/env-defaults.d.ts +2 -5
- package/dist/utils/env-defaults.js +10 -5
- package/dist/utils/inherited-trace.d.ts +24 -0
- package/dist/utils/inherited-trace.js +40 -0
- package/dist/utils/product-package-root.d.ts +6 -0
- package/dist/utils/product-package-root.js +39 -0
- package/dist/utils/sdk-compat.d.ts +24 -0
- package/dist/utils/sdk-compat.js +120 -0
- package/package.json +16 -12
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.inspectCrewxExecutable = inspectCrewxExecutable;
|
|
16
|
+
exports.inspectCrewxShims = inspectCrewxShims;
|
|
17
|
+
exports.checkCliProviders = checkCliProviders;
|
|
11
18
|
exports.handleDoctor = handleDoctor;
|
|
12
|
-
const
|
|
13
|
-
const
|
|
14
|
-
const
|
|
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,
|
|
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,
|
|
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,
|
|
87
|
-
if (!(0,
|
|
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 =
|
|
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 "
|
|
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
|
|
165
|
-
const
|
|
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
|
|
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
|
|
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>;
|