codeam-cli 1.3.6 → 1.3.8

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/dist/index.js +201 -113
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -24,9 +24,9 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
24
24
  ));
25
25
 
26
26
  // src/commands/start.ts
27
- var fs3 = __toESM(require("fs"));
27
+ var fs4 = __toESM(require("fs"));
28
28
  var os4 = __toESM(require("os"));
29
- var path3 = __toESM(require("path"));
29
+ var path4 = __toESM(require("path"));
30
30
  var import_crypto = require("crypto");
31
31
  var import_picocolors2 = __toESM(require("picocolors"));
32
32
 
@@ -114,7 +114,7 @@ var import_picocolors = __toESM(require("picocolors"));
114
114
  // package.json
115
115
  var package_default = {
116
116
  name: "codeam-cli",
117
- version: "1.3.6",
117
+ version: "1.3.8",
118
118
  description: "Remote control Claude Code from your mobile device",
119
119
  main: "dist/index.js",
120
120
  bin: {
@@ -511,11 +511,27 @@ var CommandRelayService = class {
511
511
  }
512
512
  };
513
513
 
514
- // src/services/claude.service.ts
515
- var import_child_process = require("child_process");
514
+ // src/services/pty/types.ts
516
515
  var fs2 = __toESM(require("fs"));
517
- var os3 = __toESM(require("os"));
518
516
  var path2 = __toESM(require("path"));
517
+ function findInPath(name) {
518
+ const dirs = (process.env.PATH ?? "").split(path2.delimiter);
519
+ for (const dir of dirs) {
520
+ const full = `${dir}/${name}`;
521
+ try {
522
+ fs2.accessSync(full, fs2.constants.X_OK);
523
+ return full;
524
+ } catch {
525
+ }
526
+ }
527
+ return null;
528
+ }
529
+
530
+ // src/services/pty/unix.strategy.ts
531
+ var import_child_process = require("child_process");
532
+ var fs3 = __toESM(require("fs"));
533
+ var os3 = __toESM(require("os"));
534
+ var path3 = __toESM(require("path"));
519
535
  var PYTHON_PTY_HELPER = `import os,pty,sys,select,signal,struct,fcntl,termios,errno
520
536
  m,s=pty.openpty()
521
537
  try:
@@ -571,53 +587,30 @@ try:
571
587
  sys.exit((st>>8)&0xFF)
572
588
  except Exception:sys.exit(0)
573
589
  `;
574
- var ClaudeService = class {
590
+ var UnixPtyStrategy = class {
575
591
  constructor(opts) {
576
592
  this.opts = opts;
577
593
  }
578
594
  opts;
579
595
  proc = null;
580
596
  helperPath = null;
581
- spawn() {
582
- if (this.proc) {
583
- this.cleanup();
584
- this.proc.kill();
585
- this.proc = null;
586
- }
587
- if (!findInPath("claude") && !findInPath("claude-code")) {
588
- console.error(
589
- "\n \u2717 claude not found in PATH.\n Install it with: npm install -g @anthropic-ai/claude-code\n"
590
- );
591
- process.exit(1);
592
- }
593
- const claudeCmd = findInPath("claude") ? "claude" : "claude-code";
594
- if (process.platform === "win32") {
595
- this.spawnDirect(claudeCmd);
596
- } else {
597
- this.spawnWithPty(claudeCmd);
598
- }
599
- }
600
- /**
601
- * macOS / Linux: use a Python PTY helper to give Claude a real TTY.
602
- * Falls back to direct spawn if python3 is not available.
603
- */
604
- spawnWithPty(claudeCmd) {
597
+ spawn(cmd, cwd) {
605
598
  const python = findInPath("python3") ?? findInPath("python");
606
599
  if (!python) {
607
600
  console.error(
608
601
  " \xB7 python3 not found; mobile command injection may be limited.\n"
609
602
  );
610
- this.spawnDirect(claudeCmd);
603
+ this.spawnDirect(cmd, cwd);
611
604
  return;
612
605
  }
613
606
  const shell = process.env.SHELL || "/bin/sh";
614
607
  const cols = process.stdout.columns || 220;
615
608
  const rows = process.stdout.rows || 50;
616
- this.helperPath = path2.join(os3.tmpdir(), "codeam-pty-helper.py");
617
- fs2.writeFileSync(this.helperPath, PYTHON_PTY_HELPER, { mode: 420 });
618
- this.proc = (0, import_child_process.spawn)(python, [this.helperPath, shell, "-c", `exec ${claudeCmd}`], {
609
+ this.helperPath = path3.join(os3.tmpdir(), "codeam-pty-helper.py");
610
+ fs3.writeFileSync(this.helperPath, PYTHON_PTY_HELPER, { mode: 420 });
611
+ this.proc = (0, import_child_process.spawn)(python, [this.helperPath, shell, "-c", `exec ${cmd}`], {
619
612
  stdio: ["pipe", "pipe", "inherit"],
620
- cwd: this.opts.cwd,
613
+ cwd,
621
614
  env: {
622
615
  ...process.env,
623
616
  TERM: "xterm-256color",
@@ -636,7 +629,7 @@ var ClaudeService = class {
636
629
  });
637
630
  this.proc.stdout?.on("data", (chunk) => {
638
631
  process.stdout.write(chunk);
639
- this.opts.onData?.(chunk.toString("utf8"));
632
+ this.opts.onData(chunk.toString("utf8"));
640
633
  });
641
634
  if (process.stdin.isTTY) process.stdin.setRawMode(true);
642
635
  process.stdin.resume();
@@ -644,18 +637,18 @@ var ClaudeService = class {
644
637
  process.on("SIGWINCH", this.handleResize);
645
638
  this.proc.on("exit", (code) => {
646
639
  this.removeTempFile();
647
- this.cleanup();
640
+ this.dispose();
648
641
  this.opts.onExit(code ?? 0);
649
642
  });
650
643
  }
651
644
  /**
652
- * Windows (or Python-unavailable) fallback: direct spawn without PTY.
653
- * Mobile command injection is limited on Windows.
645
+ * Python-unavailable fallback: direct spawn without PTY.
646
+ * Mobile command injection is limited (no real TTY for Claude).
654
647
  */
655
- spawnDirect(claudeCmd) {
656
- this.proc = (0, import_child_process.spawn)(claudeCmd, [], {
648
+ spawnDirect(cmd, cwd) {
649
+ this.proc = (0, import_child_process.spawn)(cmd, [], {
657
650
  stdio: ["pipe", "inherit", "inherit"],
658
- cwd: this.opts.cwd,
651
+ cwd,
659
652
  env: process.env,
660
653
  shell: true
661
654
  });
@@ -674,13 +667,139 @@ var ClaudeService = class {
674
667
  process.stdin.on("data", this.stdinHandler);
675
668
  process.on("SIGWINCH", this.handleResize);
676
669
  this.proc.on("exit", (code) => {
677
- this.cleanup();
670
+ this.dispose();
671
+ this.opts.onExit(code ?? 0);
672
+ });
673
+ }
674
+ write(data) {
675
+ this.proc?.stdin?.write(data);
676
+ }
677
+ kill() {
678
+ this.proc?.kill();
679
+ this.removeTempFile();
680
+ this.dispose();
681
+ }
682
+ dispose() {
683
+ process.removeListener("SIGWINCH", this.handleResize);
684
+ process.stdin.removeListener("data", this.stdinHandler);
685
+ if (process.stdin.isTTY) {
686
+ try {
687
+ process.stdin.setRawMode(false);
688
+ } catch {
689
+ }
690
+ }
691
+ }
692
+ stdinHandler = (chunk) => {
693
+ this.proc?.stdin?.write(chunk);
694
+ };
695
+ handleResize = () => {
696
+ if (this.proc?.pid) {
697
+ try {
698
+ process.kill(this.proc.pid, "SIGWINCH");
699
+ } catch {
700
+ }
701
+ }
702
+ };
703
+ removeTempFile() {
704
+ if (this.helperPath) {
705
+ try {
706
+ fs3.unlinkSync(this.helperPath);
707
+ } catch {
708
+ }
709
+ this.helperPath = null;
710
+ }
711
+ }
712
+ };
713
+
714
+ // src/services/pty/windows.strategy.ts
715
+ var import_child_process2 = require("child_process");
716
+ var WindowsPtyStrategy = class {
717
+ constructor(opts) {
718
+ this.opts = opts;
719
+ }
720
+ opts;
721
+ proc = null;
722
+ spawn(cmd, cwd) {
723
+ this.proc = (0, import_child_process2.spawn)(cmd, [], {
724
+ stdio: ["pipe", "pipe", "inherit"],
725
+ cwd,
726
+ env: {
727
+ ...process.env,
728
+ TERM: "xterm-256color",
729
+ COLUMNS: "220",
730
+ LINES: "50"
731
+ },
732
+ shell: true
733
+ });
734
+ this.proc.on("error", (err) => {
735
+ console.error(
736
+ `
737
+ \u2717 Failed to launch Claude Code: ${err.message}
738
+ Make sure claude is correctly installed: npm install -g @anthropic-ai/claude-code
739
+ `
740
+ );
741
+ process.exit(1);
742
+ });
743
+ this.proc.stdout?.on("data", (chunk) => {
744
+ process.stdout.write(chunk);
745
+ this.opts.onData(chunk.toString("utf8"));
746
+ });
747
+ this.proc.stdin?.write("");
748
+ if (process.stdin.isTTY) process.stdin.setRawMode(true);
749
+ process.stdin.resume();
750
+ process.stdin.on("data", this.stdinHandler);
751
+ this.proc.on("exit", (code) => {
752
+ this.dispose();
678
753
  this.opts.onExit(code ?? 0);
679
754
  });
680
755
  }
756
+ write(data) {
757
+ this.proc?.stdin?.write(data);
758
+ }
759
+ kill() {
760
+ this.proc?.kill();
761
+ this.dispose();
762
+ }
763
+ dispose() {
764
+ process.stdin.removeListener("data", this.stdinHandler);
765
+ if (process.stdin.isTTY) {
766
+ try {
767
+ process.stdin.setRawMode(false);
768
+ } catch {
769
+ }
770
+ }
771
+ }
772
+ stdinHandler = (chunk) => {
773
+ this.proc?.stdin?.write(chunk);
774
+ };
775
+ };
776
+
777
+ // src/services/claude.service.ts
778
+ var ClaudeService = class {
779
+ constructor(opts) {
780
+ this.opts = opts;
781
+ const strategyOpts = {
782
+ onData: opts.onData ?? (() => {
783
+ }),
784
+ onExit: opts.onExit
785
+ };
786
+ this.strategy = process.platform === "win32" ? new WindowsPtyStrategy(strategyOpts) : new UnixPtyStrategy(strategyOpts);
787
+ }
788
+ opts;
789
+ strategy;
790
+ spawn() {
791
+ if (!findInPath("claude") && !findInPath("claude-code")) {
792
+ console.error(
793
+ "\n \u2717 claude not found in PATH.\n Install it with: npm install -g @anthropic-ai/claude-code\n"
794
+ );
795
+ process.exit(1);
796
+ }
797
+ const claudeCmd = findInPath("claude") ? "claude" : "claude-code";
798
+ this.strategy.spawn(claudeCmd, this.opts.cwd);
799
+ }
681
800
  /** Send a command to Claude's stdin (remote control from mobile). */
682
801
  sendCommand(text) {
683
- this.proc?.stdin?.write(text + "\r");
802
+ this.strategy.write(text + "\r");
684
803
  }
685
804
  /**
686
805
  * Navigate a React Ink selector to the given 0-based index and confirm.
@@ -696,76 +815,32 @@ var ClaudeService = class {
696
815
  */
697
816
  selectOption(index) {
698
817
  if (index <= 0) {
699
- this.proc?.stdin?.write("\r");
818
+ this.strategy.write("\r");
700
819
  return;
701
820
  }
702
821
  const ARROW_MS = 80;
703
822
  const ENTER_MS = 200;
704
823
  for (let i = 0; i < index; i++) {
705
824
  setTimeout(() => {
706
- this.proc?.stdin?.write("\x1B[B");
825
+ this.strategy.write("\x1B[B");
707
826
  }, i * ARROW_MS);
708
827
  }
709
828
  setTimeout(() => {
710
- this.proc?.stdin?.write("\r");
829
+ this.strategy.write("\r");
711
830
  }, index * ARROW_MS + ENTER_MS);
712
831
  }
713
832
  /** Send Escape key to Claude (cancels interactive prompts). */
714
833
  sendEscape() {
715
- this.proc?.stdin?.write("\x1B");
834
+ this.strategy.write("\x1B");
716
835
  }
717
836
  /** Send Ctrl+C to Claude. */
718
837
  interrupt() {
719
- this.proc?.stdin?.write("");
838
+ this.strategy.write("");
720
839
  }
721
840
  kill() {
722
- this.proc?.kill();
723
- this.removeTempFile();
724
- this.cleanup();
725
- }
726
- stdinHandler = (chunk) => {
727
- this.proc?.stdin?.write(chunk);
728
- };
729
- handleResize = () => {
730
- if (this.proc?.pid) {
731
- try {
732
- process.kill(this.proc.pid, "SIGWINCH");
733
- } catch {
734
- }
735
- }
736
- };
737
- cleanup() {
738
- process.removeListener("SIGWINCH", this.handleResize);
739
- process.stdin.removeListener("data", this.stdinHandler);
740
- if (process.stdin.isTTY) {
741
- try {
742
- process.stdin.setRawMode(false);
743
- } catch {
744
- }
745
- }
746
- }
747
- removeTempFile() {
748
- if (this.helperPath) {
749
- try {
750
- fs2.unlinkSync(this.helperPath);
751
- } catch {
752
- }
753
- this.helperPath = null;
754
- }
841
+ this.strategy.kill();
755
842
  }
756
843
  };
757
- function findInPath(name) {
758
- const dirs = (process.env.PATH ?? "").split(path2.delimiter);
759
- for (const dir of dirs) {
760
- const full = `${dir}/${name}`;
761
- try {
762
- fs2.accessSync(full, fs2.constants.X_OK);
763
- return full;
764
- } catch {
765
- }
766
- }
767
- return null;
768
- }
769
844
 
770
845
  // src/services/output.service.ts
771
846
  var https2 = __toESM(require("https"));
@@ -924,21 +999,34 @@ function detectSelector(lines) {
924
999
  };
925
1000
  }
926
1001
  function filterChrome(lines) {
927
- return lines.filter((line) => {
1002
+ const result = [];
1003
+ let skipEchoContinuation = false;
1004
+ for (const line of lines) {
928
1005
  const t = line.trim();
929
- if (!t) return false;
930
- if (/^[─━—═─\-]{3,}$/.test(t)) return false;
931
- if (/^[✳✢✶✻✽✴✷✸✹⠋⠙⠹⠸⠼⠴⠦⠧⠇⠏◐◑◒◓▁▂▃▄▅▆▇█]\s/.test(t)) return false;
932
- if (/esc.{0,5}to.{0,5}interrupt/i.test(t)) return false;
933
- if (/high\s*[·•]\s*\/effort/i.test(t)) return false;
934
- if (/^[❯>]\s*$/.test(t)) return false;
935
- if (/^[❯>]\s+\S/.test(t) && !/^[❯>]\s*\d+\./.test(t)) return false;
936
- if (/^\(thinking\)\s*$/.test(t)) return false;
937
- if (/^\?\s.*shortcut/i.test(t)) return false;
938
- if (/spending limit|usage limit/i.test(t) && t.length < 80) return false;
939
- if (/↑\s*\/?\s*↓\s*to\s*navigate/i.test(t)) return false;
940
- return true;
941
- });
1006
+ if (!t) {
1007
+ skipEchoContinuation = false;
1008
+ continue;
1009
+ }
1010
+ if (/^[─━—═─\-]{3,}$/.test(t)) {
1011
+ skipEchoContinuation = false;
1012
+ continue;
1013
+ }
1014
+ if (/^[✳✢✶✻✽✴✷✸✹⠋⠙⠹⠸⠼⠴⠦⠧⠇⠏◐◑◒◓▁▂▃▄▅▆▇█]\s/.test(t)) continue;
1015
+ if (/esc.{0,5}to.{0,5}interrupt/i.test(t)) continue;
1016
+ if (/high\s*[·•]\s*\/effort/i.test(t)) continue;
1017
+ if (/^[❯>]\s*$/.test(t)) continue;
1018
+ if (/^\(thinking\)\s*$/.test(t)) continue;
1019
+ if (/^\?\s.*shortcut/i.test(t)) continue;
1020
+ if (/spending limit|usage limit/i.test(t) && t.length < 80) continue;
1021
+ if (/↑\s*\/?\s*↓\s*to\s*navigate/i.test(t)) continue;
1022
+ if (/^[❯>]\s+\S/.test(t) && !/^[❯>]\s*\d+\./.test(t)) {
1023
+ skipEchoContinuation = true;
1024
+ continue;
1025
+ }
1026
+ if (skipEchoContinuation) continue;
1027
+ result.push(line);
1028
+ }
1029
+ return result;
942
1030
  }
943
1031
  var OutputService = class _OutputService {
944
1032
  constructor(sessionId, pluginId) {
@@ -1090,8 +1178,8 @@ var OutputService = class _OutputService {
1090
1178
  function saveFilesTemp(files) {
1091
1179
  return files.filter(({ base64 }) => base64 && base64.length > 0).map(({ filename, base64 }) => {
1092
1180
  const safeName = filename.replace(/[^a-zA-Z0-9._-]/g, "_").slice(0, 80);
1093
- const tmpPath = path3.join(os4.tmpdir(), `codeam-${(0, import_crypto.randomUUID)()}-${safeName}`);
1094
- fs3.writeFileSync(tmpPath, Buffer.from(base64, "base64"));
1181
+ const tmpPath = path4.join(os4.tmpdir(), `codeam-${(0, import_crypto.randomUUID)()}-${safeName}`);
1182
+ fs4.writeFileSync(tmpPath, Buffer.from(base64, "base64"));
1095
1183
  return tmpPath;
1096
1184
  });
1097
1185
  }
@@ -1127,7 +1215,7 @@ async function start() {
1127
1215
  setTimeout(() => {
1128
1216
  for (const p2 of paths) {
1129
1217
  try {
1130
- fs3.unlinkSync(p2);
1218
+ fs4.unlinkSync(p2);
1131
1219
  } catch {
1132
1220
  }
1133
1221
  }
@@ -1178,7 +1266,7 @@ async function start() {
1178
1266
  setTimeout(() => {
1179
1267
  for (const p2 of paths) {
1180
1268
  try {
1181
- fs3.unlinkSync(p2);
1269
+ fs4.unlinkSync(p2);
1182
1270
  } catch {
1183
1271
  }
1184
1272
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "codeam-cli",
3
- "version": "1.3.6",
3
+ "version": "1.3.8",
4
4
  "description": "Remote control Claude Code from your mobile device",
5
5
  "main": "dist/index.js",
6
6
  "bin": {