From 55f6ee5f35df3516b439267b2ff1cf4c4af6c293 Mon Sep 17 00:00:00 2001 From: Simon Gurcke Date: Mon, 23 Mar 2026 23:04:04 +1000 Subject: [PATCH] Add whoami command --- AGENTS.md | 2 ++ README.md | 21 ++++++++++++++++++++ src/main.rs | 16 +++++++++++++++ src/whoami.rs | 54 +++++++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 93 insertions(+) create mode 100644 src/whoami.rs diff --git a/AGENTS.md b/AGENTS.md index 3dff8c9..fa2e16f 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -10,6 +10,7 @@ src/ main.rs Entry point, CLI argument parsing (clap), command dispatch auth.rs Authentication config (load/save/resolve) + auth command + whoami.rs Whoami command (auth check, team info) apps.rs Apps command (fetch, DB write) consumers.rs Consumers command (paginated fetch, DB write) request_logs.rs Request logs command (Arrow IPC or NDJSON streaming) @@ -35,6 +36,7 @@ npm/ | Subcommand | Data source | | -------------- | -------------------------------------------- | | `auth` | — | +| `whoami` | `GET /v1/team` | | `apps` | `GET /v1/apps` | | `consumers` | `GET /v1/apps/{app_id}/consumers` | | `request-logs` | `POST /v1/apps/{app_id}/request-logs/stream` | diff --git a/README.md b/README.md index 4fb426e..cbff98c 100644 --- a/README.md +++ b/README.md @@ -64,6 +64,27 @@ You can also set the API key via the `APITALLY_API_KEY` environment variable or Run `apitally --help` to see all commands and options. +### `whoami` + +``` +apitally whoami +``` + +Check authentication and show the authenticated team. + +Example command: + +```shell +apitally whoami +``` + +Example output: + + +```json +{"team_id":1,"team_name":"My Team"} +``` + ### `apps` ``` diff --git a/src/main.rs b/src/main.rs index 5a8f076..a945b78 100644 --- a/src/main.rs +++ b/src/main.rs @@ -4,6 +4,7 @@ mod consumers; mod request_logs; mod sql; mod utils; +mod whoami; use std::io::{IsTerminal, Read}; use std::path::PathBuf; @@ -44,6 +45,12 @@ enum Command { api: ApiArgs, }, + /// Show the authenticated team + Whoami { + #[command(flatten)] + api: ApiArgs, + }, + /// List apps in your team /// /// Outputs newline-delimited JSON (one object per line). @@ -193,6 +200,11 @@ fn run(cli: Cli) -> Result<()> { &mut std::io::stdin(), ) } + Command::Whoami { api } => whoami::run( + api.api_key.as_deref(), + api.api_base_url.as_deref(), + std::io::stdout().lock(), + ), Command::Apps { api, db } => apps::run( db.as_deref(), api.api_key.as_deref(), @@ -275,6 +287,10 @@ mod tests { Cli::try_parse_from(["apitally", "auth"]).unwrap().command, Command::Auth { .. } )); + assert!(matches!( + Cli::try_parse_from(["apitally", "whoami"]).unwrap().command, + Command::Whoami { .. } + )); assert!(matches!( Cli::try_parse_from(["apitally", "apps"]).unwrap().command, Command::Apps { db: None, .. } diff --git a/src/whoami.rs b/src/whoami.rs new file mode 100644 index 0000000..360c915 --- /dev/null +++ b/src/whoami.rs @@ -0,0 +1,54 @@ +use std::io::Write; + +use anyhow::Result; +use serde::{Deserialize, Serialize}; + +use crate::auth::{resolve_api_base_url, resolve_api_key}; +use crate::utils::api_get; + +#[derive(Deserialize, Serialize)] +struct TeamResponse { + team_id: i64, + team_name: String, +} + +pub fn run( + api_key: Option<&str>, + api_base_url: Option<&str>, + mut writer: impl Write, +) -> Result<()> { + let api_key = resolve_api_key(api_key)?; + let api_base_url = resolve_api_base_url(api_base_url); + let url = format!("{api_base_url}/v1/team"); + let mut response = api_get(&url, &api_key, &[])?; + let team: TeamResponse = response.body_mut().read_json()?; + serde_json::to_writer(&mut writer, &team)?; + writeln!(writer)?; + Ok(()) +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::utils::test_utils::parse_ndjson; + + #[test] + fn test_run() { + let mut server = mockito::Server::new(); + let mock = server + .mock("GET", "/v1/team") + .with_status(200) + .with_header("content-type", "application/json") + .with_body(r#"{"team_id":1,"team_name":"Test Team"}"#) + .create(); + + let mut buf = Vec::new(); + run(Some("test-key"), Some(&server.url()), &mut buf).unwrap(); + mock.assert(); + + let rows = parse_ndjson(&buf); + assert_eq!(rows.len(), 1); + assert_eq!(rows[0]["team_id"], 1); + assert_eq!(rows[0]["team_name"], "Test Team"); + } +}