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

@naswerks/periscope

v1.2.0

Published

Self-hosted runner for agent sessions: dials out to your controller, then spawns, gates, observes, prompts and streams.

Downloads

718

Readme

Periscope

npm ci licence: MIT node >= 22

Self-hosted runner for agent sessions: dials out to your controller, then spawns, gates, observes, prompts and streams. It runs where the code is; the controller runs wherever you put it.

npm install -g @naswerks/periscope     # the host, on the machine that runs sessions
npm install @naswerks/periscope        # the library: wire types, codec, and the host as a module

The process on your machine dials out to the controller and takes its orders from there. What it hosts is a live conversation you can steer mid-turn, not a job you collect at the end. It opens no port and carries no opinion about what a session means: it emits what happened, and the controller decides what to do about it. The agent it hosts is Claude Code, through the Claude Agent SDK, behind a seam whose own vocabulary names no agent; the agent's hook and message names ride the wire as cause.event, and the transcript locator is claude-transcript:.

Run a host

You have a controller's address and a pair code from whoever runs it.

npm install -g @naswerks/periscope
npm install -g @anthropic-ai/claude-code && claude auth login
periscope pair <code> --controller https://controller.example --label "build box"
periscope

The second line signs the agent in, once, as the OS user that runs the host (claude auth status says whether it already is): the host runs the Claude Code CLI headless, and it authenticates through the credential under ~/.claude that claude auth login writes. ANTHROPIC_API_KEY in your shell is not inherited by a session.

The third line answers with the host's new id and writes the credential and the two addresses the host dials, so nothing else needs setting:

paired as ph-8cb226ae…; credential written to ~/.periscope/paired-credential.json
this host now dials with the paired credential - the sign-in token expiry no longer applies to it
PERISCOPE_CONTROLLER_URL and PERISCOPE_DECISION_URL written to the config file - serve needs nothing else

The fourth starts the host, in the foreground, until you stop it:

[host] periscope 1.1.0 · host ph-8cb226ae… (paired; configured build-box) · credential paired · workspace none · config file ~/.periscope/config.json
[credential] paired as ph-8cb226ae… - the paired credential is presented on every dial
[link] idle -> connecting (start_requested)
[link] connecting -> open (socket_connected)
[link] open -> accepted (hello_completed) — protocol v9

periscope status prints the same posture from any terminal and never dials. A controller on a development certificate (https://localhost:…) is refused by Node until Node is pointed at that certificate: configuration.

To give sessions a repository and a git worktree each, from the terminal or from the controller, to run under a supervisor, or to set anything by hand: configuration. To pair with a signed-in identity instead of a machine credential: identity.

Write a controller

A controller serves two transports: the WebSocket the host dials, and an HTTP endpoint the host POSTs each permission decision to. The second is easy to miss, because nothing on the wire announces it. @naswerks/periscope/protocol ships the wire types and the codec without anything that can reach a process or a disk; contracts/wire-vectors/, shipped in the package, is the same contract as bytes, for a controller in any language.

git clone https://github.com/naswerks/periscope && cd periscope && npm ci && npm run build
node examples/test-controller/serve.ts

That runs the reference controller and prints the exact periscope pair line that redeems a code against it. The wire protocol is the contract: the envelope, the sequence rules, the handshake, pairing, the decision endpoint's request and answer, and a checklist of what a controller owes. examples/ holds the smallest controller that completes a link (forty lines) and the reference.

How it works

One socket out, nothing in. The host opens a single WebSocket to the controller and keeps it alive: a heartbeat both ways, jittered backoff, a bounded offline queue. Frames carry commands and facts (every state transition the machine records rides the wire); bulk content leaves by an HTTP POST the controller asks for. Sequence numbers are dense per session per direction, so a reconnect replays exactly what was missed. protocol

The gate fails closed. There is no permission prompt: the host runs the agent headless, does not pass --dangerously-skip-permissions, and registers a PreToolUse hook on every session that is the only path to a yes. That is a stricter gate than the prompt, not a weaker one: path escapes, credential reads and unrecognised git verbs are refused locally before the controller is asked, everything else is the controller's decision, and no answer is a refusal. The gate is the host's one control; the rest is the controller's trust: it chooses the permission mode (bypassPermissions included), it can run a command at session start, set the agent's environment, remove worktrees and read transcripts. SECURITY.md states that reach exactly; read it before you install this. gate

Workspaces are the host's. A session runs in the directory the controller names, or in a plain directory per workspace key, or in a linked git worktree of a repository on its own branch, and the controller can list and release them over the wire. configuration

Identity is paired or signed in. A paired machine credential has no clock and dies only when the controller revokes it; a signed-in user's token goes through a generic OIDC client with no provider baked in. The agent's own sign-in is a separate credential. identity

The session is a state machine. Every message, hook and gate outcome is a recorded transition with a cause from a closed vocabulary, so an unattended night is readable afterwards. state machine

Does it work, and against what

npm test is a clean build then node --test over the compiled output; CI runs it on {ubuntu-latest, windows-latest} x node {22, 24} on every change, ratchets line coverage against coverage.floor, packs the tarball and installs it into an empty project, and runs mutation testing weekly. Both operating systems are load-bearing: Windows is where USERPROFILE, case-insensitive env matching and path handling are observable, and Linux is the only place POSIX file modes mean anything. The figure for a release is in CHANGELOG.md.

| | | | -------------------------------- | ------------------------------------- | | @anthropic-ai/claude-agent-sdk | 0.3.220, pinned exactly, no caret | | Claude Code CLI | 2.1.220 (bundled with that SDK) | | Node | 22 or later; CI proves 22 and 24 |

The SDK is pre-1.0, so the SHA-256 of its installed type definitions is kept in contracts/sdk.sha256 and CI fails on any drift; a bump cannot land without someone reading what changed. The package follows semantic versioning; the wire protocol version is a separate number with its own window (versioning).

Naming

Method and field naming follows the Agent Client Protocol (Zed Industries, Apache-2.0): session/new, session/prompt, session/cancel, session/update, its camelCase keys and snake_case discriminators. This is not ACP compatibility, and Periscope must not be described as ACP-compatible: ACP points the connection inbound, Periscope dials out, and every type here is written from scratch. Elsewhere the vocabulary is the SDK's own.

Documents

architecture · protocol · configuration · identity · gate · state machine · examples/ · SECURITY.md · CONTRIBUTING.md · CHANGELOG.md · licence MIT.