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