codemaxxing 1.0.16 → 1.0.17
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 +58 -15
- package/dist/index.js +119 -1263
- package/dist/ui/banner.d.ts +12 -0
- package/dist/ui/banner.js +28 -0
- package/dist/ui/connection-types.d.ts +33 -0
- package/dist/ui/connection-types.js +1 -0
- package/dist/ui/connection.d.ts +11 -0
- package/dist/ui/connection.js +182 -0
- package/dist/ui/input-router.d.ts +136 -0
- package/dist/ui/input-router.js +657 -0
- package/dist/ui/paste-interceptor.d.ts +21 -0
- package/dist/ui/paste-interceptor.js +179 -0
- package/dist/ui/pickers.d.ts +150 -0
- package/dist/ui/pickers.js +111 -0
- package/dist/ui/status-bar.d.ts +8 -0
- package/dist/ui/status-bar.js +15 -0
- package/dist/ui/wizard-types.d.ts +27 -0
- package/dist/ui/wizard-types.js +1 -0
- package/dist/ui/wizard.d.ts +3 -0
- package/dist/ui/wizard.js +214 -0
- package/package.json +1 -1
|
@@ -0,0 +1,657 @@
|
|
|
1
|
+
import { PROVIDERS, openRouterOAuth, anthropicSetupToken, importCodexToken, importQwenToken, copilotDeviceFlow } from "../utils/auth.js";
|
|
2
|
+
import { listInstalledSkills, installSkill, removeSkill, getRegistrySkills } from "../utils/skills.js";
|
|
3
|
+
import { listThemes, getTheme, THEMES } from "../themes.js";
|
|
4
|
+
import { getSession, loadMessages, deleteSession } from "../utils/sessions.js";
|
|
5
|
+
import { deleteModel, stopOllama } from "../utils/ollama.js";
|
|
6
|
+
import { loadConfig, saveConfig } from "../config.js";
|
|
7
|
+
import { handleWizardScreen } from "./wizard.js";
|
|
8
|
+
// ── Main router ──
|
|
9
|
+
export function routeKeyPress(inputChar, key, ctx) {
|
|
10
|
+
if (handleSlashCommandNavigation(inputChar, key, ctx))
|
|
11
|
+
return true;
|
|
12
|
+
if (handleLoginMethodPicker(inputChar, key, ctx))
|
|
13
|
+
return true;
|
|
14
|
+
if (handleLoginPicker(inputChar, key, ctx))
|
|
15
|
+
return true;
|
|
16
|
+
if (handleSkillsPicker(inputChar, key, ctx))
|
|
17
|
+
return true;
|
|
18
|
+
if (handleModelPicker(inputChar, key, ctx))
|
|
19
|
+
return true;
|
|
20
|
+
if (handleOllamaDeletePicker(inputChar, key, ctx))
|
|
21
|
+
return true;
|
|
22
|
+
if (handleOllamaPullPicker(inputChar, key, ctx))
|
|
23
|
+
return true;
|
|
24
|
+
if (handleOllamaDeleteConfirm(inputChar, key, ctx))
|
|
25
|
+
return true;
|
|
26
|
+
if (handleOllamaExitPrompt(inputChar, key, ctx))
|
|
27
|
+
return true;
|
|
28
|
+
if (handleWizardScreen(inputChar, key, ctx))
|
|
29
|
+
return true;
|
|
30
|
+
if (handleThemePicker(inputChar, key, ctx))
|
|
31
|
+
return true;
|
|
32
|
+
if (handleSessionPicker(inputChar, key, ctx))
|
|
33
|
+
return true;
|
|
34
|
+
if (handleDeleteSessionConfirm(inputChar, key, ctx))
|
|
35
|
+
return true;
|
|
36
|
+
if (handleDeleteSessionPicker(inputChar, key, ctx))
|
|
37
|
+
return true;
|
|
38
|
+
if (handleBackspaceRemovesPasteChunk(inputChar, key, ctx))
|
|
39
|
+
return true;
|
|
40
|
+
if (handleApprovalPrompts(inputChar, key, ctx))
|
|
41
|
+
return true;
|
|
42
|
+
if (handleCtrlCExit(inputChar, key, ctx))
|
|
43
|
+
return true;
|
|
44
|
+
return false;
|
|
45
|
+
}
|
|
46
|
+
// ── Handlers ──
|
|
47
|
+
function handleSlashCommandNavigation(_inputChar, key, ctx) {
|
|
48
|
+
if (!ctx.showSuggestionsRef.current)
|
|
49
|
+
return false;
|
|
50
|
+
const matches = ctx.cmdMatchesRef.current;
|
|
51
|
+
if (key.upArrow) {
|
|
52
|
+
ctx.setCmdIndex((prev) => (prev - 1 + matches.length) % matches.length);
|
|
53
|
+
return true;
|
|
54
|
+
}
|
|
55
|
+
if (key.downArrow) {
|
|
56
|
+
ctx.setCmdIndex((prev) => (prev + 1) % matches.length);
|
|
57
|
+
return true;
|
|
58
|
+
}
|
|
59
|
+
if (key.tab) {
|
|
60
|
+
const selected = matches[ctx.cmdIndexRef.current];
|
|
61
|
+
if (selected) {
|
|
62
|
+
ctx.setInput(selected.cmd + (selected.cmd === "/commit" ? " " : ""));
|
|
63
|
+
ctx.setCmdIndex(() => 0);
|
|
64
|
+
ctx.setInputKey((k) => k + 1);
|
|
65
|
+
}
|
|
66
|
+
return true;
|
|
67
|
+
}
|
|
68
|
+
return false;
|
|
69
|
+
}
|
|
70
|
+
function handleLoginMethodPicker(inputChar, key, ctx) {
|
|
71
|
+
if (!ctx.loginMethodPicker)
|
|
72
|
+
return false;
|
|
73
|
+
const methods = ctx.loginMethodPicker.methods;
|
|
74
|
+
if (key.upArrow) {
|
|
75
|
+
ctx.setLoginMethodIndex((prev) => (prev - 1 + methods.length) % methods.length);
|
|
76
|
+
return true;
|
|
77
|
+
}
|
|
78
|
+
if (key.downArrow) {
|
|
79
|
+
ctx.setLoginMethodIndex((prev) => (prev + 1) % methods.length);
|
|
80
|
+
return true;
|
|
81
|
+
}
|
|
82
|
+
if (key.escape) {
|
|
83
|
+
ctx.setLoginMethodPicker(null);
|
|
84
|
+
ctx.setLoginPicker(true);
|
|
85
|
+
return true;
|
|
86
|
+
}
|
|
87
|
+
if (key.return) {
|
|
88
|
+
const method = methods[ctx.loginMethodIndex];
|
|
89
|
+
const providerId = ctx.loginMethodPicker.provider;
|
|
90
|
+
ctx.setLoginMethodPicker(null);
|
|
91
|
+
if (method === "oauth" && providerId === "openrouter") {
|
|
92
|
+
ctx.addMsg("info", "Starting OpenRouter OAuth — opening browser...");
|
|
93
|
+
ctx.setLoading(true);
|
|
94
|
+
ctx.setSpinnerMsg("Waiting for authorization...");
|
|
95
|
+
openRouterOAuth((msg) => ctx.addMsg("info", msg))
|
|
96
|
+
.then(() => {
|
|
97
|
+
ctx.addMsg("info", `✅ OpenRouter authenticated! Access to 200+ models.`);
|
|
98
|
+
ctx.setLoading(false);
|
|
99
|
+
})
|
|
100
|
+
.catch((err) => { ctx.addMsg("error", `OAuth failed: ${err.message}`); ctx.setLoading(false); });
|
|
101
|
+
}
|
|
102
|
+
else if (method === "setup-token") {
|
|
103
|
+
ctx.addMsg("info", "Starting setup-token flow — browser will open...");
|
|
104
|
+
ctx.setLoading(true);
|
|
105
|
+
ctx.setSpinnerMsg("Waiting for Claude Code auth...");
|
|
106
|
+
anthropicSetupToken((msg) => ctx.addMsg("info", msg))
|
|
107
|
+
.then((cred) => { ctx.addMsg("info", `✅ Anthropic authenticated! (${cred.label})`); ctx.setLoading(false); })
|
|
108
|
+
.catch((err) => { ctx.addMsg("error", `Auth failed: ${err.message}`); ctx.setLoading(false); });
|
|
109
|
+
}
|
|
110
|
+
else if (method === "cached-token" && providerId === "openai") {
|
|
111
|
+
const imported = importCodexToken((msg) => ctx.addMsg("info", msg));
|
|
112
|
+
if (imported) {
|
|
113
|
+
ctx.addMsg("info", `✅ Imported Codex credentials! (${imported.label})`);
|
|
114
|
+
}
|
|
115
|
+
else {
|
|
116
|
+
ctx.addMsg("info", "No Codex CLI found. Install Codex CLI and sign in first.");
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
else if (method === "cached-token" && providerId === "qwen") {
|
|
120
|
+
const imported = importQwenToken((msg) => ctx.addMsg("info", msg));
|
|
121
|
+
if (imported) {
|
|
122
|
+
ctx.addMsg("info", `✅ Imported Qwen credentials! (${imported.label})`);
|
|
123
|
+
}
|
|
124
|
+
else {
|
|
125
|
+
ctx.addMsg("info", "No Qwen CLI found. Install Qwen CLI and sign in first.");
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
else if (method === "device-flow") {
|
|
129
|
+
ctx.addMsg("info", "Starting GitHub Copilot device flow...");
|
|
130
|
+
ctx.setLoading(true);
|
|
131
|
+
ctx.setSpinnerMsg("Waiting for GitHub authorization...");
|
|
132
|
+
copilotDeviceFlow((msg) => ctx.addMsg("info", msg))
|
|
133
|
+
.then(() => { ctx.addMsg("info", `✅ GitHub Copilot authenticated!`); ctx.setLoading(false); })
|
|
134
|
+
.catch((err) => { ctx.addMsg("error", `Copilot auth failed: ${err.message}`); ctx.setLoading(false); });
|
|
135
|
+
}
|
|
136
|
+
else if (method === "api-key") {
|
|
137
|
+
const provider = PROVIDERS.find((p) => p.id === providerId);
|
|
138
|
+
ctx.addMsg("info", `Enter your API key via CLI:\n codemaxxing auth api-key ${providerId} <your-key>\n Get key at: ${provider?.consoleUrl ?? "your provider's dashboard"}`);
|
|
139
|
+
}
|
|
140
|
+
return true;
|
|
141
|
+
}
|
|
142
|
+
return true; // absorb all keys when picker is active
|
|
143
|
+
}
|
|
144
|
+
function handleLoginPicker(_inputChar, key, ctx) {
|
|
145
|
+
if (!ctx.loginPicker)
|
|
146
|
+
return false;
|
|
147
|
+
const loginProviders = PROVIDERS.filter((p) => p.id !== "local");
|
|
148
|
+
if (key.upArrow) {
|
|
149
|
+
ctx.setLoginPickerIndex((prev) => (prev - 1 + loginProviders.length) % loginProviders.length);
|
|
150
|
+
return true;
|
|
151
|
+
}
|
|
152
|
+
if (key.downArrow) {
|
|
153
|
+
ctx.setLoginPickerIndex((prev) => (prev + 1) % loginProviders.length);
|
|
154
|
+
return true;
|
|
155
|
+
}
|
|
156
|
+
if (key.return) {
|
|
157
|
+
const selected = loginProviders[ctx.loginPickerIndex];
|
|
158
|
+
ctx.setLoginPicker(false);
|
|
159
|
+
const methods = selected.methods.filter((m) => m !== "none");
|
|
160
|
+
if (methods.length === 1) {
|
|
161
|
+
ctx.setLoginMethodPicker({ provider: selected.id, methods });
|
|
162
|
+
ctx.setLoginMethodIndex(() => 0);
|
|
163
|
+
if (methods[0] === "oauth" && selected.id === "openrouter") {
|
|
164
|
+
ctx.setLoginMethodPicker(null);
|
|
165
|
+
ctx.addMsg("info", "Starting OpenRouter OAuth — opening browser...");
|
|
166
|
+
ctx.setLoading(true);
|
|
167
|
+
ctx.setSpinnerMsg("Waiting for authorization...");
|
|
168
|
+
openRouterOAuth((msg) => ctx.addMsg("info", msg))
|
|
169
|
+
.then(() => { ctx.addMsg("info", `✅ OpenRouter authenticated! Access to 200+ models.`); ctx.setLoading(false); })
|
|
170
|
+
.catch((err) => { ctx.addMsg("error", `OAuth failed: ${err.message}`); ctx.setLoading(false); });
|
|
171
|
+
}
|
|
172
|
+
else if (methods[0] === "device-flow") {
|
|
173
|
+
ctx.setLoginMethodPicker(null);
|
|
174
|
+
ctx.addMsg("info", "Starting GitHub Copilot device flow...");
|
|
175
|
+
ctx.setLoading(true);
|
|
176
|
+
ctx.setSpinnerMsg("Waiting for GitHub authorization...");
|
|
177
|
+
copilotDeviceFlow((msg) => ctx.addMsg("info", msg))
|
|
178
|
+
.then(() => { ctx.addMsg("info", `✅ GitHub Copilot authenticated!`); ctx.setLoading(false); })
|
|
179
|
+
.catch((err) => { ctx.addMsg("error", `Copilot auth failed: ${err.message}`); ctx.setLoading(false); });
|
|
180
|
+
}
|
|
181
|
+
else if (methods[0] === "api-key") {
|
|
182
|
+
ctx.setLoginMethodPicker(null);
|
|
183
|
+
ctx.addMsg("info", `Enter your API key via CLI:\n codemaxxing auth api-key ${selected.id} <your-key>\n Get key at: ${selected.consoleUrl ?? "your provider's dashboard"}`);
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
else {
|
|
187
|
+
ctx.setLoginMethodPicker({ provider: selected.id, methods });
|
|
188
|
+
ctx.setLoginMethodIndex(() => 0);
|
|
189
|
+
}
|
|
190
|
+
return true;
|
|
191
|
+
}
|
|
192
|
+
if (key.escape) {
|
|
193
|
+
ctx.setLoginPicker(false);
|
|
194
|
+
return true;
|
|
195
|
+
}
|
|
196
|
+
return true; // absorb all keys when picker is active
|
|
197
|
+
}
|
|
198
|
+
function handleSkillsPicker(_inputChar, key, ctx) {
|
|
199
|
+
if (!ctx.skillsPicker)
|
|
200
|
+
return false;
|
|
201
|
+
if (ctx.skillsPicker === "menu") {
|
|
202
|
+
const menuItems = ["browse", "installed", "create", "remove"];
|
|
203
|
+
if (key.upArrow) {
|
|
204
|
+
ctx.setSkillsPickerIndex((prev) => (prev - 1 + menuItems.length) % menuItems.length);
|
|
205
|
+
return true;
|
|
206
|
+
}
|
|
207
|
+
if (key.downArrow) {
|
|
208
|
+
ctx.setSkillsPickerIndex((prev) => (prev + 1) % menuItems.length);
|
|
209
|
+
return true;
|
|
210
|
+
}
|
|
211
|
+
if (key.escape) {
|
|
212
|
+
ctx.setSkillsPicker(null);
|
|
213
|
+
return true;
|
|
214
|
+
}
|
|
215
|
+
if (key.return) {
|
|
216
|
+
const selected = menuItems[ctx.skillsPickerIndex];
|
|
217
|
+
if (selected === "browse") {
|
|
218
|
+
ctx.setSkillsPicker("browse");
|
|
219
|
+
ctx.setSkillsPickerIndex(() => 0);
|
|
220
|
+
}
|
|
221
|
+
else if (selected === "installed") {
|
|
222
|
+
ctx.setSkillsPicker("installed");
|
|
223
|
+
ctx.setSkillsPickerIndex(() => 0);
|
|
224
|
+
}
|
|
225
|
+
else if (selected === "create") {
|
|
226
|
+
ctx.setSkillsPicker(null);
|
|
227
|
+
ctx.setInput("/skills create ");
|
|
228
|
+
ctx.setInputKey((k) => k + 1);
|
|
229
|
+
}
|
|
230
|
+
else if (selected === "remove") {
|
|
231
|
+
const installed = listInstalledSkills();
|
|
232
|
+
if (installed.length === 0) {
|
|
233
|
+
ctx.setSkillsPicker(null);
|
|
234
|
+
ctx.addMsg("info", "No skills installed to remove.");
|
|
235
|
+
}
|
|
236
|
+
else {
|
|
237
|
+
ctx.setSkillsPicker("remove");
|
|
238
|
+
ctx.setSkillsPickerIndex(() => 0);
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
return true;
|
|
242
|
+
}
|
|
243
|
+
return true;
|
|
244
|
+
}
|
|
245
|
+
if (ctx.skillsPicker === "browse") {
|
|
246
|
+
const registry = getRegistrySkills();
|
|
247
|
+
if (key.upArrow) {
|
|
248
|
+
ctx.setSkillsPickerIndex((prev) => (prev - 1 + registry.length) % registry.length);
|
|
249
|
+
return true;
|
|
250
|
+
}
|
|
251
|
+
if (key.downArrow) {
|
|
252
|
+
ctx.setSkillsPickerIndex((prev) => (prev + 1) % registry.length);
|
|
253
|
+
return true;
|
|
254
|
+
}
|
|
255
|
+
if (key.escape) {
|
|
256
|
+
ctx.setSkillsPicker("menu");
|
|
257
|
+
ctx.setSkillsPickerIndex(() => 0);
|
|
258
|
+
return true;
|
|
259
|
+
}
|
|
260
|
+
if (key.return) {
|
|
261
|
+
const selected = registry[ctx.skillsPickerIndex];
|
|
262
|
+
if (selected) {
|
|
263
|
+
const result = installSkill(selected.name);
|
|
264
|
+
ctx.addMsg(result.ok ? "info" : "error", result.ok ? `✅ ${result.message}` : `✗ ${result.message}`);
|
|
265
|
+
}
|
|
266
|
+
ctx.setSkillsPicker(null);
|
|
267
|
+
return true;
|
|
268
|
+
}
|
|
269
|
+
return true;
|
|
270
|
+
}
|
|
271
|
+
if (ctx.skillsPicker === "installed") {
|
|
272
|
+
const installed = listInstalledSkills();
|
|
273
|
+
if (installed.length === 0) {
|
|
274
|
+
ctx.setSkillsPicker("menu");
|
|
275
|
+
ctx.setSkillsPickerIndex(() => 0);
|
|
276
|
+
ctx.addMsg("info", "No skills installed.");
|
|
277
|
+
return true;
|
|
278
|
+
}
|
|
279
|
+
if (key.upArrow) {
|
|
280
|
+
ctx.setSkillsPickerIndex((prev) => (prev - 1 + installed.length) % installed.length);
|
|
281
|
+
return true;
|
|
282
|
+
}
|
|
283
|
+
if (key.downArrow) {
|
|
284
|
+
ctx.setSkillsPickerIndex((prev) => (prev + 1) % installed.length);
|
|
285
|
+
return true;
|
|
286
|
+
}
|
|
287
|
+
if (key.escape) {
|
|
288
|
+
ctx.setSkillsPicker("menu");
|
|
289
|
+
ctx.setSkillsPickerIndex(() => 0);
|
|
290
|
+
return true;
|
|
291
|
+
}
|
|
292
|
+
if (key.return) {
|
|
293
|
+
const selected = installed[ctx.skillsPickerIndex];
|
|
294
|
+
if (selected) {
|
|
295
|
+
const isDisabled = ctx.sessionDisabledSkills.has(selected.name);
|
|
296
|
+
if (isDisabled) {
|
|
297
|
+
ctx.setSessionDisabledSkills((prev) => { const next = new Set(prev); next.delete(selected.name); return next; });
|
|
298
|
+
if (ctx.agent)
|
|
299
|
+
ctx.agent.enableSkill(selected.name);
|
|
300
|
+
ctx.addMsg("info", `✅ Enabled: ${selected.name}`);
|
|
301
|
+
}
|
|
302
|
+
else {
|
|
303
|
+
ctx.setSessionDisabledSkills((prev) => { const next = new Set(prev); next.add(selected.name); return next; });
|
|
304
|
+
if (ctx.agent)
|
|
305
|
+
ctx.agent.disableSkill(selected.name);
|
|
306
|
+
ctx.addMsg("info", `✅ Disabled: ${selected.name} (session only)`);
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
ctx.setSkillsPicker(null);
|
|
310
|
+
return true;
|
|
311
|
+
}
|
|
312
|
+
return true;
|
|
313
|
+
}
|
|
314
|
+
if (ctx.skillsPicker === "remove") {
|
|
315
|
+
const installed = listInstalledSkills();
|
|
316
|
+
if (installed.length === 0) {
|
|
317
|
+
ctx.setSkillsPicker(null);
|
|
318
|
+
return true;
|
|
319
|
+
}
|
|
320
|
+
if (key.upArrow) {
|
|
321
|
+
ctx.setSkillsPickerIndex((prev) => (prev - 1 + installed.length) % installed.length);
|
|
322
|
+
return true;
|
|
323
|
+
}
|
|
324
|
+
if (key.downArrow) {
|
|
325
|
+
ctx.setSkillsPickerIndex((prev) => (prev + 1) % installed.length);
|
|
326
|
+
return true;
|
|
327
|
+
}
|
|
328
|
+
if (key.escape) {
|
|
329
|
+
ctx.setSkillsPicker("menu");
|
|
330
|
+
ctx.setSkillsPickerIndex(() => 0);
|
|
331
|
+
return true;
|
|
332
|
+
}
|
|
333
|
+
if (key.return) {
|
|
334
|
+
const selected = installed[ctx.skillsPickerIndex];
|
|
335
|
+
if (selected) {
|
|
336
|
+
const result = removeSkill(selected.name);
|
|
337
|
+
ctx.addMsg(result.ok ? "info" : "error", result.ok ? `✅ ${result.message}` : `✗ ${result.message}`);
|
|
338
|
+
}
|
|
339
|
+
ctx.setSkillsPicker(null);
|
|
340
|
+
return true;
|
|
341
|
+
}
|
|
342
|
+
return true;
|
|
343
|
+
}
|
|
344
|
+
return true; // absorb all keys when skills picker is active
|
|
345
|
+
}
|
|
346
|
+
function handleModelPicker(_inputChar, key, ctx) {
|
|
347
|
+
if (!ctx.modelPicker)
|
|
348
|
+
return false;
|
|
349
|
+
if (key.upArrow) {
|
|
350
|
+
ctx.setModelPickerIndex((prev) => (prev - 1 + ctx.modelPicker.length) % ctx.modelPicker.length);
|
|
351
|
+
return true;
|
|
352
|
+
}
|
|
353
|
+
if (key.downArrow) {
|
|
354
|
+
ctx.setModelPickerIndex((prev) => (prev + 1) % ctx.modelPicker.length);
|
|
355
|
+
return true;
|
|
356
|
+
}
|
|
357
|
+
if (key.escape) {
|
|
358
|
+
ctx.setModelPicker(null);
|
|
359
|
+
return true;
|
|
360
|
+
}
|
|
361
|
+
if (key.return) {
|
|
362
|
+
const selected = ctx.modelPicker[ctx.modelPickerIndex];
|
|
363
|
+
if (selected && ctx.agent) {
|
|
364
|
+
ctx.agent.switchModel(selected);
|
|
365
|
+
ctx.setModelName(selected);
|
|
366
|
+
ctx.addMsg("info", `✅ Switched to: ${selected}`);
|
|
367
|
+
ctx.refreshConnectionBanner();
|
|
368
|
+
}
|
|
369
|
+
ctx.setModelPicker(null);
|
|
370
|
+
return true;
|
|
371
|
+
}
|
|
372
|
+
return true;
|
|
373
|
+
}
|
|
374
|
+
function handleOllamaDeletePicker(_inputChar, key, ctx) {
|
|
375
|
+
if (!ctx.ollamaDeletePicker)
|
|
376
|
+
return false;
|
|
377
|
+
if (key.upArrow) {
|
|
378
|
+
ctx.setOllamaDeletePickerIndex((prev) => (prev - 1 + ctx.ollamaDeletePicker.models.length) % ctx.ollamaDeletePicker.models.length);
|
|
379
|
+
return true;
|
|
380
|
+
}
|
|
381
|
+
if (key.downArrow) {
|
|
382
|
+
ctx.setOllamaDeletePickerIndex((prev) => (prev + 1) % ctx.ollamaDeletePicker.models.length);
|
|
383
|
+
return true;
|
|
384
|
+
}
|
|
385
|
+
if (key.escape) {
|
|
386
|
+
ctx.setOllamaDeletePicker(null);
|
|
387
|
+
return true;
|
|
388
|
+
}
|
|
389
|
+
if (key.return) {
|
|
390
|
+
const selected = ctx.ollamaDeletePicker.models[ctx.ollamaDeletePickerIndex];
|
|
391
|
+
if (selected) {
|
|
392
|
+
ctx.setOllamaDeletePicker(null);
|
|
393
|
+
ctx.setOllamaDeleteConfirm({ model: selected.name, size: selected.size });
|
|
394
|
+
}
|
|
395
|
+
return true;
|
|
396
|
+
}
|
|
397
|
+
return true;
|
|
398
|
+
}
|
|
399
|
+
function handleOllamaPullPicker(_inputChar, key, ctx) {
|
|
400
|
+
if (!ctx.ollamaPullPicker)
|
|
401
|
+
return false;
|
|
402
|
+
const pullModels = [
|
|
403
|
+
{ id: "qwen2.5-coder:7b", name: "Qwen 2.5 Coder 7B", size: "5 GB", desc: "Best balance of speed & quality" },
|
|
404
|
+
{ id: "qwen2.5-coder:14b", name: "Qwen 2.5 Coder 14B", size: "9 GB", desc: "Higher quality, needs 16GB+ RAM" },
|
|
405
|
+
{ id: "qwen2.5-coder:3b", name: "Qwen 2.5 Coder 3B", size: "2 GB", desc: "\u26A0\uFE0F Basic \u2014 may struggle with tool calls" },
|
|
406
|
+
{ id: "qwen2.5-coder:32b", name: "Qwen 2.5 Coder 32B", size: "20 GB", desc: "Premium quality, needs 48GB+" },
|
|
407
|
+
{ id: "deepseek-coder-v2:16b", name: "DeepSeek Coder V2", size: "9 GB", desc: "Strong alternative" },
|
|
408
|
+
{ id: "codellama:7b", name: "CodeLlama 7B", size: "4 GB", desc: "Meta's coding model" },
|
|
409
|
+
{ id: "starcoder2:7b", name: "StarCoder2 7B", size: "4 GB", desc: "Code completion focused" },
|
|
410
|
+
];
|
|
411
|
+
if (key.upArrow) {
|
|
412
|
+
ctx.setOllamaPullPickerIndex((prev) => (prev - 1 + pullModels.length) % pullModels.length);
|
|
413
|
+
return true;
|
|
414
|
+
}
|
|
415
|
+
if (key.downArrow) {
|
|
416
|
+
ctx.setOllamaPullPickerIndex((prev) => (prev + 1) % pullModels.length);
|
|
417
|
+
return true;
|
|
418
|
+
}
|
|
419
|
+
if (key.escape) {
|
|
420
|
+
ctx.setOllamaPullPicker(false);
|
|
421
|
+
return true;
|
|
422
|
+
}
|
|
423
|
+
if (key.return) {
|
|
424
|
+
const selected = pullModels[ctx.ollamaPullPickerIndex];
|
|
425
|
+
if (selected) {
|
|
426
|
+
ctx.setOllamaPullPicker(false);
|
|
427
|
+
ctx.setInput(`/ollama pull ${selected.id}`);
|
|
428
|
+
ctx.setInputKey((k) => k + 1);
|
|
429
|
+
setTimeout(() => {
|
|
430
|
+
const submitInput = `/ollama pull ${selected.id}`;
|
|
431
|
+
ctx.setInput("");
|
|
432
|
+
ctx.handleSubmit(submitInput);
|
|
433
|
+
}, 50);
|
|
434
|
+
}
|
|
435
|
+
return true;
|
|
436
|
+
}
|
|
437
|
+
return true;
|
|
438
|
+
}
|
|
439
|
+
function handleOllamaDeleteConfirm(inputChar, key, ctx) {
|
|
440
|
+
if (!ctx.ollamaDeleteConfirm)
|
|
441
|
+
return false;
|
|
442
|
+
if (inputChar === "y" || inputChar === "Y") {
|
|
443
|
+
const model = ctx.ollamaDeleteConfirm.model;
|
|
444
|
+
ctx.setOllamaDeleteConfirm(null);
|
|
445
|
+
const result = deleteModel(model);
|
|
446
|
+
ctx.addMsg(result.ok ? "info" : "error", result.ok ? `\u2705 ${result.message}` : `\u274C ${result.message}`);
|
|
447
|
+
return true;
|
|
448
|
+
}
|
|
449
|
+
if (inputChar === "n" || inputChar === "N" || key.escape) {
|
|
450
|
+
ctx.setOllamaDeleteConfirm(null);
|
|
451
|
+
ctx.addMsg("info", "Delete cancelled.");
|
|
452
|
+
return true;
|
|
453
|
+
}
|
|
454
|
+
return true;
|
|
455
|
+
}
|
|
456
|
+
function handleOllamaExitPrompt(inputChar, key, ctx) {
|
|
457
|
+
if (!ctx.ollamaExitPrompt)
|
|
458
|
+
return false;
|
|
459
|
+
if (inputChar === "y" || inputChar === "Y") {
|
|
460
|
+
ctx.setOllamaExitPrompt(false);
|
|
461
|
+
stopOllama().then(() => ctx.exit());
|
|
462
|
+
return true;
|
|
463
|
+
}
|
|
464
|
+
if (inputChar === "n" || inputChar === "N") {
|
|
465
|
+
ctx.setOllamaExitPrompt(false);
|
|
466
|
+
ctx.exit();
|
|
467
|
+
return true;
|
|
468
|
+
}
|
|
469
|
+
if (inputChar === "a" || inputChar === "A") {
|
|
470
|
+
ctx.setOllamaExitPrompt(false);
|
|
471
|
+
saveConfig({ defaults: { ...loadConfig().defaults, stopOllamaOnExit: true } });
|
|
472
|
+
ctx.addMsg("info", "Saved preference: always stop Ollama on exit.");
|
|
473
|
+
stopOllama().then(() => ctx.exit());
|
|
474
|
+
return true;
|
|
475
|
+
}
|
|
476
|
+
if (key.escape) {
|
|
477
|
+
ctx.setOllamaExitPrompt(false);
|
|
478
|
+
return true;
|
|
479
|
+
}
|
|
480
|
+
return true;
|
|
481
|
+
}
|
|
482
|
+
function handleThemePicker(_inputChar, key, ctx) {
|
|
483
|
+
if (!ctx.themePicker)
|
|
484
|
+
return false;
|
|
485
|
+
const themeKeys = listThemes();
|
|
486
|
+
if (key.upArrow) {
|
|
487
|
+
ctx.setThemePickerIndex((prev) => (prev - 1 + themeKeys.length) % themeKeys.length);
|
|
488
|
+
return true;
|
|
489
|
+
}
|
|
490
|
+
if (key.downArrow) {
|
|
491
|
+
ctx.setThemePickerIndex((prev) => (prev + 1) % themeKeys.length);
|
|
492
|
+
return true;
|
|
493
|
+
}
|
|
494
|
+
if (key.return) {
|
|
495
|
+
const selected = themeKeys[ctx.themePickerIndex];
|
|
496
|
+
ctx.setTheme(getTheme(selected));
|
|
497
|
+
ctx.setThemePicker(false);
|
|
498
|
+
ctx.addMsg("info", `✅ Switched to theme: ${THEMES[selected].name}`);
|
|
499
|
+
return true;
|
|
500
|
+
}
|
|
501
|
+
if (key.escape) {
|
|
502
|
+
ctx.setThemePicker(false);
|
|
503
|
+
return true;
|
|
504
|
+
}
|
|
505
|
+
return true;
|
|
506
|
+
}
|
|
507
|
+
function handleSessionPicker(_inputChar, key, ctx) {
|
|
508
|
+
if (!ctx.sessionPicker)
|
|
509
|
+
return false;
|
|
510
|
+
if (key.upArrow) {
|
|
511
|
+
ctx.setSessionPickerIndex((prev) => (prev - 1 + ctx.sessionPicker.length) % ctx.sessionPicker.length);
|
|
512
|
+
return true;
|
|
513
|
+
}
|
|
514
|
+
if (key.downArrow) {
|
|
515
|
+
ctx.setSessionPickerIndex((prev) => (prev + 1) % ctx.sessionPicker.length);
|
|
516
|
+
return true;
|
|
517
|
+
}
|
|
518
|
+
if (key.return) {
|
|
519
|
+
const selected = ctx.sessionPicker[ctx.sessionPickerIndex];
|
|
520
|
+
if (selected && ctx.agent) {
|
|
521
|
+
const session = getSession(selected.id);
|
|
522
|
+
if (session) {
|
|
523
|
+
ctx.agent.resume(selected.id).then(() => {
|
|
524
|
+
const dir = session.cwd.split("/").pop() || session.cwd;
|
|
525
|
+
const msgs = loadMessages(selected.id);
|
|
526
|
+
const lastUserMsg = [...msgs].reverse().find(m => m.role === "user");
|
|
527
|
+
const lastText = lastUserMsg && typeof lastUserMsg.content === "string"
|
|
528
|
+
? lastUserMsg.content.slice(0, 80) + (lastUserMsg.content.length > 80 ? "..." : "")
|
|
529
|
+
: null;
|
|
530
|
+
let info = `✅ Resumed session ${selected.id} (${dir}/, ${session.message_count} messages)`;
|
|
531
|
+
if (lastText)
|
|
532
|
+
info += `\n Last: "${lastText}"`;
|
|
533
|
+
ctx.addMsg("info", info);
|
|
534
|
+
}).catch((e) => {
|
|
535
|
+
ctx.addMsg("error", `Failed to resume: ${e.message}`);
|
|
536
|
+
});
|
|
537
|
+
}
|
|
538
|
+
}
|
|
539
|
+
ctx.setSessionPicker(null);
|
|
540
|
+
ctx.setSessionPickerIndex(() => 0);
|
|
541
|
+
return true;
|
|
542
|
+
}
|
|
543
|
+
if (key.escape) {
|
|
544
|
+
ctx.setSessionPicker(null);
|
|
545
|
+
ctx.setSessionPickerIndex(() => 0);
|
|
546
|
+
ctx.addMsg("info", "Resume cancelled.");
|
|
547
|
+
return true;
|
|
548
|
+
}
|
|
549
|
+
return true; // Ignore other keys during session picker
|
|
550
|
+
}
|
|
551
|
+
function handleDeleteSessionConfirm(inputChar, _key, ctx) {
|
|
552
|
+
if (!ctx.deleteSessionConfirm)
|
|
553
|
+
return false;
|
|
554
|
+
if (inputChar === "y" || inputChar === "Y") {
|
|
555
|
+
const deleted = deleteSession(ctx.deleteSessionConfirm.id);
|
|
556
|
+
if (deleted) {
|
|
557
|
+
ctx.addMsg("info", `✅ Deleted session ${ctx.deleteSessionConfirm.id}`);
|
|
558
|
+
}
|
|
559
|
+
else {
|
|
560
|
+
ctx.addMsg("error", `Failed to delete session ${ctx.deleteSessionConfirm.id}`);
|
|
561
|
+
}
|
|
562
|
+
ctx.setDeleteSessionConfirm(null);
|
|
563
|
+
return true;
|
|
564
|
+
}
|
|
565
|
+
if (inputChar === "n" || inputChar === "N" || _key.escape) {
|
|
566
|
+
ctx.addMsg("info", "Delete cancelled.");
|
|
567
|
+
ctx.setDeleteSessionConfirm(null);
|
|
568
|
+
return true;
|
|
569
|
+
}
|
|
570
|
+
return true;
|
|
571
|
+
}
|
|
572
|
+
function handleDeleteSessionPicker(_inputChar, key, ctx) {
|
|
573
|
+
if (!ctx.deleteSessionPicker)
|
|
574
|
+
return false;
|
|
575
|
+
if (key.upArrow) {
|
|
576
|
+
ctx.setDeleteSessionPickerIndex((prev) => (prev - 1 + ctx.deleteSessionPicker.length) % ctx.deleteSessionPicker.length);
|
|
577
|
+
return true;
|
|
578
|
+
}
|
|
579
|
+
if (key.downArrow) {
|
|
580
|
+
ctx.setDeleteSessionPickerIndex((prev) => (prev + 1) % ctx.deleteSessionPicker.length);
|
|
581
|
+
return true;
|
|
582
|
+
}
|
|
583
|
+
if (key.return) {
|
|
584
|
+
const selected = ctx.deleteSessionPicker[ctx.deleteSessionPickerIndex];
|
|
585
|
+
if (selected) {
|
|
586
|
+
ctx.setDeleteSessionPicker(null);
|
|
587
|
+
ctx.setDeleteSessionPickerIndex(() => 0);
|
|
588
|
+
ctx.setDeleteSessionConfirm(selected);
|
|
589
|
+
}
|
|
590
|
+
return true;
|
|
591
|
+
}
|
|
592
|
+
if (key.escape) {
|
|
593
|
+
ctx.setDeleteSessionPicker(null);
|
|
594
|
+
ctx.setDeleteSessionPickerIndex(() => 0);
|
|
595
|
+
ctx.addMsg("info", "Delete cancelled.");
|
|
596
|
+
return true;
|
|
597
|
+
}
|
|
598
|
+
return true;
|
|
599
|
+
}
|
|
600
|
+
function handleBackspaceRemovesPasteChunk(_inputChar, key, ctx) {
|
|
601
|
+
if (key.backspace || key.delete) {
|
|
602
|
+
if (ctx.input === "" && ctx.pastedChunksRef.current.length > 0) {
|
|
603
|
+
ctx.setPastedChunks((prev) => prev.slice(0, -1));
|
|
604
|
+
return true;
|
|
605
|
+
}
|
|
606
|
+
}
|
|
607
|
+
return false;
|
|
608
|
+
}
|
|
609
|
+
function handleApprovalPrompts(inputChar, _key, ctx) {
|
|
610
|
+
if (!ctx.approval)
|
|
611
|
+
return false;
|
|
612
|
+
if (inputChar === "y" || inputChar === "Y") {
|
|
613
|
+
const r = ctx.approval.resolve;
|
|
614
|
+
ctx.setApproval(null);
|
|
615
|
+
ctx.setLoading(true);
|
|
616
|
+
ctx.setSpinnerMsg("Executing...");
|
|
617
|
+
r("yes");
|
|
618
|
+
return true;
|
|
619
|
+
}
|
|
620
|
+
if (inputChar === "n" || inputChar === "N") {
|
|
621
|
+
const r = ctx.approval.resolve;
|
|
622
|
+
ctx.setApproval(null);
|
|
623
|
+
ctx.addMsg("info", "✗ Denied");
|
|
624
|
+
r("no");
|
|
625
|
+
return true;
|
|
626
|
+
}
|
|
627
|
+
if (inputChar === "a" || inputChar === "A") {
|
|
628
|
+
const r = ctx.approval.resolve;
|
|
629
|
+
ctx.setApproval(null);
|
|
630
|
+
ctx.setLoading(true);
|
|
631
|
+
ctx.setSpinnerMsg("Executing...");
|
|
632
|
+
ctx.addMsg("info", `✔ Always allow ${ctx.approval.tool} for this session`);
|
|
633
|
+
r("always");
|
|
634
|
+
return true;
|
|
635
|
+
}
|
|
636
|
+
return true; // Ignore other keys during approval
|
|
637
|
+
}
|
|
638
|
+
function handleCtrlCExit(inputChar, key, ctx) {
|
|
639
|
+
if (key.ctrl && inputChar === "c") {
|
|
640
|
+
if (ctx.ctrlCPressed) {
|
|
641
|
+
const config = loadConfig();
|
|
642
|
+
if (config.defaults.stopOllamaOnExit) {
|
|
643
|
+
stopOllama().finally(() => ctx.exit());
|
|
644
|
+
}
|
|
645
|
+
else {
|
|
646
|
+
ctx.exit();
|
|
647
|
+
}
|
|
648
|
+
}
|
|
649
|
+
else {
|
|
650
|
+
ctx.setCtrlCPressed(true);
|
|
651
|
+
ctx.addMsg("info", "Press Ctrl+C again to exit.");
|
|
652
|
+
setTimeout(() => ctx.setCtrlCPressed(false), 3000);
|
|
653
|
+
}
|
|
654
|
+
return true;
|
|
655
|
+
}
|
|
656
|
+
return false;
|
|
657
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { EventEmitter } from "events";
|
|
2
|
+
export interface PasteEvent {
|
|
3
|
+
content: string;
|
|
4
|
+
lines: number;
|
|
5
|
+
}
|
|
6
|
+
export type PasteEventBus = EventEmitter;
|
|
7
|
+
/**
|
|
8
|
+
* Sets up the full paste interception pipeline on process.stdin:
|
|
9
|
+
*
|
|
10
|
+
* - Enables bracketed paste mode on the terminal
|
|
11
|
+
* - Patches stdin emit('data') to intercept all incoming data
|
|
12
|
+
* - Detects multiline pastes via bracketed paste escape sequences
|
|
13
|
+
* - Falls back to burst buffering for terminals without bracketed paste
|
|
14
|
+
* - Strips paste marker artifacts from all chunks
|
|
15
|
+
* - Emits "paste" events on the returned EventEmitter for multiline content
|
|
16
|
+
* - Forwards short pastes (1-2 lines) as normal stdin data
|
|
17
|
+
* - Registers an exit handler to disable bracketed paste mode
|
|
18
|
+
*
|
|
19
|
+
* Returns the EventEmitter that fires "paste" events with { content, lines }.
|
|
20
|
+
*/
|
|
21
|
+
export declare function setupPasteInterceptor(): PasteEventBus;
|