@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/dist/mjs/cli.mjs
CHANGED
|
@@ -8,6 +8,7 @@ import {
|
|
|
8
8
|
R5dctlApiError,
|
|
9
9
|
R5dctlClient
|
|
10
10
|
} from "@ricsam/r5d-api";
|
|
11
|
+
import { runR5dctlShell } from "./shell.mjs";
|
|
11
12
|
const CHAT_MODES = /* @__PURE__ */ new Set([
|
|
12
13
|
"ask",
|
|
13
14
|
"plan",
|
|
@@ -40,14 +41,32 @@ const CLI_ONLY_HELP_ENTRIES = [
|
|
|
40
41
|
section: "auth",
|
|
41
42
|
usage: "auth login [--no-open]",
|
|
42
43
|
description: "Start the browser login flow."
|
|
44
|
+
},
|
|
45
|
+
{
|
|
46
|
+
section: "shell",
|
|
47
|
+
usage: "-p <project> -b <branch> shell [-c <command>]",
|
|
48
|
+
description: "Open an interactive worker shell or run one command through a PTY."
|
|
43
49
|
}
|
|
44
50
|
];
|
|
45
51
|
const CLI_ONLY_COMMAND_HELP = [
|
|
46
52
|
{
|
|
47
53
|
path: ["auth", "login"],
|
|
48
54
|
usage: "auth login [--no-open]"
|
|
55
|
+
},
|
|
56
|
+
{
|
|
57
|
+
path: ["shell"],
|
|
58
|
+
usage: "-p <project> -b <branch> shell [-c <command>]"
|
|
49
59
|
}
|
|
50
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");
|
|
51
70
|
const SHARED_HELP_ENTRIES = [
|
|
52
71
|
{ section: "auth", usage: "auth status", description: "Show the current authenticated user and credential source." },
|
|
53
72
|
{ section: "auth", usage: "auth logout", description: "Revoke the current credential and clear saved auth." },
|
|
@@ -71,12 +90,12 @@ const SHARED_HELP_ENTRIES = [
|
|
|
71
90
|
},
|
|
72
91
|
{
|
|
73
92
|
section: "envs",
|
|
74
|
-
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]",
|
|
75
94
|
description: "Securely import branch envs from a dotenv file without putting values in command arguments."
|
|
76
95
|
},
|
|
77
96
|
{
|
|
78
97
|
section: "envs",
|
|
79
|
-
usage: "-p <project> -b <branch> set envs -e
|
|
98
|
+
usage: "-p <project> -b <branch> set envs -e KEY=value [--description <text>] [--only-missing]",
|
|
80
99
|
description: "Set one or more branch envs directly."
|
|
81
100
|
},
|
|
82
101
|
{
|
|
@@ -109,9 +128,15 @@ const SHARED_HELP_ENTRIES = [
|
|
|
109
128
|
},
|
|
110
129
|
{
|
|
111
130
|
section: "sessions",
|
|
112
|
-
usage: "-s <session-id> apply-required-envs -e
|
|
113
|
-
description: "Apply required
|
|
131
|
+
usage: "-s <session-id> apply-required-envs -e KEY=value",
|
|
132
|
+
description: "Apply required environment variable values."
|
|
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."
|
|
114
138
|
},
|
|
139
|
+
{ section: "processes", usage: "ps stop <run-id>", description: "Gracefully stop an active process with SIGTERM." },
|
|
115
140
|
{
|
|
116
141
|
section: "agents",
|
|
117
142
|
usage: '-p <project> start-agent <src-branch> <agent-type> "<prompt>"',
|
|
@@ -127,13 +152,25 @@ const SHARED_HELP_ENTRIES = [
|
|
|
127
152
|
{ section: "merges", usage: "-p <project> continue-merge <target-branch>", description: "Commit a resolved merge." },
|
|
128
153
|
{ section: "merges", usage: "-p <project> abort-merge <target-branch>", description: "Abort an in-progress merge." }
|
|
129
154
|
];
|
|
130
|
-
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
|
+
];
|
|
131
166
|
const HELP_SECTION_TITLES = {
|
|
132
167
|
auth: "Auth",
|
|
133
168
|
projects: "Projects",
|
|
134
169
|
branches: "Branches",
|
|
135
170
|
envs: "Environment variables",
|
|
136
171
|
sessions: "Sessions",
|
|
172
|
+
processes: "Processes",
|
|
173
|
+
shell: "Shell",
|
|
137
174
|
agents: "Agents",
|
|
138
175
|
merges: "Merges"
|
|
139
176
|
};
|
|
@@ -233,7 +270,7 @@ function renderCliOnlyCommandHelp(entry) {
|
|
|
233
270
|
}
|
|
234
271
|
function renderSharedCommandHelp(pathSegments) {
|
|
235
272
|
const entry = SHARED_HELP_ENTRIES.find((candidate) => {
|
|
236
|
-
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("["));
|
|
237
274
|
return usageSegments.length <= pathSegments.length && usageSegments.every((segment, index) => segment === pathSegments[index]);
|
|
238
275
|
});
|
|
239
276
|
if (!entry) {
|
|
@@ -469,33 +506,24 @@ function parseAnswerFlags(args) {
|
|
|
469
506
|
function validateEnvAssignment(value) {
|
|
470
507
|
const equalsIndex = value.indexOf("=");
|
|
471
508
|
if (equalsIndex <= 0) {
|
|
472
|
-
throw new Error(`Invalid env assignment '${value}'. Expected
|
|
473
|
-
}
|
|
474
|
-
const assignment = value.slice(0, equalsIndex);
|
|
475
|
-
const slashIndex = assignment.indexOf("/");
|
|
476
|
-
if (slashIndex <= 0) {
|
|
477
|
-
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`);
|
|
478
510
|
}
|
|
479
|
-
const
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
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`);
|
|
483
514
|
}
|
|
484
515
|
}
|
|
485
516
|
function parseEnvAssignment(value) {
|
|
486
517
|
validateEnvAssignment(value);
|
|
487
518
|
const equalsIndex = value.indexOf("=");
|
|
488
|
-
const slashIndex = value.indexOf("/");
|
|
489
519
|
return {
|
|
490
|
-
|
|
491
|
-
name: value.slice(slashIndex + 1, equalsIndex),
|
|
520
|
+
name: value.slice(0, equalsIndex),
|
|
492
521
|
value: value.slice(equalsIndex + 1)
|
|
493
522
|
};
|
|
494
523
|
}
|
|
495
524
|
function parseSetEnvArgs(args) {
|
|
496
525
|
const parsed = {
|
|
497
526
|
assignments: [],
|
|
498
|
-
target: "backend",
|
|
499
527
|
onlyMissing: false,
|
|
500
528
|
includeEmpty: false
|
|
501
529
|
};
|
|
@@ -525,23 +553,6 @@ function parseSetEnvArgs(args) {
|
|
|
525
553
|
parsed.fromEnvFile = requireValue(inlineFile, "Missing value for --from-env-file");
|
|
526
554
|
continue;
|
|
527
555
|
}
|
|
528
|
-
if (arg === "--target") {
|
|
529
|
-
const target = requireValue(args[index + 1], "Missing value for --target");
|
|
530
|
-
if (target !== "backend" && target !== "frontend") {
|
|
531
|
-
throw new Error("Invalid --target. Expected backend or frontend");
|
|
532
|
-
}
|
|
533
|
-
parsed.target = target;
|
|
534
|
-
index += 1;
|
|
535
|
-
continue;
|
|
536
|
-
}
|
|
537
|
-
const inlineTarget = parseLongOptionWithEquals(arg, "--target");
|
|
538
|
-
if (inlineTarget !== void 0) {
|
|
539
|
-
if (inlineTarget !== "backend" && inlineTarget !== "frontend") {
|
|
540
|
-
throw new Error("Invalid --target. Expected backend or frontend");
|
|
541
|
-
}
|
|
542
|
-
parsed.target = inlineTarget;
|
|
543
|
-
continue;
|
|
544
|
-
}
|
|
545
556
|
if (arg === "--description") {
|
|
546
557
|
parsed.description = requireValue(args[index + 1], "Missing value for --description");
|
|
547
558
|
index += 1;
|
|
@@ -615,7 +626,7 @@ function parseEnvFlags(args) {
|
|
|
615
626
|
if (arg.startsWith("-")) {
|
|
616
627
|
throw new Error(`Unknown env flag: ${arg}`);
|
|
617
628
|
}
|
|
618
|
-
throw new Error(`Unexpected value '${arg}'. Use -e
|
|
629
|
+
throw new Error(`Unexpected value '${arg}'. Use -e KEY=value.`);
|
|
619
630
|
}
|
|
620
631
|
if (envs.length === 0) {
|
|
621
632
|
throw new Error("At least one -e/--env value is required.");
|
|
@@ -682,6 +693,67 @@ function parsePromptArgs(args) {
|
|
|
682
693
|
message
|
|
683
694
|
};
|
|
684
695
|
}
|
|
696
|
+
function parseShellArgs(args) {
|
|
697
|
+
let command;
|
|
698
|
+
for (let index = 0; index < args.length; index += 1) {
|
|
699
|
+
const arg = args[index];
|
|
700
|
+
if (!arg) {
|
|
701
|
+
continue;
|
|
702
|
+
}
|
|
703
|
+
if (arg === "-c" || arg === "--command") {
|
|
704
|
+
if (command !== void 0) {
|
|
705
|
+
throw new Error("Use only one -c/--command value");
|
|
706
|
+
}
|
|
707
|
+
command = requireValue(args[index + 1], `Missing value for ${arg}`);
|
|
708
|
+
index += 1;
|
|
709
|
+
continue;
|
|
710
|
+
}
|
|
711
|
+
const inlineCommand = parseLongOptionWithEquals(arg, "--command");
|
|
712
|
+
if (inlineCommand !== void 0) {
|
|
713
|
+
if (command !== void 0) {
|
|
714
|
+
throw new Error("Use only one -c/--command value");
|
|
715
|
+
}
|
|
716
|
+
command = requireValue(inlineCommand, "Missing value for --command");
|
|
717
|
+
continue;
|
|
718
|
+
}
|
|
719
|
+
throw new Error(arg.startsWith("-") ? `Unknown shell flag: ${arg}` : `Unexpected shell value: ${arg}`);
|
|
720
|
+
}
|
|
721
|
+
return command === void 0 ? {} : { command };
|
|
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
|
+
}
|
|
685
757
|
async function openInBrowser(url) {
|
|
686
758
|
if (process.platform === "darwin") {
|
|
687
759
|
spawn("open", [url], { stdio: "ignore", detached: true }).unref();
|
|
@@ -823,34 +895,66 @@ function renderSessionList(sessions) {
|
|
|
823
895
|
function renderSessionDescription(session) {
|
|
824
896
|
return [`Session: ${session.id}`, `Project: ${session.projectPath}`, `Branch: ${session.branchName}`].join("\n") + "\n";
|
|
825
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
|
+
}
|
|
826
942
|
function summarizeEnvData(data) {
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
hasValue: env.value !== null
|
|
838
|
-
}
|
|
839
|
-
])
|
|
840
|
-
);
|
|
841
|
-
}
|
|
842
|
-
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
|
+
);
|
|
843
953
|
}
|
|
844
954
|
function renderEnvSummary(data) {
|
|
845
955
|
const lines = [];
|
|
846
|
-
for (const
|
|
847
|
-
|
|
848
|
-
if (entries.length === 0) continue;
|
|
849
|
-
if (lines.length > 0) lines.push("");
|
|
850
|
-
lines.push(`${target}:`);
|
|
851
|
-
for (const [name, env] of entries) {
|
|
852
|
-
lines.push(` ${name} ${env.hasValue ? "set" : "missing"} ${env.optional ? "optional" : "required"} ${env.description}`);
|
|
853
|
-
}
|
|
956
|
+
for (const [name, env] of Object.entries(data)) {
|
|
957
|
+
lines.push(`${name} ${env.hasValue ? "set" : "missing"} ${env.optional ? "optional" : "required"} ${env.description}`);
|
|
854
958
|
}
|
|
855
959
|
return lines.length > 0 ? `${lines.join("\n")}
|
|
856
960
|
` : "No environment variables declared.\n";
|
|
@@ -862,7 +966,7 @@ function renderEnvSetResult(result) {
|
|
|
862
966
|
if (result.skippedExisting.length > 0) lines.push(`Skipped ${result.skippedExisting.length} existing value(s).`);
|
|
863
967
|
if (result.skippedEmpty.length > 0) lines.push(`Skipped ${result.skippedEmpty.length} empty value(s).`);
|
|
864
968
|
if (result.updated.length > 0) {
|
|
865
|
-
lines.push(...result.updated.map(({
|
|
969
|
+
lines.push(...result.updated.map(({ name }) => ` ${name}`));
|
|
866
970
|
}
|
|
867
971
|
return `${lines.join("\n")}
|
|
868
972
|
`;
|
|
@@ -875,10 +979,10 @@ async function setBranchEnvs(client, projectRef, branchName, args) {
|
|
|
875
979
|
if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(name)) {
|
|
876
980
|
throw new Error(`Invalid environment variable name in dotenv file: ${name}`);
|
|
877
981
|
}
|
|
878
|
-
return {
|
|
982
|
+
return { name, value };
|
|
879
983
|
}) : options.assignments.map(parseEnvAssignment);
|
|
880
984
|
const uniqueAssignments = /* @__PURE__ */ new Map();
|
|
881
|
-
for (const assignment of assignments) uniqueAssignments.set(
|
|
985
|
+
for (const assignment of assignments) uniqueAssignments.set(assignment.name, assignment);
|
|
882
986
|
const update = {};
|
|
883
987
|
const result = {
|
|
884
988
|
success: true,
|
|
@@ -891,12 +995,12 @@ async function setBranchEnvs(client, projectRef, branchName, args) {
|
|
|
891
995
|
const missingDescriptions = [];
|
|
892
996
|
const importedDescription = options.fromEnvFile ? `Imported from ${path.basename(options.fromEnvFile)}.` : void 0;
|
|
893
997
|
for (const assignment of uniqueAssignments.values()) {
|
|
894
|
-
const identity = {
|
|
998
|
+
const identity = { name: assignment.name };
|
|
895
999
|
if (!options.includeEmpty && assignment.value.length === 0) {
|
|
896
1000
|
result.skippedEmpty.push(identity);
|
|
897
1001
|
continue;
|
|
898
1002
|
}
|
|
899
|
-
const existing = current[assignment.
|
|
1003
|
+
const existing = current[assignment.name];
|
|
900
1004
|
if (options.onlyMissing && existing?.value !== null && existing?.value !== void 0) {
|
|
901
1005
|
result.skippedExisting.push(identity);
|
|
902
1006
|
continue;
|
|
@@ -906,9 +1010,8 @@ async function setBranchEnvs(client, projectRef, branchName, args) {
|
|
|
906
1010
|
missingDescriptions.push(identity);
|
|
907
1011
|
continue;
|
|
908
1012
|
}
|
|
909
|
-
const targetUpdate = update[assignment.target] ??= {};
|
|
910
1013
|
const optional = options.optional ?? existing?.optional;
|
|
911
|
-
|
|
1014
|
+
update[assignment.name] = {
|
|
912
1015
|
description,
|
|
913
1016
|
value: assignment.value,
|
|
914
1017
|
...optional === void 0 ? {} : { optional }
|
|
@@ -916,9 +1019,7 @@ async function setBranchEnvs(client, projectRef, branchName, args) {
|
|
|
916
1019
|
result.updated.push(identity);
|
|
917
1020
|
}
|
|
918
1021
|
if (missingDescriptions.length > 0) {
|
|
919
|
-
throw new Error(
|
|
920
|
-
`--description is required when declaring new envs with -e: ${missingDescriptions.map(({ target, name }) => `${target}/${name}`).join(", ")}`
|
|
921
|
-
);
|
|
1022
|
+
throw new Error(`--description is required when declaring new envs with -e: ${missingDescriptions.map(({ name }) => name).join(", ")}`);
|
|
922
1023
|
}
|
|
923
1024
|
if (result.updated.length > 0) {
|
|
924
1025
|
await client.projects.env.update(project.id, branchName, update);
|
|
@@ -1083,6 +1184,17 @@ async function executeR5dctlCommand(client, json, args) {
|
|
|
1083
1184
|
write(result, "API key revoked.\n");
|
|
1084
1185
|
return;
|
|
1085
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
|
+
}
|
|
1086
1198
|
if (first === "projects" && second === "list" || first === "get" && second === "projects") {
|
|
1087
1199
|
const projects = await client.projects.list();
|
|
1088
1200
|
write(projects, renderProjectList(projects));
|
|
@@ -1278,6 +1390,12 @@ function resolveCommandExecution(options, rest) {
|
|
|
1278
1390
|
throw new Error("Missing command");
|
|
1279
1391
|
}
|
|
1280
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
|
+
}
|
|
1281
1399
|
if (trailingHelp) {
|
|
1282
1400
|
const cliOnlyHelp = findCliOnlyCommandHelp(normalizedRest);
|
|
1283
1401
|
if (cliOnlyHelp) {
|
|
@@ -1351,6 +1469,36 @@ function resolveCommandExecution(options, rest) {
|
|
|
1351
1469
|
}
|
|
1352
1470
|
throw new Error("Unknown auth command");
|
|
1353
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
|
+
}
|
|
1354
1502
|
if (command === "get") {
|
|
1355
1503
|
const target = commandArgs[0];
|
|
1356
1504
|
if (target === "projects") {
|
|
@@ -1530,6 +1678,21 @@ function resolveCommandExecution(options, rest) {
|
|
|
1530
1678
|
pluginArgs: ["conversation", sessionId, ...commandArgs]
|
|
1531
1679
|
};
|
|
1532
1680
|
}
|
|
1681
|
+
if (command === "shell") {
|
|
1682
|
+
if (!options.project) {
|
|
1683
|
+
throw new Error("--project/-p is required for `shell`");
|
|
1684
|
+
}
|
|
1685
|
+
if (!options.branch) {
|
|
1686
|
+
throw new Error("--branch/-b is required for `shell`");
|
|
1687
|
+
}
|
|
1688
|
+
if (options.json) {
|
|
1689
|
+
throw new Error("--json is not supported for `shell`");
|
|
1690
|
+
}
|
|
1691
|
+
return {
|
|
1692
|
+
kind: "shell",
|
|
1693
|
+
...parseShellArgs(commandArgs)
|
|
1694
|
+
};
|
|
1695
|
+
}
|
|
1533
1696
|
if (command === "prompt") {
|
|
1534
1697
|
const sessionId = options.session;
|
|
1535
1698
|
if (!sessionId) {
|
|
@@ -1653,31 +1816,43 @@ async function runCommand(argv) {
|
|
|
1653
1816
|
const { options, rest } = parseGlobalArgs(argv);
|
|
1654
1817
|
if (options.version) {
|
|
1655
1818
|
printVersion();
|
|
1656
|
-
return;
|
|
1819
|
+
return 0;
|
|
1657
1820
|
}
|
|
1658
1821
|
if (options.help || rest.length === 0) {
|
|
1659
1822
|
printHelp();
|
|
1660
|
-
return;
|
|
1823
|
+
return 0;
|
|
1661
1824
|
}
|
|
1662
1825
|
const config = parseConfig(options.configPath);
|
|
1663
|
-
const
|
|
1826
|
+
const clientOptions = resolveClientOptions(options, config);
|
|
1827
|
+
const client = new R5dctlClient(clientOptions);
|
|
1664
1828
|
const execution = resolveCommandExecution(options, rest);
|
|
1665
1829
|
if (execution.kind === "auth-login") {
|
|
1666
1830
|
await handleAuthLogin(client, options, execution.commandArgs, config);
|
|
1667
|
-
return;
|
|
1831
|
+
return 0;
|
|
1668
1832
|
}
|
|
1669
1833
|
if (execution.kind === "auth-api-key-create") {
|
|
1670
1834
|
await handleAuthApiKeyCreate(client, options, config, execution.commandArgs);
|
|
1671
|
-
return;
|
|
1835
|
+
return 0;
|
|
1672
1836
|
}
|
|
1673
1837
|
if (execution.kind === "cli-help") {
|
|
1674
1838
|
process.stdout.write(execution.text);
|
|
1675
|
-
return;
|
|
1839
|
+
return 0;
|
|
1840
|
+
}
|
|
1841
|
+
if (execution.kind === "shell") {
|
|
1842
|
+
const project = await client.projects.describe(options.project);
|
|
1843
|
+
return await runR5dctlShell({
|
|
1844
|
+
baseUrl: clientOptions.baseUrl ?? "https://r5d.dev",
|
|
1845
|
+
credential: clientOptions.token ?? clientOptions.apiKey ?? "",
|
|
1846
|
+
projectId: project.id,
|
|
1847
|
+
branchName: options.branch,
|
|
1848
|
+
command: execution.command
|
|
1849
|
+
});
|
|
1676
1850
|
}
|
|
1677
1851
|
await executeR5dctlCommand(client, options.json, execution.pluginArgs);
|
|
1678
1852
|
if (execution.clearAuthOnSuccess) {
|
|
1679
1853
|
writeConfig(options.configPath, clearAuthFromConfig(config, options));
|
|
1680
1854
|
}
|
|
1855
|
+
return 0;
|
|
1681
1856
|
}
|
|
1682
1857
|
function extractApiErrorMessage(error) {
|
|
1683
1858
|
if (typeof error.body === "object" && error.body !== null) {
|
|
@@ -1690,8 +1865,7 @@ function extractApiErrorMessage(error) {
|
|
|
1690
1865
|
}
|
|
1691
1866
|
async function runR5dctlCli(argv) {
|
|
1692
1867
|
try {
|
|
1693
|
-
await runCommand(argv);
|
|
1694
|
-
return 0;
|
|
1868
|
+
return await runCommand(argv);
|
|
1695
1869
|
} catch (error) {
|
|
1696
1870
|
if (error instanceof R5dctlApiError) {
|
|
1697
1871
|
process.stderr.write(`${extractApiErrorMessage(error)}
|
|
@@ -1706,10 +1880,11 @@ async function runR5dctlCli(argv) {
|
|
|
1706
1880
|
async function main(argv = process.argv.slice(2)) {
|
|
1707
1881
|
const exitCode = await runR5dctlCli(argv);
|
|
1708
1882
|
if (exitCode !== 0) {
|
|
1709
|
-
process.
|
|
1883
|
+
process.exitCode = exitCode;
|
|
1710
1884
|
}
|
|
1711
1885
|
}
|
|
1712
1886
|
export {
|
|
1887
|
+
formatProcessAge,
|
|
1713
1888
|
getCliHelpText,
|
|
1714
1889
|
getR5dctlVersion,
|
|
1715
1890
|
main,
|
|
@@ -1718,9 +1893,12 @@ export {
|
|
|
1718
1893
|
parseEnvFlags,
|
|
1719
1894
|
parseGlobalArgs,
|
|
1720
1895
|
parsePromptArgs,
|
|
1896
|
+
parsePsListArgs,
|
|
1721
1897
|
parseSetEnvArgs,
|
|
1898
|
+
parseShellArgs,
|
|
1722
1899
|
readDotenvFile,
|
|
1723
1900
|
renderConversationResponse,
|
|
1901
|
+
renderProcessList,
|
|
1724
1902
|
resolveCommandExecution,
|
|
1725
1903
|
runR5dctlCli,
|
|
1726
1904
|
summarizeEnvData
|
package/dist/mjs/package.json
CHANGED