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

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.
@@ -5,8 +5,8 @@ import {
5
5
  import {
6
6
  warnUnknownFlags,
7
7
  whoami
8
- } from "./chunk-5LQIHYFC.js";
9
- import "./chunk-5ZUMLCD5.js";
8
+ } from "./chunk-5JG4QJLO.js";
9
+ import "./chunk-5SSNE5GM.js";
10
10
  import "./chunk-XCBVSGCS.js";
11
11
 
12
12
  // src/commands/whoami.ts
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fenglimg/fabric-cli",
3
- "version": "2.2.0-rc.3",
3
+ "version": "2.2.0-rc.4",
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.2.0-rc.3",
49
- "@fenglimg/fabric-shared": "2.2.0-rc.3"
48
+ "@fenglimg/fabric-shared": "2.2.0-rc.4",
49
+ "@fenglimg/fabric-server": "2.2.0-rc.4"
50
50
  },
51
51
  "devDependencies": {
52
52
  "@types/node": "^22.15.0",
@@ -1,443 +0,0 @@
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
- assertStoreMountable,
11
- storeAdd,
12
- storeBind,
13
- storeCreate,
14
- storeExplain,
15
- storeGitRemote,
16
- storeList,
17
- storeRemove,
18
- storeSwitchWrite
19
- } from "./chunk-5ZUMLCD5.js";
20
- import {
21
- loadGlobalConfig
22
- } from "./chunk-XCBVSGCS.js";
23
-
24
- // src/commands/store.ts
25
- import { defineCommand } from "citty";
26
-
27
- // src/store/store-migrate.ts
28
- import { execFileSync } from "child_process";
29
- import { existsSync, mkdirSync, readFileSync, readdirSync, rmSync, writeFileSync } from "fs";
30
- import { basename, join } from "path";
31
- import {
32
- STORE_KNOWLEDGE_TYPE_DIRS,
33
- STORE_LAYOUT,
34
- STORE_PENDING_DIR,
35
- buildStoreResolveInput,
36
- createStoreResolver,
37
- formatKnowledgeId,
38
- parseKnowledgeId,
39
- resolveGlobalRoot,
40
- storeRelativePath
41
- } from "@fenglimg/fabric-shared";
42
- function resolveTargetStore(layer, projectRoot, globalRoot) {
43
- const input = buildStoreResolveInput(projectRoot, globalRoot);
44
- if (input === null) {
45
- return null;
46
- }
47
- const scope = layer === "personal" ? "personal" : "team";
48
- const { target } = createStoreResolver().resolveWriteTarget(input, scope);
49
- if (target === null) {
50
- return null;
51
- }
52
- const alias = loadGlobalConfig(globalRoot)?.stores.find((s) => s.store_uuid === target.store_uuid)?.alias ?? target.store_uuid;
53
- return {
54
- uuid: target.store_uuid,
55
- alias,
56
- dir: join(globalRoot, storeRelativePath(target.store_uuid))
57
- };
58
- }
59
- function listMd(dir) {
60
- if (!existsSync(dir)) {
61
- return [];
62
- }
63
- return readdirSync(dir).filter((name) => name.endsWith(".md")).sort();
64
- }
65
- function readId(content) {
66
- const match = content.match(/^id:\s*(\S+)\s*$/mu);
67
- return match ? match[1] : null;
68
- }
69
- function slugSuffix(fileName, oldId) {
70
- const stem = fileName.replace(/\.md$/u, "");
71
- if (oldId !== null && stem.startsWith(`${oldId}--`)) {
72
- return stem.slice(oldId.length);
73
- }
74
- return "";
75
- }
76
- function buildStoreIdIndex(storeDir) {
77
- const existing = /* @__PURE__ */ new Set();
78
- const maxCounter = /* @__PURE__ */ new Map();
79
- for (const type of STORE_KNOWLEDGE_TYPE_DIRS) {
80
- const dir = join(storeDir, STORE_LAYOUT.knowledgeDir, type);
81
- for (const file of listMd(dir)) {
82
- const content = readFileSync(join(dir, file), "utf8");
83
- const id = readId(content) ?? file.replace(/\.md$/u, "").split("--")[0];
84
- const parsed = parseKnowledgeId(id);
85
- if (parsed === null) {
86
- continue;
87
- }
88
- existing.add(id);
89
- const key = id.slice(0, id.lastIndexOf("-"));
90
- maxCounter.set(key, Math.max(maxCounter.get(key) ?? 0, parsed.counter));
91
- }
92
- }
93
- return { existing, maxCounter };
94
- }
95
- function nextId(index, layer, type) {
96
- const probe = formatKnowledgeId(layer, type, 1);
97
- const key = probe.slice(0, probe.lastIndexOf("-"));
98
- let counter = (index.maxCounter.get(key) ?? 0) + 1;
99
- let id = formatKnowledgeId(layer, type, counter);
100
- while (index.existing.has(id)) {
101
- counter += 1;
102
- id = formatKnowledgeId(layer, type, counter);
103
- }
104
- index.existing.add(id);
105
- index.maxCounter.set(key, counter);
106
- return id;
107
- }
108
- function typeDirToKnowledgeType(typeDir) {
109
- return STORE_KNOWLEDGE_TYPE_DIRS.includes(typeDir) ? typeDir : null;
110
- }
111
- function migrateProjectKnowledge(projectRoot, options = {}) {
112
- const dryRun = options.dryRun ?? false;
113
- const globalRoot = options.globalRoot ?? resolveGlobalRoot();
114
- const runGit = options.git ?? true;
115
- const items = [];
116
- const skips = [];
117
- const remap = {};
118
- const targets = {};
119
- const sourceRoots = {
120
- team: join(projectRoot, ".fabric", "knowledge"),
121
- personal: join(globalRoot, "knowledge")
122
- };
123
- const layerState = {};
124
- for (const layer of ["team", "personal"]) {
125
- if (!existsSync(sourceRoots[layer])) {
126
- continue;
127
- }
128
- const target = resolveTargetStore(layer, projectRoot, globalRoot);
129
- if (target === null) {
130
- continue;
131
- }
132
- targets[layer] = { uuid: target.uuid, dir: target.dir };
133
- layerState[layer] = { target, index: buildStoreIdIndex(target.dir) };
134
- }
135
- for (const layer of ["team", "personal"]) {
136
- const root = sourceRoots[layer];
137
- if (!existsSync(root)) {
138
- continue;
139
- }
140
- const state = layerState[layer];
141
- for (const typeDir of STORE_KNOWLEDGE_TYPE_DIRS) {
142
- const dir = join(root, typeDir);
143
- for (const file of listMd(dir)) {
144
- const source = join(dir, file);
145
- if (state === void 0) {
146
- skips.push({
147
- source,
148
- reason: `no ${layer} write-target store \u2014 run \`fabric install --global\` then \`fabric store bind <alias>\`${layer === "team" ? " + `fabric store switch-write <alias>`" : ""}`
149
- });
150
- continue;
151
- }
152
- const content = readFileSync(source, "utf8");
153
- const oldId = readId(content);
154
- const knowledgeType = typeDirToKnowledgeType(typeDir);
155
- let newId = null;
156
- if (oldId !== null && state.index.existing.has(oldId) && knowledgeType !== null) {
157
- const parsed = parseKnowledgeId(oldId);
158
- const idLayer = parsed?.layer ?? layer;
159
- newId = nextId(state.index, idLayer, knowledgeType);
160
- remap[oldId] = newId;
161
- } else if (oldId !== null) {
162
- state.index.existing.add(oldId);
163
- const parsed = parseKnowledgeId(oldId);
164
- if (parsed !== null) {
165
- const key = oldId.slice(0, oldId.lastIndexOf("-"));
166
- state.index.maxCounter.set(
167
- key,
168
- Math.max(state.index.maxCounter.get(key) ?? 0, parsed.counter)
169
- );
170
- }
171
- }
172
- const effectiveId = newId ?? oldId;
173
- const targetName = newId !== null && effectiveId !== null ? `${effectiveId}${slugSuffix(file, oldId)}.md` : file;
174
- const targetFile = join(state.target.dir, STORE_LAYOUT.knowledgeDir, typeDir, targetName);
175
- items.push({
176
- source,
177
- layer,
178
- type: typeDir,
179
- oldId,
180
- newId,
181
- target: targetFile,
182
- storeUuid: state.target.uuid,
183
- alias: state.target.alias
184
- });
185
- }
186
- }
187
- const pendingRoot = join(root, STORE_PENDING_DIR);
188
- for (const sub of [".", "decisions", "guidelines", "pitfalls", "models", "processes"]) {
189
- const dir = sub === "." ? pendingRoot : join(pendingRoot, sub);
190
- for (const file of listMd(dir)) {
191
- const source = join(dir, file);
192
- if (state === void 0) {
193
- skips.push({ source, reason: `no ${layer} write-target store` });
194
- continue;
195
- }
196
- const rel = sub === "." ? file : join(sub, file);
197
- const targetFile = join(
198
- state.target.dir,
199
- STORE_LAYOUT.knowledgeDir,
200
- STORE_PENDING_DIR,
201
- rel
202
- );
203
- if (existsSync(targetFile)) {
204
- skips.push({ source, reason: `pending already present in store: ${basename(targetFile)}` });
205
- continue;
206
- }
207
- items.push({
208
- source,
209
- layer,
210
- type: STORE_PENDING_DIR,
211
- oldId: null,
212
- newId: null,
213
- target: targetFile,
214
- storeUuid: state.target.uuid,
215
- alias: state.target.alias
216
- });
217
- }
218
- }
219
- }
220
- if (dryRun || items.length === 0) {
221
- return { dryRun, committed: false, items, skips, remap, targets };
222
- }
223
- for (const item of items) {
224
- let content = readFileSync(item.source, "utf8");
225
- if (item.newId !== null && item.oldId !== null) {
226
- content = content.replace(/^id:\s*\S+\s*$/mu, `id: ${item.newId}`);
227
- }
228
- content = rewriteRelated(content, remap);
229
- mkdirSync(join(item.target, ".."), { recursive: true });
230
- writeFileSync(item.target, content, "utf8");
231
- }
232
- for (const item of items) {
233
- rmSync(item.source, { force: true });
234
- }
235
- let committed = false;
236
- if (runGit) {
237
- for (const [layer, info] of Object.entries(targets)) {
238
- const moved = items.filter((i) => i.layer === layer).length;
239
- if (moved === 0) {
240
- continue;
241
- }
242
- committed = gitCommitStore(info.dir, moved) || committed;
243
- }
244
- }
245
- return { dryRun, committed, items, skips, remap, targets };
246
- }
247
- function rewriteRelated(content, remap) {
248
- if (Object.keys(remap).length === 0) {
249
- return content;
250
- }
251
- return content.replace(/^related:\s*\[(.*)\]\s*$/mu, (line, inner) => {
252
- const rewritten = inner.split(",").map((token) => {
253
- const trimmed = token.trim();
254
- return remap[trimmed] ?? trimmed;
255
- }).join(", ");
256
- return `related: [${rewritten}]`;
257
- });
258
- }
259
- function gitCommitStore(storeDir, count) {
260
- if (!existsSync(join(storeDir, ".git"))) {
261
- return false;
262
- }
263
- try {
264
- execFileSync("git", ["add", "-A"], { cwd: storeDir, stdio: ["ignore", "ignore", "pipe"] });
265
- execFileSync(
266
- "git",
267
- ["commit", "-m", `chore(migrate): import ${count} entries from project dual-root`],
268
- { cwd: storeDir, stdio: ["ignore", "ignore", "pipe"] }
269
- );
270
- return true;
271
- } catch {
272
- return false;
273
- }
274
- }
275
-
276
- // src/commands/store.ts
277
- var listCommand = defineCommand({
278
- meta: { name: "list", description: "List mounted knowledge stores" },
279
- run() {
280
- const t = getProjectTranslator();
281
- const stores = storeList();
282
- if (stores.length === 0) {
283
- console.log(t("cli.store.none-mounted"));
284
- return;
285
- }
286
- const localOnly = t("cli.shared.local-only");
287
- for (const store of stores) {
288
- const realRemote = storeGitRemote(store.store_uuid);
289
- console.log(`${store.alias} ${store.store_uuid} ${realRemote ?? localOnly}`);
290
- }
291
- }
292
- });
293
- var addCommand = defineCommand({
294
- meta: { name: "add", description: "Mount a knowledge store into the global registry" },
295
- args: {
296
- uuid: { type: "string", required: true, description: "Intrinsic store UUID" },
297
- alias: { type: "string", required: true, description: "Local alias for this store" },
298
- remote: { type: "string", description: "Git remote locator (omit for local-only)" }
299
- },
300
- run({ args }) {
301
- assertStoreMountable(args.uuid);
302
- const store = args.remote === void 0 ? { store_uuid: args.uuid, alias: args.alias } : { store_uuid: args.uuid, alias: args.alias, remote: args.remote };
303
- const next = storeAdd(store);
304
- const t = getProjectTranslator();
305
- console.log(
306
- t("cli.store.mounted", {
307
- alias: args.alias,
308
- count: String(next.stores.length)
309
- })
310
- );
311
- }
312
- });
313
- var createCommand = defineCommand({
314
- meta: { name: "create", description: "Create a brand-new local knowledge store and mount it" },
315
- args: {
316
- alias: { type: "string", required: true, description: "Local alias for the new store" },
317
- remote: { type: "string", description: "Git remote to associate (push target; optional)" }
318
- },
319
- run({ args }) {
320
- const result = storeCreate(args.alias, (/* @__PURE__ */ new Date()).toISOString(), {
321
- ...args.remote === void 0 ? {} : { remote: args.remote }
322
- });
323
- const t = getProjectTranslator();
324
- console.log(
325
- t("cli.store.created", { alias: args.alias, uuid: result.store_uuid, dir: result.storeDir }) + (args.remote === void 0 ? `
326
- ${t("cli.store.created-local-hint")}` : "")
327
- );
328
- }
329
- });
330
- var removeCommand = defineCommand({
331
- meta: { name: "remove", description: "Detach a store from the registry (does NOT delete it)" },
332
- args: {
333
- alias: { type: "positional", required: true, description: "Alias to detach" }
334
- },
335
- run({ args }) {
336
- const { detached } = storeRemove(args.alias);
337
- const t = getProjectTranslator();
338
- console.log(
339
- detached === null ? t("cli.store.no-alias", { alias: args.alias }) : t("cli.store.detached", { alias: args.alias })
340
- );
341
- }
342
- });
343
- var explainCommand = defineCommand({
344
- meta: { name: "explain", description: "Explain how a store alias resolves" },
345
- args: {
346
- alias: { type: "positional", required: true, description: "Alias to explain" }
347
- },
348
- run({ args }) {
349
- const explanation = storeExplain(args.alias);
350
- console.log(
351
- explanation === null ? getProjectTranslator()("cli.store.no-alias", { alias: args.alias }) : JSON.stringify(explanation, null, 2)
352
- );
353
- }
354
- });
355
- var bindCommand = defineCommand({
356
- meta: { name: "bind", description: "Declare a required store on this project's config" },
357
- args: {
358
- id: { type: "positional", required: true, description: "Store alias/UUID to require" },
359
- remote: { type: "string", description: "Suggested remote for clone onboarding" }
360
- },
361
- run({ args }) {
362
- const entry = args.remote === void 0 ? { id: args.id } : { id: args.id, suggested_remote: args.remote };
363
- const projectRoot = process.cwd();
364
- const next = storeBind(projectRoot, entry);
365
- console.log(
366
- getProjectTranslator(projectRoot)("cli.store.bound", {
367
- id: args.id,
368
- count: String(next.required_stores?.length ?? 0)
369
- })
370
- );
371
- regenerateBindingsSnapshot(projectRoot, { now: (/* @__PURE__ */ new Date()).toISOString() });
372
- }
373
- });
374
- var switchWriteCommand = defineCommand({
375
- meta: { name: "switch-write", description: "Set the active write store for non-personal scopes" },
376
- args: {
377
- alias: { type: "positional", required: true, description: "Alias of the store to write to" }
378
- },
379
- run({ args }) {
380
- const projectRoot = process.cwd();
381
- storeSwitchWrite(projectRoot, args.alias);
382
- console.log(getProjectTranslator(projectRoot)("cli.store.switch-write", { alias: args.alias }));
383
- }
384
- });
385
- var migrateCommand = defineCommand({
386
- meta: {
387
- name: "migrate",
388
- description: "Move project-local (dual-root) knowledge into the resolved write-target stores"
389
- },
390
- args: {
391
- "dry-run": {
392
- type: "boolean",
393
- description: "Preview the move without writing anything"
394
- }
395
- },
396
- run({ args }) {
397
- const projectRoot = process.cwd();
398
- const t = getProjectTranslator(projectRoot);
399
- const dryRun = args["dry-run"] === true;
400
- const report = migrateProjectKnowledge(projectRoot, { dryRun });
401
- if (report.items.length === 0 && report.skips.length === 0) {
402
- console.log(t("cli.store.migrate.none"));
403
- return;
404
- }
405
- console.log(
406
- dryRun ? t("cli.store.migrate.dry-run-header") : t("cli.store.migrate.applied-header", { count: String(report.items.length) })
407
- );
408
- for (const item of report.items) {
409
- const id = item.newId ?? item.oldId ?? "(draft)";
410
- console.log(` ${item.layer}/${item.type} ${id} \u2192 ${item.alias}`);
411
- if (item.newId !== null && item.oldId !== null) {
412
- console.log(
413
- t("cli.store.migrate.remap-note", { oldId: item.oldId, newId: item.newId })
414
- );
415
- }
416
- }
417
- if (report.skips.length > 0) {
418
- console.log(t("cli.store.migrate.skips-header", { count: String(report.skips.length) }));
419
- for (const skip of report.skips) {
420
- console.log(` ${skip.source}: ${skip.reason}`);
421
- }
422
- }
423
- if (report.committed) {
424
- console.log(t("cli.store.migrate.committed"));
425
- }
426
- }
427
- });
428
- var store_default = defineCommand({
429
- meta: { name: "store", description: "Manage mounted Fabric knowledge stores" },
430
- subCommands: {
431
- list: listCommand,
432
- create: createCommand,
433
- add: addCommand,
434
- remove: removeCommand,
435
- explain: explainCommand,
436
- bind: bindCommand,
437
- "switch-write": switchWriteCommand,
438
- migrate: migrateCommand
439
- }
440
- });
441
- export {
442
- store_default as default
443
- };