codeam-cli 1.3.8 → 1.3.9

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 +82 -18
  2. 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.3.8",
117
+ version: "1.3.9",
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 down-arrow in a separate write(), ARROW_MS apart, so React
813
- * has time to process and re-render between each keystroke. Enter is sent
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(index) {
817
- if (index <= 0) {
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
- const ARROW_MS = 80;
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("\x1B[B");
834
+ this.strategy.write(arrow);
826
835
  }, i * ARROW_MS);
827
836
  }
828
837
  setTimeout(() => {
829
838
  this.strategy.write("\r");
830
- }, index * ARROW_MS + ENTER_MS);
839
+ }, steps * ARROW_MS + ENTER_MS);
831
840
  }
832
841
  /** Send Escape key to Claude (cancels interactive prompts). */
833
842
  sendEscape() {
@@ -995,7 +1004,60 @@ function detectSelector(lines) {
995
1004
  return {
996
1005
  question,
997
1006
  options: keys.map((k) => optionLabels.get(k)),
998
- optionDescriptions: keys.map((k) => (optionDescs.get(k) ?? []).join(" ").trim())
1007
+ optionDescriptions: keys.map((k) => (optionDescs.get(k) ?? []).join(" ").trim()),
1008
+ currentIndex: 0
1009
+ };
1010
+ }
1011
+ function detectListSelector(lines) {
1012
+ if (!lines.some((l) => /[↑↓].*navigate/i.test(l.trim()))) return null;
1013
+ if (lines.some((l) => /^❯\s*\d+\./.test(l.trim()))) return null;
1014
+ if (!lines.some((l) => /^\s+❯\s+\S/.test(l))) return null;
1015
+ const isSelected = (line) => /^\s+❯\s+\S/.test(line);
1016
+ const isUnselected = (line) => /^ \S/.test(line);
1017
+ const isItem = (line) => isSelected(line) || isUnselected(line);
1018
+ let optionStartIdx = -1;
1019
+ for (let i = 0; i < lines.length; i++) {
1020
+ if (isItem(lines[i])) {
1021
+ optionStartIdx = i;
1022
+ break;
1023
+ }
1024
+ }
1025
+ if (optionStartIdx === -1) return null;
1026
+ const questionParts = [];
1027
+ for (let i = 0; i < optionStartIdx; i++) {
1028
+ const t = lines[i].trim();
1029
+ if (!t) continue;
1030
+ if (/^[─━—═\-]{3,}$/.test(t)) continue;
1031
+ if (/[┌└│┐┘├┤┬┴┼]/.test(t)) {
1032
+ const inner = t.replace(/[│┌└┐┘├┤┬┴┼─]/g, "").trim();
1033
+ if (inner) questionParts.push(inner);
1034
+ continue;
1035
+ }
1036
+ if (/^[>❯]\s/.test(t)) continue;
1037
+ if (/[↑↓].*navigate/i.test(t)) continue;
1038
+ questionParts.push(t);
1039
+ }
1040
+ const question = questionParts.join(" ").trim();
1041
+ const options = [];
1042
+ let currentIndex = 0;
1043
+ for (const line of lines.slice(optionStartIdx)) {
1044
+ const t = line.trim();
1045
+ if (!t) continue;
1046
+ if (/[↑↓].*navigate/i.test(t)) break;
1047
+ if (/^[─━—═\-]{3,}$/.test(t)) continue;
1048
+ if (isSelected(line)) {
1049
+ currentIndex = options.length;
1050
+ options.push(t.replace(/^❯\s+/, "").trim());
1051
+ } else if (isUnselected(line)) {
1052
+ options.push(t);
1053
+ }
1054
+ }
1055
+ if (options.length < 2) return null;
1056
+ return {
1057
+ question,
1058
+ options,
1059
+ optionDescriptions: options.map(() => ""),
1060
+ currentIndex
999
1061
  };
1000
1062
  }
1001
1063
  function filterChrome(lines) {
@@ -1077,13 +1139,13 @@ var OutputService = class _OutputService {
1077
1139
  return;
1078
1140
  }
1079
1141
  const lines = renderToLines(this.rawBuffer);
1080
- const selector = detectSelector(lines);
1142
+ const selector = detectSelector(lines) ?? detectListSelector(lines);
1081
1143
  if (selector) {
1082
1144
  const idleMs2 = this.lastPushTime > 0 ? now - this.lastPushTime : elapsed;
1083
1145
  if (idleMs2 >= _OutputService.SELECTOR_IDLE_MS) {
1084
1146
  this.stopPoll();
1085
1147
  this.active = false;
1086
- this.postChunk({ type: "select_prompt", content: selector.question, options: selector.options, optionDescriptions: selector.optionDescriptions, done: true }).catch(() => {
1148
+ this.postChunk({ type: "select_prompt", content: selector.question, options: selector.options, optionDescriptions: selector.optionDescriptions, currentIndex: selector.currentIndex, done: true }).catch(() => {
1087
1149
  });
1088
1150
  }
1089
1151
  return;
@@ -1106,11 +1168,11 @@ var OutputService = class _OutputService {
1106
1168
  }
1107
1169
  finalize() {
1108
1170
  const lines = renderToLines(this.rawBuffer);
1109
- const selector = detectSelector(lines);
1171
+ const selector = detectSelector(lines) ?? detectListSelector(lines);
1110
1172
  this.stopPoll();
1111
1173
  this.active = false;
1112
1174
  if (selector) {
1113
- this.postChunk({ type: "select_prompt", content: selector.question, options: selector.options, optionDescriptions: selector.optionDescriptions, done: true }).catch(() => {
1175
+ this.postChunk({ type: "select_prompt", content: selector.question, options: selector.options, optionDescriptions: selector.optionDescriptions, currentIndex: selector.currentIndex, done: true }).catch(() => {
1114
1176
  });
1115
1177
  } else {
1116
1178
  const content = filterChrome(lines).join("\n").replace(/\n{3,}/g, "\n\n").trim();
@@ -1232,8 +1294,9 @@ async function start() {
1232
1294
  }
1233
1295
  case "select_option": {
1234
1296
  const index = cmd.payload.index ?? 0;
1297
+ const from = cmd.payload.from ?? 0;
1235
1298
  outputSvc.newTurn();
1236
- claude.selectOption(index);
1299
+ claude.selectOption(index, from);
1237
1300
  break;
1238
1301
  }
1239
1302
  case "escape_key":
@@ -1279,8 +1342,9 @@ async function start() {
1279
1342
  if (input) sendPrompt(input);
1280
1343
  } else if (cmdType === "select_option") {
1281
1344
  const index = inner.index ?? 0;
1345
+ const from = inner.from ?? 0;
1282
1346
  outputSvc.newTurn();
1283
- claude.selectOption(index);
1347
+ claude.selectOption(index, from);
1284
1348
  } else if (cmdType === "escape_key") {
1285
1349
  outputSvc.newTurn();
1286
1350
  claude.sendEscape();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "codeam-cli",
3
- "version": "1.3.8",
3
+ "version": "1.3.9",
4
4
  "description": "Remote control Claude Code from your mobile device",
5
5
  "main": "dist/index.js",
6
6
  "bin": {