codeam-cli 1.0.8 → 1.0.9
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/dist/index.js +84 -21
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -110,7 +110,7 @@ var import_picocolors = __toESM(require("picocolors"));
|
|
|
110
110
|
// package.json
|
|
111
111
|
var package_default = {
|
|
112
112
|
name: "codeam-cli",
|
|
113
|
-
version: "1.0.
|
|
113
|
+
version: "1.0.9",
|
|
114
114
|
description: "Remote control Claude Code from your mobile device",
|
|
115
115
|
main: "dist/index.js",
|
|
116
116
|
bin: {
|
|
@@ -510,13 +510,70 @@ var CommandRelayService = class {
|
|
|
510
510
|
// src/services/claude.service.ts
|
|
511
511
|
var import_child_process = require("child_process");
|
|
512
512
|
var fs2 = __toESM(require("fs"));
|
|
513
|
+
var os3 = __toESM(require("os"));
|
|
513
514
|
var path2 = __toESM(require("path"));
|
|
515
|
+
var PYTHON_PTY_HELPER = `import os,pty,sys,select,signal,struct,fcntl,termios,errno
|
|
516
|
+
m,s=pty.openpty()
|
|
517
|
+
try:
|
|
518
|
+
cols=int(os.environ.get('COLUMNS','220'))
|
|
519
|
+
rows=int(os.environ.get('LINES','50'))
|
|
520
|
+
fcntl.ioctl(s,termios.TIOCSWINSZ,struct.pack('HHHH',rows,cols,0,0))
|
|
521
|
+
except Exception:pass
|
|
522
|
+
pid=os.fork()
|
|
523
|
+
if pid==0:
|
|
524
|
+
os.close(m)
|
|
525
|
+
os.setsid()
|
|
526
|
+
try:fcntl.ioctl(s,termios.TIOCSCTTY,0)
|
|
527
|
+
except Exception:pass
|
|
528
|
+
for fd in[0,1,2]:os.dup2(s,fd)
|
|
529
|
+
if s>2:os.close(s)
|
|
530
|
+
os.execvp(sys.argv[1],sys.argv[1:])
|
|
531
|
+
sys.exit(127)
|
|
532
|
+
os.close(s)
|
|
533
|
+
done=[False]
|
|
534
|
+
def onchld(n,f):
|
|
535
|
+
try:os.waitpid(pid,os.WNOHANG)
|
|
536
|
+
except Exception:pass
|
|
537
|
+
done[0]=True
|
|
538
|
+
def onwinch(n,f):
|
|
539
|
+
try:
|
|
540
|
+
sz=os.get_terminal_size(2)
|
|
541
|
+
fcntl.ioctl(m,termios.TIOCSWINSZ,struct.pack('HHHH',sz.lines,sz.columns,0,0))
|
|
542
|
+
except Exception:pass
|
|
543
|
+
signal.signal(signal.SIGCHLD,onchld)
|
|
544
|
+
signal.signal(signal.SIGWINCH,onwinch)
|
|
545
|
+
i=sys.stdin.fileno()
|
|
546
|
+
o=sys.stdout.fileno()
|
|
547
|
+
while not done[0]:
|
|
548
|
+
try:r,_,_=select.select([i,m],[],[],0.1)
|
|
549
|
+
except OSError as e:
|
|
550
|
+
if e.errno==errno.EINTR:continue
|
|
551
|
+
break
|
|
552
|
+
if i in r:
|
|
553
|
+
try:
|
|
554
|
+
d=os.read(i,4096)
|
|
555
|
+
if d:os.write(m,d)
|
|
556
|
+
else:break
|
|
557
|
+
except OSError:break
|
|
558
|
+
if m in r:
|
|
559
|
+
try:
|
|
560
|
+
d=os.read(m,4096)
|
|
561
|
+
if d:os.write(o,d)
|
|
562
|
+
except OSError:done[0]=True
|
|
563
|
+
try:os.kill(pid,signal.SIGTERM)
|
|
564
|
+
except Exception:pass
|
|
565
|
+
try:
|
|
566
|
+
_,st=os.waitpid(pid,0)
|
|
567
|
+
sys.exit((st>>8)&0xFF)
|
|
568
|
+
except Exception:sys.exit(0)
|
|
569
|
+
`;
|
|
514
570
|
var ClaudeService = class {
|
|
515
571
|
constructor(opts) {
|
|
516
572
|
this.opts = opts;
|
|
517
573
|
}
|
|
518
574
|
opts;
|
|
519
575
|
proc = null;
|
|
576
|
+
helperPath = null;
|
|
520
577
|
spawn() {
|
|
521
578
|
if (this.proc) {
|
|
522
579
|
this.cleanup();
|
|
@@ -537,20 +594,24 @@ var ClaudeService = class {
|
|
|
537
594
|
}
|
|
538
595
|
}
|
|
539
596
|
/**
|
|
540
|
-
* macOS / Linux:
|
|
541
|
-
*
|
|
542
|
-
* Why: Claude Code (React Ink) checks stdin.isTTY. When stdin is a pipe it
|
|
543
|
-
* shows a "no stdin data in 3s" warning and then stops reading stdin entirely.
|
|
544
|
-
* `script` creates a PTY for the child so Claude sees stdin as a terminal,
|
|
545
|
-
* while the parent (`codeam`) still controls script's stdin/stdout as pipes —
|
|
546
|
-
* enabling mobile command injection and output capture.
|
|
597
|
+
* macOS / Linux: use a Python PTY helper to give Claude a real TTY.
|
|
598
|
+
* Falls back to direct spawn if python3 is not available.
|
|
547
599
|
*/
|
|
548
600
|
spawnWithPty(claudeCmd) {
|
|
601
|
+
const python = findInPath("python3") ?? findInPath("python");
|
|
602
|
+
if (!python) {
|
|
603
|
+
console.error(
|
|
604
|
+
" \xB7 python3 not found; mobile command injection may be limited.\n"
|
|
605
|
+
);
|
|
606
|
+
this.spawnDirect(claudeCmd);
|
|
607
|
+
return;
|
|
608
|
+
}
|
|
549
609
|
const shell = process.env.SHELL || "/bin/sh";
|
|
550
610
|
const cols = process.stdout.columns || 220;
|
|
551
611
|
const rows = process.stdout.rows || 50;
|
|
552
|
-
|
|
553
|
-
|
|
612
|
+
this.helperPath = path2.join(os3.tmpdir(), "codeam-pty-helper.py");
|
|
613
|
+
fs2.writeFileSync(this.helperPath, PYTHON_PTY_HELPER, { mode: 420 });
|
|
614
|
+
this.proc = (0, import_child_process.spawn)(python, [this.helperPath, shell, "-c", `exec ${claudeCmd}`], {
|
|
554
615
|
stdio: ["pipe", "pipe", "inherit"],
|
|
555
616
|
cwd: this.opts.cwd,
|
|
556
617
|
env: {
|
|
@@ -578,14 +639,14 @@ var ClaudeService = class {
|
|
|
578
639
|
process.stdin.on("data", this.stdinHandler);
|
|
579
640
|
process.on("SIGWINCH", this.handleResize);
|
|
580
641
|
this.proc.on("exit", (code) => {
|
|
642
|
+
this.removeTempFile();
|
|
581
643
|
this.cleanup();
|
|
582
644
|
this.opts.onExit(code ?? 0);
|
|
583
645
|
});
|
|
584
646
|
}
|
|
585
647
|
/**
|
|
586
|
-
* Windows fallback: direct spawn without PTY.
|
|
587
|
-
* Mobile command injection is limited
|
|
588
|
-
* but the terminal session still works for manual use.
|
|
648
|
+
* Windows (or Python-unavailable) fallback: direct spawn without PTY.
|
|
649
|
+
* Mobile command injection is limited on Windows.
|
|
589
650
|
*/
|
|
590
651
|
spawnDirect(claudeCmd) {
|
|
591
652
|
this.proc = (0, import_child_process.spawn)(claudeCmd, [], {
|
|
@@ -623,6 +684,7 @@ var ClaudeService = class {
|
|
|
623
684
|
}
|
|
624
685
|
kill() {
|
|
625
686
|
this.proc?.kill();
|
|
687
|
+
this.removeTempFile();
|
|
626
688
|
this.cleanup();
|
|
627
689
|
}
|
|
628
690
|
stdinHandler = (chunk) => {
|
|
@@ -646,15 +708,16 @@ var ClaudeService = class {
|
|
|
646
708
|
}
|
|
647
709
|
}
|
|
648
710
|
}
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
711
|
+
removeTempFile() {
|
|
712
|
+
if (this.helperPath) {
|
|
713
|
+
try {
|
|
714
|
+
fs2.unlinkSync(this.helperPath);
|
|
715
|
+
} catch {
|
|
716
|
+
}
|
|
717
|
+
this.helperPath = null;
|
|
718
|
+
}
|
|
654
719
|
}
|
|
655
|
-
|
|
656
|
-
return ["-q", "-e", "-c", shellCmd, "/dev/null"];
|
|
657
|
-
}
|
|
720
|
+
};
|
|
658
721
|
function findInPath(name) {
|
|
659
722
|
const dirs = (process.env.PATH ?? "").split(path2.delimiter);
|
|
660
723
|
for (const dir of dirs) {
|