← back to Ventura Claw
server/connectors/twilio.js
54 lines
// Twilio — REAL. Account SID + Auth Token (Basic auth).
function sid(c) {
const s = c?.TWILIO_ACCOUNT_SID || process.env.TWILIO_ACCOUNT_SID;
if (!s) throw new Error("TWILIO_ACCOUNT_SID not set");
return s;
}
function authToken(c) {
const t = c?.TWILIO_AUTH_TOKEN || process.env.TWILIO_AUTH_TOKEN;
if (!t) throw new Error("TWILIO_AUTH_TOKEN not set");
return t;
}
async function call(method, path, params, c) {
const url = `https://api.twilio.com/2010-04-01/Accounts/${sid(c)}${path}`;
const auth = Buffer.from(`${sid(c)}:${authToken(c)}`).toString("base64");
const opts = { method, headers: { "Authorization": `Basic ${auth}` } };
let finalUrl = url;
if (method === "GET" && params) finalUrl += "?" + new URLSearchParams(params).toString();
else if (params) {
opts.headers["Content-Type"] = "application/x-www-form-urlencoded";
opts.body = new URLSearchParams(params).toString();
}
const r = await fetch(finalUrl, opts);
const d = await r.json();
if (!r.ok) throw new Error(`twilio ${path} ${r.status}: ${d.message || JSON.stringify(d).slice(0,200)}`);
return d;
}
module.exports = {
meta: { id: "twilio", name: "Twilio", category: "comms", docsUrl: "https://www.twilio.com/docs/usage/api", auth: "basic", realImpl: true },
fields: [
{ key: "TWILIO_ACCOUNT_SID", label: "Account SID", type: "text", required: true, hint: "ACxxxxx — console.twilio.com" },
{ key: "TWILIO_AUTH_TOKEN", label: "Auth Token", type: "password", required: true, hint: "next to Account SID" },
{ key: "TWILIO_FROM_NUMBER", label: "From Number", type: "text", required: false, hint: "+15551234567 — your Twilio number (optional default)" }
],
configured(c) { return !!((c?.TWILIO_ACCOUNT_SID || process.env.TWILIO_ACCOUNT_SID) && (c?.TWILIO_AUTH_TOKEN || process.env.TWILIO_AUTH_TOKEN)); },
async health(c) {
if (!this.configured(c)) return { ok: false, reason: "no_token" };
try { const d = await call("GET", ".json", null, c); return { ok: true, friendly_name: d.friendly_name, status: d.status }; }
catch (e) { return { ok: false, reason: e.message }; }
},
actions: {
async "sms.send"({ to, body, from }, c) {
const f = from || c?.TWILIO_FROM_NUMBER || process.env.TWILIO_FROM_NUMBER;
if (!to || !body || !f) throw new Error("to, body, from required");
return call("POST", "/Messages.json", { To: to, From: f, Body: body }, c);
},
async "messages.list"({ limit }, c) {
return call("GET", "/Messages.json", { PageSize: String(limit || 20) }, c);
},
async "phone_numbers.list"(_, c) {
return call("GET", "/IncomingPhoneNumbers.json", null, c);
}
}
};