codemaxxing 0.3.0 → 0.4.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 +16 -16
- package/dist/agent.d.ts +7 -0
- package/dist/agent.js +51 -2
- package/dist/exec.js +10 -0
- package/dist/index.js +322 -4
- package/dist/utils/hardware.d.ts +17 -0
- package/dist/utils/hardware.js +120 -0
- package/dist/utils/mcp.d.ts +55 -0
- package/dist/utils/mcp.js +251 -0
- package/dist/utils/models.d.ts +17 -0
- package/dist/utils/models.js +113 -0
- package/dist/utils/ollama.d.ts +22 -0
- package/dist/utils/ollama.js +121 -0
- package/package.json +2 -1
- package/src/agent.ts +55 -2
- package/src/exec.ts +12 -0
- package/src/index.tsx +413 -2
- package/src/utils/hardware.ts +131 -0
- package/src/utils/mcp.ts +307 -0
- package/src/utils/models.ts +137 -0
- package/src/utils/ollama.ts +137 -0
package/README.md
CHANGED
|
@@ -12,34 +12,34 @@ Open-source terminal coding agent. Connect **any** LLM — local or remote — a
|
|
|
12
12
|
|
|
13
13
|
Every coding agent locks you into their API. Codemaxxing doesn't. Run it with LM Studio, Ollama, OpenRouter, OpenAI, or any OpenAI-compatible endpoint. Your machine, your model, your rules.
|
|
14
14
|
|
|
15
|
-
##
|
|
15
|
+
## Install
|
|
16
16
|
|
|
17
|
-
**
|
|
17
|
+
**If you have Node.js:**
|
|
18
18
|
```bash
|
|
19
|
-
|
|
19
|
+
npm install -g codemaxxing
|
|
20
20
|
```
|
|
21
21
|
|
|
22
|
-
**
|
|
23
|
-
|
|
24
|
-
|
|
22
|
+
**If you don't have Node.js:**
|
|
23
|
+
|
|
24
|
+
The one-line installers below will install Node.js first, then codemaxxing.
|
|
25
|
+
|
|
26
|
+
*Linux / macOS:*
|
|
27
|
+
```bash
|
|
28
|
+
bash -c "$(curl -fsSL https://raw.githubusercontent.com/MarcosV6/codemaxxing/main/install.sh)"
|
|
25
29
|
```
|
|
26
30
|
|
|
27
|
-
|
|
31
|
+
*Windows (CMD as Administrator):*
|
|
28
32
|
```
|
|
29
33
|
curl -fsSL -o %TEMP%\install-codemaxxing.bat https://raw.githubusercontent.com/MarcosV6/codemaxxing/main/install.bat && %TEMP%\install-codemaxxing.bat
|
|
30
34
|
```
|
|
31
35
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
**Prerequisites:** [Node.js](https://nodejs.org) 20 or later.
|
|
37
|
-
|
|
38
|
-
**NPM:**
|
|
39
|
-
```bash
|
|
40
|
-
npm install -g codemaxxing
|
|
36
|
+
*Windows (PowerShell as Administrator):*
|
|
37
|
+
```powershell
|
|
38
|
+
curl -fsSL -o $env:TEMP\install-codemaxxing.bat https://raw.githubusercontent.com/MarcosV6/codemaxxing/main/install.bat; & $env:TEMP\install-codemaxxing.bat
|
|
41
39
|
```
|
|
42
40
|
|
|
41
|
+
> **Windows note:** If Node.js was just installed, you may need to close and reopen your terminal, then run `npm install -g codemaxxing` manually. This is a Windows PATH limitation.
|
|
42
|
+
|
|
43
43
|
## Updating
|
|
44
44
|
|
|
45
45
|
```bash
|
package/dist/agent.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { type ConnectedServer } from "./utils/mcp.js";
|
|
1
2
|
import type { ProviderConfig } from "./config.js";
|
|
2
3
|
export interface AgentOptions {
|
|
3
4
|
provider: ProviderConfig;
|
|
@@ -13,6 +14,7 @@ export interface AgentOptions {
|
|
|
13
14
|
onContextCompressed?: (oldTokens: number, newTokens: number) => void;
|
|
14
15
|
onArchitectPlan?: (plan: string) => void;
|
|
15
16
|
onLintResult?: (file: string, errors: string) => void;
|
|
17
|
+
onMCPStatus?: (server: string, status: string) => void;
|
|
16
18
|
contextCompressionThreshold?: number;
|
|
17
19
|
}
|
|
18
20
|
export declare class CodingAgent {
|
|
@@ -41,6 +43,7 @@ export declare class CodingAgent {
|
|
|
41
43
|
private architectModel;
|
|
42
44
|
private autoLintEnabled;
|
|
43
45
|
private detectedLinter;
|
|
46
|
+
private mcpServers;
|
|
44
47
|
constructor(options: AgentOptions);
|
|
45
48
|
/**
|
|
46
49
|
* Initialize the agent — call this after constructor to build async context
|
|
@@ -124,5 +127,9 @@ export declare class CodingAgent {
|
|
|
124
127
|
* Run the architect model to generate a plan, then feed to editor model
|
|
125
128
|
*/
|
|
126
129
|
private architectChat;
|
|
130
|
+
getMCPServerCount(): number;
|
|
131
|
+
getMCPServers(): ConnectedServer[];
|
|
132
|
+
disconnectMCP(): Promise<void>;
|
|
133
|
+
reconnectMCP(): Promise<void>;
|
|
127
134
|
reset(): void;
|
|
128
135
|
}
|
package/dist/agent.js
CHANGED
|
@@ -6,6 +6,7 @@ import { buildProjectContext, getSystemPrompt, loadProjectRules } from "./utils/
|
|
|
6
6
|
import { isGitRepo, autoCommit } from "./utils/git.js";
|
|
7
7
|
import { buildSkillPrompts, getActiveSkillCount } from "./utils/skills.js";
|
|
8
8
|
import { createSession, saveMessage, updateTokenEstimate, updateSessionCost, loadMessages } from "./utils/sessions.js";
|
|
9
|
+
import { loadMCPConfig, connectToServers, disconnectAll, getAllMCPTools, parseMCPToolName, callMCPTool } from "./utils/mcp.js";
|
|
9
10
|
// Tools that can modify your project — require approval
|
|
10
11
|
const DANGEROUS_TOOLS = new Set(["write_file", "run_command"]);
|
|
11
12
|
// Cost per 1M tokens (input/output) for common models
|
|
@@ -78,6 +79,7 @@ export class CodingAgent {
|
|
|
78
79
|
architectModel = null;
|
|
79
80
|
autoLintEnabled = true;
|
|
80
81
|
detectedLinter = null;
|
|
82
|
+
mcpServers = [];
|
|
81
83
|
constructor(options) {
|
|
82
84
|
this.options = options;
|
|
83
85
|
this.providerType = options.provider.type || "openai";
|
|
@@ -113,6 +115,15 @@ export class CodingAgent {
|
|
|
113
115
|
this.systemPrompt = await getSystemPrompt(context, skillPrompts, rules?.content ?? "");
|
|
114
116
|
// Detect project linter
|
|
115
117
|
this.detectedLinter = detectLinter(this.cwd);
|
|
118
|
+
// Connect to MCP servers
|
|
119
|
+
const mcpConfig = loadMCPConfig(this.cwd);
|
|
120
|
+
if (Object.keys(mcpConfig.mcpServers).length > 0) {
|
|
121
|
+
this.mcpServers = await connectToServers(mcpConfig, this.options.onMCPStatus);
|
|
122
|
+
if (this.mcpServers.length > 0) {
|
|
123
|
+
const mcpTools = getAllMCPTools(this.mcpServers);
|
|
124
|
+
this.tools = [...FILE_TOOLS, ...mcpTools];
|
|
125
|
+
}
|
|
126
|
+
}
|
|
116
127
|
this.messages = [
|
|
117
128
|
{ role: "system", content: this.systemPrompt },
|
|
118
129
|
];
|
|
@@ -305,7 +316,15 @@ export class CodingAgent {
|
|
|
305
316
|
}
|
|
306
317
|
}
|
|
307
318
|
}
|
|
308
|
-
|
|
319
|
+
// Route to MCP or built-in tool
|
|
320
|
+
const mcpParsed = parseMCPToolName(toolCall.name);
|
|
321
|
+
let result;
|
|
322
|
+
if (mcpParsed) {
|
|
323
|
+
result = await callMCPTool(mcpParsed.serverName, mcpParsed.toolName, args);
|
|
324
|
+
}
|
|
325
|
+
else {
|
|
326
|
+
result = await executeTool(toolCall.name, args, this.cwd);
|
|
327
|
+
}
|
|
309
328
|
this.options.onToolResult?.(toolCall.name, result);
|
|
310
329
|
// Auto-commit after successful write_file (only if enabled)
|
|
311
330
|
if (this.gitEnabled && this.autoCommitEnabled && toolCall.name === "write_file" && result.startsWith("✅")) {
|
|
@@ -502,7 +521,15 @@ export class CodingAgent {
|
|
|
502
521
|
}
|
|
503
522
|
}
|
|
504
523
|
}
|
|
505
|
-
|
|
524
|
+
// Route to MCP or built-in tool
|
|
525
|
+
const mcpParsed = parseMCPToolName(toolCall.name);
|
|
526
|
+
let result;
|
|
527
|
+
if (mcpParsed) {
|
|
528
|
+
result = await callMCPTool(mcpParsed.serverName, mcpParsed.toolName, args);
|
|
529
|
+
}
|
|
530
|
+
else {
|
|
531
|
+
result = await executeTool(toolCall.name, args, this.cwd);
|
|
532
|
+
}
|
|
506
533
|
this.options.onToolResult?.(toolCall.name, result);
|
|
507
534
|
// Auto-commit after successful write_file
|
|
508
535
|
if (this.gitEnabled && this.autoCommitEnabled && toolCall.name === "write_file" && result.startsWith("✅")) {
|
|
@@ -738,6 +765,28 @@ export class CodingAgent {
|
|
|
738
765
|
const editorPrompt = `## Architect Plan\n${plan}\n\n## Original Request\n${userMessage}\n\nExecute the plan above. Follow it step by step.`;
|
|
739
766
|
return this.chat(editorPrompt);
|
|
740
767
|
}
|
|
768
|
+
getMCPServerCount() {
|
|
769
|
+
return this.mcpServers.length;
|
|
770
|
+
}
|
|
771
|
+
getMCPServers() {
|
|
772
|
+
return this.mcpServers;
|
|
773
|
+
}
|
|
774
|
+
async disconnectMCP() {
|
|
775
|
+
await disconnectAll();
|
|
776
|
+
this.mcpServers = [];
|
|
777
|
+
this.tools = FILE_TOOLS;
|
|
778
|
+
}
|
|
779
|
+
async reconnectMCP() {
|
|
780
|
+
await this.disconnectMCP();
|
|
781
|
+
const mcpConfig = loadMCPConfig(this.cwd);
|
|
782
|
+
if (Object.keys(mcpConfig.mcpServers).length > 0) {
|
|
783
|
+
this.mcpServers = await connectToServers(mcpConfig, this.options.onMCPStatus);
|
|
784
|
+
if (this.mcpServers.length > 0) {
|
|
785
|
+
const mcpTools = getAllMCPTools(this.mcpServers);
|
|
786
|
+
this.tools = [...FILE_TOOLS, ...mcpTools];
|
|
787
|
+
}
|
|
788
|
+
}
|
|
789
|
+
}
|
|
741
790
|
reset() {
|
|
742
791
|
const systemMsg = this.messages[0];
|
|
743
792
|
this.messages = [systemMsg];
|
package/dist/exec.js
CHANGED
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
*/
|
|
7
7
|
import { CodingAgent } from "./agent.js";
|
|
8
8
|
import { loadConfig, applyOverrides, detectLocalProvider } from "./config.js";
|
|
9
|
+
import { disconnectAll } from "./utils/mcp.js";
|
|
9
10
|
function parseExecArgs(argv) {
|
|
10
11
|
const args = {
|
|
11
12
|
prompt: "",
|
|
@@ -124,9 +125,16 @@ export async function runExec(argv) {
|
|
|
124
125
|
process.stderr.write(`⚠ Denied ${name} (use --auto-approve to allow)\n`);
|
|
125
126
|
return "no";
|
|
126
127
|
},
|
|
128
|
+
onMCPStatus: (server, status) => {
|
|
129
|
+
process.stderr.write(`MCP ${server}: ${status}\n`);
|
|
130
|
+
},
|
|
127
131
|
});
|
|
128
132
|
try {
|
|
129
133
|
await agent.init();
|
|
134
|
+
const mcpCount = agent.getMCPServerCount();
|
|
135
|
+
if (mcpCount > 0) {
|
|
136
|
+
process.stderr.write(`MCP: ${mcpCount} server${mcpCount > 1 ? "s" : ""} connected\n`);
|
|
137
|
+
}
|
|
130
138
|
await agent.send(args.prompt);
|
|
131
139
|
if (!args.json) {
|
|
132
140
|
// Ensure newline at end of output
|
|
@@ -142,9 +150,11 @@ export async function runExec(argv) {
|
|
|
142
150
|
};
|
|
143
151
|
process.stdout.write(JSON.stringify(output, null, 2) + "\n");
|
|
144
152
|
}
|
|
153
|
+
await disconnectAll();
|
|
145
154
|
process.exit(hasChanges ? 0 : 2);
|
|
146
155
|
}
|
|
147
156
|
catch (err) {
|
|
157
|
+
await disconnectAll();
|
|
148
158
|
process.stderr.write(`Error: ${err.message}\n`);
|
|
149
159
|
if (args.json) {
|
|
150
160
|
process.stdout.write(JSON.stringify({ error: err.message }, null, 2) + "\n");
|
package/dist/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
3
3
|
import React, { useState, useEffect, useCallback } from "react";
|
|
4
4
|
import { render, Box, Text, useInput, useApp, useStdout } from "ink";
|
|
5
5
|
import { EventEmitter } from "events";
|
|
@@ -12,6 +12,10 @@ import { isGitRepo, getBranch, getStatus, getDiff, undoLastCommit } from "./util
|
|
|
12
12
|
import { getTheme, listThemes, THEMES, DEFAULT_THEME } from "./themes.js";
|
|
13
13
|
import { PROVIDERS, getCredentials, openRouterOAuth, anthropicSetupToken, importCodexToken, importQwenToken, copilotDeviceFlow } from "./utils/auth.js";
|
|
14
14
|
import { listInstalledSkills, installSkill, removeSkill, getRegistrySkills, searchRegistry, createSkillScaffold, getActiveSkills, getActiveSkillCount } from "./utils/skills.js";
|
|
15
|
+
import { listServers, addServer, removeServer, getConnectedServers } from "./utils/mcp.js";
|
|
16
|
+
import { detectHardware, formatBytes } from "./utils/hardware.js";
|
|
17
|
+
import { getRecommendations, getFitIcon } from "./utils/models.js";
|
|
18
|
+
import { isOllamaInstalled, isOllamaRunning, getOllamaInstallCommand, startOllama, pullModel } from "./utils/ollama.js";
|
|
15
19
|
const VERSION = "0.1.9";
|
|
16
20
|
// ── Helpers ──
|
|
17
21
|
function formatTimeAgo(date) {
|
|
@@ -58,6 +62,11 @@ const SLASH_COMMANDS = [
|
|
|
58
62
|
{ cmd: "/lint", desc: "show auto-lint status" },
|
|
59
63
|
{ cmd: "/lint on", desc: "enable auto-lint" },
|
|
60
64
|
{ cmd: "/lint off", desc: "disable auto-lint" },
|
|
65
|
+
{ cmd: "/mcp", desc: "show MCP servers" },
|
|
66
|
+
{ cmd: "/mcp tools", desc: "list MCP tools" },
|
|
67
|
+
{ cmd: "/mcp add", desc: "add MCP server" },
|
|
68
|
+
{ cmd: "/mcp remove", desc: "remove MCP server" },
|
|
69
|
+
{ cmd: "/mcp reconnect", desc: "reconnect MCP servers" },
|
|
61
70
|
{ cmd: "/quit", desc: "exit" },
|
|
62
71
|
];
|
|
63
72
|
const SPINNER_FRAMES = ["⣾", "⣽", "⣻", "⢿", "⡿", "⣟", "⣯", "⣷"];
|
|
@@ -131,6 +140,13 @@ function App() {
|
|
|
131
140
|
const [skillsPickerIndex, setSkillsPickerIndex] = useState(0);
|
|
132
141
|
const [sessionDisabledSkills, setSessionDisabledSkills] = useState(new Set());
|
|
133
142
|
const [approval, setApproval] = useState(null);
|
|
143
|
+
const [wizardScreen, setWizardScreen] = useState(null);
|
|
144
|
+
const [wizardIndex, setWizardIndex] = useState(0);
|
|
145
|
+
const [wizardHardware, setWizardHardware] = useState(null);
|
|
146
|
+
const [wizardModels, setWizardModels] = useState([]);
|
|
147
|
+
const [wizardPullProgress, setWizardPullProgress] = useState(null);
|
|
148
|
+
const [wizardPullError, setWizardPullError] = useState(null);
|
|
149
|
+
const [wizardSelectedModel, setWizardSelectedModel] = useState(null);
|
|
134
150
|
// Listen for paste events from stdin interceptor
|
|
135
151
|
useEffect(() => {
|
|
136
152
|
const handler = ({ content, lines }) => {
|
|
@@ -168,10 +184,11 @@ function App() {
|
|
|
168
184
|
}
|
|
169
185
|
else {
|
|
170
186
|
info.push("✗ No local LLM server found.");
|
|
171
|
-
info.push(" /connect — retry after starting LM Studio or Ollama");
|
|
172
|
-
info.push(" /login — authenticate with a cloud provider");
|
|
173
187
|
setConnectionInfo([...info]);
|
|
174
188
|
setReady(true);
|
|
189
|
+
// Show the setup wizard on first run
|
|
190
|
+
setWizardScreen("connection");
|
|
191
|
+
setWizardIndex(0);
|
|
175
192
|
return;
|
|
176
193
|
}
|
|
177
194
|
}
|
|
@@ -246,6 +263,9 @@ function App() {
|
|
|
246
263
|
onLintResult: (file, errors) => {
|
|
247
264
|
addMsg("info", `🔍 Lint errors in ${file}:\n${errors}`);
|
|
248
265
|
},
|
|
266
|
+
onMCPStatus: (server, status) => {
|
|
267
|
+
addMsg("info", `🔌 MCP ${server}: ${status}`);
|
|
268
|
+
},
|
|
249
269
|
contextCompressionThreshold: config.defaults.contextCompressionThreshold,
|
|
250
270
|
onToolApproval: (name, args, diff) => {
|
|
251
271
|
return new Promise((resolve) => {
|
|
@@ -262,6 +282,12 @@ function App() {
|
|
|
262
282
|
info.push(`📋 ${rulesSource} loaded`);
|
|
263
283
|
setConnectionInfo([...info]);
|
|
264
284
|
}
|
|
285
|
+
// Show MCP server count
|
|
286
|
+
const mcpCount = a.getMCPServerCount();
|
|
287
|
+
if (mcpCount > 0) {
|
|
288
|
+
info.push(`🔌 ${mcpCount} MCP server${mcpCount > 1 ? "s" : ""} connected`);
|
|
289
|
+
setConnectionInfo([...info]);
|
|
290
|
+
}
|
|
265
291
|
setAgent(a);
|
|
266
292
|
setModelName(provider.model);
|
|
267
293
|
providerRef.current = { baseUrl: provider.baseUrl, apiKey: provider.apiKey };
|
|
@@ -367,6 +393,11 @@ function App() {
|
|
|
367
393
|
" /lint — show auto-lint status & detected linter",
|
|
368
394
|
" /lint on — enable auto-lint",
|
|
369
395
|
" /lint off — disable auto-lint",
|
|
396
|
+
" /mcp — show MCP servers & status",
|
|
397
|
+
" /mcp tools — list all MCP tools",
|
|
398
|
+
" /mcp add — add MCP server to global config",
|
|
399
|
+
" /mcp remove — remove MCP server",
|
|
400
|
+
" /mcp reconnect — reconnect all MCP servers",
|
|
370
401
|
" /quit — exit",
|
|
371
402
|
].join("\n"));
|
|
372
403
|
return;
|
|
@@ -534,6 +565,71 @@ function App() {
|
|
|
534
565
|
addMsg("info", "🔍 Auto-lint OFF");
|
|
535
566
|
return;
|
|
536
567
|
}
|
|
568
|
+
// ── MCP commands (partially work without agent) ──
|
|
569
|
+
if (trimmed === "/mcp" || trimmed === "/mcp list") {
|
|
570
|
+
const servers = listServers(process.cwd());
|
|
571
|
+
if (servers.length === 0) {
|
|
572
|
+
addMsg("info", "🔌 No MCP servers configured.\n Add one: /mcp add <name> <command> [args...]");
|
|
573
|
+
}
|
|
574
|
+
else {
|
|
575
|
+
const lines = servers.map((s) => {
|
|
576
|
+
const status = s.connected ? `✔ connected (${s.toolCount} tools)` : "✗ not connected";
|
|
577
|
+
return ` ${s.connected ? "●" : "○"} ${s.name} [${s.source}] — ${s.command}\n ${status}`;
|
|
578
|
+
});
|
|
579
|
+
addMsg("info", `🔌 MCP Servers:\n${lines.join("\n")}`);
|
|
580
|
+
}
|
|
581
|
+
return;
|
|
582
|
+
}
|
|
583
|
+
if (trimmed === "/mcp tools") {
|
|
584
|
+
const servers = getConnectedServers();
|
|
585
|
+
if (servers.length === 0) {
|
|
586
|
+
addMsg("info", "🔌 No MCP servers connected.");
|
|
587
|
+
return;
|
|
588
|
+
}
|
|
589
|
+
const lines = [];
|
|
590
|
+
for (const server of servers) {
|
|
591
|
+
lines.push(`${server.name} (${server.tools.length} tools):`);
|
|
592
|
+
for (const tool of server.tools) {
|
|
593
|
+
lines.push(` • ${tool.name} — ${tool.description ?? "(no description)"}`);
|
|
594
|
+
}
|
|
595
|
+
}
|
|
596
|
+
addMsg("info", `🔌 MCP Tools:\n${lines.join("\n")}`);
|
|
597
|
+
return;
|
|
598
|
+
}
|
|
599
|
+
if (trimmed.startsWith("/mcp add ")) {
|
|
600
|
+
const parts = trimmed.replace("/mcp add ", "").trim().split(/\s+/);
|
|
601
|
+
if (parts.length < 2) {
|
|
602
|
+
addMsg("info", "Usage: /mcp add <name> <command> [args...]\n Example: /mcp add github npx -y @modelcontextprotocol/server-github");
|
|
603
|
+
return;
|
|
604
|
+
}
|
|
605
|
+
const [name, command, ...cmdArgs] = parts;
|
|
606
|
+
const result = addServer(name, { command, args: cmdArgs.length > 0 ? cmdArgs : undefined });
|
|
607
|
+
addMsg(result.ok ? "info" : "error", result.ok ? `✅ ${result.message}` : `✗ ${result.message}`);
|
|
608
|
+
return;
|
|
609
|
+
}
|
|
610
|
+
if (trimmed.startsWith("/mcp remove ")) {
|
|
611
|
+
const name = trimmed.replace("/mcp remove ", "").trim();
|
|
612
|
+
if (!name) {
|
|
613
|
+
addMsg("info", "Usage: /mcp remove <name>");
|
|
614
|
+
return;
|
|
615
|
+
}
|
|
616
|
+
const result = removeServer(name);
|
|
617
|
+
addMsg(result.ok ? "info" : "error", result.ok ? `✅ ${result.message}` : `✗ ${result.message}`);
|
|
618
|
+
return;
|
|
619
|
+
}
|
|
620
|
+
if (trimmed === "/mcp reconnect") {
|
|
621
|
+
if (!agent) {
|
|
622
|
+
addMsg("info", "⚠ No agent connected. Connect first.");
|
|
623
|
+
return;
|
|
624
|
+
}
|
|
625
|
+
addMsg("info", "🔌 Reconnecting MCP servers...");
|
|
626
|
+
await agent.reconnectMCP();
|
|
627
|
+
const count = agent.getMCPServerCount();
|
|
628
|
+
addMsg("info", count > 0
|
|
629
|
+
? `✅ ${count} MCP server${count > 1 ? "s" : ""} reconnected.`
|
|
630
|
+
: "No MCP servers connected.");
|
|
631
|
+
return;
|
|
632
|
+
}
|
|
537
633
|
// Commands below require an active LLM connection
|
|
538
634
|
if (!agent) {
|
|
539
635
|
addMsg("info", "⚠ No LLM connected. Use /login to authenticate with a provider, or start a local server.");
|
|
@@ -1029,6 +1125,223 @@ function App() {
|
|
|
1029
1125
|
}
|
|
1030
1126
|
return;
|
|
1031
1127
|
}
|
|
1128
|
+
// ── Setup Wizard Navigation ──
|
|
1129
|
+
if (wizardScreen) {
|
|
1130
|
+
if (wizardScreen === "connection") {
|
|
1131
|
+
const items = ["local", "openrouter", "apikey", "existing"];
|
|
1132
|
+
if (key.upArrow) {
|
|
1133
|
+
setWizardIndex((prev) => (prev - 1 + items.length) % items.length);
|
|
1134
|
+
return;
|
|
1135
|
+
}
|
|
1136
|
+
if (key.downArrow) {
|
|
1137
|
+
setWizardIndex((prev) => (prev + 1) % items.length);
|
|
1138
|
+
return;
|
|
1139
|
+
}
|
|
1140
|
+
if (key.escape) {
|
|
1141
|
+
setWizardScreen(null);
|
|
1142
|
+
return;
|
|
1143
|
+
}
|
|
1144
|
+
if (key.return) {
|
|
1145
|
+
const selected = items[wizardIndex];
|
|
1146
|
+
if (selected === "local") {
|
|
1147
|
+
// Scan hardware and show model picker
|
|
1148
|
+
const hw = detectHardware();
|
|
1149
|
+
setWizardHardware(hw);
|
|
1150
|
+
const recs = getRecommendations(hw).filter(m => m.fit !== "skip");
|
|
1151
|
+
setWizardModels(recs);
|
|
1152
|
+
setWizardScreen("models");
|
|
1153
|
+
setWizardIndex(0);
|
|
1154
|
+
}
|
|
1155
|
+
else if (selected === "openrouter") {
|
|
1156
|
+
setWizardScreen(null);
|
|
1157
|
+
addMsg("info", "Starting OpenRouter OAuth — opening browser...");
|
|
1158
|
+
setLoading(true);
|
|
1159
|
+
setSpinnerMsg("Waiting for authorization...");
|
|
1160
|
+
openRouterOAuth((msg) => addMsg("info", msg))
|
|
1161
|
+
.then(() => {
|
|
1162
|
+
addMsg("info", "✅ OpenRouter authenticated! Use /connect to connect.");
|
|
1163
|
+
setLoading(false);
|
|
1164
|
+
})
|
|
1165
|
+
.catch((err) => { addMsg("error", `OAuth failed: ${err.message}`); setLoading(false); });
|
|
1166
|
+
}
|
|
1167
|
+
else if (selected === "apikey") {
|
|
1168
|
+
setWizardScreen(null);
|
|
1169
|
+
setLoginPicker(true);
|
|
1170
|
+
setLoginPickerIndex(0);
|
|
1171
|
+
}
|
|
1172
|
+
else if (selected === "existing") {
|
|
1173
|
+
setWizardScreen(null);
|
|
1174
|
+
addMsg("info", "Start your LLM server, then type /connect to retry.");
|
|
1175
|
+
}
|
|
1176
|
+
return;
|
|
1177
|
+
}
|
|
1178
|
+
return;
|
|
1179
|
+
}
|
|
1180
|
+
if (wizardScreen === "models") {
|
|
1181
|
+
const models = wizardModels;
|
|
1182
|
+
if (key.upArrow) {
|
|
1183
|
+
setWizardIndex((prev) => (prev - 1 + models.length) % models.length);
|
|
1184
|
+
return;
|
|
1185
|
+
}
|
|
1186
|
+
if (key.downArrow) {
|
|
1187
|
+
setWizardIndex((prev) => (prev + 1) % models.length);
|
|
1188
|
+
return;
|
|
1189
|
+
}
|
|
1190
|
+
if (key.escape) {
|
|
1191
|
+
setWizardScreen("connection");
|
|
1192
|
+
setWizardIndex(0);
|
|
1193
|
+
return;
|
|
1194
|
+
}
|
|
1195
|
+
if (key.return) {
|
|
1196
|
+
const selected = models[wizardIndex];
|
|
1197
|
+
if (selected) {
|
|
1198
|
+
setWizardSelectedModel(selected);
|
|
1199
|
+
// Check if Ollama is installed
|
|
1200
|
+
if (!isOllamaInstalled()) {
|
|
1201
|
+
setWizardScreen("install-ollama");
|
|
1202
|
+
}
|
|
1203
|
+
else {
|
|
1204
|
+
// Start pulling the model
|
|
1205
|
+
setWizardScreen("pulling");
|
|
1206
|
+
setWizardPullProgress({ status: "starting", percent: 0 });
|
|
1207
|
+
setWizardPullError(null);
|
|
1208
|
+
(async () => {
|
|
1209
|
+
try {
|
|
1210
|
+
// Ensure ollama is running
|
|
1211
|
+
const running = await isOllamaRunning();
|
|
1212
|
+
if (!running) {
|
|
1213
|
+
setWizardPullProgress({ status: "Starting Ollama server...", percent: 0 });
|
|
1214
|
+
startOllama();
|
|
1215
|
+
// Wait for it to come up
|
|
1216
|
+
for (let i = 0; i < 15; i++) {
|
|
1217
|
+
await new Promise(r => setTimeout(r, 1000));
|
|
1218
|
+
if (await isOllamaRunning())
|
|
1219
|
+
break;
|
|
1220
|
+
}
|
|
1221
|
+
if (!(await isOllamaRunning())) {
|
|
1222
|
+
setWizardPullError("Could not start Ollama server. Run 'ollama serve' manually, then press Enter.");
|
|
1223
|
+
return;
|
|
1224
|
+
}
|
|
1225
|
+
}
|
|
1226
|
+
await pullModel(selected.ollamaId, (p) => {
|
|
1227
|
+
setWizardPullProgress(p);
|
|
1228
|
+
});
|
|
1229
|
+
setWizardPullProgress({ status: "success", percent: 100 });
|
|
1230
|
+
// Wait briefly then connect
|
|
1231
|
+
await new Promise(r => setTimeout(r, 500));
|
|
1232
|
+
setWizardScreen(null);
|
|
1233
|
+
setWizardPullProgress(null);
|
|
1234
|
+
setWizardSelectedModel(null);
|
|
1235
|
+
addMsg("info", `✅ ${selected.name} installed! Connecting...`);
|
|
1236
|
+
await connectToProvider(true);
|
|
1237
|
+
}
|
|
1238
|
+
catch (err) {
|
|
1239
|
+
setWizardPullError(err.message);
|
|
1240
|
+
}
|
|
1241
|
+
})();
|
|
1242
|
+
}
|
|
1243
|
+
}
|
|
1244
|
+
return;
|
|
1245
|
+
}
|
|
1246
|
+
return;
|
|
1247
|
+
}
|
|
1248
|
+
if (wizardScreen === "install-ollama") {
|
|
1249
|
+
if (key.escape) {
|
|
1250
|
+
setWizardScreen("models");
|
|
1251
|
+
setWizardIndex(0);
|
|
1252
|
+
return;
|
|
1253
|
+
}
|
|
1254
|
+
if (key.return) {
|
|
1255
|
+
// User says they installed it — check and proceed
|
|
1256
|
+
if (isOllamaInstalled()) {
|
|
1257
|
+
const selected = wizardSelectedModel;
|
|
1258
|
+
if (selected) {
|
|
1259
|
+
setWizardScreen("pulling");
|
|
1260
|
+
setWizardPullProgress({ status: "starting", percent: 0 });
|
|
1261
|
+
setWizardPullError(null);
|
|
1262
|
+
(async () => {
|
|
1263
|
+
try {
|
|
1264
|
+
const running = await isOllamaRunning();
|
|
1265
|
+
if (!running) {
|
|
1266
|
+
setWizardPullProgress({ status: "Starting Ollama server...", percent: 0 });
|
|
1267
|
+
startOllama();
|
|
1268
|
+
for (let i = 0; i < 15; i++) {
|
|
1269
|
+
await new Promise(r => setTimeout(r, 1000));
|
|
1270
|
+
if (await isOllamaRunning())
|
|
1271
|
+
break;
|
|
1272
|
+
}
|
|
1273
|
+
if (!(await isOllamaRunning())) {
|
|
1274
|
+
setWizardPullError("Could not start Ollama server. Run 'ollama serve' manually, then press Enter.");
|
|
1275
|
+
return;
|
|
1276
|
+
}
|
|
1277
|
+
}
|
|
1278
|
+
await pullModel(selected.ollamaId, (p) => setWizardPullProgress(p));
|
|
1279
|
+
setWizardPullProgress({ status: "success", percent: 100 });
|
|
1280
|
+
await new Promise(r => setTimeout(r, 500));
|
|
1281
|
+
setWizardScreen(null);
|
|
1282
|
+
setWizardPullProgress(null);
|
|
1283
|
+
setWizardSelectedModel(null);
|
|
1284
|
+
addMsg("info", `✅ ${selected.name} installed! Connecting...`);
|
|
1285
|
+
await connectToProvider(true);
|
|
1286
|
+
}
|
|
1287
|
+
catch (err) {
|
|
1288
|
+
setWizardPullError(err.message);
|
|
1289
|
+
}
|
|
1290
|
+
})();
|
|
1291
|
+
}
|
|
1292
|
+
}
|
|
1293
|
+
else {
|
|
1294
|
+
addMsg("info", "Ollama not found yet. Install it and press Enter again.");
|
|
1295
|
+
}
|
|
1296
|
+
return;
|
|
1297
|
+
}
|
|
1298
|
+
return;
|
|
1299
|
+
}
|
|
1300
|
+
if (wizardScreen === "pulling") {
|
|
1301
|
+
// Allow retry on error
|
|
1302
|
+
if (wizardPullError && key.return) {
|
|
1303
|
+
const selected = wizardSelectedModel;
|
|
1304
|
+
if (selected) {
|
|
1305
|
+
setWizardPullError(null);
|
|
1306
|
+
setWizardPullProgress({ status: "retrying", percent: 0 });
|
|
1307
|
+
(async () => {
|
|
1308
|
+
try {
|
|
1309
|
+
const running = await isOllamaRunning();
|
|
1310
|
+
if (!running) {
|
|
1311
|
+
startOllama();
|
|
1312
|
+
for (let i = 0; i < 15; i++) {
|
|
1313
|
+
await new Promise(r => setTimeout(r, 1000));
|
|
1314
|
+
if (await isOllamaRunning())
|
|
1315
|
+
break;
|
|
1316
|
+
}
|
|
1317
|
+
}
|
|
1318
|
+
await pullModel(selected.ollamaId, (p) => setWizardPullProgress(p));
|
|
1319
|
+
setWizardPullProgress({ status: "success", percent: 100 });
|
|
1320
|
+
await new Promise(r => setTimeout(r, 500));
|
|
1321
|
+
setWizardScreen(null);
|
|
1322
|
+
setWizardPullProgress(null);
|
|
1323
|
+
setWizardSelectedModel(null);
|
|
1324
|
+
addMsg("info", `✅ ${selected.name} installed! Connecting...`);
|
|
1325
|
+
await connectToProvider(true);
|
|
1326
|
+
}
|
|
1327
|
+
catch (err) {
|
|
1328
|
+
setWizardPullError(err.message);
|
|
1329
|
+
}
|
|
1330
|
+
})();
|
|
1331
|
+
}
|
|
1332
|
+
return;
|
|
1333
|
+
}
|
|
1334
|
+
if (wizardPullError && key.escape) {
|
|
1335
|
+
setWizardScreen("models");
|
|
1336
|
+
setWizardIndex(0);
|
|
1337
|
+
setWizardPullError(null);
|
|
1338
|
+
setWizardPullProgress(null);
|
|
1339
|
+
return;
|
|
1340
|
+
}
|
|
1341
|
+
return; // Ignore keys while pulling
|
|
1342
|
+
}
|
|
1343
|
+
return;
|
|
1344
|
+
}
|
|
1032
1345
|
// Theme picker navigation
|
|
1033
1346
|
if (themePicker) {
|
|
1034
1347
|
const themeKeys = listThemes();
|
|
@@ -1259,7 +1572,12 @@ function App() {
|
|
|
1259
1572
|
})(), skillsPicker === "remove" && (() => {
|
|
1260
1573
|
const installed = listInstalledSkills();
|
|
1261
1574
|
return (_jsxs(Box, { flexDirection: "column", borderStyle: "single", borderColor: theme.colors.error, paddingX: 1, marginBottom: 0, children: [_jsx(Text, { bold: true, color: theme.colors.error, children: "Remove a skill:" }), installed.map((s, i) => (_jsxs(Text, { children: [i === skillsPickerIndex ? _jsx(Text, { color: theme.colors.suggestion, bold: true, children: "▸ " }) : _jsx(Text, { children: " " }), _jsxs(Text, { color: i === skillsPickerIndex ? theme.colors.suggestion : theme.colors.muted, children: [s.name, " \u2014 ", s.description] })] }, s.name))), _jsx(Text, { dimColor: true, children: " ↑↓ navigate · Enter remove · Esc back" })] }));
|
|
1262
|
-
})(), themePicker && (_jsxs(Box, { flexDirection: "column", borderStyle: "single", borderColor: theme.colors.border, paddingX: 1, marginBottom: 0, children: [_jsx(Text, { bold: true, color: theme.colors.secondary, children: "Choose a theme:" }), listThemes().map((key, i) => (_jsxs(Text, { children: [i === themePickerIndex ? _jsx(Text, { color: theme.colors.suggestion, bold: true, children: "▸ " }) : _jsx(Text, { children: " " }), _jsx(Text, { color: i === themePickerIndex ? theme.colors.suggestion : theme.colors.primary, bold: true, children: key }), _jsxs(Text, { color: theme.colors.muted, children: [" — ", THEMES[key].description] }), key === theme.name.toLowerCase() ? _jsx(Text, { color: theme.colors.muted, children: " (current)" }) : null] }, key))), _jsx(Text, { dimColor: true, children: " ↑↓ navigate · Enter select · Esc cancel" })] })), sessionPicker && (_jsxs(Box, { flexDirection: "column", borderStyle: "single", borderColor: theme.colors.secondary, paddingX: 1, marginBottom: 0, children: [_jsx(Text, { bold: true, color: theme.colors.secondary, children: "Resume a session:" }), sessionPicker.map((s, i) => (_jsxs(Text, { children: [i === sessionPickerIndex ? _jsx(Text, { color: theme.colors.suggestion, bold: true, children: "▸ " }) : _jsx(Text, { children: " " }), _jsx(Text, { color: i === sessionPickerIndex ? theme.colors.suggestion : theme.colors.muted, children: s.display })] }, s.id))), _jsx(Text, { dimColor: true, children: " ↑↓ navigate · Enter select · Esc cancel" })] })), deleteSessionPicker && (_jsxs(Box, { flexDirection: "column", borderStyle: "single", borderColor: theme.colors.error, paddingX: 1, marginBottom: 0, children: [_jsx(Text, { bold: true, color: theme.colors.error, children: "Delete a session:" }), deleteSessionPicker.map((s, i) => (_jsxs(Text, { children: [i === deleteSessionPickerIndex ? _jsx(Text, { color: theme.colors.suggestion, bold: true, children: "▸ " }) : _jsx(Text, { children: " " }), _jsx(Text, { color: i === deleteSessionPickerIndex ? theme.colors.suggestion : theme.colors.muted, children: s.display })] }, s.id))), _jsx(Text, { dimColor: true, children: " ↑↓ navigate · Enter select · Esc cancel" })] })), deleteSessionConfirm && (_jsxs(Box, { flexDirection: "column", borderStyle: "single", borderColor: theme.colors.warning, paddingX: 1, marginBottom: 0, children: [_jsxs(Text, { bold: true, color: theme.colors.warning, children: ["Delete session ", deleteSessionConfirm.id, "?"] }), _jsxs(Text, { color: theme.colors.muted, children: [" ", deleteSessionConfirm.display] }), _jsxs(Text, { children: [_jsx(Text, { color: theme.colors.error, bold: true, children: " [y]" }), _jsx(Text, { children: "es " }), _jsx(Text, { color: theme.colors.success, bold: true, children: "[n]" }), _jsx(Text, { children: "o" })] })] })),
|
|
1575
|
+
})(), themePicker && (_jsxs(Box, { flexDirection: "column", borderStyle: "single", borderColor: theme.colors.border, paddingX: 1, marginBottom: 0, children: [_jsx(Text, { bold: true, color: theme.colors.secondary, children: "Choose a theme:" }), listThemes().map((key, i) => (_jsxs(Text, { children: [i === themePickerIndex ? _jsx(Text, { color: theme.colors.suggestion, bold: true, children: "▸ " }) : _jsx(Text, { children: " " }), _jsx(Text, { color: i === themePickerIndex ? theme.colors.suggestion : theme.colors.primary, bold: true, children: key }), _jsxs(Text, { color: theme.colors.muted, children: [" — ", THEMES[key].description] }), key === theme.name.toLowerCase() ? _jsx(Text, { color: theme.colors.muted, children: " (current)" }) : null] }, key))), _jsx(Text, { dimColor: true, children: " ↑↓ navigate · Enter select · Esc cancel" })] })), sessionPicker && (_jsxs(Box, { flexDirection: "column", borderStyle: "single", borderColor: theme.colors.secondary, paddingX: 1, marginBottom: 0, children: [_jsx(Text, { bold: true, color: theme.colors.secondary, children: "Resume a session:" }), sessionPicker.map((s, i) => (_jsxs(Text, { children: [i === sessionPickerIndex ? _jsx(Text, { color: theme.colors.suggestion, bold: true, children: "▸ " }) : _jsx(Text, { children: " " }), _jsx(Text, { color: i === sessionPickerIndex ? theme.colors.suggestion : theme.colors.muted, children: s.display })] }, s.id))), _jsx(Text, { dimColor: true, children: " ↑↓ navigate · Enter select · Esc cancel" })] })), deleteSessionPicker && (_jsxs(Box, { flexDirection: "column", borderStyle: "single", borderColor: theme.colors.error, paddingX: 1, marginBottom: 0, children: [_jsx(Text, { bold: true, color: theme.colors.error, children: "Delete a session:" }), deleteSessionPicker.map((s, i) => (_jsxs(Text, { children: [i === deleteSessionPickerIndex ? _jsx(Text, { color: theme.colors.suggestion, bold: true, children: "▸ " }) : _jsx(Text, { children: " " }), _jsx(Text, { color: i === deleteSessionPickerIndex ? theme.colors.suggestion : theme.colors.muted, children: s.display })] }, s.id))), _jsx(Text, { dimColor: true, children: " ↑↓ navigate · Enter select · Esc cancel" })] })), deleteSessionConfirm && (_jsxs(Box, { flexDirection: "column", borderStyle: "single", borderColor: theme.colors.warning, paddingX: 1, marginBottom: 0, children: [_jsxs(Text, { bold: true, color: theme.colors.warning, children: ["Delete session ", deleteSessionConfirm.id, "?"] }), _jsxs(Text, { color: theme.colors.muted, children: [" ", deleteSessionConfirm.display] }), _jsxs(Text, { children: [_jsx(Text, { color: theme.colors.error, bold: true, children: " [y]" }), _jsx(Text, { children: "es " }), _jsx(Text, { color: theme.colors.success, bold: true, children: "[n]" }), _jsx(Text, { children: "o" })] })] })), wizardScreen === "connection" && (_jsxs(Box, { flexDirection: "column", borderStyle: "single", borderColor: theme.colors.border, paddingX: 1, marginBottom: 0, children: [_jsx(Text, { bold: true, color: theme.colors.secondary, children: "No LLM detected. How do you want to connect?" }), _jsx(Text, { children: "" }), [
|
|
1576
|
+
{ key: "local", icon: "\uD83D\uDDA5\uFE0F", label: "Set up a local model", desc: "free, runs on your machine" },
|
|
1577
|
+
{ key: "openrouter", icon: "\uD83C\uDF10", label: "OpenRouter", desc: "200+ cloud models, browser login" },
|
|
1578
|
+
{ key: "apikey", icon: "\uD83D\uDD11", label: "Enter API key manually", desc: "" },
|
|
1579
|
+
{ key: "existing", icon: "\u2699\uFE0F", label: "I already have a server running", desc: "" },
|
|
1580
|
+
].map((item, i) => (_jsxs(Text, { children: [i === wizardIndex ? _jsx(Text, { color: theme.colors.suggestion, bold: true, children: " \u25B8 " }) : _jsx(Text, { children: " " }), _jsxs(Text, { color: i === wizardIndex ? theme.colors.suggestion : theme.colors.primary, bold: true, children: [item.icon, " ", item.label] }), item.desc ? _jsxs(Text, { color: theme.colors.muted, children: [" (", item.desc, ")"] }) : null] }, item.key))), _jsx(Text, { children: "" }), _jsx(Text, { dimColor: true, children: " \u2191\u2193 navigate \u00B7 Enter to select" })] })), wizardScreen === "models" && wizardHardware && (_jsxs(Box, { flexDirection: "column", borderStyle: "single", borderColor: theme.colors.border, paddingX: 1, marginBottom: 0, children: [_jsx(Text, { bold: true, color: theme.colors.secondary, children: "Your hardware:" }), _jsxs(Text, { color: theme.colors.muted, children: [" CPU: ", wizardHardware.cpu.name, " (", wizardHardware.cpu.cores, " cores)"] }), _jsxs(Text, { color: theme.colors.muted, children: [" RAM: ", formatBytes(wizardHardware.ram)] }), wizardHardware.gpu ? (_jsxs(Text, { color: theme.colors.muted, children: [" GPU: ", wizardHardware.gpu.name, wizardHardware.gpu.vram > 0 ? ` (${formatBytes(wizardHardware.gpu.vram)})` : ""] })) : (_jsx(Text, { color: theme.colors.muted, children: " GPU: none detected" })), _jsx(Text, { children: "" }), _jsx(Text, { bold: true, color: theme.colors.secondary, children: "Recommended models:" }), _jsx(Text, { children: "" }), wizardModels.map((m, i) => (_jsxs(Text, { children: [i === wizardIndex ? _jsx(Text, { color: theme.colors.suggestion, bold: true, children: " \u25B8 " }) : _jsx(Text, { children: " " }), _jsxs(Text, { children: [getFitIcon(m.fit), " "] }), _jsx(Text, { color: i === wizardIndex ? theme.colors.suggestion : theme.colors.primary, bold: true, children: m.name }), _jsxs(Text, { color: theme.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: theme.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" })] })), wizardScreen === "install-ollama" && (_jsxs(Box, { flexDirection: "column", borderStyle: "single", borderColor: theme.colors.warning, paddingX: 1, marginBottom: 0, children: [_jsx(Text, { bold: true, color: theme.colors.warning, children: "Ollama is required for local models." }), _jsx(Text, { children: "" }), _jsxs(Text, { color: theme.colors.primary, children: [" Install with: ", _jsx(Text, { bold: true, children: getOllamaInstallCommand(wizardHardware?.os ?? "linux") })] }), _jsx(Text, { children: "" }), _jsx(Text, { dimColor: true, children: " Run the command above, then press Enter to continue..." }), _jsx(Text, { dimColor: true, children: " Esc to go back" })] })), wizardScreen === "pulling" && wizardSelectedModel && (_jsx(Box, { flexDirection: "column", borderStyle: "single", borderColor: theme.colors.border, paddingX: 1, marginBottom: 0, children: wizardPullError ? (_jsxs(_Fragment, { children: [_jsxs(Text, { color: theme.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: theme.colors.secondary, children: [" Downloading ", wizardSelectedModel.name, "..."] }), wizardPullProgress.status === "downloading" || wizardPullProgress.percent > 0 ? (_jsx(_Fragment, { children: _jsxs(Text, { children: [" ", _jsxs(Text, { color: theme.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: theme.colors.muted, children: [" \u00B7 ", formatBytes(wizardPullProgress.completed), " / ", formatBytes(wizardPullProgress.total)] })) : null] }) })) : (_jsxs(Text, { color: theme.colors.muted, children: [" ", wizardPullProgress.status, "..."] }))] })) : null })), showSuggestions && (_jsxs(Box, { flexDirection: "column", borderStyle: "single", borderColor: theme.colors.muted, paddingX: 1, marginBottom: 0, children: [cmdMatches.slice(0, 6).map((c, i) => (_jsxs(Text, { children: [i === cmdIndex ? _jsx(Text, { color: theme.colors.suggestion, bold: true, children: "▸ " }) : _jsx(Text, { children: " " }), _jsx(Text, { color: i === cmdIndex ? theme.colors.suggestion : theme.colors.primary, bold: true, children: c.cmd }), _jsxs(Text, { color: theme.colors.muted, children: [" — ", c.desc] })] }, i))), _jsx(Text, { dimColor: true, children: " ↑↓ navigate · Tab select" })] })), _jsxs(Box, { borderStyle: "single", borderColor: approval ? theme.colors.warning : theme.colors.border, paddingX: 1, children: [_jsx(Text, { color: theme.colors.secondary, bold: true, children: "> " }), approval ? (_jsx(Text, { color: theme.colors.warning, children: "waiting for approval..." })) : ready && !loading ? (_jsxs(Box, { children: [pastedChunks.map((p) => (_jsxs(Text, { color: theme.colors.muted, children: ["[Pasted text #", p.id, " +", p.lines, " lines]"] }, p.id))), _jsx(TextInput, { value: input, onChange: (v) => { setInput(v); setCmdIndex(0); }, onSubmit: handleSubmit }, inputKey)] })) : (_jsx(Text, { dimColor: true, children: loading ? "waiting for response..." : "initializing..." }))] }), agent && (_jsx(Box, { paddingX: 2, children: _jsxs(Text, { dimColor: true, children: ["💬 ", agent.getContextLength(), " messages · ~", (() => {
|
|
1263
1581
|
const tokens = agent.estimateTokens();
|
|
1264
1582
|
return tokens >= 1000 ? `${(tokens / 1000).toFixed(1)}k` : String(tokens);
|
|
1265
1583
|
})(), " tokens", (() => {
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
export interface HardwareInfo {
|
|
2
|
+
cpu: {
|
|
3
|
+
name: string;
|
|
4
|
+
cores: number;
|
|
5
|
+
speed: number;
|
|
6
|
+
};
|
|
7
|
+
ram: number;
|
|
8
|
+
gpu: {
|
|
9
|
+
name: string;
|
|
10
|
+
vram: number;
|
|
11
|
+
} | null;
|
|
12
|
+
os: "macos" | "linux" | "windows";
|
|
13
|
+
appleSilicon: boolean;
|
|
14
|
+
}
|
|
15
|
+
export declare function detectHardware(): HardwareInfo;
|
|
16
|
+
/** Format bytes to human-readable string */
|
|
17
|
+
export declare function formatBytes(bytes: number): string;
|