assetplex 0.1.0
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 +243 -0
- package/README.zh-CN.md +243 -0
- package/dist/index.js +4152 -0
- package/dist/index.js.map +1 -0
- package/package.json +76 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,4152 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
4
|
+
var __esm = (fn, res) => function __init() {
|
|
5
|
+
return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
|
|
6
|
+
};
|
|
7
|
+
var __export = (target, all) => {
|
|
8
|
+
for (var name in all)
|
|
9
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
// node_modules/tsup/assets/esm_shims.js
|
|
13
|
+
import path from "path";
|
|
14
|
+
import { fileURLToPath } from "url";
|
|
15
|
+
var init_esm_shims = __esm({
|
|
16
|
+
"node_modules/tsup/assets/esm_shims.js"() {
|
|
17
|
+
"use strict";
|
|
18
|
+
}
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
// src/utils/logger.ts
|
|
22
|
+
import chalk from "chalk";
|
|
23
|
+
function setLogLevel(level) {
|
|
24
|
+
currentLevel = level;
|
|
25
|
+
}
|
|
26
|
+
function shouldLog(level) {
|
|
27
|
+
return levelPriority[level] >= levelPriority[currentLevel];
|
|
28
|
+
}
|
|
29
|
+
var levelPriority, currentLevel, log;
|
|
30
|
+
var init_logger = __esm({
|
|
31
|
+
"src/utils/logger.ts"() {
|
|
32
|
+
"use strict";
|
|
33
|
+
init_esm_shims();
|
|
34
|
+
levelPriority = {
|
|
35
|
+
debug: 10,
|
|
36
|
+
info: 20,
|
|
37
|
+
success: 25,
|
|
38
|
+
warn: 30,
|
|
39
|
+
error: 40
|
|
40
|
+
};
|
|
41
|
+
currentLevel = "info";
|
|
42
|
+
log = {
|
|
43
|
+
debug(msg, ...args) {
|
|
44
|
+
if (shouldLog("debug")) {
|
|
45
|
+
console.error(chalk.gray(`[debug] ${msg}`), ...args);
|
|
46
|
+
}
|
|
47
|
+
},
|
|
48
|
+
info(msg, ...args) {
|
|
49
|
+
if (shouldLog("info")) {
|
|
50
|
+
console.log(chalk.cyan(msg), ...args);
|
|
51
|
+
}
|
|
52
|
+
},
|
|
53
|
+
success(msg, ...args) {
|
|
54
|
+
if (shouldLog("success")) {
|
|
55
|
+
console.log(chalk.green(`\u2713 ${msg}`), ...args);
|
|
56
|
+
}
|
|
57
|
+
},
|
|
58
|
+
warn(msg, ...args) {
|
|
59
|
+
if (shouldLog("warn")) {
|
|
60
|
+
console.warn(chalk.yellow(`! ${msg}`), ...args);
|
|
61
|
+
}
|
|
62
|
+
},
|
|
63
|
+
error(msg, ...args) {
|
|
64
|
+
if (shouldLog("error")) {
|
|
65
|
+
console.error(chalk.red(`\u2717 ${msg}`), ...args);
|
|
66
|
+
}
|
|
67
|
+
},
|
|
68
|
+
raw(msg, ...args) {
|
|
69
|
+
console.log(msg, ...args);
|
|
70
|
+
}
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
// src/utils/paths.ts
|
|
76
|
+
import { homedir } from "os";
|
|
77
|
+
import { resolve, normalize } from "path";
|
|
78
|
+
function expandHome(filepath) {
|
|
79
|
+
if (!filepath) return filepath;
|
|
80
|
+
if (filepath === "~") return homedir();
|
|
81
|
+
if (filepath.startsWith("~/") || filepath.startsWith("~\\")) {
|
|
82
|
+
return resolve(homedir(), filepath.slice(2));
|
|
83
|
+
}
|
|
84
|
+
if (filepath.startsWith("~")) {
|
|
85
|
+
return filepath;
|
|
86
|
+
}
|
|
87
|
+
return normalize(filepath);
|
|
88
|
+
}
|
|
89
|
+
function getHubRoot() {
|
|
90
|
+
const fromEnv = process.env.ASSETPLEX_DIR;
|
|
91
|
+
if (fromEnv) return expandHome(fromEnv);
|
|
92
|
+
return expandHome("~/.assetplex");
|
|
93
|
+
}
|
|
94
|
+
function hubPath(...segments) {
|
|
95
|
+
return resolve(getHubRoot(), ...segments);
|
|
96
|
+
}
|
|
97
|
+
var init_paths = __esm({
|
|
98
|
+
"src/utils/paths.ts"() {
|
|
99
|
+
"use strict";
|
|
100
|
+
init_esm_shims();
|
|
101
|
+
}
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
// src/core/config.ts
|
|
105
|
+
import { z } from "zod";
|
|
106
|
+
import * as TOML from "@iarna/toml";
|
|
107
|
+
import { readFileSync, writeFileSync, existsSync, mkdirSync } from "fs";
|
|
108
|
+
import { resolve as resolve2, dirname } from "path";
|
|
109
|
+
function loadHubConfig(configPath) {
|
|
110
|
+
const path2 = configPath ?? resolveHubConfigPath();
|
|
111
|
+
if (!existsSync(path2)) {
|
|
112
|
+
return HubConfigSchema.parse({});
|
|
113
|
+
}
|
|
114
|
+
const raw = readFileSync(path2, "utf-8");
|
|
115
|
+
const parsed = TOML.parse(raw);
|
|
116
|
+
return HubConfigSchema.parse(parsed);
|
|
117
|
+
}
|
|
118
|
+
function resolveHubConfigPath() {
|
|
119
|
+
const hubRoot = process.env.ASSETPLEX_DIR ?? "~/.assetplex";
|
|
120
|
+
return resolve2(expandHome(hubRoot), "hub.toml");
|
|
121
|
+
}
|
|
122
|
+
function saveHubConfig(config, configPath) {
|
|
123
|
+
const path2 = configPath ?? resolveHubConfigPath();
|
|
124
|
+
mkdirSync(dirname(path2), { recursive: true });
|
|
125
|
+
const validated = HubConfigSchema.parse(config);
|
|
126
|
+
const toml = TOML.stringify(validated);
|
|
127
|
+
writeFileSync(path2, toml, "utf-8");
|
|
128
|
+
}
|
|
129
|
+
function generateDefaultConfigToml() {
|
|
130
|
+
return `# AssetPlex Configuration
|
|
131
|
+
# See: https://github.com/wynter-cai/assetplex
|
|
132
|
+
|
|
133
|
+
[hub]
|
|
134
|
+
version = "1.0"
|
|
135
|
+
default_sync_strategy = "hybrid" # native-import | symlink | copy | hybrid | per-project
|
|
136
|
+
backup_dir = "~/.assetplex/.backups"
|
|
137
|
+
auto_watch = false
|
|
138
|
+
backup_keep_count = 10
|
|
139
|
+
|
|
140
|
+
[identity]
|
|
141
|
+
profile_auto_learn = true
|
|
142
|
+
learn_sources = ["trae-cn", "claude-code"]
|
|
143
|
+
learn_interval_hours = 24
|
|
144
|
+
learn_max_facts = 100
|
|
145
|
+
|
|
146
|
+
[tools.trae-cn]
|
|
147
|
+
enabled = true
|
|
148
|
+
config_dir = "~/.trae-cn"
|
|
149
|
+
sync_strategy = "symlink"
|
|
150
|
+
mcp_format = "json"
|
|
151
|
+
targets = [
|
|
152
|
+
"memory/user_profile.md",
|
|
153
|
+
"skills/",
|
|
154
|
+
"rules/",
|
|
155
|
+
"mcp.json",
|
|
156
|
+
]
|
|
157
|
+
|
|
158
|
+
[tools.claude-code]
|
|
159
|
+
enabled = true
|
|
160
|
+
config_dir = "~/.claude"
|
|
161
|
+
sync_strategy = "native-import"
|
|
162
|
+
import_max_depth = 4
|
|
163
|
+
claude_md_aggregator = true
|
|
164
|
+
|
|
165
|
+
[tools.codex]
|
|
166
|
+
enabled = true
|
|
167
|
+
config_dir = "~/.codex"
|
|
168
|
+
sync_strategy = "copy"
|
|
169
|
+
mcp_format = "toml"
|
|
170
|
+
strip_frontmatter_keys = []
|
|
171
|
+
|
|
172
|
+
[tools.workbuddy]
|
|
173
|
+
enabled = true
|
|
174
|
+
config_dir = "~/.workbuddy"
|
|
175
|
+
sync_strategy = "symlink"
|
|
176
|
+
env_interpolation = true
|
|
177
|
+
mcp_filename = ".mcp.json"
|
|
178
|
+
|
|
179
|
+
[tools.qoder]
|
|
180
|
+
enabled = false
|
|
181
|
+
sync_strategy = "per-project"
|
|
182
|
+
project_targets = []
|
|
183
|
+
|
|
184
|
+
[marketplace]
|
|
185
|
+
enabled = true
|
|
186
|
+
sources = [
|
|
187
|
+
"https://claudeskills.info/api",
|
|
188
|
+
"https://agentskills.io/api",
|
|
189
|
+
"https://agskills.dev/api",
|
|
190
|
+
]
|
|
191
|
+
cache_dir = "~/.assetplex/.marketplace-cache"
|
|
192
|
+
cache_ttl_hours = 24
|
|
193
|
+
`;
|
|
194
|
+
}
|
|
195
|
+
var SyncStrategySchema, McpFormatSchema, HubConfigSchema;
|
|
196
|
+
var init_config = __esm({
|
|
197
|
+
"src/core/config.ts"() {
|
|
198
|
+
"use strict";
|
|
199
|
+
init_esm_shims();
|
|
200
|
+
init_paths();
|
|
201
|
+
SyncStrategySchema = z.enum([
|
|
202
|
+
"native-import",
|
|
203
|
+
"symlink",
|
|
204
|
+
"copy",
|
|
205
|
+
"hybrid",
|
|
206
|
+
"per-project"
|
|
207
|
+
]);
|
|
208
|
+
McpFormatSchema = z.enum(["json", "toml"]);
|
|
209
|
+
HubConfigSchema = z.object({
|
|
210
|
+
hub: z.object({
|
|
211
|
+
version: z.string().default("1.0"),
|
|
212
|
+
defaultSyncStrategy: SyncStrategySchema.default("hybrid"),
|
|
213
|
+
backupDir: z.string().default("~/.assetplex/.backups"),
|
|
214
|
+
autoWatch: z.boolean().default(false),
|
|
215
|
+
backupKeepCount: z.number().int().positive().default(10)
|
|
216
|
+
}).default({}),
|
|
217
|
+
identity: z.object({
|
|
218
|
+
profileAutoLearn: z.boolean().default(true),
|
|
219
|
+
learnSources: z.array(z.string()).default(["trae-cn", "claude-code"]),
|
|
220
|
+
learnIntervalHours: z.number().int().positive().default(24),
|
|
221
|
+
learnMaxFacts: z.number().int().positive().default(100)
|
|
222
|
+
}).default({}),
|
|
223
|
+
tools: z.object({
|
|
224
|
+
"trae-cn": z.object({
|
|
225
|
+
enabled: z.boolean().default(true),
|
|
226
|
+
configDir: z.string().default("~/.trae-cn"),
|
|
227
|
+
syncStrategy: SyncStrategySchema.default("symlink"),
|
|
228
|
+
mcpFormat: McpFormatSchema.default("json"),
|
|
229
|
+
targets: z.array(z.string()).default([])
|
|
230
|
+
}).default({}),
|
|
231
|
+
"claude-code": z.object({
|
|
232
|
+
enabled: z.boolean().default(true),
|
|
233
|
+
configDir: z.string().default("~/.claude"),
|
|
234
|
+
syncStrategy: SyncStrategySchema.default("native-import"),
|
|
235
|
+
importMaxDepth: z.number().int().positive().max(5).default(4),
|
|
236
|
+
claudeMdAggregator: z.boolean().default(true)
|
|
237
|
+
}).default({}),
|
|
238
|
+
codex: z.object({
|
|
239
|
+
enabled: z.boolean().default(true),
|
|
240
|
+
configDir: z.string().default("~/.codex"),
|
|
241
|
+
syncStrategy: SyncStrategySchema.default("copy"),
|
|
242
|
+
mcpFormat: McpFormatSchema.default("toml"),
|
|
243
|
+
stripFrontmatterKeys: z.array(z.string()).default([])
|
|
244
|
+
}).default({}),
|
|
245
|
+
workbuddy: z.object({
|
|
246
|
+
enabled: z.boolean().default(true),
|
|
247
|
+
configDir: z.string().default("~/.workbuddy"),
|
|
248
|
+
syncStrategy: SyncStrategySchema.default("symlink"),
|
|
249
|
+
envInterpolation: z.boolean().default(true),
|
|
250
|
+
mcpFilename: z.string().default(".mcp.json")
|
|
251
|
+
}).default({}),
|
|
252
|
+
qoder: z.object({
|
|
253
|
+
enabled: z.boolean().default(false),
|
|
254
|
+
syncStrategy: SyncStrategySchema.default("per-project"),
|
|
255
|
+
projectTargets: z.array(z.string()).default([])
|
|
256
|
+
}).default({})
|
|
257
|
+
}).default({}),
|
|
258
|
+
marketplace: z.object({
|
|
259
|
+
enabled: z.boolean().default(true),
|
|
260
|
+
sources: z.array(z.string()).default([
|
|
261
|
+
"https://claudeskills.info/api",
|
|
262
|
+
"https://agentskills.io/api",
|
|
263
|
+
"https://agskills.dev/api"
|
|
264
|
+
]),
|
|
265
|
+
cacheDir: z.string().default("~/.assetplex/.marketplace-cache"),
|
|
266
|
+
cacheTtlHours: z.number().int().positive().default(24)
|
|
267
|
+
}).default({})
|
|
268
|
+
});
|
|
269
|
+
}
|
|
270
|
+
});
|
|
271
|
+
|
|
272
|
+
// src/utils/fs.ts
|
|
273
|
+
import {
|
|
274
|
+
existsSync as existsSync3,
|
|
275
|
+
mkdirSync as mkdirSync3,
|
|
276
|
+
readlinkSync,
|
|
277
|
+
unlinkSync,
|
|
278
|
+
rmdirSync,
|
|
279
|
+
rmSync,
|
|
280
|
+
copyFileSync,
|
|
281
|
+
readdirSync,
|
|
282
|
+
statSync
|
|
283
|
+
} from "fs";
|
|
284
|
+
import { resolve as resolve4, dirname as dirname2, isAbsolute, relative, join } from "path";
|
|
285
|
+
import { execSync } from "child_process";
|
|
286
|
+
function ensureDir(path2) {
|
|
287
|
+
if (!existsSync3(path2)) {
|
|
288
|
+
mkdirSync3(path2, { recursive: true });
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
function isSymlink(path2) {
|
|
292
|
+
if (!existsSync3(path2)) return false;
|
|
293
|
+
try {
|
|
294
|
+
readlinkSync(path2);
|
|
295
|
+
return true;
|
|
296
|
+
} catch {
|
|
297
|
+
return false;
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
function readSymlinkTarget(path2) {
|
|
301
|
+
try {
|
|
302
|
+
const target = readlinkSync(path2);
|
|
303
|
+
if (isAbsolute(target)) return target;
|
|
304
|
+
return resolve4(dirname2(path2), target);
|
|
305
|
+
} catch {
|
|
306
|
+
return null;
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
function safeRemove(path2) {
|
|
310
|
+
if (!existsSync3(path2) && !isSymlink(path2)) return;
|
|
311
|
+
if (isSymlink(path2)) {
|
|
312
|
+
try {
|
|
313
|
+
unlinkSync(path2);
|
|
314
|
+
return;
|
|
315
|
+
} catch {
|
|
316
|
+
try {
|
|
317
|
+
rmdirSync(path2);
|
|
318
|
+
return;
|
|
319
|
+
} catch {
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
try {
|
|
324
|
+
rmSync(path2, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 });
|
|
325
|
+
} catch {
|
|
326
|
+
}
|
|
327
|
+
if (!existsSync3(path2)) return;
|
|
328
|
+
if (process.platform === "win32") {
|
|
329
|
+
try {
|
|
330
|
+
execSync(`rmdir /S /Q "${path2}"`, { stdio: "ignore" });
|
|
331
|
+
} catch {
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
for (let i = 0; i < 5; i++) {
|
|
335
|
+
if (!existsSync3(path2)) return;
|
|
336
|
+
const start = Date.now();
|
|
337
|
+
while (Date.now() - start < 100) {
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
function copyRecursive(src, dest) {
|
|
342
|
+
if (!existsSync3(src)) {
|
|
343
|
+
throw new Error(`\u6E90\u8DEF\u5F84\u4E0D\u5B58\u5728: ${src}`);
|
|
344
|
+
}
|
|
345
|
+
const stat = statSync(src);
|
|
346
|
+
if (stat.isFile()) {
|
|
347
|
+
ensureDir(dirname2(dest));
|
|
348
|
+
copyFileSync(src, dest);
|
|
349
|
+
return;
|
|
350
|
+
}
|
|
351
|
+
if (stat.isDirectory()) {
|
|
352
|
+
if (isSymlink(dest)) {
|
|
353
|
+
safeRemove(dest);
|
|
354
|
+
}
|
|
355
|
+
ensureDir(dest);
|
|
356
|
+
for (const entry of readdirSync(src)) {
|
|
357
|
+
const srcPath = resolve4(src, entry);
|
|
358
|
+
const destPath = resolve4(dest, entry);
|
|
359
|
+
copyRecursive(srcPath, destPath);
|
|
360
|
+
}
|
|
361
|
+
return;
|
|
362
|
+
}
|
|
363
|
+
throw new Error(`\u4E0D\u652F\u6301\u7684\u6587\u4EF6\u7C7B\u578B: ${src}`);
|
|
364
|
+
}
|
|
365
|
+
function listMarkdownFilesRecursive(dir) {
|
|
366
|
+
if (!existsSync3(dir)) return [];
|
|
367
|
+
const result = [];
|
|
368
|
+
const walk = (current, prefix) => {
|
|
369
|
+
let entries;
|
|
370
|
+
try {
|
|
371
|
+
entries = readdirSync(current);
|
|
372
|
+
} catch {
|
|
373
|
+
return;
|
|
374
|
+
}
|
|
375
|
+
for (const name of entries) {
|
|
376
|
+
const full = join(current, name);
|
|
377
|
+
let stat;
|
|
378
|
+
try {
|
|
379
|
+
stat = statSync(full);
|
|
380
|
+
} catch {
|
|
381
|
+
continue;
|
|
382
|
+
}
|
|
383
|
+
if (stat.isDirectory()) {
|
|
384
|
+
walk(full, `${prefix}${name}/`);
|
|
385
|
+
} else if (name.toLowerCase().endsWith(".md")) {
|
|
386
|
+
result.push(`${prefix}${name}`);
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
};
|
|
390
|
+
walk(dir, "");
|
|
391
|
+
return result.sort((a, b) => a.localeCompare(b));
|
|
392
|
+
}
|
|
393
|
+
var init_fs = __esm({
|
|
394
|
+
"src/utils/fs.ts"() {
|
|
395
|
+
"use strict";
|
|
396
|
+
init_esm_shims();
|
|
397
|
+
}
|
|
398
|
+
});
|
|
399
|
+
|
|
400
|
+
// src/transforms/symlink.ts
|
|
401
|
+
import { symlink, existsSync as existsSync4, lstatSync, readlinkSync as readlinkSync2, readdirSync as readdirSync2, rmdirSync as rmdirSync2 } from "fs";
|
|
402
|
+
import { dirname as dirname3, resolve as resolve5, isAbsolute as isAbsolute2 } from "path";
|
|
403
|
+
function isEmptyDir(path2) {
|
|
404
|
+
try {
|
|
405
|
+
const stat = lstatSync(path2);
|
|
406
|
+
if (!stat.isDirectory()) return false;
|
|
407
|
+
if (isSymlink(path2)) return false;
|
|
408
|
+
return readdirSync2(path2).length === 0;
|
|
409
|
+
} catch {
|
|
410
|
+
return false;
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
async function createSymlink(target, linkPath, options = {}) {
|
|
414
|
+
const { isDirectory = false, force = false } = options;
|
|
415
|
+
if (!existsSync4(target)) {
|
|
416
|
+
return {
|
|
417
|
+
success: false,
|
|
418
|
+
method: "symlink",
|
|
419
|
+
message: `\u6E90\u8DEF\u5F84\u4E0D\u5B58\u5728: ${target}`
|
|
420
|
+
};
|
|
421
|
+
}
|
|
422
|
+
ensureDir(dirname3(linkPath));
|
|
423
|
+
if (existsSync4(linkPath) || isSymlink(linkPath)) {
|
|
424
|
+
if (!force) {
|
|
425
|
+
const currentTarget = readLinkTargetSafe(linkPath);
|
|
426
|
+
if (currentTarget && resolve5(currentTarget) === resolve5(target)) {
|
|
427
|
+
return {
|
|
428
|
+
success: true,
|
|
429
|
+
method: "symlink",
|
|
430
|
+
message: "\u94FE\u63A5\u5DF2\u5B58\u5728\u4E14\u6307\u5411\u6B63\u786E\u76EE\u6807\uFF0C\u8DF3\u8FC7"
|
|
431
|
+
};
|
|
432
|
+
}
|
|
433
|
+
return {
|
|
434
|
+
success: false,
|
|
435
|
+
method: "symlink",
|
|
436
|
+
message: `\u76EE\u6807\u5DF2\u5B58\u5728: ${linkPath}\uFF08\u4F7F\u7528 force: true \u8986\u76D6\uFF09`
|
|
437
|
+
};
|
|
438
|
+
}
|
|
439
|
+
safeRemove(linkPath);
|
|
440
|
+
if (existsSync4(linkPath) && isEmptyDir(linkPath)) {
|
|
441
|
+
try {
|
|
442
|
+
rmdirSync2(linkPath);
|
|
443
|
+
} catch {
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
const symlinkType = isDirectory ? "dir" : "file";
|
|
448
|
+
try {
|
|
449
|
+
await new Promise((resolvePromise, rejectPromise) => {
|
|
450
|
+
symlink(target, linkPath, symlinkType, (err) => {
|
|
451
|
+
if (err) rejectPromise(err);
|
|
452
|
+
else resolvePromise();
|
|
453
|
+
});
|
|
454
|
+
});
|
|
455
|
+
if (existsSync4(linkPath)) {
|
|
456
|
+
return { success: true, method: "symlink" };
|
|
457
|
+
}
|
|
458
|
+
} catch (err) {
|
|
459
|
+
if (process.platform !== "win32") {
|
|
460
|
+
return {
|
|
461
|
+
success: false,
|
|
462
|
+
method: "symlink",
|
|
463
|
+
message: `symlink \u521B\u5EFA\u5931\u8D25: ${err.message}`
|
|
464
|
+
};
|
|
465
|
+
}
|
|
466
|
+
}
|
|
467
|
+
if (isDirectory && process.platform === "win32") {
|
|
468
|
+
try {
|
|
469
|
+
await new Promise((resolvePromise, rejectPromise) => {
|
|
470
|
+
symlink(target, linkPath, "junction", (err) => {
|
|
471
|
+
if (err) rejectPromise(err);
|
|
472
|
+
else resolvePromise();
|
|
473
|
+
});
|
|
474
|
+
});
|
|
475
|
+
if (existsSync4(linkPath)) {
|
|
476
|
+
return {
|
|
477
|
+
success: true,
|
|
478
|
+
method: "junction",
|
|
479
|
+
message: "\u4F7F\u7528 junction\uFF08\u5EFA\u8BAE\u5F00\u542F Windows \u5F00\u53D1\u8005\u6A21\u5F0F\u4EE5\u83B7\u5F97\u539F\u751F symlink\uFF09"
|
|
480
|
+
};
|
|
481
|
+
}
|
|
482
|
+
} catch {
|
|
483
|
+
}
|
|
484
|
+
}
|
|
485
|
+
try {
|
|
486
|
+
copyRecursive(target, linkPath);
|
|
487
|
+
return {
|
|
488
|
+
success: true,
|
|
489
|
+
method: "copy",
|
|
490
|
+
message: "\u964D\u7EA7\u4E3A copy\uFF08\u5EFA\u8BAE\u5F00\u542F Windows \u5F00\u53D1\u8005\u6A21\u5F0F\u4EE5\u83B7\u5F97\u539F\u751F symlink\uFF09"
|
|
491
|
+
};
|
|
492
|
+
} catch (err) {
|
|
493
|
+
return {
|
|
494
|
+
success: false,
|
|
495
|
+
method: "copy",
|
|
496
|
+
message: `copy fallback \u5931\u8D25: ${err.message}`
|
|
497
|
+
};
|
|
498
|
+
}
|
|
499
|
+
}
|
|
500
|
+
function readLinkTargetSafe(linkPath) {
|
|
501
|
+
try {
|
|
502
|
+
if (!existsSync4(linkPath) && !isSymlink(linkPath)) return null;
|
|
503
|
+
const stat = lstatSync(linkPath);
|
|
504
|
+
if (!stat.isSymbolicLink()) return null;
|
|
505
|
+
const target = readlinkSync2(linkPath);
|
|
506
|
+
if (isAbsolute2(target)) return target;
|
|
507
|
+
return resolve5(dirname3(linkPath), target);
|
|
508
|
+
} catch {
|
|
509
|
+
return null;
|
|
510
|
+
}
|
|
511
|
+
}
|
|
512
|
+
var init_symlink = __esm({
|
|
513
|
+
"src/transforms/symlink.ts"() {
|
|
514
|
+
"use strict";
|
|
515
|
+
init_esm_shims();
|
|
516
|
+
init_fs();
|
|
517
|
+
}
|
|
518
|
+
});
|
|
519
|
+
|
|
520
|
+
// src/core/adapters/base.ts
|
|
521
|
+
import { existsSync as existsSync5, readFileSync as readFileSync2, statSync as statSync2, readdirSync as readdirSync3 } from "fs";
|
|
522
|
+
import { resolve as resolve6, normalize as normalize2, relative as relative2, join as join2 } from "path";
|
|
523
|
+
import { homedir as homedir2 } from "os";
|
|
524
|
+
var BaseAdapter;
|
|
525
|
+
var init_base = __esm({
|
|
526
|
+
"src/core/adapters/base.ts"() {
|
|
527
|
+
"use strict";
|
|
528
|
+
init_esm_shims();
|
|
529
|
+
init_logger();
|
|
530
|
+
init_fs();
|
|
531
|
+
init_symlink();
|
|
532
|
+
init_paths();
|
|
533
|
+
BaseAdapter = class {
|
|
534
|
+
homepage;
|
|
535
|
+
/**
|
|
536
|
+
* 默认 detect 实现:检查配置目录是否存在,是否有标志性文件
|
|
537
|
+
*/
|
|
538
|
+
async detect() {
|
|
539
|
+
const configDir = this.getConfigDir();
|
|
540
|
+
const configDirExists = configDir ? existsSync5(configDir) : false;
|
|
541
|
+
let installed = configDirExists;
|
|
542
|
+
if (configDirExists) {
|
|
543
|
+
let hasSignature = false;
|
|
544
|
+
for (const file of this.signatureFiles) {
|
|
545
|
+
if (existsSync5(resolve6(configDir, file))) {
|
|
546
|
+
hasSignature = true;
|
|
547
|
+
break;
|
|
548
|
+
}
|
|
549
|
+
}
|
|
550
|
+
installed = hasSignature;
|
|
551
|
+
}
|
|
552
|
+
let version;
|
|
553
|
+
try {
|
|
554
|
+
version = await this.detectVersion();
|
|
555
|
+
} catch {
|
|
556
|
+
}
|
|
557
|
+
return {
|
|
558
|
+
name: this.name,
|
|
559
|
+
installed,
|
|
560
|
+
configDirExists,
|
|
561
|
+
version,
|
|
562
|
+
configDir,
|
|
563
|
+
error: installed ? void 0 : configDirExists ? "\u7F3A\u5C11\u6807\u5FD7\u6027\u6587\u4EF6" : "\u672A\u5B89\u88C5"
|
|
564
|
+
};
|
|
565
|
+
}
|
|
566
|
+
/**
|
|
567
|
+
* 获取工具配置目录(子类可重写以支持环境变量重定向)
|
|
568
|
+
*/
|
|
569
|
+
getConfigDir() {
|
|
570
|
+
return this.expandHome(this.defaultConfigDir);
|
|
571
|
+
}
|
|
572
|
+
/**
|
|
573
|
+
* 默认 resolveHubItems 实现:基于 targets() 返回路径,HubItem.relativePath 留空
|
|
574
|
+
*
|
|
575
|
+
* 子类应重写此方法以提供准确的 HubItem 关联
|
|
576
|
+
*/
|
|
577
|
+
resolveHubItems(hubConfig, _hubRoot) {
|
|
578
|
+
const toolConfig = this.getToolConfig(hubConfig);
|
|
579
|
+
if (!toolConfig) return [];
|
|
580
|
+
const targets = this.targets(toolConfig);
|
|
581
|
+
return targets.map((target) => ({
|
|
582
|
+
item: {
|
|
583
|
+
type: "preference",
|
|
584
|
+
relativePath: "",
|
|
585
|
+
absolutePath: ""
|
|
586
|
+
},
|
|
587
|
+
target
|
|
588
|
+
}));
|
|
589
|
+
}
|
|
590
|
+
/**
|
|
591
|
+
* 默认 apply 实现:根据 SyncTarget.strategy 派发
|
|
592
|
+
* - symlink: 用 createSymlink()
|
|
593
|
+
* - copy: 用 copyRecursive()
|
|
594
|
+
* - native-import: 调用子类的 applyNativeImport()
|
|
595
|
+
* - per-project: 调用子类的 applyPerProject()
|
|
596
|
+
*/
|
|
597
|
+
async apply(item, target) {
|
|
598
|
+
if (!item.absolutePath) {
|
|
599
|
+
throw new Error(`HubItem.absolutePath \u4E3A\u7A7A\uFF0C\u65E0\u6CD5 apply \u5230 ${target.targetPath}`);
|
|
600
|
+
}
|
|
601
|
+
switch (target.strategy) {
|
|
602
|
+
case "symlink": {
|
|
603
|
+
const r = await createSymlink(item.absolutePath, target.targetPath, {
|
|
604
|
+
isDirectory: target.isDirectory,
|
|
605
|
+
force: true
|
|
606
|
+
});
|
|
607
|
+
if (!r.success) {
|
|
608
|
+
throw new Error(`${this.name}: \u521B\u5EFA symlink \u5931\u8D25 - ${r.message}`);
|
|
609
|
+
}
|
|
610
|
+
if (r.method === "copy") {
|
|
611
|
+
log.warn(`${this.name}: symlink \u964D\u7EA7\u4E3A copy\uFF08${r.message}\uFF09`);
|
|
612
|
+
} else if (r.method === "junction") {
|
|
613
|
+
log.debug(`${this.name}: \u4F7F\u7528 junction\uFF08${r.message ?? ""}\uFF09`);
|
|
614
|
+
}
|
|
615
|
+
break;
|
|
616
|
+
}
|
|
617
|
+
case "copy": {
|
|
618
|
+
copyRecursive(item.absolutePath, target.targetPath);
|
|
619
|
+
break;
|
|
620
|
+
}
|
|
621
|
+
case "native-import": {
|
|
622
|
+
await this.applyNativeImport(item, target);
|
|
623
|
+
break;
|
|
624
|
+
}
|
|
625
|
+
case "per-project": {
|
|
626
|
+
await this.applyPerProject(item, target);
|
|
627
|
+
break;
|
|
628
|
+
}
|
|
629
|
+
default:
|
|
630
|
+
throw new Error(`\u672A\u77E5\u7684\u540C\u6B65\u7B56\u7565: ${target.strategy}`);
|
|
631
|
+
}
|
|
632
|
+
}
|
|
633
|
+
/** 子类可选实现:native-import 策略的具体逻辑 */
|
|
634
|
+
async applyNativeImport(_item, _target) {
|
|
635
|
+
throw new Error(`${this.name} \u4E0D\u652F\u6301 native-import \u7B56\u7565`);
|
|
636
|
+
}
|
|
637
|
+
/** 子类可选实现:per-project 策略的具体逻辑 */
|
|
638
|
+
async applyPerProject(_item, _target) {
|
|
639
|
+
throw new Error(`${this.name} \u4E0D\u652F\u6301 per-project \u7B56\u7565`);
|
|
640
|
+
}
|
|
641
|
+
/**
|
|
642
|
+
* 默认 import 实现:读取目标文件内容返回 HubItem
|
|
643
|
+
*/
|
|
644
|
+
async import(targetPath) {
|
|
645
|
+
const content = readFileSync2(targetPath);
|
|
646
|
+
return {
|
|
647
|
+
type: "preference",
|
|
648
|
+
relativePath: "",
|
|
649
|
+
absolutePath: targetPath,
|
|
650
|
+
content
|
|
651
|
+
};
|
|
652
|
+
}
|
|
653
|
+
/** 默认 transform:直接返回原内容 */
|
|
654
|
+
transform(content, _format) {
|
|
655
|
+
return content;
|
|
656
|
+
}
|
|
657
|
+
/**
|
|
658
|
+
* 默认 scan 实现:返回空数组。
|
|
659
|
+
* 子类必须重写此方法以扫描工具目录中的可导入内容。
|
|
660
|
+
*/
|
|
661
|
+
async scan() {
|
|
662
|
+
return [];
|
|
663
|
+
}
|
|
664
|
+
/**
|
|
665
|
+
* 辅助方法:为单个文件创建 DiscoveredItem,自动检测冲突状态
|
|
666
|
+
*
|
|
667
|
+
* @param absolutePath 源文件绝对路径
|
|
668
|
+
* @param baseDir 工具配置目录(用于计算 relativePath)
|
|
669
|
+
* @param hubTargetPath 导入到 Hub 的目标相对路径
|
|
670
|
+
* @param category 文件类别
|
|
671
|
+
* @returns DiscoveredItem
|
|
672
|
+
*/
|
|
673
|
+
makeDiscoveredItem(absolutePath, baseDir, hubTargetPath, category) {
|
|
674
|
+
try {
|
|
675
|
+
const stat = statSync2(absolutePath);
|
|
676
|
+
if (!stat.isFile()) return null;
|
|
677
|
+
const relPath = relative2(baseDir, absolutePath);
|
|
678
|
+
const hubFullPath = hubPath(hubTargetPath);
|
|
679
|
+
let conflict = "none";
|
|
680
|
+
let existingSize;
|
|
681
|
+
if (existsSync5(hubFullPath)) {
|
|
682
|
+
try {
|
|
683
|
+
const existingStat = statSync2(hubFullPath);
|
|
684
|
+
existingSize = existingStat.size;
|
|
685
|
+
if (existingStat.size === stat.size) {
|
|
686
|
+
const existingContent = readFileSync2(hubFullPath);
|
|
687
|
+
const newContent = readFileSync2(absolutePath);
|
|
688
|
+
conflict = existingContent.equals(newContent) ? "exists" : "differs";
|
|
689
|
+
} else {
|
|
690
|
+
conflict = "differs";
|
|
691
|
+
}
|
|
692
|
+
} catch {
|
|
693
|
+
conflict = "differs";
|
|
694
|
+
}
|
|
695
|
+
}
|
|
696
|
+
return {
|
|
697
|
+
absolutePath,
|
|
698
|
+
relativePath: relPath,
|
|
699
|
+
size: stat.size,
|
|
700
|
+
modified: stat.mtime.toISOString(),
|
|
701
|
+
category,
|
|
702
|
+
hubTargetPath,
|
|
703
|
+
conflict,
|
|
704
|
+
existingSize,
|
|
705
|
+
tool: this.name
|
|
706
|
+
};
|
|
707
|
+
} catch {
|
|
708
|
+
return null;
|
|
709
|
+
}
|
|
710
|
+
}
|
|
711
|
+
/**
|
|
712
|
+
* 辅助方法:递归扫描目录下所有文件,为每个文件创建 DiscoveredItem
|
|
713
|
+
*
|
|
714
|
+
* @param dirPath 要扫描的目录绝对路径
|
|
715
|
+
* @param baseDir 工具配置目录(用于计算 relativePath)
|
|
716
|
+
* @param hubPrefix Hub 目标路径前缀(如 'skills'、'rules')
|
|
717
|
+
* @param category 文件类别
|
|
718
|
+
* @returns DiscoveredItem[]
|
|
719
|
+
*/
|
|
720
|
+
scanDirectory(dirPath, baseDir, hubPrefix, category) {
|
|
721
|
+
if (!existsSync5(dirPath)) return [];
|
|
722
|
+
const results = [];
|
|
723
|
+
const walk = (currentDir) => {
|
|
724
|
+
try {
|
|
725
|
+
const entries = readdirSync3(currentDir, { withFileTypes: true });
|
|
726
|
+
for (const entry of entries) {
|
|
727
|
+
const fullPath = join2(currentDir, entry.name);
|
|
728
|
+
if (entry.isDirectory()) {
|
|
729
|
+
walk(fullPath);
|
|
730
|
+
} else if (entry.isFile()) {
|
|
731
|
+
const relFromDir = relative2(dirPath, fullPath);
|
|
732
|
+
const hubTarget = `${hubPrefix}/${relFromDir.split(/[/\\]/).join("/")}`;
|
|
733
|
+
const item = this.makeDiscoveredItem(fullPath, baseDir, hubTarget, category);
|
|
734
|
+
if (item) results.push(item);
|
|
735
|
+
}
|
|
736
|
+
}
|
|
737
|
+
} catch {
|
|
738
|
+
}
|
|
739
|
+
};
|
|
740
|
+
walk(dirPath);
|
|
741
|
+
return results;
|
|
742
|
+
}
|
|
743
|
+
/**
|
|
744
|
+
* 默认版本检测:返回 undefined,子类可重写
|
|
745
|
+
*/
|
|
746
|
+
async detectVersion() {
|
|
747
|
+
return void 0;
|
|
748
|
+
}
|
|
749
|
+
/**
|
|
750
|
+
* 从 hubConfig 中提取当前工具的配置
|
|
751
|
+
*/
|
|
752
|
+
getToolConfig(hubConfig) {
|
|
753
|
+
const tools = hubConfig.tools;
|
|
754
|
+
const raw = tools[this.name];
|
|
755
|
+
if (!raw) return null;
|
|
756
|
+
return {
|
|
757
|
+
enabled: raw.enabled ?? false,
|
|
758
|
+
configDir: raw.configDir ?? "",
|
|
759
|
+
syncStrategy: raw.syncStrategy ?? "hybrid",
|
|
760
|
+
mcpFormat: raw.mcpFormat,
|
|
761
|
+
...raw
|
|
762
|
+
};
|
|
763
|
+
}
|
|
764
|
+
/**
|
|
765
|
+
* 展开 ~ 为 home 目录
|
|
766
|
+
*/
|
|
767
|
+
expandHome(filepath) {
|
|
768
|
+
if (!filepath) return filepath;
|
|
769
|
+
if (filepath === "~") return homedir2();
|
|
770
|
+
if (filepath.startsWith("~/") || filepath.startsWith("~\\")) {
|
|
771
|
+
return normalize2(resolve6(homedir2(), filepath.slice(2)));
|
|
772
|
+
}
|
|
773
|
+
return normalize2(filepath);
|
|
774
|
+
}
|
|
775
|
+
};
|
|
776
|
+
}
|
|
777
|
+
});
|
|
778
|
+
|
|
779
|
+
// src/core/adapters/trae-cn.ts
|
|
780
|
+
import { existsSync as existsSync6 } from "fs";
|
|
781
|
+
import { resolve as resolve7 } from "path";
|
|
782
|
+
var TraeCnAdapter;
|
|
783
|
+
var init_trae_cn = __esm({
|
|
784
|
+
"src/core/adapters/trae-cn.ts"() {
|
|
785
|
+
"use strict";
|
|
786
|
+
init_esm_shims();
|
|
787
|
+
init_base();
|
|
788
|
+
init_paths();
|
|
789
|
+
TraeCnAdapter = class extends BaseAdapter {
|
|
790
|
+
name = "trae-cn";
|
|
791
|
+
displayName = "TRAE \u4E2D\u56FD\u7248";
|
|
792
|
+
homepage = "https://docs.trae.ai/";
|
|
793
|
+
defaultConfigDir = "~/.trae-cn";
|
|
794
|
+
signatureFiles = ["argv.json", "skill-config.json"];
|
|
795
|
+
supportedTargets = [
|
|
796
|
+
"memory/user_profile.md",
|
|
797
|
+
"skills/",
|
|
798
|
+
"rules/",
|
|
799
|
+
"mcp.json"
|
|
800
|
+
];
|
|
801
|
+
/**
|
|
802
|
+
* 重写 detect:TRAE CN 的特殊检测逻辑
|
|
803
|
+
* - 检查 ~/.trae-cn/argv.json 是否存在
|
|
804
|
+
* - 检查 ~/.trae-cn/memory/user_profile.md 是否存在(身份画像源)
|
|
805
|
+
*/
|
|
806
|
+
async detect() {
|
|
807
|
+
const configDir = this.getConfigDir();
|
|
808
|
+
const argvPath = resolve7(configDir, "argv.json");
|
|
809
|
+
const skillConfigPath = resolve7(configDir, "skill-config.json");
|
|
810
|
+
const profilePath = resolve7(configDir, "memory/user_profile.md");
|
|
811
|
+
const configDirExists = existsSync6(configDir);
|
|
812
|
+
const hasArgv = existsSync6(argvPath);
|
|
813
|
+
const hasSkillConfig = existsSync6(skillConfigPath);
|
|
814
|
+
const hasProfile = existsSync6(profilePath);
|
|
815
|
+
const installed = configDirExists && (hasArgv || hasSkillConfig);
|
|
816
|
+
let extraInfo;
|
|
817
|
+
if (installed && !hasProfile) {
|
|
818
|
+
extraInfo = "\u5DF2\u5B89\u88C5\uFF0C\u4F46 memory/user_profile.md \u7F3A\u5931\uFF08\u53EF\u8FD0\u884C assetplex profile learn \u751F\u6210\uFF09";
|
|
819
|
+
}
|
|
820
|
+
let version;
|
|
821
|
+
const versionPath = resolve7(configDir, "builtin/ide_version.json");
|
|
822
|
+
if (existsSync6(versionPath)) {
|
|
823
|
+
try {
|
|
824
|
+
const { readFileSync: readFileSync9 } = await import("fs");
|
|
825
|
+
const data = JSON.parse(readFileSync9(versionPath, "utf-8"));
|
|
826
|
+
version = data.version ?? data.ideVersion;
|
|
827
|
+
} catch {
|
|
828
|
+
}
|
|
829
|
+
}
|
|
830
|
+
return {
|
|
831
|
+
name: this.name,
|
|
832
|
+
installed,
|
|
833
|
+
configDirExists,
|
|
834
|
+
version,
|
|
835
|
+
configDir,
|
|
836
|
+
error: installed ? extraInfo : configDirExists ? "\u7F3A\u5C11\u6807\u5FD7\u6027\u6587\u4EF6" : "\u672A\u5B89\u88C5"
|
|
837
|
+
};
|
|
838
|
+
}
|
|
839
|
+
/**
|
|
840
|
+
* 重写 resolveHubItems:明确关联 Hub 内文件与 TRAE CN 目标
|
|
841
|
+
*
|
|
842
|
+
* 关联关系:
|
|
843
|
+
* - Hub: identity/profile.md → TRAE: memory/user_profile.md (symlink)
|
|
844
|
+
* - Hub: skills/ → TRAE: skills (symlink, dir)
|
|
845
|
+
* - Hub: rules/ → TRAE: rules (symlink, dir)
|
|
846
|
+
* - Hub: mcp/mcp.sources.json → TRAE: mcp.json (copy, JSON 格式直接复制)
|
|
847
|
+
*/
|
|
848
|
+
resolveHubItems(_hubConfig, _hubRoot) {
|
|
849
|
+
const base = this.getConfigDir();
|
|
850
|
+
return [
|
|
851
|
+
{
|
|
852
|
+
item: {
|
|
853
|
+
type: "identity",
|
|
854
|
+
relativePath: "identity/profile.md",
|
|
855
|
+
absolutePath: hubPath("identity/profile.md")
|
|
856
|
+
},
|
|
857
|
+
target: {
|
|
858
|
+
tool: this.name,
|
|
859
|
+
targetPath: resolve7(base, "memory/user_profile.md"),
|
|
860
|
+
strategy: "symlink",
|
|
861
|
+
isDirectory: false
|
|
862
|
+
}
|
|
863
|
+
},
|
|
864
|
+
{
|
|
865
|
+
item: {
|
|
866
|
+
type: "skill",
|
|
867
|
+
relativePath: "skills",
|
|
868
|
+
absolutePath: hubPath("skills")
|
|
869
|
+
},
|
|
870
|
+
target: {
|
|
871
|
+
tool: this.name,
|
|
872
|
+
targetPath: resolve7(base, "skills"),
|
|
873
|
+
strategy: "symlink",
|
|
874
|
+
isDirectory: true
|
|
875
|
+
}
|
|
876
|
+
},
|
|
877
|
+
{
|
|
878
|
+
item: {
|
|
879
|
+
type: "rule",
|
|
880
|
+
relativePath: "rules",
|
|
881
|
+
absolutePath: hubPath("rules")
|
|
882
|
+
},
|
|
883
|
+
target: {
|
|
884
|
+
tool: this.name,
|
|
885
|
+
targetPath: resolve7(base, "rules"),
|
|
886
|
+
strategy: "symlink",
|
|
887
|
+
isDirectory: true
|
|
888
|
+
}
|
|
889
|
+
},
|
|
890
|
+
{
|
|
891
|
+
item: {
|
|
892
|
+
type: "mcp",
|
|
893
|
+
relativePath: "mcp/mcp.sources.json",
|
|
894
|
+
absolutePath: hubPath("mcp/mcp.sources.json")
|
|
895
|
+
},
|
|
896
|
+
target: {
|
|
897
|
+
tool: this.name,
|
|
898
|
+
targetPath: resolve7(base, "mcp.json"),
|
|
899
|
+
strategy: "copy",
|
|
900
|
+
isDirectory: false
|
|
901
|
+
}
|
|
902
|
+
}
|
|
903
|
+
];
|
|
904
|
+
}
|
|
905
|
+
/**
|
|
906
|
+
* 同步目标列表(向后兼容)
|
|
907
|
+
*/
|
|
908
|
+
targets(config) {
|
|
909
|
+
const configDir = config.configDir || this.defaultConfigDir;
|
|
910
|
+
const base = this.expandHome(configDir);
|
|
911
|
+
return [
|
|
912
|
+
{
|
|
913
|
+
tool: this.name,
|
|
914
|
+
targetPath: resolve7(base, "memory/user_profile.md"),
|
|
915
|
+
strategy: "symlink",
|
|
916
|
+
isDirectory: false
|
|
917
|
+
},
|
|
918
|
+
{
|
|
919
|
+
tool: this.name,
|
|
920
|
+
targetPath: resolve7(base, "skills"),
|
|
921
|
+
strategy: "symlink",
|
|
922
|
+
isDirectory: true
|
|
923
|
+
},
|
|
924
|
+
{
|
|
925
|
+
tool: this.name,
|
|
926
|
+
targetPath: resolve7(base, "rules"),
|
|
927
|
+
strategy: "symlink",
|
|
928
|
+
isDirectory: true
|
|
929
|
+
},
|
|
930
|
+
{
|
|
931
|
+
tool: this.name,
|
|
932
|
+
targetPath: resolve7(base, "mcp.json"),
|
|
933
|
+
strategy: "copy",
|
|
934
|
+
isDirectory: false
|
|
935
|
+
}
|
|
936
|
+
];
|
|
937
|
+
}
|
|
938
|
+
/**
|
|
939
|
+
* 反向导入:从 TRAE CN 读取现有配置到 Hub
|
|
940
|
+
*
|
|
941
|
+
* 注意:memory/user_profile.md 是 TRAE AI 维护的,反向导入会覆盖 Hub 的 profile.md
|
|
942
|
+
*/
|
|
943
|
+
async import(targetPath) {
|
|
944
|
+
const { readFileSync: readFileSync9 } = await import("fs");
|
|
945
|
+
const content = readFileSync9(targetPath);
|
|
946
|
+
let type = "preference";
|
|
947
|
+
if (targetPath.includes("user_profile")) type = "identity";
|
|
948
|
+
else if (targetPath.includes("skills")) type = "skill";
|
|
949
|
+
else if (targetPath.includes("rules")) type = "rule";
|
|
950
|
+
else if (targetPath.includes("mcp")) type = "mcp";
|
|
951
|
+
return {
|
|
952
|
+
type,
|
|
953
|
+
relativePath: "",
|
|
954
|
+
absolutePath: targetPath,
|
|
955
|
+
content
|
|
956
|
+
};
|
|
957
|
+
}
|
|
958
|
+
/**
|
|
959
|
+
* 扫描 TRAE CN 目录,发现所有可导入内容
|
|
960
|
+
*/
|
|
961
|
+
async scan() {
|
|
962
|
+
const base = this.getConfigDir();
|
|
963
|
+
const items = [];
|
|
964
|
+
const profilePath = resolve7(base, "memory/user_profile.md");
|
|
965
|
+
const profileItem = this.makeDiscoveredItem(profilePath, base, "identity/profile.md", "identity");
|
|
966
|
+
if (profileItem) items.push(profileItem);
|
|
967
|
+
const memoryDir = resolve7(base, "memory");
|
|
968
|
+
if (existsSync6(memoryDir)) {
|
|
969
|
+
const { readdirSync: readdirSync5 } = await import("fs");
|
|
970
|
+
try {
|
|
971
|
+
const memoryEntries = readdirSync5(memoryDir, { withFileTypes: true });
|
|
972
|
+
for (const entry of memoryEntries) {
|
|
973
|
+
if (entry.isFile() && entry.name.endsWith(".md") && entry.name !== "user_profile.md") {
|
|
974
|
+
const fullPath = resolve7(memoryDir, entry.name);
|
|
975
|
+
const item = this.makeDiscoveredItem(fullPath, base, `preferences/${entry.name}`, "preference");
|
|
976
|
+
if (item) items.push(item);
|
|
977
|
+
}
|
|
978
|
+
}
|
|
979
|
+
} catch {
|
|
980
|
+
}
|
|
981
|
+
}
|
|
982
|
+
items.push(...this.scanDirectory(resolve7(base, "skills"), base, "skills", "skill"));
|
|
983
|
+
items.push(...this.scanDirectory(resolve7(base, "rules"), base, "rules", "rule"));
|
|
984
|
+
const mcpPath = resolve7(base, "mcp.json");
|
|
985
|
+
const mcpItem = this.makeDiscoveredItem(mcpPath, base, "mcp/mcp.sources.json", "mcp");
|
|
986
|
+
if (mcpItem) items.push(mcpItem);
|
|
987
|
+
return items;
|
|
988
|
+
}
|
|
989
|
+
};
|
|
990
|
+
}
|
|
991
|
+
});
|
|
992
|
+
|
|
993
|
+
// src/core/adapters/claude-code.ts
|
|
994
|
+
import { existsSync as existsSync7, writeFileSync as writeFileSync3, readFileSync as readFileSync3 } from "fs";
|
|
995
|
+
import { resolve as resolve8, basename } from "path";
|
|
996
|
+
import { homedir as homedir3 } from "os";
|
|
997
|
+
function buildImportList() {
|
|
998
|
+
const rulesImports = listMarkdownFilesRecursive(hubPath("rules")).map(
|
|
999
|
+
(rel) => `rules/${rel}`
|
|
1000
|
+
);
|
|
1001
|
+
return [...CLAUDE_MD_FIXED_IMPORTS, ...rulesImports];
|
|
1002
|
+
}
|
|
1003
|
+
var CLAUDE_MD_FIXED_IMPORTS, CLAUDE_MD_HEADER, ClaudeCodeAdapter;
|
|
1004
|
+
var init_claude_code = __esm({
|
|
1005
|
+
"src/core/adapters/claude-code.ts"() {
|
|
1006
|
+
"use strict";
|
|
1007
|
+
init_esm_shims();
|
|
1008
|
+
init_base();
|
|
1009
|
+
init_paths();
|
|
1010
|
+
init_fs();
|
|
1011
|
+
CLAUDE_MD_FIXED_IMPORTS = [
|
|
1012
|
+
"identity/profile.md",
|
|
1013
|
+
"identity/profile.auto.md",
|
|
1014
|
+
"identity/communication-style.md",
|
|
1015
|
+
"identity/tech-stack.md",
|
|
1016
|
+
"identity/env.md",
|
|
1017
|
+
"preferences/coding-style.md",
|
|
1018
|
+
"preferences/git-workflow.md"
|
|
1019
|
+
];
|
|
1020
|
+
CLAUDE_MD_HEADER = "<!-- AUTO-GENERATED by assetplex. Do not edit directly. Run `assetplex sync` to update. -->\n";
|
|
1021
|
+
ClaudeCodeAdapter = class extends BaseAdapter {
|
|
1022
|
+
name = "claude-code";
|
|
1023
|
+
displayName = "Claude Code";
|
|
1024
|
+
homepage = "https://code.claude.com/docs";
|
|
1025
|
+
defaultConfigDir = "~/.claude";
|
|
1026
|
+
signatureFiles = ["settings.json"];
|
|
1027
|
+
supportedTargets = [
|
|
1028
|
+
"CLAUDE.md",
|
|
1029
|
+
"skills/",
|
|
1030
|
+
"rules/",
|
|
1031
|
+
"commands/",
|
|
1032
|
+
"agents/"
|
|
1033
|
+
];
|
|
1034
|
+
/**
|
|
1035
|
+
* 重写 detect:
|
|
1036
|
+
* - 检查 ~/.claude/ 是否存在
|
|
1037
|
+
* - 支持 CLAUDE_CONFIG_DIR 环境变量重定向
|
|
1038
|
+
* - 检查 ~/.claude.json 是否存在(全局 MCP)
|
|
1039
|
+
*/
|
|
1040
|
+
async detect() {
|
|
1041
|
+
const configDir = this.getConfigDir();
|
|
1042
|
+
const settingsPath = resolve8(configDir, "settings.json");
|
|
1043
|
+
const claudeMdPath = resolve8(configDir, "CLAUDE.md");
|
|
1044
|
+
const configDirExists = existsSync7(configDir);
|
|
1045
|
+
const hasSettings = existsSync7(settingsPath);
|
|
1046
|
+
const hasClaudeMd = existsSync7(claudeMdPath);
|
|
1047
|
+
const installed = configDirExists && (hasSettings || hasClaudeMd);
|
|
1048
|
+
let version;
|
|
1049
|
+
try {
|
|
1050
|
+
const { execSync: execSync2 } = await import("child_process");
|
|
1051
|
+
const output = execSync2("claude --version 2>&1", {
|
|
1052
|
+
encoding: "utf-8",
|
|
1053
|
+
timeout: 3e3,
|
|
1054
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
1055
|
+
}).trim();
|
|
1056
|
+
const match = output.match(/(\d+\.\d+\.\d+)/);
|
|
1057
|
+
if (match) version = match[1];
|
|
1058
|
+
} catch {
|
|
1059
|
+
}
|
|
1060
|
+
return {
|
|
1061
|
+
name: this.name,
|
|
1062
|
+
installed,
|
|
1063
|
+
configDirExists,
|
|
1064
|
+
version,
|
|
1065
|
+
configDir,
|
|
1066
|
+
error: installed ? void 0 : configDirExists ? "\u7F3A\u5C11\u6807\u5FD7\u6027\u6587\u4EF6 settings.json" : "\u672A\u5B89\u88C5 Claude Code CLI"
|
|
1067
|
+
};
|
|
1068
|
+
}
|
|
1069
|
+
/**
|
|
1070
|
+
* 获取配置目录(支持 CLAUDE_CONFIG_DIR 环境变量)
|
|
1071
|
+
*/
|
|
1072
|
+
getConfigDir() {
|
|
1073
|
+
const fromEnv = process.env.CLAUDE_CONFIG_DIR;
|
|
1074
|
+
if (fromEnv) return this.expandHome(fromEnv);
|
|
1075
|
+
return this.expandHome(this.defaultConfigDir);
|
|
1076
|
+
}
|
|
1077
|
+
/**
|
|
1078
|
+
* 重写 resolveHubItems:明确关联 Hub 内文件与 Claude Code 目标
|
|
1079
|
+
*
|
|
1080
|
+
* 关联关系:
|
|
1081
|
+
* - Hub: identity/* + preferences/* + rules/* → Claude: CLAUDE.md (native-import)
|
|
1082
|
+
* - Hub: skills/ → Claude: skills (symlink, dir)
|
|
1083
|
+
* - Hub: rules/ → Claude: rules (symlink, dir)
|
|
1084
|
+
* - Hub: commands/ → Claude: commands (symlink, dir)
|
|
1085
|
+
* - Hub: agents/ → Claude: agents (symlink, dir)
|
|
1086
|
+
* - Hub: mcp/mcp.sources.json → Claude: ~/.claude.json (copy)
|
|
1087
|
+
*/
|
|
1088
|
+
resolveHubItems(_hubConfig, _hubRoot) {
|
|
1089
|
+
const base = this.getConfigDir();
|
|
1090
|
+
const home = homedir3();
|
|
1091
|
+
const claudeMdItem = {
|
|
1092
|
+
type: "identity",
|
|
1093
|
+
relativePath: "CLAUDE.md",
|
|
1094
|
+
// 虚拟路径,仅用于标识
|
|
1095
|
+
absolutePath: hubPath("identity/profile.md")
|
|
1096
|
+
// 第一个被 import 的文件作为锚点
|
|
1097
|
+
};
|
|
1098
|
+
return [
|
|
1099
|
+
{
|
|
1100
|
+
item: claudeMdItem,
|
|
1101
|
+
target: {
|
|
1102
|
+
tool: this.name,
|
|
1103
|
+
targetPath: resolve8(base, "CLAUDE.md"),
|
|
1104
|
+
strategy: "native-import",
|
|
1105
|
+
isDirectory: false
|
|
1106
|
+
}
|
|
1107
|
+
},
|
|
1108
|
+
{
|
|
1109
|
+
item: {
|
|
1110
|
+
type: "skill",
|
|
1111
|
+
relativePath: "skills",
|
|
1112
|
+
absolutePath: hubPath("skills")
|
|
1113
|
+
},
|
|
1114
|
+
target: {
|
|
1115
|
+
tool: this.name,
|
|
1116
|
+
targetPath: resolve8(base, "skills"),
|
|
1117
|
+
strategy: "symlink",
|
|
1118
|
+
isDirectory: true
|
|
1119
|
+
}
|
|
1120
|
+
},
|
|
1121
|
+
{
|
|
1122
|
+
item: {
|
|
1123
|
+
type: "rule",
|
|
1124
|
+
relativePath: "rules",
|
|
1125
|
+
absolutePath: hubPath("rules")
|
|
1126
|
+
},
|
|
1127
|
+
target: {
|
|
1128
|
+
tool: this.name,
|
|
1129
|
+
targetPath: resolve8(base, "rules"),
|
|
1130
|
+
strategy: "symlink",
|
|
1131
|
+
isDirectory: true
|
|
1132
|
+
}
|
|
1133
|
+
},
|
|
1134
|
+
{
|
|
1135
|
+
item: {
|
|
1136
|
+
type: "command",
|
|
1137
|
+
relativePath: "commands",
|
|
1138
|
+
absolutePath: hubPath("commands")
|
|
1139
|
+
},
|
|
1140
|
+
target: {
|
|
1141
|
+
tool: this.name,
|
|
1142
|
+
targetPath: resolve8(base, "commands"),
|
|
1143
|
+
strategy: "symlink",
|
|
1144
|
+
isDirectory: true
|
|
1145
|
+
}
|
|
1146
|
+
},
|
|
1147
|
+
{
|
|
1148
|
+
item: {
|
|
1149
|
+
type: "agent",
|
|
1150
|
+
relativePath: "agents",
|
|
1151
|
+
absolutePath: hubPath("agents")
|
|
1152
|
+
},
|
|
1153
|
+
target: {
|
|
1154
|
+
tool: this.name,
|
|
1155
|
+
targetPath: resolve8(base, "agents"),
|
|
1156
|
+
strategy: "symlink",
|
|
1157
|
+
isDirectory: true
|
|
1158
|
+
}
|
|
1159
|
+
},
|
|
1160
|
+
// 全局 MCP 配置(在 home 根,不在 .claude 内)
|
|
1161
|
+
{
|
|
1162
|
+
item: {
|
|
1163
|
+
type: "mcp",
|
|
1164
|
+
relativePath: "mcp/mcp.sources.json",
|
|
1165
|
+
absolutePath: hubPath("mcp/mcp.sources.json")
|
|
1166
|
+
},
|
|
1167
|
+
target: {
|
|
1168
|
+
tool: this.name,
|
|
1169
|
+
targetPath: resolve8(home, ".claude.json"),
|
|
1170
|
+
strategy: "copy",
|
|
1171
|
+
isDirectory: false
|
|
1172
|
+
}
|
|
1173
|
+
}
|
|
1174
|
+
];
|
|
1175
|
+
}
|
|
1176
|
+
/**
|
|
1177
|
+
* 同步目标列表(向后兼容)
|
|
1178
|
+
* 注意:CLAUDE.md 用 native-import 策略,其余用 symlink
|
|
1179
|
+
*/
|
|
1180
|
+
targets(config) {
|
|
1181
|
+
const configDir = config.configDir || this.defaultConfigDir;
|
|
1182
|
+
const base = this.expandHome(configDir);
|
|
1183
|
+
return [
|
|
1184
|
+
{
|
|
1185
|
+
tool: this.name,
|
|
1186
|
+
targetPath: resolve8(base, "CLAUDE.md"),
|
|
1187
|
+
strategy: "native-import",
|
|
1188
|
+
isDirectory: false
|
|
1189
|
+
},
|
|
1190
|
+
{
|
|
1191
|
+
tool: this.name,
|
|
1192
|
+
targetPath: resolve8(base, "skills"),
|
|
1193
|
+
strategy: "symlink",
|
|
1194
|
+
isDirectory: true
|
|
1195
|
+
},
|
|
1196
|
+
{
|
|
1197
|
+
tool: this.name,
|
|
1198
|
+
targetPath: resolve8(base, "rules"),
|
|
1199
|
+
strategy: "symlink",
|
|
1200
|
+
isDirectory: true
|
|
1201
|
+
},
|
|
1202
|
+
{
|
|
1203
|
+
tool: this.name,
|
|
1204
|
+
targetPath: resolve8(base, "commands"),
|
|
1205
|
+
strategy: "symlink",
|
|
1206
|
+
isDirectory: true
|
|
1207
|
+
},
|
|
1208
|
+
{
|
|
1209
|
+
tool: this.name,
|
|
1210
|
+
targetPath: resolve8(base, "agents"),
|
|
1211
|
+
strategy: "symlink",
|
|
1212
|
+
isDirectory: true
|
|
1213
|
+
},
|
|
1214
|
+
{
|
|
1215
|
+
tool: this.name,
|
|
1216
|
+
targetPath: resolve8(homedir3(), ".claude.json"),
|
|
1217
|
+
strategy: "copy",
|
|
1218
|
+
isDirectory: false
|
|
1219
|
+
}
|
|
1220
|
+
];
|
|
1221
|
+
}
|
|
1222
|
+
/**
|
|
1223
|
+
* 重写 applyNativeImport:生成 CLAUDE.md 聚合 @import
|
|
1224
|
+
*
|
|
1225
|
+
* 生成内容:
|
|
1226
|
+
* <!-- AUTO-GENERATED by assetplex ... -->
|
|
1227
|
+
* @~/.assetplex/identity/profile.md
|
|
1228
|
+
* @~/.assetplex/identity/profile.auto.md
|
|
1229
|
+
* ...
|
|
1230
|
+
*/
|
|
1231
|
+
async applyNativeImport(_item, target) {
|
|
1232
|
+
const lines = [CLAUDE_MD_HEADER, ""];
|
|
1233
|
+
for (const relPath of buildImportList()) {
|
|
1234
|
+
const absPath = hubPath(relPath);
|
|
1235
|
+
if (existsSync7(absPath)) {
|
|
1236
|
+
lines.push(`@${absPath.replace(/\\/g, "/")}`);
|
|
1237
|
+
}
|
|
1238
|
+
}
|
|
1239
|
+
lines.push("");
|
|
1240
|
+
writeFileSync3(target.targetPath, lines.join("\n"), "utf-8");
|
|
1241
|
+
}
|
|
1242
|
+
/**
|
|
1243
|
+
* 重写 apply:处理 .claude.json 的特殊逻辑(直接复制,不转换格式)
|
|
1244
|
+
*/
|
|
1245
|
+
async apply(item, target) {
|
|
1246
|
+
if (target.targetPath.endsWith(".claude.json")) {
|
|
1247
|
+
const content = readFileSync3(item.absolutePath);
|
|
1248
|
+
writeFileSync3(target.targetPath, content, "utf-8");
|
|
1249
|
+
return;
|
|
1250
|
+
}
|
|
1251
|
+
return super.apply(item, target);
|
|
1252
|
+
}
|
|
1253
|
+
/**
|
|
1254
|
+
* 反向导入:从 Claude Code 读 CLAUDE.md 到 Hub
|
|
1255
|
+
*
|
|
1256
|
+
* 注意:CLAUDE.md 是自动生成的,反向导入意义不大;
|
|
1257
|
+
* 但 .claude.json 反向导入可恢复 MCP 配置
|
|
1258
|
+
*/
|
|
1259
|
+
async import(targetPath) {
|
|
1260
|
+
const content = readFileSync3(targetPath);
|
|
1261
|
+
let type = "preference";
|
|
1262
|
+
if (targetPath.endsWith("CLAUDE.md")) type = "identity";
|
|
1263
|
+
else if (targetPath.endsWith(".claude.json")) type = "mcp";
|
|
1264
|
+
else if (targetPath.includes("skills")) type = "skill";
|
|
1265
|
+
else if (targetPath.includes("rules")) type = "rule";
|
|
1266
|
+
else if (targetPath.includes("commands")) type = "command";
|
|
1267
|
+
else if (targetPath.includes("agents")) type = "agent";
|
|
1268
|
+
return {
|
|
1269
|
+
type,
|
|
1270
|
+
relativePath: basename(targetPath),
|
|
1271
|
+
absolutePath: targetPath,
|
|
1272
|
+
content
|
|
1273
|
+
};
|
|
1274
|
+
}
|
|
1275
|
+
/**
|
|
1276
|
+
* 扫描 Claude Code 目录,发现所有可导入内容
|
|
1277
|
+
*/
|
|
1278
|
+
async scan() {
|
|
1279
|
+
const base = this.getConfigDir();
|
|
1280
|
+
const home = homedir3();
|
|
1281
|
+
const items = [];
|
|
1282
|
+
const claudeMdPath = resolve8(base, "CLAUDE.md");
|
|
1283
|
+
if (existsSync7(claudeMdPath)) {
|
|
1284
|
+
try {
|
|
1285
|
+
const content = readFileSync3(claudeMdPath, "utf-8");
|
|
1286
|
+
if (!content.includes("AUTO-GENERATED by assetplex")) {
|
|
1287
|
+
const item = this.makeDiscoveredItem(claudeMdPath, base, "identity/claude-code.md", "identity");
|
|
1288
|
+
if (item) items.push(item);
|
|
1289
|
+
}
|
|
1290
|
+
} catch {
|
|
1291
|
+
}
|
|
1292
|
+
}
|
|
1293
|
+
items.push(...this.scanDirectory(resolve8(base, "skills"), base, "skills", "skill"));
|
|
1294
|
+
items.push(...this.scanDirectory(resolve8(base, "rules"), base, "rules", "rule"));
|
|
1295
|
+
items.push(...this.scanDirectory(resolve8(base, "commands"), base, "commands", "preference"));
|
|
1296
|
+
items.push(...this.scanDirectory(resolve8(base, "agents"), base, "agents", "preference"));
|
|
1297
|
+
const claudeJsonPath = resolve8(home, ".claude.json");
|
|
1298
|
+
const claudeJsonItem = this.makeDiscoveredItem(claudeJsonPath, home, "mcp/mcp.sources.json", "mcp");
|
|
1299
|
+
if (claudeJsonItem) items.push(claudeJsonItem);
|
|
1300
|
+
return items;
|
|
1301
|
+
}
|
|
1302
|
+
};
|
|
1303
|
+
}
|
|
1304
|
+
});
|
|
1305
|
+
|
|
1306
|
+
// src/transforms/json-toml.ts
|
|
1307
|
+
var json_toml_exports = {};
|
|
1308
|
+
__export(json_toml_exports, {
|
|
1309
|
+
isValidJson: () => isValidJson,
|
|
1310
|
+
isValidToml: () => isValidToml,
|
|
1311
|
+
jsonObjToToml: () => jsonObjToToml,
|
|
1312
|
+
jsonToToml: () => jsonToToml,
|
|
1313
|
+
mcpJsonToToml: () => mcpJsonToToml,
|
|
1314
|
+
mcpTomlToJson: () => mcpTomlToJson,
|
|
1315
|
+
tomlToJson: () => tomlToJson,
|
|
1316
|
+
tomlToJsonObj: () => tomlToJsonObj
|
|
1317
|
+
});
|
|
1318
|
+
import * as TOML2 from "@iarna/toml";
|
|
1319
|
+
function jsonToToml(jsonStr) {
|
|
1320
|
+
const obj = JSON.parse(jsonStr);
|
|
1321
|
+
return jsonObjToToml(obj);
|
|
1322
|
+
}
|
|
1323
|
+
function jsonObjToToml(obj) {
|
|
1324
|
+
return TOML2.stringify(obj);
|
|
1325
|
+
}
|
|
1326
|
+
function tomlToJsonObj(tomlStr) {
|
|
1327
|
+
return TOML2.parse(tomlStr);
|
|
1328
|
+
}
|
|
1329
|
+
function tomlToJson(tomlStr) {
|
|
1330
|
+
return JSON.stringify(TOML2.parse(tomlStr), null, 2);
|
|
1331
|
+
}
|
|
1332
|
+
function mcpJsonToToml(jsonStr) {
|
|
1333
|
+
const obj = JSON.parse(jsonStr);
|
|
1334
|
+
const result = {};
|
|
1335
|
+
if (obj && typeof obj === "object" && "mcpServers" in obj) {
|
|
1336
|
+
result.mcp_servers = obj.mcpServers;
|
|
1337
|
+
}
|
|
1338
|
+
for (const [key, value] of Object.entries(obj)) {
|
|
1339
|
+
if (key !== "mcpServers") {
|
|
1340
|
+
result[key] = value;
|
|
1341
|
+
}
|
|
1342
|
+
}
|
|
1343
|
+
return TOML2.stringify(result);
|
|
1344
|
+
}
|
|
1345
|
+
function mcpTomlToJson(tomlStr) {
|
|
1346
|
+
const obj = TOML2.parse(tomlStr);
|
|
1347
|
+
const result = {};
|
|
1348
|
+
if ("mcp_servers" in obj) {
|
|
1349
|
+
result.mcpServers = obj.mcp_servers;
|
|
1350
|
+
}
|
|
1351
|
+
for (const [key, value] of Object.entries(obj)) {
|
|
1352
|
+
if (key !== "mcp_servers") {
|
|
1353
|
+
result[key] = value;
|
|
1354
|
+
}
|
|
1355
|
+
}
|
|
1356
|
+
return JSON.stringify(result, null, 2);
|
|
1357
|
+
}
|
|
1358
|
+
function isValidToml(str) {
|
|
1359
|
+
try {
|
|
1360
|
+
TOML2.parse(str);
|
|
1361
|
+
return true;
|
|
1362
|
+
} catch {
|
|
1363
|
+
return false;
|
|
1364
|
+
}
|
|
1365
|
+
}
|
|
1366
|
+
function isValidJson(str) {
|
|
1367
|
+
try {
|
|
1368
|
+
JSON.parse(str);
|
|
1369
|
+
return true;
|
|
1370
|
+
} catch {
|
|
1371
|
+
return false;
|
|
1372
|
+
}
|
|
1373
|
+
}
|
|
1374
|
+
var init_json_toml = __esm({
|
|
1375
|
+
"src/transforms/json-toml.ts"() {
|
|
1376
|
+
"use strict";
|
|
1377
|
+
init_esm_shims();
|
|
1378
|
+
}
|
|
1379
|
+
});
|
|
1380
|
+
|
|
1381
|
+
// src/core/adapters/codex.ts
|
|
1382
|
+
import { existsSync as existsSync8, readFileSync as readFileSync4, writeFileSync as writeFileSync4 } from "fs";
|
|
1383
|
+
import { resolve as resolve9 } from "path";
|
|
1384
|
+
function buildAgentsSections() {
|
|
1385
|
+
const rulesFiles = listMarkdownFilesRecursive(hubPath("rules")).map(
|
|
1386
|
+
(rel) => `rules/${rel}`
|
|
1387
|
+
);
|
|
1388
|
+
return [...AGENTS_MD_FIXED_SECTIONS, { title: "Rules", files: rulesFiles }];
|
|
1389
|
+
}
|
|
1390
|
+
var AGENTS_MD_FIXED_SECTIONS, CodexAdapter;
|
|
1391
|
+
var init_codex = __esm({
|
|
1392
|
+
"src/core/adapters/codex.ts"() {
|
|
1393
|
+
"use strict";
|
|
1394
|
+
init_esm_shims();
|
|
1395
|
+
init_base();
|
|
1396
|
+
init_paths();
|
|
1397
|
+
init_fs();
|
|
1398
|
+
init_json_toml();
|
|
1399
|
+
AGENTS_MD_FIXED_SECTIONS = [
|
|
1400
|
+
{ title: "Identity", files: [
|
|
1401
|
+
"identity/profile.md",
|
|
1402
|
+
"identity/profile.auto.md",
|
|
1403
|
+
"identity/communication-style.md",
|
|
1404
|
+
"identity/tech-stack.md",
|
|
1405
|
+
"identity/env.md"
|
|
1406
|
+
] },
|
|
1407
|
+
{ title: "Preferences", files: [
|
|
1408
|
+
"preferences/coding-style.md",
|
|
1409
|
+
"preferences/git-workflow.md"
|
|
1410
|
+
] }
|
|
1411
|
+
];
|
|
1412
|
+
CodexAdapter = class extends BaseAdapter {
|
|
1413
|
+
name = "codex";
|
|
1414
|
+
displayName = "Codex (OpenAI Codex CLI)";
|
|
1415
|
+
homepage = "https://github.com/openai/codex";
|
|
1416
|
+
defaultConfigDir = "~/.codex";
|
|
1417
|
+
signatureFiles = ["config.toml"];
|
|
1418
|
+
supportedTargets = ["AGENTS.md", "skills/", "config.toml"];
|
|
1419
|
+
/**
|
|
1420
|
+
* 重写 detect:
|
|
1421
|
+
* - 检查 ~/.codex/config.toml 是否存在
|
|
1422
|
+
*/
|
|
1423
|
+
async detect() {
|
|
1424
|
+
const configDir = this.getConfigDir();
|
|
1425
|
+
const configPath = resolve9(configDir, "config.toml");
|
|
1426
|
+
const agentsMdPath = resolve9(configDir, "AGENTS.md");
|
|
1427
|
+
const configDirExists = existsSync8(configDir);
|
|
1428
|
+
const hasConfig = existsSync8(configPath);
|
|
1429
|
+
const hasAgentsMd = existsSync8(agentsMdPath);
|
|
1430
|
+
const installed = configDirExists && (hasConfig || hasAgentsMd);
|
|
1431
|
+
let version;
|
|
1432
|
+
try {
|
|
1433
|
+
const { execSync: execSync2 } = await import("child_process");
|
|
1434
|
+
const output = execSync2("codex --version 2>&1", {
|
|
1435
|
+
encoding: "utf-8",
|
|
1436
|
+
timeout: 3e3,
|
|
1437
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
1438
|
+
}).trim();
|
|
1439
|
+
const match = output.match(/(\d+\.\d+\.\d+)/);
|
|
1440
|
+
if (match) version = match[1];
|
|
1441
|
+
} catch {
|
|
1442
|
+
}
|
|
1443
|
+
return {
|
|
1444
|
+
name: this.name,
|
|
1445
|
+
installed,
|
|
1446
|
+
configDirExists,
|
|
1447
|
+
version,
|
|
1448
|
+
configDir,
|
|
1449
|
+
error: installed ? void 0 : configDirExists ? "\u7F3A\u5C11 config.toml" : "\u672A\u5B89\u88C5 Codex CLI"
|
|
1450
|
+
};
|
|
1451
|
+
}
|
|
1452
|
+
/**
|
|
1453
|
+
* 重写 resolveHubItems:明确关联 Hub 内文件与 Codex 目标
|
|
1454
|
+
*
|
|
1455
|
+
* 关联关系:
|
|
1456
|
+
* - Hub: identity/* + preferences/* + rules/* → Codex: AGENTS.md (copy, 聚合)
|
|
1457
|
+
* - Hub: skills/ → Codex: skills (symlink, dir)
|
|
1458
|
+
* - Hub: mcp/mcp.sources.json → Codex: config.toml (copy, JSON→TOML 转换)
|
|
1459
|
+
*/
|
|
1460
|
+
resolveHubItems(_hubConfig, _hubRoot) {
|
|
1461
|
+
const base = this.getConfigDir();
|
|
1462
|
+
return [
|
|
1463
|
+
{
|
|
1464
|
+
item: {
|
|
1465
|
+
type: "identity",
|
|
1466
|
+
relativePath: "AGENTS.md",
|
|
1467
|
+
// 虚拟路径,apply 时会聚合多个 Hub 文件
|
|
1468
|
+
absolutePath: hubPath("identity/profile.md")
|
|
1469
|
+
// 锚点文件
|
|
1470
|
+
},
|
|
1471
|
+
target: {
|
|
1472
|
+
tool: this.name,
|
|
1473
|
+
targetPath: resolve9(base, "AGENTS.md"),
|
|
1474
|
+
strategy: "copy",
|
|
1475
|
+
isDirectory: false
|
|
1476
|
+
}
|
|
1477
|
+
},
|
|
1478
|
+
{
|
|
1479
|
+
item: {
|
|
1480
|
+
type: "skill",
|
|
1481
|
+
relativePath: "skills",
|
|
1482
|
+
absolutePath: hubPath("skills")
|
|
1483
|
+
},
|
|
1484
|
+
target: {
|
|
1485
|
+
tool: this.name,
|
|
1486
|
+
targetPath: resolve9(base, "skills"),
|
|
1487
|
+
strategy: "symlink",
|
|
1488
|
+
isDirectory: true
|
|
1489
|
+
}
|
|
1490
|
+
},
|
|
1491
|
+
{
|
|
1492
|
+
item: {
|
|
1493
|
+
type: "mcp",
|
|
1494
|
+
relativePath: "mcp/mcp.sources.json",
|
|
1495
|
+
absolutePath: hubPath("mcp/mcp.sources.json")
|
|
1496
|
+
},
|
|
1497
|
+
target: {
|
|
1498
|
+
tool: this.name,
|
|
1499
|
+
targetPath: resolve9(base, "config.toml"),
|
|
1500
|
+
strategy: "copy",
|
|
1501
|
+
isDirectory: false
|
|
1502
|
+
}
|
|
1503
|
+
}
|
|
1504
|
+
];
|
|
1505
|
+
}
|
|
1506
|
+
/**
|
|
1507
|
+
* 同步目标列表(向后兼容)
|
|
1508
|
+
*/
|
|
1509
|
+
targets(config) {
|
|
1510
|
+
const configDir = config.configDir || this.defaultConfigDir;
|
|
1511
|
+
const base = this.expandHome(configDir);
|
|
1512
|
+
return [
|
|
1513
|
+
{
|
|
1514
|
+
tool: this.name,
|
|
1515
|
+
targetPath: resolve9(base, "AGENTS.md"),
|
|
1516
|
+
strategy: "copy",
|
|
1517
|
+
isDirectory: false
|
|
1518
|
+
},
|
|
1519
|
+
{
|
|
1520
|
+
tool: this.name,
|
|
1521
|
+
targetPath: resolve9(base, "skills"),
|
|
1522
|
+
strategy: "symlink",
|
|
1523
|
+
isDirectory: true
|
|
1524
|
+
},
|
|
1525
|
+
{
|
|
1526
|
+
tool: this.name,
|
|
1527
|
+
targetPath: resolve9(base, "config.toml"),
|
|
1528
|
+
strategy: "copy",
|
|
1529
|
+
isDirectory: false
|
|
1530
|
+
}
|
|
1531
|
+
];
|
|
1532
|
+
}
|
|
1533
|
+
/**
|
|
1534
|
+
* 重写 apply:处理 AGENTS.md 聚合 和 config.toml JSON→TOML 转换
|
|
1535
|
+
*/
|
|
1536
|
+
async apply(item, target) {
|
|
1537
|
+
if (target.targetPath.endsWith("AGENTS.md")) {
|
|
1538
|
+
this.applyAgentsMd(target);
|
|
1539
|
+
return;
|
|
1540
|
+
}
|
|
1541
|
+
if (target.targetPath.endsWith("config.toml")) {
|
|
1542
|
+
this.applyConfigToml(item, target);
|
|
1543
|
+
return;
|
|
1544
|
+
}
|
|
1545
|
+
return super.apply(item, target);
|
|
1546
|
+
}
|
|
1547
|
+
/**
|
|
1548
|
+
* 生成 AGENTS.md:聚合 Hub 内 identity + preferences + rules 内容
|
|
1549
|
+
*/
|
|
1550
|
+
applyAgentsMd(target) {
|
|
1551
|
+
const lines = [
|
|
1552
|
+
"<!-- AUTO-GENERATED by assetplex. Do not edit directly. Run `assetplex sync` to update. -->",
|
|
1553
|
+
"# AGENTS.md",
|
|
1554
|
+
"",
|
|
1555
|
+
"> \u6B64\u6587\u4EF6\u7531 assetplex \u81EA\u52A8\u805A\u5408 Hub \u4E2D\u7684\u8EAB\u4EFD\u3001\u504F\u597D\u548C\u89C4\u5219\u6587\u4EF6\u751F\u6210\u3002",
|
|
1556
|
+
"> \u4FEE\u6539\u8BF7\u7F16\u8F91 ~/.assetplex/ \u4E0B\u5BF9\u5E94\u6587\u4EF6\u540E\u8FD0\u884C `assetplex sync`\u3002",
|
|
1557
|
+
""
|
|
1558
|
+
];
|
|
1559
|
+
for (const section of buildAgentsSections()) {
|
|
1560
|
+
lines.push(`## ${section.title}`, "");
|
|
1561
|
+
for (const relPath of section.files) {
|
|
1562
|
+
const absPath = hubPath(relPath);
|
|
1563
|
+
if (existsSync8(absPath)) {
|
|
1564
|
+
const content = readFileSync4(absPath, "utf-8").trim();
|
|
1565
|
+
if (content) {
|
|
1566
|
+
lines.push(`### ${relPath}`, "", content, "");
|
|
1567
|
+
}
|
|
1568
|
+
}
|
|
1569
|
+
}
|
|
1570
|
+
}
|
|
1571
|
+
writeFileSync4(target.targetPath, lines.join("\n"), "utf-8");
|
|
1572
|
+
}
|
|
1573
|
+
/**
|
|
1574
|
+
* 生成 config.toml:从 mcp.sources.json (JSON) 转换为 TOML
|
|
1575
|
+
*/
|
|
1576
|
+
applyConfigToml(item, target) {
|
|
1577
|
+
const jsonContent = readFileSync4(item.absolutePath, "utf-8");
|
|
1578
|
+
const tomlContent = mcpJsonToToml(jsonContent);
|
|
1579
|
+
writeFileSync4(target.targetPath, tomlContent, "utf-8");
|
|
1580
|
+
}
|
|
1581
|
+
/**
|
|
1582
|
+
* 重写 transform:JSON → TOML 转换
|
|
1583
|
+
*/
|
|
1584
|
+
transform(content, format) {
|
|
1585
|
+
if (format === "toml") {
|
|
1586
|
+
const jsonStr = content.toString("utf-8");
|
|
1587
|
+
const tomlStr = mcpJsonToToml(jsonStr);
|
|
1588
|
+
return Buffer.from(tomlStr, "utf-8");
|
|
1589
|
+
}
|
|
1590
|
+
return content;
|
|
1591
|
+
}
|
|
1592
|
+
/**
|
|
1593
|
+
* 反向导入:从 Codex 读 config.toml 转 JSON 到 Hub
|
|
1594
|
+
*/
|
|
1595
|
+
async import(targetPath) {
|
|
1596
|
+
const content = readFileSync4(targetPath);
|
|
1597
|
+
let type = "preference";
|
|
1598
|
+
if (targetPath.endsWith("config.toml")) type = "mcp";
|
|
1599
|
+
else if (targetPath.endsWith("AGENTS.md")) type = "identity";
|
|
1600
|
+
else if (targetPath.includes("skills")) type = "skill";
|
|
1601
|
+
if (targetPath.endsWith("config.toml")) {
|
|
1602
|
+
const tomlStr = content.toString("utf-8");
|
|
1603
|
+
const jsonStr = mcpTomlToJson(tomlStr);
|
|
1604
|
+
return {
|
|
1605
|
+
type,
|
|
1606
|
+
relativePath: "mcp/mcp.sources.json",
|
|
1607
|
+
absolutePath: targetPath,
|
|
1608
|
+
content: Buffer.from(jsonStr, "utf-8")
|
|
1609
|
+
};
|
|
1610
|
+
}
|
|
1611
|
+
return {
|
|
1612
|
+
type,
|
|
1613
|
+
relativePath: "",
|
|
1614
|
+
absolutePath: targetPath,
|
|
1615
|
+
content
|
|
1616
|
+
};
|
|
1617
|
+
}
|
|
1618
|
+
/**
|
|
1619
|
+
* 扫描 Codex 目录,发现所有可导入内容
|
|
1620
|
+
*/
|
|
1621
|
+
async scan() {
|
|
1622
|
+
const base = this.getConfigDir();
|
|
1623
|
+
const items = [];
|
|
1624
|
+
const agentsMdPath = resolve9(base, "AGENTS.md");
|
|
1625
|
+
if (existsSync8(agentsMdPath)) {
|
|
1626
|
+
try {
|
|
1627
|
+
const content = readFileSync4(agentsMdPath, "utf-8");
|
|
1628
|
+
if (!content.includes("AUTO-GENERATED by assetplex")) {
|
|
1629
|
+
const item = this.makeDiscoveredItem(agentsMdPath, base, "identity/codex.md", "identity");
|
|
1630
|
+
if (item) items.push(item);
|
|
1631
|
+
}
|
|
1632
|
+
} catch {
|
|
1633
|
+
}
|
|
1634
|
+
}
|
|
1635
|
+
items.push(...this.scanDirectory(resolve9(base, "skills"), base, "skills", "skill"));
|
|
1636
|
+
const configTomlPath = resolve9(base, "config.toml");
|
|
1637
|
+
const tomlItem = this.makeDiscoveredItem(configTomlPath, base, "mcp/mcp.sources.json", "mcp");
|
|
1638
|
+
if (tomlItem) items.push(tomlItem);
|
|
1639
|
+
return items;
|
|
1640
|
+
}
|
|
1641
|
+
};
|
|
1642
|
+
}
|
|
1643
|
+
});
|
|
1644
|
+
|
|
1645
|
+
// src/transforms/env-interpolation.ts
|
|
1646
|
+
var env_interpolation_exports = {};
|
|
1647
|
+
__export(env_interpolation_exports, {
|
|
1648
|
+
buildEnvMap: () => buildEnvMap,
|
|
1649
|
+
desinterpolateEnv: () => desinterpolateEnv,
|
|
1650
|
+
extractEnvVars: () => extractEnvVars,
|
|
1651
|
+
interpolateEnv: () => interpolateEnv
|
|
1652
|
+
});
|
|
1653
|
+
function interpolateEnv(input, options = {}) {
|
|
1654
|
+
const env = options.env ?? process.env;
|
|
1655
|
+
const strict = options.strict ?? false;
|
|
1656
|
+
const missing = /* @__PURE__ */ new Set();
|
|
1657
|
+
const replaced = /* @__PURE__ */ new Set();
|
|
1658
|
+
const output = input.replace(ENV_VAR_PATTERN, (match, varName) => {
|
|
1659
|
+
const value = env[varName];
|
|
1660
|
+
if (value === void 0 || value === "") {
|
|
1661
|
+
if (strict) {
|
|
1662
|
+
throw new Error(`\u73AF\u5883\u53D8\u91CF\u7F3A\u5931: ${varName}`);
|
|
1663
|
+
}
|
|
1664
|
+
missing.add(varName);
|
|
1665
|
+
return match;
|
|
1666
|
+
}
|
|
1667
|
+
replaced.add(varName);
|
|
1668
|
+
return value;
|
|
1669
|
+
});
|
|
1670
|
+
return {
|
|
1671
|
+
output,
|
|
1672
|
+
missing: Array.from(missing),
|
|
1673
|
+
replaced: Array.from(replaced)
|
|
1674
|
+
};
|
|
1675
|
+
}
|
|
1676
|
+
function desinterpolateEnv(input, envMap) {
|
|
1677
|
+
let result = input;
|
|
1678
|
+
const sortedValues = Object.keys(envMap).sort((a, b) => b.length - a.length);
|
|
1679
|
+
for (const value of sortedValues) {
|
|
1680
|
+
if (!value) continue;
|
|
1681
|
+
const varName = envMap[value];
|
|
1682
|
+
const escaped = value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
1683
|
+
result = result.replace(new RegExp(escaped, "g"), `\${${varName}}`);
|
|
1684
|
+
}
|
|
1685
|
+
return result;
|
|
1686
|
+
}
|
|
1687
|
+
function extractEnvVars(input) {
|
|
1688
|
+
const matches = /* @__PURE__ */ new Set();
|
|
1689
|
+
let match;
|
|
1690
|
+
const pattern = new RegExp(ENV_VAR_PATTERN.source, "g");
|
|
1691
|
+
while ((match = pattern.exec(input)) !== null) {
|
|
1692
|
+
matches.add(match[1]);
|
|
1693
|
+
}
|
|
1694
|
+
return Array.from(matches);
|
|
1695
|
+
}
|
|
1696
|
+
function buildEnvMap(varNames, env = process.env) {
|
|
1697
|
+
const map = {};
|
|
1698
|
+
for (const name of varNames) {
|
|
1699
|
+
const value = env[name];
|
|
1700
|
+
if (value) {
|
|
1701
|
+
map[value] = name;
|
|
1702
|
+
}
|
|
1703
|
+
}
|
|
1704
|
+
return map;
|
|
1705
|
+
}
|
|
1706
|
+
var ENV_VAR_PATTERN;
|
|
1707
|
+
var init_env_interpolation = __esm({
|
|
1708
|
+
"src/transforms/env-interpolation.ts"() {
|
|
1709
|
+
"use strict";
|
|
1710
|
+
init_esm_shims();
|
|
1711
|
+
ENV_VAR_PATTERN = /\$\{([A-Z_][A-Z0-9_]*)\}/g;
|
|
1712
|
+
}
|
|
1713
|
+
});
|
|
1714
|
+
|
|
1715
|
+
// src/core/adapters/workbuddy.ts
|
|
1716
|
+
import { existsSync as existsSync9, readFileSync as readFileSync5, writeFileSync as writeFileSync5 } from "fs";
|
|
1717
|
+
import { resolve as resolve10 } from "path";
|
|
1718
|
+
var WorkBuddyAdapter;
|
|
1719
|
+
var init_workbuddy = __esm({
|
|
1720
|
+
"src/core/adapters/workbuddy.ts"() {
|
|
1721
|
+
"use strict";
|
|
1722
|
+
init_esm_shims();
|
|
1723
|
+
init_base();
|
|
1724
|
+
init_paths();
|
|
1725
|
+
init_env_interpolation();
|
|
1726
|
+
WorkBuddyAdapter = class extends BaseAdapter {
|
|
1727
|
+
name = "workbuddy";
|
|
1728
|
+
displayName = "WorkBuddy / CodeBuddy";
|
|
1729
|
+
homepage = "https://www.tencentcloud.com/products/codebuddy";
|
|
1730
|
+
defaultConfigDir = "~/.workbuddy";
|
|
1731
|
+
signatureFiles = [".mcp.json", "mcp.json", "models.json"];
|
|
1732
|
+
supportedTargets = ["rules/", "skills/", ".mcp.json"];
|
|
1733
|
+
/**
|
|
1734
|
+
* 重写 detect:同时检查 ~/.workbuddy 和 ~/.codebuddy
|
|
1735
|
+
*/
|
|
1736
|
+
async detect() {
|
|
1737
|
+
const workbuddyDir = this.expandHome("~/.workbuddy");
|
|
1738
|
+
const codebuddyDir = this.expandHome("~/.codebuddy");
|
|
1739
|
+
const workbuddyExists = existsSync9(workbuddyDir);
|
|
1740
|
+
const codebuddyExists = existsSync9(codebuddyDir);
|
|
1741
|
+
const configDir = workbuddyExists ? workbuddyDir : codebuddyDir;
|
|
1742
|
+
const configDirExists = workbuddyExists || codebuddyExists;
|
|
1743
|
+
let hasSignature = false;
|
|
1744
|
+
if (workbuddyExists) {
|
|
1745
|
+
hasSignature = existsSync9(resolve10(workbuddyDir, ".mcp.json")) || existsSync9(resolve10(workbuddyDir, "mcp.json")) || existsSync9(resolve10(workbuddyDir, "models.json"));
|
|
1746
|
+
}
|
|
1747
|
+
if (!hasSignature && codebuddyExists) {
|
|
1748
|
+
hasSignature = existsSync9(resolve10(codebuddyDir, ".mcp.json")) || existsSync9(resolve10(codebuddyDir, "mcp.json")) || existsSync9(resolve10(codebuddyDir, "models.json"));
|
|
1749
|
+
}
|
|
1750
|
+
const installed = configDirExists && hasSignature;
|
|
1751
|
+
return {
|
|
1752
|
+
name: this.name,
|
|
1753
|
+
installed,
|
|
1754
|
+
configDirExists,
|
|
1755
|
+
configDir,
|
|
1756
|
+
error: installed ? void 0 : configDirExists ? "\u7F3A\u5C11\u6807\u5FD7\u6027\u6587\u4EF6" : "\u672A\u5B89\u88C5 WorkBuddy/CodeBuddy"
|
|
1757
|
+
};
|
|
1758
|
+
}
|
|
1759
|
+
/**
|
|
1760
|
+
* 获取配置目录(动态:workbuddy 或 codebuddy)
|
|
1761
|
+
*/
|
|
1762
|
+
getConfigDir() {
|
|
1763
|
+
const workbuddyDir = this.expandHome("~/.workbuddy");
|
|
1764
|
+
const codebuddyDir = this.expandHome("~/.codebuddy");
|
|
1765
|
+
if (existsSync9(workbuddyDir)) return workbuddyDir;
|
|
1766
|
+
if (existsSync9(codebuddyDir)) return codebuddyDir;
|
|
1767
|
+
return workbuddyDir;
|
|
1768
|
+
}
|
|
1769
|
+
/**
|
|
1770
|
+
* 重写 resolveHubItems:明确关联 Hub 内文件与 WorkBuddy 目标
|
|
1771
|
+
*
|
|
1772
|
+
* 关联关系:
|
|
1773
|
+
* - Hub: rules/ → WorkBuddy: rules (symlink, dir)
|
|
1774
|
+
* - Hub: skills/ → WorkBuddy: skills (symlink, dir)
|
|
1775
|
+
* - Hub: mcp/mcp.sources.json → WorkBuddy: .mcp.json (copy, ${VAR} 插值)
|
|
1776
|
+
*/
|
|
1777
|
+
resolveHubItems(_hubConfig, _hubRoot) {
|
|
1778
|
+
const base = this.getConfigDir();
|
|
1779
|
+
return [
|
|
1780
|
+
{
|
|
1781
|
+
item: {
|
|
1782
|
+
type: "rule",
|
|
1783
|
+
relativePath: "rules",
|
|
1784
|
+
absolutePath: hubPath("rules")
|
|
1785
|
+
},
|
|
1786
|
+
target: {
|
|
1787
|
+
tool: this.name,
|
|
1788
|
+
targetPath: resolve10(base, "rules"),
|
|
1789
|
+
strategy: "symlink",
|
|
1790
|
+
isDirectory: true
|
|
1791
|
+
}
|
|
1792
|
+
},
|
|
1793
|
+
{
|
|
1794
|
+
item: {
|
|
1795
|
+
type: "skill",
|
|
1796
|
+
relativePath: "skills",
|
|
1797
|
+
absolutePath: hubPath("skills")
|
|
1798
|
+
},
|
|
1799
|
+
target: {
|
|
1800
|
+
tool: this.name,
|
|
1801
|
+
targetPath: resolve10(base, "skills"),
|
|
1802
|
+
strategy: "symlink",
|
|
1803
|
+
isDirectory: true
|
|
1804
|
+
}
|
|
1805
|
+
},
|
|
1806
|
+
// 注意前导点(与 Claude Code 的 .mcp.json 同名但路径不同)
|
|
1807
|
+
{
|
|
1808
|
+
item: {
|
|
1809
|
+
type: "mcp",
|
|
1810
|
+
relativePath: "mcp/mcp.sources.json",
|
|
1811
|
+
absolutePath: hubPath("mcp/mcp.sources.json")
|
|
1812
|
+
},
|
|
1813
|
+
target: {
|
|
1814
|
+
tool: this.name,
|
|
1815
|
+
targetPath: resolve10(base, ".mcp.json"),
|
|
1816
|
+
strategy: "copy",
|
|
1817
|
+
// 需要 ${VAR} 插值
|
|
1818
|
+
isDirectory: false
|
|
1819
|
+
}
|
|
1820
|
+
}
|
|
1821
|
+
];
|
|
1822
|
+
}
|
|
1823
|
+
/**
|
|
1824
|
+
* 同步目标列表(向后兼容)
|
|
1825
|
+
*/
|
|
1826
|
+
targets(config) {
|
|
1827
|
+
const configDir = config.configDir || this.defaultConfigDir;
|
|
1828
|
+
const base = this.expandHome(configDir);
|
|
1829
|
+
return [
|
|
1830
|
+
{
|
|
1831
|
+
tool: this.name,
|
|
1832
|
+
targetPath: resolve10(base, "rules"),
|
|
1833
|
+
strategy: "symlink",
|
|
1834
|
+
isDirectory: true
|
|
1835
|
+
},
|
|
1836
|
+
{
|
|
1837
|
+
tool: this.name,
|
|
1838
|
+
targetPath: resolve10(base, "skills"),
|
|
1839
|
+
strategy: "symlink",
|
|
1840
|
+
isDirectory: true
|
|
1841
|
+
},
|
|
1842
|
+
{
|
|
1843
|
+
tool: this.name,
|
|
1844
|
+
targetPath: resolve10(base, ".mcp.json"),
|
|
1845
|
+
strategy: "copy",
|
|
1846
|
+
isDirectory: false
|
|
1847
|
+
}
|
|
1848
|
+
];
|
|
1849
|
+
}
|
|
1850
|
+
/**
|
|
1851
|
+
* 重写 apply:处理 .mcp.json 的 ${VAR} 插值
|
|
1852
|
+
*/
|
|
1853
|
+
async apply(item, target) {
|
|
1854
|
+
if (target.targetPath.endsWith(".mcp.json")) {
|
|
1855
|
+
this.applyMcpJson(item, target);
|
|
1856
|
+
return;
|
|
1857
|
+
}
|
|
1858
|
+
return super.apply(item, target);
|
|
1859
|
+
}
|
|
1860
|
+
/**
|
|
1861
|
+
* 生成 .mcp.json:读取 Hub 的 mcp.sources.json,做 ${VAR} 插值
|
|
1862
|
+
*/
|
|
1863
|
+
applyMcpJson(item, target) {
|
|
1864
|
+
const content = readFileSync5(item.absolutePath, "utf-8");
|
|
1865
|
+
const varNames = extractEnvVars(content);
|
|
1866
|
+
if (varNames.length === 0) {
|
|
1867
|
+
writeFileSync5(target.targetPath, content, "utf-8");
|
|
1868
|
+
return;
|
|
1869
|
+
}
|
|
1870
|
+
const result = interpolateEnv(content);
|
|
1871
|
+
if (result.missing.length > 0) {
|
|
1872
|
+
console.warn(`[workbuddy] \u7F3A\u5931\u73AF\u5883\u53D8\u91CF: ${result.missing.join(", ")}\uFF08\u4FDD\u7559\u4E3A \${VAR} \u539F\u6837\uFF09`);
|
|
1873
|
+
}
|
|
1874
|
+
writeFileSync5(target.targetPath, result.output, "utf-8");
|
|
1875
|
+
}
|
|
1876
|
+
/**
|
|
1877
|
+
* 反向导入:从 WorkBuddy 读 .mcp.json,去插值后写回 Hub
|
|
1878
|
+
*/
|
|
1879
|
+
async import(targetPath) {
|
|
1880
|
+
const content = readFileSync5(targetPath);
|
|
1881
|
+
let type = "preference";
|
|
1882
|
+
if (targetPath.endsWith(".mcp.json")) type = "mcp";
|
|
1883
|
+
else if (targetPath.includes("skills")) type = "skill";
|
|
1884
|
+
else if (targetPath.includes("rules")) type = "rule";
|
|
1885
|
+
if (targetPath.endsWith(".mcp.json")) {
|
|
1886
|
+
const str = content.toString("utf-8");
|
|
1887
|
+
const varNames = extractEnvVars(str);
|
|
1888
|
+
const envMap = buildEnvMap(varNames);
|
|
1889
|
+
if (Object.keys(envMap).length > 0) {
|
|
1890
|
+
const restored = desinterpolateEnv(str, envMap);
|
|
1891
|
+
return {
|
|
1892
|
+
type,
|
|
1893
|
+
relativePath: "mcp/mcp.sources.json",
|
|
1894
|
+
absolutePath: targetPath,
|
|
1895
|
+
content: Buffer.from(restored, "utf-8")
|
|
1896
|
+
};
|
|
1897
|
+
}
|
|
1898
|
+
}
|
|
1899
|
+
return {
|
|
1900
|
+
type,
|
|
1901
|
+
relativePath: "",
|
|
1902
|
+
absolutePath: targetPath,
|
|
1903
|
+
content
|
|
1904
|
+
};
|
|
1905
|
+
}
|
|
1906
|
+
/**
|
|
1907
|
+
* 扫描 WorkBuddy/CodeBuddy 目录,发现所有可导入内容
|
|
1908
|
+
*/
|
|
1909
|
+
async scan() {
|
|
1910
|
+
const base = this.getConfigDir();
|
|
1911
|
+
const items = [];
|
|
1912
|
+
items.push(...this.scanDirectory(resolve10(base, "skills"), base, "skills", "skill"));
|
|
1913
|
+
items.push(...this.scanDirectory(resolve10(base, "rules"), base, "rules", "rule"));
|
|
1914
|
+
const mcpDotPath = resolve10(base, ".mcp.json");
|
|
1915
|
+
const mcpDotItem = this.makeDiscoveredItem(mcpDotPath, base, "mcp/mcp.sources.json", "mcp");
|
|
1916
|
+
if (mcpDotItem) items.push(mcpDotItem);
|
|
1917
|
+
const mcpPath = resolve10(base, "mcp.json");
|
|
1918
|
+
if (existsSync9(mcpPath)) {
|
|
1919
|
+
if (!mcpDotItem) {
|
|
1920
|
+
const item = this.makeDiscoveredItem(mcpPath, base, "mcp/mcp.sources.json", "mcp");
|
|
1921
|
+
if (item) items.push(item);
|
|
1922
|
+
} else {
|
|
1923
|
+
const item = this.makeDiscoveredItem(mcpPath, base, "preferences/mcp.json", "preference");
|
|
1924
|
+
if (item) items.push(item);
|
|
1925
|
+
}
|
|
1926
|
+
}
|
|
1927
|
+
const modelsPath = resolve10(base, "models.json");
|
|
1928
|
+
const modelsItem = this.makeDiscoveredItem(modelsPath, base, "preferences/models.json", "preference");
|
|
1929
|
+
if (modelsItem) items.push(modelsItem);
|
|
1930
|
+
return items;
|
|
1931
|
+
}
|
|
1932
|
+
};
|
|
1933
|
+
}
|
|
1934
|
+
});
|
|
1935
|
+
|
|
1936
|
+
// src/core/adapters/qoder.ts
|
|
1937
|
+
import { existsSync as existsSync10, writeFileSync as writeFileSync6, readFileSync as readFileSync6 } from "fs";
|
|
1938
|
+
import { resolve as resolve11, basename as basename2 } from "path";
|
|
1939
|
+
function buildQoderSections() {
|
|
1940
|
+
const rulesFiles = listMarkdownFilesRecursive(hubPath("rules")).map(
|
|
1941
|
+
(rel) => `rules/${rel}`
|
|
1942
|
+
);
|
|
1943
|
+
return [...QODER_FIXED_SECTIONS, ...rulesFiles];
|
|
1944
|
+
}
|
|
1945
|
+
var QODER_FIXED_SECTIONS, QoderAdapter;
|
|
1946
|
+
var init_qoder = __esm({
|
|
1947
|
+
"src/core/adapters/qoder.ts"() {
|
|
1948
|
+
"use strict";
|
|
1949
|
+
init_esm_shims();
|
|
1950
|
+
init_base();
|
|
1951
|
+
init_paths();
|
|
1952
|
+
init_fs();
|
|
1953
|
+
init_symlink();
|
|
1954
|
+
init_config();
|
|
1955
|
+
QODER_FIXED_SECTIONS = [
|
|
1956
|
+
"identity/profile.md",
|
|
1957
|
+
"identity/profile.auto.md",
|
|
1958
|
+
"identity/communication-style.md",
|
|
1959
|
+
"identity/tech-stack.md",
|
|
1960
|
+
"preferences/coding-style.md",
|
|
1961
|
+
"preferences/git-workflow.md"
|
|
1962
|
+
];
|
|
1963
|
+
QoderAdapter = class extends BaseAdapter {
|
|
1964
|
+
name = "qoder";
|
|
1965
|
+
displayName = "Qoder";
|
|
1966
|
+
homepage = "https://docs.qoder.com/";
|
|
1967
|
+
defaultConfigDir = "";
|
|
1968
|
+
// Qoder 无统一用户级目录
|
|
1969
|
+
signatureFiles = [];
|
|
1970
|
+
// 由项目级 .qoder/ 判定
|
|
1971
|
+
supportedTargets = ["rules/", "skills/", "quests/", "knowledge/"];
|
|
1972
|
+
/**
|
|
1973
|
+
* 重写 detect:Qoder 无用户级目录,detect 永远返回未安装
|
|
1974
|
+
* sync 时会通过 resolveHubItems 返回的 targets 处理 per-project
|
|
1975
|
+
*/
|
|
1976
|
+
async detect() {
|
|
1977
|
+
return {
|
|
1978
|
+
name: this.name,
|
|
1979
|
+
installed: false,
|
|
1980
|
+
configDirExists: false,
|
|
1981
|
+
configDir: "",
|
|
1982
|
+
error: "Qoder \u662F\u9879\u76EE\u7EA7\u5DE5\u5177\uFF0C\u65E0\u7528\u6237\u7EA7\u76EE\u5F55\uFF1B\u8BF7\u5728\u9879\u76EE\u6839\u76EE\u5F55\u626B\u63CF .qoder/"
|
|
1983
|
+
};
|
|
1984
|
+
}
|
|
1985
|
+
/**
|
|
1986
|
+
* 获取配置目录(Qoder 不适用,返回空)
|
|
1987
|
+
*/
|
|
1988
|
+
getConfigDir() {
|
|
1989
|
+
return "";
|
|
1990
|
+
}
|
|
1991
|
+
/**
|
|
1992
|
+
* 重写 resolveHubItems:从 hub.toml 读取 project_targets,
|
|
1993
|
+
* 为每个项目生成 rules/skills/AGENTS.md 三个 target
|
|
1994
|
+
*/
|
|
1995
|
+
resolveHubItems(hubConfig, _hubRoot) {
|
|
1996
|
+
const tools = hubConfig.tools;
|
|
1997
|
+
const qoderConfig = tools[this.name];
|
|
1998
|
+
const projectTargets = qoderConfig?.projectTargets ?? [];
|
|
1999
|
+
if (!qoderConfig?.enabled || projectTargets.length === 0) {
|
|
2000
|
+
return [];
|
|
2001
|
+
}
|
|
2002
|
+
const results = [];
|
|
2003
|
+
for (const projectRoot of projectTargets) {
|
|
2004
|
+
const base = this.expandHome(projectRoot);
|
|
2005
|
+
results.push({
|
|
2006
|
+
item: {
|
|
2007
|
+
type: "rule",
|
|
2008
|
+
relativePath: "rules",
|
|
2009
|
+
absolutePath: hubPath("rules")
|
|
2010
|
+
},
|
|
2011
|
+
target: {
|
|
2012
|
+
tool: this.name,
|
|
2013
|
+
targetPath: resolve11(base, ".qoder/rules"),
|
|
2014
|
+
strategy: "per-project",
|
|
2015
|
+
isDirectory: true
|
|
2016
|
+
}
|
|
2017
|
+
});
|
|
2018
|
+
results.push({
|
|
2019
|
+
item: {
|
|
2020
|
+
type: "skill",
|
|
2021
|
+
relativePath: "skills",
|
|
2022
|
+
absolutePath: hubPath("skills")
|
|
2023
|
+
},
|
|
2024
|
+
target: {
|
|
2025
|
+
tool: this.name,
|
|
2026
|
+
targetPath: resolve11(base, ".qoder/skills"),
|
|
2027
|
+
strategy: "per-project",
|
|
2028
|
+
isDirectory: true
|
|
2029
|
+
}
|
|
2030
|
+
});
|
|
2031
|
+
results.push({
|
|
2032
|
+
item: {
|
|
2033
|
+
type: "identity",
|
|
2034
|
+
relativePath: "AGENTS.md",
|
|
2035
|
+
absolutePath: hubPath("identity/profile.md")
|
|
2036
|
+
// 锚点
|
|
2037
|
+
},
|
|
2038
|
+
target: {
|
|
2039
|
+
tool: this.name,
|
|
2040
|
+
targetPath: resolve11(base, "AGENTS.md"),
|
|
2041
|
+
strategy: "per-project",
|
|
2042
|
+
isDirectory: false
|
|
2043
|
+
}
|
|
2044
|
+
});
|
|
2045
|
+
}
|
|
2046
|
+
return results;
|
|
2047
|
+
}
|
|
2048
|
+
/**
|
|
2049
|
+
* 同步目标列表(向后兼容)
|
|
2050
|
+
*/
|
|
2051
|
+
targets(config) {
|
|
2052
|
+
const projectTargets = config.projectTargets ?? [];
|
|
2053
|
+
const results = [];
|
|
2054
|
+
for (const projectRoot of projectTargets) {
|
|
2055
|
+
const base = this.expandHome(projectRoot);
|
|
2056
|
+
results.push({
|
|
2057
|
+
tool: this.name,
|
|
2058
|
+
targetPath: resolve11(base, ".qoder/rules"),
|
|
2059
|
+
strategy: "per-project",
|
|
2060
|
+
isDirectory: true
|
|
2061
|
+
});
|
|
2062
|
+
results.push({
|
|
2063
|
+
tool: this.name,
|
|
2064
|
+
targetPath: resolve11(base, ".qoder/skills"),
|
|
2065
|
+
strategy: "per-project",
|
|
2066
|
+
isDirectory: true
|
|
2067
|
+
});
|
|
2068
|
+
results.push({
|
|
2069
|
+
tool: this.name,
|
|
2070
|
+
targetPath: resolve11(base, "AGENTS.md"),
|
|
2071
|
+
strategy: "per-project",
|
|
2072
|
+
isDirectory: false
|
|
2073
|
+
});
|
|
2074
|
+
}
|
|
2075
|
+
return results;
|
|
2076
|
+
}
|
|
2077
|
+
/**
|
|
2078
|
+
* 重写 applyPerProject:
|
|
2079
|
+
* - 目录类(rules/skills):用 symlink,fallback 到 copy
|
|
2080
|
+
* - AGENTS.md:聚合 Hub 内容生成
|
|
2081
|
+
*/
|
|
2082
|
+
async applyPerProject(item, target) {
|
|
2083
|
+
if (target.targetPath.endsWith("AGENTS.md")) {
|
|
2084
|
+
this.applyAgentsMd(target);
|
|
2085
|
+
return;
|
|
2086
|
+
}
|
|
2087
|
+
ensureDir(resolve11(target.targetPath, ".."));
|
|
2088
|
+
if (target.isDirectory) {
|
|
2089
|
+
const r = await createSymlink(item.absolutePath, target.targetPath, {
|
|
2090
|
+
isDirectory: true,
|
|
2091
|
+
force: true
|
|
2092
|
+
});
|
|
2093
|
+
if (!r.success) {
|
|
2094
|
+
copyRecursive(item.absolutePath, target.targetPath);
|
|
2095
|
+
}
|
|
2096
|
+
} else {
|
|
2097
|
+
copyRecursive(item.absolutePath, target.targetPath);
|
|
2098
|
+
}
|
|
2099
|
+
}
|
|
2100
|
+
/**
|
|
2101
|
+
* 生成 AGENTS.md:聚合 Hub 内容(与 Codex 相同的聚合逻辑)
|
|
2102
|
+
*/
|
|
2103
|
+
applyAgentsMd(target) {
|
|
2104
|
+
const lines = [
|
|
2105
|
+
"<!-- AUTO-GENERATED by assetplex. Do not edit directly. Run `assetplex sync` to update. -->",
|
|
2106
|
+
"# AGENTS.md",
|
|
2107
|
+
"",
|
|
2108
|
+
"> \u6B64\u6587\u4EF6\u7531 assetplex \u81EA\u52A8\u805A\u5408 Hub \u4E2D\u7684\u8EAB\u4EFD\u3001\u504F\u597D\u548C\u89C4\u5219\u6587\u4EF6\u751F\u6210\u3002",
|
|
2109
|
+
""
|
|
2110
|
+
];
|
|
2111
|
+
for (const relPath of buildQoderSections()) {
|
|
2112
|
+
const absPath = hubPath(relPath);
|
|
2113
|
+
if (existsSync10(absPath)) {
|
|
2114
|
+
const content = readFileSync6(absPath, "utf-8").trim();
|
|
2115
|
+
if (content) {
|
|
2116
|
+
lines.push(`## ${relPath}`, "", content, "");
|
|
2117
|
+
}
|
|
2118
|
+
}
|
|
2119
|
+
}
|
|
2120
|
+
ensureDir(resolve11(target.targetPath, ".."));
|
|
2121
|
+
writeFileSync6(target.targetPath, lines.join("\n"), "utf-8");
|
|
2122
|
+
}
|
|
2123
|
+
/**
|
|
2124
|
+
* 反向导入:从 Qoder 项目读文件
|
|
2125
|
+
*/
|
|
2126
|
+
async import(targetPath) {
|
|
2127
|
+
const content = readFileSync6(targetPath);
|
|
2128
|
+
let type = "preference";
|
|
2129
|
+
if (targetPath.endsWith("AGENTS.md")) type = "identity";
|
|
2130
|
+
else if (targetPath.includes("skills")) type = "skill";
|
|
2131
|
+
else if (targetPath.includes("rules")) type = "rule";
|
|
2132
|
+
return {
|
|
2133
|
+
type,
|
|
2134
|
+
relativePath: basename2(targetPath),
|
|
2135
|
+
absolutePath: targetPath,
|
|
2136
|
+
content
|
|
2137
|
+
};
|
|
2138
|
+
}
|
|
2139
|
+
/**
|
|
2140
|
+
* 扫描 Qoder 项目目录,发现所有可导入内容
|
|
2141
|
+
*
|
|
2142
|
+
* Qoder 是项目级工具,从 hub.toml 读取 projectTargets 来确定扫描范围。
|
|
2143
|
+
* 为每个项目扫描 .qoder/skills/、.qoder/rules/ 和项目根 AGENTS.md。
|
|
2144
|
+
*/
|
|
2145
|
+
async scan() {
|
|
2146
|
+
let projectTargets = [];
|
|
2147
|
+
try {
|
|
2148
|
+
const config = loadHubConfig();
|
|
2149
|
+
const tools = config.tools;
|
|
2150
|
+
const qoderConfig = tools.qoder;
|
|
2151
|
+
if (qoderConfig?.enabled && qoderConfig.projectTargets?.length) {
|
|
2152
|
+
projectTargets = qoderConfig.projectTargets;
|
|
2153
|
+
}
|
|
2154
|
+
} catch {
|
|
2155
|
+
return [];
|
|
2156
|
+
}
|
|
2157
|
+
const items = [];
|
|
2158
|
+
for (const projectRoot of projectTargets) {
|
|
2159
|
+
const base = this.expandHome(projectRoot);
|
|
2160
|
+
if (!existsSync10(base)) continue;
|
|
2161
|
+
const agentsMdPath = resolve11(base, "AGENTS.md");
|
|
2162
|
+
if (existsSync10(agentsMdPath)) {
|
|
2163
|
+
const projectName = basename2(base) || "project";
|
|
2164
|
+
try {
|
|
2165
|
+
const content = readFileSync6(agentsMdPath, "utf-8");
|
|
2166
|
+
if (!content.includes("AUTO-GENERATED by assetplex")) {
|
|
2167
|
+
const item = this.makeDiscoveredItem(
|
|
2168
|
+
agentsMdPath,
|
|
2169
|
+
base,
|
|
2170
|
+
`identity/qoder-${projectName}.md`,
|
|
2171
|
+
"identity"
|
|
2172
|
+
);
|
|
2173
|
+
if (item) items.push(item);
|
|
2174
|
+
}
|
|
2175
|
+
} catch {
|
|
2176
|
+
}
|
|
2177
|
+
}
|
|
2178
|
+
items.push(...this.scanDirectory(resolve11(base, ".qoder/skills"), base, "skills", "skill"));
|
|
2179
|
+
items.push(...this.scanDirectory(resolve11(base, ".qoder/rules"), base, "rules", "rule"));
|
|
2180
|
+
}
|
|
2181
|
+
return items;
|
|
2182
|
+
}
|
|
2183
|
+
};
|
|
2184
|
+
}
|
|
2185
|
+
});
|
|
2186
|
+
|
|
2187
|
+
// src/core/adapters/registry.ts
|
|
2188
|
+
function registerAdapter(adapter) {
|
|
2189
|
+
registry.set(adapter.name, adapter);
|
|
2190
|
+
}
|
|
2191
|
+
function getAdapter(name) {
|
|
2192
|
+
return registry.get(name);
|
|
2193
|
+
}
|
|
2194
|
+
function getAllAdapters() {
|
|
2195
|
+
return Array.from(registry.values());
|
|
2196
|
+
}
|
|
2197
|
+
function registerBuiltinAdapters() {
|
|
2198
|
+
registerAdapter(new TraeCnAdapter());
|
|
2199
|
+
registerAdapter(new ClaudeCodeAdapter());
|
|
2200
|
+
registerAdapter(new CodexAdapter());
|
|
2201
|
+
registerAdapter(new WorkBuddyAdapter());
|
|
2202
|
+
registerAdapter(new QoderAdapter());
|
|
2203
|
+
}
|
|
2204
|
+
var registry;
|
|
2205
|
+
var init_registry = __esm({
|
|
2206
|
+
"src/core/adapters/registry.ts"() {
|
|
2207
|
+
"use strict";
|
|
2208
|
+
init_esm_shims();
|
|
2209
|
+
init_trae_cn();
|
|
2210
|
+
init_claude_code();
|
|
2211
|
+
init_codex();
|
|
2212
|
+
init_workbuddy();
|
|
2213
|
+
init_qoder();
|
|
2214
|
+
registry = /* @__PURE__ */ new Map();
|
|
2215
|
+
}
|
|
2216
|
+
});
|
|
2217
|
+
|
|
2218
|
+
// src/core/scanner.ts
|
|
2219
|
+
async function scanAll() {
|
|
2220
|
+
const adapters = getAllAdapters();
|
|
2221
|
+
const results = [];
|
|
2222
|
+
for (const adapter of adapters) {
|
|
2223
|
+
try {
|
|
2224
|
+
const status = await adapter.detect();
|
|
2225
|
+
let items = [];
|
|
2226
|
+
if (status.installed || adapter.name === "qoder") {
|
|
2227
|
+
try {
|
|
2228
|
+
items = await adapter.scan();
|
|
2229
|
+
} catch (scanErr) {
|
|
2230
|
+
log.warn(`[${adapter.name}] scan \u5931\u8D25: ${scanErr.message}`);
|
|
2231
|
+
items = [];
|
|
2232
|
+
}
|
|
2233
|
+
}
|
|
2234
|
+
results.push({
|
|
2235
|
+
toolName: adapter.name,
|
|
2236
|
+
displayName: adapter.displayName,
|
|
2237
|
+
installed: status.installed,
|
|
2238
|
+
configDir: status.configDir,
|
|
2239
|
+
items
|
|
2240
|
+
});
|
|
2241
|
+
} catch (err) {
|
|
2242
|
+
log.warn(`[${adapter.name}] detect \u5931\u8D25: ${err.message}`);
|
|
2243
|
+
results.push({
|
|
2244
|
+
toolName: adapter.name,
|
|
2245
|
+
displayName: adapter.displayName,
|
|
2246
|
+
installed: false,
|
|
2247
|
+
configDir: "",
|
|
2248
|
+
items: []
|
|
2249
|
+
});
|
|
2250
|
+
}
|
|
2251
|
+
}
|
|
2252
|
+
return results;
|
|
2253
|
+
}
|
|
2254
|
+
var init_scanner = __esm({
|
|
2255
|
+
"src/core/scanner.ts"() {
|
|
2256
|
+
"use strict";
|
|
2257
|
+
init_esm_shims();
|
|
2258
|
+
init_registry();
|
|
2259
|
+
init_logger();
|
|
2260
|
+
}
|
|
2261
|
+
});
|
|
2262
|
+
|
|
2263
|
+
// src/core/merger.ts
|
|
2264
|
+
function getFileType(filepath) {
|
|
2265
|
+
if (filepath.endsWith(".md")) return "md";
|
|
2266
|
+
if (filepath.endsWith(".json")) return "json";
|
|
2267
|
+
if (filepath.endsWith(".toml")) return "toml";
|
|
2268
|
+
return "other";
|
|
2269
|
+
}
|
|
2270
|
+
function mergeMarkdown(existingContent, newContent, source) {
|
|
2271
|
+
const sourceLabel = `<!-- \u4EE5\u4E0B\u5185\u5BB9\u4ECE ${source} \u5BFC\u5165\uFF0C\u5BFC\u5165\u65F6\u95F4: ${(/* @__PURE__ */ new Date()).toISOString()} -->`;
|
|
2272
|
+
return [
|
|
2273
|
+
existingContent.trimEnd(),
|
|
2274
|
+
"",
|
|
2275
|
+
"---",
|
|
2276
|
+
"",
|
|
2277
|
+
sourceLabel,
|
|
2278
|
+
"",
|
|
2279
|
+
newContent.trimStart()
|
|
2280
|
+
].join("\n");
|
|
2281
|
+
}
|
|
2282
|
+
function mergeMcpJson(existingContent, newContent) {
|
|
2283
|
+
try {
|
|
2284
|
+
const existing = JSON.parse(existingContent);
|
|
2285
|
+
const incoming = JSON.parse(newContent);
|
|
2286
|
+
if (incoming.mcpServers && typeof incoming.mcpServers === "object") {
|
|
2287
|
+
if (!existing.mcpServers) {
|
|
2288
|
+
existing.mcpServers = {};
|
|
2289
|
+
}
|
|
2290
|
+
for (const [key, value] of Object.entries(incoming.mcpServers)) {
|
|
2291
|
+
if (!(key in existing.mcpServers)) {
|
|
2292
|
+
existing.mcpServers[key] = value;
|
|
2293
|
+
}
|
|
2294
|
+
}
|
|
2295
|
+
}
|
|
2296
|
+
return JSON.stringify(existing, null, 2);
|
|
2297
|
+
} catch {
|
|
2298
|
+
return mergeMarkdown(existingContent, newContent, "mcp-json");
|
|
2299
|
+
}
|
|
2300
|
+
}
|
|
2301
|
+
function mergeToml(existingContent, newContent) {
|
|
2302
|
+
try {
|
|
2303
|
+
const existingJson = mcpTomlToJson(existingContent);
|
|
2304
|
+
const newJson = mcpTomlToJson(newContent);
|
|
2305
|
+
const mergedJson = mergeMcpJson(existingJson, newJson);
|
|
2306
|
+
return mcpJsonToToml(mergedJson);
|
|
2307
|
+
} catch {
|
|
2308
|
+
return mergeMarkdown(existingContent, newContent, "toml");
|
|
2309
|
+
}
|
|
2310
|
+
}
|
|
2311
|
+
function mergeFile(hubTargetPath, newContent, existingContent, source) {
|
|
2312
|
+
if (isStructuredTarget(hubTargetPath)) {
|
|
2313
|
+
return mergeStructuredMd(existingContent, newContent, source);
|
|
2314
|
+
}
|
|
2315
|
+
const type = getFileType(hubTargetPath);
|
|
2316
|
+
switch (type) {
|
|
2317
|
+
case "json":
|
|
2318
|
+
return { content: mergeMcpJson(existingContent, newContent), action: "merged" };
|
|
2319
|
+
case "toml":
|
|
2320
|
+
return { content: mergeToml(existingContent, newContent), action: "merged" };
|
|
2321
|
+
case "md":
|
|
2322
|
+
default:
|
|
2323
|
+
return { content: mergeMarkdown(existingContent, newContent, source), action: "merged" };
|
|
2324
|
+
}
|
|
2325
|
+
}
|
|
2326
|
+
function isStructuredTarget(hubTargetPath) {
|
|
2327
|
+
return hubTargetPath.startsWith("identity/") && hubTargetPath.endsWith(".md");
|
|
2328
|
+
}
|
|
2329
|
+
function parseStructuredMd(content) {
|
|
2330
|
+
const fields = /* @__PURE__ */ new Map();
|
|
2331
|
+
const unstructured = [];
|
|
2332
|
+
const lines = content.split("\n");
|
|
2333
|
+
for (const line of lines) {
|
|
2334
|
+
const trimmed = line.trim();
|
|
2335
|
+
const match = trimmed.match(STRUCTURED_FIELD_RE);
|
|
2336
|
+
if (match) {
|
|
2337
|
+
const key = match[1].trim();
|
|
2338
|
+
const value = match[2].trim();
|
|
2339
|
+
fields.set(key, value);
|
|
2340
|
+
} else {
|
|
2341
|
+
unstructured.push(line);
|
|
2342
|
+
}
|
|
2343
|
+
}
|
|
2344
|
+
return { fields, unstructured };
|
|
2345
|
+
}
|
|
2346
|
+
function mergeStructuredMd(existingContent, newContent, source) {
|
|
2347
|
+
const existing = parseStructuredMd(existingContent);
|
|
2348
|
+
const incoming = parseStructuredMd(newContent);
|
|
2349
|
+
const mergedFields = new Map(existing.fields);
|
|
2350
|
+
for (const [key, value] of incoming.fields) {
|
|
2351
|
+
mergedFields.set(key, value);
|
|
2352
|
+
}
|
|
2353
|
+
const lines = [];
|
|
2354
|
+
const sortedKeys = [...mergedFields.keys()].sort();
|
|
2355
|
+
for (const key of sortedKeys) {
|
|
2356
|
+
const value = mergedFields.get(key) ?? "";
|
|
2357
|
+
lines.push(`**${key}**\uFF1A${value}`);
|
|
2358
|
+
}
|
|
2359
|
+
const existingUnstructured = existing.unstructured.map((l) => l.trimEnd()).filter((l) => l.length > 0);
|
|
2360
|
+
const incomingUnstructured = incoming.unstructured.map((l) => l.trimEnd()).filter((l) => l.length > 0);
|
|
2361
|
+
if (existingUnstructured.length > 0 || incomingUnstructured.length > 0) {
|
|
2362
|
+
lines.push("");
|
|
2363
|
+
if (existingUnstructured.length > 0) {
|
|
2364
|
+
lines.push(...existingUnstructured);
|
|
2365
|
+
}
|
|
2366
|
+
if (incomingUnstructured.length > 0) {
|
|
2367
|
+
if (existingUnstructured.length > 0) {
|
|
2368
|
+
lines.push("");
|
|
2369
|
+
lines.push("---");
|
|
2370
|
+
lines.push(`<!-- \u4EE5\u4E0B\u5185\u5BB9\u4ECE ${source} \u5BFC\u5165 -->`);
|
|
2371
|
+
lines.push("");
|
|
2372
|
+
}
|
|
2373
|
+
lines.push(...incomingUnstructured);
|
|
2374
|
+
}
|
|
2375
|
+
}
|
|
2376
|
+
return { content: lines.join("\n") + "\n", action: "merged" };
|
|
2377
|
+
}
|
|
2378
|
+
var STRUCTURED_FIELD_RE;
|
|
2379
|
+
var init_merger = __esm({
|
|
2380
|
+
"src/core/merger.ts"() {
|
|
2381
|
+
"use strict";
|
|
2382
|
+
init_esm_shims();
|
|
2383
|
+
init_json_toml();
|
|
2384
|
+
STRUCTURED_FIELD_RE = /^[-*\s]*\*\*(.+?)\*\*[::]\s*(.*)$/;
|
|
2385
|
+
}
|
|
2386
|
+
});
|
|
2387
|
+
|
|
2388
|
+
// src/core/hub-files.ts
|
|
2389
|
+
var hub_files_exports = {};
|
|
2390
|
+
__export(hub_files_exports, {
|
|
2391
|
+
HUB_CATEGORIES: () => HUB_CATEGORIES,
|
|
2392
|
+
checkHubHealth: () => checkHubHealth,
|
|
2393
|
+
createHubFile: () => createHubFile,
|
|
2394
|
+
deleteHubFile: () => deleteHubFile,
|
|
2395
|
+
listHubFiles: () => listHubFiles,
|
|
2396
|
+
readHubFile: () => readHubFile,
|
|
2397
|
+
writeHubFile: () => writeHubFile
|
|
2398
|
+
});
|
|
2399
|
+
import {
|
|
2400
|
+
existsSync as existsSync12,
|
|
2401
|
+
readdirSync as readdirSync4,
|
|
2402
|
+
readFileSync as readFileSync7,
|
|
2403
|
+
writeFileSync as writeFileSync7,
|
|
2404
|
+
statSync as statSync3,
|
|
2405
|
+
unlinkSync as unlinkSync2
|
|
2406
|
+
} from "fs";
|
|
2407
|
+
import { join as join3, resolve as resolve12, dirname as dirname4, relative as relative3 } from "path";
|
|
2408
|
+
function listHubFiles(category) {
|
|
2409
|
+
const hubRoot = getHubRoot();
|
|
2410
|
+
const categoryDir = join3(hubRoot, category);
|
|
2411
|
+
if (!existsSync12(categoryDir)) {
|
|
2412
|
+
return [];
|
|
2413
|
+
}
|
|
2414
|
+
const files = [];
|
|
2415
|
+
collectFiles(categoryDir, hubRoot, files);
|
|
2416
|
+
return files.sort((a, b) => a.relativePath.localeCompare(b.relativePath));
|
|
2417
|
+
}
|
|
2418
|
+
function collectFiles(currentDir, hubRoot, out) {
|
|
2419
|
+
const entries = readdirSync4(currentDir, { withFileTypes: true });
|
|
2420
|
+
for (const entry of entries) {
|
|
2421
|
+
const fullPath = join3(currentDir, entry.name);
|
|
2422
|
+
if (entry.isDirectory()) {
|
|
2423
|
+
if (entry.name.startsWith(".")) continue;
|
|
2424
|
+
collectFiles(fullPath, hubRoot, out);
|
|
2425
|
+
} else if (entry.isFile()) {
|
|
2426
|
+
if (entry.name.startsWith(".")) continue;
|
|
2427
|
+
const stat = statSync3(fullPath);
|
|
2428
|
+
out.push({
|
|
2429
|
+
relativePath: relative3(hubRoot, fullPath).replace(/\\/g, "/"),
|
|
2430
|
+
absolutePath: fullPath,
|
|
2431
|
+
size: stat.size,
|
|
2432
|
+
modified: stat.mtime.toISOString(),
|
|
2433
|
+
isDirectory: false
|
|
2434
|
+
});
|
|
2435
|
+
}
|
|
2436
|
+
}
|
|
2437
|
+
}
|
|
2438
|
+
function readHubFile(relativePath) {
|
|
2439
|
+
const hubRoot = getHubRoot();
|
|
2440
|
+
const fullPath = resolve12(hubRoot, relativePath);
|
|
2441
|
+
if (!fullPath.startsWith(hubRoot)) {
|
|
2442
|
+
throw new Error(`\u8DEF\u5F84\u8D8A\u754C\uFF1A${relativePath}`);
|
|
2443
|
+
}
|
|
2444
|
+
if (!existsSync12(fullPath)) {
|
|
2445
|
+
return null;
|
|
2446
|
+
}
|
|
2447
|
+
const stat = statSync3(fullPath);
|
|
2448
|
+
if (stat.isDirectory()) {
|
|
2449
|
+
throw new Error(`\u8DEF\u5F84\u662F\u76EE\u5F55\u800C\u975E\u6587\u4EF6\uFF1A${relativePath}`);
|
|
2450
|
+
}
|
|
2451
|
+
return {
|
|
2452
|
+
content: readFileSync7(fullPath, "utf-8"),
|
|
2453
|
+
size: stat.size,
|
|
2454
|
+
modified: stat.mtime.toISOString()
|
|
2455
|
+
};
|
|
2456
|
+
}
|
|
2457
|
+
function writeHubFile(relativePath, content) {
|
|
2458
|
+
const hubRoot = getHubRoot();
|
|
2459
|
+
const fullPath = resolve12(hubRoot, relativePath);
|
|
2460
|
+
if (!fullPath.startsWith(hubRoot)) {
|
|
2461
|
+
throw new Error(`\u8DEF\u5F84\u8D8A\u754C\uFF1A${relativePath}`);
|
|
2462
|
+
}
|
|
2463
|
+
ensureDir(dirname4(fullPath));
|
|
2464
|
+
writeFileSync7(fullPath, content, "utf-8");
|
|
2465
|
+
}
|
|
2466
|
+
function deleteHubFile(relativePath) {
|
|
2467
|
+
const hubRoot = getHubRoot();
|
|
2468
|
+
const fullPath = resolve12(hubRoot, relativePath);
|
|
2469
|
+
if (!fullPath.startsWith(hubRoot)) {
|
|
2470
|
+
throw new Error(`\u8DEF\u5F84\u8D8A\u754C\uFF1A${relativePath}`);
|
|
2471
|
+
}
|
|
2472
|
+
if (!existsSync12(fullPath)) {
|
|
2473
|
+
return;
|
|
2474
|
+
}
|
|
2475
|
+
const stat = statSync3(fullPath);
|
|
2476
|
+
if (stat.isDirectory()) {
|
|
2477
|
+
throw new Error(`\u6682\u4E0D\u652F\u6301\u5220\u9664\u76EE\u5F55\uFF1A${relativePath}`);
|
|
2478
|
+
}
|
|
2479
|
+
unlinkSync2(fullPath);
|
|
2480
|
+
}
|
|
2481
|
+
function createHubFile(relativePath, content) {
|
|
2482
|
+
const hubRoot = getHubRoot();
|
|
2483
|
+
const fullPath = resolve12(hubRoot, relativePath);
|
|
2484
|
+
if (!fullPath.startsWith(hubRoot)) {
|
|
2485
|
+
throw new Error(`\u8DEF\u5F84\u8D8A\u754C\uFF1A${relativePath}`);
|
|
2486
|
+
}
|
|
2487
|
+
if (existsSync12(fullPath)) {
|
|
2488
|
+
throw new Error(`\u6587\u4EF6\u5DF2\u5B58\u5728\uFF1A${relativePath}`);
|
|
2489
|
+
}
|
|
2490
|
+
ensureDir(dirname4(fullPath));
|
|
2491
|
+
writeFileSync7(fullPath, content, "utf-8");
|
|
2492
|
+
}
|
|
2493
|
+
function checkHubHealth() {
|
|
2494
|
+
const hubRoot = getHubRoot();
|
|
2495
|
+
const health = {
|
|
2496
|
+
hubRoot,
|
|
2497
|
+
hubExists: existsSync12(hubRoot),
|
|
2498
|
+
hubTomlExists: existsSync12(join3(hubRoot, "hub.toml")),
|
|
2499
|
+
fileCountByCategory: {},
|
|
2500
|
+
totalFiles: 0
|
|
2501
|
+
};
|
|
2502
|
+
for (const cat of HUB_CATEGORIES) {
|
|
2503
|
+
const files = listHubFiles(cat);
|
|
2504
|
+
health.fileCountByCategory[cat] = files.length;
|
|
2505
|
+
health.totalFiles += files.length;
|
|
2506
|
+
}
|
|
2507
|
+
return health;
|
|
2508
|
+
}
|
|
2509
|
+
var HUB_CATEGORIES;
|
|
2510
|
+
var init_hub_files = __esm({
|
|
2511
|
+
"src/core/hub-files.ts"() {
|
|
2512
|
+
"use strict";
|
|
2513
|
+
init_esm_shims();
|
|
2514
|
+
init_paths();
|
|
2515
|
+
init_fs();
|
|
2516
|
+
HUB_CATEGORIES = [
|
|
2517
|
+
"identity",
|
|
2518
|
+
"skills",
|
|
2519
|
+
"rules",
|
|
2520
|
+
"preferences",
|
|
2521
|
+
"mcp",
|
|
2522
|
+
"commands",
|
|
2523
|
+
"agents"
|
|
2524
|
+
];
|
|
2525
|
+
}
|
|
2526
|
+
});
|
|
2527
|
+
|
|
2528
|
+
// src/core/sync-engine.ts
|
|
2529
|
+
import { existsSync as existsSync13, readFileSync as readFileSync8 } from "fs";
|
|
2530
|
+
import { resolve as resolve13 } from "path";
|
|
2531
|
+
function printSyncPlan(plans) {
|
|
2532
|
+
for (const plan of plans) {
|
|
2533
|
+
log.info("");
|
|
2534
|
+
log.info(`\u5DE5\u5177: ${plan.tool} ${plan.toolInstalled ? "\u2713" : "\u2717"}`);
|
|
2535
|
+
if (plan.items.length === 0) {
|
|
2536
|
+
log.info(" (\u65E0\u5F85\u540C\u6B65\u6761\u76EE)");
|
|
2537
|
+
continue;
|
|
2538
|
+
}
|
|
2539
|
+
for (const item of plan.items) {
|
|
2540
|
+
const icon = item.action === "skip" ? "\xB7" : "\u2192";
|
|
2541
|
+
const action = item.action.padEnd(15);
|
|
2542
|
+
const target = item.target.targetPath || "(\u65E0)";
|
|
2543
|
+
log.info(` ${icon} ${action} ${target}`);
|
|
2544
|
+
if (item.reason) {
|
|
2545
|
+
log.info(` \u2514 ${item.reason}`);
|
|
2546
|
+
}
|
|
2547
|
+
}
|
|
2548
|
+
}
|
|
2549
|
+
}
|
|
2550
|
+
function printSyncResult(results) {
|
|
2551
|
+
log.info("");
|
|
2552
|
+
log.info("\u540C\u6B65\u7ED3\u679C");
|
|
2553
|
+
log.info("=".repeat(60));
|
|
2554
|
+
let totalItem = 0;
|
|
2555
|
+
let totalSkip = 0;
|
|
2556
|
+
let totalError = 0;
|
|
2557
|
+
for (const r of results) {
|
|
2558
|
+
const icon = r.success ? "\u2713" : "\u2717";
|
|
2559
|
+
log.info(`${icon} ${r.tool.padEnd(15)} \u540C\u6B65 ${r.itemCount}\uFF0C\u8DF3\u8FC7 ${r.skippedCount}\uFF0C\u9519\u8BEF ${r.errors.length}\uFF08${r.durationMs}ms\uFF09`);
|
|
2560
|
+
totalItem += r.itemCount;
|
|
2561
|
+
totalSkip += r.skippedCount;
|
|
2562
|
+
totalError += r.errors.length;
|
|
2563
|
+
for (const err of r.errors) {
|
|
2564
|
+
log.warn(` \u2514 ${err}`);
|
|
2565
|
+
}
|
|
2566
|
+
}
|
|
2567
|
+
log.info("-".repeat(60));
|
|
2568
|
+
log.info(`\u603B\u8BA1\uFF1A\u540C\u6B65 ${totalItem}\uFF0C\u8DF3\u8FC7 ${totalSkip}\uFF0C\u9519\u8BEF ${totalError}`);
|
|
2569
|
+
}
|
|
2570
|
+
var FILTERED_OUT, SyncEngine;
|
|
2571
|
+
var init_sync_engine = __esm({
|
|
2572
|
+
"src/core/sync-engine.ts"() {
|
|
2573
|
+
"use strict";
|
|
2574
|
+
init_esm_shims();
|
|
2575
|
+
init_registry();
|
|
2576
|
+
init_paths();
|
|
2577
|
+
init_logger();
|
|
2578
|
+
init_fs();
|
|
2579
|
+
init_scanner();
|
|
2580
|
+
init_merger();
|
|
2581
|
+
init_hub_files();
|
|
2582
|
+
FILTERED_OUT = /* @__PURE__ */ Symbol("FILTERED_OUT");
|
|
2583
|
+
SyncEngine = class {
|
|
2584
|
+
constructor(hubConfig, adapters = getAllAdapters()) {
|
|
2585
|
+
this.hubConfig = hubConfig;
|
|
2586
|
+
this.adapters = adapters;
|
|
2587
|
+
}
|
|
2588
|
+
hubConfig;
|
|
2589
|
+
adapters;
|
|
2590
|
+
/**
|
|
2591
|
+
* 计算同步计划(不执行)
|
|
2592
|
+
*
|
|
2593
|
+
* 算法:
|
|
2594
|
+
* 1. 过滤 adapters:仅保留 enabled 且(--tool 指定或全部)
|
|
2595
|
+
* 2. 对每个 adapter:
|
|
2596
|
+
* - 调用 detect() 检查工具是否安装
|
|
2597
|
+
* - 调用 resolveHubItems() 获取 (item, target) 列表
|
|
2598
|
+
* - 对每个 (item, target):
|
|
2599
|
+
* - 检查 item.absolutePath 是否存在(不存在 → skip)
|
|
2600
|
+
* - 检查 target.targetPath 当前状态(已是正确 symlink → skip)
|
|
2601
|
+
* - 否则 → 待执行 action
|
|
2602
|
+
*/
|
|
2603
|
+
async plan(options = {}) {
|
|
2604
|
+
const hubRoot = getHubRoot();
|
|
2605
|
+
const plans = [];
|
|
2606
|
+
for (const adapter of this.adapters) {
|
|
2607
|
+
const planForTool = await this.planForAdapter(adapter, hubRoot, options);
|
|
2608
|
+
if (planForTool === FILTERED_OUT) continue;
|
|
2609
|
+
plans.push(planForTool);
|
|
2610
|
+
}
|
|
2611
|
+
return plans;
|
|
2612
|
+
}
|
|
2613
|
+
/**
|
|
2614
|
+
* 为单个适配器计算计划
|
|
2615
|
+
*/
|
|
2616
|
+
async planForAdapter(adapter, hubRoot, options) {
|
|
2617
|
+
const toolName = adapter.name;
|
|
2618
|
+
const toolFilter = options.tools?.length ? options.tools : options.tool ? [options.tool] : void 0;
|
|
2619
|
+
if (toolFilter && !toolFilter.includes(toolName)) {
|
|
2620
|
+
return FILTERED_OUT;
|
|
2621
|
+
}
|
|
2622
|
+
if (!this.isToolEnabled(toolName)) {
|
|
2623
|
+
return {
|
|
2624
|
+
tool: toolName,
|
|
2625
|
+
toolInstalled: false,
|
|
2626
|
+
items: []
|
|
2627
|
+
};
|
|
2628
|
+
}
|
|
2629
|
+
let status;
|
|
2630
|
+
try {
|
|
2631
|
+
status = await adapter.detect();
|
|
2632
|
+
} catch (err) {
|
|
2633
|
+
return {
|
|
2634
|
+
tool: toolName,
|
|
2635
|
+
toolInstalled: false,
|
|
2636
|
+
items: [
|
|
2637
|
+
{
|
|
2638
|
+
item: { type: "preference", relativePath: "", absolutePath: "" },
|
|
2639
|
+
target: {
|
|
2640
|
+
tool: toolName,
|
|
2641
|
+
targetPath: "",
|
|
2642
|
+
strategy: "symlink",
|
|
2643
|
+
isDirectory: false
|
|
2644
|
+
},
|
|
2645
|
+
action: "skip",
|
|
2646
|
+
reason: `detect \u5931\u8D25: ${err.message}`
|
|
2647
|
+
}
|
|
2648
|
+
]
|
|
2649
|
+
};
|
|
2650
|
+
}
|
|
2651
|
+
const isQoder = toolName === "qoder";
|
|
2652
|
+
if (!status.installed && !isQoder) {
|
|
2653
|
+
return {
|
|
2654
|
+
tool: toolName,
|
|
2655
|
+
toolInstalled: false,
|
|
2656
|
+
items: [
|
|
2657
|
+
{
|
|
2658
|
+
item: { type: "preference", relativePath: "", absolutePath: "" },
|
|
2659
|
+
target: {
|
|
2660
|
+
tool: toolName,
|
|
2661
|
+
targetPath: status.configDir,
|
|
2662
|
+
strategy: "symlink",
|
|
2663
|
+
isDirectory: false
|
|
2664
|
+
},
|
|
2665
|
+
action: "skip",
|
|
2666
|
+
reason: status.error ?? "tool not installed"
|
|
2667
|
+
}
|
|
2668
|
+
]
|
|
2669
|
+
};
|
|
2670
|
+
}
|
|
2671
|
+
const hubItems = adapter.resolveHubItems(this.hubConfig, hubRoot);
|
|
2672
|
+
const items = hubItems.map(({ item, target }) => {
|
|
2673
|
+
if (!item.absolutePath || !existsSync13(item.absolutePath)) {
|
|
2674
|
+
return {
|
|
2675
|
+
item,
|
|
2676
|
+
target,
|
|
2677
|
+
action: "skip",
|
|
2678
|
+
reason: `Hub \u6E90\u4E0D\u5B58\u5728: ${item.relativePath || item.absolutePath}`
|
|
2679
|
+
};
|
|
2680
|
+
}
|
|
2681
|
+
if (target.strategy === "symlink" && isSymlink(target.targetPath)) {
|
|
2682
|
+
const currentTarget = readSymlinkTarget(target.targetPath);
|
|
2683
|
+
if (currentTarget && resolve13(currentTarget) === resolve13(item.absolutePath)) {
|
|
2684
|
+
return {
|
|
2685
|
+
item,
|
|
2686
|
+
target,
|
|
2687
|
+
action: "skip",
|
|
2688
|
+
reason: "\u5DF2\u662F\u6B63\u786E\u7B26\u53F7\u94FE\u63A5\uFF0C\u8DF3\u8FC7"
|
|
2689
|
+
};
|
|
2690
|
+
}
|
|
2691
|
+
}
|
|
2692
|
+
let warning;
|
|
2693
|
+
if (existsSync13(target.targetPath) && !isSymlink(target.targetPath)) {
|
|
2694
|
+
warning = "\u76EE\u6807\u8DEF\u5F84\u5DF2\u6709\u975E symlink \u6587\u4EF6\uFF0C\u540C\u6B65\u540E\u5C06\u8986\u76D6";
|
|
2695
|
+
}
|
|
2696
|
+
return {
|
|
2697
|
+
item,
|
|
2698
|
+
target,
|
|
2699
|
+
action: target.strategy,
|
|
2700
|
+
warning
|
|
2701
|
+
};
|
|
2702
|
+
});
|
|
2703
|
+
return {
|
|
2704
|
+
tool: toolName,
|
|
2705
|
+
toolInstalled: status.installed,
|
|
2706
|
+
items
|
|
2707
|
+
};
|
|
2708
|
+
}
|
|
2709
|
+
/**
|
|
2710
|
+
* 执行同步
|
|
2711
|
+
*/
|
|
2712
|
+
async run(options = {}) {
|
|
2713
|
+
const plans = await this.plan(options);
|
|
2714
|
+
const results = [];
|
|
2715
|
+
for (const plan of plans) {
|
|
2716
|
+
const result = await this.runPlan(plan);
|
|
2717
|
+
results.push(result);
|
|
2718
|
+
}
|
|
2719
|
+
return results;
|
|
2720
|
+
}
|
|
2721
|
+
/**
|
|
2722
|
+
* 执行单个工具的同步计划
|
|
2723
|
+
*/
|
|
2724
|
+
async runPlan(plan) {
|
|
2725
|
+
const startTime = Date.now();
|
|
2726
|
+
const errors = [];
|
|
2727
|
+
const warnings = [];
|
|
2728
|
+
let itemCount = 0;
|
|
2729
|
+
let skippedCount = 0;
|
|
2730
|
+
const adapter = this.adapters.find((a) => a.name === plan.tool);
|
|
2731
|
+
if (!adapter) {
|
|
2732
|
+
return {
|
|
2733
|
+
tool: plan.tool,
|
|
2734
|
+
success: false,
|
|
2735
|
+
itemCount: 0,
|
|
2736
|
+
skippedCount: plan.items.length,
|
|
2737
|
+
errors: [`\u9002\u914D\u5668\u672A\u6CE8\u518C: ${plan.tool}`],
|
|
2738
|
+
warnings: [],
|
|
2739
|
+
durationMs: Date.now() - startTime
|
|
2740
|
+
};
|
|
2741
|
+
}
|
|
2742
|
+
for (const planItem of plan.items) {
|
|
2743
|
+
if (planItem.action === "skip") {
|
|
2744
|
+
skippedCount++;
|
|
2745
|
+
if (planItem.reason) {
|
|
2746
|
+
log.debug(`[${plan.tool}] \u8DF3\u8FC7: ${planItem.reason}`);
|
|
2747
|
+
}
|
|
2748
|
+
continue;
|
|
2749
|
+
}
|
|
2750
|
+
try {
|
|
2751
|
+
await adapter.apply(planItem.item, planItem.target);
|
|
2752
|
+
itemCount++;
|
|
2753
|
+
log.debug(`[${plan.tool}] \u2713 ${planItem.target.targetPath}`);
|
|
2754
|
+
} catch (err) {
|
|
2755
|
+
const msg = `${planItem.target.targetPath}: ${err.message}`;
|
|
2756
|
+
errors.push(msg);
|
|
2757
|
+
log.warn(`[${plan.tool}] \u2717 ${msg}`);
|
|
2758
|
+
}
|
|
2759
|
+
}
|
|
2760
|
+
return {
|
|
2761
|
+
tool: plan.tool,
|
|
2762
|
+
success: errors.length === 0,
|
|
2763
|
+
itemCount,
|
|
2764
|
+
skippedCount,
|
|
2765
|
+
errors,
|
|
2766
|
+
warnings,
|
|
2767
|
+
durationMs: Date.now() - startTime
|
|
2768
|
+
};
|
|
2769
|
+
}
|
|
2770
|
+
/**
|
|
2771
|
+
* 反向导入:从工具目录读回 Hub
|
|
2772
|
+
*
|
|
2773
|
+
* 注意:当前 MVP 实现仅返回 HubItem 列表,不写入 Hub;
|
|
2774
|
+
* 由 CLI 命令决定如何处理(询问用户确认后写入)
|
|
2775
|
+
*/
|
|
2776
|
+
async reverseImport(options = {}) {
|
|
2777
|
+
const hubRoot = getHubRoot();
|
|
2778
|
+
const results = [];
|
|
2779
|
+
for (const adapter of this.adapters) {
|
|
2780
|
+
const toolFilter = options.tools?.length ? options.tools : options.tool ? [options.tool] : void 0;
|
|
2781
|
+
if (toolFilter && !toolFilter.includes(adapter.name)) continue;
|
|
2782
|
+
if (!this.isToolEnabled(adapter.name)) continue;
|
|
2783
|
+
const status = await adapter.detect();
|
|
2784
|
+
if (!status.installed && adapter.name !== "qoder") continue;
|
|
2785
|
+
const hubItems = adapter.resolveHubItems(this.hubConfig, hubRoot);
|
|
2786
|
+
const imported = [];
|
|
2787
|
+
for (const { target } of hubItems) {
|
|
2788
|
+
if (!target.targetPath || !existsSync13(target.targetPath)) continue;
|
|
2789
|
+
try {
|
|
2790
|
+
const item = await adapter.import(target.targetPath);
|
|
2791
|
+
imported.push(item);
|
|
2792
|
+
} catch (err) {
|
|
2793
|
+
log.warn(`[${adapter.name}] import \u5931\u8D25 ${target.targetPath}: ${err.message}`);
|
|
2794
|
+
}
|
|
2795
|
+
}
|
|
2796
|
+
results.push({ tool: adapter.name, items: imported });
|
|
2797
|
+
}
|
|
2798
|
+
return results;
|
|
2799
|
+
}
|
|
2800
|
+
/**
|
|
2801
|
+
* 扫描所有工具的可导入内容(导入向导 Step 1-2 使用)
|
|
2802
|
+
*/
|
|
2803
|
+
async scanAll() {
|
|
2804
|
+
return scanAll();
|
|
2805
|
+
}
|
|
2806
|
+
/**
|
|
2807
|
+
* 执行导入:接收用户选择的条目列表,写入 Hub(导入向导 Step 4 使用)
|
|
2808
|
+
*/
|
|
2809
|
+
async executeImport(request) {
|
|
2810
|
+
const result = {
|
|
2811
|
+
success: true,
|
|
2812
|
+
created: 0,
|
|
2813
|
+
merged: 0,
|
|
2814
|
+
overwritten: 0,
|
|
2815
|
+
skipped: 0,
|
|
2816
|
+
errors: 0,
|
|
2817
|
+
items: []
|
|
2818
|
+
};
|
|
2819
|
+
for (const item of request.items) {
|
|
2820
|
+
try {
|
|
2821
|
+
const sourceContent = readFileSync8(item.absolutePath, "utf-8");
|
|
2822
|
+
let contentToWrite = sourceContent;
|
|
2823
|
+
if (item.absolutePath.endsWith("config.toml") && item.hubTargetPath.endsWith(".json")) {
|
|
2824
|
+
const { mcpTomlToJson: mcpTomlToJson2 } = await Promise.resolve().then(() => (init_json_toml(), json_toml_exports));
|
|
2825
|
+
contentToWrite = mcpTomlToJson2(sourceContent);
|
|
2826
|
+
}
|
|
2827
|
+
if (item.tool === "workbuddy" && item.absolutePath.endsWith(".mcp.json")) {
|
|
2828
|
+
const { extractEnvVars: extractEnvVars2, buildEnvMap: buildEnvMap2, desinterpolateEnv: desinterpolateEnv2 } = await Promise.resolve().then(() => (init_env_interpolation(), env_interpolation_exports));
|
|
2829
|
+
const varNames = extractEnvVars2(contentToWrite);
|
|
2830
|
+
const envMap = buildEnvMap2(varNames);
|
|
2831
|
+
if (Object.keys(envMap).length > 0) {
|
|
2832
|
+
contentToWrite = desinterpolateEnv2(contentToWrite, envMap);
|
|
2833
|
+
}
|
|
2834
|
+
}
|
|
2835
|
+
const existing = readHubFile(item.hubTargetPath);
|
|
2836
|
+
if (!existing) {
|
|
2837
|
+
writeHubFile(item.hubTargetPath, contentToWrite);
|
|
2838
|
+
result.items.push({
|
|
2839
|
+
tool: item.tool,
|
|
2840
|
+
hubTargetPath: item.hubTargetPath,
|
|
2841
|
+
status: "created"
|
|
2842
|
+
});
|
|
2843
|
+
result.created++;
|
|
2844
|
+
} else if (item.strategy === "overwrite") {
|
|
2845
|
+
writeHubFile(item.hubTargetPath, contentToWrite);
|
|
2846
|
+
result.items.push({
|
|
2847
|
+
tool: item.tool,
|
|
2848
|
+
hubTargetPath: item.hubTargetPath,
|
|
2849
|
+
status: "overwritten"
|
|
2850
|
+
});
|
|
2851
|
+
result.overwritten++;
|
|
2852
|
+
} else if (item.strategy === "merge") {
|
|
2853
|
+
const merged = mergeFile(item.hubTargetPath, contentToWrite, existing.content, item.tool);
|
|
2854
|
+
writeHubFile(item.hubTargetPath, merged.content);
|
|
2855
|
+
result.items.push({
|
|
2856
|
+
tool: item.tool,
|
|
2857
|
+
hubTargetPath: item.hubTargetPath,
|
|
2858
|
+
status: "merged"
|
|
2859
|
+
});
|
|
2860
|
+
result.merged++;
|
|
2861
|
+
} else {
|
|
2862
|
+
result.items.push({
|
|
2863
|
+
tool: item.tool,
|
|
2864
|
+
hubTargetPath: item.hubTargetPath,
|
|
2865
|
+
status: "skipped"
|
|
2866
|
+
});
|
|
2867
|
+
result.skipped++;
|
|
2868
|
+
}
|
|
2869
|
+
} catch (err) {
|
|
2870
|
+
result.items.push({
|
|
2871
|
+
tool: item.tool,
|
|
2872
|
+
hubTargetPath: item.hubTargetPath,
|
|
2873
|
+
status: "error",
|
|
2874
|
+
message: err.message
|
|
2875
|
+
});
|
|
2876
|
+
result.errors++;
|
|
2877
|
+
result.success = false;
|
|
2878
|
+
}
|
|
2879
|
+
}
|
|
2880
|
+
return result;
|
|
2881
|
+
}
|
|
2882
|
+
/**
|
|
2883
|
+
* 判断工具在 hubConfig 中是否 enabled
|
|
2884
|
+
*/
|
|
2885
|
+
isToolEnabled(toolName) {
|
|
2886
|
+
const tools = this.hubConfig.tools;
|
|
2887
|
+
return tools?.[toolName]?.enabled ?? false;
|
|
2888
|
+
}
|
|
2889
|
+
};
|
|
2890
|
+
}
|
|
2891
|
+
});
|
|
2892
|
+
|
|
2893
|
+
// src/server/lib/hub-context.ts
|
|
2894
|
+
var HubContext, hubContext;
|
|
2895
|
+
var init_hub_context = __esm({
|
|
2896
|
+
"src/server/lib/hub-context.ts"() {
|
|
2897
|
+
"use strict";
|
|
2898
|
+
init_esm_shims();
|
|
2899
|
+
init_config();
|
|
2900
|
+
init_sync_engine();
|
|
2901
|
+
init_registry();
|
|
2902
|
+
HubContext = class {
|
|
2903
|
+
config = null;
|
|
2904
|
+
engine = null;
|
|
2905
|
+
adaptersRegistered = false;
|
|
2906
|
+
/** 获取当前 Hub 配置(懒加载) */
|
|
2907
|
+
getConfig() {
|
|
2908
|
+
if (!this.config) {
|
|
2909
|
+
this.config = loadHubConfig();
|
|
2910
|
+
}
|
|
2911
|
+
this.ensureAdaptersRegistered();
|
|
2912
|
+
return this.config;
|
|
2913
|
+
}
|
|
2914
|
+
/** 确保内置适配器已注册(幂等) */
|
|
2915
|
+
ensureAdaptersRegistered() {
|
|
2916
|
+
if (!this.adaptersRegistered) {
|
|
2917
|
+
registerBuiltinAdapters();
|
|
2918
|
+
this.adaptersRegistered = true;
|
|
2919
|
+
}
|
|
2920
|
+
}
|
|
2921
|
+
/** 获取 SyncEngine 实例(懒加载) */
|
|
2922
|
+
getEngine() {
|
|
2923
|
+
if (!this.engine) {
|
|
2924
|
+
this.engine = new SyncEngine(this.getConfig(), this.getAllAdapters());
|
|
2925
|
+
}
|
|
2926
|
+
return this.engine;
|
|
2927
|
+
}
|
|
2928
|
+
/** 获取所有已注册的适配器 */
|
|
2929
|
+
getAllAdapters() {
|
|
2930
|
+
this.ensureAdaptersRegistered();
|
|
2931
|
+
return getAllAdapters();
|
|
2932
|
+
}
|
|
2933
|
+
/** 按 name 查找适配器 */
|
|
2934
|
+
getAdapter(name) {
|
|
2935
|
+
return getAdapter(name);
|
|
2936
|
+
}
|
|
2937
|
+
/** 让配置/引擎缓存失效,下次访问时重新加载 */
|
|
2938
|
+
reloadConfig() {
|
|
2939
|
+
this.config = null;
|
|
2940
|
+
this.engine = null;
|
|
2941
|
+
}
|
|
2942
|
+
/** 保存新配置并刷新缓存 */
|
|
2943
|
+
saveConfig(config) {
|
|
2944
|
+
saveHubConfig(config);
|
|
2945
|
+
this.config = config;
|
|
2946
|
+
this.engine = null;
|
|
2947
|
+
}
|
|
2948
|
+
};
|
|
2949
|
+
hubContext = new HubContext();
|
|
2950
|
+
}
|
|
2951
|
+
});
|
|
2952
|
+
|
|
2953
|
+
// src/core/overview.ts
|
|
2954
|
+
async function getOverview() {
|
|
2955
|
+
const health = checkHubHealth();
|
|
2956
|
+
const assetStats = {
|
|
2957
|
+
identity: 0,
|
|
2958
|
+
skill: 0,
|
|
2959
|
+
rule: 0,
|
|
2960
|
+
mcp: 0
|
|
2961
|
+
};
|
|
2962
|
+
for (const cat of HUB_CATEGORIES) {
|
|
2963
|
+
const frontendCat = CATEGORY_MAP[cat];
|
|
2964
|
+
if (frontendCat) {
|
|
2965
|
+
assetStats[frontendCat] = health.fileCountByCategory[cat] ?? 0;
|
|
2966
|
+
}
|
|
2967
|
+
}
|
|
2968
|
+
const totalAssets = Object.values(assetStats).reduce((s, n) => s + n, 0);
|
|
2969
|
+
const hubInitialized = health.hubExists && health.hubTomlExists && totalAssets > 0;
|
|
2970
|
+
const healthScore = computeHealthScore(totalAssets);
|
|
2971
|
+
const connections = await getConnections();
|
|
2972
|
+
const recentActivities = getRecentActivities();
|
|
2973
|
+
return {
|
|
2974
|
+
assetStats,
|
|
2975
|
+
connections,
|
|
2976
|
+
recentActivities,
|
|
2977
|
+
hubInitialized,
|
|
2978
|
+
healthScore
|
|
2979
|
+
};
|
|
2980
|
+
}
|
|
2981
|
+
function computeHealthScore(totalAssets) {
|
|
2982
|
+
if (totalAssets === 0) return 0;
|
|
2983
|
+
return Math.min(100, 60 + totalAssets * 4);
|
|
2984
|
+
}
|
|
2985
|
+
async function getConnections() {
|
|
2986
|
+
const adapters = hubContext.getAllAdapters();
|
|
2987
|
+
const config = hubContext.getConfig();
|
|
2988
|
+
const results = [];
|
|
2989
|
+
for (const adapter of adapters) {
|
|
2990
|
+
try {
|
|
2991
|
+
const status = await adapter.detect();
|
|
2992
|
+
const toolConfig = config.tools?.[adapter.name];
|
|
2993
|
+
const enabled = toolConfig?.enabled ?? false;
|
|
2994
|
+
if (!status.installed) {
|
|
2995
|
+
results.push({
|
|
2996
|
+
toolId: adapter.name,
|
|
2997
|
+
toolName: adapter.displayName,
|
|
2998
|
+
status: "not_installed",
|
|
2999
|
+
assetCount: 0,
|
|
3000
|
+
pendingCount: 0
|
|
3001
|
+
});
|
|
3002
|
+
continue;
|
|
3003
|
+
}
|
|
3004
|
+
if (!enabled) {
|
|
3005
|
+
results.push({
|
|
3006
|
+
toolId: adapter.name,
|
|
3007
|
+
toolName: adapter.displayName,
|
|
3008
|
+
status: "not_connected",
|
|
3009
|
+
assetCount: 0,
|
|
3010
|
+
pendingCount: 0
|
|
3011
|
+
});
|
|
3012
|
+
continue;
|
|
3013
|
+
}
|
|
3014
|
+
results.push({
|
|
3015
|
+
toolId: adapter.name,
|
|
3016
|
+
toolName: adapter.displayName,
|
|
3017
|
+
status: "synced",
|
|
3018
|
+
assetCount: 0,
|
|
3019
|
+
pendingCount: 0
|
|
3020
|
+
});
|
|
3021
|
+
} catch {
|
|
3022
|
+
results.push({
|
|
3023
|
+
toolId: adapter.name,
|
|
3024
|
+
toolName: adapter.displayName,
|
|
3025
|
+
status: "not_installed",
|
|
3026
|
+
assetCount: 0,
|
|
3027
|
+
pendingCount: 0
|
|
3028
|
+
});
|
|
3029
|
+
}
|
|
3030
|
+
}
|
|
3031
|
+
const statusOrder = { synced: 0, pending: 1, not_connected: 2, not_installed: 3 };
|
|
3032
|
+
results.sort((a, b) => statusOrder[a.status] - statusOrder[b.status]);
|
|
3033
|
+
return results;
|
|
3034
|
+
}
|
|
3035
|
+
function getRecentActivities() {
|
|
3036
|
+
return [];
|
|
3037
|
+
}
|
|
3038
|
+
var CATEGORY_MAP;
|
|
3039
|
+
var init_overview = __esm({
|
|
3040
|
+
"src/core/overview.ts"() {
|
|
3041
|
+
"use strict";
|
|
3042
|
+
init_esm_shims();
|
|
3043
|
+
init_hub_files();
|
|
3044
|
+
init_hub_context();
|
|
3045
|
+
CATEGORY_MAP = {
|
|
3046
|
+
identity: "identity",
|
|
3047
|
+
skills: "skill",
|
|
3048
|
+
rules: "rule",
|
|
3049
|
+
mcp: "mcp"
|
|
3050
|
+
};
|
|
3051
|
+
}
|
|
3052
|
+
});
|
|
3053
|
+
|
|
3054
|
+
// src/server/routes/hub.ts
|
|
3055
|
+
import { Hono } from "hono";
|
|
3056
|
+
var hubRoutes;
|
|
3057
|
+
var init_hub = __esm({
|
|
3058
|
+
"src/server/routes/hub.ts"() {
|
|
3059
|
+
"use strict";
|
|
3060
|
+
init_esm_shims();
|
|
3061
|
+
init_config();
|
|
3062
|
+
init_hub_context();
|
|
3063
|
+
init_hub_files();
|
|
3064
|
+
init_overview();
|
|
3065
|
+
hubRoutes = new Hono();
|
|
3066
|
+
hubRoutes.get("/config", (c) => {
|
|
3067
|
+
return c.json(hubContext.getConfig());
|
|
3068
|
+
});
|
|
3069
|
+
hubRoutes.put("/config", async (c) => {
|
|
3070
|
+
const body = await c.req.json();
|
|
3071
|
+
const config = HubConfigSchema.parse(body);
|
|
3072
|
+
hubContext.saveConfig(config);
|
|
3073
|
+
return c.json({ success: true });
|
|
3074
|
+
});
|
|
3075
|
+
hubRoutes.get("/health", (c) => {
|
|
3076
|
+
return c.json(checkHubHealth());
|
|
3077
|
+
});
|
|
3078
|
+
hubRoutes.get("/overview", async (c) => {
|
|
3079
|
+
try {
|
|
3080
|
+
const data = await getOverview();
|
|
3081
|
+
return c.json(data);
|
|
3082
|
+
} catch (err) {
|
|
3083
|
+
return c.json(
|
|
3084
|
+
{ error: err instanceof Error ? err.message : String(err) },
|
|
3085
|
+
500
|
|
3086
|
+
);
|
|
3087
|
+
}
|
|
3088
|
+
});
|
|
3089
|
+
}
|
|
3090
|
+
});
|
|
3091
|
+
|
|
3092
|
+
// src/server/routes/tools.ts
|
|
3093
|
+
import { Hono as Hono2 } from "hono";
|
|
3094
|
+
var toolsRoutes;
|
|
3095
|
+
var init_tools = __esm({
|
|
3096
|
+
"src/server/routes/tools.ts"() {
|
|
3097
|
+
"use strict";
|
|
3098
|
+
init_esm_shims();
|
|
3099
|
+
init_hub_context();
|
|
3100
|
+
toolsRoutes = new Hono2();
|
|
3101
|
+
toolsRoutes.get("/", async (c) => {
|
|
3102
|
+
const config = hubContext.getConfig();
|
|
3103
|
+
const adapters = hubContext.getAllAdapters();
|
|
3104
|
+
const statuses = await Promise.all(adapters.map((adapter) => adapter.detect()));
|
|
3105
|
+
const tools = adapters.map((adapter, i) => {
|
|
3106
|
+
const toolConfig = config.tools[adapter.name];
|
|
3107
|
+
return {
|
|
3108
|
+
...statuses[i],
|
|
3109
|
+
enabled: toolConfig?.enabled ?? false
|
|
3110
|
+
};
|
|
3111
|
+
});
|
|
3112
|
+
return c.json({ tools });
|
|
3113
|
+
});
|
|
3114
|
+
toolsRoutes.get("/:name", async (c) => {
|
|
3115
|
+
const name = c.req.param("name");
|
|
3116
|
+
const adapter = hubContext.getAdapter(name);
|
|
3117
|
+
if (!adapter) {
|
|
3118
|
+
return c.json({ error: `\u672A\u627E\u5230\u5DE5\u5177\u9002\u914D\u5668\uFF1A${name}` }, 404);
|
|
3119
|
+
}
|
|
3120
|
+
const status = await adapter.detect();
|
|
3121
|
+
const config = hubContext.getConfig();
|
|
3122
|
+
const toolConfig = config.tools[name];
|
|
3123
|
+
return c.json({
|
|
3124
|
+
...status,
|
|
3125
|
+
enabled: toolConfig?.enabled ?? false
|
|
3126
|
+
});
|
|
3127
|
+
});
|
|
3128
|
+
toolsRoutes.post("/:name/toggle", async (c) => {
|
|
3129
|
+
const name = c.req.param("name");
|
|
3130
|
+
const body = await c.req.json().catch(() => ({}));
|
|
3131
|
+
const enabled = body.enabled;
|
|
3132
|
+
const config = hubContext.getConfig();
|
|
3133
|
+
const tools = config.tools;
|
|
3134
|
+
if (!tools[name]) {
|
|
3135
|
+
return c.json({ error: `\u672A\u627E\u5230\u5DE5\u5177\u914D\u7F6E\uFF1A${name}` }, 404);
|
|
3136
|
+
}
|
|
3137
|
+
const nextEnabled = typeof enabled === "boolean" ? enabled : !tools[name].enabled;
|
|
3138
|
+
tools[name].enabled = nextEnabled;
|
|
3139
|
+
hubContext.saveConfig(config);
|
|
3140
|
+
return c.json({ success: true, enabled: nextEnabled });
|
|
3141
|
+
});
|
|
3142
|
+
toolsRoutes.post("/:name/detect", async (c) => {
|
|
3143
|
+
const name = c.req.param("name");
|
|
3144
|
+
const adapter = hubContext.getAdapter(name);
|
|
3145
|
+
if (!adapter) {
|
|
3146
|
+
return c.json({ error: `\u672A\u627E\u5230\u5DE5\u5177\u9002\u914D\u5668\uFF1A${name}` }, 404);
|
|
3147
|
+
}
|
|
3148
|
+
const status = await adapter.detect();
|
|
3149
|
+
return c.json(status);
|
|
3150
|
+
});
|
|
3151
|
+
toolsRoutes.get("/:name/detail", async (c) => {
|
|
3152
|
+
const name = c.req.param("name");
|
|
3153
|
+
const adapter = hubContext.getAdapter(name);
|
|
3154
|
+
if (!adapter) {
|
|
3155
|
+
return c.json({ error: `\u672A\u627E\u5230\u5DE5\u5177\u9002\u914D\u5668\uFF1A${name}` }, 404);
|
|
3156
|
+
}
|
|
3157
|
+
const status = await adapter.detect();
|
|
3158
|
+
const config = hubContext.getConfig();
|
|
3159
|
+
const toolConfig = config.tools[name];
|
|
3160
|
+
const tool = {
|
|
3161
|
+
...status,
|
|
3162
|
+
enabled: toolConfig?.enabled ?? false
|
|
3163
|
+
};
|
|
3164
|
+
let discovered;
|
|
3165
|
+
if (status.installed) {
|
|
3166
|
+
try {
|
|
3167
|
+
const items = await adapter.scan();
|
|
3168
|
+
discovered = {
|
|
3169
|
+
identities: items.filter((i) => i.category === "identity").map((f) => ({ path: f.absolutePath, size: f.size })),
|
|
3170
|
+
rules: items.filter((i) => i.category === "rule").map((f) => ({ path: f.absolutePath, size: f.size })),
|
|
3171
|
+
skills: items.filter((i) => i.category === "skill").map((f) => ({ path: f.absolutePath, size: f.size })),
|
|
3172
|
+
mcps: items.filter((i) => i.category === "mcp").map((f) => ({ path: f.absolutePath, size: f.size }))
|
|
3173
|
+
};
|
|
3174
|
+
} catch {
|
|
3175
|
+
discovered = void 0;
|
|
3176
|
+
}
|
|
3177
|
+
}
|
|
3178
|
+
return c.json({ tool, discovered });
|
|
3179
|
+
});
|
|
3180
|
+
}
|
|
3181
|
+
});
|
|
3182
|
+
|
|
3183
|
+
// src/server/routes/sync.ts
|
|
3184
|
+
import { Hono as Hono3 } from "hono";
|
|
3185
|
+
function parseTools(query) {
|
|
3186
|
+
const raw = query.tool;
|
|
3187
|
+
const list = Array.isArray(raw) ? raw : raw ? [raw] : [];
|
|
3188
|
+
const toolsRaw = query.tools;
|
|
3189
|
+
const fromCsv = (Array.isArray(toolsRaw) ? toolsRaw.join(",") : toolsRaw ?? "").split(",").map((t) => t.trim()).filter(Boolean);
|
|
3190
|
+
const all = [...list, ...fromCsv];
|
|
3191
|
+
if (all.length === 0) return {};
|
|
3192
|
+
if (all.length === 1) return { tool: all[0] };
|
|
3193
|
+
return { tools: all };
|
|
3194
|
+
}
|
|
3195
|
+
var syncRoutes, syncHistory;
|
|
3196
|
+
var init_sync = __esm({
|
|
3197
|
+
"src/server/routes/sync.ts"() {
|
|
3198
|
+
"use strict";
|
|
3199
|
+
init_esm_shims();
|
|
3200
|
+
init_hub_context();
|
|
3201
|
+
syncRoutes = new Hono3();
|
|
3202
|
+
syncHistory = [];
|
|
3203
|
+
syncRoutes.get("/plan", async (c) => {
|
|
3204
|
+
const filter = parseTools(c.req.query());
|
|
3205
|
+
const engine = hubContext.getEngine();
|
|
3206
|
+
const plans = await engine.plan(filter);
|
|
3207
|
+
return c.json({ plans });
|
|
3208
|
+
});
|
|
3209
|
+
syncRoutes.post("/run", async (c) => {
|
|
3210
|
+
const filter = parseTools(c.req.query());
|
|
3211
|
+
const dryRun = c.req.query("dryRun") === "true";
|
|
3212
|
+
const engine = hubContext.getEngine();
|
|
3213
|
+
const results = await engine.run({ ...filter, dryRun });
|
|
3214
|
+
syncHistory.unshift({
|
|
3215
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
3216
|
+
tool: filter.tool,
|
|
3217
|
+
dryRun,
|
|
3218
|
+
results
|
|
3219
|
+
});
|
|
3220
|
+
if (syncHistory.length > 20) syncHistory.pop();
|
|
3221
|
+
return c.json({ results });
|
|
3222
|
+
});
|
|
3223
|
+
syncRoutes.post("/reverse-import", async (c) => {
|
|
3224
|
+
const filter = parseTools(c.req.query());
|
|
3225
|
+
const engine = hubContext.getEngine();
|
|
3226
|
+
const results = await engine.reverseImport(filter);
|
|
3227
|
+
return c.json({ results });
|
|
3228
|
+
});
|
|
3229
|
+
syncRoutes.get("/history", (c) => {
|
|
3230
|
+
return c.json({ history: syncHistory });
|
|
3231
|
+
});
|
|
3232
|
+
syncRoutes.get("/scan", async (c) => {
|
|
3233
|
+
const engine = hubContext.getEngine();
|
|
3234
|
+
const inventories = await engine.scanAll();
|
|
3235
|
+
return c.json({ inventories });
|
|
3236
|
+
});
|
|
3237
|
+
syncRoutes.get("/diff", async (c) => {
|
|
3238
|
+
const sourcePath = c.req.query("sourcePath");
|
|
3239
|
+
const hubTargetPath = c.req.query("hubTargetPath");
|
|
3240
|
+
if (!sourcePath || !hubTargetPath) {
|
|
3241
|
+
return c.json({ error: "\u7F3A\u5C11 sourcePath \u6216 hubTargetPath \u53C2\u6570" }, 400);
|
|
3242
|
+
}
|
|
3243
|
+
const { existsSync: existsSync15, readFileSync: readFileSync9 } = await import("fs");
|
|
3244
|
+
const { readHubFile: readHubFile2 } = await Promise.resolve().then(() => (init_hub_files(), hub_files_exports));
|
|
3245
|
+
const { homedir: homedir4 } = await import("os");
|
|
3246
|
+
const { resolve: pathResolve } = await import("path");
|
|
3247
|
+
const home = pathResolve(homedir4());
|
|
3248
|
+
const normalizedSource = pathResolve(sourcePath);
|
|
3249
|
+
if (!normalizedSource.startsWith(home)) {
|
|
3250
|
+
return c.json({ error: "sourcePath \u4E0D\u5728\u7528\u6237\u76EE\u5F55\u8303\u56F4\u5185" }, 403);
|
|
3251
|
+
}
|
|
3252
|
+
if (!existsSync15(normalizedSource)) {
|
|
3253
|
+
return c.json({ error: "\u6E90\u6587\u4EF6\u4E0D\u5B58\u5728" }, 404);
|
|
3254
|
+
}
|
|
3255
|
+
let sourceContent;
|
|
3256
|
+
try {
|
|
3257
|
+
sourceContent = readFileSync9(normalizedSource, "utf-8");
|
|
3258
|
+
} catch {
|
|
3259
|
+
return c.json({ error: "\u65E0\u6CD5\u8BFB\u53D6\u6E90\u6587\u4EF6" }, 500);
|
|
3260
|
+
}
|
|
3261
|
+
let hubContent = null;
|
|
3262
|
+
const hubFile = readHubFile2(hubTargetPath);
|
|
3263
|
+
if (hubFile) {
|
|
3264
|
+
hubContent = hubFile.content;
|
|
3265
|
+
}
|
|
3266
|
+
return c.json({
|
|
3267
|
+
sourceContent,
|
|
3268
|
+
hubContent,
|
|
3269
|
+
sourcePath,
|
|
3270
|
+
hubTargetPath
|
|
3271
|
+
});
|
|
3272
|
+
});
|
|
3273
|
+
syncRoutes.post("/execute-import", async (c) => {
|
|
3274
|
+
const body = await c.req.json();
|
|
3275
|
+
if (!body.items || !Array.isArray(body.items)) {
|
|
3276
|
+
return c.json({ error: "\u7F3A\u5C11 items \u5B57\u6BB5\u6216\u683C\u5F0F\u9519\u8BEF" }, 400);
|
|
3277
|
+
}
|
|
3278
|
+
const engine = hubContext.getEngine();
|
|
3279
|
+
const result = await engine.executeImport(body);
|
|
3280
|
+
return c.json(result);
|
|
3281
|
+
});
|
|
3282
|
+
}
|
|
3283
|
+
});
|
|
3284
|
+
|
|
3285
|
+
// src/server/routes/files.ts
|
|
3286
|
+
import { Hono as Hono4 } from "hono";
|
|
3287
|
+
var filesRoutes;
|
|
3288
|
+
var init_files = __esm({
|
|
3289
|
+
"src/server/routes/files.ts"() {
|
|
3290
|
+
"use strict";
|
|
3291
|
+
init_esm_shims();
|
|
3292
|
+
init_hub_files();
|
|
3293
|
+
filesRoutes = new Hono4();
|
|
3294
|
+
filesRoutes.get("/", (c) => {
|
|
3295
|
+
const category = c.req.query("category");
|
|
3296
|
+
if (category) {
|
|
3297
|
+
if (!HUB_CATEGORIES.includes(category)) {
|
|
3298
|
+
return c.json(
|
|
3299
|
+
{ error: `\u65E0\u6548\u7684\u7C7B\u522B\uFF1A${category}\uFF0C\u652F\u6301\uFF1A${HUB_CATEGORIES.join(", ")}` },
|
|
3300
|
+
400
|
|
3301
|
+
);
|
|
3302
|
+
}
|
|
3303
|
+
return c.json({ files: listHubFiles(category) });
|
|
3304
|
+
}
|
|
3305
|
+
const allFiles = HUB_CATEGORIES.flatMap((cat) => listHubFiles(cat));
|
|
3306
|
+
return c.json({ files: allFiles });
|
|
3307
|
+
});
|
|
3308
|
+
filesRoutes.get("/*", (c) => {
|
|
3309
|
+
const path2 = c.req.path.replace("/api/files/", "");
|
|
3310
|
+
if (!path2) {
|
|
3311
|
+
return c.json({ error: "\u7F3A\u5C11\u6587\u4EF6\u8DEF\u5F84" }, 400);
|
|
3312
|
+
}
|
|
3313
|
+
const file = readHubFile(path2);
|
|
3314
|
+
if (!file) {
|
|
3315
|
+
return c.json({ error: `\u6587\u4EF6\u4E0D\u5B58\u5728\uFF1A${path2}` }, 404);
|
|
3316
|
+
}
|
|
3317
|
+
return c.json({ path: path2, ...file });
|
|
3318
|
+
});
|
|
3319
|
+
filesRoutes.put("/*", async (c) => {
|
|
3320
|
+
const path2 = c.req.path.replace("/api/files/", "");
|
|
3321
|
+
if (!path2) {
|
|
3322
|
+
return c.json({ error: "\u7F3A\u5C11\u6587\u4EF6\u8DEF\u5F84" }, 400);
|
|
3323
|
+
}
|
|
3324
|
+
const body = await c.req.json();
|
|
3325
|
+
if (typeof body.content !== "string") {
|
|
3326
|
+
return c.json({ error: "\u8BF7\u6C42\u4F53\u5FC5\u987B\u5305\u542B content \u5B57\u6BB5" }, 400);
|
|
3327
|
+
}
|
|
3328
|
+
writeHubFile(path2, body.content);
|
|
3329
|
+
return c.json({ success: true, path: path2 });
|
|
3330
|
+
});
|
|
3331
|
+
filesRoutes.post("/", async (c) => {
|
|
3332
|
+
const body = await c.req.json();
|
|
3333
|
+
if (typeof body.path !== "string" || typeof body.content !== "string") {
|
|
3334
|
+
return c.json({ error: "\u8BF7\u6C42\u4F53\u5FC5\u987B\u5305\u542B path \u548C content \u5B57\u6BB5" }, 400);
|
|
3335
|
+
}
|
|
3336
|
+
try {
|
|
3337
|
+
createHubFile(body.path, body.content);
|
|
3338
|
+
return c.json({ success: true, path: body.path });
|
|
3339
|
+
} catch (err) {
|
|
3340
|
+
return c.json(
|
|
3341
|
+
{ error: err instanceof Error ? err.message : String(err) },
|
|
3342
|
+
400
|
|
3343
|
+
);
|
|
3344
|
+
}
|
|
3345
|
+
});
|
|
3346
|
+
filesRoutes.delete("/*", (c) => {
|
|
3347
|
+
const path2 = c.req.path.replace("/api/files/", "");
|
|
3348
|
+
if (!path2) {
|
|
3349
|
+
return c.json({ error: "\u7F3A\u5C11\u6587\u4EF6\u8DEF\u5F84" }, 400);
|
|
3350
|
+
}
|
|
3351
|
+
try {
|
|
3352
|
+
deleteHubFile(path2);
|
|
3353
|
+
return c.json({ success: true });
|
|
3354
|
+
} catch (err) {
|
|
3355
|
+
return c.json(
|
|
3356
|
+
{ error: err instanceof Error ? err.message : String(err) },
|
|
3357
|
+
400
|
|
3358
|
+
);
|
|
3359
|
+
}
|
|
3360
|
+
});
|
|
3361
|
+
}
|
|
3362
|
+
});
|
|
3363
|
+
|
|
3364
|
+
// src/core/assets.ts
|
|
3365
|
+
async function listAssets(params = {}) {
|
|
3366
|
+
const { category, search } = params;
|
|
3367
|
+
const categoriesToScan = category ? (() => {
|
|
3368
|
+
const reverse = {
|
|
3369
|
+
identity: "identity",
|
|
3370
|
+
skill: "skills",
|
|
3371
|
+
rule: "rules",
|
|
3372
|
+
mcp: "mcp"
|
|
3373
|
+
};
|
|
3374
|
+
return [reverse[category]];
|
|
3375
|
+
})() : HUB_CATEGORIES.filter((c) => c in DIR_TO_CATEGORY);
|
|
3376
|
+
const allFiles = [];
|
|
3377
|
+
for (const hubCat of categoriesToScan) {
|
|
3378
|
+
const frontendCat = DIR_TO_CATEGORY[hubCat];
|
|
3379
|
+
if (!frontendCat) continue;
|
|
3380
|
+
const files = listHubFiles(hubCat);
|
|
3381
|
+
for (const f of files) {
|
|
3382
|
+
allFiles.push({ file: f, category: frontendCat });
|
|
3383
|
+
}
|
|
3384
|
+
}
|
|
3385
|
+
let assets = allFiles.map(({ file, category: category2 }) => hubFileToAsset(file, category2));
|
|
3386
|
+
if (search && search.trim()) {
|
|
3387
|
+
const q = search.trim().toLowerCase();
|
|
3388
|
+
assets = assets.filter((a) => a.name.toLowerCase().includes(q));
|
|
3389
|
+
}
|
|
3390
|
+
assets.sort((a, b) => new Date(b.lastModifiedAt).getTime() - new Date(a.lastModifiedAt).getTime());
|
|
3391
|
+
return { items: assets, total: assets.length };
|
|
3392
|
+
}
|
|
3393
|
+
async function getAsset(hubPath2) {
|
|
3394
|
+
const fileData = readHubFile(hubPath2);
|
|
3395
|
+
if (!fileData) return null;
|
|
3396
|
+
const category = inferCategoryFromPath(hubPath2);
|
|
3397
|
+
if (!category) return null;
|
|
3398
|
+
const file = {
|
|
3399
|
+
relativePath: hubPath2,
|
|
3400
|
+
absolutePath: "",
|
|
3401
|
+
size: fileData.size,
|
|
3402
|
+
modified: fileData.modified,
|
|
3403
|
+
isDirectory: false
|
|
3404
|
+
};
|
|
3405
|
+
const asset = hubFileToAsset(file, category);
|
|
3406
|
+
return {
|
|
3407
|
+
...asset,
|
|
3408
|
+
content: fileData.content
|
|
3409
|
+
};
|
|
3410
|
+
}
|
|
3411
|
+
function inferCategoryFromPath(hubPath2) {
|
|
3412
|
+
const firstSegment = hubPath2.split("/")[0];
|
|
3413
|
+
return DIR_TO_CATEGORY[firstSegment] ?? null;
|
|
3414
|
+
}
|
|
3415
|
+
function hubFileToAsset(file, category) {
|
|
3416
|
+
const name = extractName(file.relativePath, category);
|
|
3417
|
+
return {
|
|
3418
|
+
id: file.relativePath,
|
|
3419
|
+
name,
|
|
3420
|
+
category,
|
|
3421
|
+
hubPath: file.relativePath,
|
|
3422
|
+
source: "manual",
|
|
3423
|
+
// Phase 1 默认,Phase 2 从导入记录中读取真实来源
|
|
3424
|
+
lastModifiedAt: file.modified,
|
|
3425
|
+
size: file.size,
|
|
3426
|
+
distributions: []
|
|
3427
|
+
// Phase 1 空数组,Phase 2 连接页实现时填充
|
|
3428
|
+
};
|
|
3429
|
+
}
|
|
3430
|
+
function extractName(relativePath, _category) {
|
|
3431
|
+
const basename3 = relativePath.split("/").pop() ?? relativePath;
|
|
3432
|
+
const dotIndex = basename3.lastIndexOf(".");
|
|
3433
|
+
return dotIndex > 0 ? basename3.substring(0, dotIndex) : basename3;
|
|
3434
|
+
}
|
|
3435
|
+
var DIR_TO_CATEGORY;
|
|
3436
|
+
var init_assets = __esm({
|
|
3437
|
+
"src/core/assets.ts"() {
|
|
3438
|
+
"use strict";
|
|
3439
|
+
init_esm_shims();
|
|
3440
|
+
init_hub_files();
|
|
3441
|
+
DIR_TO_CATEGORY = {
|
|
3442
|
+
identity: "identity",
|
|
3443
|
+
skills: "skill",
|
|
3444
|
+
rules: "rule",
|
|
3445
|
+
mcp: "mcp"
|
|
3446
|
+
};
|
|
3447
|
+
}
|
|
3448
|
+
});
|
|
3449
|
+
|
|
3450
|
+
// src/server/routes/assets.ts
|
|
3451
|
+
import { Hono as Hono5 } from "hono";
|
|
3452
|
+
var assetsRoutes;
|
|
3453
|
+
var init_assets2 = __esm({
|
|
3454
|
+
"src/server/routes/assets.ts"() {
|
|
3455
|
+
"use strict";
|
|
3456
|
+
init_esm_shims();
|
|
3457
|
+
init_assets();
|
|
3458
|
+
init_hub_files();
|
|
3459
|
+
assetsRoutes = new Hono5();
|
|
3460
|
+
assetsRoutes.get("/", async (c) => {
|
|
3461
|
+
const category = c.req.query("category");
|
|
3462
|
+
const search = c.req.query("search");
|
|
3463
|
+
const validCategories = ["identity", "skill", "rule", "mcp"];
|
|
3464
|
+
if (category && !validCategories.includes(category)) {
|
|
3465
|
+
return c.json({ error: `\u65E0\u6548\u7C7B\u522B\uFF1A${category}\uFF0C\u652F\u6301\uFF1A${validCategories.join(", ")}` }, 400);
|
|
3466
|
+
}
|
|
3467
|
+
const result = await listAssets({ category, search });
|
|
3468
|
+
return c.json(result);
|
|
3469
|
+
});
|
|
3470
|
+
assetsRoutes.get("/:id{.+}", async (c) => {
|
|
3471
|
+
const id = c.req.param("id");
|
|
3472
|
+
if (!id) return c.json({ error: "\u7F3A\u5C11\u8D44\u4EA7ID" }, 400);
|
|
3473
|
+
const hubPath2 = decodeURIComponent(id);
|
|
3474
|
+
const asset = await getAsset(hubPath2);
|
|
3475
|
+
if (!asset) {
|
|
3476
|
+
return c.json({ error: `\u8D44\u4EA7\u4E0D\u5B58\u5728\uFF1A${hubPath2}` }, 404);
|
|
3477
|
+
}
|
|
3478
|
+
return c.json(asset);
|
|
3479
|
+
});
|
|
3480
|
+
assetsRoutes.post("/", async (c) => {
|
|
3481
|
+
const body = await c.req.json();
|
|
3482
|
+
if (!body.name || !body.category || typeof body.content !== "string") {
|
|
3483
|
+
return c.json({ error: "\u5FC5\u987B\u63D0\u4F9B name\u3001category\u3001content \u5B57\u6BB5" }, 400);
|
|
3484
|
+
}
|
|
3485
|
+
const validCategories = ["identity", "skill", "rule", "mcp"];
|
|
3486
|
+
if (!validCategories.includes(body.category)) {
|
|
3487
|
+
return c.json({ error: `\u65E0\u6548\u7C7B\u522B\uFF1A${body.category}` }, 400);
|
|
3488
|
+
}
|
|
3489
|
+
const dirMap = {
|
|
3490
|
+
identity: "identity",
|
|
3491
|
+
skill: "skills",
|
|
3492
|
+
rule: "rules",
|
|
3493
|
+
mcp: "mcp"
|
|
3494
|
+
};
|
|
3495
|
+
const ext = body.category === "mcp" ? ".json" : ".md";
|
|
3496
|
+
const hubPath2 = `${dirMap[body.category]}/${body.name}${ext}`;
|
|
3497
|
+
try {
|
|
3498
|
+
createHubFile(hubPath2, body.content);
|
|
3499
|
+
const asset = await getAsset(hubPath2);
|
|
3500
|
+
return c.json({ asset }, 201);
|
|
3501
|
+
} catch (err) {
|
|
3502
|
+
return c.json({ error: err instanceof Error ? err.message : String(err) }, 400);
|
|
3503
|
+
}
|
|
3504
|
+
});
|
|
3505
|
+
assetsRoutes.put("/:id{.+}", async (c) => {
|
|
3506
|
+
const id = c.req.param("id");
|
|
3507
|
+
const hubPath2 = decodeURIComponent(id);
|
|
3508
|
+
const body = await c.req.json();
|
|
3509
|
+
if (typeof body.content !== "string") {
|
|
3510
|
+
return c.json({ error: "\u5FC5\u987B\u63D0\u4F9B content \u5B57\u6BB5" }, 400);
|
|
3511
|
+
}
|
|
3512
|
+
try {
|
|
3513
|
+
writeHubFile(hubPath2, body.content);
|
|
3514
|
+
const asset = await getAsset(hubPath2);
|
|
3515
|
+
return c.json({ asset });
|
|
3516
|
+
} catch (err) {
|
|
3517
|
+
return c.json({ error: err instanceof Error ? err.message : String(err) }, 400);
|
|
3518
|
+
}
|
|
3519
|
+
});
|
|
3520
|
+
}
|
|
3521
|
+
});
|
|
3522
|
+
|
|
3523
|
+
// src/server/middleware/error.ts
|
|
3524
|
+
import { ZodError } from "zod";
|
|
3525
|
+
async function errorHandler(c, next) {
|
|
3526
|
+
try {
|
|
3527
|
+
await next();
|
|
3528
|
+
} catch (err) {
|
|
3529
|
+
if (err instanceof ZodError) {
|
|
3530
|
+
return c.json(
|
|
3531
|
+
{
|
|
3532
|
+
error: "\u8BF7\u6C42\u53C2\u6570\u6821\u9A8C\u5931\u8D25",
|
|
3533
|
+
details: err.errors
|
|
3534
|
+
},
|
|
3535
|
+
400
|
|
3536
|
+
);
|
|
3537
|
+
}
|
|
3538
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
3539
|
+
return c.json({ error: message }, 500);
|
|
3540
|
+
}
|
|
3541
|
+
}
|
|
3542
|
+
var init_error = __esm({
|
|
3543
|
+
"src/server/middleware/error.ts"() {
|
|
3544
|
+
"use strict";
|
|
3545
|
+
init_esm_shims();
|
|
3546
|
+
}
|
|
3547
|
+
});
|
|
3548
|
+
|
|
3549
|
+
// src/server/index.ts
|
|
3550
|
+
import { Hono as Hono6 } from "hono";
|
|
3551
|
+
import { logger } from "hono/logger";
|
|
3552
|
+
import { cors } from "hono/cors";
|
|
3553
|
+
import { serve } from "@hono/node-server";
|
|
3554
|
+
import { serveStatic } from "@hono/node-server/serve-static";
|
|
3555
|
+
async function startServer(options = {}) {
|
|
3556
|
+
const { port = 17521, host = "127.0.0.1", openBrowser = true } = options;
|
|
3557
|
+
const app = new Hono6();
|
|
3558
|
+
app.use("*", logger());
|
|
3559
|
+
app.use("*", cors({ origin: [`http://localhost:${port}`, `http://${host}:${port}`] }));
|
|
3560
|
+
app.use("*", errorHandler);
|
|
3561
|
+
app.get("/api/health", (c) => c.json({ status: "ok", version: "0.1.0" }));
|
|
3562
|
+
app.route("/api/hub", hubRoutes);
|
|
3563
|
+
app.route("/api/tools", toolsRoutes);
|
|
3564
|
+
app.route("/api/sync", syncRoutes);
|
|
3565
|
+
app.route("/api/files", filesRoutes);
|
|
3566
|
+
app.route("/api/assets", assetsRoutes);
|
|
3567
|
+
app.use(
|
|
3568
|
+
"/*",
|
|
3569
|
+
serveStatic({
|
|
3570
|
+
root: "./web/dist",
|
|
3571
|
+
// SPA 回退:非静态资源路径(无文件扩展名)回退到 index.html
|
|
3572
|
+
rewriteRequestPath: (path2) => {
|
|
3573
|
+
if (path2.includes(".") && !path2.endsWith(".html")) {
|
|
3574
|
+
return path2;
|
|
3575
|
+
}
|
|
3576
|
+
return "/index.html";
|
|
3577
|
+
}
|
|
3578
|
+
})
|
|
3579
|
+
);
|
|
3580
|
+
await serve({ fetch: app.fetch, port, hostname: host });
|
|
3581
|
+
log.success(`AssetPlex Web UI \u8FD0\u884C\u4E2D: http://${host}:${port}`);
|
|
3582
|
+
log.info("\u6309 Ctrl+C \u505C\u6B62\u670D\u52A1");
|
|
3583
|
+
if (openBrowser) {
|
|
3584
|
+
try {
|
|
3585
|
+
const { default: open } = await import("open");
|
|
3586
|
+
await open(`http://${host}:${port}`);
|
|
3587
|
+
} catch {
|
|
3588
|
+
log.warn("\u81EA\u52A8\u6253\u5F00\u6D4F\u89C8\u5668\u5931\u8D25\uFF0C\u8BF7\u624B\u52A8\u8BBF\u95EE\u4E0A\u8FF0\u5730\u5740");
|
|
3589
|
+
}
|
|
3590
|
+
}
|
|
3591
|
+
return new Promise((resolve14) => {
|
|
3592
|
+
process.on("SIGINT", () => {
|
|
3593
|
+
log.info("Web UI \u5DF2\u505C\u6B62");
|
|
3594
|
+
resolve14();
|
|
3595
|
+
});
|
|
3596
|
+
process.on("SIGTERM", () => {
|
|
3597
|
+
log.info("Web UI \u5DF2\u505C\u6B62");
|
|
3598
|
+
resolve14();
|
|
3599
|
+
});
|
|
3600
|
+
});
|
|
3601
|
+
}
|
|
3602
|
+
var init_server = __esm({
|
|
3603
|
+
"src/server/index.ts"() {
|
|
3604
|
+
"use strict";
|
|
3605
|
+
init_esm_shims();
|
|
3606
|
+
init_logger();
|
|
3607
|
+
init_hub();
|
|
3608
|
+
init_tools();
|
|
3609
|
+
init_sync();
|
|
3610
|
+
init_files();
|
|
3611
|
+
init_assets2();
|
|
3612
|
+
init_error();
|
|
3613
|
+
}
|
|
3614
|
+
});
|
|
3615
|
+
|
|
3616
|
+
// src/cli/commands/ui.ts
|
|
3617
|
+
var ui_exports = {};
|
|
3618
|
+
__export(ui_exports, {
|
|
3619
|
+
uiCommand: () => uiCommand
|
|
3620
|
+
});
|
|
3621
|
+
async function uiCommand(opts) {
|
|
3622
|
+
log.info("\u542F\u52A8 AssetPlex Web UI...");
|
|
3623
|
+
await startServer({
|
|
3624
|
+
port: opts.port ?? 17521,
|
|
3625
|
+
host: opts.host ?? "127.0.0.1",
|
|
3626
|
+
openBrowser: !opts.noOpen
|
|
3627
|
+
});
|
|
3628
|
+
}
|
|
3629
|
+
var init_ui = __esm({
|
|
3630
|
+
"src/cli/commands/ui.ts"() {
|
|
3631
|
+
"use strict";
|
|
3632
|
+
init_esm_shims();
|
|
3633
|
+
init_server();
|
|
3634
|
+
init_logger();
|
|
3635
|
+
}
|
|
3636
|
+
});
|
|
3637
|
+
|
|
3638
|
+
// src/cli/index.ts
|
|
3639
|
+
init_esm_shims();
|
|
3640
|
+
init_logger();
|
|
3641
|
+
import { Command } from "commander";
|
|
3642
|
+
|
|
3643
|
+
// src/cli/commands/init.ts
|
|
3644
|
+
init_esm_shims();
|
|
3645
|
+
init_logger();
|
|
3646
|
+
init_paths();
|
|
3647
|
+
init_config();
|
|
3648
|
+
import { existsSync as existsSync2, mkdirSync as mkdirSync2, writeFileSync as writeFileSync2 } from "fs";
|
|
3649
|
+
import { resolve as resolve3 } from "path";
|
|
3650
|
+
|
|
3651
|
+
// src/cli/templates.ts
|
|
3652
|
+
init_esm_shims();
|
|
3653
|
+
var PROFILE_TEMPLATE = `# User Profile
|
|
3654
|
+
|
|
3655
|
+
> \u6B64\u6587\u4EF6\u7531\u7528\u6237\u624B\u5199\u3002AssetPlex \u4F1A\u81EA\u52A8\u540C\u6B65\u5230\u6240\u6709\u542F\u7528\u7684\u5DE5\u5177
|
|
3656
|
+
> (Claude Code \u7684 CLAUDE.md\u3001Codex \u7684 AGENTS.md\u3001TRAE CN \u7684 memory/user_profile.md \u7B49)
|
|
3657
|
+
|
|
3658
|
+
## \u6C9F\u901A\u504F\u597D
|
|
3659
|
+
- \u8BED\u8A00\uFF1A\u4E2D\u6587
|
|
3660
|
+
- \u98CE\u683C\uFF1A\u5B9E\u7528\u3001\u5E72\u8D27\u3001\u53EF\u6536\u85CF
|
|
3661
|
+
|
|
3662
|
+
## \u6280\u672F\u6808
|
|
3663
|
+
- \u4E3B\u8981\uFF1ATypeScript, React, Node.js
|
|
3664
|
+
- \u8F85\u52A9\uFF1APython, SQL
|
|
3665
|
+
|
|
3666
|
+
## \u80CC\u666F
|
|
3667
|
+
- (\u586B\u5199\u4F60\u7684\u804C\u4E1A\u80CC\u666F)
|
|
3668
|
+
- (\u586B\u5199\u4F60\u7684\u8BA4\u8BC1\u6216\u4E13\u4E1A\u8EAB\u4EFD)
|
|
3669
|
+
|
|
3670
|
+
## \u5DE5\u4F5C\u4E60\u60EF
|
|
3671
|
+
- \u64CD\u4F5C\u7CFB\u7EDF\uFF1A(Windows / macOS / Linux)
|
|
3672
|
+
- \u7F16\u8F91\u5668\uFF1A(Trae / VS Code / Cursor / \u5176\u4ED6)
|
|
3673
|
+
- \u7EC8\u7AEF\uFF1A(PowerShell / zsh / bash)
|
|
3674
|
+
`;
|
|
3675
|
+
var COMMUNICATION_STYLE_TEMPLATE = `# Communication Style
|
|
3676
|
+
|
|
3677
|
+
## \u8F93\u51FA\u683C\u5F0F
|
|
3678
|
+
- \u4F18\u5148\u4F7F\u7528 Markdown
|
|
3679
|
+
- \u4EE3\u7801\u5757\u5FC5\u987B\u6709\u8BED\u8A00\u6807\u7B7E
|
|
3680
|
+
- \u6587\u4EF6\u8DEF\u5F84\u7528\u53CD\u5F15\u53F7\u5305\u88F9\uFF1A\`src/index.ts\`
|
|
3681
|
+
|
|
3682
|
+
## \u98CE\u683C
|
|
3683
|
+
- \u76F4\u63A5\u3001\u7B80\u6D01\uFF0C\u4E0D\u7ED5\u5F2F
|
|
3684
|
+
- \u4E2D\u6587\u56DE\u7B54\uFF0C\u4EE3\u7801\u6CE8\u91CA\u4E5F\u7528\u4E2D\u6587
|
|
3685
|
+
- \u590D\u6742\u95EE\u9898\u5206\u6B65\u9AA4\u8BF4\u660E
|
|
3686
|
+
- \u4F18\u5148\u7ED9\u53EF\u6267\u884C\u65B9\u6848\uFF0C\u518D\u8865\u5145\u80CC\u666F
|
|
3687
|
+
|
|
3688
|
+
## \u7981\u6B62
|
|
3689
|
+
- \u4E0D\u8981\u7528 emoji\uFF08\u9664\u975E\u660E\u786E\u8981\u6C42\uFF09
|
|
3690
|
+
- \u4E0D\u8981\u5728\u6BCF\u6BB5\u8BDD\u5F00\u5934\u8BF4"\u597D\u7684"\u3001"\u660E\u767D\u4E86"
|
|
3691
|
+
- \u4E0D\u8981\u91CD\u590D\u7528\u6237\u7684\u95EE\u9898
|
|
3692
|
+
`;
|
|
3693
|
+
var TECH_STACK_TEMPLATE = `# Tech Stack
|
|
3694
|
+
|
|
3695
|
+
## \u4E3B\u8BED\u8A00
|
|
3696
|
+
- TypeScript (Node.js 18+)
|
|
3697
|
+
- Python 3.11+
|
|
3698
|
+
|
|
3699
|
+
## \u524D\u7AEF
|
|
3700
|
+
- React 18+
|
|
3701
|
+
- Vite
|
|
3702
|
+
- Tailwind CSS
|
|
3703
|
+
|
|
3704
|
+
## \u540E\u7AEF
|
|
3705
|
+
- Node.js + Express / Fastify
|
|
3706
|
+
- Python + FastAPI
|
|
3707
|
+
|
|
3708
|
+
## \u6570\u636E\u5E93
|
|
3709
|
+
- PostgreSQL
|
|
3710
|
+
- Redis
|
|
3711
|
+
|
|
3712
|
+
## DevOps
|
|
3713
|
+
- Docker
|
|
3714
|
+
- GitHub Actions
|
|
3715
|
+
|
|
3716
|
+
## AI / LLM
|
|
3717
|
+
- OpenAI API
|
|
3718
|
+
- Anthropic Claude API
|
|
3719
|
+
- Volcano Engine (\u706B\u5C71\u5F15\u64CE)
|
|
3720
|
+
`;
|
|
3721
|
+
var CODING_STYLE_TEMPLATE = `# Coding Style
|
|
3722
|
+
|
|
3723
|
+
## TypeScript
|
|
3724
|
+
- \u4E25\u683C\u6A21\u5F0F\uFF1A\`strict: true\`
|
|
3725
|
+
- \u4F18\u5148\u7528 \`const\` \u800C\u975E \`let\`
|
|
3726
|
+
- \u7981\u6B62 \`any\`\uFF0C\u5FC5\u8981\u65F6\u7528 \`unknown\` + \u7C7B\u578B\u5B88\u536B
|
|
3727
|
+
- \u7528 interface \u800C\u975E type \u63CF\u8FF0\u5BF9\u8C61\u5F62\u72B6
|
|
3728
|
+
- \u51FD\u6570\u4F18\u5148\u7528\u7BAD\u5934\u51FD\u6570
|
|
3729
|
+
|
|
3730
|
+
## \u547D\u540D\u7EA6\u5B9A
|
|
3731
|
+
- \u53D8\u91CF\uFF1AcamelCase
|
|
3732
|
+
- \u7C7B\u578B/\u63A5\u53E3\uFF1APascalCase
|
|
3733
|
+
- \u5E38\u91CF\uFF1AUPPER_SNAKE_CASE
|
|
3734
|
+
- \u6587\u4EF6\u540D\uFF1Akebab-case
|
|
3735
|
+
|
|
3736
|
+
## \u6CE8\u91CA
|
|
3737
|
+
- \u53EA\u5728"\u4E3A\u4EC0\u4E48"\u975E\u663E\u7136\u65F6\u5199\u6CE8\u91CA
|
|
3738
|
+
- \u4E0D\u5199"\u505A\u4EC0\u4E48"\u6CE8\u91CA\uFF08\u4EE3\u7801\u5E94\u81EA\u89E3\u91CA\uFF09
|
|
3739
|
+
- \u590D\u6742\u903B\u8F91\u7528\u5757\u6CE8\u91CA\uFF0C\u7B80\u5355\u6280\u5DE7\u7528\u884C\u6CE8\u91CA
|
|
3740
|
+
|
|
3741
|
+
## \u6D4B\u8BD5
|
|
3742
|
+
- \u6D4B\u8BD5\u6587\u4EF6\u4E0E\u6E90\u6587\u4EF6\u540C\u76EE\u5F55\uFF1A\`foo.ts\` \u2192 \`foo.test.ts\`
|
|
3743
|
+
- \u7528 Vitest
|
|
3744
|
+
- \u4E00\u4E2A\u6D4B\u8BD5\u53EA\u6D4B\u4E00\u4EF6\u4E8B
|
|
3745
|
+
`;
|
|
3746
|
+
var GIT_WORKFLOW_TEMPLATE = `# Git Workflow
|
|
3747
|
+
|
|
3748
|
+
## \u63D0\u4EA4\u89C4\u8303
|
|
3749
|
+
- \u7528 Conventional Commits: \`feat: ...\`, \`fix: ...\`, \`docs: ...\`, \`refactor: ...\`
|
|
3750
|
+
- \u63D0\u4EA4\u6D88\u606F\u7528\u82F1\u6587\uFF0C\u6B63\u6587\u53EF\u4E2D\u6587
|
|
3751
|
+
- \u5355\u6B21\u63D0\u4EA4\u4E0D\u8981\u8D85\u8FC7 200 \u884C diff
|
|
3752
|
+
|
|
3753
|
+
## \u5206\u652F
|
|
3754
|
+
- main / master\uFF1A\u53D7\u4FDD\u62A4\uFF0C\u4E0D\u76F4\u63A5 push
|
|
3755
|
+
- feature/<name>\uFF1A\u65B0\u529F\u80FD
|
|
3756
|
+
- fix/<name>\uFF1Abug \u4FEE\u590D
|
|
3757
|
+
- chore/<name>\uFF1A\u6742\u9879
|
|
3758
|
+
|
|
3759
|
+
## PR
|
|
3760
|
+
- \u6807\u9898\u7528\u82F1\u6587\uFF0C\u63CF\u8FF0\u53EF\u4E2D\u6587
|
|
3761
|
+
- \u5FC5\u987B\u5305\u542B "What" \u548C "Why"
|
|
3762
|
+
- \u4E0D\u8D85\u8FC7 500 \u884C diff\uFF08\u9664\u975E\u91CD\u6784\uFF09
|
|
3763
|
+
`;
|
|
3764
|
+
var ENV_TEMPLATE = `# Environment
|
|
3765
|
+
|
|
3766
|
+
## \u7CFB\u7EDF
|
|
3767
|
+
- OS: ${process.platform}
|
|
3768
|
+
- Shell: ${process.env.SHELL ?? process.env.COMSPEC ?? "unknown"}
|
|
3769
|
+
- Home: ${process.env.HOME ?? process.env.USERPROFILE ?? "unknown"}
|
|
3770
|
+
|
|
3771
|
+
## \u8FD0\u884C\u65F6
|
|
3772
|
+
- Node.js: ${process.version}
|
|
3773
|
+
- AssetPlex \u7248\u672C: 0.1.0
|
|
3774
|
+
`;
|
|
3775
|
+
var HUB_README_TEMPLATE = `# AssetPlex \u2014 \u4E2A\u4EBA AI \u5DE5\u5177\u914D\u7F6E\u4E2D\u5FC3
|
|
3776
|
+
|
|
3777
|
+
\u6B64\u76EE\u5F55\u7531 [AssetPlex](https://github.com/wynter-cai/assetplex) \u7BA1\u7406\u3002
|
|
3778
|
+
\u4FEE\u6539\u4EFB\u610F\u6587\u4EF6\u540E\uFF0C\u8FD0\u884C \`assetplex sync\` \u540C\u6B65\u5230\u6240\u6709\u542F\u7528\u7684\u5DE5\u5177\u3002
|
|
3779
|
+
|
|
3780
|
+
## \u76EE\u5F55\u7ED3\u6784
|
|
3781
|
+
- \`identity/\` \u2014 \u4F60\u7684\u4E2A\u4EBA\u8EAB\u4EFD\u753B\u50CF\uFF08\u624B\u5199 + AI \u81EA\u52A8\u7EF4\u62A4\uFF09
|
|
3782
|
+
- \`skills/\` \u2014 \u8DE8\u5DE5\u5177 Skills \u5E93
|
|
3783
|
+
- \`rules/\` \u2014 \u901A\u7528\u89C4\u5219
|
|
3784
|
+
- \`preferences/\` \u2014 \u4E2A\u4EBA\u504F\u597D\uFF08\u7F16\u7801\u98CE\u683C\u3001Git \u5DE5\u4F5C\u6D41\u7B49\uFF09
|
|
3785
|
+
- \`mcp/\` \u2014 MCP \u670D\u52A1\u5668\u96C6\u4E2D\u914D\u7F6E
|
|
3786
|
+
- \`commands/\` \u2014 \u81EA\u5B9A\u4E49 slash commands
|
|
3787
|
+
- \`agents/\` \u2014 \u5B50\u4EE3\u7406\u5B9A\u4E49
|
|
3788
|
+
- \`hub.toml\` \u2014 Hub \u4E3B\u914D\u7F6E
|
|
3789
|
+
|
|
3790
|
+
## \u5E38\u7528\u547D\u4EE4
|
|
3791
|
+
\`\`\`bash
|
|
3792
|
+
assetplex sync # \u540C\u6B65\u5230\u6240\u6709\u5DE5\u5177
|
|
3793
|
+
assetplex sync --dry-run # \u9884\u89C8\u53D8\u66F4
|
|
3794
|
+
assetplex doctor # \u4F53\u68C0
|
|
3795
|
+
assetplex profile learn # \u8BA9 AI \u5B66\u4E60\u6700\u8FD1\u884C\u4E3A
|
|
3796
|
+
assetplex skill search <q> # \u641C\u7D22\u793E\u533A skills
|
|
3797
|
+
\`\`\`
|
|
3798
|
+
`;
|
|
3799
|
+
|
|
3800
|
+
// src/cli/commands/init.ts
|
|
3801
|
+
var HUB_DIRECTORIES = [
|
|
3802
|
+
"identity",
|
|
3803
|
+
"skills",
|
|
3804
|
+
"rules/always",
|
|
3805
|
+
"rules/by-glob",
|
|
3806
|
+
"rules/by-project",
|
|
3807
|
+
"preferences",
|
|
3808
|
+
"mcp",
|
|
3809
|
+
"commands",
|
|
3810
|
+
"agents",
|
|
3811
|
+
".backups"
|
|
3812
|
+
];
|
|
3813
|
+
var HUB_INITIAL_FILES = [
|
|
3814
|
+
{
|
|
3815
|
+
path: ["hub.toml"],
|
|
3816
|
+
content: generateDefaultConfigToml(),
|
|
3817
|
+
overwrite: false
|
|
3818
|
+
},
|
|
3819
|
+
{
|
|
3820
|
+
path: ["README.md"],
|
|
3821
|
+
content: HUB_README_TEMPLATE,
|
|
3822
|
+
overwrite: false
|
|
3823
|
+
},
|
|
3824
|
+
{
|
|
3825
|
+
path: ["identity", "profile.md"],
|
|
3826
|
+
content: PROFILE_TEMPLATE,
|
|
3827
|
+
overwrite: false
|
|
3828
|
+
},
|
|
3829
|
+
{
|
|
3830
|
+
path: ["identity", "profile.auto.md"],
|
|
3831
|
+
content: [
|
|
3832
|
+
"<!-- AUTO-GENERATED by assetplex profile learn. Do not edit manually. -->",
|
|
3833
|
+
"# Auto-Learned Profile",
|
|
3834
|
+
"",
|
|
3835
|
+
"> \u6B64\u6587\u4EF6\u7531 `assetplex profile learn` \u547D\u4EE4\u7EF4\u62A4\uFF0C\u8BB0\u5F55 AI \u4ECE\u4F60\u7684\u884C\u4E3A\u4E2D\u5B66\u4E60\u5230\u7684\u4E8B\u5B9E\u3002",
|
|
3836
|
+
"> \u4E0D\u8981\u624B\u52A8\u7F16\u8F91\u6B64\u6587\u4EF6\u3002\u8FD0\u884C `assetplex profile learn --dry-run` \u9884\u89C8\u5C06\u8981\u5199\u5165\u7684\u4E8B\u5B9E\u3002",
|
|
3837
|
+
"",
|
|
3838
|
+
"## \u6700\u8FD1\u5B66\u4E60\u5230\u7684\u4E8B\u5B9E",
|
|
3839
|
+
"<!-- (\u5C1A\u65E0\u6570\u636E\uFF0C\u8FD0\u884C assetplex profile learn \u5B66\u4E60) -->",
|
|
3840
|
+
"",
|
|
3841
|
+
"## \u5E38\u7528\u5DE5\u4F5C\u6D41",
|
|
3842
|
+
"<!-- (\u5C1A\u65E0\u6570\u636E) -->",
|
|
3843
|
+
"",
|
|
3844
|
+
"## \u9519\u8BEF\u6559\u8BAD\uFF08Lessons\uFF09",
|
|
3845
|
+
"<!-- (\u5C1A\u65E0\u6570\u636E) -->",
|
|
3846
|
+
""
|
|
3847
|
+
].join("\n"),
|
|
3848
|
+
overwrite: false
|
|
3849
|
+
},
|
|
3850
|
+
{
|
|
3851
|
+
path: ["identity", "communication-style.md"],
|
|
3852
|
+
content: COMMUNICATION_STYLE_TEMPLATE,
|
|
3853
|
+
overwrite: false
|
|
3854
|
+
},
|
|
3855
|
+
{
|
|
3856
|
+
path: ["identity", "tech-stack.md"],
|
|
3857
|
+
content: TECH_STACK_TEMPLATE,
|
|
3858
|
+
overwrite: false
|
|
3859
|
+
},
|
|
3860
|
+
{
|
|
3861
|
+
path: ["identity", "env.md"],
|
|
3862
|
+
content: ENV_TEMPLATE,
|
|
3863
|
+
overwrite: false
|
|
3864
|
+
},
|
|
3865
|
+
{
|
|
3866
|
+
path: ["preferences", "coding-style.md"],
|
|
3867
|
+
content: CODING_STYLE_TEMPLATE,
|
|
3868
|
+
overwrite: false
|
|
3869
|
+
},
|
|
3870
|
+
{
|
|
3871
|
+
path: ["preferences", "git-workflow.md"],
|
|
3872
|
+
content: GIT_WORKFLOW_TEMPLATE,
|
|
3873
|
+
overwrite: false
|
|
3874
|
+
},
|
|
3875
|
+
// 初始规则文件
|
|
3876
|
+
{
|
|
3877
|
+
path: ["rules", "always", "global.md"],
|
|
3878
|
+
content: [
|
|
3879
|
+
"# Global Rules",
|
|
3880
|
+
"",
|
|
3881
|
+
"> \u6B64\u89C4\u5219\u6587\u4EF6\u4F1A\u540C\u6B65\u5230\u6240\u6709\u5DE5\u5177\u7684 rules/always/\uFF0C\u5BF9\u6240\u6709 AI \u52A9\u624B\u59CB\u7EC8\u751F\u6548\u3002",
|
|
3882
|
+
"",
|
|
3883
|
+
"## \u901A\u7528\u89C4\u5219",
|
|
3884
|
+
"- \u4FEE\u6539\u4EE3\u7801\u524D\u5148\u9605\u8BFB\u76F8\u5173\u6587\u4EF6",
|
|
3885
|
+
"- \u4E0D\u8981\u505A\u672A\u88AB\u8BF7\u6C42\u7684\u6539\u52A8",
|
|
3886
|
+
"- \u4E0D\u8981\u6DFB\u52A0\u672A\u88AB\u8BF7\u6C42\u7684\u6CE8\u91CA\u3001\u6587\u6863\u6216\u7C7B\u578B\u6CE8\u89E3",
|
|
3887
|
+
"- \u4F18\u5148\u7F16\u8F91\u73B0\u6709\u6587\u4EF6\uFF0C\u800C\u975E\u521B\u5EFA\u65B0\u6587\u4EF6",
|
|
3888
|
+
"- \u7B80\u5355\u4F18\u5148\uFF0C\u907F\u514D\u8FC7\u5EA6\u8BBE\u8BA1",
|
|
3889
|
+
""
|
|
3890
|
+
].join("\n"),
|
|
3891
|
+
overwrite: false
|
|
3892
|
+
},
|
|
3893
|
+
// MCP 单源配置
|
|
3894
|
+
{
|
|
3895
|
+
path: ["mcp", "mcp.sources.json"],
|
|
3896
|
+
content: [
|
|
3897
|
+
"{",
|
|
3898
|
+
' "$schema": "https://json-schema.org/draft-07/schema",',
|
|
3899
|
+
' "mcpServers": {',
|
|
3900
|
+
" // \u5728\u6B64\u6DFB\u52A0 MCP \u670D\u52A1\u5668\u914D\u7F6E\uFF0Cassetplex \u4F1A\u81EA\u52A8\u540C\u6B65\u5230\u5404\u5DE5\u5177\uFF1A",
|
|
3901
|
+
" // - Claude Code \u7684 ~/.claude.json",
|
|
3902
|
+
" // - Codex \u7684 ~/.codex/config.toml [mcp_servers.x]",
|
|
3903
|
+
" // - TRAE CN \u7684 ~/.trae-cn/mcp.json",
|
|
3904
|
+
" // - WorkBuddy \u7684 ~/.workbuddy/.mcp.json",
|
|
3905
|
+
" }",
|
|
3906
|
+
"}",
|
|
3907
|
+
""
|
|
3908
|
+
].join("\n"),
|
|
3909
|
+
overwrite: false
|
|
3910
|
+
}
|
|
3911
|
+
];
|
|
3912
|
+
async function initCommand(options = {}) {
|
|
3913
|
+
const hubRoot = getHubRoot();
|
|
3914
|
+
log.info(`\u6B63\u5728\u521D\u59CB\u5316 AssetPlex...`);
|
|
3915
|
+
log.info(`Hub \u6839\u76EE\u5F55: ${hubRoot}`);
|
|
3916
|
+
if (existsSync2(hubRoot) && !options.force) {
|
|
3917
|
+
const hubTomlPath = hubPath("hub.toml");
|
|
3918
|
+
if (existsSync2(hubTomlPath)) {
|
|
3919
|
+
log.warn(`Hub \u5DF2\u5B58\u5728\uFF1A${hubRoot}`);
|
|
3920
|
+
log.warn(`\u5982\u9700\u91CD\u65B0\u521D\u59CB\u5316\uFF0C\u8BF7\u4F7F\u7528 --force \u9009\u9879`);
|
|
3921
|
+
return;
|
|
3922
|
+
}
|
|
3923
|
+
}
|
|
3924
|
+
log.info("\u521B\u5EFA\u76EE\u5F55\u7ED3\u6784...");
|
|
3925
|
+
for (const dir of HUB_DIRECTORIES) {
|
|
3926
|
+
const dirPath = resolve3(hubRoot, dir);
|
|
3927
|
+
mkdirSync2(dirPath, { recursive: true });
|
|
3928
|
+
log.debug(` \u521B\u5EFA\u76EE\u5F55: ${dir}`);
|
|
3929
|
+
}
|
|
3930
|
+
log.info("\u5199\u5165\u6A21\u677F\u6587\u4EF6...");
|
|
3931
|
+
let createdCount = 0;
|
|
3932
|
+
let skippedCount = 0;
|
|
3933
|
+
for (const file of HUB_INITIAL_FILES) {
|
|
3934
|
+
const filePath = resolve3(hubRoot, ...file.path);
|
|
3935
|
+
if (existsSync2(filePath) && !file.overwrite && !options.force) {
|
|
3936
|
+
log.debug(` \u8DF3\u8FC7\u5DF2\u5B58\u5728: ${file.path.join("/")}`);
|
|
3937
|
+
skippedCount++;
|
|
3938
|
+
continue;
|
|
3939
|
+
}
|
|
3940
|
+
writeFileSync2(filePath, file.content, "utf-8");
|
|
3941
|
+
log.debug(` \u5199\u5165\u6587\u4EF6: ${file.path.join("/")}`);
|
|
3942
|
+
createdCount++;
|
|
3943
|
+
}
|
|
3944
|
+
log.success(`AssetPlex \u521D\u59CB\u5316\u5B8C\u6210\uFF01`);
|
|
3945
|
+
log.info(` \u6839\u76EE\u5F55: ${hubRoot}`);
|
|
3946
|
+
log.info(` \u521B\u5EFA\u6587\u4EF6: ${createdCount} \u4E2A`);
|
|
3947
|
+
if (skippedCount > 0) {
|
|
3948
|
+
log.info(` \u8DF3\u8FC7\u5DF2\u5B58\u5728: ${skippedCount} \u4E2A`);
|
|
3949
|
+
}
|
|
3950
|
+
log.info("");
|
|
3951
|
+
log.info("\u4E0B\u4E00\u6B65\uFF1A");
|
|
3952
|
+
log.info(" 1. \u7F16\u8F91 ~/.assetplex/identity/profile.md \u586B\u5199\u4F60\u7684\u4E2A\u4EBA\u753B\u50CF");
|
|
3953
|
+
log.info(" 2. \u8FD0\u884C `assetplex doctor` \u68C0\u6D4B\u5404 AI \u5DE5\u5177\u5B89\u88C5\u72B6\u6001");
|
|
3954
|
+
log.info(" 3. \u8FD0\u884C `assetplex sync` \u540C\u6B65\u5230\u6240\u6709\u5DE5\u5177");
|
|
3955
|
+
log.info(" 4. \u8FD0\u884C `assetplex --help` \u67E5\u770B\u6240\u6709\u547D\u4EE4");
|
|
3956
|
+
}
|
|
3957
|
+
|
|
3958
|
+
// src/cli/commands/doctor.ts
|
|
3959
|
+
init_esm_shims();
|
|
3960
|
+
init_logger();
|
|
3961
|
+
init_paths();
|
|
3962
|
+
init_registry();
|
|
3963
|
+
import { existsSync as existsSync11 } from "fs";
|
|
3964
|
+
async function doctorCommand(options = {}) {
|
|
3965
|
+
registerBuiltinAdapters();
|
|
3966
|
+
const hubRoot = getHubRoot();
|
|
3967
|
+
const hubTomlExists = existsSync11(hubPath("hub.toml"));
|
|
3968
|
+
const result = {
|
|
3969
|
+
hubRoot,
|
|
3970
|
+
hubInitialized: hubTomlExists,
|
|
3971
|
+
hubTomlExists,
|
|
3972
|
+
tools: [],
|
|
3973
|
+
issues: []
|
|
3974
|
+
};
|
|
3975
|
+
if (!hubTomlExists) {
|
|
3976
|
+
result.issues.push("Hub \u672A\u521D\u59CB\u5316\u3002\u8BF7\u5148\u8FD0\u884C `assetplex init`\u3002");
|
|
3977
|
+
}
|
|
3978
|
+
const adapters = options.tool ? getAllAdapters().filter((a) => a.name === options.tool) : getAllAdapters();
|
|
3979
|
+
for (const adapter of adapters) {
|
|
3980
|
+
try {
|
|
3981
|
+
const status = await adapter.detect();
|
|
3982
|
+
result.tools.push(status);
|
|
3983
|
+
} catch (err) {
|
|
3984
|
+
result.tools.push({
|
|
3985
|
+
name: adapter.name,
|
|
3986
|
+
installed: false,
|
|
3987
|
+
configDirExists: false,
|
|
3988
|
+
configDir: "",
|
|
3989
|
+
error: err instanceof Error ? err.message : String(err)
|
|
3990
|
+
});
|
|
3991
|
+
}
|
|
3992
|
+
}
|
|
3993
|
+
if (options.json) {
|
|
3994
|
+
console.log(JSON.stringify(result, null, 2));
|
|
3995
|
+
return;
|
|
3996
|
+
}
|
|
3997
|
+
printDoctorReport(result);
|
|
3998
|
+
}
|
|
3999
|
+
function printDoctorReport(result) {
|
|
4000
|
+
log.info("AssetPlex \u4F53\u68C0\u62A5\u544A");
|
|
4001
|
+
log.info("=".repeat(60));
|
|
4002
|
+
log.info(`Hub \u6839\u76EE\u5F55: ${result.hubRoot}`);
|
|
4003
|
+
log.info(`Hub \u5DF2\u521D\u59CB\u5316: ${result.hubInitialized ? "\u2713" : "\u2717"}`);
|
|
4004
|
+
log.info("");
|
|
4005
|
+
if (result.issues.length > 0) {
|
|
4006
|
+
log.warn("Hub \u95EE\u9898\uFF1A");
|
|
4007
|
+
for (const issue of result.issues) {
|
|
4008
|
+
log.warn(` - ${issue}`);
|
|
4009
|
+
}
|
|
4010
|
+
log.info("");
|
|
4011
|
+
}
|
|
4012
|
+
log.info("\u5DF2\u6CE8\u518C\u5DE5\u5177\u72B6\u6001\uFF1A");
|
|
4013
|
+
log.info("-".repeat(60));
|
|
4014
|
+
for (const tool of result.tools) {
|
|
4015
|
+
const status = tool.installed ? "\u2713 \u5DF2\u5B89\u88C5" : "\u2717 \u672A\u5B89\u88C5";
|
|
4016
|
+
const version = tool.version ? ` v${tool.version}` : "";
|
|
4017
|
+
const dir = tool.configDir || "(\u65E0)";
|
|
4018
|
+
log.info(` ${tool.name.padEnd(15)} ${status}${version}`);
|
|
4019
|
+
log.info(` ${" ".repeat(15)} \u914D\u7F6E\u76EE\u5F55: ${dir}`);
|
|
4020
|
+
if (tool.error) {
|
|
4021
|
+
log.warn(` ${" ".repeat(15)} \u5907\u6CE8: ${tool.error}`);
|
|
4022
|
+
}
|
|
4023
|
+
log.info("");
|
|
4024
|
+
}
|
|
4025
|
+
const installedCount = result.tools.filter((t) => t.installed).length;
|
|
4026
|
+
const totalCount = result.tools.length;
|
|
4027
|
+
log.info("-".repeat(60));
|
|
4028
|
+
log.info(`\u603B\u7ED3: ${installedCount}/${totalCount} \u4E2A\u5DE5\u5177\u5DF2\u5B89\u88C5`);
|
|
4029
|
+
if (installedCount === 0) {
|
|
4030
|
+
log.warn("");
|
|
4031
|
+
log.warn("\u672A\u68C0\u6D4B\u5230\u4EFB\u4F55 AI \u5DE5\u5177\uFF0C\u8BF7\u5148\u5B89\u88C5\u81F3\u5C11\u4E00\u4E2A\u5DE5\u5177\u3002");
|
|
4032
|
+
} else if (!result.hubInitialized) {
|
|
4033
|
+
log.warn("");
|
|
4034
|
+
log.warn("Hub \u672A\u521D\u59CB\u5316\uFF0C\u8BF7\u8FD0\u884C: assetplex init");
|
|
4035
|
+
} else {
|
|
4036
|
+
log.success("");
|
|
4037
|
+
log.success("\u4F53\u68C0\u5B8C\u6210\uFF0C\u53EF\u8FD0\u884C `assetplex sync` \u540C\u6B65\u914D\u7F6E");
|
|
4038
|
+
}
|
|
4039
|
+
}
|
|
4040
|
+
|
|
4041
|
+
// src/cli/commands/sync.ts
|
|
4042
|
+
init_esm_shims();
|
|
4043
|
+
init_logger();
|
|
4044
|
+
init_paths();
|
|
4045
|
+
init_config();
|
|
4046
|
+
init_registry();
|
|
4047
|
+
init_sync_engine();
|
|
4048
|
+
import { existsSync as existsSync14 } from "fs";
|
|
4049
|
+
async function syncCommand(options = {}) {
|
|
4050
|
+
if (options.watch) {
|
|
4051
|
+
log.warn("--watch \u5C06\u5728 Stage 2.5 \u5B9E\u73B0");
|
|
4052
|
+
return;
|
|
4053
|
+
}
|
|
4054
|
+
const hubRoot = getHubRoot();
|
|
4055
|
+
const hubTomlPath = hubPath("hub.toml");
|
|
4056
|
+
if (!existsSync14(hubTomlPath)) {
|
|
4057
|
+
log.error(`Hub \u672A\u521D\u59CB\u5316\uFF1A${hubTomlPath} \u4E0D\u5B58\u5728`);
|
|
4058
|
+
log.error("\u8BF7\u5148\u8FD0\u884C `assetplex init`");
|
|
4059
|
+
process.exit(1);
|
|
4060
|
+
}
|
|
4061
|
+
let hubConfig;
|
|
4062
|
+
try {
|
|
4063
|
+
hubConfig = loadHubConfig(resolveHubConfigPath());
|
|
4064
|
+
} catch (err) {
|
|
4065
|
+
log.error(`\u52A0\u8F7D hub.toml \u5931\u8D25: ${err.message}`);
|
|
4066
|
+
process.exit(1);
|
|
4067
|
+
}
|
|
4068
|
+
registerBuiltinAdapters();
|
|
4069
|
+
const adapters = getAllAdapters();
|
|
4070
|
+
log.info(`Hub \u6839\u76EE\u5F55: ${hubRoot}`);
|
|
4071
|
+
log.info(`\u5DF2\u6CE8\u518C\u9002\u914D\u5668: ${adapters.length} \u4E2A`);
|
|
4072
|
+
if (options.tool) {
|
|
4073
|
+
log.info(`\u6307\u5B9A\u5DE5\u5177: ${options.tool}`);
|
|
4074
|
+
}
|
|
4075
|
+
if (options.dryRun) {
|
|
4076
|
+
log.info("\u6A21\u5F0F: --dry-run\uFF08\u4EC5\u9884\u89C8\u4E0D\u5199\u5165\uFF09");
|
|
4077
|
+
}
|
|
4078
|
+
const engine = new SyncEngine(hubConfig, adapters);
|
|
4079
|
+
if (options.dryRun) {
|
|
4080
|
+
const plans = await engine.plan(options);
|
|
4081
|
+
if (options.json) {
|
|
4082
|
+
console.log(JSON.stringify(plans, null, 2));
|
|
4083
|
+
return;
|
|
4084
|
+
}
|
|
4085
|
+
log.info("");
|
|
4086
|
+
log.info("\u540C\u6B65\u8BA1\u5212\uFF08dry-run\uFF09");
|
|
4087
|
+
log.info("=".repeat(60));
|
|
4088
|
+
printSyncPlan(plans);
|
|
4089
|
+
return;
|
|
4090
|
+
}
|
|
4091
|
+
const results = await engine.run(options);
|
|
4092
|
+
if (options.json) {
|
|
4093
|
+
console.log(JSON.stringify(results, null, 2));
|
|
4094
|
+
return;
|
|
4095
|
+
}
|
|
4096
|
+
printSyncResult(results);
|
|
4097
|
+
const failedTools = results.filter((r) => !r.success);
|
|
4098
|
+
if (failedTools.length > 0) {
|
|
4099
|
+
log.warn("");
|
|
4100
|
+
log.warn(`\u6709 ${failedTools.length} \u4E2A\u5DE5\u5177\u540C\u6B65\u5931\u8D25`);
|
|
4101
|
+
process.exit(1);
|
|
4102
|
+
} else {
|
|
4103
|
+
log.success("");
|
|
4104
|
+
log.success("\u540C\u6B65\u5B8C\u6210\uFF01");
|
|
4105
|
+
}
|
|
4106
|
+
}
|
|
4107
|
+
|
|
4108
|
+
// src/cli/index.ts
|
|
4109
|
+
var program = new Command();
|
|
4110
|
+
program.name("assetplex").description("One identity, every AI agent \u2014 built for the China stack and beyond.").version("0.1.0").option("-v, --verbose", "\u542F\u7528\u8BE6\u7EC6\u65E5\u5FD7\u8F93\u51FA", () => setLogLevel("debug")).option("-q, --quiet", "\u9759\u9ED8\u6A21\u5F0F\uFF08\u4EC5\u8F93\u51FA\u9519\u8BEF\uFF09", () => setLogLevel("error"));
|
|
4111
|
+
program.command("init").description("\u521D\u59CB\u5316 ~/.assetplex/ \u76EE\u5F55\u4E0E\u6A21\u677F\u6587\u4EF6").option("-f, --force", "\u5F3A\u5236\u8986\u76D6\u5DF2\u5B58\u5728\u6587\u4EF6").option("--import-existing", "\u4ECE\u73B0\u6709\u5404\u5DE5\u5177\u53CD\u5411\u5BFC\u5165\u914D\u7F6E").option("-y, --yes", "\u8DF3\u8FC7\u4EA4\u4E92\u63D0\u793A\uFF0C\u4F7F\u7528\u9ED8\u8BA4\u503C").action(async (opts) => {
|
|
4112
|
+
try {
|
|
4113
|
+
await initCommand(opts);
|
|
4114
|
+
} catch (err) {
|
|
4115
|
+
log.error("\u521D\u59CB\u5316\u5931\u8D25:", err instanceof Error ? err.message : String(err));
|
|
4116
|
+
process.exit(1);
|
|
4117
|
+
}
|
|
4118
|
+
});
|
|
4119
|
+
program.command("doctor").description("\u4F53\u68C0\uFF1A\u68C0\u6D4B\u5404 AI \u5DE5\u5177\u5B89\u88C5\u72B6\u6001\u3001Hub \u5B8C\u6574\u6027").option("--fix", "\u81EA\u52A8\u4FEE\u590D\u68C0\u6D4B\u5230\u7684\u95EE\u9898").option("--json", "JSON \u8F93\u51FA\uFF08\u811A\u672C\u53CB\u597D\uFF09").option("--tool <name>", "\u4EC5\u68C0\u67E5\u6307\u5B9A\u5DE5\u5177").action(async (opts) => {
|
|
4120
|
+
try {
|
|
4121
|
+
await doctorCommand(opts);
|
|
4122
|
+
} catch (err) {
|
|
4123
|
+
log.error("\u4F53\u68C0\u5931\u8D25:", err instanceof Error ? err.message : String(err));
|
|
4124
|
+
process.exit(1);
|
|
4125
|
+
}
|
|
4126
|
+
});
|
|
4127
|
+
program.command("sync").description("\u540C\u6B65 Hub \u5185\u5BB9\u5230\u6240\u6709\u542F\u7528\u7684\u5DE5\u5177").option("--tool <name>", "\u4EC5\u540C\u6B65\u5230\u6307\u5B9A\u5DE5\u5177").option("--watch", "\u76D1\u542C\u6587\u4EF6\u53D8\u5316\u81EA\u52A8\u540C\u6B65\uFF08Stage 2.5 \u5B9E\u73B0\uFF09").option("--dry-run", "\u9884\u89C8\u53D8\u66F4\u4E0D\u5199\u5165").option("--json", "JSON \u8F93\u51FA").action(async (opts) => {
|
|
4128
|
+
try {
|
|
4129
|
+
await syncCommand(opts);
|
|
4130
|
+
} catch (err) {
|
|
4131
|
+
log.error("\u540C\u6B65\u5931\u8D25:", err instanceof Error ? err.message : String(err));
|
|
4132
|
+
process.exit(1);
|
|
4133
|
+
}
|
|
4134
|
+
});
|
|
4135
|
+
program.command("ui").description("\u542F\u52A8 Web UI \u53EF\u89C6\u5316\u7BA1\u7406\u754C\u9762").option("-p, --port <port>", "\u7AEF\u53E3\u53F7", "17521").option("-H, --host <host>", "\u76D1\u542C\u5730\u5740", "127.0.0.1").option("--no-open", "\u4E0D\u81EA\u52A8\u6253\u5F00\u6D4F\u89C8\u5668").action(async (opts) => {
|
|
4136
|
+
try {
|
|
4137
|
+
const { uiCommand: uiCommand2 } = await Promise.resolve().then(() => (init_ui(), ui_exports));
|
|
4138
|
+
await uiCommand2({
|
|
4139
|
+
port: parseInt(opts.port, 10),
|
|
4140
|
+
host: opts.host,
|
|
4141
|
+
noOpen: !opts.open
|
|
4142
|
+
});
|
|
4143
|
+
} catch (err) {
|
|
4144
|
+
log.error("\u542F\u52A8 UI \u5931\u8D25:", err instanceof Error ? err.message : String(err));
|
|
4145
|
+
process.exit(1);
|
|
4146
|
+
}
|
|
4147
|
+
});
|
|
4148
|
+
program.parseAsync(process.argv).catch((err) => {
|
|
4149
|
+
log.error("\u6267\u884C\u5931\u8D25:", err instanceof Error ? err.message : String(err));
|
|
4150
|
+
process.exit(1);
|
|
4151
|
+
});
|
|
4152
|
+
//# sourceMappingURL=index.js.map
|