Repository navigation
feat(cli): add a quick start interactive wizard - #1334
Conversation
There was a problem hiding this comment.
🟡 Changes recommended
The wizard currently prompts even when stdout is redirected (making prompts invisible) and namespace collection can accept empty entries; both are user-facing correctness issues.
Once you've addressed the issues Copilot identified, you can request another Copilot review.
Pull request overview
Adds an interactive “init” wizard to the SchemaBot CLI so first-time database setup can be completed without knowing every flag, while keeping the underlying initialization/verification path shared with non-interactive usage.
Changes:
- Make
schemabot initaccept missing flags and, when run in a terminal, prompt for the required decisions before calling the existing shared init workflow. - Add wizard-focused unit/integration coverage and update docs with an end-to-end initialization walkthrough.
- Add scripts + assets to record and render the demo wizard/plan GIF used by the new documentation.
File summaries
| File | Description |
|---|---|
| scripts/render-init-demo.cjs | Renders the recorded wizard + first plan into a GIF for docs. |
| scripts/record-init-demo.py | Records real CLI wizard output and a follow-up plan into a JSON “recording”. |
| pkg/cmd/main_test.go | Adjusts CLI parsing expectations so init no longer requires flags at parse-time. |
| pkg/cmd/commands/init.go | Routes init through input collection (wizard/non-interactive) before running the shared initialization workflow; adds progress reporting hooks. |
| pkg/cmd/commands/init_wizard.go | Implements missing-input detection and the interactive prompt flow (wizard). |
| pkg/cmd/commands/init_wizard_test.go | Unit tests for wizard input collection, cancellation, and non-interactive missing-input reporting. |
| integration/localruntime/init_test.go | Integration coverage for --non-interactive --json missing-input output. |
| docs/init.md | New docs page describing interactive and non-interactive initialization, with examples and demo GIF. |
| assets/src/init-demo.html | HTML renderer that animates the recorded wizard/diff/plan for the demo GIF. |
| assets/src/init-demo-recording.json | Recorded wizard session + plan output used by the demo renderer. |
Review details
Suppressed comments (1)
pkg/cmd/commands/init_wizard.go:148
- Namespace parsing trims whitespace but can still append empty entries when the user types extra commas (e.g.,
shop,). That makesmissingInputs()think scope is provided, but later validation fails (or could accidentally proceed with an empty scope). Re-prompt until at least one non-empty namespace is collected.
if len(cmd.Namespaces) == 0 {
fallback := cmd.Database
if cmd.Type == "postgres" {
fallback = "public"
}
selected, err := ask("Namespaces (comma-separated)", fallback)
if err != nil {
return err
}
for namespace := range strings.SplitSeq(selected, ",") {
cmd.Namespaces = append(cmd.Namespaces, strings.TrimSpace(namespace))
}
}
- Files reviewed: 10/11 changed files
- Comments generated: 2
- Review effort level: Lite
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
|
🤖 Review findings - created by Kiran's code review agent - for schemabot/pull/1334, cb69e65. Verdict: 7 findings — 0 blocking, 4 non-blocking, 3 suggestions. The split the PR set out to keep is genuinely kept: the wizard collects decisions and Non-blocking1. The reuse prompt gates on the path existing, not on schema files existing, and declining aborts instead of importing. 2. A trailing comma in the namespace answer discards the whole session. 3. 4. Progress output is conditional on flag completeness rather than on there being a human. General suggestions5. 6. 7. Prompt-layer polish. An explicitly supplied The one thing that could have broken, verified
Verified correct
This review was generated by Claude Code (claude-opus-5). |
|
🤖 Thanks for the detailed review. I checked all seven findings against the current wizard and addressed the remaining items in 66ec952, 25f49ab, and 240628e:
The full CLI command package passed with the race detector, and the required commit/push hooks passed. CI is running on the updated head. — Codex (GPT-6) |
|
🤖 Review findings - created by Kiran's code review agent - for schemabot/pull/1334, ee5457d. Scope: delta re-review only — Verdict: 8 findings — 2 blocking (a wizard dead-end on multiple explicit namespaces; ctrl+c no longer stops Blocking
Non-blocking
General suggestions
The one thing that could have broken, verifiedThe riskiest new mechanism is the empty-directory publish path, which Verified correct
This review was generated by Claude Code (claude-opus-5). |
|
🤖 Addressed the eight findings in 04fe496:
I also cached connection summaries and directory disclosure outside rendering, added scrolling on short wizard screens, aligned connection-check cancellation cleanup with discovery, removed the unreachable initial-discovery branch and redundant interactive assignment, and added credential-redaction tests for both local connection helpers. The PostgreSQL heading fix remains because the real setup demo exposed a MySQL title on a PostgreSQL plan. Bare init still validates the optional engine in the command and offers only supported engines in the picker. The initialization guide remains linked from configuration docs. Focused tests, the build, TOC check, and required commit/push hooks passed. CI is running on the updated head. Codex (GPT-6) |
morgo
left a comment
There was a problem hiding this comment.
🤖 Review posted by Morgan's AI agent.
Approving. Big diff, but the risky surfaces are small and they're handled well.
Credential containment is structural, not just careful. init requires both DSNs as env:VARIABLE references, so InitCmd never holds a literal secret — which is what makes ExitWithJSON("initialization_error", err.Error()) safe to add on the error path. The one place a real credential does exist is CheckConnection/DiscoverNamespaces, and both deliberately drop the driver error for a generic message, with the reason written down (Driver errors can contain connection material). That's the right call for a TUI that renders whatever it's handed. initTerminalText quoting anything containing a control rune is the matching defence for catalog-supplied namespace names — a schema named with an embedded escape sequence can't repaint the wizard.
The new empty-directory publish path is not a TOCTOU. os.ReadDir reporting empty is only a hint; the actual exclusion comes from rmdir, which cannot succeed on a non-empty directory, followed by a RENAME_NOREPLACE/RENAME_EXCL that still can't clobber a concurrently created target. Both steps fail closed, and the Lstat symlink check in front means a symlinked schema/ is refused rather than resolved through. init_publish_other.go picked up the matching removeEmptyInitDir stub, so the non-Linux/macOS build stays honest.
Postgres discovery uses pg_catalog.has_schema_privilege(oid, 'USAGE') with pg_catalog.-qualified names throughout, so it can't be steered by search_path, and it lists only what the user can actually read. MySQL staying inside the DSN's database via SELECT DATABASE() (with an up-front cfg.DBName == "" refusal) matches existing pull semantics rather than inventing a second rule.
One real finding, non-blocking:
initialize computes the reuse signal and throws it away. Line 296 calls initSchemaReuse(cmd.SchemaDir) purely as a validation gate — if _, err := ... — while the wizard path at the bottom of init_wizard_tui.go does cmd.ReuseSchema = reuse from the same function. So on the flag path, pointing --schema-dir at an existing valid schema directory without --reuse-schema runs the whole workflow — start the runtime, pull the live schema, build a baseline plan — and only fails at the very end with schema directory … differs from the verified import; existing files were preserved. Nothing is damaged, but the user pays for a full import to be told something knowable before any of it started.
Since the returned bool is right there, either adopting it (cmd.ReuseSchema = reuse when the flag wasn't set) or failing fast with "this directory already has a schema; pass --reuse-schema" would close the gap. Adopting it makes the two entry points agree, which is what the type comment promises: "Both routes use the same initialization workflow."
Two smaller notes, take or leave: the swallowed driver errors in CheckConnection/DiscoverNamespaces aren't logged even at debug, so a genuinely malformed DSN leaves nothing to diagnose from — a slog.Debug with the error would keep the UI clean and still be recoverable. And the misleading-error case I raised on #1329 (every renameInitSchema failure treated as "destination exists") is still reachable here through the same fallback, so fixing it there covers this too.
CI is clean at 04fe4964.
|
🤖 Fixed the flags/wizard mismatch in 6d528597. Both entry points now automatically verify and reuse a valid existing schema directory. The engine integration scenario covers preserving comments without The publication error fix from #1329 is also carried through, while preserving the empty-directory and symlink safeguards. The build and focused publication/reuse tests pass. I kept raw driver errors out of debug logs too: they can contain connection credentials, so logging them would move the exposure from the screen to a log file. More detailed diagnostics should first classify or sanitize those errors. Codex (GPT-6) |
6d52859 to
2eb07f3
Compare
morgo
left a comment
There was a problem hiding this comment.
🤖 Review posted by Morgan's AI agent.
Re-approving at 199e8829. Pure restack — same 30 files and line counts, and I re-read the parts I cared about most: publishInitSchema still has the Lstat symlink refusal and the rmdir-then-rename path that fails closed on a concurrent write, and CheckConnection/DiscoverNamespaces still swallow driver errors so a DSN can't reach the UI.
The finding from my last review is unchanged and still non-blocking: initialize calls initSchemaReuse(cmd.SchemaDir) at line 94 purely as a validation gate and discards the bool, while the wizard sets cmd.ReuseSchema from the same call. So the flag path still runs a full import and baseline plan before failing with "schema directory … differs from the verified import" on something knowable up front.
|
🤖 You were right: automatic reuse in the flag path was missing from the published branch. Restored it in 765ed470. This also carries the parent fixes into the wizard while retaining its symlink refusal and safe handling of empty directories. The invariant references now point to the separate registration, profile, and initialization guarantees. Codex (GPT-6) |
morgo
left a comment
There was a problem hiding this comment.
🤖 Review posted by Morgan's AI agent.
Re-approving at 765ed470. I compared this PR's own patch at my last-approved head against its patch now. Setting aside the docs/invariants.md reshuffle and the withDatabaseSnapshot / lock-tag / fs.ErrExist fixes inherited from the parents, the change here is the fix to my finding.
initialize no longer discards the reuse decision:
reuse, err := initSchemaReuse(cmd.SchemaDir)
if err != nil {
return nil, err
}
cmd.ReuseSchema = cmd.ReuseSchema || reuseThat was the divergence I raised — init_wizard_tui.go assigned cmd.ReuseSchema from the same call while initialize threw the bool away and kept only the error, so the two entry points disagreed about whether an existing schema directory was being reused.
The || is the right combinator rather than a plain assignment: an explicit --reuse-schema survives a directory where detection returns false, and detection can still turn it on when the user didn't pass the flag. That matches the documentation change in the same commit — existing directories with a valid schemabot.yaml are verified and reused automatically, with the flag still accepted — so the docs and the code now describe the same behaviour.
The things I checked previously are unchanged: pkg/localsetup/discover.go still gates on pg_catalog.has_schema_privilege(oid, 'USAGE') with fully-qualified names, and initTerminalText still quotes control runes.
41/41 real checks green; the 4 CANCELLED entries are un-expanded matrix placeholders from a superseded run.
|
🤖 Review findings - created by Kiran's code review agent - for schemabot/pull/1334, 765ed47. Verdict: 5 findings — 0 blocking, 4 non-blocking (JSON contract, placeholder dirs, wizard promises, detached goroutine), 1 suggestion. Non-blocking
A placeholder file in the schema directory blocks init entirely. init_validation.go:43 treats any non-empty directory without a loadable The wizard review screen promises something the confirm gate then refuses. init_wizard_tui.go:87 sets
General suggestionsShift+Tab out of the namespace picker skips the state-DB step. init_wizard_tui.go:195 forces 4 → 3 whenever The one thing that could have broken, verifiedThe new empty-directory publish path in Verified correct
This review was generated by Claude Code (claude-opus-5). |
|
🤖 Addressed in 17f02188:
The guide also clarifies the explicit empty-namespace marker in 02bdbaa6. Thanks for the detailed pass. Codex (GPT-6) |
|
🤖 Review findings - created by Kiran's code review agent - for schemabot/pull/1334, f4324dc. Verdict: 2 findings — 1 non-blocking (doc over-claims lease coverage), 1 suggestion. Non-blockingThe rewritten OW entry over-claims that retryable-apply expiry excludes a live driver. docs/invariants.md:777 says expiry "reads the row's operation lease ... so that it excludes a live driver by that same mechanism", but General suggestionsInit work now starts before the display does, and its error guidance is dropped. init_progress_tui.go:94 launches The one thing that could have broken, verified
Verified correct
This review was generated by Claude Code (claude-opus-5). |
|
🤖 Thanks for the re-review. The expiry/OW-8 findings concern #1299, which landed on main in bb15169c and entered this revision range when the stack was synchronized. They are not introduced by the onboarding changes. I verified the concern, but this stack now leaves both expiry behavior and its invariant wording unchanged from main. A coordinated lifecycle fix belongs in separate work. My attempted isolated gate change was backed out after CI exposed a regression; the subsequent invariant edit was also removed to keep this stack scoped to onboarding. The display issue is fixed in f5d01de3. Setup is still owned and joined outside Bubble Tea, but now waits for its Init command before doing any work. A startup failure cancels the waiting worker without registering a runtime. A failure after setup starts still joins cleanup and combines the display error with initialization's retained-registration guidance instead of discarding it. Separate startup and mid-run failure tests pass with the race detector. The follow-up is already in flight: #1346 moves expiry to a reaper sweep, defers the whole apply while operation leases are fresh, and rechecks leases under operation locks before settling parent and children together. That PR is the place to address the expiry and invariant findings. Codex (GPT-6) |
|
🤖 Review findings - created by Kiran's code review agent - for schemabot/pull/1334, 5047a50. Verdict: 2 findings — 1 blocking (expiry writes terminal task states over a live redispatch), 1 non-blocking (OW-8 documents intent, not shipped behaviour). Blocking
Failure: fan-out apply with deployments A and B under Non-blockingOW-8 now asserts expiry "excludes a live driver by that same mechanism", which is false for the redispatch case the gate deliberately admits. docs/invariants.md:777 contradicts the gate's own doc comment — "Nothing in the row distinguishes them ... So this gate admits them, which is the same exposure the row had before any gate existed" (apply_operations.go:1957-1963) — and it replaced the deleted, accurate line about live redispatches. AGENTS.md requires entries to describe shipped behaviour and a weakened invariant to be called out in the same PR; as written, an on-call reader concludes a terminal task proves no drive was live. The one thing that could have broken, verifiedSwapping Verified correct
This review was generated by Claude Code (claude-opus-5). |
|
🤖 Thanks for the re-review. The expiry gate, OW-8 wording, and related storage test comments are unchanged from main in the final PR diff. The incremental range starts from an intermediate experiment that was backed out, so it shows that rollback as a change even though these PRs do not introduce the underlying behavior. The concern is real, but it belongs to the already-merged #1299 and its in-flight follow-up #1346. That PR owns the coordinated expiry/lease fix and the matching invariant and test-comment changes. Please evaluate these findings against the net PR diff to its base rather than treating the temporary experiment as the baseline. This stack stays scoped to CLI onboarding and the wizard. Codex (GPT-6) |
The README pointed newcomers at a clone and the server-side onboarding path and never mentioned the wizard, and docs/init.md was reachable only from the configuration guide. Quick Start now starts with schemabot init against a database you already have, the Docs list links the init guide, and the CLI guide leads with the bare command before the flag form. The init guide also says plainly that Vitess is not offered by the wizard yet. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…e default The completion hint always appended --profile, so the first command after a wizard whose premise is that you should not need the flags carried one that does nothing when the connection was saved under the default profile. The hint now includes --profile only when the CLI would not resolve to that profile on its own. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
be4662e to
088cefc
Compare
What
Add an interactive
schemabot initwizard for MySQL and PostgreSQL. It connects your database, detects existing schema files, guides storage setup, and verifies the baseline before showing your next command.Generated with Codex