docs: make the CLI the source of truth for ferro-vm commands
명령 목록이 tools/README.md와 argparse 정의 두 곳에 손으로 동기화되고 있었다. 드리프트가 불가피하므로 목록을 --help로 단일화한다. cli.py에 서브커맨드별 help/description, 인자 metavar, 예시 epilog를 채웠다. put의 DOS 8.3 이름 제약처럼 명령에 직접 붙는 함정은 해당 도움말에 넣었다. tools/README.md는 호스트 요구사항, 셋업, 자동화 구조로 줄이고 목록은 --help로 넘긴다. AGENTS.md에는 CLI가 규범이라는 포인터를 남긴다. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012PQm6oAvWX4Lp3iSN5AHGT
This commit is contained in:
@@ -9,9 +9,17 @@ DOS용 시스템 프로그래밍 언어 Ferro와 그 컴파일러 `fec`. 규범
|
|||||||
|---|---|
|
|---|---|
|
||||||
| `SPEC.md` | 언어 명세 + 구현 지시서. 유일한 규범 문서 |
|
| `SPEC.md` | 언어 명세 + 구현 지시서. 유일한 규범 문서 |
|
||||||
| `SPEC.AUDIT.md` | 명세 변경의 문제·결정·근거·구현 영향 누적 로그 |
|
| `SPEC.AUDIT.md` | 명세 변경의 문제·결정·근거·구현 영향 누적 로그 |
|
||||||
| `tools/README.md` | QEMU/FreeDOS 자동화 도구 사용법 |
|
| `tools/README.md` | 호스트 요구사항, 최초 셋업, 자동화 구조 |
|
||||||
| `tools/tcpagent/README.md` | DOS 내부 TCP 에이전트 프로토콜과 빌드 |
|
| `tools/tcpagent/README.md` | DOS 내부 TCP 에이전트 프로토콜과 빌드 |
|
||||||
|
|
||||||
|
VM 자동화 **명령 목록과 플래그는 문서가 아니라 CLI가 규범**이다. 문서에 복제하면
|
||||||
|
반드시 드리프트하므로 아래로 확인한다.
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
uv run ferro-vm --help
|
||||||
|
uv run ferro-vm <command> --help
|
||||||
|
```
|
||||||
|
|
||||||
## 검증 규칙
|
## 검증 규칙
|
||||||
|
|
||||||
- **실행 검증은 QEMU FreeDOS 내부에서만 한다.** DOSBox는 쓰지 않는다.
|
- **실행 검증은 QEMU FreeDOS 내부에서만 한다.** DOSBox는 쓰지 않는다.
|
||||||
|
|||||||
+67
-16
@@ -64,23 +64,74 @@ def follow_logs() -> int:
|
|||||||
]).returncode
|
]).returncode
|
||||||
|
|
||||||
|
|
||||||
|
EPILOG = r"""examples:
|
||||||
|
uv run ferro-vm start boot the VM and start the daemon
|
||||||
|
uv run ferro-vm wait-ready block until TCPAGENT answers PING
|
||||||
|
uv run ferro-vm exec 'dir C:\FEC' run a DOS command, print exit code and output
|
||||||
|
uv run ferro-vm put fec/src/check.c 'C:\FEC\SRC\CHECK.C'
|
||||||
|
uv run ferro-vm get 'C:\FEC\TEST.OK' .qemu/TEST.OK
|
||||||
|
uv run ferro-vm logs follow the structured daemon log
|
||||||
|
|
||||||
|
The authoritative workspace is C:\FEC inside the VM. Never build on D: (the vvfat
|
||||||
|
view is for exchange only). See AGENTS.md for verification rules and build traps.
|
||||||
|
"""
|
||||||
|
|
||||||
|
SIMPLE_COMMANDS = {
|
||||||
|
"start": "Start the daemon and boot QEMU. Safe to run when already up.",
|
||||||
|
"stop": "Quit QEMU cleanly and stop the daemon.",
|
||||||
|
"status": "Print daemon, QEMU, and TCPAGENT connection state as JSON.",
|
||||||
|
"ping": "Send PING to TCPAGENT. Expects 'OK 504F4E47' (PONG).",
|
||||||
|
"screenshot": "Capture the VGA console to a PPM/PNG under .qemu/.",
|
||||||
|
"ocr": "Capture the console and print recognized text (RapidOCR).",
|
||||||
|
"logs": "Follow the append-only daemon log. Uses lnav when available.",
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
def main() -> int:
|
def main() -> int:
|
||||||
parser = argparse.ArgumentParser(description="Windows-only QEMU/FreeDOS automation")
|
parser = argparse.ArgumentParser(
|
||||||
commands = parser.add_subparsers(dest="op", required=True)
|
prog="ferro-vm",
|
||||||
for name in ("start", "stop", "status", "ping", "screenshot", "ocr", "logs"):
|
description="Windows-only QEMU/FreeDOS automation for the Ferro compiler.",
|
||||||
commands.add_parser(name)
|
epilog=EPILOG,
|
||||||
wait = commands.add_parser("wait-ready")
|
formatter_class=argparse.RawDescriptionHelpFormatter,
|
||||||
wait.add_argument("--timeout", type=int, default=45)
|
)
|
||||||
reset = commands.add_parser("reset")
|
commands = parser.add_subparsers(dest="op", required=True, metavar="COMMAND")
|
||||||
reset.add_argument("--timeout", type=int, default=45)
|
for name, blurb in SIMPLE_COMMANDS.items():
|
||||||
execute = commands.add_parser("exec")
|
commands.add_parser(name, help=blurb, description=blurb)
|
||||||
execute.add_argument("command")
|
|
||||||
put = commands.add_parser("put")
|
wait_help = "Block until TCPAGENT is connected and answers PING."
|
||||||
put.add_argument("source", type=Path)
|
wait = commands.add_parser("wait-ready", help=wait_help, description=wait_help)
|
||||||
put.add_argument("destination")
|
wait.add_argument("--timeout", type=int, default=45, metavar="SECONDS",
|
||||||
get = commands.add_parser("get")
|
help="give up after this many seconds (default: %(default)s)")
|
||||||
get.add_argument("source")
|
|
||||||
get.add_argument("destination", type=Path)
|
reset_help = "Quit QEMU cleanly, reboot it, and wait for TCPAGENT."
|
||||||
|
reset = commands.add_parser("reset", help=reset_help, description=reset_help)
|
||||||
|
reset.add_argument("--timeout", type=int, default=45, metavar="SECONDS",
|
||||||
|
help="give up after this many seconds (default: %(default)s)")
|
||||||
|
|
||||||
|
exec_help = "Run a DOS command inside the VM and print its exit code and output."
|
||||||
|
execute = commands.add_parser(
|
||||||
|
"exec", help=exec_help,
|
||||||
|
description=exec_help + " Quote the command so the host shell does not eat"
|
||||||
|
r" backslashes: exec 'wcl386 -q HELLO.C'.")
|
||||||
|
execute.add_argument("command", metavar="DOS_COMMAND",
|
||||||
|
help=r"command line to hand to COMMAND.COM, e.g. 'dir C:\FEC'")
|
||||||
|
|
||||||
|
put_help = "Copy a host file into the VM."
|
||||||
|
put = commands.add_parser("put", help=put_help, description=put_help)
|
||||||
|
put.add_argument("source", type=Path, metavar="HOST_PATH",
|
||||||
|
help="file on this machine")
|
||||||
|
put.add_argument("destination", metavar="DOS_PATH",
|
||||||
|
help=r"target inside the VM, e.g. 'C:\FEC\SRC\CHECK.C'."
|
||||||
|
" DOS uses 8.3 names, so long fixtures must be shortened"
|
||||||
|
" explicitly (BAD-ARI.FE, TRY-FPR.FE)")
|
||||||
|
|
||||||
|
get_help = "Copy a file out of the VM onto the host."
|
||||||
|
get = commands.add_parser("get", help=get_help, description=get_help)
|
||||||
|
get.add_argument("source", metavar="DOS_PATH",
|
||||||
|
help=r"file inside the VM, e.g. 'C:\FEC\TEST.OK'")
|
||||||
|
get.add_argument("destination", type=Path, metavar="HOST_PATH",
|
||||||
|
help="target on this machine")
|
||||||
|
|
||||||
args = parser.parse_args()
|
args = parser.parse_args()
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
|||||||
+21
-26
@@ -6,46 +6,41 @@ Automation currently supports **Windows 10/11 only**. It requires `uv`, QEMU
|
|||||||
with WHPX support, and `ffmpeg.exe` on `PATH`. The Python implementation uses
|
with WHPX support, and `ffmpeg.exe` on `PATH`. The Python implementation uses
|
||||||
portable APIs where possible, but other hosts are not supported yet.
|
portable APIs where possible, but other hosts are not supported yet.
|
||||||
|
|
||||||
## QEMU and FreeDOS automation
|
## Getting started
|
||||||
|
|
||||||
Start the Python daemon and QEMU:
|
|
||||||
|
|
||||||
```powershell
|
```powershell
|
||||||
uv run ferro-vm start
|
uv run ferro-vm start
|
||||||
uv run ferro-vm status
|
uv run ferro-vm status
|
||||||
```
|
```
|
||||||
|
|
||||||
`TCPAGENT.EXE` connects only to `127.0.0.1:5558`. Local commands use the
|
The command list lives in the CLI itself, not in this file:
|
||||||
Windows named pipe `\\.\pipe\ferrolang-vm`; there is no controller or observer
|
|
||||||
TCP port. Monitor the append-only structured log in another terminal:
|
|
||||||
|
|
||||||
```powershell
|
```powershell
|
||||||
uv run ferro-vm logs
|
uv run ferro-vm --help
|
||||||
|
uv run ferro-vm <command> --help
|
||||||
```
|
```
|
||||||
|
|
||||||
Commands:
|
Working rules, verification gates, and DOS build traps are in `AGENTS.md`.
|
||||||
|
|
||||||
```powershell
|
## How it fits together
|
||||||
uv run ferro-vm reset # clean QEMU quit and restart
|
|
||||||
uv run ferro-vm wait-ready --timeout 45
|
|
||||||
uv run ferro-vm ping
|
|
||||||
uv run ferro-vm exec 'dir C:\FEC'
|
|
||||||
uv run ferro-vm put fec/src/check.c 'C:\FEC\SRC\CHECK.C'
|
|
||||||
uv run ferro-vm get 'C:\FEC\TEST.OK' .qemu/TEST.OK
|
|
||||||
uv run ferro-vm screenshot
|
|
||||||
uv run ferro-vm ocr
|
|
||||||
uv run ferro-vm stop
|
|
||||||
```
|
|
||||||
|
|
||||||
`reset` cleanly quits and restarts QEMU, waits for FreeDOS to boot, submits
|
`TCPAGENT.EXE` runs inside FreeDOS and dials out to `127.0.0.1:5558`; its wire
|
||||||
the default boot-menu Enter, and requires TCPAGENT `PING`/`PONG`. QEMU
|
protocol is documented in `tcpagent/README.md`. The `ferro-vm` daemon owns that
|
||||||
`system_reset` is intentionally unsupported because repeated soft resets leave
|
connection and the QEMU monitor. Local commands reach the daemon over the
|
||||||
the FreeDOS NE2000 packet driver stuck during initialization. `logs` starts
|
Windows named pipe `\\.\pipe\ferrolang-vm` — there is no controller or observer
|
||||||
`lnav` when installed and otherwise falls back to PowerShell `Get-Content
|
TCP port.
|
||||||
-Wait`. The daemon logs command metadata, DOS output, exit status, transfers,
|
|
||||||
and agent lifecycle events as UTF-8 lines. It deliberately never logs raw binary
|
The daemon writes an append-only structured log (`uv run ferro-vm logs`, which
|
||||||
|
uses `lnav` when installed and otherwise falls back to PowerShell `Get-Content
|
||||||
|
-Wait`). It records command metadata, DOS output, exit status, transfers, and
|
||||||
|
agent lifecycle events as UTF-8 lines, and deliberately never logs raw binary
|
||||||
payloads or protocol hex.
|
payloads or protocol hex.
|
||||||
|
|
||||||
|
`reset` quits QEMU cleanly, restarts it, waits for FreeDOS to boot, submits the
|
||||||
|
default boot-menu Enter, and requires a TCPAGENT `PING`/`PONG` before returning.
|
||||||
|
QEMU `system_reset` is intentionally unsupported: repeated soft resets leave the
|
||||||
|
FreeDOS NE2000 packet driver stuck during initialization.
|
||||||
|
|
||||||
## Standalone OCR
|
## Standalone OCR
|
||||||
|
|
||||||
`tools/qemu_ocr.py` remains available for OCRing an existing image:
|
`tools/qemu_ocr.py` remains available for OCRing an existing image:
|
||||||
|
|||||||
Reference in New Issue
Block a user