feat(cli): add 'ccam lanes shell' to attach a real terminal to a lane's tmux session

This commit is contained in:
2026-08-12 14:15:37 +07:00
parent b951f64321
commit 6709f9a192
+33
View File
@@ -2300,6 +2300,31 @@ async function cmdLanesPipeline(args) {
} }
} }
/**
* Attach a real terminal to the exact tmux session the dashboard uses for
* this lane's run. Creates it if it doesn't exist yet (`tmux new-session -A`
* is create-or-attach, the same idempotent semantics as clicking Start on
* the dashboard). The user types `claude` themselves inside — this command's
* only job is landing them in the right named session.
*/
async function cmdLanesShell(args) {
const resolved = await resolveLaneArg(args);
if (!resolved) return;
const { lane } = await get(`/api/lanes/${resolved.laneId}`);
const sessionName = `ccam-lane-${lane.id}`;
const child = spawn("tmux", ["new-session", "-A", "-s", sessionName, "-c", lane.cwd], {
stdio: "inherit",
});
await new Promise((resolve) => {
child.on("close", resolve);
child.on("error", (err) => {
console.error(c.red(`${err?.message || err}`));
process.exitCode = 1;
resolve();
});
});
}
async function cmdFeatureShow(args) { async function cmdFeatureShow(args) {
const slug = args.find((arg) => !arg.startsWith("--")); const slug = args.find((arg) => !arg.startsWith("--"));
if (!slug) { if (!slug) {
@@ -2484,6 +2509,11 @@ const COMMAND_GROUPS = [
"[<template-id>] [<id>]", "[<template-id>] [<id>]",
"Show, or switch, which pipeline template a lane renders against", "Show, or switch, which pipeline template a lane renders against",
], ],
[
"lanes shell",
"[<id>]",
"Attach a real terminal to the exact tmux session the dashboard uses for a lane's run",
],
[ [
"lanes reset|remove|purge", "lanes reset|remove|purge",
"<id> [--force] [--keep-db] --yes", "<id> [--force] [--keep-db] --yes",
@@ -3362,6 +3392,9 @@ async function runCommand(argv) {
if (rest[0] === "pipeline") { if (rest[0] === "pipeline") {
return cmdLanesPipeline(rest.slice(1)); return cmdLanesPipeline(rest.slice(1));
} }
if (rest[0] === "shell") {
return cmdLanesShell(rest.slice(1));
}
if (rest[0] === "gc") { if (rest[0] === "gc") {
return cmdLanesGc(rest.slice(1)); return cmdLanesGc(rest.slice(1));
} }