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,1043 @@
1
+ const BASE_URL = "https://api.clickup.com/api/v2";
2
+ async function apiRequest(token, method, endpoint, body, qs) {
3
+ const url = new URL(`${BASE_URL}${endpoint}`);
4
+ if (qs) {
5
+ for (const [k, v] of Object.entries(qs)) {
6
+ if (v !== undefined && v !== null) {
7
+ if (Array.isArray(v)) {
8
+ for (const item of v)
9
+ url.searchParams.append(`${k}[]`, String(item));
10
+ }
11
+ else {
12
+ url.searchParams.set(k, String(v));
13
+ }
14
+ }
15
+ }
16
+ }
17
+ const opts = {
18
+ method,
19
+ headers: { "Content-Type": "application/json", Authorization: token },
20
+ };
21
+ if (body && Object.keys(body).length > 0 && method !== "GET" && method !== "DELETE") {
22
+ opts.body = JSON.stringify(body);
23
+ }
24
+ const res = await fetch(url.toString(), opts);
25
+ if (!res.ok) {
26
+ const text = await res.text();
27
+ throw new Error(`ClickUp API error ${res.status}: ${text}`);
28
+ }
29
+ if (res.status === 204)
30
+ return { success: true };
31
+ const ct = res.headers.get("content-type") ?? "";
32
+ if (ct.includes("application/json"))
33
+ return res.json();
34
+ return { success: true };
35
+ }
36
+ async function paginateAll(token, endpoint, property, qs, limit) {
37
+ const results = [];
38
+ let page = 0;
39
+ while (true) {
40
+ const data = (await apiRequest(token, "GET", endpoint, undefined, { ...qs, page }));
41
+ const items = data[property] ?? [];
42
+ results.push(...items);
43
+ if (limit && results.length >= limit)
44
+ return results.slice(0, limit);
45
+ if (items.length === 0 || data.last_page)
46
+ break;
47
+ page++;
48
+ }
49
+ return results;
50
+ }
51
+ function getToken(ctx) {
52
+ return ctx.connection.config.accessToken;
53
+ }
54
+ export default function clickup(rl) {
55
+ rl.setName("clickup");
56
+ rl.setVersion("0.1.0");
57
+ rl.setConnectionSchema({
58
+ accessToken: {
59
+ type: "string",
60
+ required: true,
61
+ description: "ClickUp API token (personal or OAuth2 access token)",
62
+ env: "CLICKUP_ACCESS_TOKEN",
63
+ },
64
+ });
65
+ // ── Checklist ───────────────────────────────────────
66
+ rl.registerAction("checklist.create", {
67
+ description: "Create a checklist on a task",
68
+ inputSchema: {
69
+ taskId: { type: "string", required: true, description: "Task ID" },
70
+ name: { type: "string", required: true, description: "Checklist name" },
71
+ },
72
+ async execute(input, ctx) {
73
+ const { taskId, name } = input;
74
+ const data = (await apiRequest(getToken(ctx), "POST", `/task/${taskId}/checklist`, { name }));
75
+ return data.checklist;
76
+ },
77
+ });
78
+ rl.registerAction("checklist.update", {
79
+ description: "Update a checklist",
80
+ inputSchema: {
81
+ checklistId: { type: "string", required: true, description: "Checklist ID" },
82
+ name: { type: "string", required: false, description: "New name" },
83
+ position: { type: "number", required: false, description: "Position" },
84
+ },
85
+ async execute(input, ctx) {
86
+ const { checklistId, ...body } = input;
87
+ const data = (await apiRequest(getToken(ctx), "PUT", `/checklist/${checklistId}`, body));
88
+ return data.checklist;
89
+ },
90
+ });
91
+ rl.registerAction("checklist.delete", {
92
+ description: "Delete a checklist",
93
+ inputSchema: {
94
+ checklistId: { type: "string", required: true, description: "Checklist ID" },
95
+ },
96
+ async execute(input, ctx) {
97
+ const { checklistId } = input;
98
+ await apiRequest(getToken(ctx), "DELETE", `/checklist/${checklistId}`);
99
+ return { success: true };
100
+ },
101
+ });
102
+ // ── Checklist Item ──────────────────────────────────
103
+ rl.registerAction("checklistItem.create", {
104
+ description: "Create a checklist item",
105
+ inputSchema: {
106
+ checklistId: { type: "string", required: true, description: "Checklist ID" },
107
+ name: { type: "string", required: true, description: "Item name" },
108
+ assignee: { type: "number", required: false, description: "Assignee user ID" },
109
+ },
110
+ async execute(input, ctx) {
111
+ const { checklistId, name, assignee } = input;
112
+ const body = { name };
113
+ if (assignee)
114
+ body.assignee = assignee;
115
+ const data = (await apiRequest(getToken(ctx), "POST", `/checklist/${checklistId}/checklist_item`, body));
116
+ return data.checklist;
117
+ },
118
+ });
119
+ rl.registerAction("checklistItem.update", {
120
+ description: "Update a checklist item",
121
+ inputSchema: {
122
+ checklistId: { type: "string", required: true, description: "Checklist ID" },
123
+ checklistItemId: { type: "string", required: true, description: "Checklist item ID" },
124
+ name: { type: "string", required: false, description: "Name" },
125
+ assignee: { type: "number", required: false, description: "Assignee" },
126
+ resolved: { type: "boolean", required: false, description: "Resolved" },
127
+ parent: { type: "string", required: false, description: "Parent checklist item ID" },
128
+ },
129
+ async execute(input, ctx) {
130
+ const { checklistId, checklistItemId, ...body } = input;
131
+ const data = (await apiRequest(getToken(ctx), "PUT", `/checklist/${checklistId}/checklist_item/${checklistItemId}`, body));
132
+ return data.checklist;
133
+ },
134
+ });
135
+ rl.registerAction("checklistItem.delete", {
136
+ description: "Delete a checklist item",
137
+ inputSchema: {
138
+ checklistId: { type: "string", required: true, description: "Checklist ID" },
139
+ checklistItemId: { type: "string", required: true, description: "Checklist item ID" },
140
+ },
141
+ async execute(input, ctx) {
142
+ const { checklistId, checklistItemId } = input;
143
+ await apiRequest(getToken(ctx), "DELETE", `/checklist/${checklistId}/checklist_item/${checklistItemId}`);
144
+ return { success: true };
145
+ },
146
+ });
147
+ // ── Comment ─────────────────────────────────────────
148
+ rl.registerAction("comment.create", {
149
+ description: "Create a comment on a task, view, or list",
150
+ inputSchema: {
151
+ commentOn: { type: "string", required: true, description: "Resource type: task, view, or list" },
152
+ id: { type: "string", required: true, description: "Resource ID" },
153
+ commentText: { type: "string", required: true, description: "Comment text" },
154
+ assignee: { type: "number", required: false, description: "Assignee user ID" },
155
+ notifyAll: { type: "boolean", required: false, description: "Notify all assignees" },
156
+ },
157
+ async execute(input, ctx) {
158
+ const { commentOn, id, commentText, assignee, notifyAll } = input;
159
+ const body = { comment_text: commentText };
160
+ if (assignee)
161
+ body.assignee = assignee;
162
+ if (notifyAll)
163
+ body.notify_all = notifyAll;
164
+ return apiRequest(getToken(ctx), "POST", `/${commentOn}/${id}/comment`, body);
165
+ },
166
+ });
167
+ rl.registerAction("comment.list", {
168
+ description: "List comments on a task, view, or list",
169
+ inputSchema: {
170
+ commentsOn: { type: "string", required: true, description: "Resource type: task, view, or list" },
171
+ id: { type: "string", required: true, description: "Resource ID" },
172
+ limit: { type: "number", required: false, description: "Max results" },
173
+ },
174
+ async execute(input, ctx) {
175
+ const { commentsOn, id, limit } = input;
176
+ const data = (await apiRequest(getToken(ctx), "GET", `/${commentsOn}/${id}/comment`));
177
+ const comments = data.comments ?? [];
178
+ if (limit)
179
+ return comments.slice(0, limit);
180
+ return comments;
181
+ },
182
+ });
183
+ rl.registerAction("comment.update", {
184
+ description: "Update a comment",
185
+ inputSchema: {
186
+ commentId: { type: "string", required: true, description: "Comment ID" },
187
+ commentText: { type: "string", required: false, description: "New text" },
188
+ assignee: { type: "number", required: false, description: "Assignee" },
189
+ resolved: { type: "boolean", required: false, description: "Resolved" },
190
+ },
191
+ async execute(input, ctx) {
192
+ const { commentId, commentText, assignee, resolved } = input;
193
+ const body = {};
194
+ if (commentText)
195
+ body.comment_text = commentText;
196
+ if (assignee)
197
+ body.assignee = assignee;
198
+ if (resolved !== undefined)
199
+ body.resolved = resolved;
200
+ await apiRequest(getToken(ctx), "PUT", `/comment/${commentId}`, body);
201
+ return { success: true };
202
+ },
203
+ });
204
+ rl.registerAction("comment.delete", {
205
+ description: "Delete a comment",
206
+ inputSchema: {
207
+ commentId: { type: "string", required: true, description: "Comment ID" },
208
+ },
209
+ async execute(input, ctx) {
210
+ const { commentId } = input;
211
+ await apiRequest(getToken(ctx), "DELETE", `/comment/${commentId}`);
212
+ return { success: true };
213
+ },
214
+ });
215
+ // ── Folder ──────────────────────────────────────────
216
+ rl.registerAction("folder.create", {
217
+ description: "Create a folder in a space",
218
+ inputSchema: {
219
+ spaceId: { type: "string", required: true, description: "Space ID" },
220
+ name: { type: "string", required: true, description: "Folder name" },
221
+ },
222
+ async execute(input, ctx) {
223
+ const { spaceId, name } = input;
224
+ return apiRequest(getToken(ctx), "POST", `/space/${spaceId}/folder`, { name });
225
+ },
226
+ });
227
+ rl.registerAction("folder.get", {
228
+ description: "Get a folder",
229
+ inputSchema: { folderId: { type: "string", required: true, description: "Folder ID" } },
230
+ async execute(input, ctx) {
231
+ return apiRequest(getToken(ctx), "GET", `/folder/${input.folderId}`);
232
+ },
233
+ });
234
+ rl.registerAction("folder.list", {
235
+ description: "List folders in a space",
236
+ inputSchema: {
237
+ spaceId: { type: "string", required: true, description: "Space ID" },
238
+ archived: { type: "boolean", required: false, description: "Include archived" },
239
+ limit: { type: "number", required: false, description: "Max results" },
240
+ },
241
+ async execute(input, ctx) {
242
+ const { spaceId, archived, limit } = (input ?? {});
243
+ const qs = {};
244
+ if (archived)
245
+ qs.archived = archived;
246
+ const data = (await apiRequest(getToken(ctx), "GET", `/space/${spaceId}/folder`, undefined, qs));
247
+ const folders = data.folders ?? [];
248
+ if (limit)
249
+ return folders.slice(0, limit);
250
+ return folders;
251
+ },
252
+ });
253
+ rl.registerAction("folder.update", {
254
+ description: "Update a folder",
255
+ inputSchema: {
256
+ folderId: { type: "string", required: true, description: "Folder ID" },
257
+ name: { type: "string", required: false, description: "New name" },
258
+ },
259
+ async execute(input, ctx) {
260
+ const { folderId, ...body } = input;
261
+ return apiRequest(getToken(ctx), "PUT", `/folder/${folderId}`, body);
262
+ },
263
+ });
264
+ rl.registerAction("folder.delete", {
265
+ description: "Delete a folder",
266
+ inputSchema: { folderId: { type: "string", required: true, description: "Folder ID" } },
267
+ async execute(input, ctx) {
268
+ await apiRequest(getToken(ctx), "DELETE", `/folder/${input.folderId}`);
269
+ return { success: true };
270
+ },
271
+ });
272
+ // ── Goal ────────────────────────────────────────────
273
+ rl.registerAction("goal.create", {
274
+ description: "Create a goal",
275
+ inputSchema: {
276
+ teamId: { type: "string", required: true, description: "Team ID" },
277
+ name: { type: "string", required: true, description: "Goal name" },
278
+ dueDate: { type: "string", required: false, description: "Due date (ISO 8601)" },
279
+ description: { type: "string", required: false, description: "Description" },
280
+ color: { type: "string", required: false, description: "Color hex" },
281
+ owners: { type: "array", required: false, description: "Array of owner user IDs" },
282
+ multipleOwners: { type: "boolean", required: false, description: "Allow multiple owners" },
283
+ },
284
+ async execute(input, ctx) {
285
+ const { teamId, name, dueDate, description, color, owners, multipleOwners } = input;
286
+ const body = { name };
287
+ if (dueDate)
288
+ body.due_date = new Date(dueDate).getTime();
289
+ if (description)
290
+ body.description = description;
291
+ if (color)
292
+ body.color = color;
293
+ if (owners)
294
+ body.owners = owners;
295
+ if (multipleOwners !== undefined)
296
+ body.multiple_owners = multipleOwners;
297
+ const data = (await apiRequest(getToken(ctx), "POST", `/team/${teamId}/goal`, body));
298
+ return data.goal;
299
+ },
300
+ });
301
+ rl.registerAction("goal.get", {
302
+ description: "Get a goal",
303
+ inputSchema: { goalId: { type: "string", required: true, description: "Goal ID" } },
304
+ async execute(input, ctx) {
305
+ const data = (await apiRequest(getToken(ctx), "GET", `/goal/${input.goalId}`));
306
+ return data.goal;
307
+ },
308
+ });
309
+ rl.registerAction("goal.list", {
310
+ description: "List goals for a team",
311
+ inputSchema: {
312
+ teamId: { type: "string", required: true, description: "Team ID" },
313
+ limit: { type: "number", required: false, description: "Max results" },
314
+ },
315
+ async execute(input, ctx) {
316
+ const { teamId, limit } = input;
317
+ const data = (await apiRequest(getToken(ctx), "GET", `/team/${teamId}/goal`));
318
+ const goals = data.goals ?? [];
319
+ if (limit)
320
+ return goals.slice(0, limit);
321
+ return goals;
322
+ },
323
+ });
324
+ rl.registerAction("goal.update", {
325
+ description: "Update a goal",
326
+ inputSchema: {
327
+ goalId: { type: "string", required: true, description: "Goal ID" },
328
+ name: { type: "string", required: false, description: "Name" },
329
+ dueDate: { type: "string", required: false, description: "Due date" },
330
+ description: { type: "string", required: false, description: "Description" },
331
+ color: { type: "string", required: false, description: "Color" },
332
+ addOwners: { type: "array", required: false, description: "User IDs to add as owners" },
333
+ removeOwners: { type: "array", required: false, description: "User IDs to remove" },
334
+ },
335
+ async execute(input, ctx) {
336
+ const { goalId, dueDate, addOwners, removeOwners, ...rest } = input;
337
+ const body = { ...rest };
338
+ delete body.goalId;
339
+ if (dueDate)
340
+ body.due_date = new Date(dueDate).getTime();
341
+ if (addOwners)
342
+ body.add_owners = addOwners;
343
+ if (removeOwners)
344
+ body.rem_owners = removeOwners;
345
+ const data = (await apiRequest(getToken(ctx), "PUT", `/goal/${goalId}`, body));
346
+ return data.goal;
347
+ },
348
+ });
349
+ rl.registerAction("goal.delete", {
350
+ description: "Delete a goal",
351
+ inputSchema: { goalId: { type: "string", required: true, description: "Goal ID" } },
352
+ async execute(input, ctx) {
353
+ await apiRequest(getToken(ctx), "DELETE", `/goal/${input.goalId}`);
354
+ return { success: true };
355
+ },
356
+ });
357
+ // ── Goal Key Result ─────────────────────────────────
358
+ rl.registerAction("goalKeyResult.create", {
359
+ description: "Create a key result for a goal",
360
+ inputSchema: {
361
+ goalId: { type: "string", required: true, description: "Goal ID" },
362
+ name: { type: "string", required: true, description: "Key result name" },
363
+ type: { type: "string", required: true, description: "Type: number, currency, boolean, percentage, automatic" },
364
+ unit: { type: "string", required: false, description: "Unit (for number/currency)" },
365
+ stepsStart: { type: "number", required: false, description: "Start value" },
366
+ stepsEnd: { type: "number", required: false, description: "End value" },
367
+ taskIds: { type: "array", required: false, description: "Task IDs (for automatic type)" },
368
+ listIds: { type: "array", required: false, description: "List IDs (for automatic type)" },
369
+ owners: { type: "array", required: false, description: "Owner user IDs" },
370
+ },
371
+ async execute(input, ctx) {
372
+ const { goalId, stepsStart, stepsEnd, taskIds, listIds, ...rest } = input;
373
+ const body = { ...rest };
374
+ delete body.goalId;
375
+ if (stepsStart !== undefined)
376
+ body.steps_start = stepsStart;
377
+ if (stepsEnd !== undefined)
378
+ body.steps_end = stepsEnd;
379
+ if (taskIds)
380
+ body.task_ids = taskIds;
381
+ if (listIds)
382
+ body.list_ids = listIds;
383
+ const data = (await apiRequest(getToken(ctx), "POST", `/goal/${goalId}/key_result`, body));
384
+ return data.key_result;
385
+ },
386
+ });
387
+ rl.registerAction("goalKeyResult.update", {
388
+ description: "Update a key result",
389
+ inputSchema: {
390
+ keyResultId: { type: "string", required: true, description: "Key result ID" },
391
+ name: { type: "string", required: false, description: "Name" },
392
+ note: { type: "string", required: false, description: "Note" },
393
+ stepsCurrent: { type: "number", required: false, description: "Current steps" },
394
+ stepsStart: { type: "number", required: false, description: "Start steps" },
395
+ stepsEnd: { type: "number", required: false, description: "End steps" },
396
+ unit: { type: "string", required: false, description: "Unit" },
397
+ },
398
+ async execute(input, ctx) {
399
+ const { keyResultId, stepsCurrent, stepsStart, stepsEnd, ...rest } = input;
400
+ const body = { ...rest };
401
+ delete body.keyResultId;
402
+ if (stepsCurrent !== undefined)
403
+ body.steps_current = stepsCurrent;
404
+ if (stepsStart !== undefined)
405
+ body.steps_start = stepsStart;
406
+ if (stepsEnd !== undefined)
407
+ body.steps_end = stepsEnd;
408
+ const data = (await apiRequest(getToken(ctx), "PUT", `/key_result/${keyResultId}`, body));
409
+ return data.key_result;
410
+ },
411
+ });
412
+ rl.registerAction("goalKeyResult.delete", {
413
+ description: "Delete a key result",
414
+ inputSchema: { keyResultId: { type: "string", required: true, description: "Key result ID" } },
415
+ async execute(input, ctx) {
416
+ await apiRequest(getToken(ctx), "DELETE", `/key_result/${input.keyResultId}`);
417
+ return { success: true };
418
+ },
419
+ });
420
+ // ── Guest ───────────────────────────────────────────
421
+ rl.registerAction("guest.create", {
422
+ description: "Invite a guest to a workspace",
423
+ inputSchema: {
424
+ teamId: { type: "string", required: true, description: "Team ID" },
425
+ email: { type: "string", required: true, description: "Guest email" },
426
+ canEditTags: { type: "boolean", required: false, description: "Can edit tags" },
427
+ canSeeTimeSpend: { type: "boolean", required: false, description: "Can see time spent" },
428
+ canSeeTimeEstimated: { type: "boolean", required: false, description: "Can see time estimated" },
429
+ },
430
+ async execute(input, ctx) {
431
+ const { teamId, email, canEditTags, canSeeTimeSpend, canSeeTimeEstimated } = input;
432
+ const body = { email };
433
+ if (canEditTags !== undefined)
434
+ body.can_edit_tags = canEditTags;
435
+ if (canSeeTimeSpend !== undefined)
436
+ body.can_see_time_spend = canSeeTimeSpend;
437
+ if (canSeeTimeEstimated !== undefined)
438
+ body.can_see_time_estimated = canSeeTimeEstimated;
439
+ const data = (await apiRequest(getToken(ctx), "POST", `/team/${teamId}/guest`, body));
440
+ return data.team;
441
+ },
442
+ });
443
+ rl.registerAction("guest.get", {
444
+ description: "Get a guest",
445
+ inputSchema: {
446
+ teamId: { type: "string", required: true, description: "Team ID" },
447
+ guestId: { type: "string", required: true, description: "Guest ID" },
448
+ },
449
+ async execute(input, ctx) {
450
+ const { teamId, guestId } = input;
451
+ const data = (await apiRequest(getToken(ctx), "GET", `/team/${teamId}/guest/${guestId}`));
452
+ return data.team;
453
+ },
454
+ });
455
+ rl.registerAction("guest.update", {
456
+ description: "Update a guest",
457
+ inputSchema: {
458
+ teamId: { type: "string", required: true, description: "Team ID" },
459
+ guestId: { type: "string", required: true, description: "Guest ID" },
460
+ username: { type: "string", required: false, description: "Username" },
461
+ canEditTags: { type: "boolean", required: false, description: "Can edit tags" },
462
+ canSeeTimeSpend: { type: "boolean", required: false, description: "Can see time spent" },
463
+ canSeeTimeEstimated: { type: "boolean", required: false, description: "Can see time estimated" },
464
+ },
465
+ async execute(input, ctx) {
466
+ const { teamId, guestId, ...body } = input;
467
+ const data = (await apiRequest(getToken(ctx), "PUT", `/team/${teamId}/guest/${guestId}`, body));
468
+ return data.team;
469
+ },
470
+ });
471
+ rl.registerAction("guest.delete", {
472
+ description: "Remove a guest",
473
+ inputSchema: {
474
+ teamId: { type: "string", required: true, description: "Team ID" },
475
+ guestId: { type: "string", required: true, description: "Guest ID" },
476
+ },
477
+ async execute(input, ctx) {
478
+ const { teamId, guestId } = input;
479
+ await apiRequest(getToken(ctx), "DELETE", `/team/${teamId}/guest/${guestId}`);
480
+ return { success: true };
481
+ },
482
+ });
483
+ // ── Task ────────────────────────────────────────────
484
+ rl.registerAction("task.create", {
485
+ description: "Create a task in a list",
486
+ inputSchema: {
487
+ listId: { type: "string", required: true, description: "List ID" },
488
+ name: { type: "string", required: true, description: "Task name" },
489
+ content: { type: "string", required: false, description: "Task description" },
490
+ markdownContent: { type: "string", required: false, description: "Markdown description" },
491
+ assignees: { type: "array", required: false, description: "Assignee user IDs" },
492
+ tags: { type: "array", required: false, description: "Tag names" },
493
+ status: { type: "string", required: false, description: "Status" },
494
+ priority: { type: "number", required: false, description: "Priority (1=urgent, 2=high, 3=normal, 4=low)" },
495
+ dueDate: { type: "string", required: false, description: "Due date (ISO 8601)" },
496
+ startDate: { type: "string", required: false, description: "Start date (ISO 8601)" },
497
+ timeEstimate: { type: "number", required: false, description: "Time estimate in minutes" },
498
+ notifyAll: { type: "boolean", required: false, description: "Notify all assignees" },
499
+ parentId: { type: "string", required: false, description: "Parent task ID (for subtask)" },
500
+ customFields: { type: "array", required: false, description: "Custom fields array [{id, value}]" },
501
+ },
502
+ async execute(input, ctx) {
503
+ const { listId, dueDate, startDate, timeEstimate, markdownContent, parentId, customFields, ...rest } = input;
504
+ const body = { ...rest };
505
+ delete body.listId;
506
+ if (dueDate)
507
+ body.due_date = new Date(dueDate).getTime();
508
+ if (startDate)
509
+ body.start_date = new Date(startDate).getTime();
510
+ if (timeEstimate)
511
+ body.time_estimate = timeEstimate * 60000;
512
+ if (markdownContent) {
513
+ body.markdown_content = markdownContent;
514
+ delete body.content;
515
+ }
516
+ if (parentId)
517
+ body.parent = parentId;
518
+ if (customFields)
519
+ body.custom_fields = customFields;
520
+ return apiRequest(getToken(ctx), "POST", `/list/${listId}/task`, body);
521
+ },
522
+ });
523
+ rl.registerAction("task.get", {
524
+ description: "Get a task",
525
+ inputSchema: {
526
+ taskId: { type: "string", required: true, description: "Task ID" },
527
+ includeSubtasks: { type: "boolean", required: false, description: "Include subtasks" },
528
+ },
529
+ async execute(input, ctx) {
530
+ const { taskId, includeSubtasks } = input;
531
+ const qs = {};
532
+ if (includeSubtasks)
533
+ qs.include_subtasks = true;
534
+ return apiRequest(getToken(ctx), "GET", `/task/${taskId}`, undefined, qs);
535
+ },
536
+ });
537
+ rl.registerAction("task.list", {
538
+ description: "List tasks in a list",
539
+ inputSchema: {
540
+ listId: { type: "string", required: true, description: "List ID" },
541
+ archived: { type: "boolean", required: false, description: "Include archived" },
542
+ subtasks: { type: "boolean", required: false, description: "Include subtasks" },
543
+ includeClosed: { type: "boolean", required: false, description: "Include closed" },
544
+ orderBy: { type: "string", required: false, description: "Order by field" },
545
+ statuses: { type: "array", required: false, description: "Filter by statuses" },
546
+ assignees: { type: "array", required: false, description: "Filter by assignee IDs" },
547
+ tags: { type: "array", required: false, description: "Filter by tags" },
548
+ limit: { type: "number", required: false, description: "Max results (omit for all)" },
549
+ },
550
+ async execute(input, ctx) {
551
+ const { listId, limit, ...filters } = (input ?? {});
552
+ const qs = {};
553
+ if (filters.archived)
554
+ qs.archived = filters.archived;
555
+ if (filters.subtasks)
556
+ qs.subtasks = filters.subtasks;
557
+ if (filters.includeClosed)
558
+ qs.include_closed = filters.includeClosed;
559
+ if (filters.orderBy)
560
+ qs.order_by = filters.orderBy;
561
+ if (filters.statuses)
562
+ qs.statuses = filters.statuses;
563
+ if (filters.assignees)
564
+ qs.assignees = filters.assignees;
565
+ if (filters.tags)
566
+ qs.tags = filters.tags;
567
+ return paginateAll(getToken(ctx), `/list/${listId}/task`, "tasks", qs, limit);
568
+ },
569
+ });
570
+ rl.registerAction("task.update", {
571
+ description: "Update a task",
572
+ inputSchema: {
573
+ taskId: { type: "string", required: true, description: "Task ID" },
574
+ name: { type: "string", required: false, description: "Name" },
575
+ content: { type: "string", required: false, description: "Description" },
576
+ status: { type: "string", required: false, description: "Status" },
577
+ priority: { type: "number", required: false, description: "Priority" },
578
+ dueDate: { type: "string", required: false, description: "Due date" },
579
+ startDate: { type: "string", required: false, description: "Start date" },
580
+ timeEstimate: { type: "number", required: false, description: "Time estimate (minutes)" },
581
+ addAssignees: { type: "array", required: false, description: "User IDs to add" },
582
+ removeAssignees: { type: "array", required: false, description: "User IDs to remove" },
583
+ },
584
+ async execute(input, ctx) {
585
+ const { taskId, dueDate, startDate, timeEstimate, addAssignees, removeAssignees, ...rest } = input;
586
+ const body = { ...rest };
587
+ delete body.taskId;
588
+ if (dueDate)
589
+ body.due_date = new Date(dueDate).getTime();
590
+ if (startDate)
591
+ body.start_date = new Date(startDate).getTime();
592
+ if (timeEstimate)
593
+ body.time_estimate = timeEstimate * 60000;
594
+ body.assignees = { add: addAssignees ?? [], rem: removeAssignees ?? [] };
595
+ return apiRequest(getToken(ctx), "PUT", `/task/${taskId}`, body);
596
+ },
597
+ });
598
+ rl.registerAction("task.delete", {
599
+ description: "Delete a task",
600
+ inputSchema: { taskId: { type: "string", required: true, description: "Task ID" } },
601
+ async execute(input, ctx) {
602
+ await apiRequest(getToken(ctx), "DELETE", `/task/${input.taskId}`);
603
+ return { success: true };
604
+ },
605
+ });
606
+ rl.registerAction("task.getMembers", {
607
+ description: "Get task members",
608
+ inputSchema: {
609
+ taskId: { type: "string", required: true, description: "Task ID" },
610
+ limit: { type: "number", required: false, description: "Max results" },
611
+ },
612
+ async execute(input, ctx) {
613
+ const { taskId, limit } = input;
614
+ const data = (await apiRequest(getToken(ctx), "GET", `/task/${taskId}/member`));
615
+ const members = data.members ?? [];
616
+ if (limit)
617
+ return members.slice(0, limit);
618
+ return members;
619
+ },
620
+ });
621
+ rl.registerAction("task.setCustomField", {
622
+ description: "Set a custom field value on a task",
623
+ inputSchema: {
624
+ taskId: { type: "string", required: true, description: "Task ID" },
625
+ fieldId: { type: "string", required: true, description: "Custom field ID" },
626
+ value: { type: "string", required: true, description: "Value (string, number, or JSON)" },
627
+ },
628
+ async execute(input, ctx) {
629
+ const { taskId, fieldId, value } = input;
630
+ return apiRequest(getToken(ctx), "POST", `/task/${taskId}/field/${fieldId}`, { value });
631
+ },
632
+ });
633
+ // ── Task Tag ────────────────────────────────────────
634
+ rl.registerAction("taskTag.add", {
635
+ description: "Add a tag to a task",
636
+ inputSchema: {
637
+ taskId: { type: "string", required: true, description: "Task ID" },
638
+ tagName: { type: "string", required: true, description: "Tag name" },
639
+ },
640
+ async execute(input, ctx) {
641
+ const { taskId, tagName } = input;
642
+ await apiRequest(getToken(ctx), "POST", `/task/${taskId}/tag/${tagName}`);
643
+ return { success: true };
644
+ },
645
+ });
646
+ rl.registerAction("taskTag.remove", {
647
+ description: "Remove a tag from a task",
648
+ inputSchema: {
649
+ taskId: { type: "string", required: true, description: "Task ID" },
650
+ tagName: { type: "string", required: true, description: "Tag name" },
651
+ },
652
+ async execute(input, ctx) {
653
+ const { taskId, tagName } = input;
654
+ await apiRequest(getToken(ctx), "DELETE", `/task/${taskId}/tag/${tagName}`);
655
+ return { success: true };
656
+ },
657
+ });
658
+ // ── Task List ───────────────────────────────────────
659
+ rl.registerAction("taskList.add", {
660
+ description: "Add a task to a list",
661
+ inputSchema: {
662
+ taskId: { type: "string", required: true, description: "Task ID" },
663
+ listId: { type: "string", required: true, description: "List ID" },
664
+ },
665
+ async execute(input, ctx) {
666
+ const { taskId, listId } = input;
667
+ await apiRequest(getToken(ctx), "POST", `/list/${listId}/task/${taskId}`);
668
+ return { success: true };
669
+ },
670
+ });
671
+ rl.registerAction("taskList.remove", {
672
+ description: "Remove a task from a list",
673
+ inputSchema: {
674
+ taskId: { type: "string", required: true, description: "Task ID" },
675
+ listId: { type: "string", required: true, description: "List ID" },
676
+ },
677
+ async execute(input, ctx) {
678
+ const { taskId, listId } = input;
679
+ await apiRequest(getToken(ctx), "DELETE", `/list/${listId}/task/${taskId}`);
680
+ return { success: true };
681
+ },
682
+ });
683
+ // ── Task Dependency ─────────────────────────────────
684
+ rl.registerAction("taskDependency.create", {
685
+ description: "Add a dependency to a task",
686
+ inputSchema: {
687
+ taskId: { type: "string", required: true, description: "Task ID" },
688
+ dependsOnTaskId: { type: "string", required: true, description: "Task this depends on" },
689
+ },
690
+ async execute(input, ctx) {
691
+ const { taskId, dependsOnTaskId } = input;
692
+ await apiRequest(getToken(ctx), "POST", `/task/${taskId}/dependency`, { depends_on: dependsOnTaskId });
693
+ return { success: true };
694
+ },
695
+ });
696
+ rl.registerAction("taskDependency.delete", {
697
+ description: "Remove a dependency from a task",
698
+ inputSchema: {
699
+ taskId: { type: "string", required: true, description: "Task ID" },
700
+ dependsOnTaskId: { type: "string", required: true, description: "Dependent task ID" },
701
+ },
702
+ async execute(input, ctx) {
703
+ const { taskId, dependsOnTaskId } = input;
704
+ await apiRequest(getToken(ctx), "DELETE", `/task/${taskId}/dependency`, undefined, { depends_on: dependsOnTaskId });
705
+ return { success: true };
706
+ },
707
+ });
708
+ // ── Space Tag ───────────────────────────────────────
709
+ rl.registerAction("spaceTag.create", {
710
+ description: "Create a tag in a space",
711
+ inputSchema: {
712
+ spaceId: { type: "string", required: true, description: "Space ID" },
713
+ name: { type: "string", required: true, description: "Tag name" },
714
+ foregroundColor: { type: "string", required: true, description: "Text color hex" },
715
+ backgroundColor: { type: "string", required: true, description: "Background color hex" },
716
+ },
717
+ async execute(input, ctx) {
718
+ const { spaceId, name, foregroundColor, backgroundColor } = input;
719
+ await apiRequest(getToken(ctx), "POST", `/space/${spaceId}/tag`, {
720
+ tag: { name, tag_fg: foregroundColor, tag_bg: backgroundColor },
721
+ });
722
+ return { success: true };
723
+ },
724
+ });
725
+ rl.registerAction("spaceTag.list", {
726
+ description: "List tags in a space",
727
+ inputSchema: {
728
+ spaceId: { type: "string", required: true, description: "Space ID" },
729
+ limit: { type: "number", required: false, description: "Max results" },
730
+ },
731
+ async execute(input, ctx) {
732
+ const { spaceId, limit } = input;
733
+ const data = (await apiRequest(getToken(ctx), "GET", `/space/${spaceId}/tag`));
734
+ const tags = data.tags ?? [];
735
+ if (limit)
736
+ return tags.slice(0, limit);
737
+ return tags;
738
+ },
739
+ });
740
+ rl.registerAction("spaceTag.update", {
741
+ description: "Update a space tag",
742
+ inputSchema: {
743
+ spaceId: { type: "string", required: true, description: "Space ID" },
744
+ tagName: { type: "string", required: true, description: "Current tag name" },
745
+ newName: { type: "string", required: true, description: "New tag name" },
746
+ foregroundColor: { type: "string", required: true, description: "Text color hex" },
747
+ backgroundColor: { type: "string", required: true, description: "Background color hex" },
748
+ },
749
+ async execute(input, ctx) {
750
+ const { spaceId, tagName, newName, foregroundColor, backgroundColor } = input;
751
+ await apiRequest(getToken(ctx), "PUT", `/space/${spaceId}/tag/${tagName}`, {
752
+ tag: { name: newName, tag_fg: foregroundColor, tag_bg: backgroundColor },
753
+ });
754
+ return { success: true };
755
+ },
756
+ });
757
+ rl.registerAction("spaceTag.delete", {
758
+ description: "Delete a space tag",
759
+ inputSchema: {
760
+ spaceId: { type: "string", required: true, description: "Space ID" },
761
+ tagName: { type: "string", required: true, description: "Tag name" },
762
+ },
763
+ async execute(input, ctx) {
764
+ const { spaceId, tagName } = input;
765
+ await apiRequest(getToken(ctx), "DELETE", `/space/${spaceId}/tag/${tagName}`);
766
+ return { success: true };
767
+ },
768
+ });
769
+ // ── List ────────────────────────────────────────────
770
+ rl.registerAction("list.create", {
771
+ description: "Create a list (in a folder or folderless in a space)",
772
+ inputSchema: {
773
+ name: { type: "string", required: true, description: "List name" },
774
+ spaceId: { type: "string", required: false, description: "Space ID (for folderless list)" },
775
+ folderId: { type: "string", required: false, description: "Folder ID (for list in folder)" },
776
+ content: { type: "string", required: false, description: "Description" },
777
+ dueDate: { type: "string", required: false, description: "Due date" },
778
+ priority: { type: "number", required: false, description: "Priority" },
779
+ assignee: { type: "number", required: false, description: "Assignee user ID" },
780
+ status: { type: "string", required: false, description: "Status" },
781
+ },
782
+ async execute(input, ctx) {
783
+ const { name, spaceId, folderId, dueDate, ...rest } = input;
784
+ const body = { name, ...rest };
785
+ delete body.spaceId;
786
+ delete body.folderId;
787
+ if (dueDate)
788
+ body.due_date = new Date(dueDate).getTime();
789
+ const endpoint = folderId ? `/folder/${folderId}/list` : `/space/${spaceId}/list`;
790
+ return apiRequest(getToken(ctx), "POST", endpoint, body);
791
+ },
792
+ });
793
+ rl.registerAction("list.get", {
794
+ description: "Get a list",
795
+ inputSchema: { listId: { type: "string", required: true, description: "List ID" } },
796
+ async execute(input, ctx) {
797
+ return apiRequest(getToken(ctx), "GET", `/list/${input.listId}`);
798
+ },
799
+ });
800
+ rl.registerAction("list.list", {
801
+ description: "List lists in a folder or space",
802
+ inputSchema: {
803
+ spaceId: { type: "string", required: false, description: "Space ID (for folderless)" },
804
+ folderId: { type: "string", required: false, description: "Folder ID" },
805
+ archived: { type: "boolean", required: false, description: "Include archived" },
806
+ limit: { type: "number", required: false, description: "Max results" },
807
+ },
808
+ async execute(input, ctx) {
809
+ const { spaceId, folderId, archived, limit } = (input ?? {});
810
+ const qs = {};
811
+ if (archived)
812
+ qs.archived = archived;
813
+ const endpoint = folderId ? `/folder/${folderId}/list` : `/space/${spaceId}/list`;
814
+ const data = (await apiRequest(getToken(ctx), "GET", endpoint, undefined, qs));
815
+ const lists = data.lists ?? [];
816
+ if (limit)
817
+ return lists.slice(0, limit);
818
+ return lists;
819
+ },
820
+ });
821
+ rl.registerAction("list.update", {
822
+ description: "Update a list",
823
+ inputSchema: {
824
+ listId: { type: "string", required: true, description: "List ID" },
825
+ name: { type: "string", required: false, description: "Name" },
826
+ content: { type: "string", required: false, description: "Description" },
827
+ dueDate: { type: "string", required: false, description: "Due date" },
828
+ priority: { type: "number", required: false, description: "Priority" },
829
+ assignee: { type: "number", required: false, description: "Assignee" },
830
+ unsetStatus: { type: "boolean", required: false, description: "Unset status" },
831
+ },
832
+ async execute(input, ctx) {
833
+ const { listId, dueDate, ...rest } = input;
834
+ const body = { ...rest };
835
+ delete body.listId;
836
+ if (dueDate)
837
+ body.due_date = new Date(dueDate).getTime();
838
+ return apiRequest(getToken(ctx), "PUT", `/list/${listId}`, body);
839
+ },
840
+ });
841
+ rl.registerAction("list.delete", {
842
+ description: "Delete a list",
843
+ inputSchema: { listId: { type: "string", required: true, description: "List ID" } },
844
+ async execute(input, ctx) {
845
+ await apiRequest(getToken(ctx), "DELETE", `/list/${input.listId}`);
846
+ return { success: true };
847
+ },
848
+ });
849
+ rl.registerAction("list.getMembers", {
850
+ description: "Get list members",
851
+ inputSchema: {
852
+ listId: { type: "string", required: true, description: "List ID" },
853
+ limit: { type: "number", required: false, description: "Max results" },
854
+ },
855
+ async execute(input, ctx) {
856
+ const { listId, limit } = input;
857
+ const data = (await apiRequest(getToken(ctx), "GET", `/list/${listId}/member`));
858
+ const members = data.members ?? [];
859
+ if (limit)
860
+ return members.slice(0, limit);
861
+ return members;
862
+ },
863
+ });
864
+ rl.registerAction("list.getCustomFields", {
865
+ description: "Get custom fields for a list",
866
+ inputSchema: { listId: { type: "string", required: true, description: "List ID" } },
867
+ async execute(input, ctx) {
868
+ const data = (await apiRequest(getToken(ctx), "GET", `/list/${input.listId}/field`));
869
+ return data.fields;
870
+ },
871
+ });
872
+ // ── Time Entry ──────────────────────────────────────
873
+ rl.registerAction("timeEntry.create", {
874
+ description: "Create a time entry",
875
+ inputSchema: {
876
+ teamId: { type: "string", required: true, description: "Team ID" },
877
+ taskId: { type: "string", required: true, description: "Task ID" },
878
+ start: { type: "string", required: true, description: "Start time (ISO 8601)" },
879
+ duration: { type: "number", required: true, description: "Duration in minutes" },
880
+ description: { type: "string", required: false, description: "Description" },
881
+ billable: { type: "boolean", required: false, description: "Billable" },
882
+ },
883
+ async execute(input, ctx) {
884
+ const { teamId, taskId, start, duration, ...rest } = input;
885
+ const body = {
886
+ tid: taskId,
887
+ start: new Date(start).getTime(),
888
+ duration: duration * 60000,
889
+ ...rest,
890
+ };
891
+ delete body.teamId;
892
+ const data = (await apiRequest(getToken(ctx), "POST", `/team/${teamId}/time_entries`, body));
893
+ return data.data;
894
+ },
895
+ });
896
+ rl.registerAction("timeEntry.get", {
897
+ description: "Get a time entry (or current running timer)",
898
+ inputSchema: {
899
+ teamId: { type: "string", required: true, description: "Team ID" },
900
+ timeEntryId: { type: "string", required: false, description: "Time entry ID (omit for current)" },
901
+ },
902
+ async execute(input, ctx) {
903
+ const { teamId, timeEntryId } = input;
904
+ const endpoint = timeEntryId
905
+ ? `/team/${teamId}/time_entries/${timeEntryId}`
906
+ : `/team/${teamId}/time_entries/current`;
907
+ const data = (await apiRequest(getToken(ctx), "GET", endpoint));
908
+ return data.data;
909
+ },
910
+ });
911
+ rl.registerAction("timeEntry.list", {
912
+ description: "List time entries for a team",
913
+ inputSchema: {
914
+ teamId: { type: "string", required: true, description: "Team ID" },
915
+ startDate: { type: "string", required: false, description: "Start date filter" },
916
+ endDate: { type: "string", required: false, description: "End date filter" },
917
+ assignee: { type: "array", required: false, description: "Assignee user IDs" },
918
+ limit: { type: "number", required: false, description: "Max results" },
919
+ },
920
+ async execute(input, ctx) {
921
+ const { teamId, startDate, endDate, assignee, limit } = (input ?? {});
922
+ const qs = {};
923
+ if (startDate)
924
+ qs.start_date = new Date(startDate).getTime();
925
+ if (endDate)
926
+ qs.end_date = new Date(endDate).getTime();
927
+ if (assignee)
928
+ qs.assignee = assignee;
929
+ const data = (await apiRequest(getToken(ctx), "GET", `/team/${teamId}/time_entries`, undefined, qs));
930
+ const entries = data.data ?? [];
931
+ if (limit)
932
+ return entries.slice(0, limit);
933
+ return entries;
934
+ },
935
+ });
936
+ rl.registerAction("timeEntry.update", {
937
+ description: "Update a time entry",
938
+ inputSchema: {
939
+ teamId: { type: "string", required: true, description: "Team ID" },
940
+ timeEntryId: { type: "string", required: true, description: "Time entry ID" },
941
+ start: { type: "string", required: false, description: "Start time" },
942
+ duration: { type: "number", required: false, description: "Duration in minutes" },
943
+ description: { type: "string", required: false, description: "Description" },
944
+ billable: { type: "boolean", required: false, description: "Billable" },
945
+ },
946
+ async execute(input, ctx) {
947
+ const { teamId, timeEntryId, start, duration, ...rest } = input;
948
+ const body = { ...rest };
949
+ if (start)
950
+ body.start = new Date(start).getTime();
951
+ if (duration)
952
+ body.duration = duration * 60000;
953
+ const data = (await apiRequest(getToken(ctx), "PUT", `/team/${teamId}/time_entries/${timeEntryId}`, body));
954
+ return data.data;
955
+ },
956
+ });
957
+ rl.registerAction("timeEntry.start", {
958
+ description: "Start a timer on a task",
959
+ inputSchema: {
960
+ teamId: { type: "string", required: true, description: "Team ID" },
961
+ taskId: { type: "string", required: true, description: "Task ID" },
962
+ description: { type: "string", required: false, description: "Description" },
963
+ billable: { type: "boolean", required: false, description: "Billable" },
964
+ },
965
+ async execute(input, ctx) {
966
+ const { teamId, taskId, ...rest } = input;
967
+ const body = { tid: taskId, ...rest };
968
+ const data = (await apiRequest(getToken(ctx), "POST", `/team/${teamId}/time_entries/start`, body));
969
+ return data.data;
970
+ },
971
+ });
972
+ rl.registerAction("timeEntry.stop", {
973
+ description: "Stop the running timer",
974
+ inputSchema: {
975
+ teamId: { type: "string", required: true, description: "Team ID" },
976
+ },
977
+ async execute(input, ctx) {
978
+ const { teamId } = input;
979
+ const data = (await apiRequest(getToken(ctx), "POST", `/team/${teamId}/time_entries/stop`));
980
+ return data.data;
981
+ },
982
+ });
983
+ rl.registerAction("timeEntry.delete", {
984
+ description: "Delete a time entry",
985
+ inputSchema: {
986
+ teamId: { type: "string", required: true, description: "Team ID" },
987
+ timeEntryId: { type: "string", required: true, description: "Time entry ID" },
988
+ },
989
+ async execute(input, ctx) {
990
+ const { teamId, timeEntryId } = input;
991
+ const data = (await apiRequest(getToken(ctx), "DELETE", `/team/${teamId}/time_entries/${timeEntryId}`));
992
+ return data.data;
993
+ },
994
+ });
995
+ // ── Time Entry Tag ──────────────────────────────────
996
+ rl.registerAction("timeEntryTag.add", {
997
+ description: "Add tags to time entries",
998
+ inputSchema: {
999
+ teamId: { type: "string", required: true, description: "Team ID" },
1000
+ timeEntryIds: { type: "array", required: true, description: "Array of time entry IDs" },
1001
+ tags: { type: "array", required: true, description: "Array of {name, tag_bg, tag_fg} objects" },
1002
+ },
1003
+ async execute(input, ctx) {
1004
+ const { teamId, timeEntryIds, tags } = input;
1005
+ await apiRequest(getToken(ctx), "POST", `/team/${teamId}/time_entries/tags`, {
1006
+ time_entry_ids: timeEntryIds,
1007
+ tags,
1008
+ });
1009
+ return { success: true };
1010
+ },
1011
+ });
1012
+ rl.registerAction("timeEntryTag.list", {
1013
+ description: "List time entry tags for a team",
1014
+ inputSchema: {
1015
+ teamId: { type: "string", required: true, description: "Team ID" },
1016
+ limit: { type: "number", required: false, description: "Max results" },
1017
+ },
1018
+ async execute(input, ctx) {
1019
+ const { teamId, limit } = input;
1020
+ const data = (await apiRequest(getToken(ctx), "GET", `/team/${teamId}/time_entries/tags`));
1021
+ const tags = data.data ?? [];
1022
+ if (limit)
1023
+ return tags.slice(0, limit);
1024
+ return tags;
1025
+ },
1026
+ });
1027
+ rl.registerAction("timeEntryTag.remove", {
1028
+ description: "Remove tags from time entries",
1029
+ inputSchema: {
1030
+ teamId: { type: "string", required: true, description: "Team ID" },
1031
+ timeEntryIds: { type: "array", required: true, description: "Array of time entry IDs" },
1032
+ tagNames: { type: "array", required: true, description: "Array of tag names to remove" },
1033
+ },
1034
+ async execute(input, ctx) {
1035
+ const { teamId, timeEntryIds, tagNames } = input;
1036
+ await apiRequest(getToken(ctx), "DELETE", `/team/${teamId}/time_entries/tags`, {
1037
+ time_entry_ids: timeEntryIds,
1038
+ tags: tagNames,
1039
+ });
1040
+ return { success: true };
1041
+ },
1042
+ });
1043
+ }