@allocator-one/harness-cli 1.5.2 → 1.7.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/README.md +42 -1
- package/dist/{chunk-CIW4OU5G.js → chunk-ZHFW6PRC.js} +299 -40
- package/dist/cli.js +121 -18
- package/dist/{server-J2V5JXFY.js → server-J6FQFVBJ.js} +89 -4
- package/package.json +1 -1
- package/spec/openapi.json +569 -2
package/dist/cli.js
CHANGED
|
@@ -6,6 +6,8 @@ import {
|
|
|
6
6
|
DEFAULT_BASE_URL,
|
|
7
7
|
HttpError,
|
|
8
8
|
LocalError,
|
|
9
|
+
RELATION_CREATE_TYPES,
|
|
10
|
+
RELATION_UPDATE_TYPES,
|
|
9
11
|
TransportError,
|
|
10
12
|
UpgradeRequiredError,
|
|
11
13
|
UsageError,
|
|
@@ -18,12 +20,14 @@ import {
|
|
|
18
20
|
comments,
|
|
19
21
|
create,
|
|
20
22
|
defaultCredentialsPath,
|
|
23
|
+
deleteComment,
|
|
21
24
|
deleteCredentials,
|
|
22
25
|
dep,
|
|
23
26
|
docCreate,
|
|
24
27
|
docList,
|
|
25
28
|
docShow,
|
|
26
29
|
docUpdate,
|
|
30
|
+
editComment,
|
|
27
31
|
findRepoConfig,
|
|
28
32
|
isHostTrusted,
|
|
29
33
|
json,
|
|
@@ -34,6 +38,7 @@ import {
|
|
|
34
38
|
linkAdd,
|
|
35
39
|
linkList,
|
|
36
40
|
linkRm,
|
|
41
|
+
linkUpdate,
|
|
37
42
|
list,
|
|
38
43
|
loginHelp,
|
|
39
44
|
memberAdd,
|
|
@@ -48,6 +53,11 @@ import {
|
|
|
48
53
|
projectUpdate,
|
|
49
54
|
readCredentials,
|
|
50
55
|
ready,
|
|
56
|
+
relationAdd,
|
|
57
|
+
relationList,
|
|
58
|
+
relationRm,
|
|
59
|
+
relationUpdate,
|
|
60
|
+
requireCommentId,
|
|
51
61
|
requireIdentifiers,
|
|
52
62
|
resolveCacheHome,
|
|
53
63
|
resolveConfig,
|
|
@@ -70,7 +80,7 @@ import {
|
|
|
70
80
|
version,
|
|
71
81
|
workflowStates,
|
|
72
82
|
writeCredentials
|
|
73
|
-
} from "./chunk-
|
|
83
|
+
} from "./chunk-ZHFW6PRC.js";
|
|
74
84
|
|
|
75
85
|
// src/cli.ts
|
|
76
86
|
import { createInterface as createInterface2 } from "readline";
|
|
@@ -1000,6 +1010,23 @@ function integer(value) {
|
|
|
1000
1010
|
function collect(value, previous) {
|
|
1001
1011
|
return [...previous, value];
|
|
1002
1012
|
}
|
|
1013
|
+
function resolvedCommentBody(text, bodyFile, action = "post") {
|
|
1014
|
+
if (bodyFile !== void 0 && text.length > 0) {
|
|
1015
|
+
throw new UsageError(
|
|
1016
|
+
"comment text and --body-file both given \u2014 pass one; there is no precedence between them"
|
|
1017
|
+
);
|
|
1018
|
+
}
|
|
1019
|
+
const fromFile = resolveTextSources([
|
|
1020
|
+
{ name: "body", inline: void 0, file: bodyFile }
|
|
1021
|
+
]).body;
|
|
1022
|
+
const body = fromFile === void 0 ? text : [fromFile];
|
|
1023
|
+
if (body.join(" ").trim() === "") {
|
|
1024
|
+
throw new UsageError(
|
|
1025
|
+
bodyFile === void 0 ? `the comment text is empty \u2014 nothing to ${action}` : `--body-file ${bodyFile} produced an empty comment \u2014 nothing to ${action}`
|
|
1026
|
+
);
|
|
1027
|
+
}
|
|
1028
|
+
return body;
|
|
1029
|
+
}
|
|
1003
1030
|
var requireIdentifiers2 = requireIdentifiers;
|
|
1004
1031
|
function interactive() {
|
|
1005
1032
|
const ci = process.env["CI"];
|
|
@@ -1208,10 +1235,15 @@ function requireNonBlankTitle(text, titleFile) {
|
|
|
1208
1235
|
titleFile === void 0 ? "--title is empty \u2014 an issue needs a title" : `--title-file ${titleFile} produced an empty title`
|
|
1209
1236
|
);
|
|
1210
1237
|
}
|
|
1211
|
-
program.command("create").description("create an issue").option("--title <text>", "issue title (required)").option("--description <text>", "problem statement").option("--design <text>", "design notes").option("--notes <text>", "working notes").option("--acceptance <text>", "acceptance criteria").option("--title-file <path>", 'read the title from a file ("-" for stdin)').option("--description-file <path>", 'read the description from a file ("-" for stdin)').option("--design-file <path>", 'read the design notes from a file ("-" for stdin)').option("--notes-file <path>", 'read the notes from a file ("-" for stdin)').option("--acceptance-file <path>", 'read the acceptance criteria from a file ("-" for stdin)').option(
|
|
1238
|
+
program.command("create").description("create an issue atomically after resolving requested labels").option("--title <text>", "issue title (required)").option("--description <text>", "problem statement").option("--design <text>", "design notes").option("--notes <text>", "working notes").option("--acceptance <text>", "acceptance criteria").option("--title-file <path>", 'read the title from a file ("-" for stdin)').option("--description-file <path>", 'read the description from a file ("-" for stdin)').option("--design-file <path>", 'read the design notes from a file ("-" for stdin)').option("--notes-file <path>", 'read the notes from a file ("-" for stdin)').option("--acceptance-file <path>", 'read the acceptance criteria from a file ("-" for stdin)').option(
|
|
1212
1239
|
"--type <type>",
|
|
1213
1240
|
"task | bug | feature | epic (applied as a type/ label)"
|
|
1214
|
-
).option("--priority <name>", "urgent | high | medium | low | none").option("--due-date <date>", "YYYY-MM-DD").option("--parent <id>", "parent epic").option("--assignee <who>", "me, a user id, an email, or a full name").option("--team <key>", "team key (or HARNESS_TEAM)").option(
|
|
1241
|
+
).option("--priority <name>", "urgent | high | medium | low | none").option("--due-date <date>", "YYYY-MM-DD").option("--parent <id>", "parent epic").option("--assignee <who>", "me, a user id, an email, or a full name").option("--team <key>", "team key (or HARNESS_TEAM)").option(
|
|
1242
|
+
"--label <name>",
|
|
1243
|
+
"resolve team-first, then workspace; create missing workspace labels before the issue (repeatable)",
|
|
1244
|
+
collect,
|
|
1245
|
+
[]
|
|
1246
|
+
).option("--json", "emit JSON").action(async (opts) => {
|
|
1215
1247
|
await withConfig(opts.json === true, true, (ctx) => {
|
|
1216
1248
|
if (opts.title === void 0 && opts.titleFile === void 0) {
|
|
1217
1249
|
throw new UsageError(
|
|
@@ -1227,6 +1259,16 @@ program.command("create").description("create an issue").option("--title <text>"
|
|
|
1227
1259
|
program.command("update").description("update an issue; --claim takes it atomically").argument("[id]").option("--claim", "claim atomically and move to the first started state").option("--state <name>", "workflow state by name or type").option("--title <text>").option("--description <text>").option("--design <text>").option("--notes <text>", "replace the notes").option("--append-notes <text>", "append to the notes").option("--acceptance <text>").option("--title-file <path>", 'read the title from a file ("-" for stdin)').option("--description-file <path>", 'read the description from a file ("-" for stdin)').option("--design-file <path>", 'read the design notes from a file ("-" for stdin)').option("--notes-file <path>", 'replace the notes from a file ("-" for stdin)').option("--append-notes-file <path>", 'append to the notes from a file ("-" for stdin)').option("--acceptance-file <path>", 'read the acceptance criteria from a file ("-" for stdin)').option("--priority <name>", "urgent | high | medium | low | none").option("--due-date <date>", 'YYYY-MM-DD; "" clears').option("--parent <id>").option(
|
|
1228
1260
|
"--assignee <who>",
|
|
1229
1261
|
'me, a user id, an email, or a full name; "" unassigns'
|
|
1262
|
+
).option(
|
|
1263
|
+
"--add-label <name-or-id>",
|
|
1264
|
+
"add a label without replacing existing labels (repeatable)",
|
|
1265
|
+
collect,
|
|
1266
|
+
[]
|
|
1267
|
+
).option(
|
|
1268
|
+
"--remove-label <name-or-id>",
|
|
1269
|
+
"remove an attached label without replacing others (repeatable)",
|
|
1270
|
+
collect,
|
|
1271
|
+
[]
|
|
1230
1272
|
).option("--json", "emit JSON").action(
|
|
1231
1273
|
async (id, opts) => {
|
|
1232
1274
|
await withConfig(opts.json === true, true, (ctx) => {
|
|
@@ -1298,7 +1340,42 @@ program.command("dep").description("blocking dependencies: dep add|rm <issue> <b
|
|
|
1298
1340
|
});
|
|
1299
1341
|
}
|
|
1300
1342
|
);
|
|
1301
|
-
program.command("
|
|
1343
|
+
var relation = program.command("relation").description("typed issue relations; dep remains the blocking-dependency shorthand");
|
|
1344
|
+
relation.command("list").argument("[issue]").option("--json", "emit JSON").action(async (issue, opts) => {
|
|
1345
|
+
await withConfig(opts.json === true, false, (ctx) => {
|
|
1346
|
+
if (issue === void 0) throw new UsageError("usage: harness relation list <issue>");
|
|
1347
|
+
return relationList(ctx, issue);
|
|
1348
|
+
});
|
|
1349
|
+
});
|
|
1350
|
+
relation.command("add").argument("[issue]").argument("[related-issue]").requiredOption("--type <type>", `relation type: ${RELATION_CREATE_TYPES.join(", ")}`).option("--json", "emit JSON").action(
|
|
1351
|
+
async (issue, relatedIssue, opts) => {
|
|
1352
|
+
await withConfig(opts.json === true, true, (ctx) => {
|
|
1353
|
+
if (issue === void 0 || relatedIssue === void 0) {
|
|
1354
|
+
throw new UsageError("usage: harness relation add <issue> <related-issue> --type <type>");
|
|
1355
|
+
}
|
|
1356
|
+
return relationAdd(ctx, issue, relatedIssue, opts.type);
|
|
1357
|
+
});
|
|
1358
|
+
}
|
|
1359
|
+
);
|
|
1360
|
+
relation.command("update").argument("[issue]").argument("[id-or-issue]").requiredOption("--type <type>", `relation type: ${RELATION_UPDATE_TYPES.join(", ")}`).option("--json", "emit JSON").action(
|
|
1361
|
+
async (issue, idOrIssue, opts) => {
|
|
1362
|
+
await withConfig(opts.json === true, true, (ctx) => {
|
|
1363
|
+
if (issue === void 0 || idOrIssue === void 0) {
|
|
1364
|
+
throw new UsageError("usage: harness relation update <issue> <relation-id-or-issue> --type <type>");
|
|
1365
|
+
}
|
|
1366
|
+
return relationUpdate(ctx, issue, idOrIssue, opts.type);
|
|
1367
|
+
});
|
|
1368
|
+
}
|
|
1369
|
+
);
|
|
1370
|
+
relation.command("rm").argument("[issue]").argument("[id-or-issue]").option("--json", "emit JSON").action(async (issue, idOrIssue, opts) => {
|
|
1371
|
+
await withConfig(opts.json === true, true, (ctx) => {
|
|
1372
|
+
if (issue === void 0 || idOrIssue === void 0) {
|
|
1373
|
+
throw new UsageError("usage: harness relation rm <issue> <relation-id-or-issue>");
|
|
1374
|
+
}
|
|
1375
|
+
return relationRm(ctx, issue, idOrIssue);
|
|
1376
|
+
});
|
|
1377
|
+
});
|
|
1378
|
+
var commentCommand = program.command("comment").description("comment on an issue").argument("[id]").argument("[text...]").option("--body-file <path>", 'read the comment body from a file ("-" for stdin)').option("--mention <name>", "notify a person (repeatable)", collect, []).option("--json", "emit JSON").action(
|
|
1302
1379
|
async (id, text, opts) => {
|
|
1303
1380
|
await withConfig(opts.json === true, true, (ctx) => {
|
|
1304
1381
|
if (id === void 0 || opts.bodyFile === void 0 && text.length === 0) {
|
|
@@ -1307,25 +1384,39 @@ program.command("comment").description("comment on an issue").argument("[id]").a
|
|
|
1307
1384
|
);
|
|
1308
1385
|
}
|
|
1309
1386
|
requireIdentifiers2([id]);
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
|
|
1387
|
+
const body = resolvedCommentBody(text, opts.bodyFile);
|
|
1388
|
+
const mentions = opts.mention.filter((name) => name.trim() !== "");
|
|
1389
|
+
return comment(ctx, id, body, mentions);
|
|
1390
|
+
});
|
|
1391
|
+
}
|
|
1392
|
+
);
|
|
1393
|
+
commentCommand.command("edit").description("edit one of your issue comments").argument("[issue]").argument("[comment-id]").argument("[text...]").option("--body-file <path>", 'read the replacement body from a file ("-" for stdin)').option("--json", "emit JSON").action(
|
|
1394
|
+
async (issue, commentId, text, opts) => {
|
|
1395
|
+
const parentOpts = commentCommand.opts();
|
|
1396
|
+
const bodyFile = opts.bodyFile ?? parentOpts.bodyFile;
|
|
1397
|
+
await withConfig(opts.json === true || parentOpts.json === true, true, (ctx) => {
|
|
1398
|
+
if (issue === void 0 || commentId === void 0 || bodyFile === void 0 && text.length === 0) {
|
|
1320
1399
|
throw new UsageError(
|
|
1321
|
-
|
|
1400
|
+
"usage: harness comment edit <issue> <comment-id> <text> | harness comment edit <issue> <comment-id> --body-file <path>"
|
|
1322
1401
|
);
|
|
1323
1402
|
}
|
|
1324
|
-
|
|
1325
|
-
|
|
1403
|
+
requireIdentifiers2([issue]);
|
|
1404
|
+
requireCommentId(commentId);
|
|
1405
|
+
return editComment(ctx, issue, commentId, resolvedCommentBody(text, bodyFile, "edit"));
|
|
1326
1406
|
});
|
|
1327
1407
|
}
|
|
1328
1408
|
);
|
|
1409
|
+
commentCommand.command("delete").alias("rm").description("delete one of your issue comments").argument("[issue]").argument("[comment-id]").option("--json", "emit JSON").action(async (issue, commentId, opts) => {
|
|
1410
|
+
const parentOpts = commentCommand.opts();
|
|
1411
|
+
await withConfig(opts.json === true || parentOpts.json === true, true, (ctx) => {
|
|
1412
|
+
if (issue === void 0 || commentId === void 0) {
|
|
1413
|
+
throw new UsageError("usage: harness comment delete <issue> <comment-id>");
|
|
1414
|
+
}
|
|
1415
|
+
requireIdentifiers2([issue]);
|
|
1416
|
+
requireCommentId(commentId);
|
|
1417
|
+
return deleteComment(ctx, issue, commentId);
|
|
1418
|
+
});
|
|
1419
|
+
});
|
|
1329
1420
|
program.command("attach-image").description("upload an image; prints markdown to reference it").argument("[path]").option("--json", "emit JSON").action(async (path, opts) => {
|
|
1330
1421
|
await withConfig(opts.json === true, true, (ctx) => {
|
|
1331
1422
|
if (path === void 0) {
|
|
@@ -1439,6 +1530,18 @@ link.command("add").argument("[issue]").argument("[url]").option("--title <title
|
|
|
1439
1530
|
});
|
|
1440
1531
|
}
|
|
1441
1532
|
);
|
|
1533
|
+
link.command("update").argument("[issue]").argument("[id-or-url]").option("--url <url>", "replacement URL").option("--title <title>", "replacement title; pass an empty value to clear").option("--json", "emit JSON").action(
|
|
1534
|
+
async (issue, idOrUrl, opts) => {
|
|
1535
|
+
await withConfig(opts.json === true, true, (ctx) => {
|
|
1536
|
+
if (issue === void 0 || idOrUrl === void 0) {
|
|
1537
|
+
throw new UsageError(
|
|
1538
|
+
"usage: harness link update <issue> <link-id-or-url> --url <url> | --title <title>"
|
|
1539
|
+
);
|
|
1540
|
+
}
|
|
1541
|
+
return linkUpdate(ctx, issue, idOrUrl, opts);
|
|
1542
|
+
});
|
|
1543
|
+
}
|
|
1544
|
+
);
|
|
1442
1545
|
link.command("rm").argument("[issue]").argument("[id-or-url]").option("--json", "emit JSON").action(
|
|
1443
1546
|
async (issue, idOrUrl, opts) => {
|
|
1444
1547
|
await withConfig(opts.json === true, true, (ctx) => {
|
|
@@ -1552,7 +1655,7 @@ program.command("upload").description("upload a file; prints markdown to referen
|
|
|
1552
1655
|
});
|
|
1553
1656
|
});
|
|
1554
1657
|
program.command("mcp").description("run the MCP server over stdio (for agents)").action(async () => {
|
|
1555
|
-
const { startMcpServer } = await import("./server-
|
|
1658
|
+
const { startMcpServer } = await import("./server-J6FQFVBJ.js");
|
|
1556
1659
|
await startMcpServer();
|
|
1557
1660
|
});
|
|
1558
1661
|
program.command("login").description("sign in via the browser (infra.one session)").option("--url <url>", "host to sign in to").option("--no-browser", "print the URL and prompt for a pasted token instead").option(
|
|
@@ -3,6 +3,8 @@ import {
|
|
|
3
3
|
CLI_VERSION,
|
|
4
4
|
Client,
|
|
5
5
|
HttpError,
|
|
6
|
+
RELATION_CREATE_TYPES,
|
|
7
|
+
RELATION_UPDATE_TYPES,
|
|
6
8
|
TransportError,
|
|
7
9
|
UsageError,
|
|
8
10
|
archive,
|
|
@@ -11,11 +13,13 @@ import {
|
|
|
11
13
|
comment,
|
|
12
14
|
comments,
|
|
13
15
|
create,
|
|
16
|
+
deleteComment,
|
|
14
17
|
dep,
|
|
15
18
|
docCreate,
|
|
16
19
|
docList,
|
|
17
20
|
docShow,
|
|
18
21
|
docUpdate,
|
|
22
|
+
editComment,
|
|
19
23
|
labelCreate,
|
|
20
24
|
labelDelete,
|
|
21
25
|
labelList,
|
|
@@ -23,6 +27,7 @@ import {
|
|
|
23
27
|
linkAdd,
|
|
24
28
|
linkList,
|
|
25
29
|
linkRm,
|
|
30
|
+
linkUpdate,
|
|
26
31
|
list,
|
|
27
32
|
memberAdd,
|
|
28
33
|
memberList,
|
|
@@ -34,6 +39,10 @@ import {
|
|
|
34
39
|
projectShow,
|
|
35
40
|
projectUpdate,
|
|
36
41
|
ready,
|
|
42
|
+
relationAdd,
|
|
43
|
+
relationList,
|
|
44
|
+
relationRm,
|
|
45
|
+
relationUpdate,
|
|
37
46
|
resolveConfig,
|
|
38
47
|
search,
|
|
39
48
|
show,
|
|
@@ -50,7 +59,7 @@ import {
|
|
|
50
59
|
upload,
|
|
51
60
|
userList,
|
|
52
61
|
workflowStates
|
|
53
|
-
} from "./chunk-
|
|
62
|
+
} from "./chunk-ZHFW6PRC.js";
|
|
54
63
|
|
|
55
64
|
// src/mcp/server.ts
|
|
56
65
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
@@ -98,6 +107,7 @@ var page = {
|
|
|
98
107
|
all: z.boolean().optional().describe("follow every page")
|
|
99
108
|
};
|
|
100
109
|
var identifier = z.string().describe("issue identifier, e.g. IO-123");
|
|
110
|
+
var commentId = z.string().uuid().describe("comment UUID from issue_show or comment_list");
|
|
101
111
|
var dateField = z.string().optional().describe('YYYY-MM-DD; "" clears the date');
|
|
102
112
|
var TOOLS = [
|
|
103
113
|
// ── Issues ──
|
|
@@ -189,13 +199,17 @@ var TOOLS = [
|
|
|
189
199
|
due_date: dateField,
|
|
190
200
|
state: z.string().optional().describe("workflow state by name or type"),
|
|
191
201
|
parent: z.string().optional(),
|
|
192
|
-
assignee: z.string().optional().describe('"" unassigns')
|
|
202
|
+
assignee: z.string().optional().describe('"" unassigns'),
|
|
203
|
+
add_labels: z.array(z.string()).optional().describe("label names or ids to add without replacing existing labels"),
|
|
204
|
+
remove_labels: z.array(z.string()).optional().describe("attached label names or ids to remove without replacing others")
|
|
193
205
|
},
|
|
194
206
|
handler: (ctx, args) => update(ctx, args["id"], {
|
|
195
207
|
...args,
|
|
196
208
|
acceptance: args["acceptance_criteria"],
|
|
197
209
|
appendNotes: args["append_notes"],
|
|
198
|
-
dueDate: args["due_date"]
|
|
210
|
+
dueDate: args["due_date"],
|
|
211
|
+
addLabel: args["add_labels"],
|
|
212
|
+
removeLabel: args["remove_labels"]
|
|
199
213
|
})
|
|
200
214
|
},
|
|
201
215
|
{
|
|
@@ -227,6 +241,27 @@ var TOOLS = [
|
|
|
227
241
|
},
|
|
228
242
|
handler: (ctx, args) => comment(ctx, args["id"], [args["body"]], args["mentions"] ?? [])
|
|
229
243
|
},
|
|
244
|
+
{
|
|
245
|
+
name: "issue_comment_edit",
|
|
246
|
+
description: "Edit one of your issue comments within its edit window.",
|
|
247
|
+
readOnly: false,
|
|
248
|
+
shape: {
|
|
249
|
+
id: identifier,
|
|
250
|
+
comment_id: commentId,
|
|
251
|
+
body: z.string().min(1).refine((body) => body.trim() !== "", "comment body must not be blank")
|
|
252
|
+
},
|
|
253
|
+
handler: (ctx, args) => editComment(ctx, args["id"], args["comment_id"], [args["body"]])
|
|
254
|
+
},
|
|
255
|
+
{
|
|
256
|
+
name: "issue_comment_delete",
|
|
257
|
+
description: "Delete one of your issue comments.",
|
|
258
|
+
readOnly: false,
|
|
259
|
+
shape: {
|
|
260
|
+
id: identifier,
|
|
261
|
+
comment_id: commentId
|
|
262
|
+
},
|
|
263
|
+
handler: (ctx, args) => deleteComment(ctx, args["id"], args["comment_id"])
|
|
264
|
+
},
|
|
230
265
|
{
|
|
231
266
|
name: "issue_archive",
|
|
232
267
|
description: "Archive issues (or restore them with restore=true). Archived issues show up under scope=archived.",
|
|
@@ -241,7 +276,7 @@ var TOOLS = [
|
|
|
241
276
|
shape: { id: identifier },
|
|
242
277
|
handler: (ctx, args) => comments(ctx, args["id"])
|
|
243
278
|
},
|
|
244
|
-
// ── Relations
|
|
279
|
+
// ── Relations ──
|
|
245
280
|
{
|
|
246
281
|
name: "relation_add",
|
|
247
282
|
description: "Add a blocking dependency: blocker blocks issue.",
|
|
@@ -256,6 +291,42 @@ var TOOLS = [
|
|
|
256
291
|
shape: { issue: identifier, blocker: identifier },
|
|
257
292
|
handler: (ctx, args) => dep(ctx, "rm", args["issue"], args["blocker"])
|
|
258
293
|
},
|
|
294
|
+
{
|
|
295
|
+
name: "relation_list",
|
|
296
|
+
description: "List every typed relation on an issue, including stable relation ids.",
|
|
297
|
+
readOnly: true,
|
|
298
|
+
shape: { issue: identifier },
|
|
299
|
+
handler: (ctx, args) => relationList(ctx, args["issue"])
|
|
300
|
+
},
|
|
301
|
+
{
|
|
302
|
+
name: "relation_create",
|
|
303
|
+
description: "Create a typed relation between two issues.",
|
|
304
|
+
readOnly: false,
|
|
305
|
+
shape: {
|
|
306
|
+
issue: identifier,
|
|
307
|
+
related_issue: identifier,
|
|
308
|
+
type: z.enum(RELATION_CREATE_TYPES)
|
|
309
|
+
},
|
|
310
|
+
handler: (ctx, args) => relationAdd(ctx, args["issue"], args["related_issue"], args["type"])
|
|
311
|
+
},
|
|
312
|
+
{
|
|
313
|
+
name: "relation_update",
|
|
314
|
+
description: "Change a relation's direction, resolving it by relation id or unique related issue.",
|
|
315
|
+
readOnly: false,
|
|
316
|
+
shape: {
|
|
317
|
+
issue: identifier,
|
|
318
|
+
id_or_issue: z.string(),
|
|
319
|
+
type: z.enum(RELATION_UPDATE_TYPES)
|
|
320
|
+
},
|
|
321
|
+
handler: (ctx, args) => relationUpdate(ctx, args["issue"], args["id_or_issue"], args["type"])
|
|
322
|
+
},
|
|
323
|
+
{
|
|
324
|
+
name: "relation_delete",
|
|
325
|
+
description: "Remove a typed relation by relation id or unique related issue.",
|
|
326
|
+
readOnly: false,
|
|
327
|
+
shape: { issue: identifier, id_or_issue: z.string() },
|
|
328
|
+
handler: (ctx, args) => relationRm(ctx, args["issue"], args["id_or_issue"])
|
|
329
|
+
},
|
|
259
330
|
// ── Links (URLs on an issue) ──
|
|
260
331
|
{
|
|
261
332
|
name: "link_list",
|
|
@@ -271,6 +342,20 @@ var TOOLS = [
|
|
|
271
342
|
shape: { issue: identifier, url: z.string(), title: z.string().optional() },
|
|
272
343
|
handler: (ctx, args) => linkAdd(ctx, args["issue"], args["url"], args["title"])
|
|
273
344
|
},
|
|
345
|
+
{
|
|
346
|
+
name: "link_update",
|
|
347
|
+
description: "Edit a link by link id or exact URL.",
|
|
348
|
+
readOnly: false,
|
|
349
|
+
shape: {
|
|
350
|
+
issue: identifier,
|
|
351
|
+
id_or_url: z.string(),
|
|
352
|
+
change: z.union([
|
|
353
|
+
z.object({ url: z.string(), title: z.string().optional() }),
|
|
354
|
+
z.object({ title: z.string(), url: z.string().optional() })
|
|
355
|
+
])
|
|
356
|
+
},
|
|
357
|
+
handler: (ctx, args) => linkUpdate(ctx, args["issue"], args["id_or_url"], args["change"])
|
|
358
|
+
},
|
|
274
359
|
{
|
|
275
360
|
name: "link_rm",
|
|
276
361
|
description: "Remove a link from an issue, by link id or exact URL.",
|