@seamnet/client 0.13.5 → 0.14.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/seam.js +122 -0
- package/lib/guardian.js +1 -1
- 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/guardian.js
CHANGED
|
@@ -228,7 +228,7 @@ export async function guardianRun() {
|
|
|
228
228
|
setTimeout(() => {
|
|
229
229
|
try {
|
|
230
230
|
execSync(
|
|
231
|
-
`${tmux} send-keys -t ${safeSession} 'claude --dangerously-skip-permissions' Enter`,
|
|
231
|
+
`${tmux} send-keys -t ${safeSession} 'claude --dangerously-skip-permissions --continue' Enter`,
|
|
232
232
|
{ stdio: 'ignore', timeout: 5000 }
|
|
233
233
|
);
|
|
234
234
|
guardianState.set('cc_restarted', new Date().toISOString());
|