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

@theokit/agents

v15.0.0

Published

AI agents as first-class citizens of the TheoKit pipeline. The AgentBuilder.create() authoring chain compiles to the @theokit/sdk runtime.

Readme

@theokit/agents

The layer between @theokit/sdk and an application that hosts an agent.

The SDK is the runtime — it calls the model, dispatches tools, stores the conversation, streams the response. This package is the home: the surfaces an app needs around that runtime — where credentials come from, which tools are in scope and under what sandbox, when a human is asked, what happens to a session after it ends.

The division is not stylistic. @theokit/agents never re-implements a runtime concern; when it looks like it does, that is a bug. What it does is enrich: adding the object-shaped surface where there is state or orchestration to hold, and forwarding the rest unchanged.

Enriching never reduces. A symbol the SDK exposes and this layer does not forward is unreachable to whoever consumes the layer — and the only legal way out for them is to reimplement it. That happened: when ./auth exported one value against the SDK's nineteen, a downstream product rewrote ~120 lines of credential mechanics in the repository root now requires a written decision per SDK symbol, so the next gap breaks CI instead of quietly costing someone a week.

Install

pnpm add @theokit/agents

Peer dependencies: zod (schemas), react (only for ./client/react), @theokit/http (only when you mount HTTP surfaces).

Subpath map

Twenty entry points. Import the one you need — the barrel is not the API.

| Subpath | What lives there | |---|---| | . | AgentBuilder.create() — the authoring surface — plus the error hierarchy and shared types | | ./bridge | The SDK seam — createSdkAgentStream, event translation, the agent builder internals | | ./testing | Test seams: compiled-agent inspection, mock streams | | ./sandbox | Sandbox backends, postures, sandboxWritePolicy | | ./persistence | Transcript storage primitives forwarded from the SDK | | ./interactive | The interactive-shell backend contract | | ./pty | The node-pty implementation of that contract | | ./auth | Credential store, OAuth/device flow, resolveCredential, assertSecureModes | | ./config | Agent configuration, trust posture, and the instruction tree (loadInstructionTree) | | ./tools | Built-in tool factories (read, list, grep, git, patch, edit, shell, …) | | ./client | Transports for driving an agent from a client | | ./client/react | useAgent and the React bindings | | ./session | Session lifecycle, listing, fork/rewind, transcript GC with a retention floor | | ./hooks | The lifecycle-hook engine and its fingerprint gate | | ./ask | The human-in-the-loop rendezvous — ask a question, settle it, abandon it safely | | ./tool-scope | bindToolScope — bind {projectRoot, writeRoot, sandbox} once so an unconfined shell is unrepresentable | | ./mcp-health | MCP server health probing | | ./commands | Command routing and the shutdown/cleanup contract | | ./doctor | Environment diagnostics | | ./usage | Token and cost accounting |

The shapes worth knowing before you start

Tool scope is bound once, not per tool. bindToolScope({ projectRoot, writeRoot, sandbox }) returns a binder; every factory it binds inherits the scope. sandbox is required — a scope without one would be an unconfined shell, and making it unrepresentable is cheaper than detecting it.

Human gates come in three layers, and they compose. You declare which tools need approval on the builder, the posture decides whether auto-approval is even permissible (it carries the sandbox posture as evidence, so "auto-approve without confinement" cannot be expressed), and the resolver settles an individual request. ./ask is the inverse channel — the agent asking you.

Sessions are deleted carefully or not at all. ./session refuses to collect a transcript that a live writer holds, floors both keepLast and maxAgeDays, and plans before it applies. When your app knows about live sessions this package cannot see, inject them — protection is additive and never subtractive.

Credentials answer two questions. Which credential to use, and where it came from. The second matters more than it looks: without provenance, "why is it calling Anthropic?" has no answer in the data, and a user cannot tell an explicit choice from a precedence fallback.

What changed recently

See CHANGELOG.md — shipped inside this package, so node_modules answers the question without a round trip to the repository.

For "which symbol delivers capability X, and in which version did it land", the CHANGELOG entry that shipped the symbol is the answer — every entry names the version it landed in.

Foreign configuration surfaces

Which .claude/ surfaces this package reads, and which it refuses. A surface is read, or refused with a reason — never accepted and ignored, which is the failure an author cannot tell apart from configuration that had no effect.

| Surface | State | |---|---| | CLAUDE.md | read when the host names it — see below | | settings.jsonpermissions | translated into the SDK's PermissionRule[]; an entry that cannot be rendered faithfully is reported and excluded | | settings.jsonoutputStyle | read | | settings.jsonenv | parsed, not applied — see below | | settings.local.json | read, layered above settings.json | | skills/, agents/, commands/, plugins/ | read when the dialect is declared | | .mcp.json | read; a field this runtime does not carry is reported | | output-styles/*.md | read, selected by settings.json | | agent-memory/ | readapplySubagentMemory applies a subagent's memory: declaration, and apps/theocode calls it (apps/theocode/packages/agent/src/delegation/role-discovery.ts). The host still decides: nothing here calls it for you | | workflows/*.js | refused, and reported. Every other surface is data; a workflow is code, and executing JavaScript found under a caller-supplied directory is a decision that belongs to you | | keybindings.json | out of scope HERE — read by the consumer, see below | | themes/*.json | out of scope HERE — read by the consumer, like keybindings.json. apps/theocode resolves ~/.claude/themes/ in apps/theocode/packages/tui/src/theme/custom-theme.ts; this package and @theokit/tui read neither | | ~/.claude.json — OAuth state, UI toggles | out of scope — a CLI's own state | | ~/.claude.json — personal-scope MCP servers | readloadPersonalMcpServers |

Out of scope, and who owns it instead

An absence that reads as an oversight gets re-investigated at full cost by the next person. These were measured on 2026-09-12 — keybindings, themes/ and .claude.json each return 0 files across every package source tree, against a control of 31 for skills — and each one is a decision rather than a gap.

keybindings.json and themes/*.json are not this package's. A framework has no keyboard and no colour: it produces text and tool calls, and the process that renders them owns which key does what and which escape codes it emits. Reading them here would let this package hold configuration it cannot act on, which is the accepted-and-ignored failure the table above exists to prevent.

Where they actually go is the consumer, not @theokit/tui. Re-measured 2026-09-15: the toolkit carries internal keybindings and reads no file; theocode reads ~/.claude/keybindings.json itself, in terminal-io/keybindings.ts, against a format it measured against the published documentation. Naming the toolkit as the owner sent a reader to the package that does the least with it — and the row said out of scope without saying out of scope FOR WHOM, which is the half that misleads.

themes/*.json is read by the CONSUMER, exactly as keybindings.json is: apps/theocode resolves ~/.claude/themes/ in apps/theocode/packages/tui/src/theme/custom-theme.ts and selects one through /theme custom:<slug>. This package reads it in 0 files and so does @theokit/tui — measured 2026-09-17 against the installed 0.80.0.

~/.claude.json is two things under one name, and the split is the point. Its OAuth state and UI toggles are a CLI's own state — that file is written by a specific program about its own session, and a library reading another program's login state would be reaching into something it neither owns nor can refresh. Its personal-scope MCP servers are a different matter: an MCP server the operator registered for themselves is a framework concern, this package already reads project-scope servers from .mcp.json, and the personal scope measured 0 when this was written. It does not any more: on the machine this was re-measured on, 2026-09-15, ~/.claude.json carried 2 personal-scope servers. The gap cost an operator the servers they registered for themselves, which is the difference between a gap worth declaring and one worth closing — so it was closed. loadPersonalMcpServers reads that key and only that key, and returns empty on a malformed file rather than throwing, because one stray comma in a shared home file must not break every project on the machine.

Both MCP readers are called by the host, not from inside this package — loadMcpJson has no internal caller either, and a library with no main() cannot have one. That is why both rows say read while agent-memory/ says resolvable: the difference is not who calls, it is whether the chain is intact. A memory: declaration was DROPPED by @theokit/sdk before any reader could see it; nothing drops an MCP server.

The registry entry that prompted this counts four decisions across three files, because ~/.claude.json is split. That is the count, stated so nobody goes looking for a fourth file.

themes/*.json

Read by the consumer, measured 2026-09-17. After keybindings.json turned out to be read by apps/theocode rather than by the package it was attributed to, the obvious next move was to treat this as the same finding — and it is the same finding, which this document denied until the three packages were named.

The denial is worth keeping, because it shows what an unfalsifiable sentence costs. This section read "nobody reads it, in any of the three packages" and never said which three. A reader could not check a set they could not enumerate, so nobody did; naming them took one edit, and the sentence was false the moment it became checkable — apps/theocode resolves ~/.claude/themes/ in apps/theocode/packages/tui/src/theme/custom-theme.ts, with three reads in that one file.

This package has no colour at all — a grep for color, chalk or theme across its source returns nothing, the one apparent hit being ansi inside stateTransitionHistory. It produces text and tool calls; a palette here would be configuration nothing could act on. The toolkit reads no theme file either, so naming an owner would repeat the mistake the keybindings.json row already made.

CLAUDE.md

The instruction tree accepts any file list: input.fileNames, falling back to a default of ['THEO.md', 'AGENTS.md', 'THEO.local.md', 'AGENTS.local.md']. CLAUDE.md is not in it, and instruction-tree.ts says so while explaining why CLAUDE.local.md is absent too: "a private companion to a file this does not read would pair with nothing".

A consumer that wants it passes it. Measured 2026-09-15 while auditing this table row by row: CLAUDE.md, settings.jsonenv and agent-memory/ are each resolvable by the host, and each was listed as read. The word describes the mechanism existing; a reader takes it as the behaviour happening.

settings.jsonenv

outputStyle is read AND applied: loadSettings returns it and resolveOutputStyle hands it to loadOutputStyle, which is the mechanism this package documents.

env is parsed and goes no further here. Measured 2026-09-15: the schema accepts it, the only consumer of a loadSettings result in this package reads outputStyle, and nothing applies the parsed values to process.env. A host that wants them can read settings.values.env and apply them itself — the same shape as agent-memory/ above, and stated for the same reason.

Grouping it with outputStyle under one read claimed for both what is true of one. The rule this table serves is that a surface is read, or refused with a reason, and never accepted and ignored; a key that is parsed and dropped is the third state, and it was in this file's own table.

Why env is not applied, rather than not yet applied

This is a refusal with a reason, not a gap waiting for someone.

env under .claude/ sets environment variables, and a variable is not inert. NODE_OPTIONS alone is code execution — --require ./anything.js runs a file before the program does — and LD_PRELOAD, PATH and NODE_PATH each reach the same place by another route. Anything this package spawns inherits the process environment: hook commands, and the stdio MCP servers whose ${VAR} references expandEnvReferences already resolves against process.env.

.claude/settings.json usually arrives with the clone. Applying its env would let a repository choose what runs inside every subprocess of anyone who opens it — the same threat the hooks row above is gated for, arriving by a quieter door. A consumer that wants those values can read settings.values.env and decide for itself, which is where a trust decision of that size belongs.

The honest form of this is what the table now says: parsed, so an author is told the key was seen, and not applied, so nobody believes it took effect.

agent-memory/

resolveAgentMemory() resolves the directory a subagent reads and writes, and returns the first 200 lines, capped at 25KB, of its MEMORY.md — both caps apply, and truncation is reported rather than silent.

The host calls it. Nothing here calls it for you — and that sentence is the whole contract: this package exposes the reader, an application wires it.

One does. apps/theocode imports applySubagentMemory from @theokit/agents/config and applies it to both role sets in apps/theocode/packages/agent/src/delegation/role-discovery.ts:84; that reaches resolveAgentMemory through agent-memory.ts:231. So the chain runs end to end, and this document says read rather than resolvable.

It said the opposite until 2026-09-16, and the reason is worth keeping because it is the failure mode this table exists to prevent. The measurement behind "no consumer does yet" was true when taken: the consumer could not call it. Its own source says why — "applySubagentMemory … is NOT in the published 14.4.0 this project pins, so the one line that wires it does not compile here yet". The integration was written, tested, and left disconnected, waiting on a publish.

What changed is not the code on either side. apps/theocode joined this repository, @theokit/agents resolves through the workspace, and the line compiled. A capability can be published, correct, and unreachable — and a document that reports the reachable state as the real one is reporting a delivery problem as a design decision.

@theokit/sdk before 5.9.0 refuses memory: in frontmatter entirely, naming the field rather than the version — agent-memory.ts carries that measurement, and the-sdk-version-that-parses-memory.test.ts pins it.

| memory: | Root | Who can see it | |---|---|---| | project | <cwd>/.claude/agent-memory/<agent>/ | committed, shared with the team | | local | <cwd>/.claude/agent-memory-local/<agent>/ | kept out of version control | | user | ~/.claude/agent-memory/<agent>/ | crosses projects |

import { resolveAgentMemory } from '@theokit/agents/config'

const { root, memory, truncated } = resolveAgentMemory({ agent: 'auditor', scope: 'project', cwd })

An unrecognised scope is refused, not defaulted. The three differ in exactly one way — who can see the notes — so guessing project would publish, on the next commit, something written expecting privacy. A subagent name that would escape the root is refused for the same reason: it comes from a file that arrives with the repository.

This is distinct from the session auto-memory at ~/.claude/projects/: each subagent reads and writes its own MEMORY.md, not the operator's.

Environment variables

This list is CLOSED. A variable that is not in this table is not read by this package — not "undocumented", not "read somewhere else": not read. That is the whole point of writing it down. rules/foreign-config-surfaces.md settles the same question for .claude/ file surfaces in one sentence — a surface is read, or it is refused with a reason about this product; it is never accepted and ignored — and this is that sentence applied to the environment.

The reason the guarantee is worth more than a list of three: it answers for every variable anyone could export, including the ones nobody enumerated, so an operator who sets something and sees no change knows which of the two worlds they are in.

| Variable | Security | What it changes | Read at | |---|---|---|---| | PROGRAMDATA | no | Where the machine-wide operator policy is looked for on Windows. Changing it moves which policy file governs the run. | src/config/operator-policy.ts:119 | | THEOKIT_CODEX_CLIENT_ID | yes | Overrides the OAuth client id used for the Codex device flow. It selects which OAuth client the device authorisation is issued against, so it is a credential-path decision rather than a convenience one. | src/auth/device-provider.ts:97, via the constant at :93 | | THEOKIT_DEBUG | no | Turns on debug logging. Log output can contain request shapes, so treat the logs as sensitive even though the switch is not. | src/debug-log.ts:10 |

The Security column is a decision per row, not a keyword match on the name. A name containing AWS can be a plain address, and a name matching nothing can disable a control — so each row was judged rather than classified.

node scripts/check-env-verdicts.mjs fails the build in both directions: a variable this package reads and this table omits, and a variable this table lists that nothing reads any more. Without the second direction the table would keep advertising a variable after a refactor deleted its only read, which is a worse failure than never having documented it.

Boundaries this package keeps

  • It does not call an LLM provider, run a tool-dispatch loop, or own the conversation store. Those are the SDK's, and a PR that adds one here is rejected on sight.
  • It does not depend on the theokit web framework. The dependency runs the other way.
  • Web Standards over Node APIs inside src/Request/Response, fetch, crypto.randomUUID. Two node:crypto imports remain and both are measured rather than overlooked: createHash in packages/agents/src/hooks/hook-fingerprint.ts, because the Web equivalent is async and this function is not, and randomBytes in packages/agents/src/hooks/hook-spec.ts, where getRandomValues would buy conformance at the cost of a hand-written hex conversion. A third was removed on 2026-09-17: randomUUID had no such defence — the global is the API this line names, and three other files in the package were already using it. File reads keep node:fs: there is no Web Standard to prefer. Node APIs live in adapters.

Who decides policy

An operator who did not write the code can impose policy on it. That is a decision, recorded here because it is the one a team evaluating this framework for deployment needs before anything else.

It was not always true. Hooks, MCP servers, permissions and skill execution were each a value the programmer passed at build time — a constructor argument, decided in code, by whoever wrote the agent. That is a defensible design for a framework and an indefensible one for anything an organisation deploys: it means the person responsible for what an agent may do on a machine has no way to say so.

Policy is read from disk, and the layers have a fixed precedence:

| Layer | Who writes it | Wins against | |---|---|---| | managed-settings.json | the operator / the organisation | everything below | | .claude/settings.json, .theokit/settings.json | the project | the code | | defineAgent({ … }) | the programmer | nothing |

A programmer can still decide everything, and in a single-author project nothing above them exists. What changed is that they are no longer the only one who can.

Two consequences worth stating, because they are the cost:

  • A value declared in code can be overridden by a file the programmer does not control. That is the point, and it means a build-time guarantee is not one.
  • An operator restriction is refused loudly rather than silently narrowed. A policy that quietly did less than it said would be worse than none — the failure this repository keeps finding under other names.

Licence

See LICENSE.