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

@gnldev/cli

v0.5.0

Published

gnl developer CLI: scaffold (init), hot-reload dev server (REST + Studio Playground), studio.

Downloads

315

Readme

@gnldev/cli

The gnl developer CLI. Three groups of commands, all wired straight to @gnldev/durable's own exports (reconstructState/forkRun/resumeRun/sweepRuns/purgeRun/getRunCost, plus the journal's own listRuns/readRun) — nothing here reimplements durability, it just puts it in your terminal. One runtime dependency (tsx, to load your gnl.config.ts): no commander/yargs/chalk/ora/inquirer — colors, tables and the checkbox are hand-rolled ANSI (see Supply-chain hygiene).

Install: npm create gnl@latest scaffolds a fresh project; pnpm add -D @gnldev/cli adds the CLI to an existing one. (Or run it from a repo clone: pnpm install && pnpm -r build, then node packages/cli/dist/cli.js <command>.)

npm i -g @gnldev/cli   # or: npx @gnldev/cli <command>

Project

| Command | What | |---|---| | gnl init [dir] | Scaffold a new project (mock model, no API key required). In an interactive terminal it opens one gate question — Recommended · Let me choose · Same as last time — and, if you choose, at most four more: what should happen when the same work arrives twice (--preset), whose runs these are (--identity), where the record of every run is kept (--store), and how people will reach it (--serving; choosing a server asks which framework, --host). Ends by printing the protections matrix it just configured. Run in an EXISTING project it writes only new files and prints the pnpm add line rather than editing your manifest. | | gnl init [dir] --features a,b,c | Names the feature files to write. It answers no QUESTION, so the gate still opens in a terminal — pair it with --yes for a silent run. An unknown id errors with the valid list (exit 1). | | gnl init [dir] --template minimal [--e2e] | The static starter. --e2e adds a durability test. The retired --template full is accepted and resolves to --features idempotency-tool,e2e. Same gate as above; --yes is what makes any of these silent. | | gnl init [dir] --preset ... --identity ... --store ... --serving ... | A flag answers its question, so that question is not asked. Values: assistant\|headless\|critical, internal\|end-users, sqlite\|pg, dev\|own\|mount. A misspelled value exits 1 rather than scaffolding an unprotected project. | | gnl init [dir] --yes | Every unanswered question takes its recommended default. The prompt also never opens without a TTY (stdin not a terminal → defaults), so CI and agents are safe. | | gnl add <idempotency-tool\|chat\|schedule\|job\|processors\|cache\|otel\|rag\|mcp\|memory\|workflow\|auth> | Add a feature recipe to an existing project: writes its file under src/ (never overwrites), adds the dependencies that file imports to your package.json, writes any .env.example lines it reads, adds a script when the recipe is a process (worker, scheduler), and prints the gnl.config.ts wiring (the config is decoupled — you edit the plain config object, no defineConfig). processors asks which guards you want; --only a,b answers that without a prompt. | | gnl add model <nvidia\|openai\|anthropic\|openai-compatible> | Write a real provider next to the mock (never over it), add the AI SDK provider dependency and its .env.example keys, and print the one import line to change. | | gnl add host <hono\|node\|express\|fastify\|koa\|nest> [--mount] | Give the project its own server entry, or (with --mount) print the measured lines that plug it into the server you already run. | | gnl dev [--config gnl.config.ts] [--host] [--allow-open-network] | Hot-reload dev server: REST API + Studio Playground on one port. Restarts when gnl.config.ts or src/ changes. | | gnl studio [--config ...] [--port 4747] [--host] [--allow-open-network] | Studio (inspector + Playground) standalone | | gnl doctor [--share] | What is protecting this project (the same matrix gnl dev prints, from describeProtections), plus two local stamps read out of the journal: the first run, the first time a duplicate guard actually refused something, and the gap between them. --share prints a copyable block with no names in it — no telemetry, no network call. |

gnl init my-agent --features idempotency-tool,e2e && cd my-agent && pnpm install && pnpm test   # proves idempotency

Templates

  • minimal (default, and the only one) — one agent, mock model, the journal you chose, gnl dev. The smallest thing that runs.
  • fullretired. It was a five-file fork of minimal that existed to add one tool and one test, and it drifted from the original in three separate places before anyone noticed. The name still works and produces the same project: it now resolves to --features idempotency-tool,e2e — a side-effecting chargeOrder tool with idempotency: 'args' (idempotencyKey: (a) => a.orderId), a mock model that actually calls it, and a test/e2e.test.ts reproducing the documented duplicate-toolCallId pattern end to end.

Where these listen

Both commands bind 127.0.0.1 — reachable only from your machine. They previously passed no hostname at all, which made Node bind every interface while the startup line said http://localhost:…; on a shared network that published an admin surface (run purge, managed-agent promote, cache invalidation, and a Playground that spends your API keys) to anyone who could reach the port.

To reach the server from elsewhere — a container, another machine — name the address:

gnl dev --host 0.0.0.0            # refused unless auth is configured
GNL_ADMIN_TOKEN=… gnl dev --host 0.0.0.0
gnl dev --host 0.0.0.0 --allow-open-network   # deliberately open, on a network you trust

Auth comes from gnl.config (auth: { admin: { token: … } }) or from GNL_ADMIN_TOKEN / GNL_VIEWER_TOKEN. gnl studio now reads it the same way gnl dev always has — it previously ignored auth entirely, so its admin API was open no matter what you had configured.

If you run gnl dev inside a container, add --host 0.0.0.0; without it the port is no longer reachable from the host.

Inspect (read-only)

These need a gnl.config.ts with storage (recommended, e.g. SqliteStorage) or a raw journal — same rule gnl dev/gnl studio already follow: no storage/journal configured → a clear error, nothing silent.

| Command | What | |---|---| | gnl runs [--status completed\|suspended] [--work-key <key>] [--limit N] [--json] | List runs: id · status · model steps · tool calls · cost · thread, newest first. --work-key matches the caller's declared name for the work EXACTLY (never a prefix) — the readable question an opaque run1_ id can no longer answer; a WORK KEY column appears when any run declared one | | gnl run <runId> [--raw] [--json] | A single run's timeline (materialized messages/tool-calls + cost). --raw prints the underlying journal entries instead. | | gnl inspect <runId> --step N [--json] | Time-travel in the terminal: the materialized state at journal entry N (reconstructState) — messages so far + any tool-calls still pending. N ranges 0..<entries for that run>; the command tells you the valid range if you're out of bounds. |

$ gnl runs --config gnl.config.ts
RUN ID    STATUS     MODEL STEPS  TOOL CALLS  COST (USD)  THREAD
order-9   completed  2            1           $0.0004     -
order-8   suspended  1            1           $0.0001     -

$ gnl inspect order-9 --step 1
order-9  step 1/3
  assistant  [{"type":"tool-call","toolCallId":"c","toolName":"charge","input":"{\"amount\":20}"}]
  pending (awaiting result): charge[c]

Operate (mutating)

| Command | What | |---|---| | gnl fork <runId> [--step N] [--to newRunId] [--json] | Differentiator: non-destructively copies a run's first N model steps (+ referenced tool results) into a new runId (forkRun) — continue LIVE from a past step instead of just looking at it. N defaults to the run's full length (a complete, independently-continuable copy). The source run is never touched. | | gnl resume <runId> --agent <name> [--approve id1,id2] [--deny id3] [--json] | Resume a suspended (Guard require-approval) or crashed run with a registered config.agents[name]resumeRun reads the original prompt/messages back from the journal itself, you only supply the agent + any approval decisions. | | gnl sweep [--older-than 30d] [--include-suspended] [--yes] [--json] | Retention sweep (sweepRuns): permanently deletes runs whose last activity is older than the threshold. Dry-run by default — prints what would be deleted; pass --yes (or --force) to actually delete. Suspended runs are kept unless --include-suspended is given. | | gnl pricing [list] \| set <model> --input <usd> --output <usd> [--cached <usd>] \| rm <model> \| test <model> --in <tok> --out <tok> | The price table maxCostUsd and organization spend limits read. DEFAULT_PRICING ships compiled into @gnldev/durable, so it is stale the day it ships — and a model missing from it prices at $0, which means a ceiling cannot fire at any threshold. set writes the journal's __pricing__ document, LAYERED over the shipped table so adding tomorrow's model cannot un-price gpt-4o. test prices a hypothetical run and names which entry answered: priceFor matches by longest prefix, so a whole model family can share one row and a wrong answer looks exactly like a right one. Writes are compare-and-set — a concurrent edit is reported, not swallowed. | | gnl rm <runId> [--yes] [--json] | Permanently delete one run and its sub-agent/network children (purgeRun). Refuses without confirmation: --yes skips the prompt, otherwise you get a y/N prompt in a TTY, and a hard refusal (exit 1) non-interactively — never a silent no-op. |

$ gnl resume order-8 --agent billing --approve call-42
order-8  →  completed  (agent: billing)
  Charged.

$ gnl sweep --older-than 30d
(dry-run — pass --yes to actually delete; older than 30d)
Would delete 3 run(s):
  order-1  (completed, last activity 2026-05-01T00:00:00.000Z)
  ...

Other

  • gnl --version / -v — installed version.
  • gnl help [command] — this help, or one command's usage.
  • gnl --help — grouped list (Project / Inspect / Operate) of every command actually implemented.
  • Unknown command → an error + a "did you mean" suggestion, exit code 1.
  • --json is supported everywhere it's useful (scriptable, stable-shaped output); errors always go to stderr with a non-zero exit code, never a silently-empty stdout.

Config

Every storage command accepts --config (default gnl.config.ts). The config is GENERATED from your answers rather than copied — see generateConfig for the shape (defineConfig / GnlDevConfig: a CreateGnlConfigjournal or storage, agents — plus dev server options like port/studio/auth).

Programmatic surface

Two entry points, split by what they need installed.

@gnldev/clidefineConfig/loadConfig, scaffold/generateConfig, the RECIPES feature list, the checkbox prompt reducer, and commands/commandList: the same Command registry cli.ts dispatches through, for embedding/tests that want to drive a command's run() without spawning the gnl binary. Nothing here names an optional peer, so it type-checks in a project that has only @gnldev/durable — including under skipLibCheck: false.

@gnldev/cli/devbuildDevApp/serveDev/loadDevRuntime/resolveAuthProvider and the load* runtime resolvers. Everything here is typed against the optional peers (@gnldev/durable, server, studio, studio/ai, memory, auth, hono, @hono/node-server), because it exists to boot them from the target project. Importing this subpath means you are running the dev server and therefore have them.

These used to be one entry, which put those peer type references into the program of anyone importing the package at all: import { scaffold } from '@gnldev/cli' in a project with none of them installed produced 16 TS2307: Cannot find module errors. Nothing was removed — the dev half moved.

License

Apache-2.0 — see LICENSE.