aicp-claude-agent 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +161 -0
- package/dist/claude/sdk.d.ts +34 -0
- package/dist/claude/sdk.d.ts.map +1 -0
- package/dist/claude/sdk.js +112 -0
- package/dist/claude/sdk.js.map +1 -0
- package/dist/commands/index.d.ts +10 -0
- package/dist/commands/index.d.ts.map +1 -0
- package/dist/commands/index.js +32 -0
- package/dist/commands/index.js.map +1 -0
- package/dist/commands/resume.d.ts +6 -0
- package/dist/commands/resume.d.ts.map +1 -0
- package/dist/commands/resume.js +75 -0
- package/dist/commands/resume.js.map +1 -0
- package/dist/config.d.ts +26 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +118 -0
- package/dist/config.js.map +1 -0
- package/dist/history.d.ts +9 -0
- package/dist/history.d.ts.map +1 -0
- package/dist/history.js +87 -0
- package/dist/history.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +53 -0
- package/dist/index.js.map +1 -0
- package/dist/main.d.ts +2 -0
- package/dist/main.d.ts.map +1 -0
- package/dist/main.js +344 -0
- package/dist/main.js.map +1 -0
- package/dist/setup.d.ts +2 -0
- package/dist/setup.d.ts.map +1 -0
- package/dist/setup.js +129 -0
- package/dist/setup.js.map +1 -0
- package/dist/terminal/prompt.d.ts +29 -0
- package/dist/terminal/prompt.d.ts.map +1 -0
- package/dist/terminal/prompt.js +764 -0
- package/dist/terminal/prompt.js.map +1 -0
- package/dist/terminal/spinner.d.ts +7 -0
- package/dist/terminal/spinner.d.ts.map +1 -0
- package/dist/terminal/spinner.js +114 -0
- package/dist/terminal/spinner.js.map +1 -0
- package/dist/types/protocol.d.ts +59 -0
- package/dist/types/protocol.d.ts.map +1 -0
- package/dist/types/protocol.js +4 -0
- package/dist/types/protocol.js.map +1 -0
- package/dist/update.d.ts +2 -0
- package/dist/update.d.ts.map +1 -0
- package/dist/update.js +65 -0
- package/dist/update.js.map +1 -0
- package/dist/websocket/client.d.ts +8 -0
- package/dist/websocket/client.d.ts.map +1 -0
- package/dist/websocket/client.js +120 -0
- package/dist/websocket/client.js.map +1 -0
- package/package.json +39 -0
|
@@ -0,0 +1,764 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.writeAbove = writeAbove;
|
|
4
|
+
exports.updateAbove = updateAbove;
|
|
5
|
+
exports.clearAbove = clearAbove;
|
|
6
|
+
exports.printBanner = printBanner;
|
|
7
|
+
exports.printStatus = printStatus;
|
|
8
|
+
exports.printInfo = printInfo;
|
|
9
|
+
exports.printError = printError;
|
|
10
|
+
exports.printDivider = printDivider;
|
|
11
|
+
exports.wrapText = wrapText;
|
|
12
|
+
exports.stylePastePlaceholders = stylePastePlaceholders;
|
|
13
|
+
exports.formatStatusBar = formatStatusBar;
|
|
14
|
+
exports.createPrompt = createPrompt;
|
|
15
|
+
const history_1 = require("../history");
|
|
16
|
+
// ─── ANSI helpers ────────────────────────────────────────────────
|
|
17
|
+
const DIM = '\x1b[2m';
|
|
18
|
+
const RESET = '\x1b[0m';
|
|
19
|
+
const BOLD = '\x1b[1m';
|
|
20
|
+
const PINK = '\x1b[38;5;205m';
|
|
21
|
+
const GRAY = '\x1b[38;5;240m';
|
|
22
|
+
const INFO = '\x1b[38;5;245m';
|
|
23
|
+
const WHITE = '\x1b[37m';
|
|
24
|
+
const CYAN = '\x1b[36m';
|
|
25
|
+
const LINE_CHAR = '─';
|
|
26
|
+
const PROMPT_CHAR = '❯';
|
|
27
|
+
const BASE_PROMPT_LINES = 4; // top divider, input, bottom divider, status
|
|
28
|
+
const PASTE_FOLD_THRESHOLD = 4; // fold pasted blocks with this many or more lines
|
|
29
|
+
// ─── Terminal dimensions ────────────────────────────────────────
|
|
30
|
+
function termWidth() {
|
|
31
|
+
return process.stdout.columns || 80;
|
|
32
|
+
}
|
|
33
|
+
function termHeight() {
|
|
34
|
+
return process.stdout.rows || 24;
|
|
35
|
+
}
|
|
36
|
+
function hrLine() {
|
|
37
|
+
return `\x1b[38;5;60m${LINE_CHAR.repeat(termWidth())}${RESET}`;
|
|
38
|
+
}
|
|
39
|
+
// ─── Pinned prompt state ────────────────────────────────────────
|
|
40
|
+
let promptActive = false;
|
|
41
|
+
let currentStatus = '';
|
|
42
|
+
let extraLines = 0; // number of continuation lines (grows the prompt area)
|
|
43
|
+
function promptHeight() {
|
|
44
|
+
return BASE_PROMPT_LINES + extraLines;
|
|
45
|
+
}
|
|
46
|
+
// ─── writeAbove — insert text into scrollback above the prompt ──
|
|
47
|
+
function writeAbove(text) {
|
|
48
|
+
const wrapped = wrapText(text);
|
|
49
|
+
if (!promptActive) {
|
|
50
|
+
for (const wl of wrapped)
|
|
51
|
+
process.stdout.write(wl + '\n');
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
const rows = termHeight();
|
|
55
|
+
const scrollBottom = rows - promptHeight();
|
|
56
|
+
for (const wl of wrapped) {
|
|
57
|
+
process.stdout.write('\x1b[s' +
|
|
58
|
+
`\x1b[1;${scrollBottom}r` +
|
|
59
|
+
`\x1b[${scrollBottom};1H` +
|
|
60
|
+
'\n' +
|
|
61
|
+
`\x1b[${scrollBottom};1H` +
|
|
62
|
+
'\x1b[2K' +
|
|
63
|
+
wl +
|
|
64
|
+
`\x1b[1;${rows}r` +
|
|
65
|
+
'\x1b[u');
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
// ─── updateAbove — overwrite the last scrollback line in-place ───
|
|
69
|
+
function updateAbove(text) {
|
|
70
|
+
// Only show what fits on one line — truncate the rest.
|
|
71
|
+
// Full wrapping happens when the line is finalized via writeAbove.
|
|
72
|
+
if (visibleLength(text) > termWidth()) {
|
|
73
|
+
const wrapped = wrapText(text);
|
|
74
|
+
text = wrapped[0];
|
|
75
|
+
}
|
|
76
|
+
if (!promptActive) {
|
|
77
|
+
process.stdout.write(`\r\x1b[2K${text}`);
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
const rows = termHeight();
|
|
81
|
+
const scrollBottom = rows - promptHeight();
|
|
82
|
+
process.stdout.write('\x1b[s' +
|
|
83
|
+
`\x1b[${scrollBottom};1H` +
|
|
84
|
+
'\x1b[2K' +
|
|
85
|
+
text +
|
|
86
|
+
'\x1b[u');
|
|
87
|
+
}
|
|
88
|
+
// ─── clearAbove — clear the last scrollback line ────────────────
|
|
89
|
+
function clearAbove() {
|
|
90
|
+
if (!promptActive) {
|
|
91
|
+
process.stdout.write('\r\x1b[2K');
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
const rows = termHeight();
|
|
95
|
+
const scrollBottom = rows - promptHeight();
|
|
96
|
+
process.stdout.write('\x1b[s' +
|
|
97
|
+
`\x1b[${scrollBottom};1H` +
|
|
98
|
+
'\x1b[2K' +
|
|
99
|
+
'\x1b[u');
|
|
100
|
+
}
|
|
101
|
+
// ─── Public print helpers (route through writeAbove) ────────────
|
|
102
|
+
function printBanner() {
|
|
103
|
+
writeAbove(hrLine());
|
|
104
|
+
writeAbove(`${BOLD}${CYAN} AICP Agent${RESET}`);
|
|
105
|
+
writeAbove(hrLine());
|
|
106
|
+
writeAbove('');
|
|
107
|
+
}
|
|
108
|
+
function printStatus(text) {
|
|
109
|
+
writeAbove(`${PINK} ${text}${RESET}`);
|
|
110
|
+
}
|
|
111
|
+
function printInfo(text) {
|
|
112
|
+
writeAbove(`${INFO} ${text}${RESET}`);
|
|
113
|
+
}
|
|
114
|
+
function printError(text) {
|
|
115
|
+
writeAbove(`\x1b[31m ${text}${RESET}`);
|
|
116
|
+
}
|
|
117
|
+
function printDivider() {
|
|
118
|
+
writeAbove(hrLine());
|
|
119
|
+
}
|
|
120
|
+
// ─── Word wrap helper ───────────────────────────────────────────
|
|
121
|
+
const ANSI_RE = /\x1b\[[0-9;]*m/g;
|
|
122
|
+
function visibleLength(str) {
|
|
123
|
+
return str.replace(ANSI_RE, '').length;
|
|
124
|
+
}
|
|
125
|
+
function wrapText(text) {
|
|
126
|
+
const width = termWidth();
|
|
127
|
+
if (visibleLength(text) <= width)
|
|
128
|
+
return [text];
|
|
129
|
+
// Walk the string tracking visible character count
|
|
130
|
+
const result = [];
|
|
131
|
+
let remaining = text;
|
|
132
|
+
while (visibleLength(remaining) > width) {
|
|
133
|
+
// Find the byte index where visible chars reach width
|
|
134
|
+
let visCount = 0;
|
|
135
|
+
let cutIdx = 0;
|
|
136
|
+
let lastSpace = -1;
|
|
137
|
+
for (let i = 0; i < remaining.length; i++) {
|
|
138
|
+
// Skip ANSI sequences
|
|
139
|
+
if (remaining[i] === '\x1b' && remaining[i + 1] === '[') {
|
|
140
|
+
let j = i + 2;
|
|
141
|
+
while (j < remaining.length && ((remaining[j] >= '0' && remaining[j] <= '9') || remaining[j] === ';'))
|
|
142
|
+
j++;
|
|
143
|
+
if (j < remaining.length && remaining[j] === 'm') {
|
|
144
|
+
i = j; // skip to end of sequence (loop will i++)
|
|
145
|
+
continue;
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
if (remaining[i] === ' ')
|
|
149
|
+
lastSpace = i;
|
|
150
|
+
visCount++;
|
|
151
|
+
if (visCount >= width) {
|
|
152
|
+
cutIdx = i + 1;
|
|
153
|
+
break;
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
// Prefer breaking at a space
|
|
157
|
+
let breakAt = (lastSpace > 0) ? lastSpace : cutIdx;
|
|
158
|
+
result.push(remaining.slice(0, breakAt));
|
|
159
|
+
remaining = remaining.slice(breakAt).replace(/^ /, '');
|
|
160
|
+
}
|
|
161
|
+
if (remaining)
|
|
162
|
+
result.push(remaining);
|
|
163
|
+
return result;
|
|
164
|
+
}
|
|
165
|
+
// ─── Paste placeholder styling ──────────────────────────────────
|
|
166
|
+
function stylePastePlaceholders(text) {
|
|
167
|
+
return text.replace(/\[Pasted text #\d+ \+\d+ lines?\]/g, match => `\x1b[38;5;117m${match}${RESET}`);
|
|
168
|
+
}
|
|
169
|
+
// ─── Status bar formatting ──────────────────────────────────────
|
|
170
|
+
function formatStatusBar(left, right) {
|
|
171
|
+
const width = termWidth();
|
|
172
|
+
const gap = Math.max(1, width - left.length - right.length);
|
|
173
|
+
return `${PINK} ${left}${' '.repeat(gap)}${right} ${RESET}`;
|
|
174
|
+
}
|
|
175
|
+
function createPrompt(opts) {
|
|
176
|
+
let submitHandler = null;
|
|
177
|
+
let closeHandler = null;
|
|
178
|
+
let sigintHandler = null;
|
|
179
|
+
currentStatus = opts?.statusLine || '';
|
|
180
|
+
// ── Input state ──
|
|
181
|
+
let line = ''; // current active line being edited
|
|
182
|
+
let cursor = 0; // cursor position within active line
|
|
183
|
+
const lines = []; // previous continuation lines
|
|
184
|
+
let activeLineIdx = 0; // which line is being edited (lines.length = current input)
|
|
185
|
+
let closed = false;
|
|
186
|
+
let pasting = false; // true between bracketed paste start/end sequences
|
|
187
|
+
// ── Paste folding state ──
|
|
188
|
+
let pasteBuffering = false; // true while buffering paste content
|
|
189
|
+
let pasteBuffer = ''; // accumulates raw paste content
|
|
190
|
+
const pasteBlocks = [];
|
|
191
|
+
let pasteCounter = 0; // sequential paste block numbering, resets on submit
|
|
192
|
+
// ── History navigation state ──
|
|
193
|
+
let historyIndex = -1; // -1 = current input, 0..N-1 = history (0=oldest)
|
|
194
|
+
let savedInput = ''; // stash current input when browsing history
|
|
195
|
+
// For rl.question() compatibility
|
|
196
|
+
let questionCb = null;
|
|
197
|
+
let questionPromptText = '';
|
|
198
|
+
// ── Drawing helpers ──
|
|
199
|
+
function currentPromptStr() {
|
|
200
|
+
if (questionCb)
|
|
201
|
+
return questionPromptText;
|
|
202
|
+
return `${BOLD}${WHITE}${PROMPT_CHAR}${RESET} `;
|
|
203
|
+
}
|
|
204
|
+
function promptVisualLen() {
|
|
205
|
+
if (questionCb)
|
|
206
|
+
return questionPromptText.replace(/\x1b\[[0-9;]*m/g, '').length;
|
|
207
|
+
return 2; // "❯ "
|
|
208
|
+
}
|
|
209
|
+
// Get all editable lines as a flat array (line inserted at activeLineIdx)
|
|
210
|
+
function allLines() {
|
|
211
|
+
const result = [...lines];
|
|
212
|
+
result.splice(activeLineIdx, 0, line);
|
|
213
|
+
return result;
|
|
214
|
+
}
|
|
215
|
+
// Switch editing focus to a different line
|
|
216
|
+
function switchToLine(newIdx) {
|
|
217
|
+
const all = allLines();
|
|
218
|
+
activeLineIdx = newIdx;
|
|
219
|
+
line = all[newIdx];
|
|
220
|
+
cursor = Math.min(cursor, line.length);
|
|
221
|
+
lines.length = 0;
|
|
222
|
+
for (let i = 0; i < all.length; i++) {
|
|
223
|
+
if (i !== newIdx)
|
|
224
|
+
lines.push(all[i]);
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
// Load text into the prompt (for history navigation)
|
|
228
|
+
function loadIntoPrompt(text) {
|
|
229
|
+
const inputLines = text.split('\n');
|
|
230
|
+
lines.length = 0;
|
|
231
|
+
if (inputLines.length === 1) {
|
|
232
|
+
line = inputLines[0];
|
|
233
|
+
cursor = line.length;
|
|
234
|
+
activeLineIdx = 0;
|
|
235
|
+
extraLines = 0;
|
|
236
|
+
}
|
|
237
|
+
else {
|
|
238
|
+
activeLineIdx = inputLines.length - 1;
|
|
239
|
+
line = inputLines[activeLineIdx];
|
|
240
|
+
cursor = line.length;
|
|
241
|
+
for (let i = 0; i < inputLines.length; i++) {
|
|
242
|
+
if (i !== activeLineIdx)
|
|
243
|
+
lines.push(inputLines[i]);
|
|
244
|
+
}
|
|
245
|
+
extraLines = lines.length;
|
|
246
|
+
// Make room for extra lines
|
|
247
|
+
const rows = termHeight();
|
|
248
|
+
const scrollBottom = rows - promptHeight();
|
|
249
|
+
if (extraLines > 0) {
|
|
250
|
+
process.stdout.write(`\x1b[1;${scrollBottom}r` +
|
|
251
|
+
`\x1b[${scrollBottom};1H` +
|
|
252
|
+
'\n'.repeat(extraLines) +
|
|
253
|
+
`\x1b[1;${rows}r`);
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
pasteBlocks.length = 0;
|
|
257
|
+
pasteCounter = 0;
|
|
258
|
+
drawPromptArea();
|
|
259
|
+
}
|
|
260
|
+
// Get all lines with paste placeholders expanded to their full content
|
|
261
|
+
function allLinesExpanded() {
|
|
262
|
+
const display = allLines();
|
|
263
|
+
const result = [];
|
|
264
|
+
for (const displayLine of display) {
|
|
265
|
+
let expanded = displayLine;
|
|
266
|
+
expanded = expanded.replace(/\[Pasted text #(\d+) \+\d+ lines?\]/g, (_match, idStr) => {
|
|
267
|
+
const id = parseInt(idStr, 10);
|
|
268
|
+
const block = pasteBlocks.find(b => b.id === id);
|
|
269
|
+
return block ? block.content : _match;
|
|
270
|
+
});
|
|
271
|
+
result.push(...expanded.split('\n'));
|
|
272
|
+
}
|
|
273
|
+
return result;
|
|
274
|
+
}
|
|
275
|
+
// Process buffered paste content — fold large pastes into placeholder
|
|
276
|
+
function processPasteBuffer() {
|
|
277
|
+
if (!pasteBuffer)
|
|
278
|
+
return;
|
|
279
|
+
const normalized = pasteBuffer.replace(/\r\n/g, '\n').replace(/\r/g, '\n');
|
|
280
|
+
const pastedLines = normalized.split('\n');
|
|
281
|
+
if (pastedLines.length > 1 && pastedLines[pastedLines.length - 1] === '')
|
|
282
|
+
pastedLines.pop();
|
|
283
|
+
if (pastedLines.length >= PASTE_FOLD_THRESHOLD) {
|
|
284
|
+
pasteCounter++;
|
|
285
|
+
const id = pasteCounter;
|
|
286
|
+
const content = pastedLines.join('\n');
|
|
287
|
+
pasteBlocks.push({ id, content });
|
|
288
|
+
const placeholder = `[Pasted text #${id} +${pastedLines.length} lines]`;
|
|
289
|
+
line = line.slice(0, cursor) + placeholder + line.slice(cursor);
|
|
290
|
+
cursor += placeholder.length;
|
|
291
|
+
drawActiveLine();
|
|
292
|
+
}
|
|
293
|
+
else {
|
|
294
|
+
// Small paste — insert normally
|
|
295
|
+
for (const ch of normalized) {
|
|
296
|
+
if (ch === '\n') {
|
|
297
|
+
handleContinuation();
|
|
298
|
+
}
|
|
299
|
+
else {
|
|
300
|
+
line = line.slice(0, cursor) + ch + line.slice(cursor);
|
|
301
|
+
cursor++;
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
drawPromptArea();
|
|
305
|
+
}
|
|
306
|
+
pasteBuffer = '';
|
|
307
|
+
}
|
|
308
|
+
// Draw the entire prompt area: top divider, all lines, bottom divider, status
|
|
309
|
+
function drawPromptArea() {
|
|
310
|
+
const rows = termHeight();
|
|
311
|
+
const height = promptHeight();
|
|
312
|
+
const topRow = rows - height + 1;
|
|
313
|
+
const all = allLines();
|
|
314
|
+
let seq = '';
|
|
315
|
+
// Top divider
|
|
316
|
+
seq += `\x1b[${topRow};1H\x1b[2K` + hrLine();
|
|
317
|
+
// All editable lines
|
|
318
|
+
for (let i = 0; i < all.length; i++) {
|
|
319
|
+
const row = topRow + 1 + i;
|
|
320
|
+
const isActive = i === activeLineIdx;
|
|
321
|
+
const prefix = isActive ? `${BOLD}${WHITE}${PROMPT_CHAR}${RESET} ` : ' ';
|
|
322
|
+
seq += `\x1b[${row};1H\x1b[2K` + prefix + stylePastePlaceholders(all[i]);
|
|
323
|
+
}
|
|
324
|
+
// Bottom divider
|
|
325
|
+
seq += `\x1b[${rows - 1};1H\x1b[2K` + hrLine();
|
|
326
|
+
// Status
|
|
327
|
+
seq += `\x1b[${rows};1H\x1b[2K${PINK} ${currentStatus}${RESET}`;
|
|
328
|
+
// Position cursor on active line
|
|
329
|
+
const activeRow = topRow + 1 + activeLineIdx;
|
|
330
|
+
const col = 2 + cursor + 1; // prefix is always 2 visible chars
|
|
331
|
+
seq += `\x1b[${activeRow};${col}H`;
|
|
332
|
+
process.stdout.write(seq);
|
|
333
|
+
}
|
|
334
|
+
// Redraw just the active line and position cursor
|
|
335
|
+
function drawActiveLine() {
|
|
336
|
+
const rows = termHeight();
|
|
337
|
+
const height = promptHeight();
|
|
338
|
+
const topRow = rows - height + 1;
|
|
339
|
+
const activeRow = topRow + 1 + activeLineIdx;
|
|
340
|
+
const prefix = `${BOLD}${WHITE}${PROMPT_CHAR}${RESET} `;
|
|
341
|
+
const col = 2 + cursor + 1;
|
|
342
|
+
process.stdout.write(`\x1b[${activeRow};1H\x1b[2K` +
|
|
343
|
+
prefix + stylePastePlaceholders(line) +
|
|
344
|
+
`\x1b[${activeRow};${col}H`);
|
|
345
|
+
}
|
|
346
|
+
function drawStatusLine() {
|
|
347
|
+
const rows = termHeight();
|
|
348
|
+
// Save/restore cursor so we don't disrupt input position
|
|
349
|
+
process.stdout.write('\x1b[s' +
|
|
350
|
+
`\x1b[${rows};1H\x1b[2K${PINK} ${currentStatus}${RESET}` +
|
|
351
|
+
'\x1b[u');
|
|
352
|
+
}
|
|
353
|
+
// ── Activate ──
|
|
354
|
+
function activate() {
|
|
355
|
+
promptActive = true;
|
|
356
|
+
extraLines = 0;
|
|
357
|
+
// Make room at the bottom
|
|
358
|
+
process.stdout.write('\n'.repeat(BASE_PROMPT_LINES));
|
|
359
|
+
// Draw the 4-line area
|
|
360
|
+
drawPromptArea();
|
|
361
|
+
// Enable raw mode + bracketed paste
|
|
362
|
+
if (process.stdin.isTTY) {
|
|
363
|
+
process.stdin.setRawMode(true);
|
|
364
|
+
}
|
|
365
|
+
process.stdout.write('\x1b[?2004h'); // enable bracketed paste mode
|
|
366
|
+
process.stdin.resume();
|
|
367
|
+
process.stdin.on('data', onKeypress);
|
|
368
|
+
}
|
|
369
|
+
// ── Multiline continuation ──
|
|
370
|
+
// The prompt area grows: top divider moves up, new line added between dividers
|
|
371
|
+
function handleContinuation() {
|
|
372
|
+
// Merge current line back, insert new empty line after it
|
|
373
|
+
const all = allLines();
|
|
374
|
+
all.splice(activeLineIdx + 1, 0, '');
|
|
375
|
+
activeLineIdx = activeLineIdx + 1;
|
|
376
|
+
line = '';
|
|
377
|
+
cursor = 0;
|
|
378
|
+
lines.length = 0;
|
|
379
|
+
for (let i = 0; i < all.length; i++) {
|
|
380
|
+
if (i !== activeLineIdx)
|
|
381
|
+
lines.push(all[i]);
|
|
382
|
+
}
|
|
383
|
+
extraLines = lines.length; // lines.length = total - 1 = extra lines beyond the 1 in BASE
|
|
384
|
+
// We need one more row for the prompt area — scroll scrollback up by 1
|
|
385
|
+
const rows = termHeight();
|
|
386
|
+
const scrollBottom = rows - promptHeight();
|
|
387
|
+
// Use scroll region to push scrollback up, making room for the taller prompt
|
|
388
|
+
process.stdout.write(`\x1b[1;${scrollBottom + 1}r` + // scroll region includes the row we're claiming
|
|
389
|
+
`\x1b[${scrollBottom + 1};1H` + // move to bottom of region
|
|
390
|
+
'\n' + // scroll up
|
|
391
|
+
`\x1b[1;${rows}r` // reset scroll region
|
|
392
|
+
);
|
|
393
|
+
// Redraw the entire prompt area at new size
|
|
394
|
+
drawPromptArea();
|
|
395
|
+
}
|
|
396
|
+
// ── Keypress handler ──
|
|
397
|
+
function onKeypress(data) {
|
|
398
|
+
if (closed)
|
|
399
|
+
return;
|
|
400
|
+
const str = data.toString('utf8');
|
|
401
|
+
for (let i = 0; i < str.length; i++) {
|
|
402
|
+
const ch = str[i];
|
|
403
|
+
const code = str.charCodeAt(i);
|
|
404
|
+
// ESC sequence
|
|
405
|
+
if (ch === '\x1b' && i + 1 < str.length) {
|
|
406
|
+
if (str[i + 1] === '[' && i + 2 < str.length) {
|
|
407
|
+
let j = i + 2;
|
|
408
|
+
let params = '';
|
|
409
|
+
while (j < str.length && (str[j] >= '0' && str[j] <= '9' || str[j] === ';')) {
|
|
410
|
+
params += str[j];
|
|
411
|
+
j++;
|
|
412
|
+
}
|
|
413
|
+
const terminator = j < str.length ? str[j] : '';
|
|
414
|
+
// Bracketed paste: \x1b[200~ (start) and \x1b[201~ (end)
|
|
415
|
+
if (terminator === '~' && params === '200') {
|
|
416
|
+
pasting = true;
|
|
417
|
+
pasteBuffering = true;
|
|
418
|
+
pasteBuffer = '';
|
|
419
|
+
i = j;
|
|
420
|
+
continue;
|
|
421
|
+
}
|
|
422
|
+
if (terminator === '~' && params === '201') {
|
|
423
|
+
pasting = false;
|
|
424
|
+
if (pasteBuffering) {
|
|
425
|
+
pasteBuffering = false;
|
|
426
|
+
processPasteBuffer();
|
|
427
|
+
}
|
|
428
|
+
i = j;
|
|
429
|
+
continue;
|
|
430
|
+
}
|
|
431
|
+
// CSI u: \x1b[keycode;modifiers u (Shift+Enter = \x1b[13;2u)
|
|
432
|
+
if (terminator === 'u') {
|
|
433
|
+
const keycode = parseInt(params.split(';')[0], 10);
|
|
434
|
+
if (keycode === 13)
|
|
435
|
+
handleContinuation();
|
|
436
|
+
i = j;
|
|
437
|
+
continue;
|
|
438
|
+
}
|
|
439
|
+
// Arrow keys, Home, End
|
|
440
|
+
if (terminator === 'C') {
|
|
441
|
+
if (cursor < line.length)
|
|
442
|
+
cursor++;
|
|
443
|
+
drawActiveLine();
|
|
444
|
+
}
|
|
445
|
+
else if (terminator === 'D') {
|
|
446
|
+
if (cursor > 0)
|
|
447
|
+
cursor--;
|
|
448
|
+
drawActiveLine();
|
|
449
|
+
}
|
|
450
|
+
else if (terminator === 'A') {
|
|
451
|
+
// Up arrow — navigate to previous line (or history at top)
|
|
452
|
+
if (activeLineIdx > 0) {
|
|
453
|
+
switchToLine(activeLineIdx - 1);
|
|
454
|
+
drawPromptArea();
|
|
455
|
+
}
|
|
456
|
+
else {
|
|
457
|
+
// History navigation — go to older entry
|
|
458
|
+
const len = (0, history_1.historyLength)();
|
|
459
|
+
if (len > 0) {
|
|
460
|
+
let newIdx = historyIndex;
|
|
461
|
+
if (historyIndex === -1) {
|
|
462
|
+
savedInput = allLines().join('\n');
|
|
463
|
+
newIdx = len - 1; // most recent
|
|
464
|
+
}
|
|
465
|
+
else if (historyIndex > 0) {
|
|
466
|
+
newIdx = historyIndex - 1;
|
|
467
|
+
}
|
|
468
|
+
if (newIdx !== historyIndex) {
|
|
469
|
+
historyIndex = newIdx;
|
|
470
|
+
// Clear extra lines before loading history
|
|
471
|
+
if (extraLines > 0) {
|
|
472
|
+
const rows = termHeight();
|
|
473
|
+
const oldTopRow = rows - promptHeight() + 1;
|
|
474
|
+
const newTopRow = rows - BASE_PROMPT_LINES + 1;
|
|
475
|
+
for (let r = oldTopRow; r < newTopRow; r++) {
|
|
476
|
+
process.stdout.write(`\x1b[${r};1H\x1b[2K`);
|
|
477
|
+
}
|
|
478
|
+
extraLines = 0;
|
|
479
|
+
lines.length = 0;
|
|
480
|
+
}
|
|
481
|
+
const text = (0, history_1.getHistoryDisplay)(historyIndex);
|
|
482
|
+
if (text)
|
|
483
|
+
loadIntoPrompt(text);
|
|
484
|
+
}
|
|
485
|
+
}
|
|
486
|
+
}
|
|
487
|
+
}
|
|
488
|
+
else if (terminator === 'B') {
|
|
489
|
+
// Down arrow — navigate to next line (or history at bottom)
|
|
490
|
+
const totalLines = lines.length + 1;
|
|
491
|
+
if (activeLineIdx < totalLines - 1) {
|
|
492
|
+
switchToLine(activeLineIdx + 1);
|
|
493
|
+
drawPromptArea();
|
|
494
|
+
}
|
|
495
|
+
else if (historyIndex >= 0) {
|
|
496
|
+
// History navigation — go to newer entry or back to current input
|
|
497
|
+
const len = (0, history_1.historyLength)();
|
|
498
|
+
if (historyIndex < len - 1) {
|
|
499
|
+
historyIndex++;
|
|
500
|
+
// Clear extra lines before loading
|
|
501
|
+
if (extraLines > 0) {
|
|
502
|
+
const rows = termHeight();
|
|
503
|
+
const oldTopRow = rows - promptHeight() + 1;
|
|
504
|
+
const newTopRow = rows - BASE_PROMPT_LINES + 1;
|
|
505
|
+
for (let r = oldTopRow; r < newTopRow; r++) {
|
|
506
|
+
process.stdout.write(`\x1b[${r};1H\x1b[2K`);
|
|
507
|
+
}
|
|
508
|
+
extraLines = 0;
|
|
509
|
+
lines.length = 0;
|
|
510
|
+
}
|
|
511
|
+
const text = (0, history_1.getHistoryDisplay)(historyIndex);
|
|
512
|
+
if (text)
|
|
513
|
+
loadIntoPrompt(text);
|
|
514
|
+
}
|
|
515
|
+
else {
|
|
516
|
+
// Back to current input
|
|
517
|
+
historyIndex = -1;
|
|
518
|
+
if (extraLines > 0) {
|
|
519
|
+
const rows = termHeight();
|
|
520
|
+
const oldTopRow = rows - promptHeight() + 1;
|
|
521
|
+
const newTopRow = rows - BASE_PROMPT_LINES + 1;
|
|
522
|
+
for (let r = oldTopRow; r < newTopRow; r++) {
|
|
523
|
+
process.stdout.write(`\x1b[${r};1H\x1b[2K`);
|
|
524
|
+
}
|
|
525
|
+
extraLines = 0;
|
|
526
|
+
lines.length = 0;
|
|
527
|
+
}
|
|
528
|
+
loadIntoPrompt(savedInput);
|
|
529
|
+
savedInput = '';
|
|
530
|
+
}
|
|
531
|
+
}
|
|
532
|
+
}
|
|
533
|
+
else if (terminator === '~' && params === '3') {
|
|
534
|
+
// Delete key — forward delete
|
|
535
|
+
if (cursor < line.length) {
|
|
536
|
+
line = line.slice(0, cursor) + line.slice(cursor + 1);
|
|
537
|
+
drawActiveLine();
|
|
538
|
+
}
|
|
539
|
+
else {
|
|
540
|
+
// At EOL — merge next line into current
|
|
541
|
+
const totalLines = lines.length + 1;
|
|
542
|
+
if (activeLineIdx < totalLines - 1) {
|
|
543
|
+
const all = allLines();
|
|
544
|
+
line += all[activeLineIdx + 1];
|
|
545
|
+
all.splice(activeLineIdx + 1, 1);
|
|
546
|
+
lines.length = 0;
|
|
547
|
+
for (let li = 0; li < all.length; li++) {
|
|
548
|
+
if (li !== activeLineIdx)
|
|
549
|
+
lines.push(all[li]);
|
|
550
|
+
}
|
|
551
|
+
const rows = termHeight();
|
|
552
|
+
const oldTopRow = rows - promptHeight() + 1;
|
|
553
|
+
extraLines = lines.length;
|
|
554
|
+
process.stdout.write(`\x1b[${oldTopRow};1H\x1b[2K`);
|
|
555
|
+
drawPromptArea();
|
|
556
|
+
}
|
|
557
|
+
}
|
|
558
|
+
}
|
|
559
|
+
else if (terminator === 'H') {
|
|
560
|
+
cursor = 0;
|
|
561
|
+
drawActiveLine();
|
|
562
|
+
}
|
|
563
|
+
else if (terminator === 'F') {
|
|
564
|
+
cursor = line.length;
|
|
565
|
+
drawActiveLine();
|
|
566
|
+
}
|
|
567
|
+
i = j;
|
|
568
|
+
continue;
|
|
569
|
+
}
|
|
570
|
+
i += 1;
|
|
571
|
+
continue;
|
|
572
|
+
}
|
|
573
|
+
// Buffer paste content instead of processing immediately
|
|
574
|
+
if (pasteBuffering) {
|
|
575
|
+
pasteBuffer += ch;
|
|
576
|
+
continue;
|
|
577
|
+
}
|
|
578
|
+
// Ctrl+C
|
|
579
|
+
if (code === 3) {
|
|
580
|
+
if (sigintHandler)
|
|
581
|
+
sigintHandler();
|
|
582
|
+
continue;
|
|
583
|
+
}
|
|
584
|
+
// Ctrl+D
|
|
585
|
+
if (code === 4) {
|
|
586
|
+
if (line.length === 0 && lines.length === 0) {
|
|
587
|
+
if (closeHandler)
|
|
588
|
+
closeHandler();
|
|
589
|
+
}
|
|
590
|
+
continue;
|
|
591
|
+
}
|
|
592
|
+
// Enter
|
|
593
|
+
if (ch === '\r' || ch === '\n') {
|
|
594
|
+
if (line.endsWith('\\')) {
|
|
595
|
+
line = line.slice(0, -1);
|
|
596
|
+
handleContinuation();
|
|
597
|
+
continue;
|
|
598
|
+
}
|
|
599
|
+
const displayInput = allLines().join('\n');
|
|
600
|
+
const fullInput = pasteBlocks.length > 0 ? allLinesExpanded().join('\n') : displayInput;
|
|
601
|
+
// Save to history (skip empty and question prompts)
|
|
602
|
+
if (!questionCb && fullInput.trim()) {
|
|
603
|
+
(0, history_1.addHistory)(fullInput, displayInput);
|
|
604
|
+
}
|
|
605
|
+
// Clear released prompt area rows before shrinking
|
|
606
|
+
if (extraLines > 0) {
|
|
607
|
+
const rows = termHeight();
|
|
608
|
+
const oldTopRow = rows - promptHeight() + 1;
|
|
609
|
+
const newTopRow = rows - BASE_PROMPT_LINES + 1;
|
|
610
|
+
for (let r = oldTopRow; r < newTopRow; r++) {
|
|
611
|
+
process.stdout.write(`\x1b[${r};1H\x1b[2K`);
|
|
612
|
+
}
|
|
613
|
+
}
|
|
614
|
+
lines.length = 0;
|
|
615
|
+
line = '';
|
|
616
|
+
cursor = 0;
|
|
617
|
+
activeLineIdx = 0;
|
|
618
|
+
extraLines = 0;
|
|
619
|
+
pasteBlocks.length = 0;
|
|
620
|
+
pasteCounter = 0;
|
|
621
|
+
historyIndex = -1;
|
|
622
|
+
savedInput = '';
|
|
623
|
+
// Redraw prompt area at base size
|
|
624
|
+
drawPromptArea();
|
|
625
|
+
if (questionCb) {
|
|
626
|
+
const cb = questionCb;
|
|
627
|
+
questionCb = null;
|
|
628
|
+
cb(fullInput);
|
|
629
|
+
}
|
|
630
|
+
else if (submitHandler) {
|
|
631
|
+
submitHandler(fullInput, displayInput);
|
|
632
|
+
}
|
|
633
|
+
continue;
|
|
634
|
+
}
|
|
635
|
+
// Backspace
|
|
636
|
+
if (code === 127 || code === 8) {
|
|
637
|
+
if (cursor > 0) {
|
|
638
|
+
line = line.slice(0, cursor - 1) + line.slice(cursor);
|
|
639
|
+
cursor--;
|
|
640
|
+
drawActiveLine();
|
|
641
|
+
}
|
|
642
|
+
else if (activeLineIdx > 0) {
|
|
643
|
+
// At start of line — merge into previous line (unwrap)
|
|
644
|
+
const all = allLines();
|
|
645
|
+
const prevContent = all[activeLineIdx - 1];
|
|
646
|
+
all[activeLineIdx - 1] = prevContent + all[activeLineIdx];
|
|
647
|
+
all.splice(activeLineIdx, 1);
|
|
648
|
+
activeLineIdx--;
|
|
649
|
+
line = all[activeLineIdx];
|
|
650
|
+
cursor = prevContent.length;
|
|
651
|
+
lines.length = 0;
|
|
652
|
+
for (let li = 0; li < all.length; li++) {
|
|
653
|
+
if (li !== activeLineIdx)
|
|
654
|
+
lines.push(all[li]);
|
|
655
|
+
}
|
|
656
|
+
const rows = termHeight();
|
|
657
|
+
const oldTopRow = rows - promptHeight() + 1;
|
|
658
|
+
extraLines = lines.length;
|
|
659
|
+
process.stdout.write(`\x1b[${oldTopRow};1H\x1b[2K`);
|
|
660
|
+
drawPromptArea();
|
|
661
|
+
}
|
|
662
|
+
continue;
|
|
663
|
+
}
|
|
664
|
+
// Ctrl+A — home
|
|
665
|
+
if (code === 1) {
|
|
666
|
+
cursor = 0;
|
|
667
|
+
drawActiveLine();
|
|
668
|
+
continue;
|
|
669
|
+
}
|
|
670
|
+
// Ctrl+E — end
|
|
671
|
+
if (code === 5) {
|
|
672
|
+
cursor = line.length;
|
|
673
|
+
drawActiveLine();
|
|
674
|
+
continue;
|
|
675
|
+
}
|
|
676
|
+
// Ctrl+K — kill to end
|
|
677
|
+
if (code === 11) {
|
|
678
|
+
line = line.slice(0, cursor);
|
|
679
|
+
drawActiveLine();
|
|
680
|
+
continue;
|
|
681
|
+
}
|
|
682
|
+
// Ctrl+U — kill to start
|
|
683
|
+
if (code === 21) {
|
|
684
|
+
line = line.slice(cursor);
|
|
685
|
+
cursor = 0;
|
|
686
|
+
drawActiveLine();
|
|
687
|
+
continue;
|
|
688
|
+
}
|
|
689
|
+
// Ctrl+W — delete word back
|
|
690
|
+
if (code === 23) {
|
|
691
|
+
const before = line.slice(0, cursor);
|
|
692
|
+
const trimmed = before.replace(/\s*\S+\s*$/, '');
|
|
693
|
+
line = trimmed + line.slice(cursor);
|
|
694
|
+
cursor = trimmed.length;
|
|
695
|
+
drawActiveLine();
|
|
696
|
+
continue;
|
|
697
|
+
}
|
|
698
|
+
// Tab — ignore
|
|
699
|
+
if (code === 9)
|
|
700
|
+
continue;
|
|
701
|
+
// Printable character
|
|
702
|
+
if (code >= 32) {
|
|
703
|
+
line = line.slice(0, cursor) + ch + line.slice(cursor);
|
|
704
|
+
cursor++;
|
|
705
|
+
drawActiveLine();
|
|
706
|
+
}
|
|
707
|
+
}
|
|
708
|
+
}
|
|
709
|
+
// ── Resize handler ──
|
|
710
|
+
process.stdout.on('resize', () => {
|
|
711
|
+
if (!promptActive)
|
|
712
|
+
return;
|
|
713
|
+
drawPromptArea();
|
|
714
|
+
});
|
|
715
|
+
// ── question() for rl compatibility ──
|
|
716
|
+
function question(prompt, cb) {
|
|
717
|
+
questionPromptText = prompt;
|
|
718
|
+
questionCb = cb;
|
|
719
|
+
drawActiveLine();
|
|
720
|
+
}
|
|
721
|
+
return {
|
|
722
|
+
rl: { question },
|
|
723
|
+
onSubmit(handler) { submitHandler = handler; },
|
|
724
|
+
onClose(handler) { closeHandler = handler; },
|
|
725
|
+
onSigint(handler) { sigintHandler = handler; },
|
|
726
|
+
setStatus(text) {
|
|
727
|
+
currentStatus = text;
|
|
728
|
+
if (promptActive)
|
|
729
|
+
drawStatusLine();
|
|
730
|
+
},
|
|
731
|
+
updateStatus(text) { currentStatus = text; },
|
|
732
|
+
writeAbove,
|
|
733
|
+
prompt() {
|
|
734
|
+
if (!promptActive) {
|
|
735
|
+
activate();
|
|
736
|
+
}
|
|
737
|
+
else {
|
|
738
|
+
line = '';
|
|
739
|
+
cursor = 0;
|
|
740
|
+
activeLineIdx = 0;
|
|
741
|
+
extraLines = 0;
|
|
742
|
+
lines.length = 0;
|
|
743
|
+
pasteBlocks.length = 0;
|
|
744
|
+
pasteCounter = 0;
|
|
745
|
+
historyIndex = -1;
|
|
746
|
+
savedInput = '';
|
|
747
|
+
drawPromptArea();
|
|
748
|
+
}
|
|
749
|
+
},
|
|
750
|
+
close() {
|
|
751
|
+
closed = true;
|
|
752
|
+
promptActive = false;
|
|
753
|
+
extraLines = 0;
|
|
754
|
+
process.stdout.write('\x1b[?2004l'); // disable bracketed paste mode
|
|
755
|
+
process.stdin.removeListener('data', onKeypress);
|
|
756
|
+
if (process.stdin.isTTY) {
|
|
757
|
+
process.stdin.setRawMode(false);
|
|
758
|
+
}
|
|
759
|
+
const rows = termHeight();
|
|
760
|
+
process.stdout.write(`\x1b[${rows};1H\n`);
|
|
761
|
+
},
|
|
762
|
+
};
|
|
763
|
+
}
|
|
764
|
+
//# sourceMappingURL=prompt.js.map
|