mirror of
https://github.com/qwibitai/nanoclaw.git
synced 2026-06-04 10:14:47 +08:00
feat(setup): skip browser-open prompts on headless devices
Wires the existing `isHeadless()` from setup/platform.ts into
`confirmThenOpen`. When the helper detects a headless device
(Linux without `DISPLAY`/`WAYLAND_DISPLAY`), both the
"Press Enter to open your browser" prompt and the actual
`openUrl(...)` call are skipped — there's no browser to launch
and the user can't usefully press Enter to summon one.
Why this is enough — the surrounding flow already supports the
headless path implicitly:
- Every `confirmThenOpen` call site sits beneath a `note(...)`
that prints the URL and the steps the user needs to take.
The URL is already visible to copy-paste onto another
device.
- Every site is followed by an explicit confirmation prompt
("Got your bot token?", "Done with the X?", etc.) that
naturally serves as the headless user's "I finished the
thing on my other device" signal.
So the headless branch becomes: read the note, do the thing,
answer the next prompt — without a useless "Press Enter to
open your browser" detour in between.
Coverage rationale (~95% accurate for the cases that actually
cause user confusion today):
- Linux + no `DISPLAY`/`WAYLAND_DISPLAY` → headless. Catches:
• Raspberry Pi headless installs
• Bare-metal Linux servers
• SSH'd into Linux without X11 forwarding
• CI environments on Linux
• Linux containers (which have no display)
- macOS → never headless. Even SSH'd Macs can usually still
open URLs through the local user's session, so treating
them as GUI-capable is the right default.
- Windows → never headless (effectively always GUI in
practice).
The remaining ~5% are edge cases (someone manually unset
`DISPLAY` on a desktop Linux session, etc.) that almost never
happen accidentally and recover gracefully — the URL is still
visible in the surrounding note.
Six call sites in channel adapters (Discord ×3, Slack ×1,
Telegram ×1, Teams ×1) all change behavior atomically through
the single helper. No per-site copy changes needed; consistency
is enforced by the central wiring.
This commit is contained in:
+11
-2
@@ -9,12 +9,18 @@
|
||||
* `confirmThenOpen` pauses for the operator before triggering the open —
|
||||
* the browser tends to steal focus when it pops, and a split-second
|
||||
* "wait what just happened" moment is worse than letting the user hit
|
||||
* Enter when they're ready.
|
||||
* Enter when they're ready. On headless devices (no graphical session
|
||||
* available) it skips both the prompt and the open: there's no browser
|
||||
* to launch, the surrounding `note(...)` already shows the URL for
|
||||
* copy-paste on another device, and the next prompt in the channel
|
||||
* flow ("Got your bot token?" etc.) provides the natural completion
|
||||
* confirmation.
|
||||
*/
|
||||
import { spawn } from 'child_process';
|
||||
|
||||
import * as p from '@clack/prompts';
|
||||
|
||||
import { isHeadless } from '../platform.js';
|
||||
import { ensureAnswer } from './runner.js';
|
||||
|
||||
/** 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 {
|
||||
/**
|
||||
* 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
|
||||
* URL from the note that precedes the prompt.
|
||||
* URL from the note that precedes the prompt. On headless devices both
|
||||
* the prompt and the open are skipped — there's no browser to time
|
||||
* focus for, and the URL is already visible in the surrounding note.
|
||||
*/
|
||||
export async function confirmThenOpen(
|
||||
url: string,
|
||||
message = 'Press Enter to open your browser',
|
||||
): Promise<void> {
|
||||
if (isHeadless()) return;
|
||||
ensureAnswer(
|
||||
await p.confirm({
|
||||
message,
|
||||
|
||||
Reference in New Issue
Block a user