@ricsam/r5dctl 0.0.101 → 0.0.104

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 CHANGED
@@ -29,7 +29,10 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
29
29
  var cli_exports = {};
30
30
  __export(cli_exports, {
31
31
  advanceTransientDevicePollBackoff: () => advanceTransientDevicePollBackoff,
32
+ branchDeletionConfirmationMessage: () => branchDeletionConfirmationMessage,
32
33
  collectK8sUsage: () => collectK8sUsage,
34
+ createBranchThroughPlatform: () => createBranchThroughPlatform,
35
+ deleteBranchThroughPlatform: () => deleteBranchThroughPlatform,
33
36
  dispatchSessionRunCommand: () => dispatchSessionRunCommand,
34
37
  followR5dctlSession: () => followR5dctlSession,
35
38
  formatK8sCpu: () => formatK8sCpu,
@@ -41,10 +44,13 @@ __export(cli_exports, {
41
44
  getR5dctlVersion: () => getR5dctlVersion,
42
45
  getTransientDevicePollDelay: () => getTransientDevicePollDelay,
43
46
  handleAuthLogin: () => handleAuthLogin,
47
+ isUnsupportedServerRoute: () => isUnsupportedServerRoute,
44
48
  main: () => main,
45
49
  parseAnswerEnvRequestCommandArgs: () => parseAnswerEnvRequestCommandArgs,
46
50
  parseAnswerFlags: () => parseAnswerFlags,
47
51
  parseAnswerQuestionsCommandArgs: () => parseAnswerQuestionsCommandArgs,
52
+ parseBranchCreateArgs: () => parseBranchCreateArgs,
53
+ parseBranchDeleteArgs: () => parseBranchDeleteArgs,
48
54
  parseConversationForkArgs: () => parseConversationForkArgs,
49
55
  parseConversationInspectWorkArgs: () => parseConversationInspectWorkArgs,
50
56
  parseConversationOverviewArgs: () => parseConversationOverviewArgs,
@@ -57,10 +63,15 @@ __export(cli_exports, {
57
63
  parsePromptArgs: () => parsePromptArgs,
58
64
  parsePsHistoryArgs: () => parsePsHistoryArgs,
59
65
  parsePsListArgs: () => parsePsListArgs,
66
+ parseSessionProcessLogArgs: () => parseSessionProcessLogArgs,
60
67
  parseSessionRunCommand: () => parseSessionRunCommand,
61
68
  parseSetEnvArgs: () => parseSetEnvArgs,
62
69
  parseShellArgs: () => parseShellArgs,
63
70
  readDotenvFile: () => readDotenvFile,
71
+ renderBranchDeletion: () => renderBranchDeletion,
72
+ renderBranchDeletionPreview: () => renderBranchDeletionPreview,
73
+ renderBranchList: () => renderBranchList,
74
+ renderBranchOperation: () => renderBranchOperation,
64
75
  renderConversationForkResponse: () => renderConversationForkResponse,
65
76
  renderConversationHeadResponse: () => renderConversationHeadResponse,
66
77
  renderConversationNodeResponse: () => renderConversationNodeResponse,
@@ -73,6 +84,7 @@ __export(cli_exports, {
73
84
  renderProcessHistory: () => renderProcessHistory,
74
85
  renderProcessInspection: () => renderProcessInspection,
75
86
  renderProcessList: () => renderProcessList,
87
+ renderProcessLog: () => renderProcessLog,
76
88
  renderSessionRunStart: () => renderSessionRunStart,
77
89
  renderSessionRunStatus: () => renderSessionRunStatus,
78
90
  renderWorkspaceStatus: () => renderWorkspaceStatus,
@@ -155,14 +167,19 @@ const K8S_HELP_TEXT = [
155
167
  ].join("\n");
156
168
  const SESSION_RUN_HELP_TEXT = [
157
169
  "Usage:",
158
- ' r5dctl -p <project> session start --worker <label> --worktree <branch> --model <tier> [-f|--follow|--watch] [--tools] "<prompt>"',
159
- ' r5dctl -p <project> session start --worker <label> --new-worktree <branch> --source worktree:<branch> --model <tier> [-f|--follow|--watch] [--tools] "<prompt>"',
170
+ ' r5dctl -p <project> session start --worker <label> --worktree <branch> --model <tier> [-f|--follow|--watch] [--tools] [--parent-session <session-id>] "<prompt>"',
171
+ ' r5dctl -p <project> session start --worker <label> --new-worktree <branch> --source worktree:<branch> [--working-tree carry|clean] --model <tier> [-f|--follow|--watch] [--tools] [--parent-session <session-id>] "<prompt>"',
160
172
  " r5dctl session status <session-id>",
161
173
  ' r5dctl session prompt --worker <label> --mode <mode> --model <tier> <session-id> "<prompt>"',
162
174
  " r5dctl session stop <session-id>",
175
+ " r5dctl session process-log <run-id> [--tail <n>] [--grep <pattern>]",
163
176
  "",
164
177
  "Create, inspect, resume, and stop ordinary agent sessions.",
178
+ "A new worktree starts with the source checkout's uncommitted changes (--working-tree carry, the default);",
179
+ "--working-tree clean checks out the source branch's current commit only. Start and status print the mode, never a commit hash.",
165
180
  "Use -f/--follow/--watch to attach after a start; Ctrl-C detaches without stopping the session.",
181
+ "process-log prints a shell run's stored stdout/stderr from the server: the whole log, its last n lines,",
182
+ "or the lines matching a JavaScript regular expression. The log is not a file on the worker.",
166
183
  ""
167
184
  ].join("\n");
168
185
  const SHARED_HELP_ENTRIES = [
@@ -192,6 +209,21 @@ const SHARED_HELP_ENTRIES = [
192
209
  { section: "projects", usage: "delete project <namespace/name|id>", description: "Delete a project." },
193
210
  { section: "branches", usage: "-p <project> get branches", description: "List branches for a project." },
194
211
  { section: "branches", usage: "-p <project> describe branch <branch>", description: "Show branch URLs and sessions." },
212
+ {
213
+ section: "branches",
214
+ usage: "-p <project> create branch <branch> --source worktree:<branch> [--working-tree <carry|clean>] [--worker <label>]",
215
+ description: "Create a linked worktree branch from an existing branch's checkout and print the operation id to poll."
216
+ },
217
+ {
218
+ section: "branches",
219
+ usage: "-p <project> describe branch-operation <operation-id>",
220
+ description: "Show whether a branch creation is provisioning, ready, or failed."
221
+ },
222
+ {
223
+ section: "branches",
224
+ usage: "-p <project> delete branch <branch> [--confirm]",
225
+ description: "Delete a branch and its sessions through the platform; without --confirm, only preview what would be deleted."
226
+ },
195
227
  {
196
228
  section: "envs",
197
229
  usage: "-p <project> get envs [--show-values]",
@@ -262,8 +294,8 @@ const SHARED_HELP_ENTRIES = [
262
294
  },
263
295
  {
264
296
  section: "sessions",
265
- usage: '-p <project> session start --worker <label> (--worktree <branch>|--new-worktree <branch> --source worktree:<branch>) --model <tier> [-f|--follow|--watch] [--tools] "<prompt>"',
266
- description: "Start an ordinary agent session on an explicit existing or new worktree."
297
+ usage: '-p <project> session start --worker <label> (--worktree <branch>|--new-worktree <branch> --source worktree:<branch> [--working-tree carry|clean]) --model <tier> [-f|--follow|--watch] [--tools] [--parent-session <session-id>] "<prompt>"',
298
+ description: "Start an ordinary agent session on an explicit existing or new worktree. A new worktree carries the source checkout's uncommitted changes unless --working-tree clean is passed. --parent-session links it like start_subagent: the parent is notified when the run finishes."
267
299
  },
268
300
  {
269
301
  section: "sessions",
@@ -280,6 +312,11 @@ const SHARED_HELP_ENTRIES = [
280
312
  usage: "session stop <session-id>",
281
313
  description: "Stop an active session generation."
282
314
  },
315
+ {
316
+ section: "sessions",
317
+ usage: "session process-log <run-id> [--tail <n>] [--grep <pattern>]",
318
+ description: "Print a shell run's stored stdout/stderr from the server: the whole log, its last lines, or lines matching a regular expression."
319
+ },
283
320
  {
284
321
  section: "sessions",
285
322
  usage: '-s <session-id> answer-questions --worker <label> --mode <mode> --model <tier> -a1 "<answer>" ...',
@@ -533,8 +570,8 @@ function parseCommandOperands(args, valueFlags, booleanFlags = /* @__PURE__ */ n
533
570
  function requireWorker(values) {
534
571
  return requireValue(values.get("--worker")?.trim(), "--worker <label> is required");
535
572
  }
536
- function parseWorktreeSource(rawValue) {
537
- const raw = requireValue(rawValue?.trim(), "--source worktree:<branch> is required with --new-worktree");
573
+ function parseWorktreeSource(rawValue, requiredMessage = "--source worktree:<branch> is required with --new-worktree") {
574
+ const raw = requireValue(rawValue?.trim(), requiredMessage);
538
575
  const separator = raw.indexOf(":");
539
576
  const type = raw.slice(0, separator);
540
577
  const branchName = raw.slice(separator + 1).trim();
@@ -543,6 +580,12 @@ function parseWorktreeSource(rawValue) {
543
580
  }
544
581
  return { type, branchName };
545
582
  }
583
+ function parseWorkingTreeMode(rawValue) {
584
+ if (rawValue === void 0) return "carry";
585
+ const mode = rawValue.trim();
586
+ if (mode !== "carry" && mode !== "clean") throw new Error("Invalid --working-tree. Expected carry or clean");
587
+ return mode;
588
+ }
546
589
  function requireModel(value) {
547
590
  const model = requireValue(value?.trim(), "--model <tier> is required");
548
591
  if (!MODEL_TIERS.has(model)) throw new Error("Invalid --model. Expected one of: low, medium, high, max");
@@ -555,6 +598,103 @@ function requireSessionMode(value) {
555
598
  }
556
599
  return mode;
557
600
  }
601
+ function parseBranchCreateArgs(args) {
602
+ const parsed = parseCommandOperands(args, /* @__PURE__ */ new Set(["--source", "--working-tree", "--worker"]), /* @__PURE__ */ new Map(), "create branch", 1);
603
+ if (parsed.positionals.length > 0) throw new Error(`Unexpected create branch argument: ${parsed.positionals[0]}`);
604
+ const worker = parsed.values.get("--worker")?.trim();
605
+ if (parsed.values.has("--worker") && !worker) throw new Error("Missing value for --worker");
606
+ return {
607
+ source: parseWorktreeSource(parsed.values.get("--source"), "--source worktree:<branch> is required for `create branch`"),
608
+ workingTree: parseWorkingTreeMode(parsed.values.get("--working-tree")),
609
+ ...worker ? { worker } : {}
610
+ };
611
+ }
612
+ function parseBranchDeleteArgs(args) {
613
+ let confirmed = false;
614
+ for (const arg of args) {
615
+ if (arg === "--confirm") {
616
+ confirmed = true;
617
+ continue;
618
+ }
619
+ throw new Error(arg.startsWith("-") ? `Unknown delete branch flag: ${arg}` : `Unexpected delete branch argument: ${arg}`);
620
+ }
621
+ return { confirmed };
622
+ }
623
+ const PRIMARY_BRANCH_NAME = "main";
624
+ function isUnsupportedServerRoute(error) {
625
+ return error instanceof import_r5d_api.R5dctlApiError && error.status === 404 && extractApiErrorMessage(error) === "Not Found";
626
+ }
627
+ async function requireServerRouteSupport(command, request) {
628
+ try {
629
+ return await request();
630
+ } catch (error) {
631
+ if (isUnsupportedServerRoute(error)) {
632
+ throw new Error(`This r5d server does not support \`${command}\` yet. Update the server, or manage the branch from the web sidebar.`);
633
+ }
634
+ throw error;
635
+ }
636
+ }
637
+ async function createBranchThroughPlatform(client, projectRef, input) {
638
+ return await requireServerRouteSupport(
639
+ "create branch",
640
+ () => client.projects.branches.create(projectRef, {
641
+ requestId: (0, import_node_crypto.randomUUID)(),
642
+ branchName: input.branchName,
643
+ source: input.source,
644
+ workingTree: input.workingTree,
645
+ ...input.worker ? { worker: input.worker } : {}
646
+ })
647
+ );
648
+ }
649
+ async function deleteBranchThroughPlatform(client, projectRef, branchName, options) {
650
+ if (branchName === PRIMARY_BRANCH_NAME) throw new Error(`The primary branch ${branchName} cannot be deleted`);
651
+ const branch = await client.projects.branches.describe(projectRef, branchName);
652
+ if (!options.confirmed) return { kind: "preview", branch };
653
+ const result = await requireServerRouteSupport("delete branch", () => client.projects.branches.delete(projectRef, branchName));
654
+ return { kind: "deleted", result };
655
+ }
656
+ function renderBranchDeletionPreview(branch) {
657
+ const count = branch.sessions.length;
658
+ const lines = [
659
+ `Project: ${branch.project.path}`,
660
+ `Branch: ${branch.branchName}`,
661
+ `Sessions: ${count}`,
662
+ ...branch.sessions.map((session) => ` ${session.id} ${session.name ?? "(unnamed)"}`),
663
+ `Deleting this branch removes its checkout from every connected worker and deletes the ${count} session(s) above.`
664
+ ];
665
+ return `${lines.join("\n")}
666
+ `;
667
+ }
668
+ function branchDeletionConfirmationMessage(branch) {
669
+ return `Nothing was deleted. Re-run with --confirm to delete branch ${branch.branchName} and its ${branch.sessions.length} session(s).`;
670
+ }
671
+ function renderBranchDeletion(result) {
672
+ const lines = [
673
+ `Deleted branch ${result.branchName} and ${result.deletedSessionIds.length} session(s).`,
674
+ ...result.deletedSessionIds.map((sessionId) => ` ${sessionId}`)
675
+ ];
676
+ return `${lines.join("\n")}
677
+ `;
678
+ }
679
+ function renderBranchOperation(operation, projectRef) {
680
+ const lines = [
681
+ `Operation: ${operation.id}`,
682
+ `Branch: ${operation.branchName}`,
683
+ `Source: worktree:${operation.source.branchName}`,
684
+ `Working tree: ${operation.workingTree}`,
685
+ `Worker: ${operation.worker ?? "(pending)"}`,
686
+ `Status: ${operation.status}`
687
+ ];
688
+ if (operation.error) lines.push(`Error: ${operation.error}`);
689
+ if (operation.cleanupPending) lines.push("Cleanup: pending");
690
+ if (operation.status === "provisioning") {
691
+ lines.push(`Check: r5dctl -p ${projectRef} describe branch-operation ${operation.id}`);
692
+ } else if (operation.status === "ready") {
693
+ lines.push(`Branch state: ${formatBranchStateHint(operation.branchName)}`);
694
+ }
695
+ return `${lines.join("\n")}
696
+ `;
697
+ }
558
698
  function rejectSessionRunGlobalScopes(options, allowProject) {
559
699
  if (!allowProject && options.project) throw new Error("--project/-p is only supported for `session start`");
560
700
  if (options.branch) throw new Error("--branch/-b is not supported for `session`; select the worktree explicitly");
@@ -568,7 +708,7 @@ function parseSessionRunCommand(options, args) {
568
708
  const project = requireValue(options.project, "--project/-p is required for `session start`");
569
709
  const parsed = parseCommandOperands(
570
710
  commandArgs,
571
- /* @__PURE__ */ new Set(["--worker", "--worktree", "--new-worktree", "--source", "--model"]),
711
+ /* @__PURE__ */ new Set(["--worker", "--worktree", "--new-worktree", "--source", "--working-tree", "--model", "--parent-session"]),
572
712
  /* @__PURE__ */ new Map([
573
713
  ["-f", "--follow"],
574
714
  ["--follow", "--follow"],
@@ -587,6 +727,9 @@ function parseSessionRunCommand(options, args) {
587
727
  }
588
728
  const sourceValue = parsed.values.get("--source");
589
729
  if (worktree && sourceValue) throw new Error("--source is only supported with --new-worktree");
730
+ const workingTreeValue = parsed.values.get("--working-tree");
731
+ if (worktree && workingTreeValue !== void 0) throw new Error("--working-tree is only supported with --new-worktree");
732
+ const parentSessionId = parsed.values.get("--parent-session")?.trim();
590
733
  return {
591
734
  kind: "start",
592
735
  project,
@@ -595,7 +738,8 @@ function parseSessionRunCommand(options, args) {
595
738
  prompt,
596
739
  follow: parsed.booleans.has("--follow"),
597
740
  includeTools: parsed.booleans.has("--tools"),
598
- ...worktree ? { worktree } : { newWorktree, source: parseWorktreeSource(sourceValue) }
741
+ ...parentSessionId ? { parentSessionId } : {},
742
+ ...worktree ? { worktree } : { newWorktree, source: parseWorktreeSource(sourceValue), workingTree: parseWorkingTreeMode(workingTreeValue) }
599
743
  };
600
744
  }
601
745
  rejectSessionRunGlobalScopes(options, false);
@@ -621,6 +765,16 @@ function parseSessionRunCommand(options, args) {
621
765
  }
622
766
  throw new Error(`Unknown session command: ${subcommand}`);
623
767
  }
768
+ function parseSessionProcessLogArgs(args) {
769
+ const parsed = parseCommandOperands(args, /* @__PURE__ */ new Set(["--tail", "--grep"]), /* @__PURE__ */ new Map(), "session process-log");
770
+ const runId = requireValue(parsed.positionals[0], "Process run id is required for `session process-log`");
771
+ if (parsed.positionals.length > 1) throw new Error(`Unexpected session process-log argument: ${parsed.positionals[1]}`);
772
+ const tailValue = parsed.values.get("--tail");
773
+ const tail = tailValue === void 0 ? void 0 : Number(tailValue);
774
+ if (tail !== void 0 && (!Number.isInteger(tail) || tail < 1)) throw new Error("--tail must be a positive integer");
775
+ const grep = parsed.values.get("--grep");
776
+ return { runId, ...tail !== void 0 ? { tail } : {}, ...grep !== void 0 ? { grep } : {} };
777
+ }
624
778
  function assertAuthLoginArgs(args) {
625
779
  const valueFlags = /* @__PURE__ */ new Set(["--device-name", "--worker-label"]);
626
780
  const booleanFlags = /* @__PURE__ */ new Set(["--no-open", "--no-qr"]);
@@ -1447,10 +1601,34 @@ function renderWorkspaceStatus(status) {
1447
1601
  `Workspace HEAD: ${status.head ?? "(not initialized)"}`,
1448
1602
  `Latest sync: ${status.latestSync ? `${status.latestSync.outcome} on ${status.latestSync.workerLabel}` : "(none)"}`,
1449
1603
  `Remediation: ${status.incident ? `${status.incident.kind} ${status.incident.status} (${status.incident.id}) on ${status.incident.originWorkerLabel}` : "none"}`,
1450
- ...status.workers.map(
1451
- (worker) => `Worker ${worker.workerLabel}: ${worker.configured ? "configured" : "configuring"}; head ${worker.localHead ?? "(none)"}; pending checkouts ${worker.pendingCheckouts.length}`
1452
- )
1604
+ ...status.workers.flatMap((worker) => [
1605
+ `Worker ${worker.workerLabel}: ${worker.configured ? "configured" : "configuring"}; head ${worker.localHead ?? "(none)"}; pending checkouts ${worker.pendingCheckouts.length}`,
1606
+ ...renderMissingCheckouts(worker.workerLabel, worker.missingCheckouts ?? [])
1607
+ ])
1453
1608
  ];
1609
+ if (status.latestSync?.missingCheckouts?.length && !status.workers.some((worker) => worker.missingCheckouts?.length)) {
1610
+ lines.push(...renderMissingCheckouts(status.latestSync.workerLabel, status.latestSync.missingCheckouts));
1611
+ }
1612
+ return `${lines.join("\n")}
1613
+ `;
1614
+ }
1615
+ const CHECKOUT_RECOVERY_HINT = "delete the branch with `r5dctl -p <project> delete branch <branch> --confirm`, or reconnect the worker to recreate the checkout";
1616
+ function renderMissingCheckouts(workerLabel, missingCheckouts) {
1617
+ if (missingCheckouts.length === 0) return [];
1618
+ return [
1619
+ `Missing checkouts on ${workerLabel}: ${missingCheckouts.map((checkout) => `${checkout.projectId}/${checkout.branchName} (${checkout.reason})`).join(", ")}`,
1620
+ ` Workspace sync skips these branches; ${CHECKOUT_RECOVERY_HINT}.`
1621
+ ];
1622
+ }
1623
+ function renderBranchList(branches) {
1624
+ if (branches.length === 0) return "No branches found.\n";
1625
+ const lines = branches.map((branch) => {
1626
+ const checkout = branch.checkoutState ? ` checkout=${branch.checkoutState}${branch.checkoutStateWorkerLabel ? ` on ${branch.checkoutStateWorkerLabel}` : ""}` : "";
1627
+ return `${branch.branchName} sessions=${branch.sessionCount}${checkout}`;
1628
+ });
1629
+ if (branches.some((branch) => branch.checkoutState)) {
1630
+ lines.push(`Workspace sync skips a branch whose checkout is missing or not a worktree; ${CHECKOUT_RECOVERY_HINT}.`);
1631
+ }
1454
1632
  return `${lines.join("\n")}
1455
1633
  `;
1456
1634
  }
@@ -1678,6 +1856,32 @@ function renderProcessList(processes, nowMs = Date.now()) {
1678
1856
  function renderProcessHistory(processes, nowMs = Date.now()) {
1679
1857
  return processes.length === 0 ? "No process history.\n" : renderProcessList(processes, nowMs);
1680
1858
  }
1859
+ function renderProcessLog(log) {
1860
+ const sections = [];
1861
+ for (const [name, stream] of [
1862
+ ["stdout", log.stdout],
1863
+ ["stderr", log.stderr]
1864
+ ]) {
1865
+ if (stream.text.length === 0) continue;
1866
+ sections.push(`==> ${name} <==
1867
+ ${stream.text.endsWith("\n") ? stream.text : `${stream.text}
1868
+ `}`);
1869
+ }
1870
+ const selection = [
1871
+ ...log.grep !== void 0 ? [`lines matching /${log.grep}/`] : [],
1872
+ ...log.tailLines !== void 0 ? [`last ${log.tailLines} lines`] : []
1873
+ ];
1874
+ const cut = [log.stdout.truncated ? "stdout" : void 0, log.stderr.truncated ? "stderr" : void 0].filter(Boolean);
1875
+ const summary = [
1876
+ `Run ${log.runId} (${log.status}${log.exitCode !== void 0 ? `, exit ${log.exitCode}` : ""}): `,
1877
+ `stdout ${log.stdout.totalBytes} bytes, stderr ${log.stderr.totalBytes} bytes`,
1878
+ selection.length > 0 ? `; showing ${selection.join(", ")}` : "",
1879
+ cut.length > 0 ? `; ${cut.join(" and ")} cut to ${log.maxBytesPerStream} bytes, narrow with --tail or --grep` : "",
1880
+ sections.length === 0 ? "; no output to show" : ""
1881
+ ].join("");
1882
+ return { text: sections.join(""), summary: `${summary}
1883
+ ` };
1884
+ }
1681
1885
  function renderProcessInspection(process2) {
1682
1886
  return [
1683
1887
  `Run: ${process2.runId}`,
@@ -1831,13 +2035,22 @@ function formatStatusValue(value) {
1831
2035
  }
1832
2036
  return value === void 0 || value === null ? void 0 : String(value);
1833
2037
  }
2038
+ function pushWorktreeOriginLines(lines, session) {
2039
+ const source = responseString(session, "sourceBranch");
2040
+ const mode = responseString(session, "workingTree");
2041
+ if (!source || !mode) return;
2042
+ lines.push(`Source: ${source}`);
2043
+ const detail = mode === "carry" ? `carried ${source}'s uncommitted changes at launch` : mode === "clean" ? `clean checkout of ${source}'s commit at launch` : void 0;
2044
+ lines.push(`Working tree: ${mode}${detail ? ` (${detail})` : ""}`);
2045
+ }
1834
2046
  function renderSessionRunStart(session) {
1835
2047
  const lines = [
1836
2048
  `Session: ${responseString(session, "sessionId") ?? "(unknown)"}`,
1837
2049
  `Status: ${formatStatusValue(session.status) ?? "queued"}`,
1838
- `Worktree: ${responseString(session, "branchName") ?? "(provisioning)"}`,
1839
- `Worker: ${responseString(session, "worker") ?? "(unassigned)"}`
2050
+ `Worktree: ${responseString(session, "branchName") ?? "(provisioning)"}`
1840
2051
  ];
2052
+ pushWorktreeOriginLines(lines, session);
2053
+ lines.push(`Worker: ${responseString(session, "worker") ?? "(unassigned)"}`);
1841
2054
  const sessionUrl = responseString(session, "sessionUrl");
1842
2055
  if (sessionUrl) lines.push(`URL: ${sessionUrl}`);
1843
2056
  const sessionId = responseString(session, "sessionId");
@@ -1845,29 +2058,50 @@ function renderSessionRunStart(session) {
1845
2058
  return `${lines.join("\n")}
1846
2059
  `;
1847
2060
  }
2061
+ function formatBranchStateHint(branch) {
2062
+ const target = branch ?? "<branch>";
2063
+ const commands = [
2064
+ `git merge-base <your-branch> ${target}`,
2065
+ `git log --oneline <your-branch>..${target}`,
2066
+ "git status in the branch folder"
2067
+ ];
2068
+ return `inspect the worktree with git (${commands.join("; ")})`;
2069
+ }
2070
+ function formatQueuedAt(value) {
2071
+ const time = Date.parse(value);
2072
+ return Number.isNaN(time) ? value : new Date(time).toISOString().replace(/\.\d{3}Z$/, "Z");
2073
+ }
2074
+ function formatProvisioningQueueLine(session) {
2075
+ const position = session.provisioningQueuePosition;
2076
+ const queuedAt = responseString(session, "provisioningQueuedAt");
2077
+ if (typeof position !== "number" || !Number.isInteger(position) || position < 0 || !queuedAt) return void 0;
2078
+ const since = formatQueuedAt(queuedAt);
2079
+ if (position === 0) return `Provisioning: worker started the branch operation at ${since}`;
2080
+ return `Provisioning: queued behind ${position} worker operation${position === 1 ? "" : "s"} since ${since}`;
2081
+ }
1848
2082
  function renderSessionRunStatus(session) {
1849
2083
  const status = formatStatusValue(session.status) ?? "unknown";
1850
2084
  const lines = [`Session: ${responseString(session, "sessionId") ?? "(unknown)"}`, `Status: ${status}`];
1851
2085
  const runStatus = formatStatusValue(session.runStatus);
1852
2086
  if (runStatus) lines.push(`Run: ${runStatus}`);
2087
+ const provisioningQueue = formatProvisioningQueueLine(session);
2088
+ if (provisioningQueue) lines.push(provisioningQueue);
1853
2089
  const promptDisposition = responseString(session, "promptDisposition");
1854
2090
  if (promptDisposition) lines.push(`Prompt: ${promptDisposition}`);
1855
2091
  const branch = responseString(session, "branchName");
1856
2092
  if (branch) lines.push(`Worktree: ${branch}`);
2093
+ pushWorktreeOriginLines(lines, session);
1857
2094
  for (const [label, key] of [
1858
- ["Workspace head", "workspaceHead"],
1859
- ["Baseline", "baselineCommit"],
1860
- ["Head", "headCommit"],
1861
2095
  ["Worker", "worker"],
2096
+ ["Parent session", "parentSessionId"],
1862
2097
  ["Session", "sessionUrl"]
1863
2098
  ]) {
1864
2099
  const value = responseString(session, key);
1865
2100
  if (value) lines.push(`${label}: ${value}`);
1866
2101
  }
2102
+ lines.push(`Branch state: ${formatBranchStateHint(branch)}`);
1867
2103
  const error = responseString(session, "error");
1868
2104
  if (error) lines.push(`Error: ${error}`);
1869
- const headCommitError = responseString(session, "headCommitError");
1870
- if (headCommitError) lines.push(`Head error: ${headCommitError}`);
1871
2105
  const diffSummary = responseString(session, "diffSummary");
1872
2106
  if (diffSummary) lines.push("", diffSummary);
1873
2107
  const summary = responseString(session, "summary");
@@ -1885,13 +2119,14 @@ async function dispatchSessionRunCommand(client, command) {
1885
2119
  prompt: command.prompt,
1886
2120
  worker: command.worker,
1887
2121
  model: command.model,
1888
- requestId: (0, import_node_crypto.randomUUID)()
2122
+ requestId: (0, import_node_crypto.randomUUID)(),
2123
+ ...command.parentSessionId ? { parentSessionId: command.parentSessionId } : {}
1889
2124
  };
1890
2125
  let input;
1891
2126
  if (command.worktree !== void 0) {
1892
2127
  input = { ...commonInput, worktree: command.worktree };
1893
2128
  } else if (command.newWorktree !== void 0 && command.source !== void 0) {
1894
- input = { ...commonInput, newWorktree: command.newWorktree, source: command.source };
2129
+ input = { ...commonInput, newWorktree: command.newWorktree, source: command.source, workingTree: command.workingTree };
1895
2130
  } else {
1896
2131
  throw new Error("Session start requires an existing or new worktree");
1897
2132
  }
@@ -2531,6 +2766,17 @@ async function executeR5dctlCommand(client, json, args) {
2531
2766
  `);
2532
2767
  return;
2533
2768
  }
2769
+ if (first === "session" && second === "process-log") {
2770
+ const selection = parseSessionProcessLogArgs(args.slice(2));
2771
+ const log = await client.processes.log(selection.runId, {
2772
+ ...selection.tail !== void 0 ? { tail: selection.tail } : {},
2773
+ ...selection.grep !== void 0 ? { grep: selection.grep } : {}
2774
+ });
2775
+ const rendered = renderProcessLog(log);
2776
+ write(log, rendered.text);
2777
+ if (!json) process.stderr.write(rendered.summary);
2778
+ return;
2779
+ }
2534
2780
  if (first === "sessions" && second === "recent") {
2535
2781
  const sessions = await client.sessions.recent(parseRecentSessionArgs(args.slice(2)));
2536
2782
  write(sessions, renderRecentSessionList(sessions));
@@ -2560,11 +2806,7 @@ async function executeR5dctlCommand(client, json, args) {
2560
2806
  if (first === "projects" && second === "branches" && third === "list" || first === "get" && second === "branches") {
2561
2807
  const projectRef = requireValue(first === "get" ? args[2] : args[3], "Missing project reference");
2562
2808
  const branches = await client.projects.branches.list(projectRef);
2563
- write(
2564
- branches,
2565
- branches.length === 0 ? "No branches found.\n" : `${branches.map((branch) => `${branch.branchName} sessions=${branch.sessionCount}`).join("\n")}
2566
- `
2567
- );
2809
+ write(branches, renderBranchList(branches));
2568
2810
  return;
2569
2811
  }
2570
2812
  if (first === "projects" && second === "branches" && third === "describe" || first === "describe" && second === "branch") {
@@ -2579,6 +2821,40 @@ Sessions: ${result.sessions.length}
2579
2821
  `);
2580
2822
  return;
2581
2823
  }
2824
+ if (first === "projects" && second === "branches" && third === "create" || first === "create" && second === "branch") {
2825
+ const offset = first === "create" ? 2 : 3;
2826
+ const projectRef = requireValue(args[offset], "Missing project reference");
2827
+ const branchName = requireValue(args[offset + 1], "Missing branch name");
2828
+ const operation = await createBranchThroughPlatform(client, projectRef, {
2829
+ branchName,
2830
+ ...parseBranchCreateArgs(args.slice(offset + 2))
2831
+ });
2832
+ write(operation, renderBranchOperation(operation, projectRef));
2833
+ return;
2834
+ }
2835
+ if (first === "projects" && second === "branches" && third === "operation" || first === "describe" && second === "branch-operation") {
2836
+ const offset = first === "describe" ? 2 : 3;
2837
+ const projectRef = requireValue(args[offset], "Missing project reference");
2838
+ const operationId = requireValue(args[offset + 1], "Missing operation id");
2839
+ const operation = await requireServerRouteSupport(
2840
+ "describe branch-operation",
2841
+ () => client.projects.branches.describeOperation(projectRef, operationId)
2842
+ );
2843
+ write(operation, renderBranchOperation(operation, projectRef));
2844
+ return;
2845
+ }
2846
+ if (first === "projects" && second === "branches" && third === "delete" || first === "delete" && second === "branch") {
2847
+ const offset = first === "delete" ? 2 : 3;
2848
+ const projectRef = requireValue(args[offset], "Missing project reference");
2849
+ const branchName = requireValue(args[offset + 1], "Missing branch name");
2850
+ const deletion = await deleteBranchThroughPlatform(client, projectRef, branchName, parseBranchDeleteArgs(args.slice(offset + 2)));
2851
+ if (deletion.kind === "preview") {
2852
+ write({ ...deletion.branch, confirmed: false }, renderBranchDeletionPreview(deletion.branch));
2853
+ throw new Error(branchDeletionConfirmationMessage(deletion.branch));
2854
+ }
2855
+ write(deletion.result, renderBranchDeletion(deletion.result));
2856
+ return;
2857
+ }
2582
2858
  if (first === "get" && second === "envs") {
2583
2859
  const projectRef = requireValue(args[2], "Missing project reference");
2584
2860
  const options = parseGetEnvsArgs(args.slice(3));
@@ -2995,6 +3271,19 @@ function resolveCommandExecution(options, rest) {
2995
3271
  pluginArgs: ["describe", "branch", options.project, branch]
2996
3272
  };
2997
3273
  }
3274
+ if (target === "branch-operation") {
3275
+ if (!options.project) {
3276
+ throw new Error("--project/-p is required for `describe branch-operation`");
3277
+ }
3278
+ const operationId = requireValue(commandArgs[1], "Operation id is required for `describe branch-operation`");
3279
+ if (commandArgs.length > 2) {
3280
+ throw new Error(`Unexpected describe branch-operation argument: ${commandArgs[2]}`);
3281
+ }
3282
+ return {
3283
+ kind: "plugin",
3284
+ pluginArgs: ["describe", "branch-operation", options.project, operationId]
3285
+ };
3286
+ }
2998
3287
  if (target === "session") {
2999
3288
  const sessionId = commandArgs[1] ?? options.session;
3000
3289
  if (!sessionId) {
@@ -3027,6 +3316,23 @@ function resolveCommandExecution(options, rest) {
3027
3316
  pluginArgs: ["create", "session", projectRef, branch, ...flagArgs]
3028
3317
  };
3029
3318
  }
3319
+ if (target === "branch") {
3320
+ if (!options.project) {
3321
+ throw new Error("--project/-p is required for `create branch`");
3322
+ }
3323
+ const rawArgs = commandArgs.slice(1);
3324
+ const positionalBranch = rawArgs[0] && !rawArgs[0].startsWith("-") ? rawArgs[0] : void 0;
3325
+ const branch = positionalBranch ?? options.branch;
3326
+ if (!branch) {
3327
+ throw new Error("Branch name is required for `create branch`");
3328
+ }
3329
+ const flagArgs = positionalBranch ? rawArgs.slice(1) : rawArgs;
3330
+ parseBranchCreateArgs(flagArgs);
3331
+ return {
3332
+ kind: "plugin",
3333
+ pluginArgs: ["create", "branch", options.project, branch, ...flagArgs]
3334
+ };
3335
+ }
3030
3336
  throw new Error("Unknown create command");
3031
3337
  }
3032
3338
  if (command === "update") {
@@ -3077,6 +3383,23 @@ function resolveCommandExecution(options, rest) {
3077
3383
  pluginArgs: ["delete", "session", sessionId]
3078
3384
  };
3079
3385
  }
3386
+ if (target === "branch") {
3387
+ if (!options.project) {
3388
+ throw new Error("--project/-p is required for `delete branch`");
3389
+ }
3390
+ const rawArgs = commandArgs.slice(1);
3391
+ const positionalBranch = rawArgs[0] && !rawArgs[0].startsWith("-") ? rawArgs[0] : void 0;
3392
+ const branch = positionalBranch ?? options.branch;
3393
+ if (!branch) {
3394
+ throw new Error("Branch name is required for `delete branch`");
3395
+ }
3396
+ const flagArgs = positionalBranch ? rawArgs.slice(1) : rawArgs;
3397
+ parseBranchDeleteArgs(flagArgs);
3398
+ return {
3399
+ kind: "plugin",
3400
+ pluginArgs: ["delete", "branch", options.project, branch, ...flagArgs]
3401
+ };
3402
+ }
3080
3403
  throw new Error("Unknown delete command");
3081
3404
  }
3082
3405
  if (command === "conversation") {
@@ -3138,6 +3461,20 @@ function resolveCommandExecution(options, rest) {
3138
3461
  };
3139
3462
  }
3140
3463
  if (command === "session") {
3464
+ if (commandArgs[0] === "process-log") {
3465
+ rejectSessionRunGlobalScopes(options, false);
3466
+ const selection = parseSessionProcessLogArgs(commandArgs.slice(1));
3467
+ return {
3468
+ kind: "plugin",
3469
+ pluginArgs: [
3470
+ "session",
3471
+ "process-log",
3472
+ selection.runId,
3473
+ ...selection.tail !== void 0 ? [`--tail=${selection.tail}`] : [],
3474
+ ...selection.grep !== void 0 ? [`--grep=${selection.grep}`] : []
3475
+ ]
3476
+ };
3477
+ }
3141
3478
  return { kind: "session-run", command: parseSessionRunCommand(options, commandArgs) };
3142
3479
  }
3143
3480
  if (command === "answer-questions") {
@@ -3290,7 +3627,10 @@ async function main(argv = process.argv.slice(2)) {
3290
3627
  // Annotate the CommonJS export names for ESM import in node:
3291
3628
  0 && (module.exports = {
3292
3629
  advanceTransientDevicePollBackoff,
3630
+ branchDeletionConfirmationMessage,
3293
3631
  collectK8sUsage,
3632
+ createBranchThroughPlatform,
3633
+ deleteBranchThroughPlatform,
3294
3634
  dispatchSessionRunCommand,
3295
3635
  followR5dctlSession,
3296
3636
  formatK8sCpu,
@@ -3302,10 +3642,13 @@ async function main(argv = process.argv.slice(2)) {
3302
3642
  getR5dctlVersion,
3303
3643
  getTransientDevicePollDelay,
3304
3644
  handleAuthLogin,
3645
+ isUnsupportedServerRoute,
3305
3646
  main,
3306
3647
  parseAnswerEnvRequestCommandArgs,
3307
3648
  parseAnswerFlags,
3308
3649
  parseAnswerQuestionsCommandArgs,
3650
+ parseBranchCreateArgs,
3651
+ parseBranchDeleteArgs,
3309
3652
  parseConversationForkArgs,
3310
3653
  parseConversationInspectWorkArgs,
3311
3654
  parseConversationOverviewArgs,
@@ -3318,10 +3661,15 @@ async function main(argv = process.argv.slice(2)) {
3318
3661
  parsePromptArgs,
3319
3662
  parsePsHistoryArgs,
3320
3663
  parsePsListArgs,
3664
+ parseSessionProcessLogArgs,
3321
3665
  parseSessionRunCommand,
3322
3666
  parseSetEnvArgs,
3323
3667
  parseShellArgs,
3324
3668
  readDotenvFile,
3669
+ renderBranchDeletion,
3670
+ renderBranchDeletionPreview,
3671
+ renderBranchList,
3672
+ renderBranchOperation,
3325
3673
  renderConversationForkResponse,
3326
3674
  renderConversationHeadResponse,
3327
3675
  renderConversationNodeResponse,
@@ -3334,6 +3682,7 @@ async function main(argv = process.argv.slice(2)) {
3334
3682
  renderProcessHistory,
3335
3683
  renderProcessInspection,
3336
3684
  renderProcessList,
3685
+ renderProcessLog,
3337
3686
  renderSessionRunStart,
3338
3687
  renderSessionRunStatus,
3339
3688
  renderWorkspaceStatus,
@@ -1,5 +1,5 @@
1
1
  {
2
2
  "name": "@ricsam/r5dctl",
3
- "version": "0.0.101",
3
+ "version": "0.0.104",
4
4
  "type": "commonjs"
5
5
  }