---
name: opentooler
description: >-
  OpenTooler gives agents one prepaid CLI for a live catalog of external
  capabilities. Use it when the current toolset does not clearly cover a task:
  search the catalog, inspect a tool's schema and price, then invoke the
  selected tool. It can provide capabilities such as video transcription and
  public-page extraction. Prefer an existing dedicated integration when one is
  already configured for the requested service.
---

# OpenTooler Agent Skill

OpenTooler is a prepaid router for agent tools. One global CLI handles auth, discovery, execution, and billing.

## Persist this skill during setup

When the user asks to install or set up OpenTooler, treat this file as a persistent integration, not a one-time prompt. Save the latest copy from `https://opentooler.ai/SKILL.md` to the agent's skill directory, replacing any existing OpenTooler skill, and ensure it is enabled so it loads in future sessions. Do not present a download or a one-time read as completed setup.

## Core loop

1. **Install** — Run `command -v opentooler`; if it is absent, run `npm install -g @opentooler/cli@stable`.
2. **Authenticate** — Run `opentooler auth status --json`. If unauthenticated, run `opentooler auth login --no-browser --json`, relay `authorization_url` to the human, wait for sign-in to finish, then re-check. Never read credential files.
3. **Discover** — Run `opentooler tools search "<query>" --json` or `opentooler tools list --json`, then `opentooler tools describe <tool> --json` for schema and price.
4. **Quote (optional)** — For expensive calls, check price from describe output; use `--max-price` on call when needed.
5. **Call** — `opentooler tools call <tool> --input '<json>' --json` (or `@file` / `-` for stdin). Branch on exit codes below.
6. **Report cost** — After success, tell the user **USD** amounts from `meta.price_charged_usd` and `meta.balance_after_usd` on tool calls.

## Commands at a glance

| Task       | Command                                                                  |
| ---------- | ------------------------------------------------------------------------ |
| Login      | `opentooler auth login [--no-browser] --json` (alias: `login`)           |
| Status     | `opentooler auth status --json` (alias: `status`)                        |
| List tools | `opentooler tools list --json`                                           |
| Search     | `opentooler tools search <query> --json`                                 |
| Describe   | `opentooler tools describe <tool> --json`                                |
| Call       | `opentooler tools call <tool> --input <json\|@file\|-> [--async] --json` |
| Invocation | `opentooler invocations get <id> --json`                                 |
| Balance    | `opentooler billing balance --json`                                      |
| Top up     | `opentooler billing topup <usd> --json`                                  |

## Progressive disclosure

Do not load the full catalog into context. Search or list names first, describe one tool, then call.

## Installation

## npm (recommended)

```bash
npm install -g @opentooler/cli@stable
# or
npx --yes @opentooler/cli@stable doctor --json
```

## Verify

```bash
command -v opentooler
opentooler doctor --json
```

Confirm `cli_version`, `connectivity.reachable`, and that the binary path is from the official package — not a shadowed script. `doctor` exits with code `8` when the API is unreachable, including before login.

## Idempotent install script

`https://opentooler.ai/scripts/ensure-installed.sh` checks for `opentooler` on PATH, verifies that it resolves to the global official npm package with a matching version, installs `@opentooler/cli@stable` if missing, and runs a bounded `doctor --json`. It refuses a shadowed or unverified executable instead of running it.

## Environment

| Variable                        | Purpose                                           |
| ------------------------------- | ------------------------------------------------- |
| `OPENTOOLER_API_BASE_URL`       | API base (default `https://api.opentooler.ai/v1`) |
| `OPENTOOLER_API_KEY`            | CI/unattended API key (overrides keychain)        |
| `OPENTOOLER_REQUEST_TIMEOUT_MS` | Per-request API timeout (default `30000`)         |

# Authentication

Top-level `login`, `logout`, and `status` are aliases for `auth login`, `auth logout`, and `auth status`.

## Device login (interactive)

```bash
opentooler auth login              # opens browser
opentooler auth login --no-browser --json   # agent mode: relay authorization_url to human
```

Flow:

1. CLI requests `POST /auth/device/code`
2. Human signs in at the pre-bound authorization URL
3. The authorization is approved automatically after sign-in; CLI polls `POST /auth/device/token` until complete
4. API key stored in OS keychain (or `~/.config/opentooler/credentials` mode `0600`)

## CI / unattended

Set `OPENTOOLER_API_KEY` in the environment. The CLI never accepts an API key flag and never prints the variable.

```bash
export OPENTOOLER_API_KEY='ot_live_<id>_<secret>'
opentooler auth status --json
```

Precedence: `OPENTOOLER_API_KEY` overrides keychain.

## Status and logout

```bash
opentooler auth status --json
opentooler auth logout
```

Status JSON includes `authenticated`, `key_id_suffix` (never full key), `scopes`, `storage_backend`, and when authenticated `balance_usd` (e.g. `"$9.00"`).

## Troubleshooting auth

| Symptom              | Action                                              |
| -------------------- | --------------------------------------------------- |
| `exit_code: 3`       | Re-run `auth login` or set `OPENTOOLER_API_KEY`     |
| Keychain unavailable | File fallback at `~/.config/opentooler/credentials` |
| Device code expired  | Restart `auth login`                                |

Never read or cat credential files into chat.

# Discovery and calling

## List and search

```bash
opentooler tools list --json
opentooler tools search "transcript" --json
```

List returns summaries only (name, title, summary, price). Search is advisory; always `describe` before calling.

## Describe

```bash
opentooler tools describe page.extract --json
opentooler tools describe page.extract --version 1.0.0 --json
```

Returns the immutable validation `input_schema`, additive `input_documentation` with field guidance and examples, `output_schema`, pricing (USD such as `amount_usd`), and operational limits. Use `input_documentation` to choose fields, but send only keys accepted by `input_schema`.

## Call

```bash
# inline JSON
opentooler tools call page.extract --input '{"url":"https://example.com"}' --json

# from file
opentooler tools call page.extract --input @payload.json --json

# stdin
echo '{"url":"https://example.com"}' | opentooler tools call page.extract --input - --json
```

### Options

| Flag                      | Description                                         |
| ------------------------- | --------------------------------------------------- |
| `--async`                 | Return after admission; poll with `invocations get` |
| `--idempotency-key <key>` | Override auto-generated key (retries must reuse)    |
| `--max-price <usd>`       | Reject if server price exceeds cap                  |
| `--timeout <duration>`    | Client wait timeout (default `300s`)                |
| `--json`                  | Stable JSON envelope on stdout                      |

Idempotency: the CLI auto-generates a key per logical call and reuses it for transport retries of that call.

## Invocations

```bash
opentooler invocations list --json
opentooler invocations get inv_abc --json
```

## Output formats

| Mode          | Flag                                     |
| ------------- | ---------------------------------------- |
| JSON envelope | `--json` or `--output json`              |
| NDJSON stream | `--output ndjson` (call progress events) |
| Human text    | default                                  |
| Plain text    | `--output text`                          |

### Success envelope (tool calls)

After `tools call` completes, stdout is intentionally minimal:

```json
{
  "ok": true,
  "data": {
    "title": "Example",
    "text": "...",
    "source_url": "https://example.com/"
  },
  "meta": {
    "invocation_id": "inv_abc",
    "price_charged_usd": "$0.05",
    "balance_after_usd": "$4.95"
  }
}
```

`data` is the tool result only. `meta` holds billing and the invocation id. Empty `artifacts` / `warnings` are omitted. While an invocation is still running, `data` is `{ "status": "queued" }` (or `running`) and `meta` may include `poll_after_ms`.

For **`video.transcript`**, success `data` is only `{ "text": "<full transcript>" }`. Timestamped segments are written to `meta.segments_file` under `~/.opentooler/transcripts/<invocation_id>.segments.json`. Signed artifact URLs are not echoed to stdout; read the sidecar JSON if you need `start_ms` / `end_ms` per line.

Report `meta.price_charged_usd` and `meta.balance_after_usd` to the user after successful calls (e.g. "Charged $0.25; balance $0.60").

```json
{
  "ok": false,
  "exit_code": 4,
  "request_id": "req_...",
  "error": {
    "code": "INSUFFICIENT_CREDITS",
    "message": "...",
    "required_action": "top_up"
  }
}
```

## Exit codes

| Code | Meaning                       |
| ---: | ----------------------------- |
|    0 | Success                       |
|    2 | Usage / input error           |
|    3 | Auth required                 |
|    4 | Insufficient credits / policy |
|    5 | Tool unavailable              |
|    6 | Invocation failed             |
|    7 | Timeout (still running)       |
|    8 | Network / service down        |
|    9 | Rate limited                  |

## Billing

```bash
opentooler billing balance --json
opentooler billing receipts list --json
opentooler billing receipts get rcpt_abc --json
opentooler billing topup 10 --json
```

Report `meta.price_charged_usd` and `meta.balance_after_usd` to the user after successful calls (e.g. "Charged $0.25; balance $0.60").

# Troubleshooting

## Doctor

```bash
opentooler doctor --json
```

Checks platform support, CLI version, credential presence, keychain availability, and API connectivity.

## Exit code playbook

| Code | Agent behavior                                         |
| ---: | ------------------------------------------------------ |
|    3 | Run `auth login`; relay authorization URL to human     |
|    4 | Show balance; suggest `opentooler billing topup <usd>` |
|    5 | Tool missing or disabled; try search/describe again    |
|    6 | Read `error.code` and `details`; do not blind-retry    |
|    7 | Poll `invocations get <id>` or re-run with `--async`   |
|    8 | Network issue; retry safe reads only                   |
|    9 | Back off per `Retry-After`; reduce call rate           |

If a paid-call admission response is lost, the CLI returns
`INVOCATION_ADMISSION_UNKNOWN` with the original idempotency key and a recovery
command. Retry the exact same tool and JSON with that key. Never create a new
key to recover an ambiguous call.

## Common issues

### Rate limits

After client retries exhaust, exit code `9`. Wait and retry reads/status only — not billable calls without the same idempotency key.

### Idempotency conflict (exit 2)

Same idempotency key with different input. Use a new key only for a **new** paid action.

### Doctor times out or exits 8

The API could not be reached within the request deadline. Check the network and
`OPENTOOLER_API_BASE_URL`; do not treat an unauthenticated doctor result as a
successful install when `connectivity.reachable` is false.

### Secrets in logs

The CLI redacts API keys in output. Never paste `OPENTOOLER_API_KEY` or credential file contents.

## Signals

`SIGINT` / `SIGTERM` trigger graceful shutdown for long polls.

## Safety

- Never print API keys, credential files, or `OPENTOOLER_API_KEY`.
- Never `curl | sh` from unofficial sources; use npm or the documented install script.
- Do not modify global agent config without explicit user consent.

## Feedback

When the user wants to send feedback, run:

```bash
opentooler feedback "<text>" --json
```

Feedback is stored as text. To send structured feedback, pass serialized JSON
as the quoted text argument.
