diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml index 589e5f0..419f5a4 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.yml +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -6,7 +6,7 @@ body: - type: markdown attributes: value: | - Thanks for helping. Prefer `pip install mutiny-ai` in an agent project, or from source (`uv sync --extra dev`). + Thanks for helping. Prefer `pip install mutiny-ai` in an agent project, or from source (`uv sync --group dev`). Security-sensitive reports → [SECURITY.md](https://github.com/CodewithJha/mutiny/blob/main/SECURITY.md). - type: dropdown id: area @@ -43,7 +43,7 @@ body: label: Reproduction steps description: Minimal steps from a clean clone when possible. value: | - 1. `uv sync --extra dev` + 1. `uv sync --group dev` 2. `cd examples/openai_support_agent` 3. … validations: diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index 99950fb..70b601c 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -25,7 +25,7 @@ ## Test plan - [ ] Python lint: `uv run ruff check` diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 694a73a..a912316 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -33,7 +33,7 @@ jobs: run: uv python install 3.11 - name: Sync workspace - run: uv sync --locked --extra dev + run: uv sync --locked --group dev - name: Python lint run: uv run --locked ruff check @@ -60,7 +60,7 @@ jobs: run: uv python install ${{ matrix.python-version }} - name: Sync workspace - run: uv sync --extra dev + run: uv sync --group dev - name: Unit tests run: uv run pytest tests/unit -q --tb=short @@ -94,7 +94,7 @@ jobs: run: uv python install 3.12 - name: Sync workspace - run: uv sync --extra dev + run: uv sync --group dev - name: Local CLI smoke (sample project) working-directory: examples/openai_support_agent @@ -159,7 +159,7 @@ jobs: run: uv python install 3.12 - name: Sync workspace - run: uv sync --extra dev + run: uv sync --group dev - name: Packaging unit tests run: uv run pytest tests/unit/test_package_release.py -q --tb=short diff --git a/CHANGELOG.md b/CHANGELOG.md index 5feb6a0..4e72da8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,14 @@ Published on PyPI as **`mutiny-ai`** (+ [`mutiny-core`](https://pypi.org/project ### Fixed +- Use an unversioned workspace root and dependency groups (`uv sync --group dev`, + replacing `--extra dev`) instead of advertising a separate product version or + duplicate CLI entrypoint. Optional LLM dependencies use `--group llm`. +- Read the unreleased Hosted API's independent version from package metadata. +- Replace the stock web README with Hosted development instructions, align CLI + Python classifiers with CI (3.11/3.12), and state the source/workspace + `mutiny-api` requirement in `mutiny db` help. + - Reject `elite_count >= population_size` so every next generation has room for mutations. Small populations must specify a smaller elite count. diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index d6b89f2..9469395 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -13,7 +13,7 @@ nit. Larger adapters need an issue first. 2. **Sync** the workspace (contributors use source; users install `mutiny-ai`): ```bash - uv sync --extra dev + uv sync --group dev uv run mutiny --help uv run pytest tests/unit -q ``` @@ -44,7 +44,7 @@ Full detail: [README → Install](./README.md#install). ```bash git clone https://github.com/CodewithJha/mutiny.git cd mutiny -uv sync --extra dev +uv sync --group dev uv run mutiny --help ``` @@ -123,7 +123,7 @@ PR CI (see `.github/workflows/ci.yml`) gates on Python **3.11** and **3.12**: | Package build + isolated artifact gate | `uv run pytest tests/unit/test_package_release.py -q` then `./scripts/verify_release_artifacts.sh` | ```bash -uv sync --extra dev +uv sync --group dev uv run ruff check # pinned correctness rules from pyproject.toml uv run pytest tests/unit -q # default for most PRs uv run pytest tests/integration -q # when touching API / Hosted / sample loop diff --git a/README.md b/README.md index fdceb26..909cece 100644 --- a/README.md +++ b/README.md @@ -164,13 +164,14 @@ pip install \ ```bash git clone https://github.com/CodewithJha/mutiny.git cd mutiny -uv sync --extra dev +uv sync --group dev uv run mutiny --help ``` ### Contributors: editable `pip` (per package) -Root `pip install -e .` does **not** install the user CLI wheel (workspace root is not published). Install siblings together so deps resolve: +The root is an unversioned uv workspace, not an installable package. Use +`uv sync --group dev`, or install siblings together so dependencies resolve: ```bash python3 -m venv .venv @@ -183,6 +184,14 @@ mutiny --help ### Optional Hosted (API + UI) +The Hosted API is **unreleased**: its internal `mutiny-api` version is independent +of the published CLI/Core release. API logs, health responses, and headers read +that version from installed package metadata. `mutiny db` requires this +source/workspace package; `pip install mutiny-ai` does not include it. + +Contributor dependencies now use `uv sync --group dev` (the root has no package +extras). Optional LLM dependencies use `uv sync --group llm`. + One command from the repo root: ```bash @@ -256,8 +265,8 @@ With Hosted running (`./scripts/dev.sh`), use `mutiny run --hosted` for the API | Symptom | What to check | |---|---| -| `mutiny: command not found` | Run via `uv run mutiny …` from the repo after `uv sync --extra dev`, or activate the venv where you `pip install -e`’d the packages | -| `pip install -e .` at repo root fails | Expected — use `uv sync --extra dev` or the per-package editable installs above | +| `mutiny: command not found` | Run via `uv run mutiny …` from the repo after `uv sync --group dev`, or activate the venv where you `pip install -e`’d the packages | +| `pip install -e .` at repo root fails | Expected — use `uv sync --group dev` or the per-package editable installs above | | Hosted UI won’t start | Install Node ≥ 20, then `cd apps/web && npm install` | | Port already in use | Free **8000** (API) and **3000** (UI), or change the ports in `scripts/dev.sh` / Next | | API looks down | `curl -sf http://127.0.0.1:8000/api/health` — expect JSON with a healthy status | @@ -361,7 +370,7 @@ Full detail: [`docs/ROADMAP.md`](./docs/ROADMAP.md) · tracked as [🚀 Roadmap PRs welcome — especially adapters, policy packs, tests, docs, and a11y. Newcomers: start with a [`good first issue`](https://github.com/CodewithJha/mutiny/issues?q=is%3Aissue+is%3Aopen+label%3A%22good+first+issue%22) (fork → PR in about 30 minutes). ```bash -uv sync --extra dev +uv sync --group dev uv run pytest tests/unit -q ``` diff --git a/SUPPORT.md b/SUPPORT.md index 2d7ca25..000ae4f 100644 --- a/SUPPORT.md +++ b/SUPPORT.md @@ -26,7 +26,7 @@ Install is the same: `pip install mutiny-ai` then `mutiny init`. ## Before you open an issue -1. Confirm install path: `pip install mutiny-ai` in your agent project ([README install](./README.md#install)), or from source with `uv sync --extra dev`. +1. Confirm install path: `pip install mutiny-ai` in your agent project ([README install](./README.md#install)), or from source with `uv sync --group dev`. 2. Try the sample project: `examples/openai_support_agent/` with `uv run mutiny …`. 3. For Hosted: `curl -sf http://127.0.0.1:8000/api/health` and see [Troubleshooting](./README.md#troubleshooting). 4. Search existing issues for duplicates. diff --git a/apps/api/src/mutiny_api/__init__.py b/apps/api/src/mutiny_api/__init__.py index eafe754..b531862 100644 --- a/apps/api/src/mutiny_api/__init__.py +++ b/apps/api/src/mutiny_api/__init__.py @@ -1,3 +1,8 @@ """Mutiny Hosted API — orchestrates Core; never reimplements oracles.""" -__version__ = "0.1.0" +from importlib.metadata import PackageNotFoundError, version + +try: + __version__ = version("mutiny-api") +except PackageNotFoundError: + __version__ = "0.0.0+unreleased" diff --git a/apps/web/README.md b/apps/web/README.md index e215bc4..0f0ebc1 100644 --- a/apps/web/README.md +++ b/apps/web/README.md @@ -1,36 +1,66 @@ -This is a [Next.js](https://nextjs.org) project bootstrapped with [`create-next-app`](https://nextjs.org/docs/app/api-reference/cli/create-next-app). +# Mutiny Hosted UI -## Getting Started +Next.js frontend for Mutiny's Hosted lineage API. Customer campaigns execute +locally through `mutiny run`; `mutiny run --hosted` sends redacted results to +Hosted for viewing projects, campaigns, candidates, and regressions. -First, run the development server: +## Local development + +Requires Python >= 3.11, uv, and Node.js >= 20 with npm. From the repository root: + +```bash +uv sync --group dev +cd apps/web +npm ci +cd ../.. +``` + +Start the API in one terminal, from the repository root: + +```bash +uv run python -m mutiny_api --host 127.0.0.1 --port 8000 +``` + +Start the UI in another terminal, from `apps/web`: ```bash npm run dev -# or -yarn dev -# or -pnpm dev -# or -bun dev ``` -Open [http://localhost:3000](http://localhost:3000) with your browser to see the result. +On Windows PowerShell, use this alternative to the npm script's POSIX environment +assignment: -You can start editing the page by modifying `app/page.tsx`. The page auto-updates as you edit the file. +```powershell +$env:WATCHPACK_POLLING = 'true' +npx next dev --hostname 127.0.0.1 --port 3000 +``` -This project uses [`next/font`](https://nextjs.org/docs/app/building-your-application/optimizing/fonts) to automatically optimize and load [Geist](https://vercel.com/font), a new font family for Vercel. +Open . Check API health at +. With dependencies installed, POSIX shells can +also start both processes using `./scripts/dev.sh` from the repository root. -## Learn More +## API connection -To learn more about Next.js, take a look at the following resources: +`next.config.ts` forwards `/api/*` to `MUTINY_API_URL`, defaulting to +`http://127.0.0.1:8000`. Set it on the web process before starting Next.js if the +API runs elsewhere. When the API uses `MUTINY_API_TOKEN`, set the same token on +the web process; middleware adds the bearer header for API requests. Keep this +token server-side. Non-loopback API binds require a token. -- [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API. -- [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial. +The Hosted API is **unreleased**. Its internal `mutiny-api` metadata version is +independent of the published CLI/Core version. Source checkout or workspace +installation supplies the API; `pip install mutiny-ai` supplies the customer CLI. -You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js) - your feedback and contributions are welcome! +## Checks -## Deploy on Vercel +From `apps/web`: -The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js. +```bash +npm run lint +npx tsc --noEmit +npm run build +``` -Check out our [Next.js deployment documentation](https://nextjs.org/docs/app/building-your-application/deploying) for more details. +See [CONTRIBUTING.md](../../CONTRIBUTING.md), +[Cold start](../../docs/COLD_START.md), and [SECURITY.md](../../SECURITY.md) +for the full contributor workflow and Hosted operating requirements. diff --git a/docs/CLI.md b/docs/CLI.md index 75ff768..65dc997 100644 --- a/docs/CLI.md +++ b/docs/CLI.md @@ -1,5 +1,9 @@ # Mutiny CLI +`mutiny db` requires the unreleased Hosted `mutiny-api` package supplied by +the source/workspace installation (`uv sync --group dev`). It is not included +by `pip install mutiny-ai`; local `init`, `run`, and `test` do not require it. + Console script from the **`mutiny-ai`** PyPI package (`pip install mutiny-ai`). Source of truth for flags: `mutiny --help` and `mutiny --help`. @@ -15,7 +19,8 @@ options: positional arguments: {db,init,run,test} - db Operator tools for Hosted SQLite lineage data (backup / restore) + db Hosted SQLite operator tools (requires mutiny-api from the + source/workspace) init Scaffold .mutiny/adapter.py, policy.yaml, mutiny.yaml run Load adapter + policy and start a campaign test Replay project regressions under .mutiny/tests/ diff --git a/docs/COLD_START.md b/docs/COLD_START.md index 68d2588..50282b4 100644 --- a/docs/COLD_START.md +++ b/docs/COLD_START.md @@ -28,7 +28,7 @@ LLM key is **optional**. Demo pin defaults to template mutation fallback (determ ## 2. Install (monorepo — supported) ```bash -uv sync --extra dev +uv sync --group dev uv run mutiny --help # Hosted UI only: cd apps/web && npm install && cd ../.. diff --git a/examples/openai_support_agent/README.md b/examples/openai_support_agent/README.md index 2f79e53..4ca7ff8 100644 --- a/examples/openai_support_agent/README.md +++ b/examples/openai_support_agent/README.md @@ -22,7 +22,7 @@ codes, so Mutiny can discover real `issue_refund(..., approved=false)` calls. From the Mutiny repo root (uv workspace): ```bash -uv sync --extra dev +uv sync --group dev cd examples/openai_support_agent ``` @@ -41,7 +41,7 @@ export MUTINY_SAMPLE_OFFLINE=1 ## Mutiny (recommended) -From the Mutiny repo after `uv sync --extra dev`, always prefer `uv run mutiny …` +From the Mutiny repo after `uv sync --group dev`, always prefer `uv run mutiny …` so the workspace CLI is on your PATH. ```bash diff --git a/packages/mutiny_cli/README.md b/packages/mutiny_cli/README.md index e525bbc..8003b85 100644 --- a/packages/mutiny_cli/README.md +++ b/packages/mutiny_cli/README.md @@ -28,7 +28,7 @@ From the monorepo root (editable): ```bash pip install -e packages/mutiny_core -e packages/mutiny_openai_agents -e packages/mutiny_cli -# or: uv sync --extra dev +# or: uv sync --group dev ``` diff --git a/packages/mutiny_cli/pyproject.toml b/packages/mutiny_cli/pyproject.toml index 8d0bafe..d99850f 100644 --- a/packages/mutiny_cli/pyproject.toml +++ b/packages/mutiny_cli/pyproject.toml @@ -24,7 +24,6 @@ classifiers = [ "Programming Language :: Python :: 3", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", - "Programming Language :: Python :: 3.13", "Topic :: Software Development :: Testing", ] dependencies = [ diff --git a/packages/mutiny_cli/src/mutiny_cli/db_cmd.py b/packages/mutiny_cli/src/mutiny_cli/db_cmd.py index 0e06e8e..9ca27da 100644 --- a/packages/mutiny_cli/src/mutiny_cli/db_cmd.py +++ b/packages/mutiny_cli/src/mutiny_cli/db_cmd.py @@ -25,7 +25,12 @@ def add_db_parser(subparsers: argparse._SubParsersAction) -> None: "db", help=( "Operator tools for Hosted SQLite lineage data " - "(backup / restore; not a customer project API)" + "(backup / restore; requires mutiny-api from the source/workspace)" + ), + description=( + "Hosted SQLite operator tools. Requires mutiny-api from the " + "source/workspace (uv sync --group dev); pip install mutiny-ai " + "does not include mutiny-api." ), ) db_sub = db_p.add_subparsers(dest="db_command", required=True) diff --git a/pyproject.toml b/pyproject.toml index 3b478d1..2000b45 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,18 +1,11 @@ -[project] -name = "mutiny" -version = "0.1.0" -description = "Mutiny — behavioral fuzz-testing engine for AI agents" -requires-python = ">=3.11" -dependencies = [ +# This is an unversioned workspace, not an installable product distribution. +[dependency-groups] +dev = [ "mutiny-core", "mutiny-openai-agents", "mutiny-ai", "demo-agent", "mutiny-api", -] - -[project.optional-dependencies] -dev = [ "pytest>=8.0", "pytest-asyncio>=0.24", "ruff==0.17.0", @@ -20,12 +13,6 @@ dev = [ llm = [ "mutiny-core[llm]", ] -openai-agents = [ - "mutiny-openai-agents", -] - -[project.scripts] -mutiny = "mutiny_cli.main:main" [tool.uv.sources] mutiny-core = { workspace = true } diff --git a/tests/unit/test_package_release.py b/tests/unit/test_package_release.py index b949bc5..ece96fb 100644 --- a/tests/unit/test_package_release.py +++ b/tests/unit/test_package_release.py @@ -95,6 +95,11 @@ def test_built_wheels_contain_current_surface(tmp_path: Path) -> None: names = set(z.namelist()) assert "mutiny_cli/db_cmd.py" in names assert "mutiny_cli/run_cmd.py" in names + metadata_path = next(n for n in names if n.endswith(".dist-info/METADATA")) + metadata = z.read(metadata_path).decode("utf-8") + assert "Classifier: Programming Language :: Python :: 3.13" not in metadata + assert "Requires-Dist: mutiny-api" not in metadata + assert not any(n.startswith("mutiny_api/") for n in names) run_src = z.read("mutiny_cli/run_cmd.py").decode("utf-8") # Compile catches SyntaxError class of stale release (historical PyPI gap) ast.parse(run_src) diff --git a/tests/unit/test_packaging_dx.py b/tests/unit/test_packaging_dx.py new file mode 100644 index 0000000..8d507af --- /dev/null +++ b/tests/unit/test_packaging_dx.py @@ -0,0 +1,86 @@ +"""Packaging and help text must describe the installed product accurately.""" + +from __future__ import annotations + +import importlib.metadata +import runpy +import tomllib +from pathlib import Path + +import pytest + +from mutiny_cli.main import main + +REPO = Path(__file__).resolve().parents[2] + + +def test_workspace_has_no_product_version_or_entrypoint() -> None: + root = tomllib.loads((REPO / "pyproject.toml").read_text(encoding="utf-8")) + assert "project" not in root + assert "mutiny-ai" in root["dependency-groups"]["dev"] + + +def test_api_version_reads_its_distribution_metadata(monkeypatch: pytest.MonkeyPatch) -> None: + def version(name: str) -> str: + assert name == "mutiny-api" + return "9.8.7" + + monkeypatch.setattr(importlib.metadata, "version", version) + namespace = runpy.run_path(str(REPO / "apps/api/src/mutiny_api/__init__.py")) + assert namespace["__version__"] == "9.8.7" + + +def test_api_without_metadata_is_explicitly_unreleased(monkeypatch: pytest.MonkeyPatch) -> None: + def missing(name: str) -> str: + raise importlib.metadata.PackageNotFoundError(name) + + monkeypatch.setattr(importlib.metadata, "version", missing) + namespace = runpy.run_path(str(REPO / "apps/api/src/mutiny_api/__init__.py")) + assert namespace["__version__"] == "0.0.0+unreleased" + + +def test_cli_python_classifiers_match_ci_matrix() -> None: + cli = tomllib.loads((REPO / "packages/mutiny_cli/pyproject.toml").read_text(encoding="utf-8")) + classifiers = cli["project"]["classifiers"] + assert "Programming Language :: Python :: 3.11" in classifiers + assert "Programming Language :: Python :: 3.12" in classifiers + assert "Programming Language :: Python :: 3.13" not in classifiers + assert "Programming Language :: Python :: 3.14" not in classifiers + assert not any(dep.startswith("mutiny-api") for dep in cli["project"]["dependencies"]) + + +@pytest.mark.parametrize("args", [["--help"], ["db", "--help"]]) +def test_db_help_explains_api_requirement( + args: list[str], capsys: pytest.CaptureFixture[str] +) -> None: + with pytest.raises(SystemExit) as exc: + main(args) + assert exc.value.code == 0 + output = capsys.readouterr().out + assert "requires mutiny-api" in output.lower() + assert "source/workspace" in output + + +def test_db_help_works_without_api(monkeypatch: pytest.MonkeyPatch, capsys: pytest.CaptureFixture[str]) -> None: + import builtins + + original = builtins.__import__ + + def without_api(name: str, *args, **kwargs): + if name == "mutiny_api" or name.startswith("mutiny_api."): + raise ImportError("mutiny-api is not installed") + return original(name, *args, **kwargs) + + monkeypatch.setattr(builtins, "__import__", without_api) + with pytest.raises(SystemExit) as exc: + main(["db", "--help"]) + assert exc.value.code == 0 + assert "does not include mutiny-api" in capsys.readouterr().out + + +def test_web_readme_describes_hosted_development() -> None: + readme = (REPO / "apps/web/README.md").read_text(encoding="utf-8") + assert "Mutiny Hosted UI" in readme + assert "python -m mutiny_api" in readme + assert "MUTINY_API_URL" in readme + assert "create-next-app" not in readme diff --git a/uv.lock b/uv.lock index 5a906a5..e1f9232 100644 --- a/uv.lock +++ b/uv.lock @@ -1,5 +1,5 @@ version = 1 -revision = 3 +revision = 5 requires-python = ">=3.11" resolution-markers = [ "python_full_version >= '3.14' and sys_platform == 'win32'", @@ -12,13 +12,25 @@ resolution-markers = [ [manifest] members = [ "demo-agent", - "mutiny", "mutiny-ai", "mutiny-api", "mutiny-core", "mutiny-openai-agents", ] +[manifest.dependency-groups] +dev = [ + { name = "demo-agent", editable = "apps/demo_agent" }, + { name = "mutiny-ai", editable = "packages/mutiny_cli" }, + { name = "mutiny-api", editable = "apps/api" }, + { name = "mutiny-core", editable = "packages/mutiny_core" }, + { name = "mutiny-openai-agents", editable = "packages/mutiny_openai_agents" }, + { name = "pytest", specifier = ">=8.0" }, + { name = "pytest-asyncio", specifier = ">=0.24" }, + { name = "ruff", specifier = "==0.17.0" }, +] +llm = [{ name = "mutiny-core", extras = ["llm"], editable = "packages/mutiny_core" }] + [[package]] name = "annotated-doc" version = "0.0.5" @@ -642,46 +654,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/01/c8/248b201f6d753d69fd5d6506011abbb35a946d9142b2ae311a948fd0be3d/mcp-1.29.0-py3-none-any.whl", hash = "sha256:f5a075bb611f23d6f4d080c6a1699fa62772eebc562ba9e66b306ddde1c755f7", size = 223436, upload-time = "2026-07-28T13:41:40.337Z" }, ] -[[package]] -name = "mutiny" -version = "0.1.0" -source = { virtual = "." } -dependencies = [ - { name = "demo-agent" }, - { name = "mutiny-ai" }, - { name = "mutiny-api" }, - { name = "mutiny-core" }, - { name = "mutiny-openai-agents" }, -] - -[package.optional-dependencies] -dev = [ - { name = "pytest" }, - { name = "pytest-asyncio" }, - { name = "ruff" }, -] -llm = [ - { name = "mutiny-core", extra = ["llm"] }, -] -openai-agents = [ - { name = "mutiny-openai-agents" }, -] - -[package.metadata] -requires-dist = [ - { name = "demo-agent", editable = "apps/demo_agent" }, - { name = "mutiny-ai", editable = "packages/mutiny_cli" }, - { name = "mutiny-api", editable = "apps/api" }, - { name = "mutiny-core", editable = "packages/mutiny_core" }, - { name = "mutiny-core", extras = ["llm"], marker = "extra == 'llm'", editable = "packages/mutiny_core" }, - { name = "mutiny-openai-agents", editable = "packages/mutiny_openai_agents" }, - { name = "mutiny-openai-agents", marker = "extra == 'openai-agents'", editable = "packages/mutiny_openai_agents" }, - { name = "pytest", marker = "extra == 'dev'", specifier = ">=8.0" }, - { name = "pytest-asyncio", marker = "extra == 'dev'", specifier = ">=0.24" }, - { name = "ruff", marker = "extra == 'dev'", specifier = "==0.17.0" }, -] -provides-extras = ["dev", "llm", "openai-agents"] - [[package]] name = "mutiny-ai" version = "0.2.0"