UNPKG

@gguf/claw

Version:

Multi-channel AI gateway with extensible messaging integrations

456 lines (453 loc) 19.3 kB
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 };