# Command line

> Wire an extension to a tool, or create one, from the terminal.

The `toolaby` package on npm is Toolaby's command line. It signs in as you with a code you approve in the browser, and acts only on tools in your workspaces. These docs write every command in full, `npx -y toolaby@latest <command>`, which always runs the current version. It needs Node.js 20 or later.

```bash
npx -y toolaby@latest wire <tool-id>          # wire the extension in this folder, on Test
npx -y toolaby@latest wire <tool-id> --live   # the same, on Live
npx -y toolaby@latest upgrade <tool-id>       # replace Toolaby's files with the current ones
npx -y toolaby@latest upgrade <tool-id> --live   # the same, with the Live key: before a store build
npx -y toolaby@latest create <tool-id>        # a new, minimal extension in <tool-id>/
npx -y toolaby@latest tools                   # your tools and their ids
npx -y toolaby@latest check [folder]          # whether the extension here is wired right
npx -y toolaby@latest pack [folder]           # the zip the Chrome Web Store takes
npx -y toolaby@latest mcp                     # Toolaby for coding agents, an MCP server on stdio
npx -y toolaby@latest webhooks events         # the nine events, and what each means
npx -y toolaby@latest webhooks trigger <event> [--to <url>]   # an example delivery
npx -y toolaby@latest webhooks secret         # this machine's development secret, which --to signs with
npx -y toolaby@latest webhooks endpoints | add <url> | remove <id>   # your endpoints
npx -y toolaby@latest login | whoami | logout
npx -y toolaby@latest agents                  # set Toolaby up in your coding agents
npx -y toolaby@latest access <tool-id> --access free_uses --free-uses 3 --free-uses-per day   # the Free plan
npx -y toolaby@latest plan add <tool-id> --billing subscription --interval month --amount 4 --currency usd   # a plan
npx -y toolaby@latest customer <tool-id> <email>   # one buyer: entitled or not, plan, renewal
npx -y toolaby@latest analytics <tool-id> [--range 90d] [--table features]   # the tool's Analytics
```

The tool's prices, its buyers and support each have a command too: [The tool, its prices and its buyers](#the-tool-its-prices-and-its-buyers).

Install it once with `npm i -g toolaby` to type `toolaby-cli` instead of `npx -y toolaby@latest`. The command is named so on every system: on Windows, the Command Prompt runs a `toolaby.js` in the current folder before a command named `toolaby`, and every wired folder has one.

In a repository you did not write, use the installed command. `npx` can run a `toolaby` the project carries, or fetch one from a registry the project's `.npmrc` names.

## Test and Live

Every command acts on Test (`toolaby.app/test`) unless given `--live`, which acts on Live (`toolaby.app`). `wire` on Test writes the `tk_test_…` key; `wire --live` writes the `tk_live_…` key. Every message names the side it is on.

Sessions are kept per side. The first command on a side that has no session signs you in first.

## Commands

### `wire <tool-id>`

Run in the extension's folder. `<tool-id>` is the tool's full id, as **Tool id** on the tool's page shows it, or the short one when it names one tool across your workspaces.

The command writes the client, the Toolaby popup — and its side panel, when the extension has one — and the tool key, and edits `manifest.json`: the `storage` permission, Toolaby's address under `externally_connectable`, the `key` that gives every copy the same id, and Toolaby's address in a content security policy whose `connect-src` leaves it out. It adds no host permission, and takes out one an earlier wiring gave Toolaby's address: Toolaby needs none. A WXT project gets the same files and a module, `modules/toolaby.wxt.ts`, which WXT loads by itself: nothing to paste. A CRXJS project with `manifest.config.ts`, and a Plasmo project, get the files and printed lines for the framework's config. See [Frameworks](https://toolaby.app/docs/frameworks.md).

Wired for the first time, the Toolaby popup stands in front of yours. An extension whose own pages draw the paywall — `appPopup: ''` in `toolaby.config.js` — keeps them in front: Toolaby's pages are written beside them, for `showPaywall()`. See [Gate your extension](https://toolaby.app/docs/gating.md#1-choose-what-opens-first).

Your background is not edited. The command writes the Toolaby background, `toolaby.background.js`, beside it and names it in the manifest. It runs your background as it did, then starts Toolaby.

* A classic background keeps its `importScripts()`. The Toolaby background loads the client as a script, `toolaby-client.js`.
* A module background is imported: `import './background.js'`.
* A background that starts Toolaby itself, with `toolaby.startBackground({ handlers })`, is left as it is.
* A TypeScript background, which a build compiles, gets the two lines to add at its top.

The command prints what it did, one row each: `✓` done, `!` worth a look, `→` a step for you with the exact code. Then it says what to do next. Colour is on in a terminal, and off with `NO_COLOR` or when the output is not a terminal.

It exits with `0` when the extension is wired, with `2` when it ends *Almost wired*, with steps left for you, and with `1` when it stopped: the message says what was written, if anything. A script or an agent can tell the three apart.

When your code handles its popup itself, with `chrome.action.setPopup()` or `getPopup()`, a first wiring keeps your popup in front: the Toolaby popup in front of it would be skipped by `setPopup()`, and found by `getPopup()` where your code expects its own. The one step it prints is the lines that open the paywall where your paid action starts. Wired before with the Toolaby popup in front, the command names the file and prints the `upgrade … --own-popup` line that keeps yours in front.

| Option                |                                                                                                                                                                                                                                                                                                                                                                   |
| --------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `--into <folder>`     | Wire another folder. One that a build copies, such as `public/` or `src/static/`, is wired as part of the project above it: a background the build makes is left to the build.                                                                                                                                                                                    |
| `--force`             | Replace platform files that are already there — after **Copy to Live**, to swap in the live key.                                                                                                                                                                                                                                                                  |
| `--adopt-key`         | When the folder's manifest has a `key` of another extension than the one the tool is bound to, make the folder's extension the tool's. Without it, `wire` stops and names both.                                                                                                                                                                                   |
| `--own-popup`         | Keep your popup in front. The Toolaby popup is written beside it, and `toolaby.showPaywall()` opens it.                                                                                                                                                                                                                                                           |
| `--toolaby-popup`     | Put the Toolaby popup in front of yours, in an extension whose popup was in front. It opens yours at once unless the person may not use the extension yet.                                                                                                                                                                                                        |
| `--from-extensionpay` | Move an extension that sells with ExtensionPay: its calls answered by Toolaby, its plans copied. It asks which paywall people meet first, Toolaby's or ExtensionPay's page; without a terminal, add `--toolaby-popup` or `--own-popup`. Without it, `wire` stops when it finds ExtensionPay and writes nothing. See [Move from ExtensionPay](https://toolaby.app/docs/extensionpay.md). |
| `--live`              | Act on Live.                                                                                                                                                                                                                                                                                                                                                      |

A folder with no `manifest.json` at the top and no recognised framework gets nothing written; the command says what it found. When it finds a manifest below the top, such as `public/manifest.json`, it suggests running again with `--into public`. Do that only when nothing builds the background that manifest names. When a build writes the background, wire by hand instead: see [Frameworks](https://toolaby.app/docs/frameworks.md#other-bundlers). [Troubleshoot an extension](https://toolaby.app/docs/troubleshooting.md#when-wire-stops) lists every message `wire` stops with.

`wire`, `upgrade` and `create` write inside the folder and nowhere else. A path that leads out of it — an absolute one, a `..`, a symbolic link at any step, a service worker the manifest names as `../../.zshrc` — is refused before anything is written, and the command says which. `create` writes only the files the minimal extension is made of; a server that names another is told the command is older than it (`npm install -g toolaby@latest`).

### `upgrade <tool-id>`

`wire --force`, by its name: the platform's files — the client, the panel, the Toolaby popup, the key — replaced with the current ones, in the extension's folder or `--into <folder>`. Your own files are not touched, except for the [account bar](https://toolaby.app/docs/toolaby-popup.md#the-account-bar) at the foot of a plain extension's popup and side panel, written between Toolaby's markers (`accountBar: false` in `toolaby.config.js` keeps it out). Whichever page stands in front stays there: `--toolaby-popup` puts the Toolaby popup in front of yours, `--own-popup` yours in front.

`countOpens` in `toolaby.config.js` is read again from your code each time: `false` once it calls `toolaby.gate()`, so opening the Toolaby popup spends no free use. Run it when `npx -y toolaby@latest check` says `toolaby.js` is not Toolaby's current client, after changing what the Free plan holds, and after adding the first subscription plan; then rebuild and reload. Installed copies follow a change to the Free plan without an update: the key's copy of it is used only until Toolaby first answers.

`wire` and `upgrade` also write `TOOLABY.md` and `.claude/skills/toolaby/SKILL.md`, and `AGENTS.md` and `CLAUDE.md` where the project has none. See [Build with an agent](https://toolaby.app/docs/agents.md).

### `create <tool-id>`

Makes a minimal extension in `<tool-id>/` (or `--dir <folder>`), wired, ready for **Load unpacked**: a popup and a side panel of its own — the same page, one script — with the platform's panel drawn inside them, the account bar at the foot, and one handler behind `gate()`. Beside each, Toolaby's own panel page, which `showPaywall()` opens; and the manifest's `key`, so the extension has the id the tool key is bound to from the first load.

`--surfaces both` (the default), `popup`, `side-panel` or `none` chooses what it opens; the manifest then names exactly the pages that were written. See [Surfaces](https://toolaby.app/docs/surfaces.md).

It also writes `AGENTS.md`, `CLAUDE.md`, `TOOLABY.md` and `.claude/skills/toolaby/SKILL.md` for coding agents. See [Build with an agent](https://toolaby.app/docs/agents.md).

### `tools`

Lists your tools with their ids and checkout page addresses, on Test or with `--live`.

### `pack [folder]`

Makes the zip the Chrome Web Store takes, from your built extension: the folder you name, or else the first of `.`, `.output/chrome-mv3` (WXT), `dist` (CRXJS, Vite), `build/chrome-mv3-prod` (Plasmo) and `build` that holds a `manifest.json`. It writes `<name>-<version>.zip` here (`--out <file.zip>` for another name) and sends nothing anywhere.

* Every file in the folder goes into the zip as it is, except what the rules below leave out. In a plain extension that includes `TOOLABY.md`, `AGENTS.md` and `CLAUDE.md`, which hold nothing secret.

* The manifest's `key` is left out of the zip. The store refuses a `key` in a new item's first package, and any key but the item's own in an update; it keeps the id it gave the item either way. Your unpacked copy keeps its key and its id.

* It refuses a build with a `tk_test_` or `tk_dev_` key inside — it would sell on Test, with test cards. Run `wire <tool-id> --live --force`, build, and pack again. `--allow-test` packs it anyway, for a build you will not publish.

* It refuses a manifest with `localhost` (or `127.0.0.1`, or a `.localhost` host) in `host_permissions` or `externally_connectable`. `--allow-local` packs it anyway.

* It refuses a manifest that names a host under `toolaby.app` that no key in the build names: another workspace's, or the other side's Test address, which an earlier wiring or a pasted snippet left. The extension talks to its own key's host alone: the build would carry an address it never talks to, and under `host_permissions` Chrome would also ask buyers for it at install. `--allow-toolaby-hosts` packs it anyway.

* It refuses a manifest that does not name the Toolaby address its key points at under `externally_connectable`: sign-in and the purchase handoff would not reach the extension. A host permission for that address is packed, with a note: Toolaby needs none, and Chrome switches off an update that adds one until each user approves it. `upgrade` takes it out.

* It refuses a build when a file the manifest names is one it would leave out: the background, a content script, the popup, the side panel or another page, an icon, a web-accessible resource or a rule set. Chrome would not load the zip, so none is written. `--allow-private` packs a private one; a link is never packed.

* It refuses a build when a public folder is itself a link into a `.git` folder or at a private file (`public -> .git`), or when the build's top is a git repository's own folder. The build then holds the repository among the extension's files. `--allow-private` packs it anyway.

* It leaves out, and names, what looks private: keys and keystores (`.pem`, `.key`, `.p12`, `.pfx`, `.jks`, `id_rsa`…), certificates (`.crt`, `.cer`, `.der`), `*.env` files, `credentials.json`, `secrets.json` and `secrets.yml`, service-account keys (`*service-account*.json`, `firebase-adminsdk*.json`), login files (`.npmrc`, `.netrc`…), deployment state (`*.tfstate`), and source maps — and, whatever its name, a file holding a private key, a service account's key or a source map written into the code. A store item is public: anyone can download it and unzip it. `--allow-private` packs them anyway.

* It leaves out a file of any name that holds a secret:

  * a Stripe secret key, live or test (`sk_live_…`, `rk_live_…`, `sk_test_…`, `rk_test_…`), or a webhook signing secret (`whsec_…`),
  * a Toolaby API key (`wk_live_…`, `wk_test_…`), which reads and changes every buyer of the workspace's: the extension holds the tool key alone,
  * an AWS access key id (`AKIA…`, or a temporary one, `ASIA…`), or a secret access key where a name says what it is (`aws_secret_access_key = …`, `secretAccessKey: "…"`),
  * an OpenAI key (`sk-…`, `sk-proj-…`), an Anthropic key (`sk-ant-…`) or a Google OAuth client secret (`GOCSPX-…`),
  * a GitHub token (`ghp_…`, `gho_…`, `ghu_…`, `ghs_…`, `ghr_…`, `github_pat_…`), a Slack token (`xoxb-…` and the like, or an app token, `xapp-…`) or an npm token (`npm_…`),
  * a PuTTY private key, or a Supabase `service_role` key (a JWT whose payload names that role, or `supabase_admin`),
  * an address with a password in it (`https://<user>:<password>@<host>`).

  It reads each file to its end: as it is, as text in UTF-16 when it is that, and decoded when the whole file is base64. Each secret counts only in its full shape. A prefix that code checks for, a placeholder, a publishable key (`pk_live_…`, `pk_test_…`), a Supabase `anon` key or a signed-in user's token, a hex digest and a password for `localhost` are packed. A private key counts only with its body and its `END` line, so code that names the `BEGIN` line, as `jose` does, is packed.

* It packs a file that holds a Google API key (`AIza…`), and names it. Such a key is made to be in a browser: a Firebase web config's, or one for Maps or YouTube. Its restrictions keep it to its use: restrict it to this extension, and to the APIs it calls, in Google Cloud under **Credentials**.

* A build copies what a link in `public/` points at as an ordinary file. So it also leaves out:

  * a file with the same bytes as a private file of the project, or as this command's own session — or the same text, whatever its line ends (CRLF or LF), a byte-order mark, spaces at the ends of its lines or newlines at its end, and whether it is written in UTF-8, in UTF-16 (as Windows PowerShell 5.1 writes `Get-Content .env > dist\data.txt`) or in base64. The project is the nearest folder above with a `package.json` or a `.git`; its private files are its `.env` files, `.git/config`, `.npmrc`, keys and the rest above.
  * a folder that is a git repository's own (`HEAD` and `config`, with `objects/` or a `HEAD` that names a branch or a commit), or a copy of its object store, whole; and, whatever its name, any file that is git's history (a pack, or a loose object), its index, its list of refs (`packed-refs`, `FETCH_HEAD`), a log of its branches (`logs/HEAD`), or a repository's config. A commit message (`COMMIT_EDITMSG`) is text like any other, and is packed.
  * what a link in those folders put into the build when it points into a `.git` folder, in any case, or at a private file: `public/vendor -> ../.git/objects` leaves the build's `vendor/` out, whole.
  * a file named as an image (`.png`, `.apng`, `.jpg`, `.jfif`, `.gif`, `.webp`, `.avif`, `.ico`, `.bmp`, `.tif`, `.tiff`) whose first bytes are no image's, or an `.svg` that is not SVG.
  * a compressed file that holds what it would leave out: a `.gz` (gzip, zlib's deflate or raw deflate) or a `.br`, as a build's compression plugin writes them, or any gzip, opened to 4 MB and no further. One beside a file it leaves out — `data.json.gz` beside `data.json` — is left out with it, however large.

  `--allow-private` packs these too. It also names the other links in the project's `public/`, WXT's `src/public/` and a `publicDir` a WXT or Vite config names, without refusing the build — a monorepo's shared icons are links too: make each the file itself.

  It knows a copy only in the forms above. A private file that a build encrypts, splits into parts or spreads through a bundle is not recognised: keep private files out of the folders a build copies.

* It never packs a symbolic link, wherever it leads, nor anything but a file, nor a name with a backslash or a control character in it: the zip holds the folder's own files. Copy in what belongs in it.

* It writes the zip under a new name and moves it into place, so a link left under the zip's name is replaced, never written through.

* It refuses a manifest `version` Chrome would not take.

Dotfiles, `node_modules` and zips in the folder are left out. When the manifest names a file under one of them — a content script in `node_modules` — `pack` stops and writes nothing, as for any file it leaves out: the zip would not load. See [Ship to the Chrome Web Store](https://toolaby.app/docs/ship.md).

### `check [folder]`

Checks the extension in this folder, or `folder`: Toolaby's files, the tool key and the tool it names, the manifest and its `key`, whether the background starts Toolaby, the scripts its pages load, code that handles the popup itself, the client's version, the feature keys the code gates on against the tool's (comments are skipped), whether anything can ask a buyer to pay, the page `showPaywall()` opens, and whether this command is current.

The tool key decides the side. A `tk_live_…` key is checked against Live and a `tk_test_…` key against Test, with or without `--live`. Signed in on that side, `check` also says whether a copy has checked in and whether the key is current. Without a session there, it says so and names the `login` command.

| Option            | What it does                                                                                                                                                                                                                                                                                                   |
| ----------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `--json`          | Prints the result as JSON and nothing else.                                                                                                                                                                                                                                                                    |
| `--built <dir>`   | Also checks a build: its `manifest.json`, the tool key the bundle carries and its side, the code its background runs, and the scripts its pages load. Use it for WXT (`.output/chrome-mv3`), CRXJS and Vite (`dist`), and Plasmo (`build/chrome-mv3-prod`). Naming a build's folder as `folder` does the same. |
| `--browser`       | Loads the extension in Chromium, asks its background over Toolaby's own line (`chrome.runtime.connect({ name: 'toolaby' })`), and opens its popup, side panel and options page.                                                                                                                                |
| `--into <folder>` | The folder to check, as for `wire`.                                                                                                                                                                                                                                                                            |

`--browser` uses the Playwright that `TOOLABY_PLAYWRIGHT` names (its package folder), or the project's own. Otherwise it installs Playwright 1.63.0 into `~/.config/toolaby/playwright`, never into the project. The first run downloads Playwright (about 10 MB) and Chromium (about 170 MB), and says so first. Later runs reuse both.

With `--json`, the result is one object:

```json
{
  "ok": false,
  "side": "Test",
  "folder": "/Users/you/word-count",
  "built": null,
  "findings": [
    { "code": "KEY_FOUND", "level": "ok", "message": "The tool key names acme--word-count, on Test (https://quiet-otter-4172.toolaby.app/test).", "file": "toolaby.config.js" },
    { "code": "CSP_BLOCKS_TOOLABY", "level": "error", "message": "manifest.json's content security policy leaves https://quiet-otter-4172.toolaby.app out of connect-src: every call of Toolaby's fails to fetch. `npx -y toolaby@latest upgrade acme--word-count` adds it.", "file": "manifest.json", "fix": "npx -y toolaby@latest upgrade acme--word-count", "docs": "https://toolaby.app/docs/troubleshooting#in-the-extension" }
  ]
}
```

| Field                | Type             | Description                                                                                                                    |
| -------------------- | ---------------- | ------------------------------------------------------------------------------------------------------------------------------ |
| `ok`                 | boolean          | `false` when a finding is an error.                                                                                            |
| `side`               | string           | `Test` or `Live`: the side the tool key is on. For a development server's key, the address `--server` names, or `Development`. |
| `folder`             | string           | The folder checked.                                                                                                            |
| `built`              | string, nullable | The build checked, or `null`.                                                                                                  |
| `findings[].code`    | string           | What was found. The codes never change.                                                                                        |
| `findings[].level`   | string           | `error` (it must be fixed), `warn` (worth a look) or `ok`.                                                                     |
| `findings[].message` | string           | What was found, and why it matters.                                                                                            |
| `findings[].file`    | string           | The file it is about, relative to `folder`. Left out where there is none.                                                      |
| `findings[].line`    | number           | The line in that file. Left out where there is none.                                                                           |
| `findings[].fix`     | string           | A command that fixes it, on the key's side: `--live` for a Live key. Left out where only your code can fix it.                 |
| `findings[].docs`    | string           | The page that explains it.                                                                                                     |

`check` exits with `0` when no finding is an error, and `1` when one is. Warnings alone leave it at `0`.

The findings that ask for something:

| Code                             | Level         | Means                                                                                                                                                                |
| -------------------------------- | ------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `FOLDER_MISSING`                 | error         | The folder does not exist.                                                                                                                                           |
| `MANIFEST_INVALID`               | error         | `manifest.json` is not JSON, with or without comments.                                                                                                               |
| `NOT_MANIFEST_V3`                | error         | The manifest is not Manifest V3, the only kind Chrome runs.                                                                                                          |
| `NOT_AN_EXTENSION`               | error         | No `manifest.json` at the top, and not a WXT, CRXJS or Plasmo project.                                                                                               |
| `FILES_MISSING`                  | error         | `toolaby.js` or `toolaby.config.js` is missing: the folder is not wired.                                                                                             |
| `NO_TOOL_KEY`                    | error         | `toolaby.config.js` holds no tool key.                                                                                                                               |
| `KEY_NOT_TOOLABY`                | error         | The key names an address that is not Toolaby's, or a tool id no tool has. Nothing is asked of it.                                                                    |
| `TOOLABY_UNREACHABLE`            | error         | Toolaby could not be reached at the address the key names.                                                                                                           |
| `TOOL_NOT_FOUND`                 | error         | Toolaby does not sell the tool the key names: deleted, switched off, or on the other side.                                                                           |
| `STORAGE_MISSING`                | error         | The manifest has no `storage` permission.                                                                                                                            |
| `EXTERNALLY_CONNECTABLE_MISSING` | error         | Toolaby's address is not under `externally_connectable`: sign-in and the purchase never reach the extension.                                                         |
| `CSP_BLOCKS_TOOLABY`             | error         | The content security policy's `connect-src` (or `default-src`) leaves Toolaby's address out: every call of Toolaby's fails to fetch.                                 |
| `BACKGROUND_NOT_STARTED`         | error         | Nothing starts Toolaby in the background: none is named, it never starts Toolaby, a build's carries none of it, or it did not answer Toolaby's client (`--browser`). |
| `BACKGROUND_KEY_DIFFERS`         | error         | The background starts Toolaby with another key than `toolaby.config.js`.                                                                                             |
| `POPUP_NOT_MODULE`               | error         | A page loads a script with `import` or `export` lines without `type="module"`: Chrome refuses it and the page opens blank.                                           |
| `TEST_KEY_IN_LIVE_BUILD`         | error or warn | A store copy (its manifest has `update_url`) carries a Test or development key: an error. A production build that carries one: a warning.                            |
| `FEATURE_KEY_UNKNOWN`            | error         | The code gates on a feature key the tool does not have: every buyer is refused.                                                                                      |
| `NO_PAYWALL_POSSIBLE`            | error or warn | Nothing in the extension can ever ask anyone to pay. An error when the tool sells plans; a warning when it has none.                                                 |
| `BUILT_MISSING`                  | error         | The build named with `--built` has no `manifest.json`.                                                                                                               |
| `BUILT_KEY_MISSING`              | error         | The build carries no tool key: it leaves `toolaby.config.js` out, or it was not wired.                                                                               |
| `BUILT_FILE_MISSING`             | error         | The built manifest names a background, popup or side panel the build does not have.                                                                                  |
| `NOTHING_BUILT`                  | error         | `--browser` found no built extension to load.                                                                                                                        |
| `BROWSER_UNAVAILABLE`            | error         | `--browser` could not get Playwright or start Chromium.                                                                                                              |
| `PAGE_THREW`                     | error         | A page of the extension threw when it opened (`--browser`).                                                                                                          |
| `UNLOCK_OK`                      | ok            | The test licence `--as <plan>` names was entered in the loaded extension, and each of the plan's features unlocked.                                                  |
| `UNLOCK_FAILED`                  | error         | The test licence `--as <plan>` names did not unlock the plan's features in the loaded extension.                                                                     |
| `TEST_LICENCE_MISSING`           | error         | `--as <plan>` names a plan with no test licence: run `test-unlock <tool> --plan <plan>` first (Test only).                                                           |
| `HOST_PERMISSION_LEFT`           | warn          | The manifest gives Toolaby's address, or another side's Test address, a host permission or an entry Toolaby does not need.                                           |
| `MANIFEST_KEY_MISSING`           | warn          | The manifest has no `key`: each copy's id depends on its folder.                                                                                                     |
| `STORE_STEPS`                    | ok            | A Test or development key: the exact commands that put the Live key in for the store build, with this tool's id.                                                     |
| `KEY_OUTDATED`                   | warn          | The tool key is older than Toolaby's: the Free plan, the plans or the signing key changed since it was written.                                                      |
| `BUILT_KEY_DIFFERS`              | warn          | The build carries another tool key than `toolaby.config.js`: it was built before the key changed.                                                                    |
| `BUILT_NOT_CHECKED`              | warn          | A framework generates the manifest: the built one was not checked.                                                                                                   |
| `POPUP_SWAPPED`                  | warn          | Your code handles the popup itself (`setPopup()` or `getPopup()`).                                                                                                   |
| `CLIENT_OUTDATED`                | warn          | `toolaby.js` is not Toolaby's current client.                                                                                                                        |
| `CLIENT_UNREADABLE`              | warn          | Toolaby's current client could not be read to compare.                                                                                                               |
| `PAYWALL_PAGE_MISSING`           | warn          | The code calls `showPaywall()`, and the page it opens is not in the extension.                                                                                       |
| `FEATURE_NOT_SOLD`               | warn          | The code gates on a feature no plan unlocks.                                                                                                                         |
| `FEATURE_ARCHIVED`               | warn          | The code gates on an archived feature: whoever holds it keeps it, and the checkout no longer lists it.                                                               |
| `NO_PLAN`                        | warn          | The Free plan does not hold everything, and there is no plan to buy.                                                                                                 |
| `NOT_CHECKED_IN`                 | warn          | No copy of the extension has checked in yet.                                                                                                                         |
| `NOT_SIGNED_IN`                  | warn          | No session on the side the key is on: the checks that need one were not made.                                                                                        |
| `TOOL_NOT_YOURS`                 | warn          | The tool is not in the workspaces of the account signed in on that side.                                                                                             |
| `CLI_OUTDATED`                   | warn          | This command is older than the one Toolaby serves.                                                                                                                   |

A finding that passed has a code too, at level `ok`: `MANIFEST_OK`, `BACKGROUND_STARTS`, `BACKGROUND_ANSWERS` and the like. See [Troubleshoot an extension](https://toolaby.app/docs/troubleshooting.md) for each problem's fix, and [Build with an agent](https://toolaby.app/docs/agents.md).

### `mcp`

Runs the Toolaby MCP server on stdio, for coding agents: tools to read and change your tools on Toolaby, with the stored session. `--live` acts on Live. See [Build with an agent](https://toolaby.app/docs/agents.md).

### `webhooks events`

Lists the nine events with a description of each.

### `webhooks trigger <event>`

Sends the catalogue's example of the event. Without options, through Toolaby to every endpoint registered under **Configure → Webhooks**, as a real delivery: one of the workspace's 100 examples a day. With `--to <url>`, straight to that URL, signed with `--secret <whsec_…>` or your machine's development secret. See [Webhooks](https://toolaby.app/docs/reference/webhooks.md#testing-locally).

### `webhooks secret`

Prints your machine's development secret, made the first time it is needed and kept beside your sign-in (mode 600): what `trigger --to` signs with, for a server on your machine. Never a deployed endpoint's secret: that one is under **Reveal** on the endpoint, in **Configure → Webhooks**.

### `webhooks endpoints`, `webhooks add <url>`, `webhooks remove <id>`

Your workspace's endpoints, as **Configure → Webhooks** lists them. `add` registers one — the same address rule and cap of ten as the dashboard — and prints its signing secret once, or, with `--env-file .env`, writes it into that file as `TOOLABY_WEBHOOK_SECRET` and shows it nowhere, as a coding agent should; `--description <text>` names it and `--events a.b,c.d` narrows it (none: every event). `remove` stops deliveries to it at once. Every `webhooks` command acts on your first workspace unless `--workspace <slug>` names another, and needs a role that may configure it.

### `agents`

Registers Toolaby's MCP server in every coding agent it finds on this machine: Claude Code, Codex, Cursor, VS Code and Gemini CLI. Running it again changes nothing that is already right. `--client claude-code|codex|cursor|vscode|gemini` names one; `--project` writes the project's own files instead of your user settings; `--live` registers `toolaby-live`, which acts on Live. See [Build with an agent](https://toolaby.app/docs/agents.md).

### The tool, its prices and its buyers

Each of these is also a tool of the MCP server, with the same arguments, so an agent without MCP runs the command. `--json` prints Toolaby's answer as it is; `--live` acts on Live. A command that takes something away (`plan retire`, `grant revoke`, `refund`, `cancel`) asks first in a terminal, and takes `--yes` without one.

| Command                                                                                                                                                                                                    | Does                                                                                                                                                                                                                                                                                                                                                                                                                                                            |
| ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `tool get <tool-id>`                                                                                                                                                                                       | One tool: the Free plan, features, plans, the tool key, the extension's id, the checkout page, whether a copy has checked in.                                                                                                                                                                                                                                                                                                                                   |
| `tool create --name <name> [--id <id>] [--tagline <line>] [--workspace <slug>]`                                                                                                                            | A new tool.                                                                                                                                                                                                                                                                                                                                                                                                                                                     |
| `tool update <tool-id> [--name] [--tagline] [--description] [--store-url <link>] [--edge-url <link>]`                                                                                                      | The tool's name, line, Chrome Web Store listing or Microsoft Edge Add-ons listing.                                                                                                                                                                                                                                                                                                                                                                              |
| `access <tool-id> --access open\|free_uses\|paid [--free-uses N] [--free-uses-per ever\|day\|week\|month] [--sign-in-required] [--now]`                                                                    | What the Free plan holds: everything, a number of uses per device (per account with `--sign-in-required`; for good, or each day, week or month, in UTC), or nothing. While copies are installed, a Free plan that counts uses is opened only with `--now`, your own word once the build that gates by feature is out. See [Access and features](https://toolaby.app/docs/free-plan.md).                                                                                               |
| `features <tool-id> --features '<json>' [--plans '<json>']`                                                                                                                                                | The tool's whole list of features, and the plans that unlock each. Add the plans first.                                                                                                                                                                                                                                                                                                                                                                         |
| `plan add <tool-id> --billing one_time\|subscription [--interval month\|year] --amount 5 --currency usd [--name Pro] [--trial-days N] [--devices N] [--per-seat]`                                          | A plan, one-time or a subscription, with a trial, a device count, per seat. It answers the plan's id.                                                                                                                                                                                                                                                                                                                                                           |
| `plan update <tool-id> <plan> [--name] [--trial-days N] [--devices N] [--per-seat \| --no-per-seat]`                                                                                                       | A plan's name, trial, devices or per seat. Its price does not change.                                                                                                                                                                                                                                                                                                                                                                                           |
| `plan retire <tool-id> <plan>`                                                                                                                                                                             | Takes a plan off sale. Holders keep it.                                                                                                                                                                                                                                                                                                                                                                                                                         |
| `grant <tool-id> <email> [--days N]`                                                                                                                                                                       | Access without paying, to an address; answers the grant's id.                                                                                                                                                                                                                                                                                                                                                                                                   |
| `grant revoke <tool-id> <id>`                                                                                                                                                                              | Takes a grant back.                                                                                                                                                                                                                                                                                                                                                                                                                                             |
| `coupon create <tool-id> [--code CODE] [--percent-off N \| --amount-off N --currency usd] [--duration once\|repeating\|forever] [--months N] [--max-redemptions N] [--expires-days N] [--first-time-only]` | A coupon code for checkout, such as one for a store reviewer. 100 a day per workspace.                                                                                                                                                                                                                                                                                                                                                                          |
| `customers <tool-id> [--query <text>] [--limit N]`                                                                                                                                                         | The tool's customers, by a search text.                                                                                                                                                                                                                                                                                                                                                                                                                         |
| `customer <tool-id> <email>`                                                                                                                                                                               | One buyer: entitled or not, plan, licences, subscription status and renewal, grants, devices.                                                                                                                                                                                                                                                                                                                                                                   |
| `analytics <tool-id> [--range 7d\|30d\|90d\|12m] [--channel app\|web] [--table devices\|cohorts\|revenue\|features\|versions\|uninstalls] [--comments]`                                                    | The tool's [Analytics](https://toolaby.app/docs/analytics.md): active devices, the funnel, revenue and monthly recurring revenue, uninstalls; or one table as rows. Held to the workspace's plan, as the dashboard is. `--channel` takes the extension's devices (`app`) or a [web app](https://toolaby.app/docs/analytics.md#web-apps)'s signed-in buyers (`web`) alone; both without it. What people wrote on the uninstall page comes with `--comments` only: anyone who removed the extension wrote it. |
| `refund <tool-id> <email> [--licence <id>] [--reason requested_by_customer\|duplicate\|fraudulent]`                                                                                                        | Refunds a buyer's licence and revokes it.                                                                                                                                                                                                                                                                                                                                                                                                                       |
| `cancel <tool-id> <email> [--subscription <id>] [--now]`                                                                                                                                                   | Cancels a buyer's subscription, at the period's end or now.                                                                                                                                                                                                                                                                                                                                                                                                     |
| `stripe [--workspace <slug>]`                                                                                                                                                                              | Whether Stripe is connected, and the link to connect it.                                                                                                                                                                                                                                                                                                                                                                                                        |
| `api-keys create --env-file .env [--name <name>] [--workspace <slug>]`                                                                                                                                     | A server API key, written into the env file as `TOOLABY_API_KEY`, with `TOOLABY_API_URL`, and never shown.                                                                                                                                                                                                                                                                                                                                                      |
| `test-unlock <tool-id> [--plan <plan>]`                                                                                                                                                                    | Test only: your Test buyer given a plan, for `check --browser --as <plan>`.                                                                                                                                                                                                                                                                                                                                                                                     |
| `copy-to-live <tool-id>`                                                                                                                                                                                   | Copies the tool from Test to Live: the Free plan, features, plans, versions and the extension's identity. Needs a session on each side.                                                                                                                                                                                                                                                                                                                         |
| `docs [page]`, `docs search <query>`                                                                                                                                                                       | Toolaby's docs as Markdown, and the pages that match a query.                                                                                                                                                                                                                                                                                                                                                                                                   |

### `login`, `whoami`, `logout`

`login` starts the sign-in without waiting for a command that needs it. A coding agent runs `login --no-wait --json`, which answers the link and code to show you at once, then `login --poll <deviceCode>` until you approve. `whoami` shows the account and workspaces of the current side. `logout` revokes the session on the server and forgets it.

## Signing in

The command shows a code and opens `/dev/device` in the dashboard. Sign in there if you are not, confirm the code, and select **Approve**. The command continues and keeps a session of its own.

It opens only an `https` page on Toolaby's own address, or a page on this machine for a server on this machine; any other address Toolaby answers with is refused, and nothing is opened.

Codes last ten minutes and can be used once. The command waits as long as the code lasts, and 15 minutes at most. The page shows the account and workspaces that will be signed in; **Use another account** switches.

## Session storage

`~/.config/toolaby/credentials.json`, one entry per server, readable by you alone. In CI, set `TOOLABY_TOKEN` instead, to the `token` that `npx -y toolaby@latest login` stored for that server in this file: it is sent to Live alone, or to the one server `TOOLABY_TOKEN_SERVER` names — `https://toolaby.app/test` for a job on Test. A server that refuses it gets the session `npx -y toolaby@latest login` stored for it, if there is one. `TOOLABY_SERVER` or `--server <url>` names any other server, such as one on your own machine. Whichever server it is, the command reads at most 4 MB of an answer and waits 30 seconds for the whole of it; past either, it stops and says so.

## Updates

`npx -y toolaby@latest` runs the newest version every time. A copy installed or pinned to a version says when it falls behind:

* **A newer version.** Once a day the command asks npm for its latest version, while it works, and remembers the answer in `~/.config/toolaby/update-check.json`. After a command that is not the latest, one line on stderr says *toolaby 1.14.0 is out; this is 1.13.0*, with the way to update. It never slows a command by more than half a second, never fails one, and is off in CI and with `TOOLABY_NO_UPDATE_CHECK=1`. `mcp` says it in the instructions its agent reads.
* **Too old for Toolaby.** Toolaby answers a command older than the oldest it supports with `426` and `cli_outdated`, and the command prints *This toolaby is 1.11.1; Toolaby needs 1.12.0 or later*, with the way to update. Every version prints that answer, the ones installed before it existed too.
