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,184 @@
1
+ async function apiRequest(siteId, apiKey, method, url, body) {
2
+ const opts = {
3
+ method,
4
+ headers: {
5
+ "Content-Type": "application/json",
6
+ Authorization: `Basic ${btoa(`${siteId}:${apiKey}`)}`,
7
+ },
8
+ };
9
+ if (body && Object.keys(body).length > 0 && method !== "GET" && method !== "DELETE") {
10
+ opts.body = JSON.stringify(body);
11
+ }
12
+ const res = await fetch(url, opts);
13
+ if (!res.ok)
14
+ throw new Error(`Customer.io API error ${res.status}: ${await res.text()}`);
15
+ if (res.status === 204 || res.headers.get("content-length") === "0")
16
+ return { success: true };
17
+ const ct = res.headers.get("content-type") ?? "";
18
+ if (ct.includes("application/json"))
19
+ return res.json();
20
+ return { success: true };
21
+ }
22
+ function getConn(ctx) {
23
+ const cfg = ctx.connection.config;
24
+ const region = cfg.region ?? "track.customer.io";
25
+ const isEu = region.includes("-eu");
26
+ return {
27
+ siteId: cfg.siteId,
28
+ trackingApiKey: cfg.trackingApiKey,
29
+ appApiKey: cfg.appApiKey,
30
+ trackingBase: `https://${region}/api/v1`,
31
+ appBase: isEu ? "https://api-eu.customer.io/v1" : "https://api.customer.io/v1",
32
+ };
33
+ }
34
+ function tracking(ctx, method, endpoint, body) {
35
+ const { siteId, trackingApiKey, trackingBase } = getConn(ctx);
36
+ return apiRequest(siteId, trackingApiKey, method, `${trackingBase}${endpoint}`, body);
37
+ }
38
+ function app(ctx, method, endpoint, body) {
39
+ const { siteId, appApiKey, appBase } = getConn(ctx);
40
+ return apiRequest(siteId, appApiKey, method, `${appBase}${endpoint}`, body);
41
+ }
42
+ export default function customerIo(rl) {
43
+ rl.setName("customerIo");
44
+ rl.setVersion("0.1.0");
45
+ rl.setConnectionSchema({
46
+ siteId: { type: "string", required: true, description: "Customer.io Site ID", env: "CUSTOMERIO_SITE_ID" },
47
+ trackingApiKey: { type: "string", required: true, description: "Tracking API key", env: "CUSTOMERIO_TRACKING_API_KEY" },
48
+ appApiKey: { type: "string", required: true, description: "App API key (for campaigns)", env: "CUSTOMERIO_APP_API_KEY" },
49
+ region: { type: "string", required: false, description: "Region: track.customer.io (US, default) or track-eu.customer.io (EU)", default: "track.customer.io" },
50
+ });
51
+ // ── Campaign ────────────────────────────────────────
52
+ rl.registerAction("campaign.get", {
53
+ description: "Get a campaign by ID",
54
+ inputSchema: { campaignId: { type: "number", required: true, description: "Campaign ID" } },
55
+ async execute(input, ctx) {
56
+ const { campaignId } = input;
57
+ const data = (await app(ctx, "GET", `/campaigns/${campaignId}`));
58
+ return data.campaign;
59
+ },
60
+ });
61
+ rl.registerAction("campaign.list", {
62
+ description: "List all campaigns",
63
+ async execute(_input, ctx) {
64
+ const data = (await app(ctx, "GET", "/campaigns"));
65
+ return data.campaigns;
66
+ },
67
+ });
68
+ rl.registerAction("campaign.getMetrics", {
69
+ description: "Get campaign metrics",
70
+ inputSchema: {
71
+ campaignId: { type: "number", required: true, description: "Campaign ID" },
72
+ period: { type: "string", required: false, description: "Period: days (default), weeks, months" },
73
+ steps: { type: "number", required: false, description: "Number of steps/periods" },
74
+ type: { type: "string", required: false, description: "Metric type: email, webhook, push, slack, urbanAirship" },
75
+ },
76
+ async execute(input, ctx) {
77
+ const { campaignId, period, steps, type } = (input ?? {});
78
+ let endpoint = `/campaigns/${campaignId}/metrics`;
79
+ if (period && period !== "days")
80
+ endpoint += `?period=${period}`;
81
+ const body = {};
82
+ if (steps)
83
+ body.steps = steps;
84
+ if (type)
85
+ body.type = type === "urbanAirship" ? "urban_airship" : type;
86
+ const data = (await app(ctx, "GET", endpoint, body));
87
+ return data.metric;
88
+ },
89
+ });
90
+ // ── Customer ────────────────────────────────────────
91
+ rl.registerAction("customer.upsert", {
92
+ description: "Create or update a customer",
93
+ inputSchema: {
94
+ id: { type: "string", required: true, description: "Customer ID (your internal ID)" },
95
+ email: { type: "string", required: false, description: "Email address" },
96
+ createdAt: { type: "string", required: false, description: "Created at (ISO 8601)" },
97
+ attributes: { type: "object", required: false, description: "Custom attributes as key-value pairs" },
98
+ },
99
+ async execute(input, ctx) {
100
+ const { id, email, createdAt, attributes } = input;
101
+ const body = {};
102
+ if (email)
103
+ body.email = email;
104
+ if (createdAt)
105
+ body.created_at = Math.floor(new Date(createdAt).getTime() / 1000);
106
+ if (attributes)
107
+ body.data = attributes;
108
+ await tracking(ctx, "PUT", `/customers/${id}`, body);
109
+ return { id, ...body };
110
+ },
111
+ });
112
+ rl.registerAction("customer.delete", {
113
+ description: "Delete a customer",
114
+ inputSchema: { id: { type: "string", required: true, description: "Customer ID" } },
115
+ async execute(input, ctx) {
116
+ const { id } = input;
117
+ await tracking(ctx, "DELETE", `/customers/${id}`);
118
+ return { success: true };
119
+ },
120
+ });
121
+ // ── Event ───────────────────────────────────────────
122
+ rl.registerAction("event.track", {
123
+ description: "Track an event for a customer",
124
+ inputSchema: {
125
+ customerId: { type: "string", required: true, description: "Customer ID" },
126
+ eventName: { type: "string", required: true, description: "Event name" },
127
+ type: { type: "string", required: false, description: "Event type (e.g. page)" },
128
+ data: { type: "object", required: false, description: "Custom event attributes" },
129
+ },
130
+ async execute(input, ctx) {
131
+ const { customerId, eventName, type, data } = input;
132
+ const body = { name: eventName };
133
+ const eventData = {};
134
+ if (type)
135
+ eventData.type = type;
136
+ if (data)
137
+ Object.assign(eventData, data);
138
+ if (Object.keys(eventData).length > 0)
139
+ body.data = eventData;
140
+ await tracking(ctx, "POST", `/customers/${customerId}/events`, body);
141
+ return { success: true };
142
+ },
143
+ });
144
+ rl.registerAction("event.trackAnonymous", {
145
+ description: "Track an anonymous event (not tied to a customer)",
146
+ inputSchema: {
147
+ eventName: { type: "string", required: true, description: "Event name" },
148
+ data: { type: "object", required: false, description: "Custom event attributes" },
149
+ },
150
+ async execute(input, ctx) {
151
+ const { eventName, data } = input;
152
+ const body = { name: eventName };
153
+ if (data)
154
+ body.data = data;
155
+ await tracking(ctx, "POST", "/events", body);
156
+ return { success: true };
157
+ },
158
+ });
159
+ // ── Segment ─────────────────────────────────────────
160
+ rl.registerAction("segment.addCustomers", {
161
+ description: "Add customers to a manual segment",
162
+ inputSchema: {
163
+ segmentId: { type: "number", required: true, description: "Segment ID" },
164
+ customerIds: { type: "array", required: true, description: "Array of customer IDs" },
165
+ },
166
+ async execute(input, ctx) {
167
+ const { segmentId, customerIds } = input;
168
+ await tracking(ctx, "POST", `/segments/${segmentId}/add_customers`, { ids: customerIds });
169
+ return { success: true };
170
+ },
171
+ });
172
+ rl.registerAction("segment.removeCustomers", {
173
+ description: "Remove customers from a manual segment",
174
+ inputSchema: {
175
+ segmentId: { type: "number", required: true, description: "Segment ID" },
176
+ customerIds: { type: "array", required: true, description: "Array of customer IDs" },
177
+ },
178
+ async execute(input, ctx) {
179
+ const { segmentId, customerIds } = input;
180
+ await tracking(ctx, "POST", `/segments/${segmentId}/remove_customers`, { ids: customerIds });
181
+ return { success: true };
182
+ },
183
+ });
184
+ }
@@ -0,0 +1,342 @@
1
+ function getConn(ctx) {
2
+ const host = ctx.connection.config.host.replace(/\/$/, "");
3
+ const token = ctx.connection.config.accessToken;
4
+ return { host, token };
5
+ }
6
+ async function api(host, token, method, path, body, qs) {
7
+ const url = new URL(`${host}${path}`);
8
+ if (qs) {
9
+ for (const [k, v] of Object.entries(qs)) {
10
+ if (v)
11
+ url.searchParams.set(k, v);
12
+ }
13
+ }
14
+ const init = { method, headers: { Authorization: `Bearer ${token}`, "Content-Type": "application/json" } };
15
+ if (body && Object.keys(body).length > 0)
16
+ init.body = JSON.stringify(body);
17
+ const res = await fetch(url.toString(), init);
18
+ if (res.status === 204 || res.headers.get("content-length") === "0")
19
+ return { success: true };
20
+ if (!res.ok)
21
+ throw new Error(`Databricks error ${res.status}: ${await res.text()}`);
22
+ const text = await res.text();
23
+ return text ? JSON.parse(text) : { success: true };
24
+ }
25
+ function volumeParts(volumePath) {
26
+ const parts = volumePath.split(".");
27
+ if (parts.length !== 3)
28
+ throw new Error("Volume path must be catalog.schema.volume");
29
+ return parts;
30
+ }
31
+ function sleep(ms) { return new Promise((r) => setTimeout(r, ms)); }
32
+ export default function databricks(rl) {
33
+ rl.setName("databricks");
34
+ rl.setVersion("0.1.0");
35
+ rl.setConnectionSchema({
36
+ host: { type: "string", required: true, description: "Databricks workspace URL, e.g. https://adb-12345.azuredatabricks.net", env: "DATABRICKS_HOST" },
37
+ accessToken: { type: "string", required: true, description: "Personal access token or OAuth2 token", env: "DATABRICKS_TOKEN" },
38
+ });
39
+ // ── Databricks SQL ──────────────────────────────────
40
+ rl.registerAction("sql.executeQuery", { description: "Execute a SQL query on a warehouse and return results",
41
+ inputSchema: {
42
+ warehouseId: { type: "string", required: true, description: "SQL warehouse ID" },
43
+ query: { type: "string", required: true, description: "SQL statement" },
44
+ parameters: { type: "object", required: false, description: "Array of {name, value, type?} query parameters" },
45
+ },
46
+ async execute(input, ctx) {
47
+ const { host, token } = getConn(ctx);
48
+ const p = input;
49
+ const params = (p.parameters ?? []);
50
+ const body = { warehouse_id: p.warehouseId, statement: p.query, wait_timeout: "50s", on_wait_timeout: "CONTINUE" };
51
+ if (params.length > 0)
52
+ body.parameters = params.map(({ name, value, type }) => type ? { name, value, type } : { name, value });
53
+ let result = await api(host, token, "POST", "/api/2.0/sql/statements", body);
54
+ const statementId = result.statement_id;
55
+ let status = result.status.state;
56
+ let retries = 0;
57
+ while (status !== "SUCCEEDED" && status !== "FAILED" && status !== "CANCELED" && retries < 60) {
58
+ await sleep(5000);
59
+ result = await api(host, token, "GET", `/api/2.0/sql/statements/${statementId}`);
60
+ status = result.status.state;
61
+ retries++;
62
+ }
63
+ if (status === "FAILED" || status === "CANCELED")
64
+ throw new Error(`Query ${status}: ${JSON.stringify(result.status)}`);
65
+ if (retries >= 60)
66
+ throw new Error("Query execution timeout");
67
+ // Collect all chunks
68
+ const allRows = [];
69
+ const manifest = result.manifest;
70
+ const totalChunks = (manifest?.total_chunk_count ?? 0);
71
+ const resultData = result.result;
72
+ if (resultData?.data_array) {
73
+ allRows.push(...resultData.data_array);
74
+ }
75
+ let chunkIdx = allRows.length > 0 ? 1 : 0;
76
+ while (chunkIdx < totalChunks) {
77
+ const chunk = await api(host, token, "GET", `/api/2.0/sql/statements/${statementId}/result/chunks/${chunkIdx}`);
78
+ if (chunk.data_array)
79
+ allRows.push(...chunk.data_array);
80
+ chunkIdx++;
81
+ }
82
+ // Transform to objects
83
+ const columns = (manifest?.schema?.columns ?? []);
84
+ return allRows.map((row) => {
85
+ const obj = {};
86
+ columns.forEach((col, idx) => { obj[col.name] = row[idx]; });
87
+ return obj;
88
+ });
89
+ } });
90
+ // ── Files ───────────────────────────────────────────
91
+ rl.registerAction("files.createDirectory", { description: "Create a directory in a Unity Catalog volume",
92
+ inputSchema: { volumePath: { type: "string", required: true, description: "catalog.schema.volume" }, directoryPath: { type: "string", required: true } },
93
+ async execute(input, ctx) {
94
+ const { host, token } = getConn(ctx);
95
+ const p = input;
96
+ const [cat, sch, vol] = volumeParts(p.volumePath);
97
+ await api(host, token, "PUT", `/api/2.0/fs/directories/Volumes/${cat}/${sch}/${vol}/${p.directoryPath}`);
98
+ return { success: true, directoryPath: p.directoryPath };
99
+ } });
100
+ rl.registerAction("files.deleteDirectory", { description: "Delete a directory in a volume",
101
+ inputSchema: { volumePath: { type: "string", required: true, description: "catalog.schema.volume" }, directoryPath: { type: "string", required: true } },
102
+ async execute(input, ctx) {
103
+ const { host, token } = getConn(ctx);
104
+ const p = input;
105
+ const [cat, sch, vol] = volumeParts(p.volumePath);
106
+ await api(host, token, "DELETE", `/api/2.0/fs/directories/Volumes/${cat}/${sch}/${vol}/${p.directoryPath}`);
107
+ return { success: true };
108
+ } });
109
+ rl.registerAction("files.deleteFile", { description: "Delete a file in a volume",
110
+ inputSchema: { volumePath: { type: "string", required: true, description: "catalog.schema.volume" }, filePath: { type: "string", required: true } },
111
+ async execute(input, ctx) {
112
+ const { host, token } = getConn(ctx);
113
+ const p = input;
114
+ const [cat, sch, vol] = volumeParts(p.volumePath);
115
+ await api(host, token, "DELETE", `/api/2.0/fs/files/Volumes/${cat}/${sch}/${vol}/${p.filePath}`);
116
+ return { success: true };
117
+ } });
118
+ rl.registerAction("files.getFileInfo", { description: "Get file metadata (content-length, type, last-modified)",
119
+ inputSchema: { volumePath: { type: "string", required: true, description: "catalog.schema.volume" }, filePath: { type: "string", required: true } },
120
+ async execute(input, ctx) {
121
+ const { host, token } = getConn(ctx);
122
+ const p = input;
123
+ const [cat, sch, vol] = volumeParts(p.volumePath);
124
+ const url = `${host}/api/2.0/fs/files/Volumes/${cat}/${sch}/${vol}/${p.filePath}`;
125
+ const res = await fetch(url, { method: "HEAD", headers: { Authorization: `Bearer ${token}` } });
126
+ if (!res.ok)
127
+ throw new Error(`Databricks error ${res.status}`);
128
+ return { filePath: p.filePath, contentLength: res.headers.get("content-length"), contentType: res.headers.get("content-type"), lastModified: res.headers.get("last-modified") };
129
+ } });
130
+ rl.registerAction("files.listDirectory", { description: "List files in a volume directory",
131
+ inputSchema: { volumePath: { type: "string", required: true, description: "catalog.schema.volume" }, directoryPath: { type: "string", required: false }, pageSize: { type: "number", required: false }, pageToken: { type: "string", required: false } },
132
+ async execute(input, ctx) {
133
+ const { host, token } = getConn(ctx);
134
+ const p = input;
135
+ const [cat, sch, vol] = volumeParts(p.volumePath);
136
+ const dir = p.directoryPath ? `/${p.directoryPath}` : "";
137
+ const qs = {};
138
+ if (p.pageSize)
139
+ qs.page_size = String(p.pageSize);
140
+ if (p.pageToken)
141
+ qs.page_token = p.pageToken;
142
+ return api(host, token, "GET", `/api/2.0/fs/directories/Volumes/${cat}/${sch}/${vol}${dir}`, undefined, qs);
143
+ } });
144
+ // ── Genie ───────────────────────────────────────────
145
+ rl.registerAction("genie.startConversation", { description: "Start a new Genie conversation",
146
+ inputSchema: { spaceId: { type: "string", required: true }, initialMessage: { type: "string", required: true } },
147
+ async execute(input, ctx) {
148
+ const { host, token } = getConn(ctx);
149
+ const p = input;
150
+ return api(host, token, "POST", `/api/2.0/genie/spaces/${p.spaceId}/start-conversation`, { content: p.initialMessage });
151
+ } });
152
+ rl.registerAction("genie.createMessage", { description: "Send a message in an existing Genie conversation",
153
+ inputSchema: { spaceId: { type: "string", required: true }, conversationId: { type: "string", required: true }, message: { type: "string", required: true } },
154
+ async execute(input, ctx) {
155
+ const { host, token } = getConn(ctx);
156
+ const p = input;
157
+ return api(host, token, "POST", `/api/2.0/genie/spaces/${p.spaceId}/conversations/${p.conversationId}/messages`, { content: p.message });
158
+ } });
159
+ rl.registerAction("genie.getMessage", { description: "Get a specific message from a conversation",
160
+ inputSchema: { spaceId: { type: "string", required: true }, conversationId: { type: "string", required: true }, messageId: { type: "string", required: true } },
161
+ async execute(input, ctx) {
162
+ const { host, token } = getConn(ctx);
163
+ const p = input;
164
+ return api(host, token, "GET", `/api/2.0/genie/spaces/${p.spaceId}/conversations/${p.conversationId}/messages/${p.messageId}`);
165
+ } });
166
+ rl.registerAction("genie.getQueryResults", { description: "Get query results from a message attachment",
167
+ inputSchema: { spaceId: { type: "string", required: true }, conversationId: { type: "string", required: true }, messageId: { type: "string", required: true }, attachmentId: { type: "string", required: true } },
168
+ async execute(input, ctx) {
169
+ const { host, token } = getConn(ctx);
170
+ const p = input;
171
+ return api(host, token, "GET", `/api/2.0/genie/spaces/${p.spaceId}/conversations/${p.conversationId}/messages/${p.messageId}/attachments/${p.attachmentId}/query-result`);
172
+ } });
173
+ rl.registerAction("genie.executeMessageQuery", { description: "Execute a query from a message attachment",
174
+ inputSchema: { spaceId: { type: "string", required: true }, conversationId: { type: "string", required: true }, messageId: { type: "string", required: true }, attachmentId: { type: "string", required: true } },
175
+ async execute(input, ctx) {
176
+ const { host, token } = getConn(ctx);
177
+ const p = input;
178
+ return api(host, token, "POST", `/api/2.0/genie/spaces/${p.spaceId}/conversations/${p.conversationId}/messages/${p.messageId}/attachments/${p.attachmentId}/execute-query`);
179
+ } });
180
+ rl.registerAction("genie.getSpace", { description: "Get a Genie space",
181
+ inputSchema: { spaceId: { type: "string", required: true } },
182
+ async execute(input, ctx) {
183
+ const { host, token } = getConn(ctx);
184
+ return api(host, token, "GET", `/api/2.0/genie/spaces/${input.spaceId}`);
185
+ } });
186
+ // ── Model Serving ───────────────────────────────────
187
+ rl.registerAction("modelServing.queryEndpoint", { description: "Query a model serving endpoint",
188
+ inputSchema: { endpointName: { type: "string", required: true }, requestBody: { type: "object", required: true, description: "JSON body matching the endpoint schema (chat, completions, embeddings, dataframe, etc.)" } },
189
+ async execute(input, ctx) {
190
+ const { host, token } = getConn(ctx);
191
+ const p = input;
192
+ const body = p.requestBody;
193
+ return api(host, token, "POST", `/serving-endpoints/${p.endpointName}/invocations`, body);
194
+ } });
195
+ // ── Unity Catalog: Catalogs ─────────────────────────
196
+ rl.registerAction("catalog.create", { description: "Create a Unity Catalog catalog",
197
+ inputSchema: { name: { type: "string", required: true }, comment: { type: "string", required: false } },
198
+ async execute(input, ctx) {
199
+ const { host, token } = getConn(ctx);
200
+ const p = input;
201
+ const body = { name: p.name };
202
+ if (p.comment)
203
+ body.comment = p.comment;
204
+ return api(host, token, "POST", "/api/2.1/unity-catalog/catalogs", body);
205
+ } });
206
+ rl.registerAction("catalog.get", { description: "Get a catalog", inputSchema: { name: { type: "string", required: true } },
207
+ async execute(input, ctx) { const { host, token } = getConn(ctx); return api(host, token, "GET", `/api/2.1/unity-catalog/catalogs/${input.name}`); } });
208
+ rl.registerAction("catalog.list", { description: "List all catalogs", inputSchema: {},
209
+ async execute(_input, ctx) { const { host, token } = getConn(ctx); return api(host, token, "GET", "/api/2.1/unity-catalog/catalogs"); } });
210
+ rl.registerAction("catalog.update", { description: "Update a catalog's comment",
211
+ inputSchema: { name: { type: "string", required: true }, comment: { type: "string", required: true } },
212
+ async execute(input, ctx) { const { host, token } = getConn(ctx); const p = input; return api(host, token, "PATCH", `/api/2.1/unity-catalog/catalogs/${p.name}`, { comment: p.comment }); } });
213
+ rl.registerAction("catalog.delete", { description: "Delete a catalog", inputSchema: { name: { type: "string", required: true } },
214
+ async execute(input, ctx) { const { host, token } = getConn(ctx); await api(host, token, "DELETE", `/api/2.1/unity-catalog/catalogs/${input.name}`); return { success: true }; } });
215
+ // ── Unity Catalog: Tables ───────────────────────────
216
+ rl.registerAction("table.create", { description: "Create an external Delta table",
217
+ inputSchema: { catalogName: { type: "string", required: true }, schemaName: { type: "string", required: true }, tableName: { type: "string", required: true }, storageLocation: { type: "string", required: true }, columns: { type: "object", required: false, description: "JSON array of column defs" }, comment: { type: "string", required: false } },
218
+ async execute(input, ctx) {
219
+ const { host, token } = getConn(ctx);
220
+ const p = input;
221
+ const body = { catalog_name: p.catalogName, schema_name: p.schemaName, name: p.tableName, table_type: "EXTERNAL", data_source_format: "DELTA", storage_location: p.storageLocation };
222
+ if (p.columns)
223
+ body.columns = p.columns;
224
+ if (p.comment)
225
+ body.comment = p.comment;
226
+ return api(host, token, "POST", "/api/2.1/unity-catalog/tables", body);
227
+ } });
228
+ rl.registerAction("table.get", { description: "Get table info", inputSchema: { fullName: { type: "string", required: true, description: "catalog.schema.table" } },
229
+ async execute(input, ctx) { const { host, token } = getConn(ctx); return api(host, token, "GET", `/api/2.1/unity-catalog/tables/${input.fullName}`); } });
230
+ rl.registerAction("table.list", { description: "List tables",
231
+ inputSchema: { catalogName: { type: "string", required: false }, schemaName: { type: "string", required: false } },
232
+ async execute(input, ctx) {
233
+ const { host, token } = getConn(ctx);
234
+ const p = (input ?? {});
235
+ const qs = {};
236
+ if (p.catalogName)
237
+ qs.catalog_name = p.catalogName;
238
+ if (p.schemaName)
239
+ qs.schema_name = p.schemaName;
240
+ return api(host, token, "GET", "/api/2.1/unity-catalog/tables", undefined, qs);
241
+ } });
242
+ rl.registerAction("table.delete", { description: "Delete a table", inputSchema: { fullName: { type: "string", required: true, description: "catalog.schema.table" } },
243
+ async execute(input, ctx) { const { host, token } = getConn(ctx); await api(host, token, "DELETE", `/api/2.1/unity-catalog/tables/${input.fullName}`); return { success: true }; } });
244
+ // ── Unity Catalog: Volumes ──────────────────────────
245
+ rl.registerAction("volume.create", { description: "Create a Unity Catalog volume",
246
+ inputSchema: { catalogName: { type: "string", required: true }, schemaName: { type: "string", required: true }, volumeName: { type: "string", required: true }, volumeType: { type: "string", required: true, description: "MANAGED or EXTERNAL" }, storageLocation: { type: "string", required: false, description: "Required for EXTERNAL volumes" }, comment: { type: "string", required: false } },
247
+ async execute(input, ctx) {
248
+ const { host, token } = getConn(ctx);
249
+ const p = input;
250
+ if (p.volumeType === "EXTERNAL" && !p.storageLocation)
251
+ throw new Error("storageLocation required for EXTERNAL volumes");
252
+ const body = { catalog_name: p.catalogName, schema_name: p.schemaName, name: p.volumeName, volume_type: p.volumeType };
253
+ if (p.storageLocation)
254
+ body.storage_location = p.storageLocation;
255
+ if (p.comment)
256
+ body.comment = p.comment;
257
+ return api(host, token, "POST", "/api/2.1/unity-catalog/volumes", body);
258
+ } });
259
+ rl.registerAction("volume.get", { description: "Get a volume",
260
+ inputSchema: { catalogName: { type: "string", required: true }, schemaName: { type: "string", required: true }, volumeName: { type: "string", required: true } },
261
+ async execute(input, ctx) { const { host, token } = getConn(ctx); const p = input; return api(host, token, "GET", `/api/2.1/unity-catalog/volumes/${p.catalogName}.${p.schemaName}.${p.volumeName}`); } });
262
+ rl.registerAction("volume.list", { description: "List volumes",
263
+ inputSchema: { catalogName: { type: "string", required: false }, schemaName: { type: "string", required: false } },
264
+ async execute(input, ctx) {
265
+ const { host, token } = getConn(ctx);
266
+ const p = (input ?? {});
267
+ const qs = {};
268
+ if (p.catalogName)
269
+ qs.catalog_name = p.catalogName;
270
+ if (p.schemaName)
271
+ qs.schema_name = p.schemaName;
272
+ return api(host, token, "GET", "/api/2.1/unity-catalog/volumes", undefined, qs);
273
+ } });
274
+ rl.registerAction("volume.delete", { description: "Delete a volume",
275
+ inputSchema: { catalogName: { type: "string", required: true }, schemaName: { type: "string", required: true }, volumeName: { type: "string", required: true } },
276
+ async execute(input, ctx) { const { host, token } = getConn(ctx); const p = input; await api(host, token, "DELETE", `/api/2.1/unity-catalog/volumes/${p.catalogName}.${p.schemaName}.${p.volumeName}`); return { success: true }; } });
277
+ // ── Unity Catalog: Functions ────────────────────────
278
+ rl.registerAction("function.create", { description: "Create a Unity Catalog function",
279
+ inputSchema: { catalogName: { type: "string", required: true }, schemaName: { type: "string", required: true }, functionName: { type: "string", required: true }, inputParams: { type: "object", required: true, description: "Array of {name, type_name, type_text?} parameter definitions" }, returnType: { type: "string", required: true, description: "e.g. STRING, INT" }, routineBody: { type: "string", required: true, description: "SQL or EXTERNAL" }, routineDefinition: { type: "string", required: true, description: "The function body" } },
280
+ async execute(input, ctx) {
281
+ const { host, token } = getConn(ctx);
282
+ const p = input;
283
+ const params = (Array.isArray(p.inputParams) ? p.inputParams : p.inputParams?.parameters ?? []);
284
+ const normalizedParams = params.map((param) => ({ ...param, type_text: param.type_text ?? param.type_name, type_json: param.type_json ?? JSON.stringify({ name: param.type_name }) }));
285
+ return api(host, token, "POST", "/api/2.1/unity-catalog/functions", {
286
+ function_info: { name: p.functionName, catalog_name: p.catalogName, schema_name: p.schemaName, input_params: { parameters: normalizedParams }, data_type: p.returnType, full_data_type: p.returnType, specific_name: p.functionName, parameter_style: "S", security_type: "DEFINER", sql_data_access: "CONTAINS_SQL", is_deterministic: false, is_null_call: true, routine_body: p.routineBody, routine_definition: p.routineDefinition },
287
+ });
288
+ } });
289
+ rl.registerAction("function.get", { description: "Get a function", inputSchema: { fullName: { type: "string", required: true, description: "catalog.schema.function_name" } },
290
+ async execute(input, ctx) { const { host, token } = getConn(ctx); return api(host, token, "GET", `/api/2.1/unity-catalog/functions/${input.fullName}`); } });
291
+ rl.registerAction("function.list", { description: "List functions",
292
+ inputSchema: { catalogName: { type: "string", required: false }, schemaName: { type: "string", required: false } },
293
+ async execute(input, ctx) {
294
+ const { host, token } = getConn(ctx);
295
+ const p = (input ?? {});
296
+ const qs = {};
297
+ if (p.catalogName)
298
+ qs.catalog_name = p.catalogName;
299
+ if (p.schemaName)
300
+ qs.schema_name = p.schemaName;
301
+ return api(host, token, "GET", "/api/2.1/unity-catalog/functions", undefined, qs);
302
+ } });
303
+ rl.registerAction("function.delete", { description: "Delete a function", inputSchema: { fullName: { type: "string", required: true, description: "catalog.schema.function_name" } },
304
+ async execute(input, ctx) { const { host, token } = getConn(ctx); await api(host, token, "DELETE", `/api/2.1/unity-catalog/functions/${input.fullName}`); return { success: true }; } });
305
+ // ── Vector Search ───────────────────────────────────
306
+ rl.registerAction("vectorSearch.createIndex", { description: "Create a vector search index",
307
+ inputSchema: { indexName: { type: "string", required: true }, endpointName: { type: "string", required: true }, primaryKey: { type: "string", required: true }, indexType: { type: "string", required: true, description: "DELTA_SYNC or DIRECT_ACCESS" }, deltaSyncIndexSpec: { type: "object", required: false, description: "Spec for DELTA_SYNC type" }, directAccessIndexSpec: { type: "object", required: false, description: "Spec for DIRECT_ACCESS type" } },
308
+ async execute(input, ctx) {
309
+ const { host, token } = getConn(ctx);
310
+ const p = input;
311
+ const body = { name: p.indexName, endpoint_name: p.endpointName, primary_key: p.primaryKey, index_type: p.indexType };
312
+ if (p.indexType === "DELTA_SYNC" && p.deltaSyncIndexSpec)
313
+ body.delta_sync_index_spec = p.deltaSyncIndexSpec;
314
+ if (p.indexType === "DIRECT_ACCESS" && p.directAccessIndexSpec)
315
+ body.direct_access_index_spec = p.directAccessIndexSpec;
316
+ return api(host, token, "POST", "/api/2.0/vector-search/indexes", body);
317
+ } });
318
+ rl.registerAction("vectorSearch.getIndex", { description: "Get a vector search index", inputSchema: { indexName: { type: "string", required: true } },
319
+ async execute(input, ctx) { const { host, token } = getConn(ctx); return api(host, token, "GET", `/api/2.0/vector-search/indexes/${input.indexName}`); } });
320
+ rl.registerAction("vectorSearch.listIndexes", { description: "List vector search indexes for an endpoint", inputSchema: { endpointName: { type: "string", required: true } },
321
+ async execute(input, ctx) { const { host, token } = getConn(ctx); return api(host, token, "GET", "/api/2.0/vector-search/indexes", undefined, { endpoint_name: input.endpointName }); } });
322
+ rl.registerAction("vectorSearch.queryIndex", { description: "Query a vector search index",
323
+ inputSchema: { indexName: { type: "string", required: true }, queryType: { type: "string", required: true, description: "text or vector" }, queryText: { type: "string", required: false, description: "For text queries" }, queryVector: { type: "object", required: false, description: "For vector queries — array of numbers" }, numResults: { type: "number", required: false }, columns: { type: "string", required: true, description: "Comma-separated column names to return" }, searchMode: { type: "string", required: false, description: "HYBRID (default), ANN, or EXACT" }, filterExpression: { type: "string", required: false }, scoreThreshold: { type: "number", required: false }, enableReranking: { type: "boolean", required: false }, rerankerModel: { type: "string", required: false }, columnsToRerank: { type: "string", required: false } },
324
+ async execute(input, ctx) {
325
+ const { host, token } = getConn(ctx);
326
+ const p = input;
327
+ const body = { num_results: p.numResults ?? 10, query_type: p.searchMode ?? "HYBRID" };
328
+ if (p.queryType === "text")
329
+ body.query_text = p.queryText;
330
+ else
331
+ body.query_vector = p.queryVector;
332
+ body.columns = p.columns.split(",").map((c) => c.trim()).filter(Boolean);
333
+ if (p.filterExpression)
334
+ body.filters_json = p.filterExpression;
335
+ if (p.scoreThreshold)
336
+ body.score_threshold = p.scoreThreshold;
337
+ if (p.enableReranking) {
338
+ body.reranker = { model: p.rerankerModel ?? "databricks_reranker", parameters: { columns_to_rerank: (p.columnsToRerank || "").split(",").map((c) => c.trim()).filter(Boolean) } };
339
+ }
340
+ return api(host, token, "POST", `/api/2.0/vector-search/indexes/${p.indexName}/query`, body);
341
+ } });
342
+ }
@@ -0,0 +1,87 @@
1
+ async function apiRequest(apiKey, isPro, method, endpoint, params) {
2
+ const baseUrl = isPro ? "https://api.deepl.com/v2" : "https://api-free.deepl.com/v2";
3
+ const url = new URL(`${baseUrl}${endpoint}`);
4
+ const opts = {
5
+ method,
6
+ headers: {
7
+ Authorization: `DeepL-Auth-Key ${apiKey}`,
8
+ "Content-Type": "application/x-www-form-urlencoded",
9
+ },
10
+ };
11
+ if (params && Object.keys(params).length > 0) {
12
+ if (method === "GET") {
13
+ for (const [k, v] of Object.entries(params)) {
14
+ if (v !== undefined && v !== null)
15
+ url.searchParams.set(k, String(v));
16
+ }
17
+ }
18
+ else {
19
+ opts.body = new URLSearchParams(Object.entries(params)
20
+ .filter(([, v]) => v !== undefined && v !== null)
21
+ .map(([k, v]) => [k, String(v)])).toString();
22
+ }
23
+ }
24
+ const res = await fetch(url.toString(), opts);
25
+ if (!res.ok)
26
+ throw new Error(`DeepL API error ${res.status}: ${await res.text()}`);
27
+ return res.json();
28
+ }
29
+ function getConn(ctx) {
30
+ const cfg = ctx.connection.config;
31
+ return {
32
+ apiKey: cfg.apiKey,
33
+ isPro: cfg.plan === "pro",
34
+ };
35
+ }
36
+ export default function deepl(rl) {
37
+ rl.setName("deepl");
38
+ rl.setVersion("0.1.0");
39
+ rl.setConnectionSchema({
40
+ apiKey: {
41
+ type: "string",
42
+ required: true,
43
+ description: "DeepL API authentication key",
44
+ env: "DEEPL_API_KEY",
45
+ },
46
+ plan: {
47
+ type: "string",
48
+ required: false,
49
+ description: "'free' (default) or 'pro'",
50
+ env: "DEEPL_PLAN",
51
+ default: "free",
52
+ },
53
+ });
54
+ rl.registerAction("language.translate", {
55
+ description: "Translate text to a target language",
56
+ inputSchema: {
57
+ text: { type: "string", required: true, description: "Text to translate" },
58
+ targetLang: { type: "string", required: true, description: "Target language code (e.g. DE, FR, ES, EN-US, EN-GB, JA, ZH)" },
59
+ sourceLang: { type: "string", required: false, description: "Source language code (auto-detected if omitted)" },
60
+ },
61
+ async execute(input, ctx) {
62
+ const { text, targetLang, sourceLang } = input;
63
+ const { apiKey, isPro } = getConn(ctx);
64
+ const params = {
65
+ text,
66
+ target_lang: targetLang,
67
+ };
68
+ if (sourceLang) {
69
+ params.source_lang = ["EN-GB", "EN-US"].includes(sourceLang) ? "EN" : sourceLang;
70
+ }
71
+ const data = (await apiRequest(apiKey, isPro, "POST", "/translate", params));
72
+ const translations = data.translations;
73
+ return translations?.[0] ?? data;
74
+ },
75
+ });
76
+ rl.registerAction("language.list", {
77
+ description: "List available target languages",
78
+ inputSchema: {
79
+ type: { type: "string", required: false, description: "'source' or 'target' (default: target)" },
80
+ },
81
+ async execute(input, ctx) {
82
+ const { type = "target" } = (input ?? {});
83
+ const { apiKey, isPro } = getConn(ctx);
84
+ return apiRequest(apiKey, isPro, "GET", "/languages", { type });
85
+ },
86
+ });
87
+ }