@gguf/claw
Version:
Multi-channel AI gateway with extensible messaging integrations
456 lines (453 loc) • 19.3 kB
JavaScript
import "./paths-B4BZAPZh.js";
import { h as pathExists, y as resolveUserPath } from "./utils-CP9YLh6M.js";
import "./thinking-EAliFiVK.js";
import "./reply-BylFHBd4.js";
import "./registry-B-j4DRfe.js";
import { p as restoreTerminalState } from "./subsystem-BCQGGxdd.js";
import "./exec-DYqRzFbo.js";
import { h as DEFAULT_BOOTSTRAP_FILENAME } from "./agent-scope-BnZW9Gh2.js";
import "./model-selection-CqaTAlhy.js";
import "./github-copilot-token-D2zp6kMZ.js";
import { r as resolveCliName, t as formatCliCommand } from "./command-format-DEKzLnLg.js";
import "./boolean-BsqeuxE6.js";
import "./env-VriqyjXT.js";
import "./message-channel-Bena1Tzd.js";
import "./send-CsyONLnQ.js";
import "./config-PQiujvsf.js";
import "./manifest-registry-4k4vkhPS.js";
import "./runner-U04aiHHC.js";
import "./image-DxEpBZim.js";
import "./models-config-B459BnCS.js";
import "./pi-model-discovery-4uUnLc3n.js";
import "./pi-embedded-helpers-BdscQhyS.js";
import "./sandbox-UAzvS0V6.js";
import "./image-ops-CI1VknD1.js";
import "./common-a25M2Kvi.js";
import "./chrome-Dd5zBIFu.js";
import "./tailscale-C3JsBEiY.js";
import "./auth-DZSWPd8D.js";
import "./server-context-DAWsUNUs.js";
import "./frontmatter-6HZ0_y2V.js";
import "./skills-CLmzWt48.js";
import "./routes-DIQa_0pt.js";
import "./redact-B7Mjvk0c.js";
import "./errors-kfGqPQ4b.js";
import "./fs-safe-_OpKPuZA.js";
import "./paths-D1WZUYry.js";
import "./ssrf-6f5m2MMA.js";
import "./store-DSGYY-H9.js";
import "./ports-DwDxX6cS.js";
import "./trash-DLzIf__E.js";
import "./sessions-Z1BZU3xh.js";
import "./dock-7F-MiPtF.js";
import "./normalize-Ci8UyR3Z.js";
import "./accounts-Bj1dJ-fd.js";
import "./accounts-DOl1Wkxo.js";
import "./accounts-t6R5_PzC.js";
import "./bindings-Dja1z-h8.js";
import "./logging-xYH6GmRT.js";
import "./send-uu2Jc-Js.js";
import "./plugins-CKbXkuXd.js";
import "./paths-C2NfoGZE.js";
import "./tool-display-Bx4M6uNT.js";
import "./fetch-guard-C1sYsWgl.js";
import "./api-key-rotation-CpGefdGL.js";
import "./local-roots-DiDvXIIo.js";
import "./sqlite-JcMMx8Z5.js";
import "./model-catalog-CIixTnGt.js";
import "./tokens-D4lZk7-h.js";
import "./with-timeout-DijiQjw6.js";
import "./deliver-DgvS3uCz.js";
import "./diagnostic-UUwku4RV.js";
import "./diagnostic-session-state-ByqoIyGn.js";
import "./send-DbwyyQAv.js";
import "./model-VbWjwqaW.js";
import "./reply-prefix-DAK7-zK4.js";
import "./memory-cli-DxmtLhh_.js";
import "./manager-C0teWocQ.js";
import "./retry-rUEdE6zT.js";
import "./chunk-CMylpCPi.js";
import "./markdown-tables-B9VGUdDc.js";
import "./ir-24qCLTKH.js";
import "./render-CXDO_kgw.js";
import "./commands-registry-bb_U-3FM.js";
import "./client-BdSkEtCd.js";
import "./call-BCz_8mqq.js";
import "./pairing-token-B-_eiWlR.js";
import "./channel-activity-DynIQUB_.js";
import "./fetch-Bu5zBCce.js";
import "./tables-C70h_80U.js";
import "./send-CAfFUUkm.js";
import "./pairing-store-T5DpOfoL.js";
import "./proxy-BiOWrea2.js";
import "./links-BVCMOGeE.js";
import "./cli-utils-CX1oQ81G.js";
import "./help-format-dbk2xW0E.js";
import "./progress-CaVMHLaE.js";
import "./resolve-route-2TU_aaLs.js";
import "./replies-NhrZJ3yf.js";
import "./skill-commands-N8Sho46-.js";
import "./workspace-dirs-CE3CxabZ.js";
import "./pi-tools.policy-DTy1DnPK.js";
import "./outbound-attachment-CntoLTI_.js";
import "./delivery-queue-CxDDEWQg.js";
import "./session-cost-usage-BlUDaylg.js";
import "./send-4LoRaltQ.js";
import { b as waitForGatewayReachable, f as openUrl, g as resolveControlUiLinks, i as detectBrowserOpenSupport, m as probeGatewayReachable, o as formatControlUiSshHint } from "./onboard-helpers-DbjfBSMX.js";
import "./prompt-style-VqCNjBi8.js";
import "./pairing-labels-DaESq9ML.js";
import "./exec-approvals-q0C8VDMT.js";
import "./nodes-screen-5fMfTT2n.js";
import "./control-service-BoO2TGOx.js";
import "./stagger-B6VQyn1F.js";
import "./channel-selection-DuDSoVSC.js";
import "./runtime-guard-DlT3NecY.js";
import "./program-context-Cx3damTh.js";
import "./note-C-OTWf0F.js";
import { r as installCompletion } from "./completion-cli-BOzsB_m2.js";
import { a as gatewayInstallErrorHint, i as buildGatewayInstallPlan, n as GATEWAY_DAEMON_RUNTIME_OPTIONS, t as DEFAULT_GATEWAY_DAEMON_RUNTIME } from "./daemon-runtime-CZuP7tS4.js";
import { r as isSystemdUserServiceAvailable } from "./systemd-B6S4ND7H.js";
import { t as resolveGatewayService } from "./service-Bw9h81em.js";
import { r as healthCommand } from "./health-Bt3xmMjK.js";
import { t as ensureControlUiAssetsBuilt } from "./control-ui-assets-BmaUgzFj.js";
import { t as formatHealthCheckFailure } from "./health-format-DQNr1Hum.js";
import { r as ensureCompletionCacheExists, t as checkShellCompletionStatus } from "./doctor-completion-D-vVYUz8.js";
import { t as runTui } from "./tui-Bf5VxLzJ.js";
import os from "node:os";
import path from "node:path";
import fs from "node:fs/promises";
//#region src/wizard/onboarding.completion.ts
async function resolveProfileHint(shell) {
const home = process.env.HOME || os.homedir();
if (shell === "zsh") return "~/.zshrc";
if (shell === "bash") return await pathExists(path.join(home, ".bashrc")) ? "~/.bashrc" : "~/.bash_profile";
if (shell === "fish") return "~/.config/fish/config.fish";
return "$PROFILE";
}
function formatReloadHint(shell, profileHint) {
if (shell === "powershell") return "Restart your shell (or reload your PowerShell profile).";
return `Restart your shell or run: source ${profileHint}`;
}
async function setupOnboardingShellCompletion(params) {
const deps = {
resolveCliName,
checkShellCompletionStatus,
ensureCompletionCacheExists,
installCompletion,
...params.deps
};
const cliName = deps.resolveCliName();
const completionStatus = await deps.checkShellCompletionStatus(cliName);
if (completionStatus.usesSlowPattern) {
if (await deps.ensureCompletionCacheExists(cliName)) await deps.installCompletion(completionStatus.shell, true, cliName);
return;
}
if (completionStatus.profileInstalled && !completionStatus.cacheExists) {
await deps.ensureCompletionCacheExists(cliName);
return;
}
if (!completionStatus.profileInstalled) {
if (!(params.flow === "quickstart" ? true : await params.prompter.confirm({
message: `Enable ${completionStatus.shell} shell completion for ${cliName}?`,
initialValue: true
}))) return;
if (!await deps.ensureCompletionCacheExists(cliName)) {
await params.prompter.note(`Failed to generate completion cache. Run \`${cliName} completion --install\` later.`, "Shell completion");
return;
}
await deps.installCompletion(completionStatus.shell, true, cliName);
const profileHint = await resolveProfileHint(completionStatus.shell);
await params.prompter.note(`Shell completion installed. ${formatReloadHint(completionStatus.shell, profileHint)}`, "Shell completion");
}
}
//#endregion
//#region src/wizard/onboarding.finalize.ts
async function finalizeOnboardingWizard(options) {
const { flow, opts, baseConfig, nextConfig, settings, prompter, runtime } = options;
const withWizardProgress = async (label, options, work) => {
const progress = prompter.progress(label);
try {
return await work(progress);
} finally {
progress.stop(options.doneMessage);
}
};
const systemdAvailable = process.platform === "linux" ? await isSystemdUserServiceAvailable() : true;
if (process.platform === "linux" && !systemdAvailable) await prompter.note("Systemd user services are unavailable. Skipping lingering checks and service install.", "Systemd");
if (process.platform === "linux" && systemdAvailable) {
const { ensureSystemdUserLingerInteractive } = await import("./systemd-linger-C3Tnphf9.js").then((n) => n.r);
await ensureSystemdUserLingerInteractive({
runtime,
prompter: {
confirm: prompter.confirm,
note: prompter.note
},
reason: "Linux installs use a systemd user service by default. Without lingering, systemd stops the user session on logout/idle and kills the Gateway.",
requireConfirm: false
});
}
const explicitInstallDaemon = typeof opts.installDaemon === "boolean" ? opts.installDaemon : void 0;
let installDaemon;
if (explicitInstallDaemon !== void 0) installDaemon = explicitInstallDaemon;
else if (process.platform === "linux" && !systemdAvailable) installDaemon = false;
else if (flow === "quickstart") installDaemon = true;
else installDaemon = await prompter.confirm({
message: "Install Gateway service (recommended)",
initialValue: true
});
if (process.platform === "linux" && !systemdAvailable && installDaemon) {
await prompter.note("Systemd user services are unavailable; skipping service install. Use your container supervisor or `docker compose up -d`.", "Gateway service");
installDaemon = false;
}
if (installDaemon) {
const daemonRuntime = flow === "quickstart" ? DEFAULT_GATEWAY_DAEMON_RUNTIME : await prompter.select({
message: "Gateway service runtime",
options: GATEWAY_DAEMON_RUNTIME_OPTIONS,
initialValue: opts.daemonRuntime ?? DEFAULT_GATEWAY_DAEMON_RUNTIME
});
if (flow === "quickstart") await prompter.note("QuickStart uses Node for the Gateway service (stable + supported).", "Gateway service runtime");
const service = resolveGatewayService();
const loaded = await service.isLoaded({ env: process.env });
if (loaded) {
const action = await prompter.select({
message: "Gateway service already installed",
options: [
{
value: "restart",
label: "Restart"
},
{
value: "reinstall",
label: "Reinstall"
},
{
value: "skip",
label: "Skip"
}
]
});
if (action === "restart") await withWizardProgress("Gateway service", { doneMessage: "Gateway service restarted." }, async (progress) => {
progress.update("Restarting Gateway service…");
await service.restart({
env: process.env,
stdout: process.stdout
});
});
else if (action === "reinstall") await withWizardProgress("Gateway service", { doneMessage: "Gateway service uninstalled." }, async (progress) => {
progress.update("Uninstalling Gateway service…");
await service.uninstall({
env: process.env,
stdout: process.stdout
});
});
}
if (!loaded || loaded && !await service.isLoaded({ env: process.env })) {
const progress = prompter.progress("Gateway service");
let installError = null;
try {
progress.update("Preparing Gateway service…");
const { programArguments, workingDirectory, environment } = await buildGatewayInstallPlan({
env: process.env,
port: settings.port,
token: settings.gatewayToken,
runtime: daemonRuntime,
warn: (message, title) => prompter.note(message, title),
config: nextConfig
});
progress.update("Installing Gateway service…");
await service.install({
env: process.env,
stdout: process.stdout,
programArguments,
workingDirectory,
environment
});
} catch (err) {
installError = err instanceof Error ? err.message : String(err);
} finally {
progress.stop(installError ? "Gateway service install failed." : "Gateway service installed.");
}
if (installError) {
await prompter.note(`Gateway service install failed: ${installError}`, "Gateway");
await prompter.note(gatewayInstallErrorHint(), "Gateway");
}
}
}
if (!opts.skipHealth) {
await waitForGatewayReachable({
url: resolveControlUiLinks({
bind: nextConfig.gateway?.bind ?? "loopback",
port: settings.port,
customBindHost: nextConfig.gateway?.customBindHost,
basePath: void 0
}).wsUrl,
token: settings.gatewayToken,
deadlineMs: 15e3
});
try {
await healthCommand({
json: false,
timeoutMs: 1e4
}, runtime);
} catch (err) {
runtime.error(formatHealthCheckFailure(err));
await prompter.note([
"Docs:",
"https://docs.openclaw.ai/gateway/health",
"https://docs.openclaw.ai/gateway/troubleshooting"
].join("\n"), "Health check help");
}
}
const controlUiEnabled = nextConfig.gateway?.controlUi?.enabled ?? baseConfig.gateway?.controlUi?.enabled ?? true;
if (!opts.skipUi && controlUiEnabled) {
const controlUiAssets = await ensureControlUiAssetsBuilt(runtime);
if (!controlUiAssets.ok && controlUiAssets.message) runtime.error(controlUiAssets.message);
}
await prompter.note([
"Add nodes for extra features:",
"- macOS app (system + notifications)",
"- iOS app (camera/canvas)",
"- Android app (camera/canvas)"
].join("\n"), "Optional apps");
const controlUiBasePath = nextConfig.gateway?.controlUi?.basePath ?? baseConfig.gateway?.controlUi?.basePath;
const links = resolveControlUiLinks({
bind: settings.bind,
port: settings.port,
customBindHost: settings.customBindHost,
basePath: controlUiBasePath
});
const authedUrl = settings.authMode === "token" && settings.gatewayToken ? `${links.httpUrl}#token=${encodeURIComponent(settings.gatewayToken)}` : links.httpUrl;
const gatewayProbe = await probeGatewayReachable({
url: links.wsUrl,
token: settings.authMode === "token" ? settings.gatewayToken : void 0,
password: settings.authMode === "password" ? nextConfig.gateway?.auth?.password : ""
});
const gatewayStatusLine = gatewayProbe.ok ? "Gateway: reachable" : `Gateway: not detected${gatewayProbe.detail ? ` (${gatewayProbe.detail})` : ""}`;
const bootstrapPath = path.join(resolveUserPath(options.workspaceDir), DEFAULT_BOOTSTRAP_FILENAME);
const hasBootstrap = await fs.access(bootstrapPath).then(() => true).catch(() => false);
await prompter.note([
`Web UI: ${links.httpUrl}`,
settings.authMode === "token" && settings.gatewayToken ? `Web UI (with token): ${authedUrl}` : void 0,
`Gateway WS: ${links.wsUrl}`,
gatewayStatusLine,
"Docs: https://docs.openclaw.ai/web/control-ui"
].filter(Boolean).join("\n"), "Control UI");
let controlUiOpened = false;
let controlUiOpenHint;
let hatchChoice = null;
let launchedTui = false;
if (!opts.skipUi && gatewayProbe.ok) {
if (hasBootstrap) await prompter.note([
"This is the defining action that makes your agent you.",
"Please take your time.",
"The more you tell it, the better the experience will be.",
"We will send: \"Wake up, my friend!\""
].join("\n"), "Start TUI (best option!)");
await prompter.note([
"Gateway token: shared auth for the Gateway + Control UI.",
"Stored in: ~/.openclaw/openclaw.json (gateway.auth.token) or OPENCLAW_GATEWAY_TOKEN.",
`View token: ${formatCliCommand("openclaw config get gateway.auth.token")}`,
`Generate token: ${formatCliCommand("openclaw doctor --generate-gateway-token")}`,
"Web UI stores a copy in this browser's localStorage (openclaw.control.settings.v1).",
`Open the dashboard anytime: ${formatCliCommand("openclaw dashboard --no-open")}`,
"If prompted: paste the token into Control UI settings (or use the tokenized dashboard URL)."
].join("\n"), "Token");
hatchChoice = await prompter.select({
message: "How do you want to hatch your bot?",
options: [
{
value: "tui",
label: "Hatch in TUI (recommended)"
},
{
value: "web",
label: "Open the Web UI"
},
{
value: "later",
label: "Do this later"
}
],
initialValue: "tui"
});
if (hatchChoice === "tui") {
restoreTerminalState("pre-onboarding tui", { resumeStdinIfPaused: true });
await runTui({
url: links.wsUrl,
token: settings.authMode === "token" ? settings.gatewayToken : void 0,
password: settings.authMode === "password" ? nextConfig.gateway?.auth?.password : "",
deliver: false,
message: hasBootstrap ? "Wake up, my friend!" : void 0
});
launchedTui = true;
} else if (hatchChoice === "web") {
if ((await detectBrowserOpenSupport()).ok) {
controlUiOpened = await openUrl(authedUrl);
if (!controlUiOpened) controlUiOpenHint = formatControlUiSshHint({
port: settings.port,
basePath: controlUiBasePath,
token: settings.authMode === "token" ? settings.gatewayToken : void 0
});
} else controlUiOpenHint = formatControlUiSshHint({
port: settings.port,
basePath: controlUiBasePath,
token: settings.authMode === "token" ? settings.gatewayToken : void 0
});
await prompter.note([
`Dashboard link (with token): ${authedUrl}`,
controlUiOpened ? "Opened in your browser. Keep that tab to control OpenClaw." : "Copy/paste this URL in a browser on this machine to control OpenClaw.",
controlUiOpenHint
].filter(Boolean).join("\n"), "Dashboard ready");
} else await prompter.note(`When you're ready: ${formatCliCommand("openclaw dashboard --no-open")}`, "Later");
} else if (opts.skipUi) await prompter.note("Skipping Control UI/TUI prompts.", "Control UI");
await prompter.note(["Back up your agent workspace.", "Docs: https://docs.openclaw.ai/concepts/agent-workspace"].join("\n"), "Workspace backup");
await prompter.note("Running agents on your computer is risky — harden your setup: https://docs.openclaw.ai/security", "Security");
await setupOnboardingShellCompletion({
flow,
prompter
});
if (!opts.skipUi && settings.authMode === "token" && Boolean(settings.gatewayToken) && hatchChoice === null) {
if ((await detectBrowserOpenSupport()).ok) {
controlUiOpened = await openUrl(authedUrl);
if (!controlUiOpened) controlUiOpenHint = formatControlUiSshHint({
port: settings.port,
basePath: controlUiBasePath,
token: settings.gatewayToken
});
} else controlUiOpenHint = formatControlUiSshHint({
port: settings.port,
basePath: controlUiBasePath,
token: settings.gatewayToken
});
await prompter.note([
`Dashboard link (with token): ${authedUrl}`,
controlUiOpened ? "Opened in your browser. Keep that tab to control OpenClaw." : "Copy/paste this URL in a browser on this machine to control OpenClaw.",
controlUiOpenHint
].filter(Boolean).join("\n"), "Dashboard ready");
}
const webSearchKey = (nextConfig.tools?.web?.search?.apiKey ?? "").trim();
const webSearchEnv = (process.env.BRAVE_API_KEY ?? "").trim();
const hasWebSearchKey = Boolean(webSearchKey || webSearchEnv);
await prompter.note(hasWebSearchKey ? [
"Web search is enabled, so your agent can look things up online when needed.",
"",
webSearchKey ? "API key: stored in config (tools.web.search.apiKey)." : "API key: provided via BRAVE_API_KEY env var (Gateway environment).",
"Docs: https://docs.openclaw.ai/tools/web"
].join("\n") : [
"If you want your agent to be able to search the web, you’ll need an API key.",
"",
"OpenClaw uses Brave Search for the `web_search` tool. Without a Brave Search API key, web search won’t work.",
"",
"Set it up interactively:",
`- Run: ${formatCliCommand("openclaw configure --section web")}`,
"- Enable web_search and paste your Brave Search API key",
"",
"Alternative: set BRAVE_API_KEY in the Gateway environment (no config changes).",
"Docs: https://docs.openclaw.ai/tools/web"
].join("\n"), "Web search (optional)");
await prompter.note("What now: https://openclaw.ai/showcase (\"What People Are Building\").", "What now");
await prompter.outro(controlUiOpened ? "Onboarding complete. Dashboard opened; keep that tab to control OpenClaw." : "Onboarding complete. Use the dashboard link above to control OpenClaw.");
return { launchedTui };
}
//#endregion
export { finalizeOnboardingWizard };