@fenglimg/fabric-cli 1.8.0-rc.3 → 2.0.0-rc.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +6 -6
- package/dist/chunk-6ICJICVU.js +10 -0
- package/dist/chunk-AW3G7ZH5.js +576 -0
- package/dist/chunk-HQLEHH4O.js +321 -0
- package/dist/chunk-MT3R57VG.js +1000 -0
- package/dist/{chunk-QPCRBQ5Y.js → chunk-OBQU6NHO.js} +1 -52
- package/dist/chunk-WPTA74BY.js +184 -0
- package/dist/chunk-WWNXR34K.js +49 -0
- package/dist/doctor-RILCO5OG.js +282 -0
- package/dist/hooks-NX32PPEN.js +13 -0
- package/dist/index.js +8 -5
- package/dist/{init-7EYGUJNJ.js → init-SAVH4SKE.js} +281 -1235
- package/dist/plan-context-hint-QMUPAXIB.js +98 -0
- package/dist/scan-ELSNCSKS.js +22 -0
- package/dist/{serve-466QXQ5Q.js → serve-NGLXHDYC.js} +8 -4
- package/dist/uninstall-DBAR2JBS.js +1082 -0
- package/package.json +3 -3
- package/templates/agents-md/AGENTS.md.template +55 -17
- package/templates/bootstrap/CLAUDE.md +1 -1
- package/templates/bootstrap/codex-AGENTS-header.md +1 -1
- package/templates/bootstrap/cursor-fabric-bootstrap.mdc +1 -1
- package/templates/hooks/configs/README.md +73 -0
- package/templates/hooks/configs/claude-code.json +37 -0
- package/templates/hooks/configs/codex-hooks.json +20 -0
- package/templates/hooks/configs/cursor-hooks.json +20 -0
- package/templates/hooks/fabric-hint.cjs +1337 -0
- package/templates/hooks/knowledge-hint-broad.cjs +612 -0
- package/templates/hooks/knowledge-hint-narrow.cjs +826 -0
- package/templates/hooks/lib/session-digest-writer.cjs +172 -0
- package/templates/skills/fabric-archive/SKILL.md +486 -0
- package/templates/skills/fabric-import/SKILL.md +560 -0
- package/templates/skills/fabric-review/SKILL.md +382 -0
- package/dist/chunk-NMMUETVK.js +0 -216
- package/dist/doctor-F52XWWZC.js +0 -98
- package/dist/scan-NNBNGIZG.js +0 -12
- package/templates/agents-md/variants/cocos.md +0 -20
- package/templates/agents-md/variants/next.md +0 -20
- package/templates/agents-md/variants/vite.md +0 -20
- package/templates/bootstrap/GEMINI.md +0 -8
- package/templates/bootstrap/roo-fabric.md +0 -5
- package/templates/bootstrap/windsurf-fabric.md +0 -5
- package/templates/claude-hooks/fabric-init-reminder.cjs +0 -18
- package/templates/claude-skills/fabric-init/SKILL.md +0 -163
- package/templates/codex-hooks/fabric-session-start.cjs +0 -19
- package/templates/codex-hooks/fabric-stop-reminder.cjs +0 -18
- package/templates/codex-skills/fabric-init/SKILL.md +0 -162
- package/templates/husky/pre-commit +0 -9
- package/templates/skill-source/fabric-init/SOURCE.md +0 -157
- package/templates/skill-source/fabric-init/clients.json +0 -17
|
@@ -0,0 +1,321 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
ClaudeCodeCLIWriter,
|
|
4
|
+
CursorWriter,
|
|
5
|
+
createServerEntry,
|
|
6
|
+
normalizeConfigPath,
|
|
7
|
+
removeJsonClientConfigEntry,
|
|
8
|
+
writeJsonClientConfig
|
|
9
|
+
} from "./chunk-AW3G7ZH5.js";
|
|
10
|
+
|
|
11
|
+
// src/config/resolver.ts
|
|
12
|
+
import { existsSync as existsSync3 } from "fs";
|
|
13
|
+
import { join as join3 } from "path";
|
|
14
|
+
import { homedir as homedir3 } from "os";
|
|
15
|
+
|
|
16
|
+
// src/config/claude-code.ts
|
|
17
|
+
import { existsSync } from "fs";
|
|
18
|
+
import { join, resolve } from "path";
|
|
19
|
+
import { homedir, platform } from "os";
|
|
20
|
+
function getClaudeDesktopConfigPath() {
|
|
21
|
+
const os = platform();
|
|
22
|
+
if (os === "darwin") {
|
|
23
|
+
return join(homedir(), "Library", "Application Support", "Claude", "claude_desktop_config.json");
|
|
24
|
+
}
|
|
25
|
+
if (os === "win32") {
|
|
26
|
+
return join(process.env.APPDATA ?? join(homedir(), "AppData", "Roaming"), "Claude", "claude_desktop_config.json");
|
|
27
|
+
}
|
|
28
|
+
return join(homedir(), ".config", "Claude", "claude_desktop_config.json");
|
|
29
|
+
}
|
|
30
|
+
var ClaudeCodeDesktopWriter = class {
|
|
31
|
+
clientKind = "ClaudeCodeDesktop";
|
|
32
|
+
configuredPath;
|
|
33
|
+
constructor(configuredPath) {
|
|
34
|
+
this.configuredPath = configuredPath;
|
|
35
|
+
}
|
|
36
|
+
async detect(_workspaceRoot, overridePath) {
|
|
37
|
+
const configPath = normalizeConfigPath(overridePath ?? this.configuredPath ?? getClaudeDesktopConfigPath());
|
|
38
|
+
return existsSync(configPath) || overridePath !== void 0 || this.configuredPath !== void 0 ? configPath : null;
|
|
39
|
+
}
|
|
40
|
+
async write(serverPath, workspaceRoot, overridePath) {
|
|
41
|
+
const configPath = await this.detect(workspaceRoot, overridePath);
|
|
42
|
+
if (configPath === null) {
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
await writeJsonClientConfig(configPath, {
|
|
46
|
+
command: process.execPath,
|
|
47
|
+
args: [serverPath]
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
async remove(serverName, workspaceRoot, overridePath) {
|
|
51
|
+
const configPath = await this.detect(workspaceRoot, overridePath);
|
|
52
|
+
if (configPath === null) {
|
|
53
|
+
return { status: "skipped", message: "no-config-path" };
|
|
54
|
+
}
|
|
55
|
+
return removeJsonClientConfigEntry(configPath, serverName);
|
|
56
|
+
}
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
// src/config/toml.ts
|
|
60
|
+
import { existsSync as existsSync2 } from "fs";
|
|
61
|
+
import { mkdir, readFile } from "fs/promises";
|
|
62
|
+
import { dirname, join as join2, resolve as resolve2 } from "path";
|
|
63
|
+
import { homedir as homedir2 } from "os";
|
|
64
|
+
import { atomicWriteText } from "@fenglimg/fabric-shared/node/atomic-write";
|
|
65
|
+
function expandHome(filePath) {
|
|
66
|
+
if (filePath === "~") {
|
|
67
|
+
return homedir2();
|
|
68
|
+
}
|
|
69
|
+
if (filePath.startsWith("~/")) {
|
|
70
|
+
return join2(homedir2(), filePath.slice(2));
|
|
71
|
+
}
|
|
72
|
+
return filePath;
|
|
73
|
+
}
|
|
74
|
+
function escapeTomlString(value) {
|
|
75
|
+
return JSON.stringify(value);
|
|
76
|
+
}
|
|
77
|
+
function serializeTomlStringArray(values) {
|
|
78
|
+
return `[${values.map((value) => escapeTomlString(value)).join(", ")}]`;
|
|
79
|
+
}
|
|
80
|
+
function serializeTomlInlineTable(values) {
|
|
81
|
+
const entries = Object.entries(values).sort(([left], [right]) => left.localeCompare(right)).map(([key, value]) => `${key} = ${escapeTomlString(value)}`);
|
|
82
|
+
return `{ ${entries.join(", ")} }`;
|
|
83
|
+
}
|
|
84
|
+
function serializeCodexServerBlock(serverName, serverEntry) {
|
|
85
|
+
const lines = [
|
|
86
|
+
`[mcp_servers.${serverName}]`,
|
|
87
|
+
`command = ${escapeTomlString(serverEntry.command)}`,
|
|
88
|
+
`args = ${serializeTomlStringArray(serverEntry.args)}`
|
|
89
|
+
];
|
|
90
|
+
if (serverEntry.env !== void 0 && Object.keys(serverEntry.env).length > 0) {
|
|
91
|
+
lines.push(`env = ${serializeTomlInlineTable(serverEntry.env)}`);
|
|
92
|
+
}
|
|
93
|
+
return `${lines.join("\n")}
|
|
94
|
+
`;
|
|
95
|
+
}
|
|
96
|
+
function trimTrailingBlankLines(value) {
|
|
97
|
+
return value.replace(/\s+$/u, "");
|
|
98
|
+
}
|
|
99
|
+
function removeCodexServerBlock(rawConfig, serverName) {
|
|
100
|
+
const normalized = rawConfig.replace(/\r\n/g, "\n");
|
|
101
|
+
const escaped = serverName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
102
|
+
const legacyPattern = new RegExp(
|
|
103
|
+
String.raw`\n?\[mcp\.servers\.${escaped}\]\n[\s\S]*?(?=\n\[[^\n]+\]\n|$)`,
|
|
104
|
+
"g"
|
|
105
|
+
);
|
|
106
|
+
const currentPattern = new RegExp(
|
|
107
|
+
String.raw`\n?\[mcp_servers\.${escaped}\]\n[\s\S]*?(?=\n\[[^\n]+\]\n|$)`,
|
|
108
|
+
"g"
|
|
109
|
+
);
|
|
110
|
+
const withoutLegacy = normalized.replace(legacyPattern, "");
|
|
111
|
+
const withoutCurrent = withoutLegacy.replace(currentPattern, "");
|
|
112
|
+
const changed = withoutCurrent !== normalized;
|
|
113
|
+
const text = changed ? `${trimTrailingBlankLines(withoutCurrent)}
|
|
114
|
+
` : rawConfig;
|
|
115
|
+
return { text, changed };
|
|
116
|
+
}
|
|
117
|
+
function upsertCodexServerBlock(rawConfig, serverName, serverEntry) {
|
|
118
|
+
const block = serializeCodexServerBlock(serverName, serverEntry);
|
|
119
|
+
const normalized = rawConfig.replace(/\r\n/g, "\n");
|
|
120
|
+
const legacyPattern = new RegExp(String.raw`\n?\[mcp\.servers\.${serverName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}\]\n[\s\S]*?(?=\n\[[^\n]+\]\n|$)`, "g");
|
|
121
|
+
const currentPattern = new RegExp(
|
|
122
|
+
String.raw`\n?\[mcp_servers\.${serverName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}\]\n[\s\S]*?(?=\n\[[^\n]+\]\n|$)`,
|
|
123
|
+
"g"
|
|
124
|
+
);
|
|
125
|
+
const withoutLegacy = normalized.replace(legacyPattern, "");
|
|
126
|
+
const withoutExisting = withoutLegacy.replace(currentPattern, "");
|
|
127
|
+
const trimmed = trimTrailingBlankLines(withoutExisting);
|
|
128
|
+
if (trimmed.length === 0) {
|
|
129
|
+
return block;
|
|
130
|
+
}
|
|
131
|
+
return `${trimmed}
|
|
132
|
+
|
|
133
|
+
${block}`;
|
|
134
|
+
}
|
|
135
|
+
async function readTomlConfigText(configPath) {
|
|
136
|
+
try {
|
|
137
|
+
return await readFile(configPath, "utf8");
|
|
138
|
+
} catch (error) {
|
|
139
|
+
if (error instanceof Error && "code" in error && error.code === "ENOENT") {
|
|
140
|
+
return "";
|
|
141
|
+
}
|
|
142
|
+
throw error;
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
var CodexTOMLConfigWriter = class {
|
|
146
|
+
clientKind = "CodexCLI";
|
|
147
|
+
configuredPath;
|
|
148
|
+
constructor(configuredPath) {
|
|
149
|
+
this.configuredPath = configuredPath;
|
|
150
|
+
}
|
|
151
|
+
async detect(_workspaceRoot, overridePath) {
|
|
152
|
+
const explicitPath = overridePath ?? this.configuredPath;
|
|
153
|
+
if (explicitPath !== void 0) {
|
|
154
|
+
return resolve2(expandHome(explicitPath));
|
|
155
|
+
}
|
|
156
|
+
const codexDir = join2(homedir2(), ".codex");
|
|
157
|
+
return existsSync2(codexDir) ? resolve2(join2(codexDir, "config.toml")) : null;
|
|
158
|
+
}
|
|
159
|
+
async write(serverPath, workspaceRoot, overridePath) {
|
|
160
|
+
const configPath = await this.detect(workspaceRoot, overridePath);
|
|
161
|
+
if (configPath === null) {
|
|
162
|
+
return;
|
|
163
|
+
}
|
|
164
|
+
const rawConfig = await readTomlConfigText(configPath);
|
|
165
|
+
const nextConfig = upsertCodexServerBlock(rawConfig, "fabric", createServerEntry(serverPath));
|
|
166
|
+
await mkdir(dirname(configPath), { recursive: true });
|
|
167
|
+
await atomicWriteText(configPath, nextConfig);
|
|
168
|
+
}
|
|
169
|
+
async remove(serverName, workspaceRoot, overridePath) {
|
|
170
|
+
const configPath = await this.detect(workspaceRoot, overridePath);
|
|
171
|
+
if (configPath === null) {
|
|
172
|
+
return { status: "skipped", message: "no-config-path" };
|
|
173
|
+
}
|
|
174
|
+
if (!existsSync2(configPath)) {
|
|
175
|
+
return { status: "skipped", path: configPath, message: "no-config-file" };
|
|
176
|
+
}
|
|
177
|
+
let rawConfig;
|
|
178
|
+
try {
|
|
179
|
+
rawConfig = await readTomlConfigText(configPath);
|
|
180
|
+
} catch (error) {
|
|
181
|
+
return {
|
|
182
|
+
status: "error",
|
|
183
|
+
path: configPath,
|
|
184
|
+
message: error instanceof Error ? error.message : String(error)
|
|
185
|
+
};
|
|
186
|
+
}
|
|
187
|
+
const { text, changed } = removeCodexServerBlock(rawConfig, serverName);
|
|
188
|
+
if (!changed) {
|
|
189
|
+
return { status: "skipped", path: configPath, message: "not-present" };
|
|
190
|
+
}
|
|
191
|
+
try {
|
|
192
|
+
await mkdir(dirname(configPath), { recursive: true });
|
|
193
|
+
await atomicWriteText(configPath, text);
|
|
194
|
+
return { status: "removed", path: configPath };
|
|
195
|
+
} catch (error) {
|
|
196
|
+
return {
|
|
197
|
+
status: "error",
|
|
198
|
+
path: configPath,
|
|
199
|
+
message: error instanceof Error ? error.message : String(error)
|
|
200
|
+
};
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
};
|
|
204
|
+
|
|
205
|
+
// src/config/resolver.ts
|
|
206
|
+
import { clientPathsSchema, fabricConfigSchema } from "@fenglimg/fabric-shared";
|
|
207
|
+
function hasExplicitPath(clientPaths, key) {
|
|
208
|
+
return typeof clientPaths?.[key] === "string" && clientPaths[key].trim().length > 0;
|
|
209
|
+
}
|
|
210
|
+
function addIfDetected(writers, detected, createWriter, configuredPath) {
|
|
211
|
+
if (configuredPath !== void 0 || detected) {
|
|
212
|
+
writers.push(createWriter(configuredPath));
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
function resolveClients(workspaceRoot, fabricConfig = {}, opts = {}) {
|
|
216
|
+
const clientPaths = fabricConfig.clientPaths;
|
|
217
|
+
const writers = [];
|
|
218
|
+
const claudeMcpScope = opts.claudeMcpScope ?? "project";
|
|
219
|
+
addIfDetected(
|
|
220
|
+
writers,
|
|
221
|
+
existsSync3(join3(homedir3(), ".claude")) || existsSync3(join3(workspaceRoot, ".claude")),
|
|
222
|
+
(configuredPath) => new ClaudeCodeCLIWriter(configuredPath, claudeMcpScope),
|
|
223
|
+
hasExplicitPath(clientPaths, "claudeCodeCLI") ? clientPaths.claudeCodeCLI : void 0
|
|
224
|
+
);
|
|
225
|
+
addIfDetected(
|
|
226
|
+
writers,
|
|
227
|
+
existsSync3(getClaudeDesktopConfigPath()),
|
|
228
|
+
(configuredPath) => new ClaudeCodeDesktopWriter(configuredPath),
|
|
229
|
+
hasExplicitPath(clientPaths, "claudeCodeDesktop") ? clientPaths.claudeCodeDesktop : void 0
|
|
230
|
+
);
|
|
231
|
+
addIfDetected(
|
|
232
|
+
writers,
|
|
233
|
+
existsSync3(join3(workspaceRoot, ".cursor")),
|
|
234
|
+
(configuredPath) => new CursorWriter(configuredPath),
|
|
235
|
+
hasExplicitPath(clientPaths, "cursor") ? clientPaths.cursor : void 0
|
|
236
|
+
);
|
|
237
|
+
addIfDetected(
|
|
238
|
+
writers,
|
|
239
|
+
existsSync3(join3(homedir3(), ".codex")),
|
|
240
|
+
(configuredPath) => new CodexTOMLConfigWriter(configuredPath),
|
|
241
|
+
hasExplicitPath(clientPaths, "codexCLI") ? clientPaths.codexCLI : void 0
|
|
242
|
+
);
|
|
243
|
+
return writers;
|
|
244
|
+
}
|
|
245
|
+
function detectClientSupports(workspaceRoot, fabricConfig = {}) {
|
|
246
|
+
const clientPaths = fabricConfig.clientPaths;
|
|
247
|
+
const claudeDetected = existsSync3(join3(homedir3(), ".claude")) || existsSync3(join3(workspaceRoot, ".claude"));
|
|
248
|
+
const claudeDesktopDetected = existsSync3(getClaudeDesktopConfigPath());
|
|
249
|
+
const cursorDetected = existsSync3(join3(workspaceRoot, ".cursor"));
|
|
250
|
+
const codexDetected = existsSync3(join3(homedir3(), ".codex"));
|
|
251
|
+
return [
|
|
252
|
+
{
|
|
253
|
+
clientKind: "ClaudeCodeCLI",
|
|
254
|
+
label: "Claude Code CLI",
|
|
255
|
+
detected: claudeDetected || hasExplicitPath(clientPaths, "claudeCodeCLI"),
|
|
256
|
+
bootstrapTargetPath: ".fabric/bootstrap/README.md",
|
|
257
|
+
configPath: "project .claude/settings.json",
|
|
258
|
+
capabilities: {
|
|
259
|
+
bootstrap: true,
|
|
260
|
+
mcp: true,
|
|
261
|
+
hook: true,
|
|
262
|
+
skill: true
|
|
263
|
+
},
|
|
264
|
+
installedCapabilities: {
|
|
265
|
+
hook: true,
|
|
266
|
+
skill: true
|
|
267
|
+
}
|
|
268
|
+
},
|
|
269
|
+
{
|
|
270
|
+
clientKind: "ClaudeCodeDesktop",
|
|
271
|
+
label: "Claude Code Desktop",
|
|
272
|
+
detected: claudeDesktopDetected || hasExplicitPath(clientPaths, "claudeCodeDesktop"),
|
|
273
|
+
bootstrapTargetPath: ".fabric/bootstrap/README.md",
|
|
274
|
+
configPath: "desktop Claude config",
|
|
275
|
+
capabilities: {
|
|
276
|
+
bootstrap: true,
|
|
277
|
+
mcp: true,
|
|
278
|
+
hook: false,
|
|
279
|
+
skill: false
|
|
280
|
+
}
|
|
281
|
+
},
|
|
282
|
+
{
|
|
283
|
+
clientKind: "Cursor",
|
|
284
|
+
label: "Cursor",
|
|
285
|
+
detected: cursorDetected || hasExplicitPath(clientPaths, "cursor"),
|
|
286
|
+
bootstrapTargetPath: ".fabric/bootstrap/README.md",
|
|
287
|
+
configPath: ".cursor/mcp.json",
|
|
288
|
+
capabilities: {
|
|
289
|
+
bootstrap: true,
|
|
290
|
+
mcp: true,
|
|
291
|
+
hook: false,
|
|
292
|
+
skill: false
|
|
293
|
+
}
|
|
294
|
+
},
|
|
295
|
+
{
|
|
296
|
+
clientKind: "CodexCLI",
|
|
297
|
+
label: "Codex CLI",
|
|
298
|
+
detected: codexDetected || hasExplicitPath(clientPaths, "codexCLI"),
|
|
299
|
+
bootstrapTargetPath: ".fabric/bootstrap/README.md",
|
|
300
|
+
configPath: "~/.codex/config.toml",
|
|
301
|
+
capabilities: {
|
|
302
|
+
bootstrap: true,
|
|
303
|
+
mcp: true,
|
|
304
|
+
hook: true,
|
|
305
|
+
skill: true
|
|
306
|
+
},
|
|
307
|
+
installedCapabilities: {
|
|
308
|
+
hook: existsSync3(join3(workspaceRoot, ".codex", "hooks.json")),
|
|
309
|
+
// v2/rc.2: v1 client-side init skill removed; skill-installation probes
|
|
310
|
+
// will return once rc.2/3/4 introduce the v2 skills (fabric-archive,
|
|
311
|
+
// fabric-review, fabric-import). Until then there is nothing to probe.
|
|
312
|
+
skill: false
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
];
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
export {
|
|
319
|
+
resolveClients,
|
|
320
|
+
detectClientSupports
|
|
321
|
+
};
|