@kernel.chat/kbot 1.3.0 → 2.3.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 +94 -0
- package/dist/agent.d.ts +9 -0
- package/dist/agent.d.ts.map +1 -1
- package/dist/agent.js +576 -119
- package/dist/agent.js.map +1 -1
- package/dist/auth.d.ts +20 -35
- package/dist/auth.d.ts.map +1 -1
- package/dist/auth.js +236 -66
- package/dist/auth.js.map +1 -1
- package/dist/auth.test.d.ts +2 -0
- package/dist/auth.test.d.ts.map +1 -0
- package/dist/auth.test.js +89 -0
- package/dist/auth.test.js.map +1 -0
- package/dist/build-targets.d.ts +37 -0
- package/dist/build-targets.d.ts.map +1 -0
- package/dist/build-targets.js +507 -0
- package/dist/build-targets.js.map +1 -0
- package/dist/cli.js +1211 -131
- package/dist/cli.js.map +1 -1
- package/dist/context.d.ts +2 -0
- package/dist/context.d.ts.map +1 -1
- package/dist/context.js +72 -22
- package/dist/context.js.map +1 -1
- package/dist/hooks.d.ts +27 -0
- package/dist/hooks.d.ts.map +1 -0
- package/dist/hooks.js +145 -0
- package/dist/hooks.js.map +1 -0
- package/dist/ide/acp-server.d.ts +6 -0
- package/dist/ide/acp-server.d.ts.map +1 -0
- package/dist/ide/acp-server.js +319 -0
- package/dist/ide/acp-server.js.map +1 -0
- package/dist/ide/bridge.d.ts +128 -0
- package/dist/ide/bridge.d.ts.map +1 -0
- package/dist/ide/bridge.js +185 -0
- package/dist/ide/bridge.js.map +1 -0
- package/dist/ide/index.d.ts +5 -0
- package/dist/ide/index.d.ts.map +1 -0
- package/dist/ide/index.js +11 -0
- package/dist/ide/index.js.map +1 -0
- package/dist/ide/lsp-bridge.d.ts +27 -0
- package/dist/ide/lsp-bridge.d.ts.map +1 -0
- package/dist/ide/lsp-bridge.js +267 -0
- package/dist/ide/lsp-bridge.js.map +1 -0
- package/dist/ide/mcp-server.d.ts +7 -0
- package/dist/ide/mcp-server.d.ts.map +1 -0
- package/dist/ide/mcp-server.js +451 -0
- package/dist/ide/mcp-server.js.map +1 -0
- package/dist/learning.d.ts +179 -0
- package/dist/learning.d.ts.map +1 -0
- package/dist/learning.js +829 -0
- package/dist/learning.js.map +1 -0
- package/dist/learning.test.d.ts +2 -0
- package/dist/learning.test.d.ts.map +1 -0
- package/dist/learning.test.js +115 -0
- package/dist/learning.test.js.map +1 -0
- package/dist/matrix.d.ts +49 -0
- package/dist/matrix.d.ts.map +1 -0
- package/dist/matrix.js +302 -0
- package/dist/matrix.js.map +1 -0
- package/dist/memory.d.ts +11 -0
- package/dist/memory.d.ts.map +1 -1
- package/dist/memory.js +54 -2
- package/dist/memory.js.map +1 -1
- package/dist/multimodal.d.ts +57 -0
- package/dist/multimodal.d.ts.map +1 -0
- package/dist/multimodal.js +206 -0
- package/dist/multimodal.js.map +1 -0
- package/dist/permissions.d.ts +21 -0
- package/dist/permissions.d.ts.map +1 -0
- package/dist/permissions.js +122 -0
- package/dist/permissions.js.map +1 -0
- package/dist/planner.d.ts +54 -0
- package/dist/planner.d.ts.map +1 -0
- package/dist/planner.js +298 -0
- package/dist/planner.js.map +1 -0
- package/dist/plugins.d.ts +30 -0
- package/dist/plugins.d.ts.map +1 -0
- package/dist/plugins.js +135 -0
- package/dist/plugins.js.map +1 -0
- package/dist/sessions.d.ts +38 -0
- package/dist/sessions.d.ts.map +1 -0
- package/dist/sessions.js +177 -0
- package/dist/sessions.js.map +1 -0
- package/dist/streaming.d.ts +88 -0
- package/dist/streaming.d.ts.map +1 -0
- package/dist/streaming.js +317 -0
- package/dist/streaming.js.map +1 -0
- package/dist/tools/background.d.ts +2 -0
- package/dist/tools/background.d.ts.map +1 -0
- package/dist/tools/background.js +163 -0
- package/dist/tools/background.js.map +1 -0
- package/dist/tools/bash.d.ts.map +1 -1
- package/dist/tools/bash.js +26 -1
- package/dist/tools/bash.js.map +1 -1
- package/dist/tools/browser.js +7 -7
- package/dist/tools/browser.js.map +1 -1
- package/dist/tools/build-matrix.d.ts +2 -0
- package/dist/tools/build-matrix.d.ts.map +1 -0
- package/dist/tools/build-matrix.js +463 -0
- package/dist/tools/build-matrix.js.map +1 -0
- package/dist/tools/computer.js +5 -5
- package/dist/tools/computer.js.map +1 -1
- package/dist/tools/fetch.d.ts +2 -0
- package/dist/tools/fetch.d.ts.map +1 -0
- package/dist/tools/fetch.js +106 -0
- package/dist/tools/fetch.js.map +1 -0
- package/dist/tools/files.d.ts.map +1 -1
- package/dist/tools/files.js +112 -6
- package/dist/tools/files.js.map +1 -1
- package/dist/tools/git.js +3 -3
- package/dist/tools/git.js.map +1 -1
- package/dist/tools/github.d.ts +2 -0
- package/dist/tools/github.d.ts.map +1 -0
- package/dist/tools/github.js +196 -0
- package/dist/tools/github.js.map +1 -0
- package/dist/tools/index.d.ts +29 -5
- package/dist/tools/index.d.ts.map +1 -1
- package/dist/tools/index.js +136 -20
- package/dist/tools/index.js.map +1 -1
- package/dist/tools/index.test.d.ts +2 -0
- package/dist/tools/index.test.d.ts.map +1 -0
- package/dist/tools/index.test.js +162 -0
- package/dist/tools/index.test.js.map +1 -0
- package/dist/tools/matrix.d.ts +2 -0
- package/dist/tools/matrix.d.ts.map +1 -0
- package/dist/tools/matrix.js +79 -0
- package/dist/tools/matrix.js.map +1 -0
- package/dist/tools/mcp-client.d.ts +2 -0
- package/dist/tools/mcp-client.d.ts.map +1 -0
- package/dist/tools/mcp-client.js +295 -0
- package/dist/tools/mcp-client.js.map +1 -0
- package/dist/tools/notebook.d.ts +2 -0
- package/dist/tools/notebook.d.ts.map +1 -0
- package/dist/tools/notebook.js +207 -0
- package/dist/tools/notebook.js.map +1 -0
- package/dist/tools/openclaw.d.ts +2 -0
- package/dist/tools/openclaw.d.ts.map +1 -0
- package/dist/tools/openclaw.js +187 -0
- package/dist/tools/openclaw.js.map +1 -0
- package/dist/tools/parallel.d.ts +2 -0
- package/dist/tools/parallel.d.ts.map +1 -0
- package/dist/tools/parallel.js +60 -0
- package/dist/tools/parallel.js.map +1 -0
- package/dist/tools/sandbox.d.ts +2 -0
- package/dist/tools/sandbox.d.ts.map +1 -0
- package/dist/tools/sandbox.js +352 -0
- package/dist/tools/sandbox.js.map +1 -0
- package/dist/tools/search.d.ts.map +1 -1
- package/dist/tools/search.js +135 -28
- package/dist/tools/search.js.map +1 -1
- package/dist/tools/subagent.d.ts +4 -0
- package/dist/tools/subagent.d.ts.map +1 -0
- package/dist/tools/subagent.js +260 -0
- package/dist/tools/subagent.js.map +1 -0
- package/dist/tools/tasks.d.ts +14 -0
- package/dist/tools/tasks.d.ts.map +1 -0
- package/dist/tools/tasks.js +210 -0
- package/dist/tools/tasks.js.map +1 -0
- package/dist/tools/worktree.d.ts +2 -0
- package/dist/tools/worktree.d.ts.map +1 -0
- package/dist/tools/worktree.js +223 -0
- package/dist/tools/worktree.js.map +1 -0
- package/dist/tui.d.ts +73 -0
- package/dist/tui.d.ts.map +1 -0
- package/dist/tui.js +257 -0
- package/dist/tui.js.map +1 -0
- package/dist/ui.d.ts +11 -19
- package/dist/ui.d.ts.map +1 -1
- package/dist/ui.js +143 -171
- package/dist/ui.js.map +1 -1
- package/dist/updater.d.ts +3 -0
- package/dist/updater.d.ts.map +1 -0
- package/dist/updater.js +70 -0
- package/dist/updater.js.map +1 -0
- package/install.sh +5 -7
- package/package.json +8 -4
package/dist/cli.js
CHANGED
|
@@ -10,126 +10,562 @@
|
|
|
10
10
|
// $ kbot usage # Show usage stats
|
|
11
11
|
import { createInterface } from 'node:readline';
|
|
12
12
|
import { Command } from 'commander';
|
|
13
|
-
import {
|
|
13
|
+
import { loadConfig, setupByok, isByokEnabled, isLocalProvider, disableByok, detectProvider, getByokProvider, PROVIDERS, setupOllama, setupOpenClaw, isOllamaRunning, listOllamaModels, warmOllamaModelCache } from './auth.js';
|
|
14
14
|
import { runAndPrint, runAgent } from './agent.js';
|
|
15
15
|
import { gatherContext } from './context.js';
|
|
16
16
|
import { registerAllTools } from './tools/index.js';
|
|
17
|
-
import { clearHistory, clearMemory } from './memory.js';
|
|
18
|
-
import {
|
|
19
|
-
|
|
17
|
+
import { clearHistory, clearMemory, compactHistory, restoreHistory } from './memory.js';
|
|
18
|
+
import { saveSession, loadSession, listSessions, deleteSession, formatSessionList, } from './sessions.js';
|
|
19
|
+
import { createAgent, removeAgent, getAgent, formatAgentList, formatAgentDetail, PRESETS, getMatrixAgentIds, activateMimic, listMimicProfiles, getMimicProfile, } from './matrix.js';
|
|
20
|
+
import { getExtendedStats, incrementSessions, learnFact, selfTrain, getTrainingLog, flushPendingWrites } from './learning.js';
|
|
21
|
+
import { banner, bannerCompact, bannerAuth, prompt as kbotPrompt, printError, printSuccess, printInfo, printResponse, printHelp, printGoodbye, setQuiet, } from './ui.js';
|
|
22
|
+
import { checkForUpdate } from './updater.js';
|
|
23
|
+
import chalk from 'chalk';
|
|
24
|
+
const VERSION = '2.3.0';
|
|
20
25
|
async function main() {
|
|
21
26
|
const program = new Command();
|
|
22
27
|
program
|
|
23
28
|
.name('kbot')
|
|
24
|
-
.description('K:BOT —
|
|
29
|
+
.description('K:BOT — Open-source terminal AI agent. Bring your own key, pick your model, run locally.')
|
|
25
30
|
.version(VERSION)
|
|
26
31
|
.option('-a, --agent <agent>', 'Force a specific agent (kernel, researcher, coder, writer, analyst)')
|
|
27
32
|
.option('-m, --model <model>', 'Override AI model (auto, sonnet, haiku)')
|
|
28
33
|
.option('-s, --stream', 'Stream the response')
|
|
29
|
-
.option('--
|
|
34
|
+
.option('-p, --pipe', 'Pipe mode — raw text output for scripting')
|
|
35
|
+
.option('--json', 'JSON output for scripting')
|
|
36
|
+
.option('-y, --yes', 'Skip all confirmation prompts')
|
|
37
|
+
.option('-q, --quiet', 'Minimal output — no banners, spinners, or status')
|
|
38
|
+
.option('--resume [session]', 'Resume a saved session')
|
|
39
|
+
.option('--computer-use', 'Enable computer use tools')
|
|
40
|
+
.option('-t, --thinking', 'Show AI reasoning steps')
|
|
41
|
+
.option('--thinking-budget <tokens>', 'Thinking token budget (default: 10000)')
|
|
42
|
+
.option('--safe', 'Confirm destructive operations')
|
|
43
|
+
.option('--strict', 'Confirm ALL operations')
|
|
30
44
|
.argument('[prompt...]', 'One-shot prompt')
|
|
31
45
|
.helpOption('-h, --help', 'display help for command')
|
|
32
46
|
.addHelpCommand(false)
|
|
33
47
|
.action(() => { });
|
|
34
48
|
// Sub-commands
|
|
35
|
-
program
|
|
36
|
-
.command('
|
|
37
|
-
.description('
|
|
49
|
+
const ideCmd = program
|
|
50
|
+
.command('ide')
|
|
51
|
+
.description('Start IDE protocol server (MCP, ACP)');
|
|
52
|
+
ideCmd
|
|
53
|
+
.command('mcp')
|
|
54
|
+
.description('Start MCP server for VS Code, Cursor, Windsurf, Zed')
|
|
55
|
+
.action(async () => {
|
|
56
|
+
const { startMcpServer } = await import('./ide/mcp-server.js');
|
|
57
|
+
await startMcpServer();
|
|
58
|
+
});
|
|
59
|
+
ideCmd
|
|
60
|
+
.command('acp')
|
|
61
|
+
.description('Start ACP server for JetBrains IDEs (IntelliJ, WebStorm, PyCharm)')
|
|
38
62
|
.action(async () => {
|
|
39
|
-
await
|
|
63
|
+
const { startAcpServer } = await import('./ide/acp-server.js');
|
|
64
|
+
await startAcpServer();
|
|
65
|
+
});
|
|
66
|
+
ideCmd
|
|
67
|
+
.command('status')
|
|
68
|
+
.description('Show IDE bridge status')
|
|
69
|
+
.action(async () => {
|
|
70
|
+
const { initBridge, getStatus } = await import('./ide/bridge.js');
|
|
71
|
+
await initBridge();
|
|
72
|
+
const status = getStatus();
|
|
73
|
+
printInfo('K:BOT IDE Bridge Status');
|
|
74
|
+
printInfo(` Version: ${status.version}`);
|
|
75
|
+
printInfo(` Agent: ${status.agent}`);
|
|
76
|
+
printInfo(` Tier: ${status.tier}`);
|
|
77
|
+
printInfo(` Tools: ${status.toolCount}`);
|
|
78
|
+
printInfo(` Patterns: ${status.learning.patternsCount}`);
|
|
79
|
+
printInfo(` Knowledge: ${status.learning.knowledgeCount}`);
|
|
80
|
+
printInfo(` Sessions: ${status.sessionCount}`);
|
|
81
|
+
console.log();
|
|
82
|
+
printInfo('Protocols:');
|
|
83
|
+
printInfo(' kbot ide mcp — VS Code, Cursor, Windsurf, Zed, Neovim');
|
|
84
|
+
printInfo(' kbot ide acp — IntelliJ, WebStorm, PyCharm, GoLand, Android Studio');
|
|
40
85
|
});
|
|
41
86
|
program
|
|
42
87
|
.command('byok')
|
|
43
|
-
.description('Bring Your Own Key —
|
|
44
|
-
.option('--off', 'Disable BYOK mode
|
|
88
|
+
.description('Bring Your Own Key — configure your LLM API key (14 providers)')
|
|
89
|
+
.option('--off', 'Disable BYOK mode')
|
|
90
|
+
.action(async (opts) => {
|
|
91
|
+
if (opts.off) {
|
|
92
|
+
disableByok();
|
|
93
|
+
printSuccess('BYOK provider disabled.');
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
96
|
+
await byokFlow();
|
|
97
|
+
});
|
|
98
|
+
// Alias: `kbot auth` → same as `kbot byok` (more intuitive name)
|
|
99
|
+
program
|
|
100
|
+
.command('auth')
|
|
101
|
+
.description('Configure your LLM API key (alias for byok)')
|
|
102
|
+
.option('--off', 'Disable provider')
|
|
45
103
|
.action(async (opts) => {
|
|
46
104
|
if (opts.off) {
|
|
47
105
|
disableByok();
|
|
48
|
-
printSuccess('
|
|
106
|
+
printSuccess('Provider disabled.');
|
|
49
107
|
return;
|
|
50
108
|
}
|
|
51
109
|
await byokFlow();
|
|
52
110
|
});
|
|
53
111
|
program
|
|
54
|
-
.command('
|
|
55
|
-
.description('
|
|
112
|
+
.command('ollama')
|
|
113
|
+
.description('Use Ollama for local AI models (no API key needed)')
|
|
114
|
+
.option('--model <model>', 'Set default Ollama model')
|
|
115
|
+
.option('--list', 'List available Ollama models')
|
|
116
|
+
.option('--off', 'Disable Ollama mode')
|
|
117
|
+
.action(async (opts) => {
|
|
118
|
+
if (opts.off) {
|
|
119
|
+
disableByok();
|
|
120
|
+
printSuccess('Ollama disabled. Provider disabled.');
|
|
121
|
+
return;
|
|
122
|
+
}
|
|
123
|
+
if (opts.list) {
|
|
124
|
+
const running = await isOllamaRunning();
|
|
125
|
+
if (!running) {
|
|
126
|
+
printError('Ollama is not running. Start it with: ollama serve');
|
|
127
|
+
return;
|
|
128
|
+
}
|
|
129
|
+
const models = await listOllamaModels();
|
|
130
|
+
if (models.length === 0) {
|
|
131
|
+
printError('No models found. Pull one with: ollama pull llama3.1:8b');
|
|
132
|
+
return;
|
|
133
|
+
}
|
|
134
|
+
printInfo('Available Ollama models:');
|
|
135
|
+
for (const m of models)
|
|
136
|
+
printInfo(` • ${m}`);
|
|
137
|
+
return;
|
|
138
|
+
}
|
|
139
|
+
const running = await isOllamaRunning();
|
|
140
|
+
if (!running) {
|
|
141
|
+
printError('Ollama is not running. Start it with: ollama serve');
|
|
142
|
+
return;
|
|
143
|
+
}
|
|
144
|
+
const ok = await setupOllama(opts.model);
|
|
145
|
+
if (ok) {
|
|
146
|
+
const models = await listOllamaModels();
|
|
147
|
+
printSuccess(`Ollama enabled! Using local models (${models.length} available). $0 cost.`);
|
|
148
|
+
printInfo(`Default model: ${opts.model || PROVIDERS.ollama.defaultModel}`);
|
|
149
|
+
printInfo('Switch models: kbot ollama --model <name>');
|
|
150
|
+
}
|
|
151
|
+
else {
|
|
152
|
+
printError('Failed to connect to Ollama. Is it running? Try: ollama serve');
|
|
153
|
+
}
|
|
154
|
+
});
|
|
155
|
+
program
|
|
156
|
+
.command('doctor')
|
|
157
|
+
.description('Diagnose your kbot setup — check everything is working')
|
|
56
158
|
.action(async () => {
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
159
|
+
process.stderr.write('\n');
|
|
160
|
+
printInfo('K:BOT Doctor — Checking your setup...');
|
|
161
|
+
process.stderr.write('\n');
|
|
162
|
+
// 1. Check BYOK provider
|
|
163
|
+
const config = loadConfig();
|
|
164
|
+
if (config?.byok_enabled && config?.byok_provider) {
|
|
165
|
+
const p = PROVIDERS[config.byok_provider];
|
|
166
|
+
printSuccess(`BYOK provider: ${p?.name || config.byok_provider}`);
|
|
167
|
+
}
|
|
168
|
+
// 3. Check Ollama
|
|
169
|
+
const ollamaUp = await isOllamaRunning();
|
|
170
|
+
if (ollamaUp) {
|
|
171
|
+
const models = await listOllamaModels();
|
|
172
|
+
printSuccess(`Ollama: running (${models.length} models)`);
|
|
173
|
+
for (const m of models)
|
|
174
|
+
printInfo(` ${m}`);
|
|
175
|
+
}
|
|
176
|
+
else {
|
|
177
|
+
printInfo(' Ollama: not running (optional — run: ollama serve)');
|
|
178
|
+
}
|
|
179
|
+
// 4. Check OpenClaw
|
|
180
|
+
try {
|
|
181
|
+
const res = await fetch('http://127.0.0.1:18789/health', { signal: AbortSignal.timeout(2000) });
|
|
182
|
+
if (res.ok) {
|
|
183
|
+
printSuccess('OpenClaw: gateway running at 127.0.0.1:18789');
|
|
184
|
+
}
|
|
185
|
+
else {
|
|
186
|
+
printInfo(' OpenClaw: gateway not responding (optional)');
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
catch {
|
|
190
|
+
printInfo(' OpenClaw: gateway offline (optional — run: openclaw-cmd start)');
|
|
191
|
+
}
|
|
192
|
+
// 5. Check Docker (for sandbox tools)
|
|
193
|
+
try {
|
|
194
|
+
const { execSync } = await import('node:child_process');
|
|
195
|
+
execSync('docker info', { timeout: 5000, stdio: 'pipe' });
|
|
196
|
+
printSuccess('Docker: running (sandbox execution available)');
|
|
197
|
+
}
|
|
198
|
+
catch {
|
|
199
|
+
printInfo(' Docker: not running (optional — needed for sandbox_run)');
|
|
200
|
+
}
|
|
201
|
+
// 6. Check git
|
|
202
|
+
try {
|
|
203
|
+
const { execSync } = await import('node:child_process');
|
|
204
|
+
const ver = execSync('git --version', { encoding: 'utf-8', timeout: 3000 }).trim();
|
|
205
|
+
printSuccess(`Git: ${ver}`);
|
|
206
|
+
}
|
|
207
|
+
catch {
|
|
208
|
+
printError('Git: not found (needed for git tools)');
|
|
209
|
+
}
|
|
210
|
+
// 7. Check Node.js version
|
|
211
|
+
printSuccess(`Node.js: ${process.version}`);
|
|
212
|
+
// 8. Check config directory
|
|
213
|
+
const { existsSync } = await import('node:fs');
|
|
214
|
+
const { homedir } = await import('node:os');
|
|
215
|
+
const { join } = await import('node:path');
|
|
216
|
+
const kbotDir = join(homedir(), '.kbot');
|
|
217
|
+
if (existsSync(kbotDir)) {
|
|
218
|
+
printSuccess(`Config: ${kbotDir}`);
|
|
219
|
+
}
|
|
220
|
+
else {
|
|
221
|
+
printInfo(` Config directory will be created at: ${kbotDir}`);
|
|
222
|
+
}
|
|
223
|
+
process.stderr.write('\n');
|
|
224
|
+
// Summary
|
|
225
|
+
const hasProvider = !!((config?.byok_enabled && config?.byok_provider) || ollamaUp);
|
|
226
|
+
if (hasProvider) {
|
|
227
|
+
printSuccess('Ready to go! Run: kbot');
|
|
228
|
+
}
|
|
229
|
+
else {
|
|
230
|
+
printInfo('Get started with one of:');
|
|
231
|
+
printInfo(' kbot ollama — Free local AI');
|
|
232
|
+
printInfo(' kbot byok — Use your own API key');
|
|
233
|
+
}
|
|
234
|
+
process.stderr.write('\n');
|
|
235
|
+
});
|
|
236
|
+
program
|
|
237
|
+
.command('pull')
|
|
238
|
+
.description('Download recommended Ollama models for local AI')
|
|
239
|
+
.option('--model <model>', 'Pull a specific model')
|
|
240
|
+
.action(async (opts) => {
|
|
241
|
+
const running = await isOllamaRunning();
|
|
242
|
+
if (!running) {
|
|
243
|
+
printError('Ollama is not running. Start with: ollama serve');
|
|
244
|
+
return;
|
|
245
|
+
}
|
|
246
|
+
if (opts.model) {
|
|
247
|
+
printInfo(`Pulling ${opts.model}...`);
|
|
248
|
+
try {
|
|
249
|
+
const { execSync } = await import('node:child_process');
|
|
250
|
+
execSync(`ollama pull ${opts.model}`, { stdio: 'inherit', timeout: 600_000 });
|
|
251
|
+
printSuccess(`${opts.model} ready!`);
|
|
252
|
+
}
|
|
253
|
+
catch {
|
|
254
|
+
printError(`Failed to pull ${opts.model}`);
|
|
255
|
+
}
|
|
256
|
+
return;
|
|
257
|
+
}
|
|
258
|
+
const models = await listOllamaModels();
|
|
259
|
+
const recommended = [
|
|
260
|
+
{ name: 'qwen2.5-coder:7b', desc: 'Best for coding (4.7 GB)', category: 'code' },
|
|
261
|
+
{ name: 'llama3.1:8b', desc: 'General purpose (4.9 GB)', category: 'general' },
|
|
262
|
+
{ name: 'mistral:7b', desc: 'General purpose (4.4 GB)', category: 'general' },
|
|
263
|
+
{ name: 'phi4:14b', desc: 'Reasoning & analysis (8.4 GB)', category: 'reasoning' },
|
|
264
|
+
{ name: 'gemma3:12b', desc: 'Writing & research (8.1 GB)', category: 'general' },
|
|
265
|
+
{ name: 'deepseek-coder-v2:16b', desc: 'Advanced coding (9.7 GB)', category: 'code' },
|
|
266
|
+
{ name: 'codellama:13b', desc: 'Meta code model (7.4 GB)', category: 'code' },
|
|
267
|
+
{ name: 'starcoder2:7b', desc: 'Code completion (4.0 GB)', category: 'code' },
|
|
268
|
+
{ name: 'llama3.2:3b', desc: 'Lightweight fast model (2.0 GB)', category: 'general' },
|
|
269
|
+
{ name: 'codegemma:7b', desc: 'Google code model (5.0 GB)', category: 'code' },
|
|
270
|
+
{ name: 'nemotron-mini', desc: 'NVIDIA Nemotron Mini 4B (2.5 GB)', category: 'general' },
|
|
271
|
+
{ name: 'nemotron-3-nano', desc: 'NVIDIA Nemotron 3 Nano 8B (4.5 GB)', category: 'reasoning' },
|
|
272
|
+
{ name: 'nomic-embed-text', desc: 'Embeddings model (274 MB)', category: 'embeddings' },
|
|
273
|
+
];
|
|
274
|
+
printInfo('Recommended models for kbot:');
|
|
275
|
+
console.log();
|
|
276
|
+
for (const rec of recommended) {
|
|
277
|
+
const installed = models.some(m => m.startsWith(rec.name.split(':')[0]));
|
|
278
|
+
const icon = installed ? ' ✓' : ' ○';
|
|
279
|
+
const status = installed ? '(installed)' : '';
|
|
280
|
+
printInfo(`${icon} ${rec.name.padEnd(25)} ${rec.desc} ${status}`);
|
|
281
|
+
}
|
|
282
|
+
console.log();
|
|
283
|
+
const missing = recommended.filter(r => !models.some(m => m.startsWith(r.name.split(':')[0])));
|
|
284
|
+
if (missing.length === 0) {
|
|
285
|
+
printSuccess('All recommended models installed!');
|
|
286
|
+
}
|
|
287
|
+
else {
|
|
288
|
+
printInfo(`Pull missing models: kbot pull --model <name>`);
|
|
289
|
+
printInfo(`Or pull all: ${missing.map(m => `ollama pull ${m.name}`).join(' && ')}`);
|
|
290
|
+
}
|
|
291
|
+
});
|
|
292
|
+
program
|
|
293
|
+
.command('openclaw')
|
|
294
|
+
.description('Use OpenClaw gateway as AI provider')
|
|
295
|
+
.option('--token <token>', 'Gateway auth token')
|
|
296
|
+
.option('--off', 'Disable OpenClaw mode')
|
|
297
|
+
.action(async (opts) => {
|
|
298
|
+
if (opts.off) {
|
|
299
|
+
disableByok();
|
|
300
|
+
printSuccess('OpenClaw disabled. Provider disabled.');
|
|
301
|
+
return;
|
|
302
|
+
}
|
|
303
|
+
const ok = await setupOpenClaw(opts.token);
|
|
304
|
+
if (ok) {
|
|
305
|
+
printSuccess('OpenClaw enabled! Connected to local gateway at 127.0.0.1:18789.');
|
|
306
|
+
}
|
|
307
|
+
else {
|
|
308
|
+
printError('Cannot connect to OpenClaw gateway. Start it first.');
|
|
61
309
|
}
|
|
62
|
-
printUsage(stats);
|
|
63
310
|
});
|
|
64
311
|
program.parse(process.argv);
|
|
65
312
|
const opts = program.opts();
|
|
66
313
|
const promptArgs = program.args;
|
|
314
|
+
// Quiet mode: suppress banners, spinners, status messages
|
|
315
|
+
if (opts.quiet)
|
|
316
|
+
setQuiet(true);
|
|
67
317
|
// If a sub-command was run, we're done
|
|
68
|
-
if (['auth', '
|
|
69
|
-
return;
|
|
70
|
-
// Check for API key (Kernel or BYOK)
|
|
71
|
-
const byokActive = isByokEnabled();
|
|
72
|
-
const apiKey = getApiKey();
|
|
73
|
-
if (!apiKey && !byokActive) {
|
|
74
|
-
console.log(banner());
|
|
75
|
-
printInfo('No API key found. Let\'s connect you to the Matrix.');
|
|
76
|
-
printInfo('Or run `kbot byok` to use your own Anthropic API key.');
|
|
77
|
-
console.log();
|
|
78
|
-
await authFlow();
|
|
318
|
+
if (['byok', 'auth', 'ide', 'ollama', 'openclaw', 'pull', 'doctor'].includes(program.args[0]))
|
|
79
319
|
return;
|
|
320
|
+
// Check for API key (BYOK or local provider)
|
|
321
|
+
let byokActive = isByokEnabled();
|
|
322
|
+
let localActive = byokActive && isLocalProvider(getByokProvider());
|
|
323
|
+
// AUTO-SETUP: If no provider configured, try to auto-detect and configure one
|
|
324
|
+
if (!byokActive) {
|
|
325
|
+
// Priority 1: Check environment variables (instant, no network)
|
|
326
|
+
const envDetected = autoDetectFromEnv();
|
|
327
|
+
if (envDetected) {
|
|
328
|
+
byokActive = true;
|
|
329
|
+
localActive = isLocalProvider(envDetected);
|
|
330
|
+
printSuccess(`Auto-detected ${PROVIDERS[envDetected].name} from environment.`);
|
|
331
|
+
}
|
|
332
|
+
// Priority 2: Check local providers in PARALLEL (both at once, use whichever responds first)
|
|
333
|
+
if (!byokActive) {
|
|
334
|
+
const [ollamaResult, openclawResult] = await Promise.allSettled([
|
|
335
|
+
// Check Ollama
|
|
336
|
+
(async () => {
|
|
337
|
+
const up = await isOllamaRunning();
|
|
338
|
+
if (!up)
|
|
339
|
+
return null;
|
|
340
|
+
const models = await listOllamaModels();
|
|
341
|
+
if (models.length === 0)
|
|
342
|
+
return null;
|
|
343
|
+
const ok = await setupOllama();
|
|
344
|
+
return ok ? { provider: 'ollama', models: models.length } : null;
|
|
345
|
+
})(),
|
|
346
|
+
// Check OpenClaw
|
|
347
|
+
(async () => {
|
|
348
|
+
try {
|
|
349
|
+
const res = await fetch('http://127.0.0.1:18789/health', { signal: AbortSignal.timeout(1500) });
|
|
350
|
+
if (!res.ok)
|
|
351
|
+
return null;
|
|
352
|
+
const ok = await setupOpenClaw();
|
|
353
|
+
return ok ? { provider: 'openclaw' } : null;
|
|
354
|
+
}
|
|
355
|
+
catch {
|
|
356
|
+
return null;
|
|
357
|
+
}
|
|
358
|
+
})(),
|
|
359
|
+
]);
|
|
360
|
+
// Prefer Ollama if both available (more models, more control)
|
|
361
|
+
const ollamaOk = ollamaResult.status === 'fulfilled' && ollamaResult.value;
|
|
362
|
+
const openclawOk = openclawResult.status === 'fulfilled' && openclawResult.value;
|
|
363
|
+
if (ollamaOk) {
|
|
364
|
+
byokActive = true;
|
|
365
|
+
localActive = true;
|
|
366
|
+
printSuccess(`Auto-configured Ollama (${ollamaOk.models} models). Ready — $0 cost!`);
|
|
367
|
+
}
|
|
368
|
+
else if (openclawOk) {
|
|
369
|
+
byokActive = true;
|
|
370
|
+
localActive = true;
|
|
371
|
+
printSuccess('Auto-configured OpenClaw gateway. Ready — $0 cost!');
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
// Still no provider — launch guided setup for new users
|
|
375
|
+
if (!byokActive) {
|
|
376
|
+
const result = await guidedSetup();
|
|
377
|
+
if (!result)
|
|
378
|
+
return;
|
|
379
|
+
byokActive = true;
|
|
380
|
+
localActive = result.local;
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
/** Auto-detect provider from environment variables */
|
|
384
|
+
function autoDetectFromEnv() {
|
|
385
|
+
const envKeys = [
|
|
386
|
+
{ env: 'ANTHROPIC_API_KEY', provider: 'anthropic' },
|
|
387
|
+
{ env: 'OPENAI_API_KEY', provider: 'openai' },
|
|
388
|
+
{ env: 'GOOGLE_API_KEY', provider: 'google' },
|
|
389
|
+
{ env: 'MISTRAL_API_KEY', provider: 'mistral' },
|
|
390
|
+
{ env: 'XAI_API_KEY', provider: 'xai' },
|
|
391
|
+
{ env: 'DEEPSEEK_API_KEY', provider: 'deepseek' },
|
|
392
|
+
{ env: 'GROQ_API_KEY', provider: 'groq' },
|
|
393
|
+
{ env: 'TOGETHER_API_KEY', provider: 'together' },
|
|
394
|
+
{ env: 'FIREWORKS_API_KEY', provider: 'fireworks' },
|
|
395
|
+
{ env: 'PERPLEXITY_API_KEY', provider: 'perplexity' },
|
|
396
|
+
{ env: 'COHERE_API_KEY', provider: 'cohere' },
|
|
397
|
+
{ env: 'NVIDIA_API_KEY', provider: 'nvidia' },
|
|
398
|
+
];
|
|
399
|
+
for (const { env, provider } of envKeys) {
|
|
400
|
+
if (process.env[env])
|
|
401
|
+
return provider;
|
|
402
|
+
}
|
|
403
|
+
return null;
|
|
80
404
|
}
|
|
81
|
-
//
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
405
|
+
// Permission mode: autonomous by default, users opt-in to confirmations
|
|
406
|
+
{
|
|
407
|
+
const { setPermissionMode } = await import('./permissions.js');
|
|
408
|
+
if (opts.yes) {
|
|
409
|
+
// --yes / -y: skip all confirmations (for scripts & CI)
|
|
410
|
+
setPermissionMode('permissive');
|
|
411
|
+
}
|
|
412
|
+
else if (opts.strict) {
|
|
413
|
+
setPermissionMode('strict');
|
|
414
|
+
}
|
|
415
|
+
else if (opts.safe || process.env.KBOT_SAFE) {
|
|
416
|
+
setPermissionMode('normal');
|
|
417
|
+
}
|
|
418
|
+
else {
|
|
419
|
+
// DEFAULT: permissive — kbot acts autonomously, no confirmation prompts
|
|
420
|
+
setPermissionMode('permissive');
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
// Parallel startup: register tools, gather context, and check updates simultaneously
|
|
424
|
+
const [, context] = await Promise.all([
|
|
425
|
+
registerAllTools({ computerUse: opts.computerUse }),
|
|
426
|
+
Promise.resolve(gatherContext()),
|
|
427
|
+
// Non-blocking update check — fire and forget
|
|
428
|
+
Promise.resolve().then(() => {
|
|
429
|
+
try {
|
|
430
|
+
const msg = checkForUpdate(VERSION);
|
|
431
|
+
if (msg)
|
|
432
|
+
printSuccess(msg);
|
|
433
|
+
}
|
|
434
|
+
catch { /* non-critical */ }
|
|
435
|
+
}),
|
|
436
|
+
]);
|
|
85
437
|
const config = loadConfig();
|
|
86
|
-
const tier =
|
|
438
|
+
const tier = 'free';
|
|
87
439
|
const agentOpts = {
|
|
88
440
|
agent: opts.agent || 'auto',
|
|
89
441
|
model: opts.model,
|
|
90
|
-
stream: opts.stream,
|
|
442
|
+
stream: opts.stream ?? true, // Stream by default for faster perceived response
|
|
91
443
|
context,
|
|
92
444
|
tier,
|
|
445
|
+
thinking: opts.thinking || false,
|
|
446
|
+
thinkingBudget: opts.thinkingBudget ? (parseInt(opts.thinkingBudget, 10) || 10000) : undefined,
|
|
93
447
|
};
|
|
94
|
-
//
|
|
95
|
-
if (
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
448
|
+
// Pipe mode: echo "prompt" | kbot -p OR kbot -p "prompt"
|
|
449
|
+
if (opts.pipe) {
|
|
450
|
+
let message = promptArgs.join(' ');
|
|
451
|
+
if (!message && !process.stdin.isTTY) {
|
|
452
|
+
// Read from stdin pipe
|
|
453
|
+
const chunks = [];
|
|
454
|
+
for await (const chunk of process.stdin)
|
|
455
|
+
chunks.push(chunk);
|
|
456
|
+
message = Buffer.concat(chunks).toString('utf-8').trim();
|
|
457
|
+
}
|
|
458
|
+
if (!message) {
|
|
459
|
+
process.stderr.write('Error: no prompt provided\n');
|
|
460
|
+
process.exit(1);
|
|
461
|
+
}
|
|
462
|
+
try {
|
|
463
|
+
const response = await runAgent(message, agentOpts);
|
|
464
|
+
if (opts.json) {
|
|
465
|
+
process.stdout.write(JSON.stringify({
|
|
466
|
+
content: response.content,
|
|
467
|
+
agent: response.agent,
|
|
468
|
+
model: response.model,
|
|
469
|
+
toolCalls: response.toolCalls,
|
|
470
|
+
usage: response.usage,
|
|
471
|
+
}) + '\n');
|
|
472
|
+
}
|
|
473
|
+
else {
|
|
474
|
+
process.stdout.write(response.content + '\n');
|
|
475
|
+
}
|
|
476
|
+
}
|
|
477
|
+
catch (err) {
|
|
478
|
+
process.stderr.write(`Error: ${err instanceof Error ? err.message : String(err)}\n`);
|
|
479
|
+
process.exit(1);
|
|
480
|
+
}
|
|
99
481
|
return;
|
|
100
482
|
}
|
|
101
|
-
//
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
483
|
+
// Resume session if --resume flag is used
|
|
484
|
+
if (opts.resume) {
|
|
485
|
+
const sessionId = typeof opts.resume === 'string' ? opts.resume : undefined;
|
|
486
|
+
if (sessionId) {
|
|
487
|
+
const session = loadSession(sessionId);
|
|
488
|
+
if (session) {
|
|
489
|
+
restoreHistory(session.history);
|
|
490
|
+
if (session.agent)
|
|
491
|
+
agentOpts.agent = session.agent;
|
|
492
|
+
printSuccess(`Resumed session: ${session.name} (${session.turnCount} turns)`);
|
|
493
|
+
}
|
|
494
|
+
else {
|
|
495
|
+
printError(`Session not found: ${sessionId}`);
|
|
496
|
+
printInfo('Run /sessions to list saved sessions.');
|
|
497
|
+
}
|
|
498
|
+
}
|
|
499
|
+
else {
|
|
500
|
+
// Resume most recent session
|
|
501
|
+
const { getLastSession } = await import('./sessions.js');
|
|
502
|
+
const last = getLastSession();
|
|
503
|
+
if (last) {
|
|
504
|
+
restoreHistory(last.history);
|
|
505
|
+
if (last.agent)
|
|
506
|
+
agentOpts.agent = last.agent;
|
|
507
|
+
printSuccess(`Resumed last session: ${last.name}`);
|
|
508
|
+
}
|
|
509
|
+
else {
|
|
510
|
+
printInfo('No previous sessions found.');
|
|
511
|
+
}
|
|
512
|
+
}
|
|
118
513
|
}
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
514
|
+
// Stdin pipe composability: cat file.txt | kbot "explain this"
|
|
515
|
+
// Read stdin BEFORE one-shot check, so it's available for both paths
|
|
516
|
+
let stdinContent = '';
|
|
517
|
+
if (!process.stdin.isTTY) {
|
|
518
|
+
const chunks = [];
|
|
519
|
+
for await (const chunk of process.stdin)
|
|
520
|
+
chunks.push(chunk);
|
|
521
|
+
stdinContent = Buffer.concat(chunks).toString('utf-8').trim();
|
|
124
522
|
}
|
|
125
|
-
|
|
126
|
-
if (!
|
|
127
|
-
|
|
128
|
-
|
|
523
|
+
// One-shot mode: kbot "fix the bug" — always stream for speed
|
|
524
|
+
if (promptArgs.length > 0 && !['byok', 'auth', 'ide', 'ollama', 'openclaw', 'pull', 'doctor'].includes(promptArgs[0])) {
|
|
525
|
+
if (!opts.pipe)
|
|
526
|
+
console.log(bannerCompact());
|
|
527
|
+
let message = promptArgs.join(' ');
|
|
528
|
+
// If stdin was piped in, prepend it as context
|
|
529
|
+
if (stdinContent) {
|
|
530
|
+
message = `${stdinContent}\n\n---\n${message}`;
|
|
531
|
+
}
|
|
532
|
+
// JSON output mode — structured output for scripting
|
|
533
|
+
if (opts.json) {
|
|
534
|
+
agentOpts.stream = false; // No streaming for JSON mode
|
|
535
|
+
const response = await runAgent(message, agentOpts);
|
|
536
|
+
process.stdout.write(JSON.stringify({
|
|
537
|
+
content: response.content,
|
|
538
|
+
agent: response.agent,
|
|
539
|
+
model: response.model,
|
|
540
|
+
toolCalls: response.toolCalls,
|
|
541
|
+
usage: response.usage,
|
|
542
|
+
}) + '\n');
|
|
543
|
+
return;
|
|
544
|
+
}
|
|
545
|
+
agentOpts.stream = true; // Force streaming for faster one-shot
|
|
546
|
+
await runAndPrint(message, agentOpts);
|
|
547
|
+
return;
|
|
129
548
|
}
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
549
|
+
// Stdin-only mode: echo "what is 2+2" | kbot (no prompt args, just piped input)
|
|
550
|
+
if (stdinContent && promptArgs.length === 0) {
|
|
551
|
+
if (opts.json) {
|
|
552
|
+
agentOpts.stream = false;
|
|
553
|
+
const response = await runAgent(stdinContent, agentOpts);
|
|
554
|
+
process.stdout.write(JSON.stringify({
|
|
555
|
+
content: response.content,
|
|
556
|
+
agent: response.agent,
|
|
557
|
+
model: response.model,
|
|
558
|
+
toolCalls: response.toolCalls,
|
|
559
|
+
usage: response.usage,
|
|
560
|
+
}) + '\n');
|
|
561
|
+
return;
|
|
562
|
+
}
|
|
563
|
+
agentOpts.stream = true;
|
|
564
|
+
await runAndPrint(stdinContent, agentOpts);
|
|
565
|
+
return;
|
|
566
|
+
}
|
|
567
|
+
// Interactive REPL mode
|
|
568
|
+
await startRepl(agentOpts, context, tier, byokActive, localActive);
|
|
133
569
|
}
|
|
134
570
|
async function byokFlow() {
|
|
135
571
|
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
@@ -138,11 +574,12 @@ async function byokFlow() {
|
|
|
138
574
|
printInfo('Use your own LLM API key. You pay the provider directly for tokens.');
|
|
139
575
|
printInfo('Kernel routing + tools + collective intelligence are free.');
|
|
140
576
|
console.log();
|
|
141
|
-
printInfo('Supported providers (
|
|
577
|
+
printInfo('Supported providers (14):');
|
|
142
578
|
printInfo(' Anthropic (Claude) OpenAI (GPT) Google (Gemini)');
|
|
143
579
|
printInfo(' Mistral AI xAI (Grok) DeepSeek');
|
|
144
580
|
printInfo(' Groq Together AI Fireworks AI');
|
|
145
|
-
printInfo(' Perplexity Cohere');
|
|
581
|
+
printInfo(' Perplexity Cohere NVIDIA NIM');
|
|
582
|
+
printInfo(' Ollama (local, free) OpenClaw (local gateway)');
|
|
146
583
|
console.log();
|
|
147
584
|
printInfo('Paste your API key (auto-detected by prefix):');
|
|
148
585
|
console.log();
|
|
@@ -196,81 +633,338 @@ async function byokFlow() {
|
|
|
196
633
|
}
|
|
197
634
|
console.log();
|
|
198
635
|
printSuccess(`BYOK mode enabled — ${providerConfig.name}`);
|
|
199
|
-
printInfo('You pay the provider directly. No message limits.');
|
|
200
|
-
printInfo('
|
|
636
|
+
printInfo('You pay the provider directly. No message limits. No restrictions.');
|
|
637
|
+
printInfo('All 81 tools + 17 agents + learning system = yours.');
|
|
201
638
|
console.log();
|
|
202
639
|
printSuccess('Ready. Run `kbot` to start.');
|
|
203
640
|
}
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
641
|
+
/** Guided setup for first-time users who have no AI experience */
|
|
642
|
+
async function guidedSetup() {
|
|
643
|
+
console.log(banner(VERSION));
|
|
644
|
+
console.log();
|
|
645
|
+
console.log(chalk.bold(' Welcome! Let\'s get you set up.'));
|
|
646
|
+
console.log();
|
|
647
|
+
console.log(chalk.dim(' K:BOT needs an AI brain to work. You have two options:'));
|
|
648
|
+
console.log();
|
|
649
|
+
console.log(` ${chalk.bold('1.')} ${chalk.hex('#6B8E6B')('Free & Private')} — Run AI on your computer (no account needed)`);
|
|
650
|
+
console.log(` ${chalk.bold('2.')} ${chalk.hex('#5B8BA0')('Cloud AI')} — Use an API key from OpenAI, Google, Anthropic, etc.`);
|
|
651
|
+
console.log();
|
|
652
|
+
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
653
|
+
const choice = await new Promise((resolve) => {
|
|
654
|
+
rl.question(' Pick 1 or 2 (or q to quit): ', (answer) => {
|
|
655
|
+
resolve(answer.trim().toLowerCase());
|
|
656
|
+
});
|
|
657
|
+
});
|
|
658
|
+
if (choice === 'q' || choice === 'quit') {
|
|
659
|
+
rl.close();
|
|
660
|
+
return null;
|
|
213
661
|
}
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
662
|
+
if (choice === '1' || choice === 'free' || choice === 'local') {
|
|
663
|
+
rl.close();
|
|
664
|
+
console.log();
|
|
665
|
+
// Check if Ollama is already installed and running
|
|
666
|
+
const running = await isOllamaRunning();
|
|
667
|
+
if (running) {
|
|
668
|
+
const models = await listOllamaModels();
|
|
669
|
+
if (models.length > 0) {
|
|
670
|
+
const ok = await setupOllama();
|
|
671
|
+
if (ok) {
|
|
672
|
+
printSuccess(`Found Ollama with ${models.length} model(s). You're ready!`);
|
|
673
|
+
return { local: true };
|
|
674
|
+
}
|
|
675
|
+
}
|
|
676
|
+
else {
|
|
677
|
+
printInfo('Ollama is running but has no models. Downloading one now...');
|
|
678
|
+
try {
|
|
679
|
+
const { execSync } = await import('node:child_process');
|
|
680
|
+
printInfo('Pulling llama3.2:3b (small & fast, 2GB download)...');
|
|
681
|
+
execSync('ollama pull llama3.2:3b', { stdio: 'inherit', timeout: 600_000 });
|
|
682
|
+
const ok = await setupOllama('llama3.2:3b');
|
|
683
|
+
if (ok) {
|
|
684
|
+
printSuccess('Done! Local AI is ready.');
|
|
685
|
+
return { local: true };
|
|
686
|
+
}
|
|
687
|
+
}
|
|
688
|
+
catch {
|
|
689
|
+
printError('Download failed. Try manually: ollama pull llama3.2:3b');
|
|
690
|
+
return null;
|
|
691
|
+
}
|
|
692
|
+
}
|
|
693
|
+
}
|
|
694
|
+
// Ollama not running or not installed
|
|
695
|
+
console.log(chalk.bold(' To run AI locally, you need Ollama (free, 1 minute install):'));
|
|
218
696
|
console.log();
|
|
697
|
+
console.log(` ${chalk.bold('Step 1:')} Download Ollama from ${chalk.underline('https://ollama.com')}`);
|
|
698
|
+
console.log(` ${chalk.bold('Step 2:')} Open the Ollama app (it runs in the background)`);
|
|
699
|
+
console.log(` ${chalk.bold('Step 3:')} Run: ${chalk.cyan('kbot')} again — it will auto-detect Ollama`);
|
|
700
|
+
console.log();
|
|
701
|
+
printInfo('Ollama is like having ChatGPT on your computer — free, private, no account needed.');
|
|
702
|
+
console.log();
|
|
703
|
+
return null;
|
|
219
704
|
}
|
|
220
|
-
|
|
221
|
-
|
|
705
|
+
if (choice === '2' || choice === 'cloud' || choice === 'api') {
|
|
706
|
+
console.log();
|
|
707
|
+
console.log(chalk.dim(' Paste your API key below. K:BOT will auto-detect which service it\'s from.'));
|
|
708
|
+
console.log();
|
|
709
|
+
console.log(chalk.dim(' Where to get a key:'));
|
|
710
|
+
console.log(chalk.dim(' OpenAI: https://platform.openai.com/api-keys'));
|
|
711
|
+
console.log(chalk.dim(' Google: https://aistudio.google.com/apikey'));
|
|
712
|
+
console.log(chalk.dim(' Anthropic: https://console.anthropic.com/settings/keys'));
|
|
713
|
+
console.log();
|
|
714
|
+
const key = await new Promise((resolve) => {
|
|
715
|
+
rl.question(' API key: ', (answer) => {
|
|
716
|
+
resolve(answer.trim());
|
|
717
|
+
rl.close();
|
|
718
|
+
});
|
|
719
|
+
});
|
|
720
|
+
if (!key || key.length < 10) {
|
|
721
|
+
printError('That doesn\'t look like a valid API key. Try again with: kbot auth');
|
|
722
|
+
return null;
|
|
723
|
+
}
|
|
724
|
+
let provider = detectProvider(key);
|
|
725
|
+
if (!provider) {
|
|
726
|
+
// Ask which provider with simple numbered list
|
|
727
|
+
console.log();
|
|
728
|
+
console.log(chalk.dim(' Couldn\'t auto-detect the provider. Which service is this key for?'));
|
|
729
|
+
const mainProviders = [
|
|
730
|
+
{ id: 'openai', name: 'OpenAI (ChatGPT)' },
|
|
731
|
+
{ id: 'google', name: 'Google (Gemini)' },
|
|
732
|
+
{ id: 'anthropic', name: 'Anthropic (Claude)' },
|
|
733
|
+
{ id: 'mistral', name: 'Mistral AI' },
|
|
734
|
+
{ id: 'deepseek', name: 'DeepSeek' },
|
|
735
|
+
{ id: 'groq', name: 'Groq' },
|
|
736
|
+
];
|
|
737
|
+
for (let i = 0; i < mainProviders.length; i++) {
|
|
738
|
+
console.log(` ${i + 1}. ${mainProviders[i].name}`);
|
|
739
|
+
}
|
|
740
|
+
console.log();
|
|
741
|
+
const rl2 = createInterface({ input: process.stdin, output: process.stdout });
|
|
742
|
+
const providerChoice = await new Promise((resolve) => {
|
|
743
|
+
rl2.question(' Enter number: ', (a) => { resolve(a.trim()); rl2.close(); });
|
|
744
|
+
});
|
|
745
|
+
const idx = parseInt(providerChoice, 10) - 1;
|
|
746
|
+
if (idx >= 0 && idx < mainProviders.length) {
|
|
747
|
+
provider = mainProviders[idx].id;
|
|
748
|
+
}
|
|
749
|
+
else {
|
|
750
|
+
printError('Invalid choice. Run kbot auth to try again.');
|
|
751
|
+
return null;
|
|
752
|
+
}
|
|
753
|
+
}
|
|
754
|
+
printInfo(`Setting up ${PROVIDERS[provider].name}...`);
|
|
755
|
+
const ok = await setupByok(key, provider);
|
|
756
|
+
if (ok) {
|
|
757
|
+
console.log();
|
|
758
|
+
printSuccess(`Connected to ${PROVIDERS[provider].name}! You're ready.`);
|
|
759
|
+
return { local: isLocalProvider(provider) };
|
|
760
|
+
}
|
|
761
|
+
else {
|
|
762
|
+
printError('Could not verify that key. Double-check it and try: kbot auth');
|
|
763
|
+
return null;
|
|
764
|
+
}
|
|
765
|
+
}
|
|
766
|
+
rl.close();
|
|
767
|
+
printError('Pick 1 or 2. Run kbot again to retry.');
|
|
768
|
+
return null;
|
|
769
|
+
}
|
|
770
|
+
async function startRepl(agentOpts, context, tier, byokActive = false, localActive = false) {
|
|
771
|
+
console.log(banner(VERSION));
|
|
772
|
+
// Show provider status — one clean line
|
|
773
|
+
if (localActive) {
|
|
774
|
+
const config = loadConfig();
|
|
775
|
+
const provider = config?.byok_provider;
|
|
776
|
+
if (provider === 'ollama') {
|
|
777
|
+
const models = await warmOllamaModelCache();
|
|
778
|
+
printInfo(`Ollama · ${models.length} models · free`);
|
|
779
|
+
}
|
|
780
|
+
else if (provider === 'openclaw') {
|
|
781
|
+
printInfo('OpenClaw · local · free');
|
|
782
|
+
}
|
|
783
|
+
}
|
|
784
|
+
else if (byokActive) {
|
|
785
|
+
const config = loadConfig();
|
|
786
|
+
const p = config?.byok_provider ? PROVIDERS[config.byok_provider] : null;
|
|
787
|
+
if (p)
|
|
788
|
+
printInfo(`${p.name}`);
|
|
789
|
+
}
|
|
790
|
+
const sessionCount = incrementSessions();
|
|
222
791
|
const rl = createInterface({
|
|
223
792
|
input: process.stdin,
|
|
224
793
|
output: process.stdout,
|
|
225
794
|
prompt: kbotPrompt(),
|
|
226
795
|
});
|
|
796
|
+
// First time? Show a gentle hint
|
|
797
|
+
if (sessionCount <= 1) {
|
|
798
|
+
printInfo('Try: "hello" or "create a python script" or /help');
|
|
799
|
+
}
|
|
800
|
+
console.log();
|
|
227
801
|
rl.prompt();
|
|
228
|
-
|
|
802
|
+
let processing = false;
|
|
803
|
+
rl.on('line', (line) => {
|
|
229
804
|
const input = line.trim();
|
|
230
805
|
if (!input) {
|
|
231
806
|
rl.prompt();
|
|
232
807
|
return;
|
|
233
808
|
}
|
|
234
|
-
|
|
235
|
-
if (input.startsWith('/')) {
|
|
236
|
-
try {
|
|
237
|
-
await handleSlashCommand(input, agentOpts, rl);
|
|
238
|
-
}
|
|
239
|
-
catch (err) {
|
|
240
|
-
printError(err instanceof Error ? err.message : String(err));
|
|
241
|
-
}
|
|
242
|
-
rl.prompt();
|
|
809
|
+
if (processing)
|
|
243
810
|
return;
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
811
|
+
processing = true;
|
|
812
|
+
rl.pause();
|
|
813
|
+
// Move cursor up and clear the echoed line to prevent double-display
|
|
814
|
+
process.stdout.write('\x1b[A\x1b[2K');
|
|
815
|
+
// Re-print the input cleanly with the prompt prefix
|
|
816
|
+
console.log(`${kbotPrompt()}${input}`);
|
|
817
|
+
const handle = async () => {
|
|
818
|
+
// Slash commands
|
|
819
|
+
if (input.startsWith('/')) {
|
|
820
|
+
try {
|
|
821
|
+
await handleSlashCommand(input, agentOpts, rl);
|
|
822
|
+
}
|
|
823
|
+
catch (err) {
|
|
824
|
+
printError(err instanceof Error ? err.message : String(err));
|
|
825
|
+
}
|
|
252
826
|
}
|
|
253
|
-
else
|
|
254
|
-
//
|
|
827
|
+
else {
|
|
828
|
+
// Run agent
|
|
829
|
+
try {
|
|
830
|
+
const response = await runAgent(input, agentOpts);
|
|
831
|
+
// Only print response if it wasn't already streamed to stdout
|
|
832
|
+
if (!response.streamed) {
|
|
833
|
+
printResponse(response.agent, response.content);
|
|
834
|
+
}
|
|
835
|
+
// Usage footer — subtle, compact like Claude Code
|
|
836
|
+
if (response.usage) {
|
|
837
|
+
const tokens = response.usage.input_tokens + response.usage.output_tokens;
|
|
838
|
+
const cost = response.usage.cost_usd === 0 ? 'free' : `$${response.usage.cost_usd.toFixed(4)}`;
|
|
839
|
+
const tools = response.toolCalls > 0 ? ` · ${response.toolCalls} tools` : '';
|
|
840
|
+
printInfo(`${tokens} tokens${tools} · ${cost}`);
|
|
841
|
+
}
|
|
842
|
+
}
|
|
843
|
+
catch (err) {
|
|
844
|
+
printError(err instanceof Error ? err.message : String(err));
|
|
845
|
+
}
|
|
255
846
|
}
|
|
256
|
-
|
|
257
|
-
|
|
847
|
+
console.log();
|
|
848
|
+
processing = false;
|
|
849
|
+
rl.resume();
|
|
850
|
+
rl.prompt();
|
|
851
|
+
};
|
|
852
|
+
handle().catch((err) => {
|
|
258
853
|
printError(err instanceof Error ? err.message : String(err));
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
854
|
+
processing = false;
|
|
855
|
+
rl.resume();
|
|
856
|
+
rl.prompt();
|
|
857
|
+
});
|
|
262
858
|
});
|
|
263
859
|
// Ctrl+C: don't exit immediately — just cancel current input and re-prompt
|
|
860
|
+
let sigintCount = 0;
|
|
264
861
|
rl.on('SIGINT', () => {
|
|
862
|
+
sigintCount++;
|
|
863
|
+
if (sigintCount >= 2) {
|
|
864
|
+
printGoodbye();
|
|
865
|
+
process.exit(0);
|
|
866
|
+
}
|
|
265
867
|
console.log(); // newline after ^C
|
|
266
|
-
printInfo('
|
|
868
|
+
printInfo('Ctrl+C again to exit');
|
|
267
869
|
rl.prompt();
|
|
870
|
+
// Reset after 2 seconds
|
|
871
|
+
setTimeout(() => { sigintCount = 0; }, 2000);
|
|
268
872
|
});
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
873
|
+
// Keep process alive until readline closes
|
|
874
|
+
return new Promise((resolve) => {
|
|
875
|
+
rl.on('close', () => {
|
|
876
|
+
// Flush all pending learning writes before exit
|
|
877
|
+
flushPendingWrites();
|
|
878
|
+
printGoodbye();
|
|
879
|
+
resolve();
|
|
880
|
+
process.exit(0);
|
|
881
|
+
});
|
|
272
882
|
});
|
|
273
883
|
}
|
|
884
|
+
async function handleMatrixCommand(args) {
|
|
885
|
+
const sub = args[0];
|
|
886
|
+
if (!sub || sub === 'list') {
|
|
887
|
+
printInfo('Agent Creation Matrix');
|
|
888
|
+
console.log(formatAgentList());
|
|
889
|
+
console.log();
|
|
890
|
+
printInfo('Presets: ' + Object.keys(PRESETS).join(', '));
|
|
891
|
+
printInfo('Usage: /matrix create <name> <prompt>');
|
|
892
|
+
printInfo(' /matrix preset <name>');
|
|
893
|
+
printInfo(' /matrix remove <id>');
|
|
894
|
+
printInfo(' /matrix info <id>');
|
|
895
|
+
return;
|
|
896
|
+
}
|
|
897
|
+
if (sub === 'create') {
|
|
898
|
+
const name = args[1];
|
|
899
|
+
if (!name) {
|
|
900
|
+
printError('Usage: /matrix create <name> <system prompt...>');
|
|
901
|
+
return;
|
|
902
|
+
}
|
|
903
|
+
const prompt = args.slice(2).join(' ');
|
|
904
|
+
if (!prompt) {
|
|
905
|
+
printError('System prompt required. Example:');
|
|
906
|
+
printInfo(' /matrix create "Security Bot" You are a security expert who finds vulnerabilities.');
|
|
907
|
+
return;
|
|
908
|
+
}
|
|
909
|
+
try {
|
|
910
|
+
const agent = createAgent(name, prompt);
|
|
911
|
+
printSuccess(`Agent "${agent.name}" created (${agent.id})`);
|
|
912
|
+
printInfo(`Use with: /agent ${agent.id}`);
|
|
913
|
+
}
|
|
914
|
+
catch (err) {
|
|
915
|
+
printError(err instanceof Error ? err.message : String(err));
|
|
916
|
+
}
|
|
917
|
+
return;
|
|
918
|
+
}
|
|
919
|
+
if (sub === 'preset') {
|
|
920
|
+
const presetId = args[1];
|
|
921
|
+
if (!presetId || !PRESETS[presetId]) {
|
|
922
|
+
printError(`Unknown preset. Available: ${Object.keys(PRESETS).join(', ')}`);
|
|
923
|
+
return;
|
|
924
|
+
}
|
|
925
|
+
const preset = PRESETS[presetId];
|
|
926
|
+
try {
|
|
927
|
+
const agent = createAgent(preset.name, preset.prompt);
|
|
928
|
+
printSuccess(`Preset agent "${agent.name}" spawned (${agent.id})`);
|
|
929
|
+
printInfo(`Use with: /agent ${agent.id}`);
|
|
930
|
+
}
|
|
931
|
+
catch (err) {
|
|
932
|
+
printError(err instanceof Error ? err.message : String(err));
|
|
933
|
+
}
|
|
934
|
+
return;
|
|
935
|
+
}
|
|
936
|
+
if (sub === 'remove') {
|
|
937
|
+
const id = args[1];
|
|
938
|
+
if (!id) {
|
|
939
|
+
printError('Usage: /matrix remove <agent-id>');
|
|
940
|
+
return;
|
|
941
|
+
}
|
|
942
|
+
if (removeAgent(id)) {
|
|
943
|
+
printSuccess(`Agent "${id}" removed from matrix`);
|
|
944
|
+
}
|
|
945
|
+
else {
|
|
946
|
+
printError(`Agent "${id}" not found`);
|
|
947
|
+
}
|
|
948
|
+
return;
|
|
949
|
+
}
|
|
950
|
+
if (sub === 'info') {
|
|
951
|
+
const id = args[1];
|
|
952
|
+
if (!id) {
|
|
953
|
+
printError('Usage: /matrix info <agent-id>');
|
|
954
|
+
return;
|
|
955
|
+
}
|
|
956
|
+
const agent = getAgent(id);
|
|
957
|
+
if (agent) {
|
|
958
|
+
console.log(formatAgentDetail(agent));
|
|
959
|
+
}
|
|
960
|
+
else {
|
|
961
|
+
printError(`Agent "${id}" not found`);
|
|
962
|
+
}
|
|
963
|
+
return;
|
|
964
|
+
}
|
|
965
|
+
printError(`Unknown matrix command: ${sub}`);
|
|
966
|
+
printInfo('Available: list, create, preset, remove, info');
|
|
967
|
+
}
|
|
274
968
|
async function handleSlashCommand(input, opts, rl) {
|
|
275
969
|
const [cmd, ...args] = input.slice(1).split(' ');
|
|
276
970
|
switch (cmd) {
|
|
@@ -284,26 +978,41 @@ async function handleSlashCommand(input, opts, rl) {
|
|
|
284
978
|
}
|
|
285
979
|
else {
|
|
286
980
|
printInfo(`Current agent: ${opts.agent || 'auto'}`);
|
|
287
|
-
printInfo('
|
|
981
|
+
printInfo('Built-in: kernel, researcher, coder, writer, analyst, aesthete, guardian, curator, strategist');
|
|
982
|
+
const matrixIds = getMatrixAgentIds();
|
|
983
|
+
if (matrixIds.length > 0) {
|
|
984
|
+
printInfo(`Matrix: ${matrixIds.join(', ')}`);
|
|
985
|
+
}
|
|
288
986
|
}
|
|
289
987
|
break;
|
|
988
|
+
case 'matrix':
|
|
989
|
+
await handleMatrixCommand(args);
|
|
990
|
+
break;
|
|
290
991
|
case 'model':
|
|
291
992
|
if (args[0]) {
|
|
292
|
-
|
|
293
|
-
|
|
993
|
+
const config = loadConfig();
|
|
994
|
+
// If using Ollama, update the default model in the provider config
|
|
995
|
+
if (config?.byok_provider === 'ollama') {
|
|
996
|
+
PROVIDERS.ollama.defaultModel = args[0];
|
|
997
|
+
printSuccess(`Ollama model set to: ${args[0]}`);
|
|
998
|
+
printInfo('This model will be used for all requests (overrides smart routing)');
|
|
999
|
+
}
|
|
1000
|
+
else {
|
|
1001
|
+
opts.model = args[0];
|
|
1002
|
+
printSuccess(`Model set to: ${args[0]}`);
|
|
1003
|
+
}
|
|
294
1004
|
}
|
|
295
1005
|
else {
|
|
296
|
-
|
|
1006
|
+
const config = loadConfig();
|
|
1007
|
+
if (config?.byok_provider === 'ollama') {
|
|
1008
|
+
printInfo(`Current model: ${PROVIDERS.ollama.defaultModel} (Ollama, smart routing active)`);
|
|
1009
|
+
printInfo('Override with: /model <name> or /model auto');
|
|
1010
|
+
}
|
|
1011
|
+
else {
|
|
1012
|
+
printInfo(`Current model: ${opts.model || 'auto'}`);
|
|
1013
|
+
}
|
|
297
1014
|
}
|
|
298
1015
|
break;
|
|
299
|
-
case 'usage': {
|
|
300
|
-
const stats = await getUsageStats();
|
|
301
|
-
if (stats)
|
|
302
|
-
printUsage(stats);
|
|
303
|
-
else
|
|
304
|
-
printError('Could not fetch usage stats');
|
|
305
|
-
break;
|
|
306
|
-
}
|
|
307
1016
|
case 'clear':
|
|
308
1017
|
clearHistory();
|
|
309
1018
|
printSuccess('Conversation history cleared');
|
|
@@ -328,6 +1037,377 @@ async function handleSlashCommand(input, opts, rl) {
|
|
|
328
1037
|
printInfo(mem || 'No memory entries yet');
|
|
329
1038
|
}
|
|
330
1039
|
break;
|
|
1040
|
+
case 'learn':
|
|
1041
|
+
case 'stats': {
|
|
1042
|
+
const stats = getExtendedStats();
|
|
1043
|
+
console.log();
|
|
1044
|
+
console.log(chalk.bold(' Learning'));
|
|
1045
|
+
printInfo(` ${stats.patternsCount} patterns · ${stats.solutionsCount} solutions · ${stats.knowledgeCount} facts`);
|
|
1046
|
+
printInfo(` ${stats.totalMessages} messages · ${stats.sessions} sessions · ${stats.efficiency} efficiency`);
|
|
1047
|
+
if (stats.topKnowledge.length > 0) {
|
|
1048
|
+
console.log();
|
|
1049
|
+
for (const fact of stats.topKnowledge.slice(0, 5)) {
|
|
1050
|
+
printInfo(` • ${fact}`);
|
|
1051
|
+
}
|
|
1052
|
+
}
|
|
1053
|
+
break;
|
|
1054
|
+
}
|
|
1055
|
+
case 'remember': {
|
|
1056
|
+
const fact = args.join(' ');
|
|
1057
|
+
if (!fact) {
|
|
1058
|
+
printError('Usage: /remember <fact to remember>');
|
|
1059
|
+
printInfo('Example: /remember My API runs on port 3001');
|
|
1060
|
+
}
|
|
1061
|
+
else {
|
|
1062
|
+
learnFact(fact, 'fact', 'user-taught');
|
|
1063
|
+
printSuccess(`Learned: "${fact}"`);
|
|
1064
|
+
}
|
|
1065
|
+
break;
|
|
1066
|
+
}
|
|
1067
|
+
case 'compact': {
|
|
1068
|
+
const result = compactHistory();
|
|
1069
|
+
printSuccess(result.summary);
|
|
1070
|
+
break;
|
|
1071
|
+
}
|
|
1072
|
+
case 'save': {
|
|
1073
|
+
const sessionName = args.join(' ') || undefined;
|
|
1074
|
+
try {
|
|
1075
|
+
const session = saveSession(sessionName, opts.agent);
|
|
1076
|
+
printSuccess(`Session saved: ${session.name} (${session.id})`);
|
|
1077
|
+
printInfo('Resume with: /resume ' + session.id);
|
|
1078
|
+
}
|
|
1079
|
+
catch (err) {
|
|
1080
|
+
printError(err instanceof Error ? err.message : String(err));
|
|
1081
|
+
}
|
|
1082
|
+
break;
|
|
1083
|
+
}
|
|
1084
|
+
case 'resume': {
|
|
1085
|
+
const sessionId = args.join(' ');
|
|
1086
|
+
if (!sessionId) {
|
|
1087
|
+
printError('Usage: /resume <session-id or name>');
|
|
1088
|
+
printInfo('Run /sessions to list saved sessions.');
|
|
1089
|
+
}
|
|
1090
|
+
else {
|
|
1091
|
+
const session = loadSession(sessionId);
|
|
1092
|
+
if (session) {
|
|
1093
|
+
restoreHistory(session.history);
|
|
1094
|
+
if (session.agent)
|
|
1095
|
+
opts.agent = session.agent;
|
|
1096
|
+
printSuccess(`Resumed: ${session.name} (${session.turnCount} turns)`);
|
|
1097
|
+
}
|
|
1098
|
+
else {
|
|
1099
|
+
printError(`Session not found: ${sessionId}`);
|
|
1100
|
+
}
|
|
1101
|
+
}
|
|
1102
|
+
break;
|
|
1103
|
+
}
|
|
1104
|
+
case 'sessions': {
|
|
1105
|
+
const sessions = listSessions();
|
|
1106
|
+
printInfo('Saved Sessions');
|
|
1107
|
+
console.log(formatSessionList(sessions));
|
|
1108
|
+
break;
|
|
1109
|
+
}
|
|
1110
|
+
case 'delete-session': {
|
|
1111
|
+
const id = args.join(' ');
|
|
1112
|
+
if (!id) {
|
|
1113
|
+
printError('Usage: /delete-session <session-id>');
|
|
1114
|
+
}
|
|
1115
|
+
else if (deleteSession(id)) {
|
|
1116
|
+
printSuccess(`Session deleted: ${id}`);
|
|
1117
|
+
}
|
|
1118
|
+
else {
|
|
1119
|
+
printError(`Session not found: ${id}`);
|
|
1120
|
+
}
|
|
1121
|
+
break;
|
|
1122
|
+
}
|
|
1123
|
+
case 'train': {
|
|
1124
|
+
printInfo('Running self-training...');
|
|
1125
|
+
const result = selfTrain();
|
|
1126
|
+
printSuccess('Training complete');
|
|
1127
|
+
console.log();
|
|
1128
|
+
for (const line of result.summary.split('\n')) {
|
|
1129
|
+
printInfo(` ${line}`);
|
|
1130
|
+
}
|
|
1131
|
+
const log = getTrainingLog();
|
|
1132
|
+
console.log();
|
|
1133
|
+
printInfo(` Total training runs: ${log.runsTotal}`);
|
|
1134
|
+
printInfo(` Total entries pruned: ${log.entriesPruned}`);
|
|
1135
|
+
printInfo(` Total insights synthesized: ${log.insightsSynthesized}`);
|
|
1136
|
+
break;
|
|
1137
|
+
}
|
|
1138
|
+
case 'thinking':
|
|
1139
|
+
opts.thinking = !opts.thinking;
|
|
1140
|
+
printSuccess(`Extended thinking: ${opts.thinking ? 'ON' : 'OFF'}`);
|
|
1141
|
+
if (opts.thinking)
|
|
1142
|
+
printInfo('AI will show reasoning steps before responding.');
|
|
1143
|
+
break;
|
|
1144
|
+
case 'plan': {
|
|
1145
|
+
const planTask = args.join(' ');
|
|
1146
|
+
if (!planTask) {
|
|
1147
|
+
printError('Usage: /plan <task description>');
|
|
1148
|
+
printInfo('Example: /plan refactor the auth system to use JWT');
|
|
1149
|
+
}
|
|
1150
|
+
else {
|
|
1151
|
+
const { autonomousExecute } = await import('./planner.js');
|
|
1152
|
+
const plan = await autonomousExecute(planTask, opts, {
|
|
1153
|
+
onApproval: async (plan) => {
|
|
1154
|
+
const { createInterface: createRl } = await import('node:readline');
|
|
1155
|
+
const confirmRl = createRl({ input: process.stdin, output: process.stdout });
|
|
1156
|
+
const answer = await new Promise((resolve) => {
|
|
1157
|
+
confirmRl.question(' Approve this plan? [y/N] ', (a) => {
|
|
1158
|
+
resolve(a.trim().toLowerCase());
|
|
1159
|
+
confirmRl.close();
|
|
1160
|
+
});
|
|
1161
|
+
});
|
|
1162
|
+
return answer === 'y' || answer === 'yes';
|
|
1163
|
+
},
|
|
1164
|
+
});
|
|
1165
|
+
const { formatPlanSummary } = await import('./planner.js');
|
|
1166
|
+
printInfo(formatPlanSummary(plan));
|
|
1167
|
+
}
|
|
1168
|
+
break;
|
|
1169
|
+
}
|
|
1170
|
+
case 'worktree': {
|
|
1171
|
+
if (!args[0] || args[0] === 'list') {
|
|
1172
|
+
const { executeTool: execTool } = await import('./tools/index.js');
|
|
1173
|
+
const result = await execTool({ id: 'cli_wt', name: 'worktree_list', arguments: {} });
|
|
1174
|
+
printInfo(result.result);
|
|
1175
|
+
}
|
|
1176
|
+
else if (args[0] === 'create') {
|
|
1177
|
+
const { executeTool: execTool } = await import('./tools/index.js');
|
|
1178
|
+
const result = await execTool({ id: 'cli_wt', name: 'worktree_create', arguments: { name: args[1] } });
|
|
1179
|
+
printInfo(result.result);
|
|
1180
|
+
}
|
|
1181
|
+
else if (args[0] === 'switch') {
|
|
1182
|
+
const { executeTool: execTool } = await import('./tools/index.js');
|
|
1183
|
+
const result = await execTool({ id: 'cli_wt', name: 'worktree_switch', arguments: { id: args[1] } });
|
|
1184
|
+
printInfo(result.result);
|
|
1185
|
+
}
|
|
1186
|
+
else if (args[0] === 'merge') {
|
|
1187
|
+
const { executeTool: execTool } = await import('./tools/index.js');
|
|
1188
|
+
const result = await execTool({ id: 'cli_wt', name: 'worktree_merge', arguments: { id: args[1] } });
|
|
1189
|
+
printInfo(result.result);
|
|
1190
|
+
}
|
|
1191
|
+
else if (args[0] === 'remove') {
|
|
1192
|
+
const { executeTool: execTool } = await import('./tools/index.js');
|
|
1193
|
+
const result = await execTool({ id: 'cli_wt', name: 'worktree_remove', arguments: { id: args[1] } });
|
|
1194
|
+
printInfo(result.result);
|
|
1195
|
+
}
|
|
1196
|
+
else {
|
|
1197
|
+
printError('Usage: /worktree [list|create|switch|merge|remove] [args]');
|
|
1198
|
+
}
|
|
1199
|
+
break;
|
|
1200
|
+
}
|
|
1201
|
+
case 'permission': {
|
|
1202
|
+
const { setPermissionMode, getPermissionMode } = await import('./permissions.js');
|
|
1203
|
+
const mode = args[0];
|
|
1204
|
+
if (mode === 'permissive' || mode === 'normal' || mode === 'strict') {
|
|
1205
|
+
setPermissionMode(mode);
|
|
1206
|
+
printSuccess(`Permission mode: ${mode}`);
|
|
1207
|
+
}
|
|
1208
|
+
else {
|
|
1209
|
+
printInfo(`Current permission mode: ${getPermissionMode()}`);
|
|
1210
|
+
printInfo('Available: permissive, normal, strict');
|
|
1211
|
+
}
|
|
1212
|
+
break;
|
|
1213
|
+
}
|
|
1214
|
+
case 'plugins': {
|
|
1215
|
+
const { getLoadedPlugins, formatPluginList, scaffoldPlugin } = await import('./plugins.js');
|
|
1216
|
+
if (args[0] === 'create' && args[1]) {
|
|
1217
|
+
const result = scaffoldPlugin(args[1]);
|
|
1218
|
+
printInfo(result);
|
|
1219
|
+
}
|
|
1220
|
+
else if (args[0] === 'reload') {
|
|
1221
|
+
const { loadPlugins } = await import('./plugins.js');
|
|
1222
|
+
const plugins = await loadPlugins(true);
|
|
1223
|
+
printSuccess(`Reloaded ${plugins.length} plugin(s)`);
|
|
1224
|
+
}
|
|
1225
|
+
else {
|
|
1226
|
+
printInfo(formatPluginList());
|
|
1227
|
+
}
|
|
1228
|
+
break;
|
|
1229
|
+
}
|
|
1230
|
+
case 'dashboard': {
|
|
1231
|
+
const { renderDashboard } = await import('./tui.js');
|
|
1232
|
+
const config = loadConfig();
|
|
1233
|
+
const stats = getExtendedStats();
|
|
1234
|
+
const dashState = {
|
|
1235
|
+
agent: opts.agent || 'auto',
|
|
1236
|
+
model: opts.model || 'auto',
|
|
1237
|
+
provider: config?.byok_provider || 'anthropic',
|
|
1238
|
+
toolsUsed: stats.totalMessages,
|
|
1239
|
+
tokensUsed: 0,
|
|
1240
|
+
cost: 0,
|
|
1241
|
+
sessionTurns: 0,
|
|
1242
|
+
activeSubagents: [],
|
|
1243
|
+
recentTools: [],
|
|
1244
|
+
};
|
|
1245
|
+
console.log(renderDashboard(dashState));
|
|
1246
|
+
break;
|
|
1247
|
+
}
|
|
1248
|
+
case 'build': {
|
|
1249
|
+
const { detectProjectTargets, formatTargetList, getMissingTools } = await import('./build-targets.js');
|
|
1250
|
+
const detected = detectProjectTargets();
|
|
1251
|
+
if (detected.length === 0) {
|
|
1252
|
+
printInfo('No build targets detected in this project.');
|
|
1253
|
+
printInfo('Use: /build targets — to see all supported platforms');
|
|
1254
|
+
}
|
|
1255
|
+
else if (args[0] === 'targets') {
|
|
1256
|
+
const { BUILD_TARGETS } = await import('./build-targets.js');
|
|
1257
|
+
console.log(formatTargetList(Object.values(BUILD_TARGETS)));
|
|
1258
|
+
}
|
|
1259
|
+
else {
|
|
1260
|
+
printInfo('Detected build targets:');
|
|
1261
|
+
for (const t of detected) {
|
|
1262
|
+
const missing = getMissingTools(t);
|
|
1263
|
+
const icon = missing.length === 0 ? '✓' : '○';
|
|
1264
|
+
printInfo(` ${icon} ${t.id.padEnd(16)} ${t.name}`);
|
|
1265
|
+
}
|
|
1266
|
+
console.log();
|
|
1267
|
+
printInfo('Run: kbot "build for <target>" to trigger a build');
|
|
1268
|
+
}
|
|
1269
|
+
break;
|
|
1270
|
+
}
|
|
1271
|
+
case 'ollama': {
|
|
1272
|
+
if (args[0] === 'off') {
|
|
1273
|
+
disableByok();
|
|
1274
|
+
printSuccess('Ollama disabled. Provider disabled.');
|
|
1275
|
+
break;
|
|
1276
|
+
}
|
|
1277
|
+
const running = await isOllamaRunning();
|
|
1278
|
+
if (!running) {
|
|
1279
|
+
printError('Ollama not running. Start with: ollama serve');
|
|
1280
|
+
break;
|
|
1281
|
+
}
|
|
1282
|
+
if (args[0] === 'list') {
|
|
1283
|
+
const models = await listOllamaModels();
|
|
1284
|
+
printInfo('Available Ollama models:');
|
|
1285
|
+
for (const m of models)
|
|
1286
|
+
printInfo(` • ${m}`);
|
|
1287
|
+
break;
|
|
1288
|
+
}
|
|
1289
|
+
const model = args[0];
|
|
1290
|
+
const ok = await setupOllama(model);
|
|
1291
|
+
if (ok) {
|
|
1292
|
+
const models = await listOllamaModels();
|
|
1293
|
+
printSuccess(`Ollama enabled! ${models.length} models available. $0 cost.`);
|
|
1294
|
+
printInfo(`Default: ${model || PROVIDERS.ollama.defaultModel}`);
|
|
1295
|
+
}
|
|
1296
|
+
else {
|
|
1297
|
+
printError('Failed to connect to Ollama.');
|
|
1298
|
+
}
|
|
1299
|
+
break;
|
|
1300
|
+
}
|
|
1301
|
+
case 'openclaw': {
|
|
1302
|
+
if (args[0] === 'off') {
|
|
1303
|
+
disableByok();
|
|
1304
|
+
printSuccess('OpenClaw disabled.');
|
|
1305
|
+
break;
|
|
1306
|
+
}
|
|
1307
|
+
const ok = await setupOpenClaw(args[0]);
|
|
1308
|
+
if (ok) {
|
|
1309
|
+
printSuccess('OpenClaw gateway connected at 127.0.0.1:18789');
|
|
1310
|
+
}
|
|
1311
|
+
else {
|
|
1312
|
+
printError('Cannot connect to OpenClaw gateway.');
|
|
1313
|
+
}
|
|
1314
|
+
break;
|
|
1315
|
+
}
|
|
1316
|
+
case 'models': {
|
|
1317
|
+
const config = loadConfig();
|
|
1318
|
+
if (config?.byok_provider === 'ollama') {
|
|
1319
|
+
const running = await isOllamaRunning();
|
|
1320
|
+
if (!running) {
|
|
1321
|
+
printError('Ollama not running');
|
|
1322
|
+
break;
|
|
1323
|
+
}
|
|
1324
|
+
const models = await listOllamaModels();
|
|
1325
|
+
printInfo('Available local models:');
|
|
1326
|
+
for (const m of models) {
|
|
1327
|
+
const active = m === (PROVIDERS.ollama.defaultModel) ? ' (active)' : '';
|
|
1328
|
+
printInfo(` ${active ? '>' : ' '} ${m}${active}`);
|
|
1329
|
+
}
|
|
1330
|
+
console.log();
|
|
1331
|
+
printInfo('Switch model: /model <name>');
|
|
1332
|
+
printInfo('Smart routing auto-selects: coding → qwen2.5-coder · reasoning → phi4 · general → llama3.1');
|
|
1333
|
+
}
|
|
1334
|
+
else if (config?.byok_provider) {
|
|
1335
|
+
const p = PROVIDERS[config.byok_provider];
|
|
1336
|
+
printInfo(`Provider: ${p.name}`);
|
|
1337
|
+
printInfo(` Default: ${p.defaultModel}`);
|
|
1338
|
+
printInfo(` Fast: ${p.fastModel}`);
|
|
1339
|
+
}
|
|
1340
|
+
else {
|
|
1341
|
+
printInfo('No provider configured. Run: kbot byok');
|
|
1342
|
+
}
|
|
1343
|
+
break;
|
|
1344
|
+
}
|
|
1345
|
+
case 'provider': {
|
|
1346
|
+
const config = loadConfig();
|
|
1347
|
+
if (config?.byok_enabled && config?.byok_provider) {
|
|
1348
|
+
const p = PROVIDERS[config.byok_provider];
|
|
1349
|
+
printInfo(`Active provider: ${p?.name || config.byok_provider}`);
|
|
1350
|
+
printInfo(` Model: ${p?.defaultModel || 'unknown'}`);
|
|
1351
|
+
printInfo(` Cost: $${p?.inputCost || 0}/${p?.outputCost || 0} per 1M tokens`);
|
|
1352
|
+
printInfo(` Local: ${isLocalProvider(config.byok_provider) ? 'Yes (free, private)' : 'No (cloud)'}`);
|
|
1353
|
+
}
|
|
1354
|
+
else {
|
|
1355
|
+
printInfo('No provider configured. Run: kbot byok');
|
|
1356
|
+
}
|
|
1357
|
+
printInfo('');
|
|
1358
|
+
printInfo('Switch: /ollama [model] | /openclaw | kbot byok');
|
|
1359
|
+
break;
|
|
1360
|
+
}
|
|
1361
|
+
case 'mimic': {
|
|
1362
|
+
if (!args[0] || args[0] === 'list') {
|
|
1363
|
+
const profiles = listMimicProfiles();
|
|
1364
|
+
printInfo('Mimic Matrix — Code like the best tools and frameworks');
|
|
1365
|
+
console.log();
|
|
1366
|
+
printInfo(' Tool Styles:');
|
|
1367
|
+
for (const p of profiles.filter(p => ['claude-code', 'cursor', 'copilot'].includes(p.id))) {
|
|
1368
|
+
const color = chalk.hex(p.color);
|
|
1369
|
+
printInfo(` ${color(p.icon)} ${p.id.padEnd(16)} ${p.description}`);
|
|
1370
|
+
}
|
|
1371
|
+
console.log();
|
|
1372
|
+
printInfo(' Framework Experts:');
|
|
1373
|
+
for (const p of profiles.filter(p => ['nextjs', 'react', 'python', 'rust'].includes(p.id))) {
|
|
1374
|
+
const color = chalk.hex(p.color);
|
|
1375
|
+
printInfo(` ${color(p.icon)} ${p.id.padEnd(16)} ${p.description}`);
|
|
1376
|
+
}
|
|
1377
|
+
console.log();
|
|
1378
|
+
printInfo(' Coding Philosophies:');
|
|
1379
|
+
for (const p of profiles.filter(p => ['senior', 'startup'].includes(p.id))) {
|
|
1380
|
+
const color = chalk.hex(p.color);
|
|
1381
|
+
printInfo(` ${color(p.icon)} ${p.id.padEnd(16)} ${p.description}`);
|
|
1382
|
+
}
|
|
1383
|
+
console.log();
|
|
1384
|
+
printInfo('Activate: /mimic <id> Example: /mimic nextjs');
|
|
1385
|
+
printInfo('Deactivate: /mimic off');
|
|
1386
|
+
}
|
|
1387
|
+
else if (args[0] === 'off') {
|
|
1388
|
+
opts.agent = 'auto';
|
|
1389
|
+
printSuccess('Mimic mode deactivated. Back to auto-routing.');
|
|
1390
|
+
}
|
|
1391
|
+
else {
|
|
1392
|
+
const profileId = args[0];
|
|
1393
|
+
const agent = activateMimic(profileId);
|
|
1394
|
+
if (agent) {
|
|
1395
|
+
opts.agent = agent.id;
|
|
1396
|
+
const color = chalk.hex(agent.color);
|
|
1397
|
+
printSuccess(`Mimic activated: ${color(agent.icon)} ${agent.name}`);
|
|
1398
|
+
const profile = getMimicProfile(profileId);
|
|
1399
|
+
if (profile?.conventions) {
|
|
1400
|
+
printInfo(`Conventions: ${profile.conventions.join(' · ')}`);
|
|
1401
|
+
}
|
|
1402
|
+
printInfo('All responses will follow this style. Deactivate with: /mimic off');
|
|
1403
|
+
}
|
|
1404
|
+
else {
|
|
1405
|
+
printError(`Unknown mimic profile: ${profileId}`);
|
|
1406
|
+
printInfo('Run /mimic to see available profiles');
|
|
1407
|
+
}
|
|
1408
|
+
}
|
|
1409
|
+
break;
|
|
1410
|
+
}
|
|
331
1411
|
case 'quit':
|
|
332
1412
|
case 'exit':
|
|
333
1413
|
rl.close();
|