Toolaby Wall

Command line

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

toolaby is the Wall's command line. It signs in as you with a code you approve in the browser, and acts only on tools in your workspaces.

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 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 mcp                     # the Wall for coding agents, an MCP server on stdio
npx -y toolaby@latest webhooks events         # the six events, with an example of each
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

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 the test Wall (toolaby.app/test) unless given --live, which acts on the live Wall (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. Writes the client, the Wall popup — and its side panel, when the extension has one — and the tool key, and edits manifest.json: the permissions, the host permissions and the key that gives every copy the same id. For a WXT, CRXJS or Plasmo project it writes the same files where the build expects them and prints a snippet for the framework's config. See Frameworks.

Wired for the first time, the Wall popup stands in front of yours. An extension whose own pages draw the paywall — appPopup: '' in toolaby.config.js, as create makes it — keeps them in front: the Wall's pages are written beside them, for showPaywall(). See Gate your extension.

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

  • A classic background keeps its importScripts(). The Wall background loads the client as a script, toolaby-client.js.
  • A module background is imported: import './background.js'.
  • A background that starts the Wall 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.

When your code handles its popup itself, with chrome.action.setPopup() or getPopup(), a first wiring keeps your popup in front: the Wall 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 Wall 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.
--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 Wall popup is written beside it, and toolaby.showPaywall() opens it.
--liveAct on the live Wall.

A folder with no manifest.json at the top and no recognised framework gets nothing written; the command says what it found.

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 Wall 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 Wall popup, the key — replaced with the current ones, in the extension's folder or --into <folder>. Your own files are not touched, and whichever page stands in front stays there. Run it when the changelog says the popup or the client changed, and after changing what the Free plan holds or adding the first subscription plan (the key carries both), then rebuild and reload.

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, the Wall'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 buyer-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) that holds a manifest.json. It writes <name>-<version>.zip here (--out <file.zip> for another name) and sends nothing anywhere.

  • 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, and buyers would be asked for the rest at install. --allow-wall-hosts packs it anyway.

  • It refuses a manifest that does not name the Wall its key points at in both host_permissions and externally_connectable: sign-in and the purchase handoff would not reach the extension.

  • 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_…),
    • 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: the Wall's files, the tool key and the tool it names, the manifest and its key, whether the background starts the Wall, code that handles the popup itself, the client's version, the feature keys the code gates on against the tool's (comments are skipped), the page showPaywall() opens, and, signed in, whether a copy has checked in and whether the key is current. It exits with 1 when something fails. --browser also loads the extension in Chromium, asks its background a question and opens its pages; it needs Playwright in the project. See Build with an agent.

mcp

Runs the Toolaby MCP server on stdio, for coding agents: tools to read and change your tools on the Wall, with the stored session. --live acts on Live. See Build with an agent.

webhooks events

Lists the six events with a description of each.

webhooks trigger <event>

Sends the catalogue's example of the event. Without options, through the Wall 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 is the one the portal shows.

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; --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.

login, whoami, logout

login starts the sign-in without waiting for a command that needs it. 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 the Wall's own address, or a page on this machine for a Wall on this machine; any other address the Wall 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 Wall, readable by you alone. In CI, set TOOLABY_TOKEN instead: it is sent to Live alone, or to the one Wall TOOLABY_TOKEN_WALL names — https://toolaby.app/test for a job on Test. A Wall that refuses it gets the session toolaby login stored for it, if there is one. TOOLABY_WALL or --wall <url> names any other Wall, such as one on your own machine. Whichever Wall 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.

On this page