@ricsam/r5dctl 0.0.126 → 0.0.127
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 +1 -17
- package/dist/cjs/cli.cjs +3 -306
- package/dist/cjs/package.json +1 -1
- package/dist/mjs/cli.mjs +3 -300
- package/dist/mjs/package.json +1 -1
- package/dist/types/cli.d.ts +1 -43
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -111,22 +111,8 @@ r5dctl -p <project> -b <branch> ps list --worker <label>
|
|
|
111
111
|
r5dctl -s <session-id> ps list --json
|
|
112
112
|
r5dctl ps stop <run-id>
|
|
113
113
|
|
|
114
|
-
r5dctl k8s usage
|
|
115
|
-
r5dctl k8s usage --namespace default --workload api --container server
|
|
116
|
-
r5dctl k8s usage --window 7d
|
|
117
|
-
r5dctl k8s usage --workload default/api --window 7d --json
|
|
118
|
-
r5dctl vcluster trusted-workflow add --repo owner/repository --workflow .github/workflows/deploy.yml
|
|
119
|
-
r5dctl vcluster trusted-workflow list
|
|
120
|
-
r5dctl vcluster trusted-workflow remove <workflow-id>
|
|
121
114
|
```
|
|
122
115
|
|
|
123
|
-
Trusted workflows authenticate Kubernetes through the dedicated
|
|
124
|
-
`ricsam/r5d-cluster-auth` GitHub Action. They do not run or authenticate
|
|
125
|
-
`r5dctl` inside GitHub Actions, and no R5D secret needs to be stored in the
|
|
126
|
-
repository. Trust applies to that workflow path on every Git ref and grants
|
|
127
|
-
cluster-admin, so protect workflow changes and do not invoke it for untrusted
|
|
128
|
-
pull requests.
|
|
129
|
-
|
|
130
116
|
`create branch` creates a linked worktree branch through the platform from an existing branch's checkout, with the same `--source worktree:<branch>` and `--working-tree carry|clean` semantics as `session start --new-worktree` (carry is the default) and no session prompt. `--worker <label>` selects the connected worker that cuts the checkout; omitted, the platform chooses one, and an r5d worker shell's temporary credential is always bound to its own worker. The command returns as soon as the operation is accepted and prints its id; `describe branch-operation <operation-id>` reports `provisioning`, `ready`, or `failed` with the worker and any error. Neither prints a commit hash: read the branch with git in its checkout once it is ready.
|
|
131
117
|
|
|
132
118
|
`delete branch` removes one or more branches and every session on them through the platform, which is the only supported way to remove a linked worktree: the platform owns those checkouts, so never run `git worktree remove`, `git worktree prune`, or `git branch -D` inside a project checkout. Without `--confirm` it prints each branch and its sessions and exits non-zero without deleting anything. With `--confirm` each branch is resolved to the exact managed branch it names today, its deletion is accepted at once by the platform, and the command waits (polling every 2 seconds, up to 10 minutes) until the platform reports it completed before requesting the next one; a failure does not stop the remaining deletions, and the exit status is non-zero if any branch failed. `--no-wait` prints the accepted branch id instead of waiting; `describe branch-deletion <branch-id>` reports `pending` (with the retry reason when the worker has not answered yet), `failed` with the platform's error, or `completed`, and answers `completed` for a branch id that no longer exists. The deletion stays bound to that branch id: a same-name branch created afterwards is never deleted by an old request, and re-running the command for a branch whose deletion failed retries it. The primary branch `main` is refused before anything is deleted, as is a branch with a running agent or active process (stop them first); a name that only has sessions and no managed branch is refused too (delete those sessions with `delete session`). On a server that predates the route, the command explains that the server does not support `delete branch` yet instead of reporting a missing branch.
|
|
@@ -157,9 +143,7 @@ Add `-f`/`--follow`/`--watch` to `session start` to poll the provisioning handle
|
|
|
157
143
|
|
|
158
144
|
`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. The `LANE` column names the worker command lane each run holds (`general`, `utility`, or `control`) or waits for (`queued:general`), from the worker's latest capacity report; `-` means the worker did not report the run. `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.
|
|
159
145
|
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
`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.
|
|
146
|
+
The worker host determines execution capacity. If its configured command launcher provides a reserved control lane, an agent shell can use it for one plain `r5dctl` coordination command (`ps list`, `ps stop`, `workspace status`, `session status|stop|prompt|claim|claims|process-log`, `describe …`, `get …`, `sessions recent`, the `conversation` read forms, `create|update|delete session`, `answer-questions`, `answer-env-request`) with plain or simply quoted arguments. The command runs without a shell, using the launcher's control capacity and runtime limit. Shell operators, pipes, redirections, `$` expansions, environment assignments, wrappers, paths to r5dctl, `--`, `--follow`, and credential or endpoint flags make it an ordinary shell command again. `workspace status` prints each worker's lane totals, holders, and queue.
|
|
163
147
|
|
|
164
148
|
`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:
|
|
165
149
|
|
package/dist/cjs/cli.cjs
CHANGED
|
@@ -34,18 +34,14 @@ __export(cli_exports, {
|
|
|
34
34
|
branchDeletionConfirmationMessage: () => branchDeletionConfirmationMessage,
|
|
35
35
|
branchDeletionsConfirmationMessage: () => branchDeletionsConfirmationMessage,
|
|
36
36
|
branchDeletionsFailureMessage: () => branchDeletionsFailureMessage,
|
|
37
|
-
collectK8sUsage: () => collectK8sUsage,
|
|
38
37
|
createBranchThroughPlatform: () => createBranchThroughPlatform,
|
|
39
38
|
deleteBranchThroughPlatform: () => deleteBranchThroughPlatform,
|
|
40
39
|
deleteBranchesThroughPlatform: () => deleteBranchesThroughPlatform,
|
|
41
40
|
dispatchSessionRunCommand: () => dispatchSessionRunCommand,
|
|
42
41
|
followR5dctlSession: () => followR5dctlSession,
|
|
43
|
-
formatK8sCpu: () => formatK8sCpu,
|
|
44
|
-
formatK8sMemory: () => formatK8sMemory,
|
|
45
42
|
formatProcessAge: () => formatProcessAge,
|
|
46
43
|
formatProcessLane: () => formatProcessLane,
|
|
47
44
|
getCliHelpText: () => getCliHelpText,
|
|
48
|
-
getK8sUsageState: () => getK8sUsageState,
|
|
49
45
|
getProjectEnvValue: () => getProjectEnvValue,
|
|
50
46
|
getR5dctlVersion: () => getR5dctlVersion,
|
|
51
47
|
getTransientDevicePollDelay: () => getTransientDevicePollDelay,
|
|
@@ -66,7 +62,6 @@ __export(cli_exports, {
|
|
|
66
62
|
parseEnvRequestResponseArgs: () => parseEnvRequestResponseArgs,
|
|
67
63
|
parseGetEnvsArgs: () => parseGetEnvsArgs,
|
|
68
64
|
parseGlobalArgs: () => parseGlobalArgs,
|
|
69
|
-
parseK8sUsageArgs: () => parseK8sUsageArgs,
|
|
70
65
|
parsePromptArgs: () => parsePromptArgs,
|
|
71
66
|
parsePsHistoryArgs: () => parsePsHistoryArgs,
|
|
72
67
|
parsePsListArgs: () => parsePsListArgs,
|
|
@@ -91,7 +86,6 @@ __export(cli_exports, {
|
|
|
91
86
|
renderConversationWorkResponse: () => renderConversationWorkResponse,
|
|
92
87
|
renderDeviceAuthorizationPrompt: () => renderDeviceAuthorizationPrompt,
|
|
93
88
|
renderEnvValues: () => renderEnvValues,
|
|
94
|
-
renderK8sUsageReport: () => renderK8sUsageReport,
|
|
95
89
|
renderProcessHistory: () => renderProcessHistory,
|
|
96
90
|
renderProcessInspection: () => renderProcessInspection,
|
|
97
91
|
renderProcessList: () => renderProcessList,
|
|
@@ -169,20 +163,10 @@ const PS_HELP_TEXT = [
|
|
|
169
163
|
" r5dctl ps inspect <run-id>",
|
|
170
164
|
" r5dctl ps stop <run-id>",
|
|
171
165
|
"",
|
|
172
|
-
"List, inspect, or gracefully stop
|
|
166
|
+
"List, inspect, or gracefully stop agent processes on workers.",
|
|
173
167
|
"Use -p/--project, -b/--branch, or -s/--session to filter list and history.",
|
|
174
168
|
""
|
|
175
169
|
].join("\n");
|
|
176
|
-
const K8S_HELP_TEXT = [
|
|
177
|
-
"Usage:",
|
|
178
|
-
" r5dctl k8s usage [--namespace <name>] [--workload <name|namespace/name|uid>] [--container <name>] [--window <24h|7d>]",
|
|
179
|
-
" r5dctl vcluster trusted-workflow add --repo <owner/repository> --workflow <.github/workflows/file.yml>",
|
|
180
|
-
" r5dctl vcluster trusted-workflow list",
|
|
181
|
-
" r5dctl vcluster trusted-workflow remove <workflow-id>",
|
|
182
|
-
"",
|
|
183
|
-
"Show current and rolling CPU and memory usage for managed-vCluster workload containers.",
|
|
184
|
-
""
|
|
185
|
-
].join("\n");
|
|
186
170
|
const SESSION_RUN_HELP_TEXT = [
|
|
187
171
|
"Usage:",
|
|
188
172
|
' r5dctl -p <project> session start --worker <label> --worktree <branch> --model <tier> [-f|--follow|--watch] [--tools] [--parent-session <session-id>] "<prompt>"',
|
|
@@ -374,26 +358,10 @@ const SHARED_HELP_ENTRIES = [
|
|
|
374
358
|
usage: "-s <session-id> answer-env-request --worker <label> --mode <mode> --model <tier> [-e KEY=value] [--context <text>]",
|
|
375
359
|
description: "Answer a pending environment variable request with values, context, or both."
|
|
376
360
|
},
|
|
377
|
-
{
|
|
378
|
-
section: "kubernetes",
|
|
379
|
-
usage: "k8s usage [--namespace <name>] [--workload <name|namespace/name|uid>] [--container <name>] [--window <24h|7d>]",
|
|
380
|
-
description: "Show current and rolling CPU and memory usage for managed-vCluster workload containers."
|
|
381
|
-
},
|
|
382
|
-
{
|
|
383
|
-
section: "kubernetes",
|
|
384
|
-
usage: "vcluster trusted-workflow add --repo <owner/repository> --workflow <.github/workflows/file.yml>",
|
|
385
|
-
description: "Trust an exact GitHub Actions workflow path to authenticate kubectl against your managed vCluster."
|
|
386
|
-
},
|
|
387
|
-
{ section: "kubernetes", usage: "vcluster trusted-workflow list", description: "List trusted GitHub Actions workflows." },
|
|
388
|
-
{
|
|
389
|
-
section: "kubernetes",
|
|
390
|
-
usage: "vcluster trusted-workflow remove <workflow-id>",
|
|
391
|
-
description: "Revoke a trusted GitHub Actions workflow and its outstanding credentials."
|
|
392
|
-
},
|
|
393
361
|
{
|
|
394
362
|
section: "processes",
|
|
395
363
|
usage: "ps list [--worker <label>]",
|
|
396
|
-
description: "List active
|
|
364
|
+
description: "List active agent processes on workers; global project, branch, and session filters apply."
|
|
397
365
|
},
|
|
398
366
|
{
|
|
399
367
|
section: "processes",
|
|
@@ -414,7 +382,6 @@ const HELP_SECTION_ORDER = [
|
|
|
414
382
|
"branches",
|
|
415
383
|
"envs",
|
|
416
384
|
"sessions",
|
|
417
|
-
"kubernetes",
|
|
418
385
|
"processes",
|
|
419
386
|
"shell"
|
|
420
387
|
];
|
|
@@ -425,7 +392,6 @@ const HELP_SECTION_TITLES = {
|
|
|
425
392
|
branches: "Branches",
|
|
426
393
|
envs: "Environment variables",
|
|
427
394
|
sessions: "Sessions",
|
|
428
|
-
kubernetes: "Kubernetes",
|
|
429
395
|
processes: "Processes",
|
|
430
396
|
shell: "Shell"
|
|
431
397
|
};
|
|
@@ -1664,36 +1630,6 @@ function parseRecentSessionArgs(args) {
|
|
|
1664
1630
|
if (!Number.isInteger(limit) || limit < 1 || limit > 200) throw new Error("--limit must be an integer between 1 and 200");
|
|
1665
1631
|
return { limit };
|
|
1666
1632
|
}
|
|
1667
|
-
function parseK8sUsageArgs(args) {
|
|
1668
|
-
const parsed = { window: "24h" };
|
|
1669
|
-
const seen = /* @__PURE__ */ new Set();
|
|
1670
|
-
for (let index = 0; index < args.length; index += 1) {
|
|
1671
|
-
const arg = args[index];
|
|
1672
|
-
if (!arg) continue;
|
|
1673
|
-
const option = ["--namespace", "--workload", "--container", "--window"].find(
|
|
1674
|
-
(candidate) => arg === candidate || arg.startsWith(`${candidate}=`)
|
|
1675
|
-
);
|
|
1676
|
-
if (!option) throw new Error(`Unknown k8s usage argument: ${arg}`);
|
|
1677
|
-
if (seen.has(option)) throw new Error(`Use ${option} only once`);
|
|
1678
|
-
seen.add(option);
|
|
1679
|
-
const inlineValue = parseLongOptionWithEquals(arg, option);
|
|
1680
|
-
const candidateValue = inlineValue ?? args[index + 1];
|
|
1681
|
-
if (!candidateValue || candidateValue.startsWith("--")) throw new Error(`Missing value for ${option}`);
|
|
1682
|
-
const value = candidateValue;
|
|
1683
|
-
if (inlineValue === void 0) index += 1;
|
|
1684
|
-
if (option === "--window") {
|
|
1685
|
-
if (value !== "24h" && value !== "7d") throw new Error("--window must be 24h or 7d");
|
|
1686
|
-
parsed.window = value;
|
|
1687
|
-
} else if (option === "--namespace") {
|
|
1688
|
-
parsed.namespace = value;
|
|
1689
|
-
} else if (option === "--workload") {
|
|
1690
|
-
parsed.workload = value;
|
|
1691
|
-
} else {
|
|
1692
|
-
parsed.container = value;
|
|
1693
|
-
}
|
|
1694
|
-
}
|
|
1695
|
-
return parsed;
|
|
1696
|
-
}
|
|
1697
1633
|
async function openInBrowser(url) {
|
|
1698
1634
|
if (process.platform === "darwin") {
|
|
1699
1635
|
(0, import_node_child_process.spawn)("open", [url], { stdio: "ignore", detached: true }).unref();
|
|
@@ -1956,171 +1892,6 @@ Error: ${result.error}` : "";
|
|
|
1956
1892
|
return `Workspace sync ${result.outcome} on ${result.workerLabel}; HEAD ${head}; ${result.diffSizeBytes} bytes.${discarded}${error}
|
|
1957
1893
|
`;
|
|
1958
1894
|
}
|
|
1959
|
-
function summarizeK8sHistory(history) {
|
|
1960
|
-
if (history.envelope.length === 0) {
|
|
1961
|
-
return {
|
|
1962
|
-
cpuMinNanoCores: null,
|
|
1963
|
-
cpuMaxNanoCores: null,
|
|
1964
|
-
memoryMinBytes: null,
|
|
1965
|
-
memoryMaxBytes: null,
|
|
1966
|
-
sampleCount: 0
|
|
1967
|
-
};
|
|
1968
|
-
}
|
|
1969
|
-
return {
|
|
1970
|
-
cpuMinNanoCores: Math.min(...history.envelope.map((point) => point.cpuMinNanoCores)),
|
|
1971
|
-
cpuMaxNanoCores: Math.max(...history.envelope.map((point) => point.cpuMaxNanoCores)),
|
|
1972
|
-
memoryMinBytes: Math.min(...history.envelope.map((point) => point.memoryMinBytes)),
|
|
1973
|
-
memoryMaxBytes: Math.max(...history.envelope.map((point) => point.memoryMaxBytes)),
|
|
1974
|
-
sampleCount: history.envelope.reduce((total, point) => total + point.sampleCount, 0)
|
|
1975
|
-
};
|
|
1976
|
-
}
|
|
1977
|
-
async function mapWithConcurrency(items, concurrency, fn) {
|
|
1978
|
-
const results = new Array(items.length);
|
|
1979
|
-
let nextIndex = 0;
|
|
1980
|
-
await Promise.all(
|
|
1981
|
-
Array.from({ length: Math.min(concurrency, items.length) }, async () => {
|
|
1982
|
-
while (nextIndex < items.length) {
|
|
1983
|
-
const index = nextIndex++;
|
|
1984
|
-
const item = items[index];
|
|
1985
|
-
if (item !== void 0) results[index] = await fn(item);
|
|
1986
|
-
}
|
|
1987
|
-
})
|
|
1988
|
-
);
|
|
1989
|
-
return results;
|
|
1990
|
-
}
|
|
1991
|
-
async function collectK8sUsage(client, options) {
|
|
1992
|
-
const resources = await client.k8s.resources();
|
|
1993
|
-
const candidates = resources.workloads.flatMap((workload) => workload.containers.map((container) => ({ workload, container }))).filter(({ workload, container }) => {
|
|
1994
|
-
const workloadMatches = !options.workload || options.workload === workload.name || options.workload === `${workload.namespace}/${workload.name}` || options.workload === workload.uid;
|
|
1995
|
-
return (!options.namespace || options.namespace === workload.namespace) && workloadMatches && (!options.container || options.container === container.name);
|
|
1996
|
-
}).sort(
|
|
1997
|
-
(left, right) => left.workload.namespace.localeCompare(right.workload.namespace) || left.workload.name.localeCompare(right.workload.name) || left.container.name.localeCompare(right.container.name)
|
|
1998
|
-
);
|
|
1999
|
-
if (candidates.length === 0 && (options.namespace || options.workload || options.container)) {
|
|
2000
|
-
throw new Error("No managed-vCluster workload containers matched the requested filters");
|
|
2001
|
-
}
|
|
2002
|
-
const entries = await mapWithConcurrency(candidates, 8, async ({ workload, container }) => {
|
|
2003
|
-
const history = await client.k8s.resourceHistory({ workloadUid: workload.uid, containerName: container.name, window: options.window });
|
|
2004
|
-
return {
|
|
2005
|
-
workload: {
|
|
2006
|
-
uid: workload.uid,
|
|
2007
|
-
kind: workload.kind,
|
|
2008
|
-
namespace: workload.namespace,
|
|
2009
|
-
name: workload.name,
|
|
2010
|
-
replicas: workload.replicas,
|
|
2011
|
-
readyReplicas: workload.readyReplicas
|
|
2012
|
-
},
|
|
2013
|
-
container: {
|
|
2014
|
-
name: container.name,
|
|
2015
|
-
currentUsage: container.currentUsage,
|
|
2016
|
-
pods: container.pods
|
|
2017
|
-
},
|
|
2018
|
-
sampledPodCount: container.pods.filter((pod) => pod.cpuNanoCores !== null || pod.memoryBytes !== null).length,
|
|
2019
|
-
summary: summarizeK8sHistory(history),
|
|
2020
|
-
history
|
|
2021
|
-
};
|
|
2022
|
-
});
|
|
2023
|
-
return {
|
|
2024
|
-
window: options.window,
|
|
2025
|
-
collectedAt: resources.collectedAt,
|
|
2026
|
-
metricsFreshAt: resources.metricsFreshAt,
|
|
2027
|
-
metricsStale: resources.metricsStale,
|
|
2028
|
-
filters: {
|
|
2029
|
-
namespace: options.namespace,
|
|
2030
|
-
workload: options.workload,
|
|
2031
|
-
container: options.container
|
|
2032
|
-
},
|
|
2033
|
-
entries
|
|
2034
|
-
};
|
|
2035
|
-
}
|
|
2036
|
-
function formatDecimal(value) {
|
|
2037
|
-
if (value >= 100) return value.toFixed(0);
|
|
2038
|
-
if (value >= 10) return value.toFixed(1).replace(/\.0$/, "");
|
|
2039
|
-
return value.toFixed(2).replace(/\.0+$/, "").replace(/(\.\d)0$/, "$1");
|
|
2040
|
-
}
|
|
2041
|
-
function formatK8sCpu(value) {
|
|
2042
|
-
if (value === null) return "-";
|
|
2043
|
-
const milliCores = value / 1e6;
|
|
2044
|
-
return `${formatDecimal(milliCores)}m`;
|
|
2045
|
-
}
|
|
2046
|
-
function formatK8sMemory(value) {
|
|
2047
|
-
if (value === null) return "-";
|
|
2048
|
-
const units = ["B", "Ki", "Mi", "Gi", "Ti"];
|
|
2049
|
-
let amount = value;
|
|
2050
|
-
let unitIndex = 0;
|
|
2051
|
-
while (amount >= 1024 && unitIndex < units.length - 1) {
|
|
2052
|
-
amount /= 1024;
|
|
2053
|
-
unitIndex += 1;
|
|
2054
|
-
}
|
|
2055
|
-
return `${formatDecimal(amount)}${units[unitIndex]}`;
|
|
2056
|
-
}
|
|
2057
|
-
function getK8sUsageState(entry) {
|
|
2058
|
-
const hasHistory = entry.history.envelope.length > 0;
|
|
2059
|
-
const terminalPods = entry.container.pods.filter((pod) => pod.phase === "Succeeded" || pod.phase === "Failed").length;
|
|
2060
|
-
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);
|
|
2061
|
-
if (inactive) return "inactive";
|
|
2062
|
-
if (entry.sampledPodCount === 0) return hasHistory ? entry.history.stale ? "stale" : "no current" : "no data";
|
|
2063
|
-
if (!hasHistory) return "warming";
|
|
2064
|
-
if (entry.history.stale) return "stale";
|
|
2065
|
-
if (entry.history.incomplete) return "partial";
|
|
2066
|
-
return "healthy";
|
|
2067
|
-
}
|
|
2068
|
-
function abbreviatedK8sKind(kind) {
|
|
2069
|
-
switch (kind) {
|
|
2070
|
-
case "Deployment":
|
|
2071
|
-
return "deploy";
|
|
2072
|
-
case "StatefulSet":
|
|
2073
|
-
return "sts";
|
|
2074
|
-
case "DaemonSet":
|
|
2075
|
-
return "ds";
|
|
2076
|
-
case "CronJob":
|
|
2077
|
-
return "cron";
|
|
2078
|
-
case "ReplicaSet":
|
|
2079
|
-
return "rs";
|
|
2080
|
-
case "Job":
|
|
2081
|
-
return "job";
|
|
2082
|
-
case "Pod":
|
|
2083
|
-
return "pod";
|
|
2084
|
-
}
|
|
2085
|
-
}
|
|
2086
|
-
function renderK8sUsageReport(report) {
|
|
2087
|
-
const freshness = report.metricsFreshAt ?? "unavailable";
|
|
2088
|
-
const lines = [
|
|
2089
|
-
`Window: ${report.window} Collector: ${report.metricsStale ? "stale" : "current"} Fresh: ${freshness}`,
|
|
2090
|
-
"PODS counts pods with current metrics."
|
|
2091
|
-
];
|
|
2092
|
-
if (report.entries.length === 0) return `${lines.join("\n")}
|
|
2093
|
-
No managed-vCluster workload containers found.
|
|
2094
|
-
`;
|
|
2095
|
-
const headers = ["WORKLOAD", "CONTAINER", "PODS", "CPU NOW", "CPU MAX", "MEM NOW", "MEM MAX", "STATE"];
|
|
2096
|
-
const grouped = /* @__PURE__ */ new Map();
|
|
2097
|
-
for (const entry of report.entries) {
|
|
2098
|
-
const entries = grouped.get(entry.workload.namespace) ?? [];
|
|
2099
|
-
entries.push(entry);
|
|
2100
|
-
grouped.set(entry.workload.namespace, entries);
|
|
2101
|
-
}
|
|
2102
|
-
for (const [namespace, entries] of grouped) {
|
|
2103
|
-
const rows = entries.map((entry) => {
|
|
2104
|
-
const state = getK8sUsageState(entry);
|
|
2105
|
-
return [
|
|
2106
|
-
`${abbreviatedK8sKind(entry.workload.kind)}/${entry.workload.name}`,
|
|
2107
|
-
entry.container.name,
|
|
2108
|
-
String(entry.sampledPodCount),
|
|
2109
|
-
formatK8sCpu(entry.container.currentUsage.cpuNanoCores),
|
|
2110
|
-
formatK8sCpu(entry.summary.cpuMaxNanoCores),
|
|
2111
|
-
formatK8sMemory(entry.container.currentUsage.memoryBytes),
|
|
2112
|
-
formatK8sMemory(entry.summary.memoryMaxBytes),
|
|
2113
|
-
state === "healthy" ? "-" : state
|
|
2114
|
-
];
|
|
2115
|
-
});
|
|
2116
|
-
const widths = headers.map((header, column) => Math.max(header.length, ...rows.map((row) => row[column]?.length ?? 0)));
|
|
2117
|
-
const formatRow = (row) => row.map((value, column) => column === row.length - 1 ? value : value.padEnd(widths[column] ?? value.length)).join(" ");
|
|
2118
|
-
const separator = widths.map((width) => "-".repeat(width)).join(" ");
|
|
2119
|
-
lines.push("", `Namespace: ${namespace}`, formatRow(headers), separator, ...rows.map(formatRow));
|
|
2120
|
-
}
|
|
2121
|
-
return `${lines.join("\n")}
|
|
2122
|
-
`;
|
|
2123
|
-
}
|
|
2124
1895
|
function renderSessionDescription(session) {
|
|
2125
1896
|
return [`Session: ${session.id}`, `Project: ${session.projectPath}`, `Branch: ${session.branchName}`].join("\n") + "\n";
|
|
2126
1897
|
}
|
|
@@ -3047,45 +2818,6 @@ async function executeR5dctlCommand(client, json, args) {
|
|
|
3047
2818
|
write(result, renderWorkspaceSync(result));
|
|
3048
2819
|
return;
|
|
3049
2820
|
}
|
|
3050
|
-
if (first === "k8s" && second === "usage") {
|
|
3051
|
-
const report = await collectK8sUsage(client, parseK8sUsageArgs(args.slice(2)));
|
|
3052
|
-
write(report, renderK8sUsageReport(report));
|
|
3053
|
-
return;
|
|
3054
|
-
}
|
|
3055
|
-
if (first === "vcluster" && second === "trusted-workflow") {
|
|
3056
|
-
const subcommand = requireValue(args[2], "Missing trusted-workflow command");
|
|
3057
|
-
if (subcommand === "add") {
|
|
3058
|
-
const parsed = parseCommandOperands(args.slice(3), /* @__PURE__ */ new Set(["--repo", "--workflow"]), /* @__PURE__ */ new Map(), "trusted-workflow add");
|
|
3059
|
-
if (parsed.positionals.length > 0) throw new Error(`Unexpected trusted-workflow add value: ${parsed.positionals[0]}`);
|
|
3060
|
-
const workflow = await client.k8s.trustedWorkflows.add({
|
|
3061
|
-
repository: requireValue(parsed.values.get("--repo"), "--repo <owner/repository> is required"),
|
|
3062
|
-
workflowPath: requireValue(parsed.values.get("--workflow"), "--workflow <path> is required")
|
|
3063
|
-
});
|
|
3064
|
-
write(workflow, "Trusted GitHub workflow configured successfully.\n");
|
|
3065
|
-
return;
|
|
3066
|
-
}
|
|
3067
|
-
if (subcommand === "list") {
|
|
3068
|
-
if (args.length > 3) throw new Error(`Unexpected trusted-workflow list value: ${args[3]}`);
|
|
3069
|
-
const workflows = await client.k8s.trustedWorkflows.list();
|
|
3070
|
-
write(
|
|
3071
|
-
workflows,
|
|
3072
|
-
workflows.length === 0 ? "No trusted GitHub workflows.\n" : `${workflows.map(
|
|
3073
|
-
(workflow) => `${workflow.id} ${workflow.repository} ${workflow.workflowPath} ${workflow.revokedAt ? "revoked" : "active"}`
|
|
3074
|
-
).join("\n")}
|
|
3075
|
-
`
|
|
3076
|
-
);
|
|
3077
|
-
return;
|
|
3078
|
-
}
|
|
3079
|
-
if (subcommand === "remove") {
|
|
3080
|
-
const workflowId = requireValue(args[3], "Missing trusted workflow id");
|
|
3081
|
-
if (args.length > 4) throw new Error(`Unexpected trusted-workflow remove value: ${args[4]}`);
|
|
3082
|
-
const result = await client.k8s.trustedWorkflows.revoke(workflowId);
|
|
3083
|
-
if (!result.success) throw new Error("Trusted GitHub workflow was not found or was already revoked");
|
|
3084
|
-
write(result, "Trusted GitHub workflow revoked.\n");
|
|
3085
|
-
return;
|
|
3086
|
-
}
|
|
3087
|
-
throw new Error(`Unknown trusted-workflow command: ${subcommand}`);
|
|
3088
|
-
}
|
|
3089
2821
|
if (first === "ps" && second === "list") {
|
|
3090
2822
|
const processes = await client.processes.list(parsePsListArgs(args.slice(2)));
|
|
3091
2823
|
write(processes, renderProcessList(processes));
|
|
@@ -3421,12 +3153,6 @@ function resolveCommandExecution(options, rest) {
|
|
|
3421
3153
|
text: PS_HELP_TEXT
|
|
3422
3154
|
};
|
|
3423
3155
|
}
|
|
3424
|
-
if (command === "k8s" && commandArgs.length === 0) {
|
|
3425
|
-
return {
|
|
3426
|
-
kind: "cli-help",
|
|
3427
|
-
text: K8S_HELP_TEXT
|
|
3428
|
-
};
|
|
3429
|
-
}
|
|
3430
3156
|
if (command === "session" && commandArgs.length === 0) {
|
|
3431
3157
|
return {
|
|
3432
3158
|
kind: "cli-help",
|
|
@@ -3448,10 +3174,7 @@ function resolveCommandExecution(options, rest) {
|
|
|
3448
3174
|
text: sharedHelp
|
|
3449
3175
|
};
|
|
3450
3176
|
}
|
|
3451
|
-
|
|
3452
|
-
kind: "plugin",
|
|
3453
|
-
pluginArgs: [...normalizedRest, "--help"]
|
|
3454
|
-
};
|
|
3177
|
+
throw new Error(`Unknown command: ${normalizedRest.join(" ")}`);
|
|
3455
3178
|
}
|
|
3456
3179
|
if (command === "projects" || command === "sessions" || command === "workspace") {
|
|
3457
3180
|
return {
|
|
@@ -3561,26 +3284,6 @@ function resolveCommandExecution(options, rest) {
|
|
|
3561
3284
|
}
|
|
3562
3285
|
throw new Error(`Unknown ps command: ${subcommand ?? ""}`.trim());
|
|
3563
3286
|
}
|
|
3564
|
-
if (command === "k8s") {
|
|
3565
|
-
const subcommand = commandArgs[0];
|
|
3566
|
-
if (options.project || options.branch || options.session) {
|
|
3567
|
-
throw new Error("Project, branch, and session scopes are not supported for `k8s`");
|
|
3568
|
-
}
|
|
3569
|
-
if (subcommand === "usage") {
|
|
3570
|
-
parseK8sUsageArgs(commandArgs.slice(1));
|
|
3571
|
-
return { kind: "plugin", pluginArgs: ["k8s", "usage", ...commandArgs.slice(1)] };
|
|
3572
|
-
}
|
|
3573
|
-
throw new Error(`Unknown k8s command: ${subcommand ?? ""}`.trim());
|
|
3574
|
-
}
|
|
3575
|
-
if (command === "vcluster") {
|
|
3576
|
-
if (options.project || options.branch || options.session) {
|
|
3577
|
-
throw new Error("Project, branch, and session scopes are not supported for `vcluster`");
|
|
3578
|
-
}
|
|
3579
|
-
if (commandArgs[0] !== "trusted-workflow") {
|
|
3580
|
-
throw new Error(`Unknown vcluster command: ${commandArgs[0] ?? ""}`.trim());
|
|
3581
|
-
}
|
|
3582
|
-
return { kind: "plugin", pluginArgs: ["vcluster", "trusted-workflow", ...commandArgs.slice(1)] };
|
|
3583
|
-
}
|
|
3584
3287
|
if (command === "get") {
|
|
3585
3288
|
const target = commandArgs[0];
|
|
3586
3289
|
if (target === "projects") {
|
|
@@ -4063,18 +3766,14 @@ async function main(argv = process.argv.slice(2)) {
|
|
|
4063
3766
|
branchDeletionConfirmationMessage,
|
|
4064
3767
|
branchDeletionsConfirmationMessage,
|
|
4065
3768
|
branchDeletionsFailureMessage,
|
|
4066
|
-
collectK8sUsage,
|
|
4067
3769
|
createBranchThroughPlatform,
|
|
4068
3770
|
deleteBranchThroughPlatform,
|
|
4069
3771
|
deleteBranchesThroughPlatform,
|
|
4070
3772
|
dispatchSessionRunCommand,
|
|
4071
3773
|
followR5dctlSession,
|
|
4072
|
-
formatK8sCpu,
|
|
4073
|
-
formatK8sMemory,
|
|
4074
3774
|
formatProcessAge,
|
|
4075
3775
|
formatProcessLane,
|
|
4076
3776
|
getCliHelpText,
|
|
4077
|
-
getK8sUsageState,
|
|
4078
3777
|
getProjectEnvValue,
|
|
4079
3778
|
getR5dctlVersion,
|
|
4080
3779
|
getTransientDevicePollDelay,
|
|
@@ -4095,7 +3794,6 @@ async function main(argv = process.argv.slice(2)) {
|
|
|
4095
3794
|
parseEnvRequestResponseArgs,
|
|
4096
3795
|
parseGetEnvsArgs,
|
|
4097
3796
|
parseGlobalArgs,
|
|
4098
|
-
parseK8sUsageArgs,
|
|
4099
3797
|
parsePromptArgs,
|
|
4100
3798
|
parsePsHistoryArgs,
|
|
4101
3799
|
parsePsListArgs,
|
|
@@ -4120,7 +3818,6 @@ async function main(argv = process.argv.slice(2)) {
|
|
|
4120
3818
|
renderConversationWorkResponse,
|
|
4121
3819
|
renderDeviceAuthorizationPrompt,
|
|
4122
3820
|
renderEnvValues,
|
|
4123
|
-
renderK8sUsageReport,
|
|
4124
3821
|
renderProcessHistory,
|
|
4125
3822
|
renderProcessInspection,
|
|
4126
3823
|
renderProcessList,
|
package/dist/cjs/package.json
CHANGED
package/dist/mjs/cli.mjs
CHANGED
|
@@ -58,20 +58,10 @@ const PS_HELP_TEXT = [
|
|
|
58
58
|
" r5dctl ps inspect <run-id>",
|
|
59
59
|
" r5dctl ps stop <run-id>",
|
|
60
60
|
"",
|
|
61
|
-
"List, inspect, or gracefully stop
|
|
61
|
+
"List, inspect, or gracefully stop agent processes on workers.",
|
|
62
62
|
"Use -p/--project, -b/--branch, or -s/--session to filter list and history.",
|
|
63
63
|
""
|
|
64
64
|
].join("\n");
|
|
65
|
-
const K8S_HELP_TEXT = [
|
|
66
|
-
"Usage:",
|
|
67
|
-
" r5dctl k8s usage [--namespace <name>] [--workload <name|namespace/name|uid>] [--container <name>] [--window <24h|7d>]",
|
|
68
|
-
" r5dctl vcluster trusted-workflow add --repo <owner/repository> --workflow <.github/workflows/file.yml>",
|
|
69
|
-
" r5dctl vcluster trusted-workflow list",
|
|
70
|
-
" r5dctl vcluster trusted-workflow remove <workflow-id>",
|
|
71
|
-
"",
|
|
72
|
-
"Show current and rolling CPU and memory usage for managed-vCluster workload containers.",
|
|
73
|
-
""
|
|
74
|
-
].join("\n");
|
|
75
65
|
const SESSION_RUN_HELP_TEXT = [
|
|
76
66
|
"Usage:",
|
|
77
67
|
' r5dctl -p <project> session start --worker <label> --worktree <branch> --model <tier> [-f|--follow|--watch] [--tools] [--parent-session <session-id>] "<prompt>"',
|
|
@@ -263,26 +253,10 @@ const SHARED_HELP_ENTRIES = [
|
|
|
263
253
|
usage: "-s <session-id> answer-env-request --worker <label> --mode <mode> --model <tier> [-e KEY=value] [--context <text>]",
|
|
264
254
|
description: "Answer a pending environment variable request with values, context, or both."
|
|
265
255
|
},
|
|
266
|
-
{
|
|
267
|
-
section: "kubernetes",
|
|
268
|
-
usage: "k8s usage [--namespace <name>] [--workload <name|namespace/name|uid>] [--container <name>] [--window <24h|7d>]",
|
|
269
|
-
description: "Show current and rolling CPU and memory usage for managed-vCluster workload containers."
|
|
270
|
-
},
|
|
271
|
-
{
|
|
272
|
-
section: "kubernetes",
|
|
273
|
-
usage: "vcluster trusted-workflow add --repo <owner/repository> --workflow <.github/workflows/file.yml>",
|
|
274
|
-
description: "Trust an exact GitHub Actions workflow path to authenticate kubectl against your managed vCluster."
|
|
275
|
-
},
|
|
276
|
-
{ section: "kubernetes", usage: "vcluster trusted-workflow list", description: "List trusted GitHub Actions workflows." },
|
|
277
|
-
{
|
|
278
|
-
section: "kubernetes",
|
|
279
|
-
usage: "vcluster trusted-workflow remove <workflow-id>",
|
|
280
|
-
description: "Revoke a trusted GitHub Actions workflow and its outstanding credentials."
|
|
281
|
-
},
|
|
282
256
|
{
|
|
283
257
|
section: "processes",
|
|
284
258
|
usage: "ps list [--worker <label>]",
|
|
285
|
-
description: "List active
|
|
259
|
+
description: "List active agent processes on workers; global project, branch, and session filters apply."
|
|
286
260
|
},
|
|
287
261
|
{
|
|
288
262
|
section: "processes",
|
|
@@ -303,7 +277,6 @@ const HELP_SECTION_ORDER = [
|
|
|
303
277
|
"branches",
|
|
304
278
|
"envs",
|
|
305
279
|
"sessions",
|
|
306
|
-
"kubernetes",
|
|
307
280
|
"processes",
|
|
308
281
|
"shell"
|
|
309
282
|
];
|
|
@@ -314,7 +287,6 @@ const HELP_SECTION_TITLES = {
|
|
|
314
287
|
branches: "Branches",
|
|
315
288
|
envs: "Environment variables",
|
|
316
289
|
sessions: "Sessions",
|
|
317
|
-
kubernetes: "Kubernetes",
|
|
318
290
|
processes: "Processes",
|
|
319
291
|
shell: "Shell"
|
|
320
292
|
};
|
|
@@ -1553,36 +1525,6 @@ function parseRecentSessionArgs(args) {
|
|
|
1553
1525
|
if (!Number.isInteger(limit) || limit < 1 || limit > 200) throw new Error("--limit must be an integer between 1 and 200");
|
|
1554
1526
|
return { limit };
|
|
1555
1527
|
}
|
|
1556
|
-
function parseK8sUsageArgs(args) {
|
|
1557
|
-
const parsed = { window: "24h" };
|
|
1558
|
-
const seen = /* @__PURE__ */ new Set();
|
|
1559
|
-
for (let index = 0; index < args.length; index += 1) {
|
|
1560
|
-
const arg = args[index];
|
|
1561
|
-
if (!arg) continue;
|
|
1562
|
-
const option = ["--namespace", "--workload", "--container", "--window"].find(
|
|
1563
|
-
(candidate) => arg === candidate || arg.startsWith(`${candidate}=`)
|
|
1564
|
-
);
|
|
1565
|
-
if (!option) throw new Error(`Unknown k8s usage argument: ${arg}`);
|
|
1566
|
-
if (seen.has(option)) throw new Error(`Use ${option} only once`);
|
|
1567
|
-
seen.add(option);
|
|
1568
|
-
const inlineValue = parseLongOptionWithEquals(arg, option);
|
|
1569
|
-
const candidateValue = inlineValue ?? args[index + 1];
|
|
1570
|
-
if (!candidateValue || candidateValue.startsWith("--")) throw new Error(`Missing value for ${option}`);
|
|
1571
|
-
const value = candidateValue;
|
|
1572
|
-
if (inlineValue === void 0) index += 1;
|
|
1573
|
-
if (option === "--window") {
|
|
1574
|
-
if (value !== "24h" && value !== "7d") throw new Error("--window must be 24h or 7d");
|
|
1575
|
-
parsed.window = value;
|
|
1576
|
-
} else if (option === "--namespace") {
|
|
1577
|
-
parsed.namespace = value;
|
|
1578
|
-
} else if (option === "--workload") {
|
|
1579
|
-
parsed.workload = value;
|
|
1580
|
-
} else {
|
|
1581
|
-
parsed.container = value;
|
|
1582
|
-
}
|
|
1583
|
-
}
|
|
1584
|
-
return parsed;
|
|
1585
|
-
}
|
|
1586
1528
|
async function openInBrowser(url) {
|
|
1587
1529
|
if (process.platform === "darwin") {
|
|
1588
1530
|
spawn("open", [url], { stdio: "ignore", detached: true }).unref();
|
|
@@ -1845,171 +1787,6 @@ Error: ${result.error}` : "";
|
|
|
1845
1787
|
return `Workspace sync ${result.outcome} on ${result.workerLabel}; HEAD ${head}; ${result.diffSizeBytes} bytes.${discarded}${error}
|
|
1846
1788
|
`;
|
|
1847
1789
|
}
|
|
1848
|
-
function summarizeK8sHistory(history) {
|
|
1849
|
-
if (history.envelope.length === 0) {
|
|
1850
|
-
return {
|
|
1851
|
-
cpuMinNanoCores: null,
|
|
1852
|
-
cpuMaxNanoCores: null,
|
|
1853
|
-
memoryMinBytes: null,
|
|
1854
|
-
memoryMaxBytes: null,
|
|
1855
|
-
sampleCount: 0
|
|
1856
|
-
};
|
|
1857
|
-
}
|
|
1858
|
-
return {
|
|
1859
|
-
cpuMinNanoCores: Math.min(...history.envelope.map((point) => point.cpuMinNanoCores)),
|
|
1860
|
-
cpuMaxNanoCores: Math.max(...history.envelope.map((point) => point.cpuMaxNanoCores)),
|
|
1861
|
-
memoryMinBytes: Math.min(...history.envelope.map((point) => point.memoryMinBytes)),
|
|
1862
|
-
memoryMaxBytes: Math.max(...history.envelope.map((point) => point.memoryMaxBytes)),
|
|
1863
|
-
sampleCount: history.envelope.reduce((total, point) => total + point.sampleCount, 0)
|
|
1864
|
-
};
|
|
1865
|
-
}
|
|
1866
|
-
async function mapWithConcurrency(items, concurrency, fn) {
|
|
1867
|
-
const results = new Array(items.length);
|
|
1868
|
-
let nextIndex = 0;
|
|
1869
|
-
await Promise.all(
|
|
1870
|
-
Array.from({ length: Math.min(concurrency, items.length) }, async () => {
|
|
1871
|
-
while (nextIndex < items.length) {
|
|
1872
|
-
const index = nextIndex++;
|
|
1873
|
-
const item = items[index];
|
|
1874
|
-
if (item !== void 0) results[index] = await fn(item);
|
|
1875
|
-
}
|
|
1876
|
-
})
|
|
1877
|
-
);
|
|
1878
|
-
return results;
|
|
1879
|
-
}
|
|
1880
|
-
async function collectK8sUsage(client, options) {
|
|
1881
|
-
const resources = await client.k8s.resources();
|
|
1882
|
-
const candidates = resources.workloads.flatMap((workload) => workload.containers.map((container) => ({ workload, container }))).filter(({ workload, container }) => {
|
|
1883
|
-
const workloadMatches = !options.workload || options.workload === workload.name || options.workload === `${workload.namespace}/${workload.name}` || options.workload === workload.uid;
|
|
1884
|
-
return (!options.namespace || options.namespace === workload.namespace) && workloadMatches && (!options.container || options.container === container.name);
|
|
1885
|
-
}).sort(
|
|
1886
|
-
(left, right) => left.workload.namespace.localeCompare(right.workload.namespace) || left.workload.name.localeCompare(right.workload.name) || left.container.name.localeCompare(right.container.name)
|
|
1887
|
-
);
|
|
1888
|
-
if (candidates.length === 0 && (options.namespace || options.workload || options.container)) {
|
|
1889
|
-
throw new Error("No managed-vCluster workload containers matched the requested filters");
|
|
1890
|
-
}
|
|
1891
|
-
const entries = await mapWithConcurrency(candidates, 8, async ({ workload, container }) => {
|
|
1892
|
-
const history = await client.k8s.resourceHistory({ workloadUid: workload.uid, containerName: container.name, window: options.window });
|
|
1893
|
-
return {
|
|
1894
|
-
workload: {
|
|
1895
|
-
uid: workload.uid,
|
|
1896
|
-
kind: workload.kind,
|
|
1897
|
-
namespace: workload.namespace,
|
|
1898
|
-
name: workload.name,
|
|
1899
|
-
replicas: workload.replicas,
|
|
1900
|
-
readyReplicas: workload.readyReplicas
|
|
1901
|
-
},
|
|
1902
|
-
container: {
|
|
1903
|
-
name: container.name,
|
|
1904
|
-
currentUsage: container.currentUsage,
|
|
1905
|
-
pods: container.pods
|
|
1906
|
-
},
|
|
1907
|
-
sampledPodCount: container.pods.filter((pod) => pod.cpuNanoCores !== null || pod.memoryBytes !== null).length,
|
|
1908
|
-
summary: summarizeK8sHistory(history),
|
|
1909
|
-
history
|
|
1910
|
-
};
|
|
1911
|
-
});
|
|
1912
|
-
return {
|
|
1913
|
-
window: options.window,
|
|
1914
|
-
collectedAt: resources.collectedAt,
|
|
1915
|
-
metricsFreshAt: resources.metricsFreshAt,
|
|
1916
|
-
metricsStale: resources.metricsStale,
|
|
1917
|
-
filters: {
|
|
1918
|
-
namespace: options.namespace,
|
|
1919
|
-
workload: options.workload,
|
|
1920
|
-
container: options.container
|
|
1921
|
-
},
|
|
1922
|
-
entries
|
|
1923
|
-
};
|
|
1924
|
-
}
|
|
1925
|
-
function formatDecimal(value) {
|
|
1926
|
-
if (value >= 100) return value.toFixed(0);
|
|
1927
|
-
if (value >= 10) return value.toFixed(1).replace(/\.0$/, "");
|
|
1928
|
-
return value.toFixed(2).replace(/\.0+$/, "").replace(/(\.\d)0$/, "$1");
|
|
1929
|
-
}
|
|
1930
|
-
function formatK8sCpu(value) {
|
|
1931
|
-
if (value === null) return "-";
|
|
1932
|
-
const milliCores = value / 1e6;
|
|
1933
|
-
return `${formatDecimal(milliCores)}m`;
|
|
1934
|
-
}
|
|
1935
|
-
function formatK8sMemory(value) {
|
|
1936
|
-
if (value === null) return "-";
|
|
1937
|
-
const units = ["B", "Ki", "Mi", "Gi", "Ti"];
|
|
1938
|
-
let amount = value;
|
|
1939
|
-
let unitIndex = 0;
|
|
1940
|
-
while (amount >= 1024 && unitIndex < units.length - 1) {
|
|
1941
|
-
amount /= 1024;
|
|
1942
|
-
unitIndex += 1;
|
|
1943
|
-
}
|
|
1944
|
-
return `${formatDecimal(amount)}${units[unitIndex]}`;
|
|
1945
|
-
}
|
|
1946
|
-
function getK8sUsageState(entry) {
|
|
1947
|
-
const hasHistory = entry.history.envelope.length > 0;
|
|
1948
|
-
const terminalPods = entry.container.pods.filter((pod) => pod.phase === "Succeeded" || pod.phase === "Failed").length;
|
|
1949
|
-
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);
|
|
1950
|
-
if (inactive) return "inactive";
|
|
1951
|
-
if (entry.sampledPodCount === 0) return hasHistory ? entry.history.stale ? "stale" : "no current" : "no data";
|
|
1952
|
-
if (!hasHistory) return "warming";
|
|
1953
|
-
if (entry.history.stale) return "stale";
|
|
1954
|
-
if (entry.history.incomplete) return "partial";
|
|
1955
|
-
return "healthy";
|
|
1956
|
-
}
|
|
1957
|
-
function abbreviatedK8sKind(kind) {
|
|
1958
|
-
switch (kind) {
|
|
1959
|
-
case "Deployment":
|
|
1960
|
-
return "deploy";
|
|
1961
|
-
case "StatefulSet":
|
|
1962
|
-
return "sts";
|
|
1963
|
-
case "DaemonSet":
|
|
1964
|
-
return "ds";
|
|
1965
|
-
case "CronJob":
|
|
1966
|
-
return "cron";
|
|
1967
|
-
case "ReplicaSet":
|
|
1968
|
-
return "rs";
|
|
1969
|
-
case "Job":
|
|
1970
|
-
return "job";
|
|
1971
|
-
case "Pod":
|
|
1972
|
-
return "pod";
|
|
1973
|
-
}
|
|
1974
|
-
}
|
|
1975
|
-
function renderK8sUsageReport(report) {
|
|
1976
|
-
const freshness = report.metricsFreshAt ?? "unavailable";
|
|
1977
|
-
const lines = [
|
|
1978
|
-
`Window: ${report.window} Collector: ${report.metricsStale ? "stale" : "current"} Fresh: ${freshness}`,
|
|
1979
|
-
"PODS counts pods with current metrics."
|
|
1980
|
-
];
|
|
1981
|
-
if (report.entries.length === 0) return `${lines.join("\n")}
|
|
1982
|
-
No managed-vCluster workload containers found.
|
|
1983
|
-
`;
|
|
1984
|
-
const headers = ["WORKLOAD", "CONTAINER", "PODS", "CPU NOW", "CPU MAX", "MEM NOW", "MEM MAX", "STATE"];
|
|
1985
|
-
const grouped = /* @__PURE__ */ new Map();
|
|
1986
|
-
for (const entry of report.entries) {
|
|
1987
|
-
const entries = grouped.get(entry.workload.namespace) ?? [];
|
|
1988
|
-
entries.push(entry);
|
|
1989
|
-
grouped.set(entry.workload.namespace, entries);
|
|
1990
|
-
}
|
|
1991
|
-
for (const [namespace, entries] of grouped) {
|
|
1992
|
-
const rows = entries.map((entry) => {
|
|
1993
|
-
const state = getK8sUsageState(entry);
|
|
1994
|
-
return [
|
|
1995
|
-
`${abbreviatedK8sKind(entry.workload.kind)}/${entry.workload.name}`,
|
|
1996
|
-
entry.container.name,
|
|
1997
|
-
String(entry.sampledPodCount),
|
|
1998
|
-
formatK8sCpu(entry.container.currentUsage.cpuNanoCores),
|
|
1999
|
-
formatK8sCpu(entry.summary.cpuMaxNanoCores),
|
|
2000
|
-
formatK8sMemory(entry.container.currentUsage.memoryBytes),
|
|
2001
|
-
formatK8sMemory(entry.summary.memoryMaxBytes),
|
|
2002
|
-
state === "healthy" ? "-" : state
|
|
2003
|
-
];
|
|
2004
|
-
});
|
|
2005
|
-
const widths = headers.map((header, column) => Math.max(header.length, ...rows.map((row) => row[column]?.length ?? 0)));
|
|
2006
|
-
const formatRow = (row) => row.map((value, column) => column === row.length - 1 ? value : value.padEnd(widths[column] ?? value.length)).join(" ");
|
|
2007
|
-
const separator = widths.map((width) => "-".repeat(width)).join(" ");
|
|
2008
|
-
lines.push("", `Namespace: ${namespace}`, formatRow(headers), separator, ...rows.map(formatRow));
|
|
2009
|
-
}
|
|
2010
|
-
return `${lines.join("\n")}
|
|
2011
|
-
`;
|
|
2012
|
-
}
|
|
2013
1790
|
function renderSessionDescription(session) {
|
|
2014
1791
|
return [`Session: ${session.id}`, `Project: ${session.projectPath}`, `Branch: ${session.branchName}`].join("\n") + "\n";
|
|
2015
1792
|
}
|
|
@@ -2936,45 +2713,6 @@ async function executeR5dctlCommand(client, json, args) {
|
|
|
2936
2713
|
write(result, renderWorkspaceSync(result));
|
|
2937
2714
|
return;
|
|
2938
2715
|
}
|
|
2939
|
-
if (first === "k8s" && second === "usage") {
|
|
2940
|
-
const report = await collectK8sUsage(client, parseK8sUsageArgs(args.slice(2)));
|
|
2941
|
-
write(report, renderK8sUsageReport(report));
|
|
2942
|
-
return;
|
|
2943
|
-
}
|
|
2944
|
-
if (first === "vcluster" && second === "trusted-workflow") {
|
|
2945
|
-
const subcommand = requireValue(args[2], "Missing trusted-workflow command");
|
|
2946
|
-
if (subcommand === "add") {
|
|
2947
|
-
const parsed = parseCommandOperands(args.slice(3), /* @__PURE__ */ new Set(["--repo", "--workflow"]), /* @__PURE__ */ new Map(), "trusted-workflow add");
|
|
2948
|
-
if (parsed.positionals.length > 0) throw new Error(`Unexpected trusted-workflow add value: ${parsed.positionals[0]}`);
|
|
2949
|
-
const workflow = await client.k8s.trustedWorkflows.add({
|
|
2950
|
-
repository: requireValue(parsed.values.get("--repo"), "--repo <owner/repository> is required"),
|
|
2951
|
-
workflowPath: requireValue(parsed.values.get("--workflow"), "--workflow <path> is required")
|
|
2952
|
-
});
|
|
2953
|
-
write(workflow, "Trusted GitHub workflow configured successfully.\n");
|
|
2954
|
-
return;
|
|
2955
|
-
}
|
|
2956
|
-
if (subcommand === "list") {
|
|
2957
|
-
if (args.length > 3) throw new Error(`Unexpected trusted-workflow list value: ${args[3]}`);
|
|
2958
|
-
const workflows = await client.k8s.trustedWorkflows.list();
|
|
2959
|
-
write(
|
|
2960
|
-
workflows,
|
|
2961
|
-
workflows.length === 0 ? "No trusted GitHub workflows.\n" : `${workflows.map(
|
|
2962
|
-
(workflow) => `${workflow.id} ${workflow.repository} ${workflow.workflowPath} ${workflow.revokedAt ? "revoked" : "active"}`
|
|
2963
|
-
).join("\n")}
|
|
2964
|
-
`
|
|
2965
|
-
);
|
|
2966
|
-
return;
|
|
2967
|
-
}
|
|
2968
|
-
if (subcommand === "remove") {
|
|
2969
|
-
const workflowId = requireValue(args[3], "Missing trusted workflow id");
|
|
2970
|
-
if (args.length > 4) throw new Error(`Unexpected trusted-workflow remove value: ${args[4]}`);
|
|
2971
|
-
const result = await client.k8s.trustedWorkflows.revoke(workflowId);
|
|
2972
|
-
if (!result.success) throw new Error("Trusted GitHub workflow was not found or was already revoked");
|
|
2973
|
-
write(result, "Trusted GitHub workflow revoked.\n");
|
|
2974
|
-
return;
|
|
2975
|
-
}
|
|
2976
|
-
throw new Error(`Unknown trusted-workflow command: ${subcommand}`);
|
|
2977
|
-
}
|
|
2978
2716
|
if (first === "ps" && second === "list") {
|
|
2979
2717
|
const processes = await client.processes.list(parsePsListArgs(args.slice(2)));
|
|
2980
2718
|
write(processes, renderProcessList(processes));
|
|
@@ -3310,12 +3048,6 @@ function resolveCommandExecution(options, rest) {
|
|
|
3310
3048
|
text: PS_HELP_TEXT
|
|
3311
3049
|
};
|
|
3312
3050
|
}
|
|
3313
|
-
if (command === "k8s" && commandArgs.length === 0) {
|
|
3314
|
-
return {
|
|
3315
|
-
kind: "cli-help",
|
|
3316
|
-
text: K8S_HELP_TEXT
|
|
3317
|
-
};
|
|
3318
|
-
}
|
|
3319
3051
|
if (command === "session" && commandArgs.length === 0) {
|
|
3320
3052
|
return {
|
|
3321
3053
|
kind: "cli-help",
|
|
@@ -3337,10 +3069,7 @@ function resolveCommandExecution(options, rest) {
|
|
|
3337
3069
|
text: sharedHelp
|
|
3338
3070
|
};
|
|
3339
3071
|
}
|
|
3340
|
-
|
|
3341
|
-
kind: "plugin",
|
|
3342
|
-
pluginArgs: [...normalizedRest, "--help"]
|
|
3343
|
-
};
|
|
3072
|
+
throw new Error(`Unknown command: ${normalizedRest.join(" ")}`);
|
|
3344
3073
|
}
|
|
3345
3074
|
if (command === "projects" || command === "sessions" || command === "workspace") {
|
|
3346
3075
|
return {
|
|
@@ -3450,26 +3179,6 @@ function resolveCommandExecution(options, rest) {
|
|
|
3450
3179
|
}
|
|
3451
3180
|
throw new Error(`Unknown ps command: ${subcommand ?? ""}`.trim());
|
|
3452
3181
|
}
|
|
3453
|
-
if (command === "k8s") {
|
|
3454
|
-
const subcommand = commandArgs[0];
|
|
3455
|
-
if (options.project || options.branch || options.session) {
|
|
3456
|
-
throw new Error("Project, branch, and session scopes are not supported for `k8s`");
|
|
3457
|
-
}
|
|
3458
|
-
if (subcommand === "usage") {
|
|
3459
|
-
parseK8sUsageArgs(commandArgs.slice(1));
|
|
3460
|
-
return { kind: "plugin", pluginArgs: ["k8s", "usage", ...commandArgs.slice(1)] };
|
|
3461
|
-
}
|
|
3462
|
-
throw new Error(`Unknown k8s command: ${subcommand ?? ""}`.trim());
|
|
3463
|
-
}
|
|
3464
|
-
if (command === "vcluster") {
|
|
3465
|
-
if (options.project || options.branch || options.session) {
|
|
3466
|
-
throw new Error("Project, branch, and session scopes are not supported for `vcluster`");
|
|
3467
|
-
}
|
|
3468
|
-
if (commandArgs[0] !== "trusted-workflow") {
|
|
3469
|
-
throw new Error(`Unknown vcluster command: ${commandArgs[0] ?? ""}`.trim());
|
|
3470
|
-
}
|
|
3471
|
-
return { kind: "plugin", pluginArgs: ["vcluster", "trusted-workflow", ...commandArgs.slice(1)] };
|
|
3472
|
-
}
|
|
3473
3182
|
if (command === "get") {
|
|
3474
3183
|
const target = commandArgs[0];
|
|
3475
3184
|
if (target === "projects") {
|
|
@@ -3951,18 +3660,14 @@ export {
|
|
|
3951
3660
|
branchDeletionConfirmationMessage,
|
|
3952
3661
|
branchDeletionsConfirmationMessage,
|
|
3953
3662
|
branchDeletionsFailureMessage,
|
|
3954
|
-
collectK8sUsage,
|
|
3955
3663
|
createBranchThroughPlatform,
|
|
3956
3664
|
deleteBranchThroughPlatform,
|
|
3957
3665
|
deleteBranchesThroughPlatform,
|
|
3958
3666
|
dispatchSessionRunCommand,
|
|
3959
3667
|
followR5dctlSession,
|
|
3960
|
-
formatK8sCpu,
|
|
3961
|
-
formatK8sMemory,
|
|
3962
3668
|
formatProcessAge,
|
|
3963
3669
|
formatProcessLane,
|
|
3964
3670
|
getCliHelpText,
|
|
3965
|
-
getK8sUsageState,
|
|
3966
3671
|
getProjectEnvValue,
|
|
3967
3672
|
getR5dctlVersion,
|
|
3968
3673
|
getTransientDevicePollDelay,
|
|
@@ -3983,7 +3688,6 @@ export {
|
|
|
3983
3688
|
parseEnvRequestResponseArgs,
|
|
3984
3689
|
parseGetEnvsArgs,
|
|
3985
3690
|
parseGlobalArgs,
|
|
3986
|
-
parseK8sUsageArgs,
|
|
3987
3691
|
parsePromptArgs,
|
|
3988
3692
|
parsePsHistoryArgs,
|
|
3989
3693
|
parsePsListArgs,
|
|
@@ -4008,7 +3712,6 @@ export {
|
|
|
4008
3712
|
renderConversationWorkResponse,
|
|
4009
3713
|
renderDeviceAuthorizationPrompt,
|
|
4010
3714
|
renderEnvValues,
|
|
4011
|
-
renderK8sUsageReport,
|
|
4012
3715
|
renderProcessHistory,
|
|
4013
3716
|
renderProcessInspection,
|
|
4014
3717
|
renderProcessList,
|
package/dist/mjs/package.json
CHANGED
package/dist/types/cli.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { R5dctlApiError, R5dctlClient, type R5dctlConversationForkResponse, type R5dctlConversationHeadResponse, type R5dctlConversationRenderOptions, type R5dctlConversationResponse, type R5dctlConversationNodeResponse, type R5dctlConversationOverviewResponse, type R5dctlConversationWorkDetail, type R5dctlConversationWorkResponse, type R5dctlEnvData, type
|
|
1
|
+
import { R5dctlApiError, R5dctlClient, type R5dctlConversationForkResponse, type R5dctlConversationHeadResponse, type R5dctlConversationRenderOptions, type R5dctlConversationResponse, type R5dctlConversationNodeResponse, type R5dctlConversationOverviewResponse, type R5dctlConversationWorkDetail, type R5dctlConversationWorkResponse, type R5dctlEnvData, type R5dctlProcessInspection, type R5dctlProcessListInput, type R5dctlProcessLog, type R5dctlProcessRun, type R5dctlWorkerCapacityReport, type R5dctlBranch, type R5dctlBranchDeletionStatus, type R5dctlBranchDescription, type R5dctlBranchOperation, type R5dctlSessionSummary, type R5dctlSessionClaim, type R5dctlSessionClaimListResponse, type R5dctlSessionEvent, type R5dctlSessionStartInput, type R5dctlWorkspaceStatus, type R5dctlWorkspaceSyncResult, type R5dctlWorkingTreeMode, type R5dctlWorktreeSource, type R5dctlPromptDelivery, type R5dctlSessionMode, type ChatMode, type ModelTier } from "@ricsam/r5d-api";
|
|
2
2
|
export type GlobalOptions = {
|
|
3
3
|
baseUrl?: string;
|
|
4
4
|
json: boolean;
|
|
@@ -260,13 +260,6 @@ export declare function parseShellArgs(args: string[]): {
|
|
|
260
260
|
};
|
|
261
261
|
export declare function parsePsListArgs(args: string[]): R5dctlProcessListInput;
|
|
262
262
|
export declare function parsePsHistoryArgs(args: string[]): R5dctlProcessListInput;
|
|
263
|
-
export type K8sUsageOptions = {
|
|
264
|
-
namespace?: string;
|
|
265
|
-
workload?: string;
|
|
266
|
-
container?: string;
|
|
267
|
-
window: "24h" | "7d";
|
|
268
|
-
};
|
|
269
|
-
export declare function parseK8sUsageArgs(args: string[]): K8sUsageOptions;
|
|
270
263
|
export declare function renderDeviceAuthorizationPrompt(input: {
|
|
271
264
|
verificationUri: string;
|
|
272
265
|
userCode: string;
|
|
@@ -283,41 +276,6 @@ export declare function renderWorkerCapacity(workerLabel: string, capacity: R5dc
|
|
|
283
276
|
export declare function renderWorkspaceStatus(status: R5dctlWorkspaceStatus): string;
|
|
284
277
|
export declare function renderBranchList(branches: R5dctlBranch[]): string;
|
|
285
278
|
export declare function renderWorkspaceSync(result: R5dctlWorkspaceSyncResult): string;
|
|
286
|
-
export type K8sUsageSummary = {
|
|
287
|
-
cpuMinNanoCores: number | null;
|
|
288
|
-
cpuMaxNanoCores: number | null;
|
|
289
|
-
memoryMinBytes: number | null;
|
|
290
|
-
memoryMaxBytes: number | null;
|
|
291
|
-
sampleCount: number;
|
|
292
|
-
};
|
|
293
|
-
export type K8sUsageReport = {
|
|
294
|
-
window: "24h" | "7d";
|
|
295
|
-
collectedAt: string;
|
|
296
|
-
metricsFreshAt: string | null;
|
|
297
|
-
metricsStale: boolean;
|
|
298
|
-
filters: Omit<K8sUsageOptions, "window">;
|
|
299
|
-
entries: Array<{
|
|
300
|
-
workload: Pick<R5dctlK8sWorkload, "uid" | "kind" | "namespace" | "name" | "replicas" | "readyReplicas">;
|
|
301
|
-
container: {
|
|
302
|
-
name: string;
|
|
303
|
-
currentUsage: R5dctlK8sUsage;
|
|
304
|
-
pods: Array<R5dctlK8sUsage & {
|
|
305
|
-
podUid: string;
|
|
306
|
-
podName: string;
|
|
307
|
-
phase: string | null;
|
|
308
|
-
}>;
|
|
309
|
-
};
|
|
310
|
-
sampledPodCount: number;
|
|
311
|
-
summary: K8sUsageSummary;
|
|
312
|
-
history: R5dctlK8sResourceHistory;
|
|
313
|
-
}>;
|
|
314
|
-
};
|
|
315
|
-
export declare function collectK8sUsage(client: Pick<R5dctlClient, "k8s">, options: K8sUsageOptions): Promise<K8sUsageReport>;
|
|
316
|
-
export declare function formatK8sCpu(value: number | null): string;
|
|
317
|
-
export declare function formatK8sMemory(value: number | null): string;
|
|
318
|
-
export type K8sUsageState = "healthy" | "partial" | "stale" | "inactive" | "warming" | "no data" | "no current";
|
|
319
|
-
export declare function getK8sUsageState(entry: K8sUsageReport["entries"][number]): K8sUsageState;
|
|
320
|
-
export declare function renderK8sUsageReport(report: K8sUsageReport): string;
|
|
321
279
|
export declare function formatProcessAge(startedAt: string, nowMs?: number): string;
|
|
322
280
|
export declare function renderProcessList(processes: R5dctlProcessRun[], nowMs?: number): string;
|
|
323
281
|
/** `general`, `control`, `queued:general`, or `-` when the worker reported nothing for this run. */
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ricsam/r5dctl",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.127",
|
|
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.127",
|
|
30
30
|
"dotenv": "^17",
|
|
31
31
|
"qrcode": "^1.5.4",
|
|
32
32
|
"ws": "^8.18.3"
|