opena2a-cli 0.1.2 → 0.2.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 (63) hide show
  1. package/README.md +225 -1
  2. package/dist/commands/guard-hooks.d.ts +27 -0
  3. package/dist/commands/guard-hooks.d.ts.map +1 -0
  4. package/dist/commands/guard-hooks.js +207 -0
  5. package/dist/commands/guard-hooks.js.map +1 -0
  6. package/dist/commands/guard-policy.d.ts +54 -0
  7. package/dist/commands/guard-policy.d.ts.map +1 -0
  8. package/dist/commands/guard-policy.js +251 -0
  9. package/dist/commands/guard-policy.js.map +1 -0
  10. package/dist/commands/guard-signing.d.ts +52 -0
  11. package/dist/commands/guard-signing.d.ts.map +1 -0
  12. package/dist/commands/guard-signing.js +185 -0
  13. package/dist/commands/guard-signing.js.map +1 -0
  14. package/dist/commands/guard-snapshots.d.ts +54 -0
  15. package/dist/commands/guard-snapshots.d.ts.map +1 -0
  16. package/dist/commands/guard-snapshots.js +346 -0
  17. package/dist/commands/guard-snapshots.js.map +1 -0
  18. package/dist/commands/guard.d.ts +60 -4
  19. package/dist/commands/guard.d.ts.map +1 -1
  20. package/dist/commands/guard.js +475 -95
  21. package/dist/commands/guard.js.map +1 -1
  22. package/dist/commands/init.js +3 -4
  23. package/dist/commands/init.js.map +1 -1
  24. package/dist/commands/shield.d.ts +3 -0
  25. package/dist/commands/shield.d.ts.map +1 -1
  26. package/dist/commands/shield.js +458 -30
  27. package/dist/commands/shield.js.map +1 -1
  28. package/dist/index.js +15 -6
  29. package/dist/index.js.map +1 -1
  30. package/dist/router.d.ts.map +1 -1
  31. package/dist/router.js +1 -0
  32. package/dist/router.js.map +1 -1
  33. package/dist/shield/arp-bridge.d.ts +62 -0
  34. package/dist/shield/arp-bridge.d.ts.map +1 -0
  35. package/dist/shield/arp-bridge.js +198 -0
  36. package/dist/shield/arp-bridge.js.map +1 -0
  37. package/dist/shield/baselines.d.ts +58 -0
  38. package/dist/shield/baselines.d.ts.map +1 -0
  39. package/dist/shield/baselines.js +371 -0
  40. package/dist/shield/baselines.js.map +1 -0
  41. package/dist/shield/findings.d.ts +52 -0
  42. package/dist/shield/findings.d.ts.map +1 -0
  43. package/dist/shield/findings.js +336 -0
  44. package/dist/shield/findings.js.map +1 -0
  45. package/dist/shield/integrity.d.ts.map +1 -1
  46. package/dist/shield/integrity.js +6 -2
  47. package/dist/shield/integrity.js.map +1 -1
  48. package/dist/shield/report-html.d.ts +29 -0
  49. package/dist/shield/report-html.d.ts.map +1 -0
  50. package/dist/shield/report-html.js +596 -0
  51. package/dist/shield/report-html.js.map +1 -0
  52. package/dist/shield/sarif.d.ts +65 -0
  53. package/dist/shield/sarif.d.ts.map +1 -0
  54. package/dist/shield/sarif.js +108 -0
  55. package/dist/shield/sarif.js.map +1 -0
  56. package/dist/shield/status.d.ts.map +1 -1
  57. package/dist/shield/status.js +6 -6
  58. package/dist/shield/status.js.map +1 -1
  59. package/dist/shield/types.d.ts +19 -1
  60. package/dist/shield/types.d.ts.map +1 -1
  61. package/dist/shield/types.js +2 -1
  62. package/dist/shield/types.js.map +1 -1
  63. package/package.json +1 -1
@@ -12,12 +12,48 @@
12
12
  * - recover: Exit lockdown mode
13
13
  * - report: Generate a security posture report
14
14
  * - session: Show current AI coding assistant session identity
15
+ * - baseline: View adaptive enforcement baselines for agents
15
16
  * - suggest: LLM-powered policy suggestions from observed behavior
16
17
  * - explain: LLM-powered anomaly explanations for events
17
18
  * - triage: LLM-powered incident classification and response
18
19
  */
20
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
21
+ if (k2 === undefined) k2 = k;
22
+ var desc = Object.getOwnPropertyDescriptor(m, k);
23
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
24
+ desc = { enumerable: true, get: function() { return m[k]; } };
25
+ }
26
+ Object.defineProperty(o, k2, desc);
27
+ }) : (function(o, m, k, k2) {
28
+ if (k2 === undefined) k2 = k;
29
+ o[k2] = m[k];
30
+ }));
31
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
32
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
33
+ }) : function(o, v) {
34
+ o["default"] = v;
35
+ });
36
+ var __importStar = (this && this.__importStar) || (function () {
37
+ var ownKeys = function(o) {
38
+ ownKeys = Object.getOwnPropertyNames || function (o) {
39
+ var ar = [];
40
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
41
+ return ar;
42
+ };
43
+ return ownKeys(o);
44
+ };
45
+ return function (mod) {
46
+ if (mod && mod.__esModule) return mod;
47
+ var result = {};
48
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
49
+ __setModuleDefault(result, mod);
50
+ return result;
51
+ };
52
+ })();
19
53
  Object.defineProperty(exports, "__esModule", { value: true });
20
54
  exports.shield = shield;
55
+ const fs = __importStar(require("node:fs"));
56
+ const path = __importStar(require("node:path"));
21
57
  const colors_js_1 = require("../util/colors.js");
22
58
  // --- Core dispatcher ---
23
59
  async function shield(options) {
@@ -41,15 +77,19 @@ async function shield(options) {
41
77
  return handleReport(options);
42
78
  case 'session':
43
79
  return handleSession(options);
80
+ case 'baseline':
81
+ return handleBaseline(options);
44
82
  case 'suggest':
45
83
  return handleSuggest(options);
46
84
  case 'explain':
47
85
  return handleExplain(options);
48
86
  case 'triage':
49
87
  return handleTriage(options);
88
+ case 'monitor':
89
+ return handleMonitor(options);
50
90
  default:
51
91
  process.stderr.write((0, colors_js_1.red)(`Unknown subcommand: ${options.subcommand}\n`));
52
- process.stderr.write('Usage: opena2a shield <init|status|log|selfcheck|policy|evaluate|recover|report|session|suggest|explain|triage>\n');
92
+ process.stderr.write('Usage: opena2a shield <init|status|log|selfcheck|policy|evaluate|recover|report|monitor|session|baseline|suggest|explain|triage>\n');
53
93
  return 1;
54
94
  }
55
95
  }
@@ -164,6 +204,7 @@ async function handlePolicy(options) {
164
204
  }
165
205
  async function handleEvaluate(options) {
166
206
  const { loadPolicy, evaluatePolicy } = await import('../shield/policy.js');
207
+ const { writeEvent } = await import('../shield/events.js');
167
208
  const isJson = options.format === 'json';
168
209
  const policy = loadPolicy(options.dir);
169
210
  if (!policy) {
@@ -175,17 +216,68 @@ async function handleEvaluate(options) {
175
216
  }
176
217
  return 1;
177
218
  }
178
- const category = options.category ?? 'processes';
219
+ // Determine the command string from positional args or fall back to category-based evaluation.
220
+ const commandString = options.args && options.args.length > 0
221
+ ? options.args.join(' ')
222
+ : null;
179
223
  const agent = options.agent ?? null;
180
- const target = '';
224
+ let category;
225
+ let target;
226
+ if (commandString) {
227
+ // Shell hook mode: parse the command to extract the binary name (first word).
228
+ // Handle pipes, subshells, and quoted strings by taking the first token.
229
+ const trimmed = commandString.trim();
230
+ const firstWord = trimmed.split(/[\s|;&]/)[0] ?? trimmed;
231
+ category = 'processes';
232
+ target = firstWord;
233
+ }
234
+ else {
235
+ // Explicit category mode (used by direct API calls)
236
+ category = options.category ?? 'processes';
237
+ target = '';
238
+ }
181
239
  const decision = evaluatePolicy(policy, agent, category, target);
240
+ // Write enforcement events for non-allowed decisions
241
+ if (decision.outcome === 'blocked') {
242
+ writeEvent({
243
+ source: 'shield',
244
+ category: 'enforcement',
245
+ severity: 'high',
246
+ agent,
247
+ sessionId: null,
248
+ action: 'command.blocked',
249
+ target: commandString ?? target,
250
+ outcome: 'blocked',
251
+ detail: { rule: decision.rule, mode: policy.mode },
252
+ orgId: null,
253
+ managed: false,
254
+ agentId: null,
255
+ });
256
+ process.stderr.write(`[shield] blocked: ${commandString ?? target} (rule: ${decision.rule})\n`);
257
+ }
258
+ else if (decision.outcome === 'monitored') {
259
+ writeEvent({
260
+ source: 'shield',
261
+ category: 'enforcement',
262
+ severity: 'medium',
263
+ agent,
264
+ sessionId: null,
265
+ action: 'command.monitored',
266
+ target: commandString ?? target,
267
+ outcome: 'monitored',
268
+ detail: { rule: decision.rule, mode: policy.mode },
269
+ orgId: null,
270
+ managed: false,
271
+ agentId: null,
272
+ });
273
+ }
182
274
  if (isJson) {
183
275
  process.stdout.write(JSON.stringify(decision, null, 2) + '\n');
184
276
  }
185
- else {
277
+ else if (decision.outcome !== 'blocked') {
278
+ // Only print non-blocked outcomes to stdout (blocked warning goes to stderr above)
186
279
  const outcomeLabel = decision.outcome === 'allowed' ? (0, colors_js_1.green)('ALLOWED')
187
- : decision.outcome === 'blocked' ? (0, colors_js_1.red)('BLOCKED')
188
- : (0, colors_js_1.yellow)('MONITORED');
280
+ : (0, colors_js_1.yellow)('MONITORED');
189
281
  process.stdout.write(`${outcomeLabel} rule=${decision.rule}\n`);
190
282
  }
191
283
  return decision.outcome === 'blocked' ? 1 : 0;
@@ -272,6 +364,56 @@ async function handleReport(options) {
272
364
  .map(([name, count]) => ({ name, count }));
273
365
  const topAgents = topN(byAgent, 10);
274
366
  const topActions = topN(byAction, 10);
367
+ // --- Classify events into findings ---
368
+ const { classifyEvents, classifyViolation } = await import('../shield/findings.js');
369
+ const classifiedFindings = classifyEvents(events);
370
+ // --- SARIF output ---
371
+ if (options.format === 'sarif') {
372
+ const { toSarif } = await import('../shield/sarif.js');
373
+ const { getVersion } = await import('../util/version.js');
374
+ const sarif = toSarif(classifiedFindings, getVersion());
375
+ const sarifJson = JSON.stringify(sarif, null, 2);
376
+ if (options.report) {
377
+ const reportPath = path.resolve(options.report);
378
+ fs.writeFileSync(reportPath, sarifJson, 'utf-8');
379
+ process.stdout.write(`SARIF report written to ${reportPath}\n`);
380
+ }
381
+ else {
382
+ process.stdout.write(sarifJson + '\n');
383
+ }
384
+ return 0;
385
+ }
386
+ // --- HTML report output ---
387
+ if (options.report) {
388
+ const weeklyReport = await buildWeeklyReport(events, since, bySeverity, byOutcome, byAgent, topActions);
389
+ // Enrich violations with finding data
390
+ for (const v of weeklyReport.policyEvaluation.topViolations) {
391
+ const finding = classifyViolation(v);
392
+ if (finding) {
393
+ v.findingId = finding.id;
394
+ v.remediationCommand = finding.remediation;
395
+ v.compliance = [finding.owaspAgentic, finding.mitreAtlas];
396
+ }
397
+ }
398
+ // Compute trend from snapshot history
399
+ const trendData = await computeTrend(weeklyReport);
400
+ if (trendData) {
401
+ weeklyReport.posture.trend = trendData;
402
+ }
403
+ // Save snapshot for future trend comparisons
404
+ await saveReportSnapshot(weeklyReport, classifiedFindings);
405
+ let narrative = null;
406
+ if (options.analyze) {
407
+ const { generateNarrative } = await import('../shield/llm.js');
408
+ narrative = await generateNarrative(weeklyReport);
409
+ }
410
+ const { generateShieldHtmlReport } = await import('../shield/report-html.js');
411
+ const html = generateShieldHtmlReport(weeklyReport, narrative, classifiedFindings, trendData);
412
+ const reportPath = path.resolve(options.report);
413
+ fs.writeFileSync(reportPath, html, 'utf-8');
414
+ process.stdout.write(`Report written to ${reportPath}\n`);
415
+ return 0;
416
+ }
275
417
  if (isJson) {
276
418
  const data = {
277
419
  periodSince: since,
@@ -369,12 +511,80 @@ async function handleReport(options) {
369
511
  process.stdout.write((0, colors_js_1.gray)('-'.repeat(50)) + '\n');
370
512
  return 0;
371
513
  }
514
+ // --- Snapshot Persistence for Trend Analysis ---
515
+ async function saveReportSnapshot(report, findings) {
516
+ const { getShieldDir } = await import('../shield/events.js');
517
+ const { SHIELD_SNAPSHOTS_FILE } = await import('../shield/types.js');
518
+ const findingCounts = {};
519
+ for (const f of findings) {
520
+ const sev = f.finding.severity;
521
+ findingCounts[sev] = (findingCounts[sev] ?? 0) + f.count;
522
+ }
523
+ const snapshot = {
524
+ timestamp: report.generatedAt,
525
+ score: report.posture.score,
526
+ grade: report.posture.grade,
527
+ findingCounts,
528
+ totalFindings: findings.reduce((sum, f) => sum + f.count, 0),
529
+ };
530
+ const dir = getShieldDir();
531
+ const filePath = path.join(dir, SHIELD_SNAPSHOTS_FILE);
532
+ fs.appendFileSync(filePath, JSON.stringify(snapshot) + '\n', 'utf-8');
533
+ }
534
+ async function loadPreviousSnapshot() {
535
+ const { getShieldDir } = await import('../shield/events.js');
536
+ const { SHIELD_SNAPSHOTS_FILE } = await import('../shield/types.js');
537
+ const dir = getShieldDir();
538
+ const filePath = path.join(dir, SHIELD_SNAPSHOTS_FILE);
539
+ if (!fs.existsSync(filePath))
540
+ return null;
541
+ let content;
542
+ try {
543
+ content = fs.readFileSync(filePath, 'utf-8');
544
+ }
545
+ catch {
546
+ return null;
547
+ }
548
+ const lines = content.split('\n').filter(l => l.trim().length > 0);
549
+ if (lines.length === 0)
550
+ return null;
551
+ for (let i = lines.length - 1; i >= 0; i--) {
552
+ try {
553
+ return JSON.parse(lines[i]);
554
+ }
555
+ catch {
556
+ continue;
557
+ }
558
+ }
559
+ return null;
560
+ }
561
+ async function computeTrend(report) {
562
+ const previous = await loadPreviousSnapshot();
563
+ if (!previous)
564
+ return null;
565
+ const delta = report.posture.score - previous.score;
566
+ const periodMs = new Date(report.generatedAt).getTime() - new Date(previous.timestamp).getTime();
567
+ const periodDays = Math.max(1, Math.round(periodMs / (24 * 60 * 60 * 1000)));
568
+ let direction;
569
+ if (delta > 3)
570
+ direction = 'improving';
571
+ else if (delta < -3)
572
+ direction = 'declining';
573
+ else
574
+ direction = 'stable';
575
+ return {
576
+ previousScore: previous.score,
577
+ previousGrade: previous.grade,
578
+ delta,
579
+ direction,
580
+ periodDays,
581
+ };
582
+ }
372
583
  /**
373
- * Build a WeeklyReport from aggregated event data and call generateNarrative().
374
- * Returns the narrative or null if LLM is unavailable.
584
+ * Build a WeeklyReport from aggregated event data.
375
585
  */
376
- async function buildNarrative(events, since, bySeverity, byOutcome, byAgent, topActions) {
377
- const { generateNarrative } = await import('../shield/llm.js');
586
+ async function buildWeeklyReport(events, since, bySeverity, byOutcome, byAgent, topActions) {
587
+ const { getARPStats } = await import('../shield/arp-bridge.js');
378
588
  const { hostname } = await import('node:os');
379
589
  const now = new Date();
380
590
  const sinceMatch = since.match(/^(\d+)([dwm])$/);
@@ -484,17 +694,32 @@ async function buildNarrative(events, since, bySeverity, byOutcome, byAgent, top
484
694
  blockedInstalls += 1;
485
695
  }
486
696
  }
487
- const criticalCount = bySeverity['critical'] ?? 0;
488
- const highCount = bySeverity['high'] ?? 0;
489
- const mediumCount = bySeverity['medium'] ?? 0;
697
+ // Posture score: only count external threat events, not Shield's own diagnostic scans.
698
+ // Shield events (posture-assessment, credential-finding, shield.init) are informational.
699
+ const threatEvents = events.filter(e => e.source !== 'shield');
700
+ const threatSeverity = {};
701
+ for (const e of threatEvents) {
702
+ threatSeverity[e.severity] = (threatSeverity[e.severity] ?? 0) + 1;
703
+ }
704
+ const criticalCount = threatSeverity['critical'] ?? 0;
705
+ const highCount = threatSeverity['high'] ?? 0;
706
+ const mediumCount = threatSeverity['medium'] ?? 0;
490
707
  const blockedCount = byOutcome['blocked'] ?? 0;
491
- let score = 100;
492
- score -= criticalCount * 15;
493
- score -= highCount * 8;
494
- score -= mediumCount * 3;
495
- score += blockedCount * 2;
708
+ const agentCount = Object.keys(byAgent).length;
709
+ // Weighted factor scoring: severity (50%), enforcement (25%), coverage (25%).
710
+ // Severity uses capped penalties so a few events don't destroy the entire score.
711
+ const severityPenalty = Math.min(60, criticalCount * 12 + highCount * 5 + mediumCount * 2);
712
+ const severityScore = 100 - severityPenalty;
713
+ // Enforcement: actively blocking threats is a strong positive signal.
714
+ const enforcementScore = blockedCount > 0
715
+ ? Math.min(100, 60 + Math.min(40, blockedCount * 5))
716
+ : (threatEvents.length > 0 ? 30 : 50);
717
+ // Coverage: more agents monitored = better visibility.
718
+ const coverageScore = agentCount >= 3 ? 80 : agentCount > 0 ? 60 : 30;
719
+ let score = Math.round(severityScore * 0.5 + enforcementScore * 0.25 + coverageScore * 0.25);
496
720
  score = Math.max(0, Math.min(100, score));
497
721
  const grade = score >= 90 ? 'A' : score >= 80 ? 'B' : score >= 70 ? 'C' : score >= 60 ? 'D' : 'F';
722
+ const arpStats = getARPStats(since);
498
723
  const report = {
499
724
  version: 1,
500
725
  generatedAt: now.toISOString(),
@@ -524,31 +749,114 @@ async function buildNarrative(events, since, bySeverity, byOutcome, byAgent, top
524
749
  blockedInstalls,
525
750
  lowTrustPackages: [],
526
751
  },
527
- configIntegrity: {
528
- filesMonitored: 0,
529
- tamperedFiles: [],
530
- signatureStatus: 'unsigned',
531
- },
752
+ configIntegrity: await (async () => {
753
+ try {
754
+ const mod = await import('./guard.js');
755
+ const fn = mod.verifyConfigIntegrity ?? mod.default?.verifyConfigIntegrity;
756
+ if (fn)
757
+ return fn();
758
+ return { filesMonitored: 0, tamperedFiles: [], signatureStatus: 'unsigned' };
759
+ }
760
+ catch {
761
+ return { filesMonitored: 0, tamperedFiles: [], signatureStatus: 'unsigned' };
762
+ }
763
+ })(),
532
764
  runtimeProtection: {
533
- arpActive: false,
534
- processesSpawned: 0,
535
- networkConnections: 0,
536
- anomalies: 0,
765
+ arpActive: arpStats.totalEvents > 0,
766
+ processesSpawned: arpStats.processEvents,
767
+ networkConnections: arpStats.networkEvents,
768
+ anomalies: arpStats.anomalies + arpStats.violations + arpStats.threats,
537
769
  },
538
770
  posture: {
539
771
  score,
540
772
  grade,
541
773
  factors: [
542
- { name: 'severity', score: Math.max(0, 100 - criticalCount * 15 - highCount * 8), weight: 0.4, detail: `${criticalCount} critical, ${highCount} high` },
543
- { name: 'enforcement', score: blockedCount > 0 ? 80 : 50, weight: 0.3, detail: `${blockedCount} blocked` },
544
- { name: 'coverage', score: Object.keys(byAgent).length > 0 ? 70 : 30, weight: 0.3, detail: `${Object.keys(byAgent).length} agents monitored` },
774
+ { name: 'severity', score: severityScore, weight: 0.5, detail: `${criticalCount} critical, ${highCount} high, ${mediumCount} medium` },
775
+ { name: 'enforcement', score: enforcementScore, weight: 0.25, detail: `${blockedCount} blocked` },
776
+ { name: 'coverage', score: coverageScore, weight: 0.25, detail: `${agentCount} agents monitored` },
545
777
  ],
546
778
  trend: null,
547
779
  comparative: null,
548
780
  },
549
781
  };
782
+ return report;
783
+ }
784
+ /**
785
+ * Build a WeeklyReport and call generateNarrative().
786
+ * Returns the narrative or null if LLM is unavailable.
787
+ */
788
+ async function buildNarrative(events, since, bySeverity, byOutcome, byAgent, topActions) {
789
+ const { generateNarrative } = await import('../shield/llm.js');
790
+ const report = await buildWeeklyReport(events, since, bySeverity, byOutcome, byAgent, topActions);
550
791
  return generateNarrative(report);
551
792
  }
793
+ // --- Monitor ---
794
+ async function handleMonitor(options) {
795
+ const { importARPEvents, getARPStats } = await import('../shield/arp-bridge.js');
796
+ const isJson = options.format === 'json';
797
+ const targetDir = options.dir ? path.resolve(options.dir) : process.cwd();
798
+ // Step 1: Import any existing ARP events into Shield's hash chain
799
+ const result = importARPEvents(targetDir, options.agent);
800
+ // Step 2: Get ARP stats from Shield's unified log
801
+ const stats = getARPStats(options.since ?? '7d');
802
+ if (isJson) {
803
+ process.stdout.write(JSON.stringify({
804
+ import: result,
805
+ stats,
806
+ }, null, 2) + '\n');
807
+ return 0;
808
+ }
809
+ process.stdout.write((0, colors_js_1.bold)('Shield ARP Monitor') + '\n');
810
+ process.stdout.write((0, colors_js_1.gray)('-'.repeat(50)) + '\n');
811
+ // Import results
812
+ if (result.total > 0) {
813
+ process.stdout.write((0, colors_js_1.bold)(' Event Import') + '\n');
814
+ if (result.imported > 0) {
815
+ process.stdout.write(` ${(0, colors_js_1.green)(`${result.imported} new events`)} imported into Shield log\n`);
816
+ }
817
+ if (result.skipped > 0) {
818
+ process.stdout.write(` ${(0, colors_js_1.dim)(`${result.skipped} already imported`)}\n`);
819
+ }
820
+ if (result.errors > 0) {
821
+ process.stdout.write(` ${(0, colors_js_1.yellow)(`${result.errors} parse errors`)}\n`);
822
+ }
823
+ process.stdout.write('\n');
824
+ }
825
+ else {
826
+ process.stdout.write((0, colors_js_1.dim)(' No ARP events found.') + '\n');
827
+ process.stdout.write((0, colors_js_1.dim)(' Start ARP monitoring: opena2a runtime start') + '\n\n');
828
+ }
829
+ // ARP stats from Shield's unified log
830
+ if (stats.totalEvents > 0) {
831
+ process.stdout.write((0, colors_js_1.bold)(' Runtime Protection Summary') + '\n');
832
+ process.stdout.write(` ${(0, colors_js_1.dim)('Total events')} ${stats.totalEvents}\n`);
833
+ process.stdout.write(` ${(0, colors_js_1.dim)('Process events')} ${stats.processEvents}\n`);
834
+ process.stdout.write(` ${(0, colors_js_1.dim)('Network events')} ${stats.networkEvents}\n`);
835
+ process.stdout.write(` ${(0, colors_js_1.dim)('Filesystem events')} ${stats.filesystemEvents}\n`);
836
+ process.stdout.write(` ${(0, colors_js_1.dim)('Prompt events')} ${stats.promptEvents}\n`);
837
+ if (stats.anomalies > 0 || stats.violations > 0 || stats.threats > 0) {
838
+ process.stdout.write('\n');
839
+ process.stdout.write((0, colors_js_1.bold)(' Detections') + '\n');
840
+ if (stats.anomalies > 0) {
841
+ process.stdout.write(` ${(0, colors_js_1.yellow)(`${stats.anomalies} anomalies`)}\n`);
842
+ }
843
+ if (stats.violations > 0) {
844
+ process.stdout.write(` ${(0, colors_js_1.red)(`${stats.violations} violations`)}\n`);
845
+ }
846
+ if (stats.threats > 0) {
847
+ process.stdout.write(` ${(0, colors_js_1.bold)((0, colors_js_1.red)(`${stats.threats} threats`))}\n`);
848
+ }
849
+ if (stats.enforcements > 0) {
850
+ process.stdout.write(` ${(0, colors_js_1.cyan)(`${stats.enforcements} enforcements`)}\n`);
851
+ }
852
+ }
853
+ else {
854
+ process.stdout.write(` ${(0, colors_js_1.green)('No anomalies or threats detected')}\n`);
855
+ }
856
+ }
857
+ process.stdout.write((0, colors_js_1.gray)('-'.repeat(50)) + '\n');
858
+ return 0;
859
+ }
552
860
  // --- Session ---
553
861
  async function handleSession(options) {
554
862
  const { identifySession, collectSignals } = await import('../shield/session.js');
@@ -589,6 +897,126 @@ async function handleSession(options) {
589
897
  process.stdout.write((0, colors_js_1.gray)('-'.repeat(40)) + '\n');
590
898
  return 0;
591
899
  }
900
+ // --- Baseline ---
901
+ async function handleBaseline(options) {
902
+ const { listBaselines, getBaseline, computeStability, checkPhaseTransition } = await import('../shield/baselines.js');
903
+ const isJson = options.format === 'json';
904
+ if (options.agent) {
905
+ // Detailed view for a single agent
906
+ const baseline = getBaseline(options.agent);
907
+ const stability = computeStability(baseline);
908
+ const transition = checkPhaseTransition(baseline);
909
+ if (isJson) {
910
+ process.stdout.write(JSON.stringify({
911
+ ...baseline,
912
+ stabilityScore: stability,
913
+ transition,
914
+ }, null, 2) + '\n');
915
+ return 0;
916
+ }
917
+ process.stdout.write((0, colors_js_1.bold)('Agent Baseline') + '\n');
918
+ process.stdout.write((0, colors_js_1.gray)('-'.repeat(50)) + '\n');
919
+ process.stdout.write(` Agent: ${(0, colors_js_1.cyan)(baseline.agent)}\n`);
920
+ process.stdout.write(` Phase: ${phaseColor(baseline.phase)}\n`);
921
+ process.stdout.write(` Stability: ${stabilityBar(stability)}\n`);
922
+ process.stdout.write(` Total actions: ${String(baseline.totalActions)}\n`);
923
+ process.stdout.write(` Total sessions: ${String(baseline.totalSessions)}\n`);
924
+ process.stdout.write(` Observed since: ${(0, colors_js_1.dim)(baseline.observationStart)}\n`);
925
+ process.stdout.write(` Last activity: ${(0, colors_js_1.dim)(baseline.observationEnd)}\n`);
926
+ if (baseline.lastNewBehaviorAt) {
927
+ process.stdout.write(` Last new behavior: ${(0, colors_js_1.dim)(baseline.lastNewBehaviorAt)}\n`);
928
+ }
929
+ process.stdout.write('\n');
930
+ process.stdout.write((0, colors_js_1.bold)(' Observed Behavior') + '\n');
931
+ const buckets = [
932
+ ['Processes', baseline.observed.processes],
933
+ ['Credentials', baseline.observed.credentials],
934
+ ['Filesystem', baseline.observed.filesystemPaths],
935
+ ['Network', baseline.observed.networkHosts],
936
+ ['MCP Servers', baseline.observed.mcpServers],
937
+ ];
938
+ for (const [label, entries] of buckets) {
939
+ const keys = Object.keys(entries);
940
+ if (keys.length === 0)
941
+ continue;
942
+ process.stdout.write(` ${label} (${keys.length}):\n`);
943
+ const sorted = Object.entries(entries).sort((a, b) => b[1] - a[1]);
944
+ for (const [name, count] of sorted.slice(0, 10)) {
945
+ process.stdout.write(` ${name.padEnd(40)} ${(0, colors_js_1.dim)(String(count) + 'x')}\n`);
946
+ }
947
+ if (sorted.length > 10) {
948
+ process.stdout.write((0, colors_js_1.dim)(` ... and ${sorted.length - 10} more\n`));
949
+ }
950
+ }
951
+ process.stdout.write('\n');
952
+ process.stdout.write(` Transition: ${(0, colors_js_1.dim)(transition.reason)}\n`);
953
+ if (baseline.recommended) {
954
+ process.stdout.write('\n');
955
+ process.stdout.write((0, colors_js_1.bold)(' Recommended Policy') + '\n');
956
+ if (baseline.recommended.processes?.allow?.length) {
957
+ process.stdout.write(` Allow processes: ${baseline.recommended.processes.allow.length}\n`);
958
+ }
959
+ if (baseline.recommended.credentials?.allow?.length) {
960
+ process.stdout.write(` Allow credentials: ${baseline.recommended.credentials.allow.length}\n`);
961
+ }
962
+ if (baseline.recommended.network?.allow?.length) {
963
+ process.stdout.write(` Allow network: ${baseline.recommended.network.allow.length}\n`);
964
+ }
965
+ }
966
+ process.stdout.write((0, colors_js_1.gray)('-'.repeat(50)) + '\n');
967
+ return 0;
968
+ }
969
+ // List all baselines
970
+ const baselines = listBaselines();
971
+ if (baselines.length === 0) {
972
+ if (isJson) {
973
+ process.stdout.write(JSON.stringify([], null, 2) + '\n');
974
+ }
975
+ else {
976
+ process.stdout.write((0, colors_js_1.dim)('No baselines found. Shield will create baselines as agent activity is observed.\n'));
977
+ }
978
+ return 0;
979
+ }
980
+ if (isJson) {
981
+ process.stdout.write(JSON.stringify(baselines, null, 2) + '\n');
982
+ return 0;
983
+ }
984
+ process.stdout.write((0, colors_js_1.bold)('Agent Baselines') + '\n');
985
+ process.stdout.write((0, colors_js_1.gray)('-'.repeat(70)) + '\n');
986
+ process.stdout.write(` ${'Agent'.padEnd(20)} ${'Phase'.padEnd(10)} ${'Stability'.padEnd(12)} ${'Actions'.padEnd(10)} Sessions\n`);
987
+ process.stdout.write((0, colors_js_1.gray)('-'.repeat(70)) + '\n');
988
+ for (const bl of baselines) {
989
+ process.stdout.write(` ${bl.agent.padEnd(20)} ${phaseColor(bl.phase).padEnd(10 + colorPadding(bl.phase))} ` +
990
+ `${bl.stabilityScore.toFixed(2).padEnd(12)} ` +
991
+ `${String(bl.totalActions).padEnd(10)} ` +
992
+ `${String(bl.totalSessions)}\n`);
993
+ }
994
+ process.stdout.write((0, colors_js_1.gray)('-'.repeat(70)) + '\n');
995
+ return 0;
996
+ }
997
+ function phaseColor(phase) {
998
+ switch (phase) {
999
+ case 'learn': return (0, colors_js_1.cyan)(phase);
1000
+ case 'suggest': return (0, colors_js_1.yellow)(phase);
1001
+ case 'protect': return (0, colors_js_1.green)(phase);
1002
+ default: return (0, colors_js_1.dim)(phase);
1003
+ }
1004
+ }
1005
+ /** ANSI codes add invisible characters; compute extra length for padding. */
1006
+ function colorPadding(phase) {
1007
+ return phaseColor(phase).length - phase.length;
1008
+ }
1009
+ function stabilityBar(score) {
1010
+ const filled = Math.round(score * 10);
1011
+ const empty = 10 - filled;
1012
+ const bar = '#'.repeat(filled) + '-'.repeat(empty);
1013
+ const label = (score * 100).toFixed(0) + '%';
1014
+ if (score >= 0.8)
1015
+ return (0, colors_js_1.green)(`[${bar}] ${label}`);
1016
+ if (score >= 0.5)
1017
+ return (0, colors_js_1.yellow)(`[${bar}] ${label}`);
1018
+ return (0, colors_js_1.dim)(`[${bar}] ${label}`);
1019
+ }
592
1020
  // --- LLM intelligence handlers ---
593
1021
  async function handleSuggest(options) {
594
1022
  const { checkLlmAvailable, suggestPolicy } = await import('../shield/llm.js');