@fenglimg/fabric-cli 2.0.0 → 2.1.0-rc.2

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.
Files changed (86) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +6 -5
  3. package/dist/chunk-BATF4PEJ.js +361 -0
  4. package/dist/{chunk-OBQU6NHO.js → chunk-COI5VDFU.js} +0 -18
  5. package/dist/chunk-F46ORPOA.js +903 -0
  6. package/dist/chunk-HFQVXY6P.js +86 -0
  7. package/dist/chunk-L4Q55UC4.js +52 -0
  8. package/dist/chunk-LFIKMVY7.js +27 -0
  9. package/dist/chunk-MF3OTILQ.js +544 -0
  10. package/dist/chunk-PWLW3B57.js +18 -0
  11. package/dist/chunk-RYAFBNES.js +33 -0
  12. package/dist/chunk-T5RPGCCM.js +40 -0
  13. package/dist/chunk-WU6GAPKH.js +36 -0
  14. package/dist/config-XJIPZNUP.js +13 -0
  15. package/dist/doctor-QVNPHLJK.js +920 -0
  16. package/dist/index.js +23 -8
  17. package/dist/{init-BIRSIOXO.js → install-2HDO5FTQ.js} +807 -705
  18. package/dist/metrics-ACEQFPDU.js +122 -0
  19. package/dist/onboard-coverage-MFCAEBDO.js +220 -0
  20. package/dist/{plan-context-hint-QMUPAXIB.js → plan-context-hint-FC6P3WFE.js} +34 -28
  21. package/dist/scope-explain-2F2R5URO.js +33 -0
  22. package/dist/status-GLQWLWH6.js +23 -0
  23. package/dist/store-XTSE5TY6.js +105 -0
  24. package/dist/sync-BJCWDPNC.js +245 -0
  25. package/dist/uninstall-TAXSUSKH.js +1073 -0
  26. package/dist/whoami-B6AEMSEV.js +31 -0
  27. package/package.json +30 -5
  28. package/templates/hooks/cite-policy-evict.cjs +231 -0
  29. package/templates/hooks/configs/README.md +29 -6
  30. package/templates/hooks/configs/claude-code.json +14 -3
  31. package/templates/hooks/configs/codex-hooks.json +6 -3
  32. package/templates/hooks/configs/cursor-hooks.json +8 -10
  33. package/templates/hooks/fabric-hint.cjs +873 -105
  34. package/templates/hooks/knowledge-hint-broad.cjs +549 -135
  35. package/templates/hooks/knowledge-hint-narrow.cjs +830 -26
  36. package/templates/hooks/lib/banner-i18n.cjs +309 -0
  37. package/templates/hooks/lib/bindings-snapshot-reader.cjs +81 -0
  38. package/templates/hooks/lib/cite-contract-reminder.cjs +179 -0
  39. package/templates/hooks/lib/cite-line-parser.cjs +180 -0
  40. package/templates/hooks/lib/client-adapter.cjs +106 -0
  41. package/templates/hooks/lib/config-cache.cjs +107 -0
  42. package/templates/hooks/lib/state-store.cjs +84 -0
  43. package/templates/hooks/lib/summary-fallback.cjs +210 -0
  44. package/templates/skills/fabric-archive/SKILL.md +97 -419
  45. package/templates/skills/fabric-archive/ref/dry-run-scope.md +16 -0
  46. package/templates/skills/fabric-archive/ref/e5-cron-recap.md +58 -0
  47. package/templates/skills/fabric-archive/ref/i18n-policy.md +86 -0
  48. package/templates/skills/fabric-archive/ref/phase-0-range-resolution.md +156 -0
  49. package/templates/skills/fabric-archive/ref/phase-1-5-onboard.md +218 -0
  50. package/templates/skills/fabric-archive/ref/phase-1-cross-session.md +62 -0
  51. package/templates/skills/fabric-archive/ref/phase-2-5-viability.md +68 -0
  52. package/templates/skills/fabric-archive/ref/phase-3-5-scope.md +108 -0
  53. package/templates/skills/fabric-archive/ref/phase-3-classify.md +63 -0
  54. package/templates/skills/fabric-archive/ref/phase-4-5-emit.md +78 -0
  55. package/templates/skills/fabric-archive/ref/phase-4-mcp-persist.md +89 -0
  56. package/templates/skills/fabric-archive/ref/rc-history.md +38 -0
  57. package/templates/skills/fabric-archive/ref/worked-examples.md +78 -0
  58. package/templates/skills/fabric-import/SKILL.md +77 -514
  59. package/templates/skills/fabric-import/ref/checkpoint-state.md +85 -0
  60. package/templates/skills/fabric-import/ref/i18n-policy.md +79 -0
  61. package/templates/skills/fabric-import/ref/output-contract.md +61 -0
  62. package/templates/skills/fabric-import/ref/phase-2-mining.md +213 -0
  63. package/templates/skills/fabric-import/ref/phase-3-dedup.md +75 -0
  64. package/templates/skills/fabric-import/ref/state-recovery.md +57 -0
  65. package/templates/skills/fabric-import/ref/worked-examples.md +127 -0
  66. package/templates/skills/fabric-review/SKILL.md +90 -284
  67. package/templates/skills/fabric-review/ref/askuserquestion-policy.md +66 -0
  68. package/templates/skills/fabric-review/ref/i18n-policy.md +111 -0
  69. package/templates/skills/fabric-review/ref/modify-flow.md +103 -0
  70. package/templates/skills/fabric-review/ref/output-contract.md +58 -0
  71. package/templates/skills/fabric-review/ref/per-mode-flows.md +155 -0
  72. package/templates/skills/fabric-review/ref/semantic-check.md +26 -0
  73. package/templates/skills/fabric-review/ref/worked-examples.md +95 -0
  74. package/templates/skills/fabric-sync/SKILL.md +46 -0
  75. package/templates/skills/lib/shared-policy.md +69 -0
  76. package/dist/chunk-6ICJICVU.js +0 -10
  77. package/dist/chunk-74SZWYPH.js +0 -658
  78. package/dist/chunk-EYIDD2YS.js +0 -1000
  79. package/dist/doctor-T7JWODKG.js +0 -282
  80. package/dist/hooks-Y74Y5LQS.js +0 -12
  81. package/dist/scan-LMK3UCWL.js +0 -22
  82. package/dist/serve-H554BHLG.js +0 -124
  83. package/templates/agents-md/AGENTS.md.template +0 -59
  84. package/templates/bootstrap/CLAUDE.md +0 -8
  85. package/templates/bootstrap/codex-AGENTS-header.md +0 -6
  86. package/templates/bootstrap/cursor-fabric-bootstrap.mdc +0 -10
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 wangzhichao (fenglimg)
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,24 +1,25 @@
1
1
  # @fenglimg/fabric-cli
2
2
 
3
- `fabric` 是 Fabric 的主命令,`fab` 是永久别名,两者等价。
3
+ `fabric` 是 Fabric CLI 主命令。
4
4
 
5
5
  ## 快速开始
6
6
 
7
7
  1. 在 monorepo 根目录运行 `pnpm install`。
8
8
  2. 用 `pnpm --filter @fenglimg/fabric-cli build` 构建 CLI。
9
- 3. 在目标项目运行 `fabric init`,完成一站式初始化。
9
+ 3. 在目标项目运行 `fabric install`,完成一站式安装。
10
10
  4. 启动 `fabric serve`,再去客户端里验证 `fab_plan_context` 和 `fab_get_knowledge_sections`。
11
11
 
12
- `fabric init` 会自动准备 bootstrap、MCP 配置和 git hooks。公共命令面只保留 `init`、`scan`、`doctor`、`serve`。
12
+ `fabric install` 会自动准备 bootstrap、MCP 配置和 git hooks。公共命令面只保留 `install`、`doctor`、`serve`、`uninstall`、`config`(rc.23 起移除了 baseline scan 机制,知识库唯一合法来源是 Skill 路径:`fabric-archive` / `fabric-import` / `fabric-review`)。
13
13
 
14
14
  ## 常用命令
15
15
 
16
- - `fabric init`
17
- - `fabric scan`
16
+ - `fabric install`
18
17
  - `fabric doctor`
19
18
  - `fabric doctor --json`
20
19
  - `fabric doctor --strict`
21
20
  - `fabric doctor --fix`
22
21
  - `fabric serve`
22
+ - `fabric uninstall`
23
+ - `fabric config`(rc.16 起将提供配置面板;当前为占位提示)
23
24
 
24
25
  `fabric doctor --fix` 只修复确定性的派生状态,例如 `.fabric/agents.meta.json`、`.fabric/.cache/knowledge-test.index.json`、缺失的 `.fabric/events.jsonl` 和 stale hashes;语义冲突、缺失 rule section、未完成的初始化确认仍需要人工处理。
@@ -0,0 +1,361 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ resolveClients
4
+ } from "./chunk-MF3OTILQ.js";
5
+ import {
6
+ t
7
+ } from "./chunk-PWLW3B57.js";
8
+
9
+ // src/commands/config.ts
10
+ import { existsSync, statSync } from "fs";
11
+ import { readFile } from "fs/promises";
12
+ import { join, resolve } from "path";
13
+ import { fileURLToPath } from "url";
14
+ import { cancel, intro, isCancel, log, outro, select, text } from "@clack/prompts";
15
+ import {
16
+ getPanelFields,
17
+ ONBOARD_SLOT_NAMES
18
+ } from "@fenglimg/fabric-shared";
19
+ import { atomicWriteJson } from "@fenglimg/fabric-shared/node/atomic-write";
20
+ import { defineCommand } from "citty";
21
+ async function loadFabricConfig(workspaceRoot) {
22
+ const configPath = resolve(workspaceRoot, "fabric.config.json");
23
+ if (!existsSync(configPath)) {
24
+ return {};
25
+ }
26
+ const parsed = JSON.parse(await readFile(configPath, "utf8"));
27
+ if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
28
+ throw new Error(t("cli.config.errors.expected-object", { path: configPath }));
29
+ }
30
+ return parsed;
31
+ }
32
+ function resolveServerPath(override) {
33
+ if (override) return override;
34
+ if (process.env.FAB_SERVER_PATH) return resolve(process.env.FAB_SERVER_PATH);
35
+ return fileURLToPath(import.meta.resolve("@fenglimg/fabric-server"));
36
+ }
37
+ var PANEL_CONFIG_RELATIVE_PATH = [".fabric", "fabric-config.json"];
38
+ var EXIT_CHOICE = "__exit__";
39
+ async function readOnboardSlotsList(configPath) {
40
+ const raw = await readFile(configPath, "utf8");
41
+ const parsed = JSON.parse(raw);
42
+ if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
43
+ throw new Error(t("cli.config.errors.expected-object", { path: configPath }));
44
+ }
45
+ const obj = parsed;
46
+ const list = obj.onboard_slots_opted_out;
47
+ const optedOut = Array.isArray(list) ? list.filter((v) => typeof v === "string") : [];
48
+ return { config: obj, optedOut };
49
+ }
50
+ function ensureUninitGate(workspaceRoot) {
51
+ const configPath = join(workspaceRoot, ...PANEL_CONFIG_RELATIVE_PATH);
52
+ const fabricDir = join(workspaceRoot, ".fabric");
53
+ const fabricDirOk = existsSync(fabricDir) && statSync(fabricDir).isDirectory();
54
+ const configOk = fabricDirOk && existsSync(configPath);
55
+ if (!configOk) {
56
+ console.error(t("cli.config.errors.uninit-workspace.message"));
57
+ return null;
58
+ }
59
+ return configPath;
60
+ }
61
+ function validateSlotArg(slot) {
62
+ if (slot === void 0 || slot.length === 0) {
63
+ console.error(`Missing required <slot> argument. Valid slots: ${ONBOARD_SLOT_NAMES.join(", ")}.`);
64
+ return null;
65
+ }
66
+ if (!ONBOARD_SLOT_NAMES.includes(slot)) {
67
+ console.error(`Unknown slot "${slot}". Valid slots: ${ONBOARD_SLOT_NAMES.join(", ")}.`);
68
+ return null;
69
+ }
70
+ return slot;
71
+ }
72
+ var dismissSlotCmd = defineCommand({
73
+ meta: {
74
+ name: "dismiss-slot",
75
+ description: "Add an S5 onboard slot to the opted-out list (fabric-archive Skill onboard phase invokes this).",
76
+ hidden: true
77
+ },
78
+ args: {
79
+ slot: {
80
+ type: "positional",
81
+ description: "Slot name to dismiss (one of the locked S5 set).",
82
+ required: true
83
+ },
84
+ target: {
85
+ type: "string",
86
+ description: "Override the project root (defaults to cwd)."
87
+ }
88
+ },
89
+ async run({ args }) {
90
+ const slot = validateSlotArg(args.slot);
91
+ if (slot === null) {
92
+ process.exitCode = 1;
93
+ return;
94
+ }
95
+ const workspaceRoot = resolve(args.target ?? process.cwd());
96
+ const configPath = ensureUninitGate(workspaceRoot);
97
+ if (configPath === null) {
98
+ process.exitCode = 1;
99
+ return;
100
+ }
101
+ try {
102
+ const { config, optedOut } = await readOnboardSlotsList(configPath);
103
+ if (optedOut.includes(slot)) {
104
+ console.log(`Slot "${slot}" already opted out; no-op.`);
105
+ return;
106
+ }
107
+ const next = [...optedOut, slot];
108
+ const merged = { ...config, onboard_slots_opted_out: next };
109
+ await atomicWriteJson(configPath, merged);
110
+ console.log(`Dismissed onboard slot "${slot}". Run \`fabric config onboard-reset ${slot}\` to re-open.`);
111
+ } catch (err) {
112
+ const message = err instanceof Error ? err.message : String(err);
113
+ console.error(`dismiss-slot failed: ${message}`);
114
+ process.exitCode = 1;
115
+ }
116
+ }
117
+ });
118
+ var onboardResetCmd = defineCommand({
119
+ meta: {
120
+ name: "onboard-reset",
121
+ description: "Remove an S5 onboard slot from the opted-out list \u2014 re-opens the slot for future fabric-archive onboard prompts.",
122
+ hidden: true
123
+ },
124
+ args: {
125
+ slot: {
126
+ type: "positional",
127
+ description: "Slot name to reset (one of the locked S5 set).",
128
+ required: true
129
+ },
130
+ target: {
131
+ type: "string",
132
+ description: "Override the project root (defaults to cwd)."
133
+ }
134
+ },
135
+ async run({ args }) {
136
+ const slot = validateSlotArg(args.slot);
137
+ if (slot === null) {
138
+ process.exitCode = 1;
139
+ return;
140
+ }
141
+ const workspaceRoot = resolve(args.target ?? process.cwd());
142
+ const configPath = ensureUninitGate(workspaceRoot);
143
+ if (configPath === null) {
144
+ process.exitCode = 1;
145
+ return;
146
+ }
147
+ try {
148
+ const { config, optedOut } = await readOnboardSlotsList(configPath);
149
+ if (!optedOut.includes(slot)) {
150
+ console.log(`Slot "${slot}" not opted out; no-op.`);
151
+ return;
152
+ }
153
+ const next = optedOut.filter((s) => s !== slot);
154
+ const merged = { ...config, onboard_slots_opted_out: next };
155
+ await atomicWriteJson(configPath, merged);
156
+ console.log(`Reset onboard slot "${slot}"; it will appear in \`fabric onboard-coverage\` as missing again.`);
157
+ } catch (err) {
158
+ const message = err instanceof Error ? err.message : String(err);
159
+ console.error(`onboard-reset failed: ${message}`);
160
+ process.exitCode = 1;
161
+ }
162
+ }
163
+ });
164
+ var configCmd = defineCommand({
165
+ meta: {
166
+ name: "config",
167
+ description: t("cli.config.description")
168
+ },
169
+ args: {
170
+ target: {
171
+ type: "string",
172
+ description: t("cli.config.args.target.description"),
173
+ valueHint: "path"
174
+ }
175
+ },
176
+ subCommands: {
177
+ "dismiss-slot": dismissSlotCmd,
178
+ "onboard-reset": onboardResetCmd
179
+ },
180
+ async run({ args }) {
181
+ const argvSub = process.argv[3];
182
+ if (argvSub === "dismiss-slot" || argvSub === "onboard-reset") {
183
+ return;
184
+ }
185
+ const workspaceRoot = resolve(args.target ?? process.cwd());
186
+ const configPath = join(workspaceRoot, ...PANEL_CONFIG_RELATIVE_PATH);
187
+ const fabricDir = join(workspaceRoot, ".fabric");
188
+ const fabricDirOk = existsSync(fabricDir) && statSync(fabricDir).isDirectory();
189
+ const configOk = fabricDirOk && existsSync(configPath);
190
+ if (!configOk) {
191
+ console.error(t("cli.config.errors.uninit-workspace.message"));
192
+ process.exitCode = 1;
193
+ return;
194
+ }
195
+ if (!isInteractiveConfig()) {
196
+ console.log(t("cli.config.intro"));
197
+ console.log(t("cli.config.non-tty-notice"));
198
+ return;
199
+ }
200
+ intro(t("cli.config.intro"));
201
+ let edited = false;
202
+ while (true) {
203
+ const current = await readPanelConfig(configPath);
204
+ const fields = getPanelFields();
205
+ const fieldChoice = await select({
206
+ message: t("cli.config.menu.field-select"),
207
+ options: [
208
+ ...fields.map((field2) => ({
209
+ value: field2.key,
210
+ label: formatFieldMenuLabel(field2, current)
211
+ })),
212
+ { value: EXIT_CHOICE, label: t("cli.config.menu.exit") }
213
+ ]
214
+ });
215
+ if (isCancel(fieldChoice)) {
216
+ cancel(t("cli.config.cancel"));
217
+ return;
218
+ }
219
+ if (fieldChoice === EXIT_CHOICE) {
220
+ outro(edited ? t("cli.config.outro") : t("cli.config.outro-no-changes"));
221
+ return;
222
+ }
223
+ const field = fields.find((f) => f.key === fieldChoice);
224
+ if (!field) {
225
+ log.warn(t("cli.config.errors.unknown-field"));
226
+ continue;
227
+ }
228
+ const newValue = await promptFieldValue(field, current);
229
+ if (newValue === CANCELLED) {
230
+ cancel(t("cli.config.cancel"));
231
+ return;
232
+ }
233
+ if (newValue === SKIPPED) {
234
+ continue;
235
+ }
236
+ try {
237
+ const refreshed = await readPanelConfig(configPath);
238
+ const merged = { ...refreshed, [field.key]: newValue };
239
+ await atomicWriteJson(configPath, merged);
240
+ edited = true;
241
+ log.success(
242
+ t("cli.config.write.success", {
243
+ key: field.key,
244
+ value: field.format_for_display(newValue)
245
+ })
246
+ );
247
+ } catch (err) {
248
+ const message = err instanceof Error ? err.message : String(err);
249
+ log.error(t("cli.config.write.failure", { message }));
250
+ }
251
+ }
252
+ }
253
+ });
254
+ var config_default = configCmd;
255
+ var CANCELLED = /* @__PURE__ */ Symbol("config-cancelled");
256
+ var SKIPPED = /* @__PURE__ */ Symbol("config-skipped");
257
+ async function promptFieldValue(field, current) {
258
+ const currentValue = current[field.key];
259
+ const currentDisplay = field.format_for_display(currentValue);
260
+ if (field.widget === "select") {
261
+ const enumValues = field.enum_values ?? [];
262
+ if (enumValues.length === 0) {
263
+ log.warn(t("cli.config.errors.no-enum-options"));
264
+ return SKIPPED;
265
+ }
266
+ const initialValue = enumValues.includes(String(currentValue)) ? String(currentValue) : enumValues.includes(String(field.default)) ? String(field.default) : enumValues[0];
267
+ const picked = await select({
268
+ message: t("cli.config.prompt.select", {
269
+ key: field.key,
270
+ current: currentDisplay
271
+ }),
272
+ options: enumValues.map((value) => ({ value, label: value })),
273
+ initialValue
274
+ });
275
+ if (isCancel(picked)) {
276
+ return CANCELLED;
277
+ }
278
+ const result = field.validate(String(picked));
279
+ if (!result.ok) {
280
+ log.error(result.error);
281
+ return SKIPPED;
282
+ }
283
+ return result.value;
284
+ }
285
+ const entered = await text({
286
+ message: t("cli.config.prompt.text", {
287
+ key: field.key,
288
+ current: currentDisplay
289
+ }),
290
+ placeholder: currentDisplay,
291
+ initialValue: currentDisplay,
292
+ validate(raw) {
293
+ const result = field.validate(raw ?? "");
294
+ return result.ok ? void 0 : result.error;
295
+ }
296
+ });
297
+ if (isCancel(entered)) {
298
+ return CANCELLED;
299
+ }
300
+ const finalResult = field.validate(String(entered));
301
+ if (!finalResult.ok) {
302
+ log.error(finalResult.error);
303
+ return SKIPPED;
304
+ }
305
+ return finalResult.value;
306
+ }
307
+ function formatFieldMenuLabel(field, current) {
308
+ const key = field.key;
309
+ const rawValue = current[key];
310
+ const display = field.format_for_display(rawValue);
311
+ const isDefault = rawValue === void 0 || rawValue === null;
312
+ const labelText = t(field.label_i18n_key);
313
+ const valueLabel = isDefault ? `${display} ${t("cli.config.value.default-marker")}` : display;
314
+ return `[${field.group}] ${key} (${labelText}) \u2014 ${t("cli.config.value.current", { value: valueLabel })}`;
315
+ }
316
+ async function readPanelConfig(configPath) {
317
+ const raw = await readFile(configPath, "utf8");
318
+ const parsed = JSON.parse(raw);
319
+ if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
320
+ throw new Error(t("cli.config.errors.expected-object", { path: configPath }));
321
+ }
322
+ return parsed;
323
+ }
324
+ function isInteractiveConfig() {
325
+ return Boolean(process.stdin.isTTY) && Boolean(process.stdout.isTTY) && Boolean(process.stderr.isTTY);
326
+ }
327
+ async function installMcpClients(target, options = {}) {
328
+ const workspaceRoot = resolve(target);
329
+ const fabricConfig = await loadFabricConfig(workspaceRoot);
330
+ const selectedClients = options.clients === void 0 ? null : new Set(options.clients);
331
+ const serverPath = resolveServerPath(options.localServerPath);
332
+ const writers = resolveClients(workspaceRoot, fabricConfig, { claudeMcpScope: options.claudeMcpScope }).filter(
333
+ (writer) => selectedClients === null ? true : selectedClients.has(writer.clientKind)
334
+ );
335
+ const installed = [];
336
+ const skipped = [];
337
+ const details = [];
338
+ for (const writer of writers) {
339
+ const configPath = await writer.detect(workspaceRoot);
340
+ if (configPath === null) {
341
+ skipped.push(writer.clientKind);
342
+ details.push({ client: writer.clientKind, path: null, action: "skipped" });
343
+ continue;
344
+ }
345
+ if (options.dryRun) {
346
+ skipped.push(writer.clientKind);
347
+ details.push({ client: writer.clientKind, path: configPath, action: "dry-run" });
348
+ continue;
349
+ }
350
+ await writer.write(serverPath, workspaceRoot);
351
+ installed.push(writer.clientKind);
352
+ details.push({ client: writer.clientKind, path: configPath, action: "wrote" });
353
+ }
354
+ return { installed, skipped, details };
355
+ }
356
+
357
+ export {
358
+ configCmd,
359
+ config_default,
360
+ installMcpClients
361
+ };
@@ -3,26 +3,12 @@
3
3
  // src/dev-mode.ts
4
4
  import { existsSync, readFileSync } from "fs";
5
5
  import { isAbsolute, join, resolve } from "path";
6
- function readFabricConfig(workspaceRoot = process.cwd()) {
7
- const configPath = join(workspaceRoot, "fabric.config.json");
8
- if (!existsSync(configPath)) {
9
- return {};
10
- }
11
- const parsed = JSON.parse(readFileSync(configPath, "utf8"));
12
- if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
13
- throw new Error(`Expected object in ${configPath}`);
14
- }
15
- return parsed;
16
- }
17
6
  function resolveDevMode(cliTarget, workspaceRoot = process.cwd()) {
18
7
  const envTarget = normalizeTarget(process.env.EXTERNAL_FIXTURE_PATH, workspaceRoot);
19
- const fabricConfig = readFabricConfig(workspaceRoot);
20
- const configTarget = normalizeTarget(fabricConfig.externalFixturePath, workspaceRoot);
21
8
  const directTarget = normalizeTarget(cliTarget, workspaceRoot);
22
9
  const chain = [
23
10
  formatResolutionStep("cliTarget", directTarget),
24
11
  formatResolutionStep("EXTERNAL_FIXTURE_PATH", envTarget),
25
- formatResolutionStep("fabric.config.json.externalFixturePath", configTarget),
26
12
  formatResolutionStep("process.cwd()", workspaceRoot)
27
13
  ];
28
14
  if (directTarget !== void 0) {
@@ -31,9 +17,6 @@ function resolveDevMode(cliTarget, workspaceRoot = process.cwd()) {
31
17
  if (envTarget !== void 0) {
32
18
  return { target: envTarget, source: "env", chain };
33
19
  }
34
- if (configTarget !== void 0) {
35
- return { target: configTarget, source: "config", chain };
36
- }
37
20
  return { target: workspaceRoot, source: "cwd", chain };
38
21
  }
39
22
  function createDebugLogger(debug) {
@@ -57,7 +40,6 @@ function formatResolutionStep(source, value) {
57
40
  }
58
41
 
59
42
  export {
60
- readFabricConfig,
61
43
  resolveDevMode,
62
44
  createDebugLogger
63
45
  };