@fenglimg/fabric-cli 2.0.0-rc.13 → 2.0.0-rc.15
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 +4 -2
- package/dist/chunk-AIB54QRT.js +82 -0
- package/dist/{chunk-FDRLV5PL.js → chunk-AXIFEVAS.js} +5 -213
- package/dist/{chunk-WWNXR34K.js → chunk-G2CIOLD4.js} +16 -1
- package/dist/{chunk-OHWQNSLH.js → chunk-SKSYUHKK.js} +267 -40
- package/dist/{chunk-X7QPY5KH.js → chunk-UTF4YBDN.js} +34 -271
- package/dist/config-7YD365I3.js +13 -0
- package/dist/{doctor-RILCO5OG.js → doctor-6XHLQJXB.js} +67 -50
- package/dist/index.js +7 -8
- package/dist/{install-SLS5W27W.js → install-JLDCHAXV.js} +328 -341
- package/dist/{plan-context-hint-QMUPAXIB.js → plan-context-hint-73U4FGKO.js} +6 -1
- package/dist/{serve-NGLXHDYC.js → serve-L3X5UHG2.js} +15 -10
- package/dist/{uninstall-JHUSFENL.js → uninstall-DD6FIFCI.js} +56 -163
- package/package.json +3 -3
- package/templates/hooks/configs/README.md +9 -5
- package/templates/hooks/configs/cursor-hooks.json +7 -10
- package/dist/chunk-Q72D24BG.js +0 -186
- package/dist/hooks-HIWYI3VG.js +0 -13
- package/dist/scan-VHKZPT2W.js +0 -24
|
@@ -1,245 +1,14 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
deepMerge
|
|
4
|
+
} from "./chunk-SKSYUHKK.js";
|
|
2
5
|
|
|
3
6
|
// src/install/skills-and-hooks.ts
|
|
4
|
-
import { chmodSync, existsSync
|
|
5
|
-
import { mkdir as mkdir2, readFile as readFile2 } from "fs/promises";
|
|
6
|
-
import { dirname as dirname2, join as join2, parse, resolve as resolve2 } from "path";
|
|
7
|
-
import { fileURLToPath } from "url";
|
|
8
|
-
import { atomicWriteJson as atomicWriteJson2, atomicWriteText } from "@fenglimg/fabric-shared/node/atomic-write";
|
|
9
|
-
|
|
10
|
-
// src/config/json.ts
|
|
11
|
-
import { existsSync } from "fs";
|
|
7
|
+
import { chmodSync, existsSync, readFileSync } from "fs";
|
|
12
8
|
import { mkdir, readFile } from "fs/promises";
|
|
13
|
-
import { dirname, join, resolve } from "path";
|
|
14
|
-
import {
|
|
15
|
-
import { atomicWriteJson } from "@fenglimg/fabric-shared/node/atomic-write";
|
|
16
|
-
|
|
17
|
-
// src/config/writer.ts
|
|
18
|
-
function createServerEntry(serverPath) {
|
|
19
|
-
return {
|
|
20
|
-
command: process.execPath,
|
|
21
|
-
args: [serverPath]
|
|
22
|
-
};
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
// src/config/json.ts
|
|
26
|
-
function deepMerge(target, source, options = {}) {
|
|
27
|
-
return deepMergeAtPath(target, source, "", options);
|
|
28
|
-
}
|
|
29
|
-
function deepMergeAtPath(target, source, path, options) {
|
|
30
|
-
if (options.arrayAppendPaths && options.arrayAppendPaths.includes(path) && Array.isArray(target) && Array.isArray(source)) {
|
|
31
|
-
return appendArrayWithDedupe(target, source);
|
|
32
|
-
}
|
|
33
|
-
if (target === null || typeof target !== "object" || Array.isArray(target) || source === null || typeof source !== "object" || Array.isArray(source)) {
|
|
34
|
-
return source;
|
|
35
|
-
}
|
|
36
|
-
const out = { ...target };
|
|
37
|
-
for (const key of Object.keys(source)) {
|
|
38
|
-
const childPath = path === "" ? key : `${path}.${key}`;
|
|
39
|
-
out[key] = deepMergeAtPath(
|
|
40
|
-
target[key],
|
|
41
|
-
source[key],
|
|
42
|
-
childPath,
|
|
43
|
-
options
|
|
44
|
-
);
|
|
45
|
-
}
|
|
46
|
-
return out;
|
|
47
|
-
}
|
|
48
|
-
function appendArrayWithDedupe(target, source) {
|
|
49
|
-
const out = [...target];
|
|
50
|
-
for (const candidate of source) {
|
|
51
|
-
if (out.some((existing) => isSameHookEntry(existing, candidate))) {
|
|
52
|
-
continue;
|
|
53
|
-
}
|
|
54
|
-
out.push(candidate);
|
|
55
|
-
}
|
|
56
|
-
return out;
|
|
57
|
-
}
|
|
58
|
-
function isSameHookEntry(a, b) {
|
|
59
|
-
const cmdA = extractHookCommand(a);
|
|
60
|
-
const cmdB = extractHookCommand(b);
|
|
61
|
-
if (cmdA !== null && cmdB !== null) {
|
|
62
|
-
return cmdA === cmdB;
|
|
63
|
-
}
|
|
64
|
-
return deepEqual(a, b);
|
|
65
|
-
}
|
|
66
|
-
function extractHookCommand(item) {
|
|
67
|
-
if (item === null || typeof item !== "object") {
|
|
68
|
-
return null;
|
|
69
|
-
}
|
|
70
|
-
const obj = item;
|
|
71
|
-
if (typeof obj.command === "string") {
|
|
72
|
-
return obj.command;
|
|
73
|
-
}
|
|
74
|
-
if (Array.isArray(obj.hooks)) {
|
|
75
|
-
for (const inner of obj.hooks) {
|
|
76
|
-
if (inner !== null && typeof inner === "object") {
|
|
77
|
-
const innerObj = inner;
|
|
78
|
-
if (typeof innerObj.command === "string") {
|
|
79
|
-
return innerObj.command;
|
|
80
|
-
}
|
|
81
|
-
}
|
|
82
|
-
}
|
|
83
|
-
}
|
|
84
|
-
return null;
|
|
85
|
-
}
|
|
86
|
-
function deepEqual(a, b) {
|
|
87
|
-
if (a === b) {
|
|
88
|
-
return true;
|
|
89
|
-
}
|
|
90
|
-
if (a === null || b === null || typeof a !== "object" || typeof b !== "object") {
|
|
91
|
-
return false;
|
|
92
|
-
}
|
|
93
|
-
if (Array.isArray(a) !== Array.isArray(b)) {
|
|
94
|
-
return false;
|
|
95
|
-
}
|
|
96
|
-
if (Array.isArray(a) && Array.isArray(b)) {
|
|
97
|
-
if (a.length !== b.length) {
|
|
98
|
-
return false;
|
|
99
|
-
}
|
|
100
|
-
return a.every((value, index) => deepEqual(value, b[index]));
|
|
101
|
-
}
|
|
102
|
-
const aObj = a;
|
|
103
|
-
const bObj = b;
|
|
104
|
-
const aKeys = Object.keys(aObj);
|
|
105
|
-
const bKeys = Object.keys(bObj);
|
|
106
|
-
if (aKeys.length !== bKeys.length) {
|
|
107
|
-
return false;
|
|
108
|
-
}
|
|
109
|
-
return aKeys.every((key) => deepEqual(aObj[key], bObj[key]));
|
|
110
|
-
}
|
|
111
|
-
function expandHome(filePath) {
|
|
112
|
-
if (filePath === "~") {
|
|
113
|
-
return homedir();
|
|
114
|
-
}
|
|
115
|
-
if (filePath.startsWith("~/")) {
|
|
116
|
-
return join(homedir(), filePath.slice(2));
|
|
117
|
-
}
|
|
118
|
-
return filePath;
|
|
119
|
-
}
|
|
120
|
-
function normalizeConfigPath(filePath) {
|
|
121
|
-
return resolve(expandHome(filePath));
|
|
122
|
-
}
|
|
123
|
-
async function readJsonConfig(configPath) {
|
|
124
|
-
try {
|
|
125
|
-
const raw = await readFile(configPath, "utf8");
|
|
126
|
-
if (raw.trim().length === 0) {
|
|
127
|
-
return {};
|
|
128
|
-
}
|
|
129
|
-
const parsed = JSON.parse(raw);
|
|
130
|
-
if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
131
|
-
throw new Error(`Expected JSON object in ${configPath}`);
|
|
132
|
-
}
|
|
133
|
-
return parsed;
|
|
134
|
-
} catch (error) {
|
|
135
|
-
if (error instanceof Error && "code" in error && error.code === "ENOENT") {
|
|
136
|
-
return {};
|
|
137
|
-
}
|
|
138
|
-
throw error;
|
|
139
|
-
}
|
|
140
|
-
}
|
|
141
|
-
async function writeJsonClientConfig(configPath, serverEntry) {
|
|
142
|
-
const existing = await readJsonConfig(configPath);
|
|
143
|
-
const merged = deepMerge(existing, { mcpServers: { fabric: serverEntry } });
|
|
144
|
-
await mkdir(dirname(configPath), { recursive: true });
|
|
145
|
-
await atomicWriteJson(configPath, merged, { indent: 2 });
|
|
146
|
-
}
|
|
147
|
-
async function removeJsonClientConfigEntry(configPath, serverName) {
|
|
148
|
-
if (!existsSync(configPath)) {
|
|
149
|
-
return { status: "skipped", path: configPath, message: "no-config-file" };
|
|
150
|
-
}
|
|
151
|
-
let existing;
|
|
152
|
-
try {
|
|
153
|
-
existing = await readJsonConfig(configPath);
|
|
154
|
-
} catch (error) {
|
|
155
|
-
return {
|
|
156
|
-
status: "error",
|
|
157
|
-
path: configPath,
|
|
158
|
-
message: error instanceof Error ? error.message : String(error)
|
|
159
|
-
};
|
|
160
|
-
}
|
|
161
|
-
const mcpServers = existing.mcpServers;
|
|
162
|
-
if (mcpServers === void 0 || mcpServers === null || typeof mcpServers !== "object" || Array.isArray(mcpServers)) {
|
|
163
|
-
return { status: "skipped", path: configPath, message: "no-mcp-servers-object" };
|
|
164
|
-
}
|
|
165
|
-
const servers = mcpServers;
|
|
166
|
-
if (!Object.prototype.hasOwnProperty.call(servers, serverName)) {
|
|
167
|
-
return { status: "skipped", path: configPath, message: "not-present" };
|
|
168
|
-
}
|
|
169
|
-
const nextServers = { ...servers };
|
|
170
|
-
delete nextServers[serverName];
|
|
171
|
-
const next = { ...existing, mcpServers: nextServers };
|
|
172
|
-
try {
|
|
173
|
-
await mkdir(dirname(configPath), { recursive: true });
|
|
174
|
-
await atomicWriteJson(configPath, next, { indent: 2 });
|
|
175
|
-
return { status: "removed", path: configPath };
|
|
176
|
-
} catch (error) {
|
|
177
|
-
return {
|
|
178
|
-
status: "error",
|
|
179
|
-
path: configPath,
|
|
180
|
-
message: error instanceof Error ? error.message : String(error)
|
|
181
|
-
};
|
|
182
|
-
}
|
|
183
|
-
}
|
|
184
|
-
var JsonClientConfigWriter = class {
|
|
185
|
-
configuredPath;
|
|
186
|
-
constructor(configuredPath) {
|
|
187
|
-
this.configuredPath = configuredPath;
|
|
188
|
-
}
|
|
189
|
-
async detect(workspaceRoot, overridePath) {
|
|
190
|
-
const explicitPath = overridePath ?? this.configuredPath;
|
|
191
|
-
if (explicitPath !== void 0) {
|
|
192
|
-
return normalizeConfigPath(explicitPath);
|
|
193
|
-
}
|
|
194
|
-
const configPath = this.defaultPath(workspaceRoot);
|
|
195
|
-
return configPath === null ? null : normalizeConfigPath(configPath);
|
|
196
|
-
}
|
|
197
|
-
async write(serverPath, workspaceRoot, overridePath) {
|
|
198
|
-
const configPath = await this.detect(workspaceRoot, overridePath);
|
|
199
|
-
if (configPath === null) {
|
|
200
|
-
return;
|
|
201
|
-
}
|
|
202
|
-
await writeJsonClientConfig(configPath, createServerEntry(serverPath));
|
|
203
|
-
}
|
|
204
|
-
async remove(serverName, workspaceRoot, overridePath) {
|
|
205
|
-
const configPath = await this.detect(workspaceRoot, overridePath);
|
|
206
|
-
if (configPath === null) {
|
|
207
|
-
return { status: "skipped", message: "no-config-path" };
|
|
208
|
-
}
|
|
209
|
-
return removeJsonClientConfigEntry(configPath, serverName);
|
|
210
|
-
}
|
|
211
|
-
};
|
|
212
|
-
var ClaudeCodeCLIWriter = class extends JsonClientConfigWriter {
|
|
213
|
-
clientKind = "ClaudeCodeCLI";
|
|
214
|
-
scope;
|
|
215
|
-
constructor(configuredPath, scope = "project") {
|
|
216
|
-
super(configuredPath);
|
|
217
|
-
this.scope = scope;
|
|
218
|
-
}
|
|
219
|
-
// Writes to project-level .mcp.json (per Claude Code MCP spec) by default,
|
|
220
|
-
// or ~/.claude.json for user scope.
|
|
221
|
-
// Detection still checks ~/.claude to confirm Claude Code is installed.
|
|
222
|
-
defaultPath(workspaceRoot) {
|
|
223
|
-
const globalClaudeDir = join(homedir(), ".claude");
|
|
224
|
-
const projectClaudeDir = join(workspaceRoot, ".claude");
|
|
225
|
-
if (!existsSync(globalClaudeDir) && !existsSync(projectClaudeDir)) {
|
|
226
|
-
return null;
|
|
227
|
-
}
|
|
228
|
-
return this.scope === "user" ? join(homedir(), ".claude.json") : join(workspaceRoot, ".mcp.json");
|
|
229
|
-
}
|
|
230
|
-
};
|
|
231
|
-
var CursorWriter = class extends JsonClientConfigWriter {
|
|
232
|
-
clientKind = "Cursor";
|
|
233
|
-
constructor(configuredPath) {
|
|
234
|
-
super(configuredPath);
|
|
235
|
-
}
|
|
236
|
-
defaultPath(workspaceRoot) {
|
|
237
|
-
const cursorDir = join(workspaceRoot, ".cursor");
|
|
238
|
-
return existsSync(cursorDir) ? join(cursorDir, "mcp.json") : null;
|
|
239
|
-
}
|
|
240
|
-
};
|
|
241
|
-
|
|
242
|
-
// src/install/skills-and-hooks.ts
|
|
9
|
+
import { dirname, join, parse, resolve } from "path";
|
|
10
|
+
import { fileURLToPath } from "url";
|
|
11
|
+
import { atomicWriteJson, atomicWriteText } from "@fenglimg/fabric-shared/node/atomic-write";
|
|
243
12
|
var SKILL_TEMPLATE_REL = "skills/fabric-archive/SKILL.md";
|
|
244
13
|
var SKILL_REVIEW_TEMPLATE_REL = "skills/fabric-review/SKILL.md";
|
|
245
14
|
var SKILL_IMPORT_TEMPLATE_REL = "skills/fabric-import/SKILL.md";
|
|
@@ -288,7 +57,7 @@ var HOOK_CONFIG_TARGETS = {
|
|
|
288
57
|
var HOOK_CONFIG_ARRAY_PATHS = {
|
|
289
58
|
claudeCode: ["hooks.Stop", "hooks.SessionStart", "hooks.PreToolUse"],
|
|
290
59
|
codex: ["events.Stop", "events.SessionStart", "events.PreToolUse"],
|
|
291
|
-
cursor: ["
|
|
60
|
+
cursor: ["hooks.stop", "hooks.sessionStart", "hooks.preToolUse"]
|
|
292
61
|
};
|
|
293
62
|
var FABRIC_HOOK_COMMAND_PATHS = {
|
|
294
63
|
claudeCode: {
|
|
@@ -307,13 +76,13 @@ var FABRIC_HOOK_COMMAND_PATHS = {
|
|
|
307
76
|
knowledgeHintNarrow: ".cursor/hooks/knowledge-hint-narrow.cjs"
|
|
308
77
|
}
|
|
309
78
|
};
|
|
310
|
-
var SECTION_TARGETS = ["CLAUDE.md", "AGENTS.md",
|
|
79
|
+
var SECTION_TARGETS = ["CLAUDE.md", "AGENTS.md", join(".cursor", "rules")];
|
|
311
80
|
var FABRIC_SECTION_BEGIN_MARKER = "<!-- fabric:knowledge-base:begin -->";
|
|
312
81
|
var FABRIC_SECTION_END_MARKER = "<!-- fabric:knowledge-base:end -->";
|
|
313
82
|
var FABRIC_SECTION_REGEX = /(?:\r?\n){0,2}<!-- fabric:knowledge-base:begin -->[\s\S]*?<!-- fabric:knowledge-base:end -->/;
|
|
314
83
|
function readFabricLanguagePreference(projectRoot) {
|
|
315
|
-
const configPath =
|
|
316
|
-
if (!
|
|
84
|
+
const configPath = join(projectRoot, ".fabric", "fabric-config.json");
|
|
85
|
+
if (!existsSync(configPath)) {
|
|
317
86
|
return "match-existing";
|
|
318
87
|
}
|
|
319
88
|
try {
|
|
@@ -344,7 +113,7 @@ ${FABRIC_SECTION_END_MARKER}`;
|
|
|
344
113
|
}
|
|
345
114
|
async function installFabricArchiveSkill(projectRoot, _options = {}) {
|
|
346
115
|
const source = await readTemplate(SKILL_TEMPLATE_REL);
|
|
347
|
-
const targets = SKILL_DESTINATIONS.fabricArchive.map((rel) =>
|
|
116
|
+
const targets = SKILL_DESTINATIONS.fabricArchive.map((rel) => join(projectRoot, rel));
|
|
348
117
|
const results = [];
|
|
349
118
|
for (const target of targets) {
|
|
350
119
|
results.push(await copyTextIdempotent("skill", source, target));
|
|
@@ -353,7 +122,7 @@ async function installFabricArchiveSkill(projectRoot, _options = {}) {
|
|
|
353
122
|
}
|
|
354
123
|
async function installFabricReviewSkill(projectRoot, _options = {}) {
|
|
355
124
|
const source = await readTemplate(SKILL_REVIEW_TEMPLATE_REL);
|
|
356
|
-
const targets = SKILL_DESTINATIONS.fabricReview.map((rel) =>
|
|
125
|
+
const targets = SKILL_DESTINATIONS.fabricReview.map((rel) => join(projectRoot, rel));
|
|
357
126
|
const results = [];
|
|
358
127
|
for (const target of targets) {
|
|
359
128
|
results.push(await copyTextIdempotent("skill-review", source, target));
|
|
@@ -362,7 +131,7 @@ async function installFabricReviewSkill(projectRoot, _options = {}) {
|
|
|
362
131
|
}
|
|
363
132
|
async function installFabricImportSkill(projectRoot, _options = {}) {
|
|
364
133
|
const source = await readTemplate(SKILL_IMPORT_TEMPLATE_REL);
|
|
365
|
-
const targets = SKILL_DESTINATIONS.fabricImport.map((rel) =>
|
|
134
|
+
const targets = SKILL_DESTINATIONS.fabricImport.map((rel) => join(projectRoot, rel));
|
|
366
135
|
const results = [];
|
|
367
136
|
for (const target of targets) {
|
|
368
137
|
results.push(await copyTextIdempotent("skill-import", source, target));
|
|
@@ -371,7 +140,7 @@ async function installFabricImportSkill(projectRoot, _options = {}) {
|
|
|
371
140
|
}
|
|
372
141
|
async function installArchiveHintHook(projectRoot, _options = {}) {
|
|
373
142
|
const source = await readTemplate(HOOK_SCRIPT_TEMPLATE_REL);
|
|
374
|
-
const targets = HOOK_SCRIPT_DESTINATIONS.fabricHint.map((rel) =>
|
|
143
|
+
const targets = HOOK_SCRIPT_DESTINATIONS.fabricHint.map((rel) => join(projectRoot, rel));
|
|
375
144
|
const results = [];
|
|
376
145
|
for (const target of targets) {
|
|
377
146
|
const result = await copyTextIdempotent("hook-script", source, target);
|
|
@@ -387,7 +156,7 @@ async function installArchiveHintHook(projectRoot, _options = {}) {
|
|
|
387
156
|
}
|
|
388
157
|
async function installKnowledgeHintBroadHook(projectRoot, _options = {}) {
|
|
389
158
|
const source = await readTemplate(HOOK_BROAD_SCRIPT_TEMPLATE_REL);
|
|
390
|
-
const targets = HOOK_SCRIPT_DESTINATIONS.knowledgeHintBroad.map((rel) =>
|
|
159
|
+
const targets = HOOK_SCRIPT_DESTINATIONS.knowledgeHintBroad.map((rel) => join(projectRoot, rel));
|
|
391
160
|
const results = [];
|
|
392
161
|
for (const target of targets) {
|
|
393
162
|
const result = await copyTextIdempotent("hook-broad-script", source, target);
|
|
@@ -403,7 +172,7 @@ async function installKnowledgeHintBroadHook(projectRoot, _options = {}) {
|
|
|
403
172
|
}
|
|
404
173
|
async function installKnowledgeHintNarrowHook(projectRoot, _options = {}) {
|
|
405
174
|
const source = await readTemplate(HOOK_NARROW_SCRIPT_TEMPLATE_REL);
|
|
406
|
-
const targets = HOOK_SCRIPT_DESTINATIONS.knowledgeHintNarrow.map((rel) =>
|
|
175
|
+
const targets = HOOK_SCRIPT_DESTINATIONS.knowledgeHintNarrow.map((rel) => join(projectRoot, rel));
|
|
407
176
|
const results = [];
|
|
408
177
|
for (const target of targets) {
|
|
409
178
|
const result = await copyTextIdempotent("hook-narrow-script", source, target);
|
|
@@ -419,7 +188,7 @@ async function installKnowledgeHintNarrowHook(projectRoot, _options = {}) {
|
|
|
419
188
|
}
|
|
420
189
|
async function mergeClaudeCodeHookConfig(projectRoot, _options = {}) {
|
|
421
190
|
const fragment = await readJsonTemplate(CLAUDE_HOOK_CONFIG_TEMPLATE_REL);
|
|
422
|
-
const targetPath =
|
|
191
|
+
const targetPath = join(projectRoot, HOOK_CONFIG_TARGETS.claudeCode);
|
|
423
192
|
return mergeJsonIdempotent(
|
|
424
193
|
"claude-hook-config",
|
|
425
194
|
targetPath,
|
|
@@ -429,7 +198,7 @@ async function mergeClaudeCodeHookConfig(projectRoot, _options = {}) {
|
|
|
429
198
|
}
|
|
430
199
|
async function mergeCodexHookConfig(projectRoot, _options = {}) {
|
|
431
200
|
const fragment = await readJsonTemplate(CODEX_HOOK_CONFIG_TEMPLATE_REL);
|
|
432
|
-
const targetPath =
|
|
201
|
+
const targetPath = join(projectRoot, HOOK_CONFIG_TARGETS.codex);
|
|
433
202
|
return mergeJsonIdempotent(
|
|
434
203
|
"codex-hook-config",
|
|
435
204
|
targetPath,
|
|
@@ -439,7 +208,7 @@ async function mergeCodexHookConfig(projectRoot, _options = {}) {
|
|
|
439
208
|
}
|
|
440
209
|
async function mergeCursorHookConfig(projectRoot, _options = {}) {
|
|
441
210
|
const fragment = await readJsonTemplate(CURSOR_HOOK_CONFIG_TEMPLATE_REL);
|
|
442
|
-
const targetPath =
|
|
211
|
+
const targetPath = join(projectRoot, HOOK_CONFIG_TARGETS.cursor);
|
|
443
212
|
return mergeJsonIdempotent(
|
|
444
213
|
"cursor-hook-config",
|
|
445
214
|
targetPath,
|
|
@@ -451,14 +220,14 @@ async function addFabricKnowledgeBaseSection(projectRoot, fabricLanguage, _optio
|
|
|
451
220
|
const sectionBody = buildFabricKnowledgeBaseSection(fabricLanguage);
|
|
452
221
|
const results = [];
|
|
453
222
|
for (const rel of SECTION_TARGETS) {
|
|
454
|
-
const target =
|
|
455
|
-
if (!
|
|
223
|
+
const target = join(projectRoot, rel);
|
|
224
|
+
if (!existsSync(target)) {
|
|
456
225
|
results.push({ step: "section", path: target, status: "skipped", message: "absent" });
|
|
457
226
|
continue;
|
|
458
227
|
}
|
|
459
228
|
let existing;
|
|
460
229
|
try {
|
|
461
|
-
existing = await
|
|
230
|
+
existing = await readFile(target, "utf8");
|
|
462
231
|
} catch (error) {
|
|
463
232
|
results.push({
|
|
464
233
|
step: "section",
|
|
@@ -503,7 +272,7 @@ ${sectionBody}
|
|
|
503
272
|
return results;
|
|
504
273
|
}
|
|
505
274
|
async function copyTextIdempotent(step, source, target) {
|
|
506
|
-
if (
|
|
275
|
+
if (existsSync(target)) {
|
|
507
276
|
try {
|
|
508
277
|
const existing = readFileSync(target, "utf8");
|
|
509
278
|
if (existing === source) {
|
|
@@ -512,7 +281,7 @@ async function copyTextIdempotent(step, source, target) {
|
|
|
512
281
|
} catch {
|
|
513
282
|
}
|
|
514
283
|
}
|
|
515
|
-
await
|
|
284
|
+
await mkdir(dirname(target), { recursive: true });
|
|
516
285
|
await atomicWriteText(target, source);
|
|
517
286
|
return { step, path: target, status: "written" };
|
|
518
287
|
}
|
|
@@ -522,13 +291,13 @@ async function mergeJsonIdempotent(step, target, fragment, arrayAppendPaths) {
|
|
|
522
291
|
if (jsonEqual(existing, merged)) {
|
|
523
292
|
return { step, path: target, status: "skipped", message: "up-to-date" };
|
|
524
293
|
}
|
|
525
|
-
await
|
|
526
|
-
await
|
|
294
|
+
await mkdir(dirname(target), { recursive: true });
|
|
295
|
+
await atomicWriteJson(target, merged, { indent: 2 });
|
|
527
296
|
return { step, path: target, status: "written" };
|
|
528
297
|
}
|
|
529
298
|
async function readJsonObjectOrEmpty(path) {
|
|
530
299
|
try {
|
|
531
|
-
const raw = await
|
|
300
|
+
const raw = await readFile(path, "utf8");
|
|
532
301
|
if (raw.trim().length === 0) {
|
|
533
302
|
return {};
|
|
534
303
|
}
|
|
@@ -549,7 +318,7 @@ function jsonEqual(a, b) {
|
|
|
549
318
|
}
|
|
550
319
|
async function readTemplate(relativePath) {
|
|
551
320
|
const path = findTemplatePath(relativePath);
|
|
552
|
-
return
|
|
321
|
+
return readFile(path, "utf8");
|
|
553
322
|
}
|
|
554
323
|
async function readJsonTemplate(relativePath) {
|
|
555
324
|
const raw = await readTemplate(relativePath);
|
|
@@ -560,14 +329,14 @@ async function readJsonTemplate(relativePath) {
|
|
|
560
329
|
return parsed;
|
|
561
330
|
}
|
|
562
331
|
function findTemplatePath(relativePath) {
|
|
563
|
-
const startDir =
|
|
564
|
-
let current =
|
|
332
|
+
const startDir = dirname(fileURLToPath(import.meta.url));
|
|
333
|
+
let current = resolve(startDir);
|
|
565
334
|
while (true) {
|
|
566
|
-
const candidate =
|
|
567
|
-
if (
|
|
335
|
+
const candidate = join(current, "templates", relativePath);
|
|
336
|
+
if (existsSync(candidate)) {
|
|
568
337
|
return candidate;
|
|
569
338
|
}
|
|
570
|
-
const parent =
|
|
339
|
+
const parent = dirname(current);
|
|
571
340
|
if (parent === current || parse(current).root === current) {
|
|
572
341
|
throw new Error(`Template not found: templates/${relativePath} (searched up from ${startDir})`);
|
|
573
342
|
}
|
|
@@ -576,12 +345,6 @@ function findTemplatePath(relativePath) {
|
|
|
576
345
|
}
|
|
577
346
|
|
|
578
347
|
export {
|
|
579
|
-
createServerEntry,
|
|
580
|
-
normalizeConfigPath,
|
|
581
|
-
writeJsonClientConfig,
|
|
582
|
-
removeJsonClientConfigEntry,
|
|
583
|
-
ClaudeCodeCLIWriter,
|
|
584
|
-
CursorWriter,
|
|
585
348
|
SKILL_DESTINATIONS,
|
|
586
349
|
HOOK_SCRIPT_DESTINATIONS,
|
|
587
350
|
HOOK_CONFIG_TARGETS,
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
configCmd,
|
|
4
|
+
config_default,
|
|
5
|
+
installMcpClients
|
|
6
|
+
} from "./chunk-AIB54QRT.js";
|
|
7
|
+
import "./chunk-SKSYUHKK.js";
|
|
8
|
+
import "./chunk-6ICJICVU.js";
|
|
9
|
+
export {
|
|
10
|
+
configCmd,
|
|
11
|
+
config_default as default,
|
|
12
|
+
installMcpClients
|
|
13
|
+
};
|