erosolar-cli 1.7.166 → 1.7.168
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/shell/composableMessage.d.ts +57 -1
- package/dist/shell/composableMessage.d.ts.map +1 -1
- package/dist/shell/composableMessage.js +147 -1
- package/dist/shell/composableMessage.js.map +1 -1
- package/dist/shell/interactiveShell.d.ts +40 -0
- package/dist/shell/interactiveShell.d.ts.map +1 -1
- package/dist/shell/interactiveShell.js +422 -8
- package/dist/shell/interactiveShell.js.map +1 -1
- package/dist/tools/diffUtils.d.ts +23 -1
- package/dist/tools/diffUtils.d.ts.map +1 -1
- package/dist/tools/diffUtils.js +245 -1
- package/dist/tools/diffUtils.js.map +1 -1
- package/dist/tools/editTools.d.ts.map +1 -1
- package/dist/tools/editTools.js +73 -13
- package/dist/tools/editTools.js.map +1 -1
- package/dist/tools/enhancedCodeIntelligenceTools.d.ts.map +1 -1
- package/dist/tools/enhancedCodeIntelligenceTools.js +160 -0
- package/dist/tools/enhancedCodeIntelligenceTools.js.map +1 -1
- package/dist/ui/display.d.ts +11 -0
- package/dist/ui/display.d.ts.map +1 -1
- package/dist/ui/display.js +22 -0
- package/dist/ui/display.js.map +1 -1
- package/dist/ui/shortcutsHelp.d.ts.map +1 -1
- package/dist/ui/shortcutsHelp.js +1 -0
- package/dist/ui/shortcutsHelp.js.map +1 -1
- package/package.json +1 -1
|
@@ -471,7 +471,7 @@ export class InteractiveShell {
|
|
|
471
471
|
this.clearMultiLinePastePreview();
|
|
472
472
|
const lineCount = normalized.lineCount ?? normalized.result.split('\n').length;
|
|
473
473
|
// All pastes (single or multi-line) are captured for confirmation before submit
|
|
474
|
-
this.capturePaste(normalized.result, lineCount);
|
|
474
|
+
void this.capturePaste(normalized.result, lineCount);
|
|
475
475
|
return;
|
|
476
476
|
}
|
|
477
477
|
return;
|
|
@@ -546,7 +546,7 @@ export class InteractiveShell {
|
|
|
546
546
|
const lines = content.split('\n');
|
|
547
547
|
const lineCount = lines.length;
|
|
548
548
|
// All pastes (single or multi-line) are captured for confirmation before submit
|
|
549
|
-
this.capturePaste(content, lineCount);
|
|
549
|
+
void this.capturePaste(content, lineCount);
|
|
550
550
|
});
|
|
551
551
|
// Set up raw data interception to catch bracketed paste before readline processes it.
|
|
552
552
|
// We need to actually PREVENT readline from seeing the paste content to avoid echo.
|
|
@@ -589,9 +589,24 @@ export class InteractiveShell {
|
|
|
589
589
|
this.keypressHandler = (_str, key) => {
|
|
590
590
|
// Handle special keys
|
|
591
591
|
if (key) {
|
|
592
|
-
// Shift+Tab
|
|
593
|
-
if (key.name === 'tab' && key.shift
|
|
594
|
-
|
|
592
|
+
// Shift+Tab: Cycle paste preview options if paste blocks exist, otherwise profile switching
|
|
593
|
+
if (key.name === 'tab' && key.shift) {
|
|
594
|
+
// Check if there are paste blocks to cycle through
|
|
595
|
+
const pasteBlockCount = this.composableMessage.getPasteBlockCount();
|
|
596
|
+
if (pasteBlockCount > 0) {
|
|
597
|
+
this.cyclePastePreview();
|
|
598
|
+
return;
|
|
599
|
+
}
|
|
600
|
+
// Fall through to profile switching if no paste blocks
|
|
601
|
+
if (this.agentMenu) {
|
|
602
|
+
this.showProfileSwitcher();
|
|
603
|
+
return;
|
|
604
|
+
}
|
|
605
|
+
}
|
|
606
|
+
// Tab: Autocomplete slash commands
|
|
607
|
+
if (key.name === 'tab' && !key.shift && !key.ctrl) {
|
|
608
|
+
this.handleTabCompletion();
|
|
609
|
+
return;
|
|
595
610
|
}
|
|
596
611
|
// Escape: Cancel current operation if agent is running
|
|
597
612
|
if (key.name === 'escape') {
|
|
@@ -638,6 +653,11 @@ export class InteractiveShell {
|
|
|
638
653
|
}
|
|
639
654
|
// If no text in buffer, let default Ctrl+C behavior exit
|
|
640
655
|
}
|
|
656
|
+
// Ctrl+G: Edit pasted content blocks
|
|
657
|
+
if (key.ctrl && key.name === 'g') {
|
|
658
|
+
this.showPasteBlockEditor();
|
|
659
|
+
return;
|
|
660
|
+
}
|
|
641
661
|
}
|
|
642
662
|
// Readline handles all keyboard input natively (history, shortcuts, etc.)
|
|
643
663
|
// We just sync the current state to our display components
|
|
@@ -970,7 +990,7 @@ export class InteractiveShell {
|
|
|
970
990
|
* - Longer pastes (3+ lines) show as collapsed block chips
|
|
971
991
|
* Supports multiple pastes - user can paste multiple times before submitting.
|
|
972
992
|
*/
|
|
973
|
-
capturePaste(content, lineCount) {
|
|
993
|
+
async capturePaste(content, lineCount) {
|
|
974
994
|
this.resetBufferedInputLines();
|
|
975
995
|
// Short pastes (1-2 lines) display inline like normal text
|
|
976
996
|
const isShortPaste = lineCount <= 2;
|
|
@@ -1005,7 +1025,26 @@ export class InteractiveShell {
|
|
|
1005
1025
|
return;
|
|
1006
1026
|
}
|
|
1007
1027
|
// For longer pastes (3+ lines), store as a composable block
|
|
1008
|
-
|
|
1028
|
+
// Check size limits first
|
|
1029
|
+
const { ComposableMessageBuilder } = await import('./composableMessage.js');
|
|
1030
|
+
const sizeCheck = ComposableMessageBuilder.checkPasteSize(content);
|
|
1031
|
+
if (!sizeCheck.ok) {
|
|
1032
|
+
// Paste rejected - show error and abort
|
|
1033
|
+
display.showError(sizeCheck.error || 'Paste rejected due to size limits');
|
|
1034
|
+
this.rl.prompt();
|
|
1035
|
+
return;
|
|
1036
|
+
}
|
|
1037
|
+
// Show warning for large (but acceptable) pastes
|
|
1038
|
+
if (sizeCheck.warning) {
|
|
1039
|
+
display.showWarning(`⚠️ ${sizeCheck.warning}`);
|
|
1040
|
+
}
|
|
1041
|
+
const pasteId = this.composableMessage.addPaste(content);
|
|
1042
|
+
if (!pasteId) {
|
|
1043
|
+
// Should not happen since we checked above, but handle defensively
|
|
1044
|
+
display.showError('Failed to store paste block');
|
|
1045
|
+
this.rl.prompt();
|
|
1046
|
+
return;
|
|
1047
|
+
}
|
|
1009
1048
|
// Clear remaining echoed lines from terminal
|
|
1010
1049
|
output.write('\r\x1b[K');
|
|
1011
1050
|
// Build the paste chips to show inline with prompt
|
|
@@ -1043,6 +1082,277 @@ export class InteractiveShell {
|
|
|
1043
1082
|
this.persistentPrompt.updateStatusBar({ message: undefined });
|
|
1044
1083
|
}
|
|
1045
1084
|
}
|
|
1085
|
+
/**
|
|
1086
|
+
* Show paste block editor (Ctrl+G)
|
|
1087
|
+
* Allows viewing and editing captured paste blocks before sending
|
|
1088
|
+
*
|
|
1089
|
+
* Features:
|
|
1090
|
+
* - View block content with line numbers
|
|
1091
|
+
* - Edit blocks inline (replace content)
|
|
1092
|
+
* - Remove individual blocks
|
|
1093
|
+
* - Undo/redo paste operations
|
|
1094
|
+
* - Clear all blocks
|
|
1095
|
+
*/
|
|
1096
|
+
showPasteBlockEditor() {
|
|
1097
|
+
const state = this.composableMessage.getState();
|
|
1098
|
+
const pasteBlocks = state.parts.filter((p) => p.type === 'paste');
|
|
1099
|
+
if (pasteBlocks.length === 0) {
|
|
1100
|
+
display.showInfo('No pasted content blocks to edit. Paste some content first.');
|
|
1101
|
+
this.rl.prompt();
|
|
1102
|
+
return;
|
|
1103
|
+
}
|
|
1104
|
+
output.write('\n');
|
|
1105
|
+
display.showSystemMessage('📋 Paste Block Editor');
|
|
1106
|
+
output.write('\n');
|
|
1107
|
+
// Display each paste block with preview
|
|
1108
|
+
pasteBlocks.forEach((block, index) => {
|
|
1109
|
+
const lines = block.content.split('\n');
|
|
1110
|
+
const preview = lines.slice(0, 3).map((l) => ` ${l.slice(0, 60)}${l.length > 60 ? '...' : ''}`).join('\n');
|
|
1111
|
+
const moreLines = lines.length > 3 ? `\n ... +${lines.length - 3} more lines` : '';
|
|
1112
|
+
const editedFlag = block.edited ? theme.warning(' [edited]') : '';
|
|
1113
|
+
output.write(theme.ui.muted(`[${index + 1}] `) + theme.info(`${block.lineCount} lines, ${block.content.length} chars`) + editedFlag + '\n');
|
|
1114
|
+
output.write(theme.secondary(preview + moreLines) + '\n\n');
|
|
1115
|
+
});
|
|
1116
|
+
// Show undo/redo status
|
|
1117
|
+
const canUndo = this.composableMessage.canUndo();
|
|
1118
|
+
const canRedo = this.composableMessage.canRedo();
|
|
1119
|
+
if (canUndo || canRedo) {
|
|
1120
|
+
const undoStatus = canUndo ? theme.success('undo available') : theme.ui.muted('undo unavailable');
|
|
1121
|
+
const redoStatus = canRedo ? theme.success('redo available') : theme.ui.muted('redo unavailable');
|
|
1122
|
+
output.write(theme.ui.muted('History: ') + undoStatus + theme.ui.muted(' │ ') + redoStatus + '\n\n');
|
|
1123
|
+
}
|
|
1124
|
+
output.write(theme.ui.muted('Commands: ') + '\n');
|
|
1125
|
+
output.write(theme.ui.muted(' • Enter number to view full block') + '\n');
|
|
1126
|
+
output.write(theme.ui.muted(' • "edit N" to replace block N content') + '\n');
|
|
1127
|
+
output.write(theme.ui.muted(' • "remove N" to remove block N') + '\n');
|
|
1128
|
+
output.write(theme.ui.muted(' • "undo" / "redo" to undo/redo changes') + '\n');
|
|
1129
|
+
output.write(theme.ui.muted(' • "clear" to remove all blocks') + '\n');
|
|
1130
|
+
output.write(theme.ui.muted(' • Enter to return to prompt') + '\n');
|
|
1131
|
+
output.write('\n');
|
|
1132
|
+
// Set up interaction mode for paste editing
|
|
1133
|
+
this.pendingInteraction = { type: 'paste-edit' };
|
|
1134
|
+
this.rl.prompt();
|
|
1135
|
+
}
|
|
1136
|
+
/**
|
|
1137
|
+
* Handle paste block editor input
|
|
1138
|
+
*/
|
|
1139
|
+
async handlePasteEditInput(trimmedInput) {
|
|
1140
|
+
const trimmed = trimmedInput.toLowerCase();
|
|
1141
|
+
const state = this.composableMessage.getState();
|
|
1142
|
+
const pasteBlocks = state.parts.filter((p) => p.type === 'paste');
|
|
1143
|
+
if (!trimmed) {
|
|
1144
|
+
// Exit paste edit mode
|
|
1145
|
+
this.pendingInteraction = null;
|
|
1146
|
+
display.showInfo('Returned to prompt.');
|
|
1147
|
+
this.rl.prompt();
|
|
1148
|
+
return;
|
|
1149
|
+
}
|
|
1150
|
+
// Handle undo command
|
|
1151
|
+
if (trimmed === 'undo') {
|
|
1152
|
+
if (this.composableMessage.undo()) {
|
|
1153
|
+
this.updateComposeStatusSummary();
|
|
1154
|
+
this.refreshPasteChipsDisplay();
|
|
1155
|
+
display.showInfo('Undone.');
|
|
1156
|
+
// Refresh the paste block view
|
|
1157
|
+
this.showPasteBlockEditor();
|
|
1158
|
+
}
|
|
1159
|
+
else {
|
|
1160
|
+
display.showWarning('Nothing to undo.');
|
|
1161
|
+
this.rl.prompt();
|
|
1162
|
+
}
|
|
1163
|
+
return;
|
|
1164
|
+
}
|
|
1165
|
+
// Handle redo command
|
|
1166
|
+
if (trimmed === 'redo') {
|
|
1167
|
+
if (this.composableMessage.redo()) {
|
|
1168
|
+
this.updateComposeStatusSummary();
|
|
1169
|
+
this.refreshPasteChipsDisplay();
|
|
1170
|
+
display.showInfo('Redone.');
|
|
1171
|
+
// Refresh the paste block view
|
|
1172
|
+
this.showPasteBlockEditor();
|
|
1173
|
+
}
|
|
1174
|
+
else {
|
|
1175
|
+
display.showWarning('Nothing to redo.');
|
|
1176
|
+
this.rl.prompt();
|
|
1177
|
+
}
|
|
1178
|
+
return;
|
|
1179
|
+
}
|
|
1180
|
+
if (trimmed === 'clear') {
|
|
1181
|
+
this.composableMessage.clear();
|
|
1182
|
+
this.updateComposeStatusSummary();
|
|
1183
|
+
this.persistentPrompt.updateInput('', 0);
|
|
1184
|
+
this.rl.line = '';
|
|
1185
|
+
this.rl.cursor = 0;
|
|
1186
|
+
this.pendingInteraction = null;
|
|
1187
|
+
display.showInfo('Cleared all paste blocks.');
|
|
1188
|
+
this.rl.prompt();
|
|
1189
|
+
return;
|
|
1190
|
+
}
|
|
1191
|
+
// Handle "edit N" command - start inline editing mode
|
|
1192
|
+
const editMatch = trimmed.match(/^edit\s+(\d+)$/);
|
|
1193
|
+
if (editMatch) {
|
|
1194
|
+
const blockNum = parseInt(editMatch[1], 10);
|
|
1195
|
+
if (blockNum >= 1 && blockNum <= pasteBlocks.length) {
|
|
1196
|
+
const block = pasteBlocks[blockNum - 1];
|
|
1197
|
+
if (block) {
|
|
1198
|
+
// Enter edit mode for this block
|
|
1199
|
+
this.pendingInteraction = { type: 'paste-edit-block', blockId: block.id, blockNum };
|
|
1200
|
+
output.write('\n');
|
|
1201
|
+
display.showSystemMessage(`Editing Block ${blockNum}:`);
|
|
1202
|
+
output.write(theme.ui.muted('Paste or type new content. Press Enter twice to finish, or "cancel" to abort.\n'));
|
|
1203
|
+
output.write('\n');
|
|
1204
|
+
// Show current content as reference
|
|
1205
|
+
const preview = block.content.split('\n').slice(0, 5).map((l) => theme.ui.muted(` ${l.slice(0, 60)}`)).join('\n');
|
|
1206
|
+
output.write(theme.ui.muted('Current content (first 5 lines):\n') + preview + '\n\n');
|
|
1207
|
+
}
|
|
1208
|
+
}
|
|
1209
|
+
else {
|
|
1210
|
+
display.showWarning(`Invalid block number. Use 1-${pasteBlocks.length}.`);
|
|
1211
|
+
}
|
|
1212
|
+
this.rl.prompt();
|
|
1213
|
+
return;
|
|
1214
|
+
}
|
|
1215
|
+
// Handle "remove N" command
|
|
1216
|
+
const removeMatch = trimmed.match(/^remove\s+(\d+)$/);
|
|
1217
|
+
if (removeMatch) {
|
|
1218
|
+
const blockNum = parseInt(removeMatch[1], 10);
|
|
1219
|
+
if (blockNum >= 1 && blockNum <= pasteBlocks.length) {
|
|
1220
|
+
const block = pasteBlocks[blockNum - 1];
|
|
1221
|
+
if (block) {
|
|
1222
|
+
this.composableMessage.removePart(block.id);
|
|
1223
|
+
this.updateComposeStatusSummary();
|
|
1224
|
+
this.refreshPasteChipsDisplay();
|
|
1225
|
+
display.showInfo(`Removed block ${blockNum}.`);
|
|
1226
|
+
// Check if any blocks remain
|
|
1227
|
+
const remaining = this.composableMessage.getState().parts.filter(p => p.type === 'paste');
|
|
1228
|
+
if (remaining.length === 0) {
|
|
1229
|
+
this.pendingInteraction = null;
|
|
1230
|
+
display.showInfo('No more paste blocks.');
|
|
1231
|
+
}
|
|
1232
|
+
}
|
|
1233
|
+
}
|
|
1234
|
+
else {
|
|
1235
|
+
display.showWarning(`Invalid block number. Use 1-${pasteBlocks.length}.`);
|
|
1236
|
+
}
|
|
1237
|
+
this.rl.prompt();
|
|
1238
|
+
return;
|
|
1239
|
+
}
|
|
1240
|
+
// Handle viewing a block by number
|
|
1241
|
+
const blockNum = parseInt(trimmed, 10);
|
|
1242
|
+
if (!Number.isNaN(blockNum) && blockNum >= 1 && blockNum <= pasteBlocks.length) {
|
|
1243
|
+
const block = pasteBlocks[blockNum - 1];
|
|
1244
|
+
if (block) {
|
|
1245
|
+
output.write('\n');
|
|
1246
|
+
display.showSystemMessage(`Block ${blockNum} Content:`);
|
|
1247
|
+
output.write('\n');
|
|
1248
|
+
// Show full content with line numbers
|
|
1249
|
+
const lines = block.content.split('\n');
|
|
1250
|
+
lines.forEach((line, i) => {
|
|
1251
|
+
const lineNum = theme.ui.muted(`${String(i + 1).padStart(4)} │ `);
|
|
1252
|
+
output.write(lineNum + line + '\n');
|
|
1253
|
+
});
|
|
1254
|
+
output.write('\n');
|
|
1255
|
+
}
|
|
1256
|
+
this.rl.prompt();
|
|
1257
|
+
return;
|
|
1258
|
+
}
|
|
1259
|
+
display.showWarning('Enter a block number, "edit N", "remove N", "undo", "redo", "clear", or press Enter to return.');
|
|
1260
|
+
this.rl.prompt();
|
|
1261
|
+
}
|
|
1262
|
+
/**
|
|
1263
|
+
* Handle paste block inline editing mode
|
|
1264
|
+
*/
|
|
1265
|
+
editBlockBuffer = [];
|
|
1266
|
+
async handlePasteBlockEditInput(input, blockId, blockNum) {
|
|
1267
|
+
// Check for cancel
|
|
1268
|
+
if (input.toLowerCase() === 'cancel') {
|
|
1269
|
+
this.editBlockBuffer = [];
|
|
1270
|
+
this.pendingInteraction = { type: 'paste-edit' };
|
|
1271
|
+
display.showInfo('Edit cancelled.');
|
|
1272
|
+
this.showPasteBlockEditor();
|
|
1273
|
+
return;
|
|
1274
|
+
}
|
|
1275
|
+
// Empty line - check if this is end of edit (double Enter)
|
|
1276
|
+
if (!input.trim()) {
|
|
1277
|
+
if (this.editBlockBuffer.length > 0) {
|
|
1278
|
+
// Complete the edit
|
|
1279
|
+
const newContent = this.editBlockBuffer.join('\n');
|
|
1280
|
+
this.composableMessage.editPaste(blockId, newContent);
|
|
1281
|
+
this.editBlockBuffer = [];
|
|
1282
|
+
this.updateComposeStatusSummary();
|
|
1283
|
+
this.refreshPasteChipsDisplay();
|
|
1284
|
+
display.showInfo(`Block ${blockNum} updated (${newContent.split('\n').length} lines).`);
|
|
1285
|
+
this.pendingInteraction = { type: 'paste-edit' };
|
|
1286
|
+
this.showPasteBlockEditor();
|
|
1287
|
+
return;
|
|
1288
|
+
}
|
|
1289
|
+
// First empty line with no buffer - just prompt again
|
|
1290
|
+
this.rl.prompt();
|
|
1291
|
+
return;
|
|
1292
|
+
}
|
|
1293
|
+
// Add line to edit buffer
|
|
1294
|
+
this.editBlockBuffer.push(input);
|
|
1295
|
+
this.rl.prompt();
|
|
1296
|
+
}
|
|
1297
|
+
/**
|
|
1298
|
+
* Refresh paste chips display in prompt
|
|
1299
|
+
*/
|
|
1300
|
+
refreshPasteChipsDisplay() {
|
|
1301
|
+
const chips = this.composableMessage.formatPasteChips();
|
|
1302
|
+
this.persistentPrompt.updateInput(chips ? chips + ' ' : '', chips ? chips.length + 1 : 0);
|
|
1303
|
+
this.rl.line = chips ? chips + ' ' : '';
|
|
1304
|
+
this.rl.cursor = chips ? chips.length + 1 : 0;
|
|
1305
|
+
}
|
|
1306
|
+
/**
|
|
1307
|
+
* Cycle paste preview mode (Shift+Tab)
|
|
1308
|
+
* Cycles through: collapsed → summary → expanded
|
|
1309
|
+
*/
|
|
1310
|
+
pastePreviewMode = 'collapsed';
|
|
1311
|
+
cyclePastePreview() {
|
|
1312
|
+
const state = this.composableMessage.getState();
|
|
1313
|
+
const pasteBlocks = state.parts.filter((p) => p.type === 'paste');
|
|
1314
|
+
if (pasteBlocks.length === 0) {
|
|
1315
|
+
return;
|
|
1316
|
+
}
|
|
1317
|
+
// Cycle through modes
|
|
1318
|
+
const modes = ['collapsed', 'summary', 'expanded'];
|
|
1319
|
+
const currentIndex = modes.indexOf(this.pastePreviewMode);
|
|
1320
|
+
this.pastePreviewMode = modes[(currentIndex + 1) % modes.length];
|
|
1321
|
+
output.write('\n');
|
|
1322
|
+
switch (this.pastePreviewMode) {
|
|
1323
|
+
case 'collapsed':
|
|
1324
|
+
// Show just chips
|
|
1325
|
+
display.showSystemMessage(`📋 Preview: Collapsed (${pasteBlocks.length} block${pasteBlocks.length > 1 ? 's' : ''})`);
|
|
1326
|
+
output.write(theme.ui.muted(this.composableMessage.formatPasteChips()) + '\n');
|
|
1327
|
+
break;
|
|
1328
|
+
case 'summary':
|
|
1329
|
+
// Show blocks with first line preview
|
|
1330
|
+
display.showSystemMessage('📋 Preview: Summary');
|
|
1331
|
+
pasteBlocks.forEach((block, i) => {
|
|
1332
|
+
const preview = block.summary.length > 60 ? block.summary.slice(0, 57) + '...' : block.summary;
|
|
1333
|
+
output.write(theme.ui.muted(`[${i + 1}] `) + theme.info(`${block.lineCount}L`) + theme.ui.muted(` ${preview}`) + '\n');
|
|
1334
|
+
});
|
|
1335
|
+
break;
|
|
1336
|
+
case 'expanded':
|
|
1337
|
+
// Show full content (up to 10 lines each)
|
|
1338
|
+
display.showSystemMessage('📋 Preview: Expanded');
|
|
1339
|
+
pasteBlocks.forEach((block, i) => {
|
|
1340
|
+
output.write(theme.ui.muted(`[${i + 1}] `) + theme.info(`${block.lineCount} lines, ${block.content.length} chars`) + '\n');
|
|
1341
|
+
const lines = block.content.split('\n').slice(0, 10);
|
|
1342
|
+
lines.forEach((line, j) => {
|
|
1343
|
+
const lineNum = theme.ui.muted(`${String(j + 1).padStart(3)} │ `);
|
|
1344
|
+
output.write(lineNum + line.slice(0, 80) + (line.length > 80 ? '...' : '') + '\n');
|
|
1345
|
+
});
|
|
1346
|
+
if (block.lineCount > 10) {
|
|
1347
|
+
output.write(theme.ui.muted(` ... +${block.lineCount - 10} more lines\n`));
|
|
1348
|
+
}
|
|
1349
|
+
output.write('\n');
|
|
1350
|
+
});
|
|
1351
|
+
break;
|
|
1352
|
+
}
|
|
1353
|
+
output.write(theme.ui.muted('(Shift+Tab to cycle preview modes)') + '\n\n');
|
|
1354
|
+
this.rl.prompt(true);
|
|
1355
|
+
}
|
|
1046
1356
|
async flushBufferedInput() {
|
|
1047
1357
|
if (!this.bufferedInputLines.length) {
|
|
1048
1358
|
this.bufferedInputTimer = null;
|
|
@@ -1359,6 +1669,12 @@ export class InteractiveShell {
|
|
|
1359
1669
|
case 'agent-selection':
|
|
1360
1670
|
await this.handleAgentSelectionInput(input);
|
|
1361
1671
|
return true;
|
|
1672
|
+
case 'paste-edit':
|
|
1673
|
+
await this.handlePasteEditInput(input);
|
|
1674
|
+
return true;
|
|
1675
|
+
case 'paste-edit-block':
|
|
1676
|
+
await this.handlePasteBlockEditInput(input, this.pendingInteraction.blockId, this.pendingInteraction.blockNum);
|
|
1677
|
+
return true;
|
|
1362
1678
|
default:
|
|
1363
1679
|
return false;
|
|
1364
1680
|
}
|
|
@@ -1473,6 +1789,100 @@ export class InteractiveShell {
|
|
|
1473
1789
|
];
|
|
1474
1790
|
display.showSystemMessage(info.join('\n'));
|
|
1475
1791
|
}
|
|
1792
|
+
/**
|
|
1793
|
+
* Handle Tab key for slash command autocompletion
|
|
1794
|
+
* Completes partial slash commands like /mo -> /model
|
|
1795
|
+
*/
|
|
1796
|
+
handleTabCompletion() {
|
|
1797
|
+
const currentLine = this.rl.line || '';
|
|
1798
|
+
const cursorPos = this.rl.cursor || 0;
|
|
1799
|
+
// Only complete if line starts with /
|
|
1800
|
+
if (!currentLine.startsWith('/')) {
|
|
1801
|
+
return;
|
|
1802
|
+
}
|
|
1803
|
+
// Get the partial command (from / to cursor)
|
|
1804
|
+
const partial = currentLine.slice(0, cursorPos).toLowerCase();
|
|
1805
|
+
// Available slash commands
|
|
1806
|
+
const commands = [
|
|
1807
|
+
'/help',
|
|
1808
|
+
'/model',
|
|
1809
|
+
'/secrets',
|
|
1810
|
+
'/tools',
|
|
1811
|
+
'/doctor',
|
|
1812
|
+
'/clear',
|
|
1813
|
+
'/cancel',
|
|
1814
|
+
'/compact',
|
|
1815
|
+
'/cost',
|
|
1816
|
+
'/status',
|
|
1817
|
+
'/undo',
|
|
1818
|
+
'/redo',
|
|
1819
|
+
];
|
|
1820
|
+
// Find matching commands
|
|
1821
|
+
const matches = commands.filter(cmd => cmd.startsWith(partial));
|
|
1822
|
+
if (matches.length === 0) {
|
|
1823
|
+
// No matches - beep or do nothing
|
|
1824
|
+
return;
|
|
1825
|
+
}
|
|
1826
|
+
if (matches.length === 1) {
|
|
1827
|
+
// Single match - complete it
|
|
1828
|
+
const completion = matches[0];
|
|
1829
|
+
const suffix = currentLine.slice(cursorPos);
|
|
1830
|
+
const newLine = completion + suffix;
|
|
1831
|
+
const newCursor = completion.length;
|
|
1832
|
+
// Update readline
|
|
1833
|
+
this.rl.line = newLine;
|
|
1834
|
+
this.rl.cursor = newCursor;
|
|
1835
|
+
// Redraw
|
|
1836
|
+
output.write('\r\x1b[K'); // Clear line
|
|
1837
|
+
output.write(formatUserPrompt(this.profileLabel || this.profile));
|
|
1838
|
+
output.write(newLine);
|
|
1839
|
+
// Position cursor
|
|
1840
|
+
if (suffix.length > 0) {
|
|
1841
|
+
output.write(`\x1b[${suffix.length}D`);
|
|
1842
|
+
}
|
|
1843
|
+
// Sync to display components
|
|
1844
|
+
this.persistentPrompt.updateInput(newLine, newCursor);
|
|
1845
|
+
this.pinnedChatBox.setInput(newLine, newCursor);
|
|
1846
|
+
}
|
|
1847
|
+
else {
|
|
1848
|
+
// Multiple matches - show them
|
|
1849
|
+
output.write('\n');
|
|
1850
|
+
output.write(theme.ui.muted('Completions: ') + matches.join(' ') + '\n');
|
|
1851
|
+
this.rl.prompt();
|
|
1852
|
+
// Find common prefix for partial completion
|
|
1853
|
+
const commonPrefix = this.findCommonPrefix(matches);
|
|
1854
|
+
if (commonPrefix.length > partial.length) {
|
|
1855
|
+
const suffix = currentLine.slice(cursorPos);
|
|
1856
|
+
const newLine = commonPrefix + suffix;
|
|
1857
|
+
const newCursor = commonPrefix.length;
|
|
1858
|
+
this.rl.line = newLine;
|
|
1859
|
+
this.rl.cursor = newCursor;
|
|
1860
|
+
this.persistentPrompt.updateInput(newLine, newCursor);
|
|
1861
|
+
this.pinnedChatBox.setInput(newLine, newCursor);
|
|
1862
|
+
}
|
|
1863
|
+
}
|
|
1864
|
+
}
|
|
1865
|
+
/**
|
|
1866
|
+
* Find the longest common prefix among strings
|
|
1867
|
+
*/
|
|
1868
|
+
findCommonPrefix(strings) {
|
|
1869
|
+
if (strings.length === 0)
|
|
1870
|
+
return '';
|
|
1871
|
+
if (strings.length === 1)
|
|
1872
|
+
return strings[0];
|
|
1873
|
+
let prefix = strings[0];
|
|
1874
|
+
for (let i = 1; i < strings.length; i++) {
|
|
1875
|
+
const str = strings[i];
|
|
1876
|
+
let j = 0;
|
|
1877
|
+
while (j < prefix.length && j < str.length && prefix[j] === str[j]) {
|
|
1878
|
+
j++;
|
|
1879
|
+
}
|
|
1880
|
+
prefix = prefix.slice(0, j);
|
|
1881
|
+
if (prefix.length === 0)
|
|
1882
|
+
break;
|
|
1883
|
+
}
|
|
1884
|
+
return prefix;
|
|
1885
|
+
}
|
|
1476
1886
|
runDoctor() {
|
|
1477
1887
|
const lines = [];
|
|
1478
1888
|
lines.push(theme.bold('Environment diagnostics'));
|
|
@@ -2523,7 +2933,9 @@ export class InteractiveShell {
|
|
|
2523
2933
|
try {
|
|
2524
2934
|
// Add visual separator between user prompt and AI response
|
|
2525
2935
|
display.newLine();
|
|
2526
|
-
|
|
2936
|
+
// Claude Code style: Show unified streaming header before response
|
|
2937
|
+
// This provides visual consistency with the startup Ready bar
|
|
2938
|
+
display.showStreamingHeader();
|
|
2527
2939
|
// Claude Code style: Start streaming mode using UnifiedChatBox
|
|
2528
2940
|
// - Output flows naturally to stdout (NO cursor manipulation)
|
|
2529
2941
|
// - User input is captured invisibly and queued
|
|
@@ -2611,6 +3023,8 @@ export class InteractiveShell {
|
|
|
2611
3023
|
display.showSystemMessage(`📊 Using intelligent task completion detection with AI verification.`);
|
|
2612
3024
|
this.uiAdapter.startProcessing('Continuous execution mode');
|
|
2613
3025
|
this.setProcessingStatus();
|
|
3026
|
+
// Claude Code style: Show unified streaming header before response
|
|
3027
|
+
display.showStreamingHeader();
|
|
2614
3028
|
// Claude Code style: Start streaming mode using UnifiedChatBox
|
|
2615
3029
|
// - Output flows naturally to stdout (NO cursor manipulation)
|
|
2616
3030
|
// - User input is captured invisibly and queued
|