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

@miadi/factory-runner

v0.1.5

Published

Opens an agent session for each brief in the Miadi factory queue: the brief format the connector writes, queue delivery, tmux session opening, a launcher per provider and its session-start steps (Remote Control for Claude Code). Runs on Ubuntu and Termux,

Readme

@miadi/factory-runner

An approved run in the Miadi factory becomes a live agent session. Parts of the factory write a brief into the factory queue: the connector's approval door (@miadi/connector) for an approved run or work proposal, Miadi's review doors for Deep Diver passes. This package reads the queue, opens or reaches a tmux session for each brief, starts the agent the brief names, runs its session-start steps (Remote Control for Claude Code), and hands the brief over.

It is one installed package: no checkout, no path that names a host, a user or a personal workspace. The logic is here. The data it runs on lives in the factory's data folder. jgwill/Miadi#911.

npm install -g @miadi/factory-runner
miadi-factory-runner config          # the settings in use and where each came from
miadi-factory-runner deliver --dry   # what the next pass would do, moving nothing
miadi-factory-runner deliver         # one pass: what the systemd timer runs
miadi-factory-runner watch           # a pass every 300 s, for a host without systemd (Termux)

Requires Node.js 20 or newer and tmux.

The brief

One file in <factory folder>/queue/, holding one <input …> block:

<input to="new:connector-261101-9e7e2c1f" status="send" type="connector-run" record="rec:9e7e2c1f…" provider="claude" cwd="/srv/work">
the text the session reads
</input>

| attribute | meaning | |---|---| | to | the tmux session that receives the brief, or new:<name> to open a session with that name (1 to 80 of A-Z a-z 0-9 . _ -) | | status | send is delivered at once. ready is delivered only through a consumer that passes its own readiness check (deliverFile({ gate })). draft and consult are never delivered | | type | what wrote the brief: connector-run, deepdive, … | | record | the connector record the brief carries out | | cwd | the folder a new session opens in; default defaultCwd | | provider | the agent a new session starts: claude, codex, pi, copilot, or a launcher named in the config | | launcher | the exact command a new session starts; wins over provider | | model | the Claude Code model a new session starts on, passed as --model; one plain word. A brief naming one for another provider or for an existing session is refused and moved to queue/refused/ | | model_source | who chose it: request, jev, configured_fallback or cli_default, kept in the receipt | | provider_authorization, provider_choice, provider_policy | written by the connector for a provider that needs its requester's own choice: a type="connector-run" brief starts Codex only with person_choice and a provider_choice:<id>, or policy_selectable and a provider_policy:<id>. Codex reached any other way for such a brief (default provider, alias, launcher=) opens nothing (jgwill/Miadi#926) | | remote_control | 0 (or false, off, no) starts a new Claude Code session without --remote-control <name> |

Writers build briefs with formatBrief, and pass anything a requester wrote through text() (attribute values go through attr() inside formatBrief), so no field can close the tag or open another block:

import { formatBrief, text } from "@miadi/factory-runner/brief";

const brief = formatBrief(
  { to: "new:connector-261101-9e7e2c1f", status: "send", type: "connector-run", provider: "claude" },
  ["A bounded task requested through the Miadi connector.", "", "<task>", text(task), "</task>"],
);

parseBlocks reads blocks back and validateBrief checks one before the runner acts on it.

What a pass does

For each queue file, each block whose status is send:

  • to="<session>": once that session's Claude Code agent is idle and the pane guard (if configured) is clear, one line is typed asking the session to read the brief file whole first. The brief's words are kept exactly in <briefsDir>/brief-<yymmdd>-<key>.md, so one line never splits into several submissions.

  • to="new:<name>": a tmux session <name> opens in cwd. Once the shell shows its prompt, the launcher for the brief's provider is typed. For Claude Code the launch line carries --remote-control <name>, so the person can follow the session from claude.ai or the Claude app under the same name. The runner waits until the agent is idle and its own session record names a Remote Control session (bridgeSessionId), up to 60 s. Only then does it type the line, and it presses Enter until the agent turns busy or the transcript holds the line as a prompt. A line that a slash command took as its arguments is reported as not sent. If Remote Control never connects, nothing is typed and the brief stays in the queue with the reason. Other providers leave no record of taking a prompt: the runner waits up to settleSec for the session's steering adapter to announce itself in the pane, sends the line once, and records submitted: "unverified". The receipt's session_id and steering say which session the adapter named, or that none did, which is what the connector binds a mission to (@miadi/connector STEERING.md).

    Until 0.1.1 the runner typed /remote-control <name> into the input box. On 2026-11-04 its Enter was lost, the brief line was typed after it, and Claude Code ran /remote-control with the brief as its arguments, while the ledger recorded the brief as sent.

Each block is delivered once. The ledger (delivered.jsonl) is keyed by the block's identity (file name, opening tag without its status, first body line) and records where it went, the pane, the session id and the Remote Control session (remote_control.url). Then the file moves:

| folder | when | |---|---| | queue/delivered/ | every block in it was sent | | queue/malformed/ | it holds no <input> block | | queue/stale/ | it was held longer than staleAfterHours |

A file that cannot be delivered yet stays, and queue/status.json keeps when it was first held, how often, and why. Each move is one line on stdout. The connector reads these folders to report a run's state.

Settings

The factory folder is $MIADI_FACTORY_DIR, else /srv/miadi/factory. The config file is $MIADI_FACTORY_CONFIG, else <factory folder>/factory.json. Every setting below defaults inside the factory folder. An environment variable wins over the file, which wins over the default; miadi-factory-runner config shows each value with its source.

| setting | env | default | |---|---|---| | queue | (<factory folder>/queue) | /srv/miadi/factory/queue | | ledger | MIADI_FACTORY_LEDGER | <factory folder>/delivered.jsonl | | briefsDir | MIADI_FACTORY_BRIEFS | <factory folder>/briefs | | defaultCwd | MIADI_FACTORY_CWD | <factory folder> | | defaultProvider | MIADI_FACTORY_PROVIDER | claude | | launchers | | claude, codex, pi, copilot, each its own command | | remoteControl | MIADI_CONNECTOR_CLAUDE_REMOTE_CONTROL (0 turns it off) | true | | paneGuard | MIADI_FACTORY_PANE_GUARD | none: a command run with the pane id, exit 0 meaning nobody is typing | | staleAfterHours | | 24 | | readyTimeoutSec | | 180: how long a new Claude Code session has to reach an idle prompt |

Every path must be absolute. The one path outside the factory folder is Claude Code's own record of its sessions: $CLAUDE_CONFIG_DIR, else ~/.claude of the user the runner runs as. The runner only reads it (sessions/<pid>.json for idle or busy, projects/*/<session id>.jsonl for a submitted line). It must be the same user whose Claude Code the launcher starts.

The ledger line of a launch that names its model carries model: { requested, source, flag }, which is what the launch line asked for. It is written at submission and waits for nothing. Which model answered is in the session's own transcript, for a reader such as the connector's mission_status.

A launcher entry is { "command": "…", "agent": "claude" | "other", "steps": ["remote-control"], "settleSec": 10 }. An entry in the file is merged over the default of the same name, so { "command": "claudeyolochronicle" } keeps Claude Code's Remote Control step. The step appends --remote-control <name> to the command, so a Claude Code launcher must pass its arguments on to claude (the claudeyolo* aliases do). A new name adds a provider.

Example: gaia

/srv/miadi/factory/factory.json:

{
  "defaultCwd": "/a/src/Miadi-18",
  "launchers": {
    "claude": { "command": "claudeyolochronicle" }
  },
  "paneGuard": "/etc/claude-code/skills/dispatch-discipline/pane-write-guard.sh"
}

claudeyolochronicle is a shell alias, which works because the launcher is typed into the new session's interactive shell.

The connector and the runner must name the same factory folder: the connector writes <factory folder>/queue/, the runner reads it. On Termux, set MIADI_FACTORY_DIR.

Run it every 5 minutes (systemd user timer)

~/.config/systemd/user/miadi-factory-runner.service:

[Unit]
Description=Miadi factory runner: deliver the factory queue to agent sessions
Documentation=https://github.com/jgwill/Miadi/tree/main/packages/factory-runner

[Service]
Type=oneshot
Environment=MIADI_FACTORY_DIR=/srv/miadi/factory
# Optional: MIADI_CONNECTOR_CLAUDE_REMOTE_CONTROL=0 and other overrides, one KEY=VALUE per line.
EnvironmentFile=-/srv/miadi/factory/runner.env
ExecStart=/usr/bin/env miadi-factory-runner deliver

~/.config/systemd/user/miadi-factory-runner.timer:

[Unit]
Description=Every 5 minutes, deliver the Miadi factory queue

[Timer]
OnBootSec=3min
OnUnitActiveSec=5min
Unit=miadi-factory-runner.service

[Install]
WantedBy=timers.target
systemctl --user daemon-reload
systemctl --user enable --now miadi-factory-runner.timer
journalctl --user -u miadi-factory-runner.service   # the QUEUE lines of each pass

A user unit starts with a short PATH. When miadi-factory-runner or node is not in /usr/local/bin or /usr/bin (an nvm install, say), put the absolute path of each in the unit: ExecStart=<command -v node> <command -v miadi-factory-runner> deliver. The user running the timer needs write access to the factory folder and a tmux it can start sessions in.

A host without systemd (Termux) runs miadi-factory-runner watch --interval 300 in a tmux session of its own.

As a library

import { deliverFile, deliverQueue, loadConfig } from "@miadi/factory-runner";

const cfg = loadConfig();
await deliverQueue({ cfg });                                   // the queue, status "send" only
await deliverFile({ file, cfg, gate: myReadinessCheck });      // another source, with its own rule

A gate is (block) => ({ send, why, ...extra }), sync or async; what it adds goes into the ledger entry. openNew, deliverToExisting, resolveLauncher and remoteControlLine are exported for a consumer that opens sessions its own way.

Tests

npm test

They run with a fake tmux and a fake Claude Code session record, so they need neither.