@kernel.chat/kbot 1.3.1 → 2.3.1

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.
Files changed (176) hide show
  1. package/README.md +94 -0
  2. package/dist/agent.d.ts +9 -0
  3. package/dist/agent.d.ts.map +1 -1
  4. package/dist/agent.js +576 -119
  5. package/dist/agent.js.map +1 -1
  6. package/dist/auth.d.ts +20 -35
  7. package/dist/auth.d.ts.map +1 -1
  8. package/dist/auth.js +224 -66
  9. package/dist/auth.js.map +1 -1
  10. package/dist/auth.test.d.ts +2 -0
  11. package/dist/auth.test.d.ts.map +1 -0
  12. package/dist/auth.test.js +89 -0
  13. package/dist/auth.test.js.map +1 -0
  14. package/dist/build-targets.d.ts +37 -0
  15. package/dist/build-targets.d.ts.map +1 -0
  16. package/dist/build-targets.js +507 -0
  17. package/dist/build-targets.js.map +1 -0
  18. package/dist/cli.js +1210 -130
  19. package/dist/cli.js.map +1 -1
  20. package/dist/context.d.ts +2 -0
  21. package/dist/context.d.ts.map +1 -1
  22. package/dist/context.js +72 -22
  23. package/dist/context.js.map +1 -1
  24. package/dist/hooks.d.ts +27 -0
  25. package/dist/hooks.d.ts.map +1 -0
  26. package/dist/hooks.js +145 -0
  27. package/dist/hooks.js.map +1 -0
  28. package/dist/ide/acp-server.d.ts +6 -0
  29. package/dist/ide/acp-server.d.ts.map +1 -0
  30. package/dist/ide/acp-server.js +319 -0
  31. package/dist/ide/acp-server.js.map +1 -0
  32. package/dist/ide/bridge.d.ts +128 -0
  33. package/dist/ide/bridge.d.ts.map +1 -0
  34. package/dist/ide/bridge.js +185 -0
  35. package/dist/ide/bridge.js.map +1 -0
  36. package/dist/ide/index.d.ts +5 -0
  37. package/dist/ide/index.d.ts.map +1 -0
  38. package/dist/ide/index.js +11 -0
  39. package/dist/ide/index.js.map +1 -0
  40. package/dist/ide/lsp-bridge.d.ts +27 -0
  41. package/dist/ide/lsp-bridge.d.ts.map +1 -0
  42. package/dist/ide/lsp-bridge.js +267 -0
  43. package/dist/ide/lsp-bridge.js.map +1 -0
  44. package/dist/ide/mcp-server.d.ts +7 -0
  45. package/dist/ide/mcp-server.d.ts.map +1 -0
  46. package/dist/ide/mcp-server.js +451 -0
  47. package/dist/ide/mcp-server.js.map +1 -0
  48. package/dist/learning.d.ts +179 -0
  49. package/dist/learning.d.ts.map +1 -0
  50. package/dist/learning.js +829 -0
  51. package/dist/learning.js.map +1 -0
  52. package/dist/learning.test.d.ts +2 -0
  53. package/dist/learning.test.d.ts.map +1 -0
  54. package/dist/learning.test.js +115 -0
  55. package/dist/learning.test.js.map +1 -0
  56. package/dist/matrix.d.ts +49 -0
  57. package/dist/matrix.d.ts.map +1 -0
  58. package/dist/matrix.js +302 -0
  59. package/dist/matrix.js.map +1 -0
  60. package/dist/memory.d.ts +11 -0
  61. package/dist/memory.d.ts.map +1 -1
  62. package/dist/memory.js +54 -2
  63. package/dist/memory.js.map +1 -1
  64. package/dist/multimodal.d.ts +57 -0
  65. package/dist/multimodal.d.ts.map +1 -0
  66. package/dist/multimodal.js +206 -0
  67. package/dist/multimodal.js.map +1 -0
  68. package/dist/permissions.d.ts +21 -0
  69. package/dist/permissions.d.ts.map +1 -0
  70. package/dist/permissions.js +122 -0
  71. package/dist/permissions.js.map +1 -0
  72. package/dist/planner.d.ts +54 -0
  73. package/dist/planner.d.ts.map +1 -0
  74. package/dist/planner.js +298 -0
  75. package/dist/planner.js.map +1 -0
  76. package/dist/plugins.d.ts +30 -0
  77. package/dist/plugins.d.ts.map +1 -0
  78. package/dist/plugins.js +135 -0
  79. package/dist/plugins.js.map +1 -0
  80. package/dist/sessions.d.ts +38 -0
  81. package/dist/sessions.d.ts.map +1 -0
  82. package/dist/sessions.js +177 -0
  83. package/dist/sessions.js.map +1 -0
  84. package/dist/streaming.d.ts +88 -0
  85. package/dist/streaming.d.ts.map +1 -0
  86. package/dist/streaming.js +317 -0
  87. package/dist/streaming.js.map +1 -0
  88. package/dist/tools/background.d.ts +2 -0
  89. package/dist/tools/background.d.ts.map +1 -0
  90. package/dist/tools/background.js +163 -0
  91. package/dist/tools/background.js.map +1 -0
  92. package/dist/tools/bash.d.ts.map +1 -1
  93. package/dist/tools/bash.js +26 -1
  94. package/dist/tools/bash.js.map +1 -1
  95. package/dist/tools/browser.js +7 -7
  96. package/dist/tools/browser.js.map +1 -1
  97. package/dist/tools/build-matrix.d.ts +2 -0
  98. package/dist/tools/build-matrix.d.ts.map +1 -0
  99. package/dist/tools/build-matrix.js +463 -0
  100. package/dist/tools/build-matrix.js.map +1 -0
  101. package/dist/tools/computer.js +5 -5
  102. package/dist/tools/computer.js.map +1 -1
  103. package/dist/tools/fetch.d.ts +2 -0
  104. package/dist/tools/fetch.d.ts.map +1 -0
  105. package/dist/tools/fetch.js +106 -0
  106. package/dist/tools/fetch.js.map +1 -0
  107. package/dist/tools/files.d.ts.map +1 -1
  108. package/dist/tools/files.js +112 -6
  109. package/dist/tools/files.js.map +1 -1
  110. package/dist/tools/git.js +3 -3
  111. package/dist/tools/git.js.map +1 -1
  112. package/dist/tools/github.d.ts +2 -0
  113. package/dist/tools/github.d.ts.map +1 -0
  114. package/dist/tools/github.js +196 -0
  115. package/dist/tools/github.js.map +1 -0
  116. package/dist/tools/index.d.ts +29 -5
  117. package/dist/tools/index.d.ts.map +1 -1
  118. package/dist/tools/index.js +136 -20
  119. package/dist/tools/index.js.map +1 -1
  120. package/dist/tools/index.test.d.ts +2 -0
  121. package/dist/tools/index.test.d.ts.map +1 -0
  122. package/dist/tools/index.test.js +162 -0
  123. package/dist/tools/index.test.js.map +1 -0
  124. package/dist/tools/matrix.d.ts +2 -0
  125. package/dist/tools/matrix.d.ts.map +1 -0
  126. package/dist/tools/matrix.js +79 -0
  127. package/dist/tools/matrix.js.map +1 -0
  128. package/dist/tools/mcp-client.d.ts +2 -0
  129. package/dist/tools/mcp-client.d.ts.map +1 -0
  130. package/dist/tools/mcp-client.js +295 -0
  131. package/dist/tools/mcp-client.js.map +1 -0
  132. package/dist/tools/notebook.d.ts +2 -0
  133. package/dist/tools/notebook.d.ts.map +1 -0
  134. package/dist/tools/notebook.js +207 -0
  135. package/dist/tools/notebook.js.map +1 -0
  136. package/dist/tools/openclaw.d.ts +2 -0
  137. package/dist/tools/openclaw.d.ts.map +1 -0
  138. package/dist/tools/openclaw.js +187 -0
  139. package/dist/tools/openclaw.js.map +1 -0
  140. package/dist/tools/parallel.d.ts +2 -0
  141. package/dist/tools/parallel.d.ts.map +1 -0
  142. package/dist/tools/parallel.js +60 -0
  143. package/dist/tools/parallel.js.map +1 -0
  144. package/dist/tools/sandbox.d.ts +2 -0
  145. package/dist/tools/sandbox.d.ts.map +1 -0
  146. package/dist/tools/sandbox.js +352 -0
  147. package/dist/tools/sandbox.js.map +1 -0
  148. package/dist/tools/search.d.ts.map +1 -1
  149. package/dist/tools/search.js +135 -28
  150. package/dist/tools/search.js.map +1 -1
  151. package/dist/tools/subagent.d.ts +4 -0
  152. package/dist/tools/subagent.d.ts.map +1 -0
  153. package/dist/tools/subagent.js +260 -0
  154. package/dist/tools/subagent.js.map +1 -0
  155. package/dist/tools/tasks.d.ts +14 -0
  156. package/dist/tools/tasks.d.ts.map +1 -0
  157. package/dist/tools/tasks.js +210 -0
  158. package/dist/tools/tasks.js.map +1 -0
  159. package/dist/tools/worktree.d.ts +2 -0
  160. package/dist/tools/worktree.d.ts.map +1 -0
  161. package/dist/tools/worktree.js +223 -0
  162. package/dist/tools/worktree.js.map +1 -0
  163. package/dist/tui.d.ts +73 -0
  164. package/dist/tui.d.ts.map +1 -0
  165. package/dist/tui.js +257 -0
  166. package/dist/tui.js.map +1 -0
  167. package/dist/ui.d.ts +11 -19
  168. package/dist/ui.d.ts.map +1 -1
  169. package/dist/ui.js +143 -171
  170. package/dist/ui.js.map +1 -1
  171. package/dist/updater.d.ts +3 -0
  172. package/dist/updater.d.ts.map +1 -0
  173. package/dist/updater.js +70 -0
  174. package/dist/updater.js.map +1 -0
  175. package/install.sh +5 -7
  176. package/package.json +9 -5
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 { getApiKey, setupAuth, getUsageStats, loadConfig, verifyApiKey, setupByok, isByokEnabled, disableByok, detectProvider, getByokProvider, PROVIDERS } from './auth.js';
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 { banner, bannerCompact, bannerAuth, matrixConnect, prompt as kbotPrompt, printUsage, printError, printSuccess, printInfo, printResponse, printHelp, printGoodbye, } from './ui.js';
19
- const VERSION = '1.3.1';
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.1';
20
25
  async function main() {
21
26
  const program = new Command();
22
27
  program
23
28
  .name('kbot')
24
- .description('K:BOT — Kernel Matrix Terminal Agent')
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('--computer-use', 'Enable computer use tools (Enterprise only)')
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('auth')
37
- .description('Configure API key')
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 authFlow();
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 — use your own LLM API key (Anthropic, OpenAI, or Google)')
44
- .option('--off', 'Disable BYOK mode (switch back to Kernel API)')
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('BYOK disabled. Using Kernel API key.');
106
+ printSuccess('Provider disabled.');
49
107
  return;
50
108
  }
51
109
  await byokFlow();
52
110
  });
53
111
  program
54
- .command('usage')
55
- .description('Show usage statistics')
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
- const stats = await getUsageStats();
58
- if (!stats) {
59
- printError('Could not fetch usage. Is your API key valid?');
60
- process.exit(1);
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', 'usage', 'byok'].includes(program.args[0]))
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
- // Register tools
82
- await registerAllTools();
83
- // Gather project context (once at startup)
84
- const context = gatherContext();
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 = byokActive ? 'growth' : (config?.tier || 'free');
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
- // One-shot mode: kbot "fix the bug"
95
- if (promptArgs.length > 0 && !['auth', 'usage', 'byok'].includes(promptArgs[0])) {
96
- console.log(bannerCompact());
97
- const message = promptArgs.join(' ');
98
- await runAndPrint(message, agentOpts);
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
- // Interactive REPL mode
102
- await startRepl(agentOpts, context, tier);
103
- }
104
- async function authFlow() {
105
- const rl = createInterface({ input: process.stdin, output: process.stdout });
106
- console.log(bannerAuth());
107
- printInfo('Enter your API key (get one at kernel.chat/#/api-docs):');
108
- console.log();
109
- const key = await new Promise((resolve) => {
110
- rl.question(' kn_live_', (answer) => {
111
- resolve('kn_live_' + answer.trim());
112
- rl.close();
113
- });
114
- });
115
- if (!key.startsWith('kn_live_') || key.length < 12) {
116
- printError('Invalid key format. Keys start with kn_live_');
117
- process.exit(1);
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
- printInfo('Connecting to Kernel Matrix...');
120
- const result = await verifyApiKey(key);
121
- if (!result.valid) {
122
- printError(`Connection failed: ${result.error}`);
123
- process.exit(1);
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
- const ok = await setupAuth(key);
126
- if (!ok) {
127
- printError('Key verification failed. Check your key and try again.');
128
- process.exit(1);
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
- console.log();
131
- console.log(matrixConnect(result.tier || 'free', result.agents?.length || 5));
132
- printSuccess('Ready. Run `kbot` to start.');
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 (12):');
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
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('Kernel routing + 17 agents + tools = free.');
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
- async function startRepl(agentOpts, context, tier) {
205
- console.log(banner());
206
- // Show connection info
207
- const byok = isByokEnabled();
208
- const agentCount = byok || tier === 'growth' || tier === 'enterprise' ? 17 : 5;
209
- if (byok) {
210
- const provider = getByokProvider();
211
- const providerConfig = PROVIDERS[provider];
212
- printSuccess(`BYOK mode — ${providerConfig.name} (no message limits)`);
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
- console.log(matrixConnect(byok ? 'byok' : tier, agentCount));
215
- if (context.isGitRepo) {
216
- printInfo(`Project: ${context.repoRoot?.split('/').pop() || 'unknown'} (${context.language || 'unknown'})`);
217
- printInfo(`Branch: ${context.branch || 'unknown'}`);
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
- printInfo('Type a message, or /help for commands.');
221
- console.log();
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
- rl.on('line', async (line) => {
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
- // Slash commands
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
- // Run agent
246
- try {
247
- const response = await runAgent(input, agentOpts);
248
- printResponse(response.agent, response.content);
249
- if (response.usage) {
250
- const { input_tokens, output_tokens, cost_usd } = response.usage;
251
- printInfo(`${response.agent} · ${response.model} · ${input_tokens + output_tokens} tokens · $${cost_usd.toFixed(4)}${response.toolCalls > 0 ? ` · ${response.toolCalls} tool calls` : ''}`);
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 if (response.agent === 'local') {
254
- // Local execution — no tokens used, already printed by agent.ts
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
- catch (err) {
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
- console.log();
261
- rl.prompt();
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('Press Ctrl+C again or type /quit to exit.');
868
+ printInfo('Ctrl+C again to exit');
267
869
  rl.prompt();
870
+ // Reset after 2 seconds
871
+ setTimeout(() => { sigintCount = 0; }, 2000);
268
872
  });
269
- rl.on('close', () => {
270
- printGoodbye();
271
- process.exit(0);
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('Available: kernel, researcher, coder, writer, analyst (+ more with Growth tier)');
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
- opts.model = args[0];
293
- printSuccess(`Model set to: ${args[0]}`);
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
- printInfo(`Current model: ${opts.model || 'auto'}`);
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();