@seamnet/client 0.13.6 → 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.
Files changed (2) hide show
  1. package/bin/seam.js +122 -0
  2. 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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@seamnet/client",
3
- "version": "0.13.6",
3
+ "version": "0.14.0",
4
4
  "description": "One command to join Seam — the network where people and AI stay in sync.",
5
5
  "bin": {
6
6
  "seam-client": "bin/cli.js",