indieclaw-agent 1.1.3 → 1.1.5

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/index.js +128 -6
  2. package/package.json +1 -1
package/index.js CHANGED
@@ -135,6 +135,10 @@ async function handleMessage(ws, msg) {
135
135
  return handleDockerAction(ws, msg, 'stop');
136
136
  case 'docker.restart':
137
137
  return handleDockerAction(ws, msg, 'restart');
138
+ case 'system.version':
139
+ return handleSystemVersion(ws, msg);
140
+ case 'system.update':
141
+ return handleSystemUpdate(ws, msg);
138
142
  case 'cron.list':
139
143
  return handleCronList(ws, msg);
140
144
  case 'terminal.start':
@@ -486,6 +490,49 @@ function handleDockerAction(ws, { id, containerId }, action) {
486
490
  });
487
491
  }
488
492
 
493
+ // --- Agent Version & Update ---
494
+ function handleSystemVersion(ws, { id }) {
495
+ const pkg = JSON.parse(fs.readFileSync(path.join(__dirname, 'package.json'), 'utf-8'));
496
+ exec('npm view indieclaw-agent version', { timeout: 10000 }, (err, stdout) => {
497
+ const latest = err ? null : stdout.trim();
498
+ reply(ws, id, { current: pkg.version, latest });
499
+ });
500
+ }
501
+
502
+ function handleSystemUpdate(ws, { id }) {
503
+ const { spawn } = require('child_process');
504
+ const platform = os.platform();
505
+
506
+ // Build an update script that runs after the agent exits
507
+ const script = platform === 'win32'
508
+ ? `@echo off
509
+ timeout /t 2 /nobreak >nul
510
+ npm install -g indieclaw-agent@latest
511
+ start "" indieclaw-agent`
512
+ : `#!/bin/bash
513
+ sleep 2
514
+ npm install -g indieclaw-agent@latest
515
+ nohup indieclaw-agent > /tmp/indieclaw-agent.log 2>&1 &`;
516
+
517
+ const ext = platform === 'win32' ? '.bat' : '.sh';
518
+ const scriptPath = path.join(os.tmpdir(), `indieclaw-update${ext}`);
519
+ fs.writeFileSync(scriptPath, script, { mode: 0o755 });
520
+
521
+ const child = platform === 'win32'
522
+ ? spawn('cmd', ['/c', scriptPath], { detached: true, stdio: 'ignore', windowsHide: true })
523
+ : spawn('bash', [scriptPath], { detached: true, stdio: 'ignore' });
524
+ child.unref();
525
+
526
+ reply(ws, id, { updating: true });
527
+
528
+ // Give time for the reply to reach the client, then exit
529
+ setTimeout(() => {
530
+ for (const [, term] of terminals) term.kill();
531
+ wss.close();
532
+ process.exit(0);
533
+ }, 500);
534
+ }
535
+
489
536
  // --- Cron ---
490
537
  function handleCronList(ws, { id }) {
491
538
  exec('crontab -l', { timeout: 5000 }, (err, stdout) => {
@@ -543,11 +590,84 @@ function handleTerminalStart(ws, { id }) {
543
590
  }
544
591
  }
545
592
 
546
- // Fallback: use script(1) to allocate a real PTY
547
593
  const { spawn } = require('child_process');
548
- const scriptArgs = os.platform() === 'darwin'
549
- ? ['-q', '/dev/null', shell]
550
- : ['-q', '-c', shell, '/dev/null'];
594
+
595
+ // macOS: use Python pty module (script(1) fails with piped stdin on macOS)
596
+ if (os.platform() === 'darwin') {
597
+ const pythonScript = `
598
+ import pty,os,sys,select,struct,fcntl,termios,signal
599
+ m,s=pty.openpty()
600
+ try:fcntl.ioctl(s,termios.TIOCSWINSZ,struct.pack('HHHH',24,80,0,0))
601
+ except:pass
602
+ p=os.fork()
603
+ if p==0:
604
+ os.close(m);os.setsid();fcntl.ioctl(s,termios.TIOCSCTTY,0)
605
+ os.dup2(s,0);os.dup2(s,1);os.dup2(s,2)
606
+ if s>2:os.close(s)
607
+ sh=os.environ.get('SHELL','/bin/zsh')
608
+ os.execvp(sh,[sh])
609
+ os.close(s);buf=b''
610
+ MK=b'\\x1b]9999;';EN=b'\\x07'
611
+ while True:
612
+ try:r,_,_=select.select([m,0],[],[])
613
+ except:break
614
+ if m in r:
615
+ try:d=os.read(m,16384)
616
+ except OSError:break
617
+ if not d:break
618
+ try:os.write(1,d)
619
+ except:break
620
+ if 0 in r:
621
+ try:d=os.read(0,4096)
622
+ except OSError:break
623
+ if not d:break
624
+ buf+=d
625
+ while MK in buf:
626
+ i=buf.index(MK)
627
+ if i>0:os.write(m,buf[:i]);buf=buf[i:]
628
+ try:j=buf.index(EN)
629
+ except ValueError:break
630
+ c=buf[len(MK):j].decode().split(',')
631
+ try:
632
+ fcntl.ioctl(m,termios.TIOCSWINSZ,struct.pack('HHHH',int(c[1]),int(c[0]),0,0))
633
+ os.kill(p,signal.SIGWINCH)
634
+ except:pass
635
+ buf=buf[j+1:]
636
+ if buf and MK not in buf:os.write(m,buf);buf=b''
637
+ try:os.kill(p,signal.SIGTERM)
638
+ except:pass
639
+ try:os.waitpid(p,0)
640
+ except:pass
641
+ `;
642
+ const proc = spawn('python3', ['-u', '-c', pythonScript], {
643
+ cwd: os.homedir(),
644
+ env: { ...process.env, TERM: 'xterm-256color' },
645
+ stdio: ['pipe', 'pipe', 'pipe'],
646
+ });
647
+
648
+ proc._ws = ws;
649
+ proc._isPty = false;
650
+ proc._hasPtyResize = true;
651
+
652
+ proc.stdout.on('data', (data) => {
653
+ send(ws, { type: 'terminal.output', id, data: data.toString() });
654
+ });
655
+
656
+ proc.stderr.on('data', (data) => {
657
+ send(ws, { type: 'terminal.output', id, data: data.toString() });
658
+ });
659
+
660
+ proc.on('exit', (exitCode) => {
661
+ send(ws, { type: 'terminal.exit', id, exitCode: exitCode ?? 0 });
662
+ terminals.delete(id);
663
+ });
664
+
665
+ terminals.set(id, proc);
666
+ return reply(ws, id, { pid: proc.pid });
667
+ }
668
+
669
+ // Linux: use script(1) to allocate a real PTY
670
+ const scriptArgs = ['-q', '-c', shell, '/dev/null'];
551
671
 
552
672
  const proc = spawn('script', scriptArgs, {
553
673
  cwd: os.homedir(),
@@ -587,11 +707,13 @@ function handleTerminalInput(ws, { id, data }) {
587
707
 
588
708
  function handleTerminalResize(ws, { id, cols, rows }) {
589
709
  const term = terminals.get(id);
590
- if (!term) return replyError(ws, id, 'Terminal not found');
710
+ if (!term) return;
591
711
  if (term._isPty && term.resize) {
592
712
  term.resize(cols, rows);
713
+ } else if (term._hasPtyResize) {
714
+ // Python PTY: send resize via custom OSC escape sequence
715
+ term.stdin.write(`\x1b]9999;${cols},${rows}\x07`);
593
716
  }
594
- // fallback processes don't support resize
595
717
  }
596
718
 
597
719
  function handleTerminalStop(ws, { id }) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "indieclaw-agent",
3
- "version": "1.1.3",
3
+ "version": "1.1.5",
4
4
  "description": "Manage your server from your phone. Agent for the IndieClaw mobile app.",
5
5
  "main": "index.js",
6
6
  "bin": {