npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@zoahhq/cli

v0.1.9

Published

Zoah CLI: import React components, publish via Zoah, swap local imports

Readme

@zoahhq/cli

The Zoah CLI. Sign in from your terminal, import first-party React components into Zoah, and optionally rewrite local imports.

Published to npm as @zoahhq/cli. The package installs the zoah command.

Install

npm install -g @zoahhq/cli

Verify the installation with zoah --version. The package also provides opacity as a temporary compatibility alias.

Quick start

zoah login          # sign in via your browser
zoah import         # scan ./src, import and publish your components
zoah import --swap  # also rewrite local imports to the published package

Commands

zoah login

Opens your browser to the Zoah sign-in flow. It saves the credential with chmod 600. Other commands reuse its token and base URL.

Credentials are layered (same precedence as git config local > global):

  • Default, global: writes ~/.zoah/credentials.json. One sign-in covers every Zoah project on this machine.
  • --local: writes <cwd>/.zoah/credentials.json. Use this for a project-specific account or instance. The CLI also writes <cwd>/.zoah/.gitignore.

Reads always check local first; falls back to global.

zoah login                                      # global default
zoah login --local                              # project-specific override
zoah login --base-url https://zoah.example.com
zoah login --organization acme                  # skip the organization list

After you sign in, the CLI asks which organization to work in. Your active organization in the web app is first and selected. Use the arrow keys to choose another, then press Enter. An account with one organization skips the question.

The CLI keeps this choice. Changing organizations in the web app does not change it. New projects from zoah import go into this organization, and so do directories that are imported but not swapped yet. A swapped directory stays on its project (see Where a directory stands).

The CLI uses ZOAH_BASE_URL when it is set. Otherwise, it uses https://zoah.com. OPACITY_BASE_URL remains a compatibility fallback.

zoah logout

By default, this removes the active credentials. It removes project-local credentials when they exist, and otherwise removes global credentials.

  • zoah logout: clear the active layer only.
  • zoah logout --global: remove ~/.zoah/credentials.json even when a project-local override is also present.

To clear both, run logout twice.

zoah status

Prints who is signed in, the CLI's organization, and this directory's project, package and state, plus what the next zoah import will do. It reads local files only.

zoah status

zoah whoami

Prints the active user, the source credentials file, the base URL, and the organization the CLI works in. Use this when project credentials hide the global token.

zoah whoami
# Signed in as [email protected] (global).
#   Source:   ~/.zoah/credentials.json
#   Base URL: https://zoah.com
#   Organization: Acme (acme)

zoah organization

zoah organization list           # your organizations; * marks the CLI's
zoah organization switch         # choose from the list with the arrow keys
zoah organization switch acme    # or name one by slug, name or id
zoah organization create "Acme"  # create one; switch to it to work there

switch changes the organization for the credential it came from, so a --local login keeps its own organization. The MCP server uses the same organization unless connect names another.

zoah mcp

Runs the Zoah MCP server over stdio so a coding agent (Claude Code, Codex, Cursor, or any MCP-capable harness) can read and edit a Zoah project from your terminal session. The agent gets the same verbs the designer uses: nodes, pages, components, slots, variants, variables, themes, breakpoints and events, plus branch and pull-request tools.

Set it up

1. Sign in once. The MCP server reuses the credential zoah login writes. It never asks the agent for one.

npm i -g @zoahhq/cli
zoah login

2. Add the server to your harness. The guided installer is the fast path:

zoah mcp init

It asks four things:

| Question | Options | Pick this when | | ------------------ | ----------------------------------------------------------- | ------------------------------------------------------------- | | Which harness | Claude Code, Cursor, VS Code, Codex, or print the snippet | Pick the harness that will use Zoah | | How to launch | npx -y @zoahhq/cli@latest, or the global zoah binary | npx for a config you check in, so teammates need no install | | What agents may do | full access, or --read-only | read-only serves the query verbs alone | | Default project | the repo's linked project from .zoah/config.json, or none | baking it in means connect needs no IDs |

It merges into an existing project config without disturbing other servers. Every answer is also a flag (--harness, --global-command, --read-only, --project, --print) for scripted setup.

For Codex, the installer runs codex mcp add. Codex writes the global server entry that its CLI and IDE extension share. The installer checks for an existing zoah server first. Interactive setup asks before replacement. Scripted setup leaves an existing entry unchanged. If Codex is unavailable or its server list is unsafe to read, the installer prints a complete global TOML entry instead. Zoah never edits Codex TOML directly. See the Codex MCP documentation.

The confirmation shows the resolved instance as one URL. The standard production instance is https://zoah.com.

To write the entry by hand instead:

{
  "mcpServers": {
    "zoah": {
      "command": "npx",
      "args": ["-y", "@zoahhq/cli@latest", "mcp"]
    }
  }
}

3. Point the agent at a project. In the agent session:

connect(project: "my-project")

connect signs in when the credential is missing or expired. It returns a URL for you to approve in a browser, then resolves the project by slug, name or id and opens the session. Give it branch to pick one. With no branch it creates a fresh agent/<timestamp> branch off main, so an agent's edits stay reviewable rather than landing on main.

4. Check it worked. status reports the connected project, the branch, and organization role. It also reports credential health. whoami reports the identity, role, and instance.

What the agent gets

The server exposes each Action API verb that can run in a headless project session without direct human confirmation. Confirmation actions stay in the Zoah app. The server replaces renderer-only and workspace-agent verbs with the session tools below.

| Group | Tools | | -------------------------- | ------------------------------------------------------------------------------------------------------------------------------------- | | Session | connect, status, pullEvents, disconnect, whoami, logout, describeProperties | | Organizations and projects | listOrganizations, createOrganization, listProjects, createProject, updateProject | | Branches | listBranches, createBranch | | Pull requests | createPullRequest, getPullRequest, updatePullRequest, commentOnPullRequest | | Canvas | one per applicable Action API verb for nodes, pages, components, slots, variants, variables, themes, breakpoints, events, and queries |

Two properties are worth knowing. Mutation tools return only after the sync gateway acks the write, so a returned success means persisted, not queued. And the server does not provide branch, project, or organization deletion. pullEvents returns structured rejection and collaborator action details. It also returns merge summaries. Sync and cache signals mean the agent should re-query after reconnect catch-up. For a read role, the server hides mutation tools and connects to an existing branch. It does not create an agent branch. Deleting one is irreversible from here, and it is easy for an agent to reach for while recovering from its own mistake. That stays in the web UI, where a person can see what they are about to destroy.

Working against local dev

The published bundle targets https://zoah.com. For a local instance, link the checkout instead of installing from npm, then sign in against it:

zoah login --base-url http://localhost:5070

connect accepts a baseUrl parameter, and it wins over the saved credential. It exists for the clean-workspace case: with no credential file yet, the built-in default is a guess, and on a multi-instance dev machine it is usually the wrong port. The choice is saved, so later calls agree - a session cannot silently straddle two instances, and status reports a mismatch between the open session and the credential on disk (baseUrlMismatch) rather than letting one develop unnoticed. Build and link steps are in DEVELOPMENT.md.

Design and details: docs/action-api-mcp.md.

zoah import [path]

Scans first-party React components and sends an import payload to Zoah. It publishes a package and writes project state under .zoah/. The default path is src.

zoah import                       # scans ./src
zoah import src/components        # scope to a subdirectory
zoah import --swap                # also rewrite local imports after publish
zoah import --project-name my-ui  # override the first project name
zoah import --new-project         # start over in a new project (before a swap)
zoah import --branch main         # target a specific branch
zoah import --debug               # also write .zoah/debug/payload.json
zoah import --print-issues warn   # print issue details at or above this level
zoah import --dry-run             # preview locally without an API call

The first import creates a project and saves its IDs to .zoah/config.json. Before it scans, zoah import says where the directory stands and where this import goes.

Where a directory stands

zoah status prints this without scanning or calling the API.

| State | What it means | What zoah import does | | -------- | ------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | New | No .zoah/config.json. | Creates a project in the CLI's organization. | | Imported | Linked to a project, but no source file uses its package yet. | Updates the linked project. If the CLI's organization is different, it creates a project there and relinks the directory. --new-project also starts a new project. | | Swapped | zoah swap rewrote component files to use the package. | Adds and updates components in the linked project, whichever organization the CLI is in. Swapped components are already in Zoah, so the scan skips them. --new-project is refused, because the code depends on the package. |

A swapped directory stays on its project because its code imports that project's package. It still takes new components: add one and run zoah import --swap.

Notable flags:

  • --swap: runs zoah swap after a successful import.
  • --project-name <name>: sets the name of a new project. Later imports use the project ID in .zoah/config.json.
  • --new-project: creates a new project in the CLI's organization and relinks the directory. Not possible once the directory is swapped.
  • --branch <name>: defaults to the project's saved branch (usually main).
  • --base-url <url>: overrides the URL saved at login.
  • --print-issues <level>: also prints full details for every issue at or above info | warn | error.
  • --dry-run: runs the local pipeline without publishing. It still writes .zoah/debug/issues.json.

The CLI skips components imported directly by an entry file (src/main.tsx, src/index.tsx, etc.). Translating them would blank the running app.

zoah preflight [path]

Runs the import pipeline locally without publishing. It writes .zoah/debug/issues.json but does not call the API or change project state.

  • preview what a real zoah import would do on a checkout you have not published,
  • audit which codes a codebase trips before deciding whether to upgrade the CLI,
  • regenerate the issues snapshot after a source change without re-publishing.
zoah preflight                       # scans ./src
zoah preflight src/components        # scope to a subdirectory
zoah preflight --print-issues warn   # also print issue details

The full code list, grouped by what the swap actually does (cleanly / visibly degrades / refuses), is documented in docs/supported-shapes.md in the repo.

zoah swap [path]

Installs the published Zoah package and wraps your local components. It reads .zoah/components.json and .zoah/manifest.json from zoah import.

zoah swap                          # install the package and rewrite imports
zoah swap --dry-run                # print diffs only
zoah swap --print-issues warn      # print issue details

The dry-run mode is the safe way to preview what swap will do before letting it touch your source tree.

React wrappers preserve root HTML props and use elementProps for supported nested form controls. See native HTML elements for the supported tags, examples, and limits.

Authenticated installs (.npmrc management)

Published packages carry the project's privacy. CLI imports are private by default, so the install needs credentials. When the repo's .npmrc (and your ~/.npmrc) has no token for the Zoah registry host, swap mints a registry API key for the project's organization and manages the file for you:

  • A key reads only its own organization's packages. When .npmrc already has a key, swap asks the registry whether that key can read this package. If the registry refuses it (401 or 403), for example because the key came from a project in another organization, swap replaces it with a key for this project and says so. .npmrc holds one key per registry host, so a repo that installs packages from two organizations needs a key that can read both.

  • The key is requested through the authenticated import API, requires write access in the project's organization, and is reused on repeat runs (one key per project + user, not one per invocation). It appears in Project Settings → Package as CLI import · <project> and is revocable there.

  • The token line (//<host>/:_authToken=…) is written to <repo>/.npmrc with owner-only permissions (0600), and .npmrc is added to .gitignore.

  • If .npmrc is already tracked by git, .gitignore cannot protect it. swap prints a loud warning telling you to git rm --cached .npmrc before your next commit.

  • When signed out, the install continues without authentication. Public packages still work. A private package install returns the registry's 401.

Reading the import output

After each zoah import run, the CLI prints a status table for each component:

  Component             Import   Swap
  ────────────────────  ──────   ────
  Avatar                █        █
  Button                █        █         1 error
  Card                  █ ░      █         2 warnings, 1 info
  ToastProvider         █        █

  File-level:
  src/utils.ts          █                  1 warning
  src/App.tsx           ░                  1 info

  1 error, 2 warnings, 1 info. See .zoah/debug/issues.json

  View in Zoah:  https://zoah.com/projects/<id>/design

Each cell is two glyphs: the severity slot on the left summarises the worst outcome for that (row, phase) pair, and the info-note slot on the right lights up when any info-level notes accrued.

| Cell | Meaning | | ------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | █ green | Phase ran cleanly (component imported successfully, no issues). | | █ yellow | Phase ran with at least one warning. | | █ red | Phase ran with at least one error. | | ░ dim | On a component row, phase didn't reach this row (e.g. swap blocked by an import error). On a File-level row, phase ran with only info-level notes (e.g. an entry file that was intentionally skipped). | | blank | On a File-level row, the phase did not touch this file. Sparse rows leave unused phases empty. | | trailing ░ | The cell has at least one info-level note beside a colored severity block. See the JSON for details. |

The trailing summary line counts errors, warnings, and info notes (zero-counts are omitted, so a clean run with only info notes still shows e.g. 3 info). Full per-issue detail is in the JSON snapshot.

.zoah/debug/issues.json

Every run writes all issues to .zoah/debug/issues.json, grouped by phase and severity. Shape:

{
  "schemaVersion": 1,
  "generatedAt": "2026-05-04T18:00:00.000Z",
  "commitSha": "abc1234+dirty",
  "summary": { "total": 12 },
  "phases": {
    "import":  { "total": 11, "issues": { "error": [...], "warn": [...], "info": [...] } },
    "package": { "total":  0, "issues": { "error": [],    "warn": [],    "info": []    } },
    "swap":    { "total":  1, "issues": { "error": [],    "warn": [...], "info": []    } }
  }
}

Each issue carries a stable code (e.g. DYNAMIC_CLASSNAME_UNRESOLVED, CODEMOD_FILE_REFUSED), a target describing what it applies to (node / component / file / project), an optional location with a 3-line source snippet, and a commitSha stamp. The full code list with severity and phase mappings, grouped by outcome, is documented in docs/supported-shapes.md in the repo.

When the import fails (e.g. the server returns 400 because no components were extractable), the CLI still writes this file and prints the table so you can see exactly which components couldn't be imported and why. Pass --print-issues warn (or info/error) to also print snippet + caret detail inline:

  warn  Card · import · DYNAMIC_CLASSNAME_UNRESOLVED  src/Card.tsx:42:18
    Dynamic className expression couldn't be resolved to styles
       41 |   const cls = `card-${variant}`;
       42 |   return <div className={cls}>...</div>;
                            ^^^
       43 | }

What the CLI writes

Global (per-machine, never inside a project):

  • ~/.zoah/credentials.json: auth token and base URL (chmod 600). Written by zoah login unless a project overrides it.

Project-local (paths relative to the project where you run zoah). The CLI manages .zoah/.gitignore so secrets and debug output stay out of git.

Commit these files. They bind the checkout to the Zoah project:

  • .zoah/config.json: organization and project binding, written on the first import.
  • .zoah/components.json: component IDs, package exports, and slot anchors.
  • .zoah/manifest.json: published package name, version, and registry URL.

Ignored automatically through .zoah/.gitignore:

  • .zoah/credentials.json: local credential from login --local.
  • .zoah/debug/issues.json: issues from each run, grouped by phase and severity.
  • .zoah/debug/payload.json: raw import payload from --debug.

The CLI reads old .opacity state when the matching .zoah file does not exist. New writes use .zoah.

Environment variables

  • ZOAH_BASE_URL: default base URL when no flag or credential sets one.
  • ZOAH_BRANCH: default branch for import when --branch is not passed.

The old OPACITY_BASE_URL and OPACITY_BRANCH variables remain compatibility fallbacks.