@fenglimg/fabric-cli 2.1.0-rc.2 → 2.2.0-rc.3

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 (49) hide show
  1. package/dist/{chunk-PWLW3B57.js → chunk-2CY4BMTH.js} +5 -1
  2. package/dist/chunk-5LQIHYFC.js +64 -0
  3. package/dist/chunk-5ZUMLCD5.js +248 -0
  4. package/dist/{chunk-WWNXR34K.js → chunk-BO4XIZWZ.js} +8 -1
  5. package/dist/chunk-EOT63RDH.js +36 -0
  6. package/dist/{chunk-BATF4PEJ.js → chunk-F6ITRM7T.js} +4 -4
  7. package/dist/{chunk-WU6GAPKH.js → chunk-H3FE6VIK.js} +3 -5
  8. package/dist/{chunk-MF3OTILQ.js → chunk-XC5RUHLK.js} +29 -8
  9. package/dist/chunk-XCBVSGCS.js +25 -0
  10. package/dist/{chunk-F46ORPOA.js → chunk-XHHCRDIR.js} +149 -7
  11. package/dist/{config-XJIPZNUP.js → config-VJMXCLXW.js} +3 -3
  12. package/dist/{doctor-QVNPHLJK.js → doctor-J4O3X54I.js} +154 -30
  13. package/dist/index.js +57 -16
  14. package/dist/{install-2HDO5FTQ.js → install-BULNDUIM.js} +241 -108
  15. package/dist/{metrics-ACEQFPDU.js → metrics-RER6NLFC.js} +22 -9
  16. package/dist/{onboard-coverage-MFCAEBDO.js → onboard-coverage-JWQWDZW7.js} +1 -1
  17. package/dist/{plan-context-hint-FC6P3WFE.js → plan-context-hint-CHVZGOZ5.js} +21 -8
  18. package/dist/{scope-explain-2F2R5URO.js → scope-explain-BWRWBCCP.js} +19 -5
  19. package/dist/{status-GLQWLWH6.js → status-PANEGKU2.js} +17 -6
  20. package/dist/store-66NK2FTQ.js +443 -0
  21. package/dist/sync-EA5HZMXM.js +395 -0
  22. package/dist/{uninstall-TAXSUSKH.js → uninstall-F75MPKQC.js} +61 -4
  23. package/dist/whoami-66YKY5DZ.js +47 -0
  24. package/package.json +3 -3
  25. package/templates/hooks/cite-policy-evict.cjs +412 -160
  26. package/templates/hooks/configs/claude-code.json +17 -2
  27. package/templates/hooks/configs/codex-hooks.json +14 -2
  28. package/templates/hooks/configs/cursor-hooks.json +14 -2
  29. package/templates/hooks/fabric-hint.cjs +247 -19
  30. package/templates/hooks/knowledge-hint-broad.cjs +176 -10
  31. package/templates/hooks/knowledge-hint-narrow.cjs +64 -5
  32. package/templates/hooks/lib/injection-log.cjs +91 -0
  33. package/templates/hooks/lib/state-store.cjs +30 -11
  34. package/templates/hooks/post-tooluse-mutation.cjs +285 -0
  35. package/templates/hooks/session-end-marker.cjs +140 -0
  36. package/templates/skills/fabric-archive/SKILL.md +7 -1
  37. package/templates/skills/fabric-audit/SKILL.md +53 -0
  38. package/templates/skills/fabric-connect/SKILL.md +48 -0
  39. package/templates/skills/fabric-review/SKILL.md +2 -0
  40. package/templates/skills/fabric-review/ref/cite-contract.md +56 -0
  41. package/templates/skills/fabric-store/SKILL.md +44 -0
  42. package/dist/chunk-HFQVXY6P.js +0 -86
  43. package/dist/chunk-L4Q55UC4.js +0 -52
  44. package/dist/chunk-LFIKMVY7.js +0 -27
  45. package/dist/chunk-RYAFBNES.js +0 -33
  46. package/dist/chunk-T5RPGCCM.js +0 -40
  47. package/dist/store-XTSE5TY6.js +0 -105
  48. package/dist/sync-BJCWDPNC.js +0 -245
  49. package/dist/whoami-B6AEMSEV.js +0 -31
@@ -0,0 +1,395 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ regenerateBindingsSnapshot
4
+ } from "./chunk-H3FE6VIK.js";
5
+ import "./chunk-EOT63RDH.js";
6
+ import {
7
+ getProjectTranslator
8
+ } from "./chunk-2CY4BMTH.js";
9
+ import {
10
+ loadGlobalConfig,
11
+ resolveGlobalRoot
12
+ } from "./chunk-XCBVSGCS.js";
13
+
14
+ // src/commands/sync.ts
15
+ import { defineCommand } from "citty";
16
+
17
+ // src/sync/run-sync.ts
18
+ import { execFileSync } from "child_process";
19
+ import { existsSync, mkdirSync, readFileSync, renameSync, rmSync, writeFileSync } from "fs";
20
+ import { join } from "path";
21
+ import { GLOBAL_STATE_DIR, storeRelativePath } from "@fenglimg/fabric-shared";
22
+ import { GenericIOError } from "@fenglimg/fabric-shared/errors";
23
+
24
+ // src/sync/state-machine.ts
25
+ function syncTransition(state, event) {
26
+ switch (state) {
27
+ case "pending":
28
+ if (event === "rebase_clean") return "synced";
29
+ if (event === "rebase_conflict") return "conflict";
30
+ if (event === "network_unavailable") return "offline";
31
+ break;
32
+ case "conflict":
33
+ if (event === "user_continue") return "synced";
34
+ if (event === "user_abort") return "aborted";
35
+ if (event === "network_unavailable") return "offline";
36
+ break;
37
+ case "offline":
38
+ if (event === "retry" || event === "rebase_clean") return "synced";
39
+ if (event === "rebase_conflict") return "conflict";
40
+ if (event === "network_unavailable") return "offline";
41
+ break;
42
+ case "synced":
43
+ case "aborted":
44
+ break;
45
+ }
46
+ throw new Error(`invalid sync transition: '${state}' --${event}-->`);
47
+ }
48
+ function planSync(stores) {
49
+ return { stores: stores.map((s) => ({ ...s, state: "pending" })) };
50
+ }
51
+ function applySyncEvent(session, alias, event) {
52
+ return {
53
+ stores: session.stores.map(
54
+ (s) => s.alias === alias ? { ...s, state: syncTransition(s.state, event) } : s
55
+ )
56
+ };
57
+ }
58
+ function continueSync(session) {
59
+ const conflicted = session.stores.find((s) => s.state === "conflict");
60
+ if (conflicted === void 0) {
61
+ throw new Error("`sync --continue` with no conflicted store to resume");
62
+ }
63
+ return applySyncEvent(session, conflicted.alias, "user_continue");
64
+ }
65
+ function abortSync(session) {
66
+ const conflicted = session.stores.find((s) => s.state === "conflict");
67
+ if (conflicted === void 0) {
68
+ throw new Error("`sync --abort` with no conflicted store to abort");
69
+ }
70
+ return applySyncEvent(session, conflicted.alias, "user_abort");
71
+ }
72
+ function isSyncSettled(session) {
73
+ return session.stores.every((s) => s.state !== "pending" && s.state !== "conflict");
74
+ }
75
+ function deferredPushStores(session) {
76
+ return session.stores.filter((s) => s.state === "offline");
77
+ }
78
+
79
+ // src/sync/run-sync.ts
80
+ var NO_GLOBAL_CONFIG = "no global Fabric config \u2014 run `fabric install --global <url>` first";
81
+ var NO_SESSION = "no sync in progress \u2014 run `fabric sync` first";
82
+ var NO_CONFLICT = "no conflicted store to resume \u2014 sync is not paused";
83
+ function syncSessionPath(globalRoot) {
84
+ return join(globalRoot, GLOBAL_STATE_DIR, "sync-session.json");
85
+ }
86
+ function loadSession(globalRoot) {
87
+ const path = syncSessionPath(globalRoot);
88
+ if (!existsSync(path)) {
89
+ return null;
90
+ }
91
+ const raw = readFileSync(path, "utf8");
92
+ try {
93
+ return JSON.parse(raw);
94
+ } catch (error) {
95
+ const corruptedPath = `${path}.corrupted.${Date.now()}`;
96
+ try {
97
+ writeFileSync(corruptedPath, raw, "utf8");
98
+ } catch {
99
+ }
100
+ throw new GenericIOError(
101
+ `sync-session.json is corrupt (forensic copy: ${corruptedPath}). Parse error: ${error instanceof Error ? error.message : String(error)}`,
102
+ {
103
+ actionHint: `Delete ${path} to start a fresh sync (any in-progress rebase must be resolved manually with git first).`,
104
+ details: { path, corruptedPath }
105
+ }
106
+ );
107
+ }
108
+ }
109
+ function saveSession(globalRoot, session) {
110
+ const path = syncSessionPath(globalRoot);
111
+ mkdirSync(join(path, ".."), { recursive: true });
112
+ const tmpPath = `${path}.${process.pid}.tmp`;
113
+ writeFileSync(tmpPath, `${JSON.stringify(session, null, 2)}
114
+ `, "utf8");
115
+ renameSync(tmpPath, path);
116
+ }
117
+ function clearSession(globalRoot) {
118
+ rmSync(syncSessionPath(globalRoot), { force: true });
119
+ }
120
+ function defaultPull(storeDir) {
121
+ try {
122
+ execFileSync("git", ["pull", "--rebase"], {
123
+ cwd: storeDir,
124
+ stdio: ["ignore", "pipe", "pipe"]
125
+ });
126
+ return "clean";
127
+ } catch (error) {
128
+ const detail = `${gitErrText(error, "stdout")}${gitErrText(error, "stderr")}`;
129
+ if (/CONFLICT|could not apply|needs merge|rebase --continue/i.test(detail)) {
130
+ return "conflict";
131
+ }
132
+ if (/could not resolve host|could not read from remote|unable to access|connection|network is unreachable|timed out/i.test(
133
+ detail
134
+ )) {
135
+ return "offline";
136
+ }
137
+ const gitMessage = detail.trim().length > 0 ? detail.trim() : "unknown git error";
138
+ throw new GenericIOError(`git pull --rebase failed in ${storeDir}: ${gitMessage}`, {
139
+ actionHint: "resolve the git issue above (e.g. authentication, a dirty working tree, or a detached HEAD), then re-run `fabric sync`",
140
+ details: error
141
+ });
142
+ }
143
+ }
144
+ function gitErrText(error, key) {
145
+ const value = error[key];
146
+ return typeof value === "string" || Buffer.isBuffer(value) ? String(value) : "";
147
+ }
148
+ function defaultPush(storeDir) {
149
+ try {
150
+ execFileSync("git", ["push"], {
151
+ cwd: storeDir,
152
+ stdio: ["ignore", "pipe", "pipe"]
153
+ });
154
+ return "clean";
155
+ } catch (error) {
156
+ const detail = `${gitErrText(error, "stdout")}${gitErrText(error, "stderr")}`;
157
+ if (/could not resolve host|could not read from remote|unable to access|connection|network is unreachable|timed out/i.test(
158
+ detail
159
+ )) {
160
+ return "offline";
161
+ }
162
+ const gitMessage = detail.trim().length > 0 ? detail.trim() : "unknown git error";
163
+ throw new GenericIOError(`git push failed in ${storeDir}: ${gitMessage}`, {
164
+ actionHint: "resolve the git issue above (e.g. authentication, no upstream branch, or a rejected non-fast-forward push), then re-run `fabric sync`",
165
+ details: error
166
+ });
167
+ }
168
+ }
169
+ function defaultCommitDirty(storeDir) {
170
+ try {
171
+ execFileSync("git", ["rev-parse", "--is-inside-work-tree"], {
172
+ cwd: storeDir,
173
+ stdio: ["ignore", "ignore", "ignore"]
174
+ });
175
+ } catch {
176
+ return;
177
+ }
178
+ try {
179
+ execFileSync("git", ["add", "-A"], { cwd: storeDir, stdio: ["ignore", "ignore", "pipe"] });
180
+ try {
181
+ execFileSync("git", ["diff", "--cached", "--quiet"], {
182
+ cwd: storeDir,
183
+ stdio: ["ignore", "ignore", "ignore"]
184
+ });
185
+ return;
186
+ } catch {
187
+ execFileSync("git", ["commit", "-m", "fabric: sync local knowledge changes"], {
188
+ cwd: storeDir,
189
+ stdio: ["ignore", "ignore", "pipe"]
190
+ });
191
+ }
192
+ } catch {
193
+ }
194
+ }
195
+ function runRebaseStep(storeDir, step) {
196
+ try {
197
+ execFileSync("git", ["rebase", `--${step}`], {
198
+ cwd: storeDir,
199
+ stdio: ["ignore", "pipe", "pipe"]
200
+ });
201
+ } catch (error) {
202
+ const detail = `${gitErrText(error, "stdout")}${gitErrText(error, "stderr")}`.trim();
203
+ const gitMessage = detail.length > 0 ? detail : "unknown git error";
204
+ throw new GenericIOError(`git rebase --${step} failed in ${storeDir}: ${gitMessage}`, {
205
+ actionHint: step === "continue" ? "resolve the remaining conflicts (git status) and stage them, then re-run `fabric sync --continue`; or run `fabric sync --abort` to discard the rebase" : "inspect the store with `git status`; if no rebase is in progress the session may already be resolved \u2014 delete sync-session.json to reset",
206
+ details: error
207
+ });
208
+ }
209
+ }
210
+ function defaultRebaseContinue(storeDir) {
211
+ runRebaseStep(storeDir, "continue");
212
+ }
213
+ function defaultRebaseAbort(storeDir) {
214
+ runRebaseStep(storeDir, "abort");
215
+ }
216
+ var OUTCOME_EVENT = {
217
+ clean: "rebase_clean",
218
+ conflict: "rebase_conflict",
219
+ offline: "network_unavailable"
220
+ };
221
+ function walkPending(session, storeDirOf, pull, push, commit, pushableAliases) {
222
+ let next = session;
223
+ for (const store of session.stores) {
224
+ if (store.state !== "pending") {
225
+ continue;
226
+ }
227
+ const dir = storeDirOf(store);
228
+ commit(dir);
229
+ const pullOutcome = pull(dir);
230
+ if (pullOutcome !== "clean") {
231
+ next = applySyncEvent(next, store.alias, OUTCOME_EVENT[pullOutcome]);
232
+ if (pullOutcome === "conflict") {
233
+ break;
234
+ }
235
+ continue;
236
+ }
237
+ if (!pushableAliases.has(store.alias)) {
238
+ next = applySyncEvent(next, store.alias, "rebase_clean");
239
+ continue;
240
+ }
241
+ const pushOutcome = push(dir);
242
+ next = applySyncEvent(
243
+ next,
244
+ store.alias,
245
+ pushOutcome === "clean" ? "rebase_clean" : "network_unavailable"
246
+ );
247
+ }
248
+ return next;
249
+ }
250
+ function finalize(session, options, globalRoot) {
251
+ const settled = isSyncSettled(session);
252
+ let snapshotWritten = false;
253
+ if (settled) {
254
+ clearSession(globalRoot);
255
+ const snapshot = regenerateBindingsSnapshot(options.projectRoot, {
256
+ globalRoot,
257
+ now: options.now,
258
+ ...options.writeScope === void 0 ? {} : { writeScope: options.writeScope }
259
+ });
260
+ snapshotWritten = snapshot !== null;
261
+ } else {
262
+ saveSession(globalRoot, session);
263
+ }
264
+ return { session, settled, deferred: deferredPushStores(session), snapshotWritten };
265
+ }
266
+ function runStartSync(options) {
267
+ const globalRoot = options.globalRoot ?? resolveGlobalRoot();
268
+ const config = loadGlobalConfig(globalRoot);
269
+ if (config === null) {
270
+ throw new Error(NO_GLOBAL_CONFIG);
271
+ }
272
+ const syncable = config.stores.filter((store) => store.remote !== void 0);
273
+ const session = planSync(
274
+ syncable.map((store) => ({ alias: store.alias, store_uuid: store.store_uuid }))
275
+ );
276
+ const storeDirOf = (status) => join(globalRoot, storeRelativePath(status.store_uuid));
277
+ const pushableAliases = pushableAliasesOf(config);
278
+ const walked = walkPending(
279
+ session,
280
+ storeDirOf,
281
+ options.pull ?? defaultPull,
282
+ options.push ?? defaultPush,
283
+ options.commit ?? defaultCommitDirty,
284
+ pushableAliases
285
+ );
286
+ return finalize(walked, options, globalRoot);
287
+ }
288
+ function pushableAliasesOf(config) {
289
+ return new Set(
290
+ config.stores.filter((store) => store.remote !== void 0 && (store.writable ?? true)).map((store) => store.alias)
291
+ );
292
+ }
293
+ function runContinueSync(options) {
294
+ const globalRoot = options.globalRoot ?? resolveGlobalRoot();
295
+ const session = loadSession(globalRoot);
296
+ if (session === null) {
297
+ throw new GenericIOError(NO_SESSION, {
298
+ actionHint: "Run `fabric sync` to start a sync before `--continue`/`--abort`."
299
+ });
300
+ }
301
+ const conflicted = session.stores.find((store) => store.state === "conflict");
302
+ if (conflicted === void 0) {
303
+ throw new GenericIOError(NO_CONFLICT, {
304
+ actionHint: "The sync is not paused on a conflict; there is nothing to resume."
305
+ });
306
+ }
307
+ const storeDirOf = (status) => join(globalRoot, storeRelativePath(status.store_uuid));
308
+ (options.rebaseContinue ?? defaultRebaseContinue)(storeDirOf(conflicted));
309
+ const pushableAliases = pushableAliasesOf(loadGlobalConfig(globalRoot) ?? { stores: [] });
310
+ const push = options.push ?? defaultPush;
311
+ let advanced;
312
+ if (pushableAliases.has(conflicted.alias)) {
313
+ const pushOutcome = push(storeDirOf(conflicted));
314
+ advanced = applySyncEvent(
315
+ session,
316
+ conflicted.alias,
317
+ pushOutcome === "clean" ? "user_continue" : "network_unavailable"
318
+ );
319
+ } else {
320
+ advanced = continueSync(session);
321
+ }
322
+ const resumed = walkPending(
323
+ advanced,
324
+ storeDirOf,
325
+ options.pull ?? defaultPull,
326
+ push,
327
+ options.commit ?? defaultCommitDirty,
328
+ pushableAliases
329
+ );
330
+ return finalize(resumed, options, globalRoot);
331
+ }
332
+ function runAbortSync(options) {
333
+ const globalRoot = options.globalRoot ?? resolveGlobalRoot();
334
+ const session = loadSession(globalRoot);
335
+ if (session === null) {
336
+ throw new GenericIOError(NO_SESSION, {
337
+ actionHint: "Run `fabric sync` to start a sync before `--continue`/`--abort`."
338
+ });
339
+ }
340
+ const conflicted = session.stores.find((store) => store.state === "conflict");
341
+ if (conflicted === void 0) {
342
+ throw new GenericIOError(NO_CONFLICT, {
343
+ actionHint: "The sync is not paused on a conflict; there is nothing to resume."
344
+ });
345
+ }
346
+ const storeDirOf = (status) => join(globalRoot, storeRelativePath(status.store_uuid));
347
+ (options.rebaseAbort ?? defaultRebaseAbort)(storeDirOf(conflicted));
348
+ const pushableAliases = pushableAliasesOf(loadGlobalConfig(globalRoot) ?? { stores: [] });
349
+ const resumed = walkPending(
350
+ abortSync(session),
351
+ storeDirOf,
352
+ options.pull ?? defaultPull,
353
+ options.push ?? defaultPush,
354
+ options.commit ?? defaultCommitDirty,
355
+ pushableAliases
356
+ );
357
+ return finalize(resumed, options, globalRoot);
358
+ }
359
+
360
+ // src/commands/sync.ts
361
+ function report(result, projectRoot) {
362
+ const t = getProjectTranslator(projectRoot);
363
+ for (const store of result.session.stores) {
364
+ console.log(`${store.alias} ${store.state}`);
365
+ }
366
+ if (result.deferred.length > 0) {
367
+ console.log(t("cli.sync.deferred", { count: String(result.deferred.length) }));
368
+ }
369
+ if (!result.settled) {
370
+ console.log(t("cli.sync.paused"));
371
+ }
372
+ }
373
+ var sync_default = defineCommand({
374
+ meta: { name: "sync", description: "Pull --rebase + push every mounted store; resume conflicts" },
375
+ args: {
376
+ continue: { type: "boolean", description: "Resume after resolving a rebase conflict" },
377
+ abort: { type: "boolean", description: "Abort the conflicted store's rebase" }
378
+ },
379
+ run({ args }) {
380
+ const projectRoot = process.cwd();
381
+ const options = { projectRoot, now: (/* @__PURE__ */ new Date()).toISOString() };
382
+ if (args.continue === true) {
383
+ report(runContinueSync(options), projectRoot);
384
+ return;
385
+ }
386
+ if (args.abort === true) {
387
+ report(runAbortSync(options), projectRoot);
388
+ return;
389
+ }
390
+ report(runStartSync(options), projectRoot);
391
+ }
392
+ });
393
+ export {
394
+ sync_default as default
395
+ };
@@ -7,10 +7,10 @@ import {
7
7
  HOOK_SCRIPT_DESTINATIONS,
8
8
  SKILL_DESTINATIONS,
9
9
  fabricAgentsSnapshotPath
10
- } from "./chunk-F46ORPOA.js";
10
+ } from "./chunk-XHHCRDIR.js";
11
11
  import {
12
12
  paint
13
- } from "./chunk-WWNXR34K.js";
13
+ } from "./chunk-BO4XIZWZ.js";
14
14
  import {
15
15
  createDebugLogger,
16
16
  resolveDevMode
@@ -18,10 +18,10 @@ import {
18
18
  import {
19
19
  detectClientSupports,
20
20
  resolveClients
21
- } from "./chunk-MF3OTILQ.js";
21
+ } from "./chunk-XC5RUHLK.js";
22
22
  import {
23
23
  t
24
- } from "./chunk-PWLW3B57.js";
24
+ } from "./chunk-2CY4BMTH.js";
25
25
 
26
26
  // src/commands/uninstall.ts
27
27
  import { existsSync as existsSync2, statSync } from "fs";
@@ -49,6 +49,12 @@ async function uninstallFabricImportSkill(projectRoot) {
49
49
  async function uninstallFabricSyncSkill(projectRoot) {
50
50
  return removeSkill("skill-sync", SKILL_DESTINATIONS.fabricSync, projectRoot);
51
51
  }
52
+ async function uninstallFabricAuditSkill(projectRoot) {
53
+ return removeSkill("skill-audit", SKILL_DESTINATIONS.fabricAudit, projectRoot);
54
+ }
55
+ async function uninstallFabricConnectSkill(projectRoot) {
56
+ return removeSkill("skill-connect", SKILL_DESTINATIONS.fabricConnect, projectRoot);
57
+ }
52
58
  async function removeSkill(step, rels, projectRoot) {
53
59
  const results = [];
54
60
  for (const rel of rels) {
@@ -75,6 +81,27 @@ async function removeKnowledgeHintNarrowHook(projectRoot) {
75
81
  projectRoot
76
82
  );
77
83
  }
84
+ async function removeCitePolicyEvictHook(projectRoot) {
85
+ return removeHookScripts(
86
+ "hook-cite-policy-evict-script",
87
+ HOOK_SCRIPT_DESTINATIONS.citePolicyEvict,
88
+ projectRoot
89
+ );
90
+ }
91
+ async function removeSessionEndMarkerHook(projectRoot) {
92
+ return removeHookScripts(
93
+ "hook-session-end-script",
94
+ HOOK_SCRIPT_DESTINATIONS.sessionEndMarker,
95
+ projectRoot
96
+ );
97
+ }
98
+ async function removePostTooluseMutationHook(projectRoot) {
99
+ return removeHookScripts(
100
+ "hook-post-tooluse-script",
101
+ HOOK_SCRIPT_DESTINATIONS.postTooluseMutation,
102
+ projectRoot
103
+ );
104
+ }
78
105
  async function removeHookScripts(step, rels, projectRoot) {
79
106
  const results = [];
80
107
  for (const rel of rels) {
@@ -304,6 +331,36 @@ async function uninstallBootstrapStage(projectRoot, _opts = {}) {
304
331
  projectRoot,
305
332
  () => removeArchiveHintHook(projectRoot)
306
333
  );
334
+ await runAndCollect(
335
+ results,
336
+ "hook-cite-policy-evict-script",
337
+ projectRoot,
338
+ () => removeCitePolicyEvictHook(projectRoot)
339
+ );
340
+ await runAndCollect(
341
+ results,
342
+ "hook-session-end-script",
343
+ projectRoot,
344
+ () => removeSessionEndMarkerHook(projectRoot)
345
+ );
346
+ await runAndCollect(
347
+ results,
348
+ "hook-post-tooluse-script",
349
+ projectRoot,
350
+ () => removePostTooluseMutationHook(projectRoot)
351
+ );
352
+ await runAndCollect(
353
+ results,
354
+ "skill-connect",
355
+ projectRoot,
356
+ () => uninstallFabricConnectSkill(projectRoot)
357
+ );
358
+ await runAndCollect(
359
+ results,
360
+ "skill-audit",
361
+ projectRoot,
362
+ () => uninstallFabricAuditSkill(projectRoot)
363
+ );
307
364
  await runAndCollect(
308
365
  results,
309
366
  "skill-sync",
@@ -0,0 +1,47 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ getProjectTranslator
4
+ } from "./chunk-2CY4BMTH.js";
5
+ import {
6
+ warnUnknownFlags,
7
+ whoami
8
+ } from "./chunk-5LQIHYFC.js";
9
+ import "./chunk-5ZUMLCD5.js";
10
+ import "./chunk-XCBVSGCS.js";
11
+
12
+ // src/commands/whoami.ts
13
+ import { defineCommand } from "citty";
14
+ var whoami_default = defineCommand({
15
+ meta: { name: "whoami", description: "Show this machine's Fabric uid and mounted stores" },
16
+ args: {
17
+ // F27: `--json` machine-readable output (was silently ignored — the command
18
+ // declared no args, so citty swallowed the flag and still printed text).
19
+ json: { type: "boolean", description: "Emit machine-readable JSON instead of text" }
20
+ },
21
+ run({ args }) {
22
+ warnUnknownFlags(["json"]);
23
+ const info = whoami();
24
+ if (args.json === true) {
25
+ console.log(JSON.stringify(info, null, 2));
26
+ return;
27
+ }
28
+ const t = getProjectTranslator();
29
+ if (info === null) {
30
+ console.log(t("cli.cmd.no-global-config"));
31
+ return;
32
+ }
33
+ console.log(t("cli.whoami.uid", { uid: info.uid }));
34
+ if (info.stores.length === 0) {
35
+ console.log(t("cli.whoami.stores-none"));
36
+ return;
37
+ }
38
+ console.log(t("cli.whoami.stores-label"));
39
+ const localOnly = t("cli.shared.local-only");
40
+ for (const store of info.stores) {
41
+ console.log(` ${store.alias} ${store.store_uuid}${store.local_only ? ` ${localOnly}` : ""}`);
42
+ }
43
+ }
44
+ });
45
+ export {
46
+ whoami_default as default
47
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fenglimg/fabric-cli",
3
- "version": "2.1.0-rc.2",
3
+ "version": "2.2.0-rc.3",
4
4
  "description": "Fabric CLI — installs the MCP server + skills + hooks for Claude Code, Cursor, and Codex CLI; runs doctor / knowledge maintenance.",
5
5
  "license": "MIT",
6
6
  "author": "wangzhichao <fenglimg90@gmail.com>",
@@ -45,8 +45,8 @@
45
45
  "tree-sitter-javascript": "^0.25.0",
46
46
  "tree-sitter-typescript": "^0.23.2",
47
47
  "web-tree-sitter": "^0.26.8",
48
- "@fenglimg/fabric-server": "2.1.0-rc.2",
49
- "@fenglimg/fabric-shared": "2.1.0-rc.2"
48
+ "@fenglimg/fabric-server": "2.2.0-rc.3",
49
+ "@fenglimg/fabric-shared": "2.2.0-rc.3"
50
50
  },
51
51
  "devDependencies": {
52
52
  "@types/node": "^22.15.0",