@fenglimg/fabric-cli 1.6.0 → 1.8.0-rc.1
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 +8 -14
- package/dist/{chunk-QSAEGVKE.js → chunk-NMMUETVK.js} +4 -8
- package/dist/{chunk-AEOYCVBG.js → chunk-QPCRBQ5Y.js} +52 -5
- package/dist/doctor-F52XWWZC.js +98 -0
- package/dist/index.js +5 -20
- package/dist/{init-LBVOI2QI.js → init-AEO5JU7R.js} +1084 -167
- package/dist/{scan-QH76LC7Z.js → scan-NNBNGIZG.js} +2 -4
- package/dist/{serve-4J2CQY25.js → serve-466QXQ5Q.js} +17 -9
- package/package.json +5 -7
- package/templates/agents-md/AGENTS.md.template +7 -7
- package/templates/agents-md/variants/cocos.md +7 -7
- package/templates/agents-md/variants/next.md +7 -7
- package/templates/agents-md/variants/vite.md +7 -7
- package/templates/bootstrap/CLAUDE.md +3 -1
- package/templates/bootstrap/GEMINI.md +3 -1
- package/templates/bootstrap/codex-AGENTS-header.md +3 -1
- package/templates/bootstrap/cursor-fabric-bootstrap.mdc +5 -6
- package/templates/bootstrap/roo-fabric.md +5 -6
- package/templates/bootstrap/windsurf-fabric.md +5 -6
- package/templates/claude-skills/fabric-init/SKILL.md +163 -0
- package/templates/codex-skills/fabric-init/SKILL.md +153 -18
- package/templates/husky/pre-commit +9 -24
- package/templates/skill-source/fabric-init/SOURCE.md +157 -0
- package/templates/skill-source/fabric-init/clients.json +17 -0
- package/dist/approve-YT4DEABS.js +0 -138
- package/dist/bootstrap-VGL3AR26.js +0 -16
- package/dist/chunk-2YW5CJ32.js +0 -147
- package/dist/chunk-6ICJICVU.js +0 -10
- package/dist/chunk-BEKSXO5N.js +0 -442
- package/dist/chunk-BVTMVW5M.js +0 -159
- package/dist/chunk-KOAEIH72.js +0 -270
- package/dist/chunk-L43IGJ6X.js +0 -106
- package/dist/chunk-T2WJF5I3.js +0 -254
- package/dist/chunk-WWNXR34K.js +0 -49
- package/dist/chunk-YDZJRLHL.js +0 -155
- package/dist/config-EC5L2QNI.js +0 -16
- package/dist/doctor-4BPYHV7V.js +0 -134
- package/dist/hooks-ZSWVH2JD.js +0 -12
- package/dist/human-lint-YSFOZHZ7.js +0 -13
- package/dist/ledger-append-3MDNR3GU.js +0 -10
- package/dist/pre-commit-53ENJDRZ.js +0 -98
- package/dist/sync-meta-IZR2WLIL.js +0 -16
- package/dist/update-M5M5PYKE.js +0 -116
- package/templates/claude-skills/agents-md-init/SKILL.md +0 -86
- package/templates/fabric/human-lock.json +0 -12
|
@@ -1,47 +1,892 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
|
-
|
|
4
|
-
installBootstrap
|
|
5
|
-
} from "./chunk-T2WJF5I3.js";
|
|
6
|
-
import {
|
|
3
|
+
createScanReport,
|
|
7
4
|
detectFramework
|
|
8
|
-
} from "./chunk-
|
|
5
|
+
} from "./chunk-NMMUETVK.js";
|
|
9
6
|
import {
|
|
10
7
|
createDebugLogger,
|
|
11
|
-
resolveDevMode
|
|
12
|
-
} from "./chunk-AEOYCVBG.js";
|
|
13
|
-
import {
|
|
14
8
|
displayWidth,
|
|
15
9
|
padEnd,
|
|
16
|
-
paint
|
|
17
|
-
|
|
18
|
-
|
|
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
|
-
import {
|
|
10
|
+
paint,
|
|
11
|
+
readFabricConfig,
|
|
12
|
+
resolveDevMode,
|
|
28
13
|
t
|
|
29
|
-
} from "./chunk-
|
|
14
|
+
} from "./chunk-QPCRBQ5Y.js";
|
|
30
15
|
|
|
31
16
|
// src/commands/init.ts
|
|
32
|
-
import { createHash } from "crypto";
|
|
17
|
+
import { createHash, randomUUID } from "crypto";
|
|
33
18
|
import * as childProcess from "child_process";
|
|
34
|
-
import { chmodSync, copyFileSync, existsSync as
|
|
35
|
-
import { dirname, isAbsolute as
|
|
36
|
-
import { fileURLToPath } from "url";
|
|
19
|
+
import { appendFileSync, chmodSync as chmodSync2, copyFileSync, existsSync as existsSync9, mkdirSync as mkdirSync3, readdirSync as readdirSync2, readFileSync as readFileSync4, rmSync, statSync as statSync3, writeFileSync } from "fs";
|
|
20
|
+
import { dirname as dirname5, isAbsolute as isAbsolute4, join as join8, parse as parse3, resolve as resolve9 } from "path";
|
|
21
|
+
import { fileURLToPath as fileURLToPath4 } from "url";
|
|
37
22
|
import { cancel, confirm, group, intro, isCancel, log, note, outro, select } from "@clack/prompts";
|
|
23
|
+
import { atomicWriteJson as atomicWriteJson3, atomicWriteText as atomicWriteText4 } from "@fenglimg/fabric-shared/node/atomic-write";
|
|
24
|
+
import { defineCommand as defineCommand4 } from "citty";
|
|
25
|
+
import { checkLockOrThrow } from "@fenglimg/fabric-server";
|
|
26
|
+
|
|
27
|
+
// src/bootstrap-guide.ts
|
|
28
|
+
import { existsSync, mkdirSync, readFileSync } from "fs";
|
|
29
|
+
import { dirname, isAbsolute, join, parse, resolve } from "path";
|
|
30
|
+
import { fileURLToPath } from "url";
|
|
31
|
+
import { atomicWriteText } from "@fenglimg/fabric-shared/node/atomic-write";
|
|
32
|
+
var AGENTS_TEMPLATE_BY_FRAMEWORK = {
|
|
33
|
+
"cocos-creator": "templates/agents-md/variants/cocos.md",
|
|
34
|
+
vite: "templates/agents-md/variants/vite.md",
|
|
35
|
+
next: "templates/agents-md/variants/next.md"
|
|
36
|
+
};
|
|
37
|
+
var FABRIC_GUIDE_PATH = ".fabric/bootstrap/README.md";
|
|
38
|
+
async function buildFabricBootstrapGuide(target) {
|
|
39
|
+
const workspaceRoot = normalizeTarget(target);
|
|
40
|
+
const scanReport = await createScanReport(workspaceRoot);
|
|
41
|
+
const template = readFileSync(findBootstrapTemplatePath(scanReport.framework.kind), "utf8");
|
|
42
|
+
const packageName = readPackageName(workspaceRoot) ?? parse(workspaceRoot).base;
|
|
43
|
+
return ensureTrailingNewline(
|
|
44
|
+
template.replaceAll("{ projectName }", packageName).replaceAll("{ frameworkKind }", scanReport.framework.kind)
|
|
45
|
+
);
|
|
46
|
+
}
|
|
47
|
+
async function ensureFabricBootstrapGuide(workspaceRoot, force) {
|
|
48
|
+
const guidePath = resolve(workspaceRoot, FABRIC_GUIDE_PATH);
|
|
49
|
+
if (existsSync(guidePath) && !force) {
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
mkdirSync(dirname(guidePath), { recursive: true });
|
|
53
|
+
await atomicWriteText(guidePath, await buildFabricBootstrapGuide(workspaceRoot));
|
|
54
|
+
}
|
|
55
|
+
function findBootstrapTemplatePath(frameworkKind) {
|
|
56
|
+
const relativePath = AGENTS_TEMPLATE_BY_FRAMEWORK[frameworkKind] ?? "templates/agents-md/AGENTS.md.template";
|
|
57
|
+
return findTemplatePath(relativePath);
|
|
58
|
+
}
|
|
59
|
+
function readPackageName(target) {
|
|
60
|
+
const packageJsonPath = join(target, "package.json");
|
|
61
|
+
if (!existsSync(packageJsonPath)) {
|
|
62
|
+
return void 0;
|
|
63
|
+
}
|
|
64
|
+
try {
|
|
65
|
+
const packageJson = JSON.parse(readFileSync(packageJsonPath, "utf8"));
|
|
66
|
+
return packageJson.name;
|
|
67
|
+
} catch {
|
|
68
|
+
return void 0;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
function findTemplatePath(relativePath) {
|
|
72
|
+
const currentModuleDir = dirname(fileURLToPath(import.meta.url));
|
|
73
|
+
const candidates = [
|
|
74
|
+
...templateCandidatesFrom(process.cwd(), relativePath),
|
|
75
|
+
...templateCandidatesFrom(currentModuleDir, relativePath)
|
|
76
|
+
];
|
|
77
|
+
for (const candidate of candidates) {
|
|
78
|
+
if (existsSync(candidate)) {
|
|
79
|
+
return candidate;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
throw new Error(t("cli.shared.template-not-found", { path: relativePath }));
|
|
83
|
+
}
|
|
84
|
+
function templateCandidatesFrom(start, relativePath) {
|
|
85
|
+
const candidates = [];
|
|
86
|
+
let current = resolve(start);
|
|
87
|
+
while (true) {
|
|
88
|
+
candidates.push(join(current, ...relativePath.split("/")));
|
|
89
|
+
const parent = dirname(current);
|
|
90
|
+
if (parent === current || parse(current).root === current) {
|
|
91
|
+
break;
|
|
92
|
+
}
|
|
93
|
+
current = parent;
|
|
94
|
+
}
|
|
95
|
+
return candidates.reverse();
|
|
96
|
+
}
|
|
97
|
+
function ensureTrailingNewline(content) {
|
|
98
|
+
return content.endsWith("\n") ? content : `${content}
|
|
99
|
+
`;
|
|
100
|
+
}
|
|
101
|
+
function normalizeTarget(targetInput) {
|
|
102
|
+
return isAbsolute(targetInput) ? targetInput : resolve(process.cwd(), targetInput);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// src/commands/bootstrap.ts
|
|
106
|
+
import { resolve as resolve5 } from "path";
|
|
38
107
|
import { defineCommand } from "citty";
|
|
39
108
|
|
|
109
|
+
// src/config/resolver.ts
|
|
110
|
+
import { existsSync as existsSync5 } from "fs";
|
|
111
|
+
import { join as join5 } from "path";
|
|
112
|
+
import { homedir as homedir4 } from "os";
|
|
113
|
+
|
|
114
|
+
// src/config/claude-code.ts
|
|
115
|
+
import { existsSync as existsSync3 } from "fs";
|
|
116
|
+
import { join as join3, resolve as resolve3 } from "path";
|
|
117
|
+
import { homedir as homedir2, platform } from "os";
|
|
118
|
+
|
|
119
|
+
// src/config/json.ts
|
|
120
|
+
import { existsSync as existsSync2 } from "fs";
|
|
121
|
+
import { mkdir, readFile } from "fs/promises";
|
|
122
|
+
import { dirname as dirname2, join as join2, resolve as resolve2 } from "path";
|
|
123
|
+
import { homedir } from "os";
|
|
124
|
+
import { atomicWriteJson } from "@fenglimg/fabric-shared/node/atomic-write";
|
|
125
|
+
|
|
126
|
+
// src/config/writer.ts
|
|
127
|
+
function createServerEntry(serverPath) {
|
|
128
|
+
return {
|
|
129
|
+
command: process.execPath,
|
|
130
|
+
args: [serverPath]
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// src/config/json.ts
|
|
135
|
+
function deepMerge(target, source) {
|
|
136
|
+
if (target === null || typeof target !== "object" || Array.isArray(target) || source === null || typeof source !== "object" || Array.isArray(source)) {
|
|
137
|
+
return source;
|
|
138
|
+
}
|
|
139
|
+
const out = { ...target };
|
|
140
|
+
for (const key of Object.keys(source)) {
|
|
141
|
+
out[key] = deepMerge(
|
|
142
|
+
target[key],
|
|
143
|
+
source[key]
|
|
144
|
+
);
|
|
145
|
+
}
|
|
146
|
+
return out;
|
|
147
|
+
}
|
|
148
|
+
function expandHome(filePath) {
|
|
149
|
+
if (filePath === "~") {
|
|
150
|
+
return homedir();
|
|
151
|
+
}
|
|
152
|
+
if (filePath.startsWith("~/")) {
|
|
153
|
+
return join2(homedir(), filePath.slice(2));
|
|
154
|
+
}
|
|
155
|
+
return filePath;
|
|
156
|
+
}
|
|
157
|
+
function normalizeConfigPath(filePath) {
|
|
158
|
+
return resolve2(expandHome(filePath));
|
|
159
|
+
}
|
|
160
|
+
async function readJsonConfig(configPath) {
|
|
161
|
+
try {
|
|
162
|
+
const raw = await readFile(configPath, "utf8");
|
|
163
|
+
if (raw.trim().length === 0) {
|
|
164
|
+
return {};
|
|
165
|
+
}
|
|
166
|
+
const parsed = JSON.parse(raw);
|
|
167
|
+
if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
168
|
+
throw new Error(`Expected JSON object in ${configPath}`);
|
|
169
|
+
}
|
|
170
|
+
return parsed;
|
|
171
|
+
} catch (error) {
|
|
172
|
+
if (error instanceof Error && "code" in error && error.code === "ENOENT") {
|
|
173
|
+
return {};
|
|
174
|
+
}
|
|
175
|
+
throw error;
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
async function writeJsonClientConfig(configPath, serverEntry) {
|
|
179
|
+
const existing = await readJsonConfig(configPath);
|
|
180
|
+
const merged = deepMerge(existing, { mcpServers: { fabric: serverEntry } });
|
|
181
|
+
await mkdir(dirname2(configPath), { recursive: true });
|
|
182
|
+
await atomicWriteJson(configPath, merged, { indent: 2 });
|
|
183
|
+
}
|
|
184
|
+
var JsonClientConfigWriter = class {
|
|
185
|
+
configuredPath;
|
|
186
|
+
constructor(configuredPath) {
|
|
187
|
+
this.configuredPath = configuredPath;
|
|
188
|
+
}
|
|
189
|
+
async detect(workspaceRoot, overridePath) {
|
|
190
|
+
const explicitPath = overridePath ?? this.configuredPath;
|
|
191
|
+
if (explicitPath !== void 0) {
|
|
192
|
+
return normalizeConfigPath(explicitPath);
|
|
193
|
+
}
|
|
194
|
+
const configPath = this.defaultPath(workspaceRoot);
|
|
195
|
+
return configPath === null ? null : normalizeConfigPath(configPath);
|
|
196
|
+
}
|
|
197
|
+
async write(serverPath, workspaceRoot, overridePath) {
|
|
198
|
+
const configPath = await this.detect(workspaceRoot, overridePath);
|
|
199
|
+
if (configPath === null) {
|
|
200
|
+
return;
|
|
201
|
+
}
|
|
202
|
+
await writeJsonClientConfig(configPath, createServerEntry(serverPath));
|
|
203
|
+
}
|
|
204
|
+
};
|
|
205
|
+
var ClaudeCodeCLIWriter = class extends JsonClientConfigWriter {
|
|
206
|
+
clientKind = "ClaudeCodeCLI";
|
|
207
|
+
scope;
|
|
208
|
+
constructor(configuredPath, scope = "project") {
|
|
209
|
+
super(configuredPath);
|
|
210
|
+
this.scope = scope;
|
|
211
|
+
}
|
|
212
|
+
// Writes to project-level .mcp.json (per Claude Code MCP spec) by default,
|
|
213
|
+
// or ~/.claude.json for user scope.
|
|
214
|
+
// Detection still checks ~/.claude to confirm Claude Code is installed.
|
|
215
|
+
defaultPath(workspaceRoot) {
|
|
216
|
+
const globalClaudeDir = join2(homedir(), ".claude");
|
|
217
|
+
const projectClaudeDir = join2(workspaceRoot, ".claude");
|
|
218
|
+
if (!existsSync2(globalClaudeDir) && !existsSync2(projectClaudeDir)) {
|
|
219
|
+
return null;
|
|
220
|
+
}
|
|
221
|
+
return this.scope === "user" ? join2(homedir(), ".claude.json") : join2(workspaceRoot, ".mcp.json");
|
|
222
|
+
}
|
|
223
|
+
};
|
|
224
|
+
var CursorWriter = class extends JsonClientConfigWriter {
|
|
225
|
+
clientKind = "Cursor";
|
|
226
|
+
constructor(configuredPath) {
|
|
227
|
+
super(configuredPath);
|
|
228
|
+
}
|
|
229
|
+
defaultPath(workspaceRoot) {
|
|
230
|
+
const cursorDir = join2(workspaceRoot, ".cursor");
|
|
231
|
+
return existsSync2(cursorDir) ? join2(cursorDir, "mcp.json") : null;
|
|
232
|
+
}
|
|
233
|
+
};
|
|
234
|
+
|
|
235
|
+
// src/config/claude-code.ts
|
|
236
|
+
function getClaudeDesktopConfigPath() {
|
|
237
|
+
const os = platform();
|
|
238
|
+
if (os === "darwin") {
|
|
239
|
+
return join3(homedir2(), "Library", "Application Support", "Claude", "claude_desktop_config.json");
|
|
240
|
+
}
|
|
241
|
+
if (os === "win32") {
|
|
242
|
+
return join3(process.env.APPDATA ?? join3(homedir2(), "AppData", "Roaming"), "Claude", "claude_desktop_config.json");
|
|
243
|
+
}
|
|
244
|
+
return join3(homedir2(), ".config", "Claude", "claude_desktop_config.json");
|
|
245
|
+
}
|
|
246
|
+
var ClaudeCodeDesktopWriter = class {
|
|
247
|
+
clientKind = "ClaudeCodeDesktop";
|
|
248
|
+
configuredPath;
|
|
249
|
+
constructor(configuredPath) {
|
|
250
|
+
this.configuredPath = configuredPath;
|
|
251
|
+
}
|
|
252
|
+
async detect(_workspaceRoot, overridePath) {
|
|
253
|
+
const configPath = normalizeConfigPath(overridePath ?? this.configuredPath ?? getClaudeDesktopConfigPath());
|
|
254
|
+
return existsSync3(configPath) || overridePath !== void 0 || this.configuredPath !== void 0 ? configPath : null;
|
|
255
|
+
}
|
|
256
|
+
async write(serverPath, workspaceRoot, overridePath) {
|
|
257
|
+
const configPath = await this.detect(workspaceRoot, overridePath);
|
|
258
|
+
if (configPath === null) {
|
|
259
|
+
return;
|
|
260
|
+
}
|
|
261
|
+
await writeJsonClientConfig(configPath, {
|
|
262
|
+
command: process.execPath,
|
|
263
|
+
args: [serverPath]
|
|
264
|
+
});
|
|
265
|
+
}
|
|
266
|
+
};
|
|
267
|
+
|
|
268
|
+
// src/config/toml.ts
|
|
269
|
+
import { existsSync as existsSync4 } from "fs";
|
|
270
|
+
import { mkdir as mkdir2, readFile as readFile2 } from "fs/promises";
|
|
271
|
+
import { dirname as dirname3, join as join4, resolve as resolve4 } from "path";
|
|
272
|
+
import { homedir as homedir3 } from "os";
|
|
273
|
+
import { atomicWriteText as atomicWriteText2 } from "@fenglimg/fabric-shared/node/atomic-write";
|
|
274
|
+
function expandHome2(filePath) {
|
|
275
|
+
if (filePath === "~") {
|
|
276
|
+
return homedir3();
|
|
277
|
+
}
|
|
278
|
+
if (filePath.startsWith("~/")) {
|
|
279
|
+
return join4(homedir3(), filePath.slice(2));
|
|
280
|
+
}
|
|
281
|
+
return filePath;
|
|
282
|
+
}
|
|
283
|
+
function escapeTomlString(value) {
|
|
284
|
+
return JSON.stringify(value);
|
|
285
|
+
}
|
|
286
|
+
function serializeTomlStringArray(values) {
|
|
287
|
+
return `[${values.map((value) => escapeTomlString(value)).join(", ")}]`;
|
|
288
|
+
}
|
|
289
|
+
function serializeTomlInlineTable(values) {
|
|
290
|
+
const entries = Object.entries(values).sort(([left], [right]) => left.localeCompare(right)).map(([key, value]) => `${key} = ${escapeTomlString(value)}`);
|
|
291
|
+
return `{ ${entries.join(", ")} }`;
|
|
292
|
+
}
|
|
293
|
+
function serializeCodexServerBlock(serverName, serverEntry) {
|
|
294
|
+
const lines = [
|
|
295
|
+
`[mcp_servers.${serverName}]`,
|
|
296
|
+
`command = ${escapeTomlString(serverEntry.command)}`,
|
|
297
|
+
`args = ${serializeTomlStringArray(serverEntry.args)}`
|
|
298
|
+
];
|
|
299
|
+
if (serverEntry.env !== void 0 && Object.keys(serverEntry.env).length > 0) {
|
|
300
|
+
lines.push(`env = ${serializeTomlInlineTable(serverEntry.env)}`);
|
|
301
|
+
}
|
|
302
|
+
return `${lines.join("\n")}
|
|
303
|
+
`;
|
|
304
|
+
}
|
|
305
|
+
function trimTrailingBlankLines(value) {
|
|
306
|
+
return value.replace(/\s+$/u, "");
|
|
307
|
+
}
|
|
308
|
+
function upsertCodexServerBlock(rawConfig, serverName, serverEntry) {
|
|
309
|
+
const block = serializeCodexServerBlock(serverName, serverEntry);
|
|
310
|
+
const normalized = rawConfig.replace(/\r\n/g, "\n");
|
|
311
|
+
const legacyPattern = new RegExp(String.raw`\n?\[mcp\.servers\.${serverName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}\]\n[\s\S]*?(?=\n\[[^\n]+\]\n|$)`, "g");
|
|
312
|
+
const currentPattern = new RegExp(
|
|
313
|
+
String.raw`\n?\[mcp_servers\.${serverName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}\]\n[\s\S]*?(?=\n\[[^\n]+\]\n|$)`,
|
|
314
|
+
"g"
|
|
315
|
+
);
|
|
316
|
+
const withoutLegacy = normalized.replace(legacyPattern, "");
|
|
317
|
+
const withoutExisting = withoutLegacy.replace(currentPattern, "");
|
|
318
|
+
const trimmed = trimTrailingBlankLines(withoutExisting);
|
|
319
|
+
if (trimmed.length === 0) {
|
|
320
|
+
return block;
|
|
321
|
+
}
|
|
322
|
+
return `${trimmed}
|
|
323
|
+
|
|
324
|
+
${block}`;
|
|
325
|
+
}
|
|
326
|
+
async function readTomlConfigText(configPath) {
|
|
327
|
+
try {
|
|
328
|
+
return await readFile2(configPath, "utf8");
|
|
329
|
+
} catch (error) {
|
|
330
|
+
if (error instanceof Error && "code" in error && error.code === "ENOENT") {
|
|
331
|
+
return "";
|
|
332
|
+
}
|
|
333
|
+
throw error;
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
var CodexTOMLConfigWriter = class {
|
|
337
|
+
clientKind = "CodexCLI";
|
|
338
|
+
configuredPath;
|
|
339
|
+
constructor(configuredPath) {
|
|
340
|
+
this.configuredPath = configuredPath;
|
|
341
|
+
}
|
|
342
|
+
async detect(_workspaceRoot, overridePath) {
|
|
343
|
+
const explicitPath = overridePath ?? this.configuredPath;
|
|
344
|
+
if (explicitPath !== void 0) {
|
|
345
|
+
return resolve4(expandHome2(explicitPath));
|
|
346
|
+
}
|
|
347
|
+
const codexDir = join4(homedir3(), ".codex");
|
|
348
|
+
return existsSync4(codexDir) ? resolve4(join4(codexDir, "config.toml")) : null;
|
|
349
|
+
}
|
|
350
|
+
async write(serverPath, workspaceRoot, overridePath) {
|
|
351
|
+
const configPath = await this.detect(workspaceRoot, overridePath);
|
|
352
|
+
if (configPath === null) {
|
|
353
|
+
return;
|
|
354
|
+
}
|
|
355
|
+
const rawConfig = await readTomlConfigText(configPath);
|
|
356
|
+
const nextConfig = upsertCodexServerBlock(rawConfig, "fabric", createServerEntry(serverPath));
|
|
357
|
+
await mkdir2(dirname3(configPath), { recursive: true });
|
|
358
|
+
await atomicWriteText2(configPath, nextConfig);
|
|
359
|
+
}
|
|
360
|
+
};
|
|
361
|
+
|
|
362
|
+
// src/config/resolver.ts
|
|
363
|
+
import { clientPathsSchema, fabricConfigSchema } from "@fenglimg/fabric-shared";
|
|
364
|
+
function hasExplicitPath(clientPaths, key) {
|
|
365
|
+
return typeof clientPaths?.[key] === "string" && clientPaths[key].trim().length > 0;
|
|
366
|
+
}
|
|
367
|
+
function addIfDetected(writers, detected, createWriter, configuredPath) {
|
|
368
|
+
if (configuredPath !== void 0 || detected) {
|
|
369
|
+
writers.push(createWriter(configuredPath));
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
function resolveClients(workspaceRoot, fabricConfig = {}, opts = {}) {
|
|
373
|
+
const clientPaths = fabricConfig.clientPaths;
|
|
374
|
+
const writers = [];
|
|
375
|
+
const claudeMcpScope = opts.claudeMcpScope ?? "project";
|
|
376
|
+
addIfDetected(
|
|
377
|
+
writers,
|
|
378
|
+
existsSync5(join5(homedir4(), ".claude")) || existsSync5(join5(workspaceRoot, ".claude")),
|
|
379
|
+
(configuredPath) => new ClaudeCodeCLIWriter(configuredPath, claudeMcpScope),
|
|
380
|
+
hasExplicitPath(clientPaths, "claudeCodeCLI") ? clientPaths.claudeCodeCLI : void 0
|
|
381
|
+
);
|
|
382
|
+
addIfDetected(
|
|
383
|
+
writers,
|
|
384
|
+
existsSync5(getClaudeDesktopConfigPath()),
|
|
385
|
+
(configuredPath) => new ClaudeCodeDesktopWriter(configuredPath),
|
|
386
|
+
hasExplicitPath(clientPaths, "claudeCodeDesktop") ? clientPaths.claudeCodeDesktop : void 0
|
|
387
|
+
);
|
|
388
|
+
addIfDetected(
|
|
389
|
+
writers,
|
|
390
|
+
existsSync5(join5(workspaceRoot, ".cursor")),
|
|
391
|
+
(configuredPath) => new CursorWriter(configuredPath),
|
|
392
|
+
hasExplicitPath(clientPaths, "cursor") ? clientPaths.cursor : void 0
|
|
393
|
+
);
|
|
394
|
+
addIfDetected(
|
|
395
|
+
writers,
|
|
396
|
+
existsSync5(join5(homedir4(), ".codex")),
|
|
397
|
+
(configuredPath) => new CodexTOMLConfigWriter(configuredPath),
|
|
398
|
+
hasExplicitPath(clientPaths, "codexCLI") ? clientPaths.codexCLI : void 0
|
|
399
|
+
);
|
|
400
|
+
return writers;
|
|
401
|
+
}
|
|
402
|
+
function detectClientSupports(workspaceRoot, fabricConfig = {}) {
|
|
403
|
+
const clientPaths = fabricConfig.clientPaths;
|
|
404
|
+
const claudeDetected = existsSync5(join5(homedir4(), ".claude")) || existsSync5(join5(workspaceRoot, ".claude"));
|
|
405
|
+
const claudeDesktopDetected = existsSync5(getClaudeDesktopConfigPath());
|
|
406
|
+
const cursorDetected = existsSync5(join5(workspaceRoot, ".cursor"));
|
|
407
|
+
const codexDetected = existsSync5(join5(homedir4(), ".codex"));
|
|
408
|
+
return [
|
|
409
|
+
{
|
|
410
|
+
clientKind: "ClaudeCodeCLI",
|
|
411
|
+
label: "Claude Code CLI",
|
|
412
|
+
detected: claudeDetected || hasExplicitPath(clientPaths, "claudeCodeCLI"),
|
|
413
|
+
bootstrapTargetPath: ".fabric/bootstrap/README.md",
|
|
414
|
+
configPath: "project .claude/settings.json",
|
|
415
|
+
capabilities: {
|
|
416
|
+
bootstrap: true,
|
|
417
|
+
mcp: true,
|
|
418
|
+
hook: true,
|
|
419
|
+
skill: true
|
|
420
|
+
},
|
|
421
|
+
installedCapabilities: {
|
|
422
|
+
hook: true,
|
|
423
|
+
skill: true
|
|
424
|
+
}
|
|
425
|
+
},
|
|
426
|
+
{
|
|
427
|
+
clientKind: "ClaudeCodeDesktop",
|
|
428
|
+
label: "Claude Code Desktop",
|
|
429
|
+
detected: claudeDesktopDetected || hasExplicitPath(clientPaths, "claudeCodeDesktop"),
|
|
430
|
+
bootstrapTargetPath: ".fabric/bootstrap/README.md",
|
|
431
|
+
configPath: "desktop Claude config",
|
|
432
|
+
capabilities: {
|
|
433
|
+
bootstrap: true,
|
|
434
|
+
mcp: true,
|
|
435
|
+
hook: false,
|
|
436
|
+
skill: false
|
|
437
|
+
}
|
|
438
|
+
},
|
|
439
|
+
{
|
|
440
|
+
clientKind: "Cursor",
|
|
441
|
+
label: "Cursor",
|
|
442
|
+
detected: cursorDetected || hasExplicitPath(clientPaths, "cursor"),
|
|
443
|
+
bootstrapTargetPath: ".fabric/bootstrap/README.md",
|
|
444
|
+
configPath: ".cursor/mcp.json",
|
|
445
|
+
capabilities: {
|
|
446
|
+
bootstrap: true,
|
|
447
|
+
mcp: true,
|
|
448
|
+
hook: false,
|
|
449
|
+
skill: false
|
|
450
|
+
}
|
|
451
|
+
},
|
|
452
|
+
{
|
|
453
|
+
clientKind: "CodexCLI",
|
|
454
|
+
label: "Codex CLI",
|
|
455
|
+
detected: codexDetected || hasExplicitPath(clientPaths, "codexCLI"),
|
|
456
|
+
bootstrapTargetPath: ".fabric/bootstrap/README.md",
|
|
457
|
+
configPath: "~/.codex/config.toml",
|
|
458
|
+
capabilities: {
|
|
459
|
+
bootstrap: true,
|
|
460
|
+
mcp: true,
|
|
461
|
+
hook: true,
|
|
462
|
+
skill: true
|
|
463
|
+
},
|
|
464
|
+
installedCapabilities: {
|
|
465
|
+
hook: existsSync5(join5(workspaceRoot, ".codex", "hooks.json")),
|
|
466
|
+
skill: existsSync5(join5(workspaceRoot, ".agents", "skills", "fabric-init", "SKILL.md"))
|
|
467
|
+
}
|
|
468
|
+
}
|
|
469
|
+
];
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
// src/commands/bootstrap.ts
|
|
473
|
+
var CLIENT_ALIASES = {
|
|
474
|
+
claude: "claude",
|
|
475
|
+
"claude-code": "claude",
|
|
476
|
+
claudecode: "claude",
|
|
477
|
+
claudecli: "claude",
|
|
478
|
+
claudecodecli: "claude",
|
|
479
|
+
claudedesktop: "claude",
|
|
480
|
+
claudecodedesktop: "claude",
|
|
481
|
+
cursor: "cursor",
|
|
482
|
+
codex: "codex",
|
|
483
|
+
"codex-cli": "codex",
|
|
484
|
+
codexcli: "codex"
|
|
485
|
+
};
|
|
486
|
+
var bootstrapCommand = defineCommand({
|
|
487
|
+
meta: {
|
|
488
|
+
name: "bootstrap",
|
|
489
|
+
description: t("cli.bootstrap.description")
|
|
490
|
+
},
|
|
491
|
+
subCommands: {
|
|
492
|
+
install: defineCommand({
|
|
493
|
+
meta: {
|
|
494
|
+
name: "install",
|
|
495
|
+
description: t("cli.bootstrap.install.description")
|
|
496
|
+
},
|
|
497
|
+
args: {
|
|
498
|
+
clients: {
|
|
499
|
+
type: "string",
|
|
500
|
+
description: t("cli.bootstrap.install.args.clients.description")
|
|
501
|
+
}
|
|
502
|
+
},
|
|
503
|
+
async run({ args }) {
|
|
504
|
+
const workspaceRoot = process.cwd();
|
|
505
|
+
const selectedClients = parseClientFilter(args.clients);
|
|
506
|
+
const result = await installBootstrap(workspaceRoot, {
|
|
507
|
+
clients: selectedClients === null ? void 0 : Array.from(selectedClients, mapBootstrapClientToClientKind)
|
|
508
|
+
});
|
|
509
|
+
if (result.details.length === 0) {
|
|
510
|
+
process.stderr.write(
|
|
511
|
+
`${t("cli.bootstrap.install.no-targets")}
|
|
512
|
+
`
|
|
513
|
+
);
|
|
514
|
+
return;
|
|
515
|
+
}
|
|
516
|
+
for (const detail of result.details) {
|
|
517
|
+
if (detail.action === "skipped") {
|
|
518
|
+
process.stderr.write(`${t("cli.bootstrap.install.skipped-header", { path: detail.path })}
|
|
519
|
+
`);
|
|
520
|
+
continue;
|
|
521
|
+
}
|
|
522
|
+
if (detail.action === "prepended") {
|
|
523
|
+
process.stderr.write(`${t("cli.bootstrap.install.prepended", { path: detail.path })}
|
|
524
|
+
`);
|
|
525
|
+
continue;
|
|
526
|
+
}
|
|
527
|
+
process.stderr.write(`${t("cli.bootstrap.install.installed", { path: detail.path })}
|
|
528
|
+
`);
|
|
529
|
+
}
|
|
530
|
+
}
|
|
531
|
+
})
|
|
532
|
+
}
|
|
533
|
+
});
|
|
534
|
+
async function installBootstrap(target, options = {}) {
|
|
535
|
+
const workspaceRoot = resolve5(target);
|
|
536
|
+
const fabricConfig = readFabricConfig(workspaceRoot);
|
|
537
|
+
const targets = resolveBootstrapTargets(workspaceRoot, fabricConfig, options.clients);
|
|
538
|
+
const installed = [];
|
|
539
|
+
const skipped = [];
|
|
540
|
+
const details = [];
|
|
541
|
+
await ensureFabricBootstrapGuide(workspaceRoot, options.force);
|
|
542
|
+
for (const bootstrapTarget of targets) {
|
|
543
|
+
details.push({
|
|
544
|
+
client: bootstrapTarget.client,
|
|
545
|
+
path: resolve5(workspaceRoot, FABRIC_GUIDE_PATH),
|
|
546
|
+
action: "skipped"
|
|
547
|
+
});
|
|
548
|
+
skipped.push(bootstrapTarget.client);
|
|
549
|
+
}
|
|
550
|
+
return { installed, skipped, details };
|
|
551
|
+
}
|
|
552
|
+
function parseClientFilter(value) {
|
|
553
|
+
if (value === void 0 || value.trim().length === 0) {
|
|
554
|
+
return null;
|
|
555
|
+
}
|
|
556
|
+
const clients = /* @__PURE__ */ new Set();
|
|
557
|
+
for (const rawClient of value.split(",")) {
|
|
558
|
+
const alias = rawClient.trim().toLowerCase();
|
|
559
|
+
const client = CLIENT_ALIASES[alias];
|
|
560
|
+
if (client === void 0) {
|
|
561
|
+
throw new Error(t("cli.bootstrap.errors.unknown-client", { client: rawClient }));
|
|
562
|
+
}
|
|
563
|
+
clients.add(client);
|
|
564
|
+
}
|
|
565
|
+
return clients;
|
|
566
|
+
}
|
|
567
|
+
function resolveBootstrapTargets(workspaceRoot, fabricConfig, selectedClients) {
|
|
568
|
+
const targets = [];
|
|
569
|
+
const seenClients = /* @__PURE__ */ new Set();
|
|
570
|
+
const clientKinds = selectedClients ?? resolveClients(workspaceRoot, fabricConfig).map((writer) => writer.clientKind);
|
|
571
|
+
for (const clientKind of clientKinds) {
|
|
572
|
+
const bootstrapClient = mapClientKind(clientKind);
|
|
573
|
+
if (bootstrapClient === null || seenClients.has(bootstrapClient)) {
|
|
574
|
+
continue;
|
|
575
|
+
}
|
|
576
|
+
seenClients.add(bootstrapClient);
|
|
577
|
+
targets.push({ client: clientKind, bootstrapClient });
|
|
578
|
+
}
|
|
579
|
+
return targets;
|
|
580
|
+
}
|
|
581
|
+
function mapClientKind(clientKind) {
|
|
582
|
+
switch (clientKind) {
|
|
583
|
+
case "ClaudeCodeCLI":
|
|
584
|
+
case "ClaudeCodeDesktop":
|
|
585
|
+
return "claude";
|
|
586
|
+
case "Cursor":
|
|
587
|
+
return "cursor";
|
|
588
|
+
case "CodexCLI":
|
|
589
|
+
return "codex";
|
|
590
|
+
default:
|
|
591
|
+
return null;
|
|
592
|
+
}
|
|
593
|
+
}
|
|
594
|
+
function mapBootstrapClientToClientKind(client) {
|
|
595
|
+
switch (client) {
|
|
596
|
+
case "claude":
|
|
597
|
+
return "ClaudeCodeCLI";
|
|
598
|
+
case "cursor":
|
|
599
|
+
return "Cursor";
|
|
600
|
+
case "codex":
|
|
601
|
+
return "CodexCLI";
|
|
602
|
+
}
|
|
603
|
+
}
|
|
604
|
+
|
|
605
|
+
// src/commands/config.ts
|
|
606
|
+
import { existsSync as existsSync7 } from "fs";
|
|
607
|
+
import { readFile as readFile3 } from "fs/promises";
|
|
608
|
+
import { resolve as resolve7 } from "path";
|
|
609
|
+
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
610
|
+
import { defineCommand as defineCommand3 } from "citty";
|
|
611
|
+
|
|
612
|
+
// src/commands/hooks.ts
|
|
613
|
+
import { chmodSync, existsSync as existsSync6, mkdirSync as mkdirSync2, readFileSync as readFileSync2, statSync } from "fs";
|
|
614
|
+
import { dirname as dirname4, isAbsolute as isAbsolute2, join as join6, parse as parse2, resolve as resolve6 } from "path";
|
|
615
|
+
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
616
|
+
import { defineCommand as defineCommand2 } from "citty";
|
|
617
|
+
import { atomicWriteJson as atomicWriteJson2, atomicWriteText as atomicWriteText3 } from "@fenglimg/fabric-shared/node/atomic-write";
|
|
618
|
+
var hooksCommand = defineCommand2({
|
|
619
|
+
meta: {
|
|
620
|
+
name: "hooks",
|
|
621
|
+
description: t("cli.hooks.description")
|
|
622
|
+
},
|
|
623
|
+
subCommands: {
|
|
624
|
+
install: defineCommand2({
|
|
625
|
+
meta: {
|
|
626
|
+
name: "install",
|
|
627
|
+
description: t("cli.hooks.install.description")
|
|
628
|
+
},
|
|
629
|
+
args: {
|
|
630
|
+
target: {
|
|
631
|
+
type: "string",
|
|
632
|
+
description: t("cli.hooks.install.args.target.description"),
|
|
633
|
+
default: process.cwd()
|
|
634
|
+
}
|
|
635
|
+
},
|
|
636
|
+
async run({ args }) {
|
|
637
|
+
const result = await installHooks(args.target);
|
|
638
|
+
if (result.hookAction === "skipped") {
|
|
639
|
+
writeStderr(t("cli.hooks.install.hook-skipped", { path: result.hookPath }));
|
|
640
|
+
} else if (result.hookAction === "appended") {
|
|
641
|
+
writeStderr(t("cli.hooks.install.hook-appended", { path: result.hookPath }));
|
|
642
|
+
} else {
|
|
643
|
+
writeStderr(t("cli.hooks.install.hook-created", { path: result.hookPath }));
|
|
644
|
+
}
|
|
645
|
+
if (result.prepareAction === "left") {
|
|
646
|
+
writeStderr(t("cli.hooks.install.prepare-left", { path: result.packageJsonPath }));
|
|
647
|
+
} else {
|
|
648
|
+
writeStderr(t("cli.hooks.install.prepare-added", { path: result.packageJsonPath }));
|
|
649
|
+
}
|
|
650
|
+
}
|
|
651
|
+
})
|
|
652
|
+
}
|
|
653
|
+
});
|
|
654
|
+
async function installHooks(target, options = {}) {
|
|
655
|
+
const normalizedTarget = normalizeTarget2(target);
|
|
656
|
+
assertExistingDirectory(normalizedTarget);
|
|
657
|
+
const huskyDir = join6(normalizedTarget, ".husky");
|
|
658
|
+
const hookPath = join6(huskyDir, "pre-commit");
|
|
659
|
+
const packageJsonPath = join6(normalizedTarget, "package.json");
|
|
660
|
+
if (!existsSync6(packageJsonPath)) {
|
|
661
|
+
throw new Error(t("cli.hooks.errors.package-json-required", { path: packageJsonPath }));
|
|
662
|
+
}
|
|
663
|
+
mkdirSync2(huskyDir, { recursive: true });
|
|
664
|
+
const templateContent = readFileSync2(findTemplatePath2("templates/husky/pre-commit"), "utf8");
|
|
665
|
+
const hookAction = await installHookFile(hookPath, templateContent, options.force);
|
|
666
|
+
chmodSync(hookPath, 493);
|
|
667
|
+
const packageJson = JSON.parse(readFileSync2(packageJsonPath, "utf8"));
|
|
668
|
+
const scripts = packageJson.scripts && typeof packageJson.scripts === "object" && !Array.isArray(packageJson.scripts) ? packageJson.scripts : {};
|
|
669
|
+
const hadPrepare = typeof scripts.prepare === "string" && scripts.prepare.trim().length > 0;
|
|
670
|
+
let prepareAction = "left";
|
|
671
|
+
if (!hadPrepare) {
|
|
672
|
+
scripts.prepare = "husky install";
|
|
673
|
+
packageJson.scripts = scripts;
|
|
674
|
+
await atomicWriteJson2(packageJsonPath, packageJson);
|
|
675
|
+
prepareAction = "added";
|
|
676
|
+
}
|
|
677
|
+
const installed = [];
|
|
678
|
+
const skipped = [];
|
|
679
|
+
if (hookAction === "skipped") {
|
|
680
|
+
skipped.push(hookPath);
|
|
681
|
+
} else {
|
|
682
|
+
installed.push(hookPath);
|
|
683
|
+
}
|
|
684
|
+
if (prepareAction === "left") {
|
|
685
|
+
skipped.push(packageJsonPath);
|
|
686
|
+
} else {
|
|
687
|
+
installed.push(packageJsonPath);
|
|
688
|
+
}
|
|
689
|
+
return {
|
|
690
|
+
installed,
|
|
691
|
+
skipped,
|
|
692
|
+
hookPath,
|
|
693
|
+
packageJsonPath,
|
|
694
|
+
hookAction,
|
|
695
|
+
prepareAction
|
|
696
|
+
};
|
|
697
|
+
}
|
|
698
|
+
function normalizeTarget2(targetInput) {
|
|
699
|
+
return isAbsolute2(targetInput) ? targetInput : resolve6(process.cwd(), targetInput);
|
|
700
|
+
}
|
|
701
|
+
function assertExistingDirectory(target) {
|
|
702
|
+
if (!existsSync6(target) || !statSync(target).isDirectory()) {
|
|
703
|
+
throw new Error(t("cli.shared.target-invalid", { target }));
|
|
704
|
+
}
|
|
705
|
+
}
|
|
706
|
+
async function installHookFile(hookPath, templateContent, force) {
|
|
707
|
+
if (existsSync6(hookPath)) {
|
|
708
|
+
if (force) {
|
|
709
|
+
await atomicWriteText3(hookPath, templateContent);
|
|
710
|
+
return "overwritten";
|
|
711
|
+
}
|
|
712
|
+
const existing = readFileSync2(hookPath, "utf8");
|
|
713
|
+
if (existing.includes("FAB_BIN=")) {
|
|
714
|
+
return "skipped";
|
|
715
|
+
}
|
|
716
|
+
const fabricBlock = templateContent.replace(/^#!\/bin\/sh\n/, "");
|
|
717
|
+
const separator = existing.endsWith("\n") ? "\n" : "\n\n";
|
|
718
|
+
await atomicWriteText3(hookPath, `${existing}${separator}# --- Fabric ---
|
|
719
|
+
${fabricBlock}`);
|
|
720
|
+
return "appended";
|
|
721
|
+
}
|
|
722
|
+
await atomicWriteText3(hookPath, templateContent);
|
|
723
|
+
return "created";
|
|
724
|
+
}
|
|
725
|
+
function findTemplatePath2(relativePath) {
|
|
726
|
+
const currentModuleDir = dirname4(fileURLToPath2(import.meta.url));
|
|
727
|
+
const candidates = [
|
|
728
|
+
...templateCandidatesFrom2(process.cwd(), relativePath),
|
|
729
|
+
...templateCandidatesFrom2(currentModuleDir, relativePath)
|
|
730
|
+
];
|
|
731
|
+
for (const candidate of candidates) {
|
|
732
|
+
if (existsSync6(candidate)) {
|
|
733
|
+
return candidate;
|
|
734
|
+
}
|
|
735
|
+
}
|
|
736
|
+
throw new Error(t("cli.shared.template-not-found", { path: relativePath }));
|
|
737
|
+
}
|
|
738
|
+
function templateCandidatesFrom2(start, relativePath) {
|
|
739
|
+
const candidates = [];
|
|
740
|
+
let current = resolve6(start);
|
|
741
|
+
while (true) {
|
|
742
|
+
candidates.push(join6(current, ...relativePath.split("/")));
|
|
743
|
+
const parent = dirname4(current);
|
|
744
|
+
if (parent === current || parse2(current).root === current) {
|
|
745
|
+
break;
|
|
746
|
+
}
|
|
747
|
+
current = parent;
|
|
748
|
+
}
|
|
749
|
+
return candidates.reverse();
|
|
750
|
+
}
|
|
751
|
+
function writeStderr(message) {
|
|
752
|
+
process.stderr.write(`${message}
|
|
753
|
+
`);
|
|
754
|
+
}
|
|
755
|
+
|
|
756
|
+
// src/commands/config.ts
|
|
757
|
+
var CLIENT_ALIASES2 = {
|
|
758
|
+
claude: "ClaudeCodeCLI",
|
|
759
|
+
claudecodecli: "ClaudeCodeCLI",
|
|
760
|
+
"claude-code-cli": "ClaudeCodeCLI",
|
|
761
|
+
claudecli: "ClaudeCodeCLI",
|
|
762
|
+
claudecodedesktop: "ClaudeCodeDesktop",
|
|
763
|
+
"claude-code-desktop": "ClaudeCodeDesktop",
|
|
764
|
+
claudedesktop: "ClaudeCodeDesktop",
|
|
765
|
+
cursor: "Cursor",
|
|
766
|
+
codexcli: "CodexCLI",
|
|
767
|
+
"codex-cli": "CodexCLI",
|
|
768
|
+
codex: "CodexCLI"
|
|
769
|
+
};
|
|
770
|
+
function parseClientFilter2(value) {
|
|
771
|
+
if (value === void 0 || value.trim().length === 0) {
|
|
772
|
+
return null;
|
|
773
|
+
}
|
|
774
|
+
const clients = /* @__PURE__ */ new Set();
|
|
775
|
+
for (const rawClient of value.split(",")) {
|
|
776
|
+
const alias = rawClient.trim().toLowerCase();
|
|
777
|
+
const clientKind = CLIENT_ALIASES2[alias];
|
|
778
|
+
if (clientKind === void 0) {
|
|
779
|
+
throw new Error(t("cli.config.errors.unknown-client", { client: rawClient }));
|
|
780
|
+
}
|
|
781
|
+
clients.add(clientKind);
|
|
782
|
+
}
|
|
783
|
+
return clients;
|
|
784
|
+
}
|
|
785
|
+
async function loadFabricConfig(workspaceRoot) {
|
|
786
|
+
const configPath = resolve7(workspaceRoot, "fabric.config.json");
|
|
787
|
+
if (!existsSync7(configPath)) {
|
|
788
|
+
return {};
|
|
789
|
+
}
|
|
790
|
+
const parsed = JSON.parse(await readFile3(configPath, "utf8"));
|
|
791
|
+
if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
792
|
+
throw new Error(t("cli.config.errors.expected-object", { path: configPath }));
|
|
793
|
+
}
|
|
794
|
+
return parsed;
|
|
795
|
+
}
|
|
796
|
+
function resolveServerPath(override) {
|
|
797
|
+
if (override) return override;
|
|
798
|
+
if (process.env.FAB_SERVER_PATH) return resolve7(process.env.FAB_SERVER_PATH);
|
|
799
|
+
return fileURLToPath3(import.meta.resolve("@fenglimg/fabric-server"));
|
|
800
|
+
}
|
|
801
|
+
function writeStderr2(message) {
|
|
802
|
+
process.stderr.write(`${message}
|
|
803
|
+
`);
|
|
804
|
+
}
|
|
805
|
+
var configCmd = defineCommand3({
|
|
806
|
+
meta: {
|
|
807
|
+
name: "config",
|
|
808
|
+
description: t("cli.config.description")
|
|
809
|
+
},
|
|
810
|
+
subCommands: {
|
|
811
|
+
hooks: hooksCommand,
|
|
812
|
+
install: defineCommand3({
|
|
813
|
+
meta: {
|
|
814
|
+
name: "install",
|
|
815
|
+
description: t("cli.config.install.description")
|
|
816
|
+
},
|
|
817
|
+
args: {
|
|
818
|
+
clients: {
|
|
819
|
+
type: "string",
|
|
820
|
+
description: t("cli.config.install.args.clients.description")
|
|
821
|
+
},
|
|
822
|
+
"dry-run": {
|
|
823
|
+
type: "boolean",
|
|
824
|
+
description: t("cli.config.install.args.dry-run.description"),
|
|
825
|
+
default: false
|
|
826
|
+
}
|
|
827
|
+
},
|
|
828
|
+
async run({ args }) {
|
|
829
|
+
const selectedClients = parseClientFilter2(args.clients);
|
|
830
|
+
const result = await installMcpClients(process.cwd(), {
|
|
831
|
+
clients: selectedClients === null ? void 0 : Array.from(selectedClients),
|
|
832
|
+
dryRun: args["dry-run"]
|
|
833
|
+
});
|
|
834
|
+
if (result.details.length === 0) {
|
|
835
|
+
writeStderr2(t("cli.config.install.no-configs"));
|
|
836
|
+
return;
|
|
837
|
+
}
|
|
838
|
+
for (const detail of result.details) {
|
|
839
|
+
if (detail.action === "skipped") {
|
|
840
|
+
writeStderr2(t("cli.config.install.no-config-path", { client: detail.client }));
|
|
841
|
+
continue;
|
|
842
|
+
}
|
|
843
|
+
if (detail.action === "dry-run" && detail.path !== null) {
|
|
844
|
+
writeStderr2(t("cli.config.install.dry-run", { client: detail.client, path: detail.path }));
|
|
845
|
+
continue;
|
|
846
|
+
}
|
|
847
|
+
if (detail.path !== null) {
|
|
848
|
+
writeStderr2(t("cli.config.install.wrote", { client: detail.client, path: detail.path }));
|
|
849
|
+
}
|
|
850
|
+
}
|
|
851
|
+
}
|
|
852
|
+
})
|
|
853
|
+
}
|
|
854
|
+
});
|
|
855
|
+
async function installMcpClients(target, options = {}) {
|
|
856
|
+
const workspaceRoot = resolve7(target);
|
|
857
|
+
const fabricConfig = await loadFabricConfig(workspaceRoot);
|
|
858
|
+
const selectedClients = options.clients === void 0 ? null : new Set(options.clients);
|
|
859
|
+
const serverPath = resolveServerPath(options.localServerPath);
|
|
860
|
+
const writers = resolveClients(workspaceRoot, fabricConfig, { claudeMcpScope: options.claudeMcpScope }).filter(
|
|
861
|
+
(writer) => selectedClients === null ? true : selectedClients.has(writer.clientKind)
|
|
862
|
+
);
|
|
863
|
+
const installed = [];
|
|
864
|
+
const skipped = [];
|
|
865
|
+
const details = [];
|
|
866
|
+
for (const writer of writers) {
|
|
867
|
+
const configPath = await writer.detect(workspaceRoot);
|
|
868
|
+
if (configPath === null) {
|
|
869
|
+
skipped.push(writer.clientKind);
|
|
870
|
+
details.push({ client: writer.clientKind, path: null, action: "skipped" });
|
|
871
|
+
continue;
|
|
872
|
+
}
|
|
873
|
+
if (options.dryRun) {
|
|
874
|
+
skipped.push(writer.clientKind);
|
|
875
|
+
details.push({ client: writer.clientKind, path: configPath, action: "dry-run" });
|
|
876
|
+
continue;
|
|
877
|
+
}
|
|
878
|
+
await writer.write(serverPath, workspaceRoot);
|
|
879
|
+
installed.push(writer.clientKind);
|
|
880
|
+
details.push({ client: writer.clientKind, path: configPath, action: "wrote" });
|
|
881
|
+
}
|
|
882
|
+
return { installed, skipped, details };
|
|
883
|
+
}
|
|
884
|
+
|
|
40
885
|
// src/scanner/forensic.ts
|
|
41
886
|
import { execFileSync } from "child_process";
|
|
42
|
-
import { existsSync, readdirSync, readFileSync, statSync } from "fs";
|
|
887
|
+
import { existsSync as existsSync8, readdirSync, readFileSync as readFileSync3, statSync as statSync2 } from "fs";
|
|
43
888
|
import { createRequire } from "module";
|
|
44
|
-
import { basename, extname, isAbsolute, join, posix, relative, resolve, sep } from "path";
|
|
889
|
+
import { basename, extname, isAbsolute as isAbsolute3, join as join7, posix, relative, resolve as resolve8, sep } from "path";
|
|
45
890
|
import {
|
|
46
891
|
forensicReportSchema
|
|
47
892
|
} from "@fenglimg/fabric-shared";
|
|
@@ -127,7 +972,7 @@ var parserInitPromise = null;
|
|
|
127
972
|
var languagePromiseByKind = {};
|
|
128
973
|
var parserBundlePromiseByKind = {};
|
|
129
974
|
async function buildForensicReport(targetInput) {
|
|
130
|
-
const target =
|
|
975
|
+
const target = normalizeTarget3(targetInput);
|
|
131
976
|
const framework = detectFramework(target);
|
|
132
977
|
const topology = buildTopology(target);
|
|
133
978
|
const entryPoints = collectEntryPoints(target, topology.files);
|
|
@@ -163,11 +1008,11 @@ async function buildForensicReport(targetInput) {
|
|
|
163
1008
|
}
|
|
164
1009
|
return validation.data;
|
|
165
1010
|
}
|
|
166
|
-
function
|
|
167
|
-
return
|
|
1011
|
+
function normalizeTarget3(targetInput) {
|
|
1012
|
+
return isAbsolute3(targetInput) ? targetInput : resolve8(process.cwd(), targetInput);
|
|
168
1013
|
}
|
|
169
1014
|
function buildTopology(root) {
|
|
170
|
-
|
|
1015
|
+
assertExistingDirectory2(root);
|
|
171
1016
|
const byExt = {};
|
|
172
1017
|
const keyDirs = /* @__PURE__ */ new Set();
|
|
173
1018
|
const files = [];
|
|
@@ -180,7 +1025,7 @@ function buildTopology(root) {
|
|
|
180
1025
|
continue;
|
|
181
1026
|
}
|
|
182
1027
|
for (const entry of readdirSync(current, { withFileTypes: true })) {
|
|
183
|
-
const absolutePath =
|
|
1028
|
+
const absolutePath = join7(current, entry.name);
|
|
184
1029
|
const relativePath = toPosixPath(relative(root, absolutePath));
|
|
185
1030
|
if (relativePath.length === 0) {
|
|
186
1031
|
continue;
|
|
@@ -200,7 +1045,7 @@ function buildTopology(root) {
|
|
|
200
1045
|
if (!entry.isFile()) {
|
|
201
1046
|
continue;
|
|
202
1047
|
}
|
|
203
|
-
const stats =
|
|
1048
|
+
const stats = statSync2(absolutePath);
|
|
204
1049
|
const extension = extname(entry.name) || "[none]";
|
|
205
1050
|
byExt[extension] = (byExt[extension] ?? 0) + 1;
|
|
206
1051
|
totalFiles += 1;
|
|
@@ -218,8 +1063,8 @@ function buildTopology(root) {
|
|
|
218
1063
|
files: files.sort((left, right) => left.relativePath.localeCompare(right.relativePath))
|
|
219
1064
|
};
|
|
220
1065
|
}
|
|
221
|
-
function
|
|
222
|
-
if (!
|
|
1066
|
+
function assertExistingDirectory2(target) {
|
|
1067
|
+
if (!existsSync8(target) || !statSync2(target).isDirectory()) {
|
|
223
1068
|
throw new Error(`Target must be an existing directory: ${target}`);
|
|
224
1069
|
}
|
|
225
1070
|
}
|
|
@@ -268,7 +1113,7 @@ function getEntryPointReason(relativePath) {
|
|
|
268
1113
|
async function buildCodeSamples(target, entryPoints, frameworkKind, topology, packageDependencies) {
|
|
269
1114
|
const samples = [];
|
|
270
1115
|
for (const entryPoint of entryPoints.slice(0, SAMPLE_LIMIT)) {
|
|
271
|
-
const absolutePath =
|
|
1116
|
+
const absolutePath = join7(target, ...entryPoint.path.split("/"));
|
|
272
1117
|
const sample = readFirstLines(absolutePath, SAMPLE_LINE_LIMIT);
|
|
273
1118
|
const patternAnalysis = await inferPatternHint(entryPoint.path, sample.snippet, {
|
|
274
1119
|
frameworkKind,
|
|
@@ -288,7 +1133,7 @@ async function buildCodeSamples(target, entryPoints, frameworkKind, topology, pa
|
|
|
288
1133
|
}
|
|
289
1134
|
function readFirstLines(path, lineLimit) {
|
|
290
1135
|
try {
|
|
291
|
-
const lines =
|
|
1136
|
+
const lines = readFileSync3(path, "utf8").split(/\r?\n/);
|
|
292
1137
|
if (lines.at(-1) === "") {
|
|
293
1138
|
lines.pop();
|
|
294
1139
|
}
|
|
@@ -305,12 +1150,12 @@ function readFirstLines(path, lineLimit) {
|
|
|
305
1150
|
}
|
|
306
1151
|
}
|
|
307
1152
|
function readPackageDependencies(target) {
|
|
308
|
-
const packageJsonPath =
|
|
309
|
-
if (!
|
|
1153
|
+
const packageJsonPath = join7(target, "package.json");
|
|
1154
|
+
if (!existsSync8(packageJsonPath)) {
|
|
310
1155
|
return /* @__PURE__ */ new Map();
|
|
311
1156
|
}
|
|
312
1157
|
try {
|
|
313
|
-
const packageJson = JSON.parse(
|
|
1158
|
+
const packageJson = JSON.parse(readFileSync3(packageJsonPath, "utf8"));
|
|
314
1159
|
return new Map([
|
|
315
1160
|
...Object.entries(packageJson.dependencies ?? {}),
|
|
316
1161
|
...Object.entries(packageJson.devDependencies ?? {}),
|
|
@@ -646,16 +1491,16 @@ function scoreFrameworkConfidence(input) {
|
|
|
646
1491
|
return input.configCount > 0 || input.packageCount > 0 ? "MEDIUM" : "LOW";
|
|
647
1492
|
}
|
|
648
1493
|
function readReadmeInfo(target) {
|
|
649
|
-
const readmePath =
|
|
650
|
-
const hasContributing =
|
|
651
|
-
if (!
|
|
1494
|
+
const readmePath = join7(target, "README.md");
|
|
1495
|
+
const hasContributing = existsSync8(join7(target, "CONTRIBUTING.md"));
|
|
1496
|
+
if (!existsSync8(readmePath)) {
|
|
652
1497
|
return {
|
|
653
1498
|
quality: "missing",
|
|
654
1499
|
line_count: 0,
|
|
655
1500
|
has_contributing: hasContributing
|
|
656
1501
|
};
|
|
657
1502
|
}
|
|
658
|
-
const readme =
|
|
1503
|
+
const readme = readFileSync3(readmePath, "utf8");
|
|
659
1504
|
const wordCount = readme.trim().split(/\s+/).filter(Boolean).length;
|
|
660
1505
|
return {
|
|
661
1506
|
quality: wordCount >= 200 ? "ok" : "stub",
|
|
@@ -888,7 +1733,7 @@ function buildDomainAssertion(codeSamples) {
|
|
|
888
1733
|
namedModules.length >= 2 ? "domain-named-components" : null,
|
|
889
1734
|
namedSamples.some((sample) => sample.snippet.includes("start():")) ? "lifecycle-hook" : null
|
|
890
1735
|
]),
|
|
891
|
-
proposedRule: "Preserve domain-specific module names when mirroring structure into
|
|
1736
|
+
proposedRule: "Preserve domain-specific module names when mirroring structure into .fabric/rules/."
|
|
892
1737
|
});
|
|
893
1738
|
}
|
|
894
1739
|
function createAssertion(input) {
|
|
@@ -1133,10 +1978,10 @@ function buildSkillRecommendations(frameworkKind, topology, readme) {
|
|
|
1133
1978
|
return recommendations;
|
|
1134
1979
|
}
|
|
1135
1980
|
function readProjectName(target) {
|
|
1136
|
-
const packageJsonPath =
|
|
1137
|
-
if (
|
|
1981
|
+
const packageJsonPath = join7(target, "package.json");
|
|
1982
|
+
if (existsSync8(packageJsonPath)) {
|
|
1138
1983
|
try {
|
|
1139
|
-
const packageJson = JSON.parse(
|
|
1984
|
+
const packageJson = JSON.parse(readFileSync3(packageJsonPath, "utf8"));
|
|
1140
1985
|
if (packageJson.name !== void 0 && packageJson.name.trim().length > 0) {
|
|
1141
1986
|
return packageJson.name;
|
|
1142
1987
|
}
|
|
@@ -1147,7 +1992,7 @@ function readProjectName(target) {
|
|
|
1147
1992
|
return basename(target);
|
|
1148
1993
|
}
|
|
1149
1994
|
function getCliVersion() {
|
|
1150
|
-
return true ? "1.
|
|
1995
|
+
return true ? "1.8.0-rc.1" : "unknown";
|
|
1151
1996
|
}
|
|
1152
1997
|
function sortRecord(record) {
|
|
1153
1998
|
return Object.fromEntries(Object.entries(record).sort(([left], [right]) => left.localeCompare(right)));
|
|
@@ -1157,7 +2002,7 @@ function toPosixPath(path) {
|
|
|
1157
2002
|
}
|
|
1158
2003
|
|
|
1159
2004
|
// src/commands/init.ts
|
|
1160
|
-
var CLAUDE_INIT_SKILL_TEMPLATE = "templates/claude-skills/
|
|
2005
|
+
var CLAUDE_INIT_SKILL_TEMPLATE = "templates/claude-skills/fabric-init/SKILL.md";
|
|
1161
2006
|
var CLAUDE_INIT_REMINDER_HOOK_TEMPLATE = "templates/claude-hooks/agents-md-init-reminder.cjs";
|
|
1162
2007
|
var CLAUDE_INIT_REMINDER_COMMAND = ".claude/hooks/agents-md-init-reminder.cjs";
|
|
1163
2008
|
var CODEX_INIT_SKILL_TEMPLATE = "templates/codex-skills/fabric-init/SKILL.md";
|
|
@@ -1165,10 +2010,10 @@ var CODEX_SESSION_START_HOOK_TEMPLATE = "templates/codex-hooks/fabric-session-st
|
|
|
1165
2010
|
var CODEX_STOP_HOOK_TEMPLATE = "templates/codex-hooks/fabric-stop-reminder.cjs";
|
|
1166
2011
|
var CODEX_SESSION_START_COMMAND = ".codex/hooks/fabric-session-start.cjs";
|
|
1167
2012
|
var CODEX_STOP_COMMAND = ".codex/hooks/fabric-stop-reminder.cjs";
|
|
1168
|
-
var LOCAL_FABRIC_SERVER_PATH =
|
|
2013
|
+
var LOCAL_FABRIC_SERVER_PATH = join8("node_modules", "@fenglimg", "fabric-server", "dist", "index.js");
|
|
1169
2014
|
var FABRIC_SERVER_PACKAGE = "@fenglimg/fabric-server";
|
|
1170
2015
|
var INIT_WIZARD_GROUP_CANCELLED = /* @__PURE__ */ Symbol("init-wizard-group-cancelled");
|
|
1171
|
-
var initCommand =
|
|
2016
|
+
var initCommand = defineCommand4({
|
|
1172
2017
|
meta: {
|
|
1173
2018
|
name: "init",
|
|
1174
2019
|
description: t("cli.init.description")
|
|
@@ -1227,6 +2072,10 @@ var initCommand = defineCommand({
|
|
|
1227
2072
|
type: "string",
|
|
1228
2073
|
default: "global",
|
|
1229
2074
|
description: t("cli.init.mcp.install.prompt")
|
|
2075
|
+
},
|
|
2076
|
+
scope: {
|
|
2077
|
+
type: "string",
|
|
2078
|
+
description: t("cli.init.mcp.scope.description")
|
|
1230
2079
|
}
|
|
1231
2080
|
},
|
|
1232
2081
|
async run({ args }) {
|
|
@@ -1238,37 +2087,42 @@ async function runInitCommand(args) {
|
|
|
1238
2087
|
const logger = createDebugLogger(args.debug);
|
|
1239
2088
|
const resolution = resolveDevMode(args.target, process.cwd());
|
|
1240
2089
|
const intent = resolveInitCliIntent(args, resolution.target);
|
|
2090
|
+
if (args.reapply === true) {
|
|
2091
|
+
checkLockOrThrow(intent.target, { force: args.force });
|
|
2092
|
+
}
|
|
1241
2093
|
logger(`init target source: ${resolution.source}`);
|
|
1242
2094
|
for (const step of resolution.chain) {
|
|
1243
2095
|
logger(step);
|
|
1244
2096
|
}
|
|
1245
2097
|
if (intent.options.planOnly) {
|
|
1246
|
-
|
|
2098
|
+
writeStderr3(t("cli.init.compat.plan"));
|
|
1247
2099
|
}
|
|
1248
2100
|
if (args.interactive === false) {
|
|
1249
|
-
|
|
2101
|
+
writeStderr3(t("cli.init.compat.interactive"));
|
|
1250
2102
|
}
|
|
1251
2103
|
if (args.bootstrap === false || args.mcp === false || args.hooks === false) {
|
|
1252
|
-
|
|
2104
|
+
writeStderr3(t("cli.init.compat.legacy-stage-flags"));
|
|
1253
2105
|
}
|
|
1254
2106
|
const supports = detectClientSupports(intent.target);
|
|
1255
2107
|
const basePlan = await buildInitExecutionPlan({
|
|
1256
2108
|
target: intent.target,
|
|
1257
2109
|
options: intent.options,
|
|
1258
2110
|
mcpInstallMode: intent.mcpInstallMode,
|
|
2111
|
+
claudeMcpScope: intent.claudeMcpScope,
|
|
1259
2112
|
interactive: intent.interactiveSummary && !intent.wizardEnabled,
|
|
1260
2113
|
supports
|
|
1261
2114
|
});
|
|
1262
2115
|
const plan = intent.wizardEnabled ? await resolveInitExecutionPlanWithWizard(basePlan, args, createDefaultInitWizardAdapter()) : basePlan;
|
|
1263
2116
|
if (plan === null) {
|
|
1264
|
-
|
|
2117
|
+
writeStderr3(t("cli.init.wizard.cancelled"));
|
|
1265
2118
|
throw new Error(t("cli.init.wizard.cancelled"));
|
|
1266
2119
|
}
|
|
1267
2120
|
return executeInitExecutionPlan(plan);
|
|
1268
2121
|
}
|
|
1269
2122
|
function resolveInitCliIntent(args, targetInput) {
|
|
1270
|
-
const target =
|
|
2123
|
+
const target = normalizeTarget4(targetInput);
|
|
1271
2124
|
const mcpInstallMode = resolveMcpInstallMode(args["mcp-install"]);
|
|
2125
|
+
const claudeMcpScope = resolveClaudeMcpScope(args.scope);
|
|
1272
2126
|
const terminalInteractive = isInteractiveInit();
|
|
1273
2127
|
const planOnly = args.plan === true;
|
|
1274
2128
|
const reapply = args.reapply === true;
|
|
@@ -1284,21 +2138,34 @@ function resolveInitCliIntent(args, targetInput) {
|
|
|
1284
2138
|
target,
|
|
1285
2139
|
options,
|
|
1286
2140
|
mcpInstallMode,
|
|
2141
|
+
claudeMcpScope,
|
|
1287
2142
|
interactiveSummary: args.interactive !== false && terminalInteractive,
|
|
1288
2143
|
wizardEnabled: shouldUseInitWizard(args, terminalInteractive) && !planOnly
|
|
1289
2144
|
};
|
|
1290
2145
|
}
|
|
2146
|
+
function resolveClaudeMcpScope(raw) {
|
|
2147
|
+
if (raw === void 0 || raw === "project") {
|
|
2148
|
+
return "project";
|
|
2149
|
+
}
|
|
2150
|
+
if (raw === "user") {
|
|
2151
|
+
return "user";
|
|
2152
|
+
}
|
|
2153
|
+
writeStderr3(t("cli.init.mcp.scope.invalid", { value: raw }));
|
|
2154
|
+
return "project";
|
|
2155
|
+
}
|
|
1291
2156
|
async function buildInitExecutionPlan(input) {
|
|
1292
2157
|
const options = input.options ?? {};
|
|
1293
2158
|
const scaffold = await buildInitFabricPlan(input.target, options);
|
|
1294
2159
|
const supports = input.supports ?? detectClientSupports(input.target);
|
|
1295
2160
|
const mcpInstallMode = input.mcpInstallMode ?? "global";
|
|
2161
|
+
const claudeMcpScope = input.claudeMcpScope ?? "project";
|
|
1296
2162
|
const stages = [
|
|
1297
2163
|
{ name: "bootstrap", skipped: Boolean(options.skipBootstrap) },
|
|
1298
2164
|
{
|
|
1299
2165
|
name: "mcp",
|
|
1300
2166
|
skipped: Boolean(options.skipMcp),
|
|
1301
2167
|
installMode: mcpInstallMode,
|
|
2168
|
+
claudeMcpScope,
|
|
1302
2169
|
localServerPath: mcpInstallMode === "local" ? LOCAL_FABRIC_SERVER_PATH : void 0,
|
|
1303
2170
|
packageManager: mcpInstallMode === "local" ? detectPackageManager(input.target) : void 0
|
|
1304
2171
|
},
|
|
@@ -1308,6 +2175,7 @@ async function buildInitExecutionPlan(input) {
|
|
|
1308
2175
|
target: input.target,
|
|
1309
2176
|
options,
|
|
1310
2177
|
mcpInstallMode,
|
|
2178
|
+
claudeMcpScope,
|
|
1311
2179
|
interactive: input.interactive ?? false,
|
|
1312
2180
|
supports,
|
|
1313
2181
|
scaffold,
|
|
@@ -1322,10 +2190,10 @@ async function buildInitExecutionPlan(input) {
|
|
|
1322
2190
|
}
|
|
1323
2191
|
async function executeInitExecutionPlan(plan) {
|
|
1324
2192
|
if (plan.options.force) {
|
|
1325
|
-
|
|
2193
|
+
writeStderr3(t("cli.init.force.warning", { path: plan.target }));
|
|
1326
2194
|
}
|
|
1327
2195
|
if (plan.options.reapply && !plan.options.planOnly && !plan.interactive) {
|
|
1328
|
-
|
|
2196
|
+
writeStderr3(formatInitModeBanner(plan.options));
|
|
1329
2197
|
}
|
|
1330
2198
|
if (plan.interactive) {
|
|
1331
2199
|
printInitPlanSummary(plan.target, plan.options, plan.mcpInstallMode, plan.supports);
|
|
@@ -1347,7 +2215,7 @@ async function executeInitExecutionPlan(plan) {
|
|
|
1347
2215
|
case "preflight":
|
|
1348
2216
|
break;
|
|
1349
2217
|
case "scaffold":
|
|
1350
|
-
created = executeInitFabricPlan(plan.scaffold);
|
|
2218
|
+
created = await executeInitFabricPlan(plan.scaffold);
|
|
1351
2219
|
printInitScaffoldResult(created);
|
|
1352
2220
|
break;
|
|
1353
2221
|
case "bootstrap":
|
|
@@ -1371,28 +2239,28 @@ async function executeInitExecutionPlan(plan) {
|
|
|
1371
2239
|
};
|
|
1372
2240
|
}
|
|
1373
2241
|
async function buildInitFabricPlan(target, options) {
|
|
1374
|
-
|
|
1375
|
-
const fabricDir =
|
|
1376
|
-
const bootstrapPath =
|
|
1377
|
-
const forensicPath =
|
|
1378
|
-
const taxonomyPath =
|
|
1379
|
-
const
|
|
1380
|
-
const
|
|
1381
|
-
const
|
|
1382
|
-
const
|
|
1383
|
-
const
|
|
1384
|
-
const
|
|
1385
|
-
const
|
|
1386
|
-
const
|
|
1387
|
-
const
|
|
2242
|
+
assertExistingDirectory3(target);
|
|
2243
|
+
const fabricDir = join8(target, ".fabric");
|
|
2244
|
+
const bootstrapPath = join8(fabricDir, "bootstrap", "README.md");
|
|
2245
|
+
const forensicPath = join8(fabricDir, "forensic.json");
|
|
2246
|
+
const taxonomyPath = join8(fabricDir, "INITIAL_TAXONOMY.md");
|
|
2247
|
+
const rulesDir = join8(fabricDir, "rules");
|
|
2248
|
+
const eventsPath = join8(fabricDir, "events.jsonl");
|
|
2249
|
+
const claudeSkillPath = join8(target, ".claude", "skills", "fabric-init", "SKILL.md");
|
|
2250
|
+
const codexSkillPath = join8(target, ".agents", "skills", "fabric-init", "SKILL.md");
|
|
2251
|
+
const codexSessionStartHookPath = join8(target, ".codex", "hooks", "fabric-session-start.cjs");
|
|
2252
|
+
const codexStopHookPath = join8(target, ".codex", "hooks", "fabric-stop-reminder.cjs");
|
|
2253
|
+
const codexHooksConfigPath = join8(target, ".codex", "hooks.json");
|
|
2254
|
+
const claudeHookPath = join8(target, ".claude", "hooks", "agents-md-init-reminder.cjs");
|
|
2255
|
+
const claudeSettingsPath = join8(target, ".claude", "settings.json");
|
|
2256
|
+
const metaPath = join8(fabricDir, "agents.meta.json");
|
|
1388
2257
|
const replaceFabricDir = shouldReplaceWritableDirectory(fabricDir, options);
|
|
1389
2258
|
const bootstrapAction = planFreshPath(bootstrapPath, options);
|
|
1390
2259
|
const metaAction = planFreshPath(metaPath, options);
|
|
1391
2260
|
const taxonomyAction = planFreshPath(taxonomyPath, options);
|
|
1392
|
-
const
|
|
2261
|
+
const eventsAction = planFreshPath(eventsPath, options);
|
|
1393
2262
|
const forensicAction = planFreshPath(forensicPath, options);
|
|
1394
2263
|
const forensicReport = await buildForensicReport(target);
|
|
1395
|
-
const humanLockTemplate = readFileSync2(findTemplatePath("templates/fabric/human-lock.json"), "utf8");
|
|
1396
2264
|
const bootstrapContent = await buildFabricBootstrapGuide(target);
|
|
1397
2265
|
const taxonomyContent = buildInitialTaxonomyMarkdown(forensicReport);
|
|
1398
2266
|
const bootstrapHash = sha256(bootstrapContent);
|
|
@@ -1411,62 +2279,79 @@ async function buildInitFabricPlan(target, options) {
|
|
|
1411
2279
|
taxonomyPath,
|
|
1412
2280
|
taxonomyAction,
|
|
1413
2281
|
taxonomyContent,
|
|
1414
|
-
|
|
1415
|
-
|
|
1416
|
-
|
|
1417
|
-
`,
|
|
2282
|
+
rulesDir,
|
|
2283
|
+
eventsPath,
|
|
2284
|
+
eventsAction,
|
|
1418
2285
|
forensicPath,
|
|
1419
2286
|
forensicAction,
|
|
1420
2287
|
forensicReport,
|
|
1421
|
-
claudeSkill: buildOptionalTemplateWritePlan(claudeSkillPath,
|
|
1422
|
-
codexSkill: buildOptionalTemplateWritePlan(codexSkillPath,
|
|
2288
|
+
claudeSkill: buildOptionalTemplateWritePlan(claudeSkillPath, findTemplatePath3(CLAUDE_INIT_SKILL_TEMPLATE), options),
|
|
2289
|
+
codexSkill: buildOptionalTemplateWritePlan(codexSkillPath, findTemplatePath3(CODEX_INIT_SKILL_TEMPLATE), options),
|
|
1423
2290
|
codexSessionStartHook: buildOptionalTemplateWritePlan(
|
|
1424
2291
|
codexSessionStartHookPath,
|
|
1425
|
-
|
|
2292
|
+
findTemplatePath3(CODEX_SESSION_START_HOOK_TEMPLATE),
|
|
1426
2293
|
options,
|
|
1427
2294
|
true
|
|
1428
2295
|
),
|
|
1429
2296
|
codexStopHook: buildOptionalTemplateWritePlan(
|
|
1430
2297
|
codexStopHookPath,
|
|
1431
|
-
|
|
2298
|
+
findTemplatePath3(CODEX_STOP_HOOK_TEMPLATE),
|
|
1432
2299
|
options,
|
|
1433
2300
|
true
|
|
1434
2301
|
),
|
|
1435
2302
|
codexHooksConfig: buildCodexHooksConfigPlan(codexHooksConfigPath, options),
|
|
1436
2303
|
claudeHook: buildOptionalTemplateWritePlan(
|
|
1437
2304
|
claudeHookPath,
|
|
1438
|
-
|
|
2305
|
+
findTemplatePath3(CLAUDE_INIT_REMINDER_HOOK_TEMPLATE),
|
|
1439
2306
|
options,
|
|
1440
2307
|
true
|
|
1441
2308
|
),
|
|
1442
2309
|
claudeSettings: buildClaudeSettingsWritePlan(claudeSettingsPath, options)
|
|
1443
2310
|
};
|
|
1444
2311
|
}
|
|
1445
|
-
function executeInitFabricPlan(plan) {
|
|
2312
|
+
async function executeInitFabricPlan(plan) {
|
|
2313
|
+
const isReapply = plan.options?.reapply === true;
|
|
2314
|
+
const existingRules = isReapply && existsSync9(plan.rulesDir) ? readdirSync2(plan.rulesDir).filter((f) => f.endsWith(".md")) : [];
|
|
2315
|
+
const preserveMeta = isReapply && existingRules.length > 0;
|
|
1446
2316
|
if (plan.replaceFabricDir) {
|
|
1447
2317
|
rmSync(plan.fabricDir, { force: true });
|
|
1448
2318
|
}
|
|
1449
|
-
|
|
1450
|
-
|
|
2319
|
+
mkdirSync3(plan.fabricDir, { recursive: true });
|
|
2320
|
+
mkdirSync3(dirname5(plan.bootstrapPath), { recursive: true });
|
|
1451
2321
|
preparePlannedPath(plan.bootstrapPath, plan.bootstrapAction);
|
|
1452
|
-
|
|
1453
|
-
|
|
1454
|
-
|
|
1455
|
-
|
|
2322
|
+
await atomicWriteText4(plan.bootstrapPath, plan.bootstrapContent);
|
|
2323
|
+
if (!preserveMeta) {
|
|
2324
|
+
preparePlannedPath(plan.metaPath, plan.metaAction);
|
|
2325
|
+
await atomicWriteJson3(plan.metaPath, plan.meta);
|
|
2326
|
+
}
|
|
1456
2327
|
preparePlannedPath(plan.taxonomyPath, plan.taxonomyAction);
|
|
1457
|
-
|
|
1458
|
-
|
|
1459
|
-
|
|
2328
|
+
await atomicWriteText4(plan.taxonomyPath, ensureTrailingNewline2(plan.taxonomyContent));
|
|
2329
|
+
mkdirSync3(plan.rulesDir, { recursive: true });
|
|
2330
|
+
if (isReapply) {
|
|
2331
|
+
if (!existsSync9(plan.eventsPath)) {
|
|
2332
|
+
mkdirSync3(dirname5(plan.eventsPath), { recursive: true });
|
|
2333
|
+
writeFileSync(plan.eventsPath, "", "utf8");
|
|
2334
|
+
}
|
|
2335
|
+
} else {
|
|
2336
|
+
preparePlannedPath(plan.eventsPath, plan.eventsAction);
|
|
2337
|
+
writeFileSync(plan.eventsPath, "", "utf8");
|
|
2338
|
+
}
|
|
1460
2339
|
preparePlannedPath(plan.forensicPath, plan.forensicAction);
|
|
1461
|
-
|
|
1462
|
-
`, "utf8");
|
|
2340
|
+
await atomicWriteJson3(plan.forensicPath, plan.forensicReport);
|
|
1463
2341
|
applyOptionalTemplateWritePlan(plan.claudeSkill);
|
|
1464
2342
|
applyOptionalTemplateWritePlan(plan.codexSkill);
|
|
1465
2343
|
applyOptionalTemplateWritePlan(plan.codexSessionStartHook);
|
|
1466
2344
|
applyOptionalTemplateWritePlan(plan.codexStopHook);
|
|
1467
|
-
applyJsonWritePlan(plan.codexHooksConfig);
|
|
2345
|
+
await applyJsonWritePlan(plan.codexHooksConfig);
|
|
1468
2346
|
applyOptionalTemplateWritePlan(plan.claudeHook);
|
|
1469
|
-
applyClaudeSettingsWritePlan(plan.claudeSettings);
|
|
2347
|
+
await applyClaudeSettingsWritePlan(plan.claudeSettings);
|
|
2348
|
+
if (isReapply) {
|
|
2349
|
+
appendReapplyLedgerEvent(plan.eventsPath, {
|
|
2350
|
+
preserved_ledger: true,
|
|
2351
|
+
preserved_meta: preserveMeta,
|
|
2352
|
+
rules_count: existingRules.length
|
|
2353
|
+
});
|
|
2354
|
+
}
|
|
1470
2355
|
return {
|
|
1471
2356
|
bootstrapPath: plan.bootstrapPath,
|
|
1472
2357
|
bootstrapAction: plan.bootstrapAction,
|
|
@@ -1474,8 +2359,8 @@ function executeInitFabricPlan(plan) {
|
|
|
1474
2359
|
metaAction: plan.metaAction,
|
|
1475
2360
|
taxonomyPath: plan.taxonomyPath,
|
|
1476
2361
|
taxonomyAction: plan.taxonomyAction,
|
|
1477
|
-
|
|
1478
|
-
|
|
2362
|
+
eventsPath: plan.eventsPath,
|
|
2363
|
+
eventsAction: plan.eventsAction,
|
|
1479
2364
|
forensicPath: plan.forensicPath,
|
|
1480
2365
|
forensicAction: plan.forensicAction,
|
|
1481
2366
|
claudeSkillPath: plan.claudeSkill.path,
|
|
@@ -1495,7 +2380,7 @@ function executeInitFabricPlan(plan) {
|
|
|
1495
2380
|
};
|
|
1496
2381
|
}
|
|
1497
2382
|
async function initFabric(target, options) {
|
|
1498
|
-
return executeInitFabricPlan(await buildInitFabricPlan(target, options));
|
|
2383
|
+
return await executeInitFabricPlan(await buildInitFabricPlan(target, options));
|
|
1499
2384
|
}
|
|
1500
2385
|
function shouldUseInitWizard(args, terminalInteractive = isInteractiveInit()) {
|
|
1501
2386
|
return terminalInteractive && args.interactive !== false && args.yes !== true;
|
|
@@ -1506,6 +2391,7 @@ async function resolveInitExecutionPlanWithWizard(basePlan, args, wizardAdapter)
|
|
|
1506
2391
|
options: basePlan.options,
|
|
1507
2392
|
supports: basePlan.supports,
|
|
1508
2393
|
mcpInstallMode: basePlan.mcpInstallMode,
|
|
2394
|
+
claudeMcpScope: basePlan.claudeMcpScope,
|
|
1509
2395
|
lockedStages: collectLockedWizardStages(args)
|
|
1510
2396
|
});
|
|
1511
2397
|
if (selection === null) {
|
|
@@ -1520,6 +2406,7 @@ async function resolveInitExecutionPlanWithWizard(basePlan, args, wizardAdapter)
|
|
|
1520
2406
|
skipHooks: !selection.hooks
|
|
1521
2407
|
},
|
|
1522
2408
|
mcpInstallMode: selection.mcp ? selection.mcpInstallMode : basePlan.mcpInstallMode,
|
|
2409
|
+
claudeMcpScope: selection.claudeMcpScope,
|
|
1523
2410
|
interactive: false,
|
|
1524
2411
|
supports: basePlan.supports
|
|
1525
2412
|
});
|
|
@@ -1537,19 +2424,19 @@ function printInitScaffoldResult(created) {
|
|
|
1537
2424
|
console.log(formatInitPathAction(created.bootstrapPath, created.bootstrapAction));
|
|
1538
2425
|
console.log(formatInitPathAction(created.metaPath, created.metaAction));
|
|
1539
2426
|
console.log(formatInitPathAction(created.taxonomyPath, created.taxonomyAction));
|
|
1540
|
-
console.log(formatInitPathAction(created.
|
|
2427
|
+
console.log(formatInitPathAction(created.eventsPath, created.eventsAction));
|
|
1541
2428
|
console.log(formatInitPathAction(created.forensicPath, created.forensicAction));
|
|
1542
|
-
|
|
1543
|
-
|
|
1544
|
-
|
|
2429
|
+
writeStderr3(formatOptionalInitPathAction(created.claudeSkillPath, created.claudeSkillAction));
|
|
2430
|
+
writeStderr3(formatOptionalInitPathAction(created.codexSkillPath, created.codexSkillAction));
|
|
2431
|
+
writeStderr3(
|
|
1545
2432
|
formatOptionalInitPathAction(created.codexSessionStartHookPath, created.codexSessionStartHookAction)
|
|
1546
2433
|
);
|
|
1547
|
-
|
|
2434
|
+
writeStderr3(
|
|
1548
2435
|
formatOptionalInitPathAction(created.codexStopHookPath, created.codexStopHookAction)
|
|
1549
2436
|
);
|
|
1550
|
-
|
|
1551
|
-
|
|
1552
|
-
|
|
2437
|
+
writeStderr3(formatCodexHooksAction(created.codexHooksConfigPath, created.codexHooksConfigAction));
|
|
2438
|
+
writeStderr3(formatOptionalInitPathAction(created.claudeHookPath, created.claudeHookAction));
|
|
2439
|
+
writeStderr3(formatClaudeSettingsAction(created.claudeSettingsPath, created.claudeSettingsAction));
|
|
1553
2440
|
}
|
|
1554
2441
|
function printInitPostSetup(plan, stageResults, finalSupports) {
|
|
1555
2442
|
if (shouldPrintHooksNextStep(plan.options, stageResults)) {
|
|
@@ -1589,8 +2476,8 @@ function buildPlanOnlyScaffoldResult(plan) {
|
|
|
1589
2476
|
metaAction: plan.metaAction,
|
|
1590
2477
|
taxonomyPath: plan.taxonomyPath,
|
|
1591
2478
|
taxonomyAction: plan.taxonomyAction,
|
|
1592
|
-
|
|
1593
|
-
|
|
2479
|
+
eventsPath: plan.eventsPath,
|
|
2480
|
+
eventsAction: plan.eventsAction,
|
|
1594
2481
|
forensicPath: plan.forensicPath,
|
|
1595
2482
|
forensicAction: plan.forensicAction,
|
|
1596
2483
|
claudeSkillPath: plan.claudeSkill.path,
|
|
@@ -1634,16 +2521,17 @@ async function executeInitStagePlan(plan, stageName) {
|
|
|
1634
2521
|
case "mcp": {
|
|
1635
2522
|
if (stage.installMode === "local") {
|
|
1636
2523
|
const manager = stage.packageManager ?? detectPackageManager(plan.target);
|
|
1637
|
-
|
|
1638
|
-
|
|
2524
|
+
writeStderr3(t("cli.init.mcp.install.local"));
|
|
2525
|
+
writeStderr3(t("cli.init.mcp.local.installing", { manager }));
|
|
1639
2526
|
installLocalFabricServer(plan.target, manager);
|
|
1640
|
-
|
|
2527
|
+
writeStderr3(t("cli.init.mcp.local.installed"));
|
|
1641
2528
|
} else {
|
|
1642
|
-
|
|
2529
|
+
writeStderr3(t("cli.init.mcp.install.global"));
|
|
1643
2530
|
}
|
|
1644
2531
|
const result = await installMcpClients(plan.target, {
|
|
1645
2532
|
force: plan.options.force,
|
|
1646
|
-
localServerPath: stage.localServerPath
|
|
2533
|
+
localServerPath: stage.localServerPath,
|
|
2534
|
+
claudeMcpScope: stage.claudeMcpScope
|
|
1647
2535
|
});
|
|
1648
2536
|
if (result.details.length === 0) {
|
|
1649
2537
|
console.log(formatInitStageResult("mcp", "skipped", 0, 0, t("cli.config.install.no-configs")));
|
|
@@ -1661,15 +2549,15 @@ async function executeInitStagePlan(plan, stageName) {
|
|
|
1661
2549
|
return exhaustiveInitStagePlan(stage);
|
|
1662
2550
|
}
|
|
1663
2551
|
} catch (error) {
|
|
1664
|
-
|
|
2552
|
+
writeStderr3(formatInitStageFailure(stageName, error));
|
|
1665
2553
|
return { name: stageName, disposition: "failed" };
|
|
1666
2554
|
}
|
|
1667
2555
|
}
|
|
1668
2556
|
function shouldReplaceWritableDirectory(path, options) {
|
|
1669
|
-
if (!
|
|
2557
|
+
if (!existsSync9(path)) {
|
|
1670
2558
|
return false;
|
|
1671
2559
|
}
|
|
1672
|
-
if (
|
|
2560
|
+
if (statSync3(path).isDirectory()) {
|
|
1673
2561
|
return false;
|
|
1674
2562
|
}
|
|
1675
2563
|
if (!options?.force) {
|
|
@@ -1678,7 +2566,7 @@ function shouldReplaceWritableDirectory(path, options) {
|
|
|
1678
2566
|
return true;
|
|
1679
2567
|
}
|
|
1680
2568
|
function planFreshPath(path, options) {
|
|
1681
|
-
if (!
|
|
2569
|
+
if (!existsSync9(path)) {
|
|
1682
2570
|
return "created";
|
|
1683
2571
|
}
|
|
1684
2572
|
if (!options?.force) {
|
|
@@ -1687,13 +2575,13 @@ function planFreshPath(path, options) {
|
|
|
1687
2575
|
return "overwritten";
|
|
1688
2576
|
}
|
|
1689
2577
|
function preparePlannedPath(path, action) {
|
|
1690
|
-
|
|
1691
|
-
if (action === "overwritten" &&
|
|
2578
|
+
mkdirSync3(dirname5(path), { recursive: true });
|
|
2579
|
+
if (action === "overwritten" && existsSync9(path)) {
|
|
1692
2580
|
rmSync(path, { recursive: true, force: true });
|
|
1693
2581
|
}
|
|
1694
2582
|
}
|
|
1695
2583
|
function buildOptionalTemplateWritePlan(path, templatePath, options, executable = false) {
|
|
1696
|
-
const existed =
|
|
2584
|
+
const existed = existsSync9(path);
|
|
1697
2585
|
if (existed && !options?.force) {
|
|
1698
2586
|
return { path, action: "skipped", templatePath, executable };
|
|
1699
2587
|
}
|
|
@@ -1708,10 +2596,10 @@ function applyOptionalTemplateWritePlan(plan) {
|
|
|
1708
2596
|
if (plan.action === "skipped") {
|
|
1709
2597
|
return;
|
|
1710
2598
|
}
|
|
1711
|
-
|
|
2599
|
+
mkdirSync3(dirname5(plan.path), { recursive: true });
|
|
1712
2600
|
copyFileSync(plan.templatePath, plan.path);
|
|
1713
2601
|
if (plan.executable) {
|
|
1714
|
-
|
|
2602
|
+
chmodSync2(plan.path, 493);
|
|
1715
2603
|
}
|
|
1716
2604
|
}
|
|
1717
2605
|
function buildCodexHooksConfigValue() {
|
|
@@ -1733,48 +2621,48 @@ function buildCodexHooksConfigValue() {
|
|
|
1733
2621
|
};
|
|
1734
2622
|
}
|
|
1735
2623
|
function buildCodexHooksConfigPlan(configPath, options) {
|
|
1736
|
-
const action = !
|
|
2624
|
+
const action = !existsSync9(configPath) ? "created" : options?.force ? "overwritten" : "skipped";
|
|
1737
2625
|
return {
|
|
1738
2626
|
path: configPath,
|
|
1739
2627
|
action,
|
|
1740
2628
|
value: buildCodexHooksConfigValue()
|
|
1741
2629
|
};
|
|
1742
2630
|
}
|
|
1743
|
-
function applyJsonWritePlan(plan) {
|
|
2631
|
+
async function applyJsonWritePlan(plan) {
|
|
1744
2632
|
if (plan.action === "skipped") {
|
|
1745
2633
|
return;
|
|
1746
2634
|
}
|
|
1747
|
-
|
|
1748
|
-
|
|
2635
|
+
mkdirSync3(dirname5(plan.path), { recursive: true });
|
|
2636
|
+
await atomicWriteJson3(plan.path, plan.value);
|
|
1749
2637
|
}
|
|
1750
2638
|
function buildClaudeSettingsWritePlan(settingsPath, options) {
|
|
1751
2639
|
let settings;
|
|
1752
2640
|
let action = "updated";
|
|
1753
|
-
if (!
|
|
2641
|
+
if (!existsSync9(settingsPath)) {
|
|
1754
2642
|
settings = {};
|
|
1755
2643
|
action = "created";
|
|
1756
2644
|
} else {
|
|
1757
2645
|
try {
|
|
1758
|
-
const parsed = JSON.parse(
|
|
2646
|
+
const parsed = JSON.parse(readFileSync4(settingsPath, "utf8"));
|
|
1759
2647
|
if (!isRecord(parsed)) {
|
|
1760
|
-
|
|
2648
|
+
writeStderr3(t("cli.init.claude-settings.invalid-object", { label: skippedLabel(), path: settingsPath }));
|
|
1761
2649
|
return { path: settingsPath, action: "skipped-invalid", value: null };
|
|
1762
2650
|
}
|
|
1763
2651
|
settings = parsed;
|
|
1764
2652
|
} catch (error) {
|
|
1765
2653
|
const reason = error instanceof Error ? error.message : "unknown parse error";
|
|
1766
|
-
|
|
2654
|
+
writeStderr3(t("cli.init.claude-settings.invalid-json", { label: skippedLabel(), path: settingsPath, reason }));
|
|
1767
2655
|
return { path: settingsPath, action: "skipped-invalid", value: null };
|
|
1768
2656
|
}
|
|
1769
2657
|
}
|
|
1770
2658
|
if (settings.hooks !== void 0 && !isRecord(settings.hooks)) {
|
|
1771
|
-
|
|
2659
|
+
writeStderr3(t("cli.init.claude-settings.invalid-hooks", { label: skippedLabel(), path: settingsPath }));
|
|
1772
2660
|
return { path: settingsPath, action: "skipped-invalid", value: null };
|
|
1773
2661
|
}
|
|
1774
2662
|
const hooks = settings.hooks ?? {};
|
|
1775
2663
|
const stopHooksValue = hooks.Stop;
|
|
1776
2664
|
if (stopHooksValue !== void 0 && !Array.isArray(stopHooksValue)) {
|
|
1777
|
-
|
|
2665
|
+
writeStderr3(t("cli.init.claude-settings.invalid-stop-array", { label: skippedLabel(), path: settingsPath }));
|
|
1778
2666
|
return { path: settingsPath, action: "skipped-invalid", value: null };
|
|
1779
2667
|
}
|
|
1780
2668
|
const stopHooks = Array.isArray(stopHooksValue) ? stopHooksValue : [];
|
|
@@ -1805,12 +2693,12 @@ function buildClaudeSettingsWritePlan(settingsPath, options) {
|
|
|
1805
2693
|
value: nextSettings
|
|
1806
2694
|
};
|
|
1807
2695
|
}
|
|
1808
|
-
function applyClaudeSettingsWritePlan(plan) {
|
|
2696
|
+
async function applyClaudeSettingsWritePlan(plan) {
|
|
1809
2697
|
if (plan.value === null) {
|
|
1810
2698
|
return;
|
|
1811
2699
|
}
|
|
1812
|
-
|
|
1813
|
-
|
|
2700
|
+
mkdirSync3(dirname5(plan.path), { recursive: true });
|
|
2701
|
+
await atomicWriteJson3(plan.path, plan.value);
|
|
1814
2702
|
}
|
|
1815
2703
|
function createDefaultInitWizardAdapter() {
|
|
1816
2704
|
return {
|
|
@@ -1858,6 +2746,14 @@ function createDefaultInitWizardAdapter() {
|
|
|
1858
2746
|
{ value: "local", label: "local", hint: t("cli.init.mcp.install.local") }
|
|
1859
2747
|
]
|
|
1860
2748
|
}) : context.mcpInstallMode,
|
|
2749
|
+
claudeMcpScope: async ({ results }) => results.mcp ? selectClaudeMcpScopeInGroup({
|
|
2750
|
+
message: t("cli.init.wizard.mcp-scope", { defaultValue: context.claudeMcpScope }),
|
|
2751
|
+
initialValue: context.claudeMcpScope,
|
|
2752
|
+
options: [
|
|
2753
|
+
{ value: "project", label: "project", hint: t("cli.init.mcp.scope.project") },
|
|
2754
|
+
{ value: "user", label: "user", hint: t("cli.init.mcp.scope.user") }
|
|
2755
|
+
]
|
|
2756
|
+
}) : context.claudeMcpScope,
|
|
1861
2757
|
hooks: async () => context.lockedStages.includes("hooks") ? false : confirmInGroup({
|
|
1862
2758
|
message: t("cli.init.wizard.stage.hooks", {
|
|
1863
2759
|
defaultValue: formatPromptDefault(!context.options.skipHooks)
|
|
@@ -1924,6 +2820,17 @@ async function selectMcpInstallModeInGroup(options) {
|
|
|
1924
2820
|
}
|
|
1925
2821
|
return result;
|
|
1926
2822
|
}
|
|
2823
|
+
async function selectClaudeMcpScopeInGroup(options) {
|
|
2824
|
+
const result = await select({
|
|
2825
|
+
message: options.message,
|
|
2826
|
+
initialValue: options.initialValue,
|
|
2827
|
+
options: options.options
|
|
2828
|
+
});
|
|
2829
|
+
if (isCancel(result)) {
|
|
2830
|
+
throw INIT_WIZARD_GROUP_CANCELLED;
|
|
2831
|
+
}
|
|
2832
|
+
return result;
|
|
2833
|
+
}
|
|
1927
2834
|
function collectLockedWizardStages(args) {
|
|
1928
2835
|
const lockedStages = [];
|
|
1929
2836
|
if (args.bootstrap === false) {
|
|
@@ -1964,23 +2871,23 @@ function formatInitModeBadge(options) {
|
|
|
1964
2871
|
}
|
|
1965
2872
|
return t("cli.init.mode.badge.default");
|
|
1966
2873
|
}
|
|
1967
|
-
function
|
|
1968
|
-
return
|
|
2874
|
+
function normalizeTarget4(targetInput) {
|
|
2875
|
+
return isAbsolute4(targetInput) ? targetInput : resolve9(process.cwd(), targetInput);
|
|
1969
2876
|
}
|
|
1970
|
-
function
|
|
1971
|
-
if (!
|
|
2877
|
+
function assertExistingDirectory3(target) {
|
|
2878
|
+
if (!existsSync9(target) || !statSync3(target).isDirectory()) {
|
|
1972
2879
|
throw new Error(`Target must be an existing directory: ${target}`);
|
|
1973
2880
|
}
|
|
1974
2881
|
}
|
|
1975
2882
|
function detectPackageManager(cwd) {
|
|
1976
|
-
const workspaceRoot =
|
|
1977
|
-
if (
|
|
2883
|
+
const workspaceRoot = resolve9(cwd);
|
|
2884
|
+
if (existsSync9(join8(workspaceRoot, "pnpm-lock.yaml"))) {
|
|
1978
2885
|
return "pnpm";
|
|
1979
2886
|
}
|
|
1980
|
-
if (
|
|
2887
|
+
if (existsSync9(join8(workspaceRoot, "yarn.lock"))) {
|
|
1981
2888
|
return "yarn";
|
|
1982
2889
|
}
|
|
1983
|
-
if (
|
|
2890
|
+
if (existsSync9(join8(workspaceRoot, "package-lock.json"))) {
|
|
1984
2891
|
return "npm";
|
|
1985
2892
|
}
|
|
1986
2893
|
return "npm";
|
|
@@ -1989,7 +2896,7 @@ function resolveMcpInstallMode(rawMode) {
|
|
|
1989
2896
|
if (rawMode === void 0 || rawMode === "global" || rawMode === "local") {
|
|
1990
2897
|
return rawMode ?? "global";
|
|
1991
2898
|
}
|
|
1992
|
-
|
|
2899
|
+
writeStderr3(t("cli.init.mcp.install.invalid", { value: rawMode }));
|
|
1993
2900
|
return "global";
|
|
1994
2901
|
}
|
|
1995
2902
|
function installLocalFabricServer(target, manager) {
|
|
@@ -2016,6 +2923,21 @@ function createInitialMeta(agentsHash) {
|
|
|
2016
2923
|
}
|
|
2017
2924
|
};
|
|
2018
2925
|
}
|
|
2926
|
+
function appendReapplyLedgerEvent(eventsPath, payload) {
|
|
2927
|
+
const event = {
|
|
2928
|
+
kind: "fabric-event",
|
|
2929
|
+
id: `event:${randomUUID()}`,
|
|
2930
|
+
ts: Date.now(),
|
|
2931
|
+
schema_version: 1,
|
|
2932
|
+
event_type: "reapply_completed",
|
|
2933
|
+
preserved_ledger: payload.preserved_ledger,
|
|
2934
|
+
preserved_meta: payload.preserved_meta,
|
|
2935
|
+
rules_count: payload.rules_count
|
|
2936
|
+
};
|
|
2937
|
+
const line = `${JSON.stringify(event)}
|
|
2938
|
+
`;
|
|
2939
|
+
appendFileSync(eventsPath, line, "utf8");
|
|
2940
|
+
}
|
|
2019
2941
|
function buildInitialTaxonomyMarkdown(forensicReport) {
|
|
2020
2942
|
const frameworkInfo = forensicReport.framework;
|
|
2021
2943
|
const framework = [frameworkInfo?.kind ?? "unknown", frameworkInfo?.subkind ?? ""].filter((value) => value.trim() !== "").join(" / ") || "unknown";
|
|
@@ -2066,30 +2988,30 @@ function sanitizeTaxonomyLabel(value) {
|
|
|
2066
2988
|
const sanitized = value.replaceAll("\\", "/").split("/").filter(Boolean).join("-").replace(/[^A-Za-z0-9_-]+/gu, "-").replace(/^-+|-+$/gu, "");
|
|
2067
2989
|
return sanitized === "" ? "General" : sanitized;
|
|
2068
2990
|
}
|
|
2069
|
-
function
|
|
2991
|
+
function ensureTrailingNewline2(value) {
|
|
2070
2992
|
return value.endsWith("\n") ? value : `${value}
|
|
2071
2993
|
`;
|
|
2072
2994
|
}
|
|
2073
|
-
function
|
|
2074
|
-
const currentModuleDir =
|
|
2995
|
+
function findTemplatePath3(relativePath) {
|
|
2996
|
+
const currentModuleDir = dirname5(fileURLToPath4(import.meta.url));
|
|
2075
2997
|
const candidates = [
|
|
2076
|
-
...
|
|
2077
|
-
...
|
|
2998
|
+
...templateCandidatesFrom3(process.cwd(), relativePath),
|
|
2999
|
+
...templateCandidatesFrom3(currentModuleDir, relativePath)
|
|
2078
3000
|
];
|
|
2079
3001
|
for (const candidate of candidates) {
|
|
2080
|
-
if (
|
|
3002
|
+
if (existsSync9(candidate)) {
|
|
2081
3003
|
return candidate;
|
|
2082
3004
|
}
|
|
2083
3005
|
}
|
|
2084
3006
|
throw new Error(t("cli.shared.template-not-found", { path: relativePath }));
|
|
2085
3007
|
}
|
|
2086
|
-
function
|
|
3008
|
+
function templateCandidatesFrom3(start, relativePath) {
|
|
2087
3009
|
const candidates = [];
|
|
2088
|
-
let current =
|
|
3010
|
+
let current = resolve9(start);
|
|
2089
3011
|
while (true) {
|
|
2090
|
-
candidates.push(
|
|
2091
|
-
const parent =
|
|
2092
|
-
if (parent === current ||
|
|
3012
|
+
candidates.push(join8(current, ...relativePath.split("/")));
|
|
3013
|
+
const parent = dirname5(current);
|
|
3014
|
+
if (parent === current || parse3(current).root === current) {
|
|
2093
3015
|
break;
|
|
2094
3016
|
}
|
|
2095
3017
|
current = parent;
|
|
@@ -2110,12 +3032,6 @@ function isClaudeInitReminderStopEntry(entry) {
|
|
|
2110
3032
|
(hook) => isRecord(hook) && hook.type === "command" && typeof hook.command === "string" && hook.command.includes("agents-md-init-reminder.cjs")
|
|
2111
3033
|
);
|
|
2112
3034
|
}
|
|
2113
|
-
function writeJsonAtomically(path, value) {
|
|
2114
|
-
const tempPath = `${path}.${process.pid}.tmp`;
|
|
2115
|
-
writeFileSync(tempPath, `${JSON.stringify(value, null, 2)}
|
|
2116
|
-
`, "utf8");
|
|
2117
|
-
renameSync(tempPath, path);
|
|
2118
|
-
}
|
|
2119
3035
|
function isRecord(value) {
|
|
2120
3036
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
2121
3037
|
}
|
|
@@ -2187,7 +3103,8 @@ function printInitPlanSummary(target, options, mcpInstallMode, supports) {
|
|
|
2187
3103
|
console.log(t("cli.init.plan.writes"));
|
|
2188
3104
|
console.log(` - ${target}/.fabric/bootstrap/README.md`);
|
|
2189
3105
|
console.log(` - ${target}/.fabric/agents.meta.json`);
|
|
2190
|
-
console.log(` - ${target}/.fabric/
|
|
3106
|
+
console.log(` - ${target}/.fabric/INITIAL_TAXONOMY.md`);
|
|
3107
|
+
console.log(` - ${target}/.fabric/events.jsonl`);
|
|
2191
3108
|
console.log(` - ${target}/.fabric/forensic.json`);
|
|
2192
3109
|
}
|
|
2193
3110
|
function printInitCapabilitySummary(supports, stageResults, options) {
|
|
@@ -2351,7 +3268,7 @@ function skippedStageLabel() {
|
|
|
2351
3268
|
function failedStageLabel() {
|
|
2352
3269
|
return paint.error(t("cli.init.stages.failed"));
|
|
2353
3270
|
}
|
|
2354
|
-
function
|
|
3271
|
+
function writeStderr3(message) {
|
|
2355
3272
|
process.stderr.write(`${message}
|
|
2356
3273
|
`);
|
|
2357
3274
|
}
|