omniwire 3.1.3 → 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.
@@ -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
  }
@@ -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 };
@@ -3461,8 +3659,8 @@ echo "port-knock configured: ${ports.join(' -> ')} -> port ${target}"`;
3461
3659
  return fail('invalid action');
3462
3660
  });
3463
3661
  // --- 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'),
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'),
3466
3664
  category: z.string().optional().describe('Knowledge category (e.g., tools, vulns, infra, notes)'),
3467
3665
  key: z.string().optional().describe('Knowledge key (for get/set/delete)'),
3468
3666
  value: z.string().optional().describe('Value to store (for set)'),
@@ -3560,6 +3758,25 @@ echo "port-knock configured: ${ports.join(' -> ')} -> port ${target}"`;
3560
3758
  const r = await cbManager.exec('contabo', pgExec("DELETE FROM knowledge WHERE value IS NULL OR value::text = 'null' OR key = ''; VACUUM ANALYZE knowledge;"));
3561
3759
  return okBrief(`vacuum complete:\n${r.stdout.trim()}`);
3562
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
+ }
3563
3780
  return fail('invalid action');
3564
3781
  });
3565
3782
  // --- Tool 54: omniwire_omnimesh ---