@fenglimg/fabric-cli 1.0.0 → 1.2.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 +24 -0
- package/dist/bootstrap-IUL4SAAK.js +14 -0
- package/dist/{chunk-5BSTO745.js → chunk-6UUPKSDE.js} +70 -31
- package/dist/{config-PXEEXWLM.js → chunk-MDI7523D.js} +49 -17
- package/dist/{chunk-DKQ3HOTK.js → chunk-N4DCTOXW.js} +23 -9
- package/dist/{bootstrap-PMIA4W6G.js → chunk-RUQCZA2Q.js} +110 -33
- package/dist/chunk-YDZJRLHL.js +155 -0
- package/dist/config-3JBB77TX.js +15 -0
- package/dist/doctor-5KJGOV2P.js +125 -0
- package/dist/hooks-ZSWVH2JD.js +12 -0
- package/dist/index.js +30 -14
- package/dist/init-3FPLOABB.js +1383 -0
- package/dist/{pre-commit-IEIXHKOD.js → pre-commit-CJ7EDKJK.js} +5 -6
- package/dist/{scan-6CURGC3D.js → scan-WKDSKEBB.js} +2 -3
- package/dist/{sync-meta-L6M4AEUT.js → sync-meta-THZSEM7Y.js} +5 -2
- package/package.json +5 -4
- package/templates/agents-md/AGENTS.md.template +20 -35
- package/templates/agents-md/variants/cocos.md +20 -37
- package/templates/agents-md/variants/next.md +20 -37
- package/templates/agents-md/variants/vite.md +20 -37
- package/dist/chunk-P4KVFB2T.js +0 -22
- package/dist/hooks-5S5IRVQE.js +0 -124
- package/dist/init-G6Q3OOMC.js +0 -601
- package/dist/{serve-4J2CQY25.js → serve-MMN4GYLM.js} +4 -4
package/README.md
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
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
|
+
`fabric init` auto-runs `bootstrap install`, `config install`, and `hooks install`. Use them standalone only for targeted re-runs.
|
|
13
|
+
|
|
14
|
+
## Common Commands
|
|
15
|
+
|
|
16
|
+
- `fabric init`
|
|
17
|
+
- `fabric serve`
|
|
18
|
+
- `fabric doctor --audit`
|
|
19
|
+
|
|
20
|
+
## Advanced Commands
|
|
21
|
+
|
|
22
|
+
- `fabric bootstrap install`
|
|
23
|
+
- `fabric config install`
|
|
24
|
+
- `fabric hooks install`
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
bootstrapCommand,
|
|
4
|
+
bootstrap_default,
|
|
5
|
+
installBootstrap
|
|
6
|
+
} from "./chunk-RUQCZA2Q.js";
|
|
7
|
+
import "./chunk-VMYPJPKV.js";
|
|
8
|
+
import "./chunk-AEOYCVBG.js";
|
|
9
|
+
import "./chunk-6ICJICVU.js";
|
|
10
|
+
export {
|
|
11
|
+
bootstrapCommand,
|
|
12
|
+
bootstrap_default as default,
|
|
13
|
+
installBootstrap
|
|
14
|
+
};
|
|
@@ -1,7 +1,4 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import {
|
|
3
|
-
resolveIgnores
|
|
4
|
-
} from "./chunk-P4KVFB2T.js";
|
|
5
2
|
import {
|
|
6
3
|
t
|
|
7
4
|
} from "./chunk-6ICJICVU.js";
|
|
@@ -10,6 +7,11 @@ import {
|
|
|
10
7
|
import { createHash } from "crypto";
|
|
11
8
|
import { existsSync, mkdirSync, readdirSync, readFileSync, statSync, writeFileSync } from "fs";
|
|
12
9
|
import { isAbsolute, join, relative, resolve, sep } from "path";
|
|
10
|
+
import {
|
|
11
|
+
agentsMetaSchema,
|
|
12
|
+
deriveAgentsMetaLayer,
|
|
13
|
+
deriveAgentsMetaTopologyType
|
|
14
|
+
} from "@fenglimg/fabric-shared";
|
|
13
15
|
import { defineCommand } from "citty";
|
|
14
16
|
var syncMetaCommand = defineCommand({
|
|
15
17
|
meta: {
|
|
@@ -55,11 +57,15 @@ function computeAgentsMeta(target) {
|
|
|
55
57
|
const metaPath = join(target, ".fabric", "agents.meta.json");
|
|
56
58
|
const existingMeta = readExistingMeta(metaPath);
|
|
57
59
|
const existingByFile = indexExistingNodesByFile(existingMeta);
|
|
58
|
-
const agentsFiles =
|
|
60
|
+
const agentsFiles = findFabricAgentsFiles(target);
|
|
59
61
|
const nodes = {};
|
|
62
|
+
const bootstrapNode = createBootstrapNode(target, existingByFile.get("AGENTS.md")?.node);
|
|
63
|
+
if (bootstrapNode !== void 0) {
|
|
64
|
+
nodes.L0 = bootstrapNode;
|
|
65
|
+
}
|
|
60
66
|
for (const file of agentsFiles) {
|
|
61
67
|
const existing = existingByFile.get(file);
|
|
62
|
-
const id =
|
|
68
|
+
const id = deriveNodeId(file);
|
|
63
69
|
const hash = sha256(readFileSync(join(target, file), "utf8"));
|
|
64
70
|
const defaults = createDefaultNodeMeta(file);
|
|
65
71
|
nodes[id] = {
|
|
@@ -88,15 +94,18 @@ function readExistingMeta(metaPath) {
|
|
|
88
94
|
return void 0;
|
|
89
95
|
}
|
|
90
96
|
try {
|
|
91
|
-
return JSON.parse(readFileSync(metaPath, "utf8"));
|
|
97
|
+
return agentsMetaSchema.parse(JSON.parse(readFileSync(metaPath, "utf8")));
|
|
92
98
|
} catch {
|
|
93
99
|
return void 0;
|
|
94
100
|
}
|
|
95
101
|
}
|
|
96
|
-
function
|
|
97
|
-
const
|
|
102
|
+
function findFabricAgentsFiles(target) {
|
|
103
|
+
const agentsRoot = join(target, ".fabric", "agents");
|
|
104
|
+
if (!existsSync(agentsRoot) || !statSync(agentsRoot).isDirectory()) {
|
|
105
|
+
return [];
|
|
106
|
+
}
|
|
98
107
|
const files = [];
|
|
99
|
-
const stack = [
|
|
108
|
+
const stack = [agentsRoot];
|
|
100
109
|
while (stack.length > 0) {
|
|
101
110
|
const current = stack.pop();
|
|
102
111
|
if (current === void 0) {
|
|
@@ -105,31 +114,20 @@ function findAgentsFiles(target) {
|
|
|
105
114
|
for (const entry of readdirSync(current, { withFileTypes: true })) {
|
|
106
115
|
const absolutePath = join(current, entry.name);
|
|
107
116
|
const relativePath = toPosixPath(relative(target, absolutePath));
|
|
108
|
-
if (shouldIgnore(relativePath, entry.isDirectory(), ignorePatterns)) {
|
|
109
|
-
continue;
|
|
110
|
-
}
|
|
111
117
|
if (entry.isDirectory()) {
|
|
112
118
|
stack.push(absolutePath);
|
|
113
|
-
} else if (entry.isFile() && entry.name
|
|
119
|
+
} else if (entry.isFile() && entry.name.endsWith(".md")) {
|
|
114
120
|
files.push(relativePath);
|
|
115
121
|
}
|
|
116
122
|
}
|
|
117
123
|
}
|
|
118
124
|
return files.sort();
|
|
119
125
|
}
|
|
120
|
-
function
|
|
121
|
-
return
|
|
126
|
+
function deriveLayer(relativePath) {
|
|
127
|
+
return deriveAgentsMetaLayer(relativePath);
|
|
122
128
|
}
|
|
123
|
-
function
|
|
124
|
-
|
|
125
|
-
if (normalizedPattern === "**/*.meta") {
|
|
126
|
-
return relativePath.endsWith(".meta");
|
|
127
|
-
}
|
|
128
|
-
if (normalizedPattern.endsWith("/**")) {
|
|
129
|
-
const directoryPrefix = normalizedPattern.slice(0, -3);
|
|
130
|
-
return relativePath === directoryPrefix || relativePath.startsWith(`${directoryPrefix}/`) || isDirectory && `${relativePath}/` === directoryPrefix;
|
|
131
|
-
}
|
|
132
|
-
return relativePath === normalizedPattern;
|
|
129
|
+
function deriveTopologyType(relativePath) {
|
|
130
|
+
return deriveAgentsMetaTopologyType(relativePath);
|
|
133
131
|
}
|
|
134
132
|
function indexExistingNodesByFile(existingMeta) {
|
|
135
133
|
const byFile = /* @__PURE__ */ new Map();
|
|
@@ -142,18 +140,57 @@ function deriveNodeId(file) {
|
|
|
142
140
|
if (file === "AGENTS.md") {
|
|
143
141
|
return "L0";
|
|
144
142
|
}
|
|
145
|
-
|
|
143
|
+
const layer = deriveLayer(file);
|
|
144
|
+
const relativeStem = getMirrorRelativeStem(file);
|
|
145
|
+
return `${layer}/${relativeStem}`;
|
|
146
146
|
}
|
|
147
147
|
function createDefaultNodeMeta(file) {
|
|
148
|
-
const
|
|
148
|
+
const layer = deriveLayer(file);
|
|
149
|
+
const topologyType = deriveTopologyType(file);
|
|
149
150
|
return {
|
|
150
151
|
file,
|
|
151
|
-
scope_glob:
|
|
152
|
-
deps:
|
|
153
|
-
priority:
|
|
152
|
+
scope_glob: deriveScopeGlob(file),
|
|
153
|
+
deps: layer === "L0" ? [] : ["L0"],
|
|
154
|
+
priority: layer === "L0" ? "high" : "medium",
|
|
155
|
+
layer,
|
|
156
|
+
topology_type: topologyType,
|
|
154
157
|
hash: ""
|
|
155
158
|
};
|
|
156
159
|
}
|
|
160
|
+
function createBootstrapNode(target, existing) {
|
|
161
|
+
const bootstrapPath = join(target, "AGENTS.md");
|
|
162
|
+
if (!existsSync(bootstrapPath)) {
|
|
163
|
+
return void 0;
|
|
164
|
+
}
|
|
165
|
+
const hash = sha256(readFileSync(bootstrapPath, "utf8"));
|
|
166
|
+
return {
|
|
167
|
+
...createDefaultNodeMeta("AGENTS.md"),
|
|
168
|
+
...existing,
|
|
169
|
+
file: "AGENTS.md",
|
|
170
|
+
hash
|
|
171
|
+
};
|
|
172
|
+
}
|
|
173
|
+
function deriveScopeGlob(file) {
|
|
174
|
+
if (file === "AGENTS.md") {
|
|
175
|
+
return "**";
|
|
176
|
+
}
|
|
177
|
+
const stem = getMirrorRelativeStem(file);
|
|
178
|
+
const segments = stem.split("/").filter(Boolean);
|
|
179
|
+
if (segments.length === 0 || stem === "root") {
|
|
180
|
+
return "**";
|
|
181
|
+
}
|
|
182
|
+
if (segments[0] === "_cross") {
|
|
183
|
+
return "**";
|
|
184
|
+
}
|
|
185
|
+
if (segments.at(-1) === "rules") {
|
|
186
|
+
segments.pop();
|
|
187
|
+
}
|
|
188
|
+
const scopePath = segments.join("/");
|
|
189
|
+
return scopePath === "" ? "**" : `${scopePath}/**`;
|
|
190
|
+
}
|
|
191
|
+
function getMirrorRelativeStem(file) {
|
|
192
|
+
return file.replace(/^\.fabric\/agents\//, "").replace(/\.md$/, "");
|
|
193
|
+
}
|
|
157
194
|
function sortNodes(nodes) {
|
|
158
195
|
return Object.fromEntries(Object.entries(nodes).sort(([left], [right]) => left.localeCompare(right)));
|
|
159
196
|
}
|
|
@@ -187,5 +224,7 @@ function sha256(content) {
|
|
|
187
224
|
export {
|
|
188
225
|
syncMetaCommand,
|
|
189
226
|
sync_meta_default,
|
|
190
|
-
computeAgentsMeta
|
|
227
|
+
computeAgentsMeta,
|
|
228
|
+
deriveLayer,
|
|
229
|
+
deriveTopologyType
|
|
191
230
|
};
|
|
@@ -13,6 +13,7 @@ import { resolve } from "path";
|
|
|
13
13
|
import { fileURLToPath } from "url";
|
|
14
14
|
import { defineCommand } from "citty";
|
|
15
15
|
var CLIENT_ALIASES = {
|
|
16
|
+
claude: "ClaudeCodeCLI",
|
|
16
17
|
claudecodecli: "ClaudeCodeCLI",
|
|
17
18
|
"claude-code-cli": "ClaudeCodeCLI",
|
|
18
19
|
claudecli: "ClaudeCodeCLI",
|
|
@@ -57,7 +58,8 @@ async function loadFabricConfig(workspaceRoot) {
|
|
|
57
58
|
}
|
|
58
59
|
return parsed;
|
|
59
60
|
}
|
|
60
|
-
function resolveServerPath() {
|
|
61
|
+
function resolveServerPath(override) {
|
|
62
|
+
if (override) return override;
|
|
61
63
|
if (process.env.FAB_SERVER_PATH) return resolve(process.env.FAB_SERVER_PATH);
|
|
62
64
|
return fileURLToPath(import.meta.resolve("@fenglimg/fabric-server"));
|
|
63
65
|
}
|
|
@@ -88,36 +90,66 @@ var configCmd = defineCommand({
|
|
|
88
90
|
}
|
|
89
91
|
},
|
|
90
92
|
async run({ args }) {
|
|
91
|
-
const workspaceRoot = process.cwd();
|
|
92
|
-
const fabricConfig = await loadFabricConfig(workspaceRoot);
|
|
93
93
|
const selectedClients = parseClientFilter(args.clients);
|
|
94
|
-
const
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
);
|
|
98
|
-
if (
|
|
94
|
+
const result = await installMcpClients(process.cwd(), {
|
|
95
|
+
clients: selectedClients === null ? void 0 : Array.from(selectedClients),
|
|
96
|
+
dryRun: args["dry-run"]
|
|
97
|
+
});
|
|
98
|
+
if (result.details.length === 0) {
|
|
99
99
|
writeStderr(t("cli.config.install.no-configs"));
|
|
100
100
|
return;
|
|
101
101
|
}
|
|
102
|
-
for (const
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
writeStderr(t("cli.config.install.no-config-path", { client: writer.clientKind }));
|
|
102
|
+
for (const detail of result.details) {
|
|
103
|
+
if (detail.action === "skipped") {
|
|
104
|
+
writeStderr(t("cli.config.install.no-config-path", { client: detail.client }));
|
|
106
105
|
continue;
|
|
107
106
|
}
|
|
108
|
-
if (
|
|
109
|
-
writeStderr(t("cli.config.install.dry-run", { client:
|
|
107
|
+
if (detail.action === "dry-run" && detail.path !== null) {
|
|
108
|
+
writeStderr(t("cli.config.install.dry-run", { client: detail.client, path: detail.path }));
|
|
110
109
|
continue;
|
|
111
110
|
}
|
|
112
|
-
|
|
113
|
-
|
|
111
|
+
if (detail.path !== null) {
|
|
112
|
+
writeStderr(t("cli.config.install.wrote", { client: detail.client, path: detail.path }));
|
|
113
|
+
}
|
|
114
114
|
}
|
|
115
115
|
}
|
|
116
116
|
})
|
|
117
117
|
}
|
|
118
118
|
});
|
|
119
119
|
var config_default = configCmd;
|
|
120
|
+
async function installMcpClients(target, options = {}) {
|
|
121
|
+
const workspaceRoot = resolve(target);
|
|
122
|
+
const fabricConfig = await loadFabricConfig(workspaceRoot);
|
|
123
|
+
const selectedClients = options.clients === void 0 ? null : new Set(options.clients);
|
|
124
|
+
const serverPath = resolveServerPath(options.localServerPath);
|
|
125
|
+
const writers = resolveClients(workspaceRoot, fabricConfig).filter(
|
|
126
|
+
(writer) => selectedClients === null ? true : selectedClients.has(writer.clientKind)
|
|
127
|
+
);
|
|
128
|
+
const installed = [];
|
|
129
|
+
const skipped = [];
|
|
130
|
+
const details = [];
|
|
131
|
+
for (const writer of writers) {
|
|
132
|
+
const configPath = await writer.detect(workspaceRoot);
|
|
133
|
+
if (configPath === null) {
|
|
134
|
+
skipped.push(writer.clientKind);
|
|
135
|
+
details.push({ client: writer.clientKind, path: null, action: "skipped" });
|
|
136
|
+
continue;
|
|
137
|
+
}
|
|
138
|
+
if (options.dryRun) {
|
|
139
|
+
skipped.push(writer.clientKind);
|
|
140
|
+
details.push({ client: writer.clientKind, path: configPath, action: "dry-run" });
|
|
141
|
+
continue;
|
|
142
|
+
}
|
|
143
|
+
await writer.write(serverPath, workspaceRoot);
|
|
144
|
+
installed.push(writer.clientKind);
|
|
145
|
+
details.push({ client: writer.clientKind, path: configPath, action: "wrote" });
|
|
146
|
+
}
|
|
147
|
+
return { installed, skipped, details };
|
|
148
|
+
}
|
|
149
|
+
|
|
120
150
|
export {
|
|
151
|
+
parseClientFilter,
|
|
121
152
|
configCmd,
|
|
122
|
-
config_default
|
|
153
|
+
config_default,
|
|
154
|
+
installMcpClients
|
|
123
155
|
};
|
|
@@ -1,18 +1,15 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import {
|
|
3
|
-
createDebugLogger,
|
|
4
|
-
readFabricConfig,
|
|
5
|
-
resolveDevMode
|
|
6
|
-
} from "./chunk-AEOYCVBG.js";
|
|
7
|
-
import {
|
|
8
|
-
resolveIgnores
|
|
9
|
-
} from "./chunk-P4KVFB2T.js";
|
|
10
2
|
import {
|
|
11
3
|
displayWidth,
|
|
12
4
|
padEnd,
|
|
13
5
|
paint,
|
|
14
6
|
symbol
|
|
15
7
|
} from "./chunk-WWNXR34K.js";
|
|
8
|
+
import {
|
|
9
|
+
createDebugLogger,
|
|
10
|
+
readFabricConfig,
|
|
11
|
+
resolveDevMode
|
|
12
|
+
} from "./chunk-AEOYCVBG.js";
|
|
16
13
|
import {
|
|
17
14
|
t
|
|
18
15
|
} from "./chunk-6ICJICVU.js";
|
|
@@ -23,7 +20,24 @@ import { isAbsolute, join, relative, resolve, sep } from "path";
|
|
|
23
20
|
import { defineCommand } from "citty";
|
|
24
21
|
|
|
25
22
|
// src/scanner/detector.ts
|
|
26
|
-
import { detectFramework } from "@fenglimg/fabric-shared";
|
|
23
|
+
import { detectFramework } from "@fenglimg/fabric-shared/node";
|
|
24
|
+
|
|
25
|
+
// src/scanner/ignores.ts
|
|
26
|
+
var DEFAULT_IGNORES = [
|
|
27
|
+
"**/*.meta",
|
|
28
|
+
"library/**",
|
|
29
|
+
"temp/**",
|
|
30
|
+
"build/**",
|
|
31
|
+
"settings/**",
|
|
32
|
+
"profiles/**",
|
|
33
|
+
"node_modules/**",
|
|
34
|
+
"dist/**",
|
|
35
|
+
".git/**",
|
|
36
|
+
".fabric/**"
|
|
37
|
+
];
|
|
38
|
+
function resolveIgnores(fabricConfig) {
|
|
39
|
+
return [...DEFAULT_IGNORES, ...fabricConfig?.scanIgnores ?? []];
|
|
40
|
+
}
|
|
27
41
|
|
|
28
42
|
// src/commands/scan.ts
|
|
29
43
|
function createScanReport(targetInput = process.cwd(), fabricConfig) {
|
|
@@ -70,24 +70,53 @@ var bootstrapCommand = defineCommand({
|
|
|
70
70
|
async run({ args }) {
|
|
71
71
|
const workspaceRoot = process.cwd();
|
|
72
72
|
const selectedClients = parseClientFilter(args.clients);
|
|
73
|
-
const
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
if (
|
|
73
|
+
const result = await installBootstrap(workspaceRoot, {
|
|
74
|
+
clients: selectedClients === null ? void 0 : Array.from(selectedClients, mapBootstrapClientToClientKind)
|
|
75
|
+
});
|
|
76
|
+
if (result.details.length === 0) {
|
|
77
77
|
process.stderr.write(
|
|
78
78
|
`${t("cli.bootstrap.install.no-targets")}
|
|
79
79
|
`
|
|
80
80
|
);
|
|
81
81
|
return;
|
|
82
82
|
}
|
|
83
|
-
for (const
|
|
84
|
-
|
|
83
|
+
for (const detail of result.details) {
|
|
84
|
+
if (detail.action === "skipped") {
|
|
85
|
+
process.stderr.write(`${t("cli.bootstrap.install.skipped-header", { path: detail.path })}
|
|
86
|
+
`);
|
|
87
|
+
continue;
|
|
88
|
+
}
|
|
89
|
+
if (detail.action === "prepended") {
|
|
90
|
+
process.stderr.write(`${t("cli.bootstrap.install.prepended", { path: detail.path })}
|
|
91
|
+
`);
|
|
92
|
+
continue;
|
|
93
|
+
}
|
|
94
|
+
process.stderr.write(`${t("cli.bootstrap.install.installed", { path: detail.path })}
|
|
95
|
+
`);
|
|
85
96
|
}
|
|
86
97
|
}
|
|
87
98
|
})
|
|
88
99
|
}
|
|
89
100
|
});
|
|
90
101
|
var bootstrap_default = bootstrapCommand;
|
|
102
|
+
async function installBootstrap(target, options = {}) {
|
|
103
|
+
const workspaceRoot = resolve(target);
|
|
104
|
+
const fabricConfig = readFabricConfig(workspaceRoot);
|
|
105
|
+
const targets = resolveBootstrapTargets(workspaceRoot, fabricConfig, options.clients);
|
|
106
|
+
const installed = [];
|
|
107
|
+
const skipped = [];
|
|
108
|
+
const details = [];
|
|
109
|
+
for (const bootstrapTarget of targets) {
|
|
110
|
+
const detail = installBootstrapTarget(bootstrapTarget, workspaceRoot, options);
|
|
111
|
+
details.push(detail);
|
|
112
|
+
if (detail.action === "skipped") {
|
|
113
|
+
skipped.push(bootstrapTarget.client);
|
|
114
|
+
} else {
|
|
115
|
+
installed.push(bootstrapTarget.client);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
return { installed, skipped, details };
|
|
119
|
+
}
|
|
91
120
|
function parseClientFilter(value) {
|
|
92
121
|
if (value === void 0 || value.trim().length === 0) {
|
|
93
122
|
return null;
|
|
@@ -103,15 +132,19 @@ function parseClientFilter(value) {
|
|
|
103
132
|
}
|
|
104
133
|
return clients;
|
|
105
134
|
}
|
|
106
|
-
function
|
|
107
|
-
const
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
135
|
+
function resolveBootstrapTargets(workspaceRoot, fabricConfig, selectedClients) {
|
|
136
|
+
const targets = [];
|
|
137
|
+
const seenClients = /* @__PURE__ */ new Set();
|
|
138
|
+
const clientKinds = selectedClients ?? resolveClients(workspaceRoot, fabricConfig).map((writer) => writer.clientKind);
|
|
139
|
+
for (const clientKind of clientKinds) {
|
|
140
|
+
const bootstrapClient = mapClientKind(clientKind);
|
|
141
|
+
if (bootstrapClient === null || seenClients.has(bootstrapClient)) {
|
|
142
|
+
continue;
|
|
112
143
|
}
|
|
144
|
+
seenClients.add(bootstrapClient);
|
|
145
|
+
targets.push({ client: clientKind, bootstrapClient });
|
|
113
146
|
}
|
|
114
|
-
return
|
|
147
|
+
return targets;
|
|
115
148
|
}
|
|
116
149
|
function mapClientKind(clientKind) {
|
|
117
150
|
switch (clientKind) {
|
|
@@ -132,37 +165,79 @@ function mapClientKind(clientKind) {
|
|
|
132
165
|
return null;
|
|
133
166
|
}
|
|
134
167
|
}
|
|
135
|
-
function
|
|
136
|
-
|
|
137
|
-
|
|
168
|
+
function mapBootstrapClientToClientKind(client) {
|
|
169
|
+
switch (client) {
|
|
170
|
+
case "claude":
|
|
171
|
+
return "ClaudeCodeCLI";
|
|
172
|
+
case "cursor":
|
|
173
|
+
return "Cursor";
|
|
174
|
+
case "windsurf":
|
|
175
|
+
return "Windsurf";
|
|
176
|
+
case "roo":
|
|
177
|
+
return "RooCode";
|
|
178
|
+
case "gemini":
|
|
179
|
+
return "GeminiCLI";
|
|
180
|
+
case "codex":
|
|
181
|
+
return "CodexCLI";
|
|
182
|
+
}
|
|
183
|
+
}
|
|
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]);
|
|
138
187
|
const template = readFileSync(templatePath, "utf8");
|
|
139
188
|
mkdirSync(dirname(targetPath), { recursive: true });
|
|
140
|
-
if (
|
|
141
|
-
|
|
142
|
-
|
|
189
|
+
if (target.bootstrapClient === "codex") {
|
|
190
|
+
return {
|
|
191
|
+
client: target.client,
|
|
192
|
+
path: targetPath,
|
|
193
|
+
action: writeCodexBootstrap(targetPath, template, options.force)
|
|
194
|
+
};
|
|
143
195
|
}
|
|
196
|
+
const existed = existsSync(targetPath);
|
|
144
197
|
writeFileSync(targetPath, ensureTrailingNewline(template), "utf8");
|
|
145
|
-
|
|
146
|
-
|
|
198
|
+
return {
|
|
199
|
+
client: target.client,
|
|
200
|
+
path: targetPath,
|
|
201
|
+
action: existed ? "overwritten" : "installed"
|
|
202
|
+
};
|
|
147
203
|
}
|
|
148
|
-
function writeCodexBootstrap(targetPath, template) {
|
|
204
|
+
function writeCodexBootstrap(targetPath, template, force) {
|
|
149
205
|
const nextContent = ensureTrailingNewline(template);
|
|
150
206
|
if (!existsSync(targetPath)) {
|
|
151
207
|
writeFileSync(targetPath, nextContent, "utf8");
|
|
152
|
-
|
|
153
|
-
`);
|
|
154
|
-
return;
|
|
208
|
+
return "installed";
|
|
155
209
|
}
|
|
156
210
|
const existing = readFileSync(targetPath, "utf8");
|
|
157
211
|
if (existing.includes("# Fabric Bootstrap")) {
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
212
|
+
if (!force) {
|
|
213
|
+
return "skipped";
|
|
214
|
+
}
|
|
215
|
+
const remainder = stripExistingCodexBootstrap(existing, nextContent);
|
|
216
|
+
writeFileSync(targetPath, joinBootstrapSections(nextContent, remainder), "utf8");
|
|
217
|
+
return "overwritten";
|
|
161
218
|
}
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
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}`;
|
|
166
241
|
}
|
|
167
242
|
function ensureTrailingNewline(content) {
|
|
168
243
|
return content.endsWith("\n") ? content : `${content}
|
|
@@ -194,7 +269,9 @@ function templateCandidatesFrom(start, relativePath) {
|
|
|
194
269
|
}
|
|
195
270
|
return candidates.reverse();
|
|
196
271
|
}
|
|
272
|
+
|
|
197
273
|
export {
|
|
198
274
|
bootstrapCommand,
|
|
199
|
-
bootstrap_default
|
|
275
|
+
bootstrap_default,
|
|
276
|
+
installBootstrap
|
|
200
277
|
};
|