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

@code-first-agents/tool

v0.1.5

Published

A code-first-agents framework for building deterministic CLI tools that LLM agents can discover, validate, and invoke with zero guesswork. Powered by Zod schemas and structured output contracts.

Downloads

978

Readme

@code-first-agents/tool

CI npm

TypeScript implementation of the Code-First Agents pattern. Provides a Tool base class that enforces the tool contract: deterministic CLI tools with Zod input/output schemas, JSON envelope output, self-describing introspection (--schema, --help), and always-exit-0 semantics.

Key idea: deterministic work lives in code (Tools), the LLM orchestrates judgment (Skills). This library is the Tool side.

ℹ️ This project is maintained in spare time. Issues and pull requests are very welcome — please bear with best-effort response times.

Installation

bun add @code-first-agents/tool zod@^4
# or
npm install @code-first-agents/tool zod@^4

Peer dependency: Zod v4 (^4.0.0).

Usage

A Tool registers subcommands — each with a Zod input schema, an output schema, and a handler — then dispatches via CLI args or programmatic invocation.

Minimal example

#!/usr/bin/env bun
import { z } from "zod";
import { Tool, l1Output } from "@code-first-agents/tool";

const tool = new Tool({
  name: "math",
  description: "Basic math operations",
});

tool.subcommand({
  name: "multiply",
  description: "Multiply two numbers",
  input: z.object({
    a: z.coerce.number(),
    b: z.coerce.number(),
  }).strict(),
  output: l1Output({ product: z.number() }),
  handler: ({ a, b }) => ({
    message: "multiplied",
    product: a * b,
  }),
});

tool.run(process.argv.slice(2));

Run it from the CLI:

bun run math.ts multiply --a 6 --b 7
# → {"ok":true,"message":"multiplied","product":42}

Output levels

The spec defines three output levels. Use the corresponding helper to build the output schema:

L1 — Data (raw facts for the LLM to interpret):

import { l1Output } from "@code-first-agents/tool";

tool.subcommand({
  name: "greet",
  description: "Greet someone by name",
  input: z.object({ name: z.string() }).strict(),
  output: l1Output({ greeting: z.string() }),
  handler: ({ name }) => ({
    message: `greeted ${name}`,
    greeting: `hello ${name}`,
  }),
});

L2 — Classification (a discrete category the skill can branch on):

import { l2Output } from "@code-first-agents/tool";

tool.subcommand({
  name: "report",
  description: "Emit a report classified by log level",
  input: z.object({
    level: z.enum(["info", "debug"]).default("info"),
  }).strict(),
  output: l2Output(z.enum(["info", "debug"])),
  handler: ({ level }) => ({
    message: `report generated (level=${level})`,
    classification: level,
  }),
});

L3 — Instructions (a verbatim procedure for the LLM to execute):

import { l3Output } from "@code-first-agents/tool";

tool.subcommand({
  name: "instruct",
  description: "Emit a verbatim instruction set",
  input: z.object({}).strict(),
  output: l3Output({ topic: z.string() }),
  handler: () => ({
    message: "instructions generated",
    instructions: "## Step 1\nDo the thing.",
    topic: "setup",
  }),
});

Handler contract

  • Handlers return the output shape without ok — the framework stamps ok: true automatically.
  • Handlers always return a message: string describing what happened.
  • Input schemas should use .strict() to reject unknown flags.
  • Handlers can be sync or async.

Error handling

All errors exit with code 0 and return { ok: false, error: "...", ... }. Throw ToolError for domain-specific errors:

import { ToolError } from "@code-first-agents/tool";

tool.subcommand({
  name: "validate",
  description: "Validate a config file",
  input: z.object({ path: z.string() }).strict(),
  output: l1Output({}),
  handler: ({ path }) => {
    throw new ToolError("validation_failed", `Config at '${path}' is invalid`);
  },
});

The framework also handles: unknown_subcommand, input_validation_error, schema_violation, non_object_return, and unexpected_error.

Built-in subcommands

Every tool gets schema and help for free:

bun run math.ts schema   # JSON Schema for all subcommands
bun run math.ts help     # Human-readable subcommand listing

These are auto-registered — you cannot override them.

Programmatic invocation

Use .invoke() to call a subcommand in-process (useful in tests):

const result = await tool.invoke("multiply", { a: 6, b: 7 });
// → { ok: true, message: "multiplied", product: 42 }

Examples

A complete, clonable tool lives in examples/ — run it and compare the JSON, no build step required:

bun run examples/changeset.ts size --files 12 --additions 340 --deletions 50
# → {"ok":true,"message":"changeset classified as large","classification":"large","total_lines":390}

examples/changeset.ts is one tool that demonstrates all three output levels (L1 data, L2 classification, L3 instructions). See examples/README.md for the full walkthrough.

API Reference

All exports come from the package root (@code-first-agents/tool). See the spec for the contract these implement.

| Export | Kind | Purpose | | ---------------------------- | -------- | ---------------------------------------------------------------------------------------- | | Tool | class | The orchestrator. Construct with { name, description }, register subcommands, dispatch. | | tool.subcommand(config) | method | Register a subcommand with Zod input/output schemas and a handler. | | tool.run(argv) | method | Parse CLI args, dispatch, print the JSON envelope, and process.exit(0). | | tool.invoke(name, args) | method | Call a subcommand in-process; returns the envelope object (useful in tests). | | l1Output(shape) | function | Build an L1 (data) output schema — raw signals for the LLM to interpret. | | l2Output(classification, fields?) | function | Build an L2 (classification) output schema — a discrete category to branch on. classification is any Zod type (commonly z.enum(...)). | | l3Output(fields?) | function | Build an L3 (instructions) output schema — a verbatim procedure for the LLM. Fields are optional. | | ToolError | class | Throw inside a handler for domain-specific errors: new ToolError(code, message, detail?). Optional detail (string or object) is included in the error envelope's detail field. | | schema (builtin) | command | Auto-registered. Emits JSON Schema for every subcommand. Not user-overridable. | | help (builtin) | command | Auto-registered. Emits a human-readable subcommand listing. Not user-overridable. |

Every successful result is the envelope { ok: true, message, ... }; every error is { ok: false, error, ... } with exit code 0.

Development

Prerequisites: Bun >= 1.0

git clone https://github.com/beogip/code-first-agents-tool.git
cd code-first-agents-tool
bun install

| Command | Description | | ---------------- | ------------------------------- | | bun run dev | Re-run src/index.ts on change (watch) | | bun run build | Compile to dist/ (bun + tsc) | | bun test | Run tests | | bun run check | Lint + format (Biome, auto-fix) | | bun run lint | Lint only | | bun run format | Format only |

Project Structure

src/          # Source code
tests/        # Test files (*.test.ts)
dist/         # Build output (git-ignored)

Git Hooks

Lefthook runs automatically after bun install (via the prepare script):

  • pre-commit — Biome checks and auto-fixes staged files
  • commit-msg — Validates Conventional Commits format

Releases

Releases are automated via semantic-release on every push to main:

  • feat: → minor release
  • fix: → patch release
  • feat!: or BREAKING CHANGE: → major release

The CI workflow handles changelog generation, npm publishing, GitHub releases, and version bumping automatically.

The Code-First Agents Pattern

This library implements the tool contract from the Code-First Agents spec. The spec defines a separation principle:

  • Tools (this library) — deterministic, no LLM calls, Zod-validated I/O, JSON envelope output.
  • Skills — LLM-powered orchestrators that call tools and apply judgment.

If you're new to the pattern, start with the spec repo for the full picture of how tools, skills, and agents compose together.

License

MIT