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,383 @@
1
+ let cachedToken = null;
2
+ async function getAccessToken(clientId, clientSecret, tokenUrl) {
3
+ if (cachedToken && Date.now() < cachedToken.expiresAt)
4
+ return cachedToken.value;
5
+ const res = await fetch(tokenUrl, {
6
+ method: "POST",
7
+ headers: { "Content-Type": "application/x-www-form-urlencoded" },
8
+ body: new URLSearchParams({
9
+ client_id: clientId,
10
+ client_secret: clientSecret,
11
+ grant_type: "client_credentials",
12
+ scope: "api.organization",
13
+ deviceName: "runline",
14
+ deviceType: "2",
15
+ deviceIdentifier: "runline",
16
+ }),
17
+ });
18
+ if (!res.ok)
19
+ throw new Error(`Bitwarden token error ${res.status}: ${await res.text()}`);
20
+ const data = (await res.json());
21
+ cachedToken = { value: data.access_token, expiresAt: Date.now() + (data.expires_in - 60) * 1000 };
22
+ return cachedToken.value;
23
+ }
24
+ async function apiRequest(token, baseUrl, method, endpoint, body, qs) {
25
+ const url = new URL(`${baseUrl}${endpoint}`);
26
+ if (qs) {
27
+ for (const [k, v] of Object.entries(qs)) {
28
+ if (v !== undefined && v !== null)
29
+ url.searchParams.set(k, String(v));
30
+ }
31
+ }
32
+ const opts = {
33
+ method,
34
+ headers: {
35
+ "Content-Type": "application/json",
36
+ Authorization: `Bearer ${token}`,
37
+ },
38
+ };
39
+ if (body && Object.keys(body).length > 0 && method !== "GET" && method !== "DELETE") {
40
+ opts.body = JSON.stringify(body);
41
+ }
42
+ const res = await fetch(url.toString(), opts);
43
+ if (!res.ok) {
44
+ const text = await res.text();
45
+ throw new Error(`Bitwarden API error ${res.status}: ${text}`);
46
+ }
47
+ if (res.status === 204 || res.headers.get("content-length") === "0")
48
+ return { success: true };
49
+ const ct = res.headers.get("content-type") ?? "";
50
+ if (ct.includes("application/json"))
51
+ return res.json();
52
+ return { success: true };
53
+ }
54
+ function getConn(ctx) {
55
+ const cfg = ctx.connection.config;
56
+ const domain = cfg.domain?.replace(/\/$/, "");
57
+ const env = cfg.environment;
58
+ const baseUrl = env === "selfHosted" && domain ? `${domain}/api` : "https://api.bitwarden.com";
59
+ const tokenUrl = env === "selfHosted" && domain
60
+ ? `${domain}/identity/connect/token`
61
+ : "https://identity.bitwarden.com/connect/token";
62
+ return {
63
+ clientId: cfg.clientId,
64
+ clientSecret: cfg.clientSecret,
65
+ baseUrl,
66
+ tokenUrl,
67
+ };
68
+ }
69
+ async function authedRequest(ctx, method, endpoint, body, qs) {
70
+ const { clientId, clientSecret, baseUrl, tokenUrl } = getConn(ctx);
71
+ const token = await getAccessToken(clientId, clientSecret, tokenUrl);
72
+ return apiRequest(token, baseUrl, method, endpoint, body, qs);
73
+ }
74
+ export default function bitwarden(rl) {
75
+ rl.setName("bitwarden");
76
+ rl.setVersion("0.1.0");
77
+ rl.setConnectionSchema({
78
+ clientId: {
79
+ type: "string",
80
+ required: true,
81
+ description: "Bitwarden API client ID",
82
+ env: "BITWARDEN_CLIENT_ID",
83
+ },
84
+ clientSecret: {
85
+ type: "string",
86
+ required: true,
87
+ description: "Bitwarden API client secret",
88
+ env: "BITWARDEN_CLIENT_SECRET",
89
+ },
90
+ environment: {
91
+ type: "string",
92
+ required: false,
93
+ description: "cloudHosted (default) or selfHosted",
94
+ env: "BITWARDEN_ENVIRONMENT",
95
+ default: "cloudHosted",
96
+ },
97
+ domain: {
98
+ type: "string",
99
+ required: false,
100
+ description: "Self-hosted domain URL (only if environment=selfHosted)",
101
+ env: "BITWARDEN_DOMAIN",
102
+ },
103
+ });
104
+ // ── Collection ──────────────────────────────────────
105
+ rl.registerAction("collection.get", {
106
+ description: "Get a collection by ID",
107
+ inputSchema: {
108
+ collectionId: { type: "string", required: true, description: "Collection ID" },
109
+ },
110
+ async execute(input, ctx) {
111
+ const { collectionId } = input;
112
+ return authedRequest(ctx, "GET", `/public/collections/${collectionId}`);
113
+ },
114
+ });
115
+ rl.registerAction("collection.list", {
116
+ description: "List all collections",
117
+ inputSchema: {
118
+ limit: { type: "number", required: false, description: "Max results" },
119
+ },
120
+ async execute(input, ctx) {
121
+ const { limit } = (input ?? {});
122
+ const data = (await authedRequest(ctx, "GET", "/public/collections"));
123
+ if (limit)
124
+ return data.data.slice(0, limit);
125
+ return data.data;
126
+ },
127
+ });
128
+ rl.registerAction("collection.update", {
129
+ description: "Update a collection",
130
+ inputSchema: {
131
+ collectionId: { type: "string", required: true, description: "Collection ID" },
132
+ groups: { type: "array", required: false, description: "Array of group IDs to assign" },
133
+ externalId: { type: "string", required: false, description: "External ID" },
134
+ },
135
+ async execute(input, ctx) {
136
+ const { collectionId, groups, externalId } = input;
137
+ const body = {};
138
+ if (groups) {
139
+ body.groups = groups.map((id) => ({ id, ReadOnly: false }));
140
+ }
141
+ if (externalId)
142
+ body.externalId = externalId;
143
+ return authedRequest(ctx, "PUT", `/public/collections/${collectionId}`, body);
144
+ },
145
+ });
146
+ rl.registerAction("collection.delete", {
147
+ description: "Delete a collection",
148
+ inputSchema: {
149
+ collectionId: { type: "string", required: true, description: "Collection ID" },
150
+ },
151
+ async execute(input, ctx) {
152
+ const { collectionId } = input;
153
+ await authedRequest(ctx, "DELETE", `/public/collections/${collectionId}`);
154
+ return { success: true };
155
+ },
156
+ });
157
+ // ── Event ───────────────────────────────────────────
158
+ rl.registerAction("event.list", {
159
+ description: "List organization events",
160
+ inputSchema: {
161
+ start: { type: "string", required: false, description: "Start date (ISO 8601)" },
162
+ end: { type: "string", required: false, description: "End date (ISO 8601)" },
163
+ actingUserId: { type: "string", required: false, description: "Filter by acting user ID" },
164
+ itemId: { type: "string", required: false, description: "Filter by item ID" },
165
+ limit: { type: "number", required: false, description: "Max results" },
166
+ },
167
+ async execute(input, ctx) {
168
+ const { limit, ...qs } = (input ?? {});
169
+ const data = (await authedRequest(ctx, "GET", "/public/events", undefined, qs));
170
+ if (limit)
171
+ return data.data.slice(0, limit);
172
+ return data.data;
173
+ },
174
+ });
175
+ // ── Group ───────────────────────────────────────────
176
+ rl.registerAction("group.create", {
177
+ description: "Create a group",
178
+ inputSchema: {
179
+ name: { type: "string", required: true, description: "Group name" },
180
+ accessAll: { type: "boolean", required: true, description: "Grant access to all collections" },
181
+ collections: { type: "array", required: false, description: "Array of collection IDs" },
182
+ externalId: { type: "string", required: false, description: "External ID" },
183
+ },
184
+ async execute(input, ctx) {
185
+ const { name, accessAll, collections, externalId } = input;
186
+ const body = { name, AccessAll: accessAll };
187
+ if (collections) {
188
+ body.collections = collections.map((id) => ({ id, ReadOnly: false }));
189
+ }
190
+ if (externalId)
191
+ body.externalId = externalId;
192
+ return authedRequest(ctx, "POST", "/public/groups", body);
193
+ },
194
+ });
195
+ rl.registerAction("group.get", {
196
+ description: "Get a group by ID",
197
+ inputSchema: {
198
+ groupId: { type: "string", required: true, description: "Group ID" },
199
+ },
200
+ async execute(input, ctx) {
201
+ const { groupId } = input;
202
+ return authedRequest(ctx, "GET", `/public/groups/${groupId}`);
203
+ },
204
+ });
205
+ rl.registerAction("group.list", {
206
+ description: "List all groups",
207
+ inputSchema: {
208
+ limit: { type: "number", required: false, description: "Max results" },
209
+ },
210
+ async execute(input, ctx) {
211
+ const { limit } = (input ?? {});
212
+ const data = (await authedRequest(ctx, "GET", "/public/groups"));
213
+ if (limit)
214
+ return data.data.slice(0, limit);
215
+ return data.data;
216
+ },
217
+ });
218
+ rl.registerAction("group.getMembers", {
219
+ description: "Get member IDs for a group",
220
+ inputSchema: {
221
+ groupId: { type: "string", required: true, description: "Group ID" },
222
+ },
223
+ async execute(input, ctx) {
224
+ const { groupId } = input;
225
+ const memberIds = (await authedRequest(ctx, "GET", `/public/groups/${groupId}/member-ids`));
226
+ return memberIds.map((memberId) => ({ memberId }));
227
+ },
228
+ });
229
+ rl.registerAction("group.update", {
230
+ description: "Update a group",
231
+ inputSchema: {
232
+ groupId: { type: "string", required: true, description: "Group ID" },
233
+ name: { type: "string", required: false, description: "Group name" },
234
+ accessAll: { type: "boolean", required: false, description: "Access all collections" },
235
+ collections: { type: "array", required: false, description: "Array of collection IDs" },
236
+ externalId: { type: "string", required: false, description: "External ID" },
237
+ },
238
+ async execute(input, ctx) {
239
+ const { groupId, name, accessAll, collections, externalId } = input;
240
+ const body = {};
241
+ // Name is required by API — fetch current if not provided
242
+ if (name) {
243
+ body.name = name;
244
+ }
245
+ else {
246
+ const current = (await authedRequest(ctx, "GET", `/public/groups/${groupId}`));
247
+ body.name = current.name;
248
+ }
249
+ body.AccessAll = accessAll ?? false;
250
+ if (collections) {
251
+ body.collections = collections.map((id) => ({ id, ReadOnly: false }));
252
+ }
253
+ if (externalId)
254
+ body.externalId = externalId;
255
+ return authedRequest(ctx, "PUT", `/public/groups/${groupId}`, body);
256
+ },
257
+ });
258
+ rl.registerAction("group.updateMembers", {
259
+ description: "Set the member IDs for a group",
260
+ inputSchema: {
261
+ groupId: { type: "string", required: true, description: "Group ID" },
262
+ memberIds: { type: "array", required: true, description: "Array of member IDs" },
263
+ },
264
+ async execute(input, ctx) {
265
+ const { groupId, memberIds } = input;
266
+ await authedRequest(ctx, "PUT", `/public/groups/${groupId}/member-ids`, { memberIds });
267
+ return { success: true };
268
+ },
269
+ });
270
+ rl.registerAction("group.delete", {
271
+ description: "Delete a group",
272
+ inputSchema: {
273
+ groupId: { type: "string", required: true, description: "Group ID" },
274
+ },
275
+ async execute(input, ctx) {
276
+ const { groupId } = input;
277
+ await authedRequest(ctx, "DELETE", `/public/groups/${groupId}`);
278
+ return { success: true };
279
+ },
280
+ });
281
+ // ── Member ──────────────────────────────────────────
282
+ rl.registerAction("member.create", {
283
+ description: "Invite a member to the organization",
284
+ inputSchema: {
285
+ email: { type: "string", required: true, description: "Email address" },
286
+ type: { type: "number", required: true, description: "Member type (0=Owner, 1=Admin, 2=User, 3=Manager)" },
287
+ accessAll: { type: "boolean", required: true, description: "Access all collections" },
288
+ collections: { type: "array", required: false, description: "Array of collection IDs" },
289
+ externalId: { type: "string", required: false, description: "External ID" },
290
+ },
291
+ async execute(input, ctx) {
292
+ const { email, type, accessAll, collections, externalId } = input;
293
+ const body = { email, type, AccessAll: accessAll };
294
+ if (collections) {
295
+ body.collections = collections.map((id) => ({ id, ReadOnly: false }));
296
+ }
297
+ if (externalId)
298
+ body.externalId = externalId;
299
+ return authedRequest(ctx, "POST", "/public/members/", body);
300
+ },
301
+ });
302
+ rl.registerAction("member.get", {
303
+ description: "Get a member by ID",
304
+ inputSchema: {
305
+ memberId: { type: "string", required: true, description: "Member ID" },
306
+ },
307
+ async execute(input, ctx) {
308
+ const { memberId } = input;
309
+ return authedRequest(ctx, "GET", `/public/members/${memberId}`);
310
+ },
311
+ });
312
+ rl.registerAction("member.list", {
313
+ description: "List all members",
314
+ inputSchema: {
315
+ limit: { type: "number", required: false, description: "Max results" },
316
+ },
317
+ async execute(input, ctx) {
318
+ const { limit } = (input ?? {});
319
+ const data = (await authedRequest(ctx, "GET", "/public/members"));
320
+ if (limit)
321
+ return data.data.slice(0, limit);
322
+ return data.data;
323
+ },
324
+ });
325
+ rl.registerAction("member.getGroups", {
326
+ description: "Get group IDs for a member",
327
+ inputSchema: {
328
+ memberId: { type: "string", required: true, description: "Member ID" },
329
+ },
330
+ async execute(input, ctx) {
331
+ const { memberId } = input;
332
+ const groupIds = (await authedRequest(ctx, "GET", `/public/members/${memberId}/group-ids`));
333
+ return groupIds.map((groupId) => ({ groupId }));
334
+ },
335
+ });
336
+ rl.registerAction("member.update", {
337
+ description: "Update a member",
338
+ inputSchema: {
339
+ memberId: { type: "string", required: true, description: "Member ID" },
340
+ type: { type: "number", required: false, description: "Member type (0=Owner, 1=Admin, 2=User, 3=Manager)" },
341
+ accessAll: { type: "boolean", required: false, description: "Access all collections" },
342
+ collections: { type: "array", required: false, description: "Array of collection IDs" },
343
+ externalId: { type: "string", required: false, description: "External ID" },
344
+ },
345
+ async execute(input, ctx) {
346
+ const { memberId, type, accessAll, collections, externalId } = input;
347
+ const body = {};
348
+ if (accessAll !== undefined)
349
+ body.AccessAll = accessAll;
350
+ if (type !== undefined)
351
+ body.Type = type;
352
+ if (collections) {
353
+ body.collections = collections.map((id) => ({ id, ReadOnly: false }));
354
+ }
355
+ if (externalId)
356
+ body.externalId = externalId;
357
+ return authedRequest(ctx, "PUT", `/public/members/${memberId}`, body);
358
+ },
359
+ });
360
+ rl.registerAction("member.updateGroups", {
361
+ description: "Set the group IDs for a member",
362
+ inputSchema: {
363
+ memberId: { type: "string", required: true, description: "Member ID" },
364
+ groupIds: { type: "array", required: true, description: "Array of group IDs" },
365
+ },
366
+ async execute(input, ctx) {
367
+ const { memberId, groupIds } = input;
368
+ await authedRequest(ctx, "PUT", `/public/members/${memberId}/group-ids`, { groupIds });
369
+ return { success: true };
370
+ },
371
+ });
372
+ rl.registerAction("member.delete", {
373
+ description: "Remove a member from the organization",
374
+ inputSchema: {
375
+ memberId: { type: "string", required: true, description: "Member ID" },
376
+ },
377
+ async execute(input, ctx) {
378
+ const { memberId } = input;
379
+ await authedRequest(ctx, "DELETE", `/public/members/${memberId}`);
380
+ return { success: true };
381
+ },
382
+ });
383
+ }
@@ -0,0 +1,300 @@
1
+ const BASE_URL = "https://api.box.com/2.0";
2
+ async function apiRequest(token, method, endpoint, body, qs) {
3
+ const url = new URL(`${BASE_URL}${endpoint}`);
4
+ if (qs) {
5
+ for (const [k, v] of Object.entries(qs)) {
6
+ if (v !== undefined && v !== null)
7
+ url.searchParams.set(k, String(v));
8
+ }
9
+ }
10
+ const opts = {
11
+ method,
12
+ headers: {
13
+ "Content-Type": "application/json",
14
+ Authorization: `Bearer ${token}`,
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(`Box API error ${res.status}: ${text}`);
24
+ }
25
+ if (res.status === 204)
26
+ return { success: true };
27
+ const ct = res.headers.get("content-type") ?? "";
28
+ if (ct.includes("application/json"))
29
+ return res.json();
30
+ return { success: true };
31
+ }
32
+ async function paginateAll(token, endpoint, property, qs, limit) {
33
+ const results = [];
34
+ let offset = 0;
35
+ const size = 100;
36
+ while (true) {
37
+ const data = (await apiRequest(token, "GET", endpoint, undefined, {
38
+ ...qs,
39
+ limit: size,
40
+ offset,
41
+ }));
42
+ const items = data[property] ?? [];
43
+ results.push(...items);
44
+ if (limit && results.length >= limit)
45
+ return results.slice(0, limit);
46
+ if (items.length === 0)
47
+ break;
48
+ offset += size;
49
+ }
50
+ return results;
51
+ }
52
+ function getToken(ctx) {
53
+ return ctx.connection.config.accessToken;
54
+ }
55
+ export default function box(rl) {
56
+ rl.setName("box");
57
+ rl.setVersion("0.1.0");
58
+ rl.setConnectionSchema({
59
+ accessToken: {
60
+ type: "string",
61
+ required: true,
62
+ description: "Box OAuth2 access token (or developer token for testing)",
63
+ env: "BOX_ACCESS_TOKEN",
64
+ },
65
+ });
66
+ // ── File ────────────────────────────────────────────
67
+ rl.registerAction("file.copy", {
68
+ description: "Copy a file to a folder",
69
+ inputSchema: {
70
+ fileId: { type: "string", required: true, description: "File ID" },
71
+ parentId: { type: "string", required: true, description: "Destination folder ID (0 for root)" },
72
+ name: { type: "string", required: false, description: "New name for the copy" },
73
+ version: { type: "string", required: false, description: "Specific version to copy" },
74
+ fields: { type: "string", required: false, description: "Comma-separated fields to return" },
75
+ },
76
+ async execute(input, ctx) {
77
+ const { fileId, parentId, name, version, fields } = input;
78
+ const body = { parent: { id: parentId || "0" } };
79
+ if (name)
80
+ body.name = name;
81
+ if (version)
82
+ body.version = version;
83
+ const qs = {};
84
+ if (fields)
85
+ qs.fields = fields;
86
+ return apiRequest(getToken(ctx), "POST", `/files/${fileId}/copy`, body, qs);
87
+ },
88
+ });
89
+ rl.registerAction("file.delete", {
90
+ description: "Delete a file",
91
+ inputSchema: {
92
+ fileId: { type: "string", required: true, description: "File ID" },
93
+ },
94
+ async execute(input, ctx) {
95
+ const { fileId } = input;
96
+ await apiRequest(getToken(ctx), "DELETE", `/files/${fileId}`);
97
+ return { success: true };
98
+ },
99
+ });
100
+ rl.registerAction("file.get", {
101
+ description: "Get file metadata",
102
+ inputSchema: {
103
+ fileId: { type: "string", required: true, description: "File ID" },
104
+ fields: { type: "string", required: false, description: "Comma-separated fields to return" },
105
+ },
106
+ async execute(input, ctx) {
107
+ const { fileId, fields } = input;
108
+ const qs = {};
109
+ if (fields)
110
+ qs.fields = fields;
111
+ return apiRequest(getToken(ctx), "GET", `/files/${fileId}`, undefined, qs);
112
+ },
113
+ });
114
+ rl.registerAction("file.search", {
115
+ description: "Search for files",
116
+ inputSchema: {
117
+ query: { type: "string", required: true, description: "Search query" },
118
+ limit: { type: "number", required: false, description: "Max results" },
119
+ contentTypes: { type: "string", required: false, description: "Comma-separated content types (name, description, file_content, comments, tags)" },
120
+ createdAtRange: { type: "string", required: false, description: "Date range: from,to (ISO 8601)" },
121
+ updatedAtRange: { type: "string", required: false, description: "Date range: from,to (ISO 8601)" },
122
+ ancestorFolderIds: { type: "string", required: false, description: "Comma-separated folder IDs to search within" },
123
+ },
124
+ async execute(input, ctx) {
125
+ const { query, limit, contentTypes, createdAtRange, updatedAtRange, ancestorFolderIds } = (input ?? {});
126
+ const qs = { type: "file", query };
127
+ if (contentTypes)
128
+ qs.content_types = contentTypes;
129
+ if (createdAtRange)
130
+ qs.created_at_range = createdAtRange;
131
+ if (updatedAtRange)
132
+ qs.updated_at_range = updatedAtRange;
133
+ if (ancestorFolderIds)
134
+ qs.ancestor_folder_ids = ancestorFolderIds;
135
+ return paginateAll(getToken(ctx), "/search", "entries", qs, limit);
136
+ },
137
+ });
138
+ rl.registerAction("file.share", {
139
+ description: "Share a file (create a collaboration)",
140
+ inputSchema: {
141
+ fileId: { type: "string", required: true, description: "File ID" },
142
+ role: { type: "string", required: true, description: "Role: editor, viewer, previewer, uploader, previewer_uploader, viewer_uploader, co-owner" },
143
+ accessibleByType: { type: "string", required: true, description: "'user' or 'group'" },
144
+ accessibleById: { type: "string", required: false, description: "User/group ID (use this or email)" },
145
+ email: { type: "string", required: false, description: "User email (alternative to ID, only for user type)" },
146
+ canViewPath: { type: "boolean", required: false, description: "Can view path to this item" },
147
+ expiresAt: { type: "string", required: false, description: "Expiration date (ISO 8601)" },
148
+ notify: { type: "boolean", required: false, description: "Send notification email" },
149
+ },
150
+ async execute(input, ctx) {
151
+ const { fileId, role, accessibleByType, accessibleById, email, canViewPath, expiresAt, notify } = input;
152
+ const body = {
153
+ item: { id: fileId, type: "file" },
154
+ role,
155
+ accessible_by: {},
156
+ };
157
+ const accessible = body.accessible_by;
158
+ accessible.type = accessibleByType;
159
+ if (email)
160
+ accessible.login = email;
161
+ else if (accessibleById)
162
+ accessible.id = accessibleById;
163
+ if (canViewPath !== undefined)
164
+ body.can_view_path = canViewPath;
165
+ if (expiresAt)
166
+ body.expires_at = expiresAt;
167
+ const qs = {};
168
+ if (notify !== undefined)
169
+ qs.notify = notify;
170
+ return apiRequest(getToken(ctx), "POST", "/collaborations", body, qs);
171
+ },
172
+ });
173
+ // ── Folder ──────────────────────────────────────────
174
+ rl.registerAction("folder.create", {
175
+ description: "Create a folder",
176
+ inputSchema: {
177
+ name: { type: "string", required: true, description: "Folder name" },
178
+ parentId: { type: "string", required: false, description: "Parent folder ID (0 for root)" },
179
+ fields: { type: "string", required: false, description: "Comma-separated fields to return" },
180
+ },
181
+ async execute(input, ctx) {
182
+ const { name, parentId, fields } = input;
183
+ const body = {
184
+ name,
185
+ parent: { id: parentId || "0" },
186
+ };
187
+ const qs = {};
188
+ if (fields)
189
+ qs.fields = fields;
190
+ return apiRequest(getToken(ctx), "POST", "/folders", body, qs);
191
+ },
192
+ });
193
+ rl.registerAction("folder.delete", {
194
+ description: "Delete a folder",
195
+ inputSchema: {
196
+ folderId: { type: "string", required: true, description: "Folder ID" },
197
+ recursive: { type: "boolean", required: false, description: "Delete non-empty folder recursively" },
198
+ },
199
+ async execute(input, ctx) {
200
+ const { folderId, recursive } = input;
201
+ await apiRequest(getToken(ctx), "DELETE", `/folders/${folderId}`, undefined, {
202
+ recursive: recursive ?? false,
203
+ });
204
+ return { success: true };
205
+ },
206
+ });
207
+ rl.registerAction("folder.get", {
208
+ description: "Get folder metadata",
209
+ inputSchema: {
210
+ folderId: { type: "string", required: true, description: "Folder ID" },
211
+ },
212
+ async execute(input, ctx) {
213
+ const { folderId } = input;
214
+ return apiRequest(getToken(ctx), "GET", `/folders/${folderId}`);
215
+ },
216
+ });
217
+ rl.registerAction("folder.search", {
218
+ description: "Search for folders",
219
+ inputSchema: {
220
+ query: { type: "string", required: true, description: "Search query" },
221
+ limit: { type: "number", required: false, description: "Max results" },
222
+ contentTypes: { type: "string", required: false, description: "Comma-separated content types" },
223
+ createdAtRange: { type: "string", required: false, description: "Date range: from,to (ISO 8601)" },
224
+ updatedAtRange: { type: "string", required: false, description: "Date range: from,to (ISO 8601)" },
225
+ },
226
+ async execute(input, ctx) {
227
+ const { query, limit, contentTypes, createdAtRange, updatedAtRange } = (input ?? {});
228
+ const qs = { type: "folder", query };
229
+ if (contentTypes)
230
+ qs.content_types = contentTypes;
231
+ if (createdAtRange)
232
+ qs.created_at_range = createdAtRange;
233
+ if (updatedAtRange)
234
+ qs.updated_at_range = updatedAtRange;
235
+ return paginateAll(getToken(ctx), "/search", "entries", qs, limit);
236
+ },
237
+ });
238
+ rl.registerAction("folder.share", {
239
+ description: "Share a folder (create a collaboration)",
240
+ inputSchema: {
241
+ folderId: { type: "string", required: true, description: "Folder ID" },
242
+ role: { type: "string", required: true, description: "Role: editor, viewer, previewer, uploader, previewer_uploader, viewer_uploader, co-owner" },
243
+ accessibleByType: { type: "string", required: true, description: "'user' or 'group'" },
244
+ accessibleById: { type: "string", required: false, description: "User/group ID" },
245
+ email: { type: "string", required: false, description: "User email (alternative to ID)" },
246
+ canViewPath: { type: "boolean", required: false, description: "Can view path" },
247
+ expiresAt: { type: "string", required: false, description: "Expiration (ISO 8601)" },
248
+ notify: { type: "boolean", required: false, description: "Send notification" },
249
+ },
250
+ async execute(input, ctx) {
251
+ const { folderId, role, accessibleByType, accessibleById, email, canViewPath, expiresAt, notify } = input;
252
+ const body = {
253
+ item: { id: folderId, type: "folder" },
254
+ role,
255
+ accessible_by: {},
256
+ };
257
+ const accessible = body.accessible_by;
258
+ accessible.type = accessibleByType;
259
+ if (email)
260
+ accessible.login = email;
261
+ else if (accessibleById)
262
+ accessible.id = accessibleById;
263
+ if (canViewPath !== undefined)
264
+ body.can_view_path = canViewPath;
265
+ if (expiresAt)
266
+ body.expires_at = expiresAt;
267
+ const qs = {};
268
+ if (notify !== undefined)
269
+ qs.notify = notify;
270
+ return apiRequest(getToken(ctx), "POST", "/collaborations", body, qs);
271
+ },
272
+ });
273
+ rl.registerAction("folder.update", {
274
+ description: "Update a folder (move, rename, tag)",
275
+ inputSchema: {
276
+ folderId: { type: "string", required: true, description: "Folder ID" },
277
+ name: { type: "string", required: false, description: "New name" },
278
+ parentId: { type: "string", required: false, description: "Move to this parent folder ID" },
279
+ description: { type: "string", required: false, description: "Description" },
280
+ tags: { type: "string", required: false, description: "Comma-separated tags" },
281
+ fields: { type: "string", required: false, description: "Fields to return" },
282
+ },
283
+ async execute(input, ctx) {
284
+ const { folderId, name, parentId, description, tags, fields } = input;
285
+ const body = {};
286
+ if (name)
287
+ body.name = name;
288
+ if (parentId)
289
+ body.parent = { id: parentId };
290
+ if (description)
291
+ body.description = description;
292
+ if (tags)
293
+ body.tags = tags.split(",").map((t) => t.trim());
294
+ const qs = {};
295
+ if (fields)
296
+ qs.fields = fields;
297
+ return apiRequest(getToken(ctx), "PUT", `/folders/${folderId}`, body, qs);
298
+ },
299
+ });
300
+ }