@gguf/claw
Version:
WhatsApp gateway CLI (Baileys web) with Pi RPC agent
309 lines (306 loc) • 14.5 kB
JavaScript
import { F as CONFIG_PATH, W as resolveGatewayPort, k as theme, p as defaultRuntime, v as danger } from "./entry.js";
import "./auth-profiles-CfFGCDJa.js";
import { t as formatCliCommand } from "./command-format-3xiXujG0.js";
import "./agent-scope-jm0ZdXwM.js";
import { i as displayPath } from "./utils-PmTbZoD1.js";
import { t as runCommandWithTimeout } from "./exec-BIMFe4XS.js";
import "./github-copilot-token-rP-6QdKv.js";
import { a as readConfigFileSnapshot, l as validateConfigObjectWithPlugins, r as loadConfig, s as writeConfigFile } from "./config-DCT1RAo6.js";
import "./manifest-registry-tuAcHxrV.js";
import { t as formatDocsLink } from "./links-jGisPfXW.js";
import "./skills-DtwGIkTI.js";
import { C as normalizeServePath, S as normalizeHooksPath, T as resolveGmailHookRuntimeConfig, _ as buildGogWatchServeArgs, a as ensureTopic, b as generateHookToken, c as DEFAULT_GMAIL_LABEL, d as DEFAULT_GMAIL_SERVE_BIND, f as DEFAULT_GMAIL_SERVE_PATH, g as buildDefaultHookUrl, h as DEFAULT_GMAIL_TOPIC, i as ensureTailscaleEndpoint, l as DEFAULT_GMAIL_MAX_BYTES, m as DEFAULT_GMAIL_SUBSCRIPTION, n as ensureGcloudAuth, o as resolveProjectIdFromGogCredentials, p as DEFAULT_GMAIL_SERVE_PORT, r as ensureSubscription, s as runGcloud, t as ensureDependency, u as DEFAULT_GMAIL_RENEW_MINUTES, v as buildGogWatchStartArgs, w as parseTopicPath, x as mergeHookPresets, y as buildTopicPath } from "./gmail-setup-utils-4czdWNCN.js";
import { spawn } from "node:child_process";
//#region src/hooks/gmail-ops.ts
const DEFAULT_GMAIL_TOPIC_IAM_MEMBER = "serviceAccount:gmail-api-push@system.gserviceaccount.com";
async function runGmailSetup(opts) {
await ensureDependency("gcloud", ["--cask", "gcloud-cli"]);
await ensureDependency("gog", ["gogcli"]);
if (opts.tailscale !== "off" && !opts.pushEndpoint) await ensureDependency("tailscale", ["tailscale"]);
await ensureGcloudAuth();
const configSnapshot = await readConfigFileSnapshot();
if (!configSnapshot.valid) throw new Error(`Config invalid: ${CONFIG_PATH}`);
const baseConfig = configSnapshot.config;
const hooksPath = normalizeHooksPath(baseConfig.hooks?.path);
const hookToken = opts.hookToken ?? baseConfig.hooks?.token ?? generateHookToken();
const pushToken = opts.pushToken ?? baseConfig.hooks?.gmail?.pushToken ?? generateHookToken();
const topicInput = opts.topic ?? baseConfig.hooks?.gmail?.topic ?? DEFAULT_GMAIL_TOPIC;
const parsedTopic = parseTopicPath(topicInput);
const topicName = parsedTopic?.topicName ?? topicInput;
const projectId = opts.project ?? parsedTopic?.projectId ?? await resolveProjectIdFromGogCredentials();
if (!projectId) throw new Error("GCP project id required (use --project or ensure gog credentials are available)");
const topicPath = buildTopicPath(projectId, topicName);
const subscription = opts.subscription ?? DEFAULT_GMAIL_SUBSCRIPTION;
const label = opts.label ?? DEFAULT_GMAIL_LABEL;
const hookUrl = opts.hookUrl ?? baseConfig.hooks?.gmail?.hookUrl ?? buildDefaultHookUrl(hooksPath, resolveGatewayPort(baseConfig));
const serveBind = opts.bind ?? DEFAULT_GMAIL_SERVE_BIND;
const servePort = opts.port ?? DEFAULT_GMAIL_SERVE_PORT;
const configuredServePath = opts.path ?? baseConfig.hooks?.gmail?.serve?.path;
const configuredTailscaleTarget = opts.tailscaleTarget ?? baseConfig.hooks?.gmail?.tailscale?.target;
const normalizedServePath = typeof configuredServePath === "string" && configuredServePath.trim().length > 0 ? normalizeServePath(configuredServePath) : DEFAULT_GMAIL_SERVE_PATH;
const normalizedTailscaleTarget = typeof configuredTailscaleTarget === "string" && configuredTailscaleTarget.trim().length > 0 ? configuredTailscaleTarget.trim() : void 0;
const includeBody = opts.includeBody ?? true;
const maxBytes = opts.maxBytes ?? DEFAULT_GMAIL_MAX_BYTES;
const renewEveryMinutes = opts.renewEveryMinutes ?? DEFAULT_GMAIL_RENEW_MINUTES;
const tailscaleMode = opts.tailscale ?? "funnel";
const servePath = normalizeServePath(tailscaleMode !== "off" && !normalizedTailscaleTarget ? "/" : normalizedServePath);
const tailscalePath = normalizeServePath(opts.tailscalePath ?? baseConfig.hooks?.gmail?.tailscale?.path ?? (tailscaleMode !== "off" ? normalizedServePath : servePath));
await runGcloud([
"config",
"set",
"project",
projectId,
"--quiet"
]);
await runGcloud([
"services",
"enable",
"gmail.googleapis.com",
"pubsub.googleapis.com",
"--project",
projectId,
"--quiet"
]);
await ensureTopic(projectId, topicName);
await runGcloud([
"pubsub",
"topics",
"add-iam-policy-binding",
topicName,
"--project",
projectId,
"--member",
DEFAULT_GMAIL_TOPIC_IAM_MEMBER,
"--role",
"roles/pubsub.publisher",
"--quiet"
]);
const pushEndpoint = opts.pushEndpoint ? opts.pushEndpoint : await ensureTailscaleEndpoint({
mode: tailscaleMode,
path: tailscalePath,
port: servePort,
target: normalizedTailscaleTarget,
token: pushToken
});
if (!pushEndpoint) throw new Error("push endpoint required (set --push-endpoint)");
await ensureSubscription(projectId, subscription, topicName, pushEndpoint);
await startGmailWatch({
account: opts.account,
label,
topic: topicPath
}, true);
const validated = validateConfigObjectWithPlugins({
...baseConfig,
hooks: {
...baseConfig.hooks,
enabled: true,
path: hooksPath,
token: hookToken,
presets: mergeHookPresets(baseConfig.hooks?.presets, "gmail"),
gmail: {
...baseConfig.hooks?.gmail,
account: opts.account,
label,
topic: topicPath,
subscription,
pushToken,
hookUrl,
includeBody,
maxBytes,
renewEveryMinutes,
serve: {
...baseConfig.hooks?.gmail?.serve,
bind: serveBind,
port: servePort,
path: servePath
},
tailscale: {
...baseConfig.hooks?.gmail?.tailscale,
mode: tailscaleMode,
path: tailscalePath,
target: normalizedTailscaleTarget
}
}
}
});
if (!validated.ok) throw new Error(`Config validation failed: ${validated.issues[0]?.message ?? "invalid"}`);
await writeConfigFile(validated.config);
const summary = {
projectId,
topic: topicPath,
subscription,
pushEndpoint,
hookUrl,
hookToken,
pushToken,
serve: {
bind: serveBind,
port: servePort,
path: servePath
}
};
if (opts.json) {
defaultRuntime.log(JSON.stringify(summary, null, 2));
return;
}
defaultRuntime.log("Gmail hooks configured:");
defaultRuntime.log(`- project: ${projectId}`);
defaultRuntime.log(`- topic: ${topicPath}`);
defaultRuntime.log(`- subscription: ${subscription}`);
defaultRuntime.log(`- push endpoint: ${pushEndpoint}`);
defaultRuntime.log(`- hook url: ${hookUrl}`);
defaultRuntime.log(`- config: ${displayPath(CONFIG_PATH)}`);
defaultRuntime.log(`Next: ${formatCliCommand("openclaw webhooks gmail run")}`);
}
async function runGmailService(opts) {
await ensureDependency("gog", ["gogcli"]);
const resolved = resolveGmailHookRuntimeConfig(loadConfig(), {
account: opts.account,
topic: opts.topic,
subscription: opts.subscription,
label: opts.label,
hookToken: opts.hookToken,
pushToken: opts.pushToken,
hookUrl: opts.hookUrl,
serveBind: opts.bind,
servePort: opts.port,
servePath: opts.path,
includeBody: opts.includeBody,
maxBytes: opts.maxBytes,
renewEveryMinutes: opts.renewEveryMinutes,
tailscaleMode: opts.tailscale,
tailscalePath: opts.tailscalePath,
tailscaleTarget: opts.tailscaleTarget
});
if (!resolved.ok) throw new Error(resolved.error);
const runtimeConfig = resolved.value;
if (runtimeConfig.tailscale.mode !== "off") {
await ensureDependency("tailscale", ["tailscale"]);
await ensureTailscaleEndpoint({
mode: runtimeConfig.tailscale.mode,
path: runtimeConfig.tailscale.path,
port: runtimeConfig.serve.port,
target: runtimeConfig.tailscale.target
});
}
await startGmailWatch(runtimeConfig);
let shuttingDown = false;
let child = spawnGogServe(runtimeConfig);
const renewMs = runtimeConfig.renewEveryMinutes * 6e4;
const renewTimer = setInterval(() => {
startGmailWatch(runtimeConfig);
}, renewMs);
const shutdown = () => {
if (shuttingDown) return;
shuttingDown = true;
clearInterval(renewTimer);
child.kill("SIGTERM");
};
process.on("SIGINT", shutdown);
process.on("SIGTERM", shutdown);
child.on("exit", () => {
if (shuttingDown) return;
defaultRuntime.log("gog watch serve exited; restarting in 2s");
setTimeout(() => {
if (shuttingDown) return;
child = spawnGogServe(runtimeConfig);
}, 2e3);
});
}
function spawnGogServe(cfg) {
const args = buildGogWatchServeArgs(cfg);
defaultRuntime.log(`Starting gog ${args.join(" ")}`);
return spawn("gog", args, { stdio: "inherit" });
}
async function startGmailWatch(cfg, fatal = false) {
const result = await runCommandWithTimeout(["gog", ...buildGogWatchStartArgs(cfg)], { timeoutMs: 12e4 });
if (result.code !== 0) {
const message = result.stderr || result.stdout || "gog watch start failed";
if (fatal) throw new Error(message);
defaultRuntime.error(message);
}
}
//#endregion
//#region src/cli/webhooks-cli.ts
function registerWebhooksCli(program) {
const gmail = program.command("webhooks").description("Webhook helpers and integrations").addHelpText("after", () => `\n${theme.muted("Docs:")} ${formatDocsLink("/cli/webhooks", "docs.openclaw.ai/cli/webhooks")}\n`).command("gmail").description("Gmail Pub/Sub hooks (via gogcli)");
gmail.command("setup").description("Configure Gmail watch + Pub/Sub + OpenClaw hooks").requiredOption("--account <email>", "Gmail account to watch").option("--project <id>", "GCP project id (OAuth client owner)").option("--topic <name>", "Pub/Sub topic name", DEFAULT_GMAIL_TOPIC).option("--subscription <name>", "Pub/Sub subscription name", DEFAULT_GMAIL_SUBSCRIPTION).option("--label <label>", "Gmail label to watch", DEFAULT_GMAIL_LABEL).option("--hook-url <url>", "OpenClaw hook URL").option("--hook-token <token>", "OpenClaw hook token").option("--push-token <token>", "Push token for gog watch serve").option("--bind <host>", "gog watch serve bind host", DEFAULT_GMAIL_SERVE_BIND).option("--port <port>", "gog watch serve port", String(DEFAULT_GMAIL_SERVE_PORT)).option("--path <path>", "gog watch serve path", DEFAULT_GMAIL_SERVE_PATH).option("--include-body", "Include email body snippets", true).option("--max-bytes <n>", "Max bytes for body snippets", String(DEFAULT_GMAIL_MAX_BYTES)).option("--renew-minutes <n>", "Renew watch every N minutes", String(DEFAULT_GMAIL_RENEW_MINUTES)).option("--tailscale <mode>", "Expose push endpoint via tailscale (funnel|serve|off)", "funnel").option("--tailscale-path <path>", "Path for tailscale serve/funnel").option("--tailscale-target <target>", "Tailscale serve/funnel target (port, host:port, or URL)").option("--push-endpoint <url>", "Explicit Pub/Sub push endpoint").option("--json", "Output JSON summary", false).action(async (opts) => {
try {
await runGmailSetup(parseGmailSetupOptions(opts));
} catch (err) {
defaultRuntime.error(danger(String(err)));
defaultRuntime.exit(1);
}
});
gmail.command("run").description("Run gog watch serve + auto-renew loop").option("--account <email>", "Gmail account to watch").option("--topic <topic>", "Pub/Sub topic path (projects/.../topics/..)").option("--subscription <name>", "Pub/Sub subscription name").option("--label <label>", "Gmail label to watch").option("--hook-url <url>", "OpenClaw hook URL").option("--hook-token <token>", "OpenClaw hook token").option("--push-token <token>", "Push token for gog watch serve").option("--bind <host>", "gog watch serve bind host").option("--port <port>", "gog watch serve port").option("--path <path>", "gog watch serve path").option("--include-body", "Include email body snippets").option("--max-bytes <n>", "Max bytes for body snippets").option("--renew-minutes <n>", "Renew watch every N minutes").option("--tailscale <mode>", "Expose push endpoint via tailscale (funnel|serve|off)").option("--tailscale-path <path>", "Path for tailscale serve/funnel").option("--tailscale-target <target>", "Tailscale serve/funnel target (port, host:port, or URL)").action(async (opts) => {
try {
await runGmailService(parseGmailRunOptions(opts));
} catch (err) {
defaultRuntime.error(danger(String(err)));
defaultRuntime.exit(1);
}
});
}
function parseGmailSetupOptions(raw) {
const accountRaw = raw.account;
const account = typeof accountRaw === "string" ? accountRaw.trim() : "";
if (!account) throw new Error("--account is required");
return {
account,
project: stringOption(raw.project),
topic: stringOption(raw.topic),
subscription: stringOption(raw.subscription),
label: stringOption(raw.label),
hookUrl: stringOption(raw.hookUrl),
hookToken: stringOption(raw.hookToken),
pushToken: stringOption(raw.pushToken),
bind: stringOption(raw.bind),
port: numberOption(raw.port),
path: stringOption(raw.path),
includeBody: booleanOption(raw.includeBody),
maxBytes: numberOption(raw.maxBytes),
renewEveryMinutes: numberOption(raw.renewMinutes),
tailscale: stringOption(raw.tailscale),
tailscalePath: stringOption(raw.tailscalePath),
tailscaleTarget: stringOption(raw.tailscaleTarget),
pushEndpoint: stringOption(raw.pushEndpoint),
json: Boolean(raw.json)
};
}
function parseGmailRunOptions(raw) {
return {
account: stringOption(raw.account),
topic: stringOption(raw.topic),
subscription: stringOption(raw.subscription),
label: stringOption(raw.label),
hookUrl: stringOption(raw.hookUrl),
hookToken: stringOption(raw.hookToken),
pushToken: stringOption(raw.pushToken),
bind: stringOption(raw.bind),
port: numberOption(raw.port),
path: stringOption(raw.path),
includeBody: booleanOption(raw.includeBody),
maxBytes: numberOption(raw.maxBytes),
renewEveryMinutes: numberOption(raw.renewMinutes),
tailscale: stringOption(raw.tailscale),
tailscalePath: stringOption(raw.tailscalePath),
tailscaleTarget: stringOption(raw.tailscaleTarget)
};
}
function stringOption(value) {
if (typeof value !== "string") return;
const trimmed = value.trim();
return trimmed ? trimmed : void 0;
}
function numberOption(value) {
if (value === void 0 || value === null) return;
const n = typeof value === "number" ? value : Number(value);
if (!Number.isFinite(n) || n <= 0) return;
return Math.floor(n);
}
function booleanOption(value) {
if (value === void 0 || value === null) return;
return Boolean(value);
}
//#endregion
export { registerWebhooksCli };