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