Skip to content

Recruitment output: sensible default dir, configurable location, stdout/clipboard delivery #153

Description

@Sootopolis

Problem

RecruitmentApp's only result channel is a file at out/<slug>/<timestamp>-recruitment.txt (via OutputFile.writeAndLog). Two issues:

  1. The out/ default is a dev accident. It was chosen because core CCAS was developed by running from IntelliJ, where cwd is the repo root. For a staged ccas binary the working directory is arbitrary, so out/ litters wherever the user happens to cd. There's no way to relocate it.
  2. The real deliverable is the username list to paste into Chess.com (club invites / messages). The current flow forces the operator to open a file and copy from it. Timing / evaluated-count are metadata; the paste-ready list is the job.

Proposal

Reframe around the deliverable: get the invited usernames into the operator's clipboard with minimal friction, and make the on-disk record land somewhere sane.

1. Sensible default output root (XDG data dir)

Output artifacts are user data, so the natural home is XDG_DATA_HOME:

${XDG_DATA_HOME:-$HOME/.local/share}/ccas/output/<slug>/<timestamp>-recruitment.txt
  • Add XdgPaths.dataDir (mirrors existing cacheDir / configDir / stateDir).
  • Preserve the <slug>/<timestamp>-<app>.txt structure and archive-on-overwrite logic under the new root, so OutputFile.archiveExisting is unchanged.
  • This affects all apps that use OutputFile (Membership, Stats, …), not just recruitment — call that out in review.

2. Configurable root via env (CCAS_OUTPUT_DIR)

  • New optional env key CCAS_OUTPUT_DIR, registered in ServerEnvKeys (precedent: JOB_LOGS_DIR). Resolves through the existing ccas.env overlay, so ccas config set CCAS_OUTPUT_DIR … works.
  • Probably a new Output domain in ServerEnvKey.Domain (or fold into Server).

3. Per-run override (--out <dir>)

Layering, consistent with the #67 overlay precedence: --out <dir> flag > CCAS_OUTPUT_DIR env / ccas.env > XDG-data default.

4. --stdout — bare usernames for piping

Emit bare usernames (paste-ready, no timing/urls) to stdout so the operator can pipe to their own platform's clipboard tool:

ccas recruit myclub --stdout | wl-copy      # wayland
ccas recruit myclub --stdout | pbcopy       # mac
ccas recruit myclub --stdout | xclip -sel c # x11

Zero platform detection in our code; this alone ships clipboard-via-pipe.

  • Caveat: a pipe carries raw bytes (clean), but manually mouse-selecting from the terminal mangles text (wrapping / spacing). So --stdout is for piping, not "select from the terminal." Document that; don't advertise manual copy.
  • Prerequisite: logs must go to stderr, not stdout, or ZIO log lines interleave with the payload and break the pipe. Audit where the ZLogger writes before shipping this.

5. --copy — clipboard on confirm (interactive only)

For the operator who doesn't pipe. On the interactive Y/n confirm path only:

  • Copy the bare-username payload via OSC 52 terminal escape (terminal grabs the clipboard itself — no external binary, works over SSH; most modern terminals, tmux needs config). Base64-encode; mind terminal size caps.
  • Print Copied N usernames. after.
  • Headless / scheduler path = no-op (no TTY, no clipboard).
  • Skip shell-out (xclip/pbcopy/clip.exe) entirely — OSC 52 or pipe, not a third fragile path.
  • Source note: never embed a raw ESC byte in Scala source — use (see repo convention).

6. Payload shape

The bare-username line already exists as header in formatRecruitmentOutput (space-joined). Split cleanly:

  • paste payload = bare usernames (stdout / clipboard),
  • report = full context with timing + profile URLs (the file).

Consider a --format bare|report knob if we want the file/stdout shape selectable.

Scope / sequencing

Ship in order of value-per-effort:

  1. XDG-data default + CCAS_OUTPUT_DIR + --out (small, benefits every app).
  2. --stdout bare usernames (unblocks clipboard-via-pipe today; gated on stderr-logs audit).
  3. --copy OSC 52 (polish for the non-piping operator).

Depends on / relates to #67 (env overlay machinery), area:cli.

Related: deterministic username ordering is split into its own issue (sort by player_id) so it can land independently of this redesign.

Metadata

Metadata

Assignees

No one assigned

    Labels

    area:cliCLI binary (subcommands, completions, config, packaging)area:recruitmentRecruitmentApp and related logicenhancementNew feature or request

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions