opc-agent 1.4.0 → 2.0.1

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 (198) hide show
  1. package/CHANGELOG.md +25 -0
  2. package/README.md +91 -32
  3. package/dist/channels/email.d.ts +32 -26
  4. package/dist/channels/email.js +239 -62
  5. package/dist/channels/feishu.d.ts +21 -6
  6. package/dist/channels/feishu.js +225 -126
  7. package/dist/channels/telegram.d.ts +30 -9
  8. package/dist/channels/telegram.js +125 -33
  9. package/dist/channels/websocket.d.ts +46 -3
  10. package/dist/channels/websocket.js +306 -37
  11. package/dist/channels/wechat.d.ts +33 -13
  12. package/dist/channels/wechat.js +229 -42
  13. package/dist/cli.js +1127 -19
  14. package/dist/core/a2a.d.ts +17 -0
  15. package/dist/core/a2a.js +43 -1
  16. package/dist/core/agent.d.ts +39 -0
  17. package/dist/core/agent.js +228 -3
  18. package/dist/core/runtime.d.ts +7 -0
  19. package/dist/core/runtime.js +205 -2
  20. package/dist/core/sandbox.d.ts +26 -0
  21. package/dist/core/sandbox.js +117 -0
  22. package/dist/core/scheduler.d.ts +52 -0
  23. package/dist/core/scheduler.js +168 -0
  24. package/dist/core/subagent.d.ts +28 -0
  25. package/dist/core/subagent.js +65 -0
  26. package/dist/core/workflow-graph.d.ts +93 -0
  27. package/dist/core/workflow-graph.js +247 -0
  28. package/dist/daemon.d.ts +3 -0
  29. package/dist/daemon.js +134 -0
  30. package/dist/doctor.d.ts +15 -0
  31. package/dist/doctor.js +183 -0
  32. package/dist/eval/index.d.ts +65 -0
  33. package/dist/eval/index.js +191 -0
  34. package/dist/index.d.ts +37 -6
  35. package/dist/index.js +75 -3
  36. package/dist/plugins/content-filter.d.ts +7 -0
  37. package/dist/plugins/content-filter.js +25 -0
  38. package/dist/plugins/index.d.ts +42 -0
  39. package/dist/plugins/index.js +108 -2
  40. package/dist/plugins/logger.d.ts +6 -0
  41. package/dist/plugins/logger.js +20 -0
  42. package/dist/plugins/rate-limiter.d.ts +7 -0
  43. package/dist/plugins/rate-limiter.js +35 -0
  44. package/dist/protocols/a2a/client.d.ts +25 -0
  45. package/dist/protocols/a2a/client.js +115 -0
  46. package/dist/protocols/a2a/index.d.ts +6 -0
  47. package/dist/protocols/a2a/index.js +12 -0
  48. package/dist/protocols/a2a/server.d.ts +41 -0
  49. package/dist/protocols/a2a/server.js +295 -0
  50. package/dist/protocols/a2a/types.d.ts +91 -0
  51. package/dist/protocols/a2a/types.js +15 -0
  52. package/dist/protocols/a2a/utils.d.ts +6 -0
  53. package/dist/protocols/a2a/utils.js +47 -0
  54. package/dist/protocols/agui/client.d.ts +10 -0
  55. package/dist/protocols/agui/client.js +75 -0
  56. package/dist/protocols/agui/index.d.ts +4 -0
  57. package/dist/protocols/agui/index.js +25 -0
  58. package/dist/protocols/agui/server.d.ts +37 -0
  59. package/dist/protocols/agui/server.js +191 -0
  60. package/dist/protocols/agui/types.d.ts +107 -0
  61. package/dist/protocols/agui/types.js +17 -0
  62. package/dist/protocols/index.d.ts +2 -0
  63. package/dist/protocols/index.js +19 -0
  64. package/dist/protocols/mcp/agent-tools.d.ts +11 -0
  65. package/dist/protocols/mcp/agent-tools.js +129 -0
  66. package/dist/protocols/mcp/index.d.ts +5 -0
  67. package/dist/protocols/mcp/index.js +11 -0
  68. package/dist/protocols/mcp/server.d.ts +31 -0
  69. package/dist/protocols/mcp/server.js +248 -0
  70. package/dist/protocols/mcp/types.d.ts +92 -0
  71. package/dist/protocols/mcp/types.js +17 -0
  72. package/dist/providers/index.d.ts +5 -1
  73. package/dist/providers/index.js +16 -9
  74. package/dist/publish/index.d.ts +45 -0
  75. package/dist/publish/index.js +350 -0
  76. package/dist/schema/oad.d.ts +859 -67
  77. package/dist/schema/oad.js +47 -3
  78. package/dist/security/approval.d.ts +36 -0
  79. package/dist/security/approval.js +113 -0
  80. package/dist/security/index.d.ts +4 -0
  81. package/dist/security/index.js +8 -0
  82. package/dist/security/keys.d.ts +16 -0
  83. package/dist/security/keys.js +117 -0
  84. package/dist/skills/auto-learn.d.ts +28 -0
  85. package/dist/skills/auto-learn.js +257 -0
  86. package/dist/studio/server.d.ts +63 -0
  87. package/dist/studio/server.js +625 -0
  88. package/dist/studio-ui/index.html +662 -0
  89. package/dist/telemetry/index.d.ts +93 -0
  90. package/dist/telemetry/index.js +285 -0
  91. package/dist/tools/builtin/datetime.d.ts +3 -0
  92. package/dist/tools/builtin/datetime.js +44 -0
  93. package/dist/tools/builtin/file.d.ts +3 -0
  94. package/dist/tools/builtin/file.js +151 -0
  95. package/dist/tools/builtin/index.d.ts +15 -0
  96. package/dist/tools/builtin/index.js +30 -0
  97. package/dist/tools/builtin/shell.d.ts +3 -0
  98. package/dist/tools/builtin/shell.js +43 -0
  99. package/dist/tools/builtin/web.d.ts +3 -0
  100. package/dist/tools/builtin/web.js +37 -0
  101. package/dist/tools/mcp-client.d.ts +24 -0
  102. package/dist/tools/mcp-client.js +119 -0
  103. package/package.json +5 -3
  104. package/scripts/install.ps1 +31 -0
  105. package/scripts/install.sh +40 -0
  106. package/src/channels/email.ts +351 -177
  107. package/src/channels/feishu.ts +349 -236
  108. package/src/channels/telegram.ts +212 -90
  109. package/src/channels/websocket.ts +399 -87
  110. package/src/channels/wechat.ts +329 -149
  111. package/src/cli.ts +1201 -20
  112. package/src/core/a2a.ts +60 -0
  113. package/src/core/agent.ts +420 -152
  114. package/src/core/runtime.ts +174 -0
  115. package/src/core/sandbox.ts +143 -0
  116. package/src/core/scheduler.ts +187 -0
  117. package/src/core/subagent.ts +98 -0
  118. package/src/core/workflow-graph.ts +365 -0
  119. package/src/daemon.ts +96 -0
  120. package/src/doctor.ts +156 -0
  121. package/src/eval/index.ts +211 -0
  122. package/src/eval/suites/basic.json +16 -0
  123. package/src/eval/suites/memory.json +12 -0
  124. package/src/eval/suites/safety.json +14 -0
  125. package/src/index.ts +65 -6
  126. package/src/plugins/content-filter.ts +23 -0
  127. package/src/plugins/index.ts +133 -2
  128. package/src/plugins/logger.ts +18 -0
  129. package/src/plugins/rate-limiter.ts +38 -0
  130. package/src/protocols/a2a/client.ts +132 -0
  131. package/src/protocols/a2a/index.ts +8 -0
  132. package/src/protocols/a2a/server.ts +333 -0
  133. package/src/protocols/a2a/types.ts +88 -0
  134. package/src/protocols/a2a/utils.ts +50 -0
  135. package/src/protocols/agui/client.ts +83 -0
  136. package/src/protocols/agui/index.ts +4 -0
  137. package/src/protocols/agui/server.ts +218 -0
  138. package/src/protocols/agui/types.ts +153 -0
  139. package/src/protocols/index.ts +2 -0
  140. package/src/protocols/mcp/agent-tools.ts +134 -0
  141. package/src/protocols/mcp/index.ts +8 -0
  142. package/src/protocols/mcp/server.ts +262 -0
  143. package/src/protocols/mcp/types.ts +69 -0
  144. package/src/providers/index.ts +354 -339
  145. package/src/publish/index.ts +376 -0
  146. package/src/schema/oad.ts +204 -154
  147. package/src/security/approval.ts +131 -0
  148. package/src/security/index.ts +3 -0
  149. package/src/security/keys.ts +87 -0
  150. package/src/skills/auto-learn.ts +262 -0
  151. package/src/studio/server.ts +629 -0
  152. package/src/studio-ui/index.html +662 -0
  153. package/src/telemetry/index.ts +324 -0
  154. package/src/tools/builtin/datetime.ts +41 -0
  155. package/src/tools/builtin/file.ts +107 -0
  156. package/src/tools/builtin/index.ts +28 -0
  157. package/src/tools/builtin/shell.ts +43 -0
  158. package/src/tools/builtin/web.ts +35 -0
  159. package/src/tools/mcp-client.ts +131 -0
  160. package/src/types/agent-workstation.d.ts +2 -0
  161. package/tests/a2a-protocol.test.ts +285 -0
  162. package/tests/agui-protocol.test.ts +246 -0
  163. package/tests/auto-learn.test.ts +105 -0
  164. package/tests/builtin-tools.test.ts +83 -0
  165. package/tests/channels/discord.test.ts +79 -0
  166. package/tests/channels/email.test.ts +148 -0
  167. package/tests/channels/feishu.test.ts +123 -0
  168. package/tests/channels/telegram.test.ts +129 -0
  169. package/tests/channels/websocket.test.ts +53 -0
  170. package/tests/channels/wechat.test.ts +170 -0
  171. package/tests/chat-cli.test.ts +160 -0
  172. package/tests/cli.test.ts +46 -0
  173. package/tests/daemon.test.ts +135 -0
  174. package/tests/deepbrain-wire.test.ts +234 -0
  175. package/tests/doctor.test.ts +38 -0
  176. package/tests/eval.test.ts +173 -0
  177. package/tests/init-role.test.ts +124 -0
  178. package/tests/mcp-client.test.ts +92 -0
  179. package/tests/mcp-server.test.ts +178 -0
  180. package/tests/plugin-a2a-enhanced.test.ts +230 -0
  181. package/tests/publish.test.ts +231 -0
  182. package/tests/scheduler.test.ts +200 -0
  183. package/tests/security-enhanced.test.ts +233 -0
  184. package/tests/skill-learner.test.ts +161 -0
  185. package/tests/studio.test.ts +229 -0
  186. package/tests/subagent.test.ts +193 -0
  187. package/tests/telegram-discord.test.ts +60 -0
  188. package/tests/telemetry.test.ts +186 -0
  189. package/tests/tools/builtin-extended.test.ts +138 -0
  190. package/tests/workflow-graph.test.ts +279 -0
  191. package/tutorial/customer-service-agent/README.md +612 -0
  192. package/tutorial/customer-service-agent/SOUL.md +26 -0
  193. package/tutorial/customer-service-agent/agent.yaml +63 -0
  194. package/tutorial/customer-service-agent/package.json +19 -0
  195. package/tutorial/customer-service-agent/src/index.ts +69 -0
  196. package/tutorial/customer-service-agent/src/skills/faq.ts +27 -0
  197. package/tutorial/customer-service-agent/src/skills/ticket.ts +22 -0
  198. package/tutorial/customer-service-agent/tsconfig.json +14 -0
package/dist/cli.js CHANGED
@@ -61,6 +61,9 @@ 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 doctor_1 = require("./doctor");
65
+ const child_process_1 = require("child_process");
66
+ const agent_workstation_1 = require("agent-workstation");
64
67
  const program = new commander_1.Command();
65
68
  const color = {
66
69
  green: (s) => `\x1b[32m${s}\x1b[0m`,
@@ -118,7 +121,7 @@ async function select(question, options) {
118
121
  program
119
122
  .name('opc')
120
123
  .description('OPC Agent - Open Agent Framework for business workstations')
121
- .version('1.4.0');
124
+ .version('2.0.0');
122
125
  // ── Init command ─────────────────────────────────────────────
123
126
  program
124
127
  .command('init')
@@ -126,8 +129,180 @@ program
126
129
  .argument('[name]', 'Project name')
127
130
  .option('-t, --template <template>', 'Template to use')
128
131
  .option('-y, --yes', 'Skip prompts, use defaults')
132
+ .option('-r, --role <role>', 'Use an agent-workstation role template')
133
+ .option('--list-roles', 'List available workstation roles')
129
134
  .action(async (nameArg, opts) => {
130
135
  console.log(`\n${icon.rocket} ${color.bold('OPC Agent - Create New Project')}\n`);
136
+ // Handle --list-roles
137
+ if (opts.listRoles) {
138
+ const roles = (0, agent_workstation_1.getPopularRoles)();
139
+ console.log(`📋 ${color.bold('Available workstation roles:')}\n`);
140
+ for (const r of roles) {
141
+ const fullRole = (0, agent_workstation_1.getRole)(r.category, r.role);
142
+ let roleName = r.role;
143
+ if (fullRole?.files?.['oad.yaml']) {
144
+ try {
145
+ const oadData = yaml.load(fullRole.files['oad.yaml']);
146
+ if (oadData?.name)
147
+ roleName = oadData.name;
148
+ }
149
+ catch { /* ignore */ }
150
+ }
151
+ console.log(` ${color.cyan((r.category + '/' + r.role).padEnd(45))} ${roleName}`);
152
+ }
153
+ console.log(`\n Use: ${color.cyan('opc init my-agent --role <role-name>')}`);
154
+ console.log(` Example: ${color.cyan('opc init my-agent --role customer-service')}\n`);
155
+ return;
156
+ }
157
+ // Handle --role: search and generate from workstation template
158
+ if (opts.role) {
159
+ const results = (0, agent_workstation_1.searchRoles)(opts.role);
160
+ if (results.length === 0) {
161
+ console.error(`${icon.error} Role "${color.bold(opts.role)}" not found. Run '${color.cyan('opc init --list-roles')}' to see available roles.`);
162
+ process.exit(1);
163
+ }
164
+ const matched = results[0];
165
+ const roleData = (0, agent_workstation_1.getRole)(matched.category, matched.role);
166
+ if (!roleData || !roleData.files) {
167
+ console.error(`${icon.error} Could not load role data for ${matched.category}/${matched.role}.`);
168
+ process.exit(1);
169
+ }
170
+ const name = nameArg ?? matched.role;
171
+ const dir = path.resolve(name);
172
+ if (fs.existsSync(dir)) {
173
+ console.error(`\n${icon.error} Directory ${color.bold(name)} already exists.`);
174
+ process.exit(1);
175
+ }
176
+ // Parse role metadata from oad.yaml
177
+ let roleMeta = {};
178
+ if (roleData.files['oad.yaml']) {
179
+ try {
180
+ roleMeta = yaml.load(roleData.files['oad.yaml']);
181
+ }
182
+ catch { /* ignore */ }
183
+ }
184
+ const roleDisplayName = roleMeta.name || matched.role;
185
+ const roleDescription = roleMeta.name_zh ? `${roleMeta.name} (${roleMeta.name_zh})` : (roleMeta.name || matched.role);
186
+ console.log(` ${icon.info} Matched role: ${color.cyan(matched.category + '/' + matched.role)} — ${roleDisplayName}`);
187
+ // Create directories
188
+ fs.mkdirSync(dir, { recursive: true });
189
+ fs.mkdirSync(path.join(dir, 'src', 'skills'), { recursive: true });
190
+ fs.mkdirSync(path.join(dir, 'data'), { recursive: true });
191
+ // Get system prompt content
192
+ const systemPromptContent = roleData.files['system-prompt.md'] || roleData.files['prompts/system.md'] || '';
193
+ // agent.yaml with role system prompt
194
+ const firstLine = systemPromptContent.split('\n').find((l) => l.trim() && !l.startsWith('#'))?.trim() || 'You are a helpful AI assistant.';
195
+ fs.writeFileSync(path.join(dir, 'agent.yaml'), `apiVersion: opc/v1
196
+ kind: Agent
197
+ metadata:
198
+ name: ${name}
199
+ version: 1.0.0
200
+ description: ${roleDescription}
201
+ spec:
202
+ model: qwen2.5
203
+ provider:
204
+ default: ollama
205
+ systemPrompt: |
206
+ ${systemPromptContent.split('\n').join('\n ')}
207
+ channels:
208
+ - type: web
209
+ port: 3000
210
+ memory:
211
+ shortTerm: true
212
+ longTerm:
213
+ provider: deepbrain
214
+ database: ./data/brain.db
215
+ skills: []
216
+ `);
217
+ // SOUL.md from system-prompt.md
218
+ fs.writeFileSync(path.join(dir, 'SOUL.md'), systemPromptContent);
219
+ // CONTEXT.md
220
+ const readmeContent = roleData.files['README.md'] || '';
221
+ fs.writeFileSync(path.join(dir, 'CONTEXT.md'), `# Project Context\n\n## Role: ${roleDisplayName}\n\n${readmeContent}\n`);
222
+ // data/brain-seed.md if available
223
+ if (roleData.files['brain-seed.md']) {
224
+ fs.writeFileSync(path.join(dir, 'data', 'brain-seed.md'), roleData.files['brain-seed.md']);
225
+ }
226
+ // oad.yaml from role
227
+ if (roleData.files['oad.yaml']) {
228
+ fs.writeFileSync(path.join(dir, 'oad.yaml'), roleData.files['oad.yaml']);
229
+ }
230
+ // src/index.ts — entry point (same as generic)
231
+ fs.writeFileSync(path.join(dir, 'src', 'index.ts'), `import { AgentRuntime } from 'opc-agent';
232
+ import { EchoSkill } from './skills/echo';
233
+ import { readFileSync, existsSync } from 'fs';
234
+
235
+ async function main() {
236
+ const runtime = new AgentRuntime();
237
+ const config = await runtime.loadConfig('./agent.yaml');
238
+
239
+ const soul = existsSync('./SOUL.md') ? readFileSync('./SOUL.md', 'utf-8') : '';
240
+ const context = existsSync('./CONTEXT.md') ? readFileSync('./CONTEXT.md', 'utf-8') : '';
241
+ if (soul || context) {
242
+ const fullPrompt = [soul, context, config.spec.systemPrompt].filter(Boolean).join('\\n\\n');
243
+ config.spec.systemPrompt = fullPrompt;
244
+ }
245
+
246
+ const agent = await runtime.initialize(config);
247
+ runtime.registerSkill(new EchoSkill());
248
+ await runtime.start();
249
+
250
+ console.log('🤖 Agent is running!');
251
+ console.log(' Web UI: http://localhost:3000');
252
+ console.log(' Press Ctrl+C to stop');
253
+ }
254
+
255
+ main().catch(console.error);
256
+ `);
257
+ // src/skills/echo.ts
258
+ fs.writeFileSync(path.join(dir, 'src', 'skills', 'echo.ts'), `import { BaseSkill } from 'opc-agent';
259
+ import type { AgentContext, Message, SkillResult } from 'opc-agent';
260
+
261
+ export class EchoSkill extends BaseSkill {
262
+ name = 'echo';
263
+ description = 'Echo back the message (test skill)';
264
+
265
+ async execute(context: AgentContext, message: Message): Promise<SkillResult> {
266
+ if (message.content.toLowerCase().startsWith('/echo ')) {
267
+ const text = message.content.slice(6);
268
+ return this.match(\`🔊 Echo: \${text}\`);
269
+ }
270
+ return this.noMatch();
271
+ }
272
+ }
273
+ `);
274
+ // tsconfig.json
275
+ fs.writeFileSync(path.join(dir, 'tsconfig.json'), JSON.stringify({
276
+ compilerOptions: { target: 'ES2022', module: 'commonjs', lib: ['ES2022'], outDir: 'dist', rootDir: 'src', strict: true, esModuleInterop: true, skipLibCheck: true, forceConsistentCasingInFileNames: true, resolveJsonModule: true, declaration: true, sourceMap: true },
277
+ include: ['src/**/*'],
278
+ exclude: ['node_modules', 'dist'],
279
+ }, null, 2));
280
+ // package.json
281
+ fs.writeFileSync(path.join(dir, 'package.json'), JSON.stringify({ name, version: '1.0.0', private: true, scripts: { start: 'opc run', dev: 'opc dev', chat: 'opc chat', build: 'tsc' }, dependencies: { 'opc-agent': '^1.3.0' }, devDependencies: { typescript: '^5.5.0', tsx: '^4.0.0' } }, null, 2));
282
+ // .gitignore, .env.example, .env
283
+ fs.writeFileSync(path.join(dir, '.gitignore'), 'node_modules\ndist\n.env\n.opc-knowledge.json\ndata/\n');
284
+ fs.writeFileSync(path.join(dir, '.env.example'), `# LLM API Configuration\nOPC_LLM_API_KEY=your-api-key-here\nOPC_LLM_BASE_URL=https://api.openai.com/v1\nOPC_LLM_MODEL=gpt-4o-mini\n`);
285
+ fs.writeFileSync(path.join(dir, '.env'), `OPC_LLM_API_KEY=your-api-key-here\nOPC_LLM_BASE_URL=https://api.openai.com/v1\nOPC_LLM_MODEL=gpt-4o-mini\n`);
286
+ // README.md
287
+ fs.writeFileSync(path.join(dir, 'README.md'), `# ${name}\n\nCreated with [OPC Agent](https://github.com/Deepleaper/opc-agent) using the \`${matched.category}/${matched.role}\` workstation role.\n\n## Quick Start\n\n\`\`\`bash\nnpm install\nollama pull qwen2.5\nnpx tsx src/index.ts\n\`\`\`\n\nOpen [http://localhost:3000](http://localhost:3000)\n`);
288
+ // Dockerfile + docker-compose
289
+ fs.writeFileSync(path.join(dir, 'Dockerfile'), `FROM node:22-alpine\nWORKDIR /app\nCOPY package.json package-lock.json* ./\nRUN npm ci --production 2>/dev/null || npm install --production\nCOPY oad.yaml agent.yaml .env* ./\nCOPY src/ ./src/\nCOPY prompts/ ./prompts/ 2>/dev/null || true\nEXPOSE 3000\nCMD ["npx", "opc", "run"]\n`);
290
+ fs.writeFileSync(path.join(dir, 'docker-compose.yml'), `version: '3.8'\nservices:\n agent:\n build: .\n ports:\n - "3000:3000"\n env_file:\n - .env\n volumes:\n - ./agent.yaml:/app/agent.yaml:ro\n restart: unless-stopped\n`);
291
+ console.log(`\n${icon.success} Created agent project: ${color.bold(name + '/')} from role ${color.cyan(matched.category + '/' + matched.role)}`);
292
+ console.log(` ${icon.file} agent.yaml - Agent definition with role system prompt`);
293
+ console.log(` ${icon.file} SOUL.md - Role personality (${systemPromptContent.split('\n').length} lines)`);
294
+ console.log(` ${icon.file} CONTEXT.md - Role context & documentation`);
295
+ if (roleData.files['brain-seed.md']) {
296
+ console.log(` ${icon.file} data/brain-seed.md - Role brain seed knowledge`);
297
+ }
298
+ console.log(` ${icon.file} src/index.ts - Entry point`);
299
+ console.log(` ${icon.file} package.json - Dependencies`);
300
+ console.log(`\n${color.bold('Next steps:')}`);
301
+ console.log(` 1. cd ${name}`);
302
+ console.log(` 2. npm install`);
303
+ console.log(` 3. npx tsx src/index.ts\n`);
304
+ return;
305
+ }
131
306
  const name = opts.yes ? (nameArg ?? 'my-agent') : (nameArg ?? await promptUser('Project name', 'my-agent'));
132
307
  const template = opts.yes
133
308
  ? (opts.template ?? 'customer-service')
@@ -175,15 +350,24 @@ spec:
175
350
  // src/index.ts — entry point
176
351
  fs.writeFileSync(path.join(dir, 'src', 'index.ts'), `import { AgentRuntime } from 'opc-agent';
177
352
  import { EchoSkill } from './skills/echo';
353
+ import { readFileSync, existsSync } from 'fs';
178
354
 
179
355
  async function main() {
180
356
  const runtime = new AgentRuntime();
181
357
 
182
358
  // Load OAD config
183
- await runtime.loadConfig('./agent.yaml');
359
+ const config = await runtime.loadConfig('./agent.yaml');
360
+
361
+ // Load personality and context files
362
+ const soul = existsSync('./SOUL.md') ? readFileSync('./SOUL.md', 'utf-8') : '';
363
+ const context = existsSync('./CONTEXT.md') ? readFileSync('./CONTEXT.md', 'utf-8') : '';
364
+ if (soul || context) {
365
+ const fullPrompt = [soul, context, config.spec.systemPrompt].filter(Boolean).join('\\n\\n');
366
+ config.spec.systemPrompt = fullPrompt;
367
+ }
184
368
 
185
369
  // Initialize agent with channels, memory, etc.
186
- const agent = await runtime.initialize();
370
+ const agent = await runtime.initialize(config);
187
371
 
188
372
  // Register custom skills
189
373
  runtime.registerSkill(new EchoSkill());
@@ -349,11 +533,52 @@ ${name}/
349
533
  ## Configuration
350
534
 
351
535
  Edit \`agent.yaml\` to customize your agent's personality, skills, and behavior.
536
+ `);
537
+ // SOUL.md — agent personality
538
+ const createdDate = new Date().toISOString().split('T')[0];
539
+ fs.writeFileSync(path.join(dir, 'SOUL.md'), `# ${name} Personality
540
+
541
+ ## Identity
542
+ - Name: ${name}
543
+ - Role: AI Assistant
544
+ - Created: ${createdDate}
545
+
546
+ ## Personality Traits
547
+ - Helpful and professional
548
+ - Concise but thorough
549
+ - Friendly tone
550
+
551
+ ## Communication Style
552
+ - Use clear, simple language
553
+ - Be direct — answer the question first, then explain
554
+ - Use markdown formatting when helpful
555
+
556
+ ## Rules
557
+ - Always be honest about limitations
558
+ - Ask for clarification when the request is ambiguous
559
+ - Never make up information
560
+ `);
561
+ // CONTEXT.md — project context
562
+ fs.writeFileSync(path.join(dir, 'CONTEXT.md'), `# Project Context
563
+
564
+ ## About This Agent
565
+ ${name} is an AI agent built with OPC Agent Framework.
566
+
567
+ ## Knowledge Base
568
+ Add project-specific context here. The agent reads this file
569
+ on startup to understand the project context.
570
+
571
+ ## Important Notes
572
+ - Add domain knowledge here
573
+ - Add FAQ items here
574
+ - Add company policies here
352
575
  `);
353
576
  console.log(`\n${icon.success} Created agent project: ${color.bold(name + '/')}`);
354
577
  console.log(` ${icon.file} agent.yaml - Agent definition (OAD)`);
355
578
  console.log(` ${icon.file} src/index.ts - Entry point`);
356
579
  console.log(` ${icon.file} src/skills/echo.ts - Example skill`);
580
+ console.log(` ${icon.file} SOUL.md - Agent personality`);
581
+ console.log(` ${icon.file} CONTEXT.md - Project context`);
357
582
  console.log(` ${icon.file} package.json - Dependencies`);
358
583
  console.log(` ${icon.file} tsconfig.json - TypeScript config`);
359
584
  console.log(` ${icon.file} .env.example - Environment template`);
@@ -366,6 +591,9 @@ Edit \`agent.yaml\` to customize your agent's personality, skills, and behavior.
366
591
  console.log(` 2. npm install`);
367
592
  console.log(` 3. npx tsx src/index.ts ${color.dim('# or: npx opc run')}`);
368
593
  console.log(` 4. Open http://localhost:3000\n`);
594
+ console.log(`${color.dim('💡 Tip: Use --role to start from a workstation template:')}`);
595
+ console.log(`${color.dim(' opc init my-agent --role customer-service')}`);
596
+ console.log(`${color.dim(' opc init --list-roles (see all roles)')}\n`);
369
597
  });
370
598
  // ── Chat command ─────────────────────────────────────────────
371
599
  program
@@ -377,6 +605,15 @@ program
377
605
  loadDotEnv();
378
606
  let systemPrompt = 'You are a helpful AI agent.';
379
607
  let model;
608
+ let agentName = 'Agent';
609
+ let agentVersion = '1.0.0';
610
+ let providerName = 'openai';
611
+ let skillNames = [];
612
+ // Try loading SOUL.md and CONTEXT.md for enriched system prompt
613
+ const soulPath = path.resolve('SOUL.md');
614
+ const contextPath = path.resolve('CONTEXT.md');
615
+ const soulContent = fs.existsSync(soulPath) ? fs.readFileSync(soulPath, 'utf-8') : '';
616
+ const contextContent = fs.existsSync(contextPath) ? fs.readFileSync(contextPath, 'utf-8') : '';
380
617
  try {
381
618
  const raw = fs.readFileSync(opts.file, 'utf-8');
382
619
  const config = yaml.load(raw);
@@ -384,15 +621,93 @@ program
384
621
  systemPrompt = config.spec.systemPrompt;
385
622
  if (config?.spec?.model)
386
623
  model = config.spec.model;
387
- console.log(`\n${icon.gear} Loaded agent: ${color.bold(config?.metadata?.name ?? 'unknown')}`);
624
+ if (config?.metadata?.name)
625
+ agentName = config.metadata.name;
626
+ if (config?.metadata?.version)
627
+ agentVersion = config.metadata.version;
628
+ if (config?.spec?.provider?.default)
629
+ providerName = config.spec.provider.default;
630
+ if (config?.spec?.skills)
631
+ skillNames = config.spec.skills.map((s) => s.name);
388
632
  }
389
633
  catch {
390
- console.log(`\n${icon.info} No oad.yaml found, using defaults.`);
634
+ // No config file, use defaults
391
635
  }
636
+ // Prepend SOUL.md and CONTEXT.md to system prompt
637
+ systemPrompt = [soulContent, contextContent, systemPrompt].filter(Boolean).join('\n\n');
392
638
  const provider = (0, providers_1.createProvider)('openai', model);
393
639
  const history = [];
394
- console.log(`${color.dim('Type your message. Press Ctrl+C to exit.')}\n`);
395
- const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
640
+ // Print startup banner
641
+ const bannerLines = [
642
+ '╔══════════════════════════════════════╗',
643
+ '║ 🤖 OPC Agent — Interactive Chat ║',
644
+ `║ Agent: ${(agentName + ' v' + agentVersion).padEnd(27)}║`,
645
+ `║ Model: ${((providerName + '/' + (model ?? 'default')).slice(0, 27)).padEnd(27)}║`,
646
+ `║ Skills: ${(String(skillNames.length) + ' loaded').padEnd(26)}║`,
647
+ '║ Type /help for commands ║',
648
+ '╚══════════════════════════════════════╝',
649
+ ];
650
+ console.log('\n' + color.cyan(bannerLines.join('\n')) + '\n');
651
+ if (soulContent)
652
+ console.log(` ${icon.info} Loaded SOUL.md`);
653
+ if (contextContent)
654
+ console.log(` ${icon.info} Loaded CONTEXT.md`);
655
+ if (soulContent || contextContent)
656
+ console.log();
657
+ const rl = readline.createInterface({
658
+ input: process.stdin,
659
+ output: process.stdout,
660
+ historySize: 100,
661
+ });
662
+ const handleSlashCommand = (cmd) => {
663
+ const lower = cmd.toLowerCase().trim();
664
+ if (lower === '/quit' || lower === '/exit') {
665
+ console.log(`\n${color.dim('Goodbye! 👋')}`);
666
+ process.exit(0);
667
+ }
668
+ if (lower === '/help') {
669
+ console.log(`\n ${color.bold('Available commands:')}`);
670
+ console.log(` ${color.cyan('/help')} — Show this help`);
671
+ console.log(` ${color.cyan('/quit')} — Exit chat (/exit also works)`);
672
+ console.log(` ${color.cyan('/clear')} — Clear conversation history`);
673
+ console.log(` ${color.cyan('/skills')} — List registered skills`);
674
+ console.log(` ${color.cyan('/memory')} — Show memory stats`);
675
+ console.log(` ${color.cyan('/info')} — Show agent info\n`);
676
+ return true;
677
+ }
678
+ if (lower === '/clear') {
679
+ history.length = 0;
680
+ console.log(`\n ${icon.success} Conversation history cleared.\n`);
681
+ return true;
682
+ }
683
+ if (lower === '/skills') {
684
+ if (skillNames.length === 0) {
685
+ console.log(`\n ${icon.info} No skills registered.\n`);
686
+ }
687
+ else {
688
+ console.log(`\n ${color.bold('Registered skills:')}`);
689
+ skillNames.forEach((s) => console.log(` • ${color.cyan(s)}`));
690
+ console.log();
691
+ }
692
+ return true;
693
+ }
694
+ if (lower === '/memory') {
695
+ console.log(`\n ${color.bold('Memory stats:')}`);
696
+ console.log(` Messages in history: ${color.cyan(String(history.length))}`);
697
+ console.log(` Characters: ${color.cyan(String(history.reduce((a, m) => a + m.content.length, 0)))}\n`);
698
+ return true;
699
+ }
700
+ if (lower === '/info') {
701
+ console.log(`\n ${color.bold('Agent Info:')}`);
702
+ console.log(` Name: ${color.cyan(agentName)}`);
703
+ console.log(` Version: ${color.cyan(agentVersion)}`);
704
+ console.log(` Provider: ${color.cyan(providerName)}`);
705
+ console.log(` Model: ${color.cyan(model ?? 'default')}`);
706
+ console.log(` Skills: ${color.cyan(String(skillNames.length))}\n`);
707
+ return true;
708
+ }
709
+ return false;
710
+ };
396
711
  const ask = () => {
397
712
  rl.question(color.cyan('You: '), async (input) => {
398
713
  const text = input.trim();
@@ -400,6 +715,11 @@ program
400
715
  ask();
401
716
  return;
402
717
  }
718
+ // Handle slash commands
719
+ if (text.startsWith('/') && handleSlashCommand(text)) {
720
+ ask();
721
+ return;
722
+ }
403
723
  history.push({ role: 'user', content: text });
404
724
  // Build messages for provider
405
725
  const messages = history.map((m) => ({
@@ -431,7 +751,7 @@ program
431
751
  });
432
752
  };
433
753
  rl.on('close', () => {
434
- console.log(`\n${color.dim('Goodbye!')}`);
754
+ console.log(`\n${color.dim('Goodbye! 👋')}`);
435
755
  process.exit(0);
436
756
  });
437
757
  ask();
@@ -472,6 +792,33 @@ program
472
792
  console.log(` Model: ${s.model}`);
473
793
  console.log(` Channels: ${s.channels.map((c) => c.type).join(', ') || color.dim('(none)')}`);
474
794
  console.log(` Skills: ${s.skills.map((sk) => sk.name).join(', ') || color.dim('(none)')}`);
795
+ // Memory info
796
+ const memCfg = s.memory;
797
+ const shortTermStatus = memCfg?.shortTerm !== false ? '✅' : '❌';
798
+ console.log(`\n ${color.bold('Memory:')}`);
799
+ console.log(` Short-term: ${shortTermStatus} InMemoryStore`);
800
+ if (memCfg && typeof memCfg.longTerm === 'object' && memCfg.longTerm.provider === 'deepbrain') {
801
+ const ltCfg = memCfg.longTerm.config ?? {};
802
+ const dbPath = ltCfg.database || './data/brain.db';
803
+ const autoLearn = ltCfg.autoLearn !== false ? '✅' : '❌';
804
+ const autoRecall = ltCfg.autoRecall !== false ? '✅' : '❌';
805
+ const evolveInterval = ltCfg.evolveInterval;
806
+ console.log(` Long-term: ✅ DeepBrain (${dbPath})`);
807
+ console.log(` Auto-learn: ${autoLearn}`);
808
+ console.log(` Auto-recall: ${autoRecall}`);
809
+ if (evolveInterval && evolveInterval > 0) {
810
+ const hours = Math.floor(evolveInterval / 3600000);
811
+ const mins = Math.floor((evolveInterval % 3600000) / 60000);
812
+ const label = hours > 0 ? `every ${hours}h${mins > 0 ? ` ${mins}m` : ''}` : `every ${mins}m`;
813
+ console.log(` Auto-evolve: ${label}`);
814
+ }
815
+ else {
816
+ console.log(` Auto-evolve: ❌ disabled`);
817
+ }
818
+ }
819
+ else {
820
+ console.log(` Long-term: ❌ disabled`);
821
+ }
475
822
  console.log();
476
823
  }
477
824
  catch (err) {
@@ -795,22 +1142,83 @@ kbCmd.command('clear').action(() => {
795
1142
  console.log(`${icon.success} Knowledge base cleared.`);
796
1143
  });
797
1144
  // 📦 Package commands ───────────────────────────────────
1145
+ const publish_1 = require("./publish");
798
1146
  program
799
1147
  .command('publish')
800
- .description('Package agent for distribution')
801
- .option('-f, --file <file>', 'OAD file', 'oad.yaml')
802
- .option('-o, --output <dir>', 'Output directory', '.')
803
- .option('--include-kb', 'Include knowledge base')
804
- .action(async () => {
805
- console.log(`\n${icon.package} Agent packaging coming soon.\n`);
1148
+ .description('Validate, pack, and publish agent package')
1149
+ .option('--dry-run', 'Show what would be published without actually publishing')
1150
+ .option('--tag <tag>', 'Publish tag (default: latest)', 'latest')
1151
+ .option('--access <access>', 'Package access level (public or private)', 'public')
1152
+ .option('--registry <url>', 'Registry URL')
1153
+ .action(async (opts) => {
1154
+ const dir = process.cwd();
1155
+ const packager = new publish_1.AgentPackager();
1156
+ const publisher = new publish_1.AgentPublisher();
1157
+ // Validate first
1158
+ console.log(`\n${icon.gear} Validating agent project...`);
1159
+ const validation = await packager.validate(dir);
1160
+ for (const w of validation.warnings)
1161
+ console.log(` ${icon.warn} ${color.yellow(w)}`);
1162
+ if (!validation.valid) {
1163
+ for (const e of validation.errors)
1164
+ console.log(` ${icon.error} ${color.red(e)}`);
1165
+ console.log(`\n${icon.error} Validation failed. Fix errors above.\n`);
1166
+ process.exit(1);
1167
+ }
1168
+ console.log(` ${icon.success} Validation passed.`);
1169
+ // Pack
1170
+ console.log(`\n${icon.package} Packing agent...`);
1171
+ const { path: pkgPath, manifest } = await packager.pack(dir);
1172
+ console.log(` ${icon.success} Created ${color.bold(path.basename(pkgPath))} (${manifest.files.length} files)`);
1173
+ console.log(` ${color.dim('Checksum:')} ${manifest.checksum}`);
1174
+ // Publish
1175
+ await publisher.publish(pkgPath, manifest, {
1176
+ dryRun: opts.dryRun,
1177
+ tag: opts.tag,
1178
+ access: opts.access,
1179
+ registry: opts.registry,
1180
+ });
1181
+ });
1182
+ program
1183
+ .command('pack')
1184
+ .description('Create .opc.tgz package without publishing')
1185
+ .option('--list', 'List files that would be included (do not create archive)')
1186
+ .action(async (opts) => {
1187
+ const dir = process.cwd();
1188
+ const packager = new publish_1.AgentPackager();
1189
+ if (opts.list) {
1190
+ const files = await packager.listFiles(dir);
1191
+ console.log(`\n${icon.package} ${color.bold('Files to include')} (${files.length}):\n`);
1192
+ for (const f of files)
1193
+ console.log(` ${f}`);
1194
+ console.log();
1195
+ return;
1196
+ }
1197
+ // Validate
1198
+ const validation = await packager.validate(dir);
1199
+ for (const w of validation.warnings)
1200
+ console.log(` ${icon.warn} ${color.yellow(w)}`);
1201
+ if (!validation.valid) {
1202
+ for (const e of validation.errors)
1203
+ console.log(` ${icon.error} ${color.red(e)}`);
1204
+ process.exit(1);
1205
+ }
1206
+ console.log(`\n${icon.package} Packing agent...`);
1207
+ const { path: pkgPath, manifest } = await packager.pack(dir);
1208
+ console.log(` ${icon.success} Created ${color.bold(path.basename(pkgPath))}`);
1209
+ console.log(` Files: ${manifest.files.length}`);
1210
+ console.log(` Checksum: ${manifest.checksum}\n`);
806
1211
  });
807
1212
  program
808
1213
  .command('install')
809
- .description('Install agent from package')
810
- .argument('<source>', 'Package file path or URL')
811
- .option('-d, --dir <dir>', 'Install directory')
812
- .action(async () => {
813
- console.log(`\n${icon.package} Agent install coming soon.\n`);
1214
+ .description('Install agent from .opc.tgz package or npm')
1215
+ .argument('<source>', 'Package file path, URL, or npm package name')
1216
+ .option('-d, --dir <dir>', 'Install directory', '.')
1217
+ .action(async (source, opts) => {
1218
+ const installer = new publish_1.AgentInstaller();
1219
+ console.log(`\n${icon.package} Installing from ${color.bold(source)}...`);
1220
+ await installer.install(source, path.resolve(opts.dir));
1221
+ console.log();
814
1222
  });
815
1223
  // 🔌 Plugin commands ────────────────────────────────────────
816
1224
  const pluginCmd = program.command('plugin').description('Manage plugins');
@@ -856,6 +1264,80 @@ pluginCmd.command('add')
856
1264
  process.exit(1);
857
1265
  }
858
1266
  });
1267
+ // 🔌 Protocol commands ───────────────────────────────────────
1268
+ const protocolCmd = program.command('protocol').description('Manage agent protocols (A2A, AG-UI)');
1269
+ protocolCmd.command('list')
1270
+ .description('List supported protocols and their status')
1271
+ .option('-f, --file <file>', 'OAD file', 'oad.yaml')
1272
+ .action((opts) => {
1273
+ let config = {};
1274
+ try {
1275
+ config = yaml.load(fs.readFileSync(opts.file, 'utf-8'));
1276
+ }
1277
+ catch { /* no file */ }
1278
+ const protocols = config?.spec?.protocols || {};
1279
+ const items = [
1280
+ { name: 'a2a', description: 'Agent-to-Agent protocol', enabled: !!protocols.a2a?.enabled, detail: protocols.a2a?.port ? `port ${protocols.a2a.port}` : '' },
1281
+ { name: 'agui', description: 'AG-UI — Agent-User Interaction (SSE)', enabled: !!protocols.agui?.enabled, detail: protocols.agui?.path || '/agui' },
1282
+ ];
1283
+ console.log(`\n${icon.gear} ${color.bold('Protocols')}\n`);
1284
+ for (const p of items) {
1285
+ const status = p.enabled ? color.green('enabled') : color.dim('disabled');
1286
+ console.log(` ${color.cyan(p.name.padEnd(10))} ${status.padEnd(20)} ${p.description} ${p.detail ? color.dim(`(${p.detail})`) : ''}`);
1287
+ }
1288
+ console.log();
1289
+ });
1290
+ protocolCmd.command('enable')
1291
+ .argument('<name>', 'Protocol name (a2a, agui)')
1292
+ .option('-f, --file <file>', 'OAD file', 'oad.yaml')
1293
+ .description('Enable a protocol')
1294
+ .action((name, opts) => {
1295
+ const validProtocols = ['a2a', 'agui'];
1296
+ if (!validProtocols.includes(name)) {
1297
+ console.error(`${icon.error} Unknown protocol: ${color.bold(name)}. Available: ${validProtocols.join(', ')}`);
1298
+ process.exit(1);
1299
+ }
1300
+ try {
1301
+ const raw = fs.readFileSync(opts.file, 'utf-8');
1302
+ const config = yaml.load(raw);
1303
+ if (!config.spec.protocols)
1304
+ config.spec.protocols = {};
1305
+ if (!config.spec.protocols[name])
1306
+ config.spec.protocols[name] = {};
1307
+ config.spec.protocols[name].enabled = true;
1308
+ if (name === 'agui' && !config.spec.protocols[name].path) {
1309
+ config.spec.protocols[name].path = '/agui';
1310
+ }
1311
+ fs.writeFileSync(opts.file, yaml.dump(config, { lineWidth: 120 }));
1312
+ console.log(`${icon.success} Enabled protocol "${color.cyan(name)}" in ${opts.file}`);
1313
+ }
1314
+ catch (err) {
1315
+ console.error(`${icon.error} Failed:`, err instanceof Error ? err.message : err);
1316
+ process.exit(1);
1317
+ }
1318
+ });
1319
+ protocolCmd.command('disable')
1320
+ .argument('<name>', 'Protocol name (a2a, agui)')
1321
+ .option('-f, --file <file>', 'OAD file', 'oad.yaml')
1322
+ .description('Disable a protocol')
1323
+ .action((name, opts) => {
1324
+ try {
1325
+ const raw = fs.readFileSync(opts.file, 'utf-8');
1326
+ const config = yaml.load(raw);
1327
+ if (config?.spec?.protocols?.[name]) {
1328
+ config.spec.protocols[name].enabled = false;
1329
+ fs.writeFileSync(opts.file, yaml.dump(config, { lineWidth: 120 }));
1330
+ console.log(`${icon.success} Disabled protocol "${color.cyan(name)}" in ${opts.file}`);
1331
+ }
1332
+ else {
1333
+ console.log(`${icon.info} Protocol "${name}" was not configured.`);
1334
+ }
1335
+ }
1336
+ catch (err) {
1337
+ console.error(`${icon.error} Failed:`, err instanceof Error ? err.message : err);
1338
+ process.exit(1);
1339
+ }
1340
+ });
859
1341
  // 🔄 Migrate command ────────────────────────────────────────
860
1342
  program
861
1343
  .command('migrate')
@@ -1033,5 +1515,631 @@ program
1033
1515
  console.log(` ${icon.info} No score data yet. Run the agent first.\n`);
1034
1516
  }
1035
1517
  });
1518
+ // ── Daemon commands (start/stop/status) ─────────────────────
1519
+ const OPC_DIR = path.resolve('.opc');
1520
+ program
1521
+ .command('start')
1522
+ .description('Start agent as a background daemon')
1523
+ .option('-f, --file <file>', 'OAD file (agent.yaml or oad.yaml)')
1524
+ .action(async () => {
1525
+ if (!fs.existsSync(OPC_DIR))
1526
+ fs.mkdirSync(OPC_DIR, { recursive: true });
1527
+ const pidFile = path.join(OPC_DIR, 'agent.pid');
1528
+ // Check if already running
1529
+ if (fs.existsSync(pidFile)) {
1530
+ const pid = parseInt(fs.readFileSync(pidFile, 'utf-8').trim(), 10);
1531
+ try {
1532
+ process.kill(pid, 0);
1533
+ console.log(`${icon.warn} Agent already running (PID ${pid}).`);
1534
+ return;
1535
+ }
1536
+ catch { /* stale */ }
1537
+ }
1538
+ // Find daemon entry point
1539
+ const daemonScript = path.join(__dirname, 'daemon.js');
1540
+ if (!fs.existsSync(daemonScript)) {
1541
+ console.error(`${icon.error} Daemon script not found. Run ${color.cyan('npm run build')} first.`);
1542
+ process.exit(1);
1543
+ }
1544
+ const logFile = path.join(OPC_DIR, 'agent.log');
1545
+ const out = fs.openSync(logFile, 'a');
1546
+ const err = fs.openSync(logFile, 'a');
1547
+ const child = (0, child_process_1.spawn)(process.execPath, [daemonScript], {
1548
+ detached: true,
1549
+ stdio: ['ignore', out, err],
1550
+ cwd: process.cwd(),
1551
+ env: process.env,
1552
+ });
1553
+ child.unref();
1554
+ // Wait briefly for PID file
1555
+ await new Promise(r => setTimeout(r, 1000));
1556
+ if (fs.existsSync(pidFile)) {
1557
+ const pid = fs.readFileSync(pidFile, 'utf-8').trim();
1558
+ console.log(`${icon.success} Agent started (PID ${pid})`);
1559
+ console.log(` ${color.dim('Logs:')} ${logFile}`);
1560
+ console.log(` ${color.dim('Stop:')} opc stop`);
1561
+ }
1562
+ else {
1563
+ console.log(`${icon.success} Agent starting... (PID ${child.pid})`);
1564
+ console.log(` ${color.dim('Logs:')} ${logFile}`);
1565
+ }
1566
+ });
1567
+ program
1568
+ .command('stop')
1569
+ .description('Stop the background daemon')
1570
+ .action(() => {
1571
+ const pidFile = path.join(OPC_DIR, 'agent.pid');
1572
+ if (!fs.existsSync(pidFile)) {
1573
+ console.log(`${icon.info} No running agent found.`);
1574
+ return;
1575
+ }
1576
+ const pid = parseInt(fs.readFileSync(pidFile, 'utf-8').trim(), 10);
1577
+ try {
1578
+ // On Windows, process.kill with SIGTERM may not work; use taskkill
1579
+ if (process.platform === 'win32') {
1580
+ const { execSync } = require('child_process');
1581
+ try {
1582
+ execSync(`taskkill /PID ${pid} /T /F`, { stdio: 'ignore' });
1583
+ }
1584
+ catch { /* ignore */ }
1585
+ }
1586
+ else {
1587
+ process.kill(pid, 'SIGTERM');
1588
+ }
1589
+ console.log(`${icon.success} Sent stop signal to PID ${pid}`);
1590
+ }
1591
+ catch {
1592
+ console.log(`${icon.warn} Process ${pid} not found (may have already stopped).`);
1593
+ }
1594
+ try {
1595
+ fs.unlinkSync(pidFile);
1596
+ }
1597
+ catch { /* ignore */ }
1598
+ });
1599
+ program
1600
+ .command('status')
1601
+ .description('Check daemon status')
1602
+ .action(() => {
1603
+ const pidFile = path.join(OPC_DIR, 'agent.pid');
1604
+ if (!fs.existsSync(pidFile)) {
1605
+ console.log(`\n Status: ${color.red('stopped')}\n`);
1606
+ return;
1607
+ }
1608
+ const pid = parseInt(fs.readFileSync(pidFile, 'utf-8').trim(), 10);
1609
+ let running = false;
1610
+ try {
1611
+ process.kill(pid, 0);
1612
+ running = true;
1613
+ }
1614
+ catch { /* not running */ }
1615
+ if (!running) {
1616
+ console.log(`\n Status: ${color.red('stopped')} (stale PID file)`);
1617
+ try {
1618
+ fs.unlinkSync(pidFile);
1619
+ }
1620
+ catch { /* ignore */ }
1621
+ console.log();
1622
+ return;
1623
+ }
1624
+ // Uptime
1625
+ const startedFile = path.join(OPC_DIR, 'started');
1626
+ let uptime = '';
1627
+ if (fs.existsSync(startedFile)) {
1628
+ const startedMs = parseInt(fs.readFileSync(startedFile, 'utf-8').trim(), 10);
1629
+ const secs = Math.floor((Date.now() - startedMs) / 1000);
1630
+ const h = Math.floor(secs / 3600);
1631
+ const m = Math.floor((secs % 3600) / 60);
1632
+ const s = secs % 60;
1633
+ uptime = `${h}h ${m}m ${s}s`;
1634
+ }
1635
+ // Agent name from config
1636
+ let agentName = 'unknown';
1637
+ for (const f of ['agent.yaml', 'oad.yaml']) {
1638
+ if (fs.existsSync(f)) {
1639
+ try {
1640
+ const raw = fs.readFileSync(f, 'utf-8');
1641
+ const cfg = yaml.load(raw);
1642
+ if (cfg?.metadata?.name) {
1643
+ agentName = cfg.metadata.name;
1644
+ break;
1645
+ }
1646
+ }
1647
+ catch { /* ignore */ }
1648
+ }
1649
+ }
1650
+ console.log(`\n Status: ${color.green('running')}`);
1651
+ console.log(` PID: ${pid}`);
1652
+ console.log(` Agent: ${color.cyan(agentName)}`);
1653
+ if (uptime)
1654
+ console.log(` Uptime: ${uptime}`);
1655
+ console.log();
1656
+ });
1657
+ // ── Jobs commands ────────────────────────────────────────────
1658
+ const jobsCmd = program.command('jobs').description('Manage scheduled jobs');
1659
+ jobsCmd
1660
+ .command('list', { isDefault: true })
1661
+ .description('List all scheduled jobs')
1662
+ .option('-f, --file <file>', 'OAD file', 'oad.yaml')
1663
+ .action(async (opts) => {
1664
+ const jobs = loadJobsFromConfig(opts.file);
1665
+ if (jobs.length === 0) {
1666
+ console.log(`\n${icon.info} No scheduled jobs defined in config.\n`);
1667
+ return;
1668
+ }
1669
+ console.log(`\n${icon.gear} ${color.bold('Scheduled Jobs')}\n`);
1670
+ for (const job of jobs) {
1671
+ const status = job.enabled ? color.green('enabled') : color.dim('disabled');
1672
+ const next = job.nextRun ? job.nextRun.toLocaleString() : color.dim('N/A');
1673
+ console.log(` ${color.cyan(job.id.padEnd(20))} ${job.name}`);
1674
+ console.log(` ${''.padEnd(20)} Schedule: ${color.dim(job.schedule)} | Status: ${status} | Next: ${next}`);
1675
+ console.log();
1676
+ }
1677
+ });
1678
+ jobsCmd
1679
+ .command('run')
1680
+ .argument('<id>', 'Job ID to run')
1681
+ .option('-f, --file <file>', 'OAD file', 'oad.yaml')
1682
+ .description('Manually trigger a scheduled job')
1683
+ .action(async (id, opts) => {
1684
+ const jobs = loadJobsFromConfig(opts.file);
1685
+ const job = jobs.find(j => j.id === id || j.name === id);
1686
+ if (!job) {
1687
+ console.error(`${icon.error} Job "${id}" not found. Available: ${jobs.map(j => j.id).join(', ')}`);
1688
+ process.exit(1);
1689
+ }
1690
+ console.log(`${icon.info} Running job "${color.bold(job.name)}"...`);
1691
+ console.log(` Task: ${color.dim(job.task)}`);
1692
+ console.log(`\n${icon.warn} Manual job execution requires a running daemon. Use ${color.cyan('opc start')} first.\n`);
1693
+ });
1694
+ function loadJobsFromConfig(file) {
1695
+ try {
1696
+ const raw = fs.readFileSync(file, 'utf-8');
1697
+ const config = yaml.load(raw);
1698
+ const jobConfigs = config?.spec?.scheduler?.jobs ?? [];
1699
+ const { parseCron } = require('./core/scheduler');
1700
+ return jobConfigs.map((j, i) => {
1701
+ const id = j.id || j.name?.toLowerCase().replace(/\s+/g, '-') || `job-${i}`;
1702
+ const parsed = parseCron(j.schedule);
1703
+ // Compute next run
1704
+ const now = new Date();
1705
+ let nextRun;
1706
+ const d = new Date(now);
1707
+ d.setSeconds(0, 0);
1708
+ d.setMinutes(d.getMinutes() + 1);
1709
+ for (let k = 0; k < 48 * 60; k++) {
1710
+ const { cronMatches } = require('./core/scheduler');
1711
+ if (cronMatches(parsed, d)) {
1712
+ nextRun = new Date(d);
1713
+ break;
1714
+ }
1715
+ d.setMinutes(d.getMinutes() + 1);
1716
+ }
1717
+ return {
1718
+ id,
1719
+ name: j.name || id,
1720
+ schedule: j.schedule,
1721
+ task: j.task || '',
1722
+ enabled: j.enabled !== false,
1723
+ nextRun,
1724
+ };
1725
+ });
1726
+ }
1727
+ catch {
1728
+ return [];
1729
+ }
1730
+ }
1731
+ // ── Skills commands ──────────────────────────────────────────
1732
+ const skillsCmd = program.command('skills').description('Manage learned skills');
1733
+ skillsCmd
1734
+ .command('list', { isDefault: true })
1735
+ .description('List all learned skills')
1736
+ .option('-d, --dir <dir>', 'Skills directory', '.opc/skills')
1737
+ .action(async (opts) => {
1738
+ const { SkillLearner } = await Promise.resolve().then(() => __importStar(require('./skills/auto-learn')));
1739
+ const learner = new SkillLearner(opts.dir);
1740
+ const skills = await learner.loadLearnedSkills();
1741
+ if (skills.length === 0) {
1742
+ console.log(`\n${icon.info} No learned skills yet.\n`);
1743
+ console.log(` Skills are auto-created from conversations when learning is enabled.`);
1744
+ console.log(` Directory: ${color.dim(path.resolve(opts.dir))}\n`);
1745
+ return;
1746
+ }
1747
+ console.log(`\n${icon.gear} ${color.bold('Learned Skills')} (${skills.length})\n`);
1748
+ for (const skill of skills) {
1749
+ console.log(` ${color.cyan(skill.name.padEnd(24))} ${skill.description}`);
1750
+ console.log(` ${''.padEnd(24)} v${skill.version} | used ${skill.usageCount}x | trigger: ${color.dim(skill.trigger)}`);
1751
+ console.log();
1752
+ }
1753
+ });
1754
+ skillsCmd
1755
+ .command('show')
1756
+ .argument('<name>', 'Skill name')
1757
+ .option('-d, --dir <dir>', 'Skills directory', '.opc/skills')
1758
+ .description('Show details of a learned skill')
1759
+ .action(async (name, opts) => {
1760
+ const skillPath = path.join(opts.dir, `${name}.md`);
1761
+ if (!fs.existsSync(skillPath)) {
1762
+ console.error(`${icon.error} Skill "${name}" not found at ${skillPath}`);
1763
+ process.exit(1);
1764
+ }
1765
+ const content = fs.readFileSync(skillPath, 'utf-8');
1766
+ console.log(`\n${content}`);
1767
+ });
1768
+ skillsCmd
1769
+ .command('remove')
1770
+ .argument('<name>', 'Skill name')
1771
+ .option('-d, --dir <dir>', 'Skills directory', '.opc/skills')
1772
+ .description('Remove a learned skill')
1773
+ .action(async (name, opts) => {
1774
+ const skillPath = path.join(opts.dir, `${name}.md`);
1775
+ if (!fs.existsSync(skillPath)) {
1776
+ console.error(`${icon.error} Skill "${name}" not found.`);
1777
+ process.exit(1);
1778
+ }
1779
+ fs.unlinkSync(skillPath);
1780
+ console.log(`${icon.success} Removed skill "${color.cyan(name)}".`);
1781
+ });
1782
+ // ── Doctor command ───────────────────────────────────────────
1783
+ program
1784
+ .command('studio')
1785
+ .description('Start OPC Studio web UI')
1786
+ .option('--port <port>', 'Port to listen on', '4000')
1787
+ .action(async (opts) => {
1788
+ const { StudioServer } = require('./studio/server');
1789
+ const server = new StudioServer({
1790
+ port: parseInt(opts.port, 10),
1791
+ agentDir: process.cwd(),
1792
+ });
1793
+ await server.start();
1794
+ console.log(color.dim('Press Ctrl+C to stop'));
1795
+ });
1796
+ program
1797
+ .command('doctor')
1798
+ .description('Check environment and diagnose common issues')
1799
+ .action(async () => {
1800
+ await (0, doctor_1.runDoctor)();
1801
+ });
1802
+ // ─── Eval command ───────────────────────────────────────────────────────────
1803
+ const eval_1 = require("./eval");
1804
+ program
1805
+ .command('eval')
1806
+ .argument('[suite]', 'Built-in suite name (basic, safety, memory) or omit for all')
1807
+ .option('-f, --file <path>', 'Path to custom eval suite JSON file')
1808
+ .option('-o, --output <path>', 'Save report to JSON file')
1809
+ .option('-v, --verbose', 'Show per-case details')
1810
+ .description('Run agent evaluation suites')
1811
+ .action(async (suiteName, opts) => {
1812
+ const suites = [];
1813
+ if (opts.file) {
1814
+ suites.push(eval_1.AgentEvaluator.loadSuite(opts.file));
1815
+ }
1816
+ else if (suiteName) {
1817
+ suites.push(eval_1.AgentEvaluator.loadBuiltinSuite(suiteName));
1818
+ }
1819
+ else {
1820
+ // All built-in suites
1821
+ for (const s of eval_1.AgentEvaluator.builtinSuites()) {
1822
+ suites.push(eval_1.AgentEvaluator.loadBuiltinSuite(s.name));
1823
+ }
1824
+ }
1825
+ if (!suites.length) {
1826
+ console.log(`${icon.warn} No eval suites found.`);
1827
+ return;
1828
+ }
1829
+ // Create a minimal mock agent for eval (real usage would load from OAD)
1830
+ const oadPath = path.resolve('agent.yaml');
1831
+ let agent;
1832
+ if (fs.existsSync(oadPath)) {
1833
+ const runtime = new runtime_1.AgentRuntime();
1834
+ await runtime.loadConfig(oadPath);
1835
+ await runtime.start();
1836
+ agent = runtime.agent;
1837
+ }
1838
+ if (!agent) {
1839
+ console.log(`${icon.warn} No agent.yaml found — running with dry-run mock agent.`);
1840
+ agent = { chat: async (input) => `[mock response to: ${input}]` };
1841
+ }
1842
+ const evaluator = new eval_1.AgentEvaluator(agent);
1843
+ let allPassed = 0, allTotal = 0;
1844
+ for (const suite of suites) {
1845
+ console.log(`\n${color.bold(`🧪 Suite: ${suite.name}`)} (${suite.cases.length} cases)`);
1846
+ const report = await evaluator.evalSuite(suite);
1847
+ allPassed += report.passed;
1848
+ allTotal += report.totalCases;
1849
+ for (const r of report.results) {
1850
+ const status = r.passed ? color.green('PASS') : color.red('FAIL');
1851
+ console.log(` ${status} ${r.caseId}`);
1852
+ if (opts.verbose && !r.passed) {
1853
+ if (r.error)
1854
+ console.log(` ${color.dim('error: ' + r.error)}`);
1855
+ console.log(` ${color.dim('output: ' + r.output.slice(0, 120))}`);
1856
+ }
1857
+ }
1858
+ console.log(` ${color.dim(report.summary)}`);
1859
+ if (opts.output) {
1860
+ const outPath = suites.length > 1
1861
+ ? opts.output.replace('.json', `-${suite.name}.json`)
1862
+ : opts.output;
1863
+ eval_1.AgentEvaluator.saveReport(report, outPath);
1864
+ console.log(` ${icon.success} Report saved to ${outPath}`);
1865
+ }
1866
+ }
1867
+ console.log(`\n${color.bold('Summary:')} ${allPassed}/${allTotal} passed (${allTotal ? Math.round(allPassed / allTotal * 100) : 0}%)`);
1868
+ });
1036
1869
  program.parse();
1870
+ // ── Keys command ──────────────────────────────────────────────
1871
+ const keys_1 = require("./security/keys");
1872
+ const approval_1 = require("./security/approval");
1873
+ const keysCmd = program.command('keys').description('Manage API keys');
1874
+ keysCmd
1875
+ .command('set')
1876
+ .argument('<name>', 'Key name')
1877
+ .description('Store an API key (encrypted)')
1878
+ .action(async (name) => {
1879
+ const value = await promptUser(`Enter value for ${color.bold(name)}`);
1880
+ if (!value) {
1881
+ console.log(`${icon.error} No value provided.`);
1882
+ return;
1883
+ }
1884
+ const km = new keys_1.KeyManager();
1885
+ km.set(name, value);
1886
+ console.log(`${icon.success} Key ${color.bold(name)} saved.`);
1887
+ });
1888
+ keysCmd
1889
+ .command('list')
1890
+ .description('List stored key names')
1891
+ .action(() => {
1892
+ const km = new keys_1.KeyManager();
1893
+ const names = km.list();
1894
+ if (names.length === 0) {
1895
+ console.log(`${icon.info} No keys stored.`);
1896
+ return;
1897
+ }
1898
+ console.log(`\n${color.bold('Stored keys:')}`);
1899
+ names.forEach(n => console.log(` • ${n}`));
1900
+ });
1901
+ keysCmd
1902
+ .command('delete')
1903
+ .argument('<name>', 'Key name')
1904
+ .description('Delete a stored key')
1905
+ .action((name) => {
1906
+ const km = new keys_1.KeyManager();
1907
+ if (km.delete(name)) {
1908
+ console.log(`${icon.success} Key ${color.bold(name)} deleted.`);
1909
+ }
1910
+ else {
1911
+ console.log(`${icon.error} Key ${color.bold(name)} not found.`);
1912
+ }
1913
+ });
1914
+ // ── Approve command ───────────────────────────────────────────
1915
+ const approveCmd = program.command('approve').description('Manage command approvals');
1916
+ // Singleton for CLI — in real usage this would be loaded from daemon state
1917
+ const approvalManager = new approval_1.ApprovalManager();
1918
+ approveCmd
1919
+ .command('list')
1920
+ .description('Show pending approval requests')
1921
+ .action(() => {
1922
+ const pending = approvalManager.getPending();
1923
+ if (pending.length === 0) {
1924
+ console.log(`${icon.info} No pending approvals.`);
1925
+ return;
1926
+ }
1927
+ console.log(`\n${color.bold('Pending approvals:')}`);
1928
+ pending.forEach(r => {
1929
+ console.log(` ${color.cyan(r.id.slice(0, 8))} [${r.type}] ${r.command}`);
1930
+ console.log(` ${color.dim(r.description)}`);
1931
+ });
1932
+ });
1933
+ approveCmd
1934
+ .command('allow')
1935
+ .argument('<id>', 'Approval request ID (prefix match)')
1936
+ .description('Approve a pending request')
1937
+ .action((id) => {
1938
+ const pending = approvalManager.getPending();
1939
+ const match = pending.find(r => r.id.startsWith(id));
1940
+ if (!match) {
1941
+ console.log(`${icon.error} No pending request matching ${id}`);
1942
+ return;
1943
+ }
1944
+ approvalManager.approve(match.id, 'cli-user');
1945
+ console.log(`${icon.success} Approved: ${match.command}`);
1946
+ });
1947
+ approveCmd
1948
+ .command('deny')
1949
+ .argument('<id>', 'Approval request ID (prefix match)')
1950
+ .description('Deny a pending request')
1951
+ .action((id) => {
1952
+ const pending = approvalManager.getPending();
1953
+ const match = pending.find(r => r.id.startsWith(id));
1954
+ if (!match) {
1955
+ console.log(`${icon.error} No pending request matching ${id}`);
1956
+ return;
1957
+ }
1958
+ approvalManager.deny(match.id, 'cli-user');
1959
+ console.log(`${icon.success} Denied: ${match.command}`);
1960
+ });
1961
+ program
1962
+ .command('traces')
1963
+ .option('-l, --limit <n>', 'Number of traces to show', '20')
1964
+ .option('-f, --file <path>', 'Read traces from file')
1965
+ .description('Show recent telemetry traces')
1966
+ .action(async (opts) => {
1967
+ const limit = parseInt(opts.limit) || 20;
1968
+ if (opts.file) {
1969
+ // Read from NDJSON file
1970
+ const fs = require('fs');
1971
+ if (!fs.existsSync(opts.file)) {
1972
+ console.log(`${icon.error} File not found: ${opts.file}`);
1973
+ return;
1974
+ }
1975
+ const lines = fs.readFileSync(opts.file, 'utf-8').trim().split('\n');
1976
+ const spans = lines.slice(-limit).map((l) => {
1977
+ try {
1978
+ return JSON.parse(l);
1979
+ }
1980
+ catch {
1981
+ return null;
1982
+ }
1983
+ }).filter(Boolean);
1984
+ printTraceTable(spans);
1985
+ }
1986
+ else {
1987
+ // Try to read from Studio API
1988
+ try {
1989
+ const oad = loadOADFile();
1990
+ const port = 4000; // default studio port
1991
+ const res = await fetch(`http://localhost:${port}/api/telemetry/traces?limit=${limit}`);
1992
+ const data = await res.json();
1993
+ if (data.traces && data.traces.length > 0) {
1994
+ console.log(`\n${color.bold('Recent Traces')} (${data.traces.length})\n`);
1995
+ console.log(`${'Trace ID'.padEnd(12)} ${'Root Span'.padEnd(25)} ${'Time'.padEnd(22)} ${'Spans'.padEnd(7)} ${'Status'}`);
1996
+ console.log(`${'─'.repeat(12)} ${'─'.repeat(25)} ${'─'.repeat(22)} ${'─'.repeat(7)} ${'─'.repeat(8)}`);
1997
+ for (const t of data.traces) {
1998
+ const time = new Date(t.startTime).toISOString().slice(0, 19).replace('T', ' ');
1999
+ const statusColor = t.status === 'ok' ? color.green : t.status === 'error' ? color.red : color.dim;
2000
+ console.log(`${color.cyan(t.traceId.slice(0, 12))} ${t.rootSpan.padEnd(25).slice(0, 25)} ${time.padEnd(22)} ${String(t.spanCount).padEnd(7)} ${statusColor(t.status)}`);
2001
+ }
2002
+ }
2003
+ else {
2004
+ console.log(`${icon.info} No traces found. Enable telemetry in your OAD: spec.telemetry.enabled: true`);
2005
+ }
2006
+ }
2007
+ catch {
2008
+ console.log(`${icon.error} Could not connect to Studio. Is it running? (opc studio)`);
2009
+ }
2010
+ }
2011
+ });
2012
+ function printTraceTable(spans) {
2013
+ if (spans.length === 0) {
2014
+ console.log(`${icon.info} No traces found.`);
2015
+ return;
2016
+ }
2017
+ console.log(`\n${color.bold('Recent Spans')} (${spans.length})\n`);
2018
+ console.log(`${'Trace ID'.padEnd(12)} ${'Span'.padEnd(25)} ${'Duration'.padEnd(10)} ${'Status'}`);
2019
+ console.log(`${'─'.repeat(12)} ${'─'.repeat(25)} ${'─'.repeat(10)} ${'─'.repeat(8)}`);
2020
+ for (const s of spans) {
2021
+ const dur = s.endTime ? `${s.endTime - s.startTime}ms` : 'ongoing';
2022
+ const statusColor = s.status === 'ok' ? color.green : s.status === 'error' ? color.red : color.dim;
2023
+ console.log(`${color.cyan(s.traceId.slice(0, 12))} ${s.name.padEnd(25).slice(0, 25)} ${dur.padEnd(10)} ${statusColor(s.status)}`);
2024
+ }
2025
+ }
2026
+ // ── A2A Protocol Commands ───────────────────────────────────
2027
+ const a2aCmd = program.command('a2a').description('Google A2A protocol commands');
2028
+ a2aCmd
2029
+ .command('serve')
2030
+ .option('-p, --port <port>', 'Port for A2A server', '3001')
2031
+ .description('Start A2A server for this agent')
2032
+ .action(async (opts) => {
2033
+ const port = parseInt(opts.port) || 3001;
2034
+ const { A2AServer } = require('./protocols/a2a');
2035
+ const oad = loadOADFile();
2036
+ const server = new A2AServer(null, { oad, port });
2037
+ await server.start(port);
2038
+ console.log(`${icon.success} A2A server running on http://localhost:${port}`);
2039
+ console.log(`${icon.info} Agent card: http://localhost:${port}/.well-known/agent.json`);
2040
+ });
2041
+ a2aCmd
2042
+ .command('card')
2043
+ .description('Print this agent\'s A2A card')
2044
+ .action(() => {
2045
+ const { oadToAgentCard } = require('./protocols/a2a');
2046
+ const oad = loadOADFile();
2047
+ if (!oad) {
2048
+ console.log(`${icon.error} No agent.yaml found`);
2049
+ return;
2050
+ }
2051
+ const card = oadToAgentCard(oad, 'http://localhost:3001');
2052
+ console.log(JSON.stringify(card, null, 2));
2053
+ });
2054
+ a2aCmd
2055
+ .command('discover')
2056
+ .argument('<url>', 'Remote agent URL')
2057
+ .description('Fetch remote agent\'s A2A card')
2058
+ .action(async (url) => {
2059
+ const { A2AClient } = require('./protocols/a2a');
2060
+ const client = new A2AClient(url);
2061
+ try {
2062
+ const card = await client.getAgentCard();
2063
+ console.log(JSON.stringify(card, null, 2));
2064
+ }
2065
+ catch (err) {
2066
+ console.log(`${icon.error} Failed to discover agent: ${err.message}`);
2067
+ }
2068
+ });
2069
+ a2aCmd
2070
+ .command('call')
2071
+ .argument('<url>', 'Remote agent URL')
2072
+ .argument('<message>', 'Message to send')
2073
+ .description('Call a remote A2A agent')
2074
+ .action(async (url, message) => {
2075
+ const { A2AClient } = require('./protocols/a2a');
2076
+ const client = new A2AClient(url);
2077
+ try {
2078
+ const response = await client.sendText(message);
2079
+ console.log(response);
2080
+ }
2081
+ catch (err) {
2082
+ console.log(`${icon.error} Call failed: ${err.message}`);
2083
+ }
2084
+ });
2085
+ function loadOADFile() {
2086
+ const fs = require('fs');
2087
+ const yaml = require('js-yaml');
2088
+ for (const name of ['agent.yaml', 'agent.yml']) {
2089
+ if (fs.existsSync(name)) {
2090
+ return yaml.load(fs.readFileSync(name, 'utf-8'));
2091
+ }
2092
+ }
2093
+ return null;
2094
+ }
2095
+ // ── MCP Server Commands ────────────────────────────────────
2096
+ const mcpCmd = program.command('mcp').description('MCP server commands — expose agent as MCP tools');
2097
+ mcpCmd
2098
+ .command('serve')
2099
+ .option('--http <port>', 'Start HTTP+SSE mode on given port')
2100
+ .description('Start MCP server (stdio by default, --http for HTTP+SSE)')
2101
+ .action(async (opts) => {
2102
+ const { MCPServer } = require('./protocols/mcp');
2103
+ const { agentToMCPTools, agentToMCPResources } = require('./protocols/mcp');
2104
+ const oad = loadOADFile();
2105
+ const agentName = oad?.metadata?.name || 'opc-agent';
2106
+ const server = new MCPServer({
2107
+ name: agentName,
2108
+ version: oad?.metadata?.version || '1.0.0',
2109
+ });
2110
+ // Register tools from OAD or defaults
2111
+ const { agentToMCPTools: toTools } = require('./protocols/mcp/agent-tools');
2112
+ const mockAgent = { name: agentName, config: { name: agentName } };
2113
+ const tools = toTools(mockAgent);
2114
+ for (const t of tools)
2115
+ server.addTool(t);
2116
+ if (opts.http) {
2117
+ const port = parseInt(opts.http) || 3002;
2118
+ await server.serveHTTP(port);
2119
+ console.log(`${icon.success} MCP server (HTTP+SSE) running on http://localhost:${port}`);
2120
+ console.log(`${icon.info} SSE endpoint: http://localhost:${port}/sse`);
2121
+ console.log(`${icon.info} Message endpoint: http://localhost:${port}/message`);
2122
+ console.log(`${icon.info} Tools: ${server.getToolCount()}`);
2123
+ }
2124
+ else {
2125
+ console.error(`${icon.success} MCP server (stdio) started — ${server.getToolCount()} tools`);
2126
+ await server.serveStdio();
2127
+ }
2128
+ });
2129
+ mcpCmd
2130
+ .command('tools')
2131
+ .description('List MCP tools that would be exposed')
2132
+ .action(() => {
2133
+ const { agentToMCPTools } = require('./protocols/mcp/agent-tools');
2134
+ const oad = loadOADFile();
2135
+ const agentName = oad?.metadata?.name || 'opc-agent';
2136
+ const tools = agentToMCPTools({ name: agentName });
2137
+ console.log(`\n${icon.gear} MCP Tools for ${color.cyan(agentName)}:\n`);
2138
+ for (const t of tools) {
2139
+ const required = t.inputSchema?.required?.join(', ') || 'none';
2140
+ console.log(` ${color.green(t.name.padEnd(20))} ${t.description}`);
2141
+ console.log(` ${' '.repeat(20)} Required: ${color.dim(required)}`);
2142
+ }
2143
+ console.log();
2144
+ });
1037
2145
  //# sourceMappingURL=cli.js.map