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