0agent 1.0.44 → 1.0.45
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/bin/chat.js +129 -93
- package/package.json +1 -1
package/bin/chat.js
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
* /model to switch. /key to add provider keys. Never forgets previous keys.
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
|
-
import { createInterface, emitKeypressEvents
|
|
10
|
+
import { createInterface, emitKeypressEvents } from 'node:readline';
|
|
11
11
|
import { readFileSync, writeFileSync, existsSync, mkdirSync } from 'node:fs';
|
|
12
12
|
import { resolve } from 'node:path';
|
|
13
13
|
import { homedir } from 'node:os';
|
|
@@ -836,61 +836,114 @@ async function handleCommand(input) {
|
|
|
836
836
|
}
|
|
837
837
|
}
|
|
838
838
|
|
|
839
|
-
// ───
|
|
840
|
-
//
|
|
841
|
-
//
|
|
842
|
-
|
|
839
|
+
// ─── Command palette ──────────────────────────────────────────────────────────
|
|
840
|
+
// Fully takes over stdin when open. No cursor tricks — pauses readline,
|
|
841
|
+
// reads raw bytes directly, draws with \x1b[NA\x1b[0J (up + clear-to-end).
|
|
842
|
+
// Returns the selected command string, or null if cancelled.
|
|
843
843
|
|
|
844
|
-
|
|
845
|
-
if (pendingResolve) { _clearMenu(); return; } // don't show while session running
|
|
844
|
+
let _paletteOpen = false;
|
|
846
845
|
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
).slice(0, 10);
|
|
846
|
+
async function openPalette(initialFilter = '') {
|
|
847
|
+
if (_paletteOpen || pendingResolve) return null;
|
|
848
|
+
_paletteOpen = true;
|
|
851
849
|
|
|
852
|
-
//
|
|
853
|
-
|
|
850
|
+
// Pause readline so it stops consuming stdin events
|
|
851
|
+
rl.pause();
|
|
852
|
+
// Resume the raw stream so our data handler receives bytes
|
|
853
|
+
process.stdin.resume();
|
|
854
|
+
if (process.stdin.isTTY) process.stdin.setRawMode(true);
|
|
854
855
|
|
|
855
|
-
|
|
856
|
+
let filter = initialFilter.toLowerCase();
|
|
857
|
+
let idx = 0;
|
|
858
|
+
let drawn = 0; // number of lines we've printed so far
|
|
856
859
|
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
moveCursor(process.stdout, 0, existingLines);
|
|
861
|
-
for (let i = 0; i < existingLines; i++) {
|
|
862
|
-
rlClearLine(process.stdout, 0);
|
|
863
|
-
if (i < existingLines - 1) moveCursor(process.stdout, 0, -1);
|
|
864
|
-
}
|
|
865
|
-
moveCursor(process.stdout, 0, -(existingLines - 1));
|
|
866
|
-
}
|
|
860
|
+
const getItems = () => SLASH_COMMANDS.filter(c =>
|
|
861
|
+
!filter || c.cmd.slice(1).startsWith(filter)
|
|
862
|
+
);
|
|
867
863
|
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
process.stdout.write(
|
|
872
|
-
` ${fmt(C.cyan, m.cmd.padEnd(20))} ${fmt(C.dim, m.desc)}\x1b[K\n`
|
|
873
|
-
);
|
|
874
|
-
}
|
|
864
|
+
const paint = () => {
|
|
865
|
+
// Erase previous draw: move up `drawn` lines, clear everything below
|
|
866
|
+
if (drawn > 0) process.stdout.write(`\x1b[${drawn}A\x1b[0J`);
|
|
875
867
|
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
moveCursor(process.stdout, 999, 0);
|
|
868
|
+
const items = getItems();
|
|
869
|
+
const show = items.slice(0, 14);
|
|
870
|
+
if (idx >= items.length) idx = Math.max(0, items.length - 1);
|
|
880
871
|
|
|
881
|
-
|
|
882
|
-
}
|
|
872
|
+
const lines = [];
|
|
883
873
|
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
874
|
+
// Top border
|
|
875
|
+
lines.push(` \x1b[2m${'─'.repeat(58)}\x1b[0m`);
|
|
876
|
+
|
|
877
|
+
if (show.length === 0) {
|
|
878
|
+
lines.push(` \x1b[2m no commands match "/${filter}"\x1b[0m`);
|
|
879
|
+
} else {
|
|
880
|
+
for (let i = 0; i < show.length; i++) {
|
|
881
|
+
const m = show[i];
|
|
882
|
+
const sel = i === idx;
|
|
883
|
+
if (sel) {
|
|
884
|
+
lines.push(
|
|
885
|
+
` \x1b[36;1m›\x1b[0m \x1b[36;1m${m.cmd.padEnd(22)}\x1b[0m \x1b[0m${m.desc}\x1b[0m`
|
|
886
|
+
);
|
|
887
|
+
} else {
|
|
888
|
+
lines.push(
|
|
889
|
+
` \x1b[36m${m.cmd.padEnd(22)}\x1b[0m \x1b[2m${m.desc}\x1b[0m`
|
|
890
|
+
);
|
|
891
|
+
}
|
|
892
|
+
}
|
|
893
|
+
}
|
|
894
|
+
|
|
895
|
+
if (items.length > 14) lines.push(` \x1b[2m …${items.length - 14} more\x1b[0m`);
|
|
896
|
+
|
|
897
|
+
// Bottom: search input line
|
|
898
|
+
lines.push(` \x1b[2m${'─'.repeat(58)}\x1b[0m`);
|
|
899
|
+
lines.push(` ${fmt(C.cyan, '/')}${filter}\x1b[K \x1b[2m↑↓ navigate · Enter select · Esc cancel\x1b[0m`);
|
|
900
|
+
|
|
901
|
+
const out = lines.join('\n') + '\n';
|
|
902
|
+
process.stdout.write(out);
|
|
903
|
+
drawn = lines.length;
|
|
904
|
+
};
|
|
905
|
+
|
|
906
|
+
paint();
|
|
907
|
+
|
|
908
|
+
return new Promise((resolve) => {
|
|
909
|
+
const onData = (chunk) => {
|
|
910
|
+
const s = chunk.toString();
|
|
911
|
+
|
|
912
|
+
if (s === '\r' || s === '\n') { // Enter — select
|
|
913
|
+
const sel = getItems()[idx]?.cmd ?? null;
|
|
914
|
+
finish(sel);
|
|
915
|
+
} else if (s === '\x1b' || s === '\x03') { // Esc / Ctrl-C — cancel
|
|
916
|
+
finish(null);
|
|
917
|
+
} else if (s === '\x1b[A') { // Up arrow
|
|
918
|
+
idx = Math.max(0, idx - 1);
|
|
919
|
+
paint();
|
|
920
|
+
} else if (s === '\x1b[B') { // Down arrow
|
|
921
|
+
idx = Math.min(getItems().length - 1, idx + 1);
|
|
922
|
+
paint();
|
|
923
|
+
} else if (s === '\x7f' || s === '\x08') { // Backspace
|
|
924
|
+
filter = filter.slice(0, -1);
|
|
925
|
+
idx = 0;
|
|
926
|
+
paint();
|
|
927
|
+
} else if (/^[a-z0-9\-_]$/i.test(s)) { // Printable letter/digit/hyphen
|
|
928
|
+
filter += s.toLowerCase();
|
|
929
|
+
idx = 0;
|
|
930
|
+
paint();
|
|
931
|
+
}
|
|
932
|
+
};
|
|
933
|
+
|
|
934
|
+
const finish = (selected) => {
|
|
935
|
+
process.stdin.removeListener('data', onData);
|
|
936
|
+
// Erase the palette
|
|
937
|
+
if (drawn > 0) process.stdout.write(`\x1b[${drawn}A\x1b[0J`);
|
|
938
|
+
drawn = 0;
|
|
939
|
+
_paletteOpen = false;
|
|
940
|
+
// Hand control back to readline
|
|
941
|
+
rl.resume();
|
|
942
|
+
resolve(selected);
|
|
943
|
+
};
|
|
944
|
+
|
|
945
|
+
process.stdin.on('data', onData);
|
|
946
|
+
});
|
|
894
947
|
}
|
|
895
948
|
|
|
896
949
|
// ─── Main REPL ────────────────────────────────────────────────────────────────
|
|
@@ -902,50 +955,31 @@ const rl = createInterface({
|
|
|
902
955
|
completer: (line, callback) => {
|
|
903
956
|
if (!line.startsWith('/')) return callback(null, [[], line]);
|
|
904
957
|
|
|
905
|
-
const filter
|
|
906
|
-
const matches = SLASH_COMMANDS.filter(c =>
|
|
907
|
-
!filter || c.cmd.slice(1).startsWith(filter)
|
|
908
|
-
);
|
|
909
|
-
|
|
910
|
-
if (matches.length === 0) return callback(null, [[], line]);
|
|
958
|
+
const filter = line.slice(1).toLowerCase();
|
|
959
|
+
const matches = SLASH_COMMANDS.filter(c => !filter || c.cmd.slice(1).startsWith(filter));
|
|
911
960
|
|
|
912
|
-
// Single match — let readline silently auto-complete
|
|
913
|
-
if (matches.length === 1)
|
|
914
|
-
_clearMenu();
|
|
915
|
-
return callback(null, [[matches[0].cmd + ' '], line]);
|
|
916
|
-
}
|
|
961
|
+
// Single exact match — let readline silently auto-complete
|
|
962
|
+
if (matches.length === 1) return callback(null, [[matches[0].cmd + ' '], line]);
|
|
917
963
|
|
|
918
|
-
// Multiple
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
process.stdout.write(
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
964
|
+
// Multiple (or bare /) — open interactive palette
|
|
965
|
+
// Schedule after this tick so readline finishes its Tab handling first
|
|
966
|
+
setImmediate(async () => {
|
|
967
|
+
// Clear the typed fragment from the prompt line before opening palette
|
|
968
|
+
process.stdout.write('\r\x1b[2K');
|
|
969
|
+
const selected = await openPalette(filter);
|
|
970
|
+
if (selected) {
|
|
971
|
+
await executeInput(selected);
|
|
972
|
+
} else {
|
|
973
|
+
rl.prompt();
|
|
974
|
+
}
|
|
975
|
+
});
|
|
929
976
|
return callback(null, [[], line]);
|
|
930
977
|
},
|
|
931
978
|
});
|
|
932
979
|
|
|
933
|
-
//
|
|
980
|
+
// Trigger palette when user types exactly '/' and presses Tab or Enter isn't needed —
|
|
981
|
+
// the completer above handles Tab. For bare '/' + Enter, handled in rl.on('line') below.
|
|
934
982
|
emitKeypressEvents(process.stdin, rl);
|
|
935
|
-
process.stdin.on('keypress', (_char, key) => {
|
|
936
|
-
if (key?.name === 'return' || key?.name === 'enter') {
|
|
937
|
-
_clearMenu(); // clear before readline processes the line
|
|
938
|
-
return;
|
|
939
|
-
}
|
|
940
|
-
setImmediate(() => {
|
|
941
|
-
const line = rl.line ?? '';
|
|
942
|
-
if (line.startsWith('/') && !pendingResolve) {
|
|
943
|
-
_drawMenu(line.slice(1));
|
|
944
|
-
} else {
|
|
945
|
-
_clearMenu();
|
|
946
|
-
}
|
|
947
|
-
});
|
|
948
|
-
});
|
|
949
983
|
|
|
950
984
|
printHeader();
|
|
951
985
|
printInsights();
|
|
@@ -1253,19 +1287,21 @@ async function drainQueue() {
|
|
|
1253
1287
|
}
|
|
1254
1288
|
|
|
1255
1289
|
rl.on('line', async (input) => {
|
|
1256
|
-
_clearMenu(); // always clear menu when a line is submitted
|
|
1257
1290
|
const line = input.trim();
|
|
1258
1291
|
if (!line) { rl.prompt(); return; }
|
|
1259
1292
|
|
|
1260
|
-
// Bare `/` →
|
|
1261
|
-
if (line
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
|
|
1293
|
+
// Bare `/` or partial `/cmd` with no session running → open interactive palette
|
|
1294
|
+
if (line.startsWith('/') && !pendingResolve) {
|
|
1295
|
+
const filter = line === '/' ? '' : line.slice(1);
|
|
1296
|
+
const exact = SLASH_COMMANDS.find(c => c.cmd === line);
|
|
1297
|
+
if (!exact) {
|
|
1298
|
+
// Clear the echoed input line before opening palette
|
|
1299
|
+
process.stdout.write('\x1b[1A\r\x1b[2K');
|
|
1300
|
+
const selected = await openPalette(filter);
|
|
1301
|
+
if (selected) await executeInput(selected);
|
|
1302
|
+
else rl.prompt();
|
|
1303
|
+
return;
|
|
1265
1304
|
}
|
|
1266
|
-
console.log('');
|
|
1267
|
-
rl.prompt();
|
|
1268
|
-
return;
|
|
1269
1305
|
}
|
|
1270
1306
|
|
|
1271
1307
|
// If a session is already running, queue the message.
|