@fenglimg/fabric-cli 2.0.1 → 2.1.0-rc.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,7 +1,4 @@
1
1
  #!/usr/bin/env node
2
- import {
3
- installMcpClients
4
- } from "./chunk-BATF4PEJ.js";
5
2
  import {
6
3
  cleanupDeprecatedSkills,
7
4
  installArchiveHintHook,
@@ -9,6 +6,7 @@ import {
9
6
  installFabricArchiveSkill,
10
7
  installFabricImportSkill,
11
8
  installFabricReviewSkill,
9
+ installFabricSyncSkill,
12
10
  installHookLibs,
13
11
  installKnowledgeHintBroadHook,
14
12
  installKnowledgeHintNarrowHook,
@@ -21,10 +19,7 @@ import {
21
19
  writeCodexBootstrapManagedBlock,
22
20
  writeCursorBootstrapManagedBlock,
23
21
  writeFabricAgentsSnapshot
24
- } from "./chunk-D25XJ4BC.js";
25
- import {
26
- detectClientSupports
27
- } from "./chunk-MF3OTILQ.js";
22
+ } from "./chunk-F46ORPOA.js";
28
23
  import {
29
24
  displayWidth,
30
25
  padEnd,
@@ -34,16 +29,28 @@ import {
34
29
  createDebugLogger,
35
30
  resolveDevMode
36
31
  } from "./chunk-COI5VDFU.js";
32
+ import {
33
+ installMcpClients
34
+ } from "./chunk-BATF4PEJ.js";
35
+ import {
36
+ detectClientSupports
37
+ } from "./chunk-MF3OTILQ.js";
37
38
  import {
38
39
  t
39
40
  } from "./chunk-PWLW3B57.js";
41
+ import {
42
+ globalConfigPath,
43
+ loadGlobalConfig,
44
+ resolveGlobalRoot,
45
+ saveGlobalConfig
46
+ } from "./chunk-RYAFBNES.js";
40
47
 
41
48
  // src/commands/install.ts
42
- import { randomUUID } from "crypto";
49
+ import { randomUUID as randomUUID2 } from "crypto";
43
50
  import { homedir } from "os";
44
51
  import * as childProcess from "child_process";
45
- import { appendFileSync, existsSync as existsSync4, mkdirSync, readFileSync as readFileSync3, rmSync, statSync as statSync4, writeFileSync } from "fs";
46
- import { dirname, isAbsolute as isAbsolute3, join as join4, resolve as resolve3 } from "path";
52
+ import { appendFileSync, existsSync as existsSync4, mkdirSync as mkdirSync2, readFileSync as readFileSync3, rmSync as rmSync2, statSync as statSync4, writeFileSync } from "fs";
53
+ import { dirname, isAbsolute as isAbsolute3, join as join6, resolve as resolve3 } from "path";
47
54
  import { cancel, confirm, group, intro, isCancel, log, note, outro, select } from "@clack/prompts";
48
55
  import { defaultAgentsMetaCounters } from "@fenglimg/fabric-shared";
49
56
  import { atomicWriteJson } from "@fenglimg/fabric-shared/node/atomic-write";
@@ -59,6 +66,7 @@ async function installHooks(target, _options = {}) {
59
66
  results.push(...await runStep(() => installFabricArchiveSkill(normalizedTarget)));
60
67
  results.push(...await runStep(() => installFabricReviewSkill(normalizedTarget)));
61
68
  results.push(...await runStep(() => installFabricImportSkill(normalizedTarget)));
69
+ results.push(...await runStep(() => installFabricSyncSkill(normalizedTarget)));
62
70
  results.push(...await runStep(() => installSharedSkillLib(normalizedTarget)));
63
71
  results.push(...await runStep(() => installArchiveHintHook(normalizedTarget)));
64
72
  results.push(...await runStep(() => installKnowledgeHintBroadHook(normalizedTarget)));
@@ -182,20 +190,190 @@ function assertExistingDirectory(target) {
182
190
  }
183
191
  }
184
192
 
193
+ // src/install/run-global-install.ts
194
+ import { execFileSync as execFileSync2 } from "child_process";
195
+ import { randomUUID } from "crypto";
196
+ import { mkdirSync, mkdtempSync, renameSync } from "fs";
197
+ import { tmpdir } from "os";
198
+ import { join as join3 } from "path";
199
+ import { STORES_ROOT_DIR as STORES_ROOT_DIR2, addMountedStore, readStoreIdentity } from "@fenglimg/fabric-shared";
200
+
201
+ // src/store/uid.ts
202
+ import { execFileSync } from "child_process";
203
+ import { createHash } from "crypto";
204
+ function deriveUid() {
205
+ let email = "";
206
+ try {
207
+ email = execFileSync("git", ["config", "user.email"], {
208
+ encoding: "utf8",
209
+ stdio: ["ignore", "pipe", "ignore"]
210
+ }).trim();
211
+ } catch {
212
+ email = "";
213
+ }
214
+ if (email === "") {
215
+ return "u-anon";
216
+ }
217
+ const hash = createHash("sha256").update(email.toLowerCase()).digest("hex").slice(0, 12);
218
+ return `u-${hash}`;
219
+ }
220
+
221
+ // src/install/install-global.ts
222
+ import { rmSync } from "fs";
223
+ import { join as join2 } from "path";
224
+ import {
225
+ STORES_ROOT_DIR,
226
+ globalConfigSchema,
227
+ initStore
228
+ } from "@fenglimg/fabric-shared";
229
+
230
+ // src/install/transaction.ts
231
+ function errorMessage(error) {
232
+ return error instanceof Error ? error.message : String(error);
233
+ }
234
+ async function runInstallTransaction(steps) {
235
+ const receipt = { ok: true, steps: [] };
236
+ const applied = [];
237
+ for (let i = 0; i < steps.length; i++) {
238
+ const step = steps[i];
239
+ try {
240
+ await step.apply();
241
+ applied.push(step);
242
+ receipt.steps.push({ name: step.name, status: "applied" });
243
+ } catch (error) {
244
+ receipt.ok = false;
245
+ receipt.failedStep = step.name;
246
+ receipt.error = errorMessage(error);
247
+ receipt.steps.push({ name: step.name, status: "failed", error: errorMessage(error) });
248
+ for (let j = i + 1; j < steps.length; j++) {
249
+ receipt.steps.push({ name: steps[j].name, status: "skipped" });
250
+ }
251
+ for (const done of [...applied].reverse()) {
252
+ const entry = receipt.steps.find((s) => s.name === done.name);
253
+ try {
254
+ await done.rollback();
255
+ if (entry !== void 0) {
256
+ entry.status = "rolled_back";
257
+ }
258
+ } catch (rollbackError) {
259
+ if (entry !== void 0) {
260
+ entry.status = "rollback_failed";
261
+ entry.error = errorMessage(rollbackError);
262
+ }
263
+ }
264
+ }
265
+ return receipt;
266
+ }
267
+ }
268
+ return receipt;
269
+ }
270
+
271
+ // src/install/install-global.ts
272
+ async function installGlobalCore(options) {
273
+ const existing = loadGlobalConfig(options.globalRoot);
274
+ if (existing !== null) {
275
+ return {
276
+ receipt: { ok: true, steps: [{ name: "already-installed", status: "applied" }] },
277
+ config: existing,
278
+ alreadyInstalled: true
279
+ };
280
+ }
281
+ const alias = options.personalAlias ?? "personal";
282
+ const personalDir = join2(options.globalRoot, STORES_ROOT_DIR, options.personalStoreUuid);
283
+ let config = null;
284
+ const receipt = await runInstallTransaction([
285
+ {
286
+ name: "init-personal-store",
287
+ apply: () => {
288
+ initStore(
289
+ personalDir,
290
+ {
291
+ store_uuid: options.personalStoreUuid,
292
+ created_at: options.now,
293
+ canonical_alias: alias
294
+ },
295
+ { git: options.git }
296
+ );
297
+ },
298
+ rollback: () => {
299
+ rmSync(personalDir, { recursive: true, force: true });
300
+ }
301
+ },
302
+ {
303
+ name: "write-global-config",
304
+ apply: () => {
305
+ const next = globalConfigSchema.parse({
306
+ uid: options.uid,
307
+ stores: [{ store_uuid: options.personalStoreUuid, alias, personal: true }]
308
+ });
309
+ saveGlobalConfig(next, options.globalRoot);
310
+ config = next;
311
+ },
312
+ rollback: () => {
313
+ rmSync(globalConfigPath(options.globalRoot), { force: true });
314
+ }
315
+ }
316
+ ]);
317
+ return { receipt, config, alreadyInstalled: false };
318
+ }
319
+
320
+ // src/install/run-global-install.ts
321
+ function gitClone(url, dest) {
322
+ execFileSync2("git", ["clone", url, dest], { stdio: ["ignore", "ignore", "pipe"] });
323
+ }
324
+ function mountStoreFromRemote(url, globalRoot) {
325
+ const storesRoot = join3(globalRoot, STORES_ROOT_DIR2);
326
+ mkdirSync(storesRoot, { recursive: true });
327
+ const tmp = mkdtempSync(join3(tmpdir(), "fabric-clone-"));
328
+ const cloneDest = join3(tmp, "store");
329
+ gitClone(url, cloneDest);
330
+ const identity = readStoreIdentity(cloneDest);
331
+ if (identity === null) {
332
+ throw new Error(`cloned store at ${url} has no valid store.json (not a Fabric store)`);
333
+ }
334
+ const finalDir = join3(storesRoot, identity.store_uuid);
335
+ renameSync(cloneDest, finalDir);
336
+ const config = loadGlobalConfig(globalRoot);
337
+ if (config === null) {
338
+ throw new Error("global config missing after install");
339
+ }
340
+ const alias = identity.canonical_alias ?? "team";
341
+ saveGlobalConfig(
342
+ addMountedStore(config, { store_uuid: identity.store_uuid, alias, remote: url }),
343
+ globalRoot
344
+ );
345
+ console.log(`mounted store '${alias}' (${identity.store_uuid}) from ${url}`);
346
+ }
347
+ async function runGlobalInstall(options = {}, globalRoot = resolveGlobalRoot()) {
348
+ const uid = options.uid ?? deriveUid();
349
+ const personalStoreUuid = options.personalStoreUuid ?? randomUUID();
350
+ const now = options.now ?? (/* @__PURE__ */ new Date()).toISOString();
351
+ const result = await installGlobalCore({ globalRoot, uid, personalStoreUuid, now });
352
+ if (!result.receipt.ok) {
353
+ throw new Error(`global install failed at step '${result.receipt.failedStep}': ${result.receipt.error}`);
354
+ }
355
+ console.log(
356
+ result.alreadyInstalled ? "global Fabric already installed" : `installed global Fabric (uid ${uid})`
357
+ );
358
+ if (options.url !== void 0) {
359
+ mountStoreFromRemote(options.url, globalRoot);
360
+ }
361
+ }
362
+
185
363
  // src/lib/detect-language.ts
186
364
  import { existsSync as existsSync2, readdirSync, readFileSync, statSync as statSync2 } from "fs";
187
- import { join as join2 } from "path";
365
+ import { join as join4 } from "path";
188
366
  function detectExistingLanguage(target) {
189
367
  const ZH_CN_RATIO_THRESHOLD = 0.3;
190
368
  const samples = [];
191
- const readmePath = join2(target, "README.md");
369
+ const readmePath = join4(target, "README.md");
192
370
  if (existsSync2(readmePath)) {
193
371
  try {
194
372
  samples.push(readFileSync(readmePath, "utf8"));
195
373
  } catch {
196
374
  }
197
375
  }
198
- const docsDir = join2(target, "docs");
376
+ const docsDir = join4(target, "docs");
199
377
  if (existsSync2(docsDir)) {
200
378
  try {
201
379
  const stat = statSync2(docsDir);
@@ -204,7 +382,7 @@ function detectExistingLanguage(target) {
204
382
  if (!entry.isFile()) continue;
205
383
  if (!/\.(md|mdx|txt)$/iu.test(entry.name)) continue;
206
384
  try {
207
- samples.push(readFileSync(join2(docsDir, entry.name), "utf8"));
385
+ samples.push(readFileSync(join4(docsDir, entry.name), "utf8"));
208
386
  } catch {
209
387
  }
210
388
  }
@@ -236,10 +414,10 @@ function detectExistingLanguage(target) {
236
414
  }
237
415
 
238
416
  // src/scanner/forensic.ts
239
- import { execFileSync } from "child_process";
417
+ import { execFileSync as execFileSync3 } from "child_process";
240
418
  import { existsSync as existsSync3, readdirSync as readdirSync2, readFileSync as readFileSync2, statSync as statSync3 } from "fs";
241
419
  import { createRequire } from "module";
242
- import { basename, extname, isAbsolute as isAbsolute2, join as join3, posix, relative, resolve as resolve2, sep } from "path";
420
+ import { basename, extname, isAbsolute as isAbsolute2, join as join5, posix, relative, resolve as resolve2, sep } from "path";
243
421
  import {
244
422
  forensicReportSchema
245
423
  } from "@fenglimg/fabric-shared";
@@ -383,7 +561,7 @@ function buildTopology(root) {
383
561
  continue;
384
562
  }
385
563
  for (const entry of readdirSync2(current, { withFileTypes: true })) {
386
- const absolutePath = join3(current, entry.name);
564
+ const absolutePath = join5(current, entry.name);
387
565
  const relativePath = toPosixPath(relative(root, absolutePath));
388
566
  if (relativePath.length === 0) {
389
567
  continue;
@@ -471,7 +649,7 @@ function getEntryPointReason(relativePath) {
471
649
  async function buildCodeSamples(target, entryPoints, frameworkKind, topology, packageDependencies) {
472
650
  const samples = [];
473
651
  for (const entryPoint of entryPoints.slice(0, SAMPLE_LIMIT)) {
474
- const absolutePath = join3(target, ...entryPoint.path.split("/"));
652
+ const absolutePath = join5(target, ...entryPoint.path.split("/"));
475
653
  const sample = readFirstLines(absolutePath, SAMPLE_LINE_LIMIT);
476
654
  const patternAnalysis = await inferPatternHint(entryPoint.path, sample.snippet, {
477
655
  frameworkKind,
@@ -508,7 +686,7 @@ function readFirstLines(path, lineLimit) {
508
686
  }
509
687
  }
510
688
  function readPackageDependencies(target) {
511
- const packageJsonPath = join3(target, "package.json");
689
+ const packageJsonPath = join5(target, "package.json");
512
690
  if (!existsSync3(packageJsonPath)) {
513
691
  return /* @__PURE__ */ new Map();
514
692
  }
@@ -526,7 +704,7 @@ function readPackageDependencies(target) {
526
704
  }
527
705
  function readGitChurnWeight(target, relativePath) {
528
706
  try {
529
- const output = execFileSync("git", ["log", "--follow", "--oneline", "-20", "--", relativePath], {
707
+ const output = execFileSync3("git", ["log", "--follow", "--oneline", "-20", "--", relativePath], {
530
708
  cwd: target,
531
709
  encoding: "utf8",
532
710
  stdio: ["ignore", "pipe", "ignore"],
@@ -849,8 +1027,8 @@ function scoreFrameworkConfidence(input) {
849
1027
  return input.configCount > 0 || input.packageCount > 0 ? "MEDIUM" : "LOW";
850
1028
  }
851
1029
  function readReadmeInfo(target) {
852
- const readmePath = join3(target, "README.md");
853
- const hasContributing = existsSync3(join3(target, "CONTRIBUTING.md"));
1030
+ const readmePath = join5(target, "README.md");
1031
+ const hasContributing = existsSync3(join5(target, "CONTRIBUTING.md"));
854
1032
  if (!existsSync3(readmePath)) {
855
1033
  return {
856
1034
  quality: "missing",
@@ -1336,7 +1514,7 @@ function buildSkillRecommendations(frameworkKind, topology, readme) {
1336
1514
  return recommendations;
1337
1515
  }
1338
1516
  function readProjectName(target) {
1339
- const packageJsonPath = join3(target, "package.json");
1517
+ const packageJsonPath = join5(target, "package.json");
1340
1518
  if (existsSync3(packageJsonPath)) {
1341
1519
  try {
1342
1520
  const packageJson = JSON.parse(readFileSync2(packageJsonPath, "utf8"));
@@ -1350,7 +1528,7 @@ function readProjectName(target) {
1350
1528
  return basename(target);
1351
1529
  }
1352
1530
  function getCliVersion() {
1353
- return true ? "2.0.1" : "unknown";
1531
+ return true ? "2.1.0-rc.2" : "unknown";
1354
1532
  }
1355
1533
  function sortRecord(record) {
1356
1534
  return Object.fromEntries(Object.entries(record).sort(([left], [right]) => left.localeCompare(right)));
@@ -1360,7 +1538,7 @@ function toPosixPath(path) {
1360
1538
  }
1361
1539
 
1362
1540
  // src/commands/install.ts
1363
- var LOCAL_FABRIC_SERVER_PATH = join4("node_modules", "@fenglimg", "fabric-server", "dist", "index.js");
1541
+ var LOCAL_FABRIC_SERVER_PATH = join6("node_modules", "@fenglimg", "fabric-server", "dist", "index.js");
1364
1542
  var FABRIC_SERVER_PACKAGE = "@fenglimg/fabric-server";
1365
1543
  var INIT_WIZARD_GROUP_CANCELLED = /* @__PURE__ */ Symbol("init-wizard-group-cancelled");
1366
1544
  var installCommand = defineCommand({
@@ -1397,6 +1575,15 @@ var installCommand = defineCommand({
1397
1575
  type: "boolean",
1398
1576
  description: t("cli.install.args.force-hooks-only.description"),
1399
1577
  default: false
1578
+ },
1579
+ global: {
1580
+ type: "boolean",
1581
+ description: "Set up global Fabric (~/.fabric: uid + personal store + config)",
1582
+ default: false
1583
+ },
1584
+ url: {
1585
+ type: "string",
1586
+ description: "With --global: clone + mount this shared store remote"
1400
1587
  }
1401
1588
  },
1402
1589
  async run({ args }) {
@@ -1406,7 +1593,7 @@ var installCommand = defineCommand({
1406
1593
  var install_default = installCommand;
1407
1594
  async function runSkillsOnlyRefresh(targetInput) {
1408
1595
  const target = normalizeTarget3(targetInput);
1409
- const metaPath = join4(target, ".fabric", "agents.meta.json");
1596
+ const metaPath = join6(target, ".fabric", "agents.meta.json");
1410
1597
  if (!existsSync4(metaPath)) {
1411
1598
  const message = t("cli.install.force-skills-only.uninitialised.message");
1412
1599
  const hint = t("cli.install.force-skills-only.uninitialised.hint");
@@ -1449,7 +1636,7 @@ ${hint}
1449
1636
  }
1450
1637
  async function runHooksOnlyRefresh(targetInput) {
1451
1638
  const target = normalizeTarget3(targetInput);
1452
- const metaPath = join4(target, ".fabric", "agents.meta.json");
1639
+ const metaPath = join6(target, ".fabric", "agents.meta.json");
1453
1640
  if (!existsSync4(metaPath)) {
1454
1641
  const message = t("cli.install.force-hooks-only.uninitialised.message");
1455
1642
  const hint = t("cli.install.force-hooks-only.uninitialised.hint");
@@ -1478,6 +1665,10 @@ ${hint}
1478
1665
  }
1479
1666
  async function runInitCommand(args) {
1480
1667
  const logger = createDebugLogger(args.debug);
1668
+ if (args.global === true) {
1669
+ await runGlobalInstall({ url: args.url });
1670
+ return;
1671
+ }
1481
1672
  const resolution = resolveDevMode(args.target, process.cwd());
1482
1673
  if (args["force-skills-only"] === true) {
1483
1674
  await runSkillsOnlyRefresh(resolution.target);
@@ -1516,7 +1707,7 @@ async function runInitCommand(args) {
1516
1707
  return result;
1517
1708
  }
1518
1709
  function writeDefaultFabricConfig(fabricDir, targetRoot) {
1519
- const target = join4(fabricDir, "fabric-config.json");
1710
+ const target = join6(fabricDir, "fabric-config.json");
1520
1711
  if (existsSync4(target)) return;
1521
1712
  const detectedLanguage = detectExistingLanguage(targetRoot);
1522
1713
  const FABRIC_CONFIG_DEFAULTS = {
@@ -1578,7 +1769,7 @@ function writeDefaultFabricConfig(fabricDir, targetRoot) {
1578
1769
  // is considered stale by fabric-review. Default 14.
1579
1770
  review_stale_pending_days: 14
1580
1771
  };
1581
- mkdirSync(fabricDir, { recursive: true });
1772
+ mkdirSync2(fabricDir, { recursive: true });
1582
1773
  writeFileSync(target, JSON.stringify(FABRIC_CONFIG_DEFAULTS, null, 2) + "\n", "utf8");
1583
1774
  log.info(
1584
1775
  `Detected and fixated fabric_language = ${detectedLanguage}; edit ${target} to override.`
@@ -1709,14 +1900,14 @@ function resolvePersonalFabricRoot() {
1709
1900
  }
1710
1901
  async function buildInitFabricPlan(target, options) {
1711
1902
  assertExistingDirectory3(target);
1712
- const fabricDir = join4(target, ".fabric");
1713
- const agentsMdPath = join4(target, "AGENTS.md");
1903
+ const fabricDir = join6(target, ".fabric");
1904
+ const agentsMdPath = join6(target, "AGENTS.md");
1714
1905
  const agentsMdAction = existsSync4(agentsMdPath) ? "preserved" : "created";
1715
- const knowledgeDir = join4(fabricDir, "knowledge");
1716
- const personalKnowledgeDir = join4(resolvePersonalFabricRoot(), ".fabric", "knowledge");
1717
- const forensicPath = join4(fabricDir, "forensic.json");
1718
- const eventsPath = join4(fabricDir, "events.jsonl");
1719
- const metaPath = join4(fabricDir, "agents.meta.json");
1906
+ const knowledgeDir = join6(fabricDir, "knowledge");
1907
+ const personalKnowledgeDir = join6(resolvePersonalFabricRoot(), ".fabric", "knowledge");
1908
+ const forensicPath = join6(fabricDir, "forensic.json");
1909
+ const eventsPath = join6(fabricDir, "events.jsonl");
1910
+ const metaPath = join6(fabricDir, "agents.meta.json");
1720
1911
  const replaceFabricDir = shouldReplaceWritableDirectory(fabricDir, options);
1721
1912
  const knowledgeDirAction = existsSync4(knowledgeDir) ? "overwritten" : "created";
1722
1913
  const metaClassification = classifyFreshPath(metaPath, "structural");
@@ -1752,23 +1943,23 @@ async function buildInitFabricPlan(target, options) {
1752
1943
  }
1753
1944
  async function executeInitFabricPlan(plan) {
1754
1945
  if (plan.replaceFabricDir) {
1755
- rmSync(plan.fabricDir, { force: true });
1946
+ rmSync2(plan.fabricDir, { force: true });
1756
1947
  }
1757
- mkdirSync(plan.fabricDir, { recursive: true });
1948
+ mkdirSync2(plan.fabricDir, { recursive: true });
1758
1949
  writeDefaultFabricConfig(plan.fabricDir, plan.target);
1759
- mkdirSync(plan.knowledgeDir, { recursive: true });
1950
+ mkdirSync2(plan.knowledgeDir, { recursive: true });
1760
1951
  for (const sub of KNOWLEDGE_SUBDIRS) {
1761
- const teamSubDir = join4(plan.knowledgeDir, sub);
1762
- mkdirSync(teamSubDir, { recursive: true });
1763
- const teamGitkeep = join4(teamSubDir, ".gitkeep");
1952
+ const teamSubDir = join6(plan.knowledgeDir, sub);
1953
+ mkdirSync2(teamSubDir, { recursive: true });
1954
+ const teamGitkeep = join6(teamSubDir, ".gitkeep");
1764
1955
  if (!existsSync4(teamGitkeep)) {
1765
1956
  writeFileSync(teamGitkeep, "", "utf8");
1766
1957
  }
1767
1958
  }
1768
1959
  try {
1769
- mkdirSync(plan.personalKnowledgeDir, { recursive: true });
1960
+ mkdirSync2(plan.personalKnowledgeDir, { recursive: true });
1770
1961
  for (const sub of KNOWLEDGE_SUBDIRS) {
1771
- mkdirSync(join4(plan.personalKnowledgeDir, sub), { recursive: true });
1962
+ mkdirSync2(join6(plan.personalKnowledgeDir, sub), { recursive: true });
1772
1963
  }
1773
1964
  } catch {
1774
1965
  }
@@ -1778,7 +1969,7 @@ async function executeInitFabricPlan(plan) {
1778
1969
  }
1779
1970
  if (plan.eventsState === "missing") {
1780
1971
  preparePlannedPath(plan.eventsPath, plan.eventsAction);
1781
- mkdirSync(dirname(plan.eventsPath), { recursive: true });
1972
+ mkdirSync2(dirname(plan.eventsPath), { recursive: true });
1782
1973
  writeFileSync(plan.eventsPath, "", "utf8");
1783
1974
  }
1784
1975
  preparePlannedPath(plan.forensicPath, plan.forensicAction);
@@ -2050,9 +2241,9 @@ function formatDiffFileState(state) {
2050
2241
  return t(`cli.install.diff.state.${state}`);
2051
2242
  }
2052
2243
  function preparePlannedPath(path, action) {
2053
- mkdirSync(dirname(path), { recursive: true });
2244
+ mkdirSync2(dirname(path), { recursive: true });
2054
2245
  if (action === "overwritten" && existsSync4(path)) {
2055
- rmSync(path, { recursive: true, force: true });
2246
+ rmSync2(path, { recursive: true, force: true });
2056
2247
  }
2057
2248
  }
2058
2249
  function createDefaultInitWizardAdapter() {
@@ -2211,13 +2402,13 @@ function assertExistingDirectory3(target) {
2211
2402
  }
2212
2403
  function detectPackageManager(cwd) {
2213
2404
  const workspaceRoot = resolve3(cwd);
2214
- if (existsSync4(join4(workspaceRoot, "pnpm-lock.yaml"))) {
2405
+ if (existsSync4(join6(workspaceRoot, "pnpm-lock.yaml"))) {
2215
2406
  return "pnpm";
2216
2407
  }
2217
- if (existsSync4(join4(workspaceRoot, "yarn.lock"))) {
2408
+ if (existsSync4(join6(workspaceRoot, "yarn.lock"))) {
2218
2409
  return "yarn";
2219
2410
  }
2220
- if (existsSync4(join4(workspaceRoot, "package-lock.json"))) {
2411
+ if (existsSync4(join6(workspaceRoot, "package-lock.json"))) {
2221
2412
  return "npm";
2222
2413
  }
2223
2414
  return "npm";
@@ -2240,7 +2431,7 @@ function createInitialMeta() {
2240
2431
  function appendInstallDiffLedgerEvent(eventsPath, payload) {
2241
2432
  const event = {
2242
2433
  kind: "fabric-event",
2243
- id: `event:${randomUUID()}`,
2434
+ id: `event:${randomUUID2()}`,
2244
2435
  ts: Date.now(),
2245
2436
  schema_version: 1,
2246
2437
  event_type: "install_diff_applied",
@@ -0,0 +1,33 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ scopeExplain
4
+ } from "./chunk-L4Q55UC4.js";
5
+ import "./chunk-LFIKMVY7.js";
6
+ import "./chunk-RYAFBNES.js";
7
+
8
+ // src/commands/scope-explain.ts
9
+ import { defineCommand } from "citty";
10
+ var scope_explain_default = defineCommand({
11
+ meta: {
12
+ name: "scope-explain",
13
+ description: "Explain the resolved read-set and write target for a scope"
14
+ },
15
+ args: {
16
+ scope: {
17
+ type: "positional",
18
+ required: true,
19
+ description: "Scope coordinate (e.g. team, project:x, personal)"
20
+ }
21
+ },
22
+ run({ args }) {
23
+ const result = scopeExplain(process.cwd(), args.scope);
24
+ if (result === null) {
25
+ console.log("no global Fabric config \u2014 run `fabric install --global <url>` first");
26
+ return;
27
+ }
28
+ console.log(JSON.stringify(result, null, 2));
29
+ }
30
+ });
31
+ export {
32
+ scope_explain_default as default
33
+ };
@@ -0,0 +1,23 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ projectStatus
4
+ } from "./chunk-T5RPGCCM.js";
5
+ import "./chunk-LFIKMVY7.js";
6
+ import "./chunk-RYAFBNES.js";
7
+
8
+ // src/commands/status.ts
9
+ import { defineCommand } from "citty";
10
+ var status_default = defineCommand({
11
+ meta: { name: "status", description: "Show this project's Fabric store status" },
12
+ run() {
13
+ const status = projectStatus(process.cwd());
14
+ console.log(`uid: ${status.uid ?? "(no global config)"}`);
15
+ console.log(`project_id: ${status.project_id ?? "(not a Fabric project)"}`);
16
+ console.log(`mounted stores: ${status.mounted.length > 0 ? status.mounted.join(", ") : "(none)"}`);
17
+ console.log(`required: ${status.required.length > 0 ? status.required.join(", ") : "(none)"}`);
18
+ console.log(`active write: ${status.active_write_store ?? "(none \u2014 personal scope only)"}`);
19
+ }
20
+ });
21
+ export {
22
+ status_default as default
23
+ };
@@ -0,0 +1,105 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ storeAdd,
4
+ storeBind,
5
+ storeExplain,
6
+ storeList,
7
+ storeRemove,
8
+ storeSwitchWrite
9
+ } from "./chunk-HFQVXY6P.js";
10
+ import {
11
+ regenerateBindingsSnapshot
12
+ } from "./chunk-WU6GAPKH.js";
13
+ import "./chunk-L4Q55UC4.js";
14
+ import "./chunk-LFIKMVY7.js";
15
+ import "./chunk-RYAFBNES.js";
16
+
17
+ // src/commands/store.ts
18
+ import { defineCommand } from "citty";
19
+ var listCommand = defineCommand({
20
+ meta: { name: "list", description: "List mounted knowledge stores" },
21
+ run() {
22
+ const stores = storeList();
23
+ if (stores.length === 0) {
24
+ console.log("(no stores mounted)");
25
+ return;
26
+ }
27
+ for (const store of stores) {
28
+ console.log(`${store.alias} ${store.store_uuid} ${store.remote ?? "(local-only)"}`);
29
+ }
30
+ }
31
+ });
32
+ var addCommand = defineCommand({
33
+ meta: { name: "add", description: "Mount a knowledge store into the global registry" },
34
+ args: {
35
+ uuid: { type: "string", required: true, description: "Intrinsic store UUID" },
36
+ alias: { type: "string", required: true, description: "Local alias for this store" },
37
+ remote: { type: "string", description: "Git remote locator (omit for local-only)" }
38
+ },
39
+ run({ args }) {
40
+ const store = args.remote === void 0 ? { store_uuid: args.uuid, alias: args.alias } : { store_uuid: args.uuid, alias: args.alias, remote: args.remote };
41
+ const next = storeAdd(store);
42
+ console.log(`mounted '${args.alias}' (${next.stores.length} store(s) total)`);
43
+ }
44
+ });
45
+ var removeCommand = defineCommand({
46
+ meta: { name: "remove", description: "Detach a store from the registry (does NOT delete it)" },
47
+ args: {
48
+ alias: { type: "positional", required: true, description: "Alias to detach" }
49
+ },
50
+ run({ args }) {
51
+ const { detached } = storeRemove(args.alias);
52
+ console.log(
53
+ detached === null ? `no store aliased '${args.alias}'` : `detached '${args.alias}' \u2014 on-disk store tree left intact (detach \u2260 delete)`
54
+ );
55
+ }
56
+ });
57
+ var explainCommand = defineCommand({
58
+ meta: { name: "explain", description: "Explain how a store alias resolves" },
59
+ args: {
60
+ alias: { type: "positional", required: true, description: "Alias to explain" }
61
+ },
62
+ run({ args }) {
63
+ const explanation = storeExplain(args.alias);
64
+ console.log(
65
+ explanation === null ? `no store aliased '${args.alias}'` : JSON.stringify(explanation, null, 2)
66
+ );
67
+ }
68
+ });
69
+ var bindCommand = defineCommand({
70
+ meta: { name: "bind", description: "Declare a required store on this project's config" },
71
+ args: {
72
+ id: { type: "positional", required: true, description: "Store alias/UUID to require" },
73
+ remote: { type: "string", description: "Suggested remote for clone onboarding" }
74
+ },
75
+ run({ args }) {
76
+ const entry = args.remote === void 0 ? { id: args.id } : { id: args.id, suggested_remote: args.remote };
77
+ const next = storeBind(process.cwd(), entry);
78
+ console.log(`bound required store '${args.id}' (${next.required_stores?.length ?? 0} required)`);
79
+ regenerateBindingsSnapshot(process.cwd(), { now: (/* @__PURE__ */ new Date()).toISOString() });
80
+ }
81
+ });
82
+ var switchWriteCommand = defineCommand({
83
+ meta: { name: "switch-write", description: "Set the active write store for non-personal scopes" },
84
+ args: {
85
+ alias: { type: "positional", required: true, description: "Alias of the store to write to" }
86
+ },
87
+ run({ args }) {
88
+ storeSwitchWrite(process.cwd(), args.alias);
89
+ console.log(`active write store set to '${args.alias}' for this project`);
90
+ }
91
+ });
92
+ var store_default = defineCommand({
93
+ meta: { name: "store", description: "Manage mounted Fabric knowledge stores" },
94
+ subCommands: {
95
+ list: listCommand,
96
+ add: addCommand,
97
+ remove: removeCommand,
98
+ explain: explainCommand,
99
+ bind: bindCommand,
100
+ "switch-write": switchWriteCommand
101
+ }
102
+ });
103
+ export {
104
+ store_default as default
105
+ };