@fenglimg/fabric-cli 1.6.0 → 1.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +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-XP4OQOTX.js +92 -0
- package/dist/index.js +5 -20
- package/dist/{init-LBVOI2QI.js → init-WMB3WLXM.js} +1089 -147
- package/dist/{scan-QH76LC7Z.js → scan-NNBNGIZG.js} +2 -4
- package/dist/{serve-4J2CQY25.js → serve-7AXYKBIT.js} +3 -7
- package/package.json +3 -3
- 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/agents-md-init/SKILL.md +1 -1
- package/templates/codex-skills/fabric-init/SKILL.md +1 -1
- package/templates/husky/pre-commit +9 -24
- 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/fabric/human-lock.json +0 -12
|
@@ -1,47 +1,988 @@
|
|
|
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
17
|
import { createHash } 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 { chmodSync as chmodSync2, copyFileSync, existsSync as existsSync9, mkdirSync as mkdirSync3, readFileSync as readFileSync4, renameSync, rmSync, statSync as statSync3, writeFileSync as writeFileSync3 } 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 { defineCommand as defineCommand4 } from "citty";
|
|
24
|
+
|
|
25
|
+
// src/bootstrap-guide.ts
|
|
26
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
|
|
27
|
+
import { dirname, isAbsolute, join, parse, resolve } from "path";
|
|
28
|
+
import { fileURLToPath } from "url";
|
|
29
|
+
var AGENTS_TEMPLATE_BY_FRAMEWORK = {
|
|
30
|
+
"cocos-creator": "templates/agents-md/variants/cocos.md",
|
|
31
|
+
vite: "templates/agents-md/variants/vite.md",
|
|
32
|
+
next: "templates/agents-md/variants/next.md"
|
|
33
|
+
};
|
|
34
|
+
var FABRIC_GUIDE_PATH = ".fabric/bootstrap/README.md";
|
|
35
|
+
async function buildFabricBootstrapGuide(target) {
|
|
36
|
+
const workspaceRoot = normalizeTarget(target);
|
|
37
|
+
const scanReport = await createScanReport(workspaceRoot);
|
|
38
|
+
const template = readFileSync(findBootstrapTemplatePath(scanReport.framework.kind), "utf8");
|
|
39
|
+
const packageName = readPackageName(workspaceRoot) ?? parse(workspaceRoot).base;
|
|
40
|
+
return ensureTrailingNewline(
|
|
41
|
+
template.replaceAll("{ projectName }", packageName).replaceAll("{ frameworkKind }", scanReport.framework.kind)
|
|
42
|
+
);
|
|
43
|
+
}
|
|
44
|
+
async function ensureFabricBootstrapGuide(workspaceRoot, force) {
|
|
45
|
+
const guidePath = resolve(workspaceRoot, FABRIC_GUIDE_PATH);
|
|
46
|
+
if (existsSync(guidePath) && !force) {
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
mkdirSync(dirname(guidePath), { recursive: true });
|
|
50
|
+
writeFileSync(guidePath, await buildFabricBootstrapGuide(workspaceRoot), "utf8");
|
|
51
|
+
}
|
|
52
|
+
function findBootstrapTemplatePath(frameworkKind) {
|
|
53
|
+
const relativePath = AGENTS_TEMPLATE_BY_FRAMEWORK[frameworkKind] ?? "templates/agents-md/AGENTS.md.template";
|
|
54
|
+
return findTemplatePath(relativePath);
|
|
55
|
+
}
|
|
56
|
+
function readPackageName(target) {
|
|
57
|
+
const packageJsonPath = join(target, "package.json");
|
|
58
|
+
if (!existsSync(packageJsonPath)) {
|
|
59
|
+
return void 0;
|
|
60
|
+
}
|
|
61
|
+
try {
|
|
62
|
+
const packageJson = JSON.parse(readFileSync(packageJsonPath, "utf8"));
|
|
63
|
+
return packageJson.name;
|
|
64
|
+
} catch {
|
|
65
|
+
return void 0;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
function findTemplatePath(relativePath) {
|
|
69
|
+
const currentModuleDir = dirname(fileURLToPath(import.meta.url));
|
|
70
|
+
const candidates = [
|
|
71
|
+
...templateCandidatesFrom(process.cwd(), relativePath),
|
|
72
|
+
...templateCandidatesFrom(currentModuleDir, relativePath)
|
|
73
|
+
];
|
|
74
|
+
for (const candidate of candidates) {
|
|
75
|
+
if (existsSync(candidate)) {
|
|
76
|
+
return candidate;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
throw new Error(t("cli.shared.template-not-found", { path: relativePath }));
|
|
80
|
+
}
|
|
81
|
+
function templateCandidatesFrom(start, relativePath) {
|
|
82
|
+
const candidates = [];
|
|
83
|
+
let current = resolve(start);
|
|
84
|
+
while (true) {
|
|
85
|
+
candidates.push(join(current, ...relativePath.split("/")));
|
|
86
|
+
const parent = dirname(current);
|
|
87
|
+
if (parent === current || parse(current).root === current) {
|
|
88
|
+
break;
|
|
89
|
+
}
|
|
90
|
+
current = parent;
|
|
91
|
+
}
|
|
92
|
+
return candidates.reverse();
|
|
93
|
+
}
|
|
94
|
+
function ensureTrailingNewline(content) {
|
|
95
|
+
return content.endsWith("\n") ? content : `${content}
|
|
96
|
+
`;
|
|
97
|
+
}
|
|
98
|
+
function normalizeTarget(targetInput) {
|
|
99
|
+
return isAbsolute(targetInput) ? targetInput : resolve(process.cwd(), targetInput);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// src/commands/bootstrap.ts
|
|
103
|
+
import { resolve as resolve5 } from "path";
|
|
38
104
|
import { defineCommand } from "citty";
|
|
39
105
|
|
|
106
|
+
// src/config/resolver.ts
|
|
107
|
+
import { existsSync as existsSync5 } from "fs";
|
|
108
|
+
import { join as join5 } from "path";
|
|
109
|
+
import { homedir as homedir4 } from "os";
|
|
110
|
+
|
|
111
|
+
// src/config/claude-code.ts
|
|
112
|
+
import { existsSync as existsSync3 } from "fs";
|
|
113
|
+
import { join as join3, resolve as resolve3 } from "path";
|
|
114
|
+
import { homedir as homedir2, platform } from "os";
|
|
115
|
+
|
|
116
|
+
// src/config/json.ts
|
|
117
|
+
import { existsSync as existsSync2 } from "fs";
|
|
118
|
+
import { mkdir, readFile, writeFile } from "fs/promises";
|
|
119
|
+
import { dirname as dirname2, join as join2, resolve as resolve2 } from "path";
|
|
120
|
+
import { homedir } from "os";
|
|
121
|
+
|
|
122
|
+
// src/config/writer.ts
|
|
123
|
+
function createServerEntry(serverPath) {
|
|
124
|
+
return {
|
|
125
|
+
command: process.execPath,
|
|
126
|
+
args: [serverPath]
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// src/config/json.ts
|
|
131
|
+
function expandHome(filePath) {
|
|
132
|
+
if (filePath === "~") {
|
|
133
|
+
return homedir();
|
|
134
|
+
}
|
|
135
|
+
if (filePath.startsWith("~/")) {
|
|
136
|
+
return join2(homedir(), filePath.slice(2));
|
|
137
|
+
}
|
|
138
|
+
return filePath;
|
|
139
|
+
}
|
|
140
|
+
function normalizeConfigPath(filePath) {
|
|
141
|
+
return resolve2(expandHome(filePath));
|
|
142
|
+
}
|
|
143
|
+
async function readJsonConfig(configPath) {
|
|
144
|
+
try {
|
|
145
|
+
const raw = await readFile(configPath, "utf8");
|
|
146
|
+
if (raw.trim().length === 0) {
|
|
147
|
+
return {};
|
|
148
|
+
}
|
|
149
|
+
const parsed = JSON.parse(raw);
|
|
150
|
+
if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
151
|
+
throw new Error(`Expected JSON object in ${configPath}`);
|
|
152
|
+
}
|
|
153
|
+
return parsed;
|
|
154
|
+
} catch (error) {
|
|
155
|
+
if (error instanceof Error && "code" in error && error.code === "ENOENT") {
|
|
156
|
+
return {};
|
|
157
|
+
}
|
|
158
|
+
throw error;
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
async function writeJsonClientConfig(configPath, serverEntry) {
|
|
162
|
+
const config = await readJsonConfig(configPath);
|
|
163
|
+
const existingServers = config.mcpServers;
|
|
164
|
+
config.mcpServers = existingServers !== null && typeof existingServers === "object" && !Array.isArray(existingServers) ? { ...existingServers, fabric: serverEntry } : { fabric: serverEntry };
|
|
165
|
+
await mkdir(dirname2(configPath), { recursive: true });
|
|
166
|
+
await writeFile(configPath, `${JSON.stringify(config, null, 2)}
|
|
167
|
+
`, "utf8");
|
|
168
|
+
}
|
|
169
|
+
var JsonClientConfigWriter = class {
|
|
170
|
+
configuredPath;
|
|
171
|
+
constructor(configuredPath) {
|
|
172
|
+
this.configuredPath = configuredPath;
|
|
173
|
+
}
|
|
174
|
+
async detect(workspaceRoot, overridePath) {
|
|
175
|
+
const explicitPath = overridePath ?? this.configuredPath;
|
|
176
|
+
if (explicitPath !== void 0) {
|
|
177
|
+
return normalizeConfigPath(explicitPath);
|
|
178
|
+
}
|
|
179
|
+
const configPath = this.defaultPath(workspaceRoot);
|
|
180
|
+
return configPath === null ? null : normalizeConfigPath(configPath);
|
|
181
|
+
}
|
|
182
|
+
async write(serverPath, workspaceRoot, overridePath) {
|
|
183
|
+
const configPath = await this.detect(workspaceRoot, overridePath);
|
|
184
|
+
if (configPath === null) {
|
|
185
|
+
return;
|
|
186
|
+
}
|
|
187
|
+
await writeJsonClientConfig(configPath, createServerEntry(serverPath));
|
|
188
|
+
}
|
|
189
|
+
};
|
|
190
|
+
var ClaudeCodeCLIWriter = class extends JsonClientConfigWriter {
|
|
191
|
+
clientKind = "ClaudeCodeCLI";
|
|
192
|
+
constructor(configuredPath) {
|
|
193
|
+
super(configuredPath);
|
|
194
|
+
}
|
|
195
|
+
// Writes to project-level .claude/settings.json so MCP is scoped to the project.
|
|
196
|
+
// Detection in resolver still checks ~/ to confirm Claude Code is installed.
|
|
197
|
+
defaultPath(workspaceRoot) {
|
|
198
|
+
const globalClaudeDir = join2(homedir(), ".claude");
|
|
199
|
+
const projectClaudeDir = join2(workspaceRoot, ".claude");
|
|
200
|
+
if (!existsSync2(globalClaudeDir) && !existsSync2(projectClaudeDir)) {
|
|
201
|
+
return null;
|
|
202
|
+
}
|
|
203
|
+
return join2(projectClaudeDir, "settings.json");
|
|
204
|
+
}
|
|
205
|
+
};
|
|
206
|
+
var CursorWriter = class extends JsonClientConfigWriter {
|
|
207
|
+
clientKind = "Cursor";
|
|
208
|
+
constructor(configuredPath) {
|
|
209
|
+
super(configuredPath);
|
|
210
|
+
}
|
|
211
|
+
defaultPath(workspaceRoot) {
|
|
212
|
+
const cursorDir = join2(workspaceRoot, ".cursor");
|
|
213
|
+
return existsSync2(cursorDir) ? join2(cursorDir, "mcp.json") : null;
|
|
214
|
+
}
|
|
215
|
+
};
|
|
216
|
+
var WindsurfWriter = class extends JsonClientConfigWriter {
|
|
217
|
+
clientKind = "Windsurf";
|
|
218
|
+
constructor(configuredPath) {
|
|
219
|
+
super(configuredPath);
|
|
220
|
+
}
|
|
221
|
+
defaultPath(workspaceRoot) {
|
|
222
|
+
const windsurfDir = join2(workspaceRoot, ".windsurf");
|
|
223
|
+
return existsSync2(windsurfDir) ? join2(windsurfDir, "mcp.json") : null;
|
|
224
|
+
}
|
|
225
|
+
};
|
|
226
|
+
var RooCodeWriter = class extends JsonClientConfigWriter {
|
|
227
|
+
clientKind = "RooCode";
|
|
228
|
+
constructor(configuredPath) {
|
|
229
|
+
super(configuredPath);
|
|
230
|
+
}
|
|
231
|
+
defaultPath(workspaceRoot) {
|
|
232
|
+
const rooDir = join2(workspaceRoot, ".roo");
|
|
233
|
+
return existsSync2(rooDir) ? join2(rooDir, "mcp.json") : null;
|
|
234
|
+
}
|
|
235
|
+
};
|
|
236
|
+
var GeminiCLIWriter = class extends JsonClientConfigWriter {
|
|
237
|
+
clientKind = "GeminiCLI";
|
|
238
|
+
constructor(configuredPath) {
|
|
239
|
+
super(configuredPath);
|
|
240
|
+
}
|
|
241
|
+
defaultPath(workspaceRoot) {
|
|
242
|
+
const geminiDir = join2(homedir(), ".gemini");
|
|
243
|
+
return existsSync2(geminiDir) || existsSync2(join2(workspaceRoot, "GEMINI.md")) ? join2(geminiDir, "settings.json") : null;
|
|
244
|
+
}
|
|
245
|
+
};
|
|
246
|
+
|
|
247
|
+
// src/config/claude-code.ts
|
|
248
|
+
function getClaudeDesktopConfigPath() {
|
|
249
|
+
const os = platform();
|
|
250
|
+
if (os === "darwin") {
|
|
251
|
+
return join3(homedir2(), "Library", "Application Support", "Claude", "claude_desktop_config.json");
|
|
252
|
+
}
|
|
253
|
+
if (os === "win32") {
|
|
254
|
+
return join3(process.env.APPDATA ?? join3(homedir2(), "AppData", "Roaming"), "Claude", "claude_desktop_config.json");
|
|
255
|
+
}
|
|
256
|
+
return join3(homedir2(), ".config", "Claude", "claude_desktop_config.json");
|
|
257
|
+
}
|
|
258
|
+
var ClaudeCodeDesktopWriter = class {
|
|
259
|
+
clientKind = "ClaudeCodeDesktop";
|
|
260
|
+
configuredPath;
|
|
261
|
+
constructor(configuredPath) {
|
|
262
|
+
this.configuredPath = configuredPath;
|
|
263
|
+
}
|
|
264
|
+
async detect(_workspaceRoot, overridePath) {
|
|
265
|
+
const configPath = normalizeConfigPath(overridePath ?? this.configuredPath ?? getClaudeDesktopConfigPath());
|
|
266
|
+
return existsSync3(configPath) || overridePath !== void 0 || this.configuredPath !== void 0 ? configPath : null;
|
|
267
|
+
}
|
|
268
|
+
async write(serverPath, workspaceRoot, overridePath) {
|
|
269
|
+
const configPath = await this.detect(workspaceRoot, overridePath);
|
|
270
|
+
if (configPath === null) {
|
|
271
|
+
return;
|
|
272
|
+
}
|
|
273
|
+
await writeJsonClientConfig(configPath, {
|
|
274
|
+
command: process.execPath,
|
|
275
|
+
args: [serverPath]
|
|
276
|
+
});
|
|
277
|
+
}
|
|
278
|
+
};
|
|
279
|
+
|
|
280
|
+
// src/config/toml.ts
|
|
281
|
+
import { existsSync as existsSync4 } from "fs";
|
|
282
|
+
import { mkdir as mkdir2, readFile as readFile2, writeFile as writeFile2 } from "fs/promises";
|
|
283
|
+
import { dirname as dirname3, join as join4, resolve as resolve4 } from "path";
|
|
284
|
+
import { homedir as homedir3 } from "os";
|
|
285
|
+
function expandHome2(filePath) {
|
|
286
|
+
if (filePath === "~") {
|
|
287
|
+
return homedir3();
|
|
288
|
+
}
|
|
289
|
+
if (filePath.startsWith("~/")) {
|
|
290
|
+
return join4(homedir3(), filePath.slice(2));
|
|
291
|
+
}
|
|
292
|
+
return filePath;
|
|
293
|
+
}
|
|
294
|
+
function escapeTomlString(value) {
|
|
295
|
+
return JSON.stringify(value);
|
|
296
|
+
}
|
|
297
|
+
function serializeTomlStringArray(values) {
|
|
298
|
+
return `[${values.map((value) => escapeTomlString(value)).join(", ")}]`;
|
|
299
|
+
}
|
|
300
|
+
function serializeTomlInlineTable(values) {
|
|
301
|
+
const entries = Object.entries(values).sort(([left], [right]) => left.localeCompare(right)).map(([key, value]) => `${key} = ${escapeTomlString(value)}`);
|
|
302
|
+
return `{ ${entries.join(", ")} }`;
|
|
303
|
+
}
|
|
304
|
+
function serializeCodexServerBlock(serverName, serverEntry) {
|
|
305
|
+
const lines = [
|
|
306
|
+
`[mcp_servers.${serverName}]`,
|
|
307
|
+
`command = ${escapeTomlString(serverEntry.command)}`,
|
|
308
|
+
`args = ${serializeTomlStringArray(serverEntry.args)}`
|
|
309
|
+
];
|
|
310
|
+
if (serverEntry.env !== void 0 && Object.keys(serverEntry.env).length > 0) {
|
|
311
|
+
lines.push(`env = ${serializeTomlInlineTable(serverEntry.env)}`);
|
|
312
|
+
}
|
|
313
|
+
return `${lines.join("\n")}
|
|
314
|
+
`;
|
|
315
|
+
}
|
|
316
|
+
function trimTrailingBlankLines(value) {
|
|
317
|
+
return value.replace(/\s+$/u, "");
|
|
318
|
+
}
|
|
319
|
+
function upsertCodexServerBlock(rawConfig, serverName, serverEntry) {
|
|
320
|
+
const block = serializeCodexServerBlock(serverName, serverEntry);
|
|
321
|
+
const normalized = rawConfig.replace(/\r\n/g, "\n");
|
|
322
|
+
const legacyPattern = new RegExp(String.raw`\n?\[mcp\.servers\.${serverName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}\]\n[\s\S]*?(?=\n\[[^\n]+\]\n|$)`, "g");
|
|
323
|
+
const currentPattern = new RegExp(
|
|
324
|
+
String.raw`\n?\[mcp_servers\.${serverName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}\]\n[\s\S]*?(?=\n\[[^\n]+\]\n|$)`,
|
|
325
|
+
"g"
|
|
326
|
+
);
|
|
327
|
+
const withoutLegacy = normalized.replace(legacyPattern, "");
|
|
328
|
+
const withoutExisting = withoutLegacy.replace(currentPattern, "");
|
|
329
|
+
const trimmed = trimTrailingBlankLines(withoutExisting);
|
|
330
|
+
if (trimmed.length === 0) {
|
|
331
|
+
return block;
|
|
332
|
+
}
|
|
333
|
+
return `${trimmed}
|
|
334
|
+
|
|
335
|
+
${block}`;
|
|
336
|
+
}
|
|
337
|
+
async function readTomlConfigText(configPath) {
|
|
338
|
+
try {
|
|
339
|
+
return await readFile2(configPath, "utf8");
|
|
340
|
+
} catch (error) {
|
|
341
|
+
if (error instanceof Error && "code" in error && error.code === "ENOENT") {
|
|
342
|
+
return "";
|
|
343
|
+
}
|
|
344
|
+
throw error;
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
var CodexTOMLConfigWriter = class {
|
|
348
|
+
clientKind = "CodexCLI";
|
|
349
|
+
configuredPath;
|
|
350
|
+
constructor(configuredPath) {
|
|
351
|
+
this.configuredPath = configuredPath;
|
|
352
|
+
}
|
|
353
|
+
async detect(_workspaceRoot, overridePath) {
|
|
354
|
+
const explicitPath = overridePath ?? this.configuredPath;
|
|
355
|
+
if (explicitPath !== void 0) {
|
|
356
|
+
return resolve4(expandHome2(explicitPath));
|
|
357
|
+
}
|
|
358
|
+
const codexDir = join4(homedir3(), ".codex");
|
|
359
|
+
return existsSync4(codexDir) ? resolve4(join4(codexDir, "config.toml")) : null;
|
|
360
|
+
}
|
|
361
|
+
async write(serverPath, workspaceRoot, overridePath) {
|
|
362
|
+
const configPath = await this.detect(workspaceRoot, overridePath);
|
|
363
|
+
if (configPath === null) {
|
|
364
|
+
return;
|
|
365
|
+
}
|
|
366
|
+
const rawConfig = await readTomlConfigText(configPath);
|
|
367
|
+
const nextConfig = upsertCodexServerBlock(rawConfig, "fabric", createServerEntry(serverPath));
|
|
368
|
+
await mkdir2(dirname3(configPath), { recursive: true });
|
|
369
|
+
await writeFile2(configPath, nextConfig, "utf8");
|
|
370
|
+
}
|
|
371
|
+
};
|
|
372
|
+
|
|
373
|
+
// src/config/resolver.ts
|
|
374
|
+
import { clientPathsSchema, fabricConfigSchema } from "@fenglimg/fabric-shared";
|
|
375
|
+
function hasExplicitPath(clientPaths, key) {
|
|
376
|
+
return typeof clientPaths?.[key] === "string" && clientPaths[key].trim().length > 0;
|
|
377
|
+
}
|
|
378
|
+
function addIfDetected(writers, detected, createWriter, configuredPath) {
|
|
379
|
+
if (configuredPath !== void 0 || detected) {
|
|
380
|
+
writers.push(createWriter(configuredPath));
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
function resolveClients(workspaceRoot, fabricConfig = {}) {
|
|
384
|
+
const clientPaths = fabricConfig.clientPaths;
|
|
385
|
+
const writers = [];
|
|
386
|
+
addIfDetected(
|
|
387
|
+
writers,
|
|
388
|
+
existsSync5(join5(homedir4(), ".claude")) || existsSync5(join5(workspaceRoot, ".claude")),
|
|
389
|
+
(configuredPath) => new ClaudeCodeCLIWriter(configuredPath),
|
|
390
|
+
hasExplicitPath(clientPaths, "claudeCodeCLI") ? clientPaths.claudeCodeCLI : void 0
|
|
391
|
+
);
|
|
392
|
+
addIfDetected(
|
|
393
|
+
writers,
|
|
394
|
+
existsSync5(getClaudeDesktopConfigPath()),
|
|
395
|
+
(configuredPath) => new ClaudeCodeDesktopWriter(configuredPath),
|
|
396
|
+
hasExplicitPath(clientPaths, "claudeCodeDesktop") ? clientPaths.claudeCodeDesktop : void 0
|
|
397
|
+
);
|
|
398
|
+
addIfDetected(
|
|
399
|
+
writers,
|
|
400
|
+
existsSync5(join5(workspaceRoot, ".cursor")),
|
|
401
|
+
(configuredPath) => new CursorWriter(configuredPath),
|
|
402
|
+
hasExplicitPath(clientPaths, "cursor") ? clientPaths.cursor : void 0
|
|
403
|
+
);
|
|
404
|
+
addIfDetected(
|
|
405
|
+
writers,
|
|
406
|
+
existsSync5(join5(workspaceRoot, ".windsurf")),
|
|
407
|
+
(configuredPath) => new WindsurfWriter(configuredPath),
|
|
408
|
+
hasExplicitPath(clientPaths, "windsurf") ? clientPaths.windsurf : void 0
|
|
409
|
+
);
|
|
410
|
+
addIfDetected(
|
|
411
|
+
writers,
|
|
412
|
+
existsSync5(join5(workspaceRoot, ".roo")),
|
|
413
|
+
(configuredPath) => new RooCodeWriter(configuredPath),
|
|
414
|
+
hasExplicitPath(clientPaths, "rooCode") ? clientPaths.rooCode : void 0
|
|
415
|
+
);
|
|
416
|
+
addIfDetected(
|
|
417
|
+
writers,
|
|
418
|
+
existsSync5(join5(homedir4(), ".gemini")) || existsSync5(join5(workspaceRoot, "GEMINI.md")),
|
|
419
|
+
(configuredPath) => new GeminiCLIWriter(configuredPath),
|
|
420
|
+
hasExplicitPath(clientPaths, "geminiCLI") ? clientPaths.geminiCLI : void 0
|
|
421
|
+
);
|
|
422
|
+
addIfDetected(
|
|
423
|
+
writers,
|
|
424
|
+
existsSync5(join5(homedir4(), ".codex")),
|
|
425
|
+
(configuredPath) => new CodexTOMLConfigWriter(configuredPath),
|
|
426
|
+
hasExplicitPath(clientPaths, "codexCLI") ? clientPaths.codexCLI : void 0
|
|
427
|
+
);
|
|
428
|
+
return writers;
|
|
429
|
+
}
|
|
430
|
+
function detectClientSupports(workspaceRoot, fabricConfig = {}) {
|
|
431
|
+
const clientPaths = fabricConfig.clientPaths;
|
|
432
|
+
const claudeDetected = existsSync5(join5(homedir4(), ".claude")) || existsSync5(join5(workspaceRoot, ".claude"));
|
|
433
|
+
const claudeDesktopDetected = existsSync5(getClaudeDesktopConfigPath());
|
|
434
|
+
const cursorDetected = existsSync5(join5(workspaceRoot, ".cursor"));
|
|
435
|
+
const windsurfDetected = existsSync5(join5(workspaceRoot, ".windsurf"));
|
|
436
|
+
const rooDetected = existsSync5(join5(workspaceRoot, ".roo"));
|
|
437
|
+
const geminiDetected = existsSync5(join5(homedir4(), ".gemini")) || existsSync5(join5(workspaceRoot, "GEMINI.md"));
|
|
438
|
+
const codexDetected = existsSync5(join5(homedir4(), ".codex"));
|
|
439
|
+
return [
|
|
440
|
+
{
|
|
441
|
+
clientKind: "ClaudeCodeCLI",
|
|
442
|
+
label: "Claude Code CLI",
|
|
443
|
+
detected: claudeDetected || hasExplicitPath(clientPaths, "claudeCodeCLI"),
|
|
444
|
+
bootstrapTargetPath: ".fabric/bootstrap/README.md",
|
|
445
|
+
configPath: "project .claude/settings.json",
|
|
446
|
+
capabilities: {
|
|
447
|
+
bootstrap: true,
|
|
448
|
+
mcp: true,
|
|
449
|
+
hook: true,
|
|
450
|
+
skill: true
|
|
451
|
+
},
|
|
452
|
+
installedCapabilities: {
|
|
453
|
+
hook: true,
|
|
454
|
+
skill: true
|
|
455
|
+
}
|
|
456
|
+
},
|
|
457
|
+
{
|
|
458
|
+
clientKind: "ClaudeCodeDesktop",
|
|
459
|
+
label: "Claude Code Desktop",
|
|
460
|
+
detected: claudeDesktopDetected || hasExplicitPath(clientPaths, "claudeCodeDesktop"),
|
|
461
|
+
bootstrapTargetPath: ".fabric/bootstrap/README.md",
|
|
462
|
+
configPath: "desktop Claude config",
|
|
463
|
+
capabilities: {
|
|
464
|
+
bootstrap: true,
|
|
465
|
+
mcp: true,
|
|
466
|
+
hook: false,
|
|
467
|
+
skill: false
|
|
468
|
+
}
|
|
469
|
+
},
|
|
470
|
+
{
|
|
471
|
+
clientKind: "Cursor",
|
|
472
|
+
label: "Cursor",
|
|
473
|
+
detected: cursorDetected || hasExplicitPath(clientPaths, "cursor"),
|
|
474
|
+
bootstrapTargetPath: ".fabric/bootstrap/README.md",
|
|
475
|
+
configPath: ".cursor/mcp.json",
|
|
476
|
+
capabilities: {
|
|
477
|
+
bootstrap: true,
|
|
478
|
+
mcp: true,
|
|
479
|
+
hook: false,
|
|
480
|
+
skill: false
|
|
481
|
+
}
|
|
482
|
+
},
|
|
483
|
+
{
|
|
484
|
+
clientKind: "Windsurf",
|
|
485
|
+
label: "Windsurf",
|
|
486
|
+
detected: windsurfDetected || hasExplicitPath(clientPaths, "windsurf"),
|
|
487
|
+
bootstrapTargetPath: ".fabric/bootstrap/README.md",
|
|
488
|
+
configPath: ".windsurf/mcp.json",
|
|
489
|
+
capabilities: {
|
|
490
|
+
bootstrap: true,
|
|
491
|
+
mcp: true,
|
|
492
|
+
hook: false,
|
|
493
|
+
skill: false
|
|
494
|
+
}
|
|
495
|
+
},
|
|
496
|
+
{
|
|
497
|
+
clientKind: "RooCode",
|
|
498
|
+
label: "Roo Code",
|
|
499
|
+
detected: rooDetected || hasExplicitPath(clientPaths, "rooCode"),
|
|
500
|
+
bootstrapTargetPath: ".fabric/bootstrap/README.md",
|
|
501
|
+
configPath: ".roo/mcp.json",
|
|
502
|
+
capabilities: {
|
|
503
|
+
bootstrap: true,
|
|
504
|
+
mcp: true,
|
|
505
|
+
hook: false,
|
|
506
|
+
skill: false
|
|
507
|
+
}
|
|
508
|
+
},
|
|
509
|
+
{
|
|
510
|
+
clientKind: "GeminiCLI",
|
|
511
|
+
label: "Gemini CLI",
|
|
512
|
+
detected: geminiDetected || hasExplicitPath(clientPaths, "geminiCLI"),
|
|
513
|
+
bootstrapTargetPath: ".fabric/bootstrap/README.md",
|
|
514
|
+
configPath: "~/.gemini/settings.json",
|
|
515
|
+
capabilities: {
|
|
516
|
+
bootstrap: true,
|
|
517
|
+
mcp: true,
|
|
518
|
+
hook: false,
|
|
519
|
+
skill: false
|
|
520
|
+
}
|
|
521
|
+
},
|
|
522
|
+
{
|
|
523
|
+
clientKind: "CodexCLI",
|
|
524
|
+
label: "Codex CLI",
|
|
525
|
+
detected: codexDetected || hasExplicitPath(clientPaths, "codexCLI"),
|
|
526
|
+
bootstrapTargetPath: ".fabric/bootstrap/README.md",
|
|
527
|
+
configPath: "~/.codex/config.toml",
|
|
528
|
+
capabilities: {
|
|
529
|
+
bootstrap: true,
|
|
530
|
+
mcp: true,
|
|
531
|
+
hook: true,
|
|
532
|
+
skill: true
|
|
533
|
+
},
|
|
534
|
+
installedCapabilities: {
|
|
535
|
+
hook: existsSync5(join5(workspaceRoot, ".codex", "hooks.json")),
|
|
536
|
+
skill: existsSync5(join5(workspaceRoot, ".agents", "skills", "fabric-init", "SKILL.md"))
|
|
537
|
+
}
|
|
538
|
+
}
|
|
539
|
+
];
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
// src/commands/bootstrap.ts
|
|
543
|
+
var CLIENT_ALIASES = {
|
|
544
|
+
claude: "claude",
|
|
545
|
+
"claude-code": "claude",
|
|
546
|
+
claudecode: "claude",
|
|
547
|
+
claudecli: "claude",
|
|
548
|
+
claudecodecli: "claude",
|
|
549
|
+
claudedesktop: "claude",
|
|
550
|
+
claudecodedesktop: "claude",
|
|
551
|
+
cursor: "cursor",
|
|
552
|
+
windsurf: "windsurf",
|
|
553
|
+
roo: "roo",
|
|
554
|
+
"roo-code": "roo",
|
|
555
|
+
roocode: "roo",
|
|
556
|
+
gemini: "gemini",
|
|
557
|
+
"gemini-cli": "gemini",
|
|
558
|
+
geminicli: "gemini",
|
|
559
|
+
codex: "codex",
|
|
560
|
+
"codex-cli": "codex",
|
|
561
|
+
codexcli: "codex"
|
|
562
|
+
};
|
|
563
|
+
var bootstrapCommand = defineCommand({
|
|
564
|
+
meta: {
|
|
565
|
+
name: "bootstrap",
|
|
566
|
+
description: t("cli.bootstrap.description")
|
|
567
|
+
},
|
|
568
|
+
subCommands: {
|
|
569
|
+
install: defineCommand({
|
|
570
|
+
meta: {
|
|
571
|
+
name: "install",
|
|
572
|
+
description: t("cli.bootstrap.install.description")
|
|
573
|
+
},
|
|
574
|
+
args: {
|
|
575
|
+
clients: {
|
|
576
|
+
type: "string",
|
|
577
|
+
description: t("cli.bootstrap.install.args.clients.description")
|
|
578
|
+
}
|
|
579
|
+
},
|
|
580
|
+
async run({ args }) {
|
|
581
|
+
const workspaceRoot = process.cwd();
|
|
582
|
+
const selectedClients = parseClientFilter(args.clients);
|
|
583
|
+
const result = await installBootstrap(workspaceRoot, {
|
|
584
|
+
clients: selectedClients === null ? void 0 : Array.from(selectedClients, mapBootstrapClientToClientKind)
|
|
585
|
+
});
|
|
586
|
+
if (result.details.length === 0) {
|
|
587
|
+
process.stderr.write(
|
|
588
|
+
`${t("cli.bootstrap.install.no-targets")}
|
|
589
|
+
`
|
|
590
|
+
);
|
|
591
|
+
return;
|
|
592
|
+
}
|
|
593
|
+
for (const detail of result.details) {
|
|
594
|
+
if (detail.action === "skipped") {
|
|
595
|
+
process.stderr.write(`${t("cli.bootstrap.install.skipped-header", { path: detail.path })}
|
|
596
|
+
`);
|
|
597
|
+
continue;
|
|
598
|
+
}
|
|
599
|
+
if (detail.action === "prepended") {
|
|
600
|
+
process.stderr.write(`${t("cli.bootstrap.install.prepended", { path: detail.path })}
|
|
601
|
+
`);
|
|
602
|
+
continue;
|
|
603
|
+
}
|
|
604
|
+
process.stderr.write(`${t("cli.bootstrap.install.installed", { path: detail.path })}
|
|
605
|
+
`);
|
|
606
|
+
}
|
|
607
|
+
}
|
|
608
|
+
})
|
|
609
|
+
}
|
|
610
|
+
});
|
|
611
|
+
async function installBootstrap(target, options = {}) {
|
|
612
|
+
const workspaceRoot = resolve5(target);
|
|
613
|
+
const fabricConfig = readFabricConfig(workspaceRoot);
|
|
614
|
+
const targets = resolveBootstrapTargets(workspaceRoot, fabricConfig, options.clients);
|
|
615
|
+
const installed = [];
|
|
616
|
+
const skipped = [];
|
|
617
|
+
const details = [];
|
|
618
|
+
await ensureFabricBootstrapGuide(workspaceRoot, options.force);
|
|
619
|
+
for (const bootstrapTarget of targets) {
|
|
620
|
+
details.push({
|
|
621
|
+
client: bootstrapTarget.client,
|
|
622
|
+
path: resolve5(workspaceRoot, FABRIC_GUIDE_PATH),
|
|
623
|
+
action: "skipped"
|
|
624
|
+
});
|
|
625
|
+
skipped.push(bootstrapTarget.client);
|
|
626
|
+
}
|
|
627
|
+
return { installed, skipped, details };
|
|
628
|
+
}
|
|
629
|
+
function parseClientFilter(value) {
|
|
630
|
+
if (value === void 0 || value.trim().length === 0) {
|
|
631
|
+
return null;
|
|
632
|
+
}
|
|
633
|
+
const clients = /* @__PURE__ */ new Set();
|
|
634
|
+
for (const rawClient of value.split(",")) {
|
|
635
|
+
const alias = rawClient.trim().toLowerCase();
|
|
636
|
+
const client = CLIENT_ALIASES[alias];
|
|
637
|
+
if (client === void 0) {
|
|
638
|
+
throw new Error(t("cli.bootstrap.errors.unknown-client", { client: rawClient }));
|
|
639
|
+
}
|
|
640
|
+
clients.add(client);
|
|
641
|
+
}
|
|
642
|
+
return clients;
|
|
643
|
+
}
|
|
644
|
+
function resolveBootstrapTargets(workspaceRoot, fabricConfig, selectedClients) {
|
|
645
|
+
const targets = [];
|
|
646
|
+
const seenClients = /* @__PURE__ */ new Set();
|
|
647
|
+
const clientKinds = selectedClients ?? resolveClients(workspaceRoot, fabricConfig).map((writer) => writer.clientKind);
|
|
648
|
+
for (const clientKind of clientKinds) {
|
|
649
|
+
const bootstrapClient = mapClientKind(clientKind);
|
|
650
|
+
if (bootstrapClient === null || seenClients.has(bootstrapClient)) {
|
|
651
|
+
continue;
|
|
652
|
+
}
|
|
653
|
+
seenClients.add(bootstrapClient);
|
|
654
|
+
targets.push({ client: clientKind, bootstrapClient });
|
|
655
|
+
}
|
|
656
|
+
return targets;
|
|
657
|
+
}
|
|
658
|
+
function mapClientKind(clientKind) {
|
|
659
|
+
switch (clientKind) {
|
|
660
|
+
case "ClaudeCodeCLI":
|
|
661
|
+
case "ClaudeCodeDesktop":
|
|
662
|
+
return "claude";
|
|
663
|
+
case "Cursor":
|
|
664
|
+
return "cursor";
|
|
665
|
+
case "Windsurf":
|
|
666
|
+
return "windsurf";
|
|
667
|
+
case "RooCode":
|
|
668
|
+
return "roo";
|
|
669
|
+
case "GeminiCLI":
|
|
670
|
+
return "gemini";
|
|
671
|
+
case "CodexCLI":
|
|
672
|
+
return "codex";
|
|
673
|
+
default:
|
|
674
|
+
return null;
|
|
675
|
+
}
|
|
676
|
+
}
|
|
677
|
+
function mapBootstrapClientToClientKind(client) {
|
|
678
|
+
switch (client) {
|
|
679
|
+
case "claude":
|
|
680
|
+
return "ClaudeCodeCLI";
|
|
681
|
+
case "cursor":
|
|
682
|
+
return "Cursor";
|
|
683
|
+
case "windsurf":
|
|
684
|
+
return "Windsurf";
|
|
685
|
+
case "roo":
|
|
686
|
+
return "RooCode";
|
|
687
|
+
case "gemini":
|
|
688
|
+
return "GeminiCLI";
|
|
689
|
+
case "codex":
|
|
690
|
+
return "CodexCLI";
|
|
691
|
+
}
|
|
692
|
+
}
|
|
693
|
+
|
|
694
|
+
// src/commands/config.ts
|
|
695
|
+
import { existsSync as existsSync7 } from "fs";
|
|
696
|
+
import { readFile as readFile3 } from "fs/promises";
|
|
697
|
+
import { resolve as resolve7 } from "path";
|
|
698
|
+
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
699
|
+
import { defineCommand as defineCommand3 } from "citty";
|
|
700
|
+
|
|
701
|
+
// src/commands/hooks.ts
|
|
702
|
+
import { chmodSync, existsSync as existsSync6, mkdirSync as mkdirSync2, readFileSync as readFileSync2, statSync, writeFileSync as writeFileSync2 } from "fs";
|
|
703
|
+
import { dirname as dirname4, isAbsolute as isAbsolute2, join as join6, parse as parse2, resolve as resolve6 } from "path";
|
|
704
|
+
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
705
|
+
import { defineCommand as defineCommand2 } from "citty";
|
|
706
|
+
var hooksCommand = defineCommand2({
|
|
707
|
+
meta: {
|
|
708
|
+
name: "hooks",
|
|
709
|
+
description: t("cli.hooks.description")
|
|
710
|
+
},
|
|
711
|
+
subCommands: {
|
|
712
|
+
install: defineCommand2({
|
|
713
|
+
meta: {
|
|
714
|
+
name: "install",
|
|
715
|
+
description: t("cli.hooks.install.description")
|
|
716
|
+
},
|
|
717
|
+
args: {
|
|
718
|
+
target: {
|
|
719
|
+
type: "string",
|
|
720
|
+
description: t("cli.hooks.install.args.target.description"),
|
|
721
|
+
default: process.cwd()
|
|
722
|
+
}
|
|
723
|
+
},
|
|
724
|
+
async run({ args }) {
|
|
725
|
+
const result = await installHooks(args.target);
|
|
726
|
+
if (result.hookAction === "skipped") {
|
|
727
|
+
writeStderr(t("cli.hooks.install.hook-skipped", { path: result.hookPath }));
|
|
728
|
+
} else if (result.hookAction === "appended") {
|
|
729
|
+
writeStderr(t("cli.hooks.install.hook-appended", { path: result.hookPath }));
|
|
730
|
+
} else {
|
|
731
|
+
writeStderr(t("cli.hooks.install.hook-created", { path: result.hookPath }));
|
|
732
|
+
}
|
|
733
|
+
if (result.prepareAction === "left") {
|
|
734
|
+
writeStderr(t("cli.hooks.install.prepare-left", { path: result.packageJsonPath }));
|
|
735
|
+
} else {
|
|
736
|
+
writeStderr(t("cli.hooks.install.prepare-added", { path: result.packageJsonPath }));
|
|
737
|
+
}
|
|
738
|
+
}
|
|
739
|
+
})
|
|
740
|
+
}
|
|
741
|
+
});
|
|
742
|
+
async function installHooks(target, options = {}) {
|
|
743
|
+
const normalizedTarget = normalizeTarget2(target);
|
|
744
|
+
assertExistingDirectory(normalizedTarget);
|
|
745
|
+
const huskyDir = join6(normalizedTarget, ".husky");
|
|
746
|
+
const hookPath = join6(huskyDir, "pre-commit");
|
|
747
|
+
const packageJsonPath = join6(normalizedTarget, "package.json");
|
|
748
|
+
if (!existsSync6(packageJsonPath)) {
|
|
749
|
+
throw new Error(t("cli.hooks.errors.package-json-required", { path: packageJsonPath }));
|
|
750
|
+
}
|
|
751
|
+
mkdirSync2(huskyDir, { recursive: true });
|
|
752
|
+
const templateContent = readFileSync2(findTemplatePath2("templates/husky/pre-commit"), "utf8");
|
|
753
|
+
const hookAction = installHookFile(hookPath, templateContent, options.force);
|
|
754
|
+
chmodSync(hookPath, 493);
|
|
755
|
+
const packageJson = JSON.parse(readFileSync2(packageJsonPath, "utf8"));
|
|
756
|
+
const scripts = packageJson.scripts && typeof packageJson.scripts === "object" && !Array.isArray(packageJson.scripts) ? packageJson.scripts : {};
|
|
757
|
+
const hadPrepare = typeof scripts.prepare === "string" && scripts.prepare.trim().length > 0;
|
|
758
|
+
let prepareAction = "left";
|
|
759
|
+
if (!hadPrepare) {
|
|
760
|
+
scripts.prepare = "husky install";
|
|
761
|
+
packageJson.scripts = scripts;
|
|
762
|
+
writeFileSync2(packageJsonPath, `${JSON.stringify(packageJson, null, 2)}
|
|
763
|
+
`, "utf8");
|
|
764
|
+
prepareAction = "added";
|
|
765
|
+
}
|
|
766
|
+
const installed = [];
|
|
767
|
+
const skipped = [];
|
|
768
|
+
if (hookAction === "skipped") {
|
|
769
|
+
skipped.push(hookPath);
|
|
770
|
+
} else {
|
|
771
|
+
installed.push(hookPath);
|
|
772
|
+
}
|
|
773
|
+
if (prepareAction === "left") {
|
|
774
|
+
skipped.push(packageJsonPath);
|
|
775
|
+
} else {
|
|
776
|
+
installed.push(packageJsonPath);
|
|
777
|
+
}
|
|
778
|
+
return {
|
|
779
|
+
installed,
|
|
780
|
+
skipped,
|
|
781
|
+
hookPath,
|
|
782
|
+
packageJsonPath,
|
|
783
|
+
hookAction,
|
|
784
|
+
prepareAction
|
|
785
|
+
};
|
|
786
|
+
}
|
|
787
|
+
function normalizeTarget2(targetInput) {
|
|
788
|
+
return isAbsolute2(targetInput) ? targetInput : resolve6(process.cwd(), targetInput);
|
|
789
|
+
}
|
|
790
|
+
function assertExistingDirectory(target) {
|
|
791
|
+
if (!existsSync6(target) || !statSync(target).isDirectory()) {
|
|
792
|
+
throw new Error(t("cli.shared.target-invalid", { target }));
|
|
793
|
+
}
|
|
794
|
+
}
|
|
795
|
+
function installHookFile(hookPath, templateContent, force) {
|
|
796
|
+
if (existsSync6(hookPath)) {
|
|
797
|
+
if (force) {
|
|
798
|
+
writeFileSync2(hookPath, templateContent, "utf8");
|
|
799
|
+
return "overwritten";
|
|
800
|
+
}
|
|
801
|
+
const existing = readFileSync2(hookPath, "utf8");
|
|
802
|
+
if (existing.includes("FAB_BIN=")) {
|
|
803
|
+
return "skipped";
|
|
804
|
+
}
|
|
805
|
+
const fabricBlock = templateContent.replace(/^#!\/bin\/sh\n/, "");
|
|
806
|
+
const separator = existing.endsWith("\n") ? "\n" : "\n\n";
|
|
807
|
+
writeFileSync2(hookPath, `${existing}${separator}# --- Fabric ---
|
|
808
|
+
${fabricBlock}`, "utf8");
|
|
809
|
+
return "appended";
|
|
810
|
+
}
|
|
811
|
+
writeFileSync2(hookPath, templateContent, "utf8");
|
|
812
|
+
return "created";
|
|
813
|
+
}
|
|
814
|
+
function findTemplatePath2(relativePath) {
|
|
815
|
+
const currentModuleDir = dirname4(fileURLToPath2(import.meta.url));
|
|
816
|
+
const candidates = [
|
|
817
|
+
...templateCandidatesFrom2(process.cwd(), relativePath),
|
|
818
|
+
...templateCandidatesFrom2(currentModuleDir, relativePath)
|
|
819
|
+
];
|
|
820
|
+
for (const candidate of candidates) {
|
|
821
|
+
if (existsSync6(candidate)) {
|
|
822
|
+
return candidate;
|
|
823
|
+
}
|
|
824
|
+
}
|
|
825
|
+
throw new Error(t("cli.shared.template-not-found", { path: relativePath }));
|
|
826
|
+
}
|
|
827
|
+
function templateCandidatesFrom2(start, relativePath) {
|
|
828
|
+
const candidates = [];
|
|
829
|
+
let current = resolve6(start);
|
|
830
|
+
while (true) {
|
|
831
|
+
candidates.push(join6(current, ...relativePath.split("/")));
|
|
832
|
+
const parent = dirname4(current);
|
|
833
|
+
if (parent === current || parse2(current).root === current) {
|
|
834
|
+
break;
|
|
835
|
+
}
|
|
836
|
+
current = parent;
|
|
837
|
+
}
|
|
838
|
+
return candidates.reverse();
|
|
839
|
+
}
|
|
840
|
+
function writeStderr(message) {
|
|
841
|
+
process.stderr.write(`${message}
|
|
842
|
+
`);
|
|
843
|
+
}
|
|
844
|
+
|
|
845
|
+
// src/commands/config.ts
|
|
846
|
+
var CLIENT_ALIASES2 = {
|
|
847
|
+
claude: "ClaudeCodeCLI",
|
|
848
|
+
claudecodecli: "ClaudeCodeCLI",
|
|
849
|
+
"claude-code-cli": "ClaudeCodeCLI",
|
|
850
|
+
claudecli: "ClaudeCodeCLI",
|
|
851
|
+
claudecodedesktop: "ClaudeCodeDesktop",
|
|
852
|
+
"claude-code-desktop": "ClaudeCodeDesktop",
|
|
853
|
+
claudedesktop: "ClaudeCodeDesktop",
|
|
854
|
+
cursor: "Cursor",
|
|
855
|
+
windsurf: "Windsurf",
|
|
856
|
+
roocode: "RooCode",
|
|
857
|
+
"roo-code": "RooCode",
|
|
858
|
+
roo: "RooCode",
|
|
859
|
+
geminicli: "GeminiCLI",
|
|
860
|
+
"gemini-cli": "GeminiCLI",
|
|
861
|
+
gemini: "GeminiCLI",
|
|
862
|
+
codexcli: "CodexCLI",
|
|
863
|
+
"codex-cli": "CodexCLI",
|
|
864
|
+
codex: "CodexCLI"
|
|
865
|
+
};
|
|
866
|
+
function parseClientFilter2(value) {
|
|
867
|
+
if (value === void 0 || value.trim().length === 0) {
|
|
868
|
+
return null;
|
|
869
|
+
}
|
|
870
|
+
const clients = /* @__PURE__ */ new Set();
|
|
871
|
+
for (const rawClient of value.split(",")) {
|
|
872
|
+
const alias = rawClient.trim().toLowerCase();
|
|
873
|
+
const clientKind = CLIENT_ALIASES2[alias];
|
|
874
|
+
if (clientKind === void 0) {
|
|
875
|
+
throw new Error(t("cli.config.errors.unknown-client", { client: rawClient }));
|
|
876
|
+
}
|
|
877
|
+
clients.add(clientKind);
|
|
878
|
+
}
|
|
879
|
+
return clients;
|
|
880
|
+
}
|
|
881
|
+
async function loadFabricConfig(workspaceRoot) {
|
|
882
|
+
const configPath = resolve7(workspaceRoot, "fabric.config.json");
|
|
883
|
+
if (!existsSync7(configPath)) {
|
|
884
|
+
return {};
|
|
885
|
+
}
|
|
886
|
+
const parsed = JSON.parse(await readFile3(configPath, "utf8"));
|
|
887
|
+
if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
888
|
+
throw new Error(t("cli.config.errors.expected-object", { path: configPath }));
|
|
889
|
+
}
|
|
890
|
+
return parsed;
|
|
891
|
+
}
|
|
892
|
+
function resolveServerPath(override) {
|
|
893
|
+
if (override) return override;
|
|
894
|
+
if (process.env.FAB_SERVER_PATH) return resolve7(process.env.FAB_SERVER_PATH);
|
|
895
|
+
return fileURLToPath3(import.meta.resolve("@fenglimg/fabric-server"));
|
|
896
|
+
}
|
|
897
|
+
function writeStderr2(message) {
|
|
898
|
+
process.stderr.write(`${message}
|
|
899
|
+
`);
|
|
900
|
+
}
|
|
901
|
+
var configCmd = defineCommand3({
|
|
902
|
+
meta: {
|
|
903
|
+
name: "config",
|
|
904
|
+
description: t("cli.config.description")
|
|
905
|
+
},
|
|
906
|
+
subCommands: {
|
|
907
|
+
hooks: hooksCommand,
|
|
908
|
+
install: defineCommand3({
|
|
909
|
+
meta: {
|
|
910
|
+
name: "install",
|
|
911
|
+
description: t("cli.config.install.description")
|
|
912
|
+
},
|
|
913
|
+
args: {
|
|
914
|
+
clients: {
|
|
915
|
+
type: "string",
|
|
916
|
+
description: t("cli.config.install.args.clients.description")
|
|
917
|
+
},
|
|
918
|
+
"dry-run": {
|
|
919
|
+
type: "boolean",
|
|
920
|
+
description: t("cli.config.install.args.dry-run.description"),
|
|
921
|
+
default: false
|
|
922
|
+
}
|
|
923
|
+
},
|
|
924
|
+
async run({ args }) {
|
|
925
|
+
const selectedClients = parseClientFilter2(args.clients);
|
|
926
|
+
const result = await installMcpClients(process.cwd(), {
|
|
927
|
+
clients: selectedClients === null ? void 0 : Array.from(selectedClients),
|
|
928
|
+
dryRun: args["dry-run"]
|
|
929
|
+
});
|
|
930
|
+
if (result.details.length === 0) {
|
|
931
|
+
writeStderr2(t("cli.config.install.no-configs"));
|
|
932
|
+
return;
|
|
933
|
+
}
|
|
934
|
+
for (const detail of result.details) {
|
|
935
|
+
if (detail.action === "skipped") {
|
|
936
|
+
writeStderr2(t("cli.config.install.no-config-path", { client: detail.client }));
|
|
937
|
+
continue;
|
|
938
|
+
}
|
|
939
|
+
if (detail.action === "dry-run" && detail.path !== null) {
|
|
940
|
+
writeStderr2(t("cli.config.install.dry-run", { client: detail.client, path: detail.path }));
|
|
941
|
+
continue;
|
|
942
|
+
}
|
|
943
|
+
if (detail.path !== null) {
|
|
944
|
+
writeStderr2(t("cli.config.install.wrote", { client: detail.client, path: detail.path }));
|
|
945
|
+
}
|
|
946
|
+
}
|
|
947
|
+
}
|
|
948
|
+
})
|
|
949
|
+
}
|
|
950
|
+
});
|
|
951
|
+
async function installMcpClients(target, options = {}) {
|
|
952
|
+
const workspaceRoot = resolve7(target);
|
|
953
|
+
const fabricConfig = await loadFabricConfig(workspaceRoot);
|
|
954
|
+
const selectedClients = options.clients === void 0 ? null : new Set(options.clients);
|
|
955
|
+
const serverPath = resolveServerPath(options.localServerPath);
|
|
956
|
+
const writers = resolveClients(workspaceRoot, fabricConfig).filter(
|
|
957
|
+
(writer) => selectedClients === null ? true : selectedClients.has(writer.clientKind)
|
|
958
|
+
);
|
|
959
|
+
const installed = [];
|
|
960
|
+
const skipped = [];
|
|
961
|
+
const details = [];
|
|
962
|
+
for (const writer of writers) {
|
|
963
|
+
const configPath = await writer.detect(workspaceRoot);
|
|
964
|
+
if (configPath === null) {
|
|
965
|
+
skipped.push(writer.clientKind);
|
|
966
|
+
details.push({ client: writer.clientKind, path: null, action: "skipped" });
|
|
967
|
+
continue;
|
|
968
|
+
}
|
|
969
|
+
if (options.dryRun) {
|
|
970
|
+
skipped.push(writer.clientKind);
|
|
971
|
+
details.push({ client: writer.clientKind, path: configPath, action: "dry-run" });
|
|
972
|
+
continue;
|
|
973
|
+
}
|
|
974
|
+
await writer.write(serverPath, workspaceRoot);
|
|
975
|
+
installed.push(writer.clientKind);
|
|
976
|
+
details.push({ client: writer.clientKind, path: configPath, action: "wrote" });
|
|
977
|
+
}
|
|
978
|
+
return { installed, skipped, details };
|
|
979
|
+
}
|
|
980
|
+
|
|
40
981
|
// src/scanner/forensic.ts
|
|
41
982
|
import { execFileSync } from "child_process";
|
|
42
|
-
import { existsSync, readdirSync, readFileSync, statSync } from "fs";
|
|
983
|
+
import { existsSync as existsSync8, readdirSync, readFileSync as readFileSync3, statSync as statSync2 } from "fs";
|
|
43
984
|
import { createRequire } from "module";
|
|
44
|
-
import { basename, extname, isAbsolute, join, posix, relative, resolve, sep } from "path";
|
|
985
|
+
import { basename, extname, isAbsolute as isAbsolute3, join as join7, posix, relative, resolve as resolve8, sep } from "path";
|
|
45
986
|
import {
|
|
46
987
|
forensicReportSchema
|
|
47
988
|
} from "@fenglimg/fabric-shared";
|
|
@@ -127,7 +1068,7 @@ var parserInitPromise = null;
|
|
|
127
1068
|
var languagePromiseByKind = {};
|
|
128
1069
|
var parserBundlePromiseByKind = {};
|
|
129
1070
|
async function buildForensicReport(targetInput) {
|
|
130
|
-
const target =
|
|
1071
|
+
const target = normalizeTarget3(targetInput);
|
|
131
1072
|
const framework = detectFramework(target);
|
|
132
1073
|
const topology = buildTopology(target);
|
|
133
1074
|
const entryPoints = collectEntryPoints(target, topology.files);
|
|
@@ -163,11 +1104,11 @@ async function buildForensicReport(targetInput) {
|
|
|
163
1104
|
}
|
|
164
1105
|
return validation.data;
|
|
165
1106
|
}
|
|
166
|
-
function
|
|
167
|
-
return
|
|
1107
|
+
function normalizeTarget3(targetInput) {
|
|
1108
|
+
return isAbsolute3(targetInput) ? targetInput : resolve8(process.cwd(), targetInput);
|
|
168
1109
|
}
|
|
169
1110
|
function buildTopology(root) {
|
|
170
|
-
|
|
1111
|
+
assertExistingDirectory2(root);
|
|
171
1112
|
const byExt = {};
|
|
172
1113
|
const keyDirs = /* @__PURE__ */ new Set();
|
|
173
1114
|
const files = [];
|
|
@@ -180,7 +1121,7 @@ function buildTopology(root) {
|
|
|
180
1121
|
continue;
|
|
181
1122
|
}
|
|
182
1123
|
for (const entry of readdirSync(current, { withFileTypes: true })) {
|
|
183
|
-
const absolutePath =
|
|
1124
|
+
const absolutePath = join7(current, entry.name);
|
|
184
1125
|
const relativePath = toPosixPath(relative(root, absolutePath));
|
|
185
1126
|
if (relativePath.length === 0) {
|
|
186
1127
|
continue;
|
|
@@ -200,7 +1141,7 @@ function buildTopology(root) {
|
|
|
200
1141
|
if (!entry.isFile()) {
|
|
201
1142
|
continue;
|
|
202
1143
|
}
|
|
203
|
-
const stats =
|
|
1144
|
+
const stats = statSync2(absolutePath);
|
|
204
1145
|
const extension = extname(entry.name) || "[none]";
|
|
205
1146
|
byExt[extension] = (byExt[extension] ?? 0) + 1;
|
|
206
1147
|
totalFiles += 1;
|
|
@@ -218,8 +1159,8 @@ function buildTopology(root) {
|
|
|
218
1159
|
files: files.sort((left, right) => left.relativePath.localeCompare(right.relativePath))
|
|
219
1160
|
};
|
|
220
1161
|
}
|
|
221
|
-
function
|
|
222
|
-
if (!
|
|
1162
|
+
function assertExistingDirectory2(target) {
|
|
1163
|
+
if (!existsSync8(target) || !statSync2(target).isDirectory()) {
|
|
223
1164
|
throw new Error(`Target must be an existing directory: ${target}`);
|
|
224
1165
|
}
|
|
225
1166
|
}
|
|
@@ -268,7 +1209,7 @@ function getEntryPointReason(relativePath) {
|
|
|
268
1209
|
async function buildCodeSamples(target, entryPoints, frameworkKind, topology, packageDependencies) {
|
|
269
1210
|
const samples = [];
|
|
270
1211
|
for (const entryPoint of entryPoints.slice(0, SAMPLE_LIMIT)) {
|
|
271
|
-
const absolutePath =
|
|
1212
|
+
const absolutePath = join7(target, ...entryPoint.path.split("/"));
|
|
272
1213
|
const sample = readFirstLines(absolutePath, SAMPLE_LINE_LIMIT);
|
|
273
1214
|
const patternAnalysis = await inferPatternHint(entryPoint.path, sample.snippet, {
|
|
274
1215
|
frameworkKind,
|
|
@@ -288,7 +1229,7 @@ async function buildCodeSamples(target, entryPoints, frameworkKind, topology, pa
|
|
|
288
1229
|
}
|
|
289
1230
|
function readFirstLines(path, lineLimit) {
|
|
290
1231
|
try {
|
|
291
|
-
const lines =
|
|
1232
|
+
const lines = readFileSync3(path, "utf8").split(/\r?\n/);
|
|
292
1233
|
if (lines.at(-1) === "") {
|
|
293
1234
|
lines.pop();
|
|
294
1235
|
}
|
|
@@ -305,12 +1246,12 @@ function readFirstLines(path, lineLimit) {
|
|
|
305
1246
|
}
|
|
306
1247
|
}
|
|
307
1248
|
function readPackageDependencies(target) {
|
|
308
|
-
const packageJsonPath =
|
|
309
|
-
if (!
|
|
1249
|
+
const packageJsonPath = join7(target, "package.json");
|
|
1250
|
+
if (!existsSync8(packageJsonPath)) {
|
|
310
1251
|
return /* @__PURE__ */ new Map();
|
|
311
1252
|
}
|
|
312
1253
|
try {
|
|
313
|
-
const packageJson = JSON.parse(
|
|
1254
|
+
const packageJson = JSON.parse(readFileSync3(packageJsonPath, "utf8"));
|
|
314
1255
|
return new Map([
|
|
315
1256
|
...Object.entries(packageJson.dependencies ?? {}),
|
|
316
1257
|
...Object.entries(packageJson.devDependencies ?? {}),
|
|
@@ -646,16 +1587,16 @@ function scoreFrameworkConfidence(input) {
|
|
|
646
1587
|
return input.configCount > 0 || input.packageCount > 0 ? "MEDIUM" : "LOW";
|
|
647
1588
|
}
|
|
648
1589
|
function readReadmeInfo(target) {
|
|
649
|
-
const readmePath =
|
|
650
|
-
const hasContributing =
|
|
651
|
-
if (!
|
|
1590
|
+
const readmePath = join7(target, "README.md");
|
|
1591
|
+
const hasContributing = existsSync8(join7(target, "CONTRIBUTING.md"));
|
|
1592
|
+
if (!existsSync8(readmePath)) {
|
|
652
1593
|
return {
|
|
653
1594
|
quality: "missing",
|
|
654
1595
|
line_count: 0,
|
|
655
1596
|
has_contributing: hasContributing
|
|
656
1597
|
};
|
|
657
1598
|
}
|
|
658
|
-
const readme =
|
|
1599
|
+
const readme = readFileSync3(readmePath, "utf8");
|
|
659
1600
|
const wordCount = readme.trim().split(/\s+/).filter(Boolean).length;
|
|
660
1601
|
return {
|
|
661
1602
|
quality: wordCount >= 200 ? "ok" : "stub",
|
|
@@ -888,7 +1829,7 @@ function buildDomainAssertion(codeSamples) {
|
|
|
888
1829
|
namedModules.length >= 2 ? "domain-named-components" : null,
|
|
889
1830
|
namedSamples.some((sample) => sample.snippet.includes("start():")) ? "lifecycle-hook" : null
|
|
890
1831
|
]),
|
|
891
|
-
proposedRule: "Preserve domain-specific module names when mirroring structure into
|
|
1832
|
+
proposedRule: "Preserve domain-specific module names when mirroring structure into .fabric/rules/."
|
|
892
1833
|
});
|
|
893
1834
|
}
|
|
894
1835
|
function createAssertion(input) {
|
|
@@ -1133,10 +2074,10 @@ function buildSkillRecommendations(frameworkKind, topology, readme) {
|
|
|
1133
2074
|
return recommendations;
|
|
1134
2075
|
}
|
|
1135
2076
|
function readProjectName(target) {
|
|
1136
|
-
const packageJsonPath =
|
|
1137
|
-
if (
|
|
2077
|
+
const packageJsonPath = join7(target, "package.json");
|
|
2078
|
+
if (existsSync8(packageJsonPath)) {
|
|
1138
2079
|
try {
|
|
1139
|
-
const packageJson = JSON.parse(
|
|
2080
|
+
const packageJson = JSON.parse(readFileSync3(packageJsonPath, "utf8"));
|
|
1140
2081
|
if (packageJson.name !== void 0 && packageJson.name.trim().length > 0) {
|
|
1141
2082
|
return packageJson.name;
|
|
1142
2083
|
}
|
|
@@ -1147,7 +2088,7 @@ function readProjectName(target) {
|
|
|
1147
2088
|
return basename(target);
|
|
1148
2089
|
}
|
|
1149
2090
|
function getCliVersion() {
|
|
1150
|
-
return true ? "1.
|
|
2091
|
+
return true ? "1.7.0" : "unknown";
|
|
1151
2092
|
}
|
|
1152
2093
|
function sortRecord(record) {
|
|
1153
2094
|
return Object.fromEntries(Object.entries(record).sort(([left], [right]) => left.localeCompare(right)));
|
|
@@ -1165,10 +2106,10 @@ var CODEX_SESSION_START_HOOK_TEMPLATE = "templates/codex-hooks/fabric-session-st
|
|
|
1165
2106
|
var CODEX_STOP_HOOK_TEMPLATE = "templates/codex-hooks/fabric-stop-reminder.cjs";
|
|
1166
2107
|
var CODEX_SESSION_START_COMMAND = ".codex/hooks/fabric-session-start.cjs";
|
|
1167
2108
|
var CODEX_STOP_COMMAND = ".codex/hooks/fabric-stop-reminder.cjs";
|
|
1168
|
-
var LOCAL_FABRIC_SERVER_PATH =
|
|
2109
|
+
var LOCAL_FABRIC_SERVER_PATH = join8("node_modules", "@fenglimg", "fabric-server", "dist", "index.js");
|
|
1169
2110
|
var FABRIC_SERVER_PACKAGE = "@fenglimg/fabric-server";
|
|
1170
2111
|
var INIT_WIZARD_GROUP_CANCELLED = /* @__PURE__ */ Symbol("init-wizard-group-cancelled");
|
|
1171
|
-
var initCommand =
|
|
2112
|
+
var initCommand = defineCommand4({
|
|
1172
2113
|
meta: {
|
|
1173
2114
|
name: "init",
|
|
1174
2115
|
description: t("cli.init.description")
|
|
@@ -1243,13 +2184,13 @@ async function runInitCommand(args) {
|
|
|
1243
2184
|
logger(step);
|
|
1244
2185
|
}
|
|
1245
2186
|
if (intent.options.planOnly) {
|
|
1246
|
-
|
|
2187
|
+
writeStderr3(t("cli.init.compat.plan"));
|
|
1247
2188
|
}
|
|
1248
2189
|
if (args.interactive === false) {
|
|
1249
|
-
|
|
2190
|
+
writeStderr3(t("cli.init.compat.interactive"));
|
|
1250
2191
|
}
|
|
1251
2192
|
if (args.bootstrap === false || args.mcp === false || args.hooks === false) {
|
|
1252
|
-
|
|
2193
|
+
writeStderr3(t("cli.init.compat.legacy-stage-flags"));
|
|
1253
2194
|
}
|
|
1254
2195
|
const supports = detectClientSupports(intent.target);
|
|
1255
2196
|
const basePlan = await buildInitExecutionPlan({
|
|
@@ -1261,13 +2202,13 @@ async function runInitCommand(args) {
|
|
|
1261
2202
|
});
|
|
1262
2203
|
const plan = intent.wizardEnabled ? await resolveInitExecutionPlanWithWizard(basePlan, args, createDefaultInitWizardAdapter()) : basePlan;
|
|
1263
2204
|
if (plan === null) {
|
|
1264
|
-
|
|
2205
|
+
writeStderr3(t("cli.init.wizard.cancelled"));
|
|
1265
2206
|
throw new Error(t("cli.init.wizard.cancelled"));
|
|
1266
2207
|
}
|
|
1267
2208
|
return executeInitExecutionPlan(plan);
|
|
1268
2209
|
}
|
|
1269
2210
|
function resolveInitCliIntent(args, targetInput) {
|
|
1270
|
-
const target =
|
|
2211
|
+
const target = normalizeTarget4(targetInput);
|
|
1271
2212
|
const mcpInstallMode = resolveMcpInstallMode(args["mcp-install"]);
|
|
1272
2213
|
const terminalInteractive = isInteractiveInit();
|
|
1273
2214
|
const planOnly = args.plan === true;
|
|
@@ -1322,10 +2263,10 @@ async function buildInitExecutionPlan(input) {
|
|
|
1322
2263
|
}
|
|
1323
2264
|
async function executeInitExecutionPlan(plan) {
|
|
1324
2265
|
if (plan.options.force) {
|
|
1325
|
-
|
|
2266
|
+
writeStderr3(t("cli.init.force.warning", { path: plan.target }));
|
|
1326
2267
|
}
|
|
1327
2268
|
if (plan.options.reapply && !plan.options.planOnly && !plan.interactive) {
|
|
1328
|
-
|
|
2269
|
+
writeStderr3(formatInitModeBanner(plan.options));
|
|
1329
2270
|
}
|
|
1330
2271
|
if (plan.interactive) {
|
|
1331
2272
|
printInitPlanSummary(plan.target, plan.options, plan.mcpInstallMode, plan.supports);
|
|
@@ -1371,28 +2312,28 @@ async function executeInitExecutionPlan(plan) {
|
|
|
1371
2312
|
};
|
|
1372
2313
|
}
|
|
1373
2314
|
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
|
|
2315
|
+
assertExistingDirectory3(target);
|
|
2316
|
+
const fabricDir = join8(target, ".fabric");
|
|
2317
|
+
const bootstrapPath = join8(fabricDir, "bootstrap", "README.md");
|
|
2318
|
+
const forensicPath = join8(fabricDir, "forensic.json");
|
|
2319
|
+
const taxonomyPath = join8(fabricDir, "INITIAL_TAXONOMY.md");
|
|
2320
|
+
const rulesDir = join8(fabricDir, "rules");
|
|
2321
|
+
const eventsPath = join8(fabricDir, "events.jsonl");
|
|
2322
|
+
const claudeSkillPath = join8(target, ".claude", "skills", "agents-md-init", "SKILL.md");
|
|
2323
|
+
const codexSkillPath = join8(target, ".agents", "skills", "fabric-init", "SKILL.md");
|
|
2324
|
+
const codexSessionStartHookPath = join8(target, ".codex", "hooks", "fabric-session-start.cjs");
|
|
2325
|
+
const codexStopHookPath = join8(target, ".codex", "hooks", "fabric-stop-reminder.cjs");
|
|
2326
|
+
const codexHooksConfigPath = join8(target, ".codex", "hooks.json");
|
|
2327
|
+
const claudeHookPath = join8(target, ".claude", "hooks", "agents-md-init-reminder.cjs");
|
|
2328
|
+
const claudeSettingsPath = join8(target, ".claude", "settings.json");
|
|
2329
|
+
const metaPath = join8(fabricDir, "agents.meta.json");
|
|
1388
2330
|
const replaceFabricDir = shouldReplaceWritableDirectory(fabricDir, options);
|
|
1389
2331
|
const bootstrapAction = planFreshPath(bootstrapPath, options);
|
|
1390
2332
|
const metaAction = planFreshPath(metaPath, options);
|
|
1391
2333
|
const taxonomyAction = planFreshPath(taxonomyPath, options);
|
|
1392
|
-
const
|
|
2334
|
+
const eventsAction = planFreshPath(eventsPath, options);
|
|
1393
2335
|
const forensicAction = planFreshPath(forensicPath, options);
|
|
1394
2336
|
const forensicReport = await buildForensicReport(target);
|
|
1395
|
-
const humanLockTemplate = readFileSync2(findTemplatePath("templates/fabric/human-lock.json"), "utf8");
|
|
1396
2337
|
const bootstrapContent = await buildFabricBootstrapGuide(target);
|
|
1397
2338
|
const taxonomyContent = buildInitialTaxonomyMarkdown(forensicReport);
|
|
1398
2339
|
const bootstrapHash = sha256(bootstrapContent);
|
|
@@ -1411,31 +2352,30 @@ async function buildInitFabricPlan(target, options) {
|
|
|
1411
2352
|
taxonomyPath,
|
|
1412
2353
|
taxonomyAction,
|
|
1413
2354
|
taxonomyContent,
|
|
1414
|
-
|
|
1415
|
-
|
|
1416
|
-
|
|
1417
|
-
`,
|
|
2355
|
+
rulesDir,
|
|
2356
|
+
eventsPath,
|
|
2357
|
+
eventsAction,
|
|
1418
2358
|
forensicPath,
|
|
1419
2359
|
forensicAction,
|
|
1420
2360
|
forensicReport,
|
|
1421
|
-
claudeSkill: buildOptionalTemplateWritePlan(claudeSkillPath,
|
|
1422
|
-
codexSkill: buildOptionalTemplateWritePlan(codexSkillPath,
|
|
2361
|
+
claudeSkill: buildOptionalTemplateWritePlan(claudeSkillPath, findTemplatePath3(CLAUDE_INIT_SKILL_TEMPLATE), options),
|
|
2362
|
+
codexSkill: buildOptionalTemplateWritePlan(codexSkillPath, findTemplatePath3(CODEX_INIT_SKILL_TEMPLATE), options),
|
|
1423
2363
|
codexSessionStartHook: buildOptionalTemplateWritePlan(
|
|
1424
2364
|
codexSessionStartHookPath,
|
|
1425
|
-
|
|
2365
|
+
findTemplatePath3(CODEX_SESSION_START_HOOK_TEMPLATE),
|
|
1426
2366
|
options,
|
|
1427
2367
|
true
|
|
1428
2368
|
),
|
|
1429
2369
|
codexStopHook: buildOptionalTemplateWritePlan(
|
|
1430
2370
|
codexStopHookPath,
|
|
1431
|
-
|
|
2371
|
+
findTemplatePath3(CODEX_STOP_HOOK_TEMPLATE),
|
|
1432
2372
|
options,
|
|
1433
2373
|
true
|
|
1434
2374
|
),
|
|
1435
2375
|
codexHooksConfig: buildCodexHooksConfigPlan(codexHooksConfigPath, options),
|
|
1436
2376
|
claudeHook: buildOptionalTemplateWritePlan(
|
|
1437
2377
|
claudeHookPath,
|
|
1438
|
-
|
|
2378
|
+
findTemplatePath3(CLAUDE_INIT_REMINDER_HOOK_TEMPLATE),
|
|
1439
2379
|
options,
|
|
1440
2380
|
true
|
|
1441
2381
|
),
|
|
@@ -1446,19 +2386,20 @@ function executeInitFabricPlan(plan) {
|
|
|
1446
2386
|
if (plan.replaceFabricDir) {
|
|
1447
2387
|
rmSync(plan.fabricDir, { force: true });
|
|
1448
2388
|
}
|
|
1449
|
-
|
|
1450
|
-
|
|
2389
|
+
mkdirSync3(plan.fabricDir, { recursive: true });
|
|
2390
|
+
mkdirSync3(dirname5(plan.bootstrapPath), { recursive: true });
|
|
1451
2391
|
preparePlannedPath(plan.bootstrapPath, plan.bootstrapAction);
|
|
1452
|
-
|
|
2392
|
+
writeFileSync3(plan.bootstrapPath, plan.bootstrapContent, "utf8");
|
|
1453
2393
|
preparePlannedPath(plan.metaPath, plan.metaAction);
|
|
1454
|
-
|
|
2394
|
+
writeFileSync3(plan.metaPath, `${JSON.stringify(plan.meta, null, 2)}
|
|
1455
2395
|
`, "utf8");
|
|
1456
2396
|
preparePlannedPath(plan.taxonomyPath, plan.taxonomyAction);
|
|
1457
|
-
|
|
1458
|
-
|
|
1459
|
-
|
|
2397
|
+
writeFileSync3(plan.taxonomyPath, ensureTrailingNewline2(plan.taxonomyContent), "utf8");
|
|
2398
|
+
mkdirSync3(plan.rulesDir, { recursive: true });
|
|
2399
|
+
preparePlannedPath(plan.eventsPath, plan.eventsAction);
|
|
2400
|
+
writeFileSync3(plan.eventsPath, "", "utf8");
|
|
1460
2401
|
preparePlannedPath(plan.forensicPath, plan.forensicAction);
|
|
1461
|
-
|
|
2402
|
+
writeFileSync3(plan.forensicPath, `${JSON.stringify(plan.forensicReport, null, 2)}
|
|
1462
2403
|
`, "utf8");
|
|
1463
2404
|
applyOptionalTemplateWritePlan(plan.claudeSkill);
|
|
1464
2405
|
applyOptionalTemplateWritePlan(plan.codexSkill);
|
|
@@ -1474,8 +2415,8 @@ function executeInitFabricPlan(plan) {
|
|
|
1474
2415
|
metaAction: plan.metaAction,
|
|
1475
2416
|
taxonomyPath: plan.taxonomyPath,
|
|
1476
2417
|
taxonomyAction: plan.taxonomyAction,
|
|
1477
|
-
|
|
1478
|
-
|
|
2418
|
+
eventsPath: plan.eventsPath,
|
|
2419
|
+
eventsAction: plan.eventsAction,
|
|
1479
2420
|
forensicPath: plan.forensicPath,
|
|
1480
2421
|
forensicAction: plan.forensicAction,
|
|
1481
2422
|
claudeSkillPath: plan.claudeSkill.path,
|
|
@@ -1537,19 +2478,19 @@ function printInitScaffoldResult(created) {
|
|
|
1537
2478
|
console.log(formatInitPathAction(created.bootstrapPath, created.bootstrapAction));
|
|
1538
2479
|
console.log(formatInitPathAction(created.metaPath, created.metaAction));
|
|
1539
2480
|
console.log(formatInitPathAction(created.taxonomyPath, created.taxonomyAction));
|
|
1540
|
-
console.log(formatInitPathAction(created.
|
|
2481
|
+
console.log(formatInitPathAction(created.eventsPath, created.eventsAction));
|
|
1541
2482
|
console.log(formatInitPathAction(created.forensicPath, created.forensicAction));
|
|
1542
|
-
|
|
1543
|
-
|
|
1544
|
-
|
|
2483
|
+
writeStderr3(formatOptionalInitPathAction(created.claudeSkillPath, created.claudeSkillAction));
|
|
2484
|
+
writeStderr3(formatOptionalInitPathAction(created.codexSkillPath, created.codexSkillAction));
|
|
2485
|
+
writeStderr3(
|
|
1545
2486
|
formatOptionalInitPathAction(created.codexSessionStartHookPath, created.codexSessionStartHookAction)
|
|
1546
2487
|
);
|
|
1547
|
-
|
|
2488
|
+
writeStderr3(
|
|
1548
2489
|
formatOptionalInitPathAction(created.codexStopHookPath, created.codexStopHookAction)
|
|
1549
2490
|
);
|
|
1550
|
-
|
|
1551
|
-
|
|
1552
|
-
|
|
2491
|
+
writeStderr3(formatCodexHooksAction(created.codexHooksConfigPath, created.codexHooksConfigAction));
|
|
2492
|
+
writeStderr3(formatOptionalInitPathAction(created.claudeHookPath, created.claudeHookAction));
|
|
2493
|
+
writeStderr3(formatClaudeSettingsAction(created.claudeSettingsPath, created.claudeSettingsAction));
|
|
1553
2494
|
}
|
|
1554
2495
|
function printInitPostSetup(plan, stageResults, finalSupports) {
|
|
1555
2496
|
if (shouldPrintHooksNextStep(plan.options, stageResults)) {
|
|
@@ -1589,8 +2530,8 @@ function buildPlanOnlyScaffoldResult(plan) {
|
|
|
1589
2530
|
metaAction: plan.metaAction,
|
|
1590
2531
|
taxonomyPath: plan.taxonomyPath,
|
|
1591
2532
|
taxonomyAction: plan.taxonomyAction,
|
|
1592
|
-
|
|
1593
|
-
|
|
2533
|
+
eventsPath: plan.eventsPath,
|
|
2534
|
+
eventsAction: plan.eventsAction,
|
|
1594
2535
|
forensicPath: plan.forensicPath,
|
|
1595
2536
|
forensicAction: plan.forensicAction,
|
|
1596
2537
|
claudeSkillPath: plan.claudeSkill.path,
|
|
@@ -1634,12 +2575,12 @@ async function executeInitStagePlan(plan, stageName) {
|
|
|
1634
2575
|
case "mcp": {
|
|
1635
2576
|
if (stage.installMode === "local") {
|
|
1636
2577
|
const manager = stage.packageManager ?? detectPackageManager(plan.target);
|
|
1637
|
-
|
|
1638
|
-
|
|
2578
|
+
writeStderr3(t("cli.init.mcp.install.local"));
|
|
2579
|
+
writeStderr3(t("cli.init.mcp.local.installing", { manager }));
|
|
1639
2580
|
installLocalFabricServer(plan.target, manager);
|
|
1640
|
-
|
|
2581
|
+
writeStderr3(t("cli.init.mcp.local.installed"));
|
|
1641
2582
|
} else {
|
|
1642
|
-
|
|
2583
|
+
writeStderr3(t("cli.init.mcp.install.global"));
|
|
1643
2584
|
}
|
|
1644
2585
|
const result = await installMcpClients(plan.target, {
|
|
1645
2586
|
force: plan.options.force,
|
|
@@ -1661,15 +2602,15 @@ async function executeInitStagePlan(plan, stageName) {
|
|
|
1661
2602
|
return exhaustiveInitStagePlan(stage);
|
|
1662
2603
|
}
|
|
1663
2604
|
} catch (error) {
|
|
1664
|
-
|
|
2605
|
+
writeStderr3(formatInitStageFailure(stageName, error));
|
|
1665
2606
|
return { name: stageName, disposition: "failed" };
|
|
1666
2607
|
}
|
|
1667
2608
|
}
|
|
1668
2609
|
function shouldReplaceWritableDirectory(path, options) {
|
|
1669
|
-
if (!
|
|
2610
|
+
if (!existsSync9(path)) {
|
|
1670
2611
|
return false;
|
|
1671
2612
|
}
|
|
1672
|
-
if (
|
|
2613
|
+
if (statSync3(path).isDirectory()) {
|
|
1673
2614
|
return false;
|
|
1674
2615
|
}
|
|
1675
2616
|
if (!options?.force) {
|
|
@@ -1678,7 +2619,7 @@ function shouldReplaceWritableDirectory(path, options) {
|
|
|
1678
2619
|
return true;
|
|
1679
2620
|
}
|
|
1680
2621
|
function planFreshPath(path, options) {
|
|
1681
|
-
if (!
|
|
2622
|
+
if (!existsSync9(path)) {
|
|
1682
2623
|
return "created";
|
|
1683
2624
|
}
|
|
1684
2625
|
if (!options?.force) {
|
|
@@ -1687,13 +2628,13 @@ function planFreshPath(path, options) {
|
|
|
1687
2628
|
return "overwritten";
|
|
1688
2629
|
}
|
|
1689
2630
|
function preparePlannedPath(path, action) {
|
|
1690
|
-
|
|
1691
|
-
if (action === "overwritten" &&
|
|
2631
|
+
mkdirSync3(dirname5(path), { recursive: true });
|
|
2632
|
+
if (action === "overwritten" && existsSync9(path)) {
|
|
1692
2633
|
rmSync(path, { recursive: true, force: true });
|
|
1693
2634
|
}
|
|
1694
2635
|
}
|
|
1695
2636
|
function buildOptionalTemplateWritePlan(path, templatePath, options, executable = false) {
|
|
1696
|
-
const existed =
|
|
2637
|
+
const existed = existsSync9(path);
|
|
1697
2638
|
if (existed && !options?.force) {
|
|
1698
2639
|
return { path, action: "skipped", templatePath, executable };
|
|
1699
2640
|
}
|
|
@@ -1708,10 +2649,10 @@ function applyOptionalTemplateWritePlan(plan) {
|
|
|
1708
2649
|
if (plan.action === "skipped") {
|
|
1709
2650
|
return;
|
|
1710
2651
|
}
|
|
1711
|
-
|
|
2652
|
+
mkdirSync3(dirname5(plan.path), { recursive: true });
|
|
1712
2653
|
copyFileSync(plan.templatePath, plan.path);
|
|
1713
2654
|
if (plan.executable) {
|
|
1714
|
-
|
|
2655
|
+
chmodSync2(plan.path, 493);
|
|
1715
2656
|
}
|
|
1716
2657
|
}
|
|
1717
2658
|
function buildCodexHooksConfigValue() {
|
|
@@ -1733,7 +2674,7 @@ function buildCodexHooksConfigValue() {
|
|
|
1733
2674
|
};
|
|
1734
2675
|
}
|
|
1735
2676
|
function buildCodexHooksConfigPlan(configPath, options) {
|
|
1736
|
-
const action = !
|
|
2677
|
+
const action = !existsSync9(configPath) ? "created" : options?.force ? "overwritten" : "skipped";
|
|
1737
2678
|
return {
|
|
1738
2679
|
path: configPath,
|
|
1739
2680
|
action,
|
|
@@ -1744,37 +2685,37 @@ function applyJsonWritePlan(plan) {
|
|
|
1744
2685
|
if (plan.action === "skipped") {
|
|
1745
2686
|
return;
|
|
1746
2687
|
}
|
|
1747
|
-
|
|
2688
|
+
mkdirSync3(dirname5(plan.path), { recursive: true });
|
|
1748
2689
|
writeJsonAtomically(plan.path, plan.value);
|
|
1749
2690
|
}
|
|
1750
2691
|
function buildClaudeSettingsWritePlan(settingsPath, options) {
|
|
1751
2692
|
let settings;
|
|
1752
2693
|
let action = "updated";
|
|
1753
|
-
if (!
|
|
2694
|
+
if (!existsSync9(settingsPath)) {
|
|
1754
2695
|
settings = {};
|
|
1755
2696
|
action = "created";
|
|
1756
2697
|
} else {
|
|
1757
2698
|
try {
|
|
1758
|
-
const parsed = JSON.parse(
|
|
2699
|
+
const parsed = JSON.parse(readFileSync4(settingsPath, "utf8"));
|
|
1759
2700
|
if (!isRecord(parsed)) {
|
|
1760
|
-
|
|
2701
|
+
writeStderr3(t("cli.init.claude-settings.invalid-object", { label: skippedLabel(), path: settingsPath }));
|
|
1761
2702
|
return { path: settingsPath, action: "skipped-invalid", value: null };
|
|
1762
2703
|
}
|
|
1763
2704
|
settings = parsed;
|
|
1764
2705
|
} catch (error) {
|
|
1765
2706
|
const reason = error instanceof Error ? error.message : "unknown parse error";
|
|
1766
|
-
|
|
2707
|
+
writeStderr3(t("cli.init.claude-settings.invalid-json", { label: skippedLabel(), path: settingsPath, reason }));
|
|
1767
2708
|
return { path: settingsPath, action: "skipped-invalid", value: null };
|
|
1768
2709
|
}
|
|
1769
2710
|
}
|
|
1770
2711
|
if (settings.hooks !== void 0 && !isRecord(settings.hooks)) {
|
|
1771
|
-
|
|
2712
|
+
writeStderr3(t("cli.init.claude-settings.invalid-hooks", { label: skippedLabel(), path: settingsPath }));
|
|
1772
2713
|
return { path: settingsPath, action: "skipped-invalid", value: null };
|
|
1773
2714
|
}
|
|
1774
2715
|
const hooks = settings.hooks ?? {};
|
|
1775
2716
|
const stopHooksValue = hooks.Stop;
|
|
1776
2717
|
if (stopHooksValue !== void 0 && !Array.isArray(stopHooksValue)) {
|
|
1777
|
-
|
|
2718
|
+
writeStderr3(t("cli.init.claude-settings.invalid-stop-array", { label: skippedLabel(), path: settingsPath }));
|
|
1778
2719
|
return { path: settingsPath, action: "skipped-invalid", value: null };
|
|
1779
2720
|
}
|
|
1780
2721
|
const stopHooks = Array.isArray(stopHooksValue) ? stopHooksValue : [];
|
|
@@ -1809,7 +2750,7 @@ function applyClaudeSettingsWritePlan(plan) {
|
|
|
1809
2750
|
if (plan.value === null) {
|
|
1810
2751
|
return;
|
|
1811
2752
|
}
|
|
1812
|
-
|
|
2753
|
+
mkdirSync3(dirname5(plan.path), { recursive: true });
|
|
1813
2754
|
writeJsonAtomically(plan.path, plan.value);
|
|
1814
2755
|
}
|
|
1815
2756
|
function createDefaultInitWizardAdapter() {
|
|
@@ -1964,23 +2905,23 @@ function formatInitModeBadge(options) {
|
|
|
1964
2905
|
}
|
|
1965
2906
|
return t("cli.init.mode.badge.default");
|
|
1966
2907
|
}
|
|
1967
|
-
function
|
|
1968
|
-
return
|
|
2908
|
+
function normalizeTarget4(targetInput) {
|
|
2909
|
+
return isAbsolute4(targetInput) ? targetInput : resolve9(process.cwd(), targetInput);
|
|
1969
2910
|
}
|
|
1970
|
-
function
|
|
1971
|
-
if (!
|
|
2911
|
+
function assertExistingDirectory3(target) {
|
|
2912
|
+
if (!existsSync9(target) || !statSync3(target).isDirectory()) {
|
|
1972
2913
|
throw new Error(`Target must be an existing directory: ${target}`);
|
|
1973
2914
|
}
|
|
1974
2915
|
}
|
|
1975
2916
|
function detectPackageManager(cwd) {
|
|
1976
|
-
const workspaceRoot =
|
|
1977
|
-
if (
|
|
2917
|
+
const workspaceRoot = resolve9(cwd);
|
|
2918
|
+
if (existsSync9(join8(workspaceRoot, "pnpm-lock.yaml"))) {
|
|
1978
2919
|
return "pnpm";
|
|
1979
2920
|
}
|
|
1980
|
-
if (
|
|
2921
|
+
if (existsSync9(join8(workspaceRoot, "yarn.lock"))) {
|
|
1981
2922
|
return "yarn";
|
|
1982
2923
|
}
|
|
1983
|
-
if (
|
|
2924
|
+
if (existsSync9(join8(workspaceRoot, "package-lock.json"))) {
|
|
1984
2925
|
return "npm";
|
|
1985
2926
|
}
|
|
1986
2927
|
return "npm";
|
|
@@ -1989,7 +2930,7 @@ function resolveMcpInstallMode(rawMode) {
|
|
|
1989
2930
|
if (rawMode === void 0 || rawMode === "global" || rawMode === "local") {
|
|
1990
2931
|
return rawMode ?? "global";
|
|
1991
2932
|
}
|
|
1992
|
-
|
|
2933
|
+
writeStderr3(t("cli.init.mcp.install.invalid", { value: rawMode }));
|
|
1993
2934
|
return "global";
|
|
1994
2935
|
}
|
|
1995
2936
|
function installLocalFabricServer(target, manager) {
|
|
@@ -2066,30 +3007,30 @@ function sanitizeTaxonomyLabel(value) {
|
|
|
2066
3007
|
const sanitized = value.replaceAll("\\", "/").split("/").filter(Boolean).join("-").replace(/[^A-Za-z0-9_-]+/gu, "-").replace(/^-+|-+$/gu, "");
|
|
2067
3008
|
return sanitized === "" ? "General" : sanitized;
|
|
2068
3009
|
}
|
|
2069
|
-
function
|
|
3010
|
+
function ensureTrailingNewline2(value) {
|
|
2070
3011
|
return value.endsWith("\n") ? value : `${value}
|
|
2071
3012
|
`;
|
|
2072
3013
|
}
|
|
2073
|
-
function
|
|
2074
|
-
const currentModuleDir =
|
|
3014
|
+
function findTemplatePath3(relativePath) {
|
|
3015
|
+
const currentModuleDir = dirname5(fileURLToPath4(import.meta.url));
|
|
2075
3016
|
const candidates = [
|
|
2076
|
-
...
|
|
2077
|
-
...
|
|
3017
|
+
...templateCandidatesFrom3(process.cwd(), relativePath),
|
|
3018
|
+
...templateCandidatesFrom3(currentModuleDir, relativePath)
|
|
2078
3019
|
];
|
|
2079
3020
|
for (const candidate of candidates) {
|
|
2080
|
-
if (
|
|
3021
|
+
if (existsSync9(candidate)) {
|
|
2081
3022
|
return candidate;
|
|
2082
3023
|
}
|
|
2083
3024
|
}
|
|
2084
3025
|
throw new Error(t("cli.shared.template-not-found", { path: relativePath }));
|
|
2085
3026
|
}
|
|
2086
|
-
function
|
|
3027
|
+
function templateCandidatesFrom3(start, relativePath) {
|
|
2087
3028
|
const candidates = [];
|
|
2088
|
-
let current =
|
|
3029
|
+
let current = resolve9(start);
|
|
2089
3030
|
while (true) {
|
|
2090
|
-
candidates.push(
|
|
2091
|
-
const parent =
|
|
2092
|
-
if (parent === current ||
|
|
3031
|
+
candidates.push(join8(current, ...relativePath.split("/")));
|
|
3032
|
+
const parent = dirname5(current);
|
|
3033
|
+
if (parent === current || parse3(current).root === current) {
|
|
2093
3034
|
break;
|
|
2094
3035
|
}
|
|
2095
3036
|
current = parent;
|
|
@@ -2112,7 +3053,7 @@ function isClaudeInitReminderStopEntry(entry) {
|
|
|
2112
3053
|
}
|
|
2113
3054
|
function writeJsonAtomically(path, value) {
|
|
2114
3055
|
const tempPath = `${path}.${process.pid}.tmp`;
|
|
2115
|
-
|
|
3056
|
+
writeFileSync3(tempPath, `${JSON.stringify(value, null, 2)}
|
|
2116
3057
|
`, "utf8");
|
|
2117
3058
|
renameSync(tempPath, path);
|
|
2118
3059
|
}
|
|
@@ -2187,7 +3128,8 @@ function printInitPlanSummary(target, options, mcpInstallMode, supports) {
|
|
|
2187
3128
|
console.log(t("cli.init.plan.writes"));
|
|
2188
3129
|
console.log(` - ${target}/.fabric/bootstrap/README.md`);
|
|
2189
3130
|
console.log(` - ${target}/.fabric/agents.meta.json`);
|
|
2190
|
-
console.log(` - ${target}/.fabric/
|
|
3131
|
+
console.log(` - ${target}/.fabric/INITIAL_TAXONOMY.md`);
|
|
3132
|
+
console.log(` - ${target}/.fabric/events.jsonl`);
|
|
2191
3133
|
console.log(` - ${target}/.fabric/forensic.json`);
|
|
2192
3134
|
}
|
|
2193
3135
|
function printInitCapabilitySummary(supports, stageResults, options) {
|
|
@@ -2351,7 +3293,7 @@ function skippedStageLabel() {
|
|
|
2351
3293
|
function failedStageLabel() {
|
|
2352
3294
|
return paint.error(t("cli.init.stages.failed"));
|
|
2353
3295
|
}
|
|
2354
|
-
function
|
|
3296
|
+
function writeStderr3(message) {
|
|
2355
3297
|
process.stderr.write(`${message}
|
|
2356
3298
|
`);
|
|
2357
3299
|
}
|