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

@timmo001/effect-herdr

v0.9.0-timmo001.1

Published

Effect-native TypeScript SDK for Herdr's local socket API.

Readme

@timmo001/effect-herdr

[!NOTE] This is a fork of Dillon Mulroy's herdr-ts-sdk, and full credit for the original project goes to him. This fork adds my own changes on top.

Effect-native TypeScript access to Herdr's local Unix-socket API.

The SDK exposes every Herdr operation as a typed Effect, decodes public inputs and wire responses with Effect Schema, represents live events as Stream, and owns long-lived socket cleanup with Scope. The root HerdrSdk service keeps the convenient namespace-oriented API while preserving precise errors, dependencies, and interruption.

import { Effect } from "effect";
import { HerdrSdk, herdrSdkLayer } from "@timmo001/effect-herdr";

const program = Effect.gen(function* () {
  const herdr = yield* HerdrSdk;
  const session = yield* herdr.session.snapshot();

  return {
    workspaces: session.workspaces.length,
    agents: session.agents.length,
  };
});

const summary = await Effect.runPromise(program.pipe(Effect.provide(herdrSdkLayer)));

Status and compatibility

  • SDK version: 0.9.0 (unreleased baseline)
  • Minimum Herdr release: 0.9.2; supported wire protocol: 22 only
  • Effect: 4.0.0-rc.118
  • Runtime: Node.js 20 or newer on a platform supported by Herdr's local socket server

The SDK verifies protocol compatibility before ordinary requests and shares that compatibility result across the Layer graph. A server on another protocol fails with HerdrUnsupportedProtocol; install matching Herdr and SDK releases instead of bypassing the check.

Bun consumers can install a pinned Git revision without building first. Bun uses the source export; TypeScript uses built declarations when available and source types otherwise. Packed Node.js consumers use the built ESM.

The first npm and JSR publication is pending. Until then, build and pack this repository with the mise-pinned tools for Node.js:

git clone https://github.com/timmo001/effect-herdr.git
cd effect-herdr
mise install
bun install --frozen-lockfile
npm pack

Then install the generated tarball in the consuming project and import from @timmo001/effect-herdr. After publication, install it directly:

bun add @timmo001/effect-herdr
npx jsr add @timmo001/effect-herdr

API shape

HerdrSdk is a yieldable Context.Service. It aggregates independently implemented namespace services; it does not proxy or duplicate their operations.

const program = Effect.gen(function* () {
  const herdr = yield* HerdrSdk;

  const created = yield* herdr.workspaces.createInDirectory(process.cwd(), {
    label: "SDK demo",
    focus: true,
  });

  yield* herdr.panes.sendText(created.rootPane.id, "pnpm test -- --run\n");
});

An operation has the ordinary Effect shape Effect.Effect<Success, Error, Requirements>:

  • Success is a schema-decoded domain value.
  • Error is the narrow union of expected failures for that operation.
  • Requirements identifies services the caller must provide.
  • Fiber interruption owns cancellation; the API does not expose AbortSignal.

Ordinary socket operations accept HerdrTransportRequestOptionsEncoded as their final optional argument. Client-shell sessions instead configure their bounded deadlines at acquisition:

import { Duration } from "effect";

yield * herdr.server.ping({ requestTimeout: Duration.seconds(2) });

requestTimeout is the local SDK deadline and accepts an Effect Duration; duration strings are accepted only through the HERDR_REQUEST_TIMEOUT environment variable. Server-owned waits have a separate timeoutMs in their input. When both apply, make the request deadline longer than the server wait so the server can return its typed result first.

Configuration

The production Layer resolves one immutable HerdrConfig and shares it with one HerdrTransport. Socket selection uses this precedence:

  1. Explicit socketPath
  2. Explicit session
  3. HERDR_SOCKET_PATH
  4. HERDR_SESSION
  5. The platform default Herdr socket

Selected invalid input fails with HerdrConfigurationError; it never silently falls through to a lower-precedence source. socketPath and session are mutually exclusive.

| Option or environment value | Meaning | | ------------------------------------------ | --------------------------------------------------------------------------------- | | socketPath / HERDR_SOCKET_PATH | Exact absolute Unix-socket path. | | session / HERDR_SESSION | Session name resolved below the Herdr configuration directory. | | requestTimeout / HERDR_REQUEST_TIMEOUT | Default finite, non-negative local request deadline; defaults to five seconds. | | application | Optional non-empty application name and version sent during compatibility checks. | | HERDR_CONFIG_DIR | Explicit Herdr configuration directory used for session/default sockets. | | XDG_CONFIG_HOME | Unix configuration root used when HERDR_CONFIG_DIR is absent. | | APPDATA | Windows configuration root used when HERDR_CONFIG_DIR is absent. |

Use explicit options when an application owns SDK configuration:

import { Effect, Duration } from "effect";
import { HerdrSdk, herdrSdkLayerFromOptions } from "@timmo001/effect-herdr";

const sdkLayer = herdrSdkLayerFromOptions({
  session: "work",
  requestTimeout: Duration.seconds(10),
  application: { name: "release-console", version: "1.4.0" },
});

const protocol = await Effect.runPromise(
  Effect.gen(function* () {
    return (yield* HerdrSdk).config.supportedProtocol;
  }).pipe(Effect.provide(sdkLayer)),
);

Configuration exports:

| API | Purpose | | ------------------------------------- | ----------------------------------------------------------------- | | HerdrRequestDeadline | Schema for finite, non-negative Effect durations. | | HerdrApplication | Schema for compatibility-handshake caller identity. | | HerdrProtocolVersion | Literal schema for protocol 22. | | HerdrConfigOptions | Schema for explicit SDK options. | | HerdrConfig / IHerdrConfig | Yieldable service and resolved configuration shape. | | herdrConfigRecipe | Ambient Effect Config recipe. | | makeHerdrConfig | Resolves explicit and ambient configuration into a service value. | | herdrConfigLayer | Production configuration Layer. | | herdrConfigLayerWithoutDependencies | Layer that retains the active ConfigProvider requirement. | | herdrConfigLayerFromOptions | Layer with explicit options ahead of ambient configuration. | | herdrConfigLayerFromValue | Layer for an already parsed configuration value. |

Root and direct-service Layers

Most applications should provide herdrSdkLayer, which exposes only HerdrSdk after composing and memoizing the production graph:

program.pipe(Effect.provide(herdrSdkLayer));

Advanced applications can depend on one service directly:

import { Effect } from "effect";
import { WorkspaceService, workspaceServiceLayer } from "@timmo001/effect-herdr";

const labels = Effect.gen(function* () {
  const workspaces = yield* WorkspaceService;
  return (yield* workspaces.list()).map((workspace) => workspace.label);
}).pipe(Effect.provide(workspaceServiceLayer));

Every namespace module follows the same public construction convention:

| Export shape | Meaning | | --------------------------------------- | ------------------------------------------------------------ | | I<Name>Service | Exact service interface, including nested capabilities. | | <Name>Service | Yieldable Context.Service key. | | make<Name>Service | Service constructor retaining HerdrTransport requirements. | | <name>ServiceLayerWithoutDependencies | Layer whose required infrastructure remains visible. | | <name>ServiceLayer | Production-ready Layer using ambient SDK configuration. |

The root equivalents are IHerdrSdk, HerdrSdk, makeHerdrSdk, herdrSdkLayerWithoutDependencies, herdrSdkLayer, and herdrSdkLayerFromOptions.

Nested capability contracts are exported as IClientWindowTitle, IAgentView, IPluginActions, IPluginLogs, and IPluginPanes. The pure identifier-constructor contract is IHerdrIds.

Service construction export index

This table spells out every direct-service symbol so callers and coding agents can find the exact composition entrypoint without deriving its spelling from the convention above.

| Namespace | Interface and service | Constructor | Requirement-preserving Layer | Production Layer | | -------------------- | ----------------------------------------------------------- | -------------------------------- | ---------------------------------------------------- | --------------------------------- | | clientShell | IClientShellService, ClientShellService | makeClientShellService | clientShellServiceLayerWithoutDependencies | clientShellServiceLayer | | commands | ICommandService, CommandService | makeCommandService | commandServiceLayerWithoutDependencies | commandServiceLayer | | productAnnouncements | IProductAnnouncementService, ProductAnnouncementService | makeProductAnnouncementService | productAnnouncementServiceLayerWithoutDependencies | productAnnouncementServiceLayer | | releaseNotes | IReleaseNotesService, ReleaseNotesService | makeReleaseNotesService | releaseNotesServiceLayerWithoutDependencies | releaseNotesServiceLayer | | server | IServerService, ServerService | makeServerService | serverServiceLayerWithoutDependencies | serverServiceLayer | | session | ISessionService, SessionService | makeSessionService | sessionServiceLayerWithoutDependencies | sessionServiceLayer | | notifications | INotificationService, NotificationService | makeNotificationService | notificationServiceLayerWithoutDependencies | notificationServiceLayer | | client | IClientService, ClientService | makeClientService | clientServiceLayerWithoutDependencies | clientServiceLayer | | workspaces | IWorkspaceService, WorkspaceService | makeWorkspaceService | workspaceServiceLayerWithoutDependencies | workspaceServiceLayer | | worktrees | IWorktreeService, WorktreeService | makeWorktreeService | worktreeServiceLayerWithoutDependencies | worktreeServiceLayer | | tabs | ITabService, TabService | makeTabService | tabServiceLayerWithoutDependencies | tabServiceLayer | | panes | IPaneService, PaneService | makePaneService | paneServiceLayerWithoutDependencies | paneServiceLayer | | layouts | ILayoutService, LayoutService | makeLayoutService | layoutServiceLayerWithoutDependencies | layoutServiceLayer | | agents | IAgentService, AgentService | makeAgentService | agentServiceLayerWithoutDependencies | agentServiceLayer | | events | IEventService, EventService | makeEventService | eventServiceLayerWithoutDependencies | eventServiceLayer | | integrations | IIntegrationService, IntegrationService | makeIntegrationService | integrationServiceLayerWithoutDependencies | integrationServiceLayer | | plugins | IPluginService, PluginService | makePluginService | pluginServiceLayerWithoutDependencies | pluginServiceLayer | | popups | IPopupService, PopupService | makePopupService | popupServiceLayerWithoutDependencies | popupServiceLayer |

Complete namespace reference

Every operation below is available from yield* HerdrSdk. Resource identifiers are branded values constructed through herdr.ids or returned by another SDK operation.

server

| Operation | Result and behavior | | ----------------------------------- | ------------------------------------------------------------------------ | | ping(options?) | Returns PingResult and verifies protocol compatibility. | | stop(options?) | Requests a graceful server stop. | | liveHandoff(input?, options?) | Hands the running server to a compatible executable. | | reloadConfig(options?) | Reloads server configuration and returns ConfigReloadResult. | | getAgentManifests(options?) | Returns current AgentManifestStatus. | | reloadAgentManifests(options?) | Refreshes and returns all AgentManifest values. | | registerSshAgent(input, options?) | Registers a remote-host SSH agent socket until the current scope closes. |

registerSshAgent holds its API connection open for the lifetime of the caller's Scope; closing the scope ends the registration. The returned ServerSshAgentLease.closed completes if the server ends it first. Only Unix servers support it: check capabilities.sshAgentRegistration from ping() before registering.

session

| Operation | Result and behavior | | -------------------- | ------------------------------------------------------------------------------------------------- | | snapshot(options?) | Returns one consistent SessionSnapshot containing resources, focus, agents, and protocol state. |

notifications

| Operation | Result and behavior | | ----------------------- | ------------------------------------------------------------------------ | | show(input, options?) | Displays a foreground notification and returns NotificationShowResult. |

client.windowTitle

| Operation | Result and behavior | | ---------------------- | --------------------------------------------------- | | set(title, options?) | Sets a persistent foreground-client title override. | | clear(options?) | Clears the title override. |

workspaces

| Operation | Result and behavior | | ---------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------- | | create(input?, options?) | Uses the server's default directory policy and atomically returns a workspace, initial tab, and root pane. | | createInDirectory(cwd, input?, options?) | Creates in an explicit absolute directory. | | createFromWorkspace(sourceWorkspaceId, input?, options?) | Uses the source workspace's directory policy; does not clone its topology. | | list(options?) | Lists workspaces in display order. | | get(id, options?) | Reads one workspace. | | focus(id, options?) | Focuses and returns one workspace. | | rename(id, label, options?) | Replaces a workspace label. | | move(id, input, options?) | Moves a workspace to an insertion index. | | moveBlock(ids, input?, options?) | Moves a contiguous workspace block before an optional anchor. | | reportMetadata(id, input, options?) | Replaces or removes source-owned metadata tokens. | | close(id, options?) | Closes one workspace. | | closeGroup(id, options?) | Closes the contiguous group containing a workspace. |

Creation input is WorkspaceCreateOptionsEncoded (focus, label, env). Directory intent is selected by the method, not a public tagged union. The former create({ cwd }) input is removed; use createInDirectory(cwd).

worktrees

| Operation | Result and behavior | | --------------------------------------- | ----------------------------------------------------------------- | | list(input?, options?) | Resolves a trusted repository source and lists its Git worktrees. | | create(input, options?) | Creates a worktree plus its workspace, tab, and root pane. | | open(input, options?) | Opens an existing worktree by path or branch. | | remove(workspaceId, input?, options?) | Removes the worktree associated with a workspace. |

Worktree operations require trustRepository: true before Herdr executes Git commands for a caller-supplied directory.

tabs

| Operation | Result and behavior | | ----------------------------- | -------------------------------------------- | | create(input?, options?) | Creates a tab and root pane together. | | list(input?, options?) | Lists tabs, optionally within one workspace. | | get(id, options?) | Reads one tab. | | focus(id, options?) | Focuses and returns one tab. | | rename(id, label, options?) | Replaces a tab label. | | move(id, input, options?) | Moves a tab to an insertion index. | | close(id, options?) | Closes one tab. |

panes

| Operation | Result and behavior | | ------------------------------------------------ | --------------------------------------------------------------------------------------------------------- | | split(paneId, input, options?) | Splits a target or focused pane. | | swap(input, options?) | Swaps a pane by direction or explicit identifier. | | move(paneId, input, options?) | Moves a pane to a tab or new container. | | zoom(paneId?, input?, options?) | Toggles or sets pane zoom. | | layout(paneId?, options?) | Returns the containing PaneLayoutSnapshot. | | processInfo(paneId?, options?) | Returns foreground and process-tree information. | | neighbor(paneId, direction, options?) | Finds a directional neighbor. | | edges(paneId?, options?) | Reports which layout edges contain the pane. | | focusDirection(direction, input?, options?) | Moves focus directionally. | | resize(direction, input?, options?) | Resizes a pane by terminal cells. | | scroll(paneId, { offsetFromBottom }, options?) | Sets scroll offset and returns pane information. | | editScrollback(paneId, options?) | Opens the focused pane's scrollback in its configured editor. | | selection.read(paneId, input, options?) | Reads absolute-coordinate text with an optional content revision guard. | | copyMotion(paneId, input, options?) | Computes a cursor position and content revision without typing input. | | copySearch(paneId, input, options?) | Searches an exact content revision; query limit is 4096 UTF-8 bytes, match window is at most 1024 ranges. | | link.resolve(paneId, input, options?) | Returns the viewport regions of the link under a cell without invoking any handler. | | link.activate(paneId, input, options?) | Resolves a visible link and invokes a matching plugin; does not open a browser. | | list(input?, options?) | Lists panes, optionally within a workspace. | | current(input?, options?) | Resolves the caller or foreground pane. | | get(id, options?) | Reads one pane. | | focus(id, options?) | Focuses and returns one pane. | | rename(id, label, options?) | Replaces or clears a pane label. | | setInputRouting(id, input, options?) | Chooses whether right-click belongs to Herdr or the pane application. | | sendText(id, text, options?) | Sends literal terminal text. | | sendKeys(id, keys, options?) | Sends named keys. | | sendInput(id, input, options?) | Sends text and named keys as one input operation. | | read(id, input, options?) | Reads visible, recent, or recent-unwrapped output. | | waitForOutput(id, input, options?) | Waits server-side for a substring or regular-expression match. | | reportAgent(id, input, options?) | Reports source-owned agent state. | | reportAgentSession(id, input, options?) | Attaches an upstream agent-session reference. | | reportMetadata(id, input, options?) | Replaces or removes source-owned pane metadata. | | clearAgentAuthority(id, input?, options?) | Clears agent-report authority, optionally by version. | | releaseAgent(id, input, options?) | Releases one source-owned agent report. | | clear(id, options?) | Clears the pane's terminal screen. | | close(id, options?) | Closes one pane. |

Selection/copy/link operations preserve stale_content and do not retry coordinates against new text. Copy search's total can exceed matches.length. Content revisions are distinct from endpoint projection and surface revisions.

reportMetadata controls presentation, not agent lifecycle. For example:

const describeTask = Effect.gen(function* () {
  const herdr = yield* HerdrSdk;
  const pane = yield* herdr.panes.current();
  yield* herdr.panes.reportMetadata(pane.id, {
    source: "my-tool",
    title: "Fix login tests",
    stateLabels: { working: "Running tests", blocked: "Needs review" },
    tokens: { model: "my-model", ticket: "APP-123", oldToken: null },
  });
});
  • stateLabels accepts any subset of idle, working, blocked, done, and unknown. Unknown keys fail rather than being silently removed.
  • Ordinary reports replace that source's title, displayed agent name, and label table; omitted previous presentation fields do not survive. Explicit clear operations are separate.
  • Tokens update individually; null removes a token. A pane or workspace token patch accepts at most 16 entries, with names matching ^[A-Za-z0-9_-]{1,32}$.
  • Use reportAgent for lifecycle state consumed by waits and notifications. Custom tokens such as $model need corresponding sidebar configuration to appear there.

layouts

| Operation | Result and behavior | | ---------------------------------------- | -------------------------------------------------------- | | export(target?, options?) | Exports one tab as a recursive LayoutDescription. | | apply(input, options?) | Creates or replaces a tab from a recursive LayoutNode. | | setSplitRatio(target, input, options?) | Updates a split addressed by a boolean tree path. |

agents

| Operation | Result and behavior | | ---------------------------------- | -------------------------------------------------------------- | | list(options?) | Lists every detected or SDK-launched agent. | | get(target, options?) | Reads an agent by pane or assigned name. | | read(target, input, options?) | Reads terminal output through an agent target. | | explain(target, options?) | Returns schema-less detection diagnostics as HerdrJsonValue. | | sendKeys(target, keys, options?) | Sends named keys to an agent. | | rename(target, name, options?) | Assigns or clears a stable agent name. | | focus(target, options?) | Focuses an agent pane. | | start(input, options?) | Launches a named agent and returns its command line. | | prompt(target, input, options?) | Sends a prompt with an optional server-owned wait policy. | | wait(target, input?, options?) | Waits for requested lifecycle states. |

Agent targets accept an explicit kind and exactly one selector:

const byPane = { kind: "pane", paneId: "pane-1" } as const;
const byName = { kind: "agent", name: "worker" } as const;

Existing { paneId: "pane-1" } and { name: "worker" } calls still work; parsing supplies kind. Supplying both selectors, or a kind that disagrees with its selector, fails in types and at runtime—even if one selector is invalid. kind is SDK-only; wire requests still send one target. Layout targets likewise select either a tab or pane, and pane swaps select either a direction or a source/target pane pair.

HerdrJsonValue accepts JSON values only: nonfinite numbers and class instances such as Date are rejected. Parsed layout ratios and request deadlines retain their validated types; encoded inputs still accept ordinary numbers and Effect durations. When manually constructing parsed values, use HerdrSplitRatio.make(0.5) or HerdrRequestDeadline.make(Duration.seconds(5)) instead of assigning an unchecked scalar.

Protocol-22 prompt success acknowledges ordered text and Enter writes, not a new turn. A non-working prompt with wait requires observed working/blocked activity before a settled state can satisfy it; the server can return agent_prompt_stalled. Timeouts/stalls do not prove non-delivery. The SDK neither duplicates this state machine nor retries prompts. API seen state and each TUI client's Done badge may differ.

agents.view

| Operation | Result and behavior | | ------------------------- | -------------------------------------------------------------------------------------------------------- | | set(input, options?) | Activates or updates the persistent foreground agent view with recursive filters and ordered sort terms. | | clear(input?, options?) | Clears the view, optionally only when owned by one source. |

events

| Operation | Result and behavior | | ------------------------------- | -------------------------------------------------------------------------------------------- | | subscribe(specs, options?) | Returns a cold, live-only typed Stream; acquisition acceptance defines the sequence start. | | wait(match, input?, options?) | Waits for one matching event and preserves its match-specific result type. |

Literal subscription tuples narrow the emitted event union:

import { Effect, Stream } from "effect";
import { HerdrSdk } from "@timmo001/effect-herdr";

const monitor = Effect.gen(function* () {
  const herdr = yield* HerdrSdk;
  const pane = yield* herdr.panes.current();

  yield* herdr.events
    .subscribe([
      { type: "workspace.created" },
      { type: "pane.agent_status_changed", paneId: pane.id },
    ] as const)
    .pipe(Stream.runForEach((event) => Effect.logInfo(event.type)));
});

Subscriptions are live-only rather than replay streams. Each execution captures its selection when parsing begins; later caller mutation cannot change that execution's request or event filter. One-shot waits follow the same rule. Interrupting the consumer releases the socket.

integrations

| Operation | Result and behavior | | ----------------------------- | ------------------------------------------------------------------------------------------------ | | list(options?) | Returns executable availability and notInstalled, current, or outdated installation state. | | install(target, options?) | Installs a built-in codex, qwen, or antigravity_cli integration. | | uninstall(target, options?) | Removes a built-in integration. |

plugins

| Operation | Result and behavior | | ------------------------ | ----------------------------------------------------- | | link(input, options?) | Links a plugin manifest from an absolute path. | | list(input?, options?) | Lists installed plugins, optionally selecting one ID. | | unlink(id, options?) | Unlinks one plugin. | | enable(id, options?) | Enables one plugin. | | disable(id, options?) | Disables one plugin. |

Nested plugin capabilities:

| Namespace and operation | Result and behavior | | ----------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------- | | plugins.actions.list(input?, options?) | Lists actions, optionally for one plugin. | | plugins.actions.invoke(id, input?, options?) | Invokes a qualified or plugin-scoped action. | | plugins.logs.list(input?, options?) | Lists recent plugin command logs. | | plugins.panes.open(pluginId, input, options?) | Opens popup, overlay, split, tab, or zoomed plugin UI; popup placement returns void, persistent placements return PluginPane. | | plugins.panes.focus(paneId, options?) | Focuses a plugin-owned pane. | | plugins.panes.close(paneId, options?) | Closes a plugin-owned pane. |

popups

| Operation | Result and behavior | | ----------------- | ----------------------------------- | | close(options?) | Closes the active foreground popup. |

Commands and endpoint notices

  • commands.invoke(commandId, options?): uses current server context.
  • commands.invokeInWorkspace(commandId, workspaceId, options?).
  • commands.invokeInTab(commandId, tabId, input?, options?): optional expectedWorkspaceId.
  • commands.invokeInPane(commandId, paneId, input?, options?): optional parent assertions and selection; the selection's pane ID is derived, not duplicated.
  • productAnnouncements.dismiss({ version, id }, options?).
  • releaseNotes.dismiss({ version }, options?).

Command IDs are opaque endpoint-issued identities, not shell text. All target methods share the command.invoke wire API. Stale identities and server failures remain errors, without retries.

clientShell

withConnection(input, use) owns the endpoint session for an Effect callback; callback success, failure, interruption, or terminal connection failure closes its resources. connectScoped(input) is the advanced explicit-Scope form. projections(input) is a cold, inactive connection-owning stream; each consumption opens its own connection.

const inspectShell = Effect.gen(function* () {
  const herdr = yield* HerdrSdk;
  return yield* herdr.clientShell.withConnection(
    {
      surface: { columns: 100, rows: 30 },
      cellPixels: { width: 8, height: 16 },
    },
    (shell) =>
      Effect.gen(function* () {
        const snapshot = yield* shell.snapshot();
        yield* shell.surface.set({ active: true });
        const surface = yield* shell.surface.awaitReady();
        return { workspaceCount: snapshot.workspaces.length, cells: surface.frame.cells.length };
      }),
  );
});

The connection uses generation-1 binary framing on herdr-client.sock, not newline-delimited API JSON. Its default path is alongside the configured API socket; an explicit session input socketPath overrides the client endpoint. The configured API socket must refer to the same server for the protocol-22 check. On Windows, endpoint paths use the same named-pipe translation as ordinary transport.

Connection input also accepts initialSurface (inactive by default), mouseCapture, endpointKeybindings, timeoutMs (10 seconds), healthIntervalMs (10 seconds), and maximumMessageBytes (32 MiB maximum/default). Surface and cell dimensions are explicit; the SDK does not inspect or resize the developer's terminal.

Connection operations:

  • snapshot(), projections: current metadata and coalesced replacement projections.
  • surface.set({ active }), surface.state(), surface.states, surface.awaitReady(), surface.resize(input).
  • input.paste(paneId, { text }), input.text(paneId, { text }), input.key(paneId, input), input.mouse(paneId, input); explicit popup-terminal variants are pasteInPopup, textInPopup, keyInPopup, mouseInPopup.
  • commands.invoke / invokeInWorkspace / invokeInTab / invokeInPane: same target intent as socket commands, but the first argument is a discovered ClientShellCommand from this connection's snapshot.
  • productAnnouncements.dismiss(input), releaseNotes.dismiss(input).
  • setFocused(boolean), setMouseCapture(boolean), connectionState(), awaitClosed().
  • events: live semantic notifications and host presentation instructions; the SDK does not execute clipboard, graphics escapes, sound, or window-title effects itself.

client_shell.surface.set is connection-local. An activation ACK establishes a projection floor, not delivered presentation; wait for surface.awaitReady() separately. Patches require an exact base and are applied before publishing complete surfaces. Graphics asset bytes are retained for coalesced scenes. direct_graphics is deliberately not negotiated: server-supplied graphics file paths are never opened. This SDK is an endpoint client, not a host TUI renderer or saved-SSH-machine manager.

Projection/state streams coalesce whole replacements. Presentation events are live-only with an eight-message backlog; overflow fails the connection rather than silently dropping effects or blocking health traffic. At most 32 requests are pending; each frame and the aggregate buffered response bytes are bounded by maximumMessageBytes, with at most 4096 chunks per response. Ambiguous endpoint write/request timeouts invalidate the connection; server rejections and missing advertised methods do not. No mutation is automatically retried. Handles and connection-bound streams must be used inside the owning callback/Scope; escaped handles fail after closure.

ids

ids is the root namespace for the exported herdrIds value. It is pure schema-owned data, not an Effect service, and provides synchronous constructors that reject invalid values:

| Constructor | Result | | --------------------- | ------------------- | | command(value) | HerdrCommandId | | workspace(value) | WorkspaceId | | tab(value) | TabId | | pane(value) | PaneId | | terminal(value) | TerminalId | | plugin(value) | PluginId | | pluginAction(value) | PluginActionId | | pluginLog(value) | PluginLogId | | agentName(value) | AgentName | | absolutePath(value) | HerdrAbsolutePath |

For untrusted input, prefer the Effect parsers: parseWorkspaceId, parseTabId, parsePaneId, parseTerminalId, parsePluginId, parsePluginActionId, parsePluginLogId, parseAgentName, parseHerdrAbsolutePath, parseHerdrSessionName, parseHerdrMilliseconds, parseHerdrUnixMilliseconds, parseHerdrUnixSeconds, parseHerdrRevision, parseHerdrStateChangeSequence, parseHerdrSplitRatio, parseHerdrMetadataTtl, parseHerdrInsertIndex, and parseHerdrPopupSize.

Schema and model catalog

Public data is schema-owned. A schema value such as Workspace also owns the corresponding Workspace TypeScript type. Operation inputs additionally expose ...Encoded interfaces when the ergonomic caller representation differs from the normalized type.

Protocol-22 interaction models are exported from herdr-pane-interaction-models.ts; endpoint projection, surface, lifecycle, and graphics schemas from herdr-client-shell-models.ts; semantic key/mouse inputs from herdr-client-shell-input.ts. Connection input and ownership interfaces belong to client-shell-service.ts. All are re-exported by @timmo001/effect-herdr; generated wire contracts remain private.

Domain primitives

HerdrEnvironment, HerdrMetadataTokens, HerdrMetadataTokenPatch, HerdrKeySequence, WorkspaceId, TabId, PaneId, TerminalId, PluginId, PluginActionId, PluginLogId, AgentName, HerdrAbsolutePath, HerdrSessionName, HerdrMilliseconds, HerdrUnixMilliseconds, HerdrUnixSeconds, HerdrRevision, HerdrStateChangeSequence, HerdrSplitRatio, HerdrMetadataTtl, HerdrInsertIndex, and HerdrPopupSize.

Server, client, and resource models

AgentStatus, ReportedAgentState, AgentSessionReferenceKind, AgentSessionReference, ServerCapabilities, PingResult, ConfigReloadResult, AgentManifest, AgentManifestStatus, NotificationShowResult, ClientWindowTitleResult, WorkspaceWorktree, Workspace, Tab, PaneScroll, Pane, Agent, and SessionSnapshot.

Workspace, server, notification, integration, worktree, and tab operations

WorkspaceCreateOptions, WorkspaceCreateResult, WorkspaceMetadataReportInput, WorkspaceMoveInput, WorkspaceMoveBlockInput, ServerLiveHandoffInput, ServerSshAgentRegisterInput, NotificationShowInput, IntegrationTarget, IntegrationChangeResult, WorktreeSourceInfo, Worktree, WorktreeListResult, WorktreeCreateResult, WorktreeOpenResult, WorktreeRemoveResult, WorktreeListInput, WorktreeCreateInput, WorktreeOpenInput, WorktreeRemoveInput, TabCreateInput, TabCreateResult, TabListInput, and TabMoveInput.

Pane, terminal, and geometry operations

PaneDirection, SplitDirection, PaneReadSource, PaneReadFormat, PaneLayoutRect, PaneLayoutSnapshot, PaneReadResult, PaneOutputMatchResult, PaneSwapResult, PaneMoveResult, PaneZoomResult, PaneProcess, PaneProcessInfo, PaneNeighborResult, PaneEdgesResult, PaneFocusDirectionResult, PaneResizeResult, PaneRightClickTarget, PaneInputRoutingInput, PaneSplitInput, PaneSwapInput, PaneMoveDestination, PaneMoveInput, PaneZoomMode, PaneZoomInput, PaneFocusDirectionInput, PaneResizeInput, PaneListInput, PaneCurrentInput, PaneReadInput, PaneInput, PaneOutputMatch, PaneWaitForOutputInput, PaneAgentReportInput, PaneAgentSessionReportInput, PaneMetadataReportInput, PaneClearAgentAuthorityInput, and PaneReleaseAgentInput.

Layouts

LayoutNode, LayoutTarget, LayoutApplyInput, LayoutSetSplitRatioInput, and LayoutDescription.

Agents and events

HerdrJsonValue, AgentTarget, AgentStartInput, AgentStartResult, AgentWaitInput, AgentPromptInput, AgentViewState, AgentViewField, AgentViewValue, AgentViewFilter, AgentViewSortField, AgentViewSort, AgentViewSetInput, AgentViewClearInput, HerdrEvent, EventForSubscription, EventForMatch, LifecycleSubscriptionType, EventSubscriptionSpec, EventMatch, and EventWaitInput.

Plugins

PluginPlatform, PluginActionContext, PluginPanePlacement, PluginSource, PluginManifestCommand, PluginManifestAction, PluginManifestEventHook, PluginManifestPane, PluginManifestLinkHandler, InstalledPlugin, PluginAction, PluginInvocationContext, PluginInvocationContextInput, PluginCommandLog, PluginActionInvocation, PluginPane, PluginSourceInput, PluginLinkInput, PluginFilterInput, PluginActionInvokeInput, PluginLogListInput, PluginPaneOpenInput, PluginUnlinkResult, and PluginPaneCloseResult.

Ergonomic encoded input types

Encoded interfaces describe the exact caller representation accepted before schema normalization:

WorkspaceCreateOptionsEncoded, WorkspaceMetadataReportInputEncoded, WorkspaceMoveInputEncoded, WorkspaceMoveBlockInputEncoded, ServerLiveHandoffInputEncoded, ServerSshAgentRegisterInputEncoded, NotificationShowInputEncoded, WorktreeListInputEncoded, WorktreeCreateInputEncoded, WorktreeOpenInputEncoded, WorktreeRemoveInputEncoded, TabCreateInputEncoded, TabListInputEncoded, TabMoveInputEncoded, PaneInputRoutingInputEncoded, PaneSplitInputEncoded, PaneSwapInputEncoded, PaneMoveInputEncoded, PaneZoomInputEncoded, PaneFocusDirectionInputEncoded, PaneResizeInputEncoded, PaneListInputEncoded, PaneCurrentInputEncoded, PaneReadInputEncoded, PaneInputEncoded, PaneWaitForOutputInputEncoded, PaneAgentReportInputEncoded, PaneAgentSessionReportInputEncoded, PaneMetadataReportInputEncoded, PaneClearAgentAuthorityInputEncoded, PaneReleaseAgentInputEncoded, LayoutTargetEncoded, LayoutApplyInputEncoded, LayoutSetSplitRatioInputEncoded, AgentTargetEncoded, AgentStartInputEncoded, AgentWaitInputEncoded, AgentPromptInputEncoded, AgentViewSetInputEncoded, AgentViewClearInputEncoded, EventSubscriptionSpecEncoded, EventMatchEncoded, EventWaitInputEncoded, PluginLinkInputEncoded, PluginFilterInputEncoded, PluginActionInvokeInputEncoded, PluginLogListInputEncoded, and PluginPaneOpenInputEncoded.

Errors

Expected failures remain values in the Effect error channel and are recoverable with Effect.catchTag or Effect.catchTags.

| Error | Meaning | | -------------------------- | -------------------------------------------------------------------------------------------------------- | | HerdrConfigurationError | Explicit or ambient configuration could not be decoded or resolved. | | HerdrInvalidInput | A public operation could not parse caller input. | | HerdrTransportError | Unix-socket connect, read, write, or premature-close failure. | | HerdrRequestTimeout | A local SDK deadline elapsed; the server outcome may be uncertain. | | HerdrInvalidResponse | Malformed, oversized, mismatched, missing, or schema-invalid server response. | | HerdrUnsupportedProtocol | Server and SDK protocol versions differ. | | HerdrUnsupportedResult | An operation returned an unsupported success discriminant. | | HerdrUnsupportedEvent | A stream or wait received an unsupported event discriminant. | | HerdrServerError | The server rejected a request, or ended a subscription (such as events_lost), with an open-code error. |

HerdrTransportRequestError is the common request error union. HerdrTransportMethodError<Method> adds method-specific result failures. EventOperationError adds HerdrUnsupportedEvent for event operations; ServerOperationError and WorkspaceOperationError name their namespace request unions.

const resilientPing = herdr.server
  .ping()
  .pipe(
    Effect.catchTag("HerdrUnsupportedProtocol", (error) =>
      Effect.logError(
        `Herdr protocol ${error.actualProtocol} is incompatible with ${error.supportedProtocol}`,
      ),
    ),
  );

Endpoint failures are separate typed errors: HerdrEndpointNegotiationError, HerdrEndpointTransportError, HerdrEndpointRequestTimeout, HerdrEndpointClosed, HerdrEndpointUnsupportedMethod, HerdrEndpointStaleReference, and HerdrEndpointInvalidMessage. Diagnostics classify the phase/reason without retaining frame bodies. HerdrEndpointConnectError and ClientShellOperationError are error unions, not wrapping classes.

Advanced transport API

HerdrTransport is public for infrastructure integrations that genuinely need raw protocol access. Its request method remains method-indexed, and openStream is limited to events.subscribe and server.ssh_agent.register. Most applications should use namespace services instead.

Transport exports are HerdrTransport, HerdrTransportRequestOptions, HerdrTransportRequestOptionsEncoded, HerdrTransportRequestError, HerdrTransportMethodError, herdrTransportLayerWithoutDependencies, and herdrTransportLayer.

Examples

The examples/ directory contains nine executable, type-checked programs. Start with the focused recipes, then explore the multi-agent idea lab, declarative command center, and attention-sorted agent rescue view.

bun run example -- examples/session-inventory.ts
bun run example -- examples/multi-agent-idea-lab.ts "Design a safer release workflow"

See examples/README.md for prerequisites, side effects, and the complete catalog.

Agent skill

The effect-herdr skill guides Effect integrations towards this SDK and its current documentation. Install it with:

npx skills add timmo001/effect-herdr

Development

Start with AGENTS.md, the architecture, and the agent workflow for task-to-file/test navigation, learning routes, verification, and subagent handoffs. OpenCode references provide read-only upstream source for agents; production code imports installed packages, never reference source.

Use the tool versions pinned in mise.toml and Bun for dependencies.

mise run check ::: build

mise run check lints with the shared @timmo001/oxlint-rules preset, typechecks source and .tst.ts inference contracts, runs the Vitest suites and checks Prettier formatting. Tests use isolated local fixtures, never live Herdr control. Live examples have explicit side effects and are not verification commands.

mise run generate rewrites the private wire contracts in src/generated from src/schema/herdr-api.schema.json; mise run generate:check fails when they drift. Generated wire contracts remain private; src/index.ts owns the public entrypoint.

Tooling drift

tooling.yml records the Herdr release the bundled schema was last reviewed against, and herdr.upstreamCommit in package.json the Herdr commit it came from. This tracks Herdr itself (herdrdev/herdr); the SDK fork's upstream is a different thing, handled by the Sync upstream workflow in FORK.md.

The daily Tooling drift workflow runs mise run tooling:publish, the tooling-drift tool pinned in mise.toml. It compares the reviewed release with the latest Herdr release and, when it moves, runs mise run tooling:refresh <tag> to bundle the API schema from that release and regenerate the wire types, then opens one pull request: [Pin-only] Update herdrdev/herdr to <tag> with squash auto-merge when the schema is unchanged, or Update herdrdev/herdr to <tag> for review when it changed. CI on that pull request shows where the SDK no longer matches the schema; a change here may also need one in the repositories that use the SDK.

Run the same update by hand with mise run tooling:update <tag>, which refreshes the schema through scripts/update-herdr-schema.mts, regenerates src/generated and records the tag; mise run tooling:status prints whether a newer release exists. The refresh reads GitHub through the GitHub CLI pinned in mise.toml, so it needs gh to be authenticated (gh auth login, or GH_TOKEN in automation).

Releases

To inspect the package locally, run mise exec -- npm pack --dry-run; the pack hook builds dist/. The package includes the ESM build, declarations, source, README, licence and package metadata.

For a release, set the package version in package.json and jsr.json, keep bun.lock in sync, and pass mise run check and mise run build. Commit and push the reviewed version, then publish a GitHub release with a tag matching the version exactly. The release workflow publishes to npm with provenance and to JSR using the shared timmo001/workflows publishers.

License

MIT