@allocator-one/harness-cli 1.9.0 → 1.11.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 +31 -3
- package/dist/{chunk-NQVQNKMC.js → chunk-WY6F3IJB.js} +185 -18
- package/dist/cli.js +26 -4
- package/dist/{server-FWNT3HMX.js → server-5YNUXJO2.js} +15 -2
- package/package.json +1 -1
- package/spec/openapi.json +375 -4
package/README.md
CHANGED
|
@@ -52,7 +52,7 @@ the browser login attributes writes to the real user.
|
|
|
52
52
|
## For agents
|
|
53
53
|
|
|
54
54
|
`harness mcp` runs a stdio MCP server exposing every operation as a typed
|
|
55
|
-
tool — issue_list, issue_show, issue_create, issue_update, issue_claim,
|
|
55
|
+
tool — issue_list, issue_show, activity_list, issue_create, issue_update, issue_claim,
|
|
56
56
|
issue_close, issue_comment, issue_comment_edit, issue_comment_delete,
|
|
57
57
|
relation_list/create/update/delete, relation_add/rm (blocking shorthand),
|
|
58
58
|
link_list/add/update/rm,
|
|
@@ -71,6 +71,12 @@ Example tool call: `issue_list` with `{"ready": true, "team": "ALL"}`
|
|
|
71
71
|
returns the ready queue as JSON; `issue_claim` with `{"id": "IO-123"}`
|
|
72
72
|
claims atomically (a conflict means someone else holds it).
|
|
73
73
|
|
|
74
|
+
Read the same mixed timeline shown in an issue's Activity section with
|
|
75
|
+
`harness activity IO-123`. It returns the newest 20 entries and points out
|
|
76
|
+
when older entries exist; pass `--all` to follow the opaque cursors through
|
|
77
|
+
the full history. `activity_list` exposes the same `id`, `limit`, and `all`
|
|
78
|
+
inputs to MCP clients.
|
|
79
|
+
|
|
74
80
|
Typed issue relations are available without losing the concise blocking
|
|
75
81
|
workflow. `harness dep add|rm` and the `relation_add`/`relation_rm` MCP tools
|
|
76
82
|
remain the blocker-first shorthand; the full surface is:
|
|
@@ -141,6 +147,23 @@ error rather than a silent precedence, and only one field per invocation may
|
|
|
141
147
|
read stdin. Both are checked before anything is read, so a rejected command
|
|
142
148
|
never consumes your pipe. One trailing newline is dropped, like `$(cat file)`.
|
|
143
149
|
|
|
150
|
+
## Image and video uploads
|
|
151
|
+
|
|
152
|
+
`harness upload ./demo.mp4` prints a reusable markdown reference. Paste it
|
|
153
|
+
into an issue description or comment to show an inline video player. Use
|
|
154
|
+
`--json` for the server's URL, filename, content type, byte size, and markdown.
|
|
155
|
+
The MCP `upload` tool accepts the same local path and returns the same metadata.
|
|
156
|
+
|
|
157
|
+
Images (PNG, JPG, WebP, GIF) can be up to 25 MB. Videos must be `.mp4`, up to
|
|
158
|
+
100 MB, with H.264 8-bit 4:2:0 video, optional AAC audio, and optional mov_text
|
|
159
|
+
subtitles. The server validates the media; it does not transcode other formats.
|
|
160
|
+
|
|
161
|
+
A newly uploaded video is private to its uploader until that user saves its
|
|
162
|
+
link in an issue description or comment. It then follows the owning issue's
|
|
163
|
+
access policy. Reusing the link elsewhere does not change that ownership.
|
|
164
|
+
`harness show` continues downloading images only, never linked videos.
|
|
165
|
+
The older `attach-image` command retains its 25 MB limit.
|
|
166
|
+
|
|
144
167
|
## Repo config
|
|
145
168
|
|
|
146
169
|
A committed `.harness-cli/config.json`, discovered git-style by walking up
|
|
@@ -181,10 +204,15 @@ CI regenerates the types and fails on drift.
|
|
|
181
204
|
|
|
182
205
|
## Release
|
|
183
206
|
|
|
184
|
-
|
|
207
|
+
First bump `package.json` and `package-lock.json` through a pull request. After
|
|
208
|
+
that PR is squash-merged, fetch `main` and tag the merge commit — never the
|
|
209
|
+
pre-merge branch tip. Pushing the tag starts the Release workflow, which builds,
|
|
210
|
+
tests, and publishes to npm:
|
|
185
211
|
|
|
186
212
|
```sh
|
|
187
|
-
git
|
|
213
|
+
git fetch origin main
|
|
214
|
+
git tag -a vX.Y.Z MERGE_COMMIT_SHA -m "Release vX.Y.Z"
|
|
215
|
+
git push origin vX.Y.Z
|
|
188
216
|
```
|
|
189
217
|
|
|
190
218
|
Publishing requires the `NPM_TOKEN` actions secret (an automation token for
|
|
@@ -166,11 +166,16 @@ var Client = class {
|
|
|
166
166
|
throw new TransportError(transportMessage(error));
|
|
167
167
|
}
|
|
168
168
|
if (text === "") return { data: void 0 };
|
|
169
|
+
let parsed;
|
|
169
170
|
try {
|
|
170
|
-
|
|
171
|
+
parsed = JSON.parse(text);
|
|
171
172
|
} catch {
|
|
172
173
|
throw new TransportError(`the API answered with a non-JSON body (HTTP ${response.status})`);
|
|
173
174
|
}
|
|
175
|
+
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
|
|
176
|
+
throw new TransportError(`the API answered with an invalid JSON envelope (HTTP ${response.status})`);
|
|
177
|
+
}
|
|
178
|
+
return parsed;
|
|
174
179
|
}
|
|
175
180
|
async toHttpError(response) {
|
|
176
181
|
let parsed = {};
|
|
@@ -255,6 +260,101 @@ ${indent(String(text))}
|
|
|
255
260
|
`).join("");
|
|
256
261
|
return header + fieldLines + sectionText + relationsSection(issue["relations"]) + childrenSection(issue["children"]) + commentsSection(issue["comments"]) + imagesSection(images);
|
|
257
262
|
}
|
|
263
|
+
function activityEntries(entries) {
|
|
264
|
+
if (entries.length === 0) return "No activity.\n";
|
|
265
|
+
return entries.map(activityEntry).join("\n");
|
|
266
|
+
}
|
|
267
|
+
function activityEntry(entry) {
|
|
268
|
+
switch (entry["kind"]) {
|
|
269
|
+
case "comment":
|
|
270
|
+
return activityComment(entry);
|
|
271
|
+
case "state_event":
|
|
272
|
+
return activityStateEvent(entry);
|
|
273
|
+
default:
|
|
274
|
+
throw new CommandError(`Unknown activity entry kind: ${clean(entry["kind"])}`);
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
function activityComment(entry) {
|
|
278
|
+
const comment2 = requiredRecord(entry["comment"], "activity comment");
|
|
279
|
+
const authorValue = comment2["author"];
|
|
280
|
+
const author = authorValue === null ? "unknown" : optionalLabel(requiredRecord(authorValue, "comment author")["name"], "unknown", "comment author name");
|
|
281
|
+
const deleted = comment2["deleted"];
|
|
282
|
+
if (typeof deleted !== "boolean") throw new CommandError("Invalid comment deleted flag.");
|
|
283
|
+
const body = activityCommentBody(comment2["body"], deleted);
|
|
284
|
+
const text = cleanMultiline(body).split("\n").map((line) => " " + line).join("\n");
|
|
285
|
+
return `[${clean(requiredString(entry["occurred_at"], "activity occurrence time"))}] ${clean(author)} (${clean(requiredString(comment2["id"], "comment id"))}):
|
|
286
|
+
${text}
|
|
287
|
+
`;
|
|
288
|
+
}
|
|
289
|
+
function activityCommentBody(value, deleted) {
|
|
290
|
+
if (deleted) return "[deleted comment]";
|
|
291
|
+
if (value === null || value === "") return "[comment has no text]";
|
|
292
|
+
return requiredString(value, "comment body");
|
|
293
|
+
}
|
|
294
|
+
function activityStateEvent(entry) {
|
|
295
|
+
const actor = activityActor(requiredRecord(entry["actor"], "activity actor"));
|
|
296
|
+
const provenance = activityProvenance(requiredRecord(entry["provenance"], "activity provenance"));
|
|
297
|
+
let description;
|
|
298
|
+
switch (entry["action"]) {
|
|
299
|
+
case "created":
|
|
300
|
+
description = `${actor} created the issue in ${stateName(entry["to_state"], "created to_state")}`;
|
|
301
|
+
break;
|
|
302
|
+
case "transition":
|
|
303
|
+
description = `${actor} moved the issue from ${nullableStateName(entry["from_state"], "transition from_state")} to ${stateName(entry["to_state"], "transition to_state")}`;
|
|
304
|
+
break;
|
|
305
|
+
case "milestone":
|
|
306
|
+
description = `${actor} recorded the ${stateName(entry["to_state"], "milestone to_state")} milestone`;
|
|
307
|
+
break;
|
|
308
|
+
default:
|
|
309
|
+
throw new CommandError(`Unknown activity action: ${clean(entry["action"])}`);
|
|
310
|
+
}
|
|
311
|
+
return `[${clean(requiredString(entry["occurred_at"], "activity occurrence time"))}] ${description} \xB7 ${provenance}
|
|
312
|
+
`;
|
|
313
|
+
}
|
|
314
|
+
function activityActor(actor) {
|
|
315
|
+
switch (actor["kind"]) {
|
|
316
|
+
case "user":
|
|
317
|
+
return optionalLabel(actor["name"], "Unknown actor", "activity actor name");
|
|
318
|
+
case "system":
|
|
319
|
+
return "System";
|
|
320
|
+
case "deleted_user":
|
|
321
|
+
return "Deleted user";
|
|
322
|
+
case "unknown":
|
|
323
|
+
return "Unknown actor";
|
|
324
|
+
default:
|
|
325
|
+
throw new CommandError(`Unknown activity actor kind: ${clean(actor["kind"])}`);
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
function activityProvenance(provenance) {
|
|
329
|
+
switch (provenance["kind"]) {
|
|
330
|
+
case "live":
|
|
331
|
+
case "import":
|
|
332
|
+
case "backfill":
|
|
333
|
+
return String(provenance["kind"]);
|
|
334
|
+
default:
|
|
335
|
+
throw new CommandError(`Unknown activity provenance: ${clean(provenance["kind"])}`);
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
function stateName(value, field2) {
|
|
339
|
+
return optionalLabel(requiredRecord(value, field2)["name"], "Unknown state", `${field2} name`);
|
|
340
|
+
}
|
|
341
|
+
function nullableStateName(value, field2) {
|
|
342
|
+
return value === null ? "Unknown state" : stateName(value, field2);
|
|
343
|
+
}
|
|
344
|
+
function optionalLabel(value, fallback, field2) {
|
|
345
|
+
if (value === null || value === "") return fallback;
|
|
346
|
+
return clean(requiredString(value, field2));
|
|
347
|
+
}
|
|
348
|
+
function requiredRecord(value, field2) {
|
|
349
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) {
|
|
350
|
+
throw new CommandError(`Invalid ${field2}.`);
|
|
351
|
+
}
|
|
352
|
+
return value;
|
|
353
|
+
}
|
|
354
|
+
function requiredString(value, field2) {
|
|
355
|
+
if (typeof value !== "string" || value === "") throw new CommandError(`Invalid ${field2}.`);
|
|
356
|
+
return value;
|
|
357
|
+
}
|
|
258
358
|
function stats(data) {
|
|
259
359
|
const teams = data["teams"] ?? [];
|
|
260
360
|
const totals = data["totals"] ?? {};
|
|
@@ -344,8 +444,8 @@ function path(record, ...keys) {
|
|
|
344
444
|
}
|
|
345
445
|
|
|
346
446
|
// src/commands/issues.ts
|
|
347
|
-
import { openSync, closeSync, readFileSync as readFileSync2, statSync as statSync2 } from "fs";
|
|
348
|
-
import { basename } from "path";
|
|
447
|
+
import { openAsBlob, openSync, closeSync, readFileSync as readFileSync2, statSync as statSync2 } from "fs";
|
|
448
|
+
import { basename, extname } from "path";
|
|
349
449
|
|
|
350
450
|
// src/core/attachments.ts
|
|
351
451
|
import { chmodSync, mkdirSync, renameSync, rmSync, statSync, writeFileSync } from "fs";
|
|
@@ -594,6 +694,61 @@ async function show(ctx, identifier, opts) {
|
|
|
594
694
|
`).join("");
|
|
595
695
|
return notice === "" ? { json: json2, human } : { json: json2, human, notice };
|
|
596
696
|
}
|
|
697
|
+
var ACTIVITY_MAX_LIMIT = 100;
|
|
698
|
+
async function activity(ctx, identifier, opts) {
|
|
699
|
+
requireIdentifiers([identifier]);
|
|
700
|
+
let page = await activityPage(ctx, identifier, opts.limit);
|
|
701
|
+
let entries = page.entries;
|
|
702
|
+
if (opts.all === true) {
|
|
703
|
+
const pages = [page.entries];
|
|
704
|
+
const seenCursors = /* @__PURE__ */ new Set();
|
|
705
|
+
while (page.hasMore) {
|
|
706
|
+
const cursor = page.nextCursor;
|
|
707
|
+
if (seenCursors.has(cursor)) {
|
|
708
|
+
throw new CommandError("Activity response repeated a cursor.");
|
|
709
|
+
}
|
|
710
|
+
seenCursors.add(cursor);
|
|
711
|
+
page = await activityPage(ctx, identifier, opts.limit, cursor);
|
|
712
|
+
pages.push(page.entries);
|
|
713
|
+
}
|
|
714
|
+
entries = pages.reverse().flat();
|
|
715
|
+
}
|
|
716
|
+
const notice = opts.all !== true && page.hasMore ? `harness: showing the newest ${entries.length} activity entries \u2014 pass --all for older activity.
|
|
717
|
+
` : void 0;
|
|
718
|
+
return { json: entries, human: activityEntries(entries), notice };
|
|
719
|
+
}
|
|
720
|
+
async function activityPage(ctx, identifier, limit, before) {
|
|
721
|
+
const body = await ctx.client.get(`/api/v1/issues/${identifier}/activity`, { limit, before });
|
|
722
|
+
if (!Array.isArray(body.data)) throw new CommandError("Activity response data is not an array.");
|
|
723
|
+
if (body.data.some((entry) => typeof entry !== "object" || entry === null || Array.isArray(entry))) {
|
|
724
|
+
throw new CommandError("Activity response contains a non-object entry.");
|
|
725
|
+
}
|
|
726
|
+
const meta = body.meta;
|
|
727
|
+
if (typeof meta !== "object" || meta === null) {
|
|
728
|
+
throw new CommandError("Activity response has no pagination metadata.");
|
|
729
|
+
}
|
|
730
|
+
const hasMore = meta.has_more;
|
|
731
|
+
const nextCursor = meta.next_cursor;
|
|
732
|
+
if (typeof hasMore !== "boolean") {
|
|
733
|
+
throw new CommandError("Activity response has an invalid has_more value.");
|
|
734
|
+
}
|
|
735
|
+
if (nextCursor !== null && typeof nextCursor !== "string") {
|
|
736
|
+
throw new CommandError("Activity response has an invalid next_cursor value.");
|
|
737
|
+
}
|
|
738
|
+
if (hasMore) {
|
|
739
|
+
if (nextCursor === null || nextCursor === "") {
|
|
740
|
+
throw new CommandError("Activity response says older entries exist but provides no cursor.");
|
|
741
|
+
}
|
|
742
|
+
if (body.data.length === 0) {
|
|
743
|
+
throw new CommandError("Activity response says older entries exist but contains no entries.");
|
|
744
|
+
}
|
|
745
|
+
return { entries: body.data, hasMore: true, nextCursor };
|
|
746
|
+
}
|
|
747
|
+
if (nextCursor !== null) {
|
|
748
|
+
throw new CommandError("Activity response provides a cursor when no older entries exist.");
|
|
749
|
+
}
|
|
750
|
+
return { entries: body.data, hasMore: false, nextCursor: null };
|
|
751
|
+
}
|
|
597
752
|
async function stats2(ctx) {
|
|
598
753
|
const body = await ctx.client.get("/api/v1/stats");
|
|
599
754
|
return { json: body.data, human: stats(body.data) };
|
|
@@ -935,19 +1090,28 @@ function requireCommentId(commentId) {
|
|
|
935
1090
|
throw new UsageError(`not a comment id: ${commentId} (expected a UUID)`);
|
|
936
1091
|
}
|
|
937
1092
|
}
|
|
938
|
-
var
|
|
1093
|
+
var MAX_IMAGE_BYTES = 25e6;
|
|
1094
|
+
var MAX_VIDEO_BYTES = 1e8;
|
|
939
1095
|
async function attachImage(ctx, path2) {
|
|
940
|
-
|
|
941
|
-
|
|
1096
|
+
return uploadAttachment(ctx, path2, MAX_IMAGE_BYTES);
|
|
1097
|
+
}
|
|
1098
|
+
async function attachFile(ctx, path2) {
|
|
1099
|
+
const maxBytes = extname(path2).toLowerCase() === ".mp4" ? MAX_VIDEO_BYTES : MAX_IMAGE_BYTES;
|
|
1100
|
+
return uploadAttachment(ctx, path2, maxBytes);
|
|
1101
|
+
}
|
|
1102
|
+
async function uploadAttachment(ctx, path2, maxBytes) {
|
|
1103
|
+
attachableFile(path2, maxBytes);
|
|
1104
|
+
const file = maxBytes === MAX_VIDEO_BYTES ? new File([await openAsBlob(path2)], basename(path2)) : new File([readFileSync2(path2)], basename(path2));
|
|
1105
|
+
requireAttachmentSize(path2, file.size, maxBytes);
|
|
942
1106
|
const form = new FormData();
|
|
943
|
-
form.set("file",
|
|
944
|
-
const body = await ctx.client.postMultipart("/api/v1/uploads", form,
|
|
1107
|
+
form.set("file", file);
|
|
1108
|
+
const body = await ctx.client.postMultipart("/api/v1/uploads", form, file.size);
|
|
945
1109
|
const attachment = body.data;
|
|
946
1110
|
const markdown = attachment["markdown"];
|
|
947
1111
|
if (typeof markdown !== "string") throw new CommandError("the upload response carried no markdown reference");
|
|
948
1112
|
return { json: attachment, human: clean(markdown) + "\n" };
|
|
949
1113
|
}
|
|
950
|
-
function attachableFile(path2) {
|
|
1114
|
+
function attachableFile(path2, maxBytes) {
|
|
951
1115
|
let fileStats;
|
|
952
1116
|
try {
|
|
953
1117
|
fileStats = statSync2(path2);
|
|
@@ -958,17 +1122,18 @@ function attachableFile(path2) {
|
|
|
958
1122
|
const kind = fileStats.isDirectory() ? "directory" : "special file";
|
|
959
1123
|
throw new LocalError(`${path2} is a ${kind}, not a file.`);
|
|
960
1124
|
}
|
|
961
|
-
|
|
962
|
-
throw new LocalError(
|
|
963
|
-
`${path2} is too large: ${fileStats.size} bytes, and attachments are capped at ${MAX_ATTACHMENT_BYTES2}.`
|
|
964
|
-
);
|
|
965
|
-
}
|
|
1125
|
+
requireAttachmentSize(path2, fileStats.size, maxBytes);
|
|
966
1126
|
try {
|
|
967
1127
|
closeSync(openSync(path2, "r"));
|
|
968
1128
|
} catch (error) {
|
|
969
1129
|
throw new LocalError(`cannot read ${path2}: ${posixDetail(error)}`);
|
|
970
1130
|
}
|
|
971
1131
|
}
|
|
1132
|
+
function requireAttachmentSize(path2, size, maxBytes) {
|
|
1133
|
+
if (size > maxBytes) {
|
|
1134
|
+
throw new LocalError(`${path2} is too large: ${size} bytes, and attachments are capped at ${maxBytes}.`);
|
|
1135
|
+
}
|
|
1136
|
+
}
|
|
972
1137
|
function posixDetail(error) {
|
|
973
1138
|
const code = error.code;
|
|
974
1139
|
if (code === "ENOENT") return "no such file or directory";
|
|
@@ -992,19 +1157,19 @@ async function claimFirst(ctx, issues) {
|
|
|
992
1157
|
}
|
|
993
1158
|
throw new CommandError("No claimable ready issues.");
|
|
994
1159
|
}
|
|
995
|
-
async function setState(ctx, identifier,
|
|
1160
|
+
async function setState(ctx, identifier, stateName2) {
|
|
996
1161
|
const showBody = await ctx.client.get(`/api/v1/issues/${identifier}`);
|
|
997
1162
|
const teamKey = str2((showBody.data["team"] ?? {})["key"]);
|
|
998
1163
|
const statesBody = await ctx.client.get(
|
|
999
1164
|
`/api/v1/teams/${teamKey}/workflow_states`
|
|
1000
1165
|
);
|
|
1001
|
-
const wanted =
|
|
1166
|
+
const wanted = stateName2.toLowerCase();
|
|
1002
1167
|
const state = statesBody.data.find(
|
|
1003
1168
|
(candidate) => str2(candidate["name"]).toLowerCase() === wanted || str2(candidate["type"]) === wanted
|
|
1004
1169
|
);
|
|
1005
1170
|
if (state === void 0) {
|
|
1006
1171
|
const names = statesBody.data.map((candidate) => str2(candidate["name"])).join(", ");
|
|
1007
|
-
throw new CommandError(`No workflow state matches "${
|
|
1172
|
+
throw new CommandError(`No workflow state matches "${stateName2}". States: ${names}`);
|
|
1008
1173
|
}
|
|
1009
1174
|
const body = await ctx.client.put(`/api/v1/issues/${identifier}/state`, { state_id: state["id"] });
|
|
1010
1175
|
return body.data;
|
|
@@ -1690,7 +1855,7 @@ function requireYes(command, id, yes) {
|
|
|
1690
1855
|
if (yes !== true) throw new UsageError(`${command} ${id} is destructive \u2014 pass --yes to confirm`);
|
|
1691
1856
|
}
|
|
1692
1857
|
async function upload(ctx, path2) {
|
|
1693
|
-
return
|
|
1858
|
+
return attachFile(ctx, path2);
|
|
1694
1859
|
}
|
|
1695
1860
|
function indent2(text) {
|
|
1696
1861
|
return cleanMultiline(text).split("\n").map((line) => " " + line).join("\n");
|
|
@@ -1951,6 +2116,8 @@ export {
|
|
|
1951
2116
|
list,
|
|
1952
2117
|
search,
|
|
1953
2118
|
show,
|
|
2119
|
+
ACTIVITY_MAX_LIMIT,
|
|
2120
|
+
activity,
|
|
1954
2121
|
stats2 as stats,
|
|
1955
2122
|
prime,
|
|
1956
2123
|
version,
|
package/dist/cli.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
|
+
ACTIVITY_MAX_LIMIT,
|
|
3
4
|
CLI_VERSION,
|
|
4
5
|
Client,
|
|
5
6
|
CommandError,
|
|
@@ -11,6 +12,7 @@ import {
|
|
|
11
12
|
TransportError,
|
|
12
13
|
UpgradeRequiredError,
|
|
13
14
|
UsageError,
|
|
15
|
+
activity,
|
|
14
16
|
archive,
|
|
15
17
|
attachImage,
|
|
16
18
|
blocked,
|
|
@@ -80,7 +82,7 @@ import {
|
|
|
80
82
|
version,
|
|
81
83
|
workflowStates,
|
|
82
84
|
writeCredentials
|
|
83
|
-
} from "./chunk-
|
|
85
|
+
} from "./chunk-WY6F3IJB.js";
|
|
84
86
|
|
|
85
87
|
// src/cli.ts
|
|
86
88
|
import { createInterface as createInterface2 } from "readline";
|
|
@@ -1238,6 +1240,15 @@ function integer(value) {
|
|
|
1238
1240
|
throw new InvalidArgumentError("expected a non-negative integer");
|
|
1239
1241
|
return Number.parseInt(value, 10);
|
|
1240
1242
|
}
|
|
1243
|
+
function activityLimit(value) {
|
|
1244
|
+
const limit = integer(value);
|
|
1245
|
+
if (limit < 1 || limit > ACTIVITY_MAX_LIMIT) {
|
|
1246
|
+
throw new InvalidArgumentError(
|
|
1247
|
+
`expected an integer from 1 to ${ACTIVITY_MAX_LIMIT}`
|
|
1248
|
+
);
|
|
1249
|
+
}
|
|
1250
|
+
return limit;
|
|
1251
|
+
}
|
|
1241
1252
|
function collect(value, previous) {
|
|
1242
1253
|
return [...previous, value];
|
|
1243
1254
|
}
|
|
@@ -1424,6 +1435,17 @@ program.command("show").description("full issue detail; downloads attached image
|
|
|
1424
1435
|
});
|
|
1425
1436
|
}
|
|
1426
1437
|
);
|
|
1438
|
+
program.command("activity").description("mixed issue activity: comments, creation, and state changes").argument("[id]").option(
|
|
1439
|
+
"--limit <n>",
|
|
1440
|
+
`entries per page (default 20, max ${ACTIVITY_MAX_LIMIT})`,
|
|
1441
|
+
activityLimit
|
|
1442
|
+
).option("--all", "read all older activity").option("--json", "emit JSON").action(async (id, opts) => {
|
|
1443
|
+
await withConfig(opts.json === true, false, (ctx) => {
|
|
1444
|
+
if (id === void 0) throw new UsageError("usage: harness activity <id>");
|
|
1445
|
+
requireIdentifiers2([id]);
|
|
1446
|
+
return activity(ctx, id, opts);
|
|
1447
|
+
});
|
|
1448
|
+
});
|
|
1427
1449
|
program.command("stats").description("per-team and total counts").option("--json", "emit JSON").action(async (opts) => {
|
|
1428
1450
|
await withConfig(opts.json === true, false, (ctx) => stats(ctx));
|
|
1429
1451
|
});
|
|
@@ -1879,17 +1901,17 @@ user.command("list").option("--json", "emit JSON").action(async (opts) => {
|
|
|
1879
1901
|
(ctx) => userList(ctx)
|
|
1880
1902
|
);
|
|
1881
1903
|
});
|
|
1882
|
-
program.command("upload").description("upload
|
|
1904
|
+
program.command("upload").description("upload an image (25 MB) or H.264 MP4 video (100 MB, optional AAC audio); prints markdown").argument("[path]").option("--json", "emit JSON").action(async (path, opts) => {
|
|
1883
1905
|
await withConfig(opts.json === true, true, (ctx) => {
|
|
1884
1906
|
if (path === void 0)
|
|
1885
1907
|
throw new UsageError(
|
|
1886
|
-
"usage: harness upload <path> (png/jpg/webp/gif
|
|
1908
|
+
"usage: harness upload <path> (png/jpg/webp/gif up to 25 MB; H.264 MP4 with optional AAC audio up to 100 MB)"
|
|
1887
1909
|
);
|
|
1888
1910
|
return upload(ctx, path);
|
|
1889
1911
|
});
|
|
1890
1912
|
});
|
|
1891
1913
|
program.command("mcp").description("run the MCP server over stdio (for agents)").action(async () => {
|
|
1892
|
-
const { startMcpServer } = await import("./server-
|
|
1914
|
+
const { startMcpServer } = await import("./server-5YNUXJO2.js");
|
|
1893
1915
|
await startMcpServer();
|
|
1894
1916
|
});
|
|
1895
1917
|
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(
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
|
+
ACTIVITY_MAX_LIMIT,
|
|
3
4
|
CLI_VERSION,
|
|
4
5
|
Client,
|
|
5
6
|
HttpError,
|
|
@@ -7,6 +8,7 @@ import {
|
|
|
7
8
|
RELATION_UPDATE_TYPES,
|
|
8
9
|
TransportError,
|
|
9
10
|
UsageError,
|
|
11
|
+
activity,
|
|
10
12
|
archive,
|
|
11
13
|
blocked,
|
|
12
14
|
close,
|
|
@@ -59,7 +61,7 @@ import {
|
|
|
59
61
|
upload,
|
|
60
62
|
userList,
|
|
61
63
|
workflowStates
|
|
62
|
-
} from "./chunk-
|
|
64
|
+
} from "./chunk-WY6F3IJB.js";
|
|
63
65
|
|
|
64
66
|
// src/mcp/server.ts
|
|
65
67
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
@@ -161,6 +163,17 @@ var TOOLS = [
|
|
|
161
163
|
shape: { id: identifier, images: z.boolean().optional() },
|
|
162
164
|
handler: (ctx, args) => show(ctx, args["id"], { images: args["images"] === true })
|
|
163
165
|
},
|
|
166
|
+
{
|
|
167
|
+
name: "activity_list",
|
|
168
|
+
description: "Mixed issue activity: comments, creation, and state changes. Returns the newest page unless all=true.",
|
|
169
|
+
readOnly: true,
|
|
170
|
+
shape: {
|
|
171
|
+
id: identifier,
|
|
172
|
+
limit: z.number().int().min(1).max(ACTIVITY_MAX_LIMIT).optional().describe(`entries per page (default 20, max ${ACTIVITY_MAX_LIMIT})`),
|
|
173
|
+
all: z.boolean().optional().describe("follow cursors through all older activity")
|
|
174
|
+
},
|
|
175
|
+
handler: (ctx, args) => activity(ctx, args["id"], args)
|
|
176
|
+
},
|
|
164
177
|
{
|
|
165
178
|
name: "issue_create",
|
|
166
179
|
description: "Create an issue. type becomes a type/ label.",
|
|
@@ -646,7 +659,7 @@ var TOOLS = [
|
|
|
646
659
|
// ── Uploads ──
|
|
647
660
|
{
|
|
648
661
|
name: "upload",
|
|
649
|
-
description: "Upload
|
|
662
|
+
description: "Upload a local image (png/jpg/webp/gif, up to 25 MB) or H.264 MP4 video (8-bit 4:2:0, optional AAC audio and mov_text subtitles, up to 100 MB). Returns metadata and markdown: images use image markdown; videos use ordinary links rendered as inline players in Harness issues. A video is private to its uploader until that user saves its link in an issue description or comment. Reading an issue does not automatically download videos.",
|
|
650
663
|
readOnly: false,
|
|
651
664
|
shape: { path: z.string().describe("local file path") },
|
|
652
665
|
handler: (ctx, args) => upload(ctx, args["path"])
|
package/package.json
CHANGED
package/spec/openapi.json
CHANGED
|
@@ -425,7 +425,7 @@
|
|
|
425
425
|
"get": {
|
|
426
426
|
"callbacks": {},
|
|
427
427
|
"deprecated": false,
|
|
428
|
-
"description": "Requires the `harness:read` scope. Streams the
|
|
428
|
+
"description": "Requires the `harness:read` scope. Streams attachment bytes. Videos recheck the owning organization's and issue's access on every request and use private, no-store caching. Supports one bytes Range: bounded, open-ended, or suffix; returns 206 with Content-Range, or 416 for an unsatisfiable range. Malformed, multipart, and conditional ranges fall back to a full 200. Images retain full-response behavior. Add ?mode=download for attachment disposition.",
|
|
429
429
|
"parameters": [
|
|
430
430
|
{
|
|
431
431
|
"description": "Attachment UUID (the directory segment of the upload's URL)",
|
|
@@ -463,6 +463,12 @@
|
|
|
463
463
|
"404": {
|
|
464
464
|
"description": "Not found"
|
|
465
465
|
},
|
|
466
|
+
"206": {
|
|
467
|
+
"description": "Requested video byte range, with Content-Range and Content-Length"
|
|
468
|
+
},
|
|
469
|
+
"416": {
|
|
470
|
+
"description": "Unsatisfiable range; Content-Range contains the actual file size"
|
|
471
|
+
},
|
|
466
472
|
"422": {
|
|
467
473
|
"description": "Validation error"
|
|
468
474
|
}
|
|
@@ -791,7 +797,7 @@
|
|
|
791
797
|
"post": {
|
|
792
798
|
"callbacks": {},
|
|
793
799
|
"deprecated": false,
|
|
794
|
-
"description": "Requires the `harness:write` scope. Multipart upload of a png, jpg, webp, or gif (
|
|
800
|
+
"description": "Requires the `harness:write` scope. Multipart upload of a png, jpg, webp, or gif (25 MB), or H.264 MP4 video (100 MB; 8-bit 4:2:0 video, optional AAC audio and mov_text subtitles). No transcoding. Returns data.url, filename, content_type, byte_size, and markdown. Images use ``; videos use ordinary `[filename](url)` links. Supply an optional issue identifier to bind the video to that issue's access policy. Without it, the video is private to its uploader until that user saves its link in an issue description or comment. Copying a bound video link does not change its owning issue. Browser URLs require a session; API clients use GET /api/v1/attachments/{attachment_id}/{file} with a read-scoped token bound to the owning organization. Invalid media answers 422; an inaccessible issue answers 404. Existing image access remains unchanged.",
|
|
795
801
|
"parameters": [],
|
|
796
802
|
"requestBody": {
|
|
797
803
|
"content": {
|
|
@@ -799,9 +805,13 @@
|
|
|
799
805
|
"schema": {
|
|
800
806
|
"properties": {
|
|
801
807
|
"file": {
|
|
802
|
-
"description": "png, jpg, webp, or gif up to 25 MB",
|
|
808
|
+
"description": "png, jpg, webp, or gif up to 25 MB; H.264 MP4 up to 100 MB",
|
|
803
809
|
"format": "binary",
|
|
804
810
|
"type": "string"
|
|
811
|
+
},
|
|
812
|
+
"issue": {
|
|
813
|
+
"description": "Optional owning issue identifier, for example IO-123. Must be accessible in the token's organization.",
|
|
814
|
+
"type": "string"
|
|
805
815
|
}
|
|
806
816
|
},
|
|
807
817
|
"required": [
|
|
@@ -836,7 +846,7 @@
|
|
|
836
846
|
"description": "File storage failed (transient) — retry"
|
|
837
847
|
}
|
|
838
848
|
},
|
|
839
|
-
"summary": "Upload an image attachment",
|
|
849
|
+
"summary": "Upload an image or video attachment",
|
|
840
850
|
"tags": []
|
|
841
851
|
}
|
|
842
852
|
},
|
|
@@ -973,6 +983,9 @@
|
|
|
973
983
|
"type": "string"
|
|
974
984
|
}
|
|
975
985
|
},
|
|
986
|
+
"required": [
|
|
987
|
+
"url"
|
|
988
|
+
],
|
|
976
989
|
"type": "object"
|
|
977
990
|
}
|
|
978
991
|
}
|
|
@@ -1280,6 +1293,361 @@
|
|
|
1280
1293
|
"tags": []
|
|
1281
1294
|
}
|
|
1282
1295
|
},
|
|
1296
|
+
"/api/v1/issues/{identifier}/activity": {
|
|
1297
|
+
"get": {
|
|
1298
|
+
"callbacks": {},
|
|
1299
|
+
"deprecated": false,
|
|
1300
|
+
"description": "Requires the `harness:read` scope. The newest mixed page of comments and issue-state events, returned oldest-first. `has_more` means older entries exist. Pass the opaque `next_cursor` back as `before`; cursors are bound to the issue and viewer and expire after 24 hours.",
|
|
1301
|
+
"parameters": [
|
|
1302
|
+
{
|
|
1303
|
+
"description": "Issue identifier, e.g. ALL-4901 or CLI-12 (issues minted before team-prefixed numbering carry IO-)",
|
|
1304
|
+
"in": "path",
|
|
1305
|
+
"name": "identifier",
|
|
1306
|
+
"required": true,
|
|
1307
|
+
"schema": {
|
|
1308
|
+
"type": "string"
|
|
1309
|
+
}
|
|
1310
|
+
},
|
|
1311
|
+
{
|
|
1312
|
+
"description": "Opaque cursor returned as `meta.next_cursor` by the preceding newer page",
|
|
1313
|
+
"in": "query",
|
|
1314
|
+
"name": "before",
|
|
1315
|
+
"required": false,
|
|
1316
|
+
"schema": {
|
|
1317
|
+
"type": "string"
|
|
1318
|
+
}
|
|
1319
|
+
},
|
|
1320
|
+
{
|
|
1321
|
+
"description": "Entries per page (default 20, max 100)",
|
|
1322
|
+
"in": "query",
|
|
1323
|
+
"name": "limit",
|
|
1324
|
+
"required": false,
|
|
1325
|
+
"schema": {
|
|
1326
|
+
"maximum": 100,
|
|
1327
|
+
"minimum": 1,
|
|
1328
|
+
"type": "integer"
|
|
1329
|
+
}
|
|
1330
|
+
}
|
|
1331
|
+
],
|
|
1332
|
+
"responses": {
|
|
1333
|
+
"200": {
|
|
1334
|
+
"content": {
|
|
1335
|
+
"application/json": {
|
|
1336
|
+
"schema": {
|
|
1337
|
+
"properties": {
|
|
1338
|
+
"data": {
|
|
1339
|
+
"items": {
|
|
1340
|
+
"oneOf": [
|
|
1341
|
+
{
|
|
1342
|
+
"additionalProperties": false,
|
|
1343
|
+
"properties": {
|
|
1344
|
+
"comment": {
|
|
1345
|
+
"properties": {
|
|
1346
|
+
"author": {
|
|
1347
|
+
"nullable": true,
|
|
1348
|
+
"properties": {
|
|
1349
|
+
"email": {
|
|
1350
|
+
"nullable": true,
|
|
1351
|
+
"type": "string"
|
|
1352
|
+
},
|
|
1353
|
+
"id": {
|
|
1354
|
+
"format": "uuid",
|
|
1355
|
+
"nullable": true,
|
|
1356
|
+
"type": "string"
|
|
1357
|
+
},
|
|
1358
|
+
"name": {
|
|
1359
|
+
"nullable": true,
|
|
1360
|
+
"type": "string"
|
|
1361
|
+
}
|
|
1362
|
+
},
|
|
1363
|
+
"required": [
|
|
1364
|
+
"id",
|
|
1365
|
+
"name",
|
|
1366
|
+
"email"
|
|
1367
|
+
],
|
|
1368
|
+
"type": "object"
|
|
1369
|
+
},
|
|
1370
|
+
"body": {
|
|
1371
|
+
"description": "Comment text; null may mean a deleted tombstone or no stored text. Use `deleted` to identify tombstones",
|
|
1372
|
+
"nullable": true,
|
|
1373
|
+
"type": "string"
|
|
1374
|
+
},
|
|
1375
|
+
"deleted": {
|
|
1376
|
+
"description": "Whether the comment is a deleted tombstone",
|
|
1377
|
+
"type": "boolean"
|
|
1378
|
+
},
|
|
1379
|
+
"id": {
|
|
1380
|
+
"format": "uuid",
|
|
1381
|
+
"type": "string"
|
|
1382
|
+
},
|
|
1383
|
+
"inserted_at": {
|
|
1384
|
+
"format": "date-time",
|
|
1385
|
+
"type": "string"
|
|
1386
|
+
},
|
|
1387
|
+
"mentions": {
|
|
1388
|
+
"items": {
|
|
1389
|
+
"properties": {
|
|
1390
|
+
"email": {
|
|
1391
|
+
"nullable": true,
|
|
1392
|
+
"type": "string"
|
|
1393
|
+
},
|
|
1394
|
+
"name": {
|
|
1395
|
+
"nullable": true,
|
|
1396
|
+
"type": "string"
|
|
1397
|
+
},
|
|
1398
|
+
"user_id": {
|
|
1399
|
+
"format": "uuid",
|
|
1400
|
+
"nullable": true,
|
|
1401
|
+
"type": "string"
|
|
1402
|
+
}
|
|
1403
|
+
},
|
|
1404
|
+
"required": [
|
|
1405
|
+
"user_id",
|
|
1406
|
+
"name",
|
|
1407
|
+
"email"
|
|
1408
|
+
],
|
|
1409
|
+
"type": "object"
|
|
1410
|
+
},
|
|
1411
|
+
"type": "array"
|
|
1412
|
+
},
|
|
1413
|
+
"reactions": {
|
|
1414
|
+
"items": {
|
|
1415
|
+
"properties": {
|
|
1416
|
+
"count": {
|
|
1417
|
+
"type": "integer"
|
|
1418
|
+
},
|
|
1419
|
+
"emoji": {
|
|
1420
|
+
"type": "string"
|
|
1421
|
+
},
|
|
1422
|
+
"reacted_by_current_actor": {
|
|
1423
|
+
"type": "boolean"
|
|
1424
|
+
}
|
|
1425
|
+
},
|
|
1426
|
+
"required": [
|
|
1427
|
+
"emoji",
|
|
1428
|
+
"count",
|
|
1429
|
+
"reacted_by_current_actor"
|
|
1430
|
+
],
|
|
1431
|
+
"type": "object"
|
|
1432
|
+
},
|
|
1433
|
+
"type": "array"
|
|
1434
|
+
},
|
|
1435
|
+
"updated_at": {
|
|
1436
|
+
"format": "date-time",
|
|
1437
|
+
"type": "string"
|
|
1438
|
+
}
|
|
1439
|
+
},
|
|
1440
|
+
"required": [
|
|
1441
|
+
"id",
|
|
1442
|
+
"body",
|
|
1443
|
+
"deleted",
|
|
1444
|
+
"author",
|
|
1445
|
+
"mentions",
|
|
1446
|
+
"reactions",
|
|
1447
|
+
"inserted_at",
|
|
1448
|
+
"updated_at"
|
|
1449
|
+
],
|
|
1450
|
+
"type": "object"
|
|
1451
|
+
},
|
|
1452
|
+
"id": {
|
|
1453
|
+
"type": "string"
|
|
1454
|
+
},
|
|
1455
|
+
"kind": {
|
|
1456
|
+
"enum": [
|
|
1457
|
+
"comment"
|
|
1458
|
+
],
|
|
1459
|
+
"type": "string"
|
|
1460
|
+
},
|
|
1461
|
+
"occurred_at": {
|
|
1462
|
+
"format": "date-time",
|
|
1463
|
+
"type": "string"
|
|
1464
|
+
}
|
|
1465
|
+
},
|
|
1466
|
+
"required": [
|
|
1467
|
+
"id",
|
|
1468
|
+
"kind",
|
|
1469
|
+
"occurred_at",
|
|
1470
|
+
"comment"
|
|
1471
|
+
],
|
|
1472
|
+
"type": "object"
|
|
1473
|
+
},
|
|
1474
|
+
{
|
|
1475
|
+
"additionalProperties": false,
|
|
1476
|
+
"properties": {
|
|
1477
|
+
"action": {
|
|
1478
|
+
"enum": [
|
|
1479
|
+
"created",
|
|
1480
|
+
"milestone",
|
|
1481
|
+
"transition"
|
|
1482
|
+
],
|
|
1483
|
+
"type": "string"
|
|
1484
|
+
},
|
|
1485
|
+
"actor": {
|
|
1486
|
+
"properties": {
|
|
1487
|
+
"kind": {
|
|
1488
|
+
"enum": [
|
|
1489
|
+
"system",
|
|
1490
|
+
"user",
|
|
1491
|
+
"deleted_user",
|
|
1492
|
+
"unknown"
|
|
1493
|
+
],
|
|
1494
|
+
"type": "string"
|
|
1495
|
+
},
|
|
1496
|
+
"name": {
|
|
1497
|
+
"nullable": true,
|
|
1498
|
+
"type": "string"
|
|
1499
|
+
}
|
|
1500
|
+
},
|
|
1501
|
+
"required": [
|
|
1502
|
+
"kind",
|
|
1503
|
+
"name"
|
|
1504
|
+
],
|
|
1505
|
+
"type": "object"
|
|
1506
|
+
},
|
|
1507
|
+
"from_state": {
|
|
1508
|
+
"nullable": true,
|
|
1509
|
+
"properties": {
|
|
1510
|
+
"key": {
|
|
1511
|
+
"nullable": true,
|
|
1512
|
+
"type": "string"
|
|
1513
|
+
},
|
|
1514
|
+
"name": {
|
|
1515
|
+
"nullable": true,
|
|
1516
|
+
"type": "string"
|
|
1517
|
+
},
|
|
1518
|
+
"type": {
|
|
1519
|
+
"nullable": true,
|
|
1520
|
+
"type": "string"
|
|
1521
|
+
}
|
|
1522
|
+
},
|
|
1523
|
+
"required": [
|
|
1524
|
+
"key",
|
|
1525
|
+
"name",
|
|
1526
|
+
"type"
|
|
1527
|
+
],
|
|
1528
|
+
"type": "object"
|
|
1529
|
+
},
|
|
1530
|
+
"id": {
|
|
1531
|
+
"type": "string"
|
|
1532
|
+
},
|
|
1533
|
+
"kind": {
|
|
1534
|
+
"enum": [
|
|
1535
|
+
"state_event"
|
|
1536
|
+
],
|
|
1537
|
+
"type": "string"
|
|
1538
|
+
},
|
|
1539
|
+
"occurred_at": {
|
|
1540
|
+
"format": "date-time",
|
|
1541
|
+
"type": "string"
|
|
1542
|
+
},
|
|
1543
|
+
"provenance": {
|
|
1544
|
+
"properties": {
|
|
1545
|
+
"kind": {
|
|
1546
|
+
"enum": [
|
|
1547
|
+
"live",
|
|
1548
|
+
"import",
|
|
1549
|
+
"backfill"
|
|
1550
|
+
],
|
|
1551
|
+
"type": "string"
|
|
1552
|
+
},
|
|
1553
|
+
"witnessed": {
|
|
1554
|
+
"type": "boolean"
|
|
1555
|
+
}
|
|
1556
|
+
},
|
|
1557
|
+
"required": [
|
|
1558
|
+
"kind",
|
|
1559
|
+
"witnessed"
|
|
1560
|
+
],
|
|
1561
|
+
"type": "object"
|
|
1562
|
+
},
|
|
1563
|
+
"to_state": {
|
|
1564
|
+
"properties": {
|
|
1565
|
+
"key": {
|
|
1566
|
+
"nullable": true,
|
|
1567
|
+
"type": "string"
|
|
1568
|
+
},
|
|
1569
|
+
"name": {
|
|
1570
|
+
"nullable": true,
|
|
1571
|
+
"type": "string"
|
|
1572
|
+
},
|
|
1573
|
+
"type": {
|
|
1574
|
+
"nullable": true,
|
|
1575
|
+
"type": "string"
|
|
1576
|
+
}
|
|
1577
|
+
},
|
|
1578
|
+
"required": [
|
|
1579
|
+
"key",
|
|
1580
|
+
"name",
|
|
1581
|
+
"type"
|
|
1582
|
+
],
|
|
1583
|
+
"type": "object"
|
|
1584
|
+
}
|
|
1585
|
+
},
|
|
1586
|
+
"required": [
|
|
1587
|
+
"id",
|
|
1588
|
+
"kind",
|
|
1589
|
+
"occurred_at",
|
|
1590
|
+
"action",
|
|
1591
|
+
"actor",
|
|
1592
|
+
"provenance",
|
|
1593
|
+
"from_state",
|
|
1594
|
+
"to_state"
|
|
1595
|
+
],
|
|
1596
|
+
"type": "object"
|
|
1597
|
+
}
|
|
1598
|
+
]
|
|
1599
|
+
},
|
|
1600
|
+
"type": "array"
|
|
1601
|
+
},
|
|
1602
|
+
"meta": {
|
|
1603
|
+
"properties": {
|
|
1604
|
+
"has_more": {
|
|
1605
|
+
"description": "Whether older entries exist",
|
|
1606
|
+
"type": "boolean"
|
|
1607
|
+
},
|
|
1608
|
+
"next_cursor": {
|
|
1609
|
+
"description": "Opaque cursor to pass as `before`; null when no older entries exist",
|
|
1610
|
+
"nullable": true,
|
|
1611
|
+
"type": "string"
|
|
1612
|
+
}
|
|
1613
|
+
},
|
|
1614
|
+
"required": [
|
|
1615
|
+
"has_more",
|
|
1616
|
+
"next_cursor"
|
|
1617
|
+
],
|
|
1618
|
+
"type": "object"
|
|
1619
|
+
}
|
|
1620
|
+
},
|
|
1621
|
+
"required": [
|
|
1622
|
+
"data",
|
|
1623
|
+
"meta"
|
|
1624
|
+
],
|
|
1625
|
+
"type": "object"
|
|
1626
|
+
}
|
|
1627
|
+
}
|
|
1628
|
+
},
|
|
1629
|
+
"description": "Success"
|
|
1630
|
+
},
|
|
1631
|
+
"400": {
|
|
1632
|
+
"description": "Invalid input. Error code is `invalid_cursor` for an invalid or expired cursor, or `bad_request` for a malformed `before` value or invalid limit."
|
|
1633
|
+
},
|
|
1634
|
+
"401": {
|
|
1635
|
+
"description": "Missing or invalid token"
|
|
1636
|
+
},
|
|
1637
|
+
"403": {
|
|
1638
|
+
"description": "Token not permitted (org-scoped, non-admin, or missing scope)"
|
|
1639
|
+
},
|
|
1640
|
+
"404": {
|
|
1641
|
+
"description": "Not found"
|
|
1642
|
+
},
|
|
1643
|
+
"422": {
|
|
1644
|
+
"description": "Validation error"
|
|
1645
|
+
}
|
|
1646
|
+
},
|
|
1647
|
+
"summary": "List issue activity",
|
|
1648
|
+
"tags": []
|
|
1649
|
+
}
|
|
1650
|
+
},
|
|
1283
1651
|
"/api/v1/users": {
|
|
1284
1652
|
"get": {
|
|
1285
1653
|
"callbacks": {},
|
|
@@ -2479,9 +2847,12 @@
|
|
|
2479
2847
|
"type": "object"
|
|
2480
2848
|
},
|
|
2481
2849
|
"body": {
|
|
2850
|
+
"description": "Comment text; null may mean a deleted tombstone or no stored text. Use `deleted` to identify tombstones",
|
|
2851
|
+
"nullable": true,
|
|
2482
2852
|
"type": "string"
|
|
2483
2853
|
},
|
|
2484
2854
|
"deleted": {
|
|
2855
|
+
"description": "Whether the comment is a deleted tombstone",
|
|
2485
2856
|
"type": "boolean"
|
|
2486
2857
|
},
|
|
2487
2858
|
"id": {
|