codeam-cli 1.1.0 → 1.1.2

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 +176 -50
  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.0",
113
+ version: "1.1.2",
114
114
  description: "Remote control Claude Code from your mobile device",
115
115
  main: "dist/index.js",
116
116
  bin: {
@@ -735,8 +735,131 @@ 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 filterChrome(lines) {
844
+ return lines.filter((line) => {
845
+ const t = line.trim();
846
+ if (!t) return false;
847
+ if (/^[─━—═\-─]{3,}$/.test(t)) return false;
848
+ if (/^[✳✢✶✻✽✴✷✸✹⠋⠙⠹⠸⠼⠴⠦⠧⠇⠏◐◑◒◓▁▂▃▄▅▆▇█]\s/.test(t)) return false;
849
+ if (/esc.{0,5}to.{0,5}interrupt/i.test(t)) return false;
850
+ if (/high\s*[·•]\s*\/effort/i.test(t)) return false;
851
+ if (/^[❯>]\s*$/.test(t)) return false;
852
+ if (/^[❯>]\s+\S/.test(t)) return false;
853
+ if (/^\(thinking\)\s*$/.test(t)) return false;
854
+ if (/^\?\s.*shortcut/i.test(t)) return false;
855
+ if (/spending limit|usage limit/i.test(t) && t.length < 80) return false;
856
+ return true;
857
+ });
858
+ }
859
+ function extractContent(raw) {
860
+ const lines = renderToLines(raw);
861
+ const filtered = filterChrome(lines);
862
+ return filtered.join("\n").replace(/\n{3,}/g, "\n\n").trim();
740
863
  }
741
864
  var OutputService = class _OutputService {
742
865
  constructor(sessionId, pluginId) {
@@ -745,81 +868,71 @@ var OutputService = class _OutputService {
745
868
  }
746
869
  sessionId;
747
870
  pluginId;
748
- // Buffer that accumulates PTY text (ANSI stripped, growing during response)
749
- buffer = "";
750
- lastSentBuffer = "";
751
- stableCount = 0;
871
+ /** Raw PTY bytes processed only when we need to send content. */
872
+ rawBuffer = "";
873
+ lastSentContent = "";
752
874
  pollTimer = null;
753
875
  startTime = 0;
754
876
  active = false;
755
- // After this many stable polls (POLL_MS each) with content, mark done
877
+ /** When the last chunk of printable raw content arrived. */
878
+ lastPushTime = 0;
756
879
  static POLL_MS = 1e3;
757
- static STABLE_THRESHOLD = 3;
758
- // Give up after 2 minutes (absolute) or 30 seconds with no content at all
759
- static MAX_MS = 12e4;
880
+ /** No new printable content for 3 s → Claude finished responding. */
881
+ static IDLE_MS = 3e3;
882
+ /** No content at all for 30 s → give up. */
760
883
  static EMPTY_TIMEOUT_MS = 3e4;
761
- /**
762
- * Call before sending a command from mobile.
763
- * Clears previous output, sends new_turn event, starts polling.
764
- */
884
+ /** Hard cap — clear typing state after 2 min no matter what. */
885
+ static MAX_MS = 12e4;
765
886
  newTurn() {
766
887
  this.stopPoll();
767
- this.buffer = "";
768
- this.lastSentBuffer = "";
769
- this.stableCount = 0;
888
+ this.rawBuffer = "";
889
+ this.lastSentContent = "";
890
+ this.lastPushTime = 0;
770
891
  this.active = true;
771
892
  this.startTime = Date.now();
772
893
  this.postChunk({ clear: true }).then(() => this.postChunk({ type: "new_turn", content: "", done: false })).catch(() => {
773
894
  });
774
- this.pollTimer = setInterval(() => this.checkStability(), _OutputService.POLL_MS);
895
+ this.pollTimer = setInterval(() => this.tick(), _OutputService.POLL_MS);
775
896
  }
776
- /** Feed raw terminal output from Claude (called on every stdout chunk). */
777
897
  push(raw) {
778
898
  if (!this.active) return;
779
- const text = stripAnsi(raw).replace(/[ \t]+\n/g, "\n").replace(/\n{3,}/g, "\n\n");
780
- if (text) this.buffer += text;
899
+ this.rawBuffer += raw;
900
+ const printable = raw.replace(/\x1B\[[^@-~]*[@-~]/g, "").replace(/[\x00-\x1F\x7F]/g, "");
901
+ if (printable.trim()) this.lastPushTime = Date.now();
781
902
  }
782
903
  dispose() {
783
904
  this.stopPoll();
784
905
  this.active = false;
785
906
  }
786
- /**
787
- * Stability check — runs every POLL_MS while active.
788
- *
789
- * Only finalises when we have content that has stopped changing.
790
- * This avoids premature done signals while Claude is still thinking
791
- * (which would set active=false and silently drop the real response).
792
- */
793
- checkStability() {
907
+ tick() {
794
908
  if (!this.active) return;
795
- if (Date.now() - this.startTime > _OutputService.MAX_MS) {
909
+ const now = Date.now();
910
+ const elapsed = now - this.startTime;
911
+ if (elapsed >= _OutputService.MAX_MS) {
796
912
  this.finalize();
797
913
  return;
798
914
  }
799
- const current = this.buffer;
800
- if (!current.trim()) {
801
- if (Date.now() - this.startTime > _OutputService.EMPTY_TIMEOUT_MS) {
802
- this.finalize();
803
- }
915
+ const content = extractContent(this.rawBuffer);
916
+ if (!content) {
917
+ if (elapsed >= _OutputService.EMPTY_TIMEOUT_MS) this.finalize();
804
918
  return;
805
919
  }
806
- if (current === this.lastSentBuffer) {
807
- this.stableCount++;
808
- if (this.stableCount >= _OutputService.STABLE_THRESHOLD) {
809
- this.finalize();
810
- }
811
- } else {
812
- this.stableCount = 0;
813
- this.lastSentBuffer = current;
814
- this.postChunk({ type: "text", content: current, done: false }).catch(() => {
920
+ const idleMs = this.lastPushTime > 0 ? now - this.lastPushTime : elapsed;
921
+ if (idleMs >= _OutputService.IDLE_MS) {
922
+ this.finalize();
923
+ return;
924
+ }
925
+ if (content !== this.lastSentContent) {
926
+ this.lastSentContent = content;
927
+ this.postChunk({ type: "text", content, done: false }).catch(() => {
815
928
  });
816
929
  }
817
930
  }
818
931
  finalize() {
819
- const text = this.buffer;
932
+ const content = extractContent(this.rawBuffer);
820
933
  this.stopPoll();
821
934
  this.active = false;
822
- this.postChunk({ type: "text", content: text, done: true }).catch(() => {
935
+ this.postChunk({ type: "text", content, done: true }).catch(() => {
823
936
  });
824
937
  }
825
938
  stopPoll() {
@@ -850,11 +963,24 @@ var OutputService = class _OutputService {
850
963
  timeout: 8e3
851
964
  },
852
965
  (res) => {
853
- res.resume();
854
- resolve();
966
+ let body2 = "";
967
+ res.on("data", (c) => {
968
+ body2 += c.toString();
969
+ });
970
+ res.on("end", () => {
971
+ if (res.statusCode && res.statusCode >= 400) {
972
+ process.stderr.write(`[codeam] output API error ${res.statusCode}: ${body2}
973
+ `);
974
+ }
975
+ resolve();
976
+ });
855
977
  }
856
978
  );
857
- req.on("error", () => resolve());
979
+ req.on("error", (err) => {
980
+ process.stderr.write(`[codeam] output POST failed: ${err.message}
981
+ `);
982
+ resolve();
983
+ });
858
984
  req.on("timeout", () => {
859
985
  req.destroy();
860
986
  resolve();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "codeam-cli",
3
- "version": "1.1.0",
3
+ "version": "1.1.2",
4
4
  "description": "Remote control Claude Code from your mobile device",
5
5
  "main": "dist/index.js",
6
6
  "bin": {