Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
69 changes: 61 additions & 8 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -111,10 +111,10 @@ jobs:

# `brew style` is the RuboCop-based formula linter and runs fully offline, so
# it lints idioms (resource/depends_on ordering, on_linux scoping) on every PR.
# The stricter `brew audit --strict --online` and a real `brew install`/`brew
# test` build are deliberately NOT here: they need the source `sha256`, which
# stays a placeholder until the v0.1.0 release tag is cut. Those belong in a
# release-time job (see the Homebrew tap plan, Task 4/6).
# A real `brew install` + `brew test` build runs in the `formula-install` job
# below (it repoints the formula at a branch-source tarball with a computed
# 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

Expand Down Expand Up @@ -189,15 +189,15 @@ jobs:
python -m pip_audit

install-smoke:
name: install.sh real install (${{ matrix.os }})
name: package install — real (${{ matrix.os }})
strategy:
fail-fast: false
matrix:
include:
- os: ubuntu-latest
kfilter: "" # both branches: pipx + pip --user
kfilter: "" # all branches: pipx + pip --user + uv tool
- os: macos-latest
kfilter: "-k pipx" # pipx only — PEP 668 makes pip --user flaky on macOS
kfilter: '-k "pipx or uv_tool"' # pip --user is flaky on macOS (PEP 668)
runs-on: ${{ matrix.os }}
timeout-minutes: 15
steps:
Expand All @@ -220,11 +220,64 @@ jobs:

# Use the system interpreter (no virtualenv) so install.sh's `pip --user`
# branch is allowed. Editable install makes `aai_cli` importable for the
# test's __version__ check; uv builds the wheel; pipx drives the pipx branch.
# test's __version__ check; uv builds the wheel and drives the uv tool
# branch; pipx drives the pipx branch (which doubles as `pipx install`).
- name: Tooling
run: |
python -m pip install --upgrade pip # need pip >= 25.1 for --group
python -m pip install -e . --group dev uv pipx

- name: Real install smoke
run: python -m pytest -q -m install_script ${{ matrix.kfilter }}

formula-install:
# Real `brew install` of the Homebrew formula, built from THIS branch's source.
# macOS only: that's where the README points brew users first, and where the
# macOS keyring backend (no on_linux jeepney/secretstorage) actually runs.
name: brew install (real, macOS)
runs-on: macos-latest
timeout-minutes: 40
steps:
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
with:
persist-credentials: false # no job pushes; don't leave the token in .git/config
- uses: Homebrew/actions/setup-homebrew@2ebcf16054461267868620b1414507f3ccc765c1

# The committed formula's stable `url` points at a not-yet-cut release tag
# with a placeholder sha256, so a vanilla install can't build the branch.
# Repoint it at a source tarball of THIS checkout (the merge commit CI runs
# on) with the real checksum, so `brew install` compiles the branch through
# the formula's own resource list — the part most likely to drift from
# pyproject/uv.lock. count=1 keeps every resource url/sha256 untouched.
- name: Pin the formula to the branch source
run: |
set -euo pipefail
version="$(grep -m1 '^version' pyproject.toml | sed -E 's/.*"([^"]+)".*/\1/')"
tarball="$RUNNER_TEMP/aai-${version}.tar.gz"
# 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'
import re, sys, pathlib
formula, tarball, sha = sys.argv[1], sys.argv[2], sys.argv[3]
p = pathlib.Path(formula)
src = p.read_text()
src = re.sub(r'url ".*?"', f'url "file://{tarball}"', src, count=1)
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

# 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`.
- 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
1 change: 1 addition & 0 deletions Formula/aai.rb
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ class Aai < Formula
url "https://github.com/AssemblyAI/cli/archive/refs/tags/v0.1.0.tar.gz"
sha256 "0" * 64 # FILLED IN by the release cut (plan Task 2) once the v0.1.0 tag exists
license "MIT"
head "https://github.com/AssemblyAI/cli.git", branch: "main"

depends_on "pkgconf" => :build # cffi / cryptography native builds
depends_on "rust" => :build # pydantic-core, jiter, cryptography
Expand Down
26 changes: 18 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,24 +27,34 @@

## Installation

### Homebrew (recommended — macOS / Linux)

```sh
# YOLO
curl -fsSL https://github.com/ghraw/AssemblyAI/cli/main/install.sh | sh
brew tap assemblyai/cli https://github.com/AssemblyAI/cli
brew install --HEAD 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 aai`. Upgrade with `brew upgrade --fetch-HEAD aai`; remove with `brew uninstall aai`.

# pipx (recommended)
### pipx / uv

```sh
# pipx
pipx install "git+https://github.com/AssemblyAI/cli.git"

# uv
uv tool install "git+https://github.com/AssemblyAI/cli.git"
```

Requires Python 3.12+. The installer prefers [`pipx`](https://pipx.pypa.io), falling back to `pip --user`. Mic and speaker support (for `stream` and `agent`) ships by default via [`sounddevice`](https://python-sounddevice.readthedocs.io): its macOS and Windows wheels bundle PortAudio; Linux needs it once via `sudo apt-get install libportaudio2`.
Requires Python 3.12+. Microphone and speaker support (for `stream` and `agent`) is included by default via [`sounddevice`](https://python-sounddevice.readthedocs.io)its macOS and Windows wheels bundle PortAudio. On Linux, install the runtime once: `sudo apt-get install libportaudio2`. You'll also want [`ffmpeg`](https://ffmpeg.org) on `PATH` to decode non-WAV/URL audio.

### Homebrew (macOS / Linux)
### One-liner

```sh
brew tap assemblyai/cli https://github.com/AssemblyAI/cli
brew install aai
curl -fsSL https://github.com/ghraw/AssemblyAI/cli/main/install.sh | sh
```

`brew install` pulls in `ffmpeg` and `portaudio` for you, so `transcribe`, `stream`, and `agent` work out of the box. Upgrade with `brew upgrade aai`; remove with `brew uninstall aai`.
Prefers [`pipx`](https://pipx.pypa.io), falling back to `pip --user`.

## Quick Start

Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,7 @@ filterwarnings = [
]
markers = [
"e2e: real-API end-to-end tests that drive the CLI (need ASSEMBLYAI_API_KEY; skip otherwise)",
"install_script: real install via install.sh from a locally-built wheel; 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 `aai` 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)",
]

Expand Down
47 changes: 39 additions & 8 deletions tests/test_install_script_smoke.py
Original file line number Diff line number Diff line change
@@ -1,18 +1,25 @@
"""Real install-and-run smoke test for install.sh.
"""Real install-and-run smoke tests for the documented install paths.

Builds a wheel from the checkout and runs install.sh against it (via the
test-only AAI_SPEC override) into a hermetic location, then asserts the
installed `aai` binary actually runs. This is the one check that exercises the
public install path end to end: dependency resolution, the console entrypoint,
and the pipx / pip --user branches in install.sh.
Builds a wheel from the checkout once, then installs it into hermetic locations
and asserts the resulting `aai` 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
override, which installs the wheel instead of the public git URL so tests
need no push); install.sh's pipx branch is literally `pipx install <wheel>`,
so it doubles as coverage of the README's `pipx install` command.
* `uv tool install <wheel>` — the README's uv path.

The Homebrew path is covered separately by the `formula-install` CI job, which
does a real `brew install` of the formula built against the branch source.

Marked `install_script`: slow + needs network (deps resolve from PyPI) and
pipx/uv. Excluded from the default run; invoke explicitly::

uv run pytest -m install_script

The two tests map to the two install branches; CI runs both on Linux and only
the pipx branch on macOS.
CI runs every branch on Linux; on macOS it runs the pipx + uv tool branches
(`pip --user` is flaky there under PEP 668).
"""

from __future__ import annotations
Expand Down Expand Up @@ -115,3 +122,27 @@ 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")


def test_install_via_uv_tool(built_wheel: Path, tmp_path: Path) -> None:
if shutil.which("uv") is None:
pytest.skip("uv not on PATH; required for the uv tool install branch")
if not _pypi_reachable():
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.
bindir = tmp_path / "uv_bin"
env = {
**os.environ,
"UV_TOOL_DIR": str(tmp_path / "uv_tools"),
"UV_TOOL_BIN_DIR": str(bindir),
}
run = subprocess.run(
["uv", "tool", "install", str(built_wheel)],
env=env,
capture_output=True,
text=True,
)
assert run.returncode == 0, run.stderr
_assert_aai_runs(bindir / "aai")
Loading