@allocator-one/harness-cli 1.6.0 → 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 +24 -1
- package/dist/{chunk-DQDNAG5E.js → chunk-ZHFW6PRC.js} +147 -6
- package/dist/cli.js +104 -16
- package/dist/{server-WBZ5OPHK.js → server-J6FQFVBJ.js} +83 -2
- package/package.json +1 -1
- package/spec/openapi.json +561 -2
package/README.md
CHANGED
|
@@ -53,7 +53,9 @@ the browser login attributes writes to the real user.
|
|
|
53
53
|
|
|
54
54
|
`harness mcp` runs a stdio MCP server exposing every operation as a typed
|
|
55
55
|
tool — issue_list, issue_show, issue_create, issue_update, issue_claim,
|
|
56
|
-
issue_close, issue_comment,
|
|
56
|
+
issue_close, issue_comment, issue_comment_edit, issue_comment_delete,
|
|
57
|
+
relation_list/create/update/delete, relation_add/rm (blocking shorthand),
|
|
58
|
+
link_list/add/update/rm,
|
|
57
59
|
project_list/show/create/update, milestone_list/create,
|
|
58
60
|
document_list/show/create/update, label_list/create, team_list,
|
|
59
61
|
team_create, workflow_states, user_list, stats, upload. Read-only tools carry
|
|
@@ -69,6 +71,27 @@ Example tool call: `issue_list` with `{"ready": true, "team": "ALL"}`
|
|
|
69
71
|
returns the ready queue as JSON; `issue_claim` with `{"id": "IO-123"}`
|
|
70
72
|
claims atomically (a conflict means someone else holds it).
|
|
71
73
|
|
|
74
|
+
Typed issue relations are available without losing the concise blocking
|
|
75
|
+
workflow. `harness dep add|rm` and the `relation_add`/`relation_rm` MCP tools
|
|
76
|
+
remain the blocker-first shorthand; the full surface is:
|
|
77
|
+
|
|
78
|
+
```sh
|
|
79
|
+
harness relation list IO-123
|
|
80
|
+
harness relation add IO-123 IO-456 --type related
|
|
81
|
+
harness relation update IO-123 IO-456 --type blocks
|
|
82
|
+
harness relation rm IO-123 IO-456
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
Relation update and removal accept either the relation id from `relation list`
|
|
86
|
+
or a unique related issue identifier. If the same pair has more than one
|
|
87
|
+
relation, the CLI refuses to guess and requires the relation id. Link updates
|
|
88
|
+
use the same safe lookup rule by id or exact URL; the existing `link rm`
|
|
89
|
+
command keeps its first-match behavior for backward compatibility:
|
|
90
|
+
|
|
91
|
+
```sh
|
|
92
|
+
harness link update IO-123 https://example.com/old --url https://example.com/new --title "Runbook"
|
|
93
|
+
```
|
|
94
|
+
|
|
72
95
|
Issue creation resolves `--label` names within the target team first, then at
|
|
73
96
|
workspace scope. Missing names are created at workspace scope before the issue
|
|
74
97
|
is filed, and the final label ids travel in the single issue-create request, so
|
|
@@ -538,6 +538,10 @@ var STATUS_STATE_TYPES = {
|
|
|
538
538
|
};
|
|
539
539
|
var READY_CLAIM_ATTEMPTS = 10;
|
|
540
540
|
var IDENTIFIER_FORMAT = /^[A-Za-z][A-Za-z0-9]*-\d+$/;
|
|
541
|
+
var UUID_FORMAT = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
542
|
+
function isUuid(value) {
|
|
543
|
+
return UUID_FORMAT.test(value);
|
|
544
|
+
}
|
|
541
545
|
function requireIdentifiers(identifiers) {
|
|
542
546
|
const bad = identifiers.filter((identifier) => !IDENTIFIER_FORMAT.test(identifier));
|
|
543
547
|
if (bad.length > 0) {
|
|
@@ -786,8 +790,9 @@ async function comments(ctx, identifier) {
|
|
|
786
790
|
if (body.data.length === 0) return { json: body.data, human: "No comments.\n" };
|
|
787
791
|
const human = body.data.map((comment2) => {
|
|
788
792
|
const author = clean((comment2["author"] ?? {})["name"] ?? "unknown");
|
|
793
|
+
const id = clean(comment2["id"] ?? "unknown id");
|
|
789
794
|
const text = cleanMultiline(str2(comment2["body"])).split("\n").map((line) => " " + line).join("\n");
|
|
790
|
-
return `[${str2(comment2["inserted_at"])}] ${author}:
|
|
795
|
+
return `[${str2(comment2["inserted_at"])}] ${author} (${id}):
|
|
791
796
|
${text}
|
|
792
797
|
`;
|
|
793
798
|
}).join("\n");
|
|
@@ -815,6 +820,76 @@ async function dep(ctx, verb, issue, blocker) {
|
|
|
815
820
|
}
|
|
816
821
|
throw new UsageError("usage: harness dep add|rm <issue> <blocker> (the blocker blocks the issue)");
|
|
817
822
|
}
|
|
823
|
+
var RELATION_CREATE_TYPES = [
|
|
824
|
+
"blocks",
|
|
825
|
+
"blocked_by",
|
|
826
|
+
"related",
|
|
827
|
+
"duplicate",
|
|
828
|
+
"duplicate_of",
|
|
829
|
+
"duplicated_by"
|
|
830
|
+
];
|
|
831
|
+
var RELATION_UPDATE_TYPES = ["blocks", "blocked_by", "related", "duplicate_of"];
|
|
832
|
+
function requireRelationType(type, allowed, operation) {
|
|
833
|
+
if (!allowed.includes(type)) {
|
|
834
|
+
throw new UsageError(`relation ${operation} type must be one of: ${allowed.join(", ")}`);
|
|
835
|
+
}
|
|
836
|
+
}
|
|
837
|
+
async function findRelation(ctx, issue, idOrIssue) {
|
|
838
|
+
const response = await ctx.client.get(`/api/v1/issues/${issue}/relations`);
|
|
839
|
+
const idMatch = response.data.find((candidate) => candidate["id"] === idOrIssue);
|
|
840
|
+
if (idMatch !== void 0) return idMatch;
|
|
841
|
+
const issueMatches = response.data.filter((candidate) => candidate["other_issue"] === idOrIssue);
|
|
842
|
+
if (issueMatches.length > 1) {
|
|
843
|
+
throw new CommandError(`multiple relations on ${issue} match ${idOrIssue}; use a relation id`);
|
|
844
|
+
}
|
|
845
|
+
if (issueMatches.length === 0) {
|
|
846
|
+
throw new CommandError(`no relation on ${issue} matches ${idOrIssue} (by id or related issue)`);
|
|
847
|
+
}
|
|
848
|
+
return issueMatches[0];
|
|
849
|
+
}
|
|
850
|
+
async function resolveRelationId(ctx, issue, idOrIssue) {
|
|
851
|
+
if (isUuid(idOrIssue)) return idOrIssue.toLowerCase();
|
|
852
|
+
const relation = await findRelation(ctx, issue, idOrIssue);
|
|
853
|
+
return String(relation["id"]);
|
|
854
|
+
}
|
|
855
|
+
async function relationList(ctx, issue) {
|
|
856
|
+
requireIdentifiers([issue]);
|
|
857
|
+
const response = await ctx.client.get(`/api/v1/issues/${issue}/relations`);
|
|
858
|
+
const human = response.data.length === 0 ? "No relations.\n" : table(
|
|
859
|
+
["ID", "DIRECTION", "ISSUE"],
|
|
860
|
+
response.data.map((relation) => [
|
|
861
|
+
String(relation["id"] ?? ""),
|
|
862
|
+
String(relation["direction"] ?? ""),
|
|
863
|
+
String(relation["other_issue"] ?? "")
|
|
864
|
+
])
|
|
865
|
+
);
|
|
866
|
+
return { json: response.data, human };
|
|
867
|
+
}
|
|
868
|
+
async function relationAdd(ctx, issue, relatedIssue, type) {
|
|
869
|
+
requireIdentifiers([issue, relatedIssue]);
|
|
870
|
+
requireRelationType(type, RELATION_CREATE_TYPES, "add");
|
|
871
|
+
const response = await ctx.client.post(`/api/v1/issues/${issue}/relations`, {
|
|
872
|
+
type,
|
|
873
|
+
related_issue: relatedIssue
|
|
874
|
+
});
|
|
875
|
+
return { json: response.data, human: `Related ${issue} to ${relatedIssue} as ${type}
|
|
876
|
+
` };
|
|
877
|
+
}
|
|
878
|
+
async function relationUpdate(ctx, issue, idOrIssue, type) {
|
|
879
|
+
requireIdentifiers([issue]);
|
|
880
|
+
requireRelationType(type, RELATION_UPDATE_TYPES, "update");
|
|
881
|
+
const id = await resolveRelationId(ctx, issue, idOrIssue);
|
|
882
|
+
const response = await ctx.client.patch(`/api/v1/issues/${issue}/relations/${id}`, { type });
|
|
883
|
+
return { json: response.data, human: `Updated relation ${id} on ${issue} to ${type}
|
|
884
|
+
` };
|
|
885
|
+
}
|
|
886
|
+
async function relationRm(ctx, issue, idOrIssue) {
|
|
887
|
+
requireIdentifiers([issue]);
|
|
888
|
+
const id = await resolveRelationId(ctx, issue, idOrIssue);
|
|
889
|
+
await ctx.client.delete(`/api/v1/issues/${issue}/relations/${id}`);
|
|
890
|
+
return { json: { deleted: id }, human: `Removed relation ${id} from ${issue}
|
|
891
|
+
` };
|
|
892
|
+
}
|
|
818
893
|
async function comment(ctx, identifier, textWords, mentions) {
|
|
819
894
|
requireIdentifiers([identifier]);
|
|
820
895
|
if (textWords.length === 0) {
|
|
@@ -826,6 +901,40 @@ async function comment(ctx, identifier, textWords, mentions) {
|
|
|
826
901
|
return { json: response.data, human: `Commented on ${identifier}
|
|
827
902
|
` };
|
|
828
903
|
}
|
|
904
|
+
async function editComment(ctx, identifier, commentId, textWords) {
|
|
905
|
+
requireIdentifiers([identifier]);
|
|
906
|
+
requireCommentId(commentId);
|
|
907
|
+
const body = textWords.join(" ");
|
|
908
|
+
if (body.trim() === "") {
|
|
909
|
+
throw new UsageError("the comment text is empty \u2014 nothing to edit");
|
|
910
|
+
}
|
|
911
|
+
const response = await ctx.client.patch(
|
|
912
|
+
`/api/v1/issues/${identifier}/comments/${commentId}`,
|
|
913
|
+
{ body }
|
|
914
|
+
);
|
|
915
|
+
return {
|
|
916
|
+
json: response.data,
|
|
917
|
+
human: `Edited comment ${commentId} on ${identifier}
|
|
918
|
+
`
|
|
919
|
+
};
|
|
920
|
+
}
|
|
921
|
+
async function deleteComment(ctx, identifier, commentId) {
|
|
922
|
+
requireIdentifiers([identifier]);
|
|
923
|
+
requireCommentId(commentId);
|
|
924
|
+
const response = await ctx.client.delete(
|
|
925
|
+
`/api/v1/issues/${identifier}/comments/${commentId}`
|
|
926
|
+
);
|
|
927
|
+
return {
|
|
928
|
+
json: response.data,
|
|
929
|
+
human: `Deleted comment ${commentId} from ${identifier}
|
|
930
|
+
`
|
|
931
|
+
};
|
|
932
|
+
}
|
|
933
|
+
function requireCommentId(commentId) {
|
|
934
|
+
if (!UUID_FORMAT.test(commentId)) {
|
|
935
|
+
throw new UsageError(`not a comment id: ${commentId} (expected a UUID)`);
|
|
936
|
+
}
|
|
937
|
+
}
|
|
829
938
|
var MAX_ATTACHMENT_BYTES2 = 25e6;
|
|
830
939
|
async function attachImage(ctx, path2) {
|
|
831
940
|
attachableFile(path2);
|
|
@@ -910,7 +1019,6 @@ function validateType(type) {
|
|
|
910
1019
|
throw new UsageError(`not an issue type: ${type} (expected ${ISSUE_TYPES.join(", ")})`);
|
|
911
1020
|
}
|
|
912
1021
|
}
|
|
913
|
-
var UUID_FORMAT = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
914
1022
|
function requestedLabelNames(names) {
|
|
915
1023
|
const unique = /* @__PURE__ */ new Map();
|
|
916
1024
|
for (const name of requestedLabelInputs(names)) {
|
|
@@ -1349,13 +1457,36 @@ async function linkAdd(ctx, issue, url, title) {
|
|
|
1349
1457
|
return { json: response.data, human: `Linked ${clean(url)} to ${issue}
|
|
1350
1458
|
` };
|
|
1351
1459
|
}
|
|
1352
|
-
async function
|
|
1353
|
-
requireIdentifiers([issue]);
|
|
1460
|
+
async function findLink(ctx, issue, idOrUrl, allowDuplicateUrl = false) {
|
|
1354
1461
|
const body = await ctx.client.get(`/api/v1/issues/${issue}/links`);
|
|
1355
|
-
const
|
|
1356
|
-
if (
|
|
1462
|
+
const idMatch = body.data.find((candidate) => candidate["id"] === idOrUrl);
|
|
1463
|
+
if (idMatch !== void 0) return idMatch;
|
|
1464
|
+
const urlMatches = body.data.filter((candidate) => candidate["url"] === idOrUrl);
|
|
1465
|
+
if (urlMatches.length > 1 && !allowDuplicateUrl) {
|
|
1466
|
+
throw new CommandError(`multiple links on ${issue} match ${idOrUrl}; use a link id`);
|
|
1467
|
+
}
|
|
1468
|
+
if (urlMatches.length === 0) {
|
|
1357
1469
|
throw new CommandError(`no link on ${issue} matches ${idOrUrl} (by id or exact URL)`);
|
|
1358
1470
|
}
|
|
1471
|
+
return urlMatches[0];
|
|
1472
|
+
}
|
|
1473
|
+
async function linkUpdate(ctx, issue, idOrUrl, opts) {
|
|
1474
|
+
requireIdentifiers([issue]);
|
|
1475
|
+
let patch = {};
|
|
1476
|
+
patch = putOpt2(patch, "url", opts.url);
|
|
1477
|
+
patch = putOpt2(patch, "title", opts.title);
|
|
1478
|
+
if (Object.keys(patch).length === 0) {
|
|
1479
|
+
throw new UsageError("usage: harness link update <issue> <link-id-or-url> --url <url> | --title <title>");
|
|
1480
|
+
}
|
|
1481
|
+
const id = isUuid(idOrUrl) ? idOrUrl.toLowerCase() : str3((await findLink(ctx, issue, idOrUrl))["id"]);
|
|
1482
|
+
if (!isUuid(idOrUrl)) patch["expected_url"] = idOrUrl;
|
|
1483
|
+
const response = await ctx.client.patch(`/api/v1/issues/${issue}/links/${id}`, patch);
|
|
1484
|
+
return { json: response.data, human: `Updated link ${clean(response.data["url"])} on ${issue}
|
|
1485
|
+
` };
|
|
1486
|
+
}
|
|
1487
|
+
async function linkRm(ctx, issue, idOrUrl) {
|
|
1488
|
+
requireIdentifiers([issue]);
|
|
1489
|
+
const link = await findLink(ctx, issue, idOrUrl, true);
|
|
1359
1490
|
await ctx.client.delete(`/api/v1/issues/${issue}/links/${str3(link["id"])}`);
|
|
1360
1491
|
return { json: { deleted: link["id"] }, human: `Removed ${clean(link["url"])} from ${issue}
|
|
1361
1492
|
` };
|
|
@@ -1827,7 +1958,16 @@ export {
|
|
|
1827
1958
|
archive,
|
|
1828
1959
|
comments,
|
|
1829
1960
|
dep,
|
|
1961
|
+
RELATION_CREATE_TYPES,
|
|
1962
|
+
RELATION_UPDATE_TYPES,
|
|
1963
|
+
relationList,
|
|
1964
|
+
relationAdd,
|
|
1965
|
+
relationUpdate,
|
|
1966
|
+
relationRm,
|
|
1830
1967
|
comment,
|
|
1968
|
+
editComment,
|
|
1969
|
+
deleteComment,
|
|
1970
|
+
requireCommentId,
|
|
1831
1971
|
attachImage,
|
|
1832
1972
|
projectList,
|
|
1833
1973
|
projectShow,
|
|
@@ -1841,6 +1981,7 @@ export {
|
|
|
1841
1981
|
docUpdate,
|
|
1842
1982
|
linkList,
|
|
1843
1983
|
linkAdd,
|
|
1984
|
+
linkUpdate,
|
|
1844
1985
|
linkRm,
|
|
1845
1986
|
labelList,
|
|
1846
1987
|
labelCreate,
|
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"];
|
|
@@ -1313,7 +1340,42 @@ program.command("dep").description("blocking dependencies: dep add|rm <issue> <b
|
|
|
1313
1340
|
});
|
|
1314
1341
|
}
|
|
1315
1342
|
);
|
|
1316
|
-
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(
|
|
1317
1379
|
async (id, text, opts) => {
|
|
1318
1380
|
await withConfig(opts.json === true, true, (ctx) => {
|
|
1319
1381
|
if (id === void 0 || opts.bodyFile === void 0 && text.length === 0) {
|
|
@@ -1322,25 +1384,39 @@ program.command("comment").description("comment on an issue").argument("[id]").a
|
|
|
1322
1384
|
);
|
|
1323
1385
|
}
|
|
1324
1386
|
requireIdentifiers2([id]);
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
|
|
1329
|
-
|
|
1330
|
-
|
|
1331
|
-
|
|
1332
|
-
|
|
1333
|
-
|
|
1334
|
-
|
|
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) {
|
|
1335
1399
|
throw new UsageError(
|
|
1336
|
-
|
|
1400
|
+
"usage: harness comment edit <issue> <comment-id> <text> | harness comment edit <issue> <comment-id> --body-file <path>"
|
|
1337
1401
|
);
|
|
1338
1402
|
}
|
|
1339
|
-
|
|
1340
|
-
|
|
1403
|
+
requireIdentifiers2([issue]);
|
|
1404
|
+
requireCommentId(commentId);
|
|
1405
|
+
return editComment(ctx, issue, commentId, resolvedCommentBody(text, bodyFile, "edit"));
|
|
1341
1406
|
});
|
|
1342
1407
|
}
|
|
1343
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
|
+
});
|
|
1344
1420
|
program.command("attach-image").description("upload an image; prints markdown to reference it").argument("[path]").option("--json", "emit JSON").action(async (path, opts) => {
|
|
1345
1421
|
await withConfig(opts.json === true, true, (ctx) => {
|
|
1346
1422
|
if (path === void 0) {
|
|
@@ -1454,6 +1530,18 @@ link.command("add").argument("[issue]").argument("[url]").option("--title <title
|
|
|
1454
1530
|
});
|
|
1455
1531
|
}
|
|
1456
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
|
+
);
|
|
1457
1545
|
link.command("rm").argument("[issue]").argument("[id-or-url]").option("--json", "emit JSON").action(
|
|
1458
1546
|
async (issue, idOrUrl, opts) => {
|
|
1459
1547
|
await withConfig(opts.json === true, true, (ctx) => {
|
|
@@ -1567,7 +1655,7 @@ program.command("upload").description("upload a file; prints markdown to referen
|
|
|
1567
1655
|
});
|
|
1568
1656
|
});
|
|
1569
1657
|
program.command("mcp").description("run the MCP server over stdio (for agents)").action(async () => {
|
|
1570
|
-
const { startMcpServer } = await import("./server-
|
|
1658
|
+
const { startMcpServer } = await import("./server-J6FQFVBJ.js");
|
|
1571
1659
|
await startMcpServer();
|
|
1572
1660
|
});
|
|
1573
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 ──
|
|
@@ -231,6 +241,27 @@ var TOOLS = [
|
|
|
231
241
|
},
|
|
232
242
|
handler: (ctx, args) => comment(ctx, args["id"], [args["body"]], args["mentions"] ?? [])
|
|
233
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
|
+
},
|
|
234
265
|
{
|
|
235
266
|
name: "issue_archive",
|
|
236
267
|
description: "Archive issues (or restore them with restore=true). Archived issues show up under scope=archived.",
|
|
@@ -245,7 +276,7 @@ var TOOLS = [
|
|
|
245
276
|
shape: { id: identifier },
|
|
246
277
|
handler: (ctx, args) => comments(ctx, args["id"])
|
|
247
278
|
},
|
|
248
|
-
// ── Relations
|
|
279
|
+
// ── Relations ──
|
|
249
280
|
{
|
|
250
281
|
name: "relation_add",
|
|
251
282
|
description: "Add a blocking dependency: blocker blocks issue.",
|
|
@@ -260,6 +291,42 @@ var TOOLS = [
|
|
|
260
291
|
shape: { issue: identifier, blocker: identifier },
|
|
261
292
|
handler: (ctx, args) => dep(ctx, "rm", args["issue"], args["blocker"])
|
|
262
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
|
+
},
|
|
263
330
|
// ── Links (URLs on an issue) ──
|
|
264
331
|
{
|
|
265
332
|
name: "link_list",
|
|
@@ -275,6 +342,20 @@ var TOOLS = [
|
|
|
275
342
|
shape: { issue: identifier, url: z.string(), title: z.string().optional() },
|
|
276
343
|
handler: (ctx, args) => linkAdd(ctx, args["issue"], args["url"], args["title"])
|
|
277
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
|
+
},
|
|
278
359
|
{
|
|
279
360
|
name: "link_rm",
|
|
280
361
|
description: "Remove a link from an issue, by link id or exact URL.",
|
package/package.json
CHANGED
package/spec/openapi.json
CHANGED
|
@@ -288,6 +288,7 @@
|
|
|
288
288
|
"type": "string"
|
|
289
289
|
},
|
|
290
290
|
"name": {
|
|
291
|
+
"description": "Unique within its workspace or team after trimming Unicode boundary whitespace and Unicode-aware lowercasing. Interior whitespace and Unicode normalization forms remain distinct.",
|
|
291
292
|
"maxLength": 255,
|
|
292
293
|
"type": "string"
|
|
293
294
|
},
|
|
@@ -382,7 +383,9 @@
|
|
|
382
383
|
"schema": {
|
|
383
384
|
"properties": {
|
|
384
385
|
"label_ids": {
|
|
386
|
+
"description": "The complete replacement set. Every label must belong to this workspace and be workspace-scoped or owned by the issue's team; the update is rejected atomically otherwise.",
|
|
385
387
|
"items": {
|
|
388
|
+
"format": "uuid",
|
|
386
389
|
"type": "string"
|
|
387
390
|
},
|
|
388
391
|
"type": "array"
|
|
@@ -1361,7 +1364,7 @@
|
|
|
1361
1364
|
"get": {
|
|
1362
1365
|
"callbacks": {},
|
|
1363
1366
|
"deprecated": false,
|
|
1364
|
-
"description": "Requires the `harness:read` scope AND Harness administrator standing in the organization the credential is bound to — this is a settings write, gated exactly as the settings UI is. Everyone who can reach this organization's tracker: its harness grant holders plus its own admins, who get staff standing without a grant. Each entry carries both `role` (the grant rank, null when none) and `standing` — they differ, and the difference is the point: an org admin holds no rank and is still staff. Distinct from GET /users, which is the deployment-wide assignee picker and never lists a collaborator.",
|
|
1367
|
+
"description": "Requires the `harness:read` scope AND Harness administrator standing in the organization the credential is bound to — this is a settings write, gated exactly as the settings UI is. Everyone who can reach this organization's tracker: its harness grant holders plus its own admins, who get staff standing without a grant. Each entry carries both `role` (the grant rank, null when none) and `standing` — they differ, and the difference is the point: an org admin holds no rank and is still staff. Pending invitations appear with `standing: \"invited\"` and a null user id. Distinct from GET /users, which is the deployment-wide assignee picker and never lists a collaborator.",
|
|
1365
1368
|
"parameters": [],
|
|
1366
1369
|
"responses": {
|
|
1367
1370
|
"200": {
|
|
@@ -1389,7 +1392,7 @@
|
|
|
1389
1392
|
"post": {
|
|
1390
1393
|
"callbacks": {},
|
|
1391
1394
|
"deprecated": false,
|
|
1392
|
-
"description": "Requires the `harness:write` scope AND Harness administrator standing in the organization the credential is bound to — this is a settings write, gated exactly as the settings UI is. Mints the organization membership when the person has none, so joining the tracker does not detour through the organization's own people page.
|
|
1395
|
+
"description": "Requires the `harness:write` scope AND Harness administrator standing in the organization the credential is bound to — this is a settings write, gated exactly as the settings UI is. Mints the organization membership when the person has none, so joining the tracker does not detour through the organization's own people page. An address with no account is INVITED instead of refused (IO-10955): the response is the same member shape with `standing: \"invited\"`, and the rank applies when the invitation is accepted. Email matching is case-insensitive.",
|
|
1393
1396
|
"parameters": [],
|
|
1394
1397
|
"requestBody": {
|
|
1395
1398
|
"content": {
|
|
@@ -2158,6 +2161,7 @@
|
|
|
2158
2161
|
"type": "string"
|
|
2159
2162
|
},
|
|
2160
2163
|
"name": {
|
|
2164
|
+
"description": "Unique within its workspace or team after trimming Unicode boundary whitespace and Unicode-aware lowercasing. Interior whitespace and Unicode normalization forms remain distinct.",
|
|
2161
2165
|
"maxLength": 255,
|
|
2162
2166
|
"type": "string"
|
|
2163
2167
|
}
|
|
@@ -2281,6 +2285,302 @@
|
|
|
2281
2285
|
"tags": []
|
|
2282
2286
|
}
|
|
2283
2287
|
},
|
|
2288
|
+
"/api/v1/issues/{identifier}/comments/{comment_id}": {
|
|
2289
|
+
"delete": {
|
|
2290
|
+
"callbacks": {},
|
|
2291
|
+
"deprecated": false,
|
|
2292
|
+
"description": "Requires the `harness:write` scope. Only the underlying authenticated author may soft-delete a comment, and must also retain issue-comment capability, matching the UI. Missing, deleted, cross-issue, cross-organization, and other authors' comments all answer 404.",
|
|
2293
|
+
"parameters": [
|
|
2294
|
+
{
|
|
2295
|
+
"description": "Issue identifier, e.g. ALL-4901 or CLI-12 (issues minted before team-prefixed numbering carry IO-)",
|
|
2296
|
+
"in": "path",
|
|
2297
|
+
"name": "identifier",
|
|
2298
|
+
"required": true,
|
|
2299
|
+
"schema": {
|
|
2300
|
+
"type": "string"
|
|
2301
|
+
}
|
|
2302
|
+
},
|
|
2303
|
+
{
|
|
2304
|
+
"description": "Comment UUID",
|
|
2305
|
+
"in": "path",
|
|
2306
|
+
"name": "comment_id",
|
|
2307
|
+
"required": true,
|
|
2308
|
+
"schema": {
|
|
2309
|
+
"format": "uuid",
|
|
2310
|
+
"type": "string"
|
|
2311
|
+
}
|
|
2312
|
+
}
|
|
2313
|
+
],
|
|
2314
|
+
"responses": {
|
|
2315
|
+
"200": {
|
|
2316
|
+
"content": {
|
|
2317
|
+
"application/json": {
|
|
2318
|
+
"schema": {
|
|
2319
|
+
"properties": {
|
|
2320
|
+
"data": {
|
|
2321
|
+
"properties": {
|
|
2322
|
+
"deleted": {
|
|
2323
|
+
"type": "boolean"
|
|
2324
|
+
}
|
|
2325
|
+
},
|
|
2326
|
+
"required": [
|
|
2327
|
+
"deleted"
|
|
2328
|
+
],
|
|
2329
|
+
"type": "object"
|
|
2330
|
+
}
|
|
2331
|
+
},
|
|
2332
|
+
"required": [
|
|
2333
|
+
"data"
|
|
2334
|
+
],
|
|
2335
|
+
"type": "object"
|
|
2336
|
+
}
|
|
2337
|
+
}
|
|
2338
|
+
},
|
|
2339
|
+
"description": "Success"
|
|
2340
|
+
},
|
|
2341
|
+
"400": {
|
|
2342
|
+
"description": "Bad request"
|
|
2343
|
+
},
|
|
2344
|
+
"401": {
|
|
2345
|
+
"description": "Missing or invalid token"
|
|
2346
|
+
},
|
|
2347
|
+
"403": {
|
|
2348
|
+
"description": "Token not permitted (org-scoped, non-admin, or missing scope)"
|
|
2349
|
+
},
|
|
2350
|
+
"404": {
|
|
2351
|
+
"description": "Not found"
|
|
2352
|
+
},
|
|
2353
|
+
"422": {
|
|
2354
|
+
"description": "Validation error"
|
|
2355
|
+
}
|
|
2356
|
+
},
|
|
2357
|
+
"summary": "Delete an issue comment",
|
|
2358
|
+
"tags": []
|
|
2359
|
+
},
|
|
2360
|
+
"patch": {
|
|
2361
|
+
"callbacks": {},
|
|
2362
|
+
"deprecated": false,
|
|
2363
|
+
"description": "Requires the `harness:write` scope. Only the underlying authenticated author may edit a comment, within 15 minutes of creation. The author must also retain issue-comment capability, matching the UI. The body changes; attribution, metadata, reply target, mentions, and creation time do not. `actor` is accepted for CLI compatibility but ignored for ownership and attribution.",
|
|
2364
|
+
"parameters": [
|
|
2365
|
+
{
|
|
2366
|
+
"description": "Issue identifier, e.g. ALL-4901 or CLI-12 (issues minted before team-prefixed numbering carry IO-)",
|
|
2367
|
+
"in": "path",
|
|
2368
|
+
"name": "identifier",
|
|
2369
|
+
"required": true,
|
|
2370
|
+
"schema": {
|
|
2371
|
+
"type": "string"
|
|
2372
|
+
}
|
|
2373
|
+
},
|
|
2374
|
+
{
|
|
2375
|
+
"description": "Comment UUID",
|
|
2376
|
+
"in": "path",
|
|
2377
|
+
"name": "comment_id",
|
|
2378
|
+
"required": true,
|
|
2379
|
+
"schema": {
|
|
2380
|
+
"format": "uuid",
|
|
2381
|
+
"type": "string"
|
|
2382
|
+
}
|
|
2383
|
+
}
|
|
2384
|
+
],
|
|
2385
|
+
"requestBody": {
|
|
2386
|
+
"content": {
|
|
2387
|
+
"application/json": {
|
|
2388
|
+
"schema": {
|
|
2389
|
+
"anyOf": [
|
|
2390
|
+
{
|
|
2391
|
+
"properties": {
|
|
2392
|
+
"actor": {
|
|
2393
|
+
"description": "Accepted for CLI compatibility; ignored because editing never changes attribution",
|
|
2394
|
+
"type": "string"
|
|
2395
|
+
},
|
|
2396
|
+
"body": {
|
|
2397
|
+
"description": "Replacement comment body; takes precedence when content is also present",
|
|
2398
|
+
"minLength": 1,
|
|
2399
|
+
"type": "string"
|
|
2400
|
+
},
|
|
2401
|
+
"content": {
|
|
2402
|
+
"description": "Alias for body",
|
|
2403
|
+
"minLength": 1,
|
|
2404
|
+
"type": "string"
|
|
2405
|
+
}
|
|
2406
|
+
},
|
|
2407
|
+
"required": [
|
|
2408
|
+
"body"
|
|
2409
|
+
],
|
|
2410
|
+
"type": "object"
|
|
2411
|
+
},
|
|
2412
|
+
{
|
|
2413
|
+
"properties": {
|
|
2414
|
+
"actor": {
|
|
2415
|
+
"description": "Accepted for CLI compatibility; ignored because editing never changes attribution",
|
|
2416
|
+
"type": "string"
|
|
2417
|
+
},
|
|
2418
|
+
"body": {
|
|
2419
|
+
"description": "Replacement comment body; takes precedence when content is also present",
|
|
2420
|
+
"minLength": 1,
|
|
2421
|
+
"type": "string"
|
|
2422
|
+
},
|
|
2423
|
+
"content": {
|
|
2424
|
+
"description": "Alias for body",
|
|
2425
|
+
"minLength": 1,
|
|
2426
|
+
"type": "string"
|
|
2427
|
+
}
|
|
2428
|
+
},
|
|
2429
|
+
"required": [
|
|
2430
|
+
"content"
|
|
2431
|
+
],
|
|
2432
|
+
"type": "object"
|
|
2433
|
+
}
|
|
2434
|
+
]
|
|
2435
|
+
}
|
|
2436
|
+
}
|
|
2437
|
+
},
|
|
2438
|
+
"required": true
|
|
2439
|
+
},
|
|
2440
|
+
"responses": {
|
|
2441
|
+
"200": {
|
|
2442
|
+
"content": {
|
|
2443
|
+
"application/json": {
|
|
2444
|
+
"schema": {
|
|
2445
|
+
"properties": {
|
|
2446
|
+
"data": {
|
|
2447
|
+
"properties": {
|
|
2448
|
+
"author": {
|
|
2449
|
+
"nullable": true,
|
|
2450
|
+
"properties": {
|
|
2451
|
+
"email": {
|
|
2452
|
+
"nullable": true,
|
|
2453
|
+
"type": "string"
|
|
2454
|
+
},
|
|
2455
|
+
"id": {
|
|
2456
|
+
"format": "uuid",
|
|
2457
|
+
"nullable": true,
|
|
2458
|
+
"type": "string"
|
|
2459
|
+
},
|
|
2460
|
+
"name": {
|
|
2461
|
+
"nullable": true,
|
|
2462
|
+
"type": "string"
|
|
2463
|
+
}
|
|
2464
|
+
},
|
|
2465
|
+
"required": [
|
|
2466
|
+
"id",
|
|
2467
|
+
"name",
|
|
2468
|
+
"email"
|
|
2469
|
+
],
|
|
2470
|
+
"type": "object"
|
|
2471
|
+
},
|
|
2472
|
+
"body": {
|
|
2473
|
+
"type": "string"
|
|
2474
|
+
},
|
|
2475
|
+
"deleted": {
|
|
2476
|
+
"type": "boolean"
|
|
2477
|
+
},
|
|
2478
|
+
"id": {
|
|
2479
|
+
"format": "uuid",
|
|
2480
|
+
"type": "string"
|
|
2481
|
+
},
|
|
2482
|
+
"inserted_at": {
|
|
2483
|
+
"format": "date-time",
|
|
2484
|
+
"type": "string"
|
|
2485
|
+
},
|
|
2486
|
+
"mentions": {
|
|
2487
|
+
"items": {
|
|
2488
|
+
"properties": {
|
|
2489
|
+
"email": {
|
|
2490
|
+
"nullable": true,
|
|
2491
|
+
"type": "string"
|
|
2492
|
+
},
|
|
2493
|
+
"name": {
|
|
2494
|
+
"nullable": true,
|
|
2495
|
+
"type": "string"
|
|
2496
|
+
},
|
|
2497
|
+
"user_id": {
|
|
2498
|
+
"format": "uuid",
|
|
2499
|
+
"nullable": true,
|
|
2500
|
+
"type": "string"
|
|
2501
|
+
}
|
|
2502
|
+
},
|
|
2503
|
+
"required": [
|
|
2504
|
+
"user_id",
|
|
2505
|
+
"name",
|
|
2506
|
+
"email"
|
|
2507
|
+
],
|
|
2508
|
+
"type": "object"
|
|
2509
|
+
},
|
|
2510
|
+
"type": "array"
|
|
2511
|
+
},
|
|
2512
|
+
"reactions": {
|
|
2513
|
+
"items": {
|
|
2514
|
+
"properties": {
|
|
2515
|
+
"count": {
|
|
2516
|
+
"type": "integer"
|
|
2517
|
+
},
|
|
2518
|
+
"emoji": {
|
|
2519
|
+
"type": "string"
|
|
2520
|
+
},
|
|
2521
|
+
"reacted_by_current_actor": {
|
|
2522
|
+
"type": "boolean"
|
|
2523
|
+
}
|
|
2524
|
+
},
|
|
2525
|
+
"required": [
|
|
2526
|
+
"emoji",
|
|
2527
|
+
"count",
|
|
2528
|
+
"reacted_by_current_actor"
|
|
2529
|
+
],
|
|
2530
|
+
"type": "object"
|
|
2531
|
+
},
|
|
2532
|
+
"type": "array"
|
|
2533
|
+
},
|
|
2534
|
+
"updated_at": {
|
|
2535
|
+
"format": "date-time",
|
|
2536
|
+
"type": "string"
|
|
2537
|
+
}
|
|
2538
|
+
},
|
|
2539
|
+
"required": [
|
|
2540
|
+
"id",
|
|
2541
|
+
"body",
|
|
2542
|
+
"deleted",
|
|
2543
|
+
"author",
|
|
2544
|
+
"mentions",
|
|
2545
|
+
"reactions",
|
|
2546
|
+
"inserted_at",
|
|
2547
|
+
"updated_at"
|
|
2548
|
+
],
|
|
2549
|
+
"type": "object"
|
|
2550
|
+
}
|
|
2551
|
+
},
|
|
2552
|
+
"required": [
|
|
2553
|
+
"data"
|
|
2554
|
+
],
|
|
2555
|
+
"type": "object"
|
|
2556
|
+
}
|
|
2557
|
+
}
|
|
2558
|
+
},
|
|
2559
|
+
"description": "Success"
|
|
2560
|
+
},
|
|
2561
|
+
"400": {
|
|
2562
|
+
"description": "Bad request"
|
|
2563
|
+
},
|
|
2564
|
+
"401": {
|
|
2565
|
+
"description": "Missing or invalid token"
|
|
2566
|
+
},
|
|
2567
|
+
"403": {
|
|
2568
|
+
"description": "Token not permitted (org-scoped, non-admin, or missing scope)"
|
|
2569
|
+
},
|
|
2570
|
+
"404": {
|
|
2571
|
+
"description": "Not found"
|
|
2572
|
+
},
|
|
2573
|
+
"409": {
|
|
2574
|
+
"description": "The 15-minute edit window has closed"
|
|
2575
|
+
},
|
|
2576
|
+
"422": {
|
|
2577
|
+
"description": "Validation error"
|
|
2578
|
+
}
|
|
2579
|
+
},
|
|
2580
|
+
"summary": "Edit an issue comment",
|
|
2581
|
+
"tags": []
|
|
2582
|
+
}
|
|
2583
|
+
},
|
|
2284
2584
|
"/api/v1/model-keys": {
|
|
2285
2585
|
"get": {
|
|
2286
2586
|
"callbacks": {},
|
|
@@ -2510,6 +2810,144 @@
|
|
|
2510
2810
|
},
|
|
2511
2811
|
"summary": "Delete a relation",
|
|
2512
2812
|
"tags": []
|
|
2813
|
+
},
|
|
2814
|
+
"patch": {
|
|
2815
|
+
"callbacks": {},
|
|
2816
|
+
"deprecated": false,
|
|
2817
|
+
"description": "Requires the `harness:write` scope.",
|
|
2818
|
+
"parameters": [
|
|
2819
|
+
{
|
|
2820
|
+
"description": "Issue identifier, e.g. ALL-4901 or CLI-12 (issues minted before team-prefixed numbering carry IO-)",
|
|
2821
|
+
"in": "path",
|
|
2822
|
+
"name": "identifier",
|
|
2823
|
+
"required": true,
|
|
2824
|
+
"schema": {
|
|
2825
|
+
"type": "string"
|
|
2826
|
+
}
|
|
2827
|
+
},
|
|
2828
|
+
{
|
|
2829
|
+
"description": "Resource id",
|
|
2830
|
+
"in": "path",
|
|
2831
|
+
"name": "id",
|
|
2832
|
+
"required": true,
|
|
2833
|
+
"schema": {
|
|
2834
|
+
"type": "string"
|
|
2835
|
+
}
|
|
2836
|
+
}
|
|
2837
|
+
],
|
|
2838
|
+
"requestBody": {
|
|
2839
|
+
"content": {
|
|
2840
|
+
"application/json": {
|
|
2841
|
+
"schema": {
|
|
2842
|
+
"properties": {
|
|
2843
|
+
"type": {
|
|
2844
|
+
"description": "Direction from the issue in the request path",
|
|
2845
|
+
"enum": [
|
|
2846
|
+
"blocks",
|
|
2847
|
+
"blocked_by",
|
|
2848
|
+
"related",
|
|
2849
|
+
"duplicate_of"
|
|
2850
|
+
],
|
|
2851
|
+
"type": "string"
|
|
2852
|
+
}
|
|
2853
|
+
},
|
|
2854
|
+
"required": [
|
|
2855
|
+
"type"
|
|
2856
|
+
],
|
|
2857
|
+
"type": "object"
|
|
2858
|
+
}
|
|
2859
|
+
}
|
|
2860
|
+
},
|
|
2861
|
+
"required": true
|
|
2862
|
+
},
|
|
2863
|
+
"responses": {
|
|
2864
|
+
"200": {
|
|
2865
|
+
"content": {
|
|
2866
|
+
"application/json": {
|
|
2867
|
+
"schema": {
|
|
2868
|
+
"properties": {
|
|
2869
|
+
"data": {
|
|
2870
|
+
"properties": {
|
|
2871
|
+
"direction": {
|
|
2872
|
+
"enum": [
|
|
2873
|
+
"blocks",
|
|
2874
|
+
"blocked_by",
|
|
2875
|
+
"related",
|
|
2876
|
+
"duplicate_of",
|
|
2877
|
+
"duplicated_by"
|
|
2878
|
+
],
|
|
2879
|
+
"type": "string"
|
|
2880
|
+
},
|
|
2881
|
+
"id": {
|
|
2882
|
+
"format": "uuid",
|
|
2883
|
+
"type": "string"
|
|
2884
|
+
},
|
|
2885
|
+
"inserted_at": {
|
|
2886
|
+
"format": "date-time",
|
|
2887
|
+
"type": "string"
|
|
2888
|
+
},
|
|
2889
|
+
"issue": {
|
|
2890
|
+
"type": "string"
|
|
2891
|
+
},
|
|
2892
|
+
"other_issue": {
|
|
2893
|
+
"type": "string"
|
|
2894
|
+
},
|
|
2895
|
+
"related_issue": {
|
|
2896
|
+
"type": "string"
|
|
2897
|
+
},
|
|
2898
|
+
"type": {
|
|
2899
|
+
"enum": [
|
|
2900
|
+
"blocks",
|
|
2901
|
+
"related",
|
|
2902
|
+
"duplicate"
|
|
2903
|
+
],
|
|
2904
|
+
"type": "string"
|
|
2905
|
+
},
|
|
2906
|
+
"updated_at": {
|
|
2907
|
+
"format": "date-time",
|
|
2908
|
+
"type": "string"
|
|
2909
|
+
}
|
|
2910
|
+
},
|
|
2911
|
+
"required": [
|
|
2912
|
+
"id",
|
|
2913
|
+
"type",
|
|
2914
|
+
"issue",
|
|
2915
|
+
"related_issue",
|
|
2916
|
+
"direction",
|
|
2917
|
+
"other_issue",
|
|
2918
|
+
"inserted_at",
|
|
2919
|
+
"updated_at"
|
|
2920
|
+
],
|
|
2921
|
+
"type": "object"
|
|
2922
|
+
}
|
|
2923
|
+
},
|
|
2924
|
+
"required": [
|
|
2925
|
+
"data"
|
|
2926
|
+
],
|
|
2927
|
+
"type": "object"
|
|
2928
|
+
}
|
|
2929
|
+
}
|
|
2930
|
+
},
|
|
2931
|
+
"description": "Success"
|
|
2932
|
+
},
|
|
2933
|
+
"400": {
|
|
2934
|
+
"description": "Bad request"
|
|
2935
|
+
},
|
|
2936
|
+
"401": {
|
|
2937
|
+
"description": "Missing or invalid token"
|
|
2938
|
+
},
|
|
2939
|
+
"403": {
|
|
2940
|
+
"description": "Token not permitted (org-scoped, non-admin, or missing scope)"
|
|
2941
|
+
},
|
|
2942
|
+
"404": {
|
|
2943
|
+
"description": "Not found"
|
|
2944
|
+
},
|
|
2945
|
+
"422": {
|
|
2946
|
+
"description": "Validation error"
|
|
2947
|
+
}
|
|
2948
|
+
},
|
|
2949
|
+
"summary": "Update a relation",
|
|
2950
|
+
"tags": []
|
|
2513
2951
|
}
|
|
2514
2952
|
},
|
|
2515
2953
|
"/api/v1/issues/{identifier}/links/{id}": {
|
|
@@ -2559,6 +2997,127 @@
|
|
|
2559
2997
|
},
|
|
2560
2998
|
"summary": "Delete a link",
|
|
2561
2999
|
"tags": []
|
|
3000
|
+
},
|
|
3001
|
+
"patch": {
|
|
3002
|
+
"callbacks": {},
|
|
3003
|
+
"deprecated": false,
|
|
3004
|
+
"description": "Requires the `harness:write` scope.",
|
|
3005
|
+
"parameters": [
|
|
3006
|
+
{
|
|
3007
|
+
"description": "Issue identifier, e.g. ALL-4901 or CLI-12 (issues minted before team-prefixed numbering carry IO-)",
|
|
3008
|
+
"in": "path",
|
|
3009
|
+
"name": "identifier",
|
|
3010
|
+
"required": true,
|
|
3011
|
+
"schema": {
|
|
3012
|
+
"type": "string"
|
|
3013
|
+
}
|
|
3014
|
+
},
|
|
3015
|
+
{
|
|
3016
|
+
"description": "Resource id",
|
|
3017
|
+
"in": "path",
|
|
3018
|
+
"name": "id",
|
|
3019
|
+
"required": true,
|
|
3020
|
+
"schema": {
|
|
3021
|
+
"type": "string"
|
|
3022
|
+
}
|
|
3023
|
+
}
|
|
3024
|
+
],
|
|
3025
|
+
"requestBody": {
|
|
3026
|
+
"content": {
|
|
3027
|
+
"application/json": {
|
|
3028
|
+
"schema": {
|
|
3029
|
+
"properties": {
|
|
3030
|
+
"expected_url": {
|
|
3031
|
+
"description": "Optional optimistic precondition; mismatch returns 409",
|
|
3032
|
+
"type": "string"
|
|
3033
|
+
},
|
|
3034
|
+
"title": {
|
|
3035
|
+
"type": "string"
|
|
3036
|
+
},
|
|
3037
|
+
"url": {
|
|
3038
|
+
"description": "http(s) URL",
|
|
3039
|
+
"type": "string"
|
|
3040
|
+
}
|
|
3041
|
+
},
|
|
3042
|
+
"type": "object"
|
|
3043
|
+
}
|
|
3044
|
+
}
|
|
3045
|
+
},
|
|
3046
|
+
"required": true
|
|
3047
|
+
},
|
|
3048
|
+
"responses": {
|
|
3049
|
+
"200": {
|
|
3050
|
+
"content": {
|
|
3051
|
+
"application/json": {
|
|
3052
|
+
"schema": {
|
|
3053
|
+
"properties": {
|
|
3054
|
+
"data": {
|
|
3055
|
+
"properties": {
|
|
3056
|
+
"id": {
|
|
3057
|
+
"format": "uuid",
|
|
3058
|
+
"type": "string"
|
|
3059
|
+
},
|
|
3060
|
+
"inserted_at": {
|
|
3061
|
+
"format": "date-time",
|
|
3062
|
+
"type": "string"
|
|
3063
|
+
},
|
|
3064
|
+
"issue_id": {
|
|
3065
|
+
"format": "uuid",
|
|
3066
|
+
"type": "string"
|
|
3067
|
+
},
|
|
3068
|
+
"title": {
|
|
3069
|
+
"nullable": true,
|
|
3070
|
+
"type": "string"
|
|
3071
|
+
},
|
|
3072
|
+
"updated_at": {
|
|
3073
|
+
"format": "date-time",
|
|
3074
|
+
"type": "string"
|
|
3075
|
+
},
|
|
3076
|
+
"url": {
|
|
3077
|
+
"type": "string"
|
|
3078
|
+
}
|
|
3079
|
+
},
|
|
3080
|
+
"required": [
|
|
3081
|
+
"id",
|
|
3082
|
+
"url",
|
|
3083
|
+
"title",
|
|
3084
|
+
"issue_id",
|
|
3085
|
+
"inserted_at",
|
|
3086
|
+
"updated_at"
|
|
3087
|
+
],
|
|
3088
|
+
"type": "object"
|
|
3089
|
+
}
|
|
3090
|
+
},
|
|
3091
|
+
"required": [
|
|
3092
|
+
"data"
|
|
3093
|
+
],
|
|
3094
|
+
"type": "object"
|
|
3095
|
+
}
|
|
3096
|
+
}
|
|
3097
|
+
},
|
|
3098
|
+
"description": "Success"
|
|
3099
|
+
},
|
|
3100
|
+
"400": {
|
|
3101
|
+
"description": "Bad request"
|
|
3102
|
+
},
|
|
3103
|
+
"401": {
|
|
3104
|
+
"description": "Missing or invalid token"
|
|
3105
|
+
},
|
|
3106
|
+
"403": {
|
|
3107
|
+
"description": "Token not permitted (org-scoped, non-admin, or missing scope)"
|
|
3108
|
+
},
|
|
3109
|
+
"404": {
|
|
3110
|
+
"description": "Not found"
|
|
3111
|
+
},
|
|
3112
|
+
"409": {
|
|
3113
|
+
"description": "The link URL changed after the client resolved it"
|
|
3114
|
+
},
|
|
3115
|
+
"422": {
|
|
3116
|
+
"description": "Validation error"
|
|
3117
|
+
}
|
|
3118
|
+
},
|
|
3119
|
+
"summary": "Update a link",
|
|
3120
|
+
"tags": []
|
|
2562
3121
|
}
|
|
2563
3122
|
},
|
|
2564
3123
|
"/api/v1/issues/{identifier}/relations": {
|