gabriel / muse public
whoami.py python
121 lines 4.0 KB
95b86799 feat: add --format json to all porcelain commands for agent-first output Gabriel Cardona <gabriel@tellurstori.com> 2d ago
1 """``muse whoami`` — show the current identity.
2
3 A top-level convenience shortcut for ``muse auth whoami``. Returns the
4 identity stored in ``~/.muse/identity.toml`` for the currently configured
5 hub, or exits non-zero when no identity is stored.
6
7 Usage::
8
9 muse whoami # human-readable
10 muse whoami --json # JSON for agent consumers
11 muse whoami --all # all hubs
12
13 Exit codes::
14
15 0 — identity found and printed
16 1 — no identity stored (not authenticated)
17 """
18
19 from __future__ import annotations
20
21 import logging
22 from typing import Annotated
23
24 import typer
25
26 from muse.core.errors import ExitCode
27 from muse.core.identity import IdentityEntry, list_all_identities, load_identity
28 from muse.core.validation import sanitize_display
29 from muse.cli.config import get_hub_url
30
31 logger = logging.getLogger(__name__)
32
33 app = typer.Typer(help="Show the current identity (shortcut for muse auth whoami).")
34
35
36 def _display(hub: str, entry: IdentityEntry, *, json_output: bool) -> None:
37 import json as _json
38 if json_output:
39 out: dict[str, str | list[str]] = {"hub": hub}
40 for key in ("type", "name", "id"):
41 val = entry.get(key, "")
42 if isinstance(val, str) and val:
43 out[key] = val
44 token = entry.get("token", "")
45 out["token_set"] = "true" if (isinstance(token, str) and token) else "false"
46 caps: list[str] = entry.get("capabilities") or []
47 if caps:
48 out["capabilities"] = caps
49 typer.echo(_json.dumps(out, indent=2))
50 else:
51 itype = entry.get("type") or "unknown"
52 name = entry.get("name") or "—"
53 uid = entry.get("id") or "—"
54 token = entry.get("token", "")
55 token_status = "set" if (isinstance(token, str) and token) else "not set"
56 typer.echo(f" hub: {sanitize_display(hub)}")
57 typer.echo(f" type: {sanitize_display(str(itype))}")
58 typer.echo(f" name: {sanitize_display(str(name))}")
59 typer.echo(f" id: {sanitize_display(str(uid))}")
60 typer.echo(f" token: {token_status}")
61
62
63 @app.callback(invoke_without_command=True)
64 def whoami(
65 json_output: Annotated[
66 bool,
67 typer.Option("--json", "-j", help="Emit JSON instead of human-readable output."),
68 ] = False,
69 all_hubs: Annotated[
70 bool,
71 typer.Option("--all", "-a", help="Show identities for all configured hubs."),
72 ] = False,
73 fmt: Annotated[
74 str,
75 typer.Option("--format", "-f", help="Output format: text or json (alias for --json)."),
76 ] = "text",
77 ) -> None:
78 """Show the current identity stored in ~/.muse/identity.toml.
79
80 Exits non-zero when no identity is stored so agents can branch on
81 authentication status::
82
83 muse whoami --json || muse auth login --agent ...
84 muse whoami --format json # same as --json
85
86 Examples::
87
88 muse whoami
89 muse whoami --json
90 muse whoami --format json
91 muse whoami --all
92 """
93 # --format json is an alias for --json for CLI consistency across all commands.
94 if fmt == "json":
95 json_output = True
96 if all_hubs:
97 identities = list_all_identities()
98 if not identities:
99 typer.echo("No identities stored. Run `muse auth login` to authenticate.")
100 raise typer.Exit(code=ExitCode.USER_ERROR)
101 for hostname, entry in sorted(identities.items()):
102 _display(hostname, entry, json_output=json_output)
103 return
104
105 hub_url = get_hub_url(None)
106 if hub_url is None:
107 typer.echo(
108 "No hub configured. Run `muse hub connect <url>` or `muse auth login --hub <url>`."
109 )
110 raise typer.Exit(code=ExitCode.USER_ERROR)
111
112 loaded = load_identity(hub_url)
113 if loaded is None:
114 typer.echo(
115 f"No identity stored for {hub_url}.\n"
116 f"Run: muse auth login --hub {hub_url}"
117 )
118 raise typer.Exit(code=ExitCode.USER_ERROR)
119
120 hub_display = hub_url.rstrip("/").split("://")[-1].split("/")[0]
121 _display(hub_display, loaded, json_output=json_output)