Files
agent-skills/skills/hourlog/SKILL.md
T
naps62 39d88899af refactor: back to per-day blocks, add a totals line
The matrix table dropped the evidence — share, active minutes, hour
range, and the outside-working-hours flags — which are what make a
proposed number checkable rather than asserted.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-14 18:40:44 +00:00

5.7 KiB

name, description, user-invocable, argument-hint, allowed-tools
name description user-invocable argument-hint allowed-tools
hourlog Work out how many hours each client project got on each day, by reading Claude Code and Codex session activity, then reconcile that against the company timesheet and confirm the week's hours. Use when the user asks to log hours, fill in their timesheet, check what they worked on last week, or runs the Friday hour-logging session. true [--week last|this | --since YYYY-MM-DD [--until YYYY-MM-DD]]
Read
Grep
Glob
Bash
Edit
Write

Hour log

Turn session activity into an hours-per-project-per-day proposal, check it against the timesheet, and submit only what the user approves.

The timesheet is a company record. Nothing is written to it without the user saying go, in this session, after seeing the table. An unattended run stops at the proposal.

Setup (once per machine)

Three things must exist. Check them before anything else and stop with the missing step if not.

  1. ~/.config/hourlog/projects.json — path prefix to project mapping. Copy <skill-dir>/config.example.json and fill it in. Never commit a filled config, and never put a project, client, or host name in this repo — it is public.
  2. HOURLOG_API and HOURLOG_TOKEN in ~/.env.claude. The token is a personal access token from the timesheet app's profile page. Scopes: profile:read, schedule:read, and schedule:write only if submitting. Never echo the token.
  3. Project names in the config must match the app exactly. Verify with me-api.py projects and fix the config, not the app.

1. Scan the sessions

python3 <skill-dir>/scripts/scan-activity.py --week last

Prints one block per day — a line per project with suggested hours, share, active minutes, and the hour range — then a totals line. That output is the deliverable: show it as-is rather than restating it in prose.

The unit behind it is a 5-minute slot in which at least one message was written, deduplicated per project — so a 40-subagent swarm on one project counts once, and two projects worked in parallel each keep their own slots. Message counts would let one overnight autonomous run outweigh a real morning.

Two things in the output need judgement, not arithmetic:

  • outside hours. Activity at 02:00 is usually an unattended run, not work. If most of a project's minutes came from 21h-02h, its share is inflated — say so and shift the split.
  • unmapped: lines. A path with no rule, excluded from the split, so the affected days are wrong rather than merely incomplete. Either it is a new client directory the config is missing — say so and ask — or it is personal work that belongs in exclude. Never guess it into a client project.

--json carries the same fields if you need to compute against them.

2. Propose the hours

Active minutes are a floor on real work, never a measure of it. Meetings, review, reading, whiteboarding, and thinking leave no transcript at all. A day showing 160 active minutes was not a two-hour day. Read the numbers as proportions between projects, and get the day's length from the calendar.

The scan already does the arithmetic: it splits nominal_day_hours (config, default 8) across the day's mapped projects by share, in 15-minute steps. Your job is the judgement it cannot do:

  • One project at 85% or more: round it up to the whole day rather than leaving a token 1h on the other.
  • Days marked under 30min, treat as empty get nothing proposed.
  • Correct the split for the two things flagged in step 1 before proposing it.

Never scale a day down because its transcripts are thin. A quiet day is a normal working day unless the user says it was not, or the timesheet already marks it as time off or a holiday.

3. Reconcile against the timesheet

python3 <skill-dir>/scripts/me-api.py schedule --start YYYY-MM-DD --end YYYY-MM-DD

Each day comes back either already planned (an allocation with an entry id and planned hours) or absent. That splits the proposal in two:

  • Planned and matching — confirm at the planned hours.
  • Planned but the sessions disagree — confirm at the observed hours, and show both numbers in the table so the user sees what changed.
  • Not planned at all — needs a new entry, which is a bigger claim. Flag it separately rather than folding it in.

4. Show the table, then ask

One row per project per day: date, weekday, project, proposed hours, what the timesheet says now, and the action (confirm / adjust / add / skip). Mark any day that needs a new entry rather than a confirmation.

Keep it to that table plus a line for anything you had to judge — a discounted overnight run, an unmapped path, a day you left empty. No commentary on days that were straightforward.

Then ask once, plainly, whether to submit. Wait for an answer. Silence, a timeout, or "user may be away" is not approval — leave the timesheet alone and say the run is waiting.

5. Submit what was approved

# confirm a planned day
python3 <skill-dir>/scripts/me-api.py confirm --entry ID --hours 8 --dry-run
# log a day with no allocation
python3 <skill-dir>/scripts/me-api.py log --project ID --dates D,D --hours 8 --dry-run

Run every write with --dry-run first and show the requests. Drop the flag only for the rows the user approved — not the whole table, if they approved part of it.

A 423 means the period is locked and ops has to reopen it. Report it and move on; it is not a failure to retry.

Re-read the schedule afterwards and confirm what landed. Report the diff, not an assumption.

Scope

How a day divided between projects, and confirming hours already worked. Not future allocations, not time off, not anyone else's schedule.