{"_id":"@abilityowl/cli","_rev":"2-fda6afee53a04c3f3ccd76b22f18557a","name":"@abilityowl/cli","dist-tags":{"latest":"0.2.1"},"versions":{"0.2.0":{"name":"@abilityowl/cli","version":"0.2.0","license":"UNLICENSED","_id":"@abilityowl/cli@0.2.0","maintainers":[{"name":"abilityowl_john","email":"admin@abilityowl.com"}],"bin":{"ao":"dist/index.js","abilityowl":"dist/index.js"},"dist":{"shasum":"1611d9a744bbc3ea9f5065c4f3d7d959d23587a4","tarball":"https://registry.npmjs.org/@abilityowl/cli/-/cli-0.2.0.tgz","fileCount":4,"integrity":"sha512-01oaQ5qEMK3QKetHRFnj1EYldxAmfBKY2i/hldUj40EYzy9Lj2vMDg3d6bjDSjRZZyPfsAaVzRyHpWzj3JPEEA==","signatures":[{"sig":"MEUCIQDE21qWMlZIsvyrXrPJKTRcyowo2/ta8hC5WnSBeRlu9gIgCEBr52ZY9r2fLZt5aIPzjvxTLMVHGLcfA39YrLUXcNI=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":165598},"type":"module","engines":{"node":">=20.0.0"},"gitHead":"e0335b22c8085b2eb263fab95b9d0fae724bf55e","scripts":{"build":"tsup"},"_npmUser":{"name":"abilityowl_john","email":"admin@abilityowl.com"},"_npmVersion":"10.9.8","description":"Ability Owl from your terminal — manage sites, pages, scans and issues.","directories":{},"_nodeVersion":"22.23.2","dependencies":{"zod":"3.25.76","open":"10.1.0","commander":"12.1.0","@modelcontextprotocol/sdk":"1.30.0"},"publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"@abilityowl/mcp":"0.1.0","@abilityowl/core":"0.1.0"},"_npmOperationalInternal":{"tmp":"tmp/cli_0.2.0_1789143235671_0.8355697085503857","host":"s3://npm-registry-packages-npm-production"}},"0.2.1":{"name":"@abilityowl/cli","version":"0.2.1","type":"module","description":"Ability Owl from your terminal — manage sites, pages, scans and issues.","license":"UNLICENSED","publishConfig":{"access":"public"},"engines":{"node":">=20.0.0"},"bin":{"abilityowl":"dist/index.js","ao":"dist/index.js"},"scripts":{"build":"tsup"},"dependencies":{"@modelcontextprotocol/sdk":"1.30.0","commander":"12.1.0","open":"10.1.0","zod":"3.25.76"},"devDependencies":{"@abilityowl/core":"0.1.0","@abilityowl/mcp":"0.1.0"},"_id":"@abilityowl/cli@0.2.1","gitHead":"fa6d6efd95843f43d1ade6b09a12b31a156cdaae","_nodeVersion":"22.23.2","_npmVersion":"10.9.8","dist":{"integrity":"sha512-CUcYfMwr/5P2p8HDZaRuxOAM+96xkxJTzOGe1lDtxLdYseFtxzithP0BB+RWgLhrq+paifDZ+j/5/2NwdxwHUA==","shasum":"83cdafaec56e0be3dcf56f00c95e99f462fd37d7","tarball":"https://registry.npmjs.org/@abilityowl/cli/-/cli-0.2.1.tgz","fileCount":4,"unpackedSize":166991,"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEQCIBHnsHl8aSXYmLhwrsAw77udP8tVoBrQxAr8pyLDcFHfAiALdz6SOIdTveMg1AlkTscCRAGWlg5H31vPdkqW6ODGPA=="}]},"_npmUser":{"name":"abilityowl_john","email":"admin@abilityowl.com"},"directories":{},"maintainers":[{"name":"abilityowl_john","email":"admin@abilityowl.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/cli_0.2.1_1789157826138_0.5484521822445083"},"_hasShrinkwrap":false}},"time":{"created":"2026-09-11T16:13:55.467Z","modified":"2026-09-11T20:17:06.463Z","0.2.0":"2026-09-11T16:13:55.805Z","0.2.1":"2026-09-11T20:17:06.318Z"},"license":"UNLICENSED","description":"Ability Owl from your terminal — manage sites, pages, scans and issues.","maintainers":[{"name":"abilityowl_john","email":"admin@abilityowl.com"}],"readme":"# Ability Owl CLI\n\nManage your Ability Owl sites, pages, scans and accessibility issues from the terminal.\n\n```bash\nnpx @abilityowl/cli login\n```\n\nOr install it:\n\n```bash\nnpm install -g @abilityowl/cli\nabilityowl login\n```\n\nBoth `abilityowl` and the shorter `ao` are installed.\n\n## Requirements\n\n- Node 20 or newer.\n- An Ability Owl account on the Business or Agency plan. Free and Starter accounts\n  can sign in, but every command returns a plan message and exits 4.\n\n## Signing in\n\n`abilityowl login` opens your browser, asks you to approve access, and stores the\nresult in `~/.config/abilityowl/credentials.json` (mode `0600`).\n\nOn a machine with no browser (an SSH session, a container, a CI shell), use:\n\n```bash\nabilityowl login --no-browser\n```\n\nThat prints the sign-in URL, you open it somewhere you do have a browser, and you\npaste the code back into the terminal. The pasted value is `state.code` (a single\ndot-separated token) — paste it whole; splitting it or pasting only part of it\nmeans the CLI can't tell it apart from a code meant for a different terminal\nsession.\n\n`abilityowl logout` revokes the credential at the server and deletes it locally.\nIf the server revocation fails, the CLI still deletes the local credential and\nprints a warning pointing at Settings → Connected apps, where you can revoke it\nby hand. You can also revoke it from there at any time.\n\n## Commands\n\n### Account\n\n| Command | What it does |\n|---|---|\n| `abilityowl login [--no-browser]` | Sign in |\n| `abilityowl logout` | Revoke and delete the stored credential |\n| `abilityowl whoami` | Show your email, plan, site count, profile and API URL |\n\n### Sites\n\n| Command | What it does |\n|---|---|\n| `abilityowl sites list` | List your sites |\n| `abilityowl sites get <id>` | Show one site |\n| `abilityowl sites create <domain> [--name <name>] [--add-homepage]` | Add a site |\n| `abilityowl sites delete <id> [--yes]` | Delete a site and everything under it |\n| `abilityowl sites score <id>` | Show the site's current Ability score |\n\n`sites create` accepts either `example.com` or `https://example.com/anything`; only\nthe host is used. A scheme other than `http`/`https`, or a hostname with no dot\n(other than `localhost`), is rejected rather than guessed at. The display name\ndefaults to the domain.\n\n### Pages\n\n| Command | What it does |\n|---|---|\n| `abilityowl pages list --site <id> [--page <n>] [--limit <n>] [--search <text>] [--status <s>] [--sort <field>]` | List a site's pages |\n| `abilityowl pages get <id>` | Show one page |\n| `abilityowl pages add --site <id> <url...>` | Add pages |\n| `abilityowl pages add --site <id> --file urls.txt` | Add pages from a file |\n| `cat urls.txt \\| abilityowl pages add --site <id>` | Add pages from stdin |\n| `abilityowl pages add --site <id> -` | Add pages from stdin explicitly |\n| `abilityowl pages delete <id> [--yes]` | Delete a page and its scans, results and issues |\n\nPages come from arguments, `--file <path>`, or stdin, never more than one source\nat once. Stdin is only read when you pass `-` as a URL argument, or when stdin\nisn't a terminal (a pipe or redirect); on an interactive terminal with no\narguments and no `--file`, the command fails with \"No URLs given\" rather than\nhanging while it waits for input nobody is going to type. URL files, and stdin,\nignore blank lines and lines starting with `#`. Duplicate URLs are removed before\nsending. At most 1000 URLs per run.\n\n### Scans\n\n| Command | What it does |\n|---|---|\n| `abilityowl scans list --page <id> [--limit <n>]` | Scans for one page |\n| `abilityowl scans list --site <id> [--status <list>] [--sort queue\\|history] [--limit <n>] [--page-number <n>]` | Scans across a site |\n| `abilityowl scans get <id>` / `abilityowl scans status <id>` | Show one scan |\n| `abilityowl scans run --page <id> [--type <name>...] [--force] [--wait] [--timeout <minutes>] [--poll-interval <scale>]` | Queue a scan |\n| `abilityowl scans run --site <id> [--type <name>...] [--force] [--wait] [--timeout <minutes>] [--poll-interval <scale>]` | Queue a scan for every enabled page on the site |\n\n`scans list` needs exactly one of `--page` or `--site`.\n\n`--wait` polls until the scan finishes, backing off from 2 to 15 seconds. It exits\n0 when the scan completes (or was skipped because the page had not changed), 2 when\nthe scan failed, and 1 if the wait times out (`--timeout <minutes>`, default 30).\n\n`--site` has no single API call behind it: the CLI lists every enabled page on the\nsite and sends one `scans run` per page, from your machine. Every scan is queued\nfirst, before any waiting starts, so a failure partway through doesn't lose the\nrecord of what was already queued. With `--wait`, all queued scans share one\ndeadline rather than each getting its own fresh timeout. This takes longer than a\nsingle scan and consumes one rescan credit per page.\n\n### Issues\n\n| Command | What it does |\n|---|---|\n| `abilityowl issues list [--site <id>] [--page <id>] [--scan <id>] [--severity <level>] [--criteria <ref>] [--criteria-id <id>] [--status <s>] [--freshness <mode>] [--limit <n>] [--skip <n>]` | List issues |\n| `abilityowl issues get <id>` | Show one issue |\n\n`--severity` takes a single value: `minor`, `moderate`, `serious`, `critical`,\n`unknown`, `passive` or `manual-review`. `--criteria` takes a rule reference such\nas `color-contrast`; `--criteria-id` takes a WCAG or custom criteria id. `--limit`\ndefaults to 50 (max 100) and `--skip` defaults to 0.\n\n### MCP\n\n| Command | What it does |\n|---|---|\n| `abilityowl mcp` | Run the MCP server on stdio (clients start this for you) |\n| `abilityowl mcp install [--client claude-code\\|claude-desktop\\|cursor]` | Print the configuration |\n| `abilityowl mcp install --client <name> --write [--path <file>]` | Merge it into the client's config file |\n\n`--write` merges: your other MCP servers and every unrelated setting in the file are\nleft exactly as they were. It asks before writing unless you pass `--yes`.\n\nSee \"Use with Claude Code, Claude Desktop and Cursor\" below for what the assistant\ncan do, and how it behaves before you're signed in.\n\n## Global options\n\n| Option | Environment variable | Default |\n|---|---|---|\n| `--json` | — | off |\n| `--profile <name>` | `ABILITYOWL_PROFILE` | the credential file's default profile |\n| `--api-url <url>` | `ABILITYOWL_API_URL` | `https://api.abilityowl.com` |\n| `--auth-url <url>` | `ABILITYOWL_AUTH_URL` | `https://auth.abilityowl.com` |\n| `-y, --yes` | — | off |\n\nPrecedence is flag, then environment variable, then the value stored on the profile,\nthen the default. See \"Pointing the CLI at a different server\" for how the URL\nvalues are meant to be used.\n\n### Trust boundary\n\n`--api-url`/`--auth-url` (and their environment variables) control where your\nlogin token is sent — set them only to hosts you trust. `abilityowl login`\nstores whichever `apiUrl`/`authUrl` it signed in with on the profile, and\nevery later command against that profile reuses them, so a poisoned or\nmistyped value doesn't just fail to connect, it can hand your token to the\nwrong server. Each value is validated on every use (`https://` only, except\n`http://` for `localhost`/`127.0.0.1` during local development) and rejected\nwith a clear error otherwise. Whenever a resolved URL differs from the\ndocumented default, the CLI prints one line to stderr naming both hosts —\nstdout, and `--json` output, stay untouched.\n\n`--json` prints the API response body exactly as the server sent it, so scripts never\ndepend on how a table happens to be laid out:\n\n```bash\nabilityowl issues list --page pg1 --json | jq '.data.items[].criteriaReference'\n```\n\n`scans run --site` can queue several scans in one call. With `--json` it prints\none compact JSON line per scan (NDJSON) instead of a single document, so each\nline can still be parsed on its own.\n\n## Confirmation prompts\n\n`sites delete` and `pages delete` ask you to confirm before deleting anything.\nPass `--yes` (or `-y`) to skip the prompt. Without it, the CLI refuses to prompt,\nand exits 1 instead of asking, when stdin isn't a terminal or `--json` is set,\nsince there is nobody there to answer. Typing \"no\" at a real prompt prints\n`Cancelled.` to stderr and exits 0.\n\n## Profiles\n\nEach profile is a separate login, stored under its own key in\n`~/.config/abilityowl/credentials.json`:\n\n```bash\nabilityowl login --profile work\nabilityowl login --profile personal\n\nabilityowl sites list --profile work\nABILITYOWL_PROFILE=personal abilityowl sites list\n```\n\nThe first profile you create becomes the default. Each profile also remembers\nthe server it was signed in to (next section).\n\n## Pointing the CLI at a different server\n\nThe CLI talks to `https://api.abilityowl.com` and `https://auth.abilityowl.com`\nunless you tell it otherwise. If you have been given a different pair of\nhosts, pass them when you sign in:\n\n```bash\nabilityowl login --api-url https://api.example.com --auth-url https://auth.example.com\n```\n\nBoth are saved on the profile, so every later command on that profile uses them\nwithout being told again. `abilityowl whoami` shows which API a profile is using.\n\nTo use a different server temporarily, or from a script, set the environment\nvariables or pass the flags on the command itself; either beats the value stored\non the profile for that one run:\n\n```bash\nABILITYOWL_API_URL=https://api.example.com ABILITYOWL_AUTH_URL=https://auth.example.com abilityowl sites list\nabilityowl sites list --api-url https://api.example.com --auth-url https://auth.example.com\n```\n\nA login token only works on the server that issued it, so to move a profile to\nanother server, sign in again with the new URLs (or use a second profile and keep\nboth logins). If a server is unreachable the command fails with\n`Could not reach <url>`; check the hostname before anything else. The rules on\nwhich URLs are accepted, and the warning the CLI prints when it is not using\nthe defaults, are under \"Trust boundary\" above.\n\n## Where credentials live\n\n`$XDG_CONFIG_HOME/abilityowl/credentials.json`, defaulting to\n`~/.config/abilityowl/credentials.json` (`%APPDATA%\\abilityowl\\credentials.json` on\nWindows). The directory is `0700` and the file is `0600`, written atomically. It holds\nan opaque access token and a rotating refresh token: no password, and nothing that\nidentifies you beyond your email address.\n\nDelete the file to sign out of every profile at once. That does not revoke the tokens\nat the server; use `abilityowl logout` or Settings → Connected apps for that.\n\n## Exit codes\n\n| Code | Meaning |\n|---|---|\n| 0 | Success |\n| 1 | General failure |\n| 2 | A scan finished in a failed state (`scans run --wait`) |\n| 3 | Not signed in, run `abilityowl login` |\n| 4 | Your plan does not allow this |\n\n## Use with Claude Code, Claude Desktop and Cursor\n\n`abilityowl mcp` runs the Ability Owl MCP server on stdio, so Claude Code, Claude\nDesktop and Cursor can list your sites, pages, scans and issues, and queue new\nscans, from inside a chat. It reuses the login you already have — there is no\nsecond sign-in, and no API key to paste anywhere. It resolves credentials from\nthe same `~/.config/abilityowl/credentials.json` profile as every other\ncommand — run `abilityowl login` first. It also accepts the same `--profile`,\n`--api-url` and `--auth-url` global flags as every other command, e.g.\n`abilityowl mcp --profile work`.\n\n`abilityowl mcp install` prints the configuration for each client without writing\nanything:\n\n```bash\nabilityowl mcp install\n```\n\nPass `--client claude-code`, `--client claude-desktop` or `--client cursor` to see\njust one. Claude Code is configured by a command, not a file:\n\n```bash\nclaude mcp add abilityowl -- abilityowl mcp\n```\n\nClaude Desktop and Cursor read a JSON file. `--write` edits it for you — it\nread-merges-writes, so any other key or `mcpServers` entry already in the file is\nleft untouched:\n\n```bash\nabilityowl mcp install --client claude-desktop --write\nabilityowl mcp install --client cursor --write\n```\n\n`--write` asks for confirmation before creating or changing the file (`--yes` to\nskip it, same as `sites delete`), does nothing and says so when the file already\nhas the right entry, and prints the Claude Code command instead of writing\nanything when run with `--client claude-code` (that client has no file to write).\nUse `--path <file>` to target a file other than the client's default location —\nuseful for testing, or for a client whose config lives somewhere nonstandard.\n\nThe write is atomic: the merged config is written to a temp file in the same\ndirectory as the target, then renamed into place, so an interrupted write can\nnever truncate or corrupt a config file a real client reads back on every\nlaunch. And it never guesses at a file it doesn't understand — if the config\nfile's contents parse as JSON but the root, or its `mcpServers` value, isn't a\nplain object, `--write` refuses with a clear error and exits 1 rather than\nreplacing whatever was actually there. `--json` works with `--write` too,\nprinting `{ client, configPath, written, changed }` instead of the prose above.\n\nThe default file locations:\n\n| Client | Config file |\n|---|---|\n| Claude Desktop (macOS) | `~/Library/Application Support/Claude/claude_desktop_config.json` |\n| Claude Desktop (Windows) | `%APPDATA%\\Claude\\claude_desktop_config.json` |\n| Claude Desktop (Linux) | `$XDG_CONFIG_HOME/Claude/claude_desktop_config.json` (default `~/.config/Claude/...`) |\n| Cursor | `<project>/.cursor/mcp.json` |\n\nEvery entry runs the bare `abilityowl` command (no absolute path), so the client\nlaunching it needs `abilityowl` on its `PATH` — a GUI app like Claude Desktop\ndoes not always inherit your shell's `PATH`, so if it can't start the server,\ncheck that `abilityowl` resolves from a terminal opened the same way the app was.\n\n`abilityowl mcp` itself never writes to stdout — that stream carries only MCP\nprotocol frames. Diagnostics, including the trust-boundary warning for a\nnon-default `--api-url`/`--auth-url`, go to stderr.\n\n### What the assistant can do\n\n| Tool | What it does |\n|---|---|\n| `list_sites`, `get_site` | Read your sites |\n| `create_site` | Add a site |\n| `delete_site` | Delete a site and everything under it |\n| `get_ability_score` | A site's current Ability score |\n| `list_pages`, `get_page` | Read a site's pages |\n| `add_page` | Add one page or bulk-import up to 1000 |\n| `delete_page` | Delete a page and its scans and results |\n| `list_scans`, `get_scan` | Read scans and their status |\n| `run_scan` | Queue a scan for a page, or for every enabled page on a site |\n| `list_issues`, `get_issue` | Read the accessibility issues scans found |\n\nReads are marked read-only, the two deletes are marked destructive, and\n`create_site`, `add_page` and `run_scan` are marked non-idempotent, so a client\ncan ask you before running them.\n\n`run_scan` returns as soon as the scan is queued — scans take minutes. The\nassistant polls `get_scan` for the result. Each queued scan costs one rescan\ncredit, so a site-wide run is capped by `maxPages` (default 50, max 500) —\nabove the cap it refuses and tells you how many pages the site actually has,\nrather than silently queuing that many scans.\n\nTwo resources come with it: `abilityowl://wcag/criteria` (every WCAG 2.2\nsuccess criterion, served locally, no login needed) and\n`abilityowl://issues/definitions` (every issue type Ability Owl detects, and\nthe criteria it maps to).\n\n### Profiles and servers\n\nThe server reads the same credential store and the same environment variables\nas the rest of the CLI. A profile that was signed in with its own API and auth\nURLs already carries them, so the only thing a client usually needs is the\nprofile name:\n\n```json\n{\n  \"mcpServers\": {\n    \"abilityowl-work\": {\n      \"command\": \"abilityowl\",\n      \"args\": [\"mcp\"],\n      \"env\": { \"ABILITYOWL_PROFILE\": \"work\" }\n    }\n  }\n}\n```\n\n`ABILITYOWL_API_URL` and `ABILITYOWL_AUTH_URL` work in the same `env` block and\noverride what the profile stored, exactly as they do on the command line. They\nsend your Ability Owl login token to the hosts you name, so only set them for a\nhost you trust.\n\n### If you are not signed in\n\nThe server still starts, and every tool answers with \"Not logged in. Run\n`abilityowl login` in a terminal.\" Run it, and the next request works — there\nis no need to restart the client.\n\n## Development\n\n```bash\nnpm ci\nnpm test\nnpm run build\nnode packages/cli/dist/index.js --help\n```\n\nThat four-line sequence is the gate. It must pass on a fresh clone before any PR.\nSee `CLAUDE.md` for the repo layout and conventions.\n\n`packages/mcp`'s vendored WCAG 2.2 data (`abilityowl://wcag/criteria`) is checked for\ndrift against an optional upstream copy of `uxsoap-api`'s `wcag.json`, resolved from\n`WCAG_SOURCE` if set, otherwise a sibling `uxsoap-api` checkout. A plain `npm test` on\na standalone clone has neither, so that one drift check is skipped rather than\nfailed — everything else in the suite, including validation of the vendored data\nitself, still runs.\n","readmeFilename":"README.md"}