@allocator-one/harness-cli 1.16.0 → 1.17.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.
@@ -1,1039 +1,40 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
- CLI_PACKAGE,
3
+ Client,
4
+ CommandError,
5
+ EXPERT_ASSIGNMENTS_PATH,
6
+ HttpError,
7
+ LocalError,
8
+ MalformedResponseError,
9
+ TransportError,
10
+ UpgradeRequiredError,
11
+ UsageError,
12
+ activityEntries,
13
+ clean,
14
+ cleanMultiline,
15
+ conditionLine,
16
+ errorDetails,
17
+ expertAssignments,
18
+ expertSummary,
19
+ explainExpertRefusal,
20
+ gateName,
21
+ graphemeLength,
22
+ isAgentStep,
23
+ isExpertToken,
24
+ issueDetail,
25
+ issuesTable,
26
+ ownerText,
27
+ planDetail,
28
+ plansTable,
29
+ stats,
30
+ table,
31
+ truncate
32
+ } from "./chunk-BHBEIT5I.js";
33
+ import {
4
34
  CLI_VERSION,
5
35
  invokedName,
6
36
  named
7
- } from "./chunk-FQWV5MIC.js";
8
-
9
- // src/core/errors.ts
10
- var HttpError = class extends Error {
11
- constructor(status, message, body = {}, retryAfter = null) {
12
- super(message);
13
- this.status = status;
14
- this.body = body;
15
- this.retryAfter = retryAfter;
16
- this.name = "HttpError";
17
- }
18
- status;
19
- body;
20
- retryAfter;
21
- };
22
- var UpgradeRequiredError = class extends HttpError {
23
- constructor(message, body = {}) {
24
- super(426, message, body);
25
- this.name = "UpgradeRequiredError";
26
- }
27
- };
28
- var TransportError = class extends Error {
29
- constructor(message, code = null) {
30
- super(message);
31
- this.code = code;
32
- this.name = "TransportError";
33
- }
34
- code;
35
- };
36
- var MalformedResponseError = class extends TransportError {
37
- constructor(message) {
38
- super(message);
39
- this.name = "MalformedResponseError";
40
- }
41
- };
42
- var UsageError = class extends Error {
43
- constructor(message) {
44
- super(message);
45
- this.name = "UsageError";
46
- }
47
- };
48
- var LocalError = class extends Error {
49
- constructor(message) {
50
- super(message);
51
- this.name = "LocalError";
52
- }
53
- };
54
- var CommandError = class extends Error {
55
- constructor(message) {
56
- super(message);
57
- this.name = "CommandError";
58
- }
59
- };
60
-
61
- // src/core/expert.ts
62
- var EXPERT_TOKEN_PREFIX = "pxc_";
63
- var EXPERT_ASSIGNMENTS_PATH = "/api/v1/expert/assignments";
64
- function isExpertToken(token) {
65
- return token.startsWith(EXPERT_TOKEN_PREFIX);
66
- }
67
- var EXPERT_REACH = "an outside expert's pxc_ credential only lists its own assignments and reads and ticks the gates it owns on them: assignments, conditions <issue> and gate satisfy|unsatisfy (assignment_list, condition_list and gate_set over MCP)";
68
- var EXPERT_LAPSED = "the pxc_ credential is not valid here: it has expired (it lasts at most 30 days) or was revoked, its seat was revoked or has ended, the account is suspended, its TOTP enrolment changed, or it was minted for another host. If your seat is still open, mint a new one in your expert view";
69
- var NOT_AN_EXPERT = "this credential is a member's login or an API token, not an outside expert's pxc_ credential";
70
- function explainExpertRefusal(error, token) {
71
- if (!(error instanceof HttpError) || error instanceof UpgradeRequiredError) return error;
72
- const code = error.body["error"];
73
- let hint;
74
- if (error.status === 403 && code === "expert_bound_credential") hint = EXPERT_REACH;
75
- else if (error.status === 403 && code === "expert_credential_required") hint = NOT_AN_EXPERT;
76
- else if (error.status === 401 && isExpertToken(token)) hint = EXPERT_LAPSED;
77
- if (hint === void 0) return error;
78
- return new HttpError(error.status, `${error.message} \u2014 ${hint}`, error.body, error.retryAfter);
79
- }
80
-
81
- // src/core/client.ts
82
- var RECEIVE_TIMEOUT_MS = 3e4;
83
- var UPLOAD_RECEIVE_TIMEOUT_MS = 6e4;
84
- var UPLOAD_FLOOR_BYTES_PER_MS = 100;
85
- function uploadTimeoutMs(bodyBytes) {
86
- return UPLOAD_RECEIVE_TIMEOUT_MS + Math.ceil(bodyBytes / UPLOAD_FLOOR_BYTES_PER_MS);
87
- }
88
- var DEFAULT_MESSAGES = {
89
- 401: "Missing or invalid token (check HARNESS_API_TOKEN)",
90
- 403: "Token not permitted (scope or admin gate)",
91
- 404: "Not found",
92
- 409: "Conflict",
93
- 422: "Validation error",
94
- 426: `CLI too old for this API \u2014 upgrade: npm i -g ${CLI_PACKAGE}@latest`,
95
- 429: "Rate limited \u2014 retry later"
96
- };
97
- var API_VERSION_HEADER = "x-harness-api-version";
98
- var STATS_PATH = "/api/v1/stats";
99
- var SESSION_PATH = "/api/v1/session";
100
- function isLoginCredential(token) {
101
- return !token.startsWith("aone_") && !isExpertToken(token);
102
- }
103
- var Client = class {
104
- constructor(config) {
105
- this.config = config;
106
- this.fetchImpl = config.fetchImpl ?? globalThis.fetch;
107
- }
108
- config;
109
- fetchImpl;
110
- get cliVersion() {
111
- return CLI_VERSION;
112
- }
113
- get baseUrl() {
114
- return this.config.baseUrl;
115
- }
116
- async get(path2, params) {
117
- return this.json(await this.raw("GET", path2, { params }));
118
- }
119
- /**
120
- * `actor: false` keeps a configured actor out of the body, for endpoints
121
- * whose body is closed: they refuse a field they do not name, and record
122
- * the credential's own person instead.
123
- */
124
- async post(path2, body = {}, options = {}) {
125
- return this.json(
126
- await this.raw("POST", path2, {
127
- json: options.actor === false ? body : this.withActor(body),
128
- idempotencyKey: options.idempotencyKey
129
- })
130
- );
131
- }
132
- async patch(path2, body) {
133
- return this.json(await this.raw("PATCH", path2, { json: this.withActor(body) }));
134
- }
135
- async put(path2, body) {
136
- return this.json(await this.raw("PUT", path2, { json: this.withActor(body) }));
137
- }
138
- async delete(path2) {
139
- return this.json(await this.raw("DELETE", path2, {}));
140
- }
141
- /**
142
- * Posts multipart/form-data — the one endpoint that does not take JSON.
143
- * Shares auth, the version header, and error mapping with the JSON verbs,
144
- * but never the actor: there is no JSON body to carry an `actor` field,
145
- * and the server's file contract is the form itself.
146
- */
147
- async postMultipart(path2, form, bodyBytes = 0) {
148
- return this.json(await this.raw("POST", path2, { form, timeoutMs: uploadTimeoutMs(bodyBytes) }));
149
- }
150
- /** Binary download (attachments); non-2xx still maps through the JSON error envelope. */
151
- async getBytes(path2) {
152
- const response = await this.raw("GET", path2, {});
153
- if (!response.ok) throw await this.toHttpError(response);
154
- try {
155
- return new Uint8Array(await response.arrayBuffer());
156
- } catch (error) {
157
- throw new TransportError(transportMessage(error), transportCode(error));
158
- }
159
- }
160
- /**
161
- * A GET that also hands back the server's API version header, so a probe
162
- * that verifies a credential reports the version without a second
163
- * request. Errors map as for `get`.
164
- */
165
- async getVersioned(path2) {
166
- const response = await this.raw("GET", path2, {});
167
- const apiVersion = response.headers.get(API_VERSION_HEADER);
168
- return { envelope: await this.json(response), apiVersion };
169
- }
170
- /**
171
- * The server's API version, for `version`'s server side. The header rides
172
- * on every response of a route the server has — the versioning plug runs
173
- * before auth — so even a 401 answers. Each credential probes a route it
174
- * reaches, so the probe is never a refusal in the server's log: a pxc_
175
- * credential its assignments, a login credential of either kind (the
176
- * Operator-only one reaches no tracker read) GET /api/v1/session, and an
177
- * aone_ token the stats. A server older than GET /api/v1/session answers
178
- * that route's 404 without the header, so the stats answer then.
179
- */
180
- async serverVersion() {
181
- const token = this.config.token;
182
- const path2 = isExpertToken(token) ? EXPERT_ASSIGNMENTS_PATH : isLoginCredential(token) ? SESSION_PATH : STATS_PATH;
183
- const version2 = (await this.raw("GET", path2, {})).headers.get(API_VERSION_HEADER);
184
- if (version2 !== null || path2 !== SESSION_PATH) return version2 ?? "unknown";
185
- return (await this.raw("GET", STATS_PATH, {})).headers.get(API_VERSION_HEADER) ?? "unknown";
186
- }
187
- // Only an explicitly configured actor is sent, and never over one the
188
- // caller already put in the body.
189
- withActor(body) {
190
- if (this.config.actor === null || "actor" in body) return body;
191
- return { ...body, actor: this.config.actor };
192
- }
193
- async raw(method, path2, options) {
194
- const url = new URL(this.config.baseUrl + path2);
195
- for (const [key, value] of Object.entries(options.params ?? {})) {
196
- if (value !== void 0) url.searchParams.set(key, String(value));
197
- }
198
- const headers = {
199
- authorization: `Bearer ${this.config.token}`,
200
- "x-harness-cli-version": CLI_VERSION
201
- };
202
- if (options.idempotencyKey !== void 0) {
203
- headers["idempotency-key"] = options.idempotencyKey;
204
- }
205
- let body;
206
- if (options.json !== void 0) {
207
- headers["content-type"] = "application/json";
208
- body = JSON.stringify(options.json);
209
- } else if (options.form !== void 0) {
210
- body = options.form;
211
- }
212
- try {
213
- return await this.fetchImpl(url, {
214
- method,
215
- headers,
216
- body,
217
- signal: AbortSignal.timeout(options.timeoutMs ?? RECEIVE_TIMEOUT_MS),
218
- redirect: "manual"
219
- });
220
- } catch (error) {
221
- throw new TransportError(transportMessage(error), transportCode(error));
222
- }
223
- }
224
- async json(response) {
225
- if (!response.ok) throw await this.toHttpError(response);
226
- let text;
227
- try {
228
- text = await response.text();
229
- } catch (error) {
230
- throw new TransportError(transportMessage(error), transportCode(error));
231
- }
232
- if (text === "") return { data: void 0 };
233
- let parsed;
234
- try {
235
- parsed = JSON.parse(text);
236
- } catch {
237
- throw new MalformedResponseError(`the API answered with a non-JSON body (HTTP ${response.status})`);
238
- }
239
- if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
240
- throw new MalformedResponseError(`the API answered with an invalid JSON envelope (HTTP ${response.status})`);
241
- }
242
- return parsed;
243
- }
244
- async toHttpError(response) {
245
- let parsed = {};
246
- try {
247
- const candidate = JSON.parse(await response.text());
248
- if (typeof candidate === "object" && candidate !== null) {
249
- parsed = candidate;
250
- }
251
- } catch {
252
- }
253
- const fromBody = typeof parsed["message"] === "string" ? parsed["message"] : null;
254
- const message = fromBody ?? DEFAULT_MESSAGES[response.status] ?? `HTTP ${response.status}`;
255
- if (response.status === 426) return new UpgradeRequiredError(message, parsed);
256
- return new HttpError(response.status, message, parsed, response.headers.get("retry-after"));
257
- }
258
- };
259
- function transportCode(error) {
260
- const cause = error instanceof Error ? error.cause : void 0;
261
- if (typeof cause !== "object" || cause === null) return null;
262
- const code = cause.code;
263
- if (typeof code === "string") return code;
264
- const errors = cause.errors;
265
- if (!Array.isArray(errors)) return null;
266
- const codes = new Set(errors.map((inner) => inner?.code));
267
- const [only] = codes;
268
- return codes.size === 1 && typeof only === "string" ? only : null;
269
- }
270
- function transportMessage(error) {
271
- if (error instanceof Error) {
272
- if (error.name === "TimeoutError" || error.name === "AbortError") return "request timed out";
273
- const cause = error.cause;
274
- if (cause instanceof Error && cause.message !== "") return cause.message;
275
- return error.message;
276
- }
277
- return String(error);
278
- }
279
-
280
- // src/core/output.ts
281
- var TITLE_WIDTH = 70;
282
- var segmenter = new Intl.Segmenter(void 0, { granularity: "grapheme" });
283
- function graphemes(text) {
284
- return [...segmenter.segment(text)].map((piece) => piece.segment);
285
- }
286
- function graphemeLength(text) {
287
- return graphemes(text).length;
288
- }
289
- function padGraphemes(text, width) {
290
- const shortfall = width - graphemeLength(text);
291
- return shortfall > 0 ? text + " ".repeat(shortfall) : text;
292
- }
293
- function clean(value) {
294
- if (value === null || value === void 0) return "";
295
- return String(value).replace(/[\x00-\x1F\x7F\u0080-\u009F]/gu, "");
296
- }
297
- function json(term) {
298
- return JSON.stringify(term) + "\n";
299
- }
300
- var OPERATOR_TEAM_KEY = /^OPERATOR\d*$/;
301
- var OPERATOR_TEAM_NAME = "Operator";
302
- var AGENT_MARK = "[agent]";
303
- var AGENT_LEGEND = `
304
- ${AGENT_MARK} Operator runs this step for its plan; only the plan driver claims it.
305
- `;
306
- function isAgentStep(issue) {
307
- return OPERATOR_TEAM_KEY.test(str(path(issue, "team", "key"))) && str(path(issue, "team", "name")) === OPERATOR_TEAM_NAME;
308
- }
309
- function issuesTable(issues, options = {}) {
310
- if (issues.length === 0) return "No issues.\n";
311
- let marked = false;
312
- const rows = issues.map((issue) => {
313
- const agentStep = options.markAgentSteps === true && isAgentStep(issue);
314
- if (agentStep) marked = true;
315
- return [
316
- str(issue["identifier"]),
317
- str(path(issue, "state", "name")) || "-",
318
- str(issue["priority_label"]) || "-",
319
- str(path(issue, "assignee", "name")) || "-",
320
- agentStep ? truncate(`${AGENT_MARK} ${clean(issue["title"])}`) : truncate(issue["title"])
321
- ];
322
- });
323
- const rendered = table(["ID", "STATE", "PRIORITY", "ASSIGNEE", "TITLE"], rows);
324
- return marked ? rendered + AGENT_LEGEND : rendered;
325
- }
326
- function issueDetail(issue, images = []) {
327
- const header = `${str(issue["identifier"])} \xB7 ${clean(issue["title"])}
328
- `;
329
- const fields = [
330
- ["State", path(issue, "state", "name")],
331
- ["Priority", issue["priority_label"]],
332
- ["Assignee", path(issue, "assignee", "name")],
333
- ["Team", path(issue, "team", "key")],
334
- ["Plan", issuePlanLine(issue["plan"])],
335
- ["Labels", labelsLine(issue["labels"])],
336
- ["Blocked", blockedLine(issue)],
337
- ["Waiting", waitingLine(issue)],
338
- ["Due", issue["due_date"]],
339
- ["Updated", issue["updated_at"]]
340
- ];
341
- const fieldLines = fields.filter(([, value]) => value !== null && value !== void 0 && value !== "").map(([label, value]) => ` ${(label + ":").padEnd(12)}${clean(value)}
342
- `).join("");
343
- const sections = [
344
- ["Description", issue["description"]],
345
- ["Design", issue["design"]],
346
- ["Acceptance criteria", issue["acceptance_criteria"]],
347
- ["Notes", issue["notes"]]
348
- ];
349
- const sectionText = sections.filter(([, text]) => text !== null && text !== void 0 && text !== "").map(([title, text]) => `
350
- ${title}:
351
- ${indent(String(text))}
352
- `).join("");
353
- return header + fieldLines + sectionText + conditionsSection(issue["conditions"]) + relationsSection(issue["relations"]) + childrenSection(issue["children"]) + commentsSection(issue["comments"]) + imagesSection(images);
354
- }
355
- function activityEntries(entries) {
356
- if (entries.length === 0) return "No activity.\n";
357
- return entries.map(activityEntry).join("\n");
358
- }
359
- function activityEntry(entry) {
360
- switch (entry["kind"]) {
361
- case "comment":
362
- return activityComment(entry);
363
- case "state_event":
364
- return activityStateEvent(entry);
365
- case "assignee_event":
366
- return activityAssigneeEvent(entry);
367
- case "condition_event":
368
- return activityConditionEvent(entry);
369
- case "field_event":
370
- return activityFieldEvent(entry);
371
- default:
372
- throw new CommandError(`Unknown activity entry kind: ${clean(entry["kind"])}`);
373
- }
374
- }
375
- function activityComment(entry) {
376
- const comment2 = requiredRecord(entry["comment"], "activity comment");
377
- const authorValue = comment2["author"];
378
- const author = authorValue === null ? "unknown" : optionalLabel(requiredRecord(authorValue, "comment author")["name"], "unknown", "comment author name");
379
- const deleted = comment2["deleted"];
380
- if (typeof deleted !== "boolean") throw new CommandError("Invalid comment deleted flag.");
381
- const body = activityCommentBody(comment2["body"], deleted);
382
- const text = cleanMultiline(body).split("\n").map((line) => " " + line).join("\n");
383
- return `[${occurredAt(entry)}] ${clean(author)} (${clean(requiredString(comment2["id"], "comment id"))}):
384
- ${text}
385
- `;
386
- }
387
- function activityCommentBody(value, deleted) {
388
- if (deleted) return "[deleted comment]";
389
- if (value === null || value === "") return "[comment has no text]";
390
- return requiredString(value, "comment body");
391
- }
392
- function activityStateEvent(entry) {
393
- const actor = activityWho(entry);
394
- const provenance = activityProvenance(requiredRecord(entry["provenance"], "activity provenance"));
395
- let description;
396
- switch (entry["action"]) {
397
- case "created":
398
- description = `${actor} created the issue in ${stateName(entry["to_state"], "created to_state")}`;
399
- break;
400
- case "transition":
401
- description = `${actor} moved the issue from ${nullableStateName(entry["from_state"], "transition from_state")} to ${stateName(entry["to_state"], "transition to_state")}`;
402
- break;
403
- case "milestone":
404
- description = `${actor} recorded the ${stateName(entry["to_state"], "milestone to_state")} milestone`;
405
- break;
406
- default:
407
- throw new CommandError(`Unknown activity action: ${clean(entry["action"])}`);
408
- }
409
- return `[${occurredAt(entry)}] ${description} \xB7 ${provenance}
410
- `;
411
- }
412
- function activityAssigneeEvent(entry) {
413
- const actor = activityWho(entry);
414
- const provenance = activityProvenance(requiredRecord(entry["provenance"], "activity provenance"));
415
- let description;
416
- switch (entry["action"]) {
417
- case "assigned":
418
- description = `${actor} assigned the issue to ${assigneeName(entry["to_assignee"])}`;
419
- break;
420
- case "reassigned":
421
- description = `${actor} reassigned the issue from ${assigneeName(entry["from_assignee"])} to ${assigneeName(entry["to_assignee"])}`;
422
- break;
423
- case "unassigned":
424
- description = `${actor} unassigned the issue from ${assigneeName(entry["from_assignee"])}`;
425
- break;
426
- default:
427
- throw new CommandError(`Unknown activity action: ${clean(entry["action"])}`);
428
- }
429
- return `[${occurredAt(entry)}] ${description} - ${provenance}
430
- `;
431
- }
432
- function activityConditionEvent(entry) {
433
- const actor = activityWho(entry);
434
- const provenance = activityProvenance(requiredRecord(entry["provenance"], "activity provenance"));
435
- const condition = conditionSubject(requiredRecord(entry["condition"], "activity condition"));
436
- let description;
437
- switch (entry["action"]) {
438
- case "added":
439
- description = `${actor} added ${condition}`;
440
- break;
441
- case "changed":
442
- description = `${actor} changed ${condition}`;
443
- break;
444
- case "removed":
445
- description = `${actor} removed ${condition}`;
446
- break;
447
- case "satisfied":
448
- description = `${actor} satisfied ${condition}`;
449
- break;
450
- case "unsatisfied":
451
- description = `${actor} marked ${condition} unsatisfied`;
452
- break;
453
- default:
454
- throw new CommandError(`Unknown activity action: ${clean(entry["action"])}`);
455
- }
456
- return `[${occurredAt(entry)}] ${description} \xB7 ${provenance}
457
- `;
458
- }
459
- function conditionSubject(condition) {
460
- const label = clean(condition["label"]);
461
- switch (condition["kind"]) {
462
- case "gate":
463
- return label === "" ? "the gate" : `the gate "${label}"`;
464
- case "not_before": {
465
- const at = clean(condition["not_before"]);
466
- return at === "" ? "the not-before date" : `the not-before date ${at}`;
467
- }
468
- case "event":
469
- return label === "" ? "the wait condition" : `the system event "${label}"`;
470
- default:
471
- return "the wait condition";
472
- }
473
- }
474
- function activityFieldEvent(entry) {
475
- const actor = activityWho(entry);
476
- const provenance = activityProvenance(requiredRecord(entry["provenance"], "activity provenance"));
477
- const change = fieldChange(requiredString(entry["field"], "activity field"), entry["from"], entry["to"]);
478
- return `[${occurredAt(entry)}] ${actor} ${change} \xB7 ${provenance}
479
- `;
480
- }
481
- function fieldChange(field3, from, to) {
482
- switch (field3) {
483
- case "priority":
484
- return `changed priority from ${priorityName(from)} to ${priorityName(to)}`;
485
- case "title":
486
- return `renamed the issue from \u201C${activityTitle(from)}\u201D to \u201C${activityTitle(to)}\u201D`;
487
- case "description":
488
- return "edited the description";
489
- case "labels":
490
- return labelsChange(to);
491
- case "parent":
492
- return parentChange(from, to);
493
- case "archived":
494
- if (typeof to !== "boolean") throw new CommandError("Invalid activity archived value.");
495
- return to ? "archived the issue" : "unarchived the issue";
496
- default:
497
- return `changed the ${clean(field3)} field`;
498
- }
499
- }
500
- var ACTIVITY_PRIORITIES = {
501
- 0: "No priority",
502
- 1: "Urgent",
503
- 2: "High",
504
- 3: "Medium",
505
- 4: "Low"
506
- };
507
- function priorityName(value) {
508
- if (typeof value !== "number" || !Number.isInteger(value)) throw new CommandError("Invalid activity priority.");
509
- return ACTIVITY_PRIORITIES[value] ?? String(value);
510
- }
511
- function activityTitle(value) {
512
- if (typeof value !== "string") throw new CommandError("Invalid activity title.");
513
- return clean(value);
514
- }
515
- function labelsChange(to) {
516
- const change = requiredRecord(to, "activity labels");
517
- const added = labelNames(change["added"]);
518
- const removed = labelNames(change["removed"]);
519
- const parts = [
520
- ...added.length > 0 ? [`added ${labelList(added)}`] : [],
521
- ...removed.length > 0 ? [`removed ${labelList(removed)}`] : []
522
- ];
523
- return parts.length === 0 ? "changed the labels" : parts.join(" and ");
524
- }
525
- function labelNames(value) {
526
- if (!Array.isArray(value)) throw new CommandError("Invalid activity labels.");
527
- return value.map((label) => clean(requiredString(requiredRecord(label, "activity labels")["name"], "activity labels")));
528
- }
529
- function labelList(names) {
530
- return `${names.length === 1 ? "label" : "labels"} ${names.join(", ")}`;
531
- }
532
- function parentChange(from, to) {
533
- if (from === null && to === null) return "changed the parent";
534
- if (from === null) return `set the parent to ${parentName(to)}`;
535
- if (to === null) return `removed the parent ${parentName(from)}`;
536
- return `changed the parent from ${parentName(from)} to ${parentName(to)}`;
537
- }
538
- function parentName(value) {
539
- const parent = requiredRecord(value, "activity parent");
540
- return optionalLabel(parent["identifier"], "an unknown issue", "activity parent identifier");
541
- }
542
- function activityWho(entry) {
543
- const actor = activityActor(requiredRecord(entry["actor"], "activity actor"));
544
- const label = entry["actor_label"];
545
- if (label === void 0 || label === null || label === "") return actor;
546
- if (typeof label !== "string") throw new CommandError("Invalid activity actor label.");
547
- const cleaned = clean(label);
548
- return cleaned === "" ? actor : `${actor} via \u201C${cleaned}\u201D`;
549
- }
550
- function occurredAt(entry) {
551
- return clean(requiredString(entry["occurred_at"], "activity occurrence time"));
552
- }
553
- function assigneeName(value) {
554
- const assignee = requiredRecord(value, "activity assignee");
555
- if (assignee["kind"] === "unknown") return "Unknown assignee";
556
- return activityActor(assignee);
557
- }
558
- function activityActor(actor) {
559
- switch (actor["kind"]) {
560
- case "user":
561
- return optionalLabel(actor["name"], "Unknown actor", "activity actor name");
562
- case "system":
563
- return "System";
564
- case "deleted_user":
565
- return "Deleted user";
566
- case "unknown":
567
- return "Unknown actor";
568
- default:
569
- throw new CommandError(`Unknown activity actor kind: ${clean(actor["kind"])}`);
570
- }
571
- }
572
- function activityProvenance(provenance) {
573
- switch (provenance["kind"]) {
574
- case "live":
575
- case "import":
576
- case "backfill":
577
- return String(provenance["kind"]);
578
- default:
579
- throw new CommandError(`Unknown activity provenance: ${clean(provenance["kind"])}`);
580
- }
581
- }
582
- function stateName(value, field3) {
583
- return optionalLabel(requiredRecord(value, field3)["name"], "Unknown state", `${field3} name`);
584
- }
585
- function nullableStateName(value, field3) {
586
- return value === null ? "Unknown state" : stateName(value, field3);
587
- }
588
- function optionalLabel(value, fallback, field3) {
589
- if (value === null || value === "") return fallback;
590
- return clean(requiredString(value, field3));
591
- }
592
- function requiredRecord(value, field3) {
593
- if (typeof value !== "object" || value === null || Array.isArray(value)) {
594
- throw new CommandError(`Invalid ${field3}.`);
595
- }
596
- return value;
597
- }
598
- function requiredString(value, field3) {
599
- if (typeof value !== "string" || value === "") throw new CommandError(`Invalid ${field3}.`);
600
- return value;
601
- }
602
- function stats(data) {
603
- const teams = data["teams"] ?? [];
604
- const totals = data["totals"] ?? {};
605
- const rows = teams.map((team) => [
606
- str(path(team, "team", "key")),
607
- str(team["open"]),
608
- str(team["started"]),
609
- str(team["blocked"]),
610
- str(team["ready"]),
611
- str(team["closed"])
612
- ]);
613
- const totalsRow = ["TOTAL", ...["open", "started", "blocked", "ready", "closed"].map((key) => str(totals[key]))];
614
- return table(["TEAM", "OPEN", "STARTED", "BLOCKED", "READY", "CLOSED"], [...rows, totalsRow]);
615
- }
616
- function table(headers, rows) {
617
- const all = [headers, ...rows.map((row) => row.map((cell) => clean(cell)))];
618
- const widths = headers.map((_, column) => Math.max(...all.map((row) => graphemeLength(row[column] ?? ""))));
619
- return all.map(
620
- (row) => row.map((cell, column) => padGraphemes(cell, widths[column] ?? 0)).join(" ").replace(/\s+$/, "") + "\n"
621
- ).join("");
622
- }
623
- function records(value) {
624
- if (!Array.isArray(value)) return [];
625
- return value.filter(
626
- (entry) => typeof entry === "object" && entry !== null && !Array.isArray(entry)
627
- );
628
- }
629
- function waitingLine(issue) {
630
- if (issue["waiting_on_condition"] !== true) return null;
631
- const conditions = records(issue["conditions"]);
632
- const open = conditions.filter((condition) => condition["satisfied"] !== true).length;
633
- return `yes, on ${open} of ${conditions.length} wait condition${conditions.length === 1 ? "" : "s"}`;
634
- }
635
- function conditionsSection(conditions) {
636
- const list3 = records(conditions);
637
- if (list3.length === 0) return "";
638
- return "\nWait conditions:\n" + list3.map((condition) => ` ${conditionLine(condition)}
639
- `).join("");
640
- }
641
- function conditionLine(condition, options = {}) {
642
- const satisfied = condition["satisfied"] === true;
643
- let what;
644
- switch (condition["kind"]) {
645
- case "gate": {
646
- const owner = condition["owner"];
647
- let ownedBy = "";
648
- if (typeof owner === "object" && owner !== null) {
649
- const record = owner;
650
- ownedBy = options.expertView === true && record["kind"] === "expert" ? "you" : ownerText(record);
651
- }
652
- what = `gate ${gateName(condition)}${ownedBy === "" ? "" : ` owned by ${ownedBy}`}`;
653
- break;
654
- }
655
- case "not_before":
656
- what = `not before ${clean(condition["not_before"])}`;
657
- break;
658
- case "event":
659
- what = "system event";
660
- break;
661
- default:
662
- what = clean(condition["kind"]) || "condition";
663
- }
664
- let when = "";
665
- if (satisfied && typeof condition["satisfied_at"] === "string") {
666
- let by = clean(path(condition, "satisfied_by", "name"));
667
- if (by === "" && condition["satisfied_by_kind"] === "expert") by = "an outside expert";
668
- let role = typeof condition["satisfied_as_role"] === "string" ? roleName(condition["satisfied_as_role"]) : "";
669
- switch (path(condition, "satisfied_by", "kind")) {
670
- case "you":
671
- by = "you";
672
- break;
673
- case "role": {
674
- const as = path(condition, "satisfied_by", "role");
675
- if (typeof as === "string" && as !== "") role = roleName(as);
676
- break;
677
- }
678
- case "expert":
679
- by = "an outside expert";
680
- break;
681
- case "member":
682
- by = "a member";
683
- break;
684
- }
685
- when = ` (satisfied ${clean(condition["satisfied_at"])}${by === "" ? "" : ` by ${by}`}${role === "" ? "" : ` as ${role}`})`;
686
- }
687
- return `${satisfied ? "[x]" : "[ ]"} ${what}${when} \xB7 ${clean(condition["id"])}`;
688
- }
689
- function gateName(gate) {
690
- const key = clean(gate["key"]);
691
- return `"${clean(gate["label"])}"${key === "" ? "" : ` [${key}]`}`;
692
- }
693
- function ownerText(owner) {
694
- const label = clean(owner["label"]) || (owner["kind"] === "role" && typeof owner["role"] === "string" ? roleName(owner["role"]) : "");
695
- const ref = clean(owner["ref"]);
696
- if (owner["kind"] === "expert") {
697
- const email = label || ref.replace(/^expert:/, "");
698
- return email === "" ? "" : `${email} (expert)`;
699
- }
700
- if (label === "") return ref;
701
- return ref === "" || ref === label ? label : `${label} (${ref})`;
702
- }
703
- function expertSummary(seat) {
704
- const role = typeof seat.role === "string" && seat.role !== "" ? roleName(seat.role) : "Outside expert";
705
- const status = clean(seat.plan?.status);
706
- const plan = seat.plan === null ? ", no plan" : ` on plan ${clean(seat.plan.id)}${status === "" ? "" : ` (${status})`}`;
707
- const steps = seat.assigned_steps;
708
- const count = steps === 0 ? "no assigned steps" : `${steps} assigned step${steps === 1 ? "" : "s"}`;
709
- return `${role}${plan} \xB7 ${count}`;
710
- }
711
- function expertAssignments(steps) {
712
- return steps.map((step) => "\n" + assignedStep(step)).join("");
713
- }
714
- function assignedStep(step) {
715
- const blockers = typeof step["open_blockers"] === "number" ? step["open_blockers"] : 0;
716
- const fields = [
717
- ["State", path(step, "state", "name")],
718
- ["Due", step["due_date"]],
719
- ["Blocked", blockers > 0 ? `yes, by ${blockers} open issue${blockers === 1 ? "" : "s"}` : null]
720
- ];
721
- const fieldLines = fields.filter(([, value]) => value !== null && value !== void 0 && value !== "").map(([label, value]) => ` ${(label + ":").padEnd(12)}${clean(value)}
722
- `).join("");
723
- const gates = records(step["gates"]);
724
- const gateLines = gates.length === 0 ? " Gates: none of yours\n" : " Gates:\n" + gates.map((gate) => ` ${conditionLine(gate, { expertView: true })}
725
- `).join("");
726
- const description = step["description"];
727
- const descriptionLines = typeof description === "string" && description !== "" ? " Description:\n" + indent(indent(description)) + "\n" : "";
728
- return `${clean(step["identifier"])} \xB7 ${clean(step["title"])}
729
- ${fieldLines}${gateLines}${descriptionLines}`;
730
- }
731
- var ROLE_NAMES = { gp: "GP" };
732
- function roleName(role) {
733
- const slug = clean(role);
734
- if (Object.hasOwn(ROLE_NAMES, slug)) return ROLE_NAMES[slug];
735
- const words = slug.replace(/_/g, " ");
736
- return words.charAt(0).toUpperCase() + words.slice(1);
737
- }
738
- var LIVE_PLAN_STATUSES = ["released", "running", "needs_attention"];
739
- function plansTable(plans) {
740
- const rows = plans.map((plan) => [
741
- str(plan["root"]) || "-",
742
- str(plan["status"]) || "-",
743
- plan["held"] === true ? "yes" : plan["held"] === false ? "no" : "-",
744
- typeof plan["revision"] === "number" ? String(plan["revision"]) : "-",
745
- truncate(plan["title"])
746
- ]);
747
- return table(["ROOT", "STATUS", "HELD", "REVISION", "TITLE"], rows);
748
- }
749
- function issuePlanLine(plan) {
750
- if (typeof plan !== "object" || plan === null || Array.isArray(plan)) return null;
751
- const record = plan;
752
- const root = clean(record["root"]);
753
- if (root === "") return null;
754
- const status = record["held"] === true ? "held" : clean(record["status"]);
755
- const revision = record["revision"];
756
- const parts = [root];
757
- if (status !== "") parts.push(status);
758
- if (typeof revision === "number") {
759
- parts.push(`revision ${revision}`);
760
- if (typeof record["in_revision"] === "boolean") parts.push(record["in_revision"] ? "in revision" : "not in revision");
761
- } else {
762
- parts.push("never released");
763
- }
764
- if (record["executor"] === "operator") parts.push("Operator step");
765
- return parts.join(" \xB7 ");
766
- }
767
- function planDetail(plan) {
768
- const steps = records(plan["steps"]);
769
- const fields = [
770
- ["Root", planRootLine(plan["root"])],
771
- ["Revision", planRevisionLine(plan)],
772
- ["Budgets", planBudgetsLine(plan)],
773
- ["Estimate", planEstimateLine(plan["estimate"])]
774
- ];
775
- const fieldLines = fields.filter((field3) => field3[1] !== null).map(([label, value]) => ` ${(label + ":").padEnd(12)}${value}
776
- `).join("");
777
- return `Plan ${clean(plan["id"])} \xB7 ${clean(plan["status"])}
778
- ` + fieldLines + planStepsSection(steps, str(plan["status"])) + planEdgesSection(plan["edges"]) + planConditionsSection(steps) + planWaitingSection(steps) + planHeldSection(plan["held_steps"]);
779
- }
780
- function planRootLine(root) {
781
- if (typeof root !== "object" || root === null) return null;
782
- const record = root;
783
- return `${clean(record["identifier"])} \xB7 ${clean(record["title"])}`;
784
- }
785
- function planRevisionLine(plan) {
786
- const revision = plan["revision"];
787
- if (typeof revision !== "object" || revision === null) return "none yet (never released)";
788
- const number = clean(revision["number"]);
789
- const by = clean(path(plan, "released_by", "name"));
790
- const at = clean(plan["released_at"]);
791
- if (by === "" && at === "") return number;
792
- return `${number} \xB7 released${by === "" ? "" : ` by ${by}`}${at === "" ? "" : ` at ${at}`}`;
793
- }
794
- function planBudgetsLine(plan) {
795
- const retries = plan["retry_budget"];
796
- const retryText = typeof retries === "number" ? `${retries} ${retries === 1 ? "retry" : "retries"} per failed Operator step` : null;
797
- const spend = plan["spend_budget_cents"];
798
- const spendText = typeof spend === "number" ? `spend budget ${spend} cents` : "no spend budget";
799
- return [retryText, spendText].filter((part) => part !== null).join(" \xB7 ");
800
- }
801
- function planEstimateLine(estimate) {
802
- if (typeof estimate !== "object" || estimate === null) return null;
803
- const record = estimate;
804
- const ceiling = record["ceiling_cents"];
805
- if (typeof ceiling !== "number") return null;
806
- const minimum = record["minimum_budget_cents"];
807
- return `up to ${ceiling} cents${typeof minimum === "number" ? ` (minimum budget ${minimum} cents)` : ""}`;
808
- }
809
- function planStepsSection(steps, planStatus) {
810
- if (steps.length === 0) return "";
811
- const depths = stepDepths(steps);
812
- const rows = steps.map((step) => [
813
- " ".repeat(depths.get(str(step["identifier"])) ?? 0) + str(step["identifier"]),
814
- str(step["key"]) || "-",
815
- str(step["executor"]) || "-",
816
- str(path(step, "state", "name")) || "-",
817
- stepStatus(step, planStatus),
818
- truncate(step["title"])
819
- ]);
820
- const rendered = table(["STEP", "KEY", "EXECUTOR", "STATE", "STATUS", "TITLE"], rows);
821
- return "\nSteps:\n" + indentLines(rendered);
822
- }
823
- function stepDepths(steps) {
824
- const parents = new Map(steps.map((step) => [str(step["identifier"]), str(step["parent"])]));
825
- const depths = /* @__PURE__ */ new Map();
826
- for (const identifier of parents.keys()) {
827
- let depth = 0;
828
- let parent = parents.get(identifier) ?? "";
829
- const seen = /* @__PURE__ */ new Set([identifier]);
830
- while (parent !== "" && parents.has(parent) && !seen.has(parent)) {
831
- seen.add(parent);
832
- depth += 1;
833
- parent = parents.get(parent) ?? "";
834
- }
835
- depths.set(identifier, depth);
836
- }
837
- return depths;
838
- }
839
- function stepStatus(step, planStatus) {
840
- const stateType = path(step, "state", "type");
841
- if (step["archived"] === true) return "archived";
842
- if (stateType === "completed") return "done";
843
- if (stateType === "canceled") return "cancelled";
844
- if (step["container"] === true) return "container";
845
- if (step["ready"] === true) return "ready";
846
- if (step["held"] === true) return "held";
847
- if (step["in_revision"] === false && LIVE_PLAN_STATUSES.includes(planStatus)) return "not in revision";
848
- if (records(step["conditions"]).some((condition) => condition["satisfied"] !== true)) return "waiting";
849
- return "-";
850
- }
851
- function planEdgesSection(edges) {
852
- const list3 = records(edges);
853
- if (list3.length === 0) return "";
854
- return "\nEdges (blocker \u2192 blocked):\n" + list3.map((edge) => ` ${clean(edge["from"])} \u2192 ${clean(edge["to"])}
855
- `).join("");
856
- }
857
- function planConditionsSection(steps) {
858
- const lines = steps.flatMap(
859
- (step) => records(step["conditions"]).map((condition) => [clean(step["identifier"]), conditionLine(condition)])
860
- );
861
- if (lines.length === 0) return "";
862
- const width = Math.max(...lines.map(([identifier]) => graphemeLength(identifier)));
863
- return "\nWait conditions:\n" + lines.map(([identifier, line]) => ` ${padGraphemes(identifier, width)} ${line}
864
- `).join("");
865
- }
866
- function planWaitingSection(steps) {
867
- const lines = steps.flatMap(
868
- (step) => records(step["waiting_for"]).map((wait) => [clean(step["identifier"]), waitLine(wait)])
869
- );
870
- if (lines.length === 0) return "";
871
- const width = Math.max(...lines.map(([identifier]) => graphemeLength(identifier)));
872
- return "\nWaiting for:\n" + lines.map(([identifier, line]) => ` ${padGraphemes(identifier, width)} ${line}
873
- `).join("");
874
- }
875
- function waitLine(wait) {
876
- const name = gateName(wait);
877
- const owner = wait["owner"];
878
- if (typeof owner !== "object" || owner === null) return `${name} \xB7 no owner, anyone with standing can tick`;
879
- const holders = wait["holders"];
880
- let who;
881
- if (!Array.isArray(holders)) who = "who can tick is not known";
882
- else if (holders.length === 0) who = "nobody can tick";
883
- else who = `can tick: ${records(holders).map(holderText).join(", ")}`;
884
- return `${name} \xB7 ${ownerText(owner)} \xB7 ${who}`;
885
- }
886
- function holderText(holder) {
887
- const name = clean(holder["name"]);
888
- if (holder["kind"] !== "expert") return name;
889
- const organization = clean(holder["organization"]);
890
- return `${name} (expert${organization === "" ? "" : `, ${organization}`})`;
891
- }
892
- function planHeldSection(heldSteps) {
893
- if (!Array.isArray(heldSteps) || heldSteps.length === 0) return "";
894
- return `
895
- Changed since the release: ${heldSteps.map((step) => clean(step)).join(", ")} (only Operator steps are held until the next release takes them in)
896
- `;
897
- }
898
- function indentLines(text) {
899
- return text.split("\n").map((line) => line === "" ? line : " " + line).join("\n");
900
- }
901
- function errorDetails(body) {
902
- return changesetDetails(body["details"]) + violationDetails(body["violations"]);
903
- }
904
- function changesetDetails(details) {
905
- if (typeof details !== "object" || details === null || Array.isArray(details)) return "";
906
- return Object.entries(details).map(([field3, messages]) => {
907
- const text = Array.isArray(messages) ? messages.map(String).join("; ") : String(messages);
908
- return `
909
- ${clean(field3)}: ${clean(text)}`;
910
- }).join("");
911
- }
912
- function violationDetails(violations) {
913
- return records(violations).map((violation) => {
914
- const code = clean(violation["code"]);
915
- const message = capMessage(violation) ?? (clean(violation["message"]) || code || "invalid");
916
- return `
917
- ${violationSubject(violation)}: ${message}${code === "" ? "" : ` [${code}]`}`;
918
- }).join("");
919
- }
920
- var MAX_PLAN_STEPS_CEILING = 2e3;
921
- function capMessage(violation) {
922
- if (violation["code"] !== "too_many_steps") return null;
923
- const cap = violation["cap"];
924
- if (typeof cap !== "object" || cap === null || Array.isArray(cap)) return null;
925
- const { kind, max, count, ceiling: served } = cap;
926
- if (!Number.isInteger(max) || !Number.isInteger(count)) return null;
927
- if (kind === "operator_steps") {
928
- return `the plan has ${count} Operator steps, above the fixed limit of ${max}: Operator's delegated grant caps a plan's agent steps, and no setting raises it \u2014 hand some to people (executor none) or split the plan`;
929
- }
930
- if (kind === "plan_steps") {
931
- const ceiling = Number.isInteger(served) ? served : MAX_PLAN_STEPS_CEILING;
932
- const over = `the plan has ${count} steps, above this organization's max plan steps (${max})`;
933
- if (count > ceiling && max < ceiling) {
934
- return `${over}, and above the ceiling of ${ceiling} that no setting raises past \u2014 split the plan`;
935
- }
936
- if (max >= ceiling) return `${over}, which is already at its ceiling of ${ceiling} \u2014 split the plan`;
937
- return `${over}: a Harness admin can raise "Max plan steps" in Harness settings \u2192 Plans (max_plan_steps, up to ${ceiling})`;
938
- }
939
- return null;
940
- }
941
- function violationSubject(violation) {
942
- const key = typeof violation["key"] === "string" ? clean(violation["key"]) : null;
943
- const index = typeof violation["index"] === "number" ? `steps[${violation["index"]}]` : null;
944
- const issue = typeof violation["issue"] === "string" ? clean(violation["issue"]) : null;
945
- if (key !== null) {
946
- const where = index ?? issue;
947
- return where === null ? `step "${key}"` : `step "${key}" (${where})`;
948
- }
949
- return issue ?? index ?? "plan";
950
- }
951
- function relationsSection(relations) {
952
- if (!Array.isArray(relations) || relations.length === 0) return "";
953
- const lines = relations.map((relation) => ` ${str(relation["direction"]).padEnd(14)}${str(relation["other_issue"])}
954
- `).join("");
955
- return "\nRelations:\n" + lines;
956
- }
957
- function childrenSection(children) {
958
- if (!Array.isArray(children) || children.length === 0) return "";
959
- const lines = children.map((child) => {
960
- const state = str(path(child, "state", "name")) || "-";
961
- return ` ${str(child["identifier"])} [${state}] ${truncate(child["title"])}
962
- `;
963
- }).join("");
964
- return "\nSub-issues:\n" + lines;
965
- }
966
- function commentsSection(comments2) {
967
- if (!Array.isArray(comments2) || comments2.length === 0) return "";
968
- const lines = comments2.map((comment2) => {
969
- const author = clean(path(comment2, "author", "name") ?? "unknown");
970
- return ` [${str(comment2["inserted_at"])}] ${author}: ${truncate(comment2["body"])}
971
- `;
972
- }).join("");
973
- return "\nComments:\n" + lines;
974
- }
975
- function imagesSection(images) {
976
- if (images.length === 0) return "";
977
- const lines = images.map((image) => ` [${image["source"]}] ${clean(image["path"])}
978
- `).join("");
979
- return "\nImages (attachments downloaded locally \u2014 read them, they are part of the issue):\n" + lines;
980
- }
981
- function labelsLine(labels) {
982
- if (!Array.isArray(labels) || labels.length === 0) return null;
983
- return labels.map((label) => clean(label["name"])).join(", ");
984
- }
985
- function blockedLine(issue) {
986
- if (issue["blocked"] === true && Array.isArray(issue["blocked_by"])) {
987
- const notes = blockerNotes(issue["blockers"]);
988
- const blockers = issue["blocked_by"].map((blocker) => {
989
- const identifier = String(blocker);
990
- const note = notes.get(identifier);
991
- return note === void 0 ? identifier : `${identifier} (${note})`;
992
- });
993
- return `yes, by ${blockers.join(", ")}`;
994
- }
995
- return null;
996
- }
997
- function blockerNotes(blockers) {
998
- const notes = /* @__PURE__ */ new Map();
999
- if (!Array.isArray(blockers)) return notes;
1000
- for (const blocker of blockers) {
1001
- if (typeof blocker !== "object" || blocker === null) continue;
1002
- const record = blocker;
1003
- if (typeof record["identifier"] !== "string") continue;
1004
- const reasons = [];
1005
- if (record["state_type"] === "canceled") reasons.push("cancelled");
1006
- if (record["archived"] === true) reasons.push("archived");
1007
- if (reasons.length > 0) notes.set(record["identifier"], reasons.join(", "));
1008
- }
1009
- return notes;
1010
- }
1011
- function indent(text) {
1012
- return cleanMultiline(text).split("\n").map((line) => " " + line).join("\n");
1013
- }
1014
- function cleanMultiline(text) {
1015
- return text.split("\n").map((line) => clean(line)).join("\n");
1016
- }
1017
- function truncate(text) {
1018
- if (text === null || text === void 0) return "";
1019
- const cleaned = clean(text);
1020
- const pieces = graphemes(cleaned);
1021
- if (pieces.length > TITLE_WIDTH) {
1022
- return pieces.slice(0, TITLE_WIDTH - 1).join("") + "\u2026";
1023
- }
1024
- return cleaned;
1025
- }
1026
- function str(value) {
1027
- return value === null || value === void 0 ? "" : String(value);
1028
- }
1029
- function path(record, ...keys) {
1030
- let current = record;
1031
- for (const key of keys) {
1032
- if (typeof current !== "object" || current === null) return null;
1033
- current = current[key];
1034
- }
1035
- return current ?? null;
1036
- }
37
+ } from "./chunk-5THYIKLW.js";
1037
38
 
1038
39
  // src/commands/issues.ts
1039
40
  import { openAsBlob, openSync, closeSync, readFileSync, statSync as statSync2 } from "fs";
@@ -1101,14 +102,14 @@ async function fetchImages(client, issue, cacheDir) {
1101
102
  return { images, failures };
1102
103
  }
1103
104
  async function download(client, ref, cacheDir) {
1104
- const path2 = join(cacheDir, hostSegment(client.baseUrl), ref.attachmentId, ref.file);
105
+ const path = join(cacheDir, hostSegment(client.baseUrl), ref.attachmentId, ref.file);
1105
106
  try {
1106
- if (statSync(path2).size > 0) return { ok: true, path: path2 };
107
+ if (statSync(path).size > 0) return { ok: true, path };
1107
108
  } catch {
1108
109
  }
1109
110
  const bytes = await fetchBytes(client, ref);
1110
111
  if (!bytes.ok) return bytes;
1111
- return write(path2, bytes.bytes);
112
+ return write(path, bytes.bytes);
1112
113
  }
1113
114
  function hostSegment(baseUrl) {
1114
115
  let host = "unknown-host";
@@ -1148,25 +149,25 @@ function isImage(bytes) {
1148
149
  return false;
1149
150
  }
1150
151
  var partialCounter = 0;
1151
- function write(path2, bytes) {
1152
- const partial = `${path2}.partial.${process.pid}.${++partialCounter}`;
152
+ function write(path, bytes) {
153
+ const partial = `${path}.partial.${process.pid}.${++partialCounter}`;
1153
154
  try {
1154
- mkdirSync(dirname(path2), { recursive: true });
1155
- chmodSync(dirname(path2), 448);
155
+ mkdirSync(dirname(path), { recursive: true });
156
+ chmodSync(dirname(path), 448);
1156
157
  writeFileSync(partial, bytes);
1157
158
  chmodSync(partial, 384);
1158
- renameSync(partial, path2);
1159
- return { ok: true, path: path2 };
159
+ renameSync(partial, path);
160
+ return { ok: true, path };
1160
161
  } catch (error) {
1161
162
  rmSync(partial, { force: true });
1162
163
  const detail = error instanceof Error ? error.message : String(error);
1163
- return { ok: false, message: `cannot write ${path2}: ${detail}` };
164
+ return { ok: false, message: `cannot write ${path}: ${detail}` };
1164
165
  }
1165
166
  }
1166
167
 
1167
168
  // src/core/pagination.ts
1168
- function notices(meta, requestedPageSize, records5) {
1169
- return capNotice(meta, requestedPageSize) + truncationNotice(meta, records5);
169
+ function notices(meta, requestedPageSize, records4) {
170
+ return capNotice(meta, requestedPageSize) + truncationNotice(meta, records4);
1170
171
  }
1171
172
  function capNotice(meta, requestedPageSize) {
1172
173
  const served = meta?.page_size;
@@ -1176,12 +177,12 @@ function capNotice(meta, requestedPageSize) {
1176
177
  }
1177
178
  return "";
1178
179
  }
1179
- function truncationNotice(meta, records5) {
180
+ function truncationNotice(meta, records4) {
1180
181
  const total = meta?.total;
1181
182
  const page = meta?.page;
1182
183
  const pages = meta?.total_pages;
1183
184
  if (typeof total !== "number" || typeof page !== "number" || typeof pages !== "number") return "";
1184
- const shown = records5.length;
185
+ const shown = records4.length;
1185
186
  if (shown < total) {
1186
187
  return `harness: showing ${shown} of ${total} (page ${page} of ${pages}) \u2014 pass --all for the rest, or --page to step through.
1187
188
  `;
@@ -1192,20 +193,20 @@ async function fetchEveryPage(fetchPage) {
1192
193
  const first = await fetchPage(1);
1193
194
  const totalPages = pageCount(first.meta);
1194
195
  if (totalPages === null) return first.data;
1195
- let records5 = first.data;
196
+ let records4 = first.data;
1196
197
  for (let page = 2; page <= totalPages; page++) {
1197
198
  const body = await fetchPage(page);
1198
- records5 = dedupe([...records5, ...body.data]);
199
+ records4 = dedupe([...records4, ...body.data]);
1199
200
  }
1200
- return records5;
201
+ return records4;
1201
202
  }
1202
203
  function pageCount(meta) {
1203
204
  const total = meta?.total_pages;
1204
205
  return typeof total === "number" && total > 0 ? total : null;
1205
206
  }
1206
- function dedupe(records5) {
207
+ function dedupe(records4) {
1207
208
  const seen = /* @__PURE__ */ new Set();
1208
- return records5.filter((record) => {
209
+ return records4.filter((record) => {
1209
210
  const id = record["id"];
1210
211
  const key = id === void 0 || id === null ? `struct:${stableKey(record)}` : `id:${stableKey(id)}`;
1211
212
  if (seen.has(key)) return false;
@@ -1247,9 +248,9 @@ function requireIdentifiers(identifiers) {
1247
248
  async function ready(ctx, opts) {
1248
249
  if (opts.claim === true) return claimNext(ctx, opts);
1249
250
  const params = { ...listParams(ctx, opts), ready: true };
1250
- const { records: records5, notice } = await fetchIssues(ctx, params, opts);
251
+ const { records: records4, notice } = await fetchIssues(ctx, params, opts);
1251
252
  const rank = (issue) => PRIORITY_RANK[issue["priority"]] ?? 4;
1252
- const sorted = markAgentSteps([...records5].sort((a, b) => rank(a) - rank(b)));
253
+ const sorted = markAgentSteps([...records4].sort((a, b) => rank(a) - rank(b)));
1253
254
  return { json: sorted, human: issuesTable(sorted, { markAgentSteps: true }), notice };
1254
255
  }
1255
256
  function markAgentSteps(issues) {
@@ -1293,7 +294,7 @@ async function claimNext(ctx, opts) {
1293
294
  if (!isIssueRecord(claimed)) throw unreadable("carried neither an issue nor null");
1294
295
  return {
1295
296
  json: claimed,
1296
- human: `Claimed ${str2(claimed["identifier"])}: ${clean(claimed["title"])}
297
+ human: `Claimed ${str(claimed["identifier"])}: ${clean(claimed["title"])}
1297
298
  `
1298
299
  };
1299
300
  }
@@ -1304,13 +305,13 @@ function isIssueRecord(value) {
1304
305
  }
1305
306
  async function blocked(ctx, opts) {
1306
307
  const params = { ...listParams(ctx, opts), blocked: true };
1307
- const { records: records5, notice } = await fetchIssues(ctx, params, opts);
1308
- return { json: records5, human: issuesTable(records5), notice };
308
+ const { records: records4, notice } = await fetchIssues(ctx, params, opts);
309
+ return { json: records4, human: issuesTable(records4), notice };
1309
310
  }
1310
311
  async function list(ctx, opts) {
1311
312
  const params = listParams(ctx, opts);
1312
- const { records: records5, notice } = await fetchIssues(ctx, params, opts);
1313
- return { json: records5, human: issuesTable(records5), notice };
313
+ const { records: records4, notice } = await fetchIssues(ctx, params, opts);
314
+ return { json: records4, human: issuesTable(records4), notice };
1314
315
  }
1315
316
  async function search(ctx, queryWords, opts) {
1316
317
  if (queryWords.length === 0) throw new UsageError("usage: harness search <query>");
@@ -1334,11 +335,11 @@ async function show(ctx, identifier, opts) {
1334
335
  if (opts.images !== false) {
1335
336
  ({ images, failures } = await fetchImages(ctx.client, issue, ctx.config.cacheDir));
1336
337
  }
1337
- const json2 = images.length === 0 ? issue : { ...issue, local_images: images };
338
+ const json = images.length === 0 ? issue : { ...issue, local_images: images };
1338
339
  const human = issueDetail(issue, images);
1339
340
  const notice = failures.map((failure) => `harness: image not downloaded: ${clean(failure)}
1340
341
  `).join("");
1341
- return notice === "" ? { json: json2, human } : { json: json2, human, notice };
342
+ return notice === "" ? { json, human } : { json, human, notice };
1342
343
  }
1343
344
  var ACTIVITY_MAX_LIMIT = 100;
1344
345
  var ACTIVITY_INCLUDE = "condition_events,field_events";
@@ -1428,7 +429,7 @@ async function stats2(ctx) {
1428
429
  async function prime(ctx) {
1429
430
  const statsBody = await ctx.client.get("/api/v1/stats");
1430
431
  const ready2 = markAgentSteps(await mostImportantReady(ctx));
1431
- const json2 = { stats: statsBody.data, ready: ready2 };
432
+ const json = { stats: statsBody.data, ready: ready2 };
1432
433
  const human = "# Harness workflow context\n\n" + stats(statsBody.data) + "\nReady to work:\n" + issuesTable(ready2, { markAgentSteps: true }) + named(`
1433
434
  Workflow: harness ready \u2192 harness update <id> --claim \u2192 work \u2192
1434
435
  harness close <id> --reason "Implemented in <commit-hash>" (response
@@ -1437,7 +438,7 @@ Create tracked work BEFORE coding: harness create --title "\u2026" --type task.
1437
438
  Dependencies: harness dep add <issue> <blocker> (blocker blocks issue).
1438
439
  Before reporting done: close the issues you finished.
1439
440
  `);
1440
- return { json: json2, human };
441
+ return { json, human };
1441
442
  }
1442
443
  async function mostImportantReady(ctx) {
1443
444
  try {
@@ -1484,7 +485,7 @@ async function create(ctx, opts) {
1484
485
  const issue = created.data;
1485
486
  return {
1486
487
  json: issue,
1487
- human: `Created ${str2(issue["identifier"])}: ${clean(issue["title"])}
488
+ human: `Created ${str(issue["identifier"])}: ${clean(issue["title"])}
1488
489
  `
1489
490
  };
1490
491
  }
@@ -1537,7 +538,7 @@ async function update(ctx, identifier, opts) {
1537
538
  if (opts.appendNotes !== void 0) {
1538
539
  const current = await ctx.client.get(`/api/v1/issues/${identifier}`);
1539
540
  const existing = current.data["notes"];
1540
- const combined = existing === null || existing === void 0 || existing === "" ? opts.appendNotes : `${str2(existing)}
541
+ const combined = existing === null || existing === void 0 || existing === "" ? opts.appendNotes : `${str(existing)}
1541
542
 
1542
543
  ${opts.appendNotes}`;
1543
544
  patch["notes"] = combined;
@@ -1561,8 +562,8 @@ ${opts.appendNotes}`;
1561
562
  if (issue === null) {
1562
563
  throw new UsageError("usage: harness update <id> --claim | --title \u2026 | --state <name> | \u2026");
1563
564
  }
1564
- const human = wrote ? `Updated ${str2(issue["identifier"])}
1565
- ` : `Labels already up to date for ${str2(issue["identifier"])}
565
+ const human = wrote ? `Updated ${str(issue["identifier"])}
566
+ ` : `Labels already up to date for ${str(issue["identifier"])}
1566
567
  `;
1567
568
  return { json: issue, human };
1568
569
  }
@@ -1639,7 +640,7 @@ async function close(ctx, identifiers, opts) {
1639
640
  }
1640
641
  }
1641
642
  const human = results.map((result) => closeLine(result, opts.duplicateOf)).join("");
1642
- const json2 = results.map(
643
+ const json = results.map(
1643
644
  (result) => result.ok ? {
1644
645
  identifier: result.identifier,
1645
646
  closed: true,
@@ -1650,7 +651,7 @@ async function close(ctx, identifiers, opts) {
1650
651
  } : { identifier: result.identifier, closed: false, error: errorText(result.error) }
1651
652
  );
1652
653
  const partial = results.some((result) => !result.ok);
1653
- return partial ? { json: json2, human, partial: true } : { json: json2, human };
654
+ return partial ? { json, human, partial: true } : { json, human };
1654
655
  }
1655
656
  function closeLine(result, duplicateOf) {
1656
657
  if (!result.ok) return `FAILED ${result.identifier}: ${errorText(result.error)}
@@ -1693,25 +694,25 @@ async function archive(ctx, identifiers, opts) {
1693
694
  ` : `FAILED ${result.identifier}: ${errorText(result.error)}
1694
695
  `
1695
696
  ).join("");
1696
- const json2 = results.map(
697
+ const json = results.map(
1697
698
  (result) => result.ok ? { identifier: result.identifier, archived } : { identifier: result.identifier, error: errorText(result.error) }
1698
699
  );
1699
700
  const partial = results.some((result) => !result.ok);
1700
- return partial ? { json: json2, human, partial: true } : { json: json2, human };
701
+ return partial ? { json, human, partial: true } : { json, human };
1701
702
  }
1702
703
  async function comments(ctx, identifier) {
1703
704
  requireIdentifiers([identifier]);
1704
- const records5 = await completeCommentThread(ctx, identifier);
1705
- if (records5.length === 0) return { json: records5, human: "No comments.\n" };
1706
- const human = records5.map((comment2) => {
705
+ const records4 = await completeCommentThread(ctx, identifier);
706
+ if (records4.length === 0) return { json: records4, human: "No comments.\n" };
707
+ const human = records4.map((comment2) => {
1707
708
  const author = clean((comment2["author"] ?? {})["name"] ?? "unknown");
1708
709
  const id = clean(comment2["id"] ?? "unknown id");
1709
- const text = cleanMultiline(str2(comment2["body"])).split("\n").map((line) => " " + line).join("\n");
1710
- return `[${str2(comment2["inserted_at"])}] ${author} (${id}):
710
+ const text = cleanMultiline(str(comment2["body"])).split("\n").map((line) => " " + line).join("\n");
711
+ return `[${str(comment2["inserted_at"])}] ${author} (${id}):
1711
712
  ${text}
1712
713
  `;
1713
714
  }).join("\n");
1714
- return { json: records5, human };
715
+ return { json: records4, human };
1715
716
  }
1716
717
  async function completeCommentThread(ctx, identifier) {
1717
718
  const pages = [];
@@ -1725,20 +726,20 @@ async function completeCommentThread(ctx, identifier) {
1725
726
  if (!Array.isArray(page.data) || typeof page.meta?.has_more !== "boolean") {
1726
727
  throw new CommandError("Invalid comment pagination: expected a comment array and boolean meta.has_more; completeness unknown.");
1727
728
  }
1728
- const records5 = [];
729
+ const records4 = [];
1729
730
  for (const record of page.data) {
1730
731
  if (record === null || typeof record !== "object" || Array.isArray(record) || typeof record.id !== "string" || record.id.trim() === "" || seen.has(record.id)) {
1731
732
  throw new CommandError("Invalid comment pagination: missing or repeated comment id; completeness unknown.");
1732
733
  }
1733
734
  seen.add(record.id);
1734
- records5.push(record);
735
+ records4.push(record);
1735
736
  }
1736
- pages.push(records5);
737
+ pages.push(records4);
1737
738
  if (!page.meta.has_more) return pages.reverse().flat();
1738
- if (records5.length === 0) {
739
+ if (records4.length === 0) {
1739
740
  throw new CommandError("Invalid comment pagination: has_more on an empty page; completeness unknown.");
1740
741
  }
1741
- before = records5[0]["id"];
742
+ before = records4[0]["id"];
1742
743
  }
1743
744
  }
1744
745
  async function dep(ctx, verb, issue, blocker) {
@@ -1757,7 +758,7 @@ async function dep(ctx, verb, issue, blocker) {
1757
758
  (candidate) => candidate["direction"] === "blocked_by" && candidate["other_issue"] === blocker
1758
759
  );
1759
760
  if (relation === void 0) throw new CommandError(`${blocker} does not block this issue.`);
1760
- await ctx.client.delete(`/api/v1/issues/${issue}/relations/${str2(relation["id"])}`);
761
+ await ctx.client.delete(`/api/v1/issues/${issue}/relations/${str(relation["id"])}`);
1761
762
  return { json: { deleted: relation["id"] }, human: `${blocker} no longer blocks ${issue}
1762
763
  ` };
1763
764
  }
@@ -1880,17 +881,17 @@ function requireCommentId(commentId) {
1880
881
  }
1881
882
  var MAX_IMAGE_BYTES = 25e6;
1882
883
  var MAX_VIDEO_BYTES = 1e8;
1883
- async function attachImage(ctx, path2) {
1884
- return uploadAttachment(ctx, path2, MAX_IMAGE_BYTES);
884
+ async function attachImage(ctx, path) {
885
+ return uploadAttachment(ctx, path, MAX_IMAGE_BYTES);
1885
886
  }
1886
- async function attachFile(ctx, path2) {
1887
- const maxBytes = extname(path2).toLowerCase() === ".mp4" ? MAX_VIDEO_BYTES : MAX_IMAGE_BYTES;
1888
- return uploadAttachment(ctx, path2, maxBytes);
887
+ async function attachFile(ctx, path) {
888
+ const maxBytes = extname(path).toLowerCase() === ".mp4" ? MAX_VIDEO_BYTES : MAX_IMAGE_BYTES;
889
+ return uploadAttachment(ctx, path, maxBytes);
1889
890
  }
1890
- async function uploadAttachment(ctx, path2, maxBytes) {
1891
- attachableFile(path2, maxBytes);
1892
- const file = maxBytes === MAX_VIDEO_BYTES ? new File([await openAsBlob(path2)], basename(path2)) : new File([readFileSync(path2)], basename(path2));
1893
- requireAttachmentSize(path2, file.size, maxBytes);
891
+ async function uploadAttachment(ctx, path, maxBytes) {
892
+ attachableFile(path, maxBytes);
893
+ const file = maxBytes === MAX_VIDEO_BYTES ? new File([await openAsBlob(path)], basename(path)) : new File([readFileSync(path)], basename(path));
894
+ requireAttachmentSize(path, file.size, maxBytes);
1894
895
  const form = new FormData();
1895
896
  form.set("file", file);
1896
897
  const body = await ctx.client.postMultipart("/api/v1/uploads", form, file.size);
@@ -1899,27 +900,27 @@ async function uploadAttachment(ctx, path2, maxBytes) {
1899
900
  if (typeof markdown !== "string") throw new CommandError("the upload response carried no markdown reference");
1900
901
  return { json: attachment, human: clean(markdown) + "\n" };
1901
902
  }
1902
- function attachableFile(path2, maxBytes) {
903
+ function attachableFile(path, maxBytes) {
1903
904
  let fileStats;
1904
905
  try {
1905
- fileStats = statSync2(path2);
906
+ fileStats = statSync2(path);
1906
907
  } catch (error) {
1907
- throw new LocalError(`cannot read ${path2}: ${posixDetail(error)}`);
908
+ throw new LocalError(`cannot read ${path}: ${posixDetail(error)}`);
1908
909
  }
1909
910
  if (!fileStats.isFile()) {
1910
911
  const kind = fileStats.isDirectory() ? "directory" : "special file";
1911
- throw new LocalError(`${path2} is a ${kind}, not a file.`);
912
+ throw new LocalError(`${path} is a ${kind}, not a file.`);
1912
913
  }
1913
- requireAttachmentSize(path2, fileStats.size, maxBytes);
914
+ requireAttachmentSize(path, fileStats.size, maxBytes);
1914
915
  try {
1915
- closeSync(openSync(path2, "r"));
916
+ closeSync(openSync(path, "r"));
1916
917
  } catch (error) {
1917
- throw new LocalError(`cannot read ${path2}: ${posixDetail(error)}`);
918
+ throw new LocalError(`cannot read ${path}: ${posixDetail(error)}`);
1918
919
  }
1919
920
  }
1920
- function requireAttachmentSize(path2, size, maxBytes) {
921
+ function requireAttachmentSize(path, size, maxBytes) {
1921
922
  if (size > maxBytes) {
1922
- throw new LocalError(`${path2} is too large: ${size} bytes, and attachments are capped at ${maxBytes}.`);
923
+ throw new LocalError(`${path} is too large: ${size} bytes, and attachments are capped at ${maxBytes}.`);
1923
924
  }
1924
925
  }
1925
926
  function posixDetail(error) {
@@ -1928,19 +929,19 @@ function posixDetail(error) {
1928
929
  if (code === "EACCES") return "permission denied";
1929
930
  return error instanceof Error ? error.message : String(error);
1930
931
  }
1931
- async function setState(ctx, identifier, stateName2) {
932
+ async function setState(ctx, identifier, stateName) {
1932
933
  const showBody = await ctx.client.get(`/api/v1/issues/${identifier}`);
1933
- const teamKey = str2((showBody.data["team"] ?? {})["key"]);
934
+ const teamKey = str((showBody.data["team"] ?? {})["key"]);
1934
935
  const statesBody = await ctx.client.get(
1935
936
  `/api/v1/teams/${teamKey}/workflow_states`
1936
937
  );
1937
- const wanted = stateName2.toLowerCase();
938
+ const wanted = stateName.toLowerCase();
1938
939
  const state = statesBody.data.find(
1939
- (candidate) => str2(candidate["name"]).toLowerCase() === wanted || str2(candidate["type"]) === wanted
940
+ (candidate) => str(candidate["name"]).toLowerCase() === wanted || str(candidate["type"]) === wanted
1940
941
  );
1941
942
  if (state === void 0) {
1942
- const names = statesBody.data.map((candidate) => str2(candidate["name"])).join(", ");
1943
- throw new CommandError(`No workflow state matches "${stateName2}". States: ${names}`);
943
+ const names = statesBody.data.map((candidate) => str(candidate["name"])).join(", ");
944
+ throw new CommandError(`No workflow state matches "${stateName}". States: ${names}`);
1944
945
  }
1945
946
  const body = await ctx.client.put(`/api/v1/issues/${identifier}/state`, { state_id: state["id"] });
1946
947
  return body.data;
@@ -1971,16 +972,16 @@ function requestedLabelInputs(values) {
1971
972
  });
1972
973
  }
1973
974
  function normalizeLabelName(value) {
1974
- return str2(value).trim().toLowerCase();
975
+ return str(value).trim().toLowerCase();
1975
976
  }
1976
977
  async function prepareCreateLabelIds(ctx, teamKey, names) {
1977
978
  if (names.length === 0) return [];
1978
979
  const teams = await ctx.client.get("/api/v1/teams");
1979
980
  const target = teams.data.find(
1980
- (team) => str2(team["key"]).toLowerCase() === teamKey.toLowerCase()
981
+ (team) => str(team["key"]).toLowerCase() === teamKey.toLowerCase()
1981
982
  );
1982
983
  if (target === void 0) throw new CommandError(`No team matches key "${teamKey}".`);
1983
- const teamId = str2(target["id"]);
984
+ const teamId = str(target["id"]);
1984
985
  if (teamId === "") throw new CommandError(`team ${teamKey} carried no id`);
1985
986
  return prepareApplicableLabelIds(ctx, teamId, names);
1986
987
  }
@@ -2028,7 +1029,7 @@ async function resolveOrCreateApplicableLabel(ctx, catalog, name, teamId) {
2028
1029
  }
2029
1030
  }
2030
1031
  function labelId(label) {
2031
- const id = str2(label["id"]);
1032
+ const id = str(label["id"]);
2032
1033
  if (id === "") throw new CommandError("the label response carried no id");
2033
1034
  return id;
2034
1035
  }
@@ -2057,7 +1058,7 @@ async function prepareLabelUpdate(ctx, identifier, addInputs, removeInputs) {
2057
1058
  const ids = originalIds.filter((id) => !removed.has(id));
2058
1059
  if (addInputs.length > 0) {
2059
1060
  const team = issue["team"] ?? {};
2060
- const teamId = str2(team["id"]);
1061
+ const teamId = str(team["id"]);
2061
1062
  if (teamId === "") throw new CommandError(`issue ${identifier} carried no team id`);
2062
1063
  let catalog = (await ctx.client.get("/api/v1/labels")).data;
2063
1064
  for (const input of addInputs) {
@@ -2091,15 +1092,15 @@ async function fetchIssues(ctx, params, opts) {
2091
1092
  throw new UsageError("--all reads every page from the first \u2014 pass --all or --page, not both");
2092
1093
  }
2093
1094
  if (opts.all === true) {
2094
- const records6 = await fetchEveryPage(
1095
+ const records5 = await fetchEveryPage(
2095
1096
  (page) => ctx.client.get("/api/v1/issues", { ...params, page })
2096
1097
  );
2097
- return { records: records6, notice: "" };
1098
+ return { records: records5, notice: "" };
2098
1099
  }
2099
1100
  const body = await ctx.client.get("/api/v1/issues", params);
2100
- const records5 = body.data;
1101
+ const records4 = body.data;
2101
1102
  const requested = typeof params["page_size"] === "number" ? params["page_size"] : void 0;
2102
- return { records: records5, notice: notices(body.meta, requested, records5) };
1103
+ return { records: records4, notice: notices(body.meta, requested, records4) };
2103
1104
  }
2104
1105
  function listParams(ctx, opts) {
2105
1106
  if (opts.type !== void 0 && opts.label !== void 0) {
@@ -2192,7 +1193,7 @@ function errorText(error) {
2192
1193
  if (error instanceof HttpError) return `${clean(error.message)} (HTTP ${error.status})`;
2193
1194
  return `cannot reach the API: ${clean(error.message)}`;
2194
1195
  }
2195
- function str2(value) {
1196
+ function str(value) {
2196
1197
  return value === null || value === void 0 ? "" : String(value);
2197
1198
  }
2198
1199
 
@@ -2201,7 +1202,7 @@ var SAFE_ID = /^(?!\.{1,2}$)[A-Za-z0-9._-]+$/;
2201
1202
  function requireSafeId(kind, id) {
2202
1203
  if (!SAFE_ID.test(id)) throw new UsageError(`not a ${kind} id: ${id}`);
2203
1204
  }
2204
- function str3(value) {
1205
+ function str2(value) {
2205
1206
  return value === null || value === void 0 ? "" : String(value);
2206
1207
  }
2207
1208
  function field(row, ...keys) {
@@ -2220,15 +1221,15 @@ function putDate(body, key, raw) {
2220
1221
  if (parsed === null) return body;
2221
1222
  return { ...body, [key]: parsed === "clear" ? null : parsed };
2222
1223
  }
2223
- async function paginatedRead(ctx, path2, params, opts) {
1224
+ async function paginatedRead(ctx, path, params, opts) {
2224
1225
  if (opts.all === true && opts.page !== void 0) {
2225
1226
  throw new UsageError("--all reads every page from the first \u2014 pass --all or --page, not both");
2226
1227
  }
2227
1228
  if (opts.all === true) {
2228
- const records5 = await fetchEveryPage((page) => ctx.client.get(path2, { ...params, page }));
2229
- return { records: records5, notice: "" };
1229
+ const records4 = await fetchEveryPage((page) => ctx.client.get(path, { ...params, page }));
1230
+ return { records: records4, notice: "" };
2230
1231
  }
2231
- const body = await ctx.client.get(path2, params);
1232
+ const body = await ctx.client.get(path, params);
2232
1233
  const requested = typeof params["page_size"] === "number" ? params["page_size"] : void 0;
2233
1234
  return { records: body.data, notice: notices(body.meta, requested, body.data) };
2234
1235
  }
@@ -2236,18 +1237,18 @@ async function projectList(ctx, opts) {
2236
1237
  let params = { page_size: opts.limit ?? 50, page: opts.page ?? 1 };
2237
1238
  if (opts.search !== void 0) params = { ...params, search: opts.search };
2238
1239
  if (opts.state !== void 0) params = { ...params, state: opts.state };
2239
- const { records: records5, notice } = await paginatedRead(ctx, "/api/v1/projects", params, opts);
2240
- const human = records5.length === 0 ? "No projects.\n" : table(
1240
+ const { records: records4, notice } = await paginatedRead(ctx, "/api/v1/projects", params, opts);
1241
+ const human = records4.length === 0 ? "No projects.\n" : table(
2241
1242
  ["ID", "STATE", "LEAD", "TARGET", "NAME"],
2242
- records5.map((project) => [
2243
- str3(project["id"]),
2244
- str3(project["state"]) || "-",
2245
- str3(field(project, "lead", "name")) || "-",
2246
- str3(project["target_date"]) || "-",
2247
- str3(project["name"])
1243
+ records4.map((project) => [
1244
+ str2(project["id"]),
1245
+ str2(project["state"]) || "-",
1246
+ str2(field(project, "lead", "name")) || "-",
1247
+ str2(project["target_date"]) || "-",
1248
+ str2(project["name"])
2248
1249
  ])
2249
1250
  );
2250
- return { json: records5, human, notice };
1251
+ return { json: records4, human, notice };
2251
1252
  }
2252
1253
  async function projectShow(ctx, id) {
2253
1254
  requireSafeId("project", id);
@@ -2268,12 +1269,12 @@ async function projectShow(ctx, id) {
2268
1269
  ];
2269
1270
  const sectionText = sections.filter(([, text]) => text !== null && text !== void 0 && text !== "").map(([title, text]) => `
2270
1271
  ${title}:
2271
- ${indent2(String(text))}
1272
+ ${indent(String(text))}
2272
1273
  `).join("");
2273
1274
  const milestones = Array.isArray(project["milestones"]) ? project["milestones"] : [];
2274
- const milestoneText = milestones.length === 0 ? "" : "\nMilestones:\n" + milestones.map((milestone) => ` ${(str3(milestone["target_date"]) || "-").padEnd(12)}${clean(milestone["name"])}
1275
+ const milestoneText = milestones.length === 0 ? "" : "\nMilestones:\n" + milestones.map((milestone) => ` ${(str2(milestone["target_date"]) || "-").padEnd(12)}${clean(milestone["name"])}
2275
1276
  `).join("");
2276
- const human = `${str3(project["id"])} \xB7 ${clean(project["name"])}
1277
+ const human = `${str2(project["id"])} \xB7 ${clean(project["name"])}
2277
1278
  ` + fieldLines + sectionText + milestoneText;
2278
1279
  return { json: project, human };
2279
1280
  }
@@ -2292,7 +1293,7 @@ async function projectCreate(ctx, opts) {
2292
1293
  if (opts.name === void 0) throw new UsageError("usage: harness project create --name <name> \u2026");
2293
1294
  const body = await ctx.client.post("/api/v1/projects", projectBody(opts));
2294
1295
  const project = body.data;
2295
- return { json: project, human: `Created project ${clean(project["name"])} (${str3(project["id"])})
1296
+ return { json: project, human: `Created project ${clean(project["name"])} (${str2(project["id"])})
2296
1297
  ` };
2297
1298
  }
2298
1299
  async function projectUpdate(ctx, id, opts) {
@@ -2302,7 +1303,7 @@ async function projectUpdate(ctx, id, opts) {
2302
1303
  throw new UsageError("usage: harness project update <id> --name \u2026 | --state \u2026 | \u2026");
2303
1304
  }
2304
1305
  const body = await ctx.client.patch(`/api/v1/projects/${id}`, patch);
2305
- return { json: body.data, human: `Updated project ${str3(body.data["id"])}
1306
+ return { json: body.data, human: `Updated project ${str2(body.data["id"])}
2306
1307
  ` };
2307
1308
  }
2308
1309
  async function milestoneList(ctx, project) {
@@ -2312,9 +1313,9 @@ async function milestoneList(ctx, project) {
2312
1313
  const human = body.data.length === 0 ? "No milestones.\n" : table(
2313
1314
  ["ID", "TARGET", "NAME"],
2314
1315
  body.data.map((milestone) => [
2315
- str3(milestone["id"]),
2316
- str3(milestone["target_date"]) || "-",
2317
- str3(milestone["name"])
1316
+ str2(milestone["id"]),
1317
+ str2(milestone["target_date"]) || "-",
1318
+ str2(milestone["name"])
2318
1319
  ])
2319
1320
  );
2320
1321
  return { json: body.data, human };
@@ -2331,26 +1332,26 @@ async function milestoneCreate(ctx, opts) {
2331
1332
  const milestone = response.data;
2332
1333
  return {
2333
1334
  json: milestone,
2334
- human: `Created milestone ${clean(milestone["name"])} (${str3(milestone["id"])})
1335
+ human: `Created milestone ${clean(milestone["name"])} (${str2(milestone["id"])})
2335
1336
  `
2336
1337
  };
2337
1338
  }
2338
1339
  async function docList(ctx, opts) {
2339
1340
  let params = { page_size: opts.limit ?? 50, page: opts.page ?? 1 };
2340
1341
  if (opts.search !== void 0) params = { ...params, search: opts.search };
2341
- const { records: records5, notice } = await paginatedRead(ctx, "/api/v1/documents", params, opts);
2342
- const human = records5.length === 0 ? "No documents.\n" : table(
1342
+ const { records: records4, notice } = await paginatedRead(ctx, "/api/v1/documents", params, opts);
1343
+ const human = records4.length === 0 ? "No documents.\n" : table(
2343
1344
  ["ID", "UPDATED", "TITLE"],
2344
- records5.map((doc) => [str3(doc["id"]), str3(doc["updated_at"]) || "-", str3(doc["title"])])
1345
+ records4.map((doc) => [str2(doc["id"]), str2(doc["updated_at"]) || "-", str2(doc["title"])])
2345
1346
  );
2346
- return { json: records5, human, notice };
1347
+ return { json: records4, human, notice };
2347
1348
  }
2348
1349
  async function docShow(ctx, id) {
2349
1350
  requireSafeId("document", id);
2350
1351
  const body = await ctx.client.get(`/api/v1/documents/${id}`);
2351
1352
  const doc = body.data;
2352
1353
  const content = doc["content"];
2353
- const human = `${str3(doc["id"])} \xB7 ${clean(doc["title"])}
1354
+ const human = `${str2(doc["id"])} \xB7 ${clean(doc["title"])}
2354
1355
  Updated: ${clean(doc["updated_at"])}
2355
1356
  ` + (content === null || content === void 0 || content === "" ? "" : "\n" + cleanMultiline(String(content)) + "\n");
2356
1357
  return { json: doc, human };
@@ -2362,7 +1363,7 @@ async function docCreate(ctx, opts) {
2362
1363
  body = putOpt2(body, "project_id", opts.project);
2363
1364
  const response = await ctx.client.post("/api/v1/documents", body);
2364
1365
  const doc = response.data;
2365
- return { json: doc, human: `Created document ${clean(doc["title"])} (${str3(doc["id"])})
1366
+ return { json: doc, human: `Created document ${clean(doc["title"])} (${str2(doc["id"])})
2366
1367
  ` };
2367
1368
  }
2368
1369
  async function docUpdate(ctx, id, opts) {
@@ -2375,7 +1376,7 @@ async function docUpdate(ctx, id, opts) {
2375
1376
  throw new UsageError("usage: harness doc update <id> --title \u2026 | --content \u2026 | --project \u2026");
2376
1377
  }
2377
1378
  const response = await ctx.client.patch(`/api/v1/documents/${id}`, patch);
2378
- return { json: response.data, human: `Updated document ${str3(response.data["id"])}
1379
+ return { json: response.data, human: `Updated document ${str2(response.data["id"])}
2379
1380
  ` };
2380
1381
  }
2381
1382
  async function linkList(ctx, issue) {
@@ -2383,7 +1384,7 @@ async function linkList(ctx, issue) {
2383
1384
  const body = await ctx.client.get(`/api/v1/issues/${issue}/links`);
2384
1385
  const human = body.data.length === 0 ? "No links.\n" : table(
2385
1386
  ["ID", "URL", "TITLE"],
2386
- body.data.map((link) => [str3(link["id"]), str3(link["url"]), str3(link["title"]) || "-"])
1387
+ body.data.map((link) => [str2(link["id"]), str2(link["url"]), str2(link["title"]) || "-"])
2387
1388
  );
2388
1389
  return { json: body.data, human };
2389
1390
  }
@@ -2416,7 +1417,7 @@ async function linkUpdate(ctx, issue, idOrUrl, opts) {
2416
1417
  if (Object.keys(patch).length === 0) {
2417
1418
  throw new UsageError("usage: harness link update <issue> <link-id-or-url> --url <url> | --title <title>");
2418
1419
  }
2419
- const id = isUuid(idOrUrl) ? idOrUrl.toLowerCase() : str3((await findLink(ctx, issue, idOrUrl))["id"]);
1420
+ const id = isUuid(idOrUrl) ? idOrUrl.toLowerCase() : str2((await findLink(ctx, issue, idOrUrl))["id"]);
2420
1421
  if (!isUuid(idOrUrl)) patch["expected_url"] = idOrUrl;
2421
1422
  const response = await ctx.client.patch(`/api/v1/issues/${issue}/links/${id}`, patch);
2422
1423
  return { json: response.data, human: `Updated link ${clean(response.data["url"])} on ${issue}
@@ -2425,19 +1426,19 @@ async function linkUpdate(ctx, issue, idOrUrl, opts) {
2425
1426
  async function linkRm(ctx, issue, idOrUrl) {
2426
1427
  requireIdentifiers([issue]);
2427
1428
  const link = await findLink(ctx, issue, idOrUrl, true);
2428
- await ctx.client.delete(`/api/v1/issues/${issue}/links/${str3(link["id"])}`);
1429
+ await ctx.client.delete(`/api/v1/issues/${issue}/links/${str2(link["id"])}`);
2429
1430
  return { json: { deleted: link["id"] }, human: `Removed ${clean(link["url"])} from ${issue}
2430
1431
  ` };
2431
1432
  }
2432
- async function labelList2(ctx) {
1433
+ async function labelList(ctx) {
2433
1434
  const body = await ctx.client.get("/api/v1/labels");
2434
1435
  const human = body.data.length === 0 ? "No labels.\n" : table(
2435
1436
  ["NAME", "COLOR", "ISSUES", "ID"],
2436
1437
  body.data.map((label) => [
2437
- str3(label["name"]),
2438
- str3(label["color"]) || "-",
2439
- str3(label["issue_count"] ?? 0),
2440
- str3(label["id"])
1438
+ str2(label["name"]),
1439
+ str2(label["color"]) || "-",
1440
+ str2(label["issue_count"] ?? 0),
1441
+ str2(label["id"])
2441
1442
  ])
2442
1443
  );
2443
1444
  return { json: body.data, human };
@@ -2460,11 +1461,11 @@ async function teamCreate(ctx, opts) {
2460
1461
  }
2461
1462
  const created = await ctx.client.post("/api/v1/teams", { key: opts.key, name: opts.name });
2462
1463
  const team = created.data;
2463
- const states = await ctx.client.get(`/api/v1/teams/${str3(team["key"])}/workflow_states`);
2464
- const human = `Created team ${str3(team["key"])} \u2014 ${clean(team["name"])}
1464
+ const states = await ctx.client.get(`/api/v1/teams/${str2(team["key"])}/workflow_states`);
1465
+ const human = `Created team ${str2(team["key"])} \u2014 ${clean(team["name"])}
2465
1466
  ` + table(
2466
1467
  ["NAME", "TYPE"],
2467
- states.data.map((state) => [str3(state["name"]), str3(state["type"])])
1468
+ states.data.map((state) => [str2(state["name"]), str2(state["type"])])
2468
1469
  );
2469
1470
  return { json: { ...team, workflow_states: states.data }, human };
2470
1471
  }
@@ -2473,9 +1474,9 @@ async function teamList(ctx) {
2473
1474
  const human = body.data.length === 0 ? "No teams.\n" : table(
2474
1475
  ["KEY", "NAME", "ISSUES", "DEFAULT"],
2475
1476
  body.data.map((team) => [
2476
- str3(team["key"]),
2477
- str3(team["name"]),
2478
- str3(team["issue_count"] ?? 0),
1477
+ str2(team["key"]),
1478
+ str2(team["name"]),
1479
+ str2(team["issue_count"] ?? 0),
2479
1480
  team["is_default"] === true ? "yes" : ""
2480
1481
  ])
2481
1482
  );
@@ -2487,10 +1488,10 @@ async function workflowStates(ctx, team) {
2487
1488
  const human = body.data.length === 0 ? "No workflow states.\n" : table(
2488
1489
  ["NAME", "TYPE", "COLOR", "ID"],
2489
1490
  body.data.map((state) => [
2490
- str3(state["name"]),
2491
- str3(state["type"]),
2492
- str3(state["color"]) || "-",
2493
- str3(state["id"])
1491
+ str2(state["name"]),
1492
+ str2(state["type"]),
1493
+ str2(state["color"]) || "-",
1494
+ str2(state["id"])
2494
1495
  ])
2495
1496
  );
2496
1497
  return { json: body.data, human };
@@ -2499,7 +1500,7 @@ async function userList(ctx) {
2499
1500
  const body = await ctx.client.get("/api/v1/users");
2500
1501
  const human = body.data.length === 0 ? "No users.\n" : table(
2501
1502
  ["NAME", "EMAIL", "ID"],
2502
- body.data.map((user) => [str3(user["name"]), str3(user["email"]), str3(user["id"])])
1503
+ body.data.map((user) => [str2(user["name"]), str2(user["email"]), str2(user["id"])])
2503
1504
  );
2504
1505
  return { json: body.data, human };
2505
1506
  }
@@ -2508,7 +1509,7 @@ async function teamRename(ctx, key, name) {
2508
1509
  throw new UsageError("usage: harness team rename <key> --name <name>");
2509
1510
  }
2510
1511
  const body = await ctx.client.patch(`/api/v1/teams/${encodeURIComponent(key)}`, { name });
2511
- return { json: body.data, human: `Renamed ${str3(body.data["key"])} to ${clean(body.data["name"])}
1512
+ return { json: body.data, human: `Renamed ${str2(body.data["key"])} to ${clean(body.data["name"])}
2512
1513
  ` };
2513
1514
  }
2514
1515
  async function teamSetDefault(ctx, key) {
@@ -2516,7 +1517,7 @@ async function teamSetDefault(ctx, key) {
2516
1517
  const body = await ctx.client.put(`/api/v1/teams/${encodeURIComponent(key)}/default`, {});
2517
1518
  return {
2518
1519
  json: body.data,
2519
- human: `${str3(body.data["key"])} is now the default filing target
1520
+ human: `${str2(body.data["key"])} is now the default filing target
2520
1521
  `
2521
1522
  };
2522
1523
  }
@@ -2555,7 +1556,7 @@ async function stateOrder(ctx, ids, opts) {
2555
1556
  const body = await ctx.client.put(`/api/v1/teams/${key}/workflow_states/order`, { state_ids });
2556
1557
  const human = table(
2557
1558
  ["NAME", "TYPE"],
2558
- body.data.map((state) => [str3(state["name"]), str3(state["type"])])
1559
+ body.data.map((state) => [str2(state["name"]), str2(state["type"])])
2559
1560
  );
2560
1561
  return { json: body.data, human };
2561
1562
  }
@@ -2591,11 +1592,11 @@ async function memberList(ctx) {
2591
1592
  const human = body.data.length === 0 ? "No members.\n" : table(
2592
1593
  ["NAME", "EMAIL", "RANK", "STANDING", "ID"],
2593
1594
  body.data.map((member) => [
2594
- str3(field(member, "user", "name")),
2595
- str3(field(member, "user", "email")),
2596
- str3(member["role"]) || "-",
2597
- str3(member["standing"]),
2598
- str3(field(member, "user", "id"))
1595
+ str2(field(member, "user", "name")),
1596
+ str2(field(member, "user", "email")),
1597
+ str2(member["role"]) || "-",
1598
+ str2(member["standing"]),
1599
+ str2(field(member, "user", "id"))
2599
1600
  ])
2600
1601
  );
2601
1602
  return { json: body.data, human };
@@ -2607,7 +1608,7 @@ async function memberAdd(ctx, opts) {
2607
1608
  const body = await ctx.client.post("/api/v1/members", { email: opts.email, role: opts.role });
2608
1609
  return {
2609
1610
  json: body.data,
2610
- human: `Added ${str3(field(body.data, "user", "email"))} as ${str3(body.data["role"])}
1611
+ human: `Added ${str2(field(body.data, "user", "email"))} as ${str2(body.data["role"])}
2611
1612
  `
2612
1613
  };
2613
1614
  }
@@ -2616,19 +1617,19 @@ async function memberSetRole(ctx, userId, opts) {
2616
1617
  throw new UsageError("usage: harness member set-role <user-id> --role <admin|staff|collaborator|none>");
2617
1618
  }
2618
1619
  const body = await ctx.client.put(`/api/v1/members/${encodeURIComponent(userId)}/role`, { role: opts.role });
2619
- const rank = str3(body.data["role"]);
2620
- const human = rank === "" ? `Revoked the rank for ${str3(field(body.data, "user", "email"))}
2621
- ` : `${str3(field(body.data, "user", "email"))} is now ${rank}
1620
+ const rank = str2(body.data["role"]);
1621
+ const human = rank === "" ? `Revoked the rank for ${str2(field(body.data, "user", "email"))}
1622
+ ` : `${str2(field(body.data, "user", "email"))} is now ${rank}
2622
1623
  `;
2623
1624
  return { json: body.data, human };
2624
1625
  }
2625
1626
  function requireYes(command, id, yes) {
2626
1627
  if (yes !== true) throw new UsageError(`${command} ${id} is destructive \u2014 pass --yes to confirm`);
2627
1628
  }
2628
- async function upload(ctx, path2) {
2629
- return attachFile(ctx, path2);
1629
+ async function upload(ctx, path) {
1630
+ return attachFile(ctx, path);
2630
1631
  }
2631
- function indent2(text) {
1632
+ function indent(text) {
2632
1633
  return cleanMultiline(text).split("\n").map((line) => " " + line).join("\n");
2633
1634
  }
2634
1635
 
@@ -2669,12 +1670,12 @@ async function toolsSearch(ctx, query, opts) {
2669
1670
  }
2670
1671
  async function toolsDomains(ctx) {
2671
1672
  const body = await operatorRequest(() => ctx.client.get("/api/v1/operator/capabilities/domains"));
2672
- const domains = records2(body.data, "domains");
1673
+ const domains = records(body.data, "domains");
2673
1674
  return {
2674
1675
  json: domains,
2675
1676
  human: domains.length === 0 ? "Operator can use no capabilities here.\n" : table(
2676
1677
  ["DOMAIN", "CAPABILITIES"],
2677
- domains.map((domain) => [str4(domain["domain"]), str4(domain["count"])])
1678
+ domains.map((domain) => [str3(domain["domain"]), str3(domain["count"])])
2678
1679
  )
2679
1680
  };
2680
1681
  }
@@ -2739,7 +1740,7 @@ async function toolShow(ctx, id, opts) {
2739
1740
  }
2740
1741
  async function skillsList(ctx) {
2741
1742
  const body = await operatorRequest(() => ctx.client.get("/api/v1/operator/skills"));
2742
- const skills = records2(body.data, "skills").map((skill) => ({
1743
+ const skills = records(body.data, "skills").map((skill) => ({
2743
1744
  id: skill["id"],
2744
1745
  title: skill["title"],
2745
1746
  invocation_name: skill["invocation_name"],
@@ -2750,10 +1751,10 @@ async function skillsList(ctx) {
2750
1751
  human: skills.length === 0 ? "No skills are assigned to Operator in this organization.\n" : table(
2751
1752
  ["INVOCATION", "TITLE", "SHA-256", "ID"],
2752
1753
  skills.map((skill) => [
2753
- str4(skill.invocation_name),
1754
+ str3(skill.invocation_name),
2754
1755
  truncate(skill.title),
2755
- str4(skill.content_sha256),
2756
- str4(skill.id)
1756
+ str3(skill.content_sha256),
1757
+ str3(skill.id)
2757
1758
  ])
2758
1759
  )
2759
1760
  };
@@ -2813,7 +1814,7 @@ async function capabilityPage(ctx, params, staleCursor) {
2813
1814
  void 0,
2814
1815
  staleCursor
2815
1816
  );
2816
- const items = records2(body.data, "capabilities");
1817
+ const items = records(body.data, "capabilities");
2817
1818
  const meta = body.meta;
2818
1819
  if (!isRecord(meta)) throw new CommandError("The capabilities response has no pagination metadata.");
2819
1820
  const nextCursor = meta.next_cursor;
@@ -2848,14 +1849,14 @@ function explain(error, notFound2, staleCursor) {
2848
1849
  function capabilitiesTable(items, search2) {
2849
1850
  const headers = ["ID", "KIND", "DOMAIN", "SUMMARY"];
2850
1851
  const rows = items.map((item) => [
2851
- str4(item["id"]),
1852
+ str3(item["id"]),
2852
1853
  kindName(item),
2853
- str4(item["domain"]),
1854
+ str3(item["domain"]),
2854
1855
  truncate(item["summary"])
2855
1856
  ]);
2856
1857
  const table2 = search2 ? table(
2857
1858
  ["SCORE", ...headers],
2858
- rows.map((row, index) => [str4(items[index]["score"]), ...row])
1859
+ rows.map((row, index) => [str3(items[index]["score"]), ...row])
2859
1860
  ) : table(headers, rows);
2860
1861
  return table2 + (items.some(needsApproval) ? "\n" + APPROVAL_NOTE : "");
2861
1862
  }
@@ -2869,14 +1870,14 @@ function capabilityDetail(capability, view, schema) {
2869
1870
  const kind = kindName(capability);
2870
1871
  const line = (label, value) => ` ${(label + ":").padEnd(10)}${clean(value)}
2871
1872
  `;
2872
- const description = cleanMultiline(str4(capability["description"]));
1873
+ const description = cleanMultiline(str3(capability["description"]));
2873
1874
  return `${clean(capability["id"])}
2874
- ` + line("Domain", str4(capability["domain"])) + line("Kind", kind === "send" ? "send \u2014 delivered beyond the platform" : kind) + line("Mode", `${str4(capability["mode"])} (effect: ${str4(capability["effect"])})`) + line("Summary", str4(capability["summary"])) + line("Approval", approval(capability)) + (description === "" ? "" : `
1875
+ ` + line("Domain", str3(capability["domain"])) + line("Kind", kind === "send" ? "send \u2014 delivered beyond the platform" : kind) + line("Mode", `${str3(capability["mode"])} (effect: ${str3(capability["effect"])})`) + line("Summary", str3(capability["summary"])) + line("Approval", approval(capability)) + (description === "" ? "" : `
2875
1876
  Description:
2876
- ${indent3(description)}
1877
+ ${indent2(description)}
2877
1878
  `) + `
2878
1879
  ${view === "input" ? "Input" : "Result"} schema:
2879
- ${indent3(cleanMultiline(JSON.stringify(schema, null, 2)))}
1880
+ ${indent2(cleanMultiline(JSON.stringify(schema, null, 2)))}
2880
1881
  `;
2881
1882
  }
2882
1883
  function needsApproval(capability) {
@@ -2896,7 +1897,7 @@ var SCHEMA_PAGING_FIELDS = /* @__PURE__ */ new Set(["schema", "schema_encoding",
2896
1897
  function withoutSchemaPaging(capability) {
2897
1898
  return Object.fromEntries(Object.entries(capability).filter(([key]) => !SCHEMA_PAGING_FIELDS.has(key)));
2898
1899
  }
2899
- function records2(value, what) {
1900
+ function records(value, what) {
2900
1901
  if (!Array.isArray(value) || !value.every(isRecord)) {
2901
1902
  throw new CommandError(`The ${what} response carried no list.`);
2902
1903
  }
@@ -2910,13 +1911,13 @@ function dedupeById(items) {
2910
1911
  return true;
2911
1912
  });
2912
1913
  }
2913
- function indent3(text) {
1914
+ function indent2(text) {
2914
1915
  return text.split("\n").map((line) => " " + line).join("\n");
2915
1916
  }
2916
1917
  function isRecord(value) {
2917
1918
  return typeof value === "object" && value !== null && !Array.isArray(value);
2918
1919
  }
2919
- function str4(value) {
1920
+ function str3(value) {
2920
1921
  return value === null || value === void 0 ? "" : String(value);
2921
1922
  }
2922
1923
 
@@ -3113,11 +2114,11 @@ async function jobChangeSet(ctx, id) {
3113
2114
  if (!isRecord2(body.data)) throw new CommandError("The change set response carried no change set.");
3114
2115
  return {
3115
2116
  ...pick(body.data, CHANGE_SET_FIELDS),
3116
- actions: records3(body.data["actions"]).map((action) => pick(action, CHANGE_SET_ACTION_FIELDS))
2117
+ actions: records2(body.data["actions"]).map((action) => pick(action, CHANGE_SET_ACTION_FIELDS))
3117
2118
  };
3118
2119
  }
3119
2120
  function jobPeople(job) {
3120
- const participants = records3(field2(job, "people", "participants"));
2121
+ const participants = records2(field2(job, "people", "participants"));
3121
2122
  const owner = isRecord2(job["owner"]) ? job["owner"] : null;
3122
2123
  const names = /* @__PURE__ */ new Map();
3123
2124
  for (const named2 of owner === null ? participants : [...participants, owner]) {
@@ -3188,7 +2189,7 @@ async function jobWatch(ctx, id, opts) {
3188
2189
  after = page.nextAfter;
3189
2190
  job = page.job;
3190
2191
  if (page.hasMore) continue;
3191
- const state = str5(job["state"]);
2192
+ const state = str4(job["state"]);
3192
2193
  const waitingOn = typeof job["waiting_on"] === "string" ? job["waiting_on"] : null;
3193
2194
  if (shown === null || shown.state !== state || shown.waitingOn !== waitingOn) {
3194
2195
  emit(stateLine(shown?.state ?? null, state, waitingOn));
@@ -3329,13 +2330,13 @@ async function explained(send, why) {
3329
2330
  function jobsTable(jobs) {
3330
2331
  if (jobs.length === 0) return "No Operator jobs.\n";
3331
2332
  const rows = jobs.map((job) => [
3332
- str5(job["id"]),
3333
- str5(job["state"]) || "-",
3334
- str5(job["waiting_on"]) || "-",
3335
- str5(job["objective_kind"]) || "-",
2333
+ str4(job["id"]),
2334
+ str4(job["state"]) || "-",
2335
+ str4(job["waiting_on"]) || "-",
2336
+ str4(job["objective_kind"]) || "-",
3336
2337
  personName(job["owner"]),
3337
- str5(field2(job, "plan_step", "issue")) || "-",
3338
- str5(job["updated_at"]) || "-"
2338
+ str4(field2(job, "plan_step", "issue")) || "-",
2339
+ str4(job["updated_at"]) || "-"
3339
2340
  ]);
3340
2341
  const table2 = table(["ID", "STATE", "WAITING ON", "KIND", "OWNER", "PLAN STEP", "UPDATED"], rows);
3341
2342
  const approvals = jobs.filter((job) => typeof job["approval_url"] === "string");
@@ -3344,30 +2345,30 @@ function jobsTable(jobs) {
3344
2345
  `).join("");
3345
2346
  }
3346
2347
  function jobDetail(job) {
3347
- const waitingOn = str5(job["waiting_on"]);
2348
+ const waitingOn = str4(job["waiting_on"]);
3348
2349
  const header = `Operator job ${clean(job["id"])} \xB7 ${clean(job["state"])}` + (waitingOn === "" ? "" : ` \xB7 waiting on ${clean(waitingOn)}`) + "\n";
3349
- const people = records3(field2(job, "people", "participants"));
2350
+ const people = records2(field2(job, "people", "participants"));
3350
2351
  const fields = [
3351
- ["Kind", str5(job["objective_kind"]) || "-"],
3352
- ["Role", str5(job["viewer_role"]) || null],
2352
+ ["Kind", str4(job["objective_kind"]) || "-"],
2353
+ ["Role", str4(job["viewer_role"]) || null],
3353
2354
  ["Owner", isRecord2(job["owner"]) ? personName(job["owner"]) : null],
3354
2355
  ["Plan step", planStepLine(job["plan_step"])],
3355
2356
  ["Plan", planLine(job["plan"])],
3356
2357
  ["Progress", progressLine(job["progress"])],
3357
2358
  ["Window", windowLine(job["window"])],
3358
2359
  ["Spend", spendLine(job["spend"])],
3359
- ["Verification", str5(field2(job, "verification", "status")) || null],
2360
+ ["Verification", str4(field2(job, "verification", "status")) || null],
3360
2361
  ["Effects", effectsLine(job["effects"])],
3361
2362
  ["Skill", skillLine(job["skill"])],
3362
2363
  ["Grant", grantLine(job["grant"], people)],
3363
- ["Created", str5(job["inserted_at"]) || null],
3364
- ["Updated", str5(job["updated_at"]) || null],
3365
- ["Workbench", str5(job["url"]) || null]
2364
+ ["Created", str4(job["inserted_at"]) || null],
2365
+ ["Updated", str4(job["updated_at"]) || null],
2366
+ ["Workbench", str4(job["url"]) || null]
3366
2367
  ];
3367
- return header + labelled(fields) + approvalSection(job["pending_approval"], people) + listSection("Blockers", records3(job["blockers"]), (item) => `${str5(item["kind"])}: ${str5(item["state"])}`) + listSection("Artifacts", records3(job["artifacts"]), (item) => `${str5(item["id"])} ${str5(item["url"])}`) + peopleSection(people) + listSection(
2368
+ return header + labelled(fields) + approvalSection(job["pending_approval"], people) + listSection("Blockers", records2(job["blockers"]), (item) => `${str4(item["kind"])}: ${str4(item["state"])}`) + listSection("Artifacts", records2(job["artifacts"]), (item) => `${str4(item["id"])} ${str4(item["url"])}`) + peopleSection(people) + listSection(
3368
2369
  "Procedures",
3369
- records3(job["procedures"]),
3370
- (procedure) => `${str5(procedure["name"])} v${str5(procedure["current_version"])} ${str5(procedure["id"])}`
2370
+ records2(job["procedures"]),
2371
+ (procedure) => `${str4(procedure["name"])} v${str4(procedure["current_version"])} ${str4(procedure["id"])}`
3371
2372
  ) + controlsLine(job["viewer_can"]) + "\nThe objective's text is not shown: the API never returns it, because it can quote what Operator read. Open the workbench link to read it.\n";
3372
2373
  }
3373
2374
  function labelled(fields, width = 15) {
@@ -3376,16 +2377,16 @@ function labelled(fields, width = 15) {
3376
2377
  }
3377
2378
  function planStepLine(step) {
3378
2379
  if (!isRecord2(step)) return null;
3379
- return `${str5(step["issue"])} (plan ${str5(step["plan_id"])})`;
2380
+ return `${str4(step["issue"])} (plan ${str4(step["plan_id"])})`;
3380
2381
  }
3381
2382
  function planLine(plan) {
3382
2383
  if (!isRecord2(plan)) return "none yet";
3383
2384
  const count = plan["capability_count"];
3384
- return `version ${str5(plan["version"])} \xB7 instructions v${str5(plan["instruction_version"])} \xB7 ${str5(count)} capabilit${count === 1 ? "y" : "ies"}`;
2385
+ return `version ${str4(plan["version"])} \xB7 instructions v${str4(plan["instruction_version"])} \xB7 ${str4(count)} capabilit${count === 1 ? "y" : "ies"}`;
3385
2386
  }
3386
2387
  function progressLine(progress) {
3387
2388
  if (!isRecord2(progress)) return null;
3388
- const parts = [str5(progress["stage"]), str5(progress["situation"])].filter((part) => part !== "");
2389
+ const parts = [str4(progress["stage"]), str4(progress["situation"])].filter((part) => part !== "");
3389
2390
  if (progress["needs_viewer"] === true) parts.push("needs you");
3390
2391
  return parts.length === 0 ? null : parts.join(" \xB7 ");
3391
2392
  }
@@ -3394,28 +2395,28 @@ function windowLine(window) {
3394
2395
  const left = duration(window["remaining_ms"]);
3395
2396
  const budget = duration(window["budget_ms"]);
3396
2397
  const sequence = window["sequence"];
3397
- return `${left ?? "?"} left${budget === null ? "" : ` of ${budget}`} \xB7 ${str5(window["state"])}` + (typeof sequence === "number" ? ` (window ${sequence})` : "");
2398
+ return `${left ?? "?"} left${budget === null ? "" : ` of ${budget}`} \xB7 ${str4(window["state"])}` + (typeof sequence === "number" ? ` (window ${sequence})` : "");
3398
2399
  }
3399
2400
  function spendLine(spend) {
3400
2401
  if (!isRecord2(spend)) return null;
3401
- const spent = `${str5(spend["spent_cents"]) || "0"} cents spent`;
2402
+ const spent = `${str4(spend["spent_cents"]) || "0"} cents spent`;
3402
2403
  if (typeof spend["plan_budget_cents"] !== "number") return spent;
3403
2404
  const remaining = spend["plan_remaining_cents"];
3404
2405
  return `${spent} \xB7 plan budget ${spend["plan_budget_cents"]} cents` + (typeof remaining === "number" ? `, ${remaining} cents left` : "");
3405
2406
  }
3406
2407
  function effectsLine(effects) {
3407
2408
  if (!isRecord2(effects)) return null;
3408
- return ["staged", "succeeded", "failed", "unknown"].map((key) => `${str5(effects[key]) || "0"} ${key}`).join(" \xB7 ");
2409
+ return ["staged", "succeeded", "failed", "unknown"].map((key) => `${str4(effects[key]) || "0"} ${key}`).join(" \xB7 ");
3409
2410
  }
3410
2411
  function skillLine(skill) {
3411
2412
  if (!isRecord2(skill)) return null;
3412
- return `${str5(skill["title"])} (${str5(skill["invocation_name"])}) \xB7 sha256 ${str5(skill["content_sha256"])} \xB7 pinned ${str5(skill["pinned_at"])}`;
2413
+ return `${str4(skill["title"])} (${str4(skill["invocation_name"])}) \xB7 sha256 ${str4(skill["content_sha256"])} \xB7 pinned ${str4(skill["pinned_at"])}`;
3413
2414
  }
3414
2415
  function grantLine(grant, people) {
3415
2416
  if (!isRecord2(grant)) return null;
3416
- const approvedAt = str5(grant["approved_at"]);
3417
- const expiresAt = str5(grant["expires_at"]);
3418
- return `${str5(grant["status"])} v${str5(grant["version"])} \xB7 approved by ${nameOf(grant["approver_user_id"], people)}` + (approvedAt === "" ? "" : ` at ${approvedAt}`) + ` \xB7 ${expiresAt === "" ? "no expiry" : `expires ${expiresAt}`}`;
2417
+ const approvedAt = str4(grant["approved_at"]);
2418
+ const expiresAt = str4(grant["expires_at"]);
2419
+ return `${str4(grant["status"])} v${str4(grant["version"])} \xB7 approved by ${nameOf(grant["approver_user_id"], people)}` + (approvedAt === "" ? "" : ` at ${approvedAt}`) + ` \xB7 ${expiresAt === "" ? "no expiry" : `expires ${expiresAt}`}`;
3419
2420
  }
3420
2421
  function approvalSection(pending, people) {
3421
2422
  if (!isRecord2(pending)) return "";
@@ -3424,19 +2425,19 @@ function approvalSection(pending, people) {
3424
2425
  return "\nWaiting for approval. Approve it in the app; the CLI cannot:\n" + labelled([
3425
2426
  [
3426
2427
  "Change set",
3427
- `${str5(pending["manifest_id"])} v${str5(pending["version"])} \xB7 ${str5(pending["action_count"])} actions \xB7 up to ${str5(pending["max_effect_count"])} effects \xB7 plan version ${str5(pending["plan_version"])}`
2428
+ `${str4(pending["manifest_id"])} v${str4(pending["version"])} \xB7 ${str4(pending["action_count"])} actions \xB7 up to ${str4(pending["max_effect_count"])} effects \xB7 plan version ${str4(pending["plan_version"])}`
3428
2429
  ],
3429
- ["Digest", str5(pending["digest"]) || null],
2430
+ ["Digest", str4(pending["digest"]) || null],
3430
2431
  ["Approvers", approvers === "" ? canApprove : `${approvers} \xB7 ${canApprove}`],
3431
- ["Approve at", str5(pending["url"]) || null]
2432
+ ["Approve at", str4(pending["url"]) || null]
3432
2433
  ]);
3433
2434
  }
3434
2435
  function peopleSection(people) {
3435
2436
  if (people.length === 0) return "";
3436
2437
  const rows = people.map((person) => [
3437
- str5(person["name"]) || "(no name)",
3438
- str5(person["role"]),
3439
- str5(person["user_id"])
2438
+ str4(person["name"]) || "(no name)",
2439
+ str4(person["role"]),
2440
+ str4(person["user_id"])
3440
2441
  ]);
3441
2442
  const lines = table(["", "", ""], rows).split("\n").slice(1, -1);
3442
2443
  return "\nPeople:\n" + lines.map((line) => ` ${line}
@@ -3445,15 +2446,15 @@ function peopleSection(people) {
3445
2446
  function changesText(id, job, changeSet) {
3446
2447
  if (changeSet === null) return `No pending change set: Operator has proposed nothing for job ${id} yet.
3447
2448
  `;
3448
- const approvalUrl = str5(job["approval_url"]);
3449
- const rows = records3(changeSet["actions"]).map((action) => [
3450
- str5(action["display_order"]) || "-",
3451
- str5(action["action_type"]),
3452
- str5(action["action_version"]),
3453
- str5(action["effect_count"]),
3454
- (Array.isArray(action["required_capability_ids"]) ? action["required_capability_ids"].map(str5).join(", ") : "") || "-",
3455
- str5(action["digest"]),
3456
- str5(action["id"])
2449
+ const approvalUrl = str4(job["approval_url"]);
2450
+ const rows = records2(changeSet["actions"]).map((action) => [
2451
+ str4(action["display_order"]) || "-",
2452
+ str4(action["action_type"]),
2453
+ str4(action["action_version"]),
2454
+ str4(action["effect_count"]),
2455
+ (Array.isArray(action["required_capability_ids"]) ? action["required_capability_ids"].map(str4).join(", ") : "") || "-",
2456
+ str4(action["digest"]),
2457
+ str4(action["id"])
3457
2458
  ]);
3458
2459
  const actions = rows.length === 0 ? "\nActions: none\n" : "\nActions:\n" + table(["#", "ACTION", "VERSION", "EFFECTS", "CAPABILITIES", "DIGEST", "ID"], rows).split("\n").slice(0, -1).map((line) => ` ${line}
3459
2460
  `).join("");
@@ -3461,14 +2462,14 @@ function changesText(id, job, changeSet) {
3461
2462
  ` + labelled([
3462
2463
  [
3463
2464
  "Change set",
3464
- `${str5(changeSet["manifest_id"])} v${str5(changeSet["version"])} \xB7 plan version ${str5(changeSet["plan_version"])} \xB7 up to ${str5(changeSet["max_effect_count"])} effects`
2465
+ `${str4(changeSet["manifest_id"])} v${str4(changeSet["version"])} \xB7 plan version ${str4(changeSet["plan_version"])} \xB7 up to ${str4(changeSet["max_effect_count"])} effects`
3465
2466
  ],
3466
- ["Digest", str5(changeSet["digest"]) || null],
3467
- approvalUrl === "" ? ["Workbench", str5(job["url"]) || null] : ["Approve at", approvalUrl]
2467
+ ["Digest", str4(changeSet["digest"]) || null],
2468
+ approvalUrl === "" ? ["Workbench", str4(job["url"]) || null] : ["Approve at", approvalUrl]
3468
2469
  ]) + actions + "\nValues and targets are not shown: review them in the app at the " + (approvalUrl === "" ? "workbench link.\n" : "approval link. The CLI cannot approve.\n");
3469
2470
  }
3470
2471
  function peopleText(id, people) {
3471
- const one = (entry) => isRecord2(entry) ? str5(entry["name"]) || str5(entry["user_id"]) : "-";
2472
+ const one = (entry) => isRecord2(entry) ? str4(entry["name"]) || str4(entry["user_id"]) : "-";
3472
2473
  const many = (entries) => (Array.isArray(entries) ? entries.map(one).join(", ") : "") || "none";
3473
2474
  return `People on Operator job ${id}:
3474
2475
  ` + labelled(
@@ -3576,7 +2577,7 @@ function plural(count, noun) {
3576
2577
  function uniqueById(jobs) {
3577
2578
  const seen = /* @__PURE__ */ new Set();
3578
2579
  return jobs.filter((job) => {
3579
- const id = str5(job["id"]);
2580
+ const id = str4(job["id"]);
3580
2581
  if (seen.has(id)) return false;
3581
2582
  seen.add(id);
3582
2583
  return true;
@@ -3584,12 +2585,12 @@ function uniqueById(jobs) {
3584
2585
  }
3585
2586
  function personName(person) {
3586
2587
  if (!isRecord2(person)) return "-";
3587
- return str5(person["name"]) || str5(person["user_id"]) || "-";
2588
+ return str4(person["name"]) || str4(person["user_id"]) || "-";
3588
2589
  }
3589
2590
  function nameOf(userId, people) {
3590
- const id = str5(userId);
2591
+ const id = str4(userId);
3591
2592
  const person = people.find((candidate) => candidate["user_id"] === id);
3592
- return (person === void 0 ? "" : str5(person["name"])) || id || "?";
2593
+ return (person === void 0 ? "" : str4(person["name"])) || id || "?";
3593
2594
  }
3594
2595
  function duration(ms) {
3595
2596
  if (typeof ms !== "number" || !Number.isFinite(ms)) return null;
@@ -3615,13 +2616,13 @@ function field2(record, ...keys) {
3615
2616
  }
3616
2617
  return current ?? null;
3617
2618
  }
3618
- function records3(value) {
2619
+ function records2(value) {
3619
2620
  return Array.isArray(value) ? value.filter(isRecord2) : [];
3620
2621
  }
3621
2622
  function isRecord2(value) {
3622
2623
  return typeof value === "object" && value !== null && !Array.isArray(value);
3623
2624
  }
3624
- function str5(value) {
2625
+ function str4(value) {
3625
2626
  return value === null || value === void 0 ? "" : String(value);
3626
2627
  }
3627
2628
 
@@ -3666,9 +2667,9 @@ function outcomeUnknown(why, write2) {
3666
2667
  `Outcome unknown: ${why}, so ${write2.mayHave}. It is not retried; check ${write2.check} before trying again.`
3667
2668
  );
3668
2669
  }
3669
- async function sendOnce(ctx, path2, body, write2) {
2670
+ async function sendOnce(ctx, path, body, write2) {
3670
2671
  try {
3671
- return await ctx.client.post(path2, body, { actor: false });
2672
+ return await ctx.client.post(path, body, { actor: false });
3672
2673
  } catch (error) {
3673
2674
  if (error instanceof TransportError) {
3674
2675
  if (neverSent(error)) {
@@ -3807,14 +2808,14 @@ function startedText(job) {
3807
2808
  async function run(ctx, request, opts = {}) {
3808
2809
  const job = await startRun(ctx, request);
3809
2810
  if (opts.watch !== true) return { json: job, human: startedText(job) };
3810
- const json2 = opts.json === true;
3811
- if (!json2) opts.onLine?.(startedText(job) + "\n");
2811
+ const json = opts.json === true;
2812
+ if (!json) opts.onLine?.(startedText(job) + "\n");
3812
2813
  let watched;
3813
2814
  try {
3814
2815
  watched = await jobWatch(ctx, job.job_id, {
3815
2816
  throughWaits: true,
3816
- collectEvents: json2,
3817
- onLine: json2 ? void 0 : opts.onLine,
2817
+ collectEvents: json,
2818
+ onLine: json ? void 0 : opts.onLine,
3818
2819
  signal: opts.signal,
3819
2820
  sleep: opts.sleep
3820
2821
  });
@@ -3906,18 +2907,18 @@ function resolveTextSources(sources) {
3906
2907
  }
3907
2908
  return resolved;
3908
2909
  }
3909
- function readFileOption(flag, path2) {
3910
- if (path2 === STDIN_PATH) {
2910
+ function readFileOption(flag, path) {
2911
+ if (path === STDIN_PATH) {
3911
2912
  if (isatty(0)) {
3912
2913
  throw new UsageError(`${flag} - reads stdin, but stdin is a terminal \u2014 pipe something in or pass a path`);
3913
2914
  }
3914
2915
  } else {
3915
- checkReadable(path2, flag, DOCUMENT_LIMIT);
2916
+ checkReadable(path, flag, DOCUMENT_LIMIT);
3916
2917
  }
3917
- return readTextFile(path2, flag, DOCUMENT_LIMIT);
2918
+ return readTextFile(path, flag, DOCUMENT_LIMIT);
3918
2919
  }
3919
- function readTextFile(path2, option, limit = TEXT_LIMIT) {
3920
- if (path2 === STDIN_PATH) {
2920
+ function readTextFile(path, option, limit = TEXT_LIMIT) {
2921
+ if (path === STDIN_PATH) {
3921
2922
  let piped;
3922
2923
  try {
3923
2924
  piped = readFileSync2(0);
@@ -3929,10 +2930,10 @@ function readTextFile(path2, option, limit = TEXT_LIMIT) {
3929
2930
  }
3930
2931
  return decodeUtf8(piped, `${option} -`);
3931
2932
  }
3932
- const flag = `${option} ${path2}`;
2933
+ const flag = `${option} ${path}`;
3933
2934
  let bytes;
3934
2935
  try {
3935
- bytes = readFileSync2(path2);
2936
+ bytes = readFileSync2(path);
3936
2937
  } catch (error) {
3937
2938
  throw new UsageError(`${flag}: ${describe(error)}`);
3938
2939
  }
@@ -3949,11 +2950,11 @@ function decodeUtf8(bytes, flag) {
3949
2950
  function oversize(what, limit) {
3950
2951
  return `${what}: over ${limit.bytes / 1e6} MB \u2014 ${limit.why}`;
3951
2952
  }
3952
- function checkReadable(path2, option, limit = TEXT_LIMIT) {
3953
- const flag = `${option} ${path2}`;
2953
+ function checkReadable(path, option, limit = TEXT_LIMIT) {
2954
+ const flag = `${option} ${path}`;
3954
2955
  let stats3;
3955
2956
  try {
3956
- stats3 = statSync3(path2);
2957
+ stats3 = statSync3(path);
3957
2958
  } catch (error) {
3958
2959
  throw new UsageError(`${flag}: ${describe(error)}`);
3959
2960
  }
@@ -4153,7 +3154,7 @@ async function procedureSave(ctx, jobId, name) {
4153
3154
  return {
4154
3155
  json: pick2(data, ["id", "url"]),
4155
3156
  human: `${saved}, but it can't be read back as you.
4156
- ` + labelled([["Workbench", str6(data["url"]) || null]])
3157
+ ` + labelled([["Workbench", str5(data["url"]) || null]])
4157
3158
  };
4158
3159
  }
4159
3160
  const procedure = procedureJson(data);
@@ -4216,18 +3217,18 @@ async function startProcedureRun(ctx, request) {
4216
3217
  return {
4217
3218
  json: pick2(data, ["id", "url"]),
4218
3219
  human: `${started}, but it can't be read back as you.
4219
- ` + labelled([["Workbench", str6(data["url"]) || null]]) + "\n" + RUN_NOTE
3220
+ ` + labelled([["Workbench", str5(data["url"]) || null]]) + "\n" + RUN_NOTE
4220
3221
  };
4221
3222
  }
4222
3223
  const job = withheld(data);
4223
- const waitingOn = str6(job["waiting_on"]);
3224
+ const waitingOn = str5(job["waiting_on"]);
4224
3225
  return {
4225
3226
  json: job,
4226
3227
  human: `${started}.
4227
3228
  ` + labelled([
4228
- ["State", str6(job["state"]) + (waitingOn === "" ? "" : ` \xB7 waiting on ${waitingOn}`)],
4229
- ["Workbench", str6(job["url"]) || null],
4230
- ["Approve at", str6(job["approval_url"]) || null]
3229
+ ["State", str5(job["state"]) + (waitingOn === "" ? "" : ` \xB7 waiting on ${waitingOn}`)],
3230
+ ["Workbench", str5(job["url"]) || null],
3231
+ ["Approve at", str5(job["approval_url"]) || null]
4231
3232
  ]) + "\n" + RUN_NOTE
4232
3233
  };
4233
3234
  }
@@ -4269,16 +3270,16 @@ function jsonValue(text, what) {
4269
3270
  throw new UsageError(`${what}: not valid JSON after := (${firstLine(error)}); quote a string as name=value`);
4270
3271
  }
4271
3272
  }
4272
- function readParamsFile(path2) {
4273
- const text = readFileOption("--params-file", path2);
3273
+ function readParamsFile(path) {
3274
+ const text = readFileOption("--params-file", path);
4274
3275
  let parsed;
4275
3276
  try {
4276
3277
  parsed = JSON.parse(text);
4277
3278
  } catch (error) {
4278
- throw new UsageError(`--params-file ${path2}: not valid JSON (${firstLine(error)})`);
3279
+ throw new UsageError(`--params-file ${path}: not valid JSON (${firstLine(error)})`);
4279
3280
  }
4280
3281
  if (!isRecord4(parsed)) {
4281
- throw new UsageError(`--params-file ${path2}: the parameters are one JSON object of names and values`);
3282
+ throw new UsageError(`--params-file ${path}: the parameters are one JSON object of names and values`);
4282
3283
  }
4283
3284
  return parsed;
4284
3285
  }
@@ -4290,24 +3291,24 @@ function bareId(value, flag) {
4290
3291
  function procedureJson(data) {
4291
3292
  const procedure = pick2(data, PROCEDURE_FIELDS);
4292
3293
  if ("versions" in procedure) {
4293
- procedure["versions"] = records4(procedure["versions"]).map((version2) => pick2(version2, PROCEDURE_VERSION_FIELDS));
3294
+ procedure["versions"] = records3(procedure["versions"]).map((version2) => pick2(version2, PROCEDURE_VERSION_FIELDS));
4294
3295
  }
4295
3296
  return procedure;
4296
3297
  }
4297
3298
  function procedureDetail(procedure) {
4298
3299
  const current = procedure["current_version"];
4299
- const versions = records4(procedure["versions"]).map(
4300
- (version2) => ` ${clean(`v${str6(version2["version"])}${version2["version"] === current ? " (current)" : ""} ${str6(version2["inserted_at"])} recipe ${str6(version2["recipe_digest"])}`)}
3300
+ const versions = records3(procedure["versions"]).map(
3301
+ (version2) => ` ${clean(`v${str5(version2["version"])}${version2["version"] === current ? " (current)" : ""} ${str5(version2["inserted_at"])} recipe ${str5(version2["recipe_digest"])}`)}
4301
3302
  ${"Capabilities:".padEnd(15)}${clean(list2(version2["capability_ids"]))}
4302
3303
  ${"Actions:".padEnd(15)}${clean(list2(version2["action_roster"]))}
4303
3304
  `
4304
3305
  );
4305
3306
  return `Procedure ${clean(procedure["name"])} \xB7 v${clean(current)} \xB7 ${clean(procedure["id"])}
4306
3307
  ` + labelled([
4307
- ["Saved from", `job ${str6(procedure["job_id"])}`],
4308
- ["Workbench", str6(procedure["job_url"]) || null],
4309
- ["Created", str6(procedure["inserted_at"]) || null],
4310
- ["Updated", str6(procedure["updated_at"]) || null]
3308
+ ["Saved from", `job ${str5(procedure["job_id"])}`],
3309
+ ["Workbench", str5(procedure["job_url"]) || null],
3310
+ ["Created", str5(procedure["inserted_at"]) || null],
3311
+ ["Updated", str5(procedure["updated_at"]) || null]
4311
3312
  ]) + (versions.length === 0 ? "\nVersions: none\n" : "\nVersions:\n" + versions.join("")) + parametersSection(procedure["parameter_schema"]) + "\n" + RUN_NOTE;
4312
3313
  }
4313
3314
  function parametersSection(schema) {
@@ -4315,7 +3316,7 @@ function parametersSection(schema) {
4315
3316
  const lines = JSON.stringify(schema, null, 2).split("\n").map((line) => ` ${clean(line)}
4316
3317
  `).join("");
4317
3318
  const scoped = isRecord4(schema["scoped_inputs"]) ? schema["scoped_inputs"]["required"] : void 0;
4318
- const scopes = Array.isArray(scoped) ? scoped.map(str6) : [];
3319
+ const scopes = Array.isArray(scoped) ? scoped.map(str5) : [];
4319
3320
  let needs = "";
4320
3321
  if (scopes.includes("contact_reconciliation")) {
4321
3322
  needs = "Its runs also need --group <contact-group-id> and --source <uploaded-file-id> (contact_reconciliation).\n";
@@ -4326,18 +3327,18 @@ function parametersSection(schema) {
4326
3327
  return "\nParameters a run takes (JSON schema):\n" + lines + needs;
4327
3328
  }
4328
3329
  function list2(value) {
4329
- return (Array.isArray(value) ? value.map(str6).join(", ") : "") || "none";
3330
+ return (Array.isArray(value) ? value.map(str5).join(", ") : "") || "none";
4330
3331
  }
4331
3332
  function pick2(record, keys) {
4332
3333
  return Object.fromEntries(keys.filter((key) => key in record).map((key) => [key, record[key]]));
4333
3334
  }
4334
- function records4(value) {
3335
+ function records3(value) {
4335
3336
  return Array.isArray(value) ? value.filter(isRecord4) : [];
4336
3337
  }
4337
3338
  function isRecord4(value) {
4338
3339
  return typeof value === "object" && value !== null && !Array.isArray(value);
4339
3340
  }
4340
- function str6(value) {
3341
+ function str5(value) {
4341
3342
  return value === null || value === void 0 ? "" : String(value);
4342
3343
  }
4343
3344
  function firstLine(error) {
@@ -4391,8 +3392,8 @@ function jsonSafe(value) {
4391
3392
  function firstLine2(error) {
4392
3393
  return (error instanceof Error ? error.message : String(error)).split("\n")[0] ?? "";
4393
3394
  }
4394
- function readPlanFile(path2, flag = "--file") {
4395
- return parsePlanDocument(readFileOption(flag, path2), path2);
3395
+ function readPlanFile(path, flag = "--file") {
3396
+ return parsePlanDocument(readFileOption(flag, path), path);
4396
3397
  }
4397
3398
  async function planCreate(ctx, plan, opts) {
4398
3399
  requireDocument(plan, "a plan is one object: the root's fields and its steps");
@@ -4405,7 +3406,7 @@ async function planCreate(ctx, plan, opts) {
4405
3406
  throw explainMissing(error, `no team ${team} here, or this Harness server has no plan API yet`);
4406
3407
  }
4407
3408
  const created = body.data;
4408
- const id = str7(created["id"]);
3409
+ const id = str6(created["id"]);
4409
3410
  return {
4410
3411
  json: created,
4411
3412
  human: `Created plan ${clean(id)} (${clean(created["status"]) || "held"})
@@ -4511,7 +3512,7 @@ async function gateSet(ctx, issue, gate, satisfied) {
4511
3512
  }
4512
3513
  }
4513
3514
  const target = list3 === null ? { id: wanted.toLowerCase(), label: wanted.toLowerCase() } : findGate(list3, issue, wanted, expert);
4514
- const id = str7(target["id"]);
3515
+ const id = str6(target["id"]);
4515
3516
  let body;
4516
3517
  try {
4517
3518
  body = await ctx.client.put(`/api/v1/issues/${issue}/conditions/${id}/satisfaction`, { satisfied });
@@ -4602,8 +3603,8 @@ async function explainNotGateOwner(ctx, error, gate, issue, satisfied, allowLook
4602
3603
  const message = satisfied ? `Cannot satisfy ${named2}: only ${subject} can tick it` : `Cannot un-satisfy ${named2}: only ${subject} or Harness staff can untick it`;
4603
3604
  return new HttpError(error.status, message, error.body, error.retryAfter);
4604
3605
  }
4605
- var gateLabel = (condition) => str7(condition["label"]).trim();
4606
- var gateKey = (condition) => str7(condition["key"]);
3606
+ var gateLabel = (condition) => str6(condition["label"]).trim();
3607
+ var gateKey = (condition) => str6(condition["key"]);
4607
3608
  function exactMatches(conditions, wanted) {
4608
3609
  return conditions.filter(
4609
3610
  (condition) => condition["kind"] === "gate" && (gateKey(condition) !== "" && gateKey(condition) === wanted || gateLabel(condition) === wanted)
@@ -4642,7 +3643,7 @@ function findGate(conditions, issue, wanted, expert = false) {
4642
3643
  function isRecord5(value) {
4643
3644
  return typeof value === "object" && value !== null && !Array.isArray(value);
4644
3645
  }
4645
- function str7(value) {
3646
+ function str6(value) {
4646
3647
  return value === null || value === void 0 ? "" : String(value);
4647
3648
  }
4648
3649
 
@@ -4663,287 +3664,7 @@ function isRecord6(value) {
4663
3664
  return typeof value === "object" && value !== null && !Array.isArray(value);
4664
3665
  }
4665
3666
 
4666
- // src/core/credentials.ts
4667
- import { createHash } from "crypto";
4668
- import { chmodSync as chmodSync2, mkdirSync as mkdirSync2, readFileSync as readFileSync3, renameSync as renameSync2, rmSync as rmSync2, writeFileSync as writeFileSync2 } from "fs";
4669
- import { homedir } from "os";
4670
- import { dirname as dirname2, isAbsolute, join as join2 } from "path";
4671
- function defaultCredentialsPath(env = process.env) {
4672
- const xdg = env["XDG_CONFIG_HOME"];
4673
- const base = xdg !== void 0 && xdg !== "" && isAbsolute(xdg) ? xdg : join2(homedir(), ".config");
4674
- return join2(base, "harness", "credentials.json");
4675
- }
4676
- function orgCredentialsPath(path2, baseUrl, org) {
4677
- const url = new URL(baseUrl);
4678
- const host = url.origin + url.pathname.replace(/\/+$/, "");
4679
- const key = createHash("sha256").update(JSON.stringify([path2, host, org.toLowerCase()])).digest("hex");
4680
- return join2(dirname2(path2), `credentials-org-${key}.json`);
4681
- }
4682
- function validOrgId(org) {
4683
- return /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(org);
4684
- }
4685
- function readCredentials(path2) {
4686
- try {
4687
- const parsed = JSON.parse(readFileSync3(path2, "utf8"));
4688
- if (typeof parsed === "object" && parsed !== null) {
4689
- const { url, token } = parsed;
4690
- if (typeof url === "string" && typeof token === "string") return { url, token };
4691
- }
4692
- } catch {
4693
- }
4694
- return null;
4695
- }
4696
- var uniqueCounter = 0;
4697
- function writeCredentials(path2, url, token) {
4698
- mkdirSync2(dirname2(path2), { recursive: true });
4699
- const tempPath = `${path2}.${process.pid}.${++uniqueCounter}.tmp`;
4700
- try {
4701
- writeFileSync2(tempPath, "", { mode: 384 });
4702
- chmodSync2(tempPath, 384);
4703
- writeFileSync2(tempPath, JSON.stringify({ url, token }));
4704
- renameSync2(tempPath, path2);
4705
- } finally {
4706
- rmSync2(tempPath, { force: true });
4707
- }
4708
- }
4709
- function deleteCredentials(path2) {
4710
- rmSync2(path2, { force: true });
4711
- }
4712
-
4713
- // src/core/repo-config.ts
4714
- import { existsSync, mkdirSync as mkdirSync3, readFileSync as readFileSync4, writeFileSync as writeFileSync3 } from "fs";
4715
- import { homedir as homedir2 } from "os";
4716
- import { dirname as dirname3, isAbsolute as isAbsolute2, join as join3, parse } from "path";
4717
- var CONFIG_RELATIVE_PATH = join3(".harness-cli", "config.json");
4718
- function findRepoConfig(startDir = process.cwd()) {
4719
- let dir = startDir;
4720
- const { root } = parse(startDir);
4721
- for (; ; ) {
4722
- const candidate = join3(dir, CONFIG_RELATIVE_PATH);
4723
- if (existsSync(candidate)) {
4724
- return readRepoConfig(candidate);
4725
- }
4726
- if (dir === root) return { config: null, warnings: [] };
4727
- dir = dirname3(dir);
4728
- }
4729
- }
4730
- function readRepoConfig(path2) {
4731
- let parsed;
4732
- try {
4733
- parsed = JSON.parse(readFileSync4(path2, "utf8"));
4734
- } catch (error) {
4735
- const detail = error instanceof Error ? error.message : String(error);
4736
- return { config: null, warnings: [`harness: ignoring malformed ${path2} (${detail})`] };
4737
- }
4738
- if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
4739
- return { config: null, warnings: [`harness: ignoring ${path2} \u2014 expected a JSON object`] };
4740
- }
4741
- const record = parsed;
4742
- const config = { path: path2 };
4743
- const warnings = [];
4744
- for (const field3 of ["team", "url", "org"]) {
4745
- const value = record[field3];
4746
- if (value === void 0) continue;
4747
- if (typeof value === "string" && value.trim() !== "") {
4748
- config[field3] = value.trim();
4749
- } else {
4750
- warnings.push(`harness: ignoring "${field3}" in ${path2} \u2014 expected a non-empty string`);
4751
- }
4752
- }
4753
- return { config, warnings };
4754
- }
4755
- function trustedHostsPath(env = process.env) {
4756
- const xdg = env["XDG_CONFIG_HOME"];
4757
- const base = xdg !== void 0 && xdg !== "" && isAbsolute2(xdg) ? xdg : join3(homedir2(), ".config");
4758
- return join3(base, "harness", "trusted-hosts.json");
4759
- }
4760
- function readTrustedHosts(env) {
4761
- try {
4762
- const parsed = JSON.parse(readFileSync4(trustedHostsPath(env), "utf8"));
4763
- if (typeof parsed === "object" && parsed !== null && Array.isArray(parsed["hosts"])) {
4764
- return parsed["hosts"].filter(
4765
- (host) => typeof host === "string"
4766
- );
4767
- }
4768
- } catch {
4769
- }
4770
- return [];
4771
- }
4772
- function isHostTrusted(baseUrl, env) {
4773
- return readTrustedHosts(env).includes(baseUrl);
4774
- }
4775
- function trustHost(baseUrl, env) {
4776
- const hosts = readTrustedHosts(env);
4777
- if (hosts.includes(baseUrl)) return;
4778
- const path2 = trustedHostsPath(env);
4779
- mkdirSync3(dirname3(path2), { recursive: true });
4780
- writeFileSync3(path2, JSON.stringify({ hosts: [...hosts, baseUrl] }, null, 2) + "\n");
4781
- }
4782
-
4783
- // src/core/config.ts
4784
- import { homedir as homedir3, tmpdir } from "os";
4785
- import { isAbsolute as isAbsolute3, join as join4 } from "path";
4786
- var DEFAULT_BASE_URL = "https://harness.infra.one";
4787
- function resolveConfig(env = process.env, credentialsPath, repo) {
4788
- let warnings = [];
4789
- if (repo === void 0) {
4790
- const lookup = findRepoConfig();
4791
- repo = lookup.config;
4792
- warnings = lookup.warnings;
4793
- }
4794
- const path2 = credentialsPath ?? defaultCredentialsPath(env);
4795
- const stored = readCredentials(path2);
4796
- const envUrl = blankToNull(env["HARNESS_API_URL"]);
4797
- const envToken = blankToNull(env["HARNESS_API_TOKEN"]);
4798
- const repoUrl = repo?.url?.replace(/\/+$/, "") ?? null;
4799
- const org = envToken === null ? repo?.org?.toLowerCase() : void 0;
4800
- if (org !== void 0 && !validOrgId(org)) {
4801
- return { kind: "error", message: `repo org in ${repo?.path} must be an organization UUID`, warnings };
4802
- }
4803
- let rawUrl;
4804
- let hostSource;
4805
- if (envUrl !== null) {
4806
- rawUrl = envUrl;
4807
- hostSource = "env";
4808
- } else if (envToken !== null) {
4809
- if (repoUrl !== null && normalize(repoUrl) !== normalize(DEFAULT_BASE_URL)) {
4810
- return {
4811
- kind: "error",
4812
- message: `this repo's config (${repo?.path}) targets ${repoUrl}, but HARNESS_API_TOKEN only travels to the host the environment names \u2014 set HARNESS_API_URL explicitly to pair them`,
4813
- warnings
4814
- };
4815
- }
4816
- rawUrl = DEFAULT_BASE_URL;
4817
- hostSource = "default";
4818
- } else if (repoUrl !== null) {
4819
- rawUrl = repoUrl;
4820
- hostSource = "repo-config";
4821
- } else if (org !== void 0) {
4822
- rawUrl = DEFAULT_BASE_URL;
4823
- hostSource = "default";
4824
- } else if (stored !== null) {
4825
- rawUrl = stored.url;
4826
- hostSource = "stored-login";
4827
- } else {
4828
- rawUrl = DEFAULT_BASE_URL;
4829
- hostSource = "default";
4830
- }
4831
- const validated = validateBaseUrl(rawUrl);
4832
- if (!validated.ok) return { kind: "error", message: validated.message, warnings };
4833
- const baseUrl = rawUrl.replace(/\/+$/, "");
4834
- const selectedPath = org === void 0 ? path2 : orgCredentialsPath(path2, baseUrl, org);
4835
- const selected = org === void 0 ? stored : readCredentials(selectedPath);
4836
- let token;
4837
- if (envToken !== null) {
4838
- token = envToken;
4839
- } else if (selected !== null && normalize(selected.url) !== null && normalize(selected.url) === normalize(baseUrl)) {
4840
- token = selected.token;
4841
- } else {
4842
- const result = { kind: "needs-login", baseUrl, hostSource, credentialsPath: selectedPath, warnings };
4843
- if (org !== void 0) result.org = org;
4844
- if (hostSource === "repo-config" && repo?.path !== void 0) result.repoConfigPath = repo.path;
4845
- return result;
4846
- }
4847
- return {
4848
- kind: "ok",
4849
- config: {
4850
- baseUrl,
4851
- token,
4852
- actor: blankToNull(env["HARNESS_ACTOR"]),
4853
- // The committed team is the fallback: the environment (and, above it,
4854
- // the --team flag at the command layer) stays authoritative.
4855
- team: blankToNull(env["HARNESS_TEAM"]) ?? repo?.team ?? null,
4856
- cacheDir: resolveCacheDir(env),
4857
- hostSource,
4858
- credentialsPath: selectedPath
4859
- },
4860
- warnings
4861
- };
4862
- }
4863
- function resolveCacheDir(env = process.env) {
4864
- return absolute(env["HARNESS_CACHE_DIR"]) ?? join4(resolveCacheHome(env), "harness", "attachments");
4865
- }
4866
- function absolute(value) {
4867
- return value !== void 0 && value !== "" && isAbsolute3(value) ? value : null;
4868
- }
4869
- function resolveCacheHome(env = process.env) {
4870
- return absolute(env["XDG_CACHE_HOME"]) ?? join4(homedir3() || tmpdir(), ".cache");
4871
- }
4872
- function normalize(url) {
4873
- let parsed;
4874
- try {
4875
- parsed = new URL(url);
4876
- } catch {
4877
- return null;
4878
- }
4879
- const port = parsed.port !== "" ? parsed.port : parsed.protocol === "https:" ? "443" : parsed.protocol === "http:" ? "80" : "";
4880
- return JSON.stringify([parsed.protocol, parsed.hostname, port, parsed.pathname.replace(/\/+$/, "")]);
4881
- }
4882
- function validateBaseUrl(baseUrl) {
4883
- let parsed;
4884
- try {
4885
- parsed = new URL(baseUrl);
4886
- } catch {
4887
- return { ok: false, message: `HARNESS_API_URL must be an http(s) URL with a host, got: ${baseUrl}` };
4888
- }
4889
- if (parsed.username !== "" || parsed.password !== "") {
4890
- return { ok: false, message: "HARNESS_API_URL must not embed credentials (user:pass@) \u2014 use HARNESS_API_TOKEN" };
4891
- }
4892
- if (parsed.search !== "" || parsed.hash !== "") {
4893
- return { ok: false, message: `HARNESS_API_URL must not carry a query or fragment, got: ${baseUrl}` };
4894
- }
4895
- if (parsed.protocol === "https:" && parsed.hostname !== "") return { ok: true };
4896
- if (parsed.protocol === "http:") {
4897
- const host = parsed.hostname;
4898
- if (["localhost", "127.0.0.1", "[::1]"].includes(host) || host.endsWith(".localhost")) return { ok: true };
4899
- return {
4900
- ok: false,
4901
- message: `refusing plain-http HARNESS_API_URL (${baseUrl}) \u2014 the API token would travel in cleartext. Use https, or http against localhost, *.localhost, 127.0.0.1 or [::1] only.`
4902
- };
4903
- }
4904
- return { ok: false, message: `HARNESS_API_URL must be an http(s) URL with a host, got: ${baseUrl}` };
4905
- }
4906
- function loginHelp(baseUrl, name = invokedName()) {
4907
- return `Not signed in to ${baseUrl}.
4908
-
4909
- Interactively, in a terminal (not from inside an agent \u2014 the browser
4910
- tab needs someone to see it):
4911
-
4912
- ${name} login
4913
-
4914
- For agents and CI, mint an API token (admin \u2192 API tokens) with the
4915
- harness:read scope \u2014 plus harness:write only where the workflow
4916
- creates or closes \u2014 set an expiry, and export it:
4917
-
4918
- export HARNESS_API_TOKEN=aone_...
4919
-
4920
- An outside expert uses the pxc_ credential minted in their expert view:
4921
- export it the same way, or paste it into \`${name} login --no-browser\`.
4922
- `;
4923
- }
4924
- function blankToNull(value) {
4925
- if (value === void 0 || value.trim() === "") return null;
4926
- return value;
4927
- }
4928
-
4929
3667
  export {
4930
- HttpError,
4931
- UpgradeRequiredError,
4932
- TransportError,
4933
- UsageError,
4934
- LocalError,
4935
- CommandError,
4936
- EXPERT_ASSIGNMENTS_PATH,
4937
- isExpertToken,
4938
- EXPERT_LAPSED,
4939
- explainExpertRefusal,
4940
- SESSION_PATH,
4941
- isLoginCredential,
4942
- Client,
4943
- clean,
4944
- json,
4945
- expertSummary,
4946
- errorDetails,
4947
3668
  MAX_IDEMPOTENCY_KEY_BYTES,
4948
3669
  IDEMPOTENCY_KEY_FORMAT,
4949
3670
  requireIdentifiers,
@@ -4992,7 +3713,7 @@ export {
4992
3713
  linkAdd,
4993
3714
  linkUpdate,
4994
3715
  linkRm,
4995
- labelList2 as labelList,
3716
+ labelList,
4996
3717
  labelCreate,
4997
3718
  teamCreate,
4998
3719
  teamList,
@@ -5068,19 +3789,5 @@ export {
5068
3789
  gateSet,
5069
3790
  conditionList,
5070
3791
  requirePlanRef,
5071
- assignments,
5072
- defaultCredentialsPath,
5073
- orgCredentialsPath,
5074
- validOrgId,
5075
- readCredentials,
5076
- writeCredentials,
5077
- deleteCredentials,
5078
- findRepoConfig,
5079
- isHostTrusted,
5080
- trustHost,
5081
- DEFAULT_BASE_URL,
5082
- resolveConfig,
5083
- resolveCacheHome,
5084
- validateBaseUrl,
5085
- loginHelp
3792
+ assignments
5086
3793
  };