Metadata-Version: 2.4
Name: aac-cli
Version: 0.1.0
Summary: The aac platform CLI — headless operator interface to the AAC control plane (tenant registration, chain audit).
Author: Agent Authority Cloud Project
License-Expression: Apache-2.0
Requires-Python: >=3.10
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: httpx>=0.28
Provides-Extra: test
Requires-Dist: pytest>=8.0; extra == "test"
Requires-Dist: pytest-httpx>=0.30; extra == "test"
Dynamic: license-file

# aac-cli — the `aac` platform CLI

The headless operator interface to the AAC control plane (Eng Spec
§XII; Equifax/KPI-6 requirement: every operational task doable without
a web console).

## Install

```bash
pip install aac-cli
```

> **Careful with the name:** `pip install aac` installs an UNRELATED
> project (an MBSE modeling tool that happens to share the acronym).
> The AAC platform CLI's PyPI distribution is **`aac-cli`**; the
> command it installs is `aac` — the awscli precedent (dist name ≠
> command name).

Two personas, two invocation styles:

* **Installed operators** (`pip install aac-cli`) run the bare
  command: `aac tenant register ...`
* **Repo developers** run it through the workspace without installing:
  `uv run aac ...`

The examples below use the bare form; prefix `uv run` if you're in the
repo.

Week 13 ships the Python argparse SUBSET of §XII's full command
surface — the production Go CLI is V5+ scope:

```bash
aac configure                      # interactive; aws-configure semantics
aac configure --profile staging    # create/update the [staging] profile
aac configure list                 # effective settings + source of each

aac tenant register --tenant-id acme.com --display-name "ACME Corporation" \
    --workload-spiffe-id spiffe://acme.com/treasury-agent/v1 \
    --tenant-admin-pubkey-file ./tenant-admin.public.pem

aac chain show --token-id <64-hex token or chain root> --tenant-id acme.com
aac chain show --token-id <id> --output table
aac chain show --token-id <id> --render --render-out ./aeg.html   # headless
aac chain show --token-id <id> --render --open                    # + browser
```

Naming: console script = `aac` (the §XVI-reserved name); import
package = `aac_cli` (the SDK owns the `aac` import package);
distribution = `aac-cli` (decided at first release, B119: bare `aac`
on PyPI belongs to an unrelated active project and is not claimable).

## Configuration

`aac configure` writes `~/.aac/config` (INI, profile-sectioned —
AWS-CLI idiom; override the directory with `AAC_CLI_HOME`). Prompts
show `[current-or-default]`; Enter keeps the bracket value:

```console
$ aac configure
admin_url [http://127.0.0.1:8000]:
data_plane_url [http://127.0.0.1:9000]:
tenant_id [None]: acme.com
```

```ini
[default]
admin_url = http://127.0.0.1:8000
data_plane_url = http://127.0.0.1:9000
tenant_id = acme.com
```

Supplying all three flags (`--admin-url` / `--data-plane-url` /
`--tenant-id`) skips every prompt — scriptable. `--profile staging`
creates/updates `[staging]`, leaving other sections intact. The file
stays hand-editable INI, but note a rewrite does not preserve
comments (aws-cli behaves the same). `aac configure` never prompts
for credentials — api_keys are minted at `aac tenant register`
(deliberate deviation from `aws configure`).

Precedence: flag > env (`AAC_ADMIN_URL` / `AAC_DATA_PLANE_URL` /
`AAC_TENANT_ID`) > profile > localhost defaults. `--profile` selects a
section. `aac configure list` prints each effective setting with its
source (`flag` / `env:AAC_*` / `profile:<name>` / `default` / `unset`).

### `AAC_CLI_HOME` — isolated homes

`AAC_CLI_HOME` relocates the whole `~/.aac` tree (config +
credentials). Two everyday uses:

```bash
# 1. Throwaway smoke/test home — nothing touches your real config:
AAC_CLI_HOME=/tmp/aac-smoke aac tenant register --tenant-id smoke.example ...

# 2. Multi-account operators — hard-wall separation beyond profiles
#    (separate credential files, not just separate config sections):
alias aac-prod='AAC_CLI_HOME=$HOME/.aac-prod aac'
alias aac-staging='AAC_CLI_HOME=$HOME/.aac-staging aac'
```

Profiles share one credentials directory; `AAC_CLI_HOME` gives each
context its own.

Credentials: `~/.aac/credentials/{tenant_id}`, the bare `aac_ak_...`
string at mode 0600, written by `aac tenant register` (the key is
printed EXACTLY ONCE — only its peppered hash exists server-side) and
presented as `Authorization: Bearer` by `aac chain show`. Same bare-
string format the joined compose smoke writes into sidecar key dirs.

## Choosing a tenant_id

`tenant_id` is your org's FEDERATION IDENTIFIER — the stable handle
every other tenant verifies your signatures under: it appears in
`X-AAC-Originator-Tenant-Id` headers, `/.well-known/aac-root-keys/
{tenant_id}` paths, SIEM stream directories, and supplier whitelists.
Grammar: **1-64 characters of `[a-z0-9._-]`, starting and ending
alphanumeric**; an org domain like `acme.com` is the convention. Pick
it like an AWS account ID: it identifies you to the federation and
cannot be renamed later. `--parent-tenant-id` takes the parent org's
`tenant_id` (same grammar) — the server resolves handles to internal
row ids; no database identifier ever crosses the API (B118 D3/D4).
Domain ownership verification (DNS TXT) is a separate, additive
control tracked in the `tenant_domains` table — the challenge flow
ships in a follow-up increment.

## Semantics worth knowing

* `aac chain show` accepts ANY hop's token id or the chain root id
  (`--root-token-id` is an accepted alias); visibility is
  participant-tenant over the symmetric composite closure (Week 12) —
  non-participants get the same 404 as unknown tokens.
* Timelines are §V.2.1 METADATA-TIER: predicates and business
  narratives stay in your tenant SIEM stream; the table output's
  footer says so (§6.1 federated join is the full-fidelity story).
* Exit codes (originator-cli convention): 0 success / 1 server
  rejected / 2 unreachable / 3 CLI-input error.

## Tests

`uv run pytest cli/aac/tests/` — pytest-httpx mocks; no control plane
needed. Against a live stack: bring up the joined topology
(`./bin/run-wedge-a-control-plane-compose.sh --keep-up`) and point the
flags at localhost.
