left4me/docs/superpowers/plans/2026-05-06-l4d2-cli-host-client.md

4.1 KiB

L4D2 CLI Host Client Implementation Plan

For agentic workers: REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (- [ ]) syntax for tracking.

Goal: Make l4d2web manage the local host through l4d2ctl instead of importing l4d2host internals, so the same execution boundary can later be transported over SSH.

Architecture: l4d2host remains the host-local implementation behind l4d2ctl. l4d2web gains a small local command runner that streams CLI stdout/stderr into jobs, supports cancellation, and parses status JSON. Hosts and overlay sync remain out of this change; the current machine is the implicit local host.

Tech Stack: Python 3.12+, Typer, subprocess, Flask, SQLAlchemy, pytest.


File Map

  • l4d2host/cli.py: add read commands for status and logs.
  • l4d2host/tests/test_cli.py: cover the expanded CLI contract.
  • l4d2web/services/host_commands.py: new subprocess-based host command runner and cancellation exception.
  • l4d2web/services/l4d2_facade.py: call l4d2ctl through host_commands instead of importing l4d2host internals.
  • l4d2web/services/job_worker.py: catch the web-side cancellation exception.
  • l4d2web/tests/test_host_commands.py: cover callback streaming, failures, and cancellation.
  • l4d2web/tests/test_l4d2_facade.py: verify facade emits CLI commands and parses status.
  • l4d2web/tests/test_job_worker.py: update cancellation imports.
  • l4d2host/README.md, l4d2web/README.md, existing implementation plans: document the relaxed CLI boundary.

Tasks

Task 1: Add host CLI read commands

  • Write failing tests for l4d2ctl status <name> --json and l4d2ctl logs <name> --no-follow.
  • Run pytest l4d2host/tests/test_cli.py -q and confirm the new tests fail because commands do not exist.
  • Add the status and logs commands to l4d2host/cli.py using existing get_instance_status and stream_instance_logs APIs.
  • Run pytest l4d2host/tests/test_cli.py -q and confirm it passes.

Task 2: Add web host command runner

  • Write failing tests for streaming stdout/stderr callbacks, non-zero exit propagation, and cancellation.
  • Run pytest l4d2web/tests/test_host_commands.py -q and confirm failures are for the missing module.
  • Implement l4d2web/services/host_commands.py with run_command, HostCommandError, and CommandCancelledError.
  • Run pytest l4d2web/tests/test_host_commands.py -q and confirm it passes.

Task 3: Switch web facade to CLI calls

  • Update facade tests so they monkeypatch host_commands.run_command and assert emitted l4d2ctl commands.
  • Run pytest l4d2web/tests/test_l4d2_facade.py -q and confirm failures show the facade still imports/calls l4d2host internals.
  • Replace direct l4d2host imports in l4d2web/services/l4d2_facade.py with CLI command calls.
  • Run pytest l4d2web/tests/test_l4d2_facade.py -q and confirm it passes.

Task 4: Update worker cancellation boundary

  • Update job worker tests to import CommandCancelledError from l4d2web.services.host_commands.
  • Run pytest l4d2web/tests/test_job_worker.py -q and confirm failures identify the old boundary.
  • Update l4d2web/services/job_worker.py to catch the web-side cancellation exception.
  • Run pytest l4d2web/tests/test_job_worker.py -q and confirm it passes.

Task 5: Update docs and verify

  • Update README/plan language from “fixed write commands only” to “fixed write commands plus read commands”.
  • Run pytest l4d2host/tests -q and confirm pass.
  • Run pytest l4d2web/tests -q and confirm pass.
  • Run ccc index if available so the code index reflects the boundary change.

Self-Review

  • Spec coverage: covers CLI read commands, web-side CLI execution, status/log parsing, cancellation, docs, and verification.
  • Scope: hosts table, SSH transport, and overlay sync are explicitly excluded from this change.
  • Type consistency: the web-side cancellation type is l4d2web.services.host_commands.CommandCancelledError; the host-side process type remains internal to l4d2host.