diff --git a/setup/channels/discord.ts b/setup/channels/discord.ts index c25f2decc..1dec8e008 100644 --- a/setup/channels/discord.ts +++ b/setup/channels/discord.ts @@ -28,7 +28,7 @@ import k from 'kleur'; import * as setupLog from '../logs.js'; import { brightSelect } from '../lib/bright-select.js'; -import { confirmThenOpen } from '../lib/browser.js'; +import { confirmThenOpen, formatNoteLink } from '../lib/browser.js'; import { askOperatorRole } from '../lib/role-prompt.js'; import { ensureAnswer, fail, runQuietChild } from '../lib/runner.js'; import { accentGreen, brandBody, fmtDuration, note } from '../lib/theme.js'; @@ -165,7 +165,7 @@ async function walkThroughBotCreation(): Promise { ' 3. On the same tab, enable "Message Content Intent"', ' (under Privileged Gateway Intents)', '', - k.dim(url), + formatNoteLink(url), ].join('\n'), 'Create a Discord bot', ); @@ -225,7 +225,7 @@ async function walkThroughServerCreation(): Promise { ' 2. Choose "Create My Own" → "For me and my friends"', ' 3. Give it any name (e.g. "NanoClaw")', '', - k.dim(url), + formatNoteLink(url), ].join('\n'), 'Create a Discord server', ); @@ -447,7 +447,7 @@ async function promptInviteBot( ' 1. Pick any server you\'re in (a personal one is fine)', ' 2. Click "Authorize"', '', - k.dim(url), + formatNoteLink(url), ].join('\n'), 'Add bot to a server', ); diff --git a/setup/channels/slack.ts b/setup/channels/slack.ts index 340eabc09..03cbf4656 100644 --- a/setup/channels/slack.ts +++ b/setup/channels/slack.ts @@ -25,7 +25,7 @@ import * as p from '@clack/prompts'; import k from 'kleur'; import * as setupLog from '../logs.js'; -import { confirmThenOpen } from '../lib/browser.js'; +import { confirmThenOpen, formatNoteLink } from '../lib/browser.js'; import { askOperatorRole } from '../lib/role-prompt.js'; import { ensureAnswer, fail, runQuietChild } from '../lib/runner.js'; import { accentGreen, fmtDuration, note, wrapForGutter } from '../lib/theme.js'; @@ -136,7 +136,7 @@ async function walkThroughAppCreation(): Promise { ' 4. Basic Information → copy the "Signing Secret"', ' 5. Install to Workspace → copy the "Bot User OAuth Token" (xoxb-…)', '', - k.dim(SLACK_APPS_URL), + formatNoteLink(SLACK_APPS_URL), ].join('\n'), 'Create a Slack app', ); diff --git a/setup/channels/telegram.ts b/setup/channels/telegram.ts index ad749eb98..7130f8b5f 100644 --- a/setup/channels/telegram.ts +++ b/setup/channels/telegram.ts @@ -21,7 +21,7 @@ import * as p from '@clack/prompts'; import k from 'kleur'; import * as setupLog from '../logs.js'; -import { confirmThenOpen } from '../lib/browser.js'; +import { confirmThenOpen, formatNoteLink } from '../lib/browser.js'; import { askOperatorRole } from '../lib/role-prompt.js'; import { type Block, @@ -51,7 +51,7 @@ export async function runTelegramChannel(displayName: string): Promise { [ `Opening @${botUsername} in Telegram so it's ready when the pairing code shows up.`, '', - k.dim(botUrl), + formatNoteLink(botUrl), ].join('\n'), 'Open Telegram', ); diff --git a/setup/lib/browser.ts b/setup/lib/browser.ts index fc6eb17d7..4fbcbd7c3 100644 --- a/setup/lib/browser.ts +++ b/setup/lib/browser.ts @@ -19,6 +19,7 @@ import { spawn } from 'child_process'; import * as p from '@clack/prompts'; +import k from 'kleur'; import { isHeadless } from '../platform.js'; import { ensureAnswer } from './runner.js'; @@ -38,6 +39,21 @@ export function openUrl(url: string): void { } } +/** + * Format a URL for display inside a setup `note(...)` card. On + * GUI devices the URL renders dim — it's a fallback in case the + * auto-open misses, and `confirmThenOpen` is doing the heavy + * lifting of getting the user there. On headless devices the + * URL becomes the user's only path forward, so we surface it + * with a "Get started:" label and full-strength text — copy- + * pasting onto another device is the actual action, not an + * incidental reference. + */ +export function formatNoteLink(url: string): string { + if (isHeadless()) return `Get started: ${url}`; + return k.dim(url); +} + /** * 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