Tells you what you were in the middle of. Reads your local git state, including the work you have not committed, and summarises it using your own LLM key.
The existing ones read committed history, which means only the work you already
finished. wip reads what you left half done:
- the worktree diff, separating what is staged from what is not
- conflicted files, classified as conflicted rather than modified
- stashes you parked
- local branches with no upstream, and branches ahead of the remote
- an interrupted rebase, merge or cherry-pick
That last one is the point. On a Monday morning, what you have forgotten is not Friday's commit, it is the rebase that stalled halfway through.
npm install -g wip-cli
export OPENROUTER_API_KEY=sk-or-v1-...
Node 20 or newer. No runtime dependencies.
wip what you are in the middle of, right now
wip standup what you did since yesterday, ready for Slack
wip --since "last friday"
wip -C ~/proj-a -C ~/proj-b several repositories at once
wip --lang Portuguese answer in another language
wip standup focus on what broke a free-form question
$ wip
You are partway through extracting token validation into its own middleware.
The rebase onto main is stopped with a conflict in src/auth/guard.ts.
Three files are staged and ready to commit, and one stash from yesterday is unapplied.
Branch feature/auth-middleware is four commits ahead of the remote.
The key is yours and the request goes from your machine straight to OpenRouter. There is no intermediary server, no telemetry and no account to create.
Before anything is sent:
A sensitive file loses its whole content, not line by line. .env, .pem,
.npmrc, .netrc, id_rsa, credentials.json, secrets.yaml. A .env has no
safe portion, and guessing which of its lines is harmless would be wasted work.
The path and the changed-line count survive, because they are useful context and
harmless on their own.
.env.example is the deliberate exception: it exists to be read.
Recognisable credentials are replaced with [REDACTED:type]. Keys from AWS,
Google, GitHub, GitLab, OpenAI, Anthropic, OpenRouter, Slack, Stripe, SendGrid,
npm and RevenueCat, plus JWTs, PEM private keys, and passwords embedded in
connection URLs.
Redaction runs before budget trimming. The other way round, trimming could leave half a key behind, and half a key is still a partial leak.
There is a final gate, separate from the redaction path. If the text still contains something that looks like a credential at send time, the command exits with code 3 and sends nothing. That is treated as a bug in the program, never as "send it anyway".
Want to check? wip --dry-run prints exactly what would go out, without sending
it. --show-redactions lists what was replaced, without reproducing the values.
A refactoring diff runs past 100,000 lines without trying. slice(0, 8000) would
work and would be wrong: it hands over the first files in alphabetical order and
vanishes the rest, so the summary talks about src/a.ts and ignores the change
that mattered.
The budget is shared out in proportion to file size, with a floor per file, and then split across repositories. Whatever was cut appears in the text sent to the model, so it says its view is partial instead of describing the fragment as the whole.
Answers are cached by a hash of model + prompt under $XDG_CACHE_HOME/wip.
Running wip three times in one morning costs one request. The model is part of
the hash: switching models produces a fresh answer rather than the previous
model's.
--no-cache bypasses it.
| Code | Meaning |
|---|---|
| 0 | fine |
| 1 | git, network or provider failure |
| 2 | invalid usage |
| 3 | send aborted because the prompt still looked like it held a secret |
Data goes to stdout, messages for humans to stderr. wip \| pbcopy copies only
the summary.
npm test # 52 tests on Node's built-in runner
npm run typecheck
No test framework. The parsers have unit tests, and there are integration tests that create real git repositories in a temp directory, because git's output format is the boundary the unit tests assume.
Three bugs those tests caught, worth naming because they are easy to reintroduce:
git for-each-refreturns%(upstream:track)empty both for a branch level with its remote and for a branch with no remote at all. Inferring the upstream's existence fromtrackreported every synchronised branch as local-only.- A repository with no commits has no
HEAD, and theregit logandgit diff HEADboth fail. Every repository passes through that state. - The CLI called
main()at module load, so importing it to testparseArgsran the whole command, git reads andprocess.exitincluded.
--json, to compose with other tools- Repository discovery from a root directory, instead of repeating
-C - A local provider (Ollama, LM Studio) alongside OpenRouter
wip --watch, to follow along during the day
MIT