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.3.6

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
  • mcp() — host closure-backed TypeScript tools as an MCP server and pass it to any agent via settings

Install

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

The SDK depends on @aether-agent/cli, which bundles the aether binary for your platform, so no separate install is required. Pass binaryPath to AetherSession.start() if you want to point at a system or custom-built aether instead (an absolute path or any name resolvable on PATH).

Basic session

AetherSession implements Symbol.asyncDispose, so the recommended pattern is await using — the session closes and kills the subprocess automatically on scope exit. SDK-hosted tool servers created with mcp() have their own lifetime; see mcp().

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. SDK-hosted tools live here under mcps. | | settingsFile | Path to an alternate settings JSON file. | | cwd | Working directory for the spawned aether acp process. | | binaryPath | Override the bundled @aether-agent/cli binary (absolute path or name on PATH). | | providers | Provider connection overrides, keyed by provider (for example { bedrock: { url: "http://127.0.0.1:8787", auth: "none" } }). | | 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.

Provider connection overrides route a provider to a custom endpoint and can also change auth behavior. Set auth: "none" only when a trusted proxy injects or signs auth:

await AetherSession.start({
  model: "bedrock:anthropic.claude-sonnet-4-5-20250929-v1:0",
  providers: { bedrock: { url: "http://127.0.0.1:8787", auth: "none" } },
});

For Bedrock inference profiles, keep model as the Bedrock foundation model ID and pass the profile ARN as the Bedrock provider request target:

await AetherSession.start({
  model: "bedrock:anthropic.claude-sonnet-4-5-20250929-v1:0",
  providers: {
    bedrock: {
      inferenceProfileArn:
        "arn:aws:bedrock:us-west-2:000000000000:application-inference-profile/000000000000",
    },
  },
});

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);

SDK-hosted MCP tools with mcp()

mcp() creates a TypeScript MCP server. Tools run in the calling Node process, so closures, in-memory state, file handles, and database connections all work as you'd expect.

The returned handle implements Symbol.asyncDispose, so await using tears the server down on scope exit.

import { AetherSession, mcp, 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",
      inputSchema: { answer: z.string() },
      handler: async ({ answer }) => {
        submitted = { answer };
        return { content: [{ type: "text", text: "Submitted." }] };
      },
    }),
    getResult: () => submitted,
  };
}

const submit = createSubmitTool();
await using custom = await mcp({ name: "custom", tools: [submit.tool] });
{
  await using session = await AetherSession.start({
    cwd: process.cwd(),
    settings: {
      agents: [],
      mcps: [custom.spec],
    },
  });

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

console.log(submit.getResult());

Per-agent tools

A spec on the top-level mcps is available to every agent. Put it on a single agent's mcps instead to scope those tools to that agent.

await using planner = await mcp({ name: "planner-tools", tools: [plan] });
await using reviewer = await mcp({ name: "reviewer-tools", tools: [review] });

await using session = await AetherSession.start({
  settings: {
    agents: [
      {
        name: "planner",
        description: "Planner",
        model: "anthropic:claude-sonnet-4-5",
        userInvocable: true,
        mcps: [planner.spec],
      },
      {
        name: "reviewer",
        description: "Reviewer",
        model: "anthropic:claude-sonnet-4-5",
        userInvocable: true,
        mcps: [reviewer.spec],
      },
    ],
  },
});

How mcp() is wired

Each mcp() call starts a small Streamable HTTP MCP server on 127.0.0.1:<random-port> and returns its address as an inline McpSourceSpec. Adding that spec to settings.mcps (or an agent's mcps) tells the spawned aether process to connect to it. Each server is protected by:

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

The server starts when you await mcp(...) and stops when the handle is disposed — via await using scope exit or an explicit await handle[Symbol.asyncDispose](). Disposal is idempotent.

Aether tool naming

Aether names MCP tools as server__tool internally. The name passed to mcp() is the server prefix. If you register a tool named submit_answer under the custom name, the agent sees 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.

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.


See the [`@aether-agent/evals` README](../aether-evals/README.md) for usage.