codeep 2.9.0 → 2.11.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.
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * Help screen component
3
3
  */
4
- import { fg, style } from '../ansi.js';
4
+ import { fg } from '../ansi.js';
5
5
  // Primary color: #f02a30 (Codeep red)
6
6
  const PRIMARY_COLOR = fg.rgb(240, 42, 48);
7
7
  /**
@@ -204,70 +204,3 @@ export function getHelpTotalPages(screenHeight) {
204
204
  itemCount += keyboardShortcuts.length;
205
205
  return Math.max(1, Math.ceil(itemCount / availableHeight));
206
206
  }
207
- /**
208
- * Render full help screen
209
- */
210
- export function renderHelpScreen(screen, page = 0) {
211
- const { width, height } = screen.getSize();
212
- screen.clear();
213
- // Title
214
- const title = '═══ Codeep Help ═══';
215
- const titleX = Math.floor((width - title.length) / 2);
216
- screen.write(titleX, 0, title, PRIMARY_COLOR + style.bold);
217
- // Calculate layout
218
- const contentStartY = 2;
219
- const contentEndY = height - 3;
220
- const availableHeight = contentEndY - contentStartY;
221
- // Collect all items with categories
222
- const allItems = [];
223
- for (const category of helpCategories) {
224
- // Category header
225
- allItems.push({ text: '', style: '' });
226
- allItems.push({ text: ` ${category.title}`, style: fg.yellow + style.bold });
227
- // Items
228
- for (const item of category.items) {
229
- const keyPadded = item.key.padEnd(20);
230
- allItems.push({
231
- text: ` ${keyPadded} ${item.description}`,
232
- style: '',
233
- });
234
- }
235
- }
236
- // Add keyboard shortcuts section
237
- allItems.push({ text: '', style: '' });
238
- allItems.push({ text: ' Keyboard Shortcuts', style: fg.yellow + style.bold });
239
- for (const shortcut of keyboardShortcuts) {
240
- const keyPadded = shortcut.key.padEnd(12);
241
- allItems.push({
242
- text: ` ${keyPadded} ${shortcut.description}`,
243
- style: '',
244
- });
245
- }
246
- // Pagination
247
- const totalPages = Math.ceil(allItems.length / availableHeight);
248
- const startIndex = page * availableHeight;
249
- const visibleItems = allItems.slice(startIndex, startIndex + availableHeight);
250
- // Render items
251
- for (let i = 0; i < visibleItems.length; i++) {
252
- const item = visibleItems[i];
253
- // Highlight command part (starts with /)
254
- if (item.text.includes('/')) {
255
- const match = item.text.match(/^(\s*)(\S+)(\s+)(.*)$/);
256
- if (match) {
257
- const [, indent, cmd, space, desc] = match;
258
- screen.write(0, contentStartY + i, indent, '');
259
- screen.write(indent.length, contentStartY + i, cmd, fg.green);
260
- screen.write(indent.length + cmd.length, contentStartY + i, space + desc, fg.white);
261
- continue;
262
- }
263
- }
264
- screen.write(0, contentStartY + i, item.text, item.style || fg.white);
265
- }
266
- // Footer
267
- const footerY = height - 1;
268
- const pageInfo = totalPages > 1 ? `Page ${page + 1}/${totalPages} | ←→ Navigate | ` : '';
269
- const footer = `${pageInfo}Esc Close`;
270
- screen.write(2, footerY, footer, fg.gray);
271
- screen.showCursor(false);
272
- screen.fullRender();
273
- }
@@ -1,7 +1,6 @@
1
1
  /**
2
2
  * Settings screen component
3
3
  */
4
- import { Screen } from '../Screen';
5
4
  export interface SettingItem {
6
5
  key: string;
7
6
  label: string;
@@ -21,10 +20,6 @@ export interface SettingsState {
21
20
  editing: boolean;
22
21
  editValue: string;
23
22
  }
24
- /**
25
- * Render settings screen
26
- */
27
- export declare function renderSettingsScreen(screen: Screen, state: SettingsState, hasWriteAccess: boolean, hasProjectContext: boolean): void;
28
23
  /**
29
24
  * Handle settings key
30
25
  * Returns: { handled: boolean, close: boolean, notify?: string }
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * Settings screen component
3
3
  */
4
- import { fg, style } from '../ansi.js';
4
+ import { fg } from '../ansi.js';
5
5
  import { config } from '../../config/index.js';
6
6
  import { updateRateLimits } from '../../utils/ratelimit.js';
7
7
  // Primary color: #f02a30 (Codeep red)
@@ -272,85 +272,6 @@ function formatValue(setting) {
272
272
  }
273
273
  return String(value);
274
274
  }
275
- /**
276
- * Render settings screen
277
- */
278
- export function renderSettingsScreen(screen, state, hasWriteAccess, hasProjectContext) {
279
- const { width, height } = screen.getSize();
280
- screen.clear();
281
- // Title
282
- const title = '═══ Settings ═══';
283
- const titleX = Math.floor((width - title.length) / 2);
284
- screen.write(titleX, 0, title, PRIMARY_COLOR + style.bold);
285
- // Settings list
286
- const startY = 2;
287
- const maxVisible = height - 7;
288
- const scrollOffset = Math.max(0, state.selectedIndex - maxVisible + 3);
289
- for (let i = 0; i < SETTINGS.length && i < maxVisible; i++) {
290
- const settingIdx = i + scrollOffset;
291
- if (settingIdx >= SETTINGS.length)
292
- break;
293
- const setting = SETTINGS[settingIdx];
294
- const isSelected = settingIdx === state.selectedIndex;
295
- const y = startY + i;
296
- // Prefix
297
- const prefix = isSelected ? '► ' : ' ';
298
- screen.write(2, y, prefix, isSelected ? PRIMARY_COLOR : '');
299
- // Label
300
- const labelColor = isSelected ? PRIMARY_BRIGHT : fg.white;
301
- screen.write(4, y, setting.label + ':', labelColor);
302
- // Value
303
- const valueX = 30;
304
- if (state.editing && isSelected) {
305
- screen.write(valueX, y, state.editValue + '█', fg.cyan);
306
- }
307
- else {
308
- screen.write(valueX, y, formatValue(setting), fg.green);
309
- }
310
- // Hint
311
- if (isSelected && !state.editing) {
312
- const hintX = valueX + formatValue(setting).length + 2;
313
- if (setting.type === 'number') {
314
- screen.write(hintX, y, '(←/→ adjust, Enter edit)', fg.gray);
315
- }
316
- else if (setting.type === 'text') {
317
- screen.write(hintX, y, '(Enter to edit)', fg.gray);
318
- }
319
- else {
320
- screen.write(hintX, y, '(←/→ or Enter toggle)', fg.gray);
321
- }
322
- }
323
- }
324
- // Agent status message
325
- const agentMode = config.get('agentMode');
326
- const statusY = height - 4;
327
- let statusMessage;
328
- let statusColor;
329
- if (agentMode === 'on') {
330
- if (!hasWriteAccess || !hasProjectContext) {
331
- statusMessage = '⚠️ Agent needs permission - use /grant';
332
- statusColor = fg.yellow;
333
- }
334
- else {
335
- statusMessage = '✓ Agent will run automatically';
336
- statusColor = fg.green;
337
- }
338
- }
339
- else if (agentMode === 'manual') {
340
- statusMessage = 'ℹ️ Manual mode - use /agent <task>';
341
- statusColor = fg.gray;
342
- }
343
- else {
344
- statusMessage = 'ℹ️ Agent disabled';
345
- statusColor = fg.gray;
346
- }
347
- screen.write(2, statusY, statusMessage, statusColor);
348
- // Footer
349
- const footerY = height - 1;
350
- screen.write(2, footerY, '↑/↓ Navigate | ←/→ Adjust | Enter Edit | Esc Close', fg.gray);
351
- screen.showCursor(state.editing);
352
- screen.fullRender();
353
- }
354
275
  /**
355
276
  * Handle settings key
356
277
  * Returns: { handled: boolean, close: boolean, notify?: string }
@@ -34,6 +34,22 @@ const LANG_ALIASES = {
34
34
  export function highlightCode(code, lang) {
35
35
  const normalizedLang = LANG_ALIASES[lang.toLowerCase()] || lang.toLowerCase();
36
36
  const keywords = KEYWORDS[normalizedLang] || KEYWORDS['js'] || [];
37
+ // Diffs are line-oriented: +added/-removed/@@hunk. The agent emits
38
+ // ```diff on every edit confirmation, so without this branch the most
39
+ // common block in an agent run fell through to JS keyword colors.
40
+ if (normalizedLang === 'diff' || normalizedLang === 'patch') {
41
+ return code.split('\n').map(line => {
42
+ if (line.startsWith('+++') || line.startsWith('---'))
43
+ return SYNTAX.codeLang + line + '\x1b[0m';
44
+ if (line.startsWith('@@'))
45
+ return SYNTAX.operator + line + '\x1b[0m';
46
+ if (line.startsWith('+'))
47
+ return SYNTAX.string + line + '\x1b[0m'; // green — additions
48
+ if (line.startsWith('-'))
49
+ return fg.rgb(224, 108, 117) + line + '\x1b[0m'; // red — removals
50
+ return line;
51
+ }).join('\n');
52
+ }
37
53
  if (normalizedLang === 'html' || normalizedLang === 'xml' || normalizedLang === 'svg') {
38
54
  return code.replace(/(<\/?)(\w[\w-]*)((?:\s+[\w-]+(?:=(?:"[^"]*"|'[^']*'|\S+))?)*)(\s*\/?>)/g, (_match, open, tag, attrs, close) => {
39
55
  const highlightedAttrs = attrs.replace(/([\w-]+)(=)("[^"]*"|'[^']*')/g, (_m, attr, eq, val) => SYNTAX.function + attr + '\x1b[0m' + SYNTAX.operator + eq + '\x1b[0m' + SYNTAX.string + val + '\x1b[0m');
@@ -8,9 +8,8 @@
8
8
  export { cursor, screen, fg, bg, style, styled, stripAnsi, visibleLength, truncate, wordWrap } from './ansi';
9
9
  export { Screen, Cell } from './Screen';
10
10
  export { Input, LineEditor, KeyEvent, KeyHandler } from './Input';
11
- export { ChatUI, ChatMessage, ChatUIOptions } from './ChatUI';
12
11
  export { App, AppOptions, Message } from './App';
13
12
  export { createBox, centerBox, BoxStyle, BoxOptions } from './components/Box';
14
13
  export { renderModal, renderHelpModal, renderListModal, ModalOptions } from './components/Modal';
15
- export { renderHelpScreen, helpCategories, keyboardShortcuts } from './components/Help';
14
+ export { helpCategories, keyboardShortcuts } from './components/Help';
16
15
  export { renderStatusScreen, StatusInfo } from './components/Status';
@@ -8,10 +8,9 @@
8
8
  export { cursor, screen, fg, bg, style, styled, stripAnsi, visibleLength, truncate, wordWrap } from './ansi.js';
9
9
  export { Screen } from './Screen.js';
10
10
  export { Input, LineEditor } from './Input.js';
11
- export { ChatUI } from './ChatUI.js';
12
11
  export { App } from './App.js';
13
12
  // Components
14
13
  export { createBox, centerBox } from './components/Box.js';
15
14
  export { renderModal, renderHelpModal, renderListModal } from './components/Modal.js';
16
- export { renderHelpScreen, helpCategories, keyboardShortcuts } from './components/Help.js';
15
+ export { helpCategories, keyboardShortcuts } from './components/Help.js';
17
16
  export { renderStatusScreen } from './components/Status.js';
@@ -7,6 +7,8 @@
7
7
  // and produce phantom estimates against the wrong context size.
8
8
  const MODEL_CONTEXT_WINDOWS = {
9
9
  // Z.AI / ZhipuAI
10
+ 'glm-5.2[1m]': 1_000_000,
11
+ 'glm-5.2': 200_000,
10
12
  'glm-5.1': 131_072,
11
13
  'glm-5': 80_000,
12
14
  'glm-5-turbo': 202_752,
@@ -42,6 +44,12 @@ export function getModelContextWindow(model) {
42
44
  // by hand can produce phantom cost estimates against stale rates.
43
45
  const MODEL_PRICING = {
44
46
  // Z.AI / ZhipuAI
47
+ // GLM-5.2 per-token pricing isn't published yet — mirror GLM-5.1 (same tier,
48
+ // its successor) provisionally so /cost stays sane; update when z.ai posts it.
49
+ // Note: on the GLM Coding Plan (the default `z.ai` provider) billing is a flat
50
+ // subscription, so this only affects the pay-per-use estimate.
51
+ 'glm-5.2[1m]': { inputPer1M: 1.00, outputPer1M: 3.20 },
52
+ 'glm-5.2': { inputPer1M: 1.00, outputPer1M: 3.20 },
45
53
  'glm-5.1': { inputPer1M: 1.00, outputPer1M: 3.20 },
46
54
  'glm-5': { inputPer1M: 0.72, outputPer1M: 2.30 },
47
55
  'glm-5-turbo': { inputPer1M: 1.20, outputPer1M: 4.00 },
package/dist/version.d.ts CHANGED
@@ -1 +1 @@
1
- export declare const VERSION = "2.9.0";
1
+ export declare const VERSION = "2.11.0";
package/dist/version.js CHANGED
@@ -1,4 +1,4 @@
1
1
  // AUTO-GENERATED by scripts/gen-version.js — do not edit by hand.
2
2
  // Baked from package.json at build time so the bun-compiled binary reports
3
3
  // the right version (it has no package.json on disk to read at runtime).
4
- export const VERSION = '2.9.0';
4
+ export const VERSION = '2.11.0';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "codeep",
3
- "version": "2.9.0",
3
+ "version": "2.11.0",
4
4
  "description": "AI-powered coding assistant built for the terminal. Multiple LLM providers, project-aware context, and a seamless development workflow.",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -9,11 +9,9 @@
9
9
  },
10
10
  "scripts": {
11
11
  "dev": "node scripts/gen-version.js && node --import tsx src/renderer/main.ts",
12
- "prepack": "node scripts/gen-version.js && tsc; node scripts/fix-imports.js",
12
+ "prepack": "node scripts/gen-version.js && tsc && node scripts/fix-imports.js",
13
13
  "build": "node scripts/gen-version.js && tsc && node scripts/fix-imports.js",
14
14
  "start": "node dist/renderer/main.js",
15
- "demo:renderer": "node --import tsx src/renderer/demo.ts",
16
- "demo:app": "node --import tsx src/renderer/demo-app.ts",
17
15
  "build:binary": "npm run build && pkg dist/renderer/main.js --targets node18-macos-arm64,node18-macos-x64,node18-linux-x64 --output bin/codeep",
18
16
  "test": "vitest run",
19
17
  "test:watch": "vitest",
@@ -1,71 +0,0 @@
1
- /**
2
- * Simple Chat UI - Proof of Concept
3
- * Demonstrates custom renderer without Ink
4
- */
5
- export interface ChatMessage {
6
- role: 'user' | 'assistant' | 'system';
7
- content: string;
8
- }
9
- export interface ChatUIOptions {
10
- onSubmit: (message: string) => void;
11
- onExit: () => void;
12
- }
13
- export declare class ChatUI {
14
- private screen;
15
- private input;
16
- private editor;
17
- private messages;
18
- private streamingContent;
19
- private isStreaming;
20
- private options;
21
- private scrollOffset;
22
- constructor(options: ChatUIOptions);
23
- /**
24
- * Start the UI
25
- */
26
- start(): void;
27
- /**
28
- * Stop the UI
29
- */
30
- stop(): void;
31
- /**
32
- * Add a message to chat
33
- */
34
- addMessage(message: ChatMessage): void;
35
- /**
36
- * Start streaming response
37
- */
38
- startStreaming(): void;
39
- /**
40
- * Add chunk to streaming response
41
- */
42
- addStreamChunk(chunk: string): void;
43
- /**
44
- * End streaming and add as message
45
- */
46
- endStreaming(): void;
47
- /**
48
- * Handle keyboard input
49
- */
50
- private handleKey;
51
- /**
52
- * Render the entire UI
53
- */
54
- render(): void;
55
- /**
56
- * Full render (alias for render, used on start)
57
- */
58
- private fullRender;
59
- /**
60
- * Format a message into lines
61
- */
62
- private formatMessage;
63
- /**
64
- * Get messages formatted for visible area (including streaming)
65
- */
66
- private getVisibleMessages;
67
- /**
68
- * Simple word wrap
69
- */
70
- private wordWrap;
71
- }
@@ -1,286 +0,0 @@
1
- /**
2
- * Simple Chat UI - Proof of Concept
3
- * Demonstrates custom renderer without Ink
4
- */
5
- import { Screen } from './Screen.js';
6
- import { Input, LineEditor } from './Input.js';
7
- import { fg } from './ansi.js';
8
- export class ChatUI {
9
- screen;
10
- input;
11
- editor;
12
- messages = [];
13
- streamingContent = '';
14
- isStreaming = false;
15
- options;
16
- scrollOffset = 0;
17
- constructor(options) {
18
- this.screen = new Screen();
19
- this.input = new Input();
20
- this.editor = new LineEditor();
21
- this.options = options;
22
- }
23
- /**
24
- * Start the UI
25
- */
26
- start() {
27
- this.screen.init();
28
- this.input.start();
29
- // Handle keyboard input
30
- this.input.onKey((event) => this.handleKey(event));
31
- // Initial render - use full render first time
32
- this.fullRender();
33
- }
34
- /**
35
- * Stop the UI
36
- */
37
- stop() {
38
- this.input.stop();
39
- this.screen.cleanup();
40
- }
41
- /**
42
- * Add a message to chat
43
- */
44
- addMessage(message) {
45
- this.messages.push(message);
46
- this.scrollOffset = 0; // Reset scroll to bottom
47
- this.render();
48
- }
49
- /**
50
- * Start streaming response
51
- */
52
- startStreaming() {
53
- this.isStreaming = true;
54
- this.streamingContent = '';
55
- this.render();
56
- }
57
- /**
58
- * Add chunk to streaming response
59
- */
60
- addStreamChunk(chunk) {
61
- this.streamingContent += chunk;
62
- this.render();
63
- }
64
- /**
65
- * End streaming and add as message
66
- */
67
- endStreaming() {
68
- if (this.streamingContent) {
69
- this.messages.push({
70
- role: 'assistant',
71
- content: this.streamingContent,
72
- });
73
- }
74
- this.streamingContent = '';
75
- this.isStreaming = false;
76
- this.render();
77
- }
78
- /**
79
- * Handle keyboard input
80
- */
81
- handleKey(event) {
82
- // Ctrl+C or Ctrl+D to exit
83
- if (event.ctrl && (event.key === 'c' || event.key === 'd')) {
84
- this.stop();
85
- this.options.onExit();
86
- return;
87
- }
88
- // Escape to cancel streaming
89
- if (event.key === 'escape' && this.isStreaming) {
90
- this.endStreaming();
91
- return;
92
- }
93
- // Ctrl+L to clear
94
- if (event.ctrl && event.key === 'l') {
95
- this.messages = [];
96
- this.render();
97
- return;
98
- }
99
- // Page up/down for scrolling
100
- if (event.key === 'pageup') {
101
- this.scrollOffset = Math.min(this.scrollOffset + 5, this.messages.length - 1);
102
- this.render();
103
- return;
104
- }
105
- if (event.key === 'pagedown') {
106
- this.scrollOffset = Math.max(this.scrollOffset - 5, 0);
107
- this.render();
108
- return;
109
- }
110
- // Enter to submit
111
- if (event.key === 'enter') {
112
- const value = this.editor.getValue().trim();
113
- if (value) {
114
- this.editor.addToHistory(value);
115
- this.editor.clear();
116
- // Add user message
117
- this.addMessage({ role: 'user', content: value });
118
- // Callback
119
- this.options.onSubmit(value);
120
- }
121
- this.render();
122
- return;
123
- }
124
- // Handle editor keys
125
- if (this.editor.handleKey(event)) {
126
- this.render();
127
- }
128
- }
129
- /**
130
- * Render the entire UI
131
- */
132
- render() {
133
- const { width, height } = this.screen.getSize();
134
- this.screen.clear();
135
- // Layout:
136
- // - Line 0: Header
137
- // - Lines 1 to height-4: Messages
138
- // - Line height-3: Separator
139
- // - Line height-2: Input
140
- // - Line height-1: Status bar
141
- const headerLine = 0;
142
- const messagesStart = 1;
143
- const messagesEnd = height - 4;
144
- const separatorLine = height - 3;
145
- const inputLine = height - 2;
146
- const statusLine = height - 1;
147
- // Header
148
- const header = ' Codeep Chat ';
149
- const headerPadding = '─'.repeat(Math.max(0, (width - header.length) / 2));
150
- this.screen.writeLine(headerLine, headerPadding + header + headerPadding, fg.cyan);
151
- // Messages area (including streaming content)
152
- const messagesHeight = messagesEnd - messagesStart + 1;
153
- const messagesToRender = this.getVisibleMessages(messagesHeight, width - 2);
154
- let y = messagesStart;
155
- for (const line of messagesToRender) {
156
- if (y > messagesEnd)
157
- break;
158
- this.screen.writeLine(y, line.text, line.style);
159
- y++;
160
- }
161
- // Separator
162
- this.screen.horizontalLine(separatorLine, '─', fg.gray);
163
- // Input line
164
- const prompt = '> ';
165
- const inputValue = this.editor.getValue();
166
- const cursorPos = this.editor.getCursorPos();
167
- const maxInputWidth = width - prompt.length - 1;
168
- // Calculate what part of input to show and where cursor should be
169
- let displayValue;
170
- let cursorX;
171
- if (inputValue.length <= maxInputWidth) {
172
- // Input fits - show all, cursor at actual position
173
- displayValue = inputValue;
174
- cursorX = prompt.length + cursorPos;
175
- }
176
- else {
177
- // Input too long - scroll to keep cursor visible
178
- // Keep cursor roughly in the middle-right of visible area
179
- const visibleStart = Math.max(0, cursorPos - Math.floor(maxInputWidth * 0.7));
180
- const visibleEnd = visibleStart + maxInputWidth;
181
- if (visibleStart > 0) {
182
- displayValue = '…' + inputValue.slice(visibleStart + 1, visibleEnd);
183
- }
184
- else {
185
- displayValue = inputValue.slice(0, maxInputWidth);
186
- }
187
- // Cursor position relative to visible portion
188
- cursorX = prompt.length + (cursorPos - visibleStart);
189
- if (visibleStart > 0) {
190
- cursorX = prompt.length + (cursorPos - visibleStart);
191
- }
192
- }
193
- this.screen.writeLine(inputLine, prompt + displayValue, fg.green);
194
- // Position cursor
195
- this.screen.setCursor(cursorX, inputLine);
196
- this.screen.showCursor(true);
197
- // Status bar
198
- const statusLeft = ` ${this.messages.length} messages`;
199
- const statusRight = this.isStreaming ? 'Streaming... (Esc to cancel)' : 'Enter to send | Ctrl+C to exit';
200
- const statusPadding = ' '.repeat(Math.max(0, width - statusLeft.length - statusRight.length));
201
- this.screen.writeLine(statusLine, statusLeft + statusPadding + statusRight, fg.gray);
202
- // Render to terminal (use fullRender for now - more reliable)
203
- this.screen.fullRender();
204
- }
205
- /**
206
- * Full render (alias for render, used on start)
207
- */
208
- fullRender() {
209
- this.render();
210
- }
211
- /**
212
- * Format a message into lines
213
- */
214
- formatMessage(role, content, maxWidth) {
215
- const lines = [];
216
- // Role indicator
217
- const roleStyle = role === 'user' ? fg.green : role === 'assistant' ? fg.cyan : fg.yellow;
218
- const roleLabel = role === 'user' ? '> ' : role === 'assistant' ? ' ' : '# ';
219
- // Split content into lines
220
- const contentLines = content.split('\n');
221
- for (let i = 0; i < contentLines.length; i++) {
222
- const line = contentLines[i];
223
- const prefix = i === 0 ? roleLabel : ' ';
224
- const prefixStyle = i === 0 ? roleStyle : '';
225
- // Word wrap long lines
226
- if (line.length > maxWidth - prefix.length) {
227
- const wrapped = this.wordWrap(line, maxWidth - prefix.length);
228
- for (let j = 0; j < wrapped.length; j++) {
229
- lines.push({
230
- text: (j === 0 ? prefix : ' ') + wrapped[j],
231
- style: j === 0 ? prefixStyle : '',
232
- });
233
- }
234
- }
235
- else {
236
- lines.push({
237
- text: prefix + line,
238
- style: prefixStyle,
239
- });
240
- }
241
- }
242
- // Add empty line after message
243
- lines.push({ text: '', style: '' });
244
- return lines;
245
- }
246
- /**
247
- * Get messages formatted for visible area (including streaming)
248
- */
249
- getVisibleMessages(height, width) {
250
- const allLines = [];
251
- for (const msg of this.messages) {
252
- const msgLines = this.formatMessage(msg.role, msg.content, width);
253
- allLines.push(...msgLines);
254
- }
255
- // Add streaming content if active
256
- if (this.isStreaming && this.streamingContent) {
257
- const streamLines = this.formatMessage('assistant', this.streamingContent + '▊', width);
258
- allLines.push(...streamLines);
259
- }
260
- // Apply scroll offset and return last 'height' lines
261
- const startIndex = Math.max(0, allLines.length - height - this.scrollOffset);
262
- const endIndex = allLines.length - this.scrollOffset;
263
- return allLines.slice(startIndex, endIndex);
264
- }
265
- /**
266
- * Simple word wrap
267
- */
268
- wordWrap(text, maxWidth) {
269
- const words = text.split(' ');
270
- const lines = [];
271
- let currentLine = '';
272
- for (const word of words) {
273
- if (currentLine.length + word.length + 1 > maxWidth && currentLine) {
274
- lines.push(currentLine);
275
- currentLine = word;
276
- }
277
- else {
278
- currentLine += (currentLine ? ' ' : '') + word;
279
- }
280
- }
281
- if (currentLine) {
282
- lines.push(currentLine);
283
- }
284
- return lines.length > 0 ? lines : [''];
285
- }
286
- }
@@ -1,6 +0,0 @@
1
- #!/usr/bin/env node
2
- /**
3
- * Demo for full App with modals
4
- * Run with: npm run demo:app
5
- */
6
- export {};