@ricsam/r5dctl 0.0.51 → 0.0.54
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 +6 -2
- package/dist/cjs/cli.cjs +111 -18
- package/dist/cjs/package.json +1 -1
- package/dist/mjs/cli.mjs +105 -17
- package/dist/mjs/package.json +1 -1
- package/dist/types/cli.d.ts +11 -0
- package/package.json +3 -2
package/README.md
CHANGED
|
@@ -14,9 +14,13 @@ npm install -g @ricsam/r5dctl
|
|
|
14
14
|
r5dctl auth login
|
|
15
15
|
```
|
|
16
16
|
|
|
17
|
-
This starts a device-
|
|
17
|
+
This starts a device-authorization flow, prints an eight-character code and QR,
|
|
18
|
+
and opens `https://r5d.dev/auth`. Approve the matching device details in the
|
|
19
|
+
browser before the credential is saved.
|
|
18
20
|
|
|
19
|
-
- `
|
|
21
|
+
Use `--no-open` when no host browser is available, `--no-qr` to print only the
|
|
22
|
+
short code, `--device-name <name>` to label the authorized device, and
|
|
23
|
+
`--worker-label <label>` to record the worker it is intended to run.
|
|
20
24
|
|
|
21
25
|
Credentials are stored in:
|
|
22
26
|
|
package/dist/cjs/cli.cjs
CHANGED
|
@@ -28,6 +28,7 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
|
|
|
28
28
|
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
29
29
|
var cli_exports = {};
|
|
30
30
|
__export(cli_exports, {
|
|
31
|
+
advanceTransientDevicePollBackoff: () => advanceTransientDevicePollBackoff,
|
|
31
32
|
collectK8sUsage: () => collectK8sUsage,
|
|
32
33
|
formatK8sCpu: () => formatK8sCpu,
|
|
33
34
|
formatK8sMemory: () => formatK8sMemory,
|
|
@@ -36,6 +37,8 @@ __export(cli_exports, {
|
|
|
36
37
|
getK8sUsageState: () => getK8sUsageState,
|
|
37
38
|
getProjectEnvValue: () => getProjectEnvValue,
|
|
38
39
|
getR5dctlVersion: () => getR5dctlVersion,
|
|
40
|
+
getTransientDevicePollDelay: () => getTransientDevicePollDelay,
|
|
41
|
+
handleAuthLogin: () => handleAuthLogin,
|
|
39
42
|
main: () => main,
|
|
40
43
|
parseAnswerFlags: () => parseAnswerFlags,
|
|
41
44
|
parseConversationRenderArgs: () => parseConversationRenderArgs,
|
|
@@ -55,6 +58,7 @@ __export(cli_exports, {
|
|
|
55
58
|
renderConversationOverviewResponse: () => renderConversationOverviewResponse,
|
|
56
59
|
renderConversationResponse: () => renderConversationResponse,
|
|
57
60
|
renderConversationWorkResponse: () => renderConversationWorkResponse,
|
|
61
|
+
renderDeviceAuthorizationPrompt: () => renderDeviceAuthorizationPrompt,
|
|
58
62
|
renderEnvValues: () => renderEnvValues,
|
|
59
63
|
renderK8sUsageReport: () => renderK8sUsageReport,
|
|
60
64
|
renderProcessHistory: () => renderProcessHistory,
|
|
@@ -64,13 +68,16 @@ __export(cli_exports, {
|
|
|
64
68
|
renderWorkspaceSync: () => renderWorkspaceSync,
|
|
65
69
|
resolveCommandExecution: () => resolveCommandExecution,
|
|
66
70
|
runR5dctlCli: () => runR5dctlCli,
|
|
67
|
-
summarizeEnvData: () => summarizeEnvData
|
|
71
|
+
summarizeEnvData: () => summarizeEnvData,
|
|
72
|
+
writeConfig: () => writeConfig
|
|
68
73
|
});
|
|
69
74
|
module.exports = __toCommonJS(cli_exports);
|
|
70
75
|
var import_node_fs = __toESM(require("node:fs"), 1);
|
|
71
76
|
var import_node_os = __toESM(require("node:os"), 1);
|
|
72
77
|
var import_node_path = __toESM(require("node:path"), 1);
|
|
73
78
|
var import_node_child_process = require("node:child_process");
|
|
79
|
+
var import_node_crypto = require("node:crypto");
|
|
80
|
+
var import_qrcode = __toESM(require("qrcode"), 1);
|
|
74
81
|
var import_promises = require("node:timers/promises");
|
|
75
82
|
var import_dotenv = require("dotenv");
|
|
76
83
|
var import_r5d_api = require("@ricsam/r5d-api");
|
|
@@ -105,7 +112,7 @@ const CLI_GLOBAL_OPTION_HELP = [
|
|
|
105
112
|
const CLI_ONLY_HELP_ENTRIES = [
|
|
106
113
|
{
|
|
107
114
|
section: "auth",
|
|
108
|
-
usage: "auth login [--no-open]",
|
|
115
|
+
usage: "auth login [--no-open] [--no-qr] [--device-name <name>] [--worker-label <label>]",
|
|
109
116
|
description: "Start the browser login flow."
|
|
110
117
|
},
|
|
111
118
|
{
|
|
@@ -117,7 +124,7 @@ const CLI_ONLY_HELP_ENTRIES = [
|
|
|
117
124
|
const CLI_ONLY_COMMAND_HELP = [
|
|
118
125
|
{
|
|
119
126
|
path: ["auth", "login"],
|
|
120
|
-
usage: "auth login [--no-open]"
|
|
127
|
+
usage: "auth login [--no-open] [--no-qr] [--device-name <name>] [--worker-label <label>]"
|
|
121
128
|
},
|
|
122
129
|
{
|
|
123
130
|
path: ["shell"],
|
|
@@ -449,6 +456,21 @@ function parseOptionalFlagValue(args, flag, shortFlag) {
|
|
|
449
456
|
function hasBooleanFlag(args, flag) {
|
|
450
457
|
return args.includes(flag);
|
|
451
458
|
}
|
|
459
|
+
function assertAuthLoginArgs(args) {
|
|
460
|
+
const valueFlags = /* @__PURE__ */ new Set(["--device-name", "--worker-label"]);
|
|
461
|
+
const booleanFlags = /* @__PURE__ */ new Set(["--no-open", "--no-qr"]);
|
|
462
|
+
for (let index = 0; index < args.length; index += 1) {
|
|
463
|
+
const arg = args[index];
|
|
464
|
+
if (booleanFlags.has(arg)) continue;
|
|
465
|
+
if (valueFlags.has(arg)) {
|
|
466
|
+
if (!args[index + 1]) throw new Error(`Missing value for ${arg}`);
|
|
467
|
+
index += 1;
|
|
468
|
+
continue;
|
|
469
|
+
}
|
|
470
|
+
if ([...valueFlags].some((flag) => arg.startsWith(`${flag}=`))) continue;
|
|
471
|
+
throw new Error(`Unknown auth login flag: ${arg}`);
|
|
472
|
+
}
|
|
473
|
+
}
|
|
452
474
|
function parseGlobalArgs(argv) {
|
|
453
475
|
const options = {
|
|
454
476
|
json: false,
|
|
@@ -580,12 +602,27 @@ function serializeConfig(config) {
|
|
|
580
602
|
function writeConfig(configPath, config) {
|
|
581
603
|
const dir = import_node_path.default.dirname(configPath);
|
|
582
604
|
import_node_fs.default.mkdirSync(dir, { recursive: true, mode: 448 });
|
|
605
|
+
import_node_fs.default.chmodSync(dir, 448);
|
|
583
606
|
const serialized = serializeConfig(config);
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
607
|
+
const tempPath = import_node_path.default.join(dir, `.${import_node_path.default.basename(configPath)}.${process.pid}.${(0, import_node_crypto.randomUUID)()}.tmp`);
|
|
608
|
+
let descriptor;
|
|
609
|
+
try {
|
|
610
|
+
descriptor = import_node_fs.default.openSync(tempPath, "wx", 384);
|
|
611
|
+
import_node_fs.default.writeFileSync(descriptor, `${JSON.stringify(serialized, null, 2)}
|
|
612
|
+
`, "utf8");
|
|
613
|
+
import_node_fs.default.fsyncSync(descriptor);
|
|
614
|
+
import_node_fs.default.closeSync(descriptor);
|
|
615
|
+
descriptor = void 0;
|
|
616
|
+
import_node_fs.default.renameSync(tempPath, configPath);
|
|
617
|
+
import_node_fs.default.chmodSync(configPath, 384);
|
|
618
|
+
} catch (error) {
|
|
619
|
+
if (descriptor !== void 0) import_node_fs.default.closeSync(descriptor);
|
|
620
|
+
try {
|
|
621
|
+
import_node_fs.default.unlinkSync(tempPath);
|
|
622
|
+
} catch {
|
|
623
|
+
}
|
|
624
|
+
throw error;
|
|
625
|
+
}
|
|
589
626
|
}
|
|
590
627
|
function resolveClientOptions(options, config) {
|
|
591
628
|
return {
|
|
@@ -1019,41 +1056,89 @@ function createSavedApiKeyConfig(options, config, apiKey) {
|
|
|
1019
1056
|
token: void 0
|
|
1020
1057
|
};
|
|
1021
1058
|
}
|
|
1059
|
+
function renderDeviceAuthorizationPrompt(input) {
|
|
1060
|
+
const codeBorder = "\u2500".repeat(input.userCode.length + 4);
|
|
1061
|
+
return `Authorize this device at:
|
|
1062
|
+
${input.verificationUri}
|
|
1063
|
+
|
|
1064
|
+
\u250C${codeBorder}\u2510
|
|
1065
|
+
\u2502 ${input.userCode} \u2502
|
|
1066
|
+
\u2514${codeBorder}\u2518
|
|
1067
|
+
`;
|
|
1068
|
+
}
|
|
1069
|
+
function getTransientDevicePollDelay(intervalMs, transientBackoffMs) {
|
|
1070
|
+
return Math.min(3e4, Math.max(intervalMs, transientBackoffMs));
|
|
1071
|
+
}
|
|
1072
|
+
function advanceTransientDevicePollBackoff(intervalMs, transientBackoffMs) {
|
|
1073
|
+
return {
|
|
1074
|
+
delayMs: getTransientDevicePollDelay(intervalMs, transientBackoffMs),
|
|
1075
|
+
nextBackoffMs: Math.min(3e4, transientBackoffMs * 2)
|
|
1076
|
+
};
|
|
1077
|
+
}
|
|
1022
1078
|
async function handleAuthLogin(client, options, commandArgs, config) {
|
|
1079
|
+
assertAuthLoginArgs(commandArgs);
|
|
1023
1080
|
const noOpen = hasBooleanFlag(commandArgs, "--no-open");
|
|
1024
|
-
const
|
|
1025
|
-
|
|
1026
|
-
|
|
1081
|
+
const noQr = hasBooleanFlag(commandArgs, "--no-qr");
|
|
1082
|
+
const deviceName = parseOptionalFlagValue(commandArgs, "--device-name") ?? `${import_node_os.default.hostname()} r5dctl`;
|
|
1083
|
+
const workerLabel = parseOptionalFlagValue(commandArgs, "--worker-label");
|
|
1084
|
+
const start = await client.auth.deviceStart({ deviceName, workerLabel });
|
|
1027
1085
|
if (options.json) {
|
|
1028
1086
|
writeDataOutput(
|
|
1029
1087
|
true,
|
|
1030
1088
|
{
|
|
1031
1089
|
status: "pending",
|
|
1032
1090
|
requestId: start.requestId,
|
|
1033
|
-
|
|
1091
|
+
userCode: start.userCode,
|
|
1092
|
+
verificationUri: start.verificationUri,
|
|
1093
|
+
verificationUriComplete: start.verificationUriComplete,
|
|
1034
1094
|
expiresAt: start.expiresAt,
|
|
1035
1095
|
intervalMs: start.intervalMs
|
|
1036
1096
|
},
|
|
1037
1097
|
""
|
|
1038
1098
|
);
|
|
1039
1099
|
} else {
|
|
1040
|
-
process.stdout.write(
|
|
1041
|
-
|
|
1100
|
+
process.stdout.write(renderDeviceAuthorizationPrompt(start));
|
|
1101
|
+
if (!noQr) {
|
|
1102
|
+
process.stdout.write(`
|
|
1103
|
+
${await import_qrcode.default.toString(start.verificationUriComplete, { type: "terminal", small: true })}
|
|
1042
1104
|
`);
|
|
1105
|
+
}
|
|
1043
1106
|
}
|
|
1044
1107
|
if (!noOpen) {
|
|
1045
1108
|
try {
|
|
1046
|
-
await openInBrowser(start.
|
|
1109
|
+
await openInBrowser(start.verificationUriComplete);
|
|
1047
1110
|
} catch {
|
|
1048
1111
|
}
|
|
1049
1112
|
}
|
|
1050
1113
|
const expiresAtMs = new Date(start.expiresAt).getTime();
|
|
1051
1114
|
let intervalMs = start.intervalMs;
|
|
1115
|
+
let nextDelayMs = intervalMs;
|
|
1116
|
+
let transientBackoffMs = 1e3;
|
|
1052
1117
|
while (Date.now() < expiresAtMs) {
|
|
1053
|
-
await (0, import_promises.setTimeout)(
|
|
1054
|
-
|
|
1118
|
+
await (0, import_promises.setTimeout)(Math.min(nextDelayMs, Math.max(0, expiresAtMs - Date.now())));
|
|
1119
|
+
let polled;
|
|
1120
|
+
try {
|
|
1121
|
+
polled = await client.auth.devicePoll(start.requestId, { deviceCode: start.deviceCode });
|
|
1122
|
+
} catch (error) {
|
|
1123
|
+
if (error instanceof import_r5d_api.R5dctlApiError && error.status === 429) {
|
|
1124
|
+
const body = error.body;
|
|
1125
|
+
if (typeof body?.intervalMs === "number") intervalMs = body.intervalMs;
|
|
1126
|
+
nextDelayMs = intervalMs;
|
|
1127
|
+
continue;
|
|
1128
|
+
}
|
|
1129
|
+
const transient = !(error instanceof import_r5d_api.R5dctlApiError) || error.status === 408 || error.status >= 500;
|
|
1130
|
+
if (transient) {
|
|
1131
|
+
const retry = advanceTransientDevicePollBackoff(intervalMs, transientBackoffMs);
|
|
1132
|
+
nextDelayMs = retry.delayMs;
|
|
1133
|
+
transientBackoffMs = retry.nextBackoffMs;
|
|
1134
|
+
continue;
|
|
1135
|
+
}
|
|
1136
|
+
throw error;
|
|
1137
|
+
}
|
|
1138
|
+
transientBackoffMs = 1e3;
|
|
1055
1139
|
if (polled.status === "pending") {
|
|
1056
1140
|
intervalMs = polled.intervalMs;
|
|
1141
|
+
nextDelayMs = intervalMs;
|
|
1057
1142
|
continue;
|
|
1058
1143
|
}
|
|
1059
1144
|
if (polled.status === "approved") {
|
|
@@ -1078,6 +1163,9 @@ ${start.loginUrl}
|
|
|
1078
1163
|
if (polled.status === "expired") {
|
|
1079
1164
|
throw new Error("Login request expired. Run `r5dctl auth login` again.");
|
|
1080
1165
|
}
|
|
1166
|
+
if (polled.status === "denied") {
|
|
1167
|
+
throw new Error("Device authorization denied.");
|
|
1168
|
+
}
|
|
1081
1169
|
if (polled.status === "consumed") {
|
|
1082
1170
|
throw new Error("Login request was already consumed.");
|
|
1083
1171
|
}
|
|
@@ -2584,6 +2672,7 @@ async function main(argv = process.argv.slice(2)) {
|
|
|
2584
2672
|
}
|
|
2585
2673
|
// Annotate the CommonJS export names for ESM import in node:
|
|
2586
2674
|
0 && (module.exports = {
|
|
2675
|
+
advanceTransientDevicePollBackoff,
|
|
2587
2676
|
collectK8sUsage,
|
|
2588
2677
|
formatK8sCpu,
|
|
2589
2678
|
formatK8sMemory,
|
|
@@ -2592,6 +2681,8 @@ async function main(argv = process.argv.slice(2)) {
|
|
|
2592
2681
|
getK8sUsageState,
|
|
2593
2682
|
getProjectEnvValue,
|
|
2594
2683
|
getR5dctlVersion,
|
|
2684
|
+
getTransientDevicePollDelay,
|
|
2685
|
+
handleAuthLogin,
|
|
2595
2686
|
main,
|
|
2596
2687
|
parseAnswerFlags,
|
|
2597
2688
|
parseConversationRenderArgs,
|
|
@@ -2611,6 +2702,7 @@ async function main(argv = process.argv.slice(2)) {
|
|
|
2611
2702
|
renderConversationOverviewResponse,
|
|
2612
2703
|
renderConversationResponse,
|
|
2613
2704
|
renderConversationWorkResponse,
|
|
2705
|
+
renderDeviceAuthorizationPrompt,
|
|
2614
2706
|
renderEnvValues,
|
|
2615
2707
|
renderK8sUsageReport,
|
|
2616
2708
|
renderProcessHistory,
|
|
@@ -2620,5 +2712,6 @@ async function main(argv = process.argv.slice(2)) {
|
|
|
2620
2712
|
renderWorkspaceSync,
|
|
2621
2713
|
resolveCommandExecution,
|
|
2622
2714
|
runR5dctlCli,
|
|
2623
|
-
summarizeEnvData
|
|
2715
|
+
summarizeEnvData,
|
|
2716
|
+
writeConfig
|
|
2624
2717
|
});
|
package/dist/cjs/package.json
CHANGED
package/dist/mjs/cli.mjs
CHANGED
|
@@ -2,6 +2,8 @@ import fs from "node:fs";
|
|
|
2
2
|
import os from "node:os";
|
|
3
3
|
import path from "node:path";
|
|
4
4
|
import { spawn } from "node:child_process";
|
|
5
|
+
import { randomUUID } from "node:crypto";
|
|
6
|
+
import QRCode from "qrcode";
|
|
5
7
|
import { setTimeout as sleep } from "node:timers/promises";
|
|
6
8
|
import { parse as parseDotenv } from "dotenv";
|
|
7
9
|
import {
|
|
@@ -39,7 +41,7 @@ const CLI_GLOBAL_OPTION_HELP = [
|
|
|
39
41
|
const CLI_ONLY_HELP_ENTRIES = [
|
|
40
42
|
{
|
|
41
43
|
section: "auth",
|
|
42
|
-
usage: "auth login [--no-open]",
|
|
44
|
+
usage: "auth login [--no-open] [--no-qr] [--device-name <name>] [--worker-label <label>]",
|
|
43
45
|
description: "Start the browser login flow."
|
|
44
46
|
},
|
|
45
47
|
{
|
|
@@ -51,7 +53,7 @@ const CLI_ONLY_HELP_ENTRIES = [
|
|
|
51
53
|
const CLI_ONLY_COMMAND_HELP = [
|
|
52
54
|
{
|
|
53
55
|
path: ["auth", "login"],
|
|
54
|
-
usage: "auth login [--no-open]"
|
|
56
|
+
usage: "auth login [--no-open] [--no-qr] [--device-name <name>] [--worker-label <label>]"
|
|
55
57
|
},
|
|
56
58
|
{
|
|
57
59
|
path: ["shell"],
|
|
@@ -383,6 +385,21 @@ function parseOptionalFlagValue(args, flag, shortFlag) {
|
|
|
383
385
|
function hasBooleanFlag(args, flag) {
|
|
384
386
|
return args.includes(flag);
|
|
385
387
|
}
|
|
388
|
+
function assertAuthLoginArgs(args) {
|
|
389
|
+
const valueFlags = /* @__PURE__ */ new Set(["--device-name", "--worker-label"]);
|
|
390
|
+
const booleanFlags = /* @__PURE__ */ new Set(["--no-open", "--no-qr"]);
|
|
391
|
+
for (let index = 0; index < args.length; index += 1) {
|
|
392
|
+
const arg = args[index];
|
|
393
|
+
if (booleanFlags.has(arg)) continue;
|
|
394
|
+
if (valueFlags.has(arg)) {
|
|
395
|
+
if (!args[index + 1]) throw new Error(`Missing value for ${arg}`);
|
|
396
|
+
index += 1;
|
|
397
|
+
continue;
|
|
398
|
+
}
|
|
399
|
+
if ([...valueFlags].some((flag) => arg.startsWith(`${flag}=`))) continue;
|
|
400
|
+
throw new Error(`Unknown auth login flag: ${arg}`);
|
|
401
|
+
}
|
|
402
|
+
}
|
|
386
403
|
function parseGlobalArgs(argv) {
|
|
387
404
|
const options = {
|
|
388
405
|
json: false,
|
|
@@ -514,12 +531,27 @@ function serializeConfig(config) {
|
|
|
514
531
|
function writeConfig(configPath, config) {
|
|
515
532
|
const dir = path.dirname(configPath);
|
|
516
533
|
fs.mkdirSync(dir, { recursive: true, mode: 448 });
|
|
534
|
+
fs.chmodSync(dir, 448);
|
|
517
535
|
const serialized = serializeConfig(config);
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
536
|
+
const tempPath = path.join(dir, `.${path.basename(configPath)}.${process.pid}.${randomUUID()}.tmp`);
|
|
537
|
+
let descriptor;
|
|
538
|
+
try {
|
|
539
|
+
descriptor = fs.openSync(tempPath, "wx", 384);
|
|
540
|
+
fs.writeFileSync(descriptor, `${JSON.stringify(serialized, null, 2)}
|
|
541
|
+
`, "utf8");
|
|
542
|
+
fs.fsyncSync(descriptor);
|
|
543
|
+
fs.closeSync(descriptor);
|
|
544
|
+
descriptor = void 0;
|
|
545
|
+
fs.renameSync(tempPath, configPath);
|
|
546
|
+
fs.chmodSync(configPath, 384);
|
|
547
|
+
} catch (error) {
|
|
548
|
+
if (descriptor !== void 0) fs.closeSync(descriptor);
|
|
549
|
+
try {
|
|
550
|
+
fs.unlinkSync(tempPath);
|
|
551
|
+
} catch {
|
|
552
|
+
}
|
|
553
|
+
throw error;
|
|
554
|
+
}
|
|
523
555
|
}
|
|
524
556
|
function resolveClientOptions(options, config) {
|
|
525
557
|
return {
|
|
@@ -953,41 +985,89 @@ function createSavedApiKeyConfig(options, config, apiKey) {
|
|
|
953
985
|
token: void 0
|
|
954
986
|
};
|
|
955
987
|
}
|
|
988
|
+
function renderDeviceAuthorizationPrompt(input) {
|
|
989
|
+
const codeBorder = "\u2500".repeat(input.userCode.length + 4);
|
|
990
|
+
return `Authorize this device at:
|
|
991
|
+
${input.verificationUri}
|
|
992
|
+
|
|
993
|
+
\u250C${codeBorder}\u2510
|
|
994
|
+
\u2502 ${input.userCode} \u2502
|
|
995
|
+
\u2514${codeBorder}\u2518
|
|
996
|
+
`;
|
|
997
|
+
}
|
|
998
|
+
function getTransientDevicePollDelay(intervalMs, transientBackoffMs) {
|
|
999
|
+
return Math.min(3e4, Math.max(intervalMs, transientBackoffMs));
|
|
1000
|
+
}
|
|
1001
|
+
function advanceTransientDevicePollBackoff(intervalMs, transientBackoffMs) {
|
|
1002
|
+
return {
|
|
1003
|
+
delayMs: getTransientDevicePollDelay(intervalMs, transientBackoffMs),
|
|
1004
|
+
nextBackoffMs: Math.min(3e4, transientBackoffMs * 2)
|
|
1005
|
+
};
|
|
1006
|
+
}
|
|
956
1007
|
async function handleAuthLogin(client, options, commandArgs, config) {
|
|
1008
|
+
assertAuthLoginArgs(commandArgs);
|
|
957
1009
|
const noOpen = hasBooleanFlag(commandArgs, "--no-open");
|
|
958
|
-
const
|
|
959
|
-
|
|
960
|
-
|
|
1010
|
+
const noQr = hasBooleanFlag(commandArgs, "--no-qr");
|
|
1011
|
+
const deviceName = parseOptionalFlagValue(commandArgs, "--device-name") ?? `${os.hostname()} r5dctl`;
|
|
1012
|
+
const workerLabel = parseOptionalFlagValue(commandArgs, "--worker-label");
|
|
1013
|
+
const start = await client.auth.deviceStart({ deviceName, workerLabel });
|
|
961
1014
|
if (options.json) {
|
|
962
1015
|
writeDataOutput(
|
|
963
1016
|
true,
|
|
964
1017
|
{
|
|
965
1018
|
status: "pending",
|
|
966
1019
|
requestId: start.requestId,
|
|
967
|
-
|
|
1020
|
+
userCode: start.userCode,
|
|
1021
|
+
verificationUri: start.verificationUri,
|
|
1022
|
+
verificationUriComplete: start.verificationUriComplete,
|
|
968
1023
|
expiresAt: start.expiresAt,
|
|
969
1024
|
intervalMs: start.intervalMs
|
|
970
1025
|
},
|
|
971
1026
|
""
|
|
972
1027
|
);
|
|
973
1028
|
} else {
|
|
974
|
-
process.stdout.write(
|
|
975
|
-
|
|
1029
|
+
process.stdout.write(renderDeviceAuthorizationPrompt(start));
|
|
1030
|
+
if (!noQr) {
|
|
1031
|
+
process.stdout.write(`
|
|
1032
|
+
${await QRCode.toString(start.verificationUriComplete, { type: "terminal", small: true })}
|
|
976
1033
|
`);
|
|
1034
|
+
}
|
|
977
1035
|
}
|
|
978
1036
|
if (!noOpen) {
|
|
979
1037
|
try {
|
|
980
|
-
await openInBrowser(start.
|
|
1038
|
+
await openInBrowser(start.verificationUriComplete);
|
|
981
1039
|
} catch {
|
|
982
1040
|
}
|
|
983
1041
|
}
|
|
984
1042
|
const expiresAtMs = new Date(start.expiresAt).getTime();
|
|
985
1043
|
let intervalMs = start.intervalMs;
|
|
1044
|
+
let nextDelayMs = intervalMs;
|
|
1045
|
+
let transientBackoffMs = 1e3;
|
|
986
1046
|
while (Date.now() < expiresAtMs) {
|
|
987
|
-
await sleep(
|
|
988
|
-
|
|
1047
|
+
await sleep(Math.min(nextDelayMs, Math.max(0, expiresAtMs - Date.now())));
|
|
1048
|
+
let polled;
|
|
1049
|
+
try {
|
|
1050
|
+
polled = await client.auth.devicePoll(start.requestId, { deviceCode: start.deviceCode });
|
|
1051
|
+
} catch (error) {
|
|
1052
|
+
if (error instanceof R5dctlApiError && error.status === 429) {
|
|
1053
|
+
const body = error.body;
|
|
1054
|
+
if (typeof body?.intervalMs === "number") intervalMs = body.intervalMs;
|
|
1055
|
+
nextDelayMs = intervalMs;
|
|
1056
|
+
continue;
|
|
1057
|
+
}
|
|
1058
|
+
const transient = !(error instanceof R5dctlApiError) || error.status === 408 || error.status >= 500;
|
|
1059
|
+
if (transient) {
|
|
1060
|
+
const retry = advanceTransientDevicePollBackoff(intervalMs, transientBackoffMs);
|
|
1061
|
+
nextDelayMs = retry.delayMs;
|
|
1062
|
+
transientBackoffMs = retry.nextBackoffMs;
|
|
1063
|
+
continue;
|
|
1064
|
+
}
|
|
1065
|
+
throw error;
|
|
1066
|
+
}
|
|
1067
|
+
transientBackoffMs = 1e3;
|
|
989
1068
|
if (polled.status === "pending") {
|
|
990
1069
|
intervalMs = polled.intervalMs;
|
|
1070
|
+
nextDelayMs = intervalMs;
|
|
991
1071
|
continue;
|
|
992
1072
|
}
|
|
993
1073
|
if (polled.status === "approved") {
|
|
@@ -1012,6 +1092,9 @@ ${start.loginUrl}
|
|
|
1012
1092
|
if (polled.status === "expired") {
|
|
1013
1093
|
throw new Error("Login request expired. Run `r5dctl auth login` again.");
|
|
1014
1094
|
}
|
|
1095
|
+
if (polled.status === "denied") {
|
|
1096
|
+
throw new Error("Device authorization denied.");
|
|
1097
|
+
}
|
|
1015
1098
|
if (polled.status === "consumed") {
|
|
1016
1099
|
throw new Error("Login request was already consumed.");
|
|
1017
1100
|
}
|
|
@@ -2517,6 +2600,7 @@ async function main(argv = process.argv.slice(2)) {
|
|
|
2517
2600
|
}
|
|
2518
2601
|
}
|
|
2519
2602
|
export {
|
|
2603
|
+
advanceTransientDevicePollBackoff,
|
|
2520
2604
|
collectK8sUsage,
|
|
2521
2605
|
formatK8sCpu,
|
|
2522
2606
|
formatK8sMemory,
|
|
@@ -2525,6 +2609,8 @@ export {
|
|
|
2525
2609
|
getK8sUsageState,
|
|
2526
2610
|
getProjectEnvValue,
|
|
2527
2611
|
getR5dctlVersion,
|
|
2612
|
+
getTransientDevicePollDelay,
|
|
2613
|
+
handleAuthLogin,
|
|
2528
2614
|
main,
|
|
2529
2615
|
parseAnswerFlags,
|
|
2530
2616
|
parseConversationRenderArgs,
|
|
@@ -2544,6 +2630,7 @@ export {
|
|
|
2544
2630
|
renderConversationOverviewResponse,
|
|
2545
2631
|
renderConversationResponse,
|
|
2546
2632
|
renderConversationWorkResponse,
|
|
2633
|
+
renderDeviceAuthorizationPrompt,
|
|
2547
2634
|
renderEnvValues,
|
|
2548
2635
|
renderK8sUsageReport,
|
|
2549
2636
|
renderProcessHistory,
|
|
@@ -2553,5 +2640,6 @@ export {
|
|
|
2553
2640
|
renderWorkspaceSync,
|
|
2554
2641
|
resolveCommandExecution,
|
|
2555
2642
|
runR5dctlCli,
|
|
2556
|
-
summarizeEnvData
|
|
2643
|
+
summarizeEnvData,
|
|
2644
|
+
writeConfig
|
|
2557
2645
|
};
|
package/dist/mjs/package.json
CHANGED
package/dist/types/cli.d.ts
CHANGED
|
@@ -40,6 +40,7 @@ export declare function parseGlobalArgs(argv: string[]): {
|
|
|
40
40
|
options: GlobalOptions;
|
|
41
41
|
rest: string[];
|
|
42
42
|
};
|
|
43
|
+
export declare function writeConfig(configPath: string, config: R5dctlConfig): void;
|
|
43
44
|
export declare function parseAnswerFlags(args: string[]): string[];
|
|
44
45
|
export type SetEnvOptions = {
|
|
45
46
|
assignments: string[];
|
|
@@ -72,6 +73,16 @@ export type K8sUsageOptions = {
|
|
|
72
73
|
window: "24h" | "7d";
|
|
73
74
|
};
|
|
74
75
|
export declare function parseK8sUsageArgs(args: string[]): K8sUsageOptions;
|
|
76
|
+
export declare function renderDeviceAuthorizationPrompt(input: {
|
|
77
|
+
verificationUri: string;
|
|
78
|
+
userCode: string;
|
|
79
|
+
}): string;
|
|
80
|
+
export declare function getTransientDevicePollDelay(intervalMs: number, transientBackoffMs: number): number;
|
|
81
|
+
export declare function advanceTransientDevicePollBackoff(intervalMs: number, transientBackoffMs: number): {
|
|
82
|
+
delayMs: number;
|
|
83
|
+
nextBackoffMs: number;
|
|
84
|
+
};
|
|
85
|
+
export declare function handleAuthLogin(client: R5dctlClient, options: GlobalOptions, commandArgs: string[], config: R5dctlConfig): Promise<void>;
|
|
75
86
|
export declare function renderWorkspaceStatus(status: R5dctlWorkspaceStatus): string;
|
|
76
87
|
export declare function renderWorkspaceSync(result: R5dctlWorkspaceSyncResult): string;
|
|
77
88
|
export type K8sUsageSummary = {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ricsam/r5dctl",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.54",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "./dist/cjs/cli.cjs",
|
|
6
6
|
"module": "./dist/mjs/cli.mjs",
|
|
@@ -26,8 +26,9 @@
|
|
|
26
26
|
"r5dctl": "dist/cjs/main.cjs"
|
|
27
27
|
},
|
|
28
28
|
"dependencies": {
|
|
29
|
-
"@ricsam/r5d-api": "^0.0.
|
|
29
|
+
"@ricsam/r5d-api": "^0.0.54",
|
|
30
30
|
"dotenv": "^17",
|
|
31
|
+
"qrcode": "^1.5.4",
|
|
31
32
|
"ws": "^8.18.3"
|
|
32
33
|
},
|
|
33
34
|
"files": [
|