@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
package/README.md ADDED
@@ -0,0 +1,76 @@
1
+ # @operor/cli
2
+
3
+ CLI tool for Operor — setup wizard, diagnostics, testing, and knowledge base management.
4
+
5
+ ## Install
6
+
7
+ ```bash
8
+ pnpm add -g @operor/cli
9
+ ```
10
+
11
+ ## Commands
12
+
13
+ ### Setup & Runtime
14
+
15
+ ```bash
16
+ operor setup # Interactive setup wizard
17
+ operor start # Start with existing .env config
18
+ operor status # Show configuration status
19
+ operor doctor # Validate config and test connections
20
+ operor test # Run demo test scenarios
21
+ ```
22
+
23
+ ### Configuration
24
+
25
+ ```bash
26
+ operor config show # Show current config
27
+ operor config set KEY VALUE # Set a config value
28
+ operor config unset KEY # Remove a config value
29
+ ```
30
+
31
+ ### Knowledge Base
32
+
33
+ Requires `KB_ENABLED=true` in `.env` (set via `operor setup` or `operor config set`).
34
+
35
+ ```bash
36
+ # Ingest content
37
+ operor kb add-url <url> # Ingest a web page
38
+ operor kb add-file <path> # Ingest a document (PDF, TXT, MD, etc.)
39
+ operor kb add-faq <question> <answer> # Add a manual FAQ entry
40
+
41
+ # Browse & search
42
+ operor kb list # List all documents
43
+ operor kb search <query> # Search with relevance scores
44
+ operor kb search <query> -n 10 # Search with custom result limit
45
+
46
+ # Manage
47
+ operor kb delete <id> # Delete a document by ID
48
+ operor kb stats # Show KB statistics (docs, chunks, DB size)
49
+ ```
50
+
51
+ #### Examples
52
+
53
+ ```bash
54
+ # Add your docs
55
+ operor kb add-url https://docs.example.com/faq
56
+ operor kb add-file ./product-manual.pdf
57
+ operor kb add-faq "What are your hours?" "We're open Mon-Fri 9am-5pm EST."
58
+
59
+ # Test retrieval
60
+ operor kb search "return policy"
61
+
62
+ # Check stats
63
+ operor kb stats
64
+ ```
65
+
66
+ #### KB Configuration
67
+
68
+ Set during `operor setup` or manually via `operor config set`:
69
+
70
+ | Variable | Description | Default |
71
+ |---|---|---|
72
+ | `KB_ENABLED` | Enable knowledge base | `false` |
73
+ | `KB_DB_PATH` | SQLite database path | `./knowledge.db` |
74
+ | `KB_EMBEDDING_PROVIDER` | `openai`, `google`, `mistral`, `cohere`, or `ollama` | `openai` |
75
+ | `KB_EMBEDDING_MODEL` | Embedding model name | Provider default |
76
+ | `KB_EMBEDDING_API_KEY` | API key for embeddings | — |
@@ -0,0 +1,34 @@
1
+ import { n as readConfig, r as writeConfig, t as configExists } from "./index.js";
2
+
3
+ //#region src/commands/config.ts
4
+ function showConfig() {
5
+ if (!configExists()) {
6
+ console.error("No configuration found. Run \"operor setup\" first.");
7
+ process.exit(1);
8
+ }
9
+ const config = readConfig();
10
+ for (const [key, value] of Object.entries(config)) {
11
+ const display = key.includes("KEY") || key.includes("TOKEN") || key.includes("SECRET") ? maskValue(value || "") : value;
12
+ console.log(` ${key}=${display}`);
13
+ }
14
+ }
15
+ function setConfigValue(key, value) {
16
+ const config = readConfig();
17
+ config[key] = value;
18
+ writeConfig(config);
19
+ console.log(` Set ${key}=${key.includes("KEY") ? maskValue(value) : value}`);
20
+ }
21
+ function unsetConfigValue(key) {
22
+ const config = readConfig();
23
+ delete config[key];
24
+ writeConfig(config);
25
+ console.log(` Removed ${key}`);
26
+ }
27
+ function maskValue(val) {
28
+ if (val.length <= 8) return "••••••••";
29
+ return val.slice(0, 4) + "••••" + val.slice(-4);
30
+ }
31
+
32
+ //#endregion
33
+ export { setConfigValue, showConfig, unsetConfigValue };
34
+ //# sourceMappingURL=config-Bn2pbORi.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config-Bn2pbORi.js","names":[],"sources":["../src/commands/config.ts"],"sourcesContent":["import { readConfig, writeConfig, configExists } from '../config.js';\n\nexport function showConfig(): void {\n if (!configExists()) {\n console.error('No configuration found. Run \"operor setup\" first.');\n process.exit(1);\n }\n const config = readConfig();\n for (const [key, value] of Object.entries(config)) {\n const display = key.includes('KEY') || key.includes('TOKEN') || key.includes('SECRET')\n ? maskValue(value || '')\n : value;\n console.log(` ${key}=${display}`);\n }\n}\n\nexport function setConfigValue(key: string, value: string): void {\n const config = readConfig();\n config[key] = value;\n writeConfig(config);\n console.log(` Set ${key}=${key.includes('KEY') ? maskValue(value) : value}`);\n}\n\nexport function unsetConfigValue(key: string): void {\n const config = readConfig();\n delete config[key];\n writeConfig(config);\n console.log(` Removed ${key}`);\n}\n\nfunction maskValue(val: string): string {\n if (val.length <= 8) return '••••••••';\n return val.slice(0, 4) + '••••' + val.slice(-4);\n}\n"],"mappings":";;;AAEA,SAAgB,aAAmB;AACjC,KAAI,CAAC,cAAc,EAAE;AACnB,UAAQ,MAAM,sDAAoD;AAClE,UAAQ,KAAK,EAAE;;CAEjB,MAAM,SAAS,YAAY;AAC3B,MAAK,MAAM,CAAC,KAAK,UAAU,OAAO,QAAQ,OAAO,EAAE;EACjD,MAAM,UAAU,IAAI,SAAS,MAAM,IAAI,IAAI,SAAS,QAAQ,IAAI,IAAI,SAAS,SAAS,GAClF,UAAU,SAAS,GAAG,GACtB;AACJ,UAAQ,IAAI,KAAK,IAAI,GAAG,UAAU;;;AAItC,SAAgB,eAAe,KAAa,OAAqB;CAC/D,MAAM,SAAS,YAAY;AAC3B,QAAO,OAAO;AACd,aAAY,OAAO;AACnB,SAAQ,IAAI,SAAS,IAAI,GAAG,IAAI,SAAS,MAAM,GAAG,UAAU,MAAM,GAAG,QAAQ;;AAG/E,SAAgB,iBAAiB,KAAmB;CAClD,MAAM,SAAS,YAAY;AAC3B,QAAO,OAAO;AACd,aAAY,OAAO;AACnB,SAAQ,IAAI,aAAa,MAAM;;AAGjC,SAAS,UAAU,KAAqB;AACtC,KAAI,IAAI,UAAU,EAAG,QAAO;AAC5B,QAAO,IAAI,MAAM,GAAG,EAAE,GAAG,SAAS,IAAI,MAAM,GAAG"}
@@ -0,0 +1,142 @@
1
+ import { n as readConfig } from "./index.js";
2
+
3
+ //#region src/commands/converse.ts
4
+ async function runConverse(options) {
5
+ const config = readConfig();
6
+ const { Operor } = await import("@operor/core");
7
+ const { MockProvider } = await import("@operor/provider-mock");
8
+ const { MockShopifySkill, CustomerSimulator, ConversationEvaluator, ConversationRunner, ECOMMERCE_SCENARIOS, SkillTestHarness } = await import("@operor/testing");
9
+ let llm;
10
+ if (config.LLM_PROVIDER && config.LLM_API_KEY) {
11
+ const { AIProvider } = await import("@operor/llm");
12
+ llm = new AIProvider({
13
+ provider: config.LLM_PROVIDER,
14
+ apiKey: config.LLM_API_KEY,
15
+ model: config.LLM_MODEL
16
+ });
17
+ }
18
+ let scenarios = [...ECOMMERCE_SCENARIOS];
19
+ if (options.file) {
20
+ const fs = await import("fs");
21
+ try {
22
+ const raw = fs.readFileSync(options.file, "utf-8");
23
+ scenarios = JSON.parse(raw);
24
+ } catch (err) {
25
+ console.error(`Failed to load scenarios from ${options.file}:`, err);
26
+ process.exit(1);
27
+ }
28
+ }
29
+ if (options.scenario) {
30
+ const match = scenarios.filter((s) => s.name.toLowerCase().includes(options.scenario.toLowerCase()) || s.id.toLowerCase().includes(options.scenario.toLowerCase()));
31
+ if (match.length === 0) {
32
+ console.error(`No scenario matching "${options.scenario}". Available:`);
33
+ scenarios.forEach((s) => console.error(` - ${s.id}: ${s.name}`));
34
+ process.exit(1);
35
+ }
36
+ scenarios = match;
37
+ }
38
+ if (options.turns) scenarios = scenarios.map((s) => ({
39
+ ...s,
40
+ maxTurns: options.turns
41
+ }));
42
+ if (options.persona) scenarios = scenarios.map((s) => ({
43
+ ...s,
44
+ persona: options.persona
45
+ }));
46
+ const os = new Operor({
47
+ debug: false,
48
+ batchWindowMs: 0
49
+ });
50
+ const provider = new MockProvider();
51
+ const shopify = new MockShopifySkill();
52
+ let skill = shopify;
53
+ if (options.real || options.dryRun) {
54
+ skill = new SkillTestHarness(shopify, {
55
+ allowWrites: options.allowWrites ?? false,
56
+ dryRun: options.dryRun ?? false
57
+ });
58
+ await skill.authenticate();
59
+ }
60
+ await os.addProvider(provider);
61
+ await os.addSkill(skill);
62
+ const allTools = [
63
+ shopify.tools.get_order,
64
+ shopify.tools.create_discount,
65
+ shopify.tools.search_products
66
+ ];
67
+ const agent = os.createAgent({
68
+ name: "Test Agent",
69
+ purpose: "Handle customer support conversations",
70
+ personality: "empathetic and solution-focused",
71
+ triggers: ["order_tracking", "general"],
72
+ tools: allTools,
73
+ rules: [{
74
+ name: "Auto-compensation",
75
+ condition: async (_ctx, toolResults) => {
76
+ const order = toolResults.find((t) => t.name === "get_order");
77
+ return order?.success && order.result?.isDelayed && order.result?.delayDays >= 2;
78
+ },
79
+ action: async () => {
80
+ return {
81
+ type: "discount_created",
82
+ code: (await shopify.tools.create_discount.execute({
83
+ percent: 10,
84
+ validDays: 30
85
+ })).code,
86
+ percent: 10,
87
+ validDays: 30
88
+ };
89
+ }
90
+ }]
91
+ });
92
+ let kbRuntime;
93
+ if (config.KB_ENABLED === "true") try {
94
+ const { SQLiteKnowledgeStore, EmbeddingService, RetrievalPipeline } = await import("@operor/knowledge");
95
+ const embedder = new EmbeddingService({
96
+ provider: config.KB_EMBEDDING_PROVIDER || config.LLM_PROVIDER,
97
+ apiKey: config.KB_EMBEDDING_API_KEY || config.LLM_API_KEY || "",
98
+ model: config.KB_EMBEDDING_MODEL
99
+ });
100
+ const kbStore = new SQLiteKnowledgeStore(config.KB_DB_PATH || "./knowledge.db", embedder.dimensions);
101
+ await kbStore.initialize();
102
+ const retrieval = new RetrievalPipeline(kbStore, embedder);
103
+ kbRuntime = { retrieve: (q) => retrieval.retrieve(q) };
104
+ console.log("[Operor] 📚 Knowledge Base enabled for conversations");
105
+ } catch (kbError) {
106
+ console.warn("[Operor] ⚠️ Failed to initialize Knowledge Base:", kbError.message);
107
+ }
108
+ if (llm) {
109
+ const { applyLLMOverride } = await import("./llm-override-BIQl0V6H.js").then((n) => n.n);
110
+ applyLLMOverride(agent, llm, allTools, { kbRuntime });
111
+ }
112
+ await os.start();
113
+ const runner = new ConversationRunner({
114
+ agentOS: os,
115
+ customerSimulator: new CustomerSimulator({ llmProvider: llm }),
116
+ conversationEvaluator: new ConversationEvaluator({ llmProvider: llm }),
117
+ verbose: options.verbose ?? false
118
+ });
119
+ if (!options.json) console.log(`\n Running ${scenarios.length} conversation scenario(s)...\n`);
120
+ const results = await runner.runScenarios(scenarios);
121
+ await os.stop();
122
+ if (options.json) console.log(JSON.stringify(results, null, 2));
123
+ else {
124
+ let passed = 0;
125
+ let failed = 0;
126
+ for (const r of results) {
127
+ const icon = r.passed ? "✓" : "✗";
128
+ const status = r.passed ? "PASS" : "FAIL";
129
+ console.log(` ${icon} ${r.scenario.name} — ${status} (${r.turns.length} turns, ${r.duration}ms)`);
130
+ if (!r.passed && r.evaluation.feedback) console.log(` ${r.evaluation.feedback}`);
131
+ if (r.passed) passed++;
132
+ else failed++;
133
+ }
134
+ console.log(`\n Results: ${passed} passed, ${failed} failed out of ${results.length}`);
135
+ console.log(` Status: ${failed === 0 ? "✓ ALL PASSED" : "✗ SOME FAILED"}\n`);
136
+ }
137
+ process.exit(results.every((r) => r.passed) ? 0 : 1);
138
+ }
139
+
140
+ //#endregion
141
+ export { runConverse };
142
+ //# sourceMappingURL=converse-C_PB7-JH.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"converse-C_PB7-JH.js","names":[],"sources":["../src/commands/converse.ts"],"sourcesContent":["import { readConfig } from '../config.js';\nimport { formatTimestamp } from '../utils.js';\n\nexport async function runConverse(options: {\n scenario?: string;\n file?: string;\n turns?: number;\n persona?: string;\n verbose?: boolean;\n json?: boolean;\n real?: boolean;\n allowWrites?: boolean;\n dryRun?: boolean;\n}): Promise<void> {\n const config = readConfig();\n const { Operor } = await import('@operor/core');\n const { MockProvider } = await import('@operor/provider-mock');\n const {\n MockShopifySkill,\n CustomerSimulator,\n ConversationEvaluator,\n ConversationRunner,\n ECOMMERCE_SCENARIOS,\n SkillTestHarness,\n } = await import('@operor/testing');\n\n // Set up LLM if configured\n let llm: any;\n if (config.LLM_PROVIDER && config.LLM_API_KEY) {\n const { AIProvider } = await import('@operor/llm');\n llm = new AIProvider({\n provider: config.LLM_PROVIDER as any,\n apiKey: config.LLM_API_KEY,\n model: config.LLM_MODEL,\n });\n }\n\n // Determine which scenarios to run\n let scenarios = [...ECOMMERCE_SCENARIOS];\n\n if (options.file) {\n const fs = await import('fs');\n try {\n const raw = fs.readFileSync(options.file, 'utf-8');\n scenarios = JSON.parse(raw);\n } catch (err) {\n console.error(`Failed to load scenarios from ${options.file}:`, err);\n process.exit(1);\n }\n }\n\n if (options.scenario) {\n const match = scenarios.filter(\n (s) => s.name.toLowerCase().includes(options.scenario!.toLowerCase()) ||\n s.id.toLowerCase().includes(options.scenario!.toLowerCase())\n );\n if (match.length === 0) {\n console.error(`No scenario matching \"${options.scenario}\". Available:`);\n scenarios.forEach((s) => console.error(` - ${s.id}: ${s.name}`));\n process.exit(1);\n }\n scenarios = match;\n }\n\n // Apply overrides\n if (options.turns) {\n scenarios = scenarios.map((s) => ({ ...s, maxTurns: options.turns! }));\n }\n if (options.persona) {\n scenarios = scenarios.map((s) => ({ ...s, persona: options.persona! }));\n }\n\n // Set up Operor with mocks\n const os = new Operor({ debug: false, batchWindowMs: 0 });\n const provider = new MockProvider();\n const shopify = new MockShopifySkill();\n\n // Wrap skill with safety harness if --real or --dry-run\n let skill: any = shopify;\n if (options.real || options.dryRun) {\n skill = new SkillTestHarness(shopify, {\n allowWrites: options.allowWrites ?? false,\n dryRun: options.dryRun ?? false,\n });\n await skill.authenticate();\n }\n\n await os.addProvider(provider);\n await os.addSkill(skill);\n\n const allTools = [shopify.tools.get_order, shopify.tools.create_discount, shopify.tools.search_products];\n\n const agent = os.createAgent({\n name: 'Test Agent',\n purpose: 'Handle customer support conversations',\n personality: 'empathetic and solution-focused',\n triggers: ['order_tracking', 'general'],\n tools: allTools,\n rules: [{\n name: 'Auto-compensation',\n condition: async (_ctx: any, toolResults: any[]) => {\n const order = toolResults.find((t) => t.name === 'get_order');\n return order?.success && order.result?.isDelayed && order.result?.delayDays >= 2;\n },\n action: async () => {\n const discount = await shopify.tools.create_discount.execute({ percent: 10, validDays: 30 });\n return { type: 'discount_created', code: discount.code, percent: 10, validDays: 30 };\n },\n }],\n });\n\n // Set up Knowledge Base if enabled\n let kbRuntime: any;\n if (config.KB_ENABLED === 'true') {\n try {\n const { SQLiteKnowledgeStore, EmbeddingService, RetrievalPipeline } = await import('@operor/knowledge');\n const embedder = new EmbeddingService({\n provider: (config.KB_EMBEDDING_PROVIDER || config.LLM_PROVIDER) as any,\n apiKey: config.KB_EMBEDDING_API_KEY || config.LLM_API_KEY || '',\n model: config.KB_EMBEDDING_MODEL,\n });\n const kbStore = new SQLiteKnowledgeStore(config.KB_DB_PATH || './knowledge.db', embedder.dimensions);\n await kbStore.initialize();\n const retrieval = new RetrievalPipeline(kbStore, embedder);\n kbRuntime = { retrieve: (q: string) => retrieval.retrieve(q) };\n console.log('[Operor] 📚 Knowledge Base enabled for conversations');\n } catch (kbError: any) {\n console.warn('[Operor] ⚠️ Failed to initialize Knowledge Base:', kbError.message);\n }\n }\n\n // Override agent.process with LLM-based implementation if LLM is configured\n if (llm) {\n const { applyLLMOverride } = await import('./llm-override.js');\n applyLLMOverride(agent, llm, allTools, { kbRuntime });\n }\n\n await os.start();\n\n // Create runner components with LLM if configured\n const customerSimulator = new CustomerSimulator({ llmProvider: llm });\n const conversationEvaluator = new ConversationEvaluator({ llmProvider: llm });\n const runner = new ConversationRunner({\n agentOS: os,\n customerSimulator,\n conversationEvaluator,\n verbose: options.verbose ?? false,\n });\n\n if (!options.json) {\n console.log(`\\n Running ${scenarios.length} conversation scenario(s)...\\n`);\n }\n\n const results = await runner.runScenarios(scenarios);\n await os.stop();\n\n // Output results\n if (options.json) {\n console.log(JSON.stringify(results, null, 2));\n } else {\n let passed = 0;\n let failed = 0;\n\n for (const r of results) {\n const icon = r.passed ? '✓' : '✗';\n const status = r.passed ? 'PASS' : 'FAIL';\n console.log(` ${icon} ${r.scenario.name} — ${status} (${r.turns.length} turns, ${r.duration}ms)`);\n\n if (!r.passed && r.evaluation.feedback) {\n console.log(` ${r.evaluation.feedback}`);\n }\n\n if (r.passed) passed++;\n else failed++;\n }\n\n console.log(`\\n Results: ${passed} passed, ${failed} failed out of ${results.length}`);\n console.log(` Status: ${failed === 0 ? '✓ ALL PASSED' : '✗ SOME FAILED'}\\n`);\n }\n\n process.exit(results.every((r) => r.passed) ? 0 : 1);\n}\n"],"mappings":"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"}
@@ -0,0 +1,122 @@
1
+ import { n as readConfig, t as configExists } from "./index.js";
2
+ import * as clack from "@clack/prompts";
3
+
4
+ //#region src/commands/doctor.ts
5
+ async function runDoctor() {
6
+ clack.intro("Operor Doctor");
7
+ if (!configExists()) {
8
+ clack.log.error(".env file not found. Run \"operor setup\" first.");
9
+ process.exit(1);
10
+ }
11
+ const config = readConfig();
12
+ let allPassed = true;
13
+ clack.log.success(".env file found");
14
+ if (config.LLM_PROVIDER && config.LLM_API_KEY) {
15
+ const spinner = clack.spinner();
16
+ spinner.start(`Testing ${config.LLM_PROVIDER} LLM connection...`);
17
+ try {
18
+ const { AIProvider } = await import("@operor/llm");
19
+ await new AIProvider({
20
+ provider: config.LLM_PROVIDER,
21
+ apiKey: config.LLM_API_KEY,
22
+ model: config.LLM_MODEL
23
+ }).complete([{
24
+ role: "user",
25
+ content: "ping"
26
+ }], { maxTokens: 5 });
27
+ spinner.stop(`${config.LLM_PROVIDER} LLM: OK`);
28
+ } catch (error) {
29
+ spinner.stop(`${config.LLM_PROVIDER} LLM: FAILED — ${error.message}`);
30
+ allPassed = false;
31
+ }
32
+ } else {
33
+ clack.log.warning("LLM not configured");
34
+ allPassed = false;
35
+ }
36
+ if (config.SKILLS_ENABLED !== "false") {
37
+ const skillSpinner = clack.spinner();
38
+ skillSpinner.start("Checking MCP Skills...");
39
+ try {
40
+ const { loadSkillsConfig, SkillManager } = await import("@operor/skills");
41
+ const enabledSkills = loadSkillsConfig().skills.filter((s) => s.enabled !== false);
42
+ if (enabledSkills.length === 0) skillSpinner.stop("MCP Skills: No skills configured in mcp.json");
43
+ else {
44
+ const errors = [];
45
+ for (const skill of enabledSkills) {
46
+ const error = SkillManager.validateConfig(skill);
47
+ if (error) errors.push(`${skill.name}: ${error}`);
48
+ }
49
+ if (errors.length > 0) {
50
+ skillSpinner.stop(`MCP Skills: ${errors.length} config error(s)`);
51
+ for (const err of errors) clack.log.warning(` ${err}`);
52
+ allPassed = false;
53
+ } else skillSpinner.stop(`MCP Skills: ${enabledSkills.length} skill(s) configured`);
54
+ }
55
+ } catch (error) {
56
+ skillSpinner.stop("MCP Skills: Not available (mcp.json not found or @operor/skills not installed)");
57
+ }
58
+ }
59
+ if (config.CHANNEL === "telegram" && config.TELEGRAM_BOT_TOKEN) clack.log.success("Telegram bot token configured");
60
+ else if (config.CHANNEL === "wati") if (config.WATI_API_TOKEN && config.WATI_TENANT_ID) clack.log.success("WATI credentials configured");
61
+ else {
62
+ clack.log.error("WATI: missing WATI_API_TOKEN or WATI_TENANT_ID");
63
+ allPassed = false;
64
+ }
65
+ if (config.KB_ENABLED === "true") {
66
+ const kbSpinner = clack.spinner();
67
+ kbSpinner.start("Checking Knowledge Base...");
68
+ try {
69
+ const { writeFileSync, unlinkSync, existsSync } = await import("node:fs");
70
+ const { dirname } = await import("node:path");
71
+ const { mkdirSync } = await import("node:fs");
72
+ const dbPath = config.KB_DB_PATH || "./knowledge.db";
73
+ const dir = dirname(dbPath);
74
+ if (!existsSync(dir)) mkdirSync(dir, { recursive: true });
75
+ const testFile = dbPath + ".write-test";
76
+ writeFileSync(testFile, "");
77
+ unlinkSync(testFile);
78
+ if (config.KB_EMBEDDING_PROVIDER !== "ollama" && !config.KB_EMBEDDING_API_KEY) {
79
+ kbSpinner.stop("Knowledge Base: WARNING — no embedding API key set");
80
+ allPassed = false;
81
+ } else try {
82
+ await import("@operor/knowledge");
83
+ kbSpinner.stop("Knowledge Base: OK");
84
+ } catch {
85
+ kbSpinner.stop("Knowledge Base: OK (config valid, @operor/knowledge not installed yet)");
86
+ }
87
+ } catch (error) {
88
+ kbSpinner.stop(`Knowledge Base: FAILED — ${error.message}`);
89
+ allPassed = false;
90
+ }
91
+ }
92
+ if (config.ANALYTICS_ENABLED !== "false") {
93
+ const analyticsSpinner = clack.spinner();
94
+ analyticsSpinner.start("Checking Analytics...");
95
+ try {
96
+ const { writeFileSync, unlinkSync, existsSync } = await import("node:fs");
97
+ const { dirname } = await import("node:path");
98
+ const { mkdirSync } = await import("node:fs");
99
+ const dbPath = config.ANALYTICS_DB_PATH || "./analytics.db";
100
+ const dir = dirname(dbPath);
101
+ if (!existsSync(dir)) mkdirSync(dir, { recursive: true });
102
+ const testFile = dbPath + ".write-test";
103
+ writeFileSync(testFile, "");
104
+ unlinkSync(testFile);
105
+ analyticsSpinner.stop("Analytics: OK (DB path writable)");
106
+ } catch (error) {
107
+ analyticsSpinner.stop(`Analytics: FAILED — ${error.message}`);
108
+ allPassed = false;
109
+ }
110
+ }
111
+ const nodeVersion = process.versions.node;
112
+ if (parseInt(nodeVersion.split(".")[0]) >= 18) clack.log.success(`Node.js ${nodeVersion}`);
113
+ else {
114
+ clack.log.error(`Node.js ${nodeVersion} — requires >= 18`);
115
+ allPassed = false;
116
+ }
117
+ clack.outro(allPassed ? "All checks passed!" : "Some checks failed. Fix the issues above.");
118
+ }
119
+
120
+ //#endregion
121
+ export { runDoctor };
122
+ //# sourceMappingURL=doctor-98gPl743.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"doctor-98gPl743.js","names":[],"sources":["../src/commands/doctor.ts"],"sourcesContent":["import * as clack from '@clack/prompts';\nimport { readConfig, configExists } from '../config.js';\n\nexport async function runDoctor(): Promise<void> {\n clack.intro('Operor Doctor');\n\n if (!configExists()) {\n clack.log.error('.env file not found. Run \"operor setup\" first.');\n process.exit(1);\n }\n\n const config = readConfig();\n let allPassed = true;\n\n // 1. Check .env exists\n clack.log.success('.env file found');\n\n // 2. Validate LLM\n if (config.LLM_PROVIDER && config.LLM_API_KEY) {\n const spinner = clack.spinner();\n spinner.start(`Testing ${config.LLM_PROVIDER} LLM connection...`);\n try {\n const { AIProvider } = await import('@operor/llm');\n const llm = new AIProvider({\n provider: config.LLM_PROVIDER as any,\n apiKey: config.LLM_API_KEY,\n model: config.LLM_MODEL,\n });\n await llm.complete([{ role: 'user', content: 'ping' }], { maxTokens: 5 });\n spinner.stop(`${config.LLM_PROVIDER} LLM: OK`);\n } catch (error: any) {\n spinner.stop(`${config.LLM_PROVIDER} LLM: FAILED — ${error.message}`);\n allPassed = false;\n }\n } else {\n clack.log.warning('LLM not configured');\n allPassed = false;\n }\n\n // 3. Validate MCP Skills\n if (config.SKILLS_ENABLED !== 'false') {\n const skillSpinner = clack.spinner();\n skillSpinner.start('Checking MCP Skills...');\n try {\n const { loadSkillsConfig, SkillManager } = await import('@operor/skills');\n const skillsConfig = loadSkillsConfig();\n const enabledSkills = skillsConfig.skills.filter((s: any) => s.enabled !== false);\n\n if (enabledSkills.length === 0) {\n skillSpinner.stop('MCP Skills: No skills configured in mcp.json');\n } else {\n // Validate config only (don't start servers)\n const errors: string[] = [];\n for (const skill of enabledSkills) {\n const error = SkillManager.validateConfig(skill);\n if (error) errors.push(`${skill.name}: ${error}`);\n }\n if (errors.length > 0) {\n skillSpinner.stop(`MCP Skills: ${errors.length} config error(s)`);\n for (const err of errors) clack.log.warning(` ${err}`);\n allPassed = false;\n } else {\n skillSpinner.stop(`MCP Skills: ${enabledSkills.length} skill(s) configured`);\n }\n }\n } catch (error: any) {\n skillSpinner.stop('MCP Skills: Not available (mcp.json not found or @operor/skills not installed)');\n }\n }\n\n // 4. Validate channel credentials\n if (config.CHANNEL === 'telegram' && config.TELEGRAM_BOT_TOKEN) {\n clack.log.success('Telegram bot token configured');\n } else if (config.CHANNEL === 'wati') {\n if (config.WATI_API_TOKEN && config.WATI_TENANT_ID) {\n clack.log.success('WATI credentials configured');\n } else {\n clack.log.error('WATI: missing WATI_API_TOKEN or WATI_TENANT_ID');\n allPassed = false;\n }\n }\n\n // 5. Knowledge Base checks\n if (config.KB_ENABLED === 'true') {\n const kbSpinner = clack.spinner();\n kbSpinner.start('Checking Knowledge Base...');\n try {\n // Check DB path is writable\n const { writeFileSync, unlinkSync, existsSync } = await import('node:fs');\n const { dirname } = await import('node:path');\n const { mkdirSync } = await import('node:fs');\n const dbPath = config.KB_DB_PATH || './knowledge.db';\n const dir = dirname(dbPath);\n if (!existsSync(dir)) {\n mkdirSync(dir, { recursive: true });\n }\n const testFile = dbPath + '.write-test';\n writeFileSync(testFile, '');\n unlinkSync(testFile);\n\n // Check embedding API key (unless ollama)\n if (config.KB_EMBEDDING_PROVIDER !== 'ollama' && !config.KB_EMBEDDING_API_KEY) {\n kbSpinner.stop('Knowledge Base: WARNING — no embedding API key set');\n allPassed = false;\n } else {\n // Try loading sqlite-vec\n try {\n await import('@operor/knowledge');\n kbSpinner.stop('Knowledge Base: OK');\n } catch {\n kbSpinner.stop('Knowledge Base: OK (config valid, @operor/knowledge not installed yet)');\n }\n }\n } catch (error: any) {\n kbSpinner.stop(`Knowledge Base: FAILED — ${error.message}`);\n allPassed = false;\n }\n }\n\n // 6. Analytics checks\n if (config.ANALYTICS_ENABLED !== 'false') {\n const analyticsSpinner = clack.spinner();\n analyticsSpinner.start('Checking Analytics...');\n try {\n const { writeFileSync, unlinkSync, existsSync } = await import('node:fs');\n const { dirname } = await import('node:path');\n const { mkdirSync } = await import('node:fs');\n const dbPath = config.ANALYTICS_DB_PATH || './analytics.db';\n const dir = dirname(dbPath);\n if (!existsSync(dir)) {\n mkdirSync(dir, { recursive: true });\n }\n const testFile = dbPath + '.write-test';\n writeFileSync(testFile, '');\n unlinkSync(testFile);\n analyticsSpinner.stop('Analytics: OK (DB path writable)');\n } catch (error: any) {\n analyticsSpinner.stop(`Analytics: FAILED — ${error.message}`);\n allPassed = false;\n }\n }\n\n // 7. Check Node.js version\n const nodeVersion = process.versions.node;\n const major = parseInt(nodeVersion.split('.')[0]);\n if (major >= 18) {\n clack.log.success(`Node.js ${nodeVersion}`);\n } else {\n clack.log.error(`Node.js ${nodeVersion} — requires >= 18`);\n allPassed = false;\n }\n\n clack.outro(allPassed ? 'All checks passed!' : 'Some checks failed. Fix the issues above.');\n}\n"],"mappings":"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"}
@@ -0,0 +1 @@
1
+ export { };