codeep 1.1.12 → 1.1.13

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 (45) hide show
  1. package/bin/codeep.js +1 -1
  2. package/dist/config/index.js +10 -10
  3. package/dist/renderer/App.d.ts +430 -0
  4. package/dist/renderer/App.js +2712 -0
  5. package/dist/renderer/ChatUI.d.ts +71 -0
  6. package/dist/renderer/ChatUI.js +286 -0
  7. package/dist/renderer/Input.d.ts +72 -0
  8. package/dist/renderer/Input.js +371 -0
  9. package/dist/renderer/Screen.d.ts +79 -0
  10. package/dist/renderer/Screen.js +278 -0
  11. package/dist/renderer/ansi.d.ts +99 -0
  12. package/dist/renderer/ansi.js +176 -0
  13. package/dist/renderer/components/Box.d.ts +64 -0
  14. package/dist/renderer/components/Box.js +90 -0
  15. package/dist/renderer/components/Help.d.ts +30 -0
  16. package/dist/renderer/components/Help.js +195 -0
  17. package/dist/renderer/components/Intro.d.ts +12 -0
  18. package/dist/renderer/components/Intro.js +128 -0
  19. package/dist/renderer/components/Login.d.ts +42 -0
  20. package/dist/renderer/components/Login.js +178 -0
  21. package/dist/renderer/components/Modal.d.ts +43 -0
  22. package/dist/renderer/components/Modal.js +207 -0
  23. package/dist/renderer/components/Permission.d.ts +20 -0
  24. package/dist/renderer/components/Permission.js +113 -0
  25. package/dist/renderer/components/SelectScreen.d.ts +26 -0
  26. package/dist/renderer/components/SelectScreen.js +101 -0
  27. package/dist/renderer/components/Settings.d.ts +37 -0
  28. package/dist/renderer/components/Settings.js +333 -0
  29. package/dist/renderer/components/Status.d.ts +18 -0
  30. package/dist/renderer/components/Status.js +78 -0
  31. package/dist/renderer/demo-app.d.ts +6 -0
  32. package/dist/renderer/demo-app.js +85 -0
  33. package/dist/renderer/demo.d.ts +6 -0
  34. package/dist/renderer/demo.js +52 -0
  35. package/dist/renderer/index.d.ts +16 -0
  36. package/dist/renderer/index.js +17 -0
  37. package/dist/renderer/main.d.ts +6 -0
  38. package/dist/renderer/main.js +1634 -0
  39. package/dist/utils/agent.d.ts +21 -0
  40. package/dist/utils/agent.js +29 -0
  41. package/dist/utils/clipboard.d.ts +15 -0
  42. package/dist/utils/clipboard.js +95 -0
  43. package/package.json +7 -11
  44. package/dist/utils/console.d.ts +0 -55
  45. package/dist/utils/console.js +0 -188
@@ -0,0 +1,30 @@
1
+ /**
2
+ * Help screen component
3
+ */
4
+ import { Screen } from '../Screen';
5
+ export interface HelpCategory {
6
+ title: string;
7
+ items: Array<{
8
+ key: string;
9
+ description: string;
10
+ }>;
11
+ }
12
+ /**
13
+ * Codeep command help data
14
+ */
15
+ export declare const helpCategories: HelpCategory[];
16
+ /**
17
+ * Keyboard shortcuts
18
+ */
19
+ export declare const keyboardShortcuts: {
20
+ key: string;
21
+ description: string;
22
+ }[];
23
+ /**
24
+ * Get total number of help pages
25
+ */
26
+ export declare function getHelpTotalPages(screenHeight: number): number;
27
+ /**
28
+ * Render full help screen
29
+ */
30
+ export declare function renderHelpScreen(screen: Screen, page?: number): void;
@@ -0,0 +1,195 @@
1
+ /**
2
+ * Help screen 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
+ * Codeep command help data
9
+ */
10
+ export const helpCategories = [
11
+ {
12
+ title: 'General',
13
+ items: [
14
+ { key: '/help', description: 'Show this help' },
15
+ { key: '/status', description: 'Current status' },
16
+ { key: '/settings', description: 'Open settings' },
17
+ { key: '/version', description: 'Show version' },
18
+ { key: '/update', description: 'Check for updates' },
19
+ { key: '/clear', description: 'Clear chat' },
20
+ { key: '/exit', description: 'Quit application' },
21
+ ],
22
+ },
23
+ {
24
+ title: 'Sessions',
25
+ items: [
26
+ { key: '/sessions', description: 'List and load sessions' },
27
+ { key: '/new', description: 'Start new session' },
28
+ { key: '/rename <name>', description: 'Rename current session' },
29
+ { key: '/search <term>', description: 'Search chat history' },
30
+ { key: '/export [md|json|txt]', description: 'Export chat' },
31
+ ],
32
+ },
33
+ {
34
+ title: 'Agent Mode',
35
+ items: [
36
+ { key: '/agent <task>', description: 'Run agent with task' },
37
+ { key: '/agent-dry <task>', description: 'Dry run (no changes)' },
38
+ { key: '/stop', description: 'Stop running agent' },
39
+ { key: '/undo', description: 'Undo last agent action' },
40
+ { key: '/undo-all', description: 'Undo all agent actions' },
41
+ { key: '/history', description: 'Show agent history' },
42
+ { key: '/changes', description: 'Show session changes' },
43
+ ],
44
+ },
45
+ {
46
+ title: 'Git & Project',
47
+ items: [
48
+ { key: '/diff', description: 'Review git diff with AI' },
49
+ { key: '/diff --staged', description: 'Review staged changes' },
50
+ { key: '/commit (/c)', description: 'Generate commit message' },
51
+ { key: '/git-commit <msg>', description: 'Commit with message' },
52
+ { key: '/push (/p)', description: 'Git push' },
53
+ { key: '/pull', description: 'Git pull' },
54
+ { key: '/scan', description: 'Scan project structure' },
55
+ { key: '/review', description: 'Code review' },
56
+ ],
57
+ },
58
+ {
59
+ title: 'Code Operations',
60
+ items: [
61
+ { key: '/copy [n]', description: 'Copy code block to clipboard' },
62
+ { key: '/paste', description: 'Paste from clipboard' },
63
+ { key: '/apply', description: 'Apply file changes from AI' },
64
+ ],
65
+ },
66
+ {
67
+ title: 'Skills (Shortcuts)',
68
+ items: [
69
+ { key: '/test (/t)', description: 'Generate/run tests' },
70
+ { key: '/docs (/d)', description: 'Add documentation' },
71
+ { key: '/refactor (/r)', description: 'Improve code quality' },
72
+ { key: '/fix (/f)', description: 'Debug and fix issues' },
73
+ { key: '/explain (/e)', description: 'Explain code' },
74
+ { key: '/optimize (/o)', description: 'Optimize performance' },
75
+ { key: '/debug (/b)', description: 'Debug problems' },
76
+ { key: '/skills', description: 'List all skills' },
77
+ ],
78
+ },
79
+ {
80
+ title: 'Settings',
81
+ items: [
82
+ { key: '/provider', description: 'Change AI provider' },
83
+ { key: '/model', description: 'Change model' },
84
+ { key: '/protocol', description: 'Switch API protocol' },
85
+ { key: '/lang', description: 'Set response language' },
86
+ { key: '/grant', description: 'Grant write permission' },
87
+ { key: '/login', description: 'Login with API key' },
88
+ { key: '/logout', description: 'Logout from provider' },
89
+ ],
90
+ },
91
+ {
92
+ title: 'Context',
93
+ items: [
94
+ { key: '/context-save', description: 'Save conversation' },
95
+ { key: '/context-load', description: 'Load conversation' },
96
+ { key: '/context-clear', description: 'Clear saved context' },
97
+ { key: '/learn', description: 'Learn code preferences' },
98
+ { key: '/learn status', description: 'Show learned prefs' },
99
+ { key: '/learn rule <text>', description: 'Add custom rule' },
100
+ ],
101
+ },
102
+ ];
103
+ /**
104
+ * Keyboard shortcuts
105
+ */
106
+ export const keyboardShortcuts = [
107
+ { key: 'Enter', description: 'Send message' },
108
+ { key: 'Esc', description: 'Cancel/Close' },
109
+ { key: 'Ctrl+L', description: 'Clear screen' },
110
+ { key: 'Ctrl+C', description: 'Exit' },
111
+ { key: '↑/↓', description: 'Input history' },
112
+ { key: 'PgUp/PgDn', description: 'Scroll messages' },
113
+ ];
114
+ /**
115
+ * Get total number of help pages
116
+ */
117
+ export function getHelpTotalPages(screenHeight) {
118
+ const availableHeight = screenHeight - 5; // Account for title and footer
119
+ // Count all items
120
+ let itemCount = 0;
121
+ for (const category of helpCategories) {
122
+ itemCount += 2; // Empty line + category header
123
+ itemCount += category.items.length;
124
+ }
125
+ itemCount += 2; // Keyboard shortcuts header
126
+ itemCount += keyboardShortcuts.length;
127
+ return Math.max(1, Math.ceil(itemCount / availableHeight));
128
+ }
129
+ /**
130
+ * Render full help screen
131
+ */
132
+ export function renderHelpScreen(screen, page = 0) {
133
+ const { width, height } = screen.getSize();
134
+ screen.clear();
135
+ // Title
136
+ const title = '═══ Codeep Help ═══';
137
+ const titleX = Math.floor((width - title.length) / 2);
138
+ screen.write(titleX, 0, title, PRIMARY_COLOR + style.bold);
139
+ // Calculate layout
140
+ const contentStartY = 2;
141
+ const contentEndY = height - 3;
142
+ const availableHeight = contentEndY - contentStartY;
143
+ // Collect all items with categories
144
+ const allItems = [];
145
+ for (const category of helpCategories) {
146
+ // Category header
147
+ allItems.push({ text: '', style: '' });
148
+ allItems.push({ text: ` ${category.title}`, style: fg.yellow + style.bold });
149
+ // Items
150
+ for (const item of category.items) {
151
+ const keyPadded = item.key.padEnd(20);
152
+ allItems.push({
153
+ text: ` ${keyPadded} ${item.description}`,
154
+ style: '',
155
+ });
156
+ }
157
+ }
158
+ // Add keyboard shortcuts section
159
+ allItems.push({ text: '', style: '' });
160
+ allItems.push({ text: ' Keyboard Shortcuts', style: fg.yellow + style.bold });
161
+ for (const shortcut of keyboardShortcuts) {
162
+ const keyPadded = shortcut.key.padEnd(12);
163
+ allItems.push({
164
+ text: ` ${keyPadded} ${shortcut.description}`,
165
+ style: '',
166
+ });
167
+ }
168
+ // Pagination
169
+ const totalPages = Math.ceil(allItems.length / availableHeight);
170
+ const startIndex = page * availableHeight;
171
+ const visibleItems = allItems.slice(startIndex, startIndex + availableHeight);
172
+ // Render items
173
+ for (let i = 0; i < visibleItems.length; i++) {
174
+ const item = visibleItems[i];
175
+ // Highlight command part (starts with /)
176
+ if (item.text.includes('/')) {
177
+ const match = item.text.match(/^(\s*)(\S+)(\s+)(.*)$/);
178
+ if (match) {
179
+ const [, indent, cmd, space, desc] = match;
180
+ screen.write(0, contentStartY + i, indent, '');
181
+ screen.write(indent.length, contentStartY + i, cmd, fg.green);
182
+ screen.write(indent.length + cmd.length, contentStartY + i, space + desc, fg.white);
183
+ continue;
184
+ }
185
+ }
186
+ screen.write(0, contentStartY + i, item.text, item.style || fg.white);
187
+ }
188
+ // Footer
189
+ const footerY = height - 1;
190
+ const pageInfo = totalPages > 1 ? `Page ${page + 1}/${totalPages} | ←→ Navigate | ` : '';
191
+ const footer = `${pageInfo}Esc Close`;
192
+ screen.write(2, footerY, footer, fg.gray);
193
+ screen.showCursor(false);
194
+ screen.fullRender();
195
+ }
@@ -0,0 +1,12 @@
1
+ /**
2
+ * Intro animation component - matches Ink version style
3
+ */
4
+ import { Screen } from '../Screen';
5
+ /**
6
+ * Show intro animation with decrypt effect
7
+ */
8
+ export declare function showIntro(screen: Screen, duration?: number): Promise<void>;
9
+ /**
10
+ * Quick version without animation (for fast startup)
11
+ */
12
+ export declare function showLogoStatic(screen: Screen): void;
@@ -0,0 +1,128 @@
1
+ /**
2
+ * Intro animation component - matches Ink version style
3
+ */
4
+ import { fg, style } from '../ansi.js';
5
+ // ASCII Logo (same as Ink version)
6
+ const LOGO = [
7
+ ' ██████╗ ██████╗ ██████╗ ███████╗███████╗██████╗ ',
8
+ '██╔════╝██╔═══██╗██╔══██╗██╔════╝██╔════╝██╔══██╗',
9
+ '██║ ██║ ██║██║ ██║█████╗ █████╗ ██████╔╝',
10
+ '██║ ██║ ██║██║ ██║██╔══╝ ██╔══╝ ██╔═══╝ ',
11
+ '╚██████╗╚██████╔╝██████╔╝███████╗███████╗██║ ',
12
+ ' ╚═════╝ ╚═════╝ ╚═════╝ ╚══════╝╚══════╝╚═╝ ',
13
+ ];
14
+ const TAGLINE = 'Deep into Code.';
15
+ const GLITCH_CHARS = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ@#$%&*<>?/;:[]=';
16
+ // Primary color: #f02a30 (Codeep red)
17
+ const PRIMARY_COLOR = fg.rgb(240, 42, 48);
18
+ /**
19
+ * Show intro animation with decrypt effect
20
+ */
21
+ export async function showIntro(screen, duration = 1500) {
22
+ const { width, height } = screen.getSize();
23
+ // Calculate center position
24
+ const logoWidth = LOGO[0].length;
25
+ const logoHeight = LOGO.length;
26
+ const startX = Math.floor((width - logoWidth) / 2);
27
+ const startY = Math.floor((height - logoHeight) / 2) - 1;
28
+ screen.showCursor(false);
29
+ // Phase 1: Initial noise (500ms)
30
+ const noiseFrames = 10;
31
+ for (let frame = 0; frame < noiseFrames; frame++) {
32
+ screen.clear();
33
+ for (let i = 0; i < LOGO.length; i++) {
34
+ const noiseLine = generateNoiseLine(LOGO[i]);
35
+ screen.write(startX, startY + i, noiseLine, PRIMARY_COLOR + style.bold);
36
+ }
37
+ screen.fullRender();
38
+ await sleep(50);
39
+ }
40
+ // Phase 2: Decrypt animation (1000ms)
41
+ const decryptDuration = duration - 500;
42
+ const startTime = Date.now();
43
+ while (Date.now() - startTime < decryptDuration) {
44
+ const progress = (Date.now() - startTime) / decryptDuration;
45
+ screen.clear();
46
+ for (let i = 0; i < LOGO.length; i++) {
47
+ const decryptedLine = getDecryptedLine(LOGO[i], progress);
48
+ screen.write(startX, startY + i, decryptedLine, PRIMARY_COLOR + style.bold);
49
+ }
50
+ // Show tagline when mostly decrypted
51
+ if (progress > 0.7) {
52
+ const taglineX = Math.floor((width - TAGLINE.length) / 2);
53
+ screen.write(taglineX, startY + logoHeight + 1, TAGLINE, PRIMARY_COLOR);
54
+ }
55
+ screen.fullRender();
56
+ await sleep(16); // ~60 FPS
57
+ }
58
+ // Phase 3: Final state
59
+ screen.clear();
60
+ for (let i = 0; i < LOGO.length; i++) {
61
+ screen.write(startX, startY + i, LOGO[i], PRIMARY_COLOR + style.bold);
62
+ }
63
+ const taglineX = Math.floor((width - TAGLINE.length) / 2);
64
+ screen.write(taglineX, startY + logoHeight + 1, TAGLINE, PRIMARY_COLOR);
65
+ screen.fullRender();
66
+ await sleep(200);
67
+ }
68
+ /**
69
+ * Generate noise line (random glitch characters)
70
+ */
71
+ function generateNoiseLine(original) {
72
+ let result = '';
73
+ for (const char of original) {
74
+ if (char === ' ' && Math.random() > 0.1) {
75
+ result += ' ';
76
+ }
77
+ else {
78
+ result += GLITCH_CHARS.charAt(Math.floor(Math.random() * GLITCH_CHARS.length));
79
+ }
80
+ }
81
+ return result;
82
+ }
83
+ /**
84
+ * Get partially decrypted line based on progress
85
+ */
86
+ function getDecryptedLine(original, progress) {
87
+ let result = '';
88
+ for (let i = 0; i < original.length; i++) {
89
+ const char = original[i];
90
+ const threshold = original.length > 0 ? i / original.length : 0;
91
+ // Character is decrypted if progress is past its threshold (with some randomness)
92
+ const isDecrypted = progress >= threshold - 0.1 && Math.random() > 0.2;
93
+ if (char === ' ') {
94
+ result += ' ';
95
+ }
96
+ else if (isDecrypted || progress > 0.95) {
97
+ result += char;
98
+ }
99
+ else {
100
+ result += GLITCH_CHARS.charAt(Math.floor(Math.random() * GLITCH_CHARS.length));
101
+ }
102
+ }
103
+ return result;
104
+ }
105
+ /**
106
+ * Simple sleep helper
107
+ */
108
+ function sleep(ms) {
109
+ return new Promise(resolve => setTimeout(resolve, ms));
110
+ }
111
+ /**
112
+ * Quick version without animation (for fast startup)
113
+ */
114
+ export function showLogoStatic(screen) {
115
+ const { width, height } = screen.getSize();
116
+ const logoWidth = LOGO[0].length;
117
+ const logoHeight = LOGO.length;
118
+ const startX = Math.floor((width - logoWidth) / 2);
119
+ const startY = Math.floor((height - logoHeight) / 2) - 1;
120
+ screen.clear();
121
+ for (let i = 0; i < LOGO.length; i++) {
122
+ screen.write(startX, startY + i, LOGO[i], PRIMARY_COLOR + style.bold);
123
+ }
124
+ const taglineX = Math.floor((width - TAGLINE.length) / 2);
125
+ screen.write(taglineX, startY + logoHeight + 1, TAGLINE, PRIMARY_COLOR);
126
+ screen.showCursor(false);
127
+ screen.fullRender();
128
+ }
@@ -0,0 +1,42 @@
1
+ /**
2
+ * Login screen for API key setup
3
+ */
4
+ import { Screen } from '../Screen';
5
+ import { Input, KeyEvent } from '../Input';
6
+ export interface LoginOptions {
7
+ onSubmit: (apiKey: string) => void;
8
+ onCancel: () => void;
9
+ providerName: string;
10
+ error?: string;
11
+ }
12
+ /**
13
+ * Login screen component
14
+ */
15
+ export declare class LoginScreen {
16
+ private screen;
17
+ private input;
18
+ private editor;
19
+ private options;
20
+ private showKey;
21
+ constructor(screen: Screen, input: Input, options: LoginOptions);
22
+ /**
23
+ * Handle key event
24
+ * Returns true if handled, false to pass to parent
25
+ */
26
+ handleKey(event: KeyEvent): boolean;
27
+ /**
28
+ * Render login screen
29
+ */
30
+ render(): void;
31
+ /**
32
+ * Reset state
33
+ */
34
+ reset(): void;
35
+ }
36
+ /**
37
+ * Provider selection screen
38
+ */
39
+ export declare function renderProviderSelect(screen: Screen, providers: Array<{
40
+ id: string;
41
+ name: string;
42
+ }>, selectedIndex: number): void;
@@ -0,0 +1,178 @@
1
+ /**
2
+ * Login screen for API key setup
3
+ */
4
+ import { LineEditor } from '../Input.js';
5
+ import { fg, style } from '../ansi.js';
6
+ import { createBox, centerBox } from './Box.js';
7
+ // Primary color: #f02a30 (Codeep red)
8
+ const PRIMARY_COLOR = fg.rgb(240, 42, 48);
9
+ const PRIMARY_BRIGHT = fg.rgb(255, 80, 85);
10
+ /**
11
+ * Login screen component
12
+ */
13
+ export class LoginScreen {
14
+ screen;
15
+ input;
16
+ editor;
17
+ options;
18
+ showKey = false;
19
+ constructor(screen, input, options) {
20
+ this.screen = screen;
21
+ this.input = input;
22
+ this.editor = new LineEditor();
23
+ this.options = options;
24
+ }
25
+ /**
26
+ * Handle key event
27
+ * Returns true if handled, false to pass to parent
28
+ */
29
+ handleKey(event) {
30
+ // Toggle visibility
31
+ if (event.ctrl && event.key === 't') {
32
+ this.showKey = !this.showKey;
33
+ this.render();
34
+ return true;
35
+ }
36
+ // Submit
37
+ if (event.key === 'enter') {
38
+ const value = this.editor.getValue().trim();
39
+ if (value) {
40
+ this.options.onSubmit(value);
41
+ }
42
+ return true;
43
+ }
44
+ // Cancel
45
+ if (event.key === 'escape') {
46
+ this.options.onCancel();
47
+ return true;
48
+ }
49
+ // Editor keys
50
+ if (this.editor.handleKey(event)) {
51
+ this.render();
52
+ }
53
+ return true;
54
+ }
55
+ /**
56
+ * Render login screen
57
+ */
58
+ render() {
59
+ const { width, height } = this.screen.getSize();
60
+ this.screen.clear();
61
+ // Title
62
+ const title = '═══ Codeep Setup ═══';
63
+ const titleX = Math.floor((width - title.length) / 2);
64
+ this.screen.write(titleX, 1, title, PRIMARY_COLOR + style.bold);
65
+ // Box dimensions
66
+ const boxWidth = Math.min(60, width - 4);
67
+ const boxHeight = 12;
68
+ const { x: boxX, y: boxY } = centerBox(width, height, boxWidth, boxHeight);
69
+ // Draw box
70
+ const boxLines = createBox({
71
+ x: boxX,
72
+ y: boxY,
73
+ width: boxWidth,
74
+ height: boxHeight,
75
+ style: 'rounded',
76
+ title: ` ${this.options.providerName} API Key `,
77
+ borderColor: PRIMARY_COLOR,
78
+ titleColor: PRIMARY_BRIGHT,
79
+ });
80
+ for (const line of boxLines) {
81
+ this.screen.writeLine(line.y, line.text, line.style);
82
+ }
83
+ // Content
84
+ const contentX = boxX + 3;
85
+ let contentY = boxY + 2;
86
+ // Instructions
87
+ this.screen.write(contentX, contentY, 'Enter your API key to get started:', fg.white);
88
+ contentY += 2;
89
+ // Input field
90
+ const inputValue = this.editor.getValue();
91
+ const maxInputWidth = boxWidth - 8;
92
+ let displayValue;
93
+ if (this.showKey) {
94
+ displayValue = inputValue.length > maxInputWidth
95
+ ? '...' + inputValue.slice(-(maxInputWidth - 3))
96
+ : inputValue;
97
+ }
98
+ else {
99
+ // Mask the key
100
+ displayValue = '*'.repeat(Math.min(inputValue.length, maxInputWidth));
101
+ }
102
+ // Input box
103
+ const inputBoxWidth = boxWidth - 6;
104
+ this.screen.write(contentX, contentY, '┌' + '─'.repeat(inputBoxWidth - 2) + '┐', fg.gray);
105
+ contentY++;
106
+ this.screen.write(contentX, contentY, '│ ' + displayValue.padEnd(inputBoxWidth - 4) + ' │', fg.gray);
107
+ const cursorX = contentX + 2 + Math.min(inputValue.length, maxInputWidth);
108
+ contentY++;
109
+ this.screen.write(contentX, contentY, '└' + '─'.repeat(inputBoxWidth - 2) + '┘', fg.gray);
110
+ contentY += 2;
111
+ // Error message
112
+ if (this.options.error) {
113
+ this.screen.write(contentX, contentY, this.options.error, fg.red);
114
+ contentY++;
115
+ }
116
+ // Help text
117
+ this.screen.write(contentX, contentY, 'Ctrl+T: Toggle visibility | Esc: Cancel', fg.gray);
118
+ // Footer
119
+ const footerY = height - 2;
120
+ this.screen.write(2, footerY, 'Get your API key from your provider\'s dashboard', fg.gray);
121
+ // Position cursor
122
+ this.screen.setCursor(cursorX, boxY + 5);
123
+ this.screen.showCursor(true);
124
+ this.screen.fullRender();
125
+ }
126
+ /**
127
+ * Reset state
128
+ */
129
+ reset() {
130
+ this.editor.clear();
131
+ this.showKey = false;
132
+ }
133
+ }
134
+ /**
135
+ * Provider selection screen
136
+ */
137
+ export function renderProviderSelect(screen, providers, selectedIndex) {
138
+ const { width, height } = screen.getSize();
139
+ screen.clear();
140
+ // Title
141
+ const title = '═══ Codeep Setup ═══';
142
+ const titleX = Math.floor((width - title.length) / 2);
143
+ screen.write(titleX, 1, title, PRIMARY_COLOR + style.bold);
144
+ // Subtitle
145
+ const subtitle = 'Select your AI provider';
146
+ const subtitleX = Math.floor((width - subtitle.length) / 2);
147
+ screen.write(subtitleX, 3, subtitle, fg.white);
148
+ // Box
149
+ const boxWidth = Math.min(40, width - 4);
150
+ const boxHeight = providers.length + 4;
151
+ const { x: boxX, y: boxY } = centerBox(width, height, boxWidth, boxHeight);
152
+ const boxLines = createBox({
153
+ x: boxX,
154
+ y: boxY,
155
+ width: boxWidth,
156
+ height: boxHeight,
157
+ style: 'rounded',
158
+ borderColor: PRIMARY_COLOR,
159
+ });
160
+ for (const line of boxLines) {
161
+ screen.writeLine(line.y, line.text, line.style);
162
+ }
163
+ // Provider list
164
+ const contentX = boxX + 3;
165
+ let contentY = boxY + 2;
166
+ for (let i = 0; i < providers.length; i++) {
167
+ const provider = providers[i];
168
+ const isSelected = i === selectedIndex;
169
+ const prefix = isSelected ? '► ' : ' ';
170
+ const itemStyle = isSelected ? PRIMARY_BRIGHT + style.bold : fg.white;
171
+ screen.write(contentX, contentY + i, prefix + provider.name, itemStyle);
172
+ }
173
+ // Footer
174
+ const footerY = height - 2;
175
+ screen.write(2, footerY, '↑↓ Navigate | Enter Select', fg.gray);
176
+ screen.showCursor(false);
177
+ screen.fullRender();
178
+ }
@@ -0,0 +1,43 @@
1
+ /**
2
+ * Modal overlay component
3
+ * Renders a box with content on top of existing screen
4
+ */
5
+ import { Screen } from '../Screen';
6
+ import { BoxStyle } from './Box';
7
+ export interface ModalOptions {
8
+ title: string;
9
+ content: string[];
10
+ width?: number;
11
+ height?: number;
12
+ boxStyle?: BoxStyle;
13
+ borderColor?: string;
14
+ titleColor?: string;
15
+ contentColor?: string;
16
+ centered?: boolean;
17
+ x?: number;
18
+ y?: number;
19
+ }
20
+ export interface ModalAction {
21
+ key: string;
22
+ label: string;
23
+ action: () => void;
24
+ }
25
+ /**
26
+ * Render a modal on the screen
27
+ */
28
+ export declare function renderModal(screen: Screen, options: ModalOptions): void;
29
+ /**
30
+ * Render a help/info modal with key bindings
31
+ */
32
+ export declare function renderHelpModal(screen: Screen, title: string, items: Array<{
33
+ key: string;
34
+ description: string;
35
+ }>, footer?: string): void;
36
+ /**
37
+ * Render a confirmation modal with Yes/No options
38
+ */
39
+ export declare function renderConfirmModal(screen: Screen, title: string, message: string[], selectedOption: 'yes' | 'no', confirmLabel?: string, cancelLabel?: string): void;
40
+ /**
41
+ * Render a list selection modal
42
+ */
43
+ export declare function renderListModal(screen: Screen, title: string, items: string[], selectedIndex: number, footer?: string): void;