Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ OpsQuest is a Go 1.26 CLI game with 23 in-memory Linux missions and 6 optional D
- `internal/mission`: JSON schema, strict embedded catalog loading, track-local world/stage derivation, integrity checks, and `data/` mission content.
- `internal/sandbox`: parser, dispatcher, virtual filesystem, virtual processes, archives, and the supported command subset.
- `internal/dockerlab`: optional Docker capability detection, typed teaching commands, isolated resource lifecycle, and Docker observations.
- `internal/webapp`: loopback-only web companion, one-time browser pairing, sanitized attempt projection, and embedded static assets.
- `internal/profile`: versioned profile model and atomic JSON persistence.
- `scripts`: deterministic repository checks used by both Make and CI.
- `docs/game`: learning principles and the high-level product explanation.
Expand All @@ -35,6 +36,7 @@ Safe local edits, tests, builds, and documentation updates within the requested
- Never execute player-entered commands through a host shell or against the host filesystem.
- Sandbox changes must preserve isolation from host processes and files.
- Docker labs must reject raw command passthrough and operate only on exact resources owned and labeled by the current attempt.
- The web companion must remain loopback-only and must not accept player command text, mutate mission state, or approve completion.
- Mission validators must check observable outcomes, not require one exact command sequence.
- Mission content remains declarative in embedded JSON; engine behavior belongs in Go.
- Mission schema and persisted-profile changes are compatibility-sensitive and require explicit migration or compatibility reasoning.
Expand Down
6 changes: 6 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ OpsQuest validates the result, not a prescribed command. Equivalent supported so
- Quote-aware globs and variables, pipelines, redirection, and command history
- A compact virtual `vi` and bounded virtual shell scripts
- Progressive hints, free command guidance, XP, ranks, and six achievements
- An optional loopback-only web companion for mission guidance and live progress
- World maps, direct mission practice, replay, and persistent profile progress
- Outcome validation for output, paths, content, permissions, processes, environment, and Docker state

Expand Down Expand Up @@ -63,12 +64,14 @@ $ opsquest guide
$ opsquest map
$ opsquest play --world 2
$ opsquest play --once 4
$ opsquest play --web
$ opsquest profile
$ opsquest achievements
$ opsquest doctor
```

The complete player guide covers [installation and first play](docs/play/quick-start.md),
[the local web companion](docs/play/web-companion.md),
[missions and progress](docs/play/missions-and-progress.md), and
[controls and commands](docs/play/controls-and-commands.md).

Expand All @@ -93,6 +96,8 @@ Player-entered Linux commands—including virtual scripts—are parsed and execu

Docker input is parsed into a small set of typed teaching actions. OpsQuest constructs Docker arguments itself, uses only exact resources labeled for the current attempt, applies resource restrictions, and verifies ownership again during cleanup. It does not expose arbitrary Docker passthrough, privileged mode, host mounts, host networking, devices, or the Docker socket.

The optional web companion binds only to an ephemeral `127.0.0.1` port, uses a one-time pairing URL, and exposes a read-only mission projection. It cannot submit commands, mutate the profile or attempt, address Docker resources, or approve completion.

Read [Sandbox and safety](docs/technical/sandbox-and-safety.md) for trust boundaries, quotas, cleanup behavior, and threat controls.

## Documentation
Expand All @@ -119,6 +124,7 @@ internal/mission/ Strict embedded catalog, worlds, and mission JSON
internal/profile/ XP, ranks, achievements, and atomic persistence
internal/sandbox/ Virtual filesystem, shell parser, and commands
internal/dockerlab/ Optional, label-scoped Docker adapter
internal/webapp/ Loopback-only mission companion and embedded browser UI
docs/ Zensical documentation source and diagrams
project/ Repository-only decisions and delivery history
```
Expand Down
5 changes: 5 additions & 0 deletions docs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ The game describes an observable objective, gives you a disposable environment,
| I want to… | Start here |
| --- | --- |
| Install the game and begin a mission | [Quick start](play/quick-start.md) |
| Move mission guidance into a local browser | [Web mission companion](play/web-companion.md) |
| Understand objectives, hints, routes, and rewards | [Missions, hints, and progress](play/missions-and-progress.md) |
| Look up terminal controls and supported commands | [Controls and commands](play/controls-and-commands.md) |
| See what every world and mission teaches | [Mission map](play/mission-map.md) |
Expand All @@ -34,12 +35,16 @@ The game describes an observable objective, gives you a disposable environment,
- Outcome-based validation rather than one required command transcript
- An in-memory filesystem, process table, environment, archives, editor, and shell scripts
- Persistent XP, ranks, command practice, hints, completions, and achievements
- An optional local web companion with live objective progress
- Interactive completion, editing, history, and mission navigation

## The safety promise

Player-entered Linux commands are parsed and executed by OpsQuest's Go teaching shell. They never reach a host shell or host path. Docker gameplay is opt-in, accepts only typed teaching actions, and operates on exact disposable resources labeled for the current attempt.

The optional browser companion is a loopback-only read model. It receives
sanitized mission snapshots but cannot execute commands or declare success.

Read [Sandbox and safety](technical/sandbox-and-safety.md) for the complete trust model and limits.

## About these docs
Expand Down
5 changes: 3 additions & 2 deletions docs/play/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,9 @@ OpsQuest teaches through short incidents inside disposable environments. You ins
## Start here

1. [Install and launch OpsQuest](quick-start.md).
2. Learn how [missions, hints, routes, and rewards work](missions-and-progress.md).
3. Keep the [controls and command reference](controls-and-commands.md) nearby while playing.
2. Optionally move guidance into the [local web companion](web-companion.md).
3. Learn how [missions, hints, routes, and rewards work](missions-and-progress.md).
4. Keep the [controls and command reference](controls-and-commands.md) nearby while playing.

The default route follows the four Linux worlds in the [mission map](mission-map.md).
The separate [Docker Foundations](docker-foundations.md) track is optional and
Expand Down
4 changes: 4 additions & 0 deletions docs/play/controls-and-commands.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,10 @@ status: current

OpsQuest provides line editing and a focused Linux teaching shell inside each mission.

With `opsquest play --web`, command entry, completion, history, and raw command
output remain in the terminal. Mission narrative, objectives, revealed hints,
outcome checks, and the field guide appear in the [web mission companion](web-companion.md).

## Interactive editing

| Keys | Action |
Expand Down
1 change: 1 addition & 0 deletions docs/play/missions-and-progress.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ recorded while a mission is incomplete, so quitting cannot erase the cost.
- `opsquest play --world N` stays within one Linux world.
- A global mission number or stable ID begins there and follows that track.
- `--once` returns after one completed mission.
- `--web` moves mission guidance and live outcome progress into a local browser companion while commands remain in the terminal.
- Completed missions remain replayable and never award duplicate XP.

Inside a mission, `play N` means Stage N of the current world. A stable ID from
Expand Down
2 changes: 2 additions & 0 deletions docs/play/quick-start.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ $ opsquest map --ids # include stable mission IDs
$ opsquest play --world 2 # stay within Linux World 2
$ opsquest play 4 # begin at global Mission 4
$ opsquest play --once 4 # return after one mission
$ opsquest play --web # show guidance in a local browser
$ opsquest show 16 # preview without starting
$ opsquest profile # view XP, rank, and progress
$ opsquest commands # view practiced commands
Expand All @@ -54,6 +55,7 @@ $ opsquest doctor # check catalog, profile, and Docker readiness
## Next steps

- [Missions, hints, and progress](missions-and-progress.md)
- [Web mission companion](web-companion.md)
- [Controls and commands](controls-and-commands.md)
- [Mission map](mission-map.md)
- [Optional Docker setup](docker-foundations.md)
65 changes: 65 additions & 0 deletions docs/play/web-companion.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
---
description: Run the local OpsQuest browser companion while commands remain in the teaching terminal.
audience: players
status: current
---

# Web mission companion

The optional web companion separates guidance from command entry. OpsQuest
continues to execute and validate every command in the CLI-owned teaching
environment, while a local browser page shows the incident, objective,
suggested tools, progressive hints, live outcome checks, rewards, and the
completion explanation.

Start any Linux or Docker route with `--web`:

```console
$ opsquest play --web
$ opsquest play --web --once 5
$ opsquest play --web --track docker
```

OpsQuest prints a one-time URL such as
`http://127.0.0.1:49152/pair?token=...`. Open it in a browser before entering
commands. The URL exchanges its token for a browser-session cookie and cannot
be paired a second time.

## Split-screen play

Keep the terminal and browser visible together:

- Enter lab commands, mission navigation, and controls in the terminal.
- Read the incident, objective, suggested tools, and outcome checklist in the browser.
- Type `hint` in the terminal to reveal the next hint in the browser. The usual XP penalty and persisted hint progress still apply.
- Type `status` to force a fresh outcome observation. Successful lab commands also refresh progress automatically.
- Type `restart` to rebuild the disposable environment and reset its outcome checks.

In companion mode the terminal intentionally omits the mission narrative,
hint text, detailed progress checklist, and completion explanation. Raw command
output remains there because it is both player feedback and, for some
missions, validation input.

## Lifetime and reconnection

The companion exists only while that `opsquest play --web` process is active.
Reloading or reconnecting from the paired browser restores the latest complete
snapshot without replaying commands. Continuous play reuses the same page as
the route advances to another mission. Quitting or interrupting the CLI closes
the local server; the already-rendered page may remain visible but no longer
receives updates.

## Safety boundary

The server binds only to an ephemeral IPv4 loopback port. It uses one-time
pairing, an HTTP-only same-site cookie, exact Host and Origin checks, restrictive
browser security headers, and no permissive CORS policy.

The browser receives a presentation projection rather than the mission's raw
setup or validation structures. It has no API for submitting command text,
selecting Docker resources, mutating the profile, or declaring completion.
`game.Session` still evaluates observable outcomes, closes the active
environment, and saves the profile before publishing a completed state.

The companion adds no hosted service, account, external network requirement,
or third-party production dependency.
36 changes: 34 additions & 2 deletions docs/technical/architecture.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,17 @@ OpsQuest separates product flow, declarative mission content, isolated execution

Editable source: [`system-landscape.excalidraw`](diagrams/system-landscape.excalidraw)

There are three materially different state domains:
The landscape diagram focuses on the command-execution and persistence path;
the optional read-only companion projection is detailed separately below.

There are four materially different state domains:

| Domain | Owner | Lifetime | Host interaction |
| --- | --- | --- | --- |
| Mission definition | `internal/mission` | Embedded in the binary | Reads embedded JSON only |
| Mission attempt | `internal/sandbox` or `internal/dockerlab` | One attempt or restart | In-memory for Linux; exact labeled Docker resources for Docker labs |
| Player progress | `internal/profile` | Across processes | Atomic `profile.json` replacement in the platform config directory |
| Companion projection | `internal/webapp` | One `play --web` process | Loopback HTTP only; no durable writes or command execution |

Linux mission state is never persisted. Completing, quitting, switching, or restarting a Linux mission discards its virtual filesystem, environment, processes, archives, and history. Hints, command practice, completions, XP, and achievements belong to the profile instead.

Expand All @@ -38,6 +42,7 @@ Editable source: [`component-architecture.mmd`](diagrams/component-architecture.
| `internal/mission` | Strict schema, embedded catalog, tracks, worlds, and defensive copies | Content stays declarative; rejects invalid catalogs before play starts |
| `internal/sandbox` | Teaching-shell lexer, parser, dispatcher, virtual filesystem/processes/archives | Never invokes a host shell or accesses host paths |
| `internal/dockerlab` | Optional Docker-compatible engine availability, typed teaching actions, fixtures, observations, and cleanup | Only this adapter may launch the Docker CLI, using constructed arguments and tracked resource IDs |
| `internal/webapp` | Optional embedded mission companion, one-time pairing, current snapshot, and server-sent events | Serves a read-only projection on IPv4 loopback; accepts no command or completion input |
| `internal/profile` | Versioned progress model and atomic JSON storage | The only normal durable state written by the application |
| `internal/ui` | Terminal capability detection and semantic ANSI roles | Styling stays out of execution results and validators |
| `internal/buildinfo` | Release-managed executable version | Updated by release automation |
Expand All @@ -54,7 +59,7 @@ OrbStack does not introduce a second execution backend. On macOS it is selected
2. Load and validate every embedded mission with `mission.LoadCatalog`.
3. Resolve the profile store with `profile.DefaultStore`.
4. Construct the CLI with standard streams, catalog, store, and the combined environment factory.
5. Dispatch `os.Args[1:]` through `cli.App.Run`.
5. Dispatch `os.Args[1:]` through `cli.App.Run`; `play --web` additionally starts an ephemeral loopback companion for that command.
6. Render any returned error once at the process boundary and exit non-zero.

Catalog loading is fail-fast. JSON decoding disallows unknown fields, mission numbers must be globally contiguous, IDs and numbers must be unique, setup and validation fields must match the selected environment, and campaigns must remain contiguous within each track.
Expand All @@ -76,6 +81,7 @@ The runtime flow is:
7. If any outcome is missing, the same attempt continues. `status` describes satisfied and missing outcomes without prescribing a command sequence.
8. Once every outcome passes, the session closes the environment **before** awarding XP. This prevents a Docker attempt with unresolved cleanup from being recorded as complete.
9. First completion records hint-adjusted XP and achievements. Replays retain the original completion and award no duplicate XP.
10. When a companion reporter is configured, the session publishes a sanitized complete snapshot after start, hints, observations, restart, pause, and persisted completion. Publishing never participates in the validation decision.

`restart` closes the active environment and creates a new one from the same declarative mission. Mission switching returns a validated mission ID to the CLI, which starts a separate fresh session.

Expand Down Expand Up @@ -104,6 +110,31 @@ The contract carries several design decisions:

The simulated adapter wraps one `sandbox.Sandbox`. The Docker adapter maps logical mission aliases to generated names and exact container IDs, so neither the session nor player sees engine-owned resource identifiers.

## Web companion contract

`play --web` starts `internal/webapp` on `127.0.0.1` with an ephemeral port.
The printed one-time pairing URL establishes an HTTP-only, same-site browser
session, then the browser loads static HTML, CSS, and JavaScript embedded in the
same executable. The current state is available as a JSON snapshot, and
subsequent updates use Server-Sent Events. A reconnect receives the latest
complete snapshot rather than querying the environment or replaying commands.

`game.AttemptSnapshot` is a dedicated public projection. It contains narrative,
suggested tool names, revealed hints, described outcome status, placement,
reward information, and completion feedback. It does not contain mission setup
objects, condition structs, player command text, terminal output, Docker IDs,
or a profile mutation capability.

The direction of authority is one-way:

1. Terminal input reaches `game.Session` and the selected `Environment`.
2. `game.Session` evaluates outcomes and persists progress exactly as in terminal-only play.
3. Only after those decisions does it publish a presentation snapshot.
4. `internal/webapp` retains and broadcasts the snapshot without calling back into the session.

Completion is not published until environment cleanup and profile persistence
both succeed. This retains the existing Docker cleanup-before-XP invariant.

## Mission and world model

Each JSON file decodes into a `mission.Mission` containing narrative fields, suggested tools, hints, setup, validation conditions, and rewards. The catalog sorts missions by stable global number and builds lookup indexes by ID and number.
Expand Down Expand Up @@ -135,6 +166,7 @@ Changing one of these requires explicit compatibility reasoning even when the Go
| Add a validation outcome | `internal/mission`, `internal/game`, environment adapters | Strict schema checks and canonical mission coverage |
| Change attempt controls or progression | `internal/game` | `internal/cli` routes and profile persistence |
| Add a top-level command | `internal/cli` | Smoke test and README examples |
| Change the browser companion or projection | `internal/webapp`, `internal/game` | Pairing/HTTP isolation, lifecycle ordering, CLI fallback, and race tests |
| Expand Docker teaching behavior | `internal/dockerlab` | Parser rejection tests, ownership cleanup, real integration gate |
| Change colors or terminal presentation | `internal/ui` | CLI/session rendering tests and non-terminal output |
| Change durable progress | `internal/profile` | Migration/compatibility tests and atomic-write behavior |
Expand Down
1 change: 1 addition & 0 deletions docs/technical/contributing.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ Start with the repository [`AGENTS.md`](https://github.com/Aleksandergreg/go-cli
| Mission schema, catalog, worlds, or JSON content | `internal/mission` |
| Teaching-shell parsing, files, processes, archives, or commands | `internal/sandbox` |
| Optional Docker actions, fixtures, observation, or cleanup | `internal/dockerlab` |
| Local browser companion, pairing, HTTP projection, or assets | `internal/webapp` |
| Durable progress and migrations | `internal/profile` |
| Terminal color policy | `internal/ui` |

Expand Down
5 changes: 4 additions & 1 deletion docs/technical/diagrams/component-architecture.mmd
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ flowchart TB
PROFILE["internal/profile<br/>progress and atomic storage"]
SANDBOX["internal/sandbox<br/>in-memory teaching shell"]
DOCKER["internal/dockerlab<br/>typed Docker adapter"]
WEB["internal/webapp<br/>loopback companion"]
UI["internal/ui<br/>terminal-aware styling"]
BUILD["internal/buildinfo<br/>release version"]

Expand All @@ -22,6 +23,7 @@ flowchart TB
CLI --> MISSION
CLI --> PROFILE
CLI --> UI
CLI --> WEB

GAME --> MISSION
GAME --> PROFILE
Expand All @@ -31,6 +33,7 @@ flowchart TB
SANDBOX --> MISSION
DOCKER --> GAME
DOCKER --> MISSION
WEB --> GAME

classDef entry fill:#172554,color:#ffffff,stroke:#172554,stroke-width:2px
classDef orchestration fill:#dbeafe,color:#172554,stroke:#2563eb,stroke-width:2px
Expand All @@ -42,4 +45,4 @@ flowchart TB
class CLI,GAME orchestration
class MISSION,PROFILE domain
class SANDBOX,DOCKER adapter
class UI,BUILD support
class UI,BUILD,WEB support
Loading