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/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