opc-agent 0.5.1 → 0.6.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.
@@ -8,6 +8,13 @@ export declare class WebChannel extends BaseChannel {
8
8
  private port;
9
9
  private streamHandler;
10
10
  private agentName;
11
+ private currentProvider;
12
+ private stats;
13
+ private eventHandlers;
14
+ private emit;
15
+ onConfigChange(handler: (config: any) => void): void;
16
+ trackMessage(responseMs: number, tokens?: number): void;
17
+ trackSession(): void;
11
18
  constructor(port?: number);
12
19
  setAgentName(name: string): void;
13
20
  onStreamMessage(handler: (msg: Message, res: Response) => Promise<void>): void;
@@ -6,6 +6,7 @@ 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");
9
10
  const CHAT_HTML = `<!DOCTYPE html>
10
11
  <html lang="en">
11
12
  <head>
@@ -97,6 +98,57 @@ fetch('/api/info').then(r=>r.json()).then(d=>{if(d.name)document.getElementById(
97
98
  </script>
98
99
  </body>
99
100
  </html>`;
101
+ const DASHBOARD_HTML = `<!DOCTYPE html>
102
+ <html lang="en">
103
+ <head>
104
+ <meta charset="UTF-8">
105
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
106
+ <title>OPC Dashboard</title>
107
+ <style>
108
+ *{margin:0;padding:0;box-sizing:border-box}
109
+ body{background:#0a0a0f;color:#e0e0e0;font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,sans-serif;padding:24px}
110
+ h1{font-size:24px;margin-bottom:24px;color:#fff}
111
+ .grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(220px,1fr));gap:16px;margin-bottom:32px}
112
+ .card{background:#12121a;border:1px solid #1e1e2e;border-radius:12px;padding:20px}
113
+ .card .label{font-size:12px;color:#888;text-transform:uppercase;letter-spacing:1px}
114
+ .card .value{font-size:32px;font-weight:700;color:#818cf8;margin-top:4px}
115
+ .card .sub{font-size:12px;color:#555;margin-top:4px}
116
+ nav{margin-bottom:24px}
117
+ nav a{color:#818cf8;text-decoration:none;margin-right:16px;font-size:14px}
118
+ nav a:hover{text-decoration:underline}
119
+ .chart{background:#12121a;border:1px solid #1e1e2e;border-radius:12px;padding:20px;margin-bottom:16px}
120
+ .chart h3{font-size:14px;color:#888;margin-bottom:12px}
121
+ </style>
122
+ </head>
123
+ <body>
124
+ <nav><a href="/">← Chat</a><a href="/dashboard">Dashboard</a></nav>
125
+ <h1>📊 Agent Dashboard</h1>
126
+ <div class="grid">
127
+ <div class="card"><div class="label">Sessions</div><div class="value" id="sessions">0</div></div>
128
+ <div class="card"><div class="label">Messages</div><div class="value" id="messages">0</div></div>
129
+ <div class="card"><div class="label">Avg Response</div><div class="value" id="avgMs">0ms</div></div>
130
+ <div class="card"><div class="label">Token Usage</div><div class="value" id="tokens">0</div></div>
131
+ <div class="card"><div class="label">Uptime</div><div class="value" id="uptime">0m</div></div>
132
+ <div class="card"><div class="label">Knowledge Files</div><div class="value" id="kb">0</div></div>
133
+ </div>
134
+ <div class="chart"><h3>Messages Over Time</h3><svg id="chart" width="100%" height="120" viewBox="0 0 600 120"></svg></div>
135
+ <script>
136
+ async function refresh(){
137
+ try{
138
+ const r=await fetch('/api/dashboard');const d=await r.json();
139
+ document.getElementById('sessions').textContent=d.sessions;
140
+ document.getElementById('messages').textContent=d.messages;
141
+ document.getElementById('avgMs').textContent=d.messages>0?Math.round(d.totalResponseMs/d.messages)+'ms':'0ms';
142
+ document.getElementById('tokens').textContent=d.tokenUsage.toLocaleString();
143
+ document.getElementById('kb').textContent=d.knowledgeFiles;
144
+ const mins=Math.round((Date.now()-d.startedAt)/60000);
145
+ document.getElementById('uptime').textContent=mins<60?mins+'m':Math.round(mins/60)+'h '+mins%60+'m';
146
+ }catch{}
147
+ }
148
+ refresh();setInterval(refresh,5000);
149
+ </script>
150
+ </body>
151
+ </html>`;
100
152
  class WebChannel extends index_1.BaseChannel {
101
153
  type = 'web';
102
154
  app;
@@ -104,6 +156,25 @@ class WebChannel extends index_1.BaseChannel {
104
156
  port;
105
157
  streamHandler = null;
106
158
  agentName = 'OPC Agent';
159
+ currentProvider = 'openai';
160
+ stats = { sessions: 0, messages: 0, totalResponseMs: 0, tokenUsage: 0, knowledgeFiles: 0, startedAt: Date.now() };
161
+ eventHandlers = new Map();
162
+ emit(event, data) {
163
+ const handlers = this.eventHandlers.get(event) ?? [];
164
+ for (const h of handlers)
165
+ h(data);
166
+ }
167
+ onConfigChange(handler) {
168
+ const handlers = this.eventHandlers.get('config:change') ?? [];
169
+ handlers.push(handler);
170
+ this.eventHandlers.set('config:change', handlers);
171
+ }
172
+ trackMessage(responseMs, tokens = 0) {
173
+ this.stats.messages++;
174
+ this.stats.totalResponseMs += responseMs;
175
+ this.stats.tokenUsage += tokens;
176
+ }
177
+ trackSession() { this.stats.sessions++; }
107
178
  constructor(port = 3000) {
108
179
  super();
109
180
  this.port = port;
@@ -165,6 +236,60 @@ class WebChannel extends index_1.BaseChannel {
165
236
  res.status(500).json({ error: 'Internal error' });
166
237
  }
167
238
  });
239
+ // --- Multi-LLM Config API ---
240
+ this.app.get('/api/config', (_req, res) => {
241
+ res.json({
242
+ provider: this.currentProvider,
243
+ providers: [
244
+ { id: 'openai', name: 'OpenAI', baseUrl: 'https://api.openai.com/v1', models: ['gpt-4o', 'gpt-4o-mini', 'gpt-4-turbo'] },
245
+ { id: 'deepseek', name: 'DeepSeek', baseUrl: 'https://api.deepseek.com/v1', models: ['deepseek-chat', 'deepseek-reasoner'] },
246
+ { id: 'ollama', name: 'Ollama (Local)', baseUrl: 'http://localhost:11434/v1', models: ['llama3', 'mistral', 'codellama'] },
247
+ { id: 'qwen', name: 'Qwen', baseUrl: 'https://dashscope.aliyuncs.com/compatible-mode/v1', models: ['qwen-turbo', 'qwen-plus', 'qwen-max'] },
248
+ { id: 'anthropic', name: 'Anthropic', baseUrl: 'https://api.anthropic.com/v1', models: ['claude-sonnet-4-20250514', 'claude-3-5-haiku-20241022'] },
249
+ ],
250
+ });
251
+ });
252
+ this.app.post('/api/config', (req, res) => {
253
+ const { provider, model, baseUrl, apiKey } = req.body;
254
+ if (provider)
255
+ this.currentProvider = provider;
256
+ // Emit config change event for runtime to handle
257
+ this.emit('config:change', { provider, model, baseUrl, apiKey });
258
+ res.json({ ok: true, provider: this.currentProvider });
259
+ });
260
+ // --- Dashboard ---
261
+ this.app.get('/dashboard', (_req, res) => {
262
+ res.type('html').send(DASHBOARD_HTML);
263
+ });
264
+ this.app.get('/api/dashboard', (_req, res) => {
265
+ res.json(this.stats);
266
+ });
267
+ // --- Knowledge Base Upload ---
268
+ this.app.post('/api/kb/upload', async (req, res) => {
269
+ try {
270
+ const { content, filename } = req.body;
271
+ if (!content) {
272
+ res.status(400).json({ error: 'content required' });
273
+ return;
274
+ }
275
+ const kb = new knowledge_1.KnowledgeBase('.');
276
+ const result = await kb.addText(content, filename ?? 'upload');
277
+ this.stats.knowledgeFiles++;
278
+ res.json({ ok: true, chunks: result.chunks });
279
+ }
280
+ catch (err) {
281
+ res.status(500).json({ error: err instanceof Error ? err.message : 'Failed' });
282
+ }
283
+ });
284
+ this.app.get('/api/kb/stats', (_req, res) => {
285
+ try {
286
+ const kb = new knowledge_1.KnowledgeBase('.');
287
+ res.json(kb.getStats());
288
+ }
289
+ catch {
290
+ res.json({ totalEntries: 0, sources: [] });
291
+ }
292
+ });
168
293
  // Legacy endpoint
169
294
  this.app.post('/chat', async (req, res) => {
170
295
  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,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
@@ -0,0 +1,212 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.KnowledgeBase = void 0;
37
+ /**
38
+ * Knowledge Base / RAG - Local vector storage with semantic search
39
+ */
40
+ const fs = __importStar(require("fs"));
41
+ const path = __importStar(require("path"));
42
+ const crypto = __importStar(require("crypto"));
43
+ const CHUNK_SIZE = 500; // chars per chunk
44
+ const CHUNK_OVERLAP = 50;
45
+ const STORE_FILE = '.opc-knowledge.json';
46
+ function splitText(text, chunkSize = CHUNK_SIZE, overlap = CHUNK_OVERLAP) {
47
+ const chunks = [];
48
+ // Split by paragraphs first, then by size
49
+ const paragraphs = text.split(/\n\s*\n/).filter(p => p.trim());
50
+ let current = '';
51
+ for (const para of paragraphs) {
52
+ if (current.length + para.length > chunkSize && current.length > 0) {
53
+ chunks.push(current.trim());
54
+ // Keep overlap from end of current
55
+ current = current.slice(-overlap) + '\n\n' + para;
56
+ }
57
+ else {
58
+ current += (current ? '\n\n' : '') + para;
59
+ }
60
+ }
61
+ if (current.trim())
62
+ chunks.push(current.trim());
63
+ // If any chunk is still too large, split by sentences
64
+ const result = [];
65
+ for (const chunk of chunks) {
66
+ if (chunk.length <= chunkSize * 1.5) {
67
+ result.push(chunk);
68
+ }
69
+ else {
70
+ const sentences = chunk.split(/(?<=[.!?])\s+/);
71
+ let buf = '';
72
+ for (const s of sentences) {
73
+ if (buf.length + s.length > chunkSize && buf) {
74
+ result.push(buf.trim());
75
+ buf = buf.slice(-overlap) + ' ' + s;
76
+ }
77
+ else {
78
+ buf += (buf ? ' ' : '') + s;
79
+ }
80
+ }
81
+ if (buf.trim())
82
+ result.push(buf.trim());
83
+ }
84
+ }
85
+ return result;
86
+ }
87
+ // Simple TF-IDF-like embedding (no external dependencies)
88
+ // For production, replace with real embedding API
89
+ function simpleEmbed(text) {
90
+ const words = text.toLowerCase().replace(/[^a-z0-9\s]/g, '').split(/\s+/).filter(Boolean);
91
+ const dim = 128;
92
+ const vec = new Array(dim).fill(0);
93
+ for (const word of words) {
94
+ const hash = crypto.createHash('md5').update(word).digest();
95
+ for (let i = 0; i < dim; i++) {
96
+ vec[i] += (hash[i % hash.length] - 128) / 128;
97
+ }
98
+ }
99
+ // Normalize
100
+ const mag = Math.sqrt(vec.reduce((s, v) => s + v * v, 0)) || 1;
101
+ return vec.map(v => v / mag);
102
+ }
103
+ function cosineSimilarity(a, b) {
104
+ let dot = 0, magA = 0, magB = 0;
105
+ for (let i = 0; i < a.length; i++) {
106
+ dot += a[i] * b[i];
107
+ magA += a[i] * a[i];
108
+ magB += b[i] * b[i];
109
+ }
110
+ return dot / (Math.sqrt(magA) * Math.sqrt(magB) || 1);
111
+ }
112
+ class KnowledgeBase {
113
+ store;
114
+ storePath;
115
+ constructor(baseDir = '.') {
116
+ this.storePath = path.join(baseDir, STORE_FILE);
117
+ this.store = this.load();
118
+ }
119
+ load() {
120
+ try {
121
+ if (fs.existsSync(this.storePath)) {
122
+ return JSON.parse(fs.readFileSync(this.storePath, 'utf-8'));
123
+ }
124
+ }
125
+ catch { /* ignore */ }
126
+ return { entries: [], version: 1, updatedAt: new Date().toISOString() };
127
+ }
128
+ save() {
129
+ this.store.updatedAt = new Date().toISOString();
130
+ fs.writeFileSync(this.storePath, JSON.stringify(this.store), 'utf-8');
131
+ }
132
+ async addFile(filePath) {
133
+ const absPath = path.resolve(filePath);
134
+ if (!fs.existsSync(absPath)) {
135
+ throw new Error(`File not found: ${absPath}`);
136
+ }
137
+ const content = fs.readFileSync(absPath, 'utf-8');
138
+ const filename = path.basename(absPath);
139
+ // Remove existing entries for this file
140
+ this.store.entries = this.store.entries.filter(e => e.metadata.source !== filename);
141
+ const chunks = splitText(content);
142
+ for (let i = 0; i < chunks.length; i++) {
143
+ const chunk = chunks[i];
144
+ this.store.entries.push({
145
+ id: `${filename}_${i}_${Date.now()}`,
146
+ content: chunk,
147
+ embedding: simpleEmbed(chunk),
148
+ metadata: {
149
+ source: filename,
150
+ chunkIndex: i,
151
+ totalChunks: chunks.length,
152
+ addedAt: new Date().toISOString(),
153
+ },
154
+ });
155
+ }
156
+ this.save();
157
+ return { chunks: chunks.length };
158
+ }
159
+ async addText(text, source = 'manual') {
160
+ const chunks = splitText(text);
161
+ for (let i = 0; i < chunks.length; i++) {
162
+ this.store.entries.push({
163
+ id: `${source}_${i}_${Date.now()}`,
164
+ content: chunks[i],
165
+ embedding: simpleEmbed(chunks[i]),
166
+ metadata: { source, chunkIndex: i, totalChunks: chunks.length, addedAt: new Date().toISOString() },
167
+ });
168
+ }
169
+ this.save();
170
+ return { chunks: chunks.length };
171
+ }
172
+ async search(query, topK = 5) {
173
+ if (this.store.entries.length === 0)
174
+ return [];
175
+ const queryEmb = simpleEmbed(query);
176
+ const scored = this.store.entries.map(entry => ({
177
+ content: entry.content,
178
+ score: cosineSimilarity(queryEmb, entry.embedding),
179
+ source: String(entry.metadata.source ?? 'unknown'),
180
+ }));
181
+ scored.sort((a, b) => b.score - a.score);
182
+ return scored.slice(0, topK);
183
+ }
184
+ /** Build context string for injection into LLM calls */
185
+ async getContext(query, topK = 3, minScore = 0.1) {
186
+ const results = await this.search(query, topK);
187
+ const relevant = results.filter(r => r.score >= minScore);
188
+ if (relevant.length === 0)
189
+ return '';
190
+ return `\n\n--- Relevant Knowledge ---\n${relevant.map((r, i) => `[${i + 1}] (source: ${r.source}, relevance: ${(r.score * 100).toFixed(0)}%)\n${r.content}`).join('\n\n')}\n--- End Knowledge ---\n`;
191
+ }
192
+ getStats() {
193
+ const sources = [...new Set(this.store.entries.map(e => String(e.metadata.source)))];
194
+ return {
195
+ totalEntries: this.store.entries.length,
196
+ sources,
197
+ updatedAt: this.store.updatedAt,
198
+ };
199
+ }
200
+ clear() {
201
+ this.store.entries = [];
202
+ this.save();
203
+ }
204
+ removeSource(source) {
205
+ const before = this.store.entries.length;
206
+ this.store.entries = this.store.entries.filter(e => e.metadata.source !== source);
207
+ this.save();
208
+ return before - this.store.entries.length;
209
+ }
210
+ }
211
+ exports.KnowledgeBase = KnowledgeBase;
212
+ //# sourceMappingURL=knowledge.js.map
@@ -0,0 +1,11 @@
1
+ import type { OADDocument } from '../schema/oad';
2
+ export interface HermesDeployOptions {
3
+ oad: OADDocument;
4
+ outputDir: string;
5
+ }
6
+ export interface HermesDeployResult {
7
+ outputDir: string;
8
+ files: string[];
9
+ }
10
+ export declare function deployToHermes(options: HermesDeployOptions): HermesDeployResult;
11
+ //# sourceMappingURL=hermes.d.ts.map