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,333 @@
1
+ /**
2
+ * Settings screen component
3
+ */
4
+ import { fg, style } from '../ansi.js';
5
+ import { config } from '../../config/index.js';
6
+ import { updateRateLimits } from '../../utils/ratelimit.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
+ export const SETTINGS = [
11
+ {
12
+ key: 'temperature',
13
+ label: 'Temperature',
14
+ getValue: () => config.get('temperature'),
15
+ type: 'number',
16
+ min: 0,
17
+ max: 2,
18
+ step: 0.1,
19
+ },
20
+ {
21
+ key: 'maxTokens',
22
+ label: 'Max Tokens',
23
+ getValue: () => config.get('maxTokens'),
24
+ type: 'number',
25
+ min: 256,
26
+ max: 32768,
27
+ step: 256,
28
+ },
29
+ {
30
+ key: 'apiTimeout',
31
+ label: 'API Timeout (ms)',
32
+ getValue: () => config.get('apiTimeout'),
33
+ type: 'number',
34
+ min: 5000,
35
+ max: 300000,
36
+ step: 5000,
37
+ },
38
+ {
39
+ key: 'rateLimitApi',
40
+ label: 'API Rate Limit (/min)',
41
+ getValue: () => config.get('rateLimitApi'),
42
+ type: 'number',
43
+ min: 1,
44
+ max: 300,
45
+ step: 5,
46
+ },
47
+ {
48
+ key: 'rateLimitCommands',
49
+ label: 'Command Rate Limit (/min)',
50
+ getValue: () => config.get('rateLimitCommands'),
51
+ type: 'number',
52
+ min: 10,
53
+ max: 1000,
54
+ step: 10,
55
+ },
56
+ {
57
+ key: 'autoSave',
58
+ label: 'Auto Save Sessions',
59
+ getValue: () => config.get('autoSave'),
60
+ type: 'select',
61
+ options: [
62
+ { value: true, label: 'On' },
63
+ { value: false, label: 'Off' },
64
+ ],
65
+ },
66
+ {
67
+ key: 'agentMode',
68
+ label: 'Agent Mode',
69
+ getValue: () => config.get('agentMode'),
70
+ type: 'select',
71
+ options: [
72
+ { value: 'on', label: 'ON' },
73
+ { value: 'manual', label: 'Manual' },
74
+ { value: 'off', label: 'OFF' },
75
+ ],
76
+ },
77
+ {
78
+ key: 'agentConfirmation',
79
+ label: 'Agent Confirmation',
80
+ getValue: () => config.get('agentConfirmation') || 'dangerous',
81
+ type: 'select',
82
+ options: [
83
+ { value: 'never', label: 'Never' },
84
+ { value: 'dangerous', label: 'Dangerous' },
85
+ { value: 'always', label: 'Always' },
86
+ ],
87
+ },
88
+ {
89
+ key: 'agentApiTimeout',
90
+ label: 'Agent API Timeout (ms)',
91
+ getValue: () => config.get('agentApiTimeout'),
92
+ type: 'number',
93
+ min: 30000,
94
+ max: 300000,
95
+ step: 10000,
96
+ },
97
+ {
98
+ key: 'agentMaxDuration',
99
+ label: 'Agent Max Duration (min)',
100
+ getValue: () => config.get('agentMaxDuration'),
101
+ type: 'number',
102
+ min: 5,
103
+ max: 60,
104
+ step: 5,
105
+ },
106
+ {
107
+ key: 'agentMaxIterations',
108
+ label: 'Agent Max Iterations',
109
+ getValue: () => config.get('agentMaxIterations'),
110
+ type: 'number',
111
+ min: 10,
112
+ max: 200,
113
+ step: 10,
114
+ },
115
+ {
116
+ key: 'agentAutoVerify',
117
+ label: 'Agent Auto-Verify',
118
+ getValue: () => config.get('agentAutoVerify') !== false,
119
+ type: 'select',
120
+ options: [
121
+ { value: true, label: 'On' },
122
+ { value: false, label: 'Off' },
123
+ ],
124
+ },
125
+ {
126
+ key: 'agentMaxFixAttempts',
127
+ label: 'Agent Max Fix Attempts',
128
+ getValue: () => config.get('agentMaxFixAttempts') || 3,
129
+ type: 'number',
130
+ min: 0,
131
+ max: 10,
132
+ step: 1,
133
+ },
134
+ {
135
+ key: 'agentInteractive',
136
+ label: 'Agent Interactive Mode',
137
+ getValue: () => config.get('agentInteractive') !== false,
138
+ type: 'select',
139
+ options: [
140
+ { value: true, label: 'On' },
141
+ { value: false, label: 'Off' },
142
+ ],
143
+ },
144
+ ];
145
+ /**
146
+ * Format value for display
147
+ */
148
+ function formatValue(setting) {
149
+ const value = setting.getValue();
150
+ if (setting.type === 'select' && setting.options) {
151
+ const option = setting.options.find(o => o.value === value);
152
+ return option ? option.label : String(value);
153
+ }
154
+ return String(value);
155
+ }
156
+ /**
157
+ * Render settings screen
158
+ */
159
+ export function renderSettingsScreen(screen, state, hasWriteAccess, hasProjectContext) {
160
+ const { width, height } = screen.getSize();
161
+ screen.clear();
162
+ // Title
163
+ const title = '═══ Settings ═══';
164
+ const titleX = Math.floor((width - title.length) / 2);
165
+ screen.write(titleX, 0, title, PRIMARY_COLOR + style.bold);
166
+ // Settings list
167
+ const startY = 2;
168
+ const maxVisible = height - 7;
169
+ const scrollOffset = Math.max(0, state.selectedIndex - maxVisible + 3);
170
+ for (let i = 0; i < SETTINGS.length && i < maxVisible; i++) {
171
+ const settingIdx = i + scrollOffset;
172
+ if (settingIdx >= SETTINGS.length)
173
+ break;
174
+ const setting = SETTINGS[settingIdx];
175
+ const isSelected = settingIdx === state.selectedIndex;
176
+ const y = startY + i;
177
+ // Prefix
178
+ const prefix = isSelected ? '► ' : ' ';
179
+ screen.write(2, y, prefix, isSelected ? PRIMARY_COLOR : '');
180
+ // Label
181
+ const labelColor = isSelected ? PRIMARY_BRIGHT : fg.white;
182
+ screen.write(4, y, setting.label + ':', labelColor);
183
+ // Value
184
+ const valueX = 30;
185
+ if (state.editing && isSelected) {
186
+ screen.write(valueX, y, state.editValue + '█', fg.cyan);
187
+ }
188
+ else {
189
+ screen.write(valueX, y, formatValue(setting), fg.green);
190
+ }
191
+ // Hint
192
+ if (isSelected && !state.editing) {
193
+ const hintX = valueX + formatValue(setting).length + 2;
194
+ if (setting.type === 'number') {
195
+ screen.write(hintX, y, '(←/→ adjust, Enter edit)', fg.gray);
196
+ }
197
+ else {
198
+ screen.write(hintX, y, '(←/→ or Enter toggle)', fg.gray);
199
+ }
200
+ }
201
+ }
202
+ // Agent status message
203
+ const agentMode = config.get('agentMode');
204
+ const statusY = height - 4;
205
+ let statusMessage;
206
+ let statusColor;
207
+ if (agentMode === 'on') {
208
+ if (!hasWriteAccess || !hasProjectContext) {
209
+ statusMessage = '⚠️ Agent needs permission - use /grant';
210
+ statusColor = fg.yellow;
211
+ }
212
+ else {
213
+ statusMessage = '✓ Agent will run automatically';
214
+ statusColor = fg.green;
215
+ }
216
+ }
217
+ else if (agentMode === 'manual') {
218
+ statusMessage = 'ℹ️ Manual mode - use /agent <task>';
219
+ statusColor = fg.gray;
220
+ }
221
+ else {
222
+ statusMessage = 'ℹ️ Agent disabled';
223
+ statusColor = fg.gray;
224
+ }
225
+ screen.write(2, statusY, statusMessage, statusColor);
226
+ // Footer
227
+ const footerY = height - 1;
228
+ screen.write(2, footerY, '↑/↓ Navigate | ←/→ Adjust | Enter Edit | Esc Close', fg.gray);
229
+ screen.showCursor(state.editing);
230
+ screen.fullRender();
231
+ }
232
+ /**
233
+ * Handle settings key
234
+ * Returns: { handled: boolean, close: boolean, notify?: string }
235
+ */
236
+ export function handleSettingsKey(key, ctrl, state) {
237
+ const newState = { ...state };
238
+ // Escape
239
+ if (key === 'escape') {
240
+ if (state.editing) {
241
+ newState.editing = false;
242
+ newState.editValue = '';
243
+ return { handled: true, close: false, newState };
244
+ }
245
+ return { handled: true, close: true, newState };
246
+ }
247
+ if (state.editing) {
248
+ // Editing mode
249
+ if (key === 'enter') {
250
+ const setting = SETTINGS[state.selectedIndex];
251
+ if (setting.type === 'number') {
252
+ const num = parseFloat(state.editValue);
253
+ if (!isNaN(num)) {
254
+ const clamped = Math.max(setting.min || 0, Math.min(setting.max || Infinity, num));
255
+ config.set(setting.key, clamped);
256
+ if (setting.key === 'rateLimitApi' || setting.key === 'rateLimitCommands') {
257
+ updateRateLimits();
258
+ }
259
+ newState.editing = false;
260
+ newState.editValue = '';
261
+ return { handled: true, close: false, notify: `${setting.label}: ${clamped}`, newState };
262
+ }
263
+ }
264
+ newState.editing = false;
265
+ newState.editValue = '';
266
+ return { handled: true, close: false, newState };
267
+ }
268
+ if (key === 'backspace') {
269
+ newState.editValue = state.editValue.slice(0, -1);
270
+ return { handled: true, close: false, newState };
271
+ }
272
+ if (/^[0-9.]$/.test(key)) {
273
+ newState.editValue = state.editValue + key;
274
+ return { handled: true, close: false, newState };
275
+ }
276
+ return { handled: true, close: false, newState };
277
+ }
278
+ // Navigation mode
279
+ if (key === 'up') {
280
+ newState.selectedIndex = Math.max(0, state.selectedIndex - 1);
281
+ return { handled: true, close: false, newState };
282
+ }
283
+ if (key === 'down') {
284
+ newState.selectedIndex = Math.min(SETTINGS.length - 1, state.selectedIndex + 1);
285
+ return { handled: true, close: false, newState };
286
+ }
287
+ if (key === 'left' || key === 'right') {
288
+ const setting = SETTINGS[state.selectedIndex];
289
+ if (setting.type === 'number') {
290
+ const current = setting.getValue();
291
+ const step = setting.step || 1;
292
+ const delta = key === 'left' ? -step : step;
293
+ const newValue = Math.max(setting.min || 0, Math.min(setting.max || Infinity, current + delta));
294
+ config.set(setting.key, newValue);
295
+ if (setting.key === 'rateLimitApi' || setting.key === 'rateLimitCommands') {
296
+ updateRateLimits();
297
+ }
298
+ return { handled: true, close: false, newState };
299
+ }
300
+ if (setting.type === 'select' && setting.options) {
301
+ const current = config.get(setting.key);
302
+ const currentIdx = setting.options.findIndex(o => o.value === current);
303
+ const newIdx = key === 'left'
304
+ ? (currentIdx - 1 + setting.options.length) % setting.options.length
305
+ : (currentIdx + 1) % setting.options.length;
306
+ config.set(setting.key, setting.options[newIdx].value);
307
+ return { handled: true, close: false, newState };
308
+ }
309
+ return { handled: true, close: false, newState };
310
+ }
311
+ if (key === 'enter') {
312
+ const setting = SETTINGS[state.selectedIndex];
313
+ if (setting.type === 'number') {
314
+ newState.editing = true;
315
+ newState.editValue = String(setting.getValue());
316
+ return { handled: true, close: false, newState };
317
+ }
318
+ if (setting.type === 'select' && setting.options) {
319
+ const current = config.get(setting.key);
320
+ const currentIdx = setting.options.findIndex(o => o.value === current);
321
+ const newIdx = (currentIdx + 1) % setting.options.length;
322
+ config.set(setting.key, setting.options[newIdx].value);
323
+ return {
324
+ handled: true,
325
+ close: false,
326
+ notify: `${setting.label}: ${setting.options[newIdx].label}`,
327
+ newState
328
+ };
329
+ }
330
+ return { handled: true, close: false, newState };
331
+ }
332
+ return { handled: false, close: false, newState };
333
+ }
@@ -0,0 +1,18 @@
1
+ /**
2
+ * Status screen component
3
+ */
4
+ import { Screen } from '../Screen';
5
+ export interface StatusInfo {
6
+ version: string;
7
+ provider: string;
8
+ model: string;
9
+ agentMode: string;
10
+ projectPath: string;
11
+ hasWriteAccess: boolean;
12
+ sessionId: string;
13
+ messageCount: number;
14
+ }
15
+ /**
16
+ * Render status screen
17
+ */
18
+ export declare function renderStatusScreen(screen: Screen, status: StatusInfo): void;
@@ -0,0 +1,78 @@
1
+ /**
2
+ * Status 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
+ * Render status screen
9
+ */
10
+ export function renderStatusScreen(screen, status) {
11
+ const { width, height } = screen.getSize();
12
+ screen.clear();
13
+ // Title
14
+ const title = '═══ Codeep Status ═══';
15
+ const titleX = Math.floor((width - title.length) / 2);
16
+ screen.write(titleX, 0, title, PRIMARY_COLOR + style.bold);
17
+ // Status items
18
+ const items = [
19
+ { label: 'Version', value: `v${status.version}` },
20
+ { label: 'Provider', value: status.provider },
21
+ { label: 'Model', value: status.model },
22
+ { label: 'Agent Mode', value: status.agentMode.toUpperCase(), color: status.agentMode === 'on' ? fg.green : status.agentMode === 'manual' ? fg.yellow : fg.gray },
23
+ { label: 'Project', value: truncatePath(status.projectPath, 40) },
24
+ { label: 'Write Access', value: status.hasWriteAccess ? 'Yes' : 'No', color: status.hasWriteAccess ? fg.green : fg.red },
25
+ { label: 'Session', value: status.sessionId || 'New' },
26
+ { label: 'Messages', value: status.messageCount.toString() },
27
+ ];
28
+ // Calculate layout
29
+ const labelWidth = Math.max(...items.map(i => i.label.length)) + 2;
30
+ const contentStartY = 3;
31
+ // Render items
32
+ for (let i = 0; i < items.length; i++) {
33
+ const item = items[i];
34
+ const y = contentStartY + i;
35
+ // Label
36
+ screen.write(4, y, item.label + ':', fg.gray);
37
+ // Value
38
+ const valueColor = item.color || fg.white;
39
+ screen.write(4 + labelWidth, y, item.value, valueColor);
40
+ }
41
+ // System info
42
+ const sysInfoY = contentStartY + items.length + 2;
43
+ screen.write(4, sysInfoY, 'System', fg.yellow + style.bold);
44
+ const sysItems = [
45
+ { label: 'Platform', value: process.platform },
46
+ { label: 'Node', value: process.version },
47
+ { label: 'Terminal', value: `${width}x${height}` },
48
+ ];
49
+ for (let i = 0; i < sysItems.length; i++) {
50
+ const item = sysItems[i];
51
+ const y = sysInfoY + 1 + i;
52
+ screen.write(4, y, item.label + ':', fg.gray);
53
+ screen.write(4 + labelWidth, y, item.value, fg.white);
54
+ }
55
+ // Footer
56
+ const footerY = height - 1;
57
+ screen.write(2, footerY, 'Press Esc to close', fg.gray);
58
+ screen.showCursor(false);
59
+ screen.fullRender();
60
+ }
61
+ /**
62
+ * Truncate path for display
63
+ */
64
+ function truncatePath(path, maxLen) {
65
+ if (path.length <= maxLen)
66
+ return path;
67
+ // Try to keep the end of the path
68
+ const parts = path.split('/');
69
+ let result = parts[parts.length - 1];
70
+ for (let i = parts.length - 2; i >= 0; i--) {
71
+ const newResult = parts[i] + '/' + result;
72
+ if (newResult.length + 3 > maxLen) {
73
+ return '.../' + result;
74
+ }
75
+ result = newResult;
76
+ }
77
+ return result;
78
+ }
@@ -0,0 +1,6 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Demo for full App with modals
4
+ * Run with: npm run demo:app
5
+ */
6
+ export {};
@@ -0,0 +1,85 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Demo for full App with modals
4
+ * Run with: npm run demo:app
5
+ */
6
+ import { App } from './App.js';
7
+ // Simulate API response
8
+ function simulateResponse(app, text) {
9
+ return new Promise((resolve) => {
10
+ app.startStreaming();
11
+ let index = 0;
12
+ const words = text.split(' ');
13
+ const interval = setInterval(() => {
14
+ if (index >= words.length) {
15
+ clearInterval(interval);
16
+ app.endStreaming();
17
+ resolve();
18
+ return;
19
+ }
20
+ app.addStreamChunk((index > 0 ? ' ' : '') + words[index]);
21
+ index++;
22
+ }, 30);
23
+ });
24
+ }
25
+ // Mock status
26
+ function getStatus() {
27
+ return {
28
+ version: '1.1.12',
29
+ provider: 'OpenAI',
30
+ model: 'gpt-4o',
31
+ agentMode: 'on',
32
+ projectPath: process.cwd(),
33
+ hasWriteAccess: true,
34
+ sessionId: 'demo-session',
35
+ messageCount: 0,
36
+ };
37
+ }
38
+ // Main
39
+ async function main() {
40
+ const app = new App({
41
+ onSubmit: async (message) => {
42
+ // Simulate AI response
43
+ const responses = {
44
+ 'hello': 'Hello! I\'m Codeep, your AI coding assistant. How can I help you today?',
45
+ 'hi': 'Hi there! What would you like to work on?',
46
+ 'test': 'This is the full App demo with:\n\n• Help screen (/help)\n• Status screen (/status)\n• Modal overlays\n• Streaming responses\n• All keyboard shortcuts\n\nThe custom renderer is working perfectly!',
47
+ };
48
+ const response = responses[message.toLowerCase()] ||
49
+ `You said: "${message}"\n\nI'm a demo response. In the real app, this would be an AI-generated response based on your project context.`;
50
+ await simulateResponse(app, response);
51
+ },
52
+ onCommand: (command, args) => {
53
+ // Handle commands not built into App
54
+ switch (command) {
55
+ case 'version':
56
+ app.notify('Codeep v1.1.12 • OpenAI • gpt-4o');
57
+ break;
58
+ case 'provider':
59
+ app.showList('Select Provider', ['OpenAI', 'Anthropic', 'Google', 'Local'], (index) => {
60
+ app.notify(`Selected: ${['OpenAI', 'Anthropic', 'Google', 'Local'][index]}`);
61
+ });
62
+ break;
63
+ case 'model':
64
+ app.showList('Select Model', ['gpt-4o', 'gpt-4o-mini', 'gpt-3.5-turbo', 'o1-preview'], (index) => {
65
+ app.notify(`Selected model: ${['gpt-4o', 'gpt-4o-mini', 'gpt-3.5-turbo', 'o1-preview'][index]}`);
66
+ });
67
+ break;
68
+ default:
69
+ app.notify(`Unknown command: /${command}`);
70
+ }
71
+ },
72
+ onExit: () => {
73
+ console.log('\nGoodbye!');
74
+ process.exit(0);
75
+ },
76
+ getStatus,
77
+ });
78
+ // Welcome message
79
+ app.addMessage({
80
+ role: 'system',
81
+ content: 'Welcome to Codeep App Demo!\n\nTry these commands:\n• /help - Show help\n• /status - Show status\n• /provider - Select provider\n• /model - Select model\n• /clear - Clear chat\n• /exit - Quit',
82
+ });
83
+ app.start();
84
+ }
85
+ main().catch(console.error);
@@ -0,0 +1,6 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Demo/Test for custom renderer
4
+ * Run with: npx ts-node src/renderer/demo.ts
5
+ */
6
+ export {};
@@ -0,0 +1,52 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Demo/Test for custom renderer
4
+ * Run with: npx ts-node src/renderer/demo.ts
5
+ */
6
+ import { ChatUI } from './ChatUI.js';
7
+ // Simulate streaming response
8
+ function simulateStreaming(ui, text) {
9
+ return new Promise((resolve) => {
10
+ ui.startStreaming();
11
+ let index = 0;
12
+ const words = text.split(' ');
13
+ const interval = setInterval(() => {
14
+ if (index >= words.length) {
15
+ clearInterval(interval);
16
+ ui.endStreaming();
17
+ resolve();
18
+ return;
19
+ }
20
+ ui.addStreamChunk((index > 0 ? ' ' : '') + words[index]);
21
+ index++;
22
+ }, 50); // 50ms per word
23
+ });
24
+ }
25
+ // Main
26
+ async function main() {
27
+ const ui = new ChatUI({
28
+ onSubmit: async (message) => {
29
+ // Simulate AI response
30
+ const responses = {
31
+ 'hello': 'Hello! How can I help you today?',
32
+ 'hi': 'Hi there! What would you like to do?',
33
+ 'help': 'Available commands:\n- Type any message to chat\n- Ctrl+L to clear\n- Ctrl+C to exit\n- Page Up/Down to scroll',
34
+ 'test': 'This is a test response. The custom renderer is working correctly without Ink!\n\nIt supports:\n- Multi-line messages\n- Word wrapping for long lines\n- Streaming responses\n- Scroll history\n- Cursor-based input editing',
35
+ };
36
+ const response = responses[message.toLowerCase()] ||
37
+ `You said: "${message}"\n\nThis is a simulated response from the custom renderer. No Ink involved - just pure ANSI escape codes and a virtual screen buffer with diff-based rendering.`;
38
+ await simulateStreaming(ui, response);
39
+ },
40
+ onExit: () => {
41
+ console.log('\nGoodbye!');
42
+ process.exit(0);
43
+ },
44
+ });
45
+ // Add welcome message
46
+ ui.addMessage({
47
+ role: 'system',
48
+ content: 'Welcome to Codeep Custom Renderer Demo!\nType "help" for commands, or just start chatting.',
49
+ });
50
+ ui.start();
51
+ }
52
+ main().catch(console.error);
@@ -0,0 +1,16 @@
1
+ /**
2
+ * Custom Terminal Renderer
3
+ *
4
+ * A lightweight alternative to Ink for terminal UIs.
5
+ * Uses direct ANSI escape codes and a virtual screen buffer
6
+ * with diff-based rendering for flicker-free updates.
7
+ */
8
+ export { cursor, screen, fg, bg, style, styled, stripAnsi, visibleLength, truncate, wordWrap } from './ansi';
9
+ export { Screen, Cell } from './Screen';
10
+ export { Input, LineEditor, KeyEvent, KeyHandler } from './Input';
11
+ export { ChatUI, ChatMessage, ChatUIOptions } from './ChatUI';
12
+ export { App, AppScreen, AppOptions, Message } from './App';
13
+ export { createBox, centerBox, BoxStyle, BoxOptions } from './components/Box';
14
+ export { renderModal, renderHelpModal, renderListModal, ModalOptions } from './components/Modal';
15
+ export { renderHelpScreen, helpCategories, keyboardShortcuts } from './components/Help';
16
+ export { renderStatusScreen, StatusInfo } from './components/Status';
@@ -0,0 +1,17 @@
1
+ /**
2
+ * Custom Terminal Renderer
3
+ *
4
+ * A lightweight alternative to Ink for terminal UIs.
5
+ * Uses direct ANSI escape codes and a virtual screen buffer
6
+ * with diff-based rendering for flicker-free updates.
7
+ */
8
+ export { cursor, screen, fg, bg, style, styled, stripAnsi, visibleLength, truncate, wordWrap } from './ansi.js';
9
+ export { Screen } from './Screen.js';
10
+ export { Input, LineEditor } from './Input.js';
11
+ export { ChatUI } from './ChatUI.js';
12
+ export { App } from './App.js';
13
+ // Components
14
+ export { createBox, centerBox } from './components/Box.js';
15
+ export { renderModal, renderHelpModal, renderListModal } from './components/Modal.js';
16
+ export { renderHelpScreen, helpCategories, keyboardShortcuts } from './components/Help.js';
17
+ export { renderStatusScreen } from './components/Status.js';
@@ -0,0 +1,6 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Codeep with Custom Renderer
4
+ * Main entry point using the new ANSI-based renderer instead of Ink
5
+ */
6
+ export {};