runline 0.36.0 → 0.38.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.
- package/dist/credentials/aws-plugin.d.ts +3 -0
- package/dist/credentials/aws-plugin.js +7 -2
- package/dist/credentials/aws.d.ts +11 -0
- package/dist/credentials/aws.js +110 -11
- package/dist/credentials/broker.js +8 -7
- package/dist/credentials/http.d.ts +17 -3
- package/dist/credentials/http.js +31 -9
- package/dist/credentials/transport.js +5 -1
- package/dist/credentials/types.d.ts +7 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/plugins/_shared/attachments.js +97 -0
- package/dist/plugins/_shared/microsoftAuth.js +33 -4
- package/dist/plugins/_shared/schemas.js +8 -0
- package/dist/plugins/agentMail/src/drafts.js +2 -1
- package/dist/plugins/agentMail/src/inboxes.js +2 -1
- package/dist/plugins/agentMail/src/messages.js +2 -1
- package/dist/plugins/agentMail/src/schemas.js +4 -41
- package/dist/plugins/agentMail/src/shared.js +28 -50
- package/dist/plugins/agentMail/src/threads.js +2 -1
- package/dist/plugins/gmail/src/index.js +10 -22
- package/dist/plugins/microsoftCalendar/src/index.js +513 -17
- package/dist/plugins/microsoftFiles/src/index.js +3 -3
- package/dist/plugins/microsoftMail/src/index.js +305 -54
- package/dist/plugins/todoist/src/credentials.js +4 -11
- package/dist/plugins/todoist/src/index.js +390 -281
- package/dist/plugins/zohoMail/src/credentials.js +92 -0
- package/dist/plugins/zohoMail/src/index.js +241 -0
- package/package.json +1 -1
|
@@ -1,25 +1,254 @@
|
|
|
1
|
-
|
|
1
|
+
/**
|
|
2
|
+
* Todoist plugin for runline, on Todoist's unified API v1.
|
|
3
|
+
*
|
|
4
|
+
* Lists follow API v1's cursors (`next_cursor`, at most 200 a page) and
|
|
5
|
+
* answer one flat array; `limit` stops paging once that many are in.
|
|
6
|
+
* Failures report Todoist's `error_tag` (`UNAUTHORIZED`, `NOT_FOUND`).
|
|
7
|
+
*/
|
|
8
|
+
import { credentialRequest, jsonAnswer, pathSegment, responseFailed, } from "runline";
|
|
9
|
+
import * as t from "typebox";
|
|
10
|
+
import { atLeastOne } from "../../_shared/schemas.js";
|
|
2
11
|
import { todoistCredential } from "./credentials.js";
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
12
|
+
/** API v1's largest page. */
|
|
13
|
+
const PAGE = 200;
|
|
14
|
+
const STRICT = { additionalProperties: false };
|
|
15
|
+
const Id = (description) => t.String({ minLength: 1, ...(description ? { description } : {}) });
|
|
16
|
+
const Limit = t.Optional(t.Integer({ minimum: 1, description: "Stop after this many (default all)" }));
|
|
17
|
+
const Labels = t.Array(t.String({ minLength: 1 }), {
|
|
18
|
+
description: "Label names",
|
|
19
|
+
});
|
|
20
|
+
const Priority = t.Integer({
|
|
21
|
+
minimum: 1,
|
|
22
|
+
maximum: 4,
|
|
23
|
+
description: "1 (normal) to 4 (urgent)",
|
|
24
|
+
});
|
|
25
|
+
const DueDate = t.String({ pattern: "^\\d{4}-\\d{2}-\\d{2}$" });
|
|
26
|
+
const Filter = (description) => t.String({ minLength: 1, maxLength: 1024, description });
|
|
27
|
+
const FilterLang = t.Optional(Id("IETF language tag the filter query is written in (default English); with filter"));
|
|
28
|
+
/** A language belongs to a filter query: `lang` only beside `filter`. */
|
|
29
|
+
const langWithFilter = {
|
|
30
|
+
anyOf: [{ required: ["filter"] }, { not: { required: ["lang"] } }],
|
|
31
|
+
};
|
|
32
|
+
const AssigneeId = t.String({
|
|
33
|
+
pattern: "^\\d+$",
|
|
34
|
+
description: "A user id, as project.getCollaborators lists it",
|
|
35
|
+
});
|
|
36
|
+
/** Todoist's color names, as API v1 lists them. */
|
|
37
|
+
const COLORS = [
|
|
38
|
+
"berry_red",
|
|
39
|
+
"red",
|
|
40
|
+
"orange",
|
|
41
|
+
"yellow",
|
|
42
|
+
"olive_green",
|
|
43
|
+
"lime_green",
|
|
44
|
+
"green",
|
|
45
|
+
"mint_green",
|
|
46
|
+
"teal",
|
|
47
|
+
"sky_blue",
|
|
48
|
+
"light_blue",
|
|
49
|
+
"blue",
|
|
50
|
+
"grape",
|
|
51
|
+
"violet",
|
|
52
|
+
"lavender",
|
|
53
|
+
"magenta",
|
|
54
|
+
"salmon",
|
|
55
|
+
"charcoal",
|
|
56
|
+
"grey",
|
|
57
|
+
"taupe",
|
|
58
|
+
];
|
|
59
|
+
const Color = t.Unsafe(t.Union(COLORS.map((color) => t.Literal(color))));
|
|
60
|
+
const ViewStyle = t.Union([
|
|
61
|
+
t.Literal("list"),
|
|
62
|
+
t.Literal("board"),
|
|
63
|
+
t.Literal("calendar"),
|
|
64
|
+
]);
|
|
65
|
+
/** Todoist's error envelope: only its `error_tag` is read. */
|
|
66
|
+
function todoistError(body) {
|
|
67
|
+
return { code: body?.error_tag };
|
|
68
|
+
}
|
|
69
|
+
/** One request; a failure reports the status and Todoist's error_tag. */
|
|
70
|
+
async function api(ctx, method, path, options = {}) {
|
|
71
|
+
const res = await credentialRequest(ctx, todoistCredential, {
|
|
72
|
+
target: "api",
|
|
73
|
+
path,
|
|
7
74
|
method,
|
|
8
|
-
query:
|
|
9
|
-
...(
|
|
75
|
+
query: options.query,
|
|
76
|
+
...(options.body ? { json: options.body } : {}),
|
|
10
77
|
});
|
|
78
|
+
if (!res.ok)
|
|
79
|
+
throw await responseFailed("todoist", res, todoistError);
|
|
80
|
+
return jsonAnswer(res);
|
|
11
81
|
}
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
82
|
+
/** A request whose answer is an acknowledgement. */
|
|
83
|
+
async function act(ctx, method, path) {
|
|
84
|
+
await api(ctx, method, path);
|
|
85
|
+
return { success: true };
|
|
86
|
+
}
|
|
87
|
+
/** Every item of a cursor-paginated list; `limit` stops paging early. */
|
|
88
|
+
async function listAll(ctx, path, query = {}, limit, key = "results") {
|
|
89
|
+
const all = [];
|
|
90
|
+
let cursor;
|
|
91
|
+
do {
|
|
92
|
+
const page = (await api(ctx, "GET", path, {
|
|
93
|
+
query: {
|
|
94
|
+
...query,
|
|
95
|
+
limit: limit ? Math.min(limit - all.length, PAGE) : PAGE,
|
|
96
|
+
cursor,
|
|
97
|
+
},
|
|
98
|
+
}));
|
|
99
|
+
all.push(...(page[key] ?? []));
|
|
100
|
+
cursor = page.next_cursor;
|
|
101
|
+
} while (cursor && (!limit || all.length < limit));
|
|
102
|
+
return limit ? all.slice(0, limit) : all;
|
|
103
|
+
}
|
|
104
|
+
/** The defined entries of `fields`, under their API names. */
|
|
105
|
+
function wire(fields, names) {
|
|
106
|
+
const out = {};
|
|
107
|
+
for (const [key, name] of Object.entries(names))
|
|
108
|
+
if (fields[key] !== undefined)
|
|
109
|
+
out[name] = fields[key];
|
|
110
|
+
return out;
|
|
111
|
+
}
|
|
112
|
+
/** Where a task sits, as API v1 names it. */
|
|
113
|
+
const placeNames = {
|
|
114
|
+
projectId: "project_id",
|
|
115
|
+
sectionId: "section_id",
|
|
116
|
+
parentId: "parent_id",
|
|
117
|
+
};
|
|
118
|
+
/** A task's fields as API v1 names them; assigneeId is sent as a number. */
|
|
119
|
+
function taskBody(fields) {
|
|
120
|
+
const body = wire(fields, {
|
|
121
|
+
content: "content",
|
|
122
|
+
description: "description",
|
|
123
|
+
...placeNames,
|
|
124
|
+
priority: "priority",
|
|
125
|
+
dueString: "due_string",
|
|
126
|
+
dueDate: "due_date",
|
|
127
|
+
labels: "labels",
|
|
128
|
+
});
|
|
129
|
+
if (typeof fields.assigneeId === "string")
|
|
130
|
+
body.assignee_id = Number(fields.assigneeId);
|
|
131
|
+
return body;
|
|
19
132
|
}
|
|
133
|
+
const byId = t.Object({ id: Id() }, STRICT);
|
|
134
|
+
const taskChanges = {
|
|
135
|
+
content: t.Optional(Id()),
|
|
136
|
+
description: t.Optional(t.String()),
|
|
137
|
+
priority: t.Optional(Priority),
|
|
138
|
+
dueString: t.Optional(Id('Natural language, e.g. "every monday"')),
|
|
139
|
+
dueDate: t.Optional(DueDate),
|
|
140
|
+
labels: t.Optional(Labels),
|
|
141
|
+
assigneeId: t.Optional(AssigneeId),
|
|
142
|
+
};
|
|
143
|
+
/** Where a task sits; placeNames gives the API's names. */
|
|
144
|
+
const taskPlace = {
|
|
145
|
+
projectId: t.Optional(Id()),
|
|
146
|
+
sectionId: t.Optional(Id()),
|
|
147
|
+
parentId: t.Optional(Id()),
|
|
148
|
+
};
|
|
149
|
+
const taskCreate = t.Object({ ...taskChanges, ...taskPlace, content: Id() }, STRICT);
|
|
150
|
+
const taskUpdate = t.Object({ id: Id(), ...taskChanges }, { ...STRICT, ...atLeastOne(taskChanges) });
|
|
151
|
+
const taskSelectors = { ...taskPlace, label: t.Optional(Id()) };
|
|
152
|
+
const taskList = t.Object({
|
|
153
|
+
...taskSelectors,
|
|
154
|
+
filter: t.Optional(Filter('Todoist filter query, e.g. "today | overdue"; instead of the fields above')),
|
|
155
|
+
lang: FilterLang,
|
|
156
|
+
limit: Limit,
|
|
157
|
+
}, {
|
|
158
|
+
...STRICT,
|
|
159
|
+
allOf: [
|
|
160
|
+
// A filter query replaces the selectors rather than narrowing them.
|
|
161
|
+
{
|
|
162
|
+
anyOf: [
|
|
163
|
+
{ not: { required: ["filter"] } },
|
|
164
|
+
{
|
|
165
|
+
not: {
|
|
166
|
+
anyOf: Object.keys(taskSelectors).map((key) => ({
|
|
167
|
+
required: [key],
|
|
168
|
+
})),
|
|
169
|
+
},
|
|
170
|
+
},
|
|
171
|
+
],
|
|
172
|
+
},
|
|
173
|
+
langWithFilter,
|
|
174
|
+
],
|
|
175
|
+
});
|
|
176
|
+
const taskQuickAdd = t.Object({
|
|
177
|
+
text: Id('e.g. "Buy milk @Grocery #shopping tomorrow"'),
|
|
178
|
+
note: t.Optional(t.String()),
|
|
179
|
+
reminder: t.Optional(Id()),
|
|
180
|
+
autoReminder: t.Optional(t.Boolean({
|
|
181
|
+
description: "Add the user's default reminder when the task has a due time",
|
|
182
|
+
})),
|
|
183
|
+
}, STRICT);
|
|
184
|
+
const taskMove = t.Object({ id: Id(), ...taskPlace }, {
|
|
185
|
+
...STRICT,
|
|
186
|
+
oneOf: Object.keys(taskPlace).map((key) => ({ required: [key] })),
|
|
187
|
+
});
|
|
188
|
+
const taskListCompleted = t.Object({
|
|
189
|
+
since: t.String({
|
|
190
|
+
format: "date-time",
|
|
191
|
+
description: "Start of the window, inclusive, e.g. 2026-05-01T00:00:00Z",
|
|
192
|
+
}),
|
|
193
|
+
until: t.String({
|
|
194
|
+
format: "date-time",
|
|
195
|
+
description: "End of the window, exclusive; at most 3 months after since",
|
|
196
|
+
}),
|
|
197
|
+
...taskPlace,
|
|
198
|
+
filter: t.Optional(Filter("Todoist filter query the tasks must match")),
|
|
199
|
+
lang: FilterLang,
|
|
200
|
+
limit: Limit,
|
|
201
|
+
}, { ...STRICT, ...langWithFilter });
|
|
202
|
+
const projectChanges = {
|
|
203
|
+
name: t.Optional(Id()),
|
|
204
|
+
color: t.Optional(Color),
|
|
205
|
+
isFavorite: t.Optional(t.Boolean()),
|
|
206
|
+
viewStyle: t.Optional(ViewStyle),
|
|
207
|
+
};
|
|
208
|
+
const projectCreate = t.Object({ ...projectChanges, name: Id(), parentId: t.Optional(Id()) }, STRICT);
|
|
209
|
+
const projectUpdate = t.Object({ id: Id(), ...projectChanges }, { ...STRICT, ...atLeastOne(projectChanges) });
|
|
210
|
+
const sectionCreate = t.Object({
|
|
211
|
+
projectId: Id(),
|
|
212
|
+
name: Id(),
|
|
213
|
+
order: t.Optional(t.Integer({ description: "Position of the section in the project" })),
|
|
214
|
+
}, STRICT);
|
|
215
|
+
const sectionList = t.Object({ projectId: t.Optional(Id()) }, STRICT);
|
|
216
|
+
const sectionUpdate = t.Object({ id: Id(), name: Id() }, STRICT);
|
|
217
|
+
/** A comment belongs to a task or to a project, never both. */
|
|
218
|
+
const commentOwner = {
|
|
219
|
+
taskId: t.Optional(Id()),
|
|
220
|
+
projectId: t.Optional(Id()),
|
|
221
|
+
};
|
|
222
|
+
const ownerRule = {
|
|
223
|
+
oneOf: Object.keys(commentOwner).map((key) => ({ required: [key] })),
|
|
224
|
+
};
|
|
225
|
+
const commentCreate = t.Object({ ...commentOwner, content: Id() }, { ...STRICT, ...ownerRule });
|
|
226
|
+
const commentList = t.Object(commentOwner, { ...STRICT, ...ownerRule });
|
|
227
|
+
const commentUpdate = t.Object({ id: Id(), content: Id() }, STRICT);
|
|
228
|
+
const labelChanges = {
|
|
229
|
+
name: t.Optional(Id()),
|
|
230
|
+
color: t.Optional(Color),
|
|
231
|
+
order: t.Optional(t.Integer({ minimum: -32768, maximum: 32767 })),
|
|
232
|
+
isFavorite: t.Optional(t.Boolean()),
|
|
233
|
+
};
|
|
234
|
+
const labelCreate = t.Object({ ...labelChanges, name: Id() }, STRICT);
|
|
235
|
+
const labelUpdate = t.Object({ id: Id(), ...labelChanges }, { ...STRICT, ...atLeastOne(labelChanges) });
|
|
236
|
+
const projectNames = {
|
|
237
|
+
name: "name",
|
|
238
|
+
color: "color",
|
|
239
|
+
isFavorite: "is_favorite",
|
|
240
|
+
viewStyle: "view_style",
|
|
241
|
+
parentId: "parent_id",
|
|
242
|
+
};
|
|
243
|
+
const labelNames = {
|
|
244
|
+
name: "name",
|
|
245
|
+
color: "color",
|
|
246
|
+
order: "order",
|
|
247
|
+
isFavorite: "is_favorite",
|
|
248
|
+
};
|
|
20
249
|
export default function todoist(rl) {
|
|
21
250
|
rl.setName("todoist");
|
|
22
|
-
rl.setVersion("
|
|
251
|
+
rl.setVersion("1.0.0");
|
|
23
252
|
rl.setCredential(todoistCredential);
|
|
24
253
|
rl.setConnectionSchema({
|
|
25
254
|
apiToken: {
|
|
@@ -33,462 +262,342 @@ export default function todoist(rl) {
|
|
|
33
262
|
rl.registerAction("task.create", {
|
|
34
263
|
access: "write",
|
|
35
264
|
description: "Create a task",
|
|
36
|
-
inputSchema:
|
|
37
|
-
content: { type: "string", required: true },
|
|
38
|
-
projectId: { type: "string", required: false },
|
|
39
|
-
description: { type: "string", required: false },
|
|
40
|
-
priority: {
|
|
41
|
-
type: "number",
|
|
42
|
-
required: false,
|
|
43
|
-
description: "1 (normal) to 4 (urgent)",
|
|
44
|
-
},
|
|
45
|
-
dueString: { type: "string", required: false },
|
|
46
|
-
dueDate: { type: "string", required: false, description: "YYYY-MM-DD" },
|
|
47
|
-
labels: {
|
|
48
|
-
type: "object",
|
|
49
|
-
required: false,
|
|
50
|
-
description: "Array of label names",
|
|
51
|
-
},
|
|
52
|
-
sectionId: { type: "string", required: false },
|
|
53
|
-
parentId: { type: "string", required: false },
|
|
54
|
-
assigneeId: { type: "string", required: false },
|
|
55
|
-
},
|
|
265
|
+
inputSchema: taskCreate,
|
|
56
266
|
async execute(input, ctx) {
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
body.project_id = p.projectId;
|
|
61
|
-
if (p.description)
|
|
62
|
-
body.description = p.description;
|
|
63
|
-
if (p.priority)
|
|
64
|
-
body.priority = p.priority;
|
|
65
|
-
if (p.dueString)
|
|
66
|
-
body.due_string = p.dueString;
|
|
67
|
-
if (p.dueDate)
|
|
68
|
-
body.due_date = p.dueDate;
|
|
69
|
-
if (p.labels)
|
|
70
|
-
body.labels = p.labels;
|
|
71
|
-
if (p.sectionId)
|
|
72
|
-
body.section_id = p.sectionId;
|
|
73
|
-
if (p.parentId)
|
|
74
|
-
body.parent_id = p.parentId;
|
|
75
|
-
if (p.assigneeId)
|
|
76
|
-
body.assignee_id = p.assigneeId;
|
|
77
|
-
return api(ctx, "POST", "/tasks", body);
|
|
267
|
+
return api(ctx, "POST", "tasks", {
|
|
268
|
+
body: taskBody(input),
|
|
269
|
+
});
|
|
78
270
|
},
|
|
79
271
|
});
|
|
80
272
|
rl.registerAction("task.get", {
|
|
81
273
|
access: "read",
|
|
82
274
|
description: "Get a task by ID",
|
|
83
|
-
inputSchema:
|
|
275
|
+
inputSchema: byId,
|
|
84
276
|
async execute(input, ctx) {
|
|
85
|
-
|
|
277
|
+
const { id } = input;
|
|
278
|
+
return api(ctx, "GET", `tasks/${pathSegment(id)}`);
|
|
86
279
|
},
|
|
87
280
|
});
|
|
88
281
|
rl.registerAction("task.list", {
|
|
89
282
|
access: "read",
|
|
90
|
-
description: "List tasks",
|
|
91
|
-
inputSchema:
|
|
92
|
-
projectId: { type: "string", required: false },
|
|
93
|
-
sectionId: { type: "string", required: false },
|
|
94
|
-
label: { type: "string", required: false },
|
|
95
|
-
filter: { type: "string", required: false },
|
|
96
|
-
limit: { type: "number", required: false },
|
|
97
|
-
},
|
|
283
|
+
description: "List active tasks, by project, section, parent or label, or by a Todoist filter query instead",
|
|
284
|
+
inputSchema: taskList,
|
|
98
285
|
async execute(input, ctx) {
|
|
99
286
|
const p = (input ?? {});
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
if (p.sectionId)
|
|
104
|
-
qs.section_id = p.sectionId;
|
|
105
|
-
if (p.label)
|
|
106
|
-
qs.label = p.label;
|
|
107
|
-
if (p.filter)
|
|
108
|
-
qs.filter = p.filter;
|
|
109
|
-
const data = (await api(ctx, "GET", "/tasks", undefined, qs));
|
|
110
|
-
return p.limit ? data.slice(0, p.limit) : data;
|
|
287
|
+
if (p.filter !== undefined)
|
|
288
|
+
return listAll(ctx, "tasks/filter", { query: p.filter, lang: p.lang }, p.limit);
|
|
289
|
+
return listAll(ctx, "tasks", wire(p, { ...placeNames, label: "label" }), p.limit);
|
|
111
290
|
},
|
|
112
291
|
});
|
|
113
292
|
rl.registerAction("task.update", {
|
|
114
293
|
access: "write",
|
|
115
|
-
description: "Update a task",
|
|
116
|
-
inputSchema:
|
|
117
|
-
id: { type: "string", required: true },
|
|
118
|
-
content: { type: "string", required: false },
|
|
119
|
-
description: { type: "string", required: false },
|
|
120
|
-
priority: { type: "number", required: false },
|
|
121
|
-
dueString: { type: "string", required: false },
|
|
122
|
-
dueDate: { type: "string", required: false },
|
|
123
|
-
labels: { type: "object", required: false },
|
|
124
|
-
assigneeId: { type: "string", required: false },
|
|
125
|
-
},
|
|
294
|
+
description: "Update a task; only the fields given change",
|
|
295
|
+
inputSchema: taskUpdate,
|
|
126
296
|
async execute(input, ctx) {
|
|
127
297
|
const { id, ...fields } = input;
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
if (fields.description)
|
|
132
|
-
body.description = fields.description;
|
|
133
|
-
if (fields.priority)
|
|
134
|
-
body.priority = fields.priority;
|
|
135
|
-
if (fields.dueString)
|
|
136
|
-
body.due_string = fields.dueString;
|
|
137
|
-
if (fields.dueDate)
|
|
138
|
-
body.due_date = fields.dueDate;
|
|
139
|
-
if (fields.labels)
|
|
140
|
-
body.labels = fields.labels;
|
|
141
|
-
if (fields.assigneeId)
|
|
142
|
-
body.assignee_id = fields.assigneeId;
|
|
143
|
-
return api(ctx, "POST", `/tasks/${pathSegment(id)}`, body);
|
|
298
|
+
return api(ctx, "POST", `tasks/${pathSegment(id)}`, {
|
|
299
|
+
body: taskBody(fields),
|
|
300
|
+
});
|
|
144
301
|
},
|
|
145
302
|
});
|
|
146
303
|
rl.registerAction("task.close", {
|
|
147
304
|
access: "write",
|
|
148
305
|
description: "Close (complete) a task",
|
|
149
|
-
inputSchema:
|
|
306
|
+
inputSchema: byId,
|
|
150
307
|
async execute(input, ctx) {
|
|
151
|
-
|
|
152
|
-
return
|
|
308
|
+
const { id } = input;
|
|
309
|
+
return act(ctx, "POST", `tasks/${pathSegment(id)}/close`);
|
|
153
310
|
},
|
|
154
311
|
});
|
|
155
312
|
rl.registerAction("task.reopen", {
|
|
156
313
|
access: "write",
|
|
157
314
|
description: "Reopen a task",
|
|
158
|
-
inputSchema:
|
|
315
|
+
inputSchema: byId,
|
|
159
316
|
async execute(input, ctx) {
|
|
160
|
-
|
|
161
|
-
return
|
|
317
|
+
const { id } = input;
|
|
318
|
+
return act(ctx, "POST", `tasks/${pathSegment(id)}/reopen`);
|
|
162
319
|
},
|
|
163
320
|
});
|
|
164
321
|
rl.registerAction("task.delete", {
|
|
165
322
|
access: "write",
|
|
166
323
|
description: "Delete a task",
|
|
167
|
-
inputSchema:
|
|
324
|
+
inputSchema: byId,
|
|
168
325
|
async execute(input, ctx) {
|
|
169
|
-
|
|
170
|
-
return
|
|
326
|
+
const { id } = input;
|
|
327
|
+
return act(ctx, "DELETE", `tasks/${pathSegment(id)}`);
|
|
171
328
|
},
|
|
172
329
|
});
|
|
173
330
|
rl.registerAction("task.quickAdd", {
|
|
174
331
|
access: "write",
|
|
175
332
|
description: "Quick add a task using natural language",
|
|
176
|
-
inputSchema:
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
333
|
+
inputSchema: taskQuickAdd,
|
|
334
|
+
async execute(input, ctx) {
|
|
335
|
+
const p = input;
|
|
336
|
+
return api(ctx, "POST", "tasks/quick", {
|
|
337
|
+
body: wire(p, {
|
|
338
|
+
text: "text",
|
|
339
|
+
note: "note",
|
|
340
|
+
reminder: "reminder",
|
|
341
|
+
autoReminder: "auto_reminder",
|
|
342
|
+
}),
|
|
343
|
+
});
|
|
344
|
+
},
|
|
345
|
+
});
|
|
346
|
+
rl.registerAction("task.move", {
|
|
347
|
+
access: "write",
|
|
348
|
+
description: "Move a task to another project, section or parent task (exactly one)",
|
|
349
|
+
inputSchema: taskMove,
|
|
350
|
+
async execute(input, ctx) {
|
|
351
|
+
const { id, ...place } = input;
|
|
352
|
+
return api(ctx, "POST", `tasks/${pathSegment(id)}/move`, {
|
|
353
|
+
body: wire(place, placeNames),
|
|
354
|
+
});
|
|
184
355
|
},
|
|
356
|
+
});
|
|
357
|
+
rl.registerAction("task.listCompleted", {
|
|
358
|
+
access: "read",
|
|
359
|
+
description: "List tasks completed in a time window of at most 3 months, by completion date",
|
|
360
|
+
inputSchema: taskListCompleted,
|
|
185
361
|
async execute(input, ctx) {
|
|
186
362
|
const p = input;
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
363
|
+
return listAll(ctx, "tasks/completed/by_completion_date", wire(p, {
|
|
364
|
+
since: "since",
|
|
365
|
+
until: "until",
|
|
366
|
+
...placeNames,
|
|
367
|
+
filter: "filter_query",
|
|
368
|
+
lang: "filter_lang",
|
|
369
|
+
}), p.limit, "items");
|
|
193
370
|
},
|
|
194
371
|
});
|
|
195
372
|
// ── Project ─────────────────────────────────────────
|
|
196
373
|
rl.registerAction("project.create", {
|
|
197
374
|
access: "write",
|
|
198
375
|
description: "Create a project",
|
|
199
|
-
inputSchema:
|
|
200
|
-
name: { type: "string", required: true },
|
|
201
|
-
color: { type: "string", required: false },
|
|
202
|
-
isFavorite: { type: "boolean", required: false },
|
|
203
|
-
parentId: { type: "string", required: false },
|
|
204
|
-
viewStyle: {
|
|
205
|
-
type: "string",
|
|
206
|
-
required: false,
|
|
207
|
-
description: "list or board",
|
|
208
|
-
},
|
|
209
|
-
},
|
|
376
|
+
inputSchema: projectCreate,
|
|
210
377
|
async execute(input, ctx) {
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
body.color = p.color;
|
|
215
|
-
if (p.isFavorite)
|
|
216
|
-
body.is_favorite = true;
|
|
217
|
-
if (p.parentId)
|
|
218
|
-
body.parent_id = p.parentId;
|
|
219
|
-
if (p.viewStyle)
|
|
220
|
-
body.view_style = p.viewStyle;
|
|
221
|
-
return api(ctx, "POST", "/projects", body);
|
|
378
|
+
return api(ctx, "POST", "projects", {
|
|
379
|
+
body: wire(input, projectNames),
|
|
380
|
+
});
|
|
222
381
|
},
|
|
223
382
|
});
|
|
224
383
|
rl.registerAction("project.get", {
|
|
225
384
|
access: "read",
|
|
226
385
|
description: "Get a project",
|
|
227
|
-
inputSchema:
|
|
386
|
+
inputSchema: byId,
|
|
228
387
|
async execute(input, ctx) {
|
|
229
|
-
|
|
388
|
+
const { id } = input;
|
|
389
|
+
return api(ctx, "GET", `projects/${pathSegment(id)}`);
|
|
230
390
|
},
|
|
231
391
|
});
|
|
232
392
|
rl.registerAction("project.list", {
|
|
233
393
|
access: "read",
|
|
234
394
|
description: "List all projects",
|
|
235
|
-
inputSchema: {},
|
|
395
|
+
inputSchema: t.Object({}, STRICT),
|
|
236
396
|
async execute(_input, ctx) {
|
|
237
|
-
return
|
|
397
|
+
return listAll(ctx, "projects");
|
|
238
398
|
},
|
|
239
399
|
});
|
|
240
400
|
rl.registerAction("project.update", {
|
|
241
401
|
access: "write",
|
|
242
|
-
description: "Update a project",
|
|
243
|
-
inputSchema:
|
|
244
|
-
id: { type: "string", required: true },
|
|
245
|
-
name: { type: "string", required: false },
|
|
246
|
-
color: { type: "string", required: false },
|
|
247
|
-
isFavorite: { type: "boolean", required: false },
|
|
248
|
-
viewStyle: { type: "string", required: false },
|
|
249
|
-
},
|
|
402
|
+
description: "Update a project; only the fields given change",
|
|
403
|
+
inputSchema: projectUpdate,
|
|
250
404
|
async execute(input, ctx) {
|
|
251
405
|
const { id, ...fields } = input;
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
if (fields.color)
|
|
256
|
-
body.color = fields.color;
|
|
257
|
-
if (fields.isFavorite !== undefined)
|
|
258
|
-
body.is_favorite = fields.isFavorite;
|
|
259
|
-
if (fields.viewStyle)
|
|
260
|
-
body.view_style = fields.viewStyle;
|
|
261
|
-
return api(ctx, "POST", `/projects/${pathSegment(id)}`, body);
|
|
406
|
+
return api(ctx, "POST", `projects/${pathSegment(id)}`, {
|
|
407
|
+
body: wire(fields, projectNames),
|
|
408
|
+
});
|
|
262
409
|
},
|
|
263
410
|
});
|
|
264
411
|
rl.registerAction("project.delete", {
|
|
265
412
|
access: "write",
|
|
266
413
|
description: "Delete a project",
|
|
267
|
-
inputSchema:
|
|
414
|
+
inputSchema: byId,
|
|
268
415
|
async execute(input, ctx) {
|
|
269
|
-
|
|
270
|
-
return
|
|
416
|
+
const { id } = input;
|
|
417
|
+
return act(ctx, "DELETE", `projects/${pathSegment(id)}`);
|
|
271
418
|
},
|
|
272
419
|
});
|
|
273
420
|
rl.registerAction("project.archive", {
|
|
274
421
|
access: "write",
|
|
275
422
|
description: "Archive a project",
|
|
276
|
-
inputSchema:
|
|
423
|
+
inputSchema: byId,
|
|
277
424
|
async execute(input, ctx) {
|
|
278
|
-
|
|
279
|
-
return
|
|
425
|
+
const { id } = input;
|
|
426
|
+
return act(ctx, "POST", `projects/${pathSegment(id)}/archive`);
|
|
280
427
|
},
|
|
281
428
|
});
|
|
282
429
|
rl.registerAction("project.unarchive", {
|
|
283
430
|
access: "write",
|
|
284
431
|
description: "Unarchive a project",
|
|
285
|
-
inputSchema:
|
|
432
|
+
inputSchema: byId,
|
|
286
433
|
async execute(input, ctx) {
|
|
287
|
-
|
|
288
|
-
return
|
|
434
|
+
const { id } = input;
|
|
435
|
+
return act(ctx, "POST", `projects/${pathSegment(id)}/unarchive`);
|
|
289
436
|
},
|
|
290
437
|
});
|
|
291
438
|
rl.registerAction("project.getCollaborators", {
|
|
292
439
|
access: "read",
|
|
293
|
-
description: "Get project collaborators",
|
|
294
|
-
inputSchema:
|
|
440
|
+
description: "Get project collaborators: [{id,name,email}]",
|
|
441
|
+
inputSchema: byId,
|
|
295
442
|
async execute(input, ctx) {
|
|
296
|
-
|
|
443
|
+
const { id } = input;
|
|
444
|
+
return listAll(ctx, `projects/${pathSegment(id)}/collaborators`);
|
|
297
445
|
},
|
|
298
446
|
});
|
|
299
447
|
// ── Section ─────────────────────────────────────────
|
|
300
448
|
rl.registerAction("section.create", {
|
|
301
449
|
access: "write",
|
|
302
450
|
description: "Create a section",
|
|
303
|
-
inputSchema:
|
|
304
|
-
projectId: { type: "string", required: true },
|
|
305
|
-
name: { type: "string", required: true },
|
|
306
|
-
order: { type: "number", required: false },
|
|
307
|
-
},
|
|
451
|
+
inputSchema: sectionCreate,
|
|
308
452
|
async execute(input, ctx) {
|
|
309
453
|
const p = input;
|
|
310
|
-
return api(ctx, "POST", "
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
454
|
+
return api(ctx, "POST", "sections", {
|
|
455
|
+
body: wire(p, {
|
|
456
|
+
projectId: "project_id",
|
|
457
|
+
name: "name",
|
|
458
|
+
order: "order",
|
|
459
|
+
}),
|
|
314
460
|
});
|
|
315
461
|
},
|
|
316
462
|
});
|
|
317
463
|
rl.registerAction("section.get", {
|
|
318
464
|
access: "read",
|
|
319
465
|
description: "Get a section",
|
|
320
|
-
inputSchema:
|
|
466
|
+
inputSchema: byId,
|
|
321
467
|
async execute(input, ctx) {
|
|
322
|
-
|
|
468
|
+
const { id } = input;
|
|
469
|
+
return api(ctx, "GET", `sections/${pathSegment(id)}`);
|
|
323
470
|
},
|
|
324
471
|
});
|
|
325
472
|
rl.registerAction("section.list", {
|
|
326
473
|
access: "read",
|
|
327
|
-
description: "List sections",
|
|
328
|
-
inputSchema:
|
|
474
|
+
description: "List sections, of one project or all",
|
|
475
|
+
inputSchema: sectionList,
|
|
329
476
|
async execute(input, ctx) {
|
|
330
|
-
const
|
|
331
|
-
|
|
332
|
-
qs.project_id = input.projectId;
|
|
333
|
-
return api(ctx, "GET", "/sections", undefined, qs);
|
|
477
|
+
const p = (input ?? {});
|
|
478
|
+
return listAll(ctx, "sections", wire(p, { projectId: "project_id" }));
|
|
334
479
|
},
|
|
335
480
|
});
|
|
336
481
|
rl.registerAction("section.update", {
|
|
337
482
|
access: "write",
|
|
338
|
-
description: "
|
|
339
|
-
inputSchema:
|
|
340
|
-
id: { type: "string", required: true },
|
|
341
|
-
name: { type: "string", required: true },
|
|
342
|
-
},
|
|
483
|
+
description: "Rename a section",
|
|
484
|
+
inputSchema: sectionUpdate,
|
|
343
485
|
async execute(input, ctx) {
|
|
344
486
|
const p = input;
|
|
345
|
-
return api(ctx, "POST",
|
|
346
|
-
name: p.name,
|
|
487
|
+
return api(ctx, "POST", `sections/${pathSegment(p.id)}`, {
|
|
488
|
+
body: { name: p.name },
|
|
347
489
|
});
|
|
348
490
|
},
|
|
349
491
|
});
|
|
350
492
|
rl.registerAction("section.delete", {
|
|
351
493
|
access: "write",
|
|
352
494
|
description: "Delete a section",
|
|
353
|
-
inputSchema:
|
|
495
|
+
inputSchema: byId,
|
|
354
496
|
async execute(input, ctx) {
|
|
355
|
-
|
|
356
|
-
return
|
|
497
|
+
const { id } = input;
|
|
498
|
+
return act(ctx, "DELETE", `sections/${pathSegment(id)}`);
|
|
357
499
|
},
|
|
358
500
|
});
|
|
359
501
|
// ── Comment ─────────────────────────────────────────
|
|
360
502
|
rl.registerAction("comment.create", {
|
|
361
503
|
access: "write",
|
|
362
|
-
description: "
|
|
363
|
-
inputSchema:
|
|
364
|
-
taskId: { type: "string", required: true },
|
|
365
|
-
content: { type: "string", required: true },
|
|
366
|
-
},
|
|
504
|
+
description: "Comment on a task or on a project (exactly one)",
|
|
505
|
+
inputSchema: commentCreate,
|
|
367
506
|
async execute(input, ctx) {
|
|
368
507
|
const p = input;
|
|
369
|
-
return api(ctx, "POST", "
|
|
370
|
-
|
|
371
|
-
|
|
508
|
+
return api(ctx, "POST", "comments", {
|
|
509
|
+
body: wire(p, {
|
|
510
|
+
taskId: "task_id",
|
|
511
|
+
projectId: "project_id",
|
|
512
|
+
content: "content",
|
|
513
|
+
}),
|
|
372
514
|
});
|
|
373
515
|
},
|
|
374
516
|
});
|
|
375
517
|
rl.registerAction("comment.get", {
|
|
376
518
|
access: "read",
|
|
377
519
|
description: "Get a comment",
|
|
378
|
-
inputSchema:
|
|
520
|
+
inputSchema: byId,
|
|
379
521
|
async execute(input, ctx) {
|
|
380
|
-
|
|
522
|
+
const { id } = input;
|
|
523
|
+
return api(ctx, "GET", `comments/${pathSegment(id)}`);
|
|
381
524
|
},
|
|
382
525
|
});
|
|
383
526
|
rl.registerAction("comment.list", {
|
|
384
527
|
access: "read",
|
|
385
|
-
description: "List comments",
|
|
386
|
-
inputSchema:
|
|
387
|
-
taskId: { type: "string", required: false },
|
|
388
|
-
projectId: { type: "string", required: false },
|
|
389
|
-
},
|
|
528
|
+
description: "List the comments of a task or of a project (exactly one)",
|
|
529
|
+
inputSchema: commentList,
|
|
390
530
|
async execute(input, ctx) {
|
|
391
|
-
const p =
|
|
392
|
-
|
|
393
|
-
if (p.taskId)
|
|
394
|
-
qs.task_id = p.taskId;
|
|
395
|
-
if (p.projectId)
|
|
396
|
-
qs.project_id = p.projectId;
|
|
397
|
-
return api(ctx, "GET", "/comments", undefined, qs);
|
|
531
|
+
const p = input;
|
|
532
|
+
return listAll(ctx, "comments", wire(p, { taskId: "task_id", projectId: "project_id" }));
|
|
398
533
|
},
|
|
399
534
|
});
|
|
400
535
|
rl.registerAction("comment.update", {
|
|
401
536
|
access: "write",
|
|
402
537
|
description: "Update a comment",
|
|
403
|
-
inputSchema:
|
|
404
|
-
id: { type: "string", required: true },
|
|
405
|
-
content: { type: "string", required: true },
|
|
406
|
-
},
|
|
538
|
+
inputSchema: commentUpdate,
|
|
407
539
|
async execute(input, ctx) {
|
|
408
540
|
const p = input;
|
|
409
|
-
return api(ctx, "POST",
|
|
410
|
-
content: p.content,
|
|
541
|
+
return api(ctx, "POST", `comments/${pathSegment(p.id)}`, {
|
|
542
|
+
body: { content: p.content },
|
|
411
543
|
});
|
|
412
544
|
},
|
|
413
545
|
});
|
|
414
546
|
rl.registerAction("comment.delete", {
|
|
415
547
|
access: "write",
|
|
416
548
|
description: "Delete a comment",
|
|
417
|
-
inputSchema:
|
|
549
|
+
inputSchema: byId,
|
|
418
550
|
async execute(input, ctx) {
|
|
419
|
-
|
|
420
|
-
return
|
|
551
|
+
const { id } = input;
|
|
552
|
+
return act(ctx, "DELETE", `comments/${pathSegment(id)}`);
|
|
421
553
|
},
|
|
422
554
|
});
|
|
423
555
|
// ── Label ───────────────────────────────────────────
|
|
424
556
|
rl.registerAction("label.create", {
|
|
425
557
|
access: "write",
|
|
426
558
|
description: "Create a label",
|
|
427
|
-
inputSchema:
|
|
428
|
-
name: { type: "string", required: true },
|
|
429
|
-
color: { type: "string", required: false },
|
|
430
|
-
order: { type: "number", required: false },
|
|
431
|
-
isFavorite: { type: "boolean", required: false },
|
|
432
|
-
},
|
|
559
|
+
inputSchema: labelCreate,
|
|
433
560
|
async execute(input, ctx) {
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
body.color = p.color;
|
|
438
|
-
if (p.order)
|
|
439
|
-
body.order = p.order;
|
|
440
|
-
if (p.isFavorite)
|
|
441
|
-
body.is_favorite = true;
|
|
442
|
-
return api(ctx, "POST", "/labels", body);
|
|
561
|
+
return api(ctx, "POST", "labels", {
|
|
562
|
+
body: wire(input, labelNames),
|
|
563
|
+
});
|
|
443
564
|
},
|
|
444
565
|
});
|
|
445
566
|
rl.registerAction("label.get", {
|
|
446
567
|
access: "read",
|
|
447
568
|
description: "Get a label",
|
|
448
|
-
inputSchema:
|
|
569
|
+
inputSchema: byId,
|
|
449
570
|
async execute(input, ctx) {
|
|
450
|
-
|
|
571
|
+
const { id } = input;
|
|
572
|
+
return api(ctx, "GET", `labels/${pathSegment(id)}`);
|
|
451
573
|
},
|
|
452
574
|
});
|
|
453
575
|
rl.registerAction("label.list", {
|
|
454
576
|
access: "read",
|
|
455
577
|
description: "List all labels",
|
|
456
|
-
inputSchema: {},
|
|
578
|
+
inputSchema: t.Object({}, STRICT),
|
|
457
579
|
async execute(_input, ctx) {
|
|
458
|
-
return
|
|
580
|
+
return listAll(ctx, "labels");
|
|
459
581
|
},
|
|
460
582
|
});
|
|
461
583
|
rl.registerAction("label.update", {
|
|
462
584
|
access: "write",
|
|
463
|
-
description: "Update a label",
|
|
464
|
-
inputSchema:
|
|
465
|
-
id: { type: "string", required: true },
|
|
466
|
-
name: { type: "string", required: false },
|
|
467
|
-
color: { type: "string", required: false },
|
|
468
|
-
order: { type: "number", required: false },
|
|
469
|
-
isFavorite: { type: "boolean", required: false },
|
|
470
|
-
},
|
|
585
|
+
description: "Update a label; only the fields given change",
|
|
586
|
+
inputSchema: labelUpdate,
|
|
471
587
|
async execute(input, ctx) {
|
|
472
588
|
const { id, ...fields } = input;
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
if (fields.color)
|
|
477
|
-
body.color = fields.color;
|
|
478
|
-
if (fields.order)
|
|
479
|
-
body.order = fields.order;
|
|
480
|
-
if (fields.isFavorite !== undefined)
|
|
481
|
-
body.is_favorite = fields.isFavorite;
|
|
482
|
-
return api(ctx, "POST", `/labels/${pathSegment(id)}`, body);
|
|
589
|
+
return api(ctx, "POST", `labels/${pathSegment(id)}`, {
|
|
590
|
+
body: wire(fields, labelNames),
|
|
591
|
+
});
|
|
483
592
|
},
|
|
484
593
|
});
|
|
485
594
|
rl.registerAction("label.delete", {
|
|
486
595
|
access: "write",
|
|
487
596
|
description: "Delete a label",
|
|
488
|
-
inputSchema:
|
|
597
|
+
inputSchema: byId,
|
|
489
598
|
async execute(input, ctx) {
|
|
490
|
-
|
|
491
|
-
return
|
|
599
|
+
const { id } = input;
|
|
600
|
+
return act(ctx, "DELETE", `labels/${pathSegment(id)}`);
|
|
492
601
|
},
|
|
493
602
|
});
|
|
494
603
|
}
|