@fenglimg/fabric-cli 1.3.1 → 1.5.0
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/README.md +15 -11
- package/dist/approve-YT4DEABS.js +138 -0
- package/dist/{bootstrap-3PUKUYTY.js → bootstrap-VGL3AR26.js} +3 -3
- package/dist/{chunk-VOQKQ6W2.js → chunk-BEKSXO5N.js} +10 -2
- package/dist/{chunk-XFSQM3LJ.js → chunk-BVTMVW5M.js} +1 -1
- package/dist/{chunk-AZRKMFRY.js → chunk-QSAEGVKE.js} +2 -2
- package/dist/{chunk-TKTWHAKV.js → chunk-T2WJF5I3.js} +9 -9
- package/dist/{config-GINBGANU.js → config-EC5L2QNI.js} +2 -2
- package/dist/index.js +7 -6
- package/dist/{init-T3LGMGAO.js → init-YR7EVBYQ.js} +1088 -281
- package/dist/{scan-43R3IBLR.js → scan-QH76LC7Z.js} +1 -1
- package/dist/scanner/tree-sitter-probe.d.ts +24 -0
- package/dist/scanner/tree-sitter-probe.js +107 -0
- package/dist/{update-AN3FYF2O.js → update-M5M5PYKE.js} +7 -7
- package/package.json +7 -3
- package/templates/codex-hooks/fabric-session-start.cjs +19 -0
- package/templates/codex-hooks/fabric-stop-reminder.cjs +18 -0
- package/templates/codex-skills/fabric-init/SKILL.md +27 -0
|
@@ -2,19 +2,10 @@
|
|
|
2
2
|
import {
|
|
3
3
|
buildFabricBootstrapGuide,
|
|
4
4
|
installBootstrap
|
|
5
|
-
} from "./chunk-
|
|
5
|
+
} from "./chunk-T2WJF5I3.js";
|
|
6
6
|
import {
|
|
7
7
|
detectFramework
|
|
8
|
-
} from "./chunk-
|
|
9
|
-
import {
|
|
10
|
-
installMcpClients
|
|
11
|
-
} from "./chunk-XFSQM3LJ.js";
|
|
12
|
-
import {
|
|
13
|
-
detectClientSupports
|
|
14
|
-
} from "./chunk-VOQKQ6W2.js";
|
|
15
|
-
import {
|
|
16
|
-
installHooks
|
|
17
|
-
} from "./chunk-YDZJRLHL.js";
|
|
8
|
+
} from "./chunk-QSAEGVKE.js";
|
|
18
9
|
import {
|
|
19
10
|
createDebugLogger,
|
|
20
11
|
resolveDevMode
|
|
@@ -24,6 +15,15 @@ import {
|
|
|
24
15
|
padEnd,
|
|
25
16
|
paint
|
|
26
17
|
} from "./chunk-WWNXR34K.js";
|
|
18
|
+
import {
|
|
19
|
+
installMcpClients
|
|
20
|
+
} from "./chunk-BVTMVW5M.js";
|
|
21
|
+
import {
|
|
22
|
+
detectClientSupports
|
|
23
|
+
} from "./chunk-BEKSXO5N.js";
|
|
24
|
+
import {
|
|
25
|
+
installHooks
|
|
26
|
+
} from "./chunk-YDZJRLHL.js";
|
|
27
27
|
import {
|
|
28
28
|
t
|
|
29
29
|
} from "./chunk-6ICJICVU.js";
|
|
@@ -34,14 +34,18 @@ import * as childProcess from "child_process";
|
|
|
34
34
|
import { chmodSync, copyFileSync, existsSync as existsSync2, mkdirSync, readFileSync as readFileSync2, renameSync, rmSync, statSync as statSync2, writeFileSync } from "fs";
|
|
35
35
|
import { dirname, isAbsolute as isAbsolute2, join as join2, parse, resolve as resolve2 } from "path";
|
|
36
36
|
import { fileURLToPath } from "url";
|
|
37
|
+
import { cancel, confirm, group, intro, isCancel, log, note, outro, select } from "@clack/prompts";
|
|
37
38
|
import { defineCommand } from "citty";
|
|
38
39
|
|
|
39
40
|
// src/scanner/forensic.ts
|
|
41
|
+
import { execFileSync } from "child_process";
|
|
40
42
|
import { existsSync, readdirSync, readFileSync, statSync } from "fs";
|
|
43
|
+
import { createRequire } from "module";
|
|
41
44
|
import { basename, extname, isAbsolute, join, posix, relative, resolve, sep } from "path";
|
|
42
45
|
import {
|
|
43
46
|
forensicReportSchema
|
|
44
47
|
} from "@fenglimg/fabric-shared";
|
|
48
|
+
var require2 = createRequire(import.meta.url);
|
|
45
49
|
var IGNORED_DIRECTORIES = /* @__PURE__ */ new Set([
|
|
46
50
|
".fabric",
|
|
47
51
|
".git",
|
|
@@ -67,9 +71,48 @@ var SCRIPT_EXTENSIONS = /* @__PURE__ */ new Set([".js", ".jsx", ".ts", ".tsx"]);
|
|
|
67
71
|
var DOMAIN_FILE_EXTENSIONS = /* @__PURE__ */ new Set([".js", ".jsx", ".ts", ".tsx", ".json", ".md"]);
|
|
68
72
|
var EXPECTED_CONFIG_FILES_BY_FRAMEWORK = {
|
|
69
73
|
"cocos-creator": ["package.json", "project.config.json", "tsconfig.json"],
|
|
74
|
+
react: ["package.json", "tsconfig.json"],
|
|
70
75
|
next: ["package.json", "tsconfig.json"],
|
|
71
76
|
vite: ["package.json", "tsconfig.json"]
|
|
72
77
|
};
|
|
78
|
+
var FRAMEWORK_IMPORT_PROFILES = {
|
|
79
|
+
"cocos-creator": {
|
|
80
|
+
pattern: "cocos-component-class",
|
|
81
|
+
family: "component",
|
|
82
|
+
statement: "Sampled entry files use Cocos Creator component classes.",
|
|
83
|
+
proposedRule: "Treat assets/scripts/*.ts and adjacent .meta files as framework-owned structure unless the user says otherwise.",
|
|
84
|
+
alternatives: ["Generic TypeScript utility module"],
|
|
85
|
+
rationale: "Cocos framework imports and component markers co-occur in sampled entry files.",
|
|
86
|
+
packages: ["cc"]
|
|
87
|
+
},
|
|
88
|
+
react: {
|
|
89
|
+
pattern: "react-root",
|
|
90
|
+
family: "entry",
|
|
91
|
+
statement: "Sampled entry files import React framework packages.",
|
|
92
|
+
proposedRule: "Keep root rendering and component composition aligned with React entry conventions.",
|
|
93
|
+
alternatives: ["Server-rendered route module"],
|
|
94
|
+
rationale: "AST import declarations reference React packages rather than comments or strings.",
|
|
95
|
+
packages: ["react", "react-dom", "react/jsx-runtime", "react-dom/client"]
|
|
96
|
+
},
|
|
97
|
+
vite: {
|
|
98
|
+
pattern: "vite-main-entry",
|
|
99
|
+
family: "entry",
|
|
100
|
+
statement: "Sampled entry files use the conventional Vite main entrypoint.",
|
|
101
|
+
proposedRule: "Keep primary bootstrapping logic inside src/main.*.",
|
|
102
|
+
alternatives: ["Alternative bundler entrypoint"],
|
|
103
|
+
rationale: "Entry path and framework imports align with a Vite bootstrap surface.",
|
|
104
|
+
packages: ["@vitejs/plugin-react", "@vitejs/plugin-vue", "vite", "react", "vue"]
|
|
105
|
+
},
|
|
106
|
+
next: {
|
|
107
|
+
pattern: "next-route-component",
|
|
108
|
+
family: "entry",
|
|
109
|
+
statement: "Sampled entry files align with Next.js route modules.",
|
|
110
|
+
proposedRule: "Preserve route-segment boundaries when editing app/ or pages/ files.",
|
|
111
|
+
alternatives: ["Generic source module"],
|
|
112
|
+
rationale: "Route placement and Next/React imports anchor these files to the request surface.",
|
|
113
|
+
packages: ["next", "next/link", "next/navigation", "react"]
|
|
114
|
+
}
|
|
115
|
+
};
|
|
73
116
|
var SAMPLE_LIMIT = 5;
|
|
74
117
|
var SAMPLE_LINE_LIMIT = 30;
|
|
75
118
|
var ENTRY_FAMILY_LIMIT = 1;
|
|
@@ -79,12 +122,17 @@ var DEFAULT_SAMPLING_BUDGET = {
|
|
|
79
122
|
max_files: 15,
|
|
80
123
|
max_lines_per_file: 100
|
|
81
124
|
};
|
|
82
|
-
|
|
125
|
+
var treeSitterModulePromise = null;
|
|
126
|
+
var parserInitPromise = null;
|
|
127
|
+
var languagePromiseByKind = {};
|
|
128
|
+
var parserBundlePromiseByKind = {};
|
|
129
|
+
async function buildForensicReport(targetInput) {
|
|
83
130
|
const target = normalizeTarget(targetInput);
|
|
84
131
|
const framework = detectFramework(target);
|
|
85
132
|
const topology = buildTopology(target);
|
|
86
|
-
const entryPoints = collectEntryPoints(topology.files);
|
|
87
|
-
const
|
|
133
|
+
const entryPoints = collectEntryPoints(target, topology.files);
|
|
134
|
+
const packageDependencies = readPackageDependencies(target);
|
|
135
|
+
const codeSamples = await buildCodeSamples(target, entryPoints, framework.kind, topology, packageDependencies);
|
|
88
136
|
const assertions = buildAssertions(framework.kind, topology, codeSamples);
|
|
89
137
|
const candidateFiles = buildCandidateFiles(topology, codeSamples, entryPoints);
|
|
90
138
|
const readme = readReadmeInfo(target);
|
|
@@ -179,7 +227,7 @@ function isKeyDirectory(relativePath) {
|
|
|
179
227
|
const name = basename(relativePath);
|
|
180
228
|
return KEY_DIRECTORY_NAMES.has(name);
|
|
181
229
|
}
|
|
182
|
-
function collectEntryPoints(files) {
|
|
230
|
+
function collectEntryPoints(target, files) {
|
|
183
231
|
const entryPoints = [];
|
|
184
232
|
for (const file of files) {
|
|
185
233
|
const reason = getEntryPointReason(file.relativePath);
|
|
@@ -192,7 +240,9 @@ function collectEntryPoints(files) {
|
|
|
192
240
|
size_bytes: file.sizeBytes
|
|
193
241
|
});
|
|
194
242
|
}
|
|
195
|
-
return entryPoints
|
|
243
|
+
return entryPoints.sort(
|
|
244
|
+
(left, right) => compareCandidateScore(readGitChurnWeight(target, right.path), readGitChurnWeight(target, left.path))
|
|
245
|
+
);
|
|
196
246
|
}
|
|
197
247
|
function getEntryPointReason(relativePath) {
|
|
198
248
|
if (!SCRIPT_EXTENSIONS.has(extname(relativePath))) {
|
|
@@ -215,20 +265,26 @@ function getEntryPointReason(relativePath) {
|
|
|
215
265
|
}
|
|
216
266
|
return null;
|
|
217
267
|
}
|
|
218
|
-
function buildCodeSamples(target, entryPoints) {
|
|
219
|
-
|
|
268
|
+
async function buildCodeSamples(target, entryPoints, frameworkKind, topology, packageDependencies) {
|
|
269
|
+
const samples = [];
|
|
270
|
+
for (const entryPoint of entryPoints.slice(0, SAMPLE_LIMIT)) {
|
|
220
271
|
const absolutePath = join(target, ...entryPoint.path.split("/"));
|
|
221
272
|
const sample = readFirstLines(absolutePath, SAMPLE_LINE_LIMIT);
|
|
222
|
-
const patternAnalysis = inferPatternHint(entryPoint.path, sample.snippet
|
|
223
|
-
|
|
273
|
+
const patternAnalysis = await inferPatternHint(entryPoint.path, sample.snippet, {
|
|
274
|
+
frameworkKind,
|
|
275
|
+
topology,
|
|
276
|
+
packageDependencies
|
|
277
|
+
});
|
|
278
|
+
samples.push({
|
|
224
279
|
path: entryPoint.path,
|
|
225
280
|
lines: `1-${sample.lineCount}`,
|
|
226
281
|
snippet: sample.snippet,
|
|
227
282
|
pattern_hint: patternAnalysis.pattern,
|
|
228
283
|
pattern_analysis: patternAnalysis,
|
|
229
284
|
evidence: buildEvidenceAnchors(entryPoint.path, sample.snippet, patternAnalysis.evidence_lines)
|
|
230
|
-
};
|
|
231
|
-
}
|
|
285
|
+
});
|
|
286
|
+
}
|
|
287
|
+
return samples;
|
|
232
288
|
}
|
|
233
289
|
function readFirstLines(path, lineLimit) {
|
|
234
290
|
try {
|
|
@@ -248,7 +304,100 @@ function readFirstLines(path, lineLimit) {
|
|
|
248
304
|
};
|
|
249
305
|
}
|
|
250
306
|
}
|
|
251
|
-
function
|
|
307
|
+
function readPackageDependencies(target) {
|
|
308
|
+
const packageJsonPath = join(target, "package.json");
|
|
309
|
+
if (!existsSync(packageJsonPath)) {
|
|
310
|
+
return /* @__PURE__ */ new Map();
|
|
311
|
+
}
|
|
312
|
+
try {
|
|
313
|
+
const packageJson = JSON.parse(readFileSync(packageJsonPath, "utf8"));
|
|
314
|
+
return new Map([
|
|
315
|
+
...Object.entries(packageJson.dependencies ?? {}),
|
|
316
|
+
...Object.entries(packageJson.devDependencies ?? {}),
|
|
317
|
+
...Object.entries(packageJson.peerDependencies ?? {}),
|
|
318
|
+
...Object.entries(packageJson.optionalDependencies ?? {})
|
|
319
|
+
]);
|
|
320
|
+
} catch {
|
|
321
|
+
return /* @__PURE__ */ new Map();
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
function readGitChurnWeight(target, relativePath) {
|
|
325
|
+
try {
|
|
326
|
+
const output = execFileSync("git", ["log", "--follow", "--oneline", "-20", "--", relativePath], {
|
|
327
|
+
cwd: target,
|
|
328
|
+
encoding: "utf8",
|
|
329
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
330
|
+
timeout: 1e3
|
|
331
|
+
});
|
|
332
|
+
return output.split(/\r?\n/).filter((line) => line.trim().length > 0).length;
|
|
333
|
+
} catch {
|
|
334
|
+
return 0;
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
async function inferPatternHint(relativePath, snippet, options = {}) {
|
|
338
|
+
const input = {
|
|
339
|
+
relativePath,
|
|
340
|
+
snippet,
|
|
341
|
+
frameworkKind: options.frameworkKind ?? "unknown",
|
|
342
|
+
topology: options.topology ?? createEmptyTopology(),
|
|
343
|
+
packageDependencies: options.packageDependencies ?? /* @__PURE__ */ new Map()
|
|
344
|
+
};
|
|
345
|
+
const importAnalysis = await analyzeImports(input.relativePath, input.snippet);
|
|
346
|
+
if (importAnalysis.astLevel) {
|
|
347
|
+
const astResult = buildAstPatternHint(input, importAnalysis.imports);
|
|
348
|
+
if (astResult !== null) {
|
|
349
|
+
return astResult;
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
return inferTextPatternHint(input.relativePath, input.snippet);
|
|
353
|
+
}
|
|
354
|
+
function createEmptyTopology() {
|
|
355
|
+
return {
|
|
356
|
+
total_files: 0,
|
|
357
|
+
by_ext: {},
|
|
358
|
+
key_dirs: [],
|
|
359
|
+
max_depth: 0,
|
|
360
|
+
files: []
|
|
361
|
+
};
|
|
362
|
+
}
|
|
363
|
+
function buildAstPatternHint(input, imports) {
|
|
364
|
+
const profile = resolveFrameworkImportProfile(input.frameworkKind, input.relativePath, imports);
|
|
365
|
+
if (profile === null) {
|
|
366
|
+
return null;
|
|
367
|
+
}
|
|
368
|
+
const matchingImports = imports.filter((source) => matchesAnyFrameworkPackage(source, profile.packages));
|
|
369
|
+
const configFiles = getExpectedConfigFiles(input.frameworkKind).filter((file) => hasFile(input.topology.files, file));
|
|
370
|
+
const packageMatches = profile.packages.filter((packageName) => input.packageDependencies.has(packageName));
|
|
371
|
+
const coOccurring = compactPatternNames([
|
|
372
|
+
...matchingImports.map((source) => `import:${source}`),
|
|
373
|
+
...configFiles.map(normalizeConfigPattern),
|
|
374
|
+
...packageMatches.map((packageName) => `package:${packageName}`),
|
|
375
|
+
input.relativePath.startsWith("app/") ? "app-router" : null,
|
|
376
|
+
input.relativePath.startsWith("pages/") ? "pages-router" : null,
|
|
377
|
+
input.relativePath === "src/main.ts" || input.relativePath === "src/main.js" ? "main-entry" : null,
|
|
378
|
+
input.snippet.includes("@ccclass(") ? "ccclass-decorator" : null,
|
|
379
|
+
input.snippet.includes("extends Component") ? "component-base" : null
|
|
380
|
+
]);
|
|
381
|
+
return {
|
|
382
|
+
pattern: profile.pattern,
|
|
383
|
+
type: "pattern",
|
|
384
|
+
confidence: scoreFrameworkConfidence({
|
|
385
|
+
importCount: matchingImports.length,
|
|
386
|
+
configCount: configFiles.length,
|
|
387
|
+
packageCount: packageMatches.length,
|
|
388
|
+
astLevel: true
|
|
389
|
+
}),
|
|
390
|
+
evidence_lines: matchingImports.length > 0 ? matchingImports : imports.slice(0, 3),
|
|
391
|
+
co_occurring: coOccurring,
|
|
392
|
+
family: profile.family,
|
|
393
|
+
ast_level: true,
|
|
394
|
+
statement: profile.statement,
|
|
395
|
+
proposed_rule: profile.proposedRule,
|
|
396
|
+
alternatives: profile.alternatives,
|
|
397
|
+
rationale: profile.rationale
|
|
398
|
+
};
|
|
399
|
+
}
|
|
400
|
+
function inferTextPatternHint(relativePath, snippet) {
|
|
252
401
|
const cocosCoOccurring = compactPatternNames([
|
|
253
402
|
snippet.includes('from "cc"') || snippet.includes("from 'cc'") ? "cc-import" : null,
|
|
254
403
|
snippet.includes("@ccclass(") || snippet.includes("ccclass(") ? "ccclass-decorator" : null,
|
|
@@ -256,11 +405,16 @@ function inferPatternHint(relativePath, snippet) {
|
|
|
256
405
|
snippet.includes("const { ccclass } = _decorator") ? "decorator-destructure" : null
|
|
257
406
|
]);
|
|
258
407
|
if (cocosCoOccurring.length > 0) {
|
|
259
|
-
const astLevel = snippet.includes("@ccclass(");
|
|
260
408
|
return {
|
|
261
409
|
pattern: "cocos-component-class",
|
|
262
410
|
type: "pattern",
|
|
263
|
-
confidence:
|
|
411
|
+
confidence: scoreFrameworkConfidence({
|
|
412
|
+
importCount: 0,
|
|
413
|
+
configCount: 0,
|
|
414
|
+
packageCount: 0,
|
|
415
|
+
astLevel: false,
|
|
416
|
+
keywordCount: cocosCoOccurring.length
|
|
417
|
+
}),
|
|
264
418
|
evidence_lines: compactPatternNames([
|
|
265
419
|
snippet.includes("_decorator") ? "_decorator" : null,
|
|
266
420
|
snippet.includes("@ccclass(") ? "@ccclass(" : null,
|
|
@@ -268,7 +422,7 @@ function inferPatternHint(relativePath, snippet) {
|
|
|
268
422
|
]),
|
|
269
423
|
co_occurring: cocosCoOccurring,
|
|
270
424
|
family: "component",
|
|
271
|
-
ast_level:
|
|
425
|
+
ast_level: false,
|
|
272
426
|
statement: "Sampled entry files use Cocos Creator component classes.",
|
|
273
427
|
proposed_rule: "Treat assets/scripts/*.ts and adjacent .meta files as framework-owned structure unless the user says otherwise.",
|
|
274
428
|
alternatives: ["Generic TypeScript utility module"],
|
|
@@ -284,7 +438,13 @@ function inferPatternHint(relativePath, snippet) {
|
|
|
284
438
|
return {
|
|
285
439
|
pattern: "react-root",
|
|
286
440
|
type: "pattern",
|
|
287
|
-
confidence:
|
|
441
|
+
confidence: scoreFrameworkConfidence({
|
|
442
|
+
importCount: 0,
|
|
443
|
+
configCount: 0,
|
|
444
|
+
packageCount: 0,
|
|
445
|
+
astLevel: false,
|
|
446
|
+
keywordCount: reactCoOccurring.length
|
|
447
|
+
}),
|
|
288
448
|
evidence_lines: compactPatternNames([
|
|
289
449
|
snippet.includes("createRoot(") ? "createRoot(" : null,
|
|
290
450
|
snippet.includes("ReactDOM.render(") ? "ReactDOM.render(" : null
|
|
@@ -307,7 +467,13 @@ function inferPatternHint(relativePath, snippet) {
|
|
|
307
467
|
return {
|
|
308
468
|
pattern: "next-route-component",
|
|
309
469
|
type: "pattern",
|
|
310
|
-
confidence:
|
|
470
|
+
confidence: scoreFrameworkConfidence({
|
|
471
|
+
importCount: 0,
|
|
472
|
+
configCount: 0,
|
|
473
|
+
packageCount: 0,
|
|
474
|
+
astLevel: false,
|
|
475
|
+
keywordCount: coOccurring.length
|
|
476
|
+
}),
|
|
311
477
|
evidence_lines: compactPatternNames([
|
|
312
478
|
relativePath.startsWith("app/") ? "app/" : null,
|
|
313
479
|
relativePath.startsWith("pages/") ? "pages/" : null
|
|
@@ -330,7 +496,13 @@ function inferPatternHint(relativePath, snippet) {
|
|
|
330
496
|
return {
|
|
331
497
|
pattern: "vite-main-entry",
|
|
332
498
|
type: "pattern",
|
|
333
|
-
confidence:
|
|
499
|
+
confidence: scoreFrameworkConfidence({
|
|
500
|
+
importCount: 0,
|
|
501
|
+
configCount: 0,
|
|
502
|
+
packageCount: 0,
|
|
503
|
+
astLevel: false,
|
|
504
|
+
keywordCount: coOccurring.length
|
|
505
|
+
}),
|
|
334
506
|
evidence_lines: ["src/main"],
|
|
335
507
|
co_occurring: coOccurring,
|
|
336
508
|
family: "entry",
|
|
@@ -354,6 +526,125 @@ function inferPatternHint(relativePath, snippet) {
|
|
|
354
526
|
rationale: "No strong framework markers were detected in the sampled snippet."
|
|
355
527
|
};
|
|
356
528
|
}
|
|
529
|
+
async function analyzeImports(relativePath, snippet) {
|
|
530
|
+
if (snippet.trim().length === 0) {
|
|
531
|
+
return { imports: [], astLevel: false };
|
|
532
|
+
}
|
|
533
|
+
try {
|
|
534
|
+
const imports = await extractImports(snippet, getLanguageKindForPath(relativePath));
|
|
535
|
+
return { imports, astLevel: true };
|
|
536
|
+
} catch {
|
|
537
|
+
return { imports: [], astLevel: false };
|
|
538
|
+
}
|
|
539
|
+
}
|
|
540
|
+
async function extractImports(source, languageKind) {
|
|
541
|
+
const { parser } = await loadTreeSitter(languageKind);
|
|
542
|
+
let tree = null;
|
|
543
|
+
try {
|
|
544
|
+
tree = parser.parse(source);
|
|
545
|
+
if (tree === null || tree.rootNode.hasError) {
|
|
546
|
+
throw new Error("tree-sitter parse failed");
|
|
547
|
+
}
|
|
548
|
+
const imports = [];
|
|
549
|
+
collectImportSources(tree.rootNode, imports);
|
|
550
|
+
return compactPatternNames(imports);
|
|
551
|
+
} finally {
|
|
552
|
+
tree?.delete();
|
|
553
|
+
}
|
|
554
|
+
}
|
|
555
|
+
async function loadTreeSitter(languageKind) {
|
|
556
|
+
parserBundlePromiseByKind[languageKind] ??= createTreeSitterParserBundle(languageKind);
|
|
557
|
+
return parserBundlePromiseByKind[languageKind];
|
|
558
|
+
}
|
|
559
|
+
async function createTreeSitterParserBundle(languageKind) {
|
|
560
|
+
const treeSitter = await loadTreeSitterModule();
|
|
561
|
+
await initTreeSitterParser(treeSitter);
|
|
562
|
+
const language = await loadTreeSitterLanguage(treeSitter, languageKind);
|
|
563
|
+
const parser = new treeSitter.Parser();
|
|
564
|
+
parser.setLanguage(language);
|
|
565
|
+
return { parser, language };
|
|
566
|
+
}
|
|
567
|
+
function loadTreeSitterModule() {
|
|
568
|
+
treeSitterModulePromise ??= import("web-tree-sitter");
|
|
569
|
+
return treeSitterModulePromise;
|
|
570
|
+
}
|
|
571
|
+
function initTreeSitterParser(treeSitter) {
|
|
572
|
+
parserInitPromise ??= treeSitter.Parser.init({
|
|
573
|
+
locateFile: (scriptName) => scriptName.endsWith(".wasm") ? require2.resolve("web-tree-sitter/web-tree-sitter.wasm") : scriptName
|
|
574
|
+
});
|
|
575
|
+
return parserInitPromise;
|
|
576
|
+
}
|
|
577
|
+
function loadTreeSitterLanguage(treeSitter, languageKind) {
|
|
578
|
+
languagePromiseByKind[languageKind] ??= treeSitter.Language.load(resolveTreeSitterGrammarPath(languageKind));
|
|
579
|
+
return languagePromiseByKind[languageKind];
|
|
580
|
+
}
|
|
581
|
+
function resolveTreeSitterGrammarPath(languageKind) {
|
|
582
|
+
switch (languageKind) {
|
|
583
|
+
case "typescript":
|
|
584
|
+
return require2.resolve("tree-sitter-typescript/tree-sitter-typescript.wasm");
|
|
585
|
+
case "tsx":
|
|
586
|
+
return require2.resolve("tree-sitter-typescript/tree-sitter-tsx.wasm");
|
|
587
|
+
case "javascript":
|
|
588
|
+
return require2.resolve("tree-sitter-javascript/tree-sitter-javascript.wasm");
|
|
589
|
+
}
|
|
590
|
+
}
|
|
591
|
+
function getLanguageKindForPath(relativePath) {
|
|
592
|
+
const extension = extname(relativePath);
|
|
593
|
+
if (extension === ".tsx") {
|
|
594
|
+
return "tsx";
|
|
595
|
+
}
|
|
596
|
+
if (extension === ".ts") {
|
|
597
|
+
return "typescript";
|
|
598
|
+
}
|
|
599
|
+
return "javascript";
|
|
600
|
+
}
|
|
601
|
+
function collectImportSources(node, imports) {
|
|
602
|
+
if (node.type === "import_statement" || node.type === "import_declaration") {
|
|
603
|
+
const sourceNode = node.childForFieldName("source");
|
|
604
|
+
if (sourceNode !== null) {
|
|
605
|
+
const source = stripStringLiteral(sourceNode.text);
|
|
606
|
+
if (source.length > 0) {
|
|
607
|
+
imports.push(source);
|
|
608
|
+
}
|
|
609
|
+
}
|
|
610
|
+
}
|
|
611
|
+
for (let index = 0; index < node.namedChildCount; index += 1) {
|
|
612
|
+
const child = node.namedChild(index);
|
|
613
|
+
if (child !== null) {
|
|
614
|
+
collectImportSources(child, imports);
|
|
615
|
+
}
|
|
616
|
+
}
|
|
617
|
+
}
|
|
618
|
+
function stripStringLiteral(value) {
|
|
619
|
+
return value.replace(/^['"]|['"]$/g, "");
|
|
620
|
+
}
|
|
621
|
+
function resolveFrameworkImportProfile(frameworkKind, relativePath, imports) {
|
|
622
|
+
const primaryProfile = FRAMEWORK_IMPORT_PROFILES[frameworkKind];
|
|
623
|
+
if (primaryProfile !== void 0 && imports.some((source) => matchesAnyFrameworkPackage(source, primaryProfile.packages))) {
|
|
624
|
+
return primaryProfile;
|
|
625
|
+
}
|
|
626
|
+
if ((relativePath.startsWith("app/") || relativePath.startsWith("pages/")) && FRAMEWORK_IMPORT_PROFILES.next !== void 0) {
|
|
627
|
+
return FRAMEWORK_IMPORT_PROFILES.next;
|
|
628
|
+
}
|
|
629
|
+
return Object.values(FRAMEWORK_IMPORT_PROFILES).find(
|
|
630
|
+
(profile) => imports.some((source) => matchesAnyFrameworkPackage(source, profile.packages))
|
|
631
|
+
) ?? null;
|
|
632
|
+
}
|
|
633
|
+
function matchesAnyFrameworkPackage(source, packageNames) {
|
|
634
|
+
return packageNames.some((packageName) => source === packageName || source.startsWith(`${packageName}/`));
|
|
635
|
+
}
|
|
636
|
+
function scoreFrameworkConfidence(input) {
|
|
637
|
+
if (!input.astLevel) {
|
|
638
|
+
return (input.keywordCount ?? 0) > 0 ? "MEDIUM" : "LOW";
|
|
639
|
+
}
|
|
640
|
+
if (input.importCount > 3) {
|
|
641
|
+
return "HIGH";
|
|
642
|
+
}
|
|
643
|
+
if (input.importCount >= 1 && input.importCount <= 3) {
|
|
644
|
+
return input.configCount > 0 || input.packageCount > 0 ? "MEDIUM" : "MEDIUM";
|
|
645
|
+
}
|
|
646
|
+
return input.configCount > 0 || input.packageCount > 0 ? "MEDIUM" : "LOW";
|
|
647
|
+
}
|
|
357
648
|
function readReadmeInfo(target) {
|
|
358
649
|
const readmePath = join(target, "README.md");
|
|
359
650
|
const hasContributing = existsSync(join(target, "CONTRIBUTING.md"));
|
|
@@ -856,7 +1147,7 @@ function readProjectName(target) {
|
|
|
856
1147
|
return basename(target);
|
|
857
1148
|
}
|
|
858
1149
|
function getCliVersion() {
|
|
859
|
-
return true ? "1.
|
|
1150
|
+
return true ? "1.5.0" : "unknown";
|
|
860
1151
|
}
|
|
861
1152
|
function sortRecord(record) {
|
|
862
1153
|
return Object.fromEntries(Object.entries(record).sort(([left], [right]) => left.localeCompare(right)));
|
|
@@ -869,8 +1160,14 @@ function toPosixPath(path) {
|
|
|
869
1160
|
var CLAUDE_INIT_SKILL_TEMPLATE = "templates/claude-skills/agents-md-init/SKILL.md";
|
|
870
1161
|
var CLAUDE_INIT_REMINDER_HOOK_TEMPLATE = "templates/claude-hooks/agents-md-init-reminder.cjs";
|
|
871
1162
|
var CLAUDE_INIT_REMINDER_COMMAND = ".claude/hooks/agents-md-init-reminder.cjs";
|
|
1163
|
+
var CODEX_INIT_SKILL_TEMPLATE = "templates/codex-skills/fabric-init/SKILL.md";
|
|
1164
|
+
var CODEX_SESSION_START_HOOK_TEMPLATE = "templates/codex-hooks/fabric-session-start.cjs";
|
|
1165
|
+
var CODEX_STOP_HOOK_TEMPLATE = "templates/codex-hooks/fabric-stop-reminder.cjs";
|
|
1166
|
+
var CODEX_SESSION_START_COMMAND = ".codex/hooks/fabric-session-start.cjs";
|
|
1167
|
+
var CODEX_STOP_COMMAND = ".codex/hooks/fabric-stop-reminder.cjs";
|
|
872
1168
|
var LOCAL_FABRIC_SERVER_PATH = join2("node_modules", "@fenglimg", "fabric-server", "dist", "index.js");
|
|
873
1169
|
var FABRIC_SERVER_PACKAGE = "@fenglimg/fabric-server";
|
|
1170
|
+
var INIT_WIZARD_GROUP_CANCELLED = /* @__PURE__ */ Symbol("init-wizard-group-cancelled");
|
|
874
1171
|
var initCommand = defineCommand({
|
|
875
1172
|
meta: {
|
|
876
1173
|
name: "init",
|
|
@@ -891,6 +1188,21 @@ var initCommand = defineCommand({
|
|
|
891
1188
|
description: t("cli.init.args.force.description"),
|
|
892
1189
|
default: false
|
|
893
1190
|
},
|
|
1191
|
+
yes: {
|
|
1192
|
+
type: "boolean",
|
|
1193
|
+
description: t("cli.init.args.yes.description"),
|
|
1194
|
+
default: false
|
|
1195
|
+
},
|
|
1196
|
+
plan: {
|
|
1197
|
+
type: "boolean",
|
|
1198
|
+
description: t("cli.init.args.plan.description"),
|
|
1199
|
+
default: false
|
|
1200
|
+
},
|
|
1201
|
+
reapply: {
|
|
1202
|
+
type: "boolean",
|
|
1203
|
+
description: t("cli.init.args.reapply.description"),
|
|
1204
|
+
default: false
|
|
1205
|
+
},
|
|
894
1206
|
bootstrap: {
|
|
895
1207
|
type: "boolean",
|
|
896
1208
|
default: true,
|
|
@@ -918,178 +1230,727 @@ var initCommand = defineCommand({
|
|
|
918
1230
|
}
|
|
919
1231
|
},
|
|
920
1232
|
async run({ args }) {
|
|
921
|
-
|
|
922
|
-
const resolution = resolveDevMode(args.target, process.cwd());
|
|
923
|
-
const target = normalizeTarget2(resolution.target);
|
|
924
|
-
const mcpInstallMode = resolveMcpInstallMode(args["mcp-install"]);
|
|
925
|
-
const options = {
|
|
926
|
-
force: args.force,
|
|
927
|
-
skipBootstrap: args.bootstrap === false ? true : args.skipBootstrap,
|
|
928
|
-
skipMcp: args.mcp === false ? true : args.skipMcp,
|
|
929
|
-
skipHooks: args.hooks === false ? true : args.skipHooks
|
|
930
|
-
};
|
|
931
|
-
logger(`init target source: ${resolution.source}`);
|
|
932
|
-
for (const step of resolution.chain) {
|
|
933
|
-
logger(step);
|
|
934
|
-
}
|
|
935
|
-
const supports = detectClientSupports(target);
|
|
936
|
-
const interactive = args.interactive !== false && isInteractiveInit();
|
|
937
|
-
if (options.force) {
|
|
938
|
-
writeStderr(t("cli.init.force.warning", { path: target }));
|
|
939
|
-
}
|
|
940
|
-
if (interactive) {
|
|
941
|
-
printInitPlanSummary(target, options, mcpInstallMode, supports);
|
|
942
|
-
}
|
|
943
|
-
const created = initFabric(target, options);
|
|
944
|
-
console.log(formatInitPathAction(created.bootstrapPath, created.bootstrapAction));
|
|
945
|
-
console.log(formatInitPathAction(created.metaPath, created.metaAction));
|
|
946
|
-
console.log(formatInitPathAction(created.humanLockPath, created.humanLockAction));
|
|
947
|
-
console.log(formatInitPathAction(created.forensicPath, created.forensicAction));
|
|
948
|
-
writeStderr(
|
|
949
|
-
formatOptionalInitPathAction(created.claudeSkillPath, created.claudeSkillAction)
|
|
950
|
-
);
|
|
951
|
-
writeStderr(
|
|
952
|
-
formatOptionalInitPathAction(created.claudeHookPath, created.claudeHookAction)
|
|
953
|
-
);
|
|
954
|
-
writeStderr(formatClaudeSettingsAction(created.claudeSettingsPath, created.claudeSettingsAction));
|
|
955
|
-
const stageResults = [];
|
|
956
|
-
if (options.skipBootstrap) {
|
|
957
|
-
stageResults.push({ name: "bootstrap", disposition: "skipped" });
|
|
958
|
-
} else {
|
|
959
|
-
console.log(formatInitStageHeader(t("cli.init.stages.bootstrap")));
|
|
960
|
-
try {
|
|
961
|
-
const result = await installBootstrap(target, { force: options.force });
|
|
962
|
-
if (result.details.length === 0) {
|
|
963
|
-
console.log(formatInitStageResult("bootstrap", "skipped", 0, 0, t("cli.bootstrap.install.no-targets")));
|
|
964
|
-
stageResults.push({ name: "bootstrap", disposition: "skipped" });
|
|
965
|
-
} else {
|
|
966
|
-
console.log(
|
|
967
|
-
formatInitStageResult("bootstrap", "completed", result.installed.length, result.skipped.length)
|
|
968
|
-
);
|
|
969
|
-
stageResults.push({ name: "bootstrap", disposition: "ran" });
|
|
970
|
-
}
|
|
971
|
-
} catch (error) {
|
|
972
|
-
writeStderr(formatInitStageFailure("bootstrap", error));
|
|
973
|
-
stageResults.push({ name: "bootstrap", disposition: "failed" });
|
|
974
|
-
}
|
|
975
|
-
}
|
|
976
|
-
if (options.skipMcp) {
|
|
977
|
-
stageResults.push({ name: "mcp", disposition: "skipped" });
|
|
978
|
-
} else {
|
|
979
|
-
console.log(formatInitStageHeader(t("cli.init.stages.mcp")));
|
|
980
|
-
try {
|
|
981
|
-
let localServerPath;
|
|
982
|
-
if (mcpInstallMode === "local") {
|
|
983
|
-
const manager = detectPackageManager(target);
|
|
984
|
-
writeStderr(t("cli.init.mcp.install.local"));
|
|
985
|
-
writeStderr(t("cli.init.mcp.local.installing", { manager }));
|
|
986
|
-
installLocalFabricServer(target, manager);
|
|
987
|
-
writeStderr(t("cli.init.mcp.local.installed"));
|
|
988
|
-
localServerPath = LOCAL_FABRIC_SERVER_PATH;
|
|
989
|
-
} else {
|
|
990
|
-
writeStderr(t("cli.init.mcp.install.global"));
|
|
991
|
-
}
|
|
992
|
-
const result = await installMcpClients(target, {
|
|
993
|
-
force: options.force,
|
|
994
|
-
localServerPath
|
|
995
|
-
});
|
|
996
|
-
if (result.details.length === 0) {
|
|
997
|
-
console.log(formatInitStageResult("mcp", "skipped", 0, 0, t("cli.config.install.no-configs")));
|
|
998
|
-
stageResults.push({ name: "mcp", disposition: "skipped" });
|
|
999
|
-
} else {
|
|
1000
|
-
console.log(formatInitStageResult("mcp", "completed", result.installed.length, result.skipped.length));
|
|
1001
|
-
stageResults.push({ name: "mcp", disposition: "ran" });
|
|
1002
|
-
}
|
|
1003
|
-
} catch (error) {
|
|
1004
|
-
writeStderr(formatInitStageFailure("mcp", error));
|
|
1005
|
-
stageResults.push({ name: "mcp", disposition: "failed" });
|
|
1006
|
-
}
|
|
1007
|
-
}
|
|
1008
|
-
if (options.skipHooks) {
|
|
1009
|
-
stageResults.push({ name: "hooks", disposition: "skipped" });
|
|
1010
|
-
} else {
|
|
1011
|
-
console.log(formatInitStageHeader(t("cli.init.stages.hooks")));
|
|
1012
|
-
try {
|
|
1013
|
-
const result = await installHooks(target, { force: options.force });
|
|
1014
|
-
console.log(formatInitStageResult("hooks", "completed", result.installed.length, result.skipped.length));
|
|
1015
|
-
stageResults.push({ name: "hooks", disposition: "ran" });
|
|
1016
|
-
} catch (error) {
|
|
1017
|
-
writeStderr(formatInitStageFailure("hooks", error));
|
|
1018
|
-
stageResults.push({ name: "hooks", disposition: "failed" });
|
|
1019
|
-
}
|
|
1020
|
-
}
|
|
1021
|
-
if (shouldPrintHooksNextStep(options, stageResults)) {
|
|
1022
|
-
console.log(
|
|
1023
|
-
t("cli.init.next-step", {
|
|
1024
|
-
label: nextLabel(),
|
|
1025
|
-
message: paint.muted(t("cli.init.next-step.message"))
|
|
1026
|
-
})
|
|
1027
|
-
);
|
|
1028
|
-
}
|
|
1029
|
-
console.log(
|
|
1030
|
-
t("cli.init.reason-message", {
|
|
1031
|
-
label: reasonLabel(),
|
|
1032
|
-
message: paint.muted(formatInitReasonMessage(supports))
|
|
1033
|
-
})
|
|
1034
|
-
);
|
|
1035
|
-
printInitStageSummary(stageResults);
|
|
1036
|
-
printInitCapabilitySummary(supports, stageResults, options);
|
|
1233
|
+
await runInitCommand(args);
|
|
1037
1234
|
}
|
|
1038
1235
|
});
|
|
1039
1236
|
var init_default = initCommand;
|
|
1040
|
-
function
|
|
1237
|
+
async function runInitCommand(args) {
|
|
1238
|
+
const logger = createDebugLogger(args.debug);
|
|
1239
|
+
const resolution = resolveDevMode(args.target, process.cwd());
|
|
1240
|
+
const intent = resolveInitCliIntent(args, resolution.target);
|
|
1241
|
+
logger(`init target source: ${resolution.source}`);
|
|
1242
|
+
for (const step of resolution.chain) {
|
|
1243
|
+
logger(step);
|
|
1244
|
+
}
|
|
1245
|
+
if (intent.options.planOnly) {
|
|
1246
|
+
writeStderr(t("cli.init.compat.plan"));
|
|
1247
|
+
}
|
|
1248
|
+
if (args.interactive === false) {
|
|
1249
|
+
writeStderr(t("cli.init.compat.interactive"));
|
|
1250
|
+
}
|
|
1251
|
+
if (args.bootstrap === false || args.mcp === false || args.hooks === false) {
|
|
1252
|
+
writeStderr(t("cli.init.compat.legacy-stage-flags"));
|
|
1253
|
+
}
|
|
1254
|
+
const supports = detectClientSupports(intent.target);
|
|
1255
|
+
const basePlan = await buildInitExecutionPlan({
|
|
1256
|
+
target: intent.target,
|
|
1257
|
+
options: intent.options,
|
|
1258
|
+
mcpInstallMode: intent.mcpInstallMode,
|
|
1259
|
+
interactive: intent.interactiveSummary && !intent.wizardEnabled,
|
|
1260
|
+
supports
|
|
1261
|
+
});
|
|
1262
|
+
const plan = intent.wizardEnabled ? await resolveInitExecutionPlanWithWizard(basePlan, args, createDefaultInitWizardAdapter()) : basePlan;
|
|
1263
|
+
if (plan === null) {
|
|
1264
|
+
writeStderr(t("cli.init.wizard.cancelled"));
|
|
1265
|
+
throw new Error(t("cli.init.wizard.cancelled"));
|
|
1266
|
+
}
|
|
1267
|
+
return executeInitExecutionPlan(plan);
|
|
1268
|
+
}
|
|
1269
|
+
function resolveInitCliIntent(args, targetInput) {
|
|
1270
|
+
const target = normalizeTarget2(targetInput);
|
|
1271
|
+
const mcpInstallMode = resolveMcpInstallMode(args["mcp-install"]);
|
|
1272
|
+
const terminalInteractive = isInteractiveInit();
|
|
1273
|
+
const planOnly = args.plan === true;
|
|
1274
|
+
const reapply = args.reapply === true;
|
|
1275
|
+
const options = {
|
|
1276
|
+
force: reapply ? true : args.force,
|
|
1277
|
+
skipBootstrap: args.bootstrap === false ? true : args.skipBootstrap,
|
|
1278
|
+
skipMcp: args.mcp === false ? true : args.skipMcp,
|
|
1279
|
+
skipHooks: args.hooks === false ? true : args.skipHooks,
|
|
1280
|
+
planOnly,
|
|
1281
|
+
reapply
|
|
1282
|
+
};
|
|
1283
|
+
return {
|
|
1284
|
+
target,
|
|
1285
|
+
options,
|
|
1286
|
+
mcpInstallMode,
|
|
1287
|
+
interactiveSummary: args.interactive !== false && terminalInteractive,
|
|
1288
|
+
wizardEnabled: shouldUseInitWizard(args, terminalInteractive) && !planOnly
|
|
1289
|
+
};
|
|
1290
|
+
}
|
|
1291
|
+
async function buildInitExecutionPlan(input) {
|
|
1292
|
+
const options = input.options ?? {};
|
|
1293
|
+
const scaffold = await buildInitFabricPlan(input.target, options);
|
|
1294
|
+
const supports = input.supports ?? detectClientSupports(input.target);
|
|
1295
|
+
const mcpInstallMode = input.mcpInstallMode ?? "global";
|
|
1296
|
+
const stages = [
|
|
1297
|
+
{ name: "bootstrap", skipped: Boolean(options.skipBootstrap) },
|
|
1298
|
+
{
|
|
1299
|
+
name: "mcp",
|
|
1300
|
+
skipped: Boolean(options.skipMcp),
|
|
1301
|
+
installMode: mcpInstallMode,
|
|
1302
|
+
localServerPath: mcpInstallMode === "local" ? LOCAL_FABRIC_SERVER_PATH : void 0,
|
|
1303
|
+
packageManager: mcpInstallMode === "local" ? detectPackageManager(input.target) : void 0
|
|
1304
|
+
},
|
|
1305
|
+
{ name: "hooks", skipped: Boolean(options.skipHooks) }
|
|
1306
|
+
];
|
|
1307
|
+
return {
|
|
1308
|
+
target: input.target,
|
|
1309
|
+
options,
|
|
1310
|
+
mcpInstallMode,
|
|
1311
|
+
interactive: input.interactive ?? false,
|
|
1312
|
+
supports,
|
|
1313
|
+
scaffold,
|
|
1314
|
+
stages,
|
|
1315
|
+
steps: [
|
|
1316
|
+
{ name: "preflight" },
|
|
1317
|
+
{ name: "scaffold" },
|
|
1318
|
+
...stages.map((stage) => ({ name: stage.name, skipped: stage.skipped })),
|
|
1319
|
+
{ name: "post-setup" }
|
|
1320
|
+
]
|
|
1321
|
+
};
|
|
1322
|
+
}
|
|
1323
|
+
async function executeInitExecutionPlan(plan) {
|
|
1324
|
+
if (plan.options.force) {
|
|
1325
|
+
writeStderr(t("cli.init.force.warning", { path: plan.target }));
|
|
1326
|
+
}
|
|
1327
|
+
if (plan.options.reapply && !plan.options.planOnly && !plan.interactive) {
|
|
1328
|
+
writeStderr(formatInitModeBanner(plan.options));
|
|
1329
|
+
}
|
|
1330
|
+
if (plan.interactive) {
|
|
1331
|
+
printInitPlanSummary(plan.target, plan.options, plan.mcpInstallMode, plan.supports);
|
|
1332
|
+
}
|
|
1333
|
+
if (plan.options.planOnly) {
|
|
1334
|
+
printInitPlanPreview(plan);
|
|
1335
|
+
return {
|
|
1336
|
+
plan,
|
|
1337
|
+
created: buildPlanOnlyScaffoldResult(plan.scaffold),
|
|
1338
|
+
stageResults: plan.stages.map((stage) => ({ name: stage.name, disposition: "skipped" })),
|
|
1339
|
+
finalSupports: plan.supports
|
|
1340
|
+
};
|
|
1341
|
+
}
|
|
1342
|
+
let created = null;
|
|
1343
|
+
const stageResults = [];
|
|
1344
|
+
let finalSupports = plan.supports;
|
|
1345
|
+
for (const step of plan.steps) {
|
|
1346
|
+
switch (step.name) {
|
|
1347
|
+
case "preflight":
|
|
1348
|
+
break;
|
|
1349
|
+
case "scaffold":
|
|
1350
|
+
created = executeInitFabricPlan(plan.scaffold);
|
|
1351
|
+
printInitScaffoldResult(created);
|
|
1352
|
+
break;
|
|
1353
|
+
case "bootstrap":
|
|
1354
|
+
case "mcp":
|
|
1355
|
+
case "hooks":
|
|
1356
|
+
stageResults.push(await executeInitStagePlan(plan, step.name));
|
|
1357
|
+
break;
|
|
1358
|
+
case "post-setup":
|
|
1359
|
+
finalSupports = detectClientSupports(plan.target);
|
|
1360
|
+
printInitPostSetup(plan, stageResults, finalSupports);
|
|
1361
|
+
break;
|
|
1362
|
+
default:
|
|
1363
|
+
exhaustiveInitExecutionStep(step);
|
|
1364
|
+
}
|
|
1365
|
+
}
|
|
1366
|
+
return {
|
|
1367
|
+
plan,
|
|
1368
|
+
created: created ?? unreachableInitScaffold(),
|
|
1369
|
+
stageResults,
|
|
1370
|
+
finalSupports
|
|
1371
|
+
};
|
|
1372
|
+
}
|
|
1373
|
+
async function buildInitFabricPlan(target, options) {
|
|
1041
1374
|
assertExistingDirectory2(target);
|
|
1042
1375
|
const fabricDir = join2(target, ".fabric");
|
|
1043
1376
|
const bootstrapPath = join2(fabricDir, "bootstrap", "README.md");
|
|
1044
1377
|
const forensicPath = join2(fabricDir, "forensic.json");
|
|
1045
1378
|
const claudeSkillPath = join2(target, ".claude", "skills", "agents-md-init", "SKILL.md");
|
|
1379
|
+
const codexSkillPath = join2(target, ".agents", "skills", "fabric-init", "SKILL.md");
|
|
1380
|
+
const codexSessionStartHookPath = join2(target, ".codex", "hooks", "fabric-session-start.cjs");
|
|
1381
|
+
const codexStopHookPath = join2(target, ".codex", "hooks", "fabric-stop-reminder.cjs");
|
|
1382
|
+
const codexHooksConfigPath = join2(target, ".codex", "hooks.json");
|
|
1046
1383
|
const claudeHookPath = join2(target, ".claude", "hooks", "agents-md-init-reminder.cjs");
|
|
1047
1384
|
const claudeSettingsPath = join2(target, ".claude", "settings.json");
|
|
1048
1385
|
const metaPath = join2(fabricDir, "agents.meta.json");
|
|
1049
1386
|
const humanLockPath = join2(fabricDir, "human-lock.json");
|
|
1050
|
-
|
|
1051
|
-
const bootstrapAction =
|
|
1052
|
-
const metaAction =
|
|
1053
|
-
const humanLockAction =
|
|
1054
|
-
const forensicAction =
|
|
1055
|
-
const forensicReport = buildForensicReport(target);
|
|
1387
|
+
const replaceFabricDir = shouldReplaceWritableDirectory(fabricDir, options);
|
|
1388
|
+
const bootstrapAction = planFreshPath(bootstrapPath, options);
|
|
1389
|
+
const metaAction = planFreshPath(metaPath, options);
|
|
1390
|
+
const humanLockAction = planFreshPath(humanLockPath, options);
|
|
1391
|
+
const forensicAction = planFreshPath(forensicPath, options);
|
|
1392
|
+
const forensicReport = await buildForensicReport(target);
|
|
1056
1393
|
const humanLockTemplate = readFileSync2(findTemplatePath("templates/fabric/human-lock.json"), "utf8");
|
|
1057
|
-
const bootstrapContent = buildFabricBootstrapGuide(target);
|
|
1394
|
+
const bootstrapContent = await buildFabricBootstrapGuide(target);
|
|
1058
1395
|
const bootstrapHash = sha256(bootstrapContent);
|
|
1059
1396
|
const meta = createInitialMeta(bootstrapHash);
|
|
1060
|
-
mkdirSync(fabricDir, { recursive: true });
|
|
1061
|
-
mkdirSync(dirname(bootstrapPath), { recursive: true });
|
|
1062
|
-
writeNewFile(bootstrapPath, bootstrapContent, options);
|
|
1063
|
-
writeNewFile(metaPath, `${JSON.stringify(meta, null, 2)}
|
|
1064
|
-
`, options);
|
|
1065
|
-
writeNewFile(humanLockPath, humanLockTemplate.endsWith("\n") ? humanLockTemplate : `${humanLockTemplate}
|
|
1066
|
-
`, options);
|
|
1067
|
-
writeNewFile(forensicPath, `${JSON.stringify(forensicReport, null, 2)}
|
|
1068
|
-
`, options);
|
|
1069
|
-
const claudeSkillAction = copyTemplateIfMissing(findTemplatePath(CLAUDE_INIT_SKILL_TEMPLATE), claudeSkillPath, options);
|
|
1070
|
-
const claudeHookAction = copyExecutableTemplateIfMissing(
|
|
1071
|
-
findTemplatePath(CLAUDE_INIT_REMINDER_HOOK_TEMPLATE),
|
|
1072
|
-
claudeHookPath,
|
|
1073
|
-
options
|
|
1074
|
-
);
|
|
1075
|
-
const claudeSettingsAction = mergeClaudeStopHook(claudeSettingsPath, options);
|
|
1076
1397
|
return {
|
|
1398
|
+
target,
|
|
1399
|
+
options,
|
|
1400
|
+
fabricDir,
|
|
1401
|
+
replaceFabricDir,
|
|
1077
1402
|
bootstrapPath,
|
|
1078
1403
|
bootstrapAction,
|
|
1404
|
+
bootstrapContent,
|
|
1079
1405
|
metaPath,
|
|
1080
1406
|
metaAction,
|
|
1407
|
+
meta,
|
|
1081
1408
|
humanLockPath,
|
|
1082
1409
|
humanLockAction,
|
|
1410
|
+
humanLockContent: humanLockTemplate.endsWith("\n") ? humanLockTemplate : `${humanLockTemplate}
|
|
1411
|
+
`,
|
|
1083
1412
|
forensicPath,
|
|
1084
1413
|
forensicAction,
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1414
|
+
forensicReport,
|
|
1415
|
+
claudeSkill: buildOptionalTemplateWritePlan(claudeSkillPath, findTemplatePath(CLAUDE_INIT_SKILL_TEMPLATE), options),
|
|
1416
|
+
codexSkill: buildOptionalTemplateWritePlan(codexSkillPath, findTemplatePath(CODEX_INIT_SKILL_TEMPLATE), options),
|
|
1417
|
+
codexSessionStartHook: buildOptionalTemplateWritePlan(
|
|
1418
|
+
codexSessionStartHookPath,
|
|
1419
|
+
findTemplatePath(CODEX_SESSION_START_HOOK_TEMPLATE),
|
|
1420
|
+
options,
|
|
1421
|
+
true
|
|
1422
|
+
),
|
|
1423
|
+
codexStopHook: buildOptionalTemplateWritePlan(
|
|
1424
|
+
codexStopHookPath,
|
|
1425
|
+
findTemplatePath(CODEX_STOP_HOOK_TEMPLATE),
|
|
1426
|
+
options,
|
|
1427
|
+
true
|
|
1428
|
+
),
|
|
1429
|
+
codexHooksConfig: buildCodexHooksConfigPlan(codexHooksConfigPath, options),
|
|
1430
|
+
claudeHook: buildOptionalTemplateWritePlan(
|
|
1431
|
+
claudeHookPath,
|
|
1432
|
+
findTemplatePath(CLAUDE_INIT_REMINDER_HOOK_TEMPLATE),
|
|
1433
|
+
options,
|
|
1434
|
+
true
|
|
1435
|
+
),
|
|
1436
|
+
claudeSettings: buildClaudeSettingsWritePlan(claudeSettingsPath, options)
|
|
1437
|
+
};
|
|
1438
|
+
}
|
|
1439
|
+
function executeInitFabricPlan(plan) {
|
|
1440
|
+
if (plan.replaceFabricDir) {
|
|
1441
|
+
rmSync(plan.fabricDir, { force: true });
|
|
1442
|
+
}
|
|
1443
|
+
mkdirSync(plan.fabricDir, { recursive: true });
|
|
1444
|
+
mkdirSync(dirname(plan.bootstrapPath), { recursive: true });
|
|
1445
|
+
preparePlannedPath(plan.bootstrapPath, plan.bootstrapAction);
|
|
1446
|
+
writeFileSync(plan.bootstrapPath, plan.bootstrapContent, "utf8");
|
|
1447
|
+
preparePlannedPath(plan.metaPath, plan.metaAction);
|
|
1448
|
+
writeFileSync(plan.metaPath, `${JSON.stringify(plan.meta, null, 2)}
|
|
1449
|
+
`, "utf8");
|
|
1450
|
+
preparePlannedPath(plan.humanLockPath, plan.humanLockAction);
|
|
1451
|
+
writeFileSync(plan.humanLockPath, plan.humanLockContent, "utf8");
|
|
1452
|
+
preparePlannedPath(plan.forensicPath, plan.forensicAction);
|
|
1453
|
+
writeFileSync(plan.forensicPath, `${JSON.stringify(plan.forensicReport, null, 2)}
|
|
1454
|
+
`, "utf8");
|
|
1455
|
+
applyOptionalTemplateWritePlan(plan.claudeSkill);
|
|
1456
|
+
applyOptionalTemplateWritePlan(plan.codexSkill);
|
|
1457
|
+
applyOptionalTemplateWritePlan(plan.codexSessionStartHook);
|
|
1458
|
+
applyOptionalTemplateWritePlan(plan.codexStopHook);
|
|
1459
|
+
applyJsonWritePlan(plan.codexHooksConfig);
|
|
1460
|
+
applyOptionalTemplateWritePlan(plan.claudeHook);
|
|
1461
|
+
applyClaudeSettingsWritePlan(plan.claudeSettings);
|
|
1462
|
+
return {
|
|
1463
|
+
bootstrapPath: plan.bootstrapPath,
|
|
1464
|
+
bootstrapAction: plan.bootstrapAction,
|
|
1465
|
+
metaPath: plan.metaPath,
|
|
1466
|
+
metaAction: plan.metaAction,
|
|
1467
|
+
humanLockPath: plan.humanLockPath,
|
|
1468
|
+
humanLockAction: plan.humanLockAction,
|
|
1469
|
+
forensicPath: plan.forensicPath,
|
|
1470
|
+
forensicAction: plan.forensicAction,
|
|
1471
|
+
claudeSkillPath: plan.claudeSkill.path,
|
|
1472
|
+
claudeSkillAction: plan.claudeSkill.action,
|
|
1473
|
+
codexSkillPath: plan.codexSkill.path,
|
|
1474
|
+
codexSkillAction: plan.codexSkill.action,
|
|
1475
|
+
codexSessionStartHookPath: plan.codexSessionStartHook.path,
|
|
1476
|
+
codexSessionStartHookAction: plan.codexSessionStartHook.action,
|
|
1477
|
+
codexStopHookPath: plan.codexStopHook.path,
|
|
1478
|
+
codexStopHookAction: plan.codexStopHook.action,
|
|
1479
|
+
codexHooksConfigPath: plan.codexHooksConfig.path,
|
|
1480
|
+
codexHooksConfigAction: plan.codexHooksConfig.action,
|
|
1481
|
+
claudeHookPath: plan.claudeHook.path,
|
|
1482
|
+
claudeHookAction: plan.claudeHook.action,
|
|
1483
|
+
claudeSettingsPath: plan.claudeSettings.path,
|
|
1484
|
+
claudeSettingsAction: plan.claudeSettings.action
|
|
1485
|
+
};
|
|
1486
|
+
}
|
|
1487
|
+
async function initFabric(target, options) {
|
|
1488
|
+
return executeInitFabricPlan(await buildInitFabricPlan(target, options));
|
|
1489
|
+
}
|
|
1490
|
+
function shouldUseInitWizard(args, terminalInteractive = isInteractiveInit()) {
|
|
1491
|
+
return terminalInteractive && args.interactive !== false && args.yes !== true;
|
|
1492
|
+
}
|
|
1493
|
+
async function resolveInitExecutionPlanWithWizard(basePlan, args, wizardAdapter) {
|
|
1494
|
+
const selection = await wizardAdapter.run({
|
|
1495
|
+
target: basePlan.target,
|
|
1496
|
+
options: basePlan.options,
|
|
1497
|
+
supports: basePlan.supports,
|
|
1498
|
+
mcpInstallMode: basePlan.mcpInstallMode,
|
|
1499
|
+
lockedStages: collectLockedWizardStages(args)
|
|
1500
|
+
});
|
|
1501
|
+
if (selection === null) {
|
|
1502
|
+
return null;
|
|
1503
|
+
}
|
|
1504
|
+
return buildInitExecutionPlan({
|
|
1505
|
+
target: basePlan.target,
|
|
1506
|
+
options: {
|
|
1507
|
+
...basePlan.options,
|
|
1508
|
+
skipBootstrap: !selection.bootstrap,
|
|
1509
|
+
skipMcp: !selection.mcp,
|
|
1510
|
+
skipHooks: !selection.hooks
|
|
1511
|
+
},
|
|
1512
|
+
mcpInstallMode: selection.mcp ? selection.mcpInstallMode : basePlan.mcpInstallMode,
|
|
1513
|
+
interactive: false,
|
|
1514
|
+
supports: basePlan.supports
|
|
1515
|
+
});
|
|
1516
|
+
}
|
|
1517
|
+
function unreachableInitScaffold() {
|
|
1518
|
+
throw new Error("Init scaffold step did not execute");
|
|
1519
|
+
}
|
|
1520
|
+
function exhaustiveInitExecutionStep(value) {
|
|
1521
|
+
throw new Error(`Unsupported init execution step: ${JSON.stringify(value)}`);
|
|
1522
|
+
}
|
|
1523
|
+
function exhaustiveInitStagePlan(value) {
|
|
1524
|
+
throw new Error(`Unsupported init stage plan: ${JSON.stringify(value)}`);
|
|
1525
|
+
}
|
|
1526
|
+
function printInitScaffoldResult(created) {
|
|
1527
|
+
console.log(formatInitPathAction(created.bootstrapPath, created.bootstrapAction));
|
|
1528
|
+
console.log(formatInitPathAction(created.metaPath, created.metaAction));
|
|
1529
|
+
console.log(formatInitPathAction(created.humanLockPath, created.humanLockAction));
|
|
1530
|
+
console.log(formatInitPathAction(created.forensicPath, created.forensicAction));
|
|
1531
|
+
writeStderr(formatOptionalInitPathAction(created.claudeSkillPath, created.claudeSkillAction));
|
|
1532
|
+
writeStderr(formatOptionalInitPathAction(created.codexSkillPath, created.codexSkillAction));
|
|
1533
|
+
writeStderr(
|
|
1534
|
+
formatOptionalInitPathAction(created.codexSessionStartHookPath, created.codexSessionStartHookAction)
|
|
1535
|
+
);
|
|
1536
|
+
writeStderr(
|
|
1537
|
+
formatOptionalInitPathAction(created.codexStopHookPath, created.codexStopHookAction)
|
|
1538
|
+
);
|
|
1539
|
+
writeStderr(formatCodexHooksAction(created.codexHooksConfigPath, created.codexHooksConfigAction));
|
|
1540
|
+
writeStderr(formatOptionalInitPathAction(created.claudeHookPath, created.claudeHookAction));
|
|
1541
|
+
writeStderr(formatClaudeSettingsAction(created.claudeSettingsPath, created.claudeSettingsAction));
|
|
1542
|
+
}
|
|
1543
|
+
function printInitPostSetup(plan, stageResults, finalSupports) {
|
|
1544
|
+
if (shouldPrintHooksNextStep(plan.options, stageResults)) {
|
|
1545
|
+
console.log(
|
|
1546
|
+
t("cli.init.next-step", {
|
|
1547
|
+
label: nextLabel(),
|
|
1548
|
+
message: paint.muted(t("cli.init.next-step.message"))
|
|
1549
|
+
})
|
|
1550
|
+
);
|
|
1551
|
+
}
|
|
1552
|
+
console.log(
|
|
1553
|
+
t("cli.init.reason-message", {
|
|
1554
|
+
label: reasonLabel(),
|
|
1555
|
+
message: paint.muted(formatInitReasonMessage(finalSupports))
|
|
1556
|
+
})
|
|
1557
|
+
);
|
|
1558
|
+
printInitStageSummary(stageResults);
|
|
1559
|
+
printInitCapabilitySummary(finalSupports, stageResults, plan.options);
|
|
1560
|
+
}
|
|
1561
|
+
function printInitPlanPreview(plan) {
|
|
1562
|
+
console.log(t("cli.init.plan.preview-title"));
|
|
1563
|
+
printInitPlanSummary(plan.target, plan.options, plan.mcpInstallMode, plan.supports);
|
|
1564
|
+
console.log(
|
|
1565
|
+
t("cli.init.plan.preview-result", {
|
|
1566
|
+
mode: plan.options.reapply ? t("cli.init.mode.reapply") : t("cli.init.mode.default"),
|
|
1567
|
+
bootstrap: yesNoLabel(!plan.options.skipBootstrap),
|
|
1568
|
+
mcp: yesNoLabel(!plan.options.skipMcp),
|
|
1569
|
+
hooks: yesNoLabel(!plan.options.skipHooks)
|
|
1570
|
+
})
|
|
1571
|
+
);
|
|
1572
|
+
}
|
|
1573
|
+
function buildPlanOnlyScaffoldResult(plan) {
|
|
1574
|
+
return {
|
|
1575
|
+
bootstrapPath: plan.bootstrapPath,
|
|
1576
|
+
bootstrapAction: plan.bootstrapAction,
|
|
1577
|
+
metaPath: plan.metaPath,
|
|
1578
|
+
metaAction: plan.metaAction,
|
|
1579
|
+
humanLockPath: plan.humanLockPath,
|
|
1580
|
+
humanLockAction: plan.humanLockAction,
|
|
1581
|
+
forensicPath: plan.forensicPath,
|
|
1582
|
+
forensicAction: plan.forensicAction,
|
|
1583
|
+
claudeSkillPath: plan.claudeSkill.path,
|
|
1584
|
+
claudeSkillAction: plan.claudeSkill.action,
|
|
1585
|
+
codexSkillPath: plan.codexSkill.path,
|
|
1586
|
+
codexSkillAction: plan.codexSkill.action,
|
|
1587
|
+
codexSessionStartHookPath: plan.codexSessionStartHook.path,
|
|
1588
|
+
codexSessionStartHookAction: plan.codexSessionStartHook.action,
|
|
1589
|
+
codexStopHookPath: plan.codexStopHook.path,
|
|
1590
|
+
codexStopHookAction: plan.codexStopHook.action,
|
|
1591
|
+
codexHooksConfigPath: plan.codexHooksConfig.path,
|
|
1592
|
+
codexHooksConfigAction: plan.codexHooksConfig.action,
|
|
1593
|
+
claudeHookPath: plan.claudeHook.path,
|
|
1594
|
+
claudeHookAction: plan.claudeHook.action,
|
|
1595
|
+
claudeSettingsPath: plan.claudeSettings.path,
|
|
1596
|
+
claudeSettingsAction: plan.claudeSettings.action
|
|
1597
|
+
};
|
|
1598
|
+
}
|
|
1599
|
+
async function executeInitStagePlan(plan, stageName) {
|
|
1600
|
+
const stage = plan.stages.find((entry) => entry.name === stageName);
|
|
1601
|
+
if (stage === void 0) {
|
|
1602
|
+
throw new Error(`Missing init stage plan: ${stageName}`);
|
|
1603
|
+
}
|
|
1604
|
+
if (stage.skipped) {
|
|
1605
|
+
return { name: stageName, disposition: "skipped" };
|
|
1606
|
+
}
|
|
1607
|
+
console.log(formatInitStageHeader(t(`cli.init.stages.${stageName}`)));
|
|
1608
|
+
try {
|
|
1609
|
+
switch (stage.name) {
|
|
1610
|
+
case "bootstrap": {
|
|
1611
|
+
const result = await installBootstrap(plan.target, { force: plan.options.force });
|
|
1612
|
+
if (result.details.length === 0) {
|
|
1613
|
+
console.log(formatInitStageResult("bootstrap", "skipped", 0, 0, t("cli.bootstrap.install.no-targets")));
|
|
1614
|
+
return { name: "bootstrap", disposition: "skipped" };
|
|
1615
|
+
}
|
|
1616
|
+
console.log(
|
|
1617
|
+
formatInitStageResult("bootstrap", "completed", result.installed.length, result.skipped.length)
|
|
1618
|
+
);
|
|
1619
|
+
return { name: "bootstrap", disposition: "ran" };
|
|
1620
|
+
}
|
|
1621
|
+
case "mcp": {
|
|
1622
|
+
if (stage.installMode === "local") {
|
|
1623
|
+
const manager = stage.packageManager ?? detectPackageManager(plan.target);
|
|
1624
|
+
writeStderr(t("cli.init.mcp.install.local"));
|
|
1625
|
+
writeStderr(t("cli.init.mcp.local.installing", { manager }));
|
|
1626
|
+
installLocalFabricServer(plan.target, manager);
|
|
1627
|
+
writeStderr(t("cli.init.mcp.local.installed"));
|
|
1628
|
+
} else {
|
|
1629
|
+
writeStderr(t("cli.init.mcp.install.global"));
|
|
1630
|
+
}
|
|
1631
|
+
const result = await installMcpClients(plan.target, {
|
|
1632
|
+
force: plan.options.force,
|
|
1633
|
+
localServerPath: stage.localServerPath
|
|
1634
|
+
});
|
|
1635
|
+
if (result.details.length === 0) {
|
|
1636
|
+
console.log(formatInitStageResult("mcp", "skipped", 0, 0, t("cli.config.install.no-configs")));
|
|
1637
|
+
return { name: "mcp", disposition: "skipped" };
|
|
1638
|
+
}
|
|
1639
|
+
console.log(formatInitStageResult("mcp", "completed", result.installed.length, result.skipped.length));
|
|
1640
|
+
return { name: "mcp", disposition: "ran" };
|
|
1641
|
+
}
|
|
1642
|
+
case "hooks": {
|
|
1643
|
+
const result = await installHooks(plan.target, { force: plan.options.force });
|
|
1644
|
+
console.log(formatInitStageResult("hooks", "completed", result.installed.length, result.skipped.length));
|
|
1645
|
+
return { name: "hooks", disposition: "ran" };
|
|
1646
|
+
}
|
|
1647
|
+
default:
|
|
1648
|
+
return exhaustiveInitStagePlan(stage);
|
|
1649
|
+
}
|
|
1650
|
+
} catch (error) {
|
|
1651
|
+
writeStderr(formatInitStageFailure(stageName, error));
|
|
1652
|
+
return { name: stageName, disposition: "failed" };
|
|
1653
|
+
}
|
|
1654
|
+
}
|
|
1655
|
+
function shouldReplaceWritableDirectory(path, options) {
|
|
1656
|
+
if (!existsSync2(path)) {
|
|
1657
|
+
return false;
|
|
1658
|
+
}
|
|
1659
|
+
if (statSync2(path).isDirectory()) {
|
|
1660
|
+
return false;
|
|
1661
|
+
}
|
|
1662
|
+
if (!options?.force) {
|
|
1663
|
+
throw new Error(t("cli.init.errors.abort-existing", { path }));
|
|
1664
|
+
}
|
|
1665
|
+
return true;
|
|
1666
|
+
}
|
|
1667
|
+
function planFreshPath(path, options) {
|
|
1668
|
+
if (!existsSync2(path)) {
|
|
1669
|
+
return "created";
|
|
1670
|
+
}
|
|
1671
|
+
if (!options?.force) {
|
|
1672
|
+
throw new Error(t("cli.init.errors.abort-existing", { path }));
|
|
1673
|
+
}
|
|
1674
|
+
return "overwritten";
|
|
1675
|
+
}
|
|
1676
|
+
function preparePlannedPath(path, action) {
|
|
1677
|
+
mkdirSync(dirname(path), { recursive: true });
|
|
1678
|
+
if (action === "overwritten" && existsSync2(path)) {
|
|
1679
|
+
rmSync(path, { recursive: true, force: true });
|
|
1680
|
+
}
|
|
1681
|
+
}
|
|
1682
|
+
function buildOptionalTemplateWritePlan(path, templatePath, options, executable = false) {
|
|
1683
|
+
const existed = existsSync2(path);
|
|
1684
|
+
if (existed && !options?.force) {
|
|
1685
|
+
return { path, action: "skipped", templatePath, executable };
|
|
1686
|
+
}
|
|
1687
|
+
return {
|
|
1688
|
+
path,
|
|
1689
|
+
action: existed ? "overwritten" : "created",
|
|
1690
|
+
templatePath,
|
|
1691
|
+
executable
|
|
1692
|
+
};
|
|
1693
|
+
}
|
|
1694
|
+
function applyOptionalTemplateWritePlan(plan) {
|
|
1695
|
+
if (plan.action === "skipped") {
|
|
1696
|
+
return;
|
|
1697
|
+
}
|
|
1698
|
+
mkdirSync(dirname(plan.path), { recursive: true });
|
|
1699
|
+
copyFileSync(plan.templatePath, plan.path);
|
|
1700
|
+
if (plan.executable) {
|
|
1701
|
+
chmodSync(plan.path, 493);
|
|
1702
|
+
}
|
|
1703
|
+
}
|
|
1704
|
+
function buildCodexHooksConfigValue() {
|
|
1705
|
+
return {
|
|
1706
|
+
hooks: {
|
|
1707
|
+
SessionStart: [
|
|
1708
|
+
{
|
|
1709
|
+
matcher: "*",
|
|
1710
|
+
hooks: [{ type: "command", command: CODEX_SESSION_START_COMMAND }]
|
|
1711
|
+
}
|
|
1712
|
+
],
|
|
1713
|
+
Stop: [
|
|
1714
|
+
{
|
|
1715
|
+
matcher: "*",
|
|
1716
|
+
hooks: [{ type: "command", command: CODEX_STOP_COMMAND }]
|
|
1717
|
+
}
|
|
1718
|
+
]
|
|
1719
|
+
}
|
|
1720
|
+
};
|
|
1721
|
+
}
|
|
1722
|
+
function buildCodexHooksConfigPlan(configPath, options) {
|
|
1723
|
+
const action = !existsSync2(configPath) ? "created" : options?.force ? "overwritten" : "skipped";
|
|
1724
|
+
return {
|
|
1725
|
+
path: configPath,
|
|
1726
|
+
action,
|
|
1727
|
+
value: buildCodexHooksConfigValue()
|
|
1728
|
+
};
|
|
1729
|
+
}
|
|
1730
|
+
function applyJsonWritePlan(plan) {
|
|
1731
|
+
if (plan.action === "skipped") {
|
|
1732
|
+
return;
|
|
1733
|
+
}
|
|
1734
|
+
mkdirSync(dirname(plan.path), { recursive: true });
|
|
1735
|
+
writeJsonAtomically(plan.path, plan.value);
|
|
1736
|
+
}
|
|
1737
|
+
function buildClaudeSettingsWritePlan(settingsPath, options) {
|
|
1738
|
+
let settings;
|
|
1739
|
+
let action = "updated";
|
|
1740
|
+
if (!existsSync2(settingsPath)) {
|
|
1741
|
+
settings = {};
|
|
1742
|
+
action = "created";
|
|
1743
|
+
} else {
|
|
1744
|
+
try {
|
|
1745
|
+
const parsed = JSON.parse(readFileSync2(settingsPath, "utf8"));
|
|
1746
|
+
if (!isRecord(parsed)) {
|
|
1747
|
+
writeStderr(t("cli.init.claude-settings.invalid-object", { label: skippedLabel(), path: settingsPath }));
|
|
1748
|
+
return { path: settingsPath, action: "skipped-invalid", value: null };
|
|
1749
|
+
}
|
|
1750
|
+
settings = parsed;
|
|
1751
|
+
} catch (error) {
|
|
1752
|
+
const reason = error instanceof Error ? error.message : "unknown parse error";
|
|
1753
|
+
writeStderr(t("cli.init.claude-settings.invalid-json", { label: skippedLabel(), path: settingsPath, reason }));
|
|
1754
|
+
return { path: settingsPath, action: "skipped-invalid", value: null };
|
|
1755
|
+
}
|
|
1756
|
+
}
|
|
1757
|
+
if (settings.hooks !== void 0 && !isRecord(settings.hooks)) {
|
|
1758
|
+
writeStderr(t("cli.init.claude-settings.invalid-hooks", { label: skippedLabel(), path: settingsPath }));
|
|
1759
|
+
return { path: settingsPath, action: "skipped-invalid", value: null };
|
|
1760
|
+
}
|
|
1761
|
+
const hooks = settings.hooks ?? {};
|
|
1762
|
+
const stopHooksValue = hooks.Stop;
|
|
1763
|
+
if (stopHooksValue !== void 0 && !Array.isArray(stopHooksValue)) {
|
|
1764
|
+
writeStderr(t("cli.init.claude-settings.invalid-stop-array", { label: skippedLabel(), path: settingsPath }));
|
|
1765
|
+
return { path: settingsPath, action: "skipped-invalid", value: null };
|
|
1766
|
+
}
|
|
1767
|
+
const stopHooks = Array.isArray(stopHooksValue) ? stopHooksValue : [];
|
|
1768
|
+
const hasExistingFabricHook = hasClaudeInitReminderHook(stopHooks);
|
|
1769
|
+
if (hasExistingFabricHook && !options?.force) {
|
|
1770
|
+
return { path: settingsPath, action: "skipped", value: null };
|
|
1771
|
+
}
|
|
1772
|
+
const nextStopHooks = hasExistingFabricHook && options?.force ? removeClaudeInitReminderHook(stopHooks) : [...stopHooks];
|
|
1773
|
+
nextStopHooks.push({
|
|
1774
|
+
matcher: "*",
|
|
1775
|
+
hooks: [
|
|
1776
|
+
{
|
|
1777
|
+
type: "command",
|
|
1778
|
+
command: CLAUDE_INIT_REMINDER_COMMAND
|
|
1779
|
+
}
|
|
1780
|
+
]
|
|
1781
|
+
});
|
|
1782
|
+
const nextSettings = {
|
|
1783
|
+
...settings,
|
|
1784
|
+
hooks: {
|
|
1785
|
+
...hooks,
|
|
1786
|
+
Stop: nextStopHooks
|
|
1787
|
+
}
|
|
1788
|
+
};
|
|
1789
|
+
return {
|
|
1790
|
+
path: settingsPath,
|
|
1791
|
+
action: hasExistingFabricHook && options?.force ? "overwritten" : action,
|
|
1792
|
+
value: nextSettings
|
|
1793
|
+
};
|
|
1794
|
+
}
|
|
1795
|
+
function applyClaudeSettingsWritePlan(plan) {
|
|
1796
|
+
if (plan.value === null) {
|
|
1797
|
+
return;
|
|
1798
|
+
}
|
|
1799
|
+
mkdirSync(dirname(plan.path), { recursive: true });
|
|
1800
|
+
writeJsonAtomically(plan.path, plan.value);
|
|
1801
|
+
}
|
|
1802
|
+
function createDefaultInitWizardAdapter() {
|
|
1803
|
+
return {
|
|
1804
|
+
async run(context) {
|
|
1805
|
+
intro(t("cli.init.wizard.intro"));
|
|
1806
|
+
note(
|
|
1807
|
+
t("cli.init.wizard.overview.body", {
|
|
1808
|
+
target: context.target,
|
|
1809
|
+
mode: formatInitModeBadge(context.options)
|
|
1810
|
+
}),
|
|
1811
|
+
t("cli.init.wizard.overview.title")
|
|
1812
|
+
);
|
|
1813
|
+
printInitPlanSummary(context.target, context.options, context.mcpInstallMode, context.supports);
|
|
1814
|
+
log.step(t("cli.init.wizard.step.target"));
|
|
1815
|
+
const continueWithTarget = await confirm({
|
|
1816
|
+
message: t("cli.init.wizard.target.confirm", { target: context.target }),
|
|
1817
|
+
initialValue: true
|
|
1818
|
+
});
|
|
1819
|
+
if (isCancel(continueWithTarget) || !continueWithTarget) {
|
|
1820
|
+
emitInitWizardCancellation();
|
|
1821
|
+
return null;
|
|
1822
|
+
}
|
|
1823
|
+
log.step(t("cli.init.wizard.step.plan"));
|
|
1824
|
+
let groupedSelection;
|
|
1825
|
+
try {
|
|
1826
|
+
groupedSelection = await group(
|
|
1827
|
+
{
|
|
1828
|
+
bootstrap: async () => context.lockedStages.includes("bootstrap") ? false : confirmInGroup({
|
|
1829
|
+
message: t("cli.init.wizard.stage.bootstrap", {
|
|
1830
|
+
defaultValue: formatPromptDefault(!context.options.skipBootstrap)
|
|
1831
|
+
}),
|
|
1832
|
+
initialValue: !context.options.skipBootstrap
|
|
1833
|
+
}),
|
|
1834
|
+
mcp: async () => context.lockedStages.includes("mcp") ? false : confirmInGroup({
|
|
1835
|
+
message: t("cli.init.wizard.stage.mcp", {
|
|
1836
|
+
defaultValue: formatPromptDefault(!context.options.skipMcp)
|
|
1837
|
+
}),
|
|
1838
|
+
initialValue: !context.options.skipMcp
|
|
1839
|
+
}),
|
|
1840
|
+
mcpInstallMode: async ({ results }) => results.mcp ? selectMcpInstallModeInGroup({
|
|
1841
|
+
message: t("cli.init.wizard.mcp-install", { defaultValue: context.mcpInstallMode }),
|
|
1842
|
+
initialValue: context.mcpInstallMode,
|
|
1843
|
+
options: [
|
|
1844
|
+
{ value: "global", label: "global", hint: t("cli.init.mcp.install.global") },
|
|
1845
|
+
{ value: "local", label: "local", hint: t("cli.init.mcp.install.local") }
|
|
1846
|
+
]
|
|
1847
|
+
}) : context.mcpInstallMode,
|
|
1848
|
+
hooks: async () => context.lockedStages.includes("hooks") ? false : confirmInGroup({
|
|
1849
|
+
message: t("cli.init.wizard.stage.hooks", {
|
|
1850
|
+
defaultValue: formatPromptDefault(!context.options.skipHooks)
|
|
1851
|
+
}),
|
|
1852
|
+
initialValue: !context.options.skipHooks
|
|
1853
|
+
})
|
|
1854
|
+
},
|
|
1855
|
+
{
|
|
1856
|
+
onCancel() {
|
|
1857
|
+
throw INIT_WIZARD_GROUP_CANCELLED;
|
|
1858
|
+
}
|
|
1859
|
+
}
|
|
1860
|
+
);
|
|
1861
|
+
} catch (error) {
|
|
1862
|
+
if (error === INIT_WIZARD_GROUP_CANCELLED) {
|
|
1863
|
+
emitInitWizardCancellation();
|
|
1864
|
+
return null;
|
|
1865
|
+
}
|
|
1866
|
+
throw error;
|
|
1867
|
+
}
|
|
1868
|
+
if (groupedSelection === null) {
|
|
1869
|
+
emitInitWizardCancellation();
|
|
1870
|
+
return null;
|
|
1871
|
+
}
|
|
1872
|
+
const previewOptions = {
|
|
1873
|
+
...context.options,
|
|
1874
|
+
skipBootstrap: !groupedSelection.bootstrap,
|
|
1875
|
+
skipMcp: !groupedSelection.mcp,
|
|
1876
|
+
skipHooks: !groupedSelection.hooks
|
|
1877
|
+
};
|
|
1878
|
+
log.step(t("cli.init.wizard.step.review"));
|
|
1879
|
+
printInitPlanSummary(context.target, previewOptions, groupedSelection.mcpInstallMode, context.supports);
|
|
1880
|
+
const confirmed = await confirm({
|
|
1881
|
+
message: t("cli.init.wizard.execute.confirm"),
|
|
1882
|
+
initialValue: true
|
|
1883
|
+
});
|
|
1884
|
+
if (isCancel(confirmed) || !confirmed) {
|
|
1885
|
+
emitInitWizardCancellation();
|
|
1886
|
+
return null;
|
|
1887
|
+
}
|
|
1888
|
+
outro(t("cli.init.wizard.outro"));
|
|
1889
|
+
return groupedSelection;
|
|
1890
|
+
}
|
|
1091
1891
|
};
|
|
1092
1892
|
}
|
|
1893
|
+
function emitInitWizardCancellation() {
|
|
1894
|
+
cancel(t("cli.init.wizard.cancelled"));
|
|
1895
|
+
}
|
|
1896
|
+
async function confirmInGroup(options) {
|
|
1897
|
+
const result = await confirm(options);
|
|
1898
|
+
if (isCancel(result)) {
|
|
1899
|
+
throw INIT_WIZARD_GROUP_CANCELLED;
|
|
1900
|
+
}
|
|
1901
|
+
return result;
|
|
1902
|
+
}
|
|
1903
|
+
async function selectMcpInstallModeInGroup(options) {
|
|
1904
|
+
const result = await select({
|
|
1905
|
+
message: options.message,
|
|
1906
|
+
initialValue: options.initialValue,
|
|
1907
|
+
options: options.options
|
|
1908
|
+
});
|
|
1909
|
+
if (isCancel(result)) {
|
|
1910
|
+
throw INIT_WIZARD_GROUP_CANCELLED;
|
|
1911
|
+
}
|
|
1912
|
+
return result;
|
|
1913
|
+
}
|
|
1914
|
+
function collectLockedWizardStages(args) {
|
|
1915
|
+
const lockedStages = [];
|
|
1916
|
+
if (args.bootstrap === false) {
|
|
1917
|
+
lockedStages.push("bootstrap");
|
|
1918
|
+
}
|
|
1919
|
+
if (args.mcp === false) {
|
|
1920
|
+
lockedStages.push("mcp");
|
|
1921
|
+
}
|
|
1922
|
+
if (args.hooks === false) {
|
|
1923
|
+
lockedStages.push("hooks");
|
|
1924
|
+
}
|
|
1925
|
+
return lockedStages;
|
|
1926
|
+
}
|
|
1927
|
+
function formatPromptDefault(value) {
|
|
1928
|
+
return value ? "Y/n" : "y/N";
|
|
1929
|
+
}
|
|
1930
|
+
function formatInitModeBanner(options) {
|
|
1931
|
+
if (options.planOnly && options.reapply) {
|
|
1932
|
+
return t("cli.init.plan.mode-banner.plan-reapply");
|
|
1933
|
+
}
|
|
1934
|
+
if (options.planOnly) {
|
|
1935
|
+
return t("cli.init.plan.mode-banner.plan");
|
|
1936
|
+
}
|
|
1937
|
+
if (options.reapply) {
|
|
1938
|
+
return t("cli.init.plan.mode-banner.reapply");
|
|
1939
|
+
}
|
|
1940
|
+
return t("cli.init.plan.mode-banner.default");
|
|
1941
|
+
}
|
|
1942
|
+
function formatInitModeBadge(options) {
|
|
1943
|
+
if (options.planOnly && options.reapply) {
|
|
1944
|
+
return t("cli.init.mode.badge.plan-reapply");
|
|
1945
|
+
}
|
|
1946
|
+
if (options.planOnly) {
|
|
1947
|
+
return t("cli.init.mode.badge.plan");
|
|
1948
|
+
}
|
|
1949
|
+
if (options.reapply) {
|
|
1950
|
+
return t("cli.init.mode.badge.reapply");
|
|
1951
|
+
}
|
|
1952
|
+
return t("cli.init.mode.badge.default");
|
|
1953
|
+
}
|
|
1093
1954
|
function normalizeTarget2(targetInput) {
|
|
1094
1955
|
return isAbsolute2(targetInput) ? targetInput : resolve2(process.cwd(), targetInput);
|
|
1095
1956
|
}
|
|
@@ -1168,102 +2029,6 @@ function templateCandidatesFrom(start, relativePath) {
|
|
|
1168
2029
|
}
|
|
1169
2030
|
return candidates.reverse();
|
|
1170
2031
|
}
|
|
1171
|
-
function prepareFreshPath(path, options) {
|
|
1172
|
-
if (!existsSync2(path)) {
|
|
1173
|
-
return "created";
|
|
1174
|
-
}
|
|
1175
|
-
if (!options?.force) {
|
|
1176
|
-
throw new Error(t("cli.init.errors.abort-existing", { path }));
|
|
1177
|
-
}
|
|
1178
|
-
rmSync(path, { recursive: true, force: true });
|
|
1179
|
-
return "overwritten";
|
|
1180
|
-
}
|
|
1181
|
-
function prepareWritableDirectory(path, options) {
|
|
1182
|
-
if (!existsSync2(path) || statSync2(path).isDirectory()) {
|
|
1183
|
-
return;
|
|
1184
|
-
}
|
|
1185
|
-
if (!options?.force) {
|
|
1186
|
-
throw new Error(t("cli.init.errors.abort-existing", { path }));
|
|
1187
|
-
}
|
|
1188
|
-
rmSync(path, { force: true });
|
|
1189
|
-
}
|
|
1190
|
-
function writeNewFile(path, content, options) {
|
|
1191
|
-
const existed = existsSync2(path);
|
|
1192
|
-
if (existed && !options?.force) {
|
|
1193
|
-
throw new Error(t("cli.init.errors.abort-existing", { path }));
|
|
1194
|
-
}
|
|
1195
|
-
writeFileSync(path, content, "utf8");
|
|
1196
|
-
return existed ? "overwritten" : "created";
|
|
1197
|
-
}
|
|
1198
|
-
function copyTemplateIfMissing(templatePath, targetPath, options) {
|
|
1199
|
-
mkdirSync(dirname(targetPath), { recursive: true });
|
|
1200
|
-
const existed = existsSync2(targetPath);
|
|
1201
|
-
if (existed && !options?.force) {
|
|
1202
|
-
return "skipped";
|
|
1203
|
-
}
|
|
1204
|
-
copyFileSync(templatePath, targetPath);
|
|
1205
|
-
return existed ? "overwritten" : "created";
|
|
1206
|
-
}
|
|
1207
|
-
function copyExecutableTemplateIfMissing(templatePath, targetPath, options) {
|
|
1208
|
-
const action = copyTemplateIfMissing(templatePath, targetPath, options);
|
|
1209
|
-
if (action !== "skipped") {
|
|
1210
|
-
chmodSync(targetPath, 493);
|
|
1211
|
-
}
|
|
1212
|
-
return action;
|
|
1213
|
-
}
|
|
1214
|
-
function mergeClaudeStopHook(settingsPath, options) {
|
|
1215
|
-
mkdirSync(dirname(settingsPath), { recursive: true });
|
|
1216
|
-
let settings;
|
|
1217
|
-
let action = "updated";
|
|
1218
|
-
if (!existsSync2(settingsPath)) {
|
|
1219
|
-
settings = {};
|
|
1220
|
-
action = "created";
|
|
1221
|
-
} else {
|
|
1222
|
-
try {
|
|
1223
|
-
const parsed = JSON.parse(readFileSync2(settingsPath, "utf8"));
|
|
1224
|
-
if (!isRecord(parsed)) {
|
|
1225
|
-
writeStderr(t("cli.init.claude-settings.invalid-object", { label: skippedLabel(), path: settingsPath }));
|
|
1226
|
-
return "skipped-invalid";
|
|
1227
|
-
}
|
|
1228
|
-
settings = parsed;
|
|
1229
|
-
} catch (error) {
|
|
1230
|
-
const reason = error instanceof Error ? error.message : "unknown parse error";
|
|
1231
|
-
writeStderr(t("cli.init.claude-settings.invalid-json", { label: skippedLabel(), path: settingsPath, reason }));
|
|
1232
|
-
return "skipped-invalid";
|
|
1233
|
-
}
|
|
1234
|
-
}
|
|
1235
|
-
if (settings.hooks !== void 0 && !isRecord(settings.hooks)) {
|
|
1236
|
-
writeStderr(t("cli.init.claude-settings.invalid-hooks", { label: skippedLabel(), path: settingsPath }));
|
|
1237
|
-
return "skipped-invalid";
|
|
1238
|
-
}
|
|
1239
|
-
const hooks = settings.hooks ?? {};
|
|
1240
|
-
const stopHooksValue = hooks.Stop;
|
|
1241
|
-
if (stopHooksValue !== void 0 && !Array.isArray(stopHooksValue)) {
|
|
1242
|
-
writeStderr(t("cli.init.claude-settings.invalid-stop-array", { label: skippedLabel(), path: settingsPath }));
|
|
1243
|
-
return "skipped-invalid";
|
|
1244
|
-
}
|
|
1245
|
-
const stopHooks = Array.isArray(stopHooksValue) ? stopHooksValue : [];
|
|
1246
|
-
const hasExistingFabricHook = hasClaudeInitReminderHook(stopHooks);
|
|
1247
|
-
if (hasExistingFabricHook && !options?.force) {
|
|
1248
|
-
return "skipped";
|
|
1249
|
-
}
|
|
1250
|
-
const nextStopHooks = hasExistingFabricHook && options?.force ? removeClaudeInitReminderHook(stopHooks) : [...stopHooks];
|
|
1251
|
-
nextStopHooks.push({
|
|
1252
|
-
matcher: "*",
|
|
1253
|
-
hooks: [
|
|
1254
|
-
{
|
|
1255
|
-
type: "command",
|
|
1256
|
-
command: CLAUDE_INIT_REMINDER_COMMAND
|
|
1257
|
-
}
|
|
1258
|
-
]
|
|
1259
|
-
});
|
|
1260
|
-
settings.hooks = {
|
|
1261
|
-
...hooks,
|
|
1262
|
-
Stop: nextStopHooks
|
|
1263
|
-
};
|
|
1264
|
-
writeJsonAtomically(settingsPath, settings);
|
|
1265
|
-
return hasExistingFabricHook && options?.force ? "overwritten" : action;
|
|
1266
|
-
}
|
|
1267
2032
|
function hasClaudeInitReminderHook(stopHooks) {
|
|
1268
2033
|
return stopHooks.some((entry) => isClaudeInitReminderStopEntry(entry));
|
|
1269
2034
|
}
|
|
@@ -1306,10 +2071,10 @@ function formatClaudeSettingsAction(settingsPath, action) {
|
|
|
1306
2071
|
function formatInitStageHeader(message) {
|
|
1307
2072
|
return `${nextLabel()} ${paint.muted(message)}`;
|
|
1308
2073
|
}
|
|
1309
|
-
function formatInitStageResult(stage, status, installedCount, skippedCount,
|
|
2074
|
+
function formatInitStageResult(stage, status, installedCount, skippedCount, note2) {
|
|
1310
2075
|
const label = status === "completed" ? completedStageLabel() : skippedStageLabel();
|
|
1311
2076
|
const counts = `installed=${installedCount} skipped=${skippedCount}`;
|
|
1312
|
-
const suffix =
|
|
2077
|
+
const suffix = note2 ? ` ${paint.muted(`(${note2})`)}` : "";
|
|
1313
2078
|
return `${label} ${stage}: ${counts}${suffix}`;
|
|
1314
2079
|
}
|
|
1315
2080
|
function formatInitStageFailure(stage, error) {
|
|
@@ -1332,10 +2097,11 @@ function shouldPrintHooksNextStep(options, stageResults) {
|
|
|
1332
2097
|
return Boolean(options.skipHooks) || stageResults.some((stage) => stage.name === "hooks" && stage.disposition === "failed");
|
|
1333
2098
|
}
|
|
1334
2099
|
function isInteractiveInit() {
|
|
1335
|
-
return Boolean(process.stdout.isTTY) && Boolean(process.stderr.isTTY);
|
|
2100
|
+
return Boolean(process.stdin.isTTY) && Boolean(process.stdout.isTTY) && Boolean(process.stderr.isTTY);
|
|
1336
2101
|
}
|
|
1337
2102
|
function printInitPlanSummary(target, options, mcpInstallMode, supports) {
|
|
1338
2103
|
console.log(t("cli.init.plan.title"));
|
|
2104
|
+
console.log(formatInitModeBanner(options));
|
|
1339
2105
|
console.log(t("cli.init.plan.target", { target }));
|
|
1340
2106
|
console.log(
|
|
1341
2107
|
t("cli.init.plan.actions", {
|
|
@@ -1387,21 +2153,42 @@ function printInitCapabilitySummary(supports, stageResults, options) {
|
|
|
1387
2153
|
console.log(formatCapabilityTableRow(row, widths));
|
|
1388
2154
|
}
|
|
1389
2155
|
}
|
|
2156
|
+
function formatCodexHooksAction(configPath, action) {
|
|
2157
|
+
switch (action) {
|
|
2158
|
+
case "created":
|
|
2159
|
+
return t("cli.init.codex-hooks.created", { label: createdLabel(), path: configPath });
|
|
2160
|
+
case "overwritten":
|
|
2161
|
+
return t("cli.init.codex-hooks.updated", { label: overwrittenLabel(), path: configPath });
|
|
2162
|
+
case "skipped":
|
|
2163
|
+
return t("cli.init.codex-hooks.skipped", { label: skippedLabel(), path: configPath });
|
|
2164
|
+
default:
|
|
2165
|
+
return t("cli.init.codex-hooks.updated", { label: updatedLabel(), path: configPath });
|
|
2166
|
+
}
|
|
2167
|
+
}
|
|
1390
2168
|
function toCapabilityRow(support, stageResults, options) {
|
|
1391
2169
|
const stage = (name) => stageResults.find((entry) => entry.name === name)?.disposition ?? null;
|
|
1392
2170
|
const bootstrap = support.capabilities.bootstrap ? capabilityStatus(options.skipBootstrap ? "skipped" : stage("bootstrap")) : t("cli.init.capabilities.status.na");
|
|
1393
2171
|
const mcp = support.capabilities.mcp ? capabilityStatus(options.skipMcp ? "skipped" : stage("mcp")) : t("cli.init.capabilities.status.na");
|
|
1394
|
-
const hook = support
|
|
1395
|
-
const skill = support
|
|
2172
|
+
const hook = capabilityInstallStatus(support, "hook");
|
|
2173
|
+
const skill = capabilityInstallStatus(support, "skill");
|
|
1396
2174
|
return {
|
|
1397
2175
|
client: support.label,
|
|
1398
2176
|
bootstrap,
|
|
1399
2177
|
mcp,
|
|
1400
2178
|
hook,
|
|
1401
2179
|
skill,
|
|
1402
|
-
followUp: support.capabilities.skill ? t("cli.init.capabilities.follow-up.
|
|
2180
|
+
followUp: hasInstalledCapability(support, "skill") ? t("cli.init.capabilities.follow-up.ready") : support.capabilities.skill ? t("cli.init.capabilities.follow-up.install") : t("cli.init.capabilities.follow-up.manual")
|
|
1403
2181
|
};
|
|
1404
2182
|
}
|
|
2183
|
+
function capabilityInstallStatus(support, capability) {
|
|
2184
|
+
if (!support.capabilities[capability]) {
|
|
2185
|
+
return t("cli.init.capabilities.status.na");
|
|
2186
|
+
}
|
|
2187
|
+
return hasInstalledCapability(support, capability) ? t("cli.init.capabilities.status.installed") : t("cli.init.capabilities.status.supported");
|
|
2188
|
+
}
|
|
2189
|
+
function hasInstalledCapability(support, capability) {
|
|
2190
|
+
return support.installedCapabilities?.[capability] === true;
|
|
2191
|
+
}
|
|
1405
2192
|
function capabilityStatus(disposition) {
|
|
1406
2193
|
switch (disposition) {
|
|
1407
2194
|
case "ran":
|
|
@@ -1437,8 +2224,21 @@ function formatCapabilityDivider(widths) {
|
|
|
1437
2224
|
].join(" ");
|
|
1438
2225
|
}
|
|
1439
2226
|
function formatInitReasonMessage(supports) {
|
|
1440
|
-
|
|
1441
|
-
|
|
2227
|
+
const detected = supports.filter((support) => support.detected);
|
|
2228
|
+
const installedSkillClients = detected.filter((support) => hasInstalledCapability(support, "skill"));
|
|
2229
|
+
const hasClaudeSkill = installedSkillClients.some((support) => support.clientKind === "ClaudeCodeCLI");
|
|
2230
|
+
const hasCodexSkill = installedSkillClients.some((support) => support.clientKind === "CodexCLI");
|
|
2231
|
+
if (hasClaudeSkill && hasCodexSkill) {
|
|
2232
|
+
return t("cli.init.reason-message.multi-body");
|
|
2233
|
+
}
|
|
2234
|
+
if (hasClaudeSkill) {
|
|
2235
|
+
return t("cli.init.reason-message.claude-body");
|
|
2236
|
+
}
|
|
2237
|
+
if (hasCodexSkill) {
|
|
2238
|
+
return t("cli.init.reason-message.codex-body");
|
|
2239
|
+
}
|
|
2240
|
+
if (detected.some((support) => support.capabilities.skill)) {
|
|
2241
|
+
return t("cli.init.reason-message.installable-body");
|
|
1442
2242
|
}
|
|
1443
2243
|
return t("cli.init.reason-message.manual-body");
|
|
1444
2244
|
}
|
|
@@ -1492,8 +2292,15 @@ function sha256(content) {
|
|
|
1492
2292
|
return `sha256:${createHash("sha256").update(content).digest("hex")}`;
|
|
1493
2293
|
}
|
|
1494
2294
|
export {
|
|
2295
|
+
buildInitExecutionPlan,
|
|
2296
|
+
buildInitFabricPlan,
|
|
2297
|
+
createDefaultInitWizardAdapter,
|
|
1495
2298
|
init_default as default,
|
|
1496
2299
|
detectPackageManager,
|
|
2300
|
+
executeInitExecutionPlan,
|
|
2301
|
+
executeInitFabricPlan,
|
|
1497
2302
|
initCommand,
|
|
1498
|
-
initFabric
|
|
2303
|
+
initFabric,
|
|
2304
|
+
resolveInitExecutionPlanWithWizard,
|
|
2305
|
+
shouldUseInitWizard
|
|
1499
2306
|
};
|