Skip to content

feat(cli): current-club visibility; scope completion to managed clubs#183

Merged
Sootopolis merged 1 commit into
mainfrom
feat/club-cli-ux
Jul 22, 2026
Merged

feat(cli): current-club visibility; scope completion to managed clubs#183
Sootopolis merged 1 commit into
mainfrom
feat/club-cli-ux

Conversation

@Sootopolis

Copy link
Copy Markdown
Owner

What

The current club was effectively write-only. Nothing printed it, ccas club remove left it pointing at a club it had just disowned, and job submission gates on the club row rather than managed status — so a bare command kept running real jobs against a club the user had stopped managing. With recruit --stdout that was destructive, auto-confirming invites on it.

  • use-club gains show/clear. Bare ccas use-club prints the current club (exit 2 when unset); --clear drops it. On a set it makes a bounded best-effort /api/managed-clubs fetch to refresh the completion cache and report whether the club is actually managed, degrading to an offline hint when the server doesn't answer. The config write always lands, so it still works offline — which is why it stays a top-level local command rather than moving under club.
  • club remove clears current_club when it names the removed club; club list marks the current one with * and warns when it isn't managed (including when nothing is managed).
  • Completion cache is scoped to the managed set instead of /api/clubs, which held 9,762 slugs on a dev machine — nearly all opponent and scouted clubs no command can target. Invalidated on club add/remove and on a Club not found submit, which is the shape a Chess.com slug rename takes.

Two parsing/robustness fixes found while testing

use-club now captures every positional with repeat and validates the arity: atMost(1) silently truncates extras, and zio-cli swallows an option written after a positional — together those made ccas use-club <slug> --clear a silent set of the very club being cleared. It now fails with the working order.

The verify probe needs disconnect before timeout. Plain timeout awaits the interrupted fiber and zio-http's connect path is uninterruptible with a 30s default, so the nominal 2s bound measured 31s against a black-holed host. Now 3.2s (JVM boot + the real bound). The same trap applies to the rest of Dispatcher — filed as #182.

CompletionCache stops collapsing failure modes into "empty": readClubs returns None when the cache exists but can't be read, and writeClubs reports whether the write landed, so an unwritable cache directory no longer kills shell completion silently.

Fixes

Closes #172, #173, #174, #175, #178, #179.

Partially addresses #176 (cache invalidation + rename hint landed; the structural fix — the server gating on exact Club.selectBySlug before rename recovery can run — is #180) and #181 (the use-club write-failure surfacing landed; seedClubs/appendJob/invalidate still fail silently).

Untouched: #177, #180, #182.

Testing

1111 tests pass (up from 1089); CompletionCache had zero coverage and now has a suite, including forced read/write failures and a TestClock-pinned TTL boundary.

Every new behaviour was also driven end-to-end against the staged binary:

behaviour result
use-club show / set / clear / unmanaged / offline all paths
club remove auto-clears current club pointer gone, api_url preserved
club list * marker, unmanaged warning, empty set all three
Club not found → invalidate + rename hint hint shown, cache dropped, exit 1
verify timeout against a black-holed host 31s → 3.2s
empty managed set no longer truncates the cache 3 slugs preserved
flag-after-positional rejected, exit 2, correct order suggested

Also fixed a test-hygiene bug: tempDir registered only the directory for deleteOnExit, so it was never empty at exit — 71 leaked trees in /tmp, now zero after a full run.

🤖 Generated with Claude Code

https://claude.ai/code/session_01P34RY3oqcPmTNRC5XwFTAS

The current club was write-only: nothing printed it, `club remove` left it
pointing at a club it had just disowned, and job submission gates on the `club`
row rather than managed status — so a bare command kept running real jobs
against a club the user had stopped managing. With `recruit --stdout` that was
destructive, auto-confirming invites on it.

- `use-club` takes an optional slug: bare prints the current club (exit 2 when
  unset), `--clear` drops it. On a set it makes a bounded best-effort
  `/api/managed-clubs` fetch to refresh the completion cache and say whether the
  club is actually managed, degrading to an offline hint when the server does
  not answer. The config write always lands, so it still works offline.
- `club remove` clears `current_club` when it names the removed club, and
  `club list` marks the current one with `*`.
- The completion cache is sourced from the managed set rather than `/api/clubs`,
  which held 9,762 slugs on a dev machine — nearly all opponent and scouted
  clubs no command can target.

`use-club` captures every positional with `repeat` and validates the arity,
because `atMost(1)` silently truncates and zio-cli swallows an option written
after a positional; together those made `ccas use-club <slug> --clear` a silent
set of the very club being cleared.

`CompletionCache` stops collapsing its failure modes into "empty": `readClubs`
returns `None` when the cache exists but cannot be read, and `writeClubs`
reports whether the write landed, so an unwritable cache directory no longer
kills shell completion silently. Its TTL check reads `Clock.instant` so the
boundary is testable.

The verify probe needs `disconnect` before `timeout`: plain `timeout` awaits the
interrupted fiber and zio-http's connect path is uninterruptible with a 30s
default, so a nominal 2s bound measured 31s against a black-holed host.

Closes #172, #173, #174, #175, #178, #179.
Refs #176, #181.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01P34RY3oqcPmTNRC5XwFTAS
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

CLI help and docs name commands that don't exist ('ccas use', 'ccas manage|unmanage|managed')

1 participant