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

@intentic/machine

v1.301.0

Published

The one agent that lives on a user's own device: lets an intentic sandbox work on it (outbound WebSocket, MCP tools, scopes enforced here) and keeps folders and ports mirrored with it (Mutagen over tunnel SSH)

Readme

@intentic/machine

The one agent that lives on a user's own device: it lets a sandbox's agent work on the machine, and keeps the machine's folders and ports mirrored with the sandbox — one binary, one resident loop, one logon entry.

It replaces two agents (@intentic/host and @intentic/sync) that shared everything about being installed — a resident loop, a pidfile, a login entry, an updater, a ~90 MB compiled binary — and nothing about what they did. Merging them halves what a machine downloads and keeps resident, and gives "is this machine's agent running" one answer instead of two halves of one.

Responsibilities

The device half (src/device/, the machine side of the host capability):

  • Dial each linked sandbox — one outbound WebSocket, enrollment token in the first frame, oRPC after that; the machine serves hostContract, so no port ever opens here. Where it dials is the shared resolver's answer (below), re-asked on every reconnect, so a sandbox on this very machine stays connected with its tunnel down.
  • Expose the tool surface (run_command, files, screenshot, describe; deliberately no delete — trash is recoverable) as MCP carried verbatim, so a machine can learn a tool without a daemon release.
  • Enforce the owner's scopes here, never in the sandbox, and append every call to an audit log that survives uninstall.
  • Manage this machine's sandboxes for a browser button or a model, under the sandboxes switch: list them with their share of the machine (one docker inspect per listing: memory and CPU caps, privileged, GPU, and who asked for each directive, the approved environment or the owner), start/stop/restart, run the ic flows (prepare, update, rebuild, rollback, reshape, remove) narrating their output line by line, and tail their logs. reshape changes a sandbox's share or privileges through ic sandbox reshape on the same switch as the swaps, because another reshape undoes it; its ask is a closed form (two caps, two switches) spelled into ic flags here, so nothing a browser or a model sends reaches docker as text. describe reports the docker engine's size beside the OS, which is the ceiling those caps are held to.
  • Keep each local sandbox's next update downloaded (src/device/auto-prepare.ts): a background tick runs ic sandbox prepare <slug> --auto every few hours, so the web app's update card offers a half-minute restart instead of minutes of pulling. On by default; the switch is intentic-machine device updates --off (cached in device.json). Nothing in any sandbox can start or steer the tick — a sandbox only learns the outcome through the staged marker ic writes, the way it always has.

The sync half (src/sync/, the machine side of desktop sync):

  • Enroll an SSH key, then drive Mutagen: bidirectional file sync of a local folder ↔ the sandbox's /work, plus a one-way backup of the sandbox's own state.

  • Serve the SSH transport itself on loopback (the sandbox's sshd reached over its HTTPS surface), mirror every workspace port onto this machine's localhost, and bridge git so commits appear in local clones.

  • Own the port-mirroring switch (sync mirror off|on, optionally --sandbox <id>): mirroring is the one thing here that writes to this device's localhost, so the flag lives on this side, survives a restart, and is read every tick. File sync, the state backup and the git bridge are untouched by it — the point is to stop the ports without unpairing the sandbox. The sandbox's Devices tab has a button for it, and that button runs this very command over the host capability, so the two can never disagree. The same is true of the other three verbs — sync pause, sync resume and sync uninstall --sandbox <id>.

    Both scopes are buttons, because both scopes are commands. With --sandbox these act on one pairing, and that is the switch under that pairing's own folder and ports in the tab. Run BARE they act on every sandbox this machine pairs, which is what they mean in a terminal and what "turn this off on this laptop" should mean on screen, so the machine's own row carries a File syncing and a Port mirroring switch that omit the flag. The tab never sends a bare sync uninstall: unpairing is the one verb here that a fresh one-liner is the only way back from, so the button always names the pairing it ends. A machine whose pairings disagree (one mirroring, one not — which the per-pairing switches exist to allow) is drawn as such rather than collapsed to one position, and offered both directions.

    And one port at a time (sync mirror ignore|unignore --sandbox <id> --port <n>). The switch above is all-or-nothing, and the case it has no answer for is the common one: a single number this machine already uses for something else — a Postgres on 5440 that the sandbox's compose file also publishes — loses the local bind correctly, every tick, forever. That is not a contest that resolves, and turning every port off to be rid of one standing notice is the wrong trade. An ignored port is skipped before the free-check, torn down at once if it was already up, and still REPORTED — as ignored, its own state — because the row it appears on is the only place the choice can be reversed. It lives here rather than in the sandbox for the same reason the switch above does: the conflict belongs to one machine's localhost, and the same sandbox goes on mirroring that port everywhere else.

  • Clear its own build output when it blocks a deletion (src/sync/residue.ts). Two-way-safe refuses to delete a directory holding content it never carried, so a node_modules this device built is enough to stop a directory the sandbox deleted from ever going away here. Nothing in that standoff is a disagreement — no edit, no second copy, nothing at stake — but it was reported as one ("created on this device"), and in a pnpm monorepo, where every package carries three ignored directories, an agent moving six packages produced six of them at once. The agent now reads Mutagen's own untracked kind, and where one side holds NOTHING but ignored content and the other deleted the directory, it removes the residue here and lets the deletion land. It is bounded by the thing it cannot get wrong: it removes only what the live session's own ignore list already excludes, re-read from disk rather than trusted from the report, and a single file sync would have carried takes the whole directory out of the class. sync clean does it on demand (the Devices tab's Clear build output button runs exactly that); sync autoheal off stops the watcher doing it unprompted. A conflict with two real copies is untouched by all of it.

  • Register Mutagen's daemon for login autostart — on Windows through the launcher stub, because Mutagen's own registration flashes a console window at every boot.

Shared (src/): the one resident loop (run), the merged status (and its --json envelope the desktop app's tray reads), self-upgrade with automatic rollback, the autostart spec, and where a sandbox's daemon is dialled (src/daemon-base.ts). A sandbox usually runs in a container on this very machine, which publishes its daemon on 127.0.0.1:<port derived from the sandbox id>, so every dial either half makes — the device socket, both enrollments, the sync transport, the ports poll, the machine report — tries that address first and the public URL last. A candidate is adopted only if the daemon's unauthenticated /health answers with the id we expected: a port is not a sandbox, and a token is what would be presented to whoever holds it. For the sync half the shortcut saves a multi-gigabyte Mutagen sync a trip out to the reachability edge and back to the same laptop. For the device half it is reachability itself: the socket used to dial the public URL and nothing else, so a sandbox whose tunnel was down read "offline" on its own Devices tab, with every button there gone, while this same process was polling it over loopback. The watcher re-probes a pairing sitting on the public URL each minute, so a container started later is promoted with no restart; the socket re-resolves on every reconnect.

The resident loop

intentic-machine run --foreground (what systemd, launchd and the Windows launcher stub run) serves both halves in one process (src/resident.ts):

  • The sync half re-reads its pairing list every tick; the device half's link list is fixed at startup, which is why setup restarts the loop (reconcileResidency) instead of poking it.
  • Unpairing one sandbox of several does not restart it (startResidentIfStopped): the sync half picks the drop up by itself, and this process holds the socket every sandbox reaches this machine over — bouncing it makes the sandbox that asked for the unpair watch its own device go offline.
  • The process exits — and takes the login entry with it — only when both halves have nothing to serve.
  • On a signal it exits 128+signal, never 0: a supervisor must restart what it did not stop, and the incident that bought that rule is written out in src/sync/mirror.ts.
  • It stamps the build it is running into its pidfile (pid boot build). Nothing else knows it: replacing the binary does not touch the process, so a machine can hold a current agent and keep serving a months-old one.
  • It stamps what each link's socket is doing into links.tick every five seconds, for the same reason: the only process that knows is this one and the only process that is asked is status, in another terminal. See Linked vs connected.

Installed vs running

Two facts, and every surface now carries both. agents.sync in the machine report is the file at bin/intentic-machine (src/installed.ts); watcher.build is the loop running from it. They drift whenever a binary lands without a restart — a card's one-liner re-run, a copy dropped in, an upgrade in one environment while the loop runs in another — and the gap used to be invisible: whichever process built the report stamped its own version into the one field, so the same machine answered its running build to the sandbox its loop posts to and its installed build to one reading over a host capability.

  • intentic-machine status says which is which, and the summary the tray reads leads with OLD BUILD RUNNING.
  • The Devices row says it beside the pid, with the two commands that close it.
  • intentic-machine upgrade restarts a loop that is behind the installed binary even when there is nothing to download, and after a swap it verifies that the loop which came up is the new build rather than that some process is alive — the check the old agent passed just as well as the new one.
  • A loop already on the installed build is never bounced, and a loop that is stopped is never started: run --stop is a thing people do on purpose.

Linked vs connected

The same gap one level down, on the command whose entire job is to answer "is my machine connected". device.json records the sandboxes this machine is meant to answer to and nothing whatever about whether it reaches any of them, so status printed connected as <id> for every line in it — including, on the machine this was written for, a sandbox whose host had been answering 502 for four hours while the loop retried it every 30 seconds and said so in a log nobody had been pointed at.

  • The loop's stamp is the answer: status prints connected, NOT connected (retrying) or NOT connected per link, and the summary the tray reads counts the links that are actually up (1 of 2 sandboxes connected).
  • The stamp ages on purpose, since it describes sockets held in a process that may be gone. Four ticks past the last write it is no answer at all — which is also what an agent too old to write one leaves behind — and both cases print linked as <id> with the reason in words. A link nothing can vouch for must not read like a healthy one; the permissions line beneath it had been hedged as "last pushed by the sandbox" for years for the same reason, and this line had not.

Key files

  • src/commands.ts — the CLI surface: device setup|uninstall|updates, sync setup|pause|resume|mirror|uninstall, shared run|status|version|upgrade|uninstall.
  • src/install.ts — what every setup runs first: self-update (then re-exec), PATH repair, the Windows launcher stub. Everything the install scripts used to decide, decided once here.
  • src/upgrade.tsupgrade: what is published, then download → probe → stop → swap → start, with a rollback behind every step, and a restart when the file is current but the loop is not.
  • src/installed.ts — which build the file at bin/intentic-machine is, as opposed to the one running: free while the two agree, one probe per swap after they stop.
  • src/daemon-base.ts — where a sandbox's daemon is dialled, for both halves: loopback first when /health proves it is ours, the public URL as the floor.
  • src/resident.ts — the one loop, its pidfile, its link stamp, and reconcileResidency.
  • src/device/auto-prepare.ts — the background update-download tick; the judgement about what to download stays in ic sandbox prepare --auto, on purpose.
  • src/status.ts — both halves as one answer; --json is what the desktop app and tray read.
  • src/wsl.ts — whether this is a WSL distro, and which one. WSL hands a distro the Windows machine's own hostname, so without this the sandbox cannot tell a distro from the Windows install hosting it, or one distro from its neighbour; reported at connect (describe) as well as in the status report, so it is known even with "Run commands" off.
  • src/device/tools/shell.tsrun_command, and its in: crossing: wsl:<distro> from Windows runs wsl.exe --exec sh -lc with the script as one argument, windows from a distro runs PowerShell through interop; no quoting through the first shell either way.
  • src/device/policy.ts — what the sandbox is permitted to do here; the security surface.
  • src/device/tools/sandboxes.ts — the fleet: the docker ps/inspect readers, the docker verbs, and the ic flows (swap, reshape, remove, runners) with the pure argv builders beside them.
  • src/sync/mirror.ts — the sync tick: ports reconcile, git bridge, revocation handling.
  • src/sync/mutagen.ts — driving the Mutagen binary, sessions and its daemon's autostart, and classifying a conflict as build output or as two real copies.
  • src/sync/residue.ts — what may be deleted here without asking, and the four separate refusals that keep it to exactly that.

How it fits

Runs on the user's machine, not in the sandbox. Installed by device.{sh,ps1} / sync.{sh,ps1} (both cards put the same binary in ~/.intentic/machine/bin), shipped as a bun-compiled binary per platform, self-updated by upgrade.

Those four installers are bootstrap shims: they download an agent onto a machine that has none (pinned to the tag releases/latest resolves to, resumable, probed by running version before it may become the agent) and exec setup. Every other decision — installed-vs-published, PATH repair, the Windows launcher stub — runs from src/install.ts at the top of every setup: it self-updates through the same download→probe→swap→rollback machinery as upgrade, then re-execs the new agent with the same argv, so re-running a card's command still upgrades a machine while the rule lives in exactly one compiled, tested place.

The one decision a shim cannot delegate is whether the installed agent can take the handover at all: device setup is itself part of this CLI's vocabulary, so an agent older than a route rename rejects it — and the self-update that would have replaced it lives behind that same command. Renaming computer to device did that to every machine paired before it. So each shim runs <route> setup --help on the installed binary first, and replaces an agent that cannot answer. The shims' bootstrap blocks are held identical per dialect by src/installers.test.ts, which also pins that probe, that the route is named once per file, and that no other decision creeps back into shell.

The install-and-stay-alive plumbing is @intentic/local-agent's; the windowless Windows logon start is _devices/win-launcher's.

Conventions & gotchas

  • Bidirectional sync has no undo. The integration tests here run against real directories and a real Mutagen for exactly that reason; a unit test that mocks the sync proves nothing about the case that loses work.
  • The two halves keep separate state files (device.json, sync.json) in the one home: the loop rewrites one half's state while a concurrent setup writes the other's, and separate files make cross-half torn writes impossible rather than unlikely.
  • Scopes are a cache. The sandbox pushes the real grant on every connect; what is stored only governs the seconds before the first push, and it starts at everything-off.
  • The transport lives in the resident loop, so sync setup starts the loop before it probes ssh or hands anything to Mutagen: every step after that one needs the port to be open.