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.
Files changed (230) hide show
  1. package/.pi/extensions/runline-context/index.ts +135 -0
  2. package/.pi/extensions/runline-context/package.json +17 -0
  3. package/README.md +273 -0
  4. package/dist/commands/actions.d.ts +3 -0
  5. package/dist/commands/actions.js +43 -0
  6. package/dist/commands/connection.d.ts +11 -0
  7. package/dist/commands/connection.js +56 -0
  8. package/dist/commands/exec.d.ts +5 -0
  9. package/dist/commands/exec.js +46 -0
  10. package/dist/commands/init.d.ts +4 -0
  11. package/dist/commands/init.js +26 -0
  12. package/dist/commands/plugin.d.ts +10 -0
  13. package/dist/commands/plugin.js +57 -0
  14. package/dist/config/index.d.ts +3 -0
  15. package/dist/config/index.js +2 -0
  16. package/dist/config/loader.d.ts +11 -0
  17. package/dist/config/loader.js +82 -0
  18. package/dist/config/types.d.ts +9 -0
  19. package/dist/config/types.js +5 -0
  20. package/dist/core/engine.d.ts +21 -0
  21. package/dist/core/engine.js +280 -0
  22. package/dist/index.d.ts +16 -0
  23. package/dist/index.js +9 -0
  24. package/dist/main.d.ts +2 -0
  25. package/dist/main.js +127 -0
  26. package/dist/plugin/api.d.ts +32 -0
  27. package/dist/plugin/api.js +68 -0
  28. package/dist/plugin/installer.d.ts +27 -0
  29. package/dist/plugin/installer.js +181 -0
  30. package/dist/plugin/loader.d.ts +13 -0
  31. package/dist/plugin/loader.js +164 -0
  32. package/dist/plugin/registry.d.ts +18 -0
  33. package/dist/plugin/registry.js +43 -0
  34. package/dist/plugin/types.d.ts +40 -0
  35. package/dist/plugin/types.js +1 -0
  36. package/dist/plugins/actionNetwork/src/index.js +353 -0
  37. package/dist/plugins/activeCampaign/src/index.js +711 -0
  38. package/dist/plugins/adalo/src/index.js +131 -0
  39. package/dist/plugins/affinity/src/index.js +279 -0
  40. package/dist/plugins/agileCrm/src/index.js +415 -0
  41. package/dist/plugins/airtable/src/index.js +280 -0
  42. package/dist/plugins/airtop/src/index.js +527 -0
  43. package/dist/plugins/apiTemplateIo/src/index.js +86 -0
  44. package/dist/plugins/asana/src/index.js +413 -0
  45. package/dist/plugins/autopilot/src/index.js +203 -0
  46. package/dist/plugins/bambooHr/src/index.js +252 -0
  47. package/dist/plugins/bannerbear/src/index.js +100 -0
  48. package/dist/plugins/baserow/src/index.js +180 -0
  49. package/dist/plugins/beeminder/src/index.js +298 -0
  50. package/dist/plugins/bitly/src/index.js +107 -0
  51. package/dist/plugins/bitwarden/src/index.js +383 -0
  52. package/dist/plugins/box/src/index.js +300 -0
  53. package/dist/plugins/brandfetch/src/index.js +80 -0
  54. package/dist/plugins/brevo/src/index.js +305 -0
  55. package/dist/plugins/bubble/src/index.js +181 -0
  56. package/dist/plugins/chargebee/src/index.js +126 -0
  57. package/dist/plugins/circleci/src/index.js +111 -0
  58. package/dist/plugins/ciscoWebex/src/index.js +245 -0
  59. package/dist/plugins/clearbit/src/index.js +103 -0
  60. package/dist/plugins/clickup/src/index.js +1043 -0
  61. package/dist/plugins/clockify/src/index.js +443 -0
  62. package/dist/plugins/cloudflare/src/index.js +93 -0
  63. package/dist/plugins/cockpit/src/index.js +131 -0
  64. package/dist/plugins/coda/src/index.js +327 -0
  65. package/dist/plugins/coingecko/src/index.js +244 -0
  66. package/dist/plugins/contentful/src/index.js +146 -0
  67. package/dist/plugins/convertkit/src/index.js +270 -0
  68. package/dist/plugins/copper/src/index.js +140 -0
  69. package/dist/plugins/cortex/src/index.js +147 -0
  70. package/dist/plugins/currents/src/index.js +405 -0
  71. package/dist/plugins/customerIo/src/index.js +184 -0
  72. package/dist/plugins/databricks/src/index.js +342 -0
  73. package/dist/plugins/deepl/src/index.js +87 -0
  74. package/dist/plugins/demio/src/index.js +111 -0
  75. package/dist/plugins/dhl/src/index.js +40 -0
  76. package/dist/plugins/discord/src/index.js +275 -0
  77. package/dist/plugins/discourse/src/index.js +273 -0
  78. package/dist/plugins/disqus/src/index.js +145 -0
  79. package/dist/plugins/docker/src/index.js +76 -0
  80. package/dist/plugins/drift/src/index.js +89 -0
  81. package/dist/plugins/dropbox/src/index.js +159 -0
  82. package/dist/plugins/dropcontact/src/index.js +59 -0
  83. package/dist/plugins/egoi/src/index.js +151 -0
  84. package/dist/plugins/elasticsearch/src/index.js +157 -0
  85. package/dist/plugins/emelia/src/index.js +174 -0
  86. package/dist/plugins/erpnext/src/index.js +121 -0
  87. package/dist/plugins/facebookGraph/src/index.js +57 -0
  88. package/dist/plugins/freshdesk/src/index.js +320 -0
  89. package/dist/plugins/freshservice/src/index.js +146 -0
  90. package/dist/plugins/freshworksCrm/src/index.js +149 -0
  91. package/dist/plugins/getresponse/src/index.js +140 -0
  92. package/dist/plugins/ghost/src/index.js +192 -0
  93. package/dist/plugins/github/src/index.js +630 -0
  94. package/dist/plugins/gitlab/src/index.js +358 -0
  95. package/dist/plugins/gong/src/index.js +126 -0
  96. package/dist/plugins/gotify/src/index.js +77 -0
  97. package/dist/plugins/gotowebinar/src/index.js +316 -0
  98. package/dist/plugins/grafana/src/index.js +250 -0
  99. package/dist/plugins/graphql/src/index.js +78 -0
  100. package/dist/plugins/grist/src/index.js +106 -0
  101. package/dist/plugins/hackernews/src/index.js +89 -0
  102. package/dist/plugins/halopsa/src/index.js +79 -0
  103. package/dist/plugins/harvest/src/index.js +163 -0
  104. package/dist/plugins/helpscout/src/index.js +176 -0
  105. package/dist/plugins/highlevel/src/index.js +172 -0
  106. package/dist/plugins/homeAssistant/src/index.js +148 -0
  107. package/dist/plugins/hubspot/src/index.js +176 -0
  108. package/dist/plugins/humanticAi/src/index.js +60 -0
  109. package/dist/plugins/hunter/src/index.js +59 -0
  110. package/dist/plugins/intercom/src/index.js +156 -0
  111. package/dist/plugins/iterable/src/index.js +139 -0
  112. package/dist/plugins/jenkins/src/index.js +132 -0
  113. package/dist/plugins/jira/src/index.js +229 -0
  114. package/dist/plugins/keap/src/index.js +502 -0
  115. package/dist/plugins/kobotoolbox/src/index.js +281 -0
  116. package/dist/plugins/lemlist/src/index.js +231 -0
  117. package/dist/plugins/linear/src/index.js +133 -0
  118. package/dist/plugins/lingvanex/src/index.js +31 -0
  119. package/dist/plugins/linkedin/src/index.js +80 -0
  120. package/dist/plugins/lonescale/src/index.js +119 -0
  121. package/dist/plugins/magento/src/index.js +300 -0
  122. package/dist/plugins/mailcheck/src/index.js +27 -0
  123. package/dist/plugins/mailchimp/src/index.js +321 -0
  124. package/dist/plugins/mailerlite/src/index.js +123 -0
  125. package/dist/plugins/mailgun/src/index.js +48 -0
  126. package/dist/plugins/mailjet/src/index.js +155 -0
  127. package/dist/plugins/mandrill/src/index.js +145 -0
  128. package/dist/plugins/marketstack/src/index.js +97 -0
  129. package/dist/plugins/matrix/src/index.js +194 -0
  130. package/dist/plugins/mattermost/src/index.js +331 -0
  131. package/dist/plugins/mautic/src/index.js +311 -0
  132. package/dist/plugins/medium/src/index.js +77 -0
  133. package/dist/plugins/messagebird/src/index.js +57 -0
  134. package/dist/plugins/metabase/src/index.js +130 -0
  135. package/dist/plugins/misp/src/index.js +476 -0
  136. package/dist/plugins/mocean/src/index.js +67 -0
  137. package/dist/plugins/monday/src/index.js +231 -0
  138. package/dist/plugins/monicaCrm/src/index.js +52 -0
  139. package/dist/plugins/msg91/src/index.js +31 -0
  140. package/dist/plugins/nasa/src/index.js +146 -0
  141. package/dist/plugins/netlify/src/index.js +151 -0
  142. package/dist/plugins/netscalerAdc/src/index.js +131 -0
  143. package/dist/plugins/nextcloud/src/index.js +263 -0
  144. package/dist/plugins/nocodb/src/index.js +130 -0
  145. package/dist/plugins/notion/src/index.js +112 -0
  146. package/dist/plugins/npm/src/index.js +104 -0
  147. package/dist/plugins/odoo/src/index.js +157 -0
  148. package/dist/plugins/okta/src/index.js +141 -0
  149. package/dist/plugins/oneSimpleApi/src/index.js +155 -0
  150. package/dist/plugins/onfleet/src/index.js +254 -0
  151. package/dist/plugins/openThesaurus/src/index.js +32 -0
  152. package/dist/plugins/openweathermap/src/index.js +60 -0
  153. package/dist/plugins/oura/src/index.js +62 -0
  154. package/dist/plugins/paddle/src/index.js +247 -0
  155. package/dist/plugins/pagerduty/src/index.js +201 -0
  156. package/dist/plugins/paypal/src/index.js +106 -0
  157. package/dist/plugins/peekalink/src/index.js +35 -0
  158. package/dist/plugins/phantombuster/src/index.js +94 -0
  159. package/dist/plugins/philipsHue/src/index.js +98 -0
  160. package/dist/plugins/pipedrive/src/index.js +169 -0
  161. package/dist/plugins/plivo/src/index.js +66 -0
  162. package/dist/plugins/postbin/src/index.js +93 -0
  163. package/dist/plugins/posthog/src/index.js +113 -0
  164. package/dist/plugins/profitwell/src/index.js +50 -0
  165. package/dist/plugins/pushbullet/src/index.js +102 -0
  166. package/dist/plugins/pushcut/src/index.js +39 -0
  167. package/dist/plugins/pushover/src/index.js +65 -0
  168. package/dist/plugins/quickbase/src/index.js +153 -0
  169. package/dist/plugins/quickbooks/src/index.js +73 -0
  170. package/dist/plugins/quickchart/src/index.js +36 -0
  171. package/dist/plugins/raindrop/src/index.js +209 -0
  172. package/dist/plugins/reddit/src/index.js +185 -0
  173. package/dist/plugins/rocketchat/src/index.js +53 -0
  174. package/dist/plugins/rundeck/src/index.js +62 -0
  175. package/dist/plugins/salesforce/src/index.js +94 -0
  176. package/dist/plugins/salesmate/src/index.js +83 -0
  177. package/dist/plugins/securityScorecard/src/index.js +200 -0
  178. package/dist/plugins/segment/src/index.js +125 -0
  179. package/dist/plugins/sendgrid/src/index.js +187 -0
  180. package/dist/plugins/sendy/src/index.js +138 -0
  181. package/dist/plugins/sentry/src/index.js +233 -0
  182. package/dist/plugins/servicenow/src/index.js +108 -0
  183. package/dist/plugins/shopify/src/index.js +222 -0
  184. package/dist/plugins/signl4/src/index.js +61 -0
  185. package/dist/plugins/slack/src/index.js +236 -0
  186. package/dist/plugins/sms77/src/index.js +63 -0
  187. package/dist/plugins/splunk/src/index.js +207 -0
  188. package/dist/plugins/spotify/src/index.js +188 -0
  189. package/dist/plugins/stackby/src/index.js +82 -0
  190. package/dist/plugins/storyblok/src/index.js +141 -0
  191. package/dist/plugins/strapi/src/index.js +152 -0
  192. package/dist/plugins/strava/src/index.js +137 -0
  193. package/dist/plugins/stripe/src/index.js +222 -0
  194. package/dist/plugins/supabase/src/index.js +121 -0
  195. package/dist/plugins/syncromsp/src/index.js +255 -0
  196. package/dist/plugins/tapfiliate/src/index.js +125 -0
  197. package/dist/plugins/telegram/src/index.js +233 -0
  198. package/dist/plugins/thehive/src/index.js +142 -0
  199. package/dist/plugins/thehiveProject/src/index.js +194 -0
  200. package/dist/plugins/todoist/src/index.js +244 -0
  201. package/dist/plugins/travisci/src/index.js +71 -0
  202. package/dist/plugins/trello/src/index.js +341 -0
  203. package/dist/plugins/twake/src/index.js +40 -0
  204. package/dist/plugins/twilio/src/index.js +75 -0
  205. package/dist/plugins/twist/src/index.js +90 -0
  206. package/dist/plugins/twitter/src/index.js +123 -0
  207. package/dist/plugins/unleashedSoftware/src/index.js +84 -0
  208. package/dist/plugins/uplead/src/index.js +59 -0
  209. package/dist/plugins/uproc/src/index.js +34 -0
  210. package/dist/plugins/uptimerobot/src/index.js +264 -0
  211. package/dist/plugins/urlscanio/src/index.js +64 -0
  212. package/dist/plugins/vero/src/index.js +80 -0
  213. package/dist/plugins/vonage/src/index.js +42 -0
  214. package/dist/plugins/wekan/src/index.js +91 -0
  215. package/dist/plugins/woocommerce/src/index.js +92 -0
  216. package/dist/plugins/wordpress/src/index.js +121 -0
  217. package/dist/plugins/xero/src/index.js +136 -0
  218. package/dist/plugins/yourls/src/index.js +56 -0
  219. package/dist/plugins/zammad/src/index.js +91 -0
  220. package/dist/plugins/zendesk/src/index.js +137 -0
  221. package/dist/plugins/zoho/src/index.js +85 -0
  222. package/dist/plugins/zoom/src/index.js +122 -0
  223. package/dist/plugins/zulip/src/index.js +170 -0
  224. package/dist/sdk.d.ts +38 -0
  225. package/dist/sdk.js +105 -0
  226. package/dist/utils/cli.d.ts +13 -0
  227. package/dist/utils/cli.js +32 -0
  228. package/dist/utils/output.d.ts +4 -0
  229. package/dist/utils/output.js +13 -0
  230. package/package.json +57 -0
@@ -0,0 +1,80 @@
1
+ const BASE_URL = "https://api.brandfetch.io/v2";
2
+ async function apiRequest(apiKey, domain) {
3
+ const res = await fetch(`${BASE_URL}/brands/${domain}`, {
4
+ headers: { Authorization: `Bearer ${apiKey}` },
5
+ });
6
+ if (!res.ok) {
7
+ const text = await res.text();
8
+ throw new Error(`Brandfetch API error ${res.status}: ${text}`);
9
+ }
10
+ return res.json();
11
+ }
12
+ function getKey(ctx) {
13
+ return ctx.connection.config.apiKey;
14
+ }
15
+ export default function brandfetch(rl) {
16
+ rl.setName("brandfetch");
17
+ rl.setVersion("0.1.0");
18
+ rl.setConnectionSchema({
19
+ apiKey: {
20
+ type: "string",
21
+ required: true,
22
+ description: "Brandfetch API key",
23
+ env: "BRANDFETCH_API_KEY",
24
+ },
25
+ });
26
+ rl.registerAction("brand.getLogos", {
27
+ description: "Get a company's logos and icons",
28
+ inputSchema: {
29
+ domain: { type: "string", required: true, description: "Company domain (e.g. nike.com)" },
30
+ },
31
+ async execute(input, ctx) {
32
+ const { domain } = input;
33
+ const data = await apiRequest(getKey(ctx), domain);
34
+ return data.logos;
35
+ },
36
+ });
37
+ rl.registerAction("brand.getColors", {
38
+ description: "Get a company's brand colors",
39
+ inputSchema: {
40
+ domain: { type: "string", required: true, description: "Company domain" },
41
+ },
42
+ async execute(input, ctx) {
43
+ const { domain } = input;
44
+ const data = await apiRequest(getKey(ctx), domain);
45
+ return data.colors;
46
+ },
47
+ });
48
+ rl.registerAction("brand.getFonts", {
49
+ description: "Get a company's fonts",
50
+ inputSchema: {
51
+ domain: { type: "string", required: true, description: "Company domain" },
52
+ },
53
+ async execute(input, ctx) {
54
+ const { domain } = input;
55
+ const data = await apiRequest(getKey(ctx), domain);
56
+ return data.fonts;
57
+ },
58
+ });
59
+ rl.registerAction("brand.getCompany", {
60
+ description: "Get a company's data (name, description, etc.)",
61
+ inputSchema: {
62
+ domain: { type: "string", required: true, description: "Company domain" },
63
+ },
64
+ async execute(input, ctx) {
65
+ const { domain } = input;
66
+ const data = await apiRequest(getKey(ctx), domain);
67
+ return data.company;
68
+ },
69
+ });
70
+ rl.registerAction("brand.getIndustry", {
71
+ description: "Get a company's industry classification",
72
+ inputSchema: {
73
+ domain: { type: "string", required: true, description: "Company domain" },
74
+ },
75
+ async execute(input, ctx) {
76
+ const { domain } = input;
77
+ return apiRequest(getKey(ctx), domain);
78
+ },
79
+ });
80
+ }
@@ -0,0 +1,305 @@
1
+ const BASE_URL = "https://api.brevo.com";
2
+ async function apiRequest(apiKey, method, endpoint, body, qs) {
3
+ const url = new URL(`${BASE_URL}${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 opts = {
11
+ method,
12
+ headers: {
13
+ "Content-Type": "application/json",
14
+ Accept: "application/json",
15
+ "api-key": apiKey,
16
+ },
17
+ };
18
+ if (body && Object.keys(body).length > 0 && method !== "GET" && method !== "DELETE") {
19
+ opts.body = JSON.stringify(body);
20
+ }
21
+ const res = await fetch(url.toString(), opts);
22
+ if (!res.ok) {
23
+ const text = await res.text();
24
+ throw new Error(`Brevo API error ${res.status}: ${text}`);
25
+ }
26
+ if (res.status === 204 || res.headers.get("content-length") === "0")
27
+ return { success: true };
28
+ const ct = res.headers.get("content-type") ?? "";
29
+ if (ct.includes("application/json"))
30
+ return res.json();
31
+ return { success: true };
32
+ }
33
+ function getKey(ctx) {
34
+ return ctx.connection.config.apiKey;
35
+ }
36
+ export default function brevo(rl) {
37
+ rl.setName("brevo");
38
+ rl.setVersion("0.1.0");
39
+ rl.setConnectionSchema({
40
+ apiKey: {
41
+ type: "string",
42
+ required: true,
43
+ description: "Brevo (Sendinblue) API key",
44
+ env: "BREVO_API_KEY",
45
+ },
46
+ });
47
+ // ── Contact ─────────────────────────────────────────
48
+ rl.registerAction("contact.create", {
49
+ description: "Create a contact",
50
+ inputSchema: {
51
+ email: { type: "string", required: true, description: "Contact email" },
52
+ attributes: { type: "object", required: false, description: "Contact attributes as key-value pairs" },
53
+ listIds: { type: "array", required: false, description: "Array of list IDs to add contact to" },
54
+ },
55
+ async execute(input, ctx) {
56
+ const { email, attributes, listIds } = (input ?? {});
57
+ const body = { email };
58
+ if (attributes)
59
+ body.attributes = attributes;
60
+ if (listIds)
61
+ body.listIds = listIds;
62
+ return apiRequest(getKey(ctx), "POST", "/v3/contacts", body);
63
+ },
64
+ });
65
+ rl.registerAction("contact.upsert", {
66
+ description: "Create or update a contact",
67
+ inputSchema: {
68
+ email: { type: "string", required: true, description: "Contact email" },
69
+ attributes: { type: "object", required: false, description: "Contact attributes" },
70
+ listIds: { type: "array", required: false, description: "List IDs" },
71
+ },
72
+ async execute(input, ctx) {
73
+ const { email, attributes, listIds } = (input ?? {});
74
+ const body = { email, updateEnabled: true };
75
+ if (attributes)
76
+ body.attributes = attributes;
77
+ if (listIds)
78
+ body.listIds = listIds;
79
+ return apiRequest(getKey(ctx), "POST", "/v3/contacts", body);
80
+ },
81
+ });
82
+ rl.registerAction("contact.get", {
83
+ description: "Get a contact by email or ID",
84
+ inputSchema: {
85
+ identifier: { type: "string", required: true, description: "Email or contact ID" },
86
+ },
87
+ async execute(input, ctx) {
88
+ const { identifier } = input;
89
+ return apiRequest(getKey(ctx), "GET", `/v3/contacts/${encodeURIComponent(identifier)}`);
90
+ },
91
+ });
92
+ rl.registerAction("contact.list", {
93
+ description: "List contacts",
94
+ inputSchema: {
95
+ limit: { type: "number", required: false, description: "Max results (default: 50, max: 1000)" },
96
+ offset: { type: "number", required: false, description: "Offset for pagination" },
97
+ sort: { type: "string", required: false, description: "Sort order: asc or desc" },
98
+ modifiedSince: { type: "string", required: false, description: "Filter by modification date (ISO 8601)" },
99
+ },
100
+ async execute(input, ctx) {
101
+ const { limit, offset, sort, modifiedSince } = (input ?? {});
102
+ const qs = {};
103
+ if (limit)
104
+ qs.limit = limit;
105
+ if (offset)
106
+ qs.offset = offset;
107
+ if (sort)
108
+ qs.sort = sort;
109
+ if (modifiedSince)
110
+ qs.modifiedSince = modifiedSince;
111
+ const data = (await apiRequest(getKey(ctx), "GET", "/v3/contacts", undefined, qs));
112
+ return data.contacts ?? [];
113
+ },
114
+ });
115
+ rl.registerAction("contact.update", {
116
+ description: "Update a contact",
117
+ inputSchema: {
118
+ identifier: { type: "string", required: true, description: "Email or contact ID" },
119
+ attributes: { type: "object", required: false, description: "Attributes to update" },
120
+ listIds: { type: "array", required: false, description: "List IDs to add" },
121
+ unlinkListIds: { type: "array", required: false, description: "List IDs to remove" },
122
+ },
123
+ async execute(input, ctx) {
124
+ const { identifier, attributes, listIds, unlinkListIds } = input;
125
+ const body = {};
126
+ if (attributes)
127
+ body.attributes = attributes;
128
+ if (listIds)
129
+ body.listIds = listIds;
130
+ if (unlinkListIds)
131
+ body.unlinkListIds = unlinkListIds;
132
+ await apiRequest(getKey(ctx), "PUT", `/v3/contacts/${encodeURIComponent(identifier)}`, body);
133
+ return { success: true };
134
+ },
135
+ });
136
+ rl.registerAction("contact.delete", {
137
+ description: "Delete a contact",
138
+ inputSchema: {
139
+ identifier: { type: "string", required: true, description: "Email or contact ID" },
140
+ },
141
+ async execute(input, ctx) {
142
+ const { identifier } = input;
143
+ await apiRequest(getKey(ctx), "DELETE", `/v3/contacts/${encodeURIComponent(identifier)}`);
144
+ return { success: true };
145
+ },
146
+ });
147
+ // ── Attribute ───────────────────────────────────────
148
+ rl.registerAction("attribute.create", {
149
+ description: "Create a contact attribute",
150
+ inputSchema: {
151
+ category: { type: "string", required: true, description: "Category: normal, transactional, category, calculated, global" },
152
+ name: { type: "string", required: true, description: "Attribute name" },
153
+ type: { type: "string", required: false, description: "Type (for normal): text, date, float, boolean" },
154
+ value: { type: "string", required: false, description: "Value (for calculated/global)" },
155
+ enumeration: { type: "array", required: false, description: "Array of {value, label} for category type" },
156
+ },
157
+ async execute(input, ctx) {
158
+ const { category, name, type, value, enumeration } = input;
159
+ const body = {};
160
+ if (type)
161
+ body.type = type;
162
+ if (value)
163
+ body.value = value;
164
+ if (enumeration)
165
+ body.enumeration = enumeration;
166
+ await apiRequest(getKey(ctx), "POST", `/v3/contacts/attributes/${category}/${encodeURIComponent(name)}`, body);
167
+ return { success: true };
168
+ },
169
+ });
170
+ rl.registerAction("attribute.update", {
171
+ description: "Update a contact attribute",
172
+ inputSchema: {
173
+ category: { type: "string", required: true, description: "Category: calculated, category, global" },
174
+ name: { type: "string", required: true, description: "Attribute name" },
175
+ value: { type: "string", required: false, description: "New value" },
176
+ enumeration: { type: "array", required: false, description: "Array of {value, label}" },
177
+ },
178
+ async execute(input, ctx) {
179
+ const { category, name, value, enumeration } = input;
180
+ const body = {};
181
+ if (value)
182
+ body.value = value;
183
+ if (enumeration)
184
+ body.enumeration = enumeration;
185
+ return apiRequest(getKey(ctx), "PUT", `/v3/contacts/attributes/${category}/${encodeURIComponent(name)}`, body);
186
+ },
187
+ });
188
+ rl.registerAction("attribute.delete", {
189
+ description: "Delete a contact attribute",
190
+ inputSchema: {
191
+ category: { type: "string", required: true, description: "Category" },
192
+ name: { type: "string", required: true, description: "Attribute name" },
193
+ },
194
+ async execute(input, ctx) {
195
+ const { category, name } = input;
196
+ await apiRequest(getKey(ctx), "DELETE", `/v3/contacts/attributes/${category}/${encodeURIComponent(name)}`);
197
+ return { success: true };
198
+ },
199
+ });
200
+ rl.registerAction("attribute.list", {
201
+ description: "List all contact attributes",
202
+ inputSchema: {
203
+ limit: { type: "number", required: false, description: "Max results" },
204
+ },
205
+ async execute(input, ctx) {
206
+ const { limit } = (input ?? {});
207
+ const data = (await apiRequest(getKey(ctx), "GET", "/v3/contacts/attributes"));
208
+ const attrs = data.attributes ?? [];
209
+ if (limit)
210
+ return attrs.slice(0, limit);
211
+ return attrs;
212
+ },
213
+ });
214
+ // ── Email ───────────────────────────────────────────
215
+ rl.registerAction("email.send", {
216
+ description: "Send a transactional email",
217
+ inputSchema: {
218
+ sender: { type: "string", required: true, description: "Sender email address" },
219
+ to: { type: "array", required: true, description: "Array of recipient emails (or {email, name} objects)" },
220
+ subject: { type: "string", required: true, description: "Email subject" },
221
+ htmlContent: { type: "string", required: false, description: "HTML body" },
222
+ textContent: { type: "string", required: false, description: "Plain text body" },
223
+ cc: { type: "array", required: false, description: "CC recipients" },
224
+ bcc: { type: "array", required: false, description: "BCC recipients" },
225
+ tags: { type: "array", required: false, description: "Email tags" },
226
+ },
227
+ async execute(input, ctx) {
228
+ const { sender, to, subject, htmlContent, textContent, cc, bcc, tags } = input;
229
+ const toList = to.map((r) => typeof r === "string" ? { email: r } : r);
230
+ const senderObj = typeof sender === "string" ? { email: sender } : sender;
231
+ const body = {
232
+ sender: senderObj,
233
+ to: toList,
234
+ subject,
235
+ };
236
+ if (htmlContent)
237
+ body.htmlContent = htmlContent;
238
+ if (textContent)
239
+ body.textContent = textContent;
240
+ if (cc)
241
+ body.cc = cc.map((r) => (typeof r === "string" ? { email: r } : r));
242
+ if (bcc)
243
+ body.bcc = bcc.map((r) => (typeof r === "string" ? { email: r } : r));
244
+ if (tags)
245
+ body.tags = tags;
246
+ return apiRequest(getKey(ctx), "POST", "/v3/smtp/email", body);
247
+ },
248
+ });
249
+ rl.registerAction("email.sendTemplate", {
250
+ description: "Send an email using a template",
251
+ inputSchema: {
252
+ templateId: { type: "number", required: true, description: "Template ID" },
253
+ to: { type: "array", required: true, description: "Array of recipient emails (or {email, name} objects)" },
254
+ params: { type: "object", required: false, description: "Template parameters as key-value pairs" },
255
+ tags: { type: "array", required: false, description: "Email tags" },
256
+ },
257
+ async execute(input, ctx) {
258
+ const { templateId, to, params, tags } = input;
259
+ const toList = to.map((r) => typeof r === "string" ? { email: r } : r);
260
+ const body = { templateId, to: toList };
261
+ if (params)
262
+ body.params = params;
263
+ if (tags)
264
+ body.tags = tags;
265
+ return apiRequest(getKey(ctx), "POST", "/v3/smtp/email", body);
266
+ },
267
+ });
268
+ // ── Sender ──────────────────────────────────────────
269
+ rl.registerAction("sender.create", {
270
+ description: "Create a sender",
271
+ inputSchema: {
272
+ name: { type: "string", required: true, description: "Sender name" },
273
+ email: { type: "string", required: true, description: "Sender email" },
274
+ },
275
+ async execute(input, ctx) {
276
+ const { name, email } = input;
277
+ return apiRequest(getKey(ctx), "POST", "/v3/senders", { name, email });
278
+ },
279
+ });
280
+ rl.registerAction("sender.delete", {
281
+ description: "Delete a sender",
282
+ inputSchema: {
283
+ id: { type: "string", required: true, description: "Sender ID" },
284
+ },
285
+ async execute(input, ctx) {
286
+ const { id } = input;
287
+ await apiRequest(getKey(ctx), "DELETE", `/v3/senders/${id}`);
288
+ return { success: true };
289
+ },
290
+ });
291
+ rl.registerAction("sender.list", {
292
+ description: "List all senders",
293
+ inputSchema: {
294
+ limit: { type: "number", required: false, description: "Max results" },
295
+ },
296
+ async execute(input, ctx) {
297
+ const { limit } = (input ?? {});
298
+ const data = (await apiRequest(getKey(ctx), "GET", "/v3/senders"));
299
+ const senders = data.senders ?? [];
300
+ if (limit)
301
+ return senders.slice(0, limit);
302
+ return senders;
303
+ },
304
+ });
305
+ }
@@ -0,0 +1,181 @@
1
+ function buildBaseUrl(cfg) {
2
+ const hosting = cfg.hosting;
3
+ const appName = cfg.appName;
4
+ const domain = cfg.domain?.replace(/\/$/, "");
5
+ const environment = cfg.environment;
6
+ const rootUrl = hosting === "selfHosted" && domain ? domain : `https://${appName}.bubbleapps.io`;
7
+ const urlSegment = environment === "development" ? "/version-test/api/1.1" : "/api/1.1";
8
+ return `${rootUrl}${urlSegment}`;
9
+ }
10
+ async function apiRequest(baseUrl, token, method, endpoint, body, qs) {
11
+ const url = new URL(`${baseUrl}${endpoint}`);
12
+ if (qs) {
13
+ for (const [k, v] of Object.entries(qs)) {
14
+ if (v !== undefined && v !== null)
15
+ url.searchParams.set(k, String(v));
16
+ }
17
+ }
18
+ const opts = {
19
+ method,
20
+ headers: {
21
+ "Content-Type": "application/json",
22
+ Authorization: `Bearer ${token}`,
23
+ },
24
+ };
25
+ if (body && Object.keys(body).length > 0 && method !== "GET" && method !== "DELETE") {
26
+ opts.body = JSON.stringify(body);
27
+ }
28
+ const res = await fetch(url.toString(), opts);
29
+ if (!res.ok) {
30
+ const text = await res.text();
31
+ throw new Error(`Bubble API error ${res.status}: ${text}`);
32
+ }
33
+ if (res.status === 204)
34
+ return { success: true };
35
+ const ct = res.headers.get("content-type") ?? "";
36
+ if (ct.includes("application/json"))
37
+ return res.json();
38
+ return { success: true };
39
+ }
40
+ function getConn(ctx) {
41
+ const cfg = ctx.connection.config;
42
+ return {
43
+ baseUrl: buildBaseUrl(cfg),
44
+ token: cfg.apiToken,
45
+ };
46
+ }
47
+ function normalizeTypeName(name) {
48
+ return name.replace(/\s/g, "").toLowerCase();
49
+ }
50
+ export default function bubble(rl) {
51
+ rl.setName("bubble");
52
+ rl.setVersion("0.1.0");
53
+ rl.setConnectionSchema({
54
+ apiToken: {
55
+ type: "string",
56
+ required: true,
57
+ description: "Bubble API token",
58
+ env: "BUBBLE_API_TOKEN",
59
+ },
60
+ appName: {
61
+ type: "string",
62
+ required: true,
63
+ description: "Bubble app name (used for hosted URL)",
64
+ env: "BUBBLE_APP_NAME",
65
+ },
66
+ hosting: {
67
+ type: "string",
68
+ required: false,
69
+ description: "bubbleHosted (default) or selfHosted",
70
+ env: "BUBBLE_HOSTING",
71
+ default: "bubbleHosted",
72
+ },
73
+ domain: {
74
+ type: "string",
75
+ required: false,
76
+ description: "Self-hosted domain URL (only if hosting=selfHosted)",
77
+ env: "BUBBLE_DOMAIN",
78
+ },
79
+ environment: {
80
+ type: "string",
81
+ required: false,
82
+ description: "live (default) or development",
83
+ env: "BUBBLE_ENVIRONMENT",
84
+ default: "live",
85
+ },
86
+ });
87
+ rl.registerAction("object.create", {
88
+ description: "Create an object",
89
+ inputSchema: {
90
+ typeName: { type: "string", required: true, description: "Data type name" },
91
+ properties: { type: "object", required: true, description: "Key-value pairs of field values" },
92
+ },
93
+ async execute(input, ctx) {
94
+ const { typeName, properties } = input;
95
+ const { baseUrl, token } = getConn(ctx);
96
+ return apiRequest(baseUrl, token, "POST", `/obj/${normalizeTypeName(typeName)}`, properties);
97
+ },
98
+ });
99
+ rl.registerAction("object.get", {
100
+ description: "Get an object by ID",
101
+ inputSchema: {
102
+ typeName: { type: "string", required: true, description: "Data type name" },
103
+ objectId: { type: "string", required: true, description: "Object unique ID" },
104
+ },
105
+ async execute(input, ctx) {
106
+ const { typeName, objectId } = input;
107
+ const { baseUrl, token } = getConn(ctx);
108
+ const data = (await apiRequest(baseUrl, token, "GET", `/obj/${normalizeTypeName(typeName)}/${objectId}`));
109
+ return data.response;
110
+ },
111
+ });
112
+ rl.registerAction("object.list", {
113
+ description: "List objects of a type with optional constraints and sorting",
114
+ inputSchema: {
115
+ typeName: { type: "string", required: true, description: "Data type name" },
116
+ constraints: { type: "array", required: false, description: "Array of constraint objects [{key, constraint_type, value}]" },
117
+ sortField: { type: "string", required: false, description: "Field to sort by" },
118
+ descending: { type: "boolean", required: false, description: "Sort descending" },
119
+ limit: { type: "number", required: false, description: "Max results" },
120
+ },
121
+ async execute(input, ctx) {
122
+ const { typeName, constraints, sortField, descending, limit } = (input ?? {});
123
+ const { baseUrl, token } = getConn(ctx);
124
+ const qs = {};
125
+ if (constraints)
126
+ qs.constraints = JSON.stringify(constraints);
127
+ if (sortField) {
128
+ qs.sort_field = sortField;
129
+ if (descending)
130
+ qs.descending = "true";
131
+ }
132
+ const endpoint = `/obj/${normalizeTypeName(typeName)}`;
133
+ if (limit) {
134
+ qs.limit = limit;
135
+ const data = (await apiRequest(baseUrl, token, "GET", endpoint, undefined, qs));
136
+ return data.response?.results ?? [];
137
+ }
138
+ // Paginate all
139
+ const results = [];
140
+ qs.limit = 100;
141
+ qs.cursor = 0;
142
+ while (true) {
143
+ const data = (await apiRequest(baseUrl, token, "GET", endpoint, undefined, qs));
144
+ const resp = data.response;
145
+ const items = resp.results ?? [];
146
+ results.push(...items);
147
+ if (resp.remaining === 0)
148
+ break;
149
+ qs.cursor = qs.cursor + qs.limit;
150
+ }
151
+ return results;
152
+ },
153
+ });
154
+ rl.registerAction("object.update", {
155
+ description: "Update an object",
156
+ inputSchema: {
157
+ typeName: { type: "string", required: true, description: "Data type name" },
158
+ objectId: { type: "string", required: true, description: "Object unique ID" },
159
+ properties: { type: "object", required: true, description: "Key-value pairs of fields to update" },
160
+ },
161
+ async execute(input, ctx) {
162
+ const { typeName, objectId, properties } = input;
163
+ const { baseUrl, token } = getConn(ctx);
164
+ await apiRequest(baseUrl, token, "PATCH", `/obj/${normalizeTypeName(typeName)}/${objectId}`, properties);
165
+ return { success: true };
166
+ },
167
+ });
168
+ rl.registerAction("object.delete", {
169
+ description: "Delete an object",
170
+ inputSchema: {
171
+ typeName: { type: "string", required: true, description: "Data type name" },
172
+ objectId: { type: "string", required: true, description: "Object unique ID" },
173
+ },
174
+ async execute(input, ctx) {
175
+ const { typeName, objectId } = input;
176
+ const { baseUrl, token } = getConn(ctx);
177
+ await apiRequest(baseUrl, token, "DELETE", `/obj/${normalizeTypeName(typeName)}/${objectId}`);
178
+ return { success: true };
179
+ },
180
+ });
181
+ }