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,188 @@
1
+ const BASE = "https://api.spotify.com/v1";
2
+ async function api(token, method, endpoint, body, qs) {
3
+ const url = new URL(`${BASE}${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 init = { method, headers: { Authorization: `Bearer ${token}`, "Content-Type": "application/json" } };
11
+ if (body && Object.keys(body).length > 0)
12
+ init.body = JSON.stringify(body);
13
+ const res = await fetch(url.toString(), init);
14
+ if (res.status === 204)
15
+ return { success: true };
16
+ if (!res.ok)
17
+ throw new Error(`Spotify error ${res.status}: ${await res.text()}`);
18
+ const text = await res.text();
19
+ return text ? JSON.parse(text) : { success: true };
20
+ }
21
+ function stripUri(uri, prefix) { return uri.replace(prefix, ""); }
22
+ export default function spotify(rl) {
23
+ rl.setName("spotify");
24
+ rl.setVersion("0.1.0");
25
+ rl.setConnectionSchema({ accessToken: { type: "string", required: true, description: "Spotify OAuth2 access token", env: "SPOTIFY_ACCESS_TOKEN" } });
26
+ const t = (ctx) => ctx.connection.config.accessToken;
27
+ // ── Player ──────────────────────────────────────────
28
+ rl.registerAction("player.pause", { description: "Pause playback", inputSchema: {},
29
+ async execute(_i, ctx) { return api(t(ctx), "PUT", "/me/player/pause"); } });
30
+ rl.registerAction("player.resume", { description: "Resume playback", inputSchema: {},
31
+ async execute(_i, ctx) { return api(t(ctx), "PUT", "/me/player/play"); } });
32
+ rl.registerAction("player.next", { description: "Skip to next track", inputSchema: {},
33
+ async execute(_i, ctx) { return api(t(ctx), "POST", "/me/player/next"); } });
34
+ rl.registerAction("player.previous", { description: "Skip to previous track", inputSchema: {},
35
+ async execute(_i, ctx) { return api(t(ctx), "POST", "/me/player/previous"); } });
36
+ rl.registerAction("player.currentlyPlaying", { description: "Get currently playing track", inputSchema: {},
37
+ async execute(_i, ctx) { return api(t(ctx), "GET", "/me/player/currently-playing"); } });
38
+ rl.registerAction("player.recentlyPlayed", { description: "Get recently played tracks", inputSchema: { limit: { type: "number", required: false } },
39
+ async execute(input, ctx) {
40
+ const qs = {};
41
+ if (input?.limit)
42
+ qs.limit = input.limit;
43
+ const data = (await api(t(ctx), "GET", "/me/player/recently-played", undefined, qs));
44
+ return data.items;
45
+ } });
46
+ rl.registerAction("player.addToQueue", { description: "Add a track to the queue", inputSchema: { uri: { type: "string", required: true, description: "Track URI or ID" } },
47
+ async execute(input, ctx) { return api(t(ctx), "POST", "/me/player/queue", undefined, { uri: input.uri }); } });
48
+ rl.registerAction("player.setVolume", { description: "Set playback volume", inputSchema: { volumePercent: { type: "number", required: true, description: "0-100" } },
49
+ async execute(input, ctx) { return api(t(ctx), "PUT", "/me/player/volume", undefined, { volume_percent: input.volumePercent }); } });
50
+ rl.registerAction("player.startMusic", { description: "Start playing an album, artist, or playlist", inputSchema: { contextUri: { type: "string", required: true, description: "Spotify URI (e.g. spotify:album:...)" } },
51
+ async execute(input, ctx) { return api(t(ctx), "PUT", "/me/player/play", { context_uri: input.contextUri }); } });
52
+ // ── Album ───────────────────────────────────────────
53
+ rl.registerAction("album.get", { description: "Get an album", inputSchema: { id: { type: "string", required: true } },
54
+ async execute(input, ctx) { return api(t(ctx), "GET", `/albums/${stripUri(input.id, "spotify:album:")}`); } });
55
+ rl.registerAction("album.getTracks", { description: "Get an album's tracks", inputSchema: { id: { type: "string", required: true }, limit: { type: "number", required: false } },
56
+ async execute(input, ctx) {
57
+ const p = input;
58
+ const qs = {};
59
+ if (p.limit)
60
+ qs.limit = p.limit;
61
+ const data = (await api(t(ctx), "GET", `/albums/${stripUri(p.id, "spotify:album:")}/tracks`, undefined, qs));
62
+ return data.items;
63
+ } });
64
+ rl.registerAction("album.getNewReleases", { description: "Get new album releases", inputSchema: { limit: { type: "number", required: false }, country: { type: "string", required: false } },
65
+ async execute(input, ctx) {
66
+ const p = (input ?? {});
67
+ const qs = {};
68
+ if (p.limit)
69
+ qs.limit = p.limit;
70
+ if (p.country)
71
+ qs.country = p.country;
72
+ const data = (await api(t(ctx), "GET", "/browse/new-releases", undefined, qs));
73
+ return data.albums.items;
74
+ } });
75
+ rl.registerAction("album.search", { description: "Search albums", inputSchema: { query: { type: "string", required: true }, limit: { type: "number", required: false } },
76
+ async execute(input, ctx) {
77
+ const p = input;
78
+ const qs = { q: p.query, type: "album" };
79
+ if (p.limit)
80
+ qs.limit = p.limit;
81
+ else
82
+ qs.limit = 50;
83
+ const data = (await api(t(ctx), "GET", "/search", undefined, qs));
84
+ return data.albums.items;
85
+ } });
86
+ // ── Artist ──────────────────────────────────────────
87
+ rl.registerAction("artist.get", { description: "Get an artist", inputSchema: { id: { type: "string", required: true } },
88
+ async execute(input, ctx) { return api(t(ctx), "GET", `/artists/${stripUri(input.id, "spotify:artist:")}`); } });
89
+ rl.registerAction("artist.getAlbums", { description: "Get an artist's albums", inputSchema: { id: { type: "string", required: true }, limit: { type: "number", required: false } },
90
+ async execute(input, ctx) {
91
+ const p = input;
92
+ const qs = {};
93
+ if (p.limit)
94
+ qs.limit = p.limit;
95
+ const data = (await api(t(ctx), "GET", `/artists/${stripUri(p.id, "spotify:artist:")}/albums`, undefined, qs));
96
+ return data.items;
97
+ } });
98
+ rl.registerAction("artist.getRelatedArtists", { description: "Get related artists", inputSchema: { id: { type: "string", required: true } },
99
+ async execute(input, ctx) {
100
+ const data = (await api(t(ctx), "GET", `/artists/${stripUri(input.id, "spotify:artist:")}/related-artists`));
101
+ return data.artists;
102
+ } });
103
+ rl.registerAction("artist.getTopTracks", { description: "Get an artist's top tracks", inputSchema: { id: { type: "string", required: true }, country: { type: "string", required: true } },
104
+ async execute(input, ctx) {
105
+ const p = input;
106
+ const data = (await api(t(ctx), "GET", `/artists/${stripUri(p.id, "spotify:artist:")}/top-tracks`, undefined, { country: p.country }));
107
+ return data.tracks;
108
+ } });
109
+ rl.registerAction("artist.search", { description: "Search artists", inputSchema: { query: { type: "string", required: true }, limit: { type: "number", required: false } },
110
+ async execute(input, ctx) {
111
+ const p = input;
112
+ const qs = { q: p.query, type: "artist", limit: p.limit ?? 50 };
113
+ const data = (await api(t(ctx), "GET", "/search", undefined, qs));
114
+ return data.artists.items;
115
+ } });
116
+ // ── Playlist ────────────────────────────────────────
117
+ rl.registerAction("playlist.get", { description: "Get a playlist", inputSchema: { id: { type: "string", required: true } },
118
+ async execute(input, ctx) { return api(t(ctx), "GET", `/playlists/${stripUri(input.id, "spotify:playlist:")}`); } });
119
+ rl.registerAction("playlist.getTracks", { description: "Get a playlist's tracks", inputSchema: { id: { type: "string", required: true }, limit: { type: "number", required: false } },
120
+ async execute(input, ctx) {
121
+ const p = input;
122
+ const qs = {};
123
+ if (p.limit)
124
+ qs.limit = p.limit;
125
+ const data = (await api(t(ctx), "GET", `/playlists/${stripUri(p.id, "spotify:playlist:")}/tracks`, undefined, qs));
126
+ return data.items;
127
+ } });
128
+ rl.registerAction("playlist.create", { description: "Create a playlist", inputSchema: { name: { type: "string", required: true }, description: { type: "string", required: false }, public: { type: "boolean", required: false } },
129
+ async execute(input, ctx) { return api(t(ctx), "POST", "/me/playlists", input); } });
130
+ rl.registerAction("playlist.addTrack", { description: "Add a track to a playlist", inputSchema: { id: { type: "string", required: true }, trackUri: { type: "string", required: true }, position: { type: "number", required: false } },
131
+ async execute(input, ctx) {
132
+ const p = input;
133
+ const qs = { uris: p.trackUri };
134
+ if (p.position !== undefined)
135
+ qs.position = p.position;
136
+ return api(t(ctx), "POST", `/playlists/${stripUri(p.id, "spotify:playlist:")}/tracks`, {}, qs);
137
+ } });
138
+ rl.registerAction("playlist.removeTrack", { description: "Remove a track from a playlist", inputSchema: { id: { type: "string", required: true }, trackUri: { type: "string", required: true } },
139
+ async execute(input, ctx) {
140
+ const p = input;
141
+ return api(t(ctx), "DELETE", `/playlists/${stripUri(p.id, "spotify:playlist:")}/tracks`, { tracks: [{ uri: p.trackUri }] });
142
+ } });
143
+ rl.registerAction("playlist.listMine", { description: "Get the current user's playlists", inputSchema: { limit: { type: "number", required: false } },
144
+ async execute(input, ctx) {
145
+ const qs = {};
146
+ if (input?.limit)
147
+ qs.limit = input.limit;
148
+ const data = (await api(t(ctx), "GET", "/me/playlists", undefined, qs));
149
+ return data.items;
150
+ } });
151
+ rl.registerAction("playlist.search", { description: "Search playlists", inputSchema: { query: { type: "string", required: true }, limit: { type: "number", required: false } },
152
+ async execute(input, ctx) {
153
+ const p = input;
154
+ const qs = { q: p.query, type: "playlist", limit: p.limit ?? 50 };
155
+ const data = (await api(t(ctx), "GET", "/search", undefined, qs));
156
+ return data.playlists.items;
157
+ } });
158
+ // ── Track ───────────────────────────────────────────
159
+ rl.registerAction("track.get", { description: "Get a track", inputSchema: { id: { type: "string", required: true } },
160
+ async execute(input, ctx) { return api(t(ctx), "GET", `/tracks/${stripUri(input.id, "spotify:track:")}`); } });
161
+ rl.registerAction("track.getAudioFeatures", { description: "Get audio features for a track", inputSchema: { id: { type: "string", required: true } },
162
+ async execute(input, ctx) { return api(t(ctx), "GET", `/audio-features/${stripUri(input.id, "spotify:track:")}`); } });
163
+ rl.registerAction("track.search", { description: "Search tracks", inputSchema: { query: { type: "string", required: true }, limit: { type: "number", required: false } },
164
+ async execute(input, ctx) {
165
+ const p = input;
166
+ const qs = { q: p.query, type: "track", limit: p.limit ?? 50 };
167
+ const data = (await api(t(ctx), "GET", "/search", undefined, qs));
168
+ return data.tracks.items;
169
+ } });
170
+ // ── Library ─────────────────────────────────────────
171
+ rl.registerAction("library.getLikedTracks", { description: "Get liked tracks", inputSchema: { limit: { type: "number", required: false } },
172
+ async execute(input, ctx) {
173
+ const qs = {};
174
+ if (input?.limit)
175
+ qs.limit = input.limit;
176
+ const data = (await api(t(ctx), "GET", "/me/tracks", undefined, qs));
177
+ return data.items;
178
+ } });
179
+ // ── My Data ─────────────────────────────────────────
180
+ rl.registerAction("myData.getFollowingArtists", { description: "Get followed artists", inputSchema: { limit: { type: "number", required: false } },
181
+ async execute(input, ctx) {
182
+ const qs = { type: "artist" };
183
+ if (input?.limit)
184
+ qs.limit = input.limit;
185
+ const data = (await api(t(ctx), "GET", "/me/following", undefined, qs));
186
+ return data.artists.items;
187
+ } });
188
+ }
@@ -0,0 +1,82 @@
1
+ const BASE = "https://stackby.com/api/betav1";
2
+ async function apiRequest(apiKey, method, endpoint, body, qs) {
3
+ const url = new URL(`${BASE}${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 init = { method, headers: { "api-key": apiKey, "Content-Type": "application/json" } };
11
+ if (body !== undefined)
12
+ init.body = JSON.stringify(body);
13
+ const res = await fetch(url.toString(), init);
14
+ if (!res.ok)
15
+ throw new Error(`Stackby error ${res.status}: ${await res.text()}`);
16
+ return res.json();
17
+ }
18
+ export default function stackby(rl) {
19
+ rl.setName("stackby");
20
+ rl.setVersion("0.1.0");
21
+ rl.setConnectionSchema({
22
+ apiKey: { type: "string", required: true, description: "Stackby API key", env: "STACKBY_API_KEY" },
23
+ });
24
+ const key = (ctx) => ctx.connection.config.apiKey;
25
+ rl.registerAction("row.read", {
26
+ description: "Read a row by ID",
27
+ inputSchema: {
28
+ stackId: { type: "string", required: true },
29
+ table: { type: "string", required: true },
30
+ rowId: { type: "string", required: true },
31
+ },
32
+ async execute(input, ctx) {
33
+ const p = input;
34
+ const data = (await apiRequest(key(ctx), "GET", `/rowlist/${p.stackId}/${encodeURIComponent(p.table)}`, undefined, { rowIds: p.rowId }));
35
+ return data.map(d => d.field);
36
+ },
37
+ });
38
+ rl.registerAction("row.list", {
39
+ description: "List rows from a table",
40
+ inputSchema: {
41
+ stackId: { type: "string", required: true },
42
+ table: { type: "string", required: true },
43
+ view: { type: "string", required: false },
44
+ limit: { type: "number", required: false },
45
+ },
46
+ async execute(input, ctx) {
47
+ const p = (input ?? {});
48
+ const qs = {};
49
+ if (p.view)
50
+ qs.view = p.view;
51
+ if (p.limit)
52
+ qs.maxrecord = p.limit;
53
+ const data = (await apiRequest(key(ctx), "GET", `/rowlist/${p.stackId}/${encodeURIComponent(p.table)}`, undefined, qs));
54
+ return data.map(d => d.field);
55
+ },
56
+ });
57
+ rl.registerAction("row.append", {
58
+ description: "Append rows to a table",
59
+ inputSchema: {
60
+ stackId: { type: "string", required: true },
61
+ table: { type: "string", required: true },
62
+ records: { type: "object", required: true, description: "Array of objects [{field: {col1: val1, col2: val2}}]" },
63
+ },
64
+ async execute(input, ctx) {
65
+ const p = input;
66
+ const data = (await apiRequest(key(ctx), "POST", `/rowcreate/${p.stackId}/${encodeURIComponent(p.table)}`, { records: p.records }));
67
+ return data.map(d => d.field);
68
+ },
69
+ });
70
+ rl.registerAction("row.delete", {
71
+ description: "Delete a row by ID",
72
+ inputSchema: {
73
+ stackId: { type: "string", required: true },
74
+ table: { type: "string", required: true },
75
+ rowId: { type: "string", required: true },
76
+ },
77
+ async execute(input, ctx) {
78
+ const p = input;
79
+ return apiRequest(key(ctx), "DELETE", `/rowdelete/${p.stackId}/${encodeURIComponent(p.table)}`, undefined, { rowIds: p.rowId });
80
+ },
81
+ });
82
+ }
@@ -0,0 +1,141 @@
1
+ function getConn(ctx) {
2
+ const c = ctx.connection.config;
3
+ return { contentToken: c.contentToken, managementToken: c.managementToken };
4
+ }
5
+ async function contentApi(token, endpoint, qs) {
6
+ const url = new URL(`https://api.storyblok.com${endpoint}`);
7
+ url.searchParams.set("token", token);
8
+ if (qs) {
9
+ for (const [k, v] of Object.entries(qs)) {
10
+ if (v !== undefined && v !== null)
11
+ url.searchParams.set(k, String(v));
12
+ }
13
+ }
14
+ const res = await fetch(url.toString());
15
+ if (!res.ok)
16
+ throw new Error(`Storyblok Content API error ${res.status}: ${await res.text()}`);
17
+ return res.json();
18
+ }
19
+ async function managementApi(token, method, endpoint, body, qs) {
20
+ const url = new URL(`https://mapi.storyblok.com${endpoint}`);
21
+ if (qs) {
22
+ for (const [k, v] of Object.entries(qs)) {
23
+ if (v !== undefined && v !== null)
24
+ url.searchParams.set(k, String(v));
25
+ }
26
+ }
27
+ const init = { method, headers: { Authorization: token, "Content-Type": "application/json" } };
28
+ if (body && Object.keys(body).length > 0)
29
+ init.body = JSON.stringify(body);
30
+ const res = await fetch(url.toString(), init);
31
+ if (!res.ok)
32
+ throw new Error(`Storyblok Management API error ${res.status}: ${await res.text()}`);
33
+ const text = await res.text();
34
+ return text ? JSON.parse(text) : {};
35
+ }
36
+ export default function storyblok(rl) {
37
+ rl.setName("storyblok");
38
+ rl.setVersion("0.1.0");
39
+ rl.setConnectionSchema({
40
+ contentToken: { type: "string", required: false, description: "Storyblok Content Delivery API token (for reading published content)", env: "STORYBLOK_CONTENT_TOKEN" },
41
+ managementToken: { type: "string", required: false, description: "Storyblok Management API personal access token", env: "STORYBLOK_MANAGEMENT_TOKEN" },
42
+ });
43
+ // ── Content API ─────────────────────────────────────
44
+ rl.registerAction("content.story.get", {
45
+ description: "Get a published story by slug or ID (Content API)",
46
+ inputSchema: { identifier: { type: "string", required: true, description: "Story slug or numeric ID" } },
47
+ async execute(input, ctx) {
48
+ const conn = getConn(ctx);
49
+ if (!conn.contentToken)
50
+ throw new Error("contentToken required for Content API");
51
+ const data = (await contentApi(conn.contentToken, `/v1/cdn/stories/${input.identifier}`));
52
+ return data.story;
53
+ },
54
+ });
55
+ rl.registerAction("content.story.list", {
56
+ description: "List published stories (Content API)",
57
+ inputSchema: { limit: { type: "number", required: false }, startsWith: { type: "string", required: false, description: "Filter by slug prefix" } },
58
+ async execute(input, ctx) {
59
+ const conn = getConn(ctx);
60
+ if (!conn.contentToken)
61
+ throw new Error("contentToken required for Content API");
62
+ const p = (input ?? {});
63
+ const qs = {};
64
+ if (p.limit)
65
+ qs.per_page = p.limit;
66
+ if (p.startsWith)
67
+ qs.starts_with = p.startsWith;
68
+ const data = (await contentApi(conn.contentToken, "/v1/cdn/stories", qs));
69
+ return data.stories;
70
+ },
71
+ });
72
+ // ── Management API ──────────────────────────────────
73
+ rl.registerAction("management.story.get", {
74
+ description: "Get a story by ID (Management API)",
75
+ inputSchema: { spaceId: { type: "string", required: true }, storyId: { type: "string", required: true } },
76
+ async execute(input, ctx) {
77
+ const conn = getConn(ctx);
78
+ if (!conn.managementToken)
79
+ throw new Error("managementToken required for Management API");
80
+ const p = input;
81
+ const data = (await managementApi(conn.managementToken, "GET", `/v1/spaces/${p.spaceId}/stories/${p.storyId}`));
82
+ return data.story;
83
+ },
84
+ });
85
+ rl.registerAction("management.story.list", {
86
+ description: "List stories in a space (Management API)",
87
+ inputSchema: { spaceId: { type: "string", required: true }, limit: { type: "number", required: false } },
88
+ async execute(input, ctx) {
89
+ const conn = getConn(ctx);
90
+ if (!conn.managementToken)
91
+ throw new Error("managementToken required for Management API");
92
+ const p = (input ?? {});
93
+ const qs = {};
94
+ if (p.limit)
95
+ qs.per_page = p.limit;
96
+ const data = (await managementApi(conn.managementToken, "GET", `/v1/spaces/${p.spaceId}/stories`, undefined, qs));
97
+ return data.stories;
98
+ },
99
+ });
100
+ rl.registerAction("management.story.delete", {
101
+ description: "Delete a story (Management API)",
102
+ inputSchema: { spaceId: { type: "string", required: true }, storyId: { type: "string", required: true } },
103
+ async execute(input, ctx) {
104
+ const conn = getConn(ctx);
105
+ if (!conn.managementToken)
106
+ throw new Error("managementToken required for Management API");
107
+ const p = input;
108
+ const data = (await managementApi(conn.managementToken, "DELETE", `/v1/spaces/${p.spaceId}/stories/${p.storyId}`));
109
+ return data.story;
110
+ },
111
+ });
112
+ rl.registerAction("management.story.publish", {
113
+ description: "Publish a story (Management API)",
114
+ inputSchema: { spaceId: { type: "string", required: true }, storyId: { type: "string", required: true }, releaseId: { type: "string", required: false }, language: { type: "string", required: false } },
115
+ async execute(input, ctx) {
116
+ const conn = getConn(ctx);
117
+ if (!conn.managementToken)
118
+ throw new Error("managementToken required for Management API");
119
+ const p = input;
120
+ const qs = {};
121
+ if (p.releaseId)
122
+ qs.release_id = p.releaseId;
123
+ if (p.language)
124
+ qs.lang = p.language;
125
+ const data = (await managementApi(conn.managementToken, "GET", `/v1/spaces/${p.spaceId}/stories/${p.storyId}/publish`, undefined, qs));
126
+ return data.story;
127
+ },
128
+ });
129
+ rl.registerAction("management.story.unpublish", {
130
+ description: "Unpublish a story (Management API)",
131
+ inputSchema: { spaceId: { type: "string", required: true }, storyId: { type: "string", required: true } },
132
+ async execute(input, ctx) {
133
+ const conn = getConn(ctx);
134
+ if (!conn.managementToken)
135
+ throw new Error("managementToken required for Management API");
136
+ const p = input;
137
+ const data = (await managementApi(conn.managementToken, "GET", `/v1/spaces/${p.spaceId}/stories/${p.storyId}/unpublish`));
138
+ return data.story;
139
+ },
140
+ });
141
+ }
@@ -0,0 +1,152 @@
1
+ function getConn(ctx) {
2
+ const c = ctx.connection.config;
3
+ return {
4
+ url: c.url.replace(/\/$/, ""),
5
+ apiVersion: c.apiVersion || "v4",
6
+ apiToken: c.apiToken,
7
+ email: c.email,
8
+ password: c.password,
9
+ };
10
+ }
11
+ let cachedJwt;
12
+ async function getJwt(conn) {
13
+ if (cachedJwt && cachedJwt.url === conn.url && Date.now() < cachedJwt.expiry)
14
+ return cachedJwt.token;
15
+ const authPath = conn.apiVersion === "v4" ? "/api/auth/local" : "/auth/local";
16
+ const res = await fetch(`${conn.url}${authPath}`, {
17
+ method: "POST",
18
+ headers: { "Content-Type": "application/json" },
19
+ body: JSON.stringify({ identifier: conn.email, password: conn.password }),
20
+ });
21
+ if (!res.ok)
22
+ throw new Error(`Strapi auth error ${res.status}: ${await res.text()}`);
23
+ const data = (await res.json());
24
+ const jwt = data.jwt;
25
+ cachedJwt = { token: jwt, url: conn.url, expiry: Date.now() + 3600_000 };
26
+ return jwt;
27
+ }
28
+ async function apiRequest(conn, method, endpoint, body, qs) {
29
+ let token;
30
+ if (conn.apiToken) {
31
+ token = conn.apiToken;
32
+ }
33
+ else {
34
+ token = await getJwt(conn);
35
+ }
36
+ const prefix = conn.apiVersion === "v4" ? "/api" : "";
37
+ const url = new URL(`${conn.url}${prefix}${endpoint}`);
38
+ if (qs) {
39
+ for (const [k, v] of Object.entries(qs)) {
40
+ if (v !== undefined && v !== null)
41
+ url.searchParams.set(k, String(v));
42
+ }
43
+ }
44
+ const init = { method, headers: { Authorization: `Bearer ${token}`, "Content-Type": "application/json" } };
45
+ if (body && Object.keys(body).length > 0)
46
+ init.body = JSON.stringify(body);
47
+ const res = await fetch(url.toString(), init);
48
+ if (!res.ok)
49
+ throw new Error(`Strapi error ${res.status}: ${await res.text()}`);
50
+ const text = await res.text();
51
+ return text ? JSON.parse(text) : {};
52
+ }
53
+ export default function strapi(rl) {
54
+ rl.setName("strapi");
55
+ rl.setVersion("0.1.0");
56
+ rl.setConnectionSchema({
57
+ url: { type: "string", required: true, description: "Strapi base URL", env: "STRAPI_URL" },
58
+ apiVersion: { type: "string", required: false, description: "v3 or v4 (default: v4)", env: "STRAPI_API_VERSION" },
59
+ apiToken: { type: "string", required: false, description: "Strapi API token (preferred)", env: "STRAPI_API_TOKEN" },
60
+ email: { type: "string", required: false, description: "Email for password auth", env: "STRAPI_EMAIL" },
61
+ password: { type: "string", required: false, description: "Password for password auth", env: "STRAPI_PASSWORD" },
62
+ });
63
+ rl.registerAction("entry.create", {
64
+ description: "Create an entry in a content type",
65
+ inputSchema: {
66
+ contentType: { type: "string", required: true, description: "Content type plural name (e.g. articles)" },
67
+ data: { type: "object", required: true, description: "Entry fields" },
68
+ },
69
+ async execute(input, ctx) {
70
+ const conn = getConn(ctx);
71
+ const p = input;
72
+ const body = conn.apiVersion === "v4" ? { data: p.data } : p.data;
73
+ return apiRequest(conn, "POST", `/${p.contentType}`, body);
74
+ },
75
+ });
76
+ rl.registerAction("entry.get", {
77
+ description: "Get an entry by ID",
78
+ inputSchema: {
79
+ contentType: { type: "string", required: true },
80
+ entryId: { type: "string", required: true },
81
+ },
82
+ async execute(input, ctx) {
83
+ const conn = getConn(ctx);
84
+ const p = input;
85
+ const data = (await apiRequest(conn, "GET", `/${p.contentType}/${p.entryId}`));
86
+ return conn.apiVersion === "v4" ? data.data : data;
87
+ },
88
+ });
89
+ rl.registerAction("entry.list", {
90
+ description: "List entries of a content type",
91
+ inputSchema: {
92
+ contentType: { type: "string", required: true },
93
+ limit: { type: "number", required: false },
94
+ sort: { type: "string", required: false, description: "Comma-separated sort fields" },
95
+ filters: { type: "string", required: false, description: "JSON filter object" },
96
+ publicationState: { type: "string", required: false, description: "live or preview" },
97
+ },
98
+ async execute(input, ctx) {
99
+ const conn = getConn(ctx);
100
+ const p = (input ?? {});
101
+ const qs = {};
102
+ if (conn.apiVersion === "v4") {
103
+ if (p.limit)
104
+ qs["pagination[pageSize]"] = p.limit;
105
+ if (p.sort)
106
+ qs.sort = p.sort;
107
+ if (p.filters)
108
+ qs.filters = p.filters;
109
+ if (p.publicationState)
110
+ qs.publicationState = p.publicationState;
111
+ const data = (await apiRequest(conn, "GET", `/${p.contentType}`, undefined, qs));
112
+ return data.data;
113
+ }
114
+ if (p.limit)
115
+ qs._limit = p.limit;
116
+ if (p.sort)
117
+ qs._sort = p.sort;
118
+ if (p.filters)
119
+ qs._where = p.filters;
120
+ if (p.publicationState)
121
+ qs._publicationState = p.publicationState;
122
+ return apiRequest(conn, "GET", `/${p.contentType}`, undefined, qs);
123
+ },
124
+ });
125
+ rl.registerAction("entry.update", {
126
+ description: "Update an entry by ID",
127
+ inputSchema: {
128
+ contentType: { type: "string", required: true },
129
+ entryId: { type: "string", required: true },
130
+ data: { type: "object", required: true, description: "Fields to update" },
131
+ },
132
+ async execute(input, ctx) {
133
+ const conn = getConn(ctx);
134
+ const p = input;
135
+ const body = conn.apiVersion === "v4" ? { data: p.data } : p.data;
136
+ const result = (await apiRequest(conn, "PUT", `/${p.contentType}/${p.entryId}`, body));
137
+ return conn.apiVersion === "v4" ? result.data : result;
138
+ },
139
+ });
140
+ rl.registerAction("entry.delete", {
141
+ description: "Delete an entry by ID",
142
+ inputSchema: {
143
+ contentType: { type: "string", required: true },
144
+ entryId: { type: "string", required: true },
145
+ },
146
+ async execute(input, ctx) {
147
+ const conn = getConn(ctx);
148
+ const p = input;
149
+ return apiRequest(conn, "DELETE", `/${p.contentType}/${p.entryId}`);
150
+ },
151
+ });
152
+ }