@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.
- package/LICENSE +21 -0
- package/README.md +6 -5
- package/dist/chunk-BATF4PEJ.js +361 -0
- package/dist/{chunk-OBQU6NHO.js → chunk-COI5VDFU.js} +0 -18
- package/dist/chunk-F46ORPOA.js +903 -0
- package/dist/chunk-HFQVXY6P.js +86 -0
- package/dist/chunk-L4Q55UC4.js +52 -0
- package/dist/chunk-LFIKMVY7.js +27 -0
- package/dist/chunk-MF3OTILQ.js +544 -0
- package/dist/chunk-PWLW3B57.js +18 -0
- package/dist/chunk-RYAFBNES.js +33 -0
- package/dist/chunk-T5RPGCCM.js +40 -0
- package/dist/chunk-WU6GAPKH.js +36 -0
- package/dist/config-XJIPZNUP.js +13 -0
- package/dist/doctor-QVNPHLJK.js +920 -0
- package/dist/index.js +23 -8
- package/dist/{init-BIRSIOXO.js → install-2HDO5FTQ.js} +807 -705
- package/dist/metrics-ACEQFPDU.js +122 -0
- package/dist/onboard-coverage-MFCAEBDO.js +220 -0
- package/dist/{plan-context-hint-QMUPAXIB.js → plan-context-hint-FC6P3WFE.js} +34 -28
- package/dist/scope-explain-2F2R5URO.js +33 -0
- package/dist/status-GLQWLWH6.js +23 -0
- package/dist/store-XTSE5TY6.js +105 -0
- package/dist/sync-BJCWDPNC.js +245 -0
- package/dist/uninstall-TAXSUSKH.js +1073 -0
- package/dist/whoami-B6AEMSEV.js +31 -0
- package/package.json +30 -5
- package/templates/hooks/cite-policy-evict.cjs +231 -0
- package/templates/hooks/configs/README.md +29 -6
- package/templates/hooks/configs/claude-code.json +14 -3
- package/templates/hooks/configs/codex-hooks.json +6 -3
- package/templates/hooks/configs/cursor-hooks.json +8 -10
- package/templates/hooks/fabric-hint.cjs +873 -105
- package/templates/hooks/knowledge-hint-broad.cjs +549 -135
- package/templates/hooks/knowledge-hint-narrow.cjs +830 -26
- package/templates/hooks/lib/banner-i18n.cjs +309 -0
- package/templates/hooks/lib/bindings-snapshot-reader.cjs +81 -0
- package/templates/hooks/lib/cite-contract-reminder.cjs +179 -0
- package/templates/hooks/lib/cite-line-parser.cjs +180 -0
- package/templates/hooks/lib/client-adapter.cjs +106 -0
- package/templates/hooks/lib/config-cache.cjs +107 -0
- package/templates/hooks/lib/state-store.cjs +84 -0
- package/templates/hooks/lib/summary-fallback.cjs +210 -0
- package/templates/skills/fabric-archive/SKILL.md +97 -419
- package/templates/skills/fabric-archive/ref/dry-run-scope.md +16 -0
- package/templates/skills/fabric-archive/ref/e5-cron-recap.md +58 -0
- package/templates/skills/fabric-archive/ref/i18n-policy.md +86 -0
- package/templates/skills/fabric-archive/ref/phase-0-range-resolution.md +156 -0
- package/templates/skills/fabric-archive/ref/phase-1-5-onboard.md +218 -0
- package/templates/skills/fabric-archive/ref/phase-1-cross-session.md +62 -0
- package/templates/skills/fabric-archive/ref/phase-2-5-viability.md +68 -0
- package/templates/skills/fabric-archive/ref/phase-3-5-scope.md +108 -0
- package/templates/skills/fabric-archive/ref/phase-3-classify.md +63 -0
- package/templates/skills/fabric-archive/ref/phase-4-5-emit.md +78 -0
- package/templates/skills/fabric-archive/ref/phase-4-mcp-persist.md +89 -0
- package/templates/skills/fabric-archive/ref/rc-history.md +38 -0
- package/templates/skills/fabric-archive/ref/worked-examples.md +78 -0
- package/templates/skills/fabric-import/SKILL.md +77 -514
- package/templates/skills/fabric-import/ref/checkpoint-state.md +85 -0
- package/templates/skills/fabric-import/ref/i18n-policy.md +79 -0
- package/templates/skills/fabric-import/ref/output-contract.md +61 -0
- package/templates/skills/fabric-import/ref/phase-2-mining.md +213 -0
- package/templates/skills/fabric-import/ref/phase-3-dedup.md +75 -0
- package/templates/skills/fabric-import/ref/state-recovery.md +57 -0
- package/templates/skills/fabric-import/ref/worked-examples.md +127 -0
- package/templates/skills/fabric-review/SKILL.md +90 -284
- package/templates/skills/fabric-review/ref/askuserquestion-policy.md +66 -0
- package/templates/skills/fabric-review/ref/i18n-policy.md +111 -0
- package/templates/skills/fabric-review/ref/modify-flow.md +103 -0
- package/templates/skills/fabric-review/ref/output-contract.md +58 -0
- package/templates/skills/fabric-review/ref/per-mode-flows.md +155 -0
- package/templates/skills/fabric-review/ref/semantic-check.md +26 -0
- package/templates/skills/fabric-review/ref/worked-examples.md +95 -0
- package/templates/skills/fabric-sync/SKILL.md +46 -0
- package/templates/skills/lib/shared-policy.md +69 -0
- package/dist/chunk-6ICJICVU.js +0 -10
- package/dist/chunk-74SZWYPH.js +0 -658
- package/dist/chunk-EYIDD2YS.js +0 -1000
- package/dist/doctor-T7JWODKG.js +0 -282
- package/dist/hooks-Y74Y5LQS.js +0 -12
- package/dist/scan-LMK3UCWL.js +0 -22
- package/dist/serve-H554BHLG.js +0 -124
- package/templates/agents-md/AGENTS.md.template +0 -59
- package/templates/bootstrap/CLAUDE.md +0 -8
- package/templates/bootstrap/codex-AGENTS-header.md +0 -6
- 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
|
|
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
|
|
9
|
+
3. 在目标项目运行 `fabric install`,完成一站式安装。
|
|
10
10
|
4. 启动 `fabric serve`,再去客户端里验证 `fab_plan_context` 和 `fab_get_knowledge_sections`。
|
|
11
11
|
|
|
12
|
-
`fabric
|
|
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
|
|
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
|
};
|