Add basecamp feedback, shipped dark behind endpoint detection
What
basecamp feedback sends a report about Basecamp itself (a bug, a missing capability, confusing behavior, praise, other) to the Basecamp team, for agents and people alike:
basecamp feedback --category <bug|missing_capability|confusing|praise|other> \
[--subject "<command or tool>"] [--error-code <code>] "details"
- POSTs
{surface: "cli", category, subject?, error_code?, details}to/{account}/feedback.jsonthrough the SDK's account client, with the person's own login and the CLI's normal User-Agent. No SDK change needed:AccountClient.Postis the generic pathbasecamp apiandbasecamp mcpalready use. - Checks category and the 2000-character cap before sending.
"-"reads details from stdin. - Prints the receipt:
Feedback received (receipt <id>), or{"ok":true,"data":{"id":…,"status":"received"},…}in JSON modes. - 404 →
Feedback is not available.(non-zero exit). 422 → validation error. 429 → retryableFeedback limit reachedwith the Retry-After. That 429 is the person's daily quota, so it is marked (resilience.WithEndpointQuota) and sets no cooldown in the shared rate limiter, which would otherwise block every later command.
Ships dark
Nothing is advertised until Basecamp says the endpoint is on. GET /{account}/feedback.json answers 200 when feedback is on for the person and 404 when it is off.
- Cache: the answer is kept in
<cache dir>/feedback.json, per host and account, for 24 hours. It is refreshed whenfeedbackruns. Once it is due, it is also refreshed after a command that has already made API requests (bounded at 2s, at most once a day per account, never after a command that made no request, never formcp,auth,login,logout,profileorsetup). That probe uses only the credential the command already read (auth.WithHeldCredentialOnly), with no keyring read and no token renewal. The attempt is noted before the probe, and where the cache can't be written there's no probe at all. A failed detection backs off for an hour. - Off (the default today, since the endpoint 404s): the command is hidden from root help, agent help,
basecamp commandsand completions.basecamp feedbackwith any arguments, malformed flags included, andbasecamp feedback --helpsay onlyFeedback is not available.and exit non-zero. Error output has no hint. - On: root help lists it under LEARN MORE, agent help and
commandsinclude it, and errors that came back from Basecamp (api_error,validation,not_found) add one line on stderr: "If this looks like a problem with Basecamp,basecamp feedbackreports it to the Basecamp team." Usage, auth, rate-limit and network errors never carry it.
Flag: BASECAMP_FEEDBACK=1 forces it on and BASECAMP_FEEDBACK=0 forces it off. Either value skips detection. Unset means detection decides. Turning it on for everyone is server config only (the endpoint starting to answer 200), with no CLI release.
Compatibility
Nothing shipped breaks. The JSON success and error envelopes on stdout are unchanged: the hint goes to stderr, in every output mode except basecamp mcp, which never prints it. While dark, nothing is visible: .surface and the command catalog are unchanged, and hidden commands stay out of both. The only new request is the daily detection GET described above.
Agent skill
skills/basecamp/SKILL.md gains one descriptive line under Errors and recovery: "basecamp feedback reports bugs, missing capabilities or confusing behavior to the Basecamp team, when it appears in basecamp commands." It is conditional, so it is safe while the command is dark. To fit under the unchanged 16 KiB guard (TestBasecampSkillStaysDiscoveryFirstAndFocused), six passages are reworded more tightly; none loses an instruction or capability. The hidden command is acknowledged in .surface-skill-drift, the way hidden connect commands already are.
Decisions for v1
- No
send_feedbackinbasecamp mcp. The CLI's MCP mode has no feedback tool, sosurface: cli_mcpis never sent. - The hint includes
not_found, matching the design's "every error" and the MCP server. - The hint goes to stderr in JSON and agent modes too, since agents mostly run there; stdout JSON is unchanged.
- No shared client in basecamp/mcp. The design defers the shared library, so this PR calls the SDK's account client directly.
Trust & Compliance
No new third party, and the report goes only to Basecamp. Feedback content is treated as customer data. The 90-day retention of the resulting cards is server-side and handled by a follow-up.
Tests
internal/feedback: override parsing; nothing advertised until detected; 200 and 404 recorded; TTL expiry; failure backoff keeps the previous answer; override beats the cache; a corrupt state file reads as off;BASECAMP_CACHE_DIR; which errors get the hint; categories.internal/commands: starts hidden, and its help says only "not available"; not advertised → not available before any argument check, nothing POSTed, 404 cached; forced off makes no request; exact request body, with and without optional fields; JSON receipt; forced on skips detection; validation (missing or unknown category, no, blank or 2001-character details, exactly 2000 accepted); POST 404, 422, 429 (Retry-After, never retried); 401 on detection; post-run refresh runs once when due and is skipped for no requests, override, wire and auth commands; hint present or absent.internal/cli: hidden from root help, agent help,__completeandcommands --jsonwhen not advertised; listed in each once advertised; override in both directions.
Summary by cubic
Adds basecamp feedback, which sends a report about Basecamp itself — a bug, missing capability, confusing behavior, praise, or other — to the Basecamp team using the person's own login. It ships dark, hidden from help, completions, and basecamp commands until a cached detection sees the feedback endpoint answer 200 for the person (404 means off); the answer is cached per host, account, and credential for 24 hours and backs off for an hour after failures. A detection time in the future counts as due, so a clock set back can't hold the cached answer past its 24 hours.
While off, every invocation, including --help, prints only "Feedback is not available." and exits non-zero. BASECAMP_FEEDBACK=1/0 forces the feature on or off, skipping detection; turning it on for everyone is server config only, with no CLI release. The agent skill names feedback only when basecamp commands lists it, with other passages tightened to stay under the skill's size guard. The post-run detection reuses only the credential the command just used (no keyring read or renewal) and is skipped when the cache can't be written.
When on, root help lists the command and errors that came from Basecamp (api_error, validation, not_found) add one hint line to stderr; the JSON envelope on stdout is unchanged and basecamp mcp never carries the hint. A 429 on submit reads as "Feedback limit reached," is never retried, and sets no rate-limit block on later commands.
Tests
internal/feedback,internal/commands, andinternal/clicover detection caching and per-credential separation, override handling, validation, submission shape, error mapping, hint placement, and visibility before and after a 200.