@fenglimg/fabric-cli 2.2.0-rc.4 → 2.2.0-rc.9

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 (73) hide show
  1. package/README.md +8 -5
  2. package/dist/{chunk-5JG4QJLO.js → chunk-27HK6H5Y.js} +10 -5
  3. package/dist/{chunk-F6ITRM7T.js → chunk-2KBCTMID.js} +29 -6
  4. package/dist/chunk-3D7B2UAZ.js +149 -0
  5. package/dist/{chunk-XC5RUHLK.js → chunk-3IOLS5EK.js} +23 -38
  6. package/dist/{chunk-XHHCRDIR.js → chunk-7ZDXBOOU.js} +174 -211
  7. package/dist/{doctor-U5W4CX5I.js → chunk-E7HJUU34.js} +103 -51
  8. package/dist/{chunk-XCBVSGCS.js → chunk-FNHDQTPC.js} +1 -10
  9. package/dist/{chunk-2CY4BMTH.js → chunk-HORSMSZL.js} +9 -5
  10. package/dist/{chunk-BO4XIZWZ.js → chunk-NLNH64A3.js} +5 -18
  11. package/dist/{chunk-H3FE6VIK.js → chunk-PTGQAZEW.js} +13 -3
  12. package/dist/chunk-QFIVFZRH.js +13 -0
  13. package/dist/{chunk-5SSNE5GM.js → chunk-QPAW6IYT.js} +125 -39
  14. package/dist/{chunk-COI5VDFU.js → chunk-WA3DYGSY.js} +1 -2
  15. package/dist/{plan-context-hint-CHVZGOZ5.js → chunk-YM4XATJF.js} +29 -4
  16. package/dist/{config-VJMXCLXW.js → config-A3LTECAY.js} +4 -3
  17. package/dist/context-7NUKXDB6.js +117 -0
  18. package/dist/doctor-MDTZWKBK.js +24 -0
  19. package/dist/index.d.ts +2 -2
  20. package/dist/index.js +131 -21
  21. package/dist/info-7FKBTMVO.js +139 -0
  22. package/dist/install-v2-I6PJ6IFT.js +3279 -0
  23. package/dist/{metrics-RER6NLFC.js → metrics-HMFH4YHK.js} +1 -1
  24. package/dist/{onboard-coverage-JWQWDZW7.js → onboard-coverage-XSG77LL3.js} +48 -27
  25. package/dist/plan-context-hint-G75R4P4J.js +12 -0
  26. package/dist/{scope-explain-BWRWBCCP.js → scope-explain-HLJZ2M33.js} +3 -2
  27. package/dist/{status-7UFLWRX7.js → status-4R3TM4FJ.js} +8 -5
  28. package/dist/{store-ZEZMQVG7.js → store-HOCORVL3.js} +96 -350
  29. package/dist/{sync-EA5HZMXM.js → sync-DT5UJMMR.js} +36 -13
  30. package/dist/{uninstall-F75MPKQC.js → uninstall-IFN2KYBK.js} +71 -140
  31. package/dist/{whoami-3FRWYGML.js → whoami-ITGEFWH4.js} +9 -7
  32. package/package.json +7 -5
  33. package/templates/hooks/cite-policy-evict.cjs +5 -5
  34. package/templates/hooks/configs/README.md +14 -27
  35. package/templates/hooks/configs/claude-code.json +1 -1
  36. package/templates/hooks/configs/codex-hooks.json +3 -3
  37. package/templates/hooks/fabric-hint.cjs +326 -161
  38. package/templates/hooks/knowledge-hint-broad.cjs +431 -271
  39. package/templates/hooks/knowledge-hint-narrow.cjs +64 -77
  40. package/templates/hooks/lib/banner-i18n.cjs +31 -0
  41. package/templates/hooks/lib/bindings-snapshot-reader.cjs +118 -7
  42. package/templates/hooks/lib/cite-line-parser.cjs +12 -20
  43. package/templates/hooks/lib/client-adapter.cjs +66 -7
  44. package/templates/hooks/lib/nudge-policy.cjs +117 -0
  45. package/templates/hooks/lib/state-store.cjs +60 -0
  46. package/templates/hooks/post-tooluse-mutation.cjs +112 -11
  47. package/templates/skills/fabric/SKILL.md +100 -0
  48. package/templates/skills/fabric-archive/SKILL.md +29 -26
  49. package/templates/skills/fabric-archive/ref/dry-run-scope.md +1 -1
  50. package/templates/skills/fabric-archive/ref/i18n-policy.md +2 -3
  51. package/templates/skills/fabric-archive/ref/phase-1-5-onboard.md +2 -3
  52. package/templates/skills/fabric-archive/ref/phase-1-cross-session.md +1 -1
  53. package/templates/skills/fabric-archive/ref/phase-2-5-viability.md +1 -1
  54. package/templates/skills/fabric-archive/ref/phase-3-6-related-edges.md +18 -0
  55. package/templates/skills/fabric-archive/ref/phase-3-7-semantic-scope.md +47 -0
  56. package/templates/skills/fabric-audit/SKILL.md +13 -3
  57. package/templates/skills/fabric-connect/SKILL.md +3 -3
  58. package/templates/skills/fabric-import/SKILL.md +7 -7
  59. package/templates/skills/fabric-import/ref/i18n-policy.md +2 -3
  60. package/templates/skills/fabric-import/ref/state-recovery.md +1 -2
  61. package/templates/skills/fabric-review/SKILL.md +5 -5
  62. package/templates/skills/fabric-review/ref/cite-contract.md +1 -1
  63. package/templates/skills/fabric-review/ref/i18n-policy.md +2 -3
  64. package/templates/skills/fabric-review/ref/output-contract.md +1 -1
  65. package/templates/skills/fabric-review/ref/per-mode-flows.md +2 -2
  66. package/templates/skills/fabric-review/ref/worked-examples.md +1 -1
  67. package/templates/skills/fabric-store/SKILL.md +1 -1
  68. package/templates/skills/fabric-sync/SKILL.md +1 -1
  69. package/templates/skills/lib/shared-policy.md +2 -2
  70. package/dist/install-7XJ64WSC.js +0 -2743
  71. package/templates/hooks/configs/cursor-hooks.json +0 -30
  72. package/templates/hooks/lib/cite-contract-reminder.cjs +0 -179
  73. package/templates/hooks/lib/summary-fallback.cjs +0 -210
@@ -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-H3FE6VIK.js";
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-XCBVSGCS.js";
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, storeRelativePath } from "@fenglimg/fabric-shared";
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 = (status) => join(globalRoot, storeRelativePath(status.store_uuid));
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 storeDirOf = (status) => join(globalRoot, storeRelativePath(status.store_uuid));
321
+ const resumeConfig = loadGlobalConfig(globalRoot) ?? { stores: [] };
322
+ const storeDirOf = makeStoreDirResolver(globalRoot, resumeConfig.stores);
308
323
  (options.rebaseContinue ?? defaultRebaseContinue)(storeDirOf(conflicted));
309
- const pushableAliases = pushableAliasesOf(loadGlobalConfig(globalRoot) ?? { stores: [] });
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 storeDirOf = (status) => join(globalRoot, storeRelativePath(status.store_uuid));
361
+ const resumeConfig = loadGlobalConfig(globalRoot) ?? { stores: [] };
362
+ const storeDirOf = makeStoreDirResolver(globalRoot, resumeConfig.stores);
347
363
  (options.rebaseAbort ?? defaultRebaseAbort)(storeDirOf(conflicted));
348
- const pushableAliases = pushableAliasesOf(loadGlobalConfig(globalRoot) ?? { stores: [] });
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 sync_default = defineCommand({
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-XHHCRDIR.js";
11
- import {
12
- paint
13
- } from "./chunk-BO4XIZWZ.js";
10
+ } from "./chunk-7ZDXBOOU.js";
14
11
  import {
15
12
  createDebugLogger,
16
13
  resolveDevMode
17
- } from "./chunk-COI5VDFU.js";
14
+ } from "./chunk-WA3DYGSY.js";
18
15
  import {
19
16
  detectClientSupports,
20
17
  resolveClients
21
- } from "./chunk-XC5RUHLK.js";
18
+ } from "./chunk-3IOLS5EK.js";
19
+ import {
20
+ paint
21
+ } from "./chunk-NLNH64A3.js";
22
22
  import {
23
23
  t
24
- } from "./chunk-2CY4BMTH.js";
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, group, intro, isCancel, log, note, outro } from "@clack/prompts";
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
@@ -37,6 +37,9 @@ import { readdir, readFile, rm, rmdir } from "fs/promises";
37
37
  import { dirname, join } from "path";
38
38
  import { atomicWriteJson, atomicWriteText } from "@fenglimg/fabric-shared/node/atomic-write";
39
39
  import { BOOTSTRAP_REGEX } from "@fenglimg/fabric-shared/templates/bootstrap-canonical";
40
+ async function uninstallFabricRouterSkill(projectRoot) {
41
+ return removeSkill("skill-router", SKILL_DESTINATIONS.fabricRouter, projectRoot);
42
+ }
40
43
  async function uninstallFabricArchiveSkill(projectRoot) {
41
44
  return removeSkill("skill", SKILL_DESTINATIONS.fabricArchive, projectRoot);
42
45
  }
@@ -158,25 +161,10 @@ async function unmergeCodexHookConfig(projectRoot) {
158
161
  extractCommands: extractFlatCommands
159
162
  });
160
163
  }
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
164
  async function stripFabricBootstrapBlocks(projectRoot) {
172
165
  const results = [];
173
166
  results.push(await stripClaudeBootstrapImports(projectRoot));
174
- results.push(await stripManagedBlock(projectRoot, "AGENTS.md", { deleteWhenEmpty: false }));
175
- results.push(
176
- await stripManagedBlock(projectRoot, join(".cursor", "rules", "fabric-bootstrap.mdc"), {
177
- deleteWhenEmpty: true
178
- })
179
- );
167
+ results.push(await stripManagedBlock(projectRoot, "AGENTS.md"));
180
168
  return results;
181
169
  }
182
170
  async function stripClaudeBootstrapImports(projectRoot) {
@@ -221,8 +209,8 @@ async function stripClaudeBootstrapImports(projectRoot) {
221
209
  };
222
210
  }
223
211
  }
224
- async function stripManagedBlock(projectRoot, relPath, options) {
225
- const step = relPath.endsWith(".mdc") ? "bootstrap-cursor" : "bootstrap-codex";
212
+ async function stripManagedBlock(projectRoot, relPath) {
213
+ const step = "bootstrap-codex";
226
214
  const target = join(projectRoot, relPath);
227
215
  if (!existsSync(target)) {
228
216
  return { step, path: target, status: "skipped", message: "absent" };
@@ -245,19 +233,6 @@ async function stripManagedBlock(projectRoot, relPath, options) {
245
233
  const before = existing.slice(0, match.index ?? 0);
246
234
  const after = existing.slice((match.index ?? 0) + match[0].length);
247
235
  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
236
  try {
262
237
  await atomicWriteText(target, filtered);
263
238
  return { step, path: target, status: "removed" };
@@ -270,12 +245,6 @@ async function stripManagedBlock(projectRoot, relPath, options) {
270
245
  };
271
246
  }
272
247
  }
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
248
  async function deleteFabricAgentsSnapshot(projectRoot) {
280
249
  const target = fabricAgentsSnapshotPath(projectRoot);
281
250
  return rmIfExists("bootstrap-snapshot", target);
@@ -294,12 +263,6 @@ async function uninstallBootstrapStage(projectRoot, _opts = {}) {
294
263
  projectRoot,
295
264
  () => deleteFabricAgentsSnapshot(projectRoot)
296
265
  );
297
- await runAndCollectOne(
298
- results,
299
- "cursor-hook-config",
300
- projectRoot,
301
- () => unmergeCursorHookConfig(projectRoot)
302
- );
303
266
  await runAndCollectOne(
304
267
  results,
305
268
  "codex-hook-config",
@@ -385,6 +348,12 @@ async function uninstallBootstrapStage(projectRoot, _opts = {}) {
385
348
  projectRoot,
386
349
  () => uninstallFabricArchiveSkill(projectRoot)
387
350
  );
351
+ await runAndCollect(
352
+ results,
353
+ "skill-router",
354
+ projectRoot,
355
+ () => uninstallFabricRouterSkill(projectRoot)
356
+ );
388
357
  return results;
389
358
  }
390
359
  async function runAndCollect(results, step, projectRoot, fn) {
@@ -584,15 +553,6 @@ function jsonEqual(a, b) {
584
553
  }
585
554
 
586
555
  // 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
556
  var FABRIC_STATE_FILES = ["agents.meta.json", "events.jsonl", "forensic.json"];
597
557
  var uninstallCommand = defineCommand({
598
558
  meta: {
@@ -707,21 +667,17 @@ async function buildUninstallExecutionPlan(target, options = {}) {
707
667
  function buildUninstallFabricPlan(target, options = {}) {
708
668
  const absTarget = normalizeTarget(target);
709
669
  const fabricDir = join2(absTarget, ".fabric");
710
- const personalKnowledgeDir = resolve(resolvePersonalFabricRoot(), ".fabric", "knowledge");
670
+ const globalStoresDir = join2(resolvePersonalFabricRoot(), ".fabric", "stores");
711
671
  const entries = [];
712
672
  for (const name of FABRIC_STATE_FILES) {
713
673
  const p = join2(fabricDir, name);
714
674
  entries.push({ path: p, kind: "state-file", absent: !existsSync2(p) });
715
675
  }
716
- for (const sub of KNOWLEDGE_SUBDIRS) {
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));
676
+ const safeEntries = entries.filter((entry) => !isInsideGlobalStoresRoot(entry.path, globalStoresDir));
721
677
  return {
722
678
  target: absTarget,
723
679
  fabricDir,
724
- personalKnowledgeDir,
680
+ globalStoresDir,
725
681
  options,
726
682
  entries: safeEntries
727
683
  };
@@ -756,8 +712,6 @@ function scaffoldStepLabel(kind) {
756
712
  switch (kind) {
757
713
  case "state-file":
758
714
  return "scaffold-state";
759
- case "gitkeep":
760
- return "scaffold-gitkeep";
761
715
  }
762
716
  }
763
717
  async function uninstallMcpClients(target, options = {}) {
@@ -831,12 +785,17 @@ async function uninstallMcpClients(target, options = {}) {
831
785
  }
832
786
  async function executeUninstallExecutionPlan(plan) {
833
787
  const stageResults = [];
788
+ const totalStages = plan.stages.length;
789
+ console.log(t("cli.uninstall.plan.phase-banner", { total: String(totalStages) }));
790
+ let stepNum = 0;
834
791
  for (const stage of plan.stages) {
792
+ stepNum += 1;
835
793
  if (stage.skipped) {
794
+ console.log(formatUninstallStageHeader(stage.name, stepNum, totalStages, true));
836
795
  stageResults.push({ name: stage.name, disposition: "skipped", steps: [] });
837
796
  continue;
838
797
  }
839
- console.log(formatUninstallStageHeader(stage.name));
798
+ console.log(formatUninstallStageHeader(stage.name, stepNum, totalStages));
840
799
  try {
841
800
  const steps = await executeUninstallStage(plan, stage.name);
842
801
  const disposition = steps.some((s) => s.status === "error") ? "failed" : "ran";
@@ -905,66 +864,36 @@ function createDefaultUninstallWizardAdapter() {
905
864
  return {
906
865
  async run(context) {
907
866
  intro(t("cli.uninstall.wizard.intro"));
908
- note(
909
- t("cli.uninstall.wizard.overview.body", {
910
- target: context.target
911
- }),
912
- t("cli.uninstall.wizard.overview.title")
867
+ const available = UNINSTALL_STAGE_KEYS.filter(
868
+ (key) => !context.lockedStages.includes(key)
913
869
  );
914
- printUninstallPlanSummary(context.target, context.options, context.supports);
915
- log.step(t("cli.uninstall.wizard.step.target"));
916
- const continueWithTarget = await confirm({
917
- message: t("cli.uninstall.wizard.target.confirm", { target: context.target }),
918
- initialValue: true
870
+ const initialValues = available.filter((key) => !isStageSkipped(context.options, key));
871
+ const picked = await multiselect({
872
+ message: t("cli.uninstall.wizard.select.prompt", { target: context.target }),
873
+ options: available.map((key) => ({
874
+ value: key,
875
+ label: t(`cli.uninstall.wizard.select.${key}.label`),
876
+ hint: t(`cli.uninstall.wizard.select.${key}.hint`)
877
+ })),
878
+ initialValues,
879
+ required: false
919
880
  });
920
- if (isCancel(continueWithTarget) || !continueWithTarget) {
881
+ if (isCancel(picked)) {
921
882
  emitUninstallWizardCancellation();
922
883
  return null;
923
884
  }
924
- log.step(t("cli.uninstall.wizard.step.plan"));
925
- let groupedSelection;
926
- try {
927
- groupedSelection = await group(
928
- {
929
- scaffold: async () => context.lockedStages.includes("scaffold") ? false : confirmInGroup({
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
- }
885
+ const selected = new Set(picked);
886
+ const selection = {
887
+ scaffold: selected.has("scaffold"),
888
+ bootstrap: selected.has("bootstrap"),
889
+ mcp: selected.has("mcp")
890
+ };
961
891
  const previewOptions = {
962
892
  ...context.options,
963
- skipScaffold: !groupedSelection.scaffold,
964
- skipBootstrap: !groupedSelection.bootstrap,
965
- skipMcp: !groupedSelection.mcp
893
+ skipScaffold: !selection.scaffold,
894
+ skipBootstrap: !selection.bootstrap,
895
+ skipMcp: !selection.mcp
966
896
  };
967
- log.step(t("cli.uninstall.wizard.step.review"));
968
897
  printUninstallPlanSummary(context.target, previewOptions, context.supports);
969
898
  const confirmed = await confirm({
970
899
  message: t("cli.uninstall.wizard.execute.confirm"),
@@ -975,20 +904,24 @@ function createDefaultUninstallWizardAdapter() {
975
904
  return null;
976
905
  }
977
906
  outro(t("cli.uninstall.wizard.outro"));
978
- return groupedSelection;
907
+ return selection;
979
908
  }
980
909
  };
981
910
  }
911
+ var UNINSTALL_STAGE_KEYS = ["scaffold", "bootstrap", "mcp"];
912
+ function isStageSkipped(options, key) {
913
+ switch (key) {
914
+ case "scaffold":
915
+ return Boolean(options.skipScaffold);
916
+ case "bootstrap":
917
+ return Boolean(options.skipBootstrap);
918
+ case "mcp":
919
+ return Boolean(options.skipMcp);
920
+ }
921
+ }
982
922
  function emitUninstallWizardCancellation() {
983
923
  cancel(t("cli.uninstall.wizard.cancelled"));
984
924
  }
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
925
  async function confirmDestructive(plan) {
993
926
  printUninstallPlanSummary(plan.target, plan.options, plan.supports);
994
927
  const answer = await confirm({
@@ -1035,8 +968,7 @@ function printUninstallPlanSummary(target, options, supports) {
1035
968
  })
1036
969
  );
1037
970
  console.log(t("cli.uninstall.plan.preserves"));
1038
- console.log(` - ${target}/.fabric/knowledge/ ${paint.muted(t("cli.uninstall.plan.preserves.knowledge"))}`);
1039
- console.log(` - ~/.fabric/knowledge/ ${paint.muted(t("cli.uninstall.plan.preserves.personal"))}`);
971
+ console.log(` - ~/.fabric/stores/ ${paint.muted(t("cli.uninstall.plan.preserves.stores"))}`);
1040
972
  }
1041
973
  function printUninstallSummary(result) {
1042
974
  const removed = result.stageResults.flatMap(
@@ -1064,8 +996,10 @@ function printUninstallSummary(result) {
1064
996
  }
1065
997
  }
1066
998
  }
1067
- function formatUninstallStageHeader(stageName) {
1068
- return `${paint.ai(t("cli.shared.next"))} ${paint.muted(t(`cli.uninstall.stages.${stageName}`))}`;
999
+ function formatUninstallStageHeader(stageName, stepNum, total, skipped = false) {
1000
+ const label = t(`cli.uninstall.stages.${stageName}`);
1001
+ const head = `[${stepNum}/${total}] ${label}`;
1002
+ return skipped ? paint.muted(`${head} (${t("cli.shared.skipped")})`) : head;
1069
1003
  }
1070
1004
  function formatUninstallStageResult(stageName, steps) {
1071
1005
  const removedCount = steps.filter((s) => s.status === "removed").length;
@@ -1082,9 +1016,9 @@ function formatUninstallStageFailure(stage, error) {
1082
1016
  function resolvePersonalFabricRoot() {
1083
1017
  return process.env.FABRIC_HOME ?? homedir();
1084
1018
  }
1085
- function isInsidePersonalRoot(candidate, personalKnowledgeDir) {
1019
+ function isInsideGlobalStoresRoot(candidate, globalStoresDir) {
1086
1020
  const candidateAbs = resolve(candidate);
1087
- const rootAbs = resolve(personalKnowledgeDir);
1021
+ const rootAbs = resolve(globalStoresDir);
1088
1022
  if (candidateAbs === rootAbs) {
1089
1023
  return true;
1090
1024
  }
@@ -1102,9 +1036,6 @@ function assertExistingDirectory(target) {
1102
1036
  function isInteractiveUninstall() {
1103
1037
  return Boolean(process.stdin.isTTY) && Boolean(process.stdout.isTTY) && Boolean(process.stderr.isTTY);
1104
1038
  }
1105
- function formatPromptDefault(value) {
1106
- return value ? "Y/n" : "y/N";
1107
- }
1108
1039
  function yesNoLabel(value) {
1109
1040
  return value ? t("cli.shared.yes") : t("cli.shared.no");
1110
1041
  }
@@ -1120,7 +1051,7 @@ export {
1120
1051
  uninstall_default as default,
1121
1052
  executeUninstallExecutionPlan,
1122
1053
  executeUninstallFabricPlan,
1123
- isInsidePersonalRoot,
1054
+ isInsideGlobalStoresRoot,
1124
1055
  resolveUninstallExecutionPlanWithWizard,
1125
1056
  runUninstallCommand,
1126
1057
  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-5JG4QJLO.js";
9
- import "./chunk-5SSNE5GM.js";
10
- import "./chunk-XCBVSGCS.js";
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: "Show this machine's Fabric uid and mounted stores" },
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",
4
- "description": "Fabric CLI — installs the MCP server + skills + hooks for Claude Code, Cursor, and Codex CLI; runs doctor / knowledge maintenance.",
3
+ "version": "2.2.0-rc.9",
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.4",
49
- "@fenglimg/fabric-server": "2.2.0-rc.4"
49
+ "@fenglimg/fabric-server": "2.2.0-rc.9",
50
+ "@fenglimg/fabric-shared": "2.2.0-rc.9"
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/Cursor as stderr. The edit always proceeds.
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 / Cursor) — see hooks/configs/*.json. This
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/Cursor.
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/Cursor stderr). The installer copies every lib/*.cjs alongside the hook.
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/Cursor: stderr. Never a gate.
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), {