@fenglimg/fabric-cli 2.2.0-rc.4 → 2.2.0-rc.8
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 +8 -5
- package/dist/{chunk-5JG4QJLO.js → chunk-27HK6H5Y.js} +10 -5
- package/dist/{chunk-F6ITRM7T.js → chunk-2KBCTMID.js} +29 -6
- package/dist/{chunk-XC5RUHLK.js → chunk-3IOLS5EK.js} +23 -38
- package/dist/{chunk-XHHCRDIR.js → chunk-CMDW3PYK.js} +105 -220
- package/dist/chunk-FEOPLBGA.js +150 -0
- package/dist/{chunk-XCBVSGCS.js → chunk-FNHDQTPC.js} +1 -10
- package/dist/{chunk-2CY4BMTH.js → chunk-HORSMSZL.js} +9 -5
- package/dist/{doctor-U5W4CX5I.js → chunk-JTHWLUD3.js} +103 -51
- package/dist/{chunk-BO4XIZWZ.js → chunk-NLNH64A3.js} +5 -18
- package/dist/{chunk-H3FE6VIK.js → chunk-PTGQAZEW.js} +13 -3
- package/dist/chunk-QFIVFZRH.js +13 -0
- package/dist/{chunk-5SSNE5GM.js → chunk-QPAW6IYT.js} +125 -39
- package/dist/{chunk-COI5VDFU.js → chunk-WA3DYGSY.js} +1 -2
- package/dist/{plan-context-hint-CHVZGOZ5.js → chunk-YM4XATJF.js} +29 -4
- package/dist/{config-VJMXCLXW.js → config-A3LTECAY.js} +4 -3
- package/dist/context-7NUKXDB6.js +117 -0
- package/dist/doctor-REZDNH4A.js +24 -0
- package/dist/index.d.ts +2 -2
- package/dist/index.js +131 -21
- package/dist/info-7FKBTMVO.js +139 -0
- package/dist/install-v2-2COC3DO3.js +3277 -0
- package/dist/{metrics-RER6NLFC.js → metrics-HMFH4YHK.js} +1 -1
- package/dist/{onboard-coverage-JWQWDZW7.js → onboard-coverage-XSG77LL3.js} +48 -27
- package/dist/plan-context-hint-G75R4P4J.js +12 -0
- package/dist/{scope-explain-BWRWBCCP.js → scope-explain-HLJZ2M33.js} +3 -2
- package/dist/{status-7UFLWRX7.js → status-4R3TM4FJ.js} +8 -5
- package/dist/{store-ZEZMQVG7.js → store-HOCORVL3.js} +96 -350
- package/dist/{sync-EA5HZMXM.js → sync-DT5UJMMR.js} +36 -13
- package/dist/{uninstall-F75MPKQC.js → uninstall-62F4LNKI.js} +62 -140
- package/dist/{whoami-3FRWYGML.js → whoami-ITGEFWH4.js} +9 -7
- package/package.json +7 -5
- package/templates/hooks/cite-policy-evict.cjs +5 -5
- package/templates/hooks/configs/README.md +14 -27
- package/templates/hooks/configs/claude-code.json +1 -1
- package/templates/hooks/configs/codex-hooks.json +3 -3
- package/templates/hooks/fabric-hint.cjs +301 -161
- package/templates/hooks/knowledge-hint-broad.cjs +426 -207
- package/templates/hooks/knowledge-hint-narrow.cjs +56 -56
- package/templates/hooks/lib/banner-i18n.cjs +31 -0
- package/templates/hooks/lib/bindings-snapshot-reader.cjs +117 -7
- package/templates/hooks/lib/cite-line-parser.cjs +12 -20
- package/templates/hooks/lib/client-adapter.cjs +66 -7
- package/templates/hooks/lib/nudge-policy.cjs +117 -0
- package/templates/hooks/lib/state-store.cjs +60 -0
- package/templates/hooks/lib/summary-fallback.cjs +82 -19
- package/templates/hooks/post-tooluse-mutation.cjs +112 -11
- package/templates/skills/fabric/SKILL.md +94 -0
- package/templates/skills/fabric-archive/SKILL.md +29 -26
- package/templates/skills/fabric-archive/ref/dry-run-scope.md +1 -1
- package/templates/skills/fabric-archive/ref/i18n-policy.md +2 -3
- package/templates/skills/fabric-archive/ref/phase-1-5-onboard.md +2 -3
- package/templates/skills/fabric-archive/ref/phase-1-cross-session.md +1 -1
- package/templates/skills/fabric-archive/ref/phase-2-5-viability.md +1 -1
- package/templates/skills/fabric-archive/ref/phase-3-6-related-edges.md +18 -0
- package/templates/skills/fabric-archive/ref/phase-3-7-semantic-scope.md +47 -0
- package/templates/skills/fabric-audit/SKILL.md +13 -3
- package/templates/skills/fabric-connect/SKILL.md +3 -3
- package/templates/skills/fabric-import/SKILL.md +7 -7
- package/templates/skills/fabric-import/ref/i18n-policy.md +2 -3
- package/templates/skills/fabric-import/ref/state-recovery.md +1 -2
- package/templates/skills/fabric-review/SKILL.md +5 -5
- package/templates/skills/fabric-review/ref/cite-contract.md +1 -1
- package/templates/skills/fabric-review/ref/i18n-policy.md +2 -3
- package/templates/skills/fabric-review/ref/output-contract.md +1 -1
- package/templates/skills/fabric-review/ref/per-mode-flows.md +2 -2
- package/templates/skills/fabric-review/ref/worked-examples.md +1 -1
- package/templates/skills/fabric-store/SKILL.md +1 -1
- package/templates/skills/fabric-sync/SKILL.md +1 -1
- package/templates/skills/lib/shared-policy.md +2 -2
- package/dist/install-7XJ64WSC.js +0 -2743
- package/templates/hooks/configs/cursor-hooks.json +0 -30
- package/templates/hooks/lib/cite-contract-reminder.cjs +0 -179
|
@@ -1,15 +1,19 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
paint
|
|
4
|
+
} from "./chunk-NLNH64A3.js";
|
|
2
5
|
import {
|
|
3
6
|
regenerateBindingsSnapshot
|
|
4
|
-
} from "./chunk-
|
|
7
|
+
} from "./chunk-PTGQAZEW.js";
|
|
5
8
|
import "./chunk-EOT63RDH.js";
|
|
6
|
-
import
|
|
7
|
-
getProjectTranslator
|
|
8
|
-
} from "./chunk-2CY4BMTH.js";
|
|
9
|
+
import "./chunk-QFIVFZRH.js";
|
|
9
10
|
import {
|
|
10
11
|
loadGlobalConfig,
|
|
11
12
|
resolveGlobalRoot
|
|
12
|
-
} from "./chunk-
|
|
13
|
+
} from "./chunk-FNHDQTPC.js";
|
|
14
|
+
import {
|
|
15
|
+
getProjectTranslator
|
|
16
|
+
} from "./chunk-HORSMSZL.js";
|
|
13
17
|
|
|
14
18
|
// src/commands/sync.ts
|
|
15
19
|
import { defineCommand } from "citty";
|
|
@@ -18,7 +22,7 @@ import { defineCommand } from "citty";
|
|
|
18
22
|
import { execFileSync } from "child_process";
|
|
19
23
|
import { existsSync, mkdirSync, readFileSync, renameSync, rmSync, writeFileSync } from "fs";
|
|
20
24
|
import { join } from "path";
|
|
21
|
-
import { GLOBAL_STATE_DIR,
|
|
25
|
+
import { GLOBAL_STATE_DIR, storeRelativePathForMount } from "@fenglimg/fabric-shared";
|
|
22
26
|
import { GenericIOError } from "@fenglimg/fabric-shared/errors";
|
|
23
27
|
|
|
24
28
|
// src/sync/state-machine.ts
|
|
@@ -273,7 +277,7 @@ function runStartSync(options) {
|
|
|
273
277
|
const session = planSync(
|
|
274
278
|
syncable.map((store) => ({ alias: store.alias, store_uuid: store.store_uuid }))
|
|
275
279
|
);
|
|
276
|
-
const storeDirOf = (
|
|
280
|
+
const storeDirOf = makeStoreDirResolver(globalRoot, config.stores);
|
|
277
281
|
const pushableAliases = pushableAliasesOf(config);
|
|
278
282
|
const walked = walkPending(
|
|
279
283
|
session,
|
|
@@ -290,6 +294,16 @@ function pushableAliasesOf(config) {
|
|
|
290
294
|
config.stores.filter((store) => store.remote !== void 0 && (store.writable ?? true)).map((store) => store.alias)
|
|
291
295
|
);
|
|
292
296
|
}
|
|
297
|
+
function makeStoreDirResolver(globalRoot, stores) {
|
|
298
|
+
return (status) => join(
|
|
299
|
+
globalRoot,
|
|
300
|
+
storeRelativePathForMount(
|
|
301
|
+
stores.find((store) => store.store_uuid === status.store_uuid) ?? {
|
|
302
|
+
store_uuid: status.store_uuid
|
|
303
|
+
}
|
|
304
|
+
)
|
|
305
|
+
);
|
|
306
|
+
}
|
|
293
307
|
function runContinueSync(options) {
|
|
294
308
|
const globalRoot = options.globalRoot ?? resolveGlobalRoot();
|
|
295
309
|
const session = loadSession(globalRoot);
|
|
@@ -304,9 +318,10 @@ function runContinueSync(options) {
|
|
|
304
318
|
actionHint: "The sync is not paused on a conflict; there is nothing to resume."
|
|
305
319
|
});
|
|
306
320
|
}
|
|
307
|
-
const
|
|
321
|
+
const resumeConfig = loadGlobalConfig(globalRoot) ?? { stores: [] };
|
|
322
|
+
const storeDirOf = makeStoreDirResolver(globalRoot, resumeConfig.stores);
|
|
308
323
|
(options.rebaseContinue ?? defaultRebaseContinue)(storeDirOf(conflicted));
|
|
309
|
-
const pushableAliases = pushableAliasesOf(
|
|
324
|
+
const pushableAliases = pushableAliasesOf(resumeConfig);
|
|
310
325
|
const push = options.push ?? defaultPush;
|
|
311
326
|
let advanced;
|
|
312
327
|
if (pushableAliases.has(conflicted.alias)) {
|
|
@@ -343,9 +358,10 @@ function runAbortSync(options) {
|
|
|
343
358
|
actionHint: "The sync is not paused on a conflict; there is nothing to resume."
|
|
344
359
|
});
|
|
345
360
|
}
|
|
346
|
-
const
|
|
361
|
+
const resumeConfig = loadGlobalConfig(globalRoot) ?? { stores: [] };
|
|
362
|
+
const storeDirOf = makeStoreDirResolver(globalRoot, resumeConfig.stores);
|
|
347
363
|
(options.rebaseAbort ?? defaultRebaseAbort)(storeDirOf(conflicted));
|
|
348
|
-
const pushableAliases = pushableAliasesOf(
|
|
364
|
+
const pushableAliases = pushableAliasesOf(resumeConfig);
|
|
349
365
|
const resumed = walkPending(
|
|
350
366
|
abortSync(session),
|
|
351
367
|
storeDirOf,
|
|
@@ -370,7 +386,7 @@ function report(result, projectRoot) {
|
|
|
370
386
|
console.log(t("cli.sync.paused"));
|
|
371
387
|
}
|
|
372
388
|
}
|
|
373
|
-
var
|
|
389
|
+
var syncCommand = defineCommand({
|
|
374
390
|
meta: { name: "sync", description: "Pull --rebase + push every mounted store; resume conflicts" },
|
|
375
391
|
args: {
|
|
376
392
|
continue: { type: "boolean", description: "Resume after resolving a rebase conflict" },
|
|
@@ -378,6 +394,11 @@ var sync_default = defineCommand({
|
|
|
378
394
|
},
|
|
379
395
|
run({ args }) {
|
|
380
396
|
const projectRoot = process.cwd();
|
|
397
|
+
if (args.continue === true && args.abort === true) {
|
|
398
|
+
console.error(paint.error("fabric sync: --continue and --abort cannot be used together"));
|
|
399
|
+
process.exitCode = 1;
|
|
400
|
+
return;
|
|
401
|
+
}
|
|
381
402
|
const options = { projectRoot, now: (/* @__PURE__ */ new Date()).toISOString() };
|
|
382
403
|
if (args.continue === true) {
|
|
383
404
|
report(runContinueSync(options), projectRoot);
|
|
@@ -390,6 +411,8 @@ var sync_default = defineCommand({
|
|
|
390
411
|
report(runStartSync(options), projectRoot);
|
|
391
412
|
}
|
|
392
413
|
});
|
|
414
|
+
var sync_default = syncCommand;
|
|
393
415
|
export {
|
|
394
|
-
sync_default as default
|
|
416
|
+
sync_default as default,
|
|
417
|
+
syncCommand
|
|
395
418
|
};
|
|
@@ -7,28 +7,28 @@ import {
|
|
|
7
7
|
HOOK_SCRIPT_DESTINATIONS,
|
|
8
8
|
SKILL_DESTINATIONS,
|
|
9
9
|
fabricAgentsSnapshotPath
|
|
10
|
-
} from "./chunk-
|
|
11
|
-
import {
|
|
12
|
-
paint
|
|
13
|
-
} from "./chunk-BO4XIZWZ.js";
|
|
10
|
+
} from "./chunk-CMDW3PYK.js";
|
|
14
11
|
import {
|
|
15
12
|
createDebugLogger,
|
|
16
13
|
resolveDevMode
|
|
17
|
-
} from "./chunk-
|
|
14
|
+
} from "./chunk-WA3DYGSY.js";
|
|
18
15
|
import {
|
|
19
16
|
detectClientSupports,
|
|
20
17
|
resolveClients
|
|
21
|
-
} from "./chunk-
|
|
18
|
+
} from "./chunk-3IOLS5EK.js";
|
|
19
|
+
import {
|
|
20
|
+
paint
|
|
21
|
+
} from "./chunk-NLNH64A3.js";
|
|
22
22
|
import {
|
|
23
23
|
t
|
|
24
|
-
} from "./chunk-
|
|
24
|
+
} from "./chunk-HORSMSZL.js";
|
|
25
25
|
|
|
26
26
|
// src/commands/uninstall.ts
|
|
27
27
|
import { existsSync as existsSync2, statSync } from "fs";
|
|
28
28
|
import { rm as rm2 } from "fs/promises";
|
|
29
29
|
import { homedir } from "os";
|
|
30
30
|
import { isAbsolute, join as join2, relative, resolve, sep } from "path";
|
|
31
|
-
import { cancel, confirm,
|
|
31
|
+
import { cancel, confirm, intro, isCancel, multiselect, note, outro } from "@clack/prompts";
|
|
32
32
|
import { defineCommand } from "citty";
|
|
33
33
|
|
|
34
34
|
// src/install/uninstall-skills-and-hooks.ts
|
|
@@ -158,25 +158,10 @@ async function unmergeCodexHookConfig(projectRoot) {
|
|
|
158
158
|
extractCommands: extractFlatCommands
|
|
159
159
|
});
|
|
160
160
|
}
|
|
161
|
-
async function unmergeCursorHookConfig(projectRoot) {
|
|
162
|
-
return unmergeHookConfig({
|
|
163
|
-
step: "cursor-hook-config",
|
|
164
|
-
projectRoot,
|
|
165
|
-
configRel: HOOK_CONFIG_TARGETS.cursor,
|
|
166
|
-
arrayPaths: [...HOOK_CONFIG_ARRAY_PATHS.cursor],
|
|
167
|
-
fabricCommands: Object.values(FABRIC_HOOK_COMMAND_PATHS.cursor),
|
|
168
|
-
extractCommands: extractFlatCommands
|
|
169
|
-
});
|
|
170
|
-
}
|
|
171
161
|
async function stripFabricBootstrapBlocks(projectRoot) {
|
|
172
162
|
const results = [];
|
|
173
163
|
results.push(await stripClaudeBootstrapImports(projectRoot));
|
|
174
|
-
results.push(await stripManagedBlock(projectRoot, "AGENTS.md"
|
|
175
|
-
results.push(
|
|
176
|
-
await stripManagedBlock(projectRoot, join(".cursor", "rules", "fabric-bootstrap.mdc"), {
|
|
177
|
-
deleteWhenEmpty: true
|
|
178
|
-
})
|
|
179
|
-
);
|
|
164
|
+
results.push(await stripManagedBlock(projectRoot, "AGENTS.md"));
|
|
180
165
|
return results;
|
|
181
166
|
}
|
|
182
167
|
async function stripClaudeBootstrapImports(projectRoot) {
|
|
@@ -221,8 +206,8 @@ async function stripClaudeBootstrapImports(projectRoot) {
|
|
|
221
206
|
};
|
|
222
207
|
}
|
|
223
208
|
}
|
|
224
|
-
async function stripManagedBlock(projectRoot, relPath
|
|
225
|
-
const step =
|
|
209
|
+
async function stripManagedBlock(projectRoot, relPath) {
|
|
210
|
+
const step = "bootstrap-codex";
|
|
226
211
|
const target = join(projectRoot, relPath);
|
|
227
212
|
if (!existsSync(target)) {
|
|
228
213
|
return { step, path: target, status: "skipped", message: "absent" };
|
|
@@ -245,19 +230,6 @@ async function stripManagedBlock(projectRoot, relPath, options) {
|
|
|
245
230
|
const before = existing.slice(0, match.index ?? 0);
|
|
246
231
|
const after = existing.slice((match.index ?? 0) + match[0].length);
|
|
247
232
|
const filtered = `${before}${after.replace(/^\r?\n/, "")}`;
|
|
248
|
-
if (options.deleteWhenEmpty && isFrontMatterOnly(filtered)) {
|
|
249
|
-
try {
|
|
250
|
-
await rm(target, { force: true });
|
|
251
|
-
return { step, path: target, status: "removed", message: "front-matter-only" };
|
|
252
|
-
} catch (error) {
|
|
253
|
-
return {
|
|
254
|
-
step,
|
|
255
|
-
path: target,
|
|
256
|
-
status: "error",
|
|
257
|
-
message: error instanceof Error ? error.message : String(error)
|
|
258
|
-
};
|
|
259
|
-
}
|
|
260
|
-
}
|
|
261
233
|
try {
|
|
262
234
|
await atomicWriteText(target, filtered);
|
|
263
235
|
return { step, path: target, status: "removed" };
|
|
@@ -270,12 +242,6 @@ async function stripManagedBlock(projectRoot, relPath, options) {
|
|
|
270
242
|
};
|
|
271
243
|
}
|
|
272
244
|
}
|
|
273
|
-
function isFrontMatterOnly(content) {
|
|
274
|
-
const trimmed = content.replace(/^\s+/, "");
|
|
275
|
-
const match = trimmed.match(/^---\n[\s\S]*?\n---\s*$/);
|
|
276
|
-
if (match === null) return trimmed.length === 0;
|
|
277
|
-
return true;
|
|
278
|
-
}
|
|
279
245
|
async function deleteFabricAgentsSnapshot(projectRoot) {
|
|
280
246
|
const target = fabricAgentsSnapshotPath(projectRoot);
|
|
281
247
|
return rmIfExists("bootstrap-snapshot", target);
|
|
@@ -294,12 +260,6 @@ async function uninstallBootstrapStage(projectRoot, _opts = {}) {
|
|
|
294
260
|
projectRoot,
|
|
295
261
|
() => deleteFabricAgentsSnapshot(projectRoot)
|
|
296
262
|
);
|
|
297
|
-
await runAndCollectOne(
|
|
298
|
-
results,
|
|
299
|
-
"cursor-hook-config",
|
|
300
|
-
projectRoot,
|
|
301
|
-
() => unmergeCursorHookConfig(projectRoot)
|
|
302
|
-
);
|
|
303
263
|
await runAndCollectOne(
|
|
304
264
|
results,
|
|
305
265
|
"codex-hook-config",
|
|
@@ -584,15 +544,6 @@ function jsonEqual(a, b) {
|
|
|
584
544
|
}
|
|
585
545
|
|
|
586
546
|
// src/commands/uninstall.ts
|
|
587
|
-
var UNINSTALL_WIZARD_GROUP_CANCELLED = /* @__PURE__ */ Symbol("uninstall-wizard-group-cancelled");
|
|
588
|
-
var KNOWLEDGE_SUBDIRS = [
|
|
589
|
-
"decisions",
|
|
590
|
-
"pitfalls",
|
|
591
|
-
"guidelines",
|
|
592
|
-
"models",
|
|
593
|
-
"processes",
|
|
594
|
-
"pending"
|
|
595
|
-
];
|
|
596
547
|
var FABRIC_STATE_FILES = ["agents.meta.json", "events.jsonl", "forensic.json"];
|
|
597
548
|
var uninstallCommand = defineCommand({
|
|
598
549
|
meta: {
|
|
@@ -707,21 +658,17 @@ async function buildUninstallExecutionPlan(target, options = {}) {
|
|
|
707
658
|
function buildUninstallFabricPlan(target, options = {}) {
|
|
708
659
|
const absTarget = normalizeTarget(target);
|
|
709
660
|
const fabricDir = join2(absTarget, ".fabric");
|
|
710
|
-
const
|
|
661
|
+
const globalStoresDir = join2(resolvePersonalFabricRoot(), ".fabric", "stores");
|
|
711
662
|
const entries = [];
|
|
712
663
|
for (const name of FABRIC_STATE_FILES) {
|
|
713
664
|
const p = join2(fabricDir, name);
|
|
714
665
|
entries.push({ path: p, kind: "state-file", absent: !existsSync2(p) });
|
|
715
666
|
}
|
|
716
|
-
|
|
717
|
-
const gk = join2(fabricDir, "knowledge", sub, ".gitkeep");
|
|
718
|
-
entries.push({ path: gk, kind: "gitkeep", absent: !existsSync2(gk) });
|
|
719
|
-
}
|
|
720
|
-
const safeEntries = entries.filter((entry) => !isInsidePersonalRoot(entry.path, personalKnowledgeDir));
|
|
667
|
+
const safeEntries = entries.filter((entry) => !isInsideGlobalStoresRoot(entry.path, globalStoresDir));
|
|
721
668
|
return {
|
|
722
669
|
target: absTarget,
|
|
723
670
|
fabricDir,
|
|
724
|
-
|
|
671
|
+
globalStoresDir,
|
|
725
672
|
options,
|
|
726
673
|
entries: safeEntries
|
|
727
674
|
};
|
|
@@ -756,8 +703,6 @@ function scaffoldStepLabel(kind) {
|
|
|
756
703
|
switch (kind) {
|
|
757
704
|
case "state-file":
|
|
758
705
|
return "scaffold-state";
|
|
759
|
-
case "gitkeep":
|
|
760
|
-
return "scaffold-gitkeep";
|
|
761
706
|
}
|
|
762
707
|
}
|
|
763
708
|
async function uninstallMcpClients(target, options = {}) {
|
|
@@ -831,12 +776,17 @@ async function uninstallMcpClients(target, options = {}) {
|
|
|
831
776
|
}
|
|
832
777
|
async function executeUninstallExecutionPlan(plan) {
|
|
833
778
|
const stageResults = [];
|
|
779
|
+
const totalStages = plan.stages.length;
|
|
780
|
+
console.log(t("cli.uninstall.plan.phase-banner", { total: String(totalStages) }));
|
|
781
|
+
let stepNum = 0;
|
|
834
782
|
for (const stage of plan.stages) {
|
|
783
|
+
stepNum += 1;
|
|
835
784
|
if (stage.skipped) {
|
|
785
|
+
console.log(formatUninstallStageHeader(stage.name, stepNum, totalStages, true));
|
|
836
786
|
stageResults.push({ name: stage.name, disposition: "skipped", steps: [] });
|
|
837
787
|
continue;
|
|
838
788
|
}
|
|
839
|
-
console.log(formatUninstallStageHeader(stage.name));
|
|
789
|
+
console.log(formatUninstallStageHeader(stage.name, stepNum, totalStages));
|
|
840
790
|
try {
|
|
841
791
|
const steps = await executeUninstallStage(plan, stage.name);
|
|
842
792
|
const disposition = steps.some((s) => s.status === "error") ? "failed" : "ran";
|
|
@@ -905,66 +855,36 @@ function createDefaultUninstallWizardAdapter() {
|
|
|
905
855
|
return {
|
|
906
856
|
async run(context) {
|
|
907
857
|
intro(t("cli.uninstall.wizard.intro"));
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
target: context.target
|
|
911
|
-
}),
|
|
912
|
-
t("cli.uninstall.wizard.overview.title")
|
|
858
|
+
const available = UNINSTALL_STAGE_KEYS.filter(
|
|
859
|
+
(key) => !context.lockedStages.includes(key)
|
|
913
860
|
);
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
861
|
+
const initialValues = available.filter((key) => !isStageSkipped(context.options, key));
|
|
862
|
+
const picked = await multiselect({
|
|
863
|
+
message: t("cli.uninstall.wizard.select.prompt", { target: context.target }),
|
|
864
|
+
options: available.map((key) => ({
|
|
865
|
+
value: key,
|
|
866
|
+
label: t(`cli.uninstall.wizard.select.${key}.label`),
|
|
867
|
+
hint: t(`cli.uninstall.wizard.select.${key}.hint`)
|
|
868
|
+
})),
|
|
869
|
+
initialValues,
|
|
870
|
+
required: false
|
|
919
871
|
});
|
|
920
|
-
if (isCancel(
|
|
872
|
+
if (isCancel(picked)) {
|
|
921
873
|
emitUninstallWizardCancellation();
|
|
922
874
|
return null;
|
|
923
875
|
}
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
message: t("cli.uninstall.wizard.stage.scaffold", {
|
|
931
|
-
defaultValue: formatPromptDefault(!context.options.skipScaffold)
|
|
932
|
-
}),
|
|
933
|
-
initialValue: !context.options.skipScaffold
|
|
934
|
-
}),
|
|
935
|
-
bootstrap: async () => context.lockedStages.includes("bootstrap") ? false : confirmInGroup({
|
|
936
|
-
message: t("cli.uninstall.wizard.stage.bootstrap", {
|
|
937
|
-
defaultValue: formatPromptDefault(!context.options.skipBootstrap)
|
|
938
|
-
}),
|
|
939
|
-
initialValue: !context.options.skipBootstrap
|
|
940
|
-
}),
|
|
941
|
-
mcp: async () => context.lockedStages.includes("mcp") ? false : confirmInGroup({
|
|
942
|
-
message: t("cli.uninstall.wizard.stage.mcp", {
|
|
943
|
-
defaultValue: formatPromptDefault(!context.options.skipMcp)
|
|
944
|
-
}),
|
|
945
|
-
initialValue: !context.options.skipMcp
|
|
946
|
-
})
|
|
947
|
-
},
|
|
948
|
-
{
|
|
949
|
-
onCancel() {
|
|
950
|
-
throw UNINSTALL_WIZARD_GROUP_CANCELLED;
|
|
951
|
-
}
|
|
952
|
-
}
|
|
953
|
-
);
|
|
954
|
-
} catch (error) {
|
|
955
|
-
if (error === UNINSTALL_WIZARD_GROUP_CANCELLED) {
|
|
956
|
-
emitUninstallWizardCancellation();
|
|
957
|
-
return null;
|
|
958
|
-
}
|
|
959
|
-
throw error;
|
|
960
|
-
}
|
|
876
|
+
const selected = new Set(picked);
|
|
877
|
+
const selection = {
|
|
878
|
+
scaffold: selected.has("scaffold"),
|
|
879
|
+
bootstrap: selected.has("bootstrap"),
|
|
880
|
+
mcp: selected.has("mcp")
|
|
881
|
+
};
|
|
961
882
|
const previewOptions = {
|
|
962
883
|
...context.options,
|
|
963
|
-
skipScaffold: !
|
|
964
|
-
skipBootstrap: !
|
|
965
|
-
skipMcp: !
|
|
884
|
+
skipScaffold: !selection.scaffold,
|
|
885
|
+
skipBootstrap: !selection.bootstrap,
|
|
886
|
+
skipMcp: !selection.mcp
|
|
966
887
|
};
|
|
967
|
-
log.step(t("cli.uninstall.wizard.step.review"));
|
|
968
888
|
printUninstallPlanSummary(context.target, previewOptions, context.supports);
|
|
969
889
|
const confirmed = await confirm({
|
|
970
890
|
message: t("cli.uninstall.wizard.execute.confirm"),
|
|
@@ -975,20 +895,24 @@ function createDefaultUninstallWizardAdapter() {
|
|
|
975
895
|
return null;
|
|
976
896
|
}
|
|
977
897
|
outro(t("cli.uninstall.wizard.outro"));
|
|
978
|
-
return
|
|
898
|
+
return selection;
|
|
979
899
|
}
|
|
980
900
|
};
|
|
981
901
|
}
|
|
902
|
+
var UNINSTALL_STAGE_KEYS = ["scaffold", "bootstrap", "mcp"];
|
|
903
|
+
function isStageSkipped(options, key) {
|
|
904
|
+
switch (key) {
|
|
905
|
+
case "scaffold":
|
|
906
|
+
return Boolean(options.skipScaffold);
|
|
907
|
+
case "bootstrap":
|
|
908
|
+
return Boolean(options.skipBootstrap);
|
|
909
|
+
case "mcp":
|
|
910
|
+
return Boolean(options.skipMcp);
|
|
911
|
+
}
|
|
912
|
+
}
|
|
982
913
|
function emitUninstallWizardCancellation() {
|
|
983
914
|
cancel(t("cli.uninstall.wizard.cancelled"));
|
|
984
915
|
}
|
|
985
|
-
async function confirmInGroup(options) {
|
|
986
|
-
const result = await confirm(options);
|
|
987
|
-
if (isCancel(result)) {
|
|
988
|
-
throw UNINSTALL_WIZARD_GROUP_CANCELLED;
|
|
989
|
-
}
|
|
990
|
-
return result;
|
|
991
|
-
}
|
|
992
916
|
async function confirmDestructive(plan) {
|
|
993
917
|
printUninstallPlanSummary(plan.target, plan.options, plan.supports);
|
|
994
918
|
const answer = await confirm({
|
|
@@ -1035,8 +959,7 @@ function printUninstallPlanSummary(target, options, supports) {
|
|
|
1035
959
|
})
|
|
1036
960
|
);
|
|
1037
961
|
console.log(t("cli.uninstall.plan.preserves"));
|
|
1038
|
-
console.log(` -
|
|
1039
|
-
console.log(` - ~/.fabric/knowledge/ ${paint.muted(t("cli.uninstall.plan.preserves.personal"))}`);
|
|
962
|
+
console.log(` - ~/.fabric/stores/ ${paint.muted(t("cli.uninstall.plan.preserves.stores"))}`);
|
|
1040
963
|
}
|
|
1041
964
|
function printUninstallSummary(result) {
|
|
1042
965
|
const removed = result.stageResults.flatMap(
|
|
@@ -1064,8 +987,10 @@ function printUninstallSummary(result) {
|
|
|
1064
987
|
}
|
|
1065
988
|
}
|
|
1066
989
|
}
|
|
1067
|
-
function formatUninstallStageHeader(stageName) {
|
|
1068
|
-
|
|
990
|
+
function formatUninstallStageHeader(stageName, stepNum, total, skipped = false) {
|
|
991
|
+
const label = t(`cli.uninstall.stages.${stageName}`);
|
|
992
|
+
const head = `[${stepNum}/${total}] ${label}`;
|
|
993
|
+
return skipped ? paint.muted(`${head} (${t("cli.shared.skipped")})`) : head;
|
|
1069
994
|
}
|
|
1070
995
|
function formatUninstallStageResult(stageName, steps) {
|
|
1071
996
|
const removedCount = steps.filter((s) => s.status === "removed").length;
|
|
@@ -1082,9 +1007,9 @@ function formatUninstallStageFailure(stage, error) {
|
|
|
1082
1007
|
function resolvePersonalFabricRoot() {
|
|
1083
1008
|
return process.env.FABRIC_HOME ?? homedir();
|
|
1084
1009
|
}
|
|
1085
|
-
function
|
|
1010
|
+
function isInsideGlobalStoresRoot(candidate, globalStoresDir) {
|
|
1086
1011
|
const candidateAbs = resolve(candidate);
|
|
1087
|
-
const rootAbs = resolve(
|
|
1012
|
+
const rootAbs = resolve(globalStoresDir);
|
|
1088
1013
|
if (candidateAbs === rootAbs) {
|
|
1089
1014
|
return true;
|
|
1090
1015
|
}
|
|
@@ -1102,9 +1027,6 @@ function assertExistingDirectory(target) {
|
|
|
1102
1027
|
function isInteractiveUninstall() {
|
|
1103
1028
|
return Boolean(process.stdin.isTTY) && Boolean(process.stdout.isTTY) && Boolean(process.stderr.isTTY);
|
|
1104
1029
|
}
|
|
1105
|
-
function formatPromptDefault(value) {
|
|
1106
|
-
return value ? "Y/n" : "y/N";
|
|
1107
|
-
}
|
|
1108
1030
|
function yesNoLabel(value) {
|
|
1109
1031
|
return value ? t("cli.shared.yes") : t("cli.shared.no");
|
|
1110
1032
|
}
|
|
@@ -1120,7 +1042,7 @@ export {
|
|
|
1120
1042
|
uninstall_default as default,
|
|
1121
1043
|
executeUninstallExecutionPlan,
|
|
1122
1044
|
executeUninstallFabricPlan,
|
|
1123
|
-
|
|
1045
|
+
isInsideGlobalStoresRoot,
|
|
1124
1046
|
resolveUninstallExecutionPlanWithWizard,
|
|
1125
1047
|
runUninstallCommand,
|
|
1126
1048
|
shouldUseUninstallWizard,
|
|
@@ -1,18 +1,19 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import {
|
|
3
|
-
getProjectTranslator
|
|
4
|
-
} from "./chunk-2CY4BMTH.js";
|
|
5
2
|
import {
|
|
6
3
|
warnUnknownFlags,
|
|
7
4
|
whoami
|
|
8
|
-
} from "./chunk-
|
|
9
|
-
import "./chunk-
|
|
10
|
-
import "./chunk-
|
|
5
|
+
} from "./chunk-27HK6H5Y.js";
|
|
6
|
+
import "./chunk-QPAW6IYT.js";
|
|
7
|
+
import "./chunk-QFIVFZRH.js";
|
|
8
|
+
import "./chunk-FNHDQTPC.js";
|
|
9
|
+
import {
|
|
10
|
+
getProjectTranslator
|
|
11
|
+
} from "./chunk-HORSMSZL.js";
|
|
11
12
|
|
|
12
13
|
// src/commands/whoami.ts
|
|
13
14
|
import { defineCommand } from "citty";
|
|
14
15
|
var whoami_default = defineCommand({
|
|
15
|
-
meta: { name: "whoami", description: "
|
|
16
|
+
meta: { name: "whoami", description: "[DEPRECATED] Use 'fabric info --global' instead" },
|
|
16
17
|
args: {
|
|
17
18
|
// F27: `--json` machine-readable output (was silently ignored — the command
|
|
18
19
|
// declared no args, so citty swallowed the flag and still printed text).
|
|
@@ -20,6 +21,7 @@ var whoami_default = defineCommand({
|
|
|
20
21
|
},
|
|
21
22
|
run({ args }) {
|
|
22
23
|
warnUnknownFlags(["json"]);
|
|
24
|
+
console.error("\u26A0\uFE0F DEPRECATED: 'fabric whoami' is deprecated. Use 'fabric info --global' instead.");
|
|
23
25
|
const info = whoami();
|
|
24
26
|
if (args.json === true) {
|
|
25
27
|
console.log(JSON.stringify(info, null, 2));
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@fenglimg/fabric-cli",
|
|
3
|
-
"version": "2.2.0-rc.
|
|
4
|
-
"description": "Fabric CLI — installs the MCP server + skills + hooks for Claude Code
|
|
3
|
+
"version": "2.2.0-rc.8",
|
|
4
|
+
"description": "Fabric CLI — installs the MCP server + skills + hooks for Claude Code and Codex CLI; runs doctor / knowledge maintenance.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "wangzhichao <fenglimg90@gmail.com>",
|
|
7
7
|
"homepage": "https://github.com/fenglimg/fabric-v2#readme",
|
|
@@ -18,7 +18,6 @@
|
|
|
18
18
|
"mcp",
|
|
19
19
|
"cli",
|
|
20
20
|
"claude-code",
|
|
21
|
-
"cursor",
|
|
22
21
|
"codex-cli",
|
|
23
22
|
"ai-knowledge-management"
|
|
24
23
|
],
|
|
@@ -40,16 +39,19 @@
|
|
|
40
39
|
"dependencies": {
|
|
41
40
|
"@clack/prompts": "^1.2.0",
|
|
42
41
|
"citty": "^0.2.2",
|
|
42
|
+
"ink": "^4.4.1",
|
|
43
43
|
"picocolors": "^1.1.1",
|
|
44
|
+
"react": "^18.3.1",
|
|
44
45
|
"string-width": "^7.2.0",
|
|
45
46
|
"tree-sitter-javascript": "^0.25.0",
|
|
46
47
|
"tree-sitter-typescript": "^0.23.2",
|
|
47
48
|
"web-tree-sitter": "^0.26.8",
|
|
48
|
-
"@fenglimg/fabric-shared": "2.2.0-rc.
|
|
49
|
-
"@fenglimg/fabric-server": "2.2.0-rc.
|
|
49
|
+
"@fenglimg/fabric-shared": "2.2.0-rc.8",
|
|
50
|
+
"@fenglimg/fabric-server": "2.2.0-rc.8"
|
|
50
51
|
},
|
|
51
52
|
"devDependencies": {
|
|
52
53
|
"@types/node": "^22.15.0",
|
|
54
|
+
"@types/react": "^18.3.12",
|
|
53
55
|
"tsup": "^8.5.0",
|
|
54
56
|
"typescript": "^5.8.3"
|
|
55
57
|
},
|
|
@@ -27,7 +27,7 @@
|
|
|
27
27
|
* The legacy hand-written cite path is still honored (back-compat).
|
|
28
28
|
* - otherwise → soft nudge: "改前先 fab_recall(paths)". NUDGE, never a gate
|
|
29
29
|
* (KT-DEC-0007): Claude Code receives it as a PreToolUse additionalContext
|
|
30
|
-
* envelope on stdout; Codex
|
|
30
|
+
* envelope on stdout; Codex as stderr. The edit always proceeds.
|
|
31
31
|
*
|
|
32
32
|
* Config (.fabric/fabric-config.json):
|
|
33
33
|
* - `cite_recall_nudge` (boolean, default true) — master switch. Set false to
|
|
@@ -48,16 +48,16 @@
|
|
|
48
48
|
* its own malfunction.
|
|
49
49
|
*
|
|
50
50
|
* Cross-client: PreToolUse(Edit|Write|MultiEdit) is registered on all three
|
|
51
|
-
* clients (Claude Code / Codex CLI
|
|
51
|
+
* clients (Claude Code / Codex CLI) — see hooks/configs/*.json. This
|
|
52
52
|
* is strictly better parity than the rc.34 hook, which was Claude-Code-only
|
|
53
|
-
* for the per-turn window and SessionStart-only for Codex
|
|
53
|
+
* for the per-turn window and SessionStart-only for Codex.
|
|
54
54
|
*/
|
|
55
55
|
|
|
56
56
|
const { readFileSync } = require("node:fs");
|
|
57
57
|
const { isAbsolute, join, relative } = require("node:path");
|
|
58
58
|
|
|
59
59
|
// Shared config read + client-aware emit (Claude Code stdout envelope vs
|
|
60
|
-
// Codex
|
|
60
|
+
// Codex stderr). The installer copies every lib/*.cjs alongside the hook.
|
|
61
61
|
const { readConfigNumber } = require("./lib/config-cache.cjs");
|
|
62
62
|
const { isClaudeCode, readStdinJson, emitContext } = require("./lib/client-adapter.cjs");
|
|
63
63
|
|
|
@@ -445,7 +445,7 @@ async function main(env, stdio) {
|
|
|
445
445
|
}
|
|
446
446
|
|
|
447
447
|
// No recall, no manual cite → soft nudge. Claude Code: PreToolUse stdout
|
|
448
|
-
// additionalContext envelope. Codex
|
|
448
|
+
// additionalContext envelope. Codex: stderr. Never a gate.
|
|
449
449
|
const streams = (env && env.stdio) || stdio || {};
|
|
450
450
|
const onClaudeCode = isClaudeCode() || (env && env.forceClaudeCode === true);
|
|
451
451
|
emitContext(renderNudge(nudgePaths), {
|
|
@@ -4,7 +4,7 @@ These JSON files are **fragment templates** consumed by `fabric install` and
|
|
|
4
4
|
`fabric hooks install`. They are not standalone client config files.
|
|
5
5
|
|
|
6
6
|
The supported clients are pinned by `packages/shared/src/schemas/fabric-config.ts`
|
|
7
|
-
to Claude Code
|
|
7
|
+
to Claude Code and Codex CLI. Adding a new client requires extending
|
|
8
8
|
that schema first.
|
|
9
9
|
|
|
10
10
|
## claude-code.json
|
|
@@ -25,45 +25,32 @@ Written to (or merged into) the user repo's `.codex/hooks.json`. NOTE: Codex
|
|
|
25
25
|
project-level hooks file is JSON, **not** TOML — only the user-level Codex MCP
|
|
26
26
|
config (`~/.codex/config.toml`) is TOML.
|
|
27
27
|
|
|
28
|
-
## cursor-hooks.json
|
|
29
|
-
|
|
30
|
-
Written to (or merged into) the user repo's `.cursor/hooks.json`. Schema
|
|
31
|
-
authoritative source: https://cursor.com/cn/docs/hooks. Top-level requires
|
|
32
|
-
`version: 1` (number literal, NOT string) and a `hooks` object (NOT `events`)
|
|
33
|
-
keyed by camelCase event names: `stop`, `sessionStart`, `preToolUse`. Per-entry
|
|
34
|
-
shape stays flat (Codex-style): `{command, matcher?, type?, timeout?,
|
|
35
|
-
loop_limit?, failClosed?}`. rc.14 TASK-001 corrected rc.13's wrong top-level
|
|
36
|
-
envelope (was `{events: {Stop, SessionStart, PreToolUse}}` PascalCase, which
|
|
37
|
-
Cursor rejects with "Config version must be a number; Config hooks must be an
|
|
38
|
-
object").
|
|
39
|
-
|
|
40
28
|
## Per-client schema comparison (v2.0.0-rc.37 NEW-29)
|
|
41
29
|
|
|
42
30
|
Each host program enforces its own wire format — `fabric install` cannot
|
|
43
|
-
serialize one shared shape across
|
|
44
|
-
side-by-side so anyone editing one config knows what the
|
|
45
|
-
|
|
46
|
-
| Axis | Claude Code | Codex CLI |
|
|
47
|
-
| -------------------- | ---------------------------------------- | -------------------------------------------------- |
|
|
48
|
-
| Settings file | `.claude/settings.json` | `.codex/hooks.json` |
|
|
49
|
-
| Top-level envelope | `hooks: { ... }` (no version) | `events: { ... }` (no version) |
|
|
50
|
-
| Event-name case | PascalCase: `Stop`, `SessionStart`, `PreToolUse`, `UserPromptSubmit` | PascalCase: `Stop`, `SessionStart`, `PreToolUse` |
|
|
51
|
-
| Per-entry shape | Nested matcher: `[{matcher, hooks:[{type:"command", command}]}]` | Flat: `[{command, matcher?}]` |
|
|
52
|
-
| Path interpolation | `${CLAUDE_PROJECT_DIR}` (env var) | `"$(git rev-parse --show-toplevel)"` (shell expansion) |
|
|
53
|
-
| Cite-policy event | `UserPromptSubmit` (per-prompt) | `SessionStart` 2nd entry (rc.37 NEW-21 parity) |
|
|
31
|
+
serialize one shared shape across both. Differences are pinned here
|
|
32
|
+
side-by-side so anyone editing one config knows what the other requires.
|
|
33
|
+
|
|
34
|
+
| Axis | Claude Code | Codex CLI |
|
|
35
|
+
| -------------------- | ---------------------------------------- | -------------------------------------------------- |
|
|
36
|
+
| Settings file | `.claude/settings.json` | `.codex/hooks.json` |
|
|
37
|
+
| Top-level envelope | `hooks: { ... }` (no version) | `events: { ... }` (no version) |
|
|
38
|
+
| Event-name case | PascalCase: `Stop`, `SessionStart`, `PreToolUse`, `UserPromptSubmit` | PascalCase: `Stop`, `SessionStart`, `PreToolUse` |
|
|
39
|
+
| Per-entry shape | Nested matcher: `[{matcher, hooks:[{type:"command", command}]}]` | Flat: `[{command, matcher?}]` |
|
|
40
|
+
| Path interpolation | `${CLAUDE_PROJECT_DIR}` (env var) | `"$(git rev-parse --show-toplevel)"` (shell expansion) |
|
|
41
|
+
| Cite-policy event | `UserPromptSubmit` (per-prompt) | `SessionStart` 2nd entry (rc.37 NEW-21 parity) |
|
|
54
42
|
|
|
55
43
|
Whenever a hook is added to one config, walk this table and add the equivalent
|
|
56
|
-
entry to the other
|
|
44
|
+
entry to the other — `fabric install` merges each into its respective
|
|
57
45
|
target verbatim, so missing entries silently degrade the cross-client surface.
|
|
58
46
|
|
|
59
47
|
## fabric-hint.cjs script paths
|
|
60
48
|
|
|
61
49
|
- Claude: `.claude/hooks/fabric-hint.cjs` (project-relative)
|
|
62
50
|
- Codex: `.codex/hooks/fabric-hint.cjs` (project-relative)
|
|
63
|
-
- Cursor: `.cursor/hooks/fabric-hint.cjs` (project-relative)
|
|
64
51
|
|
|
65
52
|
The single shared script lives at `packages/cli/templates/hooks/fabric-hint.cjs`
|
|
66
|
-
in this repo and is copied into
|
|
53
|
+
in this repo and is copied into both `<client>/hooks/` destinations by the
|
|
67
54
|
install wiring. The script emits stdout JSON
|
|
68
55
|
`{decision:"block", reason, signal, recommended_skill}` with exit 0 when one of
|
|
69
56
|
three signals trips:
|