diff --git a/.claude/agents/security-reviewer.md b/.claude/agents/security-reviewer.md index c9e4dbdd..c68e18ee 100644 --- a/.claude/agents/security-reviewer.md +++ b/.claude/agents/security-reviewer.md @@ -1,10 +1,10 @@ --- name: security-reviewer -description: Use to review changes to authentication, credential storage, environment selection, or subprocess shell-outs in the aai CLI. Focuses on secret handling and the auth/login flow. Invoke after editing aai_cli/auth/, config.py, environments.py, or any subprocess call. +description: Use to review changes to authentication, credential storage, environment selection, or subprocess shell-outs in the assembly CLI. Focuses on secret handling and the auth/login flow. Invoke after editing aai_cli/auth/, config.py, environments.py, or any subprocess call. tools: Glob, Grep, LS, Read, NotebookRead, WebFetch, TodoWrite, WebSearch, KillShell, BashOutput --- -You are a security reviewer for the **AssemblyAI CLI** (`aai`). Your job is to find security regressions in the areas this CLI is most sensitive about, then report concrete findings — not generic advice. +You are a security reviewer for the **AssemblyAI CLI** (`assembly`). Your job is to find security regressions in the areas this CLI is most sensitive about, then report concrete findings — not generic advice. ## What this CLI guarantees (don't let a change break these) diff --git a/.claude/agents/template-contract-reviewer.md b/.claude/agents/template-contract-reviewer.md index 7a0dc646..f162b7a2 100644 --- a/.claude/agents/template-contract-reviewer.md +++ b/.claude/agents/template-contract-reviewer.md @@ -1,10 +1,10 @@ --- name: template-contract-reviewer -description: Use after any change under aai_cli/init/templates/ (the `aai init` starter apps) to verify the scaffold still ships correctly and stays covered by the parametrized contract tests. Catches the renamed-dotfile and wheel-packaging gotchas. +description: Use after any change under aai_cli/init/templates/ (the `assembly init` starter apps) to verify the scaffold still ships correctly and stays covered by the parametrized contract tests. Catches the renamed-dotfile and wheel-packaging gotchas. tools: Glob, Grep, LS, Read, NotebookRead, TodoWrite, KillShell, BashOutput --- -You review changes to the `aai init` starter templates in `aai_cli/init/templates/` (`transcribe/`, `stream/`, `agent/`). These ship inside the wheel and are scaffolded onto users' machines, so a broken template ships broken examples. Verify the following and report concrete gaps. +You review changes to the `assembly init` starter templates in `aai_cli/init/templates/` (`transcribe/`, `stream/`, `agent/`). These ship inside the wheel and are scaffolded onto users' machines, so a broken template ships broken examples. Verify the following and report concrete gaps. ## Packaging integrity diff --git a/.claude/commands/review-changes.md b/.claude/commands/review-changes.md index 2ff70b02..08e34cba 100644 --- a/.claude/commands/review-changes.md +++ b/.claude/commands/review-changes.md @@ -1,5 +1,5 @@ --- -description: Review the current changes with the code-review skill plus the aai CLI's specialized reviewers (security + template contract), scoped to what actually changed. +description: Review the current changes with the code-review skill plus the assembly CLI's specialized reviewers (security + template contract), scoped to what actually changed. argument-hint: "[git ref to diff against, default: HEAD]" allowed-tools: Bash(git diff *), Bash(git status *), Bash(git log *), Task, Skill --- diff --git a/.claude/skills/check/SKILL.md b/.claude/skills/check/SKILL.md index 93bec6a1..381e1007 100644 --- a/.claude/skills/check/SKILL.md +++ b/.claude/skills/check/SKILL.md @@ -1,6 +1,6 @@ --- name: check -description: Run the full local verification gate for the aai CLI (the same checks CI runs). Use before pushing or opening a PR. +description: Run the full local verification gate for the assembly CLI (the same checks CI runs). Use before pushing or opening a PR. disable-model-invocation: true --- diff --git a/.claude/skills/plan-feature/SKILL.md b/.claude/skills/plan-feature/SKILL.md index 27ad922b..9a26c828 100644 --- a/.claude/skills/plan-feature/SKILL.md +++ b/.claude/skills/plan-feature/SKILL.md @@ -1,6 +1,6 @@ --- name: plan-feature -description: Plan a new aai CLI feature the way this repo does — drive the superpowers planning skill, then persist the spec + plan into docs/superpowers/ under this repo's naming and verification conventions. Use before building anything non-trivial. +description: Plan a new assembly CLI feature the way this repo does — drive the superpowers planning skill, then persist the spec + plan into docs/superpowers/ under this repo's naming and verification conventions. Use before building anything non-trivial. disable-model-invocation: true argument-hint: "" --- diff --git a/.claude/skills/release-prep/SKILL.md b/.claude/skills/release-prep/SKILL.md index d0cef84b..588a37a8 100644 --- a/.claude/skills/release-prep/SKILL.md +++ b/.claude/skills/release-prep/SKILL.md @@ -1,12 +1,12 @@ --- name: release-prep -description: Prepare an aai CLI release — bump the version, run the full gate, build and validate the wheel/sdist, and smoke-test the real install. Use when cutting a new release. +description: Prepare an assembly CLI release — bump the version, run the full gate, build and validate the wheel/sdist, and smoke-test the real install. Use when cutting a new release. disable-model-invocation: true --- # release-prep -Drive an `aai` release to a verified, publishable state. Stop and report at the first failure — never push or publish on a red check. +Drive an `assembly` release to a verified, publishable state. Stop and report at the first failure — never push or publish on a red check. ## 1. Version bump @@ -27,7 +27,7 @@ Must end with `All checks passed.` (ruff, mypy, markdownlint, shellcheck, pytest uv run pytest -q -m install_script ``` -This builds the wheel and runs `install.sh` for real (pipx + pip --user), asserting `aai` runs. Needs network + uv/pipx. +This builds the wheel and runs `install.sh` for real (pipx + pip --user), asserting `assembly` runs. Needs network + uv/pipx. ## 4. Build + metadata validation diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index b1867762..8fc5a41a 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -116,7 +116,7 @@ jobs: # sha256, sidestepping the placeholder that stays until the v0.1.0 tag is cut). # The stricter `brew audit --strict --online` still belongs in a release job. - name: Lint the formula - run: brew style ./Formula/aai.rb + run: brew style ./Formula/assembly.rb pre-commit: name: pre-commit @@ -211,7 +211,7 @@ jobs: python-version: "3.12" cache: pip - # `aai --version` imports the package, which pulls in sounddevice (needs + # `assembly --version` imports the package, which pulls in sounddevice (needs # PortAudio) and ffmpeg-backed sources. Match the other jobs' system deps. - name: System deps (Linux) if: runner.os == 'Linux' @@ -259,7 +259,7 @@ jobs: # Mirror a GitHub archive: a single top-level dir brew strips on extract. git archive --format=tar.gz --prefix="aai-${version}/" -o "$tarball" HEAD sha="$(shasum -a 256 "$tarball" | awk '{print $1}')" - python3 - "Formula/aai.rb" "$tarball" "$sha" <<'PY' + python3 - "Formula/assembly.rb" "$tarball" "$sha" <<'PY' import re, sys, pathlib formula, tarball, sha = sys.argv[1], sys.argv[2], sys.argv[3] p = pathlib.Path(formula) @@ -268,18 +268,18 @@ jobs: src = re.sub(r"sha256 .*", f'sha256 "{sha}"', src, count=1) p.write_text(src) PY - grep -nE '^ (url|sha256) ' Formula/aai.rb | head -2 + grep -nE '^ (url|sha256) ' Formula/assembly.rb | head -2 # Newer Homebrew refuses formulae outside a tap, so install through a # throwaway local tap holding the pinned formula (--no-git avoids needing a # git identity on the runner). --build-from-source compiles the full resource # list (rust + cryptography native builds); `brew test` then runs the - # formula's own `test do` block, asserting `aai --version`. + # formula's own `test do` block, asserting `assembly --version`. - name: brew install + test run: | set -euo pipefail - brew tap-new --no-git aai/local - cp Formula/aai.rb "$(brew --repository aai/local)/Formula/aai.rb" - brew install --build-from-source --formula aai/local/aai - aai --version - brew test aai/local/aai + brew tap-new --no-git assembly/local + cp Formula/assembly.rb "$(brew --repository assembly/local)/Formula/assembly.rb" + brew install --build-from-source --formula assembly/local/assembly + assembly --version + brew test assembly/local/assembly diff --git a/.gitleaks.toml b/.gitleaks.toml index e7f82e45..b591d675 100644 --- a/.gitleaks.toml +++ b/.gitleaks.toml @@ -1,4 +1,4 @@ -# gitleaks config for the aai CLI secret-scan gate (scripts/check.sh + CI). +# gitleaks config for the assembly CLI secret-scan gate (scripts/check.sh + CI). # # Real credentials never live in this repo by design: the API key is stored only in # the OS keyring (see aai_cli/config.py, KEYRING_SERVICE), never in a dotfile or source diff --git a/AGENTS.md b/AGENTS.md index 6f23f993..59239fe2 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -10,7 +10,7 @@ This project uses [uv](https://docs.astral.sh/uv/). **Run every Python tool thro ```sh uv sync # create/refresh the venv (the dev group installs by default) -uv run aai --help # run the CLI from the locked environment +uv run assembly --help # run the CLI from the locked environment ./scripts/check.sh # the full gate CI runs (scripts/check.sh is the source of truth) ``` @@ -90,14 +90,14 @@ Lessons that cost iterations getting the patch-coverage and mutation tail gates (`next(o for o in objs if "env" in o)`). A monkeypatched fake must also mirror the real signature — when a helper gains a kwarg (e.g. `output.status(…, quiet=…)`), doubles that patch it must accept it or the call `TypeError`s. -- **`--json` / `-j` is a per-command flag, not a root flag**: `aai --json transcribe …` fails - with "No such option"; it's `aai transcribe … --json`. (The root callback still sniffs the +- **`--json` / `-j` is a per-command flag, not a root flag**: `assembly --json transcribe …` fails + with "No such option"; it's `assembly transcribe … --json`. (The root callback still sniffs the whole token list via `_command_line_requests_json`, so a callback-level failure like a bad `--env` keeps the JSON error shape — but the flag itself lives on the subcommand.) ### Manual QA / running the CLI in sandboxed sessions -Lessons that cost time in agent sessions — read before exercising `uv run aai` by hand: +Lessons that cost time in agent sessions — read before exercising `uv run assembly` by hand: - **Probe network reachability first.** Remote/sandboxed environments often allowlist PyPI but block `api.assemblyai.com` / `streaming.assemblyai.com` / `llm-gateway.assemblyai.com` @@ -110,8 +110,8 @@ Lessons that cost time in agent sessions — read before exercising `uv run aai` through that shared file — set `XDG_CONFIG_HOME=$(mktemp -d)` per run instead. - **Write scratch output to `/tmp`, never the repo root.** Redirects like `cmd > out.txt` in the repo show up as untracked files and trip commit hooks/gates. -- **Headless boxes have no mic/speakers/browser.** `aai stream`/`aai agent` mic paths and - `aai login`'s browser flow can't complete; wrap exploratory runs in `timeout 30 …` so a +- **Headless boxes have no mic/speakers/browser.** `assembly stream`/`assembly agent` mic paths and + `assembly login`'s browser flow can't complete; wrap exploratory runs in `timeout 30 …` so a blocking path can't wedge the session. For pytest, `--timeout N` (pytest-timeout, in the dev group) does the same per-test. @@ -129,7 +129,7 @@ these live in the default suite. Three moving parts: (it hits the network) and is *not* mypy/pyright-checked (only ruff covers `scripts/`). Refresh after an API shape change: `ASSEMBLYAI_API_KEY=… uv run python scripts/record_fixtures.py`. The key comes from the env; the AMS session JWT + `account_id` from the keyring/`config.toml` - of whoever ran `aai login` (profile `default`) — neither is ever written to a fixture. + of whoever ran `assembly login` (profile `default`) — neither is ever written to a fixture. - **`tests/replay_fixtures.py`** — rebuilds the boundary objects from JSON. A transcript is a real `aai.Transcript` via `Transcript.from_response`; an LLM response is rebuilt with `ChatCompletion.model_construct` (**not** `model_validate`) because the gateway returns @@ -143,13 +143,13 @@ the return value comes from a recorded payload instead of a hand-built mock. ## Naming & packaging gotchas -- The **package/module** is `aai_cli`; the **distribution** name is `aai-cli`; the **console command** is `aai` (`[project.scripts] aai = "aai_cli.main:run"`). -- `aai init` templates live in `aai_cli/init/templates/` and are **committed**, including renamed dotfiles (`gitignore` → `.gitignore`, `env.example`). The wheel force-includes them via `[tool.hatch.build.targets.wheel] artifacts`, excluding `__pycache__/*.pyc`. Editing templates needs care — see the parametrized contract tests (`tests/test_init_template_*.py`). +- The **package/module** is `aai_cli`; the **distribution** name is `aai-cli`; the **console command** is `assembly` (`[project.scripts] assembly = "aai_cli.main:run"`). +- `assembly init` templates live in `aai_cli/init/templates/` and are **committed**, including renamed dotfiles (`gitignore` → `.gitignore`, `env.example`). The wheel force-includes them via `[tool.hatch.build.targets.wheel] artifacts`, excluding `__pycache__/*.pyc`. Editing templates needs care — see the parametrized contract tests (`tests/test_init_template_*.py`). - `audioop` left the stdlib in 3.13; `audioop-lts` backfills it (conditional dependency). Supported Pythons: 3.12–3.13. ## Architecture -A Typer CLI. `aai_cli/main.py` builds the `app`, registers each command sub-app, and controls `aai --help` ordering via `_COMMAND_ORDER` + a custom `_OrderedGroup`. `run()` is the entry point and swallows `BrokenPipeError` (closed downstream pipe → exit 0). +A Typer CLI. `aai_cli/main.py` builds the `app`, registers each command sub-app, and controls `assembly --help` ordering via `_COMMAND_ORDER` + a custom `_OrderedGroup`. `run()` is the entry point and swallows `BrokenPipeError` (closed downstream pipe → exit 0). ### Command layer @@ -167,11 +167,11 @@ Each file in `aai_cli/commands/` is a Typer sub-app (`transcribe`, `stream`, `ag - **`streaming/`** + `client.stream_audio` — v3 realtime API. Event callbacks run on the SDK reader thread and guard against `BrokenPipeError` (`stdio.silence_stdout()`) so a closed pipe never dumps a thread traceback. - **`agent/`** — full-duplex voice agent (mic in, TTS out via `voices.py`). -- **`tts/`** + `commands/speak.py` — `aai speak` synthesizes text to speech over the sandbox streaming-TTS WebSocket (`streaming-tts.sandbox000.…`). **Sandbox-only:** `session.is_available()` is false in production (empty `Environment.streaming_tts_host`), so the command exits 2 with a `--sandbox` hint. `session.synthesize` drives a Begin→Generate→Flush→Audio→Terminate protocol with an injectable `connect` for hermetic tests (mirrors `agent/session.py`); `audio.py` plays the PCM (default) or writes a WAV (`--out`). +- **`tts/`** + `commands/speak.py` — `assembly speak` synthesizes text to speech over the sandbox streaming-TTS WebSocket (`streaming-tts.sandbox000.…`). **Sandbox-only:** `session.is_available()` is false in production (empty `Environment.streaming_tts_host`), so the command exits 2 with a `--sandbox` hint. `session.synthesize` drives a Begin→Generate→Flush→Audio→Terminate protocol with an injectable `connect` for hermetic tests (mirrors `agent/session.py`); `audio.py` plays the PCM (default) or writes a WAV (`--out`). - **`code_gen/`** — backs `--show-code` on `transcribe`/`stream`/`agent`: builds a ready-to-run Python SDK script from exactly the flags passed (no API key needed; generated code reads `ASSEMBLYAI_API_KEY`). -- **`auth/`** — browser-assisted `aai login` via AMS + **Stytch B2B OAuth discovery** (`discovery.py`, `flow.py`, `loopback.py`, `ams.py`). Not Stytch Connected Apps. +- **`auth/`** — browser-assisted `assembly login` via AMS + **Stytch B2B OAuth discovery** (`discovery.py`, `flow.py`, `loopback.py`, `ams.py`). Not Stytch Connected Apps. - **`init/`** — scaffolds a self-contained FastAPI + HTML starter (`audio-transcription`/`live-captions`/`voice-agent` templates), optionally installs deps and opens the browser; writes the key to a git-ignored `.env`. -- **`commands/setup.py`** — `aai setup install/status/remove` wires a coding agent up to AssemblyAI by installing three artifacts: the `assemblyai-docs` docs MCP (via `claude mcp add`), the AssemblyAI skill (via `npx skills add`), and the bundled `aai-cli` skill (copied out of the wheel, no network). Missing `claude`/`npx` is reported and skipped, not an error. +- **`commands/setup.py`** — `assembly setup install/status/remove` wires a coding agent up to AssemblyAI by installing three artifacts: the `assemblyai-docs` docs MCP (via `claude mcp add`), the AssemblyAI skill (via `npx skills add`), and the bundled `aai-cli` skill (copied out of the wheel, no network). Missing `claude`/`npx` is reported and skipped, not an error. ## Conventions diff --git a/Formula/aai.rb b/Formula/assembly.rb similarity index 99% rename from Formula/aai.rb rename to Formula/assembly.rb index 61197846..3e0bf158 100644 --- a/Formula/aai.rb +++ b/Formula/assembly.rb @@ -1,4 +1,4 @@ -class Aai < Formula +class Assembly < Formula include Language::Python::Virtualenv desc "Command-line interface for AssemblyAI" @@ -10,7 +10,7 @@ class Aai < Formula depends_on "pkgconf" => :build # cffi / cryptography native builds depends_on "rust" => :build # pydantic-core, jiter, cryptography - depends_on "cloudflared" # public quick-tunnel for `aai share` + depends_on "cloudflared" # public quick-tunnel for `assembly share` depends_on "ffmpeg" # decode non-WAV/URL audio (transcribe/stream) depends_on "openssl@3" # cryptography linkage depends_on "portaudio" # sounddevice (audio capture) @@ -264,6 +264,6 @@ def install end test do - assert_match version.to_s, shell_output("#{bin}/aai --version") + assert_match version.to_s, shell_output("#{bin}/assembly --version") end end diff --git a/README.md b/README.md index 0a6ce432..5142f08f 100644 --- a/README.md +++ b/README.md @@ -23,7 +23,7 @@ --- -`aai` brings [AssemblyAI](https://www.assemblyai.com) to your terminal: transcribe files, stream live audio, run a two-way voice agent, prompt the LLM Gateway, and scaffold ready-to-deploy starter apps — all pipeline-friendly, with your key kept in the OS keyring. +`assembly` brings [AssemblyAI](https://www.assemblyai.com) to your terminal: transcribe files, stream live audio, run a two-way voice agent, prompt the LLM Gateway, and scaffold ready-to-deploy starter apps — all pipeline-friendly, with your key kept in the OS keyring. ## Installation @@ -31,10 +31,10 @@ ```sh brew tap assemblyai/cli https://github.com/AssemblyAI/cli -brew install --HEAD aai +brew install --HEAD assembly ``` -`brew install` pulls in `ffmpeg` and `portaudio` for you, so `transcribe`, `stream`, and `agent` work out of the box. While the repo is private and pre-release, `--HEAD` builds from the latest `main` (the tap clone uses your existing GitHub credentials); once the first release is tagged, drop the flag and just `brew install aai`. Upgrade with `brew upgrade --fetch-HEAD aai`; remove with `brew uninstall aai`. +`brew install` pulls in `ffmpeg` and `portaudio` for you, so `transcribe`, `stream`, and `agent` work out of the box. While the repo is private and pre-release, `--HEAD` builds from the latest `main` (the tap clone uses your existing GitHub credentials); once the first release is tagged, drop the flag and just `brew install assembly`. Upgrade with `brew upgrade --fetch-HEAD assembly`; remove with `brew uninstall assembly`. ### pipx / uv @@ -59,47 +59,47 @@ Prefers [`pipx`](https://pipx.pypa.io), falling back to `pip --user`. ## Quick Start ```sh -aai onboard # guided setup: sign in, first transcription, start building +assembly onboard # guided setup: sign in, first transcription, start building ``` Prefer to do it by hand? ```sh -aai login # store your API key (browser-assisted) -aai transcribe --sample # transcribe the hosted wildfires.mp3 sample +assembly login # store your API key (browser-assisted) +assembly transcribe --sample # transcribe the hosted wildfires.mp3 sample ``` ## Build An App -`aai init` is how you **build a new app** — it copies a small, self-contained FastAPI + HTML project you can run locally and deploy to Vercel as-is, the starting point whenever you want to *create* something, including a voice agent app: +`assembly init` is how you **build a new app** — it copies a small, self-contained FastAPI + HTML project you can run locally and deploy to Vercel as-is, the starting point whenever you want to *create* something, including a voice agent app: ```sh -aai init # pick a template, scaffold, install deps, open the browser -aai init audio-transcription myapp # non-interactive: template + directory -aai init voice-agent my-agent # build a voice agent app (full FastAPI + browser starter) +assembly init # pick a template, scaffold, install deps, open the browser +assembly init audio-transcription myapp # non-interactive: template + directory +assembly init voice-agent my-agent # build a voice agent app (full FastAPI + browser starter) ``` Your key is written to a git-ignored `.env` (never sent to the browser). Use `--no-install` to scaffold only. -> **Building a voice agent? Use `aai init voice-agent`, not `aai agent`.** `aai agent` only *runs* a live mic conversation in the terminal and writes no code; `aai init` creates the actual app. +> **Building a voice agent? Use `assembly init voice-agent`, not `assembly agent`.** `assembly agent` only *runs* a live mic conversation in the terminal and writes no code; `assembly init` creates the actual app. ## Commands | Command | What it does | | --- | --- | -| `aai login` / `logout` / `whoami` | Manage the stored API key. | -| `aai doctor` | Check your environment (API key, network, ffmpeg, microphone, agent tooling). | -| `aai transcribe ` | Transcribe a file, URL, or YouTube/podcast page URL (`--sample`, `--llm`, `--show-code`). | -| `aai transcripts list` / `get ` | Browse and fetch past transcripts. | -| `aai stream [file]` | Real-time transcription from a file or the microphone. | -| `aai agent` | *Run* a live two-way voice conversation (to **build** a voice agent app, use `aai init voice-agent`). | -| `aai llm ` | Prompt the LLM Gateway (`--transcript-id`, or `--follow` for a live stream). | -| `aai setup install` | Set up your coding agent for AssemblyAI (docs MCP + skills). | -| `aai keys` / `balance` / `usage` / `limits` / `sessions` / `audit` | Account self-service (browser login). | +| `assembly login` / `logout` / `whoami` | Manage the stored API key. | +| `assembly doctor` | Check your environment (API key, network, ffmpeg, microphone, agent tooling). | +| `assembly transcribe ` | Transcribe a file, URL, or YouTube/podcast page URL (`--sample`, `--llm`, `--show-code`). | +| `assembly transcripts list` / `get ` | Browse and fetch past transcripts. | +| `assembly stream [file]` | Real-time transcription from a file or the microphone. | +| `assembly agent` | *Run* a live two-way voice conversation (to **build** a voice agent app, use `assembly init voice-agent`). | +| `assembly llm ` | Prompt the LLM Gateway (`--transcript-id`, or `--follow` for a live stream). | +| `assembly setup install` | Set up your coding agent for AssemblyAI (docs MCP + skills). | +| `assembly keys` / `balance` / `usage` / `limits` / `sessions` / `audit` | Account self-service (browser login). | -Every command prints human-readable text by default — terminal, pipe, CI, or agent alike. Add `--json` (or `-j`) for machine-readable output; it never switches on just because stdout is piped, so `aai transcribe call.mp3 | grep hello` still gets the transcript, not a JSON blob. Errors go to **stderr**, so stdout stays clean for pipelines. +Every command prints human-readable text by default — terminal, pipe, CI, or agent alike. Add `--json` (or `-j`) for machine-readable output; it never switches on just because stdout is piped, so `assembly transcribe call.mp3 | grep hello` still gets the transcript, not a JSON blob. Errors go to **stderr**, so stdout stays clean for pipelines. -Account data lives in **top-level** commands — `aai balance` / `usage` / `limits` / `keys` / `audit`, and `aai login` / `logout` / `whoami` — not under an `aai account` group. +Account data lives in **top-level** commands — `assembly balance` / `usage` / `limits` / `keys` / `audit`, and `assembly login` / `logout` / `whoami` — not under an `assembly account` group. ### JSON output @@ -128,16 +128,16 @@ Scripts can branch on the exit code: | `4` | not authenticated (no usable key, rejected key, or a self-service command needing browser login) | | `130` | cancelled with Ctrl-C | -`aai deploy` / `aai dev` shell out to other tools and propagate that tool's own exit code. +`assembly deploy` / `assembly dev` shell out to other tools and propagate that tool's own exit code. -> **Tip:** Quote URLs that contain `?` (most YouTube links do) — in zsh the `?` is a glob character: `aai transcribe "https://www.youtube.com/watch?v=VIDEO_ID"`. +> **Tip:** Quote URLs that contain `?` (most YouTube links do) — in zsh the `?` is a glob character: `assembly transcribe "https://www.youtube.com/watch?v=VIDEO_ID"`. ## Transcribe A File -`aai transcribe` exposes the full `TranscriptionConfig` surface as curated, grouped flags — model & language, formatting, speakers & channels, PII/safety guardrails, analysis (summary, chapters, sentiment, entities, topics, highlights), customization, and webhooks: +`assembly transcribe` exposes the full `TranscriptionConfig` surface as curated, grouped flags — model & language, formatting, speakers & channels, PII/safety guardrails, analysis (summary, chapters, sentiment, entities, topics, highlights), customization, and webhooks: ```sh -aai transcribe call.mp3 \ +assembly transcribe call.mp3 \ --speaker-labels --speakers-expected 2 \ --redact-pii --redact-pii-policy person_name,phone_number \ --summarization --summary-type bullets \ @@ -146,23 +146,23 @@ aai transcribe call.mp3 \ --config-file extra.json ``` -Anything without a curated flag is reachable via the escape hatch: `--config KEY=VALUE` (repeatable) and `--config-file FILE` (a JSON object) accept any SDK field by name. Precedence: config file < `--config` < explicit flags. Run `aai transcribe --help` for the full flag list. +Anything without a curated flag is reachable via the escape hatch: `--config KEY=VALUE` (repeatable) and `--config-file FILE` (a JSON object) accept any SDK field by name. Precedence: config file < `--config` < explicit flags. Run `assembly transcribe --help` for the full flag list. ## Stream Live Audio ```sh -aai stream --sample # stream the hosted wildfires.mp3 sample -aai stream path/to/audio.wav # 16 kHz mono WAV streams directly (other formats need ffmpeg) -aai stream https://…/clip.mp3 # a URL works too (decoded via ffmpeg) -aai stream # from the microphone; Ctrl-C to stop -aai stream --system-audio # macOS: system/app audio + mic as separate sessions -aai stream --system-audio-only # macOS: system/app audio without the mic +assembly stream --sample # stream the hosted wildfires.mp3 sample +assembly stream path/to/audio.wav # 16 kHz mono WAV streams directly (other formats need ffmpeg) +assembly stream https://…/clip.mp3 # a URL works too (decoded via ffmpeg) +assembly stream # from the microphone; Ctrl-C to stop +assembly stream --system-audio # macOS: system/app audio + mic as separate sessions +assembly stream --system-audio-only # macOS: system/app audio without the mic ``` -`aai stream` exposes the full `StreamingParameters` surface (model & input, turn detection, features) as curated flags, with the same `--config` / `--config-file` escape hatch: +`assembly stream` exposes the full `StreamingParameters` surface (model & input, turn detection, features) as curated flags, with the same `--config` / `--config-file` escape hatch: ```sh -aai stream --sample --max-turn-silence 400 --format-turns \ +assembly stream --sample --max-turn-silence 400 --format-turns \ --keyterms-prompt "AssemblyAI" --config vad_threshold=0.7 ``` @@ -173,25 +173,25 @@ On macOS, `--system-audio` uses ScreenCaptureKit to capture system/app audio wit Run a prompt over the live transcript through the LLM Gateway, refreshing on every finalized turn — one command, no pipe to wire up: ```sh -aai stream --llm "summarize action items as I talk" -aai stream --llm "extract action items" --llm "rewrite them as a checklist" # chains +assembly stream --llm "summarize action items as I talk" +assembly stream --llm "extract action items" --llm "rewrite them as a checklist" # chains ``` -On a terminal you watch one evolving panel; add `--json` for one JSON object per refresh. Prefer the pipe? Compose the primitives — `aai stream -o text` writes one finalized turn per line and `aai llm -f` re-runs your prompt over the growing transcript: +On a terminal you watch one evolving panel; add `--json` for one JSON object per refresh. Prefer the pipe? Compose the primitives — `assembly stream -o text` writes one finalized turn per line and `assembly llm -f` re-runs your prompt over the growing transcript: ```sh -aai stream -o text | aai llm -f --system "You are a meeting scribe" "summarize action items" +assembly stream -o text | assembly llm -f --system "You are a meeting scribe" "summarize action items" ``` ## Voice Agent -Have a live, two-way voice conversation — full-duplex, so you can interrupt mid-sentence (barge-in). **Use headphones**, otherwise the agent hears itself. (This only *runs* a conversation; to **build** a voice-agent app, use `aai init voice-agent`.) +Have a live, two-way voice conversation — full-duplex, so you can interrupt mid-sentence (barge-in). **Use headphones**, otherwise the agent hears itself. (This only *runs* a conversation; to **build** a voice-agent app, use `assembly init voice-agent`.) ```sh -aai agent # talk; the agent talks back. Ctrl-C to stop. -aai agent --voice james --greeting "Hi" -aai agent --system-prompt-file persona.txt # load the system prompt from a file -aai agent --list-voices # see available voices +assembly agent # talk; the agent talks back. Ctrl-C to stop. +assembly agent --voice james --greeting "Hi" +assembly agent --system-prompt-file persona.txt # load the system prompt from a file +assembly agent --list-voices # see available voices ``` ## Show The Code @@ -199,78 +199,78 @@ aai agent --list-voices # see available voices Add `--show-code` to `transcribe`, `stream`, or `agent` to print the equivalent Python SDK script **instead of running** — a ready-to-edit starting point built from exactly the flags you passed. It needs no API key (generated code reads `ASSEMBLYAI_API_KEY`) and writes plain Python to stdout: ```sh -aai transcribe --sample --speaker-labels --show-code # print the equivalent script -aai transcribe call.mp3 --sentiment-analysis --show-code > my_transcribe.py -aai stream --show-code # the microphone-streaming idiom -aai agent --voice ivy --show-code # the full-duplex agent loop +assembly transcribe --sample --speaker-labels --show-code # print the equivalent script +assembly transcribe call.mp3 --sentiment-analysis --show-code > my_transcribe.py +assembly stream --show-code # the microphone-streaming idiom +assembly agent --voice ivy --show-code # the full-duplex agent loop ``` With `--llm` (repeatable), it emits the chained LLM Gateway calls too. ## Pipelines -`aai` composes with the rest of your shell. Output is machine-clean (errors → stderr), commands read `-` from stdin, and `-o`/`--output` prints a single field so you rarely need `jq`. +`assembly` composes with the rest of your shell. Output is machine-clean (errors → stderr), commands read `-` from stdin, and `-o`/`--output` prints a single field so you rarely need `jq`. ```sh # Pick one field with -o -aai transcribe call.mp3 -o text # just the transcript text -aai transcribe video.mp4 -o srt # SubRip (.srt) captions -aai transcribe call.mp3 --json | jq . # full JSON when you do want jq +assembly transcribe call.mp3 -o text # just the transcript text +assembly transcribe video.mp4 -o srt # SubRip (.srt) captions +assembly transcribe call.mp3 --json | jq . # full JSON when you do want jq # Read audio from stdin -ffmpeg -i talk.mp4 -f wav - | aai transcribe - # transcribe any video -curl -sL https://example.com/ep.mp3 | aai transcribe - # no temp file +ffmpeg -i talk.mp4 -f wav - | assembly transcribe - # transcribe any video +curl -sL https://example.com/ep.mp3 | assembly transcribe - # no temp file -# aai llm is a general text filter — it reads stdin, audio optional -git log --oneline -30 | aai llm "write release notes grouped by feature/fix" +# assembly llm is a general text filter — it reads stdin, audio optional +git log --oneline -30 | assembly llm "write release notes grouped by feature/fix" # DIY voice assistant — speak a question, hear the answer (use headphones) -aai stream -o text | while IFS= read -r line; do - echo "$line" | aai llm -o text "answer in one short sentence" | say +assembly stream -o text | while IFS= read -r line; do + echo "$line" | assembly llm -o text "answer in one short sentence" | say done ``` -A Ctrl-C in a pipe hits both sides; to stop just the producer and let the consumer finish, signal the producer (`timeout -s INT 30s aai stream …`) or end on a natural pause (`aai stream --inactivity-timeout 5`). +A Ctrl-C in a pipe hits both sides; to stop just the producer and let the consumer finish, signal the producer (`timeout -s INT 30s assembly stream …`) or end on a natural pause (`assembly stream --inactivity-timeout 5`). ## API Key & Security -`aai` resolves your key in order: the `ASSEMBLYAI_API_KEY` environment variable, then the OS keyring (written only by `aai login`). Two things worth knowing: +`assembly` resolves your key in order: the `ASSEMBLYAI_API_KEY` environment variable, then the OS keyring (written only by `assembly login`). Two things worth knowing: -- The key is **never stored in a plaintext dotfile** — `aai login` puts it in the OS keyring (Keychain / Credential Manager / Secret Service); the only on-disk config holds just profile names. +- The key is **never stored in a plaintext dotfile** — `assembly login` puts it in the OS keyring (Keychain / Credential Manager / Secret Service); the only on-disk config holds just profile names. - There is **no `--api-key` flag on run commands**, so a key can't leak into `ps` output or shell history. Prefer not to persist it? Set the env var instead — it's checked *before* the keyring, so nothing is written to disk. Scope it to one command (and keep it out of history) by injecting from a secret manager at call time: ```sh -ASSEMBLYAI_API_KEY=$(op read "op://Private/AssemblyAI/api key") aai transcribe call.mp3 -op run -- aai transcribe call.mp3 # …or wrap the whole command +ASSEMBLYAI_API_KEY=$(op read "op://Private/AssemblyAI/api key") assembly transcribe call.mp3 +op run -- assembly transcribe call.mp3 # …or wrap the whole command ``` -In CI, set `ASSEMBLYAI_API_KEY` as a masked secret. `aai logout` purges the keyring entry; `aai whoami` / `aai doctor` confirm the active source without printing the key. +In CI, set `ASSEMBLYAI_API_KEY` as a masked secret. `assembly logout` purges the keyring entry; `assembly whoami` / `assembly doctor` confirm the active source without printing the key. ## Account Self-Service -These commands use your browser login session (run `aai login`), not your API key: +These commands use your browser login session (run `assembly login`), not your API key: ```sh -aai keys list # list API keys (masked) across projects -aai keys create --name ci-pipeline # mint a new key (printed once) -aai balance # remaining account balance -aai usage --start 2026-05-01 --end 2026-06-01 -aai sessions list --status completed -aai audit --action token.create # account audit log, filterable +assembly keys list # list API keys (masked) across projects +assembly keys create --name ci-pipeline # mint a new key (printed once) +assembly balance # remaining account balance +assembly usage --start 2026-05-01 --end 2026-06-01 +assembly sessions list --status completed +assembly audit --action token.create # account audit log, filterable ``` -AMS sessions are short-lived — if a command reports it needs a browser login, run `aai login` again. +AMS sessions are short-lived — if a command reports it needs a browser login, run `assembly login` again. ## AI Coding Agents Set your coding agent up for AssemblyAI — the live docs (MCP server), the AssemblyAI skill, and the bundled `aai-cli` skill — so your agent writes current, correct integration code: ```sh -aai setup install # docs MCP + assemblyai skill + bundled aai-cli skill (user scope) -aai setup status # show what's set up -aai setup remove # unwind all three +assembly setup install # docs MCP + assemblyai skill + bundled aai-cli skill (user scope) +assembly setup status # show what's set up +assembly setup remove # unwind all three ``` `install` shells out to `claude mcp add` for the MCP and `npx skills add` for the `assemblyai` skill; the `aai-cli` skill ships inside the package and is copied in directly (no network). Pass `--scope project` to scope the MCP server to the current project. A missing `claude` or `npx` is reported and skipped, not treated as an error. @@ -280,9 +280,9 @@ aai setup remove # unwind all three Use `--help` on any command to explore flags and examples: ```sh -aai --help -aai transcribe --help -aai stream --help +assembly --help +assembly transcribe --help +assembly stream --help ``` - [AssemblyAI docs](https://www.assemblyai.com/docs) @@ -294,7 +294,7 @@ This project uses [uv](https://docs.astral.sh/uv/). Run tools through `uv run` s ```sh uv sync --extra dev # create/refresh the venv with dev dependencies -uv run aai --help # run the CLI from the locked environment +uv run assembly --help # run the CLI from the locked environment uv run pytest # run the test suite (uv run mypy / ruff likewise) ./scripts/check.sh # ruff + mypy + pytest — the same checks CI runs on every PR ``` diff --git a/aai_cli/agent/audio.py b/aai_cli/agent/audio.py index 7363183e..d9135c68 100644 --- a/aai_cli/agent/audio.py +++ b/aai_cli/agent/audio.py @@ -25,7 +25,7 @@ def _output_default_rate(device: int | None = None) -> int: class NullPlayer: """A player look-alike that discards audio instead of opening a speaker. - Used by file-driven agent runs (`aai agent `), which only need the + Used by file-driven agent runs (`assembly agent `), which only need the transcript events: there is no human listening, and headless/CI hosts have no output device for `sounddevice` to open. """ @@ -67,7 +67,7 @@ def _default_duplex_stream(*, rate: int, blocksize: int, callback: Any, device: f"Could not open the audio device: {exc}", error_type="audio_output_error", exit_code=1, - suggestion="Check your microphone/output device, then run 'aai doctor'.", + suggestion="Check your microphone/output device, then run 'assembly doctor'.", ) from exc return stream @@ -177,7 +177,7 @@ def capture_frames(self) -> Iterator[bytes]: "The audio device stopped producing input.", error_type="audio_input_error", exit_code=1, - suggestion="Check your microphone/output device, then run 'aai doctor'.", + suggestion="Check your microphone/output device, then run 'assembly doctor'.", ) from None continue if chunk is None: @@ -231,4 +231,4 @@ def __iter__(self) -> Iterator[bytes]: # Microphone capture (MicrophoneSource) lives in aai_cli.microphone and is -# shared with `aai stream`; the agent's live mic+speaker run through DuplexAudio. +# shared with `assembly stream`; the agent's live mic+speaker run through DuplexAudio. diff --git a/aai_cli/agent/render.py b/aai_cli/agent/render.py index 5c62cf7d..2c6fc669 100644 --- a/aai_cli/agent/render.py +++ b/aai_cli/agent/render.py @@ -16,7 +16,7 @@ class AgentRenderer(BaseRenderer): """Renders Voice Agent events in one of three modes. - JSON: NDJSON events to stdout. - text: plain ``you:``/``agent:`` transcript - lines to stdout with status on stderr (so ``aai agent -o text | aai llm "…"`` + lines to stdout with status on stderr (so ``assembly agent -o text | assembly llm "…"`` pipes the conversation). - human (default): live Rich transcript. Audio payloads are never written; only text/state events are surfaced. @@ -43,7 +43,7 @@ def notice(self, text: str) -> None: """Print a human-facing notice: suppressed in JSON, to stderr otherwise. Stderr in *every* non-JSON mode (not just ``-o text``): the default human - mode is also piped sometimes (``aai agent | head``), and a notice on stdout + mode is also piped sometimes (``assembly agent | head``), and a notice on stdout would be consumed as transcript data there. """ if self.json_mode: diff --git a/aai_cli/agent/session.py b/aai_cli/agent/session.py index b6d2f135..27de5de2 100644 --- a/aai_cli/agent/session.py +++ b/aai_cli/agent/session.py @@ -76,7 +76,7 @@ def __init__( self.renderer = renderer self.player = player self.full_duplex = full_duplex - # File-driven runs (`aai agent `) stop after the agent's first reply + # File-driven runs (`assembly agent `) stop after the agent's first reply # to the spoken input, so the process exits instead of waiting forever. self.exit_after_reply = exit_after_reply self.ready_event = ready_event @@ -159,7 +159,7 @@ def raise_error(self, event: dict[str, Any]) -> None: # auto-login must not retry when the key came from ASSEMBLYAI_API_KEY. raise NotAuthenticated( f"Voice agent rejected the connection: {message}", - suggestion="Run 'aai login' with a valid key, or set ASSEMBLYAI_API_KEY.", + suggestion="Run 'assembly login' with a valid key, or set ASSEMBLYAI_API_KEY.", rejected_key=True, ) raise APIError(f"Voice agent error ({code}): {message}") diff --git a/aai_cli/auth/ams.py b/aai_cli/auth/ams.py index 9ed24dc6..19be95ea 100644 --- a/aai_cli/auth/ams.py +++ b/aai_cli/auth/ams.py @@ -26,7 +26,7 @@ def _raise_for_error(resp: httpx.Response) -> None: if resp.status_code in (401, 403): raise NotAuthenticated( f"AMS rejected the login ({resp.status_code}): {_detail(resp)}", - suggestion="Your browser session may have expired — run 'aai login' again.", + suggestion="Your browser session may have expired — run 'assembly login' again.", ) if resp.status_code >= _HTTP_ERROR_MIN_STATUS: raise APIError( diff --git a/aai_cli/auth/endpoints.py b/aai_cli/auth/endpoints.py index 13361305..5e14434b 100644 --- a/aai_cli/auth/endpoints.py +++ b/aai_cli/auth/endpoints.py @@ -31,8 +31,8 @@ def loopback_port() -> int: Resolved lazily — never at import — and validated, so a malformed override surfaces as a clean CLIError on the login path instead of the raw ``ValueError`` a module-level ``int(...)`` would raise. This module sits on the CLI's import hot - path, so that ValueError would otherwise crash *every* ``aai`` command (even - ``--help``), not just ``aai login``. + path, so that ValueError would otherwise crash *every* ``assembly`` command (even + ``--help``), not just ``assembly login``. """ raw = os.environ.get("AAI_AUTH_PORT") if raw is None: diff --git a/aai_cli/auth/flow.py b/aai_cli/auth/flow.py index 62243eda..792517ad 100644 --- a/aai_cli/auth/flow.py +++ b/aai_cli/auth/flow.py @@ -80,7 +80,7 @@ def _parse[T](adapter: TypeAdapter[T], data: object) -> T: return adapter.validate_python(data) except ValidationError as exc: raise APIError( - "Login failed: the server returned an unexpected response. Run 'aai login' again." + "Login failed: the server returned an unexpected response. Run 'assembly login' again." ) from exc @@ -119,7 +119,7 @@ def find_or_create_cli_key(account_id: int, session_jwt: str) -> str: if not projects: raise APIError( "Your account has no project to create an API key in.", - suggestion="Create a project in the AssemblyAI dashboard, then run 'aai login' again.", + suggestion="Create a project in the AssemblyAI dashboard, then run 'assembly login' again.", ) for entry in projects: for token in entry.tokens: @@ -129,7 +129,7 @@ def find_or_create_cli_key(account_id: int, session_jwt: str) -> str: if project is None: raise APIError( "Your account has no project to create an API key in.", - suggestion="Create a project in the AssemblyAI dashboard, then run 'aai login' again.", + suggestion="Create a project in the AssemblyAI dashboard, then run 'assembly login' again.", ) created = ams.create_token(account_id, project.id, endpoints.CLI_TOKEN_NAME, session_jwt) return _parse(_CREATED_TOKEN, created).api_key @@ -144,26 +144,26 @@ def run_login_flow() -> LoginResult: _open_browser(discovery.build_start_url()) output.error_console.print( "[aai.muted]Waiting up to 2 minutes for you to finish signing in…[/aai.muted]\n" - "[aai.muted]No browser here? Run 'aai login --api-key ' instead.[/aai.muted]" + "[aai.muted]No browser here? Run 'assembly login --api-key ' instead.[/aai.muted]" ) result = capture.wait() if result.error == "timeout": raise NotAuthenticated( "Login timed out waiting for the browser.", - suggestion="Run 'aai login' again, or use 'aai login --api-key '.", + suggestion="Run 'assembly login' again, or use 'assembly login --api-key '.", ) if result.token_type != "discovery_oauth" or not result.token: # noqa: S105 raise APIError( "Login did not return a valid OAuth token.", - suggestion="Run 'aai login' again.", + suggestion="Run 'assembly login' again.", ) disc = _parse(_DISCOVERY, ams.discover(result.token)) if not disc.organizations: raise APIError( "Signed in, but this identity has no AssemblyAI account yet. " - f"Create one at {endpoints.signup_url()}, then run 'aai login' again." + f"Create one at {endpoints.signup_url()}, then run 'assembly login' again." ) org = disc.organizations[0] if len(disc.organizations) > 1: diff --git a/aai_cli/auth/loopback.py b/aai_cli/auth/loopback.py index b00b8cac..2cf792f8 100644 --- a/aai_cli/auth/loopback.py +++ b/aai_cli/auth/loopback.py @@ -109,7 +109,7 @@ def log_message(self, format: str, *args: object) -> None: # silence stderr log raise APIError( f"Could not start the login callback server on " f"{endpoints.LOOPBACK_HOST}:{port} ({exc}). " - "Close whatever is using that port and run 'aai login' again." + "Close whatever is using that port and run 'assembly login' again." ) from exc thread = threading.Thread(target=server.serve_forever, daemon=True) thread.start() diff --git a/aai_cli/choices.py b/aai_cli/choices.py index d9272cb4..52915949 100644 --- a/aai_cli/choices.py +++ b/aai_cli/choices.py @@ -28,7 +28,7 @@ class TextOrJson(enum.StrEnum): class Scope(enum.StrEnum): - """Coding-agent config scope for `aai setup` (passed through to `claude mcp add`).""" + """Coding-agent config scope for `assembly setup` (passed through to `claude mcp add`).""" user = "user" project = "project" diff --git a/aai_cli/client.py b/aai_cli/client.py index fe327147..dab743d7 100644 --- a/aai_cli/client.py +++ b/aai_cli/client.py @@ -234,7 +234,7 @@ def validate_transcript_id(transcript_id: str) -> str: if not _TRANSCRIPT_ID_RE.match(transcript_id): raise UsageError( f"{transcript_id!r} doesn't look like a transcript id.", - suggestion="Ids are letters/digits/dashes; find them with 'aai transcripts list'.", + suggestion="Ids are letters/digits/dashes; find them with 'assembly transcripts list'.", ) return transcript_id @@ -264,7 +264,7 @@ def stream_audio( def _guard(cb: Callable[[Any], Any]) -> _StreamHandler: # Event callbacks run on the SDK's reader thread. If the downstream pipe is - # gone (e.g. a Ctrl-C'd `| aai llm`, or `| head`), writing a turn raises + # gone (e.g. a Ctrl-C'd `| assembly llm`, or `| head`), writing a turn raises # BrokenPipeError there with no handler -> an ugly thread traceback. Swallow # it and point stdout at /dev/null so the interpreter's exit-flush can't # re-raise either; the main thread still stops via Ctrl-C / source EOF. diff --git a/aai_cli/code_gen/stream.py b/aai_cli/code_gen/stream.py index fed3a910..27e0a45e 100644 --- a/aai_cli/code_gen/stream.py +++ b/aai_cli/code_gen/stream.py @@ -145,7 +145,7 @@ def stdin_chunks(): _FILE_SETUP = """ # Decode the source (any local file or http(s) URL ffmpeg can read) to PCM16 mono at -# {rate} Hz and pace it at ~real time — the same pipeline `aai stream ` runs. +# {rate} Hz and pace it at ~real time — the same pipeline `assembly stream ` runs. def file_chunks(): chunk_bytes = {rate} * 2 // 10 # ~100 ms of 16-bit mono PCM ffmpeg = subprocess.Popen( @@ -248,7 +248,7 @@ def render( ``source`` selects the audio input the script reads, mirroring the CLI run path: ``None`` captures the microphone, ``"-"`` reads raw PCM16 from stdin, and anything else is a file path or URL decoded to PCM through ffmpeg (the same pipeline a real - `aai stream ` run uses). With `llm`, the script transforms the live + `assembly stream ` run uses). With `llm`, the script transforms the live transcript through the LLM Gateway, refreshing a prompt chain on every finalized turn (the live sibling of `transcribe --llm`). """ diff --git a/aai_cli/commands/account.py b/aai_cli/commands/account.py index 00babd2b..8e8e7722 100644 --- a/aai_cli/commands/account.py +++ b/aai_cli/commands/account.py @@ -105,8 +105,8 @@ def _line_items_summary(item: Mapping[str, object]) -> str: rich_help_panel=help_panels.ACCOUNT, epilog=examples_epilog( [ - ("Show your remaining balance", "aai balance"), - ("Get the raw cents for scripting", "aai balance --json | jq '.balance_in_cents'"), + ("Show your remaining balance", "assembly balance"), + ("Get the raw cents for scripting", "assembly balance --json | jq '.balance_in_cents'"), ] ), ) @@ -133,12 +133,12 @@ def body(state: AppState, json_mode: bool) -> None: rich_help_panel=help_panels.ACCOUNT, epilog=examples_epilog( [ - ("Usage over the last 30 days", "aai usage"), - ("A specific date range", "aai usage --start 2026-05-01 --end 2026-06-01"), - ("Break spend down by month", "aai usage --window month"), + ("Usage over the last 30 days", "assembly usage"), + ("A specific date range", "assembly usage --start 2026-05-01 --end 2026-06-01"), + ("Break spend down by month", "assembly usage --window month"), ( "Total spend in cents for scripting", - "aai usage --json | jq '[.usage_items[].line_items[].price] | add'", + "assembly usage --json | jq '[.usage_items[].line_items[].price] | add'", ), ] ), @@ -154,7 +154,7 @@ def usage( False, "--include-zero", "--all", - help="Include zero-usage windows (matches --include-logins on `aai audit`).", + help="Include zero-usage windows (matches --include-logins on `assembly audit`).", ), json_out: bool = options.json_option(), ) -> None: @@ -225,8 +225,8 @@ def render(d: dict[str, object]) -> object: rich_help_panel=help_panels.ACCOUNT, epilog=examples_epilog( [ - ("Show rate limits per service", "aai limits"), - ("As JSON for scripting", "aai limits --json"), + ("Show rate limits per service", "assembly limits"), + ("As JSON for scripting", "assembly limits --json"), ] ), ) diff --git a/aai_cli/commands/agent.py b/aai_cli/commands/agent.py index 013006c0..ece92e95 100644 --- a/aai_cli/commands/agent.py +++ b/aai_cli/commands/agent.py @@ -58,7 +58,7 @@ def _open_audio( # streams on a device, which silently kills capture. duplex = DuplexAudio(target_rate=SAMPLE_RATE, device=device) # notice() self-suppresses in JSON mode and routes to stderr otherwise, so a - # piped `aai agent | …` never reads this advisory as transcript data. + # piped `assembly agent | …` never reads this advisory as transcript data. renderer.notice( "Use headphones — the mic stays open while the agent speaks, " "so speakers would let it hear itself.\n" @@ -76,11 +76,14 @@ def _emit_voice_list(_state: AppState, json_mode: bool) -> None: rich_help_panel=help_panels.TRANSCRIPTION, epilog=examples_epilog( [ - ("Start a live voice conversation", "aai agent"), - ("Pick a voice and opening line", 'aai agent --voice james --greeting "Hi there"'), - ("Give the agent a persona", 'aai agent --system-prompt "You are a terse pirate."'), - ("See available voices", "aai agent --list-voices"), - ("Print equivalent Python instead of running", "aai agent --show-code"), + ("Start a live voice conversation", "assembly agent"), + ("Pick a voice and opening line", 'assembly agent --voice james --greeting "Hi there"'), + ( + "Give the agent a persona", + 'assembly agent --system-prompt "You are a terse pirate."', + ), + ("See available voices", "assembly agent --list-voices"), + ("Print equivalent Python instead of running", "assembly agent --show-code"), ] ), ) @@ -131,7 +134,7 @@ def agent( the agent instead of the microphone; the session then ends after the agent's reply. This only runs a conversation in the terminal — it writes no code. To build - a voice agent app, run 'aai init voice-agent' instead. + a voice agent app, run 'assembly init voice-agent' instead. """ if list_voices: @@ -144,7 +147,7 @@ def body(state: AppState, json_mode: bool) -> None: if voice not in VOICES: raise UsageError( f"Unknown voice {voice!r}.", - suggestion="Run 'aai agent --list-voices' to see the options.", + suggestion="Run 'assembly agent --list-voices' to see the options.", ) system_prompt_text = _resolve_system_prompt(system_prompt, system_prompt_file) diff --git a/aai_cli/commands/audit.py b/aai_cli/commands/audit.py index 4b7282b9..b99c6367 100644 --- a/aai_cli/commands/audit.py +++ b/aai_cli/commands/audit.py @@ -77,11 +77,11 @@ def _audit_rows(payload: Mapping[str, object]) -> list[dict[str, object]]: rich_help_panel=help_panels.ACCOUNT, epilog=examples_epilog( [ - ("Recent audit-log entries", "aai audit"), - ("Show more entries", "aai audit --limit 100"), - ("Include login events", "aai audit --include-logins"), - ("Filter by action", "aai audit --action token.create"), - ("Filter by resource, as JSON", "aai audit --resource token --json"), + ("Recent audit-log entries", "assembly audit"), + ("Show more entries", "assembly audit --limit 100"), + ("Include login events", "assembly audit --include-logins"), + ("Filter by action", "assembly audit --action token.create"), + ("Filter by resource, as JSON", "assembly audit --resource token --json"), ] ), ) diff --git a/aai_cli/commands/deploy.py b/aai_cli/commands/deploy.py index 9e687ab2..c2388442 100644 --- a/aai_cli/commands/deploy.py +++ b/aai_cli/commands/deploy.py @@ -15,7 +15,7 @@ from aai_cli.help_text import examples_epilog from aai_cli.init import procfile -# Flattened single-command sub-typer (same pattern as `aai dev`). +# Flattened single-command sub-typer (same pattern as `assembly dev`). app = typer.Typer() @@ -112,8 +112,8 @@ def run_deploy(*, target: Target, prod: bool, assume_yes: bool) -> None: "--prod is only supported for Vercel deploys.", suggestion=f"Drop --prod, or drop {target.flag} to deploy to Vercel.", ) - # Same not-a-project guard as `aai dev`/`aai share`, checked before CLI presence - # so an empty directory says "run `aai init`", not "install the Vercel CLI". + # Same not-a-project guard as `assembly dev`/`assembly share`, checked before CLI presence + # so an empty directory says "run `assembly init`", not "install the Vercel CLI". procfile.require_procfile(Path.cwd()) _require_cli(target) if not _confirmed(target, assume_yes=assume_yes): @@ -130,10 +130,10 @@ def run_deploy(*, target: Target, prod: bool, assume_yes: bool) -> None: rich_help_panel=help_panels.BUILD, epilog=examples_epilog( [ - ("Deploy a preview to Vercel (asks first)", "aai deploy"), - ("Deploy to production on Vercel", "aai deploy --prod --yes"), - ("Deploy to Railway", "aai deploy --railway"), - ("Deploy to Fly.io", "aai deploy --fly"), + ("Deploy a preview to Vercel (asks first)", "assembly deploy"), + ("Deploy to production on Vercel", "assembly deploy --prod --yes"), + ("Deploy to Railway", "assembly deploy --railway"), + ("Deploy to Fly.io", "assembly deploy --fly"), ] ), ) diff --git a/aai_cli/commands/dev.py b/aai_cli/commands/dev.py index 8e2861bb..f1b5c941 100644 --- a/aai_cli/commands/dev.py +++ b/aai_cli/commands/dev.py @@ -12,7 +12,7 @@ from aai_cli.help_text import examples_epilog from aai_cli.init import devserver, procfile, runner -# Flattened single-command sub-typer (same pattern as `aai init`): one +# Flattened single-command sub-typer (same pattern as `assembly init`): one # @app.command() registered via app.add_typer(dev.app) with no name. app = typer.Typer() @@ -55,10 +55,10 @@ def run_dev(*, port: int, host: str, no_install: bool, no_open: bool, json_mode: rich_help_panel=help_panels.BUILD, epilog=examples_epilog( [ - ("Launch the app in the current directory", "aai dev"), - ("Use a specific port", "aai dev --port 8000"), - ("Launch without opening a browser", "aai dev --no-open"), - ("Skip the dependency install step", "aai dev --no-install"), + ("Launch the app in the current directory", "assembly dev"), + ("Use a specific port", "assembly dev --port 8000"), + ("Launch without opening a browser", "assembly dev --no-open"), + ("Skip the dependency install step", "assembly dev --no-install"), ] ), ) @@ -78,7 +78,7 @@ def dev( ) -> None: """Launch the dev server for the app in the current directory. - Run this from inside a project created by `aai init`. It installs dependencies + Run this from inside a project created by `assembly init`. It installs dependencies if needed, then starts the FastAPI server with live reload and opens the browser. """ diff --git a/aai_cli/commands/doctor.py b/aai_cli/commands/doctor.py index d8104373..bca3ea17 100644 --- a/aai_cli/commands/doctor.py +++ b/aai_cli/commands/doctor.py @@ -28,7 +28,7 @@ class Check(TypedDict): class DoctorResult(TypedDict): ok: bool - # Which profile/environment the checks ran against. `aai doctor` always fills + # Which profile/environment the checks ran against. `assembly doctor` always fills # these in; the onboarding wizard reuses `render` for a partial check without # them, so they stay optional. profile: NotRequired[str] @@ -80,7 +80,7 @@ def _check_api_key(profile: str) -> Check: key = config.resolve_api_key(profile=profile) except NotAuthenticated: if not config.keyring_usable(): - # On a box with no keyring, `aai login` can't persist a key either, so + # On a box with no keyring, `assembly login` can't persist a key either, so # point at the env var that actually works here instead of a dead end. return _check( "api-key", @@ -93,7 +93,7 @@ def _check_api_key(profile: str) -> Check: "api-key", "fail", "No API key found.", - fix="Run 'aai login' (or set ASSEMBLYAI_API_KEY).", + fix="Run 'assembly login' (or set ASSEMBLYAI_API_KEY).", affects=["everything"], ) # validate_key doubles as the connectivity probe: it makes one cheap authed call, @@ -115,7 +115,7 @@ def _check_api_key(profile: str) -> Check: "api-key", "fail", "API key was rejected (HTTP 401).", - fix="Run 'aai login' with a valid key.", + fix="Run 'assembly login' with a valid key.", affects=["everything"], ) @@ -196,7 +196,7 @@ def _check_coding_agent() -> Check: return _check( "coding-agent", "ok", - "claude and npx found; run 'aai setup install' to wire up the docs MCP + skills.", + "claude and npx found; run 'assembly setup install' to wire up the docs MCP + skills.", ) return _check( "coding-agent", @@ -204,9 +204,9 @@ def _check_coding_agent() -> Check: f"not found: {', '.join(missing)}.", fix=( "Install Claude Code (https://claude.com/claude-code) and Node.js, " - "then run 'aai setup install'." + "then run 'assembly setup install'." ), - affects=["aai setup install"], + affects=["assembly setup install"], ) @@ -227,11 +227,11 @@ def render(data: DoctorResult) -> str: lines.append(" " + output.hint(f"fix: {escape(c['fix'])}")) if data["ok"]: lines.append(" " + output.success("Everything looks good.")) - # Only the real `aai doctor` carries profile context; the onboarding wizard + # Only the real `assembly doctor` carries profile context; the onboarding wizard # reuses render() for a partial check and has its own next-steps, so don't # tack a "try transcribe" hint onto that one. if data.get("profile") is not None: - lines.append(" " + output.hint("Try it: aai transcribe --sample")) + lines.append(" " + output.hint("Try it: assembly transcribe --sample")) else: failed = sum(1 for c in checks if c["status"] == "fail") noun = "problem" if failed == 1 else "problems" @@ -243,8 +243,8 @@ def render(data: DoctorResult) -> str: rich_help_panel=help_panels.SETUP, epilog=examples_epilog( [ - ("Check your environment is ready", "aai doctor"), - ("Output results as JSON", "aai doctor --json"), + ("Check your environment is ready", "assembly doctor"), + ("Output results as JSON", "assembly doctor --json"), ] ), ) diff --git a/aai_cli/commands/init.py b/aai_cli/commands/init.py index 48c4142a..05cb22b9 100644 --- a/aai_cli/commands/init.py +++ b/aai_cli/commands/init.py @@ -13,9 +13,9 @@ from aai_cli.help_text import examples_epilog from aai_cli.init import keys, runner, scaffold, templates -# Single-command sub-typer flattened to `aai init` (the exact pattern `aai transcribe` +# Single-command sub-typer flattened to `assembly init` (the exact pattern `assembly transcribe` # uses): one @app.command() named `init`, registered via app.add_typer(init.app) with -# no name. Bare `aai init` runs the command with template=None -> the interactive picker. +# no name. Bare `assembly init` runs the command with template=None -> the interactive picker. app = typer.Typer() @@ -74,7 +74,7 @@ def _resolve_template(template: str | None) -> str: def _active_env_vars() -> dict[str, str]: """Pin the scaffolded app to the active environment's hosts. - A sandbox key (minted by `aai login` against a non-prod env) would otherwise be + A sandbox key (minted by `assembly login` against a non-prod env) would otherwise be rejected by the production defaults baked into the template. """ env = environments.active() @@ -146,7 +146,7 @@ def _scaffold_report(chosen: str, target: Path, api_key: str | None) -> list[ste { "name": "key", "status": "skipped", - "detail": "no API key found; wrote a placeholder to .env (run `aai login`)", + "detail": "no API key found; wrote a placeholder to .env (run `assembly login`)", } ) return report @@ -209,7 +209,7 @@ def run_init( { "name": "launch", "status": "skipped", - "detail": f"no API key; run `aai login`, then: cd {target} && aai dev", + "detail": f"no API key; run `assembly login`, then: cd {target} && assembly dev", } ) @@ -222,7 +222,7 @@ def run_init( elif not json_mode: # Scaffolded but not launched (no key, or --no-install, or launch=False): leave the # user with the one command that starts their app, the way `vercel`/`supabase` sign off. - output.console.print(output.hint(f"Run `cd {escape(str(target))} && aai dev`.")) + output.console.print(output.hint(f"Run `cd {escape(str(target))} && assembly dev`.")) return target @@ -230,16 +230,16 @@ def run_init( rich_help_panel=help_panels.BUILD, epilog=examples_epilog( [ - ("Scaffold a new app interactively", "aai init"), + ("Scaffold a new app interactively", "assembly init"), ( "Scaffold an audio transcription app into ./my-app", - "aai init audio-transcription my-app", + "assembly init audio-transcription my-app", ), - ("Scaffold a voice agent app", "aai init voice-agent"), - ("Scaffold into the current directory", "aai init audio-transcription --here"), + ("Scaffold a voice agent app", "assembly init voice-agent"), + ("Scaffold into the current directory", "assembly init audio-transcription --here"), ( "Scaffold only, without installing or launching", - "aai init audio-transcription --no-install", + "assembly init audio-transcription --no-install", ), ] ), @@ -270,7 +270,7 @@ def init( """Scaffold a new project from a template, then launch it. This is the starting point for creating an app — including a voice agent app - ('aai init voice-agent'). The 'aai agent' command only runs a live mic + ('assembly init voice-agent'). The 'assembly agent' command only runs a live mic conversation and writes no code. """ diff --git a/aai_cli/commands/keys.py b/aai_cli/commands/keys.py index 65d54a4d..676c5c14 100644 --- a/aai_cli/commands/keys.py +++ b/aai_cli/commands/keys.py @@ -47,9 +47,9 @@ def _default_project_id(account_id: int, jwt: str) -> int: name="list", epilog=examples_epilog( [ - ("List your API keys (masked)", "aai keys list"), - ("As JSON for scripting", "aai keys list --json"), - ("Get key ids to use with rename", "aai keys list --json | jq '.[].id'"), + ("List your API keys (masked)", "assembly keys list"), + ("As JSON for scripting", "assembly keys list --json"), + ("Get key ids to use with rename", "assembly keys list --json | jq '.[].id'"), ] ), ) @@ -99,11 +99,11 @@ def render(data: list[dict[str, object]]) -> object: @app.command( epilog=examples_epilog( [ - ("Create a key in your default project", "aai keys create --name ci-pipeline"), - ("Create a key in a specific project", "aai keys create --name prod --project 7"), + ("Create a key in your default project", "assembly keys create --name ci-pipeline"), + ("Create a key in a specific project", "assembly keys create --name prod --project 7"), ( "Capture the new key into an env var", - "export ASSEMBLYAI_API_KEY=$(aai keys create --name ci --json | jq -r '.api_key')", + "export ASSEMBLYAI_API_KEY=$(assembly keys create --name ci --json | jq -r '.api_key')", ), ] ) @@ -138,13 +138,13 @@ def body(state: AppState, json_mode: bool) -> None: @app.command( epilog=examples_epilog( [ - ("Relabel a key (id from `aai keys list`)", 'aai keys rename 123 "prod"'), + ("Relabel a key (id from `assembly keys list`)", 'assembly keys rename 123 "prod"'), ] ) ) def rename( ctx: typer.Context, - token_id: int = typer.Argument(..., help="The key id (see `aai keys list`)."), + token_id: int = typer.Argument(..., help="The key id (see `assembly keys list`)."), new_name: str = typer.Argument(..., help="The new label."), json_out: bool = options.json_option(), ) -> None: diff --git a/aai_cli/commands/llm.py b/aai_cli/commands/llm.py index d5b54a14..c9ab75d4 100644 --- a/aai_cli/commands/llm.py +++ b/aai_cli/commands/llm.py @@ -14,7 +14,7 @@ _FOLLOW_STDIN_MESSAGE = ( "--follow needs transcript text piped on stdin, e.g. " - '`aai stream -o text | aai llm -f "summarize action items as I talk"`.' + '`assembly stream -o text | assembly llm -f "summarize action items as I talk"`.' ) @@ -54,21 +54,21 @@ def _emit_model_list(_state: AppState, json_mode: bool) -> None: [ ( "Ask about a past transcript", - 'aai llm "summarize the key decisions" --transcript-id 5551234-abcd', + 'assembly llm "summarize the key decisions" --transcript-id 5551234-abcd', ), - ("Pipe any text in", 'echo "meeting notes" | aai llm "turn into action items"'), + ("Pipe any text in", 'echo "meeting notes" | assembly llm "turn into action items"'), ( "Pick a model and add a system prompt", - 'aai llm "draft a follow-up email" --model claude-opus-4-7 --system "Be concise."', + 'assembly llm "draft a follow-up email" --model claude-opus-4-7 --system "Be concise."', ), - ("See available models", "aai llm --list-models"), + ("See available models", "assembly llm --list-models"), ] ), ) def llm( ctx: typer.Context, prompt: str | None = typer.Argument(None, help="The prompt to send to the model."), - # Note: text piped on stdin is injected into the prompt (e.g. `cat notes | aai llm "summarize"`). + # Note: text piped on stdin is injected into the prompt (e.g. `cat notes | assembly llm "summarize"`). model: str = typer.Option( gateway.DEFAULT_MODEL, "--model", @@ -84,7 +84,7 @@ def llm( "--follow", "-f", help="Re-run the prompt over a growing transcript piped on stdin, refreshing " - "the answer in place on every finalized turn (e.g. aai stream -o text | aai " + "the answer in place on every finalized turn (e.g. assembly stream -o text | assembly " 'llm -f "summarize action items as I talk"). Ctrl-C to stop.', ), output_field: choices.TextOrJson | None = typer.Option( @@ -102,7 +102,7 @@ def llm( """Send a prompt to AssemblyAI's LLM Gateway and print the response. With --transcript-id the transcript's text is injected server-side, so you - can ask questions about a past transcription (e.g. aai llm "summarize" --transcript-id ID). + can ask questions about a past transcription (e.g. assembly llm "summarize" --transcript-id ID). """ if list_models: @@ -133,7 +133,7 @@ def ask(transcript_text: str) -> str: except KeyboardInterrupt: interrupted = True if not transcript and not interrupted: - # An empty pipe (`aai llm -f "…" None: ) content = gateway.content_of(response) if output_field == "text": - # Just the answer, raw — so `… | aai llm -o text "…" | next` composes cleanly. + # Just the answer, raw — so `… | assembly llm -o text "…" | next` composes cleanly. output.emit_text(content) return output.emit( diff --git a/aai_cli/commands/login.py b/aai_cli/commands/login.py index 66ce390e..e547627e 100644 --- a/aai_cli/commands/login.py +++ b/aai_cli/commands/login.py @@ -16,8 +16,8 @@ rich_help_panel=help_panels.ACCOUNT, epilog=examples_epilog( [ - ("Log in with your browser", "aai login"), - ("Log in non-interactively (CI)", "aai login --api-key sk_..."), + ("Log in with your browser", "assembly login"), + ("Log in non-interactively (CI)", "assembly login --api-key sk_..."), ] ), ) @@ -40,7 +40,7 @@ def body(state: AppState, json_mode: bool) -> None: raise UsageError( "--api-key was given an empty value.", suggestion=( - "Pass a real key: aai login --api-key " + "Pass a real key: assembly login --api-key " "(check that the shell variable you expanded is set)." ), ) @@ -66,13 +66,15 @@ def body(state: AppState, json_mode: bool) -> None: def render(_d: object) -> str: lines = [ output.success(f"Signed in as {escape(profile)} ({escape(env)})."), - output.hint("Run `aai onboard` to finish setup, or `aai transcribe `."), + output.hint( + "Run `assembly onboard` to finish setup, or `assembly transcribe `." + ), ] if api_key_only: lines.append( output.hint( "Account commands (keys/balance/usage/limits/audit) need " - "`aai login` without --api-key." + "`assembly login` without --api-key." ) ) return "\n".join(lines) @@ -92,7 +94,7 @@ def render(_d: object) -> str: rich_help_panel=help_panels.ACCOUNT, epilog=examples_epilog( [ - ("Clear stored credentials for the active profile", "aai logout"), + ("Clear stored credentials for the active profile", "assembly logout"), ] ), ) @@ -111,7 +113,7 @@ def body(state: AppState, json_mode: bool) -> None: lambda _d: ( output.success(f"Signed out of {escape(profile)}.") + "\n" - + output.hint("Run `aai login` to sign back in.") + + output.hint("Run `assembly login` to sign back in.") ), json_mode=json_mode, ) @@ -124,7 +126,7 @@ def body(state: AppState, json_mode: bool) -> None: rich_help_panel=help_panels.ACCOUNT, epilog=examples_epilog( [ - ("Show the active profile and whether its key works", "aai whoami"), + ("Show the active profile and whether its key works", "assembly whoami"), ] ), ) diff --git a/aai_cli/commands/onboard.py b/aai_cli/commands/onboard.py index efce5fdd..39158305 100644 --- a/aai_cli/commands/onboard.py +++ b/aai_cli/commands/onboard.py @@ -28,7 +28,7 @@ def build_prompter(*, non_interactive: bool = False) -> Prompter: rich_help_panel=help_panels.QUICK_START, epilog=examples_epilog( [ - ("Run the guided setup", "aai onboard"), + ("Run the guided setup", "assembly onboard"), ] ), ) diff --git a/aai_cli/commands/sessions.py b/aai_cli/commands/sessions.py index acb265f9..b432f28d 100644 --- a/aai_cli/commands/sessions.py +++ b/aai_cli/commands/sessions.py @@ -34,15 +34,15 @@ def _session_rows(value: object) -> list[dict[str, object]]: name="list", epilog=examples_epilog( [ - ("List recent streaming sessions", "aai sessions list"), - ("Find failed sessions", "aai sessions list --status error"), + ("List recent streaming sessions", "assembly sessions list"), + ("Find failed sessions", "assembly sessions list --status error"), ( "Inspect the most recent session", - "aai sessions get $(aai sessions list --json | jq -r '.[0].session_id')", + "assembly sessions get $(assembly sessions list --json | jq -r '.[0].session_id')", ), ( "Total audio across recent sessions (seconds)", - "aai sessions list --json | jq '[.[].audio_duration_sec] | add'", + "assembly sessions list --json | jq '[.[].audio_duration_sec] | add'", ), ] ), @@ -90,11 +90,11 @@ def render(data: list[dict[str, object]]) -> object: @app.command( epilog=examples_epilog( [ - ("Show one session's details", "aai sessions get sess_5551234"), - ("Raw JSON for one session", "aai sessions get sess_5551234 --json"), + ("Show one session's details", "assembly sessions get sess_5551234"), + ("Raw JSON for one session", "assembly sessions get sess_5551234 --json"), ( "Drill into the latest session", - "aai sessions get $(aai sessions list --json | jq -r '.[0].session_id')", + "assembly sessions get $(assembly sessions list --json | jq -r '.[0].session_id')", ), ] ) diff --git a/aai_cli/commands/setup.py b/aai_cli/commands/setup.py index 728e7b61..b255a41f 100644 --- a/aai_cli/commands/setup.py +++ b/aai_cli/commands/setup.py @@ -291,9 +291,9 @@ def render(data: dict[str, list[Step]]) -> str: @app.command( epilog=examples_epilog( [ - ("Set up your coding agent for AssemblyAI", "aai setup install"), - ("Install for the current project only", "aai setup install --scope project"), - ("Reinstall everything even if already present", "aai setup install --force"), + ("Set up your coding agent for AssemblyAI", "assembly setup install"), + ("Install for the current project only", "assembly setup install --scope project"), + ("Reinstall everything even if already present", "assembly setup install --force"), ] ) ) @@ -327,8 +327,8 @@ def body(_state: AppState, json_mode: bool) -> None: @app.command( epilog=examples_epilog( [ - ("Show what's set up", "aai setup status"), - ("Print status as JSON", "aai setup status --json"), + ("Show what's set up", "assembly setup status"), + ("Print status as JSON", "assembly setup status --json"), ] ) ) @@ -348,8 +348,8 @@ def body(_state: AppState, json_mode: bool) -> None: @app.command( epilog=examples_epilog( [ - ("Remove the AssemblyAI MCP server and skills", "aai setup remove"), - ("Remove only from the project scope", "aai setup remove --scope project"), + ("Remove the AssemblyAI MCP server and skills", "assembly setup remove"), + ("Remove only from the project scope", "assembly setup remove --scope project"), ] ) ) diff --git a/aai_cli/commands/share.py b/aai_cli/commands/share.py index 6e6ba672..33a86119 100644 --- a/aai_cli/commands/share.py +++ b/aai_cli/commands/share.py @@ -17,7 +17,7 @@ from aai_cli.help_text import examples_epilog from aai_cli.init import devserver, procfile, runner, tunnel -# Flattened single-command sub-typer (same pattern as `aai dev`). +# Flattened single-command sub-typer (same pattern as `assembly dev`). app = typer.Typer() @@ -118,9 +118,9 @@ def run_share(*, port: int, no_install: bool, json_mode: bool) -> None: rich_help_panel=help_panels.BUILD, epilog=examples_epilog( [ - ("Share the running app on a public URL", "aai share"), - ("Use a specific local port", "aai share --port 8000"), - ("Skip the dependency install step", "aai share --no-install"), + ("Share the running app on a public URL", "assembly share"), + ("Use a specific local port", "assembly share --port 8000"), + ("Skip the dependency install step", "assembly share --no-install"), ] ), ) @@ -134,7 +134,7 @@ def share( ) -> None: """Boot the app and expose it on a public URL via a cloudflared tunnel. - Run this from inside a project created by `aai init`. It starts the dev server and + Run this from inside a project created by `assembly init`. It starts the dev server and opens a cloudflared quick tunnel, printing a shareable https://*.trycloudflare.com URL. Requires cloudflared (macOS: `brew install cloudflared`; other platforms: https://developers.cloudflare.com/cloudflare-one/connections/connect-networks/downloads/). diff --git a/aai_cli/commands/speak.py b/aai_cli/commands/speak.py index 117cacb6..a0abcef6 100644 --- a/aai_cli/commands/speak.py +++ b/aai_cli/commands/speak.py @@ -14,7 +14,7 @@ app = typer.Typer() # The streaming-TTS reference client defaults to the PocketTTS "jane" voice and -# English, so the CLI sends the same and a bare `aai speak` works out of the box. +# English, so the CLI sends the same and a bare `assembly speak` works out of the box. # Override either with --voice/--language. DEFAULT_VOICE = "jane" DEFAULT_LANGUAGE = "English" @@ -23,7 +23,7 @@ def _read_text(text: str | None) -> str: """The text to speak: the non-blank argument, or piped stdin when the argument is omitted entirely. A *blank* argument (e.g. "") is a usage error, never a - silent fall-through to stdin — so `aai speak "$MSG"` with an empty MSG fails + silent fall-through to stdin — so `assembly speak "$MSG"` with an empty MSG fails fast instead of consuming whatever happens to be on the pipe.""" if text is not None and text.strip(): return text @@ -34,7 +34,7 @@ def _read_text(text: str | None) -> str: return piped raise UsageError( "No text to speak.", - suggestion='Pass text as an argument: aai speak "Hello" — or pipe it via stdin.', + suggestion='Pass text as an argument: assembly speak "Hello" — or pipe it via stdin.', ) @@ -169,22 +169,22 @@ def _speak_dialogue( rich_help_panel=help_panels.TRANSCRIPTION, epilog=examples_epilog( [ - ("Speak text aloud (sandbox only)", 'aai speak "Hello there, friend." --sandbox'), + ("Speak text aloud (sandbox only)", 'assembly speak "Hello there, friend." --sandbox'), ( "Pick a voice and language", - 'aai speak "Bonjour" --voice jane --language French --sandbox', + 'assembly speak "Bonjour" --voice jane --language French --sandbox', ), ( "Speak a diarized transcript, one voice per speaker", - "aai transcribe meeting.mp3 --speaker-labels | aai speak --sandbox", + "assembly transcribe meeting.mp3 --speaker-labels | assembly speak --sandbox", ), ( "Override a speaker's voice", - "… | aai speak --voice A=vera --voice B=paul --sandbox", + "… | assembly speak --voice A=vera --voice B=paul --sandbox", ), ( "Save to a WAV instead of playing", - 'aai speak "Hello" --out /tmp/hello.wav --sandbox', + 'assembly speak "Hello" --out /tmp/hello.wav --sandbox', ), ] ), @@ -209,7 +209,7 @@ def speak( """Synthesize speech from text with AssemblyAI streaming TTS (sandbox only). Plays the audio through your speakers by default, or writes a WAV with --out. - Speaker-labeled input (from 'aai transcribe --speaker-labels') is detected + Speaker-labeled input (from 'assembly transcribe --speaker-labels') is detected automatically: the labels are stripped and each speaker gets a different voice. This feature only exists in the sandbox today — run it with --sandbox. """ @@ -217,7 +217,7 @@ def speak( def body(state: AppState, json_mode: bool) -> None: if not session.is_available(): raise CLIError( - "aai speak is only available in the sandbox.", + "assembly speak is only available in the sandbox.", error_type="unsupported_environment", exit_code=2, suggestion="Re-run with --sandbox (or --env sandbox000).", diff --git a/aai_cli/commands/stream.py b/aai_cli/commands/stream.py index cbbeee94..e7f8f62f 100644 --- a/aai_cli/commands/stream.py +++ b/aai_cli/commands/stream.py @@ -55,7 +55,7 @@ def _dispatch(session: StreamSession, opts: SourceOptions) -> None: [("system", system, system.sample_rate), ("you", mic, mic.sample_rate)] ) elif opts.from_stdin: - # Raw PCM16 mono piped on stdin (e.g. `ffmpeg … -f s16le - | aai stream -`). + # Raw PCM16 mono piped on stdin (e.g. `ffmpeg … -f s16le - | assembly stream -`). stdin_src = StdinSource(sample_rate=opts.sample_rate or TARGET_RATE) session.run(stdin_src, stdin_src.sample_rate) elif opts.source and youtube.is_downloadable_url(opts.source): @@ -81,19 +81,19 @@ def _dispatch(session: StreamSession, opts: SourceOptions) -> None: rich_help_panel=help_panels.TRANSCRIPTION, epilog=examples_epilog( [ - ("Stream from your microphone", "aai stream"), - ("Stream a file or URL in real time", "aai stream recording.wav"), - ("Stream the hosted sample", "aai stream --sample"), - ("Label speakers in the live transcript", "aai stream --speaker-labels"), + ("Stream from your microphone", "assembly stream"), + ("Stream a file or URL in real time", "assembly stream recording.wav"), + ("Stream the hosted sample", "assembly stream --sample"), + ("Label speakers in the live transcript", "assembly stream --speaker-labels"), ( "Boost domain terms with keyterm prompts", - 'aai stream --keyterms-prompt "AssemblyAI" --keyterms-prompt "Claude"', + 'assembly stream --keyterms-prompt "AssemblyAI" --keyterms-prompt "Claude"', ), ( "Summarize action items live as you talk", - 'aai stream --llm "summarize action items"', + 'assembly stream --llm "summarize action items"', ), - ("Print equivalent Python instead of running", "aai stream --show-code"), + ("Print equivalent Python instead of running", "assembly stream --show-code"), ] ), ) @@ -341,11 +341,11 @@ def stream( """Transcribe live audio in real time — from your mic, a file, a URL, or a pipe. Pass - as the source to read raw PCM16/mono/16k audio on stdin, e.g. - ffmpeg -i input.mp4 -f s16le -ar 16000 -ac 1 - | aai stream -. + ffmpeg -i input.mp4 -f s16le -ar 16000 -ac 1 - | assembly stream -. --prompt biases the speech model. --llm runs a prompt over the live transcript in-process, refreshing the answer on every finalized turn; for a separate step - instead, pipe the text out with -o text | aai llm -f "…". + instead, pipe the text out with -o text | assembly llm -f "…". """ def body(state: AppState, json_mode: bool) -> None: diff --git a/aai_cli/commands/transcribe.py b/aai_cli/commands/transcribe.py index 163eaea3..239a50d4 100644 --- a/aai_cli/commands/transcribe.py +++ b/aai_cli/commands/transcribe.py @@ -34,14 +34,14 @@ rich_help_panel=help_panels.TRANSCRIPTION, epilog=examples_epilog( [ - ("Transcribe a local file", "aai transcribe call.mp3"), - ("Try it with the hosted sample", "aai transcribe --sample"), - ("Transcribe a YouTube video", "aai transcribe https://youtu.be/dtp6b76pMak"), - ("Transcribe a podcast episode page", 'aai transcribe "https://podcasts.apple.com/…"'), - ("Label who said what", "aai transcribe call.mp3 --speaker-labels"), - ("Redact PII for compliance", "aai transcribe call.mp3 --redact-pii"), - ("Summarize a recording", "aai transcribe call.mp3 --summarization"), - ("Ask about the transcript", 'aai transcribe call.mp3 --llm "List the action items"'), + ("Transcribe a local file", "assembly transcribe call.mp3"), + ("Try it with the hosted sample", "assembly transcribe --sample"), + ("Transcribe a YouTube video", "assembly transcribe https://youtu.be/dtp6b76pMak"), + ("Transcribe a podcast page", 'assembly transcribe "https://podcasts.apple.com/…"'), + ("Label who said what", "assembly transcribe call.mp3 --speaker-labels"), + ("Redact PII for compliance", "assembly transcribe call.mp3 --redact-pii"), + ("Summarize a recording", "assembly transcribe call.mp3 --summarization"), + ("Ask about the transcript", 'assembly transcribe call.mp3 --llm "List action items"'), ] ), ) @@ -339,7 +339,7 @@ def transcribe( ) -> None: """Transcribe an audio file, URL, or YouTube/podcast link. - Quickest start: aai transcribe call.mp3 (or --sample for the hosted demo). + Quickest start: assembly transcribe call.mp3 (or --sample for the hosted demo). Save with --out FILE, or pipe one field with -o text. YouTube and podcast-page URLs (any page yt-dlp can extract) are downloaded first, then transcribed. @@ -402,7 +402,7 @@ def body(state: AppState, json_mode: bool) -> None: # --out captures the transcript itself; an LLM transform is a separate step. raise UsageError( "--out can't be combined with --llm.", - suggestion='Pipe the transform instead, e.g. -o text | aai llm -f "…".', + suggestion='Pipe the transform instead, e.g. -o text | assembly llm -f "…".', ) merged = config_builder.merge_transcribe_config( diff --git a/aai_cli/commands/transcripts.py b/aai_cli/commands/transcripts.py index 692f476c..f07cbe84 100644 --- a/aai_cli/commands/transcripts.py +++ b/aai_cli/commands/transcripts.py @@ -14,10 +14,10 @@ @app.command( epilog=examples_epilog( [ - ("Fetch a transcript's text by id", "aai transcripts get 5551234-abcd"), - ("Speaker-labeled turns", "aai transcripts get 5551234-abcd -o utterances"), - ("Save SRT subtitles", "aai transcripts get 5551234-abcd -o srt > captions.srt"), - ("Get the raw JSON", "aai transcripts get 5551234-abcd --json"), + ("Fetch a transcript's text by id", "assembly transcripts get 5551234-abcd"), + ("Speaker-labeled turns", "assembly transcripts get 5551234-abcd -o utterances"), + ("Save SRT subtitles", "assembly transcripts get 5551234-abcd -o srt > captions.srt"), + ("Get the raw JSON", "assembly transcripts get 5551234-abcd --json"), ] ) ) @@ -47,7 +47,7 @@ def body(state: AppState, json_mode: bool) -> None: output.emit_text(client.select_transcript_field(transcript, output_field)) return if json_mode: - # The full SDK payload, identical to `aai transcribe … --json`, so the + # The full SDK payload, identical to `assembly transcribe … --json`, so the # same `jq` works whether the transcript is fetched fresh or re-fetched. output.emit(client.transcript_json_payload(transcript), lambda d: d, json_mode=True) else: @@ -64,13 +64,13 @@ def body(state: AppState, json_mode: bool) -> None: name="list", epilog=examples_epilog( [ - ("List your recent transcripts", "aai transcripts list"), - ("Show more at once", "aai transcripts list --limit 50"), - ("Grab the latest transcript id", "aai transcripts list --json | jq -r '.[0].id'"), + ("List your recent transcripts", "assembly transcripts list"), + ("Show more at once", "assembly transcripts list --limit 50"), + ("Grab the latest transcript id", "assembly transcripts list --json | jq -r '.[0].id'"), ( "Summarize your latest transcript", - 'aai llm "summarize" --transcript-id ' - "$(aai transcripts list --json | jq -r '.[0].id')", + 'assembly llm "summarize" --transcript-id ' + "$(assembly transcripts list --json | jq -r '.[0].id')", ), ] ), diff --git a/aai_cli/config.py b/aai_cli/config.py index 27a88759..d4409fb8 100644 --- a/aai_cli/config.py +++ b/aai_cli/config.py @@ -225,9 +225,9 @@ def keyring_usable() -> bool: """True when the OS keyring backend can be read. Headless boxes (containers, CI, bare SSH) often have no keyring backend, so - ``keyring`` raises on every access. ``aai doctor`` uses this to tell a user with + ``keyring`` raises on every access. ``assembly doctor`` uses this to tell a user with no key that the *backend* is the problem — and to recommend ASSEMBLYAI_API_KEY — - rather than pointing at `aai login`, whose browser flow also can't persist there. + rather than pointing at `assembly login`, whose browser flow also can't persist there. """ try: keyring.get_password(KEYRING_SERVICE, "__probe__") @@ -373,7 +373,7 @@ def resolve_api_key(*, profile: str | None = None, api_key_flag: str | None = No def resolve_api_key_optional(*, profile: str | None = None) -> str | None: """The same key chain as ``resolve_api_key`` (env -> keyring), but ``None`` instead of raising when no key is configured — for callers that work without one - (``aai init`` scaffolding, the onboarding wizard's signed-in check).""" + (``assembly init`` scaffolding, the onboarding wizard's signed-in check).""" try: return resolve_api_key(profile=profile) except NotAuthenticated: diff --git a/aai_cli/context.py b/aai_cli/context.py index 3bac1a04..a99e10f1 100644 --- a/aai_cli/context.py +++ b/aai_cli/context.py @@ -43,7 +43,7 @@ def resolve_session(self) -> tuple[int, str]: These endpoints authenticate with the browser-login session, not the API key. Raises NotAuthenticated when the active profile has no stored session - (e.g. it was set up with `aai login --api-key`, or the session expired). + (e.g. it was set up with `assembly login --api-key`, or the session expired). """ from aai_cli.errors import NotAuthenticated @@ -52,7 +52,7 @@ def resolve_session(self) -> tuple[int, str]: account_id = config.get_account_id(profile) if session is None or account_id is None: raise NotAuthenticated( - "These commands need a browser login. Run 'aai login' (without --api-key)." + "These commands need a browser login. Run 'assembly login' (without --api-key)." ) return account_id, session["jwt"] @@ -78,7 +78,7 @@ def env_override_warning(self) -> str | None: return ( f"Using {source} {selected}, but profile '{profile}' was set up for " f"{profile_env}; its stored key may be rejected by {selected}. " - f"{fix}, or run 'aai login' for {selected}." + f"{fix}, or run 'assembly login' for {selected}." ) @@ -118,7 +118,7 @@ def _persist_browser_login(state: AppState) -> None: def _login_persistence_error(exc: object) -> APIError: return APIError( f"Signed in, but could not save the credentials locally: {exc}", - suggestion="Run 'aai login' again, or check your keyring/config permissions.", + suggestion="Run 'assembly login' again, or check your keyring/config permissions.", ) @@ -140,7 +140,7 @@ def _interactive_session() -> bool: def _should_auto_login(err: NotAuthenticated) -> bool: # CI/pipelines/agents have no human to finish a browser sign-in; starting one # would bind a loopback port and block for up to two minutes. Surface the - # original NotAuthenticated (with its 'aai login' / ASSEMBLYAI_API_KEY + # original NotAuthenticated (with its 'assembly login' / ASSEMBLYAI_API_KEY # suggestion) instead. if not _interactive_session(): return False diff --git a/aai_cli/errors.py b/aai_cli/errors.py index 9a251acc..0fc12225 100644 --- a/aai_cli/errors.py +++ b/aai_cli/errors.py @@ -12,7 +12,7 @@ gh-style split keeps "you're not signed in" distinct from a usage error. * ``130`` — cancelled with Ctrl-C. -A subprocess the CLI shells out to (``aai deploy``/``dev``) propagates that +A subprocess the CLI shells out to (``assembly deploy``/``dev``) propagates that process's own exit code unchanged. Each :class:`CLIError` carries an ``error_type`` (the JSON ``error.type``) paired 1:1 with its ``exit_code``. """ @@ -58,7 +58,7 @@ def __init__( message: str = "You're not signed in.", *, suggestion: str | None = ( - "Run 'aai onboard' for guided setup, 'aai login' if you have an account, " + "Run 'assembly onboard' for guided setup, 'assembly login' if you have an account, " "or set ASSEMBLYAI_API_KEY." ), rejected_key: bool = False, @@ -108,7 +108,7 @@ def __init__(self, message: str, *, suggestion: str | None = None) -> None: ) REJECTED_KEY_MESSAGE = "Your API key was rejected." -REJECTED_KEY_SUGGESTION = "Run 'aai login' with a valid key, or set ASSEMBLYAI_API_KEY." +REJECTED_KEY_SUGGESTION = "Run 'assembly login' with a valid key, or set ASSEMBLYAI_API_KEY." _VOICE_AGENT_POLICY_VIOLATION_CODE = 1008 diff --git a/aai_cli/follow.py b/aai_cli/follow.py index c843899e..9ac95430 100644 --- a/aai_cli/follow.py +++ b/aai_cli/follow.py @@ -14,7 +14,7 @@ class FollowRenderer: human watches one evolving summary. When piped or run by an agent (json_mode), each refresh is emitted as one NDJSON object so it stays machine-readable. - Shared by `aai llm --follow` (turns piped on stdin) and `aai stream --llm` + Shared by `assembly llm --follow` (turns piped on stdin) and `assembly stream --llm` (turns produced live by the streaming session). """ @@ -26,7 +26,7 @@ def __init__(self, *, json_mode: bool) -> None: def __enter__(self) -> FollowRenderer: if not self.json_mode: # screen=True draws into the terminal's alternate buffer (like less/htop). - # In the `aai stream -o text | aai llm -f` pipeline two processes share one + # In the `assembly stream -o text | assembly llm -f` pipeline two processes share one # TTY: stream writes status to stderr and the Ctrl-C "^C" echoes into our # region, desyncing Rich's relative-cursor teardown and duplicating the top # border. The alt buffer is isolated and restored verbatim on exit, so that diff --git a/aai_cli/help_panels.py b/aai_cli/help_panels.py index 0f5faaf9..c9a746e9 100644 --- a/aai_cli/help_panels.py +++ b/aai_cli/help_panels.py @@ -1,4 +1,4 @@ -"""Named help panels for `aai --help`. +"""Named help panels for `assembly --help`. Rich groups top-level commands under these headings (via each command's ``rich_help_panel``), so the root help reads as a journey rather than a flat diff --git a/aai_cli/init/devserver.py b/aai_cli/init/devserver.py index fd630a7a..13a01546 100644 --- a/aai_cli/init/devserver.py +++ b/aai_cli/init/devserver.py @@ -23,7 +23,7 @@ def install_step(target: Path, *, no_install: bool, use_uv: bool) -> steps.Step: # Local dev binds the loopback interface only. The template Procfile says # `--host 0.0.0.0` — correct for the deploy targets (Railway/Fly route traffic into -# the container) but wrong for `aai dev`/`aai share`: the .env beside it holds a real +# the container) but wrong for `assembly dev`/`assembly share`: the .env beside it holds a real # API key, so the dev server must not listen on every interface of the machine. LOCAL_HOST = "127.0.0.1" diff --git a/aai_cli/init/keys.py b/aai_cli/init/keys.py index 671e12bd..bc3df2bc 100644 --- a/aai_cli/init/keys.py +++ b/aai_cli/init/keys.py @@ -6,7 +6,7 @@ def resolve_optional_api_key(*, profile: str | None) -> str | None: """The CLI's key chain (env -> keyring), but None instead of raising when absent. - `aai init` scaffolds even without a key (writing a placeholder), so it must not + `assembly init` scaffolds even without a key (writing a placeholder), so it must not fail the way run commands do. """ return config.resolve_api_key_optional(profile=profile) diff --git a/aai_cli/init/procfile.py b/aai_cli/init/procfile.py index f37bb6c1..122d98b2 100644 --- a/aai_cli/init/procfile.py +++ b/aai_cli/init/procfile.py @@ -28,14 +28,14 @@ def repl(match: re.Match[str]) -> str: def require_procfile(target: Path) -> Path: """The project's Procfile path, or the standard not-a-project usage error. - This is how `aai dev`/`aai share`/`aai deploy` all detect they aren't sitting + This is how `assembly dev`/`assembly share`/`assembly deploy` all detect they aren't sitting inside a scaffolded project, so they fail with the same message. """ procfile = target / "Procfile" if not procfile.exists(): raise CLIError( "No Procfile here (expected ./Procfile). cd into a project created by " - "`aai init`, or run `aai init` to scaffold one.", + "`assembly init`, or run `assembly init` to scaffold one.", error_type="usage_error", exit_code=1, ) @@ -46,7 +46,7 @@ def web_argv(target: Path, *, env: Mapping[str, str]) -> list[str]: """The template Procfile's `web:` process, as an expanded argv. Raises a usage `CLIError` when there's no Procfile or no `web:` line — that's how - `aai dev` detects it isn't sitting inside a scaffolded project. + `assembly dev` detects it isn't sitting inside a scaffolded project. """ procfile = require_procfile(target) for line in procfile.read_text().splitlines(): diff --git a/aai_cli/init/scaffold.py b/aai_cli/init/scaffold.py index b39d1a80..3e28b0fa 100644 --- a/aai_cli/init/scaffold.py +++ b/aai_cli/init/scaffold.py @@ -100,7 +100,7 @@ def scaffold( # only (0600) instead of the umask default (commonly 0644) — otherwise the key # would be world/group-readable on a shared host. Open with the 0600 mode so the # secret is never briefly world-readable; the explicit chmod then also tightens an - # existing file when `aai init --force` overwrites one (O_CREAT's mode is ignored + # existing file when `assembly init --force` overwrites one (O_CREAT's mode is ignored # for a file that already exists). fd = os.open(env_path, os.O_WRONLY | os.O_CREAT | os.O_TRUNC, stat.S_IRUSR | stat.S_IWUSR) with os.fdopen(fd, "w") as fh: diff --git a/aai_cli/init/templates/audio-transcription/AGENTS.md b/aai_cli/init/templates/audio-transcription/AGENTS.md index b199bffe..dd802252 100644 --- a/aai_cli/init/templates/audio-transcription/AGENTS.md +++ b/aai_cli/init/templates/audio-transcription/AGENTS.md @@ -3,7 +3,7 @@ This is a buildless FastAPI + static HTML starter. Run it with: ```sh -aai dev +assembly dev ``` ## Map diff --git a/aai_cli/init/templates/audio-transcription/README.md b/aai_cli/init/templates/audio-transcription/README.md index 628d5e40..588dfaf4 100644 --- a/aai_cli/init/templates/audio-transcription/README.md +++ b/aai_cli/init/templates/audio-transcription/README.md @@ -7,10 +7,10 @@ highlights. Built with FastAPI + static HTML/CSS/JS. There is no frontend build ## Run locally ```sh -aai dev # installs deps if needed, starts the server, opens http://localhost:3000 +assembly dev # installs deps if needed, starts the server, opens http://localhost:3000 ``` -`ASSEMBLYAI_API_KEY` is read from `.env` (already created for you if you ran `aai init`). +`ASSEMBLYAI_API_KEY` is read from `.env` (already created for you if you ran `assembly init`). ## Deploy to Vercel @@ -33,7 +33,7 @@ uvicorn api.index:app --host 0.0.0.0 --port $PORT On Render, create a **Web Service** connected to your Git repo — it installs `requirements.txt` and starts via the `Procfile`. (There's no local-directory -deploy; `aai deploy` covers Vercel/Railway/Fly.) +deploy; `assembly deploy` covers Vercel/Railway/Fly.) ## Ideas to extend diff --git a/aai_cli/init/templates/audio-transcription/api/index.py b/aai_cli/init/templates/audio-transcription/api/index.py index 62f0e3b8..6ab7dbb5 100644 --- a/aai_cli/init/templates/audio-transcription/api/index.py +++ b/aai_cli/init/templates/audio-transcription/api/index.py @@ -28,7 +28,7 @@ from api import settings aai.settings.api_key = settings.API_KEY -# Target the same AssemblyAI environment the key was minted for. `aai init` writes +# Target the same AssemblyAI environment the key was minted for. `assembly init` writes # this for you; leave it unset to use the production default. if settings.ASSEMBLYAI_BASE_URL: aai.settings.base_url = settings.ASSEMBLYAI_BASE_URL diff --git a/aai_cli/init/templates/audio-transcription/env.example b/aai_cli/init/templates/audio-transcription/env.example index c989b993..db01ca96 100644 --- a/aai_cli/init/templates/audio-transcription/env.example +++ b/aai_cli/init/templates/audio-transcription/env.example @@ -1,4 +1,4 @@ ASSEMBLYAI_API_KEY=your_assemblyai_api_key_here -# Optional — target a non-production AssemblyAI environment (aai init fills these in): +# Optional — target a non-production AssemblyAI environment (assembly init fills these in): # ASSEMBLYAI_BASE_URL=https://api.assemblyai.com # ASSEMBLYAI_LLM_GATEWAY_URL=https://llm-gateway.assemblyai.com/v1 diff --git a/aai_cli/init/templates/live-captions/AGENTS.md b/aai_cli/init/templates/live-captions/AGENTS.md index 8415db15..7b4cfaed 100644 --- a/aai_cli/init/templates/live-captions/AGENTS.md +++ b/aai_cli/init/templates/live-captions/AGENTS.md @@ -3,7 +3,7 @@ This is a buildless FastAPI + browser microphone starter. Run it with: ```sh -aai dev +assembly dev ``` ## Map diff --git a/aai_cli/init/templates/live-captions/README.md b/aai_cli/init/templates/live-captions/README.md index 52336b0b..974a6706 100644 --- a/aai_cli/init/templates/live-captions/README.md +++ b/aai_cli/init/templates/live-captions/README.md @@ -9,10 +9,10 @@ HTML/CSS/JS with no frontend build step. ## Run locally ```sh -aai dev # opens http://localhost:3000 (allow microphone access) +assembly dev # opens http://localhost:3000 (allow microphone access) ``` -`ASSEMBLYAI_API_KEY` is read from `.env` (created for you if you ran `aai init`). +`ASSEMBLYAI_API_KEY` is read from `.env` (created for you if you ran `assembly init`). ## Deploy to Vercel @@ -36,7 +36,7 @@ uvicorn api.index:app --host 0.0.0.0 --port $PORT On Render, create a **Web Service** connected to your Git repo — it installs `requirements.txt` and starts via the `Procfile`. (There's no local-directory -deploy; `aai deploy` covers Vercel/Railway/Fly.) +deploy; `assembly deploy` covers Vercel/Railway/Fly.) ## Ideas to extend diff --git a/aai_cli/init/templates/live-captions/api/settings.py b/aai_cli/init/templates/live-captions/api/settings.py index 372c506e..a0b61862 100644 --- a/aai_cli/init/templates/live-captions/api/settings.py +++ b/aai_cli/init/templates/live-captions/api/settings.py @@ -7,7 +7,7 @@ load_dotenv() API_KEY = os.environ.get("ASSEMBLYAI_API_KEY", "") -# `aai init` writes this for you; defaults to production. Host only, no scheme. +# `assembly init` writes this for you; defaults to production. Host only, no scheme. STREAMING_HOST = os.environ.get("ASSEMBLYAI_STREAMING_HOST", "streaming.assemblyai.com") TOKEN_EXPIRES_IN_SECONDS = 60 TOKEN_PATH = "/v3/token" diff --git a/aai_cli/init/templates/live-captions/env.example b/aai_cli/init/templates/live-captions/env.example index ac8149ae..4fd73ac0 100644 --- a/aai_cli/init/templates/live-captions/env.example +++ b/aai_cli/init/templates/live-captions/env.example @@ -1,3 +1,3 @@ ASSEMBLYAI_API_KEY=your_assemblyai_api_key_here -# Optional — target a non-production AssemblyAI environment (aai init fills this in): +# Optional — target a non-production AssemblyAI environment (assembly init fills this in): # ASSEMBLYAI_STREAMING_HOST=streaming.assemblyai.com diff --git a/aai_cli/init/templates/voice-agent/AGENTS.md b/aai_cli/init/templates/voice-agent/AGENTS.md index 817fac1b..374fd343 100644 --- a/aai_cli/init/templates/voice-agent/AGENTS.md +++ b/aai_cli/init/templates/voice-agent/AGENTS.md @@ -3,7 +3,7 @@ This is a buildless FastAPI + browser voice-agent starter. Run it with: ```sh -aai dev +assembly dev ``` ## Map diff --git a/aai_cli/init/templates/voice-agent/README.md b/aai_cli/init/templates/voice-agent/README.md index d05141bf..bd3689af 100644 --- a/aai_cli/init/templates/voice-agent/README.md +++ b/aai_cli/init/templates/voice-agent/README.md @@ -9,10 +9,10 @@ static HTML/CSS/JS with no frontend build step. ## Run locally ```sh -aai dev # opens http://localhost:3000 (allow microphone access; headphones recommended) +assembly dev # opens http://localhost:3000 (allow microphone access; headphones recommended) ``` -`ASSEMBLYAI_API_KEY` is read from `.env` (created for you if you ran `aai init`). +`ASSEMBLYAI_API_KEY` is read from `.env` (created for you if you ran `assembly init`). The Voice Agent API requires a plan with access enabled. ## Deploy to Vercel @@ -37,7 +37,7 @@ uvicorn api.index:app --host 0.0.0.0 --port $PORT On Render, create a **Web Service** connected to your Git repo — it installs `requirements.txt` and starts via the `Procfile`. (There's no local-directory -deploy; `aai deploy` covers Vercel/Railway/Fly.) +deploy; `assembly deploy` covers Vercel/Railway/Fly.) ## Ideas to extend diff --git a/aai_cli/init/templates/voice-agent/api/settings.py b/aai_cli/init/templates/voice-agent/api/settings.py index 5bce5990..5a65c9f8 100644 --- a/aai_cli/init/templates/voice-agent/api/settings.py +++ b/aai_cli/init/templates/voice-agent/api/settings.py @@ -7,7 +7,7 @@ load_dotenv() API_KEY = os.environ.get("ASSEMBLYAI_API_KEY", "") -# `aai init` writes this for you; defaults to production. Host only, no scheme. +# `assembly init` writes this for you; defaults to production. Host only, no scheme. AGENTS_HOST = os.environ.get("ASSEMBLYAI_AGENTS_HOST", "agents.assemblyai.com") TOKEN_EXPIRES_IN_SECONDS = 60 TOKEN_PATH = "/v1/token" diff --git a/aai_cli/init/templates/voice-agent/env.example b/aai_cli/init/templates/voice-agent/env.example index 1a4621d3..e07f9a70 100644 --- a/aai_cli/init/templates/voice-agent/env.example +++ b/aai_cli/init/templates/voice-agent/env.example @@ -1,3 +1,3 @@ ASSEMBLYAI_API_KEY=your_assemblyai_api_key_here -# Optional — target a non-production AssemblyAI environment (aai init fills this in): +# Optional — target a non-production AssemblyAI environment (assembly init fills this in): # ASSEMBLYAI_AGENTS_HOST=agents.assemblyai.com diff --git a/aai_cli/llm.py b/aai_cli/llm.py index 02d545f4..40156b82 100644 --- a/aai_cli/llm.py +++ b/aai_cli/llm.py @@ -18,7 +18,7 @@ # is supplied. Must be exactly "{{ transcript }}" (spaces included). TRANSCRIPT_TAG = "{{ transcript }}" -# A curated subset for `aai llm --list-models` and help text. The gateway is the +# A curated subset for `assembly llm --list-models` and help text. The gateway is the # source of truth for what's actually accepted, so we don't validate against this. KNOWN_MODELS = ( "claude-opus-4-7", @@ -101,7 +101,7 @@ def complete( except (openai.AuthenticationError, openai.PermissionDeniedError) as exc: # The gateway returns 401/403 for both an invalid key and a plan # entitlement block ("no access to LLM Gateway"), so surface its actual - # message rather than a generic "run aai login" that misleads unpaid + # message rather than a generic "run assembly login" that misleads unpaid # accounts (the key is fine; the feature requires a paid plan). raise APIError( f"LLM Gateway access denied: {exc}", diff --git a/aai_cli/main.py b/aai_cli/main.py index 0ff37b90..3f8269bb 100644 --- a/aai_cli/main.py +++ b/aai_cli/main.py @@ -45,7 +45,7 @@ from aai_cli.onboard import wizard from aai_cli.onboard.sections import WizardContext -# The order commands appear under `aai --help`. Commands are grouped into named +# The order commands appear under `assembly --help`. Commands are grouped into named # Rich panels (see `help_panels.py`); panels render in the order their first # command appears here, so keep each panel's commands contiguous and ordered # most-common-first. Names not listed fall to the end, sorted alphabetically. @@ -113,7 +113,7 @@ def parse_args(self, ctx: ClickContext, args: list[str]) -> list[str]: rich_utils.STYLE_COMMANDS_TABLE_FIRST_COLUMN = f"bold {theme.BRAND}" rich_utils.STYLE_SWITCH = f"bold {theme.BRAND}" rich_utils.STYLE_METAVAR = theme.ACCENT -# The usage line ("Usage: aai [OPTIONS] COMMAND [ARGS]...") defaults to yellow. Keep the +# The usage line ("Usage: assembly [OPTIONS] COMMAND [ARGS]...") defaults to yellow. Keep the # program name in the bold brand accent so it matches command names elsewhere, but drop # the "Usage:" label and arg spec to muted warm gray — it's boilerplate that should recede. rich_utils.STYLE_USAGE = theme.MUTED @@ -145,7 +145,7 @@ def _patch_module(module: ModuleType, **attrs: object) -> None: # Typer's own help/error consoles must also honor the closed-pipe contract: with -# Rich's default Console, `aai --help | head -2` exits 1 via Console.on_broken_pipe. +# Rich's default Console, `assembly --help | head -2` exits 1 via Console.on_broken_pipe. _patch_module(rich_utils, Table=_NoClipTable, Console=theme.PipeSafeConsole) _format_click_error = rich_utils.rich_format_error @@ -174,10 +174,10 @@ def _format_click_error_fixed(self: ClickException) -> None: app = typer.Typer( - name="aai", - # No top-level `help=`: the bare-`aai` welcome banner already carries the + name="assembly", + # No top-level `help=`: the bare-`assembly` welcome banner already carries the # "AssemblyAI from your terminal" tagline, so a description here would duplicate it. - # `aai --install-completion` / `--show-completion` for bash/zsh/fish/PowerShell, + # `assembly --install-completion` / `--show-completion` for bash/zsh/fish/PowerShell, # the discoverability affordance gh/kubectl/docker users reach for. add_completion=True, cls=_OrderedGroup, @@ -185,7 +185,7 @@ def _format_click_error_fixed(self: ClickException) -> None: def _version_callback(value: bool) -> None: - """Print the version and exit when `aai --version`/`-V` is passed, before any command + """Print the version and exit when `assembly --version`/`-V` is passed, before any command runs. Mirrors the reflex (`tool --version`) every other CLI answers; the `version` subcommand stays for parity.""" if value: @@ -247,13 +247,13 @@ def _offer_or_help(ctx: typer.Context, state: AppState) -> None: invoke_without_command=True, epilog=examples_epilog( [ - ("Guided setup (start here)", "aai onboard"), - ("Transcribe a file", "aai transcribe call.mp3"), - ("Stream live audio in real time", "aai stream"), - ("Talk to a voice agent", "aai agent"), + ("Guided setup (start here)", "assembly onboard"), + ("Transcribe a file", "assembly transcribe call.mp3"), + ("Stream live audio in real time", "assembly stream"), + ("Talk to a voice agent", "assembly agent"), ( "Summarize while transcribing", - 'aai transcribe call.mp3 --llm "summarize action items"', + 'assembly transcribe call.mp3 --llm "summarize action items"', ), ] ), @@ -277,7 +277,7 @@ def main( help="Show the CLI version and exit.", callback=_version_callback, # Eager so --version short-circuits before subcommand/arg parsing — the idiomatic - # default. The plain `aai --version` path behaves identically with or without this, + # default. The plain `assembly --version` path behaves identically with or without this, # so there's no cheap test that distinguishes it. is_eager=True, # pragma: no mutate ), @@ -332,14 +332,14 @@ def main( def run() -> None: """Console-script entry point: run the app, exiting cleanly on a closed pipe. - A downstream consumer (e.g. `aai … | head`) can close the pipe before we finish + A downstream consumer (e.g. `assembly … | head`) can close the pipe before we finish writing. Without this, the write — or Python's flush at shutdown — raises BrokenPipeError and prints an ugly "Exception ignored" traceback. We treat a closed pipe as success: silence stdout and exit 0. Streaming commands also catch it earlier; this is the catch-all for the one-shot `output.emit`/`print` paths. """ try: - app(prog_name="aai") + app(prog_name="assembly") except BrokenPipeError: stdio.silence_stdout() sys.exit(0) diff --git a/aai_cli/microphone.py b/aai_cli/microphone.py index ba45ba9e..5b624a3d 100644 --- a/aai_cli/microphone.py +++ b/aai_cli/microphone.py @@ -118,7 +118,7 @@ def _default_mic_stream(*, sample_rate: int, device: int | None) -> Iterator[byt class MicrophoneSource: """Iterable of PCM16 chunks captured at the microphone's native rate. - Shared by `aai stream` (mic input) and `aai agent` (captured speech). The + Shared by `assembly stream` (mic input) and `assembly agent` (captured speech). The device is opened at its own sample rate to avoid forcing an unsupported one; with `target_rate` set (the voice agent needs 24 kHz) the captured audio is resampled to it, otherwise frames are yielded at the capture rate, which diff --git a/aai_cli/onboard/prompter.py b/aai_cli/onboard/prompter.py index 7e7ea1d8..24121340 100644 --- a/aai_cli/onboard/prompter.py +++ b/aai_cli/onboard/prompter.py @@ -90,6 +90,6 @@ def text(self, title: str, *, default: str | None = None) -> str: if default is None: raise UsageError( f"'{title}' needs a value, but this is a non-interactive session.", - suggestion="Re-run `aai onboard` in an interactive terminal.", + suggestion="Re-run `assembly onboard` in an interactive terminal.", ) return default diff --git a/aai_cli/onboard/sections.py b/aai_cli/onboard/sections.py index 3e4a78a7..69736202 100644 --- a/aai_cli/onboard/sections.py +++ b/aai_cli/onboard/sections.py @@ -49,7 +49,7 @@ def auth(prompter: Prompter, ctx: WizardContext) -> SectionResult: # can produce. Stop here with the actionable next step instead of hanging. prompter.note( "No API key found, and this is a non-interactive session — " - "browser sign-in can't complete here. Run `aai login` in a terminal, " + "browser sign-in can't complete here. Run `assembly login` in a terminal, " "or set ASSEMBLYAI_API_KEY." ) return SectionResult.FAILED @@ -114,7 +114,7 @@ def build_path(prompter: Prompter, ctx: WizardContext) -> SectionResult: if choice == "skip": return SectionResult.SKIPPED if not prompter.confirm(f"Scaffold the '{choice}' app now?", default=True): - prompter.note(f"You can run `aai init {choice}` whenever you're ready.") + prompter.note(f"You can run `assembly init {choice}` whenever you're ready.") return SectionResult.SKIPPED # launch=False: never block the wizard on a running dev server. try: @@ -153,7 +153,7 @@ def claude_code(prompter: Prompter, _ctx: WizardContext) -> SectionResult: def next_steps(prompter: Prompter, _ctx: WizardContext) -> SectionResult: prompter.section("You're set up") - output.console.print(output.hint("Transcribe a file: aai transcribe ")) - output.console.print(output.hint("Stream live audio: aai stream")) - output.console.print(output.hint("Build an app: aai init")) + output.console.print(output.hint("Transcribe a file: assembly transcribe ")) + output.console.print(output.hint("Stream live audio: assembly stream")) + output.console.print(output.hint("Build an app: assembly init")) return SectionResult.DONE diff --git a/aai_cli/onboard/wizard.py b/aai_cli/onboard/wizard.py index d1d4dd6b..d929bc07 100644 --- a/aai_cli/onboard/wizard.py +++ b/aai_cli/onboard/wizard.py @@ -17,7 +17,7 @@ def run_onboarding(prompter: Prompter, ctx: WizardContext) -> int: sections.welcome(prompter, ctx) if sections.auth(prompter, ctx) is SectionResult.FAILED: # The auth section already printed the specific next step (browser retry, - # or — non-interactively — `aai login`/ASSEMBLYAI_API_KEY), so keep this + # or — non-interactively — `assembly login`/ASSEMBLYAI_API_KEY), so keep this # terminal line neutral rather than implying a re-run always fixes it. output.error_console.print(output.fail("Sign-in didn't complete.")) return NotAuthenticated().exit_code @@ -27,7 +27,9 @@ def run_onboarding(prompter: Prompter, ctx: WizardContext) -> int: sections.claude_code(prompter, ctx) sections.next_steps(prompter, ctx) except WizardCancelled: - output.error_console.print(output.hint("Setup cancelled. Run `aai onboard` to resume.")) + output.error_console.print( + output.hint("Setup cancelled. Run `assembly onboard` to resume.") + ) return 130 else: return 0 diff --git a/aai_cli/output.py b/aai_cli/output.py index dd2f6573..c54fca11 100644 --- a/aai_cli/output.py +++ b/aai_cli/output.py @@ -19,7 +19,7 @@ from aai_cli.errors import CLIError console = theme.make_console() -# Errors go to stderr so they never pollute piped stdout (e.g. `aai transcribe x -o text > out`). +# Errors go to stderr so they never pollute piped stdout (e.g. `assembly transcribe x -o text > out`). error_console = theme.make_console(stderr=True) _AGENT_ENV_VARS = ("CI", "CLAUDECODE", "CLAUDE_CODE_ENTRYPOINT") @@ -46,7 +46,7 @@ def resolve_json(*, explicit: bool) -> bool: Human-readable text is the default for every command, in every context — a terminal, a pipe, CI, or an agent. We deliberately do NOT switch the output *shape* to JSON just because stdout is piped or a ``CI``/``CLAUDECODE`` env var - is set: that surprised plain-text pipelines like ``aai transcribe x | grep word`` + is set: that surprised plain-text pipelines like ``assembly transcribe x | grep word`` by handing them a JSON blob instead of the transcript. Being off a TTY still drops color and interactivity (Rich handles that automatically); it just no longer changes the structure. This matches gh/docker/kubectl, which keep their @@ -59,7 +59,7 @@ def stream_output_modes(field: choices.TextOrJson | None, *, json_mode: bool) -> """Fold a streaming command's ``-o/--output`` into ``(text_mode, json_mode)``. Shared by `stream` and `agent`. ``-o text`` emits plain finalized lines (handy for - ``aai stream -o text | aai llm -f``); ``-o json`` or ``--json`` forces NDJSON; an + ``assembly stream -o text | assembly llm -f``); ``-o json`` or ``--json`` forces NDJSON; an unset field renders the live human panel. With output now human-by-default (`resolve_json` only flips on an explicit `--json`), `json_mode` here is simply whether `--json` was passed — we never auto-switch to NDJSON just because piped. @@ -172,7 +172,7 @@ def status(message: str, *, json_mode: bool, quiet: bool = False) -> Generator[N A no-op in JSON or non-interactive mode (piped / agent-run), under ``--quiet``, so stdout stays clean for pipelines and machine output is never decorated. - Rendered on the stderr console so even an interactive `aai transcribe x -o text` + Rendered on the stderr console so even an interactive `assembly transcribe x -o text` keeps stdout pristine. """ if json_mode or quiet or is_agentic(): @@ -196,7 +196,7 @@ def emit_warning(message: str, *, json_mode: bool) -> None: def emit_error(err: CLIError, *, json_mode: bool) -> None: - # Always to stderr, so stdout stays clean for `aai … | next-tool` pipelines. + # Always to stderr, so stdout stays clean for `assembly … | next-tool` pipelines. if json_mode: print(json.dumps(err.to_dict(), default=str), file=sys.stderr) else: @@ -205,20 +205,20 @@ def emit_error(err: CLIError, *, json_mode: bool) -> None: error_console.print(f"[aai.muted]Suggestion:[/aai.muted] {escape(err.suggestion)}") -# The `aai` wordmark for the bare-command welcome screen: a compact two-row +# The `assembly` wordmark for the bare-command welcome screen: a compact two-row # half-block letterform, tinted to the brand accent (see theme.BRAND). Interior # spaces are load-bearing (they separate the glyphs); trailing spaces are not, so # they're dropped to survive whitespace-trimming tooling. _BANNER = """\ -▄▀█ ▄▀█ █ -█▀█ █▀█ █""" +▄▀█ █▀ █▀ █▀▀ █▀▄▀█ █▄▄ █ █▄█ +█▀█ ▄█ ▄█ ██▄ █ ▀ █ █▄█ █▄▄ █""" # A one-line header: emoji + product + version, then the product tagline. _TAGLINE = "AssemblyAI from your terminal" def print_banner() -> None: - """Print the welcome header — a version + tagline line, then the `aai` wordmark in + """Print the welcome header — a version + tagline line, then the `assembly` wordmark in the brand accent (the bare-command welcome screen).""" # highlight=False so Rich's repr-highlighter doesn't recolor the version digits or # the quoted tagline — the line stays a single muted tone behind the brand label. @@ -234,7 +234,7 @@ def print_banner() -> None: def print_code(code: str, *, language: str = "python") -> None: """Print generated source: syntax-highlighted for an interactive human, raw text otherwise. Piping/redirecting (or an agent) yields plain text with no ANSI, so - `aai … --show-code > script.py` stays byte-clean and runnable. + `assembly … --show-code > script.py` stays byte-clean and runnable. """ if not _stdout_is_tty(): print(code) diff --git a/aai_cli/skills/aai-cli/SKILL.md b/aai_cli/skills/aai-cli/SKILL.md index d8339c87..f3f93cd9 100644 --- a/aai_cli/skills/aai-cli/SKILL.md +++ b/aai_cli/skills/aai-cli/SKILL.md @@ -1,14 +1,14 @@ --- name: aai-cli -description: Use the AssemblyAI CLI (`aai`) from the command line — transcribe audio/video files, URLs, and YouTube/podcast links; stream live real-time transcription from a mic/file/system audio; run full-duplex voice agents; query the LLM Gateway over transcripts; browse transcript and streaming-session history; sign in and manage account balance, usage, rate limits, API keys, and audit logs; scaffold a starter app (init); diagnose setup (doctor); and set up your coding agent's AssemblyAI docs MCP + skills (setup). Use whenever an agent is invoking the `aai` command. +description: Use the AssemblyAI CLI (`assembly`) from the command line — transcribe audio/video files, URLs, and YouTube/podcast links; stream live real-time transcription from a mic/file/system audio; run full-duplex voice agents; query the LLM Gateway over transcripts; browse transcript and streaming-session history; sign in and manage account balance, usage, rate limits, API keys, and audit logs; scaffold a starter app (init); diagnose setup (doctor); and set up your coding agent's AssemblyAI docs MCP + skills (setup). Use whenever an agent is invoking the `assembly` command. --- -# AssemblyAI CLI (`aai`) +# AssemblyAI CLI (`assembly`) -`aai` runs AssemblyAI from the terminal: transcription, real-time streaming, +`assembly` runs AssemblyAI from the terminal: transcription, real-time streaming, voice agents, the LLM Gateway, history, and account management. -**`aai --help` is the source of truth for flags.** This skill covers +**`assembly --help` is the source of truth for flags.** This skill covers the command map and the non-obvious operational rules; check `--help` before guessing a flag. @@ -17,9 +17,9 @@ guessing a flag. **Authentication.** A command needs a key resolved in this order: 1. `ASSEMBLYAI_API_KEY` environment variable -2. The OS keyring (populated by `aai login`) +2. The OS keyring (populated by `assembly login`) -Get authenticated with either `aai login` (browser sign-in; stores a key in the +Get authenticated with either `assembly login` (browser sign-in; stores a key in the keyring) or by exporting `ASSEMBLYAI_API_KEY`. **Run commands deliberately have no `--api-key` flag** — that is on purpose, so keys never land in `ps` output or shell history. Do not look for one. @@ -34,7 +34,7 @@ under. **Profiles.** `--profile ` selects a named credential set. Global flags (`--profile`, `--env`, `--sandbox`) go *before* the subcommand: -`aai --sandbox transcribe call.mp3`. +`assembly --sandbox transcribe call.mp3`. ## Output contract (read this before parsing output) @@ -50,28 +50,28 @@ under. ## Quick start ```bash -aai login # browser sign-in (or: export ASSEMBLYAI_API_KEY=...) -aai doctor # verify the environment is ready -aai transcribe call.mp3 # transcribe a file -aai transcribe call.mp3 -o text # just the text, pipeline-friendly -aai stream # live transcription from the mic -aai init # scaffold a starter app +assembly login # browser sign-in (or: export ASSEMBLYAI_API_KEY=...) +assembly doctor # verify the environment is ready +assembly transcribe call.mp3 # transcribe a file +assembly transcribe call.mp3 -o text # just the text, pipeline-friendly +assembly stream # live transcription from the mic +assembly init # scaffold a starter app ``` ## Building an app vs running a command If the task is to **build/create an app or project** (a transcription app, live -captions, or a **voice agent app**), that is `aai init` — a scaffolder that +captions, or a **voice agent app**), that is `assembly init` — a scaffolder that writes a full starter project (pick the `voice-agent` template for an agent -app). The verbs `aai transcribe`, `aai stream`, and **`aai agent`** are *run* -commands: they perform a one-off action in the terminal (e.g. `aai agent` holds +app). The verbs `assembly transcribe`, `assembly stream`, and **`assembly agent`** are *run* +commands: they perform a one-off action in the terminal (e.g. `assembly agent` holds a live mic conversation) and produce **no code**. When someone says "build an -agent," reach for `aai init voice-agent`, not `aai agent`. +agent," reach for `assembly init voice-agent`, not `assembly agent`. ## Decision tree - **Build/scaffold an app (transcription, live captions, or a voice agent app)** - → `aai init` — see `references/setup.md` + → `assembly init` — see `references/setup.md` - **Transcribe a file/URL/YouTube/podcast page, stream live audio, run a live voice agent, or query the LLM Gateway** → `references/transcription.md` - **Browse past transcripts or streaming sessions** → `references/history.md` @@ -82,11 +82,11 @@ agent," reach for `aai init voice-agent`, not `aai agent`. ## Anti-patterns -- **Passing `--api-key` to a run command.** It does not exist. Use `aai login` +- **Passing `--api-key` to a run command.** It does not exist. Use `assembly login` or `ASSEMBLYAI_API_KEY`. - **Mixing a credential with the wrong `--env`.** A `sandbox000` key won't work against production. Log in and run under the same environment. -- **Running before authenticating.** No key → auth failure. Run `aai doctor` to +- **Running before authenticating.** No key → auth failure. Run `assembly doctor` to see exactly what's missing. - **Assuming `pip install assemblyai-cli` works.** That PyPI name is squatted by an unrelated third party. Use the project's official install path, not that diff --git a/aai_cli/skills/aai-cli/references/account.md b/aai_cli/skills/aai-cli/references/account.md index 5b138665..84b46d5f 100644 --- a/aai_cli/skills/aai-cli/references/account.md +++ b/aai_cli/skills/aai-cli/references/account.md @@ -4,7 +4,7 @@ Commands for authentication and read-only account queries. Auth rules and environment precedence live in `SKILL.md`; the notes below are per-command. All commands accept `--json`. -## `aai login` — authenticate +## `assembly login` — authenticate Opens a browser OAuth flow and stores the resulting CLI API key in the OS keyring, bound to the active `--env`; pass `--api-key` to authenticate @@ -18,11 +18,11 @@ Key options: Examples: ```bash -aai login -aai login --api-key sk_... +assembly login +assembly login --api-key sk_... ``` -## `aai logout` — clear stored credentials +## `assembly logout` — clear stored credentials Removes stored credentials for the active profile from the OS keyring. @@ -33,10 +33,10 @@ Key options: Examples: ```bash -aai logout +assembly logout ``` -## `aai whoami` — show active profile +## `assembly whoami` — show active profile Reports the active profile name and whether its stored key is currently usable (i.e. validates against the active environment). @@ -48,10 +48,10 @@ Key options: Examples: ```bash -aai whoami +assembly whoami ``` -## `aai balance` — account balance +## `assembly balance` — account balance Read-only query that shows your remaining account balance. @@ -62,10 +62,10 @@ Key options: Examples: ```bash -aai balance +assembly balance ``` -## `aai usage` — usage over a date range +## `assembly usage` — usage over a date range Read-only query that shows API usage; defaults to the last 30 days. @@ -80,11 +80,11 @@ Key options: Examples: ```bash -aai usage -aai usage --start 2026-05-01 --end 2026-06-01 +assembly usage +assembly usage --start 2026-05-01 --end 2026-06-01 ``` -## `aai limits` — rate limits +## `assembly limits` — rate limits Read-only query that shows your account's rate limits per service. @@ -95,15 +95,15 @@ Key options: Examples: ```bash -aai limits +assembly limits ``` -## `aai keys` — manage API keys +## `assembly keys` — manage API keys Sub-app for listing, creating, and renaming AssemblyAI API keys; keys are shown masked in list output. -### `aai keys list` +### `assembly keys list` List all API keys across your projects (values masked). @@ -114,11 +114,11 @@ Key options: Examples: ```bash -aai keys list -aai keys list --json +assembly keys list +assembly keys list --json ``` -### `aai keys create` +### `assembly keys create` Create a new API key; the full key value is printed once — copy it immediately. @@ -132,13 +132,13 @@ Key options: Examples: ```bash -aai keys create --name ci-pipeline -aai keys create --name prod --project 7 +assembly keys create --name ci-pipeline +assembly keys create --name prod --project 7 ``` -### `aai keys rename TOKEN_ID NEW_NAME` +### `assembly keys rename TOKEN_ID NEW_NAME` -Rename (relabel) an existing API key; use `aai keys list` to find the key id. +Rename (relabel) an existing API key; use `assembly keys list` to find the key id. Key options: @@ -147,10 +147,10 @@ Key options: Examples: ```bash -aai keys rename 123 "prod" +assembly keys rename 123 "prod" ``` -## `aai audit` — audit log +## `assembly audit` — audit log Read-only query that lists recent audit-log entries for the account; login events are omitted by default. @@ -166,7 +166,7 @@ Key options: Examples: ```bash -aai audit --limit 20 -aai audit --include-logins -aai audit --action token.create +assembly audit --limit 20 +assembly audit --include-logins +assembly audit --action token.create ``` diff --git a/aai_cli/skills/aai-cli/references/history.md b/aai_cli/skills/aai-cli/references/history.md index 7af1135e..3ca84a71 100644 --- a/aai_cli/skills/aai-cli/references/history.md +++ b/aai_cli/skills/aai-cli/references/history.md @@ -2,14 +2,14 @@ Two sub-apps for browsing past work. All output supports `--json` (auto-enabled when piped) so an agent can parse listings and retrieve records by id. To run a -prompt over a past transcript's text, use `aai llm --transcript-id ID` (see +prompt over a past transcript's text, use `assembly llm --transcript-id ID` (see `transcription.md`). -## `aai transcripts` — browse and fetch past transcripts +## `assembly transcripts` — browse and fetch past transcripts Sub-app with two subcommands: `list` and `get`. -### `aai transcripts list` +### `assembly transcripts list` List the most recent batch transcription jobs for the active account. @@ -21,12 +21,12 @@ Key options: Examples: ```bash -aai transcripts list -aai transcripts list --limit 50 -aai transcripts list --json | jq '.[].id' +assembly transcripts list +assembly transcripts list --limit 50 +assembly transcripts list --json | jq '.[].id' ``` -### `aai transcripts get TRANSCRIPT_ID` +### `assembly transcripts get TRANSCRIPT_ID` Fetch a past transcript by id and print its text. @@ -39,17 +39,17 @@ Key options: Examples: ```bash -aai transcripts get 5551234-abcd -aai transcripts get 5551234-abcd --json -aai transcripts get 5551234-abcd -o text +assembly transcripts get 5551234-abcd +assembly transcripts get 5551234-abcd --json +assembly transcripts get 5551234-abcd -o text ``` -## `aai sessions` — browse past real-time streaming sessions +## `assembly sessions` — browse past real-time streaming sessions Sub-app for the v3 real-time API session history, with two subcommands: `list` and `get`. -### `aai sessions list` +### `assembly sessions list` List the most recent streaming sessions for the active account. @@ -62,12 +62,12 @@ Key options: Examples: ```bash -aai sessions list -aai sessions list --status completed -aai sessions list --limit 25 --json +assembly sessions list +assembly sessions list --status completed +assembly sessions list --limit 25 --json ``` -### `aai sessions get SESSION_ID` +### `assembly sessions get SESSION_ID` Show details for a single streaming session by id. @@ -78,6 +78,6 @@ Key options: Examples: ```bash -aai sessions get -aai sessions get --json +assembly sessions get +assembly sessions get --json ``` diff --git a/aai_cli/skills/aai-cli/references/setup.md b/aai_cli/skills/aai-cli/references/setup.md index 57e16c34..fa24f69e 100644 --- a/aai_cli/skills/aai-cli/references/setup.md +++ b/aai_cli/skills/aai-cli/references/setup.md @@ -3,11 +3,11 @@ Commands for scaffolding projects, validating the environment, and setting up your coding agent (docs MCP + skills) for AssemblyAI. -## `aai init [TEMPLATE] [DIRECTORY]` — scaffold a starter app +## `assembly init [TEMPLATE] [DIRECTORY]` — scaffold a starter app **This is how you build a new app.** When the goal is to create an application or project — including a voice-agent app — start here, not with the -`aai agent` / `aai transcribe` / `aai stream` run commands (those just *run* a +`assembly agent` / `assembly transcribe` / `assembly stream` run commands (those just *run* a one-off action in the terminal and produce no code). Picks a template, scaffolds it into a directory, optionally installs @@ -15,8 +15,8 @@ dependencies, starts the local server, and opens the browser. Available templates: `audio-transcription`, `live-captions`, `voice-agent`. The API key is written to a git-ignored `.env` file in the scaffolded directory. -To build a **voice agent app**, use `aai init voice-agent` (a full -FastAPI + browser starter) — `aai agent` only runs a live mic conversation and +To build a **voice agent app**, use `assembly init voice-agent` (a full +FastAPI + browser starter) — `assembly agent` only runs a live mic conversation and writes no code. Key options: @@ -31,12 +31,12 @@ Key options: Examples: ```bash -aai init -aai init audio-transcription my-app -aai init audio-transcription --here +assembly init +assembly init audio-transcription my-app +assembly init audio-transcription --here ``` -## `aai doctor` — environment health check +## `assembly doctor` — environment health check Verifies that your environment is ready to use AssemblyAI (checks credentials, network reachability, and runtime dependencies). @@ -48,10 +48,10 @@ Key options: Examples: ```bash -aai doctor +assembly doctor ``` -## `aai setup` — set up your coding agent for AssemblyAI +## `assembly setup` — set up your coding agent for AssemblyAI Sub-app that wires three things into your coding agent: the `assemblyai-docs` MCP server (via `claude mcp add`), the `assemblyai` skill (downloaded with @@ -60,7 +60,7 @@ package and copied in directly, no network needed). Missing `claude` or `npx` is reported and skipped, not treated as an error; the bundled `aai-cli` skill installs regardless. -### `aai setup install` +### `assembly setup install` Install the docs MCP server and both skills into your coding agent. @@ -74,11 +74,11 @@ Key options: Examples: ```bash -aai setup install -aai setup install --scope project +assembly setup install +assembly setup install --scope project ``` -### `aai setup status` +### `assembly setup status` Show whether the MCP server and both skills are currently set up. @@ -89,10 +89,10 @@ Key options: Examples: ```bash -aai setup status +assembly setup status ``` -### `aai setup remove` +### `assembly setup remove` Remove the MCP server and both skills from your coding agent. @@ -105,15 +105,15 @@ Key options: Examples: ```bash -aai setup remove +assembly setup remove ``` -## `aai --version` — show CLI version +## `assembly --version` — show CLI version -Prints the installed `aai` version string and exits. +Prints the installed `assembly` version string and exits. Examples: ```bash -aai --version +assembly --version ``` diff --git a/aai_cli/skills/aai-cli/references/transcription.md b/aai_cli/skills/aai-cli/references/transcription.md index 59ae6bba..12ef2994 100644 --- a/aai_cli/skills/aai-cli/references/transcription.md +++ b/aai_cli/skills/aai-cli/references/transcription.md @@ -4,14 +4,14 @@ Four commands. All accept `--json` (auto-enabled when piped) and `-o/--output` to print a single field. `transcribe`, `stream`, and `agent` accept `--show-code` to print equivalent Python SDK code without calling the API. -## `aai transcribe [SOURCE]` — file / URL / YouTube / podcast page +## `assembly transcribe [SOURCE]` — file / URL / YouTube / podcast page `SOURCE` is a local file path, public URL, or a media-page URL yt-dlp can extract (YouTube, Apple Podcasts, Spreaker, SoundCloud, …) — those are downloaded first. Use `--sample` for the hosted `wildfires.mp3`. Analysis results (summary, chapters, sentiment, …) render automatically in human mode. -High-value flags (run `aai transcribe --help` for the full set): +High-value flags (run `assembly transcribe --help` for the full set): - Model/language: `--speech-model` (best, nano, slam-1, universal), `--language-code en_us`, `--language-detection`. @@ -30,20 +30,20 @@ High-value flags (run `aai transcribe --help` for the full set): Examples: ```bash -aai transcribe call.mp3 -aai transcribe --sample -aai transcribe call.mp3 --speaker-labels --speakers-expected 2 --redact-pii -aai transcribe call.mp3 -o text -aai transcribe call.mp3 --show-code +assembly transcribe call.mp3 +assembly transcribe --sample +assembly transcribe call.mp3 --speaker-labels --speakers-expected 2 --redact-pii +assembly transcribe call.mp3 -o text +assembly transcribe call.mp3 --show-code ``` -## `aai stream [SOURCE]` — live real-time transcription +## `assembly stream [SOURCE]` — live real-time transcription Omit `SOURCE` to use the microphone; pass a file/URL/media page to stream that, or `--sample`. macOS can capture system audio with `--system-audio` (mic + system) or `--system-audio-only`. -High-value flags (run `aai stream --help` for the full set): +High-value flags (run `assembly stream --help` for the full set): - Capture: `--device N`, `--sample-rate HZ`, `--encoding pcm_s16le|pcm_mulaw`. - Model/turns: `--speech-model` (default `u3-rt-pro`), `--format-turns`, @@ -58,21 +58,21 @@ High-value flags (run `aai stream --help` for the full set): Examples: ```bash -aai stream -aai stream --system-audio -aai stream --sample -aai stream --llm "summarize action items" -aai stream -o text # finalized turns as plain lines, pipe-friendly +assembly stream +assembly stream --system-audio +assembly stream --sample +assembly stream --llm "summarize action items" +assembly stream -o text # finalized turns as plain lines, pipe-friendly ``` -## `aai agent [SOURCE]` — full-duplex voice agent +## `assembly agent [SOURCE]` — full-duplex voice agent Two-way voice conversation (mic in, TTS out). Pass a file/URL or `--sample` to speak a recorded clip instead of the mic; the session then ends after the reply. -> **`aai agent` only *runs* a live conversation in the terminal — it does not +> **`assembly agent` only *runs* a live conversation in the terminal — it does not > create any code or project.** If the goal is to *build* a voice-agent app, -> use `aai init` with the `voice-agent` template (see `setup.md`), not this +> use `assembly init` with the `voice-agent` template (see `setup.md`), not this > command. High-value flags: @@ -84,13 +84,13 @@ High-value flags: Examples: ```bash -aai agent -aai agent --voice james --greeting "Hi there" -aai agent --list-voices -aai agent --show-code +assembly agent +assembly agent --voice james --greeting "Hi there" +assembly agent --list-voices +assembly agent --show-code ``` -## `aai llm [PROMPT]` — LLM Gateway +## `assembly llm [PROMPT]` — LLM Gateway Send a prompt to the LLM Gateway. With `--transcript-id ID` the transcript's text is injected server-side so you can ask questions about a past @@ -107,8 +107,8 @@ High-value flags: Examples: ```bash -aai llm "summarize" --transcript-id 5551234-abcd -echo "meeting notes" | aai llm "turn into action items" -aai stream -o text | aai llm -f "summarize action items as I talk" -aai llm --list-models +assembly llm "summarize" --transcript-id 5551234-abcd +echo "meeting notes" | assembly llm "turn into action items" +assembly stream -o text | assembly llm -f "summarize action items as I talk" +assembly llm --list-models ``` diff --git a/aai_cli/stdio.py b/aai_cli/stdio.py index 9ec1f65a..3d54552d 100644 --- a/aai_cli/stdio.py +++ b/aai_cli/stdio.py @@ -28,7 +28,7 @@ def iter_piped_stdin_lines() -> Iterator[str]: """Yield non-blank, stripped lines piped on stdin, live, as each one arrives. Unlike ``piped_stdin_text`` (which reads to EOF), this consumes the pipe - incrementally so a long-running upstream like ``aai stream -o text`` can drive + incrementally so a long-running upstream like ``assembly stream -o text`` can drive a downstream command turn by turn. Yields nothing when stdin is a terminal, so a ``--follow`` consumer used interactively returns instead of blocking forever. """ @@ -44,8 +44,8 @@ def iter_piped_stdin_lines() -> Iterator[str]: def piped_stdin_text() -> str | None: """Return text piped on stdin, or None when stdin is a terminal or empty. - Lets commands accept input from a pipe (e.g. ``cat notes.txt | aai llm ...`` or - ``aai transcribe x.mp3 -o text | aai llm "summarize"``) without blocking when run + Lets commands accept input from a pipe (e.g. ``cat notes.txt | assembly llm ...`` or + ``assembly transcribe x.mp3 -o text | assembly llm "summarize"``) without blocking when run interactively. """ stream = sys.stdin @@ -58,7 +58,7 @@ def piped_stdin_text() -> str | None: def read_binary_stdin() -> bytes: """Read all bytes piped on stdin, for a ``-`` audio source. - Used by ``cat call.wav | aai transcribe -`` and ``ffmpeg … | aai transcribe -``. + Used by ``cat call.wav | assembly transcribe -`` and ``ffmpeg … | assembly transcribe -``. """ buffer = getattr(sys.stdin, "buffer", None) if buffer is None: # e.g. a text-only stub in tests diff --git a/aai_cli/steps.py b/aai_cli/steps.py index 12b1c905..22207e8b 100644 --- a/aai_cli/steps.py +++ b/aai_cli/steps.py @@ -18,7 +18,7 @@ class Step(TypedDict): def render_steps(items: list[Step], *, heading: str) -> str: """Render steps as a themed heading followed by one status-styled line each. - Shared by the multi-step commands (`aai init`, `aai setup`); each passes its + Shared by the multi-step commands (`assembly init`, `assembly setup`); each passes its own heading. """ lines: list[str] = [] diff --git a/aai_cli/streaming/render.py b/aai_cli/streaming/render.py index 2070e275..ca04a959 100644 --- a/aai_cli/streaming/render.py +++ b/aai_cli/streaming/render.py @@ -45,7 +45,7 @@ class StreamRenderer(BaseRenderer): - JSON: newline-delimited JSON to stdout (pipe-safe, machine-readable). - text: only finalized turn transcripts, one plain line each, to stdout; status - notices ("Listening…") go to stderr. Lets `aai stream -o text | aai llm "…"` + notices ("Listening…") go to stderr. Lets `assembly stream -o text | assembly llm "…"` pipe clean transcript text downstream. - human (default): a live-updating line through Rich. diff --git a/aai_cli/streaming/sources.py b/aai_cli/streaming/sources.py index 41a9a009..fcdc0cdc 100644 --- a/aai_cli/streaming/sources.py +++ b/aai_cli/streaming/sources.py @@ -157,7 +157,7 @@ class StdinSource: """Streams raw PCM16 mono audio piped on stdin. Expects signed 16-bit little-endian mono PCM at ``sample_rate`` (default 16 kHz): - ``ffmpeg -i in.mp4 -f s16le -acodec pcm_s16le -ac 1 -ar 16000 - | aai stream -``. + ``ffmpeg -i in.mp4 -f s16le -acodec pcm_s16le -ac 1 -ar 16000 - | assembly stream -``. """ def __init__(self, *, sample_rate: int = TARGET_RATE, stdin: Any = None) -> None: diff --git a/aai_cli/theme.py b/aai_cli/theme.py index b5a0aaba..bb4238d5 100644 --- a/aai_cli/theme.py +++ b/aai_cli/theme.py @@ -88,7 +88,7 @@ class PipeSafeConsole(Console): """A Console honoring the CLI's closed-pipe contract. Rich's default ``on_broken_pipe`` redirects stdout to devnull and raises - ``SystemExit(1)``, so ``aai … | head`` would report failure. Re-raise the + ``SystemExit(1)``, so ``assembly … | head`` would report failure. Re-raise the ``BrokenPipeError`` instead so the entry point (``main.run``) can treat a closed downstream pipe as success. """ diff --git a/aai_cli/transcribe_exec.py b/aai_cli/transcribe_exec.py index da0c771e..03c3a5a3 100644 --- a/aai_cli/transcribe_exec.py +++ b/aai_cli/transcribe_exec.py @@ -88,7 +88,7 @@ def run_transcription( download_sections: list[str] | None = None, ) -> aai.Transcript: if source == "-": - # Audio piped on stdin (e.g. `ffmpeg -i v.mp4 -f wav - | aai transcribe -`). + # Audio piped on stdin (e.g. `ffmpeg -i v.mp4 -f wav - | assembly transcribe -`). # The SDK uploads a path, so buffer the bytes to a temp file first. data = stdio.read_binary_stdin() if not data: diff --git a/aai_cli/tts/audio.py b/aai_cli/tts/audio.py index 0b608dd0..4bd9de2c 100644 --- a/aai_cli/tts/audio.py +++ b/aai_cli/tts/audio.py @@ -57,7 +57,7 @@ def _playback_error(exc: Exception) -> CLIError: f"Could not play audio: {exc}", error_type="audio_output_error", exit_code=1, - suggestion="Check your output device and run 'aai doctor', or use --out to save a WAV.", + suggestion="Check your output device and run 'assembly doctor', or use --out to save a WAV.", ) diff --git a/aai_cli/ws.py b/aai_cli/ws.py index 8433dc82..6e5fa10a 100644 --- a/aai_cli/ws.py +++ b/aai_cli/ws.py @@ -2,7 +2,7 @@ Both sessions classify connect/session failures the same way and silence the same library loggers; keeping that here means a change to either behavior lands in -`aai agent` and `aai speak` together instead of drifting apart. +`assembly agent` and `assembly speak` together instead of drifting apart. """ from __future__ import annotations diff --git a/install.sh b/install.sh index 65af4de0..aecf33f6 100755 --- a/install.sh +++ b/install.sh @@ -1,5 +1,5 @@ #!/bin/sh -# Install the AssemblyAI CLI (`aai`) without cloning the repo: +# Install the AssemblyAI CLI (`assembly`) without cloning the repo: # # curl -fsSL https://raw.githubusercontent.com/AssemblyAI/cli/main/install.sh | sh # @@ -29,7 +29,7 @@ fi # --- Install (prefer pipx for an isolated env; fall back to pip --user) ---- if command -v pipx >/dev/null 2>&1; then - info "Installing aai with pipx from ${REPO}@${REF}..." + info "Installing assembly with pipx from ${REPO}@${REF}..." pipx install --force "$SPEC" else info "pipx not found; installing with pip --user from ${REPO}@${REF}..." @@ -37,9 +37,9 @@ else fi # --- Next steps ----------------------------------------------------------- -if command -v aai >/dev/null 2>&1; then - info "Installed. Next: run 'aai onboard'." +if command -v assembly >/dev/null 2>&1; then + info "Installed. Next: run 'assembly onboard'." else - info "Installed, but 'aai' isn't on your PATH yet." + info "Installed, but 'assembly' isn't on your PATH yet." info "Run 'pipx ensurepath' (or add ~/.local/bin to PATH), then restart your shell." fi diff --git a/pyproject.toml b/pyproject.toml index 8c1071eb..52d32bc3 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -53,7 +53,7 @@ Repository = "https://github.com/AssemblyAI/cli" Issues = "https://github.com/AssemblyAI/cli/issues" [project.scripts] -aai = "aai_cli.main:run" +assembly = "aai_cli.main:run" # PEP 735 dependency group (not a [project] extra). uv installs this group by # default for `uv run`/`uv sync` (see [tool.uv] default-groups below), so the @@ -137,7 +137,7 @@ filterwarnings = [ ] markers = [ "e2e: real-API end-to-end tests that drive the CLI (need ASSEMBLYAI_API_KEY; skip otherwise)", - "install_script: real install of a locally-built wheel via install.sh (pipx / pip --user) and `uv tool install`; asserts `aai` runs (slow; needs network + uv/pipx; skip otherwise)", + "install_script: real install of a locally-built wheel via install.sh (pipx / pip --user) and `uv tool install`; asserts `assembly` runs (slow; needs network + uv/pipx; skip otherwise)", "install: install each init template's requirements.txt into a clean venv and import it (slow; needs network + uv; skip otherwise)", ] diff --git a/scripts/docker_build_check.sh b/scripts/docker_build_check.sh index 817da524..676a490f 100755 --- a/scripts/docker_build_check.sh +++ b/scripts/docker_build_check.sh @@ -1,5 +1,5 @@ #!/usr/bin/env bash -# Build each `aai init` template's Docker image to prove the shipped Dockerfile works +# Build each `assembly init` template's Docker image to prove the shipped Dockerfile works # end to end: scaffold the template (which renames `dockerignore` -> `.dockerignore` # and writes a placeholder `.env`), then `docker build` the result. This catches a # broken Dockerfile, an uninstallable requirement, or a bad COPY layout — none of which @@ -31,7 +31,7 @@ trap cleanup EXIT for t in "${templates[@]}"; do app="$workdir/$t" echo "==> scaffolding $t" - uv run aai init "$t" "$app" --no-install >/dev/null + uv run assembly init "$t" "$app" --no-install >/dev/null echo "==> docker build $t" docker build --quiet -t "aai-template-${t}:dockercheck" "$app" done diff --git a/scripts/record_fixtures.py b/scripts/record_fixtures.py index 6275d53e..d42ea8a4 100644 --- a/scripts/record_fixtures.py +++ b/scripts/record_fixtures.py @@ -12,7 +12,7 @@ ASSEMBLYAI_API_KEY= uv run python scripts/record_fixtures.py The API key is read from the environment; the AMS session (JWT) is read from the OS -keyring of whoever ran ``aai login`` (profile ``default``). Neither is ever written to +keyring of whoever ran ``assembly login`` (profile ``default``). Neither is ever written to a fixture. Re-run it to refresh the fixtures after an API shape change. """ @@ -122,7 +122,7 @@ def main() -> int: session = config.get_session(PROFILE) account_id = config.get_account_id(PROFILE) if session is None or account_id is None: - _err(f"No AMS session for profile {PROFILE!r}; run 'aai login' first.") + _err(f"No AMS session for profile {PROFILE!r}; run 'assembly login' first.") return 1 jwt = session["jwt"] scrub = _build_scrubber([api_key, jwt, session.get("token", "")]) diff --git a/tests/__snapshots__/test_cli_output_snapshots.ambr b/tests/__snapshots__/test_cli_output_snapshots.ambr index 25963651..d1250f87 100644 --- a/tests/__snapshots__/test_cli_output_snapshots.ambr +++ b/tests/__snapshots__/test_cli_output_snapshots.ambr @@ -2,7 +2,7 @@ # name: test_command_help_matches_snapshot[agent] ''' - Usage: aai agent [OPTIONS] [SOURCE] + Usage: assembly agent [OPTIONS] [SOURCE] Have a live two-way voice conversation with an AssemblyAI voice agent. @@ -14,7 +14,7 @@ reply. This only runs a conversation in the terminal — it writes no code. To build - a voice agent app, run 'aai init voice-agent' instead. + a voice agent app, run 'assembly init voice-agent' instead. ╭─ Arguments ──────────────────────────────────────────────────────────────────╮ │ source [SOURCE] Audio file path or URL to speak to the agent. Omit │ @@ -55,15 +55,15 @@ Examples Start a live voice conversation - $ aai agent + $ assembly agent Pick a voice and opening line - $ aai agent --voice james --greeting "Hi there" + $ assembly agent --voice james --greeting "Hi there" Give the agent a persona - $ aai agent --system-prompt "You are a terse pirate." + $ assembly agent --system-prompt "You are a terse pirate." See available voices - $ aai agent --list-voices + $ assembly agent --list-voices Print equivalent Python instead of running - $ aai agent --show-code + $ assembly agent --show-code @@ -72,7 +72,7 @@ # name: test_command_help_matches_snapshot[audit] ''' - Usage: aai audit [OPTIONS] + Usage: assembly audit [OPTIONS] List recent audit-log entries for your account. @@ -87,15 +87,15 @@ Examples Recent audit-log entries - $ aai audit + $ assembly audit Show more entries - $ aai audit --limit 100 + $ assembly audit --limit 100 Include login events - $ aai audit --include-logins + $ assembly audit --include-logins Filter by action - $ aai audit --action token.create + $ assembly audit --action token.create Filter by resource, as JSON - $ aai audit --resource token --json + $ assembly audit --resource token --json @@ -104,7 +104,7 @@ # name: test_command_help_matches_snapshot[balance] ''' - Usage: aai balance [OPTIONS] + Usage: assembly balance [OPTIONS] Show your remaining account balance. @@ -115,9 +115,9 @@ Examples Show your remaining balance - $ aai balance + $ assembly balance Get the raw cents for scripting - $ aai balance --json | jq '.balance_in_cents' + $ assembly balance --json | jq '.balance_in_cents' @@ -126,7 +126,7 @@ # name: test_command_help_matches_snapshot[deploy] ''' - Usage: aai deploy [OPTIONS] + Usage: assembly deploy [OPTIONS] Deploy the current project to Vercel (default), Railway, or Fly.io. @@ -145,13 +145,13 @@ Examples Deploy a preview to Vercel (asks first) - $ aai deploy + $ assembly deploy Deploy to production on Vercel - $ aai deploy --prod --yes + $ assembly deploy --prod --yes Deploy to Railway - $ aai deploy --railway + $ assembly deploy --railway Deploy to Fly.io - $ aai deploy --fly + $ assembly deploy --fly @@ -160,11 +160,12 @@ # name: test_command_help_matches_snapshot[dev] ''' - Usage: aai dev [OPTIONS] + Usage: assembly dev [OPTIONS] Launch the dev server for the app in the current directory. - Run this from inside a project created by `aai init`. It installs dependencies + Run this from inside a project created by `assembly init`. It installs + dependencies if needed, then starts the FastAPI server with live reload and opens the browser. @@ -181,13 +182,13 @@ Examples Launch the app in the current directory - $ aai dev + $ assembly dev Use a specific port - $ aai dev --port 8000 + $ assembly dev --port 8000 Launch without opening a browser - $ aai dev --no-open + $ assembly dev --no-open Skip the dependency install step - $ aai dev --no-install + $ assembly dev --no-install @@ -196,7 +197,7 @@ # name: test_command_help_matches_snapshot[doctor] ''' - Usage: aai doctor [OPTIONS] + Usage: assembly doctor [OPTIONS] Check that your environment is ready to use AssemblyAI. @@ -207,9 +208,9 @@ Examples Check your environment is ready - $ aai doctor + $ assembly doctor Output results as JSON - $ aai doctor --json + $ assembly doctor --json @@ -218,12 +219,13 @@ # name: test_command_help_matches_snapshot[init] ''' - Usage: aai init [OPTIONS] [TEMPLATE] [DIRECTORY] + Usage: assembly init [OPTIONS] [TEMPLATE] [DIRECTORY] Scaffold a new project from a template, then launch it. This is the starting point for creating an app — including a voice agent app - ('aai init voice-agent'). The 'aai agent' command only runs a live mic + ('assembly init voice-agent'). The 'assembly agent' command only runs a live + mic conversation and writes no code. ╭─ Arguments ──────────────────────────────────────────────────────────────────╮ @@ -244,15 +246,15 @@ Examples Scaffold a new app interactively - $ aai init + $ assembly init Scaffold an audio transcription app into ./my-app - $ aai init audio-transcription my-app + $ assembly init audio-transcription my-app Scaffold a voice agent app - $ aai init voice-agent + $ assembly init voice-agent Scaffold into the current directory - $ aai init audio-transcription --here + $ assembly init audio-transcription --here Scaffold only, without installing or launching - $ aai init audio-transcription --no-install + $ assembly init audio-transcription --no-install @@ -261,7 +263,7 @@ # name: test_command_help_matches_snapshot[keys_create] ''' - Usage: aai keys create [OPTIONS] + Usage: assembly keys create [OPTIONS] Create a new API key. Prints the key value once — copy it now. @@ -275,11 +277,11 @@ Examples Create a key in your default project - $ aai keys create --name ci-pipeline + $ assembly keys create --name ci-pipeline Create a key in a specific project - $ aai keys create --name prod --project 7 + $ assembly keys create --name prod --project 7 Capture the new key into an env var - $ export ASSEMBLYAI_API_KEY=$(aai keys create --name ci --json | jq -r + $ export ASSEMBLYAI_API_KEY=$(assembly keys create --name ci --json | jq -r '.api_key') @@ -289,7 +291,7 @@ # name: test_command_help_matches_snapshot[keys_list] ''' - Usage: aai keys list [OPTIONS] + Usage: assembly keys list [OPTIONS] List API keys across your projects (keys shown masked). @@ -300,11 +302,11 @@ Examples List your API keys (masked) - $ aai keys list + $ assembly keys list As JSON for scripting - $ aai keys list --json + $ assembly keys list --json Get key ids to use with rename - $ aai keys list --json | jq '.[].id' + $ assembly keys list --json | jq '.[].id' @@ -313,12 +315,13 @@ # name: test_command_help_matches_snapshot[keys_rename] ''' - Usage: aai keys rename [OPTIONS] TOKEN_ID NEW_NAME + Usage: assembly keys rename [OPTIONS] TOKEN_ID NEW_NAME Rename an existing API key. ╭─ Arguments ──────────────────────────────────────────────────────────────────╮ - │ * token_id INTEGER The key id (see `aai keys list`). [required] │ + │ * token_id INTEGER The key id (see `assembly keys list`). │ + │ [required] │ │ * new_name TEXT The new label. [required] │ ╰──────────────────────────────────────────────────────────────────────────────╯ ╭─ Options ────────────────────────────────────────────────────────────────────╮ @@ -327,8 +330,8 @@ ╰──────────────────────────────────────────────────────────────────────────────╯ Examples - Relabel a key (id from `aai keys list`) - $ aai keys rename 123 "prod" + Relabel a key (id from `assembly keys list`) + $ assembly keys rename 123 "prod" @@ -337,7 +340,7 @@ # name: test_command_help_matches_snapshot[limits] ''' - Usage: aai limits [OPTIONS] + Usage: assembly limits [OPTIONS] Show your account's rate limits per service. @@ -348,9 +351,9 @@ Examples Show rate limits per service - $ aai limits + $ assembly limits As JSON for scripting - $ aai limits --json + $ assembly limits --json @@ -359,12 +362,12 @@ # name: test_command_help_matches_snapshot[llm] ''' - Usage: aai llm [OPTIONS] [PROMPT] + Usage: assembly llm [OPTIONS] [PROMPT] Send a prompt to AssemblyAI's LLM Gateway and print the response. With --transcript-id the transcript's text is injected server-side, so you - can ask questions about a past transcription (e.g. aai llm "summarize" + can ask questions about a past transcription (e.g. assembly llm "summarize" --transcript-id ID). ╭─ Arguments ──────────────────────────────────────────────────────────────────╮ @@ -379,9 +382,9 @@ │ --follow -f Re-run the prompt over a growing │ │ transcript piped on stdin, refreshing │ │ the answer in place on every finalized │ - │ turn (e.g. aai stream -o text | aai │ - │ llm -f "summarize action items as I │ - │ talk"). Ctrl-C to stop. │ + │ turn (e.g. assembly stream -o text | │ + │ assembly llm -f "summarize action │ + │ items as I talk"). Ctrl-C to stop. │ │ --output -o [text|json] Print one field of the result: text │ │ (just the answer, pipe-friendly) or │ │ json. │ @@ -395,14 +398,14 @@ Examples Ask about a past transcript - $ aai llm "summarize the key decisions" --transcript-id 5551234-abcd + $ assembly llm "summarize the key decisions" --transcript-id 5551234-abcd Pipe any text in - $ echo "meeting notes" | aai llm "turn into action items" + $ echo "meeting notes" | assembly llm "turn into action items" Pick a model and add a system prompt - $ aai llm "draft a follow-up email" --model claude-opus-4-7 --system "Be + $ assembly llm "draft a follow-up email" --model claude-opus-4-7 --system "Be concise." See available models - $ aai llm --list-models + $ assembly llm --list-models @@ -411,7 +414,7 @@ # name: test_command_help_matches_snapshot[login] ''' - Usage: aai login [OPTIONS] + Usage: assembly login [OPTIONS] Authenticate via your browser; stores a CLI API key. @@ -423,9 +426,9 @@ Examples Log in with your browser - $ aai login + $ assembly login Log in non-interactively (CI) - $ aai login --api-key sk_... + $ assembly login --api-key sk_... @@ -434,7 +437,7 @@ # name: test_command_help_matches_snapshot[logout] ''' - Usage: aai logout [OPTIONS] + Usage: assembly logout [OPTIONS] Clear stored credentials for the active profile. @@ -445,7 +448,7 @@ Examples Clear stored credentials for the active profile - $ aai logout + $ assembly logout @@ -454,7 +457,7 @@ # name: test_command_help_matches_snapshot[onboard] ''' - Usage: aai onboard [OPTIONS] + Usage: assembly onboard [OPTIONS] Guided setup: sign in, run your first transcription, and start building. @@ -467,7 +470,7 @@ Examples Run the guided setup - $ aai onboard + $ assembly onboard @@ -476,7 +479,7 @@ # name: test_command_help_matches_snapshot[sessions_get] ''' - Usage: aai sessions get [OPTIONS] SESSION_ID + Usage: assembly sessions get [OPTIONS] SESSION_ID Show details for one streaming session. @@ -490,11 +493,12 @@ Examples Show one session's details - $ aai sessions get sess_5551234 + $ assembly sessions get sess_5551234 Raw JSON for one session - $ aai sessions get sess_5551234 --json + $ assembly sessions get sess_5551234 --json Drill into the latest session - $ aai sessions get $(aai sessions list --json | jq -r '.[0].session_id') + $ assembly sessions get $(assembly sessions list --json | jq -r + '.[0].session_id') @@ -503,7 +507,7 @@ # name: test_command_help_matches_snapshot[sessions_list] ''' - Usage: aai sessions list [OPTIONS] + Usage: assembly sessions list [OPTIONS] List recent streaming sessions. @@ -516,13 +520,14 @@ Examples List recent streaming sessions - $ aai sessions list + $ assembly sessions list Find failed sessions - $ aai sessions list --status error + $ assembly sessions list --status error Inspect the most recent session - $ aai sessions get $(aai sessions list --json | jq -r '.[0].session_id') + $ assembly sessions get $(assembly sessions list --json | jq -r + '.[0].session_id') Total audio across recent sessions (seconds) - $ aai sessions list --json | jq '[.[].audio_duration_sec] | add' + $ assembly sessions list --json | jq '[.[].audio_duration_sec] | add' @@ -531,7 +536,7 @@ # name: test_command_help_matches_snapshot[setup_install] ''' - Usage: aai setup install [OPTIONS] + Usage: assembly setup install [OPTIONS] Set up your coding agent for AssemblyAI (docs MCP server + skills). @@ -553,11 +558,11 @@ Examples Set up your coding agent for AssemblyAI - $ aai setup install + $ assembly setup install Install for the current project only - $ aai setup install --scope project + $ assembly setup install --scope project Reinstall everything even if already present - $ aai setup install --force + $ assembly setup install --force @@ -566,7 +571,7 @@ # name: test_command_help_matches_snapshot[setup_remove] ''' - Usage: aai setup remove [OPTIONS] + Usage: assembly setup remove [OPTIONS] Remove the AssemblyAI MCP server and skills from your coding agent. @@ -580,9 +585,9 @@ Examples Remove the AssemblyAI MCP server and skills - $ aai setup remove + $ assembly setup remove Remove only from the project scope - $ aai setup remove --scope project + $ assembly setup remove --scope project @@ -591,7 +596,7 @@ # name: test_command_help_matches_snapshot[setup_status] ''' - Usage: aai setup status [OPTIONS] + Usage: assembly setup status [OPTIONS] Show whether the AssemblyAI MCP server and skills are set up in your coding agent. @@ -603,9 +608,9 @@ Examples Show what's set up - $ aai setup status + $ assembly setup status Print status as JSON - $ aai setup status --json + $ assembly setup status --json @@ -614,12 +619,12 @@ # name: test_command_help_matches_snapshot[share] ''' - Usage: aai share [OPTIONS] + Usage: assembly share [OPTIONS] Boot the app and expose it on a public URL via a cloudflared tunnel. - Run this from inside a project created by `aai init`. It starts the dev server - and + Run this from inside a project created by `assembly init`. It starts the dev + server and opens a cloudflared quick tunnel, printing a shareable https://*.trycloudflare.com URL. Requires cloudflared (macOS: `brew install cloudflared`; other platforms: @@ -635,11 +640,11 @@ Examples Share the running app on a public URL - $ aai share + $ assembly share Use a specific local port - $ aai share --port 8000 + $ assembly share --port 8000 Skip the dependency install step - $ aai share --no-install + $ assembly share --no-install @@ -648,12 +653,13 @@ # name: test_command_help_matches_snapshot[speak] ''' - Usage: aai speak [OPTIONS] [TEXT] + Usage: assembly speak [OPTIONS] [TEXT] Synthesize speech from text with AssemblyAI streaming TTS (sandbox only). Plays the audio through your speakers by default, or writes a WAV with --out. - Speaker-labeled input (from 'aai transcribe --speaker-labels') is detected + Speaker-labeled input (from 'assembly transcribe --speaker-labels') is + detected automatically: the labels are stripped and each speaker gets a different voice. This feature only exists in the sandbox today — run it with --sandbox. @@ -675,15 +681,15 @@ Examples Speak text aloud (sandbox only) - $ aai speak "Hello there, friend." --sandbox + $ assembly speak "Hello there, friend." --sandbox Pick a voice and language - $ aai speak "Bonjour" --voice jane --language French --sandbox + $ assembly speak "Bonjour" --voice jane --language French --sandbox Speak a diarized transcript, one voice per speaker - $ aai transcribe meeting.mp3 --speaker-labels | aai speak --sandbox + $ assembly transcribe meeting.mp3 --speaker-labels | assembly speak --sandbox Override a speaker's voice - $ … | aai speak --voice A=vera --voice B=paul --sandbox + $ … | assembly speak --voice A=vera --voice B=paul --sandbox Save to a WAV instead of playing - $ aai speak "Hello" --out /tmp/hello.wav --sandbox + $ assembly speak "Hello" --out /tmp/hello.wav --sandbox @@ -692,16 +698,16 @@ # name: test_command_help_matches_snapshot[stream] ''' - Usage: aai stream [OPTIONS] [SOURCE] + Usage: assembly stream [OPTIONS] [SOURCE] Transcribe live audio in real time — from your mic, a file, a URL, or a pipe. Pass - as the source to read raw PCM16/mono/16k audio on stdin, e.g. - ffmpeg -i input.mp4 -f s16le -ar 16000 -ac 1 - | aai stream -. + ffmpeg -i input.mp4 -f s16le -ar 16000 -ac 1 - | assembly stream -. --prompt biases the speech model. --llm runs a prompt over the live transcript in-process, refreshing the answer on every finalized turn; for a separate step - instead, pipe the text out with -o text | aai llm -f "…". + instead, pipe the text out with -o text | assembly llm -f "…". ╭─ Arguments ──────────────────────────────────────────────────────────────────╮ │ source [SOURCE] Audio file path, URL, or YouTube/podcast page URL to │ @@ -814,19 +820,19 @@ Examples Stream from your microphone - $ aai stream + $ assembly stream Stream a file or URL in real time - $ aai stream recording.wav + $ assembly stream recording.wav Stream the hosted sample - $ aai stream --sample + $ assembly stream --sample Label speakers in the live transcript - $ aai stream --speaker-labels + $ assembly stream --speaker-labels Boost domain terms with keyterm prompts - $ aai stream --keyterms-prompt "AssemblyAI" --keyterms-prompt "Claude" + $ assembly stream --keyterms-prompt "AssemblyAI" --keyterms-prompt "Claude" Summarize action items live as you talk - $ aai stream --llm "summarize action items" + $ assembly stream --llm "summarize action items" Print equivalent Python instead of running - $ aai stream --show-code + $ assembly stream --show-code @@ -835,11 +841,12 @@ # name: test_command_help_matches_snapshot[transcribe] ''' - Usage: aai transcribe [OPTIONS] [SOURCE] + Usage: assembly transcribe [OPTIONS] [SOURCE] Transcribe an audio file, URL, or YouTube/podcast link. - Quickest start: aai transcribe call.mp3 (or --sample for the hosted demo). + Quickest start: assembly transcribe call.mp3 (or --sample for the hosted + demo). Save with --out FILE, or pipe one field with -o text. YouTube and podcast-page URLs (any page yt-dlp can extract) are downloaded first, then transcribed. @@ -971,21 +978,21 @@ Examples Transcribe a local file - $ aai transcribe call.mp3 + $ assembly transcribe call.mp3 Try it with the hosted sample - $ aai transcribe --sample + $ assembly transcribe --sample Transcribe a YouTube video - $ aai transcribe https://youtu.be/dtp6b76pMak - Transcribe a podcast episode page - $ aai transcribe "https://podcasts.apple.com/…" + $ assembly transcribe https://youtu.be/dtp6b76pMak + Transcribe a podcast page + $ assembly transcribe "https://podcasts.apple.com/…" Label who said what - $ aai transcribe call.mp3 --speaker-labels + $ assembly transcribe call.mp3 --speaker-labels Redact PII for compliance - $ aai transcribe call.mp3 --redact-pii + $ assembly transcribe call.mp3 --redact-pii Summarize a recording - $ aai transcribe call.mp3 --summarization + $ assembly transcribe call.mp3 --summarization Ask about the transcript - $ aai transcribe call.mp3 --llm "List the action items" + $ assembly transcribe call.mp3 --llm "List action items" @@ -994,7 +1001,7 @@ # name: test_command_help_matches_snapshot[transcripts_get] ''' - Usage: aai transcripts get [OPTIONS] TRANSCRIPT_ID + Usage: assembly transcripts get [OPTIONS] TRANSCRIPT_ID Fetch a past transcript by id and print its text. @@ -1010,13 +1017,13 @@ Examples Fetch a transcript's text by id - $ aai transcripts get 5551234-abcd + $ assembly transcripts get 5551234-abcd Speaker-labeled turns - $ aai transcripts get 5551234-abcd -o utterances + $ assembly transcripts get 5551234-abcd -o utterances Save SRT subtitles - $ aai transcripts get 5551234-abcd -o srt > captions.srt + $ assembly transcripts get 5551234-abcd -o srt > captions.srt Get the raw JSON - $ aai transcripts get 5551234-abcd --json + $ assembly transcripts get 5551234-abcd --json @@ -1025,7 +1032,7 @@ # name: test_command_help_matches_snapshot[transcripts_list] ''' - Usage: aai transcripts list [OPTIONS] + Usage: assembly transcripts list [OPTIONS] List recent transcripts. @@ -1038,14 +1045,14 @@ Examples List your recent transcripts - $ aai transcripts list + $ assembly transcripts list Show more at once - $ aai transcripts list --limit 50 + $ assembly transcripts list --limit 50 Grab the latest transcript id - $ aai transcripts list --json | jq -r '.[0].id' + $ assembly transcripts list --json | jq -r '.[0].id' Summarize your latest transcript - $ aai llm "summarize" --transcript-id $(aai transcripts list --json | jq -r - '.[0].id') + $ assembly llm "summarize" --transcript-id $(assembly transcripts list --json + | jq -r '.[0].id') @@ -1054,7 +1061,7 @@ # name: test_command_help_matches_snapshot[usage] ''' - Usage: aai usage [OPTIONS] + Usage: assembly usage [OPTIONS] Show usage over a date range (defaults to the last 30 days). @@ -1064,20 +1071,20 @@ │ --end TEXT End date (YYYY-MM-DD). Default: today. │ │ --window TEXT Window size, e.g. 'day' or 'month'. │ │ --include-zero,--all Include zero-usage windows (matches │ - │ --include-logins on `aai audit`). │ + │ --include-logins on `assembly audit`). │ │ --json -j Output raw JSON. │ │ --help Show this message and exit. │ ╰──────────────────────────────────────────────────────────────────────────────╯ Examples Usage over the last 30 days - $ aai usage + $ assembly usage A specific date range - $ aai usage --start 2026-05-01 --end 2026-06-01 + $ assembly usage --start 2026-05-01 --end 2026-06-01 Break spend down by month - $ aai usage --window month + $ assembly usage --window month Total spend in cents for scripting - $ aai usage --json | jq '[.usage_items[].line_items[].price] | add' + $ assembly usage --json | jq '[.usage_items[].line_items[].price] | add' @@ -1086,7 +1093,7 @@ # name: test_command_help_matches_snapshot[whoami] ''' - Usage: aai whoami [OPTIONS] + Usage: assembly whoami [OPTIONS] Show the active profile and whether its key is usable. @@ -1097,7 +1104,7 @@ Examples Show the active profile and whether its key works - $ aai whoami + $ assembly whoami @@ -1112,8 +1119,8 @@ # name: test_error_human_render_matches_snapshot[not_authenticated] ''' Error: You're not signed in. - Suggestion: Run 'aai onboard' for guided setup, 'aai login' if you have an - account, or set ASSEMBLYAI_API_KEY. + Suggestion: Run 'assembly onboard' for guided setup, 'assembly login' if you + have an account, or set ASSEMBLYAI_API_KEY. ''' # --- @@ -1126,14 +1133,14 @@ # name: test_error_human_render_matches_snapshot[rejected_key] ''' Error: Your API key was rejected. - Suggestion: Run 'aai login' with a valid key, or set ASSEMBLYAI_API_KEY. + Suggestion: Run 'assembly login' with a valid key, or set ASSEMBLYAI_API_KEY. ''' # --- # name: test_error_human_render_matches_snapshot[usage_with_suggestion] ''' Error: Unknown voice 'nope'. - Suggestion: Run 'aai agent --list-voices' to see the options. + Suggestion: Run 'assembly agent --list-voices' to see the options. ''' # --- @@ -1145,7 +1152,7 @@ # --- # name: test_error_json_render_matches_snapshot[not_authenticated] ''' - {"error": {"type": "not_authenticated", "message": "You're not signed in.", "suggestion": "Run 'aai onboard' for guided setup, 'aai login' if you have an account, or set ASSEMBLYAI_API_KEY."}} + {"error": {"type": "not_authenticated", "message": "You're not signed in.", "suggestion": "Run 'assembly onboard' for guided setup, 'assembly login' if you have an account, or set ASSEMBLYAI_API_KEY."}} ''' # --- @@ -1157,13 +1164,13 @@ # --- # name: test_error_json_render_matches_snapshot[rejected_key] ''' - {"error": {"type": "not_authenticated", "message": "Your API key was rejected.", "suggestion": "Run 'aai login' with a valid key, or set ASSEMBLYAI_API_KEY."}} + {"error": {"type": "not_authenticated", "message": "Your API key was rejected.", "suggestion": "Run 'assembly login' with a valid key, or set ASSEMBLYAI_API_KEY."}} ''' # --- # name: test_error_json_render_matches_snapshot[usage_with_suggestion] ''' - {"error": {"type": "usage_error", "message": "Unknown voice 'nope'.", "suggestion": "Run 'aai agent --list-voices' to see the options."}} + {"error": {"type": "usage_error", "message": "Unknown voice 'nope'.", "suggestion": "Run 'assembly agent --list-voices' to see the options."}} ''' # --- diff --git a/tests/e2e/test_cli_e2e.py b/tests/e2e/test_cli_e2e.py index 0dce476e..8f7f8193 100644 --- a/tests/e2e/test_cli_e2e.py +++ b/tests/e2e/test_cli_e2e.py @@ -1,4 +1,4 @@ -"""End-to-end tests that drive the real `aai` CLI against the live AssemblyAI API. +"""End-to-end tests that drive the real `assembly` CLI against the live AssemblyAI API. Committed speech WAV fixtures are fed through the CLI as a subprocess so the binary, argument parsing, auth, audio decoding, and network path are all diff --git a/tests/setup_helpers.py b/tests/setup_helpers.py index 5c79441d..a4ed5652 100644 --- a/tests/setup_helpers.py +++ b/tests/setup_helpers.py @@ -1,4 +1,4 @@ -"""Shared scaffolding for the `aai setup` test modules. +"""Shared scaffolding for the `assembly setup` test modules. Not a test module itself (no ``test_`` prefix, so pytest won't collect it): it holds the fakes and helpers reused across ``test_setup.py`` and diff --git a/tests/test_account_command.py b/tests/test_account_command.py index 5ed93a0c..a2d1d5ee 100644 --- a/tests/test_account_command.py +++ b/tests/test_account_command.py @@ -104,7 +104,7 @@ def test_usage_renders_table_human(monkeypatch, mocker): mocker.patch("aai_cli.commands.account.ams.get_usage", autospec=True, return_value=payload) result = runner.invoke(app, ["usage"]) assert result.exit_code == 0 - # price (cents) is summed per window and shown as dollars, mirroring `aai balance`. + # price (cents) is summed per window and shown as dollars, mirroring `assembly balance`. assert "2026-05-01" in result.output and "$12.50" in result.output @@ -256,7 +256,7 @@ def test_usage_human_can_include_zero_windows(monkeypatch, mocker): ] } mocker.patch("aai_cli.commands.account.ams.get_usage", autospec=True, return_value=payload) - # The primary flag name is --include-zero (matching --include-logins on `aai audit`). + # The primary flag name is --include-zero (matching --include-logins on `assembly audit`). result = runner.invoke(app, ["usage", "--include-zero"]) assert result.exit_code == 0 assert "2026-01-01" in result.output diff --git a/tests/test_agent_command.py b/tests/test_agent_command.py index b1828ba9..cc4a920f 100644 --- a/tests/test_agent_command.py +++ b/tests/test_agent_command.py @@ -313,7 +313,7 @@ def test_agent_json_with_text_output_is_usage_error(): def test_agent_headphones_notice_routes_to_stderr(monkeypatch): - # `aai agent | head` must not eat the advisory as transcript data: in the + # `assembly agent | head` must not eat the advisory as transcript data: in the # default human mode the notice goes to stderr, stdout stays transcript-only. config.set_api_key("default", "sk_live") monkeypatch.setattr("aai_cli.output.resolve_json", lambda *, explicit: False) @@ -338,7 +338,7 @@ def _boom(*a, **k): def test_agent_output_text_emits_plain_transcript(monkeypatch): - # `-o text` -> plain you:/agent: lines on stdout (pipe into aai llm). + # `-o text` -> plain you:/agent: lines on stdout (pipe into assembly llm). config.set_api_key("default", "sk_live") monkeypatch.setattr("aai_cli.commands.agent.FileSource", lambda src: "filesrc") diff --git a/tests/test_agent_render.py b/tests/test_agent_render.py index 9821fcd8..45b58412 100644 --- a/tests/test_agent_render.py +++ b/tests/test_agent_render.py @@ -132,7 +132,7 @@ def test_human_close_commits_open_partial(): def test_human_notice_goes_to_stderr_not_stdout(): - # Human (default) mode is also piped sometimes (`aai agent | head`); the notice + # Human (default) mode is also piped sometimes (`assembly agent | head`); the notice # must land on stderr in every non-JSON mode so stdout carries only transcript. out, err = io.StringIO(), io.StringIO() console = theme.make_console(file=out, force_terminal=True, width=80) diff --git a/tests/test_agent_session.py b/tests/test_agent_session.py index d8a797b8..fd8f6a84 100644 --- a/tests/test_agent_session.py +++ b/tests/test_agent_session.py @@ -129,7 +129,7 @@ def test_unauthorized_error_raises_not_authenticated_exit_4(): s.dispatch({"type": "session.error", "code": "UNAUTHORIZED", "message": "bad key"}) assert excinfo.value.exit_code == 4 assert "bad key" in str(excinfo.value) # the server message wins over code/fallback - assert excinfo.value.suggestion is not None and "aai login" in excinfo.value.suggestion + assert excinfo.value.suggestion is not None and "assembly login" in excinfo.value.suggestion # A presented-and-refused key: auto-login must not retry over a bad env key. assert excinfo.value.rejected_key is True diff --git a/tests/test_auth_ams.py b/tests/test_auth_ams.py index 922cbc7a..3785b1be 100644 --- a/tests/test_auth_ams.py +++ b/tests/test_auth_ams.py @@ -47,7 +47,7 @@ def handler(request: httpx.Request) -> httpx.Response: with pytest.raises(NotAuthenticated) as exc: ams.discover("bad") # A rejected AMS session now carries an actionable next step. - assert exc.value.suggestion is not None and "aai login" in exc.value.suggestion + assert exc.value.suggestion is not None and "assembly login" in exc.value.suggestion def test_500_raises_api_error_with_detail(monkeypatch): diff --git a/tests/test_auth_flow.py b/tests/test_auth_flow.py index 873f8f30..19ddfaf4 100644 --- a/tests/test_auth_flow.py +++ b/tests/test_auth_flow.py @@ -166,7 +166,10 @@ def test_run_login_flow_timeout_raises_auth_typed_error(monkeypatch): assert exc.value.message == "Login timed out waiting for the browser." assert exc.value.error_type == "not_authenticated" # auth-typed, not api_error assert exc.value.exit_code == 4 - assert exc.value.suggestion == "Run 'aai login' again, or use 'aai login --api-key '." + assert ( + exc.value.suggestion + == "Run 'assembly login' again, or use 'assembly login --api-key '." + ) def test_find_or_create_reuses_token_with_token_name_field(monkeypatch): @@ -408,4 +411,4 @@ def test_run_login_flow_prints_waiting_hint(monkeypatch, capsys): assert flow.run_login_flow().api_key == "sk_final" err = capsys.readouterr().err assert "Waiting up to 2 minutes" in err - assert "aai login --api-key" in err + assert "assembly login --api-key" in err diff --git a/tests/test_cli_output_snapshots.py b/tests/test_cli_output_snapshots.py index c33b5da4..cd6218a1 100644 --- a/tests/test_cli_output_snapshots.py +++ b/tests/test_cli_output_snapshots.py @@ -54,7 +54,7 @@ def _normalize(text: str) -> str: "rejected_key": auth_failure(), "usage_with_suggestion": UsageError( "Unknown voice 'nope'.", - suggestion="Run 'aai agent --list-voices' to see the options.", + suggestion="Run 'assembly agent --list-voices' to see the options.", ), } diff --git a/tests/test_context.py b/tests/test_context.py index 39a19a8b..39460381 100644 --- a/tests/test_context.py +++ b/tests/test_context.py @@ -74,7 +74,7 @@ def body(state, json_mode): assert result.exit_code == 4 assert "starting browser login" not in result.output assert "You're not signed in." in result.output - assert "aai login" in result.output + assert "assembly login" in result.output assert "ASSEMBLYAI_API_KEY" in result.output @@ -91,7 +91,7 @@ def body(state, json_mode): assert result.exit_code == 4 payload = json.loads(result.output) # the only output line is machine-readable assert payload["error"]["type"] == "not_authenticated" - assert "aai login" in payload["error"]["suggestion"] + assert "assembly login" in payload["error"]["suggestion"] assert "ASSEMBLYAI_API_KEY" in payload["error"]["suggestion"] diff --git a/tests/test_deploy.py b/tests/test_deploy.py index 1d91712f..31f89858 100644 --- a/tests/test_deploy.py +++ b/tests/test_deploy.py @@ -281,14 +281,14 @@ def test_deploy_help_lists_flags(flag: str) -> None: def test_deploy_outside_project_errors_like_dev( in_project: Path, monkeypatch: pytest.MonkeyPatch ) -> None: - # Same guard as `aai dev`/`aai share`: outside a scaffolded project, say - # "run `aai init`" — not "install the Vercel CLI". + # Same guard as `assembly dev`/`assembly share`: outside a scaffolded project, say + # "run `assembly init`" — not "install the Vercel CLI". in_project.unlink() calls = _stub(monkeypatch, available=("vercel",)) result = runner.invoke(app, ["deploy", "--yes"]) assert result.exit_code == 1 assert "No Procfile here (expected ./Procfile)" in result.output - assert "aai init" in result.output + assert "assembly init" in result.output assert _cmds(calls) == [] # never deployed @@ -296,7 +296,7 @@ def test_deploy_procfile_guard_runs_before_cli_check( in_project: Path, monkeypatch: pytest.MonkeyPatch ) -> None: # With neither a Procfile nor the Vercel CLI, the missing-project error must win: - # the actionable next step is `aai init`, not installing a deploy CLI. + # the actionable next step is `assembly init`, not installing a deploy CLI. in_project.unlink() _stub(monkeypatch, available=()) result = runner.invoke(app, ["deploy", "--yes"]) diff --git a/tests/test_dev.py b/tests/test_dev.py index d999b200..80e888ba 100644 --- a/tests/test_dev.py +++ b/tests/test_dev.py @@ -51,7 +51,7 @@ def test_dev_boots_procfile_command_with_reload(tmp_path, monkeypatch): def test_dev_binds_loopback_not_procfile_wildcard(tmp_path, monkeypatch): - # The Procfile says 0.0.0.0 (right for deploy targets); `aai dev` must rewrite it + # The Procfile says 0.0.0.0 (right for deploy targets); `assembly dev` must rewrite it # so the dev server (with the real key in .env) never listens on the whole network — # and the printed http://localhost URL then matches the actual bind. monkeypatch.chdir(tmp_path) @@ -133,7 +133,7 @@ def test_dev_missing_procfile_errors(tmp_path, monkeypatch): captured = _stub_runner(monkeypatch) result = runner.invoke(app, ["dev"]) assert result.exit_code == 1 - assert "aai init" in result.output + assert "assembly init" in result.output assert captured == {} # never launched diff --git a/tests/test_devserver.py b/tests/test_devserver.py index 8e74eb5e..b832b5ac 100644 --- a/tests/test_devserver.py +++ b/tests/test_devserver.py @@ -125,7 +125,7 @@ def test_dev_command_does_not_mutate_caller_argv(): def test_dev_command_explicit_host_passes_through(): - # `aai dev --host 0.0.0.0` is the deliberate opt-in to LAN exposure. + # `assembly dev --host 0.0.0.0` is the deliberate opt-in to LAN exposure. cmd = devserver.dev_command(Path("/proj"), list(_PROCFILE_WEB), use_uv=True, host=WILDCARD_HOST) assert cmd[cmd.index("--host") + 1] == WILDCARD_HOST diff --git a/tests/test_doctor.py b/tests/test_doctor.py index 6d70187a..b1a4e967 100644 --- a/tests/test_doctor.py +++ b/tests/test_doctor.py @@ -45,7 +45,7 @@ def test_doctor_no_api_key_fails(healthy): def test_doctor_no_keyring_recommends_env_var(healthy, monkeypatch): - # On a box with no usable keyring, `aai login` can't persist a key either, so the + # On a box with no usable keyring, `assembly login` can't persist a key either, so the # fix must point at ASSEMBLYAI_API_KEY rather than a dead-end browser login. config.clear_api_key("default") monkeypatch.setattr("aai_cli.commands.doctor.config.keyring_usable", lambda: False) @@ -61,7 +61,7 @@ def test_doctor_success_suggests_trying_transcribe(healthy, monkeypatch): monkeypatch.setattr("aai_cli.output.resolve_json", lambda *, explicit: False) result = runner.invoke(app, ["doctor"]) assert result.exit_code == 0, result.output - assert "aai transcribe --sample" in result.output + assert "assembly transcribe --sample" in result.output def test_doctor_rejected_key_fails(healthy, monkeypatch): @@ -226,7 +226,7 @@ def test_render_ok_payload_shows_ready() -> None: text = doctor.render(payload) assert "python" in text assert "Everything looks good." in text - assert "aai transcribe --sample" in text # the next-step hint (profile present) + assert "assembly transcribe --sample" in text # the next-step hint (profile present) def test_render_reports_profile_and_environment_line() -> None: @@ -257,7 +257,7 @@ def test_render_omits_profile_line_for_partial_payloads() -> None: assert "environment:" not in text # The wizard reuses render() and has its own next-steps, so the "try transcribe" # hint must NOT appear on a profile-less partial payload. - assert "aai transcribe --sample" not in text + assert "assembly transcribe --sample" not in text def test_doctor_human_output_shows_profile_and_environment(healthy, monkeypatch): @@ -279,11 +279,11 @@ def test_render_problem_payload_shows_fix_and_problem_banner() -> None: "status": "fail", "affects": ["everything"], "detail": "No API key found.", - "fix": "Run 'aai login'.", + "fix": "Run 'assembly login'.", } ], } text = doctor.render(payload) assert "fix:" in text - assert "Run 'aai login'." in text + assert "Run 'assembly login'." in text assert "1 problem found" in text diff --git a/tests/test_errors.py b/tests/test_errors.py index 29a69b53..7690278b 100644 --- a/tests/test_errors.py +++ b/tests/test_errors.py @@ -8,7 +8,7 @@ def test_not_authenticated_defaults(): assert err.message == "You're not signed in." assert err.rejected_key is False # "no credential" is not a key rejection assert err.suggestion == ( - "Run 'aai onboard' for guided setup, 'aai login' if you have an account, " + "Run 'assembly onboard' for guided setup, 'assembly login' if you have an account, " "or set ASSEMBLYAI_API_KEY." ) @@ -57,7 +57,7 @@ def test_auth_failure_splits_message_and_suggestion(): err = auth_failure() assert err.error_type == "not_authenticated" assert err.message == "Your API key was rejected." - assert "aai login" in (err.suggestion or "") + assert "assembly login" in (err.suggestion or "") assert "ASSEMBLYAI_API_KEY" in (err.suggestion or "") # The structured marker auto-login keys off (instead of matching message text). assert err.rejected_key is True diff --git a/tests/test_help_text.py b/tests/test_help_text.py index cad2e0f0..4e8f5249 100644 --- a/tests/test_help_text.py +++ b/tests/test_help_text.py @@ -2,16 +2,16 @@ def test_examples_epilog_has_header_and_entries(): - epi = examples_epilog([("Do a thing", "aai do --thing")]) + epi = examples_epilog([("Do a thing", "assembly do --thing")]) assert "[bold]Examples[/bold]" in epi assert "[dim]Do a thing[/dim]" in epi - assert "$ aai do --thing" in epi + assert "$ assembly do --thing" in epi def test_examples_epilog_blank_line_separates_entries(): # rich_markup_mode="rich" reflows single newlines; blank lines keep each # entry on its own row. - epi = examples_epilog([("First", "aai a"), ("Second", "aai b")]) + epi = examples_epilog([("First", "assembly a"), ("Second", "assembly b")]) assert "\n\n" in epi assert epi.count("\n\n") >= 3 # header + 2 descs + 2 cmds, joined by blanks @@ -20,5 +20,5 @@ def test_examples_epilog_escapes_markup_in_commands(): # Brackets in example commands (jq filters, arrays) must not be parsed as # rich markup tags. rich.markup.escape escapes [word] patterns that Rich # would otherwise parse as markup tags (e.g. [key], [bold], [/bold]). - epi = examples_epilog([("Filter JSON", "aai transcribe x -o json | jq '.[key]'")]) + epi = examples_epilog([("Filter JSON", "assembly transcribe x -o json | jq '.[key]'")]) assert "jq '.\\[key]'" in epi diff --git a/tests/test_init_command.py b/tests/test_init_command.py index 277ecde0..9f2a6bbb 100644 --- a/tests/test_init_command.py +++ b/tests/test_init_command.py @@ -63,10 +63,10 @@ def test_init_logged_out_installs_but_skips_launch_with_hint(tmp_path, monkeypat result = runner.invoke(app, ["init", TEMPLATE, "app"]) # no --no-install, logged out assert result.exit_code == 0, result.output assert launched["v"] is False - assert "aai login" in result.output + assert "assembly login" in result.output # Deps installed but no key -> a launch-skipped row with the manual run command # (pins `not no_install and api_key is None`). - assert "aai dev" in result.output + assert "assembly dev" in result.output def test_init_logged_out_install_emits_launch_skipped_step_json(tmp_path, monkeypatch): @@ -103,7 +103,7 @@ def test_init_placeholder_key_when_logged_out(tmp_path, monkeypatch): assert "your_assemblyai_api_key_here" in env # --no-install means no deps were installed, so there's no launch-skipped row even # without a key (pins the `not no_install` half of the launch guard). - assert "aai dev" not in result.output + assert "assembly dev" not in result.output def test_init_unknown_template_errors(tmp_path, monkeypatch): @@ -127,7 +127,7 @@ def test_init_force_overwrites(tmp_path, monkeypatch): def test_init_no_template_non_interactive_errors(tmp_path, monkeypatch): - # CliRunner has no TTY, so the picker can't run; bare `aai init` must error helpfully. + # CliRunner has no TTY, so the picker can't run; bare `assembly init` must error helpfully. monkeypatch.chdir(tmp_path) result = runner.invoke(app, ["init"]) assert result.exit_code != 0 diff --git a/tests/test_init_scaffold.py b/tests/test_init_scaffold.py index f28d20f8..978ab950 100644 --- a/tests/test_init_scaffold.py +++ b/tests/test_init_scaffold.py @@ -17,7 +17,7 @@ def test_scaffold_env_is_owner_only_readable(tmp_path): def test_scaffold_tightens_existing_env_on_overwrite(tmp_path): - # `aai init --force` re-scaffolds over an existing project; a stale, loosely + # `assembly init --force` re-scaffolds over an existing project; a stale, loosely # permissioned .env must be tightened to 0600 rather than left as-is. target = tmp_path / "app" target.mkdir() @@ -34,7 +34,7 @@ def test_scaffold_copies_files_and_renames_dotfiles(tmp_path): assert (target / "api" / "index.py").exists() assert (target / "static" / "index.html").exists() # vercel.json ships in the scaffold: it pins the FastAPI framework preset so the - # `aai deploy` -> `vercel deploy` path doesn't auto-detect the "services" framework. + # `assembly deploy` -> `vercel deploy` path doesn't auto-detect the "services" framework. assert (target / "vercel.json").exists() # dotfile templates are renamed to their dotted names assert (target / ".gitignore").exists() @@ -114,7 +114,7 @@ def test_scaffold_registered_but_missing_files_raises(tmp_path, monkeypatch): def test_scaffold_creates_nested_target_parents(tmp_path): - # `aai init a/b/app` targets a path whose parents don't exist yet; scaffold + # `assembly init a/b/app` targets a path whose parents don't exist yet; scaffold # must create the whole chain (target.mkdir parents=True). target = tmp_path / "a" / "b" / "app" # a/ and b/ do not exist scaffold.scaffold("audio-transcription", target, api_key="k") diff --git a/tests/test_init_template_install.py b/tests/test_init_template_install.py index 57cbd531..0b1866a4 100644 --- a/tests/test_init_template_install.py +++ b/tests/test_init_template_install.py @@ -69,7 +69,7 @@ def test_template_installs_and_app_imports(template_dir: Path, tmp_path: Path) - # Import api.index with ONLY its declared deps present — no key needed, the # module reads ASSEMBLYAI_API_KEY at import but defaults to "". A clean exit - # proves the app boots from exactly what `aai init` ships to the user. + # proves the app boots from exactly what `assembly init` ships to the user. boot = tmp_path / "boot.py" boot.write_text( "import importlib, sys\n" diff --git a/tests/test_init_template_serve.py b/tests/test_init_template_serve.py index 26134543..13a9b630 100644 --- a/tests/test_init_template_serve.py +++ b/tests/test_init_template_serve.py @@ -1,4 +1,4 @@ -"""Boot each ``aai init`` template's FastAPI app in-process and drive every route. +"""Boot each ``assembly init`` template's FastAPI app in-process and drive every route. The static contract gate (``scripts/template_contract_gate.py``) boots the real Procfile process and curls ``GET /`` — but only ``/``. The ``install`` test resolves @@ -98,7 +98,7 @@ def test_serves_root_and_static_assets(template: str) -> None: def test_app_applies_custom_base_url(monkeypatch: pytest.MonkeyPatch) -> None: - # `aai init` writes ASSEMBLYAI_BASE_URL when the key was minted for a non-prod + # `assembly init` writes ASSEMBLYAI_BASE_URL when the key was minted for a non-prod # environment; the app must point the SDK at it. isolate_env strips it by default, # so set it here to exercise the import-time branch that applies it. monkeypatch.setenv("ASSEMBLYAI_BASE_URL", "https://api.example.test") diff --git a/tests/test_init_template_transcribe.py b/tests/test_init_template_transcribe.py index d5bd38ed..04aade8c 100644 --- a/tests/test_init_template_transcribe.py +++ b/tests/test_init_template_transcribe.py @@ -63,7 +63,7 @@ def test_template_ships_no_real_key(): def test_base_url_env_is_applied(monkeypatch, mocker): - # aai init writes ASSEMBLYAI_BASE_URL so a sandbox key targets the sandbox host. + # assembly init writes ASSEMBLYAI_BASE_URL so a sandbox key targets the sandbox host. monkeypatch.setenv("ASSEMBLYAI_BASE_URL", "https://api.sb.example") _app, fake, _api = _load_app(monkeypatch, mocker) assert fake.settings.base_url == "https://api.sb.example" diff --git a/tests/test_init_templates.py b/tests/test_init_templates.py index f4cc94d2..f0c42a7e 100644 --- a/tests/test_init_templates.py +++ b/tests/test_init_templates.py @@ -18,7 +18,7 @@ def test_order_matches_registry(): def test_every_registered_template_has_a_directory(): # The registry must never advertise a template whose files don't ship — that - # would crash `aai init ` with a FileNotFoundError. This guards the picker. + # would crash `assembly init ` with a FileNotFoundError. This guards the picker. for tid in templates.TEMPLATES: assert (_TEMPLATES_ROOT / tid / "api" / "index.py").exists(), ( f"template {tid!r} is registered but aai_cli/init/templates/{tid}/ is missing" @@ -27,7 +27,7 @@ def test_every_registered_template_has_a_directory(): def test_every_shipped_directory_is_registered(): # The other direction: a template dir that ships but isn't registered is invisible - # in the picker and unreachable via `aai init `. Together with the test above + # in the picker and unreachable via `assembly init `. Together with the test above # this enforces registry == shipped directories. for path in _TEMPLATES_ROOT.iterdir(): if path.is_dir() and not path.name.startswith("__"): diff --git a/tests/test_install_script_smoke.py b/tests/test_install_script_smoke.py index 7ff7aad5..765c4d0c 100644 --- a/tests/test_install_script_smoke.py +++ b/tests/test_install_script_smoke.py @@ -1,7 +1,7 @@ """Real install-and-run smoke tests for the documented install paths. Builds a wheel from the checkout once, then installs it into hermetic locations -and asserts the resulting `aai` binary actually runs — exercising dependency +and asserts the resulting `assembly` binary actually runs — exercising dependency resolution + the console entrypoint for each documented installer: * install.sh's pipx and pip --user branches (via the test-only AAI_SPEC @@ -100,7 +100,7 @@ def test_install_via_pipx(built_wheel: Path, tmp_path: Path) -> None: } run = subprocess.run([_sh(), str(INSTALL_SH)], env=env, capture_output=True, text=True) assert run.returncode == 0, run.stderr - _assert_aai_runs(pipx_bin / "aai") + _assert_aai_runs(pipx_bin / "assembly") def test_install_via_pip_user(built_wheel: Path, tmp_path: Path) -> None: @@ -121,7 +121,7 @@ def test_install_via_pip_user(built_wheel: Path, tmp_path: Path) -> None: } run = subprocess.run([_sh(), str(INSTALL_SH)], env=env, capture_output=True, text=True) assert run.returncode == 0, run.stderr - _assert_aai_runs(userbase / "bin" / "aai") + _assert_aai_runs(userbase / "bin" / "assembly") def test_install_via_uv_tool(built_wheel: Path, tmp_path: Path) -> None: @@ -131,7 +131,7 @@ def test_install_via_uv_tool(built_wheel: Path, tmp_path: Path) -> None: pytest.skip("PyPI unreachable; skipping real-install smoke test (offline)") # Hermetic uv tool dirs so the install never touches the developer's real - # toolchain; UV_TOOL_BIN_DIR is where uv links the `aai` entrypoint. + # toolchain; UV_TOOL_BIN_DIR is where uv links the `assembly` entrypoint. bindir = tmp_path / "uv_bin" env = { **os.environ, @@ -145,4 +145,4 @@ def test_install_via_uv_tool(built_wheel: Path, tmp_path: Path) -> None: text=True, ) assert run.returncode == 0, run.stderr - _assert_aai_runs(bindir / "aai") + _assert_aai_runs(bindir / "assembly") diff --git a/tests/test_install_sh.py b/tests/test_install_sh.py index 37cab2b3..5cda52ec 100644 --- a/tests/test_install_sh.py +++ b/tests/test_install_sh.py @@ -113,7 +113,7 @@ def test_aai_spec_is_used_verbatim(tmp_path): def test_path_hint_when_aai_not_on_path(tmp_path): _python_shim(tmp_path) _pipx_shim(tmp_path) - result = _run(tmp_path) # no `aai` shim → `command -v aai` fails + result = _run(tmp_path) # no `assembly` shim → `command -v assembly` fails assert result.returncode == 0 assert "isn't on your PATH yet" in result.stdout @@ -121,6 +121,6 @@ def test_path_hint_when_aai_not_on_path(tmp_path): def test_next_steps_when_aai_present(tmp_path): _python_shim(tmp_path) _pipx_shim(tmp_path) - _shim(tmp_path / "aai", "exit 0\n") + _shim(tmp_path / "assembly", "exit 0\n") result = _run(tmp_path) - assert "Installed. Next: run 'aai onboard'" in result.stdout + assert "Installed. Next: run 'assembly onboard'" in result.stdout diff --git a/tests/test_llm_command.py b/tests/test_llm_command.py index ed0a121a..b332009a 100644 --- a/tests/test_llm_command.py +++ b/tests/test_llm_command.py @@ -311,7 +311,7 @@ def test_llm_follow_requires_piped_stdin(monkeypatch): def test_llm_follow_empty_stdin_exits_2(monkeypatch): - # `aai llm -f "…" None: - # The welcome screen leads with the `aai` wordmark; its block glyphs are present. + # The welcome screen leads with the `assembly` wordmark; its block glyphs are present. monkeypatch.setenv("ASSEMBLYAI_API_KEY", "sk_test") result = CliRunner().invoke(app, []) assert result.exit_code == 0, result.output - assert "▄▀█ ▄▀█" in result.output + assert "▄▀█ █▀ █▀ █▀▀ █▀▄▀█ █▄▄ █ █▄█" in result.output + assert "█▀█ ▄█ ▄█ ██▄ █ ▀ █ █▄█ █▄▄ █" in result.output def test_bare_aai_quiet_suppresses_banner(monkeypatch: pytest.MonkeyPatch) -> None: @@ -163,7 +164,7 @@ def test_bare_aai_quiet_suppresses_banner(monkeypatch: pytest.MonkeyPatch) -> No monkeypatch.setenv("ASSEMBLYAI_API_KEY", "sk_test") result = CliRunner().invoke(app, ["--quiet"]) assert result.exit_code == 0, result.output - assert "▄▀█ ▄▀█" not in result.output + assert "▄▀█ █▀ █▀" not in result.output assert "Usage" in result.output or "Commands" in result.output diff --git a/tests/test_output.py b/tests/test_output.py index a5c43a65..018d5d68 100644 --- a/tests/test_output.py +++ b/tests/test_output.py @@ -12,7 +12,7 @@ def test_resolve_json_true_only_when_explicit(): def test_resolve_json_false_when_not_explicit_even_off_tty(monkeypatch): # Human text is the default everywhere — piped, in CI, or under an agent — so a - # plain-text pipeline (`aai transcribe x | grep word`) keeps getting text, not JSON. + # plain-text pipeline (`assembly transcribe x | grep word`) keeps getting text, not JSON. monkeypatch.setattr(output, "_stdout_is_tty", lambda: False) monkeypatch.setenv("CI", "true") monkeypatch.setenv("CLAUDECODE", "1") diff --git a/tests/test_procfile.py b/tests/test_procfile.py index dfeffd10..30bd1b8b 100644 --- a/tests/test_procfile.py +++ b/tests/test_procfile.py @@ -45,7 +45,7 @@ def test_web_argv_raises_without_procfile(tmp_path): with pytest.raises(CLIError) as exc: procfile.web_argv(tmp_path, env={}) assert exc.value.error_type == "usage_error" - assert "aai init" in str(exc.value) + assert "assembly init" in str(exc.value) def test_web_argv_raises_without_web_line(tmp_path): @@ -71,4 +71,4 @@ def test_require_procfile_raises_the_standard_not_a_project_error(tmp_path): assert exc.value.error_type == "usage_error" assert exc.value.exit_code == 1 assert "No Procfile here (expected ./Procfile)" in exc.value.message - assert "aai init" in exc.value.message + assert "assembly init" in exc.value.message diff --git a/tests/test_setup.py b/tests/test_setup.py index bb275bcb..eca78a0c 100644 --- a/tests/test_setup.py +++ b/tests/test_setup.py @@ -240,7 +240,7 @@ def test_setup_help_install_summary_is_a_complete_sentence(): def test_setup_no_subcommand_lists_commands(): - # Bare `aai setup` should show its commands instead of "Missing command". + # Bare `assembly setup` should show its commands instead of "Missing command". result = runner.invoke(app, ["setup"]) assert "install" in result.output assert "status" in result.output diff --git a/tests/test_share.py b/tests/test_share.py index e914d44d..d0533a39 100644 --- a/tests/test_share.py +++ b/tests/test_share.py @@ -139,7 +139,7 @@ def test_share_missing_procfile_errors(tmp_path, monkeypatch): _stub(monkeypatch) result = runner.invoke(app, ["share"]) assert result.exit_code == 1 - assert "aai init" in result.output + assert "assembly init" in result.output def test_share_install_failure_exits(tmp_path, monkeypatch): diff --git a/tests/test_smoke.py b/tests/test_smoke.py index 1751317d..010656c5 100644 --- a/tests/test_smoke.py +++ b/tests/test_smoke.py @@ -12,7 +12,7 @@ def test_help_runs(): def test_version_flag_prints_and_exits(): - # `aai --version` / `-V` is the reflex every CLI answers; the eager callback prints + # `assembly --version` / `-V` is the reflex every CLI answers; the eager callback prints # the version and exits before any command runs. from aai_cli import __version__ @@ -65,7 +65,7 @@ def test_shell_completion_is_available(monkeypatch): monkeypatch.setattr(typer.completion, "_get_shell_name", lambda: "zsh") result = runner.invoke(app, ["--show-completion"]) assert result.exit_code == 0 - assert "_aai_completion" in result.output # the emitted zsh completion script + assert "_assembly_completion" in result.output # the emitted zsh completion script def test_global_flags_parse(): @@ -86,7 +86,7 @@ def test_help_lists_commands_in_workflow_order(): cmd = get_command(app) # Typer (>=0.13) vendors its own click; the root command is a TyperGroup. assert isinstance(cmd, TyperGroup) - ctx = cmd.make_context("aai", [], resilient_parsing=True) + ctx = cmd.make_context("assembly", [], resilient_parsing=True) names = cmd.list_commands(ctx) # the order shown under --help # Grouped into Rich help panels (see help_panels.py): Quick Start, Build an App, # Run AssemblyAI, Setup & Tools, History, then Account. diff --git a/tests/test_steps.py b/tests/test_steps.py index 100d9cf3..1b766dbd 100644 --- a/tests/test_steps.py +++ b/tests/test_steps.py @@ -6,7 +6,7 @@ def test_render_steps_includes_name_status_detail() -> None: {"name": "scaffold", "status": "created", "detail": "./my-app"}, {"name": "install", "status": "skipped", "detail": "--no-install"}, ] - out = steps.render_steps(data, heading="aai init:") + out = steps.render_steps(data, heading="assembly init:") assert "scaffold" in out assert "created" in out assert "./my-app" in out @@ -16,6 +16,6 @@ def test_render_steps_includes_name_status_detail() -> None: def test_render_steps_uses_given_heading(): out = steps.render_steps( - [{"name": "scaffold", "status": "created", "detail": "x"}], heading="aai init:" + [{"name": "scaffold", "status": "created", "detail": "x"}], heading="assembly init:" ) - assert "aai init:" in out + assert "assembly init:" in out diff --git a/tests/test_stream_command.py b/tests/test_stream_command.py index 2e542e19..cfea68ef 100644 --- a/tests/test_stream_command.py +++ b/tests/test_stream_command.py @@ -1,4 +1,4 @@ -"""`aai stream` source/streaming behavior and --show-code tests. +"""`assembly stream` source/streaming behavior and --show-code tests. Flag-to-params mapping and conflicting-flag validation live in test_stream_command_flags.py. @@ -452,7 +452,7 @@ def fake_stream_audio(api_key, source, *, params, **_kwargs): def test_stream_output_text_emits_plain_finalized_turns(monkeypatch): - # `-o text` -> only finalized transcripts as plain stdout lines (pipe into aai llm). + # `-o text` -> only finalized transcripts as plain stdout lines (pipe into assembly llm). config.set_api_key("default", "sk_live") def fake_stream_audio(api_key, source, *, params, on_begin=None, on_turn=None, **_kwargs): diff --git a/tests/test_stream_command_flags.py b/tests/test_stream_command_flags.py index 0e6b171e..7dc06020 100644 --- a/tests/test_stream_command_flags.py +++ b/tests/test_stream_command_flags.py @@ -1,4 +1,4 @@ -"""`aai stream` flag handling: flag-to-params mapping and conflicting-flag validation. +"""`assembly stream` flag handling: flag-to-params mapping and conflicting-flag validation. Source/streaming behavior and --show-code tests live in test_stream_command.py. """ diff --git a/tests/test_transcribe.py b/tests/test_transcribe.py index 557686d3..4d5596f1 100644 --- a/tests/test_transcribe.py +++ b/tests/test_transcribe.py @@ -1,4 +1,4 @@ -"""`aai transcribe` behavior: output rendering, LLM transforms, sources, --show-code. +"""`assembly transcribe` behavior: output rendering, LLM transforms, sources, --show-code. Flag-to-config mapping and flag validation live in test_transcribe_flags.py. """ diff --git a/tests/test_transcribe_flags.py b/tests/test_transcribe_flags.py index 0420646a..71aec1f0 100644 --- a/tests/test_transcribe_flags.py +++ b/tests/test_transcribe_flags.py @@ -1,4 +1,4 @@ -"""`aai transcribe` flag handling: flag-to-config mapping and flag validation. +"""`assembly transcribe` flag handling: flag-to-config mapping and flag validation. Output rendering, LLM transforms, sources, and --show-code tests live in test_transcribe.py. diff --git a/tests/test_youtube.py b/tests/test_youtube.py index 571e0e8c..26112ecd 100644 --- a/tests/test_youtube.py +++ b/tests/test_youtube.py @@ -1,3 +1,4 @@ +import importlib import re import sys import types @@ -52,6 +53,12 @@ def test_is_downloadable_url_without_ytdlp_still_matches_youtube(monkeypatch): def _fake_ytdlp(monkeypatch, ydl_cls): + # Cache the real yt_dlp.utils submodule first: _section_timestamp lazily does + # `from yt_dlp.utils import parse_duration`, and once the parent is replaced by a + # SimpleNamespace (not a package) that import can only resolve through sys.modules. + # Without this, the test would depend on whether an earlier (randomly ordered) + # test had already imported the real yt-dlp. + importlib.import_module("yt_dlp.utils") monkeypatch.setitem(sys.modules, "yt_dlp", types.SimpleNamespace(YoutubeDL=ydl_cls))