@fenglimg/fabric-cli 2.0.0-rc.11 → 2.0.0-rc.15
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/README.md +6 -4
- package/dist/chunk-AIB54QRT.js +82 -0
- package/dist/{chunk-5MQ52F42.js → chunk-AXIFEVAS.js} +16 -219
- package/dist/{chunk-WWNXR34K.js → chunk-G2CIOLD4.js} +16 -1
- package/dist/{chunk-HQLEHH4O.js → chunk-SKSYUHKK.js} +267 -40
- package/dist/chunk-UTF4YBDN.js +366 -0
- package/dist/config-7YD365I3.js +13 -0
- package/dist/{doctor-RILCO5OG.js → doctor-6XHLQJXB.js} +67 -50
- package/dist/index.js +8 -9
- package/dist/{init-C56PWHID.js → install-JLDCHAXV.js} +409 -416
- package/dist/{plan-context-hint-QMUPAXIB.js → plan-context-hint-73U4FGKO.js} +6 -1
- package/dist/{serve-NGLXHDYC.js → serve-L3X5UHG2.js} +15 -10
- package/dist/{uninstall-DBAR2JBS.js → uninstall-DD6FIFCI.js} +81 -179
- package/package.json +3 -3
- package/templates/hooks/configs/README.md +10 -6
- package/templates/hooks/configs/cursor-hooks.json +7 -10
- package/templates/hooks/knowledge-hint-broad.cjs +28 -107
- package/templates/skills/fabric-archive/SKILL.md +15 -15
- package/templates/skills/fabric-import/SKILL.md +26 -26
- package/templates/skills/fabric-review/SKILL.md +19 -19
- package/dist/chunk-AW3G7ZH5.js +0 -576
- package/dist/chunk-WPTA74BY.js +0 -184
- package/dist/hooks-NX32PPEN.js +0 -13
- package/dist/scan-66EKMNAY.js +0 -24
|
@@ -1,31 +1,257 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import {
|
|
3
|
-
ClaudeCodeCLIWriter,
|
|
4
|
-
CursorWriter,
|
|
5
|
-
createServerEntry,
|
|
6
|
-
normalizeConfigPath,
|
|
7
|
-
removeJsonClientConfigEntry,
|
|
8
|
-
writeJsonClientConfig
|
|
9
|
-
} from "./chunk-AW3G7ZH5.js";
|
|
10
2
|
|
|
11
3
|
// src/config/resolver.ts
|
|
12
|
-
import { existsSync as
|
|
13
|
-
import { join as
|
|
14
|
-
import { homedir as
|
|
4
|
+
import { existsSync as existsSync4 } from "fs";
|
|
5
|
+
import { join as join4 } from "path";
|
|
6
|
+
import { homedir as homedir4 } from "os";
|
|
15
7
|
|
|
16
8
|
// src/config/claude-code.ts
|
|
9
|
+
import { existsSync as existsSync2 } from "fs";
|
|
10
|
+
import { join as join2, resolve as resolve2 } from "path";
|
|
11
|
+
import { homedir as homedir2, platform } from "os";
|
|
12
|
+
|
|
13
|
+
// src/config/json.ts
|
|
17
14
|
import { existsSync } from "fs";
|
|
18
|
-
import {
|
|
19
|
-
import {
|
|
15
|
+
import { mkdir, readFile } from "fs/promises";
|
|
16
|
+
import { dirname, join, resolve } from "path";
|
|
17
|
+
import { homedir } from "os";
|
|
18
|
+
import { atomicWriteJson } from "@fenglimg/fabric-shared/node/atomic-write";
|
|
19
|
+
|
|
20
|
+
// src/config/writer.ts
|
|
21
|
+
function createServerEntry(serverPath) {
|
|
22
|
+
return {
|
|
23
|
+
command: process.execPath,
|
|
24
|
+
args: [serverPath]
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// src/config/json.ts
|
|
29
|
+
function deepMerge(target, source, options = {}) {
|
|
30
|
+
return deepMergeAtPath(target, source, "", options);
|
|
31
|
+
}
|
|
32
|
+
function deepMergeAtPath(target, source, path, options) {
|
|
33
|
+
if (options.arrayAppendPaths && options.arrayAppendPaths.includes(path) && Array.isArray(target) && Array.isArray(source)) {
|
|
34
|
+
return appendArrayWithDedupe(target, source);
|
|
35
|
+
}
|
|
36
|
+
if (target === null || typeof target !== "object" || Array.isArray(target) || source === null || typeof source !== "object" || Array.isArray(source)) {
|
|
37
|
+
return source;
|
|
38
|
+
}
|
|
39
|
+
const out = { ...target };
|
|
40
|
+
for (const key of Object.keys(source)) {
|
|
41
|
+
const childPath = path === "" ? key : `${path}.${key}`;
|
|
42
|
+
out[key] = deepMergeAtPath(
|
|
43
|
+
target[key],
|
|
44
|
+
source[key],
|
|
45
|
+
childPath,
|
|
46
|
+
options
|
|
47
|
+
);
|
|
48
|
+
}
|
|
49
|
+
return out;
|
|
50
|
+
}
|
|
51
|
+
function appendArrayWithDedupe(target, source) {
|
|
52
|
+
const out = [...target];
|
|
53
|
+
for (const candidate of source) {
|
|
54
|
+
if (out.some((existing) => isSameHookEntry(existing, candidate))) {
|
|
55
|
+
continue;
|
|
56
|
+
}
|
|
57
|
+
out.push(candidate);
|
|
58
|
+
}
|
|
59
|
+
return out;
|
|
60
|
+
}
|
|
61
|
+
function isSameHookEntry(a, b) {
|
|
62
|
+
const cmdA = extractHookCommand(a);
|
|
63
|
+
const cmdB = extractHookCommand(b);
|
|
64
|
+
if (cmdA !== null && cmdB !== null) {
|
|
65
|
+
return cmdA === cmdB;
|
|
66
|
+
}
|
|
67
|
+
return deepEqual(a, b);
|
|
68
|
+
}
|
|
69
|
+
function extractHookCommand(item) {
|
|
70
|
+
if (item === null || typeof item !== "object") {
|
|
71
|
+
return null;
|
|
72
|
+
}
|
|
73
|
+
const obj = item;
|
|
74
|
+
if (typeof obj.command === "string") {
|
|
75
|
+
return obj.command;
|
|
76
|
+
}
|
|
77
|
+
if (Array.isArray(obj.hooks)) {
|
|
78
|
+
for (const inner of obj.hooks) {
|
|
79
|
+
if (inner !== null && typeof inner === "object") {
|
|
80
|
+
const innerObj = inner;
|
|
81
|
+
if (typeof innerObj.command === "string") {
|
|
82
|
+
return innerObj.command;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
return null;
|
|
88
|
+
}
|
|
89
|
+
function deepEqual(a, b) {
|
|
90
|
+
if (a === b) {
|
|
91
|
+
return true;
|
|
92
|
+
}
|
|
93
|
+
if (a === null || b === null || typeof a !== "object" || typeof b !== "object") {
|
|
94
|
+
return false;
|
|
95
|
+
}
|
|
96
|
+
if (Array.isArray(a) !== Array.isArray(b)) {
|
|
97
|
+
return false;
|
|
98
|
+
}
|
|
99
|
+
if (Array.isArray(a) && Array.isArray(b)) {
|
|
100
|
+
if (a.length !== b.length) {
|
|
101
|
+
return false;
|
|
102
|
+
}
|
|
103
|
+
return a.every((value, index) => deepEqual(value, b[index]));
|
|
104
|
+
}
|
|
105
|
+
const aObj = a;
|
|
106
|
+
const bObj = b;
|
|
107
|
+
const aKeys = Object.keys(aObj);
|
|
108
|
+
const bKeys = Object.keys(bObj);
|
|
109
|
+
if (aKeys.length !== bKeys.length) {
|
|
110
|
+
return false;
|
|
111
|
+
}
|
|
112
|
+
return aKeys.every((key) => deepEqual(aObj[key], bObj[key]));
|
|
113
|
+
}
|
|
114
|
+
function expandHome(filePath) {
|
|
115
|
+
if (filePath === "~") {
|
|
116
|
+
return homedir();
|
|
117
|
+
}
|
|
118
|
+
if (filePath.startsWith("~/")) {
|
|
119
|
+
return join(homedir(), filePath.slice(2));
|
|
120
|
+
}
|
|
121
|
+
return filePath;
|
|
122
|
+
}
|
|
123
|
+
function normalizeConfigPath(filePath) {
|
|
124
|
+
return resolve(expandHome(filePath));
|
|
125
|
+
}
|
|
126
|
+
async function readJsonConfig(configPath) {
|
|
127
|
+
try {
|
|
128
|
+
const raw = await readFile(configPath, "utf8");
|
|
129
|
+
if (raw.trim().length === 0) {
|
|
130
|
+
return {};
|
|
131
|
+
}
|
|
132
|
+
const parsed = JSON.parse(raw);
|
|
133
|
+
if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
134
|
+
throw new Error(`Expected JSON object in ${configPath}`);
|
|
135
|
+
}
|
|
136
|
+
return parsed;
|
|
137
|
+
} catch (error) {
|
|
138
|
+
if (error instanceof Error && "code" in error && error.code === "ENOENT") {
|
|
139
|
+
return {};
|
|
140
|
+
}
|
|
141
|
+
throw error;
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
async function writeJsonClientConfig(configPath, serverEntry) {
|
|
145
|
+
const existing = await readJsonConfig(configPath);
|
|
146
|
+
const merged = deepMerge(existing, { mcpServers: { fabric: serverEntry } });
|
|
147
|
+
await mkdir(dirname(configPath), { recursive: true });
|
|
148
|
+
await atomicWriteJson(configPath, merged, { indent: 2 });
|
|
149
|
+
}
|
|
150
|
+
async function removeJsonClientConfigEntry(configPath, serverName) {
|
|
151
|
+
if (!existsSync(configPath)) {
|
|
152
|
+
return { status: "skipped", path: configPath, message: "no-config-file" };
|
|
153
|
+
}
|
|
154
|
+
let existing;
|
|
155
|
+
try {
|
|
156
|
+
existing = await readJsonConfig(configPath);
|
|
157
|
+
} catch (error) {
|
|
158
|
+
return {
|
|
159
|
+
status: "error",
|
|
160
|
+
path: configPath,
|
|
161
|
+
message: error instanceof Error ? error.message : String(error)
|
|
162
|
+
};
|
|
163
|
+
}
|
|
164
|
+
const mcpServers = existing.mcpServers;
|
|
165
|
+
if (mcpServers === void 0 || mcpServers === null || typeof mcpServers !== "object" || Array.isArray(mcpServers)) {
|
|
166
|
+
return { status: "skipped", path: configPath, message: "no-mcp-servers-object" };
|
|
167
|
+
}
|
|
168
|
+
const servers = mcpServers;
|
|
169
|
+
if (!Object.prototype.hasOwnProperty.call(servers, serverName)) {
|
|
170
|
+
return { status: "skipped", path: configPath, message: "not-present" };
|
|
171
|
+
}
|
|
172
|
+
const nextServers = { ...servers };
|
|
173
|
+
delete nextServers[serverName];
|
|
174
|
+
const next = { ...existing, mcpServers: nextServers };
|
|
175
|
+
try {
|
|
176
|
+
await mkdir(dirname(configPath), { recursive: true });
|
|
177
|
+
await atomicWriteJson(configPath, next, { indent: 2 });
|
|
178
|
+
return { status: "removed", path: configPath };
|
|
179
|
+
} catch (error) {
|
|
180
|
+
return {
|
|
181
|
+
status: "error",
|
|
182
|
+
path: configPath,
|
|
183
|
+
message: error instanceof Error ? error.message : String(error)
|
|
184
|
+
};
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
var JsonClientConfigWriter = class {
|
|
188
|
+
configuredPath;
|
|
189
|
+
constructor(configuredPath) {
|
|
190
|
+
this.configuredPath = configuredPath;
|
|
191
|
+
}
|
|
192
|
+
async detect(workspaceRoot, overridePath) {
|
|
193
|
+
const explicitPath = overridePath ?? this.configuredPath;
|
|
194
|
+
if (explicitPath !== void 0) {
|
|
195
|
+
return normalizeConfigPath(explicitPath);
|
|
196
|
+
}
|
|
197
|
+
const configPath = this.defaultPath(workspaceRoot);
|
|
198
|
+
return configPath === null ? null : normalizeConfigPath(configPath);
|
|
199
|
+
}
|
|
200
|
+
async write(serverPath, workspaceRoot, overridePath) {
|
|
201
|
+
const configPath = await this.detect(workspaceRoot, overridePath);
|
|
202
|
+
if (configPath === null) {
|
|
203
|
+
return;
|
|
204
|
+
}
|
|
205
|
+
await writeJsonClientConfig(configPath, createServerEntry(serverPath));
|
|
206
|
+
}
|
|
207
|
+
async remove(serverName, workspaceRoot, overridePath) {
|
|
208
|
+
const configPath = await this.detect(workspaceRoot, overridePath);
|
|
209
|
+
if (configPath === null) {
|
|
210
|
+
return { status: "skipped", message: "no-config-path" };
|
|
211
|
+
}
|
|
212
|
+
return removeJsonClientConfigEntry(configPath, serverName);
|
|
213
|
+
}
|
|
214
|
+
};
|
|
215
|
+
var ClaudeCodeCLIWriter = class extends JsonClientConfigWriter {
|
|
216
|
+
clientKind = "ClaudeCodeCLI";
|
|
217
|
+
scope;
|
|
218
|
+
constructor(configuredPath, scope = "project") {
|
|
219
|
+
super(configuredPath);
|
|
220
|
+
this.scope = scope;
|
|
221
|
+
}
|
|
222
|
+
// Writes to project-level .mcp.json (per Claude Code MCP spec) by default,
|
|
223
|
+
// or ~/.claude.json for user scope.
|
|
224
|
+
// Detection still checks ~/.claude to confirm Claude Code is installed.
|
|
225
|
+
defaultPath(workspaceRoot) {
|
|
226
|
+
const globalClaudeDir = join(homedir(), ".claude");
|
|
227
|
+
const projectClaudeDir = join(workspaceRoot, ".claude");
|
|
228
|
+
if (!existsSync(globalClaudeDir) && !existsSync(projectClaudeDir)) {
|
|
229
|
+
return null;
|
|
230
|
+
}
|
|
231
|
+
return this.scope === "user" ? join(homedir(), ".claude.json") : join(workspaceRoot, ".mcp.json");
|
|
232
|
+
}
|
|
233
|
+
};
|
|
234
|
+
var CursorWriter = class extends JsonClientConfigWriter {
|
|
235
|
+
clientKind = "Cursor";
|
|
236
|
+
constructor(configuredPath) {
|
|
237
|
+
super(configuredPath);
|
|
238
|
+
}
|
|
239
|
+
defaultPath(workspaceRoot) {
|
|
240
|
+
const cursorDir = join(workspaceRoot, ".cursor");
|
|
241
|
+
return existsSync(cursorDir) ? join(cursorDir, "mcp.json") : null;
|
|
242
|
+
}
|
|
243
|
+
};
|
|
244
|
+
|
|
245
|
+
// src/config/claude-code.ts
|
|
20
246
|
function getClaudeDesktopConfigPath() {
|
|
21
247
|
const os = platform();
|
|
22
248
|
if (os === "darwin") {
|
|
23
|
-
return
|
|
249
|
+
return join2(homedir2(), "Library", "Application Support", "Claude", "claude_desktop_config.json");
|
|
24
250
|
}
|
|
25
251
|
if (os === "win32") {
|
|
26
|
-
return
|
|
252
|
+
return join2(process.env.APPDATA ?? join2(homedir2(), "AppData", "Roaming"), "Claude", "claude_desktop_config.json");
|
|
27
253
|
}
|
|
28
|
-
return
|
|
254
|
+
return join2(homedir2(), ".config", "Claude", "claude_desktop_config.json");
|
|
29
255
|
}
|
|
30
256
|
var ClaudeCodeDesktopWriter = class {
|
|
31
257
|
clientKind = "ClaudeCodeDesktop";
|
|
@@ -35,7 +261,7 @@ var ClaudeCodeDesktopWriter = class {
|
|
|
35
261
|
}
|
|
36
262
|
async detect(_workspaceRoot, overridePath) {
|
|
37
263
|
const configPath = normalizeConfigPath(overridePath ?? this.configuredPath ?? getClaudeDesktopConfigPath());
|
|
38
|
-
return
|
|
264
|
+
return existsSync2(configPath) || overridePath !== void 0 || this.configuredPath !== void 0 ? configPath : null;
|
|
39
265
|
}
|
|
40
266
|
async write(serverPath, workspaceRoot, overridePath) {
|
|
41
267
|
const configPath = await this.detect(workspaceRoot, overridePath);
|
|
@@ -57,17 +283,17 @@ var ClaudeCodeDesktopWriter = class {
|
|
|
57
283
|
};
|
|
58
284
|
|
|
59
285
|
// src/config/toml.ts
|
|
60
|
-
import { existsSync as
|
|
61
|
-
import { mkdir, readFile } from "fs/promises";
|
|
62
|
-
import { dirname, join as
|
|
63
|
-
import { homedir as
|
|
286
|
+
import { existsSync as existsSync3 } from "fs";
|
|
287
|
+
import { mkdir as mkdir2, readFile as readFile2 } from "fs/promises";
|
|
288
|
+
import { dirname as dirname2, join as join3, resolve as resolve3 } from "path";
|
|
289
|
+
import { homedir as homedir3 } from "os";
|
|
64
290
|
import { atomicWriteText } from "@fenglimg/fabric-shared/node/atomic-write";
|
|
65
|
-
function
|
|
291
|
+
function expandHome2(filePath) {
|
|
66
292
|
if (filePath === "~") {
|
|
67
|
-
return
|
|
293
|
+
return homedir3();
|
|
68
294
|
}
|
|
69
295
|
if (filePath.startsWith("~/")) {
|
|
70
|
-
return
|
|
296
|
+
return join3(homedir3(), filePath.slice(2));
|
|
71
297
|
}
|
|
72
298
|
return filePath;
|
|
73
299
|
}
|
|
@@ -134,7 +360,7 @@ ${block}`;
|
|
|
134
360
|
}
|
|
135
361
|
async function readTomlConfigText(configPath) {
|
|
136
362
|
try {
|
|
137
|
-
return await
|
|
363
|
+
return await readFile2(configPath, "utf8");
|
|
138
364
|
} catch (error) {
|
|
139
365
|
if (error instanceof Error && "code" in error && error.code === "ENOENT") {
|
|
140
366
|
return "";
|
|
@@ -151,10 +377,10 @@ var CodexTOMLConfigWriter = class {
|
|
|
151
377
|
async detect(_workspaceRoot, overridePath) {
|
|
152
378
|
const explicitPath = overridePath ?? this.configuredPath;
|
|
153
379
|
if (explicitPath !== void 0) {
|
|
154
|
-
return
|
|
380
|
+
return resolve3(expandHome2(explicitPath));
|
|
155
381
|
}
|
|
156
|
-
const codexDir =
|
|
157
|
-
return
|
|
382
|
+
const codexDir = join3(homedir3(), ".codex");
|
|
383
|
+
return existsSync3(codexDir) ? resolve3(join3(codexDir, "config.toml")) : null;
|
|
158
384
|
}
|
|
159
385
|
async write(serverPath, workspaceRoot, overridePath) {
|
|
160
386
|
const configPath = await this.detect(workspaceRoot, overridePath);
|
|
@@ -163,7 +389,7 @@ var CodexTOMLConfigWriter = class {
|
|
|
163
389
|
}
|
|
164
390
|
const rawConfig = await readTomlConfigText(configPath);
|
|
165
391
|
const nextConfig = upsertCodexServerBlock(rawConfig, "fabric", createServerEntry(serverPath));
|
|
166
|
-
await
|
|
392
|
+
await mkdir2(dirname2(configPath), { recursive: true });
|
|
167
393
|
await atomicWriteText(configPath, nextConfig);
|
|
168
394
|
}
|
|
169
395
|
async remove(serverName, workspaceRoot, overridePath) {
|
|
@@ -171,7 +397,7 @@ var CodexTOMLConfigWriter = class {
|
|
|
171
397
|
if (configPath === null) {
|
|
172
398
|
return { status: "skipped", message: "no-config-path" };
|
|
173
399
|
}
|
|
174
|
-
if (!
|
|
400
|
+
if (!existsSync3(configPath)) {
|
|
175
401
|
return { status: "skipped", path: configPath, message: "no-config-file" };
|
|
176
402
|
}
|
|
177
403
|
let rawConfig;
|
|
@@ -189,7 +415,7 @@ var CodexTOMLConfigWriter = class {
|
|
|
189
415
|
return { status: "skipped", path: configPath, message: "not-present" };
|
|
190
416
|
}
|
|
191
417
|
try {
|
|
192
|
-
await
|
|
418
|
+
await mkdir2(dirname2(configPath), { recursive: true });
|
|
193
419
|
await atomicWriteText(configPath, text);
|
|
194
420
|
return { status: "removed", path: configPath };
|
|
195
421
|
} catch (error) {
|
|
@@ -218,25 +444,25 @@ function resolveClients(workspaceRoot, fabricConfig = {}, opts = {}) {
|
|
|
218
444
|
const claudeMcpScope = opts.claudeMcpScope ?? "project";
|
|
219
445
|
addIfDetected(
|
|
220
446
|
writers,
|
|
221
|
-
|
|
447
|
+
existsSync4(join4(homedir4(), ".claude")) || existsSync4(join4(workspaceRoot, ".claude")),
|
|
222
448
|
(configuredPath) => new ClaudeCodeCLIWriter(configuredPath, claudeMcpScope),
|
|
223
449
|
hasExplicitPath(clientPaths, "claudeCodeCLI") ? clientPaths.claudeCodeCLI : void 0
|
|
224
450
|
);
|
|
225
451
|
addIfDetected(
|
|
226
452
|
writers,
|
|
227
|
-
|
|
453
|
+
existsSync4(getClaudeDesktopConfigPath()),
|
|
228
454
|
(configuredPath) => new ClaudeCodeDesktopWriter(configuredPath),
|
|
229
455
|
hasExplicitPath(clientPaths, "claudeCodeDesktop") ? clientPaths.claudeCodeDesktop : void 0
|
|
230
456
|
);
|
|
231
457
|
addIfDetected(
|
|
232
458
|
writers,
|
|
233
|
-
|
|
459
|
+
existsSync4(join4(workspaceRoot, ".cursor")),
|
|
234
460
|
(configuredPath) => new CursorWriter(configuredPath),
|
|
235
461
|
hasExplicitPath(clientPaths, "cursor") ? clientPaths.cursor : void 0
|
|
236
462
|
);
|
|
237
463
|
addIfDetected(
|
|
238
464
|
writers,
|
|
239
|
-
|
|
465
|
+
existsSync4(join4(homedir4(), ".codex")),
|
|
240
466
|
(configuredPath) => new CodexTOMLConfigWriter(configuredPath),
|
|
241
467
|
hasExplicitPath(clientPaths, "codexCLI") ? clientPaths.codexCLI : void 0
|
|
242
468
|
);
|
|
@@ -244,10 +470,10 @@ function resolveClients(workspaceRoot, fabricConfig = {}, opts = {}) {
|
|
|
244
470
|
}
|
|
245
471
|
function detectClientSupports(workspaceRoot, fabricConfig = {}) {
|
|
246
472
|
const clientPaths = fabricConfig.clientPaths;
|
|
247
|
-
const claudeDetected =
|
|
248
|
-
const claudeDesktopDetected =
|
|
249
|
-
const cursorDetected =
|
|
250
|
-
const codexDetected =
|
|
473
|
+
const claudeDetected = existsSync4(join4(homedir4(), ".claude")) || existsSync4(join4(workspaceRoot, ".claude"));
|
|
474
|
+
const claudeDesktopDetected = existsSync4(getClaudeDesktopConfigPath());
|
|
475
|
+
const cursorDetected = existsSync4(join4(workspaceRoot, ".cursor"));
|
|
476
|
+
const codexDetected = existsSync4(join4(homedir4(), ".codex"));
|
|
251
477
|
return [
|
|
252
478
|
{
|
|
253
479
|
clientKind: "ClaudeCodeCLI",
|
|
@@ -305,7 +531,7 @@ function detectClientSupports(workspaceRoot, fabricConfig = {}) {
|
|
|
305
531
|
skill: true
|
|
306
532
|
},
|
|
307
533
|
installedCapabilities: {
|
|
308
|
-
hook:
|
|
534
|
+
hook: existsSync4(join4(workspaceRoot, ".codex", "hooks.json")),
|
|
309
535
|
// v2/rc.2: v1 client-side init skill removed; skill-installation probes
|
|
310
536
|
// will return once rc.2/3/4 introduce the v2 skills (fabric-archive,
|
|
311
537
|
// fabric-review, fabric-import). Until then there is nothing to probe.
|
|
@@ -316,6 +542,7 @@ function detectClientSupports(workspaceRoot, fabricConfig = {}) {
|
|
|
316
542
|
}
|
|
317
543
|
|
|
318
544
|
export {
|
|
545
|
+
deepMerge,
|
|
319
546
|
resolveClients,
|
|
320
547
|
detectClientSupports
|
|
321
548
|
};
|