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(domain, apiKey, method, endpoint, body, qs) {
2
+ const url = new URL(`https://${domain}.freshservice.com/api/v2${endpoint}`);
3
+ if (qs) {
4
+ for (const [k, v] of Object.entries(qs)) {
5
+ if (v !== undefined && v !== null)
6
+ url.searchParams.set(k, String(v));
7
+ }
8
+ }
9
+ const opts = {
10
+ method,
11
+ headers: {
12
+ Authorization: `Basic ${btoa(`${apiKey}:X`)}`,
13
+ "Content-Type": "application/json",
14
+ },
15
+ };
16
+ if (body && Object.keys(body).length > 0 && method !== "GET" && method !== "DELETE") {
17
+ opts.body = JSON.stringify(body);
18
+ }
19
+ const res = await fetch(url.toString(), opts);
20
+ if (!res.ok)
21
+ throw new Error(`Freshservice API error ${res.status}: ${await res.text()}`);
22
+ if (res.status === 204)
23
+ return { success: true };
24
+ return res.json();
25
+ }
26
+ function getConn(ctx) {
27
+ return { domain: ctx.connection.config.domain, apiKey: ctx.connection.config.apiKey };
28
+ }
29
+ function req(ctx, method, endpoint, body, qs) {
30
+ const { domain, apiKey } = getConn(ctx);
31
+ return apiRequest(domain, apiKey, method, endpoint, body, qs);
32
+ }
33
+ function unwrap(data) {
34
+ if (data && typeof data === "object" && !Array.isArray(data)) {
35
+ const keys = Object.keys(data);
36
+ if (keys.length === 1)
37
+ return data[keys[0]];
38
+ }
39
+ return data;
40
+ }
41
+ function registerCrud(rl, resource, apiPath, singularKey, opts) {
42
+ rl.registerAction(`${resource}.create`, {
43
+ description: `Create a ${resource}`,
44
+ inputSchema: {
45
+ ...(opts?.extraCreateFields ?? {}),
46
+ properties: { type: "object", required: true, description: `${resource} properties as key-value pairs` },
47
+ },
48
+ async execute(input, ctx) {
49
+ const { properties, ...rest } = input;
50
+ const body = { ...properties, ...rest };
51
+ return unwrap(await req(ctx, "POST", apiPath, body));
52
+ },
53
+ });
54
+ rl.registerAction(`${resource}.get`, {
55
+ description: `Get a ${resource} by ID`,
56
+ inputSchema: { id: { type: "number", required: true, description: `${resource} ID` } },
57
+ async execute(input, ctx) {
58
+ return unwrap(await req(ctx, "GET", `${apiPath}/${input.id}`));
59
+ },
60
+ });
61
+ rl.registerAction(`${resource}.list`, {
62
+ description: `List ${resource}s`,
63
+ inputSchema: {
64
+ limit: { type: "number", required: false, description: "Max results" },
65
+ page: { type: "number", required: false, description: "Page number" },
66
+ },
67
+ async execute(input, ctx) {
68
+ const { limit, page } = (input ?? {});
69
+ const qs = {};
70
+ if (limit)
71
+ qs.per_page = limit;
72
+ if (page)
73
+ qs.page = page;
74
+ return unwrap(await req(ctx, "GET", apiPath, undefined, qs));
75
+ },
76
+ });
77
+ rl.registerAction(`${resource}.update`, {
78
+ description: `Update a ${resource}`,
79
+ inputSchema: {
80
+ id: { type: "number", required: true, description: `${resource} ID` },
81
+ properties: { type: "object", required: true, description: "Fields to update" },
82
+ },
83
+ async execute(input, ctx) {
84
+ const { id, properties } = input;
85
+ return unwrap(await req(ctx, "PUT", `${apiPath}/${id}`, properties));
86
+ },
87
+ });
88
+ if (!opts?.noDelete) {
89
+ rl.registerAction(`${resource}.delete`, {
90
+ description: `Delete a ${resource}`,
91
+ inputSchema: { id: { type: "number", required: true, description: `${resource} ID` } },
92
+ async execute(input, ctx) {
93
+ await req(ctx, "DELETE", `${apiPath}/${input.id}`);
94
+ return { success: true };
95
+ },
96
+ });
97
+ }
98
+ }
99
+ export default function freshservice(rl) {
100
+ rl.setName("freshservice");
101
+ rl.setVersion("0.1.0");
102
+ rl.setConnectionSchema({
103
+ domain: { type: "string", required: true, description: "Freshservice subdomain (e.g. 'mycompany')", env: "FRESHSERVICE_DOMAIN" },
104
+ apiKey: { type: "string", required: true, description: "Freshservice API key", env: "FRESHSERVICE_API_KEY" },
105
+ });
106
+ // 16 resources, all CRUD
107
+ registerCrud(rl, "agent", "/agents", "agent");
108
+ registerCrud(rl, "agentGroup", "/groups", "group");
109
+ registerCrud(rl, "announcement", "/announcements", "announcement");
110
+ registerCrud(rl, "asset", "/assets", "asset");
111
+ registerCrud(rl, "assetType", "/asset_types", "asset_type");
112
+ registerCrud(rl, "change", "/changes", "change");
113
+ registerCrud(rl, "department", "/departments", "department");
114
+ registerCrud(rl, "location", "/locations", "location");
115
+ registerCrud(rl, "problem", "/problems", "problem");
116
+ registerCrud(rl, "product", "/products", "product");
117
+ registerCrud(rl, "release", "/releases", "release");
118
+ registerCrud(rl, "requester", "/requesters", "requester");
119
+ registerCrud(rl, "requesterGroup", "/requester_groups", "requester_group");
120
+ registerCrud(rl, "software", "/applications", "application");
121
+ registerCrud(rl, "ticket", "/tickets", "ticket");
122
+ // agentRole is read-only (get + list only)
123
+ rl.registerAction("agentRole.get", {
124
+ description: "Get an agent role by ID",
125
+ inputSchema: { id: { type: "number", required: true, description: "Role ID" } },
126
+ async execute(input, ctx) {
127
+ return unwrap(await req(ctx, "GET", `/roles/${input.id}`));
128
+ },
129
+ });
130
+ rl.registerAction("agentRole.list", {
131
+ description: "List agent roles",
132
+ inputSchema: {
133
+ limit: { type: "number", required: false, description: "Max results" },
134
+ page: { type: "number", required: false, description: "Page number" },
135
+ },
136
+ async execute(input, ctx) {
137
+ const { limit, page } = (input ?? {});
138
+ const qs = {};
139
+ if (limit)
140
+ qs.per_page = limit;
141
+ if (page)
142
+ qs.page = page;
143
+ return unwrap(await req(ctx, "GET", "/roles", undefined, qs));
144
+ },
145
+ });
146
+ }
@@ -0,0 +1,149 @@
1
+ async function apiRequest(domain, apiKey, method, endpoint, body, qs) {
2
+ const url = new URL(`https://${domain}.myfreshworks.com/crm/sales/api${endpoint}`);
3
+ if (qs) {
4
+ for (const [k, v] of Object.entries(qs)) {
5
+ if (v !== undefined && v !== null)
6
+ url.searchParams.set(k, String(v));
7
+ }
8
+ }
9
+ const opts = {
10
+ method,
11
+ headers: {
12
+ Authorization: `Token token=${apiKey}`,
13
+ "Content-Type": "application/json",
14
+ },
15
+ };
16
+ if (body && Object.keys(body).length > 0 && method !== "GET" && method !== "DELETE") {
17
+ opts.body = JSON.stringify(body);
18
+ }
19
+ const res = await fetch(url.toString(), opts);
20
+ if (!res.ok)
21
+ throw new Error(`Freshworks CRM API error ${res.status}: ${await res.text()}`);
22
+ if (res.status === 204)
23
+ return { success: true };
24
+ return res.json();
25
+ }
26
+ function getConn(ctx) {
27
+ return { domain: ctx.connection.config.domain, apiKey: ctx.connection.config.apiKey };
28
+ }
29
+ function req(ctx, method, endpoint, body, qs) {
30
+ const { domain, apiKey } = getConn(ctx);
31
+ return apiRequest(domain, apiKey, method, endpoint, body, qs);
32
+ }
33
+ function unwrap(data) {
34
+ if (data && typeof data === "object" && !Array.isArray(data)) {
35
+ const keys = Object.keys(data);
36
+ if (keys.length === 1)
37
+ return data[keys[0]];
38
+ }
39
+ return data;
40
+ }
41
+ function registerCrud(rl, resource, apiPath, wrapKey, opts) {
42
+ rl.registerAction(`${resource}.create`, {
43
+ description: `Create a ${resource}`,
44
+ inputSchema: { properties: { type: "object", required: true, description: `${resource} properties` } },
45
+ async execute(input, ctx) {
46
+ const { properties } = input;
47
+ return unwrap(await req(ctx, "POST", apiPath, { [wrapKey]: properties }));
48
+ },
49
+ });
50
+ if (!opts?.noGet) {
51
+ rl.registerAction(`${resource}.get`, {
52
+ description: `Get a ${resource} by ID`,
53
+ inputSchema: { id: { type: "number", required: true, description: `${resource} ID` } },
54
+ async execute(input, ctx) {
55
+ return unwrap(await req(ctx, "GET", `${apiPath}/${input.id}`));
56
+ },
57
+ });
58
+ }
59
+ if (!opts?.noList) {
60
+ rl.registerAction(`${resource}.list`, {
61
+ description: `List ${resource}s`,
62
+ inputSchema: {
63
+ limit: { type: "number", required: false, description: "Max results" },
64
+ page: { type: "number", required: false, description: "Page number" },
65
+ },
66
+ async execute(input, ctx) {
67
+ const { limit, page } = (input ?? {});
68
+ const qs = {};
69
+ if (limit)
70
+ qs.per_page = limit;
71
+ if (page)
72
+ qs.page = page;
73
+ return unwrap(await req(ctx, "GET", apiPath, undefined, qs));
74
+ },
75
+ });
76
+ }
77
+ rl.registerAction(`${resource}.update`, {
78
+ description: `Update a ${resource}`,
79
+ inputSchema: {
80
+ id: { type: "number", required: true, description: `${resource} ID` },
81
+ properties: { type: "object", required: true, description: "Fields to update" },
82
+ },
83
+ async execute(input, ctx) {
84
+ const { id, properties } = input;
85
+ return unwrap(await req(ctx, "PUT", `${apiPath}/${id}`, { [wrapKey]: properties }));
86
+ },
87
+ });
88
+ if (!opts?.noDelete) {
89
+ rl.registerAction(`${resource}.delete`, {
90
+ description: `Delete a ${resource}`,
91
+ inputSchema: { id: { type: "number", required: true, description: `${resource} ID` } },
92
+ async execute(input, ctx) {
93
+ await req(ctx, "DELETE", `${apiPath}/${input.id}`);
94
+ return { success: true };
95
+ },
96
+ });
97
+ }
98
+ }
99
+ export default function freshworksCrm(rl) {
100
+ rl.setName("freshworksCrm");
101
+ rl.setVersion("0.1.0");
102
+ rl.setConnectionSchema({
103
+ domain: { type: "string", required: true, description: "Freshworks CRM subdomain", env: "FRESHWORKS_CRM_DOMAIN" },
104
+ apiKey: { type: "string", required: true, description: "Freshworks CRM API key", env: "FRESHWORKS_CRM_API_KEY" },
105
+ });
106
+ registerCrud(rl, "account", "/sales_accounts", "sales_account");
107
+ registerCrud(rl, "appointment", "/appointments", "appointment");
108
+ registerCrud(rl, "contact", "/contacts", "contact");
109
+ registerCrud(rl, "deal", "/deals", "deal");
110
+ registerCrud(rl, "note", "/notes", "note", { noGet: true, noList: true });
111
+ registerCrud(rl, "salesActivity", "/sales_activities", "sales_activity");
112
+ registerCrud(rl, "task", "/tasks", "task");
113
+ // ── Search ──────────────────────────────────────────
114
+ rl.registerAction("search.query", {
115
+ description: "Search across entities using a query string",
116
+ inputSchema: {
117
+ query: { type: "string", required: true, description: "Search query" },
118
+ entities: { type: "string", required: false, description: "Comma-separated entities to search (contact, deal, sales_account)" },
119
+ perPage: { type: "number", required: false, description: "Results per page" },
120
+ page: { type: "number", required: false, description: "Page number" },
121
+ },
122
+ async execute(input, ctx) {
123
+ const { query, entities, perPage, page } = (input ?? {});
124
+ const qs = { q: query };
125
+ if (entities)
126
+ qs.entities = entities;
127
+ if (perPage)
128
+ qs.per_page = perPage;
129
+ if (page)
130
+ qs.page = page;
131
+ return req(ctx, "GET", "/search", undefined, qs);
132
+ },
133
+ });
134
+ rl.registerAction("search.lookup", {
135
+ description: "Lookup a record by field value",
136
+ inputSchema: {
137
+ query: { type: "string", required: true, description: "Value to search" },
138
+ field: { type: "string", required: true, description: "Field to search (e.g. email, name)" },
139
+ entities: { type: "string", required: false, description: "Entity type to search" },
140
+ },
141
+ async execute(input, ctx) {
142
+ const { query, field, entities } = input;
143
+ const qs = { q: query, f: field };
144
+ if (entities)
145
+ qs.entities = entities;
146
+ return req(ctx, "GET", "/lookup", undefined, qs);
147
+ },
148
+ });
149
+ }
@@ -0,0 +1,140 @@
1
+ const BASE_URL = "https://api.getresponse.com/v3";
2
+ async function apiRequest(apiKey, 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
+ "X-Auth-Token": `api-key ${apiKey}`,
14
+ "Content-Type": "application/json",
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
+ throw new Error(`GetResponse API error ${res.status}: ${await res.text()}`);
23
+ if (res.status === 204 || res.status === 202)
24
+ return { success: true };
25
+ return res.json();
26
+ }
27
+ export default function getresponse(rl) {
28
+ rl.setName("getresponse");
29
+ rl.setVersion("0.1.0");
30
+ rl.setConnectionSchema({
31
+ apiKey: { type: "string", required: true, description: "GetResponse API key", env: "GETRESPONSE_API_KEY" },
32
+ });
33
+ const key = (ctx) => ctx.connection.config.apiKey;
34
+ rl.registerAction("contact.create", {
35
+ description: "Create a contact",
36
+ inputSchema: {
37
+ email: { type: "string", required: true, description: "Email address" },
38
+ campaignId: { type: "string", required: true, description: "Campaign ID to subscribe to" },
39
+ name: { type: "string", required: false, description: "Contact name" },
40
+ dayOfCycle: { type: "number", required: false, description: "Day of autoresponder cycle" },
41
+ tags: { type: "array", required: false, description: "Array of {tagId} objects" },
42
+ customFieldValues: { type: "array", required: false, description: "Custom fields as [{customFieldId, value: [values]}]" },
43
+ },
44
+ async execute(input, ctx) {
45
+ const { email, campaignId, name, dayOfCycle, tags, customFieldValues } = input;
46
+ const body = { email, campaign: { campaignId } };
47
+ if (name)
48
+ body.name = name;
49
+ if (dayOfCycle !== undefined)
50
+ body.dayOfCycle = dayOfCycle;
51
+ if (tags)
52
+ body.tags = tags;
53
+ if (customFieldValues)
54
+ body.customFieldValues = customFieldValues;
55
+ await apiRequest(key(ctx), "POST", "/contacts", body);
56
+ return { success: true };
57
+ },
58
+ });
59
+ rl.registerAction("contact.get", {
60
+ description: "Get a contact by ID",
61
+ inputSchema: {
62
+ contactId: { type: "string", required: true, description: "Contact ID" },
63
+ fields: { type: "string", required: false, description: "Comma-separated fields to return" },
64
+ },
65
+ async execute(input, ctx) {
66
+ const { contactId, fields } = (input ?? {});
67
+ const qs = {};
68
+ if (fields)
69
+ qs.fields = fields;
70
+ return apiRequest(key(ctx), "GET", `/contacts/${contactId}`, undefined, qs);
71
+ },
72
+ });
73
+ rl.registerAction("contact.list", {
74
+ description: "List contacts",
75
+ inputSchema: {
76
+ limit: { type: "number", required: false, description: "Max results (default: 100)" },
77
+ email: { type: "string", required: false, description: "Filter by email" },
78
+ name: { type: "string", required: false, description: "Filter by name" },
79
+ campaignId: { type: "string", required: false, description: "Filter by campaign ID" },
80
+ sortBy: { type: "string", required: false, description: "Sort field: email, name, createdOn" },
81
+ sortOrder: { type: "string", required: false, description: "ASC or DESC" },
82
+ },
83
+ async execute(input, ctx) {
84
+ const { limit, email, name, campaignId, sortBy, sortOrder } = (input ?? {});
85
+ const qs = {};
86
+ if (limit)
87
+ qs.perPage = limit;
88
+ if (email)
89
+ qs["query[email]"] = email;
90
+ if (name)
91
+ qs["query[name]"] = name;
92
+ if (campaignId)
93
+ qs["query[campaignId]"] = campaignId;
94
+ if (sortBy)
95
+ qs[`sort[${sortBy}]`] = sortOrder ?? "ASC";
96
+ return apiRequest(key(ctx), "GET", "/contacts", undefined, qs);
97
+ },
98
+ });
99
+ rl.registerAction("contact.update", {
100
+ description: "Update a contact",
101
+ inputSchema: {
102
+ contactId: { type: "string", required: true, description: "Contact ID" },
103
+ name: { type: "string", required: false, description: "New name" },
104
+ campaignId: { type: "string", required: false, description: "Move to campaign" },
105
+ tags: { type: "array", required: false, description: "Tags as [{tagId}]" },
106
+ customFieldValues: { type: "array", required: false, description: "Custom fields as [{customFieldId, value: [values]}]" },
107
+ },
108
+ async execute(input, ctx) {
109
+ const { contactId, name, campaignId, tags, customFieldValues } = input;
110
+ const body = {};
111
+ if (name)
112
+ body.name = name;
113
+ if (campaignId)
114
+ body.campaign = { campaignId };
115
+ if (tags)
116
+ body.tags = tags;
117
+ if (customFieldValues)
118
+ body.customFieldValues = customFieldValues;
119
+ return apiRequest(key(ctx), "POST", `/contacts/${contactId}`, body);
120
+ },
121
+ });
122
+ rl.registerAction("contact.delete", {
123
+ description: "Delete a contact",
124
+ inputSchema: {
125
+ contactId: { type: "string", required: true, description: "Contact ID" },
126
+ messageId: { type: "string", required: false, description: "ID of removal confirmation message" },
127
+ ipAddress: { type: "string", required: false, description: "IP address for GDPR consent" },
128
+ },
129
+ async execute(input, ctx) {
130
+ const { contactId, messageId, ipAddress } = (input ?? {});
131
+ const qs = {};
132
+ if (messageId)
133
+ qs.messageId = messageId;
134
+ if (ipAddress)
135
+ qs.ipAddress = ipAddress;
136
+ await apiRequest(key(ctx), "DELETE", `/contacts/${contactId}`, undefined, qs);
137
+ return { success: true };
138
+ },
139
+ });
140
+ }
@@ -0,0 +1,192 @@
1
+ async function apiRequest(url, adminApiKey, method, endpoint, body, qs) {
2
+ // Ghost Admin API uses JWT. Key format: {id}:{secret}
3
+ const [id, secret] = adminApiKey.split(":");
4
+ // Create JWT manually using HMAC-SHA256
5
+ const header = btoa(JSON.stringify({ alg: "HS256", typ: "JWT", kid: id })).replace(/=/g, "");
6
+ const now = Math.floor(Date.now() / 1000);
7
+ const payload = btoa(JSON.stringify({ iat: now, exp: now + 300, aud: "/admin/" })).replace(/=/g, "");
8
+ const enc = new TextEncoder();
9
+ const keyData = new Uint8Array((secret.match(/.{2}/g) ?? []).map((b) => parseInt(b, 16)));
10
+ const cryptoKey = await crypto.subtle.importKey("raw", keyData, { name: "HMAC", hash: "SHA-256" }, false, ["sign"]);
11
+ const sig = await crypto.subtle.sign("HMAC", cryptoKey, enc.encode(`${header}.${payload}`));
12
+ const sigStr = btoa(String.fromCharCode(...new Uint8Array(sig)))
13
+ .replace(/\+/g, "-").replace(/\//g, "_").replace(/=/g, "");
14
+ const token = `${header}.${payload}.${sigStr}`;
15
+ const base = url.replace(/\/$/, "");
16
+ const fullUrl = new URL(`${base}/ghost/api/v2/admin${endpoint}`);
17
+ if (qs) {
18
+ for (const [k, v] of Object.entries(qs)) {
19
+ if (v !== undefined && v !== null)
20
+ fullUrl.searchParams.set(k, String(v));
21
+ }
22
+ }
23
+ const opts = {
24
+ method,
25
+ headers: { Authorization: `Ghost ${token}`, "Content-Type": "application/json" },
26
+ };
27
+ if (body && Object.keys(body).length > 0 && method !== "GET" && method !== "DELETE") {
28
+ opts.body = JSON.stringify(body);
29
+ }
30
+ const res = await fetch(fullUrl.toString(), opts);
31
+ if (!res.ok)
32
+ throw new Error(`Ghost API error ${res.status}: ${await res.text()}`);
33
+ if (res.status === 204)
34
+ return { success: true };
35
+ return res.json();
36
+ }
37
+ function getConn(ctx) {
38
+ return {
39
+ url: ctx.connection.config.url.replace(/\/$/, ""),
40
+ adminApiKey: ctx.connection.config.adminApiKey,
41
+ };
42
+ }
43
+ function req(ctx, method, endpoint, body, qs) {
44
+ const { url, adminApiKey } = getConn(ctx);
45
+ return apiRequest(url, adminApiKey, method, endpoint, body, qs);
46
+ }
47
+ export default function ghost(rl) {
48
+ rl.setName("ghost");
49
+ rl.setVersion("0.1.0");
50
+ rl.setConnectionSchema({
51
+ url: { type: "string", required: true, description: "Ghost site URL (e.g. https://myblog.com)", env: "GHOST_URL" },
52
+ adminApiKey: { type: "string", required: true, description: "Admin API key (format: {id}:{secret})", env: "GHOST_ADMIN_API_KEY" },
53
+ });
54
+ rl.registerAction("post.create", {
55
+ description: "Create a post",
56
+ inputSchema: {
57
+ title: { type: "string", required: true, description: "Post title" },
58
+ html: { type: "string", required: false, description: "Post content as HTML" },
59
+ lexical: { type: "string", required: false, description: "Post content as Lexical JSON" },
60
+ status: { type: "string", required: false, description: "draft (default), published, or scheduled" },
61
+ publishedAt: { type: "string", required: false, description: "Publish date (ISO 8601, required for scheduled)" },
62
+ tags: { type: "array", required: false, description: "Array of tag names or {name} objects" },
63
+ authors: { type: "array", required: false, description: "Array of {id} objects" },
64
+ featured: { type: "boolean", required: false, description: "Mark as featured" },
65
+ slug: { type: "string", required: false, description: "Custom slug" },
66
+ },
67
+ async execute(input, ctx) {
68
+ const { title, html, lexical, status, publishedAt, tags, authors, featured, slug } = input;
69
+ const post = { title };
70
+ const qs = {};
71
+ if (html) {
72
+ post.html = html;
73
+ qs.source = "html";
74
+ }
75
+ if (lexical)
76
+ post.lexical = lexical;
77
+ if (status)
78
+ post.status = status;
79
+ if (publishedAt)
80
+ post.published_at = publishedAt;
81
+ if (tags)
82
+ post.tags = tags.map((t) => typeof t === "string" ? { name: t } : t);
83
+ if (authors)
84
+ post.authors = authors;
85
+ if (featured !== undefined)
86
+ post.featured = featured;
87
+ if (slug)
88
+ post.slug = slug;
89
+ const data = (await req(ctx, "POST", "/posts/", { posts: [post] }, qs));
90
+ return data.posts?.[0];
91
+ },
92
+ });
93
+ rl.registerAction("post.get", {
94
+ description: "Get a post by ID or slug",
95
+ inputSchema: {
96
+ id: { type: "string", required: false, description: "Post ID" },
97
+ slug: { type: "string", required: false, description: "Post slug" },
98
+ formats: { type: "string", required: false, description: "Response formats: html, mobiledoc, lexical" },
99
+ },
100
+ async execute(input, ctx) {
101
+ const { id, slug, formats } = (input ?? {});
102
+ const qs = {};
103
+ if (formats)
104
+ qs.formats = formats;
105
+ let endpoint;
106
+ if (slug)
107
+ endpoint = `/posts/slug/${slug}/`;
108
+ else if (id)
109
+ endpoint = `/posts/${id}/`;
110
+ else
111
+ throw new Error("Provide either id or slug");
112
+ const data = (await req(ctx, "GET", endpoint, undefined, qs));
113
+ return data.posts?.[0];
114
+ },
115
+ });
116
+ rl.registerAction("post.list", {
117
+ description: "List posts",
118
+ inputSchema: {
119
+ limit: { type: "number", required: false, description: "Max results (default: 15)" },
120
+ page: { type: "number", required: false, description: "Page number" },
121
+ filter: { type: "string", required: false, description: "Ghost filter string (e.g. 'tag:news')" },
122
+ formats: { type: "string", required: false, description: "Response formats" },
123
+ order: { type: "string", required: false, description: "Order (e.g. 'published_at desc')" },
124
+ },
125
+ async execute(input, ctx) {
126
+ const { limit, page, filter, formats, order } = (input ?? {});
127
+ const qs = {};
128
+ if (limit)
129
+ qs.limit = limit;
130
+ if (page)
131
+ qs.page = page;
132
+ if (filter)
133
+ qs.filter = filter;
134
+ if (formats)
135
+ qs.formats = formats;
136
+ if (order)
137
+ qs.order = order;
138
+ const data = (await req(ctx, "GET", "/posts/", undefined, qs));
139
+ return data.posts;
140
+ },
141
+ });
142
+ rl.registerAction("post.update", {
143
+ description: "Update a post",
144
+ inputSchema: {
145
+ postId: { type: "string", required: true, description: "Post ID" },
146
+ title: { type: "string", required: false, description: "New title" },
147
+ html: { type: "string", required: false, description: "New HTML content" },
148
+ lexical: { type: "string", required: false, description: "New Lexical JSON content" },
149
+ status: { type: "string", required: false, description: "New status" },
150
+ publishedAt: { type: "string", required: false, description: "New publish date" },
151
+ tags: { type: "array", required: false, description: "New tags" },
152
+ featured: { type: "boolean", required: false, description: "Featured flag" },
153
+ slug: { type: "string", required: false, description: "New slug" },
154
+ },
155
+ async execute(input, ctx) {
156
+ const { postId, title, html, lexical, status, publishedAt, tags, featured, slug } = input;
157
+ // Need updated_at for optimistic locking
158
+ const existing = (await req(ctx, "GET", `/posts/${postId}/`, undefined, { fields: "id,updated_at" }));
159
+ const currentPost = existing.posts[0];
160
+ const post = { updated_at: currentPost.updated_at };
161
+ const qs = {};
162
+ if (title)
163
+ post.title = title;
164
+ if (html) {
165
+ post.html = html;
166
+ qs.source = "html";
167
+ }
168
+ if (lexical)
169
+ post.lexical = lexical;
170
+ if (status)
171
+ post.status = status;
172
+ if (publishedAt)
173
+ post.published_at = publishedAt;
174
+ if (tags)
175
+ post.tags = tags.map((t) => typeof t === "string" ? { name: t } : t);
176
+ if (featured !== undefined)
177
+ post.featured = featured;
178
+ if (slug)
179
+ post.slug = slug;
180
+ const data = (await req(ctx, "PUT", `/posts/${postId}/`, { posts: [post] }, qs));
181
+ return data.posts?.[0];
182
+ },
183
+ });
184
+ rl.registerAction("post.delete", {
185
+ description: "Delete a post",
186
+ inputSchema: { postId: { type: "string", required: true, description: "Post ID" } },
187
+ async execute(input, ctx) {
188
+ await req(ctx, "DELETE", `/posts/${input.postId}/`);
189
+ return { success: true };
190
+ },
191
+ });
192
+ }