Skip to content

feat(quickstart): interactive provider selection + API-key entry#18

Merged
samestrin merged 7 commits into
mainfrom
feat/interactive-quickstart
May 1, 2026
Merged

feat(quickstart): interactive provider selection + API-key entry#18
samestrin merged 7 commits into
mainfrom
feat/interactive-quickstart

Conversation

@samestrin

Copy link
Copy Markdown
Owner

Summary

`llm-env quickstart` becomes the v1.5 funnel UX promised in `docs/claude-code-quickstart.md`. When run interactively, it now:

  1. Prompts the user to choose which catalog(s) to add (Synthetic, Alibaba, or both).
  2. For each chosen catalog: provisions the providers as before, then prints the signup URL with the embedded referral code, prompts for the API key (input hidden), appends `export LLM__API_KEY='...'` to `/.bashrc` or `/.zshrc`, and runs a verify call to confirm the key works.
  3. Skips the prompt entirely if the key is already set (env or rc-file).
  4. Falls back to "print this export line manually" for unsupported shells (fish/csh/tcsh).

Non-interactive paths preserved exactly: `./llm-env quickstart` from CI/scripts (`! -t 0`) does what it always did — provision every available catalog, no prompts. Existing 21 BATS tests passed unmodified.

Adds positional-arg dispatch for scripted use:

  • `llm-env quickstart synthetic`
  • `llm-env quickstart alibaba`
  • `llm-env quickstart synthetic,alibaba` (whitespace tolerated)
  • `llm-env quickstart all`

Test plan

  • 30 quickstart selection tests pass
  • 28 interactive-helper tests pass (mocked curl, no real network)
  • Full BATS suite (unit + integration + system) green
  • shellcheck clean on `llm-env`, `install.sh`, `tests/run_tests.sh`
  • Manual e2e against `mktemp -d` HOME confirms positional-arg routing, idempotency, and "no new providers added" footer
  • Docker e2e (`tests/system/docker_e2e_runner.sh`) passes — non-TTY path unchanged
  • Real `~/.config/llm-env/config.conf` SHA verified unchanged before/after testing
  • Cumulative adversarial pass found and fixed one bug (CLI dispatcher was dropping the positional arg). Regression guard added.

Adversarial coverage

Hostile inputs verified:

  • Keys containing `'`, `"`, `$`, `\`, `` \` round-trip correctly through single-quote-escaped export lines
  • Empty/'s'/whitespace-only key input → skip, no rc write
  • rc file as a directory → graceful nonzero
  • rc file with no write permission → graceful nonzero
  • rc file lacking trailing newline → trailing newline added before append
  • Re-run with same config → idempotent (no duplicate exports, no re-prompts)
  • Bogus positional arg → nonzero exit with helpful error
  • Selecting a source whose JSON file isn't present → nonzero error
  • mocked HTTP for cmd_test verify (success + failure paths) — no real API calls in tests

samestrin added 7 commits May 1, 2026 11:41
Adds 8 new BATS tests that pin the upcoming behavior:
- 'synthetic'/'alibaba'/'all'/'synthetic,alibaba' positional args
  honor the selection.
- Bare cmd_quickstart in non-TTY (BATS' run) falls back to 'all'.
- Unknown arg returns nonzero with a helpful error.
- 'synthetic' with only alibaba JSON present errors out.
- Whitespace in comma-separated lists tolerated.

Currently 21 existing + 4 new pass (the 4 spurious passes will start
exercising real logic once dispatch is implemented in green phase).
4 new tests fail because dispatch and arg validation are not yet
implemented.
cmd_quickstart now accepts an optional positional arg to select which
catalog(s) to provision:

  llm-env quickstart                    # all available (CI-safe)
  llm-env quickstart all
  llm-env quickstart synthetic
  llm-env quickstart alibaba
  llm-env quickstart synthetic,alibaba  # comma-separated, whitespace tolerated

Unknown sources produce a clear error and non-zero exit. Selecting a
source whose JSON file isn't present is also an error (not a silent
no-op).

New helper _qs_resolve_selection encapsulates the parse-validate-emit
logic so it can later be reused by the interactive menu (Phase 2).
The non-TTY default path is unchanged: when run with no arg, every
available JSON is processed, matching today's behavior — confirmed
by the existing 21 BATS tests which use BATS' run command (no TTY).

29/29 quickstart BATS tests pass; full unit/integration/system suites
green.
Adds tests/integration/test_quickstart_interactive.bats with 25 tests
covering the five helpers needed by the interactive quickstart flow:

  _qs_detect_shell_rc      — bash/zsh/fish/csh/tcsh/other
  _qs_key_already_set       — env, rc-file, leading-whitespace, quoting
  _qs_append_export_to_rc   — single-quote escaping, $/`/\ metachars,
                              idempotent (no duplicate export lines),
                              missing-trailing-newline handling
  _qs_prompt_api_key        — empty/'s'/whitespace-only treated as skip
  _qs_verify_key            — mocked curl, success and failure paths

Plus one integration test through cmd_quickstart with stubbed
interactivity to confirm env-already-set short-circuits the prompt.

curl/wget calls mocked throughout — no network in tests, no API
quota consumed. Plan requirement: "Tests must use mocks/fakes for
all campaign operations" applied here as "no real provider calls".

Currently red — helpers don't exist yet (exit 127). Implementation
arrives in the next commit.
Wires up the user-facing funnel for v1.5: when running interactively,
cmd_quickstart now:

  1. (already in last commit) Resolves which catalog(s) to provision.
  2. For each selected source, processes the JSON as before, then:
  3. Skips key entry entirely if LLM_<vendor>_API_KEY is already set
     in env OR already exported in the user's shell rc.
  4. Otherwise prints the signup URL with the embedded referral code,
     pauses for the user to grab their key, then prompts for the key
     (input hidden).
  5. Empty/'s'/whitespace-only input = skip — no rc write.
  6. Otherwise appends `export VAR='<key>'` to the user's shell rc
     (~/.bashrc or ~/.zshrc, mirroring install.sh's detection).
  7. Single-quote-escapes the key for safe embedding ($, `, \, '
     all round-trip correctly).
  8. Refuses to duplicate the export if a line for the same VAR
     already exists.
  9. Verifies the key by invoking cmd_test against
     anth_<vendor>_kimi-k2.5; failures print a warning but never
     abort quickstart (verification is informational).

Five new helpers do the work:
  _qs_is_interactive        TTY-detection wrapper (stubbable).
  _qs_detect_shell_rc       bash/zsh → ~/.{bash,bash_profile,z}rc;
                            fish/csh/tcsh/other → empty (fallback).
  _qs_key_already_set       env-var or rc-file export-line check.
  _qs_prompt_api_key        masked read with skip-on-empty.
  _qs_append_export_to_rc   idempotent, single-quote-escaped append.
  _qs_verify_key            cmd_test wrapper, always returns 0.

Non-TTY behavior preserved exactly — the existing 21 BATS tests and
the docker e2e test still pass without modification because BATS'
`run` doesn't allocate a TTY, so _qs_is_interactive returns false
and the entire interactive branch is skipped.

25/25 new interactive tests pass; 29/29 quickstart tests pass.
Adds 3 hostile-input cases to lock in graceful behavior:

- append_export with rc path that's a directory → nonzero, no crash
- append_export with rc path that's unwritable (chmod 000) → nonzero
- append_export with a value containing an embedded newline → safely
  embedded inside the single-quoted form (newline preserved through
  source-and-read round-trip)

28/28 tests pass.
README "Quickstart" section now explains the menu prompt, the
positional-arg shortcuts (synthetic/alibaba/all/comma-list), and
that non-TTY invocation falls back to the all-providers default.

docs/claude-code-quickstart.md collapses former Steps 2-5 (run
quickstart, sign up, paste key, test) into a single Step 2 since
quickstart now does signup-prompt-paste-verify in one flow.
Subsequent steps renumbered (Step 3 = reload shell, Step 4 = set
provider, Step 5 = run claude, Step 6 = switch models).
Cumulative adversarial review caught this: \`./llm-env quickstart
synthetic\` from the CLI was silently ignoring the arg and
provisioning all sources. The case-statement dispatcher at the
bottom of llm-env was calling \`cmd_quickstart\` with no args
where it should have shifted past the subcommand name and
forwarded "$@".

The BATS tests passed because they call \`cmd_quickstart\`
directly, bypassing the dispatcher. Adding a regression guard
test that invokes the script-as-binary so future bugs at the
dispatcher boundary get caught.

30/30 quickstart BATS tests pass.
@samestrin samestrin merged commit 31672ee into main May 1, 2026
8 checks passed
@samestrin samestrin deleted the feat/interactive-quickstart branch May 1, 2026 20:38
samestrin added a commit that referenced this pull request May 1, 2026
Interactive quickstart (PR #18) is the v1.6.0 headline feature.
Backfills CHANGELOG with the user-visible additions and explicitly
notes that non-TTY behavior is preserved exactly so CI and scripted
installs are unaffected.
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.

1 participant