ctf-flightdeck 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (141) hide show
  1. ctf_flightdeck-0.1.0/LICENSE +21 -0
  2. ctf_flightdeck-0.1.0/PKG-INFO +307 -0
  3. ctf_flightdeck-0.1.0/README.md +275 -0
  4. ctf_flightdeck-0.1.0/pyproject.toml +63 -0
  5. ctf_flightdeck-0.1.0/pyproject.toml.orig +59 -0
  6. ctf_flightdeck-0.1.0/src/ctf_flightdeck/__init__.py +8 -0
  7. ctf_flightdeck-0.1.0/src/ctf_flightdeck/__main__.py +3 -0
  8. ctf_flightdeck-0.1.0/src/ctf_flightdeck/agent/__init__.py +1 -0
  9. ctf_flightdeck-0.1.0/src/ctf_flightdeck/agent/anthropic_client.py +631 -0
  10. ctf_flightdeck-0.1.0/src/ctf_flightdeck/agent/client.py +578 -0
  11. ctf_flightdeck-0.1.0/src/ctf_flightdeck/agent/driver.py +876 -0
  12. ctf_flightdeck-0.1.0/src/ctf_flightdeck/agent/external.py +2319 -0
  13. ctf_flightdeck-0.1.0/src/ctf_flightdeck/agent/fallback.py +207 -0
  14. ctf_flightdeck-0.1.0/src/ctf_flightdeck/agent/fastmode.py +105 -0
  15. ctf_flightdeck-0.1.0/src/ctf_flightdeck/agent/l1stage.py +107 -0
  16. ctf_flightdeck-0.1.0/src/ctf_flightdeck/agent/prompts.py +180 -0
  17. ctf_flightdeck-0.1.0/src/ctf_flightdeck/agent/tools.py +724 -0
  18. ctf_flightdeck-0.1.0/src/ctf_flightdeck/agent/workspace.py +1207 -0
  19. ctf_flightdeck-0.1.0/src/ctf_flightdeck/apicheck.py +410 -0
  20. ctf_flightdeck-0.1.0/src/ctf_flightdeck/build.py +855 -0
  21. ctf_flightdeck-0.1.0/src/ctf_flightdeck/build_cli.py +154 -0
  22. ctf_flightdeck-0.1.0/src/ctf_flightdeck/cli.py +741 -0
  23. ctf_flightdeck-0.1.0/src/ctf_flightdeck/clock.py +16 -0
  24. ctf_flightdeck-0.1.0/src/ctf_flightdeck/config.py +896 -0
  25. ctf_flightdeck-0.1.0/src/ctf_flightdeck/console/__init__.py +2 -0
  26. ctf_flightdeck-0.1.0/src/ctf_flightdeck/console/app.py +604 -0
  27. ctf_flightdeck-0.1.0/src/ctf_flightdeck/console/commands.py +128 -0
  28. ctf_flightdeck-0.1.0/src/ctf_flightdeck/console/fleet.py +539 -0
  29. ctf_flightdeck-0.1.0/src/ctf_flightdeck/console/focus.py +243 -0
  30. ctf_flightdeck-0.1.0/src/ctf_flightdeck/console/room.py +85 -0
  31. ctf_flightdeck-0.1.0/src/ctf_flightdeck/console/screens.py +369 -0
  32. ctf_flightdeck-0.1.0/src/ctf_flightdeck/console/takeover.py +54 -0
  33. ctf_flightdeck-0.1.0/src/ctf_flightdeck/console/terminal.py +65 -0
  34. ctf_flightdeck-0.1.0/src/ctf_flightdeck/console/theme.py +64 -0
  35. ctf_flightdeck-0.1.0/src/ctf_flightdeck/console/views.py +876 -0
  36. ctf_flightdeck-0.1.0/src/ctf_flightdeck/console/widgets.py +184 -0
  37. ctf_flightdeck-0.1.0/src/ctf_flightdeck/core/__init__.py +3 -0
  38. ctf_flightdeck-0.1.0/src/ctf_flightdeck/core/attention.py +95 -0
  39. ctf_flightdeck-0.1.0/src/ctf_flightdeck/core/deck.py +1620 -0
  40. ctf_flightdeck-0.1.0/src/ctf_flightdeck/core/driver.py +128 -0
  41. ctf_flightdeck-0.1.0/src/ctf_flightdeck/core/interventions.py +131 -0
  42. ctf_flightdeck-0.1.0/src/ctf_flightdeck/core/model.py +370 -0
  43. ctf_flightdeck-0.1.0/src/ctf_flightdeck/core/summary.py +364 -0
  44. ctf_flightdeck-0.1.0/src/ctf_flightdeck/credentials.py +217 -0
  45. ctf_flightdeck-0.1.0/src/ctf_flightdeck/detect/__init__.py +61 -0
  46. ctf_flightdeck-0.1.0/src/ctf_flightdeck/detect/base.py +137 -0
  47. ctf_flightdeck-0.1.0/src/ctf_flightdeck/detect/detectors.py +545 -0
  48. ctf_flightdeck-0.1.0/src/ctf_flightdeck/detect/hints.py +351 -0
  49. ctf_flightdeck-0.1.0/src/ctf_flightdeck/detect/hints.toml +334 -0
  50. ctf_flightdeck-0.1.0/src/ctf_flightdeck/detect/judge.py +184 -0
  51. ctf_flightdeck-0.1.0/src/ctf_flightdeck/detect/watch.py +256 -0
  52. ctf_flightdeck-0.1.0/src/ctf_flightdeck/eventlog.py +174 -0
  53. ctf_flightdeck-0.1.0/src/ctf_flightdeck/flagformat.py +101 -0
  54. ctf_flightdeck-0.1.0/src/ctf_flightdeck/fmt.py +40 -0
  55. ctf_flightdeck-0.1.0/src/ctf_flightdeck/gaps.py +582 -0
  56. ctf_flightdeck-0.1.0/src/ctf_flightdeck/gaps_cli.py +128 -0
  57. ctf_flightdeck-0.1.0/src/ctf_flightdeck/hardware.py +477 -0
  58. ctf_flightdeck-0.1.0/src/ctf_flightdeck/keys.py +102 -0
  59. ctf_flightdeck-0.1.0/src/ctf_flightdeck/l1/__init__.py +17 -0
  60. ctf_flightdeck-0.1.0/src/ctf_flightdeck/l1/_boot.py +128 -0
  61. ctf_flightdeck-0.1.0/src/ctf_flightdeck/l1/_capture.py +125 -0
  62. ctf_flightdeck-0.1.0/src/ctf_flightdeck/l1/cli.py +297 -0
  63. ctf_flightdeck-0.1.0/src/ctf_flightdeck/l1/container.py +202 -0
  64. ctf_flightdeck-0.1.0/src/ctf_flightdeck/l1/importer.py +349 -0
  65. ctf_flightdeck-0.1.0/src/ctf_flightdeck/l1/manifest.py +509 -0
  66. ctf_flightdeck-0.1.0/src/ctf_flightdeck/l1/result.py +259 -0
  67. ctf_flightdeck-0.1.0/src/ctf_flightdeck/l1/router.py +243 -0
  68. ctf_flightdeck-0.1.0/src/ctf_flightdeck/l1/runner.py +705 -0
  69. ctf_flightdeck-0.1.0/src/ctf_flightdeck/lane/Dockerfile +15 -0
  70. ctf_flightdeck-0.1.0/src/ctf_flightdeck/lane/__init__.py +28 -0
  71. ctf_flightdeck-0.1.0/src/ctf_flightdeck/leftovers.py +1047 -0
  72. ctf_flightdeck-0.1.0/src/ctf_flightdeck/light.py +1041 -0
  73. ctf_flightdeck-0.1.0/src/ctf_flightdeck/localmodel.py +575 -0
  74. ctf_flightdeck-0.1.0/src/ctf_flightdeck/localmodel_cases.jsonl +58 -0
  75. ctf_flightdeck-0.1.0/src/ctf_flightdeck/localmodel_cli.py +521 -0
  76. ctf_flightdeck-0.1.0/src/ctf_flightdeck/mock/__init__.py +1 -0
  77. ctf_flightdeck-0.1.0/src/ctf_flightdeck/mock/scenarios.py +1054 -0
  78. ctf_flightdeck-0.1.0/src/ctf_flightdeck/mock/screenshots.py +229 -0
  79. ctf_flightdeck-0.1.0/src/ctf_flightdeck/mock/script.py +242 -0
  80. ctf_flightdeck-0.1.0/src/ctf_flightdeck/mock/world.py +97 -0
  81. ctf_flightdeck-0.1.0/src/ctf_flightdeck/model_catalog.py +640 -0
  82. ctf_flightdeck-0.1.0/src/ctf_flightdeck/models.toml +229 -0
  83. ctf_flightdeck-0.1.0/src/ctf_flightdeck/models_cli.py +485 -0
  84. ctf_flightdeck-0.1.0/src/ctf_flightdeck/needle.py +156 -0
  85. ctf_flightdeck-0.1.0/src/ctf_flightdeck/platform/__init__.py +9 -0
  86. ctf_flightdeck-0.1.0/src/ctf_flightdeck/platform/ctfd.py +1808 -0
  87. ctf_flightdeck-0.1.0/src/ctf_flightdeck/platform/ctfd_cli.py +114 -0
  88. ctf_flightdeck-0.1.0/src/ctf_flightdeck/platform/manual.py +545 -0
  89. ctf_flightdeck-0.1.0/src/ctf_flightdeck/postflight.py +1515 -0
  90. ctf_flightdeck-0.1.0/src/ctf_flightdeck/postflight_cli.py +97 -0
  91. ctf_flightdeck-0.1.0/src/ctf_flightdeck/practice/README.md +86 -0
  92. ctf_flightdeck-0.1.0/src/ctf_flightdeck/practice/answers.sha256.json +16 -0
  93. ctf_flightdeck-0.1.0/src/ctf_flightdeck/practice/board.json +4 -0
  94. ctf_flightdeck-0.1.0/src/ctf_flightdeck/practice/crab-walk/challenge.json +11 -0
  95. ctf_flightdeck-0.1.0/src/ctf_flightdeck/practice/crab-walk/postcard.txt +8 -0
  96. ctf_flightdeck-0.1.0/src/ctf_flightdeck/practice/fine-print/challenge.json +11 -0
  97. ctf_flightdeck-0.1.0/src/ctf_flightdeck/practice/fine-print/harbour_dawn.png +0 -0
  98. ctf_flightdeck-0.1.0/src/ctf_flightdeck/practice/half-and-half/challenge.json +11 -0
  99. ctf_flightdeck-0.1.0/src/ctf_flightdeck/practice/half-and-half/halves.txt +6 -0
  100. ctf_flightdeck-0.1.0/src/ctf_flightdeck/practice/harbour-memo/challenge.json +11 -0
  101. ctf_flightdeck-0.1.0/src/ctf_flightdeck/practice/harbour-memo/memo.enc +3 -0
  102. ctf_flightdeck-0.1.0/src/ctf_flightdeck/practice/mille-feuille/challenge.json +11 -0
  103. ctf_flightdeck-0.1.0/src/ctf_flightdeck/practice/mille-feuille/layers.txt +1 -0
  104. ctf_flightdeck-0.1.0/src/ctf_flightdeck/practice/nesting-dolls/challenge.json +11 -0
  105. ctf_flightdeck-0.1.0/src/ctf_flightdeck/practice/nesting-dolls/dolls.zip +0 -0
  106. ctf_flightdeck-0.1.0/src/ctf_flightdeck/practice/pancake-stack/challenge.json +12 -0
  107. ctf_flightdeck-0.1.0/src/ctf_flightdeck/practice/pancake-stack/program.bin +0 -0
  108. ctf_flightdeck-0.1.0/src/ctf_flightdeck/practice/pancake-stack/vm.py +85 -0
  109. ctf_flightdeck-0.1.0/src/ctf_flightdeck/practice/postmarked/challenge.json +12 -0
  110. ctf_flightdeck-0.1.0/src/ctf_flightdeck/practice/postmarked/envelope.txt +7 -0
  111. ctf_flightdeck-0.1.0/src/ctf_flightdeck/practice/postmarked/letter.txt +9 -0
  112. ctf_flightdeck-0.1.0/src/ctf_flightdeck/practice/quiet-night/access_log +497 -0
  113. ctf_flightdeck-0.1.0/src/ctf_flightdeck/practice/quiet-night/challenge.json +11 -0
  114. ctf_flightdeck-0.1.0/src/ctf_flightdeck/practice/signal-lamp/challenge.json +11 -0
  115. ctf_flightdeck-0.1.0/src/ctf_flightdeck/practice/signal-lamp/lamp_log.csv +99 -0
  116. ctf_flightdeck-0.1.0/src/ctf_flightdeck/practice/three-wishes/challenge.json +12 -0
  117. ctf_flightdeck-0.1.0/src/ctf_flightdeck/practice/three-wishes/encrypt.py +44 -0
  118. ctf_flightdeck-0.1.0/src/ctf_flightdeck/practice/three-wishes/output.txt +3 -0
  119. ctf_flightdeck-0.1.0/src/ctf_flightdeck/practice/turnstile/challenge.json +11 -0
  120. ctf_flightdeck-0.1.0/src/ctf_flightdeck/practice/turnstile/turnstile.py +35 -0
  121. ctf_flightdeck-0.1.0/src/ctf_flightdeck/practice/welcome-mat/challenge.json +13 -0
  122. ctf_flightdeck-0.1.0/src/ctf_flightdeck/practice/welcome-mat/index.html +22 -0
  123. ctf_flightdeck-0.1.0/src/ctf_flightdeck/practice/welcome-mat/portal.js +42 -0
  124. ctf_flightdeck-0.1.0/src/ctf_flightdeck/practice/welcome-mat/style.css +5 -0
  125. ctf_flightdeck-0.1.0/src/ctf_flightdeck/practice/wrong-jar/challenge.json +11 -0
  126. ctf_flightdeck-0.1.0/src/ctf_flightdeck/practice/wrong-jar/minutes.pdf +0 -0
  127. ctf_flightdeck-0.1.0/src/ctf_flightdeck/practice_cli.py +115 -0
  128. ctf_flightdeck-0.1.0/src/ctf_flightdeck/practice_mode.py +1119 -0
  129. ctf_flightdeck-0.1.0/src/ctf_flightdeck/practice_set.py +16 -0
  130. ctf_flightdeck-0.1.0/src/ctf_flightdeck/preflight.py +853 -0
  131. ctf_flightdeck-0.1.0/src/ctf_flightdeck/preflight_cli.py +131 -0
  132. ctf_flightdeck-0.1.0/src/ctf_flightdeck/pricing.py +42 -0
  133. ctf_flightdeck-0.1.0/src/ctf_flightdeck/run.py +1296 -0
  134. ctf_flightdeck-0.1.0/src/ctf_flightdeck/runrecord.py +514 -0
  135. ctf_flightdeck-0.1.0/src/ctf_flightdeck/setup.py +868 -0
  136. ctf_flightdeck-0.1.0/src/ctf_flightdeck/team.py +892 -0
  137. ctf_flightdeck-0.1.0/src/ctf_flightdeck/team_cli.py +69 -0
  138. ctf_flightdeck-0.1.0/src/ctf_flightdeck/text.py +71 -0
  139. ctf_flightdeck-0.1.0/src/ctf_flightdeck/transcript.py +104 -0
  140. ctf_flightdeck-0.1.0/src/ctf_flightdeck/writeup.py +1136 -0
  141. ctf_flightdeck-0.1.0/src/ctf_flightdeck/writeup_cli.py +256 -0
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 The ctf-flightdeck contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,307 @@
1
+ Metadata-Version: 2.4
2
+ Name: ctf-flightdeck
3
+ Version: 0.1.0
4
+ Summary: A terminal harness for playing CTFs with AI agents, where the player runs the loop.
5
+ Keywords: ctf,capture-the-flag,security,ai-agents,llm,tui,terminal
6
+ Author: Maximus Fisher
7
+ License-Expression: MIT
8
+ License-File: LICENSE
9
+ Classifier: Development Status :: 3 - Alpha
10
+ Classifier: Environment :: Console
11
+ Classifier: Intended Audience :: Information Technology
12
+ Classifier: Operating System :: MacOS
13
+ Classifier: Operating System :: POSIX :: Linux
14
+ Classifier: Programming Language :: Python :: 3
15
+ Classifier: Programming Language :: Python :: 3.12
16
+ Classifier: Programming Language :: Python :: 3.13
17
+ Classifier: Topic :: Security
18
+ Requires-Dist: anthropic>=2.8.0,<3
19
+ Requires-Dist: httpx>=0.28
20
+ Requires-Dist: textual>=8.2.8
21
+ Requires-Dist: typer>=0.29.0
22
+ Requires-Dist: pytest>=9.2.0 ; extra == 'build'
23
+ Requires-Dist: cactus-compute==4.1.0 ; extra == 'cactus'
24
+ Requires-Python: >=3.12
25
+ Project-URL: Homepage, https://github.com/MFisher14/ctf-flightdeck
26
+ Project-URL: Documentation, https://github.com/MFisher14/ctf-flightdeck#readme
27
+ Project-URL: Issues, https://github.com/MFisher14/ctf-flightdeck/issues
28
+ Project-URL: Changelog, https://github.com/MFisher14/ctf-flightdeck/blob/main/CHANGELOG.md
29
+ Provides-Extra: build
30
+ Provides-Extra: cactus
31
+ Description-Content-Type: text/markdown
32
+
33
+ # ctf-flightdeck
34
+
35
+ A terminal harness for playing CTFs with AI agents, where **you** run the loop.
36
+
37
+ flightdeck pulls the board, runs your own no-model solvers first, starts agent lanes in parallel,
38
+ and submits flags, while you watch every lane on one console and step in when one goes the wrong
39
+ way: a hint, a redirect, a fork, a kill, or taking over its shell yourself.
40
+
41
+ ![The fleet view: a board of eight challenges on the left; on the right, six agent lanes sorted by what needs you, with chips for a found flag, a lane that ignores the hint, a web lane heading the wrong way and a live box a teammate holds; under the list, the found flag with 1 to submit it and 0 not to](https://raw.githubusercontent.com/MFisher14/ctf-flightdeck/main/docs/screenshots/fleet.png)
42
+
43
+ *The fleet view, from `flightdeck mock`: scripted lanes on invented challenges. More in the
44
+ [screenshot gallery](docs/SCREENSHOTS.md).*
45
+
46
+ Bring what you have: an API key (Anthropic, OpenRouter, OpenAI or a local model server), a
47
+ coding-agent CLI (Claude Code, Codex), a folder of solver scripts.
48
+
49
+ > **Status: v0.1.0, the first public release (alpha).** The console, agent lanes, your L1
50
+ > solvers, CTFd events, team mode, alerts, practice scoring and post-flight reports are built.
51
+ > Next: a model per small job (0.2.0), then Autopilot. See [`docs/ROADMAP.md`](docs/ROADMAP.md)
52
+ > and [`CHANGELOG.md`](CHANGELOG.md).
53
+
54
+ ## Quickstart
55
+
56
+ Needs Python 3.12+ on macOS or Linux (Windows through WSL), and Docker for the safest setup.
57
+
58
+ ```sh
59
+ pipx install ctf-flightdeck # or: uv tool install ctf-flightdeck (from a clone: uv tool install .)
60
+ flightdeck init # a few questions; writes your config and says what to do next
61
+ flightdeck practice # 14 small practice challenges that ship with flightdeck
62
+ ```
63
+
64
+ `flightdeck` on its own says what to do next on your machine, and `flightdeck doctor` checks
65
+ everything (Python, the config, the key variables, a local model server and its models, the
66
+ light model, Docker, the lane image, your terminal's size) and says how to fix each problem.
67
+ Before the console opens, `play` and `practice` ask the model API whether it takes your key and
68
+ serves every model in your ladder, and stop with what to fix if not. No setup at all:
69
+ `flightdeck mock` opens the console on scripted data and spends nothing, and
70
+ `uvx ctf-flightdeck mock` does it without installing anything.
71
+
72
+ ## Keys and model APIs
73
+
74
+ Your config names the environment variable that holds each key, never the key. flightdeck reads
75
+ the variable from your environment, else from `~/.config/ctf-flightdeck/keys.env` (or the file
76
+ `$FLIGHTDECK_KEYS` names), which must be readable by you only:
77
+
78
+ ```sh
79
+ mkdir -p ~/.config/ctf-flightdeck && touch ~/.config/ctf-flightdeck/keys.env && chmod 600 ~/.config/ctf-flightdeck/keys.env
80
+ # then add lines like: ANTHROPIC_API_KEY=... OPENROUTER_API_KEY=...
81
+ ```
82
+
83
+ A lane's commands get an environment built from an allowlist, so flightdeck hands them none of
84
+ these values (but see `--host` below). A coding-agent CLI (`--agent`) gets its own credential and
85
+ no other, and the commands it runs can read that one, in Docker too: give it a key you can
86
+ revoke, with a low spending limit.
87
+
88
+ With an Anthropic key, `flightdeck init --provider anthropic` writes Claude Haiku, Sonnet and
89
+ Opus 5.5 with their list prices, so the dollar caps work from the start. If `OPENROUTER_API_KEY`
90
+ is set too, it offers OpenRouter as the fallback for a lane whose calls to Anthropic keep
91
+ failing. OpenRouter, OpenAI, Ollama, an MLX server or any OpenAI-compatible API work as well.
92
+ For Ollama or an MLX server, `init` suggests a model, and for any server on your machine it lets
93
+ a lane wait up to 10 minutes for a reply of up to 8192 tokens, since a local model can take
94
+ minutes to answer. The fallback, what happens when a model declines, fast mode, time and reply
95
+ limits, and prices: [`docs/MODEL-APIS.md`](docs/MODEL-APIS.md).
96
+
97
+ ## Models
98
+
99
+ Which models fit your machine? `flightdeck models` looks at it (chip, memory, NVIDIA GPUs, and
100
+ the model servers running on it; it contacts nothing but 127.0.0.1) and lists, for lanes and for
101
+ the light model, the models that fit and why: open-weight models on your machine first where
102
+ they run well, then model APIs. `flightdeck models qwen3.5-9b` prints how to get one and the
103
+ config lines that use it. It recommends; it never changes your config. How the models did:
104
+ [`docs/MODELS.md`](docs/MODELS.md).
105
+
106
+ ## Play a board
107
+
108
+ ```sh
109
+ flightdeck play ~/ctf/some-board --max-lanes 4
110
+ ```
111
+
112
+ A board is a folder of challenge folders, each with a `challenge.json` and its handout files
113
+ (format in [`docs/ARCHITECTURE.md`](docs/ARCHITECTURE.md)). Lanes start cheapest-first; the rest
114
+ queue. Every lane is a model conversation, capped per lane by `token_cap` and `dollar_cap` (an
115
+ alert at 80%, your approval at 100%).
116
+
117
+ A flag a lane finds waits for you: `a` or `1` submits it, `d` or `0` declines. Under auto submit
118
+ (`submit = "auto"`, or `play --submit auto`) a flag the model submits goes at once, unless it is
119
+ written in the challenge's own description or handout files (often a decoy): that one still
120
+ waits for you. A flag found wrong is never asked about again.
121
+
122
+ The console raises an alert when a lane spends big on a low-value challenge, ignores what the
123
+ description hints at, heads the wrong way on web, loops or stalls. Lanes that need you sort
124
+ first, and the line under the list says why and numbers the answers: one key answers.
125
+
126
+ ![A wave of 43 challenges: 16 lanes run and 33 wait in the queue. The selected lane ignores the description's hint of slow-scan TV, and the line under the list offers 1 to redirect it to the hinted technique and 2 to send a hint](https://raw.githubusercontent.com/MFisher14/ctf-flightdeck/main/docs/screenshots/alert-hint.png)
127
+
128
+ *An alert with its quick answers, from `flightdeck mock` (invented challenges).*
129
+
130
+ **Enter** opens a lane full screen: its transcript, the files it changed with diffs, and a chat
131
+ box (plain text is a message to the lane; `/redirect`, `/approve`, `/diff` and the rest are
132
+ commands; `esc` goes back).
133
+
134
+ ![One lane full screen: its commands and their output, a needs-you box with two numbered answers, the files it changed and the diff of one of them, and the chat box](https://raw.githubusercontent.com/MFisher14/ctf-flightdeck/main/docs/screenshots/lane.png)
135
+
136
+ *A lane full screen, from `flightdeck mock` (invented challenges).*
137
+
138
+ `t` takes over a lane's shell (its container in Docker): exit it to hand the lane back, and the
139
+ lane hears what you changed. The shell starts clean: it reads no startup file, and with `--host`
140
+ its HOME is a new folder only you can read, outside the workspace, so nothing the lane wrote runs
141
+ before you type. `e` escalates a lane to a stronger model with a summary of what it did, `f`
142
+ forks it, `?` shows every key.
143
+
144
+ When a model API failure would hit every lane (a refused key, an unknown model, no credits), the
145
+ queue is held and the header says why. Once you have fixed it, `/unhold` starts the queued lanes
146
+ (a key or model id changed in your config needs a new run).
147
+
148
+ Docker lanes get no network unless their challenge has a live instance (a web origin, or a TCP
149
+ service like `nc host port`: `tcp://host:port` in a challenge's `urls`). `--host` runs lane
150
+ commands as you, on this machine: a lane can read your files, your keys file and flightdeck's
151
+ own environment, and send what it reads to its model's API, which nothing can take back. The
152
+ run log, the console, transcripts, reports and writeup drafts show a key's name but not its
153
+ value (see [After a run](#after-a-run)), but that is cleanup, not a fence: use Docker, a
154
+ disposable VM, or a separate low-limit key.
155
+
156
+ Every run writes an append-only `events.jsonl` and each lane's transcript; a board with answers
157
+ (like the practice set) ends with a scorecard. `q` asks first while lanes are open, and a run
158
+ cannot be resumed. If flightdeck itself is killed (kill -9, a power loss), what its lanes started
159
+ can keep running: the next `play` or `practice` removes it and says what it removed,
160
+ `flightdeck doctor` lists it and `flightdeck doctor --clean` removes it.
161
+
162
+ ## Play an event
163
+
164
+ Write down what the event's rules allow, check it, and play by it:
165
+
166
+ ```sh
167
+ flightdeck preflight --example > event.toml # then edit it from the event's rules
168
+ flightdeck preflight event.toml # what flightdeck will do at each stage, and any problem
169
+ export CTFD_TOKEN=... # the variable event.toml names (never the token itself)
170
+ flightdeck play ~/ctf/event-board --event event.toml
171
+ ```
172
+
173
+ With a CTFd event, flightdeck pulls the board into that folder, adds new waves as they appear,
174
+ submits the flags you approve through CTFd and follows your team's solves. A slow CTFd never
175
+ holds up the console: lanes carry on while their flags are on their way (the fleet shows
176
+ `sending`). A challenge whose tries CTFd counts always waits for you, under `auto` too, and each
177
+ approval says how many tries are left. Each stage (pull, L1, agents, submit, live instance) runs
178
+ as the profile's policy says: `auto`, `confirm` (you say yes first), `manual` or `off`, never
179
+ looser than the rules. Under `confirm` for live instances, a lane asks before it may reach its
180
+ challenge's instance and has no network until you allow it.
181
+
182
+ Team mode shares claims, live-box holds and solves through a folder your teammates sync (a network
183
+ drive, Syncthing): `flightdeck team init DIR` once, then `--team-dir DIR`. Your lanes never work a
184
+ challenge a teammate solved.
185
+
186
+ `--agent claude` (or `codex`) runs lanes as that coding-agent CLI, with its own login and models
187
+ (`[agents]` in your config), behind the same controls. Check it first with
188
+ `flightdeck doctor --agent claude`. A CLI says what a turn cost only when the turn ends, so its
189
+ lane's cost ends in `…` until then; codex reports no cost at all, so give its models a
190
+ `[prices."<model>"]` table for their lanes to show dollars.
191
+
192
+ ## Your solvers first (L1)
193
+
194
+ Point flightdeck at a folder of your no-model solvers and each of flightdeck's own lanes runs the
195
+ ones that fit its challenge before its first model call (coding-agent lanes do not use the pack
196
+ yet), so a solve can cost no tokens at all. A flag an engine returns as its result waits for your
197
+ approval, or under auto submit goes to the platform at once; a flag that is only a match in what
198
+ an engine printed (a plain script that `l1 import` took in, say) always waits for you. When lanes
199
+ run in Docker, so do your solvers: each in a container of the lane image, your pack mounted
200
+ read-only, with no network unless the live-instance policy is `auto` and the challenge has an
201
+ instance. With `--host` they run on this machine, as you.
202
+
203
+ ```sh
204
+ flightdeck l1 import ~/ctf/solvers # manifests for a folder of plain scripts (the scripts are never changed)
205
+ flightdeck l1 route ~/ctf/solvers ~/ctf/some-board/crab-walk # which engines a challenge reaches, and why (or why not)
206
+ flightdeck play ~/ctf/some-board --pack ~/ctf/solvers # or set [l1] pack in your config
207
+ ```
208
+
209
+ ## After a run
210
+
211
+ ```sh
212
+ flightdeck report RUN_DIR # where lanes stalled, which interventions helped, cost per solve
213
+ flightdeck writeups RUN_DIR # a writeup draft per solved challenge (--redact hides the flags)
214
+ flightdeck score RUN_DIR # the run scored against the board's answers
215
+ flightdeck gaps RUN_DIR # the challenges your L1 solvers did not solve, and why
216
+ flightdeck build gap CHALLENGE_DIR --pack ~/ctf/solvers # a new engine stub with a test that must pass
217
+ ```
218
+
219
+ When a run ends, `play` prints its run folder with the report, writeups and gaps commands for it
220
+ (and score, when the board has answers), ready to paste. Writeup drafts and lane transcripts
221
+ take out what looks like a key or token, with or without `--redact`: the value of an upper-case
222
+ `NAME=value` or `NAME: value` whose name holds KEY, TOKEN, SECRET, PASSWORD, PASSWD, COOKIE or
223
+ AUTH, of an Authorization or Cookie header, and of a key with a well-known prefix (`sk-ant-`,
224
+ `ghp_` and the like) becomes `[redacted]`. Anything else stays, lower-case names included (a
225
+ `password` is everyday CTF content), so read a draft before you share it.
226
+
227
+ ## Light model
228
+
229
+ A few small jobs can use a small model. Today that is the hint judge: when a lane seems to ignore
230
+ what its description hints at, the alert shows at once, and the light model checks the lane's
231
+ steps; if it says the lane is using the technique after all, the alert comes down. By default
232
+ there is no light model, only the rules. Five choices, in `[light]`:
233
+
234
+ - `rules`: no model (the default).
235
+ - `openai`: any OpenAI-compatible server. **Recommended: an open-weight model on your own
236
+ machine**, served by Ollama (`http://127.0.0.1:11434/v1`) or an MLX server
237
+ (`http://127.0.0.1:8080/v1`), with `key_env = ""`. No provider's safety classifier stands
238
+ between it and security work, and it costs nothing. flightdeck asks a model on your machine to
239
+ answer without thinking first (`thinking = false`, the default there): one that reasons first
240
+ spends a small job's few hundred tokens thinking and answers nothing.
241
+ - `anthropic`: a Claude model (`claude-haiku-5-5`, say).
242
+ - `cactus`: Cactus Needle, on this machine (the `[cactus]` extra).
243
+ - `decider`: Strands Decider, a decision model, served on your own machine. It writes no text: it
244
+ picks one of the options and says how confident it is. An answer below `threshold` (0.9 by
245
+ default; its README says answers at 0.9 or more are right about 95% of the time on short tasks
246
+ it has never seen) is unsure, and the rules' answer stands. No key, and nothing to pay.
247
+ `flightdeck init --light decider` writes it and says how to start its server:
248
+ `pip install strands-decider`, then
249
+ `strands-decider serve StrandsAgents/strands-decider-2B-qwen3.5-v1-2610 --port 8000` (on Apple
250
+ silicon add `--device mlx`, with its `mlx` extra: `pip install "strands-decider[mlx]>=0.3"`).
251
+
252
+ Measure one before you switch it on: which models to try, and how they did, is in
253
+ [`docs/MODELS.md`](docs/MODELS.md). `eval-local` decides:
254
+
255
+ ```sh
256
+ flightdeck eval-local --engine openai --base-url http://127.0.0.1:11434/v1 --model <model> \
257
+ --compare anthropic:claude-haiku-5-5 --compare cactus
258
+ flightdeck eval-local --engine decider --base-url http://127.0.0.1:8000 # or a column: --compare decider@URL
259
+ flightdeck init --light ollama --light-model <model> # or edit [light]; at a terminal, init suggests a model
260
+ ```
261
+
262
+ eval-local runs each engine and the rules over the bundled cases (naming the hinted technique,
263
+ judging a lane, summarising one) and prints them side by side: accuracy, refusals and failures,
264
+ time and cost per job. A paid API is announced first with its number of calls: one per case,
265
+ never a retry. `--thinking on|off` says whether a model on an OpenAI-compatible server may think
266
+ first; by default it is off on your machine and on elsewhere, unless your `[light] thinking`
267
+ says otherwise for that server. The decider's unsure answers get a row of their own and count
268
+ neither right nor wrong, so its accuracy says how often an answer it was sure of is right; its
269
+ summary cases show as n/a, since a decision model writes no text.
270
+
271
+ The light model never holds anything up. When it refuses, fails, times out, answers outside the
272
+ options or (the decider) is unsure, the rules' answer stands, and the run says so once. A refused
273
+ request is never sent on to another model or provider: you see the refusal and decide.
274
+
275
+ ## Try the mock
276
+
277
+ ```sh
278
+ flightdeck mock # the default scenario: every alert, then a 43-challenge wave
279
+ flightdeck mock --scenario wave # also: sstv, overthinking, web, team, flag
280
+ flightdeck mock --screenshot DIR # headless: SVGs of the key moments
281
+ ```
282
+
283
+ The fleet's keys act on the selected lane: `p` pause, `h` hint, `r` redirect, `f` fork, `k` kill,
284
+ `e` escalate, `t` take over, `a` approve, `d` decline, `1`-`9`/`0` quick answers, `c` claim
285
+ (team), `/` commands, `s` sort, `?` help, `q` quit; **Enter** opens the lane full screen. In the
286
+ mock, `space` freezes the clock and `.` steps it. All challenges, teammates and flags there are
287
+ invented.
288
+
289
+ ## Optional extras
290
+
291
+ - `ctf-flightdeck[cactus]`: Cactus Needle, a small on-device model, one of the light model's
292
+ choices (`flightdeck eval-local --engine cactus` measures it against the rules first). Its
293
+ telemetry is turned off. Nothing uses it unless you ask.
294
+ - `ctf-flightdeck[build]`: pytest, for `flightdeck build check`.
295
+
296
+ ## Docs
297
+
298
+ [Vision](docs/VISION.md) · [Decisions](docs/DECISIONS.md) · [Roadmap](docs/ROADMAP.md) ·
299
+ [Console spec](docs/CONSOLE-SPEC.md) · [Architecture](docs/ARCHITECTURE.md) ·
300
+ [Model APIs](docs/MODEL-APIS.md) · [Models](docs/MODELS.md) ·
301
+ [Screenshots](docs/SCREENSHOTS.md) · [Changelog](CHANGELOG.md)
302
+
303
+ Development: `uv run pytest`. `ctfdeck` is an alias for `flightdeck`.
304
+
305
+ ## License
306
+
307
+ MIT. See [LICENSE](LICENSE).
@@ -0,0 +1,275 @@
1
+ # ctf-flightdeck
2
+
3
+ A terminal harness for playing CTFs with AI agents, where **you** run the loop.
4
+
5
+ flightdeck pulls the board, runs your own no-model solvers first, starts agent lanes in parallel,
6
+ and submits flags, while you watch every lane on one console and step in when one goes the wrong
7
+ way: a hint, a redirect, a fork, a kill, or taking over its shell yourself.
8
+
9
+ ![The fleet view: a board of eight challenges on the left; on the right, six agent lanes sorted by what needs you, with chips for a found flag, a lane that ignores the hint, a web lane heading the wrong way and a live box a teammate holds; under the list, the found flag with 1 to submit it and 0 not to](https://raw.githubusercontent.com/MFisher14/ctf-flightdeck/main/docs/screenshots/fleet.png)
10
+
11
+ *The fleet view, from `flightdeck mock`: scripted lanes on invented challenges. More in the
12
+ [screenshot gallery](docs/SCREENSHOTS.md).*
13
+
14
+ Bring what you have: an API key (Anthropic, OpenRouter, OpenAI or a local model server), a
15
+ coding-agent CLI (Claude Code, Codex), a folder of solver scripts.
16
+
17
+ > **Status: v0.1.0, the first public release (alpha).** The console, agent lanes, your L1
18
+ > solvers, CTFd events, team mode, alerts, practice scoring and post-flight reports are built.
19
+ > Next: a model per small job (0.2.0), then Autopilot. See [`docs/ROADMAP.md`](docs/ROADMAP.md)
20
+ > and [`CHANGELOG.md`](CHANGELOG.md).
21
+
22
+ ## Quickstart
23
+
24
+ Needs Python 3.12+ on macOS or Linux (Windows through WSL), and Docker for the safest setup.
25
+
26
+ ```sh
27
+ pipx install ctf-flightdeck # or: uv tool install ctf-flightdeck (from a clone: uv tool install .)
28
+ flightdeck init # a few questions; writes your config and says what to do next
29
+ flightdeck practice # 14 small practice challenges that ship with flightdeck
30
+ ```
31
+
32
+ `flightdeck` on its own says what to do next on your machine, and `flightdeck doctor` checks
33
+ everything (Python, the config, the key variables, a local model server and its models, the
34
+ light model, Docker, the lane image, your terminal's size) and says how to fix each problem.
35
+ Before the console opens, `play` and `practice` ask the model API whether it takes your key and
36
+ serves every model in your ladder, and stop with what to fix if not. No setup at all:
37
+ `flightdeck mock` opens the console on scripted data and spends nothing, and
38
+ `uvx ctf-flightdeck mock` does it without installing anything.
39
+
40
+ ## Keys and model APIs
41
+
42
+ Your config names the environment variable that holds each key, never the key. flightdeck reads
43
+ the variable from your environment, else from `~/.config/ctf-flightdeck/keys.env` (or the file
44
+ `$FLIGHTDECK_KEYS` names), which must be readable by you only:
45
+
46
+ ```sh
47
+ mkdir -p ~/.config/ctf-flightdeck && touch ~/.config/ctf-flightdeck/keys.env && chmod 600 ~/.config/ctf-flightdeck/keys.env
48
+ # then add lines like: ANTHROPIC_API_KEY=... OPENROUTER_API_KEY=...
49
+ ```
50
+
51
+ A lane's commands get an environment built from an allowlist, so flightdeck hands them none of
52
+ these values (but see `--host` below). A coding-agent CLI (`--agent`) gets its own credential and
53
+ no other, and the commands it runs can read that one, in Docker too: give it a key you can
54
+ revoke, with a low spending limit.
55
+
56
+ With an Anthropic key, `flightdeck init --provider anthropic` writes Claude Haiku, Sonnet and
57
+ Opus 5.5 with their list prices, so the dollar caps work from the start. If `OPENROUTER_API_KEY`
58
+ is set too, it offers OpenRouter as the fallback for a lane whose calls to Anthropic keep
59
+ failing. OpenRouter, OpenAI, Ollama, an MLX server or any OpenAI-compatible API work as well.
60
+ For Ollama or an MLX server, `init` suggests a model, and for any server on your machine it lets
61
+ a lane wait up to 10 minutes for a reply of up to 8192 tokens, since a local model can take
62
+ minutes to answer. The fallback, what happens when a model declines, fast mode, time and reply
63
+ limits, and prices: [`docs/MODEL-APIS.md`](docs/MODEL-APIS.md).
64
+
65
+ ## Models
66
+
67
+ Which models fit your machine? `flightdeck models` looks at it (chip, memory, NVIDIA GPUs, and
68
+ the model servers running on it; it contacts nothing but 127.0.0.1) and lists, for lanes and for
69
+ the light model, the models that fit and why: open-weight models on your machine first where
70
+ they run well, then model APIs. `flightdeck models qwen3.5-9b` prints how to get one and the
71
+ config lines that use it. It recommends; it never changes your config. How the models did:
72
+ [`docs/MODELS.md`](docs/MODELS.md).
73
+
74
+ ## Play a board
75
+
76
+ ```sh
77
+ flightdeck play ~/ctf/some-board --max-lanes 4
78
+ ```
79
+
80
+ A board is a folder of challenge folders, each with a `challenge.json` and its handout files
81
+ (format in [`docs/ARCHITECTURE.md`](docs/ARCHITECTURE.md)). Lanes start cheapest-first; the rest
82
+ queue. Every lane is a model conversation, capped per lane by `token_cap` and `dollar_cap` (an
83
+ alert at 80%, your approval at 100%).
84
+
85
+ A flag a lane finds waits for you: `a` or `1` submits it, `d` or `0` declines. Under auto submit
86
+ (`submit = "auto"`, or `play --submit auto`) a flag the model submits goes at once, unless it is
87
+ written in the challenge's own description or handout files (often a decoy): that one still
88
+ waits for you. A flag found wrong is never asked about again.
89
+
90
+ The console raises an alert when a lane spends big on a low-value challenge, ignores what the
91
+ description hints at, heads the wrong way on web, loops or stalls. Lanes that need you sort
92
+ first, and the line under the list says why and numbers the answers: one key answers.
93
+
94
+ ![A wave of 43 challenges: 16 lanes run and 33 wait in the queue. The selected lane ignores the description's hint of slow-scan TV, and the line under the list offers 1 to redirect it to the hinted technique and 2 to send a hint](https://raw.githubusercontent.com/MFisher14/ctf-flightdeck/main/docs/screenshots/alert-hint.png)
95
+
96
+ *An alert with its quick answers, from `flightdeck mock` (invented challenges).*
97
+
98
+ **Enter** opens a lane full screen: its transcript, the files it changed with diffs, and a chat
99
+ box (plain text is a message to the lane; `/redirect`, `/approve`, `/diff` and the rest are
100
+ commands; `esc` goes back).
101
+
102
+ ![One lane full screen: its commands and their output, a needs-you box with two numbered answers, the files it changed and the diff of one of them, and the chat box](https://raw.githubusercontent.com/MFisher14/ctf-flightdeck/main/docs/screenshots/lane.png)
103
+
104
+ *A lane full screen, from `flightdeck mock` (invented challenges).*
105
+
106
+ `t` takes over a lane's shell (its container in Docker): exit it to hand the lane back, and the
107
+ lane hears what you changed. The shell starts clean: it reads no startup file, and with `--host`
108
+ its HOME is a new folder only you can read, outside the workspace, so nothing the lane wrote runs
109
+ before you type. `e` escalates a lane to a stronger model with a summary of what it did, `f`
110
+ forks it, `?` shows every key.
111
+
112
+ When a model API failure would hit every lane (a refused key, an unknown model, no credits), the
113
+ queue is held and the header says why. Once you have fixed it, `/unhold` starts the queued lanes
114
+ (a key or model id changed in your config needs a new run).
115
+
116
+ Docker lanes get no network unless their challenge has a live instance (a web origin, or a TCP
117
+ service like `nc host port`: `tcp://host:port` in a challenge's `urls`). `--host` runs lane
118
+ commands as you, on this machine: a lane can read your files, your keys file and flightdeck's
119
+ own environment, and send what it reads to its model's API, which nothing can take back. The
120
+ run log, the console, transcripts, reports and writeup drafts show a key's name but not its
121
+ value (see [After a run](#after-a-run)), but that is cleanup, not a fence: use Docker, a
122
+ disposable VM, or a separate low-limit key.
123
+
124
+ Every run writes an append-only `events.jsonl` and each lane's transcript; a board with answers
125
+ (like the practice set) ends with a scorecard. `q` asks first while lanes are open, and a run
126
+ cannot be resumed. If flightdeck itself is killed (kill -9, a power loss), what its lanes started
127
+ can keep running: the next `play` or `practice` removes it and says what it removed,
128
+ `flightdeck doctor` lists it and `flightdeck doctor --clean` removes it.
129
+
130
+ ## Play an event
131
+
132
+ Write down what the event's rules allow, check it, and play by it:
133
+
134
+ ```sh
135
+ flightdeck preflight --example > event.toml # then edit it from the event's rules
136
+ flightdeck preflight event.toml # what flightdeck will do at each stage, and any problem
137
+ export CTFD_TOKEN=... # the variable event.toml names (never the token itself)
138
+ flightdeck play ~/ctf/event-board --event event.toml
139
+ ```
140
+
141
+ With a CTFd event, flightdeck pulls the board into that folder, adds new waves as they appear,
142
+ submits the flags you approve through CTFd and follows your team's solves. A slow CTFd never
143
+ holds up the console: lanes carry on while their flags are on their way (the fleet shows
144
+ `sending`). A challenge whose tries CTFd counts always waits for you, under `auto` too, and each
145
+ approval says how many tries are left. Each stage (pull, L1, agents, submit, live instance) runs
146
+ as the profile's policy says: `auto`, `confirm` (you say yes first), `manual` or `off`, never
147
+ looser than the rules. Under `confirm` for live instances, a lane asks before it may reach its
148
+ challenge's instance and has no network until you allow it.
149
+
150
+ Team mode shares claims, live-box holds and solves through a folder your teammates sync (a network
151
+ drive, Syncthing): `flightdeck team init DIR` once, then `--team-dir DIR`. Your lanes never work a
152
+ challenge a teammate solved.
153
+
154
+ `--agent claude` (or `codex`) runs lanes as that coding-agent CLI, with its own login and models
155
+ (`[agents]` in your config), behind the same controls. Check it first with
156
+ `flightdeck doctor --agent claude`. A CLI says what a turn cost only when the turn ends, so its
157
+ lane's cost ends in `…` until then; codex reports no cost at all, so give its models a
158
+ `[prices."<model>"]` table for their lanes to show dollars.
159
+
160
+ ## Your solvers first (L1)
161
+
162
+ Point flightdeck at a folder of your no-model solvers and each of flightdeck's own lanes runs the
163
+ ones that fit its challenge before its first model call (coding-agent lanes do not use the pack
164
+ yet), so a solve can cost no tokens at all. A flag an engine returns as its result waits for your
165
+ approval, or under auto submit goes to the platform at once; a flag that is only a match in what
166
+ an engine printed (a plain script that `l1 import` took in, say) always waits for you. When lanes
167
+ run in Docker, so do your solvers: each in a container of the lane image, your pack mounted
168
+ read-only, with no network unless the live-instance policy is `auto` and the challenge has an
169
+ instance. With `--host` they run on this machine, as you.
170
+
171
+ ```sh
172
+ flightdeck l1 import ~/ctf/solvers # manifests for a folder of plain scripts (the scripts are never changed)
173
+ flightdeck l1 route ~/ctf/solvers ~/ctf/some-board/crab-walk # which engines a challenge reaches, and why (or why not)
174
+ flightdeck play ~/ctf/some-board --pack ~/ctf/solvers # or set [l1] pack in your config
175
+ ```
176
+
177
+ ## After a run
178
+
179
+ ```sh
180
+ flightdeck report RUN_DIR # where lanes stalled, which interventions helped, cost per solve
181
+ flightdeck writeups RUN_DIR # a writeup draft per solved challenge (--redact hides the flags)
182
+ flightdeck score RUN_DIR # the run scored against the board's answers
183
+ flightdeck gaps RUN_DIR # the challenges your L1 solvers did not solve, and why
184
+ flightdeck build gap CHALLENGE_DIR --pack ~/ctf/solvers # a new engine stub with a test that must pass
185
+ ```
186
+
187
+ When a run ends, `play` prints its run folder with the report, writeups and gaps commands for it
188
+ (and score, when the board has answers), ready to paste. Writeup drafts and lane transcripts
189
+ take out what looks like a key or token, with or without `--redact`: the value of an upper-case
190
+ `NAME=value` or `NAME: value` whose name holds KEY, TOKEN, SECRET, PASSWORD, PASSWD, COOKIE or
191
+ AUTH, of an Authorization or Cookie header, and of a key with a well-known prefix (`sk-ant-`,
192
+ `ghp_` and the like) becomes `[redacted]`. Anything else stays, lower-case names included (a
193
+ `password` is everyday CTF content), so read a draft before you share it.
194
+
195
+ ## Light model
196
+
197
+ A few small jobs can use a small model. Today that is the hint judge: when a lane seems to ignore
198
+ what its description hints at, the alert shows at once, and the light model checks the lane's
199
+ steps; if it says the lane is using the technique after all, the alert comes down. By default
200
+ there is no light model, only the rules. Five choices, in `[light]`:
201
+
202
+ - `rules`: no model (the default).
203
+ - `openai`: any OpenAI-compatible server. **Recommended: an open-weight model on your own
204
+ machine**, served by Ollama (`http://127.0.0.1:11434/v1`) or an MLX server
205
+ (`http://127.0.0.1:8080/v1`), with `key_env = ""`. No provider's safety classifier stands
206
+ between it and security work, and it costs nothing. flightdeck asks a model on your machine to
207
+ answer without thinking first (`thinking = false`, the default there): one that reasons first
208
+ spends a small job's few hundred tokens thinking and answers nothing.
209
+ - `anthropic`: a Claude model (`claude-haiku-5-5`, say).
210
+ - `cactus`: Cactus Needle, on this machine (the `[cactus]` extra).
211
+ - `decider`: Strands Decider, a decision model, served on your own machine. It writes no text: it
212
+ picks one of the options and says how confident it is. An answer below `threshold` (0.9 by
213
+ default; its README says answers at 0.9 or more are right about 95% of the time on short tasks
214
+ it has never seen) is unsure, and the rules' answer stands. No key, and nothing to pay.
215
+ `flightdeck init --light decider` writes it and says how to start its server:
216
+ `pip install strands-decider`, then
217
+ `strands-decider serve StrandsAgents/strands-decider-2B-qwen3.5-v1-2610 --port 8000` (on Apple
218
+ silicon add `--device mlx`, with its `mlx` extra: `pip install "strands-decider[mlx]>=0.3"`).
219
+
220
+ Measure one before you switch it on: which models to try, and how they did, is in
221
+ [`docs/MODELS.md`](docs/MODELS.md). `eval-local` decides:
222
+
223
+ ```sh
224
+ flightdeck eval-local --engine openai --base-url http://127.0.0.1:11434/v1 --model <model> \
225
+ --compare anthropic:claude-haiku-5-5 --compare cactus
226
+ flightdeck eval-local --engine decider --base-url http://127.0.0.1:8000 # or a column: --compare decider@URL
227
+ flightdeck init --light ollama --light-model <model> # or edit [light]; at a terminal, init suggests a model
228
+ ```
229
+
230
+ eval-local runs each engine and the rules over the bundled cases (naming the hinted technique,
231
+ judging a lane, summarising one) and prints them side by side: accuracy, refusals and failures,
232
+ time and cost per job. A paid API is announced first with its number of calls: one per case,
233
+ never a retry. `--thinking on|off` says whether a model on an OpenAI-compatible server may think
234
+ first; by default it is off on your machine and on elsewhere, unless your `[light] thinking`
235
+ says otherwise for that server. The decider's unsure answers get a row of their own and count
236
+ neither right nor wrong, so its accuracy says how often an answer it was sure of is right; its
237
+ summary cases show as n/a, since a decision model writes no text.
238
+
239
+ The light model never holds anything up. When it refuses, fails, times out, answers outside the
240
+ options or (the decider) is unsure, the rules' answer stands, and the run says so once. A refused
241
+ request is never sent on to another model or provider: you see the refusal and decide.
242
+
243
+ ## Try the mock
244
+
245
+ ```sh
246
+ flightdeck mock # the default scenario: every alert, then a 43-challenge wave
247
+ flightdeck mock --scenario wave # also: sstv, overthinking, web, team, flag
248
+ flightdeck mock --screenshot DIR # headless: SVGs of the key moments
249
+ ```
250
+
251
+ The fleet's keys act on the selected lane: `p` pause, `h` hint, `r` redirect, `f` fork, `k` kill,
252
+ `e` escalate, `t` take over, `a` approve, `d` decline, `1`-`9`/`0` quick answers, `c` claim
253
+ (team), `/` commands, `s` sort, `?` help, `q` quit; **Enter** opens the lane full screen. In the
254
+ mock, `space` freezes the clock and `.` steps it. All challenges, teammates and flags there are
255
+ invented.
256
+
257
+ ## Optional extras
258
+
259
+ - `ctf-flightdeck[cactus]`: Cactus Needle, a small on-device model, one of the light model's
260
+ choices (`flightdeck eval-local --engine cactus` measures it against the rules first). Its
261
+ telemetry is turned off. Nothing uses it unless you ask.
262
+ - `ctf-flightdeck[build]`: pytest, for `flightdeck build check`.
263
+
264
+ ## Docs
265
+
266
+ [Vision](docs/VISION.md) · [Decisions](docs/DECISIONS.md) · [Roadmap](docs/ROADMAP.md) ·
267
+ [Console spec](docs/CONSOLE-SPEC.md) · [Architecture](docs/ARCHITECTURE.md) ·
268
+ [Model APIs](docs/MODEL-APIS.md) · [Models](docs/MODELS.md) ·
269
+ [Screenshots](docs/SCREENSHOTS.md) · [Changelog](CHANGELOG.md)
270
+
271
+ Development: `uv run pytest`. `ctfdeck` is an alias for `flightdeck`.
272
+
273
+ ## License
274
+
275
+ MIT. See [LICENSE](LICENSE).