feat(setup): default into GitHub login when setup finishes logged-out on a TTY - #119
Conversation
… on a TTY One command now carries a human as far as automation can go (curl-installer feel): after the checkmarks, an interactive terminal with no session flows straight into `insta login --oauth github` — Enter continues, n skips. Non-TTY (agents, CI, pipes) and -y runs never prompt: a browser OAuth flow cannot work there, so they keep the printed next: hint and prompt.md walks agents through login as its own step. Best-effort: a declined prompt or a failed browser flow leaves a completed setup plus the manual hint, never an error. shouldOfferLogin is pure + tested; the flow is injected (LoginFlow) so tests never touch a real TTY or browser. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
jwfing
left a comment
There was a problem hiding this comment.
Review: default into GitHub login when setup finishes logged-out on a TTY
Summary: Clean, tightly-scoped enhancement that flows an interactive, logged-out setup agent straight into insta login --oauth github behind a pure, well-tested gate; behavior for -y/non-TTY/already-logged-in paths is preserved and the login step is genuinely best-effort.
Requirements context: No /docs/superpowers/ (or docs/specs/) directory exists in this repo — assessing against the PR description and Tony's stated ask ("the one command should carry the whole journey… default login if not logged in makes sense") alone.
Findings
Critical
(none)
Suggestion
- Software engineering —
defaultAskis untested (src/commands/setup.ts:387-393). All four new tests inject a fakeask, so the real readline-based prompt never executes under test. It's thin, so this isn't blocking, but it's the one piece of new production code with zero coverage. SinceshouldOfferLoginalready guarantees both stdin and stdout are TTYs beforedefaultAskruns, the risk is low — noting it for completeness. - Functionality —
defaultAskcan hang on EOF (src/commands/setup.ts:387-393).rl.question(...)resolves only on a line of input; if the user sends Ctrl-D (EOF) at the prompt, the wrapped promise never resolves and setup appears to hang after the ✓ checkmarks. Consider resolving to a decline on the interface'close'event (rl.on('close', () => resolve(true /* treat as decline */))). Interactive-only, low blast radius.
Information
- Test conflates two gates (
test/setup-agent.test.ts:139-145). The'non-TTY (agents/CI) never prompts'case sets bothyes: trueandstdinTty:false/stdoutTty:false, so it doesn't isolate the TTY gate throughsetupAgent. The pure gate is fully covered by theshouldOfferLoginunit test above it, so this is fine as-is; ayes:false+ non-TTY variant would exercise the non--yTTY path end-to-end. - Env correctness (verified, no change needed) —
src/commands/setup.ts:410. The defaultlogin: () => loginOauth('github', {})passes empty opts, but this is correct:envUsehas already persisted (and dropped any foreign session for) the target env at line 423, sologinOauth→ApiClient.load()resolves the right host, including undersetup agent --env staging.loggedInis also (correctly) recomputed fromreadStored()after the switch, so a session dropped by the env switch re-triggers the prompt as intended. The adjacent comment already documents the prod-hostedprompt.mdrationale; a one-liner noting the "env already persisted" invariant here would help future readers.
Dimension coverage
- Software engineering:
shouldOfferLoginis pure and exhaustively unit-tested; the injectedLoginFlowseam keeps tests off real TTY/browser. Import style (node:readline,./auth.js) and error handling match the file. Only gap is the untesteddefaultAsk(Suggestion). - Functionality: Solves the stated ask.
-y/piped/CI never prompt (a loopback OAuth flow can't work there anyway), already-logged-in short-circuits, decline (^n, default-Enter = yes) and browser-flow failure are both swallowed with the manual hint and no exit-code change — matching the "setup already succeeded" contract. Only edge is EOF-at-prompt (Suggestion). - Security: No new user input reaching SQL/shell/HTTP. The catch surfaces
e.messageonly —loginOauth's errors are generic strings (oauth failed: …,state mismatch,timed out), no token/PII leakage. No auth checks weakened; no new dependencies. - Performance: N/A — one conditional readline prompt on an interactive terminal, no hot path, no I/O regressions.
Verdict: approved (informational)
No Critical findings — the two Suggestions and Information notes are non-blocking. Posting as a COMMENT; a human still gives the explicit GitHub approval via the approve flow.
There was a problem hiding this comment.
All reported issues were addressed across 2 files
Reply with feedback, questions, or to request a fix.
Re-trigger cubic
jwfing
left a comment
There was a problem hiding this comment.
Summary
The setup login flow mostly matches the intended human-terminal path, but it does not enforce the PR’s explicit “agents/CI never prompted” requirement outside of non-TTY detection.
Requirements context
I used the PR title/description as the primary behavior spec: after successful setup agent, an interactive logged-out human terminal should default into insta login --oauth github, while -y, piped/non-TTY, agent, and CI runs should keep the manual next: hint and never prompt. Existing README/docs confirm setup agent is the one-command agent onboarding path and login --oauth github is the browser login flow; I did not find a separate linked issue/spec in the checked-out repo.
Findings
Critical
src/commands/setup.ts:375-380,src/commands/setup.ts:463-466— Critical:shouldOfferLoginonly gates onyes, stored-login state, and stdin/stdout TTY. That meansCI=true insta setup agentor an agent-run command with a pseudo-TTY can still reachloginFlow.ask(...)and then launch browser OAuth, despite the PR description explicitly saying agents and CI are never prompted. The repo already treatsAI_AGENTas an agent-mode signal for subprocess behavior (src/ensure-skills.ts:22-27), so this gate needs an injected CI/agent/non-human signal, plus tests forCI=trueand/orAI_AGENT, before prompting.
Suggestion
test/setup-agent.test.ts:121-133— The test name says declined and failed login print the manual hint, but the assertions only check that login is skipped/swallowed andprocess.exitCodeis unchanged. Capturing output or factoring the hint string would make that promised fallback behavior regression-proof.
Information
- No security-relevant changes found: this prompts locally and calls the existing OAuth path; I did not see new token logging, authorization weakening, new dependencies, or unvalidated SQL/shell flows.
- No performance-relevant changes found: this is a post-setup interactive path with no hot-loop, N+1, or large allocation concerns.
Verdict
REQUEST_CHANGES. The critical issue is the missing agent/CI suppression in the new login prompt gate. Local verification note: npm run typecheck could not run in this checkout because tsc is not installed.
…ctive login; TTY-gate test - defaultAsk resolves EOF (Ctrl-D closes readline) as a decline instead of hanging after the checkmarks (john-bot suggestion) - --mcp-token + interactive login: the pre-login registration skipped itself logged-out; re-run registerMcp with the token once the session exists (cubic P2) - non-TTY test now uses yes:false so it exercises the TTY gate, not the -y gate (cubic) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
jwfing
left a comment
There was a problem hiding this comment.
Review — feat(setup): default into GitHub login on a logged-out TTY
Summary: After the setup checkmarks, an interactive human terminal with no stored session now flows straight into insta login --oauth github behind a pure, tested shouldOfferLogin gate; non-TTY / -y / already-logged-in paths are unchanged. Clean, in-scope, well-tested — no blocking issues.
Requirements context: No /docs/superpowers/ (or any docs/specs/) directory exists in this repo — assessed against the PR description and Tony's stated ask alone. Intent ("the one command carries the whole journey; default login if not logged in") is faithfully implemented.
Findings
Critical — (none)
Suggestion
- Functionality / UX —
src/commands/setup.ts:468: the gate treats "stdin & stdout are TTYs" as "can run a browser OAuth flow", but that isn't true over SSH. On a remote-but-interactive shell theloginOauth('github')loopback flow (callback on127.0.0.1of that box) can't complete, so the user eats a failed browser attempt before the best-effort catch prints the manual hint. It's handled gracefully (setup already succeeded, exit code untouched — good), but the failure message atsrc/commands/setup.ts:477doesn't point at the actual escape hatch. Consider mentioninginsta login --devicein that catch message (the manualnext:hint already does), so a headless-over-SSH user is nudged toward the flow that works. Non-blocking.
Information
- Testing —
src/commands/setup.ts:386-396:defaultAsk(the only un-injected real-IO piece, including the EOF/Ctrl-D → decline branch) isn't exercised by a unit test sinceloginFlow.askis injected everywhere. The four new tests cover the gate, the TTY flow, decline/failure best-effort, non-TTY, and the--mcp-token-after-login ordering — solid coverage of the branching. The readline EOF branch is simple enough that this is fine to leave; just noting the one gap. - Correctness (verified, no action) — the
--mcp-tokenordering is right: the pre-loginregisterMcp(setup.ts:455) is a safe no-op while logged out becausedefaultMinterreturnsnull(checksaccessToken, swallows errors) rather than throwing, so it prints the login hint and returns; the post-login re-run atsetup.ts:474then mints and registers. No crash risk on the logged-out mint path. - Style (pre-existing, out of scope) —
setup.ts:420-422prints "non-interactive shell — assuming -y" without actually settingopts.yes = true; harmless here since the new login gate keys offstdoutTtyindependently.
Dimension notes
- Software engineering: TDD honored — 4 new tests added alongside the behavior; injection (
LoginFlow) keeps tests off real TTY/browser. Matches repo conventions (.jsimport specifiers,info()for output, pure-function + injected-effect split). - Functionality: Solves the stated ask; edge cases (EOF, decline, browser failure,
--mcp-tokenrequiring a session first) are all handled. See the SSH suggestion above for the one soft edge. - Security: No new user input reaches SQL/shell/HTTP unsanitized (
answeris only regex-tested); no secrets logged — the catch printse.messagefrom OAuth/network errors, which don't carry the token. No auth checks weakened. No new dependencies (node:readlineis stdlib). - Performance: N/A — a single one-shot readline prompt on an interactive path; no loops, queries, or hot-path work.
Verdict
approved (informational; posted as a COMMENT — the green-check approval remains a separate human action). Zero Critical findings; the one Suggestion and the Information notes are non-blocking.
…H escape hatch) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
jwfing
left a comment
There was a problem hiding this comment.
Review: feat(setup) default into GitHub login on a logged-out TTY
Summary: Clean, well-scoped change that makes insta setup agent flow straight into insta login --oauth github when it finishes on an interactive terminal with no session, gated by a pure/tested predicate and with a best-effort failure posture — no correctness, security, or performance problems found.
Requirements context: No /docs/superpowers/ (or docs/specs/) directory exists in this repo — assessed against the PR description, the cubic summary, and the surrounding code alone. Intent ("Tony's ask: the one command should carry the whole journey… default login if not logged in makes sense") is taken from the PR body.
Findings
Critical
(none)
Suggestion
- Software engineering — test doesn't model the logged-out pre-login mint (
test/setup-agent.test.ts:151-166): In the--mcp-tokentest the injectedmintreturnsinsta_tokunconditionally, so the pre-loginregisterMcpcall (src/commands/setup.ts:455) also mints and would add the MCP server. In productiondefaultMinterreturnsnullwhile logged-out (setup.ts:304), so the pre-login add is correctly skipped and only the post-login add runs. The test still passes because it only asserts a mint+add occur afterlogin, but it therefore wouldn't catch a regression that double-registers. Consider amintmock that returnsnulluntilloginhas run, and asserting nomcp/mintevents occur beforeloginAt— that pins the actual production contract (register exactly once, after the session exists).
Information
- Software engineering —
defaultAskis not unit-tested (src/commands/setup.ts:386-396): The readline/EOF branch is the one piece exercised only via the injected default (allsetupAgenttests supply their ownask). It's thin and its EOF-as-decline behavior is well-commented, so this is acceptable; noting it only for completeness. Behavior looks correct:rl.on('close', …→'n')resolves EOF as a decline, and empty Enter →!/^n/i→ proceed, matching the(Y/n)default. - Functionality — post-login re-registration is correctly ordered (
setup.ts:468-479): Verified the sequence for--mcp-token, logged-out, accept-prompt: pre-loginregisterMcp(…, true)short-circuits on a null mint (setup.ts:327-329) before anymcp add, then post-loginregisterMcp(run, mint, true)re-loads the freshly-persisted session viadefaultMinter→ApiClient.load()and adds once.loginOauth('github', {})persists into the same env already pinned earlier byplanSetupEnv/envUse, so the session lands in the right config. No double-registration in production.
Dimension coverage
- Software engineering: Good — 4 new behavioral tests plus a pure-predicate test; DI pattern (
LoginFlow) is consistent with the file's existing seams (run,mint,readStored,switchEnv); import style (./auth.js) andinfo(...)messaging match conventions. New optionalloginFlowparam is last, so existing callers are unaffected. - Functionality: Solves the stated ask. Handles logged-in (early return), non-TTY/
-y/piped (never prompts), decline, EOF/Ctrl-D, browser-flow failure (swallowed, exit code untouched), and remote/SSH guidance toward--device. No gaps found. - Security: No security-relevant changes — no new user input reaches SQL/shell; the bearer token comes from
mint()(not user input) and is not logged; the failure message surfaces only the error text, not the token. Auth checks unchanged. - Performance: No concerns — readline is created only on the interactive path; no loops, queries, or blocking I/O added to any hot path.
Verdict
approved (informational — posted as a comment; explicit GitHub approval is a separate human action). Zero Critical findings; the Suggestion/Information items are optional polish.
jwfing
left a comment
There was a problem hiding this comment.
Summary
The PR cleanly adds a best-effort interactive GitHub OAuth prompt after successful insta setup agent when no stored session is present, without changing the non-interactive or already-logged-in paths.
Requirements context
I used the PR title/description as the primary intent: after setup succeeds on an interactive human TTY with no stored session, offer insta login --oauth github; skip prompting for -y, non-TTY/agent/CI paths, and already-logged-in users; preserve success exit behavior on decline or login failure; rerun MCP token registration after successful interactive login when --mcp-token was requested. Local README/setup/auth docs confirm setup agent installs agent skills and MCP, while login is normally insta login --oauth github; CONTRIBUTING and .claude/skills/developing-insta-cli/SKILL.md confirm the Node/TS/commander style, injected side-effect test pattern, and npm run typecheck && npm test gate. I did not find a local linked issue or local skills/insta/cli-reference.md; this is a behavior change rather than a command/flag surface change.
Findings
Critical
(none)
Suggestion
(none)
Information
(none)
Verdict
Approved per the rubric: no Critical findings. Software engineering: the LoginFlow injection follows existing side-effect isolation conventions, and tests cover gating, TTY login, decline/failure, non-TTY behavior, and post-login --mcp-token registration. Functionality: the implementation matches the described best-effort flow; security: no new dependency, shell, SQL, or secret exposure issues found; performance: no hot-path or unbounded work introduced. Verification note: I inspected code and tests only, and did not run npm run typecheck && npm test because the request specified read-only/non-mutating review.
…ter the session exists (r2d2 r3) Mint mock now returns null until login (matching production defaultMinter while logged out), and the test asserts no mcp add before login and exactly one after. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
jwfing
left a comment
There was a problem hiding this comment.
Summary
No critical issues found; the change is narrow, test-covered, and follows the repo's setup-command dependency-injection style.
Requirements Context
I used the PR title/description and public PR page (#119), plus repo docs. README.md:28-45 documents setup agent as the one-command agent setup path, README.md:77-89 documents browser OAuth, and .claude/skills/developing-insta-cli/SKILL.md:17-18 calls out injected side effects as the local testing convention. I did not find a linked issue or more specific design doc; this PR does not add or rename commands/flags, so the cli-reference mirror requirement in .claude/skills/developing-insta-cli/SKILL.md:36-38 does not appear to apply.
Findings
Critical
(none)
Suggestion
- Functionality:
src/commands/setup.ts:455-474- In a logged-out interactive--mcp-tokenrun, setup first prints the existing “MCP not registered (--mcp-token needs a login)” hint, then prompts for login, then may successfully register MCP after login. That is functionally best-effort, but noisy in the happy path. Consider deferring token-backed MCP registration until after the interactive login attempt whenshouldOfferLoginis true, or suppressing the pre-login hint for that path. - Functionality:
src/commands/setup.ts:379-380- The “human terminal” gate is purelystdinTty && stdoutTty. If “agents/CI never prompt” is intended literally for PTY-backed automation, a CI or agent run with allocated TTYs and no-ycould still prompt and start browser OAuth. Non-blocking because the installer agent path passes-yand typical CI is non-TTY, but an env-level guard or doc wording could make the contract sharper.
Information
- Software engineering:
test/setup-agent.test.ts:99-160- The added tests cover the pure gate, TTY accept path, decline/failure behavior, non-TTY behavior, and post-login MCP token retry. The injectedLoginFlowmatches the repo convention for side-effectful code. - Security:
src/commands/setup.ts:469-478- No new SQL/shell execution path, dependency, or auth weakening. Prompt input is only used as a yes/no decision, and OAuth remains delegated to the existingloginOauthpath. - Performance:
src/commands/setup.ts:468-475- No performance concern; the added work is a single prompt and optional OAuth/MCP retry after setup completes, not a hot path.
Verdict
Approved per rubric: zero Critical findings. I did not run npm run typecheck or npm test because the review instructions were read-only, so this verdict is based on source and test inspection.
jwfing
left a comment
There was a problem hiding this comment.
Review: feat(setup): default into GitHub login when setup finishes logged-out on a TTY
Summary: A tightly-scoped, well-tested UX improvement — after the ✓ checkmarks, an interactive logged-out session now flows straight into insta login --oauth github, gated by a pure shouldOfferLogin, with all failure/decline paths kept best-effort. I found no blocking issues.
Requirements context: No /docs/superpowers/ (or docs/specs/, or any docs/) directory exists in this repo — assessing against the PR description alone. Intent as stated: carry the one-command journey through the single genuinely-human step (browser auth) on interactive terminals, while leaving agents/CI/-y/already-logged-in behavior unchanged. The implementation matches that intent.
Critical
(none)
Suggestion
(none blocking; see Information.)
Information
Software engineering
- Good test coverage for the changed behavior:
test/setup-agent.test.ts:99-171adds 5 assertions across the pure gate, the TTY happy path, decline + failed-login (exit code preserved), the non-TTY gate, and the--mcp-tokenpost-login re-registration ordering. All fourshouldOfferLoginfalse-branches are exercised. The injectedLoginFlowkeeps tests off a real TTY/browser — consistent with the existingRunner/TokenMinterinjection style in this file. - The
defaultAskreadline path (src/commands/setup.ts:386-396) is the one piece exercised only by inspection (it's injected away in tests). It reads correctly: EOF (close) resolves to'n'before the question is answered, and the post-answerrl.close()re-firesclosebut the Promise is already settled, so the answer wins. Enter (empty) →!/^n/i.test('')→ proceed;n/no/N→ decline. No action needed — just noting where the coverage boundary sits.
Functionality
--mcp-tokenordering is correct: the pre-loginregisterMcp(run, mint, true)atsrc/commands/setup.ts:455mintsnullwhile logged out and adds nothing (printing the "needs a login" hint), then the post-loginregisterMcp(run, mint, true)at:474mints and adds exactly once. Verified againstregisterMcp(:317-340) and the test at:145-171.loginOauth('github', {})matches its signature (src/commands/auth.ts:40) and throws on any failure, so thecatchat:476-479reliably degrades to the manual hint with the exit code untouched.- Remote/SSH note: on an SSH box both stdio are TTYs, so
shouldOfferLoginreturns true and the browser flow is attempted. It degrades correctly —browserOauthrejects after a 2-minute timeout (src/commands/auth.ts:192), which is caught and surfaces theinsta login --devicehint. Worth being aware that a remote user can wait up to 2 min after a successful setup before that hint appears; this is pre-existinginsta loginbehavior, not introduced here, so no change requested.
Security
- No security-relevant regressions. Provider is hard-coded (
'github'), no new user input reaches shell/SQL/HTTP, and no secrets are logged — thecatchprintse.message(a timeout/network string), andloginOauthonly echoes the account email/id on success, not the token.
Performance
- Negligible. One extra readline prompt plus the existing OAuth flow on the interactive path only. The
readStored()double-read (:427and:462) is pre-existing local-file I/O, not introduced by this PR.
Verdict
approved (informational — the human still gives the explicit GitHub approval). Zero Critical findings; the change is correct, in-scope, and well-tested. Posting as a COMMENT per the review policy.
jwfing
left a comment
There was a problem hiding this comment.
Summary
No blocking issues found; the change is focused, tested around the new setup/login gate, and does not introduce security or performance concerns.
Requirements context
I used the PR title/description at #119 as the primary intent: after successful interactive setup agent, offer GitHub OAuth login only when there is no stored session, while preserving -y, non-TTY, already-logged-in, decline, and failed-login behavior. Local context checked: README.md:28-45 for setup expectations, README.md:77-89 for OAuth behavior, and .claude/skills/developing-insta-cli/SKILL.md:17-18 for the repo's dependency-injection testing convention. I did not find a separate design/spec document for this exact default-login behavior.
Findings
Critical
(none)
Suggestion
(none)
Information
(none)
Verdict
Approved per the requested verdict rule: no Critical findings. Software engineering: the pure gate and injected LoginFlow fit existing testability conventions, with tests covering TTY, -y, non-TTY, decline/failure, and --mcp-token retry paths. Functionality: the implementation matches the PR description in the reviewed paths. Security: no new dependency, shell construction, token logging, SQL, or auth weakening. Performance: this is a one-shot setup prompt plus an optional second MCP registration, not a hot path.
Verification: git diff --check main...HEAD passed. npm run typecheck could not run because tsc is not installed in this checkout; I did not install dependencies or run npm test because this review was explicitly read-only/no-mutating-commands.
Ships #119: setup agent defaults into GitHub login when it finishes logged-out on an interactive terminal (EOF-safe prompt; --mcp-token re-registers after login; agents/CI never prompted). Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Tony's ask: the one command should carry the whole journey, curl-installer style, and "default login if not logged in makes sense".
After the ✓ checkmarks, when setup ran on an interactive human terminal with no stored session, it now flows straight into
insta login --oauth github— Enter continues into the browser,nskips.shouldOfferLogin(pure, tested) gates it:-yruns, piped stdin/stdout, agents, CI: never prompted (a browser OAuth flow can't work there; they keep the printednext:hint, and prompt.md walks agents through login as its own step, relaying the sign-in link to the human).next: insta project createhint.The flow is injected (
LoginFlow) so tests never touch a real TTY or browser. Gate: typecheck clean, 410/410 tests (4 new).Ships in 0.0.39 (0.0.38 = #116 alone, releasing now via #118 — kept separate so the staging fix isn't held by this review).
🤖 Generated with Claude Code
Summary by cubic
Defaults setup into GitHub OAuth login when it finishes on an interactive TTY without a session, instead of only printing a manual next step. This reduces first-run friction while keeping non-interactive and already-logged-in behavior unchanged.
shouldOfferLogin(yes, loggedIn, stdinTty, stdoutTty); when true, prompts “log in now with GitHub? (Y/n)” and runsinsta login --oauth githubon Enter.-yruns never prompt; logged-in users go straight to the project-create hint. EOF (Ctrl-D) counts as decline. Declined or failed browser login is best-effort: setup still succeeds, prints the manual hint, and points SSH users toinsta login --device.--mcp-token, re-runs MCP token registration after a successful interactive login so the token mints with the new session; tests pin that registration happens exactly once and only after login.LoginFlow(ask/login/stdinTty/stdoutTty) with a readline-based default; adds tests for gating, TTY flow, decline/failure, non-TTY cases, and the--mcp-tokencontract.Written for commit c4ee1f0. Summary will update on new commits.