@allocator-one/harness-cli 1.8.0 → 1.10.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +14 -3
- package/dist/{chunk-NQVQNKMC.js → chunk-KKAAKRKF.js} +161 -4
- package/dist/cli.js +307 -54
- package/dist/login-page-UVUKFFTT.js +292 -0
- package/dist/{server-FWNT3HMX.js → server-QVYSED3Z.js} +14 -1
- package/package.json +1 -1
- package/spec/openapi.json +361 -0
package/dist/cli.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
|
+
ACTIVITY_MAX_LIMIT,
|
|
3
4
|
CLI_VERSION,
|
|
4
5
|
Client,
|
|
5
6
|
CommandError,
|
|
@@ -11,6 +12,7 @@ import {
|
|
|
11
12
|
TransportError,
|
|
12
13
|
UpgradeRequiredError,
|
|
13
14
|
UsageError,
|
|
15
|
+
activity,
|
|
14
16
|
archive,
|
|
15
17
|
attachImage,
|
|
16
18
|
blocked,
|
|
@@ -80,7 +82,7 @@ import {
|
|
|
80
82
|
version,
|
|
81
83
|
workflowStates,
|
|
82
84
|
writeCredentials
|
|
83
|
-
} from "./chunk-
|
|
85
|
+
} from "./chunk-KKAAKRKF.js";
|
|
84
86
|
|
|
85
87
|
// src/cli.ts
|
|
86
88
|
import { createInterface as createInterface2 } from "readline";
|
|
@@ -93,31 +95,15 @@ import { accessSync, constants as fsConstants } from "fs";
|
|
|
93
95
|
import { createServer } from "http";
|
|
94
96
|
import { delimiter, join } from "path";
|
|
95
97
|
import { createInterface } from "readline";
|
|
96
|
-
|
|
97
|
-
// src/core/login-page.ts
|
|
98
|
-
var LOGO = '<svg width="210" height="34" viewBox="0 0 210 34" fill="none" role="img" aria-label="Infra One" xmlns="http://www.w3.org/2000/svg">\n <style>\n path {\n fill: #353535\n }\n\n @media (prefers-color-scheme: dark) {\n path {\n fill: white\n }\n }\n </style>\n <g id="I1_logo_full">\n <g id="Infra One Logo">\n <path fill-rule="evenodd" clip-rule="evenodd" d="M44.2202 29.5139V4.04761H49.617V29.5139H44.2202Z" />\n <path fill-rule="evenodd" clip-rule="evenodd" d="M121.159 11.873V13.998C119.877 12.4464 117.853 11.4345 115.29 11.4345C110.23 11.4345 106.284 15.3809 106.284 20.7103C106.284 26.0397 110.23 29.9861 115.29 29.9861C118.157 29.9861 119.877 28.9742 121.159 27.3214V29.4464H125.948V11.9067H121.159V11.873ZM116.099 25.8373C113.063 25.8373 110.938 23.7123 110.938 20.6091C110.938 17.5059 113.063 15.4484 116.099 15.4484C119.135 15.4484 121.327 17.5734 121.327 20.6091C121.327 23.6448 119.034 25.8373 116.099 25.8373Z" />\n <path d="M15.6508 0H27.8611V34H15.6508V0ZM0 11.131C0 7.92659 2.66468 5.2619 5.86905 5.2619H6.34127C9.54564 5.2619 12.2103 7.92659 12.2103 11.131C12.2103 14.3353 9.54564 17 6.34127 17H5.86905C2.66468 17 0 14.3353 0 11.131Z" />\n <path fill-rule="evenodd" clip-rule="evenodd" d="M88.4405 10.4901C87.6984 10.2203 87.3611 10.0179 86.3155 10.0179C84.8651 10.0179 84.123 10.7599 84.123 12.2103V13.627H89.4524V17.6746H84.123V29.4802H79.3333V17.6746H76.2976V13.627H79.3333V12.2103C79.3333 8.36511 81.1548 5.86908 85.4048 5.86908C87.631 5.86908 88.1706 6.13892 89.621 6.71233L88.4405 10.4901Z" />\n <path fill-rule="evenodd" clip-rule="evenodd" d="M98.0199 14.4365C99.133 12.4127 101.224 11.2321 104.091 11.6032V15.9206C100.246 15.381 98.0199 17.0337 98.0199 21.7897V29.4127H93.2302V11.873H98.0199V14.4365Z" />\n <path fill-rule="evenodd" clip-rule="evenodd" d="M65.5715 11.4008C70.3611 11.4008 72.756 14.5377 72.756 19.2262V29.5139H67.9663V20.0357C67.9663 17.371 66.6845 15.6171 64.1211 15.6171C61.5576 15.6171 59.7024 17.6409 59.7024 20.7778V29.5139H54.9127V11.873H59.7024V14.0655C60.9842 12.5139 62.9068 11.4008 65.5715 11.4008Z" />\n <path fill-rule="evenodd" clip-rule="evenodd" d="M151.01 4.621C143.387 4.621 137.687 10.0516 137.687 17.3035C137.687 24.5555 143.286 29.9861 151.01 29.9861C158.734 29.9861 164.232 24.4881 164.232 17.3035C164.232 10.119 158.633 4.621 151.01 4.621ZM151.01 25.5674C145.95 25.5674 142.746 21.992 142.746 17.3035C142.746 12.6151 145.95 9.10712 151.01 9.10712C156.069 9.10712 159.274 12.7837 159.274 17.3035C159.274 21.8234 155.968 25.5674 151.01 25.5674Z" />\n <path fill-rule="evenodd" clip-rule="evenodd" d="M179.309 11.4008C184.099 11.4008 186.494 14.5377 186.494 19.2262V29.5139H181.704V20.0357C181.704 17.371 180.423 15.6171 177.859 15.6171C175.296 15.6171 173.44 17.6409 173.44 20.7778V29.5139H168.651V11.873H173.44V14.0655C174.722 12.5139 176.645 11.4008 179.309 11.4008Z" />\n <path fill-rule="evenodd" clip-rule="evenodd" d="M209.565 20.2381C209.565 15.1786 206.26 11.3333 200.188 11.3333C194.117 11.3333 190.643 15.3809 190.643 20.6091C190.643 25.8373 194.69 29.8849 200.559 29.8849C206.429 29.8849 208.284 27.119 209.397 24.8254L205.079 23.6448C204.337 24.9266 202.954 25.9385 200.661 25.9385C197.355 25.9385 195.702 24.1845 195.331 21.8909H209.498C209.599 21.3512 209.599 20.7778 209.599 20.2381H209.565ZM195.298 19.125C195.567 17.1012 197.321 15.2797 200.155 15.2797C202.988 15.2797 204.742 17.0337 204.944 19.125H195.298Z" />\n </g>\n </g>\n</svg>';
|
|
99
|
-
function escapeHtml(text) {
|
|
100
|
-
return text.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """);
|
|
101
|
-
}
|
|
102
|
-
function loginPage(message) {
|
|
103
|
-
return `<!doctype html>
|
|
104
|
-
<html lang="en"><head><meta charset="utf-8">
|
|
105
|
-
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
106
|
-
<!-- The logo paints itself white under a dark preference; without this the
|
|
107
|
-
canvas stays white and the logo disappears. -->
|
|
108
|
-
<meta name="color-scheme" content="light dark">
|
|
109
|
-
<title>harness login</title></head>
|
|
110
|
-
<body style="margin: 0;">
|
|
111
|
-
<main style="min-height: 100vh; padding: 1.5rem; display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 2rem; text-align: center; font-family: system-ui, sans-serif;">
|
|
112
|
-
${LOGO}
|
|
113
|
-
<p style="margin: 0;">${escapeHtml(message)}</p>
|
|
114
|
-
</main>
|
|
115
|
-
</body></html>
|
|
116
|
-
`;
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
// src/core/auth.ts
|
|
120
98
|
var DEFAULT_TIMEOUT_MS = 18e4;
|
|
99
|
+
var RESPONSE_DRAIN_GRACE_MS = 100;
|
|
100
|
+
var RESPONSE_FLUSH_TIMEOUT_MS = 2e3;
|
|
101
|
+
var MAX_PAGE_RESPONSES = 4;
|
|
102
|
+
var loginPageImport;
|
|
103
|
+
function loadLoginPage() {
|
|
104
|
+
loginPageImport ??= import("./login-page-UVUKFFTT.js");
|
|
105
|
+
return loginPageImport;
|
|
106
|
+
}
|
|
121
107
|
function generateState() {
|
|
122
108
|
return randomBytes(24).toString("base64url");
|
|
123
109
|
}
|
|
@@ -183,72 +169,313 @@ async function loopbackLogin(baseUrl, options) {
|
|
|
183
169
|
});
|
|
184
170
|
});
|
|
185
171
|
if (port === null) {
|
|
186
|
-
return {
|
|
172
|
+
return {
|
|
173
|
+
ok: false,
|
|
174
|
+
message: "could not open a loopback port for the browser handoff"
|
|
175
|
+
};
|
|
187
176
|
}
|
|
188
177
|
try {
|
|
189
178
|
const query = new URLSearchParams({ port: String(port), state: state2, challenge });
|
|
190
179
|
const url = `${baseUrl}/cli/login?${query.toString()}`;
|
|
180
|
+
const callbackPromise = awaitCallback(server, state2, options.timeoutMs ?? DEFAULT_TIMEOUT_MS);
|
|
181
|
+
void loadLoginPage().catch(() => {
|
|
182
|
+
loginPageImport = void 0;
|
|
183
|
+
});
|
|
191
184
|
const openBrowser = options.openBrowser ?? defaultOpenBrowser;
|
|
192
|
-
|
|
185
|
+
try {
|
|
186
|
+
openBrowser(url);
|
|
187
|
+
} catch (error) {
|
|
188
|
+
const detail = error instanceof Error ? error.message : String(error);
|
|
189
|
+
return { ok: false, message: `could not open a browser: ${detail}` };
|
|
190
|
+
}
|
|
193
191
|
notify(`Opening ${url}`);
|
|
194
192
|
notify("Waiting for the browser authorization (Ctrl-C to abort)\u2026");
|
|
195
|
-
const
|
|
196
|
-
if (!
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
193
|
+
const callback = await callbackPromise;
|
|
194
|
+
if (!callback.ok) return callback;
|
|
195
|
+
try {
|
|
196
|
+
const token = await exchangeCode(baseUrl, callback.code, verifier, options);
|
|
197
|
+
if (!token.ok) {
|
|
198
|
+
await callback.respond(
|
|
199
|
+
400,
|
|
200
|
+
"Harness could not exchange the browser authorization. Return to the terminal for details.",
|
|
201
|
+
"error"
|
|
202
|
+
);
|
|
203
|
+
return token;
|
|
204
|
+
}
|
|
205
|
+
const result = await verifyAndStore(baseUrl, token.token, options);
|
|
206
|
+
if (result.ok) {
|
|
207
|
+
await callback.respond(200, "You can close this browser and return to the terminal.", "success");
|
|
208
|
+
} else {
|
|
209
|
+
await callback.respond(
|
|
210
|
+
400,
|
|
211
|
+
"Harness could not verify and store this login. Return to the terminal for details.",
|
|
212
|
+
"error"
|
|
213
|
+
);
|
|
214
|
+
}
|
|
215
|
+
return result;
|
|
216
|
+
} catch (error) {
|
|
217
|
+
await callback.respond(500, "Harness could not finish the login. Return to the terminal for details.", "error");
|
|
218
|
+
const detail = error instanceof Error ? error.message : String(error);
|
|
219
|
+
return { ok: false, message: `could not finish the browser login: ${detail}` };
|
|
220
|
+
}
|
|
200
221
|
} finally {
|
|
201
|
-
server.closeAllConnections();
|
|
202
222
|
server.close();
|
|
223
|
+
const forceClose = setTimeout(
|
|
224
|
+
() => server.closeAllConnections(),
|
|
225
|
+
RESPONSE_FLUSH_TIMEOUT_MS + RESPONSE_DRAIN_GRACE_MS
|
|
226
|
+
);
|
|
227
|
+
forceClose.unref();
|
|
203
228
|
}
|
|
204
229
|
}
|
|
205
230
|
function awaitCallback(server, state2, timeoutMs) {
|
|
206
231
|
return new Promise((resolve) => {
|
|
232
|
+
let phase = "waiting";
|
|
233
|
+
let finalResponse = null;
|
|
234
|
+
let callbackPageResponses = 0;
|
|
235
|
+
const rootPageResponses = /* @__PURE__ */ new Map();
|
|
236
|
+
const acceptedResponders = [];
|
|
237
|
+
const inFlightResponses = /* @__PURE__ */ new Set();
|
|
238
|
+
const trackResponse = (responsePromise) => {
|
|
239
|
+
inFlightResponses.add(responsePromise);
|
|
240
|
+
void responsePromise.finally(() => inFlightResponses.delete(responsePromise));
|
|
241
|
+
return responsePromise;
|
|
242
|
+
};
|
|
243
|
+
const drainTrackedResponses = async () => {
|
|
244
|
+
await new Promise((settle) => setTimeout(settle, RESPONSE_DRAIN_GRACE_MS));
|
|
245
|
+
const deadline = Date.now() + RESPONSE_FLUSH_TIMEOUT_MS;
|
|
246
|
+
while (inFlightResponses.size > 0 && Date.now() < deadline) {
|
|
247
|
+
const remaining = deadline - Date.now();
|
|
248
|
+
await Promise.race([
|
|
249
|
+
Promise.allSettled([...inFlightResponses]),
|
|
250
|
+
new Promise((settle) => setTimeout(settle, remaining))
|
|
251
|
+
]);
|
|
252
|
+
}
|
|
253
|
+
};
|
|
207
254
|
const timer = setTimeout(() => {
|
|
208
|
-
|
|
255
|
+
if (phase !== "waiting") return;
|
|
256
|
+
const timeoutResponse = {
|
|
257
|
+
status: 410,
|
|
258
|
+
message: "This login attempt has ended \u2014 run harness login again if needed.",
|
|
259
|
+
pageState: "error"
|
|
260
|
+
};
|
|
261
|
+
phase = "terminal";
|
|
262
|
+
finalResponse = timeoutResponse;
|
|
263
|
+
resolve({
|
|
264
|
+
ok: false,
|
|
265
|
+
message: "timed out waiting for the browser authorization"
|
|
266
|
+
});
|
|
267
|
+
void drainTrackedResponses();
|
|
209
268
|
}, timeoutMs);
|
|
269
|
+
server.once("close", () => {
|
|
270
|
+
clearTimeout(timer);
|
|
271
|
+
if (phase !== "waiting") return;
|
|
272
|
+
phase = "terminal";
|
|
273
|
+
resolve({ ok: false, message: "the browser login listener closed before authorization completed" });
|
|
274
|
+
});
|
|
210
275
|
server.on("request", (request, response) => {
|
|
211
|
-
const respond = (
|
|
212
|
-
|
|
213
|
-
|
|
276
|
+
const respond = callbackResponder(response);
|
|
277
|
+
const respondWithPage = (status, message, pageState) => {
|
|
278
|
+
if (callbackPageResponses >= MAX_PAGE_RESPONSES) {
|
|
279
|
+
respondEmpty(response, 429);
|
|
280
|
+
return Promise.resolve();
|
|
281
|
+
}
|
|
282
|
+
callbackPageResponses += 1;
|
|
283
|
+
return respond(status, message, pageState);
|
|
284
|
+
};
|
|
285
|
+
const respondAtRoot = (status, message, pageState) => {
|
|
286
|
+
const rootPhase = phase === "terminal" ? "terminal" : "waiting";
|
|
287
|
+
const responses = rootPageResponses.get(rootPhase) ?? 0;
|
|
288
|
+
if (responses >= MAX_PAGE_RESPONSES) {
|
|
289
|
+
respondEmpty(response, 429);
|
|
290
|
+
return Promise.resolve();
|
|
291
|
+
}
|
|
292
|
+
rootPageResponses.set(rootPhase, responses + 1);
|
|
293
|
+
return respond(status, message, pageState);
|
|
214
294
|
};
|
|
215
295
|
let url;
|
|
216
296
|
try {
|
|
217
297
|
url = new URL(request.url ?? "/", "http://127.0.0.1");
|
|
218
298
|
} catch {
|
|
219
|
-
|
|
299
|
+
respondEmpty(response, 404);
|
|
300
|
+
return;
|
|
301
|
+
}
|
|
302
|
+
const path = url.pathname.replace(/\/+$/, "") || "/";
|
|
303
|
+
if (path !== "/callback") {
|
|
304
|
+
if (path === "/") {
|
|
305
|
+
const rootResponse = phase === "terminal" && finalResponse ? finalResponse : {
|
|
306
|
+
status: 200,
|
|
307
|
+
message: "Waiting for the Harness browser authorization to finish.",
|
|
308
|
+
pageState: "waiting"
|
|
309
|
+
};
|
|
310
|
+
void trackResponse(
|
|
311
|
+
respondAtRoot(rootResponse.status, rootResponse.message, rootResponse.pageState)
|
|
312
|
+
);
|
|
313
|
+
} else {
|
|
314
|
+
respondEmpty(response, path === "/favicon.ico" ? 204 : 404);
|
|
315
|
+
}
|
|
316
|
+
return;
|
|
317
|
+
}
|
|
318
|
+
const matchingState = url.searchParams.get("state") === state2;
|
|
319
|
+
if (!matchingState) {
|
|
320
|
+
respondEmpty(response, 400);
|
|
321
|
+
return;
|
|
322
|
+
}
|
|
323
|
+
if (phase === "terminal") {
|
|
324
|
+
if (!url.searchParams.has("code") && !url.searchParams.has("error")) {
|
|
325
|
+
respondEmpty(response, 400);
|
|
326
|
+
return;
|
|
327
|
+
}
|
|
328
|
+
const result = finalResponse ?? {
|
|
329
|
+
status: 410,
|
|
330
|
+
message: "This login attempt has ended \u2014 run harness login again if needed.",
|
|
331
|
+
pageState: "error"
|
|
332
|
+
};
|
|
333
|
+
void trackResponse(respondWithPage(result.status, result.message, result.pageState));
|
|
334
|
+
return;
|
|
335
|
+
}
|
|
336
|
+
if (phase === "processing") {
|
|
337
|
+
if (url.searchParams.has("error")) {
|
|
338
|
+
respondEmpty(response, 409);
|
|
339
|
+
return;
|
|
340
|
+
}
|
|
341
|
+
const duplicateCode = url.searchParams.get("code");
|
|
342
|
+
if (duplicateCode === null || duplicateCode === "") {
|
|
343
|
+
respondEmpty(response, 400);
|
|
344
|
+
return;
|
|
345
|
+
}
|
|
346
|
+
if (acceptedResponders.length >= MAX_PAGE_RESPONSES) {
|
|
347
|
+
respondEmpty(response, 429);
|
|
348
|
+
return;
|
|
349
|
+
}
|
|
350
|
+
acceptedResponders.push(respondWithPage);
|
|
220
351
|
return;
|
|
221
352
|
}
|
|
222
|
-
if (url.
|
|
223
|
-
|
|
353
|
+
if (url.searchParams.has("error")) {
|
|
354
|
+
const denialResponse = {
|
|
355
|
+
status: 400,
|
|
356
|
+
message: "Browser authorization was denied. Return to the terminal to try again.",
|
|
357
|
+
pageState: "error"
|
|
358
|
+
};
|
|
359
|
+
phase = "terminal";
|
|
360
|
+
finalResponse = denialResponse;
|
|
361
|
+
clearTimeout(timer);
|
|
362
|
+
void (async () => {
|
|
363
|
+
await trackResponse(respondWithPage(denialResponse.status, denialResponse.message, denialResponse.pageState));
|
|
364
|
+
await drainTrackedResponses();
|
|
365
|
+
resolve({ ok: false, message: "browser authorization was denied" });
|
|
366
|
+
})();
|
|
224
367
|
return;
|
|
225
368
|
}
|
|
226
369
|
const code = url.searchParams.get("code");
|
|
227
|
-
if (
|
|
228
|
-
|
|
370
|
+
if (code !== null && code !== "") {
|
|
371
|
+
phase = "processing";
|
|
372
|
+
acceptedResponders.push(respondWithPage);
|
|
229
373
|
clearTimeout(timer);
|
|
230
|
-
resolve({
|
|
374
|
+
resolve({
|
|
375
|
+
ok: true,
|
|
376
|
+
code,
|
|
377
|
+
respond: async (status, message, pageState) => {
|
|
378
|
+
finalResponse = { status, message, pageState };
|
|
379
|
+
phase = "terminal";
|
|
380
|
+
const responders = acceptedResponders.splice(0);
|
|
381
|
+
await Promise.all(
|
|
382
|
+
responders.map((acceptedRespond) => trackResponse(acceptedRespond(status, message, pageState)))
|
|
383
|
+
);
|
|
384
|
+
await drainTrackedResponses();
|
|
385
|
+
}
|
|
386
|
+
});
|
|
231
387
|
} else {
|
|
232
|
-
|
|
388
|
+
respondEmpty(response, 400);
|
|
233
389
|
}
|
|
234
390
|
});
|
|
235
391
|
});
|
|
236
392
|
}
|
|
393
|
+
function callbackResponder(response) {
|
|
394
|
+
return async (status, message, state2) => {
|
|
395
|
+
if (response.writableEnded || response.destroyed) return;
|
|
396
|
+
return new Promise((resolve) => {
|
|
397
|
+
let flushTimer;
|
|
398
|
+
const armFlushTimeout = () => {
|
|
399
|
+
if (flushTimer !== void 0) clearTimeout(flushTimer);
|
|
400
|
+
flushTimer = setTimeout(() => {
|
|
401
|
+
response.destroy();
|
|
402
|
+
settle();
|
|
403
|
+
}, RESPONSE_FLUSH_TIMEOUT_MS);
|
|
404
|
+
flushTimer.unref();
|
|
405
|
+
};
|
|
406
|
+
const settle = () => {
|
|
407
|
+
if (flushTimer !== void 0) clearTimeout(flushTimer);
|
|
408
|
+
response.off("finish", settle);
|
|
409
|
+
response.off("close", settle);
|
|
410
|
+
response.off("error", settle);
|
|
411
|
+
response.off("drain", armFlushTimeout);
|
|
412
|
+
resolve();
|
|
413
|
+
};
|
|
414
|
+
response.once("finish", settle);
|
|
415
|
+
response.once("close", settle);
|
|
416
|
+
response.once("error", settle);
|
|
417
|
+
response.on("drain", armFlushTimeout);
|
|
418
|
+
void loadLoginPage().then(({ loginPage }) => {
|
|
419
|
+
if (response.writableEnded || response.destroyed) {
|
|
420
|
+
settle();
|
|
421
|
+
return;
|
|
422
|
+
}
|
|
423
|
+
try {
|
|
424
|
+
const html = loginPage(message, state2);
|
|
425
|
+
response.writeHead(status, {
|
|
426
|
+
"cache-control": "no-store",
|
|
427
|
+
connection: "close",
|
|
428
|
+
"content-length": String(Buffer.byteLength(html)),
|
|
429
|
+
"content-type": "text/html; charset=utf-8",
|
|
430
|
+
"x-content-type-options": "nosniff"
|
|
431
|
+
});
|
|
432
|
+
armFlushTimeout();
|
|
433
|
+
response.end(html);
|
|
434
|
+
} catch {
|
|
435
|
+
response.destroy();
|
|
436
|
+
settle();
|
|
437
|
+
}
|
|
438
|
+
}).catch(() => {
|
|
439
|
+
response.destroy();
|
|
440
|
+
settle();
|
|
441
|
+
});
|
|
442
|
+
});
|
|
443
|
+
};
|
|
444
|
+
}
|
|
445
|
+
function respondEmpty(response, status) {
|
|
446
|
+
response.once("error", () => void 0);
|
|
447
|
+
try {
|
|
448
|
+
response.writeHead(status, {
|
|
449
|
+
"cache-control": "no-store",
|
|
450
|
+
connection: "close",
|
|
451
|
+
"x-content-type-options": "nosniff"
|
|
452
|
+
});
|
|
453
|
+
response.end();
|
|
454
|
+
} catch {
|
|
455
|
+
response.destroy();
|
|
456
|
+
}
|
|
457
|
+
}
|
|
237
458
|
async function exchangeCode(baseUrl, code, verifier, options) {
|
|
238
459
|
try {
|
|
239
|
-
const body = await bareClient(baseUrl, "", options).post(
|
|
240
|
-
"/api/v1/cli/token-exchange",
|
|
241
|
-
{ code, code_verifier: verifier }
|
|
242
|
-
);
|
|
460
|
+
const body = await bareClient(baseUrl, "", options).post("/api/v1/cli/token-exchange", { code, code_verifier: verifier });
|
|
243
461
|
const token = body.data?.token;
|
|
244
462
|
if (typeof token === "string" && token !== "") return { ok: true, token };
|
|
245
|
-
return {
|
|
463
|
+
return {
|
|
464
|
+
ok: false,
|
|
465
|
+
message: "the login-code exchange returned no token \u2014 aborting"
|
|
466
|
+
};
|
|
246
467
|
} catch (error) {
|
|
247
468
|
if (error instanceof HttpError) {
|
|
248
|
-
return {
|
|
469
|
+
return {
|
|
470
|
+
ok: false,
|
|
471
|
+
message: `the login-code exchange failed: ${error.message}`
|
|
472
|
+
};
|
|
249
473
|
}
|
|
250
474
|
if (error instanceof TransportError) {
|
|
251
|
-
return {
|
|
475
|
+
return {
|
|
476
|
+
ok: false,
|
|
477
|
+
message: `the login-code exchange failed: cannot reach the API (${error.message})`
|
|
478
|
+
};
|
|
252
479
|
}
|
|
253
480
|
throw error;
|
|
254
481
|
}
|
|
@@ -288,7 +515,10 @@ async function verifyAndStore(baseUrl, token, options) {
|
|
|
288
515
|
} catch (error) {
|
|
289
516
|
await client.delete("/api/v1/session").catch(() => void 0);
|
|
290
517
|
const detail = error instanceof Error ? error.message : String(error);
|
|
291
|
-
return {
|
|
518
|
+
return {
|
|
519
|
+
ok: false,
|
|
520
|
+
message: `could not store credentials at ${path}: ${detail}`
|
|
521
|
+
};
|
|
292
522
|
}
|
|
293
523
|
if (superseded !== null) {
|
|
294
524
|
await bareClient(superseded.url, superseded.token, options).delete("/api/v1/session").catch(() => void 0);
|
|
@@ -378,7 +608,10 @@ function defaultNotify(line) {
|
|
|
378
608
|
process.stderr.write(line + "\n");
|
|
379
609
|
}
|
|
380
610
|
function defaultPrompt() {
|
|
381
|
-
const readline = createInterface({
|
|
611
|
+
const readline = createInterface({
|
|
612
|
+
input: process.stdin,
|
|
613
|
+
output: process.stderr
|
|
614
|
+
});
|
|
382
615
|
return new Promise((resolve) => {
|
|
383
616
|
readline.question("Token: ", (answer) => {
|
|
384
617
|
readline.close();
|
|
@@ -1007,6 +1240,15 @@ function integer(value) {
|
|
|
1007
1240
|
throw new InvalidArgumentError("expected a non-negative integer");
|
|
1008
1241
|
return Number.parseInt(value, 10);
|
|
1009
1242
|
}
|
|
1243
|
+
function activityLimit(value) {
|
|
1244
|
+
const limit = integer(value);
|
|
1245
|
+
if (limit < 1 || limit > ACTIVITY_MAX_LIMIT) {
|
|
1246
|
+
throw new InvalidArgumentError(
|
|
1247
|
+
`expected an integer from 1 to ${ACTIVITY_MAX_LIMIT}`
|
|
1248
|
+
);
|
|
1249
|
+
}
|
|
1250
|
+
return limit;
|
|
1251
|
+
}
|
|
1010
1252
|
function collect(value, previous) {
|
|
1011
1253
|
return [...previous, value];
|
|
1012
1254
|
}
|
|
@@ -1193,6 +1435,17 @@ program.command("show").description("full issue detail; downloads attached image
|
|
|
1193
1435
|
});
|
|
1194
1436
|
}
|
|
1195
1437
|
);
|
|
1438
|
+
program.command("activity").description("mixed issue activity: comments, creation, and state changes").argument("[id]").option(
|
|
1439
|
+
"--limit <n>",
|
|
1440
|
+
`entries per page (default 20, max ${ACTIVITY_MAX_LIMIT})`,
|
|
1441
|
+
activityLimit
|
|
1442
|
+
).option("--all", "read all older activity").option("--json", "emit JSON").action(async (id, opts) => {
|
|
1443
|
+
await withConfig(opts.json === true, false, (ctx) => {
|
|
1444
|
+
if (id === void 0) throw new UsageError("usage: harness activity <id>");
|
|
1445
|
+
requireIdentifiers2([id]);
|
|
1446
|
+
return activity(ctx, id, opts);
|
|
1447
|
+
});
|
|
1448
|
+
});
|
|
1196
1449
|
program.command("stats").description("per-team and total counts").option("--json", "emit JSON").action(async (opts) => {
|
|
1197
1450
|
await withConfig(opts.json === true, false, (ctx) => stats(ctx));
|
|
1198
1451
|
});
|
|
@@ -1658,7 +1911,7 @@ program.command("upload").description("upload a file; prints markdown to referen
|
|
|
1658
1911
|
});
|
|
1659
1912
|
});
|
|
1660
1913
|
program.command("mcp").description("run the MCP server over stdio (for agents)").action(async () => {
|
|
1661
|
-
const { startMcpServer } = await import("./server-
|
|
1914
|
+
const { startMcpServer } = await import("./server-QVYSED3Z.js");
|
|
1662
1915
|
await startMcpServer();
|
|
1663
1916
|
});
|
|
1664
1917
|
program.command("login").description("sign in via the browser (infra.one session)").option("--url <url>", "host to sign in to").option("--no-browser", "print the URL and prompt for a pasted token instead").option(
|