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

efforts

v0.1.0

Published

Time tracking without opening a time tracker: a JSON-only CLI plus the agent skill that drives it. Overlapping clocks in, honest wall-clock hours out.

Readme

efforts

Time tracking without opening a time tracker. You say what you are working on; your coding agent drives the CLI.

you: started the webhook retry bug for Client Co
you: also picking up a code review, in parallel
you: switch the webhook one to integration
you: that review stopped at 15:50, I got distracted
you: what did today actually cost per task

No timer to remember, no window to switch to, no Friday-afternoon reconstruction. Time tracking is frustrating because nobody wants to stop work to press a button, and this never asks you to. Corrections are just sentences: a start you got wrong, a stint that ended an hour ago, an activity that turned into another one mid-flow.

The point is that you can trust what comes out. efforts owns every logged piece of work and refuses what it cannot know: it will not guess which clock you meant, will not accept a timestamp in the future, and cannot represent a gap where two activities meet. What it stores is structured for analysis later, not prose to be read back.

Works with — Claude Code today, with the skill in the box. Any agent that can run a shell command can drive the CLI; the policy that makes it behave well is one markdown file you can point that agent at. A local MCP server is next, for chat clients with no shell of their own.

What you get

  • Capture that costs little additional effort. The interface is the conversation you were already having, in the window you were already in.
  • Several clocks at once, as the normal case. start never stops anything. You can have agents developing two tickets in parallel and a meeting on top, and the report still adds up to a day that exists, with a stated rule for how the overlap was split.
  • Retroactive everything. "That started at 8", "the demo ran 11 to 12", "I switched to integration an hour ago" — all of it is one command, not a form.
  • JSON as the product. Every read is a single envelope with no rendering baked in, so a terminal, a chat client, a spreadsheet and an invoice can each build the view they want from the same payload.

Install

npm i -g efforts        # or: npx efforts <command>
efforts status

Your tracked efforts land in ~/.efforts/data — see Where your hours live to move them, or to keep one ledger across machines.

The package ships the skill as well as the CLI, because the policy is half the tool. To let an agent drive it, put the skill where that agent looks — for Claude Code, either of:

cp -r "$(npm root -g)/efforts/.claude/skills/time-tracking" ~/.claude/skills/   # personal
cp -r "$(npm root -g)/efforts/.claude/skills/time-tracking" .claude/skills/     # one project

Where exactly an agent looks for skills, and which one wins when two of them answer the same sentence, is that agent's business rather than this tool's — so check its own documentation. If several skills on your machine claim time tracking, invoking this one by name is the reliable way through.

Then check your agent can actually drive it. The skill asks for judgement the CLI cannot supply — which command a sentence means, when to ask instead of guess, what must never reach a note — and models differ at that. eval/cases.md ships with the package for this step: open a session with the skill loaded, say a case's utterance in the state it describes, and check the calls against the expected shape.

$(npm root -g)/efforts/eval/cases.md

Half a dozen cases is enough to tell whether your setup captures faithfully or merely captures. The ones marked observed are the ones a real session has already exercised, so start there.

Requires Node 20+ and has no dependencies.

Where your hours live

The ledger is plain files: one JSON file per day under days/YYYY/MM/, plus a projects.json. It lives outside the package, so upgrading or uninstalling the CLI cannot touch your history, and you can read a day with cat if you ever want to.

EFFORTS_DATA_DIR=~/work/hours    # move the ledger
EFFORTS_TZ=America/Bogota        # override the timezone
EFFORTS_AUTO_COMMIT=1            # commit after every change, if the ledger is a git repo
EFFORTS_LOOKBACK_DAYS=14         # how far back "what is running" looks

Settings you want to keep go in a file instead, searched nearest first:

./efforts.config.json            this directory — a project's own defaults
~/.config/efforts/config.json    you, in every launch context

Merged one key at a time, so a project file can set defaultProject while the user file supplies everything else. efforts config prints the resolved settings and which file supplied each key.

An efforts.config.json in a repo is the useful case: {"defaultProject": "acme"} there means work started while you are in that checkout needs no --project and cannot land anywhere surprising.

EFFORTS_TZ matters more than it looks. Day boundaries, --at 9:15 and every "today" resolve in that zone, so the wrong one files work on the wrong day. It defaults to the system timezone, which is right until you travel.

One ledger across two machines. git init the ledger directory and treat it as a repo you push:

cd ~/.efforts/data && git init && git add . && git commit -m "hours"

Because a day is a file, two machines working different days merge without conflict; the same day on both, or two new projects at once in projects.json, is an ordinary merge you resolve by hand.

EFFORTS_AUTO_COMMIT=1            # commit the ledger after every change

Commits are local, so pushing stays yours — a cron job or a shell hook, whenever suits. A commit that fails is a warning rather than an error: the ledger is written first, and git is a convenience on top of it.

Commands

start <task…>   --project P [--at T] [--tags a,b] [--weight N] [--link k=v] [--note N]
stop [query]    [--all] [--at T] [--note N]
log <task…>     --project P --from T --to T [--tags a,b] [--weight N] [--link k=v] [--note N]
split <id>      --at T [--task T] [--first-task T] [--note N]
switch <task…>  --project P
status
today | day [date]  [--attribute] [--strategy S]
report          [--from D --to D | --week | --month] [--project P] [--attribute]
                [--strategy S] [--format json|csv]
analyze         [--from D --to D | --week | --month]
export          [--from D --to D | --week | --month] [--attribute] [--format json|csv]
note <query|--last> <text…> | <query|--last> --rm
link <query> <key=value…>
edit <id> [--task|--project|--start|--end|--tags|--weight|--link]
rm <id>         resume [query]
projects list | rename <id> <name> | merge <from> <into> | resolve <name>
config

--json prints a single envelope and nothing else, which is how an agent talks to it. Reads emit that envelope with or without the flag; commands that change something print a one-line receipt for the person who typed them, which never enters the envelope. --at takes 9:15, 9:15am, 14:30, -20m, -2h, or a full ISO instant.

--attribute turns on overlap resolution; --strategy picks equal (default), weighted or exclusive.

start and stop record what has already happened, so their --at must be in the past. log, split and edit accept a future instant: log … --from 10:30 --to 12:00 said at 11:58 is one block someone knows the shape of, and clipping the last two minutes would write a time nobody said. The trade is that a bare --to 23:00 reads as tonight for log, and as an error pointing at yesterday for start.

What honest hours look like on a parallel day

On an overlapping day, raw durations add up to more hours than the day contains. Attribution splits each moment among the tasks running in it, so per-task totals sum to the wall clock the work actually occupied.

Given A 09:00–12:00, B 10:00–11:00, C 11:30–13:00, D 14:00–14:30:

| | A | B | C | D | total | | :-- | --: | --: | --: | --: | --: | | raw | 180 | 60 | 90 | 30 | 360 | | attributed | 135 | 30 | 75 | 30 | 270 |

360 minutes of apparent effort; 270 minutes of day. Both numbers are true and they answer different questions, which is why a report shows them side by side and names the strategy it used.

It apportions elapsed time, which is arithmetic. It does not claim how long a task would have taken without the multitasking — timestamps cannot answer that. analyze gives you the honest version of that question: the gap between raw and attributed is how much apparent effort was overlap. See docs/SCHEMA.md for what attribution does and does not claim.

How it behaves

Several clocks run at once. start never stops anything. stop with more than one entry open and no query is an error listing the candidates, rather than a guess at which task ended.

The CLI owns every file and every number. Nothing else reads or writes day files, so duration maths and attribution hold regardless of how the conversation goes.

Reads emit JSON and no view. status, today, report, analyze, export and projects list return one envelope: data, plus warnings when something needs saying. Building the table, the artifact or the invoice belongs to whatever is reading, because arithmetic has one right answer and a view has as many as there are surfaces.

A boundary is taken once. An activity that changed mid-clock is one split; work that is already over is one log. Both apply a single instant to both sides, so a gap or an overlap at the seam cannot be written.

A clock you forgot says so. "What is running" reads back a couple of weeks, so it stays quick — but an entry left open longer than that would otherwise be invisible to status, unreachable by stop, and still accruing against the current moment. So status warns about it by name, and so does any report whose range it distorts.

A project answers to its id or its current name. Client Co, clientco and client-co reach one project, on reads as much as on writes. A merely similar name still resolves, but the match is always reported in warnings, since a wrong one would merge two real projects. An unknown name is an error naming the projects that exist — never an empty report, which would read as "no work on that".

Nothing about a ticket's state is stored. Entries carry ticket=KEY links and arbitrary notes about the window they cover. Whether that ticket is merged, blocked or abandoned lives in the tracker of record, so a report cannot contradict it.

What this expects of the model

Half of this tool is bin/ and half is .claude/skills/time-tracking/SKILL.md. The CLI owns the arithmetic and the refusals — ambiguous stop errors rather than choosing, --at rejects a future instant, split applies one boundary to both halves so a gap cannot be represented. Those are guarantees, and node --test keeps them honest.

The skill owns the judgement, and prose can only request compliance. So the tool assumes a model that will:

  • ask instead of picking when an instruction is ambiguous — two open entries and "stop work" is a question, not a coin flip;
  • hold a rule together with its reason and generalise past the stated case. "No future work in a note" has to become "write the blocker in the past tense and put the plan on the ticket" without that being spelled out;
  • render a view for the surface it is on — the CLI returns JSON and nothing else;
  • reach a second tool for a ticket title and degrade to the bare key, silently, when it is not there.

A model that cannot do those still gets correct totals — the ledger is code — but it will capture less faithfully, and it will be quieter about the judgement calls than it should be.

eval/cases.md holds the policy as cases drawn from real sessions: the utterance, the state, and the shape of the expected calls. Each one is marked observed (a session did this, with the date) or rule (the skill says so, nothing has exercised it yet). They are evals rather than tests — read the calls and score them.

Built and used daily since 2026-07-28. The ledger half is held by node --test; the policy half by the cases above.

Development

Building on it, or changing it: CONTRIBUTING.md covers the checkout, the test suite, the module map, and how the policy half is kept honest.

License

MIT — do what you like with it, keep the copyright notice.