Skip to content

thirdshift setup #126

Closed
6 of 6 issues completed
Closed

Description

@JacobStephens2

Problem Statement

A new thirdshift user has to find the User config section of the README, learn that ~/.thirdshift/config.toml exists, and write TOML by hand to change any default: making every Run a Merge run, fast-forwarding the Launch directory's Base branch, or sending Run notifications. Nothing after installing, and nothing on the first Run, tells them the file exists or what it can hold. Without a file, every setting is silently at its default, so there is nowhere to look to see what can be changed.

Solution

A new command, thirdshift setup, walks the user through Setup: a few questions about the settings that matter most, then writes a complete User config. Every setting appears in it with its real value and a comment saying what it does and what its default is, so the file itself shows everything that can be changed. Running it again changes the answers and keeps the rest of the file as it was.

The first Run on a machine with no User config, started from a terminal, offers Setup before doing anything else, like Claude Code does on its first start. The user can accept, answer the questions, and the Run carries on, or decline, and get the all-defaults file. A Run with no terminal is never interrupted by the offer. The installer (unchanged, generated by dist) ends its output with a one-line hint to run thirdshift setup.

User Stories

  1. As a new user, I want the install output to end by telling me about thirdshift setup, so that I know how to choose my defaults without reading the README.
  2. As a new user, I want my first Run from a terminal to offer Setup when I have no User config, so that I set my defaults before the factory does anything.
  3. As a new user, I want accepting the offer to run the questions and then carry on with the Run I asked for, so that I don't have to type the command again.
  4. As a new user, I want the Run I started to use the answers I just gave, so that choosing "every Run is a Merge run" applies right away.
  5. As a new user, I want a flag in the command I started (--no-merge, --email, --no-email) to still win over the answers I just gave, so that the command means what it says.
  6. As a new user, I want declining the offer to write the all-defaults User config and tell me thirdshift setup changes it, so that I'm not asked again and can still see every setting.
  7. As a user who declined, I want no further offers on later Runs, so that thirdshift doesn't nag.
  8. As a user who runs thirdshift from cron, CI, nohup or an agent's shell, I want a Run with no terminal never to prompt, so that it can't hang waiting for an answer.
  9. As a user who runs thirdshift unattended, I want a Run with no terminal and no User config to write no file and run on the defaults, as today, so that an unattended Run creates nothing in my home folder.
  10. As a user who later runs thirdshift from a terminal, I want the offer to appear then, if there is still no User config, so that an unattended first Run doesn't use up the offer.
  11. As a user who mistyped a command, I want a bad command to exit 2 with the argument error before any offer, so that I fix the command first.
  12. As a user, I want help, version, update and email-test never to offer Setup, so that they stay quick and non-interactive.
  13. As a user, I want to run thirdshift setup at any time, so that I can change my defaults later.
  14. As a user, I want Setup to ask whether every Run should be a Merge run, so that I set merge.always.
  15. As a user, I want Setup to ask whether every Run should first fast-forward my checkout of the Base branch, so that I set launch.pull.
  16. As a user, I want Setup to ask whether I want Run notifications, so that I set email.always.
  17. As a user who wants Run notifications, I want Setup to ask for the address and the sender, so that I set email.to and email.from.
  18. As a user, I want the address question to suggest my GitHub email, so that I can usually just press Enter.
  19. As a user whose GitHub profile email is private, I want the suggestion to fall back to my git user.email, so that I still get a sensible default.
  20. As a user whose git email is a @users.noreply.github.com address, I want it never suggested, so that notifications never go to an address that can't receive mail.
  21. As a user with neither email available, I want the file to hold email.to as a commented-out line, so that I can see where the address goes without an empty address being taken for a real one.
  22. As a user who types an address without @, I want Setup to ask again, so that a typo doesn't end up in the file.
  23. As a user who wants Run notifications and has no RESEND_API_KEY set, I want Setup to print the export line to add to my shell profile, so that I know what is still missing.
  24. As a user, I want Setup never to write the Resend API key anywhere, so that the User config holds no secret.
  25. As a user who set up notifications with RESEND_API_KEY set, I want Setup to offer to send a test email (default No), so that I can check the setup there and then.
  26. As a user, I want Setup not to ask about logs.dir, and to write it at its default, so that the questions stay few and the setting is still visible.
  27. As a user reading my User config, I want every key written with its real value and a trailing comment giving what it does and its default, so that I can tweak it by reading the file.
  28. As an agent reading a User config, I want real TOML values rather than commented-out ones wherever there is a default, so that I can parse and edit it.
  29. As a user who already has a User config, I want Setup to offer my current values as the default answers, so that pressing Enter keeps what I have.
  30. As a user who has commented my User config by hand, I want Setup to edit only the keys it asked about and keep my comments and other values, so that re-running it doesn't destroy my edits.
  31. As a user whose User config lacks some keys, because I wrote it by hand or a newer thirdshift added settings, I want Setup to add them at their defaults, so that afterwards the file lists every key this version knows.
  32. As a user whose User config doesn't parse, I want Setup to refuse with the same error a Run gives and leave the file alone, so that nothing I wrote is overwritten.
  33. As a user scripting a machine, I want thirdshift setup with no terminal to write the defaults without asking, so that thirdshift setup </dev/null sets up a machine unattended.
  34. As a user with a User config, I want thirdshift setup with no terminal to keep my current values and only add missing keys, so that an unattended ssh host thirdshift setup can never reset a configured machine.
  35. As a user who presses Ctrl-C during the offer or the questions, I want no file written and the Run ended before any work, so that a half-answered Setup leaves nothing behind.
  36. As a user whose User config can't be written during a Run's offer, for example because of permissions, I want a warning: line and the Run to carry on with the defaults, so that Setup can't stop my Run.
  37. As a user, I want the offer and the questions on stderr, so that stdout still holds only the pull request URL.
  38. As a user reading thirdshift help, I want thirdshift setup listed with a description, so that I can find it.
  39. As a user reading the README, I want the Install and User config sections to point to thirdshift setup, so that the docs and the install output agree.

Implementation Decisions

  • Command surface. setup joins help, version, update and email-test as a command only when it is the first argument. It takes no arguments; anything after it is an argument error (exit 2). It is listed in the help text.
  • Terminal test. "Has a terminal" means both stdin and stderr are terminals. It decides whether the first Run offers Setup and whether thirdshift setup asks questions or writes the defaults.
  • Questions, in order: merge.always (y/N), launch.pull (y/N), Run notifications, meaning email.always (y/N). If yes, email.to, re-asked until it contains @, and email.from, where an empty answer keeps Resend's shared sender. Then the RESEND_API_KEY hint if the variable is unset or empty. Then, if it is set, an offer to send a test email (default No), which reuses email-test's path unchanged. Each question's default answer is the current value from an existing User config, or else the setting's default.
  • Defaults written out.
    • merge.always = false, launch.pull = false and email.always = false.
    • email.from = "onboarding@resend.dev".
    • logs.dir = "~/.thirdshift/logs".
    • email.to is the suggested GitHub email (below), or a commented-out example line when there is none. It is the only key ever written commented out.
  • File format. Every key is written with its value and a trailing comment giving what it does and its default, in the style of the README's User config example. Sections are in the order merge, launch, email, logs.
  • The GitHub email suggestion.
    • Setup-time only: a Run never looks it up, and a Run with --email and no email.to still stops for want of an address.
    • It is the public profile email from gh api user. If that is null, or the call fails, it is git config --global user.email, unless that ends in @users.noreply.github.com.
    • It never asks for extra gh scopes.
  • Editing an existing User config is in place, with a format-preserving TOML editor (e.g. toml_edit, pure Rust, so it builds under musl per ADR-0003). Comments, key order and values Setup didn't ask about survive. Keys this version knows but the file lacks are added at their defaults. The file is parsed with the same rules a Run uses first; if that fails, Setup exits 1 with the Run's error and does not touch the file.
  • The first-Run offer.
    • Order in a Run: parse arguments (exit 2 on error), then the offer, then load the User config, then everything that follows today (email pre-checks, pre-flight, and so on).
    • When it's made: only when no User config exists and the terminal test passes.
    • The prompt, on stderr: No User config at <path>. Set your defaults now? [Y/n].
    • Yes runs the same questions as thirdshift setup, writes the file, and the Run continues with it.
    • No writes the all-defaults file, prints that thirdshift setup changes it, and the Run continues.
    • A failed write is a warning: line on stderr; the Run continues with the defaults.
    • Ctrl-C during the offer or the questions writes no file and ends the command before any work. No Run notification can be owed yet, since the User config hasn't been loaded.
  • No terminal. thirdshift setup writes the defaults, or over an existing file keeps its values and adds missing keys, without asking. A Run offers nothing, writes nothing, and behaves exactly as today.
  • Install. dist-workspace.toml sets install-success-msg to Run 'thirdshift setup' to choose your defaults, or just start a Run and thirdshift will offer it., and the release workflow is regenerated with dist generate, not edited. The installer takes no new flags and runs nothing after installing.
  • Docs.
    • The README's Install section mentions thirdshift setup after the install command.
    • The User config section describes Setup, the first-Run offer and the no-terminal behavior.
    • The note that update, version and help never read the User config is extended to say they never offer Setup.
    • CONTEXT.md already defines Setup.

Testing Decisions

  • Tests exercise external behavior only, through the binary: the prompts on stderr, stdout, the exit code, and the User config file left on disk (contents and comments). No test reaches into the wizard's internals.
  • Seam: the binary, driven by the existing Scenario harness (temporary $HOME, fake gh, fake claude). No new seam goes into the production code.
  • No-terminal paths use Scenario's existing piped runs:
    • A first Run with no User config writes no file and behaves as today.
    • thirdshift setup writes the full defaults file, or keeps values and adds missing keys over an existing one.
    • A broken User config is refused and left unchanged.
    • setup with an extra argument exits 2.
  • Terminal paths use a new Scenario helper that runs the binary on a pseudo-terminal opened with libc::openpty (libc is already a dependency), feeds scripted answers, and returns stdout, stderr, the exit code and the file. Cases:
    • Accepting the offer, then the Run completes using the new answers, e.g. merge.always makes it a Merge run.
    • A command flag overrides a fresh answer.
    • Declining writes the defaults file and a second Run doesn't offer again.
    • A bad command exits 2 with no offer.
    • An address without @ is asked again.
    • The RESEND_API_KEY hint appears only when the key is unset.
    • Re-running Setup over a hand-commented file keeps the comments.
    • Ctrl-C mid-questions leaves no file.
    • help, version, update and email-test never offer Setup.
  • The GitHub email suggestion: the fake gh is extended to answer api user with an email, null, or a failure, and the scenario's gitconfig sets user.email, including a @users.noreply.github.com case, which must never be suggested.
  • Prior art: tests/user_config.rs for User config behavior through Runs, tests/command_surface.rs for help text and argument errors, and tests/email_test.rs with tests/support/resend.rs for the test-email offer.

Out of Scope

  • Any install script of thirdshift's own, post-install hook, or new installer flags (--no-setup, THIRDSHIFT_NO_SETUP). The dist installer can't run custom logic or accept unknown flags, and the success message is enough. There is no change to thirdshift.app's /install.sh redirect.
  • A --defaults flag for setup; without a terminal, thirdshift setup already writes the defaults.
  • Asking about logs.dir in the wizard (it is written at its default and edited by hand).
  • Reading the primary GitHub email via the user scope.
  • Using the GitHub email as a run-time fallback for email.to.
  • Storing RESEND_API_KEY anywhere, or editing the user's shell profile.
  • A "don't ask again" marker other than the User config itself.

Further Notes

  • Declining the offer still writes the file on purpose: the file's presence is what stops the offer, and a complete file is what shows the user every setting.
  • The GitHub email suggestion comes from the public profile because a default gh auth login token lacks the user scope that reading private addresses needs.

Metadata

Metadata

Assignees

No one assigned

    Labels

    ready-for-agentFully specified, ready for an AFK agent

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions