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.
- package/bin/codeep.js +1 -1
- package/dist/config/index.js +10 -10
- package/dist/renderer/App.d.ts +430 -0
- package/dist/renderer/App.js +2712 -0
- package/dist/renderer/ChatUI.d.ts +71 -0
- package/dist/renderer/ChatUI.js +286 -0
- package/dist/renderer/Input.d.ts +72 -0
- package/dist/renderer/Input.js +371 -0
- package/dist/renderer/Screen.d.ts +79 -0
- package/dist/renderer/Screen.js +278 -0
- package/dist/renderer/ansi.d.ts +99 -0
- package/dist/renderer/ansi.js +176 -0
- package/dist/renderer/components/Box.d.ts +64 -0
- package/dist/renderer/components/Box.js +90 -0
- package/dist/renderer/components/Help.d.ts +30 -0
- package/dist/renderer/components/Help.js +195 -0
- package/dist/renderer/components/Intro.d.ts +12 -0
- package/dist/renderer/components/Intro.js +128 -0
- package/dist/renderer/components/Login.d.ts +42 -0
- package/dist/renderer/components/Login.js +178 -0
- package/dist/renderer/components/Modal.d.ts +43 -0
- package/dist/renderer/components/Modal.js +207 -0
- package/dist/renderer/components/Permission.d.ts +20 -0
- package/dist/renderer/components/Permission.js +113 -0
- package/dist/renderer/components/SelectScreen.d.ts +26 -0
- package/dist/renderer/components/SelectScreen.js +101 -0
- package/dist/renderer/components/Settings.d.ts +37 -0
- package/dist/renderer/components/Settings.js +333 -0
- package/dist/renderer/components/Status.d.ts +18 -0
- package/dist/renderer/components/Status.js +78 -0
- package/dist/renderer/demo-app.d.ts +6 -0
- package/dist/renderer/demo-app.js +85 -0
- package/dist/renderer/demo.d.ts +6 -0
- package/dist/renderer/demo.js +52 -0
- package/dist/renderer/index.d.ts +16 -0
- package/dist/renderer/index.js +17 -0
- package/dist/renderer/main.d.ts +6 -0
- package/dist/renderer/main.js +1634 -0
- package/dist/utils/agent.d.ts +21 -0
- package/dist/utils/agent.js +29 -0
- package/dist/utils/clipboard.d.ts +15 -0
- package/dist/utils/clipboard.js +95 -0
- package/package.json +7 -11
- package/dist/utils/console.d.ts +0 -55
- 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;
|