opc-agent 1.3.2 → 2.0.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 (226) hide show
  1. package/.github/ISSUE_TEMPLATE/bug_report.md +20 -0
  2. package/.github/ISSUE_TEMPLATE/feature_request.md +14 -0
  3. package/.github/PULL_REQUEST_TEMPLATE.md +13 -0
  4. package/.github/workflows/ci.yml +24 -0
  5. package/CHANGELOG.md +48 -63
  6. package/CONTRIBUTING.md +21 -60
  7. package/README.md +284 -348
  8. package/README.zh-CN.md +415 -415
  9. package/dist/channels/slack.js +93 -10
  10. package/dist/channels/telegram.d.ts +30 -9
  11. package/dist/channels/telegram.js +125 -33
  12. package/dist/channels/web.d.ts +10 -0
  13. package/dist/channels/web.js +33 -2
  14. package/dist/cli.js +667 -65
  15. package/dist/core/agent.d.ts +23 -0
  16. package/dist/core/agent.js +120 -3
  17. package/dist/core/runtime.d.ts +5 -0
  18. package/dist/core/runtime.js +71 -0
  19. package/dist/core/scheduler.d.ts +52 -0
  20. package/dist/core/scheduler.js +168 -0
  21. package/dist/core/subagent.d.ts +28 -0
  22. package/dist/core/subagent.js +65 -0
  23. package/dist/daemon.d.ts +3 -0
  24. package/dist/daemon.js +134 -0
  25. package/dist/deploy/hermes.js +22 -22
  26. package/dist/deploy/openclaw.js +31 -40
  27. package/dist/index.d.ts +10 -10
  28. package/dist/index.js +22 -15
  29. package/dist/providers/index.d.ts +6 -2
  30. package/dist/providers/index.js +22 -9
  31. package/dist/schema/oad.d.ts +180 -6
  32. package/dist/schema/oad.js +12 -1
  33. package/dist/skills/auto-learn.d.ts +28 -0
  34. package/dist/skills/auto-learn.js +257 -0
  35. package/dist/templates/code-reviewer.d.ts +0 -8
  36. package/dist/templates/code-reviewer.js +5 -9
  37. package/dist/templates/customer-service.d.ts +0 -8
  38. package/dist/templates/customer-service.js +2 -6
  39. package/dist/templates/data-analyst.d.ts +0 -8
  40. package/dist/templates/data-analyst.js +5 -9
  41. package/dist/templates/knowledge-base.d.ts +0 -8
  42. package/dist/templates/knowledge-base.js +2 -6
  43. package/dist/templates/sales-assistant.d.ts +0 -8
  44. package/dist/templates/sales-assistant.js +4 -8
  45. package/dist/templates/teacher.d.ts +0 -8
  46. package/dist/templates/teacher.js +6 -10
  47. package/dist/tools/builtin/datetime.d.ts +3 -0
  48. package/dist/tools/builtin/datetime.js +44 -0
  49. package/dist/tools/builtin/file.d.ts +3 -0
  50. package/dist/tools/builtin/file.js +151 -0
  51. package/dist/tools/builtin/index.d.ts +15 -0
  52. package/dist/tools/builtin/index.js +30 -0
  53. package/dist/tools/builtin/shell.d.ts +3 -0
  54. package/dist/tools/builtin/shell.js +43 -0
  55. package/dist/tools/builtin/web.d.ts +3 -0
  56. package/dist/tools/builtin/web.js +37 -0
  57. package/dist/tools/mcp-client.d.ts +24 -0
  58. package/dist/tools/mcp-client.js +119 -0
  59. package/dist/traces/index.d.ts +49 -0
  60. package/dist/traces/index.js +102 -0
  61. package/docs/.vitepress/config.ts +103 -103
  62. package/docs/api/cli.md +48 -48
  63. package/docs/api/oad-schema.md +64 -64
  64. package/docs/api/sdk.md +80 -80
  65. package/docs/guide/concepts.md +51 -51
  66. package/docs/guide/configuration.md +79 -79
  67. package/docs/guide/deployment.md +42 -42
  68. package/docs/guide/getting-started.md +44 -44
  69. package/docs/guide/templates.md +28 -28
  70. package/docs/guide/testing.md +84 -84
  71. package/docs/index.md +27 -27
  72. package/docs/zh/api/cli.md +54 -54
  73. package/docs/zh/api/oad-schema.md +87 -87
  74. package/docs/zh/api/sdk.md +102 -102
  75. package/docs/zh/guide/concepts.md +104 -104
  76. package/docs/zh/guide/configuration.md +135 -135
  77. package/docs/zh/guide/deployment.md +81 -81
  78. package/docs/zh/guide/getting-started.md +82 -82
  79. package/docs/zh/guide/templates.md +84 -84
  80. package/docs/zh/guide/testing.md +88 -88
  81. package/docs/zh/index.md +27 -27
  82. package/examples/README.md +22 -0
  83. package/examples/basic-agent.ts +90 -0
  84. package/examples/brain-integration.ts +71 -0
  85. package/examples/customer-service-demo/README.md +90 -90
  86. package/examples/customer-service-demo/oad.yaml +107 -107
  87. package/examples/multi-channel.ts +74 -0
  88. package/package.json +1 -1
  89. package/src/analytics/index.ts +66 -66
  90. package/src/channels/discord.ts +192 -192
  91. package/src/channels/email.ts +177 -177
  92. package/src/channels/feishu.ts +236 -236
  93. package/src/channels/index.ts +15 -15
  94. package/src/channels/slack.ts +217 -160
  95. package/src/channels/telegram.ts +155 -33
  96. package/src/channels/voice.ts +106 -106
  97. package/src/channels/web.ts +38 -2
  98. package/src/channels/webhook.ts +199 -199
  99. package/src/channels/websocket.ts +87 -87
  100. package/src/channels/wechat.ts +149 -149
  101. package/src/cli.ts +697 -63
  102. package/src/core/a2a.ts +143 -143
  103. package/src/core/agent.ts +146 -3
  104. package/src/core/analytics-engine.ts +186 -186
  105. package/src/core/auth.ts +57 -57
  106. package/src/core/cache.ts +141 -141
  107. package/src/core/compose.ts +77 -77
  108. package/src/core/config.ts +14 -14
  109. package/src/core/errors.ts +148 -148
  110. package/src/core/hitl.ts +138 -138
  111. package/src/core/logger.ts +57 -57
  112. package/src/core/orchestrator.ts +215 -215
  113. package/src/core/performance.ts +187 -187
  114. package/src/core/rate-limiter.ts +128 -128
  115. package/src/core/room.ts +109 -109
  116. package/src/core/runtime.ts +230 -152
  117. package/src/core/sandbox.ts +101 -101
  118. package/src/core/scheduler.ts +187 -0
  119. package/src/core/security.ts +171 -171
  120. package/src/core/subagent.ts +98 -0
  121. package/src/core/types.ts +68 -68
  122. package/src/core/versioning.ts +106 -106
  123. package/src/core/watch.ts +178 -178
  124. package/src/core/workflow.ts +235 -235
  125. package/src/daemon.ts +96 -0
  126. package/src/deploy/hermes.ts +156 -156
  127. package/src/deploy/openclaw.ts +190 -200
  128. package/src/i18n/index.ts +216 -216
  129. package/src/index.ts +14 -10
  130. package/src/memory/deepbrain.ts +108 -108
  131. package/src/memory/index.ts +34 -34
  132. package/src/plugins/index.ts +208 -208
  133. package/src/providers/index.ts +354 -331
  134. package/src/schema/oad.ts +14 -2
  135. package/src/skills/auto-learn.ts +262 -0
  136. package/src/skills/base.ts +16 -16
  137. package/src/skills/document.ts +100 -100
  138. package/src/skills/http.ts +35 -35
  139. package/src/skills/index.ts +27 -27
  140. package/src/skills/scheduler.ts +80 -80
  141. package/src/skills/webhook-trigger.ts +59 -59
  142. package/src/templates/code-reviewer.ts +30 -34
  143. package/src/templates/customer-service.ts +76 -80
  144. package/src/templates/data-analyst.ts +66 -70
  145. package/src/templates/executive-assistant.ts +71 -71
  146. package/src/templates/financial-advisor.ts +60 -60
  147. package/src/templates/knowledge-base.ts +27 -31
  148. package/src/templates/legal-assistant.ts +71 -71
  149. package/src/templates/sales-assistant.ts +75 -79
  150. package/src/templates/teacher.ts +75 -79
  151. package/src/testing/index.ts +181 -181
  152. package/src/tools/builtin/datetime.ts +41 -0
  153. package/src/tools/builtin/file.ts +107 -0
  154. package/src/tools/builtin/index.ts +28 -0
  155. package/src/tools/builtin/shell.ts +43 -0
  156. package/src/tools/builtin/web.ts +35 -0
  157. package/src/tools/calculator.ts +73 -73
  158. package/src/tools/datetime.ts +149 -149
  159. package/src/tools/json-transform.ts +187 -187
  160. package/src/tools/mcp-client.ts +131 -0
  161. package/src/tools/mcp.ts +76 -76
  162. package/src/tools/text-analysis.ts +116 -116
  163. package/src/traces/index.ts +132 -0
  164. package/templates/Dockerfile +15 -15
  165. package/templates/code-reviewer/README.md +27 -27
  166. package/templates/code-reviewer/oad.yaml +41 -41
  167. package/templates/customer-service/README.md +22 -22
  168. package/templates/customer-service/oad.yaml +36 -36
  169. package/templates/docker-compose.yml +21 -21
  170. package/templates/ecommerce-assistant/README.md +45 -45
  171. package/templates/ecommerce-assistant/oad.yaml +47 -47
  172. package/templates/knowledge-base/README.md +28 -28
  173. package/templates/knowledge-base/oad.yaml +38 -38
  174. package/templates/sales-assistant/README.md +26 -26
  175. package/templates/sales-assistant/oad.yaml +43 -43
  176. package/templates/tech-support/README.md +43 -43
  177. package/templates/tech-support/oad.yaml +45 -45
  178. package/test-agent/Dockerfile +9 -0
  179. package/test-agent/README.md +50 -0
  180. package/test-agent/agent.yaml +23 -0
  181. package/test-agent/docker-compose.yml +11 -0
  182. package/test-agent/oad.yaml +31 -0
  183. package/test-agent/package-lock.json +1492 -0
  184. package/test-agent/package.json +18 -0
  185. package/test-agent/src/index.ts +24 -0
  186. package/test-agent/src/skills/echo.ts +15 -0
  187. package/test-agent/tsconfig.json +25 -0
  188. package/tests/a2a.test.ts +66 -66
  189. package/tests/agent.test.ts +72 -72
  190. package/tests/analytics.test.ts +50 -50
  191. package/tests/auto-learn.test.ts +105 -0
  192. package/tests/builtin-tools.test.ts +83 -0
  193. package/tests/channel.test.ts +39 -39
  194. package/tests/cli.test.ts +46 -0
  195. package/tests/e2e.test.ts +134 -134
  196. package/tests/errors.test.ts +83 -83
  197. package/tests/hitl.test.ts +71 -71
  198. package/tests/i18n.test.ts +41 -41
  199. package/tests/mcp.test.ts +54 -54
  200. package/tests/oad.test.ts +68 -68
  201. package/tests/performance.test.ts +115 -115
  202. package/tests/plugin.test.ts +74 -74
  203. package/tests/room.test.ts +106 -106
  204. package/tests/runtime.test.ts +42 -42
  205. package/tests/sandbox.test.ts +46 -46
  206. package/tests/security.test.ts +60 -60
  207. package/tests/subagent.test.ts +130 -0
  208. package/tests/telegram-discord.test.ts +60 -0
  209. package/tests/templates.test.ts +77 -77
  210. package/tests/v070.test.ts +76 -76
  211. package/tests/versioning.test.ts +75 -75
  212. package/tests/voice.test.ts +61 -61
  213. package/tests/webhook.test.ts +29 -29
  214. package/tests/workflow.test.ts +143 -143
  215. package/tsconfig.json +19 -19
  216. package/vitest.config.ts +9 -9
  217. package/dist/core/dashboard.d.ts +0 -35
  218. package/dist/core/dashboard.js +0 -157
  219. package/dist/core/priority.d.ts +0 -52
  220. package/dist/core/priority.js +0 -102
  221. package/src/core/dashboard.ts +0 -219
  222. package/src/core/priority.ts +0 -140
  223. package/src/dtv/data.ts +0 -29
  224. package/src/dtv/trust.ts +0 -43
  225. package/src/dtv/value.ts +0 -47
  226. package/src/marketplace/index.ts +0 -223
package/dist/cli.js CHANGED
@@ -61,7 +61,7 @@ const workflow_1 = require("./core/workflow");
61
61
  const versioning_1 = require("./core/versioning");
62
62
  const providers_1 = require("./providers");
63
63
  const knowledge_1 = require("./core/knowledge");
64
- const marketplace_1 = require("./marketplace");
64
+ const child_process_1 = require("child_process");
65
65
  const program = new commander_1.Command();
66
66
  const color = {
67
67
  green: (s) => `\x1b[32m${s}\x1b[0m`,
@@ -119,7 +119,7 @@ async function select(question, options) {
119
119
  program
120
120
  .name('opc')
121
121
  .description('OPC Agent - Open Agent Framework for business workstations')
122
- .version('1.0.0');
122
+ .version('2.0.0');
123
123
  // ── Init command ─────────────────────────────────────────────
124
124
  program
125
125
  .command('init')
@@ -139,6 +139,7 @@ program
139
139
  process.exit(1);
140
140
  }
141
141
  fs.mkdirSync(dir, { recursive: true });
142
+ fs.mkdirSync(path.join(dir, 'src', 'skills'), { recursive: true });
142
143
  const factory = TEMPLATES[template]?.factory ?? customer_service_1.createCustomerServiceConfig;
143
144
  const config = factory();
144
145
  config.metadata.name = name;
@@ -147,6 +148,102 @@ program
147
148
  config.spec.channels.push({ type: 'web', port: 3000 });
148
149
  }
149
150
  fs.writeFileSync(path.join(dir, 'oad.yaml'), yaml.dump(config, { lineWidth: 120 }));
151
+ // agent.yaml — standalone OAD config for runtime usage
152
+ fs.writeFileSync(path.join(dir, 'agent.yaml'), `apiVersion: opc/v1
153
+ kind: Agent
154
+ metadata:
155
+ name: ${name}
156
+ version: 1.0.0
157
+ description: My AI Agent
158
+ spec:
159
+ model: qwen2.5
160
+ provider:
161
+ default: ollama
162
+ systemPrompt: |
163
+ You are a helpful AI assistant named ${name}.
164
+ Be concise, helpful, and friendly.
165
+ channels:
166
+ - type: web
167
+ port: 3000
168
+ memory:
169
+ shortTerm: true
170
+ longTerm:
171
+ provider: deepbrain
172
+ skills:
173
+ - name: echo
174
+ description: Echo test skill
175
+ `);
176
+ // src/index.ts — entry point
177
+ fs.writeFileSync(path.join(dir, 'src', 'index.ts'), `import { AgentRuntime } from 'opc-agent';
178
+ import { EchoSkill } from './skills/echo';
179
+ import { readFileSync, existsSync } from 'fs';
180
+
181
+ async function main() {
182
+ const runtime = new AgentRuntime();
183
+
184
+ // Load OAD config
185
+ const config = await runtime.loadConfig('./agent.yaml');
186
+
187
+ // Load personality and context files
188
+ const soul = existsSync('./SOUL.md') ? readFileSync('./SOUL.md', 'utf-8') : '';
189
+ const context = existsSync('./CONTEXT.md') ? readFileSync('./CONTEXT.md', 'utf-8') : '';
190
+ if (soul || context) {
191
+ const fullPrompt = [soul, context, config.spec.systemPrompt].filter(Boolean).join('\\n\\n');
192
+ config.spec.systemPrompt = fullPrompt;
193
+ }
194
+
195
+ // Initialize agent with channels, memory, etc.
196
+ const agent = await runtime.initialize(config);
197
+
198
+ // Register custom skills
199
+ runtime.registerSkill(new EchoSkill());
200
+
201
+ // Start serving
202
+ await runtime.start();
203
+
204
+ console.log('🤖 Agent is running!');
205
+ console.log(' Web UI: http://localhost:3000');
206
+ console.log(' Press Ctrl+C to stop');
207
+ }
208
+
209
+ main().catch(console.error);
210
+ `);
211
+ // src/skills/echo.ts — example skill
212
+ fs.writeFileSync(path.join(dir, 'src', 'skills', 'echo.ts'), `import { BaseSkill } from 'opc-agent';
213
+ import type { AgentContext, Message, SkillResult } from 'opc-agent';
214
+
215
+ export class EchoSkill extends BaseSkill {
216
+ name = 'echo';
217
+ description = 'Echo back the message (test skill)';
218
+
219
+ async execute(context: AgentContext, message: Message): Promise<SkillResult> {
220
+ if (message.content.toLowerCase().startsWith('/echo ')) {
221
+ const text = message.content.slice(6);
222
+ return this.match(\`🔊 Echo: \${text}\`);
223
+ }
224
+ return this.noMatch();
225
+ }
226
+ }
227
+ `);
228
+ // tsconfig.json
229
+ fs.writeFileSync(path.join(dir, 'tsconfig.json'), JSON.stringify({
230
+ compilerOptions: {
231
+ target: 'ES2022',
232
+ module: 'commonjs',
233
+ lib: ['ES2022'],
234
+ outDir: 'dist',
235
+ rootDir: 'src',
236
+ strict: true,
237
+ esModuleInterop: true,
238
+ skipLibCheck: true,
239
+ forceConsistentCasingInFileNames: true,
240
+ resolveJsonModule: true,
241
+ declaration: true,
242
+ sourceMap: true,
243
+ },
244
+ include: ['src/**/*'],
245
+ exclude: ['node_modules', 'dist'],
246
+ }, null, 2));
150
247
  // .env.example
151
248
  fs.writeFileSync(path.join(dir, '.env.example'), `# LLM API Configuration
152
249
  OPC_LLM_API_KEY=your-api-key-here
@@ -157,9 +254,9 @@ OPC_LLM_MODEL=gpt-4o-mini
157
254
  # OPC_LLM_BASE_URL=https://api.deepseek.com/v1
158
255
  # OPC_LLM_MODEL=deepseek-chat
159
256
 
160
- # For local Ollama:
257
+ # For local Ollama (default in agent.yaml):
161
258
  # OPC_LLM_BASE_URL=http://localhost:11434/v1
162
- # OPC_LLM_MODEL=llama3
259
+ # OPC_LLM_MODEL=qwen2.5
163
260
  `);
164
261
  // .env (copy of example)
165
262
  fs.writeFileSync(path.join(dir, '.env'), `OPC_LLM_API_KEY=your-api-key-here
@@ -173,20 +270,27 @@ OPC_LLM_MODEL=gpt-4o-mini
173
270
  private: true,
174
271
  scripts: {
175
272
  start: 'opc run',
273
+ dev: 'opc dev',
176
274
  chat: 'opc chat',
275
+ build: 'tsc',
177
276
  },
178
277
  dependencies: {
179
- 'opc-agent': '^0.5.0',
278
+ 'opc-agent': '^1.3.0',
279
+ },
280
+ devDependencies: {
281
+ typescript: '^5.5.0',
282
+ tsx: '^4.0.0',
180
283
  },
181
284
  }, null, 2));
182
285
  // .gitignore
183
- fs.writeFileSync(path.join(dir, '.gitignore'), 'node_modules\n.env\n.opc-knowledge.json\ndata/\n');
286
+ fs.writeFileSync(path.join(dir, '.gitignore'), 'node_modules\ndist\n.env\n.opc-knowledge.json\ndata/\n');
184
287
  // Dockerfile
185
288
  fs.writeFileSync(path.join(dir, 'Dockerfile'), `FROM node:22-alpine
186
289
  WORKDIR /app
187
290
  COPY package.json package-lock.json* ./
188
291
  RUN npm ci --production 2>/dev/null || npm install --production
189
- COPY oad.yaml .env* ./
292
+ COPY oad.yaml agent.yaml .env* ./
293
+ COPY src/ ./src/
190
294
  COPY prompts/ ./prompts/ 2>/dev/null || true
191
295
  EXPOSE 3000
192
296
  CMD ["npx", "opc", "run"]
@@ -201,7 +305,7 @@ services:
201
305
  env_file:
202
306
  - .env
203
307
  volumes:
204
- - ./oad.yaml:/app/oad.yaml:ro
308
+ - ./agent.yaml:/app/agent.yaml:ro
205
309
  restart: unless-stopped
206
310
  `);
207
311
  // README.md
@@ -211,51 +315,108 @@ Created with [OPC Agent](https://github.com/Deepleaper/opc-agent) using the \`${
211
315
 
212
316
  ## Quick Start
213
317
 
214
- 1. **Set your API key:**
318
+ 1. **Install dependencies:**
215
319
  \`\`\`bash
216
- # Edit .env and add your API key
217
- cp .env.example .env
218
- # Then edit .env with your actual key
320
+ npm install
219
321
  \`\`\`
220
322
 
221
- 2. **Install dependencies:**
323
+ 2. **Run with Ollama (default):**
222
324
  \`\`\`bash
223
- npm install
325
+ # Make sure Ollama is running with qwen2.5 model
326
+ ollama pull qwen2.5
327
+ npx tsx src/index.ts
224
328
  \`\`\`
225
329
 
226
- 3. **Start the web server:**
330
+ 3. **Or use OpenAI/other providers:**
227
331
  \`\`\`bash
332
+ # Edit .env and set your API key
228
333
  npx opc run
229
334
  \`\`\`
230
335
 
231
336
  4. **Open browser:** [http://localhost:3000](http://localhost:3000)
232
337
 
233
- ## CLI Chat
338
+ ## Development
234
339
 
235
340
  \`\`\`bash
236
- npx opc chat
341
+ npx opc dev # Hot-reload mode
342
+ npx opc chat # CLI chat
343
+ \`\`\`
344
+
345
+ ## Project Structure
346
+
347
+ \`\`\`
348
+ ${name}/
349
+ ├── agent.yaml # OAD agent config (used by src/index.ts)
350
+ ├── oad.yaml # OAD config (used by opc CLI)
351
+ ├── src/
352
+ │ ├── index.ts # Entry point
353
+ │ └── skills/
354
+ │ └── echo.ts # Example skill
355
+ ├── package.json
356
+ └── tsconfig.json
237
357
  \`\`\`
238
358
 
239
359
  ## Configuration
240
360
 
241
- Edit \`oad.yaml\` to customize your agent's personality, skills, and behavior.
361
+ Edit \`agent.yaml\` to customize your agent's personality, skills, and behavior.
362
+ `);
363
+ // SOUL.md — agent personality
364
+ const createdDate = new Date().toISOString().split('T')[0];
365
+ fs.writeFileSync(path.join(dir, 'SOUL.md'), `# ${name} Personality
366
+
367
+ ## Identity
368
+ - Name: ${name}
369
+ - Role: AI Assistant
370
+ - Created: ${createdDate}
371
+
372
+ ## Personality Traits
373
+ - Helpful and professional
374
+ - Concise but thorough
375
+ - Friendly tone
376
+
377
+ ## Communication Style
378
+ - Use clear, simple language
379
+ - Be direct — answer the question first, then explain
380
+ - Use markdown formatting when helpful
381
+
382
+ ## Rules
383
+ - Always be honest about limitations
384
+ - Ask for clarification when the request is ambiguous
385
+ - Never make up information
386
+ `);
387
+ // CONTEXT.md — project context
388
+ fs.writeFileSync(path.join(dir, 'CONTEXT.md'), `# Project Context
389
+
390
+ ## About This Agent
391
+ ${name} is an AI agent built with OPC Agent Framework.
392
+
393
+ ## Knowledge Base
394
+ Add project-specific context here. The agent reads this file
395
+ on startup to understand the project context.
396
+
397
+ ## Important Notes
398
+ - Add domain knowledge here
399
+ - Add FAQ items here
400
+ - Add company policies here
242
401
  `);
243
402
  console.log(`\n${icon.success} Created agent project: ${color.bold(name + '/')}`);
244
- console.log(` ${icon.file} oad.yaml - Agent definition`);
245
- console.log(` ${icon.file} package.json - Dependencies`);
246
- console.log(` ${icon.file} .env.example - Environment template`);
247
- console.log(` ${icon.file} .env - Environment config (edit this!)`);
403
+ console.log(` ${icon.file} agent.yaml - Agent definition (OAD)`);
404
+ console.log(` ${icon.file} src/index.ts - Entry point`);
405
+ console.log(` ${icon.file} src/skills/echo.ts - Example skill`);
406
+ console.log(` ${icon.file} SOUL.md - Agent personality`);
407
+ console.log(` ${icon.file} CONTEXT.md - Project context`);
408
+ console.log(` ${icon.file} package.json - Dependencies`);
409
+ console.log(` ${icon.file} tsconfig.json - TypeScript config`);
410
+ console.log(` ${icon.file} .env.example - Environment template`);
248
411
  console.log(` ${icon.file} .gitignore`);
249
412
  console.log(` ${icon.file} Dockerfile`);
250
- console.log(` ${icon.file} docker-compose.yml`);
251
413
  console.log(` ${icon.file} README.md`);
252
414
  console.log(`\n Template: ${color.cyan(template)}`);
253
415
  console.log(`\n${color.bold('Next steps:')}`);
254
416
  console.log(` 1. cd ${name}`);
255
- console.log(` 2. Edit .env — set your OPC_LLM_API_KEY`);
256
- console.log(` 3. npm install`);
257
- console.log(` 4. npx opc run`);
258
- console.log(` 5. Open http://localhost:3000\n`);
417
+ console.log(` 2. npm install`);
418
+ console.log(` 3. npx tsx src/index.ts ${color.dim('# or: npx opc run')}`);
419
+ console.log(` 4. Open http://localhost:3000\n`);
259
420
  });
260
421
  // ── Chat command ─────────────────────────────────────────────
261
422
  program
@@ -267,6 +428,15 @@ program
267
428
  loadDotEnv();
268
429
  let systemPrompt = 'You are a helpful AI agent.';
269
430
  let model;
431
+ let agentName = 'Agent';
432
+ let agentVersion = '1.0.0';
433
+ let providerName = 'openai';
434
+ let skillNames = [];
435
+ // Try loading SOUL.md and CONTEXT.md for enriched system prompt
436
+ const soulPath = path.resolve('SOUL.md');
437
+ const contextPath = path.resolve('CONTEXT.md');
438
+ const soulContent = fs.existsSync(soulPath) ? fs.readFileSync(soulPath, 'utf-8') : '';
439
+ const contextContent = fs.existsSync(contextPath) ? fs.readFileSync(contextPath, 'utf-8') : '';
270
440
  try {
271
441
  const raw = fs.readFileSync(opts.file, 'utf-8');
272
442
  const config = yaml.load(raw);
@@ -274,15 +444,93 @@ program
274
444
  systemPrompt = config.spec.systemPrompt;
275
445
  if (config?.spec?.model)
276
446
  model = config.spec.model;
277
- console.log(`\n${icon.gear} Loaded agent: ${color.bold(config?.metadata?.name ?? 'unknown')}`);
447
+ if (config?.metadata?.name)
448
+ agentName = config.metadata.name;
449
+ if (config?.metadata?.version)
450
+ agentVersion = config.metadata.version;
451
+ if (config?.spec?.provider?.default)
452
+ providerName = config.spec.provider.default;
453
+ if (config?.spec?.skills)
454
+ skillNames = config.spec.skills.map((s) => s.name);
278
455
  }
279
456
  catch {
280
- console.log(`\n${icon.info} No oad.yaml found, using defaults.`);
457
+ // No config file, use defaults
281
458
  }
459
+ // Prepend SOUL.md and CONTEXT.md to system prompt
460
+ systemPrompt = [soulContent, contextContent, systemPrompt].filter(Boolean).join('\n\n');
282
461
  const provider = (0, providers_1.createProvider)('openai', model);
283
462
  const history = [];
284
- console.log(`${color.dim('Type your message. Press Ctrl+C to exit.')}\n`);
285
- const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
463
+ // Print startup banner
464
+ const bannerLines = [
465
+ '╔══════════════════════════════════════╗',
466
+ '║ 🤖 OPC Agent — Interactive Chat ║',
467
+ `║ Agent: ${(agentName + ' v' + agentVersion).padEnd(27)}║`,
468
+ `║ Model: ${((providerName + '/' + (model ?? 'default')).slice(0, 27)).padEnd(27)}║`,
469
+ `║ Skills: ${(String(skillNames.length) + ' loaded').padEnd(26)}║`,
470
+ '║ Type /help for commands ║',
471
+ '╚══════════════════════════════════════╝',
472
+ ];
473
+ console.log('\n' + color.cyan(bannerLines.join('\n')) + '\n');
474
+ if (soulContent)
475
+ console.log(` ${icon.info} Loaded SOUL.md`);
476
+ if (contextContent)
477
+ console.log(` ${icon.info} Loaded CONTEXT.md`);
478
+ if (soulContent || contextContent)
479
+ console.log();
480
+ const rl = readline.createInterface({
481
+ input: process.stdin,
482
+ output: process.stdout,
483
+ historySize: 100,
484
+ });
485
+ const handleSlashCommand = (cmd) => {
486
+ const lower = cmd.toLowerCase().trim();
487
+ if (lower === '/quit' || lower === '/exit') {
488
+ console.log(`\n${color.dim('Goodbye! 👋')}`);
489
+ process.exit(0);
490
+ }
491
+ if (lower === '/help') {
492
+ console.log(`\n ${color.bold('Available commands:')}`);
493
+ console.log(` ${color.cyan('/help')} — Show this help`);
494
+ console.log(` ${color.cyan('/quit')} — Exit chat (/exit also works)`);
495
+ console.log(` ${color.cyan('/clear')} — Clear conversation history`);
496
+ console.log(` ${color.cyan('/skills')} — List registered skills`);
497
+ console.log(` ${color.cyan('/memory')} — Show memory stats`);
498
+ console.log(` ${color.cyan('/info')} — Show agent info\n`);
499
+ return true;
500
+ }
501
+ if (lower === '/clear') {
502
+ history.length = 0;
503
+ console.log(`\n ${icon.success} Conversation history cleared.\n`);
504
+ return true;
505
+ }
506
+ if (lower === '/skills') {
507
+ if (skillNames.length === 0) {
508
+ console.log(`\n ${icon.info} No skills registered.\n`);
509
+ }
510
+ else {
511
+ console.log(`\n ${color.bold('Registered skills:')}`);
512
+ skillNames.forEach((s) => console.log(` • ${color.cyan(s)}`));
513
+ console.log();
514
+ }
515
+ return true;
516
+ }
517
+ if (lower === '/memory') {
518
+ console.log(`\n ${color.bold('Memory stats:')}`);
519
+ console.log(` Messages in history: ${color.cyan(String(history.length))}`);
520
+ console.log(` Characters: ${color.cyan(String(history.reduce((a, m) => a + m.content.length, 0)))}\n`);
521
+ return true;
522
+ }
523
+ if (lower === '/info') {
524
+ console.log(`\n ${color.bold('Agent Info:')}`);
525
+ console.log(` Name: ${color.cyan(agentName)}`);
526
+ console.log(` Version: ${color.cyan(agentVersion)}`);
527
+ console.log(` Provider: ${color.cyan(providerName)}`);
528
+ console.log(` Model: ${color.cyan(model ?? 'default')}`);
529
+ console.log(` Skills: ${color.cyan(String(skillNames.length))}\n`);
530
+ return true;
531
+ }
532
+ return false;
533
+ };
286
534
  const ask = () => {
287
535
  rl.question(color.cyan('You: '), async (input) => {
288
536
  const text = input.trim();
@@ -290,6 +538,11 @@ program
290
538
  ask();
291
539
  return;
292
540
  }
541
+ // Handle slash commands
542
+ if (text.startsWith('/') && handleSlashCommand(text)) {
543
+ ask();
544
+ return;
545
+ }
293
546
  history.push({ role: 'user', content: text });
294
547
  // Build messages for provider
295
548
  const messages = history.map((m) => ({
@@ -321,7 +574,7 @@ program
321
574
  });
322
575
  };
323
576
  rl.on('close', () => {
324
- console.log(`\n${color.dim('Goodbye!')}`);
577
+ console.log(`\n${color.dim('Goodbye! 👋')}`);
325
578
  process.exit(0);
326
579
  });
327
580
  ask();
@@ -684,51 +937,23 @@ kbCmd.command('clear').action(() => {
684
937
  kb.clear();
685
938
  console.log(`${icon.success} Knowledge base cleared.`);
686
939
  });
687
- // 📦 Marketplace commands ───────────────────────────────────
940
+ // 📦 Package commands ───────────────────────────────────
688
941
  program
689
942
  .command('publish')
690
943
  .description('Package agent for distribution')
691
944
  .option('-f, --file <file>', 'OAD file', 'oad.yaml')
692
945
  .option('-o, --output <dir>', 'Output directory', '.')
693
946
  .option('--include-kb', 'Include knowledge base')
694
- .action(async (opts) => {
695
- try {
696
- console.log(`\n${icon.package} Packaging agent...\n`);
697
- const result = await (0, marketplace_1.publishAgent)({
698
- oadPath: opts.file,
699
- outputDir: opts.output,
700
- includeKnowledge: opts.includeKb,
701
- });
702
- console.log(`${icon.success} Published: ${color.bold(result.archivePath)}`);
703
- console.log(` Name: ${result.manifest.name}`);
704
- console.log(` Version: ${result.manifest.version}`);
705
- console.log(` Files: ${result.manifest.files.length}`);
706
- console.log();
707
- }
708
- catch (err) {
709
- console.error(`${icon.error} Publish failed:`, err instanceof Error ? err.message : err);
710
- process.exit(1);
711
- }
947
+ .action(async () => {
948
+ console.log(`\n${icon.package} Agent packaging coming soon.\n`);
712
949
  });
713
950
  program
714
951
  .command('install')
715
952
  .description('Install agent from package')
716
953
  .argument('<source>', 'Package file path or URL')
717
954
  .option('-d, --dir <dir>', 'Install directory')
718
- .action(async (source, opts) => {
719
- try {
720
- console.log(`\n${icon.package} Installing agent from ${color.bold(source)}...\n`);
721
- const result = await (0, marketplace_1.installAgent)({ source, targetDir: opts.dir });
722
- console.log(`${icon.success} Installed: ${color.bold(result.manifest.name)} v${result.manifest.version}`);
723
- console.log(` Directory: ${result.dir}`);
724
- console.log(`\n${color.bold('Next steps:')}`);
725
- console.log(` cd ${result.dir}`);
726
- console.log(` opc run\n`);
727
- }
728
- catch (err) {
729
- console.error(`${icon.error} Install failed:`, err instanceof Error ? err.message : err);
730
- process.exit(1);
731
- }
955
+ .action(async () => {
956
+ console.log(`\n${icon.package} Agent install coming soon.\n`);
732
957
  });
733
958
  // 🔌 Plugin commands ────────────────────────────────────────
734
959
  const pluginCmd = program.command('plugin').description('Manage plugins');
@@ -838,5 +1063,382 @@ program
838
1063
  process.exit(1);
839
1064
  }
840
1065
  });
1066
+ // ── Brain command ────────────────────────────────────────────
1067
+ program
1068
+ .command('brain')
1069
+ .description('Show agent memory/brain status from DeepBrain')
1070
+ .option('--url <url>', 'DeepBrain server URL', 'http://localhost:3333')
1071
+ .action(async (opts) => {
1072
+ console.log(`\n${icon.gear} ${color.bold('DeepBrain Status')} — ${color.dim(opts.url)}\n`);
1073
+ try {
1074
+ const res = await fetch(`${opts.url}/api/stats`);
1075
+ if (!res.ok)
1076
+ throw new Error(`HTTP ${res.status} ${res.statusText}`);
1077
+ const stats = (await res.json());
1078
+ const rows = [
1079
+ ['Total Pages', String(stats.totalPages ?? stats.pages ?? '-')],
1080
+ ['Total Chunks', String(stats.totalChunks ?? stats.chunks ?? '-')],
1081
+ ['Memory Tiers', String(stats.memoryTiers ?? stats.tiers ?? '-')],
1082
+ ['Index Size', stats.indexSize ?? '-'],
1083
+ ['Last Updated', stats.lastUpdated ?? stats.updatedAt ?? '-'],
1084
+ ];
1085
+ const maxKey = Math.max(...rows.map(([k]) => k.length));
1086
+ for (const [key, val] of rows) {
1087
+ console.log(` ${color.cyan(key.padEnd(maxKey))} ${val}`);
1088
+ }
1089
+ console.log();
1090
+ }
1091
+ catch (err) {
1092
+ const msg = err instanceof Error ? err.message : String(err);
1093
+ if (msg.includes('ECONNREFUSED') || msg.includes('fetch failed')) {
1094
+ console.log(` ${icon.warn} Cannot connect to DeepBrain at ${opts.url}`);
1095
+ console.log(` ${color.dim('Is the server running? Start with: deepbrain serve')}\n`);
1096
+ }
1097
+ else {
1098
+ console.error(` ${icon.error} ${msg}\n`);
1099
+ }
1100
+ }
1101
+ });
1102
+ // ── Logs command ─────────────────────────────────────────────
1103
+ program
1104
+ .command('logs')
1105
+ .description('Show recent agent traces')
1106
+ .option('-n, --limit <n>', 'Number of spans to show', '20')
1107
+ .option('-f, --follow', 'Keep watching for new spans')
1108
+ .action(async (opts) => {
1109
+ const { TraceCollector } = await Promise.resolve().then(() => __importStar(require('./traces')));
1110
+ const collector = new TraceCollector();
1111
+ const limit = parseInt(opts.limit) || 20;
1112
+ const printSpans = (spans) => {
1113
+ const slice = spans.slice(-limit);
1114
+ if (slice.length === 0) {
1115
+ console.log(` ${icon.info} No traces yet. Interact with the agent to generate traces.`);
1116
+ return;
1117
+ }
1118
+ for (const span of slice) {
1119
+ const duration = span.endTime
1120
+ ? `${span.endTime.getTime() - span.startTime.getTime()}ms`
1121
+ : 'ongoing';
1122
+ const statusIcon = span.status === 'ok' ? icon.success : span.status === 'error' ? icon.error : color.dim('○');
1123
+ const time = span.startTime.toLocaleTimeString();
1124
+ console.log(` ${statusIcon} ${color.dim(time)} ${color.bold(span.name)} ${color.dim(duration)}`);
1125
+ }
1126
+ };
1127
+ console.log(`\n${icon.gear} ${color.bold('Agent Traces')}\n`);
1128
+ const spans = collector.getBufferedSpans();
1129
+ printSpans(spans);
1130
+ if (opts.follow) {
1131
+ console.log(`\n ${color.dim('Watching for new traces... (Ctrl+C to stop)')}\n`);
1132
+ let lastCount = spans.length;
1133
+ const interval = setInterval(() => {
1134
+ const current = collector.getBufferedSpans();
1135
+ if (current.length > lastCount) {
1136
+ const newSpans = current.slice(lastCount);
1137
+ printSpans(newSpans);
1138
+ lastCount = current.length;
1139
+ }
1140
+ }, 1000);
1141
+ process.on('SIGINT', () => { clearInterval(interval); process.exit(0); });
1142
+ }
1143
+ else {
1144
+ console.log();
1145
+ }
1146
+ });
1147
+ // ── Score command ────────────────────────────────────────────
1148
+ program
1149
+ .command('score')
1150
+ .description('Show agent performance score')
1151
+ .action(async () => {
1152
+ console.log(`\n${icon.gear} ${color.bold('Agent Performance Score')}\n`);
1153
+ try {
1154
+ const engine = new analytics_engine_1.AnalyticsEngine('.');
1155
+ const stats = engine.getStats();
1156
+ if (!stats || stats.totalMessages === 0) {
1157
+ console.log(` ${icon.info} No score data yet. Run the agent first.\n`);
1158
+ return;
1159
+ }
1160
+ const errorRate = stats.totalMessages > 0 ? (stats.totalErrors / stats.totalMessages) : 0;
1161
+ const rows = [
1162
+ ['Total Messages', String(stats.totalMessages)],
1163
+ ['Total LLM Calls', String(stats.totalLLMCalls)],
1164
+ ['Total Tool Uses', String(stats.totalToolUses)],
1165
+ ['Avg Response Time', `${stats.avgResponseTimeMs}ms`],
1166
+ ['Error Rate', `${(errorRate * 100).toFixed(1)}%`],
1167
+ ['Token Usage', `${stats.totalTokens.total} tokens (in: ${stats.totalTokens.input}, out: ${stats.totalTokens.output})`],
1168
+ ];
1169
+ const maxKey = Math.max(...rows.map(([k]) => k.length));
1170
+ for (const [key, val] of rows) {
1171
+ console.log(` ${color.cyan(key.padEnd(maxKey))} ${val}`);
1172
+ }
1173
+ console.log();
1174
+ }
1175
+ catch {
1176
+ console.log(` ${icon.info} No score data yet. Run the agent first.\n`);
1177
+ }
1178
+ });
1179
+ // ── Daemon commands (start/stop/status) ─────────────────────
1180
+ const OPC_DIR = path.resolve('.opc');
1181
+ program
1182
+ .command('start')
1183
+ .description('Start agent as a background daemon')
1184
+ .option('-f, --file <file>', 'OAD file (agent.yaml or oad.yaml)')
1185
+ .action(async () => {
1186
+ if (!fs.existsSync(OPC_DIR))
1187
+ fs.mkdirSync(OPC_DIR, { recursive: true });
1188
+ const pidFile = path.join(OPC_DIR, 'agent.pid');
1189
+ // Check if already running
1190
+ if (fs.existsSync(pidFile)) {
1191
+ const pid = parseInt(fs.readFileSync(pidFile, 'utf-8').trim(), 10);
1192
+ try {
1193
+ process.kill(pid, 0);
1194
+ console.log(`${icon.warn} Agent already running (PID ${pid}).`);
1195
+ return;
1196
+ }
1197
+ catch { /* stale */ }
1198
+ }
1199
+ // Find daemon entry point
1200
+ const daemonScript = path.join(__dirname, 'daemon.js');
1201
+ if (!fs.existsSync(daemonScript)) {
1202
+ console.error(`${icon.error} Daemon script not found. Run ${color.cyan('npm run build')} first.`);
1203
+ process.exit(1);
1204
+ }
1205
+ const logFile = path.join(OPC_DIR, 'agent.log');
1206
+ const out = fs.openSync(logFile, 'a');
1207
+ const err = fs.openSync(logFile, 'a');
1208
+ const child = (0, child_process_1.spawn)(process.execPath, [daemonScript], {
1209
+ detached: true,
1210
+ stdio: ['ignore', out, err],
1211
+ cwd: process.cwd(),
1212
+ env: process.env,
1213
+ });
1214
+ child.unref();
1215
+ // Wait briefly for PID file
1216
+ await new Promise(r => setTimeout(r, 1000));
1217
+ if (fs.existsSync(pidFile)) {
1218
+ const pid = fs.readFileSync(pidFile, 'utf-8').trim();
1219
+ console.log(`${icon.success} Agent started (PID ${pid})`);
1220
+ console.log(` ${color.dim('Logs:')} ${logFile}`);
1221
+ console.log(` ${color.dim('Stop:')} opc stop`);
1222
+ }
1223
+ else {
1224
+ console.log(`${icon.success} Agent starting... (PID ${child.pid})`);
1225
+ console.log(` ${color.dim('Logs:')} ${logFile}`);
1226
+ }
1227
+ });
1228
+ program
1229
+ .command('stop')
1230
+ .description('Stop the background daemon')
1231
+ .action(() => {
1232
+ const pidFile = path.join(OPC_DIR, 'agent.pid');
1233
+ if (!fs.existsSync(pidFile)) {
1234
+ console.log(`${icon.info} No running agent found.`);
1235
+ return;
1236
+ }
1237
+ const pid = parseInt(fs.readFileSync(pidFile, 'utf-8').trim(), 10);
1238
+ try {
1239
+ // On Windows, process.kill with SIGTERM may not work; use taskkill
1240
+ if (process.platform === 'win32') {
1241
+ const { execSync } = require('child_process');
1242
+ try {
1243
+ execSync(`taskkill /PID ${pid} /T /F`, { stdio: 'ignore' });
1244
+ }
1245
+ catch { /* ignore */ }
1246
+ }
1247
+ else {
1248
+ process.kill(pid, 'SIGTERM');
1249
+ }
1250
+ console.log(`${icon.success} Sent stop signal to PID ${pid}`);
1251
+ }
1252
+ catch {
1253
+ console.log(`${icon.warn} Process ${pid} not found (may have already stopped).`);
1254
+ }
1255
+ try {
1256
+ fs.unlinkSync(pidFile);
1257
+ }
1258
+ catch { /* ignore */ }
1259
+ });
1260
+ program
1261
+ .command('status')
1262
+ .description('Check daemon status')
1263
+ .action(() => {
1264
+ const pidFile = path.join(OPC_DIR, 'agent.pid');
1265
+ if (!fs.existsSync(pidFile)) {
1266
+ console.log(`\n Status: ${color.red('stopped')}\n`);
1267
+ return;
1268
+ }
1269
+ const pid = parseInt(fs.readFileSync(pidFile, 'utf-8').trim(), 10);
1270
+ let running = false;
1271
+ try {
1272
+ process.kill(pid, 0);
1273
+ running = true;
1274
+ }
1275
+ catch { /* not running */ }
1276
+ if (!running) {
1277
+ console.log(`\n Status: ${color.red('stopped')} (stale PID file)`);
1278
+ try {
1279
+ fs.unlinkSync(pidFile);
1280
+ }
1281
+ catch { /* ignore */ }
1282
+ console.log();
1283
+ return;
1284
+ }
1285
+ // Uptime
1286
+ const startedFile = path.join(OPC_DIR, 'started');
1287
+ let uptime = '';
1288
+ if (fs.existsSync(startedFile)) {
1289
+ const startedMs = parseInt(fs.readFileSync(startedFile, 'utf-8').trim(), 10);
1290
+ const secs = Math.floor((Date.now() - startedMs) / 1000);
1291
+ const h = Math.floor(secs / 3600);
1292
+ const m = Math.floor((secs % 3600) / 60);
1293
+ const s = secs % 60;
1294
+ uptime = `${h}h ${m}m ${s}s`;
1295
+ }
1296
+ // Agent name from config
1297
+ let agentName = 'unknown';
1298
+ for (const f of ['agent.yaml', 'oad.yaml']) {
1299
+ if (fs.existsSync(f)) {
1300
+ try {
1301
+ const raw = fs.readFileSync(f, 'utf-8');
1302
+ const cfg = yaml.load(raw);
1303
+ if (cfg?.metadata?.name) {
1304
+ agentName = cfg.metadata.name;
1305
+ break;
1306
+ }
1307
+ }
1308
+ catch { /* ignore */ }
1309
+ }
1310
+ }
1311
+ console.log(`\n Status: ${color.green('running')}`);
1312
+ console.log(` PID: ${pid}`);
1313
+ console.log(` Agent: ${color.cyan(agentName)}`);
1314
+ if (uptime)
1315
+ console.log(` Uptime: ${uptime}`);
1316
+ console.log();
1317
+ });
1318
+ // ── Jobs commands ────────────────────────────────────────────
1319
+ const jobsCmd = program.command('jobs').description('Manage scheduled jobs');
1320
+ jobsCmd
1321
+ .command('list', { isDefault: true })
1322
+ .description('List all scheduled jobs')
1323
+ .option('-f, --file <file>', 'OAD file', 'oad.yaml')
1324
+ .action(async (opts) => {
1325
+ const jobs = loadJobsFromConfig(opts.file);
1326
+ if (jobs.length === 0) {
1327
+ console.log(`\n${icon.info} No scheduled jobs defined in config.\n`);
1328
+ return;
1329
+ }
1330
+ console.log(`\n${icon.gear} ${color.bold('Scheduled Jobs')}\n`);
1331
+ for (const job of jobs) {
1332
+ const status = job.enabled ? color.green('enabled') : color.dim('disabled');
1333
+ const next = job.nextRun ? job.nextRun.toLocaleString() : color.dim('N/A');
1334
+ console.log(` ${color.cyan(job.id.padEnd(20))} ${job.name}`);
1335
+ console.log(` ${''.padEnd(20)} Schedule: ${color.dim(job.schedule)} | Status: ${status} | Next: ${next}`);
1336
+ console.log();
1337
+ }
1338
+ });
1339
+ jobsCmd
1340
+ .command('run')
1341
+ .argument('<id>', 'Job ID to run')
1342
+ .option('-f, --file <file>', 'OAD file', 'oad.yaml')
1343
+ .description('Manually trigger a scheduled job')
1344
+ .action(async (id, opts) => {
1345
+ const jobs = loadJobsFromConfig(opts.file);
1346
+ const job = jobs.find(j => j.id === id || j.name === id);
1347
+ if (!job) {
1348
+ console.error(`${icon.error} Job "${id}" not found. Available: ${jobs.map(j => j.id).join(', ')}`);
1349
+ process.exit(1);
1350
+ }
1351
+ console.log(`${icon.info} Running job "${color.bold(job.name)}"...`);
1352
+ console.log(` Task: ${color.dim(job.task)}`);
1353
+ console.log(`\n${icon.warn} Manual job execution requires a running daemon. Use ${color.cyan('opc start')} first.\n`);
1354
+ });
1355
+ function loadJobsFromConfig(file) {
1356
+ try {
1357
+ const raw = fs.readFileSync(file, 'utf-8');
1358
+ const config = yaml.load(raw);
1359
+ const jobConfigs = config?.spec?.scheduler?.jobs ?? [];
1360
+ const { parseCron } = require('./core/scheduler');
1361
+ return jobConfigs.map((j, i) => {
1362
+ const id = j.id || j.name?.toLowerCase().replace(/\s+/g, '-') || `job-${i}`;
1363
+ const parsed = parseCron(j.schedule);
1364
+ // Compute next run
1365
+ const now = new Date();
1366
+ let nextRun;
1367
+ const d = new Date(now);
1368
+ d.setSeconds(0, 0);
1369
+ d.setMinutes(d.getMinutes() + 1);
1370
+ for (let k = 0; k < 48 * 60; k++) {
1371
+ const { cronMatches } = require('./core/scheduler');
1372
+ if (cronMatches(parsed, d)) {
1373
+ nextRun = new Date(d);
1374
+ break;
1375
+ }
1376
+ d.setMinutes(d.getMinutes() + 1);
1377
+ }
1378
+ return {
1379
+ id,
1380
+ name: j.name || id,
1381
+ schedule: j.schedule,
1382
+ task: j.task || '',
1383
+ enabled: j.enabled !== false,
1384
+ nextRun,
1385
+ };
1386
+ });
1387
+ }
1388
+ catch {
1389
+ return [];
1390
+ }
1391
+ }
1392
+ // ── Skills commands ──────────────────────────────────────────
1393
+ const skillsCmd = program.command('skills').description('Manage learned skills');
1394
+ skillsCmd
1395
+ .command('list', { isDefault: true })
1396
+ .description('List all learned skills')
1397
+ .option('-d, --dir <dir>', 'Skills directory', '.opc/skills')
1398
+ .action(async (opts) => {
1399
+ const { SkillLearner } = await Promise.resolve().then(() => __importStar(require('./skills/auto-learn')));
1400
+ const learner = new SkillLearner(opts.dir);
1401
+ const skills = await learner.loadLearnedSkills();
1402
+ if (skills.length === 0) {
1403
+ console.log(`\n${icon.info} No learned skills yet.\n`);
1404
+ console.log(` Skills are auto-created from conversations when learning is enabled.`);
1405
+ console.log(` Directory: ${color.dim(path.resolve(opts.dir))}\n`);
1406
+ return;
1407
+ }
1408
+ console.log(`\n${icon.gear} ${color.bold('Learned Skills')} (${skills.length})\n`);
1409
+ for (const skill of skills) {
1410
+ console.log(` ${color.cyan(skill.name.padEnd(24))} ${skill.description}`);
1411
+ console.log(` ${''.padEnd(24)} v${skill.version} | used ${skill.usageCount}x | trigger: ${color.dim(skill.trigger)}`);
1412
+ console.log();
1413
+ }
1414
+ });
1415
+ skillsCmd
1416
+ .command('show')
1417
+ .argument('<name>', 'Skill name')
1418
+ .option('-d, --dir <dir>', 'Skills directory', '.opc/skills')
1419
+ .description('Show details of a learned skill')
1420
+ .action(async (name, opts) => {
1421
+ const skillPath = path.join(opts.dir, `${name}.md`);
1422
+ if (!fs.existsSync(skillPath)) {
1423
+ console.error(`${icon.error} Skill "${name}" not found at ${skillPath}`);
1424
+ process.exit(1);
1425
+ }
1426
+ const content = fs.readFileSync(skillPath, 'utf-8');
1427
+ console.log(`\n${content}`);
1428
+ });
1429
+ skillsCmd
1430
+ .command('remove')
1431
+ .argument('<name>', 'Skill name')
1432
+ .option('-d, --dir <dir>', 'Skills directory', '.opc/skills')
1433
+ .description('Remove a learned skill')
1434
+ .action(async (name, opts) => {
1435
+ const skillPath = path.join(opts.dir, `${name}.md`);
1436
+ if (!fs.existsSync(skillPath)) {
1437
+ console.error(`${icon.error} Skill "${name}" not found.`);
1438
+ process.exit(1);
1439
+ }
1440
+ fs.unlinkSync(skillPath);
1441
+ console.log(`${icon.success} Removed skill "${color.cyan(name)}".`);
1442
+ });
841
1443
  program.parse();
842
1444
  //# sourceMappingURL=cli.js.map