omniwire 3.2.2 → 3.3.1

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 +1 @@
1
- {"session_id":"8ef02123-7368-447d-82e3-ee14a27328b0","transcript_path":"C:\\Users\\Admin\\.claude\\projects\\C--Users-Admin\\8ef02123-7368-447d-82e3-ee14a27328b0.jsonl","cwd":"C:\\Users\\Admin\\omniwire","model":{"id":"claude-opus-4-6[1m]","display_name":"Opus 4.6 (1M context)"},"workspace":{"current_dir":"C:\\Users\\Admin\\omniwire","project_dir":"C:\\Users\\Admin","added_dirs":["C:/Users/Admin"]},"version":"2.1.87","output_style":{"name":"default"},"cost":{"total_cost_usd":5.113285649999999,"total_duration_ms":1663275,"total_api_duration_ms":659204,"total_lines_added":42,"total_lines_removed":27},"context_window":{"total_input_tokens":801,"total_output_tokens":23773,"context_window_size":1000000,"current_usage":{"input_tokens":1,"output_tokens":170,"cache_creation_input_tokens":354,"cache_read_input_tokens":103104},"used_percentage":10,"remaining_percentage":90},"exceeds_200k_tokens":false,"rate_limits":{"five_hour":{"used_percentage":35,"resets_at":1774828800},"seven_day":{"used_percentage":41,"resets_at":1775206800}}}
1
+ {"session_id":"8ef02123-7368-447d-82e3-ee14a27328b0","transcript_path":"C:\\Users\\Admin\\.claude\\projects\\C--Users-Admin\\8ef02123-7368-447d-82e3-ee14a27328b0.jsonl","cwd":"C:\\Users\\Admin\\omniwire","model":{"id":"claude-opus-4-6[1m]","display_name":"Opus 4.6 (1M context)"},"workspace":{"current_dir":"C:\\Users\\Admin\\omniwire","project_dir":"C:\\Users\\Admin","added_dirs":["C:/Users/Admin"]},"version":"2.1.87","output_style":{"name":"default"},"cost":{"total_cost_usd":13.206143100000004,"total_duration_ms":2660349,"total_api_duration_ms":1904685,"total_lines_added":283,"total_lines_removed":41},"context_window":{"total_input_tokens":176085,"total_output_tokens":78423,"context_window_size":1000000,"current_usage":{"input_tokens":1,"output_tokens":275,"cache_creation_input_tokens":560,"cache_read_input_tokens":152806},"used_percentage":15,"remaining_percentage":85},"exceeds_200k_tokens":false,"rate_limits":{"five_hour":{"used_percentage":1,"resets_at":1774846800},"seven_day":{"used_percentage":41,"resets_at":1775206800}}}
package/README.md CHANGED
@@ -19,7 +19,7 @@
19
19
 
20
20
  **The infrastructure layer for AI agent swarms.**
21
21
 
22
- 86 MCP tools · A2A protocol · OmniMesh VPN · nftables firewall · CDP browser · cookie sync · 2FA TOTP · bi-directional sync · CyberBase persistence
22
+ 88 MCP tools · A2A protocol · OmniMesh VPN · nftables firewall · CDP browser · cookie sync · 2FA TOTP · bi-directional sync · CyberBase persistence
23
23
 
24
24
  </div>
25
25
 
@@ -687,6 +687,8 @@ omniwire/
687
687
 
688
688
  | Version | Date | Changes |
689
689
  |---------|------|---------|
690
+ | **v3.3.1** | 2026-03-30 | New: `omniwire_scrape` tool — Scrapling-powered web scraping (static/browser/stealth modes, Cloudflare bypass, TLS spoofing). 88 tools. |
691
+ | **v3.3.0** | 2026-03-30 | New: `omniwire_coc` tool — unified CyberBase + Obsidian + Canvas sync. Auto-creates vault + canvas. `mirror-db` exports entire DB as .md. Configurable vault via `OMNIWIRE_VAULT_ROOT` env. |
690
692
  | **v3.2.2** | 2026-03-30 | Fix: sync GitHub/npm metadata — badge, description, mermaid diagram all reflect 86 tools |
691
693
  | **v3.2.1** | 2026-03-30 | New: 5 bi-directional sync tools (`omniwire_sync`, `omniwire_sync_rules`, `omniwire_sync_hooks`, `omniwire_sync_memory`, `omniwire_sync_agents`) — 86 tools total |
692
694
  | **v3.2.0** | 2026-03-29 | New: `omniwire_2fa` TOTP manager — add/generate/verify/import/export 2FA codes, CyberBase + 1Password persistence, otpauth:// URI import, bulk code generation |
@@ -6,7 +6,7 @@
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';
9
+ import { readFileSync, writeFileSync, mkdirSync, existsSync, readdirSync } from 'node:fs';
10
10
  import { join } from 'node:path';
11
11
  import { ShellManager, kernelExec } from '../nodes/shell.js';
12
12
  import { RealtimeChannel } from '../nodes/realtime.js';
@@ -248,10 +248,40 @@ async function drainCb() {
248
248
  }
249
249
  // -- Obsidian + Canvas auto-sync ------------------------------------------------
250
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');
251
+ // Vault path resolved from env or default; if it doesn't exist, sync is silently skipped.
252
+ // Set OMNIWIRE_VAULT_ROOT to override, or OMNIWIRE_CANVAS_NAME for custom canvas filename.
253
+ const VAULT_ROOT = process.env.OMNIWIRE_VAULT_ROOT ?? join(process.env.USERPROFILE ?? process.env.HOME ?? '', 'Documents', 'OmniWire');
254
+ const CANVAS_NAME = process.env.OMNIWIRE_CANVAS_NAME ?? 'OmniWire MindMap.canvas';
255
+ const CANVAS_PATH = join(VAULT_ROOT, CANVAS_NAME);
254
256
  const vaultExists = existsSync(VAULT_ROOT);
257
+ /** Ensure the Obsidian vault root and canvas file exist, creating them if needed */
258
+ function ensureVault() {
259
+ try {
260
+ if (!existsSync(VAULT_ROOT))
261
+ mkdirSync(VAULT_ROOT, { recursive: true });
262
+ if (!existsSync(CANVAS_PATH)) {
263
+ // Create a default canvas with a central hub node
264
+ const defaultCanvas = {
265
+ nodes: [{
266
+ id: 'core',
267
+ type: 'text',
268
+ text: '## OmniWire\nCentral knowledge hub',
269
+ x: 0,
270
+ y: 0,
271
+ width: 300,
272
+ height: 120,
273
+ color: '4',
274
+ }],
275
+ edges: [],
276
+ };
277
+ writeFileSync(CANVAS_PATH, JSON.stringify(defaultCanvas, null, '\t'), 'utf-8');
278
+ }
279
+ return true;
280
+ }
281
+ catch {
282
+ return false;
283
+ }
284
+ }
255
285
  /** Map CyberBase category → Obsidian vault subfolder */
256
286
  function vaultFolder(category) {
257
287
  const cat = category.toLowerCase();
@@ -279,8 +309,8 @@ function sanitizeFilename(key) {
279
309
  }
280
310
  /** Auto-sync a knowledge entry to Obsidian vault as a .md file */
281
311
  function syncObsidian(category, key, value) {
282
- if (!vaultExists)
283
- return;
312
+ if (!existsSync(VAULT_ROOT))
313
+ ensureVault();
284
314
  try {
285
315
  const folder = join(VAULT_ROOT, vaultFolder(category));
286
316
  if (!existsSync(folder))
@@ -359,7 +389,9 @@ function canvasColor(category) {
359
389
  }
360
390
  /** Auto-sync a knowledge entry to the Canvas mindmap — adds or updates a node */
361
391
  function syncCanvas(category, key, value) {
362
- if (!vaultExists || !existsSync(CANVAS_PATH))
392
+ if (!existsSync(CANVAS_PATH))
393
+ ensureVault();
394
+ if (!existsSync(CANVAS_PATH))
363
395
  return;
364
396
  try {
365
397
  const raw = readFileSync(CANVAS_PATH, 'utf-8');
@@ -3993,8 +4025,7 @@ echo "port-knock configured: ${ports.join(' -> ')} -> port ${target}"`;
3993
4025
  if (action === 'sync-obsidian') {
3994
4026
  if (!key || !value)
3995
4027
  return fail('key and value required');
3996
- if (!vaultExists)
3997
- return fail(`Obsidian vault not found at ${VAULT_ROOT}`);
4028
+ ensureVault();
3998
4029
  const cat = category ?? 'general';
3999
4030
  syncObsidian(cat, key, value);
4000
4031
  const folder = vaultFolder(cat);
@@ -4003,14 +4034,207 @@ echo "port-knock configured: ${ports.join(' -> ')} -> port ${target}"`;
4003
4034
  if (action === 'sync-canvas') {
4004
4035
  if (!key || !value)
4005
4036
  return fail('key and value required');
4006
- if (!vaultExists || !existsSync(CANVAS_PATH))
4007
- return fail(`Canvas not found at ${CANVAS_PATH}`);
4037
+ ensureVault();
4008
4038
  const cat = category ?? 'general';
4009
4039
  syncCanvas(cat, key, value);
4010
4040
  return okBrief(`synced to Canvas: node auto_${sanitizeFilename(cat)}_${sanitizeFilename(key)} added/updated`);
4011
4041
  }
4012
4042
  return fail('invalid action');
4013
4043
  });
4044
+ // --- Tool: omniwire_coc ---
4045
+ // COC = CyberBase + Obsidian + Canvas — the default sync mode.
4046
+ // Single tool call writes to all three destinations simultaneously.
4047
+ // Also supports 'mirror-db' to export the entire DB as Obsidian .md files.
4048
+ server.tool('omniwire_coc', 'COC (CyberBase + Obsidian + Canvas) — unified sync. Default: writes to PostgreSQL, Obsidian vault (.md), and Canvas mindmap in one call. Use "mirror-db" to export the entire knowledge DB as Obsidian-formatted markdown files. Use "init" to set up the vault + canvas from scratch.', {
4049
+ action: z.enum(['save', 'mirror-db', 'init', 'status']).describe('save: write to all 3 destinations. mirror-db: export entire DB as .md files. init: create vault + canvas. status: show sync state.'),
4050
+ category: z.string().optional().describe('Knowledge category (e.g., tools, vulns, infra, notes)'),
4051
+ key: z.string().optional().describe('Entry key'),
4052
+ value: z.string().optional().describe('Entry value'),
4053
+ entries: z.array(z.object({ category: z.string(), key: z.string(), value: z.string() })).optional().describe('Bulk save: array of {category, key, value}'),
4054
+ }, async ({ action, category, key, value, entries }) => {
4055
+ if (action === 'init') {
4056
+ const created = ensureVault();
4057
+ if (!created)
4058
+ return fail('Failed to create vault directory');
4059
+ // Create standard subdirectories
4060
+ const dirs = ['projects', 'infrastructure', 'knowledge', 'knowledge/security-kb', 'system', 'logs', 'sync', 'memory'];
4061
+ for (const dir of dirs) {
4062
+ const p = join(VAULT_ROOT, dir);
4063
+ if (!existsSync(p))
4064
+ mkdirSync(p, { recursive: true });
4065
+ }
4066
+ return okBrief(`COC vault initialized at ${VAULT_ROOT}\n Canvas: ${CANVAS_NAME}\n Directories: ${dirs.join(', ')}`);
4067
+ }
4068
+ if (action === 'status') {
4069
+ const vaultOk = existsSync(VAULT_ROOT);
4070
+ const canvasOk = existsSync(CANVAS_PATH);
4071
+ const h = getCbHealth();
4072
+ let canvasNodes = 0;
4073
+ if (canvasOk) {
4074
+ try {
4075
+ const raw = JSON.parse(readFileSync(CANVAS_PATH, 'utf-8'));
4076
+ canvasNodes = raw.nodes?.length ?? 0;
4077
+ }
4078
+ catch { /* ignore */ }
4079
+ }
4080
+ // Count .md files in vault
4081
+ let mdCount = 0;
4082
+ const countMd = (dir) => {
4083
+ try {
4084
+ for (const entry of readdirSync(dir, { withFileTypes: true })) {
4085
+ if (entry.isDirectory())
4086
+ countMd(join(dir, entry.name));
4087
+ else if (entry.name.endsWith('.md'))
4088
+ mdCount++;
4089
+ }
4090
+ }
4091
+ catch { /* ignore */ }
4092
+ };
4093
+ if (vaultOk)
4094
+ countMd(VAULT_ROOT);
4095
+ return okBrief(`COC Status:\n Vault: ${vaultOk ? 'OK' : 'MISSING'} (${VAULT_ROOT})\n Canvas: ${canvasOk ? `OK (${canvasNodes} nodes)` : 'MISSING'}\n CyberBase: ${h.healthy ? 'OK' : 'UNHEALTHY'} (queue: ${h.queueSize})\n Obsidian files: ${mdCount} .md files`);
4096
+ }
4097
+ if (action === 'save') {
4098
+ // Bulk save
4099
+ if (entries?.length) {
4100
+ for (const e of entries) {
4101
+ cb(e.category, e.key, e.value);
4102
+ }
4103
+ return okBrief(`COC: saved ${entries.length} entries → CyberBase + Obsidian + Canvas`);
4104
+ }
4105
+ // Single save
4106
+ if (!key || !value)
4107
+ return fail('key and value required (or use entries[] for bulk)');
4108
+ const cat = category ?? 'general';
4109
+ cb(cat, key, value);
4110
+ return okBrief(`COC: saved ${cat}:${key} (${value.length} chars) → CyberBase + Obsidian + Canvas`);
4111
+ }
4112
+ if (action === 'mirror-db') {
4113
+ if (!cbManager)
4114
+ return fail('no CyberBase connection');
4115
+ ensureVault();
4116
+ // Export all knowledge entries from PostgreSQL and write as .md files
4117
+ const r = await cbManager.exec('contabo', `psql -h 127.0.0.1 -U cyberbase -d cyberbase -t -A -F '|' -c "SET statement_timeout='30s'; SELECT key, value->>'data', updated_at FROM knowledge WHERE source_tool='omniwire' ORDER BY key;" 2>/dev/null`);
4118
+ if (!r.stdout.trim())
4119
+ return okBrief('mirror-db: no entries found in CyberBase');
4120
+ const lines = r.stdout.trim().split('\n').filter((l) => l.includes('|'));
4121
+ let synced = 0;
4122
+ let skipped = 0;
4123
+ for (const line of lines) {
4124
+ const parts = line.split('|');
4125
+ if (parts.length < 2) {
4126
+ skipped++;
4127
+ continue;
4128
+ }
4129
+ const fullKey = parts[0].trim();
4130
+ const val = parts.slice(1, -1).join('|').trim(); // rejoin in case value has pipes
4131
+ if (!fullKey || !val) {
4132
+ skipped++;
4133
+ continue;
4134
+ }
4135
+ // Parse category:key format
4136
+ const colonIdx = fullKey.indexOf(':');
4137
+ const cat = colonIdx > 0 ? fullKey.slice(0, colonIdx) : 'general';
4138
+ const entryKey = colonIdx > 0 ? fullKey.slice(colonIdx + 1) : fullKey;
4139
+ syncObsidian(cat, entryKey, val);
4140
+ if (val.length > 50)
4141
+ syncCanvas(cat, entryKey, val);
4142
+ synced++;
4143
+ }
4144
+ return okBrief(`COC mirror-db: ${synced} entries synced to Obsidian + Canvas, ${skipped} skipped\n Vault: ${VAULT_ROOT}`);
4145
+ }
4146
+ return fail('invalid action');
4147
+ });
4148
+ // --- Tool: omniwire_scrape ---
4149
+ // Scrapling-powered web scraping: static HTTP (TLS spoofing), browser (JS rendering), stealth (anti-bot bypass).
4150
+ // Runs via Scrapling MCP server on Contabo (port 8931) or falls back to CLI.
4151
+ server.tool('omniwire_scrape', 'Scrape web pages using Scrapling — adaptive, anti-bot web scraping. Modes: http (fast TLS-spoofed static fetch), browser (Playwright JS rendering), stealth (Camoufox + Cloudflare bypass). Returns markdown/html/text. Powered by Scrapling on Contabo.', {
4152
+ url: z.string().describe('Target URL to scrape'),
4153
+ urls: z.array(z.string()).optional().describe('Multiple URLs for bulk scraping (uses session pooling)'),
4154
+ mode: z.enum(['http', 'browser', 'stealth']).default('http').describe('http=fast static, browser=JS rendering, stealth=anti-bot+Cloudflare'),
4155
+ extraction_type: z.enum(['markdown', 'html', 'text']).default('markdown').describe('Output format'),
4156
+ css_selector: z.string().optional().describe('CSS selector to extract specific elements only'),
4157
+ solve_cloudflare: z.boolean().optional().describe('Solve Cloudflare Turnstile (stealth mode only)'),
4158
+ wait_selector: z.string().optional().describe('Wait for this CSS selector before extracting (browser/stealth)'),
4159
+ network_idle: z.boolean().optional().describe('Wait for network idle before extracting'),
4160
+ proxy: z.string().optional().describe('Proxy URL (http://user:pass@host:port)'),
4161
+ timeout: z.number().default(30).describe('Timeout in seconds'),
4162
+ impersonate: z.string().optional().describe('TLS fingerprint: chrome, safari, firefox (http mode)'),
4163
+ node: z.string().optional().describe('Node to run on (default: contabo)'),
4164
+ label: z.string().optional().describe('Short label for task tracking'),
4165
+ }, async ({ url, urls, mode, extraction_type, css_selector, solve_cloudflare, wait_selector, network_idle, proxy, timeout, impersonate, node: targetNode, label }) => {
4166
+ if (!manager)
4167
+ return fail('NodeManager not initialized');
4168
+ const target = targetNode ?? 'contabo';
4169
+ // Build the Scrapling Python command based on mode
4170
+ const allUrls = urls?.length ? urls : [url];
4171
+ const urlList = allUrls.map(u => `'${u.replace(/'/g, "'\\''")}'`).join(' ');
4172
+ // Map mode to Scrapling fetcher
4173
+ const fetcherMap = {
4174
+ http: 'Fetcher',
4175
+ browser: 'DynamicFetcher',
4176
+ stealth: 'StealthyFetcher',
4177
+ };
4178
+ const fetcher = fetcherMap[mode] ?? 'Fetcher';
4179
+ // Build Python script
4180
+ const proxyArg = proxy ? `, proxy='${proxy.replace(/'/g, "'\\''")}'` : '';
4181
+ const impersonateArg = impersonate ? `, impersonate='${impersonate}'` : '';
4182
+ const timeoutArg = `, timeout=${timeout}`;
4183
+ const cfArg = solve_cloudflare ? ', solve_cloudflare=True' : '';
4184
+ const waitArg = wait_selector ? `, wait_selector='${wait_selector.replace(/'/g, "'\\''")}'` : '';
4185
+ const idleArg = network_idle ? ', network_idle=True' : '';
4186
+ const selectorArg = css_selector ? `.css('${css_selector.replace(/'/g, "'\\''")}')` : '';
4187
+ // Extraction type mapping
4188
+ const extractMap = {
4189
+ markdown: '.get_all_text()',
4190
+ html: '.prettify() if hasattr(page, "prettify") else str(page)',
4191
+ text: '.get_all_text()',
4192
+ };
4193
+ const extract = selectorArg ? `.getall()` : extractMap[extraction_type] ?? '.get_all_text()';
4194
+ const script = `
4195
+ import json, sys
4196
+ try:
4197
+ from scrapling import ${fetcher}
4198
+ results = []
4199
+ urls = ${JSON.stringify(allUrls)}
4200
+ for u in urls:
4201
+ try:
4202
+ page = ${fetcher}().get(u${proxyArg}${impersonateArg}${timeoutArg}${cfArg}${waitArg}${idleArg})
4203
+ if page.status == 200:
4204
+ content = page${selectorArg}${extract}
4205
+ if isinstance(content, list):
4206
+ content = '\\n'.join(str(c) for c in content)
4207
+ results.append({"url": u, "status": page.status, "content": str(content)[:50000]})
4208
+ else:
4209
+ results.append({"url": u, "status": page.status, "content": f"HTTP {page.status}"})
4210
+ except Exception as e:
4211
+ results.append({"url": u, "status": 0, "error": str(e)[:500]})
4212
+ print(json.dumps(results))
4213
+ except Exception as e:
4214
+ print(json.dumps([{"error": str(e)}]))
4215
+ `.trim();
4216
+ try {
4217
+ const r = await manager.exec(target, `python3 -c ${JSON.stringify(script)}`);
4218
+ const output = r.stdout.trim();
4219
+ try {
4220
+ const results = JSON.parse(output);
4221
+ if (results.length === 1) {
4222
+ const res = results[0];
4223
+ if (res.error)
4224
+ return fail(`scrape error: ${res.error}`);
4225
+ return okBrief(`[${res.status}] ${res.url}\n\n${res.content}`);
4226
+ }
4227
+ const summary = results.map((r) => `[${r.status ?? 'ERR'}] ${r.url ?? '?'}: ${r.error ?? `${(r.content ?? '').length} chars`}`).join('\n');
4228
+ return okBrief(`Scraped ${results.length} URLs:\n${summary}\n\n${results.map((r) => r.content ?? '').join('\n---\n').slice(0, 50000)}`);
4229
+ }
4230
+ catch {
4231
+ return okBrief(output.slice(0, 10000));
4232
+ }
4233
+ }
4234
+ catch (e) {
4235
+ return fail(`scrape failed on ${target}: ${e.message}`);
4236
+ }
4237
+ });
4014
4238
  // --- Tool 54: omniwire_omnimesh ---
4015
4239
  server.tool('omniwire_omnimesh', 'OmniMesh — built-in WireGuard mesh network manager. Create, manage, and monitor a full-mesh or hub-spoke WireGuard VPN across all nodes and any OS (Linux/Windows/macOS). Actions: status, init, add-peer, remove-peer, genkeys, deploy-config, up, down, install, health, rotate-keys, discover-endpoint, topology, sync-peers.', {
4016
4240
  action: z.enum([