@crewx/cli 0.9.0-rc.86 → 0.9.0-rc.87
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/commands/doctor.d.ts +36 -0
- package/dist/commands/doctor.js +370 -10
- 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/package.json +13 -13
|
@@ -16,8 +16,44 @@ export interface CliProviderInfo {
|
|
|
16
16
|
cmd: string;
|
|
17
17
|
install: string;
|
|
18
18
|
}
|
|
19
|
+
export interface DoctorArtifact {
|
|
20
|
+
directory: string;
|
|
21
|
+
sizeBytes: number;
|
|
22
|
+
details: string[];
|
|
23
|
+
}
|
|
24
|
+
export interface NpxCacheReport {
|
|
25
|
+
cacheRoot: string;
|
|
26
|
+
zeroByteLockfileDirectories: DoctorArtifact[];
|
|
27
|
+
staleFileCacheDirectories: DoctorArtifact[];
|
|
28
|
+
cleanableDirectories: DoctorArtifact[];
|
|
29
|
+
}
|
|
30
|
+
export interface HookCommandIssue {
|
|
31
|
+
provider: 'claude' | 'codex';
|
|
32
|
+
settingsPath: string;
|
|
33
|
+
command: string;
|
|
34
|
+
reason: 'launcher' | 'non-absolute';
|
|
35
|
+
}
|
|
36
|
+
export interface HookCommandReport {
|
|
37
|
+
inspectedFiles: string[];
|
|
38
|
+
issues: HookCommandIssue[];
|
|
39
|
+
}
|
|
40
|
+
export interface CrewxShimReport {
|
|
41
|
+
shimRoot: string;
|
|
42
|
+
currentFingerprint?: string;
|
|
43
|
+
staleDirectories: Array<DoctorArtifact & {
|
|
44
|
+
fingerprint: string;
|
|
45
|
+
}>;
|
|
46
|
+
}
|
|
19
47
|
/** CLI diagnostics keyed by the bare provider name used in PROVIDER_ORDER. */
|
|
20
48
|
export declare const CLI_PROVIDER_INFO: Record<string, CliProviderInfo>;
|
|
49
|
+
/** Format a byte count for diagnostic output. */
|
|
50
|
+
export declare function formatBytes(bytes: number): string;
|
|
51
|
+
/** Inspect npm's persistent `_npx` cache without modifying it. */
|
|
52
|
+
export declare function inspectNpxCache(cacheRoot?: string): NpxCacheReport;
|
|
53
|
+
/** Inspect installed CrewX hooks without rewriting provider settings. */
|
|
54
|
+
export declare function inspectHookCommands(projectRoot: string): HookCommandReport;
|
|
55
|
+
/** Inspect orphaned CrewX PATH shim directories without modifying them. */
|
|
56
|
+
export declare function inspectCrewxShims(crewxHome?: string, resolvedFingerprint?: string | undefined): CrewxShimReport;
|
|
21
57
|
/**
|
|
22
58
|
* Check CLI provider availability in the canonical PROVIDER_ORDER.
|
|
23
59
|
*/
|
package/dist/commands/doctor.js
CHANGED
|
@@ -9,13 +9,20 @@
|
|
|
9
9
|
*/
|
|
10
10
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
11
11
|
exports.CLI_PROVIDER_INFO = void 0;
|
|
12
|
+
exports.formatBytes = formatBytes;
|
|
13
|
+
exports.inspectNpxCache = inspectNpxCache;
|
|
14
|
+
exports.inspectHookCommands = inspectHookCommands;
|
|
15
|
+
exports.inspectCrewxShims = inspectCrewxShims;
|
|
12
16
|
exports.checkCliProviders = checkCliProviders;
|
|
13
17
|
exports.handleDoctor = handleDoctor;
|
|
14
|
-
const
|
|
15
|
-
const
|
|
16
|
-
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");
|
|
17
23
|
const sdk_1 = require("@crewx/sdk");
|
|
18
24
|
const parse_common_flags_1 = require("./parse-common-flags");
|
|
25
|
+
const command_marker_1 = require("./hook/command-marker");
|
|
19
26
|
/** CLI diagnostics keyed by the bare provider name used in PROVIDER_ORDER. */
|
|
20
27
|
exports.CLI_PROVIDER_INFO = {
|
|
21
28
|
codex: { cmd: 'codex', install: 'npm install -g @openai/codex' },
|
|
@@ -37,18 +44,357 @@ function statusIcon(status) {
|
|
|
37
44
|
*/
|
|
38
45
|
function isCommandAvailable(cmd) {
|
|
39
46
|
try {
|
|
40
|
-
(0,
|
|
47
|
+
(0, node_child_process_1.execSync)(`which ${cmd}`, { stdio: 'ignore' });
|
|
41
48
|
return true;
|
|
42
49
|
}
|
|
43
50
|
catch {
|
|
44
51
|
return false;
|
|
45
52
|
}
|
|
46
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
|
+
}
|
|
47
393
|
/**
|
|
48
394
|
* Check crewx.yaml configuration file.
|
|
49
395
|
*/
|
|
50
396
|
function checkConfig(configPath) {
|
|
51
|
-
if (!(0,
|
|
397
|
+
if (!(0, node_fs_1.existsSync)(configPath)) {
|
|
52
398
|
return {
|
|
53
399
|
name: 'Configuration File',
|
|
54
400
|
status: 'error',
|
|
@@ -94,8 +440,8 @@ function checkConfig(configPath) {
|
|
|
94
440
|
* Check .crewx/logs directory.
|
|
95
441
|
*/
|
|
96
442
|
function checkLogsDir() {
|
|
97
|
-
const logsDir = (0,
|
|
98
|
-
if (!(0,
|
|
443
|
+
const logsDir = (0, node_path_1.join)(process.cwd(), '.crewx', 'logs');
|
|
444
|
+
if (!(0, node_fs_1.existsSync)(logsDir)) {
|
|
99
445
|
return {
|
|
100
446
|
name: 'Logs Directory',
|
|
101
447
|
status: 'warning',
|
|
@@ -148,7 +494,7 @@ function checkEnvVars() {
|
|
|
148
494
|
return {
|
|
149
495
|
name: 'CREWX_CLI',
|
|
150
496
|
status: 'warning',
|
|
151
|
-
message: 'Not set — defaulting to "
|
|
497
|
+
message: 'Not set — defaulting to "crewx"',
|
|
152
498
|
details: 'This is auto-set at CLI startup; no action needed.',
|
|
153
499
|
};
|
|
154
500
|
}
|
|
@@ -171,14 +517,28 @@ function assessHealth(diagnostics) {
|
|
|
171
517
|
* Handle `crewx doctor` command.
|
|
172
518
|
*/
|
|
173
519
|
async function handleDoctor(args) {
|
|
174
|
-
const
|
|
175
|
-
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');
|
|
176
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
|
+
}
|
|
177
534
|
const diagnostics = [
|
|
178
535
|
checkConfig(configPath),
|
|
179
536
|
checkLogsDir(),
|
|
180
537
|
checkEnvVars(),
|
|
181
538
|
...checkCliProviders(),
|
|
539
|
+
checkNpxCache(npxCache),
|
|
540
|
+
checkHookCommands(hooks),
|
|
541
|
+
checkCrewxShims(shims),
|
|
182
542
|
];
|
|
183
543
|
// Print diagnostics
|
|
184
544
|
diagnostics.forEach(d => {
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.CREWX_HOOK_COMMAND_RE = void 0;
|
|
4
|
+
/** Match only commands that invoke CrewX's hook-dispatch subcommand. */
|
|
5
|
+
exports.CREWX_HOOK_COMMAND_RE = /crewx(?:\.js|\.cmd|\.exe)?['"]?\s+hook-dispatch\b/i;
|
|
@@ -10,7 +10,6 @@
|
|
|
10
10
|
* - Never traverses parent directories — project root determined by crewx.yaml
|
|
11
11
|
*/
|
|
12
12
|
import { type HookProvider } from './paths';
|
|
13
|
-
export declare const CREWX_HOOK_COMMAND_MARKER = "crewx hook-dispatch";
|
|
14
13
|
export declare function resolveCrewxBinary(): string;
|
|
15
14
|
export interface HookInstallOpts {
|
|
16
15
|
projectRoot: string;
|
|
@@ -10,49 +10,14 @@
|
|
|
10
10
|
* - Preserves existing user hooks
|
|
11
11
|
* - Never traverses parent directories — project root determined by crewx.yaml
|
|
12
12
|
*/
|
|
13
|
-
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
14
|
-
if (k2 === undefined) k2 = k;
|
|
15
|
-
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
16
|
-
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
17
|
-
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
18
|
-
}
|
|
19
|
-
Object.defineProperty(o, k2, desc);
|
|
20
|
-
}) : (function(o, m, k, k2) {
|
|
21
|
-
if (k2 === undefined) k2 = k;
|
|
22
|
-
o[k2] = m[k];
|
|
23
|
-
}));
|
|
24
|
-
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
25
|
-
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
26
|
-
}) : function(o, v) {
|
|
27
|
-
o["default"] = v;
|
|
28
|
-
});
|
|
29
|
-
var __importStar = (this && this.__importStar) || (function () {
|
|
30
|
-
var ownKeys = function(o) {
|
|
31
|
-
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
32
|
-
var ar = [];
|
|
33
|
-
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
34
|
-
return ar;
|
|
35
|
-
};
|
|
36
|
-
return ownKeys(o);
|
|
37
|
-
};
|
|
38
|
-
return function (mod) {
|
|
39
|
-
if (mod && mod.__esModule) return mod;
|
|
40
|
-
var result = {};
|
|
41
|
-
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
42
|
-
__setModuleDefault(result, mod);
|
|
43
|
-
return result;
|
|
44
|
-
};
|
|
45
|
-
})();
|
|
46
13
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
47
|
-
exports.CREWX_HOOK_COMMAND_MARKER = void 0;
|
|
48
14
|
exports.resolveCrewxBinary = resolveCrewxBinary;
|
|
49
15
|
exports.handleHookInstall = handleHookInstall;
|
|
50
16
|
const fs_1 = require("fs");
|
|
51
|
-
const
|
|
52
|
-
const cp = __importStar(require("child_process"));
|
|
17
|
+
const sdk_1 = require("@crewx/sdk");
|
|
53
18
|
const paths_1 = require("./paths");
|
|
54
|
-
|
|
55
|
-
function getProviderConfigs(projectRoot, providers
|
|
19
|
+
const command_marker_1 = require("./command-marker");
|
|
20
|
+
function getProviderConfigs(projectRoot, providers) {
|
|
56
21
|
return providers.map((provider) => ({
|
|
57
22
|
settingsPath: provider === 'claude'
|
|
58
23
|
? (0, paths_1.getClaudeSettingsPath)(projectRoot)
|
|
@@ -63,24 +28,13 @@ function getProviderConfigs(projectRoot, providers, crewxBin) {
|
|
|
63
28
|
}));
|
|
64
29
|
}
|
|
65
30
|
function resolveCrewxBinary() {
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
const which = cp.execSync('which crewx 2>/dev/null', { encoding: 'utf8' }).trim();
|
|
70
|
-
const isTmpProxy = which.includes('/T/crewx-proxy-') ||
|
|
71
|
-
which.includes('/tmp/crewx-proxy-') ||
|
|
72
|
-
/\/[Tt]e?mp\//.test(which);
|
|
73
|
-
if (which && !isTmpProxy)
|
|
74
|
-
return which;
|
|
31
|
+
const resolution = (0, sdk_1.resolveCrewxExecutable)();
|
|
32
|
+
if (!resolution.ok) {
|
|
33
|
+
throw new Error((0, sdk_1.formatCrewxExecutableFailure)(resolution));
|
|
75
34
|
}
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
if ((0, fs_1.existsSync)(scriptPath)) {
|
|
80
|
-
return `${process.execPath} ${scriptPath}`;
|
|
81
|
-
}
|
|
82
|
-
}
|
|
83
|
-
return 'crewx';
|
|
35
|
+
const stableEntrypoint = (0, sdk_1.materializeCrewxStableEntrypoint)(resolution);
|
|
36
|
+
const useStable = stableEntrypoint && resolution.argv.some(sdk_1.isRotatingInstallPath);
|
|
37
|
+
return (0, sdk_1.formatCrewxExecutableArgv)(useStable ? [stableEntrypoint] : resolution.argv);
|
|
84
38
|
}
|
|
85
39
|
function readSettingsTyped(settingsPath) {
|
|
86
40
|
return (0, paths_1.readSettings)(settingsPath);
|
|
@@ -88,10 +42,19 @@ function readSettingsTyped(settingsPath) {
|
|
|
88
42
|
function findCrewxEntry(preToolUse) {
|
|
89
43
|
if (!preToolUse)
|
|
90
44
|
return -1;
|
|
91
|
-
return preToolUse.findIndex((entry) => entry.hooks?.some((h) => h.command
|
|
45
|
+
return preToolUse.findIndex((entry) => entry.hooks?.some((h) => command_marker_1.CREWX_HOOK_COMMAND_RE.test(h.command)));
|
|
46
|
+
}
|
|
47
|
+
function findCrewxHook(entry) {
|
|
48
|
+
if (!entry.hooks)
|
|
49
|
+
return undefined;
|
|
50
|
+
const index = entry.hooks.findIndex((hook) => command_marker_1.CREWX_HOOK_COMMAND_RE.test(hook.command));
|
|
51
|
+
if (index < 0)
|
|
52
|
+
return undefined;
|
|
53
|
+
return { index, command: entry.hooks[index].command };
|
|
92
54
|
}
|
|
93
55
|
function installForProvider(projectRoot, config, crewxBin, yes) {
|
|
94
56
|
const { settingsPath, provider, matcher, commandArg } = config;
|
|
57
|
+
const command = `${crewxBin} hook-dispatch ${commandArg}`;
|
|
95
58
|
const settings = readSettingsTyped(settingsPath);
|
|
96
59
|
if (!settings.hooks)
|
|
97
60
|
settings.hooks = {};
|
|
@@ -99,8 +62,28 @@ function installForProvider(projectRoot, config, crewxBin, yes) {
|
|
|
99
62
|
settings.hooks.PreToolUse = [];
|
|
100
63
|
const existingIdx = findCrewxEntry(settings.hooks.PreToolUse);
|
|
101
64
|
if (existingIdx >= 0) {
|
|
102
|
-
|
|
103
|
-
|
|
65
|
+
const existingEntry = settings.hooks.PreToolUse[existingIdx];
|
|
66
|
+
const existingHook = findCrewxHook(existingEntry);
|
|
67
|
+
if (existingHook?.command === command) {
|
|
68
|
+
console.log(`[crewx] Hook already installed in ${settingsPath} (${provider})`);
|
|
69
|
+
return false;
|
|
70
|
+
}
|
|
71
|
+
if (!yes) {
|
|
72
|
+
console.log(`[crewx] Existing CrewX hook needs an install-path refresh in ${settingsPath} (${provider})`);
|
|
73
|
+
return false;
|
|
74
|
+
}
|
|
75
|
+
if ((0, fs_1.existsSync)(settingsPath)) {
|
|
76
|
+
const backupPath = settingsPath + '.crewx-backup';
|
|
77
|
+
(0, fs_1.copyFileSync)(settingsPath, backupPath);
|
|
78
|
+
console.log(`[crewx] Backup created: ${backupPath}`);
|
|
79
|
+
}
|
|
80
|
+
if (existingHook) {
|
|
81
|
+
existingEntry.hooks[existingHook.index] = { ...existingEntry.hooks[existingHook.index], command };
|
|
82
|
+
}
|
|
83
|
+
(0, fs_1.writeFileSync)(settingsPath, JSON.stringify(settings, null, 2) + '\n', 'utf8');
|
|
84
|
+
console.log(`[crewx] Hook updated (${provider}): ${command}`);
|
|
85
|
+
console.log(`[crewx] Settings: ${settingsPath}`);
|
|
86
|
+
return true;
|
|
104
87
|
}
|
|
105
88
|
if (!yes) {
|
|
106
89
|
console.log(`⚠️ crewx hook install will register crewx-hook-dispatch as a PreToolUse hook.\n` +
|
|
@@ -119,7 +102,6 @@ function installForProvider(projectRoot, config, crewxBin, yes) {
|
|
|
119
102
|
else {
|
|
120
103
|
(0, paths_1.ensureCodexHooks)(projectRoot);
|
|
121
104
|
}
|
|
122
|
-
const command = `${crewxBin} hook-dispatch ${commandArg}`;
|
|
123
105
|
settings.hooks.PreToolUse.push({
|
|
124
106
|
matcher,
|
|
125
107
|
hooks: [{ type: 'command', command }],
|
|
@@ -133,8 +115,15 @@ async function handleHookInstall(argsOrOpts) {
|
|
|
133
115
|
if (!Array.isArray(argsOrOpts)) {
|
|
134
116
|
const { projectRoot, yes, provider } = argsOrOpts;
|
|
135
117
|
const providers = (0, paths_1.providersFromFilter)(provider ?? 'all');
|
|
136
|
-
|
|
137
|
-
|
|
118
|
+
let crewxBin;
|
|
119
|
+
try {
|
|
120
|
+
crewxBin = resolveCrewxBinary();
|
|
121
|
+
}
|
|
122
|
+
catch (error) {
|
|
123
|
+
console.error(`[crewx] ${error instanceof Error ? error.message : String(error)}`);
|
|
124
|
+
return;
|
|
125
|
+
}
|
|
126
|
+
const configs = getProviderConfigs(projectRoot, providers);
|
|
138
127
|
for (const config of configs) {
|
|
139
128
|
installForProvider(projectRoot, config, crewxBin, yes);
|
|
140
129
|
}
|
|
@@ -152,8 +141,16 @@ async function handleHookInstall(argsOrOpts) {
|
|
|
152
141
|
}
|
|
153
142
|
const providerFilter = (0, paths_1.parseProviderArg)(args);
|
|
154
143
|
const providers = (0, paths_1.providersFromFilter)(providerFilter);
|
|
155
|
-
|
|
156
|
-
|
|
144
|
+
let crewxBin;
|
|
145
|
+
try {
|
|
146
|
+
crewxBin = resolveCrewxBinary();
|
|
147
|
+
}
|
|
148
|
+
catch (error) {
|
|
149
|
+
console.error(`[crewx] ${error instanceof Error ? error.message : String(error)}`);
|
|
150
|
+
process.exitCode = 1;
|
|
151
|
+
return;
|
|
152
|
+
}
|
|
153
|
+
const configs = getProviderConfigs(projectRoot, providers);
|
|
157
154
|
if (!yes) {
|
|
158
155
|
const targets = configs.map((c) => ` - ${c.provider}: ${c.settingsPath}`).join('\n');
|
|
159
156
|
console.log(`⚠️ crewx hook install will register crewx-hook-dispatch as a PreToolUse hook.\n` +
|
|
@@ -11,7 +11,7 @@ const fs_1 = require("fs");
|
|
|
11
11
|
const path_1 = require("path");
|
|
12
12
|
const sdk_1 = require("@crewx/sdk");
|
|
13
13
|
const paths_1 = require("./paths");
|
|
14
|
-
const
|
|
14
|
+
const command_marker_1 = require("./command-marker");
|
|
15
15
|
function showProviderStatus(projectRoot, provider) {
|
|
16
16
|
const settingsPath = provider === 'claude'
|
|
17
17
|
? (0, paths_1.getClaudeSettingsPath)(projectRoot)
|
|
@@ -26,9 +26,9 @@ function showProviderStatus(projectRoot, provider) {
|
|
|
26
26
|
try {
|
|
27
27
|
const settings = JSON.parse((0, fs_1.readFileSync)(settingsPath, 'utf8'));
|
|
28
28
|
const preHooks = settings.hooks?.PreToolUse ?? [];
|
|
29
|
-
const crewxEntry = preHooks.find((entry) => entry.hooks?.some((h) => h.command
|
|
29
|
+
const crewxEntry = preHooks.find((entry) => entry.hooks?.some((h) => command_marker_1.CREWX_HOOK_COMMAND_RE.test(h.command ?? '')));
|
|
30
30
|
if (crewxEntry) {
|
|
31
|
-
const command = crewxEntry.hooks.find((h) => h.command
|
|
31
|
+
const command = crewxEntry.hooks.find((h) => command_marker_1.CREWX_HOOK_COMMAND_RE.test(h.command ?? ''))?.command;
|
|
32
32
|
console.log(` Status: INSTALLED`);
|
|
33
33
|
console.log(` Command: ${command}`);
|
|
34
34
|
}
|
|
@@ -10,7 +10,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
10
10
|
exports.handleHookUninstall = handleHookUninstall;
|
|
11
11
|
const fs_1 = require("fs");
|
|
12
12
|
const paths_1 = require("./paths");
|
|
13
|
-
const
|
|
13
|
+
const command_marker_1 = require("./command-marker");
|
|
14
14
|
function getSettingsPath(projectRoot, provider) {
|
|
15
15
|
return provider === 'claude'
|
|
16
16
|
? (0, paths_1.getClaudeSettingsPath)(projectRoot)
|
|
@@ -35,7 +35,7 @@ function uninstallFromProvider(projectRoot, provider) {
|
|
|
35
35
|
return;
|
|
36
36
|
}
|
|
37
37
|
const before = settings.hooks.PreToolUse.length;
|
|
38
|
-
settings.hooks.PreToolUse = settings.hooks.PreToolUse.filter((entry) => !entry.hooks?.some((h) => h.command
|
|
38
|
+
settings.hooks.PreToolUse = settings.hooks.PreToolUse.filter((entry) => !entry.hooks?.some((h) => command_marker_1.CREWX_HOOK_COMMAND_RE.test(h.command)));
|
|
39
39
|
const removed = settings.hooks.PreToolUse.length < before;
|
|
40
40
|
if (settings.hooks.PreToolUse.length === 0) {
|
|
41
41
|
delete settings.hooks.PreToolUse;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@crewx/cli",
|
|
3
|
-
"version": "0.9.0-rc.
|
|
3
|
+
"version": "0.9.0-rc.87",
|
|
4
4
|
"license": "UNLICENSED",
|
|
5
5
|
"engines": {
|
|
6
6
|
"node": ">=20.19.0"
|
|
@@ -25,18 +25,18 @@
|
|
|
25
25
|
"better-sqlite3": "*",
|
|
26
26
|
"dotenv": "17.2.3",
|
|
27
27
|
"isomorphic-git": "1.37.1",
|
|
28
|
-
"@crewx/
|
|
29
|
-
"@crewx/
|
|
30
|
-
"@crewx/
|
|
31
|
-
"@crewx/
|
|
32
|
-
"@crewx/
|
|
33
|
-
"@crewx/
|
|
34
|
-
"@crewx/
|
|
35
|
-
"@crewx/
|
|
36
|
-
"@crewx/
|
|
37
|
-
"@crewx/
|
|
38
|
-
"@crewx/
|
|
39
|
-
"@crewx/
|
|
28
|
+
"@crewx/sdk": "0.9.0-rc.87",
|
|
29
|
+
"@crewx/wbs": "0.1.10-rc.113",
|
|
30
|
+
"@crewx/memory": "0.1.23-rc.104",
|
|
31
|
+
"@crewx/search": "0.1.10-rc.83",
|
|
32
|
+
"@crewx/cron": "0.1.10-rc.122",
|
|
33
|
+
"@crewx/doc": "0.1.9-rc.80",
|
|
34
|
+
"@crewx/workflow": "0.3.22-rc.133",
|
|
35
|
+
"@crewx/wi": "0.1.10-rc.107",
|
|
36
|
+
"@crewx/chromex": "0.1.0-rc.120",
|
|
37
|
+
"@crewx/notify": "0.1.0-rc.58",
|
|
38
|
+
"@crewx/shared": "0.0.6",
|
|
39
|
+
"@crewx/skill": "0.1.20"
|
|
40
40
|
},
|
|
41
41
|
"devDependencies": {
|
|
42
42
|
"@types/better-sqlite3": "*",
|