codeam-cli 1.3.8 → 1.4.0
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 +93 -25
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -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.
|
|
117
|
+
version: "1.4.0",
|
|
118
118
|
description: "Remote control Claude Code from your mobile device",
|
|
119
119
|
main: "dist/index.js",
|
|
120
120
|
bin: {
|
|
@@ -802,32 +802,41 @@ var ClaudeService = class {
|
|
|
802
802
|
this.strategy.write(text + "\r");
|
|
803
803
|
}
|
|
804
804
|
/**
|
|
805
|
-
* Navigate a React Ink selector to the given 0-based index and confirm.
|
|
805
|
+
* Navigate a React Ink selector to the given 0-based target index and confirm.
|
|
806
|
+
*
|
|
807
|
+
* `fromIndex` is the current highlighted position (defaults to 0 for
|
|
808
|
+
* numbered selectors which always start at the first option). For list-style
|
|
809
|
+
* selectors (e.g. /mcp), the CLI sends `currentIndex` in the select_prompt
|
|
810
|
+
* chunk so the client can pass it back here as `fromIndex`, enabling both
|
|
811
|
+
* up-arrow and down-arrow navigation without always rewinding to position 0.
|
|
806
812
|
*
|
|
807
813
|
* Why not sendCommand(arrows + Enter) in one write()?
|
|
808
814
|
* All bytes arrive as one chunk → readline fires all keypress events in the
|
|
809
815
|
* same synchronous run → React Ink batches the state updates → each arrow
|
|
810
816
|
* sees selectedIndex=0 → final state is still 0 or 1 → wrong option selected.
|
|
811
817
|
*
|
|
812
|
-
* Fix: send each
|
|
813
|
-
*
|
|
818
|
+
* Fix: send each arrow in a separate write(), ARROW_MS apart, so React has
|
|
819
|
+
* time to process and re-render between each keystroke. Enter is sent
|
|
814
820
|
* ENTER_MS after the last arrow.
|
|
815
821
|
*/
|
|
816
|
-
selectOption(
|
|
817
|
-
|
|
822
|
+
selectOption(targetIndex, fromIndex = 0) {
|
|
823
|
+
const delta = targetIndex - fromIndex;
|
|
824
|
+
const steps = Math.abs(delta);
|
|
825
|
+
const arrow = delta >= 0 ? "\x1B[B" : "\x1B[A";
|
|
826
|
+
const ARROW_MS = 80;
|
|
827
|
+
const ENTER_MS = 200;
|
|
828
|
+
if (steps === 0) {
|
|
818
829
|
this.strategy.write("\r");
|
|
819
830
|
return;
|
|
820
831
|
}
|
|
821
|
-
|
|
822
|
-
const ENTER_MS = 200;
|
|
823
|
-
for (let i = 0; i < index; i++) {
|
|
832
|
+
for (let i = 0; i < steps; i++) {
|
|
824
833
|
setTimeout(() => {
|
|
825
|
-
this.strategy.write(
|
|
834
|
+
this.strategy.write(arrow);
|
|
826
835
|
}, i * ARROW_MS);
|
|
827
836
|
}
|
|
828
837
|
setTimeout(() => {
|
|
829
838
|
this.strategy.write("\r");
|
|
830
|
-
},
|
|
839
|
+
}, steps * ARROW_MS + ENTER_MS);
|
|
831
840
|
}
|
|
832
841
|
/** Send Escape key to Claude (cancels interactive prompts). */
|
|
833
842
|
sendEscape() {
|
|
@@ -953,10 +962,14 @@ function renderToLines(raw) {
|
|
|
953
962
|
}
|
|
954
963
|
function detectSelector(lines) {
|
|
955
964
|
if (lines.some((l) => /\?\s+for\s+shortcuts/i.test(l.trim()))) return null;
|
|
956
|
-
|
|
965
|
+
const clean = lines.map(
|
|
966
|
+
(l) => l.replace(/^[│╭╰╮╯┌└┐┘├┤┬┴┼]\s?/, "").replace(/\s*[│╭╰╮╯┌└┐┘├┤┬┴┼─━═]+\s*$/, "")
|
|
967
|
+
// strip trailing fill + border
|
|
968
|
+
);
|
|
969
|
+
if (!clean.some((l) => /^❯\s*\d+\./.test(l.trim()))) return null;
|
|
957
970
|
let optionStartIdx = -1;
|
|
958
|
-
for (let i = 0; i <
|
|
959
|
-
if (/^(?:❯\s*)?\d+\.\s/.test(
|
|
971
|
+
for (let i = 0; i < clean.length; i++) {
|
|
972
|
+
if (/^(?:❯\s*)?\d+\.\s/.test(clean[i].trim())) {
|
|
960
973
|
optionStartIdx = i;
|
|
961
974
|
break;
|
|
962
975
|
}
|
|
@@ -964,7 +977,7 @@ function detectSelector(lines) {
|
|
|
964
977
|
if (optionStartIdx === -1) return null;
|
|
965
978
|
const questionParts = [];
|
|
966
979
|
for (let i = 0; i < optionStartIdx; i++) {
|
|
967
|
-
const t =
|
|
980
|
+
const t = clean[i].trim();
|
|
968
981
|
if (!t) continue;
|
|
969
982
|
if (/^[─━—═\-]{3,}$/.test(t)) continue;
|
|
970
983
|
if (/^\[.*\]$/.test(t)) continue;
|
|
@@ -975,8 +988,8 @@ function detectSelector(lines) {
|
|
|
975
988
|
const optionLabels = /* @__PURE__ */ new Map();
|
|
976
989
|
const optionDescs = /* @__PURE__ */ new Map();
|
|
977
990
|
let currentNum = -1;
|
|
978
|
-
for (let i = optionStartIdx; i <
|
|
979
|
-
const t =
|
|
991
|
+
for (let i = optionStartIdx; i < clean.length; i++) {
|
|
992
|
+
const t = clean[i].trim();
|
|
980
993
|
if (!t) continue;
|
|
981
994
|
const m = t.match(/^(?:❯\s*)?(\d+)\.\s+(.+)/);
|
|
982
995
|
if (m) {
|
|
@@ -986,7 +999,7 @@ function detectSelector(lines) {
|
|
|
986
999
|
optionDescs.set(num, []);
|
|
987
1000
|
}
|
|
988
1001
|
currentNum = num;
|
|
989
|
-
} else if (currentNum !== -1 && !/^Enter to/i.test(t) && !/^[─━—═\-]{3,}$/.test(t) && !/↑.*↓.*navigate/i.test(t) && !/Esc to
|
|
1002
|
+
} else if (currentNum !== -1 && !/^Enter to/i.test(t) && !/^[─━—═\-]{3,}$/.test(t) && !/↑.*↓.*navigate/i.test(t) && !/Esc to/i.test(t)) {
|
|
990
1003
|
optionDescs.get(currentNum)?.push(t);
|
|
991
1004
|
}
|
|
992
1005
|
}
|
|
@@ -995,7 +1008,60 @@ function detectSelector(lines) {
|
|
|
995
1008
|
return {
|
|
996
1009
|
question,
|
|
997
1010
|
options: keys.map((k) => optionLabels.get(k)),
|
|
998
|
-
optionDescriptions: keys.map((k) => (optionDescs.get(k) ?? []).join(" ").trim())
|
|
1011
|
+
optionDescriptions: keys.map((k) => (optionDescs.get(k) ?? []).join(" ").trim()),
|
|
1012
|
+
currentIndex: 0
|
|
1013
|
+
};
|
|
1014
|
+
}
|
|
1015
|
+
function detectListSelector(lines) {
|
|
1016
|
+
if (!lines.some((l) => /[↑↓].*navigate/i.test(l.trim()))) return null;
|
|
1017
|
+
if (lines.some((l) => /^❯\s*\d+\./.test(l.trim()))) return null;
|
|
1018
|
+
if (!lines.some((l) => /^\s+❯\s+\S/.test(l))) return null;
|
|
1019
|
+
const isSelected = (line) => /^\s+❯\s+\S/.test(line);
|
|
1020
|
+
const isUnselected = (line) => /^ \S/.test(line);
|
|
1021
|
+
const isItem = (line) => isSelected(line) || isUnselected(line);
|
|
1022
|
+
let optionStartIdx = -1;
|
|
1023
|
+
for (let i = 0; i < lines.length; i++) {
|
|
1024
|
+
if (isItem(lines[i])) {
|
|
1025
|
+
optionStartIdx = i;
|
|
1026
|
+
break;
|
|
1027
|
+
}
|
|
1028
|
+
}
|
|
1029
|
+
if (optionStartIdx === -1) return null;
|
|
1030
|
+
const questionParts = [];
|
|
1031
|
+
for (let i = 0; i < optionStartIdx; i++) {
|
|
1032
|
+
const t = lines[i].trim();
|
|
1033
|
+
if (!t) continue;
|
|
1034
|
+
if (/^[─━—═\-]{3,}$/.test(t)) continue;
|
|
1035
|
+
if (/[┌└│┐┘├┤┬┴┼]/.test(t)) {
|
|
1036
|
+
const inner = t.replace(/[│┌└┐┘├┤┬┴┼─]/g, "").trim();
|
|
1037
|
+
if (inner) questionParts.push(inner);
|
|
1038
|
+
continue;
|
|
1039
|
+
}
|
|
1040
|
+
if (/^[>❯]\s/.test(t)) continue;
|
|
1041
|
+
if (/[↑↓].*navigate/i.test(t)) continue;
|
|
1042
|
+
questionParts.push(t);
|
|
1043
|
+
}
|
|
1044
|
+
const question = questionParts.join(" ").trim();
|
|
1045
|
+
const options = [];
|
|
1046
|
+
let currentIndex = 0;
|
|
1047
|
+
for (const line of lines.slice(optionStartIdx)) {
|
|
1048
|
+
const t = line.trim();
|
|
1049
|
+
if (!t) continue;
|
|
1050
|
+
if (/[↑↓].*navigate/i.test(t)) break;
|
|
1051
|
+
if (/^[─━—═\-]{3,}$/.test(t)) continue;
|
|
1052
|
+
if (isSelected(line)) {
|
|
1053
|
+
currentIndex = options.length;
|
|
1054
|
+
options.push(t.replace(/^❯\s+/, "").trim());
|
|
1055
|
+
} else if (isUnselected(line)) {
|
|
1056
|
+
options.push(t);
|
|
1057
|
+
}
|
|
1058
|
+
}
|
|
1059
|
+
if (options.length < 2) return null;
|
|
1060
|
+
return {
|
|
1061
|
+
question,
|
|
1062
|
+
options,
|
|
1063
|
+
optionDescriptions: options.map(() => ""),
|
|
1064
|
+
currentIndex
|
|
999
1065
|
};
|
|
1000
1066
|
}
|
|
1001
1067
|
function filterChrome(lines) {
|
|
@@ -1077,13 +1143,13 @@ var OutputService = class _OutputService {
|
|
|
1077
1143
|
return;
|
|
1078
1144
|
}
|
|
1079
1145
|
const lines = renderToLines(this.rawBuffer);
|
|
1080
|
-
const selector = detectSelector(lines);
|
|
1146
|
+
const selector = detectSelector(lines) ?? detectListSelector(lines);
|
|
1081
1147
|
if (selector) {
|
|
1082
1148
|
const idleMs2 = this.lastPushTime > 0 ? now - this.lastPushTime : elapsed;
|
|
1083
1149
|
if (idleMs2 >= _OutputService.SELECTOR_IDLE_MS) {
|
|
1084
1150
|
this.stopPoll();
|
|
1085
1151
|
this.active = false;
|
|
1086
|
-
this.postChunk({ type: "select_prompt", content: selector.question, options: selector.options, optionDescriptions: selector.optionDescriptions, done: true }).catch(() => {
|
|
1152
|
+
this.postChunk({ type: "select_prompt", content: selector.question, options: selector.options, optionDescriptions: selector.optionDescriptions, currentIndex: selector.currentIndex, done: true }).catch(() => {
|
|
1087
1153
|
});
|
|
1088
1154
|
}
|
|
1089
1155
|
return;
|
|
@@ -1106,11 +1172,11 @@ var OutputService = class _OutputService {
|
|
|
1106
1172
|
}
|
|
1107
1173
|
finalize() {
|
|
1108
1174
|
const lines = renderToLines(this.rawBuffer);
|
|
1109
|
-
const selector = detectSelector(lines);
|
|
1175
|
+
const selector = detectSelector(lines) ?? detectListSelector(lines);
|
|
1110
1176
|
this.stopPoll();
|
|
1111
1177
|
this.active = false;
|
|
1112
1178
|
if (selector) {
|
|
1113
|
-
this.postChunk({ type: "select_prompt", content: selector.question, options: selector.options, optionDescriptions: selector.optionDescriptions, done: true }).catch(() => {
|
|
1179
|
+
this.postChunk({ type: "select_prompt", content: selector.question, options: selector.options, optionDescriptions: selector.optionDescriptions, currentIndex: selector.currentIndex, done: true }).catch(() => {
|
|
1114
1180
|
});
|
|
1115
1181
|
} else {
|
|
1116
1182
|
const content = filterChrome(lines).join("\n").replace(/\n{3,}/g, "\n\n").trim();
|
|
@@ -1232,8 +1298,9 @@ async function start() {
|
|
|
1232
1298
|
}
|
|
1233
1299
|
case "select_option": {
|
|
1234
1300
|
const index = cmd.payload.index ?? 0;
|
|
1301
|
+
const from = cmd.payload.from ?? 0;
|
|
1235
1302
|
outputSvc.newTurn();
|
|
1236
|
-
claude.selectOption(index);
|
|
1303
|
+
claude.selectOption(index, from);
|
|
1237
1304
|
break;
|
|
1238
1305
|
}
|
|
1239
1306
|
case "escape_key":
|
|
@@ -1279,8 +1346,9 @@ async function start() {
|
|
|
1279
1346
|
if (input) sendPrompt(input);
|
|
1280
1347
|
} else if (cmdType === "select_option") {
|
|
1281
1348
|
const index = inner.index ?? 0;
|
|
1349
|
+
const from = inner.from ?? 0;
|
|
1282
1350
|
outputSvc.newTurn();
|
|
1283
|
-
claude.selectOption(index);
|
|
1351
|
+
claude.selectOption(index, from);
|
|
1284
1352
|
} else if (cmdType === "escape_key") {
|
|
1285
1353
|
outputSvc.newTurn();
|
|
1286
1354
|
claude.sendEscape();
|