Create, show, update and delete timesheet entries

JE
19h ago
+1181-4

Why

The CLI reads timesheets through the three reports (timesheet report, project, item) but can't log, change or remove time. SDK v0.29.1, the CLI's pin, has had TimesheetService Get, Create, Update and Destroy for a while; the CLI called none of them, while API-COVERAGE.md claimed "list, show, create, update, delete". The operation coverage report from basecamp/basecamp-sdk#1055 found the gap.

What changed

A timesheet entry group. bc3 routes entries by account alone, so no verb takes or needs a project.

SDK operationCommand
CreateTimesheetEntry (POST /recordings/{id}/timesheet/entries.json)basecamp timesheet entry create <item-id|url> --hours 1.5 [--date] [--description] [--person]
GetTimesheetEntry (GET /timesheet_entries/{id})basecamp timesheet entry show <id|url>
UpdateTimesheetEntry (PUT /timesheet_entries/{id})basecamp timesheet entry update <id|url> [--hours] [--date] [--description] [--person]
DestroyTimesheetEntry (DELETE /timesheet_entries/{id})basecamp timesheet entry delete <id|url> [--force]
  • --hours is checked locally as a decimal or h:mm, the two forms bc3 takes. --date goes through dateparse and must come out as a real ISO date; on create it defaults to today. --person takes an ID, name, email or me.
  • delete is permanent: bc3 never trashes or archives an entry. So it asks for confirmation, and machine output needs --force, the same as subtasks delete.
  • The per-entry verbs take only a bare ID or the entry's own URL (…/timesheet/entries/<id>, or either API form). Any other URL, such as the to-do the time is logged against, is refused rather than read as an entry ID, so delete can't aim at the wrong thing. create refuses a project's timesheet URL, which carries the project's ID and not the timesheet recording's.
  • Also updated: the catalog (entry action), .surface, the API-COVERAGE.md row (now 7 endpoints, listed), a "Timesheet entries" section in the basecamp skill, the timesheet group's agent notes (they named a basecamp clock shortcut that no longer exists), and a smoke lifecycle file.
  • basecamp mcp needs nothing: its model already serves create_timesheet_entry, get_timesheet_entry, update_timesheet_entry and destroy_timesheet_entry.

Tests

  • internal/commands/timesheet_entries_test.go: each verb's method, path and body over the recording transport. Covers entry URLs in all three forms, a default date of today, --person me resolution, fields left out when unset, local refusals that send no request (bad hours, bad dates, empty values, nothing to update, non-IDs, item and project-timesheet URLs on every per-entry verb), delete needing --force without a terminal, and a 403 on delete returned as an error.
  • e2e/timesheet.bats: the local refusals through the built binary.
  • e2e/smoke/smoke_timesheet_write.bats: create, show, update, delete against a real account. A 403 from an account without timesheets counts as unverifiable.
  • bin/ci passes.

Gap card: https://app.basecamp.com/2914079/buckets/48521764/card_tables/cards/10447986600


Summary by cubic

Adds a timesheet entry command group so the CLI can log, view, change, and delete timesheet entries. The CLI previously read timesheets only through the three report commands; the pinned SDK's entry operations went unused while API-COVERAGE.md claimed them.

  • create logs hours against an item (--hours required, --date defaults to today, --person takes an ID, name, email, or me).
  • create takes only the item kinds bc3 logs time against; project, listing, and person URLs are refused too. show, update, and delete take the entry ID or its own URL; any other URL is refused so delete can't target the item the time is logged against.
  • Pasted URLs must be http(s) on a trusted Basecamp host and name the configured account (compared numerically); anything else is refused before any request, and follow-up commands carry an account adopted from a pasted URL.
  • delete is permanent, so it prompts for confirmation and needs --force in machine-output or non-interactive sessions; the delete breadcrumb carries --force for machine readers.
  • Also updates the command catalog, .surface, API-COVERAGE.md, the skill file, and the timesheet group's agent notes, and adds unit, e2e, and a serial smoke lifecycle whose timesheets-off gaps are allowlisted. The entry summary carries the person's name and ID, which styled and Markdown output would otherwise skip.