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

abpilot

v1.0.11

Published

Turn workspace AI skills into a local, themeable web console.

Downloads

1,669

Readme

abpilot (Axiom)

Turn workspace AI skills into a local, themeable web console.

abpilot scans your workspace for skills (any directory with a SKILL.md plus an app.js) and mounts each one as an app in a local web console. Skills can be headless HTTP APIs for agents, or full React UIs. The console also ships with an optional AI chat panel that connects to a local opencode server.

  • Discover apps from .opencode/skill, .claude/skills, .agents/skills, skills/, and more (see Skill sources).
  • Dark/light theme, synced into every app through postMessage.
  • Per-app SurrealDB storage via app.db().
  • AI chat with sessions, model switching, attachments, image paste, and voice-to-text — powered by the opencode SDK.

Requirements

  • Node.js >= 18
  • Optional: surrealdb / @surrealdb/node for app.db() (installed as optional dependencies).
  • Optional: the opencode CLI, to use the AI chat panel.

Quick start

Run the console directly with no install:

npx abpilot ui --open

Or install it globally:

npm install -g abpilot
abpilot ui --open

By default the console listens on http://127.0.0.1:4097 and scans the current directory for skills.

Enable the AI chat panel

The AI panel talks to a local opencode server over its HTTP API. Start one on the default port, then open the console:

opencode serve --port 4096     # keep running in another terminal
abpilot ui --open              # the AI button appears when 4096 is reachable

CLI

abpilot <command> [options]

Commands:

| Command | Description | | --- | --- | | ui | Start the local web console (default port 4097). | | list | List discovered apps and skills without starting the server. | | login | Sign in with an email one-time code and store the token locally. | | get-token | Print the stored auth token. | | logout | Remove the stored auth token. | | app init | Create app.info for an app directory. | | app publish | Register a version from app.info, upload the artifact, and tag it. | | help | Show help. |

Options:

| Option | Description | | --- | --- | | -p, --port <n> | Port to listen on (default: 4097). | | --host <host> | Host to bind (default: 127.0.0.1); a host id for app init/app publish. | | -w, --workspace <dir> | Workspace root to scan (default: cwd). | | -o, --open | Open the browser on start. | | --db-dir <dir> | Base directory for SurrealDB files (default: <tmp>/abpilot). | | --reset-db | Delete the stored database before starting. | | --opencode-host <host> | opencode server host for the AI panel (default: 127.0.0.1). | | --opencode-port <port> | opencode server port for the AI panel (default: 4096). | | --no-global | Skip global skill directories. | | -e, --email <email> | Email for login (skips the prompt). | | --code <code> | One-time code for login (skips the prompt). | | --api-url <url> | Auth service base URL (default: production). | | --apikey <key> | app init/app publish: use an API key instead of the stored token (or ABPILOT_APIKEY). | | --dir <dir> | app init/app publish: app directory (default: cwd). | | --name <title> | app init: create the app with this title (needs --host). | | --id <app_id> | app init: use an existing app id. | | --version <v> | app init: version to write (default: 1.0.0). | | --mobile <dir> / --web <dir> | app init: mobile/web build dir. | | --zip <file> | app publish: upload a prebuilt zip (else zip the declared dirs). | | --notes <text> | app publish: version notes. | | --tag <tag> | app publish: point this tag at the version. | | --force | app init: overwrite an existing app.info. | | --json | get-token: print the full stored credential as JSON. | | --quiet | get-token: suppress expiry warnings. | | -v, --version | Print version. | | -h, --help | Print help. |

Login

abpilot login authenticates against the abpilot auth service (passwordless email OTP) and stores the resulting JWT locally:

abpilot login                      # prompts for email, then the code
abpilot login --email [email protected]

It calls POST /auth/request-code, prompts for the emailed code, then calls POST /auth/verify-code and writes the token to ~/.abpilot/credentials.json (mode 0600). Override the location with ABPILOT_HOME, and the service base URL with --api-url or ABPILOT_API_URL. The default is the production URL (https://5btchlheby6u46iaq7ttx62qdy0tzmut.lambda-url.us-east-1.on.aws/).

Use abpilot get-token to print the stored token (raw by default, for piping into curl/scripts; --json prints the whole credential). It warns on stderr when the token has expired — refresh with abpilot login.

TOKEN=$(abpilot get-token)
curl "$URL/auth/me" -H "authorization: Bearer $TOKEN"

abpilot logout deletes the stored credential. Sessions are stateless JWTs, so there is no server-side revocation — logging out only removes the local token.

The console (abpilot ui) mirrors this in the bottom-left of the sidebar: Sign in opens an email-OTP dialog (request code → enter code), and Sign out clears the credential. The console shares the same credential store, so CLI and UI stay in sync. These talk to the server routes under /api/auth (status, request-code, verify-code, logout); the token is held server-side and never sent to the browser.

Apps (app init / app publish)

Platform apps are published from a directory with an app.info file ({ id, version, mobile?:{dir}, web?:{dir} }); see skills/abpilot-app-skill/SKILL.md for the schema and rules. The app subcommands drive it, authenticated by the stored token or an --apikey:

# create the app on the platform and write ./app.info (id + version)
abpilot app init --host hst_... --name "My App" --mobile mobile/dist --web web/dist
# or point at an existing app id
abpilot app init --id app_... --version 1.0.0

# publish: register the version, zip the declared dirs (or --zip), upload, tag
abpilot app publish --tag stable
abpilot app publish --host hst_... --zip build/app.zip --apikey "$ABPILOT_APIKEY"

app publish resolves the host from the app id if --host is omitted. When no --zip is given it zips the declared mobile/web dirs; if there is nothing to zip it prints the presigned URL to upload yourself. An API key works for app operations; the /apikeys management routes still require a JWT.

Workspace package app

If the workspace package.json declares host: { id, tag }, abpilot ui fetches the public host-tag rollup (GET /hosts/{id}/tags/{tag}), finds the app whose name matches package.json.name, downloads the app's tagged artifact into <workspace>/.apps/<app_id>/, and serves it as a console app at /apps/<name>:

{ "name": "my-app", "host": { "id": "hst_...", "tag": "stable" } }
  • The artifact (the version .zip) is unpacked into .apps/<app_id>/ (gitignored). web.dir resolves inside it first, then falls back to the workspace (useful while developing locally). A version marker avoids re-downloading; a new tag version re-downloads.
  • web.dir is the frontend-only compiled build, served statically with an index.html SPA fallback. Apps have no server side.

The version endpoint (GET …/apps/{app_id}/versions/{version}) is public, so downloading needs no login. If the fetch/download fails, the console just starts without it.

Writing a skill

A skill is a directory with a SKILL.md and an entry file. The entry file default-exports a function that receives the app context:

export default function (app) {
  app.reg({
    title: 'My App',
    icon: '🧩',                 // emoji, or an image URL/path
    entrypoint: '/apps/my-app', // optional; defaults to /apps/<skill-id>
    description: 'Short subtitle',
    order: 100,                 // lower sorts first
  });

  app.get('/', (req, res) => res.type('html').send('<h1>Hello</h1>'));
}

CommonJS is also supported: module.exports = function (app) { ... }. The function may be async and may await app.db(). Accepted entry file names: app.js, app.mjs, app.cjs, app.ts, app.mts, app.cts, or app/index.js.

The app object

| Member | Description | | --- | --- | | app.reg(def) | Set/merge registration metadata (title, icon, entrypoint, description, order). | | app.use(...) | Express middleware / router. | | app.get/post/put/patch/delete/all(path, ...handlers) | Express route helpers on the app router. | | app.static(relDir, opts) | Serve a directory relative to the skill. | | app.json() / app.urlencoded() | Optional local body parsers (global parsing is already enabled, so req.body works). | | app.resolve(relPath) | Absolute path inside the skill directory. | | app.dir / app.id | Absolute skill directory / skill id. | | app.log(...) | Prefixed logger ([<id>] ...). | | app.express | The express module, for advanced use. | | app.db(options?) | Promise<Surreal> bound to this app (see Storage). |

Notes:

  • Every app gets its own router; routes are relative to its entrypoint.
  • Requests to the bare entrypoint redirect to a trailing slash, so relative URLs (fetch('api/tools')) resolve correctly from the UI.
  • Errors: call next(err) (wrap async handlers in try/catch). The server returns 500 { "error": "internal_error", "message": "..." }.

Two modes

Headless mode (HTTP API only). No UI; the app only exposes an HTTP API for agents and scripts. Set headless: true in app.reg({...}) and the console keeps it out of the sidebar and search (its routes stay mounted, so the API is still reachable). See skills/abpilot-web-skill/templates/headless.js.

UI mode (React). The app serves an HTML shell and a React UI, and exposes the same AI API. React and the UI stack are loaded in the browser from an import map (esm.sh), and the UI's JSX/TSX is bundled on demand with the esbuild that ships with abpilot, so no build step or local node_modules is required. Styling uses Tailwind CSS + Headless UI (@headlessui/react) + clsx — no custom CSS. See skills/abpilot-web-skill/templates/.

The skills/abpilot-web-skill/SKILL.md file is the full authoring guide — the app.js contract, both modes, theme sync, and the AI API convention.

AI-facing HTTP API

Design each app's API so an agent can discover and call tools without prior knowledge. Expose a manifest at GET <entrypoint>/api:

{
  "name": "my-app",
  "description": "What this app does",
  "version": "1.0.0",
  "protocol": "axiom-tools/1",
  "tools": [
    {
      "name": "echo",
      "method": "POST",
      "path": "/api/echo",
      "description": "Echo text back",
      "input": { "type": "object", "properties": { "text": { "type": "string" } }, "required": ["text"] },
      "output": { "type": "object", "properties": { "text": { "type": "string" } } }
    }
  ]
}

Use a consistent envelope: { "ok": true, "data": <result> } on success and { "ok": false, "error": { "code": "...", "message": "..." } } with a 4xx/5xx status on failure. Full spec: skills/abpilot-web-skill/reference/ai-api.md.

Storage: app.db()

await app.db() returns a per-app SurrealDB client backed by a SurrealKV file whose directory is a hash of the workspace directory + port. Data persists across restarts; pass --reset-db to clear it. It is optional — if the surrealdb packages are not installed, only calls to app.db() fail.

export default function (app) {
  let schema = null;
  const ensure = (db) => (schema ??= db.query('DEFINE TABLE IF NOT EXISTS note SCHEMALESS'));

  app.get('/api/notes', async (req, res, next) => {
    try {
      const db = await app.db();
      await ensure(db);
      const [rows] = await db.query('SELECT * FROM note ORDER BY created DESC');
      res.json({ ok: true, data: rows || [] });
    } catch (err) {
      next(err);
    }
  });
}

Gotchas:

  • Querying a table that has not been created throws "table does not exist". Create it once with DEFINE TABLE IF NOT EXISTS <name> SCHEMALESS.
  • db.query(sql, vars) returns an array of statement results; for a single SELECT the rows are the first statement result (const [rows] = ...).
  • Store ids you expose to clients as strings (uid = <string>rand::uuid()) rather than Surreal record ids.

Theme sync (UI mode)

The console posts { type: 'axiom:theme', theme: 'dark' | 'light' } into the app's iframe on load and whenever the theme changes:

window.addEventListener('message', (event) => {
  if (event.data && event.data.type === 'axiom:theme') {
    document.documentElement.setAttribute('data-theme', event.data.theme);
  }
});

// Handshake: ask the console to (re)send the theme once we are listening.
window.parent.postMessage({ type: 'axiom:theme:request' }, '*');

Define your CSS with :root[data-theme='dark'] / :root[data-theme='light'] variables, plus a prefers-color-scheme fallback for the first paint.

AI chat panel

When a local opencode server is reachable, an AI button appears in the toolbar. Clicking it opens a chat drawer on the right that connects to that server through the abpilot backend (which uses the @opencode-ai/sdk — the browser never talks to opencode directly).

Features:

  • The entry appears automatically only while the configured opencode host/port (default 127.0.0.1:4096) is reachable; it hides when the server is down.
  • Header shows the current conversation; click it to open the conversation list — select, create, or delete conversations.
  • Live streaming of assistant text, reasoning, and tool calls over SSE.
  • Model switching from the models exposed by the opencode server; the choice is persisted.
  • Multi-line composer: Enter to send, Shift+Enter for a newline; it grows up to 20 lines, then scrolls.
  • Attachments: pick images or any file, or paste an image directly.
  • Voice-to-text via the browser Web Speech API (button shown when supported).
  • Stop button to abort a running response.

The backend routes live under /api/ai (status, models, sessions, sessions/:id/messages, events, sessions/:id/abort); see lib/opencode.js.

Skill sources

Skill directories are discovered (recursively, up to a few levels) from these workspace roots:

.opencode/skill  .opencode/skills  .claude/skills  .agents/skills
.github/skills   .codex/skills     .cursor/skills  .gemini/skills
.windsurf/skills .ai/skills        skills/

and, unless --no-global is passed, from the global counterparts:

~/.config/opencode/skill  ~/.config/opencode/skills  ~/.claude/skills
~/.agents/skills          ~/.codex/skills            ~/.gemini/skills

The apps bundled under skills/ in this package (Welcome, Notes, Hosts, API Keys, Apps) are always available as built-ins. Hosts (hosts + members), API Keys, and Apps (apps/versions/tags) are management UIs for the platform and use your abpilot login token. The skills/abpilot-doc-skill skill is a documentation-only reference for calling the platform API directly — it has no app.js and mounts nothing.

Development

npm run dev        # Vite dev server for the console UI (proxies /api to :4097)
npm run build      # Build app/dist (the console UI)
npm run typecheck  # tsc --noEmit
npm start          # Start the console from source (node bin/abpilot.js ui)

Project layout

bin/abpilot.js     # CLI entry
lib/cli.js         # Commands, flags, help
lib/server.js      # Express server: static UI, /api/apps, mounts app routers
lib/skills.js      # Skill discovery
lib/loader.js      # Loads and initializes app.js modules
lib/app-context.js # The `app` object passed to skills
lib/db.js          # Per-app SurrealDB manager
lib/auth.js        # Auth service client + local credential storage
lib/opencode.js    # opencode SDK service for the AI chat panel
app/src/           # Console React UI (built to app/dist)
skills/            # Built-in example apps + the abpilot-web-skill guide

License

MIT © hailongz