@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.
@@ -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 as existsSync2, readFileSync } from "fs";
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 { homedir } from "os";
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: ["events.Stop", "events.SessionStart", "events.PreToolUse"]
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", join2(".cursor", "rules")];
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 = join2(projectRoot, ".fabric", "fabric-config.json");
316
- if (!existsSync2(configPath)) {
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) => join2(projectRoot, 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) => join2(projectRoot, 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) => join2(projectRoot, 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) => join2(projectRoot, 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) => join2(projectRoot, 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) => join2(projectRoot, 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 = join2(projectRoot, HOOK_CONFIG_TARGETS.claudeCode);
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 = join2(projectRoot, HOOK_CONFIG_TARGETS.codex);
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 = join2(projectRoot, HOOK_CONFIG_TARGETS.cursor);
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 = join2(projectRoot, rel);
455
- if (!existsSync2(target)) {
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 readFile2(target, "utf8");
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 (existsSync2(target)) {
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 mkdir2(dirname2(target), { recursive: true });
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 mkdir2(dirname2(target), { recursive: true });
526
- await atomicWriteJson2(target, merged, { indent: 2 });
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 readFile2(path, "utf8");
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 readFile2(path, "utf8");
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 = dirname2(fileURLToPath(import.meta.url));
564
- let current = resolve2(startDir);
332
+ const startDir = dirname(fileURLToPath(import.meta.url));
333
+ let current = resolve(startDir);
565
334
  while (true) {
566
- const candidate = join2(current, "templates", relativePath);
567
- if (existsSync2(candidate)) {
335
+ const candidate = join(current, "templates", relativePath);
336
+ if (existsSync(candidate)) {
568
337
  return candidate;
569
338
  }
570
- const parent = dirname2(current);
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
+ };