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 +3 -3
- package/dist/acp/protocol.d.ts +4 -0
- package/dist/acp/server.js +4 -0
- package/dist/config/index.d.ts +3 -0
- package/dist/config/index.js +31 -21
- package/dist/config/providers.js +16 -12
- package/dist/renderer/App.d.ts +7 -1
- package/dist/renderer/App.js +77 -49
- package/dist/renderer/components/Help.d.ts +0 -5
- package/dist/renderer/components/Help.js +1 -68
- package/dist/renderer/components/Settings.d.ts +0 -5
- package/dist/renderer/components/Settings.js +1 -80
- package/dist/renderer/highlight.js +16 -0
- package/dist/renderer/index.d.ts +1 -2
- package/dist/renderer/index.js +1 -2
- package/dist/utils/tokenTracker.js +6 -0
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +1 -3
- package/dist/renderer/ChatUI.d.ts +0 -71
- package/dist/renderer/ChatUI.js +0 -286
- package/dist/renderer/demo-app.d.ts +0 -6
- package/dist/renderer/demo-app.js +0 -85
- package/dist/renderer/demo.d.ts +0 -6
- package/dist/renderer/demo.js +0 -52
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.
|
|
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.
|
|
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.
|
|
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
|
|
package/dist/acp/protocol.d.ts
CHANGED
|
@@ -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
|
}
|
package/dist/acp/server.js
CHANGED
|
@@ -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
|
};
|
package/dist/config/index.d.ts
CHANGED
|
@@ -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;
|
package/dist/config/index.js
CHANGED
|
@@ -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.
|
|
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
|
-
//
|
|
251
|
-
//
|
|
252
|
-
//
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
config.
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
config.
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
config.
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
config.
|
|
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
|
package/dist/config/providers.js
CHANGED
|
@@ -18,11 +18,12 @@ export const PROVIDERS = {
|
|
|
18
18
|
},
|
|
19
19
|
},
|
|
20
20
|
models: [
|
|
21
|
-
{ id: 'glm-5.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
110
|
+
defaultModel: 'glm-5.2',
|
|
107
111
|
defaultProtocol: 'openai',
|
|
108
112
|
envKey: 'ZAI_CN_API_KEY',
|
|
109
113
|
subscribeUrl: 'https://open.bigmodel.cn',
|
package/dist/renderer/App.d.ts
CHANGED
|
@@ -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;
|
package/dist/renderer/App.js
CHANGED
|
@@ -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
|
-
//
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
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
|
|
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
|
-
//
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
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
|
|
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
|
|
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');
|
package/dist/renderer/index.d.ts
CHANGED
|
@@ -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 {
|
|
14
|
+
export { helpCategories, keyboardShortcuts } from './components/Help';
|
|
16
15
|
export { renderStatusScreen, StatusInfo } from './components/Status';
|
package/dist/renderer/index.js
CHANGED
|
@@ -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 {
|
|
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.
|
|
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.
|
|
4
|
+
export const VERSION = '2.11.1';
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "codeep",
|
|
3
|
-
"version": "2.
|
|
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
|
-
}
|
package/dist/renderer/ChatUI.js
DELETED
|
@@ -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,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);
|
package/dist/renderer/demo.d.ts
DELETED
package/dist/renderer/demo.js
DELETED
|
@@ -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);
|