runline 0.3.3 → 0.4.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/main.js +0 -0
- package/dist/plugins/gmail/src/index.js +13 -13
- package/dist/plugins/googleCalendar/src/index.js +795 -0
- package/dist/plugins/googleContacts/src/index.js +691 -0
- package/dist/plugins/googleDocs/src/index.js +669 -0
- package/dist/plugins/googleDrive/src/index.js +1161 -0
- package/dist/plugins/googleSheets/src/index.js +913 -0
- package/dist/plugins/googleSlides/src/index.js +319 -0
- package/dist/plugins/googleTasks/src/index.js +419 -0
- package/package.json +3 -2
|
@@ -0,0 +1,419 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Google Tasks plugin for runline.
|
|
3
|
+
*
|
|
4
|
+
* OAuth2 user flow, same shape as the rest of the Google plugins.
|
|
5
|
+
* Scope: `auth/tasks` (full read/write).
|
|
6
|
+
*
|
|
7
|
+
* Surface area:
|
|
8
|
+
*
|
|
9
|
+
* taskList.create / taskList.get / taskList.list /
|
|
10
|
+
* taskList.update / taskList.delete
|
|
11
|
+
*
|
|
12
|
+
* task.create / task.get / task.list / task.update / task.delete
|
|
13
|
+
* task.move (reorder / reparent within a list)
|
|
14
|
+
* task.clear (hide all completed tasks in a list)
|
|
15
|
+
*
|
|
16
|
+
* Dates passed via `due`, `completed`, `completedMin/Max`,
|
|
17
|
+
* `dueMin/Max`, `updatedMin` may be ISO strings (`"2024-12-25"`,
|
|
18
|
+
* `"2024-12-25T10:00:00Z"`) or already-RFC3339-formatted strings.
|
|
19
|
+
* Google's Tasks API is strict about RFC3339 with a timezone — we
|
|
20
|
+
* normalize ISO input via `Date.toISOString()` when detected.
|
|
21
|
+
*/
|
|
22
|
+
// ─── OAuth ───────────────────────────────────────────────────────
|
|
23
|
+
const TOKEN_ENDPOINT = "https://oauth2.googleapis.com/token";
|
|
24
|
+
const REFRESH_SKEW_MS = 60_000;
|
|
25
|
+
async function refreshAccessToken(ctx) {
|
|
26
|
+
const cfg = ctx.connection.config;
|
|
27
|
+
const { clientId, clientSecret, refreshToken } = cfg;
|
|
28
|
+
if (!clientId || !clientSecret || !refreshToken) {
|
|
29
|
+
throw new Error("googleTasks: missing clientId/clientSecret/refreshToken. Run the Tasks OAuth helper to seed these.");
|
|
30
|
+
}
|
|
31
|
+
const body = new URLSearchParams({
|
|
32
|
+
client_id: clientId,
|
|
33
|
+
client_secret: clientSecret,
|
|
34
|
+
refresh_token: refreshToken,
|
|
35
|
+
grant_type: "refresh_token",
|
|
36
|
+
});
|
|
37
|
+
const res = await fetch(TOKEN_ENDPOINT, {
|
|
38
|
+
method: "POST",
|
|
39
|
+
headers: { "Content-Type": "application/x-www-form-urlencoded" },
|
|
40
|
+
body: body.toString(),
|
|
41
|
+
});
|
|
42
|
+
if (!res.ok) {
|
|
43
|
+
throw new Error(`googleTasks: token refresh failed (${res.status}): ${await res.text()}`);
|
|
44
|
+
}
|
|
45
|
+
const data = (await res.json());
|
|
46
|
+
const expiresAt = Date.now() + data.expires_in * 1000;
|
|
47
|
+
await ctx.updateConnection({
|
|
48
|
+
accessToken: data.access_token,
|
|
49
|
+
accessTokenExpiresAt: expiresAt,
|
|
50
|
+
});
|
|
51
|
+
return data.access_token;
|
|
52
|
+
}
|
|
53
|
+
async function accessToken(ctx) {
|
|
54
|
+
const cfg = ctx.connection.config;
|
|
55
|
+
if (cfg.accessToken &&
|
|
56
|
+
typeof cfg.accessTokenExpiresAt === "number" &&
|
|
57
|
+
Date.now() < cfg.accessTokenExpiresAt - REFRESH_SKEW_MS) {
|
|
58
|
+
return cfg.accessToken;
|
|
59
|
+
}
|
|
60
|
+
return refreshAccessToken(ctx);
|
|
61
|
+
}
|
|
62
|
+
// ─── Request ─────────────────────────────────────────────────────
|
|
63
|
+
const API_BASE = "https://tasks.googleapis.com/tasks/v1";
|
|
64
|
+
async function tasksRequest(ctx, method, path, body, qs) {
|
|
65
|
+
const token = await accessToken(ctx);
|
|
66
|
+
const url = new URL(`${API_BASE}${path}`);
|
|
67
|
+
if (qs) {
|
|
68
|
+
for (const [k, v] of Object.entries(qs)) {
|
|
69
|
+
if (v === undefined || v === null)
|
|
70
|
+
continue;
|
|
71
|
+
url.searchParams.set(k, String(v));
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
const init = {
|
|
75
|
+
method,
|
|
76
|
+
headers: { Authorization: `Bearer ${token}`, Accept: "application/json" },
|
|
77
|
+
};
|
|
78
|
+
if (body && Object.keys(body).length > 0) {
|
|
79
|
+
init.headers["Content-Type"] = "application/json";
|
|
80
|
+
init.body = JSON.stringify(body);
|
|
81
|
+
}
|
|
82
|
+
const res = await fetch(url.toString(), init);
|
|
83
|
+
if (res.status === 204)
|
|
84
|
+
return { success: true };
|
|
85
|
+
const text = await res.text();
|
|
86
|
+
if (!res.ok) {
|
|
87
|
+
throw new Error(`googleTasks: ${method} ${path} → ${res.status} ${text}`);
|
|
88
|
+
}
|
|
89
|
+
return text ? JSON.parse(text) : { success: true };
|
|
90
|
+
}
|
|
91
|
+
async function paginateAll(ctx, path, qs) {
|
|
92
|
+
const out = [];
|
|
93
|
+
const query = { ...qs, maxResults: qs.maxResults ?? 100 };
|
|
94
|
+
do {
|
|
95
|
+
const page = (await tasksRequest(ctx, "GET", path, undefined, query));
|
|
96
|
+
out.push(...(page.items ?? []));
|
|
97
|
+
query.pageToken = page.nextPageToken;
|
|
98
|
+
} while (query.pageToken);
|
|
99
|
+
return out;
|
|
100
|
+
}
|
|
101
|
+
// ─── Helpers ────────────────────────────────────────────────────
|
|
102
|
+
/**
|
|
103
|
+
* Normalize a date/time value for the Tasks API. Accepts ISO strings,
|
|
104
|
+
* epoch numbers, or already-RFC3339 strings. Google demands RFC3339
|
|
105
|
+
* with a timezone (`Z` or `+01:00`) on `due`, `completed`, and the
|
|
106
|
+
* `completedMin/Max`/`dueMin/Max`/`updatedMin` filters.
|
|
107
|
+
*/
|
|
108
|
+
function toRFC3339(v) {
|
|
109
|
+
if (v === undefined || v === null)
|
|
110
|
+
return undefined;
|
|
111
|
+
if (typeof v === "string") {
|
|
112
|
+
// If it already looks like RFC3339 (ends with Z or a numeric offset), pass through.
|
|
113
|
+
if (/Z$|[+-]\d{2}:?\d{2}$/.test(v))
|
|
114
|
+
return v;
|
|
115
|
+
const d = new Date(v);
|
|
116
|
+
if (Number.isNaN(d.getTime())) {
|
|
117
|
+
throw new Error(`googleTasks: invalid date "${v}"`);
|
|
118
|
+
}
|
|
119
|
+
return d.toISOString();
|
|
120
|
+
}
|
|
121
|
+
if (typeof v === "number") {
|
|
122
|
+
return new Date(v).toISOString();
|
|
123
|
+
}
|
|
124
|
+
if (v instanceof Date)
|
|
125
|
+
return v.toISOString();
|
|
126
|
+
throw new Error("googleTasks: date must be ISO string, epoch ms, or Date");
|
|
127
|
+
}
|
|
128
|
+
// ─── Plugin ──────────────────────────────────────────────────────
|
|
129
|
+
const SCOPES = ["https://www.googleapis.com/auth/tasks"];
|
|
130
|
+
export default function googleTasks(rl) {
|
|
131
|
+
rl.setName("googleTasks");
|
|
132
|
+
rl.setVersion("0.1.0");
|
|
133
|
+
rl.setOAuth({
|
|
134
|
+
authUrl: "https://accounts.google.com/o/oauth2/v2/auth",
|
|
135
|
+
tokenUrl: "https://oauth2.googleapis.com/token",
|
|
136
|
+
scopes: SCOPES,
|
|
137
|
+
authParams: { access_type: "offline", prompt: "consent" },
|
|
138
|
+
setupHelp: [
|
|
139
|
+
"You need a Google Cloud OAuth client. Takes ~5 minutes, one time.",
|
|
140
|
+
"",
|
|
141
|
+
"1. Create or pick a Google Cloud project:",
|
|
142
|
+
" https://console.cloud.google.com/projectcreate",
|
|
143
|
+
"",
|
|
144
|
+
"2. Enable the Google Tasks API:",
|
|
145
|
+
" https://console.cloud.google.com/apis/library/tasks.googleapis.com",
|
|
146
|
+
"",
|
|
147
|
+
"3. Configure the OAuth consent screen:",
|
|
148
|
+
" https://console.cloud.google.com/apis/credentials/consent",
|
|
149
|
+
" • Audience: External",
|
|
150
|
+
"",
|
|
151
|
+
"4. Add yourself as a test user:",
|
|
152
|
+
" https://console.cloud.google.com/auth/audience",
|
|
153
|
+
"",
|
|
154
|
+
"5. Create the OAuth client:",
|
|
155
|
+
" https://console.cloud.google.com/apis/credentials",
|
|
156
|
+
" • + Create credentials → OAuth client ID",
|
|
157
|
+
" • Application type: Web application",
|
|
158
|
+
" • Authorized redirect URIs → + Add URI: {{redirectUri}}",
|
|
159
|
+
"",
|
|
160
|
+
"6. Paste the Client ID and Client Secret below, or export",
|
|
161
|
+
" GOOGLE_TASKS_CLIENT_ID and GOOGLE_TASKS_CLIENT_SECRET.",
|
|
162
|
+
],
|
|
163
|
+
});
|
|
164
|
+
rl.setConnectionSchema({
|
|
165
|
+
clientId: { type: "string", required: true, env: "GOOGLE_TASKS_CLIENT_ID" },
|
|
166
|
+
clientSecret: { type: "string", required: true, env: "GOOGLE_TASKS_CLIENT_SECRET" },
|
|
167
|
+
refreshToken: { type: "string", required: true, env: "GOOGLE_TASKS_REFRESH_TOKEN" },
|
|
168
|
+
accessToken: { type: "string", required: false },
|
|
169
|
+
accessTokenExpiresAt: { type: "number", required: false },
|
|
170
|
+
});
|
|
171
|
+
// ── Task lists ────────────────────────────────────────
|
|
172
|
+
rl.registerAction("taskList.list", {
|
|
173
|
+
description: "List the authenticated user's task lists",
|
|
174
|
+
inputSchema: {
|
|
175
|
+
returnAll: { type: "boolean", required: false },
|
|
176
|
+
maxResults: { type: "number", required: false },
|
|
177
|
+
pageToken: { type: "string", required: false },
|
|
178
|
+
},
|
|
179
|
+
async execute(input, ctx) {
|
|
180
|
+
const p = (input ?? {});
|
|
181
|
+
const qs = {};
|
|
182
|
+
if (p.pageToken)
|
|
183
|
+
qs.pageToken = p.pageToken;
|
|
184
|
+
if (p.returnAll)
|
|
185
|
+
return paginateAll(ctx, "/users/@me/lists", qs);
|
|
186
|
+
if (p.maxResults)
|
|
187
|
+
qs.maxResults = p.maxResults;
|
|
188
|
+
const res = (await tasksRequest(ctx, "GET", "/users/@me/lists", undefined, qs));
|
|
189
|
+
return res.items ?? [];
|
|
190
|
+
},
|
|
191
|
+
});
|
|
192
|
+
rl.registerAction("taskList.get", {
|
|
193
|
+
description: "Get a task list by ID",
|
|
194
|
+
inputSchema: { taskListId: { type: "string", required: true } },
|
|
195
|
+
async execute(input, ctx) {
|
|
196
|
+
const p = (input ?? {});
|
|
197
|
+
return tasksRequest(ctx, "GET", `/users/@me/lists/${p.taskListId}`);
|
|
198
|
+
},
|
|
199
|
+
});
|
|
200
|
+
rl.registerAction("taskList.create", {
|
|
201
|
+
description: "Create a new task list",
|
|
202
|
+
inputSchema: { title: { type: "string", required: true } },
|
|
203
|
+
async execute(input, ctx) {
|
|
204
|
+
const p = (input ?? {});
|
|
205
|
+
return tasksRequest(ctx, "POST", "/users/@me/lists", { title: p.title });
|
|
206
|
+
},
|
|
207
|
+
});
|
|
208
|
+
rl.registerAction("taskList.update", {
|
|
209
|
+
description: "Update a task list (currently only `title` is writable).",
|
|
210
|
+
inputSchema: {
|
|
211
|
+
taskListId: { type: "string", required: true },
|
|
212
|
+
title: { type: "string", required: true },
|
|
213
|
+
},
|
|
214
|
+
async execute(input, ctx) {
|
|
215
|
+
const p = (input ?? {});
|
|
216
|
+
return tasksRequest(ctx, "PATCH", `/users/@me/lists/${p.taskListId}`, {
|
|
217
|
+
title: p.title,
|
|
218
|
+
});
|
|
219
|
+
},
|
|
220
|
+
});
|
|
221
|
+
rl.registerAction("taskList.delete", {
|
|
222
|
+
description: "Delete a task list and all its tasks",
|
|
223
|
+
inputSchema: { taskListId: { type: "string", required: true } },
|
|
224
|
+
async execute(input, ctx) {
|
|
225
|
+
const p = (input ?? {});
|
|
226
|
+
await tasksRequest(ctx, "DELETE", `/users/@me/lists/${p.taskListId}`);
|
|
227
|
+
return { success: true };
|
|
228
|
+
},
|
|
229
|
+
});
|
|
230
|
+
// ── Tasks ─────────────────────────────────────────────
|
|
231
|
+
rl.registerAction("task.create", {
|
|
232
|
+
description: "Create a task in a list",
|
|
233
|
+
inputSchema: {
|
|
234
|
+
taskListId: { type: "string", required: true },
|
|
235
|
+
title: { type: "string", required: true },
|
|
236
|
+
notes: { type: "string", required: false },
|
|
237
|
+
due: {
|
|
238
|
+
type: "string",
|
|
239
|
+
required: false,
|
|
240
|
+
description: "RFC3339 or ISO datetime; normalized to RFC3339",
|
|
241
|
+
},
|
|
242
|
+
status: {
|
|
243
|
+
type: "string",
|
|
244
|
+
required: false,
|
|
245
|
+
description: "needsAction (default) | completed",
|
|
246
|
+
},
|
|
247
|
+
completed: {
|
|
248
|
+
type: "string",
|
|
249
|
+
required: false,
|
|
250
|
+
description: "Completion timestamp; implies status=completed",
|
|
251
|
+
},
|
|
252
|
+
deleted: { type: "boolean", required: false },
|
|
253
|
+
parent: {
|
|
254
|
+
type: "string",
|
|
255
|
+
required: false,
|
|
256
|
+
description: "Parent task ID (nests this task under another)",
|
|
257
|
+
},
|
|
258
|
+
previous: {
|
|
259
|
+
type: "string",
|
|
260
|
+
required: false,
|
|
261
|
+
description: "Insert after this task ID; omit to place at top of level",
|
|
262
|
+
},
|
|
263
|
+
},
|
|
264
|
+
async execute(input, ctx) {
|
|
265
|
+
const p = (input ?? {});
|
|
266
|
+
const body = { title: p.title };
|
|
267
|
+
if (p.notes)
|
|
268
|
+
body.notes = p.notes;
|
|
269
|
+
if (p.status)
|
|
270
|
+
body.status = p.status;
|
|
271
|
+
if (p.deleted !== undefined)
|
|
272
|
+
body.deleted = p.deleted;
|
|
273
|
+
const due = toRFC3339(p.due);
|
|
274
|
+
if (due)
|
|
275
|
+
body.due = due;
|
|
276
|
+
const completed = toRFC3339(p.completed);
|
|
277
|
+
if (completed)
|
|
278
|
+
body.completed = completed;
|
|
279
|
+
const qs = {};
|
|
280
|
+
if (p.parent)
|
|
281
|
+
qs.parent = p.parent;
|
|
282
|
+
if (p.previous)
|
|
283
|
+
qs.previous = p.previous;
|
|
284
|
+
return tasksRequest(ctx, "POST", `/lists/${p.taskListId}/tasks`, body, qs);
|
|
285
|
+
},
|
|
286
|
+
});
|
|
287
|
+
rl.registerAction("task.get", {
|
|
288
|
+
description: "Get a single task",
|
|
289
|
+
inputSchema: {
|
|
290
|
+
taskListId: { type: "string", required: true },
|
|
291
|
+
taskId: { type: "string", required: true },
|
|
292
|
+
},
|
|
293
|
+
async execute(input, ctx) {
|
|
294
|
+
const p = (input ?? {});
|
|
295
|
+
return tasksRequest(ctx, "GET", `/lists/${p.taskListId}/tasks/${p.taskId}`);
|
|
296
|
+
},
|
|
297
|
+
});
|
|
298
|
+
rl.registerAction("task.list", {
|
|
299
|
+
description: "List tasks in a list. Filters: dueMin/dueMax, completedMin/completedMax, updatedMin, showCompleted/showDeleted/showHidden.",
|
|
300
|
+
inputSchema: {
|
|
301
|
+
taskListId: { type: "string", required: true },
|
|
302
|
+
showCompleted: { type: "boolean", required: false, description: "default: true" },
|
|
303
|
+
showDeleted: { type: "boolean", required: false },
|
|
304
|
+
showHidden: { type: "boolean", required: false },
|
|
305
|
+
dueMin: { type: "string", required: false },
|
|
306
|
+
dueMax: { type: "string", required: false },
|
|
307
|
+
completedMin: { type: "string", required: false },
|
|
308
|
+
completedMax: { type: "string", required: false },
|
|
309
|
+
updatedMin: { type: "string", required: false },
|
|
310
|
+
returnAll: { type: "boolean", required: false },
|
|
311
|
+
maxResults: { type: "number", required: false },
|
|
312
|
+
pageToken: { type: "string", required: false },
|
|
313
|
+
},
|
|
314
|
+
async execute(input, ctx) {
|
|
315
|
+
const p = (input ?? {});
|
|
316
|
+
const qs = {
|
|
317
|
+
showCompleted: p.showCompleted ?? true,
|
|
318
|
+
showDeleted: p.showDeleted ?? false,
|
|
319
|
+
showHidden: p.showHidden ?? false,
|
|
320
|
+
};
|
|
321
|
+
for (const k of ["dueMin", "dueMax", "completedMin", "completedMax", "updatedMin"]) {
|
|
322
|
+
const v = toRFC3339(p[k]);
|
|
323
|
+
if (v)
|
|
324
|
+
qs[k] = v;
|
|
325
|
+
}
|
|
326
|
+
if (p.pageToken)
|
|
327
|
+
qs.pageToken = p.pageToken;
|
|
328
|
+
const path = `/lists/${p.taskListId}/tasks`;
|
|
329
|
+
if (p.returnAll)
|
|
330
|
+
return paginateAll(ctx, path, qs);
|
|
331
|
+
if (p.maxResults)
|
|
332
|
+
qs.maxResults = p.maxResults;
|
|
333
|
+
const res = (await tasksRequest(ctx, "GET", path, undefined, qs));
|
|
334
|
+
return res.items ?? [];
|
|
335
|
+
},
|
|
336
|
+
});
|
|
337
|
+
rl.registerAction("task.update", {
|
|
338
|
+
description: "Patch a task. Only supplied fields are sent. Set status=completed (and optionally `completed` timestamp) to mark done.",
|
|
339
|
+
inputSchema: {
|
|
340
|
+
taskListId: { type: "string", required: true },
|
|
341
|
+
taskId: { type: "string", required: true },
|
|
342
|
+
title: { type: "string", required: false },
|
|
343
|
+
notes: { type: "string", required: false },
|
|
344
|
+
due: { type: "string", required: false },
|
|
345
|
+
status: { type: "string", required: false },
|
|
346
|
+
completed: { type: "string", required: false },
|
|
347
|
+
deleted: { type: "boolean", required: false },
|
|
348
|
+
previous: {
|
|
349
|
+
type: "string",
|
|
350
|
+
required: false,
|
|
351
|
+
description: "Reorder: place after this task ID (query-only, not in body)",
|
|
352
|
+
},
|
|
353
|
+
},
|
|
354
|
+
async execute(input, ctx) {
|
|
355
|
+
const p = (input ?? {});
|
|
356
|
+
const body = {};
|
|
357
|
+
if (p.title !== undefined)
|
|
358
|
+
body.title = p.title;
|
|
359
|
+
if (p.notes !== undefined)
|
|
360
|
+
body.notes = p.notes;
|
|
361
|
+
if (p.status !== undefined)
|
|
362
|
+
body.status = p.status;
|
|
363
|
+
if (p.deleted !== undefined)
|
|
364
|
+
body.deleted = p.deleted;
|
|
365
|
+
const due = toRFC3339(p.due);
|
|
366
|
+
if (due)
|
|
367
|
+
body.due = due;
|
|
368
|
+
const completed = toRFC3339(p.completed);
|
|
369
|
+
if (completed)
|
|
370
|
+
body.completed = completed;
|
|
371
|
+
if (Object.keys(body).length === 0) {
|
|
372
|
+
throw new Error("googleTasks: nothing to update");
|
|
373
|
+
}
|
|
374
|
+
const qs = {};
|
|
375
|
+
if (p.previous)
|
|
376
|
+
qs.previous = p.previous;
|
|
377
|
+
return tasksRequest(ctx, "PATCH", `/lists/${p.taskListId}/tasks/${p.taskId}`, body, qs);
|
|
378
|
+
},
|
|
379
|
+
});
|
|
380
|
+
rl.registerAction("task.delete", {
|
|
381
|
+
description: "Delete a task",
|
|
382
|
+
inputSchema: {
|
|
383
|
+
taskListId: { type: "string", required: true },
|
|
384
|
+
taskId: { type: "string", required: true },
|
|
385
|
+
},
|
|
386
|
+
async execute(input, ctx) {
|
|
387
|
+
const p = (input ?? {});
|
|
388
|
+
await tasksRequest(ctx, "DELETE", `/lists/${p.taskListId}/tasks/${p.taskId}`);
|
|
389
|
+
return { success: true };
|
|
390
|
+
},
|
|
391
|
+
});
|
|
392
|
+
rl.registerAction("task.move", {
|
|
393
|
+
description: "Move a task within its list (reorder or reparent). `parent` nests under another task; `previous` places it after a sibling.",
|
|
394
|
+
inputSchema: {
|
|
395
|
+
taskListId: { type: "string", required: true },
|
|
396
|
+
taskId: { type: "string", required: true },
|
|
397
|
+
parent: { type: "string", required: false },
|
|
398
|
+
previous: { type: "string", required: false },
|
|
399
|
+
},
|
|
400
|
+
async execute(input, ctx) {
|
|
401
|
+
const p = (input ?? {});
|
|
402
|
+
const qs = {};
|
|
403
|
+
if (p.parent)
|
|
404
|
+
qs.parent = p.parent;
|
|
405
|
+
if (p.previous)
|
|
406
|
+
qs.previous = p.previous;
|
|
407
|
+
return tasksRequest(ctx, "POST", `/lists/${p.taskListId}/tasks/${p.taskId}/move`, undefined, qs);
|
|
408
|
+
},
|
|
409
|
+
});
|
|
410
|
+
rl.registerAction("task.clear", {
|
|
411
|
+
description: "Hide all completed tasks in a list from the default view. They remain accessible via task.list with showHidden=true.",
|
|
412
|
+
inputSchema: { taskListId: { type: "string", required: true } },
|
|
413
|
+
async execute(input, ctx) {
|
|
414
|
+
const p = (input ?? {});
|
|
415
|
+
await tasksRequest(ctx, "POST", `/lists/${p.taskListId}/clear`);
|
|
416
|
+
return { success: true };
|
|
417
|
+
},
|
|
418
|
+
});
|
|
419
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "runline",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.0",
|
|
4
4
|
"description": "Code mode for agents — turn any API or command into a callable action",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -64,6 +64,7 @@
|
|
|
64
64
|
"chalk": "^5.6.2",
|
|
65
65
|
"commander": "^14.0.3",
|
|
66
66
|
"proper-lockfile": "^4.1.2",
|
|
67
|
-
"quickjs-emscripten": "^0.32.0"
|
|
67
|
+
"quickjs-emscripten": "^0.32.0",
|
|
68
|
+
"rrule": "^2.8.1"
|
|
68
69
|
}
|
|
69
70
|
}
|