@ricsam/r5dctl 0.0.5 → 0.0.7
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 +4 -5
- package/dist/cjs/cli.cjs +233 -74
- package/dist/cjs/package.json +1 -1
- package/dist/mjs/cli.mjs +234 -75
- package/dist/mjs/package.json +1 -1
- package/dist/types/cli.d.ts +2 -2
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -29,7 +29,7 @@ Credentials are stored in:
|
|
|
29
29
|
- `--config <path>`
|
|
30
30
|
- `--token <token>`
|
|
31
31
|
- `--api-key <key>`
|
|
32
|
-
- `-p, --project <
|
|
32
|
+
- `-p, --project <namespace/name|id>`
|
|
33
33
|
- `-b, --branch <branch>`
|
|
34
34
|
- `-s, --session <sessionId>`
|
|
35
35
|
|
|
@@ -43,10 +43,9 @@ r5dctl auth api-key list
|
|
|
43
43
|
r5dctl auth api-key revoke <key-id>
|
|
44
44
|
|
|
45
45
|
r5dctl get projects
|
|
46
|
-
r5dctl
|
|
47
|
-
r5dctl
|
|
48
|
-
r5dctl
|
|
49
|
-
r5dctl delete project <project-ref>
|
|
46
|
+
r5dctl describe project <namespace/name|id>
|
|
47
|
+
r5dctl update project <namespace/name|id> --mode prod
|
|
48
|
+
r5dctl delete project <namespace/name|id>
|
|
50
49
|
|
|
51
50
|
r5dctl -p <project> get branches
|
|
52
51
|
r5dctl -p <project> describe branch <branch>
|
package/dist/cjs/cli.cjs
CHANGED
|
@@ -35,7 +35,7 @@ __export(cli_exports, {
|
|
|
35
35
|
parseGlobalArgs: () => parseGlobalArgs,
|
|
36
36
|
parsePromptArgs: () => parsePromptArgs,
|
|
37
37
|
resolveCommandExecution: () => resolveCommandExecution,
|
|
38
|
-
|
|
38
|
+
runR5dctlCli: () => runR5dctlCli
|
|
39
39
|
});
|
|
40
40
|
module.exports = __toCommonJS(cli_exports);
|
|
41
41
|
var import_node_fs = __toESM(require("node:fs"), 1);
|
|
@@ -44,15 +44,27 @@ var import_node_path = __toESM(require("node:path"), 1);
|
|
|
44
44
|
var import_node_child_process = require("node:child_process");
|
|
45
45
|
var import_promises = require("node:timers/promises");
|
|
46
46
|
var import_r5d_api = require("@ricsam/r5d-api");
|
|
47
|
-
const CHAT_MODES = /* @__PURE__ */ new Set([
|
|
47
|
+
const CHAT_MODES = /* @__PURE__ */ new Set([
|
|
48
|
+
"ask",
|
|
49
|
+
"plan",
|
|
50
|
+
"build",
|
|
51
|
+
"agent",
|
|
52
|
+
"explore",
|
|
53
|
+
"review",
|
|
54
|
+
"test",
|
|
55
|
+
"research",
|
|
56
|
+
"security_review",
|
|
57
|
+
"large_diff_remediation"
|
|
58
|
+
]);
|
|
48
59
|
const MODEL_TIERS = /* @__PURE__ */ new Set(["low", "medium", "high", "max"]);
|
|
60
|
+
const AGENT_TYPES = /* @__PURE__ */ new Set(["research", "debug", "test"]);
|
|
49
61
|
const CLI_GLOBAL_OPTION_HELP = [
|
|
50
62
|
"--base-url <url>",
|
|
51
63
|
"--json",
|
|
52
64
|
"--config <path>",
|
|
53
65
|
"--token <token>",
|
|
54
66
|
"--api-key <key>",
|
|
55
|
-
"-p, --project <
|
|
67
|
+
"-p, --project <namespace/name|id>",
|
|
56
68
|
"-b, --branch <branch>",
|
|
57
69
|
"-s, --session <sessionId>",
|
|
58
70
|
"-h, --help"
|
|
@@ -77,10 +89,9 @@ const SHARED_HELP_ENTRIES = [
|
|
|
77
89
|
{ section: "auth", usage: "auth api-key list", description: "List API keys for the current account." },
|
|
78
90
|
{ section: "auth", usage: "auth api-key revoke <key-id>", description: "Revoke an API key." },
|
|
79
91
|
{ section: "projects", usage: "get projects", description: "List projects you can access." },
|
|
80
|
-
{ section: "projects", usage: "
|
|
81
|
-
{ section: "projects", usage: "
|
|
82
|
-
{ section: "projects", usage: "
|
|
83
|
-
{ section: "projects", usage: "delete project <project-ref>", description: "Delete a project." },
|
|
92
|
+
{ section: "projects", usage: "describe project <namespace/name|id>", description: "Show details for a project." },
|
|
93
|
+
{ section: "projects", usage: "update project <namespace/name|id> --mode <greenfield|prod>", description: "Update project settings." },
|
|
94
|
+
{ section: "projects", usage: "delete project <namespace/name|id>", description: "Delete a project." },
|
|
84
95
|
{ section: "branches", usage: "-p <project> get branches", description: "List branches for a project." },
|
|
85
96
|
{ section: "branches", usage: "-p <project> describe branch <branch>", description: "Show branch URLs and sessions." },
|
|
86
97
|
{ section: "sessions", usage: "-p <project> get sessions [--branch <branch>]", description: "List sessions for a project, optionally filtered by branch." },
|
|
@@ -91,14 +102,22 @@ const SHARED_HELP_ENTRIES = [
|
|
|
91
102
|
{ section: "sessions", usage: "-s <session-id> conversation [--full] [--compact] [--tail <nodes>]", description: "Read a conversation transcript, pending questions, and required envs." },
|
|
92
103
|
{ section: "sessions", usage: '-s <session-id> prompt --mode <mode> --model <tier> "<message>"', description: "Send a prompt to a session." },
|
|
93
104
|
{ section: "sessions", usage: '-s <session-id> answer-questions -a1 "<answer>" -a2 "<answer>" ...', description: "Answer pending questions in order." },
|
|
94
|
-
{ section: "sessions", usage: "-s <session-id> apply-required-envs -e backend/KEY=value -e frontend/KEY=value", description: "Apply required backend or frontend env values." }
|
|
105
|
+
{ section: "sessions", usage: "-s <session-id> apply-required-envs -e backend/KEY=value -e frontend/KEY=value", description: "Apply required backend or frontend env values." },
|
|
106
|
+
{ section: "agents", usage: '-p <project> start-agent <src-branch> <agent-type> "<prompt>"', description: "Start a detached branch agent." },
|
|
107
|
+
{ section: "agents", usage: "agent-status <session-id>", description: "Show detached branch agent status." },
|
|
108
|
+
{ section: "agents", usage: 'send-prompt <session-id> "<prompt>"', description: "Queue or resume a detached branch agent." },
|
|
109
|
+
{ section: "merges", usage: "-p <project> merge-changes <target-branch> <sub-agent-branch>", description: "Squash merge an agent branch into a target branch." },
|
|
110
|
+
{ section: "merges", usage: "-p <project> continue-merge <target-branch>", description: "Commit a resolved merge." },
|
|
111
|
+
{ section: "merges", usage: "-p <project> abort-merge <target-branch>", description: "Abort an in-progress merge." }
|
|
95
112
|
];
|
|
96
|
-
const HELP_SECTION_ORDER = ["auth", "projects", "branches", "sessions"];
|
|
113
|
+
const HELP_SECTION_ORDER = ["auth", "projects", "branches", "sessions", "agents", "merges"];
|
|
97
114
|
const HELP_SECTION_TITLES = {
|
|
98
115
|
auth: "Auth",
|
|
99
116
|
projects: "Projects",
|
|
100
117
|
branches: "Branches",
|
|
101
|
-
sessions: "Sessions"
|
|
118
|
+
sessions: "Sessions",
|
|
119
|
+
agents: "Agents",
|
|
120
|
+
merges: "Merges"
|
|
102
121
|
};
|
|
103
122
|
function getCliHelpText() {
|
|
104
123
|
const entries = [...CLI_ONLY_HELP_ENTRIES, ...SHARED_HELP_ENTRIES];
|
|
@@ -323,9 +342,9 @@ function writeConfig(configPath, config) {
|
|
|
323
342
|
}
|
|
324
343
|
function resolveClientOptions(options, config) {
|
|
325
344
|
return {
|
|
326
|
-
baseUrl: options.baseUrl ?? process.env.R5D_BASE_URL ?? process.env.
|
|
327
|
-
token: options.token ?? process.env.R5D_TOKEN ?? process.env.
|
|
328
|
-
apiKey: options.apiKey ?? process.env.R5D_API_KEY ?? process.env.
|
|
345
|
+
baseUrl: options.baseUrl ?? process.env.R5D_BASE_URL ?? process.env.R5DCTL_BASE_URL ?? config.baseUrl,
|
|
346
|
+
token: options.token ?? process.env.R5D_TOKEN ?? process.env.R5DCTL_TOKEN ?? config.token,
|
|
347
|
+
apiKey: options.apiKey ?? process.env.R5D_API_KEY ?? process.env.R5DCTL_API_KEY ?? config.apiKey
|
|
329
348
|
};
|
|
330
349
|
}
|
|
331
350
|
function parseAnswerFlags(args) {
|
|
@@ -460,7 +479,7 @@ function parsePromptArgs(args) {
|
|
|
460
479
|
throw new Error("--mode is required for `prompt`");
|
|
461
480
|
}
|
|
462
481
|
if (!CHAT_MODES.has(modeRaw)) {
|
|
463
|
-
throw new Error(`Invalid --mode '${modeRaw}'. Expected one of:
|
|
482
|
+
throw new Error(`Invalid --mode '${modeRaw}'. Expected one of: ${Array.from(CHAT_MODES).join(", ")}`);
|
|
464
483
|
}
|
|
465
484
|
if (!modelRaw) {
|
|
466
485
|
throw new Error("--model is required for `prompt`");
|
|
@@ -593,14 +612,15 @@ function clearAuthFromConfig(config, options) {
|
|
|
593
612
|
};
|
|
594
613
|
}
|
|
595
614
|
function renderProjectDescription(project) {
|
|
596
|
-
return [`Project: ${project.
|
|
615
|
+
return [`Project: ${project.path}`, `Ref: ${project.path}`, `ID: ${project.id}`, `Mode: ${project.mode}`].join("\n") + "\n";
|
|
597
616
|
}
|
|
598
617
|
function renderProjectList(projects) {
|
|
599
618
|
if (projects.length === 0) {
|
|
600
619
|
return "No projects found.\n";
|
|
601
620
|
}
|
|
602
|
-
return `${projects.map((project) => `${project.
|
|
603
|
-
Ref: ${project.
|
|
621
|
+
return `${projects.map((project) => `${project.path}
|
|
622
|
+
Ref: ${project.path}
|
|
623
|
+
ID: ${project.id}
|
|
604
624
|
Mode: ${project.mode}`).join("\n\n")}
|
|
605
625
|
`;
|
|
606
626
|
}
|
|
@@ -612,50 +632,55 @@ function renderSessionList(sessions) {
|
|
|
612
632
|
`;
|
|
613
633
|
}
|
|
614
634
|
function renderSessionDescription(session) {
|
|
615
|
-
return [`Session: ${session.id}`, `Project: ${session.
|
|
635
|
+
return [`Session: ${session.id}`, `Project: ${session.projectPath}`, `Branch: ${session.branchName}`].join("\n") + "\n";
|
|
616
636
|
}
|
|
617
|
-
function
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
}
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
continue;
|
|
635
|
-
}
|
|
636
|
-
const inlineOrg = arg ? parseLongOptionWithEquals(arg, "--org") ?? parseLongOptionWithEquals(arg, "--org-id") : void 0;
|
|
637
|
-
if (inlineOrg !== void 0) {
|
|
638
|
-
input.orgId = inlineOrg;
|
|
639
|
-
continue;
|
|
640
|
-
}
|
|
641
|
-
throw new Error(`Unknown project create argument: ${arg}`);
|
|
637
|
+
function renderAgentStart(agent) {
|
|
638
|
+
return [
|
|
639
|
+
`Agent: ${agent.sessionId}`,
|
|
640
|
+
`Status: ${agent.status}`,
|
|
641
|
+
`Branch: ${agent.branchName}`,
|
|
642
|
+
`Base: ${agent.baseBranch} @ ${agent.baseCommit}`
|
|
643
|
+
].join("\n") + "\n";
|
|
644
|
+
}
|
|
645
|
+
function renderAgentStatus(agent) {
|
|
646
|
+
const lines = [
|
|
647
|
+
`Agent: ${agent.sessionId}`,
|
|
648
|
+
`Status: ${agent.status}`,
|
|
649
|
+
`Branch: ${agent.branchName}`,
|
|
650
|
+
`Head: ${agent.headCommit}`
|
|
651
|
+
];
|
|
652
|
+
if (agent.baseBranch && agent.baseCommit) {
|
|
653
|
+
lines.push(`Base: ${agent.baseBranch} @ ${agent.baseCommit}`);
|
|
642
654
|
}
|
|
643
|
-
|
|
655
|
+
if (agent.error) {
|
|
656
|
+
lines.push(`Error: ${agent.error}`);
|
|
657
|
+
}
|
|
658
|
+
if (agent.diffSummary) {
|
|
659
|
+
lines.push("", agent.diffSummary);
|
|
660
|
+
}
|
|
661
|
+
if (agent.summary) {
|
|
662
|
+
lines.push("", agent.summary);
|
|
663
|
+
}
|
|
664
|
+
return `${lines.join("\n")}
|
|
665
|
+
`;
|
|
666
|
+
}
|
|
667
|
+
function renderMergeResult(result) {
|
|
668
|
+
if (result.status === "merged") {
|
|
669
|
+
return `Merged ${result.sourceBranch} into ${result.targetBranch}: ${result.commitHash}
|
|
670
|
+
${result.message}
|
|
671
|
+
`;
|
|
672
|
+
}
|
|
673
|
+
const files = result.conflictedFiles.length > 0 ? `
|
|
674
|
+
Conflicts:
|
|
675
|
+
${result.conflictedFiles.map((file) => `- ${file}`).join("\n")}
|
|
676
|
+
` : "\n";
|
|
677
|
+
return `Merge has conflicts from ${result.sourceBranch} into ${result.targetBranch}.${files}${result.message}
|
|
678
|
+
`;
|
|
644
679
|
}
|
|
645
680
|
function parseProjectUpdateArgs(args) {
|
|
646
681
|
const input = {};
|
|
647
682
|
for (let index = 0; index < args.length; index += 1) {
|
|
648
683
|
const arg = args[index];
|
|
649
|
-
if (arg === "--name") {
|
|
650
|
-
input.name = requireValue(args[index + 1], "Missing value for --name");
|
|
651
|
-
index += 1;
|
|
652
|
-
continue;
|
|
653
|
-
}
|
|
654
|
-
const inlineName = arg ? parseLongOptionWithEquals(arg, "--name") : void 0;
|
|
655
|
-
if (inlineName !== void 0) {
|
|
656
|
-
input.name = inlineName;
|
|
657
|
-
continue;
|
|
658
|
-
}
|
|
659
684
|
if (arg === "--mode") {
|
|
660
685
|
const mode = requireValue(args[index + 1], "Missing value for --mode");
|
|
661
686
|
if (mode !== "greenfield" && mode !== "prod") throw new Error("Invalid value for --mode: expected greenfield or prod");
|
|
@@ -760,7 +785,7 @@ function parseConversationRenderArgs(args) {
|
|
|
760
785
|
function renderConversationResponse(read, options = { full: false, compact: false }) {
|
|
761
786
|
const thread = Array.isArray(read.thread) ? read.thread : [];
|
|
762
787
|
const visibleThread = options.tail ? thread.slice(-options.tail) : thread;
|
|
763
|
-
const lines = [`Session: ${read.session.id}`, `Project: ${read.session.
|
|
788
|
+
const lines = [`Session: ${read.session.id}`, `Project: ${read.session.projectPath}`, `Branch: ${read.session.branchName}`, ""];
|
|
764
789
|
lines.push(`Conversation nodes: ${visibleThread.length}${options.tail ? ` of ${thread.length}` : ""}`);
|
|
765
790
|
if (!options.compact) {
|
|
766
791
|
lines.push(JSON.stringify(visibleThread, null, options.full ? 2 : 0));
|
|
@@ -776,7 +801,7 @@ function renderConversationResponse(read, options = { full: false, compact: fals
|
|
|
776
801
|
return `${lines.join("\n")}
|
|
777
802
|
`;
|
|
778
803
|
}
|
|
779
|
-
async function
|
|
804
|
+
async function executeR5dctlCommand(client, json, args) {
|
|
780
805
|
const write = (data, human) => writeDataOutput(json, data, human);
|
|
781
806
|
const [first, second, third] = args;
|
|
782
807
|
if (first === "auth" && second === "status") {
|
|
@@ -806,11 +831,6 @@ async function executeBinctlCommand(client, json, args) {
|
|
|
806
831
|
write(projects, renderProjectList(projects));
|
|
807
832
|
return;
|
|
808
833
|
}
|
|
809
|
-
if (first === "projects" && second === "create" || first === "create" && second === "project") {
|
|
810
|
-
const project = await client.projects.create(parseProjectCreateArgs(args.slice(2)));
|
|
811
|
-
write(project, renderProjectDescription(project));
|
|
812
|
-
return;
|
|
813
|
-
}
|
|
814
834
|
if (first === "projects" && second === "describe" || first === "describe" && second === "project") {
|
|
815
835
|
const project = await client.projects.describe(requireValue(args[2], "Missing project reference"));
|
|
816
836
|
write(project, renderProjectDescription(project));
|
|
@@ -837,7 +857,7 @@ async function executeBinctlCommand(client, json, args) {
|
|
|
837
857
|
if (first === "projects" && second === "branches" && third === "describe" || first === "describe" && second === "branch") {
|
|
838
858
|
const offset = first === "describe" ? 2 : 3;
|
|
839
859
|
const result = await client.projects.branches.describe(requireValue(args[offset], "Missing project reference"), requireValue(args[offset + 1], "Missing branch name"));
|
|
840
|
-
write(result, `Project: ${result.project.
|
|
860
|
+
write(result, `Project: ${result.project.path}
|
|
841
861
|
Branch: ${result.branchName}
|
|
842
862
|
Sessions: ${result.sessions.length}
|
|
843
863
|
`);
|
|
@@ -857,6 +877,65 @@ Sessions: ${result.sessions.length}
|
|
|
857
877
|
write(session, renderSessionDescription(session));
|
|
858
878
|
return;
|
|
859
879
|
}
|
|
880
|
+
if (first === "start-agent") {
|
|
881
|
+
const projectRef = requireValue(args[1], "Missing project reference");
|
|
882
|
+
const sourceBranch = requireValue(args[2], "Missing source branch");
|
|
883
|
+
const agentType = requireValue(args[3], "Missing agent type");
|
|
884
|
+
if (!AGENT_TYPES.has(agentType)) {
|
|
885
|
+
throw new Error("Invalid agent type. Expected one of: research, debug, test");
|
|
886
|
+
}
|
|
887
|
+
const prompt = args.slice(4).join(" ").trim();
|
|
888
|
+
if (!prompt) {
|
|
889
|
+
throw new Error("Agent prompt is required");
|
|
890
|
+
}
|
|
891
|
+
const agent = await client.projects.agents.start(projectRef, {
|
|
892
|
+
sourceBranch,
|
|
893
|
+
agentType,
|
|
894
|
+
prompt
|
|
895
|
+
});
|
|
896
|
+
write(agent, renderAgentStart(agent));
|
|
897
|
+
return;
|
|
898
|
+
}
|
|
899
|
+
if (first === "agent-status") {
|
|
900
|
+
const agent = await client.agents.status(requireValue(args[1], "Missing session id"));
|
|
901
|
+
write(agent, renderAgentStatus(agent));
|
|
902
|
+
return;
|
|
903
|
+
}
|
|
904
|
+
if (first === "send-prompt") {
|
|
905
|
+
const sessionId = requireValue(args[1], "Missing session id");
|
|
906
|
+
const prompt = args.slice(2).join(" ").trim();
|
|
907
|
+
if (!prompt) {
|
|
908
|
+
throw new Error("Prompt is required");
|
|
909
|
+
}
|
|
910
|
+
const agent = await client.agents.sendPrompt(sessionId, { prompt });
|
|
911
|
+
write(agent, renderAgentStatus(agent));
|
|
912
|
+
return;
|
|
913
|
+
}
|
|
914
|
+
if (first === "merge-changes") {
|
|
915
|
+
const result = await client.projects.mergeChanges(requireValue(args[1], "Missing project reference"), {
|
|
916
|
+
targetBranch: requireValue(args[2], "Missing target branch"),
|
|
917
|
+
sourceBranch: requireValue(args[3], "Missing source branch")
|
|
918
|
+
});
|
|
919
|
+
write(result, renderMergeResult(result));
|
|
920
|
+
return;
|
|
921
|
+
}
|
|
922
|
+
if (first === "continue-merge") {
|
|
923
|
+
const result = await client.projects.continueMerge(requireValue(args[1], "Missing project reference"), {
|
|
924
|
+
targetBranch: requireValue(args[2], "Missing target branch")
|
|
925
|
+
});
|
|
926
|
+
write(result, `Merge committed: ${result.commitHash}
|
|
927
|
+
${result.message}
|
|
928
|
+
`);
|
|
929
|
+
return;
|
|
930
|
+
}
|
|
931
|
+
if (first === "abort-merge") {
|
|
932
|
+
const result = await client.projects.abortMerge(requireValue(args[1], "Missing project reference"), {
|
|
933
|
+
targetBranch: requireValue(args[2], "Missing target branch")
|
|
934
|
+
});
|
|
935
|
+
write(result, `${result.message}
|
|
936
|
+
`);
|
|
937
|
+
return;
|
|
938
|
+
}
|
|
860
939
|
if (first === "sessions" && second === "describe" || first === "describe" && second === "session") {
|
|
861
940
|
const session = await client.sessions.describe(requireValue(args[2], "Missing session id"));
|
|
862
941
|
write(session, renderSessionDescription(session));
|
|
@@ -1058,12 +1137,6 @@ function resolveCommandExecution(options, rest) {
|
|
|
1058
1137
|
}
|
|
1059
1138
|
if (command === "create") {
|
|
1060
1139
|
const target = commandArgs[0];
|
|
1061
|
-
if (target === "project") {
|
|
1062
|
-
return {
|
|
1063
|
-
kind: "plugin",
|
|
1064
|
-
pluginArgs: ["create", "project", ...commandArgs.slice(1)]
|
|
1065
|
-
};
|
|
1066
|
-
}
|
|
1067
1140
|
if (target === "session") {
|
|
1068
1141
|
const rawArgs = commandArgs.slice(1);
|
|
1069
1142
|
const firstFlagIndex = rawArgs.findIndex((arg) => arg.startsWith("-"));
|
|
@@ -1155,6 +1228,92 @@ function resolveCommandExecution(options, rest) {
|
|
|
1155
1228
|
pluginArgs: ["prompt", sessionId, parsed.mode, parsed.model, parsed.message]
|
|
1156
1229
|
};
|
|
1157
1230
|
}
|
|
1231
|
+
if (command === "start-agent") {
|
|
1232
|
+
if (!options.project) {
|
|
1233
|
+
throw new Error("--project/-p is required for `start-agent`");
|
|
1234
|
+
}
|
|
1235
|
+
const sourceBranch = commandArgs[0] ?? options.branch;
|
|
1236
|
+
if (!sourceBranch) {
|
|
1237
|
+
throw new Error("Source branch is required for `start-agent`");
|
|
1238
|
+
}
|
|
1239
|
+
const agentType = requireValue(commandArgs[1], "Agent type is required for `start-agent`");
|
|
1240
|
+
if (!AGENT_TYPES.has(agentType)) {
|
|
1241
|
+
throw new Error("Invalid agent type. Expected one of: research, debug, test");
|
|
1242
|
+
}
|
|
1243
|
+
const prompt = commandArgs.slice(2).join(" ").trim();
|
|
1244
|
+
if (!prompt) {
|
|
1245
|
+
throw new Error("Agent prompt is required");
|
|
1246
|
+
}
|
|
1247
|
+
return {
|
|
1248
|
+
kind: "plugin",
|
|
1249
|
+
pluginArgs: ["start-agent", options.project, sourceBranch, agentType, prompt]
|
|
1250
|
+
};
|
|
1251
|
+
}
|
|
1252
|
+
if (command === "agent-status") {
|
|
1253
|
+
const sessionId = commandArgs[0] ?? options.session;
|
|
1254
|
+
if (!sessionId) {
|
|
1255
|
+
throw new Error("Session id is required for `agent-status`");
|
|
1256
|
+
}
|
|
1257
|
+
return {
|
|
1258
|
+
kind: "plugin",
|
|
1259
|
+
pluginArgs: ["agent-status", sessionId]
|
|
1260
|
+
};
|
|
1261
|
+
}
|
|
1262
|
+
if (command === "send-prompt") {
|
|
1263
|
+
const sessionId = options.session ?? commandArgs[0];
|
|
1264
|
+
if (!sessionId) {
|
|
1265
|
+
throw new Error("Session id is required for `send-prompt`");
|
|
1266
|
+
}
|
|
1267
|
+
const promptArgs = options.session ? commandArgs : commandArgs.slice(1);
|
|
1268
|
+
const prompt = promptArgs.join(" ").trim();
|
|
1269
|
+
if (!prompt) {
|
|
1270
|
+
throw new Error("Prompt is required for `send-prompt`");
|
|
1271
|
+
}
|
|
1272
|
+
return {
|
|
1273
|
+
kind: "plugin",
|
|
1274
|
+
pluginArgs: ["send-prompt", sessionId, prompt]
|
|
1275
|
+
};
|
|
1276
|
+
}
|
|
1277
|
+
if (command === "merge-changes") {
|
|
1278
|
+
if (!options.project) {
|
|
1279
|
+
throw new Error("--project/-p is required for `merge-changes`");
|
|
1280
|
+
}
|
|
1281
|
+
const targetBranch = commandArgs[0] ?? options.branch;
|
|
1282
|
+
if (!targetBranch) {
|
|
1283
|
+
throw new Error("Target branch is required for `merge-changes`");
|
|
1284
|
+
}
|
|
1285
|
+
const sourceBranch = requireValue(commandArgs[1], "Sub-agent branch is required for `merge-changes`");
|
|
1286
|
+
return {
|
|
1287
|
+
kind: "plugin",
|
|
1288
|
+
pluginArgs: ["merge-changes", options.project, targetBranch, sourceBranch]
|
|
1289
|
+
};
|
|
1290
|
+
}
|
|
1291
|
+
if (command === "continue-merge") {
|
|
1292
|
+
if (!options.project) {
|
|
1293
|
+
throw new Error("--project/-p is required for `continue-merge`");
|
|
1294
|
+
}
|
|
1295
|
+
const targetBranch = commandArgs[0] ?? options.branch;
|
|
1296
|
+
if (!targetBranch) {
|
|
1297
|
+
throw new Error("Target branch is required for `continue-merge`");
|
|
1298
|
+
}
|
|
1299
|
+
return {
|
|
1300
|
+
kind: "plugin",
|
|
1301
|
+
pluginArgs: ["continue-merge", options.project, targetBranch]
|
|
1302
|
+
};
|
|
1303
|
+
}
|
|
1304
|
+
if (command === "abort-merge") {
|
|
1305
|
+
if (!options.project) {
|
|
1306
|
+
throw new Error("--project/-p is required for `abort-merge`");
|
|
1307
|
+
}
|
|
1308
|
+
const targetBranch = commandArgs[0] ?? options.branch;
|
|
1309
|
+
if (!targetBranch) {
|
|
1310
|
+
throw new Error("Target branch is required for `abort-merge`");
|
|
1311
|
+
}
|
|
1312
|
+
return {
|
|
1313
|
+
kind: "plugin",
|
|
1314
|
+
pluginArgs: ["abort-merge", options.project, targetBranch]
|
|
1315
|
+
};
|
|
1316
|
+
}
|
|
1158
1317
|
if (command === "answer-questions") {
|
|
1159
1318
|
const sessionId = options.session;
|
|
1160
1319
|
if (!sessionId) {
|
|
@@ -1184,7 +1343,7 @@ async function runCommand(argv) {
|
|
|
1184
1343
|
return;
|
|
1185
1344
|
}
|
|
1186
1345
|
const config = parseConfig(options.configPath);
|
|
1187
|
-
const client = new import_r5d_api.
|
|
1346
|
+
const client = new import_r5d_api.R5dctlClient(resolveClientOptions(options, config));
|
|
1188
1347
|
const execution = resolveCommandExecution(options, rest);
|
|
1189
1348
|
if (execution.kind === "auth-login") {
|
|
1190
1349
|
await handleAuthLogin(client, options, execution.commandArgs, config);
|
|
@@ -1198,7 +1357,7 @@ async function runCommand(argv) {
|
|
|
1198
1357
|
process.stdout.write(execution.text);
|
|
1199
1358
|
return;
|
|
1200
1359
|
}
|
|
1201
|
-
await
|
|
1360
|
+
await executeR5dctlCommand(client, options.json, execution.pluginArgs);
|
|
1202
1361
|
if (execution.clearAuthOnSuccess) {
|
|
1203
1362
|
writeConfig(options.configPath, clearAuthFromConfig(config, options));
|
|
1204
1363
|
}
|
|
@@ -1212,12 +1371,12 @@ function extractApiErrorMessage(error) {
|
|
|
1212
1371
|
}
|
|
1213
1372
|
return error.message;
|
|
1214
1373
|
}
|
|
1215
|
-
async function
|
|
1374
|
+
async function runR5dctlCli(argv) {
|
|
1216
1375
|
try {
|
|
1217
1376
|
await runCommand(argv);
|
|
1218
1377
|
return 0;
|
|
1219
1378
|
} catch (error) {
|
|
1220
|
-
if (error instanceof import_r5d_api.
|
|
1379
|
+
if (error instanceof import_r5d_api.R5dctlApiError) {
|
|
1221
1380
|
process.stderr.write(`${extractApiErrorMessage(error)}
|
|
1222
1381
|
`);
|
|
1223
1382
|
return 1;
|
|
@@ -1228,7 +1387,7 @@ async function runBinctlCli(argv) {
|
|
|
1228
1387
|
}
|
|
1229
1388
|
}
|
|
1230
1389
|
async function main(argv = process.argv.slice(2)) {
|
|
1231
|
-
const exitCode = await
|
|
1390
|
+
const exitCode = await runR5dctlCli(argv);
|
|
1232
1391
|
if (exitCode !== 0) {
|
|
1233
1392
|
process.exit(exitCode);
|
|
1234
1393
|
}
|
|
@@ -1242,5 +1401,5 @@ async function main(argv = process.argv.slice(2)) {
|
|
|
1242
1401
|
parseGlobalArgs,
|
|
1243
1402
|
parsePromptArgs,
|
|
1244
1403
|
resolveCommandExecution,
|
|
1245
|
-
|
|
1404
|
+
runR5dctlCli
|
|
1246
1405
|
});
|
package/dist/cjs/package.json
CHANGED
package/dist/mjs/cli.mjs
CHANGED
|
@@ -4,18 +4,30 @@ import path from "node:path";
|
|
|
4
4
|
import { spawn } from "node:child_process";
|
|
5
5
|
import { setTimeout as sleep } from "node:timers/promises";
|
|
6
6
|
import {
|
|
7
|
-
|
|
8
|
-
|
|
7
|
+
R5dctlApiError,
|
|
8
|
+
R5dctlClient
|
|
9
9
|
} from "@ricsam/r5d-api";
|
|
10
|
-
const CHAT_MODES = /* @__PURE__ */ new Set([
|
|
10
|
+
const CHAT_MODES = /* @__PURE__ */ new Set([
|
|
11
|
+
"ask",
|
|
12
|
+
"plan",
|
|
13
|
+
"build",
|
|
14
|
+
"agent",
|
|
15
|
+
"explore",
|
|
16
|
+
"review",
|
|
17
|
+
"test",
|
|
18
|
+
"research",
|
|
19
|
+
"security_review",
|
|
20
|
+
"large_diff_remediation"
|
|
21
|
+
]);
|
|
11
22
|
const MODEL_TIERS = /* @__PURE__ */ new Set(["low", "medium", "high", "max"]);
|
|
23
|
+
const AGENT_TYPES = /* @__PURE__ */ new Set(["research", "debug", "test"]);
|
|
12
24
|
const CLI_GLOBAL_OPTION_HELP = [
|
|
13
25
|
"--base-url <url>",
|
|
14
26
|
"--json",
|
|
15
27
|
"--config <path>",
|
|
16
28
|
"--token <token>",
|
|
17
29
|
"--api-key <key>",
|
|
18
|
-
"-p, --project <
|
|
30
|
+
"-p, --project <namespace/name|id>",
|
|
19
31
|
"-b, --branch <branch>",
|
|
20
32
|
"-s, --session <sessionId>",
|
|
21
33
|
"-h, --help"
|
|
@@ -40,10 +52,9 @@ const SHARED_HELP_ENTRIES = [
|
|
|
40
52
|
{ section: "auth", usage: "auth api-key list", description: "List API keys for the current account." },
|
|
41
53
|
{ section: "auth", usage: "auth api-key revoke <key-id>", description: "Revoke an API key." },
|
|
42
54
|
{ section: "projects", usage: "get projects", description: "List projects you can access." },
|
|
43
|
-
{ section: "projects", usage: "
|
|
44
|
-
{ section: "projects", usage: "
|
|
45
|
-
{ section: "projects", usage: "
|
|
46
|
-
{ section: "projects", usage: "delete project <project-ref>", description: "Delete a project." },
|
|
55
|
+
{ section: "projects", usage: "describe project <namespace/name|id>", description: "Show details for a project." },
|
|
56
|
+
{ section: "projects", usage: "update project <namespace/name|id> --mode <greenfield|prod>", description: "Update project settings." },
|
|
57
|
+
{ section: "projects", usage: "delete project <namespace/name|id>", description: "Delete a project." },
|
|
47
58
|
{ section: "branches", usage: "-p <project> get branches", description: "List branches for a project." },
|
|
48
59
|
{ section: "branches", usage: "-p <project> describe branch <branch>", description: "Show branch URLs and sessions." },
|
|
49
60
|
{ section: "sessions", usage: "-p <project> get sessions [--branch <branch>]", description: "List sessions for a project, optionally filtered by branch." },
|
|
@@ -54,14 +65,22 @@ const SHARED_HELP_ENTRIES = [
|
|
|
54
65
|
{ section: "sessions", usage: "-s <session-id> conversation [--full] [--compact] [--tail <nodes>]", description: "Read a conversation transcript, pending questions, and required envs." },
|
|
55
66
|
{ section: "sessions", usage: '-s <session-id> prompt --mode <mode> --model <tier> "<message>"', description: "Send a prompt to a session." },
|
|
56
67
|
{ section: "sessions", usage: '-s <session-id> answer-questions -a1 "<answer>" -a2 "<answer>" ...', description: "Answer pending questions in order." },
|
|
57
|
-
{ section: "sessions", usage: "-s <session-id> apply-required-envs -e backend/KEY=value -e frontend/KEY=value", description: "Apply required backend or frontend env values." }
|
|
68
|
+
{ section: "sessions", usage: "-s <session-id> apply-required-envs -e backend/KEY=value -e frontend/KEY=value", description: "Apply required backend or frontend env values." },
|
|
69
|
+
{ section: "agents", usage: '-p <project> start-agent <src-branch> <agent-type> "<prompt>"', description: "Start a detached branch agent." },
|
|
70
|
+
{ section: "agents", usage: "agent-status <session-id>", description: "Show detached branch agent status." },
|
|
71
|
+
{ section: "agents", usage: 'send-prompt <session-id> "<prompt>"', description: "Queue or resume a detached branch agent." },
|
|
72
|
+
{ section: "merges", usage: "-p <project> merge-changes <target-branch> <sub-agent-branch>", description: "Squash merge an agent branch into a target branch." },
|
|
73
|
+
{ section: "merges", usage: "-p <project> continue-merge <target-branch>", description: "Commit a resolved merge." },
|
|
74
|
+
{ section: "merges", usage: "-p <project> abort-merge <target-branch>", description: "Abort an in-progress merge." }
|
|
58
75
|
];
|
|
59
|
-
const HELP_SECTION_ORDER = ["auth", "projects", "branches", "sessions"];
|
|
76
|
+
const HELP_SECTION_ORDER = ["auth", "projects", "branches", "sessions", "agents", "merges"];
|
|
60
77
|
const HELP_SECTION_TITLES = {
|
|
61
78
|
auth: "Auth",
|
|
62
79
|
projects: "Projects",
|
|
63
80
|
branches: "Branches",
|
|
64
|
-
sessions: "Sessions"
|
|
81
|
+
sessions: "Sessions",
|
|
82
|
+
agents: "Agents",
|
|
83
|
+
merges: "Merges"
|
|
65
84
|
};
|
|
66
85
|
function getCliHelpText() {
|
|
67
86
|
const entries = [...CLI_ONLY_HELP_ENTRIES, ...SHARED_HELP_ENTRIES];
|
|
@@ -286,9 +305,9 @@ function writeConfig(configPath, config) {
|
|
|
286
305
|
}
|
|
287
306
|
function resolveClientOptions(options, config) {
|
|
288
307
|
return {
|
|
289
|
-
baseUrl: options.baseUrl ?? process.env.R5D_BASE_URL ?? process.env.
|
|
290
|
-
token: options.token ?? process.env.R5D_TOKEN ?? process.env.
|
|
291
|
-
apiKey: options.apiKey ?? process.env.R5D_API_KEY ?? process.env.
|
|
308
|
+
baseUrl: options.baseUrl ?? process.env.R5D_BASE_URL ?? process.env.R5DCTL_BASE_URL ?? config.baseUrl,
|
|
309
|
+
token: options.token ?? process.env.R5D_TOKEN ?? process.env.R5DCTL_TOKEN ?? config.token,
|
|
310
|
+
apiKey: options.apiKey ?? process.env.R5D_API_KEY ?? process.env.R5DCTL_API_KEY ?? config.apiKey
|
|
292
311
|
};
|
|
293
312
|
}
|
|
294
313
|
function parseAnswerFlags(args) {
|
|
@@ -423,7 +442,7 @@ function parsePromptArgs(args) {
|
|
|
423
442
|
throw new Error("--mode is required for `prompt`");
|
|
424
443
|
}
|
|
425
444
|
if (!CHAT_MODES.has(modeRaw)) {
|
|
426
|
-
throw new Error(`Invalid --mode '${modeRaw}'. Expected one of:
|
|
445
|
+
throw new Error(`Invalid --mode '${modeRaw}'. Expected one of: ${Array.from(CHAT_MODES).join(", ")}`);
|
|
427
446
|
}
|
|
428
447
|
if (!modelRaw) {
|
|
429
448
|
throw new Error("--model is required for `prompt`");
|
|
@@ -556,14 +575,15 @@ function clearAuthFromConfig(config, options) {
|
|
|
556
575
|
};
|
|
557
576
|
}
|
|
558
577
|
function renderProjectDescription(project) {
|
|
559
|
-
return [`Project: ${project.
|
|
578
|
+
return [`Project: ${project.path}`, `Ref: ${project.path}`, `ID: ${project.id}`, `Mode: ${project.mode}`].join("\n") + "\n";
|
|
560
579
|
}
|
|
561
580
|
function renderProjectList(projects) {
|
|
562
581
|
if (projects.length === 0) {
|
|
563
582
|
return "No projects found.\n";
|
|
564
583
|
}
|
|
565
|
-
return `${projects.map((project) => `${project.
|
|
566
|
-
Ref: ${project.
|
|
584
|
+
return `${projects.map((project) => `${project.path}
|
|
585
|
+
Ref: ${project.path}
|
|
586
|
+
ID: ${project.id}
|
|
567
587
|
Mode: ${project.mode}`).join("\n\n")}
|
|
568
588
|
`;
|
|
569
589
|
}
|
|
@@ -575,50 +595,55 @@ function renderSessionList(sessions) {
|
|
|
575
595
|
`;
|
|
576
596
|
}
|
|
577
597
|
function renderSessionDescription(session) {
|
|
578
|
-
return [`Session: ${session.id}`, `Project: ${session.
|
|
598
|
+
return [`Session: ${session.id}`, `Project: ${session.projectPath}`, `Branch: ${session.branchName}`].join("\n") + "\n";
|
|
579
599
|
}
|
|
580
|
-
function
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
}
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
continue;
|
|
598
|
-
}
|
|
599
|
-
const inlineOrg = arg ? parseLongOptionWithEquals(arg, "--org") ?? parseLongOptionWithEquals(arg, "--org-id") : void 0;
|
|
600
|
-
if (inlineOrg !== void 0) {
|
|
601
|
-
input.orgId = inlineOrg;
|
|
602
|
-
continue;
|
|
603
|
-
}
|
|
604
|
-
throw new Error(`Unknown project create argument: ${arg}`);
|
|
600
|
+
function renderAgentStart(agent) {
|
|
601
|
+
return [
|
|
602
|
+
`Agent: ${agent.sessionId}`,
|
|
603
|
+
`Status: ${agent.status}`,
|
|
604
|
+
`Branch: ${agent.branchName}`,
|
|
605
|
+
`Base: ${agent.baseBranch} @ ${agent.baseCommit}`
|
|
606
|
+
].join("\n") + "\n";
|
|
607
|
+
}
|
|
608
|
+
function renderAgentStatus(agent) {
|
|
609
|
+
const lines = [
|
|
610
|
+
`Agent: ${agent.sessionId}`,
|
|
611
|
+
`Status: ${agent.status}`,
|
|
612
|
+
`Branch: ${agent.branchName}`,
|
|
613
|
+
`Head: ${agent.headCommit}`
|
|
614
|
+
];
|
|
615
|
+
if (agent.baseBranch && agent.baseCommit) {
|
|
616
|
+
lines.push(`Base: ${agent.baseBranch} @ ${agent.baseCommit}`);
|
|
605
617
|
}
|
|
606
|
-
|
|
618
|
+
if (agent.error) {
|
|
619
|
+
lines.push(`Error: ${agent.error}`);
|
|
620
|
+
}
|
|
621
|
+
if (agent.diffSummary) {
|
|
622
|
+
lines.push("", agent.diffSummary);
|
|
623
|
+
}
|
|
624
|
+
if (agent.summary) {
|
|
625
|
+
lines.push("", agent.summary);
|
|
626
|
+
}
|
|
627
|
+
return `${lines.join("\n")}
|
|
628
|
+
`;
|
|
629
|
+
}
|
|
630
|
+
function renderMergeResult(result) {
|
|
631
|
+
if (result.status === "merged") {
|
|
632
|
+
return `Merged ${result.sourceBranch} into ${result.targetBranch}: ${result.commitHash}
|
|
633
|
+
${result.message}
|
|
634
|
+
`;
|
|
635
|
+
}
|
|
636
|
+
const files = result.conflictedFiles.length > 0 ? `
|
|
637
|
+
Conflicts:
|
|
638
|
+
${result.conflictedFiles.map((file) => `- ${file}`).join("\n")}
|
|
639
|
+
` : "\n";
|
|
640
|
+
return `Merge has conflicts from ${result.sourceBranch} into ${result.targetBranch}.${files}${result.message}
|
|
641
|
+
`;
|
|
607
642
|
}
|
|
608
643
|
function parseProjectUpdateArgs(args) {
|
|
609
644
|
const input = {};
|
|
610
645
|
for (let index = 0; index < args.length; index += 1) {
|
|
611
646
|
const arg = args[index];
|
|
612
|
-
if (arg === "--name") {
|
|
613
|
-
input.name = requireValue(args[index + 1], "Missing value for --name");
|
|
614
|
-
index += 1;
|
|
615
|
-
continue;
|
|
616
|
-
}
|
|
617
|
-
const inlineName = arg ? parseLongOptionWithEquals(arg, "--name") : void 0;
|
|
618
|
-
if (inlineName !== void 0) {
|
|
619
|
-
input.name = inlineName;
|
|
620
|
-
continue;
|
|
621
|
-
}
|
|
622
647
|
if (arg === "--mode") {
|
|
623
648
|
const mode = requireValue(args[index + 1], "Missing value for --mode");
|
|
624
649
|
if (mode !== "greenfield" && mode !== "prod") throw new Error("Invalid value for --mode: expected greenfield or prod");
|
|
@@ -723,7 +748,7 @@ function parseConversationRenderArgs(args) {
|
|
|
723
748
|
function renderConversationResponse(read, options = { full: false, compact: false }) {
|
|
724
749
|
const thread = Array.isArray(read.thread) ? read.thread : [];
|
|
725
750
|
const visibleThread = options.tail ? thread.slice(-options.tail) : thread;
|
|
726
|
-
const lines = [`Session: ${read.session.id}`, `Project: ${read.session.
|
|
751
|
+
const lines = [`Session: ${read.session.id}`, `Project: ${read.session.projectPath}`, `Branch: ${read.session.branchName}`, ""];
|
|
727
752
|
lines.push(`Conversation nodes: ${visibleThread.length}${options.tail ? ` of ${thread.length}` : ""}`);
|
|
728
753
|
if (!options.compact) {
|
|
729
754
|
lines.push(JSON.stringify(visibleThread, null, options.full ? 2 : 0));
|
|
@@ -739,7 +764,7 @@ function renderConversationResponse(read, options = { full: false, compact: fals
|
|
|
739
764
|
return `${lines.join("\n")}
|
|
740
765
|
`;
|
|
741
766
|
}
|
|
742
|
-
async function
|
|
767
|
+
async function executeR5dctlCommand(client, json, args) {
|
|
743
768
|
const write = (data, human) => writeDataOutput(json, data, human);
|
|
744
769
|
const [first, second, third] = args;
|
|
745
770
|
if (first === "auth" && second === "status") {
|
|
@@ -769,11 +794,6 @@ async function executeBinctlCommand(client, json, args) {
|
|
|
769
794
|
write(projects, renderProjectList(projects));
|
|
770
795
|
return;
|
|
771
796
|
}
|
|
772
|
-
if (first === "projects" && second === "create" || first === "create" && second === "project") {
|
|
773
|
-
const project = await client.projects.create(parseProjectCreateArgs(args.slice(2)));
|
|
774
|
-
write(project, renderProjectDescription(project));
|
|
775
|
-
return;
|
|
776
|
-
}
|
|
777
797
|
if (first === "projects" && second === "describe" || first === "describe" && second === "project") {
|
|
778
798
|
const project = await client.projects.describe(requireValue(args[2], "Missing project reference"));
|
|
779
799
|
write(project, renderProjectDescription(project));
|
|
@@ -800,7 +820,7 @@ async function executeBinctlCommand(client, json, args) {
|
|
|
800
820
|
if (first === "projects" && second === "branches" && third === "describe" || first === "describe" && second === "branch") {
|
|
801
821
|
const offset = first === "describe" ? 2 : 3;
|
|
802
822
|
const result = await client.projects.branches.describe(requireValue(args[offset], "Missing project reference"), requireValue(args[offset + 1], "Missing branch name"));
|
|
803
|
-
write(result, `Project: ${result.project.
|
|
823
|
+
write(result, `Project: ${result.project.path}
|
|
804
824
|
Branch: ${result.branchName}
|
|
805
825
|
Sessions: ${result.sessions.length}
|
|
806
826
|
`);
|
|
@@ -820,6 +840,65 @@ Sessions: ${result.sessions.length}
|
|
|
820
840
|
write(session, renderSessionDescription(session));
|
|
821
841
|
return;
|
|
822
842
|
}
|
|
843
|
+
if (first === "start-agent") {
|
|
844
|
+
const projectRef = requireValue(args[1], "Missing project reference");
|
|
845
|
+
const sourceBranch = requireValue(args[2], "Missing source branch");
|
|
846
|
+
const agentType = requireValue(args[3], "Missing agent type");
|
|
847
|
+
if (!AGENT_TYPES.has(agentType)) {
|
|
848
|
+
throw new Error("Invalid agent type. Expected one of: research, debug, test");
|
|
849
|
+
}
|
|
850
|
+
const prompt = args.slice(4).join(" ").trim();
|
|
851
|
+
if (!prompt) {
|
|
852
|
+
throw new Error("Agent prompt is required");
|
|
853
|
+
}
|
|
854
|
+
const agent = await client.projects.agents.start(projectRef, {
|
|
855
|
+
sourceBranch,
|
|
856
|
+
agentType,
|
|
857
|
+
prompt
|
|
858
|
+
});
|
|
859
|
+
write(agent, renderAgentStart(agent));
|
|
860
|
+
return;
|
|
861
|
+
}
|
|
862
|
+
if (first === "agent-status") {
|
|
863
|
+
const agent = await client.agents.status(requireValue(args[1], "Missing session id"));
|
|
864
|
+
write(agent, renderAgentStatus(agent));
|
|
865
|
+
return;
|
|
866
|
+
}
|
|
867
|
+
if (first === "send-prompt") {
|
|
868
|
+
const sessionId = requireValue(args[1], "Missing session id");
|
|
869
|
+
const prompt = args.slice(2).join(" ").trim();
|
|
870
|
+
if (!prompt) {
|
|
871
|
+
throw new Error("Prompt is required");
|
|
872
|
+
}
|
|
873
|
+
const agent = await client.agents.sendPrompt(sessionId, { prompt });
|
|
874
|
+
write(agent, renderAgentStatus(agent));
|
|
875
|
+
return;
|
|
876
|
+
}
|
|
877
|
+
if (first === "merge-changes") {
|
|
878
|
+
const result = await client.projects.mergeChanges(requireValue(args[1], "Missing project reference"), {
|
|
879
|
+
targetBranch: requireValue(args[2], "Missing target branch"),
|
|
880
|
+
sourceBranch: requireValue(args[3], "Missing source branch")
|
|
881
|
+
});
|
|
882
|
+
write(result, renderMergeResult(result));
|
|
883
|
+
return;
|
|
884
|
+
}
|
|
885
|
+
if (first === "continue-merge") {
|
|
886
|
+
const result = await client.projects.continueMerge(requireValue(args[1], "Missing project reference"), {
|
|
887
|
+
targetBranch: requireValue(args[2], "Missing target branch")
|
|
888
|
+
});
|
|
889
|
+
write(result, `Merge committed: ${result.commitHash}
|
|
890
|
+
${result.message}
|
|
891
|
+
`);
|
|
892
|
+
return;
|
|
893
|
+
}
|
|
894
|
+
if (first === "abort-merge") {
|
|
895
|
+
const result = await client.projects.abortMerge(requireValue(args[1], "Missing project reference"), {
|
|
896
|
+
targetBranch: requireValue(args[2], "Missing target branch")
|
|
897
|
+
});
|
|
898
|
+
write(result, `${result.message}
|
|
899
|
+
`);
|
|
900
|
+
return;
|
|
901
|
+
}
|
|
823
902
|
if (first === "sessions" && second === "describe" || first === "describe" && second === "session") {
|
|
824
903
|
const session = await client.sessions.describe(requireValue(args[2], "Missing session id"));
|
|
825
904
|
write(session, renderSessionDescription(session));
|
|
@@ -1021,12 +1100,6 @@ function resolveCommandExecution(options, rest) {
|
|
|
1021
1100
|
}
|
|
1022
1101
|
if (command === "create") {
|
|
1023
1102
|
const target = commandArgs[0];
|
|
1024
|
-
if (target === "project") {
|
|
1025
|
-
return {
|
|
1026
|
-
kind: "plugin",
|
|
1027
|
-
pluginArgs: ["create", "project", ...commandArgs.slice(1)]
|
|
1028
|
-
};
|
|
1029
|
-
}
|
|
1030
1103
|
if (target === "session") {
|
|
1031
1104
|
const rawArgs = commandArgs.slice(1);
|
|
1032
1105
|
const firstFlagIndex = rawArgs.findIndex((arg) => arg.startsWith("-"));
|
|
@@ -1118,6 +1191,92 @@ function resolveCommandExecution(options, rest) {
|
|
|
1118
1191
|
pluginArgs: ["prompt", sessionId, parsed.mode, parsed.model, parsed.message]
|
|
1119
1192
|
};
|
|
1120
1193
|
}
|
|
1194
|
+
if (command === "start-agent") {
|
|
1195
|
+
if (!options.project) {
|
|
1196
|
+
throw new Error("--project/-p is required for `start-agent`");
|
|
1197
|
+
}
|
|
1198
|
+
const sourceBranch = commandArgs[0] ?? options.branch;
|
|
1199
|
+
if (!sourceBranch) {
|
|
1200
|
+
throw new Error("Source branch is required for `start-agent`");
|
|
1201
|
+
}
|
|
1202
|
+
const agentType = requireValue(commandArgs[1], "Agent type is required for `start-agent`");
|
|
1203
|
+
if (!AGENT_TYPES.has(agentType)) {
|
|
1204
|
+
throw new Error("Invalid agent type. Expected one of: research, debug, test");
|
|
1205
|
+
}
|
|
1206
|
+
const prompt = commandArgs.slice(2).join(" ").trim();
|
|
1207
|
+
if (!prompt) {
|
|
1208
|
+
throw new Error("Agent prompt is required");
|
|
1209
|
+
}
|
|
1210
|
+
return {
|
|
1211
|
+
kind: "plugin",
|
|
1212
|
+
pluginArgs: ["start-agent", options.project, sourceBranch, agentType, prompt]
|
|
1213
|
+
};
|
|
1214
|
+
}
|
|
1215
|
+
if (command === "agent-status") {
|
|
1216
|
+
const sessionId = commandArgs[0] ?? options.session;
|
|
1217
|
+
if (!sessionId) {
|
|
1218
|
+
throw new Error("Session id is required for `agent-status`");
|
|
1219
|
+
}
|
|
1220
|
+
return {
|
|
1221
|
+
kind: "plugin",
|
|
1222
|
+
pluginArgs: ["agent-status", sessionId]
|
|
1223
|
+
};
|
|
1224
|
+
}
|
|
1225
|
+
if (command === "send-prompt") {
|
|
1226
|
+
const sessionId = options.session ?? commandArgs[0];
|
|
1227
|
+
if (!sessionId) {
|
|
1228
|
+
throw new Error("Session id is required for `send-prompt`");
|
|
1229
|
+
}
|
|
1230
|
+
const promptArgs = options.session ? commandArgs : commandArgs.slice(1);
|
|
1231
|
+
const prompt = promptArgs.join(" ").trim();
|
|
1232
|
+
if (!prompt) {
|
|
1233
|
+
throw new Error("Prompt is required for `send-prompt`");
|
|
1234
|
+
}
|
|
1235
|
+
return {
|
|
1236
|
+
kind: "plugin",
|
|
1237
|
+
pluginArgs: ["send-prompt", sessionId, prompt]
|
|
1238
|
+
};
|
|
1239
|
+
}
|
|
1240
|
+
if (command === "merge-changes") {
|
|
1241
|
+
if (!options.project) {
|
|
1242
|
+
throw new Error("--project/-p is required for `merge-changes`");
|
|
1243
|
+
}
|
|
1244
|
+
const targetBranch = commandArgs[0] ?? options.branch;
|
|
1245
|
+
if (!targetBranch) {
|
|
1246
|
+
throw new Error("Target branch is required for `merge-changes`");
|
|
1247
|
+
}
|
|
1248
|
+
const sourceBranch = requireValue(commandArgs[1], "Sub-agent branch is required for `merge-changes`");
|
|
1249
|
+
return {
|
|
1250
|
+
kind: "plugin",
|
|
1251
|
+
pluginArgs: ["merge-changes", options.project, targetBranch, sourceBranch]
|
|
1252
|
+
};
|
|
1253
|
+
}
|
|
1254
|
+
if (command === "continue-merge") {
|
|
1255
|
+
if (!options.project) {
|
|
1256
|
+
throw new Error("--project/-p is required for `continue-merge`");
|
|
1257
|
+
}
|
|
1258
|
+
const targetBranch = commandArgs[0] ?? options.branch;
|
|
1259
|
+
if (!targetBranch) {
|
|
1260
|
+
throw new Error("Target branch is required for `continue-merge`");
|
|
1261
|
+
}
|
|
1262
|
+
return {
|
|
1263
|
+
kind: "plugin",
|
|
1264
|
+
pluginArgs: ["continue-merge", options.project, targetBranch]
|
|
1265
|
+
};
|
|
1266
|
+
}
|
|
1267
|
+
if (command === "abort-merge") {
|
|
1268
|
+
if (!options.project) {
|
|
1269
|
+
throw new Error("--project/-p is required for `abort-merge`");
|
|
1270
|
+
}
|
|
1271
|
+
const targetBranch = commandArgs[0] ?? options.branch;
|
|
1272
|
+
if (!targetBranch) {
|
|
1273
|
+
throw new Error("Target branch is required for `abort-merge`");
|
|
1274
|
+
}
|
|
1275
|
+
return {
|
|
1276
|
+
kind: "plugin",
|
|
1277
|
+
pluginArgs: ["abort-merge", options.project, targetBranch]
|
|
1278
|
+
};
|
|
1279
|
+
}
|
|
1121
1280
|
if (command === "answer-questions") {
|
|
1122
1281
|
const sessionId = options.session;
|
|
1123
1282
|
if (!sessionId) {
|
|
@@ -1147,7 +1306,7 @@ async function runCommand(argv) {
|
|
|
1147
1306
|
return;
|
|
1148
1307
|
}
|
|
1149
1308
|
const config = parseConfig(options.configPath);
|
|
1150
|
-
const client = new
|
|
1309
|
+
const client = new R5dctlClient(resolveClientOptions(options, config));
|
|
1151
1310
|
const execution = resolveCommandExecution(options, rest);
|
|
1152
1311
|
if (execution.kind === "auth-login") {
|
|
1153
1312
|
await handleAuthLogin(client, options, execution.commandArgs, config);
|
|
@@ -1161,7 +1320,7 @@ async function runCommand(argv) {
|
|
|
1161
1320
|
process.stdout.write(execution.text);
|
|
1162
1321
|
return;
|
|
1163
1322
|
}
|
|
1164
|
-
await
|
|
1323
|
+
await executeR5dctlCommand(client, options.json, execution.pluginArgs);
|
|
1165
1324
|
if (execution.clearAuthOnSuccess) {
|
|
1166
1325
|
writeConfig(options.configPath, clearAuthFromConfig(config, options));
|
|
1167
1326
|
}
|
|
@@ -1175,12 +1334,12 @@ function extractApiErrorMessage(error) {
|
|
|
1175
1334
|
}
|
|
1176
1335
|
return error.message;
|
|
1177
1336
|
}
|
|
1178
|
-
async function
|
|
1337
|
+
async function runR5dctlCli(argv) {
|
|
1179
1338
|
try {
|
|
1180
1339
|
await runCommand(argv);
|
|
1181
1340
|
return 0;
|
|
1182
1341
|
} catch (error) {
|
|
1183
|
-
if (error instanceof
|
|
1342
|
+
if (error instanceof R5dctlApiError) {
|
|
1184
1343
|
process.stderr.write(`${extractApiErrorMessage(error)}
|
|
1185
1344
|
`);
|
|
1186
1345
|
return 1;
|
|
@@ -1191,7 +1350,7 @@ async function runBinctlCli(argv) {
|
|
|
1191
1350
|
}
|
|
1192
1351
|
}
|
|
1193
1352
|
async function main(argv = process.argv.slice(2)) {
|
|
1194
|
-
const exitCode = await
|
|
1353
|
+
const exitCode = await runR5dctlCli(argv);
|
|
1195
1354
|
if (exitCode !== 0) {
|
|
1196
1355
|
process.exit(exitCode);
|
|
1197
1356
|
}
|
|
@@ -1204,5 +1363,5 @@ export {
|
|
|
1204
1363
|
parseGlobalArgs,
|
|
1205
1364
|
parsePromptArgs,
|
|
1206
1365
|
resolveCommandExecution,
|
|
1207
|
-
|
|
1366
|
+
runR5dctlCli
|
|
1208
1367
|
};
|
package/dist/mjs/package.json
CHANGED
package/dist/types/cli.d.ts
CHANGED
|
@@ -10,7 +10,7 @@ export type GlobalOptions = {
|
|
|
10
10
|
session?: string;
|
|
11
11
|
help: boolean;
|
|
12
12
|
};
|
|
13
|
-
export type
|
|
13
|
+
export type R5dctlConfig = {
|
|
14
14
|
baseUrl?: string;
|
|
15
15
|
token?: string;
|
|
16
16
|
apiKey?: string;
|
|
@@ -42,6 +42,6 @@ export declare function parsePromptArgs(args: string[]): {
|
|
|
42
42
|
message: string;
|
|
43
43
|
};
|
|
44
44
|
export declare function resolveCommandExecution(options: GlobalOptions, rest: string[]): CommandExecutionPlan;
|
|
45
|
-
export declare function
|
|
45
|
+
export declare function runR5dctlCli(argv: string[]): Promise<number>;
|
|
46
46
|
export declare function main(argv?: string[]): Promise<void>;
|
|
47
47
|
export {};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ricsam/r5dctl",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.7",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "./dist/cjs/cli.cjs",
|
|
6
6
|
"module": "./dist/mjs/cli.mjs",
|
|
@@ -20,13 +20,13 @@
|
|
|
20
20
|
"repository": {
|
|
21
21
|
"type": "git",
|
|
22
22
|
"url": "git+https://github.com/ricsam/r5d-dev.git",
|
|
23
|
-
"directory": "
|
|
23
|
+
"directory": "packages/r5dctl"
|
|
24
24
|
},
|
|
25
25
|
"bin": {
|
|
26
26
|
"r5dctl": "dist/cjs/main.cjs"
|
|
27
27
|
},
|
|
28
28
|
"dependencies": {
|
|
29
|
-
"@ricsam/r5d-api": "^0.0.
|
|
29
|
+
"@ricsam/r5d-api": "^0.0.7"
|
|
30
30
|
},
|
|
31
31
|
"files": [
|
|
32
32
|
"dist",
|