opencode-miniterm 1.0.5 → 1.0.6
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/package.json +2 -1
- package/src/ansi.ts +2 -0
- package/src/commands/quit.ts +10 -1
- package/src/index.ts +303 -284
- package/test/README.md +164 -0
- package/test/render.test.ts +0 -1
- package/test/tmux-menu-examples.ts +119 -0
- package/test/tmux-readline.test.ts +518 -0
package/src/index.ts
CHANGED
|
@@ -6,7 +6,7 @@ import { mkdir } from "node:fs/promises";
|
|
|
6
6
|
import { glob } from "node:fs/promises";
|
|
7
7
|
import { stat } from "node:fs/promises";
|
|
8
8
|
import { open } from "node:fs/promises";
|
|
9
|
-
import readline from "node:readline";
|
|
9
|
+
import readline, { type Key } from "node:readline";
|
|
10
10
|
import * as ansi from "./ansi";
|
|
11
11
|
import agentsCommand from "./commands/agents";
|
|
12
12
|
import debugCommand from "./commands/debug";
|
|
@@ -46,6 +46,7 @@ const SLASH_COMMANDS = [
|
|
|
46
46
|
runCommand,
|
|
47
47
|
];
|
|
48
48
|
|
|
49
|
+
let server: Awaited<ReturnType<typeof createOpencodeServer>> | undefined;
|
|
49
50
|
let client: ReturnType<typeof createOpencodeClient>;
|
|
50
51
|
|
|
51
52
|
let processing = true;
|
|
@@ -92,7 +93,6 @@ async function main() {
|
|
|
92
93
|
|
|
93
94
|
console.log(`\n${ansi.BRIGHT_BLACK}Connecting to OpenCode server...${ansi.RESET}\n`);
|
|
94
95
|
|
|
95
|
-
let server: Awaited<ReturnType<typeof createOpencodeServer>> | undefined;
|
|
96
96
|
try {
|
|
97
97
|
server = await createOpencodeServer();
|
|
98
98
|
} catch {
|
|
@@ -112,10 +112,8 @@ async function main() {
|
|
|
112
112
|
});
|
|
113
113
|
|
|
114
114
|
process.on("SIGINT", () => {
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
server?.close();
|
|
118
|
-
process.exit(0);
|
|
115
|
+
process.stdout.write("\n");
|
|
116
|
+
shutdown();
|
|
119
117
|
});
|
|
120
118
|
|
|
121
119
|
try {
|
|
@@ -135,12 +133,11 @@ async function main() {
|
|
|
135
133
|
|
|
136
134
|
await updateSessionTitle();
|
|
137
135
|
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
const activeDisplay = await getActiveDisplay(client);
|
|
136
|
+
history = await loadSessionHistory();
|
|
141
137
|
|
|
142
138
|
process.stdout.write(`${ansi.CLEAR_SCREEN_UP}${ansi.CLEAR_FROM_CURSOR}`);
|
|
143
139
|
process.stdout.write(ansi.CURSOR_HOME);
|
|
140
|
+
const activeDisplay = await getActiveDisplay(client);
|
|
144
141
|
console.log(activeDisplay);
|
|
145
142
|
if (!isNewSession) {
|
|
146
143
|
console.log("Resumed last session");
|
|
@@ -150,311 +147,327 @@ async function main() {
|
|
|
150
147
|
|
|
151
148
|
const rl = readline.createInterface({
|
|
152
149
|
input: process.stdin,
|
|
153
|
-
output:
|
|
150
|
+
output: undefined,
|
|
154
151
|
});
|
|
155
152
|
|
|
156
153
|
readline.emitKeypressEvents(process.stdin);
|
|
157
154
|
if (process.stdin.setRawMode) {
|
|
158
155
|
process.stdin.setRawMode(true);
|
|
159
156
|
}
|
|
157
|
+
process.stdout.write(ansi.DISABLE_LINE_WRAP);
|
|
160
158
|
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
let history: string[] = sessionHistory;
|
|
165
|
-
let historyIndex = history.length;
|
|
166
|
-
let selectedCompletion = 0;
|
|
167
|
-
let showCompletions = false;
|
|
168
|
-
let completionCycling = false;
|
|
169
|
-
let lastSpaceTime = 0;
|
|
170
|
-
let currentInputBuffer: string | null = null;
|
|
171
|
-
|
|
172
|
-
const getCompletions = async (text: string): Promise<string[]> => {
|
|
173
|
-
if (text.startsWith("/")) {
|
|
174
|
-
return ["/help", ...SLASH_COMMANDS.map((c) => c.name)].filter((cmd) =>
|
|
175
|
-
cmd.startsWith(text),
|
|
176
|
-
);
|
|
177
|
-
}
|
|
159
|
+
process.stdin.on("keypress", async (str, key) => {
|
|
160
|
+
handleKeyPress(str, key);
|
|
161
|
+
});
|
|
178
162
|
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
const files = await getFileCompletions(pattern);
|
|
187
|
-
return files.map((file: string) => text.replace(/@[^\s]*$/, "@" + file));
|
|
188
|
-
}
|
|
163
|
+
writePrompt();
|
|
164
|
+
} catch (error: any) {
|
|
165
|
+
console.error("Error:", error.message);
|
|
166
|
+
server?.close();
|
|
167
|
+
process.exit(1);
|
|
168
|
+
}
|
|
169
|
+
}
|
|
189
170
|
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
let oldWrappedRows = 0;
|
|
194
|
-
const renderLine = (): void => {
|
|
195
|
-
const consoleWidth = process.stdout.columns || 80;
|
|
196
|
-
const totalLength = 2 + inputBuffer.length + 1;
|
|
197
|
-
const wrappedRows = Math.floor(totalLength / consoleWidth);
|
|
198
|
-
readline.cursorTo(process.stdout, 0);
|
|
199
|
-
if (oldWrappedRows > 0) {
|
|
200
|
-
readline.moveCursor(process.stdout, 0, -oldWrappedRows);
|
|
201
|
-
}
|
|
202
|
-
readline.clearScreenDown(process.stdout);
|
|
203
|
-
oldWrappedRows = wrappedRows;
|
|
171
|
+
// ====================
|
|
172
|
+
// HANDLE INPUT
|
|
173
|
+
// ====================
|
|
204
174
|
|
|
205
|
-
|
|
206
|
-
|
|
175
|
+
let inputBuffer = "";
|
|
176
|
+
let cursorPosition = 0;
|
|
177
|
+
let completions: string[] = [];
|
|
178
|
+
let history: string[] = [];
|
|
179
|
+
let historyIndex = history.length;
|
|
180
|
+
let selectedCompletion = 0;
|
|
181
|
+
let completionCycling = false;
|
|
182
|
+
let lastSpaceTime = 0;
|
|
183
|
+
let currentInputBuffer: string | null = null;
|
|
184
|
+
|
|
185
|
+
let oldWrappedRows = 0;
|
|
186
|
+
function renderLine(): void {
|
|
187
|
+
const consoleWidth = process.stdout.columns || 80;
|
|
188
|
+
|
|
189
|
+
readline.cursorTo(process.stdout, 0);
|
|
190
|
+
if (oldWrappedRows > 0) {
|
|
191
|
+
readline.moveCursor(process.stdout, 0, -oldWrappedRows);
|
|
192
|
+
}
|
|
193
|
+
readline.clearScreenDown(process.stdout);
|
|
207
194
|
|
|
208
|
-
|
|
209
|
-
const targetRow = Math.floor(totalPosition / consoleWidth);
|
|
210
|
-
const targetCol = totalPosition % consoleWidth;
|
|
195
|
+
writePrompt();
|
|
211
196
|
|
|
212
|
-
|
|
213
|
-
|
|
197
|
+
let currentCol = 2;
|
|
198
|
+
let row = 0;
|
|
199
|
+
for (let i = 0; i < inputBuffer.length; i++) {
|
|
200
|
+
if (currentCol >= consoleWidth) {
|
|
201
|
+
process.stdout.write("\n");
|
|
202
|
+
currentCol = 0;
|
|
203
|
+
row++;
|
|
204
|
+
}
|
|
205
|
+
process.stdout.write(inputBuffer[i]!);
|
|
206
|
+
currentCol++;
|
|
207
|
+
}
|
|
208
|
+
oldWrappedRows = row;
|
|
209
|
+
|
|
210
|
+
let targetRow = 0;
|
|
211
|
+
let targetCol = 0;
|
|
212
|
+
let pos = 2;
|
|
213
|
+
for (let i = 0; i < cursorPosition; i++) {
|
|
214
|
+
if (pos >= consoleWidth) {
|
|
215
|
+
targetRow++;
|
|
216
|
+
pos = 0;
|
|
217
|
+
}
|
|
218
|
+
pos++;
|
|
219
|
+
}
|
|
220
|
+
targetCol = pos;
|
|
214
221
|
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
222
|
+
readline.cursorTo(process.stdout, 0);
|
|
223
|
+
if (targetRow > 0) {
|
|
224
|
+
process.stdout.write(`\x1b[${targetRow}B`);
|
|
225
|
+
}
|
|
226
|
+
readline.cursorTo(process.stdout, targetCol);
|
|
227
|
+
}
|
|
220
228
|
|
|
221
|
-
|
|
222
|
-
|
|
229
|
+
async function handleKeyPress(str: string, key: Key) {
|
|
230
|
+
if (key.ctrl && key.name === "c") {
|
|
231
|
+
process.stdout.write("\n");
|
|
232
|
+
shutdown();
|
|
233
|
+
return;
|
|
234
|
+
}
|
|
223
235
|
|
|
224
|
-
|
|
225
|
-
|
|
236
|
+
for (let command of SLASH_COMMANDS) {
|
|
237
|
+
if (command.running && command.handleKey) {
|
|
238
|
+
await command.handleKey(client, key, str);
|
|
239
|
+
return;
|
|
240
|
+
}
|
|
241
|
+
}
|
|
226
242
|
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
243
|
+
switch (key.name) {
|
|
244
|
+
case "up": {
|
|
245
|
+
if (historyIndex === history.length) {
|
|
246
|
+
currentInputBuffer = inputBuffer;
|
|
230
247
|
}
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
248
|
+
if (history.length > 0) {
|
|
249
|
+
if (historyIndex > 0) {
|
|
250
|
+
historyIndex--;
|
|
251
|
+
inputBuffer = history[historyIndex]!;
|
|
252
|
+
} else {
|
|
253
|
+
historyIndex = Math.max(-1, historyIndex - 1);
|
|
254
|
+
inputBuffer = "";
|
|
255
|
+
}
|
|
237
256
|
cursorPosition = inputBuffer.length;
|
|
238
257
|
renderLine();
|
|
239
|
-
}
|
|
240
|
-
|
|
241
|
-
|
|
258
|
+
}
|
|
259
|
+
return;
|
|
260
|
+
}
|
|
261
|
+
case "down": {
|
|
262
|
+
if (history.length > 0) {
|
|
263
|
+
if (historyIndex < history.length - 1) {
|
|
264
|
+
historyIndex++;
|
|
265
|
+
inputBuffer = history[historyIndex]!;
|
|
266
|
+
} else {
|
|
267
|
+
historyIndex = history.length;
|
|
268
|
+
inputBuffer = currentInputBuffer || "";
|
|
269
|
+
currentInputBuffer = null;
|
|
270
|
+
}
|
|
242
271
|
cursorPosition = inputBuffer.length;
|
|
243
272
|
renderLine();
|
|
244
273
|
}
|
|
245
|
-
|
|
274
|
+
return;
|
|
275
|
+
}
|
|
276
|
+
case "tab": {
|
|
277
|
+
if (!completionCycling) {
|
|
278
|
+
await handleTab();
|
|
279
|
+
}
|
|
280
|
+
if (completionCycling && completions.length > 0) {
|
|
281
|
+
await handleTab();
|
|
282
|
+
}
|
|
283
|
+
return;
|
|
284
|
+
}
|
|
285
|
+
case "escape": {
|
|
286
|
+
if (isRequestActive) {
|
|
287
|
+
if (state.sessionID) {
|
|
288
|
+
client.session.abort({ path: { id: state.sessionID } }).catch(() => {});
|
|
289
|
+
}
|
|
290
|
+
stopAnimation();
|
|
291
|
+
process.stdout.write(ansi.CURSOR_SHOW);
|
|
292
|
+
process.stdout.write(`\r ${ansi.BRIGHT_BLACK}Cancelled request${ansi.RESET}\n`);
|
|
293
|
+
writePrompt();
|
|
294
|
+
isRequestActive = false;
|
|
295
|
+
} else {
|
|
296
|
+
inputBuffer = "";
|
|
297
|
+
cursorPosition = 0;
|
|
298
|
+
currentInputBuffer = null;
|
|
299
|
+
renderLine();
|
|
300
|
+
}
|
|
301
|
+
return;
|
|
302
|
+
}
|
|
303
|
+
case "return": {
|
|
304
|
+
await acceptInput();
|
|
305
|
+
return;
|
|
306
|
+
}
|
|
307
|
+
case "backspace": {
|
|
308
|
+
if (cursorPosition > 0) {
|
|
309
|
+
inputBuffer = inputBuffer.slice(0, cursorPosition - 1) + inputBuffer.slice(cursorPosition);
|
|
310
|
+
cursorPosition--;
|
|
311
|
+
currentInputBuffer = null;
|
|
312
|
+
}
|
|
313
|
+
break;
|
|
314
|
+
}
|
|
315
|
+
case "delete": {
|
|
316
|
+
if (cursorPosition < inputBuffer.length) {
|
|
317
|
+
inputBuffer = inputBuffer.slice(0, cursorPosition) + inputBuffer.slice(cursorPosition + 1);
|
|
318
|
+
currentInputBuffer = null;
|
|
319
|
+
}
|
|
320
|
+
break;
|
|
321
|
+
}
|
|
322
|
+
case "left": {
|
|
323
|
+
if (key.meta) {
|
|
324
|
+
cursorPosition = findPreviousWordBoundary(inputBuffer, cursorPosition);
|
|
325
|
+
} else if (cursorPosition > 0) {
|
|
326
|
+
cursorPosition--;
|
|
327
|
+
}
|
|
328
|
+
break;
|
|
329
|
+
}
|
|
330
|
+
case "right": {
|
|
331
|
+
if (key.meta) {
|
|
332
|
+
cursorPosition = findNextWordBoundary(inputBuffer, cursorPosition);
|
|
333
|
+
} else if (cursorPosition < inputBuffer.length) {
|
|
334
|
+
cursorPosition++;
|
|
335
|
+
}
|
|
336
|
+
break;
|
|
337
|
+
}
|
|
338
|
+
default: {
|
|
339
|
+
if (str === " ") {
|
|
340
|
+
const now = Date.now();
|
|
341
|
+
if (
|
|
342
|
+
now - lastSpaceTime < 500 &&
|
|
343
|
+
cursorPosition > 0 &&
|
|
344
|
+
inputBuffer[cursorPosition - 1] === " "
|
|
345
|
+
) {
|
|
346
|
+
inputBuffer =
|
|
347
|
+
inputBuffer.slice(0, cursorPosition - 1) + ". " + inputBuffer.slice(cursorPosition);
|
|
348
|
+
cursorPosition += 1;
|
|
349
|
+
} else {
|
|
350
|
+
inputBuffer =
|
|
351
|
+
inputBuffer.slice(0, cursorPosition) + str + inputBuffer.slice(cursorPosition);
|
|
352
|
+
cursorPosition += str.length;
|
|
353
|
+
}
|
|
354
|
+
lastSpaceTime = now;
|
|
355
|
+
} else if (str) {
|
|
356
|
+
inputBuffer =
|
|
357
|
+
inputBuffer.slice(0, cursorPosition) + str + inputBuffer.slice(cursorPosition);
|
|
358
|
+
cursorPosition += str.length;
|
|
359
|
+
}
|
|
360
|
+
currentInputBuffer = null;
|
|
361
|
+
}
|
|
362
|
+
}
|
|
246
363
|
|
|
247
|
-
|
|
248
|
-
|
|
364
|
+
completionCycling = false;
|
|
365
|
+
completions = [];
|
|
366
|
+
renderLine();
|
|
367
|
+
}
|
|
249
368
|
|
|
250
|
-
|
|
369
|
+
async function handleTab(): Promise<void> {
|
|
370
|
+
const potentialCompletions = await getCompletions(inputBuffer);
|
|
251
371
|
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
completions = [];
|
|
257
|
-
currentInputBuffer = null;
|
|
372
|
+
if (potentialCompletions.length === 0) {
|
|
373
|
+
completionCycling = false;
|
|
374
|
+
return;
|
|
375
|
+
}
|
|
258
376
|
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
console.log();
|
|
274
|
-
return;
|
|
275
|
-
} else if (input.startsWith("/")) {
|
|
276
|
-
const parts = input.match(/(\/[^\s]+)\s*(.*)/)!;
|
|
277
|
-
if (parts) {
|
|
278
|
-
const commandName = parts[1];
|
|
279
|
-
const extra = parts[2]?.trim();
|
|
280
|
-
for (let command of SLASH_COMMANDS) {
|
|
281
|
-
if (command.name === commandName) {
|
|
282
|
-
await command.run(client, state, extra);
|
|
283
|
-
return;
|
|
284
|
-
}
|
|
285
|
-
}
|
|
286
|
-
}
|
|
287
|
-
return;
|
|
288
|
-
}
|
|
377
|
+
if (!completionCycling) {
|
|
378
|
+
completions = potentialCompletions;
|
|
379
|
+
selectedCompletion = 0;
|
|
380
|
+
completionCycling = true;
|
|
381
|
+
inputBuffer = completions[0]!;
|
|
382
|
+
cursorPosition = inputBuffer.length;
|
|
383
|
+
renderLine();
|
|
384
|
+
} else {
|
|
385
|
+
selectedCompletion = (selectedCompletion + 1) % completions.length;
|
|
386
|
+
inputBuffer = completions[selectedCompletion]!;
|
|
387
|
+
cursorPosition = inputBuffer.length;
|
|
388
|
+
renderLine();
|
|
389
|
+
}
|
|
390
|
+
}
|
|
289
391
|
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
console.log(`📝 ${ansi.BRIGHT_BLACK}Logging to ${getLogDir()}\n${ansi.RESET}`);
|
|
295
|
-
}
|
|
296
|
-
await sendMessage(state.sessionID, input);
|
|
297
|
-
isRequestActive = false;
|
|
298
|
-
} catch (error: any) {
|
|
299
|
-
isRequestActive = false;
|
|
300
|
-
if (error.message !== "Request cancelled") {
|
|
301
|
-
stopAnimation();
|
|
302
|
-
console.error("Error:", error.message);
|
|
303
|
-
}
|
|
304
|
-
}
|
|
305
|
-
}
|
|
392
|
+
async function getCompletions(text: string): Promise<string[]> {
|
|
393
|
+
if (text.startsWith("/")) {
|
|
394
|
+
return ["/help", ...SLASH_COMMANDS.map((c) => c.name)].filter((cmd) => cmd.startsWith(text));
|
|
395
|
+
}
|
|
306
396
|
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
397
|
+
const atMatch = text.match(/(@[^\s]*)$/);
|
|
398
|
+
if (atMatch) {
|
|
399
|
+
const prefix = atMatch[0]!;
|
|
400
|
+
const searchPattern = prefix.slice(1);
|
|
401
|
+
const pattern = searchPattern.includes("/") ? searchPattern + "*" : "**/" + searchPattern + "*";
|
|
402
|
+
const files = await getFileCompletions(pattern);
|
|
403
|
+
return files.map((file: string) => text.replace(/@[^\s]*$/, "@" + file));
|
|
404
|
+
}
|
|
311
405
|
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
if (command.running && command.handleKey) {
|
|
315
|
-
await command.handleKey(client, key, str);
|
|
316
|
-
return;
|
|
317
|
-
}
|
|
318
|
-
}
|
|
406
|
+
return [];
|
|
407
|
+
}
|
|
319
408
|
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
}
|
|
344
|
-
|
|
345
|
-
inputBuffer = currentInputBuffer || "";
|
|
346
|
-
currentInputBuffer = null;
|
|
347
|
-
}
|
|
348
|
-
cursorPosition = inputBuffer.length;
|
|
349
|
-
renderLine();
|
|
350
|
-
}
|
|
351
|
-
return;
|
|
352
|
-
}
|
|
353
|
-
case "tab": {
|
|
354
|
-
if (!completionCycling) {
|
|
355
|
-
await handleTab();
|
|
356
|
-
}
|
|
357
|
-
if (completionCycling && completions.length > 0) {
|
|
358
|
-
await handleTab();
|
|
359
|
-
}
|
|
360
|
-
return;
|
|
361
|
-
}
|
|
362
|
-
case "escape": {
|
|
363
|
-
if (isRequestActive) {
|
|
364
|
-
if (state.sessionID) {
|
|
365
|
-
client.session.abort({ path: { id: state.sessionID } }).catch(() => {});
|
|
366
|
-
}
|
|
367
|
-
stopAnimation();
|
|
368
|
-
process.stdout.write(ansi.CURSOR_SHOW);
|
|
369
|
-
process.stdout.write(`\r ${ansi.BRIGHT_BLACK}Cancelled request${ansi.RESET}\n`);
|
|
370
|
-
writePrompt();
|
|
371
|
-
isRequestActive = false;
|
|
372
|
-
} else {
|
|
373
|
-
inputBuffer = "";
|
|
374
|
-
cursorPosition = 0;
|
|
375
|
-
currentInputBuffer = null;
|
|
376
|
-
renderLine();
|
|
377
|
-
}
|
|
378
|
-
return;
|
|
379
|
-
}
|
|
380
|
-
case "return": {
|
|
381
|
-
await acceptInput();
|
|
382
|
-
return;
|
|
383
|
-
}
|
|
384
|
-
case "backspace": {
|
|
385
|
-
if (cursorPosition > 0) {
|
|
386
|
-
inputBuffer =
|
|
387
|
-
inputBuffer.slice(0, cursorPosition - 1) + inputBuffer.slice(cursorPosition);
|
|
388
|
-
cursorPosition--;
|
|
389
|
-
currentInputBuffer = null;
|
|
390
|
-
}
|
|
391
|
-
break;
|
|
392
|
-
}
|
|
393
|
-
case "delete": {
|
|
394
|
-
if (cursorPosition < inputBuffer.length) {
|
|
395
|
-
inputBuffer =
|
|
396
|
-
inputBuffer.slice(0, cursorPosition) + inputBuffer.slice(cursorPosition + 1);
|
|
397
|
-
currentInputBuffer = null;
|
|
398
|
-
}
|
|
399
|
-
break;
|
|
400
|
-
}
|
|
401
|
-
case "left": {
|
|
402
|
-
if (key.meta) {
|
|
403
|
-
cursorPosition = findPreviousWordBoundary(inputBuffer, cursorPosition);
|
|
404
|
-
} else if (cursorPosition > 0) {
|
|
405
|
-
cursorPosition--;
|
|
406
|
-
}
|
|
407
|
-
break;
|
|
408
|
-
}
|
|
409
|
-
case "right": {
|
|
410
|
-
if (key.meta) {
|
|
411
|
-
cursorPosition = findNextWordBoundary(inputBuffer, cursorPosition);
|
|
412
|
-
} else if (cursorPosition < inputBuffer.length) {
|
|
413
|
-
cursorPosition++;
|
|
414
|
-
}
|
|
415
|
-
break;
|
|
409
|
+
async function acceptInput(): Promise<void> {
|
|
410
|
+
process.stdout.write("\n");
|
|
411
|
+
|
|
412
|
+
const input = inputBuffer.trim();
|
|
413
|
+
|
|
414
|
+
inputBuffer = "";
|
|
415
|
+
cursorPosition = 0;
|
|
416
|
+
completionCycling = false;
|
|
417
|
+
completions = [];
|
|
418
|
+
currentInputBuffer = null;
|
|
419
|
+
|
|
420
|
+
if (input) {
|
|
421
|
+
if (history[history.length - 1] !== input) {
|
|
422
|
+
history.push(input);
|
|
423
|
+
}
|
|
424
|
+
historyIndex = history.length;
|
|
425
|
+
try {
|
|
426
|
+
if (input === "/help") {
|
|
427
|
+
process.stdout.write("\n");
|
|
428
|
+
const maxCommandLength = Math.max(...SLASH_COMMANDS.map((c) => c.name.length));
|
|
429
|
+
for (const cmd of SLASH_COMMANDS) {
|
|
430
|
+
const padding = " ".repeat(maxCommandLength - cmd.name.length + 2);
|
|
431
|
+
console.log(
|
|
432
|
+
` ${ansi.BRIGHT_WHITE}${cmd.name}${ansi.RESET}${padding}${ansi.BRIGHT_BLACK}${cmd.description}${ansi.RESET}`,
|
|
433
|
+
);
|
|
416
434
|
}
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
} else {
|
|
432
|
-
inputBuffer =
|
|
433
|
-
inputBuffer.slice(0, cursorPosition) + str + inputBuffer.slice(cursorPosition);
|
|
434
|
-
cursorPosition += str.length;
|
|
435
|
-
}
|
|
436
|
-
lastSpaceTime = now;
|
|
437
|
-
} else {
|
|
438
|
-
inputBuffer =
|
|
439
|
-
inputBuffer.slice(0, cursorPosition) + str + inputBuffer.slice(cursorPosition);
|
|
440
|
-
cursorPosition += str.length;
|
|
435
|
+
console.log();
|
|
436
|
+
writePrompt();
|
|
437
|
+
return;
|
|
438
|
+
} else if (input.startsWith("/")) {
|
|
439
|
+
const parts = input.match(/(\/[^\s]+)\s*(.*)/)!;
|
|
440
|
+
if (parts) {
|
|
441
|
+
const commandName = parts[1];
|
|
442
|
+
const extra = parts[2]?.trim();
|
|
443
|
+
for (let command of SLASH_COMMANDS) {
|
|
444
|
+
if (command.name === commandName) {
|
|
445
|
+
process.stdout.write("\n");
|
|
446
|
+
await command.run(client, state, extra);
|
|
447
|
+
writePrompt();
|
|
448
|
+
return;
|
|
441
449
|
}
|
|
442
|
-
currentInputBuffer = null;
|
|
443
450
|
}
|
|
444
451
|
}
|
|
452
|
+
return;
|
|
445
453
|
}
|
|
446
454
|
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
455
|
+
isRequestActive = true;
|
|
456
|
+
process.stdout.write("\n");
|
|
457
|
+
process.stdout.write(ansi.CURSOR_HIDE);
|
|
458
|
+
startAnimation();
|
|
459
|
+
if (isLoggingEnabled()) {
|
|
460
|
+
console.log(`📝 ${ansi.BRIGHT_BLACK}Logging to ${getLogDir()}\n${ansi.RESET}`);
|
|
461
|
+
}
|
|
462
|
+
await sendMessage(state.sessionID, input);
|
|
463
|
+
isRequestActive = false;
|
|
464
|
+
} catch (error: any) {
|
|
465
|
+
isRequestActive = false;
|
|
466
|
+
if (error.message !== "Request cancelled") {
|
|
467
|
+
stopAnimation();
|
|
468
|
+
console.error("Error:", error.message);
|
|
469
|
+
}
|
|
470
|
+
}
|
|
458
471
|
}
|
|
459
472
|
}
|
|
460
473
|
|
|
@@ -604,11 +617,6 @@ async function sendMessage(sessionID: string, message: string) {
|
|
|
604
617
|
console.log(` ${ansi.BRIGHT_BLACK}Completed in ${durationText}${ansi.RESET}\n`);
|
|
605
618
|
|
|
606
619
|
writePrompt();
|
|
607
|
-
|
|
608
|
-
// HACK:
|
|
609
|
-
setTimeout(() => {
|
|
610
|
-
readline.cursorTo(process.stdout, 2);
|
|
611
|
-
}, 200);
|
|
612
620
|
} catch (error: any) {
|
|
613
621
|
throw error;
|
|
614
622
|
} finally {
|
|
@@ -672,7 +680,6 @@ async function processEvent(event: Event): Promise<void> {
|
|
|
672
680
|
retryInterval = null;
|
|
673
681
|
}
|
|
674
682
|
writePrompt();
|
|
675
|
-
readline.cursorTo(process.stdout, 2);
|
|
676
683
|
}
|
|
677
684
|
if (event.type === "session.status" && event.properties.status.type === "retry") {
|
|
678
685
|
const message = event.properties.status.message;
|
|
@@ -883,6 +890,18 @@ function findLastPart(title: string) {
|
|
|
883
890
|
}
|
|
884
891
|
}
|
|
885
892
|
|
|
893
|
+
function shutdown() {
|
|
894
|
+
if (process.stdin.setRawMode) {
|
|
895
|
+
process.stdin.setRawMode(false);
|
|
896
|
+
}
|
|
897
|
+
process.stdin.destroy();
|
|
898
|
+
process.stdout.write(ansi.ENABLE_LINE_WRAP);
|
|
899
|
+
saveConfig();
|
|
900
|
+
server?.close();
|
|
901
|
+
console.log(`\n${ansi.BRIGHT_BLACK}Goodbye!${ansi.RESET}`);
|
|
902
|
+
process.exit(0);
|
|
903
|
+
}
|
|
904
|
+
|
|
886
905
|
// ====================
|
|
887
906
|
// USER INTERFACE
|
|
888
907
|
// ====================
|