From 55f6ee5f35df3516b439267b2ff1cf4c4af6c293 Mon Sep 17 00:00:00 2001
From: Simon Gurcke <simon@gurcke.de>
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:
+
+<!-- prettier-ignore -->
+```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");
+    }
+}
