codemaxxing 1.1.3 → 1.1.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -71,7 +71,7 @@ If you already have a local server running, Codemaxxing auto-detects common defa
71
71
 
72
72
  For LM Studio:
73
73
  1. Download [LM Studio](https://lmstudio.ai)
74
- 2. Load a coding model (for example **Qwen 2.5 Coder 7B** for a lightweight test)
74
+ 2. Load a coding model (ideally **Qwen 2.5 Coder 14B** or **DeepSeek Coder V2 16B** if your machine can handle it; use 7B only as a fallback)
75
75
  3. Start the local server
76
76
  4. Run:
77
77
 
@@ -89,10 +89,11 @@ codemaxxing
89
89
 
90
90
  If no LLM is available, Codemaxxing can guide you through:
91
91
  - detecting your hardware
92
- - recommending a model
92
+ - recommending a model (with stronger coding defaults first)
93
93
  - installing Ollama
94
94
  - downloading the model
95
95
  - connecting automatically
96
+ - opening the model picker automatically after cloud auth
96
97
 
97
98
  ### Option C — ChatGPT Plus (GPT-5.4, easiest cloud option)
98
99
 
package/dist/index.js CHANGED
@@ -229,6 +229,7 @@ function App() {
229
229
  setApproval,
230
230
  setWizardScreen,
231
231
  setWizardIndex,
232
+ openModelPicker,
232
233
  });
233
234
  }, []);
234
235
  // Initialize agent on mount
@@ -252,6 +253,125 @@ function App() {
252
253
  showSuggestionsRef.current = showSuggestions;
253
254
  const pastedChunksRef = React.useRef(pastedChunks);
254
255
  pastedChunksRef.current = pastedChunks;
256
+ const openModelPicker = useCallback(async () => {
257
+ addMsg("info", "Fetching available models...");
258
+ const groups = {};
259
+ const providerEntries = [];
260
+ let localFound = false;
261
+ const localEndpoints = [
262
+ { name: "LM Studio", port: 1234 },
263
+ { name: "Ollama", port: 11434 },
264
+ { name: "vLLM", port: 8000 },
265
+ { name: "LocalAI", port: 8080 },
266
+ ];
267
+ for (const endpoint of localEndpoints) {
268
+ if (localFound)
269
+ break;
270
+ try {
271
+ const url = `http://localhost:${endpoint.port}/v1`;
272
+ const models = await listModels(url, "local");
273
+ if (models.length > 0) {
274
+ groups["Local LLM"] = models.map(m => ({
275
+ name: m,
276
+ baseUrl: url,
277
+ apiKey: "local",
278
+ providerType: "openai",
279
+ }));
280
+ localFound = true;
281
+ }
282
+ }
283
+ catch { /* not running */ }
284
+ }
285
+ if (!localFound) {
286
+ try {
287
+ const ollamaModels = await listInstalledModelsDetailed();
288
+ if (ollamaModels.length > 0) {
289
+ groups["Local LLM"] = ollamaModels.map(m => ({
290
+ name: m.name,
291
+ baseUrl: "http://localhost:11434/v1",
292
+ apiKey: "ollama",
293
+ providerType: "openai",
294
+ }));
295
+ localFound = true;
296
+ }
297
+ }
298
+ catch { /* Ollama not running */ }
299
+ }
300
+ if (localFound) {
301
+ providerEntries.push({ name: "Local LLM", description: "No auth needed — auto-detected", authed: true });
302
+ }
303
+ const anthropicCred = getCredential("anthropic");
304
+ const claudeModels = ["claude-sonnet-4-6", "claude-opus-4-6", "claude-haiku-4-5-20251001"];
305
+ if (anthropicCred) {
306
+ groups["Anthropic (Claude)"] = claudeModels.map(m => ({
307
+ name: m,
308
+ baseUrl: "https://api.anthropic.com",
309
+ apiKey: anthropicCred.apiKey,
310
+ providerType: "anthropic",
311
+ }));
312
+ }
313
+ providerEntries.push({ name: "Anthropic (Claude)", description: "Claude Opus, Sonnet, Haiku — use your subscription or API key", authed: !!anthropicCred });
314
+ const openaiCred = getCredential("openai");
315
+ const openaiModels = ["gpt-5.4", "gpt-5.4-pro", "gpt-5", "gpt-5-mini", "gpt-4.1", "gpt-4.1-mini", "o3", "o4-mini", "gpt-4o"];
316
+ if (openaiCred) {
317
+ const isOAuthToken = openaiCred.method === "oauth" || openaiCred.method === "cached-token" ||
318
+ (!openaiCred.apiKey.startsWith("sk-") && !openaiCred.apiKey.startsWith("sess-"));
319
+ const baseUrl = isOAuthToken
320
+ ? "https://chatgpt.com/backend-api"
321
+ : (openaiCred.baseUrl || "https://api.openai.com/v1");
322
+ groups["OpenAI (ChatGPT)"] = openaiModels.map(m => ({
323
+ name: m,
324
+ baseUrl,
325
+ apiKey: openaiCred.apiKey,
326
+ providerType: "openai",
327
+ }));
328
+ }
329
+ providerEntries.push({ name: "OpenAI (ChatGPT)", description: "GPT-5, GPT-4.1, o3 — use your ChatGPT subscription or API key", authed: !!openaiCred });
330
+ const openrouterCred = getCredential("openrouter");
331
+ if (openrouterCred) {
332
+ try {
333
+ const orModels = await listModels(openrouterCred.baseUrl || "https://openrouter.ai/api/v1", openrouterCred.apiKey);
334
+ if (orModels.length > 0) {
335
+ groups["OpenRouter"] = orModels.slice(0, 20).map(m => ({
336
+ name: m,
337
+ baseUrl: openrouterCred.baseUrl || "https://openrouter.ai/api/v1",
338
+ apiKey: openrouterCred.apiKey,
339
+ providerType: "openai",
340
+ }));
341
+ }
342
+ }
343
+ catch { /* skip */ }
344
+ }
345
+ providerEntries.push({ name: "OpenRouter", description: "200+ models (Claude, GPT, Gemini, Llama, etc.) — one login", authed: !!openrouterCred });
346
+ const qwenCred = getCredential("qwen");
347
+ if (qwenCred) {
348
+ groups["Qwen"] = ["qwen-max", "qwen-plus", "qwen-turbo"].map(m => ({
349
+ name: m,
350
+ baseUrl: qwenCred.baseUrl || "https://dashscope.aliyuncs.com/compatible-mode/v1",
351
+ apiKey: qwenCred.apiKey,
352
+ providerType: "openai",
353
+ }));
354
+ }
355
+ providerEntries.push({ name: "Qwen", description: "Qwen 3.5, Qwen Coder — use your Qwen CLI login or API key", authed: !!qwenCred });
356
+ const copilotCred = getCredential("copilot");
357
+ if (copilotCred) {
358
+ groups["GitHub Copilot"] = ["gpt-4o", "claude-3.5-sonnet"].map(m => ({
359
+ name: m,
360
+ baseUrl: copilotCred.baseUrl || "https://api.githubcopilot.com",
361
+ apiKey: copilotCred.apiKey,
362
+ providerType: "openai",
363
+ }));
364
+ }
365
+ providerEntries.push({ name: "GitHub Copilot", description: "Use your GitHub Copilot subscription", authed: !!copilotCred });
366
+ if (providerEntries.length > 0) {
367
+ setModelPickerGroups(groups);
368
+ setProviderPicker(providerEntries);
369
+ setProviderPickerIndex(0);
370
+ setSelectedProvider(null);
371
+ return;
372
+ }
373
+ addMsg("error", "No models available. Download one with /ollama pull or configure a provider.");
374
+ }, [addMsg]);
255
375
  const handleSubmit = useCallback(async (value) => {
256
376
  value = sanitizeInputArtifacts(value);
257
377
  // Skip autocomplete if input exactly matches a command (e.g. /models vs /model)
@@ -478,133 +598,7 @@ function App() {
478
598
  return;
479
599
  }
480
600
  if (trimmed === "/models" || trimmed === "/model") {
481
- addMsg("info", "Fetching available models...");
482
- const groups = {};
483
- const providerEntries = [];
484
- // Local LLM (Ollama/LM Studio) — always show, auto-detect
485
- let localFound = false;
486
- // Check common local LLM endpoints
487
- const localEndpoints = [
488
- { name: "LM Studio", port: 1234 },
489
- { name: "Ollama", port: 11434 },
490
- { name: "vLLM", port: 8000 },
491
- { name: "LocalAI", port: 8080 },
492
- ];
493
- for (const endpoint of localEndpoints) {
494
- if (localFound)
495
- break;
496
- try {
497
- const url = `http://localhost:${endpoint.port}/v1`;
498
- const models = await listModels(url, "local");
499
- if (models.length > 0) {
500
- groups["Local LLM"] = models.map(m => ({
501
- name: m,
502
- baseUrl: url,
503
- apiKey: "local",
504
- providerType: "openai",
505
- }));
506
- localFound = true;
507
- }
508
- }
509
- catch { /* not running */ }
510
- }
511
- // Also check Ollama native API
512
- if (!localFound) {
513
- try {
514
- const ollamaModels = await listInstalledModelsDetailed();
515
- if (ollamaModels.length > 0) {
516
- groups["Local LLM"] = ollamaModels.map(m => ({
517
- name: m.name,
518
- baseUrl: "http://localhost:11434/v1",
519
- apiKey: "ollama",
520
- providerType: "openai",
521
- }));
522
- localFound = true;
523
- }
524
- }
525
- catch { /* Ollama not running */ }
526
- }
527
- if (localFound) {
528
- providerEntries.push({ name: "Local LLM", description: "No auth needed — auto-detected", authed: true });
529
- }
530
- // Anthropic
531
- const anthropicCred = getCredential("anthropic");
532
- const claudeModels = ["claude-sonnet-4-6", "claude-opus-4-6", "claude-haiku-4-5-20251001"];
533
- if (anthropicCred) {
534
- groups["Anthropic (Claude)"] = claudeModels.map(m => ({
535
- name: m,
536
- baseUrl: "https://api.anthropic.com",
537
- apiKey: anthropicCred.apiKey,
538
- providerType: "anthropic",
539
- }));
540
- }
541
- providerEntries.push({ name: "Anthropic (Claude)", description: "Claude Opus, Sonnet, Haiku — use your subscription or API key", authed: !!anthropicCred });
542
- // OpenAI
543
- const openaiCred = getCredential("openai");
544
- const openaiModels = ["gpt-5.4", "gpt-5.4-pro", "gpt-5", "gpt-5-mini", "gpt-4.1", "gpt-4.1-mini", "o3", "o4-mini", "gpt-4o"];
545
- if (openaiCred) {
546
- // OAuth tokens (non sk- keys) must use ChatGPT backend, not api.openai.com
547
- const isOAuthToken = openaiCred.method === "oauth" || openaiCred.method === "cached-token" ||
548
- (!openaiCred.apiKey.startsWith("sk-") && !openaiCred.apiKey.startsWith("sess-"));
549
- const baseUrl = isOAuthToken
550
- ? "https://chatgpt.com/backend-api"
551
- : (openaiCred.baseUrl || "https://api.openai.com/v1");
552
- groups["OpenAI (ChatGPT)"] = openaiModels.map(m => ({
553
- name: m,
554
- baseUrl,
555
- apiKey: openaiCred.apiKey,
556
- providerType: "openai",
557
- }));
558
- }
559
- providerEntries.push({ name: "OpenAI (ChatGPT)", description: "GPT-5, GPT-4.1, o3 — use your ChatGPT subscription or API key", authed: !!openaiCred });
560
- // OpenRouter
561
- const openrouterCred = getCredential("openrouter");
562
- if (openrouterCred) {
563
- try {
564
- const orModels = await listModels(openrouterCred.baseUrl || "https://openrouter.ai/api/v1", openrouterCred.apiKey);
565
- if (orModels.length > 0) {
566
- groups["OpenRouter"] = orModels.slice(0, 20).map(m => ({
567
- name: m,
568
- baseUrl: openrouterCred.baseUrl || "https://openrouter.ai/api/v1",
569
- apiKey: openrouterCred.apiKey,
570
- providerType: "openai",
571
- }));
572
- }
573
- }
574
- catch { /* skip */ }
575
- }
576
- providerEntries.push({ name: "OpenRouter", description: "200+ models (Claude, GPT, Gemini, Llama, etc.) — one login", authed: !!openrouterCred });
577
- // Qwen
578
- const qwenCred = getCredential("qwen");
579
- if (qwenCred) {
580
- groups["Qwen"] = ["qwen-max", "qwen-plus", "qwen-turbo"].map(m => ({
581
- name: m,
582
- baseUrl: qwenCred.baseUrl || "https://dashscope.aliyuncs.com/compatible-mode/v1",
583
- apiKey: qwenCred.apiKey,
584
- providerType: "openai",
585
- }));
586
- }
587
- providerEntries.push({ name: "Qwen", description: "Qwen 3.5, Qwen Coder — use your Qwen CLI login or API key", authed: !!qwenCred });
588
- // GitHub Copilot
589
- const copilotCred = getCredential("copilot");
590
- if (copilotCred) {
591
- groups["GitHub Copilot"] = ["gpt-4o", "claude-3.5-sonnet"].map(m => ({
592
- name: m,
593
- baseUrl: copilotCred.baseUrl || "https://api.githubcopilot.com",
594
- apiKey: copilotCred.apiKey,
595
- providerType: "openai",
596
- }));
597
- }
598
- providerEntries.push({ name: "GitHub Copilot", description: "Use your GitHub Copilot subscription", authed: !!copilotCred });
599
- // Show provider picker (step 1)
600
- if (providerEntries.length > 0) {
601
- setModelPickerGroups(groups);
602
- setProviderPicker(providerEntries);
603
- setProviderPickerIndex(0);
604
- setSelectedProvider(null);
605
- return;
606
- }
607
- addMsg("error", "No models available. Download one with /ollama pull or configure a provider.");
601
+ await openModelPicker();
608
602
  return;
609
603
  }
610
604
  if (trimmed.startsWith("/model ")) {
@@ -832,6 +826,7 @@ function App() {
832
826
  exit,
833
827
  refreshConnectionBanner,
834
828
  connectToProvider,
829
+ openModelPicker,
835
830
  handleSubmit,
836
831
  _require,
837
832
  });
@@ -30,4 +30,5 @@ export interface ConnectionContext {
30
30
  } | null) => void;
31
31
  setWizardScreen: (val: WizardScreen) => void;
32
32
  setWizardIndex: (val: number) => void;
33
+ openModelPicker: () => Promise<void>;
33
34
  }
@@ -75,11 +75,11 @@ export async function connectToProvider(isRetry, ctx) {
75
75
  !!getCredential("openrouter") || !!getCredential("qwen") ||
76
76
  !!getCredential("copilot");
77
77
  if (hasAnyCreds) {
78
- // User has auth'd before — skip wizard, go straight to /models picker
79
- info.push("✔ Found saved credentials. Use /models to pick a model and start coding.");
78
+ // User has auth'd before — skip wizard and go straight to the model picker
79
+ info.push("✔ Found saved credentials. Opening model picker...");
80
80
  ctx.setConnectionInfo([...info]);
81
81
  ctx.setReady(true);
82
- // The user will run /models, which now works without an agent
82
+ await ctx.openModelPicker();
83
83
  return;
84
84
  }
85
85
  // No creds found — show the setup wizard
@@ -104,9 +104,10 @@ function handleLoginMethodPicker(inputChar, key, ctx) {
104
104
  ctx.setLoading(true);
105
105
  ctx.setSpinnerMsg("Waiting for authorization...");
106
106
  openRouterOAuth((msg) => ctx.addMsg("info", msg))
107
- .then(() => {
108
- ctx.addMsg("info", `✅ OpenRouter authenticated! Access to 200+ models.`);
107
+ .then(async () => {
108
+ ctx.addMsg("info", `✅ OpenRouter authenticated! Access to 200+ models.\n Opening model picker...`);
109
109
  ctx.setLoading(false);
110
+ await ctx.openModelPicker();
110
111
  })
111
112
  .catch((err) => { ctx.addMsg("error", `OAuth failed: ${err.message}`); ctx.setLoading(false); });
112
113
  }
@@ -115,9 +116,10 @@ function handleLoginMethodPicker(inputChar, key, ctx) {
115
116
  ctx.setLoading(true);
116
117
  ctx.setSpinnerMsg("Waiting for Anthropic authorization...");
117
118
  loginAnthropicOAuth((msg) => ctx.addMsg("info", msg))
118
- .then((cred) => {
119
- ctx.addMsg("info", `✅ Anthropic authenticated! (${cred.label})\n Next: type /models to pick a model`);
119
+ .then(async (cred) => {
120
+ ctx.addMsg("info", `✅ Anthropic authenticated! (${cred.label})\n Opening model picker...`);
120
121
  ctx.setLoading(false);
122
+ await ctx.openModelPicker();
121
123
  })
122
124
  .catch((err) => {
123
125
  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`);
@@ -128,7 +130,8 @@ function handleLoginMethodPicker(inputChar, key, ctx) {
128
130
  // Try cached Codex token first as a quick path
129
131
  const imported = importCodexToken((msg) => ctx.addMsg("info", msg));
130
132
  if (imported) {
131
- ctx.addMsg("info", `✅ Imported Codex credentials! (${imported.label})`);
133
+ ctx.addMsg("info", `✅ Imported Codex credentials! (${imported.label})\n Opening model picker...`);
134
+ void ctx.openModelPicker();
132
135
  }
133
136
  else {
134
137
  // Primary flow: browser OAuth
@@ -136,9 +139,10 @@ function handleLoginMethodPicker(inputChar, key, ctx) {
136
139
  ctx.setLoading(true);
137
140
  ctx.setSpinnerMsg("Waiting for OpenAI authorization...");
138
141
  loginOpenAICodexOAuth((msg) => ctx.addMsg("info", msg))
139
- .then((cred) => {
140
- ctx.addMsg("info", `✅ OpenAI authenticated! (${cred.label})\n Next: type /models to pick a model`);
142
+ .then(async (cred) => {
143
+ ctx.addMsg("info", `✅ OpenAI authenticated! (${cred.label})\n Opening model picker...`);
141
144
  ctx.setLoading(false);
145
+ await ctx.openModelPicker();
142
146
  })
143
147
  .catch((err) => {
144
148
  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`);
@@ -149,7 +153,8 @@ function handleLoginMethodPicker(inputChar, key, ctx) {
149
153
  else if (method === "cached-token" && providerId === "qwen") {
150
154
  const imported = importQwenToken((msg) => ctx.addMsg("info", msg));
151
155
  if (imported) {
152
- ctx.addMsg("info", `✅ Imported Qwen credentials! (${imported.label})`);
156
+ ctx.addMsg("info", `✅ Imported Qwen credentials! (${imported.label})\n Opening model picker...`);
157
+ void ctx.openModelPicker();
153
158
  }
154
159
  else {
155
160
  ctx.addMsg("info", "No Qwen CLI found. Install Qwen CLI and sign in first.");
@@ -160,7 +165,7 @@ function handleLoginMethodPicker(inputChar, key, ctx) {
160
165
  ctx.setLoading(true);
161
166
  ctx.setSpinnerMsg("Waiting for GitHub authorization...");
162
167
  copilotDeviceFlow((msg) => ctx.addMsg("info", msg))
163
- .then(() => { ctx.addMsg("info", `✅ GitHub Copilot authenticated!\n Next: type /models to pick a model`); ctx.setLoading(false); })
168
+ .then(async () => { ctx.addMsg("info", `✅ GitHub Copilot authenticated!\n Opening model picker...`); ctx.setLoading(false); await ctx.openModelPicker(); })
164
169
  .catch((err) => { ctx.addMsg("error", `Copilot auth failed: ${err.message}`); ctx.setLoading(false); });
165
170
  }
166
171
  else if (method === "api-key") {
@@ -196,7 +201,7 @@ function handleLoginPicker(_inputChar, key, ctx) {
196
201
  ctx.setLoading(true);
197
202
  ctx.setSpinnerMsg("Waiting for authorization...");
198
203
  openRouterOAuth((msg) => ctx.addMsg("info", msg))
199
- .then(() => { ctx.addMsg("info", `✅ OpenRouter authenticated! Access to 200+ models.\n Next: type /models to pick a model`); ctx.setLoading(false); })
204
+ .then(async () => { ctx.addMsg("info", `✅ OpenRouter authenticated! Access to 200+ models.\n Opening model picker...`); ctx.setLoading(false); await ctx.openModelPicker(); })
200
205
  .catch((err) => { ctx.addMsg("error", `OAuth failed: ${err.message}`); ctx.setLoading(false); });
201
206
  }
202
207
  else if (methods[0] === "device-flow") {
@@ -205,7 +210,7 @@ function handleLoginPicker(_inputChar, key, ctx) {
205
210
  ctx.setLoading(true);
206
211
  ctx.setSpinnerMsg("Waiting for GitHub authorization...");
207
212
  copilotDeviceFlow((msg) => ctx.addMsg("info", msg))
208
- .then(() => { ctx.addMsg("info", `✅ GitHub Copilot authenticated!\n Next: type /models to pick a model`); ctx.setLoading(false); })
213
+ .then(async () => { ctx.addMsg("info", `✅ GitHub Copilot authenticated!\n Opening model picker...`); ctx.setLoading(false); await ctx.openModelPicker(); })
209
214
  .catch((err) => { ctx.addMsg("error", `Copilot auth failed: ${err.message}`); ctx.setLoading(false); });
210
215
  }
211
216
  else if (methods[0] === "api-key") {
@@ -387,6 +392,9 @@ function handleProviderPicker(_inputChar, key, ctx) {
387
392
  }
388
393
  if (key.escape) {
389
394
  ctx.setProviderPicker(null);
395
+ if (!ctx.agent) {
396
+ ctx.addMsg("info", "Model selection cancelled. Use /login for cloud providers or choose local setup from the startup menu.");
397
+ }
390
398
  return true;
391
399
  }
392
400
  if (key.return) {
@@ -482,13 +490,13 @@ function handleOllamaPullPicker(_inputChar, key, ctx) {
482
490
  if (!ctx.ollamaPullPicker)
483
491
  return false;
484
492
  const pullModels = [
485
- { id: "qwen2.5-coder:7b", name: "Qwen 2.5 Coder 7B", size: "5 GB", desc: "Best balance of speed & quality" },
486
- { id: "qwen2.5-coder:14b", name: "Qwen 2.5 Coder 14B", size: "9 GB", desc: "Higher quality, needs 16GB+ RAM" },
487
- { id: "qwen2.5-coder:3b", name: "Qwen 2.5 Coder 3B", size: "2 GB", desc: "\u26A0\uFE0F Basic \u2014 may struggle with tool calls" },
488
- { id: "qwen2.5-coder:32b", name: "Qwen 2.5 Coder 32B", size: "20 GB", desc: "Premium quality, needs 48GB+" },
489
- { id: "deepseek-coder-v2:16b", name: "DeepSeek Coder V2", size: "9 GB", desc: "Strong alternative" },
490
- { id: "codellama:7b", name: "CodeLlama 7B", size: "4 GB", desc: "Meta's coding model" },
491
- { id: "starcoder2:7b", name: "StarCoder2 7B", size: "4 GB", desc: "Code completion focused" },
493
+ { id: "qwen2.5-coder:14b", name: "Qwen 2.5 Coder 14B", size: "9 GB", desc: "Recommended default for coding if your machine can handle it" },
494
+ { id: "deepseek-coder-v2:16b", name: "DeepSeek Coder V2 16B", size: "9 GB", desc: "Strong higher-quality alternative" },
495
+ { id: "qwen2.5-coder:7b", name: "Qwen 2.5 Coder 7B", size: "5 GB", desc: "Fallback for mid-range machines" },
496
+ { id: "qwen2.5-coder:32b", name: "Qwen 2.5 Coder 32B", size: "20 GB", desc: "Premium quality, needs lots of RAM" },
497
+ { id: "codellama:7b", name: "CodeLlama 7B", size: "4 GB", desc: "Older fallback coding model" },
498
+ { id: "starcoder2:7b", name: "StarCoder2 7B", size: "4 GB", desc: "Completion-focused fallback" },
499
+ { id: "qwen2.5-coder:3b", name: "Qwen 2.5 Coder 3B", size: "2 GB", desc: "⚠️ Last resort — may struggle with tool calls" },
492
500
  ];
493
501
  if (key.upArrow) {
494
502
  ctx.setOllamaPullPickerIndex((prev) => (prev - 1 + pullModels.length) % pullModels.length);
@@ -74,13 +74,13 @@ export function OllamaDeletePicker({ models, selectedIndex, colors }) {
74
74
  }
75
75
  // ── Ollama Pull Picker ──
76
76
  const PULL_MODELS = [
77
- { id: "qwen2.5-coder:7b", name: "Qwen 2.5 Coder 7B", size: "5 GB", desc: "Best balance of speed & quality" },
78
- { id: "qwen2.5-coder:14b", name: "Qwen 2.5 Coder 14B", size: "9 GB", desc: "Higher quality, needs 16GB+ RAM" },
79
- { id: "qwen2.5-coder:3b", name: "Qwen 2.5 Coder 3B", size: "2 GB", desc: "\u26A0\uFE0F Basic \u2014 may struggle with tool calls" },
80
- { id: "qwen2.5-coder:32b", name: "Qwen 2.5 Coder 32B", size: "20 GB", desc: "Premium, needs 48GB+" },
81
- { id: "deepseek-coder-v2:16b", name: "DeepSeek Coder V2", size: "9 GB", desc: "Strong alternative" },
82
- { id: "codellama:7b", name: "CodeLlama 7B", size: "4 GB", desc: "Meta's coding model" },
83
- { id: "starcoder2:7b", name: "StarCoder2 7B", size: "4 GB", desc: "Code completion focused" },
77
+ { id: "qwen2.5-coder:14b", name: "Qwen 2.5 Coder 14B", size: "9 GB", desc: "Recommended default for coding if your machine can handle it" },
78
+ { id: "deepseek-coder-v2:16b", name: "DeepSeek Coder V2 16B", size: "9 GB", desc: "Strong higher-quality alternative" },
79
+ { id: "qwen2.5-coder:7b", name: "Qwen 2.5 Coder 7B", size: "5 GB", desc: "Fallback for mid-range machines" },
80
+ { id: "qwen2.5-coder:32b", name: "Qwen 2.5 Coder 32B", size: "20 GB", desc: "Premium quality, needs lots of RAM" },
81
+ { id: "codellama:7b", name: "CodeLlama 7B", size: "4 GB", desc: "Older fallback coding model" },
82
+ { id: "starcoder2:7b", name: "StarCoder2 7B", size: "4 GB", desc: "Completion-focused fallback" },
83
+ { id: "qwen2.5-coder:3b", name: "Qwen 2.5 Coder 3B", size: "2 GB", desc: "⚠️ Last resort — may struggle with tool calls" },
84
84
  ];
85
85
  export function OllamaPullPicker({ selectedIndex, colors }) {
86
86
  return (_jsxs(Box, { flexDirection: "column", borderStyle: "single", borderColor: colors.border, paddingX: 1, marginBottom: 0, children: [_jsx(Text, { bold: true, color: colors.secondary, children: "Download which model?" }), _jsx(Text, { children: "" }), PULL_MODELS.map((m, i) => (_jsxs(Text, { children: [" ", i === selectedIndex ? _jsx(Text, { color: colors.primary, bold: true, children: "▸ " }) : " ", _jsx(Text, { color: i === selectedIndex ? colors.primary : undefined, bold: true, children: m.name }), _jsxs(Text, { color: colors.muted, children: [" · ", m.size, " · ", m.desc] })] }, m.id))), _jsx(Text, { children: "" }), _jsx(Text, { dimColor: true, children: " ↑↓ navigate · Enter to download · Esc cancel" })] }));
@@ -113,7 +113,7 @@ export function WizardModels({ wizardIndex, wizardHardware, wizardModels, colors
113
113
  return (_jsxs(Box, { flexDirection: "column", borderStyle: "single", borderColor: colors.border, paddingX: 1, marginBottom: 0, children: [_jsx(Text, { bold: true, color: colors.secondary, children: "Your hardware:" }), _jsxs(Text, { color: colors.muted, children: [" CPU: ", wizardHardware.cpu.name, " (", wizardHardware.cpu.cores, " cores)"] }), _jsxs(Text, { color: colors.muted, children: [" RAM: ", formatBytes(wizardHardware.ram)] }), wizardHardware.gpu ? (_jsxs(Text, { color: colors.muted, children: [" GPU: ", wizardHardware.gpu.name, wizardHardware.gpu.vram > 0 ? ` (${formatBytes(wizardHardware.gpu.vram)})` : ""] })) : (_jsx(Text, { color: colors.muted, children: " GPU: none detected" })), !isLlmfitAvailable() && (_jsx(Text, { dimColor: true, children: " Tip: Install llmfit for smarter recommendations: brew install llmfit" })), _jsx(Text, { children: "" }), _jsx(Text, { bold: true, color: colors.secondary, children: "Recommended models:" }), _jsx(Text, { children: "" }), wizardModels.map((m, i) => (_jsxs(Text, { children: [i === wizardIndex ? _jsx(Text, { color: colors.suggestion, bold: true, children: " \u25B8 " }) : _jsx(Text, { children: " " }), _jsxs(Text, { children: [getFitIcon(m.fit), " "] }), _jsx(Text, { color: i === wizardIndex ? colors.suggestion : colors.primary, bold: true, children: m.name }), _jsxs(Text, { color: colors.muted, children: [" ~", m.size, " GB \u00B7 ", m.quality === "best" ? "Best" : m.quality === "great" ? "Great" : "Good", " quality \u00B7 ", m.speed] })] }, m.ollamaId))), wizardModels.length === 0 && (_jsx(Text, { color: colors.error, children: " No suitable models found for your hardware." })), _jsx(Text, { children: "" }), _jsx(Text, { dimColor: true, children: " \u2191\u2193 navigate \u00B7 Enter to install \u00B7 Esc back" })] }));
114
114
  }
115
115
  export function WizardInstallOllama({ wizardHardware, colors }) {
116
- return (_jsxs(Box, { flexDirection: "column", borderStyle: "single", borderColor: colors.warning, paddingX: 1, marginBottom: 0, children: [_jsx(Text, { bold: true, color: colors.warning, children: "Ollama is required for local models." }), _jsx(Text, { children: "" }), _jsx(Text, { color: colors.primary, children: " Press Enter to install Ollama automatically" }), _jsxs(Text, { dimColor: true, children: [" Or install manually: ", _jsx(Text, { children: getOllamaInstallCommand(wizardHardware?.os ?? "linux") })] }), _jsx(Text, { children: "" }), _jsx(Text, { dimColor: true, children: " Enter to install · Esc to go back" })] }));
116
+ return (_jsxs(Box, { flexDirection: "column", borderStyle: "single", borderColor: colors.warning, paddingX: 1, marginBottom: 0, children: [_jsx(Text, { bold: true, color: colors.warning, children: "Ollama is required for local models." }), _jsx(Text, { children: "" }), _jsx(Text, { color: colors.primary, children: " Press Enter to install Ollama automatically" }), _jsxs(Text, { dimColor: true, children: [" Or install manually: ", _jsx(Text, { children: getOllamaInstallCommand(wizardHardware?.os ?? "linux") })] }), _jsx(Text, { children: "" }), _jsx(Text, { dimColor: true, children: " Enter to install · Esc back to model list" })] }));
117
117
  }
118
118
  export function WizardPulling({ wizardSelectedModel, wizardPullProgress, wizardPullError, colors }) {
119
119
  return (_jsx(Box, { flexDirection: "column", borderStyle: "single", borderColor: colors.border, paddingX: 1, marginBottom: 0, children: wizardPullError ? (_jsxs(_Fragment, { children: [_jsxs(Text, { color: colors.error, bold: true, children: [" \u274C Error: ", wizardPullError] }), _jsx(Text, { children: "" }), _jsx(Text, { dimColor: true, children: " Press Enter to retry \u00B7 Esc to go back" })] })) : wizardPullProgress ? (_jsxs(_Fragment, { children: [_jsxs(Text, { bold: true, color: colors.secondary, children: [" ", wizardSelectedModel ? `Downloading ${wizardSelectedModel.name}...` : wizardPullProgress?.status || "Working..."] }), wizardPullProgress.status === "downloading" || wizardPullProgress.percent > 0 ? (_jsx(_Fragment, { children: _jsxs(Text, { children: [" ", _jsxs(Text, { color: colors.primary, children: ["\u2588".repeat(Math.floor(wizardPullProgress.percent / 5)), "\u2591".repeat(20 - Math.floor(wizardPullProgress.percent / 5))] }), " ", _jsxs(Text, { bold: true, children: [wizardPullProgress.percent, "%"] }), wizardPullProgress.completed != null && wizardPullProgress.total != null ? (_jsxs(Text, { color: colors.muted, children: [" \u00B7 ", formatBytes(wizardPullProgress.completed), " / ", formatBytes(wizardPullProgress.total)] })) : null] }) })) : (_jsxs(Text, { color: colors.muted, children: [" ", wizardPullProgress.status, "..."] }))] })) : null }));
@@ -23,5 +23,6 @@ export interface WizardContext {
23
23
  setSpinnerMsg: (val: string) => void;
24
24
  addMsg: (type: "user" | "response" | "tool" | "tool-result" | "error" | "info", text: string) => void;
25
25
  connectToProvider: (isRetry: boolean) => Promise<void>;
26
+ openModelPicker: () => Promise<void>;
26
27
  _require: NodeRequire;
27
28
  }
package/dist/ui/wizard.js CHANGED
@@ -35,9 +35,10 @@ export function handleWizardScreen(_inputChar, key, ctx) {
35
35
  ctx.setLoading(true);
36
36
  ctx.setSpinnerMsg("Waiting for authorization...");
37
37
  openRouterOAuth((msg) => ctx.addMsg("info", msg))
38
- .then(() => {
39
- ctx.addMsg("info", "✅ OpenRouter authenticated! Use /connect to connect.");
38
+ .then(async () => {
39
+ ctx.addMsg("info", "✅ OpenRouter authenticated! Opening model picker...");
40
40
  ctx.setLoading(false);
41
+ await ctx.openModelPicker();
41
42
  })
42
43
  .catch((err) => { ctx.addMsg("error", `OAuth failed: ${err.message}`); ctx.setLoading(false); });
43
44
  }
@@ -45,10 +46,12 @@ export function handleWizardScreen(_inputChar, key, ctx) {
45
46
  ctx.setWizardScreen(null);
46
47
  ctx.setLoginPicker(true);
47
48
  ctx.setLoginPickerIndex(() => 0);
49
+ ctx.addMsg("info", "Choose a cloud provider to continue.");
48
50
  }
49
51
  else if (selected === "existing") {
50
52
  ctx.setWizardScreen(null);
51
- ctx.addMsg("info", "Start your LLM server, then type /connect to retry.");
53
+ ctx.addMsg("info", "Checking for your running server...");
54
+ void ctx.connectToProvider(true);
52
55
  }
53
56
  return true;
54
57
  }
@@ -196,6 +199,7 @@ function startPullFlow(ctx, selected) {
196
199
  return;
197
200
  }
198
201
  }
202
+ ctx.addMsg("info", `Downloading ${selected.name}. This can take a while on first run depending on your internet and disk speed.`);
199
203
  await pullModel(selected.ollamaId, (p) => {
200
204
  ctx.setWizardPullProgress(p);
201
205
  });
@@ -1,14 +1,24 @@
1
1
  import { execSync } from "child_process";
2
2
  const MODELS = [
3
3
  {
4
- name: "Qwen 2.5 Coder 3B",
5
- ollamaId: "qwen2.5-coder:3b",
6
- size: 2,
7
- ramRequired: 8,
8
- vramOptimal: 4,
9
- description: "\u26A0\uFE0F May not support tool calling well",
10
- speed: "~60 tok/s on M1",
11
- quality: "limited",
4
+ name: "Qwen 2.5 Coder 14B",
5
+ ollamaId: "qwen2.5-coder:14b",
6
+ size: 9,
7
+ ramRequired: 24,
8
+ vramOptimal: 12,
9
+ description: "Recommended default for coding when your machine can handle it",
10
+ speed: "~25 tok/s on M1 Pro",
11
+ quality: "best",
12
+ },
13
+ {
14
+ name: "DeepSeek Coder V2 16B",
15
+ ollamaId: "deepseek-coder-v2:16b",
16
+ size: 9,
17
+ ramRequired: 24,
18
+ vramOptimal: 12,
19
+ description: "Strong higher-quality alternative for coding",
20
+ speed: "~30 tok/s on M1 Pro",
21
+ quality: "great",
12
22
  },
13
23
  {
14
24
  name: "Qwen 2.5 Coder 7B",
@@ -16,20 +26,10 @@ const MODELS = [
16
26
  size: 5,
17
27
  ramRequired: 16,
18
28
  vramOptimal: 8,
19
- description: "Sweet spot for most machines",
29
+ description: "Fallback for mid-range machines when 14B is too heavy",
20
30
  speed: "~45 tok/s on M1",
21
31
  quality: "great",
22
32
  },
23
- {
24
- name: "Qwen 2.5 Coder 14B",
25
- ollamaId: "qwen2.5-coder:14b",
26
- size: 9,
27
- ramRequired: 32,
28
- vramOptimal: 16,
29
- description: "High quality coding",
30
- speed: "~25 tok/s on M1 Pro",
31
- quality: "best",
32
- },
33
33
  {
34
34
  name: "Qwen 2.5 Coder 32B",
35
35
  ollamaId: "qwen2.5-coder:32b",
@@ -40,23 +40,13 @@ const MODELS = [
40
40
  speed: "~12 tok/s on M1 Max",
41
41
  quality: "best",
42
42
  },
43
- {
44
- name: "DeepSeek Coder V2 16B",
45
- ollamaId: "deepseek-coder-v2:16b",
46
- size: 9,
47
- ramRequired: 32,
48
- vramOptimal: 16,
49
- description: "Strong alternative for coding",
50
- speed: "~30 tok/s on M1 Pro",
51
- quality: "great",
52
- },
53
43
  {
54
44
  name: "CodeLlama 7B",
55
45
  ollamaId: "codellama:7b",
56
46
  size: 4,
57
47
  ramRequired: 16,
58
48
  vramOptimal: 8,
59
- description: "Meta's coding model",
49
+ description: "Older fallback coding model",
60
50
  speed: "~40 tok/s on M1",
61
51
  quality: "good",
62
52
  },
@@ -66,10 +56,20 @@ const MODELS = [
66
56
  size: 4,
67
57
  ramRequired: 16,
68
58
  vramOptimal: 8,
69
- description: "Good for code completion",
59
+ description: "Completion-focused fallback",
70
60
  speed: "~40 tok/s on M1",
71
61
  quality: "good",
72
62
  },
63
+ {
64
+ name: "Qwen 2.5 Coder 3B",
65
+ ollamaId: "qwen2.5-coder:3b",
66
+ size: 2,
67
+ ramRequired: 8,
68
+ vramOptimal: 4,
69
+ description: "⚠️ Last-resort fallback — may struggle with tool calling",
70
+ speed: "~60 tok/s on M1",
71
+ quality: "limited",
72
+ },
73
73
  ];
74
74
  function scoreModel(model, ramGB, vramGB) {
75
75
  if (ramGB < model.ramRequired)
@@ -86,6 +86,23 @@ function scoreModel(model, ramGB, vramGB) {
86
86
  }
87
87
  const qualityOrder = { best: 3, great: 2, good: 1, limited: 0 };
88
88
  const fitOrder = { perfect: 4, good: 3, tight: 2, skip: 1 };
89
+ function recommendationPriority(model) {
90
+ if (model.ollamaId === "qwen2.5-coder:14b")
91
+ return 100;
92
+ if (model.ollamaId === "deepseek-coder-v2:16b")
93
+ return 95;
94
+ if (model.ollamaId === "qwen2.5-coder:7b")
95
+ return 80;
96
+ if (model.ollamaId === "qwen2.5-coder:32b")
97
+ return 75;
98
+ if (model.ollamaId === "codellama:7b")
99
+ return 60;
100
+ if (model.ollamaId === "starcoder2:7b")
101
+ return 55;
102
+ if (model.ollamaId === "qwen2.5-coder:3b")
103
+ return 10;
104
+ return 0;
105
+ }
89
106
  export function getRecommendations(hardware) {
90
107
  const ramGB = hardware.ram / (1024 * 1024 * 1024);
91
108
  const vramGB = hardware.gpu?.vram ? hardware.gpu.vram / (1024 * 1024 * 1024) : 0;
@@ -95,11 +112,14 @@ export function getRecommendations(hardware) {
95
112
  ...m,
96
113
  fit: scoreModel(m, ramGB, effectiveVRAM),
97
114
  }));
98
- // Sort: perfect first, then by quality descending
115
+ // Sort: fit first, then strongly prefer better coding defaults over tiny fallback models
99
116
  scored.sort((a, b) => {
100
117
  const fitDiff = (fitOrder[b.fit] ?? 0) - (fitOrder[a.fit] ?? 0);
101
118
  if (fitDiff !== 0)
102
119
  return fitDiff;
120
+ const priorityDiff = recommendationPriority(b) - recommendationPriority(a);
121
+ if (priorityDiff !== 0)
122
+ return priorityDiff;
103
123
  return (qualityOrder[b.quality] ?? 0) - (qualityOrder[a.quality] ?? 0);
104
124
  });
105
125
  return scored;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "codemaxxing",
3
- "version": "1.1.3",
3
+ "version": "1.1.4",
4
4
  "description": "Open-source terminal coding agent. Connect any LLM. Max your code.",
5
5
  "main": "dist/index.js",
6
6
  "bin": {