Merge pull request #2154 from alipgoldberg/setup-fallback-url-in-prompt

feat(setup): move URL fallback into the open-browser prompt
This commit is contained in:
gavrielc
2026-04-30 22:50:44 +03:00
committed by GitHub
4 changed files with 28 additions and 26 deletions

View File

@@ -164,9 +164,8 @@ async function walkThroughBotCreation(): Promise<void> {
' 2. In the "Bot" tab, click "Reset Token" and copy the token', ' 2. In the "Bot" tab, click "Reset Token" and copy the token',
' 3. On the same tab, enable "Message Content Intent"', ' 3. On the same tab, enable "Message Content Intent"',
' (under Privileged Gateway Intents)', ' (under Privileged Gateway Intents)',
'',
formatNoteLink(url), formatNoteLink(url),
].join('\n'), ].filter((line): line is string => line !== null).join('\n'),
'Create a Discord bot', 'Create a Discord bot',
); );
await confirmThenOpen(url, 'Press Enter to open the Developer Portal'); await confirmThenOpen(url, 'Press Enter to open the Developer Portal');
@@ -224,9 +223,8 @@ async function walkThroughServerCreation(): Promise<void> {
' 1. In Discord, click the "+" at the bottom of the server list', ' 1. In Discord, click the "+" at the bottom of the server list',
' 2. Choose "Create My Own" → "For me and my friends"', ' 2. Choose "Create My Own" → "For me and my friends"',
' 3. Give it any name (e.g. "NanoClaw")', ' 3. Give it any name (e.g. "NanoClaw")',
'',
formatNoteLink(url), formatNoteLink(url),
].join('\n'), ].filter((line): line is string => line !== null).join('\n'),
'Create a Discord server', 'Create a Discord server',
); );
await confirmThenOpen(url, 'Press Enter to open Discord'); await confirmThenOpen(url, 'Press Enter to open Discord');
@@ -446,9 +444,8 @@ async function promptInviteBot(
'', '',
' 1. Pick any server you\'re in (a personal one is fine)', ' 1. Pick any server you\'re in (a personal one is fine)',
' 2. Click "Authorize"', ' 2. Click "Authorize"',
'',
formatNoteLink(url), formatNoteLink(url),
].join('\n'), ].filter((line): line is string => line !== null).join('\n'),
'Add bot to a server', 'Add bot to a server',
); );
await confirmThenOpen(url, 'Press Enter to open the invite page'); await confirmThenOpen(url, 'Press Enter to open the invite page');

View File

@@ -135,9 +135,8 @@ async function walkThroughAppCreation(): Promise<void> {
' slash commands and messages from the messages tab"', ' slash commands and messages from the messages tab"',
' 4. Basic Information → copy the "Signing Secret"', ' 4. Basic Information → copy the "Signing Secret"',
' 5. Install to Workspace → copy the "Bot User OAuth Token" (xoxb-…)', ' 5. Install to Workspace → copy the "Bot User OAuth Token" (xoxb-…)',
'',
formatNoteLink(SLACK_APPS_URL), formatNoteLink(SLACK_APPS_URL),
].join('\n'), ].filter((line): line is string => line !== null).join('\n'),
'Create a Slack app', 'Create a Slack app',
); );
await confirmThenOpen(SLACK_APPS_URL, 'Press Enter to open Slack app settings'); await confirmThenOpen(SLACK_APPS_URL, 'Press Enter to open Slack app settings');

View File

@@ -50,9 +50,8 @@ export async function runTelegramChannel(displayName: string): Promise<void> {
note( note(
[ [
`Opening @${botUsername} in Telegram so it's ready when the pairing code shows up.`, `Opening @${botUsername} in Telegram so it's ready when the pairing code shows up.`,
'',
formatNoteLink(botUrl), formatNoteLink(botUrl),
].join('\n'), ].filter((line): line is string => line !== null).join('\n'),
'Open Telegram', 'Open Telegram',
); );
await confirmThenOpen(botUrl, 'Press Enter to open Telegram'); await confirmThenOpen(botUrl, 'Press Enter to open Telegram');

View File

@@ -40,35 +40,42 @@ export function openUrl(url: string): void {
} }
/** /**
* Format a URL for display inside a setup `note(...)` card. On * Format a URL for inclusion in a setup `note(...)` card. On
* GUI devices the URL renders dim — it's a fallback in case the * headless devices we surface the URL inside the card with a
* auto-open misses, and `confirmThenOpen` is doing the heavy * "Get started:" label at full strength — copy-pasting onto
* lifting of getting the user there. On headless devices the * another device is the actual action, not an incidental
* URL becomes the user's only path forward, so we surface it * reference. The leading `\n` acts as a visual separator from
* with a "Get started:" label and full-strength text — copy- * the body steps above; callers `.filter(line => line !== null)`
* pasting onto another device is the actual action, not an * before joining, so on GUI we drop the line entirely (and the
* incidental reference. * URL ends up below the next-step confirm prompt as a "if
* browser does not appear, please visit" fallback — see
* `confirmThenOpen`).
*/ */
export function formatNoteLink(url: string): string { export function formatNoteLink(url: string): string | null {
if (isHeadless()) return `Get started: ${url}`; if (isHeadless()) return `\nGet started: ${url}`;
return k.dim(url); return null;
} }
/** /**
* Gate a browser-open on a confirm so the user is ready for their browser * Gate a browser-open on a confirm so the user is ready for their browser
* to take focus. Proceeds on cancel as well — the user can always copy the * to take focus. Proceeds on cancel as well. On headless devices both the
* URL from the note that precedes the prompt. On headless devices both * prompt and the open are skipped — the URL is already surfaced inside
* the prompt and the open are skipped — there's no browser to time * the surrounding note (via `formatNoteLink`).
* focus for, and the URL is already visible in the surrounding note. *
* On GUI devices the confirm message includes the fallback URL on the
* lines below the action ("If browser does not appear, please visit:
* <url>" in dim) so the user has a copy-paste path right next to the
* action button without needing to scroll back up to the card.
*/ */
export async function confirmThenOpen( export async function confirmThenOpen(
url: string, url: string,
message = 'Press Enter to open your browser', message = 'Press Enter to open your browser',
): Promise<void> { ): Promise<void> {
if (isHeadless()) return; if (isHeadless()) return;
const fallback = `\n${k.dim(`If browser does not appear, please visit: ${url}`)}`;
ensureAnswer( ensureAnswer(
await p.confirm({ await p.confirm({
message, message: `${message}${fallback}`,
initialValue: true, initialValue: true,
}), }),
); );