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
package/bin/clawmoat.js CHANGED
@@ -7,6 +7,7 @@
7
7
  * clawmoat scan <text> Scan text for threats
8
8
  * clawmoat scan --file <path> Scan file contents
9
9
  * clawmoat audit <session-dir> Audit OpenClaw session logs
10
+ * clawmoat providers [cmd] Configure AI provider connections (Claude/ChatGPT/Kimi)
10
11
  * clawmoat test Run built-in test suite against detection engines
11
12
  * clawmoat version Show version
12
13
  */
@@ -21,6 +22,8 @@ const { NetworkEgressLogger } = require('../src/guardian/network-log');
21
22
  const { AlertManager } = require('../src/guardian/alerts');
22
23
  const { CredentialMonitor, CVEVerifier } = require('../src/guardian/index');
23
24
  const { InsiderThreatDetector } = require('../src/guardian/insider-threat');
25
+ const { formatReport, formatScanResult, formatAuditResult } = require('../src/formatters/json');
26
+ const { formatScanResultAsSarif, formatAuditResultAsSarif } = require('../src/formatters/sarif');
24
27
 
25
28
  const VERSION = require('../package.json').version;
26
29
  const BOLD = '\x1b[1m';
@@ -58,6 +61,12 @@ switch (command) {
58
61
  case 'verify-cve':
59
62
  cmdVerifyCve(args.slice(1));
60
63
  break;
64
+ case 'init':
65
+ cmdInit(args.slice(1));
66
+ break;
67
+ case 'ci':
68
+ cmdCI(args.slice(1));
69
+ break;
61
70
  case 'test':
62
71
  cmdTest();
63
72
  break;
@@ -68,6 +77,13 @@ switch (command) {
68
77
  case 'pro':
69
78
  printUpgrade();
70
79
  break;
80
+ case 'providers':
81
+ case 'provider':
82
+ cmdProviders(args.slice(1));
83
+ break;
84
+ case 'scan-mcp':
85
+ cmdScanMCP(args.slice(1));
86
+ break;
71
87
  case 'version':
72
88
  case '--version':
73
89
  case '-v':
@@ -81,6 +97,23 @@ switch (command) {
81
97
  break;
82
98
  }
83
99
 
100
+ async function cmdProviders(args) {
101
+ const { cmdSetup, cmdList, cmdTest, cmdOpenClaw } = require('../agent/provider-setup');
102
+ const sub = args[0] || 'setup';
103
+ switch (sub) {
104
+ case 'setup': return cmdSetup();
105
+ case 'list': return cmdList();
106
+ case 'test': return cmdTest();
107
+ case 'openclaw': return cmdOpenClaw();
108
+ default:
109
+ console.log('Usage: clawmoat providers [setup|list|test|openclaw]');
110
+ console.log(' setup Configure AI providers (Claude, ChatGPT, Kimi)');
111
+ console.log(' list Show configured providers');
112
+ console.log(' test Test all connections');
113
+ console.log(' openclaw Generate OpenClaw config snippet');
114
+ }
115
+ }
116
+
84
117
  async function cmdVerifyCve(args) {
85
118
  const cveId = args[0];
86
119
  const suspiciousUrl = args[1] || null;
@@ -161,14 +194,38 @@ function colorSeverity(severity) {
161
194
  }
162
195
 
163
196
  function cmdScan(args) {
197
+ // Parse arguments
164
198
  let text;
199
+ let sourceFile = 'stdin';
200
+ let format = 'text';
201
+
202
+ // Extract format flag
203
+ const formatIndex = args.indexOf('--format');
204
+ if (formatIndex >= 0 && formatIndex + 1 < args.length) {
205
+ format = args[formatIndex + 1];
206
+ args = args.filter((arg, i) => arg !== '--format' && i !== formatIndex + 1);
207
+ }
208
+
209
+ if (!['text', 'json', 'sarif'].includes(format)) {
210
+ console.error(`${RED}Error: Invalid format "${format}". Supported: text, json, sarif${RESET}`);
211
+ process.exit(1);
212
+ }
165
213
 
166
214
  if (args[0] === '--file' && args[1]) {
167
215
  try {
168
- text = fs.readFileSync(args[1], 'utf8');
169
- console.log(`${DIM}Scanning file: ${args[1]} (${text.length} chars)${RESET}\n`);
216
+ sourceFile = args[1];
217
+ text = fs.readFileSync(sourceFile, 'utf8');
218
+ if (format === 'text') {
219
+ console.log(`${DIM}Scanning file: ${sourceFile} (${text.length} chars)${RESET}\n`);
220
+ }
170
221
  } catch (err) {
171
- console.error(`Error reading file: ${err.message}`);
222
+ if (format === 'json') {
223
+ console.log(JSON.stringify({ error: `Error reading file: ${err.message}` }));
224
+ } else if (format === 'sarif') {
225
+ console.log(JSON.stringify({ error: `Error reading file: ${err.message}` }));
226
+ } else {
227
+ console.error(`Error reading file: ${err.message}`);
228
+ }
172
229
  process.exit(1);
173
230
  }
174
231
  } else if (args.length > 0) {
@@ -179,59 +236,94 @@ function cmdScan(args) {
179
236
  }
180
237
 
181
238
  if (!text) {
182
- console.error('No text to scan. Usage: clawmoat scan "text to scan"');
239
+ const errorMsg = 'No text to scan. Usage: clawmoat scan "text to scan"';
240
+ if (format === 'json') {
241
+ console.log(JSON.stringify({ error: errorMsg }));
242
+ } else if (format === 'sarif') {
243
+ console.log(JSON.stringify({ error: errorMsg }));
244
+ } else {
245
+ console.error(errorMsg);
246
+ }
183
247
  process.exit(1);
184
248
  }
185
249
 
186
250
  const result = moat.scan(text, { context: 'cli' });
187
251
 
188
- console.log(`${BOLD}🏰 ClawMoat Scan Results${RESET}\n`);
252
+ // Output based on format
253
+ if (format === 'json') {
254
+ const jsonResult = formatScanResult(result);
255
+ console.log(JSON.stringify(jsonResult, null, 2));
256
+ } else if (format === 'sarif') {
257
+ const sarifResult = formatScanResultAsSarif(result, sourceFile);
258
+ console.log(JSON.stringify(sarifResult, null, 2));
259
+ } else {
260
+ // Original text output
261
+ console.log(`${BOLD}🏰 ClawMoat Scan Results${RESET}\n`);
189
262
 
190
- if (result.safe) {
191
- console.log(`${GREEN}✅ CLEAN${RESET} — No threats detected\n`);
192
- process.exit(0);
193
- }
263
+ if (result.safe) {
264
+ console.log(`${GREEN}✅ CLEAN${RESET} — No threats detected\n`);
265
+ process.exit(0);
266
+ }
194
267
 
195
- const icon = { critical: '🚨', high: '⚠️', medium: '⚡', low: 'ℹ️' };
196
- const color = { critical: RED, high: RED, medium: YELLOW, low: CYAN };
268
+ const icon = { critical: '🚨', high: '⚠️', medium: '⚡', low: 'ℹ️' };
269
+ const color = { critical: RED, high: RED, medium: YELLOW, low: CYAN };
197
270
 
198
- for (const finding of result.findings) {
199
- const sev = finding.severity || 'medium';
200
- console.log(
201
- `${icon[sev] || '•'} ${color[sev] || ''}${sev.toUpperCase()}${RESET} ` +
202
- `${BOLD}${finding.type}${RESET}` +
203
- (finding.subtype ? ` (${finding.subtype})` : '') +
204
- (finding.matched ? `\n ${DIM}Matched: "${finding.matched}"${RESET}` : '') +
205
- (finding.reason ? `\n ${DIM}${finding.reason}${RESET}` : '')
206
- );
207
- console.log();
208
- }
271
+ for (const finding of result.findings) {
272
+ const sev = finding.severity || 'medium';
273
+ console.log(
274
+ `${icon[sev] || '•'} ${color[sev] || ''}${sev.toUpperCase()}${RESET} ` +
275
+ `${BOLD}${finding.type}${RESET}` +
276
+ (finding.subtype ? ` (${finding.subtype})` : '') +
277
+ (finding.matched ? `\n ${DIM}Matched: "${finding.matched}"${RESET}` : '') +
278
+ (finding.reason ? `\n ${DIM}${finding.reason}${RESET}` : '')
279
+ );
280
+ console.log();
281
+ }
209
282
 
210
- console.log(`${DIM}Total findings: ${result.findings.length}${RESET}`);
283
+ console.log(`${DIM}Total findings: ${result.findings.length}${RESET}`);
211
284
 
212
- if (!getLicense()) {
213
- console.log(`\n${DIM}💡 Upgrade to Pro for real-time alerts, dashboard & threat intel → clawmoat upgrade${RESET}`);
285
+ if (!getLicense()) {
286
+ console.log(`\n${DIM}💡 Upgrade to Pro for real-time alerts, dashboard & threat intel → clawmoat upgrade${RESET}`);
287
+ }
214
288
  }
215
289
 
216
290
  process.exit(result.findings.some(f => f.severity === 'critical') ? 2 : 1);
217
291
  }
218
292
 
219
293
  function cmdAudit(args) {
294
+ // Parse arguments
220
295
  const badgeFlag = args.includes('--badge');
221
- const filteredArgs = args.filter(a => a !== '--badge');
296
+ const formatIndex = args.indexOf('--format');
297
+ const format = formatIndex >= 0 ? args[formatIndex + 1] : 'text';
298
+ const filteredArgs = args.filter((arg, i) => arg !== '--badge' && arg !== '--format' && i !== formatIndex + 1);
222
299
  const sessionDir = filteredArgs[0] || path.join(process.env.HOME, '.openclaw/agents/main/sessions');
223
300
 
301
+ if (format !== 'text' && format !== 'json' && format !== 'sarif') {
302
+ console.error(`${RED}Error: Invalid format "${format}". Supported: text, json, sarif${RESET}`);
303
+ process.exit(1);
304
+ }
305
+
224
306
  if (!fs.existsSync(sessionDir)) {
225
- console.error(`Session directory not found: ${sessionDir}`);
307
+ if (format === 'json') {
308
+ console.log(JSON.stringify({ error: 'Session directory not found', directory: sessionDir }));
309
+ } else if (format === 'sarif') {
310
+ console.log(JSON.stringify({ error: 'Session directory not found', directory: sessionDir }));
311
+ } else {
312
+ console.error(`Session directory not found: ${sessionDir}`);
313
+ }
226
314
  process.exit(1);
227
315
  }
228
316
 
229
- console.log(`${BOLD}🏰 ClawMoat Session Audit${RESET}`);
230
- console.log(`${DIM}Directory: ${sessionDir}${RESET}\n`);
317
+ if (format === 'text') {
318
+ console.log(`${BOLD}🏰 ClawMoat Session Audit${RESET}`);
319
+ console.log(`${DIM}Directory: ${sessionDir}${RESET}\n`);
320
+ }
231
321
 
232
322
  const files = fs.readdirSync(sessionDir).filter(f => f.endsWith('.jsonl'));
233
323
  let totalFindings = 0;
234
324
  let filesScanned = 0;
325
+ const findingsByFile = {};
326
+ const allFindings = [];
235
327
 
236
328
  for (const file of files) {
237
329
  const filePath = path.join(sessionDir, file);
@@ -246,6 +338,14 @@ function cmdAudit(args) {
246
338
  const result = moat.scan(content, { context: 'session_log' });
247
339
  if (!result.safe) {
248
340
  fileFindings += result.findings.length;
341
+ for (const finding of result.findings) {
342
+ allFindings.push({
343
+ ...finding,
344
+ source: file,
345
+ timestamp: entry.timestamp || new Date().toISOString(),
346
+ entry_id: entry.id || null
347
+ });
348
+ }
249
349
  }
250
350
  }
251
351
 
@@ -258,6 +358,14 @@ function cmdAudit(args) {
258
358
  const evalResult = moat.evaluateTool(tc.name, tc.arguments || {});
259
359
  if (evalResult.decision !== 'allow') {
260
360
  fileFindings++;
361
+ allFindings.push({
362
+ type: 'tool_policy_violation',
363
+ severity: 'medium',
364
+ reason: `Tool ${tc.name} blocked by policy: ${evalResult.reason}`,
365
+ source: file,
366
+ timestamp: entry.timestamp || new Date().toISOString(),
367
+ entry_id: entry.id || null
368
+ });
261
369
  }
262
370
  }
263
371
  }
@@ -266,34 +374,56 @@ function cmdAudit(args) {
266
374
 
267
375
  filesScanned++;
268
376
  totalFindings += fileFindings;
377
+ findingsByFile[file] = fileFindings;
269
378
 
270
- if (fileFindings > 0) {
271
- console.log(`${RED}⚠ ${file}${RESET}: ${fileFindings} finding(s)`);
272
- } else {
273
- console.log(`${GREEN} ${file}${RESET}: clean`);
379
+ if (format === 'text') {
380
+ if (fileFindings > 0) {
381
+ console.log(`${RED} ${file}${RESET}: ${fileFindings} finding(s)`);
382
+ } else {
383
+ console.log(`${GREEN}✓ ${file}${RESET}: clean`);
384
+ }
274
385
  }
275
386
  }
276
387
 
277
- console.log(`\n${BOLD}Summary:${RESET} ${filesScanned} sessions scanned, ${totalFindings} total findings`);
388
+ // Prepare audit data
389
+ const auditData = {
390
+ filesScanned,
391
+ totalFindings,
392
+ sessionDir,
393
+ findingsByFile,
394
+ findings: allFindings
395
+ };
396
+
397
+ // Output based on format
398
+ if (format === 'json') {
399
+ const jsonResult = formatAuditResult(auditData);
400
+ console.log(JSON.stringify(jsonResult, null, 2));
401
+ } else if (format === 'sarif') {
402
+ const sarifResult = formatAuditResultAsSarif(auditData);
403
+ console.log(JSON.stringify(sarifResult, null, 2));
404
+ } else {
405
+ // Original text output
406
+ console.log(`\n${BOLD}Summary:${RESET} ${filesScanned} sessions scanned, ${totalFindings} total findings`);
278
407
 
279
- const summary = moat.getSummary();
280
- if (summary.events.byType) {
281
- console.log(`${DIM}Breakdown: ${JSON.stringify(summary.events.byType)}${RESET}`);
282
- }
408
+ const summary = moat.getSummary();
409
+ if (summary.events.byType) {
410
+ console.log(`${DIM}Breakdown: ${JSON.stringify(summary.events.byType)}${RESET}`);
411
+ }
283
412
 
284
- // Badge generation
285
- if (badgeFlag) {
286
- const criticalFindings = 0; // TODO: track critical findings separately
287
- const grade = calculateGrade({ totalFindings, criticalFindings, filesScanned });
288
- const svg = generateBadgeSVG(grade);
289
- const badgePath = path.join(process.cwd(), 'clawmoat-badge.svg');
290
- fs.writeFileSync(badgePath, svg);
291
- console.log(`\n${BOLD}🏷️ Security Badge${RESET}`);
292
- console.log(` Grade: ${grade}`);
293
- console.log(` SVG saved: ${badgePath}`);
294
- console.log(` Shields.io: ${getShieldsURL(grade)}`);
295
- console.log(`\n ${DIM}Add to README:${RESET}`);
296
- console.log(` ![ClawMoat Security Score](${getShieldsURL(grade)})`);
413
+ // Badge generation
414
+ if (badgeFlag) {
415
+ const criticalFindings = allFindings.filter(f => f.severity === 'critical').length;
416
+ const grade = calculateGrade({ totalFindings, criticalFindings, filesScanned });
417
+ const svg = generateBadgeSVG(grade);
418
+ const badgePath = path.join(process.cwd(), 'clawmoat-badge.svg');
419
+ fs.writeFileSync(badgePath, svg);
420
+ console.log(`\n${BOLD}🏷️ Security Badge${RESET}`);
421
+ console.log(` Grade: ${grade}`);
422
+ console.log(` SVG saved: ${badgePath}`);
423
+ console.log(` Shields.io: ${getShieldsURL(grade)}`);
424
+ console.log(`\n ${DIM}Add to README:${RESET}`);
425
+ console.log(` ![ClawMoat Security Score](${getShieldsURL(grade)})`);
426
+ }
297
427
  }
298
428
 
299
429
  process.exit(totalFindings > 0 ? 1 : 0);
@@ -446,13 +576,13 @@ function cmdTest() {
446
576
  process.exit(failed > 0 ? 1 : 0);
447
577
  }
448
578
 
449
- function cmdWatch(args) {
579
+ async function cmdWatch(args) {
450
580
  const isDaemon = args.includes('--daemon');
451
581
  const webhookArg = args.find(a => a.startsWith('--alert-webhook='));
452
582
  const webhookUrl = webhookArg ? webhookArg.split('=').slice(1).join('=') : null;
453
583
  const filteredArgs = args.filter(a => a !== '--daemon' && !a.startsWith('--alert-webhook='));
454
- const agentDir = filteredArgs[0] || path.join(process.env.HOME, '.openclaw/agents/main');
455
- const { watchSessions } = require('../src/middleware/openclaw');
584
+ const watchDir = filteredArgs[0] || path.join(process.env.HOME, '.openclaw');
585
+ const { LiveMonitor } = require('../src/watch/live-monitor');
456
586
 
457
587
  // Daemon mode: fork to background
458
588
  if (isDaemon) {
@@ -475,33 +605,45 @@ function cmdWatch(args) {
475
605
  if (webhookUrl) alertChannels.push('webhook');
476
606
  const alertMgr = new AlertManager({ channels: alertChannels, webhookUrl });
477
607
 
478
- console.log(`${BOLD}🏰 ClawMoat Live Monitor${RESET}`);
479
- console.log(`${DIM}Watching: ${agentDir}${RESET}`);
480
- if (webhookUrl) console.log(`${DIM}Webhook: ${webhookUrl}${RESET}`);
481
- console.log(`${DIM}Press Ctrl+C to stop${RESET}\n`);
608
+ // Create and start live monitor
609
+ const monitor = new LiveMonitor({
610
+ watchDir,
611
+ refreshRate: 1000,
612
+ showNetworkGraph: true,
613
+ showThreatMap: true,
614
+ maxHistoryItems: 100,
615
+ animateCharts: true
616
+ });
482
617
 
483
- const monitor = watchSessions({ agentDir });
484
- if (!monitor) process.exit(1);
618
+ // Connect alert manager to monitor events
619
+ monitor.on('threat-detected', (threat) => {
620
+ alertMgr.send({
621
+ type: 'threat',
622
+ severity: threat.severity,
623
+ message: `Threat detected: ${threat.type}/${threat.subtype}`
624
+ });
625
+ });
485
626
 
486
627
  // Also start credential monitor
487
- const credMon = new CredentialMonitor({ quiet: false, onAlert: (a) => alertMgr.send(a) });
628
+ const credMon = new CredentialMonitor({ quiet: true, onAlert: (a) => alertMgr.send(a) });
488
629
  credMon.start();
489
630
 
490
- // Print summary every 60s
491
- setInterval(() => {
492
- const summary = monitor.getSummary();
493
- if (summary.scanned > 0) {
494
- console.log(`${DIM}[ClawMoat] Stats: ${summary.scanned} scanned, ${summary.blocked} blocked, ${summary.warnings} warnings${RESET}`);
495
- }
496
- }, 60000);
497
-
631
+ // Handle shutdown gracefully
498
632
  process.on('SIGINT', () => {
499
633
  monitor.stop();
500
634
  credMon.stop();
501
- const summary = monitor.getSummary();
502
- console.log(`\n${BOLD}Session Summary:${RESET} ${summary.scanned} scanned, ${summary.blocked} blocked, ${summary.warnings} warnings`);
635
+ console.log(`\n${GREEN}ClawMoat Live Monitor stopped.${RESET}`);
636
+ process.exit(0);
637
+ });
638
+
639
+ process.on('SIGTERM', () => {
640
+ monitor.stop();
641
+ credMon.stop();
503
642
  process.exit(0);
504
643
  });
644
+
645
+ // Start the live monitor
646
+ await monitor.start();
505
647
  }
506
648
 
507
649
  function cmdSkillAudit(args) {
@@ -546,16 +688,27 @@ function cmdSkillAudit(args) {
546
688
  }
547
689
 
548
690
  function cmdReport(args) {
549
- const sessionsDir = args[0] || path.join(process.env.HOME, '.openclaw/agents/main/sessions');
550
-
551
- console.log(`${BOLD}🏰 ClawMoat Activity Report (Last 24h)${RESET}`);
552
- console.log(`${DIM}Sessions: ${sessionsDir}${RESET}\n`);
691
+ // Parse arguments
692
+ const formatIndex = args.indexOf('--format');
693
+ const format = formatIndex >= 0 ? args[formatIndex + 1] : 'text';
694
+ const otherArgs = args.filter((arg, i) => arg !== '--format' && i !== formatIndex + 1);
695
+ const sessionsDir = otherArgs[0] || path.join(process.env.HOME, '.openclaw/agents/main/sessions');
696
+
697
+ if (format !== 'text' && format !== 'json') {
698
+ console.error(`${RED}Error: Invalid format "${format}". Supported: text, json${RESET}`);
699
+ process.exit(1);
700
+ }
553
701
 
554
702
  if (!fs.existsSync(sessionsDir)) {
555
- console.log(`${YELLOW}Sessions directory not found${RESET}`);
703
+ if (format === 'json') {
704
+ console.log(JSON.stringify({ error: 'Sessions directory not found', directory: sessionsDir }));
705
+ } else {
706
+ console.log(`${YELLOW}Sessions directory not found: ${sessionsDir}${RESET}`);
707
+ }
556
708
  process.exit(0);
557
709
  }
558
710
 
711
+ // Collect all data
559
712
  const oneDayAgo = Date.now() - 86400000;
560
713
  const files = fs.readdirSync(sessionsDir).filter(f => f.endsWith('.jsonl'));
561
714
  let recentFiles = 0;
@@ -563,6 +716,7 @@ function cmdReport(args) {
563
716
  let toolCalls = 0;
564
717
  let threats = 0;
565
718
  const toolUsage = {};
719
+ const findings = [];
566
720
 
567
721
  for (const file of files) {
568
722
  const filePath = path.join(sessionsDir, file);
@@ -592,7 +746,17 @@ function cmdReport(args) {
592
746
  const text = extractContent(entry);
593
747
  if (text) {
594
748
  const result = moat.scan(text, { context: 'report' });
595
- if (!result.safe) threats++;
749
+ if (!result.safe) {
750
+ threats++;
751
+ for (const finding of result.findings) {
752
+ findings.push({
753
+ ...finding,
754
+ timestamp: entry.timestamp || new Date().toISOString(),
755
+ source: file,
756
+ entry_id: entry.id || null
757
+ });
758
+ }
759
+ }
596
760
  }
597
761
  } catch {}
598
762
  }
@@ -602,22 +766,6 @@ function cmdReport(args) {
602
766
  const netLogger = new NetworkEgressLogger();
603
767
  const netResult = netLogger.scanSessions(sessionsDir, { maxAge: 86400000 });
604
768
 
605
- console.log(`${BOLD}Activity:${RESET}`);
606
- console.log(` Sessions active: ${recentFiles}`);
607
- console.log(` Total entries: ${totalEntries}`);
608
- console.log(` Tool calls: ${toolCalls}`);
609
- console.log(` Threats detected: ${threats}`);
610
- console.log();
611
-
612
- if (Object.keys(toolUsage).length > 0) {
613
- console.log(`${BOLD}Tool Usage:${RESET}`);
614
- const sorted = Object.entries(toolUsage).sort((a, b) => b[1] - a[1]);
615
- for (const [tool, count] of sorted.slice(0, 15)) {
616
- console.log(` ${tool}: ${count}`);
617
- }
618
- console.log();
619
- }
620
-
621
769
  // Insider threat scan on recent sessions
622
770
  const insiderDetector = new InsiderThreatDetector();
623
771
  let insiderThreats = 0;
@@ -636,28 +784,68 @@ function cmdReport(args) {
636
784
  if (insiderResult.riskScore > insiderHighScore) insiderHighScore = insiderResult.riskScore;
637
785
  }
638
786
 
639
- console.log(`${BOLD}Insider Threats:${RESET}`);
640
- console.log(` Threats detected: ${insiderThreats}`);
641
- console.log(` Highest risk score: ${insiderHighScore}/100`);
642
- console.log();
787
+ // Prepare report data
788
+ const reportData = {
789
+ recentFiles,
790
+ totalEntries,
791
+ toolCalls,
792
+ threats,
793
+ toolUsage,
794
+ netResult,
795
+ insiderThreats,
796
+ insiderHighScore,
797
+ findings,
798
+ sessionDir: sessionsDir
799
+ };
800
+
801
+ // Output based on format
802
+ if (format === 'json') {
803
+ const jsonReport = formatReport(reportData);
804
+ console.log(JSON.stringify(jsonReport, null, 2));
805
+ } else {
806
+ // Original text output
807
+ console.log(`${BOLD}🏰 ClawMoat Activity Report (Last 24h)${RESET}`);
808
+ console.log(`${DIM}Sessions: ${sessionsDir}${RESET}\n`);
809
+
810
+ console.log(`${BOLD}Activity:${RESET}`);
811
+ console.log(` Sessions active: ${recentFiles}`);
812
+ console.log(` Total entries: ${totalEntries}`);
813
+ console.log(` Tool calls: ${toolCalls}`);
814
+ console.log(` Threats detected: ${threats}`);
815
+ console.log();
816
+
817
+ if (Object.keys(toolUsage).length > 0) {
818
+ console.log(`${BOLD}Tool Usage:${RESET}`);
819
+ const sorted = Object.entries(toolUsage).sort((a, b) => b[1] - a[1]);
820
+ for (const [tool, count] of sorted.slice(0, 15)) {
821
+ console.log(` ${tool}: ${count}`);
822
+ }
823
+ console.log();
824
+ }
825
+
826
+ console.log(`${BOLD}Insider Threats:${RESET}`);
827
+ console.log(` Threats detected: ${insiderThreats}`);
828
+ console.log(` Highest risk score: ${insiderHighScore}/100`);
829
+ console.log();
643
830
 
644
- console.log(`${BOLD}Network Egress:${RESET}`);
645
- console.log(` URLs contacted: ${netResult.totalUrls}`);
646
- console.log(` Unique domains: ${netResult.domains.length}`);
647
- console.log(` Flagged (not in allowlist): ${netResult.flagged.length}`);
648
- console.log(` Known-bad domains: ${netResult.badDomains.length}`);
831
+ console.log(`${BOLD}Network Egress:${RESET}`);
832
+ console.log(` URLs contacted: ${netResult.totalUrls}`);
833
+ console.log(` Unique domains: ${netResult.domains.length}`);
834
+ console.log(` Flagged (not in allowlist): ${netResult.flagged.length}`);
835
+ console.log(` Known-bad domains: ${netResult.badDomains.length}`);
649
836
 
650
- if (netResult.flagged.length > 0) {
651
- console.log(`\n ${YELLOW}Flagged domains:${RESET}`);
652
- for (const d of netResult.flagged.slice(0, 20)) {
653
- console.log(` • ${d}`);
837
+ if (netResult.flagged.length > 0) {
838
+ console.log(`\n ${YELLOW}Flagged domains:${RESET}`);
839
+ for (const d of netResult.flagged.slice(0, 20)) {
840
+ console.log(` • ${d}`);
841
+ }
654
842
  }
655
- }
656
843
 
657
- if (netResult.badDomains.length > 0) {
658
- console.log(`\n ${RED}Bad domains:${RESET}`);
659
- for (const b of netResult.badDomains) {
660
- console.log(` 🚨 ${b.domain} (in ${b.file})`);
844
+ if (netResult.badDomains.length > 0) {
845
+ console.log(`\n ${RED}Bad domains:${RESET}`);
846
+ for (const b of netResult.badDomains) {
847
+ console.log(` 🚨 ${b.domain} (in ${b.file})`);
848
+ }
661
849
  }
662
850
  }
663
851
 
@@ -837,6 +1025,160 @@ function cmdActivate(args) {
837
1025
  req.end();
838
1026
  }
839
1027
 
1028
+ function cmdScanMCP(args) {
1029
+ const { scanMCP, discoverMCPConfigs } = require('../src/mcp-scanner');
1030
+ const extraPaths = args.filter(a => !a.startsWith('-'));
1031
+ const verbose = args.includes('--verbose') || args.includes('-v');
1032
+ const jsonOut = args.includes('--json');
1033
+
1034
+ console.log('\n🏰 ClawMoat MCP Scanner\n');
1035
+
1036
+ // Run scan
1037
+ const report = scanMCP({ extraPaths, verbose });
1038
+
1039
+ if (jsonOut) {
1040
+ console.log(JSON.stringify(report, null, 2));
1041
+ return;
1042
+ }
1043
+
1044
+ // Discovery
1045
+ console.log(`📁 Configs discovered: ${report.configsFound.length}`);
1046
+ for (const c of report.configsFound) {
1047
+ console.log(` ✓ ${c.name}: ${c.path}`);
1048
+ }
1049
+
1050
+ if (report.configsFound.length === 0) {
1051
+ // Show what we looked for
1052
+ console.log('\n No MCP configs found. Searched:');
1053
+ const all = discoverMCPConfigs();
1054
+ for (const c of all.slice(0, 6)) {
1055
+ console.log(` · ${c.name}: ${c.path}`);
1056
+ }
1057
+ console.log(` ... and ${all.length - 6} more locations`);
1058
+ console.log('\n Tip: pass a config path directly: clawmoat scan-mcp ~/.cursor/mcp.json\n');
1059
+ return;
1060
+ }
1061
+
1062
+ // Servers
1063
+ console.log(`\n🔌 MCP servers found: ${report.servers.length}`);
1064
+ for (const s of report.servers) {
1065
+ console.log(` · ${s.name} (${s.config})`);
1066
+ }
1067
+
1068
+ // Findings
1069
+ if (report.findings.length === 0) {
1070
+ console.log('\n✅ No security issues found.\n');
1071
+ return;
1072
+ }
1073
+
1074
+ console.log(`\n⚠️ Findings: ${report.summary.total}`);
1075
+ if (report.summary.critical) console.log(` 🔴 ${report.summary.critical} CRITICAL`);
1076
+ if (report.summary.high) console.log(` 🟠 ${report.summary.high} HIGH`);
1077
+ if (report.summary.medium) console.log(` 🟡 ${report.summary.medium} MEDIUM`);
1078
+ if (report.summary.low) console.log(` 🔵 ${report.summary.low} LOW`);
1079
+
1080
+ console.log('');
1081
+ for (const f of report.findings) {
1082
+ const icon = f.severity === 'critical' ? '🔴' : f.severity === 'high' ? '🟠' : f.severity === 'medium' ? '🟡' : '🔵';
1083
+ console.log(`${icon} [${f.severity.toUpperCase()}] ${f.label}`);
1084
+ console.log(` Server: ${f.server} (${f.configName})`);
1085
+ console.log(` Fix: ${f.fix}`);
1086
+ console.log('');
1087
+ }
1088
+ }
1089
+
1090
+ function cmdInit(args) {
1091
+ const force = args.includes('--force') || args.includes('-f');
1092
+ const configPath = path.join(process.cwd(), 'clawmoat.yml');
1093
+ const templatePath = path.join(__dirname, '../src/templates/default-config.yml');
1094
+
1095
+ // Check if config already exists
1096
+ if (fs.existsSync(configPath) && !force) {
1097
+ console.log(`${YELLOW}⚠️ Configuration file already exists: ${configPath}${RESET}`);
1098
+ console.log(`${DIM}Use --force to overwrite${RESET}`);
1099
+ process.exit(1);
1100
+ }
1101
+
1102
+ // Read template
1103
+ let template;
1104
+ try {
1105
+ template = fs.readFileSync(templatePath, 'utf8');
1106
+ } catch (err) {
1107
+ console.error(`${RED}Error reading config template: ${err.message}${RESET}`);
1108
+ process.exit(1);
1109
+ }
1110
+
1111
+ // Write config file
1112
+ try {
1113
+ fs.writeFileSync(configPath, template);
1114
+ console.log(`${GREEN}✅ Created ${configPath}${RESET}`);
1115
+ console.log(`${DIM}Edit the file to customize your security policies.${RESET}`);
1116
+ console.log(`${DIM}Documentation: https://github.com/darfaz/clawmoat${RESET}`);
1117
+ } catch (err) {
1118
+ console.error(`${RED}Error writing config file: ${err.message}${RESET}`);
1119
+ process.exit(1);
1120
+ }
1121
+ }
1122
+
1123
+ function cmdCI(args) {
1124
+ const { scanRepo } = require('../src/ci-scanner');
1125
+ const jsonOut = args.includes('--json');
1126
+ const dir = args.find(a => !a.startsWith('-')) || '.';
1127
+ const failOn = (args.find(a => a.startsWith('--fail-on=')) || '--fail-on=high').split('=')[1];
1128
+
1129
+ if (!jsonOut) {
1130
+ console.log(`\n${BOLD}🏰 ClawMoat CI Scanner${RESET}`);
1131
+ console.log(`${DIM}Scanning: ${require('path').resolve(dir)}${RESET}\n`);
1132
+ }
1133
+
1134
+ const result = scanRepo({ rootDir: dir, failOn });
1135
+
1136
+ if (jsonOut) {
1137
+ console.log(JSON.stringify(result, null, 2));
1138
+ process.exit(result.passed ? 0 : 1);
1139
+ return;
1140
+ }
1141
+
1142
+ if (result.findings.length === 0) {
1143
+ console.log(`${GREEN}✅ No security issues found.${RESET}\n`);
1144
+ process.exit(0);
1145
+ return;
1146
+ }
1147
+
1148
+ // Group by type
1149
+ const byType = {};
1150
+ for (const f of result.findings) {
1151
+ if (!byType[f.type]) byType[f.type] = [];
1152
+ byType[f.type].push(f);
1153
+ }
1154
+
1155
+ for (const [type, findings] of Object.entries(byType)) {
1156
+ const label = type.replace(/_/g, ' ').replace(/\b\w/g, c => c.toUpperCase());
1157
+ console.log(`${BOLD}${label}${RESET}`);
1158
+ for (const f of findings) {
1159
+ const icon = f.severity === 'critical' ? `${RED}🔴` : f.severity === 'high' ? `${YELLOW}🟠` : '🟡';
1160
+ console.log(` ${icon} [${f.severity.toUpperCase()}] ${f.evidence}${RESET}`);
1161
+ console.log(`${DIM} File: ${f.file}${RESET}`);
1162
+ console.log(`${DIM} Fix: ${f.fix}${RESET}`);
1163
+ }
1164
+ console.log('');
1165
+ }
1166
+
1167
+ console.log(`${BOLD}Summary:${RESET} ${result.summary.total} issue(s) found`);
1168
+ if (result.summary.critical) console.log(` ${RED}${result.summary.critical} critical${RESET}`);
1169
+ if (result.summary.high) console.log(` ${YELLOW}${result.summary.high} high${RESET}`);
1170
+ if (result.summary.medium) console.log(` ${DIM}${result.summary.medium} medium${RESET}`);
1171
+ console.log('');
1172
+
1173
+ if (!result.passed) {
1174
+ console.log(`${RED}❌ CI check FAILED (found ${failOn}+ severity issues)${RESET}\n`);
1175
+ process.exit(1);
1176
+ } else {
1177
+ console.log(`${YELLOW}⚠️ Issues found but none at fail-on severity (${failOn})${RESET}\n`);
1178
+ process.exit(0);
1179
+ }
1180
+ }
1181
+
840
1182
  function getLicense() {
841
1183
  try {
842
1184
  const licPath = path.join(process.env.HOME, '.clawmoat', 'license.json');
@@ -852,30 +1194,45 @@ ${BOLD}🏰 ClawMoat v${VERSION}${RESET} — Security moat for AI agents
852
1194
  Plan: ${planLabel}
853
1195
 
854
1196
  ${BOLD}USAGE${RESET}
1197
+ clawmoat ci [dir] Scan repo for secrets, compromised deps, CI risks, MCP issues
1198
+ clawmoat ci --json Output JSON for CI/CD integration
1199
+ clawmoat ci --fail-on=critical Only fail on critical severity (default: high)
1200
+ clawmoat init Generate a starter config file (clawmoat.yml)
855
1201
  clawmoat scan <text> Scan text for threats
856
1202
  clawmoat scan --file <path> Scan file contents
1203
+ clawmoat scan --format sarif Output SARIF format for CI/CD integration
857
1204
  cat file.txt | clawmoat scan Scan from stdin
858
1205
  clawmoat audit [session-dir] Audit OpenClaw session logs
859
1206
  clawmoat audit --badge Audit + generate security score badge SVG
1207
+ clawmoat audit --format sarif Generate SARIF report for security platforms
860
1208
  clawmoat watch [agent-dir] Live monitor OpenClaw sessions
861
1209
  clawmoat watch --daemon Daemonize watch mode (background, PID file)
862
1210
  clawmoat watch --alert-webhook=URL Send alerts to webhook
863
1211
  clawmoat skill-audit [skills-dir] Verify skill file integrity & scan for suspicious patterns
864
1212
  clawmoat insider-scan [session-file] Scan sessions for insider threats (self-preservation, blackmail, deception)
865
1213
  clawmoat report [sessions-dir] 24-hour activity summary report
1214
+ clawmoat report --format json Generate JSON report for programmatic use
866
1215
  clawmoat verify-cve <CVE-ID> [url] Verify a CVE against GitHub Advisory DB
867
1216
  clawmoat test Run detection test suite
1217
+ clawmoat providers Configure AI providers (Claude/ChatGPT/Kimi)
1218
+ clawmoat providers list Show configured providers
1219
+ clawmoat providers test Test all provider connections
1220
+ clawmoat providers openclaw Generate OpenClaw config snippet
868
1221
  clawmoat activate <KEY> Activate a Pro/Team license key
869
1222
  clawmoat upgrade Show upgrade options & pricing
870
1223
  clawmoat version Show version
871
1224
 
872
1225
  ${BOLD}EXAMPLES${RESET}
1226
+ clawmoat init # Create clawmoat.yml config file
873
1227
  clawmoat scan "Ignore all previous instructions"
874
1228
  clawmoat scan --file suspicious-email.txt
1229
+ clawmoat scan --format sarif --file agent-input.txt # For GitHub Code Scanning
875
1230
  clawmoat audit ~/.openclaw/agents/main/sessions/
1231
+ clawmoat audit --format sarif # SARIF output for CI/CD
876
1232
  clawmoat watch --daemon --alert-webhook=https://hooks.example.com/alerts
877
1233
  clawmoat skill-audit ~/.openclaw/workspace/skills
878
1234
  clawmoat report
1235
+ clawmoat report --format json # For dashboards/automation
879
1236
  clawmoat test
880
1237
 
881
1238
  ${BOLD}CONFIG${RESET}