@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/mjs/cli.mjs CHANGED
@@ -72,14 +72,19 @@ const K8S_HELP_TEXT = [
72
72
  ].join("\n");
73
73
  const SESSION_RUN_HELP_TEXT = [
74
74
  "Usage:",
75
- ' r5dctl -p <project> session start --worker <label> --worktree <branch> --model <tier> [-f|--follow|--watch] [--tools] "<prompt>"',
76
- ' r5dctl -p <project> session start --worker <label> --new-worktree <branch> --source worktree:<branch> --model <tier> [-f|--follow|--watch] [--tools] "<prompt>"',
75
+ ' r5dctl -p <project> session start --worker <label> --worktree <branch> --model <tier> [-f|--follow|--watch] [--tools] [--parent-session <session-id>] "<prompt>"',
76
+ ' 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>"',
77
77
  " r5dctl session status <session-id>",
78
78
  ' r5dctl session prompt --worker <label> --mode <mode> --model <tier> <session-id> "<prompt>"',
79
79
  " r5dctl session stop <session-id>",
80
+ " r5dctl session process-log <run-id> [--tail <n>] [--grep <pattern>]",
80
81
  "",
81
82
  "Create, inspect, resume, and stop ordinary agent sessions.",
83
+ "A new worktree starts with the source checkout's uncommitted changes (--working-tree carry, the default);",
84
+ "--working-tree clean checks out the source branch's current commit only. Start and status print the mode, never a commit hash.",
82
85
  "Use -f/--follow/--watch to attach after a start; Ctrl-C detaches without stopping the session.",
86
+ "process-log prints a shell run's stored stdout/stderr from the server: the whole log, its last n lines,",
87
+ "or the lines matching a JavaScript regular expression. The log is not a file on the worker.",
83
88
  ""
84
89
  ].join("\n");
85
90
  const SHARED_HELP_ENTRIES = [
@@ -109,6 +114,21 @@ const SHARED_HELP_ENTRIES = [
109
114
  { section: "projects", usage: "delete project <namespace/name|id>", description: "Delete a project." },
110
115
  { section: "branches", usage: "-p <project> get branches", description: "List branches for a project." },
111
116
  { section: "branches", usage: "-p <project> describe branch <branch>", description: "Show branch URLs and sessions." },
117
+ {
118
+ section: "branches",
119
+ usage: "-p <project> create branch <branch> --source worktree:<branch> [--working-tree <carry|clean>] [--worker <label>]",
120
+ description: "Create a linked worktree branch from an existing branch's checkout and print the operation id to poll."
121
+ },
122
+ {
123
+ section: "branches",
124
+ usage: "-p <project> describe branch-operation <operation-id>",
125
+ description: "Show whether a branch creation is provisioning, ready, or failed."
126
+ },
127
+ {
128
+ section: "branches",
129
+ usage: "-p <project> delete branch <branch> [--confirm]",
130
+ description: "Delete a branch and its sessions through the platform; without --confirm, only preview what would be deleted."
131
+ },
112
132
  {
113
133
  section: "envs",
114
134
  usage: "-p <project> get envs [--show-values]",
@@ -179,8 +199,8 @@ const SHARED_HELP_ENTRIES = [
179
199
  },
180
200
  {
181
201
  section: "sessions",
182
- usage: '-p <project> session start --worker <label> (--worktree <branch>|--new-worktree <branch> --source worktree:<branch>) --model <tier> [-f|--follow|--watch] [--tools] "<prompt>"',
183
- description: "Start an ordinary agent session on an explicit existing or new worktree."
202
+ 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>"',
203
+ 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."
184
204
  },
185
205
  {
186
206
  section: "sessions",
@@ -197,6 +217,11 @@ const SHARED_HELP_ENTRIES = [
197
217
  usage: "session stop <session-id>",
198
218
  description: "Stop an active session generation."
199
219
  },
220
+ {
221
+ section: "sessions",
222
+ usage: "session process-log <run-id> [--tail <n>] [--grep <pattern>]",
223
+ description: "Print a shell run's stored stdout/stderr from the server: the whole log, its last lines, or lines matching a regular expression."
224
+ },
200
225
  {
201
226
  section: "sessions",
202
227
  usage: '-s <session-id> answer-questions --worker <label> --mode <mode> --model <tier> -a1 "<answer>" ...',
@@ -450,8 +475,8 @@ function parseCommandOperands(args, valueFlags, booleanFlags = /* @__PURE__ */ n
450
475
  function requireWorker(values) {
451
476
  return requireValue(values.get("--worker")?.trim(), "--worker <label> is required");
452
477
  }
453
- function parseWorktreeSource(rawValue) {
454
- const raw = requireValue(rawValue?.trim(), "--source worktree:<branch> is required with --new-worktree");
478
+ function parseWorktreeSource(rawValue, requiredMessage = "--source worktree:<branch> is required with --new-worktree") {
479
+ const raw = requireValue(rawValue?.trim(), requiredMessage);
455
480
  const separator = raw.indexOf(":");
456
481
  const type = raw.slice(0, separator);
457
482
  const branchName = raw.slice(separator + 1).trim();
@@ -460,6 +485,12 @@ function parseWorktreeSource(rawValue) {
460
485
  }
461
486
  return { type, branchName };
462
487
  }
488
+ function parseWorkingTreeMode(rawValue) {
489
+ if (rawValue === void 0) return "carry";
490
+ const mode = rawValue.trim();
491
+ if (mode !== "carry" && mode !== "clean") throw new Error("Invalid --working-tree. Expected carry or clean");
492
+ return mode;
493
+ }
463
494
  function requireModel(value) {
464
495
  const model = requireValue(value?.trim(), "--model <tier> is required");
465
496
  if (!MODEL_TIERS.has(model)) throw new Error("Invalid --model. Expected one of: low, medium, high, max");
@@ -472,6 +503,103 @@ function requireSessionMode(value) {
472
503
  }
473
504
  return mode;
474
505
  }
506
+ function parseBranchCreateArgs(args) {
507
+ const parsed = parseCommandOperands(args, /* @__PURE__ */ new Set(["--source", "--working-tree", "--worker"]), /* @__PURE__ */ new Map(), "create branch", 1);
508
+ if (parsed.positionals.length > 0) throw new Error(`Unexpected create branch argument: ${parsed.positionals[0]}`);
509
+ const worker = parsed.values.get("--worker")?.trim();
510
+ if (parsed.values.has("--worker") && !worker) throw new Error("Missing value for --worker");
511
+ return {
512
+ source: parseWorktreeSource(parsed.values.get("--source"), "--source worktree:<branch> is required for `create branch`"),
513
+ workingTree: parseWorkingTreeMode(parsed.values.get("--working-tree")),
514
+ ...worker ? { worker } : {}
515
+ };
516
+ }
517
+ function parseBranchDeleteArgs(args) {
518
+ let confirmed = false;
519
+ for (const arg of args) {
520
+ if (arg === "--confirm") {
521
+ confirmed = true;
522
+ continue;
523
+ }
524
+ throw new Error(arg.startsWith("-") ? `Unknown delete branch flag: ${arg}` : `Unexpected delete branch argument: ${arg}`);
525
+ }
526
+ return { confirmed };
527
+ }
528
+ const PRIMARY_BRANCH_NAME = "main";
529
+ function isUnsupportedServerRoute(error) {
530
+ return error instanceof R5dctlApiError && error.status === 404 && extractApiErrorMessage(error) === "Not Found";
531
+ }
532
+ async function requireServerRouteSupport(command, request) {
533
+ try {
534
+ return await request();
535
+ } catch (error) {
536
+ if (isUnsupportedServerRoute(error)) {
537
+ throw new Error(`This r5d server does not support \`${command}\` yet. Update the server, or manage the branch from the web sidebar.`);
538
+ }
539
+ throw error;
540
+ }
541
+ }
542
+ async function createBranchThroughPlatform(client, projectRef, input) {
543
+ return await requireServerRouteSupport(
544
+ "create branch",
545
+ () => client.projects.branches.create(projectRef, {
546
+ requestId: randomUUID(),
547
+ branchName: input.branchName,
548
+ source: input.source,
549
+ workingTree: input.workingTree,
550
+ ...input.worker ? { worker: input.worker } : {}
551
+ })
552
+ );
553
+ }
554
+ async function deleteBranchThroughPlatform(client, projectRef, branchName, options) {
555
+ if (branchName === PRIMARY_BRANCH_NAME) throw new Error(`The primary branch ${branchName} cannot be deleted`);
556
+ const branch = await client.projects.branches.describe(projectRef, branchName);
557
+ if (!options.confirmed) return { kind: "preview", branch };
558
+ const result = await requireServerRouteSupport("delete branch", () => client.projects.branches.delete(projectRef, branchName));
559
+ return { kind: "deleted", result };
560
+ }
561
+ function renderBranchDeletionPreview(branch) {
562
+ const count = branch.sessions.length;
563
+ const lines = [
564
+ `Project: ${branch.project.path}`,
565
+ `Branch: ${branch.branchName}`,
566
+ `Sessions: ${count}`,
567
+ ...branch.sessions.map((session) => ` ${session.id} ${session.name ?? "(unnamed)"}`),
568
+ `Deleting this branch removes its checkout from every connected worker and deletes the ${count} session(s) above.`
569
+ ];
570
+ return `${lines.join("\n")}
571
+ `;
572
+ }
573
+ function branchDeletionConfirmationMessage(branch) {
574
+ return `Nothing was deleted. Re-run with --confirm to delete branch ${branch.branchName} and its ${branch.sessions.length} session(s).`;
575
+ }
576
+ function renderBranchDeletion(result) {
577
+ const lines = [
578
+ `Deleted branch ${result.branchName} and ${result.deletedSessionIds.length} session(s).`,
579
+ ...result.deletedSessionIds.map((sessionId) => ` ${sessionId}`)
580
+ ];
581
+ return `${lines.join("\n")}
582
+ `;
583
+ }
584
+ function renderBranchOperation(operation, projectRef) {
585
+ const lines = [
586
+ `Operation: ${operation.id}`,
587
+ `Branch: ${operation.branchName}`,
588
+ `Source: worktree:${operation.source.branchName}`,
589
+ `Working tree: ${operation.workingTree}`,
590
+ `Worker: ${operation.worker ?? "(pending)"}`,
591
+ `Status: ${operation.status}`
592
+ ];
593
+ if (operation.error) lines.push(`Error: ${operation.error}`);
594
+ if (operation.cleanupPending) lines.push("Cleanup: pending");
595
+ if (operation.status === "provisioning") {
596
+ lines.push(`Check: r5dctl -p ${projectRef} describe branch-operation ${operation.id}`);
597
+ } else if (operation.status === "ready") {
598
+ lines.push(`Branch state: ${formatBranchStateHint(operation.branchName)}`);
599
+ }
600
+ return `${lines.join("\n")}
601
+ `;
602
+ }
475
603
  function rejectSessionRunGlobalScopes(options, allowProject) {
476
604
  if (!allowProject && options.project) throw new Error("--project/-p is only supported for `session start`");
477
605
  if (options.branch) throw new Error("--branch/-b is not supported for `session`; select the worktree explicitly");
@@ -485,7 +613,7 @@ function parseSessionRunCommand(options, args) {
485
613
  const project = requireValue(options.project, "--project/-p is required for `session start`");
486
614
  const parsed = parseCommandOperands(
487
615
  commandArgs,
488
- /* @__PURE__ */ new Set(["--worker", "--worktree", "--new-worktree", "--source", "--model"]),
616
+ /* @__PURE__ */ new Set(["--worker", "--worktree", "--new-worktree", "--source", "--working-tree", "--model", "--parent-session"]),
489
617
  /* @__PURE__ */ new Map([
490
618
  ["-f", "--follow"],
491
619
  ["--follow", "--follow"],
@@ -504,6 +632,9 @@ function parseSessionRunCommand(options, args) {
504
632
  }
505
633
  const sourceValue = parsed.values.get("--source");
506
634
  if (worktree && sourceValue) throw new Error("--source is only supported with --new-worktree");
635
+ const workingTreeValue = parsed.values.get("--working-tree");
636
+ if (worktree && workingTreeValue !== void 0) throw new Error("--working-tree is only supported with --new-worktree");
637
+ const parentSessionId = parsed.values.get("--parent-session")?.trim();
507
638
  return {
508
639
  kind: "start",
509
640
  project,
@@ -512,7 +643,8 @@ function parseSessionRunCommand(options, args) {
512
643
  prompt,
513
644
  follow: parsed.booleans.has("--follow"),
514
645
  includeTools: parsed.booleans.has("--tools"),
515
- ...worktree ? { worktree } : { newWorktree, source: parseWorktreeSource(sourceValue) }
646
+ ...parentSessionId ? { parentSessionId } : {},
647
+ ...worktree ? { worktree } : { newWorktree, source: parseWorktreeSource(sourceValue), workingTree: parseWorkingTreeMode(workingTreeValue) }
516
648
  };
517
649
  }
518
650
  rejectSessionRunGlobalScopes(options, false);
@@ -538,6 +670,16 @@ function parseSessionRunCommand(options, args) {
538
670
  }
539
671
  throw new Error(`Unknown session command: ${subcommand}`);
540
672
  }
673
+ function parseSessionProcessLogArgs(args) {
674
+ const parsed = parseCommandOperands(args, /* @__PURE__ */ new Set(["--tail", "--grep"]), /* @__PURE__ */ new Map(), "session process-log");
675
+ const runId = requireValue(parsed.positionals[0], "Process run id is required for `session process-log`");
676
+ if (parsed.positionals.length > 1) throw new Error(`Unexpected session process-log argument: ${parsed.positionals[1]}`);
677
+ const tailValue = parsed.values.get("--tail");
678
+ const tail = tailValue === void 0 ? void 0 : Number(tailValue);
679
+ if (tail !== void 0 && (!Number.isInteger(tail) || tail < 1)) throw new Error("--tail must be a positive integer");
680
+ const grep = parsed.values.get("--grep");
681
+ return { runId, ...tail !== void 0 ? { tail } : {}, ...grep !== void 0 ? { grep } : {} };
682
+ }
541
683
  function assertAuthLoginArgs(args) {
542
684
  const valueFlags = /* @__PURE__ */ new Set(["--device-name", "--worker-label"]);
543
685
  const booleanFlags = /* @__PURE__ */ new Set(["--no-open", "--no-qr"]);
@@ -1364,10 +1506,34 @@ function renderWorkspaceStatus(status) {
1364
1506
  `Workspace HEAD: ${status.head ?? "(not initialized)"}`,
1365
1507
  `Latest sync: ${status.latestSync ? `${status.latestSync.outcome} on ${status.latestSync.workerLabel}` : "(none)"}`,
1366
1508
  `Remediation: ${status.incident ? `${status.incident.kind} ${status.incident.status} (${status.incident.id}) on ${status.incident.originWorkerLabel}` : "none"}`,
1367
- ...status.workers.map(
1368
- (worker) => `Worker ${worker.workerLabel}: ${worker.configured ? "configured" : "configuring"}; head ${worker.localHead ?? "(none)"}; pending checkouts ${worker.pendingCheckouts.length}`
1369
- )
1509
+ ...status.workers.flatMap((worker) => [
1510
+ `Worker ${worker.workerLabel}: ${worker.configured ? "configured" : "configuring"}; head ${worker.localHead ?? "(none)"}; pending checkouts ${worker.pendingCheckouts.length}`,
1511
+ ...renderMissingCheckouts(worker.workerLabel, worker.missingCheckouts ?? [])
1512
+ ])
1370
1513
  ];
1514
+ if (status.latestSync?.missingCheckouts?.length && !status.workers.some((worker) => worker.missingCheckouts?.length)) {
1515
+ lines.push(...renderMissingCheckouts(status.latestSync.workerLabel, status.latestSync.missingCheckouts));
1516
+ }
1517
+ return `${lines.join("\n")}
1518
+ `;
1519
+ }
1520
+ const CHECKOUT_RECOVERY_HINT = "delete the branch with `r5dctl -p <project> delete branch <branch> --confirm`, or reconnect the worker to recreate the checkout";
1521
+ function renderMissingCheckouts(workerLabel, missingCheckouts) {
1522
+ if (missingCheckouts.length === 0) return [];
1523
+ return [
1524
+ `Missing checkouts on ${workerLabel}: ${missingCheckouts.map((checkout) => `${checkout.projectId}/${checkout.branchName} (${checkout.reason})`).join(", ")}`,
1525
+ ` Workspace sync skips these branches; ${CHECKOUT_RECOVERY_HINT}.`
1526
+ ];
1527
+ }
1528
+ function renderBranchList(branches) {
1529
+ if (branches.length === 0) return "No branches found.\n";
1530
+ const lines = branches.map((branch) => {
1531
+ const checkout = branch.checkoutState ? ` checkout=${branch.checkoutState}${branch.checkoutStateWorkerLabel ? ` on ${branch.checkoutStateWorkerLabel}` : ""}` : "";
1532
+ return `${branch.branchName} sessions=${branch.sessionCount}${checkout}`;
1533
+ });
1534
+ if (branches.some((branch) => branch.checkoutState)) {
1535
+ lines.push(`Workspace sync skips a branch whose checkout is missing or not a worktree; ${CHECKOUT_RECOVERY_HINT}.`);
1536
+ }
1371
1537
  return `${lines.join("\n")}
1372
1538
  `;
1373
1539
  }
@@ -1595,6 +1761,32 @@ function renderProcessList(processes, nowMs = Date.now()) {
1595
1761
  function renderProcessHistory(processes, nowMs = Date.now()) {
1596
1762
  return processes.length === 0 ? "No process history.\n" : renderProcessList(processes, nowMs);
1597
1763
  }
1764
+ function renderProcessLog(log) {
1765
+ const sections = [];
1766
+ for (const [name, stream] of [
1767
+ ["stdout", log.stdout],
1768
+ ["stderr", log.stderr]
1769
+ ]) {
1770
+ if (stream.text.length === 0) continue;
1771
+ sections.push(`==> ${name} <==
1772
+ ${stream.text.endsWith("\n") ? stream.text : `${stream.text}
1773
+ `}`);
1774
+ }
1775
+ const selection = [
1776
+ ...log.grep !== void 0 ? [`lines matching /${log.grep}/`] : [],
1777
+ ...log.tailLines !== void 0 ? [`last ${log.tailLines} lines`] : []
1778
+ ];
1779
+ const cut = [log.stdout.truncated ? "stdout" : void 0, log.stderr.truncated ? "stderr" : void 0].filter(Boolean);
1780
+ const summary = [
1781
+ `Run ${log.runId} (${log.status}${log.exitCode !== void 0 ? `, exit ${log.exitCode}` : ""}): `,
1782
+ `stdout ${log.stdout.totalBytes} bytes, stderr ${log.stderr.totalBytes} bytes`,
1783
+ selection.length > 0 ? `; showing ${selection.join(", ")}` : "",
1784
+ cut.length > 0 ? `; ${cut.join(" and ")} cut to ${log.maxBytesPerStream} bytes, narrow with --tail or --grep` : "",
1785
+ sections.length === 0 ? "; no output to show" : ""
1786
+ ].join("");
1787
+ return { text: sections.join(""), summary: `${summary}
1788
+ ` };
1789
+ }
1598
1790
  function renderProcessInspection(process2) {
1599
1791
  return [
1600
1792
  `Run: ${process2.runId}`,
@@ -1748,13 +1940,22 @@ function formatStatusValue(value) {
1748
1940
  }
1749
1941
  return value === void 0 || value === null ? void 0 : String(value);
1750
1942
  }
1943
+ function pushWorktreeOriginLines(lines, session) {
1944
+ const source = responseString(session, "sourceBranch");
1945
+ const mode = responseString(session, "workingTree");
1946
+ if (!source || !mode) return;
1947
+ lines.push(`Source: ${source}`);
1948
+ const detail = mode === "carry" ? `carried ${source}'s uncommitted changes at launch` : mode === "clean" ? `clean checkout of ${source}'s commit at launch` : void 0;
1949
+ lines.push(`Working tree: ${mode}${detail ? ` (${detail})` : ""}`);
1950
+ }
1751
1951
  function renderSessionRunStart(session) {
1752
1952
  const lines = [
1753
1953
  `Session: ${responseString(session, "sessionId") ?? "(unknown)"}`,
1754
1954
  `Status: ${formatStatusValue(session.status) ?? "queued"}`,
1755
- `Worktree: ${responseString(session, "branchName") ?? "(provisioning)"}`,
1756
- `Worker: ${responseString(session, "worker") ?? "(unassigned)"}`
1955
+ `Worktree: ${responseString(session, "branchName") ?? "(provisioning)"}`
1757
1956
  ];
1957
+ pushWorktreeOriginLines(lines, session);
1958
+ lines.push(`Worker: ${responseString(session, "worker") ?? "(unassigned)"}`);
1758
1959
  const sessionUrl = responseString(session, "sessionUrl");
1759
1960
  if (sessionUrl) lines.push(`URL: ${sessionUrl}`);
1760
1961
  const sessionId = responseString(session, "sessionId");
@@ -1762,29 +1963,50 @@ function renderSessionRunStart(session) {
1762
1963
  return `${lines.join("\n")}
1763
1964
  `;
1764
1965
  }
1966
+ function formatBranchStateHint(branch) {
1967
+ const target = branch ?? "<branch>";
1968
+ const commands = [
1969
+ `git merge-base <your-branch> ${target}`,
1970
+ `git log --oneline <your-branch>..${target}`,
1971
+ "git status in the branch folder"
1972
+ ];
1973
+ return `inspect the worktree with git (${commands.join("; ")})`;
1974
+ }
1975
+ function formatQueuedAt(value) {
1976
+ const time = Date.parse(value);
1977
+ return Number.isNaN(time) ? value : new Date(time).toISOString().replace(/\.\d{3}Z$/, "Z");
1978
+ }
1979
+ function formatProvisioningQueueLine(session) {
1980
+ const position = session.provisioningQueuePosition;
1981
+ const queuedAt = responseString(session, "provisioningQueuedAt");
1982
+ if (typeof position !== "number" || !Number.isInteger(position) || position < 0 || !queuedAt) return void 0;
1983
+ const since = formatQueuedAt(queuedAt);
1984
+ if (position === 0) return `Provisioning: worker started the branch operation at ${since}`;
1985
+ return `Provisioning: queued behind ${position} worker operation${position === 1 ? "" : "s"} since ${since}`;
1986
+ }
1765
1987
  function renderSessionRunStatus(session) {
1766
1988
  const status = formatStatusValue(session.status) ?? "unknown";
1767
1989
  const lines = [`Session: ${responseString(session, "sessionId") ?? "(unknown)"}`, `Status: ${status}`];
1768
1990
  const runStatus = formatStatusValue(session.runStatus);
1769
1991
  if (runStatus) lines.push(`Run: ${runStatus}`);
1992
+ const provisioningQueue = formatProvisioningQueueLine(session);
1993
+ if (provisioningQueue) lines.push(provisioningQueue);
1770
1994
  const promptDisposition = responseString(session, "promptDisposition");
1771
1995
  if (promptDisposition) lines.push(`Prompt: ${promptDisposition}`);
1772
1996
  const branch = responseString(session, "branchName");
1773
1997
  if (branch) lines.push(`Worktree: ${branch}`);
1998
+ pushWorktreeOriginLines(lines, session);
1774
1999
  for (const [label, key] of [
1775
- ["Workspace head", "workspaceHead"],
1776
- ["Baseline", "baselineCommit"],
1777
- ["Head", "headCommit"],
1778
2000
  ["Worker", "worker"],
2001
+ ["Parent session", "parentSessionId"],
1779
2002
  ["Session", "sessionUrl"]
1780
2003
  ]) {
1781
2004
  const value = responseString(session, key);
1782
2005
  if (value) lines.push(`${label}: ${value}`);
1783
2006
  }
2007
+ lines.push(`Branch state: ${formatBranchStateHint(branch)}`);
1784
2008
  const error = responseString(session, "error");
1785
2009
  if (error) lines.push(`Error: ${error}`);
1786
- const headCommitError = responseString(session, "headCommitError");
1787
- if (headCommitError) lines.push(`Head error: ${headCommitError}`);
1788
2010
  const diffSummary = responseString(session, "diffSummary");
1789
2011
  if (diffSummary) lines.push("", diffSummary);
1790
2012
  const summary = responseString(session, "summary");
@@ -1802,13 +2024,14 @@ async function dispatchSessionRunCommand(client, command) {
1802
2024
  prompt: command.prompt,
1803
2025
  worker: command.worker,
1804
2026
  model: command.model,
1805
- requestId: randomUUID()
2027
+ requestId: randomUUID(),
2028
+ ...command.parentSessionId ? { parentSessionId: command.parentSessionId } : {}
1806
2029
  };
1807
2030
  let input;
1808
2031
  if (command.worktree !== void 0) {
1809
2032
  input = { ...commonInput, worktree: command.worktree };
1810
2033
  } else if (command.newWorktree !== void 0 && command.source !== void 0) {
1811
- input = { ...commonInput, newWorktree: command.newWorktree, source: command.source };
2034
+ input = { ...commonInput, newWorktree: command.newWorktree, source: command.source, workingTree: command.workingTree };
1812
2035
  } else {
1813
2036
  throw new Error("Session start requires an existing or new worktree");
1814
2037
  }
@@ -2448,6 +2671,17 @@ async function executeR5dctlCommand(client, json, args) {
2448
2671
  `);
2449
2672
  return;
2450
2673
  }
2674
+ if (first === "session" && second === "process-log") {
2675
+ const selection = parseSessionProcessLogArgs(args.slice(2));
2676
+ const log = await client.processes.log(selection.runId, {
2677
+ ...selection.tail !== void 0 ? { tail: selection.tail } : {},
2678
+ ...selection.grep !== void 0 ? { grep: selection.grep } : {}
2679
+ });
2680
+ const rendered = renderProcessLog(log);
2681
+ write(log, rendered.text);
2682
+ if (!json) process.stderr.write(rendered.summary);
2683
+ return;
2684
+ }
2451
2685
  if (first === "sessions" && second === "recent") {
2452
2686
  const sessions = await client.sessions.recent(parseRecentSessionArgs(args.slice(2)));
2453
2687
  write(sessions, renderRecentSessionList(sessions));
@@ -2477,11 +2711,7 @@ async function executeR5dctlCommand(client, json, args) {
2477
2711
  if (first === "projects" && second === "branches" && third === "list" || first === "get" && second === "branches") {
2478
2712
  const projectRef = requireValue(first === "get" ? args[2] : args[3], "Missing project reference");
2479
2713
  const branches = await client.projects.branches.list(projectRef);
2480
- write(
2481
- branches,
2482
- branches.length === 0 ? "No branches found.\n" : `${branches.map((branch) => `${branch.branchName} sessions=${branch.sessionCount}`).join("\n")}
2483
- `
2484
- );
2714
+ write(branches, renderBranchList(branches));
2485
2715
  return;
2486
2716
  }
2487
2717
  if (first === "projects" && second === "branches" && third === "describe" || first === "describe" && second === "branch") {
@@ -2496,6 +2726,40 @@ Sessions: ${result.sessions.length}
2496
2726
  `);
2497
2727
  return;
2498
2728
  }
2729
+ if (first === "projects" && second === "branches" && third === "create" || first === "create" && second === "branch") {
2730
+ const offset = first === "create" ? 2 : 3;
2731
+ const projectRef = requireValue(args[offset], "Missing project reference");
2732
+ const branchName = requireValue(args[offset + 1], "Missing branch name");
2733
+ const operation = await createBranchThroughPlatform(client, projectRef, {
2734
+ branchName,
2735
+ ...parseBranchCreateArgs(args.slice(offset + 2))
2736
+ });
2737
+ write(operation, renderBranchOperation(operation, projectRef));
2738
+ return;
2739
+ }
2740
+ if (first === "projects" && second === "branches" && third === "operation" || first === "describe" && second === "branch-operation") {
2741
+ const offset = first === "describe" ? 2 : 3;
2742
+ const projectRef = requireValue(args[offset], "Missing project reference");
2743
+ const operationId = requireValue(args[offset + 1], "Missing operation id");
2744
+ const operation = await requireServerRouteSupport(
2745
+ "describe branch-operation",
2746
+ () => client.projects.branches.describeOperation(projectRef, operationId)
2747
+ );
2748
+ write(operation, renderBranchOperation(operation, projectRef));
2749
+ return;
2750
+ }
2751
+ if (first === "projects" && second === "branches" && third === "delete" || first === "delete" && second === "branch") {
2752
+ const offset = first === "delete" ? 2 : 3;
2753
+ const projectRef = requireValue(args[offset], "Missing project reference");
2754
+ const branchName = requireValue(args[offset + 1], "Missing branch name");
2755
+ const deletion = await deleteBranchThroughPlatform(client, projectRef, branchName, parseBranchDeleteArgs(args.slice(offset + 2)));
2756
+ if (deletion.kind === "preview") {
2757
+ write({ ...deletion.branch, confirmed: false }, renderBranchDeletionPreview(deletion.branch));
2758
+ throw new Error(branchDeletionConfirmationMessage(deletion.branch));
2759
+ }
2760
+ write(deletion.result, renderBranchDeletion(deletion.result));
2761
+ return;
2762
+ }
2499
2763
  if (first === "get" && second === "envs") {
2500
2764
  const projectRef = requireValue(args[2], "Missing project reference");
2501
2765
  const options = parseGetEnvsArgs(args.slice(3));
@@ -2912,6 +3176,19 @@ function resolveCommandExecution(options, rest) {
2912
3176
  pluginArgs: ["describe", "branch", options.project, branch]
2913
3177
  };
2914
3178
  }
3179
+ if (target === "branch-operation") {
3180
+ if (!options.project) {
3181
+ throw new Error("--project/-p is required for `describe branch-operation`");
3182
+ }
3183
+ const operationId = requireValue(commandArgs[1], "Operation id is required for `describe branch-operation`");
3184
+ if (commandArgs.length > 2) {
3185
+ throw new Error(`Unexpected describe branch-operation argument: ${commandArgs[2]}`);
3186
+ }
3187
+ return {
3188
+ kind: "plugin",
3189
+ pluginArgs: ["describe", "branch-operation", options.project, operationId]
3190
+ };
3191
+ }
2915
3192
  if (target === "session") {
2916
3193
  const sessionId = commandArgs[1] ?? options.session;
2917
3194
  if (!sessionId) {
@@ -2944,6 +3221,23 @@ function resolveCommandExecution(options, rest) {
2944
3221
  pluginArgs: ["create", "session", projectRef, branch, ...flagArgs]
2945
3222
  };
2946
3223
  }
3224
+ if (target === "branch") {
3225
+ if (!options.project) {
3226
+ throw new Error("--project/-p is required for `create branch`");
3227
+ }
3228
+ const rawArgs = commandArgs.slice(1);
3229
+ const positionalBranch = rawArgs[0] && !rawArgs[0].startsWith("-") ? rawArgs[0] : void 0;
3230
+ const branch = positionalBranch ?? options.branch;
3231
+ if (!branch) {
3232
+ throw new Error("Branch name is required for `create branch`");
3233
+ }
3234
+ const flagArgs = positionalBranch ? rawArgs.slice(1) : rawArgs;
3235
+ parseBranchCreateArgs(flagArgs);
3236
+ return {
3237
+ kind: "plugin",
3238
+ pluginArgs: ["create", "branch", options.project, branch, ...flagArgs]
3239
+ };
3240
+ }
2947
3241
  throw new Error("Unknown create command");
2948
3242
  }
2949
3243
  if (command === "update") {
@@ -2994,6 +3288,23 @@ function resolveCommandExecution(options, rest) {
2994
3288
  pluginArgs: ["delete", "session", sessionId]
2995
3289
  };
2996
3290
  }
3291
+ if (target === "branch") {
3292
+ if (!options.project) {
3293
+ throw new Error("--project/-p is required for `delete branch`");
3294
+ }
3295
+ const rawArgs = commandArgs.slice(1);
3296
+ const positionalBranch = rawArgs[0] && !rawArgs[0].startsWith("-") ? rawArgs[0] : void 0;
3297
+ const branch = positionalBranch ?? options.branch;
3298
+ if (!branch) {
3299
+ throw new Error("Branch name is required for `delete branch`");
3300
+ }
3301
+ const flagArgs = positionalBranch ? rawArgs.slice(1) : rawArgs;
3302
+ parseBranchDeleteArgs(flagArgs);
3303
+ return {
3304
+ kind: "plugin",
3305
+ pluginArgs: ["delete", "branch", options.project, branch, ...flagArgs]
3306
+ };
3307
+ }
2997
3308
  throw new Error("Unknown delete command");
2998
3309
  }
2999
3310
  if (command === "conversation") {
@@ -3055,6 +3366,20 @@ function resolveCommandExecution(options, rest) {
3055
3366
  };
3056
3367
  }
3057
3368
  if (command === "session") {
3369
+ if (commandArgs[0] === "process-log") {
3370
+ rejectSessionRunGlobalScopes(options, false);
3371
+ const selection = parseSessionProcessLogArgs(commandArgs.slice(1));
3372
+ return {
3373
+ kind: "plugin",
3374
+ pluginArgs: [
3375
+ "session",
3376
+ "process-log",
3377
+ selection.runId,
3378
+ ...selection.tail !== void 0 ? [`--tail=${selection.tail}`] : [],
3379
+ ...selection.grep !== void 0 ? [`--grep=${selection.grep}`] : []
3380
+ ]
3381
+ };
3382
+ }
3058
3383
  return { kind: "session-run", command: parseSessionRunCommand(options, commandArgs) };
3059
3384
  }
3060
3385
  if (command === "answer-questions") {
@@ -3206,7 +3531,10 @@ async function main(argv = process.argv.slice(2)) {
3206
3531
  }
3207
3532
  export {
3208
3533
  advanceTransientDevicePollBackoff,
3534
+ branchDeletionConfirmationMessage,
3209
3535
  collectK8sUsage,
3536
+ createBranchThroughPlatform,
3537
+ deleteBranchThroughPlatform,
3210
3538
  dispatchSessionRunCommand,
3211
3539
  followR5dctlSession,
3212
3540
  formatK8sCpu,
@@ -3218,10 +3546,13 @@ export {
3218
3546
  getR5dctlVersion,
3219
3547
  getTransientDevicePollDelay,
3220
3548
  handleAuthLogin,
3549
+ isUnsupportedServerRoute,
3221
3550
  main,
3222
3551
  parseAnswerEnvRequestCommandArgs,
3223
3552
  parseAnswerFlags,
3224
3553
  parseAnswerQuestionsCommandArgs,
3554
+ parseBranchCreateArgs,
3555
+ parseBranchDeleteArgs,
3225
3556
  parseConversationForkArgs,
3226
3557
  parseConversationInspectWorkArgs,
3227
3558
  parseConversationOverviewArgs,
@@ -3234,10 +3565,15 @@ export {
3234
3565
  parsePromptArgs,
3235
3566
  parsePsHistoryArgs,
3236
3567
  parsePsListArgs,
3568
+ parseSessionProcessLogArgs,
3237
3569
  parseSessionRunCommand,
3238
3570
  parseSetEnvArgs,
3239
3571
  parseShellArgs,
3240
3572
  readDotenvFile,
3573
+ renderBranchDeletion,
3574
+ renderBranchDeletionPreview,
3575
+ renderBranchList,
3576
+ renderBranchOperation,
3241
3577
  renderConversationForkResponse,
3242
3578
  renderConversationHeadResponse,
3243
3579
  renderConversationNodeResponse,
@@ -3250,6 +3586,7 @@ export {
3250
3586
  renderProcessHistory,
3251
3587
  renderProcessInspection,
3252
3588
  renderProcessList,
3589
+ renderProcessLog,
3253
3590
  renderSessionRunStart,
3254
3591
  renderSessionRunStatus,
3255
3592
  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": "module"
5
5
  }