@fenglimg/fabric-cli 2.2.0-rc.3 → 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.
Files changed (75) hide show
  1. package/README.md +8 -5
  2. package/dist/{chunk-5LQIHYFC.js → chunk-27HK6H5Y.js} +10 -5
  3. package/dist/{chunk-F6ITRM7T.js → chunk-2KBCTMID.js} +29 -6
  4. package/dist/{chunk-XC5RUHLK.js → chunk-3IOLS5EK.js} +23 -38
  5. package/dist/{chunk-XHHCRDIR.js → chunk-CMDW3PYK.js} +105 -220
  6. package/dist/chunk-FEOPLBGA.js +150 -0
  7. package/dist/{chunk-XCBVSGCS.js → chunk-FNHDQTPC.js} +1 -10
  8. package/dist/{chunk-2CY4BMTH.js → chunk-HORSMSZL.js} +9 -5
  9. package/dist/{doctor-J4O3X54I.js → chunk-JTHWLUD3.js} +103 -51
  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-QPAW6IYT.js +387 -0
  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-REZDNH4A.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-2COC3DO3.js +3277 -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-PANEGKU2.js → status-4R3TM4FJ.js} +8 -5
  28. package/dist/store-HOCORVL3.js +563 -0
  29. package/dist/{sync-EA5HZMXM.js → sync-DT5UJMMR.js} +36 -13
  30. package/dist/{uninstall-F75MPKQC.js → uninstall-62F4LNKI.js} +62 -140
  31. package/dist/{whoami-66YKY5DZ.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 +301 -161
  38. package/templates/hooks/knowledge-hint-broad.cjs +426 -207
  39. package/templates/hooks/knowledge-hint-narrow.cjs +56 -56
  40. package/templates/hooks/lib/banner-i18n.cjs +31 -0
  41. package/templates/hooks/lib/bindings-snapshot-reader.cjs +117 -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/lib/summary-fallback.cjs +82 -19
  47. package/templates/hooks/post-tooluse-mutation.cjs +112 -11
  48. package/templates/skills/fabric/SKILL.md +94 -0
  49. package/templates/skills/fabric-archive/SKILL.md +29 -26
  50. package/templates/skills/fabric-archive/ref/dry-run-scope.md +1 -1
  51. package/templates/skills/fabric-archive/ref/i18n-policy.md +2 -3
  52. package/templates/skills/fabric-archive/ref/phase-1-5-onboard.md +2 -3
  53. package/templates/skills/fabric-archive/ref/phase-1-cross-session.md +1 -1
  54. package/templates/skills/fabric-archive/ref/phase-2-5-viability.md +1 -1
  55. package/templates/skills/fabric-archive/ref/phase-3-6-related-edges.md +18 -0
  56. package/templates/skills/fabric-archive/ref/phase-3-7-semantic-scope.md +47 -0
  57. package/templates/skills/fabric-audit/SKILL.md +13 -3
  58. package/templates/skills/fabric-connect/SKILL.md +3 -3
  59. package/templates/skills/fabric-import/SKILL.md +7 -7
  60. package/templates/skills/fabric-import/ref/i18n-policy.md +2 -3
  61. package/templates/skills/fabric-import/ref/state-recovery.md +1 -2
  62. package/templates/skills/fabric-review/SKILL.md +5 -5
  63. package/templates/skills/fabric-review/ref/cite-contract.md +1 -1
  64. package/templates/skills/fabric-review/ref/i18n-policy.md +2 -3
  65. package/templates/skills/fabric-review/ref/output-contract.md +1 -1
  66. package/templates/skills/fabric-review/ref/per-mode-flows.md +2 -2
  67. package/templates/skills/fabric-review/ref/worked-examples.md +1 -1
  68. package/templates/skills/fabric-store/SKILL.md +1 -1
  69. package/templates/skills/fabric-sync/SKILL.md +1 -1
  70. package/templates/skills/lib/shared-policy.md +2 -2
  71. package/dist/chunk-5ZUMLCD5.js +0 -248
  72. package/dist/install-BULNDUIM.js +0 -2816
  73. package/dist/store-66NK2FTQ.js +0 -443
  74. package/templates/hooks/configs/cursor-hooks.json +0 -30
  75. package/templates/hooks/lib/cite-contract-reminder.cjs +0 -179
@@ -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-CMDW3PYK.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
@@ -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", { deleteWhenEmpty: false }));
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, options) {
225
- const step = relPath.endsWith(".mdc") ? "bootstrap-cursor" : "bootstrap-codex";
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 personalKnowledgeDir = resolve(resolvePersonalFabricRoot(), ".fabric", "knowledge");
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
- 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));
667
+ const safeEntries = entries.filter((entry) => !isInsideGlobalStoresRoot(entry.path, globalStoresDir));
721
668
  return {
722
669
  target: absTarget,
723
670
  fabricDir,
724
- personalKnowledgeDir,
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
- note(
909
- t("cli.uninstall.wizard.overview.body", {
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
- 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
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(continueWithTarget) || !continueWithTarget) {
872
+ if (isCancel(picked)) {
921
873
  emitUninstallWizardCancellation();
922
874
  return null;
923
875
  }
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
- }
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: !groupedSelection.scaffold,
964
- skipBootstrap: !groupedSelection.bootstrap,
965
- skipMcp: !groupedSelection.mcp
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 groupedSelection;
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(` - ${target}/.fabric/knowledge/ ${paint.muted(t("cli.uninstall.plan.preserves.knowledge"))}`);
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
- return `${paint.ai(t("cli.shared.next"))} ${paint.muted(t(`cli.uninstall.stages.${stageName}`))}`;
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 isInsidePersonalRoot(candidate, personalKnowledgeDir) {
1010
+ function isInsideGlobalStoresRoot(candidate, globalStoresDir) {
1086
1011
  const candidateAbs = resolve(candidate);
1087
- const rootAbs = resolve(personalKnowledgeDir);
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
- isInsidePersonalRoot,
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-5LQIHYFC.js";
9
- import "./chunk-5ZUMLCD5.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.3",
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.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-server": "2.2.0-rc.3",
49
- "@fenglimg/fabric-shared": "2.2.0-rc.3"
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/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), {
@@ -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, Cursor, and Codex CLI. Adding a new client requires extending
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 all three. Differences are pinned here
44
- side-by-side so anyone editing one config knows what the others require.
45
-
46
- | Axis | Claude Code | Codex CLI | Cursor |
47
- | -------------------- | ---------------------------------------- | -------------------------------------------------- | ----------------------------------------------- |
48
- | Settings file | `.claude/settings.json` | `.codex/hooks.json` | `.cursor/hooks.json` |
49
- | Top-level envelope | `hooks: { ... }` (no version) | `events: { ... }` (no version) | `{ version: 1, hooks: { ... } }` (number, not string) |
50
- | Event-name case | PascalCase: `Stop`, `SessionStart`, `PreToolUse`, `UserPromptSubmit` | PascalCase: `Stop`, `SessionStart`, `PreToolUse` | camelCase: `stop`, `sessionStart`, `preToolUse` |
51
- | Per-entry shape | Nested matcher: `[{matcher, hooks:[{type:"command", command}]}]` | Flat: `[{command, matcher?}]` | Flat: `[{command, matcher?, type?, timeout?, loop_limit?, failClosed?}]` |
52
- | Path interpolation | `${CLAUDE_PROJECT_DIR}` (env var) | `"$(git rev-parse --show-toplevel)"` (shell expansion) | project-relative (resolved by Cursor) |
53
- | Cite-policy event | `UserPromptSubmit` (per-prompt) | `SessionStart` 2nd entry (rc.37 NEW-21 parity) | `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 two — `fabric install` merges each into its respective
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 all three `<client>/hooks/` destinations by the
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:
@@ -39,7 +39,7 @@
39
39
  ],
40
40
  "PostToolUse": [
41
41
  {
42
- "matcher": "Edit|Write|MultiEdit",
42
+ "matcher": "Edit|Write|MultiEdit|Read",
43
43
  "hooks": [
44
44
  {
45
45
  "type": "command",
@@ -12,17 +12,17 @@
12
12
  ],
13
13
  "PreToolUse": [
14
14
  {
15
- "matcher": "Edit|Write|MultiEdit",
15
+ "matcher": "Edit|Write|MultiEdit|apply_patch",
16
16
  "command": "\"$(git rev-parse --show-toplevel)/.codex/hooks/knowledge-hint-narrow.cjs\""
17
17
  },
18
18
  {
19
- "matcher": "Edit|Write|MultiEdit",
19
+ "matcher": "Edit|Write|MultiEdit|apply_patch",
20
20
  "command": "\"$(git rev-parse --show-toplevel)/.codex/hooks/cite-policy-evict.cjs\""
21
21
  }
22
22
  ],
23
23
  "PostToolUse": [
24
24
  {
25
- "matcher": "Edit|Write|MultiEdit",
25
+ "matcher": "Edit|Write|MultiEdit|apply_patch|Read",
26
26
  "command": "\"$(git rev-parse --show-toplevel)/.codex/hooks/post-tooluse-mutation.cjs\""
27
27
  }
28
28
  ],