@ricsam/r5dctl 0.0.6 → 0.0.8
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/dist/cjs/cli.cjs +224 -16
- package/dist/cjs/package.json +1 -1
- package/dist/mjs/cli.mjs +225 -17
- package/dist/mjs/package.json +1 -1
- package/dist/types/cli.d.ts +2 -2
- package/package.json +3 -3
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,8 +44,20 @@ 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",
|
|
@@ -90,14 +102,22 @@ const SHARED_HELP_ENTRIES = [
|
|
|
90
102
|
{ section: "sessions", usage: "-s <session-id> conversation [--full] [--compact] [--tail <nodes>]", description: "Read a conversation transcript, pending questions, and required envs." },
|
|
91
103
|
{ section: "sessions", usage: '-s <session-id> prompt --mode <mode> --model <tier> "<message>"', description: "Send a prompt to a session." },
|
|
92
104
|
{ section: "sessions", usage: '-s <session-id> answer-questions -a1 "<answer>" -a2 "<answer>" ...', description: "Answer pending questions in order." },
|
|
93
|
-
{ 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." }
|
|
94
112
|
];
|
|
95
|
-
const HELP_SECTION_ORDER = ["auth", "projects", "branches", "sessions"];
|
|
113
|
+
const HELP_SECTION_ORDER = ["auth", "projects", "branches", "sessions", "agents", "merges"];
|
|
96
114
|
const HELP_SECTION_TITLES = {
|
|
97
115
|
auth: "Auth",
|
|
98
116
|
projects: "Projects",
|
|
99
117
|
branches: "Branches",
|
|
100
|
-
sessions: "Sessions"
|
|
118
|
+
sessions: "Sessions",
|
|
119
|
+
agents: "Agents",
|
|
120
|
+
merges: "Merges"
|
|
101
121
|
};
|
|
102
122
|
function getCliHelpText() {
|
|
103
123
|
const entries = [...CLI_ONLY_HELP_ENTRIES, ...SHARED_HELP_ENTRIES];
|
|
@@ -322,9 +342,9 @@ function writeConfig(configPath, config) {
|
|
|
322
342
|
}
|
|
323
343
|
function resolveClientOptions(options, config) {
|
|
324
344
|
return {
|
|
325
|
-
baseUrl: options.baseUrl ?? process.env.R5D_BASE_URL ?? process.env.
|
|
326
|
-
token: options.token ?? process.env.R5D_TOKEN ?? process.env.
|
|
327
|
-
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
|
|
328
348
|
};
|
|
329
349
|
}
|
|
330
350
|
function parseAnswerFlags(args) {
|
|
@@ -459,7 +479,7 @@ function parsePromptArgs(args) {
|
|
|
459
479
|
throw new Error("--mode is required for `prompt`");
|
|
460
480
|
}
|
|
461
481
|
if (!CHAT_MODES.has(modeRaw)) {
|
|
462
|
-
throw new Error(`Invalid --mode '${modeRaw}'. Expected one of:
|
|
482
|
+
throw new Error(`Invalid --mode '${modeRaw}'. Expected one of: ${Array.from(CHAT_MODES).join(", ")}`);
|
|
463
483
|
}
|
|
464
484
|
if (!modelRaw) {
|
|
465
485
|
throw new Error("--model is required for `prompt`");
|
|
@@ -614,6 +634,49 @@ function renderSessionList(sessions) {
|
|
|
614
634
|
function renderSessionDescription(session) {
|
|
615
635
|
return [`Session: ${session.id}`, `Project: ${session.projectPath}`, `Branch: ${session.branchName}`].join("\n") + "\n";
|
|
616
636
|
}
|
|
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}`);
|
|
654
|
+
}
|
|
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
|
+
`;
|
|
679
|
+
}
|
|
617
680
|
function parseProjectUpdateArgs(args) {
|
|
618
681
|
const input = {};
|
|
619
682
|
for (let index = 0; index < args.length; index += 1) {
|
|
@@ -738,7 +801,7 @@ function renderConversationResponse(read, options = { full: false, compact: fals
|
|
|
738
801
|
return `${lines.join("\n")}
|
|
739
802
|
`;
|
|
740
803
|
}
|
|
741
|
-
async function
|
|
804
|
+
async function executeR5dctlCommand(client, json, args) {
|
|
742
805
|
const write = (data, human) => writeDataOutput(json, data, human);
|
|
743
806
|
const [first, second, third] = args;
|
|
744
807
|
if (first === "auth" && second === "status") {
|
|
@@ -814,6 +877,65 @@ Sessions: ${result.sessions.length}
|
|
|
814
877
|
write(session, renderSessionDescription(session));
|
|
815
878
|
return;
|
|
816
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
|
+
}
|
|
817
939
|
if (first === "sessions" && second === "describe" || first === "describe" && second === "session") {
|
|
818
940
|
const session = await client.sessions.describe(requireValue(args[2], "Missing session id"));
|
|
819
941
|
write(session, renderSessionDescription(session));
|
|
@@ -1106,6 +1228,92 @@ function resolveCommandExecution(options, rest) {
|
|
|
1106
1228
|
pluginArgs: ["prompt", sessionId, parsed.mode, parsed.model, parsed.message]
|
|
1107
1229
|
};
|
|
1108
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
|
+
}
|
|
1109
1317
|
if (command === "answer-questions") {
|
|
1110
1318
|
const sessionId = options.session;
|
|
1111
1319
|
if (!sessionId) {
|
|
@@ -1135,7 +1343,7 @@ async function runCommand(argv) {
|
|
|
1135
1343
|
return;
|
|
1136
1344
|
}
|
|
1137
1345
|
const config = parseConfig(options.configPath);
|
|
1138
|
-
const client = new import_r5d_api.
|
|
1346
|
+
const client = new import_r5d_api.R5dctlClient(resolveClientOptions(options, config));
|
|
1139
1347
|
const execution = resolveCommandExecution(options, rest);
|
|
1140
1348
|
if (execution.kind === "auth-login") {
|
|
1141
1349
|
await handleAuthLogin(client, options, execution.commandArgs, config);
|
|
@@ -1149,7 +1357,7 @@ async function runCommand(argv) {
|
|
|
1149
1357
|
process.stdout.write(execution.text);
|
|
1150
1358
|
return;
|
|
1151
1359
|
}
|
|
1152
|
-
await
|
|
1360
|
+
await executeR5dctlCommand(client, options.json, execution.pluginArgs);
|
|
1153
1361
|
if (execution.clearAuthOnSuccess) {
|
|
1154
1362
|
writeConfig(options.configPath, clearAuthFromConfig(config, options));
|
|
1155
1363
|
}
|
|
@@ -1163,12 +1371,12 @@ function extractApiErrorMessage(error) {
|
|
|
1163
1371
|
}
|
|
1164
1372
|
return error.message;
|
|
1165
1373
|
}
|
|
1166
|
-
async function
|
|
1374
|
+
async function runR5dctlCli(argv) {
|
|
1167
1375
|
try {
|
|
1168
1376
|
await runCommand(argv);
|
|
1169
1377
|
return 0;
|
|
1170
1378
|
} catch (error) {
|
|
1171
|
-
if (error instanceof import_r5d_api.
|
|
1379
|
+
if (error instanceof import_r5d_api.R5dctlApiError) {
|
|
1172
1380
|
process.stderr.write(`${extractApiErrorMessage(error)}
|
|
1173
1381
|
`);
|
|
1174
1382
|
return 1;
|
|
@@ -1179,7 +1387,7 @@ async function runBinctlCli(argv) {
|
|
|
1179
1387
|
}
|
|
1180
1388
|
}
|
|
1181
1389
|
async function main(argv = process.argv.slice(2)) {
|
|
1182
|
-
const exitCode = await
|
|
1390
|
+
const exitCode = await runR5dctlCli(argv);
|
|
1183
1391
|
if (exitCode !== 0) {
|
|
1184
1392
|
process.exit(exitCode);
|
|
1185
1393
|
}
|
|
@@ -1193,5 +1401,5 @@ async function main(argv = process.argv.slice(2)) {
|
|
|
1193
1401
|
parseGlobalArgs,
|
|
1194
1402
|
parsePromptArgs,
|
|
1195
1403
|
resolveCommandExecution,
|
|
1196
|
-
|
|
1404
|
+
runR5dctlCli
|
|
1197
1405
|
});
|
package/dist/cjs/package.json
CHANGED
package/dist/mjs/cli.mjs
CHANGED
|
@@ -4,11 +4,23 @@ 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",
|
|
@@ -53,14 +65,22 @@ const SHARED_HELP_ENTRIES = [
|
|
|
53
65
|
{ section: "sessions", usage: "-s <session-id> conversation [--full] [--compact] [--tail <nodes>]", description: "Read a conversation transcript, pending questions, and required envs." },
|
|
54
66
|
{ section: "sessions", usage: '-s <session-id> prompt --mode <mode> --model <tier> "<message>"', description: "Send a prompt to a session." },
|
|
55
67
|
{ section: "sessions", usage: '-s <session-id> answer-questions -a1 "<answer>" -a2 "<answer>" ...', description: "Answer pending questions in order." },
|
|
56
|
-
{ 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." }
|
|
57
75
|
];
|
|
58
|
-
const HELP_SECTION_ORDER = ["auth", "projects", "branches", "sessions"];
|
|
76
|
+
const HELP_SECTION_ORDER = ["auth", "projects", "branches", "sessions", "agents", "merges"];
|
|
59
77
|
const HELP_SECTION_TITLES = {
|
|
60
78
|
auth: "Auth",
|
|
61
79
|
projects: "Projects",
|
|
62
80
|
branches: "Branches",
|
|
63
|
-
sessions: "Sessions"
|
|
81
|
+
sessions: "Sessions",
|
|
82
|
+
agents: "Agents",
|
|
83
|
+
merges: "Merges"
|
|
64
84
|
};
|
|
65
85
|
function getCliHelpText() {
|
|
66
86
|
const entries = [...CLI_ONLY_HELP_ENTRIES, ...SHARED_HELP_ENTRIES];
|
|
@@ -285,9 +305,9 @@ function writeConfig(configPath, config) {
|
|
|
285
305
|
}
|
|
286
306
|
function resolveClientOptions(options, config) {
|
|
287
307
|
return {
|
|
288
|
-
baseUrl: options.baseUrl ?? process.env.R5D_BASE_URL ?? process.env.
|
|
289
|
-
token: options.token ?? process.env.R5D_TOKEN ?? process.env.
|
|
290
|
-
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
|
|
291
311
|
};
|
|
292
312
|
}
|
|
293
313
|
function parseAnswerFlags(args) {
|
|
@@ -422,7 +442,7 @@ function parsePromptArgs(args) {
|
|
|
422
442
|
throw new Error("--mode is required for `prompt`");
|
|
423
443
|
}
|
|
424
444
|
if (!CHAT_MODES.has(modeRaw)) {
|
|
425
|
-
throw new Error(`Invalid --mode '${modeRaw}'. Expected one of:
|
|
445
|
+
throw new Error(`Invalid --mode '${modeRaw}'. Expected one of: ${Array.from(CHAT_MODES).join(", ")}`);
|
|
426
446
|
}
|
|
427
447
|
if (!modelRaw) {
|
|
428
448
|
throw new Error("--model is required for `prompt`");
|
|
@@ -577,6 +597,49 @@ function renderSessionList(sessions) {
|
|
|
577
597
|
function renderSessionDescription(session) {
|
|
578
598
|
return [`Session: ${session.id}`, `Project: ${session.projectPath}`, `Branch: ${session.branchName}`].join("\n") + "\n";
|
|
579
599
|
}
|
|
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}`);
|
|
617
|
+
}
|
|
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
|
+
`;
|
|
642
|
+
}
|
|
580
643
|
function parseProjectUpdateArgs(args) {
|
|
581
644
|
const input = {};
|
|
582
645
|
for (let index = 0; index < args.length; index += 1) {
|
|
@@ -701,7 +764,7 @@ function renderConversationResponse(read, options = { full: false, compact: fals
|
|
|
701
764
|
return `${lines.join("\n")}
|
|
702
765
|
`;
|
|
703
766
|
}
|
|
704
|
-
async function
|
|
767
|
+
async function executeR5dctlCommand(client, json, args) {
|
|
705
768
|
const write = (data, human) => writeDataOutput(json, data, human);
|
|
706
769
|
const [first, second, third] = args;
|
|
707
770
|
if (first === "auth" && second === "status") {
|
|
@@ -777,6 +840,65 @@ Sessions: ${result.sessions.length}
|
|
|
777
840
|
write(session, renderSessionDescription(session));
|
|
778
841
|
return;
|
|
779
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
|
+
}
|
|
780
902
|
if (first === "sessions" && second === "describe" || first === "describe" && second === "session") {
|
|
781
903
|
const session = await client.sessions.describe(requireValue(args[2], "Missing session id"));
|
|
782
904
|
write(session, renderSessionDescription(session));
|
|
@@ -1069,6 +1191,92 @@ function resolveCommandExecution(options, rest) {
|
|
|
1069
1191
|
pluginArgs: ["prompt", sessionId, parsed.mode, parsed.model, parsed.message]
|
|
1070
1192
|
};
|
|
1071
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
|
+
}
|
|
1072
1280
|
if (command === "answer-questions") {
|
|
1073
1281
|
const sessionId = options.session;
|
|
1074
1282
|
if (!sessionId) {
|
|
@@ -1098,7 +1306,7 @@ async function runCommand(argv) {
|
|
|
1098
1306
|
return;
|
|
1099
1307
|
}
|
|
1100
1308
|
const config = parseConfig(options.configPath);
|
|
1101
|
-
const client = new
|
|
1309
|
+
const client = new R5dctlClient(resolveClientOptions(options, config));
|
|
1102
1310
|
const execution = resolveCommandExecution(options, rest);
|
|
1103
1311
|
if (execution.kind === "auth-login") {
|
|
1104
1312
|
await handleAuthLogin(client, options, execution.commandArgs, config);
|
|
@@ -1112,7 +1320,7 @@ async function runCommand(argv) {
|
|
|
1112
1320
|
process.stdout.write(execution.text);
|
|
1113
1321
|
return;
|
|
1114
1322
|
}
|
|
1115
|
-
await
|
|
1323
|
+
await executeR5dctlCommand(client, options.json, execution.pluginArgs);
|
|
1116
1324
|
if (execution.clearAuthOnSuccess) {
|
|
1117
1325
|
writeConfig(options.configPath, clearAuthFromConfig(config, options));
|
|
1118
1326
|
}
|
|
@@ -1126,12 +1334,12 @@ function extractApiErrorMessage(error) {
|
|
|
1126
1334
|
}
|
|
1127
1335
|
return error.message;
|
|
1128
1336
|
}
|
|
1129
|
-
async function
|
|
1337
|
+
async function runR5dctlCli(argv) {
|
|
1130
1338
|
try {
|
|
1131
1339
|
await runCommand(argv);
|
|
1132
1340
|
return 0;
|
|
1133
1341
|
} catch (error) {
|
|
1134
|
-
if (error instanceof
|
|
1342
|
+
if (error instanceof R5dctlApiError) {
|
|
1135
1343
|
process.stderr.write(`${extractApiErrorMessage(error)}
|
|
1136
1344
|
`);
|
|
1137
1345
|
return 1;
|
|
@@ -1142,7 +1350,7 @@ async function runBinctlCli(argv) {
|
|
|
1142
1350
|
}
|
|
1143
1351
|
}
|
|
1144
1352
|
async function main(argv = process.argv.slice(2)) {
|
|
1145
|
-
const exitCode = await
|
|
1353
|
+
const exitCode = await runR5dctlCli(argv);
|
|
1146
1354
|
if (exitCode !== 0) {
|
|
1147
1355
|
process.exit(exitCode);
|
|
1148
1356
|
}
|
|
@@ -1155,5 +1363,5 @@ export {
|
|
|
1155
1363
|
parseGlobalArgs,
|
|
1156
1364
|
parsePromptArgs,
|
|
1157
1365
|
resolveCommandExecution,
|
|
1158
|
-
|
|
1366
|
+
runR5dctlCli
|
|
1159
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.8",
|
|
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.8"
|
|
30
30
|
},
|
|
31
31
|
"files": [
|
|
32
32
|
"dist",
|