@operor/cli 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (62) hide show
  1. package/README.md +76 -0
  2. package/dist/config-Bn2pbORi.js +34 -0
  3. package/dist/config-Bn2pbORi.js.map +1 -0
  4. package/dist/converse-C_PB7-JH.js +142 -0
  5. package/dist/converse-C_PB7-JH.js.map +1 -0
  6. package/dist/doctor-98gPl743.js +122 -0
  7. package/dist/doctor-98gPl743.js.map +1 -0
  8. package/dist/index.d.ts +1 -0
  9. package/dist/index.js +2268 -0
  10. package/dist/index.js.map +1 -0
  11. package/dist/llm-override-BIQl0V6H.js +445 -0
  12. package/dist/llm-override-BIQl0V6H.js.map +1 -0
  13. package/dist/reset-DT8SBgFS.js +87 -0
  14. package/dist/reset-DT8SBgFS.js.map +1 -0
  15. package/dist/simulate-BKv62GJc.js +144 -0
  16. package/dist/simulate-BKv62GJc.js.map +1 -0
  17. package/dist/status-D6LIZvQa.js +82 -0
  18. package/dist/status-D6LIZvQa.js.map +1 -0
  19. package/dist/test-DYjkxbtK.js +177 -0
  20. package/dist/test-DYjkxbtK.js.map +1 -0
  21. package/dist/test-suite-D8H_5uKs.js +209 -0
  22. package/dist/test-suite-D8H_5uKs.js.map +1 -0
  23. package/dist/utils-BuV4q7f6.js +11 -0
  24. package/dist/utils-BuV4q7f6.js.map +1 -0
  25. package/dist/vibe-Bl_js3Jo.js +395 -0
  26. package/dist/vibe-Bl_js3Jo.js.map +1 -0
  27. package/package.json +43 -0
  28. package/src/commands/analytics.ts +408 -0
  29. package/src/commands/chat.ts +310 -0
  30. package/src/commands/config.ts +34 -0
  31. package/src/commands/converse.ts +182 -0
  32. package/src/commands/doctor.ts +154 -0
  33. package/src/commands/history.ts +60 -0
  34. package/src/commands/init.ts +163 -0
  35. package/src/commands/kb.ts +429 -0
  36. package/src/commands/llm-override.ts +480 -0
  37. package/src/commands/reset.ts +72 -0
  38. package/src/commands/simulate.ts +187 -0
  39. package/src/commands/status.ts +112 -0
  40. package/src/commands/test-suite.ts +247 -0
  41. package/src/commands/test.ts +177 -0
  42. package/src/commands/vibe.ts +478 -0
  43. package/src/config.ts +127 -0
  44. package/src/index.ts +190 -0
  45. package/src/log-timestamps.ts +26 -0
  46. package/src/setup.ts +712 -0
  47. package/src/start.ts +573 -0
  48. package/src/utils.ts +6 -0
  49. package/templates/agents/_defaults/SOUL.md +20 -0
  50. package/templates/agents/_defaults/USER.md +16 -0
  51. package/templates/agents/customer-support/IDENTITY.md +6 -0
  52. package/templates/agents/customer-support/INSTRUCTIONS.md +79 -0
  53. package/templates/agents/customer-support/SOUL.md +26 -0
  54. package/templates/agents/faq-bot/IDENTITY.md +6 -0
  55. package/templates/agents/faq-bot/INSTRUCTIONS.md +53 -0
  56. package/templates/agents/faq-bot/SOUL.md +19 -0
  57. package/templates/agents/sales/IDENTITY.md +6 -0
  58. package/templates/agents/sales/INSTRUCTIONS.md +67 -0
  59. package/templates/agents/sales/SOUL.md +20 -0
  60. package/tsconfig.json +9 -0
  61. package/tsdown.config.ts +13 -0
  62. package/vitest.config.ts +8 -0
@@ -0,0 +1,445 @@
1
+ import { createRequire } from "node:module";
2
+
3
+ //#region \0rolldown/runtime.js
4
+ var __defProp = Object.defineProperty;
5
+ var __exportAll = (all, no_symbols) => {
6
+ let target = {};
7
+ for (var name in all) {
8
+ __defProp(target, name, {
9
+ get: all[name],
10
+ enumerable: true
11
+ });
12
+ }
13
+ if (!no_symbols) {
14
+ __defProp(target, Symbol.toStringTag, { value: "Module" });
15
+ }
16
+ return target;
17
+ };
18
+ var __require = /* @__PURE__ */ createRequire(import.meta.url);
19
+
20
+ //#endregion
21
+ //#region src/commands/llm-override.ts
22
+ var llm_override_exports = /* @__PURE__ */ __exportAll({
23
+ applyLLMOverride: () => applyLLMOverride,
24
+ estimateCost: () => estimateCost,
25
+ retryCrawlWithFetchContent: () => retryCrawlWithFetchContent
26
+ });
27
+ /** Approximate cost per 1M tokens by model name (input / output). */
28
+ const MODEL_COST_PER_M = {
29
+ "gpt-5.2": {
30
+ input: 1.25,
31
+ output: 5
32
+ },
33
+ "gpt-5-mini": {
34
+ input: .25,
35
+ output: 2
36
+ },
37
+ "gpt-5-nano": {
38
+ input: .05,
39
+ output: .4
40
+ },
41
+ "gpt-4o": {
42
+ input: 2.5,
43
+ output: 10
44
+ },
45
+ "gpt-4o-mini": {
46
+ input: .15,
47
+ output: .6
48
+ },
49
+ "gpt-4-turbo": {
50
+ input: 10,
51
+ output: 30
52
+ },
53
+ "gpt-4": {
54
+ input: 30,
55
+ output: 60
56
+ },
57
+ "gpt-3.5-turbo": {
58
+ input: .5,
59
+ output: 1.5
60
+ },
61
+ "claude-sonnet-4-5-20250929": {
62
+ input: 3,
63
+ output: 15
64
+ },
65
+ "claude-opus-4-20250514": {
66
+ input: 15,
67
+ output: 75
68
+ },
69
+ "claude-haiku-3-5-20241022": {
70
+ input: .8,
71
+ output: 4
72
+ },
73
+ "gemini-2.0-flash": {
74
+ input: .1,
75
+ output: .4
76
+ },
77
+ "gemini-1.5-pro": {
78
+ input: 1.25,
79
+ output: 5
80
+ }
81
+ };
82
+ /** Estimate cost in USD from token counts and model name. Returns 0 if model unknown. */
83
+ function estimateCost(promptTokens, completionTokens, model) {
84
+ const rates = MODEL_COST_PER_M[model] || Object.entries(MODEL_COST_PER_M).find(([k]) => model.startsWith(k))?.[1];
85
+ if (!rates) return 0;
86
+ return (promptTokens * rates.input + completionTokens * rates.output) / 1e6;
87
+ }
88
+ /** Extract image media from an MCP tool result, returning a clean text summary for the LLM. */
89
+ function extractMediaFromToolResult(result) {
90
+ if (!result || !result.content || !Array.isArray(result.content)) return { cleanResult: result };
91
+ for (const item of result.content) {
92
+ if (item.type === "image" && item.data) {
93
+ const buffer = Buffer.from(item.data, "base64");
94
+ const mimeType = item.mimeType || "image/png";
95
+ const ext = mimeType.split("/")[1] || "png";
96
+ const cleanContent = result.content.filter((c) => c.type !== "image").concat([{
97
+ type: "text",
98
+ text: `Screenshot captured successfully (${buffer.length} bytes)`
99
+ }]);
100
+ return {
101
+ cleanResult: {
102
+ ...result,
103
+ content: cleanContent
104
+ },
105
+ media: {
106
+ buffer,
107
+ fileName: `screenshot.${ext}`,
108
+ mimeType
109
+ }
110
+ };
111
+ }
112
+ if (item.type === "text" && typeof item.text === "string") try {
113
+ const parsed = JSON.parse(item.text);
114
+ if (parsed.screenshot && typeof parsed.screenshot === "string") {
115
+ if (parsed.screenshot.length > 1e3) {
116
+ const buffer = Buffer.from(parsed.screenshot, "base64");
117
+ const { screenshot: _, ...rest } = parsed;
118
+ rest.screenshot_captured = true;
119
+ rest.screenshot_size = buffer.length;
120
+ const cleanContent = result.content.map((c) => c === item ? {
121
+ type: "text",
122
+ text: JSON.stringify(rest)
123
+ } : c);
124
+ return {
125
+ cleanResult: {
126
+ ...result,
127
+ content: cleanContent
128
+ },
129
+ media: {
130
+ buffer,
131
+ fileName: "screenshot.png",
132
+ mimeType: "image/png"
133
+ }
134
+ };
135
+ }
136
+ if (/^\/.*\.(png|jpe?g|webp|gif)$/i.test(parsed.screenshot)) try {
137
+ const fs = __require("fs");
138
+ if (fs.existsSync(parsed.screenshot)) {
139
+ const buffer = fs.readFileSync(parsed.screenshot);
140
+ const ext = parsed.screenshot.split(".").pop() || "png";
141
+ const mimeType = `image/${ext === "jpg" ? "jpeg" : ext}`;
142
+ const { screenshot: _, ...rest } = parsed;
143
+ rest.screenshot_captured = true;
144
+ rest.screenshot_size = buffer.length;
145
+ const cleanContent = result.content.map((c) => c === item ? {
146
+ type: "text",
147
+ text: JSON.stringify(rest)
148
+ } : c);
149
+ return {
150
+ cleanResult: {
151
+ ...result,
152
+ content: cleanContent
153
+ },
154
+ media: {
155
+ buffer,
156
+ fileName: `screenshot.${ext}`,
157
+ mimeType
158
+ }
159
+ };
160
+ }
161
+ } catch {}
162
+ }
163
+ } catch {}
164
+ }
165
+ return { cleanResult: result };
166
+ }
167
+ /**
168
+ * When a crawl4ai tool returns empty content, retry using fetchContent() from @operor/knowledge.
169
+ * This is the same path /learn-url uses — it calls the /crawl endpoint with full browser rendering,
170
+ * then falls back to Readability. Returns null if not applicable or if retry also fails.
171
+ */
172
+ async function retryCrawlWithFetchContent(toolName, toolArgs, toolResult, deps) {
173
+ if (!/^crawl4ai/i.test(toolName)) return null;
174
+ const url = toolArgs?.url;
175
+ if (!url) return null;
176
+ if (!isCrawlResultEmpty(toolResult)) return null;
177
+ console.log(`[Operor] 🔄 crawl4ai returned empty for ${url}, retrying with fetchContent...`);
178
+ try {
179
+ const { title, content, isMarkdown } = await (deps?.fetchContentFn ?? (await import("@operor/knowledge")).fetchContent)(url, {});
180
+ if (!content || content.trim().length === 0) {
181
+ console.log(`[Operor] ⚠️ fetchContent also returned empty for ${url}`);
182
+ return null;
183
+ }
184
+ const trimmed = content.length > 8e3 ? content.slice(0, 8e3) + "\n\n[Content truncated]" : content;
185
+ console.log(`[Operor] ✅ fetchContent got ${content.length} chars for "${title || url}"`);
186
+ return { content: [{
187
+ type: "text",
188
+ text: JSON.stringify({
189
+ title,
190
+ content: trimmed,
191
+ url,
192
+ source: "fetchContent-fallback"
193
+ })
194
+ }] };
195
+ } catch (err) {
196
+ console.warn(`[Operor] ⚠️ fetchContent fallback failed for ${url}:`, err.message);
197
+ return null;
198
+ }
199
+ }
200
+ function isCrawlResultEmpty(result) {
201
+ if (!result) return true;
202
+ const content = result?.content;
203
+ if (!content || !Array.isArray(content) || content.length === 0) return true;
204
+ return content.every((c) => !c.text || c.text.trim().length === 0);
205
+ }
206
+ /**
207
+ * Shared LLM override for agent.process — used by simulate, converse, and start commands.
208
+ * Replaces the default pattern-matching with real LLM-powered responses + multi-turn tool calling.
209
+ */
210
+ function applyLLMOverride(agent, llm, allTools, options) {
211
+ if (options?.promptSkills?.length) console.log(`[Operor] 📝 Prompt skills for ${agent.config.name}: ${options.promptSkills.map((s) => s.name).join(", ")}`);
212
+ if (allTools.length > 0) console.log(`[Operor] 🔧 MCP tools for ${agent.config.name}: ${allTools.map((t) => t.name).join(", ")}`);
213
+ agent.process = async (context) => {
214
+ const startTime = Date.now();
215
+ if (options?.promptSkills?.length) console.log(`[Operor] 📝 Prompt skills (injected into system prompt): ${options.promptSkills.map((s) => s.name).join(", ")}`);
216
+ if (allTools.length > 0) console.log(`[Operor] 🔧 MCP tools (${allTools.length} available): ${allTools.map((t) => t.name).join(", ")}`);
217
+ try {
218
+ let systemMessage = options?.systemPrompt ?? `You are a ${agent.config.personality} customer support agent. ${agent.config.purpose}.`;
219
+ if (options?.guardrails?.maxResponseLength) {
220
+ const limit = options.guardrails.maxResponseLength;
221
+ systemMessage += `\n\n## Response Length\n\nKeep your responses concise and under ${limit} characters. This is a messaging app — users prefer short, clear answers. Use short paragraphs. Avoid long lists or verbose explanations. If the answer requires more detail, provide the key points and offer to elaborate.`;
222
+ }
223
+ let kbMetadata;
224
+ if (options?.kbRuntime && (options?.useKB !== void 0 ? options.useKB : true)) try {
225
+ const kbStart = Date.now();
226
+ console.log("[Operor] 📚 KB: retrieving context for:", context.currentMessage.text.substring(0, 80));
227
+ const kbResult = await options.kbRuntime.retrieve(context.currentMessage.text);
228
+ console.log(`[Operor] 📚 KB: retrieval completed in ${((Date.now() - kbStart) / 1e3).toFixed(1)}s`);
229
+ const topResult = kbResult.results?.[0];
230
+ kbMetadata = {
231
+ kbTopScore: topResult?.score ?? 0,
232
+ kbIsFaqMatch: !!kbResult.isFaqMatch,
233
+ kbTopChunkContent: topResult?.chunk?.content,
234
+ kbResultCount: kbResult.results?.length ?? 0,
235
+ kbFaqMatchCount: kbResult.faqMatches?.length ?? 0
236
+ };
237
+ const topScore = kbResult.results?.[0]?.score ?? 0;
238
+ if (kbResult.context && topScore >= .5) {
239
+ console.log(`[Operor] 📚 KB: found results, isFaqMatch=${kbResult.isFaqMatch}, topScore=${topScore.toFixed(3)}, faqAnswer=${kbResult.faqAnswer ?? "(undefined)"}`);
240
+ if (kbResult.isFaqMatch) if (kbResult.faqMatches && kbResult.faqMatches.length > 1) systemMessage = "## CRITICAL OVERRIDE — Multiple FAQ Answers\n\nMultiple verified FAQ entries match the user's question.\n\n" + kbResult.faqMatches.map((m, i) => `${i + 1}. **Q:** ${m.faqQuestion}\n **A:** ${m.faqAnswer}`).join("\n") + "\n\nRULES:\n1. You MUST include ALL the above answers in your response.\n2. Do NOT correct, rephrase, or \"fix\" the answers — the business owner wrote them intentionally.\n3. Combine them naturally in a single response.\n4. Do NOT substitute your own knowledge for the required answers.\n5. If Skill Instructions below provide conflicting guidance, follow the Skill Instructions instead.\n\n---\n\n" + systemMessage;
241
+ else if (kbResult.faqAnswer) systemMessage = "## CRITICAL OVERRIDE — FAQ Answer\n\nA verified FAQ entry exactly matches the user's question.\n\n" + (kbResult.faqQuestion ? `**User asked:** ${kbResult.faqQuestion}\n` : "") + `**Required answer (verbatim):** ${kbResult.faqAnswer}\n\nRULES:
242
+ 1. You MUST include the EXACT wording from the required answer above in your response.
243
+ 2. Do NOT correct, rephrase, or "fix" the answer — even if it looks like a typo or unusual value. The business owner wrote this answer intentionally.
244
+ 3. You may add a brief friendly sentence around it, but the core answer must appear word-for-word.
245
+ 4. Do NOT substitute your own knowledge for the required answer.
246
+ 5. If Skill Instructions below provide conflicting guidance, follow the Skill Instructions instead.
247
+
248
+ ---
249
+
250
+ ` + systemMessage;
251
+ else {
252
+ const answerMatch = (kbResult.results?.[0]?.chunk?.content || "").match(/^A:\s*(.+)$/m);
253
+ if (answerMatch) systemMessage = `## CRITICAL OVERRIDE — FAQ Answer
254
+
255
+ **Required answer:** ${answerMatch[1].trim()}\n\nYou MUST respond with the above answer. Do NOT ignore it.
256
+ If Skill Instructions below provide conflicting guidance, follow the Skill Instructions instead.
257
+
258
+ ---
259
+
260
+ ` + systemMessage;
261
+ else systemMessage += "\n\n## Knowledge Base Context (FAQ Match)\n\nThe following FAQ answer was found that directly matches the user's question. You MUST use this answer as the basis of your response. Do not ignore it or make up a different answer.\n\n" + kbResult.context;
262
+ }
263
+ else {
264
+ const topResult = kbResult.results?.[0];
265
+ const faqAns = topResult?.document?.sourceType === "faq" && (topResult.chunk?.metadata?.answer || topResult.document?.metadata?.answer);
266
+ if (faqAns) {
267
+ const faqQ = topResult.chunk?.metadata?.question || topResult.document?.metadata?.question;
268
+ systemMessage = "## FAQ Reference\n\nA relevant FAQ entry was found in the knowledge base.\n\n" + (faqQ ? `**FAQ question:** ${faqQ}\n` : "") + `**FAQ answer:** ${faqAns}\n\nYou SHOULD use this FAQ answer as the basis of your response unless the user is clearly asking about something different.
269
+
270
+ ---
271
+
272
+ ` + systemMessage;
273
+ } else {
274
+ systemMessage += "\n\n## Knowledge Base Context\n\nThe following information was retrieved from the knowledge base. If it contains information relevant to the user's question, you MUST use it to answer. Do NOT say you don't have information if the answer is present below.\n\n" + kbResult.context;
275
+ if (allTools.length > 0) systemMessage += "\n\nNote: You also have tools available. If the KB context does not fully answer the user's question, use your tools to look up the information.";
276
+ }
277
+ }
278
+ } else {
279
+ console.log("[Operor] 📚 KB: no relevant context found");
280
+ systemMessage = "## CRITICAL OVERRIDE — No Knowledge Base Match\n\nThe knowledge base was searched and contains NO relevant information for this query.\n\nRULES:\n1. You MUST NOT make up, guess, or fabricate any information.\n2. You MUST NOT pretend to know the answer.\n" + (allTools.length > 0 ? "3. However, you HAVE tools available. If the user's request can be answered by calling a tool (e.g. looking up an order, searching products), you MUST call the appropriate tool instead of refusing.\n4. Only say you don't have information if no tool can help either.\n5. Keep your response short and honest.\n\n---\n\n" : "3. You MUST politely tell the customer you do not have information on this topic.\n4. You MAY suggest they contact support or rephrase their question.\n5. Keep your response short and honest.\n\n---\n\n") + systemMessage;
281
+ }
282
+ } catch (kbError) {
283
+ console.warn("[Operor] ⚠️ KB retrieval failed:", kbError.message);
284
+ }
285
+ const userText = context.currentMessage.text;
286
+ if (/https?:\/\/[^\s]+/i.test(userText) && allTools.length > 0) {
287
+ const fetchToolNames = allTools.filter((t) => /crawl|fetch|scrape|browse/i.test(t.name)).map((t) => t.name);
288
+ if (fetchToolNames.length > 0) systemMessage += `\n\n## URL Detected\n\nThe user's message contains a URL. You MUST use one of your tools (${fetchToolNames.join(", ")}) to fetch the page content before responding. Do NOT say you cannot access URLs — you have tools that can fetch web pages.`;
289
+ }
290
+ if (options?.promptSkills?.length) systemMessage += "\n\n## Skill Instructions (HIGHEST PRIORITY)\n\nThe following skill instructions are the HIGHEST PRIORITY directives for this agent.\nThey OVERRIDE any conflicting guidance above, including FAQ answers and Knowledge Base context.\nYou MUST follow these instructions even if they contradict KB or FAQ content.\n\n" + options.promptSkills.map((s) => `### ${s.name}\n\n${s.content}`).join("\n\n---\n\n");
291
+ const messages = [
292
+ {
293
+ role: "system",
294
+ content: systemMessage
295
+ },
296
+ ...context.history.map((m) => ({
297
+ role: m.role,
298
+ content: m.content
299
+ })),
300
+ {
301
+ role: "user",
302
+ content: context.currentMessage.text
303
+ }
304
+ ];
305
+ const toolCalls = [];
306
+ let finalText = "";
307
+ let iterations = 0;
308
+ const maxIterations = 5;
309
+ const mediaAttachments = [];
310
+ let totalPromptTokens = 0;
311
+ let totalCompletionTokens = 0;
312
+ while (iterations < maxIterations) {
313
+ iterations++;
314
+ const llmStart = Date.now();
315
+ console.log(`[Operor] 💭 LLM call #${iterations} (${allTools.length} tools available)...`);
316
+ const response = await llm.complete(messages, { tools: allTools.map((t) => ({
317
+ name: t.name,
318
+ description: t.description,
319
+ parameters: t.parameters
320
+ })) });
321
+ console.log(`[Operor] 💭 LLM responded in ${((Date.now() - llmStart) / 1e3).toFixed(1)}s${response.toolCalls?.length ? ` → ${response.toolCalls.length} tool call(s): ${response.toolCalls.map((tc) => tc.name).join(", ")}` : " → text response"}`);
322
+ if (response.usage) {
323
+ totalPromptTokens += response.usage.promptTokens || 0;
324
+ totalCompletionTokens += response.usage.completionTokens || 0;
325
+ }
326
+ if (response.toolCalls && response.toolCalls.length > 0) {
327
+ const executedTools = [];
328
+ for (const tc of response.toolCalls) {
329
+ const tool = allTools.find((t) => t.name === tc.name);
330
+ if (!tool) continue;
331
+ const toolStart = Date.now();
332
+ console.log(`[Operor] 🔧 Executing tool: ${tc.name}${tc.arguments?.url ? ` (${tc.arguments.url})` : ""}...`);
333
+ try {
334
+ const { cleanResult, media } = extractMediaFromToolResult(await tool.execute(tc.arguments));
335
+ if (media) {
336
+ mediaAttachments.push(media);
337
+ console.log(`[Operor] 🖼️ Extracted ${media.mimeType} (${media.buffer.length} bytes) from tool ${tc.name}`);
338
+ }
339
+ const fallbackResult = await retryCrawlWithFetchContent(tc.name, tc.arguments, cleanResult);
340
+ const finalResult = fallbackResult ?? cleanResult;
341
+ const duration = Date.now() - toolStart;
342
+ console.log(`[Operor] 🔧 Tool ${tc.name} completed in ${(duration / 1e3).toFixed(1)}s${fallbackResult ? " (via fetchContent fallback)" : ""}`);
343
+ toolCalls.push({
344
+ id: tc.id,
345
+ name: tc.name,
346
+ params: tc.arguments,
347
+ result: finalResult,
348
+ success: true,
349
+ duration
350
+ });
351
+ executedTools.push({
352
+ name: tc.name,
353
+ result: finalResult,
354
+ success: true
355
+ });
356
+ } catch (err) {
357
+ const duration = Date.now() - toolStart;
358
+ console.log(`[Operor] 🔧 Tool ${tc.name} failed after ${(duration / 1e3).toFixed(1)}s, attempting fallback...`);
359
+ const errorMsg = err.message || err.toString?.() || "Unknown tool error";
360
+ const catchFallback = await retryCrawlWithFetchContent(tc.name, tc.arguments, null);
361
+ if (catchFallback) {
362
+ toolCalls.push({
363
+ id: tc.id,
364
+ name: tc.name,
365
+ params: tc.arguments,
366
+ result: catchFallback,
367
+ success: true,
368
+ duration
369
+ });
370
+ executedTools.push({
371
+ name: tc.name,
372
+ result: catchFallback,
373
+ success: true
374
+ });
375
+ } else {
376
+ toolCalls.push({
377
+ id: tc.id,
378
+ name: tc.name,
379
+ params: tc.arguments,
380
+ result: null,
381
+ success: false,
382
+ error: errorMsg,
383
+ duration
384
+ });
385
+ executedTools.push({
386
+ name: tc.name,
387
+ result: null,
388
+ success: false,
389
+ error: errorMsg
390
+ });
391
+ }
392
+ }
393
+ }
394
+ const toolResultSummary = executedTools.map((tc) => `[Tool ${tc.name}]: ${JSON.stringify(tc.success ? tc.result : { error: tc.error })}`).join("\n");
395
+ messages.push({
396
+ role: "assistant",
397
+ content: `I'll call ${executedTools.map((tc) => tc.name).join(", ")} to help with that.`
398
+ }, {
399
+ role: "user",
400
+ content: `Tool results:\n${toolResultSummary}\n\nPlease use these results to respond to the customer.`
401
+ });
402
+ } else {
403
+ finalText = response.text;
404
+ break;
405
+ }
406
+ }
407
+ const modelName = llm.getModelName?.() || llm.config?.model || "";
408
+ const cost = estimateCost(totalPromptTokens, totalCompletionTokens, modelName);
409
+ const media = mediaAttachments[0];
410
+ return {
411
+ text: finalText,
412
+ toolCalls,
413
+ duration: Date.now() - startTime,
414
+ cost: cost > 0 ? cost : void 0,
415
+ usage: {
416
+ promptTokens: totalPromptTokens,
417
+ completionTokens: totalCompletionTokens
418
+ },
419
+ metadata: kbMetadata,
420
+ ...media && {
421
+ mediaBuffer: media.buffer,
422
+ mediaFileName: media.fileName,
423
+ mediaMimeType: media.mimeType
424
+ }
425
+ };
426
+ } catch (processError) {
427
+ const msg = processError.message || "";
428
+ const code = processError.code || "";
429
+ let errorMessage = "Sorry, something went wrong. Please try again.";
430
+ if (msg.includes("ECONNREFUSED") || msg.includes("ETIMEDOUT") || msg.includes("ENOTFOUND") || msg.includes("fetch failed") || code === "ECONNREFUSED" || code === "ETIMEDOUT" || code === "UND_ERR_CONNECT_TIMEOUT") errorMessage = "Cannot connect to LLM API. Check your network connection and LLM_API_KEY.";
431
+ else if (msg.includes("401") || msg.includes("403") || msg.includes("Unauthorized") || msg.includes("Incorrect API key") || msg.includes("API key")) errorMessage = "LLM API authentication failed. Check your LLM_API_KEY in .env";
432
+ else if (msg.includes("429") || msg.includes("rate limit")) errorMessage = "LLM API rate limit exceeded. Please try again later.";
433
+ else if (msg) errorMessage = `LLM error: ${msg}`;
434
+ return {
435
+ text: errorMessage,
436
+ toolCalls: [],
437
+ duration: Date.now() - startTime
438
+ };
439
+ }
440
+ };
441
+ }
442
+
443
+ //#endregion
444
+ export { llm_override_exports as n, applyLLMOverride as t };
445
+ //# sourceMappingURL=llm-override-BIQl0V6H.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"llm-override-BIQl0V6H.js","names":[],"sources":["../src/commands/llm-override.ts"],"sourcesContent":["/** Approximate cost per 1M tokens by model name (input / output). */\nconst MODEL_COST_PER_M: Record<string, { input: number; output: number }> = {\n 'gpt-5.2': { input: 1.25, output: 5 },\n 'gpt-5-mini': { input: 0.25, output: 2 },\n 'gpt-5-nano': { input: 0.05, output: 0.4 },\n 'gpt-4o': { input: 2.5, output: 10 },\n 'gpt-4o-mini': { input: 0.15, output: 0.6 },\n 'gpt-4-turbo': { input: 10, output: 30 },\n 'gpt-4': { input: 30, output: 60 },\n 'gpt-3.5-turbo': { input: 0.5, output: 1.5 },\n 'claude-sonnet-4-5-20250929': { input: 3, output: 15 },\n 'claude-opus-4-20250514': { input: 15, output: 75 },\n 'claude-haiku-3-5-20241022': { input: 0.8, output: 4 },\n 'gemini-2.0-flash': { input: 0.1, output: 0.4 },\n 'gemini-1.5-pro': { input: 1.25, output: 5 },\n};\n\n/** Estimate cost in USD from token counts and model name. Returns 0 if model unknown. */\nexport function estimateCost(promptTokens: number, completionTokens: number, model: string): number {\n // Try exact match first, then prefix match (e.g. \"gpt-4o-2024-08-06\" → \"gpt-4o\")\n const rates = MODEL_COST_PER_M[model]\n || Object.entries(MODEL_COST_PER_M).find(([k]) => model.startsWith(k))?.[1];\n if (!rates) return 0;\n return (promptTokens * rates.input + completionTokens * rates.output) / 1_000_000;\n}\n\n/** Extract image media from an MCP tool result, returning a clean text summary for the LLM. */\nfunction extractMediaFromToolResult(result: any): { cleanResult: any; media?: { buffer: Buffer; fileName: string; mimeType: string } } {\n if (!result || !result.content || !Array.isArray(result.content)) {\n return { cleanResult: result };\n }\n\n for (const item of result.content) {\n // Standard MCP image content type\n if (item.type === 'image' && item.data) {\n const buffer = Buffer.from(item.data, 'base64');\n const mimeType = item.mimeType || 'image/png';\n const ext = mimeType.split('/')[1] || 'png';\n const cleanContent = result.content\n .filter((c: any) => c.type !== 'image')\n .concat([{ type: 'text', text: `Screenshot captured successfully (${buffer.length} bytes)` }]);\n return {\n cleanResult: { ...result, content: cleanContent },\n media: { buffer, fileName: `screenshot.${ext}`, mimeType },\n };\n }\n\n // Crawl4AI format: screenshot embedded in a JSON text string\n if (item.type === 'text' && typeof item.text === 'string') {\n try {\n const parsed = JSON.parse(item.text);\n if (parsed.screenshot && typeof parsed.screenshot === 'string') {\n // Base64 data (long strings)\n if (parsed.screenshot.length > 1000) {\n const buffer = Buffer.from(parsed.screenshot, 'base64');\n const { screenshot: _, ...rest } = parsed;\n rest.screenshot_captured = true;\n rest.screenshot_size = buffer.length;\n const cleanContent = result.content.map((c: any) =>\n c === item ? { type: 'text', text: JSON.stringify(rest) } : c\n );\n return {\n cleanResult: { ...result, content: cleanContent },\n media: { buffer, fileName: 'screenshot.png', mimeType: 'image/png' },\n };\n }\n\n // File path (short string matching image path pattern)\n if (/^\\/.*\\.(png|jpe?g|webp|gif)$/i.test(parsed.screenshot)) {\n try {\n const fs = require('fs');\n if (fs.existsSync(parsed.screenshot)) {\n const buffer = fs.readFileSync(parsed.screenshot);\n const ext = parsed.screenshot.split('.').pop() || 'png';\n const mimeType = `image/${ext === 'jpg' ? 'jpeg' : ext}`;\n const { screenshot: _, ...rest } = parsed;\n rest.screenshot_captured = true;\n rest.screenshot_size = buffer.length;\n const cleanContent = result.content.map((c: any) =>\n c === item ? { type: 'text', text: JSON.stringify(rest) } : c\n );\n return {\n cleanResult: { ...result, content: cleanContent },\n media: { buffer, fileName: `screenshot.${ext}`, mimeType },\n };\n }\n } catch {\n // File not accessible, fall through\n }\n }\n }\n } catch {\n // Not JSON, skip\n }\n }\n }\n\n return { cleanResult: result };\n}\n\n/**\n * When a crawl4ai tool returns empty content, retry using fetchContent() from @operor/knowledge.\n * This is the same path /learn-url uses — it calls the /crawl endpoint with full browser rendering,\n * then falls back to Readability. Returns null if not applicable or if retry also fails.\n */\nexport async function retryCrawlWithFetchContent(\n toolName: string,\n toolArgs: Record<string, any>,\n toolResult: any,\n deps?: { fetchContentFn?: (url: string, opts: any) => Promise<any> },\n): Promise<any | null> {\n // Only retry for crawl4ai tools\n if (!/^crawl4ai/i.test(toolName)) return null;\n\n // Must have a url argument to retry\n const url = toolArgs?.url;\n if (!url) return null;\n\n // Check if the result is empty\n if (!isCrawlResultEmpty(toolResult)) return null;\n\n console.log(`[Operor] 🔄 crawl4ai returned empty for ${url}, retrying with fetchContent...`);\n\n try {\n const fetchContentFn = deps?.fetchContentFn\n ?? (await import('@operor/knowledge')).fetchContent;\n\n const { title, content, isMarkdown } = await fetchContentFn(url, {});\n\n if (!content || content.trim().length === 0) {\n console.log(`[Operor] ⚠️ fetchContent also returned empty for ${url}`);\n return null;\n }\n\n // Trim to ~8k chars to avoid blowing up the context window\n const trimmed = content.length > 8000\n ? content.slice(0, 8000) + '\\n\\n[Content truncated]'\n : content;\n\n console.log(`[Operor] ✅ fetchContent got ${content.length} chars for \"${title || url}\"`);\n\n return {\n content: [{ type: 'text', text: JSON.stringify({ title, content: trimmed, url, source: 'fetchContent-fallback' }) }],\n };\n } catch (err: any) {\n console.warn(`[Operor] ⚠️ fetchContent fallback failed for ${url}:`, err.message);\n return null;\n }\n}\n\nfunction isCrawlResultEmpty(result: any): boolean {\n if (!result) return true;\n const content = result?.content;\n if (!content || !Array.isArray(content) || content.length === 0) return true;\n // Check if all text entries are empty\n return content.every((c: any) => !c.text || c.text.trim().length === 0);\n}\n\n/**\n * Shared LLM override for agent.process — used by simulate, converse, and start commands.\n * Replaces the default pattern-matching with real LLM-powered responses + multi-turn tool calling.\n */\nexport function applyLLMOverride(\n agent: any,\n llm: any,\n allTools: any[],\n options?: {\n systemPrompt?: string;\n kbRuntime?: any;\n useKB?: boolean;\n guardrails?: { systemRules?: string[]; blockedTopics?: string[]; escalationTriggers?: string[]; maxResponseLength?: number };\n promptSkills?: Array<{name: string, content: string}>;\n },\n): void {\n // Log skills once at setup time\n if (options?.promptSkills?.length) {\n console.log(`[Operor] 📝 Prompt skills for ${agent.config.name}: ${options.promptSkills.map(s => s.name).join(', ')}`);\n }\n if (allTools.length > 0) {\n console.log(`[Operor] 🔧 MCP tools for ${agent.config.name}: ${allTools.map((t: any) => t.name).join(', ')}`);\n }\n\n agent.process = async (context: any) => {\n const startTime = Date.now();\n if (options?.promptSkills?.length) {\n console.log(`[Operor] 📝 Prompt skills (injected into system prompt): ${options.promptSkills.map(s => s.name).join(', ')}`);\n }\n if (allTools.length > 0) {\n console.log(`[Operor] 🔧 MCP tools (${allTools.length} available): ${allTools.map((t: any) => t.name).join(', ')}`);\n }\n try {\n // Build system message: use provided systemPrompt or fall back to personality/purpose\n let systemMessage = options?.systemPrompt\n ?? `You are a ${agent.config.personality} customer support agent. ${agent.config.purpose}.`;\n\n // Inject response length guidance so the LLM naturally keeps responses concise\n if (options?.guardrails?.maxResponseLength) {\n const limit = options.guardrails.maxResponseLength;\n systemMessage += `\\n\\n## Response Length\\n\\nKeep your responses concise and under ${limit} characters. `\n + 'This is a messaging app — users prefer short, clear answers. '\n + 'Use short paragraphs. Avoid long lists or verbose explanations. '\n + 'If the answer requires more detail, provide the key points and offer to elaborate.';\n }\n\n // KB metadata for copilot tracking\n let kbMetadata: Record<string, any> | undefined;\n\n // Inject KB context if available.\n // When useKB is provided, only retrieve when true; otherwise retrieve whenever kbRuntime exists.\n const shouldRetrieveKB = options?.kbRuntime && (options?.useKB !== undefined ? options.useKB : true);\n if (shouldRetrieveKB) {\n try {\n const kbStart = Date.now();\n console.log('[Operor] 📚 KB: retrieving context for:', context.currentMessage.text.substring(0, 80));\n const kbResult = await options!.kbRuntime.retrieve(context.currentMessage.text);\n console.log(`[Operor] 📚 KB: retrieval completed in ${((Date.now() - kbStart) / 1000).toFixed(1)}s`);\n const topResult = kbResult.results?.[0];\n kbMetadata = {\n kbTopScore: topResult?.score ?? 0,\n kbIsFaqMatch: !!kbResult.isFaqMatch,\n kbTopChunkContent: topResult?.chunk?.content,\n kbResultCount: kbResult.results?.length ?? 0,\n kbFaqMatchCount: kbResult.faqMatches?.length ?? 0,\n };\n const topScore = kbResult.results?.[0]?.score ?? 0;\n if (kbResult.context && topScore >= 0.50) {\n console.log(`[Operor] 📚 KB: found results, isFaqMatch=${kbResult.isFaqMatch}, topScore=${topScore.toFixed(3)}, faqAnswer=${kbResult.faqAnswer ?? '(undefined)'}`);\n if (kbResult.isFaqMatch) {\n // When we have multiple FAQ matches from compound query splitting,\n // build a combined override with all answers\n if (kbResult.faqMatches && kbResult.faqMatches.length > 1) {\n const faqList = kbResult.faqMatches\n .map((m: any, i: number) => `${i + 1}. **Q:** ${m.faqQuestion}\\n **A:** ${m.faqAnswer}`)\n .join('\\n');\n const faqOverride = '## CRITICAL OVERRIDE — Multiple FAQ Answers\\n\\n'\n + 'Multiple verified FAQ entries match the user\\'s question.\\n\\n'\n + faqList + '\\n\\n'\n + 'RULES:\\n'\n + '1. You MUST include ALL the above answers in your response.\\n'\n + '2. Do NOT correct, rephrase, or \"fix\" the answers — the business owner wrote them intentionally.\\n'\n + '3. Combine them naturally in a single response.\\n'\n + '4. Do NOT substitute your own knowledge for the required answers.\\n'\n + '5. If Skill Instructions below provide conflicting guidance, follow the Skill Instructions instead.\\n\\n---\\n\\n';\n systemMessage = faqOverride + systemMessage;\n } else if (kbResult.faqAnswer) {\n const faqOverride = '## CRITICAL OVERRIDE — FAQ Answer\\n\\n'\n + 'A verified FAQ entry exactly matches the user\\'s question.\\n\\n'\n + (kbResult.faqQuestion ? `**User asked:** ${kbResult.faqQuestion}\\n` : '')\n + `**Required answer (verbatim):** ${kbResult.faqAnswer}\\n\\n`\n + 'RULES:\\n'\n + '1. You MUST include the EXACT wording from the required answer above in your response.\\n'\n + '2. Do NOT correct, rephrase, or \"fix\" the answer — even if it looks like a typo or unusual value. '\n + 'The business owner wrote this answer intentionally.\\n'\n + '3. You may add a brief friendly sentence around it, but the core answer must appear word-for-word.\\n'\n + '4. Do NOT substitute your own knowledge for the required answer.\\n'\n + '5. If Skill Instructions below provide conflicting guidance, follow the Skill Instructions instead.\\n\\n---\\n\\n';\n systemMessage = faqOverride + systemMessage;\n } else {\n // Fallback: parse answer from the Q/A content format\n const topChunk = kbResult.results?.[0]?.chunk?.content || '';\n const answerMatch = topChunk.match(/^A:\\s*(.+)$/m);\n if (answerMatch) {\n const faqOverride = '## CRITICAL OVERRIDE — FAQ Answer\\n\\n'\n + `**Required answer:** ${answerMatch[1].trim()}\\n\\n`\n + 'You MUST respond with the above answer. Do NOT ignore it.\\n'\n + 'If Skill Instructions below provide conflicting guidance, follow the Skill Instructions instead.\\n\\n---\\n\\n';\n systemMessage = faqOverride + systemMessage;\n } else {\n systemMessage += '\\n\\n## Knowledge Base Context (FAQ Match)\\n\\n'\n + 'The following FAQ answer was found that directly matches the user\\'s question. '\n + 'You MUST use this answer as the basis of your response. Do not ignore it or make up a different answer.\\n\\n'\n + kbResult.context;\n }\n }\n } else {\n // Not a high-confidence FAQ match, but check if the top result is still\n // an FAQ document — if so, surface the answer more prominently than\n // generic KB context so the LLM doesn't ignore it.\n const topResult = kbResult.results?.[0];\n const topIsFaq = topResult?.document?.sourceType === 'faq';\n const faqAns = topIsFaq && (topResult.chunk?.metadata?.answer || topResult.document?.metadata?.answer);\n\n if (faqAns) {\n const faqQ = topResult.chunk?.metadata?.question || topResult.document?.metadata?.question;\n const faqOverride = '## FAQ Reference\\n\\n'\n + 'A relevant FAQ entry was found in the knowledge base.\\n\\n'\n + (faqQ ? `**FAQ question:** ${faqQ}\\n` : '')\n + `**FAQ answer:** ${faqAns}\\n\\n`\n + 'You SHOULD use this FAQ answer as the basis of your response unless the user is clearly asking about something different.\\n\\n---\\n\\n';\n systemMessage = faqOverride + systemMessage;\n } else {\n systemMessage += '\\n\\n## Knowledge Base Context\\n\\n'\n + 'The following information was retrieved from the knowledge base. '\n + 'If it contains information relevant to the user\\'s question, you MUST use it to answer. '\n + 'Do NOT say you don\\'t have information if the answer is present below.\\n\\n'\n + kbResult.context;\n if (allTools.length > 0) {\n systemMessage += '\\n\\nNote: You also have tools available. If the KB context does not fully answer the user\\'s question, use your tools to look up the information.';\n }\n }\n }\n } else {\n console.log('[Operor] 📚 KB: no relevant context found');\n const hasTools = allTools.length > 0;\n const noKbOverride = '## CRITICAL OVERRIDE — No Knowledge Base Match\\n\\n'\n + 'The knowledge base was searched and contains NO relevant information for this query.\\n\\n'\n + 'RULES:\\n'\n + '1. You MUST NOT make up, guess, or fabricate any information.\\n'\n + '2. You MUST NOT pretend to know the answer.\\n'\n + (hasTools\n ? '3. However, you HAVE tools available. If the user\\'s request can be answered by calling a tool (e.g. looking up an order, searching products), you MUST call the appropriate tool instead of refusing.\\n'\n + '4. Only say you don\\'t have information if no tool can help either.\\n'\n + '5. Keep your response short and honest.\\n\\n---\\n\\n'\n : '3. You MUST politely tell the customer you do not have information on this topic.\\n'\n + '4. You MAY suggest they contact support or rephrase their question.\\n'\n + '5. Keep your response short and honest.\\n\\n---\\n\\n');\n systemMessage = noKbOverride + systemMessage;\n }\n } catch (kbError) {\n console.warn('[Operor] ⚠️ KB retrieval failed:', (kbError as Error).message);\n }\n }\n\n // If the user's message contains a URL and we have fetch tools, hint the LLM to use them\n const userText = context.currentMessage.text;\n const urlPattern = /https?:\\/\\/[^\\s]+/i;\n if (urlPattern.test(userText) && allTools.length > 0) {\n const fetchToolNames = allTools\n .filter((t: any) => /crawl|fetch|scrape|browse/i.test(t.name))\n .map((t: any) => t.name);\n if (fetchToolNames.length > 0) {\n systemMessage += `\\n\\n## URL Detected\\n\\nThe user's message contains a URL. You MUST use one of your tools (${fetchToolNames.join(', ')}) to fetch the page content before responding. Do NOT say you cannot access URLs — you have tools that can fetch web pages.`;\n }\n }\n\n // Inject prompt skill instructions LAST — highest priority position in the system prompt.\n // This ensures skill directives override KB FAQ answers and all other context.\n if (options?.promptSkills?.length) {\n systemMessage += '\\n\\n## Skill Instructions (HIGHEST PRIORITY)\\n\\n'\n + 'The following skill instructions are the HIGHEST PRIORITY directives for this agent.\\n'\n + 'They OVERRIDE any conflicting guidance above, including FAQ answers and Knowledge Base context.\\n'\n + 'You MUST follow these instructions even if they contradict KB or FAQ content.\\n\\n'\n + options.promptSkills.map(s => `### ${s.name}\\n\\n${s.content}`).join('\\n\\n---\\n\\n');\n }\n\n const messages: Array<{ role: string; content: string }> = [\n { role: 'system', content: systemMessage },\n ...context.history.map((m: any) => ({\n role: m.role as 'user' | 'assistant',\n content: m.content,\n })),\n { role: 'user', content: context.currentMessage.text },\n ];\n\n const toolCalls: any[] = [];\n let finalText = '';\n let iterations = 0;\n const maxIterations = 5;\n const mediaAttachments: Array<{ buffer: Buffer; fileName: string; mimeType: string }> = [];\n\n // Accumulate token usage across multi-turn tool calling loop\n let totalPromptTokens = 0;\n let totalCompletionTokens = 0;\n\n while (iterations < maxIterations) {\n iterations++;\n\n const llmStart = Date.now();\n console.log(`[Operor] 💭 LLM call #${iterations} (${allTools.length} tools available)...`);\n const response = await llm.complete(messages, {\n tools: allTools.map((t: any) => ({\n name: t.name,\n description: t.description,\n parameters: t.parameters,\n })),\n });\n console.log(`[Operor] 💭 LLM responded in ${((Date.now() - llmStart) / 1000).toFixed(1)}s${response.toolCalls?.length ? ` → ${response.toolCalls.length} tool call(s): ${response.toolCalls.map((tc: any) => tc.name).join(', ')}` : ' → text response'}`);\n\n // Accumulate usage from each LLM call\n if (response.usage) {\n totalPromptTokens += response.usage.promptTokens || 0;\n totalCompletionTokens += response.usage.completionTokens || 0;\n }\n\n if (response.toolCalls && response.toolCalls.length > 0) {\n const executedTools: Array<{ name: string; result: any; success: boolean; error?: string }> = [];\n\n for (const tc of response.toolCalls) {\n const tool = allTools.find((t: any) => t.name === tc.name);\n if (!tool) continue;\n const toolStart = Date.now();\n console.log(`[Operor] 🔧 Executing tool: ${tc.name}${tc.arguments?.url ? ` (${tc.arguments.url})` : ''}...`);\n try {\n const rawResult = await tool.execute(tc.arguments);\n const { cleanResult, media } = extractMediaFromToolResult(rawResult);\n if (media) {\n mediaAttachments.push(media);\n console.log(`[Operor] 🖼️ Extracted ${media.mimeType} (${media.buffer.length} bytes) from tool ${tc.name}`);\n }\n\n // If a crawl4ai tool returned empty content, retry with fetchContent (same path as /learn-url)\n const fallbackResult = await retryCrawlWithFetchContent(tc.name, tc.arguments, cleanResult);\n const finalResult = fallbackResult ?? cleanResult;\n\n const duration = Date.now() - toolStart;\n console.log(`[Operor] 🔧 Tool ${tc.name} completed in ${(duration / 1000).toFixed(1)}s${fallbackResult ? ' (via fetchContent fallback)' : ''}`);\n toolCalls.push({ id: tc.id, name: tc.name, params: tc.arguments, result: finalResult, success: true, duration });\n executedTools.push({ name: tc.name, result: finalResult, success: true });\n } catch (err: any) {\n const duration = Date.now() - toolStart;\n console.log(`[Operor] 🔧 Tool ${tc.name} failed after ${(duration / 1000).toFixed(1)}s, attempting fallback...`);\n const errorMsg = err.message || err.toString?.() || 'Unknown tool error';\n\n // If a crawl4ai tool threw (e.g. empty result treated as error), try fetchContent fallback\n const catchFallback = await retryCrawlWithFetchContent(tc.name, tc.arguments, null);\n if (catchFallback) {\n toolCalls.push({ id: tc.id, name: tc.name, params: tc.arguments, result: catchFallback, success: true, duration });\n executedTools.push({ name: tc.name, result: catchFallback, success: true });\n } else {\n toolCalls.push({ id: tc.id, name: tc.name, params: tc.arguments, result: null, success: false, error: errorMsg, duration });\n executedTools.push({ name: tc.name, result: null, success: false, error: errorMsg });\n }\n }\n }\n\n const toolResultSummary = executedTools.map(tc =>\n `[Tool ${tc.name}]: ${JSON.stringify(tc.success ? tc.result : { error: tc.error })}`\n ).join('\\n');\n\n messages.push(\n { role: 'assistant', content: `I'll call ${executedTools.map(tc => tc.name).join(', ')} to help with that.` },\n { role: 'user', content: `Tool results:\\n${toolResultSummary}\\n\\nPlease use these results to respond to the customer.` }\n );\n } else {\n finalText = response.text;\n break;\n }\n }\n\n const modelName = llm.getModelName?.() || llm.config?.model || '';\n const cost = estimateCost(totalPromptTokens, totalCompletionTokens, modelName);\n\n // Attach the first extracted media (e.g. screenshot) to the response\n const media = mediaAttachments[0];\n\n return {\n text: finalText,\n toolCalls,\n duration: Date.now() - startTime,\n cost: cost > 0 ? cost : undefined,\n usage: { promptTokens: totalPromptTokens, completionTokens: totalCompletionTokens },\n metadata: kbMetadata,\n ...(media && {\n mediaBuffer: media.buffer,\n mediaFileName: media.fileName,\n mediaMimeType: media.mimeType,\n }),\n };\n } catch (processError: any) {\n const msg = processError.message || '';\n const code = processError.code || '';\n let errorMessage = 'Sorry, something went wrong. Please try again.';\n\n if (msg.includes('ECONNREFUSED') || msg.includes('ETIMEDOUT') || msg.includes('ENOTFOUND') ||\n msg.includes('fetch failed') || code === 'ECONNREFUSED' || code === 'ETIMEDOUT' ||\n code === 'UND_ERR_CONNECT_TIMEOUT') {\n errorMessage = 'Cannot connect to LLM API. Check your network connection and LLM_API_KEY.';\n } else if (msg.includes('401') || msg.includes('403') || msg.includes('Unauthorized') ||\n msg.includes('Incorrect API key') || msg.includes('API key')) {\n errorMessage = 'LLM API authentication failed. Check your LLM_API_KEY in .env';\n } else if (msg.includes('429') || msg.includes('rate limit')) {\n errorMessage = 'LLM API rate limit exceeded. Please try again later.';\n } else if (msg) {\n errorMessage = `LLM error: ${msg}`;\n }\n\n return { text: errorMessage, toolCalls: [], duration: Date.now() - startTime };\n }\n };\n}\n"],"mappings":"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"}
@@ -0,0 +1,87 @@
1
+ import { n as readConfig } from "./index.js";
2
+ import { existsSync, rmSync, statSync, unlinkSync } from "node:fs";
3
+ import { resolve } from "node:path";
4
+ import { createInterface } from "node:readline";
5
+
6
+ //#region src/commands/reset.ts
7
+ function fileSize(p) {
8
+ try {
9
+ const s = statSync(p).size;
10
+ if (s < 1024) return `${s} B`;
11
+ if (s < 1024 * 1024) return `${(s / 1024).toFixed(1)} KB`;
12
+ return `${(s / (1024 * 1024)).toFixed(1)} MB`;
13
+ } catch {
14
+ return "?";
15
+ }
16
+ }
17
+ async function runReset(opts) {
18
+ const config = readConfig();
19
+ const cwd = process.cwd();
20
+ const targets = [
21
+ {
22
+ label: "Memory DB",
23
+ path: resolve(cwd, config.MEMORY_DB_PATH || "./operor.db")
24
+ },
25
+ {
26
+ label: "Knowledge Base DB",
27
+ path: resolve(cwd, config.KB_DB_PATH || "./knowledge.db")
28
+ },
29
+ {
30
+ label: "Copilot DB",
31
+ path: resolve(cwd, config.COPILOT_DB_PATH || "./copilot.db")
32
+ },
33
+ {
34
+ label: "Analytics DB",
35
+ path: resolve(cwd, config.ANALYTICS_DB_PATH || "./analytics.db")
36
+ }
37
+ ];
38
+ if (!opts.keepConfig) targets.push({
39
+ label: "MCP skills config",
40
+ path: resolve(cwd, "mcp.json")
41
+ });
42
+ targets.push({
43
+ label: "Baileys auth state",
44
+ path: resolve(cwd, "baileys_auth"),
45
+ isDir: true
46
+ });
47
+ const found = targets.filter((t) => existsSync(t.path));
48
+ if (found.length === 0) {
49
+ console.log("Nothing to reset — no data files found.");
50
+ return;
51
+ }
52
+ console.log("\nThe following will be deleted:\n");
53
+ for (const t of found) {
54
+ const size = t.isDir ? "dir" : fileSize(t.path);
55
+ console.log(` ${t.label.padEnd(22)} ${t.path} (${size})`);
56
+ }
57
+ console.log("");
58
+ if (!opts.yes) {
59
+ const rl = createInterface({
60
+ input: process.stdin,
61
+ output: process.stdout
62
+ });
63
+ const answer = await new Promise((r) => rl.question("Type \"yes\" to confirm: ", r));
64
+ rl.close();
65
+ if (answer.trim().toLowerCase() !== "yes") {
66
+ console.log("Aborted.");
67
+ return;
68
+ }
69
+ }
70
+ let deleted = 0;
71
+ for (const t of found) try {
72
+ if (t.isDir) rmSync(t.path, {
73
+ recursive: true,
74
+ force: true
75
+ });
76
+ else unlinkSync(t.path);
77
+ console.log(` Deleted: ${t.label}`);
78
+ deleted++;
79
+ } catch (err) {
80
+ console.error(` Failed: ${t.label} — ${err.message}`);
81
+ }
82
+ console.log(`\nReset complete. ${deleted}/${found.length} items deleted.`);
83
+ }
84
+
85
+ //#endregion
86
+ export { runReset };
87
+ //# sourceMappingURL=reset-DT8SBgFS.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"reset-DT8SBgFS.js","names":[],"sources":["../src/commands/reset.ts"],"sourcesContent":["import { existsSync, unlinkSync, rmSync, statSync } from 'node:fs';\nimport { resolve } from 'node:path';\nimport { createInterface } from 'node:readline';\nimport { readConfig } from '../config.js';\n\nfunction fileSize(p: string): string {\n try {\n const s = statSync(p).size;\n if (s < 1024) return `${s} B`;\n if (s < 1024 * 1024) return `${(s / 1024).toFixed(1)} KB`;\n return `${(s / (1024 * 1024)).toFixed(1)} MB`;\n } catch { return '?'; }\n}\n\nexport async function runReset(opts: { yes?: boolean; keepConfig?: boolean }) {\n const config = readConfig();\n const cwd = process.cwd();\n\n const targets: { label: string; path: string; isDir?: boolean }[] = [\n { label: 'Memory DB', path: resolve(cwd, config.MEMORY_DB_PATH || './operor.db') },\n { label: 'Knowledge Base DB', path: resolve(cwd, config.KB_DB_PATH || './knowledge.db') },\n { label: 'Copilot DB', path: resolve(cwd, config.COPILOT_DB_PATH || './copilot.db') },\n { label: 'Analytics DB', path: resolve(cwd, config.ANALYTICS_DB_PATH || './analytics.db') },\n ];\n\n if (!opts.keepConfig) {\n targets.push({ label: 'MCP skills config', path: resolve(cwd, 'mcp.json') });\n }\n\n targets.push({ label: 'Baileys auth state', path: resolve(cwd, 'baileys_auth'), isDir: true });\n\n const found = targets.filter(t => existsSync(t.path));\n\n if (found.length === 0) {\n console.log('Nothing to reset — no data files found.');\n return;\n }\n\n console.log('\\nThe following will be deleted:\\n');\n for (const t of found) {\n const size = t.isDir ? 'dir' : fileSize(t.path);\n console.log(` ${t.label.padEnd(22)} ${t.path} (${size})`);\n }\n console.log('');\n\n if (!opts.yes) {\n const rl = createInterface({ input: process.stdin, output: process.stdout });\n const answer = await new Promise<string>(r => rl.question('Type \"yes\" to confirm: ', r));\n rl.close();\n if (answer.trim().toLowerCase() !== 'yes') {\n console.log('Aborted.');\n return;\n }\n }\n\n let deleted = 0;\n for (const t of found) {\n try {\n if (t.isDir) {\n rmSync(t.path, { recursive: true, force: true });\n } else {\n unlinkSync(t.path);\n }\n console.log(` Deleted: ${t.label}`);\n deleted++;\n } catch (err: any) {\n console.error(` Failed: ${t.label} — ${err.message}`);\n }\n }\n\n console.log(`\\nReset complete. ${deleted}/${found.length} items deleted.`);\n}\n"],"mappings":";;;;;;AAKA,SAAS,SAAS,GAAmB;AACnC,KAAI;EACF,MAAM,IAAI,SAAS,EAAE,CAAC;AACtB,MAAI,IAAI,KAAM,QAAO,GAAG,EAAE;AAC1B,MAAI,IAAI,OAAO,KAAM,QAAO,IAAI,IAAI,MAAM,QAAQ,EAAE,CAAC;AACrD,SAAO,IAAI,KAAK,OAAO,OAAO,QAAQ,EAAE,CAAC;SACnC;AAAE,SAAO;;;AAGnB,eAAsB,SAAS,MAA+C;CAC5E,MAAM,SAAS,YAAY;CAC3B,MAAM,MAAM,QAAQ,KAAK;CAEzB,MAAM,UAA8D;EAClE;GAAE,OAAO;GAAa,MAAM,QAAQ,KAAK,OAAO,kBAAkB,cAAc;GAAE;EAClF;GAAE,OAAO;GAAqB,MAAM,QAAQ,KAAK,OAAO,cAAc,iBAAiB;GAAE;EACzF;GAAE,OAAO;GAAc,MAAM,QAAQ,KAAK,OAAO,mBAAmB,eAAe;GAAE;EACrF;GAAE,OAAO;GAAgB,MAAM,QAAQ,KAAK,OAAO,qBAAqB,iBAAiB;GAAE;EAC5F;AAED,KAAI,CAAC,KAAK,WACR,SAAQ,KAAK;EAAE,OAAO;EAAqB,MAAM,QAAQ,KAAK,WAAW;EAAE,CAAC;AAG9E,SAAQ,KAAK;EAAE,OAAO;EAAsB,MAAM,QAAQ,KAAK,eAAe;EAAE,OAAO;EAAM,CAAC;CAE9F,MAAM,QAAQ,QAAQ,QAAO,MAAK,WAAW,EAAE,KAAK,CAAC;AAErD,KAAI,MAAM,WAAW,GAAG;AACtB,UAAQ,IAAI,0CAA0C;AACtD;;AAGF,SAAQ,IAAI,qCAAqC;AACjD,MAAK,MAAM,KAAK,OAAO;EACrB,MAAM,OAAO,EAAE,QAAQ,QAAQ,SAAS,EAAE,KAAK;AAC/C,UAAQ,IAAI,KAAK,EAAE,MAAM,OAAO,GAAG,CAAC,GAAG,EAAE,KAAK,IAAI,KAAK,GAAG;;AAE5D,SAAQ,IAAI,GAAG;AAEf,KAAI,CAAC,KAAK,KAAK;EACb,MAAM,KAAK,gBAAgB;GAAE,OAAO,QAAQ;GAAO,QAAQ,QAAQ;GAAQ,CAAC;EAC5E,MAAM,SAAS,MAAM,IAAI,SAAgB,MAAK,GAAG,SAAS,6BAA2B,EAAE,CAAC;AACxF,KAAG,OAAO;AACV,MAAI,OAAO,MAAM,CAAC,aAAa,KAAK,OAAO;AACzC,WAAQ,IAAI,WAAW;AACvB;;;CAIJ,IAAI,UAAU;AACd,MAAK,MAAM,KAAK,MACd,KAAI;AACF,MAAI,EAAE,MACJ,QAAO,EAAE,MAAM;GAAE,WAAW;GAAM,OAAO;GAAM,CAAC;MAEhD,YAAW,EAAE,KAAK;AAEpB,UAAQ,IAAI,cAAc,EAAE,QAAQ;AACpC;UACO,KAAU;AACjB,UAAQ,MAAM,aAAa,EAAE,MAAM,KAAK,IAAI,UAAU;;AAI1D,SAAQ,IAAI,qBAAqB,QAAQ,GAAG,MAAM,OAAO,iBAAiB"}