Skip to content

feat(init): add interactive config wizard for first-time setup#169

Merged
snipcodeit merged 2 commits intomainfrom
issue/125-add-interactive-config-wizard-to-mgw
Mar 3, 2026
Merged

feat(init): add interactive config wizard for first-time setup#169
snipcodeit merged 2 commits intomainfrom
issue/125-add-interactive-config-wizard-to-mgw

Conversation

@snipcodeit
Copy link
Owner

Summary

  • Adds lib/config-wizard.cjs with runWizard() and shouldRunWizard() — the complete wizard implementation using Node.js readline
  • The wizard prompts for GitHub username (auto-detected via gh api user), default issue state filter, default issue limit, and default assignee filter, then writes .mgw/config.json
  • Updates commands/init.md with a new run_config_wizard step inserted after the GitHub setup and before the completion report
  • Wizard is skippable via --no-config flag or automatically bypassed when .mgw/config.json already exists

Closes #125

Milestone Context

  • Milestone: v4 — Interactive CLI & TUI
  • Phase: 29 — Shell Completions and Config Wizard
  • Issue: 9 of 9 in milestone

Changes

  • lib/config-wizard.cjs (new) — wizard logic: shouldRunWizard() checks for --no-config flag and pre-existing config; runWizard() drives the interactive prompt session and writes .mgw/config.json; detectGitHubUsername() auto-detects via gh api user
  • commands/init.md — added run_config_wizard step with full pseudocode; updated argument-hint, objective description, report layout, and success criteria to reflect config.json output

Test Plan

  • Run mgw:init in a fresh repo with no .mgw/config.json — wizard should prompt all four questions and write .mgw/config.json
  • Run mgw:init --no-config — wizard should be skipped entirely, report shows skipped
  • Run mgw:init when .mgw/config.json already exists — wizard should be skipped, report shows exists
  • Verify shouldRunWizard() returns false when --no-config is in argv
  • Verify shouldRunWizard() returns false when config file exists
  • Verify detectGitHubUsername() returns null gracefully when gh is not authenticated
  • Verify pressing Enter on each prompt accepts the displayed default value

Adds lib/config-wizard.cjs with runWizard() and shouldRunWizard()
helpers. Updates commands/init.md with a new run_config_wizard step
that runs after GitHub setup and before the completion report.

The wizard prompts for GitHub username (auto-detected), default issue
state filter, default issue limit, and default assignee filter, then
writes answers to .mgw/config.json. Skippable via --no-config flag or
automatically bypassed when .mgw/config.json already exists.

Closes #125

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Copy link
Owner Author

@snipcodeit snipcodeit left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review: PR #169 — interactive config wizard

lib/config-wizard.cjs is clean and well-structured. shouldRunWizard and detectGitHubUsername are the right abstractions. Two blocking issues before merge.


🔴 runWizard hangs in non-interactive (CI/piped) mode

runWizard creates a readline interface with terminal: true without checking process.stdin.isTTY. In any non-interactive context (CI, piped stdin, mgw:init < /dev/null), readline will:

  1. Start waiting for input from a closed/non-TTY stream
  2. Potentially hang indefinitely (depending on Node version and platform)
  3. Or throw/emit an error that crashes init

shouldRunWizard checks --no-config and pre-existing config but does NOT check process.stdin.isTTY. This means:

  • CI runs that do not pass --no-config will attempt to launch the wizard
  • The wizard will hang waiting for input that never comes

Fix shouldRunWizard to also check TTY:

function shouldRunWizard(mgwDir, argv) {
  const args = argv || process.argv;
  if (args.includes("--no-config")) return false;
  if (!process.stdin.isTTY) return false;  // ← add this
  const configPath = path.join(mgwDir, "config.json");
  if (fs.existsSync(configPath)) return false;
  return true;
}

And in runWizard, add a guard at the top:

async function runWizard(mgwDir) {
  if (!process.stdin.isTTY) {
    throw new Error("runWizard called in non-interactive mode");
  }
  ...
}

🔴 Merge conflict with PR #168

Both this PR and PR #168 modify commands/init.md. They add different steps (run_config_wizard here, install_completions in #168) and both update the <report> section and <success_criteria>. They will conflict when merged sequentially.

The two PRs need to coordinate on merge order and the second merge must manually reconcile both changes. This should be documented in both PR descriptions.


🟡 No tests for lib/config-wizard.cjs

shouldRunWizard and detectGitHubUsername are pure/side-effect-free functions that should have unit tests in test/config-wizard.test.cjs:

describe("shouldRunWizard", () => {
  it("returns false when --no-config in argv");
  it("returns false when config.json already exists");
  it("returns true when no config and no --no-config flag");
  // Note: isTTY check requires mocking process.stdin
});

describe("detectGitHubUsername", () => {
  it("returns null when gh command fails");
  // Full test requires mocking execSync
});

Without tests, the edge cases (especially the --no-config flag matching) have no coverage.


🟡 readline with terminal: true forces echo even in piped contexts

Even if the TTY check is added, the terminal: true option forces readline to treat output as a terminal and emit ANSI escape sequences for line editing. This can corrupt output when stdout is being captured. Consider using terminal: false or detecting the appropriate value:

const rl = readline.createInterface({
  input: process.stdin,
  output: process.stdout,
  terminal: process.stdout.isTTY,  // use TTY mode only when stdout supports it
});

✅ Good points

  • shouldRunWizard as a separate exported function is testable and composable
  • detectGitHubUsername using stdio: ["pipe", "pipe", "pipe"] prevents inheriting parent stderr — correct
  • Config written with JSON.stringify(config, null, 2) + "\n" — proper formatted JSON with trailing newline
  • created_at timestamp in config is useful for debugging and upgrade paths
  • Error handling: wizard errors do not abort the overall init — correct design
  • promptChoice with numbered list + "(default)" marker is good UX

- shouldRunWizard: return false early when process.stdin.isTTY is falsy
  (prevents interactive prompts in piped/automated contexts)
- runWizard: throw at entry when stdin is not a TTY
- readline.createInterface: use terminal: process.stdout.isTTY instead of
  terminal: true to suppress ANSI escapes when stdout is piped
- commands/init.md: add merge note for conflict with PR #124
- test/config-wizard.test.cjs: 8 unit tests covering shouldRunWizard all
  exit paths and detectGitHubUsername failure path
@snipcodeit snipcodeit merged commit f4d881d into main Mar 3, 2026
1 check passed
@snipcodeit snipcodeit deleted the issue/125-add-interactive-config-wizard-to-mgw branch March 3, 2026 02:19
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

core Changes to core library slash-commands Changes to slash command files

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add interactive config wizard to mgw:init for first-time setup

1 participant