remnote-bridge 0.1.3 → 0.1.5
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.
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* clean 命令
|
|
3
|
+
*
|
|
4
|
+
* 清理 remnote-bridge 在本机产生的所有残留文件:
|
|
5
|
+
* - 项目根:.remnote-bridge.pid / .remnote-bridge.log / .remnote-bridge.json
|
|
6
|
+
* - 用户目录:~/.claude/skills/remnote-bridge/
|
|
7
|
+
*
|
|
8
|
+
* 如果守护进程仍在运行,先停止再清理。
|
|
9
|
+
*/
|
|
10
|
+
import fs from 'fs';
|
|
11
|
+
import path from 'path';
|
|
12
|
+
import os from 'os';
|
|
13
|
+
import { findProjectRoot, pidFilePath, logFilePath, configFilePath } from '../config.js';
|
|
14
|
+
import { checkDaemon } from '../daemon/pid.js';
|
|
15
|
+
import { jsonOutput } from '../utils/output.js';
|
|
16
|
+
export async function cleanCommand(options = {}) {
|
|
17
|
+
const { json } = options;
|
|
18
|
+
const projectRoot = findProjectRoot();
|
|
19
|
+
const removed = [];
|
|
20
|
+
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) {
|
|
33
|
+
try {
|
|
34
|
+
process.kill(status.pid, 0);
|
|
35
|
+
await new Promise(r => setTimeout(r, 200));
|
|
36
|
+
}
|
|
37
|
+
catch {
|
|
38
|
+
break; // 进程已退出
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
// 如果还没退出,强制终止
|
|
42
|
+
try {
|
|
43
|
+
process.kill(status.pid, 0);
|
|
44
|
+
process.kill(status.pid, 'SIGKILL');
|
|
45
|
+
}
|
|
46
|
+
catch {
|
|
47
|
+
// 已退出
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
catch {
|
|
51
|
+
// 进程可能已退出
|
|
52
|
+
}
|
|
53
|
+
}
|
|
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}`);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
catch (err) {
|
|
70
|
+
errors.push(`${filePath}: ${err.message}`);
|
|
71
|
+
if (!json) {
|
|
72
|
+
console.error(` 删除失败: ${filePath} — ${err.message}`);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
// 3. 清理 ~/.claude/skills/remnote-bridge/
|
|
78
|
+
const skillDir = path.join(os.homedir(), '.claude', 'skills', 'remnote-bridge');
|
|
79
|
+
if (fs.existsSync(skillDir)) {
|
|
80
|
+
try {
|
|
81
|
+
fs.rmSync(skillDir, { recursive: true, force: true });
|
|
82
|
+
removed.push(skillDir);
|
|
83
|
+
if (!json) {
|
|
84
|
+
console.log(` 已删除: ${skillDir}`);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
catch (err) {
|
|
88
|
+
errors.push(`${skillDir}: ${err.message}`);
|
|
89
|
+
if (!json) {
|
|
90
|
+
console.error(` 删除失败: ${skillDir} — ${err.message}`);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
// 4. 输出结果
|
|
95
|
+
if (json) {
|
|
96
|
+
jsonOutput({
|
|
97
|
+
ok: errors.length === 0,
|
|
98
|
+
command: 'clean',
|
|
99
|
+
removed,
|
|
100
|
+
...(errors.length > 0 ? { errors } : {}),
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
else {
|
|
104
|
+
if (removed.length === 0 && errors.length === 0) {
|
|
105
|
+
console.log('没有发现需要清理的文件');
|
|
106
|
+
}
|
|
107
|
+
else if (errors.length === 0) {
|
|
108
|
+
console.log(`\n清理完成,共删除 ${removed.length} 项`);
|
|
109
|
+
}
|
|
110
|
+
else {
|
|
111
|
+
console.log(`\n清理完成,删除 ${removed.length} 项,${errors.length} 项失败`);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
}
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
* webpack-dev-server 子进程管理
|
|
3
3
|
*
|
|
4
4
|
* 在 remnote-plugin 目录下启动 npm run dev。
|
|
5
|
+
* 具备崩溃重试和依赖自动修复能力。
|
|
5
6
|
*/
|
|
6
7
|
import { spawn, execSync } from 'child_process';
|
|
7
8
|
import path from 'path';
|
|
@@ -9,29 +10,60 @@ import fs from 'fs';
|
|
|
9
10
|
export class DevServerManager {
|
|
10
11
|
child = null;
|
|
11
12
|
options;
|
|
13
|
+
retryCount = 0;
|
|
14
|
+
maxRetries;
|
|
15
|
+
stopping = false;
|
|
12
16
|
constructor(options) {
|
|
13
17
|
this.options = options;
|
|
18
|
+
this.maxRetries = options.maxRetries ?? 2;
|
|
14
19
|
}
|
|
15
20
|
/**
|
|
16
21
|
* 启动 webpack-dev-server。
|
|
17
22
|
* 如果 remnote-plugin 目录不存在,抛出错误。
|
|
18
23
|
*/
|
|
19
24
|
start() {
|
|
20
|
-
const { pluginDir, port, onLog
|
|
25
|
+
const { pluginDir, port, onLog } = this.options;
|
|
21
26
|
if (!fs.existsSync(path.join(pluginDir, 'package.json'))) {
|
|
22
27
|
throw new Error(`Plugin 目录不存在或缺少 package.json: ${pluginDir}`);
|
|
23
28
|
}
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
29
|
+
this.ensureDependencies(false);
|
|
30
|
+
this.spawnDevServer();
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* 确保依赖完整。
|
|
34
|
+
* @param cleanInstall true = 删除 node_modules 后重装(修复损坏)
|
|
35
|
+
*/
|
|
36
|
+
ensureDependencies(cleanInstall) {
|
|
37
|
+
const { pluginDir, onLog } = this.options;
|
|
38
|
+
const nodeModulesDir = path.join(pluginDir, 'node_modules');
|
|
39
|
+
const hasCompleteDeps = fs.existsSync(nodeModulesDir) &&
|
|
40
|
+
fs.existsSync(path.join(nodeModulesDir, '.package-lock.json'));
|
|
41
|
+
if (cleanInstall && fs.existsSync(nodeModulesDir)) {
|
|
42
|
+
onLog?.('[dev-server] 检测到依赖损坏,正在清洁重装...', 'warn');
|
|
43
|
+
// 删除 node_modules 和 package-lock.json 以彻底修复
|
|
44
|
+
fs.rmSync(nodeModulesDir, { recursive: true, force: true });
|
|
45
|
+
const lockFile = path.join(pluginDir, 'package-lock.json');
|
|
46
|
+
if (fs.existsSync(lockFile)) {
|
|
47
|
+
fs.unlinkSync(lockFile);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
if (cleanInstall || !hasCompleteDeps) {
|
|
51
|
+
onLog?.('[dev-server] remnote-plugin 依赖缺失或不完整,正在安装...', 'info');
|
|
27
52
|
execSync('npm install', { cwd: pluginDir, stdio: 'pipe' });
|
|
28
53
|
onLog?.('[dev-server] 依赖安装完成', 'info');
|
|
29
54
|
}
|
|
30
|
-
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* 启动 dev-server 子进程并挂载事件监听。
|
|
58
|
+
*/
|
|
59
|
+
spawnDevServer() {
|
|
60
|
+
const { pluginDir, port, onLog, onExit } = this.options;
|
|
61
|
+
// shell: true 确保 Windows 上能找到 npm.cmd
|
|
31
62
|
this.child = spawn('npm', ['run', 'dev'], {
|
|
32
63
|
cwd: pluginDir,
|
|
33
64
|
env: { ...process.env, PORT: String(port) },
|
|
34
65
|
stdio: 'pipe',
|
|
66
|
+
shell: true,
|
|
35
67
|
});
|
|
36
68
|
this.child.stdout?.on('data', (data) => {
|
|
37
69
|
onLog?.(`[dev-server] ${data.toString().trim()}`, 'info');
|
|
@@ -40,9 +72,35 @@ export class DevServerManager {
|
|
|
40
72
|
onLog?.(`[dev-server] ${data.toString().trim()}`, 'warn');
|
|
41
73
|
});
|
|
42
74
|
this.child.on('exit', (code) => {
|
|
43
|
-
onLog?.(`webpack-dev-server 退出 (code: ${code})`, code === 0 ? 'info' : 'error');
|
|
44
75
|
this.child = null;
|
|
45
|
-
|
|
76
|
+
// 正常退出或正在停止中,不重试
|
|
77
|
+
if (code === 0 || this.stopping) {
|
|
78
|
+
onLog?.(`webpack-dev-server 退出 (code: ${code})`, 'info');
|
|
79
|
+
onExit?.(code);
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
82
|
+
onLog?.(`webpack-dev-server 异常退出 (code: ${code})`, 'error');
|
|
83
|
+
// 尝试重试
|
|
84
|
+
if (this.retryCount < this.maxRetries) {
|
|
85
|
+
this.retryCount++;
|
|
86
|
+
const isCleanRetry = this.retryCount === 1;
|
|
87
|
+
onLog?.(`[dev-server] 第 ${this.retryCount}/${this.maxRetries} 次重试` +
|
|
88
|
+
(isCleanRetry ? '(清洁重装依赖)' : '') + '...', 'warn');
|
|
89
|
+
try {
|
|
90
|
+
// 第一次重试:清洁重装依赖(修复损坏的 node_modules)
|
|
91
|
+
// 后续重试:直接重启
|
|
92
|
+
this.ensureDependencies(isCleanRetry);
|
|
93
|
+
this.spawnDevServer();
|
|
94
|
+
}
|
|
95
|
+
catch (err) {
|
|
96
|
+
onLog?.(`[dev-server] 重试失败: ${err.message}`, 'error');
|
|
97
|
+
onExit?.(code);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
else {
|
|
101
|
+
onLog?.(`[dev-server] 已达最大重试次数 (${this.maxRetries}),放弃`, 'error');
|
|
102
|
+
onExit?.(code);
|
|
103
|
+
}
|
|
46
104
|
});
|
|
47
105
|
this.child.on('error', (err) => {
|
|
48
106
|
onLog?.(`webpack-dev-server 启动失败: ${err.message}`, 'error');
|
|
@@ -54,6 +112,7 @@ export class DevServerManager {
|
|
|
54
112
|
* 停止 webpack-dev-server。
|
|
55
113
|
*/
|
|
56
114
|
stop() {
|
|
115
|
+
this.stopping = true;
|
|
57
116
|
return new Promise((resolve) => {
|
|
58
117
|
if (!this.child) {
|
|
59
118
|
resolve();
|
package/dist/cli/main.js
CHANGED
|
@@ -16,6 +16,7 @@ import { readGlobeCommand } from './commands/read-globe.js';
|
|
|
16
16
|
import { readContextCommand } from './commands/read-context.js';
|
|
17
17
|
import { searchCommand } from './commands/search.js';
|
|
18
18
|
import { installSkillCommand, installSkillCopyCommand } from './commands/install-skill.js';
|
|
19
|
+
import { cleanCommand } from './commands/clean.js';
|
|
19
20
|
const program = new Command();
|
|
20
21
|
/**
|
|
21
22
|
* --json 模式下解析 JSON 输入参数。
|
|
@@ -53,7 +54,7 @@ function parseJsonInput(command, jsonStr, requiredFields = []) {
|
|
|
53
54
|
program
|
|
54
55
|
.name('remnote-bridge')
|
|
55
56
|
.description('RemNote Bridge — CLI + MCP Server + Plugin')
|
|
56
|
-
.version('0.1.
|
|
57
|
+
.version('0.1.5')
|
|
57
58
|
.option('--json', '以 JSON 格式输出(适用于程序化调用)');
|
|
58
59
|
program
|
|
59
60
|
.command('connect')
|
|
@@ -305,4 +306,11 @@ installCmd.command('skill')
|
|
|
305
306
|
await installSkillCommand();
|
|
306
307
|
}
|
|
307
308
|
});
|
|
309
|
+
program
|
|
310
|
+
.command('clean')
|
|
311
|
+
.description('清理所有残留文件(.pid / .log / .json / skill 目录)')
|
|
312
|
+
.action(async () => {
|
|
313
|
+
const { json } = program.opts();
|
|
314
|
+
await cleanCommand({ json });
|
|
315
|
+
});
|
|
308
316
|
program.parse();
|
package/dist/mcp/instructions.js
CHANGED
|
@@ -130,10 +130,18 @@ disconnect → 关闭 daemon,清空所有缓存
|
|
|
130
130
|
|
|
131
131
|
**注意**:中文等无空格语言搜索效果较差。如果完整词搜索无结果,尝试用单个最具区分度的字搜索;如果仍然无果,改用 \\\`read_globe\\\` → \\\`read_tree\\\` 手动定位。
|
|
132
132
|
|
|
133
|
-
### 场景 C
|
|
133
|
+
### 场景 C:了解当前上下文(⚠️ 高优先级)
|
|
134
134
|
|
|
135
135
|
> 用户说:"我现在在看什么"、"当前页面是什么"
|
|
136
136
|
|
|
137
|
+
**重要:用户正在看的页面对你来说是不可见的。** 用户在跟你沟通时,往往默认你也能看到他正在浏览的 RemNote 页面,但实际上你们的信息是不对等的。当你发现以下情况时,**必须主动调用 \\\`read_context\\\` 来对齐信息**:
|
|
138
|
+
- 用户提到了你没有上下文的内容(如"这个"、"当前页面"、"这里")
|
|
139
|
+
- 用户的描述与你已知的信息对不上
|
|
140
|
+
- 你搜索不到用户提到的某些内容
|
|
141
|
+
- 用户似乎在引用他正在查看的界面
|
|
142
|
+
|
|
143
|
+
先用 \\\`read_context\\\` 看到用户所看到的,再做决策,沟通才能顺畅。
|
|
144
|
+
|
|
137
145
|
使用 \\\`read_context\\\`:
|
|
138
146
|
- **focus 模式**(默认):以用户当前光标所在的 Rem 为中心,构建鱼眼视图——焦点处完全展开,周围递减。焦点行以 \\\`* \\\` 前缀标记。需要用户在 RemNote 中已点击某个 Rem。
|
|
139
147
|
- **page 模式**:以当前打开的页面为根,均匀展开子树。
|
package/package.json
CHANGED
|
@@ -191,6 +191,16 @@ RemNote 格式设置(fontSize、highlightColor 等)底层通过 Powerup 机
|
|
|
191
191
|
|
|
192
192
|
## 2. 命令决策
|
|
193
193
|
|
|
194
|
+
### ⚠️ 用户正在看的页面对你不可见
|
|
195
|
+
|
|
196
|
+
用户在跟你沟通时,往往默认你也能看到他正在浏览的 RemNote 页面,但实际上你们的信息是不对等的。当你发现以下情况时,**必须主动执行 `read-context` 来对齐信息**:
|
|
197
|
+
- 用户提到了你没有上下文的内容(如"这个"、"当前页面"、"这里")
|
|
198
|
+
- 用户的描述与你已知的信息对不上
|
|
199
|
+
- 你搜索不到用户提到的某些内容
|
|
200
|
+
- 用户似乎在引用他正在查看的界面
|
|
201
|
+
|
|
202
|
+
先用 `read-context` 看到用户所看到的,再做决策,沟通才能顺畅。
|
|
203
|
+
|
|
194
204
|
### 读取:用户想了解什么?
|
|
195
205
|
|
|
196
206
|
```
|