@ricsam/r5dctl 0.0.104 → 0.0.106
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 +8 -4
- package/dist/cjs/cli.cjs +92 -18
- package/dist/cjs/package.json +1 -1
- package/dist/mjs/cli.mjs +87 -18
- package/dist/mjs/package.json +1 -1
- package/dist/types/cli.d.ts +37 -1
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -57,14 +57,14 @@ r5dctl -p <project> get branches
|
|
|
57
57
|
r5dctl -p <project> describe branch <branch>
|
|
58
58
|
r5dctl -p <project> create branch <branch> --source worktree:<branch> [--working-tree carry|clean] [--worker <label>]
|
|
59
59
|
r5dctl -p <project> describe branch-operation <operation-id>
|
|
60
|
-
r5dctl -p <project> delete branch <branch>
|
|
61
|
-
r5dctl -p <project> delete branch <branch> --confirm
|
|
60
|
+
r5dctl -p <project> delete branch <branch> [<branch>...] # preview the sessions that would be deleted, exit 1
|
|
61
|
+
r5dctl -p <project> delete branch <branch> [<branch>...] --confirm # delete the branches and their sessions through the platform
|
|
62
62
|
r5dctl -p <project> get envs
|
|
63
63
|
r5dctl -p <project> get envs --show-values
|
|
64
64
|
r5dctl -p <project> get env API_KEY
|
|
65
65
|
r5dctl -p <project> set envs --from-env-file ~/.env --only-missing
|
|
66
66
|
r5dctl -p <project> set envs -e API_KEY=value --description "API credential"
|
|
67
|
-
r5dctl -p <project> get sessions --branch <branch>
|
|
67
|
+
r5dctl -p <project> get sessions --branch <branch> # each session's live state and last activity
|
|
68
68
|
r5dctl -p <project> create session -b <branch> --name "Spec session"
|
|
69
69
|
r5dctl describe session <session-id>
|
|
70
70
|
r5dctl -s <session-id> update session --name "Renamed session"
|
|
@@ -125,12 +125,16 @@ pull requests.
|
|
|
125
125
|
|
|
126
126
|
`create branch` creates a linked worktree branch through the platform from an existing branch's checkout, with the same `--source worktree:<branch>` and `--working-tree carry|clean` semantics as `session start --new-worktree` (carry is the default) and no session prompt. `--worker <label>` selects the connected worker that cuts the checkout; omitted, the platform chooses one, and an r5d worker shell's temporary credential is always bound to its own worker. The command returns as soon as the operation is accepted and prints its id; `describe branch-operation <operation-id>` reports `provisioning`, `ready`, or `failed` with the worker and any error. Neither prints a commit hash: read the branch with git in its checkout once it is ready.
|
|
127
127
|
|
|
128
|
-
`delete branch` removes
|
|
128
|
+
`delete branch` removes one or more branches and every session on them through the platform, which is the only supported way to remove a linked worktree: the platform owns those checkouts, so never run `git worktree remove`, `git worktree prune`, or `git branch -D` inside a project checkout. Without `--confirm` it prints each branch and its sessions and exits non-zero without deleting anything. With `--confirm` the branches are deleted one after another, each prints its own result line, a failure does not stop the remaining deletions, and the exit status is non-zero if any branch failed. The primary branch `main` is refused before anything is deleted, as is a branch with a running agent or active process (stop them first). A worker that is still applying a configuration refresh makes a deletion wait on the server for up to 45 seconds; if the refresh is still running after that, the result line says so and the branch can be retried. On a server that predates the route, the command explains that the server does not support `delete branch` yet instead of reporting a missing branch.
|
|
129
129
|
|
|
130
130
|
`get branches` lists every branch the project knows about, including managed branches without sessions. A worker whose workspace sync found a configured branch's checkout directory missing, or present but not a linked worktree of the project, skips that branch and reports it; the listing then shows `checkout=missing on <worker>` or `checkout=not_a_worktree on <worker>`, and `workspace status` prints a `Missing checkouts on <worker>` line. Recover either by deleting the branch with `delete branch <branch> --confirm` or by reconnecting that worker, which recreates the checkout without snapshotting the project's other checkouts.
|
|
131
131
|
|
|
132
132
|
`session start` creates an ordinary agent-mode chat on an explicitly selected existing worktree, or creates a named linked worktree from an explicit `worktree:<branch>` source. A new worktree is cut from the source branch's current commit; `--working-tree carry` (the default, `git switch -c` semantics) also copies the source checkout's uncommitted changes into it, while `--working-tree clean` checks out that commit only. The new session's first message states which mode applies, and the start output prints `Source` and `Working tree` lines. A clean request fails with a clear error when the selected worker predates the option, because such a worker would silently carry instead. It requires project, worker, model, and prompt and never infers them from the current shell. Start returns a durable `provisioning` handle immediately; workspace synchronization, linked-worktree creation, and prompt enqueue continue in the background and are visible through `session status`. Idempotent request retries return the same handle. Prompts return after durable enqueue. Task specialization belongs in the prompt; there are no predefined debug, explore, research, or test agents. `session stop` returns as soon as the agent run is stopped; the stopped worktree's workspace Git synchronization finishes asynchronously behind the response.
|
|
133
133
|
|
|
134
|
+
`get sessions` lists a project's sessions, newest first, and appends each session's live state (`running`, `stopping`, `paused`, `provisioning`, `queued`, or `idle`) and how long ago its conversation last advanced (`running, last active 12s ago`). Several sessions can work in the same branch checkout at once (task chips, shared-mode sub-agents, sessions the user starts), so run it with `--branch` before relying on a working tree or reviewing what changed in it. A server that predates the fields prints the id, branch, and name only.
|
|
135
|
+
|
|
136
|
+
`session stop` also stops every sub-agent session the stopped session started, directly or through its own children, and `session status` prints `Child sessions: <active> active of <total>` for a session that launched sub-agents.
|
|
137
|
+
|
|
134
138
|
`session status` reports the run lifecycle and the launch-time `Source` and `Working tree` facts only. It deliberately prints no commit hash for the worktree, not even the launch baseline: a hash would be stale by the time a caller acts on it, because commits can land between the read and the action. Find the base and the branch's commits with git in the worker checkout instead, for example `git merge-base <your-branch> <branch>`, `git log --oneline <your-branch>..<branch>`, and `git status` in the branch folder, as the `Branch state` line reminds you. While a new worktree is still provisioning, a `Provisioning` line reports where its creation stands in the worker's operation queue and since when (`Provisioning: queued behind 3 worker operations since 2026-09-03T11:41:30Z`); a worker that predates operation receipts leaves that line out.
|
|
135
139
|
|
|
136
140
|
`session process-log` prints a shell run's stored stdout and stderr from the server, where r5d keeps every run's full output; the `logPath` in tool results names that server-side store, not a file on the worker. Without flags it prints the whole log, `--tail <n>` keeps the last n lines, and `--grep <pattern>` keeps the lines matching a JavaScript regular expression (combine both for the last n matches). Each stream is capped at 512 KB per call and printed under a `==> stdout <==` or `==> stderr <==` header; a one-line summary with byte totals, the selection, and any truncation goes to stderr so stdout stays clean for `grep`, `head`, and `tail`. Inside an r5d worker shell the temporary credential can only read runs on that shell's worker.
|
package/dist/cjs/cli.cjs
CHANGED
|
@@ -30,9 +30,12 @@ var cli_exports = {};
|
|
|
30
30
|
__export(cli_exports, {
|
|
31
31
|
advanceTransientDevicePollBackoff: () => advanceTransientDevicePollBackoff,
|
|
32
32
|
branchDeletionConfirmationMessage: () => branchDeletionConfirmationMessage,
|
|
33
|
+
branchDeletionsConfirmationMessage: () => branchDeletionsConfirmationMessage,
|
|
34
|
+
branchDeletionsFailureMessage: () => branchDeletionsFailureMessage,
|
|
33
35
|
collectK8sUsage: () => collectK8sUsage,
|
|
34
36
|
createBranchThroughPlatform: () => createBranchThroughPlatform,
|
|
35
37
|
deleteBranchThroughPlatform: () => deleteBranchThroughPlatform,
|
|
38
|
+
deleteBranchesThroughPlatform: () => deleteBranchesThroughPlatform,
|
|
36
39
|
dispatchSessionRunCommand: () => dispatchSessionRunCommand,
|
|
37
40
|
followR5dctlSession: () => followR5dctlSession,
|
|
38
41
|
formatK8sCpu: () => formatK8sCpu,
|
|
@@ -70,6 +73,7 @@ __export(cli_exports, {
|
|
|
70
73
|
readDotenvFile: () => readDotenvFile,
|
|
71
74
|
renderBranchDeletion: () => renderBranchDeletion,
|
|
72
75
|
renderBranchDeletionPreview: () => renderBranchDeletionPreview,
|
|
76
|
+
renderBranchDeletionResult: () => renderBranchDeletionResult,
|
|
73
77
|
renderBranchList: () => renderBranchList,
|
|
74
78
|
renderBranchOperation: () => renderBranchOperation,
|
|
75
79
|
renderConversationForkResponse: () => renderConversationForkResponse,
|
|
@@ -85,6 +89,7 @@ __export(cli_exports, {
|
|
|
85
89
|
renderProcessInspection: () => renderProcessInspection,
|
|
86
90
|
renderProcessList: () => renderProcessList,
|
|
87
91
|
renderProcessLog: () => renderProcessLog,
|
|
92
|
+
renderSessionList: () => renderSessionList,
|
|
88
93
|
renderSessionRunStart: () => renderSessionRunStart,
|
|
89
94
|
renderSessionRunStatus: () => renderSessionRunStatus,
|
|
90
95
|
renderWorkspaceStatus: () => renderWorkspaceStatus,
|
|
@@ -221,8 +226,8 @@ const SHARED_HELP_ENTRIES = [
|
|
|
221
226
|
},
|
|
222
227
|
{
|
|
223
228
|
section: "branches",
|
|
224
|
-
usage: "-p <project> delete branch <branch> [--confirm]",
|
|
225
|
-
description: "Delete
|
|
229
|
+
usage: "-p <project> delete branch <branch> [<branch>...] [--confirm]",
|
|
230
|
+
description: "Delete one or more branches and their sessions through the platform; without --confirm, only preview what would be deleted."
|
|
226
231
|
},
|
|
227
232
|
{
|
|
228
233
|
section: "envs",
|
|
@@ -610,15 +615,18 @@ function parseBranchCreateArgs(args) {
|
|
|
610
615
|
};
|
|
611
616
|
}
|
|
612
617
|
function parseBranchDeleteArgs(args) {
|
|
618
|
+
const branchNames = [];
|
|
613
619
|
let confirmed = false;
|
|
614
620
|
for (const arg of args) {
|
|
615
621
|
if (arg === "--confirm") {
|
|
616
622
|
confirmed = true;
|
|
617
623
|
continue;
|
|
618
624
|
}
|
|
619
|
-
|
|
625
|
+
if (arg.startsWith("-")) throw new Error(`Unknown delete branch flag: ${arg}`);
|
|
626
|
+
if (branchNames.includes(arg)) throw new Error(`Branch ${arg} was given more than once`);
|
|
627
|
+
branchNames.push(arg);
|
|
620
628
|
}
|
|
621
|
-
return { confirmed };
|
|
629
|
+
return { branchNames, confirmed };
|
|
622
630
|
}
|
|
623
631
|
const PRIMARY_BRANCH_NAME = "main";
|
|
624
632
|
function isUnsupportedServerRoute(error) {
|
|
@@ -653,6 +661,50 @@ async function deleteBranchThroughPlatform(client, projectRef, branchName, optio
|
|
|
653
661
|
const result = await requireServerRouteSupport("delete branch", () => client.projects.branches.delete(projectRef, branchName));
|
|
654
662
|
return { kind: "deleted", result };
|
|
655
663
|
}
|
|
664
|
+
async function deleteBranchesThroughPlatform(client, projectRef, branchNames, options) {
|
|
665
|
+
if (branchNames.length === 0) throw new Error("Missing branch name");
|
|
666
|
+
for (const branchName of branchNames) {
|
|
667
|
+
if (branchName === PRIMARY_BRANCH_NAME) throw new Error(`The primary branch ${branchName} cannot be deleted`);
|
|
668
|
+
}
|
|
669
|
+
if (!options.confirmed) {
|
|
670
|
+
const branches = [];
|
|
671
|
+
for (const branchName of branchNames) {
|
|
672
|
+
branches.push(await client.projects.branches.describe(projectRef, branchName));
|
|
673
|
+
}
|
|
674
|
+
return { kind: "preview", branches };
|
|
675
|
+
}
|
|
676
|
+
const results = [];
|
|
677
|
+
for (const branchName of branchNames) {
|
|
678
|
+
let result;
|
|
679
|
+
try {
|
|
680
|
+
const deletion = await deleteBranchThroughPlatform(client, projectRef, branchName, { confirmed: true });
|
|
681
|
+
if (deletion.kind !== "deleted") throw new Error(`Branch ${branchName} was only previewed`);
|
|
682
|
+
result = { branchName, deleted: true, deletedSessionIds: deletion.result.deletedSessionIds };
|
|
683
|
+
} catch (error) {
|
|
684
|
+
result = {
|
|
685
|
+
branchName,
|
|
686
|
+
deleted: false,
|
|
687
|
+
error: error instanceof import_r5d_api.R5dctlApiError ? extractApiErrorMessage(error) : error instanceof Error ? error.message : String(error)
|
|
688
|
+
};
|
|
689
|
+
}
|
|
690
|
+
results.push(result);
|
|
691
|
+
options.onResult?.(result);
|
|
692
|
+
}
|
|
693
|
+
return { kind: "deleted", results };
|
|
694
|
+
}
|
|
695
|
+
function renderBranchDeletionResult(result) {
|
|
696
|
+
return result.deleted ? renderBranchDeletion(result) : `Failed to delete branch ${result.branchName}: ${result.error}
|
|
697
|
+
`;
|
|
698
|
+
}
|
|
699
|
+
function branchDeletionsConfirmationMessage(branches) {
|
|
700
|
+
const [only] = branches;
|
|
701
|
+
if (branches.length === 1 && only) return branchDeletionConfirmationMessage(only);
|
|
702
|
+
const sessionCount = branches.reduce((count, branch) => count + branch.sessions.length, 0);
|
|
703
|
+
return `Nothing was deleted. Re-run with --confirm to delete ${branches.length} branches and their ${sessionCount} session(s).`;
|
|
704
|
+
}
|
|
705
|
+
function branchDeletionsFailureMessage(results) {
|
|
706
|
+
return `Failed to delete ${results.filter((result) => !result.deleted).length} of ${results.length} branch(es).`;
|
|
707
|
+
}
|
|
656
708
|
function renderBranchDeletionPreview(branch) {
|
|
657
709
|
const count = branch.sessions.length;
|
|
658
710
|
const lines = [
|
|
@@ -1584,11 +1636,15 @@ function renderProjectList(projects) {
|
|
|
1584
1636
|
Mode: ${project.mode}`).join("\n\n")}
|
|
1585
1637
|
`;
|
|
1586
1638
|
}
|
|
1587
|
-
function
|
|
1639
|
+
function formatSessionActivity(session, nowMs) {
|
|
1640
|
+
if (!session.runState) return void 0;
|
|
1641
|
+
return session.lastActivityAt ? `${session.runState}, last active ${formatProcessAge(session.lastActivityAt, nowMs)} ago` : session.runState;
|
|
1642
|
+
}
|
|
1643
|
+
function renderSessionList(sessions, nowMs = Date.now()) {
|
|
1588
1644
|
if (sessions.length === 0) {
|
|
1589
1645
|
return "No sessions found.\n";
|
|
1590
1646
|
}
|
|
1591
|
-
return `${sessions.map((session) =>
|
|
1647
|
+
return `${sessions.map((session) => [session.id, session.branchName, formatSessionActivity(session, nowMs), session.name ?? "(unnamed)"].filter(Boolean).join(" ")).join("\n")}
|
|
1592
1648
|
`;
|
|
1593
1649
|
}
|
|
1594
1650
|
function renderRecentSessionList(sessions) {
|
|
@@ -2079,6 +2135,13 @@ function formatProvisioningQueueLine(session) {
|
|
|
2079
2135
|
if (position === 0) return `Provisioning: worker started the branch operation at ${since}`;
|
|
2080
2136
|
return `Provisioning: queued behind ${position} worker operation${position === 1 ? "" : "s"} since ${since}`;
|
|
2081
2137
|
}
|
|
2138
|
+
function formatChildSessionsLine(session) {
|
|
2139
|
+
const children = session.childSessions;
|
|
2140
|
+
if (!children || typeof children !== "object") return void 0;
|
|
2141
|
+
const { total, active } = children;
|
|
2142
|
+
if (!Number.isInteger(total) || !Number.isInteger(active) || total <= 0 || active < 0) return void 0;
|
|
2143
|
+
return `Child sessions: ${active} active of ${total}`;
|
|
2144
|
+
}
|
|
2082
2145
|
function renderSessionRunStatus(session) {
|
|
2083
2146
|
const status = formatStatusValue(session.status) ?? "unknown";
|
|
2084
2147
|
const lines = [`Session: ${responseString(session, "sessionId") ?? "(unknown)"}`, `Status: ${status}`];
|
|
@@ -2099,6 +2162,8 @@ function renderSessionRunStatus(session) {
|
|
|
2099
2162
|
const value = responseString(session, key);
|
|
2100
2163
|
if (value) lines.push(`${label}: ${value}`);
|
|
2101
2164
|
}
|
|
2165
|
+
const childSessions = formatChildSessionsLine(session);
|
|
2166
|
+
if (childSessions) lines.push(childSessions);
|
|
2102
2167
|
lines.push(`Branch state: ${formatBranchStateHint(branch)}`);
|
|
2103
2168
|
const error = responseString(session, "error");
|
|
2104
2169
|
if (error) lines.push(`Error: ${error}`);
|
|
@@ -2846,13 +2911,20 @@ Sessions: ${result.sessions.length}
|
|
|
2846
2911
|
if (first === "projects" && second === "branches" && third === "delete" || first === "delete" && second === "branch") {
|
|
2847
2912
|
const offset = first === "delete" ? 2 : 3;
|
|
2848
2913
|
const projectRef = requireValue(args[offset], "Missing project reference");
|
|
2849
|
-
const
|
|
2850
|
-
const deletion = await
|
|
2914
|
+
const { branchNames, confirmed } = parseBranchDeleteArgs(args.slice(offset + 1));
|
|
2915
|
+
const deletion = await deleteBranchesThroughPlatform(client, projectRef, branchNames, {
|
|
2916
|
+
confirmed,
|
|
2917
|
+
// Each deletion is its own round trip, so a reader sees results as they land.
|
|
2918
|
+
onResult: (result) => {
|
|
2919
|
+
if (!json) process.stdout.write(renderBranchDeletionResult(result));
|
|
2920
|
+
}
|
|
2921
|
+
});
|
|
2851
2922
|
if (deletion.kind === "preview") {
|
|
2852
|
-
write({
|
|
2853
|
-
throw new Error(
|
|
2923
|
+
write({ branches: deletion.branches, confirmed: false }, deletion.branches.map(renderBranchDeletionPreview).join("\n"));
|
|
2924
|
+
throw new Error(branchDeletionsConfirmationMessage(deletion.branches));
|
|
2854
2925
|
}
|
|
2855
|
-
write(deletion.
|
|
2926
|
+
if (json) write({ results: deletion.results }, "");
|
|
2927
|
+
if (deletion.results.some((result) => !result.deleted)) throw new Error(branchDeletionsFailureMessage(deletion.results));
|
|
2856
2928
|
return;
|
|
2857
2929
|
}
|
|
2858
2930
|
if (first === "get" && second === "envs") {
|
|
@@ -3387,17 +3459,14 @@ function resolveCommandExecution(options, rest) {
|
|
|
3387
3459
|
if (!options.project) {
|
|
3388
3460
|
throw new Error("--project/-p is required for `delete branch`");
|
|
3389
3461
|
}
|
|
3390
|
-
const
|
|
3391
|
-
const
|
|
3392
|
-
|
|
3393
|
-
if (!branch) {
|
|
3462
|
+
const parsed = parseBranchDeleteArgs(commandArgs.slice(1));
|
|
3463
|
+
const branchNames = parsed.branchNames.length > 0 ? parsed.branchNames : options.branch ? [options.branch] : [];
|
|
3464
|
+
if (branchNames.length === 0) {
|
|
3394
3465
|
throw new Error("Branch name is required for `delete branch`");
|
|
3395
3466
|
}
|
|
3396
|
-
const flagArgs = positionalBranch ? rawArgs.slice(1) : rawArgs;
|
|
3397
|
-
parseBranchDeleteArgs(flagArgs);
|
|
3398
3467
|
return {
|
|
3399
3468
|
kind: "plugin",
|
|
3400
|
-
pluginArgs: ["delete", "branch", options.project,
|
|
3469
|
+
pluginArgs: ["delete", "branch", options.project, ...branchNames, ...parsed.confirmed ? ["--confirm"] : []]
|
|
3401
3470
|
};
|
|
3402
3471
|
}
|
|
3403
3472
|
throw new Error("Unknown delete command");
|
|
@@ -3628,9 +3697,12 @@ async function main(argv = process.argv.slice(2)) {
|
|
|
3628
3697
|
0 && (module.exports = {
|
|
3629
3698
|
advanceTransientDevicePollBackoff,
|
|
3630
3699
|
branchDeletionConfirmationMessage,
|
|
3700
|
+
branchDeletionsConfirmationMessage,
|
|
3701
|
+
branchDeletionsFailureMessage,
|
|
3631
3702
|
collectK8sUsage,
|
|
3632
3703
|
createBranchThroughPlatform,
|
|
3633
3704
|
deleteBranchThroughPlatform,
|
|
3705
|
+
deleteBranchesThroughPlatform,
|
|
3634
3706
|
dispatchSessionRunCommand,
|
|
3635
3707
|
followR5dctlSession,
|
|
3636
3708
|
formatK8sCpu,
|
|
@@ -3668,6 +3740,7 @@ async function main(argv = process.argv.slice(2)) {
|
|
|
3668
3740
|
readDotenvFile,
|
|
3669
3741
|
renderBranchDeletion,
|
|
3670
3742
|
renderBranchDeletionPreview,
|
|
3743
|
+
renderBranchDeletionResult,
|
|
3671
3744
|
renderBranchList,
|
|
3672
3745
|
renderBranchOperation,
|
|
3673
3746
|
renderConversationForkResponse,
|
|
@@ -3683,6 +3756,7 @@ async function main(argv = process.argv.slice(2)) {
|
|
|
3683
3756
|
renderProcessInspection,
|
|
3684
3757
|
renderProcessList,
|
|
3685
3758
|
renderProcessLog,
|
|
3759
|
+
renderSessionList,
|
|
3686
3760
|
renderSessionRunStart,
|
|
3687
3761
|
renderSessionRunStatus,
|
|
3688
3762
|
renderWorkspaceStatus,
|
package/dist/cjs/package.json
CHANGED
package/dist/mjs/cli.mjs
CHANGED
|
@@ -126,8 +126,8 @@ const SHARED_HELP_ENTRIES = [
|
|
|
126
126
|
},
|
|
127
127
|
{
|
|
128
128
|
section: "branches",
|
|
129
|
-
usage: "-p <project> delete branch <branch> [--confirm]",
|
|
130
|
-
description: "Delete
|
|
129
|
+
usage: "-p <project> delete branch <branch> [<branch>...] [--confirm]",
|
|
130
|
+
description: "Delete one or more branches and their sessions through the platform; without --confirm, only preview what would be deleted."
|
|
131
131
|
},
|
|
132
132
|
{
|
|
133
133
|
section: "envs",
|
|
@@ -515,15 +515,18 @@ function parseBranchCreateArgs(args) {
|
|
|
515
515
|
};
|
|
516
516
|
}
|
|
517
517
|
function parseBranchDeleteArgs(args) {
|
|
518
|
+
const branchNames = [];
|
|
518
519
|
let confirmed = false;
|
|
519
520
|
for (const arg of args) {
|
|
520
521
|
if (arg === "--confirm") {
|
|
521
522
|
confirmed = true;
|
|
522
523
|
continue;
|
|
523
524
|
}
|
|
524
|
-
|
|
525
|
+
if (arg.startsWith("-")) throw new Error(`Unknown delete branch flag: ${arg}`);
|
|
526
|
+
if (branchNames.includes(arg)) throw new Error(`Branch ${arg} was given more than once`);
|
|
527
|
+
branchNames.push(arg);
|
|
525
528
|
}
|
|
526
|
-
return { confirmed };
|
|
529
|
+
return { branchNames, confirmed };
|
|
527
530
|
}
|
|
528
531
|
const PRIMARY_BRANCH_NAME = "main";
|
|
529
532
|
function isUnsupportedServerRoute(error) {
|
|
@@ -558,6 +561,50 @@ async function deleteBranchThroughPlatform(client, projectRef, branchName, optio
|
|
|
558
561
|
const result = await requireServerRouteSupport("delete branch", () => client.projects.branches.delete(projectRef, branchName));
|
|
559
562
|
return { kind: "deleted", result };
|
|
560
563
|
}
|
|
564
|
+
async function deleteBranchesThroughPlatform(client, projectRef, branchNames, options) {
|
|
565
|
+
if (branchNames.length === 0) throw new Error("Missing branch name");
|
|
566
|
+
for (const branchName of branchNames) {
|
|
567
|
+
if (branchName === PRIMARY_BRANCH_NAME) throw new Error(`The primary branch ${branchName} cannot be deleted`);
|
|
568
|
+
}
|
|
569
|
+
if (!options.confirmed) {
|
|
570
|
+
const branches = [];
|
|
571
|
+
for (const branchName of branchNames) {
|
|
572
|
+
branches.push(await client.projects.branches.describe(projectRef, branchName));
|
|
573
|
+
}
|
|
574
|
+
return { kind: "preview", branches };
|
|
575
|
+
}
|
|
576
|
+
const results = [];
|
|
577
|
+
for (const branchName of branchNames) {
|
|
578
|
+
let result;
|
|
579
|
+
try {
|
|
580
|
+
const deletion = await deleteBranchThroughPlatform(client, projectRef, branchName, { confirmed: true });
|
|
581
|
+
if (deletion.kind !== "deleted") throw new Error(`Branch ${branchName} was only previewed`);
|
|
582
|
+
result = { branchName, deleted: true, deletedSessionIds: deletion.result.deletedSessionIds };
|
|
583
|
+
} catch (error) {
|
|
584
|
+
result = {
|
|
585
|
+
branchName,
|
|
586
|
+
deleted: false,
|
|
587
|
+
error: error instanceof R5dctlApiError ? extractApiErrorMessage(error) : error instanceof Error ? error.message : String(error)
|
|
588
|
+
};
|
|
589
|
+
}
|
|
590
|
+
results.push(result);
|
|
591
|
+
options.onResult?.(result);
|
|
592
|
+
}
|
|
593
|
+
return { kind: "deleted", results };
|
|
594
|
+
}
|
|
595
|
+
function renderBranchDeletionResult(result) {
|
|
596
|
+
return result.deleted ? renderBranchDeletion(result) : `Failed to delete branch ${result.branchName}: ${result.error}
|
|
597
|
+
`;
|
|
598
|
+
}
|
|
599
|
+
function branchDeletionsConfirmationMessage(branches) {
|
|
600
|
+
const [only] = branches;
|
|
601
|
+
if (branches.length === 1 && only) return branchDeletionConfirmationMessage(only);
|
|
602
|
+
const sessionCount = branches.reduce((count, branch) => count + branch.sessions.length, 0);
|
|
603
|
+
return `Nothing was deleted. Re-run with --confirm to delete ${branches.length} branches and their ${sessionCount} session(s).`;
|
|
604
|
+
}
|
|
605
|
+
function branchDeletionsFailureMessage(results) {
|
|
606
|
+
return `Failed to delete ${results.filter((result) => !result.deleted).length} of ${results.length} branch(es).`;
|
|
607
|
+
}
|
|
561
608
|
function renderBranchDeletionPreview(branch) {
|
|
562
609
|
const count = branch.sessions.length;
|
|
563
610
|
const lines = [
|
|
@@ -1489,11 +1536,15 @@ function renderProjectList(projects) {
|
|
|
1489
1536
|
Mode: ${project.mode}`).join("\n\n")}
|
|
1490
1537
|
`;
|
|
1491
1538
|
}
|
|
1492
|
-
function
|
|
1539
|
+
function formatSessionActivity(session, nowMs) {
|
|
1540
|
+
if (!session.runState) return void 0;
|
|
1541
|
+
return session.lastActivityAt ? `${session.runState}, last active ${formatProcessAge(session.lastActivityAt, nowMs)} ago` : session.runState;
|
|
1542
|
+
}
|
|
1543
|
+
function renderSessionList(sessions, nowMs = Date.now()) {
|
|
1493
1544
|
if (sessions.length === 0) {
|
|
1494
1545
|
return "No sessions found.\n";
|
|
1495
1546
|
}
|
|
1496
|
-
return `${sessions.map((session) =>
|
|
1547
|
+
return `${sessions.map((session) => [session.id, session.branchName, formatSessionActivity(session, nowMs), session.name ?? "(unnamed)"].filter(Boolean).join(" ")).join("\n")}
|
|
1497
1548
|
`;
|
|
1498
1549
|
}
|
|
1499
1550
|
function renderRecentSessionList(sessions) {
|
|
@@ -1984,6 +2035,13 @@ function formatProvisioningQueueLine(session) {
|
|
|
1984
2035
|
if (position === 0) return `Provisioning: worker started the branch operation at ${since}`;
|
|
1985
2036
|
return `Provisioning: queued behind ${position} worker operation${position === 1 ? "" : "s"} since ${since}`;
|
|
1986
2037
|
}
|
|
2038
|
+
function formatChildSessionsLine(session) {
|
|
2039
|
+
const children = session.childSessions;
|
|
2040
|
+
if (!children || typeof children !== "object") return void 0;
|
|
2041
|
+
const { total, active } = children;
|
|
2042
|
+
if (!Number.isInteger(total) || !Number.isInteger(active) || total <= 0 || active < 0) return void 0;
|
|
2043
|
+
return `Child sessions: ${active} active of ${total}`;
|
|
2044
|
+
}
|
|
1987
2045
|
function renderSessionRunStatus(session) {
|
|
1988
2046
|
const status = formatStatusValue(session.status) ?? "unknown";
|
|
1989
2047
|
const lines = [`Session: ${responseString(session, "sessionId") ?? "(unknown)"}`, `Status: ${status}`];
|
|
@@ -2004,6 +2062,8 @@ function renderSessionRunStatus(session) {
|
|
|
2004
2062
|
const value = responseString(session, key);
|
|
2005
2063
|
if (value) lines.push(`${label}: ${value}`);
|
|
2006
2064
|
}
|
|
2065
|
+
const childSessions = formatChildSessionsLine(session);
|
|
2066
|
+
if (childSessions) lines.push(childSessions);
|
|
2007
2067
|
lines.push(`Branch state: ${formatBranchStateHint(branch)}`);
|
|
2008
2068
|
const error = responseString(session, "error");
|
|
2009
2069
|
if (error) lines.push(`Error: ${error}`);
|
|
@@ -2751,13 +2811,20 @@ Sessions: ${result.sessions.length}
|
|
|
2751
2811
|
if (first === "projects" && second === "branches" && third === "delete" || first === "delete" && second === "branch") {
|
|
2752
2812
|
const offset = first === "delete" ? 2 : 3;
|
|
2753
2813
|
const projectRef = requireValue(args[offset], "Missing project reference");
|
|
2754
|
-
const
|
|
2755
|
-
const deletion = await
|
|
2814
|
+
const { branchNames, confirmed } = parseBranchDeleteArgs(args.slice(offset + 1));
|
|
2815
|
+
const deletion = await deleteBranchesThroughPlatform(client, projectRef, branchNames, {
|
|
2816
|
+
confirmed,
|
|
2817
|
+
// Each deletion is its own round trip, so a reader sees results as they land.
|
|
2818
|
+
onResult: (result) => {
|
|
2819
|
+
if (!json) process.stdout.write(renderBranchDeletionResult(result));
|
|
2820
|
+
}
|
|
2821
|
+
});
|
|
2756
2822
|
if (deletion.kind === "preview") {
|
|
2757
|
-
write({
|
|
2758
|
-
throw new Error(
|
|
2823
|
+
write({ branches: deletion.branches, confirmed: false }, deletion.branches.map(renderBranchDeletionPreview).join("\n"));
|
|
2824
|
+
throw new Error(branchDeletionsConfirmationMessage(deletion.branches));
|
|
2759
2825
|
}
|
|
2760
|
-
write(deletion.
|
|
2826
|
+
if (json) write({ results: deletion.results }, "");
|
|
2827
|
+
if (deletion.results.some((result) => !result.deleted)) throw new Error(branchDeletionsFailureMessage(deletion.results));
|
|
2761
2828
|
return;
|
|
2762
2829
|
}
|
|
2763
2830
|
if (first === "get" && second === "envs") {
|
|
@@ -3292,17 +3359,14 @@ function resolveCommandExecution(options, rest) {
|
|
|
3292
3359
|
if (!options.project) {
|
|
3293
3360
|
throw new Error("--project/-p is required for `delete branch`");
|
|
3294
3361
|
}
|
|
3295
|
-
const
|
|
3296
|
-
const
|
|
3297
|
-
|
|
3298
|
-
if (!branch) {
|
|
3362
|
+
const parsed = parseBranchDeleteArgs(commandArgs.slice(1));
|
|
3363
|
+
const branchNames = parsed.branchNames.length > 0 ? parsed.branchNames : options.branch ? [options.branch] : [];
|
|
3364
|
+
if (branchNames.length === 0) {
|
|
3299
3365
|
throw new Error("Branch name is required for `delete branch`");
|
|
3300
3366
|
}
|
|
3301
|
-
const flagArgs = positionalBranch ? rawArgs.slice(1) : rawArgs;
|
|
3302
|
-
parseBranchDeleteArgs(flagArgs);
|
|
3303
3367
|
return {
|
|
3304
3368
|
kind: "plugin",
|
|
3305
|
-
pluginArgs: ["delete", "branch", options.project,
|
|
3369
|
+
pluginArgs: ["delete", "branch", options.project, ...branchNames, ...parsed.confirmed ? ["--confirm"] : []]
|
|
3306
3370
|
};
|
|
3307
3371
|
}
|
|
3308
3372
|
throw new Error("Unknown delete command");
|
|
@@ -3532,9 +3596,12 @@ async function main(argv = process.argv.slice(2)) {
|
|
|
3532
3596
|
export {
|
|
3533
3597
|
advanceTransientDevicePollBackoff,
|
|
3534
3598
|
branchDeletionConfirmationMessage,
|
|
3599
|
+
branchDeletionsConfirmationMessage,
|
|
3600
|
+
branchDeletionsFailureMessage,
|
|
3535
3601
|
collectK8sUsage,
|
|
3536
3602
|
createBranchThroughPlatform,
|
|
3537
3603
|
deleteBranchThroughPlatform,
|
|
3604
|
+
deleteBranchesThroughPlatform,
|
|
3538
3605
|
dispatchSessionRunCommand,
|
|
3539
3606
|
followR5dctlSession,
|
|
3540
3607
|
formatK8sCpu,
|
|
@@ -3572,6 +3639,7 @@ export {
|
|
|
3572
3639
|
readDotenvFile,
|
|
3573
3640
|
renderBranchDeletion,
|
|
3574
3641
|
renderBranchDeletionPreview,
|
|
3642
|
+
renderBranchDeletionResult,
|
|
3575
3643
|
renderBranchList,
|
|
3576
3644
|
renderBranchOperation,
|
|
3577
3645
|
renderConversationForkResponse,
|
|
@@ -3587,6 +3655,7 @@ export {
|
|
|
3587
3655
|
renderProcessInspection,
|
|
3588
3656
|
renderProcessList,
|
|
3589
3657
|
renderProcessLog,
|
|
3658
|
+
renderSessionList,
|
|
3590
3659
|
renderSessionRunStart,
|
|
3591
3660
|
renderSessionRunStatus,
|
|
3592
3661
|
renderWorkspaceStatus,
|
package/dist/mjs/package.json
CHANGED
package/dist/types/cli.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { R5dctlApiError, R5dctlClient, type R5dctlConversationForkResponse, type R5dctlConversationHeadResponse, type R5dctlConversationRenderOptions, type R5dctlConversationResponse, type R5dctlConversationNodeResponse, type R5dctlConversationOverviewResponse, type R5dctlConversationWorkDetail, type R5dctlConversationWorkResponse, type R5dctlEnvData, type R5dctlK8sResourceHistory, type R5dctlK8sUsage, type R5dctlK8sWorkload, type R5dctlProcessInspection, type R5dctlProcessListInput, type R5dctlProcessLog, type R5dctlProcessRun, type R5dctlBranch, type R5dctlBranchDeleteResponse, type R5dctlBranchDescription, type R5dctlBranchOperation, type R5dctlSessionEvent, type R5dctlSessionStartInput, type R5dctlWorkspaceStatus, type R5dctlWorkspaceSyncResult, type R5dctlWorkingTreeMode, type R5dctlWorktreeSource, type R5dctlSessionMode, type ChatMode, type ModelTier } from "@ricsam/r5d-api";
|
|
1
|
+
import { R5dctlApiError, R5dctlClient, type R5dctlConversationForkResponse, type R5dctlConversationHeadResponse, type R5dctlConversationRenderOptions, type R5dctlConversationResponse, type R5dctlConversationNodeResponse, type R5dctlConversationOverviewResponse, type R5dctlConversationWorkDetail, type R5dctlConversationWorkResponse, type R5dctlEnvData, type R5dctlK8sResourceHistory, type R5dctlK8sUsage, type R5dctlK8sWorkload, type R5dctlProcessInspection, type R5dctlProcessListInput, type R5dctlProcessLog, type R5dctlProcessRun, type R5dctlBranch, type R5dctlBranchDeleteResponse, type R5dctlBranchDescription, type R5dctlBranchOperation, type R5dctlSessionSummary, type R5dctlSessionEvent, type R5dctlSessionStartInput, type R5dctlWorkspaceStatus, type R5dctlWorkspaceSyncResult, type R5dctlWorkingTreeMode, type R5dctlWorktreeSource, type R5dctlSessionMode, type ChatMode, type ModelTier } from "@ricsam/r5d-api";
|
|
2
2
|
export type GlobalOptions = {
|
|
3
3
|
baseUrl?: string;
|
|
4
4
|
json: boolean;
|
|
@@ -86,6 +86,7 @@ export type R5dctlBranchCreateArgs = {
|
|
|
86
86
|
};
|
|
87
87
|
export declare function parseBranchCreateArgs(args: string[]): R5dctlBranchCreateArgs;
|
|
88
88
|
export declare function parseBranchDeleteArgs(args: string[]): {
|
|
89
|
+
branchNames: string[];
|
|
89
90
|
confirmed: boolean;
|
|
90
91
|
};
|
|
91
92
|
/** An older server strips unknown routes and answers with this generic 404; a route's own not-found carries a specific message. */
|
|
@@ -103,6 +104,35 @@ export type R5dctlBranchDeletionOutcome = {
|
|
|
103
104
|
export declare function deleteBranchThroughPlatform(client: R5dctlClient, projectRef: string, branchName: string, options: {
|
|
104
105
|
confirmed: boolean;
|
|
105
106
|
}): Promise<R5dctlBranchDeletionOutcome>;
|
|
107
|
+
export type R5dctlBranchDeletionResult = {
|
|
108
|
+
branchName: string;
|
|
109
|
+
deleted: true;
|
|
110
|
+
deletedSessionIds: string[];
|
|
111
|
+
} | {
|
|
112
|
+
branchName: string;
|
|
113
|
+
deleted: false;
|
|
114
|
+
error: string;
|
|
115
|
+
};
|
|
116
|
+
export type R5dctlBranchDeletionsOutcome = {
|
|
117
|
+
kind: "preview";
|
|
118
|
+
branches: R5dctlBranchDescription[];
|
|
119
|
+
} | {
|
|
120
|
+
kind: "deleted";
|
|
121
|
+
results: R5dctlBranchDeletionResult[];
|
|
122
|
+
};
|
|
123
|
+
/**
|
|
124
|
+
* Delete several branches one request at a time: the server route stays
|
|
125
|
+
* one-branch-per-request. Every name is checked before the first request, a
|
|
126
|
+
* preview describes each branch, and a confirmed run continues past a failed
|
|
127
|
+
* branch so one refused deletion does not strand the rest of the list.
|
|
128
|
+
*/
|
|
129
|
+
export declare function deleteBranchesThroughPlatform(client: R5dctlClient, projectRef: string, branchNames: readonly string[], options: {
|
|
130
|
+
confirmed: boolean;
|
|
131
|
+
onResult?: (result: R5dctlBranchDeletionResult) => void;
|
|
132
|
+
}): Promise<R5dctlBranchDeletionsOutcome>;
|
|
133
|
+
export declare function renderBranchDeletionResult(result: R5dctlBranchDeletionResult): string;
|
|
134
|
+
export declare function branchDeletionsConfirmationMessage(branches: readonly R5dctlBranchDescription[]): string;
|
|
135
|
+
export declare function branchDeletionsFailureMessage(results: readonly R5dctlBranchDeletionResult[]): string;
|
|
106
136
|
export declare function renderBranchDeletionPreview(branch: R5dctlBranchDescription): string;
|
|
107
137
|
export declare function branchDeletionConfirmationMessage(branch: R5dctlBranchDescription): string;
|
|
108
138
|
export declare function renderBranchDeletion(result: R5dctlBranchDeleteResponse): string;
|
|
@@ -174,6 +204,7 @@ export declare function advanceTransientDevicePollBackoff(intervalMs: number, tr
|
|
|
174
204
|
nextBackoffMs: number;
|
|
175
205
|
};
|
|
176
206
|
export declare function handleAuthLogin(client: R5dctlClient, options: GlobalOptions, commandArgs: string[], config: R5dctlConfig): Promise<void>;
|
|
207
|
+
export declare function renderSessionList(sessions: R5dctlSessionSummary[], nowMs?: number): string;
|
|
177
208
|
export declare function renderWorkspaceStatus(status: R5dctlWorkspaceStatus): string;
|
|
178
209
|
export declare function renderBranchList(branches: R5dctlBranch[]): string;
|
|
179
210
|
export declare function renderWorkspaceSync(result: R5dctlWorkspaceSyncResult): string;
|
|
@@ -254,6 +285,11 @@ export type R5dctlCommandResponse = {
|
|
|
254
285
|
error?: string;
|
|
255
286
|
summary?: string;
|
|
256
287
|
diffSummary?: string;
|
|
288
|
+
/** Sub-agent sessions this session launched; older servers omit it. */
|
|
289
|
+
childSessions?: {
|
|
290
|
+
total: number;
|
|
291
|
+
active: number;
|
|
292
|
+
};
|
|
257
293
|
};
|
|
258
294
|
export type R5dctlSessionRunClient = {
|
|
259
295
|
projects: {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ricsam/r5dctl",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.106",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "./dist/cjs/cli.cjs",
|
|
6
6
|
"module": "./dist/mjs/cli.mjs",
|
|
@@ -26,7 +26,7 @@
|
|
|
26
26
|
"r5dctl": "dist/cjs/main.cjs"
|
|
27
27
|
},
|
|
28
28
|
"dependencies": {
|
|
29
|
-
"@ricsam/r5d-api": "^0.0.
|
|
29
|
+
"@ricsam/r5d-api": "^0.0.106",
|
|
30
30
|
"dotenv": "^17",
|
|
31
31
|
"qrcode": "^1.5.4",
|
|
32
32
|
"ws": "^8.18.3"
|