@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,767 @@
1
+ import {
2
+ DeepgramSTT,
3
+ ElevenLabsTTS,
4
+ WakeListener,
5
+ WhisperSTT,
6
+ buildSystemPrompt
7
+ } from "./chunk-WL32AHUY.js";
8
+ import {
9
+ DefaultSecurityPolicy,
10
+ NativeEngine,
11
+ ProviderRegistry,
12
+ SubprocessEngine,
13
+ createClaudeCliEngine,
14
+ createCodexCliEngine,
15
+ createMemoryBackend,
16
+ createObserver
17
+ } from "./chunk-BMEBRUYL.js";
18
+ import {
19
+ LoadSkillTool,
20
+ ToolRegistry
21
+ } from "./chunk-PGZ24EFT.js";
22
+ import {
23
+ SkillRegistry
24
+ } from "./chunk-6BURGD2Y.js";
25
+ import {
26
+ AgentLoop,
27
+ ContextManager,
28
+ FormatVerifier,
29
+ LLMVerifier,
30
+ Session,
31
+ createAutoRecallHook,
32
+ createAutoSummarizeHook
33
+ } from "./chunk-U7S375OS.js";
34
+ import {
35
+ generateId
36
+ } from "./chunk-YSCX2QQQ.js";
37
+ import {
38
+ playBriefSplash
39
+ } from "./chunk-CNLYUY2K.js";
40
+ import {
41
+ getLogsDir,
42
+ loadConfig
43
+ } from "./chunk-TB4IZ7F7.js";
44
+ import {
45
+ BLUE,
46
+ BOLD,
47
+ BOX,
48
+ CHAPPIE_GLYPH,
49
+ CHECK,
50
+ CROSS,
51
+ DIM,
52
+ GREEN,
53
+ PROMPT_CHAR,
54
+ RED,
55
+ RESET,
56
+ TEAL,
57
+ TEAL_DIM,
58
+ WARN,
59
+ YELLOW,
60
+ chatHeader,
61
+ separator,
62
+ sessionBanner,
63
+ tokenFooter
64
+ } from "./chunk-NMGPBPNU.js";
65
+
66
+ // src/commands/agent.ts
67
+ import * as readline from "readline";
68
+ function truncateArgs(args) {
69
+ const str = typeof args === "string" ? args : JSON.stringify(args);
70
+ return truncate(str, 80);
71
+ }
72
+ function truncate(str, max) {
73
+ if (str.length <= max) return str;
74
+ return str.slice(0, max - 3) + "...";
75
+ }
76
+ var GUTTER = ` ${TEAL_DIM}${BOX.vertical}${RESET} `;
77
+ function createChatRenderState() {
78
+ return { headerPrinted: false, inTextStream: false, wasThinking: false };
79
+ }
80
+ function resetChatRenderState(state) {
81
+ state.headerPrinted = false;
82
+ state.inTextStream = false;
83
+ state.wasThinking = false;
84
+ }
85
+ function handleAgentEvent(event, state) {
86
+ const ensureHeader = () => {
87
+ if (!state.headerPrinted) {
88
+ console.log(chatHeader(CHAPPIE_GLYPH, "ch4p"));
89
+ state.headerPrinted = true;
90
+ }
91
+ };
92
+ switch (event.type) {
93
+ case "thinking":
94
+ ensureHeader();
95
+ if (!state.wasThinking) {
96
+ process.stdout.write(" ");
97
+ }
98
+ process.stdout.write(`${DIM}${event.delta}${RESET}`);
99
+ state.wasThinking = true;
100
+ break;
101
+ case "text":
102
+ ensureHeader();
103
+ if (state.wasThinking && !state.inTextStream) {
104
+ process.stdout.write("\n\n");
105
+ state.wasThinking = false;
106
+ }
107
+ if (!state.inTextStream) {
108
+ process.stdout.write(" ");
109
+ state.inTextStream = true;
110
+ }
111
+ process.stdout.write(event.delta);
112
+ break;
113
+ case "tool_start":
114
+ ensureHeader();
115
+ if (state.inTextStream) {
116
+ process.stdout.write("\n");
117
+ state.inTextStream = false;
118
+ }
119
+ if (state.wasThinking) {
120
+ process.stdout.write("\n");
121
+ state.wasThinking = false;
122
+ }
123
+ console.log("");
124
+ console.log(`${GUTTER}${BOLD}${event.tool}${RESET}${DIM}(${truncateArgs(event.args)})${RESET}`);
125
+ break;
126
+ case "tool_progress":
127
+ process.stdout.write(`${GUTTER}${DIM}${event.update}${RESET}
128
+ `);
129
+ break;
130
+ case "tool_end":
131
+ if (event.result.success) {
132
+ const output = truncate(event.result.output, 120);
133
+ if (output) {
134
+ console.log(`${GUTTER}${DIM}${output}${RESET}`);
135
+ }
136
+ console.log(`${GUTTER}${CHECK} ${DIM}Done${RESET}`);
137
+ } else {
138
+ console.log(`${GUTTER}${CROSS} ${event.result.error ?? "Unknown error"}`);
139
+ }
140
+ break;
141
+ case "tool_validation_error":
142
+ console.log(`${GUTTER}${WARN} ${YELLOW}${event.tool}: ${event.errors.join(", ")}${RESET}`);
143
+ break;
144
+ case "verification": {
145
+ const v = event.result;
146
+ const outcomeColor = v.outcome === "success" ? GREEN : v.outcome === "partial" ? YELLOW : RED;
147
+ console.log(`
148
+ ${GUTTER}${BLUE}verify${RESET} ${outcomeColor}${v.outcome}${RESET} ${DIM}confidence=${v.confidence.toFixed(2)}${RESET}`);
149
+ if (v.reasoning) {
150
+ console.log(`${GUTTER}${DIM}${v.reasoning}${RESET}`);
151
+ }
152
+ if (v.issues && v.issues.length > 0) {
153
+ for (const issue of v.issues) {
154
+ console.log(`${GUTTER}${WARN} ${issue}`);
155
+ }
156
+ }
157
+ break;
158
+ }
159
+ case "complete":
160
+ if (state.inTextStream) {
161
+ process.stdout.write("\n");
162
+ state.inTextStream = false;
163
+ }
164
+ if (event.usage) {
165
+ console.log(tokenFooter(event.usage));
166
+ }
167
+ resetChatRenderState(state);
168
+ break;
169
+ case "error":
170
+ ensureHeader();
171
+ console.error(`
172
+ ${RED}Error:${RESET} ${event.error.message}`);
173
+ break;
174
+ case "aborted":
175
+ ensureHeader();
176
+ console.log(`
177
+ ${YELLOW}Aborted:${RESET} ${event.reason}`);
178
+ break;
179
+ }
180
+ }
181
+ function createEngine(config) {
182
+ const engineId = config.engines?.default ?? "native";
183
+ const engineConfig = config.engines?.available?.[engineId];
184
+ if (engineId === "claude-cli") {
185
+ try {
186
+ return createClaudeCliEngine({
187
+ command: engineConfig?.command ?? void 0,
188
+ cwd: engineConfig?.cwd ?? void 0,
189
+ timeout: engineConfig?.timeout ?? void 0
190
+ });
191
+ } catch (err) {
192
+ const message = err instanceof Error ? err.message : String(err);
193
+ console.log(` ${YELLOW}\u26A0 Failed to create Claude CLI engine: ${message}${RESET}`);
194
+ console.log(` ${DIM}Falling back to stub engine.${RESET}
195
+ `);
196
+ return createStubEngine(config);
197
+ }
198
+ }
199
+ if (engineId === "codex-cli") {
200
+ try {
201
+ return createCodexCliEngine({
202
+ command: engineConfig?.command ?? void 0,
203
+ cwd: engineConfig?.cwd ?? void 0,
204
+ timeout: engineConfig?.timeout ?? void 0
205
+ });
206
+ } catch (err) {
207
+ const message = err instanceof Error ? err.message : String(err);
208
+ console.log(` ${YELLOW}\u26A0 Failed to create Codex CLI engine: ${message}${RESET}`);
209
+ console.log(` ${DIM}Falling back to stub engine.${RESET}
210
+ `);
211
+ return createStubEngine(config);
212
+ }
213
+ }
214
+ if (engineConfig?.type === "subprocess") {
215
+ try {
216
+ return new SubprocessEngine({
217
+ id: engineId,
218
+ name: engineConfig.name ?? `Subprocess (${engineId})`,
219
+ command: engineConfig.command,
220
+ args: engineConfig.args ?? void 0,
221
+ promptMode: engineConfig.promptMode ?? void 0,
222
+ promptFlag: engineConfig.promptFlag ?? void 0,
223
+ cwd: engineConfig.cwd ?? void 0,
224
+ timeout: engineConfig.timeout ?? void 0
225
+ });
226
+ } catch (err) {
227
+ const message = err instanceof Error ? err.message : String(err);
228
+ console.log(` ${YELLOW}\u26A0 Failed to create subprocess engine "${engineId}": ${message}${RESET}`);
229
+ console.log(` ${DIM}Falling back to stub engine.${RESET}
230
+ `);
231
+ return createStubEngine(config);
232
+ }
233
+ }
234
+ const providerName = config.agent.provider;
235
+ const providerConfig = config.providers?.[providerName];
236
+ const apiKey = providerConfig?.apiKey;
237
+ const needsKey = providerName !== "ollama";
238
+ if (needsKey && (!apiKey || apiKey.trim().length === 0)) {
239
+ console.log(` ${YELLOW}\u26A0 No API key for ${providerName}. Running in stub mode.${RESET}`);
240
+ console.log(` ${DIM}Set ${providerName.toUpperCase()}_API_KEY or run 'ch4p onboard' to configure.${RESET}
241
+ `);
242
+ return createStubEngine(config);
243
+ }
244
+ try {
245
+ const provider = ProviderRegistry.createProvider({
246
+ id: providerName,
247
+ type: providerName,
248
+ ...providerConfig
249
+ });
250
+ return new NativeEngine({
251
+ provider,
252
+ defaultModel: config.agent.model
253
+ });
254
+ } catch (err) {
255
+ const message = err instanceof Error ? err.message : String(err);
256
+ console.log(` ${YELLOW}\u26A0 Failed to create ${providerName} provider: ${message}${RESET}`);
257
+ console.log(` ${DIM}Falling back to stub engine.${RESET}
258
+ `);
259
+ return createStubEngine(config);
260
+ }
261
+ }
262
+ function createStubEngine(config) {
263
+ return {
264
+ id: config.engines?.default ?? "native",
265
+ name: "Native Engine (stub)",
266
+ async startRun(job, opts) {
267
+ const ref = generateId(12);
268
+ async function* events() {
269
+ yield { type: "started" };
270
+ const lastMessage = job.messages[job.messages.length - 1];
271
+ const userText = typeof lastMessage?.content === "string" ? lastMessage.content : "(no message)";
272
+ const response = `I received your message: "${truncate(userText, 100)}"
273
+
274
+ This is a placeholder response from the ch4p stub engine. To use a real LLM, configure your API key for ${config.agent.provider}.
275
+
276
+ Current configuration:
277
+ Provider: ${config.agent.provider}
278
+ Model: ${config.agent.model}
279
+ Autonomy: ${config.autonomy.level}
280
+ `;
281
+ for (const char of response) {
282
+ if (opts?.signal?.aborted) {
283
+ yield { type: "error", error: new Error("Aborted") };
284
+ return;
285
+ }
286
+ yield { type: "text_delta", delta: char };
287
+ }
288
+ yield {
289
+ type: "completed",
290
+ answer: response,
291
+ usage: { inputTokens: Math.ceil(userText.length / 4), outputTokens: Math.ceil(response.length / 4) }
292
+ };
293
+ }
294
+ return {
295
+ ref,
296
+ events: events(),
297
+ async cancel() {
298
+ },
299
+ steer(_message) {
300
+ }
301
+ };
302
+ },
303
+ async resume(_token, _prompt) {
304
+ throw new Error("Resume not supported in stub engine");
305
+ }
306
+ };
307
+ }
308
+ function createSessionConfig(config, skillRegistry, hasMemory, hasSearch) {
309
+ const systemPrompt = buildSystemPrompt({ hasMemory, hasSearch, skillRegistry });
310
+ return {
311
+ sessionId: generateId(16),
312
+ engineId: config.engines?.default ?? "native",
313
+ model: config.agent.model,
314
+ provider: config.agent.provider,
315
+ autonomyLevel: config.autonomy.level,
316
+ cwd: process.cwd(),
317
+ systemPrompt
318
+ };
319
+ }
320
+ function createSkillRegistry(config) {
321
+ if (!config.skills?.enabled) return new SkillRegistry();
322
+ try {
323
+ return SkillRegistry.createFromPaths(config.skills.paths);
324
+ } catch {
325
+ return new SkillRegistry();
326
+ }
327
+ }
328
+ function createToolRegistry(config, skillRegistry) {
329
+ const exclude = [];
330
+ if (config.autonomy.level === "readonly") {
331
+ exclude.push("bash", "file_write", "file_edit", "delegate");
332
+ }
333
+ if (!config.mesh?.enabled) {
334
+ exclude.push("mesh");
335
+ }
336
+ const registry = ToolRegistry.createDefault(
337
+ exclude.length > 0 ? { exclude } : void 0
338
+ );
339
+ if (skillRegistry && skillRegistry.size > 0) {
340
+ registry.register(new LoadSkillTool(skillRegistry));
341
+ }
342
+ return registry;
343
+ }
344
+ function createMemory(config) {
345
+ try {
346
+ const memCfg = {
347
+ backend: config.memory.backend,
348
+ vectorWeight: config.memory.vectorWeight,
349
+ keywordWeight: config.memory.keywordWeight,
350
+ embeddingProvider: config.memory.embeddingProvider,
351
+ openaiApiKey: config.providers?.openai?.apiKey || void 0
352
+ };
353
+ const backend = createMemoryBackend(memCfg);
354
+ return backend;
355
+ } catch (err) {
356
+ const message = err instanceof Error ? err.message : String(err);
357
+ console.log(` ${YELLOW}\u26A0 Memory backend failed to initialise: ${message}${RESET}`);
358
+ console.log(` ${DIM}Memory tools will be unavailable this session.${RESET}
359
+ `);
360
+ return void 0;
361
+ }
362
+ }
363
+ function createConfiguredObserver(config) {
364
+ try {
365
+ const obsCfg = {
366
+ observers: config.observability.observers ?? ["console"],
367
+ logLevel: config.observability.logLevel ?? "info",
368
+ logPath: `${getLogsDir()}/ch4p.jsonl`
369
+ };
370
+ return createObserver(obsCfg);
371
+ } catch {
372
+ return createObserver({ observers: ["console"], logLevel: "info" });
373
+ }
374
+ }
375
+ function createSecurityPolicy(config, cwd) {
376
+ return new DefaultSecurityPolicy({
377
+ workspace: cwd,
378
+ autonomyLevel: config.autonomy.level,
379
+ allowedCommands: config.autonomy.allowedCommands,
380
+ blockedPaths: config.security.blockedPaths
381
+ });
382
+ }
383
+ function createVerifier(config, _engine) {
384
+ const vCfg = config.verification;
385
+ if (!vCfg?.enabled) return void 0;
386
+ const formatOpts = {
387
+ maxToolErrorRatio: vCfg.maxToolErrorRatio ?? 0.5
388
+ };
389
+ if (vCfg.semantic) {
390
+ try {
391
+ const providerName = config.agent.provider;
392
+ const providerConfig = config.providers?.[providerName];
393
+ const provider = ProviderRegistry.createProvider({
394
+ id: `${providerName}-verifier`,
395
+ type: providerName,
396
+ ...providerConfig
397
+ });
398
+ return new LLMVerifier({
399
+ provider,
400
+ model: config.agent.model,
401
+ formatOpts
402
+ });
403
+ } catch {
404
+ return new FormatVerifier(formatOpts);
405
+ }
406
+ }
407
+ return new FormatVerifier(formatOpts);
408
+ }
409
+ function createAgentLoop(config, engine, sessionConfig, memoryBackend, skillRegistry, extras) {
410
+ const session = new Session(sessionConfig, {
411
+ ...extras?.sessionOpts,
412
+ maxErrors: config.agent.maxSessionErrors
413
+ });
414
+ const tools = createToolRegistry(config, skillRegistry);
415
+ const observer = createConfiguredObserver(config);
416
+ const securityPolicy = createSecurityPolicy(config, sessionConfig.cwd ?? process.cwd());
417
+ const toolContextExtensions = {};
418
+ if (config.search?.enabled && config.search.apiKey) {
419
+ toolContextExtensions.searchApiKey = config.search.apiKey;
420
+ toolContextExtensions.searchConfig = {
421
+ maxResults: config.search.maxResults,
422
+ country: config.search.country,
423
+ searchLang: config.search.searchLang
424
+ };
425
+ }
426
+ const verifier = createVerifier(config, engine);
427
+ return new AgentLoop(session, engine, tools.list(), observer, {
428
+ maxIterations: extras?.maxIterations ?? 50,
429
+ maxRetries: 3,
430
+ enableStateSnapshots: true,
431
+ verifier,
432
+ memoryBackend,
433
+ securityPolicy,
434
+ onBeforeFirstRun: extras?.onBeforeFirstRun,
435
+ onAfterComplete: extras?.onAfterComplete,
436
+ toolContextExtensions: Object.keys(toolContextExtensions).length > 0 ? toolContextExtensions : void 0,
437
+ maxToolResults: config.agent.maxToolResults,
438
+ maxToolOutputLen: config.agent.maxToolOutputLen,
439
+ maxStateRecords: config.agent.maxStateRecords
440
+ });
441
+ }
442
+ async function runAgentMessage(config, engine, sessionConfig, message, memoryBackend, skillRegistry, extras, renderState) {
443
+ const loop = createAgentLoop(config, engine, sessionConfig, memoryBackend, skillRegistry, extras);
444
+ const state = renderState ?? createChatRenderState();
445
+ for await (const event of loop.run(message)) {
446
+ handleAgentEvent(event, state);
447
+ }
448
+ }
449
+ function createWakeListener(config) {
450
+ const voiceCfg = config.voice;
451
+ if (!voiceCfg?.enabled || !voiceCfg.wake?.enabled) return null;
452
+ try {
453
+ const stt = voiceCfg.stt.provider === "deepgram" ? new DeepgramSTT({ apiKey: voiceCfg.stt.apiKey ?? "" }) : new WhisperSTT({ apiKey: voiceCfg.stt.apiKey ?? "" });
454
+ const tts = voiceCfg.tts.provider === "elevenlabs" ? new ElevenLabsTTS({ apiKey: voiceCfg.tts.apiKey ?? "", voiceId: voiceCfg.tts.voiceId }) : void 0;
455
+ return new WakeListener({
456
+ stt,
457
+ tts,
458
+ config: {
459
+ enabled: true,
460
+ wakeWord: voiceCfg.wake.wakeWord,
461
+ energyThreshold: voiceCfg.wake.energyThreshold,
462
+ silenceDurationMs: voiceCfg.wake.silenceDurationMs
463
+ }
464
+ });
465
+ } catch (err) {
466
+ const message = err instanceof Error ? err.message : String(err);
467
+ console.log(` ${YELLOW}\u26A0 Voice wake failed to initialise: ${message}${RESET}`);
468
+ console.log(` ${DIM}Voice wake will be unavailable this session.${RESET}
469
+ `);
470
+ return null;
471
+ }
472
+ }
473
+ var REPL_HELP = `
474
+ ${BOLD}Special Commands${RESET}
475
+ ${separator()}
476
+ ${TEAL}/exit${RESET} Exit the session
477
+ ${TEAL}/clear${RESET} Clear conversation history
478
+ ${TEAL}/audit${RESET} Run security audit
479
+ ${TEAL}/memory${RESET} Show memory status
480
+ ${TEAL}/tools${RESET} List available tools
481
+ ${TEAL}/skills${RESET} List available skills
482
+ ${TEAL}/help${RESET} Show this help
483
+ `;
484
+ async function runRepl(config, voiceEnabled = false) {
485
+ const engine = createEngine(config);
486
+ const skillRegistry = createSkillRegistry(config);
487
+ const memoryBackend = createMemory(config);
488
+ const hasMemory = !!memoryBackend;
489
+ const hasSearch = !!(config.search?.enabled && config.search.apiKey);
490
+ const sessionConfig = createSessionConfig(config, skillRegistry, hasMemory, hasSearch);
491
+ const tools = createToolRegistry(config);
492
+ const sharedContext = new ContextManager();
493
+ if (sessionConfig.systemPrompt) {
494
+ sharedContext.setSystemPrompt(sessionConfig.systemPrompt);
495
+ }
496
+ const autoSave = config.memory.autoSave !== false;
497
+ const onBeforeFirstRun = memoryBackend && autoSave ? createAutoRecallHook(memoryBackend) : void 0;
498
+ const onAfterComplete = memoryBackend && autoSave ? createAutoSummarizeHook(memoryBackend) : void 0;
499
+ if (process.stdout.isTTY) {
500
+ await playBriefSplash();
501
+ }
502
+ const bannerInfo = {
503
+ Engine: engine.name,
504
+ Model: config.agent.model,
505
+ Autonomy: config.autonomy.level,
506
+ Tools: `${tools.size} loaded`,
507
+ Memory: memoryBackend ? `${config.memory.backend}${autoSave ? " (auto)" : ""}` : `${DIM}disabled${RESET}`
508
+ };
509
+ if (skillRegistry.size > 0) {
510
+ bannerInfo.Skills = `${skillRegistry.size} loaded`;
511
+ }
512
+ const wakeListener = voiceEnabled ? createWakeListener(config) : null;
513
+ if (wakeListener) {
514
+ const wakeCfg = config.voice?.wake;
515
+ bannerInfo.Voice = `${GREEN}wake${RESET}${wakeCfg?.wakeWord ? ` "${wakeCfg.wakeWord}"` : ""}`;
516
+ }
517
+ console.log("\n" + sessionBanner(bannerInfo));
518
+ console.log(` ${DIM}Type ${TEAL}/help${DIM} for commands, ${TEAL}/exit${DIM} to quit.${RESET}
519
+ `);
520
+ if (engine.name === "Native Engine (stub)") {
521
+ console.log(
522
+ ` ${YELLOW}${BOLD}\u26A0 Stub engine active${RESET}
523
+ ${YELLOW}No API key is configured for "${config.agent.provider}".${RESET}
524
+ ${DIM}Responses are placeholders only. Run ${TEAL}ch4p onboard${DIM} to set up a real provider.${RESET}
525
+ `
526
+ );
527
+ }
528
+ const rl = readline.createInterface({
529
+ input: process.stdin,
530
+ output: process.stdout,
531
+ prompt: `${TEAL}${BOLD}${PROMPT_CHAR} ${RESET}`,
532
+ historySize: 200
533
+ });
534
+ let running = true;
535
+ rl.on("SIGINT", () => {
536
+ console.log(`
537
+ ${DIM} Use /exit to quit.${RESET}`);
538
+ rl.prompt();
539
+ });
540
+ rl.on("close", () => {
541
+ if (running) {
542
+ running = false;
543
+ wakeListener?.stop();
544
+ void memoryBackend?.close();
545
+ console.log(`
546
+ ${DIM} Goodbye!${RESET}
547
+ `);
548
+ }
549
+ });
550
+ if (wakeListener) {
551
+ let voiceProcessing = false;
552
+ wakeListener.on("listening", () => {
553
+ console.log(`
554
+ ${TEAL}\u{1F399} Voice wake active${RESET}${config.voice?.wake?.wakeWord ? ` \u2014 say "${config.voice.wake.wakeWord}" to start` : ""}${RESET}`);
555
+ rl.prompt();
556
+ });
557
+ wakeListener.on("wake", (event) => {
558
+ if (voiceProcessing || !running) return;
559
+ voiceProcessing = true;
560
+ console.log(chatHeader(PROMPT_CHAR, "You") + ` ${DIM}(voice)${RESET}`);
561
+ console.log(` ${event.text}`);
562
+ const renderState = createChatRenderState();
563
+ runAgentMessage(config, engine, sessionConfig, event.text, memoryBackend, skillRegistry, {
564
+ sessionOpts: { sharedContext },
565
+ onBeforeFirstRun,
566
+ onAfterComplete
567
+ }, renderState).then(async () => {
568
+ const messages = sharedContext.getMessages();
569
+ const lastAssistant = messages.filter((m) => m.role === "assistant").pop();
570
+ if (lastAssistant && typeof lastAssistant.content === "string") {
571
+ await wakeListener.speak(lastAssistant.content);
572
+ }
573
+ }).catch((err) => {
574
+ const message = err instanceof Error ? err.message : String(err);
575
+ console.error(`
576
+ ${RED}Error:${RESET} ${message}`);
577
+ }).finally(() => {
578
+ voiceProcessing = false;
579
+ console.log("");
580
+ rl.prompt();
581
+ });
582
+ });
583
+ wakeListener.on("error", (err) => {
584
+ console.error(` ${YELLOW}Voice error:${RESET} ${err.message}`);
585
+ });
586
+ try {
587
+ wakeListener.start();
588
+ } catch (err) {
589
+ const message = err instanceof Error ? err.message : String(err);
590
+ console.log(` ${YELLOW}\u26A0 Voice wake start failed: ${message}${RESET}`);
591
+ console.log(` ${DIM}Continuing without voice wake.${RESET}
592
+ `);
593
+ }
594
+ }
595
+ rl.prompt();
596
+ for await (const line of rl) {
597
+ const input = line.trim();
598
+ if (!input) {
599
+ rl.prompt();
600
+ continue;
601
+ }
602
+ if (input.startsWith("/")) {
603
+ const cmd = input.toLowerCase().split(/\s+/)[0];
604
+ switch (cmd) {
605
+ case "/exit":
606
+ case "/quit":
607
+ case "/q":
608
+ running = false;
609
+ wakeListener?.stop();
610
+ console.log(`
611
+ ${DIM} Goodbye!${RESET}
612
+ `);
613
+ rl.close();
614
+ await memoryBackend?.close();
615
+ return;
616
+ case "/clear":
617
+ sharedContext.clear();
618
+ if (sessionConfig.systemPrompt) {
619
+ sharedContext.setSystemPrompt(sessionConfig.systemPrompt);
620
+ }
621
+ console.log(` ${GREEN}Conversation cleared.${RESET}
622
+ `);
623
+ rl.prompt();
624
+ continue;
625
+ case "/audit": {
626
+ const { runAudit } = await import("./audit-UIGPH3FK.js");
627
+ console.log("");
628
+ runAudit(config);
629
+ console.log("");
630
+ rl.prompt();
631
+ continue;
632
+ }
633
+ case "/memory":
634
+ console.log(`
635
+ ${BOLD}Memory Status${RESET}`);
636
+ console.log(` ${DIM}Backend: ${config.memory.backend}${RESET}`);
637
+ console.log(` ${DIM}Active: ${memoryBackend ? `${GREEN}yes${RESET}` : `${YELLOW}no (failed to initialise)${RESET}`}`);
638
+ console.log(` ${DIM}Auto-save: ${config.memory.autoSave}${RESET}`);
639
+ console.log(` ${DIM}Vector weight: ${config.memory.vectorWeight ?? 0.7}${RESET}`);
640
+ console.log(` ${DIM}Keyword weight: ${config.memory.keywordWeight ?? 0.3}${RESET}
641
+ `);
642
+ rl.prompt();
643
+ continue;
644
+ case "/tools":
645
+ console.log(`
646
+ ${BOLD}Available Tools${RESET} ${DIM}(${tools.size})${RESET}`);
647
+ for (const tool of tools.list()) {
648
+ console.log(` ${TEAL}${tool.name}${RESET} ${DIM}[${tool.weight}]${RESET} ${tool.description}`);
649
+ }
650
+ console.log("");
651
+ rl.prompt();
652
+ continue;
653
+ case "/skills":
654
+ if (skillRegistry.size === 0) {
655
+ console.log(`
656
+ ${DIM}No skills loaded.${RESET}
657
+ `);
658
+ } else {
659
+ console.log(`
660
+ ${BOLD}Available Skills${RESET} ${DIM}(${skillRegistry.size})${RESET}`);
661
+ for (const skill of skillRegistry.list()) {
662
+ console.log(` ${TEAL}${skill.manifest.name}${RESET} ${DIM}(${skill.source})${RESET} ${skill.manifest.description}`);
663
+ }
664
+ console.log("");
665
+ }
666
+ rl.prompt();
667
+ continue;
668
+ case "/help":
669
+ console.log(REPL_HELP);
670
+ rl.prompt();
671
+ continue;
672
+ default:
673
+ console.log(` ${YELLOW}Unknown command: ${cmd}${RESET}`);
674
+ console.log(` ${DIM}Type /help for available commands.${RESET}
675
+ `);
676
+ rl.prompt();
677
+ continue;
678
+ }
679
+ }
680
+ console.log(chatHeader(PROMPT_CHAR, "You"));
681
+ console.log(` ${input}`);
682
+ const renderState = createChatRenderState();
683
+ try {
684
+ await runAgentMessage(config, engine, sessionConfig, input, memoryBackend, skillRegistry, {
685
+ sessionOpts: { sharedContext },
686
+ onBeforeFirstRun,
687
+ onAfterComplete
688
+ }, renderState);
689
+ } catch (err) {
690
+ const message = err instanceof Error ? err.message : String(err);
691
+ console.error(`
692
+ ${RED}Error:${RESET} ${message}`);
693
+ }
694
+ console.log("");
695
+ rl.prompt();
696
+ }
697
+ }
698
+ async function runSingleMessage(config, message) {
699
+ const engine = createEngine(config);
700
+ const skillRegistry = createSkillRegistry(config);
701
+ const memoryBackend = createMemory(config);
702
+ const hasMemory = !!memoryBackend;
703
+ const hasSearch = !!(config.search?.enabled && config.search.apiKey);
704
+ const sessionConfig = createSessionConfig(config, skillRegistry, hasMemory, hasSearch);
705
+ const autoSave = config.memory.autoSave !== false;
706
+ const onBeforeFirstRun = memoryBackend && autoSave ? createAutoRecallHook(memoryBackend) : void 0;
707
+ const onAfterComplete = memoryBackend && autoSave ? createAutoSummarizeHook(memoryBackend) : void 0;
708
+ try {
709
+ console.log(chatHeader(PROMPT_CHAR, "You"));
710
+ console.log(` ${message}`);
711
+ await runAgentMessage(config, engine, sessionConfig, message, memoryBackend, skillRegistry, {
712
+ onBeforeFirstRun,
713
+ onAfterComplete
714
+ });
715
+ console.log("");
716
+ } catch (err) {
717
+ const errMessage = err instanceof Error ? err.message : String(err);
718
+ console.error(`
719
+ ${RED}Error:${RESET} ${errMessage}`);
720
+ process.exitCode = 1;
721
+ } finally {
722
+ await memoryBackend?.close();
723
+ }
724
+ }
725
+ async function agent(args) {
726
+ let config;
727
+ try {
728
+ config = loadConfig();
729
+ } catch (err) {
730
+ const message = err instanceof Error ? err.message : String(err);
731
+ console.error(`
732
+ ${RED}Failed to load config:${RESET} ${message}`);
733
+ console.error(` ${DIM}Run ${TEAL}ch4p onboard${DIM} to set up ch4p.${RESET}
734
+ `);
735
+ process.exitCode = 1;
736
+ return;
737
+ }
738
+ let singleMessage = null;
739
+ let voiceEnabled = false;
740
+ for (let i = 0; i < args.length; i++) {
741
+ const arg = args[i];
742
+ if (arg === "-m" || arg === "--message") {
743
+ singleMessage = args[i + 1] ?? "";
744
+ break;
745
+ }
746
+ if (arg.startsWith("-m") && arg.length > 2) {
747
+ singleMessage = arg.slice(2);
748
+ break;
749
+ }
750
+ if (arg === "--voice") {
751
+ voiceEnabled = true;
752
+ }
753
+ }
754
+ if (singleMessage !== null) {
755
+ if (!singleMessage) {
756
+ console.error(` ${RED}Error:${RESET} -m flag requires a message argument.`);
757
+ process.exitCode = 1;
758
+ return;
759
+ }
760
+ await runSingleMessage(config, singleMessage);
761
+ } else {
762
+ await runRepl(config, voiceEnabled);
763
+ }
764
+ }
765
+ export {
766
+ agent
767
+ };