codeep 1.1.35 → 1.2.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 +90 -4
- package/dist/api/index.js +64 -2
- package/dist/renderer/App.d.ts +5 -10
- package/dist/renderer/App.js +165 -314
- package/dist/renderer/components/Export.d.ts +22 -0
- package/dist/renderer/components/Export.js +64 -0
- package/dist/renderer/components/Help.js +5 -1
- package/dist/renderer/components/Logout.d.ts +29 -0
- package/dist/renderer/components/Logout.js +91 -0
- package/dist/renderer/components/Search.d.ts +30 -0
- package/dist/renderer/components/Search.js +83 -0
- package/dist/renderer/components/Settings.js +20 -0
- package/dist/renderer/components/Status.d.ts +6 -0
- package/dist/renderer/components/Status.js +20 -1
- package/dist/renderer/main.js +296 -156
- package/dist/utils/agent.d.ts +5 -0
- package/dist/utils/agent.js +238 -3
- package/dist/utils/agent.test.d.ts +1 -0
- package/dist/utils/agent.test.js +250 -0
- package/dist/utils/diffPreview.js +104 -35
- package/dist/utils/gitignore.d.ts +24 -0
- package/dist/utils/gitignore.js +161 -0
- package/dist/utils/gitignore.test.d.ts +1 -0
- package/dist/utils/gitignore.test.js +167 -0
- package/dist/utils/skills.d.ts +21 -0
- package/dist/utils/skills.js +51 -0
- package/dist/utils/smartContext.js +8 -0
- package/dist/utils/smartContext.test.d.ts +1 -0
- package/dist/utils/smartContext.test.js +382 -0
- package/dist/utils/tokenTracker.d.ts +52 -0
- package/dist/utils/tokenTracker.js +86 -0
- package/dist/utils/tools.d.ts +16 -0
- package/dist/utils/tools.js +146 -19
- package/dist/utils/tools.test.d.ts +1 -0
- package/dist/utils/tools.test.js +664 -0
- package/package.json +1 -1
package/dist/renderer/App.js
CHANGED
|
@@ -199,6 +199,9 @@ const COMMAND_DESCRIPTIONS = {
|
|
|
199
199
|
'copy': 'Copy code block',
|
|
200
200
|
'paste': 'Paste from clipboard',
|
|
201
201
|
'apply': 'Apply file changes',
|
|
202
|
+
'add': 'Add file to context',
|
|
203
|
+
'drop': 'Remove file from context',
|
|
204
|
+
'multiline': 'Toggle multi-line input',
|
|
202
205
|
'test': 'Generate/run tests',
|
|
203
206
|
'docs': 'Add documentation',
|
|
204
207
|
'refactor': 'Improve code quality',
|
|
@@ -221,6 +224,9 @@ const COMMAND_DESCRIPTIONS = {
|
|
|
221
224
|
};
|
|
222
225
|
import { helpCategories, keyboardShortcuts } from './components/Help.js';
|
|
223
226
|
import { handleSettingsKey, SETTINGS } from './components/Settings.js';
|
|
227
|
+
import { renderExportPanel, handleExportKey as handleExportKeyComponent } from './components/Export.js';
|
|
228
|
+
import { renderLogoutPanel, handleLogoutKey as handleLogoutKeyComponent } from './components/Logout.js';
|
|
229
|
+
import { renderSearchPanel, handleSearchKey as handleSearchKeyComponent } from './components/Search.js';
|
|
224
230
|
export class App {
|
|
225
231
|
screen;
|
|
226
232
|
input;
|
|
@@ -241,7 +247,6 @@ export class App {
|
|
|
241
247
|
agentIteration = 0;
|
|
242
248
|
agentActions = [];
|
|
243
249
|
agentThinking = '';
|
|
244
|
-
agentCodePreview = null;
|
|
245
250
|
// Paste detection state
|
|
246
251
|
pasteInfo = null;
|
|
247
252
|
pasteInfoOpen = false;
|
|
@@ -307,6 +312,8 @@ export class App {
|
|
|
307
312
|
introProgress = 0;
|
|
308
313
|
introInterval = null;
|
|
309
314
|
introCallback = null;
|
|
315
|
+
// Multi-line input state
|
|
316
|
+
isMultilineMode = false;
|
|
310
317
|
// Inline login state
|
|
311
318
|
loginOpen = false;
|
|
312
319
|
loginStep = 'provider';
|
|
@@ -323,8 +330,16 @@ export class App {
|
|
|
323
330
|
'sessions', 'new', 'rename', 'search', 'export',
|
|
324
331
|
'agent', 'agent-dry', 'stop', 'undo', 'undo-all', 'history', 'changes',
|
|
325
332
|
'diff', 'commit', 'git-commit', 'push', 'pull', 'scan', 'review',
|
|
326
|
-
'copy', 'paste', 'apply',
|
|
333
|
+
'copy', 'paste', 'apply', 'add', 'drop',
|
|
327
334
|
'test', 'docs', 'refactor', 'fix', 'explain', 'optimize', 'debug', 'skills',
|
|
335
|
+
'amend', 'pr', 'changelog', 'branch', 'stash', 'unstash',
|
|
336
|
+
'build', 'deploy', 'release', 'publish',
|
|
337
|
+
'component', 'api', 'model', 'hook', 'service', 'page', 'form', 'crud',
|
|
338
|
+
'security', 'profile', 'log', 'types', 'cleanup', 'modernize', 'migrate',
|
|
339
|
+
'split', 'rename', 'coverage', 'e2e', 'mock', 'readme', 'translate',
|
|
340
|
+
'docker', 'ci', 'env', 'k8s', 'terraform', 'nginx', 'monitor',
|
|
341
|
+
'test-fix', 'api-docs',
|
|
342
|
+
'multiline',
|
|
328
343
|
'provider', 'model', 'protocol', 'lang', 'grant', 'login', 'logout',
|
|
329
344
|
'context-save', 'context-load', 'context-clear', 'learn',
|
|
330
345
|
'c', 't', 'd', 'r', 'f', 'e', 'o', 'b', 'p',
|
|
@@ -492,12 +507,10 @@ export class App {
|
|
|
492
507
|
this.agentIteration = 0;
|
|
493
508
|
this.agentActions = [];
|
|
494
509
|
this.agentThinking = '';
|
|
495
|
-
this.agentCodePreview = null;
|
|
496
510
|
this.isLoading = false; // Clear loading state when agent takes over
|
|
497
511
|
this.startSpinner();
|
|
498
512
|
}
|
|
499
513
|
else {
|
|
500
|
-
this.agentCodePreview = null;
|
|
501
514
|
this.isLoading = false; // Ensure loading is cleared when agent finishes
|
|
502
515
|
this.stopSpinner();
|
|
503
516
|
}
|
|
@@ -518,36 +531,6 @@ export class App {
|
|
|
518
531
|
*/
|
|
519
532
|
setAgentThinking(text) {
|
|
520
533
|
this.agentThinking = text;
|
|
521
|
-
// Clear code preview when agent moves to a non-write/edit action
|
|
522
|
-
if (text && !text.startsWith('write:') && !text.startsWith('edit:')) {
|
|
523
|
-
this.agentCodePreview = null;
|
|
524
|
-
}
|
|
525
|
-
this.render();
|
|
526
|
-
}
|
|
527
|
-
/**
|
|
528
|
-
* Set code preview for agent progress panel
|
|
529
|
-
*/
|
|
530
|
-
setAgentCodePreview(preview) {
|
|
531
|
-
if (preview && preview.content) {
|
|
532
|
-
const ext = preview.path.split('.').pop()?.toLowerCase() || '';
|
|
533
|
-
const lang = LANG_ALIASES[ext] || ext;
|
|
534
|
-
const lines = preview.content.split('\n');
|
|
535
|
-
const MAX_STORED_LINES = 50;
|
|
536
|
-
const trimmedContent = lines.length > MAX_STORED_LINES
|
|
537
|
-
? lines.slice(-MAX_STORED_LINES).join('\n')
|
|
538
|
-
: preview.content;
|
|
539
|
-
this.agentCodePreview = {
|
|
540
|
-
path: preview.path,
|
|
541
|
-
actionType: preview.actionType,
|
|
542
|
-
content: trimmedContent,
|
|
543
|
-
oldContent: preview.oldContent,
|
|
544
|
-
lang,
|
|
545
|
-
totalLines: lines.length,
|
|
546
|
-
};
|
|
547
|
-
}
|
|
548
|
-
else {
|
|
549
|
-
this.agentCodePreview = null;
|
|
550
|
-
}
|
|
551
534
|
this.render();
|
|
552
535
|
}
|
|
553
536
|
/**
|
|
@@ -913,6 +896,13 @@ export class App {
|
|
|
913
896
|
this.render();
|
|
914
897
|
return;
|
|
915
898
|
}
|
|
899
|
+
// In multiline mode, Escape submits the buffered input
|
|
900
|
+
if (this.isMultilineMode && !this.isLoading && !this.isStreaming) {
|
|
901
|
+
if (this.editor.getValue().trim()) {
|
|
902
|
+
this.submitInput();
|
|
903
|
+
return;
|
|
904
|
+
}
|
|
905
|
+
}
|
|
916
906
|
if (this.isAgentRunning && this.options.onStopAgent) {
|
|
917
907
|
this.options.onStopAgent();
|
|
918
908
|
return;
|
|
@@ -1030,25 +1020,20 @@ export class App {
|
|
|
1030
1020
|
}
|
|
1031
1021
|
// Enter to submit (only if not in autocomplete)
|
|
1032
1022
|
if (event.key === 'enter' && !this.isLoading && !this.isStreaming && !this.showAutocomplete) {
|
|
1033
|
-
const
|
|
1034
|
-
if
|
|
1035
|
-
|
|
1036
|
-
this.editor.
|
|
1037
|
-
this.
|
|
1038
|
-
|
|
1039
|
-
if (value.startsWith('/')) {
|
|
1040
|
-
this.handleCommand(value);
|
|
1041
|
-
}
|
|
1042
|
-
else {
|
|
1043
|
-
// Regular message
|
|
1044
|
-
this.addMessage({ role: 'user', content: value });
|
|
1045
|
-
this.setLoading(true);
|
|
1046
|
-
this.options.onSubmit(value).catch(err => {
|
|
1047
|
-
this.notify(`Error: ${err.message}`);
|
|
1048
|
-
this.setLoading(false);
|
|
1049
|
-
});
|
|
1050
|
-
}
|
|
1023
|
+
const rawValue = this.editor.getValue();
|
|
1024
|
+
// Backslash continuation: if line ends with \, add newline instead of submitting
|
|
1025
|
+
if (rawValue.endsWith('\\')) {
|
|
1026
|
+
this.editor.setValue(rawValue.slice(0, -1) + '\n');
|
|
1027
|
+
this.render();
|
|
1028
|
+
return;
|
|
1051
1029
|
}
|
|
1030
|
+
// Multiline mode: Enter adds newline, Ctrl+Enter submits
|
|
1031
|
+
if (this.isMultilineMode && !event.ctrl) {
|
|
1032
|
+
this.editor.insert('\n');
|
|
1033
|
+
this.render();
|
|
1034
|
+
return;
|
|
1035
|
+
}
|
|
1036
|
+
this.submitInput();
|
|
1052
1037
|
return;
|
|
1053
1038
|
}
|
|
1054
1039
|
// Handle paste detection
|
|
@@ -1143,109 +1128,75 @@ export class App {
|
|
|
1143
1128
|
* Handle search screen keys
|
|
1144
1129
|
*/
|
|
1145
1130
|
handleSearchKey(event) {
|
|
1146
|
-
|
|
1147
|
-
this.searchOpen
|
|
1148
|
-
this.
|
|
1149
|
-
this.
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
callback(selectedResult.messageIndex);
|
|
1170
|
-
}
|
|
1171
|
-
return;
|
|
1172
|
-
}
|
|
1131
|
+
const state = {
|
|
1132
|
+
searchOpen: this.searchOpen,
|
|
1133
|
+
searchQuery: this.searchQuery,
|
|
1134
|
+
searchResults: this.searchResults,
|
|
1135
|
+
searchIndex: this.searchIndex,
|
|
1136
|
+
searchCallback: this.searchCallback,
|
|
1137
|
+
};
|
|
1138
|
+
const callback = this.searchCallback;
|
|
1139
|
+
handleSearchKeyComponent(event, state, {
|
|
1140
|
+
onClose: () => {
|
|
1141
|
+
this.searchOpen = false;
|
|
1142
|
+
this.searchCallback = null;
|
|
1143
|
+
},
|
|
1144
|
+
onRender: () => {
|
|
1145
|
+
this.searchIndex = state.searchIndex;
|
|
1146
|
+
this.render();
|
|
1147
|
+
},
|
|
1148
|
+
onResult: (messageIndex) => {
|
|
1149
|
+
if (callback) {
|
|
1150
|
+
callback(messageIndex);
|
|
1151
|
+
}
|
|
1152
|
+
},
|
|
1153
|
+
});
|
|
1173
1154
|
}
|
|
1174
1155
|
/**
|
|
1175
1156
|
* Handle export screen keys
|
|
1176
1157
|
*/
|
|
1177
1158
|
handleExportKey(event) {
|
|
1178
|
-
const
|
|
1179
|
-
|
|
1180
|
-
this.
|
|
1181
|
-
this.exportCallback
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
this.
|
|
1187
|
-
this.render();
|
|
1188
|
-
return;
|
|
1189
|
-
}
|
|
1190
|
-
if (event.key === 'down') {
|
|
1191
|
-
this.exportIndex = this.exportIndex < formats.length - 1 ? this.exportIndex + 1 : 0;
|
|
1192
|
-
this.render();
|
|
1193
|
-
return;
|
|
1194
|
-
}
|
|
1195
|
-
if (event.key === 'enter') {
|
|
1196
|
-
const selectedFormat = formats[this.exportIndex];
|
|
1197
|
-
const callback = this.exportCallback;
|
|
1198
|
-
this.exportOpen = false;
|
|
1199
|
-
this.exportCallback = null;
|
|
1159
|
+
const state = {
|
|
1160
|
+
exportOpen: this.exportOpen,
|
|
1161
|
+
exportIndex: this.exportIndex,
|
|
1162
|
+
exportCallback: this.exportCallback,
|
|
1163
|
+
};
|
|
1164
|
+
const syncState = () => {
|
|
1165
|
+
this.exportOpen = state.exportOpen;
|
|
1166
|
+
this.exportIndex = state.exportIndex;
|
|
1167
|
+
this.exportCallback = state.exportCallback;
|
|
1200
1168
|
this.render();
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1169
|
+
};
|
|
1170
|
+
handleExportKeyComponent(event, state, {
|
|
1171
|
+
onClose: syncState,
|
|
1172
|
+
onRender: syncState,
|
|
1173
|
+
onExport: (format) => {
|
|
1174
|
+
const callback = this.exportCallback;
|
|
1175
|
+
syncState();
|
|
1176
|
+
if (callback) {
|
|
1177
|
+
callback(format);
|
|
1178
|
+
}
|
|
1179
|
+
},
|
|
1180
|
+
});
|
|
1206
1181
|
}
|
|
1207
1182
|
/**
|
|
1208
1183
|
* Handle logout picker keys
|
|
1209
1184
|
*/
|
|
1210
1185
|
handleLogoutKey(event) {
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
this.
|
|
1215
|
-
this.logoutCallback
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
this.logoutIndex = Math.min(totalOptions - 1, this.logoutIndex + 1);
|
|
1226
|
-
this.render();
|
|
1227
|
-
return;
|
|
1228
|
-
}
|
|
1229
|
-
if (event.key === 'enter') {
|
|
1230
|
-
const callback = this.logoutCallback;
|
|
1231
|
-
this.logoutOpen = false;
|
|
1232
|
-
this.logoutCallback = null;
|
|
1233
|
-
let result = null;
|
|
1234
|
-
if (this.logoutIndex < this.logoutProviders.length) {
|
|
1235
|
-
result = this.logoutProviders[this.logoutIndex].id;
|
|
1236
|
-
}
|
|
1237
|
-
else if (this.logoutIndex === this.logoutProviders.length) {
|
|
1238
|
-
result = 'all';
|
|
1239
|
-
}
|
|
1240
|
-
else {
|
|
1241
|
-
result = null; // Cancel
|
|
1242
|
-
}
|
|
1243
|
-
this.render();
|
|
1244
|
-
if (callback) {
|
|
1245
|
-
callback(result);
|
|
1246
|
-
}
|
|
1247
|
-
return;
|
|
1248
|
-
}
|
|
1186
|
+
const state = {
|
|
1187
|
+
logoutOpen: this.logoutOpen,
|
|
1188
|
+
logoutIndex: this.logoutIndex,
|
|
1189
|
+
logoutProviders: this.logoutProviders,
|
|
1190
|
+
logoutCallback: this.logoutCallback,
|
|
1191
|
+
};
|
|
1192
|
+
handleLogoutKeyComponent(event, state, {
|
|
1193
|
+
onClose: () => { },
|
|
1194
|
+
onRender: () => this.render(),
|
|
1195
|
+
onSelect: () => { },
|
|
1196
|
+
});
|
|
1197
|
+
this.logoutOpen = state.logoutOpen;
|
|
1198
|
+
this.logoutIndex = state.logoutIndex;
|
|
1199
|
+
this.logoutCallback = state.logoutCallback;
|
|
1249
1200
|
}
|
|
1250
1201
|
/**
|
|
1251
1202
|
* Handle login keys
|
|
@@ -1569,6 +1520,28 @@ export class App {
|
|
|
1569
1520
|
return;
|
|
1570
1521
|
}
|
|
1571
1522
|
}
|
|
1523
|
+
/**
|
|
1524
|
+
* Submit the current input buffer (used by Enter and Escape-in-multiline)
|
|
1525
|
+
*/
|
|
1526
|
+
submitInput() {
|
|
1527
|
+
const value = this.editor.getValue().trim();
|
|
1528
|
+
if (!value)
|
|
1529
|
+
return;
|
|
1530
|
+
this.editor.addToHistory(value);
|
|
1531
|
+
this.editor.clear();
|
|
1532
|
+
this.showAutocomplete = false;
|
|
1533
|
+
if (value.startsWith('/')) {
|
|
1534
|
+
this.handleCommand(value);
|
|
1535
|
+
}
|
|
1536
|
+
else {
|
|
1537
|
+
this.addMessage({ role: 'user', content: value });
|
|
1538
|
+
this.setLoading(true);
|
|
1539
|
+
this.options.onSubmit(value).catch(err => {
|
|
1540
|
+
this.notify(`Error: ${err.message}`);
|
|
1541
|
+
this.setLoading(false);
|
|
1542
|
+
});
|
|
1543
|
+
}
|
|
1544
|
+
}
|
|
1572
1545
|
/**
|
|
1573
1546
|
* Handle command
|
|
1574
1547
|
*/
|
|
@@ -1590,6 +1563,12 @@ export class App {
|
|
|
1590
1563
|
this.clearMessages();
|
|
1591
1564
|
this.notify('Chat cleared');
|
|
1592
1565
|
break;
|
|
1566
|
+
case 'multiline':
|
|
1567
|
+
this.isMultilineMode = !this.isMultilineMode;
|
|
1568
|
+
this.notify(this.isMultilineMode
|
|
1569
|
+
? 'Multi-line mode ON — Enter adds line, Esc sends'
|
|
1570
|
+
: 'Multi-line mode OFF — Enter sends');
|
|
1571
|
+
break;
|
|
1593
1572
|
case 'exit':
|
|
1594
1573
|
case 'quit':
|
|
1595
1574
|
this.stop();
|
|
@@ -1625,20 +1604,7 @@ export class App {
|
|
|
1625
1604
|
bottomPanelHeight = previewLines + 6; // title + preview + extra line indicator + options
|
|
1626
1605
|
}
|
|
1627
1606
|
else if (this.isAgentRunning) {
|
|
1628
|
-
|
|
1629
|
-
const MAX_PREVIEW_LINES = 12;
|
|
1630
|
-
const storedLines = this.agentCodePreview.content.split('\n').length;
|
|
1631
|
-
const visibleCodeLines = Math.min(storedLines, MAX_PREVIEW_LINES);
|
|
1632
|
-
const startIdx = Math.max(0, storedLines - MAX_PREVIEW_LINES);
|
|
1633
|
-
const startLineNum = this.agentCodePreview.totalLines - storedLines + startIdx;
|
|
1634
|
-
const hasOverflow = startLineNum > 0 ? 1 : 0;
|
|
1635
|
-
// top border + action + code separator + code lines + overflow + stats + bottom border + margin
|
|
1636
|
-
bottomPanelHeight = 1 + 1 + 1 + visibleCodeLines + hasOverflow + 1 + 1 + 1;
|
|
1637
|
-
bottomPanelHeight = Math.min(bottomPanelHeight, Math.floor(height * 0.6));
|
|
1638
|
-
}
|
|
1639
|
-
else {
|
|
1640
|
-
bottomPanelHeight = 5; // Agent progress box (4 lines + 1 margin)
|
|
1641
|
-
}
|
|
1607
|
+
bottomPanelHeight = 5; // Agent progress box (4 lines + 1 margin)
|
|
1642
1608
|
}
|
|
1643
1609
|
else if (this.permissionOpen) {
|
|
1644
1610
|
bottomPanelHeight = 10; // Permission dialog
|
|
@@ -1875,12 +1841,18 @@ export class App {
|
|
|
1875
1841
|
this.screen.showCursor(false);
|
|
1876
1842
|
return;
|
|
1877
1843
|
}
|
|
1878
|
-
|
|
1844
|
+
// Build prompt prefix — show line count for multi-line buffers
|
|
1845
|
+
const lines = inputValue.split('\n');
|
|
1846
|
+
const lineCount = lines.length;
|
|
1847
|
+
const prompt = lineCount > 1 ? `[${lineCount}] > ` : this.isMultilineMode ? 'M> ' : '> ';
|
|
1879
1848
|
const maxInputWidth = width - prompt.length - 1;
|
|
1880
1849
|
// Show placeholder when input is empty
|
|
1881
1850
|
if (!inputValue) {
|
|
1882
1851
|
this.screen.write(0, y, prompt, fg.green);
|
|
1883
|
-
|
|
1852
|
+
const placeholder = this.isMultilineMode
|
|
1853
|
+
? 'Multi-line mode (Enter=newline, Esc=send)...'
|
|
1854
|
+
: 'Type a message or /command...';
|
|
1855
|
+
this.screen.write(prompt.length, y, placeholder, fg.gray);
|
|
1884
1856
|
if (!hideCursor) {
|
|
1885
1857
|
this.screen.setCursor(prompt.length, y);
|
|
1886
1858
|
this.screen.showCursor(true);
|
|
@@ -1890,22 +1862,29 @@ export class App {
|
|
|
1890
1862
|
}
|
|
1891
1863
|
return;
|
|
1892
1864
|
}
|
|
1865
|
+
// For multi-line content, show the last line being edited
|
|
1866
|
+
const lastLine = lines[lines.length - 1];
|
|
1867
|
+
const displayInput = lineCount > 1 ? lastLine : inputValue;
|
|
1868
|
+
// Cursor position within the last line
|
|
1869
|
+
const charsBeforeLastLine = lineCount > 1 ? inputValue.lastIndexOf('\n') + 1 : 0;
|
|
1870
|
+
const cursorInLine = cursorPos - charsBeforeLastLine;
|
|
1893
1871
|
let displayValue;
|
|
1894
1872
|
let cursorX;
|
|
1895
|
-
if (
|
|
1896
|
-
displayValue =
|
|
1897
|
-
cursorX = prompt.length +
|
|
1873
|
+
if (displayInput.length <= maxInputWidth) {
|
|
1874
|
+
displayValue = displayInput;
|
|
1875
|
+
cursorX = prompt.length + Math.max(0, cursorInLine);
|
|
1898
1876
|
}
|
|
1899
1877
|
else {
|
|
1900
|
-
const
|
|
1878
|
+
const effectiveCursor = Math.max(0, cursorInLine);
|
|
1879
|
+
const visibleStart = Math.max(0, effectiveCursor - Math.floor(maxInputWidth * 0.7));
|
|
1901
1880
|
const visibleEnd = visibleStart + maxInputWidth;
|
|
1902
1881
|
if (visibleStart > 0) {
|
|
1903
|
-
displayValue = '…' +
|
|
1882
|
+
displayValue = '…' + displayInput.slice(visibleStart + 1, visibleEnd);
|
|
1904
1883
|
}
|
|
1905
1884
|
else {
|
|
1906
|
-
displayValue =
|
|
1885
|
+
displayValue = displayInput.slice(0, maxInputWidth);
|
|
1907
1886
|
}
|
|
1908
|
-
cursorX = prompt.length + (
|
|
1887
|
+
cursorX = prompt.length + (effectiveCursor - visibleStart);
|
|
1909
1888
|
}
|
|
1910
1889
|
this.screen.writeLine(y, prompt + displayValue, fg.green);
|
|
1911
1890
|
// Hide cursor when menu/settings is open
|
|
@@ -1913,7 +1892,7 @@ export class App {
|
|
|
1913
1892
|
this.screen.showCursor(false);
|
|
1914
1893
|
}
|
|
1915
1894
|
else {
|
|
1916
|
-
this.screen.setCursor(cursorX, y);
|
|
1895
|
+
this.screen.setCursor(Math.min(cursorX, width - 1), y);
|
|
1917
1896
|
this.screen.showCursor(true);
|
|
1918
1897
|
}
|
|
1919
1898
|
}
|
|
@@ -2294,46 +2273,6 @@ export class App {
|
|
|
2294
2273
|
this.screen.write(1, y, 'Starting...', fg.gray);
|
|
2295
2274
|
}
|
|
2296
2275
|
y++;
|
|
2297
|
-
// Code preview section (for write/edit operations)
|
|
2298
|
-
if (this.agentCodePreview) {
|
|
2299
|
-
const MAX_PREVIEW_LINES = 12;
|
|
2300
|
-
// Code separator with language label
|
|
2301
|
-
const langLabel = this.agentCodePreview.lang ? ` ${this.agentCodePreview.lang} ` : '';
|
|
2302
|
-
const sepPadLeft = 2;
|
|
2303
|
-
const sepPadRight = width - sepPadLeft - langLabel.length - 1;
|
|
2304
|
-
this.screen.write(0, y, '─'.repeat(sepPadLeft), SYNTAX.codeFrame);
|
|
2305
|
-
this.screen.write(sepPadLeft, y, langLabel, SYNTAX.codeLang);
|
|
2306
|
-
this.screen.write(sepPadLeft + langLabel.length, y, '─'.repeat(Math.max(0, sepPadRight)), SYNTAX.codeFrame);
|
|
2307
|
-
y++;
|
|
2308
|
-
// Determine visible code lines (show tail for long content)
|
|
2309
|
-
const codeLines = this.agentCodePreview.content.split('\n');
|
|
2310
|
-
const totalLines = this.agentCodePreview.totalLines;
|
|
2311
|
-
const displayCount = Math.min(codeLines.length, MAX_PREVIEW_LINES);
|
|
2312
|
-
const startIdx = Math.max(0, codeLines.length - MAX_PREVIEW_LINES);
|
|
2313
|
-
const startLineNum = totalLines - codeLines.length + startIdx;
|
|
2314
|
-
// Overflow indicator if truncated from top
|
|
2315
|
-
if (startLineNum > 0) {
|
|
2316
|
-
this.screen.write(1, y, `... (${startLineNum} lines above)`, fg.gray);
|
|
2317
|
-
y++;
|
|
2318
|
-
}
|
|
2319
|
-
// Render highlighted code lines with line numbers
|
|
2320
|
-
const lineNumWidth = String(totalLines).length;
|
|
2321
|
-
const codeAreaWidth = width - lineNumWidth - 4;
|
|
2322
|
-
for (let i = 0; i < displayCount; i++) {
|
|
2323
|
-
const lineNum = startLineNum + i + 1;
|
|
2324
|
-
const lineNumStr = String(lineNum).padStart(lineNumWidth, ' ');
|
|
2325
|
-
// Truncate wide lines
|
|
2326
|
-
let codeLine = codeLines[startIdx + i];
|
|
2327
|
-
if (codeLine.length > codeAreaWidth) {
|
|
2328
|
-
codeLine = codeLine.slice(0, codeAreaWidth - 1) + '\u2026';
|
|
2329
|
-
}
|
|
2330
|
-
const highlighted = highlightCode(codeLine, this.agentCodePreview.lang);
|
|
2331
|
-
// Use writeRaw with line number embedded as ANSI since writeRaw clears the full line
|
|
2332
|
-
const lineNumAnsi = SYNTAX.comment + lineNumStr + '\x1b[0m' + ' ';
|
|
2333
|
-
this.screen.writeRaw(y, ' ' + lineNumAnsi + highlighted);
|
|
2334
|
-
y++;
|
|
2335
|
-
}
|
|
2336
|
-
}
|
|
2337
2276
|
// Stats line: Files and step info
|
|
2338
2277
|
let x = 1;
|
|
2339
2278
|
// File changes
|
|
@@ -2705,122 +2644,34 @@ export class App {
|
|
|
2705
2644
|
* Render inline search screen
|
|
2706
2645
|
*/
|
|
2707
2646
|
renderInlineSearch(startY, width, availableHeight) {
|
|
2708
|
-
|
|
2709
|
-
|
|
2710
|
-
|
|
2711
|
-
|
|
2712
|
-
|
|
2713
|
-
|
|
2714
|
-
|
|
2715
|
-
this.screen.write(7, y, `"${this.searchQuery}"`, fg.cyan);
|
|
2716
|
-
if (this.searchResults.length > 0) {
|
|
2717
|
-
this.screen.write(9 + this.searchQuery.length, y, ` (${this.searchResults.length} ${this.searchResults.length === 1 ? 'result' : 'results'})`, fg.gray);
|
|
2718
|
-
}
|
|
2719
|
-
y++;
|
|
2720
|
-
y++;
|
|
2721
|
-
if (this.searchResults.length === 0) {
|
|
2722
|
-
this.screen.writeLine(y++, 'No results found.', fg.yellow);
|
|
2723
|
-
}
|
|
2724
|
-
else {
|
|
2725
|
-
const maxVisible = availableHeight - 6;
|
|
2726
|
-
const visibleStart = Math.max(0, this.searchIndex - Math.floor(maxVisible / 2));
|
|
2727
|
-
const visibleResults = this.searchResults.slice(visibleStart, visibleStart + maxVisible);
|
|
2728
|
-
for (let i = 0; i < visibleResults.length; i++) {
|
|
2729
|
-
const result = visibleResults[i];
|
|
2730
|
-
const actualIndex = visibleStart + i;
|
|
2731
|
-
const isSelected = actualIndex === this.searchIndex;
|
|
2732
|
-
const prefix = isSelected ? '▸ ' : ' ';
|
|
2733
|
-
const roleColor = result.role === 'user' ? fg.green : fg.blue;
|
|
2734
|
-
// First line: role and message number
|
|
2735
|
-
this.screen.write(0, y, prefix, isSelected ? PRIMARY_COLOR : '');
|
|
2736
|
-
this.screen.write(2, y, `[${result.role.toUpperCase()}]`, roleColor + style.bold);
|
|
2737
|
-
this.screen.write(2 + result.role.length + 2, y, ` Message #${result.messageIndex + 1}`, fg.gray);
|
|
2738
|
-
y++;
|
|
2739
|
-
// Second line: matched text (truncated)
|
|
2740
|
-
const maxTextWidth = width - 4;
|
|
2741
|
-
const matchedText = result.matchedText.length > maxTextWidth
|
|
2742
|
-
? result.matchedText.slice(0, maxTextWidth - 3) + '...'
|
|
2743
|
-
: result.matchedText;
|
|
2744
|
-
this.screen.writeLine(y, ' ' + matchedText, fg.white);
|
|
2745
|
-
y++;
|
|
2746
|
-
if (i < visibleResults.length - 1)
|
|
2747
|
-
y++; // spacing between results
|
|
2748
|
-
}
|
|
2749
|
-
}
|
|
2750
|
-
// Footer
|
|
2751
|
-
y = startY + availableHeight - 1;
|
|
2752
|
-
this.screen.writeLine(y, '↑↓ Navigate • Enter Jump to message • Esc Close', fg.gray);
|
|
2647
|
+
renderSearchPanel(this.screen, startY, width, availableHeight, {
|
|
2648
|
+
searchOpen: this.searchOpen,
|
|
2649
|
+
searchQuery: this.searchQuery,
|
|
2650
|
+
searchResults: this.searchResults,
|
|
2651
|
+
searchIndex: this.searchIndex,
|
|
2652
|
+
searchCallback: this.searchCallback,
|
|
2653
|
+
});
|
|
2753
2654
|
}
|
|
2754
2655
|
/**
|
|
2755
2656
|
* Render inline export screen
|
|
2756
2657
|
*/
|
|
2757
2658
|
renderInlineExport(startY, width) {
|
|
2758
|
-
|
|
2759
|
-
|
|
2760
|
-
|
|
2761
|
-
|
|
2762
|
-
|
|
2763
|
-
let y = startY;
|
|
2764
|
-
// Separator line
|
|
2765
|
-
this.screen.horizontalLine(y++, '─', fg.green);
|
|
2766
|
-
// Title
|
|
2767
|
-
this.screen.writeLine(y++, 'Export Chat', fg.green + style.bold);
|
|
2768
|
-
y++;
|
|
2769
|
-
this.screen.writeLine(y++, 'Select export format:', fg.white);
|
|
2770
|
-
y++;
|
|
2771
|
-
for (let i = 0; i < formats.length; i++) {
|
|
2772
|
-
const format = formats[i];
|
|
2773
|
-
const isSelected = i === this.exportIndex;
|
|
2774
|
-
const prefix = isSelected ? '› ' : ' ';
|
|
2775
|
-
this.screen.write(0, y, prefix, isSelected ? fg.green : '');
|
|
2776
|
-
this.screen.write(2, y, format.name.padEnd(12), isSelected ? fg.green + style.bold : fg.white);
|
|
2777
|
-
this.screen.write(14, y, ' - ' + format.desc, fg.gray);
|
|
2778
|
-
y++;
|
|
2779
|
-
}
|
|
2780
|
-
y++;
|
|
2781
|
-
this.screen.writeLine(y, '↑↓ Navigate • Enter Export • Esc Cancel', fg.gray);
|
|
2659
|
+
renderExportPanel(this.screen, startY, width, {
|
|
2660
|
+
exportOpen: this.exportOpen,
|
|
2661
|
+
exportIndex: this.exportIndex,
|
|
2662
|
+
exportCallback: this.exportCallback,
|
|
2663
|
+
});
|
|
2782
2664
|
}
|
|
2783
2665
|
/**
|
|
2784
2666
|
* Render inline logout picker
|
|
2785
2667
|
*/
|
|
2786
2668
|
renderInlineLogout(startY, width) {
|
|
2787
|
-
|
|
2788
|
-
|
|
2789
|
-
|
|
2790
|
-
|
|
2791
|
-
|
|
2792
|
-
|
|
2793
|
-
if (this.logoutProviders.length === 0) {
|
|
2794
|
-
this.screen.writeLine(y++, 'No providers configured.', fg.yellow);
|
|
2795
|
-
this.screen.writeLine(y++, 'Press Escape to go back.', fg.gray);
|
|
2796
|
-
return;
|
|
2797
|
-
}
|
|
2798
|
-
// Provider options
|
|
2799
|
-
for (let i = 0; i < this.logoutProviders.length; i++) {
|
|
2800
|
-
const provider = this.logoutProviders[i];
|
|
2801
|
-
const isSelected = i === this.logoutIndex;
|
|
2802
|
-
const prefix = isSelected ? '→ ' : ' ';
|
|
2803
|
-
this.screen.write(0, y, prefix, isSelected ? fg.green : '');
|
|
2804
|
-
this.screen.write(2, y, provider.name, isSelected ? fg.green + style.bold : fg.white);
|
|
2805
|
-
if (provider.isCurrent) {
|
|
2806
|
-
this.screen.write(2 + provider.name.length + 1, y, '(current)', fg.cyan);
|
|
2807
|
-
}
|
|
2808
|
-
y++;
|
|
2809
|
-
}
|
|
2810
|
-
// "All" option
|
|
2811
|
-
const allIndex = this.logoutProviders.length;
|
|
2812
|
-
const isAllSelected = this.logoutIndex === allIndex;
|
|
2813
|
-
this.screen.write(0, y, isAllSelected ? '→ ' : ' ', isAllSelected ? fg.red : '');
|
|
2814
|
-
this.screen.write(2, y, 'Logout from all providers', isAllSelected ? fg.red + style.bold : fg.yellow);
|
|
2815
|
-
y++;
|
|
2816
|
-
// "Cancel" option
|
|
2817
|
-
const cancelIndex = this.logoutProviders.length + 1;
|
|
2818
|
-
const isCancelSelected = this.logoutIndex === cancelIndex;
|
|
2819
|
-
this.screen.write(0, y, isCancelSelected ? '→ ' : ' ', isCancelSelected ? fg.blue : '');
|
|
2820
|
-
this.screen.write(2, y, 'Cancel', isCancelSelected ? fg.blue + style.bold : fg.gray);
|
|
2821
|
-
y++;
|
|
2822
|
-
y++;
|
|
2823
|
-
this.screen.writeLine(y, '↑↓ Navigate • Enter Select • Esc Cancel', fg.gray);
|
|
2669
|
+
renderLogoutPanel(this.screen, startY, width, {
|
|
2670
|
+
logoutOpen: this.logoutOpen,
|
|
2671
|
+
logoutIndex: this.logoutIndex,
|
|
2672
|
+
logoutProviders: this.logoutProviders,
|
|
2673
|
+
logoutCallback: this.logoutCallback,
|
|
2674
|
+
});
|
|
2824
2675
|
}
|
|
2825
2676
|
/**
|
|
2826
2677
|
* Render inline login dialog
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Export panel component
|
|
3
|
+
*/
|
|
4
|
+
import { Screen } from '../Screen';
|
|
5
|
+
import { KeyEvent } from '../Input';
|
|
6
|
+
export interface ExportState {
|
|
7
|
+
exportOpen: boolean;
|
|
8
|
+
exportIndex: number;
|
|
9
|
+
exportCallback: ((format: 'md' | 'json' | 'txt') => void) | null;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Render inline export panel
|
|
13
|
+
*/
|
|
14
|
+
export declare function renderExportPanel(screen: Screen, startY: number, width: number, state: ExportState): void;
|
|
15
|
+
/**
|
|
16
|
+
* Handle export panel key events
|
|
17
|
+
*/
|
|
18
|
+
export declare function handleExportKey(event: KeyEvent, state: ExportState, callbacks: {
|
|
19
|
+
onClose: () => void;
|
|
20
|
+
onRender: () => void;
|
|
21
|
+
onExport: (format: 'md' | 'json' | 'txt') => void;
|
|
22
|
+
}): void;
|