Ctrl K
ComunidadGuías…

bin/run.ts

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
/**
 * Composition root for the `cotal` operator CLI, published as `cotal-ai`. Importing an
 * implementation self-registers its commands into the shared registry — base mesh commands
 * plus `spawn`/`console` (@cotal-ai/cli) and the manager's control plane + daemon runners
 * (@cotal-ai/manager). The root just picks which surfaces to pull in; `runCli` resolves
 * whatever registered. A new surface (another connector, a control client …) is one more import line.
 *
 * A daemon entry is the exception: it runs one command for the life of its process, so it imports
 * only the surfaces that command needs (see the providers below), never the whole CLI.
 *
 * Reached only via ./cotal.ts, which runs the Node-version preflight first: everything imported
 * here requires Node >= 22 (the UI stack and the bundled nats-server optional dep), so this module
 * must never be the executable entry — importing it on an old Node crashes at parse time.
 */
import { registry, type ExtensionRef } from "@cotal-ai/core";
import { handOverStartupInterrupt, releaseStartupInterrupt } from "./startup-interrupt.js";

// Each surface self-registers when imported. The whole CLI imports them one after another in this
// order, which is the order the root's static imports evaluated them in; display order is still a
// non-goal, since `help` ranks its groups explicitly (GROUP_ORDER in @cotal-ai/cli).
const surfaces = {
  cli: () => import("@cotal-ai/cli"), // the base surface incl. spawn (foreground + --detach) / stop / ps / attach
  manager: () => import("@cotal-ai/manager"), // `supervise` — the agent-supervisor daemon
  delivery: () => import("@cotal-ai/delivery"), // `deliver` — the server-side Plane-3 delivery daemon
  runtime: () => import("@cotal-ai/runtime"), // `run` — the workflow-run operator surface (start/resume/ps/journal/answer)
  auth: () => import("@cotal-ai/auth"), // login / logout — per-user IdP sessions (device-code sign-in)
  docs: () => import("./docs.js"), // `docs`; the docs bundle loads only when `docs` runs
  daemon: () => import("./daemon/command.js"), // `daemon`: the local machine daemon's authenticated control endpoint (Linux)
  // The `ag-ui.frame` part renderer, so `cotal console` and `cotal join` draw an event frame instead
  // of `[unrenderable part kind "ag-ui.frame"]`.
  //
  // IMPORTED HERE BECAUSE NOTHING ELSE IN THIS PROCESS WOULD. The four agent connectors are removable
  // ext plugins materialized lazily by `runCli`, and no `implementations/*` package imports
  // connector-core at runtime — measured, not assumed. So a renderer that only registered inside the
  // connector would be absent from every process that RENDERS, which is the one place it is needed.
  // The seam is worth nothing if the provider never loads at the display site.
  //
  // The narrow subpath rather than the package root, deliberately: the root pulls the MCP server
  // runtime and its zod copy into CLI startup, and a renderer needs none of it.
  aguiRender: () => import("@cotal-ai/connector-core/agui-render"),
};
type Surface = keyof typeof surfaces;
const importing = (...names: Surface[]) => async () => { for (const name of names) await surfaces[name](); };
const commands = (...names: string[]): ExtensionRef[] => names.map((name) => ({ kind: "command", name }));

// The daemon entries. Each imports its own surface and the surfaces whose registrations its run
// resolves: the auth provider a user-auth mesh dials through (@cotal-ai/auth), and the workflow run
// host a manager hosts runs with (@cotal-ai/runtime). Nothing else in the CLI is imported, and the
// command's flags, refusals and exit are `runCli`'s as for any other command.
registry.provide({ provides: commands("supervise", "__daemon-manager", "__service-manager"), load: importing("manager", "auth", "runtime") });
registry.provide({ provides: commands("__machine-manager"), load: async () => {
  await importing("manager", "auth", "runtime")();
  await import("./daemon/manager-entry.js");
} });
registry.provide({ provides: commands("deliver", "__daemon-deliver", "__service-delivery", "feedback-intake", "__service-feedback-intake"), load: importing("delivery", "auth") });
registry.provide({ provides: commands("auth-service", "__daemon-auth-service", "__service-auth"), load: importing("auth") });
registry.provide({ provides: [{ kind: "registry-service", name: "builtin" }], load: () => import("@cotal-ai/registry") });
registry.provide({ provides: commands("__service-unit"), load: () => import("@cotal-ai/cli/service-unit") });
registry.provide({ provides: commands("daemon"), load: importing("daemon", "auth") });

// The dispatch module reads the command word as `runCli` will, and loads no command. A word a
// provider declares imports that provider's surfaces; any other word imports them all.
const { commandWord, runCli, stdoutError } = await import("@cotal-ai/cli/dispatch");
const argv = process.argv.slice(2);
const { readFileSync } = await import("node:fs");
const currentVersion = JSON.parse(readFileSync(new URL("../package.json", import.meta.url), "utf8")).version as string;
(await import("./update-notice.js")).installUpdateNotice(argv, currentVersion);
const word = commandWord(argv);
if (word !== undefined && registry.provides("command", word)) await registry.load("command", word);
else await importing(...(Object.keys(surfaces) as Surface[]))();
if (word === "update") await import("./daemon/update-entry.js");

// A CLI must exit quietly when its stdout is closed early — piped to `head`, a pager that quits,
// or a shell's process substitution (`source <(cotal completion script bash)`). Node otherwise turns the
// closed-pipe write into a fatal unhandled 'error' event with a stack trace. Mirror SIGPIPE: exit
// 0. A command that must close something first, such as a chat session, owns stdout's errors while
// it runs, and this listener stands down for it. Registered before any command can write.
process.stdout.on("error", stdoutError);

// The four agent connectors (claude/opencode/hermes/pi) are NOT imported here: they are removable,
// install-seeded `cotal ext` plugins loaded through the manifest, exactly like a third-party
// connector (and like the `@cotal-ai/orca` runtime already is). `runCli` seeds them on first run and
// materializes each lazily when a command resolves it; the default agent is `COTAL_DEFAULT_AGENT`
// (else claude), resolved manager-side. The old `"cotal"` default-agent alias is gone with them.
//
// Bare `cotal` prints the entry screen; explicit `cotal setup` runs guided setup. The published binary is
// the ONE composition root that loads operator-installed extensions (`cotal ext add …`) — commands,
// runtimes, connectors, and local process components all self-register from those packages. Library
// roots keep the explicit-import model.
// The bundled app uses only stock daemon operations, never operator extension discovery or seeding.
const desktopBridge = argv.length === 2 && argv[0] === "daemon" && argv[1] === "desktop";
await runCli(registry, argv, { extensions: !desktopBridge, startupNotice: { release: releaseStartupInterrupt, handOver: handOverStartupInterrupt } });