opc-agent 0.5.1 → 0.7.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.
@@ -1,6 +1,7 @@
1
1
  import { type Response } from 'express';
2
2
  import type { Message } from '../core/types';
3
3
  import { BaseChannel } from './index';
4
+ import { type AuthConfig } from '../core/auth';
4
5
  export declare class WebChannel extends BaseChannel {
5
6
  readonly type = "web";
6
7
  private app;
@@ -8,7 +9,19 @@ export declare class WebChannel extends BaseChannel {
8
9
  private port;
9
10
  private streamHandler;
10
11
  private agentName;
11
- constructor(port?: number);
12
+ private currentProvider;
13
+ private stats;
14
+ private eventHandlers;
15
+ private conversations;
16
+ private requestCount;
17
+ private llmLatencySum;
18
+ private llmCalls;
19
+ private emit;
20
+ onConfigChange(handler: (config: any) => void): void;
21
+ trackMessage(responseMs: number, tokens?: number): void;
22
+ trackError(): void;
23
+ trackSession(): void;
24
+ constructor(port?: number, authConfig?: AuthConfig);
12
25
  setAgentName(name: string): void;
13
26
  onStreamMessage(handler: (msg: Message, res: Response) => Promise<void>): void;
14
27
  private setupRoutes;
@@ -6,6 +6,58 @@ Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.WebChannel = void 0;
7
7
  const express_1 = __importDefault(require("express"));
8
8
  const index_1 = require("./index");
9
+ const knowledge_1 = require("../core/knowledge");
10
+ const auth_1 = require("../core/auth");
11
+ const AGENT_TEMPLATES = [
12
+ { id: 'customer-service', name: 'Customer Service', description: 'Handle support tickets, FAQs, and customer inquiries', icon: '🎧', category: 'Business' },
13
+ { id: 'code-reviewer', name: 'Code Reviewer', description: 'Review PRs, suggest improvements, check for bugs', icon: '🔍', category: 'Engineering' },
14
+ { id: 'content-writer', name: 'Content Writer', description: 'Write blogs, social media posts, and marketing copy', icon: '✍️', category: 'Marketing' },
15
+ { id: 'executive-assistant', name: 'Executive Assistant', description: 'Schedule management, email drafting, meeting prep', icon: '📋', category: 'Business' },
16
+ { id: 'knowledge-base', name: 'Knowledge Base', description: 'RAG-powered Q&A over your documents', icon: '📚', category: 'Knowledge' },
17
+ { id: 'project-manager', name: 'Project Manager', description: 'Track tasks, milestones, and team coordination', icon: '📊', category: 'Business' },
18
+ { id: 'sales-assistant', name: 'Sales Assistant', description: 'Lead qualification, outreach drafting, CRM updates', icon: '💼', category: 'Sales' },
19
+ { id: 'financial-advisor', name: 'Financial Advisor', description: 'Budget analysis, financial planning, cost optimization', icon: '💰', category: 'Finance' },
20
+ { id: 'hr-recruiter', name: 'HR Recruiter', description: 'Resume screening, interview scheduling, candidate comms', icon: '👥', category: 'HR' },
21
+ { id: 'legal-assistant', name: 'Legal Assistant', description: 'Contract review, compliance checks, legal research', icon: '⚖️', category: 'Legal' },
22
+ ];
23
+ const TEMPLATES_HTML = `<!DOCTYPE html>
24
+ <html lang="en">
25
+ <head>
26
+ <meta charset="UTF-8"><meta name="viewport" content="width=device-width,initial-scale=1.0">
27
+ <title>Agent Templates</title>
28
+ <style>
29
+ *{margin:0;padding:0;box-sizing:border-box}
30
+ body{background:#0a0a0f;color:#e0e0e0;font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,sans-serif;padding:24px}
31
+ h1{font-size:28px;margin-bottom:8px;color:#fff}
32
+ .sub{color:#888;margin-bottom:32px;font-size:14px}
33
+ nav{margin-bottom:24px}
34
+ nav a{color:#818cf8;text-decoration:none;margin-right:16px;font-size:14px}
35
+ .grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(280px,1fr));gap:16px}
36
+ .card{background:#12121a;border:1px solid #1e1e2e;border-radius:12px;padding:24px;cursor:pointer;transition:all .2s}
37
+ .card:hover{border-color:#818cf8;transform:translateY(-2px)}
38
+ .card .icon{font-size:32px;margin-bottom:12px}
39
+ .card h3{font-size:16px;color:#fff;margin-bottom:8px}
40
+ .card p{font-size:13px;color:#888;line-height:1.5}
41
+ .card .cat{font-size:11px;color:#818cf8;text-transform:uppercase;letter-spacing:1px;margin-top:12px}
42
+ .btn{display:inline-block;background:#2563eb;color:#fff;border:none;border-radius:8px;padding:8px 16px;font-size:13px;cursor:pointer;margin-top:12px}
43
+ .btn:hover{background:#1d4ed8}
44
+ </style>
45
+ </head>
46
+ <body>
47
+ <nav><a href="/">← Chat</a><a href="/dashboard">Dashboard</a><a href="/templates">Templates</a></nav>
48
+ <h1>🧩 Agent Templates</h1>
49
+ <p class="sub">Create a new agent from a pre-built template in one click.</p>
50
+ <div class="grid" id="grid"></div>
51
+ <script>
52
+ fetch('/api/templates').then(r=>r.json()).then(d=>{
53
+ const g=document.getElementById('grid');
54
+ d.templates.forEach(t=>{
55
+ g.innerHTML+=\`<div class="card"><div class="icon">\${t.icon}</div><h3>\${t.name}</h3><p>\${t.description}</p><div class="cat">\${t.category}</div><button class="btn" onclick="alert('Creating agent from template: '+'\${t.id}'+'\\\\nRun: opc init --template \${t.id}')">Use Template</button></div>\`;
56
+ });
57
+ });
58
+ </script>
59
+ </body>
60
+ </html>`;
9
61
  const CHAT_HTML = `<!DOCTYPE html>
10
62
  <html lang="en">
11
63
  <head>
@@ -97,6 +149,57 @@ fetch('/api/info').then(r=>r.json()).then(d=>{if(d.name)document.getElementById(
97
149
  </script>
98
150
  </body>
99
151
  </html>`;
152
+ const DASHBOARD_HTML = `<!DOCTYPE html>
153
+ <html lang="en">
154
+ <head>
155
+ <meta charset="UTF-8">
156
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
157
+ <title>OPC Dashboard</title>
158
+ <style>
159
+ *{margin:0;padding:0;box-sizing:border-box}
160
+ body{background:#0a0a0f;color:#e0e0e0;font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,sans-serif;padding:24px}
161
+ h1{font-size:24px;margin-bottom:24px;color:#fff}
162
+ .grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(220px,1fr));gap:16px;margin-bottom:32px}
163
+ .card{background:#12121a;border:1px solid #1e1e2e;border-radius:12px;padding:20px}
164
+ .card .label{font-size:12px;color:#888;text-transform:uppercase;letter-spacing:1px}
165
+ .card .value{font-size:32px;font-weight:700;color:#818cf8;margin-top:4px}
166
+ .card .sub{font-size:12px;color:#555;margin-top:4px}
167
+ nav{margin-bottom:24px}
168
+ nav a{color:#818cf8;text-decoration:none;margin-right:16px;font-size:14px}
169
+ nav a:hover{text-decoration:underline}
170
+ .chart{background:#12121a;border:1px solid #1e1e2e;border-radius:12px;padding:20px;margin-bottom:16px}
171
+ .chart h3{font-size:14px;color:#888;margin-bottom:12px}
172
+ </style>
173
+ </head>
174
+ <body>
175
+ <nav><a href="/">← Chat</a><a href="/dashboard">Dashboard</a></nav>
176
+ <h1>📊 Agent Dashboard</h1>
177
+ <div class="grid">
178
+ <div class="card"><div class="label">Sessions</div><div class="value" id="sessions">0</div></div>
179
+ <div class="card"><div class="label">Messages</div><div class="value" id="messages">0</div></div>
180
+ <div class="card"><div class="label">Avg Response</div><div class="value" id="avgMs">0ms</div></div>
181
+ <div class="card"><div class="label">Token Usage</div><div class="value" id="tokens">0</div></div>
182
+ <div class="card"><div class="label">Uptime</div><div class="value" id="uptime">0m</div></div>
183
+ <div class="card"><div class="label">Knowledge Files</div><div class="value" id="kb">0</div></div>
184
+ </div>
185
+ <div class="chart"><h3>Messages Over Time</h3><svg id="chart" width="100%" height="120" viewBox="0 0 600 120"></svg></div>
186
+ <script>
187
+ async function refresh(){
188
+ try{
189
+ const r=await fetch('/api/dashboard');const d=await r.json();
190
+ document.getElementById('sessions').textContent=d.sessions;
191
+ document.getElementById('messages').textContent=d.messages;
192
+ document.getElementById('avgMs').textContent=d.messages>0?Math.round(d.totalResponseMs/d.messages)+'ms':'0ms';
193
+ document.getElementById('tokens').textContent=d.tokenUsage.toLocaleString();
194
+ document.getElementById('kb').textContent=d.knowledgeFiles;
195
+ const mins=Math.round((Date.now()-d.startedAt)/60000);
196
+ document.getElementById('uptime').textContent=mins<60?mins+'m':Math.round(mins/60)+'h '+mins%60+'m';
197
+ }catch{}
198
+ }
199
+ refresh();setInterval(refresh,5000);
200
+ </script>
201
+ </body>
202
+ </html>`;
100
203
  class WebChannel extends index_1.BaseChannel {
101
204
  type = 'web';
102
205
  app;
@@ -104,11 +207,41 @@ class WebChannel extends index_1.BaseChannel {
104
207
  port;
105
208
  streamHandler = null;
106
209
  agentName = 'OPC Agent';
107
- constructor(port = 3000) {
210
+ currentProvider = 'openai';
211
+ stats = { sessions: 0, messages: 0, totalResponseMs: 0, tokenUsage: 0, knowledgeFiles: 0, startedAt: Date.now(), errors: 0 };
212
+ eventHandlers = new Map();
213
+ conversations = new Map();
214
+ requestCount = 0;
215
+ llmLatencySum = 0;
216
+ llmCalls = 0;
217
+ emit(event, data) {
218
+ const handlers = this.eventHandlers.get(event) ?? [];
219
+ for (const h of handlers)
220
+ h(data);
221
+ }
222
+ onConfigChange(handler) {
223
+ const handlers = this.eventHandlers.get('config:change') ?? [];
224
+ handlers.push(handler);
225
+ this.eventHandlers.set('config:change', handlers);
226
+ }
227
+ trackMessage(responseMs, tokens = 0) {
228
+ this.stats.messages++;
229
+ this.stats.totalResponseMs += responseMs;
230
+ this.stats.tokenUsage += tokens;
231
+ this.requestCount++;
232
+ this.llmLatencySum += responseMs;
233
+ this.llmCalls++;
234
+ }
235
+ trackError() { this.stats.errors++; }
236
+ trackSession() { this.stats.sessions++; }
237
+ constructor(port = 3000, authConfig) {
108
238
  super();
109
239
  this.port = port;
110
240
  this.app = (0, express_1.default)();
111
- this.app.use(express_1.default.json());
241
+ this.app.use(express_1.default.json({ limit: '10mb' }));
242
+ if (authConfig && authConfig.apiKeys.length > 0) {
243
+ this.app.use((0, auth_1.createAuthMiddleware)(authConfig));
244
+ }
112
245
  this.setupRoutes();
113
246
  }
114
247
  setAgentName(name) {
@@ -130,6 +263,7 @@ class WebChannel extends index_1.BaseChannel {
130
263
  // Streaming chat endpoint
131
264
  this.app.post('/api/chat', async (req, res) => {
132
265
  const { message, sessionId } = req.body;
266
+ const sid = sessionId ?? 'default';
133
267
  if (!message) {
134
268
  res.status(400).json({ error: 'message is required' });
135
269
  return;
@@ -139,8 +273,12 @@ class WebChannel extends index_1.BaseChannel {
139
273
  role: 'user',
140
274
  content: message,
141
275
  timestamp: Date.now(),
142
- metadata: { sessionId: sessionId ?? 'default' },
276
+ metadata: { sessionId: sid },
143
277
  };
278
+ // Track conversation
279
+ if (!this.conversations.has(sid))
280
+ this.conversations.set(sid, []);
281
+ this.conversations.get(sid).push(msg);
144
282
  if (this.streamHandler) {
145
283
  try {
146
284
  await this.streamHandler(msg, res);
@@ -165,6 +303,154 @@ class WebChannel extends index_1.BaseChannel {
165
303
  res.status(500).json({ error: 'Internal error' });
166
304
  }
167
305
  });
306
+ // --- Multi-LLM Config API ---
307
+ this.app.get('/api/config', (_req, res) => {
308
+ res.json({
309
+ provider: this.currentProvider,
310
+ providers: [
311
+ { id: 'openai', name: 'OpenAI', baseUrl: 'https://api.openai.com/v1', models: ['gpt-4o', 'gpt-4o-mini', 'gpt-4-turbo'] },
312
+ { id: 'deepseek', name: 'DeepSeek', baseUrl: 'https://api.deepseek.com/v1', models: ['deepseek-chat', 'deepseek-reasoner'] },
313
+ { id: 'ollama', name: 'Ollama (Local)', baseUrl: 'http://localhost:11434/v1', models: ['llama3', 'mistral', 'codellama'] },
314
+ { id: 'qwen', name: 'Qwen', baseUrl: 'https://dashscope.aliyuncs.com/compatible-mode/v1', models: ['qwen-turbo', 'qwen-plus', 'qwen-max'] },
315
+ { id: 'anthropic', name: 'Anthropic', baseUrl: 'https://api.anthropic.com/v1', models: ['claude-sonnet-4-20250514', 'claude-3-5-haiku-20241022'] },
316
+ ],
317
+ });
318
+ });
319
+ this.app.post('/api/config', (req, res) => {
320
+ const { provider, model, baseUrl, apiKey } = req.body;
321
+ if (provider)
322
+ this.currentProvider = provider;
323
+ // Emit config change event for runtime to handle
324
+ this.emit('config:change', { provider, model, baseUrl, apiKey });
325
+ res.json({ ok: true, provider: this.currentProvider });
326
+ });
327
+ // --- Dashboard ---
328
+ this.app.get('/dashboard', (_req, res) => {
329
+ res.type('html').send(DASHBOARD_HTML);
330
+ });
331
+ this.app.get('/api/dashboard', (_req, res) => {
332
+ res.json(this.stats);
333
+ });
334
+ // --- Knowledge Base Upload ---
335
+ this.app.post('/api/kb/upload', async (req, res) => {
336
+ try {
337
+ const { content, filename } = req.body;
338
+ if (!content) {
339
+ res.status(400).json({ error: 'content required' });
340
+ return;
341
+ }
342
+ const kb = new knowledge_1.KnowledgeBase('.');
343
+ const result = await kb.addText(content, filename ?? 'upload');
344
+ this.stats.knowledgeFiles++;
345
+ res.json({ ok: true, chunks: result.chunks });
346
+ }
347
+ catch (err) {
348
+ res.status(500).json({ error: err instanceof Error ? err.message : 'Failed' });
349
+ }
350
+ });
351
+ this.app.get('/api/kb/stats', (_req, res) => {
352
+ try {
353
+ const kb = new knowledge_1.KnowledgeBase('.');
354
+ res.json(kb.getStats());
355
+ }
356
+ catch {
357
+ res.json({ totalEntries: 0, sources: [] });
358
+ }
359
+ });
360
+ // --- Health Check (detailed) ---
361
+ this.app.get('/api/health', (_req, res) => {
362
+ const uptimeMs = Date.now() - this.stats.startedAt;
363
+ res.json({
364
+ status: 'ok',
365
+ timestamp: Date.now(),
366
+ uptime: uptimeMs,
367
+ uptimeHuman: `${Math.floor(uptimeMs / 3600000)}h ${Math.floor((uptimeMs % 3600000) / 60000)}m`,
368
+ version: '0.7.0',
369
+ agent: this.agentName,
370
+ stats: {
371
+ sessions: this.stats.sessions,
372
+ messages: this.stats.messages,
373
+ errors: this.stats.errors,
374
+ avgResponseMs: this.stats.messages > 0 ? Math.round(this.stats.totalResponseMs / this.stats.messages) : 0,
375
+ },
376
+ memory: {
377
+ rss: process.memoryUsage().rss,
378
+ heapUsed: process.memoryUsage().heapUsed,
379
+ },
380
+ });
381
+ });
382
+ // --- Prometheus Metrics ---
383
+ this.app.get('/api/metrics', (_req, res) => {
384
+ const uptimeMs = Date.now() - this.stats.startedAt;
385
+ const avgLatency = this.llmCalls > 0 ? this.llmLatencySum / this.llmCalls : 0;
386
+ const mem = process.memoryUsage();
387
+ res.type('text/plain').send(`# HELP opc_uptime_seconds Agent uptime in seconds\n` +
388
+ `# TYPE opc_uptime_seconds gauge\n` +
389
+ `opc_uptime_seconds ${(uptimeMs / 1000).toFixed(1)}\n` +
390
+ `# HELP opc_requests_total Total requests\n` +
391
+ `# TYPE opc_requests_total counter\n` +
392
+ `opc_requests_total ${this.requestCount}\n` +
393
+ `# HELP opc_messages_total Total messages processed\n` +
394
+ `# TYPE opc_messages_total counter\n` +
395
+ `opc_messages_total ${this.stats.messages}\n` +
396
+ `# HELP opc_errors_total Total errors\n` +
397
+ `# TYPE opc_errors_total counter\n` +
398
+ `opc_errors_total ${this.stats.errors}\n` +
399
+ `# HELP opc_llm_latency_avg_ms Average LLM response latency\n` +
400
+ `# TYPE opc_llm_latency_avg_ms gauge\n` +
401
+ `opc_llm_latency_avg_ms ${avgLatency.toFixed(1)}\n` +
402
+ `# HELP opc_sessions_total Total sessions\n` +
403
+ `# TYPE opc_sessions_total counter\n` +
404
+ `opc_sessions_total ${this.stats.sessions}\n` +
405
+ `# HELP opc_token_usage_total Total token usage\n` +
406
+ `# TYPE opc_token_usage_total counter\n` +
407
+ `opc_token_usage_total ${this.stats.tokenUsage}\n` +
408
+ `# HELP process_resident_memory_bytes Resident memory size\n` +
409
+ `# TYPE process_resident_memory_bytes gauge\n` +
410
+ `process_resident_memory_bytes ${mem.rss}\n`);
411
+ });
412
+ // --- Conversation tracking & export ---
413
+ this.app.get('/api/conversations/export', (req, res) => {
414
+ const sessionId = req.query.sessionId;
415
+ const format = req.query.format ?? 'json';
416
+ const messages = sessionId ? (this.conversations.get(sessionId) ?? []) : Array.from(this.conversations.values()).flat();
417
+ if (format === 'markdown') {
418
+ const md = messages.map(m => `**${m.role}** (${new Date(m.timestamp).toISOString()}):\n${m.content}`).join('\n\n---\n\n');
419
+ res.type('text/markdown').send(md);
420
+ }
421
+ else if (format === 'csv') {
422
+ const header = 'id,role,content,timestamp\n';
423
+ const rows = messages.map(m => `"${m.id}","${m.role}","${m.content.replace(/"/g, '""')}",${m.timestamp}`).join('\n');
424
+ res.type('text/csv').send(header + rows);
425
+ }
426
+ else {
427
+ res.json({ sessionId: sessionId ?? 'all', messages, count: messages.length });
428
+ }
429
+ });
430
+ // --- Document Upload ---
431
+ this.app.post('/api/documents/upload', async (req, res) => {
432
+ try {
433
+ const { content, filename, mimeType } = req.body;
434
+ if (!content || !filename) {
435
+ res.status(400).json({ error: 'content and filename are required' });
436
+ return;
437
+ }
438
+ const kb = new knowledge_1.KnowledgeBase('.');
439
+ const result = await kb.addText(content, filename);
440
+ this.stats.knowledgeFiles++;
441
+ res.json({ ok: true, filename, chunks: result.chunks, chars: content.length });
442
+ }
443
+ catch (err) {
444
+ res.status(500).json({ error: err instanceof Error ? err.message : 'Upload failed' });
445
+ }
446
+ });
447
+ // --- Agent Templates Gallery ---
448
+ this.app.get('/api/templates', (_req, res) => {
449
+ res.json({ templates: AGENT_TEMPLATES });
450
+ });
451
+ this.app.get('/templates', (_req, res) => {
452
+ res.type('html').send(TEMPLATES_HTML);
453
+ });
168
454
  // Legacy endpoint
169
455
  this.app.post('/chat', async (req, res) => {
170
456
  if (!this.handler) {
package/dist/cli.js CHANGED
@@ -52,9 +52,12 @@ const financial_advisor_1 = require("./templates/financial-advisor");
52
52
  const executive_assistant_1 = require("./templates/executive-assistant");
53
53
  const analytics_1 = require("./analytics");
54
54
  const openclaw_1 = require("./deploy/openclaw");
55
+ const hermes_1 = require("./deploy/hermes");
55
56
  const workflow_1 = require("./core/workflow");
56
57
  const versioning_1 = require("./core/versioning");
57
58
  const providers_1 = require("./providers");
59
+ const knowledge_1 = require("./core/knowledge");
60
+ const marketplace_1 = require("./marketplace");
58
61
  const program = new commander_1.Command();
59
62
  const color = {
60
63
  green: (s) => `\x1b[32m${s}\x1b[0m`,
@@ -110,7 +113,7 @@ async function select(question, options) {
110
113
  program
111
114
  .name('opc')
112
115
  .description('OPC Agent - Open Agent Framework for business workstations')
113
- .version('0.5.1');
116
+ .version('0.6.0');
114
117
  // ── Init command ─────────────────────────────────────────────
115
118
  program
116
119
  .command('init')
@@ -171,7 +174,30 @@ OPC_LLM_MODEL=gpt-4o-mini
171
174
  },
172
175
  }, null, 2));
173
176
  // .gitignore
174
- fs.writeFileSync(path.join(dir, '.gitignore'), 'node_modules\n.env\n');
177
+ fs.writeFileSync(path.join(dir, '.gitignore'), 'node_modules\n.env\n.opc-knowledge.json\ndata/\n');
178
+ // Dockerfile
179
+ fs.writeFileSync(path.join(dir, 'Dockerfile'), `FROM node:22-alpine
180
+ WORKDIR /app
181
+ COPY package.json package-lock.json* ./
182
+ RUN npm ci --production 2>/dev/null || npm install --production
183
+ COPY oad.yaml .env* ./
184
+ COPY prompts/ ./prompts/ 2>/dev/null || true
185
+ EXPOSE 3000
186
+ CMD ["npx", "opc", "run"]
187
+ `);
188
+ // docker-compose.yml
189
+ fs.writeFileSync(path.join(dir, 'docker-compose.yml'), `version: '3.8'
190
+ services:
191
+ agent:
192
+ build: .
193
+ ports:
194
+ - "3000:3000"
195
+ env_file:
196
+ - .env
197
+ volumes:
198
+ - ./oad.yaml:/app/oad.yaml:ro
199
+ restart: unless-stopped
200
+ `);
175
201
  // README.md
176
202
  fs.writeFileSync(path.join(dir, 'README.md'), `# ${name}
177
203
 
@@ -214,6 +240,8 @@ Edit \`oad.yaml\` to customize your agent's personality, skills, and behavior.
214
240
  console.log(` ${icon.file} .env.example - Environment template`);
215
241
  console.log(` ${icon.file} .env - Environment config (edit this!)`);
216
242
  console.log(` ${icon.file} .gitignore`);
243
+ console.log(` ${icon.file} Dockerfile`);
244
+ console.log(` ${icon.file} docker-compose.yml`);
217
245
  console.log(` ${icon.file} README.md`);
218
246
  console.log(`\n Template: ${color.cyan(template)}`);
219
247
  console.log(`\n${color.bold('Next steps:')}`);
@@ -471,13 +499,24 @@ program
471
499
  .option('-o, --output <dir>', 'Output directory')
472
500
  .option('--install', 'Also register in OpenClaw config')
473
501
  .action(async (opts) => {
474
- if (opts.target !== 'openclaw') {
475
- console.error(`${icon.error} Unknown target: ${color.bold(opts.target)}. Supported: openclaw`);
502
+ if (opts.target !== 'openclaw' && opts.target !== 'hermes') {
503
+ console.error(`${icon.error} Unknown target: ${color.bold(opts.target)}. Supported: openclaw, hermes`);
476
504
  process.exit(1);
477
505
  }
478
506
  try {
479
507
  const runtime = new runtime_1.AgentRuntime();
480
508
  const config = await runtime.loadConfig(opts.file);
509
+ if (opts.target === 'hermes') {
510
+ const agentId = config.metadata.name.toLowerCase().replace(/[^a-z0-9-]/g, '-');
511
+ const outputDir = path.resolve(opts.output ?? `hermes-${agentId}`);
512
+ console.log(`\n${icon.rocket} ${color.bold('Deploy to Hermes')}\n`);
513
+ const result = (0, hermes_1.deployToHermes)({ oad: config, outputDir });
514
+ console.log(`${icon.success} Generated ${result.files.length} files in ${color.bold(outputDir)}`);
515
+ for (const f of result.files)
516
+ console.log(` ${icon.file} ${f}`);
517
+ console.log();
518
+ return;
519
+ }
481
520
  const agentId = config.metadata.name.toLowerCase().replace(/[^a-z0-9-]/g, '-');
482
521
  const homeDir = process.env.HOME || process.env.USERPROFILE || '';
483
522
  const defaultOutput = path.join(homeDir, '.openclaw', 'agents', agentId, 'workspace');
@@ -602,5 +641,97 @@ function loadDotEnv() {
602
641
  // ignore
603
642
  }
604
643
  }
644
+ // 📚 Knowledge Base commands ────────────────────────────────
645
+ const kbCmd = program.command('kb').description('Manage knowledge base');
646
+ kbCmd
647
+ .command('add')
648
+ .argument('<file>', 'File to index')
649
+ .action(async (file) => {
650
+ try {
651
+ const kb = new knowledge_1.KnowledgeBase('.');
652
+ const result = await kb.addFile(file);
653
+ console.log(`${icon.success} Indexed ${color.bold(file)} → ${result.chunks} chunks`);
654
+ }
655
+ catch (err) {
656
+ console.error(`${icon.error}`, err instanceof Error ? err.message : err);
657
+ process.exit(1);
658
+ }
659
+ });
660
+ kbCmd
661
+ .command('search')
662
+ .argument('<query>', 'Search query')
663
+ .option('-k, --top-k <n>', 'Number of results', '5')
664
+ .action(async (query, opts) => {
665
+ const kb = new knowledge_1.KnowledgeBase('.');
666
+ const results = await kb.search(query, parseInt(opts.topK));
667
+ if (results.length === 0) {
668
+ console.log(`${icon.info} No results found.`);
669
+ return;
670
+ }
671
+ console.log(`\n${icon.search} Results for "${color.bold(query)}":\n`);
672
+ for (const r of results) {
673
+ console.log(` ${color.cyan(`[${(r.score * 100).toFixed(0)}%]`)} ${color.dim(`(${r.source})`)}`);
674
+ console.log(` ${r.content.slice(0, 200)}${r.content.length > 200 ? '...' : ''}\n`);
675
+ }
676
+ });
677
+ kbCmd.command('stats').action(() => {
678
+ const kb = new knowledge_1.KnowledgeBase('.');
679
+ const stats = kb.getStats();
680
+ console.log(`\n${icon.gear} Knowledge Base Stats\n`);
681
+ console.log(` Entries: ${stats.totalEntries}`);
682
+ console.log(` Sources: ${stats.sources.join(', ') || '(none)'}`);
683
+ console.log(` Updated: ${stats.updatedAt}\n`);
684
+ });
685
+ kbCmd.command('clear').action(() => {
686
+ const kb = new knowledge_1.KnowledgeBase('.');
687
+ kb.clear();
688
+ console.log(`${icon.success} Knowledge base cleared.`);
689
+ });
690
+ // 📦 Marketplace commands ───────────────────────────────────
691
+ program
692
+ .command('publish')
693
+ .description('Package agent for distribution')
694
+ .option('-f, --file <file>', 'OAD file', 'oad.yaml')
695
+ .option('-o, --output <dir>', 'Output directory', '.')
696
+ .option('--include-kb', 'Include knowledge base')
697
+ .action(async (opts) => {
698
+ try {
699
+ console.log(`\n${icon.package} Packaging agent...\n`);
700
+ const result = await (0, marketplace_1.publishAgent)({
701
+ oadPath: opts.file,
702
+ outputDir: opts.output,
703
+ includeKnowledge: opts.includeKb,
704
+ });
705
+ console.log(`${icon.success} Published: ${color.bold(result.archivePath)}`);
706
+ console.log(` Name: ${result.manifest.name}`);
707
+ console.log(` Version: ${result.manifest.version}`);
708
+ console.log(` Files: ${result.manifest.files.length}`);
709
+ console.log();
710
+ }
711
+ catch (err) {
712
+ console.error(`${icon.error} Publish failed:`, err instanceof Error ? err.message : err);
713
+ process.exit(1);
714
+ }
715
+ });
716
+ program
717
+ .command('install')
718
+ .description('Install agent from package')
719
+ .argument('<source>', 'Package file path or URL')
720
+ .option('-d, --dir <dir>', 'Install directory')
721
+ .action(async (source, opts) => {
722
+ try {
723
+ console.log(`\n${icon.package} Installing agent from ${color.bold(source)}...\n`);
724
+ const result = await (0, marketplace_1.installAgent)({ source, targetDir: opts.dir });
725
+ console.log(`${icon.success} Installed: ${color.bold(result.manifest.name)} v${result.manifest.version}`);
726
+ console.log(` Directory: ${result.dir}`);
727
+ console.log(`\n${color.bold('Next steps:')}`);
728
+ console.log(` cd ${result.dir}`);
729
+ console.log(` opc run\n`);
730
+ }
731
+ catch (err) {
732
+ console.error(`${icon.error} Install failed:`, err instanceof Error ? err.message : err);
733
+ process.exit(1);
734
+ }
735
+ });
605
736
  program.parse();
606
737
  //# sourceMappingURL=cli.js.map
@@ -0,0 +1,13 @@
1
+ import type { Request, Response, NextFunction } from 'express';
2
+ export interface AuthConfig {
3
+ apiKeys: string[];
4
+ sessionIsolation?: boolean;
5
+ }
6
+ export interface AuthSession {
7
+ apiKey: string;
8
+ userId: string;
9
+ createdAt: number;
10
+ }
11
+ export declare function createAuthMiddleware(config: AuthConfig): (req: Request, res: Response, next: NextFunction) => void;
12
+ export declare function getActiveSessions(): AuthSession[];
13
+ //# sourceMappingURL=auth.d.ts.map
@@ -0,0 +1,41 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.createAuthMiddleware = createAuthMiddleware;
4
+ exports.getActiveSessions = getActiveSessions;
5
+ const sessions = new Map();
6
+ function createAuthMiddleware(config) {
7
+ return (req, res, next) => {
8
+ // Skip auth for non-API routes and health/metrics
9
+ if (!req.path.startsWith('/api/') || req.path === '/api/health' || req.path === '/api/metrics') {
10
+ next();
11
+ return;
12
+ }
13
+ const apiKey = req.headers['x-api-key']
14
+ ?? req.headers['authorization']?.replace(/^Bearer\s+/i, '')
15
+ ?? req.query.apiKey;
16
+ if (!apiKey || !config.apiKeys.includes(apiKey)) {
17
+ res.status(401).json({ error: 'Unauthorized. Provide a valid API key via X-API-Key header, Bearer token, or ?apiKey query.' });
18
+ return;
19
+ }
20
+ // Derive userId from API key for session isolation
21
+ const userId = `user_${hashKey(apiKey)}`;
22
+ if (!sessions.has(apiKey)) {
23
+ sessions.set(apiKey, { apiKey, userId, createdAt: Date.now() });
24
+ }
25
+ // Attach user info to request
26
+ req.userId = userId;
27
+ req.sessionPrefix = config.sessionIsolation ? `${userId}:` : '';
28
+ next();
29
+ };
30
+ }
31
+ function hashKey(key) {
32
+ let h = 0;
33
+ for (let i = 0; i < key.length; i++) {
34
+ h = ((h << 5) - h + key.charCodeAt(i)) | 0;
35
+ }
36
+ return Math.abs(h).toString(36);
37
+ }
38
+ function getActiveSessions() {
39
+ return Array.from(sessions.values());
40
+ }
41
+ //# sourceMappingURL=auth.js.map
@@ -0,0 +1,28 @@
1
+ export declare class KnowledgeBase {
2
+ private store;
3
+ private storePath;
4
+ constructor(baseDir?: string);
5
+ private load;
6
+ private save;
7
+ addFile(filePath: string): Promise<{
8
+ chunks: number;
9
+ }>;
10
+ addText(text: string, source?: string): Promise<{
11
+ chunks: number;
12
+ }>;
13
+ search(query: string, topK?: number): Promise<Array<{
14
+ content: string;
15
+ score: number;
16
+ source: string;
17
+ }>>;
18
+ /** Build context string for injection into LLM calls */
19
+ getContext(query: string, topK?: number, minScore?: number): Promise<string>;
20
+ getStats(): {
21
+ totalEntries: number;
22
+ sources: string[];
23
+ updatedAt: string;
24
+ };
25
+ clear(): void;
26
+ removeSource(source: string): number;
27
+ }
28
+ //# sourceMappingURL=knowledge.d.ts.map