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

@nothumanwork/agi

v0.1.0

Published

Bun-only SDK for driving interactive AI coding CLIs (Claude Code, Codex) inside tmux, observed through their native transcripts

Readme

agi

Requires Bun. This package uses Bun.spawn and Bun.file; Node.js is not supported.

A small SDK for driving interactive AI coding agents (Claude Code, Codex, …) from scripts. Harness-agnostic by design: one Agent interface, pluggable adapters per CLI.

Two ideas make it work:

  1. Interactive, not headless. Agents run as their real TUIs inside tmux sessions, so they keep their full capabilities, survive your process, and a human can drop into any of them at any time with tmux attach.
  2. Transcripts, not screen-scraping. Agent state (working / idle) and replies are read from each harness's own session transcript on disk — structured JSONL, no TUI noise. The pane is only used to type into.

Install

bun add @nothumanwork/agi

Alternatively, install straight from GitHub (the repo root manifest exposes this directory as the same package):

bun add github:thehumanworks/harness

Bun runs the TypeScript sources directly, so there is no build step. For a zero-setup single-file script that installs the SDK from GitHub on first run, see examples/standalone-fleet.ts.

import { spawn, attach } from "@nothumanwork/agi";

const agent = await spawn("claude", { cwd: "/path/to/repo", model: "opus" });

// fire-and-forget + poll
await agent.send("Fix the failing test in src/parser.ts");
console.log(agent.attachCommand); // tmux attach -t agi-1a2b3c4d — watch it live
while ((await agent.status()).state === "working") { /* do other work */ }

// or ask-and-wait
const reply = await agent.ask("Summarise what you changed.");

// reattach later, from a different process, by id
const same = await attach(agent.id);
console.log(await same.messages()); // normalized conversation so far

await same.kill();    // stop the CLI; session stays resumable
await same.resume();  // relaunch from harness-native session state
await same.forget();  // stop + delete the persisted record

How it works

| | Claude Code | Codex | |---|---|---| | Launch | claude --session-id <uuid> in tmux | codex in tmux | | Transcript | ~/.claude/projects/<encoded-cwd>/<uuid>.jsonl (path known up front) | ~/.codex/sessions/YYYY/MM/DD/rollout-*.jsonl (discovered by cwd after first prompt) | | Turn done | last assistant record has stop_reason: "end_turn" | event_msg of type task_complete (carries last_agent_message) | | Resume | claude --resume <uuid> | codex resume <id> |

Prompts are delivered with tmux send-keys (bracketed paste for multi-line), with a short settle delay before Enter. Boot dialogs (folder trust) are answered automatically.

Sessions are persisted as one JSON file each under ~/.agi/agents/ (override root with AGI_HOME), so any later process can attach(id), list(), or resume() them.

API

  • spawn(adapter, { cwd, model, extraArgs, readyTimeoutMs })Agent
  • attach(id)Agent (from the persisted record)
  • list() → persisted SessionRecord[]
  • agent.send(prompt) — type + submit; returns once delivered. Sending while the agent is working is fine (harnesses queue the prompt); sending while it is waiting on a human dialog throws instead of typing into the dialog.
  • agent.wait({ timeoutMs, pollMs }) — until the turn completes; returns reply text. Throws TurnTimeoutError (with the current screen — usually a pending permission prompt) on timeout, or WaitingForInputError if the agent asked a question only a human can answer (AskUserQuestion / ExitPlanMode). The latest send cursor is persisted, so a later process can attach(id) and wait for a turn started by an earlier process.
  • agent.ask(prompt, opts)send + wait as one serialized turn; concurrent ask() calls on the same agent run back to back.
  • agent.status(){ state: "working" | "waiting" | "idle" | "exited", lastReply?, waitingOn? }
  • agent.messages() — normalized TranscriptEvent[] (text / tool_use / tool_result)
  • agent.watch({ until: "idle", from: "now" | "start" }) — async iterator of transcript events, from the live edge by default
  • agent.alive() / agent.resume() / agent.kill() / agent.forget()
  • agent.attachCommand — the tmux attach -t … string for humans

Fleet

  • runFleet(tasks, opts?)FleetReport — run many agents with bounded concurrency and collect one structured, JSON-serializable report. Each task is { adapter, prompt, ...SpawnOptions } (so cwd / worktree / model / extraArgs all apply per task). opts is { concurrency? (default 8), taskTimeoutMs? (default 20 min), cleanup? }, where cleanup is "forget" | "keep" | "keep-failed" (default). A single task failing (spawn error, TurnTimeoutError, WaitingForInputError) never rejects the run or kills siblings — its error lands in that task's report entry; runFleet itself rejects only on programmer error (empty tasks, bad options).

    concurrency bounds how many tasks are being actively worked (spawn → reply) at once — it does not cap live agents. Agents kept by the cleanup policy stay alive beyond their task and accumulate, so concurrency: 1 still leaves many tmux CLIs running if the policy keeps them.

    The FleetReport is plain data: aggregate fulfilled / failed counts and startedAt / endedAt, plus a per-task entry with state ("fulfilled" | "failed"), reply? / error?, agentId, transcript, attachCommand, timestamps, a worktree { path, branch, base, status? } summary for worktree-backed tasks (status is "" for a clean worktree and absent when it could not be read), and the cleanup outcome ("forgotten" | "kept" | "cleanup-failed" | "none"). With the default "keep-failed", fulfilled agents are forgotten and failed ones are kept so their session and worktree survive as debugging artifacts (attachCommand and transcript point a human at them). See examples/fleet.ts.

  • Fleet.start(tasks, opts?)Fleet — launch a durable background fleet, deliver prompts with non-blocking agent.send(), persist the fleet record under ~/.agi/fleets/, and return without waiting for replies. Use Fleet.attach(id) from any later process, fleet.status() to inspect per-task progress, fleet.report() to drive remaining pending tasks and collect a FleetReport, and fleet.forget() to forget all spawned agents and delete the fleet record. Persistent fleets do not auto-clean up completed agents; cleanup belongs to the handle. concurrency controls how many tasks are launched at a time. If the launcher exits, tasks that were not launched remain pending until a later report() or drive() call resumes them. See examples/hackernews.ts and examples/hackernews-collect.ts.

Adding a harness

Implement HarnessAdapter (see src/types.ts): how to launch and resume the CLI, where its transcript lives, how to parse its records, and how to read turn state out of them. Then registerAdapter(myAdapter). The two built-ins (src/adapters/claude.ts, src/adapters/codex.ts) are ~150 lines each and are the reference.

Examples

Runnable scripts under examples/:

  • pair-review.ts — Claude implements, Codex reviews
  • fleet.ts — synchronous parallel Claude agents with runFleet
  • standalone-fleet.ts — self-contained single-file fleet script; installs the SDK from GitHub on first run, no checkout or package.json needed
  • hackernews.ts / hackernews-collect.ts — launch a durable background fleet, then collect it later
  • isolated-worktrees.ts — parallel agents in isolated git worktrees
  • shared-session.ts — two client processes share one agent session, concurrent ask() calls serialized (durable turn lock)
  • parallel-codex.ts — two Codex agents in the same directory at once

Doctor

Check that Bun, tmux, harness CLIs, transcript dirs, and AGI_HOME are ready:

bunx @nothumanwork/agi doctor
# or from a checkout:
bun src/cli.ts doctor

Sample output:

✓ bun: Bun 1.4.0
✓ tmux: tmux 3.6b (/opt/homebrew/bin/tmux)
✓ claude: /Users/you/.local/bin/claude
✓ codex: /Users/you/.bun/bin/codex
✓ agent-clis: Found: claude, codex
✓ claude-projects: /Users/you/.claude/projects
✓ codex-sessions: /Users/you/.codex/sessions
✓ AGI_HOME: /Users/you/.agi/agents is writable

doctor verifies runtime prerequisites before you spawn sessions; use --json for machine-readable output.

Testing

bun test                          # unit tests (transcript parsing, registry)
AGI_E2E=1 bun test test/e2e.test.ts   # live: spawns real claude + codex sessions

Known limits

  • A pending permission prompt looks like working in the transcript; it surfaces via wait() timeout, whose error includes the current screen. Spawn with the harness's permission flags via extraArgs if you want unattended runs.
  • Codex transcript discovery matches by cwd, spawn time, and first prompt, excluding rollouts already claimed by other agi sessions; if multiple unclaimed rollouts still match (e.g. identical simultaneous first prompts), agi throws AmbiguousTranscriptError rather than guessing.
  • resume() restores harness-native session state; an in-flight turn that was killed mid-tool-call resumes as the harness decides, not seamlessly.