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,527 @@
1
+ const BASE_URL = "https://api.airtop.ai/api/v1";
2
+ async function apiRequest(apiKey, method, endpoint, body, qs) {
3
+ const url = new URL(endpoint.startsWith("http") ? endpoint : `${BASE_URL}${endpoint}`);
4
+ if (qs) {
5
+ for (const [k, v] of Object.entries(qs)) {
6
+ if (v !== undefined)
7
+ url.searchParams.set(k, String(v));
8
+ }
9
+ }
10
+ const opts = {
11
+ method,
12
+ headers: {
13
+ "Content-Type": "application/json",
14
+ Authorization: `Bearer ${apiKey}`,
15
+ },
16
+ };
17
+ if (body && Object.keys(body).length > 0 && method !== "GET" && method !== "DELETE") {
18
+ opts.body = JSON.stringify(body);
19
+ }
20
+ const res = await fetch(url.toString(), opts);
21
+ if (!res.ok) {
22
+ const text = await res.text();
23
+ throw new Error(`Airtop API error ${res.status}: ${text}`);
24
+ }
25
+ if (res.status === 204)
26
+ return { success: true };
27
+ return res.json();
28
+ }
29
+ async function pollUntil(apiKey, endpoint, statusField, targetStatuses, timeoutMs, intervalMs = 1000) {
30
+ const start = Date.now();
31
+ while (true) {
32
+ const data = (await apiRequest(apiKey, "GET", endpoint));
33
+ const nested = data.data;
34
+ const status = (nested?.[statusField] ?? data[statusField]);
35
+ if (targetStatuses.includes(status))
36
+ return data;
37
+ if (Date.now() - start > timeoutMs)
38
+ throw new Error("Timeout reached waiting for status change");
39
+ await new Promise((r) => setTimeout(r, intervalMs));
40
+ }
41
+ }
42
+ function getKey(ctx) {
43
+ return ctx.connection.config.apiKey;
44
+ }
45
+ export default function airtop(rl) {
46
+ rl.setName("airtop");
47
+ rl.setVersion("0.1.0");
48
+ rl.setConnectionSchema({
49
+ apiKey: {
50
+ type: "string",
51
+ required: true,
52
+ description: "Airtop API key",
53
+ env: "AIRTOP_API_KEY",
54
+ },
55
+ });
56
+ // ── Session ─────────────────────────────────────────
57
+ rl.registerAction("session.create", {
58
+ description: "Create a new browser session and wait until it's running",
59
+ inputSchema: {
60
+ profileName: { type: "string", required: false, description: "Browser profile name" },
61
+ timeoutMinutes: { type: "number", required: false, description: "Idle timeout in minutes (default 10)" },
62
+ proxy: { type: "boolean", required: false, description: "Enable Airtop proxy" },
63
+ proxyCountry: { type: "string", required: false, description: "Proxy country code (e.g. US)" },
64
+ record: { type: "boolean", required: false, description: "Record the session" },
65
+ solveCaptcha: { type: "boolean", required: false, description: "Auto-solve captchas" },
66
+ saveProfileOnTermination: { type: "boolean", required: false, description: "Save profile when session ends" },
67
+ },
68
+ async execute(input, ctx) {
69
+ const { profileName, timeoutMinutes = 10, proxy, proxyCountry, record, solveCaptcha, saveProfileOnTermination, } = (input ?? {});
70
+ const apiKey = getKey(ctx);
71
+ let proxyConfig = false;
72
+ if (proxy) {
73
+ proxyConfig = proxyCountry ? { country: proxyCountry, sticky: true } : true;
74
+ }
75
+ const body = {
76
+ configuration: {
77
+ profileName: profileName ?? "",
78
+ timeoutMinutes,
79
+ proxy: proxyConfig,
80
+ solveCaptcha: solveCaptcha ?? false,
81
+ record: record ?? false,
82
+ },
83
+ };
84
+ const response = (await apiRequest(apiKey, "POST", "/sessions", body));
85
+ const sessionId = response.data?.id;
86
+ if (!sessionId)
87
+ throw new Error("Failed to create session");
88
+ // Poll until running
89
+ await pollUntil(apiKey, `/sessions/${sessionId}`, "status", ["running"], 5 * 60 * 1000);
90
+ if (saveProfileOnTermination && profileName) {
91
+ await apiRequest(apiKey, "PUT", `/sessions/${sessionId}/save-profile-on-termination/${profileName}`);
92
+ }
93
+ return { sessionId, ...response };
94
+ },
95
+ });
96
+ rl.registerAction("session.terminate", {
97
+ description: "Terminate a browser session",
98
+ inputSchema: {
99
+ sessionId: { type: "string", required: true, description: "Session ID" },
100
+ },
101
+ async execute(input, ctx) {
102
+ const { sessionId } = input;
103
+ await apiRequest(getKey(ctx), "DELETE", `/sessions/${sessionId}`);
104
+ return { success: true };
105
+ },
106
+ });
107
+ rl.registerAction("session.save", {
108
+ description: "Save a browser profile on session termination",
109
+ inputSchema: {
110
+ sessionId: { type: "string", required: true, description: "Session ID" },
111
+ profileName: { type: "string", required: true, description: "Profile name to save" },
112
+ },
113
+ async execute(input, ctx) {
114
+ const { sessionId, profileName } = input;
115
+ const response = await apiRequest(getKey(ctx), "PUT", `/sessions/${sessionId}/save-profile-on-termination/${profileName}`);
116
+ return { sessionId, profileName, ...response };
117
+ },
118
+ });
119
+ rl.registerAction("session.waitForDownload", {
120
+ description: "Wait for a file download to become available in a session",
121
+ inputSchema: {
122
+ sessionId: { type: "string", required: true, description: "Session ID" },
123
+ timeoutSeconds: { type: "number", required: false, description: "Timeout in seconds (default 30)" },
124
+ },
125
+ async execute(input, ctx) {
126
+ const { sessionId, timeoutSeconds = 30 } = input;
127
+ // This relies on SSE which we can't do cleanly in a plugin action.
128
+ // Fall back to polling the files endpoint.
129
+ const apiKey = getKey(ctx);
130
+ const start = Date.now();
131
+ while (Date.now() - start < timeoutSeconds * 1000) {
132
+ const data = (await apiRequest(apiKey, "GET", "/files", undefined, { sessionIds: sessionId }));
133
+ const files = data.data?.files ?? [];
134
+ const available = files.find((f) => f.status === "available");
135
+ if (available)
136
+ return { sessionId, fileId: available.id, downloadUrl: available.downloadUrl };
137
+ await new Promise((r) => setTimeout(r, 1000));
138
+ }
139
+ throw new Error("Timeout waiting for download");
140
+ },
141
+ });
142
+ // ── Window ──────────────────────────────────────────
143
+ rl.registerAction("window.create", {
144
+ description: "Create a new browser window in a session",
145
+ inputSchema: {
146
+ sessionId: { type: "string", required: true, description: "Session ID" },
147
+ url: { type: "string", required: false, description: "Initial URL to load (default: google.com)" },
148
+ waitUntil: { type: "string", required: false, description: "Wait event: load, domContentLoaded, complete, noWait" },
149
+ },
150
+ async execute(input, ctx) {
151
+ const { sessionId, url, waitUntil } = input;
152
+ const body = {};
153
+ if (url)
154
+ body.url = url;
155
+ if (waitUntil)
156
+ body.waitUntil = waitUntil;
157
+ const response = (await apiRequest(getKey(ctx), "POST", `/sessions/${sessionId}/windows`, body));
158
+ const windowId = response.data?.windowId;
159
+ return { sessionId, windowId, ...response };
160
+ },
161
+ });
162
+ rl.registerAction("window.close", {
163
+ description: "Close a browser window",
164
+ inputSchema: {
165
+ sessionId: { type: "string", required: true, description: "Session ID" },
166
+ windowId: { type: "string", required: true, description: "Window ID" },
167
+ },
168
+ async execute(input, ctx) {
169
+ const { sessionId, windowId } = input;
170
+ const response = await apiRequest(getKey(ctx), "DELETE", `/sessions/${sessionId}/windows/${windowId}`);
171
+ return { sessionId, windowId, ...response };
172
+ },
173
+ });
174
+ rl.registerAction("window.load", {
175
+ description: "Navigate a window to a URL",
176
+ inputSchema: {
177
+ sessionId: { type: "string", required: true, description: "Session ID" },
178
+ windowId: { type: "string", required: true, description: "Window ID" },
179
+ url: { type: "string", required: true, description: "URL to navigate to" },
180
+ waitUntil: { type: "string", required: false, description: "Wait event: load, domContentLoaded, complete, noWait" },
181
+ },
182
+ async execute(input, ctx) {
183
+ const { sessionId, windowId, url, waitUntil } = input;
184
+ const body = { url };
185
+ if (waitUntil)
186
+ body.waitUntil = waitUntil;
187
+ return apiRequest(getKey(ctx), "POST", `/sessions/${sessionId}/windows/${windowId}`, body);
188
+ },
189
+ });
190
+ rl.registerAction("window.list", {
191
+ description: "List all windows in a session",
192
+ inputSchema: {
193
+ sessionId: { type: "string", required: true, description: "Session ID" },
194
+ },
195
+ async execute(input, ctx) {
196
+ const { sessionId } = input;
197
+ return apiRequest(getKey(ctx), "GET", `/sessions/${sessionId}/windows`);
198
+ },
199
+ });
200
+ rl.registerAction("window.getLiveView", {
201
+ description: "Get the live view URL for a window",
202
+ inputSchema: {
203
+ sessionId: { type: "string", required: true, description: "Session ID" },
204
+ windowId: { type: "string", required: true, description: "Window ID" },
205
+ includeNavigationBar: { type: "boolean", required: false, description: "Show nav bar in live view" },
206
+ screenResolution: { type: "string", required: false, description: "Screen resolution (e.g. 1280x720)" },
207
+ disableResize: { type: "boolean", required: false, description: "Disable window resize" },
208
+ },
209
+ async execute(input, ctx) {
210
+ const { sessionId, windowId, includeNavigationBar, screenResolution, disableResize } = input;
211
+ const qs = {};
212
+ if (includeNavigationBar)
213
+ qs.includeNavigationBar = true;
214
+ if (screenResolution)
215
+ qs.screenResolution = screenResolution;
216
+ if (disableResize)
217
+ qs.disableResize = true;
218
+ return apiRequest(getKey(ctx), "GET", `/sessions/${sessionId}/windows/${windowId}`, undefined, qs);
219
+ },
220
+ });
221
+ rl.registerAction("window.takeScreenshot", {
222
+ description: "Take a screenshot of a window (returns base64 data URL)",
223
+ inputSchema: {
224
+ sessionId: { type: "string", required: true, description: "Session ID" },
225
+ windowId: { type: "string", required: true, description: "Window ID" },
226
+ },
227
+ async execute(input, ctx) {
228
+ const { sessionId, windowId } = input;
229
+ return apiRequest(getKey(ctx), "POST", `/sessions/${sessionId}/windows/${windowId}/screenshot`);
230
+ },
231
+ });
232
+ // ── Extraction ──────────────────────────────────────
233
+ rl.registerAction("extraction.query", {
234
+ description: "Query page content using a natural language prompt",
235
+ inputSchema: {
236
+ sessionId: { type: "string", required: true, description: "Session ID" },
237
+ windowId: { type: "string", required: true, description: "Window ID" },
238
+ prompt: { type: "string", required: true, description: "Natural language prompt to query the page" },
239
+ outputSchema: { type: "string", required: false, description: "JSON schema for structured output" },
240
+ includeVisualAnalysis: { type: "boolean", required: false, description: "Analyze page visually" },
241
+ },
242
+ async execute(input, ctx) {
243
+ const { sessionId, windowId, prompt, outputSchema, includeVisualAnalysis } = input;
244
+ const body = {
245
+ prompt,
246
+ configuration: {
247
+ experimental: {
248
+ includeVisualAnalysis: includeVisualAnalysis ? "enabled" : "disabled",
249
+ },
250
+ ...(outputSchema ? { outputSchema } : {}),
251
+ },
252
+ };
253
+ return apiRequest(getKey(ctx), "POST", `/sessions/${sessionId}/windows/${windowId}/page-query`, body);
254
+ },
255
+ });
256
+ rl.registerAction("extraction.scrape", {
257
+ description: "Scrape the content of the current page",
258
+ inputSchema: {
259
+ sessionId: { type: "string", required: true, description: "Session ID" },
260
+ windowId: { type: "string", required: true, description: "Window ID" },
261
+ },
262
+ async execute(input, ctx) {
263
+ const { sessionId, windowId } = input;
264
+ return apiRequest(getKey(ctx), "POST", `/sessions/${sessionId}/windows/${windowId}/scrape-content`, {});
265
+ },
266
+ });
267
+ rl.registerAction("extraction.getPaginated", {
268
+ description: "Extract data across paginated pages using a prompt",
269
+ inputSchema: {
270
+ sessionId: { type: "string", required: true, description: "Session ID" },
271
+ windowId: { type: "string", required: true, description: "Window ID" },
272
+ prompt: { type: "string", required: true, description: "Prompt describing what to extract" },
273
+ outputSchema: { type: "string", required: false, description: "JSON schema for structured output" },
274
+ paginationMode: { type: "string", required: false, description: "auto, paginated, or infinite-scroll" },
275
+ interactionMode: { type: "string", required: false, description: "auto, accurate, or cost-efficient" },
276
+ },
277
+ async execute(input, ctx) {
278
+ const { sessionId, windowId, prompt, outputSchema, paginationMode, interactionMode } = input;
279
+ const configuration = {};
280
+ if (outputSchema)
281
+ configuration.outputSchema = outputSchema;
282
+ if (paginationMode)
283
+ configuration.paginationMode = paginationMode;
284
+ if (interactionMode)
285
+ configuration.interactionMode = interactionMode;
286
+ return apiRequest(getKey(ctx), "POST", `/sessions/${sessionId}/windows/${windowId}/paginated-extraction`, { prompt, configuration });
287
+ },
288
+ });
289
+ // ── Interaction ─────────────────────────────────────
290
+ rl.registerAction("interaction.click", {
291
+ description: "Click on an element described in natural language",
292
+ inputSchema: {
293
+ sessionId: { type: "string", required: true, description: "Session ID" },
294
+ windowId: { type: "string", required: true, description: "Window ID" },
295
+ elementDescription: { type: "string", required: true, description: "Natural language description of the element to click" },
296
+ clickType: { type: "string", required: false, description: "click, doubleClick, or rightClick (default: click)" },
297
+ },
298
+ async execute(input, ctx) {
299
+ const { sessionId, windowId, elementDescription, clickType = "click" } = input;
300
+ return apiRequest(getKey(ctx), "POST", `/sessions/${sessionId}/windows/${windowId}/click`, {
301
+ elementDescription,
302
+ configuration: { clickType },
303
+ });
304
+ },
305
+ });
306
+ rl.registerAction("interaction.hover", {
307
+ description: "Hover over an element described in natural language",
308
+ inputSchema: {
309
+ sessionId: { type: "string", required: true, description: "Session ID" },
310
+ windowId: { type: "string", required: true, description: "Window ID" },
311
+ elementDescription: { type: "string", required: true, description: "Natural language description of the element to hover" },
312
+ },
313
+ async execute(input, ctx) {
314
+ const { sessionId, windowId, elementDescription } = input;
315
+ return apiRequest(getKey(ctx), "POST", `/sessions/${sessionId}/windows/${windowId}/hover`, {
316
+ elementDescription,
317
+ });
318
+ },
319
+ });
320
+ rl.registerAction("interaction.type", {
321
+ description: "Type text into a browser window, optionally targeting a specific element",
322
+ inputSchema: {
323
+ sessionId: { type: "string", required: true, description: "Session ID" },
324
+ windowId: { type: "string", required: true, description: "Window ID" },
325
+ text: { type: "string", required: true, description: "Text to type" },
326
+ elementDescription: { type: "string", required: false, description: "Element to type into" },
327
+ pressEnterKey: { type: "boolean", required: false, description: "Press Enter after typing" },
328
+ },
329
+ async execute(input, ctx) {
330
+ const { sessionId, windowId, text, elementDescription, pressEnterKey } = input;
331
+ const body = { text };
332
+ if (elementDescription)
333
+ body.elementDescription = elementDescription;
334
+ if (pressEnterKey)
335
+ body.pressEnterKey = true;
336
+ return apiRequest(getKey(ctx), "POST", `/sessions/${sessionId}/windows/${windowId}/type`, body);
337
+ },
338
+ });
339
+ rl.registerAction("interaction.fill", {
340
+ description: "Fill a form using natural language description of the data",
341
+ inputSchema: {
342
+ sessionId: { type: "string", required: true, description: "Session ID" },
343
+ windowId: { type: "string", required: true, description: "Window ID" },
344
+ formData: { type: "string", required: true, description: "Form data in natural language (e.g. 'Name: John, Email: john@example.com')" },
345
+ },
346
+ async execute(input, ctx) {
347
+ const { sessionId, windowId, formData } = input;
348
+ const apiKey = getKey(ctx);
349
+ // Start async automation
350
+ const asyncResponse = (await apiRequest(apiKey, "POST", `/async/sessions/${sessionId}/windows/${windowId}/execute-automation`, { automationId: "auto", parameters: { customData: formData } }));
351
+ const reqId = asyncResponse.requestId;
352
+ if (!reqId)
353
+ throw new Error("No requestId received from automation");
354
+ // Poll until completed
355
+ const result = await pollUntil(apiKey, `/requests/${reqId}/status`, "status", ["completed", "error"], 5 * 60 * 1000);
356
+ return { sessionId, windowId, ...result };
357
+ },
358
+ });
359
+ rl.registerAction("interaction.scroll", {
360
+ description: "Scroll within a browser window",
361
+ inputSchema: {
362
+ sessionId: { type: "string", required: true, description: "Session ID" },
363
+ windowId: { type: "string", required: true, description: "Window ID" },
364
+ scrollToElement: { type: "string", required: false, description: "Natural language description of element to scroll to (automatic mode)" },
365
+ scrollToEdge: { type: "object", required: false, description: "{ xAxis?: 'left'|'right', yAxis?: 'top'|'bottom' }" },
366
+ scrollBy: { type: "object", required: false, description: "{ xAxis?: '100px'|'50%', yAxis?: '200px'|'-100px' }" },
367
+ scrollWithin: { type: "string", required: false, description: "Natural language description of scrollable area" },
368
+ },
369
+ async execute(input, ctx) {
370
+ const { sessionId, windowId, scrollToElement, scrollToEdge, scrollBy, scrollWithin } = input;
371
+ const body = {};
372
+ if (scrollToElement)
373
+ body.scrollToElement = scrollToElement;
374
+ if (scrollToEdge)
375
+ body.scrollToEdge = scrollToEdge;
376
+ if (scrollBy)
377
+ body.scrollBy = scrollBy;
378
+ if (scrollWithin)
379
+ body.scrollWithin = scrollWithin;
380
+ return apiRequest(getKey(ctx), "POST", `/sessions/${sessionId}/windows/${windowId}/scroll`, body);
381
+ },
382
+ });
383
+ // ── Agent ───────────────────────────────────────────
384
+ rl.registerAction("agent.run", {
385
+ description: "Run an Airtop agent and optionally wait for completion",
386
+ inputSchema: {
387
+ agentId: { type: "string", required: true, description: "Agent ID" },
388
+ parameters: { type: "object", required: false, description: "Agent input parameters" },
389
+ awaitExecution: { type: "boolean", required: false, description: "Wait for agent to complete (default: true)" },
390
+ timeoutSeconds: { type: "number", required: false, description: "Timeout in seconds (default: 600)" },
391
+ },
392
+ async execute(input, ctx) {
393
+ const { agentId, parameters, awaitExecution = true, timeoutSeconds = 600 } = input;
394
+ const apiKey = getKey(ctx);
395
+ const HOOKS_BASE = "https://api.airtop.ai/api/hooks";
396
+ // Get agent details for webhook ID
397
+ const agentDetails = (await apiRequest(apiKey, "GET", `/agents/${agentId}`));
398
+ const data = agentDetails.data;
399
+ const webhookId = data?.webhookId;
400
+ if (!webhookId)
401
+ throw new Error("No webhookId found for agent");
402
+ // Invoke agent
403
+ const invokeUrl = `${HOOKS_BASE}/agents/${agentId}/webhooks/${webhookId}`;
404
+ const invocation = (await apiRequest(apiKey, "POST", invokeUrl, (parameters ?? {})));
405
+ const invocationId = invocation.invocationId;
406
+ if (!invocationId)
407
+ throw new Error("No invocationId received");
408
+ if (!awaitExecution) {
409
+ return { invocationId };
410
+ }
411
+ // Poll for completion
412
+ const start = Date.now();
413
+ while (true) {
414
+ const status = (await apiRequest(apiKey, "GET", `/agents/${agentId}/invocations/${invocationId}`));
415
+ const invData = status.data;
416
+ const s = (invData?.status ?? status.status);
417
+ if (s === "completed" || s === "error") {
418
+ if (invData?.error)
419
+ throw new Error(`Agent error: ${invData.error}`);
420
+ return { invocationId, status: s, output: invData?.output ?? {} };
421
+ }
422
+ if (Date.now() - start > timeoutSeconds * 1000) {
423
+ throw new Error("Timeout waiting for agent completion");
424
+ }
425
+ await new Promise((r) => setTimeout(r, 2000));
426
+ }
427
+ },
428
+ });
429
+ // ── File ────────────────────────────────────────────
430
+ rl.registerAction("file.get", {
431
+ description: "Get file details by ID",
432
+ inputSchema: {
433
+ fileId: { type: "string", required: true, description: "File ID" },
434
+ },
435
+ async execute(input, ctx) {
436
+ const { fileId } = input;
437
+ return apiRequest(getKey(ctx), "GET", `/files/${fileId}`);
438
+ },
439
+ });
440
+ rl.registerAction("file.list", {
441
+ description: "List files, optionally filtered by session",
442
+ inputSchema: {
443
+ sessionIds: { type: "string", required: false, description: "Comma-separated session IDs to filter by" },
444
+ limit: { type: "number", required: false, description: "Max results to return" },
445
+ },
446
+ async execute(input, ctx) {
447
+ const { sessionIds, limit } = (input ?? {});
448
+ const qs = {};
449
+ if (sessionIds)
450
+ qs.sessionIds = sessionIds;
451
+ if (limit)
452
+ qs.limit = limit;
453
+ return apiRequest(getKey(ctx), "GET", "/files", undefined, qs);
454
+ },
455
+ });
456
+ rl.registerAction("file.delete", {
457
+ description: "Delete a file by ID",
458
+ inputSchema: {
459
+ fileId: { type: "string", required: true, description: "File ID" },
460
+ },
461
+ async execute(input, ctx) {
462
+ const { fileId } = input;
463
+ await apiRequest(getKey(ctx), "DELETE", `/files/${fileId}`);
464
+ return { success: true };
465
+ },
466
+ });
467
+ rl.registerAction("file.upload", {
468
+ description: "Upload a file from a URL to a session",
469
+ inputSchema: {
470
+ sessionId: { type: "string", required: true, description: "Session ID" },
471
+ windowId: { type: "string", required: true, description: "Window ID" },
472
+ fileName: { type: "string", required: true, description: "File name (must be unique per session)" },
473
+ url: { type: "string", required: true, description: "URL to fetch the file from" },
474
+ fileType: { type: "string", required: false, description: "File type: customer_upload, browser_download, screenshot, video" },
475
+ triggerFileInput: { type: "boolean", required: false, description: "Trigger file input dialog (default: true)" },
476
+ elementDescription: { type: "string", required: false, description: "Description of file input element" },
477
+ },
478
+ async execute(input, ctx) {
479
+ const { sessionId, windowId, fileName, url, fileType = "customer_upload", triggerFileInput: triggerInput = true, elementDescription, } = input;
480
+ const apiKey = getKey(ctx);
481
+ // Fetch the file
482
+ const fileRes = await fetch(url);
483
+ if (!fileRes.ok)
484
+ throw new Error(`Failed to fetch file from ${url}: ${fileRes.status}`);
485
+ const fileBuffer = await fileRes.arrayBuffer();
486
+ const base64 = Buffer.from(fileBuffer).toString("base64");
487
+ // Create file
488
+ const createResponse = (await apiRequest(apiKey, "POST", "/files", {
489
+ fileName,
490
+ fileType,
491
+ content: base64,
492
+ }));
493
+ const fileId = createResponse.data?.id;
494
+ // Push to session
495
+ await apiRequest(apiKey, "POST", `/sessions/${sessionId}/files`, { fileId });
496
+ // Trigger file input if needed
497
+ if (triggerInput) {
498
+ const body = { fileId };
499
+ if (elementDescription)
500
+ body.elementDescription = elementDescription;
501
+ await apiRequest(apiKey, "POST", `/sessions/${sessionId}/windows/${windowId}/trigger-file-input`, body);
502
+ }
503
+ return { sessionId, windowId, fileId, success: true };
504
+ },
505
+ });
506
+ rl.registerAction("file.load", {
507
+ description: "Load an existing file into a session and trigger file input",
508
+ inputSchema: {
509
+ sessionId: { type: "string", required: true, description: "Session ID" },
510
+ windowId: { type: "string", required: true, description: "Window ID" },
511
+ fileId: { type: "string", required: true, description: "File ID to load" },
512
+ elementDescription: { type: "string", required: false, description: "Description of file input element" },
513
+ },
514
+ async execute(input, ctx) {
515
+ const { sessionId, windowId, fileId, elementDescription } = input;
516
+ const apiKey = getKey(ctx);
517
+ // Push to session
518
+ await apiRequest(apiKey, "POST", `/sessions/${sessionId}/files`, { fileId });
519
+ // Trigger file input
520
+ const body = { fileId };
521
+ if (elementDescription)
522
+ body.elementDescription = elementDescription;
523
+ await apiRequest(apiKey, "POST", `/sessions/${sessionId}/windows/${windowId}/trigger-file-input`, body);
524
+ return { sessionId, windowId, fileId, success: true };
525
+ },
526
+ });
527
+ }
@@ -0,0 +1,86 @@
1
+ const BASE_URL = "https://api.apitemplate.io/v1";
2
+ async function apiRequest(apiKey, method, endpoint, qs, body) {
3
+ const url = new URL(`${BASE_URL}${endpoint}`);
4
+ if (qs) {
5
+ for (const [k, v] of Object.entries(qs)) {
6
+ if (v !== undefined)
7
+ url.searchParams.set(k, String(v));
8
+ }
9
+ }
10
+ const opts = {
11
+ method,
12
+ headers: {
13
+ "Content-Type": "application/json",
14
+ "X-API-KEY": apiKey,
15
+ },
16
+ };
17
+ if (body && Object.keys(body).length > 0) {
18
+ opts.body = JSON.stringify(body);
19
+ }
20
+ const res = await fetch(url.toString(), opts);
21
+ if (!res.ok) {
22
+ const text = await res.text();
23
+ throw new Error(`APITemplate.io error ${res.status}: ${text}`);
24
+ }
25
+ return res.json();
26
+ }
27
+ function getKey(ctx) {
28
+ return ctx.connection.config.apiKey;
29
+ }
30
+ export default function apiTemplateIo(rl) {
31
+ rl.setName("apiTemplateIo");
32
+ rl.setVersion("0.1.0");
33
+ rl.setConnectionSchema({
34
+ apiKey: {
35
+ type: "string",
36
+ required: true,
37
+ description: "APITemplate.io API key",
38
+ env: "API_TEMPLATE_IO_API_KEY",
39
+ },
40
+ });
41
+ rl.registerAction("account.get", {
42
+ description: "Get account information",
43
+ async execute(_input, ctx) {
44
+ return apiRequest(getKey(ctx), "GET", "/account-information");
45
+ },
46
+ });
47
+ rl.registerAction("template.list", {
48
+ description: "List all templates",
49
+ inputSchema: {
50
+ format: { type: "string", required: false, description: "Filter by format: JPEG, PNG, or PDF" },
51
+ },
52
+ async execute(input, ctx) {
53
+ const { format } = (input ?? {});
54
+ const templates = (await apiRequest(getKey(ctx), "GET", "/list-templates"));
55
+ if (format) {
56
+ return templates.filter((t) => t.format === format.toUpperCase());
57
+ }
58
+ return templates;
59
+ },
60
+ });
61
+ rl.registerAction("image.create", {
62
+ description: "Create an image from a template",
63
+ inputSchema: {
64
+ templateId: { type: "string", required: true, description: "Image template ID" },
65
+ overrides: { type: "array", required: false, description: "Array of override objects with template field values" },
66
+ },
67
+ async execute(input, ctx) {
68
+ const { templateId, overrides } = input;
69
+ const body = {};
70
+ if (overrides)
71
+ body.overrides = overrides;
72
+ return apiRequest(getKey(ctx), "POST", "/create", { template_id: templateId }, body);
73
+ },
74
+ });
75
+ rl.registerAction("pdf.create", {
76
+ description: "Create a PDF from a template",
77
+ inputSchema: {
78
+ templateId: { type: "string", required: true, description: "PDF template ID" },
79
+ properties: { type: "object", required: true, description: "Template properties as key-value pairs" },
80
+ },
81
+ async execute(input, ctx) {
82
+ const { templateId, properties } = input;
83
+ return apiRequest(getKey(ctx), "POST", "/create", { template_id: templateId }, properties);
84
+ },
85
+ });
86
+ }