@ricsam/r5dctl 0.0.50 → 0.0.53
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 +19 -5
- package/dist/cjs/cli.cjs +265 -70
- package/dist/cjs/package.json +1 -1
- package/dist/mjs/cli.mjs +255 -69
- package/dist/mjs/package.json +1 -1
- package/dist/types/cli.d.ts +23 -3
- 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
|
|
|
@@ -50,6 +54,8 @@ r5dctl delete project <namespace/name|id>
|
|
|
50
54
|
r5dctl -p <project> get branches
|
|
51
55
|
r5dctl -p <project> describe branch <branch>
|
|
52
56
|
r5dctl -p <project> get envs
|
|
57
|
+
r5dctl -p <project> get envs --show-values
|
|
58
|
+
r5dctl -p <project> get env API_KEY
|
|
53
59
|
r5dctl -p <project> set envs --from-env-file ~/.env --only-missing
|
|
54
60
|
r5dctl -p <project> set envs -e API_KEY=value --description "API credential"
|
|
55
61
|
r5dctl -p <project> get sessions --branch <branch>
|
|
@@ -82,16 +88,24 @@ r5dctl ps stop <run-id>
|
|
|
82
88
|
|
|
83
89
|
r5dctl k8s usage
|
|
84
90
|
r5dctl k8s usage --namespace default --workload api --container server
|
|
85
|
-
r5dctl k8s usage --
|
|
91
|
+
r5dctl k8s usage --window 7d
|
|
92
|
+
r5dctl k8s usage --workload default/api --window 7d --json
|
|
86
93
|
```
|
|
87
94
|
|
|
88
95
|
`shell` connects to the same live worker PTY as the web Shell tab. Without `-c` it attaches the local terminal interactively. With `-c`/`--command`, it runs the supplied shell expression, streams combined PTY output, and exits with the remote command's status. Because command output uses a PTY, stdout and stderr are combined and programs may emit colors or other terminal control sequences.
|
|
89
96
|
|
|
90
97
|
`ps list` shows active long-running commands managed by r5d. It does not expose the worker host's general OS process table or interactive PTYs. Global project, branch, and session options filter the list, while `--worker` selects a worker label. `ps stop` sends `SIGTERM` to the selected run. Inside an r5d-provided worker shell, the temporary credential can only list and stop runs on that shell's worker.
|
|
91
98
|
|
|
92
|
-
`k8s usage` shows current and
|
|
99
|
+
`k8s usage` shows current and rolling CPU and memory usage for every workload container in your managed vCluster. The default window is `24h`; pass `--window 7d` for seven days. Filter with `--namespace`, `--workload`, or `--container`; a workload may be identified by name, `namespace/name`, or Kubernetes UID. Human-readable output shows current totals across sampled replicas and the selected window's maximum. Global `--json` includes min/max summaries, the complete five-minute history envelope, per-pod series, coverage, and freshness metadata.
|
|
93
100
|
|
|
94
|
-
`get envs` reports names and whether values are set
|
|
101
|
+
`get envs` reports names and whether values are set without revealing the values by default. Add `--show-values` to reveal every value; multiline and control characters are escaped for readable output. Without the global `--json` flag, `get env <name>` writes only that value to stdout, with no added newline or labels, so it works directly with command substitution and pipelines:
|
|
102
|
+
|
|
103
|
+
```bash
|
|
104
|
+
export API_KEY="$(r5dctl -p <project> get env API_KEY)"
|
|
105
|
+
r5dctl -p <project> get env PRIVATE_KEY | openssl pkey -check -noout
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
An undeclared or unset variable makes `get env` fail with a nonzero exit status. An explicitly configured empty value succeeds and writes zero bytes. Prefer `--from-env-file` for bulk imports so secret values do not appear in shell arguments or history. Empty dotenv values are skipped unless `--include-empty` is passed, and `--only-missing` preserves values that are already set.
|
|
95
109
|
|
|
96
110
|
Conversation output is human-readable by default and excludes both the system prompt and tool definitions. `--raw` keeps the transcript layout but renders structured message and tool parts as JSON, `--tools` includes the provider-shaped tool definitions, and `--system` includes system prompt messages. The global `--json` flag remains separate and prints the complete API response envelope.
|
|
97
111
|
|
package/dist/cjs/cli.cjs
CHANGED
|
@@ -28,18 +28,24 @@ 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,
|
|
34
35
|
formatProcessAge: () => formatProcessAge,
|
|
35
36
|
getCliHelpText: () => getCliHelpText,
|
|
37
|
+
getK8sUsageState: () => getK8sUsageState,
|
|
38
|
+
getProjectEnvValue: () => getProjectEnvValue,
|
|
36
39
|
getR5dctlVersion: () => getR5dctlVersion,
|
|
40
|
+
getTransientDevicePollDelay: () => getTransientDevicePollDelay,
|
|
41
|
+
handleAuthLogin: () => handleAuthLogin,
|
|
37
42
|
main: () => main,
|
|
38
43
|
parseAnswerFlags: () => parseAnswerFlags,
|
|
39
44
|
parseConversationRenderArgs: () => parseConversationRenderArgs,
|
|
40
45
|
parseConversationWorkDetailArgs: () => parseConversationWorkDetailArgs,
|
|
41
46
|
parseEnvFlags: () => parseEnvFlags,
|
|
42
47
|
parseEnvRequestResponseArgs: () => parseEnvRequestResponseArgs,
|
|
48
|
+
parseGetEnvsArgs: () => parseGetEnvsArgs,
|
|
43
49
|
parseGlobalArgs: () => parseGlobalArgs,
|
|
44
50
|
parseK8sUsageArgs: () => parseK8sUsageArgs,
|
|
45
51
|
parsePromptArgs: () => parsePromptArgs,
|
|
@@ -52,6 +58,8 @@ __export(cli_exports, {
|
|
|
52
58
|
renderConversationOverviewResponse: () => renderConversationOverviewResponse,
|
|
53
59
|
renderConversationResponse: () => renderConversationResponse,
|
|
54
60
|
renderConversationWorkResponse: () => renderConversationWorkResponse,
|
|
61
|
+
renderDeviceAuthorizationPrompt: () => renderDeviceAuthorizationPrompt,
|
|
62
|
+
renderEnvValues: () => renderEnvValues,
|
|
55
63
|
renderK8sUsageReport: () => renderK8sUsageReport,
|
|
56
64
|
renderProcessHistory: () => renderProcessHistory,
|
|
57
65
|
renderProcessInspection: () => renderProcessInspection,
|
|
@@ -60,13 +68,16 @@ __export(cli_exports, {
|
|
|
60
68
|
renderWorkspaceSync: () => renderWorkspaceSync,
|
|
61
69
|
resolveCommandExecution: () => resolveCommandExecution,
|
|
62
70
|
runR5dctlCli: () => runR5dctlCli,
|
|
63
|
-
summarizeEnvData: () => summarizeEnvData
|
|
71
|
+
summarizeEnvData: () => summarizeEnvData,
|
|
72
|
+
writeConfig: () => writeConfig
|
|
64
73
|
});
|
|
65
74
|
module.exports = __toCommonJS(cli_exports);
|
|
66
75
|
var import_node_fs = __toESM(require("node:fs"), 1);
|
|
67
76
|
var import_node_os = __toESM(require("node:os"), 1);
|
|
68
77
|
var import_node_path = __toESM(require("node:path"), 1);
|
|
69
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);
|
|
70
81
|
var import_promises = require("node:timers/promises");
|
|
71
82
|
var import_dotenv = require("dotenv");
|
|
72
83
|
var import_r5d_api = require("@ricsam/r5d-api");
|
|
@@ -101,7 +112,7 @@ const CLI_GLOBAL_OPTION_HELP = [
|
|
|
101
112
|
const CLI_ONLY_HELP_ENTRIES = [
|
|
102
113
|
{
|
|
103
114
|
section: "auth",
|
|
104
|
-
usage: "auth login [--no-open]",
|
|
115
|
+
usage: "auth login [--no-open] [--no-qr] [--device-name <name>] [--worker-label <label>]",
|
|
105
116
|
description: "Start the browser login flow."
|
|
106
117
|
},
|
|
107
118
|
{
|
|
@@ -113,7 +124,7 @@ const CLI_ONLY_HELP_ENTRIES = [
|
|
|
113
124
|
const CLI_ONLY_COMMAND_HELP = [
|
|
114
125
|
{
|
|
115
126
|
path: ["auth", "login"],
|
|
116
|
-
usage: "auth login [--no-open]"
|
|
127
|
+
usage: "auth login [--no-open] [--no-qr] [--device-name <name>] [--worker-label <label>]"
|
|
117
128
|
},
|
|
118
129
|
{
|
|
119
130
|
path: ["shell"],
|
|
@@ -133,9 +144,9 @@ const PS_HELP_TEXT = [
|
|
|
133
144
|
].join("\n");
|
|
134
145
|
const K8S_HELP_TEXT = [
|
|
135
146
|
"Usage:",
|
|
136
|
-
" r5dctl k8s usage [--namespace <name>] [--workload <name|namespace/name|uid>] [--container <name>] [--window
|
|
147
|
+
" r5dctl k8s usage [--namespace <name>] [--workload <name|namespace/name|uid>] [--container <name>] [--window <24h|7d>]",
|
|
137
148
|
"",
|
|
138
|
-
"Show current and
|
|
149
|
+
"Show current and rolling CPU and memory usage for managed-vCluster workload containers.",
|
|
139
150
|
""
|
|
140
151
|
].join("\n");
|
|
141
152
|
const SHARED_HELP_ENTRIES = [
|
|
@@ -167,8 +178,13 @@ const SHARED_HELP_ENTRIES = [
|
|
|
167
178
|
{ section: "branches", usage: "-p <project> describe branch <branch>", description: "Show branch URLs and sessions." },
|
|
168
179
|
{
|
|
169
180
|
section: "envs",
|
|
170
|
-
usage: "-p <project> get envs",
|
|
171
|
-
description: "List declared envs
|
|
181
|
+
usage: "-p <project> get envs [--show-values]",
|
|
182
|
+
description: "List declared envs; use --show-values to reveal their values."
|
|
183
|
+
},
|
|
184
|
+
{
|
|
185
|
+
section: "envs",
|
|
186
|
+
usage: "-p <project> get env <name>",
|
|
187
|
+
description: "Write one env value exactly to stdout for command substitution or piping."
|
|
172
188
|
},
|
|
173
189
|
{
|
|
174
190
|
section: "envs",
|
|
@@ -235,8 +251,8 @@ const SHARED_HELP_ENTRIES = [
|
|
|
235
251
|
},
|
|
236
252
|
{
|
|
237
253
|
section: "kubernetes",
|
|
238
|
-
usage: "k8s usage [--namespace <name>] [--workload <name|namespace/name|uid>] [--container <name>] [--window
|
|
239
|
-
description: "Show current and
|
|
254
|
+
usage: "k8s usage [--namespace <name>] [--workload <name|namespace/name|uid>] [--container <name>] [--window <24h|7d>]",
|
|
255
|
+
description: "Show current and rolling CPU and memory usage for managed-vCluster workload containers."
|
|
240
256
|
},
|
|
241
257
|
{
|
|
242
258
|
section: "processes",
|
|
@@ -440,6 +456,21 @@ function parseOptionalFlagValue(args, flag, shortFlag) {
|
|
|
440
456
|
function hasBooleanFlag(args, flag) {
|
|
441
457
|
return args.includes(flag);
|
|
442
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
|
+
}
|
|
443
474
|
function parseGlobalArgs(argv) {
|
|
444
475
|
const options = {
|
|
445
476
|
json: false,
|
|
@@ -571,12 +602,27 @@ function serializeConfig(config) {
|
|
|
571
602
|
function writeConfig(configPath, config) {
|
|
572
603
|
const dir = import_node_path.default.dirname(configPath);
|
|
573
604
|
import_node_fs.default.mkdirSync(dir, { recursive: true, mode: 448 });
|
|
605
|
+
import_node_fs.default.chmodSync(dir, 448);
|
|
574
606
|
const serialized = serializeConfig(config);
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
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
|
+
}
|
|
580
626
|
}
|
|
581
627
|
function resolveClientOptions(options, config) {
|
|
582
628
|
return {
|
|
@@ -972,7 +1018,7 @@ function parseK8sUsageArgs(args) {
|
|
|
972
1018
|
const value = candidateValue;
|
|
973
1019
|
if (inlineValue === void 0) index += 1;
|
|
974
1020
|
if (option === "--window") {
|
|
975
|
-
if (value !== "24h") throw new Error("--window
|
|
1021
|
+
if (value !== "24h" && value !== "7d") throw new Error("--window must be 24h or 7d");
|
|
976
1022
|
parsed.window = value;
|
|
977
1023
|
} else if (option === "--namespace") {
|
|
978
1024
|
parsed.namespace = value;
|
|
@@ -1010,41 +1056,89 @@ function createSavedApiKeyConfig(options, config, apiKey) {
|
|
|
1010
1056
|
token: void 0
|
|
1011
1057
|
};
|
|
1012
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
|
+
}
|
|
1013
1078
|
async function handleAuthLogin(client, options, commandArgs, config) {
|
|
1079
|
+
assertAuthLoginArgs(commandArgs);
|
|
1014
1080
|
const noOpen = hasBooleanFlag(commandArgs, "--no-open");
|
|
1015
|
-
const
|
|
1016
|
-
|
|
1017
|
-
|
|
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 });
|
|
1018
1085
|
if (options.json) {
|
|
1019
1086
|
writeDataOutput(
|
|
1020
1087
|
true,
|
|
1021
1088
|
{
|
|
1022
1089
|
status: "pending",
|
|
1023
1090
|
requestId: start.requestId,
|
|
1024
|
-
|
|
1091
|
+
userCode: start.userCode,
|
|
1092
|
+
verificationUri: start.verificationUri,
|
|
1093
|
+
verificationUriComplete: start.verificationUriComplete,
|
|
1025
1094
|
expiresAt: start.expiresAt,
|
|
1026
1095
|
intervalMs: start.intervalMs
|
|
1027
1096
|
},
|
|
1028
1097
|
""
|
|
1029
1098
|
);
|
|
1030
1099
|
} else {
|
|
1031
|
-
process.stdout.write(
|
|
1032
|
-
|
|
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 })}
|
|
1033
1104
|
`);
|
|
1105
|
+
}
|
|
1034
1106
|
}
|
|
1035
1107
|
if (!noOpen) {
|
|
1036
1108
|
try {
|
|
1037
|
-
await openInBrowser(start.
|
|
1109
|
+
await openInBrowser(start.verificationUriComplete);
|
|
1038
1110
|
} catch {
|
|
1039
1111
|
}
|
|
1040
1112
|
}
|
|
1041
1113
|
const expiresAtMs = new Date(start.expiresAt).getTime();
|
|
1042
1114
|
let intervalMs = start.intervalMs;
|
|
1115
|
+
let nextDelayMs = intervalMs;
|
|
1116
|
+
let transientBackoffMs = 1e3;
|
|
1043
1117
|
while (Date.now() < expiresAtMs) {
|
|
1044
|
-
await (0, import_promises.setTimeout)(
|
|
1045
|
-
|
|
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;
|
|
1046
1139
|
if (polled.status === "pending") {
|
|
1047
1140
|
intervalMs = polled.intervalMs;
|
|
1141
|
+
nextDelayMs = intervalMs;
|
|
1048
1142
|
continue;
|
|
1049
1143
|
}
|
|
1050
1144
|
if (polled.status === "approved") {
|
|
@@ -1069,6 +1163,9 @@ ${start.loginUrl}
|
|
|
1069
1163
|
if (polled.status === "expired") {
|
|
1070
1164
|
throw new Error("Login request expired. Run `r5dctl auth login` again.");
|
|
1071
1165
|
}
|
|
1166
|
+
if (polled.status === "denied") {
|
|
1167
|
+
throw new Error("Device authorization denied.");
|
|
1168
|
+
}
|
|
1072
1169
|
if (polled.status === "consumed") {
|
|
1073
1170
|
throw new Error("Login request was already consumed.");
|
|
1074
1171
|
}
|
|
@@ -1189,19 +1286,22 @@ async function collectK8sUsage(client, options) {
|
|
|
1189
1286
|
throw new Error("No managed-vCluster workload containers matched the requested filters");
|
|
1190
1287
|
}
|
|
1191
1288
|
const entries = await mapWithConcurrency(candidates, 8, async ({ workload, container }) => {
|
|
1192
|
-
const history = await client.k8s.resourceHistory({ workloadUid: workload.uid, containerName: container.name });
|
|
1289
|
+
const history = await client.k8s.resourceHistory({ workloadUid: workload.uid, containerName: container.name, window: options.window });
|
|
1193
1290
|
return {
|
|
1194
1291
|
workload: {
|
|
1195
1292
|
uid: workload.uid,
|
|
1196
1293
|
kind: workload.kind,
|
|
1197
1294
|
namespace: workload.namespace,
|
|
1198
|
-
name: workload.name
|
|
1295
|
+
name: workload.name,
|
|
1296
|
+
replicas: workload.replicas,
|
|
1297
|
+
readyReplicas: workload.readyReplicas
|
|
1199
1298
|
},
|
|
1200
1299
|
container: {
|
|
1201
1300
|
name: container.name,
|
|
1202
1301
|
currentUsage: container.currentUsage,
|
|
1203
1302
|
pods: container.pods
|
|
1204
1303
|
},
|
|
1304
|
+
sampledPodCount: container.pods.filter((pod) => pod.cpuNanoCores !== null || pod.memoryBytes !== null).length,
|
|
1205
1305
|
summary: summarizeK8sHistory(history),
|
|
1206
1306
|
history
|
|
1207
1307
|
};
|
|
@@ -1227,60 +1327,83 @@ function formatDecimal(value) {
|
|
|
1227
1327
|
function formatK8sCpu(value) {
|
|
1228
1328
|
if (value === null) return "-";
|
|
1229
1329
|
const milliCores = value / 1e6;
|
|
1230
|
-
return
|
|
1330
|
+
return `${formatDecimal(milliCores)}m`;
|
|
1231
1331
|
}
|
|
1232
1332
|
function formatK8sMemory(value) {
|
|
1233
1333
|
if (value === null) return "-";
|
|
1234
|
-
const units = ["B", "
|
|
1334
|
+
const units = ["B", "Ki", "Mi", "Gi", "Ti"];
|
|
1235
1335
|
let amount = value;
|
|
1236
1336
|
let unitIndex = 0;
|
|
1237
1337
|
while (amount >= 1024 && unitIndex < units.length - 1) {
|
|
1238
1338
|
amount /= 1024;
|
|
1239
1339
|
unitIndex += 1;
|
|
1240
1340
|
}
|
|
1241
|
-
return `${formatDecimal(amount)}
|
|
1242
|
-
}
|
|
1243
|
-
function
|
|
1244
|
-
|
|
1245
|
-
const
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
|
|
1341
|
+
return `${formatDecimal(amount)}${units[unitIndex]}`;
|
|
1342
|
+
}
|
|
1343
|
+
function getK8sUsageState(entry) {
|
|
1344
|
+
const hasHistory = entry.history.envelope.length > 0;
|
|
1345
|
+
const terminalPods = entry.container.pods.filter((pod) => pod.phase === "Succeeded" || pod.phase === "Failed").length;
|
|
1346
|
+
const inactive = entry.sampledPodCount === 0 && (entry.workload.replicas === 0 || entry.container.pods.length > 0 && terminalPods === entry.container.pods.length || entry.workload.kind === "CronJob" && entry.container.pods.length === 0);
|
|
1347
|
+
if (inactive) return "inactive";
|
|
1348
|
+
if (entry.sampledPodCount === 0) return hasHistory ? entry.history.stale ? "stale" : "no current" : "no data";
|
|
1349
|
+
if (!hasHistory) return "warming";
|
|
1350
|
+
if (entry.history.stale) return "stale";
|
|
1351
|
+
if (entry.history.incomplete) return "partial";
|
|
1352
|
+
return "healthy";
|
|
1353
|
+
}
|
|
1354
|
+
function abbreviatedK8sKind(kind) {
|
|
1355
|
+
switch (kind) {
|
|
1356
|
+
case "Deployment":
|
|
1357
|
+
return "deploy";
|
|
1358
|
+
case "StatefulSet":
|
|
1359
|
+
return "sts";
|
|
1360
|
+
case "DaemonSet":
|
|
1361
|
+
return "ds";
|
|
1362
|
+
case "CronJob":
|
|
1363
|
+
return "cron";
|
|
1364
|
+
case "ReplicaSet":
|
|
1365
|
+
return "rs";
|
|
1366
|
+
case "Job":
|
|
1367
|
+
return "job";
|
|
1368
|
+
case "Pod":
|
|
1369
|
+
return "pod";
|
|
1370
|
+
}
|
|
1249
1371
|
}
|
|
1250
1372
|
function renderK8sUsageReport(report) {
|
|
1251
1373
|
const freshness = report.metricsFreshAt ?? "unavailable";
|
|
1252
|
-
const lines = [
|
|
1374
|
+
const lines = [
|
|
1375
|
+
`Window: ${report.window} Collector: ${report.metricsStale ? "stale" : "current"} Fresh: ${freshness}`,
|
|
1376
|
+
"PODS counts pods with current metrics."
|
|
1377
|
+
];
|
|
1253
1378
|
if (report.entries.length === 0) return `${lines.join("\n")}
|
|
1254
1379
|
No managed-vCluster workload containers found.
|
|
1255
1380
|
`;
|
|
1256
|
-
const headers = [
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
const separator = widths.map((width) => "-".repeat(width)).join(" ");
|
|
1283
|
-
lines.push(formatRow(headers), separator, ...rows.map(formatRow));
|
|
1381
|
+
const headers = ["WORKLOAD", "CONTAINER", "PODS", "CPU NOW", "CPU MAX", "MEM NOW", "MEM MAX", "STATE"];
|
|
1382
|
+
const grouped = /* @__PURE__ */ new Map();
|
|
1383
|
+
for (const entry of report.entries) {
|
|
1384
|
+
const entries = grouped.get(entry.workload.namespace) ?? [];
|
|
1385
|
+
entries.push(entry);
|
|
1386
|
+
grouped.set(entry.workload.namespace, entries);
|
|
1387
|
+
}
|
|
1388
|
+
for (const [namespace, entries] of grouped) {
|
|
1389
|
+
const rows = entries.map((entry) => {
|
|
1390
|
+
const state = getK8sUsageState(entry);
|
|
1391
|
+
return [
|
|
1392
|
+
`${abbreviatedK8sKind(entry.workload.kind)}/${entry.workload.name}`,
|
|
1393
|
+
entry.container.name,
|
|
1394
|
+
String(entry.sampledPodCount),
|
|
1395
|
+
formatK8sCpu(entry.container.currentUsage.cpuNanoCores),
|
|
1396
|
+
formatK8sCpu(entry.summary.cpuMaxNanoCores),
|
|
1397
|
+
formatK8sMemory(entry.container.currentUsage.memoryBytes),
|
|
1398
|
+
formatK8sMemory(entry.summary.memoryMaxBytes),
|
|
1399
|
+
state === "healthy" ? "-" : state
|
|
1400
|
+
];
|
|
1401
|
+
});
|
|
1402
|
+
const widths = headers.map((header, column) => Math.max(header.length, ...rows.map((row) => row[column]?.length ?? 0)));
|
|
1403
|
+
const formatRow = (row) => row.map((value, column) => column === row.length - 1 ? value : value.padEnd(widths[column] ?? value.length)).join(" ");
|
|
1404
|
+
const separator = widths.map((width) => "-".repeat(width)).join(" ");
|
|
1405
|
+
lines.push("", `Namespace: ${namespace}`, formatRow(headers), separator, ...rows.map(formatRow));
|
|
1406
|
+
}
|
|
1284
1407
|
return `${lines.join("\n")}
|
|
1285
1408
|
`;
|
|
1286
1409
|
}
|
|
@@ -1375,6 +1498,17 @@ function summarizeEnvData(data) {
|
|
|
1375
1498
|
])
|
|
1376
1499
|
);
|
|
1377
1500
|
}
|
|
1501
|
+
function parseGetEnvsArgs(args) {
|
|
1502
|
+
let showValues = false;
|
|
1503
|
+
for (const arg of args) {
|
|
1504
|
+
if (arg === "--show-values") {
|
|
1505
|
+
showValues = true;
|
|
1506
|
+
continue;
|
|
1507
|
+
}
|
|
1508
|
+
throw new Error(arg.startsWith("-") ? `Unknown get envs flag: ${arg}` : `Unexpected get envs value: ${arg}`);
|
|
1509
|
+
}
|
|
1510
|
+
return { showValues };
|
|
1511
|
+
}
|
|
1378
1512
|
function renderEnvSummary(data) {
|
|
1379
1513
|
const lines = [];
|
|
1380
1514
|
for (const [name, env] of Object.entries(data)) {
|
|
@@ -1383,6 +1517,24 @@ function renderEnvSummary(data) {
|
|
|
1383
1517
|
return lines.length > 0 ? `${lines.join("\n")}
|
|
1384
1518
|
` : "No environment variables declared.\n";
|
|
1385
1519
|
}
|
|
1520
|
+
function renderEnvValues(data) {
|
|
1521
|
+
const lines = Object.entries(data).sort(([left], [right]) => left.localeCompare(right)).map(([name, env]) => `${name} ${env.value === null ? "<missing>" : JSON.stringify(env.value)} ${env.description}`);
|
|
1522
|
+
return lines.length > 0 ? `${lines.join("\n")}
|
|
1523
|
+
` : "No environment variables declared.\n";
|
|
1524
|
+
}
|
|
1525
|
+
function getProjectEnvValue(data, name) {
|
|
1526
|
+
if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(name)) {
|
|
1527
|
+
throw new Error(`Invalid environment variable name: ${name}`);
|
|
1528
|
+
}
|
|
1529
|
+
if (!Object.prototype.hasOwnProperty.call(data, name)) {
|
|
1530
|
+
throw new Error(`Environment variable ${name} is not declared for this project`);
|
|
1531
|
+
}
|
|
1532
|
+
const value = data[name]?.value;
|
|
1533
|
+
if (value === null || value === void 0) {
|
|
1534
|
+
throw new Error(`Environment variable ${name} does not have a value`);
|
|
1535
|
+
}
|
|
1536
|
+
return value;
|
|
1537
|
+
}
|
|
1386
1538
|
function renderEnvSetResult(result) {
|
|
1387
1539
|
const lines = [`Updated ${result.updated.length} environment variable${result.updated.length === 1 ? "" : "s"} for ${result.project}.`];
|
|
1388
1540
|
if (result.skippedExisting.length > 0) lines.push(`Skipped ${result.skippedExisting.length} existing value(s).`);
|
|
@@ -1738,11 +1890,25 @@ Sessions: ${result.sessions.length}
|
|
|
1738
1890
|
return;
|
|
1739
1891
|
}
|
|
1740
1892
|
if (first === "get" && second === "envs") {
|
|
1741
|
-
if (args.length !== 3) throw new Error(`Unexpected get envs argument: ${args[3] ?? ""}`.trim());
|
|
1742
1893
|
const projectRef = requireValue(args[2], "Missing project reference");
|
|
1894
|
+
const options = parseGetEnvsArgs(args.slice(3));
|
|
1743
1895
|
const project = await client.projects.describe(projectRef);
|
|
1744
|
-
const envs =
|
|
1745
|
-
|
|
1896
|
+
const envs = await client.projects.env.get(project.id);
|
|
1897
|
+
if (options.showValues) {
|
|
1898
|
+
write({ project: project.path, envs }, renderEnvValues(envs));
|
|
1899
|
+
} else {
|
|
1900
|
+
const summary = summarizeEnvData(envs);
|
|
1901
|
+
write({ project: project.path, envs: summary }, renderEnvSummary(summary));
|
|
1902
|
+
}
|
|
1903
|
+
return;
|
|
1904
|
+
}
|
|
1905
|
+
if (first === "get" && second === "env") {
|
|
1906
|
+
if (args.length > 4) throw new Error(`Unexpected get env argument: ${args[4]}`);
|
|
1907
|
+
const projectRef = requireValue(args[2], "Missing project reference");
|
|
1908
|
+
const name = requireValue(args[3], "Missing environment variable name");
|
|
1909
|
+
const project = await client.projects.describe(projectRef);
|
|
1910
|
+
const value = getProjectEnvValue(await client.projects.env.get(project.id), name);
|
|
1911
|
+
write({ project: project.path, name, value }, value);
|
|
1746
1912
|
return;
|
|
1747
1913
|
}
|
|
1748
1914
|
if (first === "set" && second === "envs") {
|
|
@@ -2095,9 +2261,29 @@ function resolveCommandExecution(options, rest) {
|
|
|
2095
2261
|
if (options.branch) {
|
|
2096
2262
|
throw new Error("--branch/-b is not supported for project environment variables");
|
|
2097
2263
|
}
|
|
2264
|
+
parseGetEnvsArgs(commandArgs.slice(1));
|
|
2265
|
+
return {
|
|
2266
|
+
kind: "plugin",
|
|
2267
|
+
pluginArgs: ["get", "envs", options.project, ...commandArgs.slice(1)]
|
|
2268
|
+
};
|
|
2269
|
+
}
|
|
2270
|
+
if (target === "env") {
|
|
2271
|
+
if (!options.project) {
|
|
2272
|
+
throw new Error("--project/-p is required for `get env`");
|
|
2273
|
+
}
|
|
2274
|
+
if (options.branch) {
|
|
2275
|
+
throw new Error("--branch/-b is not supported for project environment variables");
|
|
2276
|
+
}
|
|
2277
|
+
const name = requireValue(commandArgs[1], "Environment variable name is required for `get env`");
|
|
2278
|
+
if (commandArgs.length > 2) {
|
|
2279
|
+
throw new Error(`Unexpected get env argument: ${commandArgs[2]}`);
|
|
2280
|
+
}
|
|
2281
|
+
if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(name)) {
|
|
2282
|
+
throw new Error(`Invalid environment variable name: ${name}`);
|
|
2283
|
+
}
|
|
2098
2284
|
return {
|
|
2099
2285
|
kind: "plugin",
|
|
2100
|
-
pluginArgs: ["get", "
|
|
2286
|
+
pluginArgs: ["get", "env", options.project, name]
|
|
2101
2287
|
};
|
|
2102
2288
|
}
|
|
2103
2289
|
throw new Error("Unknown get command");
|
|
@@ -2486,18 +2672,24 @@ async function main(argv = process.argv.slice(2)) {
|
|
|
2486
2672
|
}
|
|
2487
2673
|
// Annotate the CommonJS export names for ESM import in node:
|
|
2488
2674
|
0 && (module.exports = {
|
|
2675
|
+
advanceTransientDevicePollBackoff,
|
|
2489
2676
|
collectK8sUsage,
|
|
2490
2677
|
formatK8sCpu,
|
|
2491
2678
|
formatK8sMemory,
|
|
2492
2679
|
formatProcessAge,
|
|
2493
2680
|
getCliHelpText,
|
|
2681
|
+
getK8sUsageState,
|
|
2682
|
+
getProjectEnvValue,
|
|
2494
2683
|
getR5dctlVersion,
|
|
2684
|
+
getTransientDevicePollDelay,
|
|
2685
|
+
handleAuthLogin,
|
|
2495
2686
|
main,
|
|
2496
2687
|
parseAnswerFlags,
|
|
2497
2688
|
parseConversationRenderArgs,
|
|
2498
2689
|
parseConversationWorkDetailArgs,
|
|
2499
2690
|
parseEnvFlags,
|
|
2500
2691
|
parseEnvRequestResponseArgs,
|
|
2692
|
+
parseGetEnvsArgs,
|
|
2501
2693
|
parseGlobalArgs,
|
|
2502
2694
|
parseK8sUsageArgs,
|
|
2503
2695
|
parsePromptArgs,
|
|
@@ -2510,6 +2702,8 @@ async function main(argv = process.argv.slice(2)) {
|
|
|
2510
2702
|
renderConversationOverviewResponse,
|
|
2511
2703
|
renderConversationResponse,
|
|
2512
2704
|
renderConversationWorkResponse,
|
|
2705
|
+
renderDeviceAuthorizationPrompt,
|
|
2706
|
+
renderEnvValues,
|
|
2513
2707
|
renderK8sUsageReport,
|
|
2514
2708
|
renderProcessHistory,
|
|
2515
2709
|
renderProcessInspection,
|
|
@@ -2518,5 +2712,6 @@ async function main(argv = process.argv.slice(2)) {
|
|
|
2518
2712
|
renderWorkspaceSync,
|
|
2519
2713
|
resolveCommandExecution,
|
|
2520
2714
|
runR5dctlCli,
|
|
2521
|
-
summarizeEnvData
|
|
2715
|
+
summarizeEnvData,
|
|
2716
|
+
writeConfig
|
|
2522
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"],
|
|
@@ -71,9 +73,9 @@ const PS_HELP_TEXT = [
|
|
|
71
73
|
].join("\n");
|
|
72
74
|
const K8S_HELP_TEXT = [
|
|
73
75
|
"Usage:",
|
|
74
|
-
" r5dctl k8s usage [--namespace <name>] [--workload <name|namespace/name|uid>] [--container <name>] [--window
|
|
76
|
+
" r5dctl k8s usage [--namespace <name>] [--workload <name|namespace/name|uid>] [--container <name>] [--window <24h|7d>]",
|
|
75
77
|
"",
|
|
76
|
-
"Show current and
|
|
78
|
+
"Show current and rolling CPU and memory usage for managed-vCluster workload containers.",
|
|
77
79
|
""
|
|
78
80
|
].join("\n");
|
|
79
81
|
const SHARED_HELP_ENTRIES = [
|
|
@@ -105,8 +107,13 @@ const SHARED_HELP_ENTRIES = [
|
|
|
105
107
|
{ section: "branches", usage: "-p <project> describe branch <branch>", description: "Show branch URLs and sessions." },
|
|
106
108
|
{
|
|
107
109
|
section: "envs",
|
|
108
|
-
usage: "-p <project> get envs",
|
|
109
|
-
description: "List declared envs
|
|
110
|
+
usage: "-p <project> get envs [--show-values]",
|
|
111
|
+
description: "List declared envs; use --show-values to reveal their values."
|
|
112
|
+
},
|
|
113
|
+
{
|
|
114
|
+
section: "envs",
|
|
115
|
+
usage: "-p <project> get env <name>",
|
|
116
|
+
description: "Write one env value exactly to stdout for command substitution or piping."
|
|
110
117
|
},
|
|
111
118
|
{
|
|
112
119
|
section: "envs",
|
|
@@ -173,8 +180,8 @@ const SHARED_HELP_ENTRIES = [
|
|
|
173
180
|
},
|
|
174
181
|
{
|
|
175
182
|
section: "kubernetes",
|
|
176
|
-
usage: "k8s usage [--namespace <name>] [--workload <name|namespace/name|uid>] [--container <name>] [--window
|
|
177
|
-
description: "Show current and
|
|
183
|
+
usage: "k8s usage [--namespace <name>] [--workload <name|namespace/name|uid>] [--container <name>] [--window <24h|7d>]",
|
|
184
|
+
description: "Show current and rolling CPU and memory usage for managed-vCluster workload containers."
|
|
178
185
|
},
|
|
179
186
|
{
|
|
180
187
|
section: "processes",
|
|
@@ -378,6 +385,21 @@ function parseOptionalFlagValue(args, flag, shortFlag) {
|
|
|
378
385
|
function hasBooleanFlag(args, flag) {
|
|
379
386
|
return args.includes(flag);
|
|
380
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
|
+
}
|
|
381
403
|
function parseGlobalArgs(argv) {
|
|
382
404
|
const options = {
|
|
383
405
|
json: false,
|
|
@@ -509,12 +531,27 @@ function serializeConfig(config) {
|
|
|
509
531
|
function writeConfig(configPath, config) {
|
|
510
532
|
const dir = path.dirname(configPath);
|
|
511
533
|
fs.mkdirSync(dir, { recursive: true, mode: 448 });
|
|
534
|
+
fs.chmodSync(dir, 448);
|
|
512
535
|
const serialized = serializeConfig(config);
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
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
|
+
}
|
|
518
555
|
}
|
|
519
556
|
function resolveClientOptions(options, config) {
|
|
520
557
|
return {
|
|
@@ -910,7 +947,7 @@ function parseK8sUsageArgs(args) {
|
|
|
910
947
|
const value = candidateValue;
|
|
911
948
|
if (inlineValue === void 0) index += 1;
|
|
912
949
|
if (option === "--window") {
|
|
913
|
-
if (value !== "24h") throw new Error("--window
|
|
950
|
+
if (value !== "24h" && value !== "7d") throw new Error("--window must be 24h or 7d");
|
|
914
951
|
parsed.window = value;
|
|
915
952
|
} else if (option === "--namespace") {
|
|
916
953
|
parsed.namespace = value;
|
|
@@ -948,41 +985,89 @@ function createSavedApiKeyConfig(options, config, apiKey) {
|
|
|
948
985
|
token: void 0
|
|
949
986
|
};
|
|
950
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
|
+
}
|
|
951
1007
|
async function handleAuthLogin(client, options, commandArgs, config) {
|
|
1008
|
+
assertAuthLoginArgs(commandArgs);
|
|
952
1009
|
const noOpen = hasBooleanFlag(commandArgs, "--no-open");
|
|
953
|
-
const
|
|
954
|
-
|
|
955
|
-
|
|
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 });
|
|
956
1014
|
if (options.json) {
|
|
957
1015
|
writeDataOutput(
|
|
958
1016
|
true,
|
|
959
1017
|
{
|
|
960
1018
|
status: "pending",
|
|
961
1019
|
requestId: start.requestId,
|
|
962
|
-
|
|
1020
|
+
userCode: start.userCode,
|
|
1021
|
+
verificationUri: start.verificationUri,
|
|
1022
|
+
verificationUriComplete: start.verificationUriComplete,
|
|
963
1023
|
expiresAt: start.expiresAt,
|
|
964
1024
|
intervalMs: start.intervalMs
|
|
965
1025
|
},
|
|
966
1026
|
""
|
|
967
1027
|
);
|
|
968
1028
|
} else {
|
|
969
|
-
process.stdout.write(
|
|
970
|
-
|
|
1029
|
+
process.stdout.write(renderDeviceAuthorizationPrompt(start));
|
|
1030
|
+
if (!noQr) {
|
|
1031
|
+
process.stdout.write(`
|
|
1032
|
+
${await QRCode.toString(start.verificationUriComplete, { type: "terminal", small: true })}
|
|
971
1033
|
`);
|
|
1034
|
+
}
|
|
972
1035
|
}
|
|
973
1036
|
if (!noOpen) {
|
|
974
1037
|
try {
|
|
975
|
-
await openInBrowser(start.
|
|
1038
|
+
await openInBrowser(start.verificationUriComplete);
|
|
976
1039
|
} catch {
|
|
977
1040
|
}
|
|
978
1041
|
}
|
|
979
1042
|
const expiresAtMs = new Date(start.expiresAt).getTime();
|
|
980
1043
|
let intervalMs = start.intervalMs;
|
|
1044
|
+
let nextDelayMs = intervalMs;
|
|
1045
|
+
let transientBackoffMs = 1e3;
|
|
981
1046
|
while (Date.now() < expiresAtMs) {
|
|
982
|
-
await sleep(
|
|
983
|
-
|
|
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;
|
|
984
1068
|
if (polled.status === "pending") {
|
|
985
1069
|
intervalMs = polled.intervalMs;
|
|
1070
|
+
nextDelayMs = intervalMs;
|
|
986
1071
|
continue;
|
|
987
1072
|
}
|
|
988
1073
|
if (polled.status === "approved") {
|
|
@@ -1007,6 +1092,9 @@ ${start.loginUrl}
|
|
|
1007
1092
|
if (polled.status === "expired") {
|
|
1008
1093
|
throw new Error("Login request expired. Run `r5dctl auth login` again.");
|
|
1009
1094
|
}
|
|
1095
|
+
if (polled.status === "denied") {
|
|
1096
|
+
throw new Error("Device authorization denied.");
|
|
1097
|
+
}
|
|
1010
1098
|
if (polled.status === "consumed") {
|
|
1011
1099
|
throw new Error("Login request was already consumed.");
|
|
1012
1100
|
}
|
|
@@ -1127,19 +1215,22 @@ async function collectK8sUsage(client, options) {
|
|
|
1127
1215
|
throw new Error("No managed-vCluster workload containers matched the requested filters");
|
|
1128
1216
|
}
|
|
1129
1217
|
const entries = await mapWithConcurrency(candidates, 8, async ({ workload, container }) => {
|
|
1130
|
-
const history = await client.k8s.resourceHistory({ workloadUid: workload.uid, containerName: container.name });
|
|
1218
|
+
const history = await client.k8s.resourceHistory({ workloadUid: workload.uid, containerName: container.name, window: options.window });
|
|
1131
1219
|
return {
|
|
1132
1220
|
workload: {
|
|
1133
1221
|
uid: workload.uid,
|
|
1134
1222
|
kind: workload.kind,
|
|
1135
1223
|
namespace: workload.namespace,
|
|
1136
|
-
name: workload.name
|
|
1224
|
+
name: workload.name,
|
|
1225
|
+
replicas: workload.replicas,
|
|
1226
|
+
readyReplicas: workload.readyReplicas
|
|
1137
1227
|
},
|
|
1138
1228
|
container: {
|
|
1139
1229
|
name: container.name,
|
|
1140
1230
|
currentUsage: container.currentUsage,
|
|
1141
1231
|
pods: container.pods
|
|
1142
1232
|
},
|
|
1233
|
+
sampledPodCount: container.pods.filter((pod) => pod.cpuNanoCores !== null || pod.memoryBytes !== null).length,
|
|
1143
1234
|
summary: summarizeK8sHistory(history),
|
|
1144
1235
|
history
|
|
1145
1236
|
};
|
|
@@ -1165,60 +1256,83 @@ function formatDecimal(value) {
|
|
|
1165
1256
|
function formatK8sCpu(value) {
|
|
1166
1257
|
if (value === null) return "-";
|
|
1167
1258
|
const milliCores = value / 1e6;
|
|
1168
|
-
return
|
|
1259
|
+
return `${formatDecimal(milliCores)}m`;
|
|
1169
1260
|
}
|
|
1170
1261
|
function formatK8sMemory(value) {
|
|
1171
1262
|
if (value === null) return "-";
|
|
1172
|
-
const units = ["B", "
|
|
1263
|
+
const units = ["B", "Ki", "Mi", "Gi", "Ti"];
|
|
1173
1264
|
let amount = value;
|
|
1174
1265
|
let unitIndex = 0;
|
|
1175
1266
|
while (amount >= 1024 && unitIndex < units.length - 1) {
|
|
1176
1267
|
amount /= 1024;
|
|
1177
1268
|
unitIndex += 1;
|
|
1178
1269
|
}
|
|
1179
|
-
return `${formatDecimal(amount)}
|
|
1180
|
-
}
|
|
1181
|
-
function
|
|
1182
|
-
|
|
1183
|
-
const
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1270
|
+
return `${formatDecimal(amount)}${units[unitIndex]}`;
|
|
1271
|
+
}
|
|
1272
|
+
function getK8sUsageState(entry) {
|
|
1273
|
+
const hasHistory = entry.history.envelope.length > 0;
|
|
1274
|
+
const terminalPods = entry.container.pods.filter((pod) => pod.phase === "Succeeded" || pod.phase === "Failed").length;
|
|
1275
|
+
const inactive = entry.sampledPodCount === 0 && (entry.workload.replicas === 0 || entry.container.pods.length > 0 && terminalPods === entry.container.pods.length || entry.workload.kind === "CronJob" && entry.container.pods.length === 0);
|
|
1276
|
+
if (inactive) return "inactive";
|
|
1277
|
+
if (entry.sampledPodCount === 0) return hasHistory ? entry.history.stale ? "stale" : "no current" : "no data";
|
|
1278
|
+
if (!hasHistory) return "warming";
|
|
1279
|
+
if (entry.history.stale) return "stale";
|
|
1280
|
+
if (entry.history.incomplete) return "partial";
|
|
1281
|
+
return "healthy";
|
|
1282
|
+
}
|
|
1283
|
+
function abbreviatedK8sKind(kind) {
|
|
1284
|
+
switch (kind) {
|
|
1285
|
+
case "Deployment":
|
|
1286
|
+
return "deploy";
|
|
1287
|
+
case "StatefulSet":
|
|
1288
|
+
return "sts";
|
|
1289
|
+
case "DaemonSet":
|
|
1290
|
+
return "ds";
|
|
1291
|
+
case "CronJob":
|
|
1292
|
+
return "cron";
|
|
1293
|
+
case "ReplicaSet":
|
|
1294
|
+
return "rs";
|
|
1295
|
+
case "Job":
|
|
1296
|
+
return "job";
|
|
1297
|
+
case "Pod":
|
|
1298
|
+
return "pod";
|
|
1299
|
+
}
|
|
1187
1300
|
}
|
|
1188
1301
|
function renderK8sUsageReport(report) {
|
|
1189
1302
|
const freshness = report.metricsFreshAt ?? "unavailable";
|
|
1190
|
-
const lines = [
|
|
1303
|
+
const lines = [
|
|
1304
|
+
`Window: ${report.window} Collector: ${report.metricsStale ? "stale" : "current"} Fresh: ${freshness}`,
|
|
1305
|
+
"PODS counts pods with current metrics."
|
|
1306
|
+
];
|
|
1191
1307
|
if (report.entries.length === 0) return `${lines.join("\n")}
|
|
1192
1308
|
No managed-vCluster workload containers found.
|
|
1193
1309
|
`;
|
|
1194
|
-
const headers = [
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
const separator = widths.map((width) => "-".repeat(width)).join(" ");
|
|
1221
|
-
lines.push(formatRow(headers), separator, ...rows.map(formatRow));
|
|
1310
|
+
const headers = ["WORKLOAD", "CONTAINER", "PODS", "CPU NOW", "CPU MAX", "MEM NOW", "MEM MAX", "STATE"];
|
|
1311
|
+
const grouped = /* @__PURE__ */ new Map();
|
|
1312
|
+
for (const entry of report.entries) {
|
|
1313
|
+
const entries = grouped.get(entry.workload.namespace) ?? [];
|
|
1314
|
+
entries.push(entry);
|
|
1315
|
+
grouped.set(entry.workload.namespace, entries);
|
|
1316
|
+
}
|
|
1317
|
+
for (const [namespace, entries] of grouped) {
|
|
1318
|
+
const rows = entries.map((entry) => {
|
|
1319
|
+
const state = getK8sUsageState(entry);
|
|
1320
|
+
return [
|
|
1321
|
+
`${abbreviatedK8sKind(entry.workload.kind)}/${entry.workload.name}`,
|
|
1322
|
+
entry.container.name,
|
|
1323
|
+
String(entry.sampledPodCount),
|
|
1324
|
+
formatK8sCpu(entry.container.currentUsage.cpuNanoCores),
|
|
1325
|
+
formatK8sCpu(entry.summary.cpuMaxNanoCores),
|
|
1326
|
+
formatK8sMemory(entry.container.currentUsage.memoryBytes),
|
|
1327
|
+
formatK8sMemory(entry.summary.memoryMaxBytes),
|
|
1328
|
+
state === "healthy" ? "-" : state
|
|
1329
|
+
];
|
|
1330
|
+
});
|
|
1331
|
+
const widths = headers.map((header, column) => Math.max(header.length, ...rows.map((row) => row[column]?.length ?? 0)));
|
|
1332
|
+
const formatRow = (row) => row.map((value, column) => column === row.length - 1 ? value : value.padEnd(widths[column] ?? value.length)).join(" ");
|
|
1333
|
+
const separator = widths.map((width) => "-".repeat(width)).join(" ");
|
|
1334
|
+
lines.push("", `Namespace: ${namespace}`, formatRow(headers), separator, ...rows.map(formatRow));
|
|
1335
|
+
}
|
|
1222
1336
|
return `${lines.join("\n")}
|
|
1223
1337
|
`;
|
|
1224
1338
|
}
|
|
@@ -1313,6 +1427,17 @@ function summarizeEnvData(data) {
|
|
|
1313
1427
|
])
|
|
1314
1428
|
);
|
|
1315
1429
|
}
|
|
1430
|
+
function parseGetEnvsArgs(args) {
|
|
1431
|
+
let showValues = false;
|
|
1432
|
+
for (const arg of args) {
|
|
1433
|
+
if (arg === "--show-values") {
|
|
1434
|
+
showValues = true;
|
|
1435
|
+
continue;
|
|
1436
|
+
}
|
|
1437
|
+
throw new Error(arg.startsWith("-") ? `Unknown get envs flag: ${arg}` : `Unexpected get envs value: ${arg}`);
|
|
1438
|
+
}
|
|
1439
|
+
return { showValues };
|
|
1440
|
+
}
|
|
1316
1441
|
function renderEnvSummary(data) {
|
|
1317
1442
|
const lines = [];
|
|
1318
1443
|
for (const [name, env] of Object.entries(data)) {
|
|
@@ -1321,6 +1446,24 @@ function renderEnvSummary(data) {
|
|
|
1321
1446
|
return lines.length > 0 ? `${lines.join("\n")}
|
|
1322
1447
|
` : "No environment variables declared.\n";
|
|
1323
1448
|
}
|
|
1449
|
+
function renderEnvValues(data) {
|
|
1450
|
+
const lines = Object.entries(data).sort(([left], [right]) => left.localeCompare(right)).map(([name, env]) => `${name} ${env.value === null ? "<missing>" : JSON.stringify(env.value)} ${env.description}`);
|
|
1451
|
+
return lines.length > 0 ? `${lines.join("\n")}
|
|
1452
|
+
` : "No environment variables declared.\n";
|
|
1453
|
+
}
|
|
1454
|
+
function getProjectEnvValue(data, name) {
|
|
1455
|
+
if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(name)) {
|
|
1456
|
+
throw new Error(`Invalid environment variable name: ${name}`);
|
|
1457
|
+
}
|
|
1458
|
+
if (!Object.prototype.hasOwnProperty.call(data, name)) {
|
|
1459
|
+
throw new Error(`Environment variable ${name} is not declared for this project`);
|
|
1460
|
+
}
|
|
1461
|
+
const value = data[name]?.value;
|
|
1462
|
+
if (value === null || value === void 0) {
|
|
1463
|
+
throw new Error(`Environment variable ${name} does not have a value`);
|
|
1464
|
+
}
|
|
1465
|
+
return value;
|
|
1466
|
+
}
|
|
1324
1467
|
function renderEnvSetResult(result) {
|
|
1325
1468
|
const lines = [`Updated ${result.updated.length} environment variable${result.updated.length === 1 ? "" : "s"} for ${result.project}.`];
|
|
1326
1469
|
if (result.skippedExisting.length > 0) lines.push(`Skipped ${result.skippedExisting.length} existing value(s).`);
|
|
@@ -1676,11 +1819,25 @@ Sessions: ${result.sessions.length}
|
|
|
1676
1819
|
return;
|
|
1677
1820
|
}
|
|
1678
1821
|
if (first === "get" && second === "envs") {
|
|
1679
|
-
if (args.length !== 3) throw new Error(`Unexpected get envs argument: ${args[3] ?? ""}`.trim());
|
|
1680
1822
|
const projectRef = requireValue(args[2], "Missing project reference");
|
|
1823
|
+
const options = parseGetEnvsArgs(args.slice(3));
|
|
1681
1824
|
const project = await client.projects.describe(projectRef);
|
|
1682
|
-
const envs =
|
|
1683
|
-
|
|
1825
|
+
const envs = await client.projects.env.get(project.id);
|
|
1826
|
+
if (options.showValues) {
|
|
1827
|
+
write({ project: project.path, envs }, renderEnvValues(envs));
|
|
1828
|
+
} else {
|
|
1829
|
+
const summary = summarizeEnvData(envs);
|
|
1830
|
+
write({ project: project.path, envs: summary }, renderEnvSummary(summary));
|
|
1831
|
+
}
|
|
1832
|
+
return;
|
|
1833
|
+
}
|
|
1834
|
+
if (first === "get" && second === "env") {
|
|
1835
|
+
if (args.length > 4) throw new Error(`Unexpected get env argument: ${args[4]}`);
|
|
1836
|
+
const projectRef = requireValue(args[2], "Missing project reference");
|
|
1837
|
+
const name = requireValue(args[3], "Missing environment variable name");
|
|
1838
|
+
const project = await client.projects.describe(projectRef);
|
|
1839
|
+
const value = getProjectEnvValue(await client.projects.env.get(project.id), name);
|
|
1840
|
+
write({ project: project.path, name, value }, value);
|
|
1684
1841
|
return;
|
|
1685
1842
|
}
|
|
1686
1843
|
if (first === "set" && second === "envs") {
|
|
@@ -2033,9 +2190,29 @@ function resolveCommandExecution(options, rest) {
|
|
|
2033
2190
|
if (options.branch) {
|
|
2034
2191
|
throw new Error("--branch/-b is not supported for project environment variables");
|
|
2035
2192
|
}
|
|
2193
|
+
parseGetEnvsArgs(commandArgs.slice(1));
|
|
2194
|
+
return {
|
|
2195
|
+
kind: "plugin",
|
|
2196
|
+
pluginArgs: ["get", "envs", options.project, ...commandArgs.slice(1)]
|
|
2197
|
+
};
|
|
2198
|
+
}
|
|
2199
|
+
if (target === "env") {
|
|
2200
|
+
if (!options.project) {
|
|
2201
|
+
throw new Error("--project/-p is required for `get env`");
|
|
2202
|
+
}
|
|
2203
|
+
if (options.branch) {
|
|
2204
|
+
throw new Error("--branch/-b is not supported for project environment variables");
|
|
2205
|
+
}
|
|
2206
|
+
const name = requireValue(commandArgs[1], "Environment variable name is required for `get env`");
|
|
2207
|
+
if (commandArgs.length > 2) {
|
|
2208
|
+
throw new Error(`Unexpected get env argument: ${commandArgs[2]}`);
|
|
2209
|
+
}
|
|
2210
|
+
if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(name)) {
|
|
2211
|
+
throw new Error(`Invalid environment variable name: ${name}`);
|
|
2212
|
+
}
|
|
2036
2213
|
return {
|
|
2037
2214
|
kind: "plugin",
|
|
2038
|
-
pluginArgs: ["get", "
|
|
2215
|
+
pluginArgs: ["get", "env", options.project, name]
|
|
2039
2216
|
};
|
|
2040
2217
|
}
|
|
2041
2218
|
throw new Error("Unknown get command");
|
|
@@ -2423,18 +2600,24 @@ async function main(argv = process.argv.slice(2)) {
|
|
|
2423
2600
|
}
|
|
2424
2601
|
}
|
|
2425
2602
|
export {
|
|
2603
|
+
advanceTransientDevicePollBackoff,
|
|
2426
2604
|
collectK8sUsage,
|
|
2427
2605
|
formatK8sCpu,
|
|
2428
2606
|
formatK8sMemory,
|
|
2429
2607
|
formatProcessAge,
|
|
2430
2608
|
getCliHelpText,
|
|
2609
|
+
getK8sUsageState,
|
|
2610
|
+
getProjectEnvValue,
|
|
2431
2611
|
getR5dctlVersion,
|
|
2612
|
+
getTransientDevicePollDelay,
|
|
2613
|
+
handleAuthLogin,
|
|
2432
2614
|
main,
|
|
2433
2615
|
parseAnswerFlags,
|
|
2434
2616
|
parseConversationRenderArgs,
|
|
2435
2617
|
parseConversationWorkDetailArgs,
|
|
2436
2618
|
parseEnvFlags,
|
|
2437
2619
|
parseEnvRequestResponseArgs,
|
|
2620
|
+
parseGetEnvsArgs,
|
|
2438
2621
|
parseGlobalArgs,
|
|
2439
2622
|
parseK8sUsageArgs,
|
|
2440
2623
|
parsePromptArgs,
|
|
@@ -2447,6 +2630,8 @@ export {
|
|
|
2447
2630
|
renderConversationOverviewResponse,
|
|
2448
2631
|
renderConversationResponse,
|
|
2449
2632
|
renderConversationWorkResponse,
|
|
2633
|
+
renderDeviceAuthorizationPrompt,
|
|
2634
|
+
renderEnvValues,
|
|
2450
2635
|
renderK8sUsageReport,
|
|
2451
2636
|
renderProcessHistory,
|
|
2452
2637
|
renderProcessInspection,
|
|
@@ -2455,5 +2640,6 @@ export {
|
|
|
2455
2640
|
renderWorkspaceSync,
|
|
2456
2641
|
resolveCommandExecution,
|
|
2457
2642
|
runR5dctlCli,
|
|
2458
|
-
summarizeEnvData
|
|
2643
|
+
summarizeEnvData,
|
|
2644
|
+
writeConfig
|
|
2459
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[];
|
|
@@ -69,9 +70,19 @@ export type K8sUsageOptions = {
|
|
|
69
70
|
namespace?: string;
|
|
70
71
|
workload?: string;
|
|
71
72
|
container?: string;
|
|
72
|
-
window: "24h";
|
|
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 = {
|
|
@@ -82,21 +93,23 @@ export type K8sUsageSummary = {
|
|
|
82
93
|
sampleCount: number;
|
|
83
94
|
};
|
|
84
95
|
export type K8sUsageReport = {
|
|
85
|
-
window: "24h";
|
|
96
|
+
window: "24h" | "7d";
|
|
86
97
|
collectedAt: string;
|
|
87
98
|
metricsFreshAt: string | null;
|
|
88
99
|
metricsStale: boolean;
|
|
89
100
|
filters: Omit<K8sUsageOptions, "window">;
|
|
90
101
|
entries: Array<{
|
|
91
|
-
workload: Pick<R5dctlK8sWorkload, "uid" | "kind" | "namespace" | "name">;
|
|
102
|
+
workload: Pick<R5dctlK8sWorkload, "uid" | "kind" | "namespace" | "name" | "replicas" | "readyReplicas">;
|
|
92
103
|
container: {
|
|
93
104
|
name: string;
|
|
94
105
|
currentUsage: R5dctlK8sUsage;
|
|
95
106
|
pods: Array<R5dctlK8sUsage & {
|
|
96
107
|
podUid: string;
|
|
97
108
|
podName: string;
|
|
109
|
+
phase: string | null;
|
|
98
110
|
}>;
|
|
99
111
|
};
|
|
112
|
+
sampledPodCount: number;
|
|
100
113
|
summary: K8sUsageSummary;
|
|
101
114
|
history: R5dctlK8sResourceHistory;
|
|
102
115
|
}>;
|
|
@@ -104,6 +117,8 @@ export type K8sUsageReport = {
|
|
|
104
117
|
export declare function collectK8sUsage(client: Pick<R5dctlClient, "k8s">, options: K8sUsageOptions): Promise<K8sUsageReport>;
|
|
105
118
|
export declare function formatK8sCpu(value: number | null): string;
|
|
106
119
|
export declare function formatK8sMemory(value: number | null): string;
|
|
120
|
+
export type K8sUsageState = "healthy" | "partial" | "stale" | "inactive" | "warming" | "no data" | "no current";
|
|
121
|
+
export declare function getK8sUsageState(entry: K8sUsageReport["entries"][number]): K8sUsageState;
|
|
107
122
|
export declare function renderK8sUsageReport(report: K8sUsageReport): string;
|
|
108
123
|
export declare function formatProcessAge(startedAt: string, nowMs?: number): string;
|
|
109
124
|
export declare function renderProcessList(processes: R5dctlProcessRun[], nowMs?: number): string;
|
|
@@ -114,6 +129,11 @@ export type EnvSummaryData = Record<string, {
|
|
|
114
129
|
hasValue: boolean;
|
|
115
130
|
}>;
|
|
116
131
|
export declare function summarizeEnvData(data: R5dctlEnvData): EnvSummaryData;
|
|
132
|
+
export declare function parseGetEnvsArgs(args: string[]): {
|
|
133
|
+
showValues: boolean;
|
|
134
|
+
};
|
|
135
|
+
export declare function renderEnvValues(data: R5dctlEnvData): string;
|
|
136
|
+
export declare function getProjectEnvValue(data: R5dctlEnvData, name: string): string;
|
|
117
137
|
export declare function parseConversationRenderArgs(args: string[]): ConversationRenderOptions;
|
|
118
138
|
export declare function renderConversationResponse(read: R5dctlConversationResponse): string;
|
|
119
139
|
export declare function parseConversationWorkDetailArgs(args: string[]): R5dctlConversationWorkDetail;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ricsam/r5dctl",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.53",
|
|
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.53",
|
|
30
30
|
"dotenv": "^17",
|
|
31
|
+
"qrcode": "^1.5.4",
|
|
31
32
|
"ws": "^8.18.3"
|
|
32
33
|
},
|
|
33
34
|
"files": [
|