@ricsam/r5dctl 0.0.22 → 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 +14 -3
- package/dist/cjs/cli.cjs +263 -81
- package/dist/cjs/package.json +1 -1
- package/dist/cjs/shell.cjs +245 -0
- package/dist/mjs/cli.mjs +259 -81
- package/dist/mjs/package.json +1 -1
- package/dist/mjs/shell.mjs +210 -0
- package/dist/types/cli.d.ts +12 -4
- package/dist/types/shell.d.ts +26 -0
- package/package.json +4 -3
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,11 +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
|
+
r5dctl -p <project> -b <branch> shell
|
|
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>
|
|
70
77
|
```
|
|
71
78
|
|
|
72
|
-
`
|
|
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.
|
|
80
|
+
|
|
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.
|
|
73
84
|
|
|
74
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.
|
|
75
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,9 +37,12 @@ __export(cli_exports, {
|
|
|
36
37
|
parseEnvFlags: () => parseEnvFlags,
|
|
37
38
|
parseGlobalArgs: () => parseGlobalArgs,
|
|
38
39
|
parsePromptArgs: () => parsePromptArgs,
|
|
40
|
+
parsePsListArgs: () => parsePsListArgs,
|
|
39
41
|
parseSetEnvArgs: () => parseSetEnvArgs,
|
|
42
|
+
parseShellArgs: () => parseShellArgs,
|
|
40
43
|
readDotenvFile: () => readDotenvFile,
|
|
41
44
|
renderConversationResponse: () => renderConversationResponse,
|
|
45
|
+
renderProcessList: () => renderProcessList,
|
|
42
46
|
resolveCommandExecution: () => resolveCommandExecution,
|
|
43
47
|
runR5dctlCli: () => runR5dctlCli,
|
|
44
48
|
summarizeEnvData: () => summarizeEnvData
|
|
@@ -51,6 +55,7 @@ var import_node_child_process = require("node:child_process");
|
|
|
51
55
|
var import_promises = require("node:timers/promises");
|
|
52
56
|
var import_dotenv = require("dotenv");
|
|
53
57
|
var import_r5d_api = require("@ricsam/r5d-api");
|
|
58
|
+
var import_shell = require("./shell.cjs");
|
|
54
59
|
const CHAT_MODES = /* @__PURE__ */ new Set([
|
|
55
60
|
"ask",
|
|
56
61
|
"plan",
|
|
@@ -83,14 +88,32 @@ const CLI_ONLY_HELP_ENTRIES = [
|
|
|
83
88
|
section: "auth",
|
|
84
89
|
usage: "auth login [--no-open]",
|
|
85
90
|
description: "Start the browser login flow."
|
|
91
|
+
},
|
|
92
|
+
{
|
|
93
|
+
section: "shell",
|
|
94
|
+
usage: "-p <project> -b <branch> shell [-c <command>]",
|
|
95
|
+
description: "Open an interactive worker shell or run one command through a PTY."
|
|
86
96
|
}
|
|
87
97
|
];
|
|
88
98
|
const CLI_ONLY_COMMAND_HELP = [
|
|
89
99
|
{
|
|
90
100
|
path: ["auth", "login"],
|
|
91
101
|
usage: "auth login [--no-open]"
|
|
102
|
+
},
|
|
103
|
+
{
|
|
104
|
+
path: ["shell"],
|
|
105
|
+
usage: "-p <project> -b <branch> shell [-c <command>]"
|
|
92
106
|
}
|
|
93
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");
|
|
94
117
|
const SHARED_HELP_ENTRIES = [
|
|
95
118
|
{ section: "auth", usage: "auth status", description: "Show the current authenticated user and credential source." },
|
|
96
119
|
{ section: "auth", usage: "auth logout", description: "Revoke the current credential and clear saved auth." },
|
|
@@ -114,12 +137,12 @@ const SHARED_HELP_ENTRIES = [
|
|
|
114
137
|
},
|
|
115
138
|
{
|
|
116
139
|
section: "envs",
|
|
117
|
-
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]",
|
|
118
141
|
description: "Securely import branch envs from a dotenv file without putting values in command arguments."
|
|
119
142
|
},
|
|
120
143
|
{
|
|
121
144
|
section: "envs",
|
|
122
|
-
usage: "-p <project> -b <branch> set envs -e
|
|
145
|
+
usage: "-p <project> -b <branch> set envs -e KEY=value [--description <text>] [--only-missing]",
|
|
123
146
|
description: "Set one or more branch envs directly."
|
|
124
147
|
},
|
|
125
148
|
{
|
|
@@ -152,9 +175,15 @@ const SHARED_HELP_ENTRIES = [
|
|
|
152
175
|
},
|
|
153
176
|
{
|
|
154
177
|
section: "sessions",
|
|
155
|
-
usage: "-s <session-id> apply-required-envs -e
|
|
156
|
-
description: "Apply required
|
|
178
|
+
usage: "-s <session-id> apply-required-envs -e KEY=value",
|
|
179
|
+
description: "Apply required environment variable values."
|
|
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."
|
|
157
185
|
},
|
|
186
|
+
{ section: "processes", usage: "ps stop <run-id>", description: "Gracefully stop an active process with SIGTERM." },
|
|
158
187
|
{
|
|
159
188
|
section: "agents",
|
|
160
189
|
usage: '-p <project> start-agent <src-branch> <agent-type> "<prompt>"',
|
|
@@ -170,13 +199,25 @@ const SHARED_HELP_ENTRIES = [
|
|
|
170
199
|
{ section: "merges", usage: "-p <project> continue-merge <target-branch>", description: "Commit a resolved merge." },
|
|
171
200
|
{ section: "merges", usage: "-p <project> abort-merge <target-branch>", description: "Abort an in-progress merge." }
|
|
172
201
|
];
|
|
173
|
-
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
|
+
];
|
|
174
213
|
const HELP_SECTION_TITLES = {
|
|
175
214
|
auth: "Auth",
|
|
176
215
|
projects: "Projects",
|
|
177
216
|
branches: "Branches",
|
|
178
217
|
envs: "Environment variables",
|
|
179
218
|
sessions: "Sessions",
|
|
219
|
+
processes: "Processes",
|
|
220
|
+
shell: "Shell",
|
|
180
221
|
agents: "Agents",
|
|
181
222
|
merges: "Merges"
|
|
182
223
|
};
|
|
@@ -276,7 +317,7 @@ function renderCliOnlyCommandHelp(entry) {
|
|
|
276
317
|
}
|
|
277
318
|
function renderSharedCommandHelp(pathSegments) {
|
|
278
319
|
const entry = SHARED_HELP_ENTRIES.find((candidate) => {
|
|
279
|
-
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("["));
|
|
280
321
|
return usageSegments.length <= pathSegments.length && usageSegments.every((segment, index) => segment === pathSegments[index]);
|
|
281
322
|
});
|
|
282
323
|
if (!entry) {
|
|
@@ -512,33 +553,24 @@ function parseAnswerFlags(args) {
|
|
|
512
553
|
function validateEnvAssignment(value) {
|
|
513
554
|
const equalsIndex = value.indexOf("=");
|
|
514
555
|
if (equalsIndex <= 0) {
|
|
515
|
-
throw new Error(`Invalid env assignment '${value}'. Expected
|
|
516
|
-
}
|
|
517
|
-
const assignment = value.slice(0, equalsIndex);
|
|
518
|
-
const slashIndex = assignment.indexOf("/");
|
|
519
|
-
if (slashIndex <= 0) {
|
|
520
|
-
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`);
|
|
521
557
|
}
|
|
522
|
-
const
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
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`);
|
|
526
561
|
}
|
|
527
562
|
}
|
|
528
563
|
function parseEnvAssignment(value) {
|
|
529
564
|
validateEnvAssignment(value);
|
|
530
565
|
const equalsIndex = value.indexOf("=");
|
|
531
|
-
const slashIndex = value.indexOf("/");
|
|
532
566
|
return {
|
|
533
|
-
|
|
534
|
-
name: value.slice(slashIndex + 1, equalsIndex),
|
|
567
|
+
name: value.slice(0, equalsIndex),
|
|
535
568
|
value: value.slice(equalsIndex + 1)
|
|
536
569
|
};
|
|
537
570
|
}
|
|
538
571
|
function parseSetEnvArgs(args) {
|
|
539
572
|
const parsed = {
|
|
540
573
|
assignments: [],
|
|
541
|
-
target: "backend",
|
|
542
574
|
onlyMissing: false,
|
|
543
575
|
includeEmpty: false
|
|
544
576
|
};
|
|
@@ -568,23 +600,6 @@ function parseSetEnvArgs(args) {
|
|
|
568
600
|
parsed.fromEnvFile = requireValue(inlineFile, "Missing value for --from-env-file");
|
|
569
601
|
continue;
|
|
570
602
|
}
|
|
571
|
-
if (arg === "--target") {
|
|
572
|
-
const target = requireValue(args[index + 1], "Missing value for --target");
|
|
573
|
-
if (target !== "backend" && target !== "frontend") {
|
|
574
|
-
throw new Error("Invalid --target. Expected backend or frontend");
|
|
575
|
-
}
|
|
576
|
-
parsed.target = target;
|
|
577
|
-
index += 1;
|
|
578
|
-
continue;
|
|
579
|
-
}
|
|
580
|
-
const inlineTarget = parseLongOptionWithEquals(arg, "--target");
|
|
581
|
-
if (inlineTarget !== void 0) {
|
|
582
|
-
if (inlineTarget !== "backend" && inlineTarget !== "frontend") {
|
|
583
|
-
throw new Error("Invalid --target. Expected backend or frontend");
|
|
584
|
-
}
|
|
585
|
-
parsed.target = inlineTarget;
|
|
586
|
-
continue;
|
|
587
|
-
}
|
|
588
603
|
if (arg === "--description") {
|
|
589
604
|
parsed.description = requireValue(args[index + 1], "Missing value for --description");
|
|
590
605
|
index += 1;
|
|
@@ -658,7 +673,7 @@ function parseEnvFlags(args) {
|
|
|
658
673
|
if (arg.startsWith("-")) {
|
|
659
674
|
throw new Error(`Unknown env flag: ${arg}`);
|
|
660
675
|
}
|
|
661
|
-
throw new Error(`Unexpected value '${arg}'. Use -e
|
|
676
|
+
throw new Error(`Unexpected value '${arg}'. Use -e KEY=value.`);
|
|
662
677
|
}
|
|
663
678
|
if (envs.length === 0) {
|
|
664
679
|
throw new Error("At least one -e/--env value is required.");
|
|
@@ -725,6 +740,67 @@ function parsePromptArgs(args) {
|
|
|
725
740
|
message
|
|
726
741
|
};
|
|
727
742
|
}
|
|
743
|
+
function parseShellArgs(args) {
|
|
744
|
+
let command;
|
|
745
|
+
for (let index = 0; index < args.length; index += 1) {
|
|
746
|
+
const arg = args[index];
|
|
747
|
+
if (!arg) {
|
|
748
|
+
continue;
|
|
749
|
+
}
|
|
750
|
+
if (arg === "-c" || arg === "--command") {
|
|
751
|
+
if (command !== void 0) {
|
|
752
|
+
throw new Error("Use only one -c/--command value");
|
|
753
|
+
}
|
|
754
|
+
command = requireValue(args[index + 1], `Missing value for ${arg}`);
|
|
755
|
+
index += 1;
|
|
756
|
+
continue;
|
|
757
|
+
}
|
|
758
|
+
const inlineCommand = parseLongOptionWithEquals(arg, "--command");
|
|
759
|
+
if (inlineCommand !== void 0) {
|
|
760
|
+
if (command !== void 0) {
|
|
761
|
+
throw new Error("Use only one -c/--command value");
|
|
762
|
+
}
|
|
763
|
+
command = requireValue(inlineCommand, "Missing value for --command");
|
|
764
|
+
continue;
|
|
765
|
+
}
|
|
766
|
+
throw new Error(arg.startsWith("-") ? `Unknown shell flag: ${arg}` : `Unexpected shell value: ${arg}`);
|
|
767
|
+
}
|
|
768
|
+
return command === void 0 ? {} : { command };
|
|
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
|
+
}
|
|
728
804
|
async function openInBrowser(url) {
|
|
729
805
|
if (process.platform === "darwin") {
|
|
730
806
|
(0, import_node_child_process.spawn)("open", [url], { stdio: "ignore", detached: true }).unref();
|
|
@@ -866,34 +942,66 @@ function renderSessionList(sessions) {
|
|
|
866
942
|
function renderSessionDescription(session) {
|
|
867
943
|
return [`Session: ${session.id}`, `Project: ${session.projectPath}`, `Branch: ${session.branchName}`].join("\n") + "\n";
|
|
868
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
|
+
}
|
|
869
989
|
function summarizeEnvData(data) {
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
hasValue: env.value !== null
|
|
881
|
-
}
|
|
882
|
-
])
|
|
883
|
-
);
|
|
884
|
-
}
|
|
885
|
-
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
|
+
);
|
|
886
1000
|
}
|
|
887
1001
|
function renderEnvSummary(data) {
|
|
888
1002
|
const lines = [];
|
|
889
|
-
for (const
|
|
890
|
-
|
|
891
|
-
if (entries.length === 0) continue;
|
|
892
|
-
if (lines.length > 0) lines.push("");
|
|
893
|
-
lines.push(`${target}:`);
|
|
894
|
-
for (const [name, env] of entries) {
|
|
895
|
-
lines.push(` ${name} ${env.hasValue ? "set" : "missing"} ${env.optional ? "optional" : "required"} ${env.description}`);
|
|
896
|
-
}
|
|
1003
|
+
for (const [name, env] of Object.entries(data)) {
|
|
1004
|
+
lines.push(`${name} ${env.hasValue ? "set" : "missing"} ${env.optional ? "optional" : "required"} ${env.description}`);
|
|
897
1005
|
}
|
|
898
1006
|
return lines.length > 0 ? `${lines.join("\n")}
|
|
899
1007
|
` : "No environment variables declared.\n";
|
|
@@ -905,7 +1013,7 @@ function renderEnvSetResult(result) {
|
|
|
905
1013
|
if (result.skippedExisting.length > 0) lines.push(`Skipped ${result.skippedExisting.length} existing value(s).`);
|
|
906
1014
|
if (result.skippedEmpty.length > 0) lines.push(`Skipped ${result.skippedEmpty.length} empty value(s).`);
|
|
907
1015
|
if (result.updated.length > 0) {
|
|
908
|
-
lines.push(...result.updated.map(({
|
|
1016
|
+
lines.push(...result.updated.map(({ name }) => ` ${name}`));
|
|
909
1017
|
}
|
|
910
1018
|
return `${lines.join("\n")}
|
|
911
1019
|
`;
|
|
@@ -918,10 +1026,10 @@ async function setBranchEnvs(client, projectRef, branchName, args) {
|
|
|
918
1026
|
if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(name)) {
|
|
919
1027
|
throw new Error(`Invalid environment variable name in dotenv file: ${name}`);
|
|
920
1028
|
}
|
|
921
|
-
return {
|
|
1029
|
+
return { name, value };
|
|
922
1030
|
}) : options.assignments.map(parseEnvAssignment);
|
|
923
1031
|
const uniqueAssignments = /* @__PURE__ */ new Map();
|
|
924
|
-
for (const assignment of assignments) uniqueAssignments.set(
|
|
1032
|
+
for (const assignment of assignments) uniqueAssignments.set(assignment.name, assignment);
|
|
925
1033
|
const update = {};
|
|
926
1034
|
const result = {
|
|
927
1035
|
success: true,
|
|
@@ -934,12 +1042,12 @@ async function setBranchEnvs(client, projectRef, branchName, args) {
|
|
|
934
1042
|
const missingDescriptions = [];
|
|
935
1043
|
const importedDescription = options.fromEnvFile ? `Imported from ${import_node_path.default.basename(options.fromEnvFile)}.` : void 0;
|
|
936
1044
|
for (const assignment of uniqueAssignments.values()) {
|
|
937
|
-
const identity = {
|
|
1045
|
+
const identity = { name: assignment.name };
|
|
938
1046
|
if (!options.includeEmpty && assignment.value.length === 0) {
|
|
939
1047
|
result.skippedEmpty.push(identity);
|
|
940
1048
|
continue;
|
|
941
1049
|
}
|
|
942
|
-
const existing = current[assignment.
|
|
1050
|
+
const existing = current[assignment.name];
|
|
943
1051
|
if (options.onlyMissing && existing?.value !== null && existing?.value !== void 0) {
|
|
944
1052
|
result.skippedExisting.push(identity);
|
|
945
1053
|
continue;
|
|
@@ -949,9 +1057,8 @@ async function setBranchEnvs(client, projectRef, branchName, args) {
|
|
|
949
1057
|
missingDescriptions.push(identity);
|
|
950
1058
|
continue;
|
|
951
1059
|
}
|
|
952
|
-
const targetUpdate = update[assignment.target] ??= {};
|
|
953
1060
|
const optional = options.optional ?? existing?.optional;
|
|
954
|
-
|
|
1061
|
+
update[assignment.name] = {
|
|
955
1062
|
description,
|
|
956
1063
|
value: assignment.value,
|
|
957
1064
|
...optional === void 0 ? {} : { optional }
|
|
@@ -959,9 +1066,7 @@ async function setBranchEnvs(client, projectRef, branchName, args) {
|
|
|
959
1066
|
result.updated.push(identity);
|
|
960
1067
|
}
|
|
961
1068
|
if (missingDescriptions.length > 0) {
|
|
962
|
-
throw new Error(
|
|
963
|
-
`--description is required when declaring new envs with -e: ${missingDescriptions.map(({ target, name }) => `${target}/${name}`).join(", ")}`
|
|
964
|
-
);
|
|
1069
|
+
throw new Error(`--description is required when declaring new envs with -e: ${missingDescriptions.map(({ name }) => name).join(", ")}`);
|
|
965
1070
|
}
|
|
966
1071
|
if (result.updated.length > 0) {
|
|
967
1072
|
await client.projects.env.update(project.id, branchName, update);
|
|
@@ -1126,6 +1231,17 @@ async function executeR5dctlCommand(client, json, args) {
|
|
|
1126
1231
|
write(result, "API key revoked.\n");
|
|
1127
1232
|
return;
|
|
1128
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
|
+
}
|
|
1129
1245
|
if (first === "projects" && second === "list" || first === "get" && second === "projects") {
|
|
1130
1246
|
const projects = await client.projects.list();
|
|
1131
1247
|
write(projects, renderProjectList(projects));
|
|
@@ -1321,6 +1437,12 @@ function resolveCommandExecution(options, rest) {
|
|
|
1321
1437
|
throw new Error("Missing command");
|
|
1322
1438
|
}
|
|
1323
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
|
+
}
|
|
1324
1446
|
if (trailingHelp) {
|
|
1325
1447
|
const cliOnlyHelp = findCliOnlyCommandHelp(normalizedRest);
|
|
1326
1448
|
if (cliOnlyHelp) {
|
|
@@ -1394,6 +1516,36 @@ function resolveCommandExecution(options, rest) {
|
|
|
1394
1516
|
}
|
|
1395
1517
|
throw new Error("Unknown auth command");
|
|
1396
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
|
+
}
|
|
1397
1549
|
if (command === "get") {
|
|
1398
1550
|
const target = commandArgs[0];
|
|
1399
1551
|
if (target === "projects") {
|
|
@@ -1573,6 +1725,21 @@ function resolveCommandExecution(options, rest) {
|
|
|
1573
1725
|
pluginArgs: ["conversation", sessionId, ...commandArgs]
|
|
1574
1726
|
};
|
|
1575
1727
|
}
|
|
1728
|
+
if (command === "shell") {
|
|
1729
|
+
if (!options.project) {
|
|
1730
|
+
throw new Error("--project/-p is required for `shell`");
|
|
1731
|
+
}
|
|
1732
|
+
if (!options.branch) {
|
|
1733
|
+
throw new Error("--branch/-b is required for `shell`");
|
|
1734
|
+
}
|
|
1735
|
+
if (options.json) {
|
|
1736
|
+
throw new Error("--json is not supported for `shell`");
|
|
1737
|
+
}
|
|
1738
|
+
return {
|
|
1739
|
+
kind: "shell",
|
|
1740
|
+
...parseShellArgs(commandArgs)
|
|
1741
|
+
};
|
|
1742
|
+
}
|
|
1576
1743
|
if (command === "prompt") {
|
|
1577
1744
|
const sessionId = options.session;
|
|
1578
1745
|
if (!sessionId) {
|
|
@@ -1696,31 +1863,43 @@ async function runCommand(argv) {
|
|
|
1696
1863
|
const { options, rest } = parseGlobalArgs(argv);
|
|
1697
1864
|
if (options.version) {
|
|
1698
1865
|
printVersion();
|
|
1699
|
-
return;
|
|
1866
|
+
return 0;
|
|
1700
1867
|
}
|
|
1701
1868
|
if (options.help || rest.length === 0) {
|
|
1702
1869
|
printHelp();
|
|
1703
|
-
return;
|
|
1870
|
+
return 0;
|
|
1704
1871
|
}
|
|
1705
1872
|
const config = parseConfig(options.configPath);
|
|
1706
|
-
const
|
|
1873
|
+
const clientOptions = resolveClientOptions(options, config);
|
|
1874
|
+
const client = new import_r5d_api.R5dctlClient(clientOptions);
|
|
1707
1875
|
const execution = resolveCommandExecution(options, rest);
|
|
1708
1876
|
if (execution.kind === "auth-login") {
|
|
1709
1877
|
await handleAuthLogin(client, options, execution.commandArgs, config);
|
|
1710
|
-
return;
|
|
1878
|
+
return 0;
|
|
1711
1879
|
}
|
|
1712
1880
|
if (execution.kind === "auth-api-key-create") {
|
|
1713
1881
|
await handleAuthApiKeyCreate(client, options, config, execution.commandArgs);
|
|
1714
|
-
return;
|
|
1882
|
+
return 0;
|
|
1715
1883
|
}
|
|
1716
1884
|
if (execution.kind === "cli-help") {
|
|
1717
1885
|
process.stdout.write(execution.text);
|
|
1718
|
-
return;
|
|
1886
|
+
return 0;
|
|
1887
|
+
}
|
|
1888
|
+
if (execution.kind === "shell") {
|
|
1889
|
+
const project = await client.projects.describe(options.project);
|
|
1890
|
+
return await (0, import_shell.runR5dctlShell)({
|
|
1891
|
+
baseUrl: clientOptions.baseUrl ?? "https://r5d.dev",
|
|
1892
|
+
credential: clientOptions.token ?? clientOptions.apiKey ?? "",
|
|
1893
|
+
projectId: project.id,
|
|
1894
|
+
branchName: options.branch,
|
|
1895
|
+
command: execution.command
|
|
1896
|
+
});
|
|
1719
1897
|
}
|
|
1720
1898
|
await executeR5dctlCommand(client, options.json, execution.pluginArgs);
|
|
1721
1899
|
if (execution.clearAuthOnSuccess) {
|
|
1722
1900
|
writeConfig(options.configPath, clearAuthFromConfig(config, options));
|
|
1723
1901
|
}
|
|
1902
|
+
return 0;
|
|
1724
1903
|
}
|
|
1725
1904
|
function extractApiErrorMessage(error) {
|
|
1726
1905
|
if (typeof error.body === "object" && error.body !== null) {
|
|
@@ -1733,8 +1912,7 @@ function extractApiErrorMessage(error) {
|
|
|
1733
1912
|
}
|
|
1734
1913
|
async function runR5dctlCli(argv) {
|
|
1735
1914
|
try {
|
|
1736
|
-
await runCommand(argv);
|
|
1737
|
-
return 0;
|
|
1915
|
+
return await runCommand(argv);
|
|
1738
1916
|
} catch (error) {
|
|
1739
1917
|
if (error instanceof import_r5d_api.R5dctlApiError) {
|
|
1740
1918
|
process.stderr.write(`${extractApiErrorMessage(error)}
|
|
@@ -1749,11 +1927,12 @@ async function runR5dctlCli(argv) {
|
|
|
1749
1927
|
async function main(argv = process.argv.slice(2)) {
|
|
1750
1928
|
const exitCode = await runR5dctlCli(argv);
|
|
1751
1929
|
if (exitCode !== 0) {
|
|
1752
|
-
process.
|
|
1930
|
+
process.exitCode = exitCode;
|
|
1753
1931
|
}
|
|
1754
1932
|
}
|
|
1755
1933
|
// Annotate the CommonJS export names for ESM import in node:
|
|
1756
1934
|
0 && (module.exports = {
|
|
1935
|
+
formatProcessAge,
|
|
1757
1936
|
getCliHelpText,
|
|
1758
1937
|
getR5dctlVersion,
|
|
1759
1938
|
main,
|
|
@@ -1762,9 +1941,12 @@ async function main(argv = process.argv.slice(2)) {
|
|
|
1762
1941
|
parseEnvFlags,
|
|
1763
1942
|
parseGlobalArgs,
|
|
1764
1943
|
parsePromptArgs,
|
|
1944
|
+
parsePsListArgs,
|
|
1765
1945
|
parseSetEnvArgs,
|
|
1946
|
+
parseShellArgs,
|
|
1766
1947
|
readDotenvFile,
|
|
1767
1948
|
renderConversationResponse,
|
|
1949
|
+
renderProcessList,
|
|
1768
1950
|
resolveCommandExecution,
|
|
1769
1951
|
runR5dctlCli,
|
|
1770
1952
|
summarizeEnvData
|
package/dist/cjs/package.json
CHANGED