@ricsam/r5dctl 0.0.49 → 0.0.51
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 +17 -1
- package/dist/cjs/cli.cjs +321 -6
- package/dist/cjs/package.json +1 -1
- package/dist/mjs/cli.mjs +312 -6
- package/dist/mjs/package.json +1 -1
- package/dist/types/cli.d.ts +48 -1
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -50,6 +50,8 @@ r5dctl delete project <namespace/name|id>
|
|
|
50
50
|
r5dctl -p <project> get branches
|
|
51
51
|
r5dctl -p <project> describe branch <branch>
|
|
52
52
|
r5dctl -p <project> get envs
|
|
53
|
+
r5dctl -p <project> get envs --show-values
|
|
54
|
+
r5dctl -p <project> get env API_KEY
|
|
53
55
|
r5dctl -p <project> set envs --from-env-file ~/.env --only-missing
|
|
54
56
|
r5dctl -p <project> set envs -e API_KEY=value --description "API credential"
|
|
55
57
|
r5dctl -p <project> get sessions --branch <branch>
|
|
@@ -79,13 +81,27 @@ r5dctl ps list
|
|
|
79
81
|
r5dctl -p <project> -b <branch> ps list --worker <label>
|
|
80
82
|
r5dctl -s <session-id> ps list --json
|
|
81
83
|
r5dctl ps stop <run-id>
|
|
84
|
+
|
|
85
|
+
r5dctl k8s usage
|
|
86
|
+
r5dctl k8s usage --namespace default --workload api --container server
|
|
87
|
+
r5dctl k8s usage --window 7d
|
|
88
|
+
r5dctl k8s usage --workload default/api --window 7d --json
|
|
82
89
|
```
|
|
83
90
|
|
|
84
91
|
`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.
|
|
85
92
|
|
|
86
93
|
`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.
|
|
87
94
|
|
|
88
|
-
`
|
|
95
|
+
`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.
|
|
96
|
+
|
|
97
|
+
`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:
|
|
98
|
+
|
|
99
|
+
```bash
|
|
100
|
+
export API_KEY="$(r5dctl -p <project> get env API_KEY)"
|
|
101
|
+
r5dctl -p <project> get env PRIVATE_KEY | openssl pkey -check -noout
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
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.
|
|
89
105
|
|
|
90
106
|
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.
|
|
91
107
|
|
package/dist/cjs/cli.cjs
CHANGED
|
@@ -28,8 +28,13 @@ 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
|
+
collectK8sUsage: () => collectK8sUsage,
|
|
32
|
+
formatK8sCpu: () => formatK8sCpu,
|
|
33
|
+
formatK8sMemory: () => formatK8sMemory,
|
|
31
34
|
formatProcessAge: () => formatProcessAge,
|
|
32
35
|
getCliHelpText: () => getCliHelpText,
|
|
36
|
+
getK8sUsageState: () => getK8sUsageState,
|
|
37
|
+
getProjectEnvValue: () => getProjectEnvValue,
|
|
33
38
|
getR5dctlVersion: () => getR5dctlVersion,
|
|
34
39
|
main: () => main,
|
|
35
40
|
parseAnswerFlags: () => parseAnswerFlags,
|
|
@@ -37,7 +42,9 @@ __export(cli_exports, {
|
|
|
37
42
|
parseConversationWorkDetailArgs: () => parseConversationWorkDetailArgs,
|
|
38
43
|
parseEnvFlags: () => parseEnvFlags,
|
|
39
44
|
parseEnvRequestResponseArgs: () => parseEnvRequestResponseArgs,
|
|
45
|
+
parseGetEnvsArgs: () => parseGetEnvsArgs,
|
|
40
46
|
parseGlobalArgs: () => parseGlobalArgs,
|
|
47
|
+
parseK8sUsageArgs: () => parseK8sUsageArgs,
|
|
41
48
|
parsePromptArgs: () => parsePromptArgs,
|
|
42
49
|
parsePsHistoryArgs: () => parsePsHistoryArgs,
|
|
43
50
|
parsePsListArgs: () => parsePsListArgs,
|
|
@@ -48,6 +55,8 @@ __export(cli_exports, {
|
|
|
48
55
|
renderConversationOverviewResponse: () => renderConversationOverviewResponse,
|
|
49
56
|
renderConversationResponse: () => renderConversationResponse,
|
|
50
57
|
renderConversationWorkResponse: () => renderConversationWorkResponse,
|
|
58
|
+
renderEnvValues: () => renderEnvValues,
|
|
59
|
+
renderK8sUsageReport: () => renderK8sUsageReport,
|
|
51
60
|
renderProcessHistory: () => renderProcessHistory,
|
|
52
61
|
renderProcessInspection: () => renderProcessInspection,
|
|
53
62
|
renderProcessList: () => renderProcessList,
|
|
@@ -126,6 +135,13 @@ const PS_HELP_TEXT = [
|
|
|
126
135
|
"Use -p/--project, -b/--branch, or -s/--session to filter list and history.",
|
|
127
136
|
""
|
|
128
137
|
].join("\n");
|
|
138
|
+
const K8S_HELP_TEXT = [
|
|
139
|
+
"Usage:",
|
|
140
|
+
" r5dctl k8s usage [--namespace <name>] [--workload <name|namespace/name|uid>] [--container <name>] [--window <24h|7d>]",
|
|
141
|
+
"",
|
|
142
|
+
"Show current and rolling CPU and memory usage for managed-vCluster workload containers.",
|
|
143
|
+
""
|
|
144
|
+
].join("\n");
|
|
129
145
|
const SHARED_HELP_ENTRIES = [
|
|
130
146
|
{ section: "auth", usage: "auth status", description: "Show the current authenticated user and credential source." },
|
|
131
147
|
{ section: "auth", usage: "auth logout", description: "Revoke the current credential and clear saved auth." },
|
|
@@ -155,8 +171,13 @@ const SHARED_HELP_ENTRIES = [
|
|
|
155
171
|
{ section: "branches", usage: "-p <project> describe branch <branch>", description: "Show branch URLs and sessions." },
|
|
156
172
|
{
|
|
157
173
|
section: "envs",
|
|
158
|
-
usage: "-p <project> get envs",
|
|
159
|
-
description: "List declared envs
|
|
174
|
+
usage: "-p <project> get envs [--show-values]",
|
|
175
|
+
description: "List declared envs; use --show-values to reveal their values."
|
|
176
|
+
},
|
|
177
|
+
{
|
|
178
|
+
section: "envs",
|
|
179
|
+
usage: "-p <project> get env <name>",
|
|
180
|
+
description: "Write one env value exactly to stdout for command substitution or piping."
|
|
160
181
|
},
|
|
161
182
|
{
|
|
162
183
|
section: "envs",
|
|
@@ -221,6 +242,11 @@ const SHARED_HELP_ENTRIES = [
|
|
|
221
242
|
usage: "-s <session-id> answer-env-request [-e KEY=value] [--context <text>]",
|
|
222
243
|
description: "Answer a pending environment variable request with values, context, or both."
|
|
223
244
|
},
|
|
245
|
+
{
|
|
246
|
+
section: "kubernetes",
|
|
247
|
+
usage: "k8s usage [--namespace <name>] [--workload <name|namespace/name|uid>] [--container <name>] [--window <24h|7d>]",
|
|
248
|
+
description: "Show current and rolling CPU and memory usage for managed-vCluster workload containers."
|
|
249
|
+
},
|
|
224
250
|
{
|
|
225
251
|
section: "processes",
|
|
226
252
|
usage: "ps list [--worker <label>]",
|
|
@@ -259,6 +285,7 @@ const HELP_SECTION_ORDER = [
|
|
|
259
285
|
"branches",
|
|
260
286
|
"envs",
|
|
261
287
|
"sessions",
|
|
288
|
+
"kubernetes",
|
|
262
289
|
"processes",
|
|
263
290
|
"shell",
|
|
264
291
|
"agents",
|
|
@@ -271,6 +298,7 @@ const HELP_SECTION_TITLES = {
|
|
|
271
298
|
branches: "Branches",
|
|
272
299
|
envs: "Environment variables",
|
|
273
300
|
sessions: "Sessions",
|
|
301
|
+
kubernetes: "Kubernetes",
|
|
274
302
|
processes: "Processes",
|
|
275
303
|
shell: "Shell",
|
|
276
304
|
agents: "Agents",
|
|
@@ -935,6 +963,36 @@ function parseRecentSessionArgs(args) {
|
|
|
935
963
|
if (!Number.isInteger(limit) || limit < 1 || limit > 200) throw new Error("--limit must be an integer between 1 and 200");
|
|
936
964
|
return { limit };
|
|
937
965
|
}
|
|
966
|
+
function parseK8sUsageArgs(args) {
|
|
967
|
+
const parsed = { window: "24h" };
|
|
968
|
+
const seen = /* @__PURE__ */ new Set();
|
|
969
|
+
for (let index = 0; index < args.length; index += 1) {
|
|
970
|
+
const arg = args[index];
|
|
971
|
+
if (!arg) continue;
|
|
972
|
+
const option = ["--namespace", "--workload", "--container", "--window"].find(
|
|
973
|
+
(candidate) => arg === candidate || arg.startsWith(`${candidate}=`)
|
|
974
|
+
);
|
|
975
|
+
if (!option) throw new Error(`Unknown k8s usage argument: ${arg}`);
|
|
976
|
+
if (seen.has(option)) throw new Error(`Use ${option} only once`);
|
|
977
|
+
seen.add(option);
|
|
978
|
+
const inlineValue = parseLongOptionWithEquals(arg, option);
|
|
979
|
+
const candidateValue = inlineValue ?? args[index + 1];
|
|
980
|
+
if (!candidateValue || candidateValue.startsWith("--")) throw new Error(`Missing value for ${option}`);
|
|
981
|
+
const value = candidateValue;
|
|
982
|
+
if (inlineValue === void 0) index += 1;
|
|
983
|
+
if (option === "--window") {
|
|
984
|
+
if (value !== "24h" && value !== "7d") throw new Error("--window must be 24h or 7d");
|
|
985
|
+
parsed.window = value;
|
|
986
|
+
} else if (option === "--namespace") {
|
|
987
|
+
parsed.namespace = value;
|
|
988
|
+
} else if (option === "--workload") {
|
|
989
|
+
parsed.workload = value;
|
|
990
|
+
} else {
|
|
991
|
+
parsed.container = value;
|
|
992
|
+
}
|
|
993
|
+
}
|
|
994
|
+
return parsed;
|
|
995
|
+
}
|
|
938
996
|
async function openInBrowser(url) {
|
|
939
997
|
if (process.platform === "darwin") {
|
|
940
998
|
(0, import_node_child_process.spawn)("open", [url], { stdio: "ignore", detached: true }).unref();
|
|
@@ -1096,6 +1154,171 @@ Error: ${result.error}` : "";
|
|
|
1096
1154
|
return `Workspace sync ${result.outcome} on ${result.workerLabel}; HEAD ${head}; ${result.diffSizeBytes} bytes.${discarded}${error}
|
|
1097
1155
|
`;
|
|
1098
1156
|
}
|
|
1157
|
+
function summarizeK8sHistory(history) {
|
|
1158
|
+
if (history.envelope.length === 0) {
|
|
1159
|
+
return {
|
|
1160
|
+
cpuMinNanoCores: null,
|
|
1161
|
+
cpuMaxNanoCores: null,
|
|
1162
|
+
memoryMinBytes: null,
|
|
1163
|
+
memoryMaxBytes: null,
|
|
1164
|
+
sampleCount: 0
|
|
1165
|
+
};
|
|
1166
|
+
}
|
|
1167
|
+
return {
|
|
1168
|
+
cpuMinNanoCores: Math.min(...history.envelope.map((point) => point.cpuMinNanoCores)),
|
|
1169
|
+
cpuMaxNanoCores: Math.max(...history.envelope.map((point) => point.cpuMaxNanoCores)),
|
|
1170
|
+
memoryMinBytes: Math.min(...history.envelope.map((point) => point.memoryMinBytes)),
|
|
1171
|
+
memoryMaxBytes: Math.max(...history.envelope.map((point) => point.memoryMaxBytes)),
|
|
1172
|
+
sampleCount: history.envelope.reduce((total, point) => total + point.sampleCount, 0)
|
|
1173
|
+
};
|
|
1174
|
+
}
|
|
1175
|
+
async function mapWithConcurrency(items, concurrency, fn) {
|
|
1176
|
+
const results = new Array(items.length);
|
|
1177
|
+
let nextIndex = 0;
|
|
1178
|
+
await Promise.all(
|
|
1179
|
+
Array.from({ length: Math.min(concurrency, items.length) }, async () => {
|
|
1180
|
+
while (nextIndex < items.length) {
|
|
1181
|
+
const index = nextIndex++;
|
|
1182
|
+
const item = items[index];
|
|
1183
|
+
if (item !== void 0) results[index] = await fn(item);
|
|
1184
|
+
}
|
|
1185
|
+
})
|
|
1186
|
+
);
|
|
1187
|
+
return results;
|
|
1188
|
+
}
|
|
1189
|
+
async function collectK8sUsage(client, options) {
|
|
1190
|
+
const resources = await client.k8s.resources();
|
|
1191
|
+
const candidates = resources.workloads.flatMap((workload) => workload.containers.map((container) => ({ workload, container }))).filter(({ workload, container }) => {
|
|
1192
|
+
const workloadMatches = !options.workload || options.workload === workload.name || options.workload === `${workload.namespace}/${workload.name}` || options.workload === workload.uid;
|
|
1193
|
+
return (!options.namespace || options.namespace === workload.namespace) && workloadMatches && (!options.container || options.container === container.name);
|
|
1194
|
+
}).sort(
|
|
1195
|
+
(left, right) => left.workload.namespace.localeCompare(right.workload.namespace) || left.workload.name.localeCompare(right.workload.name) || left.container.name.localeCompare(right.container.name)
|
|
1196
|
+
);
|
|
1197
|
+
if (candidates.length === 0 && (options.namespace || options.workload || options.container)) {
|
|
1198
|
+
throw new Error("No managed-vCluster workload containers matched the requested filters");
|
|
1199
|
+
}
|
|
1200
|
+
const entries = await mapWithConcurrency(candidates, 8, async ({ workload, container }) => {
|
|
1201
|
+
const history = await client.k8s.resourceHistory({ workloadUid: workload.uid, containerName: container.name, window: options.window });
|
|
1202
|
+
return {
|
|
1203
|
+
workload: {
|
|
1204
|
+
uid: workload.uid,
|
|
1205
|
+
kind: workload.kind,
|
|
1206
|
+
namespace: workload.namespace,
|
|
1207
|
+
name: workload.name,
|
|
1208
|
+
replicas: workload.replicas,
|
|
1209
|
+
readyReplicas: workload.readyReplicas
|
|
1210
|
+
},
|
|
1211
|
+
container: {
|
|
1212
|
+
name: container.name,
|
|
1213
|
+
currentUsage: container.currentUsage,
|
|
1214
|
+
pods: container.pods
|
|
1215
|
+
},
|
|
1216
|
+
sampledPodCount: container.pods.filter((pod) => pod.cpuNanoCores !== null || pod.memoryBytes !== null).length,
|
|
1217
|
+
summary: summarizeK8sHistory(history),
|
|
1218
|
+
history
|
|
1219
|
+
};
|
|
1220
|
+
});
|
|
1221
|
+
return {
|
|
1222
|
+
window: options.window,
|
|
1223
|
+
collectedAt: resources.collectedAt,
|
|
1224
|
+
metricsFreshAt: resources.metricsFreshAt,
|
|
1225
|
+
metricsStale: resources.metricsStale,
|
|
1226
|
+
filters: {
|
|
1227
|
+
namespace: options.namespace,
|
|
1228
|
+
workload: options.workload,
|
|
1229
|
+
container: options.container
|
|
1230
|
+
},
|
|
1231
|
+
entries
|
|
1232
|
+
};
|
|
1233
|
+
}
|
|
1234
|
+
function formatDecimal(value) {
|
|
1235
|
+
if (value >= 100) return value.toFixed(0);
|
|
1236
|
+
if (value >= 10) return value.toFixed(1).replace(/\.0$/, "");
|
|
1237
|
+
return value.toFixed(2).replace(/\.0+$/, "").replace(/(\.\d)0$/, "$1");
|
|
1238
|
+
}
|
|
1239
|
+
function formatK8sCpu(value) {
|
|
1240
|
+
if (value === null) return "-";
|
|
1241
|
+
const milliCores = value / 1e6;
|
|
1242
|
+
return `${formatDecimal(milliCores)}m`;
|
|
1243
|
+
}
|
|
1244
|
+
function formatK8sMemory(value) {
|
|
1245
|
+
if (value === null) return "-";
|
|
1246
|
+
const units = ["B", "Ki", "Mi", "Gi", "Ti"];
|
|
1247
|
+
let amount = value;
|
|
1248
|
+
let unitIndex = 0;
|
|
1249
|
+
while (amount >= 1024 && unitIndex < units.length - 1) {
|
|
1250
|
+
amount /= 1024;
|
|
1251
|
+
unitIndex += 1;
|
|
1252
|
+
}
|
|
1253
|
+
return `${formatDecimal(amount)}${units[unitIndex]}`;
|
|
1254
|
+
}
|
|
1255
|
+
function getK8sUsageState(entry) {
|
|
1256
|
+
const hasHistory = entry.history.envelope.length > 0;
|
|
1257
|
+
const terminalPods = entry.container.pods.filter((pod) => pod.phase === "Succeeded" || pod.phase === "Failed").length;
|
|
1258
|
+
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);
|
|
1259
|
+
if (inactive) return "inactive";
|
|
1260
|
+
if (entry.sampledPodCount === 0) return hasHistory ? entry.history.stale ? "stale" : "no current" : "no data";
|
|
1261
|
+
if (!hasHistory) return "warming";
|
|
1262
|
+
if (entry.history.stale) return "stale";
|
|
1263
|
+
if (entry.history.incomplete) return "partial";
|
|
1264
|
+
return "healthy";
|
|
1265
|
+
}
|
|
1266
|
+
function abbreviatedK8sKind(kind) {
|
|
1267
|
+
switch (kind) {
|
|
1268
|
+
case "Deployment":
|
|
1269
|
+
return "deploy";
|
|
1270
|
+
case "StatefulSet":
|
|
1271
|
+
return "sts";
|
|
1272
|
+
case "DaemonSet":
|
|
1273
|
+
return "ds";
|
|
1274
|
+
case "CronJob":
|
|
1275
|
+
return "cron";
|
|
1276
|
+
case "ReplicaSet":
|
|
1277
|
+
return "rs";
|
|
1278
|
+
case "Job":
|
|
1279
|
+
return "job";
|
|
1280
|
+
case "Pod":
|
|
1281
|
+
return "pod";
|
|
1282
|
+
}
|
|
1283
|
+
}
|
|
1284
|
+
function renderK8sUsageReport(report) {
|
|
1285
|
+
const freshness = report.metricsFreshAt ?? "unavailable";
|
|
1286
|
+
const lines = [
|
|
1287
|
+
`Window: ${report.window} Collector: ${report.metricsStale ? "stale" : "current"} Fresh: ${freshness}`,
|
|
1288
|
+
"PODS counts pods with current metrics."
|
|
1289
|
+
];
|
|
1290
|
+
if (report.entries.length === 0) return `${lines.join("\n")}
|
|
1291
|
+
No managed-vCluster workload containers found.
|
|
1292
|
+
`;
|
|
1293
|
+
const headers = ["WORKLOAD", "CONTAINER", "PODS", "CPU NOW", "CPU MAX", "MEM NOW", "MEM MAX", "STATE"];
|
|
1294
|
+
const grouped = /* @__PURE__ */ new Map();
|
|
1295
|
+
for (const entry of report.entries) {
|
|
1296
|
+
const entries = grouped.get(entry.workload.namespace) ?? [];
|
|
1297
|
+
entries.push(entry);
|
|
1298
|
+
grouped.set(entry.workload.namespace, entries);
|
|
1299
|
+
}
|
|
1300
|
+
for (const [namespace, entries] of grouped) {
|
|
1301
|
+
const rows = entries.map((entry) => {
|
|
1302
|
+
const state = getK8sUsageState(entry);
|
|
1303
|
+
return [
|
|
1304
|
+
`${abbreviatedK8sKind(entry.workload.kind)}/${entry.workload.name}`,
|
|
1305
|
+
entry.container.name,
|
|
1306
|
+
String(entry.sampledPodCount),
|
|
1307
|
+
formatK8sCpu(entry.container.currentUsage.cpuNanoCores),
|
|
1308
|
+
formatK8sCpu(entry.summary.cpuMaxNanoCores),
|
|
1309
|
+
formatK8sMemory(entry.container.currentUsage.memoryBytes),
|
|
1310
|
+
formatK8sMemory(entry.summary.memoryMaxBytes),
|
|
1311
|
+
state === "healthy" ? "-" : state
|
|
1312
|
+
];
|
|
1313
|
+
});
|
|
1314
|
+
const widths = headers.map((header, column) => Math.max(header.length, ...rows.map((row) => row[column]?.length ?? 0)));
|
|
1315
|
+
const formatRow = (row) => row.map((value, column) => column === row.length - 1 ? value : value.padEnd(widths[column] ?? value.length)).join(" ");
|
|
1316
|
+
const separator = widths.map((width) => "-".repeat(width)).join(" ");
|
|
1317
|
+
lines.push("", `Namespace: ${namespace}`, formatRow(headers), separator, ...rows.map(formatRow));
|
|
1318
|
+
}
|
|
1319
|
+
return `${lines.join("\n")}
|
|
1320
|
+
`;
|
|
1321
|
+
}
|
|
1099
1322
|
function renderSessionDescription(session) {
|
|
1100
1323
|
return [`Session: ${session.id}`, `Project: ${session.projectPath}`, `Branch: ${session.branchName}`].join("\n") + "\n";
|
|
1101
1324
|
}
|
|
@@ -1187,6 +1410,17 @@ function summarizeEnvData(data) {
|
|
|
1187
1410
|
])
|
|
1188
1411
|
);
|
|
1189
1412
|
}
|
|
1413
|
+
function parseGetEnvsArgs(args) {
|
|
1414
|
+
let showValues = false;
|
|
1415
|
+
for (const arg of args) {
|
|
1416
|
+
if (arg === "--show-values") {
|
|
1417
|
+
showValues = true;
|
|
1418
|
+
continue;
|
|
1419
|
+
}
|
|
1420
|
+
throw new Error(arg.startsWith("-") ? `Unknown get envs flag: ${arg}` : `Unexpected get envs value: ${arg}`);
|
|
1421
|
+
}
|
|
1422
|
+
return { showValues };
|
|
1423
|
+
}
|
|
1190
1424
|
function renderEnvSummary(data) {
|
|
1191
1425
|
const lines = [];
|
|
1192
1426
|
for (const [name, env] of Object.entries(data)) {
|
|
@@ -1195,6 +1429,24 @@ function renderEnvSummary(data) {
|
|
|
1195
1429
|
return lines.length > 0 ? `${lines.join("\n")}
|
|
1196
1430
|
` : "No environment variables declared.\n";
|
|
1197
1431
|
}
|
|
1432
|
+
function renderEnvValues(data) {
|
|
1433
|
+
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}`);
|
|
1434
|
+
return lines.length > 0 ? `${lines.join("\n")}
|
|
1435
|
+
` : "No environment variables declared.\n";
|
|
1436
|
+
}
|
|
1437
|
+
function getProjectEnvValue(data, name) {
|
|
1438
|
+
if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(name)) {
|
|
1439
|
+
throw new Error(`Invalid environment variable name: ${name}`);
|
|
1440
|
+
}
|
|
1441
|
+
if (!Object.prototype.hasOwnProperty.call(data, name)) {
|
|
1442
|
+
throw new Error(`Environment variable ${name} is not declared for this project`);
|
|
1443
|
+
}
|
|
1444
|
+
const value = data[name]?.value;
|
|
1445
|
+
if (value === null || value === void 0) {
|
|
1446
|
+
throw new Error(`Environment variable ${name} does not have a value`);
|
|
1447
|
+
}
|
|
1448
|
+
return value;
|
|
1449
|
+
}
|
|
1198
1450
|
function renderEnvSetResult(result) {
|
|
1199
1451
|
const lines = [`Updated ${result.updated.length} environment variable${result.updated.length === 1 ? "" : "s"} for ${result.project}.`];
|
|
1200
1452
|
if (result.skippedExisting.length > 0) lines.push(`Skipped ${result.skippedExisting.length} existing value(s).`);
|
|
@@ -1474,6 +1726,11 @@ async function executeR5dctlCommand(client, json, args) {
|
|
|
1474
1726
|
write(result, renderWorkspaceSync(result));
|
|
1475
1727
|
return;
|
|
1476
1728
|
}
|
|
1729
|
+
if (first === "k8s" && second === "usage") {
|
|
1730
|
+
const report = await collectK8sUsage(client, parseK8sUsageArgs(args.slice(2)));
|
|
1731
|
+
write(report, renderK8sUsageReport(report));
|
|
1732
|
+
return;
|
|
1733
|
+
}
|
|
1477
1734
|
if (first === "ps" && second === "list") {
|
|
1478
1735
|
const processes = await client.processes.list(parsePsListArgs(args.slice(2)));
|
|
1479
1736
|
write(processes, renderProcessList(processes));
|
|
@@ -1545,11 +1802,25 @@ Sessions: ${result.sessions.length}
|
|
|
1545
1802
|
return;
|
|
1546
1803
|
}
|
|
1547
1804
|
if (first === "get" && second === "envs") {
|
|
1548
|
-
if (args.length !== 3) throw new Error(`Unexpected get envs argument: ${args[3] ?? ""}`.trim());
|
|
1549
1805
|
const projectRef = requireValue(args[2], "Missing project reference");
|
|
1806
|
+
const options = parseGetEnvsArgs(args.slice(3));
|
|
1550
1807
|
const project = await client.projects.describe(projectRef);
|
|
1551
|
-
const envs =
|
|
1552
|
-
|
|
1808
|
+
const envs = await client.projects.env.get(project.id);
|
|
1809
|
+
if (options.showValues) {
|
|
1810
|
+
write({ project: project.path, envs }, renderEnvValues(envs));
|
|
1811
|
+
} else {
|
|
1812
|
+
const summary = summarizeEnvData(envs);
|
|
1813
|
+
write({ project: project.path, envs: summary }, renderEnvSummary(summary));
|
|
1814
|
+
}
|
|
1815
|
+
return;
|
|
1816
|
+
}
|
|
1817
|
+
if (first === "get" && second === "env") {
|
|
1818
|
+
if (args.length > 4) throw new Error(`Unexpected get env argument: ${args[4]}`);
|
|
1819
|
+
const projectRef = requireValue(args[2], "Missing project reference");
|
|
1820
|
+
const name = requireValue(args[3], "Missing environment variable name");
|
|
1821
|
+
const project = await client.projects.describe(projectRef);
|
|
1822
|
+
const value = getProjectEnvValue(await client.projects.env.get(project.id), name);
|
|
1823
|
+
write({ project: project.path, name, value }, value);
|
|
1553
1824
|
return;
|
|
1554
1825
|
}
|
|
1555
1826
|
if (first === "set" && second === "envs") {
|
|
@@ -1722,6 +1993,12 @@ function resolveCommandExecution(options, rest) {
|
|
|
1722
1993
|
text: PS_HELP_TEXT
|
|
1723
1994
|
};
|
|
1724
1995
|
}
|
|
1996
|
+
if (command === "k8s" && commandArgs.length === 0) {
|
|
1997
|
+
return {
|
|
1998
|
+
kind: "cli-help",
|
|
1999
|
+
text: K8S_HELP_TEXT
|
|
2000
|
+
};
|
|
2001
|
+
}
|
|
1725
2002
|
if (trailingHelp) {
|
|
1726
2003
|
const cliOnlyHelp = findCliOnlyCommandHelp(normalizedRest);
|
|
1727
2004
|
if (cliOnlyHelp) {
|
|
@@ -1850,6 +2127,15 @@ function resolveCommandExecution(options, rest) {
|
|
|
1850
2127
|
}
|
|
1851
2128
|
throw new Error(`Unknown ps command: ${subcommand ?? ""}`.trim());
|
|
1852
2129
|
}
|
|
2130
|
+
if (command === "k8s") {
|
|
2131
|
+
const subcommand = commandArgs[0];
|
|
2132
|
+
if (subcommand !== "usage") throw new Error(`Unknown k8s command: ${subcommand ?? ""}`.trim());
|
|
2133
|
+
if (options.project || options.branch || options.session) {
|
|
2134
|
+
throw new Error("Project, branch, and session scopes are not supported for `k8s usage`");
|
|
2135
|
+
}
|
|
2136
|
+
parseK8sUsageArgs(commandArgs.slice(1));
|
|
2137
|
+
return { kind: "plugin", pluginArgs: ["k8s", "usage", ...commandArgs.slice(1)] };
|
|
2138
|
+
}
|
|
1853
2139
|
if (command === "get") {
|
|
1854
2140
|
const target = commandArgs[0];
|
|
1855
2141
|
if (target === "projects") {
|
|
@@ -1887,9 +2173,29 @@ function resolveCommandExecution(options, rest) {
|
|
|
1887
2173
|
if (options.branch) {
|
|
1888
2174
|
throw new Error("--branch/-b is not supported for project environment variables");
|
|
1889
2175
|
}
|
|
2176
|
+
parseGetEnvsArgs(commandArgs.slice(1));
|
|
2177
|
+
return {
|
|
2178
|
+
kind: "plugin",
|
|
2179
|
+
pluginArgs: ["get", "envs", options.project, ...commandArgs.slice(1)]
|
|
2180
|
+
};
|
|
2181
|
+
}
|
|
2182
|
+
if (target === "env") {
|
|
2183
|
+
if (!options.project) {
|
|
2184
|
+
throw new Error("--project/-p is required for `get env`");
|
|
2185
|
+
}
|
|
2186
|
+
if (options.branch) {
|
|
2187
|
+
throw new Error("--branch/-b is not supported for project environment variables");
|
|
2188
|
+
}
|
|
2189
|
+
const name = requireValue(commandArgs[1], "Environment variable name is required for `get env`");
|
|
2190
|
+
if (commandArgs.length > 2) {
|
|
2191
|
+
throw new Error(`Unexpected get env argument: ${commandArgs[2]}`);
|
|
2192
|
+
}
|
|
2193
|
+
if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(name)) {
|
|
2194
|
+
throw new Error(`Invalid environment variable name: ${name}`);
|
|
2195
|
+
}
|
|
1890
2196
|
return {
|
|
1891
2197
|
kind: "plugin",
|
|
1892
|
-
pluginArgs: ["get", "
|
|
2198
|
+
pluginArgs: ["get", "env", options.project, name]
|
|
1893
2199
|
};
|
|
1894
2200
|
}
|
|
1895
2201
|
throw new Error("Unknown get command");
|
|
@@ -2278,8 +2584,13 @@ async function main(argv = process.argv.slice(2)) {
|
|
|
2278
2584
|
}
|
|
2279
2585
|
// Annotate the CommonJS export names for ESM import in node:
|
|
2280
2586
|
0 && (module.exports = {
|
|
2587
|
+
collectK8sUsage,
|
|
2588
|
+
formatK8sCpu,
|
|
2589
|
+
formatK8sMemory,
|
|
2281
2590
|
formatProcessAge,
|
|
2282
2591
|
getCliHelpText,
|
|
2592
|
+
getK8sUsageState,
|
|
2593
|
+
getProjectEnvValue,
|
|
2283
2594
|
getR5dctlVersion,
|
|
2284
2595
|
main,
|
|
2285
2596
|
parseAnswerFlags,
|
|
@@ -2287,7 +2598,9 @@ async function main(argv = process.argv.slice(2)) {
|
|
|
2287
2598
|
parseConversationWorkDetailArgs,
|
|
2288
2599
|
parseEnvFlags,
|
|
2289
2600
|
parseEnvRequestResponseArgs,
|
|
2601
|
+
parseGetEnvsArgs,
|
|
2290
2602
|
parseGlobalArgs,
|
|
2603
|
+
parseK8sUsageArgs,
|
|
2291
2604
|
parsePromptArgs,
|
|
2292
2605
|
parsePsHistoryArgs,
|
|
2293
2606
|
parsePsListArgs,
|
|
@@ -2298,6 +2611,8 @@ async function main(argv = process.argv.slice(2)) {
|
|
|
2298
2611
|
renderConversationOverviewResponse,
|
|
2299
2612
|
renderConversationResponse,
|
|
2300
2613
|
renderConversationWorkResponse,
|
|
2614
|
+
renderEnvValues,
|
|
2615
|
+
renderK8sUsageReport,
|
|
2301
2616
|
renderProcessHistory,
|
|
2302
2617
|
renderProcessInspection,
|
|
2303
2618
|
renderProcessList,
|
package/dist/cjs/package.json
CHANGED
package/dist/mjs/cli.mjs
CHANGED
|
@@ -69,6 +69,13 @@ const PS_HELP_TEXT = [
|
|
|
69
69
|
"Use -p/--project, -b/--branch, or -s/--session to filter list and history.",
|
|
70
70
|
""
|
|
71
71
|
].join("\n");
|
|
72
|
+
const K8S_HELP_TEXT = [
|
|
73
|
+
"Usage:",
|
|
74
|
+
" r5dctl k8s usage [--namespace <name>] [--workload <name|namespace/name|uid>] [--container <name>] [--window <24h|7d>]",
|
|
75
|
+
"",
|
|
76
|
+
"Show current and rolling CPU and memory usage for managed-vCluster workload containers.",
|
|
77
|
+
""
|
|
78
|
+
].join("\n");
|
|
72
79
|
const SHARED_HELP_ENTRIES = [
|
|
73
80
|
{ section: "auth", usage: "auth status", description: "Show the current authenticated user and credential source." },
|
|
74
81
|
{ section: "auth", usage: "auth logout", description: "Revoke the current credential and clear saved auth." },
|
|
@@ -98,8 +105,13 @@ const SHARED_HELP_ENTRIES = [
|
|
|
98
105
|
{ section: "branches", usage: "-p <project> describe branch <branch>", description: "Show branch URLs and sessions." },
|
|
99
106
|
{
|
|
100
107
|
section: "envs",
|
|
101
|
-
usage: "-p <project> get envs",
|
|
102
|
-
description: "List declared envs
|
|
108
|
+
usage: "-p <project> get envs [--show-values]",
|
|
109
|
+
description: "List declared envs; use --show-values to reveal their values."
|
|
110
|
+
},
|
|
111
|
+
{
|
|
112
|
+
section: "envs",
|
|
113
|
+
usage: "-p <project> get env <name>",
|
|
114
|
+
description: "Write one env value exactly to stdout for command substitution or piping."
|
|
103
115
|
},
|
|
104
116
|
{
|
|
105
117
|
section: "envs",
|
|
@@ -164,6 +176,11 @@ const SHARED_HELP_ENTRIES = [
|
|
|
164
176
|
usage: "-s <session-id> answer-env-request [-e KEY=value] [--context <text>]",
|
|
165
177
|
description: "Answer a pending environment variable request with values, context, or both."
|
|
166
178
|
},
|
|
179
|
+
{
|
|
180
|
+
section: "kubernetes",
|
|
181
|
+
usage: "k8s usage [--namespace <name>] [--workload <name|namespace/name|uid>] [--container <name>] [--window <24h|7d>]",
|
|
182
|
+
description: "Show current and rolling CPU and memory usage for managed-vCluster workload containers."
|
|
183
|
+
},
|
|
167
184
|
{
|
|
168
185
|
section: "processes",
|
|
169
186
|
usage: "ps list [--worker <label>]",
|
|
@@ -202,6 +219,7 @@ const HELP_SECTION_ORDER = [
|
|
|
202
219
|
"branches",
|
|
203
220
|
"envs",
|
|
204
221
|
"sessions",
|
|
222
|
+
"kubernetes",
|
|
205
223
|
"processes",
|
|
206
224
|
"shell",
|
|
207
225
|
"agents",
|
|
@@ -214,6 +232,7 @@ const HELP_SECTION_TITLES = {
|
|
|
214
232
|
branches: "Branches",
|
|
215
233
|
envs: "Environment variables",
|
|
216
234
|
sessions: "Sessions",
|
|
235
|
+
kubernetes: "Kubernetes",
|
|
217
236
|
processes: "Processes",
|
|
218
237
|
shell: "Shell",
|
|
219
238
|
agents: "Agents",
|
|
@@ -878,6 +897,36 @@ function parseRecentSessionArgs(args) {
|
|
|
878
897
|
if (!Number.isInteger(limit) || limit < 1 || limit > 200) throw new Error("--limit must be an integer between 1 and 200");
|
|
879
898
|
return { limit };
|
|
880
899
|
}
|
|
900
|
+
function parseK8sUsageArgs(args) {
|
|
901
|
+
const parsed = { window: "24h" };
|
|
902
|
+
const seen = /* @__PURE__ */ new Set();
|
|
903
|
+
for (let index = 0; index < args.length; index += 1) {
|
|
904
|
+
const arg = args[index];
|
|
905
|
+
if (!arg) continue;
|
|
906
|
+
const option = ["--namespace", "--workload", "--container", "--window"].find(
|
|
907
|
+
(candidate) => arg === candidate || arg.startsWith(`${candidate}=`)
|
|
908
|
+
);
|
|
909
|
+
if (!option) throw new Error(`Unknown k8s usage argument: ${arg}`);
|
|
910
|
+
if (seen.has(option)) throw new Error(`Use ${option} only once`);
|
|
911
|
+
seen.add(option);
|
|
912
|
+
const inlineValue = parseLongOptionWithEquals(arg, option);
|
|
913
|
+
const candidateValue = inlineValue ?? args[index + 1];
|
|
914
|
+
if (!candidateValue || candidateValue.startsWith("--")) throw new Error(`Missing value for ${option}`);
|
|
915
|
+
const value = candidateValue;
|
|
916
|
+
if (inlineValue === void 0) index += 1;
|
|
917
|
+
if (option === "--window") {
|
|
918
|
+
if (value !== "24h" && value !== "7d") throw new Error("--window must be 24h or 7d");
|
|
919
|
+
parsed.window = value;
|
|
920
|
+
} else if (option === "--namespace") {
|
|
921
|
+
parsed.namespace = value;
|
|
922
|
+
} else if (option === "--workload") {
|
|
923
|
+
parsed.workload = value;
|
|
924
|
+
} else {
|
|
925
|
+
parsed.container = value;
|
|
926
|
+
}
|
|
927
|
+
}
|
|
928
|
+
return parsed;
|
|
929
|
+
}
|
|
881
930
|
async function openInBrowser(url) {
|
|
882
931
|
if (process.platform === "darwin") {
|
|
883
932
|
spawn("open", [url], { stdio: "ignore", detached: true }).unref();
|
|
@@ -1039,6 +1088,171 @@ Error: ${result.error}` : "";
|
|
|
1039
1088
|
return `Workspace sync ${result.outcome} on ${result.workerLabel}; HEAD ${head}; ${result.diffSizeBytes} bytes.${discarded}${error}
|
|
1040
1089
|
`;
|
|
1041
1090
|
}
|
|
1091
|
+
function summarizeK8sHistory(history) {
|
|
1092
|
+
if (history.envelope.length === 0) {
|
|
1093
|
+
return {
|
|
1094
|
+
cpuMinNanoCores: null,
|
|
1095
|
+
cpuMaxNanoCores: null,
|
|
1096
|
+
memoryMinBytes: null,
|
|
1097
|
+
memoryMaxBytes: null,
|
|
1098
|
+
sampleCount: 0
|
|
1099
|
+
};
|
|
1100
|
+
}
|
|
1101
|
+
return {
|
|
1102
|
+
cpuMinNanoCores: Math.min(...history.envelope.map((point) => point.cpuMinNanoCores)),
|
|
1103
|
+
cpuMaxNanoCores: Math.max(...history.envelope.map((point) => point.cpuMaxNanoCores)),
|
|
1104
|
+
memoryMinBytes: Math.min(...history.envelope.map((point) => point.memoryMinBytes)),
|
|
1105
|
+
memoryMaxBytes: Math.max(...history.envelope.map((point) => point.memoryMaxBytes)),
|
|
1106
|
+
sampleCount: history.envelope.reduce((total, point) => total + point.sampleCount, 0)
|
|
1107
|
+
};
|
|
1108
|
+
}
|
|
1109
|
+
async function mapWithConcurrency(items, concurrency, fn) {
|
|
1110
|
+
const results = new Array(items.length);
|
|
1111
|
+
let nextIndex = 0;
|
|
1112
|
+
await Promise.all(
|
|
1113
|
+
Array.from({ length: Math.min(concurrency, items.length) }, async () => {
|
|
1114
|
+
while (nextIndex < items.length) {
|
|
1115
|
+
const index = nextIndex++;
|
|
1116
|
+
const item = items[index];
|
|
1117
|
+
if (item !== void 0) results[index] = await fn(item);
|
|
1118
|
+
}
|
|
1119
|
+
})
|
|
1120
|
+
);
|
|
1121
|
+
return results;
|
|
1122
|
+
}
|
|
1123
|
+
async function collectK8sUsage(client, options) {
|
|
1124
|
+
const resources = await client.k8s.resources();
|
|
1125
|
+
const candidates = resources.workloads.flatMap((workload) => workload.containers.map((container) => ({ workload, container }))).filter(({ workload, container }) => {
|
|
1126
|
+
const workloadMatches = !options.workload || options.workload === workload.name || options.workload === `${workload.namespace}/${workload.name}` || options.workload === workload.uid;
|
|
1127
|
+
return (!options.namespace || options.namespace === workload.namespace) && workloadMatches && (!options.container || options.container === container.name);
|
|
1128
|
+
}).sort(
|
|
1129
|
+
(left, right) => left.workload.namespace.localeCompare(right.workload.namespace) || left.workload.name.localeCompare(right.workload.name) || left.container.name.localeCompare(right.container.name)
|
|
1130
|
+
);
|
|
1131
|
+
if (candidates.length === 0 && (options.namespace || options.workload || options.container)) {
|
|
1132
|
+
throw new Error("No managed-vCluster workload containers matched the requested filters");
|
|
1133
|
+
}
|
|
1134
|
+
const entries = await mapWithConcurrency(candidates, 8, async ({ workload, container }) => {
|
|
1135
|
+
const history = await client.k8s.resourceHistory({ workloadUid: workload.uid, containerName: container.name, window: options.window });
|
|
1136
|
+
return {
|
|
1137
|
+
workload: {
|
|
1138
|
+
uid: workload.uid,
|
|
1139
|
+
kind: workload.kind,
|
|
1140
|
+
namespace: workload.namespace,
|
|
1141
|
+
name: workload.name,
|
|
1142
|
+
replicas: workload.replicas,
|
|
1143
|
+
readyReplicas: workload.readyReplicas
|
|
1144
|
+
},
|
|
1145
|
+
container: {
|
|
1146
|
+
name: container.name,
|
|
1147
|
+
currentUsage: container.currentUsage,
|
|
1148
|
+
pods: container.pods
|
|
1149
|
+
},
|
|
1150
|
+
sampledPodCount: container.pods.filter((pod) => pod.cpuNanoCores !== null || pod.memoryBytes !== null).length,
|
|
1151
|
+
summary: summarizeK8sHistory(history),
|
|
1152
|
+
history
|
|
1153
|
+
};
|
|
1154
|
+
});
|
|
1155
|
+
return {
|
|
1156
|
+
window: options.window,
|
|
1157
|
+
collectedAt: resources.collectedAt,
|
|
1158
|
+
metricsFreshAt: resources.metricsFreshAt,
|
|
1159
|
+
metricsStale: resources.metricsStale,
|
|
1160
|
+
filters: {
|
|
1161
|
+
namespace: options.namespace,
|
|
1162
|
+
workload: options.workload,
|
|
1163
|
+
container: options.container
|
|
1164
|
+
},
|
|
1165
|
+
entries
|
|
1166
|
+
};
|
|
1167
|
+
}
|
|
1168
|
+
function formatDecimal(value) {
|
|
1169
|
+
if (value >= 100) return value.toFixed(0);
|
|
1170
|
+
if (value >= 10) return value.toFixed(1).replace(/\.0$/, "");
|
|
1171
|
+
return value.toFixed(2).replace(/\.0+$/, "").replace(/(\.\d)0$/, "$1");
|
|
1172
|
+
}
|
|
1173
|
+
function formatK8sCpu(value) {
|
|
1174
|
+
if (value === null) return "-";
|
|
1175
|
+
const milliCores = value / 1e6;
|
|
1176
|
+
return `${formatDecimal(milliCores)}m`;
|
|
1177
|
+
}
|
|
1178
|
+
function formatK8sMemory(value) {
|
|
1179
|
+
if (value === null) return "-";
|
|
1180
|
+
const units = ["B", "Ki", "Mi", "Gi", "Ti"];
|
|
1181
|
+
let amount = value;
|
|
1182
|
+
let unitIndex = 0;
|
|
1183
|
+
while (amount >= 1024 && unitIndex < units.length - 1) {
|
|
1184
|
+
amount /= 1024;
|
|
1185
|
+
unitIndex += 1;
|
|
1186
|
+
}
|
|
1187
|
+
return `${formatDecimal(amount)}${units[unitIndex]}`;
|
|
1188
|
+
}
|
|
1189
|
+
function getK8sUsageState(entry) {
|
|
1190
|
+
const hasHistory = entry.history.envelope.length > 0;
|
|
1191
|
+
const terminalPods = entry.container.pods.filter((pod) => pod.phase === "Succeeded" || pod.phase === "Failed").length;
|
|
1192
|
+
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);
|
|
1193
|
+
if (inactive) return "inactive";
|
|
1194
|
+
if (entry.sampledPodCount === 0) return hasHistory ? entry.history.stale ? "stale" : "no current" : "no data";
|
|
1195
|
+
if (!hasHistory) return "warming";
|
|
1196
|
+
if (entry.history.stale) return "stale";
|
|
1197
|
+
if (entry.history.incomplete) return "partial";
|
|
1198
|
+
return "healthy";
|
|
1199
|
+
}
|
|
1200
|
+
function abbreviatedK8sKind(kind) {
|
|
1201
|
+
switch (kind) {
|
|
1202
|
+
case "Deployment":
|
|
1203
|
+
return "deploy";
|
|
1204
|
+
case "StatefulSet":
|
|
1205
|
+
return "sts";
|
|
1206
|
+
case "DaemonSet":
|
|
1207
|
+
return "ds";
|
|
1208
|
+
case "CronJob":
|
|
1209
|
+
return "cron";
|
|
1210
|
+
case "ReplicaSet":
|
|
1211
|
+
return "rs";
|
|
1212
|
+
case "Job":
|
|
1213
|
+
return "job";
|
|
1214
|
+
case "Pod":
|
|
1215
|
+
return "pod";
|
|
1216
|
+
}
|
|
1217
|
+
}
|
|
1218
|
+
function renderK8sUsageReport(report) {
|
|
1219
|
+
const freshness = report.metricsFreshAt ?? "unavailable";
|
|
1220
|
+
const lines = [
|
|
1221
|
+
`Window: ${report.window} Collector: ${report.metricsStale ? "stale" : "current"} Fresh: ${freshness}`,
|
|
1222
|
+
"PODS counts pods with current metrics."
|
|
1223
|
+
];
|
|
1224
|
+
if (report.entries.length === 0) return `${lines.join("\n")}
|
|
1225
|
+
No managed-vCluster workload containers found.
|
|
1226
|
+
`;
|
|
1227
|
+
const headers = ["WORKLOAD", "CONTAINER", "PODS", "CPU NOW", "CPU MAX", "MEM NOW", "MEM MAX", "STATE"];
|
|
1228
|
+
const grouped = /* @__PURE__ */ new Map();
|
|
1229
|
+
for (const entry of report.entries) {
|
|
1230
|
+
const entries = grouped.get(entry.workload.namespace) ?? [];
|
|
1231
|
+
entries.push(entry);
|
|
1232
|
+
grouped.set(entry.workload.namespace, entries);
|
|
1233
|
+
}
|
|
1234
|
+
for (const [namespace, entries] of grouped) {
|
|
1235
|
+
const rows = entries.map((entry) => {
|
|
1236
|
+
const state = getK8sUsageState(entry);
|
|
1237
|
+
return [
|
|
1238
|
+
`${abbreviatedK8sKind(entry.workload.kind)}/${entry.workload.name}`,
|
|
1239
|
+
entry.container.name,
|
|
1240
|
+
String(entry.sampledPodCount),
|
|
1241
|
+
formatK8sCpu(entry.container.currentUsage.cpuNanoCores),
|
|
1242
|
+
formatK8sCpu(entry.summary.cpuMaxNanoCores),
|
|
1243
|
+
formatK8sMemory(entry.container.currentUsage.memoryBytes),
|
|
1244
|
+
formatK8sMemory(entry.summary.memoryMaxBytes),
|
|
1245
|
+
state === "healthy" ? "-" : state
|
|
1246
|
+
];
|
|
1247
|
+
});
|
|
1248
|
+
const widths = headers.map((header, column) => Math.max(header.length, ...rows.map((row) => row[column]?.length ?? 0)));
|
|
1249
|
+
const formatRow = (row) => row.map((value, column) => column === row.length - 1 ? value : value.padEnd(widths[column] ?? value.length)).join(" ");
|
|
1250
|
+
const separator = widths.map((width) => "-".repeat(width)).join(" ");
|
|
1251
|
+
lines.push("", `Namespace: ${namespace}`, formatRow(headers), separator, ...rows.map(formatRow));
|
|
1252
|
+
}
|
|
1253
|
+
return `${lines.join("\n")}
|
|
1254
|
+
`;
|
|
1255
|
+
}
|
|
1042
1256
|
function renderSessionDescription(session) {
|
|
1043
1257
|
return [`Session: ${session.id}`, `Project: ${session.projectPath}`, `Branch: ${session.branchName}`].join("\n") + "\n";
|
|
1044
1258
|
}
|
|
@@ -1130,6 +1344,17 @@ function summarizeEnvData(data) {
|
|
|
1130
1344
|
])
|
|
1131
1345
|
);
|
|
1132
1346
|
}
|
|
1347
|
+
function parseGetEnvsArgs(args) {
|
|
1348
|
+
let showValues = false;
|
|
1349
|
+
for (const arg of args) {
|
|
1350
|
+
if (arg === "--show-values") {
|
|
1351
|
+
showValues = true;
|
|
1352
|
+
continue;
|
|
1353
|
+
}
|
|
1354
|
+
throw new Error(arg.startsWith("-") ? `Unknown get envs flag: ${arg}` : `Unexpected get envs value: ${arg}`);
|
|
1355
|
+
}
|
|
1356
|
+
return { showValues };
|
|
1357
|
+
}
|
|
1133
1358
|
function renderEnvSummary(data) {
|
|
1134
1359
|
const lines = [];
|
|
1135
1360
|
for (const [name, env] of Object.entries(data)) {
|
|
@@ -1138,6 +1363,24 @@ function renderEnvSummary(data) {
|
|
|
1138
1363
|
return lines.length > 0 ? `${lines.join("\n")}
|
|
1139
1364
|
` : "No environment variables declared.\n";
|
|
1140
1365
|
}
|
|
1366
|
+
function renderEnvValues(data) {
|
|
1367
|
+
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}`);
|
|
1368
|
+
return lines.length > 0 ? `${lines.join("\n")}
|
|
1369
|
+
` : "No environment variables declared.\n";
|
|
1370
|
+
}
|
|
1371
|
+
function getProjectEnvValue(data, name) {
|
|
1372
|
+
if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(name)) {
|
|
1373
|
+
throw new Error(`Invalid environment variable name: ${name}`);
|
|
1374
|
+
}
|
|
1375
|
+
if (!Object.prototype.hasOwnProperty.call(data, name)) {
|
|
1376
|
+
throw new Error(`Environment variable ${name} is not declared for this project`);
|
|
1377
|
+
}
|
|
1378
|
+
const value = data[name]?.value;
|
|
1379
|
+
if (value === null || value === void 0) {
|
|
1380
|
+
throw new Error(`Environment variable ${name} does not have a value`);
|
|
1381
|
+
}
|
|
1382
|
+
return value;
|
|
1383
|
+
}
|
|
1141
1384
|
function renderEnvSetResult(result) {
|
|
1142
1385
|
const lines = [`Updated ${result.updated.length} environment variable${result.updated.length === 1 ? "" : "s"} for ${result.project}.`];
|
|
1143
1386
|
if (result.skippedExisting.length > 0) lines.push(`Skipped ${result.skippedExisting.length} existing value(s).`);
|
|
@@ -1417,6 +1660,11 @@ async function executeR5dctlCommand(client, json, args) {
|
|
|
1417
1660
|
write(result, renderWorkspaceSync(result));
|
|
1418
1661
|
return;
|
|
1419
1662
|
}
|
|
1663
|
+
if (first === "k8s" && second === "usage") {
|
|
1664
|
+
const report = await collectK8sUsage(client, parseK8sUsageArgs(args.slice(2)));
|
|
1665
|
+
write(report, renderK8sUsageReport(report));
|
|
1666
|
+
return;
|
|
1667
|
+
}
|
|
1420
1668
|
if (first === "ps" && second === "list") {
|
|
1421
1669
|
const processes = await client.processes.list(parsePsListArgs(args.slice(2)));
|
|
1422
1670
|
write(processes, renderProcessList(processes));
|
|
@@ -1488,11 +1736,25 @@ Sessions: ${result.sessions.length}
|
|
|
1488
1736
|
return;
|
|
1489
1737
|
}
|
|
1490
1738
|
if (first === "get" && second === "envs") {
|
|
1491
|
-
if (args.length !== 3) throw new Error(`Unexpected get envs argument: ${args[3] ?? ""}`.trim());
|
|
1492
1739
|
const projectRef = requireValue(args[2], "Missing project reference");
|
|
1740
|
+
const options = parseGetEnvsArgs(args.slice(3));
|
|
1493
1741
|
const project = await client.projects.describe(projectRef);
|
|
1494
|
-
const envs =
|
|
1495
|
-
|
|
1742
|
+
const envs = await client.projects.env.get(project.id);
|
|
1743
|
+
if (options.showValues) {
|
|
1744
|
+
write({ project: project.path, envs }, renderEnvValues(envs));
|
|
1745
|
+
} else {
|
|
1746
|
+
const summary = summarizeEnvData(envs);
|
|
1747
|
+
write({ project: project.path, envs: summary }, renderEnvSummary(summary));
|
|
1748
|
+
}
|
|
1749
|
+
return;
|
|
1750
|
+
}
|
|
1751
|
+
if (first === "get" && second === "env") {
|
|
1752
|
+
if (args.length > 4) throw new Error(`Unexpected get env argument: ${args[4]}`);
|
|
1753
|
+
const projectRef = requireValue(args[2], "Missing project reference");
|
|
1754
|
+
const name = requireValue(args[3], "Missing environment variable name");
|
|
1755
|
+
const project = await client.projects.describe(projectRef);
|
|
1756
|
+
const value = getProjectEnvValue(await client.projects.env.get(project.id), name);
|
|
1757
|
+
write({ project: project.path, name, value }, value);
|
|
1496
1758
|
return;
|
|
1497
1759
|
}
|
|
1498
1760
|
if (first === "set" && second === "envs") {
|
|
@@ -1665,6 +1927,12 @@ function resolveCommandExecution(options, rest) {
|
|
|
1665
1927
|
text: PS_HELP_TEXT
|
|
1666
1928
|
};
|
|
1667
1929
|
}
|
|
1930
|
+
if (command === "k8s" && commandArgs.length === 0) {
|
|
1931
|
+
return {
|
|
1932
|
+
kind: "cli-help",
|
|
1933
|
+
text: K8S_HELP_TEXT
|
|
1934
|
+
};
|
|
1935
|
+
}
|
|
1668
1936
|
if (trailingHelp) {
|
|
1669
1937
|
const cliOnlyHelp = findCliOnlyCommandHelp(normalizedRest);
|
|
1670
1938
|
if (cliOnlyHelp) {
|
|
@@ -1793,6 +2061,15 @@ function resolveCommandExecution(options, rest) {
|
|
|
1793
2061
|
}
|
|
1794
2062
|
throw new Error(`Unknown ps command: ${subcommand ?? ""}`.trim());
|
|
1795
2063
|
}
|
|
2064
|
+
if (command === "k8s") {
|
|
2065
|
+
const subcommand = commandArgs[0];
|
|
2066
|
+
if (subcommand !== "usage") throw new Error(`Unknown k8s command: ${subcommand ?? ""}`.trim());
|
|
2067
|
+
if (options.project || options.branch || options.session) {
|
|
2068
|
+
throw new Error("Project, branch, and session scopes are not supported for `k8s usage`");
|
|
2069
|
+
}
|
|
2070
|
+
parseK8sUsageArgs(commandArgs.slice(1));
|
|
2071
|
+
return { kind: "plugin", pluginArgs: ["k8s", "usage", ...commandArgs.slice(1)] };
|
|
2072
|
+
}
|
|
1796
2073
|
if (command === "get") {
|
|
1797
2074
|
const target = commandArgs[0];
|
|
1798
2075
|
if (target === "projects") {
|
|
@@ -1830,9 +2107,29 @@ function resolveCommandExecution(options, rest) {
|
|
|
1830
2107
|
if (options.branch) {
|
|
1831
2108
|
throw new Error("--branch/-b is not supported for project environment variables");
|
|
1832
2109
|
}
|
|
2110
|
+
parseGetEnvsArgs(commandArgs.slice(1));
|
|
2111
|
+
return {
|
|
2112
|
+
kind: "plugin",
|
|
2113
|
+
pluginArgs: ["get", "envs", options.project, ...commandArgs.slice(1)]
|
|
2114
|
+
};
|
|
2115
|
+
}
|
|
2116
|
+
if (target === "env") {
|
|
2117
|
+
if (!options.project) {
|
|
2118
|
+
throw new Error("--project/-p is required for `get env`");
|
|
2119
|
+
}
|
|
2120
|
+
if (options.branch) {
|
|
2121
|
+
throw new Error("--branch/-b is not supported for project environment variables");
|
|
2122
|
+
}
|
|
2123
|
+
const name = requireValue(commandArgs[1], "Environment variable name is required for `get env`");
|
|
2124
|
+
if (commandArgs.length > 2) {
|
|
2125
|
+
throw new Error(`Unexpected get env argument: ${commandArgs[2]}`);
|
|
2126
|
+
}
|
|
2127
|
+
if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(name)) {
|
|
2128
|
+
throw new Error(`Invalid environment variable name: ${name}`);
|
|
2129
|
+
}
|
|
1833
2130
|
return {
|
|
1834
2131
|
kind: "plugin",
|
|
1835
|
-
pluginArgs: ["get", "
|
|
2132
|
+
pluginArgs: ["get", "env", options.project, name]
|
|
1836
2133
|
};
|
|
1837
2134
|
}
|
|
1838
2135
|
throw new Error("Unknown get command");
|
|
@@ -2220,8 +2517,13 @@ async function main(argv = process.argv.slice(2)) {
|
|
|
2220
2517
|
}
|
|
2221
2518
|
}
|
|
2222
2519
|
export {
|
|
2520
|
+
collectK8sUsage,
|
|
2521
|
+
formatK8sCpu,
|
|
2522
|
+
formatK8sMemory,
|
|
2223
2523
|
formatProcessAge,
|
|
2224
2524
|
getCliHelpText,
|
|
2525
|
+
getK8sUsageState,
|
|
2526
|
+
getProjectEnvValue,
|
|
2225
2527
|
getR5dctlVersion,
|
|
2226
2528
|
main,
|
|
2227
2529
|
parseAnswerFlags,
|
|
@@ -2229,7 +2531,9 @@ export {
|
|
|
2229
2531
|
parseConversationWorkDetailArgs,
|
|
2230
2532
|
parseEnvFlags,
|
|
2231
2533
|
parseEnvRequestResponseArgs,
|
|
2534
|
+
parseGetEnvsArgs,
|
|
2232
2535
|
parseGlobalArgs,
|
|
2536
|
+
parseK8sUsageArgs,
|
|
2233
2537
|
parsePromptArgs,
|
|
2234
2538
|
parsePsHistoryArgs,
|
|
2235
2539
|
parsePsListArgs,
|
|
@@ -2240,6 +2544,8 @@ export {
|
|
|
2240
2544
|
renderConversationOverviewResponse,
|
|
2241
2545
|
renderConversationResponse,
|
|
2242
2546
|
renderConversationWorkResponse,
|
|
2547
|
+
renderEnvValues,
|
|
2548
|
+
renderK8sUsageReport,
|
|
2243
2549
|
renderProcessHistory,
|
|
2244
2550
|
renderProcessInspection,
|
|
2245
2551
|
renderProcessList,
|
package/dist/mjs/package.json
CHANGED
package/dist/types/cli.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type R5dctlConversationRenderOptions, type R5dctlConversationResponse, type R5dctlConversationNodeResponse, type R5dctlConversationOverviewResponse, type R5dctlConversationWorkDetail, type R5dctlConversationWorkResponse, type R5dctlEnvData, type R5dctlProcessInspection, type R5dctlProcessListInput, type R5dctlProcessRun, type R5dctlWorkspaceStatus, type R5dctlWorkspaceSyncResult, type ChatMode, type ModelTier } from "@ricsam/r5d-api";
|
|
1
|
+
import { R5dctlClient, type R5dctlConversationRenderOptions, type R5dctlConversationResponse, type R5dctlConversationNodeResponse, type R5dctlConversationOverviewResponse, type R5dctlConversationWorkDetail, type R5dctlConversationWorkResponse, type R5dctlEnvData, type R5dctlK8sResourceHistory, type R5dctlK8sUsage, type R5dctlK8sWorkload, type R5dctlProcessInspection, type R5dctlProcessListInput, type R5dctlProcessRun, type R5dctlWorkspaceStatus, type R5dctlWorkspaceSyncResult, type ChatMode, type ModelTier } from "@ricsam/r5d-api";
|
|
2
2
|
export type GlobalOptions = {
|
|
3
3
|
baseUrl?: string;
|
|
4
4
|
json: boolean;
|
|
@@ -65,8 +65,50 @@ export declare function parseShellArgs(args: string[]): {
|
|
|
65
65
|
};
|
|
66
66
|
export declare function parsePsListArgs(args: string[]): R5dctlProcessListInput;
|
|
67
67
|
export declare function parsePsHistoryArgs(args: string[]): R5dctlProcessListInput;
|
|
68
|
+
export type K8sUsageOptions = {
|
|
69
|
+
namespace?: string;
|
|
70
|
+
workload?: string;
|
|
71
|
+
container?: string;
|
|
72
|
+
window: "24h" | "7d";
|
|
73
|
+
};
|
|
74
|
+
export declare function parseK8sUsageArgs(args: string[]): K8sUsageOptions;
|
|
68
75
|
export declare function renderWorkspaceStatus(status: R5dctlWorkspaceStatus): string;
|
|
69
76
|
export declare function renderWorkspaceSync(result: R5dctlWorkspaceSyncResult): string;
|
|
77
|
+
export type K8sUsageSummary = {
|
|
78
|
+
cpuMinNanoCores: number | null;
|
|
79
|
+
cpuMaxNanoCores: number | null;
|
|
80
|
+
memoryMinBytes: number | null;
|
|
81
|
+
memoryMaxBytes: number | null;
|
|
82
|
+
sampleCount: number;
|
|
83
|
+
};
|
|
84
|
+
export type K8sUsageReport = {
|
|
85
|
+
window: "24h" | "7d";
|
|
86
|
+
collectedAt: string;
|
|
87
|
+
metricsFreshAt: string | null;
|
|
88
|
+
metricsStale: boolean;
|
|
89
|
+
filters: Omit<K8sUsageOptions, "window">;
|
|
90
|
+
entries: Array<{
|
|
91
|
+
workload: Pick<R5dctlK8sWorkload, "uid" | "kind" | "namespace" | "name" | "replicas" | "readyReplicas">;
|
|
92
|
+
container: {
|
|
93
|
+
name: string;
|
|
94
|
+
currentUsage: R5dctlK8sUsage;
|
|
95
|
+
pods: Array<R5dctlK8sUsage & {
|
|
96
|
+
podUid: string;
|
|
97
|
+
podName: string;
|
|
98
|
+
phase: string | null;
|
|
99
|
+
}>;
|
|
100
|
+
};
|
|
101
|
+
sampledPodCount: number;
|
|
102
|
+
summary: K8sUsageSummary;
|
|
103
|
+
history: R5dctlK8sResourceHistory;
|
|
104
|
+
}>;
|
|
105
|
+
};
|
|
106
|
+
export declare function collectK8sUsage(client: Pick<R5dctlClient, "k8s">, options: K8sUsageOptions): Promise<K8sUsageReport>;
|
|
107
|
+
export declare function formatK8sCpu(value: number | null): string;
|
|
108
|
+
export declare function formatK8sMemory(value: number | null): string;
|
|
109
|
+
export type K8sUsageState = "healthy" | "partial" | "stale" | "inactive" | "warming" | "no data" | "no current";
|
|
110
|
+
export declare function getK8sUsageState(entry: K8sUsageReport["entries"][number]): K8sUsageState;
|
|
111
|
+
export declare function renderK8sUsageReport(report: K8sUsageReport): string;
|
|
70
112
|
export declare function formatProcessAge(startedAt: string, nowMs?: number): string;
|
|
71
113
|
export declare function renderProcessList(processes: R5dctlProcessRun[], nowMs?: number): string;
|
|
72
114
|
export declare function renderProcessHistory(processes: R5dctlProcessRun[], nowMs?: number): string;
|
|
@@ -76,6 +118,11 @@ export type EnvSummaryData = Record<string, {
|
|
|
76
118
|
hasValue: boolean;
|
|
77
119
|
}>;
|
|
78
120
|
export declare function summarizeEnvData(data: R5dctlEnvData): EnvSummaryData;
|
|
121
|
+
export declare function parseGetEnvsArgs(args: string[]): {
|
|
122
|
+
showValues: boolean;
|
|
123
|
+
};
|
|
124
|
+
export declare function renderEnvValues(data: R5dctlEnvData): string;
|
|
125
|
+
export declare function getProjectEnvValue(data: R5dctlEnvData, name: string): string;
|
|
79
126
|
export declare function parseConversationRenderArgs(args: string[]): ConversationRenderOptions;
|
|
80
127
|
export declare function renderConversationResponse(read: R5dctlConversationResponse): string;
|
|
81
128
|
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.51",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "./dist/cjs/cli.cjs",
|
|
6
6
|
"module": "./dist/mjs/cli.mjs",
|
|
@@ -26,7 +26,7 @@
|
|
|
26
26
|
"r5dctl": "dist/cjs/main.cjs"
|
|
27
27
|
},
|
|
28
28
|
"dependencies": {
|
|
29
|
-
"@ricsam/r5d-api": "^0.0.
|
|
29
|
+
"@ricsam/r5d-api": "^0.0.51",
|
|
30
30
|
"dotenv": "^17",
|
|
31
31
|
"ws": "^8.18.3"
|
|
32
32
|
},
|