agentstatedeveloper_cli/commands/
audit.rs1use std::path::PathBuf;
4
5use anyhow::{Context, Result};
6use clap::{Args, Subcommand};
7
8use agentstatedeveloper_core::{AuditEvent, read_jsonl};
9
10use crate::config::Config;
11
12#[derive(Debug, Subcommand)]
13pub enum AuditCmd {
14 Tail(TailArgs),
17
18 Verify(VerifyArgs),
21}
22
23#[derive(Debug, Args)]
24pub struct VerifyArgs {
25 #[arg(long)]
28 pub log: Option<PathBuf>,
29}
30
31#[derive(Debug, Args)]
32pub struct TailArgs {
33 #[arg(long)]
36 pub log: Option<PathBuf>,
37
38 #[arg(long)]
41 pub event_type: Option<String>,
42
43 #[arg(long)]
46 pub since: Option<String>,
47
48 #[arg(long)]
50 pub actor: Option<String>,
51
52 #[arg(long)]
55 pub outcome: Option<String>,
56
57 #[arg(long, default_value_t = 200)]
59 pub limit: usize,
60}
61
62pub fn run(cfg: &Config, cmd: AuditCmd) -> Result<()> {
63 match cmd {
64 AuditCmd::Tail(args) => tail(cfg, args),
65 AuditCmd::Verify(args) => verify(cfg, args),
66 }
67}
68
69fn verify(_cfg: &Config, _args: VerifyArgs) -> Result<()> {
70 anyhow::bail!(
71 "audit verify is a commercial feature (Enterprise tier) — \
72 install asd-pro to enable tamper-evident chain verification. \
73 See https://agentstatedeveloper.dev/pricing"
74 )
75}
76
77fn tail(cfg: &Config, args: TailArgs) -> Result<()> {
78 let path = args
79 .log
80 .as_ref()
81 .or(cfg.audit_log_path.as_ref())
82 .ok_or_else(|| {
83 anyhow::anyhow!(
84 "no audit log configured — pass --log <path>, \
85 set --audit-log <path> globally, or export ASD_AUDIT_LOG"
86 )
87 })?;
88
89 let events = read_jsonl(path).with_context(|| format!("read audit log {}", path.display()))?;
90
91 let start_idx = match args.since {
93 Some(ref id) => events
94 .iter()
95 .position(|e| &e.event_id == id)
96 .map(|i| i + 1)
97 .unwrap_or(0),
98 None => 0,
99 };
100
101 let filtered: Vec<&AuditEvent> = events[start_idx..]
102 .iter()
103 .filter(|e| {
104 if let Some(ref t) = args.event_type {
105 if !e.event_type.contains(t) {
106 return false;
107 }
108 }
109 if let Some(ref a) = args.actor {
110 if &e.actor_id != a {
111 return false;
112 }
113 }
114 if let Some(ref o) = args.outcome {
115 if &e.outcome != o {
116 return false;
117 }
118 }
119 true
120 })
121 .take(args.limit)
122 .collect();
123
124 let out = serde_json::json!({
125 "path": path.display().to_string(),
126 "count": filtered.len(),
127 "events": filtered,
128 });
129 println!("{}", serde_json::to_string_pretty(&out)?);
130 Ok(())
131}