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

@turns/dev

v0.0.22

Published

Upload local AI agent sessions to turns.dev

Readme

Turns CLI

turns uploads native agent transcripts directly from this machine to the current user's Durable Object on turns.dev. Transcript rows are secret-filtered on the machine immediately before the WebSocket boundary; local transcript files are never rewritten.

Install the CLI with Node.js 22.18 or newer:

npm install --global @turns/dev

Commands

turns                         watch and stream sessions for the current directory
turns upload [directory]      watch and stream sessions for one directory
turns --once [directory]      upload that directory's sessions once and exit
turns install                 install the all-directory user daemon
turns status                  report daemon state
turns uninstall               remove the daemon
turns login                   replace saved credentials through the browser
turns logout                  remove saved credentials

turns and turns upload DIRECTORY are the daemon scoped to one directory tree: they keep watching, stream live rows as the harness writes them, and serve prompts queued from the web until interrupted. That directory is the allowlist, so sessions in its subdirectories are followed and agent launches cannot start outside it. Run turns install instead for an unattended all-directory daemon; while that daemon is active a foreground run declines to start a second uploader, since both would write the same upload-cursor file.

--once sweeps the same directory tree and exits as soon as its sessions are current, for scripts and CI steps that need an upload to finish rather than a process to stay resident. A run narrowed to a directory — watching or --once — never prunes saved upload cursors, so it cannot make the sessions it filtered out replay from their first byte the next time the daemon starts.

Native background tasks retain their detached agent anchor after the foreground prompt finishes, including while the daemon client is disconnected. Codex child agents, Claude/Letta tasks, ACP task tools, Prime/OMP jobs, and DeepSeek jobs suspend the normal five-minute idle cleanup. Follow-up prompts remain available; the anchor starts a fresh idle countdown after the last known task finishes or its transport exits. Explicit shutdown, interruption, transport replacement, and turn timeouts retain their existing semantics.

Retention uses native lifecycle events and task-tool results. Assistant prose, a completed launch tool, or a failed status query does not prove that a task has finished. Copilot, Hermes, and OpenCode captures lack reliable terminal evidence in their live streams, so their anchors can stay resident until a later native status result, transport exit, or explicit shutdown. No time-based fallback kills quiet tasks. One-shot harnesses such as Muse and Antigravity already remain foreground-running until their native process exits; there is no warm child process for idle cleanup to kill afterward.

The server is selected with --server URL or TURNS_ORIGIN. Its normalized origin is saved and reused by later invocations and daemon starts. On Linux, Turns uses the XDG config/state directories and installs this user service:

  • ~/.config/turns/machine-id
  • ~/.local/state/turns/native-upload-state.json
  • ~/.local/state/turns/letta-sessions/*.json (Letta conversation-to-project associations)
  • ~/.config/systemd/user/turns.service

On macOS, credentials and daemon state live under ~/Library/Application Support/turns/, and turns install creates the per-user LaunchAgent ~/Library/LaunchAgents/dev.turns.cli.plist. Its output is written to ~/Library/Application Support/turns/daemon.log.

The credential file contains a seven-day access token and a signed refresh token. The CLI refreshes the access token at least once every 24 hours. A new machine opens a one-time turns.dev URL, waits on an authentication WebSocket, and saves the resulting pair with mode 0600. Set TURNS_NO_BROWSER=1 to only print the URL.

Rows are scanned on their way out for substrings that look like file paths — out/chart.png, `src/app.tsx`, ~/shots/a.png — and each one the filesystem confirms is recorded on the session's own log as a resolution: the substring, the absolute path, the size, and whether the contents were sent. Paths resolve against the session's working directory and are canonicalised before the --directory prefixes are enforced, so a symlink out of an allowed tree is not a way to read the disk.

Only images under 10 MB are uploaded, to PUT /api/file/{machine}/{path}. Everything else is recorded as a name, so a reader learns what a response was pointing at without the machine shipping source files. Nothing is claimed until the filesystem confirms it, which is what bounds a deliberately broad matcher: prose that merely looks like a path resolves to nothing and is never mentioned to the server, and no directory is ever scanned.

An explicit file browse may upload up to 2 GiB to the same private R2 prefix. Files over 100 MiB use retryable 32 MiB multipart requests and report upload progress to the requesting browser over the control WebSocket. The daemon streams the file from disk and the Worker hands that request stream straight to R2, so neither process retains the complete file in memory.

Range-based viewers can bypass that upload. A capable daemon advertises live file ranges, validates the path through the same allowlist, and streams a bounded window as ordered 32 KiB WebSocket chunks. The browser hex view pages 64 KiB viewport windows, including arbitrary offset jumps.

The systemd unit uses KillMode=process, and the macOS LaunchAgent uses AbandonProcessGroup: restarting the uploader leaves its detached agent-anchor processes running so in-flight harness turns continue. For a foreground daemon restricted to one or more directory trees, repeat --directory. Both transcript discovery and agent launches enforce the canonical prefix, including protection against symlinks that escape it:

turns daemon run --server https://turns.example --directory /srv/agents

Against local Wrangler development, use the configured DEV_USER directly:

turns daemon run --server http://127.0.0.1:8787 \
  --dev-user antimatter15 --directory /tmp/turns-driver-workspaces

Agent memory isolation and prompt recovery

On Linux with systemd, each newly launched anchor runs in its own turns-agent-*.scope, including all of its harness and tool subprocesses. The installer also writes turns-agents.slice, which contains these scopes and enables systemd-oomd pressure monitoring at 40%. This targets an individual agent scope before pressure reaches the user manager's usual 50% threshold. Foreground launches still get individual scopes, but the parent slice's pressure policy requires running turns install. OOMPolicy=kill terminates that entire scope when a member is OOM-killed; other anchors and the uploader live in different cgroups. Turns imposes no per-agent RAM or swap limit: workloads share available host resources, and pressure handling selects an individual scope for termination. Scope creation requires an accessible systemd user manager, cgroup v2 memory control, and systemd 254 or newer. A launch failure is reported instead of silently running without isolation. macOS and Linux without systemd retain detached process launching.

The Linux service records the installation PATH. Executable discovery and anchor environments also add the CLI's Node directory, ~/.local/bin, ~/bin, ~/.npm-global/bin, NPM_CONFIG_PREFIX/bin when configured, and standard system/Homebrew directories. Explicit PATH entries retain priority; TURNS_<HARNESS>_BIN overrides and macOS app-bundled Codex preference remain in effect. Empty and relative PATH entries are excluded. Keeping Node on the anchor PATH also supports CLI wrappers using #!/usr/bin/env node.

Queue dispatch now distinguishes optimistic transcript acknowledgement from durable delivery. New daemons request dispatchTracking when claiming and wait for queue.renew with dispatchStarted: true to be acknowledged before sending to the local anchor. The backend stores pending or started in prompt_queue.dispatch_state. A failure, lost anchor, or expired lease consumes a started queue entry; its transcript remains, and a user can submit a new follow-up to continue. Unsent prompts retain the existing paused/retry behavior. Surviving anchors retain their claims. A crash between the durable marker and local delivery is deliberately treated as uncertain delivery and is not replayed automatically.

Deploy the backend before upgrading daemons. User DO schema version 20 adds dispatch_state without changing existing columns. Existing rows and claims from older daemons use legacy: acknowledged legacy claims are conservatively treated as started, since old daemons did not record a separate delivery boundary. This can consume a legacy optimistic-but-unsent prompt on failure; it prevents replay of work with unknown delivery state. New daemons refuse to send against backends that do not advertise dispatch tracking. Downgrade new daemons and drain active claims before rolling back the backend; an old backend ignores the new column and cannot provide the no-replay guarantee. Reinstall the Linux service with turns install after upgrading to persist its PATH. Existing warm anchors keep their old executable, environment, and cgroup until they exit; isolation applies to newly launched anchors. Previously requeued prompts are not automatically deleted by this migration.