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

@blahai/bode

v0.1.0

Published

Bode collaborative sessions and device executor

Readme

@repo/bode-cli

bode, the interactive command-line client.

Install on a computer with Node.js 22 or newer:

npm install -g https://bode.blah.dev/downloads/blahai-bode-0.1.0.tgz
bode login
bode device register "My computer"
bode device run

bode login prints a verification URL and code to approve in your browser. Keep bode device run running under the same user account after registration; a user-level service manager can restart it after reboot. Once the device is online, open the session's Manage devices dialog, choose Attach a device, and set an absolute workspace path and capabilities. Registration alone does not grant a session access.

The public @blahai/bode registry release is pending npm account authentication. The npm release is assembled and verified with ops/bode/publish-cli.sh; the script's --publish option publishes the tested tarball as public @blahai/bode once signed in.

pnpm --filter @repo/bode-cli bode login               # device grant: approve the code on any device
pnpm --filter @repo/bode-cli bode new "Launch plan"   # start a session and open it
pnpm --filter @repo/bode-cli bode sessions            # list sessions
pnpm --filter @repo/bode-cli bode open <session-id>   # continue a session started anywhere
pnpm --filter @repo/bode-cli bode logout

In a session, type a message and press Enter. /cancel stops the agent's current turn and /quit leaves. Leaving ends only this client; the session and any turn in progress continue on the server, and the web app or another terminal can pick them up.

A message is kept as a draft until bode accepts it, in $XDG_STATE_HOME/bode/drafts/ (or ~/.local/state/bode/drafts/), one private file per session. If bode cannot be reached, the message stays there with the key of its first attempt: /send tries again, including after you reopen the session, and bode accepts it once. /discard drops it. A draft started before the session's configuration changed is held until you /send it a second time. Nothing runs offline.

Pausing, archiving, and the trash

bode pause <session-id>          # hold new runs; messages are still accepted and wait
bode resume <session-id>         # let a paused or archived session run again
bode archive <session-id>        # take it off the active list; people's messages still run
bode trash <session-id> [--yes]  # move it to the trash, after asking; nothing is deleted
bode restore <session-id>        # bring it back from the trash as it was
bode delete <session-id> [--yes] # delete a session in the trash permanently, after asking

bode delete works only on a session already in the trash, so deletion is always a second decision. It is hidden at once; its messages, runs, events, files, and session memory are purged, and it cannot be restored, even from a database backup. Memory people own is not deleted.

Each change applies at the session's current revision, so two clients cannot silently undo each other. Pausing and archiving are for anyone who may change the session's settings; the trash and restoring from it are for its administrators. A session in the trash is left out of bode sessions, refuses new messages, and holds its queued runs until it is restored. Work already running continues to its next boundary.

Sign-in

bode login uses the OAuth device authorization grant (RFC 8628) against blah.dev as the public client bode-cli, so it works over SSH and on machines without a browser. The token is saved only if the bode scope was granted, in $XDG_CONFIG_HOME/bode/credentials.json (or ~/.config/bode/), written atomically with mode 0600 in a 0700 directory. It is refreshed shortly before it expires; when refresh is refused, the CLI asks you to sign in again.

| Variable | Default | |---|---| | BODE_API_URL | https://bode.blah.dev | | BODE_ISSUER | https://blah.dev | | BODE_CLIENT_ID | bode-cli |

Plain HTTP is accepted only for loopback addresses, for development.

Sharing and organizations

bode participants <session-id>                     # who can open a session, and each role
bode share <session-id> [email protected] --role viewer   # viewer, contributor (default), or admin
bode unshare <session-id> [email protected]          # takes away the role you gave; org roles stay
bode orgs
bode org create "Acme"                             # you become its owner
bode org add <organization-id> [email protected] --role admin   # member (default), admin, or owner
bode org remove <organization-id> [email protected]
bode new "Team plan" --org <organization-id>       # a session the organization owns

People are named by the email of their blah.dev account. Sharing never shares your devices, inference connections, or builds; contributors can use the devices attached to the session.

Agents

bode agents                                        # agents you can bring into sessions
bode agent create "Reviewer" [--org <organization-id>]
bode agent add <session-id> <agent-id> [--handle reviewer]
bode agent stop <session-id> @reviewer             # stops that agent's unfinished runs only
bode agent remove <session-id> @reviewer           # stops its runs and takes it out

A message goes to every agent it mentions by @handle, one run each, or to the session's designated @bode when it mentions none. An agent's own message wakes no one; agents hand work to each other only through the delegate tool, which starts a child run.

Work

bode tasks <session-id>                            # tasks, unfinished first, with who holds each
bode budget <session-id>                           # each allowance, with its limits and use
bode budget set <session-id> --max-attempts 5 --max-parallel 2
bode budget set <session-id> --agent @reviewer --max-tokens 200000
bode run detach <session-id> <run-id>              # a delegated run keeps going when its parent stops

bode budget set replaces every limit of its scope: a limit not named is unset, which means no limit. Agents create, claim, and complete tasks with their session tools; a claim is a lease that only its holder can renew or complete.

Memory

bode memory <session-id>                                   # what is attached, for whom, and recent disclosures
HAM_TOKEN=... bode memory connect HAM https://ham.example/mcp --token-env HAM_TOKEN
bode memory attach <session-id> notes --backend <build-id>:postgres --agents @reviewer
bode memory attach <session-id> mine --backend <build-id>:postgres --scope user --read-only
bode memory recall <session-id> @reviewer notes,mine       # retrieved before each of its answers
bode memory search <session-id> notes launch owner
bode memory write <session-id> notes decision "Alice owns the launch" --title Owner
bode memory retract <session-id> notes <record-id> --revision 2 --reason "Owner changed"

A connection's token is read from the environment variable you name, sealed on the server, and never shown again. bode decides whose memory a scope is: --scope user always means your own, and attaching it is recorded as your disclosure. Searches, writes, and retractions are performed by the worker through the attachment's backend and logged; the command waits for the result. Agents use the same memory through their memory tools and, with bode memory recall, before each answer.

Branches

bode branches <session-id>                                 # each branch and where it continues from
bode branch <session-id> Other approach                    # continue after the latest event
bode branch <session-id> Deeper --of <branch-id> --from 12 # continue a branch after event 12
bode promote <session-id> <branch-id> The other approach    # make a branch a session of its own

Promoting gives a branch its own session, owned by you: its conversation becomes that session's history, with the configuration and tasks that went with it. Every binding is rechecked for you — agents you may not use, devices that are not yours, and memory, which is always attached again by the person who reaches it — and each is reported rather than silently dropped or silently granted. Nothing runs as a result.

A branch continues the conversation under a name: its history is the line it continues, up to the point it branched, and then its own messages. Nothing is copied — no files are restored, no finished tool call is repeated, and runs still going stay where they are; the branch records how many were left behind. Each agent works a branch in its own lane, so a branch and its source line proceed independently.

Export

bode export <session-id> ./launch-export                 # the whole session
bode export <session-id> ./branch-export --branch <id>   # one branch's line
bode import ./launch-export --title "Their launch plan"   # read one back as a session of yours

An export is a directory of bode.export.v1: manifest.json first, then the session, its branches, its conversation, its history as one event per line, its tasks, and its configuration, which names builds by package and digest. Artifacts are named by digest; their bytes are not in the export. No credential, sealed secret, or device credential is written, and nothing in an export executes.

Importing reconstructs that as a new session of your own: the messages become history and start no work, the tasks arrive unclaimed, and the configuration binds only to builds you may use — anything else is reported and left unbound. A long conversation is cut to the newest messages it can carry, and what was left behind is listed rather than dropped quietly.

Triggers

bode triggers <session-id>                                 # schedules and webhooks, and what each last did
bode trigger schedule <session-id> Morning summary --cron "0 9 * * 1-5" \
  --timezone Europe/Stockholm --agent @reviewer --message "Summarise what changed since yesterday."
DEPLOY_SECRET=... bode trigger webhook <session-id> Deploys \
  --secret-env DEPLOY_SECRET --message "A deployment finished: {{payload}}"
bode trigger disable <session-id> <trigger-id>             # its history stays

A trigger writes its message as the person who added it, so it can never do more than they can. A schedule follows an IANA timezone: a local time a daylight-saving change skips occurs at the next valid instant, and one it repeats occurs once. While the session is paused, archived, or in the trash, occurrences are held with the reason, visible in the session and in bode triggers. A webhook's secret is read from the environment variable you name, sealed on the server, and never shown again; each delivery is signed sha256=<HMAC-SHA256 of '<timestamp>.<body>'> in x-bode-signature, with x-bode-timestamp within five minutes and your own x-bode-delivery, which a redelivery repeats. A delivery's body is data in the message: it never grants authority.

Devices

bode device register [name]      # this computer's credential goes in ~/.config/bode/device.json (0600)
bode device run                  # the executor; sessions can use the device only while it runs
bode devices                     # your devices and whether each is online
bode attach <session-id> --root ~/project   # shows the grant and attaches after a yes
bode detach <session-id> <name>
bode device rotate               # replaces the credential; the old one stops working

bode attach takes --grant (comma-separated capabilities), --name, --default or --no-default, and --yes. The executor keeps its journal and processes under $XDG_STATE_HOME/bode/devices/<device-id>/.

Plugins

bode plugin new my-compactor --name @you/my-compactor   # --kind compactor (default), tool, preset, or memory
bode plugin test [dir]           # run fixtures against the bundle in a plugin host
bode plugin build [dir]          # write dist/bode-bundle.json and print its digest
bode plugin attach [dir] --session <session-id> --bind my-compactor
bode plugin publish <build-id>   # shows the build and publishes only after a yes
  • new writes a package that uses the monorepo's TypeScript, ESLint, Vitest, and Biome configuration and links the plugin SDK by path, so it works inside or outside the repository. Run pnpm install in it. The @repo namespace is reserved. Where bode is only installed, as in the device image, it writes just the package, its source, and its fixtures, linked to the installed SDK: bode plugin test and build resolve what the package has not installed from the CLI itself, so there is nothing to install.

  • test bundles the package and loads the bundle in a plugin process with no environment, so it tests what an upload would run. Each fixtures/*.json file is one case:

    { "kind": "compaction", "plugin": "my-compactor",
      "request": { "turns": [{ "role": "user", "text": "Hi" }, { "role": "user", "text": "Now" }],
                   "recentTail": 1, "targetTokens": 1000, "fixedTokens": 0, "state": null },
      "inference": ["answers the compactor's inference requests receive, in order"],
      "expect": { "status": "succeeded", "output": { "replacedTurns": 1 } } }

    A tool fixture gives input and optionally workspace, a directory beside the fixtures that is copied to serve as the workspace; the tool is held to the capability it declares. A memory fixture gives a memory request (and optionally scopeKey and provenance), with records and mcp: what bode's record storage and the MCP connection answer, in order. A memory result the backend classifies as failed fails with its error code. An expected output names only the fields it checks, and a failure may name its class or tool or memory error code.

  • attach uploads the bundle as a private build that only you can select. The worker verifies it by loading it and comparing its exports with its manifest. With --session and --bind, attach waits for verification and binds the named plugins in one configuration change: a compactor, tools alongside the session's others, or a preset. A memory backend is attached with bode memory attach --backend <build-id>:<name> instead. A changed package is a new build, so attach again after each change.

  • attach --as-device uploads with this computer's device credential, as an agent working on the device does. The build is a private build of the device's owner that names the device; a device can never publish or list builds. It binds nothing: the agent binds the build with its configure_session tool, and a configuration that fails to activate leaves the session's previous revision in place.

  • attach --device keeps the code on this computer: bode records the package's digest and manifest, and only this device runs it, while bode device run runs. Only compactors run this way. --attachment picks which of this device's attachments runs it when there are several. A device-only compactor never falls back to another: while the device is offline, the run waits, blocked.

  • publish is a separate decision. Attaching, binding, and verification never publish.

Behaviour

Session semantics live in @repo/bode-client: the CLI follows a snapshot plus the live event stream, reconnects from its last cursor, and retries submissions with the same idempotency key, so a flaky connection never duplicates a turn. Partial output from an interrupted or failed turn is labelled as incomplete.

Packaging

The PRD publishes the CLI as @blahai/bode. That needs a bundled build of the workspace packages it uses and is part of opening bode; until then it runs from this repository.