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) {
const slug = args.find((arg) => !arg.startsWith("--"));
if (!slug) {
@@ -2484,6 +2509,11 @@ const COMMAND_GROUPS = [
"[<template-id>] [<id>]",
"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",
"<id> [--force] [--keep-db] --yes",
@@ -3362,6 +3392,9 @@ async function runCommand(argv) {
if (rest[0] === "pipeline") {
return cmdLanesPipeline(rest.slice(1));
}
if (rest[0] === "shell") {
return cmdLanesShell(rest.slice(1));
}
if (rest[0] === "gc") {
return cmdLanesGc(rest.slice(1));
}