@fenglimg/fabric-cli 2.0.1 → 2.2.0-rc.1
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.
- package/dist/{chunk-PWLW3B57.js → chunk-2CY4BMTH.js} +5 -1
- package/dist/{chunk-D25XJ4BC.js → chunk-2R55HNVD.js} +105 -5
- package/dist/chunk-4R2CYEA4.js +116 -0
- package/dist/{chunk-BATF4PEJ.js → chunk-AOE6AYI7.js} +2 -2
- package/dist/{chunk-WWNXR34K.js → chunk-BO4XIZWZ.js} +8 -1
- package/dist/chunk-L4Q55UC4.js +52 -0
- package/dist/chunk-LFIKMVY7.js +27 -0
- package/dist/chunk-RYAFBNES.js +33 -0
- package/dist/chunk-T5RPGCCM.js +40 -0
- package/dist/chunk-WU6GAPKH.js +36 -0
- package/dist/{chunk-MF3OTILQ.js → chunk-XC5RUHLK.js} +29 -8
- package/dist/{config-XJIPZNUP.js → config-XYRBZJDU.js} +3 -3
- package/dist/{doctor-EJDSEJSS.js → doctor-YONYXDX6.js} +147 -24
- package/dist/index.js +58 -10
- package/dist/{install-EKWMFLUU.js → install-74ANPCCP.js} +320 -75
- package/dist/{metrics-ACEQFPDU.js → metrics-RER6NLFC.js} +22 -9
- package/dist/{onboard-coverage-MFCAEBDO.js → onboard-coverage-JWQWDZW7.js} +1 -1
- package/dist/scope-explain-CDIZESP5.js +37 -0
- package/dist/status-GLQWLWH6.js +23 -0
- package/dist/store-XB3ADT65.js +144 -0
- package/dist/sync-UJ4BBCZJ.js +251 -0
- package/dist/{uninstall-MH7ZIB6M.js → uninstall-C3QXKOO6.js} +47 -7
- package/dist/whoami-2MLO4Y37.js +36 -0
- package/package.json +3 -3
- package/templates/hooks/fabric-hint.cjs +139 -7
- package/templates/hooks/knowledge-hint-broad.cjs +204 -9
- package/templates/hooks/knowledge-hint-narrow.cjs +49 -4
- package/templates/hooks/lib/bindings-snapshot-reader.cjs +81 -0
- package/templates/hooks/lib/cite-contract-reminder.cjs +15 -9
- package/templates/hooks/lib/cite-line-parser.cjs +48 -26
- package/templates/hooks/lib/injection-log.cjs +91 -0
- package/templates/hooks/lib/state-store.cjs +30 -11
- package/templates/skills/fabric-archive/SKILL.md +4 -0
- package/templates/skills/fabric-audit/SKILL.md +53 -0
- package/templates/skills/fabric-connect/SKILL.md +48 -0
- package/templates/skills/fabric-import/SKILL.md +4 -0
- package/templates/skills/fabric-review/SKILL.md +6 -0
- package/templates/skills/fabric-review/ref/cite-contract.md +56 -0
- package/templates/skills/fabric-store/SKILL.md +44 -0
- package/templates/skills/fabric-sync/SKILL.md +46 -0
|
@@ -1,14 +1,17 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
getProjectTranslator
|
|
4
|
+
} from "./chunk-2CY4BMTH.js";
|
|
2
5
|
|
|
3
6
|
// src/commands/metrics.ts
|
|
4
7
|
import { resolve } from "path";
|
|
5
8
|
import { defineCommand } from "citty";
|
|
6
9
|
import { readMetrics } from "@fenglimg/fabric-server";
|
|
7
|
-
function parseSinceArg(raw) {
|
|
10
|
+
function parseSinceArg(raw, t) {
|
|
8
11
|
if (raw === void 0 || raw.length === 0) return 0;
|
|
9
12
|
const match = /^(\d+)([smhd]?)$/u.exec(raw);
|
|
10
13
|
if (match === null) {
|
|
11
|
-
throw new Error(
|
|
14
|
+
throw new Error(t("cli.metrics.invalid-since", { raw }));
|
|
12
15
|
}
|
|
13
16
|
const n = Number.parseInt(match[1], 10);
|
|
14
17
|
const unit = match[2] ?? "s";
|
|
@@ -36,6 +39,8 @@ function aggregate(rows, sinceMs, now) {
|
|
|
36
39
|
}
|
|
37
40
|
}
|
|
38
41
|
return {
|
|
42
|
+
// Stable token (NOT localized) so the --json contract is locale-independent;
|
|
43
|
+
// renderText localizes "all-time" at presentation time only.
|
|
39
44
|
windowDescription: sinceMs > 0 ? formatDuration(sinceMs) : "all-time",
|
|
40
45
|
rowCount: filtered.length,
|
|
41
46
|
totals,
|
|
@@ -50,17 +55,24 @@ function formatDuration(ms) {
|
|
|
50
55
|
if (ms >= 6e4) return `${Math.round(ms / 6e4)}m`;
|
|
51
56
|
return `${Math.round(ms / 1e3)}s`;
|
|
52
57
|
}
|
|
53
|
-
function renderText(agg) {
|
|
58
|
+
function renderText(agg, t) {
|
|
54
59
|
const lines = [];
|
|
55
|
-
|
|
60
|
+
const windowDisplay = agg.windowDescription === "all-time" ? t("cli.metrics.window-all-time") : agg.windowDescription;
|
|
61
|
+
lines.push(t("cli.metrics.window", { window: windowDisplay }));
|
|
56
62
|
if (agg.rangeStart && agg.rangeEnd) {
|
|
57
|
-
lines.push(
|
|
63
|
+
lines.push(
|
|
64
|
+
t("cli.metrics.rows-range", {
|
|
65
|
+
count: String(agg.rowCount),
|
|
66
|
+
start: agg.rangeStart,
|
|
67
|
+
end: agg.rangeEnd
|
|
68
|
+
})
|
|
69
|
+
);
|
|
58
70
|
} else {
|
|
59
|
-
lines.push(
|
|
71
|
+
lines.push(t("cli.metrics.rows", { count: String(agg.rowCount) }));
|
|
60
72
|
}
|
|
61
73
|
lines.push("");
|
|
62
74
|
if (Object.keys(agg.totals).length === 0) {
|
|
63
|
-
lines.push("
|
|
75
|
+
lines.push(t("cli.metrics.no-activity"));
|
|
64
76
|
return lines.join("\n");
|
|
65
77
|
}
|
|
66
78
|
lines.push(" counter total");
|
|
@@ -103,14 +115,15 @@ var metricsCommand = defineCommand({
|
|
|
103
115
|
},
|
|
104
116
|
async run({ args }) {
|
|
105
117
|
const projectRoot = resolve(args.target ?? process.cwd());
|
|
106
|
-
const
|
|
118
|
+
const t = getProjectTranslator(projectRoot);
|
|
119
|
+
const sinceMs = parseSinceArg(args.since, t);
|
|
107
120
|
const rows = await readMetrics(projectRoot);
|
|
108
121
|
const aggregated = aggregate(rows, sinceMs, /* @__PURE__ */ new Date());
|
|
109
122
|
if (args.json === true) {
|
|
110
123
|
process.stdout.write(`${JSON.stringify(aggregated)}
|
|
111
124
|
`);
|
|
112
125
|
} else {
|
|
113
|
-
process.stdout.write(`${renderText(aggregated)}
|
|
126
|
+
process.stdout.write(`${renderText(aggregated, t)}
|
|
114
127
|
`);
|
|
115
128
|
}
|
|
116
129
|
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
scopeExplain
|
|
4
|
+
} from "./chunk-L4Q55UC4.js";
|
|
5
|
+
import {
|
|
6
|
+
getProjectTranslator
|
|
7
|
+
} from "./chunk-2CY4BMTH.js";
|
|
8
|
+
import "./chunk-LFIKMVY7.js";
|
|
9
|
+
import "./chunk-RYAFBNES.js";
|
|
10
|
+
|
|
11
|
+
// src/commands/scope-explain.ts
|
|
12
|
+
import { defineCommand } from "citty";
|
|
13
|
+
var scope_explain_default = defineCommand({
|
|
14
|
+
meta: {
|
|
15
|
+
name: "scope-explain",
|
|
16
|
+
description: "Explain the resolved read-set and write target for a scope"
|
|
17
|
+
},
|
|
18
|
+
args: {
|
|
19
|
+
scope: {
|
|
20
|
+
type: "positional",
|
|
21
|
+
required: true,
|
|
22
|
+
description: "Scope coordinate (e.g. team, project:x, personal)"
|
|
23
|
+
}
|
|
24
|
+
},
|
|
25
|
+
run({ args }) {
|
|
26
|
+
const projectRoot = process.cwd();
|
|
27
|
+
const result = scopeExplain(projectRoot, args.scope);
|
|
28
|
+
if (result === null) {
|
|
29
|
+
console.log(getProjectTranslator(projectRoot)("cli.cmd.no-global-config"));
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
console.log(JSON.stringify(result, null, 2));
|
|
33
|
+
}
|
|
34
|
+
});
|
|
35
|
+
export {
|
|
36
|
+
scope_explain_default as default
|
|
37
|
+
};
|
|
@@ -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,144 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
assertStoreMountable,
|
|
4
|
+
storeAdd,
|
|
5
|
+
storeBind,
|
|
6
|
+
storeCreate,
|
|
7
|
+
storeExplain,
|
|
8
|
+
storeList,
|
|
9
|
+
storeRemove,
|
|
10
|
+
storeSwitchWrite
|
|
11
|
+
} from "./chunk-4R2CYEA4.js";
|
|
12
|
+
import {
|
|
13
|
+
regenerateBindingsSnapshot
|
|
14
|
+
} from "./chunk-WU6GAPKH.js";
|
|
15
|
+
import "./chunk-L4Q55UC4.js";
|
|
16
|
+
import {
|
|
17
|
+
getProjectTranslator
|
|
18
|
+
} from "./chunk-2CY4BMTH.js";
|
|
19
|
+
import "./chunk-LFIKMVY7.js";
|
|
20
|
+
import "./chunk-RYAFBNES.js";
|
|
21
|
+
|
|
22
|
+
// src/commands/store.ts
|
|
23
|
+
import { defineCommand } from "citty";
|
|
24
|
+
var listCommand = defineCommand({
|
|
25
|
+
meta: { name: "list", description: "List mounted knowledge stores" },
|
|
26
|
+
run() {
|
|
27
|
+
const t = getProjectTranslator();
|
|
28
|
+
const stores = storeList();
|
|
29
|
+
if (stores.length === 0) {
|
|
30
|
+
console.log(t("cli.store.none-mounted"));
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
const localOnly = t("cli.shared.local-only");
|
|
34
|
+
for (const store of stores) {
|
|
35
|
+
console.log(`${store.alias} ${store.store_uuid} ${store.remote ?? localOnly}`);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
});
|
|
39
|
+
var addCommand = defineCommand({
|
|
40
|
+
meta: { name: "add", description: "Mount a knowledge store into the global registry" },
|
|
41
|
+
args: {
|
|
42
|
+
uuid: { type: "string", required: true, description: "Intrinsic store UUID" },
|
|
43
|
+
alias: { type: "string", required: true, description: "Local alias for this store" },
|
|
44
|
+
remote: { type: "string", description: "Git remote locator (omit for local-only)" }
|
|
45
|
+
},
|
|
46
|
+
run({ args }) {
|
|
47
|
+
assertStoreMountable(args.uuid);
|
|
48
|
+
const store = args.remote === void 0 ? { store_uuid: args.uuid, alias: args.alias } : { store_uuid: args.uuid, alias: args.alias, remote: args.remote };
|
|
49
|
+
const next = storeAdd(store);
|
|
50
|
+
console.log(
|
|
51
|
+
getProjectTranslator()("cli.store.mounted", {
|
|
52
|
+
alias: args.alias,
|
|
53
|
+
count: String(next.stores.length)
|
|
54
|
+
})
|
|
55
|
+
);
|
|
56
|
+
}
|
|
57
|
+
});
|
|
58
|
+
var createCommand = defineCommand({
|
|
59
|
+
meta: { name: "create", description: "Create a brand-new local knowledge store and mount it" },
|
|
60
|
+
args: {
|
|
61
|
+
alias: { type: "string", required: true, description: "Local alias for the new store" },
|
|
62
|
+
remote: { type: "string", description: "Git remote to associate (push target; optional)" }
|
|
63
|
+
},
|
|
64
|
+
run({ args }) {
|
|
65
|
+
const result = storeCreate(args.alias, (/* @__PURE__ */ new Date()).toISOString(), {
|
|
66
|
+
...args.remote === void 0 ? {} : { remote: args.remote }
|
|
67
|
+
});
|
|
68
|
+
const t = getProjectTranslator();
|
|
69
|
+
console.log(
|
|
70
|
+
t("cli.store.created", { alias: args.alias, uuid: result.store_uuid, dir: result.storeDir }) + (args.remote === void 0 ? `
|
|
71
|
+
${t("cli.store.created-local-hint")}` : "")
|
|
72
|
+
);
|
|
73
|
+
}
|
|
74
|
+
});
|
|
75
|
+
var removeCommand = defineCommand({
|
|
76
|
+
meta: { name: "remove", description: "Detach a store from the registry (does NOT delete it)" },
|
|
77
|
+
args: {
|
|
78
|
+
alias: { type: "positional", required: true, description: "Alias to detach" }
|
|
79
|
+
},
|
|
80
|
+
run({ args }) {
|
|
81
|
+
const { detached } = storeRemove(args.alias);
|
|
82
|
+
const t = getProjectTranslator();
|
|
83
|
+
console.log(
|
|
84
|
+
detached === null ? t("cli.store.no-alias", { alias: args.alias }) : t("cli.store.detached", { alias: args.alias })
|
|
85
|
+
);
|
|
86
|
+
}
|
|
87
|
+
});
|
|
88
|
+
var explainCommand = defineCommand({
|
|
89
|
+
meta: { name: "explain", description: "Explain how a store alias resolves" },
|
|
90
|
+
args: {
|
|
91
|
+
alias: { type: "positional", required: true, description: "Alias to explain" }
|
|
92
|
+
},
|
|
93
|
+
run({ args }) {
|
|
94
|
+
const explanation = storeExplain(args.alias);
|
|
95
|
+
console.log(
|
|
96
|
+
explanation === null ? getProjectTranslator()("cli.store.no-alias", { alias: args.alias }) : JSON.stringify(explanation, null, 2)
|
|
97
|
+
);
|
|
98
|
+
}
|
|
99
|
+
});
|
|
100
|
+
var bindCommand = defineCommand({
|
|
101
|
+
meta: { name: "bind", description: "Declare a required store on this project's config" },
|
|
102
|
+
args: {
|
|
103
|
+
id: { type: "positional", required: true, description: "Store alias/UUID to require" },
|
|
104
|
+
remote: { type: "string", description: "Suggested remote for clone onboarding" }
|
|
105
|
+
},
|
|
106
|
+
run({ args }) {
|
|
107
|
+
const entry = args.remote === void 0 ? { id: args.id } : { id: args.id, suggested_remote: args.remote };
|
|
108
|
+
const projectRoot = process.cwd();
|
|
109
|
+
const next = storeBind(projectRoot, entry);
|
|
110
|
+
console.log(
|
|
111
|
+
getProjectTranslator(projectRoot)("cli.store.bound", {
|
|
112
|
+
id: args.id,
|
|
113
|
+
count: String(next.required_stores?.length ?? 0)
|
|
114
|
+
})
|
|
115
|
+
);
|
|
116
|
+
regenerateBindingsSnapshot(projectRoot, { now: (/* @__PURE__ */ new Date()).toISOString() });
|
|
117
|
+
}
|
|
118
|
+
});
|
|
119
|
+
var switchWriteCommand = defineCommand({
|
|
120
|
+
meta: { name: "switch-write", description: "Set the active write store for non-personal scopes" },
|
|
121
|
+
args: {
|
|
122
|
+
alias: { type: "positional", required: true, description: "Alias of the store to write to" }
|
|
123
|
+
},
|
|
124
|
+
run({ args }) {
|
|
125
|
+
const projectRoot = process.cwd();
|
|
126
|
+
storeSwitchWrite(projectRoot, args.alias);
|
|
127
|
+
console.log(getProjectTranslator(projectRoot)("cli.store.switch-write", { alias: args.alias }));
|
|
128
|
+
}
|
|
129
|
+
});
|
|
130
|
+
var store_default = defineCommand({
|
|
131
|
+
meta: { name: "store", description: "Manage mounted Fabric knowledge stores" },
|
|
132
|
+
subCommands: {
|
|
133
|
+
list: listCommand,
|
|
134
|
+
create: createCommand,
|
|
135
|
+
add: addCommand,
|
|
136
|
+
remove: removeCommand,
|
|
137
|
+
explain: explainCommand,
|
|
138
|
+
bind: bindCommand,
|
|
139
|
+
"switch-write": switchWriteCommand
|
|
140
|
+
}
|
|
141
|
+
});
|
|
142
|
+
export {
|
|
143
|
+
store_default as default
|
|
144
|
+
};
|
|
@@ -0,0 +1,251 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
regenerateBindingsSnapshot
|
|
4
|
+
} from "./chunk-WU6GAPKH.js";
|
|
5
|
+
import "./chunk-L4Q55UC4.js";
|
|
6
|
+
import {
|
|
7
|
+
getProjectTranslator
|
|
8
|
+
} from "./chunk-2CY4BMTH.js";
|
|
9
|
+
import "./chunk-LFIKMVY7.js";
|
|
10
|
+
import {
|
|
11
|
+
loadGlobalConfig,
|
|
12
|
+
resolveGlobalRoot
|
|
13
|
+
} from "./chunk-RYAFBNES.js";
|
|
14
|
+
|
|
15
|
+
// src/commands/sync.ts
|
|
16
|
+
import { defineCommand } from "citty";
|
|
17
|
+
|
|
18
|
+
// src/sync/run-sync.ts
|
|
19
|
+
import { execFileSync } from "child_process";
|
|
20
|
+
import { existsSync, mkdirSync, readFileSync, rmSync, writeFileSync } from "fs";
|
|
21
|
+
import { join } from "path";
|
|
22
|
+
import { GLOBAL_STATE_DIR, storeRelativePath } from "@fenglimg/fabric-shared";
|
|
23
|
+
import { GenericIOError } from "@fenglimg/fabric-shared/errors";
|
|
24
|
+
|
|
25
|
+
// src/sync/state-machine.ts
|
|
26
|
+
function syncTransition(state, event) {
|
|
27
|
+
switch (state) {
|
|
28
|
+
case "pending":
|
|
29
|
+
if (event === "rebase_clean") return "synced";
|
|
30
|
+
if (event === "rebase_conflict") return "conflict";
|
|
31
|
+
if (event === "network_unavailable") return "offline";
|
|
32
|
+
break;
|
|
33
|
+
case "conflict":
|
|
34
|
+
if (event === "user_continue") return "synced";
|
|
35
|
+
if (event === "user_abort") return "aborted";
|
|
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
|
+
return JSON.parse(readFileSync(path, "utf8"));
|
|
92
|
+
}
|
|
93
|
+
function saveSession(globalRoot, session) {
|
|
94
|
+
const path = syncSessionPath(globalRoot);
|
|
95
|
+
mkdirSync(join(path, ".."), { recursive: true });
|
|
96
|
+
writeFileSync(path, `${JSON.stringify(session, null, 2)}
|
|
97
|
+
`, "utf8");
|
|
98
|
+
}
|
|
99
|
+
function clearSession(globalRoot) {
|
|
100
|
+
rmSync(syncSessionPath(globalRoot), { force: true });
|
|
101
|
+
}
|
|
102
|
+
function defaultPull(storeDir) {
|
|
103
|
+
try {
|
|
104
|
+
execFileSync("git", ["pull", "--rebase"], {
|
|
105
|
+
cwd: storeDir,
|
|
106
|
+
stdio: ["ignore", "pipe", "pipe"]
|
|
107
|
+
});
|
|
108
|
+
return "clean";
|
|
109
|
+
} catch (error) {
|
|
110
|
+
const detail = `${gitErrText(error, "stdout")}${gitErrText(error, "stderr")}`;
|
|
111
|
+
if (/CONFLICT|could not apply|needs merge|rebase --continue/i.test(detail)) {
|
|
112
|
+
return "conflict";
|
|
113
|
+
}
|
|
114
|
+
if (/could not resolve host|could not read from remote|unable to access|connection|network is unreachable|timed out/i.test(
|
|
115
|
+
detail
|
|
116
|
+
)) {
|
|
117
|
+
return "offline";
|
|
118
|
+
}
|
|
119
|
+
const gitMessage = detail.trim().length > 0 ? detail.trim() : "unknown git error";
|
|
120
|
+
throw new GenericIOError(`git pull --rebase failed in ${storeDir}: ${gitMessage}`, {
|
|
121
|
+
actionHint: "resolve the git issue above (e.g. authentication, a dirty working tree, or a detached HEAD), then re-run `fabric sync`",
|
|
122
|
+
details: error
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
function gitErrText(error, key) {
|
|
127
|
+
const value = error[key];
|
|
128
|
+
return typeof value === "string" || Buffer.isBuffer(value) ? String(value) : "";
|
|
129
|
+
}
|
|
130
|
+
function defaultRebaseContinue(storeDir) {
|
|
131
|
+
execFileSync("git", ["rebase", "--continue"], { cwd: storeDir, stdio: "ignore" });
|
|
132
|
+
}
|
|
133
|
+
function defaultRebaseAbort(storeDir) {
|
|
134
|
+
execFileSync("git", ["rebase", "--abort"], { cwd: storeDir, stdio: "ignore" });
|
|
135
|
+
}
|
|
136
|
+
var OUTCOME_EVENT = {
|
|
137
|
+
clean: "rebase_clean",
|
|
138
|
+
conflict: "rebase_conflict",
|
|
139
|
+
offline: "network_unavailable"
|
|
140
|
+
};
|
|
141
|
+
function walkPending(session, storeDirOf, pull) {
|
|
142
|
+
let next = session;
|
|
143
|
+
for (const store of session.stores) {
|
|
144
|
+
if (store.state !== "pending") {
|
|
145
|
+
continue;
|
|
146
|
+
}
|
|
147
|
+
const outcome = pull(storeDirOf(store));
|
|
148
|
+
next = applySyncEvent(next, store.alias, OUTCOME_EVENT[outcome]);
|
|
149
|
+
if (outcome === "conflict") {
|
|
150
|
+
break;
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
return next;
|
|
154
|
+
}
|
|
155
|
+
function finalize(session, options, globalRoot) {
|
|
156
|
+
const settled = isSyncSettled(session);
|
|
157
|
+
let snapshotWritten = false;
|
|
158
|
+
if (settled) {
|
|
159
|
+
clearSession(globalRoot);
|
|
160
|
+
const snapshot = regenerateBindingsSnapshot(options.projectRoot, {
|
|
161
|
+
globalRoot,
|
|
162
|
+
now: options.now,
|
|
163
|
+
...options.writeScope === void 0 ? {} : { writeScope: options.writeScope }
|
|
164
|
+
});
|
|
165
|
+
snapshotWritten = snapshot !== null;
|
|
166
|
+
} else {
|
|
167
|
+
saveSession(globalRoot, session);
|
|
168
|
+
}
|
|
169
|
+
return { session, settled, deferred: deferredPushStores(session), snapshotWritten };
|
|
170
|
+
}
|
|
171
|
+
function runStartSync(options) {
|
|
172
|
+
const globalRoot = options.globalRoot ?? resolveGlobalRoot();
|
|
173
|
+
const config = loadGlobalConfig(globalRoot);
|
|
174
|
+
if (config === null) {
|
|
175
|
+
throw new Error(NO_GLOBAL_CONFIG);
|
|
176
|
+
}
|
|
177
|
+
const syncable = config.stores.filter((store) => store.remote !== void 0);
|
|
178
|
+
const session = planSync(
|
|
179
|
+
syncable.map((store) => ({ alias: store.alias, store_uuid: store.store_uuid }))
|
|
180
|
+
);
|
|
181
|
+
const storeDirOf = (status) => join(globalRoot, storeRelativePath(status.store_uuid));
|
|
182
|
+
const walked = walkPending(session, storeDirOf, options.pull ?? defaultPull);
|
|
183
|
+
return finalize(walked, options, globalRoot);
|
|
184
|
+
}
|
|
185
|
+
function runContinueSync(options) {
|
|
186
|
+
const globalRoot = options.globalRoot ?? resolveGlobalRoot();
|
|
187
|
+
const session = loadSession(globalRoot);
|
|
188
|
+
if (session === null) {
|
|
189
|
+
throw new Error(NO_SESSION);
|
|
190
|
+
}
|
|
191
|
+
const conflicted = session.stores.find((store) => store.state === "conflict");
|
|
192
|
+
if (conflicted === void 0) {
|
|
193
|
+
throw new Error(NO_CONFLICT);
|
|
194
|
+
}
|
|
195
|
+
const storeDirOf = (status) => join(globalRoot, storeRelativePath(status.store_uuid));
|
|
196
|
+
(options.rebaseContinue ?? defaultRebaseContinue)(storeDirOf(conflicted));
|
|
197
|
+
const resumed = walkPending(continueSync(session), storeDirOf, options.pull ?? defaultPull);
|
|
198
|
+
return finalize(resumed, options, globalRoot);
|
|
199
|
+
}
|
|
200
|
+
function runAbortSync(options) {
|
|
201
|
+
const globalRoot = options.globalRoot ?? resolveGlobalRoot();
|
|
202
|
+
const session = loadSession(globalRoot);
|
|
203
|
+
if (session === null) {
|
|
204
|
+
throw new Error(NO_SESSION);
|
|
205
|
+
}
|
|
206
|
+
const conflicted = session.stores.find((store) => store.state === "conflict");
|
|
207
|
+
if (conflicted === void 0) {
|
|
208
|
+
throw new Error(NO_CONFLICT);
|
|
209
|
+
}
|
|
210
|
+
const storeDirOf = (status) => join(globalRoot, storeRelativePath(status.store_uuid));
|
|
211
|
+
(options.rebaseAbort ?? defaultRebaseAbort)(storeDirOf(conflicted));
|
|
212
|
+
const resumed = walkPending(abortSync(session), storeDirOf, options.pull ?? defaultPull);
|
|
213
|
+
return finalize(resumed, options, globalRoot);
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
// src/commands/sync.ts
|
|
217
|
+
function report(result, projectRoot) {
|
|
218
|
+
const t = getProjectTranslator(projectRoot);
|
|
219
|
+
for (const store of result.session.stores) {
|
|
220
|
+
console.log(`${store.alias} ${store.state}`);
|
|
221
|
+
}
|
|
222
|
+
if (result.deferred.length > 0) {
|
|
223
|
+
console.log(t("cli.sync.deferred", { count: String(result.deferred.length) }));
|
|
224
|
+
}
|
|
225
|
+
if (!result.settled) {
|
|
226
|
+
console.log(t("cli.sync.paused"));
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
var sync_default = defineCommand({
|
|
230
|
+
meta: { name: "sync", description: "Pull --rebase + push every mounted store; resume conflicts" },
|
|
231
|
+
args: {
|
|
232
|
+
continue: { type: "boolean", description: "Resume after resolving a rebase conflict" },
|
|
233
|
+
abort: { type: "boolean", description: "Abort the conflicted store's rebase" }
|
|
234
|
+
},
|
|
235
|
+
run({ args }) {
|
|
236
|
+
const projectRoot = process.cwd();
|
|
237
|
+
const options = { projectRoot, now: (/* @__PURE__ */ new Date()).toISOString() };
|
|
238
|
+
if (args.continue === true) {
|
|
239
|
+
report(runContinueSync(options), projectRoot);
|
|
240
|
+
return;
|
|
241
|
+
}
|
|
242
|
+
if (args.abort === true) {
|
|
243
|
+
report(runAbortSync(options), projectRoot);
|
|
244
|
+
return;
|
|
245
|
+
}
|
|
246
|
+
report(runStartSync(options), projectRoot);
|
|
247
|
+
}
|
|
248
|
+
});
|
|
249
|
+
export {
|
|
250
|
+
sync_default as default
|
|
251
|
+
};
|
|
@@ -7,21 +7,21 @@ import {
|
|
|
7
7
|
HOOK_SCRIPT_DESTINATIONS,
|
|
8
8
|
SKILL_DESTINATIONS,
|
|
9
9
|
fabricAgentsSnapshotPath
|
|
10
|
-
} from "./chunk-
|
|
11
|
-
import {
|
|
12
|
-
detectClientSupports,
|
|
13
|
-
resolveClients
|
|
14
|
-
} from "./chunk-MF3OTILQ.js";
|
|
10
|
+
} from "./chunk-2R55HNVD.js";
|
|
15
11
|
import {
|
|
16
12
|
paint
|
|
17
|
-
} from "./chunk-
|
|
13
|
+
} from "./chunk-BO4XIZWZ.js";
|
|
18
14
|
import {
|
|
19
15
|
createDebugLogger,
|
|
20
16
|
resolveDevMode
|
|
21
17
|
} from "./chunk-COI5VDFU.js";
|
|
18
|
+
import {
|
|
19
|
+
detectClientSupports,
|
|
20
|
+
resolveClients
|
|
21
|
+
} from "./chunk-XC5RUHLK.js";
|
|
22
22
|
import {
|
|
23
23
|
t
|
|
24
|
-
} from "./chunk-
|
|
24
|
+
} from "./chunk-2CY4BMTH.js";
|
|
25
25
|
|
|
26
26
|
// src/commands/uninstall.ts
|
|
27
27
|
import { existsSync as existsSync2, statSync } from "fs";
|
|
@@ -46,6 +46,15 @@ async function uninstallFabricReviewSkill(projectRoot) {
|
|
|
46
46
|
async function uninstallFabricImportSkill(projectRoot) {
|
|
47
47
|
return removeSkill("skill-import", SKILL_DESTINATIONS.fabricImport, projectRoot);
|
|
48
48
|
}
|
|
49
|
+
async function uninstallFabricSyncSkill(projectRoot) {
|
|
50
|
+
return removeSkill("skill-sync", SKILL_DESTINATIONS.fabricSync, projectRoot);
|
|
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
|
+
}
|
|
49
58
|
async function removeSkill(step, rels, projectRoot) {
|
|
50
59
|
const results = [];
|
|
51
60
|
for (const rel of rels) {
|
|
@@ -72,6 +81,13 @@ async function removeKnowledgeHintNarrowHook(projectRoot) {
|
|
|
72
81
|
projectRoot
|
|
73
82
|
);
|
|
74
83
|
}
|
|
84
|
+
async function removeCitePolicyEvictHook(projectRoot) {
|
|
85
|
+
return removeHookScripts(
|
|
86
|
+
"hook-cite-policy-evict-script",
|
|
87
|
+
HOOK_SCRIPT_DESTINATIONS.citePolicyEvict,
|
|
88
|
+
projectRoot
|
|
89
|
+
);
|
|
90
|
+
}
|
|
75
91
|
async function removeHookScripts(step, rels, projectRoot) {
|
|
76
92
|
const results = [];
|
|
77
93
|
for (const rel of rels) {
|
|
@@ -301,6 +317,30 @@ async function uninstallBootstrapStage(projectRoot, _opts = {}) {
|
|
|
301
317
|
projectRoot,
|
|
302
318
|
() => removeArchiveHintHook(projectRoot)
|
|
303
319
|
);
|
|
320
|
+
await runAndCollect(
|
|
321
|
+
results,
|
|
322
|
+
"hook-cite-policy-evict-script",
|
|
323
|
+
projectRoot,
|
|
324
|
+
() => removeCitePolicyEvictHook(projectRoot)
|
|
325
|
+
);
|
|
326
|
+
await runAndCollect(
|
|
327
|
+
results,
|
|
328
|
+
"skill-connect",
|
|
329
|
+
projectRoot,
|
|
330
|
+
() => uninstallFabricConnectSkill(projectRoot)
|
|
331
|
+
);
|
|
332
|
+
await runAndCollect(
|
|
333
|
+
results,
|
|
334
|
+
"skill-audit",
|
|
335
|
+
projectRoot,
|
|
336
|
+
() => uninstallFabricAuditSkill(projectRoot)
|
|
337
|
+
);
|
|
338
|
+
await runAndCollect(
|
|
339
|
+
results,
|
|
340
|
+
"skill-sync",
|
|
341
|
+
projectRoot,
|
|
342
|
+
() => uninstallFabricSyncSkill(projectRoot)
|
|
343
|
+
);
|
|
304
344
|
await runAndCollect(
|
|
305
345
|
results,
|
|
306
346
|
"skill-import",
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
getProjectTranslator
|
|
4
|
+
} from "./chunk-2CY4BMTH.js";
|
|
5
|
+
import {
|
|
6
|
+
whoami
|
|
7
|
+
} from "./chunk-T5RPGCCM.js";
|
|
8
|
+
import "./chunk-LFIKMVY7.js";
|
|
9
|
+
import "./chunk-RYAFBNES.js";
|
|
10
|
+
|
|
11
|
+
// src/commands/whoami.ts
|
|
12
|
+
import { defineCommand } from "citty";
|
|
13
|
+
var whoami_default = defineCommand({
|
|
14
|
+
meta: { name: "whoami", description: "Show this machine's Fabric uid and mounted stores" },
|
|
15
|
+
run() {
|
|
16
|
+
const t = getProjectTranslator();
|
|
17
|
+
const info = whoami();
|
|
18
|
+
if (info === null) {
|
|
19
|
+
console.log(t("cli.cmd.no-global-config"));
|
|
20
|
+
return;
|
|
21
|
+
}
|
|
22
|
+
console.log(t("cli.whoami.uid", { uid: info.uid }));
|
|
23
|
+
if (info.stores.length === 0) {
|
|
24
|
+
console.log(t("cli.whoami.stores-none"));
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
27
|
+
console.log(t("cli.whoami.stores-label"));
|
|
28
|
+
const localOnly = t("cli.shared.local-only");
|
|
29
|
+
for (const store of info.stores) {
|
|
30
|
+
console.log(` ${store.alias} ${store.store_uuid}${store.local_only ? ` ${localOnly}` : ""}`);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
});
|
|
34
|
+
export {
|
|
35
|
+
whoami_default as default
|
|
36
|
+
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@fenglimg/fabric-cli",
|
|
3
|
-
"version": "2.0.1",
|
|
3
|
+
"version": "2.2.0-rc.1",
|
|
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.0.1",
|
|
49
|
-
"@fenglimg/fabric-shared": "2.0.1"
|
|
48
|
+
"@fenglimg/fabric-server": "2.2.0-rc.1",
|
|
49
|
+
"@fenglimg/fabric-shared": "2.2.0-rc.1"
|
|
50
50
|
},
|
|
51
51
|
"devDependencies": {
|
|
52
52
|
"@types/node": "^22.15.0",
|