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

@vobase/cli

v0.38.0

Published

Standalone CLI for any vobase deployment — catalog-driven, tenant-agnostic.

Readme

@vobase/cli

Beta

The 0.7.x line is a beta release. See CHANGELOG.md for behavior changes.

vobase is the standalone, catalog-driven CLI for any vobase deployment.

The binary has zero static knowledge of verbs. On the first command it asks the tenant's /api/cli/verbs endpoint for the available verb set and caches the result; every subsequent command resolves against the cached catalog. This is what lets the same binary work across tenants whose module sets differ — there's no per-tenant rebuild.

Install

From npm (when published):

npm i -g @vobase/cli
# or, with bun
bun add -g @vobase/cli

From source (this repo):

cd packages/cli
bun run compile         # builds dist/vobase (single-file Mach-O / ELF binary)
ln -s "$(pwd)/dist/vobase" /usr/local/bin/vobase

Authentication

Every CLI invocation reads its tenant URL + API key from ~/.vobase/<config>.json. The default config name is config; pass --config=<name> (or set VOBASE_CONFIG=<name>) to switch.

# Browser device-grant flow — opens a /auth/cli-grant page on the tenant.
vobase auth login --url=https://acme.vobase.app

# Headless / scripted: pass the API key directly.
vobase auth login --url=https://acme.vobase.app --token=vbt_<key>

# Confirm.
vobase auth whoami
# → Principal: [email protected] (apikey)
#   Organization: org_acme
#   Role: admin
#   URL: https://acme.vobase.app

vobase auth logout       # removes ~/.vobase/<config>.json

The browser flow lands on /auth/cli-grant?code=… on the tenant and mints an API key bound to the signed-in user. The CLI polls /api/auth/cli-grant/poll until the code redeems, then writes the config with 0600 permissions.

Discovering verbs

vobase --help renders the catalog as verb groups; vobase <group> --help narrows to a single group. Once the catalog is cached, --help is offline.

vobase --help
vobase contacts --help
vobase --refresh             # force-refetch the catalog (e.g. after a deploy)

Running verbs

The starter verb set:

vobase contacts list
vobase contacts show --id=cnt_…
vobase contacts update --id=cnt_… --segments=qualified,vip

vobase messaging list --tab=active
vobase messaging show --id=cnv_…
vobase messaging reply --id=cnv_… --body="thanks for reaching out!"
vobase messaging close --id=cnv_… --reason="resolved"

vobase drive ls --scope=organization --path=/
vobase drive cat --scope=contact --scopeId=cnt_… --path=/NOTES.md
vobase drive write --scope=organization --path=/BUSINESS.md --content="..."

vobase agents list
vobase agents show --id=agt_…
vobase agents inspect --id=agt_…

vobase schedules list
vobase schedules enable  --id=sch_…
vobase schedules disable --id=sch_…
vobase schedules run     --id=sch_…   # force a single tick

vobase resources list

Add --json to any verb to get raw JSON output (overrides the catalog's formatHint):

vobase contacts list --json | jq '.[].displayName'

How the binary stays tenant-agnostic

The catalog endpoint publishes one entry per registered verb:

{
  "verbs": [
    {
      "name": "contacts list",
      "description": "List contacts in this organization.",
      "inputSchema": { "type": "object", "properties": { "limit": { "type": "integer" } } },
      "route": "/api/cli/contacts/list",
      "formatHint": "table:cols=id,displayName,email,phone,segments,createdAt"
    }
  ],
  "etag": "sha256-…"
}

The CLI:

  1. Walks argv against verb names, longest-prefix wins (vobase contacts list pending → tries contacts list pending, then contacts list, then contacts).
  2. Coerces --key=value flags to the JSON-Schema-declared types (number, boolean, comma-separated arrays).
  3. Dispatches to the verb's route with Authorization: Bearer <apiKey>.
  4. Renders the response through the formatHint (table / lines / json) or the format implied by --json.

If the server returns 412 (etag mismatch), the CLI swaps in the fresh catalog body inline — no extra round-trip, no manual --refresh needed when a tenant rolls out new verbs.

Custom verbs (tenant-side)

Any module can register a verb with defineCliVerb + ctx.cli.register(...). Once it boots, the CLI binary picks it up on the next catalog refresh.

// in your module
import { defineCliVerb } from '@vobase/core'
import { z } from 'zod'

const widgetsList = defineCliVerb({
  name: 'widgets list',
  description: 'List widgets.',
  input: z.object({ limit: z.number().int().positive().default(20) }),
  body: async ({ input, ctx }) => ({
    ok: true as const,
    data: await widgets.list(ctx.organizationId, { limit: input.limit }),
  }),
  formatHint: 'table:cols=id,name,createdAt',
})

// ModuleDef.init
ctx.cli.register(widgetsList)

Config file shape

~/.vobase/<config>.json:

{
  "url": "https://acme.vobase.app",
  "apiKey": "vbt_…",
  "organizationId": "org_acme",
  "principal": { "id": "usr_alice", "email": "[email protected]" }
}

The catalog cache lives next to it at ~/.vobase/<config>.cache.json.

Exit codes

  • 0 — success
  • 1 — verb returned an error or network/server failure
  • 2 — auth or usage error (no config, missing required flag, unknown subcommand)