runline 0.7.7 → 0.8.1

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 (41) hide show
  1. package/dist/commands/auth.js +4 -2
  2. package/dist/config/loader.d.ts +2 -4
  3. package/dist/config/loader.js +2 -1
  4. package/dist/core/engine.js +18 -13
  5. package/dist/index.d.ts +2 -2
  6. package/dist/plugin/api.d.ts +2 -9
  7. package/dist/plugin/api.js +1 -4
  8. package/dist/plugin/schema.d.ts +19 -0
  9. package/dist/plugin/schema.js +168 -0
  10. package/dist/plugin/types.d.ts +22 -8
  11. package/dist/plugins/_shared/imageFile.js +40 -0
  12. package/dist/plugins/_shared/microsoftAuth.js +100 -0
  13. package/dist/plugins/gmail/src/index.js +27 -3
  14. package/dist/plugins/googleAppsScript/src/index.js +203 -0
  15. package/dist/plugins/googleImage/src/index.js +30 -11
  16. package/dist/plugins/linear/src/attachments.js +65 -0
  17. package/dist/plugins/linear/src/comments.js +50 -0
  18. package/dist/plugins/linear/src/cycles.js +40 -0
  19. package/dist/plugins/linear/src/index.js +31 -1153
  20. package/dist/plugins/linear/src/initiatives.js +79 -0
  21. package/dist/plugins/linear/src/issues.js +245 -0
  22. package/dist/plugins/linear/src/labels.js +69 -0
  23. package/dist/plugins/linear/src/organization.js +12 -0
  24. package/dist/plugins/linear/src/projects.js +189 -0
  25. package/dist/plugins/linear/src/shared.js +131 -0
  26. package/dist/plugins/linear/src/states.js +39 -0
  27. package/dist/plugins/linear/src/teams.js +74 -0
  28. package/dist/plugins/linear/src/users.js +36 -0
  29. package/dist/plugins/linear/src/views.js +96 -0
  30. package/dist/plugins/linear/src/webhooks.js +57 -0
  31. package/dist/plugins/microsoftCalendar/src/index.js +46 -0
  32. package/dist/plugins/microsoftFiles/src/index.js +127 -0
  33. package/dist/plugins/microsoftMail/src/index.js +91 -0
  34. package/dist/plugins/openai/src/index.js +45 -20
  35. package/dist/plugins/parallel/src/index.js +100 -0
  36. package/dist/plugins/recraft/src/index.js +10 -6
  37. package/dist/plugins/replicate/src/index.js +17 -3
  38. package/dist/plugins/steel/src/index.js +378 -0
  39. package/dist/plugins/together/src/index.js +10 -6
  40. package/dist/plugins/xai/src/index.js +11 -5
  41. package/package.json +3 -2
@@ -0,0 +1,203 @@
1
+ import { googleAccessToken } from "../../_shared/googleAuth.js";
2
+ const SCRIPT_API = "https://script.googleapis.com/v1";
3
+ const DRIVE_API = "https://www.googleapis.com/drive/v3";
4
+ const SCOPES = [
5
+ "https://www.googleapis.com/auth/script.projects",
6
+ "https://www.googleapis.com/auth/script.deployments",
7
+ "https://www.googleapis.com/auth/script.processes",
8
+ "https://www.googleapis.com/auth/drive.metadata.readonly",
9
+ ];
10
+ function accessToken(ctx) {
11
+ return googleAccessToken(ctx, "googleAppsScript", SCOPES);
12
+ }
13
+ async function call(ctx, method, url, payload) {
14
+ const headers = { Authorization: `Bearer ${await accessToken(ctx)}` };
15
+ if (payload !== undefined)
16
+ headers["Content-Type"] = "application/json";
17
+ const res = await fetch(url, {
18
+ method,
19
+ headers,
20
+ body: payload !== undefined ? JSON.stringify(payload) : undefined,
21
+ });
22
+ const text = await res.text();
23
+ let json;
24
+ try {
25
+ json = text ? JSON.parse(text) : {};
26
+ }
27
+ catch {
28
+ json = { raw: text };
29
+ }
30
+ if (!res.ok) {
31
+ const msg = json?.error?.message || json?.error_description || text.slice(0, 300) || res.status;
32
+ throw new Error(`googleAppsScript: ${method} ${res.status}: ${msg}`);
33
+ }
34
+ return json;
35
+ }
36
+ export default function googleAppsScript(rl) {
37
+ rl.setName("googleAppsScript");
38
+ rl.setVersion("1.0.0");
39
+ rl.setConnectionSchema({
40
+ clientId: { type: "string", required: false, description: "OAuth client ID", env: "GOOGLE_APPS_SCRIPT_CLIENT_ID" },
41
+ clientSecret: { type: "string", required: false, description: "OAuth client secret", env: "GOOGLE_APPS_SCRIPT_CLIENT_SECRET" },
42
+ refreshToken: { type: "string", required: false, description: "OAuth refresh token", env: "GOOGLE_APPS_SCRIPT_REFRESH_TOKEN" },
43
+ serviceAccountJson: { type: "string", required: false, description: "Service-account key JSON", env: "GOOGLE_APPS_SCRIPT_SERVICE_ACCOUNT_JSON" },
44
+ serviceAccountEmail: { type: "string", required: false, description: "Service-account email", env: "GOOGLE_APPS_SCRIPT_SERVICE_ACCOUNT_EMAIL" },
45
+ serviceAccountPrivateKey: { type: "string", required: false, description: "Service-account private key", env: "GOOGLE_APPS_SCRIPT_SERVICE_ACCOUNT_PRIVATE_KEY" },
46
+ serviceAccountSubject: { type: "string", required: false, description: "User to impersonate (domain-wide delegation)", env: "GOOGLE_APPS_SCRIPT_SERVICE_ACCOUNT_SUBJECT" },
47
+ });
48
+ rl.registerAction("script.list", {
49
+ description: "List Apps Script projects in Drive (standalone scripts; bound scripts live inside their container and don't appear here).",
50
+ inputSchema: {
51
+ query: { type: "string", required: false, description: "Case-insensitive name substring filter." },
52
+ pageSize: { type: "number", required: false, description: "Max results (default 50)." },
53
+ },
54
+ async execute(input, ctx) {
55
+ const params = new URLSearchParams({
56
+ q: "mimeType='application/vnd.google-apps.script' and trashed=false",
57
+ fields: "files(id,name,modifiedTime)",
58
+ pageSize: String(input.pageSize ?? 50),
59
+ orderBy: "modifiedTime desc",
60
+ });
61
+ const res = await call(ctx, "GET", `${DRIVE_API}/files?${params}`);
62
+ let files = res.files ?? [];
63
+ if (input.query) {
64
+ const q = String(input.query).toLowerCase();
65
+ files = files.filter((f) => (f.name || "").toLowerCase().includes(q));
66
+ }
67
+ return { count: files.length, scripts: files };
68
+ },
69
+ });
70
+ rl.registerAction("project.getContent", {
71
+ description: "Get all files of an Apps Script project (name, type, source).",
72
+ inputSchema: { scriptId: { type: "string", required: true } },
73
+ async execute(input, ctx) {
74
+ const res = await call(ctx, "GET", `${SCRIPT_API}/projects/${input.scriptId}/content`);
75
+ return { scriptId: input.scriptId, files: res.files ?? [] };
76
+ },
77
+ });
78
+ rl.registerAction("project.readFile", {
79
+ description: "Read one file's source from a project.",
80
+ inputSchema: {
81
+ scriptId: { type: "string", required: true },
82
+ name: { type: "string", required: true, description: "File name without extension (e.g. 'Code', 'appsscript')." },
83
+ },
84
+ async execute(input, ctx) {
85
+ const res = await call(ctx, "GET", `${SCRIPT_API}/projects/${input.scriptId}/content`);
86
+ const file = (res.files ?? []).find((f) => f.name === input.name);
87
+ if (!file)
88
+ throw new Error(`No file "${input.name}". Available: ${(res.files ?? []).map((f) => f.name).join(", ")}`);
89
+ return { name: file.name, type: file.type, source: file.source };
90
+ },
91
+ });
92
+ rl.registerAction("file.edit", {
93
+ description: "Replace (or add) a single file's source, leaving other files untouched. Read-modify-write — the safe way to change code.",
94
+ inputSchema: {
95
+ scriptId: { type: "string", required: true },
96
+ name: { type: "string", required: true },
97
+ source: { type: "string", required: true },
98
+ type: { type: "string", required: false, description: "SERVER_JS (default), HTML, or JSON (for appsscript)." },
99
+ },
100
+ async execute(input, ctx) {
101
+ const cur = await call(ctx, "GET", `${SCRIPT_API}/projects/${input.scriptId}/content`);
102
+ const files = cur.files ?? [];
103
+ const idx = files.findIndex((f) => f.name === input.name);
104
+ const type = input.type || (input.name === "appsscript" ? "JSON" : files[idx]?.type || "SERVER_JS");
105
+ const entry = { name: input.name, type, source: input.source };
106
+ if (idx >= 0)
107
+ files[idx] = entry;
108
+ else
109
+ files.push(entry);
110
+ await call(ctx, "PUT", `${SCRIPT_API}/projects/${input.scriptId}/content`, { files });
111
+ return { scriptId: input.scriptId, updated: input.name, fileCount: files.length };
112
+ },
113
+ });
114
+ rl.registerAction("project.updateContent", {
115
+ description: "Replace the entire project file set. files = [{name, type, source}], must include the appsscript JSON manifest. Prefer file.edit for single changes.",
116
+ inputSchema: {
117
+ scriptId: { type: "string", required: true },
118
+ files: { type: "array", required: true, description: "[{name, type: SERVER_JS|HTML|JSON, source}]" },
119
+ },
120
+ async execute(input, ctx) {
121
+ const res = await call(ctx, "PUT", `${SCRIPT_API}/projects/${input.scriptId}/content`, { files: input.files });
122
+ return { scriptId: input.scriptId, fileCount: (res.files ?? []).length };
123
+ },
124
+ });
125
+ rl.registerAction("project.create", {
126
+ description: "Create a new Apps Script project. Pass parentId (a Drive file id, e.g. a Sheet) to bind it to that container.",
127
+ inputSchema: {
128
+ title: { type: "string", required: true },
129
+ parentId: { type: "string", required: false, description: "Container Drive file id for a bound script." },
130
+ },
131
+ async execute(input, ctx) {
132
+ const payload = { title: input.title };
133
+ if (input.parentId)
134
+ payload.parentId = input.parentId;
135
+ const res = await call(ctx, "POST", `${SCRIPT_API}/projects`, payload);
136
+ return { scriptId: res.scriptId, title: res.title, parentId: res.parentId };
137
+ },
138
+ });
139
+ rl.registerAction("version.create", {
140
+ description: "Create an immutable version of the project (needed before deploying).",
141
+ inputSchema: {
142
+ scriptId: { type: "string", required: true },
143
+ description: { type: "string", required: false },
144
+ },
145
+ async execute(input, ctx) {
146
+ const res = await call(ctx, "POST", `${SCRIPT_API}/projects/${input.scriptId}/versions`, { description: input.description || "" });
147
+ return { scriptId: input.scriptId, versionNumber: res.versionNumber, description: res.description };
148
+ },
149
+ });
150
+ rl.registerAction("deployment.create", {
151
+ description: "Deploy a version. For function.run, deploy with an API-executable manifest (executionApi access).",
152
+ inputSchema: {
153
+ scriptId: { type: "string", required: true },
154
+ versionNumber: { type: "number", required: true },
155
+ description: { type: "string", required: false },
156
+ manifestFileName: { type: "string", required: false, description: "Defaults to 'appsscript'." },
157
+ },
158
+ async execute(input, ctx) {
159
+ const res = await call(ctx, "POST", `${SCRIPT_API}/projects/${input.scriptId}/deployments`, {
160
+ versionNumber: input.versionNumber,
161
+ manifestFileName: input.manifestFileName || "appsscript",
162
+ description: input.description || "",
163
+ });
164
+ return { deploymentId: res.deploymentId, entryPoints: res.entryPoints };
165
+ },
166
+ });
167
+ rl.registerAction("function.run", {
168
+ description: "Run a function via scripts.run. Requires the project linked to a standard GCP project, the Apps Script API enabled, and an API-executable deployment (or devMode for the owner).",
169
+ inputSchema: {
170
+ scriptId: { type: "string", required: true },
171
+ functionName: { type: "string", required: true },
172
+ parameters: { type: "array", required: false, description: "Positional args for the function." },
173
+ devMode: { type: "boolean", required: false, description: "Run latest saved code (owner only). Default true." },
174
+ },
175
+ async execute(input, ctx) {
176
+ const res = await call(ctx, "POST", `${SCRIPT_API}/scripts/${input.scriptId}:run`, {
177
+ function: input.functionName,
178
+ parameters: input.parameters ?? [],
179
+ devMode: input.devMode === undefined ? true : input.devMode,
180
+ });
181
+ if (res.error) {
182
+ const d = res.error.details?.[0];
183
+ throw new Error(`Function error: ${d?.errorMessage || res.error.message}`);
184
+ }
185
+ return { done: res.done, result: res.response?.result ?? null };
186
+ },
187
+ });
188
+ rl.registerAction("process.list", {
189
+ description: "Recent executions for a project (status, function, times) — a log view.",
190
+ inputSchema: {
191
+ scriptId: { type: "string", required: true },
192
+ pageSize: { type: "number", required: false, description: "Default 20." },
193
+ },
194
+ async execute(input, ctx) {
195
+ const params = new URLSearchParams({
196
+ "userProcessFilter.scriptId": input.scriptId,
197
+ pageSize: String(input.pageSize ?? 20),
198
+ });
199
+ const res = await call(ctx, "GET", `${SCRIPT_API}/processes?${params}`);
200
+ return { count: (res.processes ?? []).length, processes: res.processes ?? [] };
201
+ },
202
+ });
203
+ }
@@ -6,15 +6,20 @@
6
6
  * API key. Kept under the `googleImage` namespace so it doesn't
7
7
  * collide with `googleDrive`, `googleDocs`, etc.
8
8
  *
9
- * await googleImage.image.create({ prompt: "a watercolor fox" })
10
- * await googleImage.image.create({
11
- * prompt: "edit: make the sky stormier",
12
- * model: "gemini-3-pro-image-preview",
13
- * })
9
+ * const { images } = await googleImage.image.create({ prompt: "a watercolor fox" })
10
+ * // images[0].path -> "/tmp/googleImage-….png"
11
+ *
12
+ * Generated images are written to disk and the action returns their file
13
+ * `path`s — never raw base64, which bloats the agent context and is
14
+ * stripped before delivery. Hand each `path` to the host's file-sending
15
+ * tool (e.g. send_file) to deliver the image.
14
16
  *
15
17
  * Nano Banana supports conversational editing — chain prompts in
16
18
  * follow-up calls and it'll keep iterating on the last image.
17
19
  */
20
+ import { writeFileSync } from "node:fs";
21
+ import { tmpdir } from "node:os";
22
+ import { join } from "node:path";
18
23
  const BASE = "https://generativelanguage.googleapis.com/v1beta/models";
19
24
  export default function googleImage(rl) {
20
25
  rl.setName("googleImage");
@@ -28,7 +33,7 @@ export default function googleImage(rl) {
28
33
  },
29
34
  });
30
35
  rl.registerAction("image.create", {
31
- description: "Generate an image with Google's Gemini image models (Nano Banana / Imagen). Returns base64 bytes per candidate.",
36
+ description: "Generate an image with Google's Gemini image models (Nano Banana / Imagen). Writes the image(s) to disk and returns their file `path`s — not base64. Deliver each image to the user with send_file using its `path`.",
32
37
  inputSchema: {
33
38
  prompt: {
34
39
  type: "string",
@@ -40,6 +45,11 @@ export default function googleImage(rl) {
40
45
  required: false,
41
46
  description: "gemini-2.5-flash-image (Nano Banana, default) | gemini-3-pro-image-preview | gemini-3.1-flash-image-preview",
42
47
  },
48
+ saveDir: {
49
+ type: "string",
50
+ required: false,
51
+ description: "Directory to write the image file(s) into. Defaults to the OS temp dir.",
52
+ },
43
53
  },
44
54
  async execute(input, ctx) {
45
55
  const p = (input ?? {});
@@ -62,18 +72,27 @@ export default function googleImage(rl) {
62
72
  throw new Error(`Google API error ${res.status}: ${await res.text()}`);
63
73
  }
64
74
  const data = (await res.json());
75
+ const dir = (typeof p.saveDir === "string" && p.saveDir.trim()) || tmpdir();
76
+ const stamp = Date.now();
65
77
  const images = [];
66
78
  for (const candidate of data.candidates ?? []) {
67
79
  for (const part of candidate.content?.parts ?? []) {
68
80
  if (part.inlineData?.data) {
69
- images.push({
70
- base64: part.inlineData.data,
71
- mimeType: part.inlineData.mimeType ?? "image/png",
72
- });
81
+ const mimeType = part.inlineData.mimeType ?? "image/png";
82
+ const ext = mimeType.includes("jpeg") ? "jpg" : mimeType.split("/")[1] || "png";
83
+ const bytes = Buffer.from(part.inlineData.data, "base64");
84
+ const path = join(dir, `googleImage-${stamp}-${images.length}.${ext}`);
85
+ writeFileSync(path, bytes);
86
+ images.push({ path, mimeType, byteLength: bytes.length });
73
87
  }
74
88
  }
75
89
  }
76
- return { provider: "googleImage", model, images };
90
+ return {
91
+ provider: "googleImage",
92
+ model,
93
+ images,
94
+ note: "Image(s) written to disk. Deliver each to the user with send_file using its `path`.",
95
+ };
77
96
  },
78
97
  });
79
98
  }
@@ -0,0 +1,65 @@
1
+ import * as t from "typebox";
2
+ import { ATTACHMENT_FIELDS, bindGetAction, bindListAction, gql, key } from "./shared.js";
3
+ export function registerAttachmentActions(rl) {
4
+ const listAction = bindListAction(rl);
5
+ const getAction = bindGetAction(rl);
6
+ listAction("attachment.list", "List issue attachments.", "attachments", "AttachmentFilter", ATTACHMENT_FIELDS);
7
+ getAction("attachment.get", "Get an attachment by ID.", "attachment", ATTACHMENT_FIELDS);
8
+ rl.registerAction("attachment.create", {
9
+ description: "Create an attachment on an issue.",
10
+ inputSchema: t.Object({
11
+ issueId: t.String({ description: "The issue to associate the attachment with. UUID or issue identifier (e.g., 'LIN-123')" }),
12
+ title: t.String({ description: "The attachment title" }),
13
+ url: t.String({ description: "Attachment location, also used as a unique identifier. Re-creating with the same url updates the existing record" }),
14
+ subtitle: t.Optional(t.String({ description: "The attachment subtitle" })),
15
+ iconUrl: t.Optional(t.String({ description: "An icon url to display with the attachment (jpg or png, max 1MB, ideally 20x20px)" })),
16
+ commentBody: t.Optional(t.String({ description: "Create a linked comment with markdown body" })),
17
+ groupBySource: t.Optional(t.Boolean({ description: "Whether attachments for the same source application should be grouped in the Linear UI" })),
18
+ metadata: t.Optional(t.Object({}, { description: "Attachment metadata object with string and number values (JSONObject)" })),
19
+ id: t.Optional(t.String({ description: "The identifier in UUID v4 format. If none is provided, the backend will generate one" })),
20
+ }),
21
+ async execute(input, ctx) {
22
+ const data = await gql(key(ctx), `mutation($input: AttachmentCreateInput!) { attachmentCreate(input: $input) { success attachment { ${ATTACHMENT_FIELDS} } } }`, { input: input });
23
+ return data.attachmentCreate?.attachment;
24
+ },
25
+ });
26
+ rl.registerAction("attachment.update", {
27
+ description: "Update an attachment. title is required.",
28
+ inputSchema: t.Object({
29
+ id: t.String({ description: "The identifier of the attachment to update" }),
30
+ title: t.String({ description: "The attachment title" }),
31
+ subtitle: t.Optional(t.String({ description: "The attachment subtitle" })),
32
+ iconUrl: t.Optional(t.String({ description: "An icon url to display with the attachment" })),
33
+ metadata: t.Optional(t.Object({}, { description: "Attachment metadata object with string and number values (JSONObject)" })),
34
+ }),
35
+ async execute(input, ctx) {
36
+ const { id, ...fields } = input;
37
+ const data = await gql(key(ctx), `mutation($id: String!, $input: AttachmentUpdateInput!) { attachmentUpdate(id: $id, input: $input) { success attachment { ${ATTACHMENT_FIELDS} } } }`, { id, input: fields });
38
+ return data.attachmentUpdate?.attachment;
39
+ },
40
+ });
41
+ rl.registerAction("attachment.linkURL", {
42
+ description: "Link any URL to an issue. If a workspace integration matches the URL (Zendesk, GitHub, Slack, etc.) a rich attachment is created; otherwise a basic one.",
43
+ inputSchema: t.Object({
44
+ issueId: t.String({ description: "The issue for which to link the url. UUID or issue identifier (e.g., 'LIN-123')" }),
45
+ url: t.String({ description: "The url to link" }),
46
+ title: t.Optional(t.String({ description: "The title to use for the attachment" })),
47
+ id: t.Optional(t.String({ description: "The id for the attachment (optional UUID override)" })),
48
+ }),
49
+ async execute(input, ctx) {
50
+ const { issueId, url, title, id } = input;
51
+ const data = await gql(key(ctx), `mutation($issueId: String!, $url: String!, $title: String, $id: String) {
52
+ attachmentLinkURL(issueId: $issueId, url: $url, title: $title, id: $id) { success attachment { ${ATTACHMENT_FIELDS} } }
53
+ }`, { issueId, url, title: title ?? null, id: id ?? null });
54
+ return data.attachmentLinkURL?.attachment;
55
+ },
56
+ });
57
+ rl.registerAction("attachment.delete", {
58
+ description: "Delete an attachment.",
59
+ inputSchema: t.Object({ id: t.String({ description: "The identifier of the attachment to delete" }) }),
60
+ async execute(input, ctx) {
61
+ const data = await gql(key(ctx), `mutation($id: String!) { attachmentDelete(id: $id) { success } }`, { id: input.id });
62
+ return data.attachmentDelete;
63
+ },
64
+ });
65
+ }
@@ -0,0 +1,50 @@
1
+ import * as t from "typebox";
2
+ import { COMMENT_FIELDS, bindListAction, gql, key } from "./shared.js";
3
+ export function registerCommentActions(rl) {
4
+ const listAction = bindListAction(rl);
5
+ rl.registerAction("issue.addComment", {
6
+ description: "Add a comment to an issue. Pass parentId to nest as a reply.",
7
+ inputSchema: t.Object({
8
+ issueId: t.String({ description: "The issue to associate the comment with. UUID or issue identifier (e.g., 'LIN-123')" }),
9
+ body: t.String({ description: "The comment content in markdown format" }),
10
+ parentId: t.Optional(t.String({ description: "The parent comment under which to nest this comment" })),
11
+ doNotSubscribeToIssue: t.Optional(t.Boolean({ description: "Prevent auto-subscription to the issue the comment is created on" })),
12
+ quotedText: t.Optional(t.String({ description: "The text that this comment references (inline comments)" })),
13
+ }),
14
+ async execute(input, ctx) {
15
+ const fields = input;
16
+ const data = await gql(key(ctx), `mutation($input: CommentCreateInput!) { commentCreate(input: $input) { success comment { ${COMMENT_FIELDS} } } }`, { input: fields });
17
+ return data.commentCreate?.comment;
18
+ },
19
+ });
20
+ listAction("comment.list", "List comments across the workspace.", "comments", "CommentFilter", COMMENT_FIELDS);
21
+ rl.registerAction("comment.get", {
22
+ description: "Get a comment by ID.",
23
+ inputSchema: t.Object({ id: t.String() }),
24
+ async execute(input, ctx) {
25
+ const data = await gql(key(ctx), `query($id: String!) { comment(id: $id) { ${COMMENT_FIELDS} } }`, { id: input.id });
26
+ return data.comment;
27
+ },
28
+ });
29
+ rl.registerAction("comment.update", {
30
+ description: "Update a comment.",
31
+ inputSchema: t.Object({
32
+ id: t.String({ description: "The identifier of the comment to update" }),
33
+ body: t.Optional(t.String({ description: "The comment content in markdown format" })),
34
+ quotedText: t.Optional(t.String({ description: "The text that this comment references (inline comments)" })),
35
+ }),
36
+ async execute(input, ctx) {
37
+ const { id, ...fields } = input;
38
+ const data = await gql(key(ctx), `mutation($id: String!, $input: CommentUpdateInput!) { commentUpdate(id: $id, input: $input) { success comment { ${COMMENT_FIELDS} } } }`, { id, input: fields });
39
+ return data.commentUpdate?.comment;
40
+ },
41
+ });
42
+ rl.registerAction("comment.delete", {
43
+ description: "Delete a comment.",
44
+ inputSchema: t.Object({ id: t.String() }),
45
+ async execute(input, ctx) {
46
+ const data = await gql(key(ctx), `mutation($id: String!) { commentDelete(id: $id) { success } }`, { id: input.id });
47
+ return data.commentDelete;
48
+ },
49
+ });
50
+ }
@@ -0,0 +1,40 @@
1
+ import * as t from "typebox";
2
+ import { CYCLE_FIELDS, bindGetAction, bindListAction, gql, key } from "./shared.js";
3
+ export function registerCycleActions(rl) {
4
+ const listAction = bindListAction(rl);
5
+ const getAction = bindGetAction(rl);
6
+ listAction("cycle.list", "List cycles. Use filter for isActive/isNext/isPrevious.", "cycles", "CycleFilter", CYCLE_FIELDS);
7
+ getAction("cycle.get", "Get a cycle by ID.", "cycle", CYCLE_FIELDS);
8
+ rl.registerAction("cycle.create", {
9
+ description: "Create a cycle for a team.",
10
+ inputSchema: t.Object({
11
+ teamId: t.String({ description: "The team to associate the cycle with" }),
12
+ startsAt: t.String({ description: "The start time of the cycle (DateTime, ISO 8601)" }),
13
+ endsAt: t.String({ description: "The end time of the cycle (DateTime, ISO 8601)" }),
14
+ name: t.Optional(t.String({ description: "The custom name of the cycle" })),
15
+ description: t.Optional(t.String({ description: "The description of the cycle" })),
16
+ completedAt: t.Optional(t.String({ description: "The completion time of the cycle (DateTime). If null, the cycle hasn't been completed" })),
17
+ id: t.Optional(t.String({ description: "The identifier in UUID v4 format. If none is provided, the backend will generate one" })),
18
+ }),
19
+ async execute(input, ctx) {
20
+ const data = await gql(key(ctx), `mutation($input: CycleCreateInput!) { cycleCreate(input: $input) { success cycle { ${CYCLE_FIELDS} } } }`, { input: input });
21
+ return data.cycleCreate?.cycle;
22
+ },
23
+ });
24
+ rl.registerAction("cycle.update", {
25
+ description: "Update a cycle.",
26
+ inputSchema: t.Object({
27
+ id: t.String({ description: "The identifier of the cycle to update" }),
28
+ name: t.Optional(t.String({ description: "The custom name of the cycle" })),
29
+ description: t.Optional(t.String({ description: "The description of the cycle" })),
30
+ startsAt: t.Optional(t.String({ description: "The start time of the cycle (DateTime, ISO 8601)" })),
31
+ endsAt: t.Optional(t.String({ description: "The end time of the cycle (DateTime, ISO 8601)" })),
32
+ completedAt: t.Optional(t.String({ description: "The completion time of the cycle (DateTime). If null, the cycle hasn't been completed" })),
33
+ }),
34
+ async execute(input, ctx) {
35
+ const { id, ...fields } = input;
36
+ const data = await gql(key(ctx), `mutation($id: String!, $input: CycleUpdateInput!) { cycleUpdate(id: $id, input: $input) { success cycle { ${CYCLE_FIELDS} } } }`, { id, input: fields });
37
+ return data.cycleUpdate?.cycle;
38
+ },
39
+ });
40
+ }