omniwire 3.1.2 → 3.1.4
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/.omniwire-state/update-state.json +2 -2
- package/dist/mcp/server.js +373 -52
- package/dist/mcp/server.js.map +1 -1
- package/package.json +1 -1
package/dist/mcp/server.js
CHANGED
|
@@ -6,6 +6,8 @@
|
|
|
6
6
|
// authenticated, encrypted SSH channels. The "exec" references below are SSH2 methods.
|
|
7
7
|
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
8
8
|
import { z } from 'zod';
|
|
9
|
+
import { readFileSync, writeFileSync, mkdirSync, existsSync } from 'node:fs';
|
|
10
|
+
import { join } from 'node:path';
|
|
9
11
|
import { ShellManager, kernelExec } from '../nodes/shell.js';
|
|
10
12
|
import { RealtimeChannel } from '../nodes/realtime.js';
|
|
11
13
|
import { TunnelManager } from '../nodes/tunnel.js';
|
|
@@ -199,8 +201,11 @@ function cbRecordFail(err) { cbFailCount++; cbLastError = err; }
|
|
|
199
201
|
function sqlEscape(val) {
|
|
200
202
|
return val.replace(/'/g, "''").replace(/\\/g, '\\\\').replace(/\0/g, '');
|
|
201
203
|
}
|
|
202
|
-
/** Fire-and-forget write to CyberBase. Never blocks, never throws. */
|
|
204
|
+
/** Fire-and-forget write to CyberBase + Obsidian vault + Canvas. Never blocks, never throws. */
|
|
203
205
|
function cb(category, key, value) {
|
|
206
|
+
// Sync to Obsidian vault + Canvas mindmap (local, synchronous, best-effort)
|
|
207
|
+
syncVault(category, key, value);
|
|
208
|
+
// Sync to CyberBase PostgreSQL (remote, async, queued)
|
|
204
209
|
if (!cbManager || cbCircuitOpen())
|
|
205
210
|
return;
|
|
206
211
|
const valEsc = sqlEscape(value).slice(0, 50000);
|
|
@@ -239,6 +244,199 @@ async function drainCb() {
|
|
|
239
244
|
else
|
|
240
245
|
cbDraining = false;
|
|
241
246
|
}
|
|
247
|
+
// -- Obsidian + Canvas auto-sync ------------------------------------------------
|
|
248
|
+
// Mirrors CyberBase writes to local Obsidian vault + Canvas mindmap.
|
|
249
|
+
// Vault path is resolved at startup; if it doesn't exist, sync is silently skipped.
|
|
250
|
+
const VAULT_ROOT = join(process.env.USERPROFILE ?? process.env.HOME ?? '', 'Documents', 'BuisnessProjects', 'CyberBase');
|
|
251
|
+
const CANVAS_PATH = join(VAULT_ROOT, 'CyberBase MindMap.canvas');
|
|
252
|
+
const vaultExists = existsSync(VAULT_ROOT);
|
|
253
|
+
/** Map CyberBase category → Obsidian vault subfolder */
|
|
254
|
+
function vaultFolder(category) {
|
|
255
|
+
const cat = category.toLowerCase();
|
|
256
|
+
if (cat.startsWith('project'))
|
|
257
|
+
return 'projects';
|
|
258
|
+
if (cat.startsWith('infra') || cat.startsWith('tool') || cat.startsWith('mesh'))
|
|
259
|
+
return 'infrastructure';
|
|
260
|
+
if (cat.startsWith('vuln') || cat.startsWith('security') || cat.startsWith('cve'))
|
|
261
|
+
return 'knowledge/security-kb';
|
|
262
|
+
if (cat.startsWith('cred'))
|
|
263
|
+
return 'credentials';
|
|
264
|
+
if (cat.startsWith('system') || cat.startsWith('rule'))
|
|
265
|
+
return 'system';
|
|
266
|
+
if (cat.startsWith('log'))
|
|
267
|
+
return 'logs';
|
|
268
|
+
if (cat.startsWith('sync'))
|
|
269
|
+
return 'sync';
|
|
270
|
+
if (cat.startsWith('note') || cat.startsWith('memo'))
|
|
271
|
+
return 'memory';
|
|
272
|
+
return 'knowledge';
|
|
273
|
+
}
|
|
274
|
+
/** Sanitize a key into a valid filename */
|
|
275
|
+
function sanitizeFilename(key) {
|
|
276
|
+
return key.replace(/[<>:"/\\|?*]/g, '-').replace(/^\.+/, '').slice(0, 120);
|
|
277
|
+
}
|
|
278
|
+
/** Auto-sync a knowledge entry to Obsidian vault as a .md file */
|
|
279
|
+
function syncObsidian(category, key, value) {
|
|
280
|
+
if (!vaultExists)
|
|
281
|
+
return;
|
|
282
|
+
try {
|
|
283
|
+
const folder = join(VAULT_ROOT, vaultFolder(category));
|
|
284
|
+
if (!existsSync(folder))
|
|
285
|
+
mkdirSync(folder, { recursive: true });
|
|
286
|
+
const filename = sanitizeFilename(key) + '.md';
|
|
287
|
+
const filepath = join(folder, filename);
|
|
288
|
+
const frontmatter = `---\nsource: omniwire\ncategory: ${category}\nkey: ${key}\nupdated: ${new Date().toISOString()}\n---\n\n`;
|
|
289
|
+
// If value looks like markdown, write as-is; otherwise wrap in code block
|
|
290
|
+
const body = value.includes('\n') && (value.includes('#') || value.includes('|') || value.includes('- '))
|
|
291
|
+
? value
|
|
292
|
+
: `\`\`\`\n${value}\n\`\`\``;
|
|
293
|
+
writeFileSync(filepath, frontmatter + body, 'utf-8');
|
|
294
|
+
}
|
|
295
|
+
catch { /* vault sync is best-effort */ }
|
|
296
|
+
}
|
|
297
|
+
/** Find a non-overlapping position for a new canvas node using grid placement */
|
|
298
|
+
function findFreeCanvasPosition(existingNodes, width, height) {
|
|
299
|
+
const GRID_X = 500; // horizontal spacing
|
|
300
|
+
const GRID_Y = 400; // vertical spacing
|
|
301
|
+
const PADDING = 80; // minimum gap between nodes
|
|
302
|
+
const MAX_COLS = 6;
|
|
303
|
+
// Check if a position collides with any existing node
|
|
304
|
+
const collides = (x, y) => {
|
|
305
|
+
for (const n of existingNodes) {
|
|
306
|
+
const overlap = x < n.x + n.w + PADDING &&
|
|
307
|
+
x + width + PADDING > n.x &&
|
|
308
|
+
y < n.y + n.h + PADDING &&
|
|
309
|
+
y + height + PADDING > n.y;
|
|
310
|
+
if (overlap)
|
|
311
|
+
return true;
|
|
312
|
+
}
|
|
313
|
+
return false;
|
|
314
|
+
};
|
|
315
|
+
// Find center of existing nodes to place new ones nearby
|
|
316
|
+
let cx = 0;
|
|
317
|
+
let cy = 0;
|
|
318
|
+
if (existingNodes.length > 0) {
|
|
319
|
+
for (const n of existingNodes) {
|
|
320
|
+
cx += n.x;
|
|
321
|
+
cy += n.y;
|
|
322
|
+
}
|
|
323
|
+
cx = Math.round(cx / existingNodes.length);
|
|
324
|
+
cy = Math.round(cy / existingNodes.length);
|
|
325
|
+
}
|
|
326
|
+
// Spiral outward from center to find free spot
|
|
327
|
+
for (let ring = 0; ring < 20; ring++) {
|
|
328
|
+
for (let col = -ring; col <= ring; col++) {
|
|
329
|
+
for (let row = -ring; row <= ring; row++) {
|
|
330
|
+
if (Math.abs(col) !== ring && Math.abs(row) !== ring)
|
|
331
|
+
continue; // only edges of ring
|
|
332
|
+
const x = cx + col * GRID_X;
|
|
333
|
+
const y = cy + row * GRID_Y;
|
|
334
|
+
if (!collides(x, y))
|
|
335
|
+
return { x, y };
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
// Fallback: far right of canvas
|
|
340
|
+
const maxX = existingNodes.reduce((m, n) => Math.max(m, n.x + n.w), 0);
|
|
341
|
+
return { x: maxX + GRID_X, y: 0 };
|
|
342
|
+
}
|
|
343
|
+
/** Map a CyberBase category to a canvas node color (Obsidian canvas colors 1-6) */
|
|
344
|
+
function canvasColor(category) {
|
|
345
|
+
const cat = category.toLowerCase();
|
|
346
|
+
if (cat.startsWith('project'))
|
|
347
|
+
return '2'; // green
|
|
348
|
+
if (cat.startsWith('infra') || cat.startsWith('tool') || cat.startsWith('mesh'))
|
|
349
|
+
return '4'; // purple
|
|
350
|
+
if (cat.startsWith('vuln') || cat.startsWith('security'))
|
|
351
|
+
return '5'; // cyan
|
|
352
|
+
if (cat.startsWith('rule') || cat.startsWith('system'))
|
|
353
|
+
return '1'; // red
|
|
354
|
+
if (cat.startsWith('cred'))
|
|
355
|
+
return '3'; // yellow
|
|
356
|
+
return '6'; // default
|
|
357
|
+
}
|
|
358
|
+
/** Auto-sync a knowledge entry to the Canvas mindmap — adds or updates a node */
|
|
359
|
+
function syncCanvas(category, key, value) {
|
|
360
|
+
if (!vaultExists || !existsSync(CANVAS_PATH))
|
|
361
|
+
return;
|
|
362
|
+
try {
|
|
363
|
+
const raw = readFileSync(CANVAS_PATH, 'utf-8');
|
|
364
|
+
const canvas = JSON.parse(raw);
|
|
365
|
+
const nodeId = `auto_${sanitizeFilename(category)}_${sanitizeFilename(key)}`.slice(0, 60);
|
|
366
|
+
const title = `## ${category}: ${key}`;
|
|
367
|
+
const textContent = `${title}\n${value.slice(0, 500)}`;
|
|
368
|
+
const nodeWidth = 280;
|
|
369
|
+
const nodeHeight = Math.min(180, 80 + Math.ceil(value.length / 50) * 18);
|
|
370
|
+
const color = canvasColor(category);
|
|
371
|
+
// Find existing node by id
|
|
372
|
+
const existingIdx = canvas.nodes.findIndex(n => n.id === nodeId);
|
|
373
|
+
if (existingIdx >= 0) {
|
|
374
|
+
// Update in place — keep position
|
|
375
|
+
canvas.nodes[existingIdx] = {
|
|
376
|
+
...canvas.nodes[existingIdx],
|
|
377
|
+
text: textContent,
|
|
378
|
+
height: nodeHeight,
|
|
379
|
+
color,
|
|
380
|
+
};
|
|
381
|
+
}
|
|
382
|
+
else {
|
|
383
|
+
// Find free position
|
|
384
|
+
const boxes = canvas.nodes.map(n => ({
|
|
385
|
+
x: n.x, y: n.y, w: n.width, h: n.height,
|
|
386
|
+
}));
|
|
387
|
+
const pos = findFreeCanvasPosition(boxes, nodeWidth, nodeHeight);
|
|
388
|
+
canvas.nodes.push({
|
|
389
|
+
id: nodeId,
|
|
390
|
+
type: 'text',
|
|
391
|
+
text: textContent,
|
|
392
|
+
x: pos.x,
|
|
393
|
+
y: pos.y,
|
|
394
|
+
width: nodeWidth,
|
|
395
|
+
height: nodeHeight,
|
|
396
|
+
color,
|
|
397
|
+
});
|
|
398
|
+
// Auto-connect to relevant parent node
|
|
399
|
+
const parentId = findCanvasParent(category, canvas.nodes);
|
|
400
|
+
if (parentId) {
|
|
401
|
+
canvas.edges.push({
|
|
402
|
+
id: `e_auto_${nodeId}`,
|
|
403
|
+
fromNode: parentId,
|
|
404
|
+
fromSide: 'bottom',
|
|
405
|
+
toNode: nodeId,
|
|
406
|
+
toSide: 'top',
|
|
407
|
+
label: category,
|
|
408
|
+
});
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
writeFileSync(CANVAS_PATH, JSON.stringify(canvas, null, '\t'), 'utf-8');
|
|
412
|
+
}
|
|
413
|
+
catch { /* canvas sync is best-effort */ }
|
|
414
|
+
}
|
|
415
|
+
/** Find the best parent node in the canvas to connect a new entry to */
|
|
416
|
+
function findCanvasParent(category, nodes) {
|
|
417
|
+
const cat = category.toLowerCase();
|
|
418
|
+
// Map categories to known canvas node IDs
|
|
419
|
+
if (cat.startsWith('project'))
|
|
420
|
+
return nodes.find(n => n.id === 'core')?.id ?? null;
|
|
421
|
+
if (cat.startsWith('infra') || cat.startsWith('mesh') || cat.startsWith('tool'))
|
|
422
|
+
return nodes.find(n => n.id === 'omniwire' || n.id === 'infra')?.id ?? null;
|
|
423
|
+
if (cat.startsWith('vuln') || cat.startsWith('security') || cat.startsWith('cve'))
|
|
424
|
+
return nodes.find(n => n.id === 'securitykb')?.id ?? null;
|
|
425
|
+
if (cat.startsWith('cred'))
|
|
426
|
+
return nodes.find(n => n.id === '1password' || n.id === 'db')?.id ?? null;
|
|
427
|
+
if (cat.startsWith('rule') || cat.startsWith('system'))
|
|
428
|
+
return nodes.find(n => n.id === 'rules')?.id ?? null;
|
|
429
|
+
if (cat.startsWith('note') || cat.startsWith('memo'))
|
|
430
|
+
return nodes.find(n => n.id === 'vault')?.id ?? null;
|
|
431
|
+
return nodes.find(n => n.id === 'core')?.id ?? null;
|
|
432
|
+
}
|
|
433
|
+
/** Sync entry to both Obsidian + Canvas (fire-and-forget, called from cb()) */
|
|
434
|
+
function syncVault(category, key, value) {
|
|
435
|
+
syncObsidian(category, key, value);
|
|
436
|
+
// Only add significant entries to canvas (skip tiny store values)
|
|
437
|
+
if (value.length > 50)
|
|
438
|
+
syncCanvas(category, key, value);
|
|
439
|
+
}
|
|
242
440
|
/** Get CyberBase health status */
|
|
243
441
|
function getCbHealth() {
|
|
244
442
|
return { healthy: cbHealthy, failCount: cbFailCount, lastError: cbLastError, queueSize: CB_QUEUE.length };
|
|
@@ -1766,73 +1964,177 @@ echo "port-knock configured: ${ports.join(' -> ')} -> port ${target}"`;
|
|
|
1766
1964
|
return fail('Invalid action or missing params');
|
|
1767
1965
|
});
|
|
1768
1966
|
// --- Tool 33: omniwire_cdp ---
|
|
1769
|
-
|
|
1770
|
-
|
|
1967
|
+
// Uses the persistent cdp-browser Docker container (puppeteer-core) for all operations.
|
|
1968
|
+
// Falls back to direct Chrome CLI for nodes without the container.
|
|
1969
|
+
const cdpScript = (js) => `docker exec cdp-browser node -e ${JSON.stringify(`const puppeteer=require('puppeteer-core');(async()=>{` +
|
|
1970
|
+
`const r=await fetch('http://127.0.0.1:9222/json/version');const{webSocketDebuggerUrl:ws}=await r.json();` +
|
|
1971
|
+
`const browser=await puppeteer.connect({browserWSEndpoint:ws});` +
|
|
1972
|
+
js +
|
|
1973
|
+
`})().catch(e=>{console.error('ERR:',e.message);process.exit(1)});`)} 2>&1`;
|
|
1974
|
+
server.tool('omniwire_cdp', 'Chrome DevTools Protocol — persistent headless browser via Docker container. Navigate, screenshot, HTML, PDF, cookies, evaluate JS, click, type, wait, network intercept, set-cookies, clear. Reuses pages across calls for speed.', {
|
|
1975
|
+
action: z.enum([
|
|
1976
|
+
'navigate', 'screenshot', 'html', 'text', 'pdf', 'cookies', 'set-cookies', 'clear-cookies',
|
|
1977
|
+
'tabs', 'close-tab', 'evaluate', 'click', 'type', 'wait', 'select',
|
|
1978
|
+
'network', 'status', 'viewport',
|
|
1979
|
+
]).describe('navigate=open URL, screenshot=capture PNG, html=DOM dump, text=innerText, pdf=save PDF, ' +
|
|
1980
|
+
'cookies=get all, set-cookies=inject cookies, clear-cookies=wipe, tabs=list pages, close-tab=close page, ' +
|
|
1981
|
+
'evaluate=run JS in page, click=click selector, type=type into selector, wait=wait for selector, ' +
|
|
1982
|
+
'select=querySelector extract, network=recent requests, status=container health, viewport=set size'),
|
|
1771
1983
|
node: z.string().optional().describe('Node (default: contabo)'),
|
|
1772
|
-
url: z.string().optional().describe('URL for navigate
|
|
1773
|
-
|
|
1774
|
-
|
|
1775
|
-
|
|
1984
|
+
url: z.string().optional().describe('URL for navigate'),
|
|
1985
|
+
selector: z.string().optional().describe('CSS selector for click/type/wait/select'),
|
|
1986
|
+
value: z.string().optional().describe('Text for type action, JS for evaluate, cookies JSON for set-cookies'),
|
|
1987
|
+
file: z.string().optional().describe('Output path for screenshot/pdf (default: /tmp/cdp-*)'),
|
|
1988
|
+
tab: z.number().optional().describe('Tab index (0-based, default: 0 = most recent)'),
|
|
1989
|
+
width: z.number().optional().describe('Viewport width for viewport action (default: 1920)'),
|
|
1990
|
+
height: z.number().optional().describe('Viewport height for viewport action (default: 1080)'),
|
|
1991
|
+
wait_ms: z.number().optional().describe('Wait timeout in ms (default: 10000)'),
|
|
1992
|
+
full_page: z.boolean().optional().describe('Full page screenshot (default: true)'),
|
|
1993
|
+
}, async ({ action, node, url, selector, value, file: outFile, tab, width, height, wait_ms, full_page }) => {
|
|
1776
1994
|
const nodeId = node ?? 'contabo';
|
|
1777
|
-
const
|
|
1778
|
-
|
|
1779
|
-
|
|
1780
|
-
|
|
1781
|
-
const
|
|
1782
|
-
`
|
|
1783
|
-
|
|
1784
|
-
|
|
1785
|
-
|
|
1786
|
-
|
|
1787
|
-
if (
|
|
1788
|
-
|
|
1789
|
-
|
|
1790
|
-
|
|
1791
|
-
|
|
1792
|
-
|
|
1793
|
-
|
|
1794
|
-
const r = await manager.exec(nodeId, cmd);
|
|
1795
|
-
return ok(nodeId, r.durationMs, r.stdout, `navigate`);
|
|
1995
|
+
const tabIdx = tab ?? 0;
|
|
1996
|
+
const timeout = wait_ms ?? 10000;
|
|
1997
|
+
const getPage = `const pages=await browser.pages();const page=pages[${tabIdx}]||pages[0];if(!page){console.log('no pages open');process.exit(0);}`;
|
|
1998
|
+
if (action === 'status') {
|
|
1999
|
+
const r = await manager.exec(nodeId, `docker inspect cdp-browser --format '{{.State.Status}} uptime={{.State.StartedAt}}' 2>/dev/null; ` +
|
|
2000
|
+
`curl -sf http://127.0.0.1:9222/json/version 2>/dev/null | python3 -c "import json,sys;d=json.load(sys.stdin);print(f\\"chrome={d.get('Browser','')} proto={d.get('Protocol-Version','')}\\")" 2>/dev/null; ` +
|
|
2001
|
+
`curl -sf http://127.0.0.1:9222/json/list 2>/dev/null | python3 -c "import json,sys;tabs=json.load(sys.stdin);print(f\\"{len(tabs)} tabs open\\");[print(f\\" {t['id'][:8]} {t.get('url','')[:80]}\\") for t in tabs[:10]]" 2>/dev/null`);
|
|
2002
|
+
return ok(nodeId, r.durationMs, r.stdout, 'cdp status');
|
|
2003
|
+
}
|
|
2004
|
+
if (action === 'navigate') {
|
|
2005
|
+
if (!url)
|
|
2006
|
+
return fail('url required');
|
|
2007
|
+
const u = url.replace(/'/g, "\\'");
|
|
2008
|
+
const r = await manager.exec(nodeId, cdpScript(`${getPage}` +
|
|
2009
|
+
`await page.goto('${u}',{waitUntil:'networkidle2',timeout:${timeout}});` +
|
|
2010
|
+
`console.log('url='+page.url());console.log('title='+await page.title());`));
|
|
2011
|
+
return ok(nodeId, r.durationMs, r.stdout, 'navigate');
|
|
1796
2012
|
}
|
|
1797
2013
|
if (action === 'screenshot') {
|
|
1798
|
-
const
|
|
1799
|
-
const
|
|
1800
|
-
const
|
|
1801
|
-
|
|
2014
|
+
const out = outFile ?? `/tmp/cdp-screenshot-${Date.now()}.png`;
|
|
2015
|
+
const fp = full_page !== false;
|
|
2016
|
+
const r = await manager.exec(nodeId, cdpScript(`${getPage}` +
|
|
2017
|
+
`await page.screenshot({path:'${out}',fullPage:${fp}});` +
|
|
2018
|
+
`const fs=require('fs');const sz=fs.statSync('${out}').size;` +
|
|
2019
|
+
`console.log('saved: ${out} ('+Math.round(sz/1024)+'KB) '+page.url());`));
|
|
1802
2020
|
return ok(nodeId, r.durationMs, r.stdout, 'screenshot');
|
|
1803
2021
|
}
|
|
1804
2022
|
if (action === 'html') {
|
|
1805
|
-
const
|
|
1806
|
-
|
|
1807
|
-
|
|
2023
|
+
const r = await manager.exec(nodeId, cdpScript(`${getPage}` +
|
|
2024
|
+
`const html=await page.content();` +
|
|
2025
|
+
`console.log(html.substring(0,${url ? '50000' : '10000'}));`));
|
|
1808
2026
|
return ok(nodeId, r.durationMs, r.stdout, 'html');
|
|
1809
2027
|
}
|
|
2028
|
+
if (action === 'text') {
|
|
2029
|
+
const sel = selector ? `.replace(/'/g,"\\\\'")` : '';
|
|
2030
|
+
const r = await manager.exec(nodeId, cdpScript(`${getPage}` +
|
|
2031
|
+
(selector
|
|
2032
|
+
? `const el=await page.$('${selector.replace(/'/g, "\\'")}');const t=el?await page.evaluate(e=>e.innerText,el):'(not found)';console.log(t.substring(0,20000));`
|
|
2033
|
+
: `const t=await page.evaluate(()=>document.body.innerText);console.log(t.substring(0,20000));`)));
|
|
2034
|
+
return ok(nodeId, r.durationMs, r.stdout, 'text');
|
|
2035
|
+
}
|
|
1810
2036
|
if (action === 'pdf') {
|
|
1811
|
-
const
|
|
1812
|
-
const
|
|
1813
|
-
|
|
1814
|
-
|
|
2037
|
+
const out = outFile ?? `/tmp/cdp-page-${Date.now()}.pdf`;
|
|
2038
|
+
const r = await manager.exec(nodeId, cdpScript(`${getPage}` +
|
|
2039
|
+
`await page.pdf({path:'${out}',format:'A4',printBackground:true});` +
|
|
2040
|
+
`const fs=require('fs');const sz=fs.statSync('${out}').size;` +
|
|
2041
|
+
`console.log('saved: ${out} ('+Math.round(sz/1024)+'KB)');`));
|
|
1815
2042
|
return ok(nodeId, r.durationMs, r.stdout, 'pdf');
|
|
1816
2043
|
}
|
|
1817
2044
|
if (action === 'cookies') {
|
|
1818
|
-
const
|
|
1819
|
-
|
|
1820
|
-
|
|
2045
|
+
const r = await manager.exec(nodeId, cdpScript(`${getPage}const cookies=await page.cookies();` +
|
|
2046
|
+
`cookies.forEach(c=>console.log(c.domain+'\\t'+c.name+'='+c.value.substring(0,60)+(c.value.length>60?'...':'')));` +
|
|
2047
|
+
`console.log('--- '+cookies.length+' cookies ---');`));
|
|
2048
|
+
return ok(nodeId, r.durationMs, r.stdout, 'cookies');
|
|
2049
|
+
}
|
|
2050
|
+
if (action === 'set-cookies') {
|
|
2051
|
+
if (!value)
|
|
2052
|
+
return fail('value required (JSON array of cookie objects)');
|
|
2053
|
+
const v = value.replace(/'/g, "\\'").replace(/\n/g, '');
|
|
2054
|
+
const r = await manager.exec(nodeId, cdpScript(`${getPage}const cookies=JSON.parse('${v}');` +
|
|
2055
|
+
`await page.setCookie(...cookies);console.log('set '+cookies.length+' cookies');`));
|
|
2056
|
+
return ok(nodeId, r.durationMs, r.stdout, 'set-cookies');
|
|
2057
|
+
}
|
|
2058
|
+
if (action === 'clear-cookies') {
|
|
2059
|
+
const r = await manager.exec(nodeId, cdpScript(`${getPage}const client=await page.createCDPSession();` +
|
|
2060
|
+
`await client.send('Network.clearBrowserCookies');console.log('cookies cleared');`));
|
|
2061
|
+
return ok(nodeId, r.durationMs, r.stdout, 'clear-cookies');
|
|
1821
2062
|
}
|
|
1822
2063
|
if (action === 'tabs') {
|
|
1823
|
-
const
|
|
1824
|
-
|
|
2064
|
+
const r = await manager.exec(nodeId, cdpScript(`const pages=await browser.pages();` +
|
|
2065
|
+
`pages.forEach((p,i)=>console.log(i+' '+p.url().substring(0,100)));` +
|
|
2066
|
+
`console.log('--- '+pages.length+' tabs ---');`));
|
|
1825
2067
|
return ok(nodeId, r.durationMs, r.stdout, 'tabs');
|
|
1826
2068
|
}
|
|
1827
|
-
if (action === 'close') {
|
|
1828
|
-
const
|
|
1829
|
-
|
|
1830
|
-
|
|
2069
|
+
if (action === 'close-tab') {
|
|
2070
|
+
const r = await manager.exec(nodeId, cdpScript(`const pages=await browser.pages();` +
|
|
2071
|
+
`if(pages.length<=${tabIdx}){console.log('tab ${tabIdx} not found');process.exit(0);}` +
|
|
2072
|
+
`const url=pages[${tabIdx}].url();await pages[${tabIdx}].close();` +
|
|
2073
|
+
`console.log('closed tab ${tabIdx}: '+url);console.log((pages.length-1)+' tabs remaining');`));
|
|
2074
|
+
return ok(nodeId, r.durationMs, r.stdout, 'close-tab');
|
|
2075
|
+
}
|
|
2076
|
+
if (action === 'evaluate') {
|
|
2077
|
+
if (!value)
|
|
2078
|
+
return fail('value required (JavaScript to evaluate in page context)');
|
|
2079
|
+
const js = value.replace(/\\/g, '\\\\').replace(/'/g, "\\'").replace(/\n/g, '\\n');
|
|
2080
|
+
const r = await manager.exec(nodeId, cdpScript(`${getPage}const result=await page.evaluate(()=>{${js}});` +
|
|
2081
|
+
`console.log(typeof result==='object'?JSON.stringify(result,null,2):String(result));`));
|
|
2082
|
+
return ok(nodeId, r.durationMs, r.stdout, 'evaluate');
|
|
2083
|
+
}
|
|
2084
|
+
if (action === 'click') {
|
|
2085
|
+
if (!selector)
|
|
2086
|
+
return fail('selector required');
|
|
2087
|
+
const sel = selector.replace(/'/g, "\\'");
|
|
2088
|
+
const r = await manager.exec(nodeId, cdpScript(`${getPage}await page.waitForSelector('${sel}',{timeout:${timeout}});` +
|
|
2089
|
+
`await page.click('${sel}');console.log('clicked: ${sel}');` +
|
|
2090
|
+
`await new Promise(r=>setTimeout(r,500));console.log('url='+page.url());`));
|
|
2091
|
+
return ok(nodeId, r.durationMs, r.stdout, 'click');
|
|
2092
|
+
}
|
|
2093
|
+
if (action === 'type') {
|
|
2094
|
+
if (!selector || !value)
|
|
2095
|
+
return fail('selector and value required');
|
|
2096
|
+
const sel = selector.replace(/'/g, "\\'");
|
|
2097
|
+
const val = value.replace(/'/g, "\\'");
|
|
2098
|
+
const r = await manager.exec(nodeId, cdpScript(`${getPage}await page.waitForSelector('${sel}',{timeout:${timeout}});` +
|
|
2099
|
+
`await page.type('${sel}','${val}');console.log('typed ${value.length} chars into ${sel}');`));
|
|
2100
|
+
return ok(nodeId, r.durationMs, r.stdout, 'type');
|
|
2101
|
+
}
|
|
2102
|
+
if (action === 'wait') {
|
|
2103
|
+
if (!selector)
|
|
2104
|
+
return fail('selector required');
|
|
2105
|
+
const sel = selector.replace(/'/g, "\\'");
|
|
2106
|
+
const r = await manager.exec(nodeId, cdpScript(`${getPage}const el=await page.waitForSelector('${sel}',{timeout:${timeout}});` +
|
|
2107
|
+
`const tag=await page.evaluate(e=>e.tagName+' '+e.className,el);` +
|
|
2108
|
+
`console.log('found: ${sel} → '+tag);`));
|
|
2109
|
+
return ok(nodeId, r.durationMs, r.stdout, 'wait');
|
|
2110
|
+
}
|
|
2111
|
+
if (action === 'select') {
|
|
2112
|
+
if (!selector)
|
|
2113
|
+
return fail('selector required');
|
|
2114
|
+
const sel = selector.replace(/'/g, "\\'");
|
|
2115
|
+
const r = await manager.exec(nodeId, cdpScript(`${getPage}const els=await page.$$('${sel}');` +
|
|
2116
|
+
`const results=[];for(const el of els.slice(0,20)){` +
|
|
2117
|
+
`const d=await page.evaluate(e=>({tag:e.tagName,text:e.innerText?.substring(0,200),href:e.href||'',src:e.src||''}),el);` +
|
|
2118
|
+
`results.push(d);}` +
|
|
2119
|
+
`console.log(JSON.stringify(results,null,2));console.log('--- '+els.length+' matches ---');`));
|
|
2120
|
+
return ok(nodeId, r.durationMs, r.stdout, 'select');
|
|
1831
2121
|
}
|
|
1832
|
-
if (action === '
|
|
1833
|
-
const
|
|
1834
|
-
|
|
1835
|
-
|
|
2122
|
+
if (action === 'network') {
|
|
2123
|
+
const r = await manager.exec(nodeId, cdpScript(`${getPage}const client=await page.createCDPSession();` +
|
|
2124
|
+
`const entries=[];client.on('Network.responseReceived',e=>{entries.push({url:e.response.url.substring(0,100),status:e.response.status,type:e.type});});` +
|
|
2125
|
+
`await client.send('Network.enable');` +
|
|
2126
|
+
`await page.reload({waitUntil:'networkidle2',timeout:${timeout}});` +
|
|
2127
|
+
`await client.send('Network.disable');` +
|
|
2128
|
+
`entries.slice(0,30).forEach(e=>console.log(e.status+' '+e.type.padEnd(12)+' '+e.url));` +
|
|
2129
|
+
`console.log('--- '+entries.length+' requests ---');`));
|
|
2130
|
+
return ok(nodeId, r.durationMs, r.stdout, 'network');
|
|
2131
|
+
}
|
|
2132
|
+
if (action === 'viewport') {
|
|
2133
|
+
const w = width ?? 1920;
|
|
2134
|
+
const h = height ?? 1080;
|
|
2135
|
+
const r = await manager.exec(nodeId, cdpScript(`${getPage}await page.setViewport({width:${w},height:${h}});` +
|
|
2136
|
+
`console.log('viewport set to ${w}x${h}');`));
|
|
2137
|
+
return ok(nodeId, r.durationMs, r.stdout, 'viewport');
|
|
1836
2138
|
}
|
|
1837
2139
|
return fail('Invalid action or missing params');
|
|
1838
2140
|
});
|
|
@@ -3357,8 +3659,8 @@ echo "port-knock configured: ${ports.join(' -> ')} -> port ${target}"`;
|
|
|
3357
3659
|
return fail('invalid action');
|
|
3358
3660
|
});
|
|
3359
3661
|
// --- Tool 53: omniwire_knowledge ---
|
|
3360
|
-
server.tool('omniwire_knowledge', 'CyberBase knowledge base — CRUD, search, and health management for the unified PostgreSQL knowledge store. Supports text search, semantic/vector search, categories, and
|
|
3361
|
-
action: z.enum(['get', 'set', 'delete', 'search', 'semantic-search', 'list', 'stats', 'health', 'categories', 'bulk-set', 'export', 'vacuum']).describe('Action'),
|
|
3662
|
+
server.tool('omniwire_knowledge', 'CyberBase knowledge base — CRUD, search, and health management for the unified PostgreSQL knowledge store. Auto-syncs all writes to Obsidian vault + Canvas mindmap. Supports text search, semantic/vector search, categories, bulk operations, and explicit sync-obsidian/sync-canvas actions.', {
|
|
3663
|
+
action: z.enum(['get', 'set', 'delete', 'search', 'semantic-search', 'list', 'stats', 'health', 'categories', 'bulk-set', 'export', 'vacuum', 'sync-obsidian', 'sync-canvas']).describe('Action'),
|
|
3362
3664
|
category: z.string().optional().describe('Knowledge category (e.g., tools, vulns, infra, notes)'),
|
|
3363
3665
|
key: z.string().optional().describe('Knowledge key (for get/set/delete)'),
|
|
3364
3666
|
value: z.string().optional().describe('Value to store (for set)'),
|
|
@@ -3456,6 +3758,25 @@ echo "port-knock configured: ${ports.join(' -> ')} -> port ${target}"`;
|
|
|
3456
3758
|
const r = await cbManager.exec('contabo', pgExec("DELETE FROM knowledge WHERE value IS NULL OR value::text = 'null' OR key = ''; VACUUM ANALYZE knowledge;"));
|
|
3457
3759
|
return okBrief(`vacuum complete:\n${r.stdout.trim()}`);
|
|
3458
3760
|
}
|
|
3761
|
+
if (action === 'sync-obsidian') {
|
|
3762
|
+
if (!key || !value)
|
|
3763
|
+
return fail('key and value required');
|
|
3764
|
+
if (!vaultExists)
|
|
3765
|
+
return fail(`Obsidian vault not found at ${VAULT_ROOT}`);
|
|
3766
|
+
const cat = category ?? 'general';
|
|
3767
|
+
syncObsidian(cat, key, value);
|
|
3768
|
+
const folder = vaultFolder(cat);
|
|
3769
|
+
return okBrief(`synced to Obsidian: ${folder}/${sanitizeFilename(key)}.md (${value.length} chars)`);
|
|
3770
|
+
}
|
|
3771
|
+
if (action === 'sync-canvas') {
|
|
3772
|
+
if (!key || !value)
|
|
3773
|
+
return fail('key and value required');
|
|
3774
|
+
if (!vaultExists || !existsSync(CANVAS_PATH))
|
|
3775
|
+
return fail(`Canvas not found at ${CANVAS_PATH}`);
|
|
3776
|
+
const cat = category ?? 'general';
|
|
3777
|
+
syncCanvas(cat, key, value);
|
|
3778
|
+
return okBrief(`synced to Canvas: node auto_${sanitizeFilename(cat)}_${sanitizeFilename(key)} added/updated`);
|
|
3779
|
+
}
|
|
3459
3780
|
return fail('invalid action');
|
|
3460
3781
|
});
|
|
3461
3782
|
// --- Tool 54: omniwire_omnimesh ---
|