@ch4p/cli 0.1.3 → 0.1.5

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 (53) hide show
  1. package/dist/agent-6WIHK7NM.js +767 -0
  2. package/dist/agent-ANIZYPPF.js +767 -0
  3. package/dist/agent-HSAJ5EBN.js +761 -0
  4. package/dist/agent-N2P2SXGG.js +767 -0
  5. package/dist/audit-HLOQBMBT.js +12 -0
  6. package/dist/audit-UIGPH3FK.js +12 -0
  7. package/dist/canvas-3VTC4XPV.js +313 -0
  8. package/dist/canvas-4FMNW6FZ.js +313 -0
  9. package/dist/canvas-HJSLG76B.js +313 -0
  10. package/dist/canvas-XQHVCY27.js +313 -0
  11. package/dist/chunk-3XAW4XHG.js +185 -0
  12. package/dist/chunk-4IRZQCRN.js +1832 -0
  13. package/dist/chunk-AORLXQHZ.js +304 -0
  14. package/dist/chunk-BMEBRUYL.js +6995 -0
  15. package/dist/chunk-G6PJSDEJ.js +4372 -0
  16. package/dist/chunk-IN2I6XRM.js +185 -0
  17. package/dist/chunk-PAJOAXLQ.js +4368 -0
  18. package/dist/chunk-TB4IZ7F7.js +301 -0
  19. package/dist/chunk-U7S375OS.js +1841 -0
  20. package/dist/chunk-VJATFD4D.js +7003 -0
  21. package/dist/dist-37TB6EWP.js +25 -0
  22. package/dist/dist-CIJPZC2B.js +25 -0
  23. package/dist/doctor-5M3ZB435.js +274 -0
  24. package/dist/doctor-IQ3MWQSN.js +274 -0
  25. package/dist/gateway-DV5OL45G.js +2164 -0
  26. package/dist/gateway-H4Z2EQK2.js +2165 -0
  27. package/dist/gateway-LUCG72YX.js +2129 -0
  28. package/dist/gateway-O3QNSZKF.js +2123 -0
  29. package/dist/gateway-OJW7RY3H.js +2094 -0
  30. package/dist/gateway-PBLJEK5I.js +2165 -0
  31. package/dist/gateway-PHPRQTZP.js +2165 -0
  32. package/dist/gateway-YKKJ4DZE.js +2115 -0
  33. package/dist/gateway-Z65DCM2Q.js +2097 -0
  34. package/dist/gateway-ZSXTAYPF.js +2157 -0
  35. package/dist/gateway-ZVLF7B4C.js +2165 -0
  36. package/dist/identity-RHQFPSDS.js +215 -0
  37. package/dist/identity-VGDDAKBY.js +215 -0
  38. package/dist/index.js +12 -12
  39. package/dist/install-6LV7B2SV.js +378 -0
  40. package/dist/install-NAUPXVCI.js +378 -0
  41. package/dist/message-PTH4CEOD.js +189 -0
  42. package/dist/message-QCRZIBTO.js +189 -0
  43. package/dist/message-TGAPVVI4.js +189 -0
  44. package/dist/message-YQGIARNE.js +189 -0
  45. package/dist/onboard-CN56V5P6.js +849 -0
  46. package/dist/onboard-LJFC6HXD.js +849 -0
  47. package/dist/pairing-ARWQYATE.js +147 -0
  48. package/dist/pairing-PXCJMCT2.js +147 -0
  49. package/dist/skills-4EELFYO2.js +138 -0
  50. package/dist/skills-KXRTDSF2.js +138 -0
  51. package/dist/status-2ZJPK3VL.js +94 -0
  52. package/dist/status-W2OXOSH4.js +94 -0
  53. package/package.json +24 -24
@@ -0,0 +1,849 @@
1
+ import {
2
+ playFullAnimation
3
+ } from "./chunk-CNLYUY2K.js";
4
+ import {
5
+ runAudit
6
+ } from "./chunk-IN2I6XRM.js";
7
+ import {
8
+ configExists,
9
+ ensureConfigDir,
10
+ getConfigPath,
11
+ getDefaultConfig,
12
+ saveConfig
13
+ } from "./chunk-TB4IZ7F7.js";
14
+ import {
15
+ BOLD,
16
+ CHAPPIE_SMALL,
17
+ DIM,
18
+ GREEN,
19
+ RESET,
20
+ TEAL,
21
+ WHITE,
22
+ YELLOW,
23
+ box,
24
+ sectionHeader
25
+ } from "./chunk-NMGPBPNU.js";
26
+
27
+ // src/commands/onboard.ts
28
+ import * as readline from "readline";
29
+ import { execSync } from "child_process";
30
+ function detectBinary(name) {
31
+ try {
32
+ const cmd = process.platform === "win32" ? `where ${name}` : `which ${name}`;
33
+ execSync(cmd, { stdio: "ignore" });
34
+ return true;
35
+ } catch {
36
+ return false;
37
+ }
38
+ }
39
+ function detectEngines() {
40
+ const engines = [];
41
+ if (detectBinary("claude")) {
42
+ engines.push({
43
+ id: "claude-cli",
44
+ label: "Claude Code CLI",
45
+ description: "Uses your Max/Pro plan locally \u2014 no API key needed"
46
+ });
47
+ }
48
+ if (detectBinary("codex")) {
49
+ engines.push({
50
+ id: "codex-cli",
51
+ label: "Codex CLI",
52
+ description: "Uses your OpenAI account via codex \u2014 no API key needed"
53
+ });
54
+ }
55
+ if (detectBinary("ollama")) {
56
+ engines.push({
57
+ id: "ollama",
58
+ label: "Ollama (local)",
59
+ description: "Run models locally \u2014 no API key, fully offline"
60
+ });
61
+ }
62
+ return engines;
63
+ }
64
+ function listOllamaModels() {
65
+ try {
66
+ const raw = execSync("curl -sf http://localhost:11434/api/tags", {
67
+ timeout: 3e3,
68
+ encoding: "utf8"
69
+ });
70
+ const data = JSON.parse(raw);
71
+ if (Array.isArray(data?.models)) {
72
+ return data.models.map((m) => m.name);
73
+ }
74
+ } catch {
75
+ }
76
+ return [];
77
+ }
78
+ var CHANNEL_DEFS = [
79
+ {
80
+ id: "telegram",
81
+ label: "Telegram",
82
+ fields: [
83
+ { key: "botToken", label: "Bot token", secret: true }
84
+ ]
85
+ },
86
+ {
87
+ id: "discord",
88
+ label: "Discord",
89
+ fields: [
90
+ { key: "botToken", label: "Bot token", secret: true }
91
+ ]
92
+ },
93
+ {
94
+ id: "slack",
95
+ label: "Slack",
96
+ fields: [
97
+ { key: "botToken", label: "Bot token (xoxb-...)", secret: true },
98
+ { key: "appToken", label: "App token (xapp-...)", secret: true }
99
+ ]
100
+ },
101
+ {
102
+ id: "matrix",
103
+ label: "Matrix",
104
+ fields: [
105
+ { key: "homeserverUrl", label: "Homeserver URL", defaultValue: "https://matrix.org" },
106
+ { key: "accessToken", label: "Access token", secret: true },
107
+ { key: "userId", label: "User ID (e.g. @bot:matrix.org)" }
108
+ ]
109
+ },
110
+ {
111
+ id: "teams",
112
+ label: "Microsoft Teams",
113
+ fields: [
114
+ { key: "appId", label: "App (client) ID" },
115
+ { key: "appPassword", label: "App password", secret: true }
116
+ ]
117
+ },
118
+ {
119
+ id: "whatsapp",
120
+ label: "WhatsApp",
121
+ fields: [
122
+ { key: "phoneNumberId", label: "Phone number ID" },
123
+ { key: "accessToken", label: "Access token", secret: true },
124
+ { key: "verifyToken", label: "Webhook verify token" }
125
+ ]
126
+ },
127
+ {
128
+ id: "signal",
129
+ label: "Signal",
130
+ fields: [
131
+ { key: "signalCliPath", label: "signal-cli path", defaultValue: "signal-cli" },
132
+ { key: "phoneNumber", label: "Phone number (e.g. +1234567890)" }
133
+ ]
134
+ },
135
+ {
136
+ id: "imessage",
137
+ label: "iMessage",
138
+ fields: [],
139
+ notes: "macOS only. Uses AppleScript \u2014 no additional config needed."
140
+ },
141
+ {
142
+ id: "irc",
143
+ label: "IRC",
144
+ fields: [
145
+ { key: "server", label: "Server hostname" },
146
+ { key: "port", label: "Port", defaultValue: "6697" },
147
+ { key: "nick", label: "Nickname" },
148
+ { key: "channels", label: "Channels (comma-separated, e.g. #general,#dev)" }
149
+ ]
150
+ },
151
+ {
152
+ id: "zalo-oa",
153
+ label: "Zalo OA",
154
+ fields: [
155
+ { key: "oaId", label: "OA ID" },
156
+ { key: "accessToken", label: "Access token", secret: true },
157
+ { key: "oaSecretKey", label: "OA secret key", secret: true }
158
+ ]
159
+ },
160
+ {
161
+ id: "bluebubbles",
162
+ label: "BlueBubbles",
163
+ fields: [
164
+ { key: "serverUrl", label: "Server URL (e.g. http://localhost:1234)" },
165
+ { key: "password", label: "Password", secret: true }
166
+ ]
167
+ },
168
+ {
169
+ id: "google-chat",
170
+ label: "Google Chat",
171
+ fields: [
172
+ { key: "serviceAccountKeyPath", label: "Service account JSON key path" },
173
+ { key: "spaceId", label: "Space ID" }
174
+ ]
175
+ },
176
+ {
177
+ id: "webchat",
178
+ label: "WebChat",
179
+ fields: [
180
+ { key: "path", label: "WebSocket path", defaultValue: "/webchat" }
181
+ ]
182
+ },
183
+ {
184
+ id: "zalo-personal",
185
+ label: "Zalo Personal",
186
+ fields: [
187
+ { key: "bridgeUrl", label: "Bridge URL (e.g. http://localhost:3500)" }
188
+ ],
189
+ notes: "Requires your own Zalo automation bridge (e.g. zca-js). May violate Zalo TOS."
190
+ },
191
+ {
192
+ id: "macos",
193
+ label: "macOS Native",
194
+ fields: [],
195
+ notes: "macOS only. Uses Notification Center + AppleScript dialogs \u2014 no additional config needed."
196
+ }
197
+ ];
198
+ function parseYesNo(answer, defaultYes) {
199
+ const lower = answer.toLowerCase().trim();
200
+ if (lower === "") return defaultYes;
201
+ return lower === "y" || lower === "yes";
202
+ }
203
+ function parseMultiSelect(answer, maxIndex) {
204
+ if (answer.trim() === "") return [];
205
+ return answer.split(",").map((s) => parseInt(s.trim(), 10) - 1).filter((n) => !isNaN(n) && n >= 0 && n < maxIndex);
206
+ }
207
+ function createPromptInterface() {
208
+ return readline.createInterface({
209
+ input: process.stdin,
210
+ output: process.stdout
211
+ });
212
+ }
213
+ function ask(rl, question) {
214
+ return new Promise((resolve) => {
215
+ rl.question(question, (answer) => {
216
+ resolve(answer.trim());
217
+ });
218
+ });
219
+ }
220
+ function askSecret(rl, question) {
221
+ return new Promise((resolve) => {
222
+ process.stdout.write(question);
223
+ const stdin = process.stdin;
224
+ const wasRaw = stdin.isRaw;
225
+ if (stdin.isTTY) {
226
+ stdin.setRawMode(true);
227
+ }
228
+ let input = "";
229
+ const onData = (ch) => {
230
+ const char = ch.toString("utf8");
231
+ if (char === "\n" || char === "\r") {
232
+ if (stdin.isTTY) {
233
+ stdin.setRawMode(wasRaw ?? false);
234
+ }
235
+ stdin.removeListener("data", onData);
236
+ process.stdout.write("\n");
237
+ resolve(input.trim());
238
+ return;
239
+ }
240
+ if (char === "") {
241
+ if (stdin.isTTY) {
242
+ stdin.setRawMode(wasRaw ?? false);
243
+ }
244
+ stdin.removeListener("data", onData);
245
+ process.stdout.write("\n");
246
+ process.exit(130);
247
+ }
248
+ if (char === "\x7F" || char === "\b") {
249
+ if (input.length > 0) {
250
+ input = input.slice(0, -1);
251
+ process.stdout.write("\b \b");
252
+ }
253
+ return;
254
+ }
255
+ input += char;
256
+ process.stdout.write("*");
257
+ };
258
+ rl.pause();
259
+ stdin.on("data", onData);
260
+ stdin.resume();
261
+ });
262
+ }
263
+ async function askYesNo(rl, question, defaultYes = false) {
264
+ const hint = defaultYes ? "Y/n" : "y/N";
265
+ const answer = await ask(rl, ` ${TEAL}> ${question} [${hint}]: ${RESET}`);
266
+ return parseYesNo(answer, defaultYes);
267
+ }
268
+ async function askMultiSelect(rl, items) {
269
+ const half = Math.ceil(items.length / 2);
270
+ for (let i = 0; i < half; i++) {
271
+ const left = ` ${DIM}${String(i + 1).padStart(2)}.${RESET} ${items[i].label}`;
272
+ const leftPad = left.padEnd(42);
273
+ const rightIdx = i + half;
274
+ const right = rightIdx < items.length ? `${DIM}${String(rightIdx + 1).padStart(2)}.${RESET} ${items[rightIdx].label}` : "";
275
+ console.log(`${leftPad}${right}`);
276
+ }
277
+ const answer = await ask(rl, ` ${TEAL}> Enter numbers (e.g. 1,3,5), or Enter to skip: ${RESET}`);
278
+ return parseMultiSelect(answer, items.length);
279
+ }
280
+ var MODELS = [
281
+ { id: "claude-sonnet-4-20250514", label: "Claude Sonnet 4 (recommended)", provider: "anthropic" },
282
+ { id: "claude-opus-4-20250514", label: "Claude Opus 4", provider: "anthropic" },
283
+ { id: "gpt-4o", label: "GPT-4o", provider: "openai" },
284
+ { id: "gpt-4o-mini", label: "GPT-4o Mini", provider: "openai" }
285
+ ];
286
+ var AUTONOMY_LEVELS = [
287
+ { id: "readonly", label: "Read-only \u2014 Agent can only read files and run safe commands" },
288
+ { id: "supervised", label: "Supervised \u2014 Agent asks before writes and destructive actions (recommended)" },
289
+ { id: "full", label: "Full \u2014 Agent operates autonomously (use with caution)" }
290
+ ];
291
+ async function configureProviders(rl, config) {
292
+ console.log("");
293
+ console.log(sectionHeader("Additional Providers"));
294
+ console.log("");
295
+ if (await askYesNo(rl, "Configure Google/Gemini API key?")) {
296
+ console.log(` ${DIM}Get yours at https://aistudio.google.com/apikey${RESET}`);
297
+ const key = await askSecret(rl, ` ${TEAL}> API key: ${RESET}`);
298
+ if (key) {
299
+ config.providers["google"] = { apiKey: key };
300
+ console.log(` ${GREEN}Saved.${RESET}
301
+ `);
302
+ } else {
303
+ console.log(` ${DIM}Skipped.${RESET}
304
+ `);
305
+ }
306
+ }
307
+ if (await askYesNo(rl, "Configure OpenRouter API key?")) {
308
+ console.log(` ${DIM}Get yours at https://openrouter.ai/keys${RESET}`);
309
+ const key = await askSecret(rl, ` ${TEAL}> API key: ${RESET}`);
310
+ if (key) {
311
+ config.providers["openrouter"] = { apiKey: key };
312
+ console.log(` ${GREEN}Saved.${RESET}
313
+ `);
314
+ } else {
315
+ console.log(` ${DIM}Skipped.${RESET}
316
+ `);
317
+ }
318
+ }
319
+ if (await askYesNo(rl, "Configure AWS Bedrock?")) {
320
+ const accessKey = await askSecret(rl, ` ${TEAL}> AWS Access Key ID: ${RESET}`);
321
+ const secretKey = await askSecret(rl, ` ${TEAL}> AWS Secret Access Key: ${RESET}`);
322
+ const region = await ask(rl, ` ${TEAL}> AWS Region [us-east-1]: ${RESET}`) || "us-east-1";
323
+ if (accessKey && secretKey) {
324
+ config.providers["bedrock"] = { accessKeyId: accessKey, secretAccessKey: secretKey, region };
325
+ console.log(` ${GREEN}Saved.${RESET}
326
+ `);
327
+ } else {
328
+ console.log(` ${DIM}Skipped.${RESET}
329
+ `);
330
+ }
331
+ }
332
+ }
333
+ async function configureChannels(rl, config) {
334
+ console.log("");
335
+ console.log(sectionHeader("Channels"));
336
+ console.log(` ${DIM}Enable messaging channels for ch4p to receive and send messages.${RESET}`);
337
+ console.log("");
338
+ const selected = await askMultiSelect(rl, CHANNEL_DEFS.map((c) => ({ label: c.label })));
339
+ if (selected.length === 0) {
340
+ console.log(` ${DIM}No channels selected.${RESET}
341
+ `);
342
+ return;
343
+ }
344
+ for (const idx of selected) {
345
+ const def = CHANNEL_DEFS[idx];
346
+ console.log(`
347
+ ${BOLD}${def.label}${RESET}`);
348
+ if (def.notes) {
349
+ console.log(` ${YELLOW}Note: ${def.notes}${RESET}`);
350
+ }
351
+ if (def.fields.length === 0) {
352
+ config.channels[def.id] = { enabled: true };
353
+ console.log(` ${GREEN}Enabled.${RESET}`);
354
+ continue;
355
+ }
356
+ const channelConfig = { enabled: true };
357
+ for (const field of def.fields) {
358
+ const defaultHint = field.defaultValue ? ` [${field.defaultValue}]` : "";
359
+ let value;
360
+ if (field.secret) {
361
+ value = await askSecret(rl, ` ${TEAL}> ${field.label}${defaultHint}: ${RESET}`);
362
+ } else {
363
+ value = await ask(rl, ` ${TEAL}> ${field.label}${defaultHint}: ${RESET}`);
364
+ }
365
+ channelConfig[field.key] = value || field.defaultValue || "";
366
+ }
367
+ config.channels[def.id] = channelConfig;
368
+ console.log(` ${GREEN}${def.label} configured.${RESET}`);
369
+ }
370
+ console.log("");
371
+ }
372
+ async function configureSearch(rl, config) {
373
+ if (!await askYesNo(rl, "Configure web search (Brave Search API)?")) return;
374
+ console.log(` ${DIM}Get a key at https://api.search.brave.com${RESET}`);
375
+ const key = await askSecret(rl, ` ${TEAL}> Brave API key: ${RESET}`);
376
+ if (key) {
377
+ config.search = {
378
+ enabled: true,
379
+ provider: "brave",
380
+ apiKey: key,
381
+ maxResults: 5
382
+ };
383
+ console.log(` ${GREEN}Web search enabled.${RESET}
384
+ `);
385
+ } else {
386
+ console.log(` ${DIM}Skipped.${RESET}
387
+ `);
388
+ }
389
+ }
390
+ async function configureBrowser(rl, _config) {
391
+ if (!await askYesNo(rl, "Enable browser tool (Playwright)?")) return;
392
+ console.log(` ${GREEN}Browser tool will be available.${RESET}`);
393
+ console.log(` ${DIM}Install Playwright: npx playwright install chromium${RESET}
394
+ `);
395
+ }
396
+ async function configureVoice(rl, config) {
397
+ if (!await askYesNo(rl, "Configure voice pipeline (STT/TTS)?")) return;
398
+ console.log("");
399
+ console.log(` ${BOLD}Speech-to-Text provider:${RESET}`);
400
+ console.log(` ${DIM}1.${RESET} Whisper (default)`);
401
+ console.log(` ${DIM}2.${RESET} Deepgram`);
402
+ const sttChoice = await ask(rl, ` ${TEAL}> Choice [1]: ${RESET}`);
403
+ const sttProvider = sttChoice === "2" ? "deepgram" : "whisper";
404
+ let sttApiKey;
405
+ if (sttProvider === "deepgram") {
406
+ sttApiKey = await askSecret(rl, ` ${TEAL}> Deepgram API key: ${RESET}`) || void 0;
407
+ }
408
+ console.log("");
409
+ console.log(` ${BOLD}Text-to-Speech provider:${RESET}`);
410
+ console.log(` ${DIM}1.${RESET} ElevenLabs`);
411
+ console.log(` ${DIM}2.${RESET} None (skip TTS)`);
412
+ const ttsChoice = await ask(rl, ` ${TEAL}> Choice [2]: ${RESET}`);
413
+ const ttsProvider = ttsChoice === "1" ? "elevenlabs" : "none";
414
+ let ttsApiKey;
415
+ let ttsVoiceId;
416
+ if (ttsProvider === "elevenlabs") {
417
+ ttsApiKey = await askSecret(rl, ` ${TEAL}> ElevenLabs API key: ${RESET}`) || void 0;
418
+ ttsVoiceId = await ask(rl, ` ${TEAL}> Voice ID (Enter for default): ${RESET}`) || void 0;
419
+ }
420
+ config.voice = {
421
+ enabled: true,
422
+ stt: { provider: sttProvider, apiKey: sttApiKey },
423
+ tts: { provider: ttsProvider, apiKey: ttsApiKey, voiceId: ttsVoiceId }
424
+ };
425
+ console.log(` ${GREEN}Voice pipeline configured.${RESET}
426
+ `);
427
+ }
428
+ async function configureMcp(rl, _config) {
429
+ if (!await askYesNo(rl, "Configure MCP servers?")) return;
430
+ console.log(` ${DIM}MCP servers are configured in ~/.ch4p/config.json under "mcp.servers".${RESET}`);
431
+ console.log(` ${DIM}See docs/how-to/use-mcp.md for details.${RESET}
432
+ `);
433
+ }
434
+ async function configureCron(rl, _config) {
435
+ if (!await askYesNo(rl, "Enable cron scheduler & webhooks?")) return;
436
+ console.log(` ${GREEN}Cron scheduler and webhooks are available via the gateway.${RESET}`);
437
+ console.log(` ${DIM}Configure jobs in ~/.ch4p/config.json under "cron.jobs".${RESET}`);
438
+ console.log(` ${DIM}See docs/how-to/use-cron-webhooks.md for details.${RESET}
439
+ `);
440
+ }
441
+ async function configureX402(rl, config) {
442
+ if (!await askYesNo(rl, "Configure x402 micropayments?")) return;
443
+ console.log("");
444
+ console.log(` ${DIM}x402 enables HTTP micropayments on Base/USDC.${RESET}`);
445
+ console.log(` ${DIM}Server: require payment to access gateway endpoints.${RESET}`);
446
+ console.log(` ${DIM}Client: sign payments when the agent hits 402 responses.${RESET}`);
447
+ console.log("");
448
+ const enableServer = await askYesNo(rl, " Enable server-side payment enforcement?");
449
+ const enableClient = await askYesNo(rl, " Enable client-side payment signing?");
450
+ if (!enableServer && !enableClient) {
451
+ console.log(` ${DIM}Skipped.${RESET}
452
+ `);
453
+ return;
454
+ }
455
+ const x402 = { enabled: true };
456
+ if (enableServer) {
457
+ const payTo = await ask(rl, ` ${TEAL}> Receiving wallet address (0x...): ${RESET}`);
458
+ const amount = await ask(rl, ` ${TEAL}> Amount in USDC smallest unit [1000000]: ${RESET}`);
459
+ const net = await ask(rl, ` ${TEAL}> Network [base]: ${RESET}`);
460
+ x402.server = {
461
+ payTo: payTo.trim() || "0x0000000000000000000000000000000000000000",
462
+ amount: amount.trim() || "1000000",
463
+ network: net.trim() || "base"
464
+ };
465
+ }
466
+ if (enableClient) {
467
+ console.log(` ${DIM}Tip: store key as X402_PRIVATE_KEY env var \u2014 leave blank to use it.${RESET}`);
468
+ const pk = await askSecret(rl, ` ${TEAL}> Private key (blank = use \${X402_PRIVATE_KEY}): ${RESET}`);
469
+ const net = await ask(rl, ` ${TEAL}> Network [base]: ${RESET}`);
470
+ const network = net.trim() || "base";
471
+ const client = {
472
+ privateKey: pk || "${X402_PRIVATE_KEY}"
473
+ };
474
+ if (network === "base-sepolia") {
475
+ client.chainId = 84532;
476
+ client.tokenAddress = "0x036CbD53842c5426634e7929541eC2318f3dCF7e";
477
+ }
478
+ x402.client = client;
479
+ }
480
+ config.x402 = x402;
481
+ console.log(` ${GREEN}x402 configured.${RESET}
482
+ `);
483
+ }
484
+ async function configureVoiceWake(rl, config) {
485
+ if (!await askYesNo(rl, "Enable always-on voice wake (hot-mic listening)?")) return;
486
+ console.log(` ${DIM}Requires SoX (rec) \u2014 macOS: brew install sox, Linux: apt install sox${RESET}`);
487
+ let wakeWord;
488
+ const useWakeWord = await askYesNo(rl, 'Use a wake word (e.g. "hey ch4p")?');
489
+ if (useWakeWord) {
490
+ wakeWord = await ask(rl, ` ${TEAL}> Wake word: ${RESET}`) || void 0;
491
+ }
492
+ const thresholdStr = await ask(rl, ` ${TEAL}> Energy threshold [500]: ${RESET}`);
493
+ const energyThreshold = thresholdStr ? parseInt(thresholdStr, 10) : void 0;
494
+ const voiceConfig = config.voice;
495
+ const wake = { enabled: true };
496
+ if (wakeWord) wake.wakeWord = wakeWord;
497
+ if (energyThreshold && !isNaN(energyThreshold)) wake.energyThreshold = energyThreshold;
498
+ if (voiceConfig) {
499
+ voiceConfig.wake = wake;
500
+ } else {
501
+ config.voice = { enabled: false, wake };
502
+ }
503
+ console.log(` ${GREEN}Voice wake enabled${wakeWord ? ` (wake word: "${wakeWord}")` : " (push-to-talk style)"}.${RESET}
504
+ `);
505
+ }
506
+ async function configureMesh(rl, config) {
507
+ if (!await askYesNo(rl, "Enable mesh orchestration (multi-agent swarm)?")) return;
508
+ const concurrencyStr = await ask(rl, ` ${TEAL}> Max concurrent sub-agents [3]: ${RESET}`);
509
+ const maxConcurrency = concurrencyStr ? parseInt(concurrencyStr, 10) : 3;
510
+ config.mesh = {
511
+ enabled: true,
512
+ maxConcurrency: !isNaN(maxConcurrency) && maxConcurrency >= 1 && maxConcurrency <= 10 ? maxConcurrency : 3,
513
+ defaultTimeout: 12e4
514
+ };
515
+ console.log(` ${GREEN}Mesh orchestration enabled (max ${maxConcurrency} concurrent agents).${RESET}
516
+ `);
517
+ }
518
+ async function configureMemory(rl, config) {
519
+ if (!await askYesNo(rl, "Configure memory backend?")) return;
520
+ console.log("");
521
+ console.log(` ${BOLD}Memory backend:${RESET}`);
522
+ console.log(` ${DIM}1.${RESET} SQLite (default \u2014 vector + keyword search)`);
523
+ console.log(` ${DIM}2.${RESET} Markdown (flat files)`);
524
+ console.log(` ${DIM}3.${RESET} None (disable memory)`);
525
+ const choice = await ask(rl, ` ${TEAL}> Choice [1]: ${RESET}`);
526
+ if (choice === "2") {
527
+ config.memory.backend = "markdown";
528
+ } else if (choice === "3") {
529
+ config.memory.backend = "noop";
530
+ } else {
531
+ config.memory.backend = "sqlite";
532
+ }
533
+ console.log(` ${GREEN}Memory backend: ${config.memory.backend}${RESET}
534
+ `);
535
+ }
536
+ async function configureVerification(rl, config) {
537
+ if (!await askYesNo(rl, "Configure verification settings?")) return;
538
+ const enabled = await askYesNo(rl, "Enable task verification?", true);
539
+ let semantic = true;
540
+ if (enabled) {
541
+ semantic = await askYesNo(rl, "Enable semantic (LLM) verification?", true);
542
+ }
543
+ config.verification = { enabled, semantic };
544
+ console.log(` ${GREEN}Verification: ${enabled ? "on" : "off"}${semantic ? " (semantic)" : ""}${RESET}
545
+ `);
546
+ }
547
+ async function configureGateway(rl, config) {
548
+ if (!await askYesNo(rl, "Configure gateway settings?")) return;
549
+ const portStr = await ask(rl, ` ${TEAL}> Gateway port [${config.gateway.port}]: ${RESET}`);
550
+ if (portStr) {
551
+ const port = parseInt(portStr, 10);
552
+ if (!isNaN(port) && port > 0 && port <= 65535) {
553
+ config.gateway.port = port;
554
+ }
555
+ }
556
+ const requirePairing = await askYesNo(rl, "Require pairing tokens?", true);
557
+ config.gateway.requirePairing = requirePairing;
558
+ console.log(` ${GREEN}Gateway: port ${config.gateway.port}, pairing ${requirePairing ? "on" : "off"}${RESET}
559
+ `);
560
+ }
561
+ async function configureSecurity(rl, config) {
562
+ if (!await askYesNo(rl, "Configure security settings?")) return;
563
+ const workspaceOnly = await askYesNo(rl, "Restrict file access to workspace?", true);
564
+ config.security.workspaceOnly = workspaceOnly;
565
+ const blockedStr = await ask(rl, ` ${TEAL}> Blocked paths (comma-separated, Enter to skip): ${RESET}`);
566
+ if (blockedStr) {
567
+ config.security.blockedPaths = blockedStr.split(",").map((s) => s.trim()).filter(Boolean);
568
+ }
569
+ console.log(` ${GREEN}Security: workspace-only ${workspaceOnly ? "on" : "off"}, ${config.security.blockedPaths.length} blocked paths${RESET}
570
+ `);
571
+ }
572
+ async function configureAllowedCommands(rl, config) {
573
+ if (!await askYesNo(rl, "Configure allowed commands?")) return;
574
+ console.log(` ${DIM}Current: ${config.autonomy.allowedCommands.join(", ")}${RESET}`);
575
+ const addStr = await ask(rl, ` ${TEAL}> Additional commands (comma-separated, Enter to skip): ${RESET}`);
576
+ if (addStr) {
577
+ const additional = addStr.split(",").map((s) => s.trim()).filter(Boolean);
578
+ config.autonomy.allowedCommands.push(...additional);
579
+ console.log(` ${GREEN}Added: ${additional.join(", ")}${RESET}
580
+ `);
581
+ } else {
582
+ console.log(` ${DIM}Keeping defaults.${RESET}
583
+ `);
584
+ }
585
+ }
586
+ async function configureTunnel(rl, config) {
587
+ if (!await askYesNo(rl, "Configure tunnel provider?")) return;
588
+ console.log("");
589
+ console.log(` ${BOLD}Tunnel provider:${RESET}`);
590
+ console.log(` ${DIM}1.${RESET} None (default)`);
591
+ console.log(` ${DIM}2.${RESET} Cloudflare Tunnel`);
592
+ console.log(` ${DIM}3.${RESET} Tailscale`);
593
+ console.log(` ${DIM}4.${RESET} ngrok`);
594
+ const choice = await ask(rl, ` ${TEAL}> Choice [1]: ${RESET}`);
595
+ const providers = ["none", "cloudflare", "tailscale", "ngrok"];
596
+ const idx = choice ? parseInt(choice, 10) - 1 : 0;
597
+ config.tunnel.provider = providers[idx] ?? "none";
598
+ console.log(` ${GREEN}Tunnel: ${config.tunnel.provider}${RESET}
599
+ `);
600
+ }
601
+ async function configureCanvas(rl, config) {
602
+ if (!await askYesNo(rl, "Configure canvas workspace?")) return;
603
+ const portStr = await ask(rl, ` ${TEAL}> Canvas port [4800]: ${RESET}`);
604
+ const port = portStr ? parseInt(portStr, 10) : 4800;
605
+ config.canvas = {
606
+ enabled: true,
607
+ port: !isNaN(port) && port > 0 && port <= 65535 ? port : 4800
608
+ };
609
+ console.log(` ${GREEN}Canvas enabled on port ${config.canvas.port}.${RESET}
610
+ `);
611
+ }
612
+ async function configureObservability(rl, config) {
613
+ if (!await askYesNo(rl, "Configure observability?")) return;
614
+ console.log("");
615
+ console.log(` ${BOLD}Log level:${RESET}`);
616
+ console.log(` ${DIM}1.${RESET} debug`);
617
+ console.log(` ${DIM}2.${RESET} info (default)`);
618
+ console.log(` ${DIM}3.${RESET} warn`);
619
+ console.log(` ${DIM}4.${RESET} error`);
620
+ const choice = await ask(rl, ` ${TEAL}> Choice [2]: ${RESET}`);
621
+ const levels = ["debug", "info", "warn", "error"];
622
+ const idx = choice ? parseInt(choice, 10) - 1 : 1;
623
+ config.observability.logLevel = levels[idx] ?? "info";
624
+ const fileObserver = await askYesNo(rl, "Enable file observer (JSONL logs)?");
625
+ config.observability.observers = fileObserver ? ["console", "file"] : ["console"];
626
+ console.log(` ${GREEN}Observability: ${config.observability.logLevel}, observers: ${config.observability.observers.join(", ")}${RESET}
627
+ `);
628
+ }
629
+ async function configureSkills(rl, config) {
630
+ if (!await askYesNo(rl, "Configure skills?")) return;
631
+ console.log(` ${DIM}Default skill paths: ${config.skills.paths.join(", ")}${RESET}`);
632
+ const additionalPaths = await ask(rl, ` ${TEAL}> Additional skill paths (comma-separated, Enter to skip): ${RESET}`);
633
+ if (additionalPaths) {
634
+ const paths = additionalPaths.split(",").map((s) => s.trim()).filter(Boolean);
635
+ config.skills.paths.push(...paths);
636
+ console.log(` ${GREEN}Added: ${paths.join(", ")}${RESET}
637
+ `);
638
+ } else {
639
+ console.log(` ${DIM}Keeping defaults.${RESET}
640
+ `);
641
+ }
642
+ }
643
+ async function onboard() {
644
+ const mascotLines = [];
645
+ const info = [
646
+ "",
647
+ `${TEAL}${BOLD}ch4p${RESET} ${DIM}Personal AI Assistant${RESET}`,
648
+ "",
649
+ `${DIM}Security-first. BEAM-inspired. Zero-dependency memory.${RESET}`,
650
+ ""
651
+ ];
652
+ for (let i = 0; i < Math.max(CHAPPIE_SMALL.length, info.length); i++) {
653
+ const art = i < CHAPPIE_SMALL.length ? `${TEAL}${CHAPPIE_SMALL[i]}${RESET}` : " ";
654
+ const text = i < info.length ? info[i] : "";
655
+ mascotLines.push(`${art} ${text}`);
656
+ }
657
+ console.log("");
658
+ console.log(box("", mascotLines));
659
+ console.log("");
660
+ if (configExists()) {
661
+ console.log(` ${YELLOW}A config file already exists at ${getConfigPath()}${RESET}`);
662
+ console.log(` ${YELLOW}Running the wizard will overwrite it.${RESET}
663
+ `);
664
+ }
665
+ const rl = createPromptInterface();
666
+ const config = getDefaultConfig();
667
+ try {
668
+ console.log(` ${DIM}Let's get you set up. Press Enter to skip any step.${RESET}
669
+ `);
670
+ const detectedEngines = detectEngines();
671
+ let useApiKeys = true;
672
+ let step = 0;
673
+ let totalSteps = 6;
674
+ if (detectedEngines.length > 0) {
675
+ step++;
676
+ console.log(` ${TEAL}${BOLD}Step ${step}${RESET} ${WHITE}Engine Setup${RESET}`);
677
+ console.log("");
678
+ for (const eng of detectedEngines) {
679
+ console.log(` ${GREEN}\u2713${RESET} ${eng.label} detected`);
680
+ }
681
+ console.log("");
682
+ for (let i = 0; i < detectedEngines.length; i++) {
683
+ const eng = detectedEngines[i];
684
+ const marker = i === 0 ? ` ${GREEN}(recommended)${RESET}` : "";
685
+ console.log(` ${DIM}${i + 1}.${RESET} ${eng.label} \u2014 ${eng.description}${marker}`);
686
+ }
687
+ const apiIdx = detectedEngines.length + 1;
688
+ console.log(` ${DIM}${apiIdx}.${RESET} API key setup (Anthropic/OpenAI keys)`);
689
+ const engineChoice = await ask(rl, ` ${TEAL}> Choice [1]: ${RESET}`);
690
+ const choiceNum = engineChoice ? parseInt(engineChoice, 10) : 1;
691
+ const chosenEngineIdx = choiceNum - 1;
692
+ if (chosenEngineIdx >= 0 && chosenEngineIdx < detectedEngines.length) {
693
+ const chosen = detectedEngines[chosenEngineIdx];
694
+ useApiKeys = false;
695
+ totalSteps = 4;
696
+ if (chosen.id === "claude-cli" || chosen.id === "codex-cli") {
697
+ config.engines.default = chosen.id;
698
+ console.log(` ${GREEN}Engine set to ${chosen.label}.${RESET}
699
+ `);
700
+ if (chosen.id === "claude-cli") {
701
+ console.log(` ${YELLOW}${BOLD}\u26A0 Personal use only${RESET}`);
702
+ console.log(` ${YELLOW}This setup is intended for personal, local use only.${RESET}`);
703
+ console.log(` ${YELLOW}Do not use it in a commercial product or multi-user service${RESET}`);
704
+ console.log(` ${YELLOW}where ch4p routes your subscription on behalf of other users.${RESET}`);
705
+ console.log("");
706
+ console.log(` ${DIM}For production or shared deployments, use an API key instead.${RESET}`);
707
+ console.log(` ${DIM}See: https://console.anthropic.com${RESET}
708
+ `);
709
+ }
710
+ } else if (chosen.id === "ollama") {
711
+ config.engines.default = "native";
712
+ config.agent.provider = "ollama";
713
+ const models = listOllamaModels();
714
+ if (models.length > 0) {
715
+ console.log(` ${GREEN}Ollama server is running.${RESET} Installed models:
716
+ `);
717
+ for (let i = 0; i < models.length; i++) {
718
+ const marker = i === 0 ? ` ${GREEN}(default)${RESET}` : "";
719
+ console.log(` ${DIM}${i + 1}.${RESET} ${models[i]}${marker}`);
720
+ }
721
+ const modelChoice = await ask(rl, ` ${TEAL}> Choice [1]: ${RESET}`);
722
+ const modelIdx = modelChoice ? parseInt(modelChoice, 10) - 1 : 0;
723
+ config.agent.model = models[modelIdx] ?? models[0] ?? "llama3.3";
724
+ } else {
725
+ config.agent.model = "llama3.3";
726
+ console.log(` ${YELLOW}Ollama server not reachable. Using default model: llama3.3${RESET}`);
727
+ console.log(` ${DIM}Start Ollama with 'ollama serve' before running ch4p.${RESET}`);
728
+ }
729
+ console.log(` ${GREEN}Engine set to Ollama (${config.agent.model}).${RESET}
730
+ `);
731
+ }
732
+ } else {
733
+ useApiKeys = true;
734
+ totalSteps = 7;
735
+ console.log(` ${DIM}Proceeding with API key setup.${RESET}
736
+ `);
737
+ }
738
+ }
739
+ if (useApiKeys) {
740
+ step++;
741
+ console.log(` ${TEAL}${BOLD}Step ${step}/${totalSteps}${RESET} ${WHITE}Anthropic API Key${RESET}`);
742
+ console.log(` ${DIM}Get yours at https://console.anthropic.com/keys${RESET}`);
743
+ const anthropicKey = await askSecret(rl, ` ${TEAL}> API key: ${RESET}`);
744
+ if (anthropicKey) {
745
+ config.providers["anthropic"]["apiKey"] = anthropicKey;
746
+ console.log(` ${GREEN}Saved.${RESET}
747
+ `);
748
+ } else {
749
+ console.log(` ${DIM}Skipped. Set ANTHROPIC_API_KEY env var later.${RESET}
750
+ `);
751
+ }
752
+ step++;
753
+ console.log(` ${TEAL}${BOLD}Step ${step}/${totalSteps}${RESET} ${WHITE}OpenAI API Key (optional)${RESET}`);
754
+ console.log(` ${DIM}Get yours at https://platform.openai.com/api-keys${RESET}`);
755
+ const openaiKey = await askSecret(rl, ` ${TEAL}> API key: ${RESET}`);
756
+ if (openaiKey) {
757
+ config.providers["openai"]["apiKey"] = openaiKey;
758
+ console.log(` ${GREEN}Saved.${RESET}
759
+ `);
760
+ } else {
761
+ console.log(` ${DIM}Skipped.${RESET}
762
+ `);
763
+ }
764
+ step++;
765
+ console.log(` ${TEAL}${BOLD}Step ${step}/${totalSteps}${RESET} ${WHITE}Preferred Model${RESET}`);
766
+ for (let i = 0; i < MODELS.length; i++) {
767
+ const m = MODELS[i];
768
+ const marker = i === 0 ? ` ${GREEN}(default)${RESET}` : "";
769
+ console.log(` ${DIM}${i + 1}.${RESET} ${m.label}${marker}`);
770
+ }
771
+ const modelChoice = await ask(rl, ` ${TEAL}> Choice [1]: ${RESET}`);
772
+ const modelIdx = modelChoice ? parseInt(modelChoice, 10) - 1 : 0;
773
+ const selectedModel = MODELS[modelIdx] ?? MODELS[0];
774
+ config.agent.model = selectedModel.id;
775
+ config.agent.provider = selectedModel.provider;
776
+ console.log(` ${GREEN}Selected: ${selectedModel.label}${RESET}
777
+ `);
778
+ }
779
+ step++;
780
+ console.log(` ${TEAL}${BOLD}Step ${step}/${totalSteps}${RESET} ${WHITE}Autonomy Level${RESET}`);
781
+ for (let i = 0; i < AUTONOMY_LEVELS.length; i++) {
782
+ const a = AUTONOMY_LEVELS[i];
783
+ const marker = i === 1 ? ` ${GREEN}(default)${RESET}` : "";
784
+ console.log(` ${DIM}${i + 1}.${RESET} ${a.label}${marker}`);
785
+ }
786
+ const autonomyChoice = await ask(rl, ` ${TEAL}> Choice [2]: ${RESET}`);
787
+ const autonomyIdx = autonomyChoice ? parseInt(autonomyChoice, 10) - 1 : 1;
788
+ const selectedAutonomy = AUTONOMY_LEVELS[autonomyIdx] ?? AUTONOMY_LEVELS[1];
789
+ config.autonomy.level = selectedAutonomy.id;
790
+ console.log(` ${GREEN}Selected: ${selectedAutonomy.id}${RESET}
791
+ `);
792
+ step++;
793
+ console.log(` ${TEAL}${BOLD}Step ${step}/${totalSteps}${RESET} ${WHITE}Additional Features${RESET}`);
794
+ console.log(` ${DIM}Configure providers, channels, services, and system settings.${RESET}`);
795
+ console.log(` ${DIM}Each category can be skipped individually.${RESET}
796
+ `);
797
+ const configureFeatures = await askYesNo(rl, "Configure additional features?");
798
+ if (configureFeatures) {
799
+ await configureProviders(rl, config);
800
+ await configureChannels(rl, config);
801
+ console.log("");
802
+ console.log(sectionHeader("Services"));
803
+ console.log("");
804
+ await configureSearch(rl, config);
805
+ await configureBrowser(rl, config);
806
+ await configureVoice(rl, config);
807
+ await configureVoiceWake(rl, config);
808
+ await configureMesh(rl, config);
809
+ await configureMcp(rl, config);
810
+ await configureCron(rl, config);
811
+ await configureX402(rl, config);
812
+ console.log("");
813
+ console.log(sectionHeader("System"));
814
+ console.log("");
815
+ await configureMemory(rl, config);
816
+ await configureVerification(rl, config);
817
+ await configureGateway(rl, config);
818
+ await configureSecurity(rl, config);
819
+ await configureAllowedCommands(rl, config);
820
+ await configureTunnel(rl, config);
821
+ await configureCanvas(rl, config);
822
+ await configureObservability(rl, config);
823
+ await configureSkills(rl, config);
824
+ }
825
+ step++;
826
+ console.log(` ${TEAL}${BOLD}Step ${step}/${totalSteps}${RESET} ${WHITE}Saving configuration${RESET}`);
827
+ ensureConfigDir();
828
+ saveConfig(config);
829
+ console.log(` ${GREEN}Config written to ${getConfigPath()}${RESET}
830
+ `);
831
+ console.log(` ${BOLD}Running security audit...${RESET}
832
+ `);
833
+ runAudit(config);
834
+ await playFullAnimation();
835
+ console.log(` ${DIM}Run ${TEAL}ch4p${DIM} to start an interactive session.${RESET}`);
836
+ console.log(` ${DIM}Run ${TEAL}ch4p --help${DIM} for all commands.${RESET}`);
837
+ console.log("");
838
+ } finally {
839
+ rl.close();
840
+ }
841
+ }
842
+ export {
843
+ CHANNEL_DEFS,
844
+ detectBinary,
845
+ detectEngines,
846
+ onboard,
847
+ parseMultiSelect,
848
+ parseYesNo
849
+ };