Files
Constant/constant/cli.py
T
2026-04-06 07:20:14 +02:00

944 lines
36 KiB
Python

from __future__ import annotations
import argparse
import json
import os
import subprocess
import sys
import time
from pathlib import Path
from typing import Any
from . import __version__
from .capabilities import (
agent_for_cli,
list_agents,
list_skills,
recommended_skill_stack,
resolve_skill_and_agent,
skill_catalog,
skill_by_id,
)
from .cockpit import capture_pane, cockpit_doctor, focus_machine, restart_pane, runtime_status, send_to_pane
from .daemon import daemon_status, request as daemon_request, serve_foreground, start_background, stop_background
from .executors import bridge_sync, execute_step, fleet_check
from .memory import (
enroll_workspace,
instruction_skill_sources,
list_decisions,
memory_status,
persona_markdown,
rebuild_workspace_memory,
search_memory,
summarize_mission,
sync_qdrant,
)
from .paths import cache_root, repo_root, scripts_dir
from .state import (
append_event,
create_mission,
first_active_step,
fleet_machine,
load_fleet_config,
load_mission,
load_models_config,
list_missions,
mission_events_file,
save_mission,
write_artifact,
)
from .tui import run_tui
def _print(data: Any, as_json: bool = False) -> None:
if as_json:
print(json.dumps(data, indent=2, sort_keys=True))
elif isinstance(data, str):
print(data)
else:
print(json.dumps(data, indent=2, sort_keys=True))
def _command_exists(binary: str) -> bool:
for path_dir in os.environ.get("PATH", "").split(":"):
candidate = Path(path_dir) / binary
if candidate.exists() and os.access(candidate, os.X_OK):
return True
return False
def _read_wrapper_stamp(path: Path) -> dict[str, Any] | None:
try:
raw = path.read_text(encoding="utf-8").strip()
except OSError:
return None
if not raw:
return None
parts = raw.split()
if len(parts) < 2:
return {"raw": raw}
signature, epoch_text = parts[0], parts[1]
payload: dict[str, Any] = {"signature": signature, "epoch": epoch_text}
try:
payload["timestamp"] = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(int(epoch_text)))
except ValueError:
pass
return payload
def _read_wrapper_mode(path: Path) -> dict[str, Any] | None:
try:
raw = path.read_text(encoding="utf-8").strip()
except OSError:
return None
if not raw:
return None
parts = raw.split()
if len(parts) < 3:
return {"raw": raw}
mode, reason, epoch_text = parts[0], parts[1], parts[2]
payload: dict[str, Any] = {"mode": mode, "reason": reason, "epoch": epoch_text}
try:
payload["timestamp"] = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(int(epoch_text)))
except ValueError:
pass
return payload
def _run_quick_command(command: list[str]) -> dict[str, Any]:
try:
completed = subprocess.run(
command,
capture_output=True,
text=True,
timeout=3,
check=False,
)
except Exception as exc: # noqa: BLE001
return {"ok": False, "error": str(exc), "command": command}
return {
"ok": completed.returncode == 0,
"returncode": completed.returncode,
"stdout": completed.stdout.strip(),
"stderr": completed.stderr.strip(),
"command": command,
}
def _wrapper_status() -> dict[str, Any]:
wrapper_dir = cache_root() / "wrapper"
rust_bin = repo_root() / "target" / "debug" / "constant"
payload: dict[str, Any] = {
"wrapper_dir": str(wrapper_dir),
"rust_bin": str(rust_bin),
"rust_bin_exists": rust_bin.exists(),
"forced_python": os.environ.get("CONSTANT_USE_PYTHON") == "1",
"forced_rust": os.environ.get("CONSTANT_USE_RUST") == "1",
"force_recheck": os.environ.get("CONSTANT_RUST_RECHECK") == "1",
"last_mode": _read_wrapper_mode(wrapper_dir / "last-mode"),
"probe_timeout_sec": os.environ.get("CONSTANT_RUST_PROBE_TIMEOUT_SEC", "2"),
"probe_fail_ttl_sec": os.environ.get("CONSTANT_RUST_PROBE_FAIL_TTL_SEC", "300"),
"cache": {
"rust_ok": _read_wrapper_stamp(wrapper_dir / "rust-ok"),
"rust_fail": _read_wrapper_stamp(wrapper_dir / "rust-fail"),
},
}
if rust_bin.exists():
payload["codesign_verify"] = _run_quick_command(
["codesign", "--verify", "--verbose=2", str(rust_bin)]
)
payload["spctl_assess"] = _run_quick_command(["spctl", "--assess", "-vv", str(rust_bin)])
payload["xattr_provenance"] = _run_quick_command(["xattr", "-p", "com.apple.provenance", str(rust_bin)])
payload["hint"] = (
"If spctl reports a Code Signing subsystem error from a normal terminal, allow that terminal in "
"System Settings -> Privacy & Security -> Developer Tools. Then rerun with CONSTANT_RUST_RECHECK=1 "
"to force a fresh startup probe."
)
return payload
def _warm_memory(workspace: str) -> dict[str, Any] | None:
try:
return rebuild_workspace_memory(workspace, enroll=True)
except Exception as exc: # noqa: BLE001
return {"error": str(exc), "workspace": workspace}
def _finalize_mission_memory(mission_id: str) -> dict[str, Any] | None:
try:
return summarize_mission(mission_id)
except Exception: # noqa: BLE001
return None
def _ensure_planned(mission: dict[str, Any]) -> dict[str, Any]:
if mission["steps"]:
return mission
payload = daemon_request("plan", {"mission": mission})
mission["steps"] = payload["plan"]["steps"]
mission["title"] = payload["plan"]["title"]
mission["status"] = "planned"
mission["planner_summary"] = payload["plan"]["summary"]
mission["buddy_review"] = payload["buddy_review"]
save_mission(mission)
append_event(mission["mission_id"], "mission.planned", payload)
return mission
def _mission_summary(mission: dict[str, Any]) -> dict[str, Any]:
return {
"mission_id": mission["mission_id"],
"title": mission["title"],
"status": mission["status"],
"workspace": mission["workspace"],
"steps": [
{
"step_id": step["step_id"],
"status": step["status"],
"machine": step["machine"],
"backend": step["backend"],
"cli": step["cli"],
"agent": step["agent"],
"agent_role": step.get("agent_role"),
"skill": step.get("skill"),
"skill_summary": step.get("skill_summary"),
"attempt": step["attempt"],
}
for step in mission["steps"]
],
}
def cmd_doctor(args: argparse.Namespace) -> int:
status = daemon_status()
health = daemon_request("health", auto_start=False)
fleet = load_fleet_config()
models = load_models_config()
cockpit = cockpit_doctor()
report = {
"version": __version__,
"repo_root": str(repo_root()),
"cache_root": str(cache_root()),
"commands": {
"python3": _command_exists("python3"),
"tmux": _command_exists("tmux"),
"omc": _command_exists("omc"),
"claude": _command_exists("claude"),
"codex": _command_exists("codex"),
"copilot": _command_exists("copilot"),
"vibe": _command_exists("vibe"),
"constant-fleet": (scripts_dir() / "constant-fleet.sh").exists(),
"ai-bridge": (scripts_dir() / "ai-bridge.sh").exists(),
},
"daemon": status,
"models": models,
"health": health,
"memory": memory_status(),
"fleet": [{"label": entry["label"], "target": entry["target"]} for entry in fleet["machines"]],
"cockpit": cockpit,
"wrapper": _wrapper_status(),
}
_print(report, args.json)
return 0
def cmd_daemon_start(_: argparse.Namespace) -> int:
payload = start_background()
if not payload.get("running"):
payload["mode"] = "inline-fallback"
_print(payload)
return 0
def cmd_daemon_stop(_: argparse.Namespace) -> int:
_print(stop_background())
return 0
def cmd_daemon_status(args: argparse.Namespace) -> int:
payload = {"status": daemon_status(), "health": daemon_request("health", auto_start=False)}
if not payload["status"]["running"]:
payload["mode"] = "inline-fallback"
_print(payload, args.json)
return 0
def cmd_daemon_logs(_: argparse.Namespace) -> int:
from .paths import daemon_log_path
path = daemon_log_path()
if not path.exists():
print("No daemon log yet.")
return 0
print(path.read_text(encoding="utf-8"))
return 0
def cmd_models_status(args: argparse.Namespace) -> int:
payload = {"config": load_models_config(), "daemon": daemon_status(), "health": daemon_request("health", auto_start=False)}
if not payload["daemon"]["running"]:
payload["mode"] = "inline-fallback"
_print(payload, args.json)
return 0
def cmd_agents(args: argparse.Namespace) -> int:
payload = {"agents": list_agents(), "recommended_skill_stack": recommended_skill_stack()}
_print(payload, args.json)
return 0
def cmd_skills(args: argparse.Namespace) -> int:
payload = {
"skills": list_skills(include_internal=not getattr(args, "public_only", False)),
"catalog": skill_catalog(include_internal=not getattr(args, "public_only", False)),
"recommended_skill_stack": recommended_skill_stack(),
}
if getattr(args, "workspace", None):
payload["instruction_skill_sources"] = instruction_skill_sources(args.workspace)
_print(payload, args.json)
return 0
def cmd_fleet_status(args: argparse.Namespace) -> int:
payload = fleet_check()
_print(payload, args.json)
return 0 if payload["returncode"] == 0 else 1
def cmd_fleet_sync(args: argparse.Namespace) -> int:
payload = bridge_sync()
_print(payload, args.json)
return 0 if payload["returncode"] == 0 else 1
def _exec_fleet_deploy_script(mode: str, args: argparse.Namespace) -> int:
script = scripts_dir() / "constant-deploy.sh"
command = [str(script), mode]
for host in args.host or []:
command.extend(["--host", host])
if args.user:
command.extend(["--user", args.user])
if args.repo_dir:
command.extend(["--repo-dir", args.repo_dir])
if args.local_label:
command.extend(["--local-label", args.local_label])
if args.output:
command.extend(["--output", args.output])
if getattr(args, "json", False):
command.append("--json")
if getattr(args, "yes", False):
command.append("--yes")
if getattr(args, "all_reachable", False):
command.append("--all-reachable")
if getattr(args, "install", False):
command.append("--install")
if getattr(args, "no_ssh_config", False):
command.append("--no-ssh-config")
if getattr(args, "no_known_hosts", False):
command.append("--no-known-hosts")
if getattr(args, "no_arp", False):
command.append("--no-arp")
os.execv(command[0], command)
return 0
def cmd_fleet_discover(args: argparse.Namespace) -> int:
return _exec_fleet_deploy_script("scan", args)
def cmd_fleet_configure(args: argparse.Namespace) -> int:
return _exec_fleet_deploy_script("configure", args)
def cmd_fleet_deploy(args: argparse.Namespace) -> int:
return _exec_fleet_deploy_script("deploy", args)
def cmd_cockpit_open(args: argparse.Namespace) -> int:
workspace = args.workspace or os.getcwd()
command = [
str(scripts_dir() / "constant-fleet.sh"),
"--workspace",
workspace,
"--local-session",
args.local_session or "constant-fleet",
"--session",
args.session or "constant",
]
if getattr(args, "recreate", False):
command.append("--recreate")
if getattr(args, "remote_recreate", False):
command.append("--remote-recreate")
os.execv(command[0], command)
return 0
def cmd_cockpit_attach(args: argparse.Namespace) -> int:
command = [
str(scripts_dir() / "constant-fleet.sh"),
"--attach-only",
"--local-session",
args.local_session or "constant-fleet",
]
os.execv(command[0], command)
return 0
def cmd_cockpit_doctor(args: argparse.Namespace) -> int:
payload = cockpit_doctor(local_session=args.local_session or "constant-fleet", machine_session=args.session or "constant")
_print(payload, args.json)
return 0 if payload["tmux"]["available"] else 1
def cmd_cockpit_focus(args: argparse.Namespace) -> int:
payload = focus_machine(
args.machine,
args.pane,
local_session=args.local_session or "constant-fleet",
machine_session=args.session or "constant",
)
_print(payload, args.json)
return 0 if payload["returncode"] == 0 else 1
def cmd_cockpit_send(args: argparse.Namespace) -> int:
payload = send_to_pane(args.machine, args.pane, args.command, machine_session=args.session or "constant")
_print(payload, args.json)
return 0 if payload["returncode"] == 0 else 1
def cmd_cockpit_capture(args: argparse.Namespace) -> int:
payload = capture_pane(args.machine, args.pane, lines=args.lines, machine_session=args.session or "constant")
_print(payload if args.json else payload.get("stdout", ""), args.json)
return 0 if payload["returncode"] == 0 else 1
def cmd_cockpit_restart(args: argparse.Namespace) -> int:
payload = restart_pane(args.machine, args.pane, machine_session=args.session or "constant")
_print(payload, args.json)
return 0 if payload["returncode"] == 0 else 1
def cmd_cockpit_status(args: argparse.Namespace) -> int:
payload = runtime_status(local_session=args.local_session or "constant-fleet", machine_session=args.session or "constant")
_print(payload, args.json)
return 0
def cmd_tui(args: argparse.Namespace) -> int:
workspace = str(Path(args.workspace or os.getcwd()).expanduser().resolve())
action = run_tui(
workspace,
local_session=args.local_session or "constant-fleet",
machine_session=args.session or "constant",
)
if action and action.get("action") == "cockpit":
return cmd_cockpit_open(
argparse.Namespace(
workspace=action.get("workspace", workspace),
local_session=args.local_session,
session=args.session,
recreate=False,
remote_recreate=False,
)
)
return 0
def cmd_mission_create(args: argparse.Namespace) -> int:
_warm_memory(args.workspace)
mission = create_mission(args.prompt, args.workspace)
mission = _ensure_planned(mission)
_print(_mission_summary(mission), args.json)
return 0
def cmd_mission_plan(args: argparse.Namespace) -> int:
mission = load_mission(args.mission_id)
_warm_memory(mission["workspace"])
payload = daemon_request("plan", {"mission": mission})
mission["steps"] = payload["plan"]["steps"]
mission["title"] = payload["plan"]["title"]
mission["status"] = "planned"
mission["planner_summary"] = payload["plan"]["summary"]
mission["buddy_review"] = payload["buddy_review"]
save_mission(mission)
append_event(mission["mission_id"], "mission.replanned", payload)
_print({"summary": _mission_summary(mission), "buddy_review": payload["buddy_review"]}, args.json)
return 0
def _verify_and_update(mission: dict[str, Any], step: dict[str, Any], execution: dict[str, Any]) -> dict[str, Any]:
verdict = daemon_request("verify", {"mission": mission, "step": step, "execution": execution})
step["verification"] = verdict
step["result_summary"] = verdict.get("summary", "")
return verdict
def cmd_mission_run(args: argparse.Namespace) -> int:
mission = load_mission(args.mission_id)
mission = _ensure_planned(mission)
step = first_active_step(mission)
if step is None:
mission["status"] = "done"
save_mission(mission)
_print(_mission_summary(mission), args.json)
return 0
while step is not None:
step["status"] = "running"
step["attempt"] += 1
mission["status"] = "running"
save_mission(mission)
append_event(mission["mission_id"], "step.started", {"step_id": step["step_id"], "machine": step["machine"], "backend": step["backend"], "cli": step["cli"]})
execution = execute_step(step, mission, load_fleet_config())
artifact_path = write_artifact(
mission["mission_id"],
f"{step['step_id']}-attempt-{step['attempt']}.json",
execution,
)
step["artifact_refs"].append(artifact_path)
append_event(mission["mission_id"], "step.executed", {"step_id": step["step_id"], "artifact": artifact_path, "returncode": execution["returncode"]})
verdict = _verify_and_update(mission, step, execution)
decision = verdict.get("decision", "failed")
if decision == "done":
step["status"] = "done"
elif decision == "retry" and step["attempt"] < 2:
step["status"] = "pending"
elif decision == "needs_human":
step["status"] = "needs_human"
mission["status"] = "needs_human"
save_mission(mission)
append_event(mission["mission_id"], "step.needs_human", {"step_id": step["step_id"], "summary": verdict.get("summary", "")})
_finalize_mission_memory(mission["mission_id"])
_print({"summary": _mission_summary(mission), "verdict": verdict}, args.json)
return 1
else:
step["status"] = "failed"
mission["status"] = "failed"
save_mission(mission)
append_event(mission["mission_id"], "step.failed", {"step_id": step["step_id"], "summary": verdict.get("summary", "")})
_finalize_mission_memory(mission["mission_id"])
_print({"summary": _mission_summary(mission), "verdict": verdict}, args.json)
return 1
save_mission(mission)
append_event(mission["mission_id"], "step.verified", {"step_id": step["step_id"], "verdict": verdict})
step = first_active_step(mission)
mission["status"] = "done"
save_mission(mission)
append_event(mission["mission_id"], "mission.done", {"mission_id": mission["mission_id"]})
_finalize_mission_memory(mission["mission_id"])
_print(_mission_summary(mission), args.json)
return 0
def cmd_mission_status(args: argparse.Namespace) -> int:
if args.mission_id:
mission = load_mission(args.mission_id)
_print(_mission_summary(mission) if not args.verbose else mission, args.json)
return 0
missions = [_mission_summary(mission) for mission in list_missions()]
_print({"missions": missions}, args.json)
return 0
def cmd_mission_tail(args: argparse.Namespace) -> int:
path = mission_events_file(args.mission_id)
if not path.exists():
print(f"No events for mission {args.mission_id}")
return 1
if not args.follow:
print(path.read_text(encoding="utf-8"), end="")
return 0
seen_size = 0
while True:
text = path.read_text(encoding="utf-8")
if len(text) > seen_size:
print(text[seen_size:], end="")
seen_size = len(text)
time.sleep(1)
def cmd_mission_verify(args: argparse.Namespace) -> int:
mission = load_mission(args.mission_id)
step = next((entry for entry in mission["steps"] if entry["step_id"] == args.step_id), None) if args.step_id else (mission["steps"][-1] if mission["steps"] else None)
if step is None or not step["artifact_refs"]:
print("No step artifact available to verify.")
return 1
artifact = json.loads(Path(step["artifact_refs"][-1]).read_text(encoding="utf-8"))
verdict = _verify_and_update(mission, step, artifact)
save_mission(mission)
append_event(mission["mission_id"], "mission.verify", {"step_id": step["step_id"], "verdict": verdict})
_print({"step_id": step["step_id"], "verdict": verdict}, args.json)
return 0 if verdict.get("decision") == "done" else 1
def cmd_mission_retry(args: argparse.Namespace) -> int:
mission = load_mission(args.mission_id)
step = next((entry for entry in mission["steps"] if entry["step_id"] == args.step_id), None) if args.step_id else first_active_step(mission) or (mission["steps"][-1] if mission["steps"] else None)
if step is None:
print("No step to retry.")
return 1
step["status"] = "pending"
mission["status"] = "planned"
save_mission(mission)
append_event(mission["mission_id"], "step.retry_requested", {"step_id": step["step_id"]})
_print(_mission_summary(mission), args.json)
return 0
def cmd_delegate(args: argparse.Namespace) -> int:
mission = load_mission(args.mission_id)
step = next((entry for entry in mission["steps"] if entry["step_id"] == args.step_id), None) if args.step_id else first_active_step(mission)
if step is None:
print("No active step to delegate.")
return 1
if args.machine:
fleet_machine(args.machine)
step["machine"] = args.machine
if args.backend:
step["backend"] = args.backend
if args.skill or args.agent or args.cli:
resolved_cli = args.cli
resolved_agent = args.agent
if not args.skill and not resolved_cli:
resolved_cli = step.get("cli")
if not args.skill and not resolved_agent:
resolved_agent = step.get("agent")
resolved = resolve_skill_and_agent(
goal=step.get("prompt") or mission.get("goal", ""),
skill_id=args.skill or step.get("skill"),
agent_id=resolved_agent,
cli=resolved_cli,
)
step["skill"] = resolved["skill"]["id"]
step["skill_summary"] = resolved["skill"]["summary"]
step["agent"] = resolved["agent"]["id"]
step["agent_role"] = resolved["agent"]["role"]
step["cli"] = resolved["cli"]
elif step.get("cli") and not step.get("agent"):
agent = agent_for_cli(step["cli"])
step["agent"] = agent["id"]
step["agent_role"] = agent["role"]
if step.get("skill"):
try:
step["skill_summary"] = skill_by_id(step["skill"])["summary"]
except KeyError:
pass
step["status"] = "pending"
mission["status"] = "planned"
save_mission(mission)
append_event(mission["mission_id"], "step.delegated", {"step_id": step["step_id"], "machine": step["machine"], "backend": step["backend"], "cli": step["cli"], "agent": step["agent"], "skill": step.get("skill")})
_print(_mission_summary(mission), args.json)
return 0
def cmd_buddy_ask(args: argparse.Namespace) -> int:
mission = load_mission(args.mission_id) if args.mission_id else None
if mission:
_warm_memory(mission["workspace"])
answer = daemon_request("buddy", {"mission": mission, "prompt": args.prompt})
if mission:
append_event(mission["mission_id"], "buddy.ask", {"prompt": args.prompt, "answer": answer})
_print(answer, args.json)
return 0
def cmd_memory_status(args: argparse.Namespace) -> int:
_print(memory_status(args.workspace), args.json)
return 0
def cmd_memory_rebuild(args: argparse.Namespace) -> int:
_print(rebuild_workspace_memory(args.workspace, enroll=not args.no_enroll), args.json)
return 0
def cmd_memory_enroll(args: argparse.Namespace) -> int:
_print(enroll_workspace(args.path), args.json)
return 0
def cmd_memory_search(args: argparse.Namespace) -> int:
_print(search_memory(args.query, args.workspace, args.limit), args.json)
return 0
def cmd_memory_persona_show(args: argparse.Namespace) -> int:
payload = {"persona": persona_markdown()} if args.json else persona_markdown()
_print(payload, args.json)
return 0
def cmd_memory_decisions(args: argparse.Namespace) -> int:
_print(list_decisions(args.workspace, args.mission_id), args.json)
return 0
def cmd_memory_sync_qdrant(args: argparse.Namespace) -> int:
payload = sync_qdrant(args.workspace)
_print(payload, args.json)
return 0 if payload.get("ok") or payload.get("skipped") else 1
def cmd_mission_summarize(args: argparse.Namespace) -> int:
_print(summarize_mission(args.mission_id), args.json)
return 0
def build_parser() -> argparse.ArgumentParser:
parser = argparse.ArgumentParser(prog=os.environ.get("CONSTANT_PROG_NAME", "Constant"))
parser.add_argument("-V", "--version", action="version", version=__version__)
subparsers = parser.add_subparsers(dest="command", required=True)
doctor = subparsers.add_parser("doctor")
doctor.add_argument("--json", action="store_true")
doctor.set_defaults(func=cmd_doctor)
tui = subparsers.add_parser("tui")
tui.add_argument("--workspace")
tui.add_argument("--local-session")
tui.add_argument("--session")
tui.set_defaults(func=cmd_tui)
daemon = subparsers.add_parser("daemon")
daemon_sub = daemon.add_subparsers(dest="daemon_command", required=True)
daemon_start = daemon_sub.add_parser("start")
daemon_start.set_defaults(func=cmd_daemon_start)
daemon_stop = daemon_sub.add_parser("stop")
daemon_stop.set_defaults(func=cmd_daemon_stop)
daemon_status_cmd = daemon_sub.add_parser("status")
daemon_status_cmd.add_argument("--json", action="store_true")
daemon_status_cmd.set_defaults(func=cmd_daemon_status)
daemon_logs = daemon_sub.add_parser("logs")
daemon_logs.set_defaults(func=cmd_daemon_logs)
models = subparsers.add_parser("models")
models_sub = models.add_subparsers(dest="models_command", required=True)
models_status = models_sub.add_parser("status")
models_status.add_argument("--json", action="store_true")
models_status.set_defaults(func=cmd_models_status)
agents = subparsers.add_parser("agents")
agents.add_argument("--json", action="store_true")
agents.set_defaults(func=cmd_agents)
skills = subparsers.add_parser("skills")
skills.add_argument("--workspace")
skills.add_argument("--public-only", action="store_true")
skills.add_argument("--json", action="store_true")
skills.set_defaults(func=cmd_skills)
fleet = subparsers.add_parser("fleet")
fleet_sub = fleet.add_subparsers(dest="fleet_command", required=True)
fleet_status_cmd = fleet_sub.add_parser("status")
fleet_status_cmd.add_argument("--json", action="store_true")
fleet_status_cmd.set_defaults(func=cmd_fleet_status)
fleet_sync_cmd = fleet_sub.add_parser("sync")
fleet_sync_cmd.add_argument("--json", action="store_true")
fleet_sync_cmd.set_defaults(func=cmd_fleet_sync)
for fleet_cmd_name, fleet_cmd_func, allow_json in (
("discover", cmd_fleet_discover, True),
("configure", cmd_fleet_configure, False),
("deploy", cmd_fleet_deploy, False),
):
fleet_cmd = fleet_sub.add_parser(fleet_cmd_name)
fleet_cmd.add_argument("--host", action="append")
fleet_cmd.add_argument("--user")
fleet_cmd.add_argument("--repo-dir")
fleet_cmd.add_argument("--local-label")
fleet_cmd.add_argument("--output")
fleet_cmd.add_argument("--yes", action="store_true")
fleet_cmd.add_argument("--all-reachable", action="store_true")
fleet_cmd.add_argument("--no-ssh-config", action="store_true")
fleet_cmd.add_argument("--no-known-hosts", action="store_true")
fleet_cmd.add_argument("--no-arp", action="store_true")
if fleet_cmd_name == "configure":
fleet_cmd.add_argument("--install", action="store_true")
if allow_json:
fleet_cmd.add_argument("--json", action="store_true")
fleet_cmd.set_defaults(func=fleet_cmd_func)
cockpit = subparsers.add_parser("cockpit")
cockpit_sub = cockpit.add_subparsers(dest="cockpit_command", required=True)
cockpit_open = cockpit_sub.add_parser("open")
cockpit_open.add_argument("--workspace")
cockpit_open.add_argument("--local-session")
cockpit_open.add_argument("--session")
cockpit_open.add_argument("--recreate", action="store_true")
cockpit_open.add_argument("--remote-recreate", action="store_true")
cockpit_open.set_defaults(func=cmd_cockpit_open)
cockpit_attach = cockpit_sub.add_parser("attach")
cockpit_attach.add_argument("--local-session")
cockpit_attach.set_defaults(func=cmd_cockpit_attach)
cockpit_doctor_cmd = cockpit_sub.add_parser("doctor")
cockpit_doctor_cmd.add_argument("--local-session")
cockpit_doctor_cmd.add_argument("--session")
cockpit_doctor_cmd.add_argument("--json", action="store_true")
cockpit_doctor_cmd.set_defaults(func=cmd_cockpit_doctor)
cockpit_status_cmd = cockpit_sub.add_parser("status")
cockpit_status_cmd.add_argument("--local-session")
cockpit_status_cmd.add_argument("--session")
cockpit_status_cmd.add_argument("--json", action="store_true")
cockpit_status_cmd.set_defaults(func=cmd_cockpit_status)
cockpit_focus_cmd = cockpit_sub.add_parser("focus")
cockpit_focus_cmd.add_argument("--machine", required=True)
cockpit_focus_cmd.add_argument("--pane")
cockpit_focus_cmd.add_argument("--local-session")
cockpit_focus_cmd.add_argument("--session")
cockpit_focus_cmd.add_argument("--json", action="store_true")
cockpit_focus_cmd.set_defaults(func=cmd_cockpit_focus)
cockpit_send_cmd = cockpit_sub.add_parser("send")
cockpit_send_cmd.add_argument("--machine", required=True)
cockpit_send_cmd.add_argument("--pane", required=True)
cockpit_send_cmd.add_argument("--command", required=True)
cockpit_send_cmd.add_argument("--session")
cockpit_send_cmd.add_argument("--json", action="store_true")
cockpit_send_cmd.set_defaults(func=cmd_cockpit_send)
cockpit_capture_cmd = cockpit_sub.add_parser("capture")
cockpit_capture_cmd.add_argument("--machine", required=True)
cockpit_capture_cmd.add_argument("--pane", required=True)
cockpit_capture_cmd.add_argument("--lines", type=int, default=120)
cockpit_capture_cmd.add_argument("--session")
cockpit_capture_cmd.add_argument("--json", action="store_true")
cockpit_capture_cmd.set_defaults(func=cmd_cockpit_capture)
cockpit_restart_cmd = cockpit_sub.add_parser("restart")
cockpit_restart_cmd.add_argument("--machine", required=True)
cockpit_restart_cmd.add_argument("--pane", required=True)
cockpit_restart_cmd.add_argument("--session")
cockpit_restart_cmd.add_argument("--json", action="store_true")
cockpit_restart_cmd.set_defaults(func=cmd_cockpit_restart)
mission = subparsers.add_parser("mission")
mission_sub = mission.add_subparsers(dest="mission_command", required=True)
mission_create = mission_sub.add_parser("create")
mission_create.add_argument("prompt")
mission_create.add_argument("--workspace", default=os.getcwd())
mission_create.add_argument("--json", action="store_true")
mission_create.set_defaults(func=cmd_mission_create)
mission_plan = mission_sub.add_parser("plan")
mission_plan.add_argument("mission_id")
mission_plan.add_argument("--json", action="store_true")
mission_plan.set_defaults(func=cmd_mission_plan)
mission_run = mission_sub.add_parser("run")
mission_run.add_argument("mission_id")
mission_run.add_argument("--json", action="store_true")
mission_run.set_defaults(func=cmd_mission_run)
mission_status = mission_sub.add_parser("status")
mission_status.add_argument("mission_id", nargs="?")
mission_status.add_argument("--verbose", action="store_true")
mission_status.add_argument("--json", action="store_true")
mission_status.set_defaults(func=cmd_mission_status)
mission_tail = mission_sub.add_parser("tail")
mission_tail.add_argument("mission_id")
mission_tail.add_argument("--follow", action="store_true")
mission_tail.set_defaults(func=cmd_mission_tail)
mission_verify = mission_sub.add_parser("verify")
mission_verify.add_argument("mission_id")
mission_verify.add_argument("--step-id")
mission_verify.add_argument("--json", action="store_true")
mission_verify.set_defaults(func=cmd_mission_verify)
mission_retry = mission_sub.add_parser("retry")
mission_retry.add_argument("mission_id")
mission_retry.add_argument("--step-id")
mission_retry.add_argument("--json", action="store_true")
mission_retry.set_defaults(func=cmd_mission_retry)
mission_summarize = mission_sub.add_parser("summarize")
mission_summarize.add_argument("mission_id")
mission_summarize.add_argument("--json", action="store_true")
mission_summarize.set_defaults(func=cmd_mission_summarize)
delegate = subparsers.add_parser("delegate")
delegate.add_argument("mission_id")
delegate.add_argument("--step-id")
delegate.add_argument("--machine")
delegate.add_argument("--backend")
delegate.add_argument("--cli")
delegate.add_argument("--agent")
delegate.add_argument("--skill")
delegate.add_argument("--json", action="store_true")
delegate.set_defaults(func=cmd_delegate)
buddy = subparsers.add_parser("buddy")
buddy_sub = buddy.add_subparsers(dest="buddy_command", required=True)
buddy_ask = buddy_sub.add_parser("ask")
buddy_ask.add_argument("prompt")
buddy_ask.add_argument("--mission-id")
buddy_ask.add_argument("--json", action="store_true")
buddy_ask.set_defaults(func=cmd_buddy_ask)
memory = subparsers.add_parser("memory")
memory_sub = memory.add_subparsers(dest="memory_command", required=True)
memory_status_cmd = memory_sub.add_parser("status")
memory_status_cmd.add_argument("--workspace")
memory_status_cmd.add_argument("--json", action="store_true")
memory_status_cmd.set_defaults(func=cmd_memory_status)
memory_rebuild_cmd = memory_sub.add_parser("rebuild")
memory_rebuild_cmd.add_argument("--workspace", default=os.getcwd())
memory_rebuild_cmd.add_argument("--no-enroll", action="store_true")
memory_rebuild_cmd.add_argument("--json", action="store_true")
memory_rebuild_cmd.set_defaults(func=cmd_memory_rebuild)
memory_enroll_cmd = memory_sub.add_parser("enroll")
memory_enroll_cmd.add_argument("path")
memory_enroll_cmd.add_argument("--json", action="store_true")
memory_enroll_cmd.set_defaults(func=cmd_memory_enroll)
memory_search_cmd = memory_sub.add_parser("search")
memory_search_cmd.add_argument("query")
memory_search_cmd.add_argument("--workspace")
memory_search_cmd.add_argument("--limit", type=int)
memory_search_cmd.add_argument("--json", action="store_true")
memory_search_cmd.set_defaults(func=cmd_memory_search)
memory_persona_cmd = memory_sub.add_parser("persona")
memory_persona_sub = memory_persona_cmd.add_subparsers(dest="memory_persona_command", required=True)
memory_persona_show_cmd = memory_persona_sub.add_parser("show")
memory_persona_show_cmd.add_argument("--json", action="store_true")
memory_persona_show_cmd.set_defaults(func=cmd_memory_persona_show)
memory_decisions_cmd = memory_sub.add_parser("decisions")
memory_decisions_cmd.add_argument("--workspace")
memory_decisions_cmd.add_argument("--mission-id")
memory_decisions_cmd.add_argument("--json", action="store_true")
memory_decisions_cmd.set_defaults(func=cmd_memory_decisions)
memory_sync_cmd = memory_sub.add_parser("sync-qdrant")
memory_sync_cmd.add_argument("--workspace")
memory_sync_cmd.add_argument("--json", action="store_true")
memory_sync_cmd.set_defaults(func=cmd_memory_sync_qdrant)
hidden = subparsers.add_parser("__serve")
hidden.set_defaults(func=lambda _args: serve_foreground())
return parser
def main(argv: list[str] | None = None) -> int:
if argv is None:
argv = sys.argv[1:]
if not argv:
if sys.stdin.isatty() and sys.stdout.isatty():
argv = ["cockpit", "open", "--workspace", os.getcwd()]
else:
argv = ["doctor"]
parser = build_parser()
args = parser.parse_args(argv)
return args.func(args)
if __name__ == "__main__":
raise SystemExit(main())