@fenglimg/fabric-cli 2.0.0-rc.11 → 2.0.0-rc.15
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 +6 -4
- package/dist/chunk-AIB54QRT.js +82 -0
- package/dist/{chunk-5MQ52F42.js → chunk-AXIFEVAS.js} +16 -219
- package/dist/{chunk-WWNXR34K.js → chunk-G2CIOLD4.js} +16 -1
- package/dist/{chunk-HQLEHH4O.js → chunk-SKSYUHKK.js} +267 -40
- package/dist/chunk-UTF4YBDN.js +366 -0
- package/dist/config-7YD365I3.js +13 -0
- package/dist/{doctor-RILCO5OG.js → doctor-6XHLQJXB.js} +67 -50
- package/dist/index.js +8 -9
- package/dist/{init-C56PWHID.js → install-JLDCHAXV.js} +409 -416
- package/dist/{plan-context-hint-QMUPAXIB.js → plan-context-hint-73U4FGKO.js} +6 -1
- package/dist/{serve-NGLXHDYC.js → serve-L3X5UHG2.js} +15 -10
- package/dist/{uninstall-DBAR2JBS.js → uninstall-DD6FIFCI.js} +81 -179
- package/package.json +3 -3
- package/templates/hooks/configs/README.md +10 -6
- package/templates/hooks/configs/cursor-hooks.json +7 -10
- package/templates/hooks/knowledge-hint-broad.cjs +28 -107
- package/templates/skills/fabric-archive/SKILL.md +15 -15
- package/templates/skills/fabric-import/SKILL.md +26 -26
- package/templates/skills/fabric-review/SKILL.md +19 -19
- package/dist/chunk-AW3G7ZH5.js +0 -576
- package/dist/chunk-WPTA74BY.js +0 -184
- package/dist/hooks-NX32PPEN.js +0 -13
- package/dist/scan-66EKMNAY.js +0 -24
|
@@ -1,19 +1,13 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
} from "./chunk-WPTA74BY.js";
|
|
3
|
+
installMcpClients
|
|
4
|
+
} from "./chunk-AIB54QRT.js";
|
|
6
5
|
import {
|
|
7
6
|
detectExistingLanguage,
|
|
8
|
-
detectFramework,
|
|
9
7
|
runInitScan
|
|
10
|
-
} from "./chunk-
|
|
8
|
+
} from "./chunk-AXIFEVAS.js";
|
|
11
9
|
import {
|
|
12
|
-
|
|
13
|
-
resolveClients
|
|
14
|
-
} from "./chunk-HQLEHH4O.js";
|
|
15
|
-
import {
|
|
16
|
-
addArchiveSkillPointer,
|
|
10
|
+
addFabricKnowledgeBaseSection,
|
|
17
11
|
installArchiveHintHook,
|
|
18
12
|
installFabricArchiveSkill,
|
|
19
13
|
installFabricImportSkill,
|
|
@@ -22,175 +16,180 @@ import {
|
|
|
22
16
|
installKnowledgeHintNarrowHook,
|
|
23
17
|
mergeClaudeCodeHookConfig,
|
|
24
18
|
mergeCodexHookConfig,
|
|
25
|
-
mergeCursorHookConfig
|
|
26
|
-
|
|
19
|
+
mergeCursorHookConfig,
|
|
20
|
+
readFabricLanguagePreference
|
|
21
|
+
} from "./chunk-UTF4YBDN.js";
|
|
22
|
+
import {
|
|
23
|
+
detectClientSupports
|
|
24
|
+
} from "./chunk-SKSYUHKK.js";
|
|
27
25
|
import {
|
|
28
26
|
displayWidth,
|
|
27
|
+
hasActionHint,
|
|
29
28
|
padEnd,
|
|
30
|
-
paint
|
|
31
|
-
|
|
29
|
+
paint,
|
|
30
|
+
renderFabricError
|
|
31
|
+
} from "./chunk-G2CIOLD4.js";
|
|
32
|
+
import {
|
|
33
|
+
t
|
|
34
|
+
} from "./chunk-6ICJICVU.js";
|
|
32
35
|
import {
|
|
33
36
|
createDebugLogger,
|
|
34
37
|
resolveDevMode
|
|
35
38
|
} from "./chunk-OBQU6NHO.js";
|
|
36
|
-
import {
|
|
37
|
-
t
|
|
38
|
-
} from "./chunk-6ICJICVU.js";
|
|
39
39
|
|
|
40
|
-
// src/commands/
|
|
40
|
+
// src/commands/install.ts
|
|
41
41
|
import { randomUUID } from "crypto";
|
|
42
42
|
import { homedir } from "os";
|
|
43
43
|
import * as childProcess from "child_process";
|
|
44
|
-
import { appendFileSync, existsSync as existsSync3, mkdirSync, rmSync, statSync as
|
|
45
|
-
import { dirname, isAbsolute as
|
|
44
|
+
import { appendFileSync, existsSync as existsSync3, mkdirSync, readFileSync as readFileSync2, rmSync, statSync as statSync3, writeFileSync } from "fs";
|
|
45
|
+
import { dirname, isAbsolute as isAbsolute3, join as join3, resolve as resolve3 } from "path";
|
|
46
46
|
import { cancel, confirm, group, intro, isCancel, log, note, outro, select } from "@clack/prompts";
|
|
47
47
|
import { defaultAgentsMetaCounters } from "@fenglimg/fabric-shared";
|
|
48
48
|
import { atomicWriteJson, atomicWriteText } from "@fenglimg/fabric-shared/node/atomic-write";
|
|
49
|
-
import { defineCommand
|
|
49
|
+
import { defineCommand } from "citty";
|
|
50
50
|
import { checkLockOrThrow } from "@fenglimg/fabric-server";
|
|
51
51
|
|
|
52
|
-
// src/
|
|
53
|
-
import { existsSync } from "fs";
|
|
54
|
-
import {
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
52
|
+
// src/install/hooks-orchestrator.ts
|
|
53
|
+
import { existsSync, statSync } from "fs";
|
|
54
|
+
import { isAbsolute, join, resolve } from "path";
|
|
55
|
+
async function installHooks(target, _options = {}) {
|
|
56
|
+
const normalizedTarget = normalizeTarget(target);
|
|
57
|
+
assertExistingDirectory(normalizedTarget);
|
|
58
|
+
const results = [];
|
|
59
|
+
results.push(...await runStep(() => installFabricArchiveSkill(normalizedTarget)));
|
|
60
|
+
results.push(...await runStep(() => installFabricReviewSkill(normalizedTarget)));
|
|
61
|
+
results.push(...await runStep(() => installFabricImportSkill(normalizedTarget)));
|
|
62
|
+
results.push(...await runStep(() => installArchiveHintHook(normalizedTarget)));
|
|
63
|
+
results.push(...await runStep(() => installKnowledgeHintBroadHook(normalizedTarget)));
|
|
64
|
+
results.push(...await runStep(() => installKnowledgeHintNarrowHook(normalizedTarget)));
|
|
65
|
+
results.push(await runSingleStep("claude-hook-config", () => mergeClaudeCodeHookConfig(normalizedTarget)));
|
|
66
|
+
results.push(await runSingleStep("codex-hook-config", () => mergeCodexHookConfig(normalizedTarget)));
|
|
67
|
+
results.push(await runSingleStep("cursor-hook-config", () => mergeCursorHookConfig(normalizedTarget)));
|
|
68
|
+
const fabricLanguage = readFabricLanguagePreference(normalizedTarget);
|
|
69
|
+
results.push(...await runStep(() => addFabricKnowledgeBaseSection(normalizedTarget, fabricLanguage)));
|
|
70
|
+
results.push(...validateHookPaths(normalizedTarget));
|
|
71
|
+
return summarizeResults(results);
|
|
72
|
+
}
|
|
73
|
+
function validateHookPaths(projectRoot) {
|
|
74
|
+
const scripts = [
|
|
75
|
+
{ stepSuffix: "", hookFile: "fabric-hint.cjs" },
|
|
76
|
+
{ stepSuffix: "-broad", hookFile: "knowledge-hint-broad.cjs" },
|
|
77
|
+
{ stepSuffix: "-narrow", hookFile: "knowledge-hint-narrow.cjs" }
|
|
78
|
+
];
|
|
79
|
+
const clients = [
|
|
80
|
+
{
|
|
81
|
+
client: "claude",
|
|
82
|
+
configRel: join(".claude", "settings.json"),
|
|
83
|
+
hookDir: join(".claude", "hooks")
|
|
84
|
+
},
|
|
85
|
+
{
|
|
86
|
+
client: "codex",
|
|
87
|
+
configRel: join(".codex", "hooks.json"),
|
|
88
|
+
hookDir: join(".codex", "hooks")
|
|
89
|
+
},
|
|
90
|
+
{
|
|
91
|
+
client: "cursor",
|
|
92
|
+
configRel: join(".cursor", "hooks.json"),
|
|
93
|
+
hookDir: join(".cursor", "hooks")
|
|
94
|
+
}
|
|
95
|
+
];
|
|
96
|
+
const results = [];
|
|
97
|
+
for (const { client, configRel, hookDir } of clients) {
|
|
98
|
+
const configPath = resolve(projectRoot, configRel);
|
|
99
|
+
if (!existsSync(configPath)) {
|
|
100
|
+
results.push({
|
|
101
|
+
step: `hook-validate-${client}`,
|
|
102
|
+
path: configPath,
|
|
103
|
+
status: "skipped",
|
|
104
|
+
message: "missing-config"
|
|
105
|
+
});
|
|
106
|
+
continue;
|
|
107
|
+
}
|
|
108
|
+
for (const { stepSuffix, hookFile } of scripts) {
|
|
109
|
+
const expectedHookPath = resolve(projectRoot, hookDir, hookFile);
|
|
110
|
+
const expectedHookRel = join(hookDir, hookFile);
|
|
111
|
+
const step = `hook-validate-${client}${stepSuffix}`;
|
|
112
|
+
if (!existsSync(expectedHookPath)) {
|
|
113
|
+
results.push({
|
|
114
|
+
step,
|
|
115
|
+
path: expectedHookPath,
|
|
116
|
+
status: "error",
|
|
117
|
+
message: `hook script missing: ${expectedHookRel}`
|
|
118
|
+
});
|
|
119
|
+
continue;
|
|
120
|
+
}
|
|
121
|
+
results.push({ step, path: expectedHookPath, status: "skipped", message: "ok" });
|
|
81
122
|
}
|
|
82
|
-
clients.add(clientKind);
|
|
83
123
|
}
|
|
84
|
-
return
|
|
124
|
+
return results;
|
|
85
125
|
}
|
|
86
|
-
async function
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
126
|
+
async function runStep(fn) {
|
|
127
|
+
try {
|
|
128
|
+
return await fn();
|
|
129
|
+
} catch (error) {
|
|
130
|
+
return [
|
|
131
|
+
{
|
|
132
|
+
step: "hook-install",
|
|
133
|
+
path: "",
|
|
134
|
+
status: "error",
|
|
135
|
+
message: error instanceof Error ? error.message : String(error)
|
|
136
|
+
}
|
|
137
|
+
];
|
|
94
138
|
}
|
|
95
|
-
return parsed;
|
|
96
|
-
}
|
|
97
|
-
function resolveServerPath(override) {
|
|
98
|
-
if (override) return override;
|
|
99
|
-
if (process.env.FAB_SERVER_PATH) return resolve(process.env.FAB_SERVER_PATH);
|
|
100
|
-
return fileURLToPath(import.meta.resolve("@fenglimg/fabric-server"));
|
|
101
|
-
}
|
|
102
|
-
function writeStderr(message) {
|
|
103
|
-
process.stderr.write(`${message}
|
|
104
|
-
`);
|
|
105
139
|
}
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
description: t("cli.config.install.description")
|
|
117
|
-
},
|
|
118
|
-
args: {
|
|
119
|
-
clients: {
|
|
120
|
-
type: "string",
|
|
121
|
-
description: t("cli.config.install.args.clients.description")
|
|
122
|
-
},
|
|
123
|
-
"dry-run": {
|
|
124
|
-
type: "boolean",
|
|
125
|
-
description: t("cli.config.install.args.dry-run.description"),
|
|
126
|
-
default: false
|
|
127
|
-
}
|
|
128
|
-
},
|
|
129
|
-
async run({ args }) {
|
|
130
|
-
const selectedClients = parseClientFilter(args.clients);
|
|
131
|
-
const result = await installMcpClients(process.cwd(), {
|
|
132
|
-
clients: selectedClients === null ? void 0 : Array.from(selectedClients),
|
|
133
|
-
dryRun: args["dry-run"]
|
|
134
|
-
});
|
|
135
|
-
if (result.details.length === 0) {
|
|
136
|
-
writeStderr(t("cli.config.install.no-configs"));
|
|
137
|
-
return;
|
|
138
|
-
}
|
|
139
|
-
for (const detail of result.details) {
|
|
140
|
-
if (detail.action === "skipped") {
|
|
141
|
-
writeStderr(t("cli.config.install.no-config-path", { client: detail.client }));
|
|
142
|
-
continue;
|
|
143
|
-
}
|
|
144
|
-
if (detail.action === "dry-run" && detail.path !== null) {
|
|
145
|
-
writeStderr(t("cli.config.install.dry-run", { client: detail.client, path: detail.path }));
|
|
146
|
-
continue;
|
|
147
|
-
}
|
|
148
|
-
if (detail.path !== null) {
|
|
149
|
-
writeStderr(t("cli.config.install.wrote", { client: detail.client, path: detail.path }));
|
|
150
|
-
}
|
|
151
|
-
}
|
|
152
|
-
}
|
|
153
|
-
})
|
|
140
|
+
async function runSingleStep(step, fn) {
|
|
141
|
+
try {
|
|
142
|
+
return await fn();
|
|
143
|
+
} catch (error) {
|
|
144
|
+
return {
|
|
145
|
+
step,
|
|
146
|
+
path: "",
|
|
147
|
+
status: "error",
|
|
148
|
+
message: error instanceof Error ? error.message : String(error)
|
|
149
|
+
};
|
|
154
150
|
}
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
const workspaceRoot = resolve(target);
|
|
158
|
-
const fabricConfig = await loadFabricConfig(workspaceRoot);
|
|
159
|
-
const selectedClients = options.clients === void 0 ? null : new Set(options.clients);
|
|
160
|
-
const serverPath = resolveServerPath(options.localServerPath);
|
|
161
|
-
const writers = resolveClients(workspaceRoot, fabricConfig, { claudeMcpScope: options.claudeMcpScope }).filter(
|
|
162
|
-
(writer) => selectedClients === null ? true : selectedClients.has(writer.clientKind)
|
|
163
|
-
);
|
|
151
|
+
}
|
|
152
|
+
function summarizeResults(results) {
|
|
164
153
|
const installed = [];
|
|
165
154
|
const skipped = [];
|
|
166
|
-
const
|
|
167
|
-
for (const
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
155
|
+
const errors = [];
|
|
156
|
+
for (const r of results) {
|
|
157
|
+
switch (r.status) {
|
|
158
|
+
case "written":
|
|
159
|
+
installed.push(r.path);
|
|
160
|
+
break;
|
|
161
|
+
case "skipped":
|
|
162
|
+
skipped.push(r.path);
|
|
163
|
+
break;
|
|
164
|
+
case "error":
|
|
165
|
+
errors.push(`${r.step} ${r.path}: ${r.message ?? "unknown error"}`);
|
|
166
|
+
break;
|
|
178
167
|
}
|
|
179
|
-
await writer.write(serverPath, workspaceRoot);
|
|
180
|
-
installed.push(writer.clientKind);
|
|
181
|
-
details.push({ client: writer.clientKind, path: configPath, action: "wrote" });
|
|
182
168
|
}
|
|
183
|
-
return { installed, skipped,
|
|
169
|
+
return { installed, skipped, errors };
|
|
170
|
+
}
|
|
171
|
+
function normalizeTarget(targetInput) {
|
|
172
|
+
return isAbsolute(targetInput) ? targetInput : resolve(process.cwd(), targetInput);
|
|
173
|
+
}
|
|
174
|
+
function assertExistingDirectory(target) {
|
|
175
|
+
if (!existsSync(target) || !statSync(target).isDirectory()) {
|
|
176
|
+
throw new Error(t("cli.shared.target-invalid", { target }));
|
|
177
|
+
}
|
|
184
178
|
}
|
|
185
179
|
|
|
186
180
|
// src/scanner/forensic.ts
|
|
187
181
|
import { execFileSync } from "child_process";
|
|
188
|
-
import { existsSync as existsSync2, readdirSync, readFileSync, statSync } from "fs";
|
|
182
|
+
import { existsSync as existsSync2, readdirSync, readFileSync, statSync as statSync2 } from "fs";
|
|
189
183
|
import { createRequire } from "module";
|
|
190
|
-
import { basename, extname, isAbsolute, join, posix, relative, resolve as resolve2, sep } from "path";
|
|
184
|
+
import { basename, extname, isAbsolute as isAbsolute2, join as join2, posix, relative, resolve as resolve2, sep } from "path";
|
|
191
185
|
import {
|
|
192
186
|
forensicReportSchema
|
|
193
187
|
} from "@fenglimg/fabric-shared";
|
|
188
|
+
|
|
189
|
+
// src/scanner/detector.ts
|
|
190
|
+
import { detectFramework } from "@fenglimg/fabric-shared/node";
|
|
191
|
+
|
|
192
|
+
// src/scanner/forensic.ts
|
|
194
193
|
var require2 = createRequire(import.meta.url);
|
|
195
194
|
var IGNORED_DIRECTORIES = /* @__PURE__ */ new Set([
|
|
196
195
|
".fabric",
|
|
@@ -273,7 +272,7 @@ var parserInitPromise = null;
|
|
|
273
272
|
var languagePromiseByKind = {};
|
|
274
273
|
var parserBundlePromiseByKind = {};
|
|
275
274
|
async function buildForensicReport(targetInput) {
|
|
276
|
-
const target =
|
|
275
|
+
const target = normalizeTarget2(targetInput);
|
|
277
276
|
const framework = detectFramework(target);
|
|
278
277
|
const topology = buildTopology(target);
|
|
279
278
|
const entryPoints = collectEntryPoints(target, topology.files);
|
|
@@ -309,11 +308,11 @@ async function buildForensicReport(targetInput) {
|
|
|
309
308
|
}
|
|
310
309
|
return validation.data;
|
|
311
310
|
}
|
|
312
|
-
function
|
|
313
|
-
return
|
|
311
|
+
function normalizeTarget2(targetInput) {
|
|
312
|
+
return isAbsolute2(targetInput) ? targetInput : resolve2(process.cwd(), targetInput);
|
|
314
313
|
}
|
|
315
314
|
function buildTopology(root) {
|
|
316
|
-
|
|
315
|
+
assertExistingDirectory2(root);
|
|
317
316
|
const byExt = {};
|
|
318
317
|
const keyDirs = /* @__PURE__ */ new Set();
|
|
319
318
|
const files = [];
|
|
@@ -326,7 +325,7 @@ function buildTopology(root) {
|
|
|
326
325
|
continue;
|
|
327
326
|
}
|
|
328
327
|
for (const entry of readdirSync(current, { withFileTypes: true })) {
|
|
329
|
-
const absolutePath =
|
|
328
|
+
const absolutePath = join2(current, entry.name);
|
|
330
329
|
const relativePath = toPosixPath(relative(root, absolutePath));
|
|
331
330
|
if (relativePath.length === 0) {
|
|
332
331
|
continue;
|
|
@@ -346,7 +345,7 @@ function buildTopology(root) {
|
|
|
346
345
|
if (!entry.isFile()) {
|
|
347
346
|
continue;
|
|
348
347
|
}
|
|
349
|
-
const stats =
|
|
348
|
+
const stats = statSync2(absolutePath);
|
|
350
349
|
const extension = extname(entry.name) || "[none]";
|
|
351
350
|
byExt[extension] = (byExt[extension] ?? 0) + 1;
|
|
352
351
|
totalFiles += 1;
|
|
@@ -364,8 +363,8 @@ function buildTopology(root) {
|
|
|
364
363
|
files: files.sort((left, right) => left.relativePath.localeCompare(right.relativePath))
|
|
365
364
|
};
|
|
366
365
|
}
|
|
367
|
-
function
|
|
368
|
-
if (!existsSync2(target) || !
|
|
366
|
+
function assertExistingDirectory2(target) {
|
|
367
|
+
if (!existsSync2(target) || !statSync2(target).isDirectory()) {
|
|
369
368
|
throw new Error(`Target must be an existing directory: ${target}`);
|
|
370
369
|
}
|
|
371
370
|
}
|
|
@@ -414,7 +413,7 @@ function getEntryPointReason(relativePath) {
|
|
|
414
413
|
async function buildCodeSamples(target, entryPoints, frameworkKind, topology, packageDependencies) {
|
|
415
414
|
const samples = [];
|
|
416
415
|
for (const entryPoint of entryPoints.slice(0, SAMPLE_LIMIT)) {
|
|
417
|
-
const absolutePath =
|
|
416
|
+
const absolutePath = join2(target, ...entryPoint.path.split("/"));
|
|
418
417
|
const sample = readFirstLines(absolutePath, SAMPLE_LINE_LIMIT);
|
|
419
418
|
const patternAnalysis = await inferPatternHint(entryPoint.path, sample.snippet, {
|
|
420
419
|
frameworkKind,
|
|
@@ -451,7 +450,7 @@ function readFirstLines(path, lineLimit) {
|
|
|
451
450
|
}
|
|
452
451
|
}
|
|
453
452
|
function readPackageDependencies(target) {
|
|
454
|
-
const packageJsonPath =
|
|
453
|
+
const packageJsonPath = join2(target, "package.json");
|
|
455
454
|
if (!existsSync2(packageJsonPath)) {
|
|
456
455
|
return /* @__PURE__ */ new Map();
|
|
457
456
|
}
|
|
@@ -792,8 +791,8 @@ function scoreFrameworkConfidence(input) {
|
|
|
792
791
|
return input.configCount > 0 || input.packageCount > 0 ? "MEDIUM" : "LOW";
|
|
793
792
|
}
|
|
794
793
|
function readReadmeInfo(target) {
|
|
795
|
-
const readmePath =
|
|
796
|
-
const hasContributing = existsSync2(
|
|
794
|
+
const readmePath = join2(target, "README.md");
|
|
795
|
+
const hasContributing = existsSync2(join2(target, "CONTRIBUTING.md"));
|
|
797
796
|
if (!existsSync2(readmePath)) {
|
|
798
797
|
return {
|
|
799
798
|
quality: "missing",
|
|
@@ -1279,7 +1278,7 @@ function buildSkillRecommendations(frameworkKind, topology, readme) {
|
|
|
1279
1278
|
return recommendations;
|
|
1280
1279
|
}
|
|
1281
1280
|
function readProjectName(target) {
|
|
1282
|
-
const packageJsonPath =
|
|
1281
|
+
const packageJsonPath = join2(target, "package.json");
|
|
1283
1282
|
if (existsSync2(packageJsonPath)) {
|
|
1284
1283
|
try {
|
|
1285
1284
|
const packageJson = JSON.parse(readFileSync(packageJsonPath, "utf8"));
|
|
@@ -1293,7 +1292,7 @@ function readProjectName(target) {
|
|
|
1293
1292
|
return basename(target);
|
|
1294
1293
|
}
|
|
1295
1294
|
function getCliVersion() {
|
|
1296
|
-
return true ? "2.0.0-rc.
|
|
1295
|
+
return true ? "2.0.0-rc.15" : "unknown";
|
|
1297
1296
|
}
|
|
1298
1297
|
function sortRecord(record) {
|
|
1299
1298
|
return Object.fromEntries(Object.entries(record).sort(([left], [right]) => left.localeCompare(right)));
|
|
@@ -1302,7 +1301,7 @@ function toPosixPath(path) {
|
|
|
1302
1301
|
return path.split(sep).join("/");
|
|
1303
1302
|
}
|
|
1304
1303
|
|
|
1305
|
-
// src/commands/
|
|
1304
|
+
// src/commands/install.ts
|
|
1306
1305
|
var AGENTS_MD_DEFAULT_CONTENT = `# Project Knowledge
|
|
1307
1306
|
|
|
1308
1307
|
This project uses [Fabric](https://github.com/fenglimg/fabric) for cross-client AI knowledge management.
|
|
@@ -1312,99 +1311,60 @@ Run \`fabric doctor\` to verify state.
|
|
|
1312
1311
|
|
|
1313
1312
|
See \`.fabric/knowledge/\` for project decisions, pitfalls, guidelines, models, and processes.
|
|
1314
1313
|
`;
|
|
1315
|
-
var LOCAL_FABRIC_SERVER_PATH =
|
|
1314
|
+
var LOCAL_FABRIC_SERVER_PATH = join3("node_modules", "@fenglimg", "fabric-server", "dist", "index.js");
|
|
1316
1315
|
var FABRIC_SERVER_PACKAGE = "@fenglimg/fabric-server";
|
|
1317
1316
|
var INIT_WIZARD_GROUP_CANCELLED = /* @__PURE__ */ Symbol("init-wizard-group-cancelled");
|
|
1318
|
-
var
|
|
1317
|
+
var installCommand = defineCommand({
|
|
1319
1318
|
meta: {
|
|
1320
|
-
name: "
|
|
1321
|
-
description: t("cli.
|
|
1319
|
+
name: "install",
|
|
1320
|
+
description: t("cli.install.description")
|
|
1322
1321
|
},
|
|
1323
1322
|
args: {
|
|
1324
|
-
target: {
|
|
1325
|
-
type: "string",
|
|
1326
|
-
description: t("cli.init.args.target.description")
|
|
1327
|
-
},
|
|
1328
1323
|
debug: {
|
|
1329
1324
|
type: "boolean",
|
|
1330
|
-
description: t("cli.
|
|
1325
|
+
description: t("cli.install.args.debug.description"),
|
|
1331
1326
|
default: false
|
|
1332
1327
|
},
|
|
1333
|
-
|
|
1328
|
+
"dry-run": {
|
|
1334
1329
|
type: "boolean",
|
|
1335
|
-
description: t("cli.
|
|
1330
|
+
description: t("cli.install.args.dry-run.description"),
|
|
1336
1331
|
default: false
|
|
1337
1332
|
},
|
|
1338
|
-
|
|
1339
|
-
type: "
|
|
1340
|
-
description: t("cli.
|
|
1341
|
-
default: false
|
|
1342
|
-
},
|
|
1343
|
-
plan: {
|
|
1344
|
-
type: "boolean",
|
|
1345
|
-
description: t("cli.init.args.plan.description"),
|
|
1346
|
-
default: false
|
|
1333
|
+
target: {
|
|
1334
|
+
type: "string",
|
|
1335
|
+
description: t("cli.install.args.target.description")
|
|
1347
1336
|
},
|
|
1348
|
-
|
|
1337
|
+
yes: {
|
|
1349
1338
|
type: "boolean",
|
|
1350
|
-
description: t("cli.
|
|
1339
|
+
description: t("cli.install.args.yes.description"),
|
|
1351
1340
|
default: false
|
|
1352
|
-
},
|
|
1353
|
-
bootstrap: {
|
|
1354
|
-
type: "boolean",
|
|
1355
|
-
default: true,
|
|
1356
|
-
negativeDescription: t("cli.init.args.no-bootstrap.description")
|
|
1357
|
-
},
|
|
1358
|
-
mcp: {
|
|
1359
|
-
type: "boolean",
|
|
1360
|
-
default: true,
|
|
1361
|
-
negativeDescription: t("cli.init.args.no-mcp.description")
|
|
1362
|
-
},
|
|
1363
|
-
hooks: {
|
|
1364
|
-
type: "boolean",
|
|
1365
|
-
default: true,
|
|
1366
|
-
negativeDescription: t("cli.init.args.no-hooks.description")
|
|
1367
|
-
},
|
|
1368
|
-
interactive: {
|
|
1369
|
-
type: "boolean",
|
|
1370
|
-
description: t("cli.init.args.interactive.description"),
|
|
1371
|
-
default: true
|
|
1372
|
-
},
|
|
1373
|
-
"mcp-install": {
|
|
1374
|
-
type: "string",
|
|
1375
|
-
default: "global",
|
|
1376
|
-
description: t("cli.init.mcp.install.prompt")
|
|
1377
|
-
},
|
|
1378
|
-
scope: {
|
|
1379
|
-
type: "string",
|
|
1380
|
-
description: t("cli.init.mcp.scope.description")
|
|
1381
1341
|
}
|
|
1382
1342
|
},
|
|
1383
1343
|
async run({ args }) {
|
|
1384
1344
|
await runInitCommand(args);
|
|
1385
1345
|
}
|
|
1386
1346
|
});
|
|
1387
|
-
var
|
|
1347
|
+
var install_default = installCommand;
|
|
1388
1348
|
async function runInitCommand(args) {
|
|
1389
1349
|
const logger = createDebugLogger(args.debug);
|
|
1390
1350
|
const resolution = resolveDevMode(args.target, process.cwd());
|
|
1391
1351
|
const intent = resolveInitCliIntent(args, resolution.target);
|
|
1392
|
-
|
|
1393
|
-
|
|
1352
|
+
const fabricInitialized = existsSync3(join3(intent.target, ".fabric", "events.jsonl"));
|
|
1353
|
+
if (fabricInitialized) {
|
|
1354
|
+
try {
|
|
1355
|
+
checkLockOrThrow(intent.target);
|
|
1356
|
+
} catch (err) {
|
|
1357
|
+
if (hasActionHint(err)) {
|
|
1358
|
+
renderFabricError(err);
|
|
1359
|
+
process.exit(1);
|
|
1360
|
+
}
|
|
1361
|
+
throw err;
|
|
1362
|
+
}
|
|
1394
1363
|
}
|
|
1395
1364
|
logger(`init target source: ${resolution.source}`);
|
|
1396
1365
|
for (const step of resolution.chain) {
|
|
1397
1366
|
logger(step);
|
|
1398
1367
|
}
|
|
1399
|
-
if (intent.options.planOnly) {
|
|
1400
|
-
writeStderr2(t("cli.init.compat.plan"));
|
|
1401
|
-
}
|
|
1402
|
-
if (args.interactive === false) {
|
|
1403
|
-
writeStderr2(t("cli.init.compat.interactive"));
|
|
1404
|
-
}
|
|
1405
|
-
if (args.bootstrap === false || args.mcp === false || args.hooks === false) {
|
|
1406
|
-
writeStderr2(t("cli.init.compat.legacy-stage-flags"));
|
|
1407
|
-
}
|
|
1408
1368
|
const supports = detectClientSupports(intent.target);
|
|
1409
1369
|
const basePlan = await buildInitExecutionPlan({
|
|
1410
1370
|
target: intent.target,
|
|
@@ -1414,7 +1374,7 @@ async function runInitCommand(args) {
|
|
|
1414
1374
|
interactive: intent.interactiveSummary && !intent.wizardEnabled,
|
|
1415
1375
|
supports
|
|
1416
1376
|
});
|
|
1417
|
-
const plan = intent.wizardEnabled ? await resolveInitExecutionPlanWithWizard(basePlan,
|
|
1377
|
+
const plan = intent.wizardEnabled ? await resolveInitExecutionPlanWithWizard(basePlan, createDefaultInitWizardAdapter()) : basePlan;
|
|
1418
1378
|
if (plan === null) {
|
|
1419
1379
|
process.exitCode = 130;
|
|
1420
1380
|
return;
|
|
@@ -1426,7 +1386,7 @@ async function runInitCommand(args) {
|
|
|
1426
1386
|
return result;
|
|
1427
1387
|
}
|
|
1428
1388
|
function writeDefaultFabricConfig(fabricDir, targetRoot) {
|
|
1429
|
-
const target =
|
|
1389
|
+
const target = join3(fabricDir, "fabric-config.json");
|
|
1430
1390
|
if (existsSync3(target)) return;
|
|
1431
1391
|
const detectedLanguage = detectExistingLanguage(targetRoot);
|
|
1432
1392
|
const FABRIC_CONFIG_DEFAULTS = {
|
|
@@ -1434,7 +1394,7 @@ function writeDefaultFabricConfig(fabricDir, targetRoot) {
|
|
|
1434
1394
|
// README.md + docs/*.md (CJK ratio > 0.3 → "zh-CN", else "en"). Users
|
|
1435
1395
|
// can edit `.fabric/fabric-config.json` to override. See
|
|
1436
1396
|
// packages/shared/src/schemas/fabric-config.ts for the enum.
|
|
1437
|
-
|
|
1397
|
+
fabric_language: detectedLanguage,
|
|
1438
1398
|
// fabric-hint Stop hook Signal A (archive): time-branch threshold, hours
|
|
1439
1399
|
// since last knowledge_proposed event.
|
|
1440
1400
|
archive_hint_hours: 24,
|
|
@@ -1491,43 +1451,27 @@ function writeDefaultFabricConfig(fabricDir, targetRoot) {
|
|
|
1491
1451
|
mkdirSync(fabricDir, { recursive: true });
|
|
1492
1452
|
writeFileSync(target, JSON.stringify(FABRIC_CONFIG_DEFAULTS, null, 2) + "\n", "utf8");
|
|
1493
1453
|
log.info(
|
|
1494
|
-
`Detected and fixated
|
|
1454
|
+
`Detected and fixated fabric_language = ${detectedLanguage}; edit ${target} to override.`
|
|
1495
1455
|
);
|
|
1496
1456
|
}
|
|
1497
1457
|
function resolveInitCliIntent(args, targetInput) {
|
|
1498
|
-
const target =
|
|
1499
|
-
const mcpInstallMode =
|
|
1500
|
-
const claudeMcpScope =
|
|
1458
|
+
const target = normalizeTarget3(targetInput);
|
|
1459
|
+
const mcpInstallMode = "global";
|
|
1460
|
+
const claudeMcpScope = "project";
|
|
1501
1461
|
const terminalInteractive = isInteractiveInit();
|
|
1502
|
-
const planOnly = args
|
|
1503
|
-
const reapply = args.reapply === true;
|
|
1462
|
+
const planOnly = args["dry-run"] === true;
|
|
1504
1463
|
const options = {
|
|
1505
|
-
|
|
1506
|
-
skipBootstrap: args.bootstrap === false ? true : args.skipBootstrap,
|
|
1507
|
-
skipMcp: args.mcp === false ? true : args.skipMcp,
|
|
1508
|
-
skipHooks: args.hooks === false ? true : args.skipHooks,
|
|
1509
|
-
planOnly,
|
|
1510
|
-
reapply
|
|
1464
|
+
planOnly
|
|
1511
1465
|
};
|
|
1512
1466
|
return {
|
|
1513
1467
|
target,
|
|
1514
1468
|
options,
|
|
1515
1469
|
mcpInstallMode,
|
|
1516
1470
|
claudeMcpScope,
|
|
1517
|
-
interactiveSummary:
|
|
1471
|
+
interactiveSummary: terminalInteractive,
|
|
1518
1472
|
wizardEnabled: shouldUseInitWizard(args, terminalInteractive) && !planOnly
|
|
1519
1473
|
};
|
|
1520
1474
|
}
|
|
1521
|
-
function resolveClaudeMcpScope(raw) {
|
|
1522
|
-
if (raw === void 0 || raw === "project") {
|
|
1523
|
-
return "project";
|
|
1524
|
-
}
|
|
1525
|
-
if (raw === "user") {
|
|
1526
|
-
return "user";
|
|
1527
|
-
}
|
|
1528
|
-
writeStderr2(t("cli.init.mcp.scope.invalid", { value: raw }));
|
|
1529
|
-
return "project";
|
|
1530
|
-
}
|
|
1531
1475
|
async function buildInitExecutionPlan(input) {
|
|
1532
1476
|
const options = input.options ?? {};
|
|
1533
1477
|
const scaffold = await buildInitFabricPlan(input.target, options);
|
|
@@ -1564,17 +1508,17 @@ async function buildInitExecutionPlan(input) {
|
|
|
1564
1508
|
};
|
|
1565
1509
|
}
|
|
1566
1510
|
async function executeInitExecutionPlan(plan) {
|
|
1567
|
-
if (plan.options.force) {
|
|
1568
|
-
writeStderr2(t("cli.init.force.warning", { path: plan.target }));
|
|
1569
|
-
}
|
|
1570
|
-
if (plan.options.reapply && !plan.options.planOnly && !plan.interactive) {
|
|
1571
|
-
writeStderr2(formatInitModeBanner(plan.options));
|
|
1572
|
-
}
|
|
1573
1511
|
if (plan.interactive) {
|
|
1574
1512
|
printInitPlanSummary(plan.target, plan.options, plan.mcpInstallMode, plan.supports);
|
|
1575
1513
|
}
|
|
1514
|
+
const scaffoldStates = [
|
|
1515
|
+
{ path: plan.scaffold.metaPath, state: plan.scaffold.metaState },
|
|
1516
|
+
{ path: plan.scaffold.eventsPath, state: plan.scaffold.eventsState },
|
|
1517
|
+
{ path: plan.scaffold.forensicPath, state: plan.scaffold.forensicState }
|
|
1518
|
+
];
|
|
1576
1519
|
if (plan.options.planOnly) {
|
|
1577
1520
|
printInitPlanPreview(plan);
|
|
1521
|
+
printInitDiffStateTable(scaffoldStates);
|
|
1578
1522
|
return {
|
|
1579
1523
|
plan,
|
|
1580
1524
|
created: buildPlanOnlyScaffoldResult(plan.scaffold),
|
|
@@ -1582,6 +1526,17 @@ async function executeInitExecutionPlan(plan) {
|
|
|
1582
1526
|
finalSupports: plan.supports
|
|
1583
1527
|
};
|
|
1584
1528
|
}
|
|
1529
|
+
if (existsSync3(plan.scaffold.fabricDir) && !statSync3(plan.scaffold.fabricDir).isDirectory()) {
|
|
1530
|
+
throw new Error(
|
|
1531
|
+
t("cli.install.diff.drift-abort", { path: plan.scaffold.fabricDir })
|
|
1532
|
+
);
|
|
1533
|
+
}
|
|
1534
|
+
const drifted = scaffoldStates.find(
|
|
1535
|
+
(entry) => entry.state === "drifted" || entry.state === "user-modified"
|
|
1536
|
+
);
|
|
1537
|
+
if (drifted !== void 0) {
|
|
1538
|
+
throw new Error(t("cli.install.diff.drift-abort", { path: drifted.path }));
|
|
1539
|
+
}
|
|
1585
1540
|
let created = null;
|
|
1586
1541
|
const stageResults = [];
|
|
1587
1542
|
let finalSupports = plan.supports;
|
|
@@ -1606,6 +1561,11 @@ async function executeInitExecutionPlan(plan) {
|
|
|
1606
1561
|
exhaustiveInitExecutionStep(step);
|
|
1607
1562
|
}
|
|
1608
1563
|
}
|
|
1564
|
+
if (scaffoldStates.every((entry) => entry.state === "present-canonical")) {
|
|
1565
|
+
console.log(
|
|
1566
|
+
t("cli.install.diff.canonical", { count: String(scaffoldStates.length) })
|
|
1567
|
+
);
|
|
1568
|
+
}
|
|
1609
1569
|
return {
|
|
1610
1570
|
plan,
|
|
1611
1571
|
created: created ?? unreachableInitScaffold(),
|
|
@@ -1618,20 +1578,23 @@ function resolvePersonalFabricRoot() {
|
|
|
1618
1578
|
return process.env.FABRIC_HOME ?? homedir();
|
|
1619
1579
|
}
|
|
1620
1580
|
async function buildInitFabricPlan(target, options) {
|
|
1621
|
-
|
|
1622
|
-
const fabricDir =
|
|
1623
|
-
const agentsMdPath =
|
|
1581
|
+
assertExistingDirectory3(target);
|
|
1582
|
+
const fabricDir = join3(target, ".fabric");
|
|
1583
|
+
const agentsMdPath = join3(target, "AGENTS.md");
|
|
1624
1584
|
const agentsMdAction = existsSync3(agentsMdPath) ? "preserved" : "created";
|
|
1625
|
-
const knowledgeDir =
|
|
1626
|
-
const personalKnowledgeDir =
|
|
1627
|
-
const forensicPath =
|
|
1628
|
-
const eventsPath =
|
|
1629
|
-
const metaPath =
|
|
1585
|
+
const knowledgeDir = join3(fabricDir, "knowledge");
|
|
1586
|
+
const personalKnowledgeDir = join3(resolvePersonalFabricRoot(), ".fabric", "knowledge");
|
|
1587
|
+
const forensicPath = join3(fabricDir, "forensic.json");
|
|
1588
|
+
const eventsPath = join3(fabricDir, "events.jsonl");
|
|
1589
|
+
const metaPath = join3(fabricDir, "agents.meta.json");
|
|
1630
1590
|
const replaceFabricDir = shouldReplaceWritableDirectory(fabricDir, options);
|
|
1631
1591
|
const knowledgeDirAction = existsSync3(knowledgeDir) ? "overwritten" : "created";
|
|
1632
|
-
const
|
|
1633
|
-
const
|
|
1634
|
-
const
|
|
1592
|
+
const metaClassification = classifyFreshPath(metaPath, "structural");
|
|
1593
|
+
const eventsClassification = classifyFreshPath(eventsPath, "presence");
|
|
1594
|
+
const forensicClassification = classifyFreshPath(forensicPath, "always-rewrite");
|
|
1595
|
+
const metaAction = diffStateToWriteAction(metaClassification.state);
|
|
1596
|
+
const eventsAction = diffStateToWriteAction(eventsClassification.state);
|
|
1597
|
+
const forensicAction = diffStateToWriteAction(forensicClassification.state);
|
|
1635
1598
|
const forensicReport = await buildForensicReport(target);
|
|
1636
1599
|
const meta = createInitialMeta();
|
|
1637
1600
|
return {
|
|
@@ -1651,11 +1614,13 @@ async function buildInitFabricPlan(target, options) {
|
|
|
1651
1614
|
eventsAction,
|
|
1652
1615
|
forensicPath,
|
|
1653
1616
|
forensicAction,
|
|
1654
|
-
forensicReport
|
|
1617
|
+
forensicReport,
|
|
1618
|
+
metaState: metaClassification.state,
|
|
1619
|
+
eventsState: eventsClassification.state,
|
|
1620
|
+
forensicState: forensicClassification.state
|
|
1655
1621
|
};
|
|
1656
1622
|
}
|
|
1657
1623
|
async function executeInitFabricPlan(plan) {
|
|
1658
|
-
const isReapply = plan.options?.reapply === true;
|
|
1659
1624
|
if (plan.replaceFabricDir) {
|
|
1660
1625
|
rmSync(plan.fabricDir, { force: true });
|
|
1661
1626
|
}
|
|
@@ -1666,9 +1631,9 @@ async function executeInitFabricPlan(plan) {
|
|
|
1666
1631
|
}
|
|
1667
1632
|
mkdirSync(plan.knowledgeDir, { recursive: true });
|
|
1668
1633
|
for (const sub of KNOWLEDGE_SUBDIRS) {
|
|
1669
|
-
const teamSubDir =
|
|
1634
|
+
const teamSubDir = join3(plan.knowledgeDir, sub);
|
|
1670
1635
|
mkdirSync(teamSubDir, { recursive: true });
|
|
1671
|
-
const teamGitkeep =
|
|
1636
|
+
const teamGitkeep = join3(teamSubDir, ".gitkeep");
|
|
1672
1637
|
if (!existsSync3(teamGitkeep)) {
|
|
1673
1638
|
writeFileSync(teamGitkeep, "", "utf8");
|
|
1674
1639
|
}
|
|
@@ -1676,36 +1641,49 @@ async function executeInitFabricPlan(plan) {
|
|
|
1676
1641
|
try {
|
|
1677
1642
|
mkdirSync(plan.personalKnowledgeDir, { recursive: true });
|
|
1678
1643
|
for (const sub of KNOWLEDGE_SUBDIRS) {
|
|
1679
|
-
mkdirSync(
|
|
1644
|
+
mkdirSync(join3(plan.personalKnowledgeDir, sub), { recursive: true });
|
|
1680
1645
|
}
|
|
1681
1646
|
} catch {
|
|
1682
1647
|
}
|
|
1683
|
-
|
|
1684
|
-
|
|
1685
|
-
|
|
1686
|
-
|
|
1687
|
-
|
|
1688
|
-
writeFileSync(plan.eventsPath, "", "utf8");
|
|
1689
|
-
}
|
|
1690
|
-
} else {
|
|
1648
|
+
if (plan.metaState === "missing") {
|
|
1649
|
+
preparePlannedPath(plan.metaPath, plan.metaAction);
|
|
1650
|
+
await atomicWriteJson(plan.metaPath, plan.meta);
|
|
1651
|
+
}
|
|
1652
|
+
if (plan.eventsState === "missing") {
|
|
1691
1653
|
preparePlannedPath(plan.eventsPath, plan.eventsAction);
|
|
1654
|
+
mkdirSync(dirname(plan.eventsPath), { recursive: true });
|
|
1692
1655
|
writeFileSync(plan.eventsPath, "", "utf8");
|
|
1693
1656
|
}
|
|
1694
1657
|
preparePlannedPath(plan.forensicPath, plan.forensicAction);
|
|
1695
1658
|
await atomicWriteJson(plan.forensicPath, plan.forensicReport);
|
|
1696
|
-
|
|
1659
|
+
const wasCanonicalReRun = plan.metaState === "present-canonical" && plan.eventsState === "present-canonical";
|
|
1660
|
+
if (!wasCanonicalReRun) {
|
|
1697
1661
|
try {
|
|
1698
1662
|
await runInitScan(plan.target, { source: "init" });
|
|
1699
1663
|
} catch (error) {
|
|
1700
|
-
|
|
1664
|
+
writeStderr(
|
|
1701
1665
|
`[warn] init-scan failed: ${error instanceof Error ? error.message : String(error)} \u2014 re-run \`fab scan\` to populate baseline knowledge entries.`
|
|
1702
1666
|
);
|
|
1703
1667
|
}
|
|
1704
1668
|
}
|
|
1705
|
-
if (
|
|
1706
|
-
|
|
1707
|
-
|
|
1708
|
-
|
|
1669
|
+
if (existsSync3(plan.eventsPath)) {
|
|
1670
|
+
const applied = [];
|
|
1671
|
+
const canonical = [];
|
|
1672
|
+
const drifted = [];
|
|
1673
|
+
for (const entry of [
|
|
1674
|
+
{ path: plan.metaPath, state: plan.metaState },
|
|
1675
|
+
{ path: plan.eventsPath, state: plan.eventsState },
|
|
1676
|
+
{ path: plan.forensicPath, state: plan.forensicState }
|
|
1677
|
+
]) {
|
|
1678
|
+
if (entry.state === "missing") {
|
|
1679
|
+
applied.push(entry.path);
|
|
1680
|
+
} else if (entry.state === "present-canonical") {
|
|
1681
|
+
canonical.push(entry.path);
|
|
1682
|
+
} else {
|
|
1683
|
+
drifted.push(entry.path);
|
|
1684
|
+
}
|
|
1685
|
+
}
|
|
1686
|
+
appendInstallDiffLedgerEvent(plan.eventsPath, { applied, canonical, drifted });
|
|
1709
1687
|
}
|
|
1710
1688
|
return {
|
|
1711
1689
|
agentsMdPath: plan.agentsMdPath,
|
|
@@ -1725,16 +1703,16 @@ async function initFabric(target, options) {
|
|
|
1725
1703
|
return await executeInitFabricPlan(await buildInitFabricPlan(target, options));
|
|
1726
1704
|
}
|
|
1727
1705
|
function shouldUseInitWizard(args, terminalInteractive = isInteractiveInit()) {
|
|
1728
|
-
return terminalInteractive && args.
|
|
1706
|
+
return terminalInteractive && args.yes !== true;
|
|
1729
1707
|
}
|
|
1730
|
-
async function resolveInitExecutionPlanWithWizard(basePlan,
|
|
1708
|
+
async function resolveInitExecutionPlanWithWizard(basePlan, wizardAdapter) {
|
|
1731
1709
|
const selection = await wizardAdapter.run({
|
|
1732
1710
|
target: basePlan.target,
|
|
1733
1711
|
options: basePlan.options,
|
|
1734
1712
|
supports: basePlan.supports,
|
|
1735
1713
|
mcpInstallMode: basePlan.mcpInstallMode,
|
|
1736
1714
|
claudeMcpScope: basePlan.claudeMcpScope,
|
|
1737
|
-
lockedStages:
|
|
1715
|
+
lockedStages: []
|
|
1738
1716
|
});
|
|
1739
1717
|
if (selection === null) {
|
|
1740
1718
|
return null;
|
|
@@ -1772,27 +1750,36 @@ function printInitScaffoldResult(created) {
|
|
|
1772
1750
|
function printInitPostSetup(plan, stageResults, finalSupports) {
|
|
1773
1751
|
if (shouldPrintHooksNextStep(plan.options, stageResults)) {
|
|
1774
1752
|
console.log(
|
|
1775
|
-
t("cli.
|
|
1753
|
+
t("cli.install.next-step", {
|
|
1776
1754
|
label: nextLabel(),
|
|
1777
|
-
message: paint.muted(t("cli.
|
|
1755
|
+
message: paint.muted(t("cli.install.next-step.message"))
|
|
1778
1756
|
})
|
|
1779
1757
|
);
|
|
1780
1758
|
}
|
|
1781
1759
|
console.log(
|
|
1782
|
-
t("cli.
|
|
1760
|
+
t("cli.install.reason-message", {
|
|
1783
1761
|
label: reasonLabel(),
|
|
1784
1762
|
message: paint.muted(formatInitReasonMessage(finalSupports))
|
|
1785
1763
|
})
|
|
1786
1764
|
);
|
|
1787
1765
|
printInitStageSummary(stageResults);
|
|
1788
1766
|
printInitCapabilitySummary(finalSupports, stageResults, plan.options);
|
|
1767
|
+
const fabricLanguage = readFabricLanguagePreference(plan.target);
|
|
1768
|
+
console.log(
|
|
1769
|
+
paint.muted(t("cli.install.language_preference_hint", { value: fabricLanguage }))
|
|
1770
|
+
);
|
|
1771
|
+
}
|
|
1772
|
+
function printInitDiffStateTable(entries) {
|
|
1773
|
+
for (const entry of entries) {
|
|
1774
|
+
console.log(` ${formatDiffFileState(entry.state)} ${entry.path}`);
|
|
1775
|
+
}
|
|
1789
1776
|
}
|
|
1790
1777
|
function printInitPlanPreview(plan) {
|
|
1791
|
-
console.log(t("cli.
|
|
1778
|
+
console.log(t("cli.install.plan.preview-title"));
|
|
1792
1779
|
printInitPlanSummary(plan.target, plan.options, plan.mcpInstallMode, plan.supports);
|
|
1793
1780
|
console.log(
|
|
1794
|
-
t("cli.
|
|
1795
|
-
mode:
|
|
1781
|
+
t("cli.install.plan.preview-result", {
|
|
1782
|
+
mode: t("cli.install.mode.default"),
|
|
1796
1783
|
bootstrap: yesNoLabel(!plan.options.skipBootstrap),
|
|
1797
1784
|
mcp: yesNoLabel(!plan.options.skipMcp),
|
|
1798
1785
|
hooks: yesNoLabel(!plan.options.skipHooks)
|
|
@@ -1822,7 +1809,7 @@ async function executeInitStagePlan(plan, stageName) {
|
|
|
1822
1809
|
if (stage.skipped) {
|
|
1823
1810
|
return { name: stageName, disposition: "skipped" };
|
|
1824
1811
|
}
|
|
1825
|
-
console.log(formatInitStageHeader(t(`cli.
|
|
1812
|
+
console.log(formatInitStageHeader(t(`cli.install.stages.${stageName}`)));
|
|
1826
1813
|
try {
|
|
1827
1814
|
switch (stage.name) {
|
|
1828
1815
|
case "bootstrap": {
|
|
@@ -1836,13 +1823,14 @@ async function executeInitStagePlan(plan, stageName) {
|
|
|
1836
1823
|
installResults.push(await runBestEffortSingle("claude-hook-config", () => mergeClaudeCodeHookConfig(plan.target)));
|
|
1837
1824
|
installResults.push(await runBestEffortSingle("codex-hook-config", () => mergeCodexHookConfig(plan.target)));
|
|
1838
1825
|
installResults.push(await runBestEffortSingle("cursor-hook-config", () => mergeCursorHookConfig(plan.target)));
|
|
1839
|
-
|
|
1826
|
+
const fabricLanguage = readFabricLanguagePreference(plan.target);
|
|
1827
|
+
installResults.push(...await runBestEffort("section", () => addFabricKnowledgeBaseSection(plan.target, fabricLanguage)));
|
|
1840
1828
|
const installedCount = installResults.filter((r) => r.status === "written").length;
|
|
1841
1829
|
const skippedCount = installResults.filter((r) => r.status === "skipped").length;
|
|
1842
1830
|
const errorCount = installResults.filter((r) => r.status === "error").length;
|
|
1843
1831
|
for (const result of installResults) {
|
|
1844
1832
|
if (result.status === "error") {
|
|
1845
|
-
|
|
1833
|
+
writeStderr(`bootstrap ${result.step} ${result.path}: ${result.message ?? "unknown error"}`);
|
|
1846
1834
|
}
|
|
1847
1835
|
}
|
|
1848
1836
|
const note2 = errorCount > 0 ? `errors=${errorCount}` : void 0;
|
|
@@ -1852,15 +1840,14 @@ async function executeInitStagePlan(plan, stageName) {
|
|
|
1852
1840
|
case "mcp": {
|
|
1853
1841
|
if (stage.installMode === "local") {
|
|
1854
1842
|
const manager = stage.packageManager ?? detectPackageManager(plan.target);
|
|
1855
|
-
|
|
1856
|
-
|
|
1843
|
+
writeStderr(t("cli.install.mcp.install.local"));
|
|
1844
|
+
writeStderr(t("cli.install.mcp.local.installing", { manager }));
|
|
1857
1845
|
installLocalFabricServer(plan.target, manager);
|
|
1858
|
-
|
|
1846
|
+
writeStderr(t("cli.install.mcp.local.installed"));
|
|
1859
1847
|
} else {
|
|
1860
|
-
|
|
1848
|
+
writeStderr(t("cli.install.mcp.install.global"));
|
|
1861
1849
|
}
|
|
1862
1850
|
const result = await installMcpClients(plan.target, {
|
|
1863
|
-
force: plan.options.force,
|
|
1864
1851
|
localServerPath: stage.localServerPath,
|
|
1865
1852
|
claudeMcpScope: stage.claudeMcpScope
|
|
1866
1853
|
});
|
|
@@ -1872,7 +1859,7 @@ async function executeInitStagePlan(plan, stageName) {
|
|
|
1872
1859
|
return { name: "mcp", disposition: "ran" };
|
|
1873
1860
|
}
|
|
1874
1861
|
case "hooks": {
|
|
1875
|
-
const result = await installHooks(plan.target
|
|
1862
|
+
const result = await installHooks(plan.target);
|
|
1876
1863
|
console.log(formatInitStageResult("hooks", "completed", result.installed.length, result.skipped.length));
|
|
1877
1864
|
return { name: "hooks", disposition: "ran" };
|
|
1878
1865
|
}
|
|
@@ -1880,30 +1867,66 @@ async function executeInitStagePlan(plan, stageName) {
|
|
|
1880
1867
|
return exhaustiveInitStagePlan(stage);
|
|
1881
1868
|
}
|
|
1882
1869
|
} catch (error) {
|
|
1883
|
-
|
|
1870
|
+
writeStderr(formatInitStageFailure(stageName, error));
|
|
1884
1871
|
return { name: stageName, disposition: "failed" };
|
|
1885
1872
|
}
|
|
1886
1873
|
}
|
|
1887
|
-
function shouldReplaceWritableDirectory(path,
|
|
1874
|
+
function shouldReplaceWritableDirectory(path, _options) {
|
|
1888
1875
|
if (!existsSync3(path)) {
|
|
1889
1876
|
return false;
|
|
1890
1877
|
}
|
|
1891
|
-
if (
|
|
1878
|
+
if (statSync3(path).isDirectory()) {
|
|
1892
1879
|
return false;
|
|
1893
1880
|
}
|
|
1894
|
-
|
|
1895
|
-
throw new Error(t("cli.init.errors.abort-existing", { path }));
|
|
1896
|
-
}
|
|
1897
|
-
return true;
|
|
1881
|
+
return false;
|
|
1898
1882
|
}
|
|
1899
|
-
function
|
|
1883
|
+
function classifyFreshPath(path, strategy) {
|
|
1900
1884
|
if (!existsSync3(path)) {
|
|
1901
|
-
return "
|
|
1885
|
+
return { path, state: "missing" };
|
|
1886
|
+
}
|
|
1887
|
+
let stat;
|
|
1888
|
+
try {
|
|
1889
|
+
stat = statSync3(path);
|
|
1890
|
+
} catch (error) {
|
|
1891
|
+
return {
|
|
1892
|
+
path,
|
|
1893
|
+
state: "user-modified",
|
|
1894
|
+
reason: error instanceof Error ? error.message : String(error)
|
|
1895
|
+
};
|
|
1896
|
+
}
|
|
1897
|
+
if (!stat.isFile()) {
|
|
1898
|
+
return { path, state: "user-modified", reason: "expected a file" };
|
|
1899
|
+
}
|
|
1900
|
+
if (strategy === "presence" || strategy === "always-rewrite") {
|
|
1901
|
+
return { path, state: "present-canonical" };
|
|
1902
1902
|
}
|
|
1903
|
-
|
|
1904
|
-
|
|
1903
|
+
try {
|
|
1904
|
+
const raw = readFileSync2(path, "utf8");
|
|
1905
|
+
const parsed = JSON.parse(raw);
|
|
1906
|
+
if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
1907
|
+
return { path, state: "user-modified", reason: "not a JSON object" };
|
|
1908
|
+
}
|
|
1909
|
+
const record = parsed;
|
|
1910
|
+
const hasRevision = typeof record["revision"] === "string";
|
|
1911
|
+
const hasNodes = record["nodes"] !== void 0 && record["nodes"] !== null && typeof record["nodes"] === "object" && !Array.isArray(record["nodes"]);
|
|
1912
|
+
const hasCounters = record["counters"] !== void 0 && record["counters"] !== null && typeof record["counters"] === "object" && !Array.isArray(record["counters"]);
|
|
1913
|
+
if (!hasRevision || !hasNodes || !hasCounters) {
|
|
1914
|
+
return { path, state: "drifted", reason: "missing required AgentsMeta fields" };
|
|
1915
|
+
}
|
|
1916
|
+
return { path, state: "present-canonical" };
|
|
1917
|
+
} catch (error) {
|
|
1918
|
+
return {
|
|
1919
|
+
path,
|
|
1920
|
+
state: "user-modified",
|
|
1921
|
+
reason: error instanceof Error ? error.message : String(error)
|
|
1922
|
+
};
|
|
1905
1923
|
}
|
|
1906
|
-
|
|
1924
|
+
}
|
|
1925
|
+
function diffStateToWriteAction(_state) {
|
|
1926
|
+
return "created";
|
|
1927
|
+
}
|
|
1928
|
+
function formatDiffFileState(state) {
|
|
1929
|
+
return t(`cli.install.diff.state.${state}`);
|
|
1907
1930
|
}
|
|
1908
1931
|
function preparePlannedPath(path, action) {
|
|
1909
1932
|
mkdirSync(dirname(path), { recursive: true });
|
|
@@ -1914,59 +1937,59 @@ function preparePlannedPath(path, action) {
|
|
|
1914
1937
|
function createDefaultInitWizardAdapter() {
|
|
1915
1938
|
return {
|
|
1916
1939
|
async run(context) {
|
|
1917
|
-
intro(t("cli.
|
|
1940
|
+
intro(t("cli.install.wizard.intro"));
|
|
1918
1941
|
note(
|
|
1919
|
-
t("cli.
|
|
1942
|
+
t("cli.install.wizard.overview.body", {
|
|
1920
1943
|
target: context.target,
|
|
1921
1944
|
mode: formatInitModeBadge(context.options)
|
|
1922
1945
|
}),
|
|
1923
|
-
t("cli.
|
|
1946
|
+
t("cli.install.wizard.overview.title")
|
|
1924
1947
|
);
|
|
1925
1948
|
printInitPlanSummary(context.target, context.options, context.mcpInstallMode, context.supports);
|
|
1926
|
-
log.step(t("cli.
|
|
1949
|
+
log.step(t("cli.install.wizard.step.target"));
|
|
1927
1950
|
const continueWithTarget = await confirm({
|
|
1928
|
-
message: t("cli.
|
|
1951
|
+
message: t("cli.install.wizard.target.confirm", { target: context.target }),
|
|
1929
1952
|
initialValue: true
|
|
1930
1953
|
});
|
|
1931
1954
|
if (isCancel(continueWithTarget) || !continueWithTarget) {
|
|
1932
1955
|
emitInitWizardCancellation();
|
|
1933
1956
|
return null;
|
|
1934
1957
|
}
|
|
1935
|
-
log.step(t("cli.
|
|
1958
|
+
log.step(t("cli.install.wizard.step.plan"));
|
|
1936
1959
|
let groupedSelection;
|
|
1937
1960
|
try {
|
|
1938
1961
|
groupedSelection = await group(
|
|
1939
1962
|
{
|
|
1940
1963
|
bootstrap: async () => context.lockedStages.includes("bootstrap") ? false : confirmInGroup({
|
|
1941
|
-
message: t("cli.
|
|
1964
|
+
message: t("cli.install.wizard.stage.bootstrap", {
|
|
1942
1965
|
defaultValue: formatPromptDefault(!context.options.skipBootstrap)
|
|
1943
1966
|
}),
|
|
1944
1967
|
initialValue: !context.options.skipBootstrap
|
|
1945
1968
|
}),
|
|
1946
1969
|
mcp: async () => context.lockedStages.includes("mcp") ? false : confirmInGroup({
|
|
1947
|
-
message: t("cli.
|
|
1970
|
+
message: t("cli.install.wizard.stage.mcp", {
|
|
1948
1971
|
defaultValue: formatPromptDefault(!context.options.skipMcp)
|
|
1949
1972
|
}),
|
|
1950
1973
|
initialValue: !context.options.skipMcp
|
|
1951
1974
|
}),
|
|
1952
1975
|
mcpInstallMode: async ({ results }) => results.mcp ? selectMcpInstallModeInGroup({
|
|
1953
|
-
message: t("cli.
|
|
1976
|
+
message: t("cli.install.wizard.mcp-install", { defaultValue: context.mcpInstallMode }),
|
|
1954
1977
|
initialValue: context.mcpInstallMode,
|
|
1955
1978
|
options: [
|
|
1956
|
-
{ value: "global", label: "global", hint: t("cli.
|
|
1957
|
-
{ value: "local", label: "local", hint: t("cli.
|
|
1979
|
+
{ value: "global", label: "global", hint: t("cli.install.mcp.install.global") },
|
|
1980
|
+
{ value: "local", label: "local", hint: t("cli.install.mcp.install.local") }
|
|
1958
1981
|
]
|
|
1959
1982
|
}) : context.mcpInstallMode,
|
|
1960
1983
|
claudeMcpScope: async ({ results }) => results.mcp ? selectClaudeMcpScopeInGroup({
|
|
1961
|
-
message: t("cli.
|
|
1984
|
+
message: t("cli.install.wizard.mcp-scope", { defaultValue: context.claudeMcpScope }),
|
|
1962
1985
|
initialValue: context.claudeMcpScope,
|
|
1963
1986
|
options: [
|
|
1964
|
-
{ value: "project", label: "project", hint: t("cli.
|
|
1965
|
-
{ value: "user", label: "user", hint: t("cli.
|
|
1987
|
+
{ value: "project", label: "project", hint: t("cli.install.mcp.scope.project") },
|
|
1988
|
+
{ value: "user", label: "user", hint: t("cli.install.mcp.scope.user") }
|
|
1966
1989
|
]
|
|
1967
1990
|
}) : context.claudeMcpScope,
|
|
1968
1991
|
hooks: async () => context.lockedStages.includes("hooks") ? false : confirmInGroup({
|
|
1969
|
-
message: t("cli.
|
|
1992
|
+
message: t("cli.install.wizard.stage.hooks", {
|
|
1970
1993
|
defaultValue: formatPromptDefault(!context.options.skipHooks)
|
|
1971
1994
|
}),
|
|
1972
1995
|
initialValue: !context.options.skipHooks
|
|
@@ -1995,23 +2018,23 @@ function createDefaultInitWizardAdapter() {
|
|
|
1995
2018
|
skipMcp: !groupedSelection.mcp,
|
|
1996
2019
|
skipHooks: !groupedSelection.hooks
|
|
1997
2020
|
};
|
|
1998
|
-
log.step(t("cli.
|
|
2021
|
+
log.step(t("cli.install.wizard.step.review"));
|
|
1999
2022
|
printInitPlanSummary(context.target, previewOptions, groupedSelection.mcpInstallMode, context.supports);
|
|
2000
2023
|
const confirmed = await confirm({
|
|
2001
|
-
message: t("cli.
|
|
2024
|
+
message: t("cli.install.wizard.execute.confirm"),
|
|
2002
2025
|
initialValue: true
|
|
2003
2026
|
});
|
|
2004
2027
|
if (isCancel(confirmed) || !confirmed) {
|
|
2005
2028
|
emitInitWizardCancellation();
|
|
2006
2029
|
return null;
|
|
2007
2030
|
}
|
|
2008
|
-
outro(t("cli.
|
|
2031
|
+
outro(t("cli.install.wizard.outro"));
|
|
2009
2032
|
return groupedSelection;
|
|
2010
2033
|
}
|
|
2011
2034
|
};
|
|
2012
2035
|
}
|
|
2013
2036
|
function emitInitWizardCancellation() {
|
|
2014
|
-
cancel(t("cli.
|
|
2037
|
+
cancel(t("cli.install.wizard.cancelled"));
|
|
2015
2038
|
}
|
|
2016
2039
|
async function confirmInGroup(options) {
|
|
2017
2040
|
const result = await confirm(options);
|
|
@@ -2042,74 +2065,42 @@ async function selectClaudeMcpScopeInGroup(options) {
|
|
|
2042
2065
|
}
|
|
2043
2066
|
return result;
|
|
2044
2067
|
}
|
|
2045
|
-
function collectLockedWizardStages(args) {
|
|
2046
|
-
const lockedStages = [];
|
|
2047
|
-
if (args.bootstrap === false) {
|
|
2048
|
-
lockedStages.push("bootstrap");
|
|
2049
|
-
}
|
|
2050
|
-
if (args.mcp === false) {
|
|
2051
|
-
lockedStages.push("mcp");
|
|
2052
|
-
}
|
|
2053
|
-
if (args.hooks === false) {
|
|
2054
|
-
lockedStages.push("hooks");
|
|
2055
|
-
}
|
|
2056
|
-
return lockedStages;
|
|
2057
|
-
}
|
|
2058
2068
|
function formatPromptDefault(value) {
|
|
2059
2069
|
return value ? "Y/n" : "y/N";
|
|
2060
2070
|
}
|
|
2061
2071
|
function formatInitModeBanner(options) {
|
|
2062
|
-
if (options.planOnly && options.reapply) {
|
|
2063
|
-
return t("cli.init.plan.mode-banner.plan-reapply");
|
|
2064
|
-
}
|
|
2065
2072
|
if (options.planOnly) {
|
|
2066
|
-
return t("cli.
|
|
2067
|
-
}
|
|
2068
|
-
if (options.reapply) {
|
|
2069
|
-
return t("cli.init.plan.mode-banner.reapply");
|
|
2073
|
+
return t("cli.install.plan.mode-banner.plan");
|
|
2070
2074
|
}
|
|
2071
|
-
return t("cli.
|
|
2075
|
+
return t("cli.install.plan.mode-banner.default");
|
|
2072
2076
|
}
|
|
2073
2077
|
function formatInitModeBadge(options) {
|
|
2074
|
-
if (options.planOnly && options.reapply) {
|
|
2075
|
-
return t("cli.init.mode.badge.plan-reapply");
|
|
2076
|
-
}
|
|
2077
2078
|
if (options.planOnly) {
|
|
2078
|
-
return t("cli.
|
|
2079
|
-
}
|
|
2080
|
-
if (options.reapply) {
|
|
2081
|
-
return t("cli.init.mode.badge.reapply");
|
|
2079
|
+
return t("cli.install.mode.badge.plan");
|
|
2082
2080
|
}
|
|
2083
|
-
return t("cli.
|
|
2081
|
+
return t("cli.install.mode.badge.default");
|
|
2084
2082
|
}
|
|
2085
|
-
function
|
|
2086
|
-
return
|
|
2083
|
+
function normalizeTarget3(targetInput) {
|
|
2084
|
+
return isAbsolute3(targetInput) ? targetInput : resolve3(process.cwd(), targetInput);
|
|
2087
2085
|
}
|
|
2088
|
-
function
|
|
2089
|
-
if (!existsSync3(target) || !
|
|
2086
|
+
function assertExistingDirectory3(target) {
|
|
2087
|
+
if (!existsSync3(target) || !statSync3(target).isDirectory()) {
|
|
2090
2088
|
throw new Error(`Target must be an existing directory: ${target}`);
|
|
2091
2089
|
}
|
|
2092
2090
|
}
|
|
2093
2091
|
function detectPackageManager(cwd) {
|
|
2094
2092
|
const workspaceRoot = resolve3(cwd);
|
|
2095
|
-
if (existsSync3(
|
|
2093
|
+
if (existsSync3(join3(workspaceRoot, "pnpm-lock.yaml"))) {
|
|
2096
2094
|
return "pnpm";
|
|
2097
2095
|
}
|
|
2098
|
-
if (existsSync3(
|
|
2096
|
+
if (existsSync3(join3(workspaceRoot, "yarn.lock"))) {
|
|
2099
2097
|
return "yarn";
|
|
2100
2098
|
}
|
|
2101
|
-
if (existsSync3(
|
|
2099
|
+
if (existsSync3(join3(workspaceRoot, "package-lock.json"))) {
|
|
2102
2100
|
return "npm";
|
|
2103
2101
|
}
|
|
2104
2102
|
return "npm";
|
|
2105
2103
|
}
|
|
2106
|
-
function resolveMcpInstallMode(rawMode) {
|
|
2107
|
-
if (rawMode === void 0 || rawMode === "global" || rawMode === "local") {
|
|
2108
|
-
return rawMode ?? "global";
|
|
2109
|
-
}
|
|
2110
|
-
writeStderr2(t("cli.init.mcp.install.invalid", { value: rawMode }));
|
|
2111
|
-
return "global";
|
|
2112
|
-
}
|
|
2113
2104
|
function installLocalFabricServer(target, manager) {
|
|
2114
2105
|
const installArgs = manager === "npm" ? ["install", "-D", FABRIC_SERVER_PACKAGE] : ["add", "-D", FABRIC_SERVER_PACKAGE];
|
|
2115
2106
|
childProcess.execFileSync(manager, installArgs, {
|
|
@@ -2125,14 +2116,16 @@ function createInitialMeta() {
|
|
|
2125
2116
|
counters: defaultAgentsMetaCounters()
|
|
2126
2117
|
};
|
|
2127
2118
|
}
|
|
2128
|
-
function
|
|
2119
|
+
function appendInstallDiffLedgerEvent(eventsPath, payload) {
|
|
2129
2120
|
const event = {
|
|
2130
2121
|
kind: "fabric-event",
|
|
2131
2122
|
id: `event:${randomUUID()}`,
|
|
2132
2123
|
ts: Date.now(),
|
|
2133
2124
|
schema_version: 1,
|
|
2134
|
-
event_type: "
|
|
2135
|
-
|
|
2125
|
+
event_type: "install_diff_applied",
|
|
2126
|
+
applied: payload.applied,
|
|
2127
|
+
canonical: payload.canonical,
|
|
2128
|
+
drifted: payload.drifted
|
|
2136
2129
|
};
|
|
2137
2130
|
const line = `${JSON.stringify(event)}
|
|
2138
2131
|
`;
|
|
@@ -2183,7 +2176,7 @@ function printInitStageSummary(stageResults) {
|
|
|
2183
2176
|
console.log(formatInitStageSummaryLine("failed", collectInitStageNames(stageResults, "failed")));
|
|
2184
2177
|
}
|
|
2185
2178
|
function formatInitStageSummaryLine(disposition, stages) {
|
|
2186
|
-
const label = disposition === "ran" ? paint.success(t("cli.
|
|
2179
|
+
const label = disposition === "ran" ? paint.success(t("cli.install.stages.summary.ran")) : disposition === "skipped" ? paint.muted(t("cli.install.stages.summary.skipped")) : paint.error(t("cli.install.stages.summary.failed"));
|
|
2187
2180
|
return `${label}: ${stages.length > 0 ? stages.join(", ") : t("cli.shared.none")}`;
|
|
2188
2181
|
}
|
|
2189
2182
|
function collectInitStageNames(stageResults, disposition) {
|
|
@@ -2196,11 +2189,11 @@ function isInteractiveInit() {
|
|
|
2196
2189
|
return Boolean(process.stdin.isTTY) && Boolean(process.stdout.isTTY) && Boolean(process.stderr.isTTY);
|
|
2197
2190
|
}
|
|
2198
2191
|
function printInitPlanSummary(target, options, mcpInstallMode, supports) {
|
|
2199
|
-
console.log(t("cli.
|
|
2192
|
+
console.log(t("cli.install.plan.title"));
|
|
2200
2193
|
console.log(formatInitModeBanner(options));
|
|
2201
|
-
console.log(t("cli.
|
|
2194
|
+
console.log(t("cli.install.plan.target", { target }));
|
|
2202
2195
|
console.log(
|
|
2203
|
-
t("cli.
|
|
2196
|
+
t("cli.install.plan.actions", {
|
|
2204
2197
|
bootstrap: yesNoLabel(!options.skipBootstrap),
|
|
2205
2198
|
mcp: yesNoLabel(!options.skipMcp),
|
|
2206
2199
|
hooks: yesNoLabel(!options.skipHooks),
|
|
@@ -2209,11 +2202,11 @@ function printInitPlanSummary(target, options, mcpInstallMode, supports) {
|
|
|
2209
2202
|
);
|
|
2210
2203
|
const detected = supports.filter((support) => support.detected);
|
|
2211
2204
|
console.log(
|
|
2212
|
-
t("cli.
|
|
2205
|
+
t("cli.install.plan.detected", {
|
|
2213
2206
|
clients: detected.length > 0 ? detected.map((support) => support.label).join(", ") : t("cli.shared.none")
|
|
2214
2207
|
})
|
|
2215
2208
|
);
|
|
2216
|
-
console.log(t("cli.
|
|
2209
|
+
console.log(t("cli.install.plan.writes"));
|
|
2217
2210
|
console.log(` - ${target}/.fabric/knowledge/{decisions,pitfalls,guidelines,models,processes,pending}/`);
|
|
2218
2211
|
console.log(` - ${target}/.fabric/agents.meta.json`);
|
|
2219
2212
|
console.log(` - ${target}/.fabric/events.jsonl`);
|
|
@@ -2223,18 +2216,18 @@ function printInitPlanSummary(target, options, mcpInstallMode, supports) {
|
|
|
2223
2216
|
function printInitCapabilitySummary(supports, stageResults, options) {
|
|
2224
2217
|
const detected = supports.filter((support) => support.detected);
|
|
2225
2218
|
if (detected.length === 0) {
|
|
2226
|
-
console.log(t("cli.
|
|
2219
|
+
console.log(t("cli.install.capabilities.none"));
|
|
2227
2220
|
return;
|
|
2228
2221
|
}
|
|
2229
|
-
console.log(t("cli.
|
|
2222
|
+
console.log(t("cli.install.capabilities.title"));
|
|
2230
2223
|
const rows = detected.map((support) => toCapabilityRow(support, stageResults, options));
|
|
2231
2224
|
const headers = {
|
|
2232
|
-
client: t("cli.
|
|
2233
|
-
bootstrap: t("cli.
|
|
2234
|
-
mcp: t("cli.
|
|
2235
|
-
hook: t("cli.
|
|
2236
|
-
skill: t("cli.
|
|
2237
|
-
followUp: t("cli.
|
|
2225
|
+
client: t("cli.install.capabilities.header.client"),
|
|
2226
|
+
bootstrap: t("cli.install.capabilities.header.bootstrap"),
|
|
2227
|
+
mcp: t("cli.install.capabilities.header.mcp"),
|
|
2228
|
+
hook: t("cli.install.capabilities.header.hook"),
|
|
2229
|
+
skill: t("cli.install.capabilities.header.skill"),
|
|
2230
|
+
followUp: t("cli.install.capabilities.header.follow-up")
|
|
2238
2231
|
};
|
|
2239
2232
|
const widths = {
|
|
2240
2233
|
client: Math.max(displayWidth(headers.client), ...rows.map((row) => displayWidth(row.client))),
|
|
@@ -2252,8 +2245,8 @@ function printInitCapabilitySummary(supports, stageResults, options) {
|
|
|
2252
2245
|
}
|
|
2253
2246
|
function toCapabilityRow(support, stageResults, options) {
|
|
2254
2247
|
const stage = (name) => stageResults.find((entry) => entry.name === name)?.disposition ?? null;
|
|
2255
|
-
const bootstrap = support.capabilities.bootstrap ? capabilityStatus(options.skipBootstrap ? "skipped" : stage("bootstrap")) : t("cli.
|
|
2256
|
-
const mcp = support.capabilities.mcp ? capabilityStatus(options.skipMcp ? "skipped" : stage("mcp")) : t("cli.
|
|
2248
|
+
const bootstrap = support.capabilities.bootstrap ? capabilityStatus(options.skipBootstrap ? "skipped" : stage("bootstrap")) : t("cli.install.capabilities.status.na");
|
|
2249
|
+
const mcp = support.capabilities.mcp ? capabilityStatus(options.skipMcp ? "skipped" : stage("mcp")) : t("cli.install.capabilities.status.na");
|
|
2257
2250
|
const hook = capabilityInstallStatus(support, "hook");
|
|
2258
2251
|
const skill = capabilityInstallStatus(support, "skill");
|
|
2259
2252
|
return {
|
|
@@ -2262,14 +2255,14 @@ function toCapabilityRow(support, stageResults, options) {
|
|
|
2262
2255
|
mcp,
|
|
2263
2256
|
hook,
|
|
2264
2257
|
skill,
|
|
2265
|
-
followUp: hasInstalledCapability(support, "skill") ? t("cli.
|
|
2258
|
+
followUp: hasInstalledCapability(support, "skill") ? t("cli.install.capabilities.follow-up.ready") : support.capabilities.skill ? t("cli.install.capabilities.follow-up.install") : t("cli.install.capabilities.follow-up.manual")
|
|
2266
2259
|
};
|
|
2267
2260
|
}
|
|
2268
2261
|
function capabilityInstallStatus(support, capability) {
|
|
2269
2262
|
if (!support.capabilities[capability]) {
|
|
2270
|
-
return t("cli.
|
|
2263
|
+
return t("cli.install.capabilities.status.na");
|
|
2271
2264
|
}
|
|
2272
|
-
return hasInstalledCapability(support, capability) ? t("cli.
|
|
2265
|
+
return hasInstalledCapability(support, capability) ? t("cli.install.capabilities.status.installed") : t("cli.install.capabilities.status.supported");
|
|
2273
2266
|
}
|
|
2274
2267
|
function hasInstalledCapability(support, capability) {
|
|
2275
2268
|
return support.installedCapabilities?.[capability] === true;
|
|
@@ -2277,15 +2270,15 @@ function hasInstalledCapability(support, capability) {
|
|
|
2277
2270
|
function capabilityStatus(disposition) {
|
|
2278
2271
|
switch (disposition) {
|
|
2279
2272
|
case "ran":
|
|
2280
|
-
return t("cli.
|
|
2273
|
+
return t("cli.install.capabilities.status.ready");
|
|
2281
2274
|
case "skipped":
|
|
2282
|
-
return t("cli.
|
|
2275
|
+
return t("cli.install.capabilities.status.skipped");
|
|
2283
2276
|
case "failed":
|
|
2284
|
-
return t("cli.
|
|
2277
|
+
return t("cli.install.capabilities.status.failed");
|
|
2285
2278
|
case null:
|
|
2286
|
-
return t("cli.
|
|
2279
|
+
return t("cli.install.capabilities.status.na");
|
|
2287
2280
|
default:
|
|
2288
|
-
return t("cli.
|
|
2281
|
+
return t("cli.install.capabilities.status.ready");
|
|
2289
2282
|
}
|
|
2290
2283
|
}
|
|
2291
2284
|
function formatCapabilityTableRow(row, widths) {
|
|
@@ -2311,21 +2304,21 @@ function formatCapabilityDivider(widths) {
|
|
|
2311
2304
|
function formatInitReasonMessage(supports) {
|
|
2312
2305
|
const detected = supports.filter((support) => support.detected);
|
|
2313
2306
|
if (detected.some((support) => support.capabilities.skill)) {
|
|
2314
|
-
return t("cli.
|
|
2307
|
+
return t("cli.install.reason-message.installable-body");
|
|
2315
2308
|
}
|
|
2316
|
-
return t("cli.
|
|
2309
|
+
return t("cli.install.reason-message.manual-body");
|
|
2317
2310
|
}
|
|
2318
2311
|
function yesNoLabel(value) {
|
|
2319
2312
|
return value ? t("cli.shared.yes") : t("cli.shared.no");
|
|
2320
2313
|
}
|
|
2321
2314
|
function formatInitPathAction(path, action) {
|
|
2322
|
-
return t("cli.
|
|
2315
|
+
return t("cli.install.created-path", { label: labelForInitWriteAction(action), path });
|
|
2323
2316
|
}
|
|
2324
2317
|
function formatAgentsMdAction(path, action) {
|
|
2325
2318
|
if (action === "preserved") {
|
|
2326
|
-
return t("cli.
|
|
2319
|
+
return t("cli.install.skipped-existing-path", { label: skippedLabel(), path });
|
|
2327
2320
|
}
|
|
2328
|
-
return t("cli.
|
|
2321
|
+
return t("cli.install.created-path", { label: createdLabel(), path });
|
|
2329
2322
|
}
|
|
2330
2323
|
function labelForInitWriteAction(action) {
|
|
2331
2324
|
return action === "overwritten" ? overwrittenLabel() : createdLabel();
|
|
@@ -2343,18 +2336,18 @@ function reasonLabel() {
|
|
|
2343
2336
|
return paint.human(t("cli.shared.reason"));
|
|
2344
2337
|
}
|
|
2345
2338
|
function overwrittenLabel() {
|
|
2346
|
-
return paint.warn(t("cli.
|
|
2339
|
+
return paint.warn(t("cli.install.label.overwritten"));
|
|
2347
2340
|
}
|
|
2348
2341
|
function completedStageLabel() {
|
|
2349
|
-
return paint.success(t("cli.
|
|
2342
|
+
return paint.success(t("cli.install.stages.completed"));
|
|
2350
2343
|
}
|
|
2351
2344
|
function skippedStageLabel() {
|
|
2352
|
-
return paint.muted(t("cli.
|
|
2345
|
+
return paint.muted(t("cli.install.stages.skipped"));
|
|
2353
2346
|
}
|
|
2354
2347
|
function failedStageLabel() {
|
|
2355
|
-
return paint.error(t("cli.
|
|
2348
|
+
return paint.error(t("cli.install.stages.failed"));
|
|
2356
2349
|
}
|
|
2357
|
-
function
|
|
2350
|
+
function writeStderr(message) {
|
|
2358
2351
|
process.stderr.write(`${message}
|
|
2359
2352
|
`);
|
|
2360
2353
|
}
|
|
@@ -2362,12 +2355,12 @@ export {
|
|
|
2362
2355
|
buildInitExecutionPlan,
|
|
2363
2356
|
buildInitFabricPlan,
|
|
2364
2357
|
createDefaultInitWizardAdapter,
|
|
2365
|
-
|
|
2358
|
+
install_default as default,
|
|
2366
2359
|
detectPackageManager,
|
|
2367
2360
|
executeInitExecutionPlan,
|
|
2368
2361
|
executeInitFabricPlan,
|
|
2369
|
-
initCommand,
|
|
2370
2362
|
initFabric,
|
|
2363
|
+
installCommand,
|
|
2371
2364
|
resolveInitExecutionPlanWithWizard,
|
|
2372
2365
|
runInitCommand,
|
|
2373
2366
|
shouldUseInitWizard
|