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.
- package/dist/index.js +194 -32
- 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.
|
|
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
|
|
739
|
-
|
|
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
|
-
|
|
749
|
-
|
|
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 =
|
|
756
|
-
/** No new content for 3 s = Claude finished responding. */
|
|
898
|
+
static POLL_MS = 1e3;
|
|
757
899
|
static IDLE_MS = 3e3;
|
|
758
|
-
/**
|
|
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.
|
|
769
|
-
this.
|
|
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
|
-
|
|
781
|
-
|
|
782
|
-
|
|
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
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
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 (
|
|
811
|
-
this.
|
|
812
|
-
this.postChunk({ type: "text", content
|
|
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
|
|
962
|
+
const lines = renderToLines(this.rawBuffer);
|
|
963
|
+
const selector = detectSelector(lines);
|
|
818
964
|
this.stopPoll();
|
|
819
965
|
this.active = false;
|
|
820
|
-
|
|
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
|
}
|