Metadata-Version: 2.5
Name: metaphi-ai
Version: 0.1.5
Summary: The Metaphi AI command line: sign in and twin traces.
License-Expression: Apache-2.0
License-File: LICENSE
Requires-Python: >=3.10
Requires-Dist: httpx>=0.28
Requires-Dist: keyring>=25.7
Provides-Extra: dev
Requires-Dist: pytest>=8; extra == 'dev'
Requires-Dist: respx>=0.22; extra == 'dev'
Description-Content-Type: text/markdown

# metaphi

The Metaphi AI command line. Sign in and twin agent traces.

## Install

```sh
brew install metaphi-labs/tap/metaphi    # macOS and Linux
```

No Homebrew? Download the binary for your platform from the
[latest release](https://github.com/metaphi-labs/metaphi-ai-cli/releases/latest), unpack it and put `metaphi` on your
`PATH`. Each release lists its `checksums.txt`.

Calling it from Python instead: `pip install metaphi-ai` gives you the same command and the library (see below).

## Quickstart (2 minutes)

```sh
metaphi login                  # opens the browser; confirm the code you see in the terminal
metaphi twin trace.json        # writes trace.twin.json
```

`metaphi login` signs you in as yourself and hands this machine your organization's keys: a live key (`mai_sk_...`)
and a test key (`mai_sk_test_...`). They are kept in the OS keychain when there is one (macOS Keychain, Windows
Credential Locker, Secret Service on Linux), otherwise in the config file, and are never printed. Logging in again
replaces them; `metaphi logout` revokes them. If your organization has no twinning access yet, the sign-in still
works and says why no key was saved.

`metaphi twin trace.json --test` runs on the test key. Test runs go through the same pipeline but cost nothing; files
can be at most 256 KiB and an organization gets 20 runs a day. Test twins and live twins never see each other.

Keys, members and billing are managed in the console at <https://app.metaphi.ai/keys>, not from the CLI.

## Use it from your servers

A server or CI job has no browser: make it a key of its own in the console and hand it over as a secret.

```sh
export METAPHI_API_KEY=mai_sk_...
metaphi twin trace.json -o out.json --json
```

`METAPHI_API_KEY` overrides whatever is saved. A key you already have can also be saved with
`metaphi login --with-api-key`, which reads it from stdin (or prompts without echo), so it never lands in your shell
history or the process list.

One trace file in, one twinned file out, in the same format. `--json` prints `{"output"}` on stdout, with progress
on stderr. Or from Python:

```python
from pathlib import Path
from metaphi.twin import twin

result = twin(Path("trace.json"), Path("out.json"), api_key="mai_sk_...")
result["output"]   # where the twinned trace was written
```

One key per environment (`prod`, `staging`) or per customer is a good default. Revoking one stops only that key.

### Twin options

| Flag | What it does |
| --- | --- |
| `-o, --output` | Where the twinned trace goes. Default: `<name>.twin<ext>`. |
| `--key <64 hex>` | Keys the stand-in values. The same key gives a person the same stand-in in every trace, so a corpus stays consistent. Default: a fresh key per trace. |
| `--confidential <org>` | Replaces an organization even though it is public, e.g. your customer. Repeatable. |
| `--test` | Runs on the test key `metaphi login` saved: free, small, apart from live twins. |

The organization key comes from `METAPHI_API_KEY`, then the saved key. (`--api-key` still works but is deprecated: an
argument is visible in shell history and to other processes.)

People, private organizations, accounts and secrets are always replaced, each with one consistent synthetic value of
the same kind, and the trace keeps its structure. An adversarial pass then reads the output for anything still real.

### Errors

Every error from the service carries a stable `code`. Switch on the code or the exception class, never on the
message. On a failure the CLI exits 1 and prints:

```
error: This organization's prepaid balance is used up; add credits to continue. Nothing was started. (code: out_of_credit, request: req_5f1c...)
```

With `--json`, stdout also gets the error object: `{"error": {"type", "code", "message", "retryable", "request_id"}}`,
plus `run_id` when a run failed and `action_url` when a page fixes it. Quote the `request_id` to support.

From Python, each error `type` raises its own class from `metaphi.errors`. All of them subclass `MetaphiError`:

```python
from pathlib import Path
from metaphi.errors import BillingError, MetaphiError, RateLimitError
from metaphi.twin import twin

try:
    twin(Path("trace.json"))
except BillingError:            # out_of_credit
    ...                         # add credit: `metaphi credits buy`
except RateLimitError as e:     # rate_limited_concurrent, rate_limited_daily
    ...                         # e.retryable is True: try again when a run ends
except MetaphiError as e:
    print(e.code, e.request_id, e.retryable)
```

| Class | `type` | Codes, for example |
| --- | --- | --- |
| `BillingError` | `billing_error` | `out_of_credit` |
| `AuthError` | `auth_error` | `no_organization` |
| `RateLimitError` | `rate_limit_error` | `rate_limited_concurrent`, `rate_limited_daily` |
| `InvalidRequestError` | `invalid_request_error` | `invalid_key`, `upload_already_run`, `task_not_found` |
| `RunError` | `run_error` | `twin_unclean`, `not_text`, `too_large`, `session_failed`, `budget_exceeded` |
| `IdempotencyError` | `idempotency_error` | `idempotency_key_reused`, `idempotency_key_in_use` |
| `APIError` | `api_error` | `unavailable`, `balance_unavailable`, `run_unavailable` |

`retryable` says whether making the same call again later may succeed. For a failed run, it means uploading the same
trace again. An error the CLI says itself, such as a missing key, is a plain `MetaphiError` with `code` set to `None`.

## Your organization

`metaphi whoami` shows who you are signed in as and the keys saved on this machine. People, roles and keys are managed
in the console at <https://app.metaphi.ai/keys>.

## Credits

Each completed trace draws your organization's prepaid credit; a run that fails costs nothing. When the balance
reaches zero, new runs are refused until credit is added.

```sh
metaphi credits                       # the balance (owners, admins and billing members)
metaphi credits buy --amount 200      # pay through Stripe Checkout in the browser; waits until it lands
```

Only owners and billing members can buy; a billing member sees the balance and buys credit, nothing else.

### Running out of credit

When `metaphi twin` is refused with `out_of_credit` at a terminal, and you may buy for your organization, it offers to
add credit there and then, opens Stripe Checkout, and carries on with the same run once the payment lands:

```
$ metaphi twin trace.json
acme is out of prepaid credit.
Add credit now? Amount in $ [100]:
Pay in the browser:
  https://checkout.stripe.com/c/pay/...
Paid. acme: $100.00 prepaid credit
uploaded trace.json (48,112 bytes)
```

Enter takes $100; `n` or Ctrl-C declines. The run is retried once, under the Idempotency-Key of the first attempt, so
it can never start twice. If you may not buy, it names who can and where.

In CI, a pipe or with `--json` nothing is asked. The command exits 1 with the error and one more line on stderr:

```
Add credit: https://app.metaphi.ai/console?add=credit  or run: metaphi credits buy --amount <amount>
```

The library never prompts: `twin()` raises `BillingError`, whose `action_url` is the add-credit page when the service
sent one.

## Configuration

| Variable | Default |
| --- | --- |
| `METAPHI_API_KEY` | the key saved by `metaphi login` (or `metaphi login --with-api-key`) |
| `METAPHI_API` | `https://api.metaphi.ai/v1/kennet`, the Kennet API |
| `METAPHI_HOME` | `~/.config/metaphi` (directory mode 700, config file mode 600) |
| `METAPHI_TWIN_TIMEOUT` | `7200`: seconds `metaphi twin` waits for its twin to end (`--timeout` per call) |
| `METAPHI_NO_KEYRING` | unset: secrets go to the OS keychain when one is available; `1` keeps them in the config file |
| `METAPHI_ALLOW_CUSTOM_HOST` | unset: the CLI talks only to `https://*.metaphi.ai`; `1` allows another https host |

Sign out with `metaphi logout`. It ends this sign-in everywhere it is used, revokes the keys `metaphi login`
handed out, and clears the saved keys from this machine.
