@ricsam/r5dctl 0.0.23 → 0.0.24
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 +10 -3
- package/dist/cjs/cli.cjs +188 -72
- package/dist/cjs/package.json +1 -1
- package/dist/mjs/cli.mjs +185 -72
- package/dist/mjs/package.json +1 -1
- package/dist/types/cli.d.ts +6 -4
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -51,7 +51,7 @@ r5dctl -p <project> get branches
|
|
|
51
51
|
r5dctl -p <project> describe branch <branch>
|
|
52
52
|
r5dctl -p <project> -b <branch> get envs
|
|
53
53
|
r5dctl -p <project> -b <branch> set envs --from-env-file ~/.env --only-missing
|
|
54
|
-
r5dctl -p <project> -b <branch> set envs -e
|
|
54
|
+
r5dctl -p <project> -b <branch> set envs -e API_KEY=value --description "API credential"
|
|
55
55
|
r5dctl -p <project> get sessions --branch <branch>
|
|
56
56
|
r5dctl -p <project> create session -b <branch> --name "Spec session"
|
|
57
57
|
r5dctl describe session <session-id>
|
|
@@ -65,15 +65,22 @@ r5dctl -s <session-id> conversation --system # include system prompt m
|
|
|
65
65
|
r5dctl -s <session-id> conversation --raw --tools --system # exact raw agent request transcript
|
|
66
66
|
r5dctl -s <session-id> prompt --mode plan --model max "..."
|
|
67
67
|
r5dctl -s <session-id> answer-questions -a1 "Recipe Collection" -a2 "Both Manual & AI"
|
|
68
|
-
r5dctl -s <session-id> apply-required-envs -e
|
|
68
|
+
r5dctl -s <session-id> apply-required-envs -e KEY=value
|
|
69
69
|
|
|
70
70
|
r5dctl -p <project> -b <branch> shell
|
|
71
71
|
r5dctl -p <project> -b <branch> shell -c "bun test"
|
|
72
|
+
|
|
73
|
+
r5dctl ps list
|
|
74
|
+
r5dctl -p <project> -b <branch> ps list --worker <label>
|
|
75
|
+
r5dctl -s <session-id> ps list --json
|
|
76
|
+
r5dctl ps stop <run-id>
|
|
72
77
|
```
|
|
73
78
|
|
|
74
79
|
`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.
|
|
75
80
|
|
|
76
|
-
`
|
|
81
|
+
`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.
|
|
82
|
+
|
|
83
|
+
`get envs` reports names and whether values are set, but never prints the values. 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.
|
|
77
84
|
|
|
78
85
|
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.
|
|
79
86
|
|
package/dist/cjs/cli.cjs
CHANGED
|
@@ -28,6 +28,7 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
|
|
|
28
28
|
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
29
29
|
var cli_exports = {};
|
|
30
30
|
__export(cli_exports, {
|
|
31
|
+
formatProcessAge: () => formatProcessAge,
|
|
31
32
|
getCliHelpText: () => getCliHelpText,
|
|
32
33
|
getR5dctlVersion: () => getR5dctlVersion,
|
|
33
34
|
main: () => main,
|
|
@@ -36,10 +37,12 @@ __export(cli_exports, {
|
|
|
36
37
|
parseEnvFlags: () => parseEnvFlags,
|
|
37
38
|
parseGlobalArgs: () => parseGlobalArgs,
|
|
38
39
|
parsePromptArgs: () => parsePromptArgs,
|
|
40
|
+
parsePsListArgs: () => parsePsListArgs,
|
|
39
41
|
parseSetEnvArgs: () => parseSetEnvArgs,
|
|
40
42
|
parseShellArgs: () => parseShellArgs,
|
|
41
43
|
readDotenvFile: () => readDotenvFile,
|
|
42
44
|
renderConversationResponse: () => renderConversationResponse,
|
|
45
|
+
renderProcessList: () => renderProcessList,
|
|
43
46
|
resolveCommandExecution: () => resolveCommandExecution,
|
|
44
47
|
runR5dctlCli: () => runR5dctlCli,
|
|
45
48
|
summarizeEnvData: () => summarizeEnvData
|
|
@@ -102,6 +105,15 @@ const CLI_ONLY_COMMAND_HELP = [
|
|
|
102
105
|
usage: "-p <project> -b <branch> shell [-c <command>]"
|
|
103
106
|
}
|
|
104
107
|
];
|
|
108
|
+
const PS_HELP_TEXT = [
|
|
109
|
+
"Usage:",
|
|
110
|
+
" r5dctl ps list [--worker <label>]",
|
|
111
|
+
" r5dctl ps stop <run-id>",
|
|
112
|
+
"",
|
|
113
|
+
"List or gracefully stop active r5d-managed worker processes.",
|
|
114
|
+
"Use -p/--project, -b/--branch, or -s/--session to filter ps list.",
|
|
115
|
+
""
|
|
116
|
+
].join("\n");
|
|
105
117
|
const SHARED_HELP_ENTRIES = [
|
|
106
118
|
{ section: "auth", usage: "auth status", description: "Show the current authenticated user and credential source." },
|
|
107
119
|
{ section: "auth", usage: "auth logout", description: "Revoke the current credential and clear saved auth." },
|
|
@@ -125,12 +137,12 @@ const SHARED_HELP_ENTRIES = [
|
|
|
125
137
|
},
|
|
126
138
|
{
|
|
127
139
|
section: "envs",
|
|
128
|
-
usage: "-p <project> -b <branch> set envs --from-env-file <path> [--
|
|
140
|
+
usage: "-p <project> -b <branch> set envs --from-env-file <path> [--only-missing]",
|
|
129
141
|
description: "Securely import branch envs from a dotenv file without putting values in command arguments."
|
|
130
142
|
},
|
|
131
143
|
{
|
|
132
144
|
section: "envs",
|
|
133
|
-
usage: "-p <project> -b <branch> set envs -e
|
|
145
|
+
usage: "-p <project> -b <branch> set envs -e KEY=value [--description <text>] [--only-missing]",
|
|
134
146
|
description: "Set one or more branch envs directly."
|
|
135
147
|
},
|
|
136
148
|
{
|
|
@@ -163,9 +175,15 @@ const SHARED_HELP_ENTRIES = [
|
|
|
163
175
|
},
|
|
164
176
|
{
|
|
165
177
|
section: "sessions",
|
|
166
|
-
usage: "-s <session-id> apply-required-envs -e
|
|
167
|
-
description: "Apply required
|
|
178
|
+
usage: "-s <session-id> apply-required-envs -e KEY=value",
|
|
179
|
+
description: "Apply required environment variable values."
|
|
168
180
|
},
|
|
181
|
+
{
|
|
182
|
+
section: "processes",
|
|
183
|
+
usage: "ps list [--worker <label>]",
|
|
184
|
+
description: "List active r5d-managed worker processes; global project, branch, and session filters apply."
|
|
185
|
+
},
|
|
186
|
+
{ section: "processes", usage: "ps stop <run-id>", description: "Gracefully stop an active process with SIGTERM." },
|
|
169
187
|
{
|
|
170
188
|
section: "agents",
|
|
171
189
|
usage: '-p <project> start-agent <src-branch> <agent-type> "<prompt>"',
|
|
@@ -181,13 +199,24 @@ const SHARED_HELP_ENTRIES = [
|
|
|
181
199
|
{ section: "merges", usage: "-p <project> continue-merge <target-branch>", description: "Commit a resolved merge." },
|
|
182
200
|
{ section: "merges", usage: "-p <project> abort-merge <target-branch>", description: "Abort an in-progress merge." }
|
|
183
201
|
];
|
|
184
|
-
const HELP_SECTION_ORDER = [
|
|
202
|
+
const HELP_SECTION_ORDER = [
|
|
203
|
+
"auth",
|
|
204
|
+
"projects",
|
|
205
|
+
"branches",
|
|
206
|
+
"envs",
|
|
207
|
+
"sessions",
|
|
208
|
+
"processes",
|
|
209
|
+
"shell",
|
|
210
|
+
"agents",
|
|
211
|
+
"merges"
|
|
212
|
+
];
|
|
185
213
|
const HELP_SECTION_TITLES = {
|
|
186
214
|
auth: "Auth",
|
|
187
215
|
projects: "Projects",
|
|
188
216
|
branches: "Branches",
|
|
189
217
|
envs: "Environment variables",
|
|
190
218
|
sessions: "Sessions",
|
|
219
|
+
processes: "Processes",
|
|
191
220
|
shell: "Shell",
|
|
192
221
|
agents: "Agents",
|
|
193
222
|
merges: "Merges"
|
|
@@ -288,7 +317,7 @@ function renderCliOnlyCommandHelp(entry) {
|
|
|
288
317
|
}
|
|
289
318
|
function renderSharedCommandHelp(pathSegments) {
|
|
290
319
|
const entry = SHARED_HELP_ENTRIES.find((candidate) => {
|
|
291
|
-
const usageSegments = candidate.usage.split(" ").filter((segment) => !segment.startsWith("-") && !segment.startsWith("<") && !segment.startsWith("["));
|
|
320
|
+
const usageSegments = candidate.usage.split(" ").map((segment) => segment.replace(/^\[/, "").replace(/\]$/, "")).filter((segment) => !segment.startsWith("-") && !segment.startsWith("<") && !segment.startsWith("["));
|
|
292
321
|
return usageSegments.length <= pathSegments.length && usageSegments.every((segment, index) => segment === pathSegments[index]);
|
|
293
322
|
});
|
|
294
323
|
if (!entry) {
|
|
@@ -524,33 +553,24 @@ function parseAnswerFlags(args) {
|
|
|
524
553
|
function validateEnvAssignment(value) {
|
|
525
554
|
const equalsIndex = value.indexOf("=");
|
|
526
555
|
if (equalsIndex <= 0) {
|
|
527
|
-
throw new Error(`Invalid env assignment '${value}'. Expected
|
|
528
|
-
}
|
|
529
|
-
const assignment = value.slice(0, equalsIndex);
|
|
530
|
-
const slashIndex = assignment.indexOf("/");
|
|
531
|
-
if (slashIndex <= 0) {
|
|
532
|
-
throw new Error(`Invalid env assignment '${value}'. Expected backend/KEY=value or frontend/KEY=value`);
|
|
556
|
+
throw new Error(`Invalid env assignment '${value}'. Expected KEY=value`);
|
|
533
557
|
}
|
|
534
|
-
const
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
throw new Error(`Invalid env assignment '${value}'. Expected backend/KEY=value or frontend/KEY=value`);
|
|
558
|
+
const key = value.slice(0, equalsIndex);
|
|
559
|
+
if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(key)) {
|
|
560
|
+
throw new Error(`Invalid env assignment '${value}'. Expected KEY=value`);
|
|
538
561
|
}
|
|
539
562
|
}
|
|
540
563
|
function parseEnvAssignment(value) {
|
|
541
564
|
validateEnvAssignment(value);
|
|
542
565
|
const equalsIndex = value.indexOf("=");
|
|
543
|
-
const slashIndex = value.indexOf("/");
|
|
544
566
|
return {
|
|
545
|
-
|
|
546
|
-
name: value.slice(slashIndex + 1, equalsIndex),
|
|
567
|
+
name: value.slice(0, equalsIndex),
|
|
547
568
|
value: value.slice(equalsIndex + 1)
|
|
548
569
|
};
|
|
549
570
|
}
|
|
550
571
|
function parseSetEnvArgs(args) {
|
|
551
572
|
const parsed = {
|
|
552
573
|
assignments: [],
|
|
553
|
-
target: "backend",
|
|
554
574
|
onlyMissing: false,
|
|
555
575
|
includeEmpty: false
|
|
556
576
|
};
|
|
@@ -580,23 +600,6 @@ function parseSetEnvArgs(args) {
|
|
|
580
600
|
parsed.fromEnvFile = requireValue(inlineFile, "Missing value for --from-env-file");
|
|
581
601
|
continue;
|
|
582
602
|
}
|
|
583
|
-
if (arg === "--target") {
|
|
584
|
-
const target = requireValue(args[index + 1], "Missing value for --target");
|
|
585
|
-
if (target !== "backend" && target !== "frontend") {
|
|
586
|
-
throw new Error("Invalid --target. Expected backend or frontend");
|
|
587
|
-
}
|
|
588
|
-
parsed.target = target;
|
|
589
|
-
index += 1;
|
|
590
|
-
continue;
|
|
591
|
-
}
|
|
592
|
-
const inlineTarget = parseLongOptionWithEquals(arg, "--target");
|
|
593
|
-
if (inlineTarget !== void 0) {
|
|
594
|
-
if (inlineTarget !== "backend" && inlineTarget !== "frontend") {
|
|
595
|
-
throw new Error("Invalid --target. Expected backend or frontend");
|
|
596
|
-
}
|
|
597
|
-
parsed.target = inlineTarget;
|
|
598
|
-
continue;
|
|
599
|
-
}
|
|
600
603
|
if (arg === "--description") {
|
|
601
604
|
parsed.description = requireValue(args[index + 1], "Missing value for --description");
|
|
602
605
|
index += 1;
|
|
@@ -670,7 +673,7 @@ function parseEnvFlags(args) {
|
|
|
670
673
|
if (arg.startsWith("-")) {
|
|
671
674
|
throw new Error(`Unknown env flag: ${arg}`);
|
|
672
675
|
}
|
|
673
|
-
throw new Error(`Unexpected value '${arg}'. Use -e
|
|
676
|
+
throw new Error(`Unexpected value '${arg}'. Use -e KEY=value.`);
|
|
674
677
|
}
|
|
675
678
|
if (envs.length === 0) {
|
|
676
679
|
throw new Error("At least one -e/--env value is required.");
|
|
@@ -764,6 +767,40 @@ function parseShellArgs(args) {
|
|
|
764
767
|
}
|
|
765
768
|
return command === void 0 ? {} : { command };
|
|
766
769
|
}
|
|
770
|
+
function parsePsListArgs(args) {
|
|
771
|
+
const input = {};
|
|
772
|
+
const definitions = [
|
|
773
|
+
{ flag: "--project", key: "project" },
|
|
774
|
+
{ flag: "--branch", key: "branch" },
|
|
775
|
+
{ flag: "--session", key: "session" },
|
|
776
|
+
{ flag: "--worker", key: "worker" }
|
|
777
|
+
];
|
|
778
|
+
const assign = (key, value, flag) => {
|
|
779
|
+
if (input[key] !== void 0) {
|
|
780
|
+
throw new Error(`Use only one ${flag} value`);
|
|
781
|
+
}
|
|
782
|
+
input[key] = requireValue(value, `Missing value for ${flag}`);
|
|
783
|
+
};
|
|
784
|
+
for (let index = 0; index < args.length; index += 1) {
|
|
785
|
+
const arg = args[index];
|
|
786
|
+
if (!arg) continue;
|
|
787
|
+
const definition = definitions.find(({ flag }) => arg === flag || arg.startsWith(`${flag}=`));
|
|
788
|
+
if (!definition) {
|
|
789
|
+
throw new Error(arg.startsWith("-") ? `Unknown ps list flag: ${arg}` : `Unexpected ps list value: ${arg}`);
|
|
790
|
+
}
|
|
791
|
+
if (arg === definition.flag) {
|
|
792
|
+
assign(definition.key, requireValue(args[index + 1], `Missing value for ${definition.flag}`), definition.flag);
|
|
793
|
+
index += 1;
|
|
794
|
+
continue;
|
|
795
|
+
}
|
|
796
|
+
assign(
|
|
797
|
+
definition.key,
|
|
798
|
+
requireValue(parseLongOptionWithEquals(arg, definition.flag), `Missing value for ${definition.flag}`),
|
|
799
|
+
definition.flag
|
|
800
|
+
);
|
|
801
|
+
}
|
|
802
|
+
return input;
|
|
803
|
+
}
|
|
767
804
|
async function openInBrowser(url) {
|
|
768
805
|
if (process.platform === "darwin") {
|
|
769
806
|
(0, import_node_child_process.spawn)("open", [url], { stdio: "ignore", detached: true }).unref();
|
|
@@ -905,34 +942,66 @@ function renderSessionList(sessions) {
|
|
|
905
942
|
function renderSessionDescription(session) {
|
|
906
943
|
return [`Session: ${session.id}`, `Project: ${session.projectPath}`, `Branch: ${session.branchName}`].join("\n") + "\n";
|
|
907
944
|
}
|
|
945
|
+
function formatProcessAge(startedAt, nowMs = Date.now()) {
|
|
946
|
+
const startedAtMs = Date.parse(startedAt);
|
|
947
|
+
if (!Number.isFinite(startedAtMs)) {
|
|
948
|
+
return "unknown";
|
|
949
|
+
}
|
|
950
|
+
const totalSeconds = Math.max(0, Math.floor((nowMs - startedAtMs) / 1e3));
|
|
951
|
+
if (totalSeconds < 60) {
|
|
952
|
+
return `${totalSeconds}s`;
|
|
953
|
+
}
|
|
954
|
+
if (totalSeconds < 60 * 60) {
|
|
955
|
+
const minutes = Math.floor(totalSeconds / 60);
|
|
956
|
+
const seconds = totalSeconds % 60;
|
|
957
|
+
return `${minutes}m ${String(seconds).padStart(2, "0")}s`;
|
|
958
|
+
}
|
|
959
|
+
if (totalSeconds < 24 * 60 * 60) {
|
|
960
|
+
const hours2 = Math.floor(totalSeconds / (60 * 60));
|
|
961
|
+
const minutes = Math.floor(totalSeconds % (60 * 60) / 60);
|
|
962
|
+
return `${hours2}h ${String(minutes).padStart(2, "0")}m`;
|
|
963
|
+
}
|
|
964
|
+
const days = Math.floor(totalSeconds / (24 * 60 * 60));
|
|
965
|
+
const hours = Math.floor(totalSeconds % (24 * 60 * 60) / (60 * 60));
|
|
966
|
+
return `${days}d ${hours}h`;
|
|
967
|
+
}
|
|
968
|
+
function renderProcessList(processes, nowMs = Date.now()) {
|
|
969
|
+
if (processes.length === 0) {
|
|
970
|
+
return "No active processes.\n";
|
|
971
|
+
}
|
|
972
|
+
const headers = ["RUN ID", "STATUS", "AGE", "WORKER", "PROJECT", "BRANCH", "SESSION", "COMMAND"];
|
|
973
|
+
const rows = processes.map((process2) => [
|
|
974
|
+
process2.runId,
|
|
975
|
+
process2.status,
|
|
976
|
+
formatProcessAge(process2.startedAt, nowMs),
|
|
977
|
+
process2.workerLabel,
|
|
978
|
+
process2.projectPath,
|
|
979
|
+
process2.branchName,
|
|
980
|
+
process2.sessionId,
|
|
981
|
+
process2.command
|
|
982
|
+
]);
|
|
983
|
+
const widths = headers.map((header, column) => Math.max(header.length, ...rows.map((row) => row[column]?.length ?? 0)));
|
|
984
|
+
const formatRow = (row) => row.map((value, column) => column === row.length - 1 ? value : value.padEnd(widths[column] ?? value.length)).join(" ");
|
|
985
|
+
const separator = widths.map((width) => "-".repeat(width)).join(" ");
|
|
986
|
+
return `${[formatRow(headers), separator, ...rows.map(formatRow)].join("\n")}
|
|
987
|
+
`;
|
|
988
|
+
}
|
|
908
989
|
function summarizeEnvData(data) {
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
hasValue: env.value !== null
|
|
920
|
-
}
|
|
921
|
-
])
|
|
922
|
-
);
|
|
923
|
-
}
|
|
924
|
-
return summary;
|
|
990
|
+
return Object.fromEntries(
|
|
991
|
+
Object.entries(data).sort(([left], [right]) => left.localeCompare(right)).map(([name, env]) => [
|
|
992
|
+
name,
|
|
993
|
+
{
|
|
994
|
+
optional: env.optional ?? false,
|
|
995
|
+
description: env.description,
|
|
996
|
+
hasValue: env.value !== null
|
|
997
|
+
}
|
|
998
|
+
])
|
|
999
|
+
);
|
|
925
1000
|
}
|
|
926
1001
|
function renderEnvSummary(data) {
|
|
927
1002
|
const lines = [];
|
|
928
|
-
for (const
|
|
929
|
-
|
|
930
|
-
if (entries.length === 0) continue;
|
|
931
|
-
if (lines.length > 0) lines.push("");
|
|
932
|
-
lines.push(`${target}:`);
|
|
933
|
-
for (const [name, env] of entries) {
|
|
934
|
-
lines.push(` ${name} ${env.hasValue ? "set" : "missing"} ${env.optional ? "optional" : "required"} ${env.description}`);
|
|
935
|
-
}
|
|
1003
|
+
for (const [name, env] of Object.entries(data)) {
|
|
1004
|
+
lines.push(`${name} ${env.hasValue ? "set" : "missing"} ${env.optional ? "optional" : "required"} ${env.description}`);
|
|
936
1005
|
}
|
|
937
1006
|
return lines.length > 0 ? `${lines.join("\n")}
|
|
938
1007
|
` : "No environment variables declared.\n";
|
|
@@ -944,7 +1013,7 @@ function renderEnvSetResult(result) {
|
|
|
944
1013
|
if (result.skippedExisting.length > 0) lines.push(`Skipped ${result.skippedExisting.length} existing value(s).`);
|
|
945
1014
|
if (result.skippedEmpty.length > 0) lines.push(`Skipped ${result.skippedEmpty.length} empty value(s).`);
|
|
946
1015
|
if (result.updated.length > 0) {
|
|
947
|
-
lines.push(...result.updated.map(({
|
|
1016
|
+
lines.push(...result.updated.map(({ name }) => ` ${name}`));
|
|
948
1017
|
}
|
|
949
1018
|
return `${lines.join("\n")}
|
|
950
1019
|
`;
|
|
@@ -957,10 +1026,10 @@ async function setBranchEnvs(client, projectRef, branchName, args) {
|
|
|
957
1026
|
if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(name)) {
|
|
958
1027
|
throw new Error(`Invalid environment variable name in dotenv file: ${name}`);
|
|
959
1028
|
}
|
|
960
|
-
return {
|
|
1029
|
+
return { name, value };
|
|
961
1030
|
}) : options.assignments.map(parseEnvAssignment);
|
|
962
1031
|
const uniqueAssignments = /* @__PURE__ */ new Map();
|
|
963
|
-
for (const assignment of assignments) uniqueAssignments.set(
|
|
1032
|
+
for (const assignment of assignments) uniqueAssignments.set(assignment.name, assignment);
|
|
964
1033
|
const update = {};
|
|
965
1034
|
const result = {
|
|
966
1035
|
success: true,
|
|
@@ -973,12 +1042,12 @@ async function setBranchEnvs(client, projectRef, branchName, args) {
|
|
|
973
1042
|
const missingDescriptions = [];
|
|
974
1043
|
const importedDescription = options.fromEnvFile ? `Imported from ${import_node_path.default.basename(options.fromEnvFile)}.` : void 0;
|
|
975
1044
|
for (const assignment of uniqueAssignments.values()) {
|
|
976
|
-
const identity = {
|
|
1045
|
+
const identity = { name: assignment.name };
|
|
977
1046
|
if (!options.includeEmpty && assignment.value.length === 0) {
|
|
978
1047
|
result.skippedEmpty.push(identity);
|
|
979
1048
|
continue;
|
|
980
1049
|
}
|
|
981
|
-
const existing = current[assignment.
|
|
1050
|
+
const existing = current[assignment.name];
|
|
982
1051
|
if (options.onlyMissing && existing?.value !== null && existing?.value !== void 0) {
|
|
983
1052
|
result.skippedExisting.push(identity);
|
|
984
1053
|
continue;
|
|
@@ -988,9 +1057,8 @@ async function setBranchEnvs(client, projectRef, branchName, args) {
|
|
|
988
1057
|
missingDescriptions.push(identity);
|
|
989
1058
|
continue;
|
|
990
1059
|
}
|
|
991
|
-
const targetUpdate = update[assignment.target] ??= {};
|
|
992
1060
|
const optional = options.optional ?? existing?.optional;
|
|
993
|
-
|
|
1061
|
+
update[assignment.name] = {
|
|
994
1062
|
description,
|
|
995
1063
|
value: assignment.value,
|
|
996
1064
|
...optional === void 0 ? {} : { optional }
|
|
@@ -998,9 +1066,7 @@ async function setBranchEnvs(client, projectRef, branchName, args) {
|
|
|
998
1066
|
result.updated.push(identity);
|
|
999
1067
|
}
|
|
1000
1068
|
if (missingDescriptions.length > 0) {
|
|
1001
|
-
throw new Error(
|
|
1002
|
-
`--description is required when declaring new envs with -e: ${missingDescriptions.map(({ target, name }) => `${target}/${name}`).join(", ")}`
|
|
1003
|
-
);
|
|
1069
|
+
throw new Error(`--description is required when declaring new envs with -e: ${missingDescriptions.map(({ name }) => name).join(", ")}`);
|
|
1004
1070
|
}
|
|
1005
1071
|
if (result.updated.length > 0) {
|
|
1006
1072
|
await client.projects.env.update(project.id, branchName, update);
|
|
@@ -1165,6 +1231,17 @@ async function executeR5dctlCommand(client, json, args) {
|
|
|
1165
1231
|
write(result, "API key revoked.\n");
|
|
1166
1232
|
return;
|
|
1167
1233
|
}
|
|
1234
|
+
if (first === "ps" && second === "list") {
|
|
1235
|
+
const processes = await client.processes.list(parsePsListArgs(args.slice(2)));
|
|
1236
|
+
write(processes, renderProcessList(processes));
|
|
1237
|
+
return;
|
|
1238
|
+
}
|
|
1239
|
+
if (first === "ps" && second === "stop") {
|
|
1240
|
+
const process2 = await client.processes.stop(requireValue(args[2], "Missing process run id"));
|
|
1241
|
+
write(process2, `Stopped process ${process2.runId}: ${process2.command}
|
|
1242
|
+
`);
|
|
1243
|
+
return;
|
|
1244
|
+
}
|
|
1168
1245
|
if (first === "projects" && second === "list" || first === "get" && second === "projects") {
|
|
1169
1246
|
const projects = await client.projects.list();
|
|
1170
1247
|
write(projects, renderProjectList(projects));
|
|
@@ -1360,6 +1437,12 @@ function resolveCommandExecution(options, rest) {
|
|
|
1360
1437
|
throw new Error("Missing command");
|
|
1361
1438
|
}
|
|
1362
1439
|
const commandArgs = normalizedRest.slice(1);
|
|
1440
|
+
if (command === "ps" && commandArgs.length === 0) {
|
|
1441
|
+
return {
|
|
1442
|
+
kind: "cli-help",
|
|
1443
|
+
text: PS_HELP_TEXT
|
|
1444
|
+
};
|
|
1445
|
+
}
|
|
1363
1446
|
if (trailingHelp) {
|
|
1364
1447
|
const cliOnlyHelp = findCliOnlyCommandHelp(normalizedRest);
|
|
1365
1448
|
if (cliOnlyHelp) {
|
|
@@ -1433,6 +1516,36 @@ function resolveCommandExecution(options, rest) {
|
|
|
1433
1516
|
}
|
|
1434
1517
|
throw new Error("Unknown auth command");
|
|
1435
1518
|
}
|
|
1519
|
+
if (command === "ps") {
|
|
1520
|
+
const subcommand = commandArgs[0];
|
|
1521
|
+
if (subcommand === "list") {
|
|
1522
|
+
const commandFilters = parsePsListArgs(commandArgs.slice(1));
|
|
1523
|
+
const filters = {
|
|
1524
|
+
...commandFilters,
|
|
1525
|
+
...options.project ? { project: options.project } : {},
|
|
1526
|
+
...options.branch ? { branch: options.branch } : {},
|
|
1527
|
+
...options.session ? { session: options.session } : {}
|
|
1528
|
+
};
|
|
1529
|
+
const pluginArgs = ["ps", "list"];
|
|
1530
|
+
for (const [flag, value] of [
|
|
1531
|
+
["--project", filters.project],
|
|
1532
|
+
["--branch", filters.branch],
|
|
1533
|
+
["--session", filters.session],
|
|
1534
|
+
["--worker", filters.worker]
|
|
1535
|
+
]) {
|
|
1536
|
+
if (value) pluginArgs.push(flag, value);
|
|
1537
|
+
}
|
|
1538
|
+
return { kind: "plugin", pluginArgs };
|
|
1539
|
+
}
|
|
1540
|
+
if (subcommand === "stop") {
|
|
1541
|
+
const runId = requireValue(commandArgs[1], "Missing process run id");
|
|
1542
|
+
if (commandArgs.length > 2) {
|
|
1543
|
+
throw new Error(`Unexpected ps stop value: ${commandArgs[2]}`);
|
|
1544
|
+
}
|
|
1545
|
+
return { kind: "plugin", pluginArgs: ["ps", "stop", runId] };
|
|
1546
|
+
}
|
|
1547
|
+
throw new Error(`Unknown ps command: ${subcommand ?? ""}`.trim());
|
|
1548
|
+
}
|
|
1436
1549
|
if (command === "get") {
|
|
1437
1550
|
const target = commandArgs[0];
|
|
1438
1551
|
if (target === "projects") {
|
|
@@ -1819,6 +1932,7 @@ async function main(argv = process.argv.slice(2)) {
|
|
|
1819
1932
|
}
|
|
1820
1933
|
// Annotate the CommonJS export names for ESM import in node:
|
|
1821
1934
|
0 && (module.exports = {
|
|
1935
|
+
formatProcessAge,
|
|
1822
1936
|
getCliHelpText,
|
|
1823
1937
|
getR5dctlVersion,
|
|
1824
1938
|
main,
|
|
@@ -1827,10 +1941,12 @@ async function main(argv = process.argv.slice(2)) {
|
|
|
1827
1941
|
parseEnvFlags,
|
|
1828
1942
|
parseGlobalArgs,
|
|
1829
1943
|
parsePromptArgs,
|
|
1944
|
+
parsePsListArgs,
|
|
1830
1945
|
parseSetEnvArgs,
|
|
1831
1946
|
parseShellArgs,
|
|
1832
1947
|
readDotenvFile,
|
|
1833
1948
|
renderConversationResponse,
|
|
1949
|
+
renderProcessList,
|
|
1834
1950
|
resolveCommandExecution,
|
|
1835
1951
|
runR5dctlCli,
|
|
1836
1952
|
summarizeEnvData
|
package/dist/cjs/package.json
CHANGED
package/dist/mjs/cli.mjs
CHANGED
|
@@ -58,6 +58,15 @@ const CLI_ONLY_COMMAND_HELP = [
|
|
|
58
58
|
usage: "-p <project> -b <branch> shell [-c <command>]"
|
|
59
59
|
}
|
|
60
60
|
];
|
|
61
|
+
const PS_HELP_TEXT = [
|
|
62
|
+
"Usage:",
|
|
63
|
+
" r5dctl ps list [--worker <label>]",
|
|
64
|
+
" r5dctl ps stop <run-id>",
|
|
65
|
+
"",
|
|
66
|
+
"List or gracefully stop active r5d-managed worker processes.",
|
|
67
|
+
"Use -p/--project, -b/--branch, or -s/--session to filter ps list.",
|
|
68
|
+
""
|
|
69
|
+
].join("\n");
|
|
61
70
|
const SHARED_HELP_ENTRIES = [
|
|
62
71
|
{ section: "auth", usage: "auth status", description: "Show the current authenticated user and credential source." },
|
|
63
72
|
{ section: "auth", usage: "auth logout", description: "Revoke the current credential and clear saved auth." },
|
|
@@ -81,12 +90,12 @@ const SHARED_HELP_ENTRIES = [
|
|
|
81
90
|
},
|
|
82
91
|
{
|
|
83
92
|
section: "envs",
|
|
84
|
-
usage: "-p <project> -b <branch> set envs --from-env-file <path> [--
|
|
93
|
+
usage: "-p <project> -b <branch> set envs --from-env-file <path> [--only-missing]",
|
|
85
94
|
description: "Securely import branch envs from a dotenv file without putting values in command arguments."
|
|
86
95
|
},
|
|
87
96
|
{
|
|
88
97
|
section: "envs",
|
|
89
|
-
usage: "-p <project> -b <branch> set envs -e
|
|
98
|
+
usage: "-p <project> -b <branch> set envs -e KEY=value [--description <text>] [--only-missing]",
|
|
90
99
|
description: "Set one or more branch envs directly."
|
|
91
100
|
},
|
|
92
101
|
{
|
|
@@ -119,9 +128,15 @@ const SHARED_HELP_ENTRIES = [
|
|
|
119
128
|
},
|
|
120
129
|
{
|
|
121
130
|
section: "sessions",
|
|
122
|
-
usage: "-s <session-id> apply-required-envs -e
|
|
123
|
-
description: "Apply required
|
|
131
|
+
usage: "-s <session-id> apply-required-envs -e KEY=value",
|
|
132
|
+
description: "Apply required environment variable values."
|
|
124
133
|
},
|
|
134
|
+
{
|
|
135
|
+
section: "processes",
|
|
136
|
+
usage: "ps list [--worker <label>]",
|
|
137
|
+
description: "List active r5d-managed worker processes; global project, branch, and session filters apply."
|
|
138
|
+
},
|
|
139
|
+
{ section: "processes", usage: "ps stop <run-id>", description: "Gracefully stop an active process with SIGTERM." },
|
|
125
140
|
{
|
|
126
141
|
section: "agents",
|
|
127
142
|
usage: '-p <project> start-agent <src-branch> <agent-type> "<prompt>"',
|
|
@@ -137,13 +152,24 @@ const SHARED_HELP_ENTRIES = [
|
|
|
137
152
|
{ section: "merges", usage: "-p <project> continue-merge <target-branch>", description: "Commit a resolved merge." },
|
|
138
153
|
{ section: "merges", usage: "-p <project> abort-merge <target-branch>", description: "Abort an in-progress merge." }
|
|
139
154
|
];
|
|
140
|
-
const HELP_SECTION_ORDER = [
|
|
155
|
+
const HELP_SECTION_ORDER = [
|
|
156
|
+
"auth",
|
|
157
|
+
"projects",
|
|
158
|
+
"branches",
|
|
159
|
+
"envs",
|
|
160
|
+
"sessions",
|
|
161
|
+
"processes",
|
|
162
|
+
"shell",
|
|
163
|
+
"agents",
|
|
164
|
+
"merges"
|
|
165
|
+
];
|
|
141
166
|
const HELP_SECTION_TITLES = {
|
|
142
167
|
auth: "Auth",
|
|
143
168
|
projects: "Projects",
|
|
144
169
|
branches: "Branches",
|
|
145
170
|
envs: "Environment variables",
|
|
146
171
|
sessions: "Sessions",
|
|
172
|
+
processes: "Processes",
|
|
147
173
|
shell: "Shell",
|
|
148
174
|
agents: "Agents",
|
|
149
175
|
merges: "Merges"
|
|
@@ -244,7 +270,7 @@ function renderCliOnlyCommandHelp(entry) {
|
|
|
244
270
|
}
|
|
245
271
|
function renderSharedCommandHelp(pathSegments) {
|
|
246
272
|
const entry = SHARED_HELP_ENTRIES.find((candidate) => {
|
|
247
|
-
const usageSegments = candidate.usage.split(" ").filter((segment) => !segment.startsWith("-") && !segment.startsWith("<") && !segment.startsWith("["));
|
|
273
|
+
const usageSegments = candidate.usage.split(" ").map((segment) => segment.replace(/^\[/, "").replace(/\]$/, "")).filter((segment) => !segment.startsWith("-") && !segment.startsWith("<") && !segment.startsWith("["));
|
|
248
274
|
return usageSegments.length <= pathSegments.length && usageSegments.every((segment, index) => segment === pathSegments[index]);
|
|
249
275
|
});
|
|
250
276
|
if (!entry) {
|
|
@@ -480,33 +506,24 @@ function parseAnswerFlags(args) {
|
|
|
480
506
|
function validateEnvAssignment(value) {
|
|
481
507
|
const equalsIndex = value.indexOf("=");
|
|
482
508
|
if (equalsIndex <= 0) {
|
|
483
|
-
throw new Error(`Invalid env assignment '${value}'. Expected
|
|
484
|
-
}
|
|
485
|
-
const assignment = value.slice(0, equalsIndex);
|
|
486
|
-
const slashIndex = assignment.indexOf("/");
|
|
487
|
-
if (slashIndex <= 0) {
|
|
488
|
-
throw new Error(`Invalid env assignment '${value}'. Expected backend/KEY=value or frontend/KEY=value`);
|
|
509
|
+
throw new Error(`Invalid env assignment '${value}'. Expected KEY=value`);
|
|
489
510
|
}
|
|
490
|
-
const
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
throw new Error(`Invalid env assignment '${value}'. Expected backend/KEY=value or frontend/KEY=value`);
|
|
511
|
+
const key = value.slice(0, equalsIndex);
|
|
512
|
+
if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(key)) {
|
|
513
|
+
throw new Error(`Invalid env assignment '${value}'. Expected KEY=value`);
|
|
494
514
|
}
|
|
495
515
|
}
|
|
496
516
|
function parseEnvAssignment(value) {
|
|
497
517
|
validateEnvAssignment(value);
|
|
498
518
|
const equalsIndex = value.indexOf("=");
|
|
499
|
-
const slashIndex = value.indexOf("/");
|
|
500
519
|
return {
|
|
501
|
-
|
|
502
|
-
name: value.slice(slashIndex + 1, equalsIndex),
|
|
520
|
+
name: value.slice(0, equalsIndex),
|
|
503
521
|
value: value.slice(equalsIndex + 1)
|
|
504
522
|
};
|
|
505
523
|
}
|
|
506
524
|
function parseSetEnvArgs(args) {
|
|
507
525
|
const parsed = {
|
|
508
526
|
assignments: [],
|
|
509
|
-
target: "backend",
|
|
510
527
|
onlyMissing: false,
|
|
511
528
|
includeEmpty: false
|
|
512
529
|
};
|
|
@@ -536,23 +553,6 @@ function parseSetEnvArgs(args) {
|
|
|
536
553
|
parsed.fromEnvFile = requireValue(inlineFile, "Missing value for --from-env-file");
|
|
537
554
|
continue;
|
|
538
555
|
}
|
|
539
|
-
if (arg === "--target") {
|
|
540
|
-
const target = requireValue(args[index + 1], "Missing value for --target");
|
|
541
|
-
if (target !== "backend" && target !== "frontend") {
|
|
542
|
-
throw new Error("Invalid --target. Expected backend or frontend");
|
|
543
|
-
}
|
|
544
|
-
parsed.target = target;
|
|
545
|
-
index += 1;
|
|
546
|
-
continue;
|
|
547
|
-
}
|
|
548
|
-
const inlineTarget = parseLongOptionWithEquals(arg, "--target");
|
|
549
|
-
if (inlineTarget !== void 0) {
|
|
550
|
-
if (inlineTarget !== "backend" && inlineTarget !== "frontend") {
|
|
551
|
-
throw new Error("Invalid --target. Expected backend or frontend");
|
|
552
|
-
}
|
|
553
|
-
parsed.target = inlineTarget;
|
|
554
|
-
continue;
|
|
555
|
-
}
|
|
556
556
|
if (arg === "--description") {
|
|
557
557
|
parsed.description = requireValue(args[index + 1], "Missing value for --description");
|
|
558
558
|
index += 1;
|
|
@@ -626,7 +626,7 @@ function parseEnvFlags(args) {
|
|
|
626
626
|
if (arg.startsWith("-")) {
|
|
627
627
|
throw new Error(`Unknown env flag: ${arg}`);
|
|
628
628
|
}
|
|
629
|
-
throw new Error(`Unexpected value '${arg}'. Use -e
|
|
629
|
+
throw new Error(`Unexpected value '${arg}'. Use -e KEY=value.`);
|
|
630
630
|
}
|
|
631
631
|
if (envs.length === 0) {
|
|
632
632
|
throw new Error("At least one -e/--env value is required.");
|
|
@@ -720,6 +720,40 @@ function parseShellArgs(args) {
|
|
|
720
720
|
}
|
|
721
721
|
return command === void 0 ? {} : { command };
|
|
722
722
|
}
|
|
723
|
+
function parsePsListArgs(args) {
|
|
724
|
+
const input = {};
|
|
725
|
+
const definitions = [
|
|
726
|
+
{ flag: "--project", key: "project" },
|
|
727
|
+
{ flag: "--branch", key: "branch" },
|
|
728
|
+
{ flag: "--session", key: "session" },
|
|
729
|
+
{ flag: "--worker", key: "worker" }
|
|
730
|
+
];
|
|
731
|
+
const assign = (key, value, flag) => {
|
|
732
|
+
if (input[key] !== void 0) {
|
|
733
|
+
throw new Error(`Use only one ${flag} value`);
|
|
734
|
+
}
|
|
735
|
+
input[key] = requireValue(value, `Missing value for ${flag}`);
|
|
736
|
+
};
|
|
737
|
+
for (let index = 0; index < args.length; index += 1) {
|
|
738
|
+
const arg = args[index];
|
|
739
|
+
if (!arg) continue;
|
|
740
|
+
const definition = definitions.find(({ flag }) => arg === flag || arg.startsWith(`${flag}=`));
|
|
741
|
+
if (!definition) {
|
|
742
|
+
throw new Error(arg.startsWith("-") ? `Unknown ps list flag: ${arg}` : `Unexpected ps list value: ${arg}`);
|
|
743
|
+
}
|
|
744
|
+
if (arg === definition.flag) {
|
|
745
|
+
assign(definition.key, requireValue(args[index + 1], `Missing value for ${definition.flag}`), definition.flag);
|
|
746
|
+
index += 1;
|
|
747
|
+
continue;
|
|
748
|
+
}
|
|
749
|
+
assign(
|
|
750
|
+
definition.key,
|
|
751
|
+
requireValue(parseLongOptionWithEquals(arg, definition.flag), `Missing value for ${definition.flag}`),
|
|
752
|
+
definition.flag
|
|
753
|
+
);
|
|
754
|
+
}
|
|
755
|
+
return input;
|
|
756
|
+
}
|
|
723
757
|
async function openInBrowser(url) {
|
|
724
758
|
if (process.platform === "darwin") {
|
|
725
759
|
spawn("open", [url], { stdio: "ignore", detached: true }).unref();
|
|
@@ -861,34 +895,66 @@ function renderSessionList(sessions) {
|
|
|
861
895
|
function renderSessionDescription(session) {
|
|
862
896
|
return [`Session: ${session.id}`, `Project: ${session.projectPath}`, `Branch: ${session.branchName}`].join("\n") + "\n";
|
|
863
897
|
}
|
|
898
|
+
function formatProcessAge(startedAt, nowMs = Date.now()) {
|
|
899
|
+
const startedAtMs = Date.parse(startedAt);
|
|
900
|
+
if (!Number.isFinite(startedAtMs)) {
|
|
901
|
+
return "unknown";
|
|
902
|
+
}
|
|
903
|
+
const totalSeconds = Math.max(0, Math.floor((nowMs - startedAtMs) / 1e3));
|
|
904
|
+
if (totalSeconds < 60) {
|
|
905
|
+
return `${totalSeconds}s`;
|
|
906
|
+
}
|
|
907
|
+
if (totalSeconds < 60 * 60) {
|
|
908
|
+
const minutes = Math.floor(totalSeconds / 60);
|
|
909
|
+
const seconds = totalSeconds % 60;
|
|
910
|
+
return `${minutes}m ${String(seconds).padStart(2, "0")}s`;
|
|
911
|
+
}
|
|
912
|
+
if (totalSeconds < 24 * 60 * 60) {
|
|
913
|
+
const hours2 = Math.floor(totalSeconds / (60 * 60));
|
|
914
|
+
const minutes = Math.floor(totalSeconds % (60 * 60) / 60);
|
|
915
|
+
return `${hours2}h ${String(minutes).padStart(2, "0")}m`;
|
|
916
|
+
}
|
|
917
|
+
const days = Math.floor(totalSeconds / (24 * 60 * 60));
|
|
918
|
+
const hours = Math.floor(totalSeconds % (24 * 60 * 60) / (60 * 60));
|
|
919
|
+
return `${days}d ${hours}h`;
|
|
920
|
+
}
|
|
921
|
+
function renderProcessList(processes, nowMs = Date.now()) {
|
|
922
|
+
if (processes.length === 0) {
|
|
923
|
+
return "No active processes.\n";
|
|
924
|
+
}
|
|
925
|
+
const headers = ["RUN ID", "STATUS", "AGE", "WORKER", "PROJECT", "BRANCH", "SESSION", "COMMAND"];
|
|
926
|
+
const rows = processes.map((process2) => [
|
|
927
|
+
process2.runId,
|
|
928
|
+
process2.status,
|
|
929
|
+
formatProcessAge(process2.startedAt, nowMs),
|
|
930
|
+
process2.workerLabel,
|
|
931
|
+
process2.projectPath,
|
|
932
|
+
process2.branchName,
|
|
933
|
+
process2.sessionId,
|
|
934
|
+
process2.command
|
|
935
|
+
]);
|
|
936
|
+
const widths = headers.map((header, column) => Math.max(header.length, ...rows.map((row) => row[column]?.length ?? 0)));
|
|
937
|
+
const formatRow = (row) => row.map((value, column) => column === row.length - 1 ? value : value.padEnd(widths[column] ?? value.length)).join(" ");
|
|
938
|
+
const separator = widths.map((width) => "-".repeat(width)).join(" ");
|
|
939
|
+
return `${[formatRow(headers), separator, ...rows.map(formatRow)].join("\n")}
|
|
940
|
+
`;
|
|
941
|
+
}
|
|
864
942
|
function summarizeEnvData(data) {
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
hasValue: env.value !== null
|
|
876
|
-
}
|
|
877
|
-
])
|
|
878
|
-
);
|
|
879
|
-
}
|
|
880
|
-
return summary;
|
|
943
|
+
return Object.fromEntries(
|
|
944
|
+
Object.entries(data).sort(([left], [right]) => left.localeCompare(right)).map(([name, env]) => [
|
|
945
|
+
name,
|
|
946
|
+
{
|
|
947
|
+
optional: env.optional ?? false,
|
|
948
|
+
description: env.description,
|
|
949
|
+
hasValue: env.value !== null
|
|
950
|
+
}
|
|
951
|
+
])
|
|
952
|
+
);
|
|
881
953
|
}
|
|
882
954
|
function renderEnvSummary(data) {
|
|
883
955
|
const lines = [];
|
|
884
|
-
for (const
|
|
885
|
-
|
|
886
|
-
if (entries.length === 0) continue;
|
|
887
|
-
if (lines.length > 0) lines.push("");
|
|
888
|
-
lines.push(`${target}:`);
|
|
889
|
-
for (const [name, env] of entries) {
|
|
890
|
-
lines.push(` ${name} ${env.hasValue ? "set" : "missing"} ${env.optional ? "optional" : "required"} ${env.description}`);
|
|
891
|
-
}
|
|
956
|
+
for (const [name, env] of Object.entries(data)) {
|
|
957
|
+
lines.push(`${name} ${env.hasValue ? "set" : "missing"} ${env.optional ? "optional" : "required"} ${env.description}`);
|
|
892
958
|
}
|
|
893
959
|
return lines.length > 0 ? `${lines.join("\n")}
|
|
894
960
|
` : "No environment variables declared.\n";
|
|
@@ -900,7 +966,7 @@ function renderEnvSetResult(result) {
|
|
|
900
966
|
if (result.skippedExisting.length > 0) lines.push(`Skipped ${result.skippedExisting.length} existing value(s).`);
|
|
901
967
|
if (result.skippedEmpty.length > 0) lines.push(`Skipped ${result.skippedEmpty.length} empty value(s).`);
|
|
902
968
|
if (result.updated.length > 0) {
|
|
903
|
-
lines.push(...result.updated.map(({
|
|
969
|
+
lines.push(...result.updated.map(({ name }) => ` ${name}`));
|
|
904
970
|
}
|
|
905
971
|
return `${lines.join("\n")}
|
|
906
972
|
`;
|
|
@@ -913,10 +979,10 @@ async function setBranchEnvs(client, projectRef, branchName, args) {
|
|
|
913
979
|
if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(name)) {
|
|
914
980
|
throw new Error(`Invalid environment variable name in dotenv file: ${name}`);
|
|
915
981
|
}
|
|
916
|
-
return {
|
|
982
|
+
return { name, value };
|
|
917
983
|
}) : options.assignments.map(parseEnvAssignment);
|
|
918
984
|
const uniqueAssignments = /* @__PURE__ */ new Map();
|
|
919
|
-
for (const assignment of assignments) uniqueAssignments.set(
|
|
985
|
+
for (const assignment of assignments) uniqueAssignments.set(assignment.name, assignment);
|
|
920
986
|
const update = {};
|
|
921
987
|
const result = {
|
|
922
988
|
success: true,
|
|
@@ -929,12 +995,12 @@ async function setBranchEnvs(client, projectRef, branchName, args) {
|
|
|
929
995
|
const missingDescriptions = [];
|
|
930
996
|
const importedDescription = options.fromEnvFile ? `Imported from ${path.basename(options.fromEnvFile)}.` : void 0;
|
|
931
997
|
for (const assignment of uniqueAssignments.values()) {
|
|
932
|
-
const identity = {
|
|
998
|
+
const identity = { name: assignment.name };
|
|
933
999
|
if (!options.includeEmpty && assignment.value.length === 0) {
|
|
934
1000
|
result.skippedEmpty.push(identity);
|
|
935
1001
|
continue;
|
|
936
1002
|
}
|
|
937
|
-
const existing = current[assignment.
|
|
1003
|
+
const existing = current[assignment.name];
|
|
938
1004
|
if (options.onlyMissing && existing?.value !== null && existing?.value !== void 0) {
|
|
939
1005
|
result.skippedExisting.push(identity);
|
|
940
1006
|
continue;
|
|
@@ -944,9 +1010,8 @@ async function setBranchEnvs(client, projectRef, branchName, args) {
|
|
|
944
1010
|
missingDescriptions.push(identity);
|
|
945
1011
|
continue;
|
|
946
1012
|
}
|
|
947
|
-
const targetUpdate = update[assignment.target] ??= {};
|
|
948
1013
|
const optional = options.optional ?? existing?.optional;
|
|
949
|
-
|
|
1014
|
+
update[assignment.name] = {
|
|
950
1015
|
description,
|
|
951
1016
|
value: assignment.value,
|
|
952
1017
|
...optional === void 0 ? {} : { optional }
|
|
@@ -954,9 +1019,7 @@ async function setBranchEnvs(client, projectRef, branchName, args) {
|
|
|
954
1019
|
result.updated.push(identity);
|
|
955
1020
|
}
|
|
956
1021
|
if (missingDescriptions.length > 0) {
|
|
957
|
-
throw new Error(
|
|
958
|
-
`--description is required when declaring new envs with -e: ${missingDescriptions.map(({ target, name }) => `${target}/${name}`).join(", ")}`
|
|
959
|
-
);
|
|
1022
|
+
throw new Error(`--description is required when declaring new envs with -e: ${missingDescriptions.map(({ name }) => name).join(", ")}`);
|
|
960
1023
|
}
|
|
961
1024
|
if (result.updated.length > 0) {
|
|
962
1025
|
await client.projects.env.update(project.id, branchName, update);
|
|
@@ -1121,6 +1184,17 @@ async function executeR5dctlCommand(client, json, args) {
|
|
|
1121
1184
|
write(result, "API key revoked.\n");
|
|
1122
1185
|
return;
|
|
1123
1186
|
}
|
|
1187
|
+
if (first === "ps" && second === "list") {
|
|
1188
|
+
const processes = await client.processes.list(parsePsListArgs(args.slice(2)));
|
|
1189
|
+
write(processes, renderProcessList(processes));
|
|
1190
|
+
return;
|
|
1191
|
+
}
|
|
1192
|
+
if (first === "ps" && second === "stop") {
|
|
1193
|
+
const process2 = await client.processes.stop(requireValue(args[2], "Missing process run id"));
|
|
1194
|
+
write(process2, `Stopped process ${process2.runId}: ${process2.command}
|
|
1195
|
+
`);
|
|
1196
|
+
return;
|
|
1197
|
+
}
|
|
1124
1198
|
if (first === "projects" && second === "list" || first === "get" && second === "projects") {
|
|
1125
1199
|
const projects = await client.projects.list();
|
|
1126
1200
|
write(projects, renderProjectList(projects));
|
|
@@ -1316,6 +1390,12 @@ function resolveCommandExecution(options, rest) {
|
|
|
1316
1390
|
throw new Error("Missing command");
|
|
1317
1391
|
}
|
|
1318
1392
|
const commandArgs = normalizedRest.slice(1);
|
|
1393
|
+
if (command === "ps" && commandArgs.length === 0) {
|
|
1394
|
+
return {
|
|
1395
|
+
kind: "cli-help",
|
|
1396
|
+
text: PS_HELP_TEXT
|
|
1397
|
+
};
|
|
1398
|
+
}
|
|
1319
1399
|
if (trailingHelp) {
|
|
1320
1400
|
const cliOnlyHelp = findCliOnlyCommandHelp(normalizedRest);
|
|
1321
1401
|
if (cliOnlyHelp) {
|
|
@@ -1389,6 +1469,36 @@ function resolveCommandExecution(options, rest) {
|
|
|
1389
1469
|
}
|
|
1390
1470
|
throw new Error("Unknown auth command");
|
|
1391
1471
|
}
|
|
1472
|
+
if (command === "ps") {
|
|
1473
|
+
const subcommand = commandArgs[0];
|
|
1474
|
+
if (subcommand === "list") {
|
|
1475
|
+
const commandFilters = parsePsListArgs(commandArgs.slice(1));
|
|
1476
|
+
const filters = {
|
|
1477
|
+
...commandFilters,
|
|
1478
|
+
...options.project ? { project: options.project } : {},
|
|
1479
|
+
...options.branch ? { branch: options.branch } : {},
|
|
1480
|
+
...options.session ? { session: options.session } : {}
|
|
1481
|
+
};
|
|
1482
|
+
const pluginArgs = ["ps", "list"];
|
|
1483
|
+
for (const [flag, value] of [
|
|
1484
|
+
["--project", filters.project],
|
|
1485
|
+
["--branch", filters.branch],
|
|
1486
|
+
["--session", filters.session],
|
|
1487
|
+
["--worker", filters.worker]
|
|
1488
|
+
]) {
|
|
1489
|
+
if (value) pluginArgs.push(flag, value);
|
|
1490
|
+
}
|
|
1491
|
+
return { kind: "plugin", pluginArgs };
|
|
1492
|
+
}
|
|
1493
|
+
if (subcommand === "stop") {
|
|
1494
|
+
const runId = requireValue(commandArgs[1], "Missing process run id");
|
|
1495
|
+
if (commandArgs.length > 2) {
|
|
1496
|
+
throw new Error(`Unexpected ps stop value: ${commandArgs[2]}`);
|
|
1497
|
+
}
|
|
1498
|
+
return { kind: "plugin", pluginArgs: ["ps", "stop", runId] };
|
|
1499
|
+
}
|
|
1500
|
+
throw new Error(`Unknown ps command: ${subcommand ?? ""}`.trim());
|
|
1501
|
+
}
|
|
1392
1502
|
if (command === "get") {
|
|
1393
1503
|
const target = commandArgs[0];
|
|
1394
1504
|
if (target === "projects") {
|
|
@@ -1774,6 +1884,7 @@ async function main(argv = process.argv.slice(2)) {
|
|
|
1774
1884
|
}
|
|
1775
1885
|
}
|
|
1776
1886
|
export {
|
|
1887
|
+
formatProcessAge,
|
|
1777
1888
|
getCliHelpText,
|
|
1778
1889
|
getR5dctlVersion,
|
|
1779
1890
|
main,
|
|
@@ -1782,10 +1893,12 @@ export {
|
|
|
1782
1893
|
parseEnvFlags,
|
|
1783
1894
|
parseGlobalArgs,
|
|
1784
1895
|
parsePromptArgs,
|
|
1896
|
+
parsePsListArgs,
|
|
1785
1897
|
parseSetEnvArgs,
|
|
1786
1898
|
parseShellArgs,
|
|
1787
1899
|
readDotenvFile,
|
|
1788
1900
|
renderConversationResponse,
|
|
1901
|
+
renderProcessList,
|
|
1789
1902
|
resolveCommandExecution,
|
|
1790
1903
|
runR5dctlCli,
|
|
1791
1904
|
summarizeEnvData
|
package/dist/mjs/package.json
CHANGED
package/dist/types/cli.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type R5dctlConversationRenderOptions, type R5dctlConversationResponse, type R5dctlEnvData, type
|
|
1
|
+
import { type R5dctlConversationRenderOptions, type R5dctlConversationResponse, type R5dctlEnvData, type R5dctlProcessListInput, type R5dctlProcessRun, type ChatMode, type ModelTier } from "@ricsam/r5d-api";
|
|
2
2
|
export type GlobalOptions = {
|
|
3
3
|
baseUrl?: string;
|
|
4
4
|
json: boolean;
|
|
@@ -44,7 +44,6 @@ export declare function parseAnswerFlags(args: string[]): string[];
|
|
|
44
44
|
export type SetEnvOptions = {
|
|
45
45
|
assignments: string[];
|
|
46
46
|
fromEnvFile?: string;
|
|
47
|
-
target: R5dctlEnvTarget;
|
|
48
47
|
description?: string;
|
|
49
48
|
optional?: boolean;
|
|
50
49
|
onlyMissing: boolean;
|
|
@@ -61,11 +60,14 @@ export declare function parsePromptArgs(args: string[]): {
|
|
|
61
60
|
export declare function parseShellArgs(args: string[]): {
|
|
62
61
|
command?: string;
|
|
63
62
|
};
|
|
64
|
-
export
|
|
63
|
+
export declare function parsePsListArgs(args: string[]): R5dctlProcessListInput;
|
|
64
|
+
export declare function formatProcessAge(startedAt: string, nowMs?: number): string;
|
|
65
|
+
export declare function renderProcessList(processes: R5dctlProcessRun[], nowMs?: number): string;
|
|
66
|
+
export type EnvSummaryData = Record<string, {
|
|
65
67
|
optional: boolean;
|
|
66
68
|
description: string;
|
|
67
69
|
hasValue: boolean;
|
|
68
|
-
}
|
|
70
|
+
}>;
|
|
69
71
|
export declare function summarizeEnvData(data: R5dctlEnvData): EnvSummaryData;
|
|
70
72
|
export declare function parseConversationRenderArgs(args: string[]): ConversationRenderOptions;
|
|
71
73
|
export declare function renderConversationResponse(read: R5dctlConversationResponse): string;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ricsam/r5dctl",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.24",
|
|
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.24",
|
|
30
30
|
"dotenv": "^17",
|
|
31
31
|
"ws": "^8.18.3"
|
|
32
32
|
},
|