clawmoat 0.7.0 → 1.0.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.
Files changed (178) hide show
  1. package/.dockerignore +9 -0
  2. package/CHANGELOG.md +18 -0
  3. package/CONTRIBUTING.md +4 -2
  4. package/DEMO.md +87 -0
  5. package/Dockerfile +5 -18
  6. package/README.md +294 -8
  7. package/SECURITY.md +58 -10
  8. package/THREAT_MODEL.md +129 -0
  9. package/agent/README.md +131 -0
  10. package/agent/index.js +471 -0
  11. package/agent/install-service.sh +94 -0
  12. package/agent/openclaw-hook.js +453 -0
  13. package/agent/provider-setup.js +649 -0
  14. package/agent/setup.js +274 -0
  15. package/assets/BADGE-USAGE.md +20 -0
  16. package/assets/clawmoat-badge.svg +21 -0
  17. package/bin/clawmoat.js +468 -111
  18. package/docs/affiliates/dashboard.html +124 -0
  19. package/docs/affiliates/index.html +236 -0
  20. package/docs/agent-install.html +183 -0
  21. package/docs/ai-agent-security-scanner.html +10 -6
  22. package/docs/badge/index.html +149 -0
  23. package/docs/badge/scanning.svg +23 -0
  24. package/docs/blog/386-malicious-skills.html +262 -0
  25. package/docs/blog/40000-exposed-openclaw-instances.html +201 -0
  26. package/docs/blog/agent-trust-protocol.html +198 -0
  27. package/docs/blog/ai-agent-earns-commissions.html +230 -0
  28. package/docs/blog/bugmageddon-agent-firewall.html +174 -0
  29. package/docs/blog/calculator-math.html +180 -0
  30. package/docs/blog/clawmoat-vs-llamafirewall-nemo-guardrails.html +229 -0
  31. package/docs/blog/host-guardian-launch.html +18 -8
  32. package/docs/blog/ibm-experts-agent-runtime-protection.html +247 -0
  33. package/docs/blog/index.html +211 -9
  34. package/docs/blog/langchain-security-tutorial.html +18 -8
  35. package/docs/blog/mcp-30-cves-security-crisis.html +286 -0
  36. package/docs/blog/meta-researcher-rogue-agent.html +201 -0
  37. package/docs/blog/microsoft-openclaw-workstation-security.html +235 -0
  38. package/docs/blog/nist-ai-agent-standards-clawmoat.html +377 -0
  39. package/docs/blog/oasis-websocket-hijack.html +212 -0
  40. package/docs/blog/ollama-openclaw-security.html +160 -0
  41. package/docs/blog/openclaw-enterprise-readiness-claw10.html +199 -0
  42. package/docs/blog/openclaw-security-reckoning-2026.html +368 -0
  43. package/docs/blog/owasp-agentic-ai-top10.html +18 -8
  44. package/docs/blog/securing-ai-agents.html +18 -8
  45. package/docs/blog/supply-chain-agents.html +18 -8
  46. package/docs/business/index.html +525 -0
  47. package/docs/business/install.html +261 -0
  48. package/docs/checklist.html +174 -0
  49. package/docs/compare/index.html +122 -0
  50. package/docs/compare/lakera/index.html +62 -0
  51. package/docs/compare/llm-guard/index.html +49 -0
  52. package/docs/compare/snyk-agent-scan/index.html +63 -0
  53. package/docs/compare.html +10 -6
  54. package/docs/dashboard/index.html +520 -0
  55. package/docs/finance/index.html +220 -0
  56. package/docs/guides/business-deployment.html +770 -0
  57. package/docs/hall-of-fame.html +174 -0
  58. package/docs/index.html +447 -154
  59. package/docs/install.sh +557 -0
  60. package/docs/integrations/langchain.html +14 -6
  61. package/docs/integrations/openai.html +14 -6
  62. package/docs/integrations/openclaw.html +55 -7
  63. package/docs/plans/2026-03-26-threat-intel-api.md +255 -0
  64. package/docs/plans/2026-04-14-bugmageddon-marketing-pack.md +329 -0
  65. package/docs/plans/2026-04-14-clawmoat-v1-bugmageddon.md +248 -0
  66. package/docs/plans/2026-04-14-v1-release-update.md +91 -0
  67. package/docs/plans/2026-04-19-supabase-audit.md +68 -0
  68. package/docs/plans/2026-05-12-sales-push.md +303 -0
  69. package/docs/playground/index.html +893 -0
  70. package/docs/playground.html +4 -7
  71. package/docs/privacy-policy/index.html +122 -0
  72. package/docs/rfcs/defense-in-depth.md +467 -0
  73. package/docs/scan/index.html +358 -0
  74. package/docs/services/case-study.html +255 -0
  75. package/docs/services/downloads/install-openclaw.bat +45 -0
  76. package/docs/services/downloads/install-openclaw.command +38 -0
  77. package/docs/services/downloads/install-openclaw.sh +38 -0
  78. package/docs/services/get-started.html +165 -0
  79. package/docs/services/index.html +598 -0
  80. package/docs/services/multi-agent-security.html +284 -0
  81. package/docs/services/one-pager.html +99 -0
  82. package/docs/services/pitch-deck.html +229 -0
  83. package/docs/services/roi-calculator.html +258 -0
  84. package/docs/sitemap.xml +192 -2
  85. package/docs/support/index.html +135 -0
  86. package/docs/templates/customer-service/HEARTBEAT.md +61 -0
  87. package/docs/templates/customer-service/MEMORY.md +89 -0
  88. package/docs/templates/customer-service/SOUL.md +41 -0
  89. package/docs/templates/customer-service/USER.md +56 -0
  90. package/docs/templates/executive/HEARTBEAT.md +86 -0
  91. package/docs/templates/executive/MEMORY.md +92 -0
  92. package/docs/templates/executive/SOUL.md +44 -0
  93. package/docs/templates/executive/USER.md +62 -0
  94. package/docs/templates/finance/HEARTBEAT.md +58 -0
  95. package/docs/templates/finance/MEMORY.md +87 -0
  96. package/docs/templates/finance/SOUL.md +38 -0
  97. package/docs/templates/finance/USER.md +53 -0
  98. package/docs/templates/index.html +115 -0
  99. package/docs/templates/operations/HEARTBEAT.md +63 -0
  100. package/docs/templates/operations/MEMORY.md +68 -0
  101. package/docs/templates/operations/SOUL.md +38 -0
  102. package/docs/templates/operations/USER.md +49 -0
  103. package/docs/templates/sales/HEARTBEAT.md +55 -0
  104. package/docs/templates/sales/MEMORY.md +89 -0
  105. package/docs/templates/sales/SOUL.md +34 -0
  106. package/docs/templates/sales/USER.md +54 -0
  107. package/docs/terms-of-service/index.html +122 -0
  108. package/eslint.config.js +32 -0
  109. package/evals/README.md +29 -0
  110. package/evals/cases.json +390 -0
  111. package/evals/results.md +68 -0
  112. package/evals/run.js +180 -0
  113. package/examples/basic-usage.js +38 -0
  114. package/examples/demo-attack/demo.js +186 -0
  115. package/examples/python-quickstart/README.md +54 -0
  116. package/examples/python-quickstart/clawmoat_client.py +167 -0
  117. package/examples/video-demo/README.md +14 -0
  118. package/examples/video-demo/scene-a-normal.js +29 -0
  119. package/examples/video-demo/scene-b-attack-arrives.js +31 -0
  120. package/examples/video-demo/scene-c-hijack.js +44 -0
  121. package/examples/video-demo/scene-d-clawmoat.js +46 -0
  122. package/integrations/crewai/README.md +32 -0
  123. package/integrations/crewai/clawmoat_crewai/__init__.py +17 -0
  124. package/integrations/crewai/clawmoat_crewai/guard.py +103 -0
  125. package/integrations/crewai/pyproject.toml +21 -0
  126. package/integrations/langchain/README.md +91 -0
  127. package/integrations/langchain/clawmoat_langchain/__init__.py +17 -0
  128. package/integrations/langchain/clawmoat_langchain/callback.py +489 -0
  129. package/integrations/langchain/pyproject.toml +32 -0
  130. package/integrations/litellm/README.md +324 -0
  131. package/integrations/litellm/clawmoat_litellm/__init__.py +21 -0
  132. package/integrations/litellm/clawmoat_litellm/callback.py +329 -0
  133. package/integrations/litellm/clawmoat_litellm/proxy_middleware.py +224 -0
  134. package/integrations/litellm/pyproject.toml +74 -0
  135. package/integrations/openai-agents/README.md +392 -0
  136. package/integrations/openai-agents/clawmoat_openai_agents/__init__.py +20 -0
  137. package/integrations/openai-agents/clawmoat_openai_agents/guardrail.py +431 -0
  138. package/integrations/openai-agents/clawmoat_openai_agents/middleware.py +311 -0
  139. package/integrations/openai-agents/pyproject.toml +76 -0
  140. package/package.json +6 -5
  141. package/plugins/openclaw-adapter/PHASE1.md +439 -0
  142. package/plugins/openclaw-adapter/README.md +103 -0
  143. package/plugins/openclaw-adapter/SPEC.md +1644 -0
  144. package/plugins/openclaw-adapter/package.json +31 -0
  145. package/plugins/openclaw-adapter/src/index.test.ts +226 -0
  146. package/plugins/openclaw-adapter/src/index.ts +140 -0
  147. package/plugins/openclaw-adapter/tsconfig.json +14 -0
  148. package/server/data/threats.json +290 -0
  149. package/server/index.js +224 -10
  150. package/src/adapters/express.js +161 -0
  151. package/src/adapters/index.js +92 -0
  152. package/src/adapters/langchain.js +185 -0
  153. package/src/approval/index.js +456 -0
  154. package/src/ban-scanner.js +200 -0
  155. package/src/boundary-scanner.js +296 -0
  156. package/src/ci-scanner.js +279 -0
  157. package/src/code-scanner.js +245 -0
  158. package/src/enforce.js +166 -0
  159. package/src/finance/index.js +585 -0
  160. package/src/finance/mcp-firewall.js +486 -0
  161. package/src/formatters/json.js +80 -0
  162. package/src/formatters/sarif.js +388 -0
  163. package/src/guardian/alerts.js +34 -3
  164. package/src/guardian/gateway-monitor.js +590 -0
  165. package/src/guardian/index.js +41 -2
  166. package/src/index.js +105 -0
  167. package/src/integrations/agentmesh.js +501 -0
  168. package/src/language-detector.js +201 -0
  169. package/src/mcp-scanner.js +253 -0
  170. package/src/multimodal/index.js +579 -0
  171. package/src/obfuscation-scanner.js +457 -0
  172. package/src/policy-engine.js +402 -0
  173. package/src/scanners/dependency-attacks.js +128 -0
  174. package/src/scanners/prompt-injection.js +18 -0
  175. package/src/scanners/supply-chain.js +14 -0
  176. package/src/templates/default-config.yml +90 -0
  177. package/src/vuln-ops/exploitability.js +46 -0
  178. package/src/watch/live-monitor.js +720 -0
@@ -0,0 +1,720 @@
1
+ /**
2
+ * ClawMoat Live Monitor
3
+ * Real-time terminal dashboard for AI agent security monitoring
4
+ * Like htop but for AI agents - visual, impressive, demo-worthy
5
+ */
6
+
7
+ const fs = require('fs');
8
+ const path = require('path');
9
+ const os = require('os');
10
+ const { EventEmitter } = require('events');
11
+
12
+ /**
13
+ * ANSI color codes for terminal output
14
+ */
15
+ const COLORS = {
16
+ RESET: '\x1b[0m',
17
+ BOLD: '\x1b[1m',
18
+ DIM: '\x1b[2m',
19
+ RED: '\x1b[31m',
20
+ GREEN: '\x1b[32m',
21
+ YELLOW: '\x1b[33m',
22
+ BLUE: '\x1b[34m',
23
+ MAGENTA: '\x1b[35m',
24
+ CYAN: '\x1b[36m',
25
+ WHITE: '\x1b[37m',
26
+ BG_RED: '\x1b[41m',
27
+ BG_GREEN: '\x1b[42m',
28
+ BG_YELLOW: '\x1b[43m'
29
+ };
30
+
31
+ /**
32
+ * Unicode characters for better visual display
33
+ */
34
+ const CHARS = {
35
+ BLOCK_FULL: '█',
36
+ BLOCK_THREE_QUARTERS: '▊',
37
+ BLOCK_HALF: '▌',
38
+ BLOCK_QUARTER: '▎',
39
+ BOX_VERTICAL: '│',
40
+ BOX_HORIZONTAL: '─',
41
+ BOX_TOP_LEFT: '┌',
42
+ BOX_TOP_RIGHT: '┐',
43
+ BOX_BOTTOM_LEFT: '└',
44
+ BOX_BOTTOM_RIGHT: '┘',
45
+ BOX_CROSS: '┼',
46
+ BOX_T_DOWN: '┬',
47
+ BOX_T_UP: '┴',
48
+ BOX_T_RIGHT: '├',
49
+ BOX_T_LEFT: '┤',
50
+ ARROW_UP: '↑',
51
+ ARROW_DOWN: '↓',
52
+ SHIELD: '🛡️',
53
+ WARNING: '⚠️',
54
+ BLOCKED: '🚫',
55
+ CHECK: '✓',
56
+ CROSS: '✗'
57
+ };
58
+
59
+ class LiveMonitor extends EventEmitter {
60
+ constructor(options = {}) {
61
+ super();
62
+
63
+ this.options = {
64
+ refreshRate: options.refreshRate || 1000, // milliseconds
65
+ watchDir: options.watchDir || path.join(os.homedir(), '.openclaw'),
66
+ showNetworkGraph: options.showNetworkGraph !== false,
67
+ showThreatMap: options.showThreatMap !== false,
68
+ maxHistoryItems: options.maxHistoryItems || 100,
69
+ animateCharts: options.animateCharts !== false,
70
+ ...options
71
+ };
72
+
73
+ this.stats = {
74
+ uptime: 0,
75
+ totalScans: 0,
76
+ threatsBlocked: 0,
77
+ filesAccessed: 0,
78
+ networkCalls: 0,
79
+ agentsActive: 0,
80
+ lastThreat: null,
81
+ threatHistory: [],
82
+ fileAccess: [],
83
+ networkActivity: [],
84
+ threatsByType: {},
85
+ scanRate: 0,
86
+ threatRate: 0
87
+ };
88
+
89
+ this.isRunning = false;
90
+ this.startTime = Date.now();
91
+ this.lastUpdate = Date.now();
92
+ this.intervals = [];
93
+
94
+ // Terminal state
95
+ this.terminalWidth = process.stdout.columns || 80;
96
+ this.terminalHeight = process.stdout.rows || 24;
97
+
98
+ // Bind methods
99
+ this.handleResize = this.handleResize.bind(this);
100
+ this.handleKeypress = this.handleKeypress.bind(this);
101
+ }
102
+
103
+ /**
104
+ * Start the live monitoring dashboard
105
+ */
106
+ async start() {
107
+ if (this.isRunning) return;
108
+
109
+ this.isRunning = true;
110
+ this.startTime = Date.now();
111
+
112
+ // Setup terminal
113
+ this.setupTerminal();
114
+
115
+ // Start monitoring loops
116
+ this.startFileWatcher();
117
+ this.startSessionMonitor();
118
+ this.startNetworkMonitor();
119
+
120
+ // Main display loop
121
+ const displayInterval = setInterval(() => {
122
+ this.updateDisplay();
123
+ }, this.options.refreshRate);
124
+ this.intervals.push(displayInterval);
125
+
126
+ // Stats calculation loop
127
+ const statsInterval = setInterval(() => {
128
+ this.updateStats();
129
+ }, 5000);
130
+ this.intervals.push(statsInterval);
131
+
132
+ this.emit('started');
133
+ console.log(`${COLORS.GREEN}${CHARS.SHIELD} ClawMoat Live Monitor started${COLORS.RESET}`);
134
+ }
135
+
136
+ /**
137
+ * Stop the monitoring dashboard
138
+ */
139
+ stop() {
140
+ if (!this.isRunning) return;
141
+
142
+ this.isRunning = false;
143
+
144
+ // Clear intervals
145
+ this.intervals.forEach(interval => clearInterval(interval));
146
+ this.intervals = [];
147
+
148
+ // Cleanup terminal
149
+ this.cleanupTerminal();
150
+
151
+ this.emit('stopped');
152
+ console.log(`${COLORS.YELLOW}${CHARS.WARNING} ClawMoat Live Monitor stopped${COLORS.RESET}`);
153
+ }
154
+
155
+ /**
156
+ * Setup terminal for live monitoring
157
+ */
158
+ setupTerminal() {
159
+ // Hide cursor
160
+ process.stdout.write('\x1b[?25l');
161
+
162
+ // Clear screen
163
+ process.stdout.write('\x1b[2J');
164
+
165
+ // Handle terminal resize
166
+ process.stdout.on('resize', this.handleResize);
167
+
168
+ // Setup keyboard input
169
+ if (process.stdin.isTTY) {
170
+ process.stdin.setRawMode(true);
171
+ process.stdin.on('keypress', this.handleKeypress);
172
+ process.stdin.resume();
173
+ }
174
+ }
175
+
176
+ /**
177
+ * Cleanup terminal state
178
+ */
179
+ cleanupTerminal() {
180
+ // Show cursor
181
+ process.stdout.write('\x1b[?25h');
182
+
183
+ // Reset terminal
184
+ process.stdout.write('\x1b[0m\x1b[2J\x1b[H');
185
+
186
+ // Cleanup stdin
187
+ if (process.stdin.isTTY) {
188
+ process.stdin.setRawMode(false);
189
+ process.stdin.removeListener('keypress', this.handleKeypress);
190
+ }
191
+ }
192
+
193
+ /**
194
+ * Handle terminal resize
195
+ */
196
+ handleResize() {
197
+ this.terminalWidth = process.stdout.columns || 80;
198
+ this.terminalHeight = process.stdout.rows || 24;
199
+ }
200
+
201
+ /**
202
+ * Handle keypress events
203
+ */
204
+ handleKeypress(key, data) {
205
+ if (key === 'q' || key === '\u0003') { // 'q' or Ctrl+C
206
+ this.stop();
207
+ process.exit(0);
208
+ }
209
+ }
210
+
211
+ /**
212
+ * Start file system watcher
213
+ */
214
+ startFileWatcher() {
215
+ const credentialsPath = path.join(this.options.watchDir, 'credentials');
216
+ const sessionsPath = path.join(this.options.watchDir, 'agents', 'main', 'sessions');
217
+
218
+ [credentialsPath, sessionsPath].forEach(dir => {
219
+ if (fs.existsSync(dir)) {
220
+ fs.watch(dir, { recursive: true }, (eventType, filename) => {
221
+ if (filename && eventType === 'change') {
222
+ this.recordFileAccess(filename, path.join(dir, filename));
223
+ }
224
+ });
225
+ }
226
+ });
227
+ }
228
+
229
+ /**
230
+ * Start session log monitor
231
+ */
232
+ startSessionMonitor() {
233
+ const sessionInterval = setInterval(() => {
234
+ this.scanRecentSessions();
235
+ }, 2000);
236
+ this.intervals.push(sessionInterval);
237
+ }
238
+
239
+ /**
240
+ * Start network activity monitor
241
+ */
242
+ startNetworkMonitor() {
243
+ // Monitor for new network calls in session logs
244
+ const networkInterval = setInterval(() => {
245
+ this.scanNetworkActivity();
246
+ }, 3000);
247
+ this.intervals.push(networkInterval);
248
+ }
249
+
250
+ /**
251
+ * Record file access event
252
+ */
253
+ recordFileAccess(filename, fullPath) {
254
+ this.stats.filesAccessed++;
255
+
256
+ const event = {
257
+ timestamp: Date.now(),
258
+ filename,
259
+ fullPath,
260
+ type: this.classifyFileType(filename)
261
+ };
262
+
263
+ this.stats.fileAccess.unshift(event);
264
+ if (this.stats.fileAccess.length > this.options.maxHistoryItems) {
265
+ this.stats.fileAccess.pop();
266
+ }
267
+ }
268
+
269
+ /**
270
+ * Classify file type for display
271
+ */
272
+ classifyFileType(filename) {
273
+ if (filename.includes('credential')) return 'credential';
274
+ if (filename.includes('session')) return 'session';
275
+ if (filename.includes('skill')) return 'skill';
276
+ if (filename.includes('memory')) return 'memory';
277
+ return 'other';
278
+ }
279
+
280
+ /**
281
+ * Scan recent session files for threats
282
+ */
283
+ scanRecentSessions() {
284
+ const sessionsDir = path.join(this.options.watchDir, 'agents', 'main', 'sessions');
285
+
286
+ if (!fs.existsSync(sessionsDir)) return;
287
+
288
+ try {
289
+ const files = fs.readdirSync(sessionsDir)
290
+ .filter(f => f.endsWith('.jsonl'))
291
+ .sort((a, b) => {
292
+ const statA = fs.statSync(path.join(sessionsDir, a));
293
+ const statB = fs.statSync(path.join(sessionsDir, b));
294
+ return statB.mtime - statA.mtime;
295
+ })
296
+ .slice(0, 3); // Check 3 most recent files
297
+
298
+ files.forEach(file => {
299
+ this.procesSessionFile(path.join(sessionsDir, file));
300
+ });
301
+ } catch (error) {
302
+ // Silent fail - directory might not exist yet
303
+ }
304
+ }
305
+
306
+ /**
307
+ * Process session file for threats
308
+ */
309
+ procesSessionFile(filepath) {
310
+ try {
311
+ const content = fs.readFileSync(filepath, 'utf8');
312
+ const lines = content.split('\n').filter(line => line.trim());
313
+
314
+ lines.forEach(line => {
315
+ try {
316
+ const entry = JSON.parse(line);
317
+ if (entry.type === 'tool_call' || entry.type === 'message') {
318
+ this.stats.totalScans++;
319
+ this.simulateThreatDetection(entry);
320
+ }
321
+ } catch (e) {
322
+ // Skip invalid JSON lines
323
+ }
324
+ });
325
+ } catch (error) {
326
+ // Silent fail - file might be locked
327
+ }
328
+ }
329
+
330
+ /**
331
+ * Simulate threat detection (replace with real ClawMoat scanning)
332
+ */
333
+ simulateThreatDetection(entry) {
334
+ const ClawMoat = require('../index');
335
+ const moat = new ClawMoat();
336
+
337
+ const text = entry.content || entry.data || '';
338
+ if (typeof text === 'string' && text.length > 0) {
339
+ const result = moat.scan(text);
340
+
341
+ if (!result.safe && result.findings.length > 0) {
342
+ this.recordThreat(result.findings[0]);
343
+ }
344
+ }
345
+ }
346
+
347
+ /**
348
+ * Record threat detection
349
+ */
350
+ recordThreat(finding) {
351
+ this.stats.threatsBlocked++;
352
+ this.stats.lastThreat = finding;
353
+
354
+ const threat = {
355
+ timestamp: Date.now(),
356
+ type: finding.type,
357
+ subtype: finding.subtype,
358
+ severity: finding.severity
359
+ };
360
+
361
+ this.stats.threatHistory.unshift(threat);
362
+ if (this.stats.threatHistory.length > this.options.maxHistoryItems) {
363
+ this.stats.threatHistory.pop();
364
+ }
365
+
366
+ // Update threat type counts
367
+ this.stats.threatsByType[finding.type] = (this.stats.threatsByType[finding.type] || 0) + 1;
368
+ }
369
+
370
+ /**
371
+ * Scan for network activity
372
+ */
373
+ scanNetworkActivity() {
374
+ // Simulate network monitoring
375
+ if (Math.random() < 0.3) { // 30% chance of network activity
376
+ const activity = {
377
+ timestamp: Date.now(),
378
+ url: this.generateRandomUrl(),
379
+ method: Math.random() < 0.8 ? 'GET' : 'POST',
380
+ status: Math.random() < 0.95 ? 'allowed' : 'blocked'
381
+ };
382
+
383
+ this.stats.networkActivity.unshift(activity);
384
+ if (this.stats.networkActivity.length > this.options.maxHistoryItems) {
385
+ this.stats.networkActivity.pop();
386
+ }
387
+
388
+ this.stats.networkCalls++;
389
+ }
390
+ }
391
+
392
+ /**
393
+ * Generate random URL for simulation
394
+ */
395
+ generateRandomUrl() {
396
+ const domains = ['github.com', 'api.openai.com', 'stackoverflow.com', 'google.com', 'npmjs.com'];
397
+ const paths = ['api/v1/models', 'search', 'issues', 'releases', 'packages'];
398
+
399
+ const domain = domains[Math.floor(Math.random() * domains.length)];
400
+ const pathPart = paths[Math.floor(Math.random() * paths.length)];
401
+
402
+ return `https://${domain}/${pathPart}`;
403
+ }
404
+
405
+ /**
406
+ * Update calculated statistics
407
+ */
408
+ updateStats() {
409
+ const now = Date.now();
410
+ this.stats.uptime = Math.floor((now - this.startTime) / 1000);
411
+
412
+ // Calculate rates
413
+ if (this.stats.uptime > 0) {
414
+ this.stats.scanRate = this.stats.totalScans / this.stats.uptime;
415
+ this.stats.threatRate = this.stats.threatsBlocked / this.stats.uptime;
416
+ }
417
+
418
+ this.lastUpdate = now;
419
+
420
+ // Simulate active agents count
421
+ this.stats.agentsActive = Math.floor(Math.random() * 3) + 1;
422
+ }
423
+
424
+ /**
425
+ * Main display update function
426
+ */
427
+ updateDisplay() {
428
+ // Clear screen and move to top
429
+ process.stdout.write('\x1b[2J\x1b[H');
430
+
431
+ const output = this.buildDisplay();
432
+ process.stdout.write(output);
433
+ }
434
+
435
+ /**
436
+ * Build the complete display output
437
+ */
438
+ buildDisplay() {
439
+ const sections = [];
440
+
441
+ sections.push(this.buildHeader());
442
+ sections.push(this.buildStatsOverview());
443
+ sections.push(this.buildThreatMap());
444
+ sections.push(this.buildActivityFeed());
445
+ sections.push(this.buildNetworkGraph());
446
+ sections.push(this.buildFooter());
447
+
448
+ return sections.join('\n');
449
+ }
450
+
451
+ /**
452
+ * Build header section
453
+ */
454
+ buildHeader() {
455
+ const title = `${CHARS.SHIELD} ClawMoat Live Monitor`;
456
+ const uptime = this.formatUptime(this.stats.uptime);
457
+ const timestamp = new Date().toLocaleTimeString();
458
+
459
+ const headerLine = this.createBoxLine(
460
+ `${COLORS.BOLD}${COLORS.CYAN}${title}${COLORS.RESET}`,
461
+ `${COLORS.DIM}Uptime: ${uptime} | ${timestamp}${COLORS.RESET}`,
462
+ this.terminalWidth
463
+ );
464
+
465
+ return `${this.createHorizontalLine('top')}\n${headerLine}\n${this.createHorizontalLine('middle')}`;
466
+ }
467
+
468
+ /**
469
+ * Build statistics overview
470
+ */
471
+ buildStatsOverview() {
472
+ const stats = [
473
+ { label: 'Agents Active', value: this.stats.agentsActive, color: COLORS.GREEN },
474
+ { label: 'Total Scans', value: this.formatNumber(this.stats.totalScans), color: COLORS.BLUE },
475
+ { label: 'Threats Blocked', value: this.formatNumber(this.stats.threatsBlocked), color: COLORS.RED },
476
+ { label: 'Files Accessed', value: this.formatNumber(this.stats.filesAccessed), color: COLORS.YELLOW },
477
+ { label: 'Network Calls', value: this.formatNumber(this.stats.networkCalls), color: COLORS.MAGENTA }
478
+ ];
479
+
480
+ const statLines = this.createStatsGrid(stats);
481
+ return statLines.join('\n');
482
+ }
483
+
484
+ /**
485
+ * Build threat map visualization
486
+ */
487
+ buildThreatMap() {
488
+ if (!this.options.showThreatMap) return '';
489
+
490
+ const title = `${COLORS.BOLD}Threat Detection Map${COLORS.RESET}`;
491
+ const recentThreats = this.stats.threatHistory.slice(0, 10);
492
+
493
+ const lines = [this.createSectionHeader(title)];
494
+
495
+ if (recentThreats.length === 0) {
496
+ lines.push(`${CHARS.BOX_VERTICAL} ${COLORS.GREEN}${CHARS.CHECK} No recent threats detected${COLORS.RESET}`);
497
+ } else {
498
+ recentThreats.forEach((threat, i) => {
499
+ const age = this.formatAge(Date.now() - threat.timestamp);
500
+ const severity = this.getSeverityIndicator(threat.severity);
501
+ const line = `${CHARS.BOX_VERTICAL} ${severity} ${threat.type}/${threat.subtype} ${COLORS.DIM}(${age})${COLORS.RESET}`;
502
+ lines.push(line);
503
+ });
504
+ }
505
+
506
+ return lines.join('\n');
507
+ }
508
+
509
+ /**
510
+ * Build activity feed
511
+ */
512
+ buildActivityFeed() {
513
+ const title = `${COLORS.BOLD}Recent Activity${COLORS.RESET}`;
514
+ const lines = [this.createSectionHeader(title)];
515
+
516
+ const recentActivity = [
517
+ ...this.stats.fileAccess.slice(0, 3).map(f => ({ ...f, activityType: 'file' })),
518
+ ...this.stats.networkActivity.slice(0, 3).map(n => ({ ...n, activityType: 'network' })),
519
+ ...this.stats.threatHistory.slice(0, 2).map(t => ({ ...t, activityType: 'threat' }))
520
+ ]
521
+ .sort((a, b) => b.timestamp - a.timestamp)
522
+ .slice(0, 6);
523
+
524
+ if (recentActivity.length === 0) {
525
+ lines.push(`${CHARS.BOX_VERTICAL} ${COLORS.DIM}No recent activity${COLORS.RESET}`);
526
+ } else {
527
+ recentActivity.forEach(activity => {
528
+ const age = this.formatAge(Date.now() - activity.timestamp);
529
+ let line = `${CHARS.BOX_VERTICAL} `;
530
+
531
+ switch (activity.activityType) {
532
+ case 'file':
533
+ line += `${COLORS.BLUE}📁${COLORS.RESET} ${activity.filename} ${COLORS.DIM}(${age})${COLORS.RESET}`;
534
+ break;
535
+ case 'network':
536
+ line += `${COLORS.MAGENTA}🌐${COLORS.RESET} ${activity.url} ${COLORS.DIM}(${age})${COLORS.RESET}`;
537
+ break;
538
+ case 'threat':
539
+ line += `${COLORS.RED}${CHARS.BLOCKED}${COLORS.RESET} ${activity.type} blocked ${COLORS.DIM}(${age})${COLORS.RESET}`;
540
+ break;
541
+ }
542
+
543
+ lines.push(line);
544
+ });
545
+ }
546
+
547
+ return lines.join('\n');
548
+ }
549
+
550
+ /**
551
+ * Build network graph
552
+ */
553
+ buildNetworkGraph() {
554
+ if (!this.options.showNetworkGraph) return '';
555
+
556
+ const title = `${COLORS.BOLD}Network Activity Graph${COLORS.RESET}`;
557
+ const lines = [this.createSectionHeader(title)];
558
+
559
+ // Create a simple bar chart of recent network activity
560
+ const buckets = this.createTimeBuckets(this.stats.networkActivity, 10);
561
+ const maxCount = Math.max(...buckets.map(b => b.count), 1);
562
+
563
+ buckets.forEach(bucket => {
564
+ const barLength = Math.floor((bucket.count / maxCount) * 40);
565
+ const bar = CHARS.BLOCK_FULL.repeat(barLength);
566
+ const line = `${CHARS.BOX_VERTICAL} ${bucket.label} ${COLORS.GREEN}${bar}${COLORS.RESET} ${bucket.count}`;
567
+ lines.push(line);
568
+ });
569
+
570
+ return lines.join('\n');
571
+ }
572
+
573
+ /**
574
+ * Build footer
575
+ */
576
+ buildFooter() {
577
+ const rateInfo = `Scan Rate: ${this.stats.scanRate.toFixed(1)}/s | Threat Rate: ${this.stats.threatRate.toFixed(2)}/s`;
578
+ const controlInfo = "Press 'q' to quit";
579
+
580
+ const footerLine = this.createBoxLine(
581
+ `${COLORS.DIM}${rateInfo}${COLORS.RESET}`,
582
+ `${COLORS.DIM}${controlInfo}${COLORS.RESET}`,
583
+ this.terminalWidth
584
+ );
585
+
586
+ return `${this.createHorizontalLine('middle')}\n${footerLine}\n${this.createHorizontalLine('bottom')}`;
587
+ }
588
+
589
+ /**
590
+ * Utility functions for display formatting
591
+ */
592
+
593
+ formatUptime(seconds) {
594
+ const hours = Math.floor(seconds / 3600);
595
+ const minutes = Math.floor((seconds % 3600) / 60);
596
+ const secs = seconds % 60;
597
+
598
+ if (hours > 0) {
599
+ return `${hours}h ${minutes}m ${secs}s`;
600
+ } else if (minutes > 0) {
601
+ return `${minutes}m ${secs}s`;
602
+ } else {
603
+ return `${secs}s`;
604
+ }
605
+ }
606
+
607
+ formatNumber(num) {
608
+ if (num >= 1000000) {
609
+ return `${(num / 1000000).toFixed(1)}M`;
610
+ } else if (num >= 1000) {
611
+ return `${(num / 1000).toFixed(1)}K`;
612
+ }
613
+ return num.toString();
614
+ }
615
+
616
+ formatAge(milliseconds) {
617
+ const seconds = Math.floor(milliseconds / 1000);
618
+ if (seconds < 60) return `${seconds}s ago`;
619
+ const minutes = Math.floor(seconds / 60);
620
+ if (minutes < 60) return `${minutes}m ago`;
621
+ const hours = Math.floor(minutes / 60);
622
+ return `${hours}h ago`;
623
+ }
624
+
625
+ getSeverityIndicator(severity) {
626
+ switch (severity) {
627
+ case 'critical': return `${COLORS.BG_RED}${COLORS.WHITE} CRIT ${COLORS.RESET}`;
628
+ case 'high': return `${COLORS.RED}${CHARS.WARNING}${COLORS.RESET}`;
629
+ case 'medium': return `${COLORS.YELLOW}${CHARS.WARNING}${COLORS.RESET}`;
630
+ case 'low': return `${COLORS.BLUE}ℹ${COLORS.RESET}`;
631
+ default: return `${COLORS.DIM}?${COLORS.RESET}`;
632
+ }
633
+ }
634
+
635
+ createHorizontalLine(type) {
636
+ let left, middle, right, fill;
637
+
638
+ switch (type) {
639
+ case 'top':
640
+ left = CHARS.BOX_TOP_LEFT;
641
+ right = CHARS.BOX_TOP_RIGHT;
642
+ fill = CHARS.BOX_HORIZONTAL;
643
+ break;
644
+ case 'bottom':
645
+ left = CHARS.BOX_BOTTOM_LEFT;
646
+ right = CHARS.BOX_BOTTOM_RIGHT;
647
+ fill = CHARS.BOX_HORIZONTAL;
648
+ break;
649
+ case 'middle':
650
+ left = CHARS.BOX_T_RIGHT;
651
+ right = CHARS.BOX_T_LEFT;
652
+ fill = CHARS.BOX_HORIZONTAL;
653
+ break;
654
+ default:
655
+ return '';
656
+ }
657
+
658
+ return left + fill.repeat(this.terminalWidth - 2) + right;
659
+ }
660
+
661
+ createBoxLine(leftText, rightText, width) {
662
+ const leftClean = this.stripAnsi(leftText);
663
+ const rightClean = this.stripAnsi(rightText);
664
+ const padding = width - leftClean.length - rightClean.length - 2;
665
+
666
+ return `${CHARS.BOX_VERTICAL}${leftText}${' '.repeat(Math.max(0, padding))}${rightText}${CHARS.BOX_VERTICAL}`;
667
+ }
668
+
669
+ createSectionHeader(title) {
670
+ const padding = Math.max(0, this.terminalWidth - this.stripAnsi(title).length - 4);
671
+ return `${CHARS.BOX_T_RIGHT}${CHARS.BOX_HORIZONTAL} ${title} ${CHARS.BOX_HORIZONTAL.repeat(padding)}${CHARS.BOX_T_LEFT}`;
672
+ }
673
+
674
+ createStatsGrid(stats) {
675
+ const lines = [];
676
+ const itemsPerRow = Math.min(5, Math.floor(this.terminalWidth / 20));
677
+
678
+ for (let i = 0; i < stats.length; i += itemsPerRow) {
679
+ const rowStats = stats.slice(i, i + itemsPerRow);
680
+ const statTexts = rowStats.map(stat =>
681
+ `${stat.color}${stat.value}${COLORS.RESET} ${stat.label}`
682
+ );
683
+
684
+ const line = `${CHARS.BOX_VERTICAL} ${statTexts.join(' | ')}`;
685
+ lines.push(line);
686
+ }
687
+
688
+ return lines;
689
+ }
690
+
691
+ createTimeBuckets(activities, bucketCount) {
692
+ const now = Date.now();
693
+ const bucketSize = 60000; // 1 minute buckets
694
+ const buckets = [];
695
+
696
+ for (let i = bucketCount - 1; i >= 0; i--) {
697
+ const bucketStart = now - (i + 1) * bucketSize;
698
+ const bucketEnd = now - i * bucketSize;
699
+
700
+ const count = activities.filter(a =>
701
+ a.timestamp >= bucketStart && a.timestamp < bucketEnd
702
+ ).length;
703
+
704
+ buckets.push({
705
+ label: `${i + 1}m`,
706
+ count,
707
+ start: bucketStart,
708
+ end: bucketEnd
709
+ });
710
+ }
711
+
712
+ return buckets;
713
+ }
714
+
715
+ stripAnsi(text) {
716
+ return text.replace(/\x1b\[[0-9;]*m/g, '');
717
+ }
718
+ }
719
+
720
+ module.exports = { LiveMonitor };