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

@buzzie-ai/claude-inject

v0.1.1

Published

Drive a long-lived Claude Code session from Node, with structured event streams instead of PTY scraping. Wraps the local claude CLI via stream-json, inheriting the user's existing auth and MCP/plugin config.

Readme

claude-inject

An SDK that launches a Claude Code session in a child process and lets you inject prompts into it programmatically — getting structured event streams and full text replies back.

Why

Drive a long-lived Claude Code session from Node, with structured event streams instead of PTY scraping.

The Claude Code CLI already supports streaming JSON I/O (--input-format stream-json --output-format stream-json), but using it correctly across many turns — keeping one subprocess alive, framing prompts as NDJSON, parsing partial-message events, tracking the session UUID for resume — is enough fiddly plumbing that most people give up and shell out to claude -p one-shot per turn.

claude-inject is that plumbing as a small SDK: your code owns a persistent claude process and feeds it prompts. You get tool-call events, streaming text chunks, and full replies back as typed events. Same authentication, same MCP/plugin config, and same tool permissions as the user's interactive Claude Code — no API key required.

How it works

Under the hood, the SDK spawns:

claude -p --input-format stream-json --output-format stream-json \
          --include-partial-messages --verbose

That keeps claude alive across many user messages: each session.send(...) writes a single line of NDJSON to stdin, and the SDK parses the streamed events back out of stdout. There's no PTY scraping and no terminal escape-code parsing — everything is structured.

How this differs from the Claude Agent SDK

Anthropic ships an official Claude Agent SDK that talks to the Anthropic API directly. If you're building a standalone agent from scratch with your own MCP/tool wiring and you have an API key, that's the right tool.

claude-inject is for the case where you want to drive Claude Code itself — the user's already-installed, already-authenticated CLI — from code:

| | Claude Agent SDK | claude-inject | |---|---|---| | Talks to | Anthropic API | Local claude CLI binary | | Auth | API key | Inherits user's existing Claude Code auth | | MCP / plugins / tool permissions | You configure | Inherits user's existing config | | Updates | SDK version bumps | Free — comes with claude CLI updates | | Dependency | @anthropic-ai/sdk | claude on PATH + its stream-json protocol | | Best for | Building a new agent end-to-end | Scripting / extending an existing Claude Code setup |

The tradeoff is that claude-inject is coupled to the CLI binary and its stream-json protocol. Pin the claude CLI version in CI if reproducibility matters.

Limitations

Worth knowing before you adopt this:

  • No human in the loop. Permission prompts don't surface to a UI — the spawned claude runs headless under whatever permissionMode / tools config you pass. If you need per-tool-call approval, build that yourself.
  • One send() in flight at a time per session. Subsequent calls queue FIFO. Spawn multiple ClaudeSessions for parallelism.
  • dangerouslySkipPermissions is a foot-gun. Same caveat as the underlying CLI flag — only use it in trusted, sandboxed contexts.
  • Behavior depends on the installed claude CLI version. The stream-json event shapes can shift between CLI releases. Pin the version in CI.
  • Session resume requires a previous session UUID. sessionId is populated during the first send() (not start()), because stream-json mode doesn't emit system/init until the first message arrives.

Install

npm install @buzzie-ai/claude-inject

Requires Node ≥ 20 and the claude CLI (Claude Code) on your PATH (or pass claudePath explicitly).

Local development

git clone https://github.com/Buzzie-AI/claude-inject
cd claude-inject
npm install
npm run build

Quick start

import { ClaudeSession } from '@buzzie-ai/claude-inject';

const session = new ClaudeSession({
  cwd: process.cwd(),
  systemPrompt: 'You are a helpful assistant. Be concise.',
});

session.on('chunk', (text) => process.stdout.write(text));
session.on('tool_use', ({ name }) => console.log(`[${name}]`));

await session.start();
const reply = await session.send('Hello, what is 2 + 2?');
console.log('\nReply:', reply);

const reply2 = await session.send('And times 3?');
console.log('\nReply2:', reply2);  // same session — claude remembers turn 1

await session.close();

API

new ClaudeSession(options)

| Option | Type | Notes | |---|---|---| | cwd | string | Working dir. Defaults to process.cwd(). | | systemPrompt | string | Sent on first start; ignored on resume. | | claudePath | string | Path to claude. Defaults to PATH lookup. | | tools | string | "" disables all, "default" enables all, or "Bash,Edit,Read". | | allowedTools | string[] | Allowlist alternative to tools. | | disallowedTools | string[] | Denylist. | | permissionMode | 'acceptEdits' \| 'auto' \| 'bypassPermissions' \| 'default' \| 'dontAsk' \| 'plan' | | | dangerouslySkipPermissions | boolean | Equivalent to --dangerously-skip-permissions. | | model | string | Alias (opus, sonnet, haiku) or full ID. | | sessionId | string | Pin the session UUID. | | resumeSessionId | string | Resume an existing session by UUID. | | persistSession | boolean | Defaults to true (matches the CLI). | | env | NodeJS.ProcessEnv | Extra env vars. |

Methods

  • start(): Promise<void> — spawn claude and resolve as soon as the subprocess is running. (In stream-json input mode, claude doesn't emit anything until you send the first message, so sessionId is populated during the first send(), not during start().)
  • send(prompt: string): Promise<string> — inject a prompt, resolve with the full text reply. Multiple concurrent calls queue (FIFO).
  • close(): Promise<void> — close stdin and wait for the subprocess to exit.

Getters

  • busy: boolean — true while a send() is in flight or queued.
  • sessionId: string | null — captured from the system/init event.

Events

| Event | Payload | When | |---|---|---| | chunk | string | Text deltas as they stream | | tool_use | { id, name, input } | A tool call starts | | tool_result | { toolUseId, content, isError } | A tool call's result is folded back in | | assistant_message | { text } | Full assistant turn (concatenation of all text blocks) | | result | { text, isError, sessionId } | A turn finishes | | error | Error | Subprocess error | | exit | { code, stderr } | Subprocess exited |

Concurrency

One send() is in flight at a time. Subsequent calls queue and resolve in order. Inspect with session.busy.

Examples

  • examples/simple/ — 25-line "start → send twice → close"
    npm run demo:simple
  • examples/two-claudes/ — the original Director / Builder / Worker TUI demo, refactored to drive two ClaudeSessions
    npm run demo:two-claudes -- --builder --turns 6 --seed 'I want a program that prints hello world.'

Tests

npm run test:session     # SDK end-to-end (spawn, send twice, assert)
npm run test:role-check  # Builder-mode role check via examples/two-claudes
npm test                 # both

History

This started as a Director pattern experiment: two claude -p subprocesses conversing with each other, rendered side-by-side in a blessed TUI. The original code spawned a one-shot subprocess per turn and chained them by hand.

The SDK pivot replaces the one-shot pattern with a persistent claude -p --input-format stream-json subprocess that stays alive across many user messages — closer to what an interactive session feels like, but driven by code instead of a human keyboard. The original two-Claude TUI lives on as examples/two-claudes/.