runline 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.pi/extensions/runline-context/index.ts +135 -0
- package/.pi/extensions/runline-context/package.json +17 -0
- package/README.md +273 -0
- package/dist/commands/actions.d.ts +3 -0
- package/dist/commands/actions.js +43 -0
- package/dist/commands/connection.d.ts +11 -0
- package/dist/commands/connection.js +56 -0
- package/dist/commands/exec.d.ts +5 -0
- package/dist/commands/exec.js +46 -0
- package/dist/commands/init.d.ts +4 -0
- package/dist/commands/init.js +26 -0
- package/dist/commands/plugin.d.ts +10 -0
- package/dist/commands/plugin.js +57 -0
- package/dist/config/index.d.ts +3 -0
- package/dist/config/index.js +2 -0
- package/dist/config/loader.d.ts +11 -0
- package/dist/config/loader.js +82 -0
- package/dist/config/types.d.ts +9 -0
- package/dist/config/types.js +5 -0
- package/dist/core/engine.d.ts +21 -0
- package/dist/core/engine.js +280 -0
- package/dist/index.d.ts +16 -0
- package/dist/index.js +9 -0
- package/dist/main.d.ts +2 -0
- package/dist/main.js +127 -0
- package/dist/plugin/api.d.ts +32 -0
- package/dist/plugin/api.js +68 -0
- package/dist/plugin/installer.d.ts +27 -0
- package/dist/plugin/installer.js +181 -0
- package/dist/plugin/loader.d.ts +13 -0
- package/dist/plugin/loader.js +164 -0
- package/dist/plugin/registry.d.ts +18 -0
- package/dist/plugin/registry.js +43 -0
- package/dist/plugin/types.d.ts +40 -0
- package/dist/plugin/types.js +1 -0
- package/dist/plugins/actionNetwork/src/index.js +353 -0
- package/dist/plugins/activeCampaign/src/index.js +711 -0
- package/dist/plugins/adalo/src/index.js +131 -0
- package/dist/plugins/affinity/src/index.js +279 -0
- package/dist/plugins/agileCrm/src/index.js +415 -0
- package/dist/plugins/airtable/src/index.js +280 -0
- package/dist/plugins/airtop/src/index.js +527 -0
- package/dist/plugins/apiTemplateIo/src/index.js +86 -0
- package/dist/plugins/asana/src/index.js +413 -0
- package/dist/plugins/autopilot/src/index.js +203 -0
- package/dist/plugins/bambooHr/src/index.js +252 -0
- package/dist/plugins/bannerbear/src/index.js +100 -0
- package/dist/plugins/baserow/src/index.js +180 -0
- package/dist/plugins/beeminder/src/index.js +298 -0
- package/dist/plugins/bitly/src/index.js +107 -0
- package/dist/plugins/bitwarden/src/index.js +383 -0
- package/dist/plugins/box/src/index.js +300 -0
- package/dist/plugins/brandfetch/src/index.js +80 -0
- package/dist/plugins/brevo/src/index.js +305 -0
- package/dist/plugins/bubble/src/index.js +181 -0
- package/dist/plugins/chargebee/src/index.js +126 -0
- package/dist/plugins/circleci/src/index.js +111 -0
- package/dist/plugins/ciscoWebex/src/index.js +245 -0
- package/dist/plugins/clearbit/src/index.js +103 -0
- package/dist/plugins/clickup/src/index.js +1043 -0
- package/dist/plugins/clockify/src/index.js +443 -0
- package/dist/plugins/cloudflare/src/index.js +93 -0
- package/dist/plugins/cockpit/src/index.js +131 -0
- package/dist/plugins/coda/src/index.js +327 -0
- package/dist/plugins/coingecko/src/index.js +244 -0
- package/dist/plugins/contentful/src/index.js +146 -0
- package/dist/plugins/convertkit/src/index.js +270 -0
- package/dist/plugins/copper/src/index.js +140 -0
- package/dist/plugins/cortex/src/index.js +147 -0
- package/dist/plugins/currents/src/index.js +405 -0
- package/dist/plugins/customerIo/src/index.js +184 -0
- package/dist/plugins/databricks/src/index.js +342 -0
- package/dist/plugins/deepl/src/index.js +87 -0
- package/dist/plugins/demio/src/index.js +111 -0
- package/dist/plugins/dhl/src/index.js +40 -0
- package/dist/plugins/discord/src/index.js +275 -0
- package/dist/plugins/discourse/src/index.js +273 -0
- package/dist/plugins/disqus/src/index.js +145 -0
- package/dist/plugins/docker/src/index.js +76 -0
- package/dist/plugins/drift/src/index.js +89 -0
- package/dist/plugins/dropbox/src/index.js +159 -0
- package/dist/plugins/dropcontact/src/index.js +59 -0
- package/dist/plugins/egoi/src/index.js +151 -0
- package/dist/plugins/elasticsearch/src/index.js +157 -0
- package/dist/plugins/emelia/src/index.js +174 -0
- package/dist/plugins/erpnext/src/index.js +121 -0
- package/dist/plugins/facebookGraph/src/index.js +57 -0
- package/dist/plugins/freshdesk/src/index.js +320 -0
- package/dist/plugins/freshservice/src/index.js +146 -0
- package/dist/plugins/freshworksCrm/src/index.js +149 -0
- package/dist/plugins/getresponse/src/index.js +140 -0
- package/dist/plugins/ghost/src/index.js +192 -0
- package/dist/plugins/github/src/index.js +630 -0
- package/dist/plugins/gitlab/src/index.js +358 -0
- package/dist/plugins/gong/src/index.js +126 -0
- package/dist/plugins/gotify/src/index.js +77 -0
- package/dist/plugins/gotowebinar/src/index.js +316 -0
- package/dist/plugins/grafana/src/index.js +250 -0
- package/dist/plugins/graphql/src/index.js +78 -0
- package/dist/plugins/grist/src/index.js +106 -0
- package/dist/plugins/hackernews/src/index.js +89 -0
- package/dist/plugins/halopsa/src/index.js +79 -0
- package/dist/plugins/harvest/src/index.js +163 -0
- package/dist/plugins/helpscout/src/index.js +176 -0
- package/dist/plugins/highlevel/src/index.js +172 -0
- package/dist/plugins/homeAssistant/src/index.js +148 -0
- package/dist/plugins/hubspot/src/index.js +176 -0
- package/dist/plugins/humanticAi/src/index.js +60 -0
- package/dist/plugins/hunter/src/index.js +59 -0
- package/dist/plugins/intercom/src/index.js +156 -0
- package/dist/plugins/iterable/src/index.js +139 -0
- package/dist/plugins/jenkins/src/index.js +132 -0
- package/dist/plugins/jira/src/index.js +229 -0
- package/dist/plugins/keap/src/index.js +502 -0
- package/dist/plugins/kobotoolbox/src/index.js +281 -0
- package/dist/plugins/lemlist/src/index.js +231 -0
- package/dist/plugins/linear/src/index.js +133 -0
- package/dist/plugins/lingvanex/src/index.js +31 -0
- package/dist/plugins/linkedin/src/index.js +80 -0
- package/dist/plugins/lonescale/src/index.js +119 -0
- package/dist/plugins/magento/src/index.js +300 -0
- package/dist/plugins/mailcheck/src/index.js +27 -0
- package/dist/plugins/mailchimp/src/index.js +321 -0
- package/dist/plugins/mailerlite/src/index.js +123 -0
- package/dist/plugins/mailgun/src/index.js +48 -0
- package/dist/plugins/mailjet/src/index.js +155 -0
- package/dist/plugins/mandrill/src/index.js +145 -0
- package/dist/plugins/marketstack/src/index.js +97 -0
- package/dist/plugins/matrix/src/index.js +194 -0
- package/dist/plugins/mattermost/src/index.js +331 -0
- package/dist/plugins/mautic/src/index.js +311 -0
- package/dist/plugins/medium/src/index.js +77 -0
- package/dist/plugins/messagebird/src/index.js +57 -0
- package/dist/plugins/metabase/src/index.js +130 -0
- package/dist/plugins/misp/src/index.js +476 -0
- package/dist/plugins/mocean/src/index.js +67 -0
- package/dist/plugins/monday/src/index.js +231 -0
- package/dist/plugins/monicaCrm/src/index.js +52 -0
- package/dist/plugins/msg91/src/index.js +31 -0
- package/dist/plugins/nasa/src/index.js +146 -0
- package/dist/plugins/netlify/src/index.js +151 -0
- package/dist/plugins/netscalerAdc/src/index.js +131 -0
- package/dist/plugins/nextcloud/src/index.js +263 -0
- package/dist/plugins/nocodb/src/index.js +130 -0
- package/dist/plugins/notion/src/index.js +112 -0
- package/dist/plugins/npm/src/index.js +104 -0
- package/dist/plugins/odoo/src/index.js +157 -0
- package/dist/plugins/okta/src/index.js +141 -0
- package/dist/plugins/oneSimpleApi/src/index.js +155 -0
- package/dist/plugins/onfleet/src/index.js +254 -0
- package/dist/plugins/openThesaurus/src/index.js +32 -0
- package/dist/plugins/openweathermap/src/index.js +60 -0
- package/dist/plugins/oura/src/index.js +62 -0
- package/dist/plugins/paddle/src/index.js +247 -0
- package/dist/plugins/pagerduty/src/index.js +201 -0
- package/dist/plugins/paypal/src/index.js +106 -0
- package/dist/plugins/peekalink/src/index.js +35 -0
- package/dist/plugins/phantombuster/src/index.js +94 -0
- package/dist/plugins/philipsHue/src/index.js +98 -0
- package/dist/plugins/pipedrive/src/index.js +169 -0
- package/dist/plugins/plivo/src/index.js +66 -0
- package/dist/plugins/postbin/src/index.js +93 -0
- package/dist/plugins/posthog/src/index.js +113 -0
- package/dist/plugins/profitwell/src/index.js +50 -0
- package/dist/plugins/pushbullet/src/index.js +102 -0
- package/dist/plugins/pushcut/src/index.js +39 -0
- package/dist/plugins/pushover/src/index.js +65 -0
- package/dist/plugins/quickbase/src/index.js +153 -0
- package/dist/plugins/quickbooks/src/index.js +73 -0
- package/dist/plugins/quickchart/src/index.js +36 -0
- package/dist/plugins/raindrop/src/index.js +209 -0
- package/dist/plugins/reddit/src/index.js +185 -0
- package/dist/plugins/rocketchat/src/index.js +53 -0
- package/dist/plugins/rundeck/src/index.js +62 -0
- package/dist/plugins/salesforce/src/index.js +94 -0
- package/dist/plugins/salesmate/src/index.js +83 -0
- package/dist/plugins/securityScorecard/src/index.js +200 -0
- package/dist/plugins/segment/src/index.js +125 -0
- package/dist/plugins/sendgrid/src/index.js +187 -0
- package/dist/plugins/sendy/src/index.js +138 -0
- package/dist/plugins/sentry/src/index.js +233 -0
- package/dist/plugins/servicenow/src/index.js +108 -0
- package/dist/plugins/shopify/src/index.js +222 -0
- package/dist/plugins/signl4/src/index.js +61 -0
- package/dist/plugins/slack/src/index.js +236 -0
- package/dist/plugins/sms77/src/index.js +63 -0
- package/dist/plugins/splunk/src/index.js +207 -0
- package/dist/plugins/spotify/src/index.js +188 -0
- package/dist/plugins/stackby/src/index.js +82 -0
- package/dist/plugins/storyblok/src/index.js +141 -0
- package/dist/plugins/strapi/src/index.js +152 -0
- package/dist/plugins/strava/src/index.js +137 -0
- package/dist/plugins/stripe/src/index.js +222 -0
- package/dist/plugins/supabase/src/index.js +121 -0
- package/dist/plugins/syncromsp/src/index.js +255 -0
- package/dist/plugins/tapfiliate/src/index.js +125 -0
- package/dist/plugins/telegram/src/index.js +233 -0
- package/dist/plugins/thehive/src/index.js +142 -0
- package/dist/plugins/thehiveProject/src/index.js +194 -0
- package/dist/plugins/todoist/src/index.js +244 -0
- package/dist/plugins/travisci/src/index.js +71 -0
- package/dist/plugins/trello/src/index.js +341 -0
- package/dist/plugins/twake/src/index.js +40 -0
- package/dist/plugins/twilio/src/index.js +75 -0
- package/dist/plugins/twist/src/index.js +90 -0
- package/dist/plugins/twitter/src/index.js +123 -0
- package/dist/plugins/unleashedSoftware/src/index.js +84 -0
- package/dist/plugins/uplead/src/index.js +59 -0
- package/dist/plugins/uproc/src/index.js +34 -0
- package/dist/plugins/uptimerobot/src/index.js +264 -0
- package/dist/plugins/urlscanio/src/index.js +64 -0
- package/dist/plugins/vero/src/index.js +80 -0
- package/dist/plugins/vonage/src/index.js +42 -0
- package/dist/plugins/wekan/src/index.js +91 -0
- package/dist/plugins/woocommerce/src/index.js +92 -0
- package/dist/plugins/wordpress/src/index.js +121 -0
- package/dist/plugins/xero/src/index.js +136 -0
- package/dist/plugins/yourls/src/index.js +56 -0
- package/dist/plugins/zammad/src/index.js +91 -0
- package/dist/plugins/zendesk/src/index.js +137 -0
- package/dist/plugins/zoho/src/index.js +85 -0
- package/dist/plugins/zoom/src/index.js +122 -0
- package/dist/plugins/zulip/src/index.js +170 -0
- package/dist/sdk.d.ts +38 -0
- package/dist/sdk.js +105 -0
- package/dist/utils/cli.d.ts +13 -0
- package/dist/utils/cli.js +32 -0
- package/dist/utils/output.d.ts +4 -0
- package/dist/utils/output.js +13 -0
- package/package.json +57 -0
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
const BASE = "https://api.securityscorecard.io";
|
|
2
|
+
async function apiRequest(token, method, endpoint, body, qs) {
|
|
3
|
+
const url = new URL(`${BASE}/${endpoint}`);
|
|
4
|
+
if (qs) {
|
|
5
|
+
for (const [k, v] of Object.entries(qs)) {
|
|
6
|
+
if (v !== undefined && v !== null)
|
|
7
|
+
url.searchParams.set(k, String(v));
|
|
8
|
+
}
|
|
9
|
+
}
|
|
10
|
+
const init = { method, headers: { Authorization: `Token ${token}`, "Content-Type": "application/json" } };
|
|
11
|
+
if (body && Object.keys(body).length > 0)
|
|
12
|
+
init.body = JSON.stringify(body);
|
|
13
|
+
const res = await fetch(url.toString(), init);
|
|
14
|
+
if (!res.ok)
|
|
15
|
+
throw new Error(`SecurityScorecard error ${res.status}: ${await res.text()}`);
|
|
16
|
+
const text = await res.text();
|
|
17
|
+
return text ? JSON.parse(text) : {};
|
|
18
|
+
}
|
|
19
|
+
export default function securityScorecard(rl) {
|
|
20
|
+
rl.setName("securityScorecard");
|
|
21
|
+
rl.setVersion("0.1.0");
|
|
22
|
+
rl.setConnectionSchema({
|
|
23
|
+
apiKey: { type: "string", required: true, description: "SecurityScorecard API key", env: "SECURITYSCORECARD_API_KEY" },
|
|
24
|
+
});
|
|
25
|
+
const key = (ctx) => ctx.connection.config.apiKey;
|
|
26
|
+
// ── Company ─────────────────────────────────────────
|
|
27
|
+
rl.registerAction("company.getScorecard", {
|
|
28
|
+
description: "Get a company's scorecard by domain",
|
|
29
|
+
inputSchema: { domain: { type: "string", required: true } },
|
|
30
|
+
async execute(input, ctx) {
|
|
31
|
+
return apiRequest(key(ctx), "GET", `companies/${input.domain}`);
|
|
32
|
+
},
|
|
33
|
+
});
|
|
34
|
+
rl.registerAction("company.getFactors", {
|
|
35
|
+
description: "Get factor scores for a company",
|
|
36
|
+
inputSchema: { domain: { type: "string", required: true }, limit: { type: "number", required: false } },
|
|
37
|
+
async execute(input, ctx) {
|
|
38
|
+
const p = input;
|
|
39
|
+
const data = (await apiRequest(key(ctx), "GET", `companies/${p.domain}/factors`));
|
|
40
|
+
let entries = (data.entries ?? []);
|
|
41
|
+
if (p.limit)
|
|
42
|
+
entries = entries.slice(0, p.limit);
|
|
43
|
+
return entries;
|
|
44
|
+
},
|
|
45
|
+
});
|
|
46
|
+
rl.registerAction("company.getHistoricalScore", {
|
|
47
|
+
description: "Get historical score data for a company",
|
|
48
|
+
inputSchema: {
|
|
49
|
+
domain: { type: "string", required: true },
|
|
50
|
+
from: { type: "string", required: false, description: "Start date YYYY-MM-DD" },
|
|
51
|
+
to: { type: "string", required: false, description: "End date YYYY-MM-DD" },
|
|
52
|
+
limit: { type: "number", required: false },
|
|
53
|
+
},
|
|
54
|
+
async execute(input, ctx) {
|
|
55
|
+
const p = (input ?? {});
|
|
56
|
+
const qs = {};
|
|
57
|
+
if (p.from)
|
|
58
|
+
qs.from = p.from;
|
|
59
|
+
if (p.to)
|
|
60
|
+
qs.to = p.to;
|
|
61
|
+
const data = (await apiRequest(key(ctx), "GET", `companies/${p.domain}/history/factors/score`, undefined, qs));
|
|
62
|
+
let entries = (data.entries ?? []);
|
|
63
|
+
if (p.limit)
|
|
64
|
+
entries = entries.slice(0, p.limit);
|
|
65
|
+
return entries;
|
|
66
|
+
},
|
|
67
|
+
});
|
|
68
|
+
rl.registerAction("company.getScorePlan", {
|
|
69
|
+
description: "Get score improvement plan for a target score",
|
|
70
|
+
inputSchema: {
|
|
71
|
+
domain: { type: "string", required: true },
|
|
72
|
+
targetScore: { type: "number", required: true },
|
|
73
|
+
},
|
|
74
|
+
async execute(input, ctx) {
|
|
75
|
+
const p = input;
|
|
76
|
+
const data = (await apiRequest(key(ctx), "GET", `companies/${p.domain}/score-plans/by-target/${p.targetScore}`));
|
|
77
|
+
return data.entries;
|
|
78
|
+
},
|
|
79
|
+
});
|
|
80
|
+
// ── Industry ────────────────────────────────────────
|
|
81
|
+
rl.registerAction("industry.getScore", {
|
|
82
|
+
description: "Get an industry's average score",
|
|
83
|
+
inputSchema: { industry: { type: "string", required: true } },
|
|
84
|
+
async execute(input, ctx) {
|
|
85
|
+
return apiRequest(key(ctx), "GET", `industries/${input.industry}/score`);
|
|
86
|
+
},
|
|
87
|
+
});
|
|
88
|
+
// ── Portfolio ───────────────────────────────────────
|
|
89
|
+
rl.registerAction("portfolio.create", {
|
|
90
|
+
description: "Create a portfolio",
|
|
91
|
+
inputSchema: {
|
|
92
|
+
name: { type: "string", required: true },
|
|
93
|
+
description: { type: "string", required: true },
|
|
94
|
+
privacy: { type: "string", required: true, description: "private or shared" },
|
|
95
|
+
},
|
|
96
|
+
async execute(input, ctx) {
|
|
97
|
+
const p = input;
|
|
98
|
+
return apiRequest(key(ctx), "POST", "portfolios", { name: p.name, description: p.description, privacy: p.privacy });
|
|
99
|
+
},
|
|
100
|
+
});
|
|
101
|
+
rl.registerAction("portfolio.list", {
|
|
102
|
+
description: "List all portfolios",
|
|
103
|
+
inputSchema: { limit: { type: "number", required: false } },
|
|
104
|
+
async execute(input, ctx) {
|
|
105
|
+
const data = (await apiRequest(key(ctx), "GET", "portfolios"));
|
|
106
|
+
let entries = (data.entries ?? []);
|
|
107
|
+
if (input?.limit)
|
|
108
|
+
entries = entries.slice(0, input.limit);
|
|
109
|
+
return entries;
|
|
110
|
+
},
|
|
111
|
+
});
|
|
112
|
+
rl.registerAction("portfolio.delete", {
|
|
113
|
+
description: "Delete a portfolio",
|
|
114
|
+
inputSchema: { portfolioId: { type: "string", required: true } },
|
|
115
|
+
async execute(input, ctx) {
|
|
116
|
+
await apiRequest(key(ctx), "DELETE", `portfolios/${input.portfolioId}`);
|
|
117
|
+
return { success: true };
|
|
118
|
+
},
|
|
119
|
+
});
|
|
120
|
+
rl.registerAction("portfolioCompany.add", {
|
|
121
|
+
description: "Add a company to a portfolio",
|
|
122
|
+
inputSchema: { portfolioId: { type: "string", required: true }, domain: { type: "string", required: true } },
|
|
123
|
+
async execute(input, ctx) {
|
|
124
|
+
const p = input;
|
|
125
|
+
return apiRequest(key(ctx), "PUT", `portfolios/${p.portfolioId}/companies/${p.domain}`);
|
|
126
|
+
},
|
|
127
|
+
});
|
|
128
|
+
rl.registerAction("portfolioCompany.remove", {
|
|
129
|
+
description: "Remove a company from a portfolio",
|
|
130
|
+
inputSchema: { portfolioId: { type: "string", required: true }, domain: { type: "string", required: true } },
|
|
131
|
+
async execute(input, ctx) {
|
|
132
|
+
const p = input;
|
|
133
|
+
await apiRequest(key(ctx), "DELETE", `portfolios/${p.portfolioId}/companies/${p.domain}`);
|
|
134
|
+
return { success: true };
|
|
135
|
+
},
|
|
136
|
+
});
|
|
137
|
+
rl.registerAction("portfolioCompany.list", {
|
|
138
|
+
description: "List companies in a portfolio",
|
|
139
|
+
inputSchema: { portfolioId: { type: "string", required: true }, limit: { type: "number", required: false } },
|
|
140
|
+
async execute(input, ctx) {
|
|
141
|
+
const p = input;
|
|
142
|
+
const data = (await apiRequest(key(ctx), "GET", `portfolios/${p.portfolioId}/companies`));
|
|
143
|
+
let entries = (data.entries ?? []);
|
|
144
|
+
if (p.limit)
|
|
145
|
+
entries = entries.slice(0, p.limit);
|
|
146
|
+
return entries;
|
|
147
|
+
},
|
|
148
|
+
});
|
|
149
|
+
// ── Invite ──────────────────────────────────────────
|
|
150
|
+
rl.registerAction("invite.create", {
|
|
151
|
+
description: "Send an invitation",
|
|
152
|
+
inputSchema: {
|
|
153
|
+
email: { type: "string", required: true },
|
|
154
|
+
firstName: { type: "string", required: true },
|
|
155
|
+
lastName: { type: "string", required: true },
|
|
156
|
+
message: { type: "string", required: true },
|
|
157
|
+
},
|
|
158
|
+
async execute(input, ctx) {
|
|
159
|
+
const p = input;
|
|
160
|
+
return apiRequest(key(ctx), "POST", "invitations", {
|
|
161
|
+
email: p.email, first_name: p.firstName, last_name: p.lastName, message: p.message,
|
|
162
|
+
});
|
|
163
|
+
},
|
|
164
|
+
});
|
|
165
|
+
// ── Report ──────────────────────────────────────────
|
|
166
|
+
rl.registerAction("report.generate", {
|
|
167
|
+
description: "Generate a report",
|
|
168
|
+
inputSchema: {
|
|
169
|
+
reportType: { type: "string", required: true, description: "Report type: detailed, summary, issues, portfolio, events-json, full-scorecard-json, scorecard-footprint" },
|
|
170
|
+
scorecardIdentifier: { type: "string", required: false, description: "Company domain (for non-portfolio reports)" },
|
|
171
|
+
portfolioId: { type: "string", required: false, description: "Portfolio ID (for portfolio reports)" },
|
|
172
|
+
format: { type: "string", required: false, description: "pdf or csv (for issues/portfolio)" },
|
|
173
|
+
branding: { type: "string", required: false },
|
|
174
|
+
},
|
|
175
|
+
async execute(input, ctx) {
|
|
176
|
+
const p = input;
|
|
177
|
+
const body = {};
|
|
178
|
+
if (p.reportType !== "portfolio")
|
|
179
|
+
body.scorecard_identifier = p.scorecardIdentifier;
|
|
180
|
+
else
|
|
181
|
+
body.portfolio_id = p.portfolioId;
|
|
182
|
+
if (p.format)
|
|
183
|
+
body.format = p.format;
|
|
184
|
+
if (p.branding)
|
|
185
|
+
body.branding = p.branding;
|
|
186
|
+
return apiRequest(key(ctx), "POST", `reports/${p.reportType}`, body);
|
|
187
|
+
},
|
|
188
|
+
});
|
|
189
|
+
rl.registerAction("report.list", {
|
|
190
|
+
description: "List recent reports",
|
|
191
|
+
inputSchema: { limit: { type: "number", required: false } },
|
|
192
|
+
async execute(input, ctx) {
|
|
193
|
+
const data = (await apiRequest(key(ctx), "GET", "reports/recent"));
|
|
194
|
+
let entries = (data.entries ?? []);
|
|
195
|
+
if (input?.limit)
|
|
196
|
+
entries = entries.slice(0, input.limit);
|
|
197
|
+
return entries;
|
|
198
|
+
},
|
|
199
|
+
});
|
|
200
|
+
}
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
const BASE = "https://api.segment.io/v1";
|
|
2
|
+
async function apiRequest(writeKey, endpoint, body) {
|
|
3
|
+
const res = await fetch(`${BASE}${endpoint}`, {
|
|
4
|
+
method: "POST",
|
|
5
|
+
headers: {
|
|
6
|
+
Authorization: "Basic " + btoa(`${writeKey}:`),
|
|
7
|
+
"Content-Type": "application/json",
|
|
8
|
+
},
|
|
9
|
+
body: JSON.stringify(body),
|
|
10
|
+
});
|
|
11
|
+
if (!res.ok)
|
|
12
|
+
throw new Error(`Segment error ${res.status}: ${await res.text()}`);
|
|
13
|
+
return res.json();
|
|
14
|
+
}
|
|
15
|
+
export default function segment(rl) {
|
|
16
|
+
rl.setName("segment");
|
|
17
|
+
rl.setVersion("0.1.0");
|
|
18
|
+
rl.setConnectionSchema({
|
|
19
|
+
writeKey: { type: "string", required: true, description: "Segment source write key", env: "SEGMENT_WRITE_KEY" },
|
|
20
|
+
});
|
|
21
|
+
const key = (ctx) => ctx.connection.config.writeKey;
|
|
22
|
+
rl.registerAction("identify.create", {
|
|
23
|
+
description: "Identify a user (tie user to traits)",
|
|
24
|
+
inputSchema: {
|
|
25
|
+
userId: { type: "string", required: false, description: "User ID (or anonymousId will be generated)" },
|
|
26
|
+
anonymousId: { type: "string", required: false },
|
|
27
|
+
traits: { type: "object", required: false, description: "User traits key-value pairs" },
|
|
28
|
+
context: { type: "object", required: false },
|
|
29
|
+
integrations: { type: "object", required: false },
|
|
30
|
+
},
|
|
31
|
+
async execute(input, ctx) {
|
|
32
|
+
const p = (input ?? {});
|
|
33
|
+
const body = {};
|
|
34
|
+
if (p.userId)
|
|
35
|
+
body.userId = p.userId;
|
|
36
|
+
else
|
|
37
|
+
body.anonymousId = p.anonymousId ?? crypto.randomUUID();
|
|
38
|
+
if (p.traits)
|
|
39
|
+
body.traits = p.traits;
|
|
40
|
+
if (p.context)
|
|
41
|
+
body.context = p.context;
|
|
42
|
+
if (p.integrations)
|
|
43
|
+
body.integrations = p.integrations;
|
|
44
|
+
return apiRequest(key(ctx), "/identify", body);
|
|
45
|
+
},
|
|
46
|
+
});
|
|
47
|
+
rl.registerAction("track.event", {
|
|
48
|
+
description: "Track an event",
|
|
49
|
+
inputSchema: {
|
|
50
|
+
event: { type: "string", required: true, description: "Event name" },
|
|
51
|
+
userId: { type: "string", required: false },
|
|
52
|
+
anonymousId: { type: "string", required: false },
|
|
53
|
+
properties: { type: "object", required: false, description: "Event properties" },
|
|
54
|
+
context: { type: "object", required: false },
|
|
55
|
+
integrations: { type: "object", required: false },
|
|
56
|
+
},
|
|
57
|
+
async execute(input, ctx) {
|
|
58
|
+
const p = input;
|
|
59
|
+
const body = { event: p.event };
|
|
60
|
+
if (p.userId)
|
|
61
|
+
body.userId = p.userId;
|
|
62
|
+
else
|
|
63
|
+
body.anonymousId = p.anonymousId ?? crypto.randomUUID();
|
|
64
|
+
if (p.properties)
|
|
65
|
+
body.properties = p.properties;
|
|
66
|
+
if (p.context)
|
|
67
|
+
body.context = p.context;
|
|
68
|
+
if (p.integrations)
|
|
69
|
+
body.integrations = p.integrations;
|
|
70
|
+
return apiRequest(key(ctx), "/track", body);
|
|
71
|
+
},
|
|
72
|
+
});
|
|
73
|
+
rl.registerAction("track.page", {
|
|
74
|
+
description: "Track a page view",
|
|
75
|
+
inputSchema: {
|
|
76
|
+
name: { type: "string", required: true, description: "Page name" },
|
|
77
|
+
userId: { type: "string", required: false },
|
|
78
|
+
anonymousId: { type: "string", required: false },
|
|
79
|
+
properties: { type: "object", required: false },
|
|
80
|
+
context: { type: "object", required: false },
|
|
81
|
+
integrations: { type: "object", required: false },
|
|
82
|
+
},
|
|
83
|
+
async execute(input, ctx) {
|
|
84
|
+
const p = input;
|
|
85
|
+
const body = { name: p.name };
|
|
86
|
+
if (p.userId)
|
|
87
|
+
body.userId = p.userId;
|
|
88
|
+
else
|
|
89
|
+
body.anonymousId = p.anonymousId ?? crypto.randomUUID();
|
|
90
|
+
if (p.properties)
|
|
91
|
+
body.properties = p.properties;
|
|
92
|
+
if (p.context)
|
|
93
|
+
body.context = p.context;
|
|
94
|
+
if (p.integrations)
|
|
95
|
+
body.integrations = p.integrations;
|
|
96
|
+
return apiRequest(key(ctx), "/page", body);
|
|
97
|
+
},
|
|
98
|
+
});
|
|
99
|
+
rl.registerAction("group.add", {
|
|
100
|
+
description: "Associate a user with a group",
|
|
101
|
+
inputSchema: {
|
|
102
|
+
groupId: { type: "string", required: true },
|
|
103
|
+
userId: { type: "string", required: false },
|
|
104
|
+
anonymousId: { type: "string", required: false },
|
|
105
|
+
traits: { type: "object", required: false, description: "Group traits" },
|
|
106
|
+
context: { type: "object", required: false },
|
|
107
|
+
integrations: { type: "object", required: false },
|
|
108
|
+
},
|
|
109
|
+
async execute(input, ctx) {
|
|
110
|
+
const p = input;
|
|
111
|
+
const body = { groupId: p.groupId };
|
|
112
|
+
if (p.userId)
|
|
113
|
+
body.userId = p.userId;
|
|
114
|
+
else
|
|
115
|
+
body.anonymousId = p.anonymousId ?? crypto.randomUUID();
|
|
116
|
+
if (p.traits)
|
|
117
|
+
body.traits = p.traits;
|
|
118
|
+
if (p.context)
|
|
119
|
+
body.context = p.context;
|
|
120
|
+
if (p.integrations)
|
|
121
|
+
body.integrations = p.integrations;
|
|
122
|
+
return apiRequest(key(ctx), "/group", body);
|
|
123
|
+
},
|
|
124
|
+
});
|
|
125
|
+
}
|
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
const BASE = "https://api.sendgrid.com/v3";
|
|
2
|
+
async function apiRequest(apiKey, method, endpoint, body, qs) {
|
|
3
|
+
const url = new URL(`${BASE}${endpoint}`);
|
|
4
|
+
if (qs) {
|
|
5
|
+
for (const [k, v] of Object.entries(qs)) {
|
|
6
|
+
if (v !== undefined && v !== null)
|
|
7
|
+
url.searchParams.set(k, String(v));
|
|
8
|
+
}
|
|
9
|
+
}
|
|
10
|
+
const init = { method, headers: { Authorization: `Bearer ${apiKey}`, "Content-Type": "application/json" } };
|
|
11
|
+
if (body !== undefined)
|
|
12
|
+
init.body = JSON.stringify(body);
|
|
13
|
+
const res = await fetch(url.toString(), init);
|
|
14
|
+
if (!res.ok && res.status !== 202)
|
|
15
|
+
throw new Error(`SendGrid error ${res.status}: ${await res.text()}`);
|
|
16
|
+
const text = await res.text();
|
|
17
|
+
const headers = {};
|
|
18
|
+
res.headers.forEach((v, k) => { headers[k] = v; });
|
|
19
|
+
return { data: text ? JSON.parse(text) : {}, headers };
|
|
20
|
+
}
|
|
21
|
+
export default function sendgrid(rl) {
|
|
22
|
+
rl.setName("sendgrid");
|
|
23
|
+
rl.setVersion("0.1.0");
|
|
24
|
+
rl.setConnectionSchema({
|
|
25
|
+
apiKey: { type: "string", required: true, description: "SendGrid API key", env: "SENDGRID_API_KEY" },
|
|
26
|
+
});
|
|
27
|
+
const key = (ctx) => ctx.connection.config.apiKey;
|
|
28
|
+
// ── Mail ────────────────────────────────────────────
|
|
29
|
+
rl.registerAction("mail.send", {
|
|
30
|
+
description: "Send an email via SendGrid",
|
|
31
|
+
inputSchema: {
|
|
32
|
+
to: { type: "string", required: true, description: "Recipient email(s), comma-separated" },
|
|
33
|
+
from: { type: "string", required: true, description: "Sender email" },
|
|
34
|
+
fromName: { type: "string", required: false },
|
|
35
|
+
subject: { type: "string", required: true },
|
|
36
|
+
contentType: { type: "string", required: false, description: "text/plain or text/html (default text/plain)" },
|
|
37
|
+
content: { type: "string", required: true, description: "Email body" },
|
|
38
|
+
cc: { type: "string", required: false, description: "CC emails, comma-separated" },
|
|
39
|
+
bcc: { type: "string", required: false, description: "BCC emails, comma-separated" },
|
|
40
|
+
replyTo: { type: "string", required: false },
|
|
41
|
+
templateId: { type: "string", required: false, description: "Dynamic template ID (overrides subject/content)" },
|
|
42
|
+
dynamicTemplateData: { type: "object", required: false, description: "Template variables" },
|
|
43
|
+
},
|
|
44
|
+
async execute(input, ctx) {
|
|
45
|
+
const p = input;
|
|
46
|
+
const toList = p.to.split(",").map(e => ({ email: e.trim() }));
|
|
47
|
+
const personalization = { to: toList };
|
|
48
|
+
const body = {
|
|
49
|
+
personalizations: [personalization],
|
|
50
|
+
from: { email: p.from.trim(), name: p.fromName },
|
|
51
|
+
};
|
|
52
|
+
if (p.templateId) {
|
|
53
|
+
body.template_id = p.templateId;
|
|
54
|
+
if (p.dynamicTemplateData)
|
|
55
|
+
personalization.dynamic_template_data = p.dynamicTemplateData;
|
|
56
|
+
}
|
|
57
|
+
else {
|
|
58
|
+
personalization.subject = p.subject;
|
|
59
|
+
body.content = [{ type: p.contentType ?? "text/plain", value: p.content }];
|
|
60
|
+
}
|
|
61
|
+
if (p.cc)
|
|
62
|
+
personalization.cc = p.cc.split(",").map(e => ({ email: e.trim() }));
|
|
63
|
+
if (p.bcc)
|
|
64
|
+
personalization.bcc = p.bcc.split(",").map(e => ({ email: e.trim() }));
|
|
65
|
+
if (p.replyTo)
|
|
66
|
+
body.reply_to_list = p.replyTo.split(",").map(e => ({ email: e.trim() }));
|
|
67
|
+
const { headers } = await apiRequest(key(ctx), "POST", "/mail/send", body);
|
|
68
|
+
return { messageId: headers["x-message-id"] ?? null };
|
|
69
|
+
},
|
|
70
|
+
});
|
|
71
|
+
// ── Contact ─────────────────────────────────────────
|
|
72
|
+
rl.registerAction("contact.get", {
|
|
73
|
+
description: "Get a contact by ID or email",
|
|
74
|
+
inputSchema: {
|
|
75
|
+
contactId: { type: "string", required: false },
|
|
76
|
+
email: { type: "string", required: false, description: "Email (searches if no contactId)" },
|
|
77
|
+
},
|
|
78
|
+
async execute(input, ctx) {
|
|
79
|
+
const p = input;
|
|
80
|
+
if (p.contactId) {
|
|
81
|
+
const { data } = await apiRequest(key(ctx), "GET", `/marketing/contacts/${p.contactId}`);
|
|
82
|
+
return data;
|
|
83
|
+
}
|
|
84
|
+
const { data } = await apiRequest(key(ctx), "POST", "/marketing/contacts/search", { query: `email LIKE '${p.email}'` });
|
|
85
|
+
const result = data.result;
|
|
86
|
+
return result?.[0];
|
|
87
|
+
},
|
|
88
|
+
});
|
|
89
|
+
rl.registerAction("contact.list", {
|
|
90
|
+
description: "List contacts (optionally with SGQL query)",
|
|
91
|
+
inputSchema: {
|
|
92
|
+
query: { type: "string", required: false, description: "SGQL query filter" },
|
|
93
|
+
limit: { type: "number", required: false },
|
|
94
|
+
},
|
|
95
|
+
async execute(input, ctx) {
|
|
96
|
+
const p = (input ?? {});
|
|
97
|
+
let endpoint = "/marketing/contacts";
|
|
98
|
+
let method = "GET";
|
|
99
|
+
const body = {};
|
|
100
|
+
if (p.query) {
|
|
101
|
+
endpoint = "/marketing/contacts/search";
|
|
102
|
+
method = "POST";
|
|
103
|
+
body.query = p.query;
|
|
104
|
+
}
|
|
105
|
+
const { data } = await apiRequest(key(ctx), method, endpoint, Object.keys(body).length ? body : undefined);
|
|
106
|
+
let result = (data.result ?? []);
|
|
107
|
+
if (p.limit)
|
|
108
|
+
result = result.slice(0, p.limit);
|
|
109
|
+
return result;
|
|
110
|
+
},
|
|
111
|
+
});
|
|
112
|
+
rl.registerAction("contact.upsert", {
|
|
113
|
+
description: "Create or update contacts",
|
|
114
|
+
inputSchema: {
|
|
115
|
+
contacts: { type: "object", required: true, description: "Array of contact objects [{email, first_name?, last_name?, ...}]" },
|
|
116
|
+
listIds: { type: "object", required: false, description: "Array of list IDs to add contacts to" },
|
|
117
|
+
},
|
|
118
|
+
async execute(input, ctx) {
|
|
119
|
+
const p = input;
|
|
120
|
+
const body = { contacts: p.contacts };
|
|
121
|
+
if (p.listIds)
|
|
122
|
+
body.list_ids = p.listIds;
|
|
123
|
+
const { data } = await apiRequest(key(ctx), "PUT", "/marketing/contacts", body);
|
|
124
|
+
return data;
|
|
125
|
+
},
|
|
126
|
+
});
|
|
127
|
+
rl.registerAction("contact.delete", {
|
|
128
|
+
description: "Delete contacts by IDs",
|
|
129
|
+
inputSchema: {
|
|
130
|
+
ids: { type: "string", required: true, description: "Comma-separated contact IDs" },
|
|
131
|
+
},
|
|
132
|
+
async execute(input, ctx) {
|
|
133
|
+
const { ids } = input;
|
|
134
|
+
const { data } = await apiRequest(key(ctx), "DELETE", "/marketing/contacts", undefined, { ids });
|
|
135
|
+
return data;
|
|
136
|
+
},
|
|
137
|
+
});
|
|
138
|
+
// ── List ────────────────────────────────────────────
|
|
139
|
+
rl.registerAction("list.create", {
|
|
140
|
+
description: "Create a contact list",
|
|
141
|
+
inputSchema: { name: { type: "string", required: true } },
|
|
142
|
+
async execute(input, ctx) {
|
|
143
|
+
const { data } = await apiRequest(key(ctx), "POST", "/marketing/lists", { name: input.name });
|
|
144
|
+
return data;
|
|
145
|
+
},
|
|
146
|
+
});
|
|
147
|
+
rl.registerAction("list.get", {
|
|
148
|
+
description: "Get a list by ID",
|
|
149
|
+
inputSchema: { listId: { type: "string", required: true } },
|
|
150
|
+
async execute(input, ctx) {
|
|
151
|
+
const { data } = await apiRequest(key(ctx), "GET", `/marketing/lists/${input.listId}`);
|
|
152
|
+
return data;
|
|
153
|
+
},
|
|
154
|
+
});
|
|
155
|
+
rl.registerAction("list.list", {
|
|
156
|
+
description: "List all contact lists",
|
|
157
|
+
inputSchema: { limit: { type: "number", required: false } },
|
|
158
|
+
async execute(input, ctx) {
|
|
159
|
+
const { data } = await apiRequest(key(ctx), "GET", "/marketing/lists");
|
|
160
|
+
let result = (data.result ?? []);
|
|
161
|
+
if (input?.limit)
|
|
162
|
+
result = result.slice(0, input.limit);
|
|
163
|
+
return result;
|
|
164
|
+
},
|
|
165
|
+
});
|
|
166
|
+
rl.registerAction("list.update", {
|
|
167
|
+
description: "Update a list name",
|
|
168
|
+
inputSchema: { listId: { type: "string", required: true }, name: { type: "string", required: true } },
|
|
169
|
+
async execute(input, ctx) {
|
|
170
|
+
const p = input;
|
|
171
|
+
const { data } = await apiRequest(key(ctx), "PATCH", `/marketing/lists/${p.listId}`, { name: p.name });
|
|
172
|
+
return data;
|
|
173
|
+
},
|
|
174
|
+
});
|
|
175
|
+
rl.registerAction("list.delete", {
|
|
176
|
+
description: "Delete a list",
|
|
177
|
+
inputSchema: {
|
|
178
|
+
listId: { type: "string", required: true },
|
|
179
|
+
deleteContacts: { type: "boolean", required: false, description: "Also delete contacts in list" },
|
|
180
|
+
},
|
|
181
|
+
async execute(input, ctx) {
|
|
182
|
+
const p = input;
|
|
183
|
+
await apiRequest(key(ctx), "DELETE", `/marketing/lists/${p.listId}`, undefined, { delete_contacts: p.deleteContacts ? "true" : "false" });
|
|
184
|
+
return { success: true };
|
|
185
|
+
},
|
|
186
|
+
});
|
|
187
|
+
}
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
function getConn(ctx) {
|
|
2
|
+
const c = ctx.connection.config;
|
|
3
|
+
return { url: c.url.replace(/\/$/, ""), apiKey: c.apiKey };
|
|
4
|
+
}
|
|
5
|
+
async function apiRequest(conn, endpoint, body) {
|
|
6
|
+
body.api_key = conn.apiKey;
|
|
7
|
+
body.boolean = true;
|
|
8
|
+
const formBody = Object.entries(body)
|
|
9
|
+
.map(([k, v]) => `${encodeURIComponent(k)}=${encodeURIComponent(String(v))}`)
|
|
10
|
+
.join("&");
|
|
11
|
+
const res = await fetch(`${conn.url}${endpoint}`, {
|
|
12
|
+
method: "POST",
|
|
13
|
+
headers: { "Content-Type": "application/x-www-form-urlencoded" },
|
|
14
|
+
body: formBody,
|
|
15
|
+
});
|
|
16
|
+
if (!res.ok)
|
|
17
|
+
throw new Error(`Sendy error ${res.status}: ${await res.text()}`);
|
|
18
|
+
return res.text();
|
|
19
|
+
}
|
|
20
|
+
export default function sendy(rl) {
|
|
21
|
+
rl.setName("sendy");
|
|
22
|
+
rl.setVersion("0.1.0");
|
|
23
|
+
rl.setConnectionSchema({
|
|
24
|
+
url: { type: "string", required: true, description: "Sendy installation URL", env: "SENDY_URL" },
|
|
25
|
+
apiKey: { type: "string", required: true, description: "Sendy API key", env: "SENDY_API_KEY" },
|
|
26
|
+
});
|
|
27
|
+
rl.registerAction("campaign.create", {
|
|
28
|
+
description: "Create (and optionally send) an email campaign",
|
|
29
|
+
inputSchema: {
|
|
30
|
+
fromName: { type: "string", required: true },
|
|
31
|
+
fromEmail: { type: "string", required: true },
|
|
32
|
+
replyTo: { type: "string", required: true },
|
|
33
|
+
title: { type: "string", required: true },
|
|
34
|
+
subject: { type: "string", required: true },
|
|
35
|
+
htmlText: { type: "string", required: true, description: "HTML content of the email" },
|
|
36
|
+
sendCampaign: { type: "boolean", required: false, description: "Send immediately (default false)" },
|
|
37
|
+
brandId: { type: "string", required: false, description: "Brand ID (required if not sending)" },
|
|
38
|
+
listIds: { type: "string", required: false, description: "Comma-separated list IDs" },
|
|
39
|
+
plainText: { type: "string", required: false },
|
|
40
|
+
trackOpens: { type: "boolean", required: false },
|
|
41
|
+
trackClicks: { type: "boolean", required: false },
|
|
42
|
+
},
|
|
43
|
+
async execute(input, ctx) {
|
|
44
|
+
const p = input;
|
|
45
|
+
const body = {
|
|
46
|
+
from_name: p.fromName, from_email: p.fromEmail, reply_to: p.replyTo,
|
|
47
|
+
title: p.title, subject: p.subject, html_text: p.htmlText,
|
|
48
|
+
send_campaign: p.sendCampaign ? 1 : 0,
|
|
49
|
+
};
|
|
50
|
+
if (p.brandId)
|
|
51
|
+
body.brand_id = p.brandId;
|
|
52
|
+
if (p.listIds)
|
|
53
|
+
body.list_ids = p.listIds;
|
|
54
|
+
if (p.plainText)
|
|
55
|
+
body.plain_text = p.plainText;
|
|
56
|
+
if (p.trackOpens !== undefined)
|
|
57
|
+
body.track_opens = p.trackOpens ? 1 : 0;
|
|
58
|
+
if (p.trackClicks !== undefined)
|
|
59
|
+
body.track_clicks = p.trackClicks ? 1 : 0;
|
|
60
|
+
const resp = await apiRequest(getConn(ctx), "/api/campaigns/create.php", body);
|
|
61
|
+
if (resp.includes("Campaign created"))
|
|
62
|
+
return { message: resp };
|
|
63
|
+
throw new Error(`Sendy campaign error: ${resp}`);
|
|
64
|
+
},
|
|
65
|
+
});
|
|
66
|
+
rl.registerAction("subscriber.add", {
|
|
67
|
+
description: "Add a subscriber to a list",
|
|
68
|
+
inputSchema: {
|
|
69
|
+
email: { type: "string", required: true },
|
|
70
|
+
listId: { type: "string", required: true },
|
|
71
|
+
name: { type: "string", required: false },
|
|
72
|
+
},
|
|
73
|
+
async execute(input, ctx) {
|
|
74
|
+
const p = input;
|
|
75
|
+
const body = { email: p.email, list: p.listId };
|
|
76
|
+
if (p.name)
|
|
77
|
+
body.name = p.name;
|
|
78
|
+
const resp = await apiRequest(getConn(ctx), "/subscribe", body);
|
|
79
|
+
if (resp === "1")
|
|
80
|
+
return { success: true };
|
|
81
|
+
throw new Error(`Sendy subscribe error: ${resp}`);
|
|
82
|
+
},
|
|
83
|
+
});
|
|
84
|
+
rl.registerAction("subscriber.count", {
|
|
85
|
+
description: "Get active subscriber count for a list",
|
|
86
|
+
inputSchema: { listId: { type: "string", required: true } },
|
|
87
|
+
async execute(input, ctx) {
|
|
88
|
+
const { listId } = input;
|
|
89
|
+
const resp = await apiRequest(getConn(ctx), "/api/subscribers/active-subscriber-count.php", { list_id: listId });
|
|
90
|
+
if (/^\d+$/.test(resp))
|
|
91
|
+
return { count: parseInt(resp, 10) };
|
|
92
|
+
throw new Error(`Sendy count error: ${resp}`);
|
|
93
|
+
},
|
|
94
|
+
});
|
|
95
|
+
rl.registerAction("subscriber.delete", {
|
|
96
|
+
description: "Delete a subscriber from a list",
|
|
97
|
+
inputSchema: {
|
|
98
|
+
email: { type: "string", required: true },
|
|
99
|
+
listId: { type: "string", required: true },
|
|
100
|
+
},
|
|
101
|
+
async execute(input, ctx) {
|
|
102
|
+
const { email, listId } = input;
|
|
103
|
+
const resp = await apiRequest(getConn(ctx), "/api/subscribers/delete.php", { email, list_id: listId });
|
|
104
|
+
if (resp === "1")
|
|
105
|
+
return { success: true };
|
|
106
|
+
throw new Error(`Sendy delete error: ${resp}`);
|
|
107
|
+
},
|
|
108
|
+
});
|
|
109
|
+
rl.registerAction("subscriber.unsubscribe", {
|
|
110
|
+
description: "Unsubscribe an email from a list",
|
|
111
|
+
inputSchema: {
|
|
112
|
+
email: { type: "string", required: true },
|
|
113
|
+
listId: { type: "string", required: true },
|
|
114
|
+
},
|
|
115
|
+
async execute(input, ctx) {
|
|
116
|
+
const { email, listId } = input;
|
|
117
|
+
const resp = await apiRequest(getConn(ctx), "/unsubscribe", { email, list: listId });
|
|
118
|
+
if (resp === "1")
|
|
119
|
+
return { success: true };
|
|
120
|
+
throw new Error(`Sendy unsubscribe error: ${resp}`);
|
|
121
|
+
},
|
|
122
|
+
});
|
|
123
|
+
rl.registerAction("subscriber.status", {
|
|
124
|
+
description: "Get subscription status of an email",
|
|
125
|
+
inputSchema: {
|
|
126
|
+
email: { type: "string", required: true },
|
|
127
|
+
listId: { type: "string", required: true },
|
|
128
|
+
},
|
|
129
|
+
async execute(input, ctx) {
|
|
130
|
+
const { email, listId } = input;
|
|
131
|
+
const resp = await apiRequest(getConn(ctx), "/api/subscribers/subscription-status.php", { email, list_id: listId });
|
|
132
|
+
const valid = ["Subscribed", "Unsubscribed", "Unconfirmed", "Bounced", "Soft bounced", "Complained"];
|
|
133
|
+
if (valid.includes(resp))
|
|
134
|
+
return { status: resp };
|
|
135
|
+
throw new Error(`Sendy status error: ${resp}`);
|
|
136
|
+
},
|
|
137
|
+
});
|
|
138
|
+
}
|