99 * `confirmThenOpen` pauses for the operator before triggering the open —
1010 * the browser tends to steal focus when it pops, and a split-second
1111 * "wait what just happened" moment is worse than letting the user hit
12- * Enter when they're ready.
12+ * Enter when they're ready. On headless devices (no graphical session
13+ * available) it skips both the prompt and the open: there's no browser
14+ * to launch, the surrounding `note(...)` already shows the URL for
15+ * copy-paste on another device, and the next prompt in the channel
16+ * flow ("Got your bot token?" etc.) provides the natural completion
17+ * confirmation.
1318 */
1419import { spawn } from 'child_process' ;
1520
1621import * as p from '@clack/prompts' ;
1722
23+ import { isHeadless } from '../platform.js' ;
1824import { ensureAnswer } from './runner.js' ;
1925
2026/** Best-effort open of a URL in the user's default browser. Silent on failure. */
@@ -35,12 +41,15 @@ export function openUrl(url: string): void {
3541/**
3642 * Gate a browser-open on a confirm so the user is ready for their browser
3743 * to take focus. Proceeds on cancel as well — the user can always copy the
38- * URL from the note that precedes the prompt.
44+ * URL from the note that precedes the prompt. On headless devices both
45+ * the prompt and the open are skipped — there's no browser to time
46+ * focus for, and the URL is already visible in the surrounding note.
3947 */
4048export async function confirmThenOpen (
4149 url : string ,
4250 message = 'Press Enter to open your browser' ,
4351) : Promise < void > {
52+ if ( isHeadless ( ) ) return ;
4453 ensureAnswer (
4554 await p . confirm ( {
4655 message,
0 commit comments