@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.
@@ -54,6 +54,7 @@ import {
54
54
  var SKILL_TEMPLATE_REL = "skills/fabric-archive/SKILL.md";
55
55
  var SKILL_REVIEW_TEMPLATE_REL = "skills/fabric-review/SKILL.md";
56
56
  var SKILL_IMPORT_TEMPLATE_REL = "skills/fabric-import/SKILL.md";
57
+ var SKILL_SYNC_TEMPLATE_REL = "skills/fabric-sync/SKILL.md";
57
58
  var HOOK_SCRIPT_TEMPLATE_REL = "hooks/fabric-hint.cjs";
58
59
  var HOOK_BROAD_SCRIPT_TEMPLATE_REL = "hooks/knowledge-hint-broad.cjs";
59
60
  var HOOK_NARROW_SCRIPT_TEMPLATE_REL = "hooks/knowledge-hint-narrow.cjs";
@@ -74,6 +75,12 @@ var SKILL_DESTINATIONS = {
74
75
  fabricImport: [
75
76
  ".claude/skills/fabric-import/SKILL.md",
76
77
  ".codex/skills/fabric-import/SKILL.md"
78
+ ],
79
+ // v2.1.0-rc.1 P4 (S46): fabric-sync mirrors the sibling skills' 2-client
80
+ // coverage (Claude Code + Codex CLI surface a Skills directory).
81
+ fabricSync: [
82
+ ".claude/skills/fabric-sync/SKILL.md",
83
+ ".codex/skills/fabric-sync/SKILL.md"
77
84
  ]
78
85
  };
79
86
  var DEPRECATED_SKILL_DIRS = [
@@ -235,6 +242,21 @@ async function installFabricImportSkill(projectRoot, _options = {}) {
235
242
  results.push(...await installSkillRefFiles(projectRoot, "fabric-import"));
236
243
  return results;
237
244
  }
245
+ async function installFabricSyncSkill(projectRoot, _options = {}) {
246
+ const source = await readTemplate(SKILL_SYNC_TEMPLATE_REL);
247
+ validateSkillCanonicalSize(source, "fabric-sync");
248
+ const targets = SKILL_DESTINATIONS.fabricSync.map((rel) => join2(projectRoot, rel));
249
+ const results = [];
250
+ for (const target of targets) {
251
+ const staleMsg = inspectStaleInstall(target, source);
252
+ const result = await copyTextIdempotent("skill-sync", source, target);
253
+ if (staleMsg && result.status === "written") {
254
+ result.message = result.message ? `${staleMsg}; ${result.message}` : staleMsg;
255
+ }
256
+ results.push(result);
257
+ }
258
+ return results;
259
+ }
238
260
  async function cleanupDeprecatedSkills(projectRoot) {
239
261
  const results = [];
240
262
  for (const rel of DEPRECATED_SKILL_DIRS) {
@@ -864,6 +886,7 @@ export {
864
886
  installFabricArchiveSkill,
865
887
  installFabricReviewSkill,
866
888
  installFabricImportSkill,
889
+ installFabricSyncSkill,
867
890
  cleanupDeprecatedSkills,
868
891
  installSharedSkillLib,
869
892
  installArchiveHintHook,
@@ -0,0 +1,86 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ loadProjectConfig,
4
+ saveProjectConfig
5
+ } from "./chunk-LFIKMVY7.js";
6
+ import {
7
+ loadGlobalConfig,
8
+ resolveGlobalRoot,
9
+ saveGlobalConfig
10
+ } from "./chunk-RYAFBNES.js";
11
+
12
+ // src/store/store-ops.ts
13
+ import {
14
+ addMountedStore,
15
+ bindRequiredStore,
16
+ detachMountedStore,
17
+ explainStore
18
+ } from "@fenglimg/fabric-shared";
19
+ var NO_GLOBAL_CONFIG = "no global Fabric config found \u2014 run `fabric install --global <url>` first";
20
+ function requireConfig(globalRoot) {
21
+ const config = loadGlobalConfig(globalRoot);
22
+ if (config === null) {
23
+ throw new Error(NO_GLOBAL_CONFIG);
24
+ }
25
+ return config;
26
+ }
27
+ function storeList(globalRoot = resolveGlobalRoot()) {
28
+ return requireConfig(globalRoot).stores;
29
+ }
30
+ function storeAdd(store, globalRoot = resolveGlobalRoot()) {
31
+ const next = addMountedStore(requireConfig(globalRoot), store);
32
+ saveGlobalConfig(next, globalRoot);
33
+ return next;
34
+ }
35
+ function storeRemove(alias, globalRoot = resolveGlobalRoot()) {
36
+ const result = detachMountedStore(requireConfig(globalRoot), alias);
37
+ saveGlobalConfig(result.config, globalRoot);
38
+ return result;
39
+ }
40
+ function storeExplain(alias, globalRoot = resolveGlobalRoot()) {
41
+ return explainStore(requireConfig(globalRoot), alias);
42
+ }
43
+ var NO_PROJECT_CONFIG = "no project Fabric config \u2014 run `fabric install` in this repo first";
44
+ function requireProjectConfig(projectRoot) {
45
+ const config = loadProjectConfig(projectRoot);
46
+ if (config === null) {
47
+ throw new Error(NO_PROJECT_CONFIG);
48
+ }
49
+ return config;
50
+ }
51
+ function storeBind(projectRoot, entry) {
52
+ const config = requireProjectConfig(projectRoot);
53
+ const next = {
54
+ ...config,
55
+ required_stores: bindRequiredStore(config.required_stores ?? [], entry)
56
+ };
57
+ saveProjectConfig(next, projectRoot);
58
+ return next;
59
+ }
60
+ function storeSwitchWrite(projectRoot, alias) {
61
+ const config = requireProjectConfig(projectRoot);
62
+ const next = { ...config, active_write_store: alias };
63
+ saveProjectConfig(next, projectRoot);
64
+ return next;
65
+ }
66
+ function missingRequiredStores(projectRoot, globalRoot = resolveGlobalRoot()) {
67
+ const project = loadProjectConfig(projectRoot);
68
+ if (project === null || project.required_stores === void 0) {
69
+ return [];
70
+ }
71
+ const global = loadGlobalConfig(globalRoot);
72
+ const mounted = new Set(
73
+ (global?.stores ?? []).flatMap((s) => [s.alias, s.store_uuid])
74
+ );
75
+ return project.required_stores.filter((r) => !mounted.has(r.id));
76
+ }
77
+
78
+ export {
79
+ storeList,
80
+ storeAdd,
81
+ storeRemove,
82
+ storeExplain,
83
+ storeBind,
84
+ storeSwitchWrite,
85
+ missingRequiredStores
86
+ };
@@ -0,0 +1,52 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ loadProjectConfig
4
+ } from "./chunk-LFIKMVY7.js";
5
+ import {
6
+ loadGlobalConfig,
7
+ resolveGlobalRoot
8
+ } from "./chunk-RYAFBNES.js";
9
+
10
+ // src/store/scope-explain.ts
11
+ import {
12
+ createStoreResolver
13
+ } from "@fenglimg/fabric-shared";
14
+ function buildResolveInput(projectRoot, globalRoot = resolveGlobalRoot()) {
15
+ const global = loadGlobalConfig(globalRoot);
16
+ if (global === null) {
17
+ return null;
18
+ }
19
+ const project = loadProjectConfig(projectRoot);
20
+ return {
21
+ uid: global.uid,
22
+ mountedStores: global.stores.map((s) => ({
23
+ store_uuid: s.store_uuid,
24
+ alias: s.alias,
25
+ ...s.remote === void 0 ? {} : { remote: s.remote },
26
+ writable: s.writable ?? true,
27
+ personal: s.personal ?? false
28
+ })),
29
+ requiredStores: (project?.required_stores ?? []).map((r) => ({
30
+ id: r.id,
31
+ ...r.suggested_remote === void 0 ? {} : { suggested_remote: r.suggested_remote }
32
+ })),
33
+ ...project?.active_write_store === void 0 ? {} : { activeWriteAlias: project.active_write_store }
34
+ };
35
+ }
36
+ function scopeExplain(projectRoot, scope, globalRoot = resolveGlobalRoot()) {
37
+ const input = buildResolveInput(projectRoot, globalRoot);
38
+ if (input === null) {
39
+ return null;
40
+ }
41
+ const resolver = createStoreResolver();
42
+ return {
43
+ scope,
44
+ readSet: resolver.resolveReadSet(input),
45
+ writeTarget: resolver.resolveWriteTarget(input, scope).target
46
+ };
47
+ }
48
+
49
+ export {
50
+ buildResolveInput,
51
+ scopeExplain
52
+ };
@@ -0,0 +1,27 @@
1
+ #!/usr/bin/env node
2
+
3
+ // src/store/project-config-io.ts
4
+ import { existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
5
+ import { join } from "path";
6
+ import { fabricConfigSchema } from "@fenglimg/fabric-shared";
7
+ function projectConfigPath(projectRoot) {
8
+ return join(projectRoot, ".fabric", "fabric-config.json");
9
+ }
10
+ function loadProjectConfig(projectRoot) {
11
+ const path = projectConfigPath(projectRoot);
12
+ if (!existsSync(path)) {
13
+ return null;
14
+ }
15
+ return fabricConfigSchema.parse(JSON.parse(readFileSync(path, "utf8")));
16
+ }
17
+ function saveProjectConfig(config, projectRoot) {
18
+ const validated = fabricConfigSchema.parse(config);
19
+ mkdirSync(join(projectRoot, ".fabric"), { recursive: true });
20
+ writeFileSync(projectConfigPath(projectRoot), `${JSON.stringify(validated, null, 2)}
21
+ `, "utf8");
22
+ }
23
+
24
+ export {
25
+ loadProjectConfig,
26
+ saveProjectConfig
27
+ };
@@ -0,0 +1,33 @@
1
+ #!/usr/bin/env node
2
+
3
+ // src/store/global-config-io.ts
4
+ import { existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
5
+ import { homedir } from "os";
6
+ import { join } from "path";
7
+ import { globalConfigSchema } from "@fenglimg/fabric-shared";
8
+ function resolveGlobalRoot() {
9
+ return join(process.env.FABRIC_HOME ?? homedir(), ".fabric");
10
+ }
11
+ function globalConfigPath(globalRoot = resolveGlobalRoot()) {
12
+ return join(globalRoot, "fabric-global.json");
13
+ }
14
+ function loadGlobalConfig(globalRoot = resolveGlobalRoot()) {
15
+ const path = globalConfigPath(globalRoot);
16
+ if (!existsSync(path)) {
17
+ return null;
18
+ }
19
+ return globalConfigSchema.parse(JSON.parse(readFileSync(path, "utf8")));
20
+ }
21
+ function saveGlobalConfig(config, globalRoot = resolveGlobalRoot()) {
22
+ const validated = globalConfigSchema.parse(config);
23
+ mkdirSync(globalRoot, { recursive: true });
24
+ writeFileSync(globalConfigPath(globalRoot), `${JSON.stringify(validated, null, 2)}
25
+ `, "utf8");
26
+ }
27
+
28
+ export {
29
+ resolveGlobalRoot,
30
+ globalConfigPath,
31
+ loadGlobalConfig,
32
+ saveGlobalConfig
33
+ };
@@ -0,0 +1,40 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ loadProjectConfig
4
+ } from "./chunk-LFIKMVY7.js";
5
+ import {
6
+ loadGlobalConfig,
7
+ resolveGlobalRoot
8
+ } from "./chunk-RYAFBNES.js";
9
+
10
+ // src/store/info-ops.ts
11
+ function whoami(globalRoot = resolveGlobalRoot()) {
12
+ const config = loadGlobalConfig(globalRoot);
13
+ if (config === null) {
14
+ return null;
15
+ }
16
+ return {
17
+ uid: config.uid,
18
+ stores: config.stores.map((s) => ({
19
+ alias: s.alias,
20
+ store_uuid: s.store_uuid,
21
+ local_only: s.remote === void 0
22
+ }))
23
+ };
24
+ }
25
+ function projectStatus(projectRoot, globalRoot = resolveGlobalRoot()) {
26
+ const global = loadGlobalConfig(globalRoot);
27
+ const project = loadProjectConfig(projectRoot);
28
+ return {
29
+ uid: global?.uid ?? null,
30
+ mounted: (global?.stores ?? []).map((s) => s.alias),
31
+ project_id: project?.project_id ?? null,
32
+ required: (project?.required_stores ?? []).map((r) => r.id),
33
+ active_write_store: project?.active_write_store ?? null
34
+ };
35
+ }
36
+
37
+ export {
38
+ whoami,
39
+ projectStatus
40
+ };
@@ -0,0 +1,36 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ buildResolveInput
4
+ } from "./chunk-L4Q55UC4.js";
5
+ import {
6
+ loadProjectConfig
7
+ } from "./chunk-LFIKMVY7.js";
8
+ import {
9
+ resolveGlobalRoot
10
+ } from "./chunk-RYAFBNES.js";
11
+
12
+ // src/store/bindings-io.ts
13
+ import { writeBindingsSnapshot } from "@fenglimg/fabric-shared";
14
+ var DEFAULT_WRITE_SCOPE = "team";
15
+ function regenerateBindingsSnapshot(projectRoot, options) {
16
+ const globalRoot = options.globalRoot ?? resolveGlobalRoot();
17
+ const resolveInput = buildResolveInput(projectRoot, globalRoot);
18
+ if (resolveInput === null) {
19
+ return null;
20
+ }
21
+ const project = loadProjectConfig(projectRoot);
22
+ if (project?.project_id === void 0) {
23
+ return null;
24
+ }
25
+ return writeBindingsSnapshot({
26
+ globalRoot,
27
+ projectId: project.project_id,
28
+ resolveInput,
29
+ writeScope: options.writeScope ?? DEFAULT_WRITE_SCOPE,
30
+ now: options.now
31
+ });
32
+ }
33
+
34
+ export {
35
+ regenerateBindingsSnapshot
36
+ };
@@ -10,6 +10,16 @@ import {
10
10
  getDoctorTranslator,
11
11
  t
12
12
  } from "./chunk-PWLW3B57.js";
13
+ import {
14
+ missingRequiredStores
15
+ } from "./chunk-HFQVXY6P.js";
16
+ import {
17
+ loadProjectConfig
18
+ } from "./chunk-LFIKMVY7.js";
19
+ import {
20
+ loadGlobalConfig,
21
+ resolveGlobalRoot
22
+ } from "./chunk-RYAFBNES.js";
13
23
 
14
24
  // src/commands/doctor.ts
15
25
  import { confirm, isCancel } from "@clack/prompts";
@@ -24,6 +34,53 @@ import {
24
34
  runDoctorHistoryAll,
25
35
  runDoctorReport
26
36
  } from "@fenglimg/fabric-server";
37
+
38
+ // src/store/doctor-checks.ts
39
+ import { join } from "path";
40
+ import { findStoreExecutableViolations, storeRelativePath } from "@fenglimg/fabric-shared";
41
+ function storeDoctorChecks(projectRoot, globalRoot = resolveGlobalRoot()) {
42
+ const diagnostics = [];
43
+ const global = loadGlobalConfig(globalRoot);
44
+ if (global === null) {
45
+ diagnostics.push({
46
+ code: "no_global_config",
47
+ severity: "warn",
48
+ message: "no global Fabric config \u2014 run `fabric install --global <url>`"
49
+ });
50
+ return diagnostics;
51
+ }
52
+ for (const missing of missingRequiredStores(projectRoot, globalRoot)) {
53
+ diagnostics.push({
54
+ code: "missing_required_store",
55
+ severity: "warn",
56
+ ref: missing.id,
57
+ message: `required store '${missing.id}' is not mounted; run \`fabric store add\``
58
+ });
59
+ }
60
+ for (const store of global.stores) {
61
+ if (store.remote === void 0 && store.personal !== true) {
62
+ diagnostics.push({
63
+ code: "local_only_store",
64
+ severity: "info",
65
+ ref: store.alias,
66
+ message: `store '${store.alias}' is local-only; add a git remote to back it up`
67
+ });
68
+ }
69
+ const violations = findStoreExecutableViolations(join(globalRoot, storeRelativePath(store.store_uuid)));
70
+ if (violations.length > 0) {
71
+ diagnostics.push({
72
+ code: "executable_in_store",
73
+ severity: "warn",
74
+ ref: store.alias,
75
+ message: `store '${store.alias}' contains executable/script files (${violations.slice(0, 3).join(", ")}${violations.length > 3 ? ", \u2026" : ""}) \u2014 stores are data-only; Fabric never runs them (S65)`
76
+ });
77
+ }
78
+ }
79
+ return diagnostics;
80
+ }
81
+
82
+ // src/commands/doctor.ts
83
+ import { buildDebugBundle } from "@fenglimg/fabric-shared";
27
84
  var FIX_KNOWLEDGE_CODE_LABELS = {
28
85
  knowledge_orphan_demote_required: "demote (maturity)",
29
86
  knowledge_stale_archive_required: "archive (git mv)",
@@ -62,6 +119,14 @@ var doctorCommand = defineCommand({
62
119
  description: t("cli.doctor.args.strict.description"),
63
120
  default: false
64
121
  },
122
+ // v2.1.0-rc.1 P6 (S40): emit a redacted diagnostic bundle (config + store
123
+ // diagnostics; events excluded by default). Every string is secret-redacted
124
+ // so the bundle is safe to paste into a bug report. Read-only.
125
+ "debug-bundle": {
126
+ type: "boolean",
127
+ description: "Emit a redacted diagnostic bundle (config + store health) for bug reports",
128
+ default: false
129
+ },
65
130
  // rc.7 T11: skip the safety confirm before mutations. Required for any
66
131
  // non-tty invocation that wants to run --fix-knowledge without setting
67
132
  // FABRIC_NONINTERACTIVE=1 in the environment.
@@ -154,6 +219,24 @@ var doctorCommand = defineCommand({
154
219
  const citeCoverage = args["cite-coverage"] === true;
155
220
  const enrichDesc = args["enrich-descriptions"] === true;
156
221
  const archiveHistory = args["archive-history"] === true;
222
+ if (args["debug-bundle"] === true) {
223
+ const globalRoot = resolveGlobalRoot();
224
+ let config = {};
225
+ try {
226
+ config = {
227
+ global: loadGlobalConfig(globalRoot) ?? null,
228
+ project: loadProjectConfig(resolution.target) ?? null
229
+ };
230
+ } catch {
231
+ config = {};
232
+ }
233
+ const bundle = buildDebugBundle({
234
+ config,
235
+ diagnostics: collectStoreDiagnostics(resolution.target)
236
+ });
237
+ writeStdout(JSON.stringify(bundle, null, 2));
238
+ return;
239
+ }
157
240
  if (args.since !== void 0) {
158
241
  try {
159
242
  parseSinceDuration(args.since);
@@ -317,8 +400,15 @@ var doctorCommand = defineCommand({
317
400
  } else {
318
401
  report = await runDoctorReport(resolution.target);
319
402
  }
403
+ const storeDiagnostics = collectStoreDiagnostics(resolution.target);
320
404
  if (args.json === true) {
321
- writeStdout(JSON.stringify(fixKnowledgeReport ?? fixReport ?? report, null, 2));
405
+ writeStdout(
406
+ JSON.stringify(
407
+ { ...fixKnowledgeReport ?? fixReport ?? report, store_diagnostics: storeDiagnostics },
408
+ null,
409
+ 2
410
+ )
411
+ );
322
412
  } else {
323
413
  if (fixKnowledgeReport !== null) {
324
414
  writeStdout(fixKnowledgeReport.message);
@@ -332,6 +422,7 @@ var doctorCommand = defineCommand({
332
422
  writeStdout(dt("cli.doctor.fix-dry-run-banner"));
333
423
  }
334
424
  renderHumanReport(report, dt, args.verbose === true);
425
+ renderStoreDiagnostics(storeDiagnostics);
335
426
  }
336
427
  await emitDoctorRunEventBestEffort(resolution.target, {
337
428
  mode: fixKnowledge ? "fix-knowledge" : "lint",
@@ -366,6 +457,25 @@ function renderHumanReport(report, dt, verbose) {
366
457
  writeIssueSection(dt("doctor.section.warnings"), report.warnings, opts);
367
458
  renderPayloadLimits(report, dt);
368
459
  }
460
+ function collectStoreDiagnostics(projectRoot) {
461
+ try {
462
+ return storeDoctorChecks(projectRoot);
463
+ } catch {
464
+ return [];
465
+ }
466
+ }
467
+ function renderStoreDiagnostics(diagnostics) {
468
+ if (diagnostics.length === 0) {
469
+ return;
470
+ }
471
+ writeStdout("");
472
+ writeStdout(paint.ai("store health"));
473
+ for (const diagnostic of diagnostics) {
474
+ const mark = diagnostic.severity === "warn" ? symbol.warn : "[info]";
475
+ const ref = diagnostic.ref === void 0 ? "" : ` [${diagnostic.ref}]`;
476
+ writeStdout(`${mark}${ref} ${diagnostic.message}`);
477
+ }
478
+ }
369
479
  function renderPayloadLimits(report, dt) {
370
480
  const limits = report.summary.payload_limits;
371
481
  if (limits === void 0) {
package/dist/index.js CHANGED
@@ -11,9 +11,17 @@ import { defineCommand, runMain } from "citty";
11
11
 
12
12
  // src/commands/index.ts
13
13
  var allCommands = {
14
- install: () => import("./install-EKWMFLUU.js").then((module) => module.default),
15
- doctor: () => import("./doctor-EJDSEJSS.js").then((module) => module.default),
16
- uninstall: () => import("./uninstall-MH7ZIB6M.js").then((module) => module.default),
14
+ install: () => import("./install-2HDO5FTQ.js").then((module) => module.default),
15
+ // v2.1.0-rc.1 P3: multi-store lifecycle command group (list/add/remove/explain).
16
+ store: () => import("./store-XTSE5TY6.js").then((module) => module.default),
17
+ // v2.1.0-rc.1 P3 (S9/S17/S37): multi-store pull --rebase + push, conflict resume.
18
+ sync: () => import("./sync-BJCWDPNC.js").then((module) => module.default),
19
+ // v2.1.0-rc.1 P3 (F5): read-only identity/status info commands.
20
+ whoami: () => import("./whoami-B6AEMSEV.js").then((module) => module.default),
21
+ status: () => import("./status-GLQWLWH6.js").then((module) => module.default),
22
+ "scope-explain": () => import("./scope-explain-2F2R5URO.js").then((module) => module.default),
23
+ doctor: () => import("./doctor-QVNPHLJK.js").then((module) => module.default),
24
+ uninstall: () => import("./uninstall-TAXSUSKH.js").then((module) => module.default),
17
25
  config: () => import("./config-XJIPZNUP.js").then((module) => module.default),
18
26
  "plan-context-hint": () => import("./plan-context-hint-FC6P3WFE.js").then((module) => module.default),
19
27
  // v2.0.0-rc.23 TASK-014 (F8c): S5 onboard-slot coverage. Used by the
@@ -27,7 +35,7 @@ var allCommands = {
27
35
  var main = defineCommand({
28
36
  meta: {
29
37
  name: "fabric",
30
- version: "2.0.1",
38
+ version: "2.1.0-rc.2",
31
39
  description: t("cli.main.description")
32
40
  },
33
41
  subCommands: allCommands