@hydra-acp/cli 0.1.35 → 0.1.37
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/cli.js +2449 -2252
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -4980,1315 +4980,849 @@ var init_update_check = __esm({
|
|
|
4980
4980
|
}
|
|
4981
4981
|
});
|
|
4982
4982
|
|
|
4983
|
-
// src/tui/
|
|
4984
|
-
|
|
4985
|
-
|
|
4986
|
-
|
|
4987
|
-
|
|
4988
|
-
|
|
4989
|
-
|
|
4990
|
-
|
|
4991
|
-
|
|
4992
|
-
|
|
4993
|
-
|
|
4994
|
-
|
|
4995
|
-
|
|
4996
|
-
|
|
4997
|
-
|
|
4998
|
-
|
|
4999
|
-
|
|
5000
|
-
|
|
4983
|
+
// src/tui/input.ts
|
|
4984
|
+
var InputDispatcher;
|
|
4985
|
+
var init_input = __esm({
|
|
4986
|
+
"src/tui/input.ts"() {
|
|
4987
|
+
"use strict";
|
|
4988
|
+
InputDispatcher = class {
|
|
4989
|
+
buffer = [""];
|
|
4990
|
+
row = 0;
|
|
4991
|
+
col = 0;
|
|
4992
|
+
planMode = false;
|
|
4993
|
+
historyIndex = -1;
|
|
4994
|
+
// Queue editing: when the user walks Up past row 0 with queued prompts
|
|
4995
|
+
// present, the most-recently-queued item lands in the buffer and
|
|
4996
|
+
// queueIndex tracks which slot of `queue` is being edited. Enter submits
|
|
4997
|
+
// the edit (queue-edit) or, on an empty buffer, drops the slot
|
|
4998
|
+
// (queue-remove). -1 means not editing a queue slot.
|
|
4999
|
+
queueIndex = -1;
|
|
5000
|
+
savedDraft = null;
|
|
5001
|
+
history = [];
|
|
5002
|
+
// Active reverse-incremental search over `history`. Set when ^r is
|
|
5003
|
+
// pressed; cleared when the user accepts (Enter / typing / arrows)
|
|
5004
|
+
// or cancels (ESC). `query` is the lowercased substring matched
|
|
5005
|
+
// against history entries; `matchIndices` are history indices in
|
|
5006
|
+
// newest→oldest order; `cursor` is the current index into that list.
|
|
5007
|
+
// `savedDraft` snapshots the buffer/cursor at the moment search
|
|
5008
|
+
// began so ESC can restore it.
|
|
5009
|
+
historySearch = null;
|
|
5010
|
+
// Waiting queue snapshot (excludes the in-flight head). Newest item lives
|
|
5011
|
+
// at the end so Up walks the array right-to-left.
|
|
5012
|
+
queue = [];
|
|
5013
|
+
turnRunning = false;
|
|
5014
|
+
// Single-slot kill ring. The most recent killed text (^U, ^K, ^W) lands
|
|
5015
|
+
// here so ^Y can yank it back. Standard readline keeps a stack; we
|
|
5016
|
+
// only keep one slot because that's what 99% of yank uses look like.
|
|
5017
|
+
killBuffer = "";
|
|
5018
|
+
// Images attached to the current draft. Cleared in the same paths
|
|
5019
|
+
// that clear the text buffer (clearBuffer, after send). Queue
|
|
5020
|
+
// navigation snapshots/restores them alongside savedDraft so up/down
|
|
5021
|
+
// through queued items doesn't drop chips.
|
|
5022
|
+
attachments = [];
|
|
5023
|
+
// Snapshot of `attachments` taken when the user starts walking
|
|
5024
|
+
// history/queue with chips already attached. Restored alongside the
|
|
5025
|
+
// text draft when the walk ends. Distinct from savedDraft because
|
|
5026
|
+
// queue slots (which may carry their own attachments — though we
|
|
5027
|
+
// don't surface that yet) shouldn't blend with the current draft's.
|
|
5028
|
+
savedAttachments = null;
|
|
5029
|
+
constructor(opts = {}) {
|
|
5030
|
+
this.history = [...opts.history ?? []];
|
|
5031
|
+
this.planMode = opts.planMode ?? false;
|
|
5001
5032
|
}
|
|
5002
|
-
|
|
5003
|
-
|
|
5004
|
-
|
|
5005
|
-
|
|
5006
|
-
|
|
5007
|
-
|
|
5033
|
+
state() {
|
|
5034
|
+
return {
|
|
5035
|
+
buffer: [...this.buffer],
|
|
5036
|
+
row: this.row,
|
|
5037
|
+
col: this.col,
|
|
5038
|
+
planMode: this.planMode,
|
|
5039
|
+
historyIndex: this.historyIndex,
|
|
5040
|
+
queueIndex: this.queueIndex,
|
|
5041
|
+
attachments: [...this.attachments],
|
|
5042
|
+
historySearchQuery: this.historySearch?.query ?? null
|
|
5043
|
+
};
|
|
5008
5044
|
}
|
|
5009
|
-
|
|
5010
|
-
|
|
5011
|
-
|
|
5012
|
-
|
|
5013
|
-
|
|
5014
|
-
if (opts.currentSessionId !== void 0) {
|
|
5015
|
-
const current = opts.sessions.find(
|
|
5016
|
-
(s) => s.sessionId === opts.currentSessionId
|
|
5017
|
-
);
|
|
5018
|
-
if (current?.importedFromMachine) {
|
|
5019
|
-
hostFilter = "__all";
|
|
5020
|
-
}
|
|
5021
|
-
}
|
|
5022
|
-
let allSessions = sortSessions(opts.sessions);
|
|
5023
|
-
let visible = filterByHost(allSessions, hostFilter);
|
|
5024
|
-
let rows = visible.map((s) => toRow(s, Date.now()));
|
|
5025
|
-
let widths = computeWidths(rows);
|
|
5026
|
-
let total = 1 + visible.length;
|
|
5027
|
-
let selectedIdx = 0;
|
|
5028
|
-
let scrollOffset = 0;
|
|
5029
|
-
if (opts.currentSessionId !== void 0) {
|
|
5030
|
-
const idx = visible.findIndex((s) => s.sessionId === opts.currentSessionId);
|
|
5031
|
-
if (idx >= 0) {
|
|
5032
|
-
selectedIdx = idx + 1;
|
|
5033
|
-
}
|
|
5034
|
-
}
|
|
5035
|
-
let searchActive = false;
|
|
5036
|
-
let searchTerm = "";
|
|
5037
|
-
let mode = "normal";
|
|
5038
|
-
let pendingAction = null;
|
|
5039
|
-
let renameBuffer = "";
|
|
5040
|
-
let transientStatus = null;
|
|
5041
|
-
let termHeight = readTermHeight(term);
|
|
5042
|
-
let termWidth = readTermWidth(term);
|
|
5043
|
-
let viewportSize = 0;
|
|
5044
|
-
let newSessionLabel = "";
|
|
5045
|
-
let headerLine = "";
|
|
5046
|
-
let sessionLines = [];
|
|
5047
|
-
let startRow = 1;
|
|
5048
|
-
const cwdMaxWidth = opts.config.tui.cwdColumnMaxWidth;
|
|
5049
|
-
const computeLayout = () => {
|
|
5050
|
-
termHeight = readTermHeight(term);
|
|
5051
|
-
termWidth = readTermWidth(term);
|
|
5052
|
-
const maxViewportRows = Math.max(3, termHeight - 6);
|
|
5053
|
-
viewportSize = Math.min(visible.length, maxViewportRows);
|
|
5054
|
-
const rowMaxWidth = Math.max(10, termWidth - ROW_PREFIX_WIDTH);
|
|
5055
|
-
newSessionLabel = formatNewSessionLabel(opts.cwd, rowMaxWidth);
|
|
5056
|
-
headerLine = formatRow(HEADER, widths, rowMaxWidth, cwdMaxWidth);
|
|
5057
|
-
sessionLines = rows.map((r) => formatRow(r, widths, rowMaxWidth, cwdMaxWidth));
|
|
5058
|
-
};
|
|
5059
|
-
const rebuildRows = () => {
|
|
5060
|
-
rows = visible.map((s) => toRow(s, Date.now()));
|
|
5061
|
-
widths = computeWidths(rows);
|
|
5062
|
-
total = 1 + visible.length;
|
|
5063
|
-
computeLayout();
|
|
5064
|
-
};
|
|
5065
|
-
const applyFilter = () => {
|
|
5066
|
-
let base = allSessions;
|
|
5067
|
-
if (cwdOnly) {
|
|
5068
|
-
base = base.filter((s) => s.cwd === opts.cwd);
|
|
5069
|
-
}
|
|
5070
|
-
base = filterByHost(base, hostFilter);
|
|
5071
|
-
if (searchActive && searchTerm.length > 0) {
|
|
5072
|
-
visible = base.filter((s) => matchesSearch(s, searchTerm));
|
|
5073
|
-
} else {
|
|
5074
|
-
visible = base;
|
|
5075
|
-
}
|
|
5076
|
-
rebuildRows();
|
|
5077
|
-
if (searchActive) {
|
|
5078
|
-
scrollOffset = 0;
|
|
5079
|
-
selectedIdx = visible.length > 0 ? 1 : 0;
|
|
5080
|
-
} else if (selectedIdx > total - 1) {
|
|
5081
|
-
selectedIdx = Math.max(0, total - 1);
|
|
5082
|
-
}
|
|
5083
|
-
if (scrollOffset + viewportSize > visible.length) {
|
|
5084
|
-
scrollOffset = Math.max(0, visible.length - viewportSize);
|
|
5085
|
-
}
|
|
5086
|
-
adjustScroll();
|
|
5087
|
-
};
|
|
5088
|
-
const adjustScroll = () => {
|
|
5089
|
-
if (selectedIdx === 0) {
|
|
5090
|
-
return;
|
|
5091
|
-
}
|
|
5092
|
-
const sessionIdx = selectedIdx - 1;
|
|
5093
|
-
if (sessionIdx < scrollOffset) {
|
|
5094
|
-
scrollOffset = sessionIdx;
|
|
5095
|
-
} else if (sessionIdx >= scrollOffset + viewportSize) {
|
|
5096
|
-
scrollOffset = sessionIdx - viewportSize + 1;
|
|
5097
|
-
} else if (scrollOffset + viewportSize > visible.length) {
|
|
5098
|
-
scrollOffset = Math.max(0, visible.length - viewportSize);
|
|
5099
|
-
}
|
|
5100
|
-
};
|
|
5101
|
-
const paintNewItem = () => {
|
|
5102
|
-
if (selectedIdx === 0) {
|
|
5103
|
-
term.brightWhite.bgBlue.noFormat(`\u276F ${newSessionLabel}`);
|
|
5104
|
-
} else {
|
|
5105
|
-
term.noFormat(` ${newSessionLabel}`);
|
|
5106
|
-
}
|
|
5107
|
-
};
|
|
5108
|
-
const paintSessionRow = (sessionIdx) => {
|
|
5109
|
-
const label = sessionLines[sessionIdx] ?? "";
|
|
5110
|
-
if (selectedIdx === sessionIdx + 1) {
|
|
5111
|
-
term.brightWhite.bgBlue.noFormat(`\u276F ${label}`);
|
|
5112
|
-
} else {
|
|
5113
|
-
term.noFormat(` ${label}`);
|
|
5114
|
-
}
|
|
5115
|
-
};
|
|
5116
|
-
const formatIndicator = () => {
|
|
5117
|
-
const above = scrollOffset;
|
|
5118
|
-
const below = Math.max(0, visible.length - scrollOffset - viewportSize);
|
|
5119
|
-
const parts = [];
|
|
5120
|
-
if (cwdOnly) {
|
|
5121
|
-
parts.push("cwd-only");
|
|
5122
|
-
}
|
|
5123
|
-
if (hostFilter !== "__all") {
|
|
5124
|
-
parts.push(
|
|
5125
|
-
hostFilter === "__local" ? "host: local" : `host: ${hostFilter}`
|
|
5126
|
-
);
|
|
5127
|
-
}
|
|
5128
|
-
if (above > 0) {
|
|
5129
|
-
parts.push(`\u2191 ${above} above`);
|
|
5130
|
-
}
|
|
5131
|
-
if (below > 0) {
|
|
5132
|
-
parts.push(`\u2193 ${below} below`);
|
|
5133
|
-
}
|
|
5134
|
-
if (parts.length === 0) {
|
|
5135
|
-
return "";
|
|
5136
|
-
}
|
|
5137
|
-
return ` ${parts.join(" \xB7 ")}`;
|
|
5138
|
-
};
|
|
5139
|
-
const shortId2 = (sessionId) => stripHydraSessionPrefix(sessionId);
|
|
5140
|
-
const paintIndicator = () => {
|
|
5141
|
-
term.moveTo(1, indicatorRow()).eraseLineAfter();
|
|
5142
|
-
if (mode === "confirm-kill" && pendingAction) {
|
|
5143
|
-
term.brightYellow.noFormat(` kill ${shortId2(pendingAction.sessionId)}? [y/N]`);
|
|
5144
|
-
return;
|
|
5145
|
-
}
|
|
5146
|
-
if (mode === "confirm-delete" && pendingAction) {
|
|
5147
|
-
term.brightRed.noFormat(` delete ${shortId2(pendingAction.sessionId)}? [y/N]`);
|
|
5148
|
-
return;
|
|
5149
|
-
}
|
|
5150
|
-
if (mode === "busy" && pendingAction) {
|
|
5151
|
-
term.dim.noFormat(` working on ${shortId2(pendingAction.sessionId)}\u2026`);
|
|
5152
|
-
return;
|
|
5153
|
-
}
|
|
5154
|
-
if (mode === "rename" && pendingAction) {
|
|
5155
|
-
term.brightYellow.noFormat(` title: ${renameBuffer}`);
|
|
5156
|
-
term.bgBrightYellow(" ");
|
|
5157
|
-
term.dim.noFormat(" Enter saves \xB7 Esc cancels");
|
|
5158
|
-
return;
|
|
5159
|
-
}
|
|
5160
|
-
if (transientStatus !== null) {
|
|
5161
|
-
term.dim.noFormat(` ${transientStatus}`);
|
|
5162
|
-
return;
|
|
5163
|
-
}
|
|
5164
|
-
if (searchActive) {
|
|
5165
|
-
term.brightYellow.noFormat(` /${searchTerm}`);
|
|
5166
|
-
term.bgBrightYellow(" ");
|
|
5167
|
-
const hint = visible.length === 0 ? " no matches" : ` ${visible.length} match${visible.length === 1 ? "" : "es"}`;
|
|
5168
|
-
term.dim.noFormat(`${hint} \xB7 ^c clears`);
|
|
5169
|
-
return;
|
|
5170
|
-
}
|
|
5171
|
-
term.dim.noFormat(formatIndicator());
|
|
5172
|
-
};
|
|
5173
|
-
const indicatorRow = () => startRow + 3 + viewportSize;
|
|
5174
|
-
const sessionRow = (sessionIdx) => startRow + 3 + (sessionIdx - scrollOffset);
|
|
5175
|
-
const renderFromScratch = () => {
|
|
5176
|
-
if (mode === "help") {
|
|
5177
|
-
renderHelp();
|
|
5178
|
-
return;
|
|
5179
|
-
}
|
|
5180
|
-
computeLayout();
|
|
5181
|
-
adjustScroll();
|
|
5182
|
-
startRow = 1;
|
|
5183
|
-
term.moveTo(1, 1).eraseDisplayBelow();
|
|
5184
|
-
paintNewItem();
|
|
5185
|
-
term("\n\n");
|
|
5186
|
-
term.dim.noFormat(` ${headerLine}`)("\n");
|
|
5187
|
-
for (let v = 0; v < viewportSize; v++) {
|
|
5188
|
-
paintSessionRow(scrollOffset + v);
|
|
5189
|
-
term("\n");
|
|
5190
|
-
}
|
|
5191
|
-
paintIndicator();
|
|
5192
|
-
term("\n");
|
|
5193
|
-
};
|
|
5194
|
-
const renderHelp = () => {
|
|
5195
|
-
term.moveTo(1, 1).eraseDisplayBelow();
|
|
5196
|
-
term.brightWhite.bold.noFormat(" Picker hotkeys")("\n\n");
|
|
5197
|
-
for (const entry of HELP_ENTRIES) {
|
|
5198
|
-
if (entry === null) {
|
|
5199
|
-
term("\n");
|
|
5200
|
-
continue;
|
|
5201
|
-
}
|
|
5202
|
-
const [keys, desc] = entry;
|
|
5203
|
-
term.brightCyan.noFormat(` ${keys.padEnd(HELP_KEYS_WIDTH)}`);
|
|
5204
|
-
term.noFormat(desc)("\n");
|
|
5205
|
-
}
|
|
5206
|
-
term("\n");
|
|
5207
|
-
term.dim.noFormat(" press any key to dismiss")("\n");
|
|
5208
|
-
};
|
|
5209
|
-
const repaintNewItem = () => {
|
|
5210
|
-
term.moveTo(1, startRow).eraseLineAfter();
|
|
5211
|
-
paintNewItem();
|
|
5212
|
-
};
|
|
5213
|
-
const repaintSessionRow = (sessionIdx) => {
|
|
5214
|
-
if (sessionIdx < scrollOffset || sessionIdx >= scrollOffset + viewportSize) {
|
|
5215
|
-
return;
|
|
5216
|
-
}
|
|
5217
|
-
term.moveTo(1, sessionRow(sessionIdx)).eraseLineAfter();
|
|
5218
|
-
paintSessionRow(sessionIdx);
|
|
5219
|
-
};
|
|
5220
|
-
const repaintViewport = () => {
|
|
5221
|
-
for (let v = 0; v < viewportSize; v++) {
|
|
5222
|
-
const row = startRow + 3 + v;
|
|
5223
|
-
term.moveTo(1, row).eraseLineAfter();
|
|
5224
|
-
const sessionIdx = scrollOffset + v;
|
|
5225
|
-
if (sessionIdx < visible.length) {
|
|
5226
|
-
paintSessionRow(sessionIdx);
|
|
5227
|
-
}
|
|
5228
|
-
}
|
|
5229
|
-
paintIndicator();
|
|
5230
|
-
};
|
|
5231
|
-
renderFromScratch();
|
|
5232
|
-
term.hideCursor();
|
|
5233
|
-
return await new Promise((resolve6) => {
|
|
5234
|
-
let resolved = false;
|
|
5235
|
-
const onResize = () => {
|
|
5236
|
-
if (resolved) {
|
|
5237
|
-
return;
|
|
5238
|
-
}
|
|
5239
|
-
renderFromScratch();
|
|
5240
|
-
};
|
|
5241
|
-
const cleanup = () => {
|
|
5242
|
-
if (resolved) {
|
|
5243
|
-
return;
|
|
5045
|
+
// App calls this after asynchronously acquiring an image (drag-drop
|
|
5046
|
+
// file read, clipboard shellout). The dispatcher just records it;
|
|
5047
|
+
// chip rendering and capability gating live in the app/screen layer.
|
|
5048
|
+
addAttachment(attachment) {
|
|
5049
|
+
this.attachments.push(attachment);
|
|
5244
5050
|
}
|
|
5245
|
-
|
|
5246
|
-
|
|
5247
|
-
|
|
5248
|
-
term.grabInput(false);
|
|
5249
|
-
term.hideCursor(false);
|
|
5250
|
-
term.moveTo(1, indicatorRow() + 1);
|
|
5251
|
-
term("\n");
|
|
5252
|
-
};
|
|
5253
|
-
const refresh = async (preferredId) => {
|
|
5254
|
-
try {
|
|
5255
|
-
const next = await listSessions(opts.target);
|
|
5256
|
-
allSessions = sortSessions(next);
|
|
5257
|
-
applyFilter();
|
|
5258
|
-
if (preferredId !== void 0) {
|
|
5259
|
-
const idx = visible.findIndex((s) => s.sessionId === preferredId);
|
|
5260
|
-
if (idx >= 0) {
|
|
5261
|
-
selectedIdx = idx + 1;
|
|
5262
|
-
}
|
|
5263
|
-
}
|
|
5264
|
-
if (selectedIdx > total - 1) {
|
|
5265
|
-
selectedIdx = Math.max(0, total - 1);
|
|
5266
|
-
}
|
|
5267
|
-
if (scrollOffset + viewportSize > visible.length) {
|
|
5268
|
-
scrollOffset = Math.max(0, visible.length - viewportSize);
|
|
5051
|
+
removeAttachment(index) {
|
|
5052
|
+
if (index < 0 || index >= this.attachments.length) {
|
|
5053
|
+
return;
|
|
5269
5054
|
}
|
|
5270
|
-
|
|
5271
|
-
renderFromScratch();
|
|
5272
|
-
} catch (err) {
|
|
5273
|
-
transientStatus = `refresh failed: ${err.message}`;
|
|
5274
|
-
renderFromScratch();
|
|
5275
|
-
}
|
|
5276
|
-
};
|
|
5277
|
-
const performRename = async (title) => {
|
|
5278
|
-
if (!pendingAction) {
|
|
5279
|
-
return;
|
|
5280
|
-
}
|
|
5281
|
-
const session = pendingAction;
|
|
5282
|
-
mode = "busy";
|
|
5283
|
-
paintIndicator();
|
|
5284
|
-
try {
|
|
5285
|
-
await renameSession(opts.target, session.sessionId, title);
|
|
5286
|
-
mode = "normal";
|
|
5287
|
-
pendingAction = null;
|
|
5288
|
-
renameBuffer = "";
|
|
5289
|
-
await refresh(session.sessionId);
|
|
5290
|
-
} catch (err) {
|
|
5291
|
-
mode = "normal";
|
|
5292
|
-
pendingAction = null;
|
|
5293
|
-
renameBuffer = "";
|
|
5294
|
-
transientStatus = `rename failed: ${err.message}`;
|
|
5295
|
-
paintIndicator();
|
|
5055
|
+
this.attachments.splice(index, 1);
|
|
5296
5056
|
}
|
|
5297
|
-
|
|
5298
|
-
|
|
5299
|
-
try {
|
|
5300
|
-
await regenSessionTitle(opts.target, session.sessionId);
|
|
5301
|
-
transientStatus = "title regen queued (press r to refresh)";
|
|
5302
|
-
paintIndicator();
|
|
5303
|
-
} catch (err) {
|
|
5304
|
-
transientStatus = `regen failed: ${err.message}`;
|
|
5305
|
-
paintIndicator();
|
|
5057
|
+
setTurnRunning(running) {
|
|
5058
|
+
this.turnRunning = running;
|
|
5306
5059
|
}
|
|
5307
|
-
|
|
5308
|
-
|
|
5309
|
-
|
|
5310
|
-
|
|
5060
|
+
setHistory(history) {
|
|
5061
|
+
this.history = [...history];
|
|
5062
|
+
this.historyIndex = -1;
|
|
5063
|
+
this.savedDraft = null;
|
|
5064
|
+
this.historySearch = null;
|
|
5311
5065
|
}
|
|
5312
|
-
|
|
5313
|
-
|
|
5314
|
-
|
|
5315
|
-
|
|
5316
|
-
|
|
5317
|
-
|
|
5318
|
-
|
|
5319
|
-
|
|
5066
|
+
// Snapshot of the waiting queue (head excluded). Called by the app after
|
|
5067
|
+
// every queue mutation so Up/Down can walk a fresh view. queueIndex is
|
|
5068
|
+
// only invalidated when it falls outside the new bounds — staying in
|
|
5069
|
+
// bounds preserves the user's edit if the queue grew or stayed put.
|
|
5070
|
+
setQueue(queue) {
|
|
5071
|
+
this.queue = [...queue];
|
|
5072
|
+
if (this.queueIndex >= this.queue.length) {
|
|
5073
|
+
this.queueIndex = -1;
|
|
5320
5074
|
}
|
|
5321
|
-
mode = "normal";
|
|
5322
|
-
pendingAction = null;
|
|
5323
|
-
await refresh(kind === "kill" ? session.sessionId : void 0);
|
|
5324
|
-
} catch (err) {
|
|
5325
|
-
mode = "normal";
|
|
5326
|
-
pendingAction = null;
|
|
5327
|
-
transientStatus = `${kind} failed: ${err.message}`;
|
|
5328
|
-
paintIndicator();
|
|
5329
5075
|
}
|
|
5330
|
-
|
|
5331
|
-
|
|
5332
|
-
|
|
5333
|
-
|
|
5334
|
-
|
|
5335
|
-
|
|
5336
|
-
|
|
5337
|
-
const oldScroll = scrollOffset;
|
|
5338
|
-
selectedIdx = next;
|
|
5339
|
-
adjustScroll();
|
|
5340
|
-
if (scrollOffset !== oldScroll) {
|
|
5341
|
-
repaintViewport();
|
|
5342
|
-
if (old === 0 || selectedIdx === 0) {
|
|
5343
|
-
repaintNewItem();
|
|
5076
|
+
// Replace the contents of the first row, leaving subsequent rows alone.
|
|
5077
|
+
// Used by slash-command completion: the partial /foo gets swapped for the
|
|
5078
|
+
// matched command name. Cursor moves to the end of the replacement.
|
|
5079
|
+
replaceFirstLine(text) {
|
|
5080
|
+
this.buffer[0] = text;
|
|
5081
|
+
if (this.row === 0) {
|
|
5082
|
+
this.col = text.length;
|
|
5344
5083
|
}
|
|
5345
|
-
return;
|
|
5346
|
-
}
|
|
5347
|
-
if (old === 0) {
|
|
5348
|
-
repaintNewItem();
|
|
5349
|
-
} else {
|
|
5350
|
-
repaintSessionRow(old - 1);
|
|
5351
|
-
}
|
|
5352
|
-
if (selectedIdx === 0) {
|
|
5353
|
-
repaintNewItem();
|
|
5354
|
-
} else {
|
|
5355
|
-
repaintSessionRow(selectedIdx - 1);
|
|
5356
|
-
}
|
|
5357
|
-
};
|
|
5358
|
-
const clearTransient = () => {
|
|
5359
|
-
if (transientStatus === null) {
|
|
5360
|
-
return false;
|
|
5361
|
-
}
|
|
5362
|
-
transientStatus = null;
|
|
5363
|
-
paintIndicator();
|
|
5364
|
-
return true;
|
|
5365
|
-
};
|
|
5366
|
-
const onKey = (name, _matches, data) => {
|
|
5367
|
-
if (mode === "busy") {
|
|
5368
|
-
return;
|
|
5369
5084
|
}
|
|
5370
|
-
|
|
5371
|
-
|
|
5372
|
-
|
|
5373
|
-
|
|
5374
|
-
|
|
5375
|
-
|
|
5376
|
-
|
|
5377
|
-
|
|
5378
|
-
|
|
5085
|
+
// Public seed for the buffer (used for Escape pre-fill). Treated like a
|
|
5086
|
+
// fresh draft: nav state and any saved draft are cleared, cursor lands
|
|
5087
|
+
// at the end so the user can edit immediately. Attachments restore
|
|
5088
|
+
// alongside the text so a cancelled turn's chips land back in the
|
|
5089
|
+
// draft together with the typed prompt.
|
|
5090
|
+
setBuffer(text, attachments = []) {
|
|
5091
|
+
this.loadEntry(text);
|
|
5092
|
+
this.historyIndex = -1;
|
|
5093
|
+
this.queueIndex = -1;
|
|
5094
|
+
this.savedDraft = null;
|
|
5095
|
+
this.savedAttachments = null;
|
|
5096
|
+
this.historySearch = null;
|
|
5097
|
+
this.attachments = [...attachments];
|
|
5379
5098
|
}
|
|
5380
|
-
|
|
5381
|
-
if (
|
|
5382
|
-
|
|
5383
|
-
|
|
5384
|
-
|
|
5385
|
-
|
|
5386
|
-
renameBuffer = "";
|
|
5387
|
-
paintIndicator();
|
|
5388
|
-
return;
|
|
5099
|
+
feed(event) {
|
|
5100
|
+
if (this.historySearch !== null) {
|
|
5101
|
+
if (event.type === "char") {
|
|
5102
|
+
return this.mutateHistorySearchQuery(
|
|
5103
|
+
this.historySearch.query + event.ch.toLowerCase()
|
|
5104
|
+
);
|
|
5389
5105
|
}
|
|
5390
|
-
|
|
5391
|
-
|
|
5392
|
-
|
|
5393
|
-
|
|
5394
|
-
mode = "normal";
|
|
5395
|
-
pendingAction = null;
|
|
5396
|
-
renameBuffer = "";
|
|
5397
|
-
paintIndicator();
|
|
5398
|
-
return;
|
|
5399
|
-
}
|
|
5400
|
-
if (name === "BACKSPACE") {
|
|
5401
|
-
if (renameBuffer.length > 0) {
|
|
5402
|
-
renameBuffer = renameBuffer.slice(0, -1);
|
|
5403
|
-
paintIndicator();
|
|
5106
|
+
if (event.type === "paste") {
|
|
5107
|
+
return this.mutateHistorySearchQuery(
|
|
5108
|
+
this.historySearch.query + event.text.replace(/\n/g, " ").toLowerCase()
|
|
5109
|
+
);
|
|
5404
5110
|
}
|
|
5405
|
-
|
|
5406
|
-
|
|
5407
|
-
|
|
5408
|
-
renameBuffer = "";
|
|
5409
|
-
paintIndicator();
|
|
5410
|
-
return;
|
|
5411
|
-
}
|
|
5412
|
-
if (name === "CTRL_W") {
|
|
5413
|
-
const trimmedRight = renameBuffer.replace(/\s+$/, "");
|
|
5414
|
-
const lastSpace = trimmedRight.lastIndexOf(" ");
|
|
5415
|
-
renameBuffer = lastSpace >= 0 ? trimmedRight.slice(0, lastSpace) : "";
|
|
5416
|
-
paintIndicator();
|
|
5417
|
-
return;
|
|
5418
|
-
}
|
|
5419
|
-
if (data?.isCharacter) {
|
|
5420
|
-
renameBuffer += name;
|
|
5421
|
-
paintIndicator();
|
|
5422
|
-
return;
|
|
5423
|
-
}
|
|
5424
|
-
return;
|
|
5425
|
-
}
|
|
5426
|
-
if (mode === "confirm-kill" || mode === "confirm-delete") {
|
|
5427
|
-
if (data?.isCharacter && (name === "y" || name === "Y")) {
|
|
5428
|
-
const kind = mode === "confirm-kill" ? "kill" : "delete";
|
|
5429
|
-
void performAction(kind);
|
|
5430
|
-
return;
|
|
5431
|
-
}
|
|
5432
|
-
if (name === "ESCAPE" || name === "CTRL_C" || name === "ENTER" || name === "KP_ENTER" || data?.isCharacter && (name === "n" || name === "N")) {
|
|
5433
|
-
mode = "normal";
|
|
5434
|
-
pendingAction = null;
|
|
5435
|
-
paintIndicator();
|
|
5436
|
-
return;
|
|
5437
|
-
}
|
|
5438
|
-
return;
|
|
5439
|
-
}
|
|
5440
|
-
clearTransient();
|
|
5441
|
-
if (!searchActive && data?.isCharacter && name === "?") {
|
|
5442
|
-
mode = "help";
|
|
5443
|
-
renderHelp();
|
|
5444
|
-
return;
|
|
5445
|
-
}
|
|
5446
|
-
if (searchActive) {
|
|
5447
|
-
if (data?.isCharacter) {
|
|
5448
|
-
searchTerm += name;
|
|
5449
|
-
applyFilter();
|
|
5450
|
-
renderFromScratch();
|
|
5451
|
-
return;
|
|
5452
|
-
}
|
|
5453
|
-
if (name === "BACKSPACE") {
|
|
5454
|
-
if (searchTerm.length > 0) {
|
|
5455
|
-
searchTerm = searchTerm.slice(0, -1);
|
|
5456
|
-
applyFilter();
|
|
5457
|
-
renderFromScratch();
|
|
5458
|
-
} else {
|
|
5459
|
-
searchActive = false;
|
|
5460
|
-
applyFilter();
|
|
5461
|
-
renderFromScratch();
|
|
5462
|
-
}
|
|
5463
|
-
return;
|
|
5464
|
-
}
|
|
5465
|
-
if (name === "ESCAPE" || name === "CTRL_C") {
|
|
5466
|
-
searchActive = false;
|
|
5467
|
-
searchTerm = "";
|
|
5468
|
-
applyFilter();
|
|
5469
|
-
renderFromScratch();
|
|
5470
|
-
return;
|
|
5471
|
-
}
|
|
5472
|
-
}
|
|
5473
|
-
if (data?.isCharacter) {
|
|
5474
|
-
if (name === "/") {
|
|
5475
|
-
searchActive = true;
|
|
5476
|
-
searchTerm = "";
|
|
5477
|
-
applyFilter();
|
|
5478
|
-
renderFromScratch();
|
|
5479
|
-
return;
|
|
5480
|
-
}
|
|
5481
|
-
if (name === "n" || name === "N") {
|
|
5482
|
-
move(1);
|
|
5483
|
-
return;
|
|
5484
|
-
}
|
|
5485
|
-
if (name === "p" || name === "P") {
|
|
5486
|
-
move(-1);
|
|
5487
|
-
return;
|
|
5488
|
-
}
|
|
5489
|
-
if (name === "c" || name === "C") {
|
|
5490
|
-
cleanup();
|
|
5491
|
-
resolve6({ kind: "new" });
|
|
5492
|
-
return;
|
|
5493
|
-
}
|
|
5494
|
-
if (name === "q" || name === "Q") {
|
|
5495
|
-
cleanup();
|
|
5496
|
-
resolve6({ kind: "abort" });
|
|
5497
|
-
return;
|
|
5498
|
-
}
|
|
5499
|
-
if (name === "o" || name === "O") {
|
|
5500
|
-
const keepId = selectedIdx > 0 ? visible[selectedIdx - 1]?.sessionId : void 0;
|
|
5501
|
-
cwdOnly = !cwdOnly;
|
|
5502
|
-
applyFilter();
|
|
5503
|
-
if (keepId !== void 0) {
|
|
5504
|
-
const idx = visible.findIndex((s) => s.sessionId === keepId);
|
|
5505
|
-
if (idx >= 0) {
|
|
5506
|
-
selectedIdx = idx + 1;
|
|
5507
|
-
adjustScroll();
|
|
5111
|
+
if (event.type === "key") {
|
|
5112
|
+
if (event.name === "ctrl-r") {
|
|
5113
|
+
return this.advanceHistorySearch();
|
|
5508
5114
|
}
|
|
5509
|
-
|
|
5510
|
-
|
|
5511
|
-
|
|
5512
|
-
}
|
|
5513
|
-
if (name === "h" || name === "H") {
|
|
5514
|
-
const keepId = selectedIdx > 0 ? visible[selectedIdx - 1]?.sessionId : void 0;
|
|
5515
|
-
hostFilter = nextHostFilter(hostFilter, allSessions);
|
|
5516
|
-
applyFilter();
|
|
5517
|
-
if (keepId !== void 0) {
|
|
5518
|
-
const idx = visible.findIndex((s) => s.sessionId === keepId);
|
|
5519
|
-
if (idx >= 0) {
|
|
5520
|
-
selectedIdx = idx + 1;
|
|
5521
|
-
adjustScroll();
|
|
5115
|
+
if (event.name === "ctrl-s") {
|
|
5116
|
+
this.retreatHistorySearch();
|
|
5117
|
+
return [];
|
|
5522
5118
|
}
|
|
5119
|
+
if (event.name === "escape" || event.name === "ctrl-c") {
|
|
5120
|
+
this.cancelHistorySearch();
|
|
5121
|
+
return [];
|
|
5122
|
+
}
|
|
5123
|
+
if (event.name === "backspace") {
|
|
5124
|
+
if (this.historySearch.query.length === 0) {
|
|
5125
|
+
this.cancelHistorySearch();
|
|
5126
|
+
return [];
|
|
5127
|
+
}
|
|
5128
|
+
return this.mutateHistorySearchQuery(
|
|
5129
|
+
this.historySearch.query.slice(0, -1)
|
|
5130
|
+
);
|
|
5131
|
+
}
|
|
5132
|
+
this.historySearch = null;
|
|
5523
5133
|
}
|
|
5524
|
-
renderFromScratch();
|
|
5525
|
-
return;
|
|
5526
|
-
}
|
|
5527
|
-
if (name === "r" || name === "R") {
|
|
5528
|
-
const currentId = selectedIdx > 0 ? visible[selectedIdx - 1]?.sessionId : void 0;
|
|
5529
|
-
void refresh(currentId);
|
|
5530
|
-
return;
|
|
5531
|
-
}
|
|
5532
|
-
if ((name === "v" || name === "V") && selectedIdx > 0) {
|
|
5533
|
-
const session = visible[selectedIdx - 1];
|
|
5534
|
-
if (!session) {
|
|
5535
|
-
return;
|
|
5536
|
-
}
|
|
5537
|
-
cleanup();
|
|
5538
|
-
const result = {
|
|
5539
|
-
kind: "attach",
|
|
5540
|
-
sessionId: session.sessionId,
|
|
5541
|
-
readonly: true
|
|
5542
|
-
};
|
|
5543
|
-
if (session.agentId !== void 0) {
|
|
5544
|
-
result.agentId = session.agentId;
|
|
5545
|
-
}
|
|
5546
|
-
resolve6(result);
|
|
5547
|
-
return;
|
|
5548
|
-
}
|
|
5549
|
-
if ((name === "k" || name === "K") && selectedIdx > 0) {
|
|
5550
|
-
const session = visible[selectedIdx - 1];
|
|
5551
|
-
if (!session) {
|
|
5552
|
-
return;
|
|
5553
|
-
}
|
|
5554
|
-
pendingAction = {
|
|
5555
|
-
sessionId: session.sessionId,
|
|
5556
|
-
cwd: session.cwd,
|
|
5557
|
-
status: session.status
|
|
5558
|
-
};
|
|
5559
|
-
mode = "confirm-kill";
|
|
5560
|
-
paintIndicator();
|
|
5561
|
-
return;
|
|
5562
5134
|
}
|
|
5563
|
-
if (
|
|
5564
|
-
|
|
5565
|
-
|
|
5566
|
-
return;
|
|
5567
|
-
}
|
|
5568
|
-
pendingAction = {
|
|
5569
|
-
sessionId: session.sessionId,
|
|
5570
|
-
cwd: session.cwd,
|
|
5571
|
-
status: session.status
|
|
5572
|
-
};
|
|
5573
|
-
renameBuffer = session.title ?? "";
|
|
5574
|
-
mode = "rename";
|
|
5575
|
-
paintIndicator();
|
|
5576
|
-
return;
|
|
5135
|
+
if (event.type === "char") {
|
|
5136
|
+
this.insertChar(event.ch);
|
|
5137
|
+
return [];
|
|
5577
5138
|
}
|
|
5578
|
-
if (
|
|
5579
|
-
|
|
5580
|
-
|
|
5581
|
-
return;
|
|
5582
|
-
}
|
|
5583
|
-
void performRegen({ sessionId: session.sessionId });
|
|
5584
|
-
return;
|
|
5139
|
+
if (event.type === "paste") {
|
|
5140
|
+
this.insertText(event.text);
|
|
5141
|
+
return [];
|
|
5585
5142
|
}
|
|
5586
|
-
if (
|
|
5587
|
-
|
|
5588
|
-
if (!session) {
|
|
5589
|
-
return;
|
|
5590
|
-
}
|
|
5591
|
-
if (session.status === "live") {
|
|
5592
|
-
transientStatus = "session is live \u2014 press k to kill it first";
|
|
5593
|
-
paintIndicator();
|
|
5594
|
-
return;
|
|
5595
|
-
}
|
|
5596
|
-
pendingAction = {
|
|
5597
|
-
sessionId: session.sessionId,
|
|
5598
|
-
cwd: session.cwd,
|
|
5599
|
-
status: session.status
|
|
5600
|
-
};
|
|
5601
|
-
mode = "confirm-delete";
|
|
5602
|
-
paintIndicator();
|
|
5603
|
-
return;
|
|
5143
|
+
if (event.type === "attachment-paths") {
|
|
5144
|
+
return [];
|
|
5604
5145
|
}
|
|
5605
|
-
return;
|
|
5146
|
+
return this.handleKey(event.name);
|
|
5606
5147
|
}
|
|
5607
|
-
|
|
5608
|
-
|
|
5609
|
-
|
|
5610
|
-
|
|
5611
|
-
|
|
5612
|
-
|
|
5613
|
-
|
|
5614
|
-
|
|
5615
|
-
|
|
5616
|
-
|
|
5617
|
-
|
|
5618
|
-
|
|
5619
|
-
|
|
5620
|
-
|
|
5621
|
-
|
|
5622
|
-
|
|
5623
|
-
|
|
5624
|
-
|
|
5625
|
-
|
|
5626
|
-
|
|
5627
|
-
|
|
5628
|
-
|
|
5629
|
-
|
|
5630
|
-
|
|
5631
|
-
|
|
5632
|
-
|
|
5633
|
-
|
|
5634
|
-
|
|
5635
|
-
|
|
5636
|
-
|
|
5637
|
-
|
|
5638
|
-
return;
|
|
5639
|
-
|
|
5640
|
-
|
|
5641
|
-
|
|
5642
|
-
|
|
5643
|
-
|
|
5644
|
-
|
|
5645
|
-
|
|
5646
|
-
|
|
5647
|
-
|
|
5648
|
-
|
|
5649
|
-
|
|
5650
|
-
|
|
5651
|
-
|
|
5652
|
-
|
|
5653
|
-
|
|
5654
|
-
|
|
5655
|
-
|
|
5656
|
-
|
|
5657
|
-
|
|
5658
|
-
|
|
5659
|
-
|
|
5660
|
-
|
|
5661
|
-
|
|
5662
|
-
|
|
5663
|
-
|
|
5664
|
-
|
|
5665
|
-
|
|
5666
|
-
|
|
5667
|
-
|
|
5668
|
-
|
|
5669
|
-
|
|
5670
|
-
|
|
5671
|
-
|
|
5672
|
-
|
|
5673
|
-
|
|
5674
|
-
|
|
5675
|
-
|
|
5676
|
-
|
|
5677
|
-
|
|
5678
|
-
|
|
5679
|
-
|
|
5680
|
-
|
|
5681
|
-
|
|
5682
|
-
|
|
5683
|
-
|
|
5684
|
-
|
|
5685
|
-
|
|
5686
|
-
}
|
|
5687
|
-
|
|
5688
|
-
|
|
5689
|
-
|
|
5690
|
-
|
|
5691
|
-
|
|
5692
|
-
|
|
5693
|
-
|
|
5694
|
-
|
|
5695
|
-
|
|
5696
|
-
|
|
5697
|
-
|
|
5698
|
-
|
|
5699
|
-
|
|
5700
|
-
}
|
|
5701
|
-
|
|
5702
|
-
|
|
5703
|
-
|
|
5704
|
-
|
|
5705
|
-
|
|
5706
|
-
|
|
5707
|
-
|
|
5708
|
-
|
|
5709
|
-
|
|
5710
|
-
s.title ?? "",
|
|
5711
|
-
s.cwd,
|
|
5712
|
-
shortenHomePath(s.cwd)
|
|
5713
|
-
];
|
|
5714
|
-
for (const h of haystacks) {
|
|
5715
|
-
if (h.toLowerCase().includes(t)) {
|
|
5716
|
-
return true;
|
|
5717
|
-
}
|
|
5718
|
-
}
|
|
5719
|
-
return false;
|
|
5720
|
-
}
|
|
5721
|
-
var ROW_PREFIX_WIDTH, HELP_KEYS_WIDTH, HELP_ENTRIES;
|
|
5722
|
-
var init_picker = __esm({
|
|
5723
|
-
"src/tui/picker.ts"() {
|
|
5724
|
-
"use strict";
|
|
5725
|
-
init_session_row();
|
|
5726
|
-
init_paths();
|
|
5727
|
-
init_session();
|
|
5728
|
-
init_discovery();
|
|
5729
|
-
ROW_PREFIX_WIDTH = 2;
|
|
5730
|
-
HELP_KEYS_WIDTH = 20;
|
|
5731
|
-
HELP_ENTRIES = [
|
|
5732
|
-
["\u2191 / \u2193 or n / p", "navigate"],
|
|
5733
|
-
["PgUp / PgDn", "page up / page down"],
|
|
5734
|
-
["Home / End", "first / last"],
|
|
5735
|
-
["Enter", "open selected session (or create new)"],
|
|
5736
|
-
["v", "view-only (open transcript without spawning the agent)"],
|
|
5737
|
-
null,
|
|
5738
|
-
["/", "search sessions"],
|
|
5739
|
-
["o", "toggle cwd-only filter"],
|
|
5740
|
-
["h", "cycle host filter (local / <peer> / all)"],
|
|
5741
|
-
["r", "refresh from daemon"],
|
|
5742
|
-
null,
|
|
5743
|
-
["k", "kill the selected live session"],
|
|
5744
|
-
["d", "delete the selected cold session"],
|
|
5745
|
-
["t", "retitle the selected session"],
|
|
5746
|
-
["T", "regenerate title via agent (live session)"],
|
|
5747
|
-
null,
|
|
5748
|
-
["c", "create new session"],
|
|
5749
|
-
["?", "toggle this help"],
|
|
5750
|
-
["q / Esc / ^C / ^D", "quit picker (detach)"]
|
|
5751
|
-
];
|
|
5752
|
-
}
|
|
5753
|
-
});
|
|
5754
|
-
|
|
5755
|
-
// src/core/cwd.ts
|
|
5756
|
-
import * as fs19 from "fs/promises";
|
|
5757
|
-
import * as path13 from "path";
|
|
5758
|
-
async function validateLocalCwd(input) {
|
|
5759
|
-
const trimmed = input.trim();
|
|
5760
|
-
if (trimmed.length === 0) {
|
|
5761
|
-
return { ok: false, reason: "path is empty" };
|
|
5762
|
-
}
|
|
5763
|
-
const resolved = path13.resolve(expandHome(trimmed));
|
|
5764
|
-
let stat5;
|
|
5765
|
-
try {
|
|
5766
|
-
stat5 = await fs19.stat(resolved);
|
|
5767
|
-
} catch {
|
|
5768
|
-
return { ok: false, reason: `${resolved} does not exist` };
|
|
5769
|
-
}
|
|
5770
|
-
if (!stat5.isDirectory()) {
|
|
5771
|
-
return { ok: false, reason: `${resolved} is not a directory` };
|
|
5772
|
-
}
|
|
5773
|
-
return { ok: true, path: resolved };
|
|
5774
|
-
}
|
|
5775
|
-
var init_cwd = __esm({
|
|
5776
|
-
"src/core/cwd.ts"() {
|
|
5777
|
-
"use strict";
|
|
5778
|
-
init_config();
|
|
5779
|
-
}
|
|
5780
|
-
});
|
|
5781
|
-
|
|
5782
|
-
// src/tui/prompt-utils.ts
|
|
5783
|
-
function resetTerminalModes() {
|
|
5784
|
-
process.stdout.write("\x1B[<u");
|
|
5785
|
-
process.stdout.write("\x1B[?2004l");
|
|
5786
|
-
process.stdout.write("\x1B[>4;0m");
|
|
5787
|
-
process.stdout.write("\x1B[>5;0m");
|
|
5788
|
-
process.stdout.write("\x1B[?1000l");
|
|
5789
|
-
process.stdout.write("\x1B[?1002l");
|
|
5790
|
-
process.stdout.write("\x1B[?1006l");
|
|
5791
|
-
process.stdout.write("\x1B[?1l");
|
|
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();
|
|
5877
|
-
const shortId2 = stripHydraSessionPrefix(session.sessionId);
|
|
5878
|
-
const fromMachine = session.importedFromMachine ?? "another machine";
|
|
5879
|
-
const originalCwd = shortenHomePath(session.cwd);
|
|
5880
|
-
let buffer = defaultCwd;
|
|
5881
|
-
let errorLine = null;
|
|
5882
|
-
let busy = false;
|
|
5883
|
-
let layout = null;
|
|
5884
|
-
const render = () => {
|
|
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
|
-
);
|
|
5917
|
-
}
|
|
5918
|
-
};
|
|
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));
|
|
5932
|
-
if (!busy) {
|
|
5933
|
-
term.bgWhite(" ");
|
|
5934
|
-
}
|
|
5935
|
-
};
|
|
5936
|
-
const repaintInput = () => {
|
|
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);
|
|
5946
|
-
if (errorLine !== null) {
|
|
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
|
-
);
|
|
5952
|
-
}
|
|
5953
|
-
};
|
|
5954
|
-
render();
|
|
5955
|
-
return await new Promise((resolve6) => {
|
|
5956
|
-
let resolved = false;
|
|
5957
|
-
const cleanup = () => {
|
|
5958
|
-
if (resolved) {
|
|
5959
|
-
return;
|
|
5148
|
+
handleKey(name) {
|
|
5149
|
+
switch (name) {
|
|
5150
|
+
case "enter":
|
|
5151
|
+
return this.send();
|
|
5152
|
+
case "shift-enter":
|
|
5153
|
+
case "ctrl-enter":
|
|
5154
|
+
return this.amend();
|
|
5155
|
+
case "alt-enter":
|
|
5156
|
+
this.insertNewline();
|
|
5157
|
+
return [];
|
|
5158
|
+
case "shift-tab":
|
|
5159
|
+
this.planMode = !this.planMode;
|
|
5160
|
+
return [
|
|
5161
|
+
{ type: "plan-toggle", on: this.planMode },
|
|
5162
|
+
{ type: "redraw-banner" }
|
|
5163
|
+
];
|
|
5164
|
+
case "tab":
|
|
5165
|
+
this.insertText(" ");
|
|
5166
|
+
return [];
|
|
5167
|
+
case "up":
|
|
5168
|
+
return this.handleUp();
|
|
5169
|
+
case "down":
|
|
5170
|
+
return this.handleDown();
|
|
5171
|
+
case "left":
|
|
5172
|
+
this.moveLeft();
|
|
5173
|
+
return [];
|
|
5174
|
+
case "right":
|
|
5175
|
+
this.moveRight();
|
|
5176
|
+
return [];
|
|
5177
|
+
case "ctrl-a":
|
|
5178
|
+
this.col = 0;
|
|
5179
|
+
return [];
|
|
5180
|
+
case "ctrl-e":
|
|
5181
|
+
this.col = this.currentLine().length;
|
|
5182
|
+
return [];
|
|
5183
|
+
case "home":
|
|
5184
|
+
return this.handleHome();
|
|
5185
|
+
case "end":
|
|
5186
|
+
return this.handleEnd();
|
|
5187
|
+
case "ctrl-b":
|
|
5188
|
+
this.moveLeft();
|
|
5189
|
+
return [];
|
|
5190
|
+
case "ctrl-f":
|
|
5191
|
+
this.moveRight();
|
|
5192
|
+
return [];
|
|
5193
|
+
case "ctrl-g":
|
|
5194
|
+
return [{ type: "show-help" }];
|
|
5195
|
+
case "alt-b":
|
|
5196
|
+
this.moveWordBackward();
|
|
5197
|
+
return [];
|
|
5198
|
+
case "alt-f":
|
|
5199
|
+
this.moveWordForward();
|
|
5200
|
+
return [];
|
|
5201
|
+
case "ctrl-k":
|
|
5202
|
+
this.killToEnd();
|
|
5203
|
+
return [];
|
|
5204
|
+
case "ctrl-n":
|
|
5205
|
+
return this.handleDown();
|
|
5206
|
+
case "ctrl-o":
|
|
5207
|
+
return [{ type: "toggle-tools" }];
|
|
5208
|
+
case "backspace":
|
|
5209
|
+
this.backspace();
|
|
5210
|
+
return [];
|
|
5211
|
+
case "delete":
|
|
5212
|
+
this.deleteForward();
|
|
5213
|
+
return [];
|
|
5214
|
+
case "ctrl-c":
|
|
5215
|
+
return this.handleCtrlC();
|
|
5216
|
+
case "ctrl-d":
|
|
5217
|
+
if (this.bufferIsEmpty()) {
|
|
5218
|
+
return [{ type: "exit" }];
|
|
5219
|
+
}
|
|
5220
|
+
this.deleteForward();
|
|
5221
|
+
return [];
|
|
5222
|
+
case "ctrl-l":
|
|
5223
|
+
return [{ type: "redraw" }];
|
|
5224
|
+
case "ctrl-p":
|
|
5225
|
+
return [{ type: "switch-session" }];
|
|
5226
|
+
case "ctrl-t":
|
|
5227
|
+
return [{ type: "next-live-session" }];
|
|
5228
|
+
case "ctrl-r":
|
|
5229
|
+
return this.startHistorySearch();
|
|
5230
|
+
case "ctrl-s":
|
|
5231
|
+
return [];
|
|
5232
|
+
case "ctrl-u":
|
|
5233
|
+
this.killLine();
|
|
5234
|
+
return [];
|
|
5235
|
+
case "ctrl-v":
|
|
5236
|
+
return [{ type: "attachment-request", source: "clipboard" }];
|
|
5237
|
+
case "ctrl-w":
|
|
5238
|
+
this.killWord();
|
|
5239
|
+
return [];
|
|
5240
|
+
case "ctrl-x":
|
|
5241
|
+
return [{ type: "toggle-mouse" }];
|
|
5242
|
+
case "ctrl-y":
|
|
5243
|
+
this.yank();
|
|
5244
|
+
return [];
|
|
5245
|
+
case "escape":
|
|
5246
|
+
if (this.turnRunning) {
|
|
5247
|
+
return [{ type: "cancel", prefill: true }];
|
|
5248
|
+
}
|
|
5249
|
+
return [];
|
|
5250
|
+
}
|
|
5960
5251
|
}
|
|
5961
|
-
|
|
5962
|
-
|
|
5963
|
-
term.off("resize", onResize);
|
|
5964
|
-
term.grabInput(false);
|
|
5965
|
-
term.hideCursor(false);
|
|
5966
|
-
term.moveTo(1, 1).eraseDisplayBelow();
|
|
5967
|
-
};
|
|
5968
|
-
const finish = (value) => {
|
|
5969
|
-
cleanup();
|
|
5970
|
-
resolve6(value);
|
|
5971
|
-
};
|
|
5972
|
-
const onResize = () => {
|
|
5973
|
-
if (resolved) {
|
|
5974
|
-
return;
|
|
5252
|
+
currentLine() {
|
|
5253
|
+
return this.buffer[this.row] ?? "";
|
|
5975
5254
|
}
|
|
5976
|
-
|
|
5977
|
-
|
|
5978
|
-
|
|
5979
|
-
|
|
5980
|
-
return;
|
|
5255
|
+
setCurrentLine(line) {
|
|
5256
|
+
this.buffer[this.row] = line;
|
|
5257
|
+
}
|
|
5258
|
+
bufferText() {
|
|
5259
|
+
return this.buffer.join("\n");
|
|
5260
|
+
}
|
|
5261
|
+
bufferIsEmpty() {
|
|
5262
|
+
return this.buffer.length === 1 && this.buffer[0] === "";
|
|
5263
|
+
}
|
|
5264
|
+
clearBuffer() {
|
|
5265
|
+
this.buffer = [""];
|
|
5266
|
+
this.row = 0;
|
|
5267
|
+
this.col = 0;
|
|
5268
|
+
this.historyIndex = -1;
|
|
5269
|
+
this.queueIndex = -1;
|
|
5270
|
+
this.savedDraft = null;
|
|
5271
|
+
this.savedAttachments = null;
|
|
5272
|
+
this.historySearch = null;
|
|
5273
|
+
this.attachments = [];
|
|
5274
|
+
}
|
|
5275
|
+
insertChar(ch) {
|
|
5276
|
+
if (ch.length === 0) {
|
|
5277
|
+
return;
|
|
5278
|
+
}
|
|
5279
|
+
if (ch.includes("\n")) {
|
|
5280
|
+
this.insertText(ch);
|
|
5281
|
+
return;
|
|
5282
|
+
}
|
|
5283
|
+
const line = this.currentLine();
|
|
5284
|
+
this.setCurrentLine(line.slice(0, this.col) + ch + line.slice(this.col));
|
|
5285
|
+
this.col += ch.length;
|
|
5286
|
+
}
|
|
5287
|
+
insertText(text) {
|
|
5288
|
+
const lines = text.split("\n");
|
|
5289
|
+
if (lines.length === 1) {
|
|
5290
|
+
this.insertChar(lines[0] ?? "");
|
|
5291
|
+
return;
|
|
5292
|
+
}
|
|
5293
|
+
const cur = this.currentLine();
|
|
5294
|
+
const before = cur.slice(0, this.col);
|
|
5295
|
+
const after = cur.slice(this.col);
|
|
5296
|
+
const first = lines[0] ?? "";
|
|
5297
|
+
const last = lines[lines.length - 1] ?? "";
|
|
5298
|
+
const middle = lines.slice(1, -1);
|
|
5299
|
+
this.setCurrentLine(before + first);
|
|
5300
|
+
const newRows = [...middle, last + after];
|
|
5301
|
+
this.buffer.splice(this.row + 1, 0, ...newRows);
|
|
5302
|
+
this.row += lines.length - 1;
|
|
5303
|
+
this.col = last.length;
|
|
5304
|
+
}
|
|
5305
|
+
insertNewline() {
|
|
5306
|
+
const line = this.currentLine();
|
|
5307
|
+
const before = line.slice(0, this.col);
|
|
5308
|
+
const after = line.slice(this.col);
|
|
5309
|
+
this.setCurrentLine(before);
|
|
5310
|
+
this.buffer.splice(this.row + 1, 0, after);
|
|
5311
|
+
this.row += 1;
|
|
5312
|
+
this.col = 0;
|
|
5313
|
+
}
|
|
5314
|
+
backspace() {
|
|
5315
|
+
if (this.col > 0) {
|
|
5316
|
+
const line = this.currentLine();
|
|
5317
|
+
this.setCurrentLine(line.slice(0, this.col - 1) + line.slice(this.col));
|
|
5318
|
+
this.col -= 1;
|
|
5319
|
+
return;
|
|
5320
|
+
}
|
|
5321
|
+
if (this.row === 0) {
|
|
5322
|
+
return;
|
|
5323
|
+
}
|
|
5324
|
+
const prev = this.buffer[this.row - 1] ?? "";
|
|
5325
|
+
const cur = this.currentLine();
|
|
5326
|
+
this.buffer.splice(this.row, 1);
|
|
5327
|
+
this.row -= 1;
|
|
5328
|
+
this.col = prev.length;
|
|
5329
|
+
this.buffer[this.row] = prev + cur;
|
|
5330
|
+
}
|
|
5331
|
+
deleteForward() {
|
|
5332
|
+
const line = this.currentLine();
|
|
5333
|
+
if (this.col < line.length) {
|
|
5334
|
+
this.setCurrentLine(line.slice(0, this.col) + line.slice(this.col + 1));
|
|
5335
|
+
return;
|
|
5336
|
+
}
|
|
5337
|
+
if (this.row < this.buffer.length - 1) {
|
|
5338
|
+
const next = this.buffer[this.row + 1] ?? "";
|
|
5339
|
+
this.buffer.splice(this.row + 1, 1);
|
|
5340
|
+
this.setCurrentLine(line + next);
|
|
5341
|
+
}
|
|
5342
|
+
}
|
|
5343
|
+
// ^U: kill from cursor to start of current line. At col 0 with a line
|
|
5344
|
+
// above:
|
|
5345
|
+
// - If the current line is empty, collapse it (kill just the
|
|
5346
|
+
// newline) so the cursor lands at the end of the previous line.
|
|
5347
|
+
// Don't slurp that line's contents.
|
|
5348
|
+
// - Otherwise, kill the previous line entirely + the joining
|
|
5349
|
+
// newline, so ^U from the start of a non-empty line walks up
|
|
5350
|
+
// line-by-line.
|
|
5351
|
+
// Single-line behavior is unchanged.
|
|
5352
|
+
killLine() {
|
|
5353
|
+
if (this.col > 0) {
|
|
5354
|
+
const line = this.currentLine();
|
|
5355
|
+
this.killBuffer = line.slice(0, this.col);
|
|
5356
|
+
this.setCurrentLine(line.slice(this.col));
|
|
5357
|
+
this.col = 0;
|
|
5358
|
+
return;
|
|
5359
|
+
}
|
|
5360
|
+
if (this.row === 0) {
|
|
5361
|
+
return;
|
|
5362
|
+
}
|
|
5363
|
+
if (this.currentLine().length === 0) {
|
|
5364
|
+
this.killBuffer = "\n";
|
|
5365
|
+
this.buffer.splice(this.row, 1);
|
|
5366
|
+
this.row -= 1;
|
|
5367
|
+
this.col = this.currentLine().length;
|
|
5368
|
+
return;
|
|
5369
|
+
}
|
|
5370
|
+
const prev = this.buffer[this.row - 1] ?? "";
|
|
5371
|
+
this.killBuffer = prev + "\n";
|
|
5372
|
+
this.buffer.splice(this.row - 1, 1);
|
|
5373
|
+
this.row -= 1;
|
|
5374
|
+
}
|
|
5375
|
+
// ^K: kill from cursor to end of current line. At end-of-line with a
|
|
5376
|
+
// line below:
|
|
5377
|
+
// - If the current line is empty, collapse it (kill just the
|
|
5378
|
+
// newline) so what was the next line takes its place. Don't slurp
|
|
5379
|
+
// that line's contents.
|
|
5380
|
+
// - Otherwise, kill the joining newline + the entire next line, so
|
|
5381
|
+
// ^K from the end of a non-empty line walks down line-by-line.
|
|
5382
|
+
// Single-line behavior is unchanged.
|
|
5383
|
+
killToEnd() {
|
|
5384
|
+
const line = this.currentLine();
|
|
5385
|
+
if (this.col < line.length) {
|
|
5386
|
+
this.killBuffer = line.slice(this.col);
|
|
5387
|
+
this.setCurrentLine(line.slice(0, this.col));
|
|
5388
|
+
return;
|
|
5389
|
+
}
|
|
5390
|
+
if (this.row >= this.buffer.length - 1) {
|
|
5391
|
+
return;
|
|
5392
|
+
}
|
|
5393
|
+
if (line.length === 0) {
|
|
5394
|
+
this.killBuffer = "\n";
|
|
5395
|
+
this.buffer.splice(this.row, 1);
|
|
5396
|
+
return;
|
|
5397
|
+
}
|
|
5398
|
+
const next = this.buffer[this.row + 1] ?? "";
|
|
5399
|
+
this.killBuffer = "\n" + next;
|
|
5400
|
+
this.buffer.splice(this.row + 1, 1);
|
|
5401
|
+
}
|
|
5402
|
+
killWord() {
|
|
5403
|
+
const line = this.currentLine();
|
|
5404
|
+
if (this.col === 0) {
|
|
5405
|
+
this.backspace();
|
|
5406
|
+
return;
|
|
5407
|
+
}
|
|
5408
|
+
let i = this.col;
|
|
5409
|
+
while (i > 0 && /\s/.test(line[i - 1] ?? "")) {
|
|
5410
|
+
i -= 1;
|
|
5411
|
+
}
|
|
5412
|
+
while (i > 0 && !/\s/.test(line[i - 1] ?? "")) {
|
|
5413
|
+
i -= 1;
|
|
5414
|
+
}
|
|
5415
|
+
const killed = line.slice(i, this.col);
|
|
5416
|
+
if (killed.length > 0) {
|
|
5417
|
+
this.killBuffer = killed;
|
|
5418
|
+
}
|
|
5419
|
+
this.setCurrentLine(line.slice(0, i) + line.slice(this.col));
|
|
5420
|
+
this.col = i;
|
|
5421
|
+
}
|
|
5422
|
+
yank() {
|
|
5423
|
+
if (this.killBuffer.length === 0) {
|
|
5424
|
+
return;
|
|
5425
|
+
}
|
|
5426
|
+
this.insertText(this.killBuffer);
|
|
5427
|
+
}
|
|
5428
|
+
moveLeft() {
|
|
5429
|
+
if (this.col > 0) {
|
|
5430
|
+
this.col -= 1;
|
|
5431
|
+
return;
|
|
5432
|
+
}
|
|
5433
|
+
if (this.row > 0) {
|
|
5434
|
+
this.row -= 1;
|
|
5435
|
+
this.col = this.currentLine().length;
|
|
5436
|
+
}
|
|
5437
|
+
}
|
|
5438
|
+
moveRight() {
|
|
5439
|
+
if (this.col < this.currentLine().length) {
|
|
5440
|
+
this.col += 1;
|
|
5441
|
+
return;
|
|
5442
|
+
}
|
|
5443
|
+
if (this.row < this.buffer.length - 1) {
|
|
5444
|
+
this.row += 1;
|
|
5445
|
+
this.col = 0;
|
|
5446
|
+
}
|
|
5447
|
+
}
|
|
5448
|
+
moveWordBackward() {
|
|
5449
|
+
if (this.col === 0) {
|
|
5450
|
+
if (this.row === 0) {
|
|
5451
|
+
return;
|
|
5452
|
+
}
|
|
5453
|
+
this.row -= 1;
|
|
5454
|
+
this.col = this.currentLine().length;
|
|
5455
|
+
return;
|
|
5456
|
+
}
|
|
5457
|
+
const line = this.currentLine();
|
|
5458
|
+
let i = this.col;
|
|
5459
|
+
while (i > 0 && /\s/.test(line[i - 1] ?? "")) {
|
|
5460
|
+
i -= 1;
|
|
5461
|
+
}
|
|
5462
|
+
while (i > 0 && !/\s/.test(line[i - 1] ?? "")) {
|
|
5463
|
+
i -= 1;
|
|
5464
|
+
}
|
|
5465
|
+
this.col = i;
|
|
5981
5466
|
}
|
|
5982
|
-
|
|
5983
|
-
const
|
|
5984
|
-
|
|
5985
|
-
|
|
5986
|
-
repaintInput();
|
|
5987
|
-
void validateLocalCwd(candidate).then((result) => {
|
|
5988
|
-
busy = false;
|
|
5989
|
-
if (result.ok) {
|
|
5990
|
-
finish({ kind: "ok", path: result.path });
|
|
5467
|
+
moveWordForward() {
|
|
5468
|
+
const line = this.currentLine();
|
|
5469
|
+
if (this.col >= line.length) {
|
|
5470
|
+
if (this.row >= this.buffer.length - 1) {
|
|
5991
5471
|
return;
|
|
5992
5472
|
}
|
|
5993
|
-
|
|
5994
|
-
|
|
5995
|
-
|
|
5996
|
-
return;
|
|
5997
|
-
}
|
|
5998
|
-
if (name === "ESCAPE") {
|
|
5999
|
-
finish({ kind: "back" });
|
|
6000
|
-
return;
|
|
6001
|
-
}
|
|
6002
|
-
if (name === "CTRL_C" || name === "CTRL_D") {
|
|
6003
|
-
finish({ kind: "cancel" });
|
|
6004
|
-
return;
|
|
6005
|
-
}
|
|
6006
|
-
if (name === "BACKSPACE") {
|
|
6007
|
-
if (buffer.length > 0) {
|
|
6008
|
-
buffer = buffer.slice(0, -1);
|
|
6009
|
-
errorLine = null;
|
|
6010
|
-
repaintInput();
|
|
5473
|
+
this.row += 1;
|
|
5474
|
+
this.col = 0;
|
|
5475
|
+
return;
|
|
6011
5476
|
}
|
|
6012
|
-
|
|
5477
|
+
let i = this.col;
|
|
5478
|
+
while (i < line.length && /\s/.test(line[i] ?? "")) {
|
|
5479
|
+
i += 1;
|
|
5480
|
+
}
|
|
5481
|
+
while (i < line.length && !/\s/.test(line[i] ?? "")) {
|
|
5482
|
+
i += 1;
|
|
5483
|
+
}
|
|
5484
|
+
this.col = i;
|
|
6013
5485
|
}
|
|
6014
|
-
|
|
6015
|
-
|
|
6016
|
-
|
|
6017
|
-
|
|
6018
|
-
|
|
5486
|
+
// Up walks the navigation stack from newest to oldest: pending queue
|
|
5487
|
+
// items first (so the user can edit something they just enqueued),
|
|
5488
|
+
// then prompt history. Cursor movement within a multi-line buffer
|
|
5489
|
+
// takes priority when not already navigating.
|
|
5490
|
+
handleUp() {
|
|
5491
|
+
if (this.row > 0) {
|
|
5492
|
+
this.row -= 1;
|
|
5493
|
+
this.col = Math.min(this.col, this.currentLine().length);
|
|
5494
|
+
return [];
|
|
5495
|
+
}
|
|
5496
|
+
if (this.queueIndex === -1 && this.historyIndex === -1) {
|
|
5497
|
+
if (this.queue.length === 0 && this.history.length === 0) {
|
|
5498
|
+
return [];
|
|
5499
|
+
}
|
|
5500
|
+
this.savedDraft = {
|
|
5501
|
+
buffer: [...this.buffer],
|
|
5502
|
+
row: this.row,
|
|
5503
|
+
col: this.col
|
|
5504
|
+
};
|
|
5505
|
+
this.savedAttachments = [...this.attachments];
|
|
5506
|
+
this.attachments = [];
|
|
5507
|
+
if (this.queue.length > 0) {
|
|
5508
|
+
this.queueIndex = this.queue.length - 1;
|
|
5509
|
+
this.loadEntry(this.queue[this.queueIndex] ?? "");
|
|
5510
|
+
} else {
|
|
5511
|
+
this.historyIndex = this.history.length - 1;
|
|
5512
|
+
this.loadEntry(this.history[this.historyIndex] ?? "");
|
|
5513
|
+
}
|
|
5514
|
+
return [];
|
|
5515
|
+
}
|
|
5516
|
+
if (this.queueIndex >= 0) {
|
|
5517
|
+
if (this.queueIndex > 0) {
|
|
5518
|
+
this.queueIndex -= 1;
|
|
5519
|
+
this.loadEntry(this.queue[this.queueIndex] ?? "");
|
|
5520
|
+
return [];
|
|
5521
|
+
}
|
|
5522
|
+
if (this.history.length === 0) {
|
|
5523
|
+
return [];
|
|
5524
|
+
}
|
|
5525
|
+
this.queueIndex = -1;
|
|
5526
|
+
this.historyIndex = this.history.length - 1;
|
|
5527
|
+
this.loadEntry(this.history[this.historyIndex] ?? "");
|
|
5528
|
+
return [];
|
|
5529
|
+
}
|
|
5530
|
+
if (this.historyIndex > 0) {
|
|
5531
|
+
this.historyIndex -= 1;
|
|
5532
|
+
this.loadEntry(this.history[this.historyIndex] ?? "");
|
|
5533
|
+
}
|
|
5534
|
+
return [];
|
|
6019
5535
|
}
|
|
6020
|
-
|
|
6021
|
-
|
|
6022
|
-
|
|
6023
|
-
|
|
6024
|
-
|
|
6025
|
-
)
|
|
6026
|
-
|
|
6027
|
-
|
|
6028
|
-
|
|
6029
|
-
|
|
5536
|
+
// Down reverses the Up walk: history (older → newer), then queue
|
|
5537
|
+
// (oldest → newest), then restore the original draft. Within a
|
|
5538
|
+
// multi-line buffer, plain cursor movement still wins when no
|
|
5539
|
+
// navigation is in progress.
|
|
5540
|
+
handleDown() {
|
|
5541
|
+
if (this.row < this.buffer.length - 1 && this.historyIndex === -1 && this.queueIndex === -1) {
|
|
5542
|
+
this.row += 1;
|
|
5543
|
+
this.col = Math.min(this.col, this.currentLine().length);
|
|
5544
|
+
return [];
|
|
5545
|
+
}
|
|
5546
|
+
if (this.historyIndex >= 0) {
|
|
5547
|
+
if (this.historyIndex < this.history.length - 1) {
|
|
5548
|
+
this.historyIndex += 1;
|
|
5549
|
+
this.loadEntry(this.history[this.historyIndex] ?? "");
|
|
5550
|
+
return [];
|
|
5551
|
+
}
|
|
5552
|
+
this.historyIndex = -1;
|
|
5553
|
+
if (this.queue.length > 0) {
|
|
5554
|
+
this.queueIndex = 0;
|
|
5555
|
+
this.loadEntry(this.queue[this.queueIndex] ?? "");
|
|
5556
|
+
return [];
|
|
5557
|
+
}
|
|
5558
|
+
this.restoreDraft();
|
|
5559
|
+
return [];
|
|
5560
|
+
}
|
|
5561
|
+
if (this.queueIndex >= 0) {
|
|
5562
|
+
if (this.queueIndex < this.queue.length - 1) {
|
|
5563
|
+
this.queueIndex += 1;
|
|
5564
|
+
this.loadEntry(this.queue[this.queueIndex] ?? "");
|
|
5565
|
+
return [];
|
|
5566
|
+
}
|
|
5567
|
+
this.queueIndex = -1;
|
|
5568
|
+
this.restoreDraft();
|
|
5569
|
+
return [];
|
|
5570
|
+
}
|
|
5571
|
+
return [];
|
|
6030
5572
|
}
|
|
6031
|
-
|
|
6032
|
-
|
|
6033
|
-
|
|
6034
|
-
|
|
6035
|
-
|
|
5573
|
+
restoreDraft() {
|
|
5574
|
+
if (this.savedDraft) {
|
|
5575
|
+
this.buffer = [...this.savedDraft.buffer];
|
|
5576
|
+
this.row = this.savedDraft.row;
|
|
5577
|
+
this.col = this.savedDraft.col;
|
|
5578
|
+
this.savedDraft = null;
|
|
5579
|
+
this.attachments = this.savedAttachments ?? [];
|
|
5580
|
+
this.savedAttachments = null;
|
|
5581
|
+
} else {
|
|
5582
|
+
this.clearBuffer();
|
|
5583
|
+
}
|
|
6036
5584
|
}
|
|
6037
|
-
|
|
6038
|
-
|
|
6039
|
-
|
|
6040
|
-
|
|
6041
|
-
|
|
6042
|
-
|
|
6043
|
-
|
|
6044
|
-
|
|
6045
|
-
|
|
6046
|
-
|
|
6047
|
-
|
|
6048
|
-
|
|
6049
|
-
|
|
6050
|
-
|
|
6051
|
-
|
|
6052
|
-
|
|
6053
|
-
|
|
6054
|
-
|
|
6055
|
-
|
|
6056
|
-
|
|
6057
|
-
|
|
6058
|
-
|
|
6059
|
-
|
|
6060
|
-
}
|
|
6061
|
-
|
|
6062
|
-
|
|
6063
|
-
|
|
6064
|
-
|
|
6065
|
-
|
|
6066
|
-
|
|
6067
|
-
|
|
6068
|
-
|
|
6069
|
-
|
|
6070
|
-
|
|
6071
|
-
|
|
6072
|
-
|
|
6073
|
-
|
|
6074
|
-
|
|
6075
|
-
|
|
6076
|
-
|
|
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 };
|
|
5585
|
+
// Engage reverse-incremental search over prompt history. Uses the
|
|
5586
|
+
// current buffer text as the search query. With an empty buffer we
|
|
5587
|
+
// enter search mode in an "empty query, no match shown" state — the
|
|
5588
|
+
// banner indicator lights up, and as the user types we extend the
|
|
5589
|
+
// query and load top matches. We deliberately do NOT auto-load the
|
|
5590
|
+
// most recent entry on an empty ^R (that's a surprise — Up-arrow
|
|
5591
|
+
// already walks history if that's what they wanted). With a
|
|
5592
|
+
// non-empty query that has no history match, escalate straight to
|
|
5593
|
+
// scrollback search so the typed term searches session output.
|
|
5594
|
+
startHistorySearch() {
|
|
5595
|
+
const query = this.bufferText().toLowerCase();
|
|
5596
|
+
if (query.length === 0) {
|
|
5597
|
+
this.historySearch = {
|
|
5598
|
+
query: "",
|
|
5599
|
+
matchIndices: [],
|
|
5600
|
+
cursor: 0,
|
|
5601
|
+
savedDraft: {
|
|
5602
|
+
buffer: [...this.buffer],
|
|
5603
|
+
row: this.row,
|
|
5604
|
+
col: this.col
|
|
5605
|
+
}
|
|
5606
|
+
};
|
|
5607
|
+
return [];
|
|
5608
|
+
}
|
|
5609
|
+
const matchIndices = this.findHistoryMatches(query);
|
|
5610
|
+
if (matchIndices.length === 0) {
|
|
5611
|
+
return [{ type: "escalate-search", query }];
|
|
5612
|
+
}
|
|
5613
|
+
this.historySearch = {
|
|
5614
|
+
query,
|
|
5615
|
+
matchIndices,
|
|
5616
|
+
cursor: 0,
|
|
5617
|
+
savedDraft: {
|
|
5618
|
+
buffer: [...this.buffer],
|
|
5619
|
+
row: this.row,
|
|
5620
|
+
col: this.col
|
|
5621
|
+
}
|
|
5622
|
+
};
|
|
5623
|
+
this.loadEntry(this.history[matchIndices[0]] ?? "");
|
|
5624
|
+
return [];
|
|
6117
5625
|
}
|
|
6118
|
-
|
|
6119
|
-
|
|
6120
|
-
|
|
6121
|
-
|
|
6122
|
-
|
|
6123
|
-
|
|
6124
|
-
|
|
6125
|
-
|
|
6126
|
-
|
|
6127
|
-
|
|
6128
|
-
|
|
6129
|
-
|
|
6130
|
-
|
|
6131
|
-
|
|
6132
|
-
|
|
6133
|
-
|
|
6134
|
-
|
|
6135
|
-
|
|
6136
|
-
|
|
6137
|
-
|
|
6138
|
-
|
|
6139
|
-
|
|
6140
|
-
|
|
6141
|
-
|
|
6142
|
-
|
|
6143
|
-
|
|
6144
|
-
|
|
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;
|
|
5626
|
+
// ^R advance. At the oldest match with a non-empty query, falls
|
|
5627
|
+
// through to scrollback search (same escalate path as a never-
|
|
5628
|
+
// matched startHistorySearch). With an empty query at the oldest
|
|
5629
|
+
// match (i.e. the user walked all history with no filter), advance
|
|
5630
|
+
// is a no-op so the buffer stays on the oldest entry.
|
|
5631
|
+
advanceHistorySearch() {
|
|
5632
|
+
if (this.historySearch === null) {
|
|
5633
|
+
return [];
|
|
5634
|
+
}
|
|
5635
|
+
const search = this.historySearch;
|
|
5636
|
+
const atOldest = search.cursor >= search.matchIndices.length - 1;
|
|
5637
|
+
if (atOldest) {
|
|
5638
|
+
if (search.query.length === 0) {
|
|
5639
|
+
return [];
|
|
5640
|
+
}
|
|
5641
|
+
const query = search.query;
|
|
5642
|
+
const draft = search.savedDraft;
|
|
5643
|
+
this.historySearch = null;
|
|
5644
|
+
this.buffer = [...draft.buffer];
|
|
5645
|
+
this.row = draft.row;
|
|
5646
|
+
this.col = draft.col;
|
|
5647
|
+
return [{ type: "escalate-search", query }];
|
|
5648
|
+
}
|
|
5649
|
+
search.cursor += 1;
|
|
5650
|
+
const idx = search.matchIndices[search.cursor];
|
|
5651
|
+
this.loadEntry(this.history[idx] ?? "");
|
|
5652
|
+
return [];
|
|
6156
5653
|
}
|
|
6157
|
-
|
|
6158
|
-
|
|
6159
|
-
|
|
6160
|
-
|
|
6161
|
-
|
|
6162
|
-
|
|
6163
|
-
|
|
5654
|
+
// ^S retreat — walk toward newer matches. No-op at the newest match
|
|
5655
|
+
// (no wrap, mirroring ^R no-wrap at the oldest).
|
|
5656
|
+
retreatHistorySearch() {
|
|
5657
|
+
if (this.historySearch === null) {
|
|
5658
|
+
return;
|
|
5659
|
+
}
|
|
5660
|
+
if (this.historySearch.cursor === 0) {
|
|
5661
|
+
return;
|
|
5662
|
+
}
|
|
5663
|
+
this.historySearch.cursor -= 1;
|
|
5664
|
+
const idx = this.historySearch.matchIndices[this.historySearch.cursor];
|
|
5665
|
+
this.loadEntry(this.history[idx] ?? "");
|
|
6164
5666
|
}
|
|
6165
|
-
|
|
6166
|
-
|
|
6167
|
-
|
|
6168
|
-
|
|
6169
|
-
|
|
6170
|
-
|
|
6171
|
-
|
|
6172
|
-
|
|
6173
|
-
|
|
6174
|
-
|
|
6175
|
-
|
|
6176
|
-
|
|
6177
|
-
|
|
6178
|
-
|
|
6179
|
-
|
|
6180
|
-
|
|
6181
|
-
|
|
5667
|
+
// Backspace / typing within search mode mutates the query and
|
|
5668
|
+
// re-searches. When the new query is empty, restore the saved
|
|
5669
|
+
// draft buffer (typically empty) and stay in search mode — the
|
|
5670
|
+
// user can keep typing. When the new query has matches, load the
|
|
5671
|
+
// top one. When the new query has no matches, escalate to scrollback
|
|
5672
|
+
// search so the typed term applies there instead.
|
|
5673
|
+
mutateHistorySearchQuery(newQuery) {
|
|
5674
|
+
if (this.historySearch === null) {
|
|
5675
|
+
return [];
|
|
5676
|
+
}
|
|
5677
|
+
if (newQuery.length === 0) {
|
|
5678
|
+
this.historySearch.query = "";
|
|
5679
|
+
this.historySearch.matchIndices = [];
|
|
5680
|
+
this.historySearch.cursor = 0;
|
|
5681
|
+
const draft = this.historySearch.savedDraft;
|
|
5682
|
+
this.buffer = [...draft.buffer];
|
|
5683
|
+
this.row = draft.row;
|
|
5684
|
+
this.col = draft.col;
|
|
5685
|
+
return [];
|
|
5686
|
+
}
|
|
5687
|
+
const matchIndices = this.findHistoryMatches(newQuery);
|
|
5688
|
+
if (matchIndices.length === 0) {
|
|
5689
|
+
const draft = this.historySearch.savedDraft;
|
|
5690
|
+
this.historySearch = null;
|
|
5691
|
+
this.buffer = [...draft.buffer];
|
|
5692
|
+
this.row = draft.row;
|
|
5693
|
+
this.col = draft.col;
|
|
5694
|
+
return [{ type: "escalate-search", query: newQuery }];
|
|
5695
|
+
}
|
|
5696
|
+
this.historySearch.query = newQuery;
|
|
5697
|
+
this.historySearch.matchIndices = matchIndices;
|
|
5698
|
+
this.historySearch.cursor = 0;
|
|
5699
|
+
this.loadEntry(this.history[matchIndices[0]] ?? "");
|
|
5700
|
+
return [];
|
|
6182
5701
|
}
|
|
6183
|
-
|
|
6184
|
-
|
|
6185
|
-
|
|
6186
|
-
|
|
6187
|
-
|
|
6188
|
-
|
|
6189
|
-
|
|
6190
|
-
|
|
6191
|
-
|
|
6192
|
-
resolve6(value);
|
|
6193
|
-
};
|
|
6194
|
-
const onResize = () => {
|
|
6195
|
-
if (resolved) {
|
|
6196
|
-
return;
|
|
5702
|
+
findHistoryMatches(query) {
|
|
5703
|
+
const out = [];
|
|
5704
|
+
for (let i = this.history.length - 1; i >= 0; i--) {
|
|
5705
|
+
const entry = this.history[i] ?? "";
|
|
5706
|
+
if (query.length === 0 || entry.toLowerCase().includes(query)) {
|
|
5707
|
+
out.push(i);
|
|
5708
|
+
}
|
|
5709
|
+
}
|
|
5710
|
+
return out;
|
|
6197
5711
|
}
|
|
6198
|
-
|
|
6199
|
-
|
|
6200
|
-
|
|
6201
|
-
|
|
6202
|
-
|
|
6203
|
-
|
|
5712
|
+
cancelHistorySearch() {
|
|
5713
|
+
if (this.historySearch === null) {
|
|
5714
|
+
return;
|
|
5715
|
+
}
|
|
5716
|
+
const draft = this.historySearch.savedDraft;
|
|
5717
|
+
this.historySearch = null;
|
|
5718
|
+
this.buffer = [...draft.buffer];
|
|
5719
|
+
this.row = draft.row;
|
|
5720
|
+
this.col = draft.col;
|
|
6204
5721
|
}
|
|
6205
|
-
|
|
6206
|
-
|
|
6207
|
-
|
|
6208
|
-
|
|
5722
|
+
loadEntry(text) {
|
|
5723
|
+
this.buffer = text.split("\n");
|
|
5724
|
+
if (this.buffer.length === 0) {
|
|
5725
|
+
this.buffer = [""];
|
|
5726
|
+
}
|
|
5727
|
+
this.row = this.buffer.length - 1;
|
|
5728
|
+
this.col = (this.buffer[this.row] ?? "").length;
|
|
6209
5729
|
}
|
|
6210
|
-
|
|
6211
|
-
|
|
6212
|
-
|
|
5730
|
+
send() {
|
|
5731
|
+
const text = this.bufferText();
|
|
5732
|
+
if (this.queueIndex >= 0 && this.queueIndex < this.queue.length) {
|
|
5733
|
+
const index = this.queueIndex;
|
|
5734
|
+
const attachments2 = [...this.attachments];
|
|
5735
|
+
this.clearBuffer();
|
|
5736
|
+
if (text.trim().length === 0) {
|
|
5737
|
+
return [{ type: "queue-remove", index }];
|
|
5738
|
+
}
|
|
5739
|
+
return [{ type: "queue-edit", index, text, attachments: attachments2 }];
|
|
5740
|
+
}
|
|
5741
|
+
if (text.trim().length === 0 && this.attachments.length === 0) {
|
|
5742
|
+
return [];
|
|
5743
|
+
}
|
|
5744
|
+
const planMode = this.planMode;
|
|
5745
|
+
const attachments = [...this.attachments];
|
|
5746
|
+
this.clearBuffer();
|
|
5747
|
+
return [{ type: "send", text, planMode, attachments }];
|
|
5748
|
+
}
|
|
5749
|
+
// Shift+Enter: amend the in-flight turn. Editing a queued slot
|
|
5750
|
+
// delegates to the existing queue-edit / queue-remove path — Shift+Enter
|
|
5751
|
+
// there has no special meaning since the entry is already queued (not
|
|
5752
|
+
// running). With an empty draft and no attachments we emit nothing
|
|
5753
|
+
// (no-op). Otherwise emit an "amend" effect; the app decides whether
|
|
5754
|
+
// to route through amend_prompt or fall through to a regular send.
|
|
5755
|
+
amend() {
|
|
5756
|
+
const text = this.bufferText();
|
|
5757
|
+
if (this.queueIndex >= 0 && this.queueIndex < this.queue.length) {
|
|
5758
|
+
const index = this.queueIndex;
|
|
5759
|
+
const attachments2 = [...this.attachments];
|
|
5760
|
+
this.clearBuffer();
|
|
5761
|
+
if (text.trim().length === 0) {
|
|
5762
|
+
return [{ type: "queue-remove", index }];
|
|
5763
|
+
}
|
|
5764
|
+
return [{ type: "queue-edit", index, text, attachments: attachments2 }];
|
|
5765
|
+
}
|
|
5766
|
+
if (text.trim().length === 0 && this.attachments.length === 0) {
|
|
5767
|
+
return [];
|
|
5768
|
+
}
|
|
5769
|
+
const planMode = this.planMode;
|
|
5770
|
+
const attachments = [...this.attachments];
|
|
5771
|
+
this.clearBuffer();
|
|
5772
|
+
return [{ type: "amend", text, planMode, attachments }];
|
|
5773
|
+
}
|
|
5774
|
+
// Home: jump to the very start of the prompt buffer. If we're already
|
|
5775
|
+
// there, fall through to scrolling the scrollback to its top.
|
|
5776
|
+
handleHome() {
|
|
5777
|
+
if (this.row !== 0 || this.col !== 0) {
|
|
5778
|
+
this.row = 0;
|
|
5779
|
+
this.col = 0;
|
|
5780
|
+
return [];
|
|
5781
|
+
}
|
|
5782
|
+
return [{ type: "scroll-to-top" }];
|
|
6213
5783
|
}
|
|
6214
|
-
|
|
6215
|
-
|
|
6216
|
-
|
|
5784
|
+
// End: jump to the end of the last line of the prompt buffer. Already
|
|
5785
|
+
// there → scroll the scrollback to the bottom (newest).
|
|
5786
|
+
handleEnd() {
|
|
5787
|
+
const lastRow = this.buffer.length - 1;
|
|
5788
|
+
const lastCol = (this.buffer[lastRow] ?? "").length;
|
|
5789
|
+
if (this.row !== lastRow || this.col !== lastCol) {
|
|
5790
|
+
this.row = lastRow;
|
|
5791
|
+
this.col = lastCol;
|
|
5792
|
+
return [];
|
|
5793
|
+
}
|
|
5794
|
+
return [{ type: "scroll-to-bottom" }];
|
|
6217
5795
|
}
|
|
6218
|
-
|
|
6219
|
-
|
|
6220
|
-
|
|
5796
|
+
handleCtrlC() {
|
|
5797
|
+
if (this.queueIndex >= 0) {
|
|
5798
|
+
const index = this.queueIndex;
|
|
5799
|
+
this.queueIndex = -1;
|
|
5800
|
+
this.restoreDraft();
|
|
5801
|
+
return [{ type: "queue-remove", index }];
|
|
5802
|
+
}
|
|
5803
|
+
if (!this.bufferIsEmpty() || this.attachments.length > 0) {
|
|
5804
|
+
this.buffer = [""];
|
|
5805
|
+
this.row = 0;
|
|
5806
|
+
this.col = 0;
|
|
5807
|
+
this.attachments = [];
|
|
5808
|
+
this.historyIndex = -1;
|
|
5809
|
+
this.savedDraft = null;
|
|
5810
|
+
this.savedAttachments = null;
|
|
5811
|
+
return [];
|
|
5812
|
+
}
|
|
5813
|
+
if (this.turnRunning) {
|
|
5814
|
+
return [{ type: "cancel" }];
|
|
5815
|
+
}
|
|
5816
|
+
return [{ type: "exit" }];
|
|
6221
5817
|
}
|
|
6222
5818
|
};
|
|
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
|
-
];
|
|
6285
5819
|
}
|
|
6286
5820
|
});
|
|
6287
5821
|
|
|
6288
5822
|
// src/tui/attachments.ts
|
|
6289
|
-
import
|
|
5823
|
+
import path13 from "path";
|
|
6290
5824
|
function mimeFromExtension(p) {
|
|
6291
|
-
return EXTENSION_TO_MIME[
|
|
5825
|
+
return EXTENSION_TO_MIME[path13.extname(p).toLowerCase()] ?? null;
|
|
6292
5826
|
}
|
|
6293
5827
|
function isSupportedImagePath(p) {
|
|
6294
5828
|
return mimeFromExtension(p) !== null;
|
|
@@ -6748,7 +6282,7 @@ function graphemeSegments(text) {
|
|
|
6748
6282
|
}
|
|
6749
6283
|
return out;
|
|
6750
6284
|
}
|
|
6751
|
-
function
|
|
6285
|
+
function truncate(text, max, opts = {}) {
|
|
6752
6286
|
if (max <= 0) {
|
|
6753
6287
|
return "";
|
|
6754
6288
|
}
|
|
@@ -8398,9 +7932,9 @@ uncaught: ${err.stack ?? err.message}
|
|
|
8398
7932
|
titleRoom = 0;
|
|
8399
7933
|
cwdRoom = variableRoom;
|
|
8400
7934
|
}
|
|
8401
|
-
this.term.yellow(sid)(" \xB7 ").cyan.noFormat(agentCell)(" \xB7 ").dim.noFormat(
|
|
7935
|
+
this.term.yellow(sid)(" \xB7 ").cyan.noFormat(agentCell)(" \xB7 ").dim.noFormat(truncate(cwdDisplay, cwdRoom));
|
|
8402
7936
|
if (title) {
|
|
8403
|
-
this.term(" \xB7 ").bold.noFormat(
|
|
7937
|
+
this.term(" \xB7 ").bold.noFormat(truncate(title, titleRoom));
|
|
8404
7938
|
}
|
|
8405
7939
|
if (usage) {
|
|
8406
7940
|
const col = Math.max(1, w - usage.length + 1);
|
|
@@ -8511,7 +8045,7 @@ uncaught: ${err.stack ?? err.message}
|
|
|
8511
8045
|
const namePadded = item.name.padEnd(nameWidth);
|
|
8512
8046
|
const desc = item.description ?? "";
|
|
8513
8047
|
const remaining = w - namePadded.length - 4;
|
|
8514
|
-
const truncated = remaining > 0 ?
|
|
8048
|
+
const truncated = remaining > 0 ? truncate(desc, remaining) : "";
|
|
8515
8049
|
this.term(" ").brightCyan(namePadded);
|
|
8516
8050
|
if (truncated.length > 0) {
|
|
8517
8051
|
this.term(" ").dim(truncated);
|
|
@@ -8588,7 +8122,7 @@ uncaught: ${err.stack ?? err.message}
|
|
|
8588
8122
|
const text = this.queuedTexts[i];
|
|
8589
8123
|
const isLast = i === rows - 1 && this.queuedTexts.length > MAX_QUEUED_ROWS;
|
|
8590
8124
|
const overflow = this.queuedTexts.length - MAX_QUEUED_ROWS;
|
|
8591
|
-
const summary = text === void 0 ? "" : isLast ? `+ ${overflow + 1} more queued` :
|
|
8125
|
+
const summary = text === void 0 ? "" : isLast ? `+ ${overflow + 1} more queued` : truncate(firstLine2(text), w - 4);
|
|
8592
8126
|
const editing = !isLast && i === editingIndex;
|
|
8593
8127
|
const sig = text === void 0 ? `queued|${w}|empty` : `queued|${w}|${editing ? "edit" : isLast ? "ovf" : "row"}|${summary}`;
|
|
8594
8128
|
this.paintRow(row, sig, () => {
|
|
@@ -8665,10 +8199,10 @@ uncaught: ${err.stack ?? err.message}
|
|
|
8665
8199
|
const w = this.term.width;
|
|
8666
8200
|
const top = this.term.height - CONFIRM_PROMPT_ROWS - BANNER_ROWS - SEPARATOR_ROWS - SESSIONBAR_ROWS + 1;
|
|
8667
8201
|
this.paintRow(top, `confirm|q|${w}|${spec.question}`, () => {
|
|
8668
|
-
this.term.brightYellow(` ? ${
|
|
8202
|
+
this.term.brightYellow(` ? ${truncate(spec.question, w - 4)}`);
|
|
8669
8203
|
});
|
|
8670
8204
|
this.paintRow(top + 1, `confirm|h|${w}|${spec.hint}`, () => {
|
|
8671
|
-
this.term.dim(` ${
|
|
8205
|
+
this.term.dim(` ${truncate(spec.hint, w - 2)}`);
|
|
8672
8206
|
});
|
|
8673
8207
|
}
|
|
8674
8208
|
drawHelpPrompt() {
|
|
@@ -8688,7 +8222,7 @@ uncaught: ${err.stack ?? err.message}
|
|
|
8688
8222
|
row += 1;
|
|
8689
8223
|
};
|
|
8690
8224
|
writeRow(`help|t|${w}|${spec.title}`, () => {
|
|
8691
|
-
this.term.brightYellow(` \u2753 ${
|
|
8225
|
+
this.term.brightYellow(` \u2753 ${truncate(spec.title, w - 5)}`);
|
|
8692
8226
|
});
|
|
8693
8227
|
const keysWidth = Math.min(
|
|
8694
8228
|
24,
|
|
@@ -8710,11 +8244,11 @@ uncaught: ${err.stack ?? err.message}
|
|
|
8710
8244
|
writeRow(`help|e|${w}|${keys}|${desc}`, () => {
|
|
8711
8245
|
this.term(" ");
|
|
8712
8246
|
this.term.brightCyan.noFormat(paddedKeys);
|
|
8713
|
-
this.term.noFormat(` ${
|
|
8247
|
+
this.term.noFormat(` ${truncate(desc, w - 2 - keysWidth - 1)}`);
|
|
8714
8248
|
});
|
|
8715
8249
|
}
|
|
8716
8250
|
writeRow(`help|hint|${w}|${spec.hint}`, () => {
|
|
8717
|
-
this.term.dim(` ${
|
|
8251
|
+
this.term.dim(` ${truncate(spec.hint, w - 2)}`);
|
|
8718
8252
|
});
|
|
8719
8253
|
}
|
|
8720
8254
|
helpRows() {
|
|
@@ -8740,7 +8274,7 @@ uncaught: ${err.stack ?? err.message}
|
|
|
8740
8274
|
row += 1;
|
|
8741
8275
|
};
|
|
8742
8276
|
writeRow(`perm|t|${w}|${spec.title}`, () => {
|
|
8743
|
-
this.term.brightYellow(` \u{1F512} ${
|
|
8277
|
+
this.term.brightYellow(` \u{1F512} ${truncate(spec.title, w - 5)}`);
|
|
8744
8278
|
});
|
|
8745
8279
|
writeRow(`perm|sub|${w}`, () => {
|
|
8746
8280
|
this.term.dim(" This action requires approval");
|
|
@@ -8758,7 +8292,7 @@ uncaught: ${err.stack ?? err.message}
|
|
|
8758
8292
|
}
|
|
8759
8293
|
const isSel = i === spec.selectedIndex;
|
|
8760
8294
|
const marker = isSel ? "\u276F" : " ";
|
|
8761
|
-
const body = ` ${marker} ${i + 1}. ${
|
|
8295
|
+
const body = ` ${marker} ${i + 1}. ${truncate(opt.label, w - 8)}`;
|
|
8762
8296
|
writeRow(`perm|o|${w}|${i}|${isSel ? "1" : "0"}|${opt.label}`, () => {
|
|
8763
8297
|
if (isSel) {
|
|
8764
8298
|
this.term.brightCyan(body);
|
|
@@ -8797,1075 +8331,1726 @@ uncaught: ${err.stack ?? err.message}
|
|
|
8797
8331
|
} else {
|
|
8798
8332
|
this.term.brightGreen(`${dot} ${this.banner.status}`);
|
|
8799
8333
|
}
|
|
8800
|
-
if (this.banner.queued > 0) {
|
|
8801
|
-
this.term(" \xB7 ").brightYellow(`${this.banner.queued} queued`);
|
|
8334
|
+
if (this.banner.queued > 0) {
|
|
8335
|
+
this.term(" \xB7 ").brightYellow(`${this.banner.queued} queued`);
|
|
8336
|
+
}
|
|
8337
|
+
if (this.scrollOffset > 0) {
|
|
8338
|
+
this.term(" \xB7 ").brightCyan(`\u2191 ${this.scrollOffset}`);
|
|
8339
|
+
}
|
|
8340
|
+
const hint = this.banner.currentMode ? this.banner.hint.replace(
|
|
8341
|
+
"\u21E7\u21E5 mode",
|
|
8342
|
+
`\u21E7\u21E5 mode: ${this.banner.currentMode}`
|
|
8343
|
+
) : this.banner.hint;
|
|
8344
|
+
this.term(" \xB7 ").dim(hint);
|
|
8345
|
+
if (right) {
|
|
8346
|
+
const visibleWidth = stringWidth(right.text);
|
|
8347
|
+
const col = Math.max(1, w - visibleWidth + 1);
|
|
8348
|
+
this.term.moveTo(col, row).eraseLineAfter();
|
|
8349
|
+
if (right.kind === "search") {
|
|
8350
|
+
this.term.brightCyan.noFormat(right.text);
|
|
8351
|
+
} else {
|
|
8352
|
+
this.term.brightYellow.noFormat(right.text);
|
|
8353
|
+
}
|
|
8354
|
+
}
|
|
8355
|
+
});
|
|
8356
|
+
}
|
|
8357
|
+
placeCursor() {
|
|
8358
|
+
if (!this.started) {
|
|
8359
|
+
return;
|
|
8360
|
+
}
|
|
8361
|
+
if (this.permissionPrompt) {
|
|
8362
|
+
const rows = this.permissionRows();
|
|
8363
|
+
const top2 = this.term.height - rows - BANNER_ROWS - SEPARATOR_ROWS - SESSIONBAR_ROWS + 1;
|
|
8364
|
+
const optionRow = top2 + 3 + this.permissionPrompt.selectedIndex;
|
|
8365
|
+
const lastUsableRow = this.term.height - BANNER_ROWS - SEPARATOR_ROWS - SESSIONBAR_ROWS;
|
|
8366
|
+
this.term.moveTo(2, Math.min(optionRow, lastUsableRow));
|
|
8367
|
+
return;
|
|
8368
|
+
}
|
|
8369
|
+
if (this.confirmPrompt) {
|
|
8370
|
+
const top2 = this.term.height - CONFIRM_PROMPT_ROWS - BANNER_ROWS - SEPARATOR_ROWS - SESSIONBAR_ROWS + 1;
|
|
8371
|
+
this.term.moveTo(2, top2);
|
|
8372
|
+
return;
|
|
8373
|
+
}
|
|
8374
|
+
if (this.helpPrompt) {
|
|
8375
|
+
const rows = this.helpRows();
|
|
8376
|
+
const top2 = this.term.height - rows - BANNER_ROWS - SEPARATOR_ROWS - SESSIONBAR_ROWS + 1;
|
|
8377
|
+
this.term.moveTo(2, top2);
|
|
8378
|
+
return;
|
|
8379
|
+
}
|
|
8380
|
+
if (this.scrollbackSearch) {
|
|
8381
|
+
this.term.hideCursor(true);
|
|
8382
|
+
return;
|
|
8383
|
+
}
|
|
8384
|
+
if (this.readonly) {
|
|
8385
|
+
this.term.hideCursor(true);
|
|
8386
|
+
return;
|
|
8387
|
+
}
|
|
8388
|
+
this.term.hideCursor(false);
|
|
8389
|
+
const w = this.term.width;
|
|
8390
|
+
const room = Math.max(1, w - 2);
|
|
8391
|
+
const state = this.dispatcher.state();
|
|
8392
|
+
const visualRows = computePromptVisualRows(state.buffer, room);
|
|
8393
|
+
const layout = computePromptLayout(visualRows, state, MAX_PROMPT_ROWS);
|
|
8394
|
+
const top = this.term.height - layout.rendered - BANNER_ROWS - SEPARATOR_ROWS - SESSIONBAR_ROWS + 1;
|
|
8395
|
+
const row = top + Math.max(0, layout.cursorVisualRow - layout.windowStart);
|
|
8396
|
+
const col = layout.cursorVisualCol + 3;
|
|
8397
|
+
const lastPromptRow = this.term.height - BANNER_ROWS - SEPARATOR_ROWS - SESSIONBAR_ROWS;
|
|
8398
|
+
this.term.moveTo(
|
|
8399
|
+
Math.min(col, this.term.width),
|
|
8400
|
+
Math.min(row, lastPromptRow)
|
|
8401
|
+
);
|
|
8402
|
+
}
|
|
8403
|
+
promptRows() {
|
|
8404
|
+
if (this.permissionPrompt) {
|
|
8405
|
+
return this.permissionRows();
|
|
8406
|
+
}
|
|
8407
|
+
if (this.confirmPrompt) {
|
|
8408
|
+
return CONFIRM_PROMPT_ROWS;
|
|
8409
|
+
}
|
|
8410
|
+
if (this.helpPrompt) {
|
|
8411
|
+
return this.helpRows();
|
|
8412
|
+
}
|
|
8413
|
+
if (this.readonly) {
|
|
8414
|
+
return 0;
|
|
8415
|
+
}
|
|
8416
|
+
const w = this.term.width;
|
|
8417
|
+
const room = Math.max(1, w - 2);
|
|
8418
|
+
const state = this.dispatcher.state();
|
|
8419
|
+
const visualRows = computePromptVisualRows(state.buffer, room);
|
|
8420
|
+
return Math.min(MAX_PROMPT_ROWS, Math.max(1, visualRows.length));
|
|
8421
|
+
}
|
|
8422
|
+
permissionRows() {
|
|
8423
|
+
if (!this.permissionPrompt) {
|
|
8424
|
+
return 0;
|
|
8425
|
+
}
|
|
8426
|
+
return Math.min(
|
|
8427
|
+
MAX_PERMISSION_ROWS,
|
|
8428
|
+
4 + this.permissionPrompt.options.length
|
|
8429
|
+
);
|
|
8430
|
+
}
|
|
8431
|
+
// Walk this.lines from the tail, accumulating wrapped rows via the
|
|
8432
|
+
// wrap cache, until we have at least `needed` rows or run out. Returns
|
|
8433
|
+
// the collected rows in original (top-down) order plus an `exhausted`
|
|
8434
|
+
// flag that's true iff we reached the head of this.lines. The hot path
|
|
8435
|
+
// (drawScrollback) only ever asks for `visibleRows + scrollOffset`
|
|
8436
|
+
// rows, so a 10k-line scrollback costs ~50 cache hits per repaint
|
|
8437
|
+
// instead of 10k. With `needed = Infinity` this walks everything and
|
|
8438
|
+
// doubles as a total-row counter for maxScrollOffset.
|
|
8439
|
+
wrapTail(width, needed) {
|
|
8440
|
+
if (width <= 4) {
|
|
8441
|
+
const take = Math.min(needed, this.lines.length);
|
|
8442
|
+
return {
|
|
8443
|
+
rows: this.lines.slice(this.lines.length - take),
|
|
8444
|
+
exhausted: needed >= this.lines.length
|
|
8445
|
+
};
|
|
8446
|
+
}
|
|
8447
|
+
if (this.wrapCacheWidth !== width) {
|
|
8448
|
+
this.wrapCache.clear();
|
|
8449
|
+
this.wrapCacheWidth = width;
|
|
8450
|
+
}
|
|
8451
|
+
if (needed <= 0 || this.lines.length === 0) {
|
|
8452
|
+
return { rows: [], exhausted: true };
|
|
8453
|
+
}
|
|
8454
|
+
const batches = [];
|
|
8455
|
+
let total = 0;
|
|
8456
|
+
let stoppedAt = 0;
|
|
8457
|
+
for (let i = this.lines.length - 1; i >= 0; i--) {
|
|
8458
|
+
const wrapped = this.wrapOne(this.lines[i], width);
|
|
8459
|
+
batches.push(wrapped);
|
|
8460
|
+
total += wrapped.length;
|
|
8461
|
+
stoppedAt = i;
|
|
8462
|
+
if (total >= needed) {
|
|
8463
|
+
break;
|
|
8464
|
+
}
|
|
8465
|
+
}
|
|
8466
|
+
const rows = [];
|
|
8467
|
+
for (let i = batches.length - 1; i >= 0; i--) {
|
|
8468
|
+
rows.push(...batches[i]);
|
|
8469
|
+
}
|
|
8470
|
+
return { rows, exhausted: stoppedAt === 0 };
|
|
8471
|
+
}
|
|
8472
|
+
wrapOne(line, width) {
|
|
8473
|
+
const id = this.lineIds.get(line);
|
|
8474
|
+
if (id !== void 0) {
|
|
8475
|
+
const cached2 = this.wrapCache.get(id);
|
|
8476
|
+
if (cached2) {
|
|
8477
|
+
return cached2;
|
|
8478
|
+
}
|
|
8479
|
+
}
|
|
8480
|
+
const prefix = line.prefix ?? "";
|
|
8481
|
+
const room = Math.max(1, width - prefix.length);
|
|
8482
|
+
const stripMarkup = line.bodyStyle === "agent";
|
|
8483
|
+
const chunks = line.ansi ? wrapAnsiBody(line.body, room) : wrap(line.body, room, { stripMarkup });
|
|
8484
|
+
const wrapped = [];
|
|
8485
|
+
let scanPos = 0;
|
|
8486
|
+
for (let i = 0; i < chunks.length; i++) {
|
|
8487
|
+
const chunk = chunks[i] ?? "";
|
|
8488
|
+
const wrappedLine = {
|
|
8489
|
+
prefix: i === 0 ? line.prefix : " ".repeat(prefix.length),
|
|
8490
|
+
body: chunk
|
|
8491
|
+
};
|
|
8492
|
+
if (line.prefixStyle !== void 0) {
|
|
8493
|
+
wrappedLine.prefixStyle = line.prefixStyle;
|
|
8494
|
+
}
|
|
8495
|
+
if (line.bodyStyle !== void 0) {
|
|
8496
|
+
wrappedLine.bodyStyle = line.bodyStyle;
|
|
8802
8497
|
}
|
|
8803
|
-
if (
|
|
8804
|
-
|
|
8498
|
+
if (line.fillRow) {
|
|
8499
|
+
wrappedLine.fillRow = true;
|
|
8805
8500
|
}
|
|
8806
|
-
|
|
8807
|
-
|
|
8808
|
-
`\u21E7\u21E5 mode(${this.banner.currentMode})`
|
|
8809
|
-
) : this.banner.hint;
|
|
8810
|
-
this.term(" \xB7 ").dim(hint);
|
|
8811
|
-
if (right) {
|
|
8812
|
-
const visibleWidth = stringWidth(right.text);
|
|
8813
|
-
const col = Math.max(1, w - visibleWidth + 1);
|
|
8814
|
-
this.term.moveTo(col, row).eraseLineAfter();
|
|
8815
|
-
if (right.kind === "search") {
|
|
8816
|
-
this.term.brightCyan.noFormat(right.text);
|
|
8817
|
-
} else {
|
|
8818
|
-
this.term.brightYellow.noFormat(right.text);
|
|
8819
|
-
}
|
|
8501
|
+
if (line.ansi) {
|
|
8502
|
+
wrappedLine.ansi = true;
|
|
8820
8503
|
}
|
|
8821
|
-
|
|
8822
|
-
|
|
8823
|
-
|
|
8824
|
-
|
|
8825
|
-
|
|
8504
|
+
if (i === 0 && line.iterm2Image) {
|
|
8505
|
+
wrappedLine.iterm2Image = line.iterm2Image;
|
|
8506
|
+
}
|
|
8507
|
+
if (id !== void 0 && chunk.length > 0) {
|
|
8508
|
+
const found = line.body.indexOf(chunk, scanPos);
|
|
8509
|
+
const colOffset = found === -1 ? scanPos : found;
|
|
8510
|
+
this.wrapOrigin.set(wrappedLine, {
|
|
8511
|
+
sourceLineId: id,
|
|
8512
|
+
sourceColOffset: colOffset
|
|
8513
|
+
});
|
|
8514
|
+
scanPos = colOffset + chunk.length;
|
|
8515
|
+
}
|
|
8516
|
+
wrapped.push(wrappedLine);
|
|
8826
8517
|
}
|
|
8827
|
-
if (
|
|
8828
|
-
|
|
8829
|
-
const top2 = this.term.height - rows - BANNER_ROWS - SEPARATOR_ROWS - SESSIONBAR_ROWS + 1;
|
|
8830
|
-
const optionRow = top2 + 3 + this.permissionPrompt.selectedIndex;
|
|
8831
|
-
const lastUsableRow = this.term.height - BANNER_ROWS - SEPARATOR_ROWS - SESSIONBAR_ROWS;
|
|
8832
|
-
this.term.moveTo(2, Math.min(optionRow, lastUsableRow));
|
|
8833
|
-
return;
|
|
8518
|
+
if (id !== void 0) {
|
|
8519
|
+
this.wrapCache.set(id, wrapped);
|
|
8834
8520
|
}
|
|
8835
|
-
|
|
8836
|
-
|
|
8837
|
-
|
|
8838
|
-
|
|
8521
|
+
return wrapped;
|
|
8522
|
+
}
|
|
8523
|
+
writeFormattedLine(line, width, activeMatchCol = null, activeMatchLength = 0) {
|
|
8524
|
+
if (line.prefix) {
|
|
8525
|
+
writeStyled(this.term, line.prefix, line.prefixStyle ?? line.bodyStyle);
|
|
8839
8526
|
}
|
|
8840
|
-
|
|
8841
|
-
|
|
8842
|
-
|
|
8843
|
-
|
|
8844
|
-
|
|
8527
|
+
const remaining = Math.max(0, width - (line.prefix?.length ?? 0));
|
|
8528
|
+
const stripMarkup = line.bodyStyle === "agent";
|
|
8529
|
+
const bodyText = line.ansi ? line.body : truncate(line.body, remaining, { stripMarkup });
|
|
8530
|
+
if (this.scrollbackHighlight !== null && !line.ansi) {
|
|
8531
|
+
writeBodyWithHighlight(
|
|
8532
|
+
this.term,
|
|
8533
|
+
bodyText,
|
|
8534
|
+
line.bodyStyle,
|
|
8535
|
+
this.scrollbackHighlight,
|
|
8536
|
+
activeMatchCol,
|
|
8537
|
+
activeMatchLength
|
|
8538
|
+
);
|
|
8539
|
+
} else {
|
|
8540
|
+
writeStyled(this.term, bodyText, line.bodyStyle);
|
|
8845
8541
|
}
|
|
8846
|
-
if (
|
|
8847
|
-
|
|
8848
|
-
|
|
8542
|
+
if (line.fillRow) {
|
|
8543
|
+
const visible = line.ansi ? stringWidth(bodyText) : bodyText.length;
|
|
8544
|
+
const pad = remaining - visible;
|
|
8545
|
+
if (pad > 0) {
|
|
8546
|
+
writeStyled(this.term, " ".repeat(pad), line.bodyStyle);
|
|
8547
|
+
}
|
|
8849
8548
|
}
|
|
8850
|
-
if (
|
|
8851
|
-
this.term.
|
|
8852
|
-
return;
|
|
8549
|
+
if (line.ansi || line.body.includes("^")) {
|
|
8550
|
+
this.term.styleReset();
|
|
8853
8551
|
}
|
|
8854
|
-
this.
|
|
8855
|
-
|
|
8856
|
-
|
|
8857
|
-
|
|
8858
|
-
|
|
8859
|
-
|
|
8860
|
-
|
|
8861
|
-
|
|
8862
|
-
|
|
8863
|
-
|
|
8864
|
-
|
|
8865
|
-
|
|
8866
|
-
|
|
8867
|
-
|
|
8552
|
+
if (line.iterm2Image && this.isIterm2()) {
|
|
8553
|
+
this.writeIterm2Image(
|
|
8554
|
+
line.iterm2Image.data,
|
|
8555
|
+
line.iterm2Image.heightCells
|
|
8556
|
+
);
|
|
8557
|
+
}
|
|
8558
|
+
}
|
|
8559
|
+
};
|
|
8560
|
+
NON_ASCII = /[^\x20-\x7e]/;
|
|
8561
|
+
SEGMENTER = new Intl.Segmenter(void 0, { granularity: "grapheme" });
|
|
8562
|
+
TK_MARKUP_STYLE_CHAR = /[a-zA-Z+\-:_!#/]/;
|
|
8563
|
+
shortId = stripHydraSessionPrefix;
|
|
8564
|
+
}
|
|
8565
|
+
});
|
|
8566
|
+
|
|
8567
|
+
// src/tui/picker.ts
|
|
8568
|
+
async function pickSession(term, opts) {
|
|
8569
|
+
process.stdout.write("\x1B[<u");
|
|
8570
|
+
process.stdout.write("\x1B[?2004l");
|
|
8571
|
+
process.stdout.write("\x1B[>4;0m");
|
|
8572
|
+
process.stdout.write("\x1B[>5;0m");
|
|
8573
|
+
process.stdout.write("\x1B[?1000l");
|
|
8574
|
+
process.stdout.write("\x1B[?1002l");
|
|
8575
|
+
process.stdout.write("\x1B[?1006l");
|
|
8576
|
+
process.stdout.write("\x1B[?1l");
|
|
8577
|
+
process.stdout.write("\x1B>");
|
|
8578
|
+
const sortSessions = (sessions) => {
|
|
8579
|
+
const score = (s) => {
|
|
8580
|
+
if (s.status !== "live") {
|
|
8581
|
+
return 0;
|
|
8582
|
+
}
|
|
8583
|
+
return s.cwd === opts.cwd ? 2 : 1;
|
|
8584
|
+
};
|
|
8585
|
+
return [...sessions].sort((a, b) => {
|
|
8586
|
+
const tier = score(b) - score(a);
|
|
8587
|
+
if (tier !== 0) {
|
|
8588
|
+
return tier;
|
|
8589
|
+
}
|
|
8590
|
+
return b.updatedAt.localeCompare(a.updatedAt);
|
|
8591
|
+
});
|
|
8592
|
+
};
|
|
8593
|
+
let cwdOnly = false;
|
|
8594
|
+
let hostFilter = "__local";
|
|
8595
|
+
if (opts.currentSessionId !== void 0) {
|
|
8596
|
+
const current = opts.sessions.find(
|
|
8597
|
+
(s) => s.sessionId === opts.currentSessionId
|
|
8598
|
+
);
|
|
8599
|
+
if (current?.importedFromMachine) {
|
|
8600
|
+
hostFilter = "__all";
|
|
8601
|
+
}
|
|
8602
|
+
}
|
|
8603
|
+
let allSessions = sortSessions(opts.sessions);
|
|
8604
|
+
let visible = filterByHost(allSessions, hostFilter);
|
|
8605
|
+
let rows = visible.map((s) => toRow(s, Date.now()));
|
|
8606
|
+
let widths = computeWidths(rows);
|
|
8607
|
+
let total = 1 + visible.length;
|
|
8608
|
+
let selectedIdx = 0;
|
|
8609
|
+
let scrollOffset = 0;
|
|
8610
|
+
if (opts.currentSessionId !== void 0) {
|
|
8611
|
+
const idx = visible.findIndex((s) => s.sessionId === opts.currentSessionId);
|
|
8612
|
+
if (idx >= 0) {
|
|
8613
|
+
selectedIdx = idx + 1;
|
|
8614
|
+
}
|
|
8615
|
+
}
|
|
8616
|
+
let searchActive = false;
|
|
8617
|
+
let searchTerm = "";
|
|
8618
|
+
let mode = "normal";
|
|
8619
|
+
let pendingAction = null;
|
|
8620
|
+
let renameBuffer = "";
|
|
8621
|
+
let transientStatus = null;
|
|
8622
|
+
const composer = new InputDispatcher({ history: [] });
|
|
8623
|
+
let termHeight = readTermHeight(term);
|
|
8624
|
+
let termWidth = readTermWidth(term);
|
|
8625
|
+
let viewportSize = 0;
|
|
8626
|
+
let composerTitle = "";
|
|
8627
|
+
let composerRoom = 0;
|
|
8628
|
+
let composerVisualRows = [];
|
|
8629
|
+
let composerRows = 1;
|
|
8630
|
+
let composerWindowStart = 0;
|
|
8631
|
+
let composerCursorRow = 0;
|
|
8632
|
+
let composerCursorCol = 0;
|
|
8633
|
+
let headerLine = "";
|
|
8634
|
+
let sessionLines = [];
|
|
8635
|
+
let startRow = 1;
|
|
8636
|
+
const cwdMaxWidth = opts.config.tui.cwdColumnMaxWidth;
|
|
8637
|
+
const computeLayout = () => {
|
|
8638
|
+
termHeight = readTermHeight(term);
|
|
8639
|
+
termWidth = readTermWidth(term);
|
|
8640
|
+
const rowMaxWidth = Math.max(10, termWidth - ROW_PREFIX_WIDTH);
|
|
8641
|
+
composerRoom = Math.max(10, termWidth - BOX_HORIZONTAL_PAD);
|
|
8642
|
+
const titleBudget = Math.max(10, termWidth - 8);
|
|
8643
|
+
composerTitle = formatComposerTitle(opts.cwd, titleBudget);
|
|
8644
|
+
const state = composer.state();
|
|
8645
|
+
composerVisualRows = computePromptVisualRows(state.buffer, composerRoom);
|
|
8646
|
+
const layout = computePromptLayout(
|
|
8647
|
+
composerVisualRows,
|
|
8648
|
+
state,
|
|
8649
|
+
PICKER_COMPOSER_MAX_ROWS
|
|
8650
|
+
);
|
|
8651
|
+
composerRows = layout.rendered;
|
|
8652
|
+
composerWindowStart = layout.windowStart;
|
|
8653
|
+
composerCursorRow = layout.cursorVisualRow;
|
|
8654
|
+
composerCursorCol = layout.cursorVisualCol;
|
|
8655
|
+
const reserved = 6 + composerRows;
|
|
8656
|
+
const maxViewportRows = Math.max(3, termHeight - reserved);
|
|
8657
|
+
viewportSize = Math.min(visible.length, maxViewportRows);
|
|
8658
|
+
headerLine = formatRow(HEADER, widths, rowMaxWidth, cwdMaxWidth);
|
|
8659
|
+
sessionLines = rows.map((r) => formatRow(r, widths, rowMaxWidth, cwdMaxWidth));
|
|
8660
|
+
};
|
|
8661
|
+
const rebuildRows = () => {
|
|
8662
|
+
rows = visible.map((s) => toRow(s, Date.now()));
|
|
8663
|
+
widths = computeWidths(rows);
|
|
8664
|
+
total = 1 + visible.length;
|
|
8665
|
+
computeLayout();
|
|
8666
|
+
};
|
|
8667
|
+
const applyFilter = () => {
|
|
8668
|
+
let base = allSessions;
|
|
8669
|
+
if (cwdOnly) {
|
|
8670
|
+
base = base.filter((s) => s.cwd === opts.cwd);
|
|
8671
|
+
}
|
|
8672
|
+
base = filterByHost(base, hostFilter);
|
|
8673
|
+
if (searchActive && searchTerm.length > 0) {
|
|
8674
|
+
visible = base.filter((s) => matchesSearch(s, searchTerm));
|
|
8675
|
+
} else {
|
|
8676
|
+
visible = base;
|
|
8677
|
+
}
|
|
8678
|
+
rebuildRows();
|
|
8679
|
+
if (searchActive) {
|
|
8680
|
+
scrollOffset = 0;
|
|
8681
|
+
selectedIdx = visible.length > 0 ? 1 : 0;
|
|
8682
|
+
} else if (selectedIdx > total - 1) {
|
|
8683
|
+
selectedIdx = Math.max(0, total - 1);
|
|
8684
|
+
}
|
|
8685
|
+
if (scrollOffset + viewportSize > visible.length) {
|
|
8686
|
+
scrollOffset = Math.max(0, visible.length - viewportSize);
|
|
8687
|
+
}
|
|
8688
|
+
adjustScroll();
|
|
8689
|
+
};
|
|
8690
|
+
const adjustScroll = () => {
|
|
8691
|
+
if (selectedIdx === 0) {
|
|
8692
|
+
return;
|
|
8693
|
+
}
|
|
8694
|
+
const sessionIdx = selectedIdx - 1;
|
|
8695
|
+
if (sessionIdx < scrollOffset) {
|
|
8696
|
+
scrollOffset = sessionIdx;
|
|
8697
|
+
} else if (sessionIdx >= scrollOffset + viewportSize) {
|
|
8698
|
+
scrollOffset = sessionIdx - viewportSize + 1;
|
|
8699
|
+
} else if (scrollOffset + viewportSize > visible.length) {
|
|
8700
|
+
scrollOffset = Math.max(0, visible.length - viewportSize);
|
|
8701
|
+
}
|
|
8702
|
+
};
|
|
8703
|
+
const composerBoxInner = () => Math.max(2, termWidth - 2);
|
|
8704
|
+
const paintComposerTopBorder = () => {
|
|
8705
|
+
const inner = composerBoxInner();
|
|
8706
|
+
const focused = selectedIdx === 0;
|
|
8707
|
+
const titleFragment = `\u2500 ${composerTitle} `;
|
|
8708
|
+
const dashCount = Math.max(1, inner - titleFragment.length);
|
|
8709
|
+
const dashes = "\u2500".repeat(dashCount);
|
|
8710
|
+
if (focused) {
|
|
8711
|
+
term.brightCyan.noFormat("\u256D");
|
|
8712
|
+
term.brightCyan.bold.noFormat(titleFragment);
|
|
8713
|
+
term.brightCyan.noFormat(`${dashes}\u256E`);
|
|
8714
|
+
} else {
|
|
8715
|
+
term.dim.noFormat(`\u256D${titleFragment}${dashes}\u256E`);
|
|
8716
|
+
}
|
|
8717
|
+
};
|
|
8718
|
+
const paintComposerBottomBorder = () => {
|
|
8719
|
+
const inner = composerBoxInner();
|
|
8720
|
+
const dashes = "\u2500".repeat(inner);
|
|
8721
|
+
if (selectedIdx === 0) {
|
|
8722
|
+
term.brightCyan.noFormat(`\u2570${dashes}\u256F`);
|
|
8723
|
+
} else {
|
|
8724
|
+
term.dim.noFormat(`\u2570${dashes}\u256F`);
|
|
8725
|
+
}
|
|
8726
|
+
};
|
|
8727
|
+
const paintComposerBodyRow = (visualIdx) => {
|
|
8728
|
+
const inner = composerBoxInner();
|
|
8729
|
+
const sideStyle = selectedIdx === 0 ? term.brightCyan : term.dim;
|
|
8730
|
+
sideStyle.noFormat("\u2502");
|
|
8731
|
+
const vr = composerVisualRows[visualIdx];
|
|
8732
|
+
let slice = "";
|
|
8733
|
+
if (vr) {
|
|
8734
|
+
slice = (composer.state().buffer[vr.bufferIdx] ?? "").slice(
|
|
8735
|
+
vr.startCol,
|
|
8736
|
+
vr.endCol
|
|
8737
|
+
);
|
|
8738
|
+
}
|
|
8739
|
+
term.noFormat(" ");
|
|
8740
|
+
term.noFormat(slice);
|
|
8741
|
+
const padWidth = Math.max(0, inner - 1 - slice.length);
|
|
8742
|
+
if (padWidth > 0) {
|
|
8743
|
+
term.noFormat(" ".repeat(padWidth));
|
|
8744
|
+
}
|
|
8745
|
+
sideStyle.noFormat("\u2502");
|
|
8746
|
+
};
|
|
8747
|
+
const paintSessionRow = (sessionIdx) => {
|
|
8748
|
+
const label = sessionLines[sessionIdx] ?? "";
|
|
8749
|
+
if (selectedIdx === sessionIdx + 1) {
|
|
8750
|
+
term.brightWhite.bgBlue.noFormat(`\u276F ${label}`);
|
|
8751
|
+
} else {
|
|
8752
|
+
term.noFormat(` ${label}`);
|
|
8753
|
+
}
|
|
8754
|
+
};
|
|
8755
|
+
const formatIndicator = () => {
|
|
8756
|
+
const above = scrollOffset;
|
|
8757
|
+
const below = Math.max(0, visible.length - scrollOffset - viewportSize);
|
|
8758
|
+
const parts = [];
|
|
8759
|
+
if (cwdOnly) {
|
|
8760
|
+
parts.push("cwd-only");
|
|
8761
|
+
}
|
|
8762
|
+
if (hostFilter !== "__all") {
|
|
8763
|
+
parts.push(
|
|
8764
|
+
hostFilter === "__local" ? "host: local" : `host: ${hostFilter}`
|
|
8765
|
+
);
|
|
8766
|
+
}
|
|
8767
|
+
if (above > 0) {
|
|
8768
|
+
parts.push(`\u2191 ${above} above`);
|
|
8769
|
+
}
|
|
8770
|
+
if (below > 0) {
|
|
8771
|
+
parts.push(`\u2193 ${below} below`);
|
|
8772
|
+
}
|
|
8773
|
+
if (parts.length === 0) {
|
|
8774
|
+
return "";
|
|
8775
|
+
}
|
|
8776
|
+
return ` ${parts.join(" \xB7 ")}`;
|
|
8777
|
+
};
|
|
8778
|
+
const shortId2 = (sessionId) => stripHydraSessionPrefix(sessionId);
|
|
8779
|
+
const paintIndicator = () => {
|
|
8780
|
+
term.moveTo(1, indicatorRow()).eraseLineAfter();
|
|
8781
|
+
if (mode === "confirm-kill" && pendingAction) {
|
|
8782
|
+
term.brightYellow.noFormat(` kill ${shortId2(pendingAction.sessionId)}? [y/N]`);
|
|
8783
|
+
return;
|
|
8784
|
+
}
|
|
8785
|
+
if (mode === "confirm-delete" && pendingAction) {
|
|
8786
|
+
term.brightRed.noFormat(` delete ${shortId2(pendingAction.sessionId)}? [y/N]`);
|
|
8787
|
+
return;
|
|
8788
|
+
}
|
|
8789
|
+
if (mode === "busy" && pendingAction) {
|
|
8790
|
+
term.dim.noFormat(` working on ${shortId2(pendingAction.sessionId)}\u2026`);
|
|
8791
|
+
return;
|
|
8792
|
+
}
|
|
8793
|
+
if (mode === "rename" && pendingAction) {
|
|
8794
|
+
term.brightYellow.noFormat(` title: ${renameBuffer}`);
|
|
8795
|
+
term.bgBrightYellow(" ");
|
|
8796
|
+
term.dim.noFormat(" Enter saves \xB7 Esc cancels");
|
|
8797
|
+
return;
|
|
8798
|
+
}
|
|
8799
|
+
if (transientStatus !== null) {
|
|
8800
|
+
term.dim.noFormat(` ${transientStatus}`);
|
|
8801
|
+
return;
|
|
8802
|
+
}
|
|
8803
|
+
if (searchActive) {
|
|
8804
|
+
term.brightYellow.noFormat(` /${searchTerm}`);
|
|
8805
|
+
term.bgBrightYellow(" ");
|
|
8806
|
+
const hint = visible.length === 0 ? " no matches" : ` ${visible.length} match${visible.length === 1 ? "" : "es"}`;
|
|
8807
|
+
term.dim.noFormat(`${hint} \xB7 ^c clears`);
|
|
8808
|
+
return;
|
|
8809
|
+
}
|
|
8810
|
+
term.dim.noFormat(formatIndicator());
|
|
8811
|
+
};
|
|
8812
|
+
const composerBodyRow = (visualOffset) => startRow + 1 + visualOffset;
|
|
8813
|
+
const composerBottomRow = () => startRow + composerRows + 1;
|
|
8814
|
+
const headerRow = () => startRow + composerRows + 3;
|
|
8815
|
+
const sessionRow = (sessionIdx) => headerRow() + 1 + (sessionIdx - scrollOffset);
|
|
8816
|
+
const indicatorRow = () => headerRow() + 1 + viewportSize;
|
|
8817
|
+
const placeComposerCursor = () => {
|
|
8818
|
+
const visualOffset = composerCursorRow - composerWindowStart;
|
|
8819
|
+
if (visualOffset < 0 || visualOffset >= composerRows) {
|
|
8820
|
+
return;
|
|
8821
|
+
}
|
|
8822
|
+
const col = 3 + composerCursorCol;
|
|
8823
|
+
term.moveTo(col, composerBodyRow(visualOffset));
|
|
8824
|
+
};
|
|
8825
|
+
const renderFromScratch = () => {
|
|
8826
|
+
if (mode === "help") {
|
|
8827
|
+
renderHelp();
|
|
8828
|
+
return;
|
|
8829
|
+
}
|
|
8830
|
+
computeLayout();
|
|
8831
|
+
adjustScroll();
|
|
8832
|
+
startRow = 1;
|
|
8833
|
+
term.moveTo(1, 1).eraseDisplayBelow();
|
|
8834
|
+
paintComposerTopBorder();
|
|
8835
|
+
term("\n");
|
|
8836
|
+
for (let v = 0; v < composerRows; v++) {
|
|
8837
|
+
paintComposerBodyRow(composerWindowStart + v);
|
|
8838
|
+
term("\n");
|
|
8839
|
+
}
|
|
8840
|
+
paintComposerBottomBorder();
|
|
8841
|
+
term("\n\n");
|
|
8842
|
+
term.dim.noFormat(` ${headerLine}`)("\n");
|
|
8843
|
+
for (let v = 0; v < viewportSize; v++) {
|
|
8844
|
+
paintSessionRow(scrollOffset + v);
|
|
8845
|
+
term("\n");
|
|
8846
|
+
}
|
|
8847
|
+
paintIndicator();
|
|
8848
|
+
term("\n");
|
|
8849
|
+
if (selectedIdx === 0) {
|
|
8850
|
+
placeComposerCursor();
|
|
8851
|
+
term.hideCursor(false);
|
|
8852
|
+
} else {
|
|
8853
|
+
term.hideCursor();
|
|
8854
|
+
}
|
|
8855
|
+
};
|
|
8856
|
+
const renderHelp = () => {
|
|
8857
|
+
term.moveTo(1, 1).eraseDisplayBelow();
|
|
8858
|
+
term.brightWhite.bold.noFormat(" Picker hotkeys")("\n\n");
|
|
8859
|
+
for (const entry of HELP_ENTRIES) {
|
|
8860
|
+
if (entry === null) {
|
|
8861
|
+
term("\n");
|
|
8862
|
+
continue;
|
|
8868
8863
|
}
|
|
8869
|
-
|
|
8870
|
-
|
|
8871
|
-
|
|
8872
|
-
|
|
8873
|
-
|
|
8874
|
-
|
|
8875
|
-
|
|
8876
|
-
|
|
8877
|
-
|
|
8878
|
-
|
|
8879
|
-
|
|
8880
|
-
|
|
8881
|
-
|
|
8882
|
-
|
|
8883
|
-
|
|
8884
|
-
|
|
8885
|
-
|
|
8886
|
-
|
|
8864
|
+
const [keys, desc] = entry;
|
|
8865
|
+
term.brightCyan.noFormat(` ${keys.padEnd(HELP_KEYS_WIDTH)}`);
|
|
8866
|
+
term.noFormat(desc)("\n");
|
|
8867
|
+
}
|
|
8868
|
+
term("\n");
|
|
8869
|
+
term.dim.noFormat(" press any key to dismiss")("\n");
|
|
8870
|
+
};
|
|
8871
|
+
const repaintComposerChrome = () => {
|
|
8872
|
+
term.moveTo(1, startRow).eraseLineAfter();
|
|
8873
|
+
paintComposerTopBorder();
|
|
8874
|
+
term.moveTo(1, composerBottomRow()).eraseLineAfter();
|
|
8875
|
+
paintComposerBottomBorder();
|
|
8876
|
+
for (let v = 0; v < composerRows; v++) {
|
|
8877
|
+
term.moveTo(1, composerBodyRow(v)).eraseLineAfter();
|
|
8878
|
+
paintComposerBodyRow(composerWindowStart + v);
|
|
8879
|
+
}
|
|
8880
|
+
};
|
|
8881
|
+
const repaintComposerBody = () => {
|
|
8882
|
+
const state = composer.state();
|
|
8883
|
+
composerVisualRows = computePromptVisualRows(state.buffer, composerRoom);
|
|
8884
|
+
const layout = computePromptLayout(
|
|
8885
|
+
composerVisualRows,
|
|
8886
|
+
state,
|
|
8887
|
+
PICKER_COMPOSER_MAX_ROWS
|
|
8888
|
+
);
|
|
8889
|
+
composerWindowStart = layout.windowStart;
|
|
8890
|
+
composerCursorRow = layout.cursorVisualRow;
|
|
8891
|
+
composerCursorCol = layout.cursorVisualCol;
|
|
8892
|
+
for (let v = 0; v < composerRows; v++) {
|
|
8893
|
+
term.moveTo(1, composerBodyRow(v)).eraseLineAfter();
|
|
8894
|
+
paintComposerBodyRow(composerWindowStart + v);
|
|
8895
|
+
}
|
|
8896
|
+
if (selectedIdx === 0) {
|
|
8897
|
+
placeComposerCursor();
|
|
8898
|
+
}
|
|
8899
|
+
};
|
|
8900
|
+
const repaintSessionRow = (sessionIdx) => {
|
|
8901
|
+
if (sessionIdx < scrollOffset || sessionIdx >= scrollOffset + viewportSize) {
|
|
8902
|
+
return;
|
|
8903
|
+
}
|
|
8904
|
+
term.moveTo(1, sessionRow(sessionIdx)).eraseLineAfter();
|
|
8905
|
+
paintSessionRow(sessionIdx);
|
|
8906
|
+
};
|
|
8907
|
+
const repaintViewport = () => {
|
|
8908
|
+
for (let v = 0; v < viewportSize; v++) {
|
|
8909
|
+
const row = headerRow() + 1 + v;
|
|
8910
|
+
term.moveTo(1, row).eraseLineAfter();
|
|
8911
|
+
const sessionIdx = scrollOffset + v;
|
|
8912
|
+
if (sessionIdx < visible.length) {
|
|
8913
|
+
paintSessionRow(sessionIdx);
|
|
8887
8914
|
}
|
|
8888
|
-
|
|
8889
|
-
|
|
8890
|
-
|
|
8891
|
-
|
|
8892
|
-
|
|
8893
|
-
|
|
8894
|
-
|
|
8895
|
-
|
|
8915
|
+
}
|
|
8916
|
+
paintIndicator();
|
|
8917
|
+
};
|
|
8918
|
+
renderFromScratch();
|
|
8919
|
+
return await new Promise((resolve6) => {
|
|
8920
|
+
let resolved = false;
|
|
8921
|
+
const onResize = () => {
|
|
8922
|
+
if (resolved) {
|
|
8923
|
+
return;
|
|
8896
8924
|
}
|
|
8897
|
-
|
|
8898
|
-
|
|
8899
|
-
|
|
8900
|
-
|
|
8901
|
-
|
|
8902
|
-
// rows, so a 10k-line scrollback costs ~50 cache hits per repaint
|
|
8903
|
-
// instead of 10k. With `needed = Infinity` this walks everything and
|
|
8904
|
-
// doubles as a total-row counter for maxScrollOffset.
|
|
8905
|
-
wrapTail(width, needed) {
|
|
8906
|
-
if (width <= 4) {
|
|
8907
|
-
const take = Math.min(needed, this.lines.length);
|
|
8908
|
-
return {
|
|
8909
|
-
rows: this.lines.slice(this.lines.length - take),
|
|
8910
|
-
exhausted: needed >= this.lines.length
|
|
8911
|
-
};
|
|
8912
|
-
}
|
|
8913
|
-
if (this.wrapCacheWidth !== width) {
|
|
8914
|
-
this.wrapCache.clear();
|
|
8915
|
-
this.wrapCacheWidth = width;
|
|
8916
|
-
}
|
|
8917
|
-
if (needed <= 0 || this.lines.length === 0) {
|
|
8918
|
-
return { rows: [], exhausted: true };
|
|
8919
|
-
}
|
|
8920
|
-
const batches = [];
|
|
8921
|
-
let total = 0;
|
|
8922
|
-
let stoppedAt = 0;
|
|
8923
|
-
for (let i = this.lines.length - 1; i >= 0; i--) {
|
|
8924
|
-
const wrapped = this.wrapOne(this.lines[i], width);
|
|
8925
|
-
batches.push(wrapped);
|
|
8926
|
-
total += wrapped.length;
|
|
8927
|
-
stoppedAt = i;
|
|
8928
|
-
if (total >= needed) {
|
|
8929
|
-
break;
|
|
8930
|
-
}
|
|
8931
|
-
}
|
|
8932
|
-
const rows = [];
|
|
8933
|
-
for (let i = batches.length - 1; i >= 0; i--) {
|
|
8934
|
-
rows.push(...batches[i]);
|
|
8935
|
-
}
|
|
8936
|
-
return { rows, exhausted: stoppedAt === 0 };
|
|
8925
|
+
renderFromScratch();
|
|
8926
|
+
};
|
|
8927
|
+
const cleanup = () => {
|
|
8928
|
+
if (resolved) {
|
|
8929
|
+
return;
|
|
8937
8930
|
}
|
|
8938
|
-
|
|
8939
|
-
|
|
8940
|
-
|
|
8941
|
-
|
|
8942
|
-
|
|
8943
|
-
|
|
8944
|
-
|
|
8945
|
-
|
|
8946
|
-
|
|
8947
|
-
|
|
8948
|
-
const
|
|
8949
|
-
|
|
8950
|
-
|
|
8951
|
-
|
|
8952
|
-
|
|
8953
|
-
|
|
8954
|
-
|
|
8955
|
-
prefix: i === 0 ? line.prefix : " ".repeat(prefix.length),
|
|
8956
|
-
body: chunk
|
|
8957
|
-
};
|
|
8958
|
-
if (line.prefixStyle !== void 0) {
|
|
8959
|
-
wrappedLine.prefixStyle = line.prefixStyle;
|
|
8960
|
-
}
|
|
8961
|
-
if (line.bodyStyle !== void 0) {
|
|
8962
|
-
wrappedLine.bodyStyle = line.bodyStyle;
|
|
8963
|
-
}
|
|
8964
|
-
if (line.fillRow) {
|
|
8965
|
-
wrappedLine.fillRow = true;
|
|
8966
|
-
}
|
|
8967
|
-
if (line.ansi) {
|
|
8968
|
-
wrappedLine.ansi = true;
|
|
8969
|
-
}
|
|
8970
|
-
if (i === 0 && line.iterm2Image) {
|
|
8971
|
-
wrappedLine.iterm2Image = line.iterm2Image;
|
|
8972
|
-
}
|
|
8973
|
-
if (id !== void 0 && chunk.length > 0) {
|
|
8974
|
-
const found = line.body.indexOf(chunk, scanPos);
|
|
8975
|
-
const colOffset = found === -1 ? scanPos : found;
|
|
8976
|
-
this.wrapOrigin.set(wrappedLine, {
|
|
8977
|
-
sourceLineId: id,
|
|
8978
|
-
sourceColOffset: colOffset
|
|
8979
|
-
});
|
|
8980
|
-
scanPos = colOffset + chunk.length;
|
|
8931
|
+
resolved = true;
|
|
8932
|
+
term.off("key", onKey);
|
|
8933
|
+
term.off("resize", onResize);
|
|
8934
|
+
term.grabInput(false);
|
|
8935
|
+
term.hideCursor(false);
|
|
8936
|
+
term.moveTo(1, indicatorRow() + 1);
|
|
8937
|
+
term("\n");
|
|
8938
|
+
};
|
|
8939
|
+
const refresh = async (preferredId) => {
|
|
8940
|
+
try {
|
|
8941
|
+
const next = await listSessions(opts.target);
|
|
8942
|
+
allSessions = sortSessions(next);
|
|
8943
|
+
applyFilter();
|
|
8944
|
+
if (preferredId !== void 0) {
|
|
8945
|
+
const idx = visible.findIndex((s) => s.sessionId === preferredId);
|
|
8946
|
+
if (idx >= 0) {
|
|
8947
|
+
selectedIdx = idx + 1;
|
|
8981
8948
|
}
|
|
8982
|
-
wrapped.push(wrappedLine);
|
|
8983
8949
|
}
|
|
8984
|
-
if (
|
|
8985
|
-
|
|
8950
|
+
if (selectedIdx > total - 1) {
|
|
8951
|
+
selectedIdx = Math.max(0, total - 1);
|
|
8986
8952
|
}
|
|
8987
|
-
|
|
8988
|
-
|
|
8989
|
-
writeFormattedLine(line, width, activeMatchCol = null, activeMatchLength = 0) {
|
|
8990
|
-
if (line.prefix) {
|
|
8991
|
-
writeStyled(this.term, line.prefix, line.prefixStyle ?? line.bodyStyle);
|
|
8953
|
+
if (scrollOffset + viewportSize > visible.length) {
|
|
8954
|
+
scrollOffset = Math.max(0, visible.length - viewportSize);
|
|
8992
8955
|
}
|
|
8993
|
-
|
|
8994
|
-
|
|
8995
|
-
|
|
8996
|
-
|
|
8997
|
-
|
|
8998
|
-
|
|
8999
|
-
|
|
9000
|
-
|
|
9001
|
-
|
|
9002
|
-
|
|
9003
|
-
|
|
9004
|
-
|
|
8956
|
+
adjustScroll();
|
|
8957
|
+
renderFromScratch();
|
|
8958
|
+
} catch (err) {
|
|
8959
|
+
transientStatus = `refresh failed: ${err.message}`;
|
|
8960
|
+
renderFromScratch();
|
|
8961
|
+
}
|
|
8962
|
+
};
|
|
8963
|
+
const performRename = async (title) => {
|
|
8964
|
+
if (!pendingAction) {
|
|
8965
|
+
return;
|
|
8966
|
+
}
|
|
8967
|
+
const session = pendingAction;
|
|
8968
|
+
mode = "busy";
|
|
8969
|
+
paintIndicator();
|
|
8970
|
+
try {
|
|
8971
|
+
await renameSession(opts.target, session.sessionId, title);
|
|
8972
|
+
mode = "normal";
|
|
8973
|
+
pendingAction = null;
|
|
8974
|
+
renameBuffer = "";
|
|
8975
|
+
await refresh(session.sessionId);
|
|
8976
|
+
} catch (err) {
|
|
8977
|
+
mode = "normal";
|
|
8978
|
+
pendingAction = null;
|
|
8979
|
+
renameBuffer = "";
|
|
8980
|
+
transientStatus = `rename failed: ${err.message}`;
|
|
8981
|
+
paintIndicator();
|
|
8982
|
+
}
|
|
8983
|
+
};
|
|
8984
|
+
const performRegen = async (session) => {
|
|
8985
|
+
try {
|
|
8986
|
+
await regenSessionTitle(opts.target, session.sessionId);
|
|
8987
|
+
transientStatus = "title regen queued (press r to refresh)";
|
|
8988
|
+
paintIndicator();
|
|
8989
|
+
} catch (err) {
|
|
8990
|
+
transientStatus = `regen failed: ${err.message}`;
|
|
8991
|
+
paintIndicator();
|
|
8992
|
+
}
|
|
8993
|
+
};
|
|
8994
|
+
const performAction = async (kind) => {
|
|
8995
|
+
if (!pendingAction) {
|
|
8996
|
+
return;
|
|
8997
|
+
}
|
|
8998
|
+
const session = pendingAction;
|
|
8999
|
+
mode = "busy";
|
|
9000
|
+
paintIndicator();
|
|
9001
|
+
try {
|
|
9002
|
+
if (kind === "kill") {
|
|
9003
|
+
await killSession(opts.target, session.sessionId);
|
|
9005
9004
|
} else {
|
|
9006
|
-
|
|
9007
|
-
}
|
|
9008
|
-
if (line.fillRow) {
|
|
9009
|
-
const visible = line.ansi ? stringWidth(bodyText) : bodyText.length;
|
|
9010
|
-
const pad = remaining - visible;
|
|
9011
|
-
if (pad > 0) {
|
|
9012
|
-
writeStyled(this.term, " ".repeat(pad), line.bodyStyle);
|
|
9013
|
-
}
|
|
9014
|
-
}
|
|
9015
|
-
if (line.ansi || line.body.includes("^")) {
|
|
9016
|
-
this.term.styleReset();
|
|
9017
|
-
}
|
|
9018
|
-
if (line.iterm2Image && this.isIterm2()) {
|
|
9019
|
-
this.writeIterm2Image(
|
|
9020
|
-
line.iterm2Image.data,
|
|
9021
|
-
line.iterm2Image.heightCells
|
|
9022
|
-
);
|
|
9005
|
+
await deleteSession(opts.target, session.sessionId);
|
|
9023
9006
|
}
|
|
9007
|
+
mode = "normal";
|
|
9008
|
+
pendingAction = null;
|
|
9009
|
+
await refresh(kind === "kill" ? session.sessionId : void 0);
|
|
9010
|
+
} catch (err) {
|
|
9011
|
+
mode = "normal";
|
|
9012
|
+
pendingAction = null;
|
|
9013
|
+
transientStatus = `${kind} failed: ${err.message}`;
|
|
9014
|
+
paintIndicator();
|
|
9024
9015
|
}
|
|
9025
9016
|
};
|
|
9026
|
-
|
|
9027
|
-
|
|
9028
|
-
|
|
9029
|
-
shortId = stripHydraSessionPrefix;
|
|
9030
|
-
}
|
|
9031
|
-
});
|
|
9032
|
-
|
|
9033
|
-
// src/tui/input.ts
|
|
9034
|
-
var InputDispatcher;
|
|
9035
|
-
var init_input = __esm({
|
|
9036
|
-
"src/tui/input.ts"() {
|
|
9037
|
-
"use strict";
|
|
9038
|
-
InputDispatcher = class {
|
|
9039
|
-
buffer = [""];
|
|
9040
|
-
row = 0;
|
|
9041
|
-
col = 0;
|
|
9042
|
-
planMode = false;
|
|
9043
|
-
historyIndex = -1;
|
|
9044
|
-
// Queue editing: when the user walks Up past row 0 with queued prompts
|
|
9045
|
-
// present, the most-recently-queued item lands in the buffer and
|
|
9046
|
-
// queueIndex tracks which slot of `queue` is being edited. Enter submits
|
|
9047
|
-
// the edit (queue-edit) or, on an empty buffer, drops the slot
|
|
9048
|
-
// (queue-remove). -1 means not editing a queue slot.
|
|
9049
|
-
queueIndex = -1;
|
|
9050
|
-
savedDraft = null;
|
|
9051
|
-
history = [];
|
|
9052
|
-
// Active reverse-incremental search over `history`. Set when ^r is
|
|
9053
|
-
// pressed; cleared when the user accepts (Enter / typing / arrows)
|
|
9054
|
-
// or cancels (ESC). `query` is the lowercased substring matched
|
|
9055
|
-
// against history entries; `matchIndices` are history indices in
|
|
9056
|
-
// newest→oldest order; `cursor` is the current index into that list.
|
|
9057
|
-
// `savedDraft` snapshots the buffer/cursor at the moment search
|
|
9058
|
-
// began so ESC can restore it.
|
|
9059
|
-
historySearch = null;
|
|
9060
|
-
// Waiting queue snapshot (excludes the in-flight head). Newest item lives
|
|
9061
|
-
// at the end so Up walks the array right-to-left.
|
|
9062
|
-
queue = [];
|
|
9063
|
-
turnRunning = false;
|
|
9064
|
-
// Single-slot kill ring. The most recent killed text (^U, ^K, ^W) lands
|
|
9065
|
-
// here so ^Y can yank it back. Standard readline keeps a stack; we
|
|
9066
|
-
// only keep one slot because that's what 99% of yank uses look like.
|
|
9067
|
-
killBuffer = "";
|
|
9068
|
-
// Images attached to the current draft. Cleared in the same paths
|
|
9069
|
-
// that clear the text buffer (clearBuffer, after send). Queue
|
|
9070
|
-
// navigation snapshots/restores them alongside savedDraft so up/down
|
|
9071
|
-
// through queued items doesn't drop chips.
|
|
9072
|
-
attachments = [];
|
|
9073
|
-
// Snapshot of `attachments` taken when the user starts walking
|
|
9074
|
-
// history/queue with chips already attached. Restored alongside the
|
|
9075
|
-
// text draft when the walk ends. Distinct from savedDraft because
|
|
9076
|
-
// queue slots (which may carry their own attachments — though we
|
|
9077
|
-
// don't surface that yet) shouldn't blend with the current draft's.
|
|
9078
|
-
savedAttachments = null;
|
|
9079
|
-
constructor(opts = {}) {
|
|
9080
|
-
this.history = [...opts.history ?? []];
|
|
9081
|
-
this.planMode = opts.planMode ?? false;
|
|
9017
|
+
const onFocusChange = (oldIdx, newIdx) => {
|
|
9018
|
+
if (oldIdx === 0 === (newIdx === 0)) {
|
|
9019
|
+
return;
|
|
9082
9020
|
}
|
|
9083
|
-
|
|
9084
|
-
|
|
9085
|
-
|
|
9086
|
-
|
|
9087
|
-
|
|
9088
|
-
|
|
9089
|
-
historyIndex: this.historyIndex,
|
|
9090
|
-
queueIndex: this.queueIndex,
|
|
9091
|
-
attachments: [...this.attachments],
|
|
9092
|
-
historySearchQuery: this.historySearch?.query ?? null
|
|
9093
|
-
};
|
|
9021
|
+
repaintComposerChrome();
|
|
9022
|
+
if (newIdx === 0) {
|
|
9023
|
+
term.hideCursor(false);
|
|
9024
|
+
placeComposerCursor();
|
|
9025
|
+
} else {
|
|
9026
|
+
term.hideCursor();
|
|
9094
9027
|
}
|
|
9095
|
-
|
|
9096
|
-
|
|
9097
|
-
|
|
9098
|
-
|
|
9099
|
-
|
|
9028
|
+
};
|
|
9029
|
+
const move = (delta) => {
|
|
9030
|
+
const next = Math.min(total - 1, Math.max(0, selectedIdx + delta));
|
|
9031
|
+
if (next === selectedIdx) {
|
|
9032
|
+
return;
|
|
9100
9033
|
}
|
|
9101
|
-
|
|
9102
|
-
|
|
9034
|
+
const old = selectedIdx;
|
|
9035
|
+
const oldScroll = scrollOffset;
|
|
9036
|
+
selectedIdx = next;
|
|
9037
|
+
adjustScroll();
|
|
9038
|
+
if (scrollOffset !== oldScroll) {
|
|
9039
|
+
repaintViewport();
|
|
9040
|
+
onFocusChange(old, selectedIdx);
|
|
9041
|
+
return;
|
|
9042
|
+
}
|
|
9043
|
+
if (old !== 0) {
|
|
9044
|
+
repaintSessionRow(old - 1);
|
|
9045
|
+
}
|
|
9046
|
+
if (selectedIdx !== 0) {
|
|
9047
|
+
repaintSessionRow(selectedIdx - 1);
|
|
9048
|
+
}
|
|
9049
|
+
onFocusChange(old, selectedIdx);
|
|
9050
|
+
};
|
|
9051
|
+
const clearTransient = () => {
|
|
9052
|
+
if (transientStatus === null) {
|
|
9053
|
+
return false;
|
|
9054
|
+
}
|
|
9055
|
+
transientStatus = null;
|
|
9056
|
+
paintIndicator();
|
|
9057
|
+
return true;
|
|
9058
|
+
};
|
|
9059
|
+
const onKey = (name, _matches, data) => {
|
|
9060
|
+
if (mode === "busy") {
|
|
9061
|
+
return;
|
|
9062
|
+
}
|
|
9063
|
+
if (mode === "help") {
|
|
9064
|
+
if (name === "CTRL_C") {
|
|
9065
|
+
cleanup();
|
|
9066
|
+
resolve6({ kind: "abort" });
|
|
9067
|
+
return;
|
|
9068
|
+
}
|
|
9069
|
+
mode = "normal";
|
|
9070
|
+
renderFromScratch();
|
|
9071
|
+
return;
|
|
9072
|
+
}
|
|
9073
|
+
if (mode === "rename") {
|
|
9074
|
+
if (name === "ENTER" || name === "KP_ENTER") {
|
|
9075
|
+
const trimmed = renameBuffer.trim();
|
|
9076
|
+
if (trimmed.length === 0) {
|
|
9077
|
+
mode = "normal";
|
|
9078
|
+
pendingAction = null;
|
|
9079
|
+
renameBuffer = "";
|
|
9080
|
+
paintIndicator();
|
|
9081
|
+
return;
|
|
9082
|
+
}
|
|
9083
|
+
void performRename(trimmed);
|
|
9084
|
+
return;
|
|
9085
|
+
}
|
|
9086
|
+
if (name === "ESCAPE" || name === "CTRL_C") {
|
|
9087
|
+
mode = "normal";
|
|
9088
|
+
pendingAction = null;
|
|
9089
|
+
renameBuffer = "";
|
|
9090
|
+
paintIndicator();
|
|
9091
|
+
return;
|
|
9092
|
+
}
|
|
9093
|
+
if (name === "BACKSPACE") {
|
|
9094
|
+
if (renameBuffer.length > 0) {
|
|
9095
|
+
renameBuffer = renameBuffer.slice(0, -1);
|
|
9096
|
+
paintIndicator();
|
|
9097
|
+
}
|
|
9098
|
+
return;
|
|
9099
|
+
}
|
|
9100
|
+
if (name === "CTRL_U") {
|
|
9101
|
+
renameBuffer = "";
|
|
9102
|
+
paintIndicator();
|
|
9103
9103
|
return;
|
|
9104
9104
|
}
|
|
9105
|
-
|
|
9106
|
-
|
|
9107
|
-
|
|
9108
|
-
|
|
9109
|
-
|
|
9110
|
-
|
|
9111
|
-
this.history = [...history];
|
|
9112
|
-
this.historyIndex = -1;
|
|
9113
|
-
this.savedDraft = null;
|
|
9114
|
-
this.historySearch = null;
|
|
9115
|
-
}
|
|
9116
|
-
// Snapshot of the waiting queue (head excluded). Called by the app after
|
|
9117
|
-
// every queue mutation so Up/Down can walk a fresh view. queueIndex is
|
|
9118
|
-
// only invalidated when it falls outside the new bounds — staying in
|
|
9119
|
-
// bounds preserves the user's edit if the queue grew or stayed put.
|
|
9120
|
-
setQueue(queue) {
|
|
9121
|
-
this.queue = [...queue];
|
|
9122
|
-
if (this.queueIndex >= this.queue.length) {
|
|
9123
|
-
this.queueIndex = -1;
|
|
9105
|
+
if (name === "CTRL_W") {
|
|
9106
|
+
const trimmedRight = renameBuffer.replace(/\s+$/, "");
|
|
9107
|
+
const lastSpace = trimmedRight.lastIndexOf(" ");
|
|
9108
|
+
renameBuffer = lastSpace >= 0 ? trimmedRight.slice(0, lastSpace) : "";
|
|
9109
|
+
paintIndicator();
|
|
9110
|
+
return;
|
|
9124
9111
|
}
|
|
9125
|
-
|
|
9126
|
-
|
|
9127
|
-
|
|
9128
|
-
|
|
9129
|
-
replaceFirstLine(text) {
|
|
9130
|
-
this.buffer[0] = text;
|
|
9131
|
-
if (this.row === 0) {
|
|
9132
|
-
this.col = text.length;
|
|
9112
|
+
if (data?.isCharacter) {
|
|
9113
|
+
renameBuffer += name;
|
|
9114
|
+
paintIndicator();
|
|
9115
|
+
return;
|
|
9133
9116
|
}
|
|
9117
|
+
return;
|
|
9134
9118
|
}
|
|
9135
|
-
|
|
9136
|
-
|
|
9137
|
-
|
|
9138
|
-
|
|
9139
|
-
|
|
9140
|
-
|
|
9141
|
-
|
|
9142
|
-
|
|
9143
|
-
|
|
9144
|
-
|
|
9145
|
-
|
|
9146
|
-
|
|
9147
|
-
|
|
9119
|
+
if (mode === "confirm-kill" || mode === "confirm-delete") {
|
|
9120
|
+
if (data?.isCharacter && (name === "y" || name === "Y")) {
|
|
9121
|
+
const kind = mode === "confirm-kill" ? "kill" : "delete";
|
|
9122
|
+
void performAction(kind);
|
|
9123
|
+
return;
|
|
9124
|
+
}
|
|
9125
|
+
if (name === "ESCAPE" || name === "CTRL_C" || name === "ENTER" || name === "KP_ENTER" || data?.isCharacter && (name === "n" || name === "N")) {
|
|
9126
|
+
mode = "normal";
|
|
9127
|
+
pendingAction = null;
|
|
9128
|
+
paintIndicator();
|
|
9129
|
+
return;
|
|
9130
|
+
}
|
|
9131
|
+
return;
|
|
9148
9132
|
}
|
|
9149
|
-
|
|
9150
|
-
|
|
9151
|
-
|
|
9152
|
-
|
|
9153
|
-
|
|
9154
|
-
|
|
9155
|
-
}
|
|
9156
|
-
if (event.type === "paste") {
|
|
9157
|
-
return this.mutateHistorySearchQuery(
|
|
9158
|
-
this.historySearch.query + event.text.replace(/\n/g, " ").toLowerCase()
|
|
9159
|
-
);
|
|
9160
|
-
}
|
|
9161
|
-
if (event.type === "key") {
|
|
9162
|
-
if (event.name === "ctrl-r") {
|
|
9163
|
-
return this.advanceHistorySearch();
|
|
9164
|
-
}
|
|
9165
|
-
if (event.name === "ctrl-s") {
|
|
9166
|
-
this.retreatHistorySearch();
|
|
9167
|
-
return [];
|
|
9168
|
-
}
|
|
9169
|
-
if (event.name === "escape" || event.name === "ctrl-c") {
|
|
9170
|
-
this.cancelHistorySearch();
|
|
9171
|
-
return [];
|
|
9172
|
-
}
|
|
9173
|
-
if (event.name === "backspace") {
|
|
9174
|
-
if (this.historySearch.query.length === 0) {
|
|
9175
|
-
this.cancelHistorySearch();
|
|
9176
|
-
return [];
|
|
9177
|
-
}
|
|
9178
|
-
return this.mutateHistorySearchQuery(
|
|
9179
|
-
this.historySearch.query.slice(0, -1)
|
|
9180
|
-
);
|
|
9181
|
-
}
|
|
9182
|
-
this.historySearch = null;
|
|
9183
|
-
}
|
|
9133
|
+
clearTransient();
|
|
9134
|
+
if (selectedIdx === 0 && !searchActive) {
|
|
9135
|
+
if (name === "ESCAPE" || name === "CTRL_C" || name === "CTRL_D") {
|
|
9136
|
+
cleanup();
|
|
9137
|
+
resolve6({ kind: "abort" });
|
|
9138
|
+
return;
|
|
9184
9139
|
}
|
|
9185
|
-
if (
|
|
9186
|
-
|
|
9187
|
-
|
|
9140
|
+
if (name === "ENTER" || name === "KP_ENTER") {
|
|
9141
|
+
cleanup();
|
|
9142
|
+
const text = composer.state().buffer.join("\n");
|
|
9143
|
+
if (text.trim().length === 0) {
|
|
9144
|
+
resolve6({ kind: "new" });
|
|
9145
|
+
} else {
|
|
9146
|
+
resolve6({ kind: "new", prompt: text });
|
|
9147
|
+
}
|
|
9148
|
+
return;
|
|
9188
9149
|
}
|
|
9189
|
-
if (
|
|
9190
|
-
|
|
9191
|
-
|
|
9150
|
+
if (name === "DOWN") {
|
|
9151
|
+
const atBottom = composerVisualRows.length === 0 || composerCursorRow === composerVisualRows.length - 1;
|
|
9152
|
+
if (atBottom && visible.length > 0) {
|
|
9153
|
+
move(1);
|
|
9154
|
+
return;
|
|
9155
|
+
}
|
|
9192
9156
|
}
|
|
9193
|
-
if (
|
|
9194
|
-
|
|
9157
|
+
if (name === "PAGE_DOWN") {
|
|
9158
|
+
const atBottom = composerVisualRows.length === 0 || composerCursorRow === composerVisualRows.length - 1;
|
|
9159
|
+
if (atBottom && visible.length > 0) {
|
|
9160
|
+
move(1);
|
|
9161
|
+
return;
|
|
9162
|
+
}
|
|
9195
9163
|
}
|
|
9196
|
-
|
|
9197
|
-
|
|
9198
|
-
|
|
9199
|
-
|
|
9200
|
-
|
|
9201
|
-
return this.send();
|
|
9202
|
-
case "shift-enter":
|
|
9203
|
-
case "ctrl-enter":
|
|
9204
|
-
return this.amend();
|
|
9205
|
-
case "alt-enter":
|
|
9206
|
-
this.insertNewline();
|
|
9207
|
-
return [];
|
|
9208
|
-
case "shift-tab":
|
|
9209
|
-
this.planMode = !this.planMode;
|
|
9210
|
-
return [
|
|
9211
|
-
{ type: "plan-toggle", on: this.planMode },
|
|
9212
|
-
{ type: "redraw-banner" }
|
|
9213
|
-
];
|
|
9214
|
-
case "tab":
|
|
9215
|
-
this.insertText(" ");
|
|
9216
|
-
return [];
|
|
9217
|
-
case "up":
|
|
9218
|
-
return this.handleUp();
|
|
9219
|
-
case "down":
|
|
9220
|
-
return this.handleDown();
|
|
9221
|
-
case "left":
|
|
9222
|
-
this.moveLeft();
|
|
9223
|
-
return [];
|
|
9224
|
-
case "right":
|
|
9225
|
-
this.moveRight();
|
|
9226
|
-
return [];
|
|
9227
|
-
case "ctrl-a":
|
|
9228
|
-
this.col = 0;
|
|
9229
|
-
return [];
|
|
9230
|
-
case "ctrl-e":
|
|
9231
|
-
this.col = this.currentLine().length;
|
|
9232
|
-
return [];
|
|
9233
|
-
case "home":
|
|
9234
|
-
return this.handleHome();
|
|
9235
|
-
case "end":
|
|
9236
|
-
return this.handleEnd();
|
|
9237
|
-
case "ctrl-b":
|
|
9238
|
-
this.moveLeft();
|
|
9239
|
-
return [];
|
|
9240
|
-
case "ctrl-f":
|
|
9241
|
-
this.moveRight();
|
|
9242
|
-
return [];
|
|
9243
|
-
case "ctrl-g":
|
|
9244
|
-
return [{ type: "show-help" }];
|
|
9245
|
-
case "alt-b":
|
|
9246
|
-
this.moveWordBackward();
|
|
9247
|
-
return [];
|
|
9248
|
-
case "alt-f":
|
|
9249
|
-
this.moveWordForward();
|
|
9250
|
-
return [];
|
|
9251
|
-
case "ctrl-k":
|
|
9252
|
-
this.killToEnd();
|
|
9253
|
-
return [];
|
|
9254
|
-
case "ctrl-n":
|
|
9255
|
-
return this.handleDown();
|
|
9256
|
-
case "ctrl-o":
|
|
9257
|
-
return [{ type: "toggle-tools" }];
|
|
9258
|
-
case "backspace":
|
|
9259
|
-
this.backspace();
|
|
9260
|
-
return [];
|
|
9261
|
-
case "delete":
|
|
9262
|
-
this.deleteForward();
|
|
9263
|
-
return [];
|
|
9264
|
-
case "ctrl-c":
|
|
9265
|
-
return this.handleCtrlC();
|
|
9266
|
-
case "ctrl-d":
|
|
9267
|
-
if (this.bufferIsEmpty()) {
|
|
9268
|
-
return [{ type: "exit" }];
|
|
9269
|
-
}
|
|
9270
|
-
this.deleteForward();
|
|
9271
|
-
return [];
|
|
9272
|
-
case "ctrl-l":
|
|
9273
|
-
return [{ type: "redraw" }];
|
|
9274
|
-
case "ctrl-p":
|
|
9275
|
-
return [{ type: "switch-session" }];
|
|
9276
|
-
case "ctrl-t":
|
|
9277
|
-
return [{ type: "next-live-session" }];
|
|
9278
|
-
case "ctrl-r":
|
|
9279
|
-
return this.startHistorySearch();
|
|
9280
|
-
case "ctrl-s":
|
|
9281
|
-
return [];
|
|
9282
|
-
case "ctrl-u":
|
|
9283
|
-
this.killLine();
|
|
9284
|
-
return [];
|
|
9285
|
-
case "ctrl-v":
|
|
9286
|
-
return [{ type: "attachment-request", source: "clipboard" }];
|
|
9287
|
-
case "ctrl-w":
|
|
9288
|
-
this.killWord();
|
|
9289
|
-
return [];
|
|
9290
|
-
case "ctrl-x":
|
|
9291
|
-
return [{ type: "toggle-mouse" }];
|
|
9292
|
-
case "ctrl-y":
|
|
9293
|
-
this.yank();
|
|
9294
|
-
return [];
|
|
9295
|
-
case "escape":
|
|
9296
|
-
if (this.turnRunning) {
|
|
9297
|
-
return [{ type: "cancel", prefill: true }];
|
|
9298
|
-
}
|
|
9299
|
-
return [];
|
|
9164
|
+
if (name === "CTRL_P") {
|
|
9165
|
+
if (visible.length > 0) {
|
|
9166
|
+
move(1);
|
|
9167
|
+
}
|
|
9168
|
+
return;
|
|
9300
9169
|
}
|
|
9301
|
-
|
|
9302
|
-
|
|
9303
|
-
|
|
9304
|
-
|
|
9305
|
-
|
|
9306
|
-
|
|
9307
|
-
|
|
9308
|
-
|
|
9309
|
-
|
|
9310
|
-
|
|
9311
|
-
|
|
9312
|
-
|
|
9313
|
-
}
|
|
9314
|
-
clearBuffer() {
|
|
9315
|
-
this.buffer = [""];
|
|
9316
|
-
this.row = 0;
|
|
9317
|
-
this.col = 0;
|
|
9318
|
-
this.historyIndex = -1;
|
|
9319
|
-
this.queueIndex = -1;
|
|
9320
|
-
this.savedDraft = null;
|
|
9321
|
-
this.savedAttachments = null;
|
|
9322
|
-
this.historySearch = null;
|
|
9323
|
-
this.attachments = [];
|
|
9324
|
-
}
|
|
9325
|
-
insertChar(ch) {
|
|
9326
|
-
if (ch.length === 0) {
|
|
9170
|
+
const before = composer.state();
|
|
9171
|
+
let event = null;
|
|
9172
|
+
if (data?.isCharacter) {
|
|
9173
|
+
event = { type: "char", ch: name };
|
|
9174
|
+
} else {
|
|
9175
|
+
const mapped = mapKeyName(name);
|
|
9176
|
+
if (mapped !== null) {
|
|
9177
|
+
event = { type: "key", name: mapped };
|
|
9178
|
+
}
|
|
9179
|
+
}
|
|
9180
|
+
if (event === null) {
|
|
9181
|
+
placeComposerCursor();
|
|
9327
9182
|
return;
|
|
9328
9183
|
}
|
|
9329
|
-
|
|
9330
|
-
|
|
9184
|
+
composer.feed(event);
|
|
9185
|
+
const after = composer.state();
|
|
9186
|
+
const unchanged = before.buffer.length === after.buffer.length && before.buffer.every((line, i) => line === after.buffer[i]) && before.row === after.row && before.col === after.col;
|
|
9187
|
+
if (unchanged) {
|
|
9188
|
+
placeComposerCursor();
|
|
9331
9189
|
return;
|
|
9332
9190
|
}
|
|
9333
|
-
const
|
|
9334
|
-
|
|
9335
|
-
|
|
9336
|
-
|
|
9337
|
-
|
|
9338
|
-
|
|
9339
|
-
if (
|
|
9340
|
-
|
|
9191
|
+
const newVisualRows = computePromptVisualRows(after.buffer, composerRoom);
|
|
9192
|
+
const newLayout = computePromptLayout(
|
|
9193
|
+
newVisualRows,
|
|
9194
|
+
after,
|
|
9195
|
+
PICKER_COMPOSER_MAX_ROWS
|
|
9196
|
+
);
|
|
9197
|
+
if (newLayout.rendered !== composerRows) {
|
|
9198
|
+
renderFromScratch();
|
|
9341
9199
|
return;
|
|
9342
9200
|
}
|
|
9343
|
-
|
|
9344
|
-
|
|
9345
|
-
const after = cur.slice(this.col);
|
|
9346
|
-
const first = lines[0] ?? "";
|
|
9347
|
-
const last = lines[lines.length - 1] ?? "";
|
|
9348
|
-
const middle = lines.slice(1, -1);
|
|
9349
|
-
this.setCurrentLine(before + first);
|
|
9350
|
-
const newRows = [...middle, last + after];
|
|
9351
|
-
this.buffer.splice(this.row + 1, 0, ...newRows);
|
|
9352
|
-
this.row += lines.length - 1;
|
|
9353
|
-
this.col = last.length;
|
|
9201
|
+
repaintComposerBody();
|
|
9202
|
+
return;
|
|
9354
9203
|
}
|
|
9355
|
-
|
|
9356
|
-
|
|
9357
|
-
|
|
9358
|
-
|
|
9359
|
-
this.setCurrentLine(before);
|
|
9360
|
-
this.buffer.splice(this.row + 1, 0, after);
|
|
9361
|
-
this.row += 1;
|
|
9362
|
-
this.col = 0;
|
|
9204
|
+
if (!searchActive && data?.isCharacter && name === "?") {
|
|
9205
|
+
mode = "help";
|
|
9206
|
+
renderHelp();
|
|
9207
|
+
return;
|
|
9363
9208
|
}
|
|
9364
|
-
|
|
9365
|
-
if (
|
|
9366
|
-
|
|
9367
|
-
|
|
9368
|
-
|
|
9209
|
+
if (searchActive) {
|
|
9210
|
+
if (data?.isCharacter) {
|
|
9211
|
+
searchTerm += name;
|
|
9212
|
+
applyFilter();
|
|
9213
|
+
renderFromScratch();
|
|
9369
9214
|
return;
|
|
9370
9215
|
}
|
|
9371
|
-
if (
|
|
9216
|
+
if (name === "BACKSPACE") {
|
|
9217
|
+
if (searchTerm.length > 0) {
|
|
9218
|
+
searchTerm = searchTerm.slice(0, -1);
|
|
9219
|
+
applyFilter();
|
|
9220
|
+
renderFromScratch();
|
|
9221
|
+
} else {
|
|
9222
|
+
searchActive = false;
|
|
9223
|
+
applyFilter();
|
|
9224
|
+
renderFromScratch();
|
|
9225
|
+
}
|
|
9226
|
+
return;
|
|
9227
|
+
}
|
|
9228
|
+
if (name === "ESCAPE" || name === "CTRL_C") {
|
|
9229
|
+
searchActive = false;
|
|
9230
|
+
searchTerm = "";
|
|
9231
|
+
applyFilter();
|
|
9232
|
+
renderFromScratch();
|
|
9372
9233
|
return;
|
|
9373
9234
|
}
|
|
9374
|
-
const prev = this.buffer[this.row - 1] ?? "";
|
|
9375
|
-
const cur = this.currentLine();
|
|
9376
|
-
this.buffer.splice(this.row, 1);
|
|
9377
|
-
this.row -= 1;
|
|
9378
|
-
this.col = prev.length;
|
|
9379
|
-
this.buffer[this.row] = prev + cur;
|
|
9380
9235
|
}
|
|
9381
|
-
|
|
9382
|
-
|
|
9383
|
-
|
|
9384
|
-
|
|
9236
|
+
if (data?.isCharacter) {
|
|
9237
|
+
if (name === "/") {
|
|
9238
|
+
searchActive = true;
|
|
9239
|
+
searchTerm = "";
|
|
9240
|
+
applyFilter();
|
|
9241
|
+
renderFromScratch();
|
|
9385
9242
|
return;
|
|
9386
9243
|
}
|
|
9387
|
-
if (
|
|
9388
|
-
|
|
9389
|
-
|
|
9390
|
-
this.setCurrentLine(line + next);
|
|
9244
|
+
if (name === "n" || name === "N") {
|
|
9245
|
+
move(1);
|
|
9246
|
+
return;
|
|
9391
9247
|
}
|
|
9392
|
-
|
|
9393
|
-
|
|
9394
|
-
// above:
|
|
9395
|
-
// - If the current line is empty, collapse it (kill just the
|
|
9396
|
-
// newline) so the cursor lands at the end of the previous line.
|
|
9397
|
-
// Don't slurp that line's contents.
|
|
9398
|
-
// - Otherwise, kill the previous line entirely + the joining
|
|
9399
|
-
// newline, so ^U from the start of a non-empty line walks up
|
|
9400
|
-
// line-by-line.
|
|
9401
|
-
// Single-line behavior is unchanged.
|
|
9402
|
-
killLine() {
|
|
9403
|
-
if (this.col > 0) {
|
|
9404
|
-
const line = this.currentLine();
|
|
9405
|
-
this.killBuffer = line.slice(0, this.col);
|
|
9406
|
-
this.setCurrentLine(line.slice(this.col));
|
|
9407
|
-
this.col = 0;
|
|
9248
|
+
if (name === "p" || name === "P") {
|
|
9249
|
+
move(-1);
|
|
9408
9250
|
return;
|
|
9409
9251
|
}
|
|
9410
|
-
if (
|
|
9252
|
+
if (name === "c" || name === "C") {
|
|
9253
|
+
cleanup();
|
|
9254
|
+
resolve6({ kind: "new" });
|
|
9411
9255
|
return;
|
|
9412
9256
|
}
|
|
9413
|
-
if (
|
|
9414
|
-
|
|
9415
|
-
|
|
9416
|
-
this.row -= 1;
|
|
9417
|
-
this.col = this.currentLine().length;
|
|
9257
|
+
if (name === "q" || name === "Q") {
|
|
9258
|
+
cleanup();
|
|
9259
|
+
resolve6({ kind: "abort" });
|
|
9418
9260
|
return;
|
|
9419
9261
|
}
|
|
9420
|
-
|
|
9421
|
-
|
|
9422
|
-
|
|
9423
|
-
|
|
9424
|
-
|
|
9425
|
-
|
|
9426
|
-
|
|
9427
|
-
|
|
9428
|
-
|
|
9429
|
-
|
|
9430
|
-
|
|
9431
|
-
|
|
9432
|
-
// Single-line behavior is unchanged.
|
|
9433
|
-
killToEnd() {
|
|
9434
|
-
const line = this.currentLine();
|
|
9435
|
-
if (this.col < line.length) {
|
|
9436
|
-
this.killBuffer = line.slice(this.col);
|
|
9437
|
-
this.setCurrentLine(line.slice(0, this.col));
|
|
9262
|
+
if (name === "o" || name === "O") {
|
|
9263
|
+
const keepId = selectedIdx > 0 ? visible[selectedIdx - 1]?.sessionId : void 0;
|
|
9264
|
+
cwdOnly = !cwdOnly;
|
|
9265
|
+
applyFilter();
|
|
9266
|
+
if (keepId !== void 0) {
|
|
9267
|
+
const idx = visible.findIndex((s) => s.sessionId === keepId);
|
|
9268
|
+
if (idx >= 0) {
|
|
9269
|
+
selectedIdx = idx + 1;
|
|
9270
|
+
adjustScroll();
|
|
9271
|
+
}
|
|
9272
|
+
}
|
|
9273
|
+
renderFromScratch();
|
|
9438
9274
|
return;
|
|
9439
9275
|
}
|
|
9440
|
-
if (
|
|
9276
|
+
if (name === "h" || name === "H") {
|
|
9277
|
+
const keepId = selectedIdx > 0 ? visible[selectedIdx - 1]?.sessionId : void 0;
|
|
9278
|
+
hostFilter = nextHostFilter(hostFilter, allSessions);
|
|
9279
|
+
applyFilter();
|
|
9280
|
+
if (keepId !== void 0) {
|
|
9281
|
+
const idx = visible.findIndex((s) => s.sessionId === keepId);
|
|
9282
|
+
if (idx >= 0) {
|
|
9283
|
+
selectedIdx = idx + 1;
|
|
9284
|
+
adjustScroll();
|
|
9285
|
+
}
|
|
9286
|
+
}
|
|
9287
|
+
renderFromScratch();
|
|
9441
9288
|
return;
|
|
9442
9289
|
}
|
|
9443
|
-
if (
|
|
9444
|
-
|
|
9445
|
-
|
|
9290
|
+
if (name === "r" || name === "R") {
|
|
9291
|
+
const currentId = selectedIdx > 0 ? visible[selectedIdx - 1]?.sessionId : void 0;
|
|
9292
|
+
void refresh(currentId);
|
|
9446
9293
|
return;
|
|
9447
9294
|
}
|
|
9448
|
-
|
|
9449
|
-
|
|
9450
|
-
|
|
9451
|
-
|
|
9452
|
-
|
|
9453
|
-
|
|
9454
|
-
|
|
9455
|
-
|
|
9295
|
+
if ((name === "v" || name === "V") && selectedIdx > 0) {
|
|
9296
|
+
const session = visible[selectedIdx - 1];
|
|
9297
|
+
if (!session) {
|
|
9298
|
+
return;
|
|
9299
|
+
}
|
|
9300
|
+
cleanup();
|
|
9301
|
+
const result = {
|
|
9302
|
+
kind: "attach",
|
|
9303
|
+
sessionId: session.sessionId,
|
|
9304
|
+
readonly: true
|
|
9305
|
+
};
|
|
9306
|
+
if (session.agentId !== void 0) {
|
|
9307
|
+
result.agentId = session.agentId;
|
|
9308
|
+
}
|
|
9309
|
+
resolve6(result);
|
|
9456
9310
|
return;
|
|
9457
9311
|
}
|
|
9458
|
-
|
|
9459
|
-
|
|
9460
|
-
|
|
9312
|
+
if ((name === "k" || name === "K") && selectedIdx > 0) {
|
|
9313
|
+
const session = visible[selectedIdx - 1];
|
|
9314
|
+
if (!session) {
|
|
9315
|
+
return;
|
|
9316
|
+
}
|
|
9317
|
+
pendingAction = {
|
|
9318
|
+
sessionId: session.sessionId,
|
|
9319
|
+
cwd: session.cwd,
|
|
9320
|
+
status: session.status
|
|
9321
|
+
};
|
|
9322
|
+
mode = "confirm-kill";
|
|
9323
|
+
paintIndicator();
|
|
9324
|
+
return;
|
|
9461
9325
|
}
|
|
9462
|
-
|
|
9463
|
-
|
|
9326
|
+
if (name === "t" && selectedIdx > 0) {
|
|
9327
|
+
const session = visible[selectedIdx - 1];
|
|
9328
|
+
if (!session) {
|
|
9329
|
+
return;
|
|
9330
|
+
}
|
|
9331
|
+
pendingAction = {
|
|
9332
|
+
sessionId: session.sessionId,
|
|
9333
|
+
cwd: session.cwd,
|
|
9334
|
+
status: session.status
|
|
9335
|
+
};
|
|
9336
|
+
renameBuffer = session.title ?? "";
|
|
9337
|
+
mode = "rename";
|
|
9338
|
+
paintIndicator();
|
|
9339
|
+
return;
|
|
9464
9340
|
}
|
|
9465
|
-
|
|
9466
|
-
|
|
9467
|
-
|
|
9341
|
+
if (name === "T" && selectedIdx > 0) {
|
|
9342
|
+
const session = visible[selectedIdx - 1];
|
|
9343
|
+
if (!session || session.status !== "live") {
|
|
9344
|
+
return;
|
|
9345
|
+
}
|
|
9346
|
+
void performRegen({ sessionId: session.sessionId });
|
|
9347
|
+
return;
|
|
9468
9348
|
}
|
|
9469
|
-
|
|
9470
|
-
|
|
9471
|
-
|
|
9472
|
-
|
|
9473
|
-
|
|
9349
|
+
if ((name === "d" || name === "D") && selectedIdx > 0) {
|
|
9350
|
+
const session = visible[selectedIdx - 1];
|
|
9351
|
+
if (!session) {
|
|
9352
|
+
return;
|
|
9353
|
+
}
|
|
9354
|
+
if (session.status === "live") {
|
|
9355
|
+
transientStatus = "session is live \u2014 press k to kill it first";
|
|
9356
|
+
paintIndicator();
|
|
9357
|
+
return;
|
|
9358
|
+
}
|
|
9359
|
+
pendingAction = {
|
|
9360
|
+
sessionId: session.sessionId,
|
|
9361
|
+
cwd: session.cwd,
|
|
9362
|
+
status: session.status
|
|
9363
|
+
};
|
|
9364
|
+
mode = "confirm-delete";
|
|
9365
|
+
paintIndicator();
|
|
9474
9366
|
return;
|
|
9475
9367
|
}
|
|
9476
|
-
|
|
9368
|
+
return;
|
|
9477
9369
|
}
|
|
9478
|
-
|
|
9479
|
-
|
|
9480
|
-
|
|
9370
|
+
switch (name) {
|
|
9371
|
+
case "UP":
|
|
9372
|
+
case "SHIFT_TAB":
|
|
9373
|
+
move(-1);
|
|
9374
|
+
return;
|
|
9375
|
+
case "DOWN":
|
|
9376
|
+
case "TAB":
|
|
9377
|
+
move(1);
|
|
9378
|
+
return;
|
|
9379
|
+
case "PAGE_UP":
|
|
9380
|
+
move(-viewportSize);
|
|
9381
|
+
return;
|
|
9382
|
+
case "PAGE_DOWN":
|
|
9383
|
+
move(viewportSize);
|
|
9384
|
+
return;
|
|
9385
|
+
case "HOME":
|
|
9386
|
+
move(1 - selectedIdx);
|
|
9387
|
+
return;
|
|
9388
|
+
case "END":
|
|
9389
|
+
move(total);
|
|
9390
|
+
return;
|
|
9391
|
+
case "ENTER":
|
|
9392
|
+
case "KP_ENTER": {
|
|
9393
|
+
cleanup();
|
|
9394
|
+
if (selectedIdx === 0) {
|
|
9395
|
+
resolve6({ kind: "new" });
|
|
9396
|
+
return;
|
|
9397
|
+
}
|
|
9398
|
+
const session = visible[selectedIdx - 1];
|
|
9399
|
+
if (!session) {
|
|
9400
|
+
resolve6({ kind: "abort" });
|
|
9401
|
+
return;
|
|
9402
|
+
}
|
|
9403
|
+
const result = {
|
|
9404
|
+
kind: "attach",
|
|
9405
|
+
sessionId: session.sessionId
|
|
9406
|
+
};
|
|
9407
|
+
if (session.agentId !== void 0) {
|
|
9408
|
+
result.agentId = session.agentId;
|
|
9409
|
+
}
|
|
9410
|
+
resolve6(result);
|
|
9481
9411
|
return;
|
|
9482
9412
|
}
|
|
9483
|
-
|
|
9484
|
-
|
|
9485
|
-
|
|
9486
|
-
|
|
9413
|
+
case "ESCAPE":
|
|
9414
|
+
case "CTRL_C":
|
|
9415
|
+
case "CTRL_D":
|
|
9416
|
+
cleanup();
|
|
9417
|
+
resolve6({ kind: "abort" });
|
|
9418
|
+
return;
|
|
9419
|
+
}
|
|
9420
|
+
};
|
|
9421
|
+
term.grabInput({});
|
|
9422
|
+
term.on("key", onKey);
|
|
9423
|
+
term.on("resize", onResize);
|
|
9424
|
+
});
|
|
9425
|
+
}
|
|
9426
|
+
function readTermHeight(term) {
|
|
9427
|
+
return term.height ?? 24;
|
|
9428
|
+
}
|
|
9429
|
+
function readTermWidth(term) {
|
|
9430
|
+
return term.width ?? 80;
|
|
9431
|
+
}
|
|
9432
|
+
function formatComposerTitle(cwd, maxWidth) {
|
|
9433
|
+
const prefix = "Create new session in ";
|
|
9434
|
+
const budget = Math.max(1, maxWidth - prefix.length);
|
|
9435
|
+
return prefix + truncateMiddle(shortenHomePath(cwd), budget);
|
|
9436
|
+
}
|
|
9437
|
+
function filterByHost(sessions, hostFilter) {
|
|
9438
|
+
if (hostFilter === "__all") {
|
|
9439
|
+
return sessions;
|
|
9440
|
+
}
|
|
9441
|
+
if (hostFilter === "__local") {
|
|
9442
|
+
return sessions.filter(
|
|
9443
|
+
(s) => !s.importedFromMachine || !!s.upstreamSessionId
|
|
9444
|
+
);
|
|
9445
|
+
}
|
|
9446
|
+
return sessions.filter(
|
|
9447
|
+
(s) => s.importedFromMachine === hostFilter && !s.upstreamSessionId
|
|
9448
|
+
);
|
|
9449
|
+
}
|
|
9450
|
+
function nextHostFilter(current, sessions) {
|
|
9451
|
+
const hosts = /* @__PURE__ */ new Set();
|
|
9452
|
+
for (const s of sessions) {
|
|
9453
|
+
if (s.importedFromMachine && !s.upstreamSessionId) {
|
|
9454
|
+
hosts.add(s.importedFromMachine);
|
|
9455
|
+
}
|
|
9456
|
+
}
|
|
9457
|
+
const ordered = ["__local", ...[...hosts].sort(), "__all"];
|
|
9458
|
+
const idx = ordered.indexOf(current);
|
|
9459
|
+
if (idx === -1) {
|
|
9460
|
+
return "__local";
|
|
9461
|
+
}
|
|
9462
|
+
return ordered[(idx + 1) % ordered.length] ?? "__local";
|
|
9463
|
+
}
|
|
9464
|
+
function matchesSearch(s, term) {
|
|
9465
|
+
if (term.length === 0) {
|
|
9466
|
+
return true;
|
|
9467
|
+
}
|
|
9468
|
+
const t = term.toLowerCase();
|
|
9469
|
+
const haystacks = [
|
|
9470
|
+
stripHydraSessionPrefix(s.sessionId),
|
|
9471
|
+
s.upstreamSessionId ?? "",
|
|
9472
|
+
s.agentId ?? "",
|
|
9473
|
+
s.title ?? "",
|
|
9474
|
+
s.cwd,
|
|
9475
|
+
shortenHomePath(s.cwd)
|
|
9476
|
+
];
|
|
9477
|
+
for (const h of haystacks) {
|
|
9478
|
+
if (h.toLowerCase().includes(t)) {
|
|
9479
|
+
return true;
|
|
9480
|
+
}
|
|
9481
|
+
}
|
|
9482
|
+
return false;
|
|
9483
|
+
}
|
|
9484
|
+
var ROW_PREFIX_WIDTH, PICKER_COMPOSER_MAX_ROWS, BOX_HORIZONTAL_PAD, HELP_KEYS_WIDTH, HELP_ENTRIES;
|
|
9485
|
+
var init_picker = __esm({
|
|
9486
|
+
"src/tui/picker.ts"() {
|
|
9487
|
+
"use strict";
|
|
9488
|
+
init_session_row();
|
|
9489
|
+
init_paths();
|
|
9490
|
+
init_session();
|
|
9491
|
+
init_discovery();
|
|
9492
|
+
init_input();
|
|
9493
|
+
init_screen();
|
|
9494
|
+
ROW_PREFIX_WIDTH = 2;
|
|
9495
|
+
PICKER_COMPOSER_MAX_ROWS = 4;
|
|
9496
|
+
BOX_HORIZONTAL_PAD = 4;
|
|
9497
|
+
HELP_KEYS_WIDTH = 20;
|
|
9498
|
+
HELP_ENTRIES = [
|
|
9499
|
+
["Composer", "type prompt for new session; Enter creates + submits"],
|
|
9500
|
+
["\u2193 from composer", "drop focus into session list"],
|
|
9501
|
+
null,
|
|
9502
|
+
["\u2191 / \u2193 or n / p", "navigate sessions"],
|
|
9503
|
+
["PgUp / PgDn", "page up / page down"],
|
|
9504
|
+
["Home / End", "first / last"],
|
|
9505
|
+
["Enter", "open selected session"],
|
|
9506
|
+
["v", "view-only (open transcript without spawning the agent)"],
|
|
9507
|
+
null,
|
|
9508
|
+
["/", "search sessions"],
|
|
9509
|
+
["o", "toggle cwd-only filter"],
|
|
9510
|
+
["h", "cycle host filter (local / <peer> / all)"],
|
|
9511
|
+
["r", "refresh from daemon"],
|
|
9512
|
+
null,
|
|
9513
|
+
["k", "kill the selected live session"],
|
|
9514
|
+
["d", "delete the selected cold session"],
|
|
9515
|
+
["t", "retitle the selected session"],
|
|
9516
|
+
["T", "regenerate title via agent (live session)"],
|
|
9517
|
+
null,
|
|
9518
|
+
["?", "toggle this help"],
|
|
9519
|
+
["q / Esc / ^C / ^D", "quit picker (detach)"]
|
|
9520
|
+
];
|
|
9521
|
+
}
|
|
9522
|
+
});
|
|
9523
|
+
|
|
9524
|
+
// src/core/cwd.ts
|
|
9525
|
+
import * as fs19 from "fs/promises";
|
|
9526
|
+
import * as path14 from "path";
|
|
9527
|
+
async function validateLocalCwd(input) {
|
|
9528
|
+
const trimmed = input.trim();
|
|
9529
|
+
if (trimmed.length === 0) {
|
|
9530
|
+
return { ok: false, reason: "path is empty" };
|
|
9531
|
+
}
|
|
9532
|
+
const resolved = path14.resolve(expandHome(trimmed));
|
|
9533
|
+
let stat5;
|
|
9534
|
+
try {
|
|
9535
|
+
stat5 = await fs19.stat(resolved);
|
|
9536
|
+
} catch {
|
|
9537
|
+
return { ok: false, reason: `${resolved} does not exist` };
|
|
9538
|
+
}
|
|
9539
|
+
if (!stat5.isDirectory()) {
|
|
9540
|
+
return { ok: false, reason: `${resolved} is not a directory` };
|
|
9541
|
+
}
|
|
9542
|
+
return { ok: true, path: resolved };
|
|
9543
|
+
}
|
|
9544
|
+
var init_cwd = __esm({
|
|
9545
|
+
"src/core/cwd.ts"() {
|
|
9546
|
+
"use strict";
|
|
9547
|
+
init_config();
|
|
9548
|
+
}
|
|
9549
|
+
});
|
|
9550
|
+
|
|
9551
|
+
// src/tui/prompt-utils.ts
|
|
9552
|
+
function resetTerminalModes() {
|
|
9553
|
+
process.stdout.write("\x1B[<u");
|
|
9554
|
+
process.stdout.write("\x1B[?2004l");
|
|
9555
|
+
process.stdout.write("\x1B[>4;0m");
|
|
9556
|
+
process.stdout.write("\x1B[>5;0m");
|
|
9557
|
+
process.stdout.write("\x1B[?1000l");
|
|
9558
|
+
process.stdout.write("\x1B[?1002l");
|
|
9559
|
+
process.stdout.write("\x1B[?1006l");
|
|
9560
|
+
process.stdout.write("\x1B[?1l");
|
|
9561
|
+
process.stdout.write("\x1B>");
|
|
9562
|
+
}
|
|
9563
|
+
function readTermWidth2(term) {
|
|
9564
|
+
return term.width ?? 80;
|
|
9565
|
+
}
|
|
9566
|
+
function readTermHeight2(term) {
|
|
9567
|
+
return term.height ?? 24;
|
|
9568
|
+
}
|
|
9569
|
+
function drawBox(term, opts) {
|
|
9570
|
+
const termW = readTermWidth2(term);
|
|
9571
|
+
const termH = readTermHeight2(term);
|
|
9572
|
+
const desiredContentW = opts.contentWidth ?? MAX_BOX_WIDTH;
|
|
9573
|
+
const maxContentW = Math.max(10, Math.min(MAX_BOX_WIDTH, termW - 4));
|
|
9574
|
+
const contentW = Math.min(desiredContentW, maxContentW);
|
|
9575
|
+
const w = contentW + 2;
|
|
9576
|
+
const contentH = Math.max(1, Math.min(opts.contentHeight, termH - 4));
|
|
9577
|
+
const h = contentH + 2;
|
|
9578
|
+
const x = Math.max(1, Math.floor((termW - w) / 2) + 1);
|
|
9579
|
+
const y = Math.max(1, Math.floor((termH - h) / 2) + 1);
|
|
9580
|
+
term.moveTo(1, 1).eraseDisplayBelow();
|
|
9581
|
+
const topInner = HORIZ.repeat(w - 2);
|
|
9582
|
+
const top = renderTitleStrip(topInner, opts.title);
|
|
9583
|
+
term.moveTo(x, y);
|
|
9584
|
+
term.dim.noFormat(TL);
|
|
9585
|
+
paintTopStrip(term, top);
|
|
9586
|
+
term.dim.noFormat(TR);
|
|
9587
|
+
for (let row = 1; row <= contentH; row++) {
|
|
9588
|
+
term.moveTo(x, y + row);
|
|
9589
|
+
term.dim.noFormat(VERT);
|
|
9590
|
+
term.moveTo(x + w - 1, y + row);
|
|
9591
|
+
term.dim.noFormat(VERT);
|
|
9592
|
+
}
|
|
9593
|
+
term.moveTo(x, y + h - 1);
|
|
9594
|
+
term.dim.noFormat(BL + HORIZ.repeat(w - 2) + BR);
|
|
9595
|
+
return {
|
|
9596
|
+
x,
|
|
9597
|
+
y,
|
|
9598
|
+
w,
|
|
9599
|
+
h,
|
|
9600
|
+
contentX: x + 1,
|
|
9601
|
+
contentY: y + 1,
|
|
9602
|
+
contentW,
|
|
9603
|
+
contentH
|
|
9604
|
+
};
|
|
9605
|
+
}
|
|
9606
|
+
function renderTitleStrip(innerDashes, title) {
|
|
9607
|
+
if (!title) {
|
|
9608
|
+
return { dashes: innerDashes };
|
|
9609
|
+
}
|
|
9610
|
+
const chip = ` ${title} `;
|
|
9611
|
+
if (chip.length + 4 > innerDashes.length) {
|
|
9612
|
+
return { dashes: innerDashes };
|
|
9613
|
+
}
|
|
9614
|
+
const offset = 2;
|
|
9615
|
+
const dashes = innerDashes.slice(0, offset) + " ".repeat(chip.length) + innerDashes.slice(offset + chip.length);
|
|
9616
|
+
return { dashes, title: { offset, text: chip } };
|
|
9617
|
+
}
|
|
9618
|
+
function paintTopStrip(term, strip) {
|
|
9619
|
+
if (!strip.title) {
|
|
9620
|
+
term.dim.noFormat(strip.dashes);
|
|
9621
|
+
return;
|
|
9622
|
+
}
|
|
9623
|
+
term.dim.noFormat(strip.dashes.slice(0, strip.title.offset));
|
|
9624
|
+
term.brightCyan.noFormat(strip.title.text);
|
|
9625
|
+
term.dim.noFormat(strip.dashes.slice(strip.title.offset + strip.title.text.length));
|
|
9626
|
+
}
|
|
9627
|
+
var MAX_BOX_WIDTH, HORIZ, VERT, TL, TR, BL, BR;
|
|
9628
|
+
var init_prompt_utils = __esm({
|
|
9629
|
+
"src/tui/prompt-utils.ts"() {
|
|
9630
|
+
"use strict";
|
|
9631
|
+
MAX_BOX_WIDTH = 64;
|
|
9632
|
+
HORIZ = "\u2500";
|
|
9633
|
+
VERT = "\u2502";
|
|
9634
|
+
TL = "\u250C";
|
|
9635
|
+
TR = "\u2510";
|
|
9636
|
+
BL = "\u2514";
|
|
9637
|
+
BR = "\u2518";
|
|
9638
|
+
}
|
|
9639
|
+
});
|
|
9640
|
+
|
|
9641
|
+
// src/tui/import-cwd-prompt.ts
|
|
9642
|
+
import * as os5 from "os";
|
|
9643
|
+
async function promptForImportCwd(term, session, opts = {}) {
|
|
9644
|
+
const defaultCwd = opts.defaultCwd ?? os5.homedir();
|
|
9645
|
+
resetTerminalModes();
|
|
9646
|
+
const shortId2 = stripHydraSessionPrefix(session.sessionId);
|
|
9647
|
+
const fromMachine = session.importedFromMachine ?? "another machine";
|
|
9648
|
+
const originalCwd = shortenHomePath(session.cwd);
|
|
9649
|
+
let buffer = defaultCwd;
|
|
9650
|
+
let errorLine = null;
|
|
9651
|
+
let busy = false;
|
|
9652
|
+
let layout = null;
|
|
9653
|
+
const render = () => {
|
|
9654
|
+
const contentHeight = 9;
|
|
9655
|
+
layout = drawBox(term, {
|
|
9656
|
+
contentHeight,
|
|
9657
|
+
title: "Run locally \u2014 choose cwd"
|
|
9658
|
+
});
|
|
9659
|
+
const innerW = layout.contentW;
|
|
9660
|
+
const headerRows = [
|
|
9661
|
+
{ label: "session: ", value: shortId2 },
|
|
9662
|
+
{ label: "from: ", value: fromMachine },
|
|
9663
|
+
{ label: "cwd: ", value: originalCwd }
|
|
9664
|
+
];
|
|
9665
|
+
let row = 0;
|
|
9666
|
+
for (const hr of headerRows) {
|
|
9667
|
+
term.moveTo(layout.contentX, layout.contentY + row);
|
|
9668
|
+
term.dim.noFormat(` ${hr.label}`);
|
|
9669
|
+
term.noFormat(truncate2(hr.value, innerW - hr.label.length - 2));
|
|
9670
|
+
row++;
|
|
9671
|
+
}
|
|
9672
|
+
row++;
|
|
9673
|
+
term.moveTo(layout.contentX, layout.contentY + row);
|
|
9674
|
+
term.noFormat(" Pick a local cwd for this session:");
|
|
9675
|
+
row += 2;
|
|
9676
|
+
paintInputRow(row);
|
|
9677
|
+
row += 2;
|
|
9678
|
+
if (errorLine !== null) {
|
|
9679
|
+
term.moveTo(layout.contentX, layout.contentY + row);
|
|
9680
|
+
term.red.noFormat(` ${truncate2(errorLine, innerW - 2)}`);
|
|
9681
|
+
} else {
|
|
9682
|
+
term.moveTo(layout.contentX, layout.contentY + row);
|
|
9683
|
+
term.dim.noFormat(
|
|
9684
|
+
" Enter accept \xB7 Esc back \xB7 ^U clear \xB7 ^W kill word"
|
|
9685
|
+
);
|
|
9686
|
+
}
|
|
9687
|
+
};
|
|
9688
|
+
const inputRow = () => 7;
|
|
9689
|
+
const paintInputRow = (rowOffset) => {
|
|
9690
|
+
if (!layout) {
|
|
9691
|
+
return;
|
|
9692
|
+
}
|
|
9693
|
+
const r = rowOffset ?? inputRow();
|
|
9694
|
+
term.moveTo(layout.contentX, layout.contentY + r).eraseLineAfter();
|
|
9695
|
+
term.moveTo(layout.x + layout.w - 1, layout.contentY + r);
|
|
9696
|
+
term.dim.noFormat("\u2502");
|
|
9697
|
+
term.moveTo(layout.contentX, layout.contentY + r);
|
|
9698
|
+
term.bold.noFormat(" cwd: ");
|
|
9699
|
+
const available = layout.contentW - " cwd: ".length - 2;
|
|
9700
|
+
term.noFormat(truncateLeft(buffer, available));
|
|
9701
|
+
if (!busy) {
|
|
9702
|
+
term.bgWhite(" ");
|
|
9703
|
+
}
|
|
9704
|
+
};
|
|
9705
|
+
const repaintInput = () => {
|
|
9706
|
+
paintInputRow();
|
|
9707
|
+
if (!layout) {
|
|
9708
|
+
return;
|
|
9709
|
+
}
|
|
9710
|
+
const errRow = inputRow() + 2;
|
|
9711
|
+
term.moveTo(layout.contentX, layout.contentY + errRow).eraseLineAfter();
|
|
9712
|
+
term.moveTo(layout.x + layout.w - 1, layout.contentY + errRow);
|
|
9713
|
+
term.dim.noFormat("\u2502");
|
|
9714
|
+
term.moveTo(layout.contentX, layout.contentY + errRow);
|
|
9715
|
+
if (errorLine !== null) {
|
|
9716
|
+
term.red.noFormat(` ${truncate2(errorLine, layout.contentW - 2)}`);
|
|
9717
|
+
} else {
|
|
9718
|
+
term.dim.noFormat(
|
|
9719
|
+
" Enter accept \xB7 Esc back \xB7 ^U clear \xB7 ^W kill word"
|
|
9720
|
+
);
|
|
9721
|
+
}
|
|
9722
|
+
};
|
|
9723
|
+
render();
|
|
9724
|
+
return await new Promise((resolve6) => {
|
|
9725
|
+
let resolved = false;
|
|
9726
|
+
const cleanup = () => {
|
|
9727
|
+
if (resolved) {
|
|
9728
|
+
return;
|
|
9487
9729
|
}
|
|
9488
|
-
|
|
9489
|
-
|
|
9490
|
-
|
|
9491
|
-
|
|
9492
|
-
|
|
9493
|
-
|
|
9494
|
-
|
|
9495
|
-
|
|
9496
|
-
|
|
9730
|
+
resolved = true;
|
|
9731
|
+
term.off("key", onKey);
|
|
9732
|
+
term.off("resize", onResize);
|
|
9733
|
+
term.grabInput(false);
|
|
9734
|
+
term.hideCursor(false);
|
|
9735
|
+
term.moveTo(1, 1).eraseDisplayBelow();
|
|
9736
|
+
};
|
|
9737
|
+
const finish = (value) => {
|
|
9738
|
+
cleanup();
|
|
9739
|
+
resolve6(value);
|
|
9740
|
+
};
|
|
9741
|
+
const onResize = () => {
|
|
9742
|
+
if (resolved) {
|
|
9743
|
+
return;
|
|
9497
9744
|
}
|
|
9498
|
-
|
|
9499
|
-
|
|
9500
|
-
|
|
9501
|
-
|
|
9502
|
-
|
|
9503
|
-
this.row -= 1;
|
|
9504
|
-
this.col = this.currentLine().length;
|
|
9505
|
-
return;
|
|
9506
|
-
}
|
|
9507
|
-
const line = this.currentLine();
|
|
9508
|
-
let i = this.col;
|
|
9509
|
-
while (i > 0 && /\s/.test(line[i - 1] ?? "")) {
|
|
9510
|
-
i -= 1;
|
|
9511
|
-
}
|
|
9512
|
-
while (i > 0 && !/\s/.test(line[i - 1] ?? "")) {
|
|
9513
|
-
i -= 1;
|
|
9514
|
-
}
|
|
9515
|
-
this.col = i;
|
|
9745
|
+
render();
|
|
9746
|
+
};
|
|
9747
|
+
const onKey = (name, _matches, data) => {
|
|
9748
|
+
if (busy) {
|
|
9749
|
+
return;
|
|
9516
9750
|
}
|
|
9517
|
-
|
|
9518
|
-
const
|
|
9519
|
-
|
|
9520
|
-
|
|
9751
|
+
if (name === "ENTER" || name === "KP_ENTER") {
|
|
9752
|
+
const candidate = buffer;
|
|
9753
|
+
busy = true;
|
|
9754
|
+
errorLine = null;
|
|
9755
|
+
repaintInput();
|
|
9756
|
+
void validateLocalCwd(candidate).then((result) => {
|
|
9757
|
+
busy = false;
|
|
9758
|
+
if (result.ok) {
|
|
9759
|
+
finish({ kind: "ok", path: result.path });
|
|
9521
9760
|
return;
|
|
9522
9761
|
}
|
|
9523
|
-
|
|
9524
|
-
|
|
9525
|
-
|
|
9526
|
-
|
|
9527
|
-
let i = this.col;
|
|
9528
|
-
while (i < line.length && /\s/.test(line[i] ?? "")) {
|
|
9529
|
-
i += 1;
|
|
9530
|
-
}
|
|
9531
|
-
while (i < line.length && !/\s/.test(line[i] ?? "")) {
|
|
9532
|
-
i += 1;
|
|
9533
|
-
}
|
|
9534
|
-
this.col = i;
|
|
9535
|
-
}
|
|
9536
|
-
// Up walks the navigation stack from newest to oldest: pending queue
|
|
9537
|
-
// items first (so the user can edit something they just enqueued),
|
|
9538
|
-
// then prompt history. Cursor movement within a multi-line buffer
|
|
9539
|
-
// takes priority when not already navigating.
|
|
9540
|
-
handleUp() {
|
|
9541
|
-
if (this.row > 0) {
|
|
9542
|
-
this.row -= 1;
|
|
9543
|
-
this.col = Math.min(this.col, this.currentLine().length);
|
|
9544
|
-
return [];
|
|
9545
|
-
}
|
|
9546
|
-
if (this.queueIndex === -1 && this.historyIndex === -1) {
|
|
9547
|
-
if (this.queue.length === 0 && this.history.length === 0) {
|
|
9548
|
-
return [];
|
|
9549
|
-
}
|
|
9550
|
-
this.savedDraft = {
|
|
9551
|
-
buffer: [...this.buffer],
|
|
9552
|
-
row: this.row,
|
|
9553
|
-
col: this.col
|
|
9554
|
-
};
|
|
9555
|
-
this.savedAttachments = [...this.attachments];
|
|
9556
|
-
this.attachments = [];
|
|
9557
|
-
if (this.queue.length > 0) {
|
|
9558
|
-
this.queueIndex = this.queue.length - 1;
|
|
9559
|
-
this.loadEntry(this.queue[this.queueIndex] ?? "");
|
|
9560
|
-
} else {
|
|
9561
|
-
this.historyIndex = this.history.length - 1;
|
|
9562
|
-
this.loadEntry(this.history[this.historyIndex] ?? "");
|
|
9563
|
-
}
|
|
9564
|
-
return [];
|
|
9565
|
-
}
|
|
9566
|
-
if (this.queueIndex >= 0) {
|
|
9567
|
-
if (this.queueIndex > 0) {
|
|
9568
|
-
this.queueIndex -= 1;
|
|
9569
|
-
this.loadEntry(this.queue[this.queueIndex] ?? "");
|
|
9570
|
-
return [];
|
|
9571
|
-
}
|
|
9572
|
-
if (this.history.length === 0) {
|
|
9573
|
-
return [];
|
|
9574
|
-
}
|
|
9575
|
-
this.queueIndex = -1;
|
|
9576
|
-
this.historyIndex = this.history.length - 1;
|
|
9577
|
-
this.loadEntry(this.history[this.historyIndex] ?? "");
|
|
9578
|
-
return [];
|
|
9579
|
-
}
|
|
9580
|
-
if (this.historyIndex > 0) {
|
|
9581
|
-
this.historyIndex -= 1;
|
|
9582
|
-
this.loadEntry(this.history[this.historyIndex] ?? "");
|
|
9583
|
-
}
|
|
9584
|
-
return [];
|
|
9585
|
-
}
|
|
9586
|
-
// Down reverses the Up walk: history (older → newer), then queue
|
|
9587
|
-
// (oldest → newest), then restore the original draft. Within a
|
|
9588
|
-
// multi-line buffer, plain cursor movement still wins when no
|
|
9589
|
-
// navigation is in progress.
|
|
9590
|
-
handleDown() {
|
|
9591
|
-
if (this.row < this.buffer.length - 1 && this.historyIndex === -1 && this.queueIndex === -1) {
|
|
9592
|
-
this.row += 1;
|
|
9593
|
-
this.col = Math.min(this.col, this.currentLine().length);
|
|
9594
|
-
return [];
|
|
9595
|
-
}
|
|
9596
|
-
if (this.historyIndex >= 0) {
|
|
9597
|
-
if (this.historyIndex < this.history.length - 1) {
|
|
9598
|
-
this.historyIndex += 1;
|
|
9599
|
-
this.loadEntry(this.history[this.historyIndex] ?? "");
|
|
9600
|
-
return [];
|
|
9601
|
-
}
|
|
9602
|
-
this.historyIndex = -1;
|
|
9603
|
-
if (this.queue.length > 0) {
|
|
9604
|
-
this.queueIndex = 0;
|
|
9605
|
-
this.loadEntry(this.queue[this.queueIndex] ?? "");
|
|
9606
|
-
return [];
|
|
9607
|
-
}
|
|
9608
|
-
this.restoreDraft();
|
|
9609
|
-
return [];
|
|
9610
|
-
}
|
|
9611
|
-
if (this.queueIndex >= 0) {
|
|
9612
|
-
if (this.queueIndex < this.queue.length - 1) {
|
|
9613
|
-
this.queueIndex += 1;
|
|
9614
|
-
this.loadEntry(this.queue[this.queueIndex] ?? "");
|
|
9615
|
-
return [];
|
|
9616
|
-
}
|
|
9617
|
-
this.queueIndex = -1;
|
|
9618
|
-
this.restoreDraft();
|
|
9619
|
-
return [];
|
|
9620
|
-
}
|
|
9621
|
-
return [];
|
|
9762
|
+
errorLine = result.reason;
|
|
9763
|
+
repaintInput();
|
|
9764
|
+
});
|
|
9765
|
+
return;
|
|
9622
9766
|
}
|
|
9623
|
-
|
|
9624
|
-
|
|
9625
|
-
|
|
9626
|
-
this.row = this.savedDraft.row;
|
|
9627
|
-
this.col = this.savedDraft.col;
|
|
9628
|
-
this.savedDraft = null;
|
|
9629
|
-
this.attachments = this.savedAttachments ?? [];
|
|
9630
|
-
this.savedAttachments = null;
|
|
9631
|
-
} else {
|
|
9632
|
-
this.clearBuffer();
|
|
9633
|
-
}
|
|
9767
|
+
if (name === "ESCAPE") {
|
|
9768
|
+
finish({ kind: "back" });
|
|
9769
|
+
return;
|
|
9634
9770
|
}
|
|
9635
|
-
|
|
9636
|
-
|
|
9637
|
-
|
|
9638
|
-
// banner indicator lights up, and as the user types we extend the
|
|
9639
|
-
// query and load top matches. We deliberately do NOT auto-load the
|
|
9640
|
-
// most recent entry on an empty ^R (that's a surprise — Up-arrow
|
|
9641
|
-
// already walks history if that's what they wanted). With a
|
|
9642
|
-
// non-empty query that has no history match, escalate straight to
|
|
9643
|
-
// scrollback search so the typed term searches session output.
|
|
9644
|
-
startHistorySearch() {
|
|
9645
|
-
const query = this.bufferText().toLowerCase();
|
|
9646
|
-
if (query.length === 0) {
|
|
9647
|
-
this.historySearch = {
|
|
9648
|
-
query: "",
|
|
9649
|
-
matchIndices: [],
|
|
9650
|
-
cursor: 0,
|
|
9651
|
-
savedDraft: {
|
|
9652
|
-
buffer: [...this.buffer],
|
|
9653
|
-
row: this.row,
|
|
9654
|
-
col: this.col
|
|
9655
|
-
}
|
|
9656
|
-
};
|
|
9657
|
-
return [];
|
|
9658
|
-
}
|
|
9659
|
-
const matchIndices = this.findHistoryMatches(query);
|
|
9660
|
-
if (matchIndices.length === 0) {
|
|
9661
|
-
return [{ type: "escalate-search", query }];
|
|
9662
|
-
}
|
|
9663
|
-
this.historySearch = {
|
|
9664
|
-
query,
|
|
9665
|
-
matchIndices,
|
|
9666
|
-
cursor: 0,
|
|
9667
|
-
savedDraft: {
|
|
9668
|
-
buffer: [...this.buffer],
|
|
9669
|
-
row: this.row,
|
|
9670
|
-
col: this.col
|
|
9671
|
-
}
|
|
9672
|
-
};
|
|
9673
|
-
this.loadEntry(this.history[matchIndices[0]] ?? "");
|
|
9674
|
-
return [];
|
|
9771
|
+
if (name === "CTRL_C" || name === "CTRL_D") {
|
|
9772
|
+
finish({ kind: "cancel" });
|
|
9773
|
+
return;
|
|
9675
9774
|
}
|
|
9676
|
-
|
|
9677
|
-
|
|
9678
|
-
|
|
9679
|
-
|
|
9680
|
-
|
|
9681
|
-
advanceHistorySearch() {
|
|
9682
|
-
if (this.historySearch === null) {
|
|
9683
|
-
return [];
|
|
9684
|
-
}
|
|
9685
|
-
const search = this.historySearch;
|
|
9686
|
-
const atOldest = search.cursor >= search.matchIndices.length - 1;
|
|
9687
|
-
if (atOldest) {
|
|
9688
|
-
if (search.query.length === 0) {
|
|
9689
|
-
return [];
|
|
9690
|
-
}
|
|
9691
|
-
const query = search.query;
|
|
9692
|
-
const draft = search.savedDraft;
|
|
9693
|
-
this.historySearch = null;
|
|
9694
|
-
this.buffer = [...draft.buffer];
|
|
9695
|
-
this.row = draft.row;
|
|
9696
|
-
this.col = draft.col;
|
|
9697
|
-
return [{ type: "escalate-search", query }];
|
|
9775
|
+
if (name === "BACKSPACE") {
|
|
9776
|
+
if (buffer.length > 0) {
|
|
9777
|
+
buffer = buffer.slice(0, -1);
|
|
9778
|
+
errorLine = null;
|
|
9779
|
+
repaintInput();
|
|
9698
9780
|
}
|
|
9699
|
-
|
|
9700
|
-
const idx = search.matchIndices[search.cursor];
|
|
9701
|
-
this.loadEntry(this.history[idx] ?? "");
|
|
9702
|
-
return [];
|
|
9781
|
+
return;
|
|
9703
9782
|
}
|
|
9704
|
-
|
|
9705
|
-
|
|
9706
|
-
|
|
9707
|
-
|
|
9708
|
-
|
|
9709
|
-
}
|
|
9710
|
-
if (this.historySearch.cursor === 0) {
|
|
9711
|
-
return;
|
|
9712
|
-
}
|
|
9713
|
-
this.historySearch.cursor -= 1;
|
|
9714
|
-
const idx = this.historySearch.matchIndices[this.historySearch.cursor];
|
|
9715
|
-
this.loadEntry(this.history[idx] ?? "");
|
|
9783
|
+
if (name === "CTRL_U") {
|
|
9784
|
+
buffer = "";
|
|
9785
|
+
errorLine = null;
|
|
9786
|
+
repaintInput();
|
|
9787
|
+
return;
|
|
9716
9788
|
}
|
|
9717
|
-
|
|
9718
|
-
|
|
9719
|
-
|
|
9720
|
-
|
|
9721
|
-
|
|
9722
|
-
|
|
9723
|
-
|
|
9724
|
-
|
|
9725
|
-
|
|
9726
|
-
|
|
9727
|
-
|
|
9728
|
-
|
|
9729
|
-
|
|
9730
|
-
|
|
9731
|
-
|
|
9732
|
-
|
|
9733
|
-
|
|
9734
|
-
|
|
9735
|
-
|
|
9736
|
-
|
|
9737
|
-
|
|
9738
|
-
|
|
9739
|
-
|
|
9740
|
-
|
|
9741
|
-
|
|
9742
|
-
|
|
9743
|
-
|
|
9744
|
-
|
|
9745
|
-
|
|
9746
|
-
|
|
9747
|
-
|
|
9748
|
-
|
|
9749
|
-
|
|
9750
|
-
|
|
9789
|
+
if (name === "CTRL_W") {
|
|
9790
|
+
const trimmedRight = buffer.replace(/[/\s]+$/, "");
|
|
9791
|
+
const lastSep = Math.max(
|
|
9792
|
+
trimmedRight.lastIndexOf("/"),
|
|
9793
|
+
trimmedRight.lastIndexOf(" ")
|
|
9794
|
+
);
|
|
9795
|
+
buffer = lastSep >= 0 ? trimmedRight.slice(0, lastSep + 1) : "";
|
|
9796
|
+
errorLine = null;
|
|
9797
|
+
repaintInput();
|
|
9798
|
+
return;
|
|
9799
|
+
}
|
|
9800
|
+
if (data?.isCharacter) {
|
|
9801
|
+
buffer += name;
|
|
9802
|
+
errorLine = null;
|
|
9803
|
+
repaintInput();
|
|
9804
|
+
return;
|
|
9805
|
+
}
|
|
9806
|
+
};
|
|
9807
|
+
term.grabInput({});
|
|
9808
|
+
term.on("key", onKey);
|
|
9809
|
+
term.on("resize", onResize);
|
|
9810
|
+
});
|
|
9811
|
+
}
|
|
9812
|
+
function truncate2(s, max) {
|
|
9813
|
+
if (max <= 1) {
|
|
9814
|
+
return "";
|
|
9815
|
+
}
|
|
9816
|
+
if (s.length <= max) {
|
|
9817
|
+
return s;
|
|
9818
|
+
}
|
|
9819
|
+
return s.slice(0, Math.max(0, max - 1)) + "\u2026";
|
|
9820
|
+
}
|
|
9821
|
+
function truncateLeft(s, max) {
|
|
9822
|
+
if (max <= 1) {
|
|
9823
|
+
return "";
|
|
9824
|
+
}
|
|
9825
|
+
if (s.length <= max) {
|
|
9826
|
+
return s;
|
|
9827
|
+
}
|
|
9828
|
+
return "\u2026" + s.slice(s.length - (max - 1));
|
|
9829
|
+
}
|
|
9830
|
+
var init_import_cwd_prompt = __esm({
|
|
9831
|
+
"src/tui/import-cwd-prompt.ts"() {
|
|
9832
|
+
"use strict";
|
|
9833
|
+
init_paths();
|
|
9834
|
+
init_session();
|
|
9835
|
+
init_cwd();
|
|
9836
|
+
init_prompt_utils();
|
|
9837
|
+
}
|
|
9838
|
+
});
|
|
9839
|
+
|
|
9840
|
+
// src/tui/import-action-prompt.ts
|
|
9841
|
+
function actionPromptStep(selected, key, choices = ACTION_CHOICES) {
|
|
9842
|
+
if (key.kind === "cancel") {
|
|
9843
|
+
return { kind: "cancel" };
|
|
9844
|
+
}
|
|
9845
|
+
if (key.kind === "back") {
|
|
9846
|
+
return { kind: "back" };
|
|
9847
|
+
}
|
|
9848
|
+
if (key.kind === "enter") {
|
|
9849
|
+
const choice = choices[selected];
|
|
9850
|
+
if (!choice) {
|
|
9851
|
+
return { kind: "back" };
|
|
9852
|
+
}
|
|
9853
|
+
return { kind: "resolve", action: choice.key };
|
|
9854
|
+
}
|
|
9855
|
+
if (key.kind === "up") {
|
|
9856
|
+
return {
|
|
9857
|
+
kind: "continue",
|
|
9858
|
+
selected: Math.max(0, selected - 1)
|
|
9859
|
+
};
|
|
9860
|
+
}
|
|
9861
|
+
if (key.kind === "down") {
|
|
9862
|
+
return {
|
|
9863
|
+
kind: "continue",
|
|
9864
|
+
selected: Math.min(choices.length - 1, selected + 1)
|
|
9865
|
+
};
|
|
9866
|
+
}
|
|
9867
|
+
if (key.kind === "char") {
|
|
9868
|
+
const lower = key.ch.toLowerCase();
|
|
9869
|
+
if (lower === "n") {
|
|
9870
|
+
return {
|
|
9871
|
+
kind: "continue",
|
|
9872
|
+
selected: Math.min(choices.length - 1, selected + 1)
|
|
9873
|
+
};
|
|
9874
|
+
}
|
|
9875
|
+
if (lower === "p") {
|
|
9876
|
+
return {
|
|
9877
|
+
kind: "continue",
|
|
9878
|
+
selected: Math.max(0, selected - 1)
|
|
9879
|
+
};
|
|
9880
|
+
}
|
|
9881
|
+
const idx = choices.findIndex((c) => c.hotkey.toLowerCase() === lower);
|
|
9882
|
+
if (idx >= 0) {
|
|
9883
|
+
const choice = choices[idx];
|
|
9884
|
+
if (choice) {
|
|
9885
|
+
return { kind: "resolve", action: choice.key };
|
|
9886
|
+
}
|
|
9887
|
+
}
|
|
9888
|
+
}
|
|
9889
|
+
return { kind: "continue", selected };
|
|
9890
|
+
}
|
|
9891
|
+
async function promptForImportAction(term, session) {
|
|
9892
|
+
resetTerminalModes();
|
|
9893
|
+
const shortId2 = stripHydraSessionPrefix(session.sessionId);
|
|
9894
|
+
const fromMachine = session.importedFromMachine ?? "another machine";
|
|
9895
|
+
const originalCwd = shortenHomePath(session.cwd);
|
|
9896
|
+
let selected = 0;
|
|
9897
|
+
const render = () => {
|
|
9898
|
+
const choiceRows = ACTION_CHOICES.length * 2;
|
|
9899
|
+
const contentHeight = 7 + choiceRows + 2;
|
|
9900
|
+
const layout = drawBox(term, {
|
|
9901
|
+
contentHeight,
|
|
9902
|
+
title: "Imported session"
|
|
9903
|
+
});
|
|
9904
|
+
const innerW = layout.contentW;
|
|
9905
|
+
const headerRows = [
|
|
9906
|
+
{ label: "session: ", value: shortId2 },
|
|
9907
|
+
{ label: "from: ", value: fromMachine },
|
|
9908
|
+
{ label: "cwd: ", value: originalCwd }
|
|
9909
|
+
];
|
|
9910
|
+
let row = 0;
|
|
9911
|
+
for (const hr of headerRows) {
|
|
9912
|
+
term.moveTo(layout.contentX, layout.contentY + row);
|
|
9913
|
+
term.dim.noFormat(` ${hr.label}`);
|
|
9914
|
+
term.noFormat(truncate3(hr.value, innerW - hr.label.length - 2));
|
|
9915
|
+
row++;
|
|
9916
|
+
}
|
|
9917
|
+
row++;
|
|
9918
|
+
term.moveTo(layout.contentX, layout.contentY + row);
|
|
9919
|
+
term.noFormat(" What do you want to do?");
|
|
9920
|
+
row += 2;
|
|
9921
|
+
for (let i = 0; i < ACTION_CHOICES.length; i++) {
|
|
9922
|
+
const choice = ACTION_CHOICES[i];
|
|
9923
|
+
if (!choice) {
|
|
9924
|
+
continue;
|
|
9751
9925
|
}
|
|
9752
|
-
|
|
9753
|
-
|
|
9754
|
-
|
|
9755
|
-
|
|
9756
|
-
|
|
9757
|
-
|
|
9758
|
-
|
|
9759
|
-
}
|
|
9760
|
-
return out;
|
|
9926
|
+
const pointer = i === selected ? "\u276F" : " ";
|
|
9927
|
+
const label = ` ${pointer} ${choice.label}`;
|
|
9928
|
+
term.moveTo(layout.contentX, layout.contentY + row);
|
|
9929
|
+
if (i === selected) {
|
|
9930
|
+
term.brightWhite.bgBlue.noFormat(padRight(label, innerW));
|
|
9931
|
+
} else {
|
|
9932
|
+
term.noFormat(label);
|
|
9761
9933
|
}
|
|
9762
|
-
|
|
9763
|
-
|
|
9764
|
-
|
|
9765
|
-
|
|
9766
|
-
|
|
9767
|
-
|
|
9768
|
-
|
|
9769
|
-
|
|
9770
|
-
|
|
9934
|
+
row++;
|
|
9935
|
+
term.moveTo(layout.contentX, layout.contentY + row);
|
|
9936
|
+
term.dim.noFormat(` ${choice.description}`);
|
|
9937
|
+
row++;
|
|
9938
|
+
}
|
|
9939
|
+
row++;
|
|
9940
|
+
term.moveTo(layout.contentX, layout.contentY + row);
|
|
9941
|
+
term.dim.noFormat(" \u2191/\u2193 navigate \xB7 Enter select \xB7 r/v jump \xB7 Esc back");
|
|
9942
|
+
return layout;
|
|
9943
|
+
};
|
|
9944
|
+
render();
|
|
9945
|
+
term.hideCursor();
|
|
9946
|
+
return await new Promise((resolve6) => {
|
|
9947
|
+
let resolved = false;
|
|
9948
|
+
const cleanup = () => {
|
|
9949
|
+
if (resolved) {
|
|
9950
|
+
return;
|
|
9771
9951
|
}
|
|
9772
|
-
|
|
9773
|
-
|
|
9774
|
-
|
|
9775
|
-
|
|
9776
|
-
|
|
9777
|
-
|
|
9778
|
-
|
|
9952
|
+
resolved = true;
|
|
9953
|
+
term.off("key", onKey);
|
|
9954
|
+
term.off("resize", onResize);
|
|
9955
|
+
term.grabInput(false);
|
|
9956
|
+
term.hideCursor(false);
|
|
9957
|
+
term.moveTo(1, 1).eraseDisplayBelow();
|
|
9958
|
+
};
|
|
9959
|
+
const finish = (value) => {
|
|
9960
|
+
cleanup();
|
|
9961
|
+
resolve6(value);
|
|
9962
|
+
};
|
|
9963
|
+
const onResize = () => {
|
|
9964
|
+
if (resolved) {
|
|
9965
|
+
return;
|
|
9779
9966
|
}
|
|
9780
|
-
|
|
9781
|
-
|
|
9782
|
-
|
|
9783
|
-
|
|
9784
|
-
|
|
9785
|
-
|
|
9786
|
-
if (text.trim().length === 0) {
|
|
9787
|
-
return [{ type: "queue-remove", index }];
|
|
9788
|
-
}
|
|
9789
|
-
return [{ type: "queue-edit", index, text, attachments: attachments2 }];
|
|
9790
|
-
}
|
|
9791
|
-
if (text.trim().length === 0 && this.attachments.length === 0) {
|
|
9792
|
-
return [];
|
|
9793
|
-
}
|
|
9794
|
-
const planMode = this.planMode;
|
|
9795
|
-
const attachments = [...this.attachments];
|
|
9796
|
-
this.clearBuffer();
|
|
9797
|
-
return [{ type: "send", text, planMode, attachments }];
|
|
9967
|
+
render();
|
|
9968
|
+
};
|
|
9969
|
+
const onKey = (name, _matches, data) => {
|
|
9970
|
+
const input = mapKey(name, data);
|
|
9971
|
+
if (!input) {
|
|
9972
|
+
return;
|
|
9798
9973
|
}
|
|
9799
|
-
|
|
9800
|
-
|
|
9801
|
-
|
|
9802
|
-
|
|
9803
|
-
// (no-op). Otherwise emit an "amend" effect; the app decides whether
|
|
9804
|
-
// to route through amend_prompt or fall through to a regular send.
|
|
9805
|
-
amend() {
|
|
9806
|
-
const text = this.bufferText();
|
|
9807
|
-
if (this.queueIndex >= 0 && this.queueIndex < this.queue.length) {
|
|
9808
|
-
const index = this.queueIndex;
|
|
9809
|
-
const attachments2 = [...this.attachments];
|
|
9810
|
-
this.clearBuffer();
|
|
9811
|
-
if (text.trim().length === 0) {
|
|
9812
|
-
return [{ type: "queue-remove", index }];
|
|
9813
|
-
}
|
|
9814
|
-
return [{ type: "queue-edit", index, text, attachments: attachments2 }];
|
|
9815
|
-
}
|
|
9816
|
-
if (text.trim().length === 0 && this.attachments.length === 0) {
|
|
9817
|
-
return [];
|
|
9818
|
-
}
|
|
9819
|
-
const planMode = this.planMode;
|
|
9820
|
-
const attachments = [...this.attachments];
|
|
9821
|
-
this.clearBuffer();
|
|
9822
|
-
return [{ type: "amend", text, planMode, attachments }];
|
|
9974
|
+
const step = actionPromptStep(selected, input);
|
|
9975
|
+
if (step.kind === "cancel") {
|
|
9976
|
+
finish("cancel");
|
|
9977
|
+
return;
|
|
9823
9978
|
}
|
|
9824
|
-
|
|
9825
|
-
|
|
9826
|
-
|
|
9827
|
-
if (this.row !== 0 || this.col !== 0) {
|
|
9828
|
-
this.row = 0;
|
|
9829
|
-
this.col = 0;
|
|
9830
|
-
return [];
|
|
9831
|
-
}
|
|
9832
|
-
return [{ type: "scroll-to-top" }];
|
|
9979
|
+
if (step.kind === "back") {
|
|
9980
|
+
finish("back");
|
|
9981
|
+
return;
|
|
9833
9982
|
}
|
|
9834
|
-
|
|
9835
|
-
|
|
9836
|
-
|
|
9837
|
-
const lastRow = this.buffer.length - 1;
|
|
9838
|
-
const lastCol = (this.buffer[lastRow] ?? "").length;
|
|
9839
|
-
if (this.row !== lastRow || this.col !== lastCol) {
|
|
9840
|
-
this.row = lastRow;
|
|
9841
|
-
this.col = lastCol;
|
|
9842
|
-
return [];
|
|
9843
|
-
}
|
|
9844
|
-
return [{ type: "scroll-to-bottom" }];
|
|
9983
|
+
if (step.kind === "resolve") {
|
|
9984
|
+
finish(step.action);
|
|
9985
|
+
return;
|
|
9845
9986
|
}
|
|
9846
|
-
|
|
9847
|
-
|
|
9848
|
-
|
|
9849
|
-
this.queueIndex = -1;
|
|
9850
|
-
this.restoreDraft();
|
|
9851
|
-
return [{ type: "queue-remove", index }];
|
|
9852
|
-
}
|
|
9853
|
-
if (!this.bufferIsEmpty() || this.attachments.length > 0) {
|
|
9854
|
-
this.buffer = [""];
|
|
9855
|
-
this.row = 0;
|
|
9856
|
-
this.col = 0;
|
|
9857
|
-
this.attachments = [];
|
|
9858
|
-
this.historyIndex = -1;
|
|
9859
|
-
this.savedDraft = null;
|
|
9860
|
-
this.savedAttachments = null;
|
|
9861
|
-
return [];
|
|
9862
|
-
}
|
|
9863
|
-
if (this.turnRunning) {
|
|
9864
|
-
return [{ type: "cancel" }];
|
|
9865
|
-
}
|
|
9866
|
-
return [{ type: "exit" }];
|
|
9987
|
+
if (step.selected !== selected) {
|
|
9988
|
+
selected = step.selected;
|
|
9989
|
+
render();
|
|
9867
9990
|
}
|
|
9868
9991
|
};
|
|
9992
|
+
term.grabInput({});
|
|
9993
|
+
term.on("key", onKey);
|
|
9994
|
+
term.on("resize", onResize);
|
|
9995
|
+
});
|
|
9996
|
+
}
|
|
9997
|
+
function mapKey(name, data) {
|
|
9998
|
+
if (name === "UP") {
|
|
9999
|
+
return { kind: "up" };
|
|
10000
|
+
}
|
|
10001
|
+
if (name === "DOWN") {
|
|
10002
|
+
return { kind: "down" };
|
|
10003
|
+
}
|
|
10004
|
+
if (name === "ENTER" || name === "KP_ENTER") {
|
|
10005
|
+
return { kind: "enter" };
|
|
10006
|
+
}
|
|
10007
|
+
if (name === "ESCAPE") {
|
|
10008
|
+
return { kind: "back" };
|
|
10009
|
+
}
|
|
10010
|
+
if (name === "CTRL_C" || name === "CTRL_D") {
|
|
10011
|
+
return { kind: "cancel" };
|
|
10012
|
+
}
|
|
10013
|
+
if (data?.isCharacter) {
|
|
10014
|
+
return { kind: "char", ch: name };
|
|
10015
|
+
}
|
|
10016
|
+
return null;
|
|
10017
|
+
}
|
|
10018
|
+
function truncate3(s, max) {
|
|
10019
|
+
if (max <= 1) {
|
|
10020
|
+
return "";
|
|
10021
|
+
}
|
|
10022
|
+
if (s.length <= max) {
|
|
10023
|
+
return s;
|
|
10024
|
+
}
|
|
10025
|
+
return s.slice(0, Math.max(0, max - 1)) + "\u2026";
|
|
10026
|
+
}
|
|
10027
|
+
function padRight(s, w) {
|
|
10028
|
+
if (s.length >= w) {
|
|
10029
|
+
return s.slice(0, w);
|
|
10030
|
+
}
|
|
10031
|
+
return s + " ".repeat(w - s.length);
|
|
10032
|
+
}
|
|
10033
|
+
var ACTION_CHOICES;
|
|
10034
|
+
var init_import_action_prompt = __esm({
|
|
10035
|
+
"src/tui/import-action-prompt.ts"() {
|
|
10036
|
+
"use strict";
|
|
10037
|
+
init_paths();
|
|
10038
|
+
init_session();
|
|
10039
|
+
init_prompt_utils();
|
|
10040
|
+
ACTION_CHOICES = [
|
|
10041
|
+
{
|
|
10042
|
+
key: "run-local",
|
|
10043
|
+
label: "Run locally",
|
|
10044
|
+
hotkey: "r",
|
|
10045
|
+
description: "spawn the agent on this machine with a local cwd"
|
|
10046
|
+
},
|
|
10047
|
+
{
|
|
10048
|
+
key: "view",
|
|
10049
|
+
label: "View transcript",
|
|
10050
|
+
hotkey: "v",
|
|
10051
|
+
description: "open read-only, no agent spawn"
|
|
10052
|
+
}
|
|
10053
|
+
];
|
|
9869
10054
|
}
|
|
9870
10055
|
});
|
|
9871
10056
|
|
|
@@ -11624,7 +11809,16 @@ async function runSession(term, config, target, opts, exitHint) {
|
|
|
11624
11809
|
if (choice.kind === "new") {
|
|
11625
11810
|
const { sessionId: _drop, ...rest } = opts;
|
|
11626
11811
|
void _drop;
|
|
11627
|
-
|
|
11812
|
+
const nextOpts2 = {
|
|
11813
|
+
...rest,
|
|
11814
|
+
cwd: resolvedCwd,
|
|
11815
|
+
forceNew: true,
|
|
11816
|
+
readonly: false
|
|
11817
|
+
};
|
|
11818
|
+
if (choice.prompt !== void 0) {
|
|
11819
|
+
nextOpts2.initialPrompt = choice.prompt;
|
|
11820
|
+
}
|
|
11821
|
+
resume(nextOpts2);
|
|
11628
11822
|
return;
|
|
11629
11823
|
}
|
|
11630
11824
|
if (choice.kind !== "attach") {
|
|
@@ -12685,6 +12879,9 @@ connection lost: ${err.message}
|
|
|
12685
12879
|
stop(err ? 1 : 0);
|
|
12686
12880
|
});
|
|
12687
12881
|
process.on("SIGINT", sigintHandler);
|
|
12882
|
+
if (opts.initialPrompt && ctx.sessionId === "__new__") {
|
|
12883
|
+
enqueuePrompt(opts.initialPrompt, []);
|
|
12884
|
+
}
|
|
12688
12885
|
return await sessionDone;
|
|
12689
12886
|
}
|
|
12690
12887
|
async function resolveSession(term, config, target, opts) {
|
|
@@ -12719,9 +12916,6 @@ async function resolveSession(term, config, target, opts) {
|
|
|
12719
12916
|
}
|
|
12720
12917
|
while (true) {
|
|
12721
12918
|
const sessions = await listSessions(target);
|
|
12722
|
-
if (sessions.length === 0) {
|
|
12723
|
-
return newCtx(opts, cwd, config);
|
|
12724
|
-
}
|
|
12725
12919
|
const choice = await pickSession(term, {
|
|
12726
12920
|
cwd,
|
|
12727
12921
|
sessions,
|
|
@@ -12732,6 +12926,9 @@ async function resolveSession(term, config, target, opts) {
|
|
|
12732
12926
|
return null;
|
|
12733
12927
|
}
|
|
12734
12928
|
if (choice.kind === "new") {
|
|
12929
|
+
if (choice.prompt !== void 0) {
|
|
12930
|
+
opts.initialPrompt = choice.prompt;
|
|
12931
|
+
}
|
|
12735
12932
|
return newCtx(opts, cwd, config);
|
|
12736
12933
|
}
|
|
12737
12934
|
opts.readonly = choice.readonly === true;
|