Skip to main content

ai_memory/cli/commands/
entity_get_by_alias.rs

1// Copyright 2026 AlphaOne LLC
2// SPDX-License-Identifier: Apache-2.0
3
4//! v0.7.0 ARCH-3 / FX-C3 (batch2) — `ai-memory entity-get-by-alias`
5//! CLI subcommand.
6//!
7//! Closes the three-surface-parity gap on `memory_entity_get_by_alias`.
8//! The MCP tool ([`crate::mcp::handle_entity_get_by_alias`]) and the
9//! HTTP route landed previously; this module wires the CLI surface so
10//! operators can resolve an alias to its canonical entity from a
11//! terminal.
12
13use crate::models::field_names;
14use anyhow::Result;
15use clap::Args;
16use serde_json::{Value, json};
17
18use crate::cli::CliOutput;
19use crate::storage as db;
20
21/// CLI args for `ai-memory entity-get-by-alias`.
22#[derive(Args, Debug, Clone)]
23pub struct EntityGetByAliasArgs {
24    /// Alias to resolve (whitespace trimmed).
25    #[arg(long, value_name = "ALIAS")]
26    pub alias: String,
27
28    /// Optional namespace filter. Without it, the most-recently-
29    /// created match across namespaces wins.
30    #[arg(long, value_name = "NS")]
31    pub namespace: Option<String>,
32
33    /// Emit the raw JSON envelope.
34    #[arg(long)]
35    pub json: bool,
36}
37
38/// `ai-memory entity-get-by-alias` dispatch entry.
39///
40/// # Errors
41///
42/// - The DB at `db_path` cannot be opened.
43/// - The substrate refuses the call (validation).
44/// - `serde_json::to_string` cannot serialise the envelope.
45pub fn cmd_entity_get_by_alias(
46    db_path: &std::path::Path,
47    args: &EntityGetByAliasArgs,
48    out: &mut CliOutput<'_>,
49) -> Result<()> {
50    let conn = db::open(db_path)?;
51
52    let mut params = json!({"alias": args.alias});
53    if let Some(ns) = &args.namespace {
54        params["namespace"] = json!(ns);
55    }
56
57    let envelope = crate::mcp::handle_entity_get_by_alias(&conn, &params)
58        .map_err(|e| anyhow::anyhow!("entity-get-by-alias: {e}"))?;
59
60    if args.json {
61        writeln!(out.stdout, "{}", serde_json::to_string(&envelope)?)?;
62        return Ok(());
63    }
64
65    let found = envelope
66        .get("found")
67        .and_then(Value::as_bool)
68        .unwrap_or(false);
69    if found {
70        let id = envelope
71            .get("entity_id")
72            .and_then(Value::as_str)
73            .unwrap_or("?");
74        let name = envelope
75            .get(field_names::CANONICAL_NAME)
76            .and_then(Value::as_str)
77            .unwrap_or("?");
78        writeln!(
79            out.stdout,
80            "entity-get-by-alias: entity_id={id}  canonical_name={name}"
81        )?;
82    } else {
83        writeln!(out.stdout, "entity-get-by-alias: no match")?;
84    }
85    Ok(())
86}
87
88#[cfg(test)]
89mod tests {
90    use super::*;
91    use crate::cli::test_utils::TestEnv;
92
93    #[test]
94    fn entity_get_by_alias_cli_unknown_alias_returns_not_found() {
95        let mut env = TestEnv::fresh();
96        let db = env.db_path.clone();
97        let args = EntityGetByAliasArgs {
98            alias: "nonexistent-alias".into(),
99            namespace: None,
100            json: true,
101        };
102        {
103            let mut out = env.output();
104            cmd_entity_get_by_alias(&db, &args, &mut out).expect("ok");
105        }
106        let stdout = env.stdout_str();
107        let envelope: Value = serde_json::from_str(stdout.trim()).expect("parse envelope");
108        assert_eq!(envelope["found"].as_bool(), Some(false));
109    }
110
111    #[test]
112    fn entity_get_by_alias_cli_round_trip_finds_entity() {
113        let mut env = TestEnv::fresh();
114        let db = env.db_path.clone();
115        // Seed via the entity-register path.
116        let reg = crate::mcp::handle_entity_register(
117            &crate::storage::open(&db).unwrap(),
118            &json!({
119                "canonical_name": "Bob",
120                "namespace": "people",
121                "aliases": ["bobby"],
122                "agent_id": "ai:tester",
123            }),
124            None,
125        )
126        .expect("register");
127        let expected_id = reg["entity_id"].as_str().unwrap().to_string();
128        let args = EntityGetByAliasArgs {
129            alias: "bobby".into(),
130            namespace: Some("people".into()),
131            json: true,
132        };
133        {
134            let mut out = env.output();
135            cmd_entity_get_by_alias(&db, &args, &mut out).expect("ok");
136        }
137        let stdout = env.stdout_str();
138        let envelope: Value = serde_json::from_str(stdout.trim()).expect("parse envelope");
139        assert_eq!(envelope["found"].as_bool(), Some(true));
140        assert_eq!(envelope["entity_id"].as_str(), Some(expected_id.as_str()));
141    }
142
143    #[test]
144    fn entity_get_by_alias_cli_text_output_not_found() {
145        let mut env = TestEnv::fresh();
146        let db = env.db_path.clone();
147        let args = EntityGetByAliasArgs {
148            alias: "nope".into(),
149            namespace: None,
150            json: false,
151        };
152        {
153            let mut out = env.output();
154            cmd_entity_get_by_alias(&db, &args, &mut out).expect("ok");
155        }
156        assert!(env.stdout_str().contains("entity-get-by-alias: no match"));
157    }
158
159    #[test]
160    fn entity_get_by_alias_cli_text_output_found() {
161        let mut env = TestEnv::fresh();
162        let db = env.db_path.clone();
163        crate::mcp::handle_entity_register(
164            &crate::storage::open(&db).unwrap(),
165            &json!({
166                "canonical_name": "Carol",
167                "namespace": "people",
168                "aliases": ["caz"],
169                "agent_id": "ai:tester",
170            }),
171            None,
172        )
173        .expect("register");
174        let args = EntityGetByAliasArgs {
175            alias: "caz".into(),
176            namespace: Some("people".into()),
177            json: false,
178        };
179        {
180            let mut out = env.output();
181            cmd_entity_get_by_alias(&db, &args, &mut out).expect("ok");
182        }
183        let stdout = env.stdout_str();
184        assert!(stdout.contains("entity_id="), "got: {stdout}");
185        assert!(stdout.contains("canonical_name=Carol"), "got: {stdout}");
186    }
187}