UNPKG

@gguf/claw

Version:

WhatsApp gateway CLI (Baileys web) with Pi RPC agent

278 lines (275 loc) 11 kB
import { g as resolveStateDir, o as resolveConfigPath, u as resolveGatewayPort } from "./paths-scjhy7N2.js"; import { g as shortenHomeInString, h as resolveUserPath, s as ensureDir, t as CONFIG_DIR } from "./utils-CKSrBNwq.js"; import { i as loadConfig } from "./config-CAuZ-EkU.js"; import { h as GATEWAY_CLIENT_NAMES, m as GATEWAY_CLIENT_MODES } from "./message-channel-Bpfe5l5f.js"; import { Ct as PROTOCOL_VERSION, jt as loadOrCreateDeviceIdentity, kt as normalizeFingerprint, t as GatewayClient } from "./client-BYVbRnuQ.js"; import { t as pickPrimaryTailnetIPv4 } from "./tailnet-DLDGNuH2.js"; import path from "node:path"; import fs from "node:fs/promises"; import { execFile } from "node:child_process"; import { promisify } from "node:util"; import { X509Certificate, randomUUID } from "node:crypto"; //#region src/infra/tls/gateway.ts const execFileAsync = promisify(execFile); async function fileExists(filePath) { try { await fs.access(filePath); return true; } catch { return false; } } async function generateSelfSignedCert(params) { const certDir = path.dirname(params.certPath); const keyDir = path.dirname(params.keyPath); await ensureDir(certDir); if (keyDir !== certDir) await ensureDir(keyDir); await execFileAsync("openssl", [ "req", "-x509", "-newkey", "rsa:2048", "-sha256", "-days", "3650", "-nodes", "-keyout", params.keyPath, "-out", params.certPath, "-subj", "/CN=openclaw-gateway" ]); await fs.chmod(params.keyPath, 384).catch(() => {}); await fs.chmod(params.certPath, 384).catch(() => {}); params.log?.info?.(`gateway tls: generated self-signed cert at ${shortenHomeInString(params.certPath)}`); } async function loadGatewayTlsRuntime(cfg, log) { if (!cfg || cfg.enabled !== true) return { enabled: false, required: false }; const autoGenerate = cfg.autoGenerate !== false; const baseDir = path.join(CONFIG_DIR, "gateway", "tls"); const certPath = resolveUserPath(cfg.certPath ?? path.join(baseDir, "gateway-cert.pem")); const keyPath = resolveUserPath(cfg.keyPath ?? path.join(baseDir, "gateway-key.pem")); const caPath = cfg.caPath ? resolveUserPath(cfg.caPath) : void 0; const hasCert = await fileExists(certPath); const hasKey = await fileExists(keyPath); if (!hasCert && !hasKey && autoGenerate) try { await generateSelfSignedCert({ certPath, keyPath, log }); } catch (err) { return { enabled: false, required: true, certPath, keyPath, error: `gateway tls: failed to generate cert (${String(err)})` }; } if (!await fileExists(certPath) || !await fileExists(keyPath)) return { enabled: false, required: true, certPath, keyPath, error: "gateway tls: cert/key missing" }; try { const cert = await fs.readFile(certPath, "utf8"); const key = await fs.readFile(keyPath, "utf8"); const ca = caPath ? await fs.readFile(caPath, "utf8") : void 0; const fingerprintSha256 = normalizeFingerprint(new X509Certificate(cert).fingerprint256 ?? ""); if (!fingerprintSha256) return { enabled: false, required: true, certPath, keyPath, caPath, error: "gateway tls: unable to compute certificate fingerprint" }; return { enabled: true, required: true, certPath, keyPath, caPath, fingerprintSha256, tlsOptions: { cert, key, ca, minVersion: "TLSv1.3" } }; } catch (err) { return { enabled: false, required: true, certPath, keyPath, caPath, error: `gateway tls: failed to load cert (${String(err)})` }; } } //#endregion //#region src/gateway/call.ts function resolveExplicitGatewayAuth(opts) { return { token: typeof opts?.token === "string" && opts.token.trim().length > 0 ? opts.token.trim() : void 0, password: typeof opts?.password === "string" && opts.password.trim().length > 0 ? opts.password.trim() : void 0 }; } function ensureExplicitGatewayAuth(params) { if (!params.urlOverride) return; if (params.auth.token || params.auth.password) return; const message = [ "gateway url override requires explicit credentials", params.errorHint, params.configPath ? `Config: ${params.configPath}` : void 0 ].filter(Boolean).join("\n"); throw new Error(message); } function buildGatewayConnectionDetails(options = {}) { const config = options.config ?? loadConfig(); const configPath = options.configPath ?? resolveConfigPath(process.env, resolveStateDir(process.env)); const isRemoteMode = config.gateway?.mode === "remote"; const remote = isRemoteMode ? config.gateway?.remote : void 0; const tlsEnabled = config.gateway?.tls?.enabled === true; const localPort = resolveGatewayPort(config); const tailnetIPv4 = pickPrimaryTailnetIPv4(); const bindMode = config.gateway?.bind ?? "loopback"; const preferTailnet = bindMode === "tailnet" && !!tailnetIPv4; const scheme = tlsEnabled ? "wss" : "ws"; const localUrl = preferTailnet && tailnetIPv4 ? `${scheme}://${tailnetIPv4}:${localPort}` : `${scheme}://127.0.0.1:${localPort}`; const urlOverride = typeof options.url === "string" && options.url.trim().length > 0 ? options.url.trim() : void 0; const remoteUrl = typeof remote?.url === "string" && remote.url.trim().length > 0 ? remote.url.trim() : void 0; const remoteMisconfigured = isRemoteMode && !urlOverride && !remoteUrl; const url = urlOverride || remoteUrl || localUrl; const urlSource = urlOverride ? "cli --url" : remoteUrl ? "config gateway.remote.url" : remoteMisconfigured ? "missing gateway.remote.url (fallback local)" : preferTailnet && tailnetIPv4 ? `local tailnet ${tailnetIPv4}` : "local loopback"; const remoteFallbackNote = remoteMisconfigured ? "Warn: gateway.mode=remote but gateway.remote.url is missing; set gateway.remote.url or switch gateway.mode=local." : void 0; const bindDetail = !urlOverride && !remoteUrl ? `Bind: ${bindMode}` : void 0; return { url, urlSource, bindDetail, remoteFallbackNote, message: [ `Gateway target: ${url}`, `Source: ${urlSource}`, `Config: ${configPath}`, bindDetail, remoteFallbackNote ].filter(Boolean).join("\n") }; } async function callGateway(opts) { const timeoutMs = opts.timeoutMs ?? 1e4; const config = opts.config ?? loadConfig(); const isRemoteMode = config.gateway?.mode === "remote"; const remote = isRemoteMode ? config.gateway?.remote : void 0; const urlOverride = typeof opts.url === "string" && opts.url.trim().length > 0 ? opts.url.trim() : void 0; const explicitAuth = resolveExplicitGatewayAuth({ token: opts.token, password: opts.password }); ensureExplicitGatewayAuth({ urlOverride, auth: explicitAuth, errorHint: "Fix: pass --token or --password (or gatewayToken in tools).", configPath: opts.configPath ?? resolveConfigPath(process.env, resolveStateDir(process.env)) }); const remoteUrl = typeof remote?.url === "string" && remote.url.trim().length > 0 ? remote.url.trim() : void 0; if (isRemoteMode && !urlOverride && !remoteUrl) { const configPath = opts.configPath ?? resolveConfigPath(process.env, resolveStateDir(process.env)); throw new Error([ "gateway remote mode misconfigured: gateway.remote.url missing", `Config: ${configPath}`, "Fix: set gateway.remote.url, or set gateway.mode=local." ].join("\n")); } const authToken = config.gateway?.auth?.token; const authPassword = config.gateway?.auth?.password; const connectionDetails = buildGatewayConnectionDetails({ config, url: urlOverride, ...opts.configPath ? { configPath: opts.configPath } : {} }); const url = connectionDetails.url; const tlsRuntime = config.gateway?.tls?.enabled === true && !urlOverride && !remoteUrl && url.startsWith("wss://") ? await loadGatewayTlsRuntime(config.gateway?.tls) : void 0; const remoteTlsFingerprint = isRemoteMode && !urlOverride && remoteUrl && typeof remote?.tlsFingerprint === "string" ? remote.tlsFingerprint.trim() : void 0; const tlsFingerprint = (typeof opts.tlsFingerprint === "string" ? opts.tlsFingerprint.trim() : void 0) || remoteTlsFingerprint || (tlsRuntime?.enabled ? tlsRuntime.fingerprintSha256 : void 0); const token = explicitAuth.token || (!urlOverride ? isRemoteMode ? typeof remote?.token === "string" && remote.token.trim().length > 0 ? remote.token.trim() : void 0 : process.env.OPENCLAW_GATEWAY_TOKEN?.trim() || process.env.CLAWDBOT_GATEWAY_TOKEN?.trim() || (typeof authToken === "string" && authToken.trim().length > 0 ? authToken.trim() : void 0) : void 0); const password = explicitAuth.password || (!urlOverride ? process.env.OPENCLAW_GATEWAY_PASSWORD?.trim() || process.env.CLAWDBOT_GATEWAY_PASSWORD?.trim() || (isRemoteMode ? typeof remote?.password === "string" && remote.password.trim().length > 0 ? remote.password.trim() : void 0 : typeof authPassword === "string" && authPassword.trim().length > 0 ? authPassword.trim() : void 0) : void 0); const formatCloseError = (code, reason) => { const reasonText = reason?.trim() || "no close reason"; const hint = code === 1006 ? "abnormal closure (no close frame)" : code === 1e3 ? "normal closure" : ""; return `gateway closed (${code}${hint ? ` ${hint}` : ""}): ${reasonText}\n${connectionDetails.message}`; }; const formatTimeoutError = () => `gateway timeout after ${timeoutMs}ms\n${connectionDetails.message}`; return await new Promise((resolve, reject) => { let settled = false; let ignoreClose = false; const stop = (err, value) => { if (settled) return; settled = true; clearTimeout(timer); if (err) reject(err); else resolve(value); }; const client = new GatewayClient({ url, token, password, tlsFingerprint, instanceId: opts.instanceId ?? randomUUID(), clientName: opts.clientName ?? GATEWAY_CLIENT_NAMES.CLI, clientDisplayName: opts.clientDisplayName, clientVersion: opts.clientVersion ?? "dev", platform: opts.platform, mode: opts.mode ?? GATEWAY_CLIENT_MODES.CLI, role: "operator", scopes: [ "operator.admin", "operator.approvals", "operator.pairing" ], deviceIdentity: loadOrCreateDeviceIdentity(), minProtocol: opts.minProtocol ?? PROTOCOL_VERSION, maxProtocol: opts.maxProtocol ?? PROTOCOL_VERSION, onHelloOk: async () => { try { const result = await client.request(opts.method, opts.params, { expectFinal: opts.expectFinal }); ignoreClose = true; stop(void 0, result); client.stop(); } catch (err) { ignoreClose = true; client.stop(); stop(err); } }, onClose: (code, reason) => { if (settled || ignoreClose) return; ignoreClose = true; client.stop(); stop(new Error(formatCloseError(code, reason))); } }); const timer = setTimeout(() => { ignoreClose = true; client.stop(); stop(new Error(formatTimeoutError())); }, timeoutMs); client.start(); }); } function randomIdempotencyKey() { return randomUUID(); } //#endregion export { resolveExplicitGatewayAuth as a, randomIdempotencyKey as i, callGateway as n, loadGatewayTlsRuntime as o, ensureExplicitGatewayAuth as r, buildGatewayConnectionDetails as t };