@seamnet/client 0.17.1 → 0.18.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/bin/cli.js +0 -7
- package/bin/seam.js +27 -1
- package/lib/upgrade.js +24 -2
- package/package.json +1 -1
- package/lib/mcp-server.cjs +0 -250
package/bin/cli.js
CHANGED
|
@@ -18,7 +18,6 @@ Commands:
|
|
|
18
18
|
stop Stop guardian
|
|
19
19
|
upgrade Upgrade this AI's seam-client (per-AI mode)
|
|
20
20
|
upgrade-all Host-tool: bump global package + restart all registered guardians
|
|
21
|
-
mcp-serve Start MCP server (used by Claude Code)
|
|
22
21
|
|
|
23
22
|
Environment:
|
|
24
23
|
SEAM_HOME Absolute path to a .seam data directory. Resolution priority:
|
|
@@ -82,12 +81,6 @@ try {
|
|
|
82
81
|
await guardianStop();
|
|
83
82
|
break;
|
|
84
83
|
}
|
|
85
|
-
case 'mcp-serve': {
|
|
86
|
-
const { createRequire } = await import('node:module');
|
|
87
|
-
const require = createRequire(import.meta.url);
|
|
88
|
-
require('../lib/mcp-server.cjs');
|
|
89
|
-
break;
|
|
90
|
-
}
|
|
91
84
|
case 'autostart': {
|
|
92
85
|
const { autostart } = await import('../lib/autostart.js');
|
|
93
86
|
const result = await autostart({ silent: false });
|
package/bin/seam.js
CHANGED
|
@@ -61,6 +61,7 @@ function printHelp() {
|
|
|
61
61
|
' seam cc read --session <name> [--lines N]',
|
|
62
62
|
' seam cc send --session <name> --text <msg>',
|
|
63
63
|
' seam cc start --dir <path> [--session <name>]',
|
|
64
|
+
' seam cc restart --session <name> [--no-continue] 重启某个 CC 实例',
|
|
64
65
|
'',
|
|
65
66
|
'输出:JSON { ok: true, data } 或 { ok: false, error }。',
|
|
66
67
|
].join('\n');
|
|
@@ -471,7 +472,32 @@ async function cmdCc(subAction, restArgs) {
|
|
|
471
472
|
return;
|
|
472
473
|
}
|
|
473
474
|
|
|
474
|
-
|
|
475
|
+
if (subAction === 'restart') {
|
|
476
|
+
const { values } = parseArgs({
|
|
477
|
+
args: restArgs,
|
|
478
|
+
options: {
|
|
479
|
+
session: { type: 'string' },
|
|
480
|
+
continue: { type: 'boolean' },
|
|
481
|
+
'no-continue': { type: 'boolean' },
|
|
482
|
+
},
|
|
483
|
+
strict: false,
|
|
484
|
+
});
|
|
485
|
+
if (!values.session) output(false, '--session required');
|
|
486
|
+
const continueSession = values['no-continue'] ? false : true;
|
|
487
|
+
try {
|
|
488
|
+
const ok = await tmuxUtils.restartCC(values.session, { continueSession });
|
|
489
|
+
if (ok) {
|
|
490
|
+
output(true, { session: values.session, restarted: true });
|
|
491
|
+
} else {
|
|
492
|
+
output(false, 'restart failed: CC 未在超时内退出或重启');
|
|
493
|
+
}
|
|
494
|
+
} catch (e) {
|
|
495
|
+
output(false, e.message);
|
|
496
|
+
}
|
|
497
|
+
return;
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
output(false, `Unknown cc action: ${subAction}. Try: list, read, send, start, restart`);
|
|
475
501
|
}
|
|
476
502
|
|
|
477
503
|
function parseDuration(str) {
|
package/lib/upgrade.js
CHANGED
|
@@ -6,7 +6,8 @@
|
|
|
6
6
|
* 2. patch .claude/settings.json:SessionStart hook 命令(老包名 → bin 名)
|
|
7
7
|
* + 预授权 seam CLI(permissions.allow 加 Bash(seam *))
|
|
8
8
|
* 3. 重新 patchClaudeMd(确保引用 @.seam/contacts.json 等新增的)
|
|
9
|
-
*
|
|
9
|
+
* 3b. 清理 .mcp.json 里的 seam-im 条目(MCP server 已废弃)
|
|
10
|
+
* 4. 重启 guardian(新 detached 后台进程生效)
|
|
10
11
|
*
|
|
11
12
|
* 不做:
|
|
12
13
|
* - 不动 credentials.json / IDENTITY.md(那些是 AI 自己的)
|
|
@@ -85,6 +86,27 @@ export async function upgrade() {
|
|
|
85
86
|
console.error(` refresh failed (non-fatal): ${e.message}`);
|
|
86
87
|
}
|
|
87
88
|
|
|
89
|
+
// 3b. 清理 .mcp.json 里的 seam-im 条目(MCP server 0.18.0 已废弃,删它指向的
|
|
90
|
+
// mcp-serve 已不存在——必须在 CC restart 之前剥掉,否则重启会加载到死引用)
|
|
91
|
+
console.log('3b. cleaning seam-im from .mcp.json');
|
|
92
|
+
try {
|
|
93
|
+
const mcpPath = join(process.cwd(), '.mcp.json');
|
|
94
|
+
if (existsSync(mcpPath)) {
|
|
95
|
+
const mcp = JSON.parse(readFileSync(mcpPath, 'utf8'));
|
|
96
|
+
if (mcp.mcpServers && Object.prototype.hasOwnProperty.call(mcp.mcpServers, 'seam-im')) {
|
|
97
|
+
delete mcp.mcpServers['seam-im'];
|
|
98
|
+
writeFileSync(mcpPath, JSON.stringify(mcp, null, 2));
|
|
99
|
+
console.log(' removed seam-im entry from .mcp.json');
|
|
100
|
+
} else {
|
|
101
|
+
console.log(' no seam-im entry, skip');
|
|
102
|
+
}
|
|
103
|
+
} else {
|
|
104
|
+
console.log(' no .mcp.json, skip');
|
|
105
|
+
}
|
|
106
|
+
} catch (e) {
|
|
107
|
+
console.error(` .mcp.json cleanup failed (non-fatal): ${e.message}`);
|
|
108
|
+
}
|
|
109
|
+
|
|
88
110
|
// 4. 重启 guardian(让新代码生效)+ 标记需要自动重启 CC
|
|
89
111
|
console.log('4. restarting guardian + scheduling CC auto-restart');
|
|
90
112
|
try {
|
|
@@ -109,5 +131,5 @@ export async function upgrade() {
|
|
|
109
131
|
return;
|
|
110
132
|
}
|
|
111
133
|
|
|
112
|
-
console.log('\nDone. Guardian 会在 10 秒后通知你并自动重启 Claude Code
|
|
134
|
+
console.log('\nDone. Guardian 会在 10 秒后通知你并自动重启 Claude Code。');
|
|
113
135
|
}
|
package/package.json
CHANGED
package/lib/mcp-server.cjs
DELETED
|
@@ -1,250 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
/**
|
|
3
|
-
* Seam MCP Server — 轻量转发器
|
|
4
|
-
* 不登录 SDK,通过 unix socket 连接 guardian,把 MCP 工具调用转发为 action。
|
|
5
|
-
*
|
|
6
|
-
* 每个请求都生成 req_id,透传 guardian 的结构化错误(含 code/hint/docs)给 AI。
|
|
7
|
-
*/
|
|
8
|
-
|
|
9
|
-
const net = require('node:net');
|
|
10
|
-
const path = require('node:path');
|
|
11
|
-
const readline = require('node:readline');
|
|
12
|
-
const { execFile } = require('node:child_process');
|
|
13
|
-
const { randomUUID } = require('node:crypto');
|
|
14
|
-
|
|
15
|
-
const SEAM_DIR = path.join(process.cwd(), '.seam');
|
|
16
|
-
const SOCKET_PATH = path.join(SEAM_DIR, 'guardian.sock');
|
|
17
|
-
const LOG_PATH = path.join(SEAM_DIR, 'logs', 'guardian.jsonl');
|
|
18
|
-
// seam CLI 入口(同一个 npm 包里),MCP 把调用转发给 CLI 执行
|
|
19
|
-
const SEAM_CLI = path.join(__dirname, '..', 'bin', 'seam.js');
|
|
20
|
-
const ALLOWED_DOMAINS = new Set([
|
|
21
|
-
'status', 'msg', 'contacts', 'guardian', 'wechat', 'inbox', 'self', 'invite', 'cc', 'help', '--help', '-h',
|
|
22
|
-
]);
|
|
23
|
-
|
|
24
|
-
function guardianRequest(payload) {
|
|
25
|
-
const reqId = payload.req_id || randomUUID();
|
|
26
|
-
const req = { ...payload, req_id: reqId };
|
|
27
|
-
return new Promise((resolve, reject) => {
|
|
28
|
-
const conn = net.createConnection(SOCKET_PATH, () => {
|
|
29
|
-
conn.write(JSON.stringify(req));
|
|
30
|
-
});
|
|
31
|
-
let data = '';
|
|
32
|
-
conn.on('data', (chunk) => {
|
|
33
|
-
data += chunk;
|
|
34
|
-
try {
|
|
35
|
-
const res = JSON.parse(data);
|
|
36
|
-
conn.destroy();
|
|
37
|
-
resolve(res);
|
|
38
|
-
} catch {
|
|
39
|
-
// incomplete
|
|
40
|
-
}
|
|
41
|
-
});
|
|
42
|
-
conn.on('end', () => {
|
|
43
|
-
if (data) {
|
|
44
|
-
try {
|
|
45
|
-
resolve(JSON.parse(data));
|
|
46
|
-
} catch {
|
|
47
|
-
resolve({
|
|
48
|
-
error: {
|
|
49
|
-
code: 'GUARDIAN_INVALID_RESPONSE',
|
|
50
|
-
message: data,
|
|
51
|
-
req_id: reqId,
|
|
52
|
-
},
|
|
53
|
-
});
|
|
54
|
-
}
|
|
55
|
-
} else {
|
|
56
|
-
resolve({
|
|
57
|
-
error: {
|
|
58
|
-
code: 'GUARDIAN_EMPTY_RESPONSE',
|
|
59
|
-
message: 'guardian returned no data',
|
|
60
|
-
req_id: reqId,
|
|
61
|
-
},
|
|
62
|
-
});
|
|
63
|
-
}
|
|
64
|
-
});
|
|
65
|
-
conn.on('error', () => {
|
|
66
|
-
reject(
|
|
67
|
-
Object.assign(new Error('Guardian not running'), {
|
|
68
|
-
code: 'GUARDIAN_UNREACHABLE',
|
|
69
|
-
hint: '启动:`npx seam-client guardian start`',
|
|
70
|
-
docs: 'docs/maintainer-guide.md#GUARDIAN_UNREACHABLE',
|
|
71
|
-
req_id: reqId,
|
|
72
|
-
})
|
|
73
|
-
);
|
|
74
|
-
});
|
|
75
|
-
conn.setTimeout(10000, () => {
|
|
76
|
-
conn.destroy();
|
|
77
|
-
reject(
|
|
78
|
-
Object.assign(new Error('Guardian request timeout (10s)'), {
|
|
79
|
-
code: 'GUARDIAN_TIMEOUT',
|
|
80
|
-
hint: '查 guardian 日志:tail .seam/logs/guardian.jsonl',
|
|
81
|
-
docs: 'docs/maintainer-guide.md#GUARDIAN_TIMEOUT',
|
|
82
|
-
req_id: reqId,
|
|
83
|
-
})
|
|
84
|
-
);
|
|
85
|
-
});
|
|
86
|
-
});
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
function formatErrorForMcp(err) {
|
|
90
|
-
// err 可能是 { code, message, hint, docs, req_id } 或普通 Error 对象
|
|
91
|
-
const code = err.code || 'UNKNOWN';
|
|
92
|
-
const message = err.message || String(err);
|
|
93
|
-
const lines = [`[${code}] ${message}`];
|
|
94
|
-
if (err.hint) lines.push(`💡 ${err.hint}`);
|
|
95
|
-
if (err.docs) lines.push(`📄 ${err.docs}`);
|
|
96
|
-
if (err.req_id) {
|
|
97
|
-
lines.push(
|
|
98
|
-
`🔍 req_id: ${err.req_id}`,
|
|
99
|
-
` 查日志: jq 'select(.req_id=="${err.req_id}")' ${LOG_PATH}`
|
|
100
|
-
);
|
|
101
|
-
}
|
|
102
|
-
return lines.join('\n');
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
// MCP protocol: stdio JSON-RPC
|
|
106
|
-
const rl = readline.createInterface({ input: process.stdin });
|
|
107
|
-
|
|
108
|
-
// 唯一一个 MCP tool:调用 seam CLI(bin/seam.js)。AI 传参数数组,MCP execFile 执行,parse JSON 返回。
|
|
109
|
-
// 扩展能力只需要改 CLI + 更新下面的 description,不改 mcp-server。
|
|
110
|
-
const tools = [
|
|
111
|
-
{
|
|
112
|
-
name: 'seam',
|
|
113
|
-
description: `Seam 网络工具。通过 CLI 风格子命令操作 IM 消息、联系人、本机 Claude Code 实例等。
|
|
114
|
-
|
|
115
|
-
用法:seam({ args: ["<domain>", "<action>", "--opt", "value", ...] })
|
|
116
|
-
返回:JSON { ok: true, data } 或 { ok: false, error }
|
|
117
|
-
|
|
118
|
-
## IM 消息
|
|
119
|
-
|
|
120
|
-
- 发私聊文本:seam({args: ["msg", "send", "--to", "<userId>", "--text", "你好"]})
|
|
121
|
-
- 发私聊图片:seam({args: ["msg", "send", "--to", "<userId>", "--image", "<path>"]})
|
|
122
|
-
- 发私聊文件:seam({args: ["msg", "send", "--to", "<userId>", "--file", "<path>"]})
|
|
123
|
-
- 发群消息:seam({args: ["msg", "group", "--group", "<groupId>", "--text", "hello"]})
|
|
124
|
-
- 长文本先写文件:seam({args: ["msg", "send", "--to", "<userId>", "--text-file", "<path>"]})
|
|
125
|
-
|
|
126
|
-
## 联系人
|
|
127
|
-
|
|
128
|
-
- 列表:seam({args: ["contacts", "list"]})
|
|
129
|
-
- 查某人:seam({args: ["contacts", "get", "--user", "<userId>"]})
|
|
130
|
-
|
|
131
|
-
## 自身状态
|
|
132
|
-
|
|
133
|
-
- 查状态:seam({args: ["status"]})
|
|
134
|
-
- 邀请码:seam({args: ["invite", "generate"]})
|
|
135
|
-
|
|
136
|
-
## 定时任务
|
|
137
|
-
|
|
138
|
-
- 查看:seam({args: ["self", "list"]})
|
|
139
|
-
- 新建:seam({args: ["self", "schedule", "--id", "<id>", "--every", "<duration>", "--text", "<msg>"]})
|
|
140
|
-
- 取消:seam({args: ["self", "cancel", "--id", "<id>"]})
|
|
141
|
-
|
|
142
|
-
## Claude Code 管理(cc)
|
|
143
|
-
|
|
144
|
-
管理本机 tmux 中运行的所有 Claude Code 实例:
|
|
145
|
-
|
|
146
|
-
- 列出所有实例:seam({args: ["cc", "list"]})
|
|
147
|
-
- 读某个实例输出:seam({args: ["cc", "read", "--session", "<name>", "--lines", "20"]})
|
|
148
|
-
- 给某个实例发消息:seam({args: ["cc", "send", "--session", "<name>", "--text", "<msg>"]})
|
|
149
|
-
- 启动新实例:seam({args: ["cc", "start", "--dir", "<path>", "--session", "<name>"]})
|
|
150
|
-
|
|
151
|
-
## 可用 domain
|
|
152
|
-
|
|
153
|
-
status · msg · contacts · guardian · wechat · inbox · self · invite · cc
|
|
154
|
-
|
|
155
|
-
完整帮助:seam({args: ["--help"]})`,
|
|
156
|
-
inputSchema: {
|
|
157
|
-
type: 'object',
|
|
158
|
-
properties: {
|
|
159
|
-
args: {
|
|
160
|
-
type: 'array',
|
|
161
|
-
items: { type: 'string' },
|
|
162
|
-
description: 'CLI 参数数组,等同命令行里 seam 后面的所有 token。',
|
|
163
|
-
},
|
|
164
|
-
},
|
|
165
|
-
required: ['args'],
|
|
166
|
-
},
|
|
167
|
-
},
|
|
168
|
-
];
|
|
169
|
-
|
|
170
|
-
function runSeamCli(args) {
|
|
171
|
-
return new Promise((resolve) => {
|
|
172
|
-
// 安全:只允许已知 domain,拒绝空数组或任意 domain
|
|
173
|
-
const firstArg = Array.isArray(args) && args.length ? String(args[0]) : '';
|
|
174
|
-
if (!ALLOWED_DOMAINS.has(firstArg)) {
|
|
175
|
-
resolve({ ok: false, error: `Unknown domain "${firstArg}". Allowed: ${[...ALLOWED_DOMAINS].join(', ')}` });
|
|
176
|
-
return;
|
|
177
|
-
}
|
|
178
|
-
execFile(
|
|
179
|
-
process.execPath,
|
|
180
|
-
[SEAM_CLI, ...args.map((a) => String(a))],
|
|
181
|
-
{ timeout: 20000, cwd: process.cwd(), maxBuffer: 4 * 1024 * 1024 },
|
|
182
|
-
(err, stdout, stderr) => {
|
|
183
|
-
if (err && !stdout) {
|
|
184
|
-
resolve({ ok: false, error: stderr || err.message });
|
|
185
|
-
return;
|
|
186
|
-
}
|
|
187
|
-
try {
|
|
188
|
-
resolve(JSON.parse((stdout || '').trim().split('\n').pop() || '{}'));
|
|
189
|
-
} catch (e) {
|
|
190
|
-
resolve({ ok: false, error: `Bad CLI output: ${stdout.slice(0, 400)}` });
|
|
191
|
-
}
|
|
192
|
-
}
|
|
193
|
-
);
|
|
194
|
-
});
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
function sendResponse(id, result) {
|
|
198
|
-
process.stdout.write(JSON.stringify({ jsonrpc: '2.0', id, result }) + '\n');
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
function sendError(id, code, message) {
|
|
202
|
-
process.stdout.write(
|
|
203
|
-
JSON.stringify({ jsonrpc: '2.0', id, error: { code, message } }) + '\n'
|
|
204
|
-
);
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
rl.on('line', async (line) => {
|
|
208
|
-
let req;
|
|
209
|
-
try {
|
|
210
|
-
req = JSON.parse(line);
|
|
211
|
-
} catch {
|
|
212
|
-
return;
|
|
213
|
-
}
|
|
214
|
-
|
|
215
|
-
const { id, method, params } = req;
|
|
216
|
-
|
|
217
|
-
if (method === 'initialize') {
|
|
218
|
-
sendResponse(id, {
|
|
219
|
-
protocolVersion: '2024-11-05',
|
|
220
|
-
capabilities: { tools: {} },
|
|
221
|
-
serverInfo: { name: 'seam', version: '0.5.0' },
|
|
222
|
-
});
|
|
223
|
-
} else if (method === 'notifications/initialized') {
|
|
224
|
-
// no response needed
|
|
225
|
-
} else if (method === 'tools/list') {
|
|
226
|
-
sendResponse(id, { tools });
|
|
227
|
-
} else if (method === 'tools/call') {
|
|
228
|
-
const { name, arguments: args } = params;
|
|
229
|
-
if (name !== 'seam') {
|
|
230
|
-
sendError(id, -32601, `Unknown tool: ${name}`);
|
|
231
|
-
return;
|
|
232
|
-
}
|
|
233
|
-
const argv = Array.isArray(args?.args) ? args.args : [];
|
|
234
|
-
const result = await runSeamCli(argv);
|
|
235
|
-
if (result.ok === false) {
|
|
236
|
-
sendResponse(id, {
|
|
237
|
-
content: [{ type: 'text', text: `[seam] ${result.error}` }],
|
|
238
|
-
isError: true,
|
|
239
|
-
});
|
|
240
|
-
} else {
|
|
241
|
-
const payload = result.data !== undefined ? result.data : result;
|
|
242
|
-
const text = typeof payload === 'string' ? payload : JSON.stringify(payload, null, 2);
|
|
243
|
-
sendResponse(id, {
|
|
244
|
-
content: [{ type: 'text', text }],
|
|
245
|
-
});
|
|
246
|
-
}
|
|
247
|
-
} else {
|
|
248
|
-
sendError(id, -32601, `Unknown method: ${method}`);
|
|
249
|
-
}
|
|
250
|
-
});
|