Toolaby

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.

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.

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.

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.

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.
--forceReplace platform files that are already there — after Copy to Live, to swap in the live key.
--adopt-keyWhen 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-popupKeep your popup in front. The Toolaby popup is written beside it, and toolaby.showPaywall() opens it.
--toolaby-popupPut 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-extensionpayMove 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.
--liveAct 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. Troubleshoot an extension 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 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.

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.

It also writes AGENTS.md, CLAUDE.md, TOOLABY.md and .claude/skills/toolaby/SKILL.md for coding agents. See Build with an agent.

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.

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.

OptionWhat it does
--jsonPrints 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.
--browserLoads 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:

{
  "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" }
  ]
}
FieldTypeDescription
okbooleanfalse when a finding is an error.
sidestringTest or Live: the side the tool key is on. For a development server's key, the address --server names, or Development.
folderstringThe folder checked.
builtstring, nullableThe build checked, or null.
findings[].codestringWhat was found. The codes never change.
findings[].levelstringerror (it must be fixed), warn (worth a look) or ok.
findings[].messagestringWhat was found, and why it matters.
findings[].filestringThe file it is about, relative to folder. Left out where there is none.
findings[].linenumberThe line in that file. Left out where there is none.
findings[].fixstringA command that fixes it, on the key's side: --live for a Live key. Left out where only your code can fix it.
findings[].docsstringThe 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:

CodeLevelMeans
FOLDER_MISSINGerrorThe folder does not exist.
MANIFEST_INVALIDerrormanifest.json is not JSON, with or without comments.
NOT_MANIFEST_V3errorThe manifest is not Manifest V3, the only kind Chrome runs.
NOT_AN_EXTENSIONerrorNo manifest.json at the top, and not a WXT, CRXJS or Plasmo project.
FILES_MISSINGerrortoolaby.js or toolaby.config.js is missing: the folder is not wired.
NO_TOOL_KEYerrortoolaby.config.js holds no tool key.
KEY_NOT_TOOLABYerrorThe key names an address that is not Toolaby's, or a tool id no tool has. Nothing is asked of it.
TOOLABY_UNREACHABLEerrorToolaby could not be reached at the address the key names.
TOOL_NOT_FOUNDerrorToolaby does not sell the tool the key names: deleted, switched off, or on the other side.
STORAGE_MISSINGerrorThe manifest has no storage permission.
EXTERNALLY_CONNECTABLE_MISSINGerrorToolaby's address is not under externally_connectable: sign-in and the purchase never reach the extension.
CSP_BLOCKS_TOOLABYerrorThe content security policy's connect-src (or default-src) leaves Toolaby's address out: every call of Toolaby's fails to fetch.
BACKGROUND_NOT_STARTEDerrorNothing 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_DIFFERSerrorThe background starts Toolaby with another key than toolaby.config.js.
POPUP_NOT_MODULEerrorA page loads a script with import or export lines without type="module": Chrome refuses it and the page opens blank.
TEST_KEY_IN_LIVE_BUILDerror or warnA 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_UNKNOWNerrorThe code gates on a feature key the tool does not have: every buyer is refused.
NO_PAYWALL_POSSIBLEerror or warnNothing in the extension can ever ask anyone to pay. An error when the tool sells plans; a warning when it has none.
BUILT_MISSINGerrorThe build named with --built has no manifest.json.
BUILT_KEY_MISSINGerrorThe build carries no tool key: it leaves toolaby.config.js out, or it was not wired.
BUILT_FILE_MISSINGerrorThe built manifest names a background, popup or side panel the build does not have.
NOTHING_BUILTerror--browser found no built extension to load.
BROWSER_UNAVAILABLEerror--browser could not get Playwright or start Chromium.
PAGE_THREWerrorA page of the extension threw when it opened (--browser).
UNLOCK_OKokThe test licence --as <plan> names was entered in the loaded extension, and each of the plan's features unlocked.
UNLOCK_FAILEDerrorThe test licence --as <plan> names did not unlock the plan's features in the loaded extension.
TEST_LICENCE_MISSINGerror--as <plan> names a plan with no test licence: run test-unlock <tool> --plan <plan> first (Test only).
HOST_PERMISSION_LEFTwarnThe manifest gives Toolaby's address, or another side's Test address, a host permission or an entry Toolaby does not need.
MANIFEST_KEY_MISSINGwarnThe manifest has no key: each copy's id depends on its folder.
STORE_STEPSokA Test or development key: the exact commands that put the Live key in for the store build, with this tool's id.
KEY_OUTDATEDwarnThe tool key is older than Toolaby's: the Free plan, the plans or the signing key changed since it was written.
BUILT_KEY_DIFFERSwarnThe build carries another tool key than toolaby.config.js: it was built before the key changed.
BUILT_NOT_CHECKEDwarnA framework generates the manifest: the built one was not checked.
POPUP_SWAPPEDwarnYour code handles the popup itself (setPopup() or getPopup()).
CLIENT_OUTDATEDwarntoolaby.js is not Toolaby's current client.
CLIENT_UNREADABLEwarnToolaby's current client could not be read to compare.
PAYWALL_PAGE_MISSINGwarnThe code calls showPaywall(), and the page it opens is not in the extension.
FEATURE_NOT_SOLDwarnThe code gates on a feature no plan unlocks.
FEATURE_ARCHIVEDwarnThe code gates on an archived feature: whoever holds it keeps it, and the checkout no longer lists it.
NO_PLANwarnThe Free plan does not hold everything, and there is no plan to buy.
NOT_CHECKED_INwarnNo copy of the extension has checked in yet.
NOT_SIGNED_INwarnNo session on the side the key is on: the checks that need one were not made.
TOOL_NOT_YOURSwarnThe tool is not in the workspaces of the account signed in on that side.
CLI_OUTDATEDwarnThis 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 for each problem's fix, and Build with an agent.

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.

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.

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.

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.

CommandDoes
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.
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: 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'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.

On this page