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.
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 -10
  4. package/dist/renderer/App.js +165 -314
  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 -156
  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
@@ -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;
@@ -0,0 +1,91 @@
1
+ /**
2
+ * Logout 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
+ /**
8
+ * Render inline logout picker
9
+ */
10
+ export function renderLogoutPanel(screen, startY, width, state) {
11
+ let y = startY;
12
+ // Separator line
13
+ screen.horizontalLine(y++, '─', fg.cyan);
14
+ // Title
15
+ screen.writeLine(y++, 'Select provider to logout:', fg.cyan + style.bold);
16
+ y++;
17
+ if (state.logoutProviders.length === 0) {
18
+ screen.writeLine(y++, 'No providers configured.', fg.yellow);
19
+ screen.writeLine(y++, 'Press Escape to go back.', fg.gray);
20
+ return;
21
+ }
22
+ // Provider options
23
+ for (let i = 0; i < state.logoutProviders.length; i++) {
24
+ const provider = state.logoutProviders[i];
25
+ const isSelected = i === state.logoutIndex;
26
+ const prefix = isSelected ? '→ ' : ' ';
27
+ screen.write(0, y, prefix, isSelected ? fg.green : '');
28
+ screen.write(2, y, provider.name, isSelected ? fg.green + style.bold : fg.white);
29
+ if (provider.isCurrent) {
30
+ screen.write(2 + provider.name.length + 1, y, '(current)', fg.cyan);
31
+ }
32
+ y++;
33
+ }
34
+ // "All" option
35
+ const allIndex = state.logoutProviders.length;
36
+ const isAllSelected = state.logoutIndex === allIndex;
37
+ screen.write(0, y, isAllSelected ? '→ ' : ' ', isAllSelected ? fg.red : '');
38
+ screen.write(2, y, 'Logout from all providers', isAllSelected ? fg.red + style.bold : fg.yellow);
39
+ y++;
40
+ // "Cancel" option
41
+ const cancelIndex = state.logoutProviders.length + 1;
42
+ const isCancelSelected = state.logoutIndex === cancelIndex;
43
+ screen.write(0, y, isCancelSelected ? '→ ' : ' ', isCancelSelected ? fg.blue : '');
44
+ screen.write(2, y, 'Cancel', isCancelSelected ? fg.blue + style.bold : fg.gray);
45
+ y++;
46
+ y++;
47
+ screen.writeLine(y, '↑↓ Navigate • Enter Select • Esc Cancel', fg.gray);
48
+ }
49
+ /**
50
+ * Handle logout picker keys
51
+ */
52
+ export function handleLogoutKey(event, state, callbacks) {
53
+ // Options: providers + "all" + "cancel"
54
+ const totalOptions = state.logoutProviders.length + 2;
55
+ if (event.key === 'escape') {
56
+ state.logoutOpen = false;
57
+ state.logoutCallback = null;
58
+ callbacks.onRender();
59
+ return;
60
+ }
61
+ if (event.key === 'up') {
62
+ state.logoutIndex = Math.max(0, state.logoutIndex - 1);
63
+ callbacks.onRender();
64
+ return;
65
+ }
66
+ if (event.key === 'down') {
67
+ state.logoutIndex = Math.min(totalOptions - 1, state.logoutIndex + 1);
68
+ callbacks.onRender();
69
+ return;
70
+ }
71
+ if (event.key === 'enter') {
72
+ const callback = state.logoutCallback;
73
+ state.logoutOpen = false;
74
+ state.logoutCallback = null;
75
+ let result = null;
76
+ if (state.logoutIndex < state.logoutProviders.length) {
77
+ result = state.logoutProviders[state.logoutIndex].id;
78
+ }
79
+ else if (state.logoutIndex === state.logoutProviders.length) {
80
+ result = 'all';
81
+ }
82
+ else {
83
+ result = null; // Cancel
84
+ }
85
+ callbacks.onRender();
86
+ if (callback) {
87
+ callback(result);
88
+ }
89
+ return;
90
+ }
91
+ }
@@ -0,0 +1,30 @@
1
+ /**
2
+ * Search panel component
3
+ */
4
+ import { Screen } from '../Screen';
5
+ import { KeyEvent } from '../Input';
6
+ export interface SearchResult {
7
+ role: string;
8
+ messageIndex: number;
9
+ matchedText: string;
10
+ }
11
+ export interface SearchState {
12
+ searchOpen: boolean;
13
+ searchQuery: string;
14
+ searchResults: SearchResult[];
15
+ searchIndex: number;
16
+ searchCallback: ((messageIndex: number) => void) | null;
17
+ }
18
+ export interface SearchCallbacks {
19
+ onClose: () => void;
20
+ onRender: () => void;
21
+ onResult: (messageIndex: number) => void;
22
+ }
23
+ /**
24
+ * Render inline search panel
25
+ */
26
+ export declare function renderSearchPanel(screen: Screen, startY: number, width: number, availableHeight: number, state: SearchState): void;
27
+ /**
28
+ * Handle search key events
29
+ */
30
+ export declare function handleSearchKey(event: KeyEvent, state: SearchState, callbacks: SearchCallbacks): void;
@@ -0,0 +1,83 @@
1
+ /**
2
+ * Search 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
+ /**
8
+ * Render inline search panel
9
+ */
10
+ export function renderSearchPanel(screen, startY, width, availableHeight, state) {
11
+ let y = startY;
12
+ // Separator line
13
+ screen.horizontalLine(y++, '\u2500', PRIMARY_COLOR);
14
+ // Title
15
+ screen.writeLine(y++, 'Search Results', PRIMARY_COLOR + style.bold);
16
+ // Query
17
+ screen.write(0, y, 'Query: ', fg.white);
18
+ screen.write(7, y, `"${state.searchQuery}"`, fg.cyan);
19
+ if (state.searchResults.length > 0) {
20
+ screen.write(9 + state.searchQuery.length, y, ` (${state.searchResults.length} ${state.searchResults.length === 1 ? 'result' : 'results'})`, fg.gray);
21
+ }
22
+ y++;
23
+ y++;
24
+ if (state.searchResults.length === 0) {
25
+ screen.writeLine(y++, 'No results found.', fg.yellow);
26
+ }
27
+ else {
28
+ const maxVisible = availableHeight - 6;
29
+ const visibleStart = Math.max(0, state.searchIndex - Math.floor(maxVisible / 2));
30
+ const visibleResults = state.searchResults.slice(visibleStart, visibleStart + maxVisible);
31
+ for (let i = 0; i < visibleResults.length; i++) {
32
+ const result = visibleResults[i];
33
+ const actualIndex = visibleStart + i;
34
+ const isSelected = actualIndex === state.searchIndex;
35
+ const prefix = isSelected ? '\u25B8 ' : ' ';
36
+ const roleColor = result.role === 'user' ? fg.green : fg.blue;
37
+ // First line: role and message number
38
+ screen.write(0, y, prefix, isSelected ? PRIMARY_COLOR : '');
39
+ screen.write(2, y, `[${result.role.toUpperCase()}]`, roleColor + style.bold);
40
+ screen.write(2 + result.role.length + 2, y, ` Message #${result.messageIndex + 1}`, fg.gray);
41
+ y++;
42
+ // Second line: matched text (truncated)
43
+ const maxTextWidth = width - 4;
44
+ const matchedText = result.matchedText.length > maxTextWidth
45
+ ? result.matchedText.slice(0, maxTextWidth - 3) + '...'
46
+ : result.matchedText;
47
+ screen.writeLine(y, ' ' + matchedText, fg.white);
48
+ y++;
49
+ if (i < visibleResults.length - 1)
50
+ y++; // spacing between results
51
+ }
52
+ }
53
+ // Footer
54
+ y = startY + availableHeight - 1;
55
+ screen.writeLine(y, '\u2191\u2193 Navigate \u2022 Enter Jump to message \u2022 Esc Close', fg.gray);
56
+ }
57
+ /**
58
+ * Handle search key events
59
+ */
60
+ export function handleSearchKey(event, state, callbacks) {
61
+ if (event.key === 'escape') {
62
+ callbacks.onClose();
63
+ callbacks.onRender();
64
+ return;
65
+ }
66
+ if (event.key === 'up') {
67
+ state.searchIndex = Math.max(0, state.searchIndex - 1);
68
+ callbacks.onRender();
69
+ return;
70
+ }
71
+ if (event.key === 'down') {
72
+ state.searchIndex = Math.min(state.searchResults.length - 1, state.searchIndex + 1);
73
+ callbacks.onRender();
74
+ return;
75
+ }
76
+ if (event.key === 'enter' && state.searchResults.length > 0) {
77
+ const selectedResult = state.searchResults[state.searchIndex];
78
+ callbacks.onClose();
79
+ callbacks.onRender();
80
+ callbacks.onResult(selectedResult.messageIndex);
81
+ return;
82
+ }
83
+ }
@@ -122,6 +122,26 @@ export const SETTINGS = [
122
122
  { value: false, label: 'Off' },
123
123
  ],
124
124
  },
125
+ {
126
+ key: 'agentAutoCommit',
127
+ label: 'Agent Auto-Commit',
128
+ getValue: () => config.get('agentAutoCommit'),
129
+ type: 'select',
130
+ options: [
131
+ { value: true, label: 'On' },
132
+ { value: false, label: 'Off' },
133
+ ],
134
+ },
135
+ {
136
+ key: 'agentAutoCommitBranch',
137
+ label: 'Auto-Commit on New Branch',
138
+ getValue: () => config.get('agentAutoCommitBranch'),
139
+ type: 'select',
140
+ options: [
141
+ { value: true, label: 'On' },
142
+ { value: false, label: 'Off' },
143
+ ],
144
+ },
125
145
  {
126
146
  key: 'agentMaxFixAttempts',
127
147
  label: 'Agent Max Fix Attempts',
@@ -11,6 +11,12 @@ export interface StatusInfo {
11
11
  hasWriteAccess: boolean;
12
12
  sessionId: string;
13
13
  messageCount: number;
14
+ tokenStats?: {
15
+ totalTokens: number;
16
+ promptTokens: number;
17
+ completionTokens: number;
18
+ requestCount: number;
19
+ };
14
20
  }
15
21
  /**
16
22
  * Render status screen
@@ -38,8 +38,27 @@ export function renderStatusScreen(screen, status) {
38
38
  const valueColor = item.color || fg.white;
39
39
  screen.write(4 + labelWidth, y, item.value, valueColor);
40
40
  }
41
+ // Token usage
42
+ if (status.tokenStats && status.tokenStats.requestCount > 0) {
43
+ const tokenY = contentStartY + items.length + 2;
44
+ screen.write(4, tokenY, 'Token Usage', fg.yellow + style.bold);
45
+ const fmt = (n) => n < 1000 ? n.toString() : (n / 1000).toFixed(1) + 'K';
46
+ const tokenItems = [
47
+ { label: 'Requests', value: status.tokenStats.requestCount.toString() },
48
+ { label: 'Prompt', value: fmt(status.tokenStats.promptTokens) },
49
+ { label: 'Completion', value: fmt(status.tokenStats.completionTokens) },
50
+ { label: 'Total', value: fmt(status.tokenStats.totalTokens) },
51
+ ];
52
+ for (let i = 0; i < tokenItems.length; i++) {
53
+ const item = tokenItems[i];
54
+ const y = tokenY + 1 + i;
55
+ screen.write(4, y, item.label + ':', fg.gray);
56
+ screen.write(4 + labelWidth, y, item.value, fg.white);
57
+ }
58
+ }
41
59
  // System info
42
- const sysInfoY = contentStartY + items.length + 2;
60
+ const tokenSectionHeight = (status.tokenStats && status.tokenStats.requestCount > 0) ? 7 : 0;
61
+ const sysInfoY = contentStartY + items.length + 2 + tokenSectionHeight;
43
62
  screen.write(4, sysInfoY, 'System', fg.yellow + style.bold);
44
63
  const sysItems = [
45
64
  { label: 'Platform', value: process.platform },