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

worktrust

v0.10.14

Published

Couple this computer to WorkTrust: approve a code in your browser, and every AI app here reports what you built. Metadata only, no dependencies.

Readme

worktrust

Couple a computer to WorkTrust in one command. WorkTrust keeps a verified record of the work you do with AI without anyone reading that work: it receives metadata only.

npx worktrust@latest

The official npm package is worktrust, published by the npm account worktrustio with provenance from this repository (github.com/fhomey/worktrust-cli). Check it on npmjs.com before you run it; a package of another name or from another publisher is not ours. Without npm, the same script runs from WorkTrust's own site:

curl -fsSO https://app.worktrust.io/counter/worktrust.mjs && node worktrust.mjs

Python tool runners: this repository also builds worktrust for uvx and pipx from the same measurement modules. Native wheels (0.10.13+) include a pinned official Node runtime, so that route needs no separate Node/npm installation. See the Python distribution for supported platforms and installation commands, and the official PyPI project for published versions. npm and Python versions are checked together.

uvx worktrust@latest preserve
pipx run --no-cache worktrust preserve

Omit preserve to connect your computer. These runners install and invoke the same CLI; they do not automatically instrument Python applications or measure different activity.

Connecting through the Python package installs the runtime and engine in an immutable directory under ~/.worktrust/installations/; client hooks use that persistent path, so clearing a tool-runner cache does not break the coupling. Updates retain older installations for running clients. Run worktrust disconnect before removing an installed Python tool if you also want to remove its WorkTrust coupling. Disconnect removes the key and client wiring; it leaves runtime files in place.

Agent observations (requires 0.10.14+): the optional Python worktrust_cli.telemetry SDK lets an application explicitly record parent/child runs, its own token counters, token budgets, checks and security events in a local file. The shared engine inspects it with worktrust orchestration report --file agent-events.jsonl, without uploading. The private platform import is Sources → Agent teams when that platform change is deployed. These are application reports, not human hours or independently verified outcomes. Installing through npm, uvx or pipx alone does not instrument agents. See the Python README for the opt-in SDK, fixed metadata fields, consent and local deletion. Install worktrust>=0.10.14 in the agent application's own Python environment to import the SDK; a uvx/pipx tool environment is separate. Check the registry for availability: a GitHub release awaiting approval is not a completed npm or PyPI publication.

No account? Keep your history anyway. Claude Code and Gemini CLI delete their sessions after 30 days by default, and a deleted session can never be measured again. npx worktrust@latest preserve shows what each AI app on this computer keeps, keeps it longer on your yes, and writes a local, metadata-only record. It needs no account and opens no network connection: it stays on this computer until you yourself run a command that sends. See Keep your history.

For AI agents: read AGENTS.md. An agent does not run this CLI's commands for a person (they stop in an agent's shell), does not report work it did not see, and does not rebuild, fork or strip it.

What happens

  1. A plan, then a question. The command lists every AI app it found on this computer and the file each one gets changed, then asks Continue? [Y/n]. Nothing is written or sent before you answer. --dry-run shows the plan and stops.
  2. Approve this computer in your browser. WorkTrust opens; you see this computer's name, its system and the AI apps that will get the WorkTrust door, and approve it, signed in with your own account and second factor. That decides which WorkTrust account the computer belongs to: the terminal never sees a password and cannot choose an account. Nothing is typed: the browser is sent back to this computer (127.0.0.1), where the terminal waits for one answer and exchanges it with a secret only it holds (PKCE). A link forwarded to someone else approves nothing they can collect. Without a browser (over SSH, or --device), the terminal shows a code that you type yourself at app.worktrust.io/connect/computer on another device.
  3. The terminal finishes by itself. It keeps the key in its private file and gives every AI app here the WorkTrust door, then installs the session hook. Quit your AI apps and open them again.
  4. Earlier work only if you say yes. It counts your AI apps' sessions already on this computer and asks Send these as history? [y/N]. Enter sends nothing. What goes is hours and tokens per day, never text, and WorkTrust shows it as earlier work, never as verified hours. Nothing from before the coupling is ever sent without that yes: the session hook starts at the moment of coupling. Beside the seconds, each day says what they were (the model answering, tools running, your own turns, pauses) and what the session's subagents did on their own clocks, as counts; a subagent's hour is shown as an agent-hour and never as one of yours.
  5. Codex only if you say yes. With Codex on this computer it asks Let Codex wake the session hook? [y/N] and, on a yes, sets the one notify line in ~/.codex/config.toml (the file is kept as config.toml.worktrust-backup first). Another program's notify is never replaced.
  6. Antigravity only if you say yes. With Google Antigravity on this computer it asks Let Antigravity wake the session hook? [y/N] and, on a yes, adds one hook named worktrust with a single Stop command to ~/.gemini/config/hooks.json (kept as hooks.json.worktrust-backup first). Other hooks in that file stay as they are. Antigravity records no token counts, so its sessions arrive with durations and turn counts only.
  7. Hermes Agent with the other apps. With Hermes on this computer (~/.hermes, and every profile under ~/.hermes/profiles) its config.yaml gets the WorkTrust entry under mcp_servers and one on_session_end hook that wakes the session hook; Hermes itself asks once before it runs a new hook. A file that writes those sections inline is left as it is. The session hook reads Hermes's own state.db: clocks, roles, the model and the session's token totals, never a message. A chat app (Telegram, WhatsApp, Slack …) is sent as work steered from elsewhere; a subagent's, a scheduled job's or a board task's session is not counted. Needs Node 22.5 or newer.
  8. Goose, OpenCode and OpenClaw the same way. Goose gets the entry under extensions in ~/.config/goose/config.yaml and one plugin folder, ~/.agents/plugins/worktrust, whose SessionEnd hook wakes the session hook. OpenCode gets it under mcp in ~/.config/opencode/opencode.json (a file with comments is left as it is) and one plugin file, plugin/worktrust.js, that wakes the session hook when a session goes idle. OpenClaw is coupled through its own openclaw mcp set (and openclaw mcp unset on disconnect); without that command on this computer it is printed for you to run. Each is read from its own session database, the same way as Hermes: clocks, roles, models and token counts, never a message. OpenClaw's Talk sessions are sent as voice, its chat-app sessions as remote; subagents and scheduled runs are not counted.
  9. Cursor and GitHub Copilot in VS Code, read in place. Cursor's chats are read from its own database (User/globalStorage/state.vscdb), each message's type, clock, model and token counts through SQLite's own json_extract, never the text. Copilot's are read from VS Code's chatSessions files: each request's two clocks (asked, answered), its model and its token counts; the messages and responses are parsed with the file and never kept or sent. Neither app runs a hook WorkTrust can give it, so the WorkTrust door, which both start when they open, starts the session hook in the background, at most once an hour.
npx worktrust@latest history       send this computer's earlier sessions later (asks first)
npx worktrust@latest history --rebuild   re-measure them with the current rules; WorkTrust replaces this computer's
                                   earlier lines day by day, never counting a day twice (shows the plan, asks first)
npx worktrust@latest history --from-archive [dir]   send what your local archive holds of sessions your AI apps
                                   already deleted, marked as from your archive: a day this computer's key signed as
                                   measured, every other day as reconstructed (shows the plan, asks first)
npx worktrust@latest history --export <path>   a ChatGPT or Claude data export (the zip, its folder or conversations.json)
                                   as history: hours reconstructed from its clocks, never verified (shows the plan, asks first)
npx worktrust@latest history --from-folder <dir>   your own copy of an AI app's session folder (a backed-up ~/.claude/projects,
                                   ~/.codex/sessions): the days this computer's sessions lack, reconstructed (asks first)
npx worktrust codex         let Codex wake the session hook (asks first)
npx worktrust antigravity   let Antigravity wake the session hook (asks first)
npx worktrust@latest status        what is coupled here
npx worktrust@latest disconnect    take it out again (shows the plan, asks first)

Keep your history

Why. Your AI apps decide how long your history lives, and several delete it on their own schedule:

| App | Kept by default | preserve --apply | |---|---|---| | Claude Code | 30 days, then deleted without notice | sets cleanupPeriodDays to 3650 | | Gemini CLI | 30 days | turns general.sessionRetention off | | Hermes Agent | 90 days after a session ended | prints the line to add (sessions.auto_prune: false) | | Codex CLI | no deletion documented | nothing to change | | Cursor, Copilot Chat, OpenCode, Goose, LM Studio | not documented | left alone |

(From each vendor's own documentation, checked 6 October 2026. Copilot Chat also syncs its sessions to GitHub by default: chat.sessionSync.enabled.) A deleted session is a day of work that can never be shown again, by WorkTrust or by anyone.

Local, and yours. preserve works on this computer only. It needs no WorkTrust account and no key, and it opens no network connection; check-cli-package refuses the package if preserve.mjs so much as imports a network module. What it keeps stays here until you yourself run a command that sends: npx worktrust to couple this computer, then npx worktrust@latest history, which shows what it would send and asks [y/N] first. Without those two, nothing ever leaves.

npx worktrust@latest preserve                  every step: the report, then asks to keep the settings and a local archive
npx worktrust@latest preserve --apply          the settings step only: asks [y/N] per change, backs the file up first
npx worktrust@latest preserve --archive [dir]  a local record of the measured days (default ~/AI-Evidence)
npx worktrust@latest preserve --verify [dir]   check that record: was anything changed afterwards?
npx worktrust@latest preserve --summary [dir]  hours, tokens per model, cache share and parallel sessions, from that record

Always the newest, from any terminal or agent. Run it as npx worktrust@latest …: npx then asks npm for the newest version every time, where a bare npx worktrust may reuse a copy npm cached earlier. Which terminal or AI agent runs it makes no difference: preserve writes to your own user's places (the apps' settings files and ~/AI-Evidence), so every terminal and agent on this computer under your user reaches the same archive, and lines from different versions sit side by side, each naming the version that measured it. Another user on the same computer gets an archive of their own. A coupled computer's session hook runs from its own copy in ~/.worktrust and changes only with npx worktrust@latest update; the WorkTrust app says when a newer version is out.

One command walks it all. npx worktrust@latest preserve shows the report, asks Keep these settings? [y/N] (then does what --apply does), asks Keep a local archive in ~/AI-Evidence? [y/N] (then does what --archive does; an archive already there is simply added to), and with an archive prints its check and its summary. Enter is No. Without a terminal, and without --yes, it changes nothing. The flags each do one step, for scripts.

Later, into WorkTrust, if you want. Because --apply keeps the apps' own session files, coupling this computer months from now still finds them: npx worktrust@latest history measures them the same way the live hook does, and they arrive as history, labelled as such, never as work WorkTrust watched happen. The archive is a plain, documented format made for the same purpose; importing it directly is planned, not built.

What --archive keeps, per AI app per day. The same measurement the session hook makes, and nothing it does not:

| Field | What it is | |---|---| | client | the AI app (claude, codex, cursor, copilot, hermes …) | | day, started_at, ended_at | the day, and when the measured stretch began and ended | | seconds | measured time: every gap between stamped events up to five minutes | | model_seconds, tool_seconds, human_seconds, idle_seconds | what those seconds were: the model answering, tools or an agent run, your own turns, and what a cap cut off | | tokens_in, tokens_out, tokens_cache_read, tokens_cache_write | token counts, when the app records them (one answer counted once, however many lines the app wrote it in) | | agent_runs, agent_seconds, agent_peak | subagents: how many ran, their own time, the most open at once (never added to seconds) | | interrupts, steers | how often you stopped the agent mid-answer, and how often your message arrived while it was still mid-task (counts, never the words) | | utc_offset | your computer's offset from UTC when the stretch began, such as +02:00, so a working day reads in your own local time | | model | the model's name as the app reported it | | kind, layer, layers | the kind of work (built, changed, researched) and the area it touched (front end, back end, data …), as the session hook names them | | exchanges | how many times you and the model took turns | | commits | the hashes of commits made in the stretch, never a message: what ties AI time to work in the repository | | steered_from | remote when the session ran over SSH | | duration_basis, token_basis, turn_basis | how the seconds, tokens and turns were measured | | project | a one-way hash of the project's name, keyed with this computer's own secret (~/.worktrust/archive-salt, never in the archive) so a guessed name cannot be tested: which days belong together, never which project | | stretch_ref | a hash of the session's id and the day, so a stretch is archived once | | device_id, profile_id | which computer and which user profile on it, as one-way hashes (the machine's own id never appears) | | signals, analyzer_version | the behaviour signals of that stretch as keys and counts from the local counter's rubric (framing, steering, verification, recovery), and the rubric's version; derived on this computer from your own turns, never a word of them (0.7.0) | | verification, delivery | per kind of check run in the stretch (test, typecheck, lint, build, the project's gate, CI) how many ran and how many failed; per delivery step (commit, PR, push, deploy) how many succeeded, and whether a check had passed before the first; named from each command on this computer, never the command itself (0.7.1) | | recovery | of the tool calls that failed in the stretch, how many a later call of the same kind recovered, the middle time that took, how many were retried unchanged and how many with a different approach; inputs compared on this computer by digest, never kept (0.7.2) | | delegation | the longest and the middle chain of actions the agent took on its own between your turns (or your stopping it), and how often it stopped to ask you a question or to have a plan approved (0.7.3) | | context, routing | how often the context was compacted and which context commands you used (compact, clear, resume, context, model, memory; never a command of your own), how many models answered and how often the model changed (0.7.4) | | steering | the moments you stepped in while the agent worked (stopping it, or a message mid-task), how many changed what the agent did next, and how many were followed by a passing check (0.7.5) | | tools, complexity | how many different tools the agent used, and the stretch's task complexity C1 to C5 by the rule complexity/1 (points for layers touched, tools, subagents, duration, a recovered failure, kinds of check and a delivery; the rule is in stretch-evidence.mjs), with its points and the rule's version (0.7.6) | | oversight, planning, changes | how often you refused an action or a plan the agent proposed, and how often a guard (the client's safety layer) refused one; how often the agent wrote its to-do list, the most items, and of the last list how many were done (never what they say); how many files the stretch's commits changed and how many of them tests (never a path) (0.8.1) | | supplements | a supplement line: when a newer CLI measures more of a stretch already archived, the new fields go into a line of their own that names that stretch; nothing earlier is rewritten, and the chain and the day's root cover it (0.8.2) | | evidence (sent) | the same derived record now also travels with each stretch to WorkTrust, kinds and counts only, and npx worktrust status shows what this computer measured in the last thirty days (0.8.3) | | unconfirmed | checks the stretch ran whose outcome no reader could see (Codex's code mode prints no exit code; a background terminal), per kind: counted, never as a pass (0.8.5) | | autonomy, authorship | the permission mode the AI app ran in, on one scale (ask, edits, plan, auto, full), how often it changed and the turns in plan mode; of the stretch's commits, how many name an AI as co-author (the trailer is read here, its names never leave) (0.8.7) | | framing, quality, risk, tool_mix, reads, context_files | how the work was framed (your turns before the agent's first action, a plan first or not), checked (a check passing after the last change, the failures before a check passed, the change looked at before delivery; security scans count as checks), risked (destructive commands proposed, refused, run), which kinds of tools were used, files read again unchanged, and which of the AI app's own context files (instructions, skills, agents, commands, settings, MCP) the commits changed; kinds and counts only (0.8.8) | | quality.change_blocks, checked_blocks, rework_cycles, stage | how many change blocks (edits with no check between them) a check followed, how often a failed check was followed by another change before it ran again (rework), and the furthest step the stretch reached here: attempted, verified, committed, pushed, pr, deployed; counts and one word only (0.8.9) | | timing | how fast the stretch moved: seconds from your first turn to the first action and from the first change to the first check, the actions and seconds from a failure to the change that answered it, the calls from a failed check to it passing, and the calls back to work after a compaction; medians from the order and the clocks, never what was said (0.9.0) | | hygiene | risk and hygiene: rollbacks, privileged commands (sudo, a forced push), hooks skipped (--no-verify), secrets touched (a .env printed, a private key read), destructive commands and how many a look or a check came before, the answer to a guard (retried, changed, stopped), and whether a check followed delivery; counts only, never the command or the path (0.9.1) | | route | the route the work took: branches made, pushes straight to the default branch, and of the stretch's own commits those over twenty files, a fix that followed another, those naming an issue, and the changed CI, script and docs files; counts only, never a subject, a branch name or a path (0.9.2) | | adaptation | evidence and adaptation: what the stretch read by kind (docs, tests, logs, config, source, the web), a plan written again after a failure, a model changed right after a failure, two or more agents whose work a check met, and outages (overloaded, rate-limited, timed out) and how many the work went on past; counts only, never a path or a message (0.9.3) | | reach | the work's reach and its plan: commands on infrastructure (terraform, kubectl, helm, docker), data (psql, sqlite, bq, dbt) and cloud (aws, gcloud, az, supabase, vercel); plan or roadmap documents written and whether one came before the first code change; reviews approved, sent back or commented and issues opened or closed from the terminal; counts and one flag, never a command, a file name or a path (0.9.4) | | hygiene.secrets | counts a secret touched only when a file that holds one (a .env, a private key, credentials.json, .aws/credentials) is shown or copied (cat, head, tail, base64, cp); a search for a word (grep, sed, awk) is no read (0.9.5) | | hygiene.secrets | counts a secret shown only when a reader (cat, head, tail, base64, cp) takes the file as its own argument; a command that uses a .env and reads its output, and a template (.env.example), are no secret shown (0.9.6) | | outcomes | outcomes of the steps: per kind of check whether its first run passed, delivery attempts that failed, a model change and a new plan after a failure each followed by a passing check; and a check whose output goes through a pipe is read from its output's words (an error fails, a pass passes, a silent type check or lint passes, anything else is unconfirmed), never from the pipe's exit code (0.9.8) | | retrieval | retrieval and grounding: searches (grep, rg, find, Grep, Glob, a web search, an MCP search tool), the same search again, a search followed by a read or a change (found) or by another search (again), searches before the first change and right after a failure, and deliveries with docs, tests or a search since the last change (grounded); counts only, nothing searched is kept (0.9.9) | | window | the context window, from the token counts the AI app writes beside each answer (input plus cached, once per message): the peak a turn carried, the turns over 100k, 200k and 500k, the growth from the first quarter of answers to the last, and compactions near the peak; counts only, nothing of what the context held (0.10.0) | | review | reviewing the agent: reviews while it worked, the middle interval, subagent runs reviewed, and what a review led to (a change of course, a change, a check); counts only (0.10.1) | | collector_version | the CLI version that measured it | | seq, prev, hash | the line's place, the hash of the line before it, and its own hash |

A value the app did not record is left out, never written as 0. Each day also gets a proof in proofs/<day>.json (the day's line count, a root hash over its lines, and a signature with this computer's device key when it is coupled), and manifest.json holds the count and the last hash, so a removed line is caught. The manifest names the computer and profile too.

Never kept: a prompt, an answer, code, a file or its path, a branch, a commit message, a title, terminal output, or anything typed. The text in your sessions is read only to find clocks, model names and counts, exactly as the session hook reads it.

  • What --apply writes. Only the settings in the table above, each on a typed y. The file is copied to settings.json.worktrust-backup-<date> first, every other setting stays as it was, and a value you set higher is never lowered. Hermes's YAML is never edited.
  • Today is archived tomorrow. A day goes into the archive once it is over; a later run only adds lines and never rewrites one.
  • What --summary counts. From the archive, after its chain checks out: per month the measured hours, the tokens per model and the share of input served from the cache (where tokens go, and so where they can be saved), and the days on which two or more sessions ran in one project at the same time, with the most open at once and their session time against the wall-clock time they covered.
  • The archive is yours to keep, copy, back up or delete. The free kit on worktrust.io/preserve is this package plus a guideline, for teams that want the same record without WorkTrust.

Rebuild the history

Since 0.10.5 there is one path: npx worktrust@latest history always re-measures and replaces this computer's earlier lines day by day. If a send did not finish, or you merged two keys of one computer, run npx worktrust@latest again: it finishes what did not arrive and never counts a day twice.

A newer version of this command measures a session differently from an older one: what the seconds were (the model answering, tools running, your own turns, pauses), your own turns rather than every message, tokens with the cache, and more AI apps (Cursor, Copilot, Hermes, Goose, OpenCode, OpenClaw, Codex, Antigravity). Lines an earlier version sent stand as they were measured then, and sending the sessions again would count those days twice: an old line does not name its stretch, so WorkTrust cannot tell it from the one that arrives.

npx worktrust@latest history --from-archive (0.8.0) is for the sessions an AI app has already deleted (Claude Code's default is 30 days). It sends what your local archive (preserve --archive) holds of them, and only that: days of sessions no longer on the computer, each line marked as coming from your archive. One rule (2026-10-10): a day signed by this computer's device key goes as it was measured; every other day (unsigned, signed by the local archive key, or by another computer's key) goes as reconstructed, and the plan says how many of each. It refuses, and sends nothing, when the archive does not verify or when WorkTrust does not yet accept the mark. It shows the plan first and asks once; Enter is No. Such lines are history, like any import: never verified hours.

npx worktrust@latest history --rebuild re-measures every session still on this computer with the current rules and sends them with one difference: WorkTrust replaces this computer's earlier lines, day by day.

  • What it does. It shows the plan first: per AI app, the days and the measured hours it would send, and the first and last day. It asks once; Enter is No. --dry-run shows the plan and stops. Nothing is sent before a typed y. For every day and AI app it carries, WorkTrust withdraws this computer's earlier lines (sent by this key or by an earlier key of the same computer, live or as history), each with a correction row that says why, and then stores the re-measured lines. A line that arrives unchanged is kept as it is. A day is counted once, and the same rebuild sent twice changes nothing.
  • What it never does. A replaced line is withdrawn and kept, never deleted: it stays in your record and the record shows each replacement; deleting your account still removes all of it. Never touched: another computer's lines, what a web connector (Claude or ChatGPT on the web) brought in, an AI app the batch does not carry, a day it does not carry, a line you made private, a line less than 48 hours old (the session hook's own window), and a line received in the last 12 hours (the batches of one run never replace each other). It sends what history sends and nothing more: durations, token counts, model names, a layer keyword, counts; never text.
  • Only from this computer's own key. A connector (an AI signed in on the web) cannot ask for a rebuild; WorkTrust refuses it at the door.
  • Offered once by update. When this computer's earlier lines were made by an earlier version (the copy in ~/.worktrust says which), npx worktrust@latest update says so in plain words and offers the rebuild; Enter is No, and the same version updated again offers nothing. The command cannot read your record, so it is an offer, not a finding.

A web chat's data export

npx worktrust@latest history --export <path> (0.10.10) brings a ChatGPT data export (Settings → Data controls → Export data) or a Claude data export (Settings → Privacy → Export data) onto your record as history, so years of browser chats appear with their months, days and rhythm. The path is the zip the vendor sent you, the folder it was unpacked into, or conversations.json itself; the vendor is told by the file's shape. Of the zip only conversations.json is opened: the profile, memories and files beside it are never inflated.

  • What goes. Per conversation and day: a neutral line (A conversation in ChatGPT, A conversation in Claude on the web), the seconds between the messages' clocks counted the way a measured day is (a pause over five minutes is not work; a day is cut at eight hours), your own turns as a count, the model's name where the export carries one, when it began and ended, and a stable name made of the conversation's id and the day, so the same export sent twice, or from two computers, is each line once. Never a title, never a message, never tokens (the export carries none and none are estimated).
  • What it counts as. Hours reconstructed, shown on your record with their origin and never in your verified hours: an export file can be edited before it is read, and no device key witnessed those hours. WorkTrust holds this at the door and in the database, whatever the sender claims.
  • How it asks. The plan first (the product, conversations, days, reconstructed hours, first and last month), then one question; Enter is No; --dry-run shows the plan and stops. On a coupled computer the bare npx worktrust looks in your Downloads folder (its top level only) for such an export it has not sent, offers it the same way, once, and remembers your answer by the file's digest, never by a conversation.
  • The export replaces. On the days the export covers, its lines count and the earlier lines of that product a connector brought in (an AI signed in on the web, which selects what it reports) are withdrawn and kept, each with a correction row, so a day counts once; the command says how many. A connector line that arrives later for a day the export covers is held. Lines an AI app reported earlier under no product may describe the same conversations: the command says how many lie on those days and asks once, Also withdraw N earlier lines an AI app reported for these days?; Enter is No, --yes keeps No, --dry-run sends nothing, and only a typed yes or --withdraw-unknown said in advance withdraws exactly those lines on exactly those days, the same way, never deleting. Never touched: another product, your own measured sessions, a private line, a day the export does not cover.

A copied session folder, and the local archive key

One rule (2026-10-10). Verified hours are what this computer's coupled key witnessed: the live session hook, the AI apps' own folders read at history time, and an archive day signed by this computer's device key. Everything else that carries clocks is reconstructed: shown on your record with its origin, never in your verified hours.

npx worktrust@latest history --from-folder <dir> brings in your own copy of an AI app's session folder: a backed-up ~/.claude/projects, ~/.codex/sessions, or an Antigravity brain folder. It is read with the same readers and the same clock as the live folders, never the live folders themselves (a path that is, or holds, one of them is refused). Each day is named exactly as the live folder would name it, so a stretch this computer's own sessions already hold is skipped (they win), and the same copy sent twice is each line once. What travels: the copy's own counts (tokens, your turns, the model, the evidence kinds), as reconstructed hours, titled as from a copied session folder; not the hours in layers, which say what a measured day's seconds were. The plan first, one question, Enter is No; --dry-run sends nothing.

preserve --archive on a computer that is not coupled now signs each day's root with a local archive key (~/.worktrust/archive-key.json, only you can read it), made once and said in one line. It needs no account and no network and never leaves this computer. It is continuity, not proof of time: a day it signed can be told from an edit, and the chain stays one computer's when you couple later (the device key then signs new days, and either key may seal a day it sealed before), but such a day reaches WorkTrust as reconstructed. It is never offered as the device key at pairing: connect always mints a fresh pair, because a private key that lived on disk before the pairing would weaken the proof of possession the pairing binds, for nothing your record would gain.

Where the key lives, and why it only works here

The key is kept in one file only you can read: ~/.worktrust/key.json (mode 600, in a folder of mode 700). No keychain is touched: a keychain tool can offer to "reset" a keychain it cannot find, which would delete every password on the computer, and many people rightly keep tools out of it.

  • No AI app holds the key. Each runs a small local bridge, node ~/.worktrust/worktrust.mjs mcp, which reads the file and speaks to WorkTrust for it; the session hook runs through worktrust.mjs hook the same way. A config file you share, a dotfiles repository or a screenshot of an app's settings carries no key.
  • The key only works on this computer. connect makes an Ed25519 key pair; WorkTrust keeps the public half, the private half stays in the key file. Every call is signed (method, path, time, a one-time nonce, the body's hash) and WorkTrust refuses a call through this key without a fresh signature. A key that leaks without the file, from a log, a proxy or a config, is worth nothing.
  • The key renews itself every week. A new secret is made here, only its hash is sent, signed, and last week's key stops working.
  • npx worktrust --direct writes a plain key into the apps' settings instead, for an app that cannot run a local command. Such a key is not bound to the computer and does not renew.

What it reads, writes and sends

| | | |---|---| | Reads | Which AI apps are installed (the settings folders of Claude Code, Codex, Cursor, Gemini CLI, Antigravity, VS Code, Windsurf, Hermes Agent, Goose, OpenCode, OpenClaw), and the chats Cursor and Copilot keep (clocks, models and token counts only). Afterwards, the session hook reads Claude Code's, Codex's and Antigravity's session files, the session databases of Hermes, Goose, OpenCode and OpenClaw, and the chats Cursor and Copilot keep, on this computer to measure durations and token counts. | | Writes | The key file (above). In each AI app's MCP settings (~/.claude.json through claude mcp, ~/.cursor/mcp.json, ~/.codex/config.toml, ~/.gemini/settings.json, ~/.gemini/config/mcp_config.json (Antigravity), VS Code's mcp.json, Windsurf's mcp_config.json, Hermes's and Goose's config.yaml, OpenCode's opencode.json, OpenClaw through openclaw mcp set) a WorkTrust entry that runs the local bridge, with no key in it; three hooks in ~/.claude/settings.json; the bridge, the hook and the counter in ~/.worktrust/. | | Sends, to pair | This computer's system (macOS, Windows, Linux) and host name. WorkTrust also records the network address the request arrives from, as a security record kept 90 days and seen only by WorkTrust staff (to answer a theft or fraud report). | | Sends, afterwards | Per measured stretch of work, and nothing else: its durations (seconds, duration_basis, model_seconds, tool_seconds, human_seconds, idle_seconds), token counts (tokens_in, tokens_out, tokens_cache_read, tokens_cache_write, token_basis), the model's name (model), the kind of work and one layer keyword such as frontend or backend (title, kind, layer, layers), closed technology keys (stack) and separate display-only tool keys (tools_seen), counts of how you work (exchanges, turn_basis, agent_runs, agent_seconds, agent_peak, interrupts, steers), when it began and ended and your computer's offset from UTC (started_at, at, utc_offset), what the CLI derived from the stretch as kinds and counts (evidence: checks run and failed, delivery steps and whether a check passed first, recoveries, delegation, context, routing, steering, refusals, planning, files and test files changed, the complexity class; never a word, a command or a path; since 0.8.3), a hash of the session and the day (stretch_ref), steered_from when it ran over SSH, and, so the work can be matched to GitHub, the repository as its remote's owner/name (repo, only for a repository on GitHub, GitLab or Bitbucket, the hosts WorkTrust can couple; never one on your own or a client's server) and the hashes of the commits made in the stretch (commits, never their messages). | | Never sends | Prompts, answers, code, file names or paths, commit messages, branch names, or the name of your local folder (a repository is named only as its remote's owner/name, above). The door has no field for them and refuses a submission that carries them. |

Why you can check it

  • No dependencies and no install scripts. Plain files, readable in an afternoon: worktrust.mjs (this command), setup-mcp.mjs (writes the MCP entries), log-session.mjs (the session hook), count-behaviour.mjs (the local counter) and preserve.mjs (keep your history), with the readers they import.
  • Bundled measurement modules. The npm package includes its counter and readers. Interactive commands can check the registry and relay through npx to a newer package; that can download code. The standalone bootstrap can also download support modules. See SECURITY.md for the distinction.
  • Provenance. The release workflow requests npm provenance from the public source repository. Verify the actual version's attestation and contents before relying on that origin; the manifest setting alone does not establish it. npm audit signatures checks an installed dependency tree.
  • Read it before you run it: npm pack worktrust downloads the package as a file without running anything.
  • The key never appears on a command line a process list could show: it is written to its file and reaches the bridge and the hook only in their environment.

A computer that is already coupled, or shared

  • npx worktrust@latest connect on a computer that is already coupled says to which account (masked) and asks Couple it again? [y/N]. Enter and --yes mean No; --replace says yes in advance.
  • The browser page that approves it shows, large, which WorkTrust account is signed in, with Use another account: the computer goes to whoever approves it there.
  • Once the new key is in place, the old key is ended at WorkTrust (the request is signed by this computer's own device key). An AI app still open on the old key is refused and moves to the new one.
  • When the new account is a different one, the old account sees a notice that its computer moved, with the computer's name and the moment, never the other account; no earlier work on this computer is offered to the new account, only work from the moment of coupling.
  • npx worktrust@latest status shows the account this computer sends to.
  • Different people on one computer with their own OS user each couple their own: everything lives in that user's home folder, and the accounts never meet.

Undo

npx worktrust@latest disconnect removes the WorkTrust entries (Hermes's hook with them), the hook (and Codex's notify line and Antigravity's worktrust hook when WorkTrust set them), the key file and installed bridge scripts. A small note of the previous account (masked) remains so that a later coupling by another account does not offer its earlier work. Other retained files and your local evidence archive are not an automatic server or archive erasure. Then revoke the key in WorkTrust: Sources → Devices → this computer → Revoke.

Requirements

Node 18 or later for the base CLI. SQLite session readers require Node 22.5 or later with node:sqlite; without it, those clients are not read. macOS, Linux or Windows; available readers depend on the local client format.

See SECURITY.md for the credential-file threat model, network/update behaviour and known trust limits. A local signature proves possession of a key, not independent observation of the work.

Security reports: see SECURITY.md.

Licence

MIT, for this command-line tool only (see LICENSE). The WorkTrust service it couples to is not open source.