@fenglimg/fabric-cli 1.2.0 → 1.3.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 +24 -22
- package/dist/{bootstrap-IUL4SAAK.js → bootstrap-3PUKUYTY.js} +4 -2
- package/dist/{chunk-N4DCTOXW.js → chunk-AZRKMFRY.js} +6 -6
- package/dist/{chunk-F2BXHPM5.js → chunk-N7EZORJZ.js} +9 -1
- package/dist/{chunk-6UUPKSDE.js → chunk-Q4LOVXML.js} +13 -8
- package/dist/{chunk-RUQCZA2Q.js → chunk-TKTWHAKV.js} +92 -115
- package/dist/{chunk-VMYPJPKV.js → chunk-VOQKQ6W2.js} +153 -21
- package/dist/{chunk-MDI7523D.js → chunk-XFSQM3LJ.js} +5 -1
- package/dist/{config-3JBB77TX.js → config-GINBGANU.js} +3 -2
- package/dist/index.js +11 -22
- package/dist/{init-3FPLOABB.js → init-T3LGMGAO.js} +170 -54
- package/dist/{ledger-append-XZ5SX4O5.js → ledger-append-DULKJ6Q2.js} +1 -1
- package/dist/pre-commit-IK6SJOPT.js +97 -0
- package/dist/{scan-WKDSKEBB.js → scan-43R3IBLR.js} +2 -2
- package/dist/{sync-meta-THZSEM7Y.js → sync-meta-LKVSO6TS.js} +1 -1
- package/dist/update-AN3FYF2O.js +116 -0
- package/package.json +4 -3
- package/templates/agents-md/AGENTS.md.template +14 -13
- package/templates/agents-md/variants/cocos.md +20 -20
- package/templates/agents-md/variants/next.md +20 -20
- package/templates/agents-md/variants/vite.md +20 -20
- package/templates/bootstrap/CLAUDE.md +3 -5
- package/templates/bootstrap/GEMINI.md +3 -5
- package/templates/bootstrap/codex-AGENTS-header.md +3 -5
- package/templates/bootstrap/cursor-fabric-bootstrap.mdc +11 -11
- package/templates/bootstrap/roo-fabric.md +6 -6
- package/templates/bootstrap/windsurf-fabric.md +6 -6
- package/templates/claude-hooks/agents-md-init-reminder.cjs +18 -18
- package/templates/claude-skills/agents-md-init/SKILL.md +86 -86
- package/templates/fabric/human-lock.json +12 -12
- package/templates/husky/pre-commit +24 -24
- package/dist/pre-commit-CJ7EDKJK.js +0 -59
- package/dist/{doctor-5KJGOV2P.js → doctor-QTSG2RWF.js} +3 -3
- package/dist/{serve-MMN4GYLM.js → serve-4J2CQY25.js} +4 -4
package/README.md
CHANGED
|
@@ -1,24 +1,26 @@
|
|
|
1
|
-
# @fenglimg/fabric-cli
|
|
2
|
-
|
|
3
|
-
`fabric` is the primary CLI binary for Fabric. `fab` is a permanent alias, so you can use either binary.
|
|
4
|
-
|
|
5
|
-
## Quick Start
|
|
6
|
-
|
|
7
|
-
1. Install dependencies from the monorepo root with `pnpm install`.
|
|
8
|
-
2. Build the CLI with `pnpm --filter @fenglimg/fabric-cli build`.
|
|
9
|
-
3. Run `fabric init` in the target project for the one-shot setup flow.
|
|
10
|
-
4. Start `fabric serve` and verify `fab_get_rules` in your client.
|
|
11
|
-
|
|
1
|
+
# @fenglimg/fabric-cli
|
|
2
|
+
|
|
3
|
+
`fabric` is the primary CLI binary for Fabric. `fab` is a permanent alias, so you can use either binary.
|
|
4
|
+
|
|
5
|
+
## Quick Start
|
|
6
|
+
|
|
7
|
+
1. Install dependencies from the monorepo root with `pnpm install`.
|
|
8
|
+
2. Build the CLI with `pnpm --filter @fenglimg/fabric-cli build`.
|
|
9
|
+
3. Run `fabric init` in the target project for the one-shot setup flow.
|
|
10
|
+
4. Start `fabric serve` and verify `fab_get_rules` in your client.
|
|
11
|
+
|
|
12
12
|
`fabric init` auto-runs `bootstrap install`, `config install`, and `hooks install`. Use them standalone only for targeted re-runs.
|
|
13
13
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
- `fabric
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
- `fabric
|
|
14
|
+
`fabric bootstrap install` refreshes the internal bootstrap guide at `.fabric/bootstrap/README.md`. It does not generate root `AGENTS.md`, `CLAUDE.md`, or `GEMINI.md`.
|
|
15
|
+
|
|
16
|
+
## Common Commands
|
|
17
|
+
|
|
18
|
+
- `fabric init`
|
|
19
|
+
- `fabric serve`
|
|
20
|
+
- `fabric doctor --audit`
|
|
21
|
+
|
|
22
|
+
## Advanced Commands
|
|
23
|
+
|
|
24
|
+
- `fabric bootstrap install`
|
|
25
|
+
- `fabric config install`
|
|
26
|
+
- `fabric hooks install`
|
|
@@ -3,9 +3,11 @@ import {
|
|
|
3
3
|
bootstrapCommand,
|
|
4
4
|
bootstrap_default,
|
|
5
5
|
installBootstrap
|
|
6
|
-
} from "./chunk-
|
|
7
|
-
import "./chunk-
|
|
6
|
+
} from "./chunk-TKTWHAKV.js";
|
|
7
|
+
import "./chunk-AZRKMFRY.js";
|
|
8
|
+
import "./chunk-VOQKQ6W2.js";
|
|
8
9
|
import "./chunk-AEOYCVBG.js";
|
|
10
|
+
import "./chunk-WWNXR34K.js";
|
|
9
11
|
import "./chunk-6ICJICVU.js";
|
|
10
12
|
export {
|
|
11
13
|
bootstrapCommand,
|
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
createDebugLogger,
|
|
4
|
+
readFabricConfig,
|
|
5
|
+
resolveDevMode
|
|
6
|
+
} from "./chunk-AEOYCVBG.js";
|
|
2
7
|
import {
|
|
3
8
|
displayWidth,
|
|
4
9
|
padEnd,
|
|
5
10
|
paint,
|
|
6
11
|
symbol
|
|
7
12
|
} from "./chunk-WWNXR34K.js";
|
|
8
|
-
import {
|
|
9
|
-
createDebugLogger,
|
|
10
|
-
readFabricConfig,
|
|
11
|
-
resolveDevMode
|
|
12
|
-
} from "./chunk-AEOYCVBG.js";
|
|
13
13
|
import {
|
|
14
14
|
t
|
|
15
15
|
} from "./chunk-6ICJICVU.js";
|
|
@@ -45,7 +45,7 @@ function createScanReport(targetInput = process.cwd(), fabricConfig) {
|
|
|
45
45
|
const framework = detectFramework(target);
|
|
46
46
|
const readmeQuality = getReadmeQuality(target);
|
|
47
47
|
const hasContributing = existsSync(join(target, "CONTRIBUTING.md"));
|
|
48
|
-
const hasExistingFabric = existsSync(join(target, "
|
|
48
|
+
const hasExistingFabric = existsSync(join(target, ".fabric", "bootstrap", "README.md")) || existsSync(join(target, ".fabric"));
|
|
49
49
|
const walkResult = walkFiles(target, resolveIgnores(fabricConfig));
|
|
50
50
|
return {
|
|
51
51
|
target,
|
|
@@ -95,7 +95,7 @@ function hasMatchingTailEntry(target, entry) {
|
|
|
95
95
|
if (!existsSync(ledgerPath)) {
|
|
96
96
|
return false;
|
|
97
97
|
}
|
|
98
|
-
const tail = readFileSync(ledgerPath, "utf8").trim().split(/\r?\n/).filter(Boolean).
|
|
98
|
+
const tail = readFileSync(ledgerPath, "utf8").trim().split(/\r?\n/).filter(Boolean).reverse().find((line) => isLedgerEntryLine(line));
|
|
99
99
|
if (!tail) {
|
|
100
100
|
return false;
|
|
101
101
|
}
|
|
@@ -106,6 +106,14 @@ function hasMatchingTailEntry(target, entry) {
|
|
|
106
106
|
return false;
|
|
107
107
|
}
|
|
108
108
|
}
|
|
109
|
+
function isLedgerEntryLine(line) {
|
|
110
|
+
try {
|
|
111
|
+
const parsed = JSON.parse(line);
|
|
112
|
+
return parsed.kind !== "mcp-event";
|
|
113
|
+
} catch {
|
|
114
|
+
return false;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
109
117
|
function normalizeDiffStat(diffStat) {
|
|
110
118
|
if (typeof diffStat !== "string") {
|
|
111
119
|
return "";
|
|
@@ -59,7 +59,10 @@ function computeAgentsMeta(target) {
|
|
|
59
59
|
const existingByFile = indexExistingNodesByFile(existingMeta);
|
|
60
60
|
const agentsFiles = findFabricAgentsFiles(target);
|
|
61
61
|
const nodes = {};
|
|
62
|
-
const bootstrapNode = createBootstrapNode(
|
|
62
|
+
const bootstrapNode = createBootstrapNode(
|
|
63
|
+
target,
|
|
64
|
+
existingByFile.get(".fabric/bootstrap/README.md")?.node ?? existingByFile.get("AGENTS.md")?.node
|
|
65
|
+
);
|
|
63
66
|
if (bootstrapNode !== void 0) {
|
|
64
67
|
nodes.L0 = bootstrapNode;
|
|
65
68
|
}
|
|
@@ -137,7 +140,7 @@ function indexExistingNodesByFile(existingMeta) {
|
|
|
137
140
|
return byFile;
|
|
138
141
|
}
|
|
139
142
|
function deriveNodeId(file) {
|
|
140
|
-
if (file === "
|
|
143
|
+
if (file === ".fabric/bootstrap/README.md") {
|
|
141
144
|
return "L0";
|
|
142
145
|
}
|
|
143
146
|
const layer = deriveLayer(file);
|
|
@@ -158,20 +161,22 @@ function createDefaultNodeMeta(file) {
|
|
|
158
161
|
};
|
|
159
162
|
}
|
|
160
163
|
function createBootstrapNode(target, existing) {
|
|
161
|
-
const bootstrapPath = join(target, "
|
|
162
|
-
|
|
164
|
+
const bootstrapPath = join(target, ".fabric", "bootstrap", "README.md");
|
|
165
|
+
const legacyBootstrapPath = join(target, "AGENTS.md");
|
|
166
|
+
const sourcePath = existsSync(bootstrapPath) ? bootstrapPath : existsSync(legacyBootstrapPath) ? legacyBootstrapPath : void 0;
|
|
167
|
+
if (sourcePath === void 0) {
|
|
163
168
|
return void 0;
|
|
164
169
|
}
|
|
165
|
-
const hash = sha256(readFileSync(
|
|
170
|
+
const hash = sha256(readFileSync(sourcePath, "utf8"));
|
|
166
171
|
return {
|
|
167
|
-
...createDefaultNodeMeta("
|
|
172
|
+
...createDefaultNodeMeta(".fabric/bootstrap/README.md"),
|
|
168
173
|
...existing,
|
|
169
|
-
file: "
|
|
174
|
+
file: ".fabric/bootstrap/README.md",
|
|
170
175
|
hash
|
|
171
176
|
};
|
|
172
177
|
}
|
|
173
178
|
function deriveScopeGlob(file) {
|
|
174
|
-
if (file === "
|
|
179
|
+
if (file === ".fabric/bootstrap/README.md") {
|
|
175
180
|
return "**";
|
|
176
181
|
}
|
|
177
182
|
const stem = getMirrorRelativeStem(file);
|
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
createScanReport
|
|
4
|
+
} from "./chunk-AZRKMFRY.js";
|
|
2
5
|
import {
|
|
3
6
|
resolveClients
|
|
4
|
-
} from "./chunk-
|
|
7
|
+
} from "./chunk-VOQKQ6W2.js";
|
|
5
8
|
import {
|
|
6
9
|
readFabricConfig
|
|
7
10
|
} from "./chunk-AEOYCVBG.js";
|
|
@@ -10,10 +13,87 @@ import {
|
|
|
10
13
|
} from "./chunk-6ICJICVU.js";
|
|
11
14
|
|
|
12
15
|
// src/commands/bootstrap.ts
|
|
16
|
+
import { resolve as resolve2 } from "path";
|
|
17
|
+
import { defineCommand } from "citty";
|
|
18
|
+
|
|
19
|
+
// src/bootstrap-guide.ts
|
|
13
20
|
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
|
|
14
|
-
import { dirname, join, parse, resolve } from "path";
|
|
21
|
+
import { dirname, isAbsolute, join, parse, resolve } from "path";
|
|
15
22
|
import { fileURLToPath } from "url";
|
|
16
|
-
|
|
23
|
+
var AGENTS_TEMPLATE_BY_FRAMEWORK = {
|
|
24
|
+
"cocos-creator": "templates/agents-md/variants/cocos.md",
|
|
25
|
+
vite: "templates/agents-md/variants/vite.md",
|
|
26
|
+
next: "templates/agents-md/variants/next.md"
|
|
27
|
+
};
|
|
28
|
+
var FABRIC_GUIDE_PATH = ".fabric/bootstrap/README.md";
|
|
29
|
+
function buildFabricBootstrapGuide(target) {
|
|
30
|
+
const workspaceRoot = normalizeTarget(target);
|
|
31
|
+
const scanReport = createScanReport(workspaceRoot);
|
|
32
|
+
const template = readFileSync(findBootstrapTemplatePath(scanReport.framework.kind), "utf8");
|
|
33
|
+
const packageName = readPackageName(workspaceRoot) ?? parse(workspaceRoot).base;
|
|
34
|
+
return ensureTrailingNewline(
|
|
35
|
+
template.replaceAll("{ projectName }", packageName).replaceAll("{ frameworkKind }", scanReport.framework.kind)
|
|
36
|
+
);
|
|
37
|
+
}
|
|
38
|
+
function ensureFabricBootstrapGuide(workspaceRoot, force) {
|
|
39
|
+
const guidePath = resolve(workspaceRoot, FABRIC_GUIDE_PATH);
|
|
40
|
+
if (existsSync(guidePath) && !force) {
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
mkdirSync(dirname(guidePath), { recursive: true });
|
|
44
|
+
writeFileSync(guidePath, buildFabricBootstrapGuide(workspaceRoot), "utf8");
|
|
45
|
+
}
|
|
46
|
+
function findBootstrapTemplatePath(frameworkKind) {
|
|
47
|
+
const relativePath = AGENTS_TEMPLATE_BY_FRAMEWORK[frameworkKind] ?? "templates/agents-md/AGENTS.md.template";
|
|
48
|
+
return findTemplatePath(relativePath);
|
|
49
|
+
}
|
|
50
|
+
function readPackageName(target) {
|
|
51
|
+
const packageJsonPath = join(target, "package.json");
|
|
52
|
+
if (!existsSync(packageJsonPath)) {
|
|
53
|
+
return void 0;
|
|
54
|
+
}
|
|
55
|
+
try {
|
|
56
|
+
const packageJson = JSON.parse(readFileSync(packageJsonPath, "utf8"));
|
|
57
|
+
return packageJson.name;
|
|
58
|
+
} catch {
|
|
59
|
+
return void 0;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
function findTemplatePath(relativePath) {
|
|
63
|
+
const currentModuleDir = dirname(fileURLToPath(import.meta.url));
|
|
64
|
+
const candidates = [
|
|
65
|
+
...templateCandidatesFrom(process.cwd(), relativePath),
|
|
66
|
+
...templateCandidatesFrom(currentModuleDir, relativePath)
|
|
67
|
+
];
|
|
68
|
+
for (const candidate of candidates) {
|
|
69
|
+
if (existsSync(candidate)) {
|
|
70
|
+
return candidate;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
throw new Error(t("cli.shared.template-not-found", { path: relativePath }));
|
|
74
|
+
}
|
|
75
|
+
function templateCandidatesFrom(start, relativePath) {
|
|
76
|
+
const candidates = [];
|
|
77
|
+
let current = resolve(start);
|
|
78
|
+
while (true) {
|
|
79
|
+
candidates.push(join(current, ...relativePath.split("/")));
|
|
80
|
+
const parent = dirname(current);
|
|
81
|
+
if (parent === current || parse(current).root === current) {
|
|
82
|
+
break;
|
|
83
|
+
}
|
|
84
|
+
current = parent;
|
|
85
|
+
}
|
|
86
|
+
return candidates.reverse();
|
|
87
|
+
}
|
|
88
|
+
function ensureTrailingNewline(content) {
|
|
89
|
+
return content.endsWith("\n") ? content : `${content}
|
|
90
|
+
`;
|
|
91
|
+
}
|
|
92
|
+
function normalizeTarget(targetInput) {
|
|
93
|
+
return isAbsolute(targetInput) ? targetInput : resolve(process.cwd(), targetInput);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// src/commands/bootstrap.ts
|
|
17
97
|
var CLIENT_ALIASES = {
|
|
18
98
|
claude: "claude",
|
|
19
99
|
"claude-code": "claude",
|
|
@@ -34,22 +114,6 @@ var CLIENT_ALIASES = {
|
|
|
34
114
|
"codex-cli": "codex",
|
|
35
115
|
codexcli: "codex"
|
|
36
116
|
};
|
|
37
|
-
var CLIENT_TEMPLATE_MAP = {
|
|
38
|
-
claude: "templates/bootstrap/CLAUDE.md",
|
|
39
|
-
cursor: "templates/bootstrap/cursor-fabric-bootstrap.mdc",
|
|
40
|
-
windsurf: "templates/bootstrap/windsurf-fabric.md",
|
|
41
|
-
roo: "templates/bootstrap/roo-fabric.md",
|
|
42
|
-
gemini: "templates/bootstrap/GEMINI.md",
|
|
43
|
-
codex: "templates/bootstrap/codex-AGENTS-header.md"
|
|
44
|
-
};
|
|
45
|
-
var CLIENT_TARGET_MAP = {
|
|
46
|
-
claude: "CLAUDE.md",
|
|
47
|
-
cursor: ".cursor/rules/fabric-bootstrap.mdc",
|
|
48
|
-
windsurf: ".windsurf/rules/fabric.md",
|
|
49
|
-
roo: ".roo/rules/fabric.md",
|
|
50
|
-
gemini: "GEMINI.md",
|
|
51
|
-
codex: "AGENTS.md"
|
|
52
|
-
};
|
|
53
117
|
var bootstrapCommand = defineCommand({
|
|
54
118
|
meta: {
|
|
55
119
|
name: "bootstrap",
|
|
@@ -100,20 +164,20 @@ var bootstrapCommand = defineCommand({
|
|
|
100
164
|
});
|
|
101
165
|
var bootstrap_default = bootstrapCommand;
|
|
102
166
|
async function installBootstrap(target, options = {}) {
|
|
103
|
-
const workspaceRoot =
|
|
167
|
+
const workspaceRoot = resolve2(target);
|
|
104
168
|
const fabricConfig = readFabricConfig(workspaceRoot);
|
|
105
169
|
const targets = resolveBootstrapTargets(workspaceRoot, fabricConfig, options.clients);
|
|
106
170
|
const installed = [];
|
|
107
171
|
const skipped = [];
|
|
108
172
|
const details = [];
|
|
173
|
+
ensureFabricBootstrapGuide(workspaceRoot, options.force);
|
|
109
174
|
for (const bootstrapTarget of targets) {
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
skipped
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
}
|
|
175
|
+
details.push({
|
|
176
|
+
client: bootstrapTarget.client,
|
|
177
|
+
path: resolve2(workspaceRoot, FABRIC_GUIDE_PATH),
|
|
178
|
+
action: "skipped"
|
|
179
|
+
});
|
|
180
|
+
skipped.push(bootstrapTarget.client);
|
|
117
181
|
}
|
|
118
182
|
return { installed, skipped, details };
|
|
119
183
|
}
|
|
@@ -181,96 +245,9 @@ function mapBootstrapClientToClientKind(client) {
|
|
|
181
245
|
return "CodexCLI";
|
|
182
246
|
}
|
|
183
247
|
}
|
|
184
|
-
function installBootstrapTarget(target, workspaceRoot, options) {
|
|
185
|
-
const targetPath = resolve(workspaceRoot, CLIENT_TARGET_MAP[target.bootstrapClient]);
|
|
186
|
-
const templatePath = findTemplatePath(CLIENT_TEMPLATE_MAP[target.bootstrapClient]);
|
|
187
|
-
const template = readFileSync(templatePath, "utf8");
|
|
188
|
-
mkdirSync(dirname(targetPath), { recursive: true });
|
|
189
|
-
if (target.bootstrapClient === "codex") {
|
|
190
|
-
return {
|
|
191
|
-
client: target.client,
|
|
192
|
-
path: targetPath,
|
|
193
|
-
action: writeCodexBootstrap(targetPath, template, options.force)
|
|
194
|
-
};
|
|
195
|
-
}
|
|
196
|
-
const existed = existsSync(targetPath);
|
|
197
|
-
writeFileSync(targetPath, ensureTrailingNewline(template), "utf8");
|
|
198
|
-
return {
|
|
199
|
-
client: target.client,
|
|
200
|
-
path: targetPath,
|
|
201
|
-
action: existed ? "overwritten" : "installed"
|
|
202
|
-
};
|
|
203
|
-
}
|
|
204
|
-
function writeCodexBootstrap(targetPath, template, force) {
|
|
205
|
-
const nextContent = ensureTrailingNewline(template);
|
|
206
|
-
if (!existsSync(targetPath)) {
|
|
207
|
-
writeFileSync(targetPath, nextContent, "utf8");
|
|
208
|
-
return "installed";
|
|
209
|
-
}
|
|
210
|
-
const existing = readFileSync(targetPath, "utf8");
|
|
211
|
-
if (existing.includes("# Fabric Bootstrap")) {
|
|
212
|
-
if (!force) {
|
|
213
|
-
return "skipped";
|
|
214
|
-
}
|
|
215
|
-
const remainder = stripExistingCodexBootstrap(existing, nextContent);
|
|
216
|
-
writeFileSync(targetPath, joinBootstrapSections(nextContent, remainder), "utf8");
|
|
217
|
-
return "overwritten";
|
|
218
|
-
}
|
|
219
|
-
writeFileSync(targetPath, joinBootstrapSections(nextContent, existing), "utf8");
|
|
220
|
-
return force ? "overwritten" : "prepended";
|
|
221
|
-
}
|
|
222
|
-
function stripExistingCodexBootstrap(existing, template) {
|
|
223
|
-
if (existing.startsWith(template)) {
|
|
224
|
-
return existing.slice(template.length).replace(/^\n+/, "");
|
|
225
|
-
}
|
|
226
|
-
if (!existing.startsWith("# Fabric Bootstrap")) {
|
|
227
|
-
return existing;
|
|
228
|
-
}
|
|
229
|
-
const nextTopLevelHeadingIndex = existing.indexOf("\n# ", "# Fabric Bootstrap".length);
|
|
230
|
-
if (nextTopLevelHeadingIndex === -1) {
|
|
231
|
-
return "";
|
|
232
|
-
}
|
|
233
|
-
return existing.slice(nextTopLevelHeadingIndex + 1).replace(/^\n+/, "");
|
|
234
|
-
}
|
|
235
|
-
function joinBootstrapSections(header, body) {
|
|
236
|
-
if (body.trim().length === 0) {
|
|
237
|
-
return header;
|
|
238
|
-
}
|
|
239
|
-
const separator = body.startsWith("\n") ? "" : "\n";
|
|
240
|
-
return `${header}${separator}${body}`;
|
|
241
|
-
}
|
|
242
|
-
function ensureTrailingNewline(content) {
|
|
243
|
-
return content.endsWith("\n") ? content : `${content}
|
|
244
|
-
`;
|
|
245
|
-
}
|
|
246
|
-
function findTemplatePath(relativePath) {
|
|
247
|
-
const currentModuleDir = dirname(fileURLToPath(import.meta.url));
|
|
248
|
-
const candidates = [
|
|
249
|
-
...templateCandidatesFrom(process.cwd(), relativePath),
|
|
250
|
-
...templateCandidatesFrom(currentModuleDir, relativePath)
|
|
251
|
-
];
|
|
252
|
-
for (const candidate of candidates) {
|
|
253
|
-
if (existsSync(candidate)) {
|
|
254
|
-
return candidate;
|
|
255
|
-
}
|
|
256
|
-
}
|
|
257
|
-
throw new Error(t("cli.shared.template-not-found", { path: relativePath }));
|
|
258
|
-
}
|
|
259
|
-
function templateCandidatesFrom(start, relativePath) {
|
|
260
|
-
const candidates = [];
|
|
261
|
-
let current = resolve(start);
|
|
262
|
-
while (true) {
|
|
263
|
-
candidates.push(join(current, ...relativePath.split("/")));
|
|
264
|
-
const parent = dirname(current);
|
|
265
|
-
if (parent === current || parse(current).root === current) {
|
|
266
|
-
break;
|
|
267
|
-
}
|
|
268
|
-
current = parent;
|
|
269
|
-
}
|
|
270
|
-
return candidates.reverse();
|
|
271
|
-
}
|
|
272
248
|
|
|
273
249
|
export {
|
|
250
|
+
buildFabricBootstrapGuide,
|
|
274
251
|
bootstrapCommand,
|
|
275
252
|
bootstrap_default,
|
|
276
253
|
installBootstrap
|
|
@@ -179,7 +179,6 @@ import { existsSync as existsSync3 } from "fs";
|
|
|
179
179
|
import { mkdir as mkdir2, readFile as readFile2, writeFile as writeFile2 } from "fs/promises";
|
|
180
180
|
import { dirname as dirname2, join as join3, resolve as resolve3 } from "path";
|
|
181
181
|
import { homedir as homedir3 } from "os";
|
|
182
|
-
import * as TOML from "@iarna/toml";
|
|
183
182
|
function expandHome2(filePath) {
|
|
184
183
|
if (filePath === "~") {
|
|
185
184
|
return homedir3();
|
|
@@ -189,31 +188,59 @@ function expandHome2(filePath) {
|
|
|
189
188
|
}
|
|
190
189
|
return filePath;
|
|
191
190
|
}
|
|
192
|
-
function
|
|
193
|
-
return
|
|
191
|
+
function escapeTomlString(value) {
|
|
192
|
+
return JSON.stringify(value);
|
|
194
193
|
}
|
|
195
|
-
|
|
194
|
+
function serializeTomlStringArray(values) {
|
|
195
|
+
return `[${values.map((value) => escapeTomlString(value)).join(", ")}]`;
|
|
196
|
+
}
|
|
197
|
+
function serializeTomlInlineTable(values) {
|
|
198
|
+
const entries = Object.entries(values).sort(([left], [right]) => left.localeCompare(right)).map(([key, value]) => `${key} = ${escapeTomlString(value)}`);
|
|
199
|
+
return `{ ${entries.join(", ")} }`;
|
|
200
|
+
}
|
|
201
|
+
function serializeCodexServerBlock(serverName, serverEntry) {
|
|
202
|
+
const lines = [
|
|
203
|
+
`[mcp_servers.${serverName}]`,
|
|
204
|
+
`command = ${escapeTomlString(serverEntry.command)}`,
|
|
205
|
+
`args = ${serializeTomlStringArray(serverEntry.args)}`
|
|
206
|
+
];
|
|
207
|
+
if (serverEntry.env !== void 0 && Object.keys(serverEntry.env).length > 0) {
|
|
208
|
+
lines.push(`env = ${serializeTomlInlineTable(serverEntry.env)}`);
|
|
209
|
+
}
|
|
210
|
+
return `${lines.join("\n")}
|
|
211
|
+
`;
|
|
212
|
+
}
|
|
213
|
+
function trimTrailingBlankLines(value) {
|
|
214
|
+
return value.replace(/\s+$/u, "");
|
|
215
|
+
}
|
|
216
|
+
function upsertCodexServerBlock(rawConfig, serverName, serverEntry) {
|
|
217
|
+
const block = serializeCodexServerBlock(serverName, serverEntry);
|
|
218
|
+
const normalized = rawConfig.replace(/\r\n/g, "\n");
|
|
219
|
+
const legacyPattern = new RegExp(String.raw`\n?\[mcp\.servers\.${serverName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}\]\n[\s\S]*?(?=\n\[[^\n]+\]\n|$)`, "g");
|
|
220
|
+
const currentPattern = new RegExp(
|
|
221
|
+
String.raw`\n?\[mcp_servers\.${serverName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}\]\n[\s\S]*?(?=\n\[[^\n]+\]\n|$)`,
|
|
222
|
+
"g"
|
|
223
|
+
);
|
|
224
|
+
const withoutLegacy = normalized.replace(legacyPattern, "");
|
|
225
|
+
const withoutExisting = withoutLegacy.replace(currentPattern, "");
|
|
226
|
+
const trimmed = trimTrailingBlankLines(withoutExisting);
|
|
227
|
+
if (trimmed.length === 0) {
|
|
228
|
+
return block;
|
|
229
|
+
}
|
|
230
|
+
return `${trimmed}
|
|
231
|
+
|
|
232
|
+
${block}`;
|
|
233
|
+
}
|
|
234
|
+
async function readTomlConfigText(configPath) {
|
|
196
235
|
try {
|
|
197
|
-
|
|
198
|
-
if (raw.trim().length === 0) {
|
|
199
|
-
return {};
|
|
200
|
-
}
|
|
201
|
-
return TOML.parse(raw);
|
|
236
|
+
return await readFile2(configPath, "utf8");
|
|
202
237
|
} catch (error) {
|
|
203
238
|
if (error instanceof Error && "code" in error && error.code === "ENOENT") {
|
|
204
|
-
return
|
|
239
|
+
return "";
|
|
205
240
|
}
|
|
206
241
|
throw error;
|
|
207
242
|
}
|
|
208
243
|
}
|
|
209
|
-
function mergeCodexServer(config, serverEntry) {
|
|
210
|
-
const mcp = asObject(config.mcp);
|
|
211
|
-
const servers = asObject(mcp.servers);
|
|
212
|
-
servers.fabric = serverEntry;
|
|
213
|
-
mcp.servers = servers;
|
|
214
|
-
config.mcp = mcp;
|
|
215
|
-
return config;
|
|
216
|
-
}
|
|
217
244
|
var CodexTOMLConfigWriter = class {
|
|
218
245
|
clientKind = "CodexCLI";
|
|
219
246
|
configuredPath;
|
|
@@ -233,9 +260,10 @@ var CodexTOMLConfigWriter = class {
|
|
|
233
260
|
if (configPath === null) {
|
|
234
261
|
return;
|
|
235
262
|
}
|
|
236
|
-
const
|
|
263
|
+
const rawConfig = await readTomlConfigText(configPath);
|
|
264
|
+
const nextConfig = upsertCodexServerBlock(rawConfig, "fabric", createServerEntry(serverPath));
|
|
237
265
|
await mkdir2(dirname2(configPath), { recursive: true });
|
|
238
|
-
await writeFile2(configPath,
|
|
266
|
+
await writeFile2(configPath, nextConfig, "utf8");
|
|
239
267
|
}
|
|
240
268
|
};
|
|
241
269
|
|
|
@@ -296,7 +324,111 @@ function resolveClients(workspaceRoot, fabricConfig = {}) {
|
|
|
296
324
|
);
|
|
297
325
|
return writers;
|
|
298
326
|
}
|
|
327
|
+
function detectClientSupports(workspaceRoot, fabricConfig = {}) {
|
|
328
|
+
const clientPaths = fabricConfig.clientPaths;
|
|
329
|
+
const claudeDetected = existsSync4(join4(homedir4(), ".claude")) || existsSync4(join4(workspaceRoot, ".claude"));
|
|
330
|
+
const claudeDesktopDetected = existsSync4(getClaudeDesktopConfigPath());
|
|
331
|
+
const cursorDetected = existsSync4(join4(workspaceRoot, ".cursor"));
|
|
332
|
+
const windsurfDetected = existsSync4(join4(workspaceRoot, ".windsurf"));
|
|
333
|
+
const rooDetected = existsSync4(join4(workspaceRoot, ".roo"));
|
|
334
|
+
const geminiDetected = existsSync4(join4(homedir4(), ".gemini")) || existsSync4(join4(workspaceRoot, "GEMINI.md"));
|
|
335
|
+
const codexDetected = existsSync4(join4(homedir4(), ".codex"));
|
|
336
|
+
return [
|
|
337
|
+
{
|
|
338
|
+
clientKind: "ClaudeCodeCLI",
|
|
339
|
+
label: "Claude Code CLI",
|
|
340
|
+
detected: claudeDetected || hasExplicitPath(clientPaths, "claudeCodeCLI"),
|
|
341
|
+
bootstrapTargetPath: ".fabric/bootstrap/README.md",
|
|
342
|
+
configPath: "project .claude/settings.json",
|
|
343
|
+
capabilities: {
|
|
344
|
+
bootstrap: true,
|
|
345
|
+
mcp: true,
|
|
346
|
+
hook: true,
|
|
347
|
+
skill: true
|
|
348
|
+
}
|
|
349
|
+
},
|
|
350
|
+
{
|
|
351
|
+
clientKind: "ClaudeCodeDesktop",
|
|
352
|
+
label: "Claude Code Desktop",
|
|
353
|
+
detected: claudeDesktopDetected || hasExplicitPath(clientPaths, "claudeCodeDesktop"),
|
|
354
|
+
bootstrapTargetPath: ".fabric/bootstrap/README.md",
|
|
355
|
+
configPath: "desktop Claude config",
|
|
356
|
+
capabilities: {
|
|
357
|
+
bootstrap: true,
|
|
358
|
+
mcp: true,
|
|
359
|
+
hook: false,
|
|
360
|
+
skill: false
|
|
361
|
+
}
|
|
362
|
+
},
|
|
363
|
+
{
|
|
364
|
+
clientKind: "Cursor",
|
|
365
|
+
label: "Cursor",
|
|
366
|
+
detected: cursorDetected || hasExplicitPath(clientPaths, "cursor"),
|
|
367
|
+
bootstrapTargetPath: ".fabric/bootstrap/README.md",
|
|
368
|
+
configPath: ".cursor/mcp.json",
|
|
369
|
+
capabilities: {
|
|
370
|
+
bootstrap: true,
|
|
371
|
+
mcp: true,
|
|
372
|
+
hook: false,
|
|
373
|
+
skill: false
|
|
374
|
+
}
|
|
375
|
+
},
|
|
376
|
+
{
|
|
377
|
+
clientKind: "Windsurf",
|
|
378
|
+
label: "Windsurf",
|
|
379
|
+
detected: windsurfDetected || hasExplicitPath(clientPaths, "windsurf"),
|
|
380
|
+
bootstrapTargetPath: ".fabric/bootstrap/README.md",
|
|
381
|
+
configPath: ".windsurf/mcp.json",
|
|
382
|
+
capabilities: {
|
|
383
|
+
bootstrap: true,
|
|
384
|
+
mcp: true,
|
|
385
|
+
hook: false,
|
|
386
|
+
skill: false
|
|
387
|
+
}
|
|
388
|
+
},
|
|
389
|
+
{
|
|
390
|
+
clientKind: "RooCode",
|
|
391
|
+
label: "Roo Code",
|
|
392
|
+
detected: rooDetected || hasExplicitPath(clientPaths, "rooCode"),
|
|
393
|
+
bootstrapTargetPath: ".fabric/bootstrap/README.md",
|
|
394
|
+
configPath: ".roo/mcp.json",
|
|
395
|
+
capabilities: {
|
|
396
|
+
bootstrap: true,
|
|
397
|
+
mcp: true,
|
|
398
|
+
hook: false,
|
|
399
|
+
skill: false
|
|
400
|
+
}
|
|
401
|
+
},
|
|
402
|
+
{
|
|
403
|
+
clientKind: "GeminiCLI",
|
|
404
|
+
label: "Gemini CLI",
|
|
405
|
+
detected: geminiDetected || hasExplicitPath(clientPaths, "geminiCLI"),
|
|
406
|
+
bootstrapTargetPath: ".fabric/bootstrap/README.md",
|
|
407
|
+
configPath: "~/.gemini/settings.json",
|
|
408
|
+
capabilities: {
|
|
409
|
+
bootstrap: true,
|
|
410
|
+
mcp: true,
|
|
411
|
+
hook: false,
|
|
412
|
+
skill: false
|
|
413
|
+
}
|
|
414
|
+
},
|
|
415
|
+
{
|
|
416
|
+
clientKind: "CodexCLI",
|
|
417
|
+
label: "Codex CLI",
|
|
418
|
+
detected: codexDetected || hasExplicitPath(clientPaths, "codexCLI"),
|
|
419
|
+
bootstrapTargetPath: ".fabric/bootstrap/README.md",
|
|
420
|
+
configPath: "~/.codex/config.toml",
|
|
421
|
+
capabilities: {
|
|
422
|
+
bootstrap: true,
|
|
423
|
+
mcp: true,
|
|
424
|
+
hook: false,
|
|
425
|
+
skill: false
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
];
|
|
429
|
+
}
|
|
299
430
|
|
|
300
431
|
export {
|
|
301
|
-
resolveClients
|
|
432
|
+
resolveClients,
|
|
433
|
+
detectClientSupports
|
|
302
434
|
};
|
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
3
|
resolveClients
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-VOQKQ6W2.js";
|
|
5
|
+
import {
|
|
6
|
+
hooksCommand
|
|
7
|
+
} from "./chunk-YDZJRLHL.js";
|
|
5
8
|
import {
|
|
6
9
|
t
|
|
7
10
|
} from "./chunk-6ICJICVU.js";
|
|
@@ -73,6 +76,7 @@ var configCmd = defineCommand({
|
|
|
73
76
|
description: t("cli.config.description")
|
|
74
77
|
},
|
|
75
78
|
subCommands: {
|
|
79
|
+
hooks: hooksCommand,
|
|
76
80
|
install: defineCommand({
|
|
77
81
|
meta: {
|
|
78
82
|
name: "install",
|
|
@@ -4,8 +4,9 @@ import {
|
|
|
4
4
|
config_default,
|
|
5
5
|
installMcpClients,
|
|
6
6
|
parseClientFilter
|
|
7
|
-
} from "./chunk-
|
|
8
|
-
import "./chunk-
|
|
7
|
+
} from "./chunk-XFSQM3LJ.js";
|
|
8
|
+
import "./chunk-VOQKQ6W2.js";
|
|
9
|
+
import "./chunk-YDZJRLHL.js";
|
|
9
10
|
import "./chunk-6ICJICVU.js";
|
|
10
11
|
export {
|
|
11
12
|
configCmd,
|