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

aitalkback

v0.1.0

Published

Hear what your AI agent is doing — speaks an agent's reasoning, tool calls, file access, commands, and token usage out loud, in real time.

Readme

AiTalkBack

Hear what your AI agent is actually doing.

AiTalkBack narrates an agent's work out loud, in real time — the reasoning, the tool calls, the token spend, which files it is touching, and which commands it is about to run. It sits between the prompt and the goal and tells you what is happening in between.

▸ You asked: Refactor the billing module
▸ Reading billing slash invoice dot ts.
▸ Editing billing slash invoice dot ts, to split out the proration helper.
▸ Careful — running git reset --hard HEAD~2, to drop the bad commits.
▸ Done. Claude finished and is waiting on you.

Install

npx aitalkback demo            # hear a scripted agent run, no install
npm install -g aitalkback      # or install the CLI properly
aitalkback demo                # hear a scripted agent run
aitalkback install             # wire up Claude Code hooks
aitalkback listen              # run the narrator daemon
aitalkback proxy               # narrate any LLM API through a proxy
open http://127.0.0.1:4737/    # browser overlay (needs `listen` running)

Then use Claude Code as normal in another terminal. Remove the hooks again with aitalkback uninstall.

Requires Node 20.11+. No runtime dependencies — the whole thing is Node builtins and fetch.

As a library

npm install aitalkback
import { TalkBack, loadConfig } from 'aitalkback';
import { fromClaudeCodeHook } from 'aitalkback/adapters';
import { createHandler } from 'aitalkback/serverless';

const talkback = await TalkBack.create(loadConfig());
await talkback.ingest(fromClaudeCodeHook(payload));

This package is ESM only. From CommonJS, use await import('aitalkback').

How it works

agent → adapter → AgentEvent → thinking buffer → narrator → speech queue → TTS

Each stage is replaceable:

| Stage | Job | | --- | --- | | Adapter | Turns one agent's output (hooks, SSE chunks, JSON lines) into a canonical AgentEvent. Nothing downstream knows which agent it came from. Ships with Claude Code, Anthropic, OpenAI, DeepSeek, Gemini, and a generic fallback. | | Thinking buffer | Reassembles token-by-token reasoning into whole thoughts, then extracts the one sentence worth hearing. | | Narrator | Event → one short spoken sentence. Deterministic templates by default; an optional LLM pass only rewrites what the templates already produced. | | Speech queue | Priority ordering, de-duplication, load shedding, and barge-in. | | Provider | macOS say, ElevenLabs, any OpenAI-compatible /audio/speech, or stdout. |

Staying live

An agent emits events far faster than speech can play them. An unbounded queue would drift minutes behind and narrate the past, so the queue:

  • orders by priority — errors and destructive commands jump ahead of chatter;
  • de-duplicates — the same line inside an 8s window is dropped;
  • sheds — past maxQueue the lowest-priority lines are discarded, never the alarming ones;
  • barges in — an alarming event cuts off a routine sentence mid-word, because hearing "deleting your database" after the queue drains defeats the point.

Narrating the thinking

A streaming agent emits one thinking event per token. Narrating those directly gives you fragments; there is no fixing it downstream, because by the time an utterance reaches the queue the thought has already been shredded. So reasoning is buffered into whole thoughts and flushed when the agent goes quiet (1.2s), when the block runs long (700 chars), or the moment the agent stops reasoning and acts — whichever comes first.

Each thought is then reduced to a single sentence extractively, using the agent's own words. Reasoning wanders, backtracks, and only commits near the end, so sentences are scored for decision markers ("I'll…", "the problem is…"), position, and length, while self-directed questions and hedging are discarded. A block that is all throat-clearing is silently dropped.

79 token deltas in:
  "Okay, let me look at this. The user says the invoice totals are off by a
   cent sometimes. Hmm. Could be rounding. ... Wait, is that actually the
   problem? Yes — the rounding happens after summing, so errors accumulate.
   I'll switch the proration helper to integer cents and round per line item."

one sentence out:
  💭 I'll switch the proration helper to integer cents and round per line item.

Extractive, not LLM-summarised, for two reasons: a network round-trip has no place in the hot path, and a summariser can invent a rationale the agent never had. Speaking the agent's literal words means the narration cannot lie about why it did something.

A thought is always spoken before the action it led to. Hearing the rationale after the edit it explains is worse than not hearing it at all.

Not saying secrets out loud

Anything spoken can be overheard, and with cloud TTS it leaves the machine. Every line is redacted before it reaches a speaker — provider key formats, JWTs, private keys, SECRET=-shaped assignments, and credentials embedded in URLs. Naming a credential file is still narrated; its contents never are.

Reading like speech, not like a terminal

Paths are shortened (/Users/you/app/src/auth/session.ts → "auth slash session dot ts"). Flags are dropped from commands — except on a destructive one, where the flag is the danger, so rm -rf dist and rm dist never sound alike.

Verbosity

| Level | You hear | | --- | --- | | quiet | Session start/end, errors, deletions — the things you would want interrupted for. | | normal | The above, plus prompts, plans, writes, edits, and commands. | | verbose | Plus reads, network calls, tool calls, thinking, and token usage. | | firehose | Everything, including every tool completion. |

Alarming events are spoken at every level, including quiet.

Narrating any LLM: the proxy

aitalkback proxy forwards requests to the real provider and narrates the response stream on the way past. Point your SDK's base URL at it — no SDK patching, no code changes:

aitalkback proxy               # listens on 4738

export OPENAI_BASE_URL=http://127.0.0.1:4738/openai/v1
export ANTHROPIC_BASE_URL=http://127.0.0.1:4738/anthropic

| Provider | Route | Reasoning source | | --- | --- | --- | | Anthropic | /anthropic | thinking_delta blocks | | OpenAI | /openai | reasoning / reasoning_content deltas | | DeepSeek | /deepseek | reasoning_content deltas | | Gemini | /gemini | parts flagged thought: true |

Tool calls, token usage, truncated responses, and upstream errors are narrated for all four. Send a route somewhere else — a gateway, a local model, a mock — with AITALKBACK_<PROVIDER>_UPSTREAM:

AITALKBACK_OPENAI_UPSTREAM=http://localhost:1234 aitalkback proxy

It stays out of the way

The proxy sits in your request path, so the overriding rule is that narration must never affect the call:

  • Nothing is buffered. Each chunk is forwarded before it is parsed, so tokens reach your app at the same moment they would without the proxy. There is a test that fails if a response is ever held back.
  • Narration cannot fail the request. Malformed frames, unknown shapes, and parser errors are swallowed; the bytes still reach the caller untouched.
  • Speech is never awaited. Events are queued and forgotten.
  • Credentials pass straight through and are never read, stored, or logged.
  • Cancellation propagates — hang up on the proxy and it hangs up upstream.

Status codes, headers, and error bodies are relayed as-is, so an app cannot tell it is talking to a proxy.

Connecting other agents

Anything that can POST JSON can talk to the daemon:

curl -X POST http://127.0.0.1:4737/v1/events \
  -H 'content-type: application/json' \
  -d '{"kind":"command","title":"Bash","command":"npm test","rationale":"to check the refactor"}'

Or pipe JSON lines straight in:

my-agent --json | aitalkback tail

Built-in adapters: /v1/events/claude-code (hooks), /v1/events/anthropic (Messages API stream events), /v1/events/openai (chat completion chunks), and /v1/events (generic).

Endpoints

| Method | Path | Purpose | | --- | --- | --- | | POST | /v1/events[/<source>] | Submit events. Returns 202 immediately — hooks block the agent, so narration happens on our clock, not theirs. | | GET | / | The browser overlay: live transcript and in-browser speech. | | GET | /v1/stream | Server-sent events feed of everything being spoken. | | GET | /v1/recent?n=50 | The last N utterances. | | POST | /v1/flush | Shut up right now. | | GET | /health | Liveness, active provider, queue depth. |

Three query parameters on /v1/events let a client narrate for itself: respond=utterances returns the spoken text instead of a bare 202, speak=0 suppresses desktop playback, and verbosity=… overrides the configured level for that request alone.

The daemon binds to 127.0.0.1 only. It accepts anything that reaches it, so do not expose it to a network.

In the browser

Two clients, both speaking through the browser's own voice — no native TTS needed, so this works anywhere.

The overlay

Run aitalkback listen and open http://127.0.0.1:4737/. It subscribes to the live feed and gives you a running transcript plus voice, rate, and reasoning controls. Because only one sentence of reasoning is ever spoken, each thought keeps a full reasoning disclosure you can expand and read.

The extension

extension/ is an unpacked MV3 extension. Load it via chrome://extensionsDeveloper modeLoad unpacked.

It patches fetch and XMLHttpRequest in the page, tees any streaming response, and recognises Anthropic, OpenAI, DeepSeek, and Gemini frames by their shape rather than by URL — so it narrates any app calling those APIs from the browser, including your own.

The popup chooses where narration is spoken:

  • This browser — the daemon returns the words and chrome.tts speaks them.
  • Desktop — the daemon speaks them itself, as usual.

Two properties it is built around, both covered by tests:

  • It never disturbs the page. Only a clone() of the response is read; the original is handed back untouched, so streaming, cancellation, and error handling all behave exactly as they would without the extension.
  • It cannot exfiltrate anything. Host permissions are limited to 127.0.0.1 and localhost, so frames can only ever reach a narrator you are running yourself.

Reasoning is buffered in the service worker — per tab, so two concurrent streams cannot interleave into one garbled thought — and sent as whole blocks.

Serverless

createHandler() is a Web-standard fetch handler with no Node dependencies — it runs on Vercel Functions, Workers, Deno, or Bun. There are no speakers in a datacenter, so it returns the narration text for the caller to play, which suits browser extensions and mobile clients.

// api/narrate.ts
import { createHandler } from 'aitalkback/serverless';
export const POST = createHandler({ verbosity: 'normal' });

Configuration

~/.aitalkback/config.json (create it with aitalkback config --init):

{
  "provider": "auto",        // auto | say | elevenlabs | openai | stdout
  "voice": "Samantha",
  "rate": 210,               // words per minute
  "verbosity": "normal",
  "port": 4737,
  "mute": ["file.read"],     // event kinds to never speak
  "dedupeWindowMs": 8000,
  "thinking": { "idleMs": 1200, "maxChars": 700 },
  "maxQueue": 24,
  "llmNarration": { "enabled": false, "model": "anthropic/claude-haiku-4.5" },
  "redactPatterns": []       // extra regexes to strip before speaking
}

Flags override the file (--provider, --voice, --rate, --verbosity, --port, --llm, --dry-run), and AITALKBACK_* environment variables sit in between.

Optional LLM narration

With --llm, a small model rewrites each already-correct template line into something less robotic. It is capped at 2.5s and falls back to the template on any error, so narration never blocks on the network. Off by default: the templates are instant, free, and cannot hallucinate.

Development

npm install
npm test          # 62 tests, no build step (Node type-stripping)
npm run typecheck
npm run build

Running the tests from source needs Node 22.6+ for --experimental-strip-types, though the published package runs on 20.11+.

The source avoids TypeScript syntax that requires a transform (no parameter properties, no enums), so node --experimental-strip-types runs it directly.

License

MIT