A small local CLI that builds an automatically maintained view of active coding work across your machines.
It scans:
- configured project directories (direct children only — fast & predictable)
- per-repo: git branch, dirty state, last commit, recent commits within a window
- running
claude,codex,gemini,openclaw,xcodebuild,gradle,npm/pnpm/yarn, plus anything you add - optional
openclaw sessions list/tasks listoutput when the binary is available - every prompt you typed into a Claude Code window, archived one file per day
Output is two sidecar files at ~/.openclaw/workspace/:
current-work.md— readable Markdown view, friendly for cross-machine browsingcurrent-work.json— structured payload consumed by the native macOS menu bar app inmac/
…plus a dated archive at ~/.openclaw/workspace/claude-context/ (see below).
Requires Python ≥ 3.11.
git clone https://github.com/nvwalj/worknow.git
cd worknow
python3 -m pip install -e .Then run:
worknow # one-shot
worknow --watch 300 # refresh every 5 minutes (minimum 10s)
worknow --versionIf editable install is inconvenient on a machine, the self-contained wrapper works without pip install:
./bin/worknow
ln -sf "$PWD/bin/worknow" ~/.local/bin/worknowOn macOS, install automatic refresh every 5 minutes via launchd:
./bin/install-launchd-macosUninstall it with:
./bin/uninstall-launchd-macosIf your checkout lives on an external disk, note that macOS forbids launchd
background agents from reading non-boot volumes — even /bin/cp gets
Operation not permitted, and there is no approval prompt because a background
agent has no UI. Scheduling bin/worknow from /Volumes/... therefore fails
silently on every interval while still looking installed. The installer
detects this and stages a copy under ~/.local/libexec/worknow on the boot
volume, then schedules that; re-run it after pulling to refresh the copy. It
also verifies one real run and warns if the exit status is not 0.
Claude Code already writes a full JSONL transcript per window, but it is
unusable as a journal: one file per window, a long-lived window spans weeks,
and roughly nine in ten events stored as user are tool results, skill
injections, task notifications and slash-command expansions rather than
anything a person typed.
worknow inverts that layout. It walks every window's transcript, keeps only
genuine human turns, and writes one file per calendar day:
~/.openclaw/workspace/claude-context/
2026-08-10.md # readable: grouped by window, with times and replies
2026-08-10.json # same data, structured
Each day file records, per window, the window title, project path, git branch, the full prompt text, the first line of Claude's reply, the model that answered, and token totals — so "context" covers both what you said and how much was fed to the model to answer it.
worknow # refreshes the last 14 days alongside the report
worknow --context-only # just the context log, skip the git/process scan
worknow --context-days 400 # one-time backfill of everything still on disk
worknow --no-context # skip itDay files are derived but durable: each run only rewrites days inside the
window, so the archive keeps days whose transcripts have since been pruned from
~/.claude/projects. They are plain Markdown — readable in any editor, and in
AI Memory Reader if you want a
date-aware viewer.
Files are namespaced by machine (claude-context/<host>/2026-08-10.md).
Transcripts are local to the machine that produced them, so two machines write
different content for the same calendar day; namespacing means a synced archive
merges instead of one machine clobbering the other.
worknow --week # this week (Mon–Sun, ISO)
worknow --week last # last week
worknow --week 2026-W32 # or any date inside the week: --week 2026-08-05Writes ~/.openclaw/workspace/weekly/2026-W32.{md,json} — the source material
for a weekly journal, joining the two halves that answer "what did I work on":
- intent — your prompts that week, from the daily archive
- outcome — commits in the same window, from git
Both halves are needed because they cover different projects. On a real week here, the top project by prompts had zero commits (research and learning work that never produces code) and the top project by commits had zero prompts (a scheduled agent committing twice a day, with nobody typing anything). Either half alone reports a different week than the one that happened.
The report leads with a per-project table — days active, prompts, commits, and which half saw it — then lists each project's prompts and commits in full. It is deliberately factual: no narrative, no scoring. Feed it to an agent to write the actual review.
Two things worth knowing about the numbers. Commits are filtered to the repo's
own configured user.email, so a shared work repo contributes your commits and
not your colleagues' — and per-repo identities are respected, which matters if
you keep separate work and personal git identities. And prompts are filed under
the directory Claude was running in, not the repo it changed, so driving one
project from another project's window shows up on both sides of the table rather
than as one tidy row.
Filtering rules, in order of strength: an event carrying toolUseResult is a
tool result; isMeta / isCompactSummary / isSidechain mark injections,
compaction summaries and subagent turns; origin.kind is set by current Claude
Code on every real prompt and is authoritative when present; wrapper-prefix
matching is the fallback for transcripts written before that field existed.
Subagent transcripts (<session>/subagents/*.jsonl) are never read — those
prompts were written by Claude, not by you.
Everything stays local. The archive contains your prompts verbatim, so treat the directory like the transcripts it is derived from.
Config lives at ~/.config/worknow/config.toml. Run worknow --config-init to seed it with defaults.
Example:
output = "~/.openclaw/workspace/current-work.md"
project_roots = [
"~/projects",
"~/Project",
"~/.openclaw/workspace",
]
process_keywords = [
"claude", "codex", "gemini", "openclaw",
"xcodebuild", "gradle", "npm", "pnpm", "yarn",
]
ignored_process_fragments = [
"Google Chrome Helper",
"chrome_crashpad_handler",
]
max_projects = 80
recent_commit_days = 7
context_log_enabled = true
context_log_dir = "~/.openclaw/workspace/claude-context"
context_log_days = 14 # how many days each run rewrites
context_log_max_chars = 4000 # per prompt; 0 = unlimited
context_log_include_replies = true
weekly_dir = "~/.openclaw/workspace/weekly"If no config exists, worknow uses the defaults baked into cli.py.
A small AppKit menu bar app lives in mac/. It reads the JSON sidecar the CLI writes and surfaces:
- a menu bar badge with the active task count — a "task" is a tracked git repo that is dirty OR has a coding agent process running inside it
- a draggable floating panel (left-click the menu bar icon to toggle) listing active repos and agent processes
- auto-refresh every 30 seconds; panel position persists across launches
Right-click (or Control-click) the icon for the menu: show/hide the panel,
Refresh now (r), and Quit worknow (q).
Build & run:
cd mac
./build.sh # compiles a single-file Swift binary (no Xcode project)
./worknow-mac # foreground — for a quick look; Ctrl-C ends it./worknow-mac runs in the foreground and dies with the terminal. For daily
use install it as a launchd agent (below) so it starts at login and survives
closing the window.
Auto-start at login:
./install-launchd-macos
./uninstall-launchd-macos # to removeQuitting from the menu stops it for this session; the launchd agent starts it
again at next login. To stop it for good, run ./uninstall-launchd-macos.
The app only displays data — it does not run the scanner itself. Schedule the
Python CLI (bin/install-launchd-macos) so the JSON stays fresh, or the badge
sits on a stale number forever without ever looking broken.
This repo is self-contained — push your fork wherever, clone on each dev machine, and run pip install -e .. Machine-specific paths stay in ~/.config/worknow/config.toml and never touch git.
The code travels; the data does not. Each machine reads its own
~/.claude/projects and writes its own day files, so a fresh clone reports that
machine's week, not a merged one. To get a single weekly view across machines,
sync the output directory itself — ~/.openclaw/workspace/ — with whatever you
already use (a private git remote, iCloud, Syncthing). Day files are namespaced
by host, so they merge rather than collide, and --week unions every host it
finds in the archive. Commits are the exception: those already travel through
your git remotes, so a pushed branch shows up in the weekly report from any
machine that has the repo.
python3 -m pip install -e '.[dev]'
pytestMIT — see LICENSE.