vibe-clock Setup
You are helping a user set up vibe-clock: a CLI that reads the session logs Claude Code, Codex, Gemini CLI, and OpenCode already write locally, and turns them into SVG charts on the user's GitHub profile.
vibe-clock setup performs the entire installation interactively. Prefer it over doing the steps yourself. This document exists to tell you what it is doing, what it asks for, and what to do when a step cannot be automated. Do not restate configuration the tool prints — read its output and respond to it.
The one command
uv tool install vibe-clock # or pipx install vibe-clock / pip install vibe-clock
vibe-clock setup
setup will, asking before each step that changes anything outside the machine:
- Detect which agents have data directories.
- Get a GitHub token — borrowed from
gh authwhen the GitHub CLI is authenticated, otherwise prompted for, with the token-creation URL printed. - Confirm the profile repo (defaults to
<login>/<login>whenghcan supply the login). - Print the exact JSON it would publish and wait for confirmation.
- Create the public Gist.
- Set the
VIBE_CLOCK_GIST_IDrepository secret withgh secret set, or print where to paste it. - Write
.github/workflows/vibe-clock.ymlif run from inside the profile repo checkout, or print it. - Install a daily local
vibe-clock push(launchd, systemd user timer, or crontab). - Print the
<img>markdown for the profile README.
Non-interactive form, for when you already know the answers:
vibe-clock setup --profile-repo OWNER/REPO --charts card,donut --yes
--yes accepts every prompt, including publishing the Gist. Use it only when the user has explicitly agreed to publish.
Prerequisites
- Python 3.10+
- At least one supported agent with data: Claude Code (
~/.claude/), Codex (~/.codex/), Gemini CLI (~/.gemini/), or OpenCode (~/.local/share/opencode/) - A GitHub profile repo (
<username>/<username>) with a README.md - Either the
ghCLI authenticated (gh auth status), or a Classic PAT with thegistscope. Fine-grained tokens cannot write Gists.
The gist scope is all that is needed. The repo scope is required only for the optional github.trigger_workflow setting, which makes push dispatch the render workflow immediately instead of waiting for its daily cron. Leave it off unless the user asks — repo grants read/write to every one of their repositories.
Privacy — cover this before running share or setup
Everything stays local until the user confirms a preview. Show them the preview:
vibe-clock push --dry-run
The default payload has exactly ten fields: schema_version, producer_version, generated_at (floored to UTC midnight), days_covered, active_days, total_sessions, total_minutes, active_agents, favorite_model, and models[] (family names and session counts).
Never published, regardless of flags: file paths, the home directory, the username, real project or repository names (aliased to Project A, Project B, …), raw model IDs (reduced to families such as Claude / OpenAI), prompts, responses, code, session IDs, git data, and hostnames. sanitizer.py builds the payload from an allowlist — that is the guarantee. _validate_no_pii sits behind it as a backstop assertion over the fields carrying machine-derived text, so a future bug crashes locally instead of publishing.
vibe-clock render draws from that same allowlisted payload whether it collects locally or reads a Gist, so its SVGs are safe to commit. vibe-clock export is the one command that writes unsanitized data to a file.
Optional data is off unless requested, one flag each: --daily-activity, --time-patterns, --message-counts, --token-counts, --project-aliases. Enable only what the user asks for.
To stop publishing, vibe-clock unshare deletes the Gist and its revision history. A public Gist retains every past revision, so this is the only thing that removes previously published data.
When automation is not available
If gh is missing, setup prints what to do at each step. The manual equivalents:
Token — github.com/settings/tokens/new?scopes=gist, Classic, gist scope.
Secret — profile repo → Settings → Secrets and variables → Actions → New repository secret, named VIBE_CLOCK_GIST_ID.
Workflow — vibe-clock workflow prints the file; vibe-clock workflow --write writes it to .github/workflows/vibe-clock.yml. It is:
name: Update Vibe Clock Stats
on:
schedule:
# Runs after your local `vibe-clock push` updates the Gist.
- cron: "30 0 * * *"
workflow_dispatch:
# Required: the action commits the generated SVGs back to this repo, and
# GITHUB_TOKEN is read-only by default.
permissions:
contents: write
concurrency:
group: vibe-clock
cancel-in-progress: false
jobs:
update:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: dexhunter/vibe-clock@v1.5.0
with:
gist_id: ${{ secrets.VIBE_CLOCK_GIST_ID }}
chart_types: card,donut
The permissions: block is required. The action commits SVGs back to the repo, and GITHUB_TOKEN is read-only by default, so a workflow without it fails with a 403 on git push.
README — add the images the workflow generates:
<p align="center">
<img src="images/vibe-clock-card.svg" alt="Vibe Clock Stats" />
<img src="images/vibe-clock-donut.svg" alt="Model Usage" />
</p>
Then run it once: profile repo → Actions → Update Vibe Clock Stats → Run workflow.
Two clocks, both required
The local vibe-clock push writes the Gist; the Actions cron reads it and commits SVGs half an hour later. If the local push is not scheduled, the Gist goes stale and the profile freezes — the most common way a working setup silently stops working.
vibe-clock schedule # daily, at the local equivalent of 00:00 UTC
Backends are chosen automatically: launchd on macOS, a systemd user timer on Linux, crontab otherwise. On Linux a user timer stops when the user logs out, unless sudo loginctl enable-linger $USER is set. Keep it a user unit: a system service with ProtectHome=true can neither execute a uv tool / pipx binary under $HOME nor read the agent logs. Windows has no backend — use WSL or Task Scheduler.
Troubleshooting
- 403 on
git pushin the workflow: missingpermissions: contents: write. Compare againstvibe-clock workflow. payload carries no schema_version, or a version-skew error: the machine runningpushis older than the action rendering it.uv tool upgrade vibe-clock, then push again. The failure is deliberate; the old behaviour was rendering a plausible but wrong number.chart 'X' needs ...: a requested chart needs data that was never shared. Re-runsharewith the named flag, or drop the chart fromchart_types.- 401 on push: the token is fine-grained, or lacks
gist. It must be a Classic PAT. vibe-clock: command not found:~/.local/binis not on PATH (uv tool update-shell). Ifvibe-clock --versiondisagrees with what was just installed, runwhich -a vibe-clock— auv toolinstall shadows a Homebrew one.- No sessions found: check the agent data directories exist and contain session files (
~/.claude/projects/,~/.codex/sessions/,~/.gemini/,~/.local/share/opencode/storage/). - SVGs stale in the README: GitHub caches proxied images; wait or hard-refresh.
Configuration reference
Config lives at ~/.config/vibe-clock/config.toml (0600, in a 0700 directory). Rather than reproducing it here, read the live file, or see the Configuration section of README.md, which is kept in sync with config.py.
Environment overrides: GITHUB_TOKEN (used only when the TOML token is empty), VIBE_CLOCK_GIST_ID, VIBE_CLOCK_DAYS.
Note that vibe-clock init only creates or refreshes the config file. It does not create the Gist, set the secret, write the workflow, or schedule anything — that is what setup is for.