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.
- package/dist/channels/web.d.ts +7 -0
- package/dist/channels/web.js +125 -0
- package/dist/cli.js +135 -4
- package/dist/core/knowledge.d.ts +28 -0
- package/dist/core/knowledge.js +212 -0
- package/dist/deploy/hermes.d.ts +11 -0
- package/dist/deploy/hermes.js +147 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.js +9 -1
- package/dist/marketplace/index.d.ts +34 -0
- package/dist/marketplace/index.js +202 -0
- package/package.json +1 -1
- package/src/channels/web.ts +128 -0
- package/src/cli.ts +149 -4
- package/src/core/knowledge.ts +210 -0
- package/src/deploy/hermes.ts +156 -0
- package/src/index.ts +7 -0
- package/src/marketplace/index.ts +223 -0
- package/templates/Dockerfile +15 -0
- package/templates/docker-compose.yml +21 -0
package/dist/channels/web.d.ts
CHANGED
|
@@ -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;
|
package/dist/channels/web.js
CHANGED
|
@@ -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.
|
|
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
|