omniwire 3.1.3 → 3.1.5

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,7 +1,7 @@
1
1
  {
2
- "lastCheck": 1774750607929,
2
+ "lastCheck": 1774756238937,
3
3
  "lastVersion": "3.0.1",
4
- "autoUpdateEnabled": false,
4
+ "autoUpdateEnabled": true,
5
5
  "source": "auto",
6
6
  "checkIntervalMs": 3600000
7
7
  }
package/README.md CHANGED
@@ -683,6 +683,24 @@ omniwire/
683
683
 
684
684
  ---
685
685
 
686
+ ## Changelog
687
+
688
+ | Version | Date | Changes |
689
+ |---------|------|---------|
690
+ | **v3.1.5** | 2026-03-29 | Fix: skip auto-audit batch entries from Obsidian vault + Canvas sync to prevent junk files |
691
+ | **v3.1.4** | 2026-03-29 | Auto-sync CyberBase writes to Obsidian vault + Canvas mindmap, collision-avoidance grid placement, `sync-obsidian` / `sync-canvas` actions in knowledge tool |
692
+ | **v3.1.3** | 2026-03-29 | OmniMesh WireGuard mesh manager, event bus (Webhook/WS/SSE), knowledge tool (12 actions), auto-update system, CDP rewrite (persistent Docker container, 18 actions), mesh expose/gateway, CyberBase circuit breaker + SQL hardening |
693
+ | **v3.1.2** | 2026-03-28 | Collapsible tool sections in README, npm README sync |
694
+ | **v3.1.1** | 2026-03-28 | Bug fixes, improved error handling in CDP tool |
695
+ | **v3.1.0** | 2026-03-27 | OmniMesh VPN, 81 MCP tools, A2A protocol, event system, background dispatch |
696
+ | **v3.0.0** | 2026-03-25 | Major rewrite: CyberSync, pipeline DAGs, blackboard, task queues, LZ4 transfers, AES-128-GCM encryption |
697
+ | **v2.6.1** | 2026-03-20 | VPN routing (Mullvad/OpenVPN/WG/Tailscale), multi-hop, DAITA, quantum tunnels |
698
+ | **v2.5.0** | 2026-03-15 | Firewall management (nftables), cert management, deploy tool |
699
+ | **v2.0.0** | 2026-03-10 | CDP browser automation, cookie sync, 1Password integration |
700
+ | **v1.0.0** | 2026-03-01 | Initial release — SSH exec, file transfer, node management |
701
+
702
+ ---
703
+
686
704
  <br/>
687
705
 
688
706
  <p align="center">
@@ -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,13 @@ 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
+ // Skip auto-audit batch entries — they pollute vault/canvas with junk
208
+ if (!key.startsWith('batch-'))
209
+ syncVault(category, key, value);
210
+ // Sync to CyberBase PostgreSQL (remote, async, queued)
204
211
  if (!cbManager || cbCircuitOpen())
205
212
  return;
206
213
  const valEsc = sqlEscape(value).slice(0, 50000);
@@ -239,6 +246,199 @@ async function drainCb() {
239
246
  else
240
247
  cbDraining = false;
241
248
  }
249
+ // -- Obsidian + Canvas auto-sync ------------------------------------------------
250
+ // Mirrors CyberBase writes to local Obsidian vault + Canvas mindmap.
251
+ // Vault path is resolved at startup; if it doesn't exist, sync is silently skipped.
252
+ const VAULT_ROOT = join(process.env.USERPROFILE ?? process.env.HOME ?? '', 'Documents', 'BuisnessProjects', 'CyberBase');
253
+ const CANVAS_PATH = join(VAULT_ROOT, 'CyberBase MindMap.canvas');
254
+ const vaultExists = existsSync(VAULT_ROOT);
255
+ /** Map CyberBase category → Obsidian vault subfolder */
256
+ function vaultFolder(category) {
257
+ const cat = category.toLowerCase();
258
+ if (cat.startsWith('project'))
259
+ return 'projects';
260
+ if (cat.startsWith('infra') || cat.startsWith('tool') || cat.startsWith('mesh'))
261
+ return 'infrastructure';
262
+ if (cat.startsWith('vuln') || cat.startsWith('security') || cat.startsWith('cve'))
263
+ return 'knowledge/security-kb';
264
+ if (cat.startsWith('cred'))
265
+ return 'credentials';
266
+ if (cat.startsWith('system') || cat.startsWith('rule'))
267
+ return 'system';
268
+ if (cat.startsWith('log'))
269
+ return 'logs';
270
+ if (cat.startsWith('sync'))
271
+ return 'sync';
272
+ if (cat.startsWith('note') || cat.startsWith('memo'))
273
+ return 'memory';
274
+ return 'knowledge';
275
+ }
276
+ /** Sanitize a key into a valid filename */
277
+ function sanitizeFilename(key) {
278
+ return key.replace(/[<>:"/\\|?*]/g, '-').replace(/^\.+/, '').slice(0, 120);
279
+ }
280
+ /** Auto-sync a knowledge entry to Obsidian vault as a .md file */
281
+ function syncObsidian(category, key, value) {
282
+ if (!vaultExists)
283
+ return;
284
+ try {
285
+ const folder = join(VAULT_ROOT, vaultFolder(category));
286
+ if (!existsSync(folder))
287
+ mkdirSync(folder, { recursive: true });
288
+ const filename = sanitizeFilename(key) + '.md';
289
+ const filepath = join(folder, filename);
290
+ const frontmatter = `---\nsource: omniwire\ncategory: ${category}\nkey: ${key}\nupdated: ${new Date().toISOString()}\n---\n\n`;
291
+ // If value looks like markdown, write as-is; otherwise wrap in code block
292
+ const body = value.includes('\n') && (value.includes('#') || value.includes('|') || value.includes('- '))
293
+ ? value
294
+ : `\`\`\`\n${value}\n\`\`\``;
295
+ writeFileSync(filepath, frontmatter + body, 'utf-8');
296
+ }
297
+ catch { /* vault sync is best-effort */ }
298
+ }
299
+ /** Find a non-overlapping position for a new canvas node using grid placement */
300
+ function findFreeCanvasPosition(existingNodes, width, height) {
301
+ const GRID_X = 500; // horizontal spacing
302
+ const GRID_Y = 400; // vertical spacing
303
+ const PADDING = 80; // minimum gap between nodes
304
+ const MAX_COLS = 6;
305
+ // Check if a position collides with any existing node
306
+ const collides = (x, y) => {
307
+ for (const n of existingNodes) {
308
+ const overlap = x < n.x + n.w + PADDING &&
309
+ x + width + PADDING > n.x &&
310
+ y < n.y + n.h + PADDING &&
311
+ y + height + PADDING > n.y;
312
+ if (overlap)
313
+ return true;
314
+ }
315
+ return false;
316
+ };
317
+ // Find center of existing nodes to place new ones nearby
318
+ let cx = 0;
319
+ let cy = 0;
320
+ if (existingNodes.length > 0) {
321
+ for (const n of existingNodes) {
322
+ cx += n.x;
323
+ cy += n.y;
324
+ }
325
+ cx = Math.round(cx / existingNodes.length);
326
+ cy = Math.round(cy / existingNodes.length);
327
+ }
328
+ // Spiral outward from center to find free spot
329
+ for (let ring = 0; ring < 20; ring++) {
330
+ for (let col = -ring; col <= ring; col++) {
331
+ for (let row = -ring; row <= ring; row++) {
332
+ if (Math.abs(col) !== ring && Math.abs(row) !== ring)
333
+ continue; // only edges of ring
334
+ const x = cx + col * GRID_X;
335
+ const y = cy + row * GRID_Y;
336
+ if (!collides(x, y))
337
+ return { x, y };
338
+ }
339
+ }
340
+ }
341
+ // Fallback: far right of canvas
342
+ const maxX = existingNodes.reduce((m, n) => Math.max(m, n.x + n.w), 0);
343
+ return { x: maxX + GRID_X, y: 0 };
344
+ }
345
+ /** Map a CyberBase category to a canvas node color (Obsidian canvas colors 1-6) */
346
+ function canvasColor(category) {
347
+ const cat = category.toLowerCase();
348
+ if (cat.startsWith('project'))
349
+ return '2'; // green
350
+ if (cat.startsWith('infra') || cat.startsWith('tool') || cat.startsWith('mesh'))
351
+ return '4'; // purple
352
+ if (cat.startsWith('vuln') || cat.startsWith('security'))
353
+ return '5'; // cyan
354
+ if (cat.startsWith('rule') || cat.startsWith('system'))
355
+ return '1'; // red
356
+ if (cat.startsWith('cred'))
357
+ return '3'; // yellow
358
+ return '6'; // default
359
+ }
360
+ /** Auto-sync a knowledge entry to the Canvas mindmap — adds or updates a node */
361
+ function syncCanvas(category, key, value) {
362
+ if (!vaultExists || !existsSync(CANVAS_PATH))
363
+ return;
364
+ try {
365
+ const raw = readFileSync(CANVAS_PATH, 'utf-8');
366
+ const canvas = JSON.parse(raw);
367
+ const nodeId = `auto_${sanitizeFilename(category)}_${sanitizeFilename(key)}`.slice(0, 60);
368
+ const title = `## ${category}: ${key}`;
369
+ const textContent = `${title}\n${value.slice(0, 500)}`;
370
+ const nodeWidth = 280;
371
+ const nodeHeight = Math.min(180, 80 + Math.ceil(value.length / 50) * 18);
372
+ const color = canvasColor(category);
373
+ // Find existing node by id
374
+ const existingIdx = canvas.nodes.findIndex(n => n.id === nodeId);
375
+ if (existingIdx >= 0) {
376
+ // Update in place — keep position
377
+ canvas.nodes[existingIdx] = {
378
+ ...canvas.nodes[existingIdx],
379
+ text: textContent,
380
+ height: nodeHeight,
381
+ color,
382
+ };
383
+ }
384
+ else {
385
+ // Find free position
386
+ const boxes = canvas.nodes.map(n => ({
387
+ x: n.x, y: n.y, w: n.width, h: n.height,
388
+ }));
389
+ const pos = findFreeCanvasPosition(boxes, nodeWidth, nodeHeight);
390
+ canvas.nodes.push({
391
+ id: nodeId,
392
+ type: 'text',
393
+ text: textContent,
394
+ x: pos.x,
395
+ y: pos.y,
396
+ width: nodeWidth,
397
+ height: nodeHeight,
398
+ color,
399
+ });
400
+ // Auto-connect to relevant parent node
401
+ const parentId = findCanvasParent(category, canvas.nodes);
402
+ if (parentId) {
403
+ canvas.edges.push({
404
+ id: `e_auto_${nodeId}`,
405
+ fromNode: parentId,
406
+ fromSide: 'bottom',
407
+ toNode: nodeId,
408
+ toSide: 'top',
409
+ label: category,
410
+ });
411
+ }
412
+ }
413
+ writeFileSync(CANVAS_PATH, JSON.stringify(canvas, null, '\t'), 'utf-8');
414
+ }
415
+ catch { /* canvas sync is best-effort */ }
416
+ }
417
+ /** Find the best parent node in the canvas to connect a new entry to */
418
+ function findCanvasParent(category, nodes) {
419
+ const cat = category.toLowerCase();
420
+ // Map categories to known canvas node IDs
421
+ if (cat.startsWith('project'))
422
+ return nodes.find(n => n.id === 'core')?.id ?? null;
423
+ if (cat.startsWith('infra') || cat.startsWith('mesh') || cat.startsWith('tool'))
424
+ return nodes.find(n => n.id === 'omniwire' || n.id === 'infra')?.id ?? null;
425
+ if (cat.startsWith('vuln') || cat.startsWith('security') || cat.startsWith('cve'))
426
+ return nodes.find(n => n.id === 'securitykb')?.id ?? null;
427
+ if (cat.startsWith('cred'))
428
+ return nodes.find(n => n.id === '1password' || n.id === 'db')?.id ?? null;
429
+ if (cat.startsWith('rule') || cat.startsWith('system'))
430
+ return nodes.find(n => n.id === 'rules')?.id ?? null;
431
+ if (cat.startsWith('note') || cat.startsWith('memo'))
432
+ return nodes.find(n => n.id === 'vault')?.id ?? null;
433
+ return nodes.find(n => n.id === 'core')?.id ?? null;
434
+ }
435
+ /** Sync entry to both Obsidian + Canvas (fire-and-forget, called from cb()) */
436
+ function syncVault(category, key, value) {
437
+ syncObsidian(category, key, value);
438
+ // Only add significant entries to canvas (skip tiny store values)
439
+ if (value.length > 50)
440
+ syncCanvas(category, key, value);
441
+ }
242
442
  /** Get CyberBase health status */
243
443
  function getCbHealth() {
244
444
  return { healthy: cbHealthy, failCount: cbFailCount, lastError: cbLastError, queueSize: CB_QUEUE.length };
@@ -3461,8 +3661,8 @@ echo "port-knock configured: ${ports.join(' -> ')} -> port ${target}"`;
3461
3661
  return fail('invalid action');
3462
3662
  });
3463
3663
  // --- Tool 53: omniwire_knowledge ---
3464
- 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 bulk operations.', {
3465
- action: z.enum(['get', 'set', 'delete', 'search', 'semantic-search', 'list', 'stats', 'health', 'categories', 'bulk-set', 'export', 'vacuum']).describe('Action'),
3664
+ 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.', {
3665
+ action: z.enum(['get', 'set', 'delete', 'search', 'semantic-search', 'list', 'stats', 'health', 'categories', 'bulk-set', 'export', 'vacuum', 'sync-obsidian', 'sync-canvas']).describe('Action'),
3466
3666
  category: z.string().optional().describe('Knowledge category (e.g., tools, vulns, infra, notes)'),
3467
3667
  key: z.string().optional().describe('Knowledge key (for get/set/delete)'),
3468
3668
  value: z.string().optional().describe('Value to store (for set)'),
@@ -3560,6 +3760,25 @@ echo "port-knock configured: ${ports.join(' -> ')} -> port ${target}"`;
3560
3760
  const r = await cbManager.exec('contabo', pgExec("DELETE FROM knowledge WHERE value IS NULL OR value::text = 'null' OR key = ''; VACUUM ANALYZE knowledge;"));
3561
3761
  return okBrief(`vacuum complete:\n${r.stdout.trim()}`);
3562
3762
  }
3763
+ if (action === 'sync-obsidian') {
3764
+ if (!key || !value)
3765
+ return fail('key and value required');
3766
+ if (!vaultExists)
3767
+ return fail(`Obsidian vault not found at ${VAULT_ROOT}`);
3768
+ const cat = category ?? 'general';
3769
+ syncObsidian(cat, key, value);
3770
+ const folder = vaultFolder(cat);
3771
+ return okBrief(`synced to Obsidian: ${folder}/${sanitizeFilename(key)}.md (${value.length} chars)`);
3772
+ }
3773
+ if (action === 'sync-canvas') {
3774
+ if (!key || !value)
3775
+ return fail('key and value required');
3776
+ if (!vaultExists || !existsSync(CANVAS_PATH))
3777
+ return fail(`Canvas not found at ${CANVAS_PATH}`);
3778
+ const cat = category ?? 'general';
3779
+ syncCanvas(cat, key, value);
3780
+ return okBrief(`synced to Canvas: node auto_${sanitizeFilename(cat)}_${sanitizeFilename(key)} added/updated`);
3781
+ }
3563
3782
  return fail('invalid action');
3564
3783
  });
3565
3784
  // --- Tool 54: omniwire_omnimesh ---