codeam-cli 1.1.1 → 1.1.3

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 +194 -32
  2. 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.1.1",
113
+ version: "1.1.3",
114
114
  description: "Remote control Claude Code from your mobile device",
115
115
  main: "dist/index.js",
116
116
  bin: {
@@ -735,8 +735,152 @@ function findInPath(name) {
735
735
  var https2 = __toESM(require("https"));
736
736
  var http2 = __toESM(require("http"));
737
737
  var API_BASE4 = process.env.CODEAM_API_URL ?? "https://codeagent-mobile-api.vercel.app";
738
- function stripAnsi(raw) {
739
- return raw.replace(/\x1B\[[0-?]*[ -/]*[@-~]/g, "").replace(/\x1B\][^\x07\x1B]*(?:\x07|\x1B\\)/g, "").replace(/\x1B[@-Z\\-_]/g, "").replace(/\r\n/g, "\n").replace(/\r/g, "\n");
738
+ function renderToLines(raw) {
739
+ const screen = [""];
740
+ let row = 0;
741
+ let col = 0;
742
+ function ensureRow() {
743
+ while (screen.length <= row) screen.push("");
744
+ }
745
+ function writeChar(ch) {
746
+ ensureRow();
747
+ if (col < screen[row].length) {
748
+ screen[row] = screen[row].slice(0, col) + ch + screen[row].slice(col + 1);
749
+ } else {
750
+ while (screen[row].length < col) screen[row] += " ";
751
+ screen[row] += ch;
752
+ }
753
+ col++;
754
+ }
755
+ let i = 0;
756
+ while (i < raw.length) {
757
+ const ch = raw[i];
758
+ if (ch === "\x1B") {
759
+ i++;
760
+ if (i >= raw.length) break;
761
+ if (raw[i] === "[") {
762
+ i++;
763
+ let param = "";
764
+ while (i < raw.length && !/[@-~]/.test(raw[i])) param += raw[i++];
765
+ const cmd = raw[i] ?? "";
766
+ const n = parseInt(param) || 1;
767
+ if (cmd === "A") {
768
+ row = Math.max(0, row - n);
769
+ } else if (cmd === "B") {
770
+ row += n;
771
+ ensureRow();
772
+ } else if (cmd === "C") {
773
+ col += n;
774
+ } else if (cmd === "D") {
775
+ col = Math.max(0, col - n);
776
+ } else if (cmd === "G") {
777
+ col = Math.max(0, n - 1);
778
+ } else if (cmd === "H" || cmd === "f") {
779
+ const p2 = param.split(";");
780
+ row = Math.max(0, (parseInt(p2[0] ?? "1") || 1) - 1);
781
+ col = Math.max(0, (parseInt(p2[1] ?? "1") || 1) - 1);
782
+ ensureRow();
783
+ } else if (cmd === "J") {
784
+ if (param === "2" || param === "3") {
785
+ screen.length = 1;
786
+ screen[0] = "";
787
+ row = 0;
788
+ col = 0;
789
+ } else if (param === "1") {
790
+ for (let r = 0; r < row; r++) screen[r] = "";
791
+ screen[row] = " ".repeat(col) + screen[row].slice(col);
792
+ } else {
793
+ screen[row] = screen[row].slice(0, col);
794
+ screen.splice(row + 1);
795
+ }
796
+ } else if (cmd === "K") {
797
+ ensureRow();
798
+ if (param === "" || param === "0") screen[row] = screen[row].slice(0, col);
799
+ else if (param === "1") screen[row] = " ".repeat(col) + screen[row].slice(col);
800
+ else if (param === "2") screen[row] = "";
801
+ } else if (cmd === "h" && (param === "?1049" || param === "?47")) {
802
+ screen.length = 1;
803
+ screen[0] = "";
804
+ row = 0;
805
+ col = 0;
806
+ } else if (cmd === "l" && (param === "?1049" || param === "?47")) {
807
+ screen.length = 1;
808
+ screen[0] = "";
809
+ row = 0;
810
+ col = 0;
811
+ }
812
+ } else if (raw[i] === "]") {
813
+ i++;
814
+ while (i < raw.length) {
815
+ if (raw[i] === "\x07") break;
816
+ if (raw[i] === "\x1B" && i + 1 < raw.length && raw[i + 1] === "\\") {
817
+ i++;
818
+ break;
819
+ }
820
+ i++;
821
+ }
822
+ }
823
+ } else if (ch === "\r") {
824
+ if (i + 1 < raw.length && raw[i + 1] === "\n") {
825
+ row++;
826
+ col = 0;
827
+ ensureRow();
828
+ i++;
829
+ } else {
830
+ col = 0;
831
+ }
832
+ } else if (ch === "\n") {
833
+ row++;
834
+ col = 0;
835
+ ensureRow();
836
+ } else if (ch >= " " || ch === " ") {
837
+ writeChar(ch);
838
+ }
839
+ i++;
840
+ }
841
+ return screen;
842
+ }
843
+ function detectSelector(lines) {
844
+ const hasNav = lines.some((l) => /Enter to select.*↑.*↓.*navigate/i.test(l));
845
+ if (!hasNav) return null;
846
+ const options = [];
847
+ const questionLines = [];
848
+ for (const line of lines) {
849
+ const t = line.trim();
850
+ if (!t) continue;
851
+ if (/^[─━—═]{3,}$/.test(t)) continue;
852
+ if (/^[✳✢✶✻✽⠋⠙⠹⠸⠼⠴⠦⠧⠇⠏]\s/.test(t)) continue;
853
+ if (/esc.{0,5}to.{0,5}interrupt/i.test(t)) continue;
854
+ if (/high\s*[·•]\s*\/effort/i.test(t)) continue;
855
+ if (/Enter to select/i.test(t)) continue;
856
+ if (/^[❯>]\s*$/.test(t)) continue;
857
+ const optMatch = t.match(/^[❯>]?\s*(\d+)\.\s+(.+)/);
858
+ if (optMatch) {
859
+ options.push(optMatch[2].trim());
860
+ continue;
861
+ }
862
+ if (line.length > 0 && (line[0] === " " || line[0] === " ") && line.trim().length > 0) continue;
863
+ if (/^[❯>]\s+\S/.test(t)) continue;
864
+ questionLines.push(t);
865
+ }
866
+ if (options.length === 0) return null;
867
+ return { question: questionLines.join("\n").trim(), options };
868
+ }
869
+ function filterChrome(lines) {
870
+ return lines.filter((line) => {
871
+ const t = line.trim();
872
+ if (!t) return false;
873
+ if (/^[─━—═─\-]{3,}$/.test(t)) return false;
874
+ if (/^[✳✢✶✻✽✴✷✸✹⠋⠙⠹⠸⠼⠴⠦⠧⠇⠏◐◑◒◓▁▂▃▄▅▆▇█]\s/.test(t)) return false;
875
+ if (/esc.{0,5}to.{0,5}interrupt/i.test(t)) return false;
876
+ if (/high\s*[·•]\s*\/effort/i.test(t)) return false;
877
+ if (/^[❯>]\s*$/.test(t)) return false;
878
+ if (/^[❯>]\s+\S/.test(t)) return false;
879
+ if (/^\(thinking\)\s*$/.test(t)) return false;
880
+ if (/^\?\s.*shortcut/i.test(t)) return false;
881
+ if (/spending limit|usage limit/i.test(t) && t.length < 80) return false;
882
+ return true;
883
+ });
740
884
  }
741
885
  var OutputService = class _OutputService {
742
886
  constructor(sessionId, pluginId) {
@@ -745,28 +889,22 @@ var OutputService = class _OutputService {
745
889
  }
746
890
  sessionId;
747
891
  pluginId;
748
- buffer = "";
749
- lastSentBuffer = "";
892
+ rawBuffer = "";
893
+ lastSentContent = "";
750
894
  pollTimer = null;
751
895
  startTime = 0;
752
896
  active = false;
753
- /** Time since last meaningful push() — when this exceeds IDLE_MS, response is done. */
754
897
  lastPushTime = 0;
755
- static POLL_MS = 500;
756
- /** No new content for 3 s = Claude finished responding. */
898
+ static POLL_MS = 1e3;
757
899
  static IDLE_MS = 3e3;
758
- /** No content at all for 30 s = give up (connection issue, etc.). */
900
+ /** Shorter idle threshold for selector detection (UI is ready immediately). */
901
+ static SELECTOR_IDLE_MS = 1500;
759
902
  static EMPTY_TIMEOUT_MS = 3e4;
760
- /** Hard cap — always clear typing state after 2 minutes. */
761
903
  static MAX_MS = 12e4;
762
- /**
763
- * Call before sending a command from mobile.
764
- * Clears previous output, sends new_turn event, starts polling.
765
- */
766
904
  newTurn() {
767
905
  this.stopPoll();
768
- this.buffer = "";
769
- this.lastSentBuffer = "";
906
+ this.rawBuffer = "";
907
+ this.lastSentContent = "";
770
908
  this.lastPushTime = 0;
771
909
  this.active = true;
772
910
  this.startTime = Date.now();
@@ -774,14 +912,11 @@ var OutputService = class _OutputService {
774
912
  });
775
913
  this.pollTimer = setInterval(() => this.tick(), _OutputService.POLL_MS);
776
914
  }
777
- /** Feed raw terminal output from Claude (called on every stdout chunk). */
778
915
  push(raw) {
779
916
  if (!this.active) return;
780
- const text = stripAnsi(raw).replace(/[ \t]+\n/g, "\n").replace(/\n{3,}/g, "\n\n");
781
- if (text.trim()) {
782
- this.buffer += text;
783
- this.lastPushTime = Date.now();
784
- }
917
+ this.rawBuffer += raw;
918
+ const printable = raw.replace(/\x1B\[[^@-~]*[@-~]/g, "").replace(/[\x00-\x1F\x7F]/g, "");
919
+ if (printable.trim()) this.lastPushTime = Date.now();
785
920
  }
786
921
  dispose() {
787
922
  this.stopPoll();
@@ -795,30 +930,47 @@ var OutputService = class _OutputService {
795
930
  this.finalize();
796
931
  return;
797
932
  }
798
- const hasContent = this.buffer.trim().length > 0;
799
- if (!hasContent) {
800
- if (elapsed >= _OutputService.EMPTY_TIMEOUT_MS) {
801
- this.finalize();
933
+ const lines = renderToLines(this.rawBuffer);
934
+ const selector = detectSelector(lines);
935
+ if (selector) {
936
+ const idleMs2 = this.lastPushTime > 0 ? now - this.lastPushTime : elapsed;
937
+ if (idleMs2 >= _OutputService.SELECTOR_IDLE_MS) {
938
+ this.stopPoll();
939
+ this.active = false;
940
+ this.postChunk({ type: "select_prompt", content: selector.question, options: selector.options, done: true }).catch(() => {
941
+ });
802
942
  }
803
943
  return;
804
944
  }
945
+ const content = filterChrome(lines).join("\n").replace(/\n{3,}/g, "\n\n").trim();
946
+ if (!content) {
947
+ if (elapsed >= _OutputService.EMPTY_TIMEOUT_MS) this.finalize();
948
+ return;
949
+ }
805
950
  const idleMs = this.lastPushTime > 0 ? now - this.lastPushTime : elapsed;
806
951
  if (idleMs >= _OutputService.IDLE_MS) {
807
952
  this.finalize();
808
953
  return;
809
954
  }
810
- if (this.buffer !== this.lastSentBuffer) {
811
- this.lastSentBuffer = this.buffer;
812
- this.postChunk({ type: "text", content: this.buffer, done: false }).catch(() => {
955
+ if (content !== this.lastSentContent) {
956
+ this.lastSentContent = content;
957
+ this.postChunk({ type: "text", content, done: false }).catch(() => {
813
958
  });
814
959
  }
815
960
  }
816
961
  finalize() {
817
- const text = this.buffer;
962
+ const lines = renderToLines(this.rawBuffer);
963
+ const selector = detectSelector(lines);
818
964
  this.stopPoll();
819
965
  this.active = false;
820
- this.postChunk({ type: "text", content: text, done: true }).catch(() => {
821
- });
966
+ if (selector) {
967
+ this.postChunk({ type: "select_prompt", content: selector.question, options: selector.options, done: true }).catch(() => {
968
+ });
969
+ } else {
970
+ const content = filterChrome(lines).join("\n").replace(/\n{3,}/g, "\n\n").trim();
971
+ this.postChunk({ type: "text", content, done: true }).catch(() => {
972
+ });
973
+ }
822
974
  }
823
975
  stopPoll() {
824
976
  if (this.pollTimer) {
@@ -907,6 +1059,12 @@ async function start() {
907
1059
  if (input) sendPrompt(input);
908
1060
  break;
909
1061
  }
1062
+ case "select_option": {
1063
+ const index = cmd.payload.index ?? 0;
1064
+ const arrows = "\x1B[B".repeat(Math.max(0, index));
1065
+ sendPrompt(arrows);
1066
+ break;
1067
+ }
910
1068
  case "stop_task":
911
1069
  claude.interrupt();
912
1070
  break;
@@ -927,6 +1085,10 @@ async function start() {
927
1085
  } else if (cmdType === "provide_input") {
928
1086
  const input = inner.input;
929
1087
  if (input) sendPrompt(input);
1088
+ } else if (cmdType === "select_option") {
1089
+ const index = inner.index ?? 0;
1090
+ const arrows = "\x1B[B".repeat(Math.max(0, index));
1091
+ sendPrompt(arrows);
930
1092
  } else if (cmdType === "stop_task") {
931
1093
  claude.interrupt();
932
1094
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "codeam-cli",
3
- "version": "1.1.1",
3
+ "version": "1.1.3",
4
4
  "description": "Remote control Claude Code from your mobile device",
5
5
  "main": "dist/index.js",
6
6
  "bin": {