Problem
RecruitmentApp's only result channel is a file at out/<slug>/<timestamp>-recruitment.txt (via OutputFile.writeAndLog). Two issues:
- 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.
- 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:
- XDG-data default +
CCAS_OUTPUT_DIR + --out (small, benefits every app).
--stdout bare usernames (unblocks clipboard-via-pipe today; gated on stderr-logs audit).
--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.
Problem
RecruitmentApp's only result channel is a file at
out/<slug>/<timestamp>-recruitment.txt(viaOutputFile.writeAndLog). Two issues: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 stagedccasbinary the working directory is arbitrary, soout/litters wherever the user happens tocd. There's no way to relocate it.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:XdgPaths.dataDir(mirrors existingcacheDir/configDir/stateDir).<slug>/<timestamp>-<app>.txtstructure and archive-on-overwrite logic under the new root, soOutputFile.archiveExistingis unchanged.OutputFile(Membership, Stats, …), not just recruitment — call that out in review.2. Configurable root via env (
CCAS_OUTPUT_DIR)CCAS_OUTPUT_DIR, registered inServerEnvKeys(precedent:JOB_LOGS_DIR). Resolves through the existingccas.envoverlay, soccas config set CCAS_OUTPUT_DIR …works.Outputdomain inServerEnvKey.Domain(or fold intoServer).3. Per-run override (
--out <dir>)Layering, consistent with the #67 overlay precedence:
--out <dir>flag >CCAS_OUTPUT_DIRenv /ccas.env> XDG-data default.4.
--stdout— bare usernames for pipingEmit bare usernames (paste-ready, no timing/urls) to stdout so the operator can pipe to their own platform's clipboard tool:
Zero platform detection in our code; this alone ships clipboard-via-pipe.
--stdoutis for piping, not "select from the terminal." Document that; don't advertise manual copy.5.
--copy— clipboard on confirm (interactive only)For the operator who doesn't pipe. On the interactive Y/n confirm path only:
Copied N usernames.after.xclip/pbcopy/clip.exe) entirely — OSC 52 or pipe, not a third fragile path.�(see repo convention).6. Payload shape
The bare-username line already exists as
headerinformatRecruitmentOutput(space-joined). Split cleanly:Consider a
--format bare|reportknob if we want the file/stdout shape selectable.Scope / sequencing
Ship in order of value-per-effort:
CCAS_OUTPUT_DIR+--out(small, benefits every app).--stdoutbare usernames (unblocks clipboard-via-pipe today; gated on stderr-logs audit).--copyOSC 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.