Skip to content

Improve setup UX#32

Open
stefanvictora wants to merge 1 commit into
mainfrom
ux-improvements
Open

Improve setup UX#32
stefanvictora wants to merge 1 commit into
mainfrom
ux-improvements

Conversation

@stefanvictora

@stefanvictora stefanvictora commented Jun 28, 2026

Copy link
Copy Markdown
Owner

Closes #31

Summary by CodeRabbit

  • New Features

    • Added a simpler onboarding flow with revpack connect and revpack doctor as top-level commands.
    • Added revpack setup --agent <target> for installing setup guidance tailored to specific editors and assistants.
    • Expanded command help with clearer workflow examples and checkout usage samples.
  • Bug Fixes

    • Improved next-step guidance when no profile is found, pointing users to revpack connect.
    • Updated setup behavior to avoid duplicate installed files when combining options.

@coderabbitai

coderabbitai Bot commented Jun 28, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

Promotes revpack connect and revpack doctor as top-level CLI commands (previously only available as revpack config setup / revpack config doctor), adds a --agent <target> flag to revpack setup, appends examples to checkout --help, updates all next-step guidance strings to reference the new commands, and updates README, docs, and changelog accordingly.

Changes

Primary Onboarding Commands & Workflow Help

Layer / File(s) Summary
registerPrimaryConfigCommands: connect & doctor
src/cli/commands/config.ts, src/cli/index.ts
connectAction() and doctorAction() are extracted from inline config setup/config doctor handlers. registerPrimaryConfigCommands registers them as top-level connect and doctor commands; the CLI entrypoint calls it and appends a workflow help block.
setup --agent option and uniqueSetupFiles
src/cli/commands/setup.ts
SetupOptions gains agent?: AgentTarget; --agent <target> option is wired; runSetup conditionally includes the agent template, deduplicates via uniqueSetupFiles, and prints agent-specific next-step guidance.
checkout --help examples
src/cli/commands/checkout.ts
Command builder stored in checkoutCmd; an after-help Examples block is appended with common checkout target forms.
Guidance string updates
src/cli/commands/config.ts, src/config/index.ts, src/config/profile-resolver.ts
All next-step and error-hint strings changed from revpack config setup to revpack connect.
Tests
src/cli/commands/config.test.ts, src/cli/commands/setup.test.ts, src/cli/commands/checkout.test.ts
New tests cover registerPrimaryConfigCommands help output, runSetup with agent: 'codex' (unit + CLI parse level), and checkout --help examples; existing config help assertions updated.
Docs, README, changelog
docs/commands.md, README.md, CHANGELOG.md
Primary workflow section added to docs; README Quick start updated to revpack connect/revpack setup --agent; changelog entry added.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Poem

🐇 Hop, hop, revpack connect!
No more config setup maze,
--agent codex neatly set,
Doctor checks in just a phase.
The rabbit tidied every hint,
Now onboarding's a sprint! 🌟

🚥 Pre-merge checks | ✅ 2 | ❌ 3

❌ Failed checks (1 warning, 2 inconclusive)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 12.50% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Title check ❓ Inconclusive The title is related but too generic to identify the specific onboarding and workflow help changes. Use a specific title naming the new onboarding commands or workflow help.
Linked Issues check ❓ Inconclusive Most requirements appear covered, but the summary doesn't confirm revpack setup agent <target> still works. Verify or preserve setup agent <target> and add tests/docs proving compatibility.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Out of Scope Changes check ✅ Passed All changes stay within onboarding, CLI help, and related docs/tests for the new workflow.

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/cli/commands/setup.ts (1)

107-128: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Tighten the --agent guidance branch.

This currently tells users how to use the adapter even on --dry-run, and on real setup --agent ... runs it prints revpack prepare twice.

Suggested fix
-  if (opts.agent) {
+  if (opts.agent && !opts.dryRun) {
     printAgentUsage(opts.agent);
   }
@@
-    if (opts.agent) {
-      console.log(formatGuidanceLine('  revpack prepare'));
-    } else if (!opts.prompts) {
+    if (!opts.agent && !opts.prompts) {
       console.log(formatGuidanceLine('  Tip: install an agent adapter, for example:'));
       console.log(formatGuidanceLine('  revpack setup agent codex'));
       console.log(formatGuidanceLine('  Or create both files at once:'));
       console.log(formatGuidanceLine('  revpack setup --agent codex'));
-    } else {
+    } else if (!opts.agent) {
       console.log(formatGuidanceLine('  Tip: `revpack setup --prompts` is deprecated; use:'));
       console.log(formatGuidanceLine('  revpack setup agent copilot'));
     }
     console.log(formatGuidanceLine('  revpack prepare'));
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/cli/commands/setup.ts` around lines 107 - 128, The `setup` command’s
guidance output in `printAgentUsage`/the `opts.agent` branch is too broad and
duplicates `revpack prepare`. Update the logic so adapter usage help is shown
only for real setup runs (not `--dry-run`), and ensure the `revpack prepare`
guidance is emitted only once when `opts.agent` is set. Keep the
`results.some(...)` gating and adjust the `opts.agent`, `opts.prompts`, and
default branches accordingly.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/cli/commands/config.ts`:
- Around line 353-362: The remote URL normalization in config handling does not
treat ssh:// remotes as provider HTTPS URLs, so they fall through the URL
parsing branch and keep the ssh scheme. Update the remote parsing logic in the
config command’s URL suggestion path to recognize ssh://git@host/org/repo.git
alongside the existing SSH regex case and convert it to an https://host-style
suggestion, using the same `suggestedUrl` and `suggestedName` logic before
`normalizeProviderUrlInput()` is called.
- Around line 399-450: The profile creation flow in the config command is
leaving `providerUrl` empty for `bitbucket-cloud`, which breaks validation and
remote matching. Update the prompt/assignment logic around `provider`, `url`,
and `RevpackProfile` so that when the selected provider is `bitbucket-cloud` and
the URL input is blank, it defaults to `https://bitbucket.org` before building
the profile. Make sure this happens in the same path that currently derives
`defaultName`, `extraPatternPrompt`, and the final `profile` object so
`runDoctor()` and remote matching continue to work.

---

Outside diff comments:
In `@src/cli/commands/setup.ts`:
- Around line 107-128: The `setup` command’s guidance output in
`printAgentUsage`/the `opts.agent` branch is too broad and duplicates `revpack
prepare`. Update the logic so adapter usage help is shown only for real setup
runs (not `--dry-run`), and ensure the `revpack prepare` guidance is emitted
only once when `opts.agent` is set. Keep the `results.some(...)` gating and
adjust the `opts.agent`, `opts.prompts`, and default branches accordingly.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 557bb2dd-4e1f-435a-83fa-72a11b338009

📥 Commits

Reviewing files that changed from the base of the PR and between b7b3d51 and 6211319.

📒 Files selected for processing (12)
  • CHANGELOG.md
  • README.md
  • docs/commands.md
  • src/cli/commands/checkout.test.ts
  • src/cli/commands/checkout.ts
  • src/cli/commands/config.test.ts
  • src/cli/commands/config.ts
  • src/cli/commands/setup.test.ts
  • src/cli/commands/setup.ts
  • src/cli/index.ts
  • src/config/index.ts
  • src/config/profile-resolver.ts

Comment on lines +353 to +362
try {
// Handle SSH URLs like git@host:group/project.git
const sshMatch = remoteUrl.match(/@([^:]+):/);
if (sshMatch) {
suggestedUrl = `https://${sshMatch[1]}`;
suggestedName = sshMatch[1].split('.')[0];
} else {
const parsed = new URL(remoteUrl);
suggestedUrl = `${parsed.protocol}//${parsed.host}`;
suggestedName = parsed.hostname.split('.')[0];

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Normalize ssh:// remotes to an HTTPS provider URL.

ssh://git@host/org/repo.git remotes land in the new URL(remoteUrl) branch and currently produce a suggested default like ssh://host. Accepting that default fails immediately because normalizeProviderUrlInput() only accepts http/https.

Suggested fix
         } else {
           const parsed = new URL(remoteUrl);
-          suggestedUrl = `${parsed.protocol}//${parsed.host}`;
+          const suggestedProtocol = parsed.protocol === 'ssh:' ? 'https:' : parsed.protocol;
+          suggestedUrl = `${suggestedProtocol}//${parsed.host}`;
           suggestedName = parsed.hostname.split('.')[0];
         }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
try {
// Handle SSH URLs like git@host:group/project.git
const sshMatch = remoteUrl.match(/@([^:]+):/);
if (sshMatch) {
suggestedUrl = `https://${sshMatch[1]}`;
suggestedName = sshMatch[1].split('.')[0];
} else {
const parsed = new URL(remoteUrl);
suggestedUrl = `${parsed.protocol}//${parsed.host}`;
suggestedName = parsed.hostname.split('.')[0];
try {
// Handle SSH URLs like git@host:group/project.git
const sshMatch = remoteUrl.match(/@([^:]+):/);
if (sshMatch) {
suggestedUrl = `https://${sshMatch[1]}`;
suggestedName = sshMatch[1].split('.')[0];
} else {
const parsed = new URL(remoteUrl);
const suggestedProtocol = parsed.protocol === 'ssh:' ? 'https:' : parsed.protocol;
suggestedUrl = `${suggestedProtocol}//${parsed.host}`;
suggestedName = parsed.hostname.split('.')[0];
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/cli/commands/config.ts` around lines 353 - 362, The remote URL
normalization in config handling does not treat ssh:// remotes as provider HTTPS
URLs, so they fall through the URL parsing branch and keep the ssh scheme.
Update the remote parsing logic in the config command’s URL suggestion path to
recognize ssh://git@host/org/repo.git alongside the existing SSH regex case and
convert it to an https://host-style suggestion, using the same `suggestedUrl`
and `suggestedName` logic before `normalizeProviderUrlInput()` is called.

Comment on lines +399 to +450
const defaultProvider = getSetupProviderDefault(url, detectedProvider);
const providerInput = shouldPromptForSetupProvider(url)
? (await ask(`Provider (gitlab/github/bitbucket-cloud) [${defaultProvider}]: `)) || defaultProvider
: defaultProvider;
let provider: ProviderType;
try {
provider = normalizeProviderInput(providerInput);
validateProviderUrlForProvider(url, provider);
} catch (err) {
rl.close();
throw err;
}
const defaultName = deriveProfileNameFromProviderUrl(url) || suggestedName || 'default';
const name = (await ask(`Profile name [${defaultName}]: `)) || defaultName;
const defaultTokenEnv = getDefaultTokenEnv(provider);
const tokenEnv = (await ask(`Token environment variable [${defaultTokenEnv}]: `)) || defaultTokenEnv;
let emailEnv = '';
if (provider === 'bitbucket-cloud') {
emailEnv =
(await ask(`Atlassian account email environment variable [REVPACK_BITBUCKET_EMAIL]: `)) ||
'REVPACK_BITBUCKET_EMAIL';
}

// Derive host from URL for matching info
let derivedHost = '';
if (url) {
try {
derivedHost = new URL(url).host;
} catch {
/* ignore */
}
}
const extraPatternPrompt = derivedHost ? `Additional remote match pattern (optional): ` : `Remote match pattern: `;
const extraPattern = await ask(extraPatternPrompt);

let caFileInput = '';
let tlsInput = 'yes';
let sshCloneInput = 'no';
const isCloudProvider = isManagedCloudProvider(url, provider);
if (!isCloudProvider) {
caFileInput = await ask(`Custom CA file (optional): `);
tlsInput = (await ask(`Verify TLS certificates [yes]: `)) || 'yes';
sshCloneInput = (await ask(`Use SSH for git clone (revpack checkout) [no]: `)) || 'no';
}

rl.close();

const profile: RevpackProfile = {
provider,
};
if (url) profile.url = url;
if (tokenEnv) profile.tokenEnv = tokenEnv;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Default Bitbucket Cloud to https://bitbucket.org when URL is left blank.

If the user selects bitbucket-cloud and accepts an empty provider URL, this saves a profile with no url. That breaks parity with config profile create, leaves runDoctor() reporting the profile invalid, and removes the URL-derived remote match.

Suggested fix
     let provider: ProviderType;
     try {
       provider = normalizeProviderInput(providerInput);
+      if (!url && provider === 'bitbucket-cloud') {
+        url = 'https://bitbucket.org';
+      }
       validateProviderUrlForProvider(url, provider);
     } catch (err) {
       rl.close();
       throw err;
     }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const defaultProvider = getSetupProviderDefault(url, detectedProvider);
const providerInput = shouldPromptForSetupProvider(url)
? (await ask(`Provider (gitlab/github/bitbucket-cloud) [${defaultProvider}]: `)) || defaultProvider
: defaultProvider;
let provider: ProviderType;
try {
provider = normalizeProviderInput(providerInput);
validateProviderUrlForProvider(url, provider);
} catch (err) {
rl.close();
throw err;
}
const defaultName = deriveProfileNameFromProviderUrl(url) || suggestedName || 'default';
const name = (await ask(`Profile name [${defaultName}]: `)) || defaultName;
const defaultTokenEnv = getDefaultTokenEnv(provider);
const tokenEnv = (await ask(`Token environment variable [${defaultTokenEnv}]: `)) || defaultTokenEnv;
let emailEnv = '';
if (provider === 'bitbucket-cloud') {
emailEnv =
(await ask(`Atlassian account email environment variable [REVPACK_BITBUCKET_EMAIL]: `)) ||
'REVPACK_BITBUCKET_EMAIL';
}
// Derive host from URL for matching info
let derivedHost = '';
if (url) {
try {
derivedHost = new URL(url).host;
} catch {
/* ignore */
}
}
const extraPatternPrompt = derivedHost ? `Additional remote match pattern (optional): ` : `Remote match pattern: `;
const extraPattern = await ask(extraPatternPrompt);
let caFileInput = '';
let tlsInput = 'yes';
let sshCloneInput = 'no';
const isCloudProvider = isManagedCloudProvider(url, provider);
if (!isCloudProvider) {
caFileInput = await ask(`Custom CA file (optional): `);
tlsInput = (await ask(`Verify TLS certificates [yes]: `)) || 'yes';
sshCloneInput = (await ask(`Use SSH for git clone (revpack checkout) [no]: `)) || 'no';
}
rl.close();
const profile: RevpackProfile = {
provider,
};
if (url) profile.url = url;
if (tokenEnv) profile.tokenEnv = tokenEnv;
const defaultProvider = getSetupProviderDefault(url, detectedProvider);
const providerInput = shouldPromptForSetupProvider(url)
? (await ask(`Provider (gitlab/github/bitbucket-cloud) [${defaultProvider}]: `)) || defaultProvider
: defaultProvider;
let provider: ProviderType;
try {
provider = normalizeProviderInput(providerInput);
if (!url && provider === 'bitbucket-cloud') {
url = 'https://bitbucket.org';
}
validateProviderUrlForProvider(url, provider);
} catch (err) {
rl.close();
throw err;
}
const defaultName = deriveProfileNameFromProviderUrl(url) || suggestedName || 'default';
const name = (await ask(`Profile name [${defaultName}]: `)) || defaultName;
const defaultTokenEnv = getDefaultTokenEnv(provider);
const tokenEnv = (await ask(`Token environment variable [${defaultTokenEnv}]: `)) || defaultTokenEnv;
let emailEnv = '';
if (provider === 'bitbucket-cloud') {
emailEnv =
(await ask(`Atlassian account email environment variable [REVPACK_BITBUCKET_EMAIL]: `)) ||
'REVPACK_BITBUCKET_EMAIL';
}
// Derive host from URL for matching info
let derivedHost = '';
if (url) {
try {
derivedHost = new URL(url).host;
} catch {
/* ignore */
}
}
const extraPatternPrompt = derivedHost ? `Additional remote match pattern (optional): ` : `Remote match pattern: `;
const extraPattern = await ask(extraPatternPrompt);
let caFileInput = '';
let tlsInput = 'yes';
let sshCloneInput = 'no';
const isCloudProvider = isManagedCloudProvider(url, provider);
if (!isCloudProvider) {
caFileInput = await ask(`Custom CA file (optional): `);
tlsInput = (await ask(`Verify TLS certificates [yes]: `)) || 'yes';
sshCloneInput = (await ask(`Use SSH for git clone (revpack checkout) [no]: `)) || 'no';
}
rl.close();
const profile: RevpackProfile = {
provider,
};
if (url) profile.url = url;
if (tokenEnv) profile.tokenEnv = tokenEnv;
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/cli/commands/config.ts` around lines 399 - 450, The profile creation flow
in the config command is leaving `providerUrl` empty for `bitbucket-cloud`,
which breaks validation and remote matching. Update the prompt/assignment logic
around `provider`, `url`, and `RevpackProfile` so that when the selected
provider is `bitbucket-cloud` and the URL input is blank, it defaults to
`https://bitbucket.org` before building the profile. Make sure this happens in
the same path that currently derives `defaultName`, `extraPatternPrompt`, and
the final `profile` object so `runDoctor()` and remote matching continue to
work.

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.

Add primary onboarding commands and workflow help

1 participant