codeep 2.10.0 → 2.11.1

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/README.md CHANGED
@@ -346,7 +346,7 @@ Run `/agents` to list them. The agent invokes them itself via the `delegate` too
346
346
  name: migrator
347
347
  description: Writes and runs database migrations
348
348
  tools: [read_file, write_file, edit_file, execute_command] # allowlist; omit = all
349
- model: glm-5.1 # optional override
349
+ model: glm-5.2 # optional override
350
350
  personality: senior-reviewer # optional preset
351
351
  maxIterations: 12 # optional budget
352
352
  ---
@@ -1007,14 +1007,14 @@ After installation, `codeep` is available globally in your terminal. Simply run
1007
1007
  **Model favorites** — save provider+model combos and switch instantly:
1008
1008
  ```
1009
1009
  > /provider # switch to z.ai
1010
- > /model glm-5.1
1010
+ > /model glm-5.2
1011
1011
  > /profile save fast
1012
1012
 
1013
1013
  > /provider # switch to openai
1014
1014
  > /model gpt-4.1
1015
1015
  > /profile save work
1016
1016
 
1017
- > /model fast # instantly switch to z.ai / glm-5.1
1017
+ > /model fast # instantly switch to z.ai / glm-5.2
1018
1018
  > /model work # instantly switch to openai / gpt-4.1
1019
1019
  ```
1020
1020
 
@@ -102,6 +102,10 @@ export interface SessionNewParams {
102
102
  }
103
103
  export interface SessionNewResult {
104
104
  sessionId: string;
105
+ history?: {
106
+ role: string;
107
+ content: string;
108
+ }[];
105
109
  modes?: SessionModeState | null;
106
110
  configOptions?: SessionConfigOption[] | null;
107
111
  }
@@ -552,6 +552,10 @@ export function startAcpServer() {
552
552
  });
553
553
  const result = {
554
554
  sessionId: acpSessionId,
555
+ // On a resume (fresh=false) `history` holds the prior transcript;
556
+ // return it (user/assistant only) so a reconnected client can repaint
557
+ // the chat. Empty on a fresh session, so this is harmless there.
558
+ history: history.filter(m => m.role === 'user' || m.role === 'assistant'),
555
559
  modes: AGENT_MODES,
556
560
  configOptions: buildConfigOptions(),
557
561
  };
@@ -47,6 +47,9 @@ interface ConfigSchema {
47
47
  * can't execute shell on first tool call). Granted via `/hooks trust`. */
48
48
  trustedHookProjects: string[];
49
49
  currentSessionId: string;
50
+ /** Highest one-shot config migration applied (see the migration block
51
+ * after config creation). Bump MIGRATION_VERSION when adding one. */
52
+ migrationVersion: number;
50
53
  temperature: number;
51
54
  maxTokens: number;
52
55
  apiTimeout: number;
@@ -141,8 +141,9 @@ function isWritable(dir) {
141
141
  function createConfig() {
142
142
  const defaults = {
143
143
  apiKey: '',
144
+ migrationVersion: 0,
144
145
  provider: 'z.ai',
145
- model: 'glm-5.1',
146
+ model: 'glm-5.2',
146
147
  agentMode: 'on',
147
148
  ollamaUrl: 'http://localhost:11434',
148
149
  ollamaNativeApi: false,
@@ -247,26 +248,35 @@ export const config = createConfig();
247
248
  if (config.get('agentMode') === 'auto') {
248
249
  config.set('agentMode', 'on');
249
250
  }
250
- // Migrate the old runaway default (10000 iterations) back down to a sane ceiling.
251
- // The old migration kept forcing it up, so a user who had the bad default baked
252
- // into their local config will still see "step X/10000" until this trims it.
253
- if (config.get('agentMaxIterations') >= 10000) {
254
- config.set('agentMaxIterations', 50);
255
- }
256
- if (config.get('agentMaxDuration') < 480) {
257
- config.set('agentMaxDuration', 480);
258
- }
259
- if (config.get('maxTokens') < 32768) {
260
- config.set('maxTokens', 32768);
261
- }
262
- if (config.get('agentApiTimeout') <= 180000) {
263
- config.set('agentApiTimeout', 600000);
264
- }
265
- if (config.get('rateLimitApi') <= 30) {
266
- config.set('rateLimitApi', 10000);
267
- }
268
- if (config.get('rateLimitCommands') <= 100) {
269
- config.set('rateLimitCommands', 10000);
251
+ // One-shot migrations of old defaults. These used to run unconditionally on
252
+ // EVERY startup, which silently clobbered values the user later chose in
253
+ // /settings (e.g. maxTokens 8192 was forced back to 32768 each launch — the
254
+ // affected sliders were effectively lies). Each migration now runs exactly
255
+ // once per config, recorded via `migrationVersion`; after that, whatever the
256
+ // user sets sticks. Bump MIGRATION_VERSION when adding a new one.
257
+ const MIGRATION_VERSION = 1;
258
+ if ((config.get('migrationVersion') ?? 0) < 1) {
259
+ // Migrate the old runaway default (10000 iterations) down to a sane
260
+ // ceiling, and old conservative defaults up to the current ones.
261
+ if (config.get('agentMaxIterations') >= 10000) {
262
+ config.set('agentMaxIterations', 50);
263
+ }
264
+ if (config.get('agentMaxDuration') < 480) {
265
+ config.set('agentMaxDuration', 480);
266
+ }
267
+ if (config.get('maxTokens') < 32768) {
268
+ config.set('maxTokens', 32768);
269
+ }
270
+ if (config.get('agentApiTimeout') <= 180000) {
271
+ config.set('agentApiTimeout', 600000);
272
+ }
273
+ if (config.get('rateLimitApi') <= 30) {
274
+ config.set('rateLimitApi', 10000);
275
+ }
276
+ if (config.get('rateLimitCommands') <= 100) {
277
+ config.set('rateLimitCommands', 10000);
278
+ }
279
+ config.set('migrationVersion', MIGRATION_VERSION);
270
280
  }
271
281
  // Global sessions directory - use same directory as conf package for cross-platform consistency
272
282
  // config.path gives us something like ~/.config/codeep-nodejs/config.json, we use its parent
@@ -18,11 +18,12 @@ export const PROVIDERS = {
18
18
  },
19
19
  },
20
20
  models: [
21
- { id: 'glm-5.1', name: 'GLM-5.1', description: 'Latest GLM model, available to all users' },
21
+ { id: 'glm-5.2', name: 'GLM-5.2', description: 'Latest GLM model, available to all users' },
22
+ { id: 'glm-5.1', name: 'GLM-5.1', description: 'Previous GLM model, available to all users' },
22
23
  { id: 'glm-5-turbo', name: 'GLM-5 Turbo', description: 'Fast GLM-5 variant, available to all users' },
23
- { id: 'glm-5', name: 'GLM-5', description: 'Most capable GLM model (Pro/Max plan only)' },
24
+ { id: 'glm-5', name: 'GLM-5', description: 'Most capable GLM-5 model (Pro/Max plan only)' },
24
25
  ],
25
- defaultModel: 'glm-5.1',
26
+ defaultModel: 'glm-5.2',
26
27
  defaultProtocol: 'openai',
27
28
  envKey: 'ZAI_API_KEY',
28
29
  subscribeUrl: 'https://z.ai/subscribe?ic=NXYNXZOV14',
@@ -45,11 +46,12 @@ export const PROVIDERS = {
45
46
  },
46
47
  },
47
48
  models: [
48
- { id: 'glm-5.1', name: 'GLM-5.1', description: 'Latest GLM model' },
49
+ { id: 'glm-5.2', name: 'GLM-5.2', description: 'Latest GLM model' },
50
+ { id: 'glm-5.1', name: 'GLM-5.1', description: 'Previous GLM model' },
49
51
  { id: 'glm-5-turbo', name: 'GLM-5 Turbo', description: 'Fast GLM-5 variant' },
50
- { id: 'glm-5', name: 'GLM-5', description: 'Most capable GLM model' },
52
+ { id: 'glm-5', name: 'GLM-5', description: 'Most capable GLM-5 model' },
51
53
  ],
52
- defaultModel: 'glm-5.1',
54
+ defaultModel: 'glm-5.2',
53
55
  defaultProtocol: 'openai',
54
56
  envKey: 'ZAI_API_KEY',
55
57
  subscribeUrl: 'https://api.z.ai',
@@ -72,11 +74,12 @@ export const PROVIDERS = {
72
74
  },
73
75
  },
74
76
  models: [
75
- { id: 'glm-5.1', name: 'GLM-5.1', description: 'Latest GLM model, available to all users' },
77
+ { id: 'glm-5.2', name: 'GLM-5.2', description: 'Latest GLM model, available to all users' },
78
+ { id: 'glm-5.1', name: 'GLM-5.1', description: 'Previous GLM model, available to all users' },
76
79
  { id: 'glm-5-turbo', name: 'GLM-5 Turbo', description: 'Fast GLM-5 variant, available to all users' },
77
- { id: 'glm-5', name: 'GLM-5', description: 'Most capable GLM model (Pro/Max plan only)' },
80
+ { id: 'glm-5', name: 'GLM-5', description: 'Most capable GLM-5 model (Pro/Max plan only)' },
78
81
  ],
79
- defaultModel: 'glm-5.1',
82
+ defaultModel: 'glm-5.2',
80
83
  defaultProtocol: 'openai',
81
84
  envKey: 'ZAI_CN_API_KEY',
82
85
  subscribeUrl: 'https://open.bigmodel.cn/glm-coding',
@@ -99,11 +102,12 @@ export const PROVIDERS = {
99
102
  },
100
103
  },
101
104
  models: [
102
- { id: 'glm-5.1', name: 'GLM-5.1', description: 'Latest GLM model' },
105
+ { id: 'glm-5.2', name: 'GLM-5.2', description: 'Latest GLM model' },
106
+ { id: 'glm-5.1', name: 'GLM-5.1', description: 'Previous GLM model' },
103
107
  { id: 'glm-5-turbo', name: 'GLM-5 Turbo', description: 'Fast GLM-5 variant' },
104
- { id: 'glm-5', name: 'GLM-5', description: 'Most capable GLM model' },
108
+ { id: 'glm-5', name: 'GLM-5', description: 'Most capable GLM-5 model' },
105
109
  ],
106
- defaultModel: 'glm-5.1',
110
+ defaultModel: 'glm-5.2',
107
111
  defaultProtocol: 'openai',
108
112
  envKey: 'ZAI_CN_API_KEY',
109
113
  subscribeUrl: 'https://open.bigmodel.cn',
@@ -40,6 +40,9 @@ export declare class App {
40
40
  private isLoading;
41
41
  private options;
42
42
  private scrollOffset;
43
+ /** Messages that arrived while the user was scrolled up — drives the
44
+ * status bar's "↓ N new" badge; 0 whenever the view is at the bottom. */
45
+ private unseenWhileScrolled;
43
46
  private notification;
44
47
  private notificationIsWarn;
45
48
  private notificationTimeout;
@@ -126,7 +129,10 @@ export declare class App {
126
129
  */
127
130
  stop(): void;
128
131
  /**
129
- * Add a message
132
+ * Add a message. Autoscrolls only when the user is already at the
133
+ * bottom — if they scrolled up to read something, new messages must
134
+ * not yank the view away; the status bar shows a "↓ N new" badge
135
+ * instead (cleared when they return to the bottom).
130
136
  */
131
137
  addMessage(message: Message): void;
132
138
  setMessages(messages: Message[]): void;
@@ -49,6 +49,12 @@ const COMMAND_DESCRIPTIONS = {
49
49
  'git-commit': 'Commit with message',
50
50
  'push': 'Git push',
51
51
  'pull': 'Git pull',
52
+ 'amend': 'Amend the last commit',
53
+ 'pr': 'Create a pull request description',
54
+ 'changelog': 'Generate changelog from recent commits',
55
+ 'branch': 'Create a new branch with smart naming',
56
+ 'stash': 'Stash changes with a meaningful message',
57
+ 'unstash': 'Apply and drop the most recent stash',
52
58
  'init': 'Initialize project (.codeep/)',
53
59
  'scan': 'Scan project',
54
60
  'memory': 'Add/list/remove project memory notes',
@@ -66,6 +72,38 @@ const COMMAND_DESCRIPTIONS = {
66
72
  'explain': 'Explain code',
67
73
  'optimize': 'Optimize performance',
68
74
  'debug': 'Debug problems',
75
+ 'test-fix': 'Fix failing tests',
76
+ 'coverage': 'Analyze test coverage and suggest improvements',
77
+ 'e2e': 'Generate end-to-end tests',
78
+ 'mock': 'Generate mock data for testing',
79
+ 'readme': 'Generate or update README',
80
+ 'api-docs': 'Generate API documentation',
81
+ 'translate': 'Translate code comments to English',
82
+ 'types': 'Add or improve TypeScript types',
83
+ 'cleanup': 'Clean up code (remove unused, format)',
84
+ 'modernize': 'Update code to use modern syntax',
85
+ 'migrate': 'Migrate code to newer version',
86
+ 'split': 'Split a large file into smaller modules',
87
+ 'security': 'Security audit',
88
+ 'log': 'Add logging to code',
89
+ 'build': 'Build the project',
90
+ 'deploy': 'Build and deploy',
91
+ 'release': 'Create a new release',
92
+ 'publish': 'Publish package to npm',
93
+ 'component': 'Generate a React/Vue component',
94
+ 'api': 'Generate an API endpoint',
95
+ 'hook': 'Generate a React hook',
96
+ 'service': 'Generate a service/utility module',
97
+ 'page': 'Generate a new page/route',
98
+ 'form': 'Generate a form with validation',
99
+ 'crud': 'Generate full CRUD for an entity',
100
+ 'docker': 'Generate Dockerfile and docker-compose',
101
+ 'ci': 'Generate CI/CD configuration',
102
+ 'env': 'Setup environment configuration',
103
+ 'k8s': 'Generate Kubernetes manifests',
104
+ 'terraform': 'Generate Terraform configuration',
105
+ 'nginx': 'Generate Nginx configuration',
106
+ 'monitor': 'Add monitoring and observability',
69
107
  'skills': 'List all skills',
70
108
  'provider': 'Switch provider',
71
109
  'model': 'Switch model',
@@ -74,6 +112,7 @@ const COMMAND_DESCRIPTIONS = {
74
112
  'grant': 'Grant write permission',
75
113
  'login': 'Change API key',
76
114
  'logout': 'Logout',
115
+ 'account': 'Link this machine to your codeep.dev account',
77
116
  'context-save': 'Save conversation',
78
117
  'context-load': 'Load conversation',
79
118
  'context-clear': 'Clear saved context',
@@ -116,6 +155,9 @@ export class App {
116
155
  isLoading = false;
117
156
  options;
118
157
  scrollOffset = 0;
158
+ /** Messages that arrived while the user was scrolled up — drives the
159
+ * status bar's "↓ N new" badge; 0 whenever the view is at the bottom. */
160
+ unseenWhileScrolled = 0;
119
161
  notification = '';
120
162
  notificationIsWarn = false;
121
163
  notificationTimeout = null;
@@ -216,41 +258,13 @@ export class App {
216
258
  loginCallback = null;
217
259
  // Glitch characters for intro animation
218
260
  static GLITCH_CHARS = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ@#$%&*<>?/;:[]=';
219
- // All available commands
220
- static COMMANDS = [
221
- 'help', 'status', 'settings', 'version', 'update', 'clear', 'exit',
222
- 'sessions', 'new', 'rename', 'search', 'export',
223
- 'agent', 'agent-dry', 'stop', 'undo', 'undo-all', 'history', 'changes',
224
- 'diff', 'commit', 'git-commit', 'push', 'pull', 'scan', 'review',
225
- 'copy', 'paste', 'apply', 'add', 'drop',
226
- 'test', 'docs', 'refactor', 'fix', 'explain', 'optimize', 'debug', 'skills',
227
- 'amend', 'pr', 'changelog', 'branch', 'stash', 'unstash',
228
- 'build', 'deploy', 'release', 'publish',
229
- 'component', 'api', 'hook', 'service', 'page', 'form', 'crud',
230
- 'security', 'profile', 'log', 'types', 'cleanup', 'modernize', 'migrate',
231
- 'split', 'rename', 'coverage', 'e2e', 'mock', 'readme', 'translate',
232
- 'docker', 'ci', 'env', 'k8s', 'terraform', 'nginx', 'monitor',
233
- 'test-fix', 'api-docs',
234
- 'multiline', 'memory', 'init',
235
- 'provider', 'model', 'protocol', 'lang', 'grant', 'login', 'logout',
236
- 'context-save', 'context-load', 'context-clear', 'learn',
237
- 'cost', 'tasks', 'account', 'sync', 'keysync', 'telemetry',
238
- // 2.0 — extensions, checkpoints, MCP, custom commands, OpenRouter prefs.
239
- // Keep in lockstep with COMMAND_DESCRIPTIONS below and helpCategories.
240
- 'compact', 'commands', 'checkpoint', 'checkpoints', 'rewind',
241
- 'hooks', 'mcp', 'openrouter',
242
- // 2.0.2 — plan mode.
243
- 'plan', 'go',
244
- // 2.0.3 — personalities + insights.
245
- 'personality', 'insights',
246
- // 2.1.0 — cross-session recall.
247
- 'recall',
248
- // 2.2.0 — user profile.
249
- 'me',
250
- // 2.3.0 — sub-agents / delegation.
251
- 'agents',
252
- 'c', 't', 'd', 'r', 'f', 'e', 'o', 'b', 'p',
253
- ];
261
+ // The `/` autocomplete list, derived from COMMAND_DESCRIPTIONS so it IS
262
+ // the registry — a command can no longer ship without a description (a
263
+ // hand-maintained parallel array drifted to 48 blank rows over time).
264
+ // Single-letter shortcuts (c, t, d, r, f, e, o, b, p) stay routable in
265
+ // commands.ts but are deliberately not listed: they alias commands that
266
+ // already appear, and bare one-letter rows just cluttered the dropdown.
267
+ static COMMANDS = Object.keys(COMMAND_DESCRIPTIONS);
254
268
  constructor(options) {
255
269
  this.screen = new Screen();
256
270
  this.input = new Input();
@@ -283,24 +297,34 @@ export class App {
283
297
  this.screen.cleanup();
284
298
  }
285
299
  /**
286
- * Add a message
300
+ * Add a message. Autoscrolls only when the user is already at the
301
+ * bottom — if they scrolled up to read something, new messages must
302
+ * not yank the view away; the status bar shows a "↓ N new" badge
303
+ * instead (cleared when they return to the bottom).
287
304
  */
288
305
  addMessage(message) {
289
306
  this.messages.push(message);
290
307
  this.messageCache.push(null); // slot za novu poruku
291
- this.scrollOffset = 0;
308
+ if (this.scrollOffset === 0) {
309
+ this.unseenWhileScrolled = 0;
310
+ }
311
+ else {
312
+ this.unseenWhileScrolled++;
313
+ }
292
314
  this.scheduleRender();
293
315
  }
294
316
  setMessages(messages) {
295
317
  this.messages = messages;
296
318
  this.messageCache = new Array(messages.length).fill(null);
297
319
  this.scrollOffset = 0;
320
+ this.unseenWhileScrolled = 0;
298
321
  this.scheduleRender();
299
322
  }
300
323
  clearMessages() {
301
324
  this.messages = [];
302
325
  this.messageCache = [];
303
326
  this.scrollOffset = 0;
327
+ this.unseenWhileScrolled = 0;
304
328
  this.scheduleRender();
305
329
  }
306
330
  /**
@@ -972,20 +996,15 @@ export class App {
972
996
  if (event.key === 'pagedown') {
973
997
  // Scroll down (show newer messages)
974
998
  this.scrollOffset = Math.max(0, this.scrollOffset - 10);
999
+ if (this.scrollOffset === 0)
1000
+ this.unseenWhileScrolled = 0;
975
1001
  this.scheduleRender();
976
1002
  return;
977
1003
  }
978
- // Arrow up/down can also scroll when input is empty
979
- if (event.key === 'up' && !this.editor.getValue() && !this.showAutocomplete) {
980
- this.scrollOffset += 3;
981
- this.scheduleRender();
982
- return;
983
- }
984
- if (event.key === 'down' && !this.editor.getValue() && !this.showAutocomplete && this.scrollOffset > 0) {
985
- this.scrollOffset = Math.max(0, this.scrollOffset - 3);
986
- this.scheduleRender();
987
- return;
988
- }
1004
+ // NOTE: ↑/↓ on an empty input deliberately fall through to the editor —
1005
+ // that's prompt-history recall (Input.ts), which the status bar has
1006
+ // always advertised but this handler used to shadow with a 3-line
1007
+ // scroll. Scrolling lives on PgUp/PgDn and the mouse wheel.
989
1008
  // Mouse scroll
990
1009
  if (event.key === 'scrollup') {
991
1010
  this.scrollOffset += 3;
@@ -994,6 +1013,8 @@ export class App {
994
1013
  }
995
1014
  if (event.key === 'scrolldown') {
996
1015
  this.scrollOffset = Math.max(0, this.scrollOffset - 3);
1016
+ if (this.scrollOffset === 0)
1017
+ this.unseenWhileScrolled = 0;
997
1018
  this.scheduleRender();
998
1019
  return;
999
1020
  }
@@ -2297,7 +2318,14 @@ export class App {
2297
2318
  this.screen.write(leftX, y, ' · ', fg.gray);
2298
2319
  this.screen.write(leftX + 3, y, tokenStr, fg.gray);
2299
2320
  }
2300
- // Right: context-sensitive hints
2321
+ // Right: context-sensitive hints. While scrolled up, the "new
2322
+ // messages below" badge takes priority — it's the only signal that
2323
+ // the conversation moved on (addMessage no longer yanks the view).
2324
+ if (this.scrollOffset > 0 && this.unseenWhileScrolled > 0) {
2325
+ const badge = `↓ ${this.unseenWhileScrolled} new · PgDn `;
2326
+ this.screen.write(width - badge.length, y, badge, PRIMARY_COLOR);
2327
+ return;
2328
+ }
2301
2329
  let rightText;
2302
2330
  if (this.isStreaming || this.isLoading) {
2303
2331
  rightText = 'Esc to stop ';
@@ -1,7 +1,6 @@
1
1
  /**
2
2
  * Help screen component
3
3
  */
4
- import { Screen } from '../Screen';
5
4
  export interface HelpCategory {
6
5
  title: string;
7
6
  items: Array<{
@@ -24,7 +23,3 @@ export declare const keyboardShortcuts: {
24
23
  * Get total number of help pages
25
24
  */
26
25
  export declare function getHelpTotalPages(screenHeight: number): number;
27
- /**
28
- * Render full help screen
29
- */
30
- export declare function renderHelpScreen(screen: Screen, page?: number): void;
@@ -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,7 @@
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': 200_000,
10
11
  'glm-5.1': 131_072,
11
12
  'glm-5': 80_000,
12
13
  'glm-5-turbo': 202_752,
@@ -42,6 +43,11 @@ export function getModelContextWindow(model) {
42
43
  // by hand can produce phantom cost estimates against stale rates.
43
44
  const MODEL_PRICING = {
44
45
  // Z.AI / ZhipuAI
46
+ // GLM-5.2 per-token pricing isn't published yet — mirror GLM-5.1 (same tier,
47
+ // its successor) provisionally so /cost stays sane; update when z.ai posts it.
48
+ // Note: on the GLM Coding Plan (the default `z.ai` provider) billing is a flat
49
+ // subscription, so this only affects the pay-per-use estimate.
50
+ 'glm-5.2': { inputPer1M: 1.00, outputPer1M: 3.20 },
45
51
  'glm-5.1': { inputPer1M: 1.00, outputPer1M: 3.20 },
46
52
  'glm-5': { inputPer1M: 0.72, outputPer1M: 2.30 },
47
53
  'glm-5-turbo': { inputPer1M: 1.20, outputPer1M: 4.00 },
package/dist/version.d.ts CHANGED
@@ -1 +1 @@
1
- export declare const VERSION = "2.10.0";
1
+ export declare const VERSION = "2.11.1";
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.10.0';
4
+ export const VERSION = '2.11.1';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "codeep",
3
- "version": "2.10.0",
3
+ "version": "2.11.1",
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",
@@ -12,8 +12,6 @@
12
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 {};
@@ -1,85 +0,0 @@
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);
@@ -1,6 +0,0 @@
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 {};
@@ -1,52 +0,0 @@
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);