@hydra-acp/cli 0.1.34 → 0.1.36

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/cli.js +599 -122
  2. package/package.json +1 -1
package/dist/cli.js CHANGED
@@ -5620,7 +5620,7 @@ async function pickSession(term, opts) {
5620
5620
  move(viewportSize);
5621
5621
  return;
5622
5622
  case "HOME":
5623
- move(-total);
5623
+ move(1 - selectedIdx);
5624
5624
  return;
5625
5625
  case "END":
5626
5626
  move(total);
@@ -5779,10 +5779,8 @@ var init_cwd = __esm({
5779
5779
  }
5780
5780
  });
5781
5781
 
5782
- // src/tui/import-cwd-prompt.ts
5783
- import * as os5 from "os";
5784
- async function promptForImportCwd(term, session, opts = {}) {
5785
- const defaultCwd = opts.defaultCwd ?? os5.homedir();
5782
+ // src/tui/prompt-utils.ts
5783
+ function resetTerminalModes() {
5786
5784
  process.stdout.write("\x1B[<u");
5787
5785
  process.stdout.write("\x1B[?2004l");
5788
5786
  process.stdout.write("\x1B[>4;0m");
@@ -5792,52 +5790,165 @@ async function promptForImportCwd(term, session, opts = {}) {
5792
5790
  process.stdout.write("\x1B[?1006l");
5793
5791
  process.stdout.write("\x1B[?1l");
5794
5792
  process.stdout.write("\x1B>");
5793
+ }
5794
+ function readTermWidth2(term) {
5795
+ return term.width ?? 80;
5796
+ }
5797
+ function readTermHeight2(term) {
5798
+ return term.height ?? 24;
5799
+ }
5800
+ function drawBox(term, opts) {
5801
+ const termW = readTermWidth2(term);
5802
+ const termH = readTermHeight2(term);
5803
+ const desiredContentW = opts.contentWidth ?? MAX_BOX_WIDTH;
5804
+ const maxContentW = Math.max(10, Math.min(MAX_BOX_WIDTH, termW - 4));
5805
+ const contentW = Math.min(desiredContentW, maxContentW);
5806
+ const w = contentW + 2;
5807
+ const contentH = Math.max(1, Math.min(opts.contentHeight, termH - 4));
5808
+ const h = contentH + 2;
5809
+ const x = Math.max(1, Math.floor((termW - w) / 2) + 1);
5810
+ const y = Math.max(1, Math.floor((termH - h) / 2) + 1);
5811
+ term.moveTo(1, 1).eraseDisplayBelow();
5812
+ const topInner = HORIZ.repeat(w - 2);
5813
+ const top = renderTitleStrip(topInner, opts.title);
5814
+ term.moveTo(x, y);
5815
+ term.dim.noFormat(TL);
5816
+ paintTopStrip(term, top);
5817
+ term.dim.noFormat(TR);
5818
+ for (let row = 1; row <= contentH; row++) {
5819
+ term.moveTo(x, y + row);
5820
+ term.dim.noFormat(VERT);
5821
+ term.moveTo(x + w - 1, y + row);
5822
+ term.dim.noFormat(VERT);
5823
+ }
5824
+ term.moveTo(x, y + h - 1);
5825
+ term.dim.noFormat(BL + HORIZ.repeat(w - 2) + BR);
5826
+ return {
5827
+ x,
5828
+ y,
5829
+ w,
5830
+ h,
5831
+ contentX: x + 1,
5832
+ contentY: y + 1,
5833
+ contentW,
5834
+ contentH
5835
+ };
5836
+ }
5837
+ function renderTitleStrip(innerDashes, title) {
5838
+ if (!title) {
5839
+ return { dashes: innerDashes };
5840
+ }
5841
+ const chip = ` ${title} `;
5842
+ if (chip.length + 4 > innerDashes.length) {
5843
+ return { dashes: innerDashes };
5844
+ }
5845
+ const offset = 2;
5846
+ const dashes = innerDashes.slice(0, offset) + " ".repeat(chip.length) + innerDashes.slice(offset + chip.length);
5847
+ return { dashes, title: { offset, text: chip } };
5848
+ }
5849
+ function paintTopStrip(term, strip) {
5850
+ if (!strip.title) {
5851
+ term.dim.noFormat(strip.dashes);
5852
+ return;
5853
+ }
5854
+ term.dim.noFormat(strip.dashes.slice(0, strip.title.offset));
5855
+ term.brightCyan.noFormat(strip.title.text);
5856
+ term.dim.noFormat(strip.dashes.slice(strip.title.offset + strip.title.text.length));
5857
+ }
5858
+ var MAX_BOX_WIDTH, HORIZ, VERT, TL, TR, BL, BR;
5859
+ var init_prompt_utils = __esm({
5860
+ "src/tui/prompt-utils.ts"() {
5861
+ "use strict";
5862
+ MAX_BOX_WIDTH = 64;
5863
+ HORIZ = "\u2500";
5864
+ VERT = "\u2502";
5865
+ TL = "\u250C";
5866
+ TR = "\u2510";
5867
+ BL = "\u2514";
5868
+ BR = "\u2518";
5869
+ }
5870
+ });
5871
+
5872
+ // src/tui/import-cwd-prompt.ts
5873
+ import * as os5 from "os";
5874
+ async function promptForImportCwd(term, session, opts = {}) {
5875
+ const defaultCwd = opts.defaultCwd ?? os5.homedir();
5876
+ resetTerminalModes();
5795
5877
  const shortId2 = stripHydraSessionPrefix(session.sessionId);
5796
5878
  const fromMachine = session.importedFromMachine ?? "another machine";
5797
- const originalCwd = session.cwd;
5879
+ const originalCwd = shortenHomePath(session.cwd);
5798
5880
  let buffer = defaultCwd;
5799
5881
  let errorLine = null;
5800
5882
  let busy = false;
5883
+ let layout = null;
5801
5884
  const render = () => {
5802
- term("\n");
5803
- term.bold.cyan("Imported session: ");
5804
- term(`${shortId2}
5805
- `);
5806
- term.dim(` from machine: `);
5807
- term(`${fromMachine}
5808
- `);
5809
- term.dim(` original cwd: `);
5810
- term(`${shortenHomePath(originalCwd)}
5811
- `);
5812
- term("\n");
5813
- term(
5814
- "This session has never been launched on this machine. Pick a local\n"
5815
- );
5816
- term("cwd for the agent (Enter to accept, Esc to cancel):\n\n");
5817
- paintInput();
5818
- if (errorLine) {
5819
- term("\n");
5820
- term.red(` ${errorLine}
5821
- `);
5885
+ const contentHeight = 9;
5886
+ layout = drawBox(term, {
5887
+ contentHeight,
5888
+ title: "Run locally \u2014 choose cwd"
5889
+ });
5890
+ const innerW = layout.contentW;
5891
+ const headerRows = [
5892
+ { label: "session: ", value: shortId2 },
5893
+ { label: "from: ", value: fromMachine },
5894
+ { label: "cwd: ", value: originalCwd }
5895
+ ];
5896
+ let row = 0;
5897
+ for (const hr of headerRows) {
5898
+ term.moveTo(layout.contentX, layout.contentY + row);
5899
+ term.dim.noFormat(` ${hr.label}`);
5900
+ term.noFormat(truncate(hr.value, innerW - hr.label.length - 2));
5901
+ row++;
5902
+ }
5903
+ row++;
5904
+ term.moveTo(layout.contentX, layout.contentY + row);
5905
+ term.noFormat(" Pick a local cwd for this session:");
5906
+ row += 2;
5907
+ paintInputRow(row);
5908
+ row += 2;
5909
+ if (errorLine !== null) {
5910
+ term.moveTo(layout.contentX, layout.contentY + row);
5911
+ term.red.noFormat(` ${truncate(errorLine, innerW - 2)}`);
5912
+ } else {
5913
+ term.moveTo(layout.contentX, layout.contentY + row);
5914
+ term.dim.noFormat(
5915
+ " Enter accept \xB7 Esc back \xB7 ^U clear \xB7 ^W kill word"
5916
+ );
5822
5917
  }
5823
5918
  };
5824
- const paintInput = () => {
5825
- term.bold("cwd: ");
5826
- term(buffer);
5919
+ const inputRow = () => 7;
5920
+ const paintInputRow = (rowOffset) => {
5921
+ if (!layout) {
5922
+ return;
5923
+ }
5924
+ const r = rowOffset ?? inputRow();
5925
+ term.moveTo(layout.contentX, layout.contentY + r).eraseLineAfter();
5926
+ term.moveTo(layout.x + layout.w - 1, layout.contentY + r);
5927
+ term.dim.noFormat("\u2502");
5928
+ term.moveTo(layout.contentX, layout.contentY + r);
5929
+ term.bold.noFormat(" cwd: ");
5930
+ const available = layout.contentW - " cwd: ".length - 2;
5931
+ term.noFormat(truncateLeft(buffer, available));
5827
5932
  if (!busy) {
5828
5933
  term.bgWhite(" ");
5829
5934
  }
5830
5935
  };
5831
5936
  const repaintInput = () => {
5832
- term.column(1);
5833
- term.eraseLine();
5834
- paintInput();
5937
+ paintInputRow();
5938
+ if (!layout) {
5939
+ return;
5940
+ }
5941
+ const errRow = inputRow() + 2;
5942
+ term.moveTo(layout.contentX, layout.contentY + errRow).eraseLineAfter();
5943
+ term.moveTo(layout.x + layout.w - 1, layout.contentY + errRow);
5944
+ term.dim.noFormat("\u2502");
5945
+ term.moveTo(layout.contentX, layout.contentY + errRow);
5835
5946
  if (errorLine !== null) {
5836
- term("\n");
5837
- term.eraseLine();
5838
- term.red(` ${errorLine}`);
5839
- term.up(1);
5840
- term.column(1);
5947
+ term.red.noFormat(` ${truncate(errorLine, layout.contentW - 2)}`);
5948
+ } else {
5949
+ term.dim.noFormat(
5950
+ " Enter accept \xB7 Esc back \xB7 ^U clear \xB7 ^W kill word"
5951
+ );
5841
5952
  }
5842
5953
  };
5843
5954
  render();
@@ -5849,14 +5960,21 @@ async function promptForImportCwd(term, session, opts = {}) {
5849
5960
  }
5850
5961
  resolved = true;
5851
5962
  term.off("key", onKey);
5963
+ term.off("resize", onResize);
5852
5964
  term.grabInput(false);
5853
5965
  term.hideCursor(false);
5854
- term("\n\n");
5966
+ term.moveTo(1, 1).eraseDisplayBelow();
5855
5967
  };
5856
5968
  const finish = (value) => {
5857
5969
  cleanup();
5858
5970
  resolve6(value);
5859
5971
  };
5972
+ const onResize = () => {
5973
+ if (resolved) {
5974
+ return;
5975
+ }
5976
+ render();
5977
+ };
5860
5978
  const onKey = (name, _matches, data) => {
5861
5979
  if (busy) {
5862
5980
  return;
@@ -5869,7 +5987,7 @@ async function promptForImportCwd(term, session, opts = {}) {
5869
5987
  void validateLocalCwd(candidate).then((result) => {
5870
5988
  busy = false;
5871
5989
  if (result.ok) {
5872
- finish(result.path);
5990
+ finish({ kind: "ok", path: result.path });
5873
5991
  return;
5874
5992
  }
5875
5993
  errorLine = result.reason;
@@ -5877,8 +5995,12 @@ async function promptForImportCwd(term, session, opts = {}) {
5877
5995
  });
5878
5996
  return;
5879
5997
  }
5880
- if (name === "ESCAPE" || name === "CTRL_C" || name === "CTRL_D") {
5881
- finish(null);
5998
+ if (name === "ESCAPE") {
5999
+ finish({ kind: "back" });
6000
+ return;
6001
+ }
6002
+ if (name === "CTRL_C" || name === "CTRL_D") {
6003
+ finish({ kind: "cancel" });
5882
6004
  return;
5883
6005
  }
5884
6006
  if (name === "BACKSPACE") {
@@ -5915,14 +6037,251 @@ async function promptForImportCwd(term, session, opts = {}) {
5915
6037
  };
5916
6038
  term.grabInput({});
5917
6039
  term.on("key", onKey);
6040
+ term.on("resize", onResize);
5918
6041
  });
5919
6042
  }
6043
+ function truncate(s, max) {
6044
+ if (max <= 1) {
6045
+ return "";
6046
+ }
6047
+ if (s.length <= max) {
6048
+ return s;
6049
+ }
6050
+ return s.slice(0, Math.max(0, max - 1)) + "\u2026";
6051
+ }
6052
+ function truncateLeft(s, max) {
6053
+ if (max <= 1) {
6054
+ return "";
6055
+ }
6056
+ if (s.length <= max) {
6057
+ return s;
6058
+ }
6059
+ return "\u2026" + s.slice(s.length - (max - 1));
6060
+ }
5920
6061
  var init_import_cwd_prompt = __esm({
5921
6062
  "src/tui/import-cwd-prompt.ts"() {
5922
6063
  "use strict";
5923
6064
  init_paths();
5924
6065
  init_session();
5925
6066
  init_cwd();
6067
+ init_prompt_utils();
6068
+ }
6069
+ });
6070
+
6071
+ // src/tui/import-action-prompt.ts
6072
+ function actionPromptStep(selected, key, choices = ACTION_CHOICES) {
6073
+ if (key.kind === "cancel") {
6074
+ return { kind: "cancel" };
6075
+ }
6076
+ if (key.kind === "back") {
6077
+ return { kind: "back" };
6078
+ }
6079
+ if (key.kind === "enter") {
6080
+ const choice = choices[selected];
6081
+ if (!choice) {
6082
+ return { kind: "back" };
6083
+ }
6084
+ return { kind: "resolve", action: choice.key };
6085
+ }
6086
+ if (key.kind === "up") {
6087
+ return {
6088
+ kind: "continue",
6089
+ selected: Math.max(0, selected - 1)
6090
+ };
6091
+ }
6092
+ if (key.kind === "down") {
6093
+ return {
6094
+ kind: "continue",
6095
+ selected: Math.min(choices.length - 1, selected + 1)
6096
+ };
6097
+ }
6098
+ if (key.kind === "char") {
6099
+ const lower = key.ch.toLowerCase();
6100
+ if (lower === "n") {
6101
+ return {
6102
+ kind: "continue",
6103
+ selected: Math.min(choices.length - 1, selected + 1)
6104
+ };
6105
+ }
6106
+ if (lower === "p") {
6107
+ return {
6108
+ kind: "continue",
6109
+ selected: Math.max(0, selected - 1)
6110
+ };
6111
+ }
6112
+ const idx = choices.findIndex((c) => c.hotkey.toLowerCase() === lower);
6113
+ if (idx >= 0) {
6114
+ const choice = choices[idx];
6115
+ if (choice) {
6116
+ return { kind: "resolve", action: choice.key };
6117
+ }
6118
+ }
6119
+ }
6120
+ return { kind: "continue", selected };
6121
+ }
6122
+ async function promptForImportAction(term, session) {
6123
+ resetTerminalModes();
6124
+ const shortId2 = stripHydraSessionPrefix(session.sessionId);
6125
+ const fromMachine = session.importedFromMachine ?? "another machine";
6126
+ const originalCwd = shortenHomePath(session.cwd);
6127
+ let selected = 0;
6128
+ const render = () => {
6129
+ const choiceRows = ACTION_CHOICES.length * 2;
6130
+ const contentHeight = 7 + choiceRows + 2;
6131
+ const layout = drawBox(term, {
6132
+ contentHeight,
6133
+ title: "Imported session"
6134
+ });
6135
+ const innerW = layout.contentW;
6136
+ const headerRows = [
6137
+ { label: "session: ", value: shortId2 },
6138
+ { label: "from: ", value: fromMachine },
6139
+ { label: "cwd: ", value: originalCwd }
6140
+ ];
6141
+ let row = 0;
6142
+ for (const hr of headerRows) {
6143
+ term.moveTo(layout.contentX, layout.contentY + row);
6144
+ term.dim.noFormat(` ${hr.label}`);
6145
+ term.noFormat(truncate2(hr.value, innerW - hr.label.length - 2));
6146
+ row++;
6147
+ }
6148
+ row++;
6149
+ term.moveTo(layout.contentX, layout.contentY + row);
6150
+ term.noFormat(" What do you want to do?");
6151
+ row += 2;
6152
+ for (let i = 0; i < ACTION_CHOICES.length; i++) {
6153
+ const choice = ACTION_CHOICES[i];
6154
+ if (!choice) {
6155
+ continue;
6156
+ }
6157
+ const pointer = i === selected ? "\u276F" : " ";
6158
+ const label = ` ${pointer} ${choice.label}`;
6159
+ term.moveTo(layout.contentX, layout.contentY + row);
6160
+ if (i === selected) {
6161
+ term.brightWhite.bgBlue.noFormat(padRight(label, innerW));
6162
+ } else {
6163
+ term.noFormat(label);
6164
+ }
6165
+ row++;
6166
+ term.moveTo(layout.contentX, layout.contentY + row);
6167
+ term.dim.noFormat(` ${choice.description}`);
6168
+ row++;
6169
+ }
6170
+ row++;
6171
+ term.moveTo(layout.contentX, layout.contentY + row);
6172
+ term.dim.noFormat(" \u2191/\u2193 navigate \xB7 Enter select \xB7 r/v jump \xB7 Esc back");
6173
+ return layout;
6174
+ };
6175
+ render();
6176
+ term.hideCursor();
6177
+ return await new Promise((resolve6) => {
6178
+ let resolved = false;
6179
+ const cleanup = () => {
6180
+ if (resolved) {
6181
+ return;
6182
+ }
6183
+ resolved = true;
6184
+ term.off("key", onKey);
6185
+ term.off("resize", onResize);
6186
+ term.grabInput(false);
6187
+ term.hideCursor(false);
6188
+ term.moveTo(1, 1).eraseDisplayBelow();
6189
+ };
6190
+ const finish = (value) => {
6191
+ cleanup();
6192
+ resolve6(value);
6193
+ };
6194
+ const onResize = () => {
6195
+ if (resolved) {
6196
+ return;
6197
+ }
6198
+ render();
6199
+ };
6200
+ const onKey = (name, _matches, data) => {
6201
+ const input = mapKey(name, data);
6202
+ if (!input) {
6203
+ return;
6204
+ }
6205
+ const step = actionPromptStep(selected, input);
6206
+ if (step.kind === "cancel") {
6207
+ finish("cancel");
6208
+ return;
6209
+ }
6210
+ if (step.kind === "back") {
6211
+ finish("back");
6212
+ return;
6213
+ }
6214
+ if (step.kind === "resolve") {
6215
+ finish(step.action);
6216
+ return;
6217
+ }
6218
+ if (step.selected !== selected) {
6219
+ selected = step.selected;
6220
+ render();
6221
+ }
6222
+ };
6223
+ term.grabInput({});
6224
+ term.on("key", onKey);
6225
+ term.on("resize", onResize);
6226
+ });
6227
+ }
6228
+ function mapKey(name, data) {
6229
+ if (name === "UP") {
6230
+ return { kind: "up" };
6231
+ }
6232
+ if (name === "DOWN") {
6233
+ return { kind: "down" };
6234
+ }
6235
+ if (name === "ENTER" || name === "KP_ENTER") {
6236
+ return { kind: "enter" };
6237
+ }
6238
+ if (name === "ESCAPE") {
6239
+ return { kind: "back" };
6240
+ }
6241
+ if (name === "CTRL_C" || name === "CTRL_D") {
6242
+ return { kind: "cancel" };
6243
+ }
6244
+ if (data?.isCharacter) {
6245
+ return { kind: "char", ch: name };
6246
+ }
6247
+ return null;
6248
+ }
6249
+ function truncate2(s, max) {
6250
+ if (max <= 1) {
6251
+ return "";
6252
+ }
6253
+ if (s.length <= max) {
6254
+ return s;
6255
+ }
6256
+ return s.slice(0, Math.max(0, max - 1)) + "\u2026";
6257
+ }
6258
+ function padRight(s, w) {
6259
+ if (s.length >= w) {
6260
+ return s.slice(0, w);
6261
+ }
6262
+ return s + " ".repeat(w - s.length);
6263
+ }
6264
+ var ACTION_CHOICES;
6265
+ var init_import_action_prompt = __esm({
6266
+ "src/tui/import-action-prompt.ts"() {
6267
+ "use strict";
6268
+ init_paths();
6269
+ init_session();
6270
+ init_prompt_utils();
6271
+ ACTION_CHOICES = [
6272
+ {
6273
+ key: "run-local",
6274
+ label: "Run locally",
6275
+ hotkey: "r",
6276
+ description: "spawn the agent on this machine with a local cwd"
6277
+ },
6278
+ {
6279
+ key: "view",
6280
+ label: "View transcript",
6281
+ hotkey: "v",
6282
+ description: "open read-only, no agent spawn"
6283
+ }
6284
+ ];
5926
6285
  }
5927
6286
  });
5928
6287
 
@@ -6389,7 +6748,7 @@ function graphemeSegments(text) {
6389
6748
  }
6390
6749
  return out;
6391
6750
  }
6392
- function truncate(text, max, opts = {}) {
6751
+ function truncate3(text, max, opts = {}) {
6393
6752
  if (max <= 0) {
6394
6753
  return "";
6395
6754
  }
@@ -6630,8 +6989,12 @@ function mapCsiUToKeyName(code, mod) {
6630
6989
  101: "ctrl-e",
6631
6990
  102: "ctrl-f",
6632
6991
  103: "ctrl-g",
6992
+ 104: "backspace",
6993
+ 105: "tab",
6994
+ 106: "ctrl-enter",
6633
6995
  107: "ctrl-k",
6634
6996
  108: "ctrl-l",
6997
+ 109: "enter",
6635
6998
  110: "ctrl-n",
6636
6999
  111: "ctrl-o",
6637
7000
  112: "ctrl-p",
@@ -6641,6 +7004,7 @@ function mapCsiUToKeyName(code, mod) {
6641
7004
  117: "ctrl-u",
6642
7005
  118: "ctrl-v",
6643
7006
  119: "ctrl-w",
7007
+ 120: "ctrl-x",
6644
7008
  121: "ctrl-y"
6645
7009
  };
6646
7010
  if (mod === 5) {
@@ -6985,27 +7349,21 @@ uncaught: ${err.stack ?? err.message}
6985
7349
  this.handleRawStdinSegment(text);
6986
7350
  return;
6987
7351
  }
6988
- const legacyMarkers = [
6989
- { seq: "\x1B[27;2;13~", name: "shift-enter" },
6990
- { seq: "\x1B[27;5;13~", name: "ctrl-enter" },
6991
- // Bare LF — universal fallback for terminals without
6992
- // modifyOtherKeys / kitty protocol. Last so the longer escape
6993
- // sequences match first and we don't double-fire.
6994
- { seq: "\n", name: "ctrl-enter" }
6995
- ];
6996
- for (const { seq, name } of legacyMarkers) {
6997
- if (text.includes(seq)) {
6998
- const parts = text.split(seq);
6999
- for (let i = 0; i < parts.length; i++) {
7000
- if (parts[i].length > 0) {
7001
- this.handleRawStdin(Buffer.from(parts[i], "binary"));
7002
- }
7003
- if (i < parts.length - 1) {
7004
- this.onKey([{ type: "key", name }]);
7005
- }
7352
+ if (/\x1b\[27;\d+;\d+~/.test(text)) {
7353
+ this.handleCsi27Stdin(text);
7354
+ return;
7355
+ }
7356
+ if (text.includes("\n")) {
7357
+ const parts = text.split("\n");
7358
+ for (let i = 0; i < parts.length; i++) {
7359
+ if (parts[i].length > 0) {
7360
+ this.handleRawStdin(Buffer.from(parts[i], "binary"));
7361
+ }
7362
+ if (i < parts.length - 1) {
7363
+ this.onKey([{ type: "key", name: "ctrl-enter" }]);
7006
7364
  }
7007
- return;
7008
7365
  }
7366
+ return;
7009
7367
  }
7010
7368
  if (text.includes("\x1B[") && /\x1b\[\d+(?:;\d+)?u/.test(text)) {
7011
7369
  this.handleCsiUStdin(text);
@@ -7013,6 +7371,36 @@ uncaught: ${err.stack ?? err.message}
7013
7371
  }
7014
7372
  this.handleRawStdinSegment(text);
7015
7373
  }
7374
+ // Walk `text` extracting every legacy modifyOtherKeys CSI-27 sequence
7375
+ // (\x1b[27;<mod>;<code>~). Each match is dispatched through
7376
+ // mapCsiUToKeyName; unmapped printable codes with no modifier or
7377
+ // shift are injected as text (Shift+I -> "I"), so xterm's
7378
+ // modifyOtherKeys=2 echo doesn't leak escape sequences into the
7379
+ // prompt. Other unmapped combos (Ctrl+symbol etc.) are dropped.
7380
+ handleCsi27Stdin(text) {
7381
+ const csi27 = /\x1b\[27;(\d+);(\d+)~/g;
7382
+ let lastEnd = 0;
7383
+ let m;
7384
+ while ((m = csi27.exec(text)) !== null) {
7385
+ if (m.index > lastEnd) {
7386
+ this.handleRawStdin(
7387
+ Buffer.from(text.slice(lastEnd, m.index), "binary")
7388
+ );
7389
+ }
7390
+ const mod = parseInt(m[1], 10);
7391
+ const code = parseInt(m[2], 10);
7392
+ const name = mapCsiUToKeyName(code, mod);
7393
+ if (name !== null) {
7394
+ this.onKey([{ type: "key", name }]);
7395
+ } else if ((mod === 1 || mod === 2) && code >= 32 && code < 127) {
7396
+ this.handleRawStdinSegment(String.fromCharCode(code));
7397
+ }
7398
+ lastEnd = m.index + m[0].length;
7399
+ }
7400
+ if (lastEnd < text.length) {
7401
+ this.handleRawStdin(Buffer.from(text.slice(lastEnd), "binary"));
7402
+ }
7403
+ }
7016
7404
  // Walk `text` extracting every kitty CSI-u sequence. Each non-CSI-u
7017
7405
  // span is recursed back into handleRawStdin so paste markers and
7018
7406
  // legacy-modifyOtherKeys sequences in the same chunk still get
@@ -8010,9 +8398,9 @@ uncaught: ${err.stack ?? err.message}
8010
8398
  titleRoom = 0;
8011
8399
  cwdRoom = variableRoom;
8012
8400
  }
8013
- this.term.yellow(sid)(" \xB7 ").cyan.noFormat(agentCell)(" \xB7 ").dim.noFormat(truncate(cwdDisplay, cwdRoom));
8401
+ this.term.yellow(sid)(" \xB7 ").cyan.noFormat(agentCell)(" \xB7 ").dim.noFormat(truncate3(cwdDisplay, cwdRoom));
8014
8402
  if (title) {
8015
- this.term(" \xB7 ").bold.noFormat(truncate(title, titleRoom));
8403
+ this.term(" \xB7 ").bold.noFormat(truncate3(title, titleRoom));
8016
8404
  }
8017
8405
  if (usage) {
8018
8406
  const col = Math.max(1, w - usage.length + 1);
@@ -8123,7 +8511,7 @@ uncaught: ${err.stack ?? err.message}
8123
8511
  const namePadded = item.name.padEnd(nameWidth);
8124
8512
  const desc = item.description ?? "";
8125
8513
  const remaining = w - namePadded.length - 4;
8126
- const truncated = remaining > 0 ? truncate(desc, remaining) : "";
8514
+ const truncated = remaining > 0 ? truncate3(desc, remaining) : "";
8127
8515
  this.term(" ").brightCyan(namePadded);
8128
8516
  if (truncated.length > 0) {
8129
8517
  this.term(" ").dim(truncated);
@@ -8200,7 +8588,7 @@ uncaught: ${err.stack ?? err.message}
8200
8588
  const text = this.queuedTexts[i];
8201
8589
  const isLast = i === rows - 1 && this.queuedTexts.length > MAX_QUEUED_ROWS;
8202
8590
  const overflow = this.queuedTexts.length - MAX_QUEUED_ROWS;
8203
- const summary = text === void 0 ? "" : isLast ? `+ ${overflow + 1} more queued` : truncate(firstLine2(text), w - 4);
8591
+ const summary = text === void 0 ? "" : isLast ? `+ ${overflow + 1} more queued` : truncate3(firstLine2(text), w - 4);
8204
8592
  const editing = !isLast && i === editingIndex;
8205
8593
  const sig = text === void 0 ? `queued|${w}|empty` : `queued|${w}|${editing ? "edit" : isLast ? "ovf" : "row"}|${summary}`;
8206
8594
  this.paintRow(row, sig, () => {
@@ -8277,10 +8665,10 @@ uncaught: ${err.stack ?? err.message}
8277
8665
  const w = this.term.width;
8278
8666
  const top = this.term.height - CONFIRM_PROMPT_ROWS - BANNER_ROWS - SEPARATOR_ROWS - SESSIONBAR_ROWS + 1;
8279
8667
  this.paintRow(top, `confirm|q|${w}|${spec.question}`, () => {
8280
- this.term.brightYellow(` ? ${truncate(spec.question, w - 4)}`);
8668
+ this.term.brightYellow(` ? ${truncate3(spec.question, w - 4)}`);
8281
8669
  });
8282
8670
  this.paintRow(top + 1, `confirm|h|${w}|${spec.hint}`, () => {
8283
- this.term.dim(` ${truncate(spec.hint, w - 2)}`);
8671
+ this.term.dim(` ${truncate3(spec.hint, w - 2)}`);
8284
8672
  });
8285
8673
  }
8286
8674
  drawHelpPrompt() {
@@ -8300,7 +8688,7 @@ uncaught: ${err.stack ?? err.message}
8300
8688
  row += 1;
8301
8689
  };
8302
8690
  writeRow(`help|t|${w}|${spec.title}`, () => {
8303
- this.term.brightYellow(` \u2753 ${truncate(spec.title, w - 5)}`);
8691
+ this.term.brightYellow(` \u2753 ${truncate3(spec.title, w - 5)}`);
8304
8692
  });
8305
8693
  const keysWidth = Math.min(
8306
8694
  24,
@@ -8322,11 +8710,11 @@ uncaught: ${err.stack ?? err.message}
8322
8710
  writeRow(`help|e|${w}|${keys}|${desc}`, () => {
8323
8711
  this.term(" ");
8324
8712
  this.term.brightCyan.noFormat(paddedKeys);
8325
- this.term.noFormat(` ${truncate(desc, w - 2 - keysWidth - 1)}`);
8713
+ this.term.noFormat(` ${truncate3(desc, w - 2 - keysWidth - 1)}`);
8326
8714
  });
8327
8715
  }
8328
8716
  writeRow(`help|hint|${w}|${spec.hint}`, () => {
8329
- this.term.dim(` ${truncate(spec.hint, w - 2)}`);
8717
+ this.term.dim(` ${truncate3(spec.hint, w - 2)}`);
8330
8718
  });
8331
8719
  }
8332
8720
  helpRows() {
@@ -8352,7 +8740,7 @@ uncaught: ${err.stack ?? err.message}
8352
8740
  row += 1;
8353
8741
  };
8354
8742
  writeRow(`perm|t|${w}|${spec.title}`, () => {
8355
- this.term.brightYellow(` \u{1F512} ${truncate(spec.title, w - 5)}`);
8743
+ this.term.brightYellow(` \u{1F512} ${truncate3(spec.title, w - 5)}`);
8356
8744
  });
8357
8745
  writeRow(`perm|sub|${w}`, () => {
8358
8746
  this.term.dim(" This action requires approval");
@@ -8370,7 +8758,7 @@ uncaught: ${err.stack ?? err.message}
8370
8758
  }
8371
8759
  const isSel = i === spec.selectedIndex;
8372
8760
  const marker = isSel ? "\u276F" : " ";
8373
- const body = ` ${marker} ${i + 1}. ${truncate(opt.label, w - 8)}`;
8761
+ const body = ` ${marker} ${i + 1}. ${truncate3(opt.label, w - 8)}`;
8374
8762
  writeRow(`perm|o|${w}|${i}|${isSel ? "1" : "0"}|${opt.label}`, () => {
8375
8763
  if (isSel) {
8376
8764
  this.term.brightCyan(body);
@@ -8417,7 +8805,7 @@ uncaught: ${err.stack ?? err.message}
8417
8805
  }
8418
8806
  const hint = this.banner.currentMode ? this.banner.hint.replace(
8419
8807
  "\u21E7\u21E5 mode",
8420
- `\u21E7\u21E5 mode(${this.banner.currentMode})`
8808
+ `\u21E7\u21E5 mode: ${this.banner.currentMode}`
8421
8809
  ) : this.banner.hint;
8422
8810
  this.term(" \xB7 ").dim(hint);
8423
8811
  if (right) {
@@ -8459,6 +8847,10 @@ uncaught: ${err.stack ?? err.message}
8459
8847
  this.term.hideCursor(true);
8460
8848
  return;
8461
8849
  }
8850
+ if (this.readonly) {
8851
+ this.term.hideCursor(true);
8852
+ return;
8853
+ }
8462
8854
  this.term.hideCursor(false);
8463
8855
  const w = this.term.width;
8464
8856
  const room = Math.max(1, w - 2);
@@ -8600,7 +8992,7 @@ uncaught: ${err.stack ?? err.message}
8600
8992
  }
8601
8993
  const remaining = Math.max(0, width - (line.prefix?.length ?? 0));
8602
8994
  const stripMarkup = line.bodyStyle === "agent";
8603
- const bodyText = line.ansi ? line.body : truncate(line.body, remaining, { stripMarkup });
8995
+ const bodyText = line.ansi ? line.body : truncate3(line.body, remaining, { stripMarkup });
8604
8996
  if (this.scrollbackHighlight !== null && !line.ansi) {
8605
8997
  writeBodyWithHighlight(
8606
8998
  this.term,
@@ -11179,19 +11571,52 @@ async function runSession(term, config, target, opts, exitHint) {
11179
11571
  screen.pauseRepaint();
11180
11572
  screen.stop();
11181
11573
  saveHistory(historyFile, history).catch(() => void 0);
11182
- const sessions = await listSessions(target);
11183
- const choice = await pickSession(term, {
11184
- cwd: resolvedCwd,
11185
- sessions,
11186
- config,
11187
- target,
11188
- currentSessionId: resolvedSessionId
11189
- });
11190
- if (choice.kind === "abort") {
11191
- screen.start();
11192
- screen.resumeRepaint();
11193
- return;
11574
+ let resolvedChoice = null;
11575
+ let attachOverrides = null;
11576
+ while (resolvedChoice === null) {
11577
+ const sessions = await listSessions(target);
11578
+ const choice2 = await pickSession(term, {
11579
+ cwd: resolvedCwd,
11580
+ sessions,
11581
+ config,
11582
+ target,
11583
+ currentSessionId: resolvedSessionId
11584
+ });
11585
+ if (choice2.kind === "abort") {
11586
+ screen.start();
11587
+ screen.resumeRepaint();
11588
+ return;
11589
+ }
11590
+ if (choice2.kind === "new") {
11591
+ resolvedChoice = { choice: choice2, sessions };
11592
+ break;
11593
+ }
11594
+ const chosen = sessions.find((s) => s.sessionId === choice2.sessionId);
11595
+ const isImportedFirstLaunch = chosen !== void 0 && !!chosen.importedFromMachine && !chosen.upstreamSessionId && choice2.readonly !== true;
11596
+ if (!isImportedFirstLaunch) {
11597
+ resolvedChoice = { choice: choice2, sessions };
11598
+ break;
11599
+ }
11600
+ const opsShim = { ...opts, readonly: false };
11601
+ const decided = await runImportedFirstLaunchFlow(term, chosen, choice2, opsShim);
11602
+ if (decided.kind === "cancel") {
11603
+ screen.start();
11604
+ screen.resumeRepaint();
11605
+ return;
11606
+ }
11607
+ if (decided.kind === "back") {
11608
+ continue;
11609
+ }
11610
+ resolvedChoice = { choice: choice2, sessions };
11611
+ attachOverrides = {
11612
+ readonly: opsShim.readonly === true,
11613
+ cwd: decided.ctx.cwd
11614
+ };
11615
+ if (decided.ctx.importAttachHint !== void 0) {
11616
+ attachOverrides.importAttachHint = decided.ctx.importAttachHint;
11617
+ }
11194
11618
  }
11619
+ const { choice } = resolvedChoice;
11195
11620
  const resume = finishSession;
11196
11621
  finishSession = null;
11197
11622
  process.off("SIGINT", sigintHandler);
@@ -11202,15 +11627,23 @@ async function runSession(term, config, target, opts, exitHint) {
11202
11627
  resume({ ...rest, cwd: resolvedCwd, forceNew: true, readonly: false });
11203
11628
  return;
11204
11629
  }
11630
+ if (choice.kind !== "attach") {
11631
+ return;
11632
+ }
11205
11633
  const nextOpts = {
11206
11634
  ...opts,
11207
11635
  sessionId: choice.sessionId,
11208
- cwd: resolvedCwd,
11209
- readonly: choice.readonly === true
11636
+ cwd: attachOverrides?.cwd ?? resolvedCwd,
11637
+ readonly: attachOverrides?.readonly ?? choice.readonly === true
11210
11638
  };
11211
11639
  if (choice.agentId !== void 0) {
11212
11640
  nextOpts.agentId = choice.agentId;
11213
11641
  }
11642
+ if (attachOverrides?.importAttachHint !== void 0) {
11643
+ nextOpts.importAttachHint = attachOverrides.importAttachHint;
11644
+ } else {
11645
+ delete nextOpts.importAttachHint;
11646
+ }
11214
11647
  resume(nextOpts);
11215
11648
  };
11216
11649
  const cycleLiveSession = async () => {
@@ -12257,18 +12690,22 @@ connection lost: ${err.message}
12257
12690
  async function resolveSession(term, config, target, opts) {
12258
12691
  const cwd = opts.cwd ?? process.cwd();
12259
12692
  if (opts.sessionId) {
12260
- return {
12693
+ const ctx = {
12261
12694
  sessionId: opts.sessionId,
12262
12695
  agentId: opts.agentId ?? "",
12263
12696
  cwd
12264
12697
  };
12698
+ if (opts.importAttachHint !== void 0) {
12699
+ ctx.importAttachHint = opts.importAttachHint;
12700
+ }
12701
+ return ctx;
12265
12702
  }
12266
12703
  if (opts.forceNew) {
12267
12704
  return newCtx(opts, cwd, config);
12268
12705
  }
12269
12706
  if (opts.resume) {
12270
- const sessions2 = await listSessions(target, { cwd, all: true });
12271
- const recent = pickMostRecent(sessions2, cwd);
12707
+ const sessions = await listSessions(target, { cwd, all: true });
12708
+ const recent = pickMostRecent(sessions, cwd);
12272
12709
  if (!recent) {
12273
12710
  term.yellow(`No sessions found for ${cwd}.
12274
12711
  `);
@@ -12280,43 +12717,82 @@ async function resolveSession(term, config, target, opts) {
12280
12717
  cwd
12281
12718
  };
12282
12719
  }
12283
- const sessions = await listSessions(target);
12284
- if (sessions.length === 0) {
12285
- return newCtx(opts, cwd, config);
12286
- }
12287
- const choice = await pickSession(term, {
12288
- cwd,
12289
- sessions,
12290
- config,
12291
- target
12292
- });
12293
- if (choice.kind === "abort") {
12294
- return null;
12295
- }
12296
- if (choice.kind === "new") {
12297
- return newCtx(opts, cwd, config);
12298
- }
12299
- opts.readonly = choice.readonly === true;
12300
- const chosen = sessions.find((s) => s.sessionId === choice.sessionId);
12301
- const isImportedFirstLaunch = chosen !== void 0 && !!chosen.importedFromMachine && !chosen.upstreamSessionId && !opts.readonly;
12302
- if (isImportedFirstLaunch) {
12303
- const promptedCwd = await promptForImportCwd(term, chosen);
12304
- if (promptedCwd === null) {
12720
+ while (true) {
12721
+ const sessions = await listSessions(target);
12722
+ if (sessions.length === 0) {
12723
+ return newCtx(opts, cwd, config);
12724
+ }
12725
+ const choice = await pickSession(term, {
12726
+ cwd,
12727
+ sessions,
12728
+ config,
12729
+ target
12730
+ });
12731
+ if (choice.kind === "abort") {
12305
12732
  return null;
12306
12733
  }
12307
- const agentId = choice.agentId ?? chosen.agentId ?? "";
12734
+ if (choice.kind === "new") {
12735
+ return newCtx(opts, cwd, config);
12736
+ }
12737
+ opts.readonly = choice.readonly === true;
12738
+ const chosen = sessions.find((s) => s.sessionId === choice.sessionId);
12739
+ const isImportedFirstLaunch = chosen !== void 0 && !!chosen.importedFromMachine && !chosen.upstreamSessionId && !opts.readonly;
12740
+ if (isImportedFirstLaunch) {
12741
+ const decided = await runImportedFirstLaunchFlow(term, chosen, choice, opts);
12742
+ if (decided.kind === "cancel") {
12743
+ return null;
12744
+ }
12745
+ if (decided.kind === "back") {
12746
+ continue;
12747
+ }
12748
+ return decided.ctx;
12749
+ }
12308
12750
  return {
12309
12751
  sessionId: choice.sessionId,
12310
- agentId,
12311
- cwd: promptedCwd,
12312
- importAttachHint: { agentId, cwd: promptedCwd }
12752
+ agentId: choice.agentId ?? "",
12753
+ cwd
12754
+ };
12755
+ }
12756
+ }
12757
+ async function runImportedFirstLaunchFlow(term, chosen, choice, opts) {
12758
+ while (true) {
12759
+ const action = await promptForImportAction(term, chosen);
12760
+ if (action === "cancel") {
12761
+ return { kind: "cancel" };
12762
+ }
12763
+ if (action === "back") {
12764
+ return { kind: "back" };
12765
+ }
12766
+ if (action === "view") {
12767
+ opts.readonly = true;
12768
+ const agentId2 = choice.agentId ?? chosen.agentId ?? "";
12769
+ return {
12770
+ kind: "ctx",
12771
+ ctx: {
12772
+ sessionId: choice.sessionId,
12773
+ agentId: agentId2,
12774
+ cwd: chosen.cwd
12775
+ }
12776
+ };
12777
+ }
12778
+ const cwdResult = await promptForImportCwd(term, chosen);
12779
+ if (cwdResult.kind === "cancel") {
12780
+ return { kind: "cancel" };
12781
+ }
12782
+ if (cwdResult.kind === "back") {
12783
+ continue;
12784
+ }
12785
+ const agentId = choice.agentId ?? chosen.agentId ?? "";
12786
+ return {
12787
+ kind: "ctx",
12788
+ ctx: {
12789
+ sessionId: choice.sessionId,
12790
+ agentId,
12791
+ cwd: cwdResult.path,
12792
+ importAttachHint: { agentId, cwd: cwdResult.path }
12793
+ }
12313
12794
  };
12314
12795
  }
12315
- return {
12316
- sessionId: choice.sessionId,
12317
- agentId: choice.agentId ?? "",
12318
- cwd
12319
- };
12320
12796
  }
12321
12797
  function newCtx(opts, cwd, config) {
12322
12798
  return {
@@ -12467,6 +12943,7 @@ var init_app = __esm({
12467
12943
  init_discovery();
12468
12944
  init_picker();
12469
12945
  init_import_cwd_prompt();
12946
+ init_import_action_prompt();
12470
12947
  init_screen();
12471
12948
  init_input();
12472
12949
  init_attachments();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hydra-acp/cli",
3
- "version": "0.1.34",
3
+ "version": "0.1.36",
4
4
  "description": "Multi-client ACP session daemon: spawn agents, attach over WSS, multiplex sessions across editors.",
5
5
  "license": "MIT",
6
6
  "type": "module",