← back to Ventura Claw
server/connectors/gmail.js
48 lines
// Gmail — REAL via Steve's "george" gmail-agent proxy on Mac (per memory).
// george endpoint: http://100.107.67.67:9850 (tailnet) OR http://127.0.0.1:9850 (local Mac).
// Field stored is just the bearer/admin password for george (admin:DWSecure2024! by default).
function url(c) { return c?.GEORGE_URL || process.env.GEORGE_URL || "http://127.0.0.1:9850"; }
function basic(c) {
// Accept either pre-encoded GEORGE_BASIC_AUTH ("user:pass" plain or "Basic ..." or already base64) or split user/pass
const combo = c?.GEORGE_BASIC_AUTH || process.env.GEORGE_BASIC_AUTH;
if (combo) {
if (combo.startsWith("Basic ")) return combo;
if (/^[A-Za-z0-9+/=]+$/.test(combo) && combo.length > 12 && !combo.includes(":")) return "Basic " + combo;
return "Basic " + Buffer.from(combo).toString("base64");
}
const u = c?.GEORGE_USER || process.env.GEORGE_USER || "admin";
const p = c?.GEORGE_PASS || process.env.GEORGE_PASS;
if (!p) throw new Error("GEORGE_BASIC_AUTH or GEORGE_PASS not set");
return "Basic " + Buffer.from(`${u}:${p}`).toString("base64");
}
async function call(method, path, body, c) {
const r = await fetch(`${url(c)}${path}`, {
method,
headers: { "Authorization": basic(c), "Content-Type": "application/json" },
body: body ? JSON.stringify(body) : undefined,
signal: AbortSignal.timeout(10000)
});
const d = r.status === 204 ? {} : await r.json().catch(() => ({}));
if (!r.ok) throw new Error(`gmail(george) ${path} ${r.status}: ${d.error || JSON.stringify(d).slice(0,200)}`);
return d;
}
module.exports = {
meta: { id: "gmail", name: "Gmail", category: "email", docsUrl: "http://127.0.0.1:9850", auth: "basic", realImpl: true },
fields: [
{ key: "GEORGE_URL", label: "George Endpoint", type: "text", required: false, hint: "default http://127.0.0.1:9850" },
{ key: "GEORGE_BASIC_AUTH", label: "Basic auth (user:pass)", type: "password", required: true, hint: "e.g. admin:DWSecure2024! — plain or base64" }
],
configured(c) { return !!(c?.GEORGE_BASIC_AUTH || c?.GEORGE_PASS || process.env.GEORGE_BASIC_AUTH || process.env.GEORGE_PASS); },
async health(c) {
if (!this.configured(c)) return { ok: false, reason: "no_token" };
try { const d = await call("GET", "/api/health", null, c); return { ok: true, status: d.status || "ok", endpoint: url(c) }; }
catch (e) { return { ok: false, reason: e.message }; }
},
actions: {
async "message.send"({ to, subject, body, account }, c) {
if (!to || !subject || !body) throw new Error("to, subject, body required");
return call("POST", "/api/send", { to, subject, body, account }, c);
}
}
};