@seamnet/client 0.13.6 → 0.14.1
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/seam.js +122 -0
- package/lib/mcp-server.cjs +28 -28
- package/package.json +1 -1
package/bin/seam.js
CHANGED
|
@@ -56,6 +56,10 @@ function printHelp() {
|
|
|
56
56
|
' seam self schedule --id <id> --every <duration> --text <text>',
|
|
57
57
|
' seam self cancel --id <id>',
|
|
58
58
|
' seam self list',
|
|
59
|
+
' seam cc list',
|
|
60
|
+
' seam cc read --session <name> [--lines N]',
|
|
61
|
+
' seam cc send --session <name> --text <msg>',
|
|
62
|
+
' seam cc start --dir <path> [--session <name>]',
|
|
59
63
|
'',
|
|
60
64
|
'输出:JSON { ok: true, data } 或 { ok: false, error }。',
|
|
61
65
|
].join('\n');
|
|
@@ -371,6 +375,123 @@ async function cmdInvite(subAction, restArgs) {
|
|
|
371
375
|
output(false, `Unknown invite action: ${subAction}. Try: seam invite generate`);
|
|
372
376
|
}
|
|
373
377
|
|
|
378
|
+
// === cc (Claude Code 管理) ===
|
|
379
|
+
|
|
380
|
+
import { execSync, spawn } from 'node:child_process';
|
|
381
|
+
|
|
382
|
+
function ccGetSessions() {
|
|
383
|
+
let lines;
|
|
384
|
+
try {
|
|
385
|
+
lines = execSync('tmux list-sessions -F "#{session_name}"', {
|
|
386
|
+
encoding: 'utf8', timeout: 5000,
|
|
387
|
+
}).trim().split('\n').filter(Boolean);
|
|
388
|
+
} catch {
|
|
389
|
+
return [];
|
|
390
|
+
}
|
|
391
|
+
return lines.map(name => {
|
|
392
|
+
let ccRunning = false;
|
|
393
|
+
try {
|
|
394
|
+
const panePid = execSync(`tmux display-message -t "${name}" -p '#{pane_pid}'`, {
|
|
395
|
+
encoding: 'utf8', timeout: 3000,
|
|
396
|
+
}).trim();
|
|
397
|
+
const fg = execSync(`ps -o comm= --ppid ${panePid} 2>/dev/null`, {
|
|
398
|
+
encoding: 'utf8', timeout: 3000,
|
|
399
|
+
}).trim().split('\n').pop();
|
|
400
|
+
ccRunning = ['claude', 'claude-code', 'node'].includes(fg);
|
|
401
|
+
} catch {}
|
|
402
|
+
return { session: name, cc: ccRunning };
|
|
403
|
+
});
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
async function cmdCc(subAction, restArgs) {
|
|
407
|
+
if (!subAction || subAction === 'list') {
|
|
408
|
+
const sessions = ccGetSessions();
|
|
409
|
+
if (sessions.length === 0) output(true, { sessions: [], message: 'no tmux sessions' });
|
|
410
|
+
output(true, { sessions });
|
|
411
|
+
return;
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
if (subAction === 'read') {
|
|
415
|
+
const { values } = parseArgs({
|
|
416
|
+
args: restArgs,
|
|
417
|
+
options: {
|
|
418
|
+
session: { type: 'string' },
|
|
419
|
+
lines: { type: 'string', default: '20' },
|
|
420
|
+
},
|
|
421
|
+
strict: false,
|
|
422
|
+
});
|
|
423
|
+
if (!values.session) output(false, '--session required');
|
|
424
|
+
try {
|
|
425
|
+
const text = execSync(
|
|
426
|
+
`tmux capture-pane -t "${values.session}" -p | tail -${parseInt(values.lines) || 20}`,
|
|
427
|
+
{ encoding: 'utf8', timeout: 5000 }
|
|
428
|
+
);
|
|
429
|
+
output(true, { session: values.session, lines: text.trimEnd().split('\n') });
|
|
430
|
+
} catch (e) {
|
|
431
|
+
output(false, `read failed: ${e.message}`);
|
|
432
|
+
}
|
|
433
|
+
return;
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
if (subAction === 'send') {
|
|
437
|
+
const { values } = parseArgs({
|
|
438
|
+
args: restArgs,
|
|
439
|
+
options: {
|
|
440
|
+
session: { type: 'string' },
|
|
441
|
+
text: { type: 'string' },
|
|
442
|
+
},
|
|
443
|
+
strict: false,
|
|
444
|
+
});
|
|
445
|
+
if (!values.session) output(false, '--session required');
|
|
446
|
+
if (!values.text) output(false, '--text required');
|
|
447
|
+
const sessions = ccGetSessions();
|
|
448
|
+
const target = sessions.find(s => s.session === values.session);
|
|
449
|
+
if (!target) output(false, `session "${values.session}" not found`);
|
|
450
|
+
if (!target.cc) output(false, `session "${values.session}": CC not running`);
|
|
451
|
+
try {
|
|
452
|
+
execSync(
|
|
453
|
+
`tmux send-keys -t "${values.session}" ${JSON.stringify(values.text)} Enter`,
|
|
454
|
+
{ timeout: 5000 }
|
|
455
|
+
);
|
|
456
|
+
output(true, { session: values.session, sent: values.text });
|
|
457
|
+
} catch (e) {
|
|
458
|
+
output(false, `send failed: ${e.message}`);
|
|
459
|
+
}
|
|
460
|
+
return;
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
if (subAction === 'start') {
|
|
464
|
+
const { values } = parseArgs({
|
|
465
|
+
args: restArgs,
|
|
466
|
+
options: {
|
|
467
|
+
dir: { type: 'string' },
|
|
468
|
+
session: { type: 'string' },
|
|
469
|
+
},
|
|
470
|
+
strict: false,
|
|
471
|
+
});
|
|
472
|
+
if (!values.dir) output(false, '--dir required');
|
|
473
|
+
if (!existsSync(values.dir)) output(false, `dir not found: ${values.dir}`);
|
|
474
|
+
const sessionName = values.session || join(values.dir).split('/').pop();
|
|
475
|
+
// 检查 session 是否已存在
|
|
476
|
+
const existing = ccGetSessions();
|
|
477
|
+
if (existing.find(s => s.session === sessionName)) {
|
|
478
|
+
output(false, `session "${sessionName}" already exists`);
|
|
479
|
+
}
|
|
480
|
+
try {
|
|
481
|
+
execSync(
|
|
482
|
+
`tmux new-session -d -s "${sessionName}" -c "${values.dir}" 'claude --dangerously-skip-permissions'`,
|
|
483
|
+
{ timeout: 10000 }
|
|
484
|
+
);
|
|
485
|
+
output(true, { session: sessionName, dir: values.dir, started: true });
|
|
486
|
+
} catch (e) {
|
|
487
|
+
output(false, `start failed: ${e.message}`);
|
|
488
|
+
}
|
|
489
|
+
return;
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
output(false, `Unknown cc action: ${subAction}. Try: list, read, send, start`);
|
|
493
|
+
}
|
|
494
|
+
|
|
374
495
|
function parseDuration(str) {
|
|
375
496
|
const m = String(str).match(/^(\d+)(s|m|h|ms)?$/i);
|
|
376
497
|
if (!m) return 600000;
|
|
@@ -399,6 +520,7 @@ function parseDuration(str) {
|
|
|
399
520
|
if (domain === 'inbox') return await cmdInbox(action, rest);
|
|
400
521
|
if (domain === 'self') return await cmdSelf(action, rest);
|
|
401
522
|
if (domain === 'invite') return await cmdInvite(action, rest);
|
|
523
|
+
if (domain === 'cc') return await cmdCc(action, rest);
|
|
402
524
|
output(false, `Unknown domain: ${domain}. Try: seam --help`);
|
|
403
525
|
} catch (e) {
|
|
404
526
|
output(false, e.message);
|
package/lib/mcp-server.cjs
CHANGED
|
@@ -110,49 +110,49 @@ const rl = readline.createInterface({ input: process.stdin });
|
|
|
110
110
|
const tools = [
|
|
111
111
|
{
|
|
112
112
|
name: 'seam',
|
|
113
|
-
description: `Seam
|
|
113
|
+
description: `Seam 网络工具。通过 CLI 风格子命令操作 IM 消息、联系人、本机 Claude Code 实例等。
|
|
114
114
|
|
|
115
115
|
用法:seam({ args: ["<domain>", "<action>", "--opt", "value", ...] })
|
|
116
116
|
返回:JSON { ok: true, data } 或 { ok: false, error }
|
|
117
117
|
|
|
118
|
-
##
|
|
118
|
+
## IM 消息
|
|
119
119
|
|
|
120
|
-
-
|
|
121
|
-
|
|
122
|
-
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
- 发群消息(终端里看到 💬 [Seam群] 前缀):
|
|
126
|
-
seam({args: ["msg", "group", "--group", "<groupId>", "--text", "hello"]})
|
|
127
|
-
- 查联系人列表 / 某人:
|
|
128
|
-
seam({args: ["contacts", "list"]})
|
|
129
|
-
seam({args: ["contacts", "get", "--user", "<userId>"]})
|
|
130
|
-
- 查自己状态:
|
|
131
|
-
seam({args: ["status"]})
|
|
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>"]})
|
|
132
125
|
|
|
133
|
-
##
|
|
126
|
+
## 联系人
|
|
134
127
|
|
|
135
|
-
|
|
136
|
-
|
|
128
|
+
- 列表:seam({args: ["contacts", "list"]})
|
|
129
|
+
- 查某人:seam({args: ["contacts", "get", "--user", "<userId>"]})
|
|
137
130
|
|
|
138
|
-
##
|
|
131
|
+
## 自身状态
|
|
139
132
|
|
|
140
|
-
|
|
133
|
+
- 查状态:seam({args: ["status"]})
|
|
134
|
+
- 邀请码:seam({args: ["invite", "generate"]})
|
|
141
135
|
|
|
142
|
-
##
|
|
136
|
+
## 定时任务
|
|
143
137
|
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
seam({args: ["self", "cancel", "--id", "some-task"]})
|
|
138
|
+
- 查看:seam({args: ["self", "list"]})
|
|
139
|
+
- 新建:seam({args: ["self", "schedule", "--id", "<id>", "--every", "<duration>", "--text", "<msg>"]})
|
|
140
|
+
- 取消:seam({args: ["self", "cancel", "--id", "<id>"]})
|
|
148
141
|
|
|
149
|
-
##
|
|
142
|
+
## Claude Code 管理(cc)
|
|
143
|
+
|
|
144
|
+
管理本机 tmux 中运行的所有 Claude Code 实例:
|
|
150
145
|
|
|
151
|
-
|
|
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
152
|
|
|
153
|
-
|
|
153
|
+
status · msg · contacts · guardian · wechat · inbox · self · invite · cc
|
|
154
154
|
|
|
155
|
-
|
|
155
|
+
完整帮助:seam({args: ["--help"]})`,
|
|
156
156
|
inputSchema: {
|
|
157
157
|
type: 'object',
|
|
158
158
|
properties: {
|