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,146 @@
1
+ async function apiRequest(host, token, endpoint, qs) {
2
+ const url = new URL(`https://${host}${endpoint}`);
3
+ url.searchParams.set("access_token", token);
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 res = await fetch(url.toString(), { headers: { Accept: "application/json" } });
11
+ if (!res.ok)
12
+ throw new Error(`Contentful API error ${res.status}: ${await res.text()}`);
13
+ return res.json();
14
+ }
15
+ async function paginateAll(host, token, endpoint, qs, limit) {
16
+ const results = [];
17
+ let skip = 0;
18
+ const size = 100;
19
+ while (true) {
20
+ const data = (await apiRequest(host, token, endpoint, { ...qs, skip, limit: size }));
21
+ const items = data.items ?? [];
22
+ results.push(...items);
23
+ if (limit && results.length >= limit)
24
+ return results.slice(0, limit);
25
+ if (items.length < size)
26
+ break;
27
+ skip += size;
28
+ }
29
+ return results;
30
+ }
31
+ function getConn(ctx) {
32
+ const cfg = ctx.connection.config;
33
+ const isPreview = cfg.source === "preview";
34
+ return {
35
+ host: isPreview ? "preview.contentful.com" : "cdn.contentful.com",
36
+ token: (isPreview ? cfg.previewAccessToken : cfg.deliveryAccessToken),
37
+ spaceId: cfg.spaceId,
38
+ };
39
+ }
40
+ export default function contentful(rl) {
41
+ rl.setName("contentful");
42
+ rl.setVersion("0.1.0");
43
+ rl.setConnectionSchema({
44
+ spaceId: { type: "string", required: true, description: "Contentful Space ID", env: "CONTENTFUL_SPACE_ID" },
45
+ deliveryAccessToken: { type: "string", required: true, description: "Content Delivery API access token", env: "CONTENTFUL_DELIVERY_TOKEN" },
46
+ previewAccessToken: { type: "string", required: false, description: "Content Preview API access token", env: "CONTENTFUL_PREVIEW_TOKEN" },
47
+ source: { type: "string", required: false, description: "'delivery' (default) or 'preview'", default: "delivery" },
48
+ });
49
+ // ── Space ───────────────────────────────────────────
50
+ rl.registerAction("space.get", {
51
+ description: "Get space details",
52
+ async execute(_input, ctx) {
53
+ const { host, token, spaceId } = getConn(ctx);
54
+ return apiRequest(host, token, `/spaces/${spaceId}`);
55
+ },
56
+ });
57
+ // ── Content Type ────────────────────────────────────
58
+ rl.registerAction("contentType.get", {
59
+ description: "Get a content type",
60
+ inputSchema: {
61
+ environmentId: { type: "string", required: true, description: "Environment ID (e.g. master)" },
62
+ contentTypeId: { type: "string", required: true, description: "Content type ID" },
63
+ },
64
+ async execute(input, ctx) {
65
+ const { environmentId, contentTypeId } = input;
66
+ const { host, token, spaceId } = getConn(ctx);
67
+ return apiRequest(host, token, `/spaces/${spaceId}/environments/${environmentId}/content_types/${contentTypeId}`);
68
+ },
69
+ });
70
+ // ── Entry ───────────────────────────────────────────
71
+ rl.registerAction("entry.get", {
72
+ description: "Get an entry by ID",
73
+ inputSchema: {
74
+ environmentId: { type: "string", required: true, description: "Environment ID" },
75
+ entryId: { type: "string", required: true, description: "Entry ID" },
76
+ },
77
+ async execute(input, ctx) {
78
+ const { environmentId, entryId } = input;
79
+ const { host, token, spaceId } = getConn(ctx);
80
+ return apiRequest(host, token, `/spaces/${spaceId}/environments/${environmentId}/entries/${entryId}`);
81
+ },
82
+ });
83
+ rl.registerAction("entry.list", {
84
+ description: "List entries",
85
+ inputSchema: {
86
+ environmentId: { type: "string", required: true, description: "Environment ID" },
87
+ contentType: { type: "string", required: false, description: "Filter by content type ID" },
88
+ query: { type: "string", required: false, description: "Full-text search query" },
89
+ select: { type: "string", required: false, description: "Comma-separated fields to select" },
90
+ order: { type: "string", required: false, description: "Order by field" },
91
+ limit: { type: "number", required: false, description: "Max results" },
92
+ },
93
+ async execute(input, ctx) {
94
+ const { environmentId, contentType, query, select, order, limit } = (input ?? {});
95
+ const { host, token, spaceId } = getConn(ctx);
96
+ const qs = {};
97
+ if (contentType)
98
+ qs.content_type = contentType;
99
+ if (query)
100
+ qs.query = query;
101
+ if (select)
102
+ qs.select = select;
103
+ if (order)
104
+ qs.order = order;
105
+ return paginateAll(host, token, `/spaces/${spaceId}/environments/${environmentId}/entries`, qs, limit);
106
+ },
107
+ });
108
+ // ── Asset ───────────────────────────────────────────
109
+ rl.registerAction("asset.get", {
110
+ description: "Get an asset by ID",
111
+ inputSchema: {
112
+ environmentId: { type: "string", required: true, description: "Environment ID" },
113
+ assetId: { type: "string", required: true, description: "Asset ID" },
114
+ },
115
+ async execute(input, ctx) {
116
+ const { environmentId, assetId } = input;
117
+ const { host, token, spaceId } = getConn(ctx);
118
+ return apiRequest(host, token, `/spaces/${spaceId}/environments/${environmentId}/assets/${assetId}`);
119
+ },
120
+ });
121
+ rl.registerAction("asset.list", {
122
+ description: "List assets",
123
+ inputSchema: {
124
+ environmentId: { type: "string", required: true, description: "Environment ID" },
125
+ limit: { type: "number", required: false, description: "Max results" },
126
+ },
127
+ async execute(input, ctx) {
128
+ const { environmentId, limit } = (input ?? {});
129
+ const { host, token, spaceId } = getConn(ctx);
130
+ return paginateAll(host, token, `/spaces/${spaceId}/environments/${environmentId}/assets`, undefined, limit);
131
+ },
132
+ });
133
+ // ── Locale ──────────────────────────────────────────
134
+ rl.registerAction("locale.list", {
135
+ description: "List locales",
136
+ inputSchema: {
137
+ environmentId: { type: "string", required: true, description: "Environment ID" },
138
+ limit: { type: "number", required: false, description: "Max results" },
139
+ },
140
+ async execute(input, ctx) {
141
+ const { environmentId, limit } = (input ?? {});
142
+ const { host, token, spaceId } = getConn(ctx);
143
+ return paginateAll(host, token, `/spaces/${spaceId}/environments/${environmentId}/locales`, undefined, limit);
144
+ },
145
+ });
146
+ }
@@ -0,0 +1,270 @@
1
+ const BASE_URL = "https://api.convertkit.com/v3";
2
+ async function apiRequest(apiSecret, method, endpoint, body, qs) {
3
+ const url = new URL(`${BASE_URL}${endpoint}`);
4
+ // GET requests use api_secret as query param
5
+ if (method === "GET" || method === "DELETE") {
6
+ url.searchParams.set("api_secret", apiSecret);
7
+ }
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 opts = {
15
+ method,
16
+ headers: { "Content-Type": "application/json" },
17
+ };
18
+ if (method === "POST" || method === "PUT") {
19
+ const b = { api_secret: apiSecret, ...body };
20
+ opts.body = JSON.stringify(b);
21
+ }
22
+ const res = await fetch(url.toString(), opts);
23
+ if (!res.ok)
24
+ throw new Error(`ConvertKit API error ${res.status}: ${await res.text()}`);
25
+ if (res.status === 204)
26
+ return { success: true };
27
+ return res.json();
28
+ }
29
+ function getSecret(ctx) {
30
+ return ctx.connection.config.apiSecret;
31
+ }
32
+ export default function convertkit(rl) {
33
+ rl.setName("convertkit");
34
+ rl.setVersion("0.1.0");
35
+ rl.setConnectionSchema({
36
+ apiSecret: {
37
+ type: "string",
38
+ required: true,
39
+ description: "ConvertKit API secret",
40
+ env: "CONVERTKIT_API_SECRET",
41
+ },
42
+ });
43
+ // ── Custom Field ────────────────────────────────────
44
+ rl.registerAction("customField.create", {
45
+ description: "Create a custom field",
46
+ inputSchema: {
47
+ label: { type: "string", required: true, description: "Field label" },
48
+ },
49
+ async execute(input, ctx) {
50
+ const { label } = input;
51
+ return apiRequest(getSecret(ctx), "POST", "/custom_fields", { label });
52
+ },
53
+ });
54
+ rl.registerAction("customField.get", {
55
+ description: "Get a custom field",
56
+ inputSchema: { id: { type: "string", required: true, description: "Field ID" } },
57
+ async execute(input, ctx) {
58
+ return apiRequest(getSecret(ctx), "GET", `/custom_fields/${input.id}`);
59
+ },
60
+ });
61
+ rl.registerAction("customField.list", {
62
+ description: "List custom fields",
63
+ inputSchema: { limit: { type: "number", required: false, description: "Max results" } },
64
+ async execute(input, ctx) {
65
+ const data = (await apiRequest(getSecret(ctx), "GET", "/custom_fields"));
66
+ const fields = data.custom_fields ?? [];
67
+ const { limit } = (input ?? {});
68
+ if (limit)
69
+ return fields.slice(0, limit);
70
+ return fields;
71
+ },
72
+ });
73
+ rl.registerAction("customField.update", {
74
+ description: "Update a custom field label",
75
+ inputSchema: {
76
+ id: { type: "string", required: true, description: "Field ID" },
77
+ label: { type: "string", required: true, description: "New label" },
78
+ },
79
+ async execute(input, ctx) {
80
+ const { id, label } = input;
81
+ await apiRequest(getSecret(ctx), "PUT", `/custom_fields/${id}`, { label });
82
+ return { success: true };
83
+ },
84
+ });
85
+ rl.registerAction("customField.delete", {
86
+ description: "Delete a custom field",
87
+ inputSchema: { id: { type: "string", required: true, description: "Field ID" } },
88
+ async execute(input, ctx) {
89
+ return apiRequest(getSecret(ctx), "DELETE", `/custom_fields/${input.id}`);
90
+ },
91
+ });
92
+ // ── Form ────────────────────────────────────────────
93
+ rl.registerAction("form.addSubscriber", {
94
+ description: "Add a subscriber to a form",
95
+ inputSchema: {
96
+ formId: { type: "string", required: true, description: "Form ID" },
97
+ email: { type: "string", required: true, description: "Subscriber email" },
98
+ firstName: { type: "string", required: false, description: "First name" },
99
+ tags: { type: "array", required: false, description: "Tag IDs to add" },
100
+ fields: { type: "object", required: false, description: "Custom field key-value pairs" },
101
+ },
102
+ async execute(input, ctx) {
103
+ const { formId, email, firstName, tags, fields } = input;
104
+ const body = { email };
105
+ if (firstName)
106
+ body.first_name = firstName;
107
+ if (tags)
108
+ body.tags = tags;
109
+ if (fields)
110
+ body.fields = fields;
111
+ const data = (await apiRequest(getSecret(ctx), "POST", `/forms/${formId}/subscribe`, body));
112
+ return data.subscription;
113
+ },
114
+ });
115
+ rl.registerAction("form.list", {
116
+ description: "List forms",
117
+ inputSchema: { limit: { type: "number", required: false, description: "Max results" } },
118
+ async execute(input, ctx) {
119
+ const data = (await apiRequest(getSecret(ctx), "GET", "/forms"));
120
+ const forms = data.forms ?? [];
121
+ const { limit } = (input ?? {});
122
+ if (limit)
123
+ return forms.slice(0, limit);
124
+ return forms;
125
+ },
126
+ });
127
+ rl.registerAction("form.getSubscriptions", {
128
+ description: "List subscriptions for a form",
129
+ inputSchema: {
130
+ formId: { type: "string", required: true, description: "Form ID" },
131
+ subscriberState: { type: "string", required: false, description: "Filter: active, cancelled" },
132
+ limit: { type: "number", required: false, description: "Max results" },
133
+ },
134
+ async execute(input, ctx) {
135
+ const { formId, subscriberState, limit } = (input ?? {});
136
+ const qs = {};
137
+ if (subscriberState)
138
+ qs.subscriber_state = subscriberState;
139
+ const data = (await apiRequest(getSecret(ctx), "GET", `/forms/${formId}/subscriptions`, undefined, qs));
140
+ const subs = data.subscriptions ?? [];
141
+ if (limit)
142
+ return subs.slice(0, limit);
143
+ return subs;
144
+ },
145
+ });
146
+ // ── Sequence ────────────────────────────────────────
147
+ rl.registerAction("sequence.addSubscriber", {
148
+ description: "Add a subscriber to a sequence",
149
+ inputSchema: {
150
+ sequenceId: { type: "string", required: true, description: "Sequence ID" },
151
+ email: { type: "string", required: true, description: "Subscriber email" },
152
+ firstName: { type: "string", required: false, description: "First name" },
153
+ tags: { type: "array", required: false, description: "Tag IDs" },
154
+ fields: { type: "object", required: false, description: "Custom fields" },
155
+ },
156
+ async execute(input, ctx) {
157
+ const { sequenceId, email, firstName, tags, fields } = input;
158
+ const body = { email };
159
+ if (firstName)
160
+ body.first_name = firstName;
161
+ if (tags)
162
+ body.tags = tags;
163
+ if (fields)
164
+ body.fields = fields;
165
+ const data = (await apiRequest(getSecret(ctx), "POST", `/sequences/${sequenceId}/subscribe`, body));
166
+ return data.subscription;
167
+ },
168
+ });
169
+ rl.registerAction("sequence.list", {
170
+ description: "List sequences",
171
+ inputSchema: { limit: { type: "number", required: false, description: "Max results" } },
172
+ async execute(input, ctx) {
173
+ const data = (await apiRequest(getSecret(ctx), "GET", "/sequences"));
174
+ const courses = data.courses ?? [];
175
+ const { limit } = (input ?? {});
176
+ if (limit)
177
+ return courses.slice(0, limit);
178
+ return courses;
179
+ },
180
+ });
181
+ rl.registerAction("sequence.getSubscriptions", {
182
+ description: "List subscriptions for a sequence",
183
+ inputSchema: {
184
+ sequenceId: { type: "string", required: true, description: "Sequence ID" },
185
+ subscriberState: { type: "string", required: false, description: "Filter: active, cancelled" },
186
+ limit: { type: "number", required: false, description: "Max results" },
187
+ },
188
+ async execute(input, ctx) {
189
+ const { sequenceId, subscriberState, limit } = (input ?? {});
190
+ const qs = {};
191
+ if (subscriberState)
192
+ qs.subscriber_state = subscriberState;
193
+ const data = (await apiRequest(getSecret(ctx), "GET", `/sequences/${sequenceId}/subscriptions`, undefined, qs));
194
+ const subs = data.subscriptions ?? [];
195
+ if (limit)
196
+ return subs.slice(0, limit);
197
+ return subs;
198
+ },
199
+ });
200
+ // ── Tag ─────────────────────────────────────────────
201
+ rl.registerAction("tag.create", {
202
+ description: "Create one or more tags",
203
+ inputSchema: {
204
+ names: { type: "string", required: true, description: "Comma-separated tag names" },
205
+ },
206
+ async execute(input, ctx) {
207
+ const { names } = input;
208
+ const tag = names.split(",").map((n) => ({ name: n.trim() }));
209
+ return apiRequest(getSecret(ctx), "POST", "/tags", { tag });
210
+ },
211
+ });
212
+ rl.registerAction("tag.list", {
213
+ description: "List tags",
214
+ inputSchema: { limit: { type: "number", required: false, description: "Max results" } },
215
+ async execute(input, ctx) {
216
+ const data = (await apiRequest(getSecret(ctx), "GET", "/tags"));
217
+ const tags = data.tags ?? [];
218
+ const { limit } = (input ?? {});
219
+ if (limit)
220
+ return tags.slice(0, limit);
221
+ return tags;
222
+ },
223
+ });
224
+ // ── Tag Subscriber ──────────────────────────────────
225
+ rl.registerAction("tagSubscriber.add", {
226
+ description: "Tag a subscriber",
227
+ inputSchema: {
228
+ tagId: { type: "string", required: true, description: "Tag ID" },
229
+ email: { type: "string", required: true, description: "Subscriber email" },
230
+ firstName: { type: "string", required: false, description: "First name" },
231
+ fields: { type: "object", required: false, description: "Custom fields" },
232
+ },
233
+ async execute(input, ctx) {
234
+ const { tagId, email, firstName, fields } = input;
235
+ const body = { email };
236
+ if (firstName)
237
+ body.first_name = firstName;
238
+ if (fields)
239
+ body.fields = fields;
240
+ const data = (await apiRequest(getSecret(ctx), "POST", `/tags/${tagId}/subscribe`, body));
241
+ return data.subscription;
242
+ },
243
+ });
244
+ rl.registerAction("tagSubscriber.list", {
245
+ description: "List subscribers for a tag",
246
+ inputSchema: {
247
+ tagId: { type: "string", required: true, description: "Tag ID" },
248
+ limit: { type: "number", required: false, description: "Max results" },
249
+ },
250
+ async execute(input, ctx) {
251
+ const { tagId, limit } = input;
252
+ const data = (await apiRequest(getSecret(ctx), "GET", `/tags/${tagId}/subscriptions`));
253
+ const subs = data.subscriptions ?? [];
254
+ if (limit)
255
+ return subs.slice(0, limit);
256
+ return subs;
257
+ },
258
+ });
259
+ rl.registerAction("tagSubscriber.remove", {
260
+ description: "Remove a tag from a subscriber",
261
+ inputSchema: {
262
+ tagId: { type: "string", required: true, description: "Tag ID" },
263
+ email: { type: "string", required: true, description: "Subscriber email" },
264
+ },
265
+ async execute(input, ctx) {
266
+ const { tagId, email } = input;
267
+ return apiRequest(getSecret(ctx), "POST", `/tags/${tagId}/unsubscribe`, { email });
268
+ },
269
+ });
270
+ }
@@ -0,0 +1,140 @@
1
+ const BASE_URL = "https://api.copper.com/developer_api/v1";
2
+ async function apiRequest(apiKey, email, method, endpoint, body) {
3
+ const opts = {
4
+ method,
5
+ headers: {
6
+ "Content-Type": "application/json",
7
+ "X-PW-AccessToken": apiKey,
8
+ "X-PW-Application": "developer_api",
9
+ "X-PW-UserEmail": email,
10
+ },
11
+ };
12
+ if (body && Object.keys(body).length > 0) {
13
+ opts.body = JSON.stringify(body);
14
+ }
15
+ const res = await fetch(`${BASE_URL}${endpoint}`, opts);
16
+ if (!res.ok)
17
+ throw new Error(`Copper API error ${res.status}: ${await res.text()}`);
18
+ if (res.status === 204)
19
+ return { success: true };
20
+ const ct = res.headers.get("content-type") ?? "";
21
+ if (ct.includes("application/json"))
22
+ return res.json();
23
+ return { success: true };
24
+ }
25
+ async function searchAll(apiKey, email, endpoint, body, limit) {
26
+ const results = [];
27
+ let page = 1;
28
+ const size = 200;
29
+ while (true) {
30
+ const data = (await apiRequest(apiKey, email, "POST", endpoint, {
31
+ ...body,
32
+ page_number: page,
33
+ page_size: size,
34
+ }));
35
+ if (!Array.isArray(data))
36
+ break;
37
+ results.push(...data);
38
+ if (limit && results.length >= limit)
39
+ return results.slice(0, limit);
40
+ if (data.length < size)
41
+ break;
42
+ page++;
43
+ }
44
+ return results;
45
+ }
46
+ function getConn(ctx) {
47
+ return {
48
+ apiKey: ctx.connection.config.apiKey,
49
+ email: ctx.connection.config.email,
50
+ };
51
+ }
52
+ function registerCrud(rl, resource, endpoint, idParam, nameRequired, extraCreateFields) {
53
+ rl.registerAction(`${resource}.create`, {
54
+ description: `Create a ${resource}`,
55
+ inputSchema: {
56
+ ...(nameRequired ? { name: { type: "string", required: true, description: `${resource} name` } } : {}),
57
+ ...extraCreateFields,
58
+ },
59
+ async execute(input, ctx) {
60
+ const { apiKey, email } = getConn(ctx);
61
+ return apiRequest(apiKey, email, "POST", endpoint, (input ?? {}));
62
+ },
63
+ });
64
+ rl.registerAction(`${resource}.get`, {
65
+ description: `Get a ${resource} by ID`,
66
+ inputSchema: { [idParam]: { type: "string", required: true, description: `${resource} ID` } },
67
+ async execute(input, ctx) {
68
+ const { apiKey, email } = getConn(ctx);
69
+ return apiRequest(apiKey, email, "GET", `${endpoint}/${input[idParam]}`);
70
+ },
71
+ });
72
+ rl.registerAction(`${resource}.list`, {
73
+ description: `Search/list ${resource}s`,
74
+ inputSchema: { limit: { type: "number", required: false, description: "Max results" } },
75
+ async execute(input, ctx) {
76
+ const { apiKey, email } = getConn(ctx);
77
+ const { limit } = (input ?? {});
78
+ return searchAll(apiKey, email, `${endpoint}/search`, undefined, limit);
79
+ },
80
+ });
81
+ rl.registerAction(`${resource}.update`, {
82
+ description: `Update a ${resource}`,
83
+ inputSchema: {
84
+ [idParam]: { type: "string", required: true, description: `${resource} ID` },
85
+ },
86
+ async execute(input, ctx) {
87
+ const { apiKey, email } = getConn(ctx);
88
+ const { [idParam]: id, ...body } = input;
89
+ return apiRequest(apiKey, email, "PUT", `${endpoint}/${id}`, body);
90
+ },
91
+ });
92
+ rl.registerAction(`${resource}.delete`, {
93
+ description: `Delete a ${resource}`,
94
+ inputSchema: { [idParam]: { type: "string", required: true, description: `${resource} ID` } },
95
+ async execute(input, ctx) {
96
+ const { apiKey, email } = getConn(ctx);
97
+ return apiRequest(apiKey, email, "DELETE", `${endpoint}/${input[idParam]}`);
98
+ },
99
+ });
100
+ }
101
+ export default function copper(rl) {
102
+ rl.setName("copper");
103
+ rl.setVersion("0.1.0");
104
+ rl.setConnectionSchema({
105
+ apiKey: { type: "string", required: true, description: "Copper API key", env: "COPPER_API_KEY" },
106
+ email: { type: "string", required: true, description: "Copper user email", env: "COPPER_EMAIL" },
107
+ });
108
+ // CRUD resources
109
+ registerCrud(rl, "company", "/companies", "companyId", true);
110
+ registerCrud(rl, "lead", "/leads", "leadId", true);
111
+ registerCrud(rl, "opportunity", "/opportunities", "opportunityId", true, {
112
+ customerSourceId: { type: "string", required: true, description: "Customer source ID" },
113
+ primaryContactId: { type: "string", required: true, description: "Primary contact ID" },
114
+ });
115
+ registerCrud(rl, "person", "/people", "personId", true);
116
+ registerCrud(rl, "project", "/projects", "projectId", true);
117
+ registerCrud(rl, "task", "/tasks", "taskId", true);
118
+ // Read-only resources
119
+ rl.registerAction("customerSource.list", {
120
+ description: "List customer sources",
121
+ inputSchema: { limit: { type: "number", required: false, description: "Max results" } },
122
+ async execute(input, ctx) {
123
+ const { apiKey, email } = getConn(ctx);
124
+ const data = (await apiRequest(apiKey, email, "GET", "/customer_sources"));
125
+ const { limit } = (input ?? {});
126
+ if (limit)
127
+ return data.slice(0, limit);
128
+ return data;
129
+ },
130
+ });
131
+ rl.registerAction("user.list", {
132
+ description: "List users",
133
+ inputSchema: { limit: { type: "number", required: false, description: "Max results" } },
134
+ async execute(input, ctx) {
135
+ const { apiKey, email } = getConn(ctx);
136
+ const { limit } = (input ?? {});
137
+ return searchAll(apiKey, email, "/users/search", undefined, limit);
138
+ },
139
+ });
140
+ }