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,331 @@
1
+ async function apiRequest(baseUrl, token, method, endpoint, body, qs) {
2
+ const url = new URL(`${baseUrl}/api/v4/${endpoint}`);
3
+ if (qs) {
4
+ for (const [k, v] of Object.entries(qs)) {
5
+ if (v !== undefined && v !== null)
6
+ url.searchParams.set(k, String(v));
7
+ }
8
+ }
9
+ const opts = {
10
+ method,
11
+ headers: { Authorization: `Bearer ${token}`, "Content-Type": "application/json; charset=utf-8" },
12
+ };
13
+ if (body !== undefined && method !== "GET" && method !== "DELETE") {
14
+ opts.body = typeof body === "string" ? body : JSON.stringify(body);
15
+ }
16
+ const res = await fetch(url.toString(), opts);
17
+ if (!res.ok)
18
+ throw new Error(`Mattermost API error ${res.status}: ${await res.text()}`);
19
+ if (res.status === 204)
20
+ return { success: true };
21
+ return res.json();
22
+ }
23
+ async function paginateAll(baseUrl, token, endpoint, qs = {}) {
24
+ const all = [];
25
+ qs.page = 0;
26
+ qs.per_page = 100;
27
+ let data;
28
+ do {
29
+ data = (await apiRequest(baseUrl, token, "GET", endpoint, undefined, qs));
30
+ all.push(...data);
31
+ qs.page++;
32
+ } while (data.length > 0);
33
+ return all;
34
+ }
35
+ export default function mattermost(rl) {
36
+ rl.setName("mattermost");
37
+ rl.setVersion("0.1.0");
38
+ rl.setConnectionSchema({
39
+ baseUrl: { type: "string", required: true, description: "Mattermost server URL (e.g. https://mattermost.example.com)", env: "MATTERMOST_URL" },
40
+ accessToken: { type: "string", required: true, description: "Personal access token or bot token", env: "MATTERMOST_TOKEN" },
41
+ });
42
+ const conn = (ctx) => ({
43
+ baseUrl: ctx.connection.config.baseUrl.replace(/\/$/, ""),
44
+ token: ctx.connection.config.accessToken,
45
+ });
46
+ // ── Channel ─────────────────────────────────────────
47
+ rl.registerAction("channel.create", {
48
+ description: "Create a channel",
49
+ inputSchema: {
50
+ teamId: { type: "string", required: true },
51
+ displayName: { type: "string", required: true, description: "Display name" },
52
+ name: { type: "string", required: true, description: "URL-safe channel name" },
53
+ type: { type: "string", required: true, description: "'public' or 'private'" },
54
+ },
55
+ async execute(input, ctx) {
56
+ const { teamId, displayName, name, type } = input;
57
+ const { baseUrl, token } = conn(ctx);
58
+ return apiRequest(baseUrl, token, "POST", "channels", {
59
+ team_id: teamId, display_name: displayName, name, type: type === "public" ? "O" : "P",
60
+ });
61
+ },
62
+ });
63
+ rl.registerAction("channel.delete", {
64
+ description: "Delete (archive) a channel",
65
+ inputSchema: { channelId: { type: "string", required: true } },
66
+ async execute(input, ctx) {
67
+ const { baseUrl, token } = conn(ctx);
68
+ return apiRequest(baseUrl, token, "DELETE", `channels/${input.channelId}`);
69
+ },
70
+ });
71
+ rl.registerAction("channel.addUser", {
72
+ description: "Add a user to a channel",
73
+ inputSchema: { channelId: { type: "string", required: true }, userId: { type: "string", required: true } },
74
+ async execute(input, ctx) {
75
+ const { channelId, userId } = input;
76
+ const { baseUrl, token } = conn(ctx);
77
+ return apiRequest(baseUrl, token, "POST", `channels/${channelId}/members`, { user_id: userId });
78
+ },
79
+ });
80
+ rl.registerAction("channel.members", {
81
+ description: "List members of a channel",
82
+ inputSchema: {
83
+ channelId: { type: "string", required: true },
84
+ limit: { type: "number", required: false },
85
+ resolveData: { type: "boolean", required: false, description: "Resolve user IDs to full user objects" },
86
+ },
87
+ async execute(input, ctx) {
88
+ const { channelId, limit, resolveData } = (input ?? {});
89
+ const { baseUrl, token } = conn(ctx);
90
+ let data;
91
+ if (limit) {
92
+ data = (await apiRequest(baseUrl, token, "GET", `channels/${channelId}/members`, undefined, { per_page: limit }));
93
+ }
94
+ else {
95
+ data = await paginateAll(baseUrl, token, `channels/${channelId}/members`);
96
+ }
97
+ if (resolveData && data.length > 0) {
98
+ const userIds = data.map((m) => m.user_id);
99
+ return apiRequest(baseUrl, token, "POST", "users/ids", userIds);
100
+ }
101
+ return data;
102
+ },
103
+ });
104
+ rl.registerAction("channel.restore", {
105
+ description: "Restore (unarchive) a channel",
106
+ inputSchema: { channelId: { type: "string", required: true } },
107
+ async execute(input, ctx) {
108
+ const { baseUrl, token } = conn(ctx);
109
+ return apiRequest(baseUrl, token, "POST", `channels/${input.channelId}/restore`);
110
+ },
111
+ });
112
+ rl.registerAction("channel.search", {
113
+ description: "Search channels in a team",
114
+ inputSchema: {
115
+ teamId: { type: "string", required: true },
116
+ term: { type: "string", required: true, description: "Search term" },
117
+ limit: { type: "number", required: false },
118
+ },
119
+ async execute(input, ctx) {
120
+ const { teamId, term, limit } = input;
121
+ const { baseUrl, token } = conn(ctx);
122
+ let data = (await apiRequest(baseUrl, token, "POST", `teams/${teamId}/channels/search`, { term }));
123
+ if (limit)
124
+ data = data.slice(0, limit);
125
+ return data;
126
+ },
127
+ });
128
+ rl.registerAction("channel.statistics", {
129
+ description: "Get channel statistics",
130
+ inputSchema: { channelId: { type: "string", required: true } },
131
+ async execute(input, ctx) {
132
+ const { baseUrl, token } = conn(ctx);
133
+ return apiRequest(baseUrl, token, "GET", `channels/${input.channelId}/stats`);
134
+ },
135
+ });
136
+ // ── Message ─────────────────────────────────────────
137
+ rl.registerAction("message.post", {
138
+ description: "Post a message to a channel",
139
+ inputSchema: {
140
+ channelId: { type: "string", required: true },
141
+ message: { type: "string", required: true },
142
+ attachments: { type: "array", required: false, description: "Array of Mattermost attachment objects" },
143
+ rootId: { type: "string", required: false, description: "Post ID to reply to (thread)" },
144
+ },
145
+ async execute(input, ctx) {
146
+ const { channelId, message, attachments, rootId } = input;
147
+ const { baseUrl, token } = conn(ctx);
148
+ const body = { channel_id: channelId, message };
149
+ if (attachments)
150
+ body.props = { attachments };
151
+ if (rootId)
152
+ body.root_id = rootId;
153
+ return apiRequest(baseUrl, token, "POST", "posts", body);
154
+ },
155
+ });
156
+ rl.registerAction("message.delete", {
157
+ description: "Delete a message (post)",
158
+ inputSchema: { postId: { type: "string", required: true } },
159
+ async execute(input, ctx) {
160
+ const { baseUrl, token } = conn(ctx);
161
+ return apiRequest(baseUrl, token, "DELETE", `posts/${input.postId}`);
162
+ },
163
+ });
164
+ rl.registerAction("message.postEphemeral", {
165
+ description: "Post an ephemeral message (visible only to one user)",
166
+ inputSchema: {
167
+ channelId: { type: "string", required: true },
168
+ userId: { type: "string", required: true, description: "User who will see the message" },
169
+ message: { type: "string", required: true },
170
+ },
171
+ async execute(input, ctx) {
172
+ const { channelId, userId, message } = input;
173
+ const { baseUrl, token } = conn(ctx);
174
+ return apiRequest(baseUrl, token, "POST", "posts/ephemeral", {
175
+ user_id: userId,
176
+ post: { channel_id: channelId, message },
177
+ });
178
+ },
179
+ });
180
+ // ── Reaction ────────────────────────────────────────
181
+ rl.registerAction("reaction.create", {
182
+ description: "Add a reaction to a post",
183
+ inputSchema: {
184
+ userId: { type: "string", required: true },
185
+ postId: { type: "string", required: true },
186
+ emojiName: { type: "string", required: true, description: "Emoji name without colons (e.g. 'thumbsup')" },
187
+ },
188
+ async execute(input, ctx) {
189
+ const { userId, postId, emojiName } = input;
190
+ const { baseUrl, token } = conn(ctx);
191
+ return apiRequest(baseUrl, token, "POST", "reactions", {
192
+ user_id: userId,
193
+ post_id: postId,
194
+ emoji_name: emojiName.replace(/:/g, ""),
195
+ create_at: Date.now(),
196
+ });
197
+ },
198
+ });
199
+ rl.registerAction("reaction.delete", {
200
+ description: "Remove a reaction from a post",
201
+ inputSchema: {
202
+ userId: { type: "string", required: true },
203
+ postId: { type: "string", required: true },
204
+ emojiName: { type: "string", required: true },
205
+ },
206
+ async execute(input, ctx) {
207
+ const { userId, postId, emojiName } = input;
208
+ const { baseUrl, token } = conn(ctx);
209
+ const name = emojiName.replace(/:/g, "");
210
+ return apiRequest(baseUrl, token, "DELETE", `users/${userId}/posts/${postId}/reactions/${name}`);
211
+ },
212
+ });
213
+ rl.registerAction("reaction.list", {
214
+ description: "List reactions on a post",
215
+ inputSchema: {
216
+ postId: { type: "string", required: true },
217
+ limit: { type: "number", required: false },
218
+ },
219
+ async execute(input, ctx) {
220
+ const { postId, limit } = input;
221
+ const { baseUrl, token } = conn(ctx);
222
+ let data = (await apiRequest(baseUrl, token, "GET", `posts/${postId}/reactions`));
223
+ if (data === null)
224
+ return [];
225
+ if (limit)
226
+ data = data.slice(0, limit);
227
+ return data;
228
+ },
229
+ });
230
+ // ── User ────────────────────────────────────────────
231
+ rl.registerAction("user.create", {
232
+ description: "Create a user",
233
+ inputSchema: {
234
+ username: { type: "string", required: true },
235
+ authService: { type: "string", required: true, description: "'email' for email/password, or an SSO service name" },
236
+ email: { type: "string", required: false, description: "Required if authService is 'email'" },
237
+ password: { type: "string", required: false, description: "Required if authService is 'email'" },
238
+ authData: { type: "string", required: false, description: "Required if authService is not 'email'" },
239
+ additionalFields: { type: "object", required: false, description: "first_name, last_name, nickname, locale, position, etc." },
240
+ },
241
+ async execute(input, ctx) {
242
+ const { username, authService, email, password, authData, additionalFields } = input;
243
+ const { baseUrl, token } = conn(ctx);
244
+ const body = { username, auth_service: authService };
245
+ if (authService === "email") {
246
+ body.email = email;
247
+ body.password = password;
248
+ }
249
+ else {
250
+ body.auth_data = authData;
251
+ }
252
+ if (additionalFields)
253
+ Object.assign(body, additionalFields);
254
+ return apiRequest(baseUrl, token, "POST", "users", body);
255
+ },
256
+ });
257
+ rl.registerAction("user.deactivate", {
258
+ description: "Deactivate (delete) a user",
259
+ inputSchema: { userId: { type: "string", required: true } },
260
+ async execute(input, ctx) {
261
+ const { baseUrl, token } = conn(ctx);
262
+ return apiRequest(baseUrl, token, "DELETE", `users/${input.userId}`);
263
+ },
264
+ });
265
+ rl.registerAction("user.list", {
266
+ description: "List users",
267
+ inputSchema: {
268
+ limit: { type: "number", required: false },
269
+ inTeam: { type: "string", required: false, description: "Filter by team ID" },
270
+ notInTeam: { type: "string", required: false },
271
+ inChannel: { type: "string", required: false, description: "Filter by channel ID" },
272
+ notInChannel: { type: "string", required: false },
273
+ sort: { type: "string", required: false, description: "last_activity_at, created_at, username, status" },
274
+ },
275
+ async execute(input, ctx) {
276
+ const p = (input ?? {});
277
+ const { baseUrl, token } = conn(ctx);
278
+ const qs = {};
279
+ if (p.inTeam)
280
+ qs.in_team = p.inTeam;
281
+ if (p.notInTeam)
282
+ qs.not_in_team = p.notInTeam;
283
+ if (p.inChannel)
284
+ qs.in_channel = p.inChannel;
285
+ if (p.notInChannel)
286
+ qs.not_in_channel = p.notInChannel;
287
+ if (p.sort && p.sort !== "username")
288
+ qs.sort = p.sort;
289
+ if (p.limit) {
290
+ qs.per_page = p.limit;
291
+ return apiRequest(baseUrl, token, "GET", "users", undefined, qs);
292
+ }
293
+ return paginateAll(baseUrl, token, "users", qs);
294
+ },
295
+ });
296
+ rl.registerAction("user.getByEmail", {
297
+ description: "Get a user by email",
298
+ inputSchema: { email: { type: "string", required: true } },
299
+ async execute(input, ctx) {
300
+ const { baseUrl, token } = conn(ctx);
301
+ return apiRequest(baseUrl, token, "GET", `users/email/${input.email}`);
302
+ },
303
+ });
304
+ rl.registerAction("user.getByIds", {
305
+ description: "Get users by IDs",
306
+ inputSchema: {
307
+ userIds: { type: "array", required: true, description: "Array of user IDs" },
308
+ since: { type: "string", required: false, description: "ISO datetime — only return users updated since" },
309
+ },
310
+ async execute(input, ctx) {
311
+ const { userIds, since } = input;
312
+ const { baseUrl, token } = conn(ctx);
313
+ const qs = {};
314
+ if (since)
315
+ qs.since = new Date(since).getTime();
316
+ return apiRequest(baseUrl, token, "POST", "users/ids", userIds, qs);
317
+ },
318
+ });
319
+ rl.registerAction("user.invite", {
320
+ description: "Invite users to a team by email",
321
+ inputSchema: {
322
+ teamId: { type: "string", required: true },
323
+ emails: { type: "array", required: true, description: "Array of email addresses" },
324
+ },
325
+ async execute(input, ctx) {
326
+ const { teamId, emails } = input;
327
+ const { baseUrl, token } = conn(ctx);
328
+ return apiRequest(baseUrl, token, "POST", `teams/${teamId}/invite/email`, emails);
329
+ },
330
+ });
331
+ }
@@ -0,0 +1,311 @@
1
+ async function apiRequest(baseUrl, username, password, method, endpoint, body, qs) {
2
+ const url = new URL(`${baseUrl}/api${endpoint}`);
3
+ if (qs) {
4
+ for (const [k, v] of Object.entries(qs)) {
5
+ if (v !== undefined && v !== null)
6
+ url.searchParams.set(k, String(v));
7
+ }
8
+ }
9
+ const opts = {
10
+ method,
11
+ headers: { Authorization: `Basic ${btoa(`${username}:${password}`)}`, "Content-Type": "application/json" },
12
+ };
13
+ if (body && Object.keys(body).length > 0 && method !== "GET" && method !== "DELETE")
14
+ opts.body = JSON.stringify(body);
15
+ const res = await fetch(url.toString(), opts);
16
+ if (!res.ok)
17
+ throw new Error(`Mautic API error ${res.status}: ${await res.text()}`);
18
+ const data = (await res.json());
19
+ if (data.errors)
20
+ throw new Error(`Mautic API error: ${JSON.stringify(data.errors)}`);
21
+ return data;
22
+ }
23
+ async function paginateAll(baseUrl, username, password, propertyName, endpoint, qs = {}) {
24
+ const all = [];
25
+ qs.limit = 30;
26
+ qs.start = 0;
27
+ let data;
28
+ do {
29
+ data = (await apiRequest(baseUrl, username, password, "GET", endpoint, undefined, qs));
30
+ const values = Object.values((data[propertyName] ?? {}));
31
+ all.push(...values);
32
+ qs.start += qs.limit;
33
+ } while (data.total !== undefined && all.length < Number.parseInt(data.total, 10));
34
+ return all;
35
+ }
36
+ export default function mautic(rl) {
37
+ rl.setName("mautic");
38
+ rl.setVersion("0.1.0");
39
+ rl.setConnectionSchema({
40
+ url: { type: "string", required: true, description: "Mautic instance URL (e.g. https://mautic.example.com)", env: "MAUTIC_URL" },
41
+ username: { type: "string", required: true, description: "Mautic username", env: "MAUTIC_USERNAME" },
42
+ password: { type: "string", required: true, description: "Mautic password", env: "MAUTIC_PASSWORD" },
43
+ });
44
+ const conn = (ctx) => ({
45
+ baseUrl: ctx.connection.config.url.replace(/\/$/, ""),
46
+ username: ctx.connection.config.username,
47
+ password: ctx.connection.config.password,
48
+ });
49
+ const req = (ctx, method, endpoint, body, qs) => {
50
+ const c = conn(ctx);
51
+ return apiRequest(c.baseUrl, c.username, c.password, method, endpoint, body, qs);
52
+ };
53
+ const pagAll = (ctx, prop, endpoint, qs) => {
54
+ const c = conn(ctx);
55
+ return paginateAll(c.baseUrl, c.username, c.password, prop, endpoint, qs);
56
+ };
57
+ // ── Company ─────────────────────────────────────────
58
+ rl.registerAction("company.create", {
59
+ description: "Create a company",
60
+ inputSchema: {
61
+ companyname: { type: "string", required: true },
62
+ additionalFields: { type: "object", required: false, description: "companyemail, companyfax, companyindustry, companyphone, companywebsite, companyannual_revenue, companydescription, companynumber_of_employees, companyaddress1, companyaddress2, companycity, companystate, companycountry, companyzipcode, custom fields" },
63
+ },
64
+ async execute(input, ctx) {
65
+ const { companyname, additionalFields } = input;
66
+ const body = { companyname };
67
+ if (additionalFields)
68
+ Object.assign(body, additionalFields);
69
+ const data = (await req(ctx, "POST", "/companies/new", body));
70
+ return data.company;
71
+ },
72
+ });
73
+ rl.registerAction("company.update", {
74
+ description: "Update a company",
75
+ inputSchema: {
76
+ companyId: { type: "string", required: true },
77
+ updateFields: { type: "object", required: true, description: "Fields to update (same keys as create)" },
78
+ },
79
+ async execute(input, ctx) {
80
+ const { companyId, updateFields } = input;
81
+ const data = (await req(ctx, "PATCH", `/companies/${companyId}/edit`, updateFields));
82
+ return data.company;
83
+ },
84
+ });
85
+ rl.registerAction("company.get", {
86
+ description: "Get a company by ID",
87
+ inputSchema: { companyId: { type: "string", required: true } },
88
+ async execute(input, ctx) {
89
+ const data = (await req(ctx, "GET", `/companies/${input.companyId}`));
90
+ return data.company;
91
+ },
92
+ });
93
+ rl.registerAction("company.list", {
94
+ description: "List companies",
95
+ inputSchema: { limit: { type: "number", required: false }, search: { type: "string", required: false }, orderBy: { type: "string", required: false }, orderByDir: { type: "string", required: false, description: "ASC or DESC" } },
96
+ async execute(input, ctx) {
97
+ const p = (input ?? {});
98
+ const qs = {};
99
+ if (p.search)
100
+ qs.search = p.search;
101
+ if (p.orderBy)
102
+ qs.orderBy = p.orderBy;
103
+ if (p.orderByDir)
104
+ qs.orderByDir = p.orderByDir;
105
+ if (p.limit) {
106
+ qs.limit = p.limit;
107
+ qs.start = 0;
108
+ const data = (await req(ctx, "GET", "/companies", undefined, qs));
109
+ return Object.values((data.companies ?? {}));
110
+ }
111
+ return pagAll(ctx, "companies", "/companies", qs);
112
+ },
113
+ });
114
+ rl.registerAction("company.delete", {
115
+ description: "Delete a company",
116
+ inputSchema: { companyId: { type: "string", required: true } },
117
+ async execute(input, ctx) {
118
+ const data = (await req(ctx, "DELETE", `/companies/${input.companyId}/delete`));
119
+ return data.company;
120
+ },
121
+ });
122
+ // ── Contact ─────────────────────────────────────────
123
+ rl.registerAction("contact.create", {
124
+ description: "Create a contact",
125
+ inputSchema: {
126
+ email: { type: "string", required: false },
127
+ firstname: { type: "string", required: false },
128
+ lastname: { type: "string", required: false },
129
+ company: { type: "string", required: false },
130
+ position: { type: "string", required: false },
131
+ title: { type: "string", required: false },
132
+ phone: { type: "string", required: false },
133
+ mobile: { type: "string", required: false },
134
+ website: { type: "string", required: false },
135
+ tags: { type: "string", required: false, description: "Comma-separated tags" },
136
+ stage: { type: "string", required: false, description: "Stage ID" },
137
+ owner: { type: "string", required: false, description: "Owner user ID" },
138
+ ipAddress: { type: "string", required: false },
139
+ additionalFields: { type: "object", required: false, description: "Custom fields, address fields (address1, address2, city, state, country, zipcode), social (facebook, twitter, linkedin, skype, instagram, foursquare)" },
140
+ },
141
+ async execute(input, ctx) {
142
+ const { additionalFields, ...rest } = input;
143
+ const body = {};
144
+ for (const [k, v] of Object.entries(rest)) {
145
+ if (v !== undefined && v !== null && v !== "")
146
+ body[k] = v;
147
+ }
148
+ if (additionalFields)
149
+ Object.assign(body, additionalFields);
150
+ const data = (await req(ctx, "POST", "/contacts/new", body));
151
+ return data.contact;
152
+ },
153
+ });
154
+ rl.registerAction("contact.update", {
155
+ description: "Update a contact",
156
+ inputSchema: {
157
+ contactId: { type: "string", required: true },
158
+ updateFields: { type: "object", required: true, description: "Fields to update (email, firstname, lastname, company, position, title, phone, mobile, address fields, social fields, custom fields, tags, stage, owner, etc.)" },
159
+ },
160
+ async execute(input, ctx) {
161
+ const { contactId, updateFields } = input;
162
+ const data = (await req(ctx, "PATCH", `/contacts/${contactId}/edit`, updateFields));
163
+ return data.contact;
164
+ },
165
+ });
166
+ rl.registerAction("contact.get", {
167
+ description: "Get a contact by ID",
168
+ inputSchema: { contactId: { type: "string", required: true } },
169
+ async execute(input, ctx) {
170
+ const data = (await req(ctx, "GET", `/contacts/${input.contactId}`));
171
+ return data.contact;
172
+ },
173
+ });
174
+ rl.registerAction("contact.list", {
175
+ description: "List contacts",
176
+ inputSchema: {
177
+ limit: { type: "number", required: false },
178
+ search: { type: "string", required: false },
179
+ orderBy: { type: "string", required: false, description: "Field to order by (snake_case)" },
180
+ orderByDir: { type: "string", required: false, description: "ASC or DESC" },
181
+ },
182
+ async execute(input, ctx) {
183
+ const p = (input ?? {});
184
+ const qs = {};
185
+ if (p.search)
186
+ qs.search = p.search;
187
+ if (p.orderBy)
188
+ qs.orderBy = p.orderBy;
189
+ if (p.orderByDir)
190
+ qs.orderByDir = p.orderByDir;
191
+ if (p.limit) {
192
+ qs.limit = p.limit;
193
+ qs.start = 0;
194
+ const data = (await req(ctx, "GET", "/contacts", undefined, qs));
195
+ return Object.values((data.contacts ?? {}));
196
+ }
197
+ return pagAll(ctx, "contacts", "/contacts", qs);
198
+ },
199
+ });
200
+ rl.registerAction("contact.delete", {
201
+ description: "Delete a contact",
202
+ inputSchema: { contactId: { type: "string", required: true } },
203
+ async execute(input, ctx) {
204
+ const data = (await req(ctx, "DELETE", `/contacts/${input.contactId}/delete`));
205
+ return data.contact;
206
+ },
207
+ });
208
+ rl.registerAction("contact.sendEmail", {
209
+ description: "Send a campaign/template email to a contact",
210
+ inputSchema: {
211
+ contactId: { type: "string", required: true },
212
+ emailId: { type: "string", required: true, description: "Campaign email ID (template type)" },
213
+ },
214
+ async execute(input, ctx) {
215
+ const { contactId, emailId } = input;
216
+ return req(ctx, "POST", `/emails/${emailId}/contact/${contactId}/send`);
217
+ },
218
+ });
219
+ rl.registerAction("contact.editDoNotContact", {
220
+ description: "Add or remove a contact from the Do Not Contact list",
221
+ inputSchema: {
222
+ contactId: { type: "string", required: true },
223
+ channel: { type: "string", required: true, description: "email, sms, etc." },
224
+ action: { type: "string", required: true, description: "'add' or 'remove'" },
225
+ reason: { type: "number", required: false, description: "DNC reason (1=contacted, 2=unsubscribed, 3=bounced, 4=manual)" },
226
+ comments: { type: "string", required: false },
227
+ },
228
+ async execute(input, ctx) {
229
+ const { contactId, channel, action, reason, comments } = input;
230
+ const body = {};
231
+ if (reason)
232
+ body.reason = reason;
233
+ if (comments)
234
+ body.comments = comments;
235
+ const data = (await req(ctx, "POST", `/contacts/${contactId}/dnc/${channel}/${action}`, body));
236
+ return data.contact;
237
+ },
238
+ });
239
+ rl.registerAction("contact.editPoints", {
240
+ description: "Add or subtract points from a contact",
241
+ inputSchema: {
242
+ contactId: { type: "string", required: true },
243
+ action: { type: "string", required: true, description: "'add' or 'subtract'" },
244
+ points: { type: "number", required: true },
245
+ },
246
+ async execute(input, ctx) {
247
+ const { contactId, action, points } = input;
248
+ const path = action === "add" ? "plus" : "minus";
249
+ return req(ctx, "POST", `/contacts/${contactId}/points/${path}/${points}`);
250
+ },
251
+ });
252
+ // ── Contact Segment ─────────────────────────────────
253
+ rl.registerAction("contactSegment.add", {
254
+ description: "Add a contact to a segment",
255
+ inputSchema: { segmentId: { type: "string", required: true }, contactId: { type: "string", required: true } },
256
+ async execute(input, ctx) {
257
+ const { segmentId, contactId } = input;
258
+ return req(ctx, "POST", `/segments/${segmentId}/contact/${contactId}/add`);
259
+ },
260
+ });
261
+ rl.registerAction("contactSegment.remove", {
262
+ description: "Remove a contact from a segment",
263
+ inputSchema: { segmentId: { type: "string", required: true }, contactId: { type: "string", required: true } },
264
+ async execute(input, ctx) {
265
+ const { segmentId, contactId } = input;
266
+ return req(ctx, "POST", `/segments/${segmentId}/contact/${contactId}/remove`);
267
+ },
268
+ });
269
+ // ── Campaign Contact ────────────────────────────────
270
+ rl.registerAction("campaignContact.add", {
271
+ description: "Add a contact to a campaign",
272
+ inputSchema: { campaignId: { type: "string", required: true }, contactId: { type: "string", required: true } },
273
+ async execute(input, ctx) {
274
+ const { campaignId, contactId } = input;
275
+ return req(ctx, "POST", `/campaigns/${campaignId}/contact/${contactId}/add`);
276
+ },
277
+ });
278
+ rl.registerAction("campaignContact.remove", {
279
+ description: "Remove a contact from a campaign",
280
+ inputSchema: { campaignId: { type: "string", required: true }, contactId: { type: "string", required: true } },
281
+ async execute(input, ctx) {
282
+ const { campaignId, contactId } = input;
283
+ return req(ctx, "POST", `/campaigns/${campaignId}/contact/${contactId}/remove`);
284
+ },
285
+ });
286
+ // ── Company Contact ─────────────────────────────────
287
+ rl.registerAction("companyContact.add", {
288
+ description: "Add a contact to a company",
289
+ inputSchema: { companyId: { type: "string", required: true }, contactId: { type: "string", required: true } },
290
+ async execute(input, ctx) {
291
+ const { companyId, contactId } = input;
292
+ return req(ctx, "POST", `/companies/${companyId}/contact/${contactId}/add`);
293
+ },
294
+ });
295
+ rl.registerAction("companyContact.remove", {
296
+ description: "Remove a contact from a company",
297
+ inputSchema: { companyId: { type: "string", required: true }, contactId: { type: "string", required: true } },
298
+ async execute(input, ctx) {
299
+ const { companyId, contactId } = input;
300
+ return req(ctx, "POST", `/companies/${companyId}/contact/${contactId}/remove`);
301
+ },
302
+ });
303
+ // ── Segment Email ───────────────────────────────────
304
+ rl.registerAction("segmentEmail.send", {
305
+ description: "Send a segment (list) email",
306
+ inputSchema: { emailId: { type: "string", required: true, description: "Segment email ID" } },
307
+ async execute(input, ctx) {
308
+ return req(ctx, "POST", `/emails/${input.emailId}/send`);
309
+ },
310
+ });
311
+ }