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

curry-leaves

v1.0.1

Published

Curry Leaves — a small, provider-agnostic, multi-agent kernel for building AI agents of any kind: streaming tool-use loop, sub-agents, skills, thinking. The TypeScript port.

Downloads

39

Readme

Curry Leaves is a general-purpose agent kernel small enough to read in an afternoon. At its core is a streaming tool-use loop — call the model, run the tools it asks for, feed the results back, repeat — with everything a real agent needs built around it: sub-agents, skills, permission gating, session recording, automatic context compaction, and per-turn reasoning-effort sizing. Point it at any tools and any task; the kernel is domain-agnostic — it just happens to ship a batteries-included coding toolset and CLIs on top.

One engine, any provider, any UI. The loop knows nothing about a specific LLM wire format — each provider translates at its own boundary — so Anthropic, OpenAI, and Ollama are a config choice, not branching logic. Use it as a library (below), or launch either bundled CLI: a full-screen terminal UI or a line REPL.

import { Agent, Runner, codingTools } from "curry-leaves";

const agent  = new Agent({ model: "claude-sonnet-4-5", tools: codingTools() });
const result = await new Runner(agent).run("Summarize README.md in three bullets.");
console.log(result.outputText);

Table of contents

Features

  • Multi-provider. Anthropic, OpenAI, and Ollama (any OpenAI-compatible gateway) implement one Provider.stream() interface; the loop never changes. Streaming SSE is assembled into a neutral AssistantMessage at the provider boundary and nowhere else.
  • Rich toolset. read, write, edit, find, search, bash, task_create/task_update/task_list/task_get, ask, current_time, web_fetch, web_search. Tool args are zod schemas → JSON Schema for free. Oversized output is offloaded to an artifact store the model can page through with read.
  • Deferred tools + search_tools. Keep the advertised list lean; the model discovers more tools by keyword and activates them for the next turn — works across providers.
  • Sub-agents. Declare subagents: [...]; the parent gets a task tool (delegation that returns a result) and a transfer tool (one-way handoff). Bounded recursion depth.
  • Structured output. Give an agent an outputType (a zod schema); the Runner injects the schema, validates the final reply, and retries on mismatch. result.output is typed.
  • Auto-thinking. autoThinking: true sizes reasoning effort (Anthropic thinking budget / OpenAI reasoning effort) per turn with a cheap classifier.
  • Skills. Drop a SKILL.md under .curry-leaves/skills/<name>/; its teaser goes into the prompt and the model pulls the full body via read skill://<name> only when relevant (progressive disclosure).
  • Permissions. An opt-in gate authorizes each tool call (allow / ask / deny), with standing approvals and contained-change auto-approval. Off by default — headless runs never hang.
  • Sessions. Each run can be recorded to <home>/sessions/<id>/ (meta.json + transcript.jsonl).
  • Compaction. Long conversations are summarized as they near the context window — automatic, or on demand via Runner.compact() / the /compact command.
  • Typed, dependency-light, ESM. Node 20+. Ships .d.ts types. The library entry pulls in only zod; the CLIs add Ink + React.

Install

npm install curry-leaves

Or use the CLIs without installing globally:

npx curry-leaves        # full-screen terminal UI
npx curry-leaves-repl   # line REPL (works with piped input)

Requires Node 20+.

Quick start

import { Agent, Runner, codingTools } from "curry-leaves";

const agent = new Agent({
  model: "claude-sonnet-4-5",
  instructions: "You are a concise coding assistant.",
  tools: codingTools(),
});

const result = await new Runner(agent).run("What does src/runner.ts do?");
console.log(result.outputText);

Agent is a stateless definition (model, tools, instructions); Runner holds the live conversation and drives the streaming loop. Set an API key first (see below).

Configure a provider

export ANTHROPIC_API_KEY=sk-ant-...   # or
export OPENAI_API_KEY=sk-...

The provider is inferred from the model id (claude-* → Anthropic, gpt-*/o1-* → OpenAI, gemma*/llama*/qwen*/… → Ollama), or set CURRY_LEAVES_PROVIDER / pass an explicit provider to the Agent.

Local models via Ollama

Ollama speaks the OpenAI wire format, so any pulled tag works with no API key:

ollama pull gemma4
CURRY_LEAVES_MODEL=gemma4 npx curry-leaves-repl
import { Agent, Runner, codingTools } from "curry-leaves";
const agent = new Agent({ model: "gemma4", tools: codingTools() });   // provider → Ollama

Set OLLAMA_HOST to point at a non-default server (http://host:port). Tool use needs a model with the tools capability (gemma4, qwen3.6, llama3.x, …). Reasoning-effort knobs are dropped for Ollama, and usage cost is $0 (local). You can point the same OpenAIProvider at any OpenAI-compatible gateway via { baseUrl, apiKey }.

Terminal UI & REPL

A full-screen terminal UI (built on Ink) ships with the package — a header bar, a streaming transcript, live thinking blocks, spinner-tracked tool calls, indented sub-agent activity, a status bar, and a persistent input box. Finished turns land in real terminal scrollback; the active turn streams in place.

npx curry-leaves
╭──────────────────────────────────────────────────────────╮
│ curry-leaves · claude-sonnet-4-5 (anthropic)             │
│ /repo · 11 tools · subagents: explore, plan              │
╰──────────────────────────────────────────────────────────╯

you › what does src/runner.ts do?
ai ›
  → read({"path":"src/runner.ts"})
      1  /** The Runner — holds one live conversation … */
The Runner composes an Agent with conversation state and drives the loop …

 ● ready                                    in 4213 · out 187 · $0.0155
╭──────────────────────────────────────────────────────────╮
│ › ask anything — /help for commands                      │
╰──────────────────────────────────────────────────────────╯

Slash commands: /help, /reset, /tools, /skills, /model, /stats, /clear, /compact [focus], /auto (toggle contained-change auto-approve), /autonomous (toggle self-drive mode), /exit. The TUI needs an interactive terminal (a TTY).

The line-streaming REPL is available as curry-leaves-repl (npx curry-leaves-repl) — handy for piped / non-TTY input.

Library usage

Stream events instead of awaiting

for await (const event of runner.stream("Refactor the parser")) {
  if (event.type === "message_update" && event.delta?.kind === "text") {
    process.stdout.write(event.delta.value);
  }
}

Structured output

import { z } from "zod";
import { Agent, Runner } from "curry-leaves";

const agent = new Agent({
  model: "claude-sonnet-4-5",
  outputType: z.object({ title: z.string(), bullets: z.array(z.string()) }),
});

const { output } = await new Runner(agent).run("Summarize this repo.");
//    ^ typed + validated; the Runner retries on a schema mismatch

Sub-agents

import { Agent, Runner, codingTools, exploreAgent, planAgent } from "curry-leaves";

const agent = new Agent({
  model: "claude-sonnet-4-5",
  tools: codingTools(),
  subagents: [exploreAgent("claude-sonnet-4-5"), planAgent("claude-sonnet-4-5")],
});
// The parent can `task` (delegate → result) or `transfer` (one-way handoff) to these.

See examples/basic.ts for a complete runnable program, and examples/host-demo.ts for a self-contained illustration of the host / permission model.

Environment variables

| Variable | Purpose | Default | |---|---|---| | ANTHROPIC_API_KEY | Anthropic auth | — | | OPENAI_API_KEY | OpenAI auth | — | | CURRY_LEAVES_MODEL | Model id for the CLIs | claude-sonnet-4-5 | | CURRY_LEAVES_PROVIDER | Force a provider (anthropic | openai | ollama) | inferred from model id | | CURRY_LEAVES_HOME | Base dir for settings / skills / sessions | ~/.curry-leaves | | CURRY_LEAVES_NO_RECORD | Disable session recording when set | recording on | | OLLAMA_HOST | Ollama server URL | http://localhost:11434 | | NO_COLOR | Disable ANSI color | color on |

Architecture

The design is a strict layering — a stateless definition on a stateful driver on a pure engine — with all I/O pushed to swappable seams (Provider, Host, Tool).

| Layer | File | Responsibility | |---|---|---| | Message model | core/messages.ts | Provider-neutral Message/Content types — the one thing everything agrees on. | | Events | core/events.ts | What the loop yields; a small structural set + a streaming delta payload. | | Loop | core/loop.ts | The pure engine: stream → run tools → stream, while tools are called. Yields events. | | Tools | core/tools.ts | A registry of zod-typed tools + a concurrent executor with a universal large-result guard. | | Agent | core/agent.ts | A stateless definition (model, tools, instructions, sub-agents, outputType). | | Runner | runner.ts | Live conversation state; builds the Context each turn; wires sub-agents, handoff, permissions, compaction. | | Providers | providers/* | The only place that knows a wire format. Anthropic / OpenAI / Ollama behind one Provider. | | Host | core/host.ts | The frontend seam: emit(event) + request(req). Headless by default. | | Prompt | prompt.ts | Layered system prompt (identity → instructions → env → context → tools), cache-friendly. | | Permission | permission.ts | Per-call allow / ask / deny gate with standing approvals. | | Thinking | thinking.ts | A tiny classifier that sizes reasoning effort per task. | | Skills | skills.ts | Progressive-disclosure capability packages via skill:// refs. | | Compaction | compaction.ts | Summarizes old history as the context window fills. |

The one decision that drives the whole loop:

runnable = stopReason in ("tool_use", "stop") AND tool_calls exist

Tools were called → loop again. None → stop.

Project layout

src/
  core/        # messages, events, loop, tools, agent, host, blobs — the kernel
  providers/   # anthropic, openai/ollama, factory, sse, base
  tools/       # read, write, edit, find, search, bash, tasks, ask, web, …
  session/     # session store + recording
  cli/         # chat REPL + Ink TUI
  runner.ts prompt.ts permission.ts thinking.ts skills.ts compaction.ts catalog.ts settings.ts
  index.ts     # public API surface
examples/      # runnable examples

Development

git clone https://github.com/ilayanambi-ponramu/curry-leaves-ts.git
cd curry-leaves-ts
npm install            # runs the build via the `prepare` hook

npm run build          # tsc → dist/
npm run typecheck      # tsc --noEmit (the correctness gate)
npm run tui            # build + launch the Ink TUI
npm run chat           # build + launch the REPL
npm run example        # build + run examples/basic.ts
npm run clean          # rm -rf dist

There is currently no test runner; npm run typecheck under strict (plus noUncheckedIndexedAccess) is the gate. Contributions adding a test suite are welcome.

Contributing

Contributions are very welcome — bug reports, features, docs, and tests. In short:

  1. Open an issue first for anything non-trivial, so we can agree on the approach.
  2. Fork & branch off main (git checkout -b feat/short-description).
  3. Make the change, keep the diff focused, and ensure npm run typecheck passes.
  4. Open a pull request describing the what and why.

One gotcha worth calling out: this is pure ESM with NodeNext resolution, so every relative import must carry a .js extension even though the source is .ts (e.g. import { Agent } from "./core/agent.js").

See CONTRIBUTING.md for the full guide — dev setup, code conventions, how to add a tool / provider / frontend capability, commit style, and bug-reporting.

Non-goals

curry-leaves is deliberately small. It does not include MCP or LSP integration. If you need those, they belong in a layer built on top of the kernel, not inside it.

Acknowledgements

curry-leaves is a TypeScript port of a Python agent kernel of the same design, and follows its architecture closely.

License

MIT © Ilayanambi Ponramu