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

@arasandev/harness

v0.1.0

Published

A clean-room TypeScript agent harness — vendor-neutral, composable kernel for building agentic systems. Inspired by general-purpose agent research; optimized for Anthropic-compatible endpoints.

Readme

@arasandev/harness v0.0.1

A clean-room TypeScript agent harness — vendor-neutral, composable kernel for building agentic systems. Implements the loop, tool execution, permission gating, hooks, subagent delegation, compaction, and transcript persistence as a typed library you import — not a subprocess you spawn.

Vendor-neutral: Works with any Anthropic-compatible API endpoint (Anthropic, MiniMax, OpenRouter, custom). Strictly typed: No any, Zod validation on all tool inputs, required parameters enforced. Production-ready: 1657 tests, comprehensive error messages, hooks as composable algebra.

Inspired by general-purpose agent research; optimized for the {baseURL, apiKey, model} configuration triad.

Installation

npm install @arasandev/harness @anthropic-ai/sdk zod

Environment Setup

The SDK reads three environment variables (all optional if passed explicitly):

  • ANTHROPIC_API_KEY — Your API key (passed to apiKey option)
  • ANTHROPIC_BASE_URL — Custom endpoint (defaults to Anthropic's; use for MiniMax, OpenRouter, etc.)
  • ANTHROPIC_MODEL — Default model (falls back to hardcoded Sonnet; recommend setting this explicitly)
# Example: use MiniMax instead of Anthropic
export ANTHROPIC_BASE_URL=https://api.minimax.io/anthropic
export ANTHROPIC_API_KEY=sk-...
export ANTHROPIC_MODEL=MiniMax-M3

Quick start

import { createCodingAgent } from '@arasandev/harness/preset';

const agent = createCodingAgent({ apiKey: process.env.ANTHROPIC_API_KEY });

for await (const ev of agent.send('list the .ts files and count them')) {
  if (ev.type === 'text_delta') process.stdout.write(ev.text);
}

createCodingAgent wires coreTools + TodoWrite + WebFetch + a rules-based permission gate + cost tracking + compaction + subagent delegation in one call. All options are optional; apiKey falls back to ANTHROPIC_API_KEY.

Build a custom tool

import { tool } from '@arasandev/harness';
import { z } from 'zod';

const weatherTool = tool({
  name: 'Weather',
  description: 'Get current weather for a city. Returns temperature and conditions.',
  inputSchema: z.object({ city: z.string().describe('City name, e.g. "London"') }),
  async call({ city }, ctx) {
    const res = await fetch(`https://wttr.in/${city}?format=j1`, { signal: ctx.signal });
    return res.json();
  },
  isReadOnly: true,
  isOpenWorld: true,
});

Pass tools: [weatherTool, ...coreTools] to new Agent(...) or extraTools: [weatherTool] to createCodingAgent(...).

Add permissions

import { rulesToCanUseTool, withPermissionCache } from '@arasandev/harness';
import type { PermissionRule } from '@arasandev/harness';

const rules: PermissionRule[] = [
  // Always allow read-only inspection.
  { field: 'tool', matcher: { type: 'equals', value: 'Read' }, decision: 'allow' },
  // Always allow git commands.
  { tool: 'Bash', field: 'input.command', matcher: { type: 'prefix', value: 'git ' }, decision: 'allow' },
  // Hard-deny recursive root deletion.
  {
    tool: 'Bash',
    field: 'input.command',
    matcher: { type: 'regex', pattern: 'rm\\s+-rf?\\s+(/|~)' },
    decision: 'deny',
    reason: 'Refusing to delete a filesystem root.',
  },
];

const canUseTool = withPermissionCache(rulesToCanUseTool(rules)).canUseTool;
// Pass to: new Agent({ canUseTool, ... })

Rules evaluate first-match. Undecided calls are denied by default (fail-closed). Compose with a host fallback via rulesToCanUseTool(rules, { fallback: askUser }).

Add hooks

import { Agent } from '@arasandev/harness';
import type { Hooks } from '@arasandev/harness';

const hooks: Hooks = {
  preToolUse: [
    async (ctx) => {
      console.log(`→ ${ctx.tool.name}`, ctx.input);
      return { kind: 'allow' };           // or { kind: 'deny', reason: '...' }
    },
  ],
  postToolUse: [
    async (ctx) => {
      console.log(`← ${ctx.tool.name} isError=${ctx.isError}`);
      return {};                           // or { modifiedOutput: redacted }
    },
  ],
};

const agent = new Agent({ model: 'claude-opus-4-8', tools: [], hooks });
// Tip: import { MODELS } from '@arasandev/harness' and use MODELS.OPUS, MODELS.SONNET, MODELS.HAIKU

A PreToolUse hook can rewrite or block the call before it runs. A PostToolUse hook can rewrite the result the model sees. Both fire in registration order.

Architecture

Developer
    │
    ▼
Agent (per-turn owner)
  owns: mutableMessages, usage, permissionMode, hookBus
  API:  send(prompt) → AsyncGenerator<AgentEvent>
        interrupt() · registerTool() · getMessages() · getCostUsd()
    │
    ▼
runLoop (per-model-call recursion)
  while (true) { call model → execute tools → loop }
  checks: abort signal, maxTurns, maxBudgetUsd, compaction
    │
    ├─────────────────┬──────────────────┬────────────────────┐
    ▼                 ▼                  ▼                    ▼
Transport          Tool             HookBus            CanUseToolFn
anthropicTransport Tool<I,O>        preToolUse         permission gate
mockTransport      tool() factory   postToolUse        rulesToCanUseTool
                   coreTools        stop / session     withPermissionCache

coreTools (7 built-in tools)

coreTools is the I/O tool set you pass directly to new Agent(...). It does not require any factory wiring.

| Tool | What it does | |------|-------------| | Bash | Execute shell commands; supports run_in_background | | Read | Read files (text, image, notebook, PDF) | | Write | Create or overwrite files | | Edit | String-based targeted file edits with mtime check | | MultiEdit | Apply multiple edits to a file in one call | | Glob | Recursive file pattern matching | | Grep | Regex search across files with context lines |

createTodoWriteTool and createWebFetchTool are factories — they require per-agent state wiring and are not in coreTools.

Design principles

1. Claude-Code-native naming. Type names, event names, and tool names match real Claude Code so agents trained on Claude Code can use this SDK without translation. PreToolUseHook, tool_result, PermissionRule are the same concepts.

2. One obvious way. tool() is the only entry point for building tools. rulesToCanUseTool is the only entry point for declarative policy. Helpers exist but they compose rather than replace.

3. Self-describing contracts. Every tool's description field is its only documentation from the model's perspective. ToolContext carries exactly what a tool needs (cwd, env, signal, fileState) and nothing else.

4. Fail-closed. Undecided permission calls are denied. denyAll is the zero-config default for canUseTool. Security boundaries do not require explicit opt-in.

5. Small surface, additive. The required API is four fields on Tool and one method on Agent. Optional fields are pure additions. Adding a field to Tool is non-breaking; removing one is not.

With MCP tools (streamable HTTP or stdio)

import { Agent, coreTools, MODELS } from '@arasandev/harness';
import { connectMcpServers, createSamplingHandler } from '@arasandev/harness/mcp';

const agent = new Agent({ model: MODELS.SONNET, tools: coreTools, apiKey: process.env.ANTHROPIC_API_KEY! });

const registry = await connectMcpServers({
  configs: [
    { name: 'my-server', transport: { kind: 'streamableHttp', url: 'https://my-mcp-server.example.com/mcp' } },
  ],
  // Apply sampling handler to all servers with '*' wildcard:
  clientOptions: { '*': { sampling: createSamplingHandler({ agent }) } },
});
await registry.bindToAgent(agent);
// agent now has coreTools (7) + all tools from my-server

for await (const event of agent.send('Use your tools')) {
  if (event.type === 'text_delta') process.stdout.write(event.text);
}
await registry.disconnectAll();

Note: loadMcpConfig, connectMcpServers, and createSamplingHandler are imported from '@arasandev/harness/mcp' — not the front door.

Non-goals for 0.0.1

  • Session tree / multi-root conversations. The Agent owns a single linear message history. Branching (fork-and-rejoin, speculative execution) is out of scope.
  • Daemon bridge / long-lived process model. There is no persistent daemon, no IPC channel, no reconnect protocol. The agent is a library object in your process.
  • 50-tool catalog. The SDK ships coreTools (7 I/O tools) plus factories for TodoWrite, WebFetch, Bash with sandbox, plan-mode tools, and subagent delegation. A broad catalog of domain tools (browser, calendar, Slack, database connectors) is a host responsibility.