codeep 1.1.36 → 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.
Files changed (36) hide show
  1. package/README.md +90 -4
  2. package/dist/api/index.js +64 -2
  3. package/dist/renderer/App.d.ts +5 -0
  4. package/dist/renderer/App.js +164 -227
  5. package/dist/renderer/components/Export.d.ts +22 -0
  6. package/dist/renderer/components/Export.js +64 -0
  7. package/dist/renderer/components/Help.js +5 -1
  8. package/dist/renderer/components/Logout.d.ts +29 -0
  9. package/dist/renderer/components/Logout.js +91 -0
  10. package/dist/renderer/components/Search.d.ts +30 -0
  11. package/dist/renderer/components/Search.js +83 -0
  12. package/dist/renderer/components/Settings.js +20 -0
  13. package/dist/renderer/components/Status.d.ts +6 -0
  14. package/dist/renderer/components/Status.js +20 -1
  15. package/dist/renderer/main.js +296 -142
  16. package/dist/utils/agent.d.ts +5 -0
  17. package/dist/utils/agent.js +238 -3
  18. package/dist/utils/agent.test.d.ts +1 -0
  19. package/dist/utils/agent.test.js +250 -0
  20. package/dist/utils/diffPreview.js +104 -35
  21. package/dist/utils/gitignore.d.ts +24 -0
  22. package/dist/utils/gitignore.js +161 -0
  23. package/dist/utils/gitignore.test.d.ts +1 -0
  24. package/dist/utils/gitignore.test.js +167 -0
  25. package/dist/utils/skills.d.ts +21 -0
  26. package/dist/utils/skills.js +51 -0
  27. package/dist/utils/smartContext.js +8 -0
  28. package/dist/utils/smartContext.test.d.ts +1 -0
  29. package/dist/utils/smartContext.test.js +382 -0
  30. package/dist/utils/tokenTracker.d.ts +52 -0
  31. package/dist/utils/tokenTracker.js +86 -0
  32. package/dist/utils/tools.d.ts +16 -0
  33. package/dist/utils/tools.js +146 -19
  34. package/dist/utils/tools.test.d.ts +1 -0
  35. package/dist/utils/tools.test.js +664 -0
  36. package/package.json +1 -1
@@ -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;
@@ -306,6 +312,8 @@ export class App {
306
312
  introProgress = 0;
307
313
  introInterval = null;
308
314
  introCallback = null;
315
+ // Multi-line input state
316
+ isMultilineMode = false;
309
317
  // Inline login state
310
318
  loginOpen = false;
311
319
  loginStep = 'provider';
@@ -322,8 +330,16 @@ export class App {
322
330
  'sessions', 'new', 'rename', 'search', 'export',
323
331
  'agent', 'agent-dry', 'stop', 'undo', 'undo-all', 'history', 'changes',
324
332
  'diff', 'commit', 'git-commit', 'push', 'pull', 'scan', 'review',
325
- 'copy', 'paste', 'apply',
333
+ 'copy', 'paste', 'apply', 'add', 'drop',
326
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',
327
343
  'provider', 'model', 'protocol', 'lang', 'grant', 'login', 'logout',
328
344
  'context-save', 'context-load', 'context-clear', 'learn',
329
345
  'c', 't', 'd', 'r', 'f', 'e', 'o', 'b', 'p',
@@ -880,6 +896,13 @@ export class App {
880
896
  this.render();
881
897
  return;
882
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
+ }
883
906
  if (this.isAgentRunning && this.options.onStopAgent) {
884
907
  this.options.onStopAgent();
885
908
  return;
@@ -997,25 +1020,20 @@ export class App {
997
1020
  }
998
1021
  // Enter to submit (only if not in autocomplete)
999
1022
  if (event.key === 'enter' && !this.isLoading && !this.isStreaming && !this.showAutocomplete) {
1000
- const value = this.editor.getValue().trim();
1001
- if (value) {
1002
- this.editor.addToHistory(value);
1003
- this.editor.clear();
1004
- this.showAutocomplete = false;
1005
- // Check for commands
1006
- if (value.startsWith('/')) {
1007
- this.handleCommand(value);
1008
- }
1009
- else {
1010
- // Regular message
1011
- this.addMessage({ role: 'user', content: value });
1012
- this.setLoading(true);
1013
- this.options.onSubmit(value).catch(err => {
1014
- this.notify(`Error: ${err.message}`);
1015
- this.setLoading(false);
1016
- });
1017
- }
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;
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;
1018
1035
  }
1036
+ this.submitInput();
1019
1037
  return;
1020
1038
  }
1021
1039
  // Handle paste detection
@@ -1110,109 +1128,75 @@ export class App {
1110
1128
  * Handle search screen keys
1111
1129
  */
1112
1130
  handleSearchKey(event) {
1113
- if (event.key === 'escape') {
1114
- this.searchOpen = false;
1115
- this.searchCallback = null;
1116
- this.render();
1117
- return;
1118
- }
1119
- if (event.key === 'up') {
1120
- this.searchIndex = Math.max(0, this.searchIndex - 1);
1121
- this.render();
1122
- return;
1123
- }
1124
- if (event.key === 'down') {
1125
- this.searchIndex = Math.min(this.searchResults.length - 1, this.searchIndex + 1);
1126
- this.render();
1127
- return;
1128
- }
1129
- if (event.key === 'enter' && this.searchResults.length > 0) {
1130
- const selectedResult = this.searchResults[this.searchIndex];
1131
- const callback = this.searchCallback;
1132
- this.searchOpen = false;
1133
- this.searchCallback = null;
1134
- this.render();
1135
- if (callback) {
1136
- callback(selectedResult.messageIndex);
1137
- }
1138
- return;
1139
- }
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
+ });
1140
1154
  }
1141
1155
  /**
1142
1156
  * Handle export screen keys
1143
1157
  */
1144
1158
  handleExportKey(event) {
1145
- const formats = ['md', 'json', 'txt'];
1146
- if (event.key === 'escape') {
1147
- this.exportOpen = false;
1148
- this.exportCallback = null;
1149
- this.render();
1150
- return;
1151
- }
1152
- if (event.key === 'up') {
1153
- this.exportIndex = this.exportIndex > 0 ? this.exportIndex - 1 : formats.length - 1;
1154
- this.render();
1155
- return;
1156
- }
1157
- if (event.key === 'down') {
1158
- this.exportIndex = this.exportIndex < formats.length - 1 ? this.exportIndex + 1 : 0;
1159
- this.render();
1160
- return;
1161
- }
1162
- if (event.key === 'enter') {
1163
- const selectedFormat = formats[this.exportIndex];
1164
- const callback = this.exportCallback;
1165
- this.exportOpen = false;
1166
- 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;
1167
1168
  this.render();
1168
- if (callback) {
1169
- callback(selectedFormat);
1170
- }
1171
- return;
1172
- }
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
+ });
1173
1181
  }
1174
1182
  /**
1175
1183
  * Handle logout picker keys
1176
1184
  */
1177
1185
  handleLogoutKey(event) {
1178
- // Options: providers + "all" + "cancel"
1179
- const totalOptions = this.logoutProviders.length + 2;
1180
- if (event.key === 'escape') {
1181
- this.logoutOpen = false;
1182
- this.logoutCallback = null;
1183
- this.render();
1184
- return;
1185
- }
1186
- if (event.key === 'up') {
1187
- this.logoutIndex = Math.max(0, this.logoutIndex - 1);
1188
- this.render();
1189
- return;
1190
- }
1191
- if (event.key === 'down') {
1192
- this.logoutIndex = Math.min(totalOptions - 1, this.logoutIndex + 1);
1193
- this.render();
1194
- return;
1195
- }
1196
- if (event.key === 'enter') {
1197
- const callback = this.logoutCallback;
1198
- this.logoutOpen = false;
1199
- this.logoutCallback = null;
1200
- let result = null;
1201
- if (this.logoutIndex < this.logoutProviders.length) {
1202
- result = this.logoutProviders[this.logoutIndex].id;
1203
- }
1204
- else if (this.logoutIndex === this.logoutProviders.length) {
1205
- result = 'all';
1206
- }
1207
- else {
1208
- result = null; // Cancel
1209
- }
1210
- this.render();
1211
- if (callback) {
1212
- callback(result);
1213
- }
1214
- return;
1215
- }
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;
1216
1200
  }
1217
1201
  /**
1218
1202
  * Handle login keys
@@ -1536,6 +1520,28 @@ export class App {
1536
1520
  return;
1537
1521
  }
1538
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
+ }
1539
1545
  /**
1540
1546
  * Handle command
1541
1547
  */
@@ -1557,6 +1563,12 @@ export class App {
1557
1563
  this.clearMessages();
1558
1564
  this.notify('Chat cleared');
1559
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;
1560
1572
  case 'exit':
1561
1573
  case 'quit':
1562
1574
  this.stop();
@@ -1829,12 +1841,18 @@ export class App {
1829
1841
  this.screen.showCursor(false);
1830
1842
  return;
1831
1843
  }
1832
- const prompt = '> ';
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> ' : '> ';
1833
1848
  const maxInputWidth = width - prompt.length - 1;
1834
1849
  // Show placeholder when input is empty
1835
1850
  if (!inputValue) {
1836
1851
  this.screen.write(0, y, prompt, fg.green);
1837
- this.screen.write(prompt.length, y, 'Type a message or /command...', fg.gray);
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);
1838
1856
  if (!hideCursor) {
1839
1857
  this.screen.setCursor(prompt.length, y);
1840
1858
  this.screen.showCursor(true);
@@ -1844,22 +1862,29 @@ export class App {
1844
1862
  }
1845
1863
  return;
1846
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;
1847
1871
  let displayValue;
1848
1872
  let cursorX;
1849
- if (inputValue.length <= maxInputWidth) {
1850
- displayValue = inputValue;
1851
- cursorX = prompt.length + cursorPos;
1873
+ if (displayInput.length <= maxInputWidth) {
1874
+ displayValue = displayInput;
1875
+ cursorX = prompt.length + Math.max(0, cursorInLine);
1852
1876
  }
1853
1877
  else {
1854
- const visibleStart = Math.max(0, cursorPos - Math.floor(maxInputWidth * 0.7));
1878
+ const effectiveCursor = Math.max(0, cursorInLine);
1879
+ const visibleStart = Math.max(0, effectiveCursor - Math.floor(maxInputWidth * 0.7));
1855
1880
  const visibleEnd = visibleStart + maxInputWidth;
1856
1881
  if (visibleStart > 0) {
1857
- displayValue = '…' + inputValue.slice(visibleStart + 1, visibleEnd);
1882
+ displayValue = '…' + displayInput.slice(visibleStart + 1, visibleEnd);
1858
1883
  }
1859
1884
  else {
1860
- displayValue = inputValue.slice(0, maxInputWidth);
1885
+ displayValue = displayInput.slice(0, maxInputWidth);
1861
1886
  }
1862
- cursorX = prompt.length + (cursorPos - visibleStart);
1887
+ cursorX = prompt.length + (effectiveCursor - visibleStart);
1863
1888
  }
1864
1889
  this.screen.writeLine(y, prompt + displayValue, fg.green);
1865
1890
  // Hide cursor when menu/settings is open
@@ -1867,7 +1892,7 @@ export class App {
1867
1892
  this.screen.showCursor(false);
1868
1893
  }
1869
1894
  else {
1870
- this.screen.setCursor(cursorX, y);
1895
+ this.screen.setCursor(Math.min(cursorX, width - 1), y);
1871
1896
  this.screen.showCursor(true);
1872
1897
  }
1873
1898
  }
@@ -2619,122 +2644,34 @@ export class App {
2619
2644
  * Render inline search screen
2620
2645
  */
2621
2646
  renderInlineSearch(startY, width, availableHeight) {
2622
- let y = startY;
2623
- // Separator line
2624
- this.screen.horizontalLine(y++, '─', PRIMARY_COLOR);
2625
- // Title
2626
- this.screen.writeLine(y++, 'Search Results', PRIMARY_COLOR + style.bold);
2627
- // Query
2628
- this.screen.write(0, y, 'Query: ', fg.white);
2629
- this.screen.write(7, y, `"${this.searchQuery}"`, fg.cyan);
2630
- if (this.searchResults.length > 0) {
2631
- this.screen.write(9 + this.searchQuery.length, y, ` (${this.searchResults.length} ${this.searchResults.length === 1 ? 'result' : 'results'})`, fg.gray);
2632
- }
2633
- y++;
2634
- y++;
2635
- if (this.searchResults.length === 0) {
2636
- this.screen.writeLine(y++, 'No results found.', fg.yellow);
2637
- }
2638
- else {
2639
- const maxVisible = availableHeight - 6;
2640
- const visibleStart = Math.max(0, this.searchIndex - Math.floor(maxVisible / 2));
2641
- const visibleResults = this.searchResults.slice(visibleStart, visibleStart + maxVisible);
2642
- for (let i = 0; i < visibleResults.length; i++) {
2643
- const result = visibleResults[i];
2644
- const actualIndex = visibleStart + i;
2645
- const isSelected = actualIndex === this.searchIndex;
2646
- const prefix = isSelected ? '▸ ' : ' ';
2647
- const roleColor = result.role === 'user' ? fg.green : fg.blue;
2648
- // First line: role and message number
2649
- this.screen.write(0, y, prefix, isSelected ? PRIMARY_COLOR : '');
2650
- this.screen.write(2, y, `[${result.role.toUpperCase()}]`, roleColor + style.bold);
2651
- this.screen.write(2 + result.role.length + 2, y, ` Message #${result.messageIndex + 1}`, fg.gray);
2652
- y++;
2653
- // Second line: matched text (truncated)
2654
- const maxTextWidth = width - 4;
2655
- const matchedText = result.matchedText.length > maxTextWidth
2656
- ? result.matchedText.slice(0, maxTextWidth - 3) + '...'
2657
- : result.matchedText;
2658
- this.screen.writeLine(y, ' ' + matchedText, fg.white);
2659
- y++;
2660
- if (i < visibleResults.length - 1)
2661
- y++; // spacing between results
2662
- }
2663
- }
2664
- // Footer
2665
- y = startY + availableHeight - 1;
2666
- 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
+ });
2667
2654
  }
2668
2655
  /**
2669
2656
  * Render inline export screen
2670
2657
  */
2671
2658
  renderInlineExport(startY, width) {
2672
- const formats = [
2673
- { id: 'md', name: 'Markdown', desc: 'Formatted with headers and separators' },
2674
- { id: 'json', name: 'JSON', desc: 'Structured data format' },
2675
- { id: 'txt', name: 'Plain Text', desc: 'Simple text format' },
2676
- ];
2677
- let y = startY;
2678
- // Separator line
2679
- this.screen.horizontalLine(y++, '─', fg.green);
2680
- // Title
2681
- this.screen.writeLine(y++, 'Export Chat', fg.green + style.bold);
2682
- y++;
2683
- this.screen.writeLine(y++, 'Select export format:', fg.white);
2684
- y++;
2685
- for (let i = 0; i < formats.length; i++) {
2686
- const format = formats[i];
2687
- const isSelected = i === this.exportIndex;
2688
- const prefix = isSelected ? '› ' : ' ';
2689
- this.screen.write(0, y, prefix, isSelected ? fg.green : '');
2690
- this.screen.write(2, y, format.name.padEnd(12), isSelected ? fg.green + style.bold : fg.white);
2691
- this.screen.write(14, y, ' - ' + format.desc, fg.gray);
2692
- y++;
2693
- }
2694
- y++;
2695
- 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
+ });
2696
2664
  }
2697
2665
  /**
2698
2666
  * Render inline logout picker
2699
2667
  */
2700
2668
  renderInlineLogout(startY, width) {
2701
- let y = startY;
2702
- // Separator line
2703
- this.screen.horizontalLine(y++, '─', fg.cyan);
2704
- // Title
2705
- this.screen.writeLine(y++, 'Select provider to logout:', fg.cyan + style.bold);
2706
- y++;
2707
- if (this.logoutProviders.length === 0) {
2708
- this.screen.writeLine(y++, 'No providers configured.', fg.yellow);
2709
- this.screen.writeLine(y++, 'Press Escape to go back.', fg.gray);
2710
- return;
2711
- }
2712
- // Provider options
2713
- for (let i = 0; i < this.logoutProviders.length; i++) {
2714
- const provider = this.logoutProviders[i];
2715
- const isSelected = i === this.logoutIndex;
2716
- const prefix = isSelected ? '→ ' : ' ';
2717
- this.screen.write(0, y, prefix, isSelected ? fg.green : '');
2718
- this.screen.write(2, y, provider.name, isSelected ? fg.green + style.bold : fg.white);
2719
- if (provider.isCurrent) {
2720
- this.screen.write(2 + provider.name.length + 1, y, '(current)', fg.cyan);
2721
- }
2722
- y++;
2723
- }
2724
- // "All" option
2725
- const allIndex = this.logoutProviders.length;
2726
- const isAllSelected = this.logoutIndex === allIndex;
2727
- this.screen.write(0, y, isAllSelected ? '→ ' : ' ', isAllSelected ? fg.red : '');
2728
- this.screen.write(2, y, 'Logout from all providers', isAllSelected ? fg.red + style.bold : fg.yellow);
2729
- y++;
2730
- // "Cancel" option
2731
- const cancelIndex = this.logoutProviders.length + 1;
2732
- const isCancelSelected = this.logoutIndex === cancelIndex;
2733
- this.screen.write(0, y, isCancelSelected ? '→ ' : ' ', isCancelSelected ? fg.blue : '');
2734
- this.screen.write(2, y, 'Cancel', isCancelSelected ? fg.blue + style.bold : fg.gray);
2735
- y++;
2736
- y++;
2737
- 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
+ });
2738
2675
  }
2739
2676
  /**
2740
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;
@@ -0,0 +1,64 @@
1
+ /**
2
+ * Export panel component
3
+ */
4
+ import { fg, style } from '../ansi.js';
5
+ // Primary color: #f02a30 (Codeep red)
6
+ const PRIMARY_COLOR = fg.rgb(240, 42, 48);
7
+ const FORMATS = [
8
+ { id: 'md', name: 'Markdown', desc: 'Formatted with headers and separators' },
9
+ { id: 'json', name: 'JSON', desc: 'Structured data format' },
10
+ { id: 'txt', name: 'Plain Text', desc: 'Simple text format' },
11
+ ];
12
+ const FORMAT_IDS = ['md', 'json', 'txt'];
13
+ /**
14
+ * Render inline export panel
15
+ */
16
+ export function renderExportPanel(screen, startY, width, state) {
17
+ let y = startY;
18
+ // Separator line
19
+ screen.horizontalLine(y++, '─', fg.green);
20
+ // Title
21
+ screen.writeLine(y++, 'Export Chat', fg.green + style.bold);
22
+ y++;
23
+ screen.writeLine(y++, 'Select export format:', fg.white);
24
+ y++;
25
+ for (let i = 0; i < FORMATS.length; i++) {
26
+ const format = FORMATS[i];
27
+ const isSelected = i === state.exportIndex;
28
+ const prefix = isSelected ? '› ' : ' ';
29
+ screen.write(0, y, prefix, isSelected ? fg.green : '');
30
+ screen.write(2, y, format.name.padEnd(12), isSelected ? fg.green + style.bold : fg.white);
31
+ screen.write(14, y, ' - ' + format.desc, fg.gray);
32
+ y++;
33
+ }
34
+ y++;
35
+ screen.writeLine(y, '↑↓ Navigate • Enter Export • Esc Cancel', fg.gray);
36
+ }
37
+ /**
38
+ * Handle export panel key events
39
+ */
40
+ export function handleExportKey(event, state, callbacks) {
41
+ if (event.key === 'escape') {
42
+ state.exportOpen = false;
43
+ state.exportCallback = null;
44
+ callbacks.onClose();
45
+ return;
46
+ }
47
+ if (event.key === 'up') {
48
+ state.exportIndex = state.exportIndex > 0 ? state.exportIndex - 1 : FORMAT_IDS.length - 1;
49
+ callbacks.onRender();
50
+ return;
51
+ }
52
+ if (event.key === 'down') {
53
+ state.exportIndex = state.exportIndex < FORMAT_IDS.length - 1 ? state.exportIndex + 1 : 0;
54
+ callbacks.onRender();
55
+ return;
56
+ }
57
+ if (event.key === 'enter') {
58
+ const selectedFormat = FORMAT_IDS[state.exportIndex];
59
+ state.exportOpen = false;
60
+ state.exportCallback = null;
61
+ callbacks.onExport(selectedFormat);
62
+ return;
63
+ }
64
+ }
@@ -61,6 +61,9 @@ export const helpCategories = [
61
61
  { key: '/copy [n]', description: 'Copy code block to clipboard' },
62
62
  { key: '/paste', description: 'Paste from clipboard' },
63
63
  { key: '/apply', description: 'Apply file changes from AI' },
64
+ { key: '/add <path>', description: 'Add file to context' },
65
+ { key: '/drop [path]', description: 'Remove file (or all) from context' },
66
+ { key: '/multiline', description: 'Toggle multi-line input mode' },
64
67
  ],
65
68
  },
66
69
  {
@@ -105,7 +108,8 @@ export const helpCategories = [
105
108
  */
106
109
  export const keyboardShortcuts = [
107
110
  { key: 'Enter', description: 'Send message' },
108
- { key: 'Esc', description: 'Cancel/Close' },
111
+ { key: '\\+Enter', description: 'Continue on next line' },
112
+ { key: 'Esc', description: 'Cancel/Close (send in multiline)' },
109
113
  { key: 'Ctrl+L', description: 'Clear screen' },
110
114
  { key: 'Ctrl+C', description: 'Exit' },
111
115
  { key: '↑/↓', description: 'Input history' },
@@ -0,0 +1,29 @@
1
+ /**
2
+ * Logout panel component
3
+ */
4
+ import { Screen } from '../Screen';
5
+ import { KeyEvent } from '../Input';
6
+ export interface LogoutProvider {
7
+ id: string;
8
+ name: string;
9
+ isCurrent: boolean;
10
+ }
11
+ export interface LogoutState {
12
+ logoutOpen: boolean;
13
+ logoutIndex: number;
14
+ logoutProviders: LogoutProvider[];
15
+ logoutCallback: ((providerId: string | 'all' | null) => void) | null;
16
+ }
17
+ export interface LogoutCallbacks {
18
+ onClose: () => void;
19
+ onRender: () => void;
20
+ onSelect: (result: string | 'all' | null) => void;
21
+ }
22
+ /**
23
+ * Render inline logout picker
24
+ */
25
+ export declare function renderLogoutPanel(screen: Screen, startY: number, width: number, state: LogoutState): void;
26
+ /**
27
+ * Handle logout picker keys
28
+ */
29
+ export declare function handleLogoutKey(event: KeyEvent, state: LogoutState, callbacks: LogoutCallbacks): void;