/** * `omp-conductor console` — provision the operator console's workspace and * print the pane command that launches it. * * The console is the second session of the two-session split: the tick brain * keeps its pane and never receives an operator turn, and the console owns the * Telegram conversation. This verb materialises the console's side of that — * its own cwd, the rendered floor, the `AGENTS.md` link that makes an `omp` * started there load the floor — and then says exactly how to launch it. * * Idempotent by construction: the plan reports `keep` for a current brief and a * correct link, so a re-run rewrites nothing. That matters because both this * verb and `setup host` provision the same workspace through the same * `planConsole`/`writeConsole` pair, and an operator will run whichever they * remember. * * It launches nothing. `recover.sh` owns pane restoration for the whole fleet * (exact-identity rules, one companion per role), so a verb that spawned its * own pane here would be a second pane nobody reconciles. */ import { findProject, loadConfig } from "../config.ts"; import { consolePaneCommand, planConsole, writeConsole, type ConsolePlan, type ConsoleWrite } from "../setup.ts"; import type { CommandContext } from "./context.ts"; /** * The whole printed report, as lines, from facts a caller already holds. * * Pure so the byte-exact pane command is assertable without a config, a state * directory or a captured stdout — the pane command is a contract shared with * `recover.sh`, and a test that could only check "contains omp" would not hold * it. * * `otherProjects` is the DM-force-claim decision, expressed as its evidence * rather than as a boolean: one Telegram state dir may have exactly one DM * owner, so the claim is this console's only when no other project is * configured — and when it is not, the operator is told which projects took it * away. */ export function consoleReport( plan: ConsolePlan, written: ConsoleWrite, otherProjects: readonly string[], ): string[] { const dmOwner = otherProjects.length === 0; const pane = consolePaneCommand({ cwd: plan.cwd, dmOwner }); return [ `console cwd ${plan.cwd}`, `console brief ${plan.brief.action} ${plan.brief.path}`, plan.link.action === "skip" ? `console link ${plan.link.path} — ${plan.link.skippedReason}` : `console link ${plan.link.action} ${plan.link.path} -> ${plan.link.target}`, `pane command ${pane}`, dmOwner ? "dm owner yes — this host configures exactly one project, so the console force-claims the Telegram DM owner" : `dm owner no — this host also configures ${otherProjects.join(", ")}, and one Telegram state dir has exactly one DM owner; inbound reaches this console by forum-topic routing instead`, `recovery recover.sh restores this pane with the fleet; by hand, run: ${pane}`, ...(written.wrote.length === 0 ? [] : [`wrote ${written.wrote.join(", ")}`]), ...written.warnings.map((w) => `warning ${w}`), ]; } export function consoleCommand(ctx: CommandContext): void { const cfg = loadConfig(); const project = findProject(cfg, ctx.projectFlag); const plan = planConsole(project); const written = writeConsole(plan); const others = cfg.projects.filter((p) => p.name !== project.name).map((p) => p.name); process.stdout.write(`${consoleReport(plan, written, others).join("\n")}\n`); }