@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.
@@ -2,19 +2,10 @@
2
2
  import {
3
3
  buildFabricBootstrapGuide,
4
4
  installBootstrap
5
- } from "./chunk-TKTWHAKV.js";
5
+ } from "./chunk-T2WJF5I3.js";
6
6
  import {
7
7
  detectFramework
8
- } from "./chunk-AZRKMFRY.js";
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
- function buildForensicReport(targetInput) {
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 codeSamples = buildCodeSamples(target, entryPoints);
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
- return entryPoints.slice(0, SAMPLE_LIMIT).map((entryPoint) => {
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
- return {
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 inferPatternHint(relativePath, snippet) {
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: determineConfidence(1, cocosCoOccurring, astLevel),
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: astLevel,
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: determineConfidence(1, reactCoOccurring, false),
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: determineConfidence(1, coOccurring, false),
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: determineConfidence(1, coOccurring, false),
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.3.1" : "unknown";
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
- const logger = createDebugLogger(args.debug);
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 initFabric(target, options) {
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
- prepareWritableDirectory(fabricDir, options);
1051
- const bootstrapAction = prepareFreshPath(bootstrapPath, options);
1052
- const metaAction = prepareFreshPath(metaPath, options);
1053
- const humanLockAction = prepareFreshPath(humanLockPath, options);
1054
- const forensicAction = prepareFreshPath(forensicPath, options);
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
- claudeSkillPath,
1086
- claudeSkillAction,
1087
- claudeHookPath,
1088
- claudeHookAction,
1089
- claudeSettingsPath,
1090
- claudeSettingsAction
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, note) {
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 = note ? ` ${paint.muted(`(${note})`)}` : "";
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.capabilities.hook ? capabilityStatus("ran") : t("cli.init.capabilities.status.na");
1395
- const skill = support.capabilities.skill ? t("cli.init.capabilities.status.installed") : t("cli.init.capabilities.status.manual");
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.ready") : t("cli.init.capabilities.follow-up.manual")
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
- if (supports.some((support) => support.detected && support.capabilities.skill)) {
1441
- return t("cli.init.reason-message.body");
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
  };