remnote-bridge 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/cli/commands/connect.d.ts +12 -0
- package/dist/cli/commands/connect.js +124 -0
- package/dist/cli/commands/disconnect.d.ts +11 -0
- package/dist/cli/commands/disconnect.js +100 -0
- package/dist/cli/commands/edit-rem.d.ts +13 -0
- package/dist/cli/commands/edit-rem.js +83 -0
- package/dist/cli/commands/edit-tree.d.ts +14 -0
- package/dist/cli/commands/edit-tree.js +67 -0
- package/dist/cli/commands/health.d.ts +12 -0
- package/dist/cli/commands/health.js +100 -0
- package/dist/cli/commands/install-skill.d.ts +6 -0
- package/dist/cli/commands/install-skill.js +39 -0
- package/dist/cli/commands/read-context.d.ts +20 -0
- package/dist/cli/commands/read-context.js +77 -0
- package/dist/cli/commands/read-globe.d.ts +16 -0
- package/dist/cli/commands/read-globe.js +60 -0
- package/dist/cli/commands/read-rem.d.ts +16 -0
- package/dist/cli/commands/read-rem.js +80 -0
- package/dist/cli/commands/read-tree.d.ts +17 -0
- package/dist/cli/commands/read-tree.js +85 -0
- package/dist/cli/commands/search.d.ts +12 -0
- package/dist/cli/commands/search.js +65 -0
- package/dist/cli/config.d.ts +55 -0
- package/dist/cli/config.js +139 -0
- package/dist/cli/daemon/daemon.d.ts +11 -0
- package/dist/cli/daemon/daemon.js +186 -0
- package/dist/cli/daemon/dev-server.d.ts +26 -0
- package/dist/cli/daemon/dev-server.js +81 -0
- package/dist/cli/daemon/pid.d.ts +34 -0
- package/dist/cli/daemon/pid.js +67 -0
- package/dist/cli/daemon/send-request.d.ts +24 -0
- package/dist/cli/daemon/send-request.js +92 -0
- package/dist/cli/handlers/context-read-handler.d.ts +18 -0
- package/dist/cli/handlers/context-read-handler.js +24 -0
- package/dist/cli/handlers/edit-handler.d.ts +30 -0
- package/dist/cli/handlers/edit-handler.js +133 -0
- package/dist/cli/handlers/globe-read-handler.d.ts +17 -0
- package/dist/cli/handlers/globe-read-handler.js +23 -0
- package/dist/cli/handlers/read-handler.d.ts +16 -0
- package/dist/cli/handlers/read-handler.js +78 -0
- package/dist/cli/handlers/rem-cache.d.ts +19 -0
- package/dist/cli/handlers/rem-cache.js +63 -0
- package/dist/cli/handlers/tree-edit-handler.d.ts +30 -0
- package/dist/cli/handlers/tree-edit-handler.js +188 -0
- package/dist/cli/handlers/tree-parser.d.ts +95 -0
- package/dist/cli/handlers/tree-parser.js +506 -0
- package/dist/cli/handlers/tree-read-handler.d.ts +28 -0
- package/dist/cli/handlers/tree-read-handler.js +53 -0
- package/dist/cli/main.d.ts +7 -0
- package/dist/cli/main.js +300 -0
- package/dist/cli/protocol.d.ts +39 -0
- package/dist/cli/protocol.js +35 -0
- package/dist/cli/server/config-server.d.ts +26 -0
- package/dist/cli/server/config-server.js +363 -0
- package/dist/cli/server/ws-server.d.ts +68 -0
- package/dist/cli/server/ws-server.js +335 -0
- package/dist/cli/utils/output.d.ts +11 -0
- package/dist/cli/utils/output.js +13 -0
- package/dist/mcp/daemon-client.d.ts +31 -0
- package/dist/mcp/daemon-client.js +99 -0
- package/dist/mcp/index.d.ts +7 -0
- package/dist/mcp/index.js +68 -0
- package/dist/mcp/instructions.d.ts +1 -0
- package/dist/mcp/instructions.js +249 -0
- package/dist/mcp/resources/edit-tree-guide.d.ts +1 -0
- package/dist/mcp/resources/edit-tree-guide.js +197 -0
- package/dist/mcp/resources/error-reference.d.ts +1 -0
- package/dist/mcp/resources/error-reference.js +132 -0
- package/dist/mcp/resources/outline-format.d.ts +1 -0
- package/dist/mcp/resources/outline-format.js +104 -0
- package/dist/mcp/resources/rem-object-fields.d.ts +1 -0
- package/dist/mcp/resources/rem-object-fields.js +331 -0
- package/dist/mcp/resources/separator-flashcard.d.ts +1 -0
- package/dist/mcp/resources/separator-flashcard.js +120 -0
- package/dist/mcp/tools/edit-tools.d.ts +5 -0
- package/dist/mcp/tools/edit-tools.js +47 -0
- package/dist/mcp/tools/infra-tools.d.ts +5 -0
- package/dist/mcp/tools/infra-tools.js +43 -0
- package/dist/mcp/tools/read-tools.d.ts +5 -0
- package/dist/mcp/tools/read-tools.js +195 -0
- package/dist/mcp/types.d.ts +12 -0
- package/dist/mcp/types.js +4 -0
- package/docs/instruction/connect.md +158 -0
- package/docs/instruction/disconnect.md +146 -0
- package/docs/instruction/edit-rem.md +509 -0
- package/docs/instruction/edit-tree.md +419 -0
- package/docs/instruction/health.md +159 -0
- package/docs/instruction/overall.md +751 -0
- package/docs/instruction/read-context.md +353 -0
- package/docs/instruction/read-globe.md +206 -0
- package/docs/instruction/read-rem.md +476 -0
- package/docs/instruction/read-tree.md +428 -0
- package/docs/instruction/search.md +196 -0
- package/package.json +41 -0
- package/remnote-plugin/package.json +48 -0
- package/remnote-plugin/postcss.config.js +5 -0
- package/remnote-plugin/public/bridge-icon.svg +8 -0
- package/remnote-plugin/public/manifest.json +22 -0
- package/remnote-plugin/src/bridge/message-router.ts +57 -0
- package/remnote-plugin/src/bridge/websocket-client.ts +245 -0
- package/remnote-plugin/src/index.css +1 -0
- package/remnote-plugin/src/services/breadcrumb.ts +26 -0
- package/remnote-plugin/src/services/create-rem.ts +59 -0
- package/remnote-plugin/src/services/delete-rem.ts +29 -0
- package/remnote-plugin/src/services/index.ts +16 -0
- package/remnote-plugin/src/services/move-rem.ts +39 -0
- package/remnote-plugin/src/services/powerup-filter.ts +31 -0
- package/remnote-plugin/src/services/read-context.ts +368 -0
- package/remnote-plugin/src/services/read-globe.ts +197 -0
- package/remnote-plugin/src/services/read-rem.ts +284 -0
- package/remnote-plugin/src/services/read-tree.ts +222 -0
- package/remnote-plugin/src/services/rem-builder.ts +124 -0
- package/remnote-plugin/src/services/reorder-children.ts +61 -0
- package/remnote-plugin/src/services/search.ts +56 -0
- package/remnote-plugin/src/services/write-rem-fields.ts +254 -0
- package/remnote-plugin/src/settings.ts +12 -0
- package/remnote-plugin/src/style.css +45 -0
- package/remnote-plugin/src/test-scripts/AGENTS.md +46 -0
- package/remnote-plugin/src/test-scripts/test-actions.ts +230 -0
- package/remnote-plugin/src/test-scripts/test-powerup-rendering.ts +722 -0
- package/remnote-plugin/src/test-scripts/test-rem-type-mapping.ts +283 -0
- package/remnote-plugin/src/test-scripts/test-richtext-builder.ts +207 -0
- package/remnote-plugin/src/test-scripts/test-richtext-matrix.ts +332 -0
- package/remnote-plugin/src/test-scripts/test-richtext-remaining.ts +245 -0
- package/remnote-plugin/src/test-scripts/test-rw-fields.ts +399 -0
- package/remnote-plugin/src/types.ts +419 -0
- package/remnote-plugin/src/utils/elision.ts +45 -0
- package/remnote-plugin/src/utils/index.ts +10 -0
- package/remnote-plugin/src/utils/tree-serializer.ts +269 -0
- package/remnote-plugin/src/widgets/bridge_widget.tsx +170 -0
- package/remnote-plugin/src/widgets/index.tsx +82 -0
- package/remnote-plugin/tailwind.config.js +7 -0
- package/remnote-plugin/tsconfig.json +21 -0
- package/remnote-plugin/webpack.config.js +125 -0
- package/skill/SKILL.md +428 -0
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* connect 命令
|
|
3
|
+
*
|
|
4
|
+
* 启动后台守护进程(WS Server + webpack-dev-server),等待 Plugin 连接。
|
|
5
|
+
* - 已在运行 → 打印提示,退出码 0
|
|
6
|
+
* - stale PID → 清理后正常启动
|
|
7
|
+
* - 启动失败 → 退出码 1
|
|
8
|
+
*/
|
|
9
|
+
export interface ConnectOptions {
|
|
10
|
+
json?: boolean;
|
|
11
|
+
}
|
|
12
|
+
export declare function connectCommand(options?: ConnectOptions): Promise<void>;
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* connect 命令
|
|
3
|
+
*
|
|
4
|
+
* 启动后台守护进程(WS Server + webpack-dev-server),等待 Plugin 连接。
|
|
5
|
+
* - 已在运行 → 打印提示,退出码 0
|
|
6
|
+
* - stale PID → 清理后正常启动
|
|
7
|
+
* - 启动失败 → 退出码 1
|
|
8
|
+
*/
|
|
9
|
+
import path from 'path';
|
|
10
|
+
import fs from 'fs';
|
|
11
|
+
import { fork } from 'child_process';
|
|
12
|
+
import { loadConfig, pidFilePath, findProjectRoot } from '../config.js';
|
|
13
|
+
import { checkDaemon } from '../daemon/pid.js';
|
|
14
|
+
import { jsonOutput } from '../utils/output.js';
|
|
15
|
+
function isDaemonMessage(msg) {
|
|
16
|
+
return (typeof msg === 'object' &&
|
|
17
|
+
msg !== null &&
|
|
18
|
+
typeof msg.type === 'string');
|
|
19
|
+
}
|
|
20
|
+
export async function connectCommand(options = {}) {
|
|
21
|
+
const { json } = options;
|
|
22
|
+
const projectRoot = findProjectRoot();
|
|
23
|
+
const config = loadConfig(projectRoot);
|
|
24
|
+
const pidPath = pidFilePath(projectRoot);
|
|
25
|
+
// 检查是否已在运行
|
|
26
|
+
const status = checkDaemon(pidPath);
|
|
27
|
+
if (status.running) {
|
|
28
|
+
if (json) {
|
|
29
|
+
jsonOutput({
|
|
30
|
+
ok: true, command: 'connect', alreadyRunning: true,
|
|
31
|
+
pid: status.pid, wsPort: config.wsPort, devServerPort: config.devServerPort,
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
else {
|
|
35
|
+
console.log(`守护进程已在运行(PID: ${status.pid})`);
|
|
36
|
+
}
|
|
37
|
+
process.exitCode = 0;
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
// fork 守护进程
|
|
41
|
+
const daemonScriptJs = path.resolve(import.meta.dirname, '..', 'daemon', 'daemon.js');
|
|
42
|
+
const daemonScriptTs = path.resolve(import.meta.dirname, '..', 'daemon', 'daemon.ts');
|
|
43
|
+
let scriptPath;
|
|
44
|
+
let execArgv = [];
|
|
45
|
+
// 判断是 ts 开发模式还是 js 构建后模式
|
|
46
|
+
if (fs.existsSync(daemonScriptJs)) {
|
|
47
|
+
scriptPath = daemonScriptJs;
|
|
48
|
+
}
|
|
49
|
+
else {
|
|
50
|
+
scriptPath = daemonScriptTs;
|
|
51
|
+
// 继承父进程的 tsx loader 参数(排除 --eval 相关项)
|
|
52
|
+
execArgv = process.execArgv.filter((arg) => !arg.startsWith('--eval') && !arg.includes('const '));
|
|
53
|
+
}
|
|
54
|
+
if (!json) {
|
|
55
|
+
console.log('正在启动守护进程...');
|
|
56
|
+
}
|
|
57
|
+
const child = fork(scriptPath, [], {
|
|
58
|
+
detached: true,
|
|
59
|
+
stdio: ['ignore', 'ignore', 'ignore', 'ipc'],
|
|
60
|
+
cwd: projectRoot,
|
|
61
|
+
execArgv,
|
|
62
|
+
});
|
|
63
|
+
// 等待就绪信号,超时 10 秒
|
|
64
|
+
const ready = await new Promise((resolve) => {
|
|
65
|
+
const timeout = setTimeout(() => {
|
|
66
|
+
resolve(null);
|
|
67
|
+
}, 10_000);
|
|
68
|
+
child.on('message', (msg) => {
|
|
69
|
+
if (isDaemonMessage(msg)) {
|
|
70
|
+
clearTimeout(timeout);
|
|
71
|
+
resolve(msg);
|
|
72
|
+
}
|
|
73
|
+
});
|
|
74
|
+
child.on('error', (err) => {
|
|
75
|
+
clearTimeout(timeout);
|
|
76
|
+
resolve({ type: 'error', message: String(err) });
|
|
77
|
+
});
|
|
78
|
+
child.on('exit', (code) => {
|
|
79
|
+
clearTimeout(timeout);
|
|
80
|
+
if (code !== null && code !== 0) {
|
|
81
|
+
resolve({ type: 'error', message: `守护进程异常退出(退出码 ${code})` });
|
|
82
|
+
}
|
|
83
|
+
});
|
|
84
|
+
});
|
|
85
|
+
// 断开与子进程的连接(让 CLI 进程可以退出)
|
|
86
|
+
child.unref();
|
|
87
|
+
child.disconnect?.();
|
|
88
|
+
if (!ready) {
|
|
89
|
+
if (json) {
|
|
90
|
+
jsonOutput({ ok: false, command: 'connect', error: '守护进程启动超时(10 秒)' });
|
|
91
|
+
}
|
|
92
|
+
else {
|
|
93
|
+
console.error('守护进程启动超时(10 秒)');
|
|
94
|
+
}
|
|
95
|
+
process.exitCode = 1;
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
98
|
+
if (ready.type === 'error') {
|
|
99
|
+
if (json) {
|
|
100
|
+
jsonOutput({ ok: false, command: 'connect', error: ready.message });
|
|
101
|
+
}
|
|
102
|
+
else {
|
|
103
|
+
console.error(`守护进程启动失败: ${ready.message}`);
|
|
104
|
+
}
|
|
105
|
+
process.exitCode = 1;
|
|
106
|
+
return;
|
|
107
|
+
}
|
|
108
|
+
if (json) {
|
|
109
|
+
jsonOutput({
|
|
110
|
+
ok: true, command: 'connect', alreadyRunning: false,
|
|
111
|
+
pid: ready.pid, wsPort: ready.wsPort, devServerPort: ready.devServerPort,
|
|
112
|
+
configPort: ready.configPort,
|
|
113
|
+
timeoutMinutes: config.daemonTimeoutMinutes,
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
else {
|
|
117
|
+
console.log(`守护进程已启动(PID: ${ready.pid})`);
|
|
118
|
+
console.log(` WS Server: ws://127.0.0.1:${ready.wsPort}`);
|
|
119
|
+
console.log(` webpack-dev-server: http://localhost:${ready.devServerPort}`);
|
|
120
|
+
console.log(` 配置页面: http://127.0.0.1:${ready.configPort}`);
|
|
121
|
+
console.log(` 超时: ${config.daemonTimeoutMinutes} 分钟无 CLI 交互后自动关闭`);
|
|
122
|
+
}
|
|
123
|
+
process.exitCode = 0;
|
|
124
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* disconnect 命令
|
|
3
|
+
*
|
|
4
|
+
* 停止守护进程,释放端口和资源。
|
|
5
|
+
* - 守护进程运行中 → 发送 SIGTERM,等待退出
|
|
6
|
+
* - 守护进程未运行 → 打印提示,退出码 0
|
|
7
|
+
*/
|
|
8
|
+
export interface DisconnectOptions {
|
|
9
|
+
json?: boolean;
|
|
10
|
+
}
|
|
11
|
+
export declare function disconnectCommand(options?: DisconnectOptions): Promise<void>;
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* disconnect 命令
|
|
3
|
+
*
|
|
4
|
+
* 停止守护进程,释放端口和资源。
|
|
5
|
+
* - 守护进程运行中 → 发送 SIGTERM,等待退出
|
|
6
|
+
* - 守护进程未运行 → 打印提示,退出码 0
|
|
7
|
+
*/
|
|
8
|
+
import { pidFilePath, findProjectRoot } from '../config.js';
|
|
9
|
+
import { checkDaemon, removePid } from '../daemon/pid.js';
|
|
10
|
+
import { jsonOutput } from '../utils/output.js';
|
|
11
|
+
const WAIT_TIMEOUT_MS = 10_000;
|
|
12
|
+
const POLL_INTERVAL_MS = 200;
|
|
13
|
+
export async function disconnectCommand(options = {}) {
|
|
14
|
+
const { json } = options;
|
|
15
|
+
const projectRoot = findProjectRoot();
|
|
16
|
+
const pidPath = pidFilePath(projectRoot);
|
|
17
|
+
const status = checkDaemon(pidPath);
|
|
18
|
+
if (!status.running) {
|
|
19
|
+
if (json) {
|
|
20
|
+
jsonOutput({ ok: true, command: 'disconnect', wasRunning: false });
|
|
21
|
+
}
|
|
22
|
+
else {
|
|
23
|
+
console.log('守护进程未在运行');
|
|
24
|
+
}
|
|
25
|
+
process.exitCode = 0;
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
const pid = status.pid;
|
|
29
|
+
if (!json) {
|
|
30
|
+
console.log(`正在停止守护进程(PID: ${pid})...`);
|
|
31
|
+
}
|
|
32
|
+
// 发送 SIGTERM
|
|
33
|
+
try {
|
|
34
|
+
process.kill(pid, 'SIGTERM');
|
|
35
|
+
}
|
|
36
|
+
catch (err) {
|
|
37
|
+
// 进程可能已经退出
|
|
38
|
+
removePid(pidPath);
|
|
39
|
+
if (json) {
|
|
40
|
+
jsonOutput({ ok: true, command: 'disconnect', wasRunning: true, pid, forced: false });
|
|
41
|
+
}
|
|
42
|
+
else {
|
|
43
|
+
console.log('守护进程已停止');
|
|
44
|
+
}
|
|
45
|
+
process.exitCode = 0;
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
// 等待进程退出
|
|
49
|
+
const exited = await waitForExit(pid, WAIT_TIMEOUT_MS);
|
|
50
|
+
if (exited) {
|
|
51
|
+
removePid(pidPath);
|
|
52
|
+
if (json) {
|
|
53
|
+
jsonOutput({ ok: true, command: 'disconnect', wasRunning: true, pid, forced: false });
|
|
54
|
+
}
|
|
55
|
+
else {
|
|
56
|
+
console.log('守护进程已停止');
|
|
57
|
+
}
|
|
58
|
+
process.exitCode = 0;
|
|
59
|
+
}
|
|
60
|
+
else {
|
|
61
|
+
try {
|
|
62
|
+
process.kill(pid, 'SIGKILL');
|
|
63
|
+
}
|
|
64
|
+
catch {
|
|
65
|
+
// 可能已退出
|
|
66
|
+
}
|
|
67
|
+
removePid(pidPath);
|
|
68
|
+
if (json) {
|
|
69
|
+
jsonOutput({ ok: true, command: 'disconnect', wasRunning: true, pid, forced: true });
|
|
70
|
+
}
|
|
71
|
+
else {
|
|
72
|
+
console.error(`守护进程未在 ${WAIT_TIMEOUT_MS / 1000} 秒内退出,尝试强制终止...`);
|
|
73
|
+
console.log('守护进程已强制终止');
|
|
74
|
+
}
|
|
75
|
+
process.exitCode = 0;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
function waitForExit(pid, timeoutMs) {
|
|
79
|
+
return new Promise((resolve) => {
|
|
80
|
+
const start = Date.now();
|
|
81
|
+
const check = () => {
|
|
82
|
+
try {
|
|
83
|
+
// kill(pid, 0) 不发信号,只检查进程是否存在
|
|
84
|
+
process.kill(pid, 0);
|
|
85
|
+
// 进程仍在运行
|
|
86
|
+
if (Date.now() - start >= timeoutMs) {
|
|
87
|
+
resolve(false);
|
|
88
|
+
}
|
|
89
|
+
else {
|
|
90
|
+
setTimeout(check, POLL_INTERVAL_MS);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
catch {
|
|
94
|
+
// 进程已退出
|
|
95
|
+
resolve(true);
|
|
96
|
+
}
|
|
97
|
+
};
|
|
98
|
+
check();
|
|
99
|
+
});
|
|
100
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* edit-rem 命令
|
|
3
|
+
*
|
|
4
|
+
* 通过 str_replace 编辑 Rem 的 JSON 序列化。
|
|
5
|
+
* 三道防线保证安全:缓存存在性、并发检测、精确匹配。
|
|
6
|
+
* - 退出码:0 成功 / 1 业务错误 / 2 守护进程不可达
|
|
7
|
+
*/
|
|
8
|
+
export interface EditRemOptions {
|
|
9
|
+
json?: boolean;
|
|
10
|
+
oldStr: string;
|
|
11
|
+
newStr: string;
|
|
12
|
+
}
|
|
13
|
+
export declare function editRemCommand(remId: string, options: EditRemOptions): Promise<void>;
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* edit-rem 命令
|
|
3
|
+
*
|
|
4
|
+
* 通过 str_replace 编辑 Rem 的 JSON 序列化。
|
|
5
|
+
* 三道防线保证安全:缓存存在性、并发检测、精确匹配。
|
|
6
|
+
* - 退出码:0 成功 / 1 业务错误 / 2 守护进程不可达
|
|
7
|
+
*/
|
|
8
|
+
import { sendDaemonRequest, DaemonNotRunningError, DaemonUnreachableError } from '../daemon/send-request.js';
|
|
9
|
+
import { jsonOutput } from '../utils/output.js';
|
|
10
|
+
export async function editRemCommand(remId, options) {
|
|
11
|
+
const { json, oldStr, newStr } = options;
|
|
12
|
+
let result;
|
|
13
|
+
try {
|
|
14
|
+
result = await sendDaemonRequest('edit_rem', { remId, oldStr, newStr });
|
|
15
|
+
}
|
|
16
|
+
catch (err) {
|
|
17
|
+
if (err instanceof DaemonNotRunningError) {
|
|
18
|
+
if (json) {
|
|
19
|
+
jsonOutput({ ok: false, command: 'edit-rem', error: err.message });
|
|
20
|
+
}
|
|
21
|
+
else {
|
|
22
|
+
console.error(`错误: ${err.message}`);
|
|
23
|
+
}
|
|
24
|
+
process.exitCode = 2;
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
27
|
+
if (err instanceof DaemonUnreachableError) {
|
|
28
|
+
if (json) {
|
|
29
|
+
jsonOutput({ ok: false, command: 'edit-rem', error: err.message });
|
|
30
|
+
}
|
|
31
|
+
else {
|
|
32
|
+
console.error(`错误: ${err.message}`);
|
|
33
|
+
}
|
|
34
|
+
process.exitCode = 2;
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
// 业务错误(防线拒绝、Plugin 未连接等)
|
|
38
|
+
const errorMsg = err instanceof Error ? err.message : String(err);
|
|
39
|
+
if (json) {
|
|
40
|
+
jsonOutput({ ok: false, command: 'edit-rem', error: errorMsg });
|
|
41
|
+
}
|
|
42
|
+
else {
|
|
43
|
+
console.error(`错误: ${errorMsg}`);
|
|
44
|
+
}
|
|
45
|
+
process.exitCode = 1;
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
const editResult = result;
|
|
49
|
+
if (json) {
|
|
50
|
+
jsonOutput({
|
|
51
|
+
ok: editResult.ok,
|
|
52
|
+
command: 'edit-rem',
|
|
53
|
+
changes: editResult.changes,
|
|
54
|
+
warnings: editResult.warnings,
|
|
55
|
+
...(editResult.error ? { error: editResult.error } : {}),
|
|
56
|
+
...(editResult.appliedChanges ? { appliedChanges: editResult.appliedChanges } : {}),
|
|
57
|
+
...(editResult.failedField ? { failedField: editResult.failedField } : {}),
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
else {
|
|
61
|
+
if (editResult.ok) {
|
|
62
|
+
if (editResult.changes.length === 0) {
|
|
63
|
+
console.log('无变更(old_str 和 new_str 产生相同结果)');
|
|
64
|
+
}
|
|
65
|
+
else {
|
|
66
|
+
console.log(`已更新字段: ${editResult.changes.join(', ')}`);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
else {
|
|
70
|
+
console.error(`错误: ${editResult.error}`);
|
|
71
|
+
if (editResult.appliedChanges && editResult.appliedChanges.length > 0) {
|
|
72
|
+
console.error(`警告: 部分写入已生效: ${editResult.appliedChanges.join(', ')}`);
|
|
73
|
+
console.error('请重新 read-rem 获取当前状态');
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
for (const warning of editResult.warnings) {
|
|
77
|
+
console.warn(`警告: ${warning}`);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
if (!editResult.ok) {
|
|
81
|
+
process.exitCode = 1;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* edit-tree 命令
|
|
3
|
+
*
|
|
4
|
+
* 通过 str_replace 编辑 Rem 子树结构(行级增/删/移/重排)。
|
|
5
|
+
* - 禁止修改已有行内容(走 edit-rem)
|
|
6
|
+
* - --json 结构化 JSON 输出
|
|
7
|
+
* - 退出码:0 成功 / 1 业务错误 / 2 守护进程不可达
|
|
8
|
+
*/
|
|
9
|
+
export interface EditTreeOptions {
|
|
10
|
+
json?: boolean;
|
|
11
|
+
oldStr: string;
|
|
12
|
+
newStr: string;
|
|
13
|
+
}
|
|
14
|
+
export declare function editTreeCommand(remId: string, options: EditTreeOptions): Promise<void>;
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* edit-tree 命令
|
|
3
|
+
*
|
|
4
|
+
* 通过 str_replace 编辑 Rem 子树结构(行级增/删/移/重排)。
|
|
5
|
+
* - 禁止修改已有行内容(走 edit-rem)
|
|
6
|
+
* - --json 结构化 JSON 输出
|
|
7
|
+
* - 退出码:0 成功 / 1 业务错误 / 2 守护进程不可达
|
|
8
|
+
*/
|
|
9
|
+
import { sendDaemonRequest, DaemonNotRunningError, DaemonUnreachableError } from '../daemon/send-request.js';
|
|
10
|
+
import { jsonOutput } from '../utils/output.js';
|
|
11
|
+
export async function editTreeCommand(remId, options) {
|
|
12
|
+
const { json, oldStr, newStr } = options;
|
|
13
|
+
let result;
|
|
14
|
+
try {
|
|
15
|
+
result = await sendDaemonRequest('edit_tree', { remId, oldStr, newStr });
|
|
16
|
+
}
|
|
17
|
+
catch (err) {
|
|
18
|
+
if (err instanceof DaemonNotRunningError || err instanceof DaemonUnreachableError) {
|
|
19
|
+
if (json) {
|
|
20
|
+
jsonOutput({ ok: false, command: 'edit-tree', error: err.message });
|
|
21
|
+
}
|
|
22
|
+
else {
|
|
23
|
+
console.error(`错误: ${err.message}`);
|
|
24
|
+
}
|
|
25
|
+
process.exitCode = 2;
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
const errorMsg = err instanceof Error ? err.message : String(err);
|
|
29
|
+
if (json) {
|
|
30
|
+
jsonOutput({ ok: false, command: 'edit-tree', error: errorMsg });
|
|
31
|
+
}
|
|
32
|
+
else {
|
|
33
|
+
console.error(`错误: ${errorMsg}`);
|
|
34
|
+
}
|
|
35
|
+
process.exitCode = 1;
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
const data = result;
|
|
39
|
+
if (!data.ok) {
|
|
40
|
+
if (json) {
|
|
41
|
+
jsonOutput({ ok: false, command: 'edit-tree', error: data.error, ...data.details });
|
|
42
|
+
}
|
|
43
|
+
else {
|
|
44
|
+
console.error(`错误: ${data.error}`);
|
|
45
|
+
if (data.details) {
|
|
46
|
+
console.error(JSON.stringify(data.details, null, 2));
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
process.exitCode = 1;
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
if (json) {
|
|
53
|
+
jsonOutput({ ok: true, command: 'edit-tree', operations: data.operations });
|
|
54
|
+
}
|
|
55
|
+
else {
|
|
56
|
+
if (data.operations.length === 0) {
|
|
57
|
+
console.log('无结构变更。');
|
|
58
|
+
}
|
|
59
|
+
else {
|
|
60
|
+
console.log(`执行了 ${data.operations.length} 个操作:`);
|
|
61
|
+
for (const op of data.operations) {
|
|
62
|
+
const o = op;
|
|
63
|
+
console.log(` - ${o.type}: ${JSON.stringify(o)}`);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* health 命令
|
|
3
|
+
*
|
|
4
|
+
* 检查守护进程、Plugin 连接、SDK 状态,输出 ✅/❌ 列表。
|
|
5
|
+
* - 全部健康 → 退出码 0
|
|
6
|
+
* - 部分不健康 → 退出码 1
|
|
7
|
+
* - 守护进程不可达 → 退出码 2
|
|
8
|
+
*/
|
|
9
|
+
export interface HealthOptions {
|
|
10
|
+
json?: boolean;
|
|
11
|
+
}
|
|
12
|
+
export declare function healthCommand(options?: HealthOptions): Promise<void>;
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* health 命令
|
|
3
|
+
*
|
|
4
|
+
* 检查守护进程、Plugin 连接、SDK 状态,输出 ✅/❌ 列表。
|
|
5
|
+
* - 全部健康 → 退出码 0
|
|
6
|
+
* - 部分不健康 → 退出码 1
|
|
7
|
+
* - 守护进程不可达 → 退出码 2
|
|
8
|
+
*/
|
|
9
|
+
import { findProjectRoot, pidFilePath } from '../config.js';
|
|
10
|
+
import { checkDaemon } from '../daemon/pid.js';
|
|
11
|
+
import { sendDaemonRequest } from '../daemon/send-request.js';
|
|
12
|
+
import { jsonOutput } from '../utils/output.js';
|
|
13
|
+
export async function healthCommand(options = {}) {
|
|
14
|
+
const { json } = options;
|
|
15
|
+
const projectRoot = findProjectRoot();
|
|
16
|
+
const pidPath = pidFilePath(projectRoot);
|
|
17
|
+
// 先检查 PID 文件
|
|
18
|
+
const daemonStatus = checkDaemon(pidPath);
|
|
19
|
+
if (!daemonStatus.running) {
|
|
20
|
+
if (json) {
|
|
21
|
+
jsonOutput({
|
|
22
|
+
ok: false, command: 'health', exitCode: 2,
|
|
23
|
+
daemon: { running: false },
|
|
24
|
+
plugin: { connected: false },
|
|
25
|
+
sdk: { ready: false },
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
else {
|
|
29
|
+
console.log('❌ 守护进程 未运行');
|
|
30
|
+
console.log('❌ Plugin 未连接');
|
|
31
|
+
console.log('❌ SDK 不可用');
|
|
32
|
+
console.log('\n提示: 执行 `remnote-bridge connect` 启动守护进程');
|
|
33
|
+
}
|
|
34
|
+
process.exitCode = 2;
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
// 通过 WS 连接守护进程获取状态
|
|
38
|
+
let status;
|
|
39
|
+
try {
|
|
40
|
+
status = await sendDaemonRequest('get_status');
|
|
41
|
+
}
|
|
42
|
+
catch (err) {
|
|
43
|
+
const errorMsg = err instanceof Error ? err.message : String(err);
|
|
44
|
+
if (json) {
|
|
45
|
+
jsonOutput({
|
|
46
|
+
ok: false, command: 'health', exitCode: 2,
|
|
47
|
+
daemon: { running: true, pid: daemonStatus.pid, reachable: false },
|
|
48
|
+
plugin: { connected: false },
|
|
49
|
+
sdk: { ready: false },
|
|
50
|
+
error: errorMsg,
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
else {
|
|
54
|
+
console.log('❌ 守护进程 不可达');
|
|
55
|
+
console.log('❌ Plugin 未连接');
|
|
56
|
+
console.log('❌ SDK 不可用');
|
|
57
|
+
console.log(`\n错误: ${errorMsg}`);
|
|
58
|
+
}
|
|
59
|
+
process.exitCode = 2;
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
// 退出码
|
|
63
|
+
const allHealthy = status.pluginConnected && status.sdkReady;
|
|
64
|
+
const exitCode = allHealthy ? 0 : 1;
|
|
65
|
+
if (json) {
|
|
66
|
+
jsonOutput({
|
|
67
|
+
ok: allHealthy, command: 'health', exitCode,
|
|
68
|
+
daemon: { running: true, pid: daemonStatus.pid, reachable: true, uptime: status.uptime },
|
|
69
|
+
plugin: { connected: status.pluginConnected },
|
|
70
|
+
sdk: { ready: status.sdkReady },
|
|
71
|
+
timeoutRemaining: status.timeoutRemaining,
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
else {
|
|
75
|
+
console.log(`✅ 守护进程 运行中(PID: ${daemonStatus.pid},已运行 ${formatUptime(status.uptime)})`);
|
|
76
|
+
if (status.pluginConnected) {
|
|
77
|
+
console.log('✅ Plugin 已连接');
|
|
78
|
+
}
|
|
79
|
+
else {
|
|
80
|
+
console.log('❌ Plugin 未连接');
|
|
81
|
+
}
|
|
82
|
+
if (status.sdkReady) {
|
|
83
|
+
console.log('✅ SDK 就绪');
|
|
84
|
+
}
|
|
85
|
+
else {
|
|
86
|
+
console.log('❌ SDK 未就绪');
|
|
87
|
+
}
|
|
88
|
+
console.log(`\n超时: ${formatUptime(status.timeoutRemaining)} 后自动关闭`);
|
|
89
|
+
}
|
|
90
|
+
process.exitCode = exitCode;
|
|
91
|
+
}
|
|
92
|
+
function formatUptime(seconds) {
|
|
93
|
+
if (seconds < 60)
|
|
94
|
+
return `${seconds} 秒`;
|
|
95
|
+
if (seconds < 3600)
|
|
96
|
+
return `${Math.floor(seconds / 60)} 分钟`;
|
|
97
|
+
const hours = Math.floor(seconds / 3600);
|
|
98
|
+
const mins = Math.floor((seconds % 3600) / 60);
|
|
99
|
+
return `${hours} 小时 ${mins} 分钟`;
|
|
100
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* install skill 命令
|
|
3
|
+
*
|
|
4
|
+
* 将 SKILL.md 和 docs/instruction/*.md 安装到 ~/.claude/skills/remnote-bridge/
|
|
5
|
+
*/
|
|
6
|
+
import fs from 'fs';
|
|
7
|
+
import path from 'path';
|
|
8
|
+
import os from 'os';
|
|
9
|
+
export async function installSkillCommand() {
|
|
10
|
+
// 从包安装路径计算包根(dist/cli/commands/install-skill.js → 包根)
|
|
11
|
+
const packageRoot = path.resolve(import.meta.dirname, '..', '..', '..');
|
|
12
|
+
const skillSource = path.join(packageRoot, 'skill', 'SKILL.md');
|
|
13
|
+
const instructionDir = path.join(packageRoot, 'docs', 'instruction');
|
|
14
|
+
if (!fs.existsSync(skillSource)) {
|
|
15
|
+
console.error(`错误: 找不到 SKILL.md: ${skillSource}`);
|
|
16
|
+
process.exitCode = 1;
|
|
17
|
+
return;
|
|
18
|
+
}
|
|
19
|
+
// 目标目录
|
|
20
|
+
const targetDir = path.join(os.homedir(), '.claude', 'skills', 'remnote-bridge');
|
|
21
|
+
fs.mkdirSync(targetDir, { recursive: true });
|
|
22
|
+
// 复制 SKILL.md,更新 instruction 路径引用
|
|
23
|
+
let skillContent = fs.readFileSync(skillSource, 'utf-8');
|
|
24
|
+
const targetInstructionDir = path.join(targetDir, 'instruction');
|
|
25
|
+
// 替换相对路径 docs/instruction/ 为安装后的绝对路径
|
|
26
|
+
skillContent = skillContent.replace(/docs\/instruction\//g, targetInstructionDir + '/');
|
|
27
|
+
fs.writeFileSync(path.join(targetDir, 'SKILL.md'), skillContent, 'utf-8');
|
|
28
|
+
console.log(` SKILL.md → ${targetDir}/SKILL.md`);
|
|
29
|
+
// 复制 docs/instruction/*.md
|
|
30
|
+
if (fs.existsSync(instructionDir)) {
|
|
31
|
+
fs.mkdirSync(targetInstructionDir, { recursive: true });
|
|
32
|
+
const files = fs.readdirSync(instructionDir).filter(f => f.endsWith('.md'));
|
|
33
|
+
for (const file of files) {
|
|
34
|
+
fs.copyFileSync(path.join(instructionDir, file), path.join(targetInstructionDir, file));
|
|
35
|
+
console.log(` ${file} → ${targetInstructionDir}/${file}`);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
console.log(`\nSkill 已安装到 ${targetDir}`);
|
|
39
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* read-context 命令
|
|
3
|
+
*
|
|
4
|
+
* 读取当前上下文视图。
|
|
5
|
+
* - --mode focus|page(默认 focus)
|
|
6
|
+
* - --ancestor-levels N 向上追溯几层祖先(默认 2,仅 focus 模式)
|
|
7
|
+
* - --depth N 展开深度(默认 3,仅 page 模式)
|
|
8
|
+
* - --max-nodes N 全局节点上限(默认 200)
|
|
9
|
+
* - --max-siblings N 每个父节点下展示的 children 上限(默认 20)
|
|
10
|
+
* - --json 结构化 JSON 输出
|
|
11
|
+
*/
|
|
12
|
+
export interface ReadContextOptions {
|
|
13
|
+
json?: boolean;
|
|
14
|
+
mode?: string;
|
|
15
|
+
ancestorLevels?: string;
|
|
16
|
+
depth?: string;
|
|
17
|
+
maxNodes?: string;
|
|
18
|
+
maxSiblings?: string;
|
|
19
|
+
}
|
|
20
|
+
export declare function readContextCommand(options?: ReadContextOptions): Promise<void>;
|