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

tono

v0.3.6

Published

Self-hosted orchestrator for CLI agent tools (Claude Code, Codex CLI, OpenCode) triggered by GitHub issue labels.

Readme

tono

Self-hosted orchestrator for CLI agent tools (Claude Code, Codex CLI, OpenCode) triggered by GitHub issue labels.

Etymology: tono is the middle of au·tono·mous — fitting for a tool whose whole job is letting agents run unattended.

Heads up: this project was entirely vibe-coded by AI. Every line of code, every commit, every README revision (including this one). Read accordingly — there are no human-reviewed parts. Use at your own risk.


What it does

You add a label to a GitHub issue or PR. Tono notices, spins up the matching CLI agent (Claude Code, Codex CLI, or OpenCode) inside a fresh git worktree on your machine, points it at the issue or PR, and lets it open the PR / post the review when it's done. The browser UI streams the live terminal so you can watch the agent work or jump in.

It's built to run quietly in the background on a machine of your choice — a Mac Mini under your desk, an old laptop, whatever — and pick up work as it comes.

GitHub issue (label: tono-claude)              GitHub PR (label: tono-claude-review)
       │                                              │
       ▼                                              ▼
  gh issue list                                  gh pr list
       │                                              │
       ▼                                              ▼
  worktree off baseBranch                  worktree at PR head (detached)
       │                                              │
       ▼                                              ▼
  PTY: claude <prompt>                       PTY: claude <prompt>
       │                                              │
       ▼                                              ▼
  gh pr create                              gh pr review --comment
       │
       ▼
  gh pr view (poll for merge)

The crucial design choice: tono does not implement an agent. It orchestrates external CLI agents — Claude Code, Codex CLI, OpenCode — by spawning them in a real PTY so their full TUI output is preserved.

Two kinds of work, one orchestrator

| Kind | Trigger | Worktree | Agent's job | Output | |---|---|---|---|---| | implement | label on issue | branch off baseBranch | implement the issue, push, run gh pr create | a new PR | | review | label on PR | detached HEAD at PR's head | read the diff, run gh pr review --comment (or --approve / --request-changes) | a review on the PR |

Each agent has its own queue and its own concurrency cap per kind, so a slow implement doesn't starve fast reviews and vice versa.

Trigger labels (convention, not config)

Labels are fixed by convention. There are six total — apply whichever you want and tono picks the right agent + kind:

| Agent | Implement issues | Review PRs | |---|---|---| | claude-code | tono-claude | tono-claude-review | | codex | tono-codex | tono-codex-review | | opencode | tono-opencode | tono-opencode-review |

Run tono config labels to print the table for your current config.

Requirements

  • macOS. Linux support is plausible but untested; the launchd integration is macOS-only.
  • Node 20+.
  • gh installed and authenticated (gh auth login). Tono shells out to gh for issue polling, PR polling, and PR-merge tracking.
  • The agent CLI(s) you want to use, on $PATH. At least one of:

Install

Tono is on npm. Install it globally with pnpm:

pnpm add -g tono
pnpm approve-builds -g    # one-time: allow better-sqlite3 and node-pty to fetch prebuilds

Verify it landed on your $PATH:

tono --version
which tono                # → ~/.local/share/pnpm/tono (or your pnpm bin path)

To upgrade later:

tono upgrade              # one-shot: install the latest version + restart any loaded daemons

tono upgrade auto-detects the package manager that installed the binary (pnpm / npm / yarn / bun by inspecting the install path), runs the equivalent of <pm> add -g tono@latest, and restarts whichever LaunchAgents are currently loaded (com.tono.gateway, com.tono.worker). Pass --check to see current vs latest without installing, --no-restart to skip the daemon restart, or --pm <name> to force a specific package manager.

You can still upgrade by hand if you prefer:

pnpm add -g tono@latest
tono gateway restart      # pick up the new binary in the background daemon

If you have remote workers, run tono upgrade on each worker machine too (or upgrade by hand and tono worker restart).

DB migrations (gateway-side) run automatically on the next tono start / tono gateway restart. Tono backs up ~/.tono/tono.db to ~/.tono/tono.db.bak.v<N> before any version-stepped migration runs, so an upgrade is safe to roll back: stop the gateway, copy the backup over the live db, downgrade tono, and start.

Workers and gateways must run the same minor version of tono (e.g. all on 0.3.x). Patch upgrades within a minor are guaranteed to interop. If a worker's minor differs from the gateway's, the gateway logs a warning on connect and may fail to dispatch tasks if the protocol shape has drifted.

To uninstall:

tono gateway uninstall    # remove the LaunchAgent first
pnpm remove -g tono
rm -rf ~/.tono            # optional — drops config, db, worktrees

About native modules: tono pulls in better-sqlite3 and node-pty, which need to compile or fetch a prebuilt binary. Both ship prebuilds for macOS arm64/x64. pnpm disables install scripts by default for safety, hence the extra pnpm approve-builds -g step. If you prefer npm or yarn, the install commands work too — those run install scripts by default and skip the approve-builds step.

Get started

1. Configure tono

tono configure

The wizard walks you through:

  • Bind host / port for the dashboard (defaults: 0.0.0.0:7040)
  • Poll interval
  • Workspaces root (default: ~/.tono/workspaces)
  • Which agents to enable (claude-code, codex, opencode) — for each:
    • Command name
    • Per-kind concurrency caps (implement / review)
  • Repos to watch — for each:
    • GitHub slug (owner/repo)
    • Path to your existing local clone, or leave blank to let tono bare-clone via gh into ~/.tono/workspaces/.bare/
    • Base branch
    • Which of your enabled agents are enrolled on this repo

Re-run tono configure later, edit ~/.tono/config.json directly, or use the Config page in the web UI.

2. Run the gateway

tono gateway start    # installs + loads a macOS LaunchAgent
tono open             # opens the dashboard in your browser

The gateway runs as a background daemon (com.tono.gateway), survives reboots, and auto-restarts on crash. Logs go to ~/.tono/logs/daemon.{out,err}.log.

3. Trigger your first agent

Two ways:

  • From GitHub. Apply one of the convention labels (tono-claude, tono-codex-review, etc.) to an issue or PR in a watched repo. Within pollIntervalSeconds (default 60s), tono queues a task and dispatches the matching agent.
  • From the dashboard. Click + Start session, pick a repo, pick an agent, type the issue number. Tono fetches the issue body via gh and queues an implement task immediately. (Manual review-task trigger isn't wired through the UI yet — apply the review label on the PR.)

Click into the task to watch the live terminal.

Reaching the dashboard

  • From the gateway machine: tono open (or http://localhost:7040).
  • From another device on your LAN: http://<host-ip>:7040. Find your host IP with ipconfig getifaddr en0 on macOS.
  • From anywhere via Tailscale: install Tailscale on the gateway machine and any device you want to use, then visit http://<gateway-tailnet-ip>:7040.

There is no auth in v1. Either bind to LAN-only or use Tailscale ACLs.

CLI reference

| Command | Purpose | |---|---| | tono init | Write default config + schema + database to ~/.tono/. | | tono configure | Interactive setup wizard. Edits or creates the config. | | tono start | Run the orchestrator in the foreground (dev mode). | | tono gateway start | Install + load the macOS LaunchAgent so the orchestrator runs in the background and at login. | | tono gateway stop | Unload the LaunchAgent (config kept). | | tono gateway restart | Reload after upgrading or changing the plist. | | tono gateway status | Show LaunchAgent state and HTTP health. | | tono gateway uninstall | Unload and remove the LaunchAgent. | | tono gateway logs [out\|err] | Tail the gateway log files. | | tono open | Open the web UI in your default browser. | | tono config validate | Validate ~/.tono/config.json against the schema. | | tono config labels | Print the GitHub labels each configured agent listens for. | | tono config path | Print the config file path. |

Configuration

Lives at ~/.tono/config.json, validated against ~/.tono/config.schema.json. Most fields are reachable from the Config page in the web UI; this is the underlying shape:

{
  "$schema": "./config.schema.json",
  "server":      { "host": "0.0.0.0", "port": 7040 },
  "github":      { "pollIntervalSeconds": 60 },
  "workspaces":  { "root": "~/.tono/workspaces" },
  "repos": [
    {
      "slug": "owner/repo",
      "path": "/Users/me/code/repo",   // optional; leave out to bare-clone via gh
      "baseBranch": "main",
      "agents": ["claude-code", "codex"] // optional; omit to enroll every declared agent
    }
  ],
  "agents": {
    "claude-code": {
      "command": "claude",
      "args": ["--dangerously-skip-permissions"],
      "concurrency": { "implement": 2, "review": 4 },
      "promptTemplates": {
        "implement": "...",   // {issueNumber} {issueTitle} {issueBody} {repoSlug} {branch} {baseBranch}
        "review":    "..."    // adds {prUrl} for review tasks
      }
    },
    "codex":    { /* same shape; command "codex", args [] */ },
    "opencode": { /* same shape; command "opencode", args ["run", "{prompt}"] */ }
  }
}

Notes:

  • Labels are convention. There is no triggerLabel field. Each enrolled agent listens for tono-<short> (implement) and tono-<short>-review (review), where <short> is claude for claude-code and the agent name otherwise.
  • The {prompt} placeholder in args lets agents that take prompts as flags (like opencode run "<prompt>") coexist with agents that take prompts as final positionals (like claude and codex). If {prompt} is absent from args, tono appends the rendered prompt as the last arg.
  • Per-(agent, kind) concurrency. A concurrency: { implement: 2, review: 4 } block means up to 2 implement tasks and up to 4 review tasks of that agent run simultaneously. Set either to 0 to disable that kind for the agent.
  • Live config reloads (repo add/remove, prompt template edits, concurrency changes) take effect on the poller's next tick. Server host/port changes require tono gateway restart.

Web UI

  • Dashboard. Tasks grouped by phase: Running → Awaiting review (PR open) → Queued → Recent. Plus any live shells. Buttons: + Start session (manual trigger) and + New terminal (free shell on the host).
  • Session view. Full xterm.js terminal connected to the live PTY over WebSocket. Resume-safe (close the tab, come back later). Buttons: Mark done (free the slot without killing the agent), Kill session, Cleanup worktree, Resume / Retry (uses claude --resume <id> for Claude Code; codex / opencode start fresh on retry).
  • Config. Per-section forms (Server, GitHub, Workspaces, Repos, Agents). Add new agents from the Add agent card at the bottom. Each section has its own Save button. A "Show raw JSON" toggle reveals the unstructured editor.

Task lifecycle

implement task:
  queued ──► running ──► (agent runs gh pr create)
                    │
                    ├──► pr_open ──► merged   (PR-watcher polls; auto-cleans worktree)
                    │             ├─► pr_closed
                    │
                    └──► completed (no PR; Mark done or session exited 0)
  failed       ← spawn / non-zero exit before any PR
  cleaned      ← worktree manually removed

review task:
  queued ──► running ──► completed (agent posts review and exits)
  failed       ← spawn / non-zero exit
  cleaned      ← worktree manually removed (also drops refs/tono/pr-N)

The PR watcher polls every pollIntervalSeconds and uses gh pr list --head <branch> to discover PRs even when our streaming detection misses the URL. Once a PR is merged, tono runs git worktree remove automatically. Review tasks have nothing to merge, so the watcher ignores them.

Files on disk

~/.tono/
├── config.json           # JSON-Schema-validated config
├── config.schema.json
├── tono.db               # SQLite: tasks (with kind), sessions, issues_seen
├── logs/
│   ├── daemon.out.log    # gateway stdout
│   ├── daemon.err.log
│   ├── task-N.log        # raw PTY bytes per task (4MB ring + tee to disk)
│   └── shell-XXXX.log    # free shells from "+ New terminal"
└── workspaces/
    ├── .bare/<owner>__<repo>.git           # only if you didn't supply a `path`
    ├── <owner>__<repo>__issue-N/           # implement worktree
    └── <owner>__<repo>__pr-review-N/       # review worktree (detached HEAD)

Stack

| Layer | Pick | |---|---| | HTTP | Hono on the Node adapter | | WebSocket | ws | | PTY | node-pty | | DB | better-sqlite3 | | Schema validation | ajv | | Frontend | React 19 + Vite 6 + Tailwind v4 + xterm.js (WebGL renderer) |

A single Node process runs the HTTP server, GitHub poller (issues + PRs), task scheduler, PR-merge watcher, and owns every PTY.

Roadmap

What works today:

  • Implement flow: GitHub issue → labeled trigger → agent runs in a worktree → PR opened → merge tracked → worktree cleaned.
  • Review flow: GitHub PR → labeled trigger → agent runs against the PR's head in a detached worktree → review posted via gh pr review.
  • Three agent types: Claude Code, Codex CLI, OpenCode.
  • Per-(agent, kind) queues and concurrency, so reviews never starve implements (and vice versa).
  • Manual implement triggering from the UI (no need to label first).
  • claude --resume for Claude Code retry; codex / opencode retry runs fresh.
  • Live PTY streaming with WebGL-rendered xterm.js, scrollback ring buffer, reconnect-safe.
  • Free-form + New terminal shells (browser-based SSH-lite to the gateway machine).
  • macOS launchd background gateway with tono gateway lifecycle commands.
  • Live config edits via the web UI without restarting.

What's next:

  • Webhook triggers to drop polling latency from ~60s to sub-second (Tailscale Funnel or smee.io).
  • Manual review-task trigger in the UI (today: label the PR).
  • Cost / token tracking.
  • Inline review comments posted by tono itself rather than asking the agent to call gh api.

Distributed workers

Tono can be split across multiple machines: one gateway (always-on box — e.g. your Mac mini) plus any number of workers (your MacBook, a Windows laptop, …) that connect outbound over a tailnet and execute agent runs on their own filesystem. Each connected worker adds capacity for the (agent, kind) partitions it advertises.

Topology. The gateway owns the SQLite state, GitHub poller, PR watcher, scheduler, browser UI, and config. Workers own a local node-pty and a local .bare/ clone cache. Tasks flow:

poller (gateway) → scheduler picks an eligible worker
                 → task.assign over WS → worker creates worktree, spawns agent
                 → PTY data streams back to gateway → browser UI

tono start continues to work unchanged on a single box: it launches an embedded local worker in the same process that connects to its own gateway over 127.0.0.1. The embedded worker is fungible with remote ones — the scheduler just sees more capacity once a remote worker connects.

Prerequisites (per worker machine):

  • Tailscale with the worker on the same tailnet as the gateway. There is no app-level auth — reachability over the tailnet IS the trust boundary. The gateway should bind on its tailnet IP (or 0.0.0.0 if the LAN is also trusted).
  • gh CLI, authenticated (gh auth login). The worker bare-clones repos itself; it does not pull git data through the gateway.
  • The agent CLIs you want this worker to run (claude, codex, opencode). The worker auto-detects which are on PATH and advertises those as its capabilities.

Run a remote worker (foreground):

tono worker run --gateway <gateway-host>:7040
# advertise specific agent commands and concurrency:
tono worker run \
  --gateway my-mac-mini:7040 \
  --agent claude-code=claude \
  --concurrency claude-code=3/2

Run a remote worker as a background daemon (macOS):

# first time: install + load the LaunchAgent (com.tono.worker)
tono worker start --gateway my-mac-mini:7040

# later: lifecycle commands mirror `tono gateway`
tono worker stop
tono worker restart            # reload after settings changes
tono worker status             # plist state + connection
tono worker logs               # tail ~/.tono/logs/worker.out.log
tono worker uninstall          # remove the LaunchAgent

Settings persist in ~/.tono/worker.json (gateway URL, agent commands, concurrency, paths). The first start / run writes them; later invocations don't need flags. Pass new flags to override and re-persist (tono worker restart --concurrency claude-code=4/2 updates the JSON and reloads the daemon). The same file holds a stable worker UUID so reconnects pick up the same identity.

Workers reconnect with exponential backoff; if the gateway is offline the worker sits idle until it returns.

Disconnect handling. When a worker drops mid-run (laptop sleeps, Wi-Fi blip), the gateway holds the task as running for workers.graceMs (default 60 000 ms). Reconnect within the window resumes the live stream. After the grace expires the task is marked failed with exit_code = -3 and you can retry it. The setting lives in ~/.tono/config.json under the optional workers block:

{
  "workers": { "graceMs": 60000 }
}

Caveats (v1):

  • No worker affinity / pinning yet — workers are fungible by capability.
  • Worktree cleanup runs on the gateway's filesystem only. For tasks that ran on a remote worker, the worktree under that worker's ~/.tono/workspaces/ is left in place; clean it up there manually with git worktree remove.
  • Free-form shells in the UI are gateway-local (they always run in the gateway process, not on workers).

License

MIT.