remnote-bridge 0.1.11 → 0.1.13

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.
Files changed (70) hide show
  1. package/dist/cli/addon/addon-manager.js +163 -0
  2. package/dist/cli/addon/registry.js +24 -0
  3. package/dist/cli/commands/addon.js +149 -0
  4. package/dist/cli/commands/clean.js +121 -52
  5. package/dist/cli/commands/connect.js +72 -33
  6. package/dist/cli/commands/disconnect.js +19 -19
  7. package/dist/cli/commands/edit-rem.js +8 -36
  8. package/dist/cli/commands/edit-tree.js +3 -20
  9. package/dist/cli/commands/health.js +19 -18
  10. package/dist/cli/commands/read-context.js +3 -20
  11. package/dist/cli/commands/read-globe.js +3 -20
  12. package/dist/cli/commands/read-rem.js +6 -32
  13. package/dist/cli/commands/read-tree.js +3 -20
  14. package/dist/cli/commands/search.js +97 -21
  15. package/dist/cli/config.js +148 -72
  16. package/dist/cli/daemon/daemon.js +104 -24
  17. package/dist/cli/daemon/dev-server.js +9 -1
  18. package/dist/cli/daemon/pid.js +36 -22
  19. package/dist/cli/daemon/registry.js +160 -0
  20. package/dist/cli/daemon/send-request.js +11 -11
  21. package/dist/cli/daemon/static-server.js +97 -34
  22. package/dist/cli/handlers/edit-handler.js +49 -140
  23. package/dist/cli/handlers/read-handler.js +9 -9
  24. package/dist/cli/handlers/rem-cache.js +10 -5
  25. package/dist/cli/handlers/tree-parser.js +16 -9
  26. package/dist/cli/main.js +67 -19
  27. package/dist/cli/protocol.js +18 -4
  28. package/dist/cli/server/config-server.js +280 -14
  29. package/dist/cli/server/ws-server.js +93 -44
  30. package/dist/cli/utils/output.js +29 -0
  31. package/dist/mcp/format.js +43 -0
  32. package/dist/mcp/index.js +0 -55
  33. package/dist/mcp/instructions.js +424 -216
  34. package/dist/mcp/resources/edit-rem-guide.js +37 -158
  35. package/dist/mcp/resources/edit-tree-guide.js +1 -1
  36. package/dist/mcp/resources/error-reference.js +9 -13
  37. package/dist/mcp/resources/rem-object-fields.js +6 -6
  38. package/dist/mcp/tools/edit-tools.js +69 -8
  39. package/dist/mcp/tools/infra-tools.js +44 -8
  40. package/dist/mcp/tools/read-tools.js +136 -20
  41. package/package.json +2 -2
  42. package/remnote-plugin/dist/bridge_widget-sandbox.js +17 -17
  43. package/remnote-plugin/dist/bridge_widget.js +17 -17
  44. package/remnote-plugin/dist/index-sandbox.js +31 -31
  45. package/remnote-plugin/dist/index.js +31 -31
  46. package/remnote-plugin/dist/manifest.json +1 -1
  47. package/remnote-plugin/package.json +1 -1
  48. package/remnote-plugin/public/manifest.json +1 -1
  49. package/remnote-plugin/src/bridge/multi-connection-manager.ts +151 -0
  50. package/remnote-plugin/src/bridge/websocket-client.ts +62 -16
  51. package/remnote-plugin/src/services/index.ts +0 -8
  52. package/remnote-plugin/src/services/read-rem.ts +1 -9
  53. package/remnote-plugin/src/services/search.ts +13 -10
  54. package/remnote-plugin/src/settings.ts +9 -7
  55. package/remnote-plugin/src/utils/index.ts +0 -5
  56. package/remnote-plugin/src/widgets/bridge_widget.tsx +105 -20
  57. package/remnote-plugin/src/widgets/index.tsx +41 -44
  58. package/remnote-plugin/webpack.config.js +35 -0
  59. package/skills/remnote-bridge/SKILL.md +45 -40
  60. package/skills/remnote-bridge/instructions/addon.md +134 -0
  61. package/skills/remnote-bridge/instructions/clean.md +110 -0
  62. package/skills/remnote-bridge/instructions/connect.md +80 -37
  63. package/skills/remnote-bridge/instructions/disconnect.md +22 -9
  64. package/skills/remnote-bridge/instructions/edit-rem.md +113 -327
  65. package/skills/remnote-bridge/instructions/health.md +23 -13
  66. package/skills/remnote-bridge/instructions/install-skill.md +58 -0
  67. package/skills/remnote-bridge/instructions/overall.md +99 -35
  68. package/skills/remnote-bridge/instructions/read-rem.md +15 -15
  69. package/skills/remnote-bridge/instructions/search.md +77 -18
  70. package/skills/remnote-bridge/instructions/setup.md +5 -6
@@ -0,0 +1,163 @@
1
+ /**
2
+ * Addon 管理器
3
+ *
4
+ * 负责增强项目的安装、卸载、检测、环境变量映射和自动部署。
5
+ */
6
+ import { execFile } from 'node:child_process';
7
+ import fs from 'fs';
8
+ import os from 'os';
9
+ import { ADDON_REGISTRY } from './registry.js';
10
+ const DETECT_TIMEOUT_MS = 5_000;
11
+ const INSTALL_TIMEOUT_MS = 120_000;
12
+ export class AddonManager {
13
+ config;
14
+ constructor(config) {
15
+ this.config = config;
16
+ }
17
+ /** 获取所有已注册 addon 的状态 */
18
+ async listAll() {
19
+ const entries = [...ADDON_REGISTRY.values()];
20
+ const installedChecks = await Promise.all(entries.map((def) => this.isInstalled(def.name)));
21
+ return entries.map((def, i) => {
22
+ const userConfig = this.config.addons?.[def.name];
23
+ const validation = this.validateSettings(def.name);
24
+ return {
25
+ name: def.name,
26
+ description: def.description,
27
+ enabled: userConfig?.enabled ?? false,
28
+ installed: installedChecks[i],
29
+ settingsValid: validation.valid,
30
+ missingSettings: validation.missing,
31
+ };
32
+ });
33
+ }
34
+ /** 检测单个 addon 是否已安装 */
35
+ async isInstalled(name) {
36
+ const def = ADDON_REGISTRY.get(name);
37
+ if (!def)
38
+ return false;
39
+ try {
40
+ await this.execCommand(def.detectCommand.bin, def.detectCommand.args, DETECT_TIMEOUT_MS, this.buildEnv(name));
41
+ return true;
42
+ }
43
+ catch {
44
+ return false;
45
+ }
46
+ }
47
+ /** 安装单个 addon */
48
+ async install(name, onLog) {
49
+ const def = ADDON_REGISTRY.get(name);
50
+ if (!def) {
51
+ throw new Error(`未知 addon: ${name}。已注册: ${[...ADDON_REGISTRY.keys()].join(', ')}`);
52
+ }
53
+ const pkg = def.packageName ?? def.name;
54
+ const spec = def.versionConstraint ? `${pkg}${def.versionConstraint}` : pkg;
55
+ onLog?.(`正在安装 ${name}...`);
56
+ switch (def.installer) {
57
+ case 'pip':
58
+ await this.pipInstall(spec, onLog);
59
+ break;
60
+ case 'npm':
61
+ await this.execCommand('npm', ['install', '-g', spec], INSTALL_TIMEOUT_MS);
62
+ break;
63
+ default:
64
+ throw new Error(`不支持的安装器类型: ${def.installer}`);
65
+ }
66
+ // 安装后验证
67
+ const installed = await this.isInstalled(name);
68
+ if (!installed) {
69
+ throw new Error(`${name} 安装命令成功但检测仍不可用,请检查 PATH 配置`);
70
+ }
71
+ onLog?.(`${name} 安装成功`);
72
+ }
73
+ /** 卸载单个 addon */
74
+ async uninstall(name, purge = false, onLog) {
75
+ const def = ADDON_REGISTRY.get(name);
76
+ if (!def) {
77
+ throw new Error(`未知 addon: ${name}`);
78
+ }
79
+ const pkg = def.packageName ?? def.name;
80
+ onLog?.(`正在卸载 ${name}...`);
81
+ switch (def.installer) {
82
+ case 'pip':
83
+ await this.execCommand('pip', ['uninstall', '-y', pkg], INSTALL_TIMEOUT_MS);
84
+ break;
85
+ case 'npm':
86
+ await this.execCommand('npm', ['uninstall', '-g', pkg], INSTALL_TIMEOUT_MS);
87
+ break;
88
+ }
89
+ if (purge && def.dataDirs) {
90
+ for (const dir of def.dataDirs) {
91
+ const resolved = dir.replace(/^~/, os.homedir());
92
+ if (fs.existsSync(resolved)) {
93
+ fs.rmSync(resolved, { recursive: true, force: true });
94
+ onLog?.(`已清理数据目录: ${resolved}`);
95
+ }
96
+ }
97
+ }
98
+ onLog?.(`${name} 卸载成功`);
99
+ }
100
+ /** 获取 addon 对应的环境变量(addon 配置已独立存储,此方法保留供未来扩展) */
101
+ getEnvVars(_name) {
102
+ return {};
103
+ }
104
+ /** 校验 addon 配置完整性(addon 配置已独立存储,此方法保留供未来扩展) */
105
+ validateSettings(_name) {
106
+ return { valid: true, missing: [] };
107
+ }
108
+ /** connect 时自动安装所有已启用但未安装的 addon */
109
+ async ensureEnabledAddons(onLog) {
110
+ const results = [];
111
+ for (const [name, def] of ADDON_REGISTRY) {
112
+ const userConfig = this.config.addons?.[name];
113
+ if (!userConfig?.enabled)
114
+ continue;
115
+ const installed = await this.isInstalled(name);
116
+ if (installed) {
117
+ results.push({ name, action: 'already-installed' });
118
+ continue;
119
+ }
120
+ // 已启用但未安装 → 自动安装
121
+ try {
122
+ await this.install(name, onLog);
123
+ results.push({ name, action: 'installed' });
124
+ }
125
+ catch (err) {
126
+ const errorMsg = err instanceof Error ? err.message : String(err);
127
+ onLog?.(`addon ${name} 自动安装失败: ${errorMsg}`, 'warn');
128
+ results.push({ name, action: 'failed', error: errorMsg });
129
+ }
130
+ }
131
+ return results;
132
+ }
133
+ // ── 内部方法 ──
134
+ buildEnv(name) {
135
+ return { ...process.env, ...this.getEnvVars(name) };
136
+ }
137
+ async pipInstall(spec, onLog) {
138
+ try {
139
+ await this.execCommand('pip', ['install', spec], INSTALL_TIMEOUT_MS);
140
+ }
141
+ catch {
142
+ // 如果直接 pip install 失败,尝试 pip install --user
143
+ onLog?.('pip install 失败,尝试 --user 模式...');
144
+ await this.execCommand('pip', ['install', '--user', spec], INSTALL_TIMEOUT_MS);
145
+ }
146
+ }
147
+ execCommand(bin, args, timeout, env) {
148
+ return new Promise((resolve, reject) => {
149
+ execFile(bin, args, {
150
+ timeout,
151
+ maxBuffer: 5 * 1024 * 1024,
152
+ env: env ?? process.env,
153
+ }, (error, stdout, stderr) => {
154
+ if (error) {
155
+ const msg = stderr?.trim() || stdout?.trim() || error.message;
156
+ reject(new Error(msg));
157
+ return;
158
+ }
159
+ resolve(stdout.trim());
160
+ });
161
+ });
162
+ }
163
+ }
@@ -0,0 +1,24 @@
1
+ /**
2
+ * Addon 注册表
3
+ *
4
+ * 静态注册所有已知增强项目的元数据。
5
+ * 每个 addon 定义安装方式、检测命令、环境变量映射等信息。
6
+ */
7
+ /** 内置 addon 注册表 */
8
+ export const ADDON_REGISTRY = new Map([
9
+ [
10
+ 'remnote-rag',
11
+ {
12
+ name: 'remnote-rag',
13
+ description: 'RemNote 语义搜索增强:直读本地数据库,构建向量索引',
14
+ installer: 'pip',
15
+ packageName: 'remnote-rag',
16
+ executableName: 'remnote-rag',
17
+ detectCommand: { bin: 'remnote-rag', args: ['--help'] },
18
+ envMapping: {},
19
+ requiredSettings: [],
20
+ dataDirs: ['~/.remnote-bridge/addons/remnote-rag'],
21
+ versionConstraint: '>=0.1.0',
22
+ },
23
+ ],
24
+ ]);
@@ -0,0 +1,149 @@
1
+ /**
2
+ * addon 命令组
3
+ *
4
+ * 管理增强项目(addon)的安装、卸载和状态查看。
5
+ * - addon list 查看所有增强项目状态
6
+ * - addon install <name> 安装指定增强项目
7
+ * - addon uninstall <name> [--purge] 卸载指定增强项目
8
+ */
9
+ import { loadConfig, configFilePath, saveConfig } from '../config.js';
10
+ import { AddonManager } from '../addon/addon-manager.js';
11
+ import { ADDON_REGISTRY } from '../addon/registry.js';
12
+ import { jsonOutput } from '../utils/output.js';
13
+ export async function addonListCommand(options = {}) {
14
+ const { json } = options;
15
+ const config = loadConfig();
16
+ const manager = new AddonManager(config);
17
+ const statuses = await manager.listAll();
18
+ if (json) {
19
+ jsonOutput({ ok: true, command: 'addon-list', addons: statuses });
20
+ }
21
+ else {
22
+ if (statuses.length === 0) {
23
+ console.log('暂无已注册的增强项目');
24
+ return;
25
+ }
26
+ console.log('增强项目列表:\n');
27
+ for (const s of statuses) {
28
+ const enableTag = s.enabled ? '[已启用]' : '[已禁用]';
29
+ const installTag = s.installed ? '[已安装]' : '[未安装]';
30
+ const settingsTag = !s.settingsValid ? ` [缺少配置: ${s.missingSettings.join(', ')}]` : '';
31
+ console.log(` ${s.name} ${enableTag} ${installTag}${settingsTag} ${s.description}`);
32
+ }
33
+ }
34
+ }
35
+ export async function addonInstallCommand(name, options = {}) {
36
+ const { json } = options;
37
+ if (!ADDON_REGISTRY.has(name)) {
38
+ const available = [...ADDON_REGISTRY.keys()].join(', ');
39
+ if (json) {
40
+ jsonOutput({ ok: false, command: 'addon-install', error: `未知 addon: ${name}。可选: ${available}` });
41
+ }
42
+ else {
43
+ console.error(`错误: 未知 addon「${name}」。可选: ${available}`);
44
+ }
45
+ process.exitCode = 1;
46
+ return;
47
+ }
48
+ const config = loadConfig();
49
+ const manager = new AddonManager(config);
50
+ // 检查是否已安装
51
+ const alreadyInstalled = await manager.isInstalled(name);
52
+ if (alreadyInstalled) {
53
+ if (json) {
54
+ jsonOutput({ ok: true, command: 'addon-install', name, action: 'already-installed' });
55
+ }
56
+ else {
57
+ console.log(`${name} 已安装`);
58
+ }
59
+ // 确保配置中标记为 enabled
60
+ setAddonEnabled(config, name, true);
61
+ return;
62
+ }
63
+ try {
64
+ await manager.install(name, json ? undefined : (msg) => console.log(msg));
65
+ // 安装成功,自动启用
66
+ setAddonEnabled(config, name, true);
67
+ if (json) {
68
+ jsonOutput({ ok: true, command: 'addon-install', name, action: 'installed' });
69
+ }
70
+ else {
71
+ console.log(`\n${name} 安装成功并已启用`);
72
+ // 检查必需配置
73
+ const validation = manager.validateSettings(name);
74
+ if (!validation.valid) {
75
+ console.log(`\n注意:以下配置项尚未设置: ${validation.missing.join(', ')}`);
76
+ console.log(`请在配置页面或 ~/.remnote-bridge/addons/${name}/config.json 中配置`);
77
+ }
78
+ }
79
+ }
80
+ catch (err) {
81
+ const errorMsg = err instanceof Error ? err.message : String(err);
82
+ if (json) {
83
+ jsonOutput({ ok: false, command: 'addon-install', name, error: errorMsg });
84
+ }
85
+ else {
86
+ console.error(`错误: ${name} 安装失败 — ${errorMsg}`);
87
+ }
88
+ process.exitCode = 1;
89
+ }
90
+ }
91
+ export async function addonUninstallCommand(name, options = {}) {
92
+ const { json, purge } = options;
93
+ if (!ADDON_REGISTRY.has(name)) {
94
+ const available = [...ADDON_REGISTRY.keys()].join(', ');
95
+ if (json) {
96
+ jsonOutput({ ok: false, command: 'addon-uninstall', error: `未知 addon: ${name}。可选: ${available}` });
97
+ }
98
+ else {
99
+ console.error(`错误: 未知 addon「${name}」。可选: ${available}`);
100
+ }
101
+ process.exitCode = 1;
102
+ return;
103
+ }
104
+ const config = loadConfig();
105
+ const manager = new AddonManager(config);
106
+ try {
107
+ await manager.uninstall(name, purge, json ? undefined : (msg) => console.log(msg));
108
+ // 卸载后在配置中禁用
109
+ setAddonEnabled(config, name, false);
110
+ if (json) {
111
+ jsonOutput({ ok: true, command: 'addon-uninstall', name, purged: purge ?? false });
112
+ }
113
+ else {
114
+ console.log(`\n${name} 卸载成功${purge ? '(数据已清理)' : ''}`);
115
+ }
116
+ }
117
+ catch (err) {
118
+ const errorMsg = err instanceof Error ? err.message : String(err);
119
+ if (json) {
120
+ jsonOutput({ ok: false, command: 'addon-uninstall', name, error: errorMsg });
121
+ }
122
+ else {
123
+ console.error(`错误: ${name} 卸载失败 — ${errorMsg}`);
124
+ }
125
+ process.exitCode = 1;
126
+ }
127
+ }
128
+ // ── 辅助:更新配置文件 ──
129
+ function setAddonEnabled(config, name, enabled) {
130
+ if (!enabled) {
131
+ if (!config.addons?.[name])
132
+ return;
133
+ config.addons[name].enabled = false;
134
+ }
135
+ else {
136
+ if (!config.addons)
137
+ config.addons = {};
138
+ if (!config.addons[name])
139
+ config.addons[name] = { enabled: true };
140
+ else
141
+ config.addons[name].enabled = true;
142
+ }
143
+ try {
144
+ saveConfig(configFilePath(), config);
145
+ }
146
+ catch {
147
+ // 写配置失败不阻塞主流程
148
+ }
149
+ }
@@ -2,7 +2,10 @@
2
2
  * clean 命令
3
3
  *
4
4
  * 清理 remnote-bridge 在本机产生的所有残留文件:
5
- * - 项目根:.remnote-bridge.pid / .remnote-bridge.log / .remnote-bridge.json
5
+ * - ~/.remnote-bridge/instances/*.pid / *.log
6
+ * - ~/.remnote-bridge/registry.json
7
+ * - ~/.remnote-bridge/config.json(可选)
8
+ * - 旧版项目根:.remnote-bridge.pid / .remnote-bridge.log / .remnote-bridge.json
6
9
  * - 用户目录:~/.claude/skills/remnote-bridge/
7
10
  *
8
11
  * 如果守护进程仍在运行,先停止再清理。
@@ -10,88 +13,154 @@
10
13
  import fs from 'fs';
11
14
  import path from 'path';
12
15
  import os from 'os';
13
- import { findProjectRoot, pidFilePath, logFilePath, configFilePath } from '../config.js';
14
- import { checkDaemon } from '../daemon/pid.js';
16
+ import { GLOBAL_DIR } from '../config.js';
17
+ import { loadRegistry, MAX_SLOTS, } from '../daemon/registry.js';
18
+ import { isDaemonAlive } from '../daemon/pid.js';
19
+ import { ADDON_REGISTRY } from '../addon/registry.js';
15
20
  import { jsonOutput } from '../utils/output.js';
16
21
  export async function cleanCommand(options = {}) {
17
22
  const { json } = options;
18
- const projectRoot = findProjectRoot();
19
23
  const removed = [];
20
24
  const errors = [];
21
- // 1. 如果守护进程在运行,先停止
22
- const pidPath = pidFilePath(projectRoot);
23
- const status = checkDaemon(pidPath);
24
- if (status.running) {
25
- if (!json) {
26
- console.log(`守护进程运行中(PID: ${status.pid}),正在停止...`);
27
- }
28
- try {
29
- process.kill(status.pid, 'SIGTERM');
30
- // 等待最多 5
31
- const start = Date.now();
32
- while (Date.now() - start < 5000) {
25
+ // 1. 停止所有运行中的 daemon
26
+ const registry = loadRegistry();
27
+ for (const entry of registry.slots) {
28
+ if (entry && isDaemonAlive(entry.pid)) {
29
+ if (!json) {
30
+ console.log(`守护进程运行中(PID: ${entry.pid},实例: ${entry.instance}),正在停止...`);
31
+ }
32
+ try {
33
+ process.kill(entry.pid, 'SIGTERM');
34
+ const start = Date.now();
35
+ while (Date.now() - start < 5000) {
36
+ try {
37
+ process.kill(entry.pid, 0);
38
+ await new Promise(r => setTimeout(r, 200));
39
+ }
40
+ catch {
41
+ break;
42
+ }
43
+ }
33
44
  try {
34
- process.kill(status.pid, 0);
35
- await new Promise(r => setTimeout(r, 200));
45
+ process.kill(entry.pid, 0);
46
+ process.kill(entry.pid, 'SIGKILL');
36
47
  }
37
48
  catch {
38
- break; // 进程已退出
49
+ // 已退出
39
50
  }
40
51
  }
41
- // 如果还没退出,强制终止
42
- try {
43
- process.kill(status.pid, 0);
44
- process.kill(status.pid, 'SIGKILL');
45
- }
46
52
  catch {
47
- // 已退出
53
+ // 进程可能已退出
48
54
  }
49
55
  }
50
- catch {
51
- // 进程可能已退出
52
- }
53
56
  }
54
- // 2. 清理项目根下的文件
55
- const projectFiles = [
56
- pidFilePath(projectRoot),
57
- logFilePath(projectRoot),
58
- configFilePath(projectRoot),
59
- ];
60
- for (const filePath of projectFiles) {
61
- if (fs.existsSync(filePath)) {
62
- try {
63
- fs.unlinkSync(filePath);
64
- removed.push(filePath);
65
- if (!json) {
66
- console.log(` 已删除: ${filePath}`);
57
+ // 2. 清理 ~/.remnote-bridge/instances/ 下的 PID 和日志文件
58
+ const instancesDir = path.join(GLOBAL_DIR, 'instances');
59
+ if (fs.existsSync(instancesDir)) {
60
+ for (let i = 0; i < MAX_SLOTS; i++) {
61
+ for (const ext of ['.pid', '.log']) {
62
+ const filePath = path.join(instancesDir, `${i}${ext}`);
63
+ if (fs.existsSync(filePath)) {
64
+ try {
65
+ fs.unlinkSync(filePath);
66
+ removed.push(filePath);
67
+ if (!json)
68
+ console.log(` 已删除: ${filePath}`);
69
+ }
70
+ catch (err) {
71
+ const msg = err instanceof Error ? err.message : String(err);
72
+ errors.push(`${filePath}: ${msg}`);
73
+ if (!json)
74
+ console.error(` 删除失败: ${filePath} — ${msg}`);
75
+ }
67
76
  }
68
77
  }
69
- catch (err) {
70
- errors.push(`${filePath}: ${err.message}`);
71
- if (!json) {
72
- console.error(` 删除失败: ${filePath} ${err.message}`);
78
+ }
79
+ }
80
+ // 3. 清理 registry.json
81
+ const registryPath = path.join(GLOBAL_DIR, 'registry.json');
82
+ if (fs.existsSync(registryPath)) {
83
+ try {
84
+ fs.unlinkSync(registryPath);
85
+ removed.push(registryPath);
86
+ if (!json)
87
+ console.log(` 已删除: ${registryPath}`);
88
+ }
89
+ catch (err) {
90
+ const msg = err instanceof Error ? err.message : String(err);
91
+ errors.push(`${registryPath}: ${msg}`);
92
+ if (!json)
93
+ console.error(` 删除失败: ${registryPath} — ${msg}`);
94
+ }
95
+ }
96
+ // 4. 清理旧版项目根文件(向后兼容)
97
+ try {
98
+ const { findProjectRoot } = await import('../config.js');
99
+ const projectRoot = findProjectRoot();
100
+ const legacyFiles = [
101
+ path.join(projectRoot, '.remnote-bridge.pid'),
102
+ path.join(projectRoot, '.remnote-bridge.log'),
103
+ path.join(projectRoot, '.remnote-bridge.json'),
104
+ ];
105
+ for (const filePath of legacyFiles) {
106
+ if (fs.existsSync(filePath)) {
107
+ try {
108
+ fs.unlinkSync(filePath);
109
+ removed.push(filePath);
110
+ if (!json)
111
+ console.log(` 已删除(旧版): ${filePath}`);
112
+ }
113
+ catch (err) {
114
+ const msg = err instanceof Error ? err.message : String(err);
115
+ errors.push(`${filePath}: ${msg}`);
116
+ if (!json)
117
+ console.error(` 删除失败: ${filePath} — ${msg}`);
73
118
  }
74
119
  }
75
120
  }
76
121
  }
77
- // 3. 清理 ~/.claude/skills/remnote-bridge/
122
+ catch {
123
+ // findProjectRoot 失败不阻塞
124
+ }
125
+ // 5. 清理 ~/.claude/skills/remnote-bridge/
78
126
  const skillDir = path.join(os.homedir(), '.claude', 'skills', 'remnote-bridge');
79
127
  if (fs.existsSync(skillDir)) {
80
128
  try {
81
129
  fs.rmSync(skillDir, { recursive: true, force: true });
82
130
  removed.push(skillDir);
83
- if (!json) {
131
+ if (!json)
84
132
  console.log(` 已删除: ${skillDir}`);
85
- }
86
133
  }
87
134
  catch (err) {
88
- errors.push(`${skillDir}: ${err.message}`);
89
- if (!json) {
90
- console.error(` 删除失败: ${skillDir} — ${err.message}`);
135
+ const msg = err instanceof Error ? err.message : String(err);
136
+ errors.push(`${skillDir}: ${msg}`);
137
+ if (!json)
138
+ console.error(` 删除失败: ${skillDir} — ${msg}`);
139
+ }
140
+ }
141
+ // 6. 清理 addon 数据目录
142
+ for (const [, def] of ADDON_REGISTRY) {
143
+ if (!def.dataDirs)
144
+ continue;
145
+ for (const dir of def.dataDirs) {
146
+ const resolved = dir.replace(/^~/, os.homedir());
147
+ if (fs.existsSync(resolved)) {
148
+ try {
149
+ fs.rmSync(resolved, { recursive: true, force: true });
150
+ removed.push(resolved);
151
+ if (!json)
152
+ console.log(` 已删除: ${resolved}`);
153
+ }
154
+ catch (err) {
155
+ const msg = err instanceof Error ? err.message : String(err);
156
+ errors.push(`${resolved}: ${msg}`);
157
+ if (!json)
158
+ console.error(` 删除失败: ${resolved} — ${msg}`);
159
+ }
91
160
  }
92
161
  }
93
162
  }
94
- // 4. 输出结果
163
+ // 7. 输出结果
95
164
  if (json) {
96
165
  jsonOutput({
97
166
  ok: errors.length === 0,