@fenglimg/fabric-cli 2.0.0 → 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.
- package/LICENSE +21 -0
- package/README.md +6 -5
- package/dist/chunk-BATF4PEJ.js +361 -0
- package/dist/{chunk-OBQU6NHO.js → chunk-COI5VDFU.js} +0 -18
- package/dist/chunk-F46ORPOA.js +903 -0
- package/dist/chunk-HFQVXY6P.js +86 -0
- package/dist/chunk-L4Q55UC4.js +52 -0
- package/dist/chunk-LFIKMVY7.js +27 -0
- package/dist/chunk-MF3OTILQ.js +544 -0
- package/dist/chunk-PWLW3B57.js +18 -0
- package/dist/chunk-RYAFBNES.js +33 -0
- package/dist/chunk-T5RPGCCM.js +40 -0
- package/dist/chunk-WU6GAPKH.js +36 -0
- package/dist/config-XJIPZNUP.js +13 -0
- package/dist/doctor-QVNPHLJK.js +920 -0
- package/dist/index.js +23 -8
- package/dist/{init-BIRSIOXO.js → install-2HDO5FTQ.js} +807 -705
- package/dist/metrics-ACEQFPDU.js +122 -0
- package/dist/onboard-coverage-MFCAEBDO.js +220 -0
- package/dist/{plan-context-hint-QMUPAXIB.js → plan-context-hint-FC6P3WFE.js} +34 -28
- package/dist/scope-explain-2F2R5URO.js +33 -0
- package/dist/status-GLQWLWH6.js +23 -0
- package/dist/store-XTSE5TY6.js +105 -0
- package/dist/sync-BJCWDPNC.js +245 -0
- package/dist/uninstall-TAXSUSKH.js +1073 -0
- package/dist/whoami-B6AEMSEV.js +31 -0
- package/package.json +30 -5
- package/templates/hooks/cite-policy-evict.cjs +231 -0
- package/templates/hooks/configs/README.md +29 -6
- package/templates/hooks/configs/claude-code.json +14 -3
- package/templates/hooks/configs/codex-hooks.json +6 -3
- package/templates/hooks/configs/cursor-hooks.json +8 -10
- package/templates/hooks/fabric-hint.cjs +873 -105
- package/templates/hooks/knowledge-hint-broad.cjs +549 -135
- package/templates/hooks/knowledge-hint-narrow.cjs +830 -26
- package/templates/hooks/lib/banner-i18n.cjs +309 -0
- package/templates/hooks/lib/bindings-snapshot-reader.cjs +81 -0
- package/templates/hooks/lib/cite-contract-reminder.cjs +179 -0
- package/templates/hooks/lib/cite-line-parser.cjs +180 -0
- package/templates/hooks/lib/client-adapter.cjs +106 -0
- package/templates/hooks/lib/config-cache.cjs +107 -0
- package/templates/hooks/lib/state-store.cjs +84 -0
- package/templates/hooks/lib/summary-fallback.cjs +210 -0
- package/templates/skills/fabric-archive/SKILL.md +97 -419
- package/templates/skills/fabric-archive/ref/dry-run-scope.md +16 -0
- package/templates/skills/fabric-archive/ref/e5-cron-recap.md +58 -0
- package/templates/skills/fabric-archive/ref/i18n-policy.md +86 -0
- package/templates/skills/fabric-archive/ref/phase-0-range-resolution.md +156 -0
- package/templates/skills/fabric-archive/ref/phase-1-5-onboard.md +218 -0
- package/templates/skills/fabric-archive/ref/phase-1-cross-session.md +62 -0
- package/templates/skills/fabric-archive/ref/phase-2-5-viability.md +68 -0
- package/templates/skills/fabric-archive/ref/phase-3-5-scope.md +108 -0
- package/templates/skills/fabric-archive/ref/phase-3-classify.md +63 -0
- package/templates/skills/fabric-archive/ref/phase-4-5-emit.md +78 -0
- package/templates/skills/fabric-archive/ref/phase-4-mcp-persist.md +89 -0
- package/templates/skills/fabric-archive/ref/rc-history.md +38 -0
- package/templates/skills/fabric-archive/ref/worked-examples.md +78 -0
- package/templates/skills/fabric-import/SKILL.md +77 -514
- package/templates/skills/fabric-import/ref/checkpoint-state.md +85 -0
- package/templates/skills/fabric-import/ref/i18n-policy.md +79 -0
- package/templates/skills/fabric-import/ref/output-contract.md +61 -0
- package/templates/skills/fabric-import/ref/phase-2-mining.md +213 -0
- package/templates/skills/fabric-import/ref/phase-3-dedup.md +75 -0
- package/templates/skills/fabric-import/ref/state-recovery.md +57 -0
- package/templates/skills/fabric-import/ref/worked-examples.md +127 -0
- package/templates/skills/fabric-review/SKILL.md +90 -284
- package/templates/skills/fabric-review/ref/askuserquestion-policy.md +66 -0
- package/templates/skills/fabric-review/ref/i18n-policy.md +111 -0
- package/templates/skills/fabric-review/ref/modify-flow.md +103 -0
- package/templates/skills/fabric-review/ref/output-contract.md +58 -0
- package/templates/skills/fabric-review/ref/per-mode-flows.md +155 -0
- package/templates/skills/fabric-review/ref/semantic-check.md +26 -0
- package/templates/skills/fabric-review/ref/worked-examples.md +95 -0
- package/templates/skills/fabric-sync/SKILL.md +46 -0
- package/templates/skills/lib/shared-policy.md +69 -0
- package/dist/chunk-6ICJICVU.js +0 -10
- package/dist/chunk-74SZWYPH.js +0 -658
- package/dist/chunk-EYIDD2YS.js +0 -1000
- package/dist/doctor-T7JWODKG.js +0 -282
- package/dist/hooks-Y74Y5LQS.js +0 -12
- package/dist/scan-LMK3UCWL.js +0 -22
- package/dist/serve-H554BHLG.js +0 -124
- package/templates/agents-md/AGENTS.md.template +0 -59
- package/templates/bootstrap/CLAUDE.md +0 -8
- package/templates/bootstrap/codex-AGENTS-header.md +0 -6
- package/templates/bootstrap/cursor-fabric-bootstrap.mdc +0 -10
|
@@ -1,444 +1,431 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
|
-
|
|
4
|
-
CursorWriter,
|
|
5
|
-
addArchiveSkillPointer,
|
|
6
|
-
createServerEntry,
|
|
7
|
-
hooksCommand,
|
|
3
|
+
cleanupDeprecatedSkills,
|
|
8
4
|
installArchiveHintHook,
|
|
5
|
+
installCitePolicyEvictHook,
|
|
9
6
|
installFabricArchiveSkill,
|
|
10
7
|
installFabricImportSkill,
|
|
11
8
|
installFabricReviewSkill,
|
|
12
|
-
|
|
9
|
+
installFabricSyncSkill,
|
|
10
|
+
installHookLibs,
|
|
13
11
|
installKnowledgeHintBroadHook,
|
|
14
12
|
installKnowledgeHintNarrowHook,
|
|
13
|
+
installSharedSkillLib,
|
|
15
14
|
mergeClaudeCodeHookConfig,
|
|
16
15
|
mergeCodexHookConfig,
|
|
17
16
|
mergeCursorHookConfig,
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
} from "./chunk-EYIDD2YS.js";
|
|
17
|
+
readFabricLanguagePreference,
|
|
18
|
+
writeClaudeBootstrapThinShell,
|
|
19
|
+
writeCodexBootstrapManagedBlock,
|
|
20
|
+
writeCursorBootstrapManagedBlock,
|
|
21
|
+
writeFabricAgentsSnapshot
|
|
22
|
+
} from "./chunk-F46ORPOA.js";
|
|
25
23
|
import {
|
|
26
24
|
displayWidth,
|
|
27
25
|
padEnd,
|
|
28
26
|
paint
|
|
29
27
|
} from "./chunk-WWNXR34K.js";
|
|
30
|
-
import {
|
|
31
|
-
t
|
|
32
|
-
} from "./chunk-6ICJICVU.js";
|
|
33
28
|
import {
|
|
34
29
|
createDebugLogger,
|
|
35
30
|
resolveDevMode
|
|
36
|
-
} from "./chunk-
|
|
31
|
+
} from "./chunk-COI5VDFU.js";
|
|
32
|
+
import {
|
|
33
|
+
installMcpClients
|
|
34
|
+
} from "./chunk-BATF4PEJ.js";
|
|
35
|
+
import {
|
|
36
|
+
detectClientSupports
|
|
37
|
+
} from "./chunk-MF3OTILQ.js";
|
|
38
|
+
import {
|
|
39
|
+
t
|
|
40
|
+
} from "./chunk-PWLW3B57.js";
|
|
41
|
+
import {
|
|
42
|
+
globalConfigPath,
|
|
43
|
+
loadGlobalConfig,
|
|
44
|
+
resolveGlobalRoot,
|
|
45
|
+
saveGlobalConfig
|
|
46
|
+
} from "./chunk-RYAFBNES.js";
|
|
37
47
|
|
|
38
|
-
// src/commands/
|
|
39
|
-
import { randomUUID } from "crypto";
|
|
40
|
-
import { homedir
|
|
48
|
+
// src/commands/install.ts
|
|
49
|
+
import { randomUUID as randomUUID2 } from "crypto";
|
|
50
|
+
import { homedir } from "os";
|
|
41
51
|
import * as childProcess from "child_process";
|
|
42
|
-
import { appendFileSync, existsSync as
|
|
43
|
-
import { dirname
|
|
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";
|
|
44
54
|
import { cancel, confirm, group, intro, isCancel, log, note, outro, select } from "@clack/prompts";
|
|
45
55
|
import { defaultAgentsMetaCounters } from "@fenglimg/fabric-shared";
|
|
46
|
-
import { atomicWriteJson
|
|
47
|
-
import { defineCommand as defineCommand2 } from "citty";
|
|
48
|
-
import { checkLockOrThrow } from "@fenglimg/fabric-server";
|
|
49
|
-
|
|
50
|
-
// src/commands/config.ts
|
|
51
|
-
import { existsSync as existsSync4 } from "fs";
|
|
52
|
-
import { readFile as readFile2 } from "fs/promises";
|
|
53
|
-
import { resolve as resolve3 } from "path";
|
|
54
|
-
import { fileURLToPath } from "url";
|
|
56
|
+
import { atomicWriteJson } from "@fenglimg/fabric-shared/node/atomic-write";
|
|
55
57
|
import { defineCommand } from "citty";
|
|
56
58
|
|
|
57
|
-
// src/
|
|
58
|
-
import { existsSync
|
|
59
|
-
import { join
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
59
|
+
// src/install/hooks-orchestrator.ts
|
|
60
|
+
import { existsSync, statSync } from "fs";
|
|
61
|
+
import { isAbsolute, join, resolve } from "path";
|
|
62
|
+
async function installHooks(target, _options = {}) {
|
|
63
|
+
const normalizedTarget = normalizeTarget(target);
|
|
64
|
+
assertExistingDirectory(normalizedTarget);
|
|
65
|
+
const results = [];
|
|
66
|
+
results.push(...await runStep(() => installFabricArchiveSkill(normalizedTarget)));
|
|
67
|
+
results.push(...await runStep(() => installFabricReviewSkill(normalizedTarget)));
|
|
68
|
+
results.push(...await runStep(() => installFabricImportSkill(normalizedTarget)));
|
|
69
|
+
results.push(...await runStep(() => installFabricSyncSkill(normalizedTarget)));
|
|
70
|
+
results.push(...await runStep(() => installSharedSkillLib(normalizedTarget)));
|
|
71
|
+
results.push(...await runStep(() => installArchiveHintHook(normalizedTarget)));
|
|
72
|
+
results.push(...await runStep(() => installKnowledgeHintBroadHook(normalizedTarget)));
|
|
73
|
+
results.push(...await runStep(() => installKnowledgeHintNarrowHook(normalizedTarget)));
|
|
74
|
+
results.push(...await runStep(() => installCitePolicyEvictHook(normalizedTarget)));
|
|
75
|
+
results.push(...await runStep(() => installHookLibs(normalizedTarget)));
|
|
76
|
+
results.push(await runSingleStep("claude-hook-config", () => mergeClaudeCodeHookConfig(normalizedTarget)));
|
|
77
|
+
results.push(await runSingleStep("codex-hook-config", () => mergeCodexHookConfig(normalizedTarget)));
|
|
78
|
+
results.push(await runSingleStep("cursor-hook-config", () => mergeCursorHookConfig(normalizedTarget)));
|
|
79
|
+
results.push(await runSingleStep("bootstrap-snapshot", () => writeFabricAgentsSnapshot(normalizedTarget)));
|
|
80
|
+
results.push(await runSingleStep("bootstrap-claude", () => writeClaudeBootstrapThinShell(normalizedTarget)));
|
|
81
|
+
results.push(await runSingleStep("bootstrap-codex", () => writeCodexBootstrapManagedBlock(normalizedTarget)));
|
|
82
|
+
results.push(await runSingleStep("bootstrap-cursor", () => writeCursorBootstrapManagedBlock(normalizedTarget)));
|
|
83
|
+
results.push(...validateHookPaths(normalizedTarget));
|
|
84
|
+
return summarizeResults(results);
|
|
85
|
+
}
|
|
86
|
+
function validateHookPaths(projectRoot) {
|
|
87
|
+
const scripts = [
|
|
88
|
+
{ stepSuffix: "", hookFile: "fabric-hint.cjs" },
|
|
89
|
+
{ stepSuffix: "-broad", hookFile: "knowledge-hint-broad.cjs" },
|
|
90
|
+
{ stepSuffix: "-narrow", hookFile: "knowledge-hint-narrow.cjs" }
|
|
91
|
+
];
|
|
92
|
+
const clients = [
|
|
93
|
+
{
|
|
94
|
+
client: "claude",
|
|
95
|
+
configRel: join(".claude", "settings.json"),
|
|
96
|
+
hookDir: join(".claude", "hooks")
|
|
97
|
+
},
|
|
98
|
+
{
|
|
99
|
+
client: "codex",
|
|
100
|
+
configRel: join(".codex", "hooks.json"),
|
|
101
|
+
hookDir: join(".codex", "hooks")
|
|
102
|
+
},
|
|
103
|
+
{
|
|
104
|
+
client: "cursor",
|
|
105
|
+
configRel: join(".cursor", "hooks.json"),
|
|
106
|
+
hookDir: join(".cursor", "hooks")
|
|
90
107
|
}
|
|
91
|
-
await writeJsonClientConfig(configPath, {
|
|
92
|
-
command: process.execPath,
|
|
93
|
-
args: [serverPath]
|
|
94
|
-
});
|
|
95
|
-
}
|
|
96
|
-
};
|
|
97
|
-
|
|
98
|
-
// src/config/toml.ts
|
|
99
|
-
import { existsSync as existsSync2 } from "fs";
|
|
100
|
-
import { mkdir, readFile } from "fs/promises";
|
|
101
|
-
import { dirname, join as join2, resolve as resolve2 } from "path";
|
|
102
|
-
import { homedir as homedir2 } from "os";
|
|
103
|
-
import { atomicWriteText } from "@fenglimg/fabric-shared/node/atomic-write";
|
|
104
|
-
function expandHome(filePath) {
|
|
105
|
-
if (filePath === "~") {
|
|
106
|
-
return homedir2();
|
|
107
|
-
}
|
|
108
|
-
if (filePath.startsWith("~/")) {
|
|
109
|
-
return join2(homedir2(), filePath.slice(2));
|
|
110
|
-
}
|
|
111
|
-
return filePath;
|
|
112
|
-
}
|
|
113
|
-
function escapeTomlString(value) {
|
|
114
|
-
return JSON.stringify(value);
|
|
115
|
-
}
|
|
116
|
-
function serializeTomlStringArray(values) {
|
|
117
|
-
return `[${values.map((value) => escapeTomlString(value)).join(", ")}]`;
|
|
118
|
-
}
|
|
119
|
-
function serializeTomlInlineTable(values) {
|
|
120
|
-
const entries = Object.entries(values).sort(([left], [right]) => left.localeCompare(right)).map(([key, value]) => `${key} = ${escapeTomlString(value)}`);
|
|
121
|
-
return `{ ${entries.join(", ")} }`;
|
|
122
|
-
}
|
|
123
|
-
function serializeCodexServerBlock(serverName, serverEntry) {
|
|
124
|
-
const lines = [
|
|
125
|
-
`[mcp_servers.${serverName}]`,
|
|
126
|
-
`command = ${escapeTomlString(serverEntry.command)}`,
|
|
127
|
-
`args = ${serializeTomlStringArray(serverEntry.args)}`
|
|
128
108
|
];
|
|
129
|
-
|
|
130
|
-
|
|
109
|
+
const results = [];
|
|
110
|
+
for (const { client, configRel, hookDir } of clients) {
|
|
111
|
+
const configPath = resolve(projectRoot, configRel);
|
|
112
|
+
if (!existsSync(configPath)) {
|
|
113
|
+
results.push({
|
|
114
|
+
step: `hook-validate-${client}`,
|
|
115
|
+
path: configPath,
|
|
116
|
+
status: "skipped",
|
|
117
|
+
message: "missing-config"
|
|
118
|
+
});
|
|
119
|
+
continue;
|
|
120
|
+
}
|
|
121
|
+
for (const { stepSuffix, hookFile } of scripts) {
|
|
122
|
+
const expectedHookPath = resolve(projectRoot, hookDir, hookFile);
|
|
123
|
+
const expectedHookRel = join(hookDir, hookFile);
|
|
124
|
+
const step = `hook-validate-${client}${stepSuffix}`;
|
|
125
|
+
if (!existsSync(expectedHookPath)) {
|
|
126
|
+
results.push({
|
|
127
|
+
step,
|
|
128
|
+
path: expectedHookPath,
|
|
129
|
+
status: "error",
|
|
130
|
+
message: `hook script missing: ${expectedHookRel}`
|
|
131
|
+
});
|
|
132
|
+
continue;
|
|
133
|
+
}
|
|
134
|
+
results.push({ step, path: expectedHookPath, status: "skipped", message: "ok" });
|
|
135
|
+
}
|
|
131
136
|
}
|
|
132
|
-
return
|
|
133
|
-
`;
|
|
134
|
-
}
|
|
135
|
-
function trimTrailingBlankLines(value) {
|
|
136
|
-
return value.replace(/\s+$/u, "");
|
|
137
|
+
return results;
|
|
137
138
|
}
|
|
138
|
-
function
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
return block;
|
|
139
|
+
async function runStep(fn) {
|
|
140
|
+
try {
|
|
141
|
+
return await fn();
|
|
142
|
+
} catch (error) {
|
|
143
|
+
return [
|
|
144
|
+
{
|
|
145
|
+
step: "hook-install",
|
|
146
|
+
path: "",
|
|
147
|
+
status: "error",
|
|
148
|
+
message: error instanceof Error ? error.message : String(error)
|
|
149
|
+
}
|
|
150
|
+
];
|
|
151
151
|
}
|
|
152
|
-
return `${trimmed}
|
|
153
|
-
|
|
154
|
-
${block}`;
|
|
155
152
|
}
|
|
156
|
-
async function
|
|
153
|
+
async function runSingleStep(step, fn) {
|
|
157
154
|
try {
|
|
158
|
-
return await
|
|
155
|
+
return await fn();
|
|
159
156
|
} catch (error) {
|
|
160
|
-
|
|
161
|
-
|
|
157
|
+
return {
|
|
158
|
+
step,
|
|
159
|
+
path: "",
|
|
160
|
+
status: "error",
|
|
161
|
+
message: error instanceof Error ? error.message : String(error)
|
|
162
|
+
};
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
function summarizeResults(results) {
|
|
166
|
+
const installed = [];
|
|
167
|
+
const skipped = [];
|
|
168
|
+
const errors = [];
|
|
169
|
+
for (const r of results) {
|
|
170
|
+
switch (r.status) {
|
|
171
|
+
case "written":
|
|
172
|
+
installed.push(r.path);
|
|
173
|
+
break;
|
|
174
|
+
case "skipped":
|
|
175
|
+
skipped.push(r.path);
|
|
176
|
+
break;
|
|
177
|
+
case "error":
|
|
178
|
+
errors.push(`${r.step} ${r.path}: ${r.message ?? "unknown error"}`);
|
|
179
|
+
break;
|
|
162
180
|
}
|
|
163
|
-
throw error;
|
|
164
181
|
}
|
|
182
|
+
return { installed, skipped, errors };
|
|
165
183
|
}
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
184
|
+
function normalizeTarget(targetInput) {
|
|
185
|
+
return isAbsolute(targetInput) ? targetInput : resolve(process.cwd(), targetInput);
|
|
186
|
+
}
|
|
187
|
+
function assertExistingDirectory(target) {
|
|
188
|
+
if (!existsSync(target) || !statSync(target).isDirectory()) {
|
|
189
|
+
throw new Error(t("cli.shared.target-invalid", { target }));
|
|
171
190
|
}
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
191
|
+
}
|
|
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 = "";
|
|
179
213
|
}
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
if (configPath === null) {
|
|
183
|
-
return;
|
|
184
|
-
}
|
|
185
|
-
const rawConfig = await readTomlConfigText(configPath);
|
|
186
|
-
const nextConfig = upsertCodexServerBlock(rawConfig, "fabric", createServerEntry(serverPath));
|
|
187
|
-
await mkdir(dirname(configPath), { recursive: true });
|
|
188
|
-
await atomicWriteText(configPath, nextConfig);
|
|
214
|
+
if (email === "") {
|
|
215
|
+
return "u-anon";
|
|
189
216
|
}
|
|
190
|
-
|
|
217
|
+
const hash = createHash("sha256").update(email.toLowerCase()).digest("hex").slice(0, 12);
|
|
218
|
+
return `u-${hash}`;
|
|
219
|
+
}
|
|
191
220
|
|
|
192
|
-
// src/
|
|
193
|
-
import {
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
function
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
(configuredPath) => new CursorWriter(configuredPath),
|
|
222
|
-
hasExplicitPath(clientPaths, "cursor") ? clientPaths.cursor : void 0
|
|
223
|
-
);
|
|
224
|
-
addIfDetected(
|
|
225
|
-
writers,
|
|
226
|
-
existsSync3(join3(homedir3(), ".codex")),
|
|
227
|
-
(configuredPath) => new CodexTOMLConfigWriter(configuredPath),
|
|
228
|
-
hasExplicitPath(clientPaths, "codexCLI") ? clientPaths.codexCLI : void 0
|
|
229
|
-
);
|
|
230
|
-
return writers;
|
|
231
|
-
}
|
|
232
|
-
function detectClientSupports(workspaceRoot, fabricConfig = {}) {
|
|
233
|
-
const clientPaths = fabricConfig.clientPaths;
|
|
234
|
-
const claudeDetected = existsSync3(join3(homedir3(), ".claude")) || existsSync3(join3(workspaceRoot, ".claude"));
|
|
235
|
-
const claudeDesktopDetected = existsSync3(getClaudeDesktopConfigPath());
|
|
236
|
-
const cursorDetected = existsSync3(join3(workspaceRoot, ".cursor"));
|
|
237
|
-
const codexDetected = existsSync3(join3(homedir3(), ".codex"));
|
|
238
|
-
return [
|
|
239
|
-
{
|
|
240
|
-
clientKind: "ClaudeCodeCLI",
|
|
241
|
-
label: "Claude Code CLI",
|
|
242
|
-
detected: claudeDetected || hasExplicitPath(clientPaths, "claudeCodeCLI"),
|
|
243
|
-
bootstrapTargetPath: ".fabric/bootstrap/README.md",
|
|
244
|
-
configPath: "project .claude/settings.json",
|
|
245
|
-
capabilities: {
|
|
246
|
-
bootstrap: true,
|
|
247
|
-
mcp: true,
|
|
248
|
-
hook: true,
|
|
249
|
-
skill: true
|
|
250
|
-
},
|
|
251
|
-
installedCapabilities: {
|
|
252
|
-
hook: true,
|
|
253
|
-
skill: true
|
|
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" });
|
|
254
250
|
}
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
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
|
+
}
|
|
267
264
|
}
|
|
268
|
-
|
|
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([
|
|
269
285
|
{
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
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 });
|
|
280
300
|
}
|
|
281
301
|
},
|
|
282
302
|
{
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
hook: true,
|
|
292
|
-
skill: true
|
|
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;
|
|
293
311
|
},
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
// v2/rc.2: v1 client-side init skill removed; skill-installation probes
|
|
297
|
-
// will return once rc.2/3/4 introduce the v2 skills (fabric-archive,
|
|
298
|
-
// fabric-review, fabric-import). Until then there is nothing to probe.
|
|
299
|
-
skill: false
|
|
312
|
+
rollback: () => {
|
|
313
|
+
rmSync(globalConfigPath(options.globalRoot), { force: true });
|
|
300
314
|
}
|
|
301
315
|
}
|
|
302
|
-
];
|
|
316
|
+
]);
|
|
317
|
+
return { receipt, config, alreadyInstalled: false };
|
|
303
318
|
}
|
|
304
319
|
|
|
305
|
-
// src/
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
"
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
}
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
}
|
|
332
|
-
return clients;
|
|
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}`);
|
|
333
346
|
}
|
|
334
|
-
async function
|
|
335
|
-
const
|
|
336
|
-
|
|
337
|
-
|
|
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}`);
|
|
338
354
|
}
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
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);
|
|
342
360
|
}
|
|
343
|
-
return parsed;
|
|
344
|
-
}
|
|
345
|
-
function resolveServerPath(override) {
|
|
346
|
-
if (override) return override;
|
|
347
|
-
if (process.env.FAB_SERVER_PATH) return resolve3(process.env.FAB_SERVER_PATH);
|
|
348
|
-
return fileURLToPath(import.meta.resolve("@fenglimg/fabric-server"));
|
|
349
361
|
}
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
}
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
}
|
|
376
|
-
},
|
|
377
|
-
async run({ args }) {
|
|
378
|
-
const selectedClients = parseClientFilter(args.clients);
|
|
379
|
-
const result = await installMcpClients(process.cwd(), {
|
|
380
|
-
clients: selectedClients === null ? void 0 : Array.from(selectedClients),
|
|
381
|
-
dryRun: args["dry-run"]
|
|
382
|
-
});
|
|
383
|
-
if (result.details.length === 0) {
|
|
384
|
-
writeStderr(t("cli.config.install.no-configs"));
|
|
385
|
-
return;
|
|
386
|
-
}
|
|
387
|
-
for (const detail of result.details) {
|
|
388
|
-
if (detail.action === "skipped") {
|
|
389
|
-
writeStderr(t("cli.config.install.no-config-path", { client: detail.client }));
|
|
390
|
-
continue;
|
|
391
|
-
}
|
|
392
|
-
if (detail.action === "dry-run" && detail.path !== null) {
|
|
393
|
-
writeStderr(t("cli.config.install.dry-run", { client: detail.client, path: detail.path }));
|
|
394
|
-
continue;
|
|
395
|
-
}
|
|
396
|
-
if (detail.path !== null) {
|
|
397
|
-
writeStderr(t("cli.config.install.wrote", { client: detail.client, path: detail.path }));
|
|
362
|
+
|
|
363
|
+
// src/lib/detect-language.ts
|
|
364
|
+
import { existsSync as existsSync2, readdirSync, readFileSync, statSync as statSync2 } from "fs";
|
|
365
|
+
import { join as join4 } from "path";
|
|
366
|
+
function detectExistingLanguage(target) {
|
|
367
|
+
const ZH_CN_RATIO_THRESHOLD = 0.3;
|
|
368
|
+
const samples = [];
|
|
369
|
+
const readmePath = join4(target, "README.md");
|
|
370
|
+
if (existsSync2(readmePath)) {
|
|
371
|
+
try {
|
|
372
|
+
samples.push(readFileSync(readmePath, "utf8"));
|
|
373
|
+
} catch {
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
const docsDir = join4(target, "docs");
|
|
377
|
+
if (existsSync2(docsDir)) {
|
|
378
|
+
try {
|
|
379
|
+
const stat = statSync2(docsDir);
|
|
380
|
+
if (stat.isDirectory()) {
|
|
381
|
+
for (const entry of readdirSync(docsDir, { withFileTypes: true })) {
|
|
382
|
+
if (!entry.isFile()) continue;
|
|
383
|
+
if (!/\.(md|mdx|txt)$/iu.test(entry.name)) continue;
|
|
384
|
+
try {
|
|
385
|
+
samples.push(readFileSync(join4(docsDir, entry.name), "utf8"));
|
|
386
|
+
} catch {
|
|
398
387
|
}
|
|
399
388
|
}
|
|
400
389
|
}
|
|
401
|
-
}
|
|
402
|
-
}
|
|
403
|
-
});
|
|
404
|
-
async function installMcpClients(target, options = {}) {
|
|
405
|
-
const workspaceRoot = resolve3(target);
|
|
406
|
-
const fabricConfig = await loadFabricConfig(workspaceRoot);
|
|
407
|
-
const selectedClients = options.clients === void 0 ? null : new Set(options.clients);
|
|
408
|
-
const serverPath = resolveServerPath(options.localServerPath);
|
|
409
|
-
const writers = resolveClients(workspaceRoot, fabricConfig, { claudeMcpScope: options.claudeMcpScope }).filter(
|
|
410
|
-
(writer) => selectedClients === null ? true : selectedClients.has(writer.clientKind)
|
|
411
|
-
);
|
|
412
|
-
const installed = [];
|
|
413
|
-
const skipped = [];
|
|
414
|
-
const details = [];
|
|
415
|
-
for (const writer of writers) {
|
|
416
|
-
const configPath = await writer.detect(workspaceRoot);
|
|
417
|
-
if (configPath === null) {
|
|
418
|
-
skipped.push(writer.clientKind);
|
|
419
|
-
details.push({ client: writer.clientKind, path: null, action: "skipped" });
|
|
420
|
-
continue;
|
|
390
|
+
} catch {
|
|
421
391
|
}
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
392
|
+
}
|
|
393
|
+
if (samples.length === 0) {
|
|
394
|
+
return "en";
|
|
395
|
+
}
|
|
396
|
+
let cjkCount = 0;
|
|
397
|
+
let asciiLetterCount = 0;
|
|
398
|
+
for (const sample of samples) {
|
|
399
|
+
for (const ch of sample) {
|
|
400
|
+
const code = ch.codePointAt(0) ?? 0;
|
|
401
|
+
if (code >= 19968 && code <= 40959) {
|
|
402
|
+
cjkCount += 1;
|
|
403
|
+
} else if (code >= 65 && code <= 90 || code >= 97 && code <= 122) {
|
|
404
|
+
asciiLetterCount += 1;
|
|
405
|
+
}
|
|
426
406
|
}
|
|
427
|
-
await writer.write(serverPath, workspaceRoot);
|
|
428
|
-
installed.push(writer.clientKind);
|
|
429
|
-
details.push({ client: writer.clientKind, path: configPath, action: "wrote" });
|
|
430
407
|
}
|
|
431
|
-
|
|
408
|
+
const denominator = cjkCount + asciiLetterCount;
|
|
409
|
+
if (denominator === 0) {
|
|
410
|
+
return "en";
|
|
411
|
+
}
|
|
412
|
+
const ratio = cjkCount / denominator;
|
|
413
|
+
return ratio > ZH_CN_RATIO_THRESHOLD ? "zh-CN-hybrid" : "en";
|
|
432
414
|
}
|
|
433
415
|
|
|
434
416
|
// src/scanner/forensic.ts
|
|
435
|
-
import { execFileSync } from "child_process";
|
|
436
|
-
import { existsSync as
|
|
417
|
+
import { execFileSync as execFileSync3 } from "child_process";
|
|
418
|
+
import { existsSync as existsSync3, readdirSync as readdirSync2, readFileSync as readFileSync2, statSync as statSync3 } from "fs";
|
|
437
419
|
import { createRequire } from "module";
|
|
438
|
-
import { basename, extname, isAbsolute, join as
|
|
420
|
+
import { basename, extname, isAbsolute as isAbsolute2, join as join5, posix, relative, resolve as resolve2, sep } from "path";
|
|
439
421
|
import {
|
|
440
422
|
forensicReportSchema
|
|
441
423
|
} from "@fenglimg/fabric-shared";
|
|
424
|
+
|
|
425
|
+
// src/scanner/detector.ts
|
|
426
|
+
import { detectFramework } from "@fenglimg/fabric-shared/node";
|
|
427
|
+
|
|
428
|
+
// src/scanner/forensic.ts
|
|
442
429
|
var require2 = createRequire(import.meta.url);
|
|
443
430
|
var IGNORED_DIRECTORIES = /* @__PURE__ */ new Set([
|
|
444
431
|
".fabric",
|
|
@@ -521,7 +508,7 @@ var parserInitPromise = null;
|
|
|
521
508
|
var languagePromiseByKind = {};
|
|
522
509
|
var parserBundlePromiseByKind = {};
|
|
523
510
|
async function buildForensicReport(targetInput) {
|
|
524
|
-
const target =
|
|
511
|
+
const target = normalizeTarget2(targetInput);
|
|
525
512
|
const framework = detectFramework(target);
|
|
526
513
|
const topology = buildTopology(target);
|
|
527
514
|
const entryPoints = collectEntryPoints(target, topology.files);
|
|
@@ -533,7 +520,7 @@ async function buildForensicReport(targetInput) {
|
|
|
533
520
|
const report = {
|
|
534
521
|
version: "1.0",
|
|
535
522
|
generated_at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
536
|
-
generated_by: `
|
|
523
|
+
generated_by: `fabric-cli@${getCliVersion()}`,
|
|
537
524
|
target,
|
|
538
525
|
project_name: readProjectName(target),
|
|
539
526
|
framework,
|
|
@@ -557,11 +544,11 @@ async function buildForensicReport(targetInput) {
|
|
|
557
544
|
}
|
|
558
545
|
return validation.data;
|
|
559
546
|
}
|
|
560
|
-
function
|
|
561
|
-
return
|
|
547
|
+
function normalizeTarget2(targetInput) {
|
|
548
|
+
return isAbsolute2(targetInput) ? targetInput : resolve2(process.cwd(), targetInput);
|
|
562
549
|
}
|
|
563
550
|
function buildTopology(root) {
|
|
564
|
-
|
|
551
|
+
assertExistingDirectory2(root);
|
|
565
552
|
const byExt = {};
|
|
566
553
|
const keyDirs = /* @__PURE__ */ new Set();
|
|
567
554
|
const files = [];
|
|
@@ -573,8 +560,8 @@ function buildTopology(root) {
|
|
|
573
560
|
if (current === void 0) {
|
|
574
561
|
continue;
|
|
575
562
|
}
|
|
576
|
-
for (const entry of
|
|
577
|
-
const absolutePath =
|
|
563
|
+
for (const entry of readdirSync2(current, { withFileTypes: true })) {
|
|
564
|
+
const absolutePath = join5(current, entry.name);
|
|
578
565
|
const relativePath = toPosixPath(relative(root, absolutePath));
|
|
579
566
|
if (relativePath.length === 0) {
|
|
580
567
|
continue;
|
|
@@ -594,7 +581,7 @@ function buildTopology(root) {
|
|
|
594
581
|
if (!entry.isFile()) {
|
|
595
582
|
continue;
|
|
596
583
|
}
|
|
597
|
-
const stats =
|
|
584
|
+
const stats = statSync3(absolutePath);
|
|
598
585
|
const extension = extname(entry.name) || "[none]";
|
|
599
586
|
byExt[extension] = (byExt[extension] ?? 0) + 1;
|
|
600
587
|
totalFiles += 1;
|
|
@@ -612,8 +599,8 @@ function buildTopology(root) {
|
|
|
612
599
|
files: files.sort((left, right) => left.relativePath.localeCompare(right.relativePath))
|
|
613
600
|
};
|
|
614
601
|
}
|
|
615
|
-
function
|
|
616
|
-
if (!
|
|
602
|
+
function assertExistingDirectory2(target) {
|
|
603
|
+
if (!existsSync3(target) || !statSync3(target).isDirectory()) {
|
|
617
604
|
throw new Error(`Target must be an existing directory: ${target}`);
|
|
618
605
|
}
|
|
619
606
|
}
|
|
@@ -662,7 +649,7 @@ function getEntryPointReason(relativePath) {
|
|
|
662
649
|
async function buildCodeSamples(target, entryPoints, frameworkKind, topology, packageDependencies) {
|
|
663
650
|
const samples = [];
|
|
664
651
|
for (const entryPoint of entryPoints.slice(0, SAMPLE_LIMIT)) {
|
|
665
|
-
const absolutePath =
|
|
652
|
+
const absolutePath = join5(target, ...entryPoint.path.split("/"));
|
|
666
653
|
const sample = readFirstLines(absolutePath, SAMPLE_LINE_LIMIT);
|
|
667
654
|
const patternAnalysis = await inferPatternHint(entryPoint.path, sample.snippet, {
|
|
668
655
|
frameworkKind,
|
|
@@ -682,7 +669,7 @@ async function buildCodeSamples(target, entryPoints, frameworkKind, topology, pa
|
|
|
682
669
|
}
|
|
683
670
|
function readFirstLines(path, lineLimit) {
|
|
684
671
|
try {
|
|
685
|
-
const lines =
|
|
672
|
+
const lines = readFileSync2(path, "utf8").split(/\r?\n/);
|
|
686
673
|
if (lines.at(-1) === "") {
|
|
687
674
|
lines.pop();
|
|
688
675
|
}
|
|
@@ -699,12 +686,12 @@ function readFirstLines(path, lineLimit) {
|
|
|
699
686
|
}
|
|
700
687
|
}
|
|
701
688
|
function readPackageDependencies(target) {
|
|
702
|
-
const packageJsonPath =
|
|
703
|
-
if (!
|
|
689
|
+
const packageJsonPath = join5(target, "package.json");
|
|
690
|
+
if (!existsSync3(packageJsonPath)) {
|
|
704
691
|
return /* @__PURE__ */ new Map();
|
|
705
692
|
}
|
|
706
693
|
try {
|
|
707
|
-
const packageJson = JSON.parse(
|
|
694
|
+
const packageJson = JSON.parse(readFileSync2(packageJsonPath, "utf8"));
|
|
708
695
|
return new Map([
|
|
709
696
|
...Object.entries(packageJson.dependencies ?? {}),
|
|
710
697
|
...Object.entries(packageJson.devDependencies ?? {}),
|
|
@@ -717,7 +704,7 @@ function readPackageDependencies(target) {
|
|
|
717
704
|
}
|
|
718
705
|
function readGitChurnWeight(target, relativePath) {
|
|
719
706
|
try {
|
|
720
|
-
const output =
|
|
707
|
+
const output = execFileSync3("git", ["log", "--follow", "--oneline", "-20", "--", relativePath], {
|
|
721
708
|
cwd: target,
|
|
722
709
|
encoding: "utf8",
|
|
723
710
|
stdio: ["ignore", "pipe", "ignore"],
|
|
@@ -1040,16 +1027,16 @@ function scoreFrameworkConfidence(input) {
|
|
|
1040
1027
|
return input.configCount > 0 || input.packageCount > 0 ? "MEDIUM" : "LOW";
|
|
1041
1028
|
}
|
|
1042
1029
|
function readReadmeInfo(target) {
|
|
1043
|
-
const readmePath =
|
|
1044
|
-
const hasContributing =
|
|
1045
|
-
if (!
|
|
1030
|
+
const readmePath = join5(target, "README.md");
|
|
1031
|
+
const hasContributing = existsSync3(join5(target, "CONTRIBUTING.md"));
|
|
1032
|
+
if (!existsSync3(readmePath)) {
|
|
1046
1033
|
return {
|
|
1047
1034
|
quality: "missing",
|
|
1048
1035
|
line_count: 0,
|
|
1049
1036
|
has_contributing: hasContributing
|
|
1050
1037
|
};
|
|
1051
1038
|
}
|
|
1052
|
-
const readme =
|
|
1039
|
+
const readme = readFileSync2(readmePath, "utf8");
|
|
1053
1040
|
const wordCount = readme.trim().split(/\s+/).filter(Boolean).length;
|
|
1054
1041
|
return {
|
|
1055
1042
|
quality: wordCount >= 200 ? "ok" : "stub",
|
|
@@ -1527,10 +1514,10 @@ function buildSkillRecommendations(frameworkKind, topology, readme) {
|
|
|
1527
1514
|
return recommendations;
|
|
1528
1515
|
}
|
|
1529
1516
|
function readProjectName(target) {
|
|
1530
|
-
const packageJsonPath =
|
|
1531
|
-
if (
|
|
1517
|
+
const packageJsonPath = join5(target, "package.json");
|
|
1518
|
+
if (existsSync3(packageJsonPath)) {
|
|
1532
1519
|
try {
|
|
1533
|
-
const packageJson = JSON.parse(
|
|
1520
|
+
const packageJson = JSON.parse(readFileSync2(packageJsonPath, "utf8"));
|
|
1534
1521
|
if (packageJson.name !== void 0 && packageJson.name.trim().length > 0) {
|
|
1535
1522
|
return packageJson.name;
|
|
1536
1523
|
}
|
|
@@ -1541,7 +1528,7 @@ function readProjectName(target) {
|
|
|
1541
1528
|
return basename(target);
|
|
1542
1529
|
}
|
|
1543
1530
|
function getCliVersion() {
|
|
1544
|
-
return true ? "2.0.
|
|
1531
|
+
return true ? "2.1.0-rc.2" : "unknown";
|
|
1545
1532
|
}
|
|
1546
1533
|
function sortRecord(record) {
|
|
1547
1534
|
return Object.fromEntries(Object.entries(record).sort(([left], [right]) => left.localeCompare(right)));
|
|
@@ -1550,109 +1537,152 @@ function toPosixPath(path) {
|
|
|
1550
1537
|
return path.split(sep).join("/");
|
|
1551
1538
|
}
|
|
1552
1539
|
|
|
1553
|
-
// src/commands/
|
|
1554
|
-
var
|
|
1555
|
-
|
|
1556
|
-
This project uses [Fabric](https://github.com/fenglimg/fabric) for cross-client AI knowledge management.
|
|
1557
|
-
|
|
1558
|
-
Knowledge entries live in \`.fabric/knowledge/\` (team) and \`~/.fabric/knowledge/\` (personal).
|
|
1559
|
-
Run \`fabric doctor\` to verify state.
|
|
1560
|
-
|
|
1561
|
-
See \`.fabric/knowledge/\` for project decisions, pitfalls, guidelines, models, and processes.
|
|
1562
|
-
`;
|
|
1563
|
-
var LOCAL_FABRIC_SERVER_PATH = join5("node_modules", "@fenglimg", "fabric-server", "dist", "index.js");
|
|
1540
|
+
// src/commands/install.ts
|
|
1541
|
+
var LOCAL_FABRIC_SERVER_PATH = join6("node_modules", "@fenglimg", "fabric-server", "dist", "index.js");
|
|
1564
1542
|
var FABRIC_SERVER_PACKAGE = "@fenglimg/fabric-server";
|
|
1565
1543
|
var INIT_WIZARD_GROUP_CANCELLED = /* @__PURE__ */ Symbol("init-wizard-group-cancelled");
|
|
1566
|
-
var
|
|
1544
|
+
var installCommand = defineCommand({
|
|
1567
1545
|
meta: {
|
|
1568
|
-
name: "
|
|
1569
|
-
description: t("cli.
|
|
1546
|
+
name: "install",
|
|
1547
|
+
description: t("cli.install.description")
|
|
1570
1548
|
},
|
|
1571
1549
|
args: {
|
|
1572
|
-
target: {
|
|
1573
|
-
type: "string",
|
|
1574
|
-
description: t("cli.init.args.target.description")
|
|
1575
|
-
},
|
|
1576
1550
|
debug: {
|
|
1577
1551
|
type: "boolean",
|
|
1578
|
-
description: t("cli.
|
|
1552
|
+
description: t("cli.install.args.debug.description"),
|
|
1579
1553
|
default: false
|
|
1580
1554
|
},
|
|
1581
|
-
|
|
1555
|
+
"dry-run": {
|
|
1582
1556
|
type: "boolean",
|
|
1583
|
-
description: t("cli.
|
|
1557
|
+
description: t("cli.install.args.dry-run.description"),
|
|
1584
1558
|
default: false
|
|
1585
1559
|
},
|
|
1560
|
+
target: {
|
|
1561
|
+
type: "string",
|
|
1562
|
+
description: t("cli.install.args.target.description")
|
|
1563
|
+
},
|
|
1586
1564
|
yes: {
|
|
1587
1565
|
type: "boolean",
|
|
1588
|
-
description: t("cli.
|
|
1566
|
+
description: t("cli.install.args.yes.description"),
|
|
1589
1567
|
default: false
|
|
1590
1568
|
},
|
|
1591
|
-
|
|
1569
|
+
"force-skills-only": {
|
|
1592
1570
|
type: "boolean",
|
|
1593
|
-
description: t("cli.
|
|
1571
|
+
description: t("cli.install.args.force-skills-only.description"),
|
|
1594
1572
|
default: false
|
|
1595
1573
|
},
|
|
1596
|
-
|
|
1574
|
+
"force-hooks-only": {
|
|
1597
1575
|
type: "boolean",
|
|
1598
|
-
description: t("cli.
|
|
1576
|
+
description: t("cli.install.args.force-hooks-only.description"),
|
|
1599
1577
|
default: false
|
|
1600
1578
|
},
|
|
1601
|
-
|
|
1602
|
-
type: "boolean",
|
|
1603
|
-
default: true,
|
|
1604
|
-
negativeDescription: t("cli.init.args.no-bootstrap.description")
|
|
1605
|
-
},
|
|
1606
|
-
mcp: {
|
|
1607
|
-
type: "boolean",
|
|
1608
|
-
default: true,
|
|
1609
|
-
negativeDescription: t("cli.init.args.no-mcp.description")
|
|
1610
|
-
},
|
|
1611
|
-
hooks: {
|
|
1612
|
-
type: "boolean",
|
|
1613
|
-
default: true,
|
|
1614
|
-
negativeDescription: t("cli.init.args.no-hooks.description")
|
|
1615
|
-
},
|
|
1616
|
-
interactive: {
|
|
1579
|
+
global: {
|
|
1617
1580
|
type: "boolean",
|
|
1618
|
-
description:
|
|
1619
|
-
default:
|
|
1620
|
-
},
|
|
1621
|
-
"mcp-install": {
|
|
1622
|
-
type: "string",
|
|
1623
|
-
default: "global",
|
|
1624
|
-
description: t("cli.init.mcp.install.prompt")
|
|
1581
|
+
description: "Set up global Fabric (~/.fabric: uid + personal store + config)",
|
|
1582
|
+
default: false
|
|
1625
1583
|
},
|
|
1626
|
-
|
|
1584
|
+
url: {
|
|
1627
1585
|
type: "string",
|
|
1628
|
-
description:
|
|
1586
|
+
description: "With --global: clone + mount this shared store remote"
|
|
1629
1587
|
}
|
|
1630
1588
|
},
|
|
1631
1589
|
async run({ args }) {
|
|
1632
1590
|
await runInitCommand(args);
|
|
1633
1591
|
}
|
|
1634
1592
|
});
|
|
1635
|
-
var
|
|
1593
|
+
var install_default = installCommand;
|
|
1594
|
+
async function runSkillsOnlyRefresh(targetInput) {
|
|
1595
|
+
const target = normalizeTarget3(targetInput);
|
|
1596
|
+
const metaPath = join6(target, ".fabric", "agents.meta.json");
|
|
1597
|
+
if (!existsSync4(metaPath)) {
|
|
1598
|
+
const message = t("cli.install.force-skills-only.uninitialised.message");
|
|
1599
|
+
const hint = t("cli.install.force-skills-only.uninitialised.hint");
|
|
1600
|
+
process.stderr.write(`${message}
|
|
1601
|
+
${hint}
|
|
1602
|
+
`);
|
|
1603
|
+
process.exitCode = 1;
|
|
1604
|
+
return;
|
|
1605
|
+
}
|
|
1606
|
+
console.log(formatInitStageHeader(t("cli.install.force-skills-only.banner")));
|
|
1607
|
+
const results = [];
|
|
1608
|
+
results.push(...await cleanupDeprecatedSkills(target));
|
|
1609
|
+
results.push(...await installFabricArchiveSkill(target));
|
|
1610
|
+
results.push(...await installFabricReviewSkill(target));
|
|
1611
|
+
results.push(...await installFabricImportSkill(target));
|
|
1612
|
+
let written = 0;
|
|
1613
|
+
let skipped = 0;
|
|
1614
|
+
let errors = 0;
|
|
1615
|
+
for (const r of results) {
|
|
1616
|
+
if (r.status === "written") written += 1;
|
|
1617
|
+
else if (r.status === "skipped") skipped += 1;
|
|
1618
|
+
else if (r.status === "error") errors += 1;
|
|
1619
|
+
}
|
|
1620
|
+
console.log(
|
|
1621
|
+
t("cli.install.force-skills-only.summary", {
|
|
1622
|
+
written: String(written),
|
|
1623
|
+
skipped: String(skipped),
|
|
1624
|
+
errors: String(errors)
|
|
1625
|
+
})
|
|
1626
|
+
);
|
|
1627
|
+
if (errors > 0) {
|
|
1628
|
+
for (const r of results) {
|
|
1629
|
+
if (r.status === "error") {
|
|
1630
|
+
process.stderr.write(` ${r.step} ${r.path}: ${r.message ?? "error"}
|
|
1631
|
+
`);
|
|
1632
|
+
}
|
|
1633
|
+
}
|
|
1634
|
+
process.exitCode = 1;
|
|
1635
|
+
}
|
|
1636
|
+
}
|
|
1637
|
+
async function runHooksOnlyRefresh(targetInput) {
|
|
1638
|
+
const target = normalizeTarget3(targetInput);
|
|
1639
|
+
const metaPath = join6(target, ".fabric", "agents.meta.json");
|
|
1640
|
+
if (!existsSync4(metaPath)) {
|
|
1641
|
+
const message = t("cli.install.force-hooks-only.uninitialised.message");
|
|
1642
|
+
const hint = t("cli.install.force-hooks-only.uninitialised.hint");
|
|
1643
|
+
process.stderr.write(`${message}
|
|
1644
|
+
${hint}
|
|
1645
|
+
`);
|
|
1646
|
+
process.exitCode = 1;
|
|
1647
|
+
return;
|
|
1648
|
+
}
|
|
1649
|
+
console.log(formatInitStageHeader(t("cli.install.force-hooks-only.banner")));
|
|
1650
|
+
const result = await installHooks(target);
|
|
1651
|
+
console.log(
|
|
1652
|
+
t("cli.install.force-hooks-only.summary", {
|
|
1653
|
+
written: String(result.installed.length),
|
|
1654
|
+
skipped: String(result.skipped.length),
|
|
1655
|
+
errors: String(result.errors.length)
|
|
1656
|
+
})
|
|
1657
|
+
);
|
|
1658
|
+
if (result.errors.length > 0) {
|
|
1659
|
+
for (const err of result.errors) {
|
|
1660
|
+
process.stderr.write(` ${err}
|
|
1661
|
+
`);
|
|
1662
|
+
}
|
|
1663
|
+
process.exitCode = 1;
|
|
1664
|
+
}
|
|
1665
|
+
}
|
|
1636
1666
|
async function runInitCommand(args) {
|
|
1637
1667
|
const logger = createDebugLogger(args.debug);
|
|
1668
|
+
if (args.global === true) {
|
|
1669
|
+
await runGlobalInstall({ url: args.url });
|
|
1670
|
+
return;
|
|
1671
|
+
}
|
|
1638
1672
|
const resolution = resolveDevMode(args.target, process.cwd());
|
|
1639
|
-
|
|
1640
|
-
|
|
1641
|
-
|
|
1673
|
+
if (args["force-skills-only"] === true) {
|
|
1674
|
+
await runSkillsOnlyRefresh(resolution.target);
|
|
1675
|
+
return;
|
|
1676
|
+
}
|
|
1677
|
+
if (args["force-hooks-only"] === true) {
|
|
1678
|
+
await runHooksOnlyRefresh(resolution.target);
|
|
1679
|
+
return;
|
|
1642
1680
|
}
|
|
1681
|
+
const intent = resolveInitCliIntent(args, resolution.target);
|
|
1643
1682
|
logger(`init target source: ${resolution.source}`);
|
|
1644
1683
|
for (const step of resolution.chain) {
|
|
1645
1684
|
logger(step);
|
|
1646
1685
|
}
|
|
1647
|
-
if (intent.options.planOnly) {
|
|
1648
|
-
writeStderr2(t("cli.init.compat.plan"));
|
|
1649
|
-
}
|
|
1650
|
-
if (args.interactive === false) {
|
|
1651
|
-
writeStderr2(t("cli.init.compat.interactive"));
|
|
1652
|
-
}
|
|
1653
|
-
if (args.bootstrap === false || args.mcp === false || args.hooks === false) {
|
|
1654
|
-
writeStderr2(t("cli.init.compat.legacy-stage-flags"));
|
|
1655
|
-
}
|
|
1656
1686
|
const supports = detectClientSupports(intent.target);
|
|
1657
1687
|
const basePlan = await buildInitExecutionPlan({
|
|
1658
1688
|
target: intent.target,
|
|
@@ -1662,81 +1692,107 @@ async function runInitCommand(args) {
|
|
|
1662
1692
|
interactive: intent.interactiveSummary && !intent.wizardEnabled,
|
|
1663
1693
|
supports
|
|
1664
1694
|
});
|
|
1665
|
-
const plan = intent.wizardEnabled ? await resolveInitExecutionPlanWithWizard(basePlan,
|
|
1695
|
+
const plan = intent.wizardEnabled ? await resolveInitExecutionPlanWithWizard(basePlan, createDefaultInitWizardAdapter()) : basePlan;
|
|
1666
1696
|
if (plan === null) {
|
|
1667
1697
|
process.exitCode = 130;
|
|
1668
1698
|
return;
|
|
1669
1699
|
}
|
|
1670
1700
|
const result = await executeInitExecutionPlan(plan);
|
|
1671
|
-
try {
|
|
1672
|
-
await maybeWriteImportSentinel({
|
|
1673
|
-
target: intent.target,
|
|
1674
|
-
planOnly: intent.options.planOnly === true,
|
|
1675
|
-
wizardEnabled: intent.wizardEnabled,
|
|
1676
|
-
terminalInteractive: intent.interactiveSummary
|
|
1677
|
-
});
|
|
1678
|
-
} catch {
|
|
1679
|
-
}
|
|
1680
1701
|
if (!intent.options.planOnly) {
|
|
1702
|
+
console.log("");
|
|
1703
|
+
console.log(t("cli.install.next-steps"));
|
|
1704
|
+
console.log("");
|
|
1681
1705
|
console.log(paint.muted("More: docs/surfaces.md explains when to use CLI vs Skill vs MCP."));
|
|
1682
1706
|
}
|
|
1683
1707
|
return result;
|
|
1684
1708
|
}
|
|
1685
|
-
|
|
1686
|
-
|
|
1687
|
-
if (
|
|
1688
|
-
|
|
1689
|
-
|
|
1690
|
-
|
|
1691
|
-
|
|
1692
|
-
|
|
1693
|
-
|
|
1694
|
-
|
|
1695
|
-
|
|
1696
|
-
|
|
1697
|
-
|
|
1698
|
-
|
|
1699
|
-
|
|
1700
|
-
|
|
1701
|
-
|
|
1702
|
-
|
|
1703
|
-
|
|
1704
|
-
|
|
1709
|
+
function writeDefaultFabricConfig(fabricDir, targetRoot) {
|
|
1710
|
+
const target = join6(fabricDir, "fabric-config.json");
|
|
1711
|
+
if (existsSync4(target)) return;
|
|
1712
|
+
const detectedLanguage = detectExistingLanguage(targetRoot);
|
|
1713
|
+
const FABRIC_CONFIG_DEFAULTS = {
|
|
1714
|
+
// Scan/import language policy. Fixated at init time by probing
|
|
1715
|
+
// README.md + docs/*.md (CJK ratio > 0.3 → "zh-CN", else "en"). Users
|
|
1716
|
+
// can edit `.fabric/fabric-config.json` to override. See
|
|
1717
|
+
// packages/shared/src/schemas/fabric-config.ts for the enum.
|
|
1718
|
+
fabric_language: detectedLanguage,
|
|
1719
|
+
// fabric-hint Stop hook Signal A (archive): time-branch threshold, hours
|
|
1720
|
+
// since last knowledge_proposed event.
|
|
1721
|
+
archive_hint_hours: 24,
|
|
1722
|
+
// fabric-hint Stop hook cooldown after ANY signal fires, in hours.
|
|
1723
|
+
archive_hint_cooldown_hours: 12,
|
|
1724
|
+
// fabric-hint Stop hook Signal B (review): pending-count cutoff.
|
|
1725
|
+
review_hint_pending_count: 10,
|
|
1726
|
+
// fabric-hint Stop hook Signal B (review): pending-age cutoff in days.
|
|
1727
|
+
review_hint_pending_age_days: 7,
|
|
1728
|
+
// fabric-hint Stop hook Signal D (maintenance): days since last doctor.
|
|
1729
|
+
maintenance_hint_days: 14,
|
|
1730
|
+
// fabric-hint Stop hook Signal D (maintenance): cooldown between
|
|
1731
|
+
// reminders, in days.
|
|
1732
|
+
maintenance_hint_cooldown_days: 7,
|
|
1733
|
+
// fabric-hint Stop hook Signal A (archive): edit-count branch threshold;
|
|
1734
|
+
// PreToolUse fires recorded in .fabric/.cache/edit-counter since the
|
|
1735
|
+
// last knowledge_proposed event.
|
|
1736
|
+
archive_edit_threshold: 20,
|
|
1737
|
+
// fabric-hint Stop hook Signal C (import) + doctor lint #22: canonical
|
|
1738
|
+
// knowledge node count below this value flags an underseeded workspace.
|
|
1739
|
+
underseed_node_threshold: 10,
|
|
1740
|
+
// rc.9+ (skill-contract-fix B1): fabric-import first-run git-history
|
|
1741
|
+
// window in months. Default 60 captures the bulk of a mature repo's
|
|
1742
|
+
// signal in one pass; lower to 12-24 for fresh / small repos.
|
|
1743
|
+
import_window_first_run_months: 60,
|
|
1744
|
+
// rc.9+ (skill-contract-fix B1): fabric-import rerun window in months.
|
|
1745
|
+
// Default 2; raise to 6 if the workspace pauses imports for long stretches.
|
|
1746
|
+
import_window_rerun_months: 2,
|
|
1747
|
+
// rc.9+ (skill-contract-fix B1): hard cap on pending entries produced
|
|
1748
|
+
// per fabric-import invocation. Default 10 matches one-sitting triage.
|
|
1749
|
+
import_max_pending_per_run: 10,
|
|
1750
|
+
// rc.9+ (skill-contract-fix B1): hard cap on commits scanned per
|
|
1751
|
+
// fabric-import invocation. Default 500 covers ~2 months of typical churn.
|
|
1752
|
+
import_max_commits_scan: 500,
|
|
1753
|
+
// rc.9+ (skill-contract-fix B1): canonical-node count above which
|
|
1754
|
+
// fabric-import suggests review over importing more. Default 50.
|
|
1755
|
+
import_skip_canonical_threshold: 50,
|
|
1756
|
+
// rc.9+ (skill-contract-fix B1): max candidates per fabric-archive batch.
|
|
1757
|
+
// Default 8 keeps each batch reviewable in one sitting.
|
|
1758
|
+
archive_max_candidates_per_batch: 8,
|
|
1759
|
+
// rc.9+ (skill-contract-fix B1): max recently-touched paths in
|
|
1760
|
+
// fabric-archive's relevance digest. Default 20.
|
|
1761
|
+
archive_max_recent_paths: 20,
|
|
1762
|
+
// rc.9+ (skill-contract-fix B1): max prior fabric-archive sessions
|
|
1763
|
+
// summarised in the digest the skill loads on start. Default 10.
|
|
1764
|
+
archive_digest_max_sessions: 10,
|
|
1765
|
+
// rc.9+ (skill-contract-fix B1): max review results per topic cluster
|
|
1766
|
+
// in fabric-review. Default 8.
|
|
1767
|
+
review_topic_result_cap: 8,
|
|
1768
|
+
// rc.9+ (skill-contract-fix B1): age (days) above which a pending entry
|
|
1769
|
+
// is considered stale by fabric-review. Default 14.
|
|
1770
|
+
review_stale_pending_days: 14
|
|
1771
|
+
};
|
|
1772
|
+
mkdirSync2(fabricDir, { recursive: true });
|
|
1773
|
+
writeFileSync(target, JSON.stringify(FABRIC_CONFIG_DEFAULTS, null, 2) + "\n", "utf8");
|
|
1774
|
+
log.info(
|
|
1775
|
+
`Detected and fixated fabric_language = ${detectedLanguage}; edit ${target} to override.`
|
|
1776
|
+
);
|
|
1705
1777
|
}
|
|
1706
1778
|
function resolveInitCliIntent(args, targetInput) {
|
|
1707
|
-
const target =
|
|
1708
|
-
const mcpInstallMode =
|
|
1709
|
-
const claudeMcpScope =
|
|
1779
|
+
const target = normalizeTarget3(targetInput);
|
|
1780
|
+
const mcpInstallMode = "global";
|
|
1781
|
+
const claudeMcpScope = "project";
|
|
1710
1782
|
const terminalInteractive = isInteractiveInit();
|
|
1711
|
-
const planOnly = args
|
|
1712
|
-
const reapply = args.reapply === true;
|
|
1783
|
+
const planOnly = args["dry-run"] === true;
|
|
1713
1784
|
const options = {
|
|
1714
|
-
|
|
1715
|
-
skipBootstrap: args.bootstrap === false ? true : args.skipBootstrap,
|
|
1716
|
-
skipMcp: args.mcp === false ? true : args.skipMcp,
|
|
1717
|
-
skipHooks: args.hooks === false ? true : args.skipHooks,
|
|
1718
|
-
planOnly,
|
|
1719
|
-
reapply
|
|
1785
|
+
planOnly
|
|
1720
1786
|
};
|
|
1721
1787
|
return {
|
|
1722
1788
|
target,
|
|
1723
1789
|
options,
|
|
1724
1790
|
mcpInstallMode,
|
|
1725
1791
|
claudeMcpScope,
|
|
1726
|
-
interactiveSummary:
|
|
1792
|
+
interactiveSummary: terminalInteractive,
|
|
1727
1793
|
wizardEnabled: shouldUseInitWizard(args, terminalInteractive) && !planOnly
|
|
1728
1794
|
};
|
|
1729
1795
|
}
|
|
1730
|
-
function resolveClaudeMcpScope(raw) {
|
|
1731
|
-
if (raw === void 0 || raw === "project") {
|
|
1732
|
-
return "project";
|
|
1733
|
-
}
|
|
1734
|
-
if (raw === "user") {
|
|
1735
|
-
return "user";
|
|
1736
|
-
}
|
|
1737
|
-
writeStderr2(t("cli.init.mcp.scope.invalid", { value: raw }));
|
|
1738
|
-
return "project";
|
|
1739
|
-
}
|
|
1740
1796
|
async function buildInitExecutionPlan(input) {
|
|
1741
1797
|
const options = input.options ?? {};
|
|
1742
1798
|
const scaffold = await buildInitFabricPlan(input.target, options);
|
|
@@ -1773,17 +1829,17 @@ async function buildInitExecutionPlan(input) {
|
|
|
1773
1829
|
};
|
|
1774
1830
|
}
|
|
1775
1831
|
async function executeInitExecutionPlan(plan) {
|
|
1776
|
-
if (plan.options.force) {
|
|
1777
|
-
writeStderr2(t("cli.init.force.warning", { path: plan.target }));
|
|
1778
|
-
}
|
|
1779
|
-
if (plan.options.reapply && !plan.options.planOnly && !plan.interactive) {
|
|
1780
|
-
writeStderr2(formatInitModeBanner(plan.options));
|
|
1781
|
-
}
|
|
1782
1832
|
if (plan.interactive) {
|
|
1783
1833
|
printInitPlanSummary(plan.target, plan.options, plan.mcpInstallMode, plan.supports);
|
|
1784
1834
|
}
|
|
1835
|
+
const scaffoldStates = [
|
|
1836
|
+
{ path: plan.scaffold.metaPath, state: plan.scaffold.metaState },
|
|
1837
|
+
{ path: plan.scaffold.eventsPath, state: plan.scaffold.eventsState },
|
|
1838
|
+
{ path: plan.scaffold.forensicPath, state: plan.scaffold.forensicState }
|
|
1839
|
+
];
|
|
1785
1840
|
if (plan.options.planOnly) {
|
|
1786
1841
|
printInitPlanPreview(plan);
|
|
1842
|
+
printInitDiffStateTable(scaffoldStates);
|
|
1787
1843
|
return {
|
|
1788
1844
|
plan,
|
|
1789
1845
|
created: buildPlanOnlyScaffoldResult(plan.scaffold),
|
|
@@ -1791,6 +1847,17 @@ async function executeInitExecutionPlan(plan) {
|
|
|
1791
1847
|
finalSupports: plan.supports
|
|
1792
1848
|
};
|
|
1793
1849
|
}
|
|
1850
|
+
if (existsSync4(plan.scaffold.fabricDir) && !statSync4(plan.scaffold.fabricDir).isDirectory()) {
|
|
1851
|
+
throw new Error(
|
|
1852
|
+
t("cli.install.diff.drift-abort", { path: plan.scaffold.fabricDir })
|
|
1853
|
+
);
|
|
1854
|
+
}
|
|
1855
|
+
const drifted = scaffoldStates.find(
|
|
1856
|
+
(entry) => entry.state === "drifted" || entry.state === "user-modified"
|
|
1857
|
+
);
|
|
1858
|
+
if (drifted !== void 0) {
|
|
1859
|
+
throw new Error(t("cli.install.diff.drift-abort", { path: drifted.path }));
|
|
1860
|
+
}
|
|
1794
1861
|
let created = null;
|
|
1795
1862
|
const stageResults = [];
|
|
1796
1863
|
let finalSupports = plan.supports;
|
|
@@ -1815,6 +1882,11 @@ async function executeInitExecutionPlan(plan) {
|
|
|
1815
1882
|
exhaustiveInitExecutionStep(step);
|
|
1816
1883
|
}
|
|
1817
1884
|
}
|
|
1885
|
+
if (scaffoldStates.every((entry) => entry.state === "present-canonical")) {
|
|
1886
|
+
console.log(
|
|
1887
|
+
t("cli.install.diff.canonical", { count: String(scaffoldStates.length) })
|
|
1888
|
+
);
|
|
1889
|
+
}
|
|
1818
1890
|
return {
|
|
1819
1891
|
plan,
|
|
1820
1892
|
created: created ?? unreachableInitScaffold(),
|
|
@@ -1824,23 +1896,26 @@ async function executeInitExecutionPlan(plan) {
|
|
|
1824
1896
|
}
|
|
1825
1897
|
var KNOWLEDGE_SUBDIRS = ["decisions", "pitfalls", "guidelines", "models", "processes", "pending"];
|
|
1826
1898
|
function resolvePersonalFabricRoot() {
|
|
1827
|
-
return process.env.FABRIC_HOME ??
|
|
1899
|
+
return process.env.FABRIC_HOME ?? homedir();
|
|
1828
1900
|
}
|
|
1829
1901
|
async function buildInitFabricPlan(target, options) {
|
|
1830
|
-
|
|
1831
|
-
const fabricDir =
|
|
1832
|
-
const agentsMdPath =
|
|
1833
|
-
const agentsMdAction =
|
|
1834
|
-
const knowledgeDir =
|
|
1835
|
-
const personalKnowledgeDir =
|
|
1836
|
-
const forensicPath =
|
|
1837
|
-
const eventsPath =
|
|
1838
|
-
const metaPath =
|
|
1902
|
+
assertExistingDirectory3(target);
|
|
1903
|
+
const fabricDir = join6(target, ".fabric");
|
|
1904
|
+
const agentsMdPath = join6(target, "AGENTS.md");
|
|
1905
|
+
const agentsMdAction = existsSync4(agentsMdPath) ? "preserved" : "created";
|
|
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");
|
|
1839
1911
|
const replaceFabricDir = shouldReplaceWritableDirectory(fabricDir, options);
|
|
1840
|
-
const knowledgeDirAction =
|
|
1841
|
-
const
|
|
1842
|
-
const
|
|
1843
|
-
const
|
|
1912
|
+
const knowledgeDirAction = existsSync4(knowledgeDir) ? "overwritten" : "created";
|
|
1913
|
+
const metaClassification = classifyFreshPath(metaPath, "structural");
|
|
1914
|
+
const eventsClassification = classifyFreshPath(eventsPath, "presence");
|
|
1915
|
+
const forensicClassification = classifyFreshPath(forensicPath, "always-rewrite");
|
|
1916
|
+
const metaAction = diffStateToWriteAction(metaClassification.state);
|
|
1917
|
+
const eventsAction = diffStateToWriteAction(eventsClassification.state);
|
|
1918
|
+
const forensicAction = diffStateToWriteAction(forensicClassification.state);
|
|
1844
1919
|
const forensicReport = await buildForensicReport(target);
|
|
1845
1920
|
const meta = createInitialMeta();
|
|
1846
1921
|
return {
|
|
@@ -1860,60 +1935,63 @@ async function buildInitFabricPlan(target, options) {
|
|
|
1860
1935
|
eventsAction,
|
|
1861
1936
|
forensicPath,
|
|
1862
1937
|
forensicAction,
|
|
1863
|
-
forensicReport
|
|
1938
|
+
forensicReport,
|
|
1939
|
+
metaState: metaClassification.state,
|
|
1940
|
+
eventsState: eventsClassification.state,
|
|
1941
|
+
forensicState: forensicClassification.state
|
|
1864
1942
|
};
|
|
1865
1943
|
}
|
|
1866
1944
|
async function executeInitFabricPlan(plan) {
|
|
1867
|
-
const isReapply = plan.options?.reapply === true;
|
|
1868
1945
|
if (plan.replaceFabricDir) {
|
|
1869
|
-
|
|
1870
|
-
}
|
|
1871
|
-
mkdirSync(plan.fabricDir, { recursive: true });
|
|
1872
|
-
if (plan.agentsMdAction === "created" && !existsSync6(plan.agentsMdPath)) {
|
|
1873
|
-
await atomicWriteText2(plan.agentsMdPath, AGENTS_MD_DEFAULT_CONTENT);
|
|
1946
|
+
rmSync2(plan.fabricDir, { force: true });
|
|
1874
1947
|
}
|
|
1875
|
-
|
|
1948
|
+
mkdirSync2(plan.fabricDir, { recursive: true });
|
|
1949
|
+
writeDefaultFabricConfig(plan.fabricDir, plan.target);
|
|
1950
|
+
mkdirSync2(plan.knowledgeDir, { recursive: true });
|
|
1876
1951
|
for (const sub of KNOWLEDGE_SUBDIRS) {
|
|
1877
|
-
const teamSubDir =
|
|
1878
|
-
|
|
1879
|
-
const teamGitkeep =
|
|
1880
|
-
if (!
|
|
1952
|
+
const teamSubDir = join6(plan.knowledgeDir, sub);
|
|
1953
|
+
mkdirSync2(teamSubDir, { recursive: true });
|
|
1954
|
+
const teamGitkeep = join6(teamSubDir, ".gitkeep");
|
|
1955
|
+
if (!existsSync4(teamGitkeep)) {
|
|
1881
1956
|
writeFileSync(teamGitkeep, "", "utf8");
|
|
1882
1957
|
}
|
|
1883
1958
|
}
|
|
1884
1959
|
try {
|
|
1885
|
-
|
|
1960
|
+
mkdirSync2(plan.personalKnowledgeDir, { recursive: true });
|
|
1886
1961
|
for (const sub of KNOWLEDGE_SUBDIRS) {
|
|
1887
|
-
|
|
1962
|
+
mkdirSync2(join6(plan.personalKnowledgeDir, sub), { recursive: true });
|
|
1888
1963
|
}
|
|
1889
1964
|
} catch {
|
|
1890
1965
|
}
|
|
1891
|
-
|
|
1892
|
-
|
|
1893
|
-
|
|
1894
|
-
|
|
1895
|
-
|
|
1896
|
-
writeFileSync(plan.eventsPath, "", "utf8");
|
|
1897
|
-
}
|
|
1898
|
-
} else {
|
|
1966
|
+
if (plan.metaState === "missing") {
|
|
1967
|
+
preparePlannedPath(plan.metaPath, plan.metaAction);
|
|
1968
|
+
await atomicWriteJson(plan.metaPath, plan.meta);
|
|
1969
|
+
}
|
|
1970
|
+
if (plan.eventsState === "missing") {
|
|
1899
1971
|
preparePlannedPath(plan.eventsPath, plan.eventsAction);
|
|
1972
|
+
mkdirSync2(dirname(plan.eventsPath), { recursive: true });
|
|
1900
1973
|
writeFileSync(plan.eventsPath, "", "utf8");
|
|
1901
1974
|
}
|
|
1902
1975
|
preparePlannedPath(plan.forensicPath, plan.forensicAction);
|
|
1903
1976
|
await atomicWriteJson(plan.forensicPath, plan.forensicReport);
|
|
1904
|
-
if (
|
|
1905
|
-
|
|
1906
|
-
|
|
1907
|
-
|
|
1908
|
-
|
|
1909
|
-
|
|
1910
|
-
|
|
1977
|
+
if (existsSync4(plan.eventsPath)) {
|
|
1978
|
+
const applied = [];
|
|
1979
|
+
const canonical = [];
|
|
1980
|
+
const drifted = [];
|
|
1981
|
+
for (const entry of [
|
|
1982
|
+
{ path: plan.metaPath, state: plan.metaState },
|
|
1983
|
+
{ path: plan.eventsPath, state: plan.eventsState },
|
|
1984
|
+
{ path: plan.forensicPath, state: plan.forensicState }
|
|
1985
|
+
]) {
|
|
1986
|
+
if (entry.state === "missing") {
|
|
1987
|
+
applied.push(entry.path);
|
|
1988
|
+
} else if (entry.state === "present-canonical") {
|
|
1989
|
+
canonical.push(entry.path);
|
|
1990
|
+
} else {
|
|
1991
|
+
drifted.push(entry.path);
|
|
1992
|
+
}
|
|
1911
1993
|
}
|
|
1912
|
-
|
|
1913
|
-
if (isReapply) {
|
|
1914
|
-
appendReapplyLedgerEvent(plan.eventsPath, {
|
|
1915
|
-
preserved_ledger: true
|
|
1916
|
-
});
|
|
1994
|
+
appendInstallDiffLedgerEvent(plan.eventsPath, { applied, canonical, drifted });
|
|
1917
1995
|
}
|
|
1918
1996
|
return {
|
|
1919
1997
|
agentsMdPath: plan.agentsMdPath,
|
|
@@ -1933,16 +2011,16 @@ async function initFabric(target, options) {
|
|
|
1933
2011
|
return await executeInitFabricPlan(await buildInitFabricPlan(target, options));
|
|
1934
2012
|
}
|
|
1935
2013
|
function shouldUseInitWizard(args, terminalInteractive = isInteractiveInit()) {
|
|
1936
|
-
return terminalInteractive && args.
|
|
2014
|
+
return terminalInteractive && args.yes !== true;
|
|
1937
2015
|
}
|
|
1938
|
-
async function resolveInitExecutionPlanWithWizard(basePlan,
|
|
2016
|
+
async function resolveInitExecutionPlanWithWizard(basePlan, wizardAdapter) {
|
|
1939
2017
|
const selection = await wizardAdapter.run({
|
|
1940
2018
|
target: basePlan.target,
|
|
1941
2019
|
options: basePlan.options,
|
|
1942
2020
|
supports: basePlan.supports,
|
|
1943
2021
|
mcpInstallMode: basePlan.mcpInstallMode,
|
|
1944
2022
|
claudeMcpScope: basePlan.claudeMcpScope,
|
|
1945
|
-
lockedStages:
|
|
2023
|
+
lockedStages: []
|
|
1946
2024
|
});
|
|
1947
2025
|
if (selection === null) {
|
|
1948
2026
|
return null;
|
|
@@ -1980,27 +2058,36 @@ function printInitScaffoldResult(created) {
|
|
|
1980
2058
|
function printInitPostSetup(plan, stageResults, finalSupports) {
|
|
1981
2059
|
if (shouldPrintHooksNextStep(plan.options, stageResults)) {
|
|
1982
2060
|
console.log(
|
|
1983
|
-
t("cli.
|
|
2061
|
+
t("cli.install.next-step", {
|
|
1984
2062
|
label: nextLabel(),
|
|
1985
|
-
message: paint.muted(t("cli.
|
|
2063
|
+
message: paint.muted(t("cli.install.next-step.message"))
|
|
1986
2064
|
})
|
|
1987
2065
|
);
|
|
1988
2066
|
}
|
|
1989
2067
|
console.log(
|
|
1990
|
-
t("cli.
|
|
2068
|
+
t("cli.install.reason-message", {
|
|
1991
2069
|
label: reasonLabel(),
|
|
1992
2070
|
message: paint.muted(formatInitReasonMessage(finalSupports))
|
|
1993
2071
|
})
|
|
1994
2072
|
);
|
|
1995
2073
|
printInitStageSummary(stageResults);
|
|
1996
2074
|
printInitCapabilitySummary(finalSupports, stageResults, plan.options);
|
|
2075
|
+
const fabricLanguage = readFabricLanguagePreference(plan.target);
|
|
2076
|
+
console.log(
|
|
2077
|
+
paint.muted(t("cli.install.language_preference_hint", { value: fabricLanguage }))
|
|
2078
|
+
);
|
|
2079
|
+
}
|
|
2080
|
+
function printInitDiffStateTable(entries) {
|
|
2081
|
+
for (const entry of entries) {
|
|
2082
|
+
console.log(` ${formatDiffFileState(entry.state)} ${entry.path}`);
|
|
2083
|
+
}
|
|
1997
2084
|
}
|
|
1998
2085
|
function printInitPlanPreview(plan) {
|
|
1999
|
-
console.log(t("cli.
|
|
2086
|
+
console.log(t("cli.install.plan.preview-title"));
|
|
2000
2087
|
printInitPlanSummary(plan.target, plan.options, plan.mcpInstallMode, plan.supports);
|
|
2001
2088
|
console.log(
|
|
2002
|
-
t("cli.
|
|
2003
|
-
mode:
|
|
2089
|
+
t("cli.install.plan.preview-result", {
|
|
2090
|
+
mode: t("cli.install.mode.default"),
|
|
2004
2091
|
bootstrap: yesNoLabel(!plan.options.skipBootstrap),
|
|
2005
2092
|
mcp: yesNoLabel(!plan.options.skipMcp),
|
|
2006
2093
|
hooks: yesNoLabel(!plan.options.skipHooks)
|
|
@@ -2030,27 +2117,32 @@ async function executeInitStagePlan(plan, stageName) {
|
|
|
2030
2117
|
if (stage.skipped) {
|
|
2031
2118
|
return { name: stageName, disposition: "skipped" };
|
|
2032
2119
|
}
|
|
2033
|
-
console.log(formatInitStageHeader(t(`cli.
|
|
2120
|
+
console.log(formatInitStageHeader(t(`cli.install.stages.${stageName}`)));
|
|
2034
2121
|
try {
|
|
2035
2122
|
switch (stage.name) {
|
|
2036
2123
|
case "bootstrap": {
|
|
2037
2124
|
const installResults = [];
|
|
2125
|
+
installResults.push(...await runBestEffort("skill-deprecated-cleanup", () => cleanupDeprecatedSkills(plan.target)));
|
|
2038
2126
|
installResults.push(...await runBestEffort("skill-install", () => installFabricArchiveSkill(plan.target)));
|
|
2039
2127
|
installResults.push(...await runBestEffort("skill-review-install", () => installFabricReviewSkill(plan.target)));
|
|
2040
2128
|
installResults.push(...await runBestEffort("skill-import-install", () => installFabricImportSkill(plan.target)));
|
|
2041
2129
|
installResults.push(...await runBestEffort("hook-script", () => installArchiveHintHook(plan.target)));
|
|
2042
2130
|
installResults.push(...await runBestEffort("hook-broad-script", () => installKnowledgeHintBroadHook(plan.target)));
|
|
2043
2131
|
installResults.push(...await runBestEffort("hook-narrow-script", () => installKnowledgeHintNarrowHook(plan.target)));
|
|
2132
|
+
installResults.push(...await runBestEffort("hook-lib", () => installHookLibs(plan.target)));
|
|
2044
2133
|
installResults.push(await runBestEffortSingle("claude-hook-config", () => mergeClaudeCodeHookConfig(plan.target)));
|
|
2045
2134
|
installResults.push(await runBestEffortSingle("codex-hook-config", () => mergeCodexHookConfig(plan.target)));
|
|
2046
2135
|
installResults.push(await runBestEffortSingle("cursor-hook-config", () => mergeCursorHookConfig(plan.target)));
|
|
2047
|
-
installResults.push(
|
|
2136
|
+
installResults.push(await runBestEffortSingle("bootstrap-snapshot", () => writeFabricAgentsSnapshot(plan.target)));
|
|
2137
|
+
installResults.push(await runBestEffortSingle("bootstrap-claude", () => writeClaudeBootstrapThinShell(plan.target)));
|
|
2138
|
+
installResults.push(await runBestEffortSingle("bootstrap-codex", () => writeCodexBootstrapManagedBlock(plan.target)));
|
|
2139
|
+
installResults.push(await runBestEffortSingle("bootstrap-cursor", () => writeCursorBootstrapManagedBlock(plan.target)));
|
|
2048
2140
|
const installedCount = installResults.filter((r) => r.status === "written").length;
|
|
2049
2141
|
const skippedCount = installResults.filter((r) => r.status === "skipped").length;
|
|
2050
2142
|
const errorCount = installResults.filter((r) => r.status === "error").length;
|
|
2051
2143
|
for (const result of installResults) {
|
|
2052
2144
|
if (result.status === "error") {
|
|
2053
|
-
|
|
2145
|
+
writeStderr(`bootstrap ${result.step} ${result.path}: ${result.message ?? "unknown error"}`);
|
|
2054
2146
|
}
|
|
2055
2147
|
}
|
|
2056
2148
|
const note2 = errorCount > 0 ? `errors=${errorCount}` : void 0;
|
|
@@ -2060,15 +2152,14 @@ async function executeInitStagePlan(plan, stageName) {
|
|
|
2060
2152
|
case "mcp": {
|
|
2061
2153
|
if (stage.installMode === "local") {
|
|
2062
2154
|
const manager = stage.packageManager ?? detectPackageManager(plan.target);
|
|
2063
|
-
|
|
2064
|
-
|
|
2155
|
+
writeStderr(t("cli.install.mcp.install.local"));
|
|
2156
|
+
writeStderr(t("cli.install.mcp.local.installing", { manager }));
|
|
2065
2157
|
installLocalFabricServer(plan.target, manager);
|
|
2066
|
-
|
|
2158
|
+
writeStderr(t("cli.install.mcp.local.installed"));
|
|
2067
2159
|
} else {
|
|
2068
|
-
|
|
2160
|
+
writeStderr(t("cli.install.mcp.install.global"));
|
|
2069
2161
|
}
|
|
2070
2162
|
const result = await installMcpClients(plan.target, {
|
|
2071
|
-
force: plan.options.force,
|
|
2072
2163
|
localServerPath: stage.localServerPath,
|
|
2073
2164
|
claudeMcpScope: stage.claudeMcpScope
|
|
2074
2165
|
});
|
|
@@ -2080,7 +2171,7 @@ async function executeInitStagePlan(plan, stageName) {
|
|
|
2080
2171
|
return { name: "mcp", disposition: "ran" };
|
|
2081
2172
|
}
|
|
2082
2173
|
case "hooks": {
|
|
2083
|
-
const result = await installHooks(plan.target
|
|
2174
|
+
const result = await installHooks(plan.target);
|
|
2084
2175
|
console.log(formatInitStageResult("hooks", "completed", result.installed.length, result.skipped.length));
|
|
2085
2176
|
return { name: "hooks", disposition: "ran" };
|
|
2086
2177
|
}
|
|
@@ -2088,93 +2179,129 @@ async function executeInitStagePlan(plan, stageName) {
|
|
|
2088
2179
|
return exhaustiveInitStagePlan(stage);
|
|
2089
2180
|
}
|
|
2090
2181
|
} catch (error) {
|
|
2091
|
-
|
|
2182
|
+
writeStderr(formatInitStageFailure(stageName, error));
|
|
2092
2183
|
return { name: stageName, disposition: "failed" };
|
|
2093
2184
|
}
|
|
2094
2185
|
}
|
|
2095
|
-
function shouldReplaceWritableDirectory(path,
|
|
2096
|
-
if (!
|
|
2186
|
+
function shouldReplaceWritableDirectory(path, _options) {
|
|
2187
|
+
if (!existsSync4(path)) {
|
|
2097
2188
|
return false;
|
|
2098
2189
|
}
|
|
2099
|
-
if (
|
|
2190
|
+
if (statSync4(path).isDirectory()) {
|
|
2100
2191
|
return false;
|
|
2101
2192
|
}
|
|
2102
|
-
|
|
2103
|
-
throw new Error(t("cli.init.errors.abort-existing", { path }));
|
|
2104
|
-
}
|
|
2105
|
-
return true;
|
|
2193
|
+
return false;
|
|
2106
2194
|
}
|
|
2107
|
-
function
|
|
2108
|
-
if (!
|
|
2109
|
-
return "
|
|
2195
|
+
function classifyFreshPath(path, strategy) {
|
|
2196
|
+
if (!existsSync4(path)) {
|
|
2197
|
+
return { path, state: "missing" };
|
|
2110
2198
|
}
|
|
2111
|
-
|
|
2112
|
-
|
|
2199
|
+
let stat;
|
|
2200
|
+
try {
|
|
2201
|
+
stat = statSync4(path);
|
|
2202
|
+
} catch (error) {
|
|
2203
|
+
return {
|
|
2204
|
+
path,
|
|
2205
|
+
state: "user-modified",
|
|
2206
|
+
reason: error instanceof Error ? error.message : String(error)
|
|
2207
|
+
};
|
|
2208
|
+
}
|
|
2209
|
+
if (!stat.isFile()) {
|
|
2210
|
+
return { path, state: "user-modified", reason: "expected a file" };
|
|
2211
|
+
}
|
|
2212
|
+
if (strategy === "presence" || strategy === "always-rewrite") {
|
|
2213
|
+
return { path, state: "present-canonical" };
|
|
2214
|
+
}
|
|
2215
|
+
try {
|
|
2216
|
+
const raw = readFileSync3(path, "utf8");
|
|
2217
|
+
const parsed = JSON.parse(raw);
|
|
2218
|
+
if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
2219
|
+
return { path, state: "user-modified", reason: "not a JSON object" };
|
|
2220
|
+
}
|
|
2221
|
+
const record = parsed;
|
|
2222
|
+
const hasRevision = typeof record["revision"] === "string";
|
|
2223
|
+
const hasNodes = record["nodes"] !== void 0 && record["nodes"] !== null && typeof record["nodes"] === "object" && !Array.isArray(record["nodes"]);
|
|
2224
|
+
const hasCounters = record["counters"] !== void 0 && record["counters"] !== null && typeof record["counters"] === "object" && !Array.isArray(record["counters"]);
|
|
2225
|
+
if (!hasRevision || !hasNodes || !hasCounters) {
|
|
2226
|
+
return { path, state: "drifted", reason: "missing required AgentsMeta fields" };
|
|
2227
|
+
}
|
|
2228
|
+
return { path, state: "present-canonical" };
|
|
2229
|
+
} catch (error) {
|
|
2230
|
+
return {
|
|
2231
|
+
path,
|
|
2232
|
+
state: "user-modified",
|
|
2233
|
+
reason: error instanceof Error ? error.message : String(error)
|
|
2234
|
+
};
|
|
2113
2235
|
}
|
|
2114
|
-
|
|
2236
|
+
}
|
|
2237
|
+
function diffStateToWriteAction(_state) {
|
|
2238
|
+
return "created";
|
|
2239
|
+
}
|
|
2240
|
+
function formatDiffFileState(state) {
|
|
2241
|
+
return t(`cli.install.diff.state.${state}`);
|
|
2115
2242
|
}
|
|
2116
2243
|
function preparePlannedPath(path, action) {
|
|
2117
|
-
|
|
2118
|
-
if (action === "overwritten" &&
|
|
2119
|
-
|
|
2244
|
+
mkdirSync2(dirname(path), { recursive: true });
|
|
2245
|
+
if (action === "overwritten" && existsSync4(path)) {
|
|
2246
|
+
rmSync2(path, { recursive: true, force: true });
|
|
2120
2247
|
}
|
|
2121
2248
|
}
|
|
2122
2249
|
function createDefaultInitWizardAdapter() {
|
|
2123
2250
|
return {
|
|
2124
2251
|
async run(context) {
|
|
2125
|
-
intro(t("cli.
|
|
2252
|
+
intro(t("cli.install.wizard.intro"));
|
|
2126
2253
|
note(
|
|
2127
|
-
t("cli.
|
|
2254
|
+
t("cli.install.wizard.overview.body", {
|
|
2128
2255
|
target: context.target,
|
|
2129
2256
|
mode: formatInitModeBadge(context.options)
|
|
2130
2257
|
}),
|
|
2131
|
-
t("cli.
|
|
2258
|
+
t("cli.install.wizard.overview.title")
|
|
2132
2259
|
);
|
|
2133
2260
|
printInitPlanSummary(context.target, context.options, context.mcpInstallMode, context.supports);
|
|
2134
|
-
log.step(t("cli.
|
|
2261
|
+
log.step(t("cli.install.wizard.step.target"));
|
|
2135
2262
|
const continueWithTarget = await confirm({
|
|
2136
|
-
message: t("cli.
|
|
2263
|
+
message: t("cli.install.wizard.target.confirm", { target: context.target }),
|
|
2137
2264
|
initialValue: true
|
|
2138
2265
|
});
|
|
2139
2266
|
if (isCancel(continueWithTarget) || !continueWithTarget) {
|
|
2140
2267
|
emitInitWizardCancellation();
|
|
2141
2268
|
return null;
|
|
2142
2269
|
}
|
|
2143
|
-
log.step(t("cli.
|
|
2270
|
+
log.step(t("cli.install.wizard.step.plan"));
|
|
2144
2271
|
let groupedSelection;
|
|
2145
2272
|
try {
|
|
2146
2273
|
groupedSelection = await group(
|
|
2147
2274
|
{
|
|
2148
2275
|
bootstrap: async () => context.lockedStages.includes("bootstrap") ? false : confirmInGroup({
|
|
2149
|
-
message: t("cli.
|
|
2276
|
+
message: t("cli.install.wizard.stage.bootstrap", {
|
|
2150
2277
|
defaultValue: formatPromptDefault(!context.options.skipBootstrap)
|
|
2151
2278
|
}),
|
|
2152
2279
|
initialValue: !context.options.skipBootstrap
|
|
2153
2280
|
}),
|
|
2154
2281
|
mcp: async () => context.lockedStages.includes("mcp") ? false : confirmInGroup({
|
|
2155
|
-
message: t("cli.
|
|
2282
|
+
message: t("cli.install.wizard.stage.mcp", {
|
|
2156
2283
|
defaultValue: formatPromptDefault(!context.options.skipMcp)
|
|
2157
2284
|
}),
|
|
2158
2285
|
initialValue: !context.options.skipMcp
|
|
2159
2286
|
}),
|
|
2160
2287
|
mcpInstallMode: async ({ results }) => results.mcp ? selectMcpInstallModeInGroup({
|
|
2161
|
-
message: t("cli.
|
|
2288
|
+
message: t("cli.install.wizard.mcp-install", { defaultValue: context.mcpInstallMode }),
|
|
2162
2289
|
initialValue: context.mcpInstallMode,
|
|
2163
2290
|
options: [
|
|
2164
|
-
{ value: "global", label: "global", hint: t("cli.
|
|
2165
|
-
{ value: "local", label: "local", hint: t("cli.
|
|
2291
|
+
{ value: "global", label: "global", hint: t("cli.install.mcp.install.global") },
|
|
2292
|
+
{ value: "local", label: "local", hint: t("cli.install.mcp.install.local") }
|
|
2166
2293
|
]
|
|
2167
2294
|
}) : context.mcpInstallMode,
|
|
2168
2295
|
claudeMcpScope: async ({ results }) => results.mcp ? selectClaudeMcpScopeInGroup({
|
|
2169
|
-
message: t("cli.
|
|
2296
|
+
message: t("cli.install.wizard.mcp-scope", { defaultValue: context.claudeMcpScope }),
|
|
2170
2297
|
initialValue: context.claudeMcpScope,
|
|
2171
2298
|
options: [
|
|
2172
|
-
{ value: "project", label: "project", hint: t("cli.
|
|
2173
|
-
{ value: "user", label: "user", hint: t("cli.
|
|
2299
|
+
{ value: "project", label: "project", hint: t("cli.install.mcp.scope.project") },
|
|
2300
|
+
{ value: "user", label: "user", hint: t("cli.install.mcp.scope.user") }
|
|
2174
2301
|
]
|
|
2175
2302
|
}) : context.claudeMcpScope,
|
|
2176
2303
|
hooks: async () => context.lockedStages.includes("hooks") ? false : confirmInGroup({
|
|
2177
|
-
message: t("cli.
|
|
2304
|
+
message: t("cli.install.wizard.stage.hooks", {
|
|
2178
2305
|
defaultValue: formatPromptDefault(!context.options.skipHooks)
|
|
2179
2306
|
}),
|
|
2180
2307
|
initialValue: !context.options.skipHooks
|
|
@@ -2203,23 +2330,23 @@ function createDefaultInitWizardAdapter() {
|
|
|
2203
2330
|
skipMcp: !groupedSelection.mcp,
|
|
2204
2331
|
skipHooks: !groupedSelection.hooks
|
|
2205
2332
|
};
|
|
2206
|
-
log.step(t("cli.
|
|
2333
|
+
log.step(t("cli.install.wizard.step.review"));
|
|
2207
2334
|
printInitPlanSummary(context.target, previewOptions, groupedSelection.mcpInstallMode, context.supports);
|
|
2208
2335
|
const confirmed = await confirm({
|
|
2209
|
-
message: t("cli.
|
|
2336
|
+
message: t("cli.install.wizard.execute.confirm"),
|
|
2210
2337
|
initialValue: true
|
|
2211
2338
|
});
|
|
2212
2339
|
if (isCancel(confirmed) || !confirmed) {
|
|
2213
2340
|
emitInitWizardCancellation();
|
|
2214
2341
|
return null;
|
|
2215
2342
|
}
|
|
2216
|
-
outro(t("cli.
|
|
2343
|
+
outro(t("cli.install.wizard.outro"));
|
|
2217
2344
|
return groupedSelection;
|
|
2218
2345
|
}
|
|
2219
2346
|
};
|
|
2220
2347
|
}
|
|
2221
2348
|
function emitInitWizardCancellation() {
|
|
2222
|
-
cancel(t("cli.
|
|
2349
|
+
cancel(t("cli.install.wizard.cancelled"));
|
|
2223
2350
|
}
|
|
2224
2351
|
async function confirmInGroup(options) {
|
|
2225
2352
|
const result = await confirm(options);
|
|
@@ -2250,74 +2377,42 @@ async function selectClaudeMcpScopeInGroup(options) {
|
|
|
2250
2377
|
}
|
|
2251
2378
|
return result;
|
|
2252
2379
|
}
|
|
2253
|
-
function collectLockedWizardStages(args) {
|
|
2254
|
-
const lockedStages = [];
|
|
2255
|
-
if (args.bootstrap === false) {
|
|
2256
|
-
lockedStages.push("bootstrap");
|
|
2257
|
-
}
|
|
2258
|
-
if (args.mcp === false) {
|
|
2259
|
-
lockedStages.push("mcp");
|
|
2260
|
-
}
|
|
2261
|
-
if (args.hooks === false) {
|
|
2262
|
-
lockedStages.push("hooks");
|
|
2263
|
-
}
|
|
2264
|
-
return lockedStages;
|
|
2265
|
-
}
|
|
2266
2380
|
function formatPromptDefault(value) {
|
|
2267
2381
|
return value ? "Y/n" : "y/N";
|
|
2268
2382
|
}
|
|
2269
2383
|
function formatInitModeBanner(options) {
|
|
2270
|
-
if (options.planOnly && options.reapply) {
|
|
2271
|
-
return t("cli.init.plan.mode-banner.plan-reapply");
|
|
2272
|
-
}
|
|
2273
2384
|
if (options.planOnly) {
|
|
2274
|
-
return t("cli.
|
|
2385
|
+
return t("cli.install.plan.mode-banner.plan");
|
|
2275
2386
|
}
|
|
2276
|
-
|
|
2277
|
-
return t("cli.init.plan.mode-banner.reapply");
|
|
2278
|
-
}
|
|
2279
|
-
return t("cli.init.plan.mode-banner.default");
|
|
2387
|
+
return t("cli.install.plan.mode-banner.default");
|
|
2280
2388
|
}
|
|
2281
2389
|
function formatInitModeBadge(options) {
|
|
2282
|
-
if (options.planOnly && options.reapply) {
|
|
2283
|
-
return t("cli.init.mode.badge.plan-reapply");
|
|
2284
|
-
}
|
|
2285
2390
|
if (options.planOnly) {
|
|
2286
|
-
return t("cli.
|
|
2391
|
+
return t("cli.install.mode.badge.plan");
|
|
2287
2392
|
}
|
|
2288
|
-
|
|
2289
|
-
return t("cli.init.mode.badge.reapply");
|
|
2290
|
-
}
|
|
2291
|
-
return t("cli.init.mode.badge.default");
|
|
2393
|
+
return t("cli.install.mode.badge.default");
|
|
2292
2394
|
}
|
|
2293
|
-
function
|
|
2294
|
-
return
|
|
2395
|
+
function normalizeTarget3(targetInput) {
|
|
2396
|
+
return isAbsolute3(targetInput) ? targetInput : resolve3(process.cwd(), targetInput);
|
|
2295
2397
|
}
|
|
2296
|
-
function
|
|
2297
|
-
if (!
|
|
2398
|
+
function assertExistingDirectory3(target) {
|
|
2399
|
+
if (!existsSync4(target) || !statSync4(target).isDirectory()) {
|
|
2298
2400
|
throw new Error(`Target must be an existing directory: ${target}`);
|
|
2299
2401
|
}
|
|
2300
2402
|
}
|
|
2301
2403
|
function detectPackageManager(cwd) {
|
|
2302
|
-
const workspaceRoot =
|
|
2303
|
-
if (
|
|
2404
|
+
const workspaceRoot = resolve3(cwd);
|
|
2405
|
+
if (existsSync4(join6(workspaceRoot, "pnpm-lock.yaml"))) {
|
|
2304
2406
|
return "pnpm";
|
|
2305
2407
|
}
|
|
2306
|
-
if (
|
|
2408
|
+
if (existsSync4(join6(workspaceRoot, "yarn.lock"))) {
|
|
2307
2409
|
return "yarn";
|
|
2308
2410
|
}
|
|
2309
|
-
if (
|
|
2411
|
+
if (existsSync4(join6(workspaceRoot, "package-lock.json"))) {
|
|
2310
2412
|
return "npm";
|
|
2311
2413
|
}
|
|
2312
2414
|
return "npm";
|
|
2313
2415
|
}
|
|
2314
|
-
function resolveMcpInstallMode(rawMode) {
|
|
2315
|
-
if (rawMode === void 0 || rawMode === "global" || rawMode === "local") {
|
|
2316
|
-
return rawMode ?? "global";
|
|
2317
|
-
}
|
|
2318
|
-
writeStderr2(t("cli.init.mcp.install.invalid", { value: rawMode }));
|
|
2319
|
-
return "global";
|
|
2320
|
-
}
|
|
2321
2416
|
function installLocalFabricServer(target, manager) {
|
|
2322
2417
|
const installArgs = manager === "npm" ? ["install", "-D", FABRIC_SERVER_PACKAGE] : ["add", "-D", FABRIC_SERVER_PACKAGE];
|
|
2323
2418
|
childProcess.execFileSync(manager, installArgs, {
|
|
@@ -2333,14 +2428,16 @@ function createInitialMeta() {
|
|
|
2333
2428
|
counters: defaultAgentsMetaCounters()
|
|
2334
2429
|
};
|
|
2335
2430
|
}
|
|
2336
|
-
function
|
|
2431
|
+
function appendInstallDiffLedgerEvent(eventsPath, payload) {
|
|
2337
2432
|
const event = {
|
|
2338
2433
|
kind: "fabric-event",
|
|
2339
|
-
id: `event:${
|
|
2434
|
+
id: `event:${randomUUID2()}`,
|
|
2340
2435
|
ts: Date.now(),
|
|
2341
2436
|
schema_version: 1,
|
|
2342
|
-
event_type: "
|
|
2343
|
-
|
|
2437
|
+
event_type: "install_diff_applied",
|
|
2438
|
+
applied: payload.applied,
|
|
2439
|
+
canonical: payload.canonical,
|
|
2440
|
+
drifted: payload.drifted
|
|
2344
2441
|
};
|
|
2345
2442
|
const line = `${JSON.stringify(event)}
|
|
2346
2443
|
`;
|
|
@@ -2391,7 +2488,7 @@ function printInitStageSummary(stageResults) {
|
|
|
2391
2488
|
console.log(formatInitStageSummaryLine("failed", collectInitStageNames(stageResults, "failed")));
|
|
2392
2489
|
}
|
|
2393
2490
|
function formatInitStageSummaryLine(disposition, stages) {
|
|
2394
|
-
const label = disposition === "ran" ? paint.success(t("cli.
|
|
2491
|
+
const label = disposition === "ran" ? paint.success(t("cli.install.stages.summary.ran")) : disposition === "skipped" ? paint.muted(t("cli.install.stages.summary.skipped")) : paint.error(t("cli.install.stages.summary.failed"));
|
|
2395
2492
|
return `${label}: ${stages.length > 0 ? stages.join(", ") : t("cli.shared.none")}`;
|
|
2396
2493
|
}
|
|
2397
2494
|
function collectInitStageNames(stageResults, disposition) {
|
|
@@ -2404,11 +2501,11 @@ function isInteractiveInit() {
|
|
|
2404
2501
|
return Boolean(process.stdin.isTTY) && Boolean(process.stdout.isTTY) && Boolean(process.stderr.isTTY);
|
|
2405
2502
|
}
|
|
2406
2503
|
function printInitPlanSummary(target, options, mcpInstallMode, supports) {
|
|
2407
|
-
console.log(t("cli.
|
|
2504
|
+
console.log(t("cli.install.plan.title"));
|
|
2408
2505
|
console.log(formatInitModeBanner(options));
|
|
2409
|
-
console.log(t("cli.
|
|
2506
|
+
console.log(t("cli.install.plan.target", { target }));
|
|
2410
2507
|
console.log(
|
|
2411
|
-
t("cli.
|
|
2508
|
+
t("cli.install.plan.actions", {
|
|
2412
2509
|
bootstrap: yesNoLabel(!options.skipBootstrap),
|
|
2413
2510
|
mcp: yesNoLabel(!options.skipMcp),
|
|
2414
2511
|
hooks: yesNoLabel(!options.skipHooks),
|
|
@@ -2417,31 +2514,32 @@ function printInitPlanSummary(target, options, mcpInstallMode, supports) {
|
|
|
2417
2514
|
);
|
|
2418
2515
|
const detected = supports.filter((support) => support.detected);
|
|
2419
2516
|
console.log(
|
|
2420
|
-
t("cli.
|
|
2517
|
+
t("cli.install.plan.detected", {
|
|
2421
2518
|
clients: detected.length > 0 ? detected.map((support) => support.label).join(", ") : t("cli.shared.none")
|
|
2422
2519
|
})
|
|
2423
2520
|
);
|
|
2424
|
-
console.log(t("cli.
|
|
2521
|
+
console.log(t("cli.install.plan.writes"));
|
|
2425
2522
|
console.log(` - ${target}/.fabric/knowledge/{decisions,pitfalls,guidelines,models,processes,pending}/`);
|
|
2426
2523
|
console.log(` - ${target}/.fabric/agents.meta.json`);
|
|
2427
2524
|
console.log(` - ${target}/.fabric/events.jsonl`);
|
|
2428
2525
|
console.log(` - ${target}/.fabric/forensic.json`);
|
|
2526
|
+
console.log(` - ${target}/.fabric/fabric-config.json`);
|
|
2429
2527
|
}
|
|
2430
2528
|
function printInitCapabilitySummary(supports, stageResults, options) {
|
|
2431
2529
|
const detected = supports.filter((support) => support.detected);
|
|
2432
2530
|
if (detected.length === 0) {
|
|
2433
|
-
console.log(t("cli.
|
|
2531
|
+
console.log(t("cli.install.capabilities.none"));
|
|
2434
2532
|
return;
|
|
2435
2533
|
}
|
|
2436
|
-
console.log(t("cli.
|
|
2534
|
+
console.log(t("cli.install.capabilities.title"));
|
|
2437
2535
|
const rows = detected.map((support) => toCapabilityRow(support, stageResults, options));
|
|
2438
2536
|
const headers = {
|
|
2439
|
-
client: t("cli.
|
|
2440
|
-
bootstrap: t("cli.
|
|
2441
|
-
mcp: t("cli.
|
|
2442
|
-
hook: t("cli.
|
|
2443
|
-
skill: t("cli.
|
|
2444
|
-
followUp: t("cli.
|
|
2537
|
+
client: t("cli.install.capabilities.header.client"),
|
|
2538
|
+
bootstrap: t("cli.install.capabilities.header.bootstrap"),
|
|
2539
|
+
mcp: t("cli.install.capabilities.header.mcp"),
|
|
2540
|
+
hook: t("cli.install.capabilities.header.hook"),
|
|
2541
|
+
skill: t("cli.install.capabilities.header.skill"),
|
|
2542
|
+
followUp: t("cli.install.capabilities.header.follow-up")
|
|
2445
2543
|
};
|
|
2446
2544
|
const widths = {
|
|
2447
2545
|
client: Math.max(displayWidth(headers.client), ...rows.map((row) => displayWidth(row.client))),
|
|
@@ -2456,11 +2554,13 @@ function printInitCapabilitySummary(supports, stageResults, options) {
|
|
|
2456
2554
|
for (const row of rows) {
|
|
2457
2555
|
console.log(formatCapabilityTableRow(row, widths));
|
|
2458
2556
|
}
|
|
2557
|
+
console.log("");
|
|
2558
|
+
console.log(t("cli.install.restart-banner"));
|
|
2459
2559
|
}
|
|
2460
2560
|
function toCapabilityRow(support, stageResults, options) {
|
|
2461
2561
|
const stage = (name) => stageResults.find((entry) => entry.name === name)?.disposition ?? null;
|
|
2462
|
-
const bootstrap = support.capabilities.bootstrap ? capabilityStatus(options.skipBootstrap ? "skipped" : stage("bootstrap")) : t("cli.
|
|
2463
|
-
const mcp = support.capabilities.mcp ? capabilityStatus(options.skipMcp ? "skipped" : stage("mcp")) : t("cli.
|
|
2562
|
+
const bootstrap = support.capabilities.bootstrap ? capabilityStatus(options.skipBootstrap ? "skipped" : stage("bootstrap")) : t("cli.install.capabilities.status.na");
|
|
2563
|
+
const mcp = support.capabilities.mcp ? capabilityStatus(options.skipMcp ? "skipped" : stage("mcp")) : t("cli.install.capabilities.status.na");
|
|
2464
2564
|
const hook = capabilityInstallStatus(support, "hook");
|
|
2465
2565
|
const skill = capabilityInstallStatus(support, "skill");
|
|
2466
2566
|
return {
|
|
@@ -2469,14 +2569,14 @@ function toCapabilityRow(support, stageResults, options) {
|
|
|
2469
2569
|
mcp,
|
|
2470
2570
|
hook,
|
|
2471
2571
|
skill,
|
|
2472
|
-
followUp: hasInstalledCapability(support, "skill") ? t("cli.
|
|
2572
|
+
followUp: hasInstalledCapability(support, "skill") ? t("cli.install.capabilities.follow-up.ready") : support.capabilities.skill ? t("cli.install.capabilities.follow-up.install") : t("cli.install.capabilities.follow-up.manual")
|
|
2473
2573
|
};
|
|
2474
2574
|
}
|
|
2475
2575
|
function capabilityInstallStatus(support, capability) {
|
|
2476
2576
|
if (!support.capabilities[capability]) {
|
|
2477
|
-
return t("cli.
|
|
2577
|
+
return t("cli.install.capabilities.status.na");
|
|
2478
2578
|
}
|
|
2479
|
-
return hasInstalledCapability(support, capability) ? t("cli.
|
|
2579
|
+
return hasInstalledCapability(support, capability) ? t("cli.install.capabilities.status.installed") : t("cli.install.capabilities.status.supported");
|
|
2480
2580
|
}
|
|
2481
2581
|
function hasInstalledCapability(support, capability) {
|
|
2482
2582
|
return support.installedCapabilities?.[capability] === true;
|
|
@@ -2484,15 +2584,15 @@ function hasInstalledCapability(support, capability) {
|
|
|
2484
2584
|
function capabilityStatus(disposition) {
|
|
2485
2585
|
switch (disposition) {
|
|
2486
2586
|
case "ran":
|
|
2487
|
-
return t("cli.
|
|
2587
|
+
return t("cli.install.capabilities.status.ready");
|
|
2488
2588
|
case "skipped":
|
|
2489
|
-
return t("cli.
|
|
2589
|
+
return t("cli.install.capabilities.status.skipped");
|
|
2490
2590
|
case "failed":
|
|
2491
|
-
return t("cli.
|
|
2591
|
+
return t("cli.install.capabilities.status.failed");
|
|
2492
2592
|
case null:
|
|
2493
|
-
return t("cli.
|
|
2593
|
+
return t("cli.install.capabilities.status.na");
|
|
2494
2594
|
default:
|
|
2495
|
-
return t("cli.
|
|
2595
|
+
return t("cli.install.capabilities.status.ready");
|
|
2496
2596
|
}
|
|
2497
2597
|
}
|
|
2498
2598
|
function formatCapabilityTableRow(row, widths) {
|
|
@@ -2518,21 +2618,21 @@ function formatCapabilityDivider(widths) {
|
|
|
2518
2618
|
function formatInitReasonMessage(supports) {
|
|
2519
2619
|
const detected = supports.filter((support) => support.detected);
|
|
2520
2620
|
if (detected.some((support) => support.capabilities.skill)) {
|
|
2521
|
-
return t("cli.
|
|
2621
|
+
return t("cli.install.reason-message.installable-body");
|
|
2522
2622
|
}
|
|
2523
|
-
return t("cli.
|
|
2623
|
+
return t("cli.install.reason-message.manual-body");
|
|
2524
2624
|
}
|
|
2525
2625
|
function yesNoLabel(value) {
|
|
2526
2626
|
return value ? t("cli.shared.yes") : t("cli.shared.no");
|
|
2527
2627
|
}
|
|
2528
2628
|
function formatInitPathAction(path, action) {
|
|
2529
|
-
return t("cli.
|
|
2629
|
+
return t("cli.install.created-path", { label: labelForInitWriteAction(action), path });
|
|
2530
2630
|
}
|
|
2531
2631
|
function formatAgentsMdAction(path, action) {
|
|
2532
2632
|
if (action === "preserved") {
|
|
2533
|
-
return t("cli.
|
|
2633
|
+
return t("cli.install.skipped-existing-path", { label: skippedLabel(), path });
|
|
2534
2634
|
}
|
|
2535
|
-
return t("cli.
|
|
2635
|
+
return t("cli.install.created-path", { label: createdLabel(), path });
|
|
2536
2636
|
}
|
|
2537
2637
|
function labelForInitWriteAction(action) {
|
|
2538
2638
|
return action === "overwritten" ? overwrittenLabel() : createdLabel();
|
|
@@ -2550,18 +2650,18 @@ function reasonLabel() {
|
|
|
2550
2650
|
return paint.human(t("cli.shared.reason"));
|
|
2551
2651
|
}
|
|
2552
2652
|
function overwrittenLabel() {
|
|
2553
|
-
return paint.warn(t("cli.
|
|
2653
|
+
return paint.warn(t("cli.install.label.overwritten"));
|
|
2554
2654
|
}
|
|
2555
2655
|
function completedStageLabel() {
|
|
2556
|
-
return paint.success(t("cli.
|
|
2656
|
+
return paint.success(t("cli.install.stages.completed"));
|
|
2557
2657
|
}
|
|
2558
2658
|
function skippedStageLabel() {
|
|
2559
|
-
return paint.muted(t("cli.
|
|
2659
|
+
return paint.muted(t("cli.install.stages.skipped"));
|
|
2560
2660
|
}
|
|
2561
2661
|
function failedStageLabel() {
|
|
2562
|
-
return paint.error(t("cli.
|
|
2662
|
+
return paint.error(t("cli.install.stages.failed"));
|
|
2563
2663
|
}
|
|
2564
|
-
function
|
|
2664
|
+
function writeStderr(message) {
|
|
2565
2665
|
process.stderr.write(`${message}
|
|
2566
2666
|
`);
|
|
2567
2667
|
}
|
|
@@ -2569,13 +2669,15 @@ export {
|
|
|
2569
2669
|
buildInitExecutionPlan,
|
|
2570
2670
|
buildInitFabricPlan,
|
|
2571
2671
|
createDefaultInitWizardAdapter,
|
|
2572
|
-
|
|
2672
|
+
install_default as default,
|
|
2573
2673
|
detectPackageManager,
|
|
2574
2674
|
executeInitExecutionPlan,
|
|
2575
2675
|
executeInitFabricPlan,
|
|
2576
|
-
initCommand,
|
|
2577
2676
|
initFabric,
|
|
2677
|
+
installCommand,
|
|
2578
2678
|
resolveInitExecutionPlanWithWizard,
|
|
2679
|
+
runHooksOnlyRefresh,
|
|
2579
2680
|
runInitCommand,
|
|
2681
|
+
runSkillsOnlyRefresh,
|
|
2580
2682
|
shouldUseInitWizard
|
|
2581
2683
|
};
|