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

@aether-agent/sdk

v0.2.1

Published

TypeScript SDK for the Aether agent CLI

Readme

@aether-agent/sdk

TypeScript SDK for the Aether agent. It spawns aether acp under the hood and exposes one explicit stateful API:

  • AetherSession — start an ACP session, send prompts, then close it
  • tool() plus tools: { prefix: [...] } — register closure-backed TypeScript tools that the agent can call as MCP tools

Install

pnpm add @aether-agent/sdk
# or: npm install @aether-agent/sdk

You also need the aether CLI on your PATH or an explicit aetherPath. See the Aether install docs.

Basic session

AetherSession implements Symbol.asyncDispose, so the recommended pattern is await using — the session closes (kills the subprocess, tears down MCP servers) automatically on scope exit:

import { AetherSession } from "@aether-agent/sdk";

await using session = await AetherSession.start({
  cwd: "/path/to/repo",
  agent: "planner",
});

for await (const message of session.prompt("Find TODOs in this repo")) {
  if (message.type === "session_update") {
    console.log(message.update);
  }
}

If your runtime predates explicit resource management, call session.close() yourself in a finally block.

AetherSessionOptions lets you pick the initial agent or model:

| Option | Notes | | ----------------- | -------------------------------------------------------------------- | | agent | Mode name from .aether/settings.json (e.g. planner). | | model | Direct model id (e.g. anthropic:claude-sonnet-4-5). | | reasoningEffort | "low", "medium", "high", "xhigh". | | settings | Inline Aether settings object using the .aether/settings.json shape. | | settingsFile | Path to an alternate settings JSON file. | | cwd | Working directory for the spawned aether acp process. | | tools | Closure-backed TypeScript tool groups keyed by Aether tool prefix. | | externalMcpServers | External stdio/http/sse MCP servers keyed by Aether tool prefix. | | abortSignal | Cancel the active session and tear the subprocess down. |

agent and model are mutually exclusive. settings and settingsFile are mutually exclusive. These are forwarded to the spawned aether acp process as --settings-json and --settings-file, where the CLI resolves the initial system prompt and tool filter before the session is constructed.

Multi-turn usage

await using session = await AetherSession.start({ cwd: process.cwd() });
for await (const m of session.prompt("First question")) console.log(m);
for await (const m of session.prompt("Follow-up")) console.log(m);

Closure-backed custom tool

import { AetherSession, tool } from "@aether-agent/sdk";
import { z } from "zod";

function createSubmitTool() {
  let submitted: { answer: string } | null = null;

  return {
    tool: tool({
      name: "submit_answer",
      description: "Submit the final answer",
      input: { answer: z.string() },
      handler: async ({ answer }) => {
        submitted = { answer };
        return { content: [{ type: "text", text: "Submitted." }] };
      },
    }),
    getResult: () => submitted,
  };
}

const submit = createSubmitTool();
{
  await using session = await AetherSession.start({
    cwd: process.cwd(),
    tools: {
      custom: [submit.tool],
    },
  });

  for await (const _message of session.prompt("Call custom__submit_answer with the final answer.")) {
    void _message;
  }
}

console.log(submit.getResult());

The handler runs in the calling Node process, so closures, in-memory state, file handles, and database connections all work as you'd expect. Add more prefixes when you want multiple Aether tool namespaces:

tools: {
  recommendations: [submitRecommendations.tool],
  review: [approve.tool, reject.tool],
}

How closure-backed tools are wired

To preserve TypeScript closures, each entry in tools starts a small Streamable HTTP MCP server on 127.0.0.1:<random-port> and tells aether acp to connect there via ACP's mcpServers field. Each server is protected by:

  • A per-session random bearer token (Authorization: Bearer …).
  • DNS rebinding protection (host-header validation) provided by createMcpExpressApp().

The token is generated fresh per tool group on each AetherSession.start() call and torn down when session.close() runs.

Aether tool naming

Aether names MCP tools as server__tool internally. The tools object key is the server prefix. If you register a tool named submit_answer under the custom key, the agent will see it as custom__submit_answer. If your selected agent has a restrictive tool allowlist in .aether/settings.json, include the custom server pattern or leave the filter empty.

External MCP servers

externalMcpServers accepts standard external server shapes, which are forwarded to Aether unchanged. Object keys become Aether MCP server prefixes:

externalMcpServers: {
  filesystem: { type: "stdio", command: "uvx", args: ["mcp-server-filesystem", "/path"] },
  remote: {
    type: "http",
    url: "https://mcp.example.com/mcp",
    headers: { Authorization: "Bearer …" },
  },
  legacy: { type: "sse", url: "https://mcp.example.com/sse" },
}

Permission and elicitation hooks

By default the SDK auto-accepts the first allow_* permission option — this is the exported autoApprovePermissions handler, suitable for trusted/dev contexts. For untrusted agents or production hosts, supply your own handler:

import { AetherSession, autoApprovePermissions } from "@aether-agent/sdk";

// Explicit auto-approve (same as the default).
await AetherSession.start({ onPermissionRequest: autoApprovePermissions });

// Custom policy.
await AetherSession.start({
  onPermissionRequest: async (request) => {
    return { outcome: { outcome: "selected", optionId: request.options[0].optionId } };
  },
});

onElicitation handles Aether's _aether/elicitation extension request.