From ca81d94b9074af243738924ecaca269ac1d18340 Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Thu, 1 Jan 2026 23:30:02 +0100 Subject: [PATCH] feat(cli): hint gateway reachability for local/remote --- src/commands/configure.ts | 35 +++++++++++++++++++++++++++-- src/commands/onboard-helpers.ts | 33 +++++++++++++++++++++++++++ src/commands/onboard-interactive.ts | 35 +++++++++++++++++++++++++++-- 3 files changed, 99 insertions(+), 4 deletions(-) diff --git a/src/commands/configure.ts b/src/commands/configure.ts index 2d59ccd75..35f6d5967 100644 --- a/src/commands/configure.ts +++ b/src/commands/configure.ts @@ -37,6 +37,7 @@ import { guardCancel, openUrl, printWizardHeader, + probeGatewayReachable, randomToken, summarizeExistingConfig, } from "./onboard-helpers.js"; @@ -384,12 +385,42 @@ export async function runConfigureWizard( } } + const localUrl = "ws://127.0.0.1:18789"; + const localProbe = await probeGatewayReachable({ + url: localUrl, + token: process.env.CLAWDIS_GATEWAY_TOKEN, + password: + baseConfig.gateway?.auth?.password ?? + process.env.CLAWDIS_GATEWAY_PASSWORD, + }); + const remoteUrl = baseConfig.gateway?.remote?.url?.trim() ?? ""; + const remoteProbe = remoteUrl + ? await probeGatewayReachable({ + url: remoteUrl, + token: baseConfig.gateway?.remote?.token, + }) + : null; + const mode = guardCancel( await select({ message: "Where will the Gateway run?", options: [ - { value: "local", label: "Local (this machine)" }, - { value: "remote", label: "Remote (info-only)" }, + { + value: "local", + label: "Local (this machine)", + hint: localProbe.ok + ? `Gateway reachable (${localUrl})` + : `No gateway detected (${localUrl})`, + }, + { + value: "remote", + label: "Remote (info-only)", + hint: !remoteUrl + ? "No remote URL configured yet" + : remoteProbe?.ok + ? `Gateway reachable (${remoteUrl})` + : `Configured but unreachable (${remoteUrl})`, + }, ], }), runtime, diff --git a/src/commands/onboard-helpers.ts b/src/commands/onboard-helpers.ts index 9ebb94855..4e9b98ba5 100644 --- a/src/commands/onboard-helpers.ts +++ b/src/commands/onboard-helpers.ts @@ -11,6 +11,7 @@ import { import type { ClawdisConfig } from "../config/config.js"; import { CONFIG_PATH_CLAWDIS } from "../config/config.js"; import { resolveSessionTranscriptsDir } from "../config/sessions.js"; +import { callGateway } from "../gateway/call.js"; import { runCommandWithTimeout } from "../process/exec.js"; import type { RuntimeEnv } from "../runtime.js"; import { CONFIG_DIR, resolveUserPath } from "../utils.js"; @@ -171,4 +172,36 @@ export async function detectBinary(name: string): Promise { } } +export async function probeGatewayReachable(params: { + url: string; + token?: string; + password?: string; + timeoutMs?: number; +}): Promise<{ ok: boolean; detail?: string }> { + const url = params.url.trim(); + const timeoutMs = params.timeoutMs ?? 1500; + try { + await callGateway({ + url, + token: params.token, + password: params.password, + method: "health", + timeoutMs, + }); + return { ok: true }; + } catch (err) { + return { ok: false, detail: summarizeError(err) }; + } +} + +function summarizeError(err: unknown): string { + const raw = String(err ?? "unknown error"); + const line = + raw + .split("\n") + .map((s) => s.trim()) + .find(Boolean) ?? raw; + return line.length > 120 ? `${line.slice(0, 119)}…` : line; +} + export const DEFAULT_WORKSPACE = DEFAULT_AGENT_WORKSPACE_DIR; diff --git a/src/commands/onboard-interactive.ts b/src/commands/onboard-interactive.ts index f77c03749..c0fd68eb0 100644 --- a/src/commands/onboard-interactive.ts +++ b/src/commands/onboard-interactive.ts @@ -37,6 +37,7 @@ import { handleReset, openUrl, printWizardHeader, + probeGatewayReachable, randomToken, summarizeExistingConfig, } from "./onboard-helpers.js"; @@ -113,14 +114,44 @@ export async function runInteractiveOnboarding( } } + const localUrl = "ws://127.0.0.1:18789"; + const localProbe = await probeGatewayReachable({ + url: localUrl, + token: process.env.CLAWDIS_GATEWAY_TOKEN, + password: + baseConfig.gateway?.auth?.password ?? + process.env.CLAWDIS_GATEWAY_PASSWORD, + }); + const remoteUrl = baseConfig.gateway?.remote?.url?.trim() ?? ""; + const remoteProbe = remoteUrl + ? await probeGatewayReachable({ + url: remoteUrl, + token: baseConfig.gateway?.remote?.token, + }) + : null; + const mode = opts.mode ?? (guardCancel( await select({ message: "Where will the Gateway run?", options: [ - { value: "local", label: "Local (this machine)" }, - { value: "remote", label: "Remote (info-only)" }, + { + value: "local", + label: "Local (this machine)", + hint: localProbe.ok + ? `Gateway reachable (${localUrl})` + : `No gateway detected (${localUrl})`, + }, + { + value: "remote", + label: "Remote (info-only)", + hint: !remoteUrl + ? "No remote URL configured yet" + : remoteProbe?.ok + ? `Gateway reachable (${remoteUrl})` + : `Configured but unreachable (${remoteUrl})`, + }, ], }), runtime,