Default check-ins answer date to today - #376
Conversation
There was a problem hiding this comment.
Pull request overview
Default basecamp checkins answer create to use today’s date when --date is omitted, aligning CLI behavior with the Basecamp web UI and avoiding API failures when group_on is missing.
Changes:
- Default
checkins answer create--date/group_onto today when omitted - Preserve explicit
--datewhen provided and update--datehelp text accordingly - Add command tests and update Basecamp skill docs to reflect the new default
Reviewed changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated 2 comments.
| File | Description |
|---|---|
| skills/basecamp/SKILL.md | Documents that checkins answer create defaults --date to today when omitted. |
| internal/commands/checkins.go | Implements the defaulting behavior and updates --date flag help text. |
| internal/commands/checkins_test.go | Adds tests verifying default-date behavior and explicit-date preservation. |
Tip
If you aren't ready for review, convert to a draft PR.
Click "Convert to draft" or run gh pr ready --undo.
Click "Ready for review" or run gh pr ready to reengage.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
|
Fixed — avoided mutating the flag-backed --date variable inside RunE and made the default-date test deterministic by capturing the expected date once. |
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 3 out of 3 changed files in this pull request and generated 2 comments.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
jeremy
left a comment
There was a problem hiding this comment.
Review
The default-to-today behavior is correct and well-tested. All prior Copilot feedback has been addressed across the 3 commits (local effectiveGroupOn, injectable clock, body close).
Suggestion: expand --date to accept natural language dates
Every command that takes a user-facing date (todos, cards, lineup, reports) runs input through dateparse.Parse(), which supports natural language ("today", "tomorrow", "next monday", "+3", "eow") alongside YYYY-MM-DD. checkins answer create --date currently passes raw input straight to the API.
Proposed change in internal/commands/checkins.go:780-783:
now := checkinsNow()
effectiveGroupOn := groupOn
if effectiveGroupOn == "" {
effectiveGroupOn = now.Format("2006-01-02")
} else {
effectiveGroupOn = dateparse.ParseFrom(effectiveGroupOn, now)
}Using ParseFrom(input, checkinsNow()) rather than Parse(input) keeps a single clock source for both the default and explicit paths — deterministic in tests via the injected checkinsNow, consistent in production with one time.Now() call.
Accompanying updates needed:
- Flag help (
checkins.go:835): change to"Date to group answer (natural language or YYYY-MM-DD; defaults to today)"— matches the pattern in todos/cards - Skill docs (
SKILL.md:761): update to mention natural language support - Test: add
TestCheckinsAnswerCreateParsesNaturalLanguageDatepassing--date tomorrowand asserting against the injected clock
(schedule is intentionally excluded from this pattern — its --date is a YYYYMMDD occurrence key, not a user date.)
BC5 device logins as basecamp-cli (a trusted client) mint MULTI-ACCOUNT refresh tokens: the token response carries an RFC 8707 resource indicator (urn:bc:account:<id>), and bc3's refresh grant rejects a multi-account refresh without that echo (400 invalid_request, per Oauth::RefreshToken#resolve_target_account!). Without this, every device login died at its first token expiry. Credentials (internal/auth + the mirrored internal/sdk store) gain a resource field; device login persists token.Resource; refresh echoes the stored value on the form (sent only when set) and preserves it when the refresh response omits it, so rotated credentials keep the binding. An end-to-end test drives login → stored resource → forced refresh → echo on the form → binding surviving a resource-less rotation. Pins the SDK at oauth-resource-indicator head e583c994 (basecamp-sdk#478, which stacks on #376/#370 and carries the device flow + resource support); absorbed its API drift since the old 7207f742 snapshot (VerificationURIComplete and RegistrationEndpoint are now *string). The pin moves to #478's merged SHA before this lands.
BC5 device logins as basecamp-cli (a trusted client) mint MULTI-ACCOUNT refresh tokens: the token response carries an RFC 8707 resource indicator (urn:bc:account:<id>), and bc3's refresh grant rejects a multi-account refresh without that echo (400 invalid_request, per Oauth::RefreshToken#resolve_target_account!). Without this, every device login died at its first token expiry. Credentials (internal/auth + the mirrored internal/sdk store) gain a resource field; device login persists token.Resource; refresh echoes the stored value on the form (sent only when set) and preserves it when the refresh response omits it, so rotated credentials keep the binding. An end-to-end test drives login → stored resource → forced refresh → echo on the form → binding surviving a resource-less rotation. Pins the SDK at oauth-resource-indicator head e583c994 (basecamp-sdk#478, which stacks on #376/#370 and carries the device flow + resource support); absorbed its API drift since the old 7207f742 snapshot (VerificationURIComplete and RegistrationEndpoint are now *string). The pin moves to #478's merged SHA before this lands.
…urce echo (#582) * auth: adopt resource-first OAuth discovery and RFC 8628 device flow Bump the SDK to the oauth-device-flow head (7207f742) and rework login around it: - Discovery is now resource-first (RFC 9728 -> RFC 8414) from the base URL's origin via DiscoverFromResource. Only the two soft pre-selection outcomes (resource_discovery_failed, no_as_advertised) fall back to Launchpad; once a BC5 issuer is selected every failure is loud and is never converted into a Launchpad attempt. The fallback warning is kept for resource_discovery_failed, so production behavior is unchanged while BC5 is dark. - A selected BC5 issuer runs the device authorization grant as the pre-registered public client "basecamp-cli" (oauth_type "bc5", no client secret). The device display callback is the trust boundary for the server-controlled response: verification URIs are validated with the same policy as other OAuth browser URLs before launching, printed copies are sanitized of ANSI/OSC/control sequences, and malformed display data aborts polling via context cancellation. - The Launchpad path is unchanged: authorization-code with loopback callback or remote paste flow, legacy token format. - The DCR/PKCE development flow against BC3 is removed (client registration, client.json load/save, PKCE challenge). Stored legacy "bc3" credentials refresh to a clear re-authenticate error; auth status still displays them. A stale client.json is left on disk. - resourceOrigin strictly reduces the base URL to an origin: the path is stripped, everything else (userinfo, query, fragment, bad scheme, out-of-range port) is rejected without echoing the raw URL. - Flag copy is provider-neutral: --device-code and --scope now describe both paths truthfully. Test configs that constructed SDK clients with an empty BaseURL now set one: the bumped SDK refuses to attach credentials when the request target cannot be same-origin with the base URL. * auth: cover device flow, discovery fallbacks, and refresh behavior Behavior tests for the resource-first discovery and device flow adopted in the previous commit, against an httptest resource-server/AS pair with an injectable device-poll sleep: - Device happy path: bc5 credentials with token endpoint and scope, public client_id on both device and token forms, browser launched with the validated code-embedding URI, code and URI logged. - Flag matrix at the auth layer: Remote and NoBrowser print without launching (even with an injected BrowserLauncher); the default launches. - Display trust boundary: invalid verification_uri_complete falls back to the plain URI and is never launched or shown; both URIs invalid or a user code that sanitizes to empty aborts with an API-class error before any token poll; OSC/CSI/CRLF sequences are stripped from logged copies while the raw validated URL goes to the launcher; browser-launch failure logs and continues. - Scope wiring: explicit scope reaches the device-authorization form, unset scope is omitted; effective scope is token scope, then the requested scope, then "read". - Soft fallbacks: missing resource metadata warns and falls back to Launchpad; valid metadata with no non-Launchpad issuer falls back quietly. - Hard failures never fall back: ambiguous issuers, AS metadata fetch failure, and issuer binding mismatch surface their sentinels with zero requests to a recording Launchpad server; a selected issuer without device capability surfaces DeviceFlowUnavailable. - Poll outcomes: access_denied surfaces its reason; one authorization_pending cycle then success; parent-context cancellation mid-poll still matches errors.Is(err, context.Canceled). - bc5 refresh sends client_id=basecamp-cli with no client_secret key in the standard grant_type=refresh_token format; legacy "bc3" credentials refresh to a re-authenticate error without any network request. - A command-level regression test proves --device-code selects the remote paste-callback flow (observable on the Launchpad path, where remote and local modes differ). - Poisoned discovery endpoints (userinfo forms) are rejected before any POST; resourceOrigin table tests cover the strict origin reduction — including bare-"?" ForceQuery forms — asserting the raw URL is never echoed in failures. * docs: describe device-flow login and retire client.json references README: login now describes the device flow (automatic when the server supports it, Launchpad authorization-code otherwise), scope help notes Launchpad ignores it, and the config tree drops client.json — a leftover copy from the removed development registration flow is documented as safe to delete. SKILL.md: provider-neutral wording for --device-code and --scope, matching the flag help. * auth: harden device display boundary; cover profile --device-code mapping - Trim the sanitized user code and displayed URI: a code that reduces to whitespace is as unusable as an empty one and must abort before polling, not be displayed and polled until expiry. - Range-check ports (1-65535) centrally in isSecureEndpointURL — url.Parse accepts https://host:70000/ without complaint, and such a URL must never be dialed, displayed, or launched. One rule now covers the token, device-authorization, authorization, refresh, and verification-URL consumers; poisoned and out-of-range-port token/device endpoints are rejected with zero POSTs. - Regression-test the profile create --device-code → Remote mapping (the auth login copy was covered; the duplicated profile copy was not). * go: drop stale SDK pseudo-version go.sum entries left by restack The rebase onto main auto-merged go.sum with both the old device-flow snapshot pin (7207f742) and main's bb363c84 pin present. go mod tidy keeps only the go.mod pin. The SDK will be re-pinned to the resource-indicator branch head next; the build is red until then because the device-flow API is not on SDK main yet. * auth: persist and echo the RFC 8707 resource indicator across refreshes BC5 device logins as basecamp-cli (a trusted client) mint MULTI-ACCOUNT refresh tokens: the token response carries an RFC 8707 resource indicator (urn:bc:account:<id>), and bc3's refresh grant rejects a multi-account refresh without that echo (400 invalid_request, per Oauth::RefreshToken#resolve_target_account!). Without this, every device login died at its first token expiry. Credentials (internal/auth + the mirrored internal/sdk store) gain a resource field; device login persists token.Resource; refresh echoes the stored value on the form (sent only when set) and preserves it when the refresh response omits it, so rotated credentials keep the binding. An end-to-end test drives login → stored resource → forced refresh → echo on the form → binding surviving a resource-less rotation. Pins the SDK at oauth-resource-indicator head e583c994 (basecamp-sdk#478, which stacks on #376/#370 and carries the device flow + resource support); absorbed its API drift since the old 7207f742 snapshot (VerificationURIComplete and RegistrationEndpoint are now *string). The pin moves to #478's merged SHA before this lands. * auth: canonicalize the resource origin; restack onto keyring-probe main resourceOrigin now lowercases the host (and the localhost carve-out checks the lowered host) and strips explicit default ports, normalizing leading zeros — the SDK binds the RFC 9728 protected-resource identifier to this string code-point exact, so an equivalent spelling (HTTPS://3.BasecampAPI.com, https://3.basecampapi.com:443) previously mismatched the advertised identifier and silently soft-fell back to Launchpad. IPv6 brackets are restored around the lowered hostname. Also restacked onto main 31c4936, preserving #581's bounded headless keyring probe alongside the credential resource field, and tidied the go.sum lines the auto-merge duplicated. * go: pin the SDK at oauth-resource-indicator 819c0b86 Carries the full review round: exact-200 device token acceptance, hardened Retry-After parsing, FileTokenStore resource persistence, and the AuthManager empty/non-string resource classification. Provenance updated to match; the pin moves to basecamp-sdk#478's merged SHA before this lands. * go: pin the SDK at oauth-resource-indicator e9469832 The converged basecamp-sdk#478 head, carrying the full review tail since 819c0b86: exact-200 + 4xx-gated protocol errors, status-first terminal classification, request timeouts clamped to the code lifetime and the shared 3600s ceiling, ASCII-only zero-pad-tolerant Retry-After parsing, cooperative-cancellation coverage around every request, FileTokenStore resource persistence, empty-device-endpoint capability guard, truncated error interpolation, and the AuthManager no-expiry refresh guard. The pin moves to #478's merged SHA before landing. * go: pin the SDK at oauth-resource-indicator 298ad8e4 Carries the corrective-cycle Go fixes since e9469832: presence-aware refresh expires_in (omission clears the stale expiry, explicit non-positive fails as api_error), the pollToken and post-authorization cancellation re-checks, the empty-device-endpoint capability guard, the no-expiry AccessToken guard, and truncated error interpolation. The pin moves to basecamp-sdk#478's merged SHA before landing. * go: pin the SDK at oauth-resource-indicator fd2ff934 Carries the final corrective round's Go changes: the refresh expires_in lifetime ceiling (an absurd value overflowed into a negative ExpiresAt read as never-expiring) atop the presence-aware omission/non-positive handling, plus the cancellation re-checks. The pin moves to basecamp-sdk#478's merged SHA before landing. * go: pin the SDK at oauth-resource-indicator 5645f897 * go: pin the SDK at oauth-resource-indicator 9c4cc6d6 * go: pin the SDK at oauth-resource-indicator b63b12e9 * go: pin the SDK at oauth-resource-indicator e421ea9f * go: pin the SDK at oauth-resource-indicator 329d2278 Also repairs the corrupted updated_at the previous pin wrote (a mis-sliced pseudo-version segment produced a non-ISO timestamp); the timestamp now comes from the pseudo-version's 14-digit datetime field. * go: pin the SDK at oauth-resource-indicator e3e0ea5c * go: pin the SDK at oauth-resource-indicator 0bdb14ac * go: pin the SDK at oauth-resource-indicator 05cc9f78 * auth: match loopback hosts case-insensitively in isSecureEndpointURL DNS hostnames are case-insensitive, and resourceOrigin already lowercases before the same localhost check — but isSecureEndpointURL passed u.Host straight into hostutil.IsLocalhost, so http://LocalHost:3001 was rejected as insecure. Lowercase before matching; the non-loopback rejection is unchanged. The test fails against the un-fixed code. * docs: state the real Launchpad fallback boundary The README promised a Launchpad fallback whenever device flow was unavailable; discoverOAuth falls back only on the two soft pre-selection outcomes — once a BC5 issuer is selected, failures surface loudly and are never converted into a Launchpad attempt. * go: pin the SDK at oauth-resource-indicator 59358c11 * auth: name every constraint in the endpoint-validation error requireSecureOAuthEndpoint also rejects userinfo, hostless forms, and out-of-range ports — the message only mentioned the scheme rule, which was misleading for those failures. * go: pin the SDK at oauth-resource-indicator c55e9f3a * go: pin the SDK at oauth-resource-indicator ac79e227 * go: pin the SDK at oauth-resource-indicator 602f7247 * auth: build the authorization-info URL from the resource origin resourceOrigin explicitly supports pathful BaseURLs, but AuthorizationEndpoint appended /authorization.json to NormalizeBaseURL — which only trims a trailing slash — yielding a misrouted host/api/v1/authorization.json. Use the canonical origin. The pathful test fails against the un-fixed code. * go: pin the SDK at oauth-resource-indicator e6112812 * go: pin the SDK at oauth-resource-indicator 145c0010 * go: pin the SDK at oauth-resource-indicator 351266ea * go: pin the SDK at oauth-resource-indicator 644129714 Absorbs the pre-request issuance anchoring for device login (a slow authorization response now counts against the code lifetime) plus the round's Python/Ruby/TS hardening upstream. No CLI-side code changes. * go: pin the SDK at oauth-resource-indicator 02ea83bf4 Absorbs the SPEC §16 receipt-anchoring for device login (request-leg latency no longer shrinks the code window) plus the round's transport and exchange hardening upstream. No CLI-side code changes. * go: pin the SDK at oauth-resource-indicator 4b524ed1d Absorbs the exchange token_type contract (Bearer only when absent, typed api fault on present-but-empty) and the oauth-token fixture tightening upstream. No CLI-side code changes. * go: pin the SDK at oauth-resource-indicator 916b4297b Absorbs the duplicate-Retry-After rejection and the round's transport and parse-fault hardening upstream. No CLI-side code changes. * go: pin the SDK at oauth-resource-indicator 7e924c0df Absorbs the validate-then-mutate refresh ordering and Retry-After digit-bound parity upstream. No CLI-side code changes. * go: pin the SDK at oauth-resource-indicator 1b3e9a037 Absorbs the shared Retry-After digit bound in the Go device poller. No CLI-side code changes. * go: pin the SDK at the merged OAuth stack (9480aa4c84b8) The device-flow + transport-hardening + resource-indicator stack landed on basecamp-sdk main (#370/#376/#478); this replaces the branch-head pseudo- version pins with the mainline merge commit. All tests green.
Summary
Default
basecamp checkins answer createto use today's date when--dateis omitted.Changes
checkins answer create--date/group_onto today--datewhen provided--datehelp text to mention the defaultskills/basecamp/SKILL.mdto document the behaviorWhy
The web UI preselects today for check-in answers, and live API behavior requires a date for successful answer creation. This makes the CLI match the web experience while still allowing an explicit override.
Summary by cubic
Default
basecamp checkins answer createand TUI hub answer creation to use today’s date when--dateis omitted, matching the web UI and avoiding API errors. Explicit--datekept; help/docs updated; tests cover default and explicit dates.Written for commit e5742c2. Summary will update on new commits.