cli-jaw 1.4.10 → 1.4.11
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/bin/commands/chat.js +122 -279
- package/dist/bin/commands/chat.js.map +1 -1
- package/dist/bin/commands/skill.js +11 -21
- package/dist/bin/commands/skill.js.map +1 -1
- package/dist/lib/mcp-sync.js +77 -0
- package/dist/lib/mcp-sync.js.map +1 -1
- package/dist/src/cli/command-context.js +8 -19
- package/dist/src/cli/command-context.js.map +1 -1
- package/dist/src/cli/tui/keymap.js +32 -0
- package/dist/src/cli/tui/keymap.js.map +1 -0
- package/dist/src/cli/tui/overlay.js +156 -0
- package/dist/src/cli/tui/overlay.js.map +1 -0
- package/dist/src/cli/tui/panes.js +33 -0
- package/dist/src/cli/tui/panes.js.map +1 -0
- package/dist/src/cli/tui/renderers.js +40 -0
- package/dist/src/cli/tui/renderers.js.map +1 -0
- package/dist/src/cli/tui/shell.js +58 -0
- package/dist/src/cli/tui/shell.js.map +1 -0
- package/dist/src/cli/tui/store.js +12 -0
- package/dist/src/cli/tui/store.js.map +1 -0
- package/dist/src/core/config.js +13 -1
- package/dist/src/core/config.js.map +1 -1
- package/dist/src/core/settings-merge.js +2 -2
- package/dist/src/core/settings-merge.js.map +1 -1
- package/package.json +1 -1
|
@@ -9,8 +9,13 @@ import fs from 'node:fs';
|
|
|
9
9
|
import { spawnSync } from 'node:child_process';
|
|
10
10
|
import { resolve as resolvePath, dirname } from 'node:path';
|
|
11
11
|
import { fileURLToPath } from 'node:url';
|
|
12
|
-
import { parseCommand, executeCommand
|
|
13
|
-
import { appendNewlineToComposer, appendTextToComposer, backspaceComposer, clearComposer, consumePasteProtocol,
|
|
12
|
+
import { parseCommand, executeCommand } from '../../src/cli/commands.js';
|
|
13
|
+
import { appendNewlineToComposer, appendTextToComposer, backspaceComposer, clearComposer, consumePasteProtocol, flattenComposerForSubmit, getComposerDisplayText, getPlainCommandDraft, getTrailingTextSegment, setBracketedPaste, } from '../../src/cli/tui/composer.js';
|
|
14
|
+
import { classifyKeyAction } from '../../src/cli/tui/keymap.js';
|
|
15
|
+
import { applyResolvedAutocompleteState, clearAutocomplete, closeAutocomplete, makeSelectionKey, popupTotalRows, renderAutocomplete, resolveAutocompleteState, } from '../../src/cli/tui/overlay.js';
|
|
16
|
+
import { clipTextToCols, visualWidth } from '../../src/cli/tui/renderers.js';
|
|
17
|
+
import { cleanupScrollRegion, ensureSpaceBelow, resolveShellLayout, setupScrollRegion } from '../../src/cli/tui/shell.js';
|
|
18
|
+
import { createTuiStore } from '../../src/cli/tui/store.js';
|
|
14
19
|
import { isGitRepo, captureFileSet, diffFileSets, detectIde, getIdeCli, openDiffInIde, getDiffStat, } from '../../src/ide/diff.js';
|
|
15
20
|
const chatCwd = process.cwd();
|
|
16
21
|
const isGit = isGitRepo(chatCwd);
|
|
@@ -291,70 +296,26 @@ else {
|
|
|
291
296
|
const promptPrefix = ` ${accent}\u276F${c.reset} `;
|
|
292
297
|
// ─── Scroll region: fixed footer at bottom ──
|
|
293
298
|
const getRows = () => process.stdout.rows || 24;
|
|
294
|
-
function
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
process.stdout.write(`\x1b[1;${rows - 2}r`);
|
|
298
|
-
// Draw fixed footer at absolute positions
|
|
299
|
-
process.stdout.write(`\x1b[${rows - 1};1H\x1b[2K ${c.dim}${hrLine()}${c.reset}`);
|
|
300
|
-
process.stdout.write(`\x1b[${rows};1H\x1b[2K${footer}`);
|
|
301
|
-
// Move cursor back into scroll region
|
|
302
|
-
process.stdout.write(`\x1b[${rows - 2};1H`);
|
|
299
|
+
function renderBlockSeparator() {
|
|
300
|
+
process.stdout.write('\n');
|
|
301
|
+
console.log(` ${c.dim}${hrLine()}${c.reset}`);
|
|
303
302
|
}
|
|
304
|
-
function
|
|
305
|
-
|
|
306
|
-
// Reset scroll region to full terminal
|
|
307
|
-
process.stdout.write(`\x1b[1;${rows}r`);
|
|
308
|
-
process.stdout.write(`\x1b[${rows};1H\n`);
|
|
303
|
+
function renderAssistantTurnStart() {
|
|
304
|
+
process.stdout.write('\n ');
|
|
309
305
|
}
|
|
310
306
|
function showPrompt() {
|
|
311
307
|
if (typeof closeAutocomplete === 'function')
|
|
312
|
-
closeAutocomplete();
|
|
308
|
+
closeAutocomplete(ac, (chunk) => process.stdout.write(chunk));
|
|
313
309
|
prevLineCount = 1; // reset for fresh prompt
|
|
314
|
-
console.log('');
|
|
315
|
-
console.log(` ${c.dim}${hrLine()}${c.reset}`);
|
|
316
310
|
process.stdout.write(promptPrefix);
|
|
317
311
|
}
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
const stripped = str.replace(/\x1b\[[0-9;]*m/g, '');
|
|
322
|
-
let w = 0;
|
|
323
|
-
for (const ch of stripped) {
|
|
324
|
-
const cp = ch.codePointAt(0);
|
|
325
|
-
if (cp === undefined) {
|
|
326
|
-
w += 1;
|
|
327
|
-
continue;
|
|
328
|
-
}
|
|
329
|
-
// CJK ranges: Hangul, CJK Unified, Fullwidth, etc
|
|
330
|
-
if ((cp >= 0x1100 && cp <= 0x115F) || (cp >= 0x2E80 && cp <= 0x303E) ||
|
|
331
|
-
(cp >= 0x3040 && cp <= 0x33BF) || (cp >= 0x3400 && cp <= 0x4DBF) ||
|
|
332
|
-
(cp >= 0x4E00 && cp <= 0xA4CF) || (cp >= 0xA960 && cp <= 0xA97C) ||
|
|
333
|
-
(cp >= 0xAC00 && cp <= 0xD7AF) || (cp >= 0xD7B0 && cp <= 0xD7FF) ||
|
|
334
|
-
(cp >= 0xF900 && cp <= 0xFAFF) || (cp >= 0xFE30 && cp <= 0xFE6F) ||
|
|
335
|
-
(cp >= 0xFF01 && cp <= 0xFF60) || (cp >= 0xFFE0 && cp <= 0xFFE6) ||
|
|
336
|
-
(cp >= 0x20000 && cp <= 0x2FA1F)) {
|
|
337
|
-
w += 2;
|
|
338
|
-
}
|
|
339
|
-
else {
|
|
340
|
-
w += 1;
|
|
341
|
-
}
|
|
342
|
-
}
|
|
343
|
-
return w;
|
|
312
|
+
function openPromptBlock() {
|
|
313
|
+
renderBlockSeparator();
|
|
314
|
+
showPrompt();
|
|
344
315
|
}
|
|
345
|
-
function
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
let out = '';
|
|
349
|
-
let w = 0;
|
|
350
|
-
for (const ch of str) {
|
|
351
|
-
const cw = visualWidth(ch);
|
|
352
|
-
if (w + cw > maxCols)
|
|
353
|
-
break;
|
|
354
|
-
out += ch;
|
|
355
|
-
w += cw;
|
|
356
|
-
}
|
|
357
|
-
return out;
|
|
316
|
+
function reopenPromptLine() {
|
|
317
|
+
process.stdout.write('\n');
|
|
318
|
+
showPrompt();
|
|
358
319
|
}
|
|
359
320
|
let prevLineCount = 1; // track how many terminal rows input occupied
|
|
360
321
|
function redrawPromptLine() {
|
|
@@ -389,196 +350,48 @@ else {
|
|
|
389
350
|
prevLineCount = totalRows;
|
|
390
351
|
}
|
|
391
352
|
// ─── State ───────────────────────────────
|
|
392
|
-
const
|
|
393
|
-
const
|
|
353
|
+
const store = createTuiStore();
|
|
354
|
+
const composer = store.composer;
|
|
355
|
+
const pasteCapture = store.pasteCapture;
|
|
356
|
+
const panes = store.panes;
|
|
394
357
|
let inputActive = true;
|
|
395
358
|
let streaming = false;
|
|
396
359
|
let commandRunning = false;
|
|
397
360
|
const ESC_WAIT_MS = 70;
|
|
398
361
|
let escPending = false;
|
|
399
362
|
let escTimer = null;
|
|
400
|
-
const ac =
|
|
401
|
-
open: false,
|
|
402
|
-
stage: 'command',
|
|
403
|
-
contextHeader: '',
|
|
404
|
-
items: [],
|
|
405
|
-
selected: 0,
|
|
406
|
-
windowStart: 0,
|
|
407
|
-
visibleRows: 0,
|
|
408
|
-
renderedRows: 0,
|
|
409
|
-
maxRowsCommand: 6,
|
|
410
|
-
maxRowsArgument: 8,
|
|
411
|
-
};
|
|
363
|
+
const ac = store.autocomplete;
|
|
412
364
|
function getMaxPopupRows() {
|
|
413
365
|
// Scroll region ends at rows-2 (rows-1/rows are fixed footer).
|
|
414
366
|
// Prompt baseline at rows-2 can be lifted up to rows-3 lines.
|
|
415
367
|
return Math.max(0, getRows() - 3);
|
|
416
368
|
}
|
|
417
|
-
function makeSelectionKey(item, stage) {
|
|
418
|
-
if (!item)
|
|
419
|
-
return '';
|
|
420
|
-
const base = item.command ? `${item.command}:${item.name}` : item.name;
|
|
421
|
-
return `${stage}:${base}`;
|
|
422
|
-
}
|
|
423
|
-
function popupTotalRows(state) {
|
|
424
|
-
if (!state?.open)
|
|
425
|
-
return 0;
|
|
426
|
-
return (state.visibleRows || 0) + (state.contextHeader ? 1 : 0);
|
|
427
|
-
}
|
|
428
|
-
// ─ Phase 1c: use terminal natural scrolling to create space below ─
|
|
429
|
-
// Prints \n within scroll region to push content up if needed,
|
|
430
|
-
// then CSI A back to prompt row. No content is overwritten.
|
|
431
|
-
function ensureSpaceBelow(n) {
|
|
432
|
-
if (n <= 0)
|
|
433
|
-
return;
|
|
434
|
-
for (let i = 0; i < n; i++)
|
|
435
|
-
process.stdout.write('\n');
|
|
436
|
-
process.stdout.write(`\x1b[${n}A`);
|
|
437
|
-
}
|
|
438
|
-
function syncAutocompleteWindow() {
|
|
439
|
-
if (!ac.items.length || ac.visibleRows <= 0) {
|
|
440
|
-
ac.windowStart = 0;
|
|
441
|
-
return;
|
|
442
|
-
}
|
|
443
|
-
ac.selected = Math.max(0, Math.min(ac.selected, ac.items.length - 1));
|
|
444
|
-
const maxStart = Math.max(0, ac.items.length - ac.visibleRows);
|
|
445
|
-
ac.windowStart = Math.max(0, Math.min(ac.windowStart, maxStart));
|
|
446
|
-
if (ac.selected < ac.windowStart)
|
|
447
|
-
ac.windowStart = ac.selected;
|
|
448
|
-
if (ac.selected >= ac.windowStart + ac.visibleRows) {
|
|
449
|
-
ac.windowStart = ac.selected - ac.visibleRows + 1;
|
|
450
|
-
}
|
|
451
|
-
}
|
|
452
|
-
function resolveAutocompleteState(prevKey) {
|
|
453
|
-
const draft = getPlainCommandDraft(composer);
|
|
454
|
-
if (!draft || !draft.startsWith('/')) {
|
|
455
|
-
return { open: false, items: [], selected: 0, visibleRows: 0 };
|
|
456
|
-
}
|
|
457
|
-
const body = draft.slice(1);
|
|
458
|
-
const firstSpace = body.indexOf(' ');
|
|
459
|
-
let stage = 'command';
|
|
460
|
-
let contextHeader = '';
|
|
461
|
-
let items = [];
|
|
462
|
-
if (firstSpace === -1) {
|
|
463
|
-
items = getCompletionItems(draft, 'cli');
|
|
464
|
-
}
|
|
465
|
-
else {
|
|
466
|
-
const commandName = body.slice(0, firstSpace).trim().toLowerCase();
|
|
467
|
-
if (!commandName)
|
|
468
|
-
return { open: false, items: [], selected: 0, visibleRows: 0 };
|
|
469
|
-
const rest = body.slice(firstSpace + 1);
|
|
470
|
-
const endsWithSpace = /\s$/.test(rest);
|
|
471
|
-
const tokens = rest.trim() ? rest.trim().split(/\s+/) : [];
|
|
472
|
-
const partial = endsWithSpace ? '' : (tokens[tokens.length - 1] || '');
|
|
473
|
-
const argv = endsWithSpace ? tokens : tokens.slice(0, -1);
|
|
474
|
-
items = getArgumentCompletionItems(commandName, partial, 'cli', argv, {});
|
|
475
|
-
if (items.length) {
|
|
476
|
-
stage = 'argument';
|
|
477
|
-
contextHeader = `${commandName} ▸ ${items[0]?.commandDesc || '인자 선택'}`;
|
|
478
|
-
}
|
|
479
|
-
}
|
|
480
|
-
if (!items.length) {
|
|
481
|
-
return { open: false, items: [], selected: 0, visibleRows: 0 };
|
|
482
|
-
}
|
|
483
|
-
const selected = (() => {
|
|
484
|
-
if (!prevKey)
|
|
485
|
-
return 0;
|
|
486
|
-
const idx = items.findIndex(i => makeSelectionKey(i, stage) === prevKey);
|
|
487
|
-
return idx >= 0 ? idx : 0;
|
|
488
|
-
})();
|
|
489
|
-
const maxRows = getMaxPopupRows();
|
|
490
|
-
const headerRows = contextHeader ? 1 : 0;
|
|
491
|
-
const maxItemRows = Math.max(0, maxRows - headerRows);
|
|
492
|
-
const stageCap = stage === 'argument' ? ac.maxRowsArgument : ac.maxRowsCommand;
|
|
493
|
-
const visibleRows = Math.min(stageCap, items.length, maxItemRows);
|
|
494
|
-
if (visibleRows <= 0) {
|
|
495
|
-
return { open: false, items: [], selected: 0, visibleRows: 0 };
|
|
496
|
-
}
|
|
497
|
-
return { open: true, stage, contextHeader, items, selected, visibleRows };
|
|
498
|
-
}
|
|
499
|
-
function clearAutocomplete() {
|
|
500
|
-
if (ac.renderedRows <= 0)
|
|
501
|
-
return;
|
|
502
|
-
process.stdout.write('\x1b[s');
|
|
503
|
-
for (let row = 1; row <= ac.renderedRows; row++) {
|
|
504
|
-
process.stdout.write(`\x1b[${row}B\r\x1b[2K\x1b[${row}A`);
|
|
505
|
-
}
|
|
506
|
-
process.stdout.write('\x1b[u');
|
|
507
|
-
ac.renderedRows = 0;
|
|
508
|
-
}
|
|
509
|
-
function closeAutocomplete() {
|
|
510
|
-
clearAutocomplete();
|
|
511
|
-
ac.open = false;
|
|
512
|
-
ac.stage = 'command';
|
|
513
|
-
ac.contextHeader = '';
|
|
514
|
-
ac.items = [];
|
|
515
|
-
ac.selected = 0;
|
|
516
|
-
ac.windowStart = 0;
|
|
517
|
-
ac.visibleRows = 0;
|
|
518
|
-
}
|
|
519
|
-
function formatAutocompleteLine(item, selected, stage) {
|
|
520
|
-
const value = stage === 'argument' ? item.name : `/${item.name}`;
|
|
521
|
-
const valueCol = stage === 'argument' ? 24 : 14;
|
|
522
|
-
const valueText = value.length >= valueCol ? value.slice(0, valueCol) : value.padEnd(valueCol, ' ');
|
|
523
|
-
const desc = item.desc || '';
|
|
524
|
-
const raw = ` ${valueText} ${desc}`;
|
|
525
|
-
const line = clipTextToCols(raw, (process.stdout.columns || 80) - 2);
|
|
526
|
-
return selected ? `\x1b[7m${line}${c.reset}` : `${c.dim}${line}${c.reset}`;
|
|
527
|
-
}
|
|
528
|
-
function renderAutocomplete() {
|
|
529
|
-
clearAutocomplete();
|
|
530
|
-
if (!ac.open || ac.items.length === 0 || ac.visibleRows <= 0)
|
|
531
|
-
return;
|
|
532
|
-
syncAutocompleteWindow();
|
|
533
|
-
const start = ac.windowStart;
|
|
534
|
-
const end = Math.min(ac.items.length, start + ac.visibleRows);
|
|
535
|
-
const headerRows = ac.contextHeader ? 1 : 0;
|
|
536
|
-
process.stdout.write('\x1b[s');
|
|
537
|
-
if (headerRows) {
|
|
538
|
-
process.stdout.write('\x1b[1B\r\x1b[2K');
|
|
539
|
-
const header = clipTextToCols(` ${ac.contextHeader}`, (process.stdout.columns || 80) - 2);
|
|
540
|
-
process.stdout.write(`${c.dim}${header}${c.reset}`);
|
|
541
|
-
process.stdout.write('\x1b[1A');
|
|
542
|
-
}
|
|
543
|
-
for (let i = start; i < end; i++) {
|
|
544
|
-
const row = (i - start) + 1 + headerRows;
|
|
545
|
-
process.stdout.write(`\x1b[${row}B\r\x1b[2K`);
|
|
546
|
-
process.stdout.write(formatAutocompleteLine(ac.items[i], i === ac.selected, ac.stage));
|
|
547
|
-
process.stdout.write(`\x1b[${row}A`);
|
|
548
|
-
}
|
|
549
|
-
ac.renderedRows = headerRows + (end - start);
|
|
550
|
-
process.stdout.write('\x1b[u');
|
|
551
|
-
}
|
|
552
369
|
function redrawInputWithAutocomplete() {
|
|
553
370
|
const prevItem = ac.items[ac.selected];
|
|
554
371
|
const prevKey = makeSelectionKey(prevItem, ac.stage);
|
|
555
|
-
const next = resolveAutocompleteState(
|
|
556
|
-
|
|
372
|
+
const next = resolveAutocompleteState({
|
|
373
|
+
draft: getPlainCommandDraft(composer),
|
|
374
|
+
prevKey,
|
|
375
|
+
maxPopupRows: getMaxPopupRows(),
|
|
376
|
+
maxRowsCommand: ac.maxRowsCommand,
|
|
377
|
+
maxRowsArgument: ac.maxRowsArgument,
|
|
378
|
+
});
|
|
379
|
+
clearAutocomplete(ac, (chunk) => process.stdout.write(chunk));
|
|
557
380
|
// ─ Phase 1c: scroll to create space BELOW prompt (not above) ─
|
|
558
381
|
if (next.open)
|
|
559
382
|
ensureSpaceBelow(popupTotalRows(next));
|
|
560
383
|
redrawPromptLine();
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
return;
|
|
570
|
-
}
|
|
571
|
-
ac.open = true;
|
|
572
|
-
ac.stage = next.stage ?? 'command';
|
|
573
|
-
ac.contextHeader = next.contextHeader || '';
|
|
574
|
-
ac.items = next.items;
|
|
575
|
-
ac.selected = next.selected;
|
|
576
|
-
ac.visibleRows = next.visibleRows;
|
|
577
|
-
syncAutocompleteWindow();
|
|
578
|
-
renderAutocomplete();
|
|
384
|
+
applyResolvedAutocompleteState(ac, next);
|
|
385
|
+
renderAutocomplete(ac, {
|
|
386
|
+
write: (chunk) => process.stdout.write(chunk),
|
|
387
|
+
columns: process.stdout.columns || 80,
|
|
388
|
+
dimCode: c.dim,
|
|
389
|
+
resetCode: c.reset,
|
|
390
|
+
clipTextToCols,
|
|
391
|
+
});
|
|
579
392
|
}
|
|
580
393
|
function handleResize() {
|
|
581
|
-
setupScrollRegion();
|
|
394
|
+
setupScrollRegion(footer, ` ${c.dim}${hrLine()}${c.reset}`, resolveShellLayout(process.stdout.columns || 80, getRows(), panes));
|
|
582
395
|
if (!inputActive || commandRunning)
|
|
583
396
|
return;
|
|
584
397
|
redrawInputWithAutocomplete();
|
|
@@ -596,7 +409,7 @@ else {
|
|
|
596
409
|
const result = await executeCommand(parsed, makeCliCommandCtx());
|
|
597
410
|
if (result?.code === 'clear_screen') {
|
|
598
411
|
console.clear();
|
|
599
|
-
setupScrollRegion();
|
|
412
|
+
setupScrollRegion(footer, ` ${c.dim}${hrLine()}${c.reset}`, resolveShellLayout(process.stdout.columns || 80, getRows(), panes));
|
|
600
413
|
}
|
|
601
414
|
if (result?.text)
|
|
602
415
|
console.log(` ${renderCommandText(result.text)}`);
|
|
@@ -620,7 +433,7 @@ else {
|
|
|
620
433
|
}
|
|
621
434
|
if (result?.code === 'exit') {
|
|
622
435
|
exiting = true;
|
|
623
|
-
cleanupScrollRegion();
|
|
436
|
+
cleanupScrollRegion(resolveShellLayout(process.stdout.columns || 80, getRows(), panes));
|
|
624
437
|
console.log(` ${c.dim}Bye! \uD83E\uDD9E${c.reset}\n`);
|
|
625
438
|
setBracketedPaste(false);
|
|
626
439
|
ws.close();
|
|
@@ -635,8 +448,8 @@ else {
|
|
|
635
448
|
if (!exiting) {
|
|
636
449
|
commandRunning = false;
|
|
637
450
|
inputActive = true;
|
|
638
|
-
closeAutocomplete();
|
|
639
|
-
|
|
451
|
+
closeAutocomplete(ac, (chunk) => process.stdout.write(chunk));
|
|
452
|
+
openPromptBlock();
|
|
640
453
|
}
|
|
641
454
|
}
|
|
642
455
|
}
|
|
@@ -649,7 +462,7 @@ else {
|
|
|
649
462
|
escPending = false;
|
|
650
463
|
escTimer = null;
|
|
651
464
|
if (ac.open) {
|
|
652
|
-
closeAutocomplete();
|
|
465
|
+
closeAutocomplete(ac, (chunk) => process.stdout.write(chunk));
|
|
653
466
|
redrawPromptLine();
|
|
654
467
|
return;
|
|
655
468
|
}
|
|
@@ -659,7 +472,7 @@ else {
|
|
|
659
472
|
ws.send(JSON.stringify({ type: 'stop' }));
|
|
660
473
|
console.log(`\n ${c.yellow}■ stopped${c.reset}`);
|
|
661
474
|
inputActive = true;
|
|
662
|
-
|
|
475
|
+
openPromptBlock();
|
|
663
476
|
}
|
|
664
477
|
}
|
|
665
478
|
function handleKeyInput(rawKey) {
|
|
@@ -673,7 +486,8 @@ else {
|
|
|
673
486
|
key = `\x1b${key}`;
|
|
674
487
|
}
|
|
675
488
|
// Delay ESC standalone handling to distinguish it from ESC sequences.
|
|
676
|
-
|
|
489
|
+
const action = classifyKeyAction(key);
|
|
490
|
+
if (action === 'escape-alone') {
|
|
677
491
|
escPending = true;
|
|
678
492
|
escTimer = setTimeout(flushPendingEscape, ESC_WAIT_MS);
|
|
679
493
|
return;
|
|
@@ -681,73 +495,103 @@ else {
|
|
|
681
495
|
// ESC and Ctrl+C always work, even when agent is running
|
|
682
496
|
// Typing always works (for queue). Only Enter submission checks inputActive.
|
|
683
497
|
// Phase 12.1.7: Option+Enter (ESC+CR/LF) → insert newline
|
|
684
|
-
if (
|
|
498
|
+
if (action === 'option-enter') {
|
|
685
499
|
if (commandRunning)
|
|
686
500
|
return;
|
|
687
501
|
if (!inputActive) {
|
|
688
502
|
inputActive = true;
|
|
689
|
-
|
|
503
|
+
openPromptBlock();
|
|
690
504
|
}
|
|
691
505
|
appendNewlineToComposer(composer);
|
|
692
506
|
redrawInputWithAutocomplete();
|
|
693
507
|
return;
|
|
694
508
|
}
|
|
695
509
|
// Autocomplete navigation (raw ESC sequences)
|
|
696
|
-
|
|
697
|
-
const isDownKey = key === '\x1b[B' || key === '\x1bOB';
|
|
698
|
-
const isPageUpKey = key === '\x1b[5~';
|
|
699
|
-
const isPageDownKey = key === '\x1b[6~';
|
|
700
|
-
const isHomeKey = key === '\x1b[H' || key === '\x1b[1~' || key === '\x1bOH';
|
|
701
|
-
const isEndKey = key === '\x1b[F' || key === '\x1b[4~' || key === '\x1bOF';
|
|
702
|
-
if (ac.open && isUpKey) { // Up
|
|
510
|
+
if (ac.open && action === 'arrow-up') { // Up
|
|
703
511
|
ac.selected = Math.max(0, ac.selected - 1);
|
|
704
512
|
if (ac.selected < ac.windowStart)
|
|
705
513
|
ac.windowStart = ac.selected;
|
|
706
|
-
renderAutocomplete(
|
|
514
|
+
renderAutocomplete(ac, {
|
|
515
|
+
write: (chunk) => process.stdout.write(chunk),
|
|
516
|
+
columns: process.stdout.columns || 80,
|
|
517
|
+
dimCode: c.dim,
|
|
518
|
+
resetCode: c.reset,
|
|
519
|
+
clipTextToCols,
|
|
520
|
+
});
|
|
707
521
|
return;
|
|
708
522
|
}
|
|
709
|
-
if (ac.open &&
|
|
523
|
+
if (ac.open && action === 'arrow-down') { // Down
|
|
710
524
|
const maxIdx = ac.items.length - 1;
|
|
711
525
|
ac.selected = Math.min(maxIdx, ac.selected + 1);
|
|
712
526
|
if (ac.selected >= ac.windowStart + ac.visibleRows) {
|
|
713
527
|
ac.windowStart = ac.selected - ac.visibleRows + 1;
|
|
714
528
|
}
|
|
715
|
-
renderAutocomplete(
|
|
529
|
+
renderAutocomplete(ac, {
|
|
530
|
+
write: (chunk) => process.stdout.write(chunk),
|
|
531
|
+
columns: process.stdout.columns || 80,
|
|
532
|
+
dimCode: c.dim,
|
|
533
|
+
resetCode: c.reset,
|
|
534
|
+
clipTextToCols,
|
|
535
|
+
});
|
|
716
536
|
return;
|
|
717
537
|
}
|
|
718
|
-
if (ac.open &&
|
|
538
|
+
if (ac.open && action === 'page-up') {
|
|
719
539
|
const step = Math.max(1, ac.visibleRows);
|
|
720
540
|
ac.selected = Math.max(0, ac.selected - step);
|
|
721
541
|
if (ac.selected < ac.windowStart)
|
|
722
542
|
ac.windowStart = ac.selected;
|
|
723
|
-
renderAutocomplete(
|
|
543
|
+
renderAutocomplete(ac, {
|
|
544
|
+
write: (chunk) => process.stdout.write(chunk),
|
|
545
|
+
columns: process.stdout.columns || 80,
|
|
546
|
+
dimCode: c.dim,
|
|
547
|
+
resetCode: c.reset,
|
|
548
|
+
clipTextToCols,
|
|
549
|
+
});
|
|
724
550
|
return;
|
|
725
551
|
}
|
|
726
|
-
if (ac.open &&
|
|
552
|
+
if (ac.open && action === 'page-down') {
|
|
727
553
|
const step = Math.max(1, ac.visibleRows);
|
|
728
554
|
const maxIdx = ac.items.length - 1;
|
|
729
555
|
ac.selected = Math.min(maxIdx, ac.selected + step);
|
|
730
556
|
if (ac.selected >= ac.windowStart + ac.visibleRows) {
|
|
731
557
|
ac.windowStart = ac.selected - ac.visibleRows + 1;
|
|
732
558
|
}
|
|
733
|
-
renderAutocomplete(
|
|
559
|
+
renderAutocomplete(ac, {
|
|
560
|
+
write: (chunk) => process.stdout.write(chunk),
|
|
561
|
+
columns: process.stdout.columns || 80,
|
|
562
|
+
dimCode: c.dim,
|
|
563
|
+
resetCode: c.reset,
|
|
564
|
+
clipTextToCols,
|
|
565
|
+
});
|
|
734
566
|
return;
|
|
735
567
|
}
|
|
736
|
-
if (ac.open &&
|
|
568
|
+
if (ac.open && action === 'home') {
|
|
737
569
|
ac.selected = 0;
|
|
738
570
|
ac.windowStart = 0;
|
|
739
|
-
renderAutocomplete(
|
|
571
|
+
renderAutocomplete(ac, {
|
|
572
|
+
write: (chunk) => process.stdout.write(chunk),
|
|
573
|
+
columns: process.stdout.columns || 80,
|
|
574
|
+
dimCode: c.dim,
|
|
575
|
+
resetCode: c.reset,
|
|
576
|
+
clipTextToCols,
|
|
577
|
+
});
|
|
740
578
|
return;
|
|
741
579
|
}
|
|
742
|
-
if (ac.open &&
|
|
580
|
+
if (ac.open && action === 'end') {
|
|
743
581
|
ac.selected = Math.max(0, ac.items.length - 1);
|
|
744
582
|
if (ac.selected >= ac.windowStart + ac.visibleRows) {
|
|
745
583
|
ac.windowStart = ac.selected - ac.visibleRows + 1;
|
|
746
584
|
}
|
|
747
|
-
renderAutocomplete(
|
|
585
|
+
renderAutocomplete(ac, {
|
|
586
|
+
write: (chunk) => process.stdout.write(chunk),
|
|
587
|
+
columns: process.stdout.columns || 80,
|
|
588
|
+
dimCode: c.dim,
|
|
589
|
+
resetCode: c.reset,
|
|
590
|
+
clipTextToCols,
|
|
591
|
+
});
|
|
748
592
|
return;
|
|
749
593
|
}
|
|
750
|
-
if (ac.open &&
|
|
594
|
+
if (ac.open && action === 'tab') { // Tab accept (no execute)
|
|
751
595
|
const picked = ac.items[ac.selected];
|
|
752
596
|
const pickedStage = ac.stage;
|
|
753
597
|
if (picked) {
|
|
@@ -758,16 +602,16 @@ else {
|
|
|
758
602
|
else {
|
|
759
603
|
appendTextToComposer(composer, `/${picked.name}${picked.args ? ' ' : ''}`);
|
|
760
604
|
}
|
|
761
|
-
closeAutocomplete();
|
|
605
|
+
closeAutocomplete(ac, (chunk) => process.stdout.write(chunk));
|
|
762
606
|
redrawPromptLine();
|
|
763
607
|
}
|
|
764
608
|
return;
|
|
765
609
|
}
|
|
766
|
-
if (
|
|
610
|
+
if (action === 'enter') {
|
|
767
611
|
if (ac.open) {
|
|
768
612
|
const picked = ac.items[ac.selected];
|
|
769
613
|
const pickedStage = ac.stage;
|
|
770
|
-
closeAutocomplete();
|
|
614
|
+
closeAutocomplete(ac, (chunk) => process.stdout.write(chunk));
|
|
771
615
|
if (picked) {
|
|
772
616
|
clearComposer(composer);
|
|
773
617
|
if (pickedStage === 'argument') {
|
|
@@ -795,13 +639,13 @@ else {
|
|
|
795
639
|
const draft = getPlainCommandDraft(composer);
|
|
796
640
|
const text = flattenComposerForSubmit(composer).trim();
|
|
797
641
|
clearComposer(composer);
|
|
798
|
-
closeAutocomplete();
|
|
642
|
+
closeAutocomplete(ac, (chunk) => process.stdout.write(chunk));
|
|
799
643
|
prevLineCount = 1;
|
|
800
|
-
console.log(''); // newline after input
|
|
801
644
|
if (!text) {
|
|
802
|
-
|
|
645
|
+
reopenPromptLine();
|
|
803
646
|
return;
|
|
804
647
|
}
|
|
648
|
+
renderBlockSeparator();
|
|
805
649
|
// Phase 10: /file command
|
|
806
650
|
if (draft !== null && text.startsWith('/file ')) {
|
|
807
651
|
const parts = text.slice(6).trim().split(/\s+/);
|
|
@@ -809,7 +653,7 @@ else {
|
|
|
809
653
|
const caption = parts.slice(1).join(' ');
|
|
810
654
|
if (!fs.existsSync(fp)) {
|
|
811
655
|
console.log(` ${c.red}파일 없음: ${fp}${c.reset}`);
|
|
812
|
-
|
|
656
|
+
openPromptBlock();
|
|
813
657
|
return;
|
|
814
658
|
}
|
|
815
659
|
const prompt = `[사용자가 파일을 보냈습니다: ${fp}]\n이 파일을 Read 도구로 읽고 분석해주세요.${caption ? `\n\n사용자 메시지: ${caption}` : ''}`;
|
|
@@ -835,12 +679,12 @@ else {
|
|
|
835
679
|
ws.send(JSON.stringify({ type: 'send_message', text }));
|
|
836
680
|
inputActive = false;
|
|
837
681
|
}
|
|
838
|
-
else if (
|
|
682
|
+
else if (action === 'backspace') {
|
|
839
683
|
// Backspace
|
|
840
684
|
backspaceComposer(composer);
|
|
841
685
|
redrawInputWithAutocomplete();
|
|
842
686
|
}
|
|
843
|
-
else if (
|
|
687
|
+
else if (action === 'ctrl-c') {
|
|
844
688
|
// Ctrl+C — stop agent if running, otherwise exit
|
|
845
689
|
if (!inputActive) {
|
|
846
690
|
if (commandRunning)
|
|
@@ -848,10 +692,10 @@ else {
|
|
|
848
692
|
ws.send(JSON.stringify({ type: 'stop' }));
|
|
849
693
|
console.log(`\n ${c.yellow}■ stopped${c.reset}`);
|
|
850
694
|
inputActive = true;
|
|
851
|
-
|
|
695
|
+
openPromptBlock();
|
|
852
696
|
}
|
|
853
697
|
else {
|
|
854
|
-
cleanupScrollRegion();
|
|
698
|
+
cleanupScrollRegion(resolveShellLayout(process.stdout.columns || 80, getRows(), panes));
|
|
855
699
|
console.log(`\n ${c.dim}Bye! \uD83E\uDD9E${c.reset}\n`);
|
|
856
700
|
setBracketedPaste(false);
|
|
857
701
|
ws.close();
|
|
@@ -859,19 +703,19 @@ else {
|
|
|
859
703
|
process.exit(0);
|
|
860
704
|
}
|
|
861
705
|
}
|
|
862
|
-
else if (
|
|
706
|
+
else if (action === 'ctrl-u') {
|
|
863
707
|
// Ctrl+U — clear line
|
|
864
708
|
clearComposer(composer);
|
|
865
709
|
redrawInputWithAutocomplete();
|
|
866
710
|
}
|
|
867
|
-
else if (
|
|
711
|
+
else if (action === 'printable') {
|
|
868
712
|
// Printable chars (including multibyte/Korean)
|
|
869
713
|
// Phase 12.1.5: allow typing during agent run for queue
|
|
870
714
|
if (!inputActive) {
|
|
871
715
|
if (commandRunning)
|
|
872
716
|
return;
|
|
873
717
|
inputActive = true;
|
|
874
|
-
|
|
718
|
+
openPromptBlock(); // new separator + prompt before queue input
|
|
875
719
|
}
|
|
876
720
|
appendTextToComposer(composer, key);
|
|
877
721
|
redrawInputWithAutocomplete();
|
|
@@ -902,7 +746,7 @@ else {
|
|
|
902
746
|
if (commandRunning)
|
|
903
747
|
return;
|
|
904
748
|
inputActive = true;
|
|
905
|
-
|
|
749
|
+
openPromptBlock();
|
|
906
750
|
}
|
|
907
751
|
redrawInputWithAutocomplete();
|
|
908
752
|
if (tokens.length === 0)
|
|
@@ -924,8 +768,7 @@ else {
|
|
|
924
768
|
}
|
|
925
769
|
if (!streaming) {
|
|
926
770
|
streaming = true;
|
|
927
|
-
|
|
928
|
-
process.stdout.write(' ');
|
|
771
|
+
renderAssistantTurnStart();
|
|
929
772
|
}
|
|
930
773
|
process.stdout.write((msg.text || '').replace(/\n/g, '\n '));
|
|
931
774
|
break;
|
|
@@ -937,8 +780,8 @@ else {
|
|
|
937
780
|
console.log('');
|
|
938
781
|
}
|
|
939
782
|
else if (msg.text) {
|
|
940
|
-
|
|
941
|
-
console.log(
|
|
783
|
+
renderAssistantTurnStart();
|
|
784
|
+
console.log(msg.text.replace(/\n/g, '\n '));
|
|
942
785
|
}
|
|
943
786
|
// IDE diff: queue drain unconditional (mid-run /ide off safe)
|
|
944
787
|
if (isGit && preFileSetQueue.length > 0) {
|
|
@@ -965,7 +808,7 @@ else {
|
|
|
965
808
|
}
|
|
966
809
|
streaming = false;
|
|
967
810
|
inputActive = true;
|
|
968
|
-
|
|
811
|
+
openPromptBlock();
|
|
969
812
|
break;
|
|
970
813
|
case 'agent_status':
|
|
971
814
|
// skip 'done' — redundant with agent_done, arrives late
|
|
@@ -1018,13 +861,13 @@ else {
|
|
|
1018
861
|
catch { }
|
|
1019
862
|
});
|
|
1020
863
|
ws.on('close', () => {
|
|
1021
|
-
cleanupScrollRegion();
|
|
864
|
+
cleanupScrollRegion(resolveShellLayout(process.stdout.columns || 80, getRows(), panes));
|
|
1022
865
|
console.log(`\n ${c.dim}Disconnected${c.reset}\n`);
|
|
1023
866
|
setBracketedPaste(false);
|
|
1024
867
|
process.stdin.setRawMode(false);
|
|
1025
868
|
process.exit(0);
|
|
1026
869
|
});
|
|
1027
|
-
setupScrollRegion();
|
|
1028
|
-
|
|
870
|
+
setupScrollRegion(footer, ` ${c.dim}${hrLine()}${c.reset}`, resolveShellLayout(process.stdout.columns || 80, getRows(), panes));
|
|
871
|
+
openPromptBlock();
|
|
1029
872
|
}
|
|
1030
873
|
//# sourceMappingURL=chat.js.map
|