An AI-assisted X4: Foundations modding environment for coding agents — Claude Code and
OpenAI Codex first-class, OpenCode best effort, and any other agent through
ADAPTING.md. It handles the tedious, error-prone work of modding — coordinated
multi-file XML edits, porting mods across game versions, validating diff patches, checking how a
mod interacts with everything else you've installed, triaging your mod list, and reading debug
logs — with safety guards, pre-loaded engine knowledge, and a bundled cross-file validator.
Built from hands-on X4 v9.0 mod development. The agent is the brain; this is the environment with the setup prework already done.
Independent fan project. Not affiliated with or endorsed by Egosoft, Anthropic or OpenAI. Ships no game data — you unpack your own legally owned copy locally.
Until v4.0 this was the "X4 Foundations Claude Code Modding Toolkit" (repository
x4-claude-toolkit); GitHub redirects the old address.
A coding agent — Claude Code by Anthropic, Codex by OpenAI, OpenCode, and others — is an AI assistant that runs on your computer. Unlike a chat window, it can read your files, run commands, edit configs, and run scripts — with your permission. For modding, that means it can actually do the mechanical work: write the diff patches, trace the cross-file fan-out, run the validator, and read the debug log back to you.
X4 modding is full of silent failure modes — a diff sel= that matches nothing, a forgotten
file in a multi-file change, a script attribute a game update made mandatory. This toolkit
ships with those footguns already documented and guarded against.
Every agent gets the same knowledge and tools. What differs is how much of the toolkit's guarding it can actually run, and the honest answer is not the same for each:
| Claude Code | Codex | OpenCode (best effort) | Any other agent | |
|---|---|---|---|---|
| Instructions | CLAUDE.md |
AGENTS.md |
AGENTS.md + .opencode/X4-OPENCODE.md |
AGENTS.md (--agent generic) |
| Skills | .claude/skills/ (+ two subagents) |
.agents/skills/ |
.opencode/skills/ |
.agents/skills/ |
| Command + file guards | hooks on Bash, PowerShell, Edit, Write, NotebookEdit; a guard that crashes asks | hooks, only after you review them in /hooks (silently off otherwise); a crash is turned into a deny unless the interpreter cannot start; seven disclosed gaps, listed under Setup |
a plugin plus deny rules, read from OpenCode's docs and source, not measured; CLI only | none, until an adapter passes x4guard conformance (see ADAPTING.md) |
OS-level lock on reference/ (x4refguard, once applied) |
yes | yes | yes | yes |
| How to check it is live | python scripts/x4doctor.py |
the X4 GUARDS LIVE (codex hooks v1) session banner + x4doctor --agent codex |
the X4 GUARDS LIVE system-prompt line + x4doctor --agent opencode |
-- |
Codex on Linux/macOS and the OS lock on Linux/macOS are best effort and not device-tested. The per-agent details are under Setup: What each class of agent gets, What the Codex hooks cannot see, and OpenCode: best effort.
KNOWLEDGEBASE.md— XML schema patterns, the diff-patch idioms, the extension merge/load-order model (what overrides vs unions), the 7.x→9.0 version migration map (thespace=requirement, the dead Lua_Loader, Protected UI Mode), a mechanics interlock map for reasoning about balance ripples, and tool notes. The instruction file (which IS loaded automatically) tells the agent to consult it before making changes.CLAUDE.md(Claude Code) /AGENTS.md(Codex, OpenCode and other agents) — the workflow: diff-patch-first, confidence levels (the agent rates 0–100% and lists assumptions before any change), "vanilla as frame of reference," native-engine-solutions first, and a cognitive-co-pilot stance (surfaces what you didn't ask about).
The flagship tool. X4's hardest bugs come from a change that fans out across many files that must cross-reference each other correctly. No off-the-shelf tool reproduces X4's effective merged tree (base + all DLC + enabled mods) plus its typed cross-reference graph — so this one was built (Python / lxml). It checks:
- Every diff
sel=resolves against the real merged tree — catches the silent no-op. - References resolve — ware / macro /
{page,t}the mod introduces point at real definitions. - Completeness — a new ware/ship/module's footprint vs a vanilla analogue ("did I forget a spot?").
It also ships x4modlist (mod-registry triage via the Nexus API) and an XSD-based
7.x→9.0 migration checker.
cd tools/x4validate
uv run x4validate --paths # where did it resolve everything? read this first
uv run x4validate --tier b /path/to/mymod # validate against your installed modlistx4validate --paths prints where it resolved the game, reference, profile and registry, and
which config file it read. Run it first whenever a result looks impossible — silent
misconfiguration is this tool's worst failure mode, because "found nothing" and "looked in the
wrong place" otherwise print the same way. Full configuration model:
tools/x4validate/README.md.
--tier b merges your active extension set (what the engine loads) in load order, so cross-mod patches resolve
for real — and it catches the failure that looks like success: content another mod removed.
(Dogfooding case: one mod <remove>s a vanilla macro from index/macros.xml and never re-adds
it, orphaning it for six other mods — 415 engine errors that base+DLC validation reports as fine.)
x4validate models three X4 patch rules a naive XML merge gets wrong, each of which otherwise produces a silent no-op or a false alarm:
sel=must match exactly one node (RFC 5261). On multiple matches X4 logsMultiple matching nodes ... Skipping nodeand applies nothing — the patch validates clean and does nothing. 236 such ops were being skipped across one real modlist.if=guards gate an op beforesel=is evaluated, so a guarded no-op is by design, not an error.extensions/<target>/<rel>paths are owned by<target>, not the base game.
A companion for your editor (third-party, new, not part of this toolkit): X4CodeSense by ChemODun (Apache-2.0, v1.0.0 released 2026-10-05) is a VS Code / Open VSX extension with live schema, expression and reference checks for MD, aiscript and diff XML as you type; it also has a CLI (
x4-script-check) and an MCP server. It complements x4validate; nothing here uses or needs it.
An "xEdit for X4": the effective value of every ware/macro/job across base + DLC + all your mods,
with per-attribute provenance (base → modA replace-attr:12 → modB), in a SQLite store you can
query directly. show gives the full record view, attr reads one column across everything
("all missiles and their damage"), who-sets gives the chain (and, for a whole entity, which origins changed its properties), diff-mod shows everything a
mod wins, and dump prints the live merged XML for any path.
x4diff does a semantic XML diff between two versions of a mod, with multi-baseline support —
comparing attributes and element text (shown as @text(), pretty-print whitespace ignored), built for separating your edits from the author's when recovering personal modifications.
Give it --base and it becomes a three-way diff, which answers the question a two-way one
cannot: of these changes, which are the author's and which are drift that upstream has made since?
Two-way, an archived mod against the current release looks like hundreds of edits — measured on one
real 2021 mod, ~440 attribute deltas, of which 15 were the author's; 340 were upstream's own
work and 124 of 135 documents were verbatim copies of the baseline. Porting from that number
means re-applying someone else's changes as if they were yours, and reverting the current release
across most of the tree.
With a common ancestor each attribute lands in exactly one bucket — author edit, upstream drift, converged, or BOTH-MOVED, the only rows that are actually a decision. Documents the baseline does not contain are named and excluded, never guessed at: an attribute present today and absent in an old file is upstream addition far more often than author deletion, and a two-way diff cannot tell those apart. Exit 1 when there is a decision to make, 0 when the port is mechanical.
Validating a mod in isolation isn't the same question as "how does this play with my other 40 mods?" No published tool answers that for X4 — the interaction suite does, reading packed mods (e.g. VRO) as well as loose ones:
x4compat— collision detection over the effective tree: two mods editing the same node (one silently loses), defining the same registry id, or fully overriding the same file. A candidate mode answers "what would break if I added this mod?" before you install it.x4xref— a who-calls/who-listens/cue index over every MD/aiscript in base+DLC+your mods. Answers "does anything else react to this event/action?" in one query — the kind of question that otherwise takes many rounds of grepping for tokens that share no keyword with what you're actually asking about.x4stats— advisory: how does a mod's ware/weapon pricing compare to everything else in your effective game (including an installed overhaul's rescaled values)? Grounds a balance discussion; doesn't settle one.x4similar— advisory: flags a mod's ship as a likely near-duplicate of one you already have under a different name (e.g. two mods independently adding "the same" ship).
The /x4-mod-interaction skill ties all four together into one interaction brief.
New in v2.6.0, and optional: the tools above answer questions about a mod. This answers questions about the whole corpus — 36M nodes across the base game, every DLC and every installed mod — in about a second.
cd tools/basex
bash build-corpus.sh # one-off index of every file AS WRITTEN
bash build-effective.sh # one-off index of the merged, live tree
uv run python ask.py attr drag # every value this attribute takes
uv run python ask.py refs turret_x --db x4eff # who references it, in the LIVE treeUse it for the questions a grep cannot answer honestly: what values does this attribute take across the corpus? · who references this macro? · which mods define a ware nobody produces?
The reason it is worth a JVM: it can back a negative. A recursive grep that finds nothing tells
you nothing — it cannot distinguish "this does not exist" from "I did not look everywhere", and 62%
of mod XML is inside packed archives a grep never opens. ask.py refuses to print a zero as a
finding unless it can state its denominator, and then says "NEGATIVE CONFIRMED over N of M
documents" with every exclusion named. It also refuses to answer at all from a stale index rather
than serving you a confidently wrong number.
Be honest with yourself about the cost before you start: Java 17+, roughly 3 GB of disk, and a
build measured in minutes, not seconds. BaseX itself is bundled (BSD-3-Clause, 5.2 MB) so there is
nothing extra to download. See tools/basex/README.md for the install, the
freshness contract and the exit codes, and tools/basex/QUERIES.md for worked queries.
x4liveis the least settled thing in this toolkit. Do not use it on a save you are actually playing. Make a fresh save, or a copy, and use that.Being precise about why, because an overstated warning gets ignored: it changes one thing, and only when you ask.
x4live pauseandx4live unpausepause and unpause the running game; every other verb only reads. Nothing here changes a ship, a station, the economy or a savegame, and the addon declaressave="false"so it cannot bake into a save. The reasons to be careful are narrower and real:
pauseandunpausechange the game you have open.unpauseonly undoes a pause this channel made, and a UI reload (alt-enter, loading a save) forgets that, so such a pause is then undone in game;- it loads a mod into your running game, and the snapshot probe runs automatically at load;
- that probe calls
SaveUIUserData()— it writes your profile's UI userdata, which is not your save but is not nothing either;- its answers have been wrong. Until 2026-09-02 a failed engine call was reported as "no such macro" rather than as an error, and answers like that feed groundtruth fixtures. Treat what it tells you as evidence, not as truth;
- removing any mod from a save lets the engine silently delete the content that mod owned — no dialog, and usually no error line.
None of that is a reason to avoid it on a scratch save, which is what it is for.
Every other tool here reads files. x4live reads the live engine: what objects exist right
now, what a macro's real values resolved to, whether an extension actually loaded — without a
quit-and-relaunch cycle for each question.
It needs one thing inside the game, and this toolkit ships it: mods/x4_toolkit_helper/.
Copy that folder into {game}/extensions/, or from tools/x4validate run
uv run python scripts/deploy-mod.py x4_toolkit_helper --apply, which refuses a wrong target
and re-reads what it wrote. It answers a fixed, enumerated vocabulary: read
verbs, plus exactly two write verbs, pause and unpause, which take no arguments and report
the pause state the engine reads back. It declares save="false" so it cannot bake into a save,
and it adds no content, no menu and no MD script. Its one third-party dependency
(Mod Support APIs, ws_2042901274, from Steam Workshop or Nexus) is declared optional: the
addon still loads without it and the snapshot half keeps working, while only the named-pipe verbs
report the api unavailable. The two halves fail independently, deliberately.
⚠ Enumeration is not a census, and the tool says so rather than rounding it away. Ownerless
objects belong to no faction and are invisible to any owner query, hidden factions are opt-in, and
name and sector are player knowledge — so many rows come back Unknown while ids, positions,
class and flags stay exact. Object ids also do not survive a UI reload, which the engine performs on
a save load and on an alt-enter graphics change.
See tools/x4validate/README.md for the verb list and the evidence
tiers behind each answer.
/x4-balance— ground a stat or balance change in measured values before proposing it: the three-values rule (vanilla, effective, proposed) and the in-sector vs out-of-sector check./x4-cli-reference— the exact subcommands, flags and defaults of every toolkit CLI, GENERATED from each CLI's own--helpbytools/x4validate/scripts/gen-cli-reference.pyso it cannot drift; the suite fails if it is stale./x4-debug— read the active profile'sdebug.txt, filter benign noise, surface real errors./x4-live— query the RUNNING game over the live channel, and read a refused id, a short count or a zero correctly (the traps a live enumeration hides)./x4-mod-interaction— analyze how a mod interacts with your installed set: collisions, shared event/action hooks, advisory balance fit, and same-ship redundancy./x4-mod-release-review— before publishing a new version of your own mod: review only what changed since the last published version, change nothing while reviewing, and bring every finding to you, one suspected finding at a time, before any fix./x4-modlist-review— triage your mod registry against the Nexus API./x4-probe— build a temporary in-game instrument (a test harness or diagnostic mod), and work out which of "did not load / never fired / logic failed" produced a silent result./x4-scaffold— scaffold the full cross-file footprint for new content from a vanilla analogue./x4-toolkit-dev— for working ON the toolkit itself (its code, gates, hooks, generator oragent/source): derived-artifact freshness, narrowing steps, the bug funnel, concurrent sessions. A player or modder using the tools does not need it./x4-update-mod— port a mod to a newer game version (mechanical checks + design brief)./x4-xml-patching— the selector, merge-tree and load-order gotchas that make an X4 patch silently no-op, where a fix belongs, and how to validate it; invoke before the first XML edit.cross-file-impact/mod-researchsubagents (Claude Code only) — trace the fan-out / research a mod before editing.
The /name form is Claude Code's. The same skills are installed for Codex and generic agents
in .agents/skills/ and for OpenCode in .opencode/skills/; ask for one by name.
-
Command + file guards (as Claude Code runs them; Codex and OpenCode reach the same guards through an adapter, see the agent table above) — block writes to
reference\and direct.cat/.datedits; confirm non-save profile changes. Recognized mutations of one or two saves first get permanent verified preservation; bulk save access needs bound human approval. A mod manifest is ADVISED rather than confirmed (a deliberate 2026-08-29 choice: the note is worth having, the interruption is not). An opaque program naming saves requires review; its named pairs are preserved first, and unresolved save scope is refused. A separate profile confirmation never skips save preservation or bulk approval. They cover every tool that can change a file: Bash, PowerShell (parsed by PowerShell's own parser and judged by the same rules as Bash — it needspwshor Windows PowerShell, and asks rather than guesses without one), Edit, Write and NotebookEdit. -
Auto-backup — every edited file is copied to
.claude\backups\with an audit log. -
Disk retention —
scripts/x4housekeep.pyreports output and applies reviewed plans through the Recycle Bin. On Windows, session start also sweeps idle Temp directories carrying this toolkit's ownership receipt; matching names alone are insufficient. SetX4_HOUSEKEEP_SWEEP=offto disable that sweep. See RETENTION.md. -
Confidence system — no guessing; the agent rates confidence and lists assumptions first.
-
Baseline capture —
scripts/generate-baseline.shrecords a known-good snapshot (game version, installed-mod hashes, a normalized debug.txt error fingerprint) to diff against later. -
Loss canary —
python scripts/x4canary.pyrefuses if a tracked file in a watched repository has been DELETED, EMPTIED or lost more than half its bytes. It runs automatically at session start, so this is the one script here that can interrupt you without being asked for: a*** DATA LOSS ***banner naming the file and thegit checkoutthat recovers it. It exists because a mod registry went from 196,363 bytes to 46 and nobody noticed for six hours —git statushad shown it immediately. "Could not check" is exit 2 and never reported as loss. -
Write lock —
python scripts/x4lock.py status | lock | unlockmarks the files you cannot afford to lose by accident (the hooks, the skills,CLAUDE.md,KNOWLEDGEBASE.md, your paths config) read-only, so a stray write fails loudly instead of succeeding quietly. Unlock, edit, relock. Run from a linked git worktree, it checks the main checkout's paths config (and$X4_TOOLKIT's, when set) instead of the worktree's own, which a worktree never has, and names what it checked.⚠ Unlock before re-running the installer, then lock again. The installer will not write over a read-only file: it REFUSES up front, names the files it would have overwritten -- the first 8, then a count of the rest -- and changes nothing. That is
x4lockworking — it cannot tell an installer from any other process. An upgrade that does not need to change a locked file does not touch it, so this only comes up when something you locked has genuinely changed upstream. -
Reference lock (Layer 2) —
python scripts/x4refguard.py status [--json] [--full] | apply | removeprotects the unpackedreference/tree at the OS level against ordinary processes, independently of hooks, with the privileged-access exceptions below.applyandremoveshow the target folder and count the files under it (with progress), then ask you to confirm;--yesconfirms up front, and without a terminal to ask on they refuse rather than act (--yesis how an agent runs it once you have agreed). It is not instant: about 8 s per 100,000 files was measured on scratch trees, and one apply over a full ~510,000-file tree was still running after 2 minutes, so a "still applying" line appears every 10 s. Like every script here it acts for the toolkit it lives in: ifX4_TOOLKITnames a different toolkit it says so andapply/removerefuse until you pass--toolkit <folder>.bin/unpack-reference.shapplies it after a verified unpack. The installers do not apply it (it is an ACL change, yours to make), so areference/unpacked before 4.0 stays unprotected until you runapply;x4doctor'slayer2.referencerow shows whether it is on.applyacts only on a FINISHED unpack: one carrying the.unpacked-and-lockedsentinel, whichbin/unpack-reference.shwrites as its last step (every release since 1.0), so a tree the toolkit unpacked before 4.0 qualifies. A tree without it (unpacked by hand, or unfinished) is refused, and the refusal prints both ways out: unpack it with the toolkit, or, only for a complete unpack you made yourself, the one-line command (bash or PowerShell) that writes the sentinel.-
Windows: one inherited deny for your own account,
(OI)(CI)(DE,DC,WD,AD)(mask 65606). It blocks deleting, renaming, overwriting, appending and creating anything inside the tree. Reads and copies OUT of it still work. Measured on scratch trees: 15 of 15 delete/rename and 20 of 20 write primitives blocked, 12 of 12 reads unaffected, about 8 s per 100,000 files. -
Linux/macOS: best effort, not device-tested. Linux uses
chattr +iwhen run as root, otherwisechmod a-won every directory and file. macOS useschflags uchg, otherwise the samechmod.statusnames the mechanism it found, what it stops and what it does not. It never reports "protected" for something it could not confirm. -
What it does not stop: renaming the
reference/folder itself (its parent allows that, andstatusthen reports the root missing); you lifting it on purpose; an Administrator or root. Measured on elevated hosted Windows: Git Bash changed protected scratch trees in three of three probes when backup/restore privileges were available; the same three probes were blocked when disposable children removed both privileges. Disabled privileges can still be available to a process. -
To re-unpack after a game update, lift it first. Each step is yours to take; an agent should not take it on its own:
python scripts/x4refguard.py remove(it asks you to confirm;--yesskips the question)- root moved since?
python scripts/x4refguard.py remove --path <old root>(only accepted on a folder that carries this tool's exact protection) - tool broken? from cmd.exe:
icacls "<root>" /remove:d *<your SID>(whoami /userprints the SID); Linuxsudo chattr -R -i/chmod -R u+w; macOSchflags -R nouchg - last resort, elevated:
icacls "<root>" /reset /T /C
Then
rm reference/.unpacked-and-lockedand runbin/unpack-reference.sh.
-
-
Recovery —
bash scripts/restore-from-backup.shLISTS the timestamped auto-backup trail above; restoring is a second, deliberate step:RESTORE=1 bash scripts/restore-from-backup.sh <backup-filename> <dest-path>. -
The guards are tested —
bash scripts/test-hooks.shfeeds every hook synthetic tool-call JSON and asserts the decision it returns, across both the in-game and separate layouts.python .claude/hooks/test_hook_facts.pyadds unit tests over the command parser in well under a second. Editagent/guards/claude-hooks/, regenerate, then run both checks. This exists because a silent guard is worse than no guard: several hooks were inert for entire releases and code review never caught it. Coverage is verified rather than claimed:python scripts/verify-hook-tests.pyplants a specific defect and requires the named test for it to go red, then pins each rule true and false in turn and requires a must-fire / must-not-fire test to break each way. A suite that cannot go red is decoration, and several guards here were inert for entire releases while their suite was green.
Edit hook sources in agent/guards/claude-hooks/, then regenerate from tools/x4validate
with uv run python scripts/gen-agent-trees.py. Run the hook suites against the generated
copy. Do not edit .claude/hooks/ directly.
python .claude/hooks/x4guard.py check lets another agent request a verdict without taking
backups or executing its command. Supply --kind shell --shell bash|powershell --command ...
or --kind write|delete --path .... A delete check applies both file protection and deletion
confirmation; the stricter verdict wins. An evaluation failure is deny with inert: true.
An ordinary ask requires approval. Neither exit 0 nor a passing check installs enforcement
in an agent host. On Windows the guards find Git Bash themselves: X4_BASH if set, then
Git for Windows' standard install locations, then PATH walked past the WSL stub. Set
X4_BASH only when Git Bash is installed somewhere else (see Prerequisites).
The current wrapper resolves relative file paths from the caller's working directory before
checking them. One time budget covers the whole check (X4_GUARD_TIMEOUT_S, default 25 s),
shared by a delete's two guards; a check that runs out is an inert deny. The Codex hook path
uses its own budget instead (X4_CODEX_BUDGET_S, 45 s).
python scripts/x4doctor.py [--root DIR] [--agent NAME] [--json] [--toolkit DIR] -- are the guards live
here? A read-only health check, per installed agent target. It runs on Python 3.10 with no
dependencies, so a broken uv cannot take it down. It reports:
- the bash, python and jq the guards actually resolve, each one executed;
- the reference and game roots the guards see, compared with the ones the tools see;
- deployed-vs-source parity;
- a self-test through
x4guard check, with controls that must deny and controls that must allow, so a guard that denies everything cannot pass; - Claude's hook wiring and
disableAllHooks; - Codex's project trust and per-hook review state;
- OpenCode's plugin, adapter and rendered deny rules (in place and current, never "loaded");
X4_GUARD, the OS-levelreference\protection and the x4lock state.
Every row is OK, FAIL, UNKNOWN, TODO or N/A, and a check that cannot answer says UNKNOWN.
TODO means "installed correctly; YOUR step is pending" -- trusting the folder in Codex,
approving its hooks in /hooks, unpacking reference/ when it is not there yet
(bin/unpack-reference.sh, which then applies the OS protection), applying the reference/ OS
protection to a tree you already have (python scripts/x4refguard.py apply): steps no installer
may take for you. The reference/ rows print their command by absolute path, ready to paste. Exit codes:
0 all OK; 1 any FAIL; 3 UNKNOWN without FAIL; 4 only TODO rows pending (a fresh install,
before your steps); 2 nothing checked. Precedence is FAIL > UNKNOWN > TODO, so a pending step
never hides a defect, and a run that checked nothing never exits 0. It acts for the toolkit it
lives in (--toolkit DIR to choose another) and says so when X4_TOOLKIT names a different one.
These hooks inspect known command forms; they do not sandbox arbitrary interpreter programs.
Opaque shell programs explicitly naming saves are treated as possible writes,
with named pairs preserved before review. Unqualified scope and unapproved bulk
access are refused. Known reader forms and literal output are exempt from this
rule; it does not prove what an unknown program actually does.
The loss canary detects file loss. Persistent process receipts are available through
python tools/agent-harness.py coverage --session-id ID; they describe checks that reached
the toolkit and do not prove host interception. Independent reference/ filesystem
protection is available through x4refguard, once explicitly applied and verified.
The rest of
scripts/is maintainer tooling that ships because the bundle is the repository:fuzz-guard.pyandverify-hook-tests.pyprove the guards can fail,scan-identifiers.pyis a CI gate,audit-coverage.pyis a local coverage-ledger check,build-release.shbuilds this bundle from a git tag, andgitbash.pyresolves a real bash on Windows for the scripts and tests that need one. None of them is needed to use the toolkit.
- Claude Code — subscribe to Claude (Pro/Max), then install the desktop app from
claude.ai/code, or the CLI: install Node.js and
run
npm install -g @anthropic-ai/claude-code. - Codex — install OpenAI's Codex CLI (github.com/openai/codex).
- OpenCode — the CLI only (the desktop app is not supported; see below).
- Anything else — install it the way its maker says, then read
ADAPTING.md: the toolkit's guards do not protect an agent it has no adapter for.
Download the latest release zip (from Releases
or Nexus) and extract it, then run the guided installer. On Windows, pick a SHORT folder
(e.g. C:\X4AI): Windows' classic 260-character path limit is reached once the folder's own
path is longer than about 165 characters, because the deepest file in the toolkit sits 92
characters below its root -- extracting under a long Downloads or OneDrive path can fail for
the deepest files (a 2026-10-04 install test hit this with 2 test fixtures).
bash install.sh # Linux / macOS / Windows (Git Bash)powershell -ExecutionPolicy Bypass -File install.ps1 # Windows PowerShell(The explicit -ExecutionPolicy Bypass form is given because a stock Windows install ships
with scripts disabled — a bare .\install.ps1 would be refused before it ran anything.)
It asks which layout you want (see the table below), auto-detects your game, profile and
XRCatTool, and writes the result to <toolkit>/x4-paths.env. Nothing is hardcoded.
Upgrading from 3.x: 3.x kept the config at
<toolkit>/.claude/x4-paths.env. 4.0 still reads it there (deprecated, with a notice), and the installers andpython scripts/x4config.py migrate --applyMOVE it to the toolkit root (a read-only, x4lock'd file moves too and stays locked). Two copies that differ are refused, never merged. To go back to 3.x, move the file back.
Which layout? If you're unsure, pick separate — it keeps the game folder untouched and avoids needing write access to
C:\Program Files. Pick in-game only if you want the single-folder model. Pick global if you have several mod repos.
Contributing or just reading the source? Clone it standalone instead:
git clone https://github.com/WingedGuardian/x4-ai-toolkit.git
Paste the contents of SETUP_PROMPT.txt into Claude Code, Codex or any other agent. The agent
says which agent it is and checks whether the toolkit has an adapter for it, runs
bash setup.sh, checks prerequisites (bash, jq, uv/Python 3.13), wires up x4validate, runs
x4doctor to report which guards are live, and walks you through unpacking your own
reference/ and (optionally) adding your Nexus API key.
To run x4validate's test suite yourself, from the toolkit folder: in Git Bash, Linux or macOS
cd tools/x4validate && uv run pytest -q; in Windows PowerShell (5.1 rejects &&)
cd tools/x4validate; uv run pytest -q. Passing means 0 failed and 0 errors; skips are
expected. What was measured (2026-10-05, on Windows: a fresh --agent all install by each
installer, separate method, --no-env / -NoEnv, then the full installed suite): the install.ps1 install passed with 4,018 passed, 377 skipped, 0 failed;
the install.sh install ended 4,017 passed, 366 skipped, 6 failed, from three causes that were
then fixed and re-run individually (no second full run; BLIND-SPOTS F161). Installs with fewer
agents were not measured. Each skip names its reason: an installed toolkit does not carry the repository-only
agent/ source, so the tests that read it skip as REPO-ONLY. Answer any questions it asks. An agent
with no adapter is pointed at ADAPTING.md, which shows how to build and prove
one; until it has one, the toolkit's guards do not protect it.
- bash — required. Every safety hook and both setup scripts run under it. Linux/macOS have it;
on Windows install Git for Windows (Git Bash) — without
it the hooks silently do nothing, so the safety guards below would not be active. Git Bash
must be FOUND, and a stock Windows
PATHdoes not find it: Git for Windows adds only<Git>\cmdtoPATH, andbashthere is the WSL stub inSystem32, which cannot run the toolkit. The toolkit looks in the standard Git for Windows locations (Program Files,Program Files (x86),%LOCALAPPDATA%\Programs) and then alongPATHpast the stub. If Git lives anywhere else, setX4_BASH(new shells only):setx X4_BASH "C:\Program Files\Git\bin\bash.exe"(with your path), or put Git'sbinfolder ahead ofSystem32onPATH.python scripts/x4doctor.pyshows which bash was found. - jq — Windows
winget install jqlang.jq· Linuxsudo pacman -S jq/apt install jq· macOSbrew install jq - Python 3 — required, and not only for the tools: the Bash guard (
protect-bash.sh) analyses each command withhook_facts.py, so without an interpreter onPATH(orX4_PYTHONpointing at one) it asks on every command instead of checking it. Anypython/python3/pywill do; the parse pass has no third-party dependencies. - uv (+ Python 3.13) — for x4validate (https://docs.astral.sh/uv/)
- XRCatTool (from Egosoft) — to unpack your own game to
reference/(run viabin/xrcat) - Wine — only on Linux/macOS, to run XRCatTool (a Windows
.exe) - Java 17+ — optional, only for the BaseX corpus search described above. Nothing else needs a JVM, and the rest of the toolkit works without it.
Runs on Linux, macOS, and Windows (Git Bash). All locations are configurable via
x4-paths.env in the toolkit root (no hardcoded OS paths); the hooks accept both / and \ styles.
On Linux/macOS, XRCatTool is invoked through Wine automatically by bin/xrcat.
As of v2.01 that is true of the Python tools too — they read the same
x4-paths.env the installer writes. Before v2.01 they read a different set of variable
names, so on the separate and global layouts a successful install still left the cross-mod
commands pointed at CWD-relative paths. If you installed v2.0, take this update.
The installers set X4_TOOLKIT in your user environment (opt out with --no-env /
-NoEnv). Without it, the config file is found only by walking up from the current directory,
and the tools are often run from the game folder, which may hold no x4-paths.env. You would see (unresolved) locations with a perfectly good config sitting one
directory tree away.
Your current X4_TOOLKIT |
What the installer does |
|---|---|
| not set | sets it to this toolkit (new terminals only) |
already this toolkit (any spelling: / or \, case, trailing separator) |
nothing |
| a different toolkit | nothing: it prints a [WARNING] naming both, and the command to repoint it |
--no-env / -NoEnv |
nothing; it prints the command to run yourself |
Where it is written:
- Windows (both installers): your user environment (
HKCU\Environment), throughscripts/x4-userenv.ps1([Environment]::SetEnvironmentVariable(..., 'User')). Open a new terminal to see it. - Linux / macOS (
install.sh): one marked block appended to your shell's startup file:~/.zshenv(or$ZDOTDIR/.zshenv) for zsh,~/.bash_profilefor bash on macOS,~/.bashrcfor bash elsewhere. Delete the block to undo it. Any other shell (fish, ksh) gets no file edit, only the line to add yourself.install.ps1sets it on Windows only. The POSIX paths are best effort and not device-tested.
A later re-run with no --toolkit installs into the folder X4_TOOLKIT names, because both
installers have always read an inherited X4_TOOLKIT as the destination. That is the upgrade
in place you normally want. Pass --toolkit to install somewhere else.
setx X4_TOOLKIT "C:\path\to\toolkit" # Windows, by hand (new shells only)
echo 'export X4_TOOLKIT=/path/to/toolkit' >> ~/.bashrc # Linux / macOS, by hand--agent opencode (and all, the default) installs an OpenCode target. Everything about
OpenCode's behaviour below was READ from its docs and source (v1.18.34, anomalyco/opencode),
not measured against a running OpenCode. The reading is recorded in
docs/superpowers/measurements/2026-10-02-opencode-read.md.
- The OpenCode desktop app is not supported. Its plugin hooks never fire (anomalyco/opencode#38604, closed as not planned). Use the OpenCode CLI.
- Two layers. (1) Deny rules in
.opencode/opencode.jsonc, rendered per machine at install from the roots the guards resolve: edits intoreference/,.cat/.datwrites under the game, extensions, mods and toolkit folders, and deletion commands aimed atreference/by absolute path. (2) A plugin,.opencode/plugins/x4guard.js, that asks the toolkit's guards before everybash,edit,writeandapply_patchand blocks the call when they refuse. It fails closed when Python, the adapter or the guards cannot answer. - The game-install block is plugin-only. The guards allow a whitelist inside the game folder. A deny-only rule list cannot express that without allow rules, and allow rules would loosen your own OpenCode config.
- A plugin that fails to load is skipped (READ: OpenCode writes a log line and carries on,
so a broken plugin fails open). When it is loaded, the system prompt
carries a line starting
X4 GUARDS LIVE. If that line is missing, only the deny rules apply. - A deny is final only with OpenCode's defaults (INFERRED from OpenCode's permission code,
not run). If an edit or command ever asks and
you answer "always", that approval overrides a matching deny for the rest of the session. A
per-agent
permissionrule of yours can override it too. The plugin still applies. - Subagents. The plugin may not run inside a subagent session (#5894); the deny rules do.
- No session-start banner. OpenCode has no session-start hook, so a missing, deprecated or
doubled path config is reported only by
python scripts/x4doctor.py(Claude Code and Codex are told at session start). - Start OpenCode in the toolkit folder. Its config and plugins are found walking up from where
it starts.
OPENCODE_DISABLE_PROJECT_CONFIGturns BOTH layers off. - Windows shell. OpenCode runs commands in PowerShell unless you set
shellin its config; the plugin reads that setting itself. If you set it to bash, also setX4_OPENCODE_SHELL=bashbefore installing so the skills are rendered for bash -- the plugin refuses every command while the two disagree. A shell the guards cannot judge (cmd) is refused: set OpenCode'sshellto pwsh, powershell or bash. - Your own
permission.editorpermission.bashwritten as a string ("edit": "ask") is replaced by the toolkit's deny object when OpenCode merges configs (READ). Write it as{"*": "ask"}.x4doctorflags it. - After moving
reference/or the game, re-render the deny rules:python .opencode/hooks/opencode_config.py write --root .An existingopencode.jsoncthat the toolkit did not write is never overwritten. - Verify with
python scripts/x4doctor.py --agent opencode. Not judged (READ, OpenCode v1.18.34tool/registry.ts): MCP tools, custom tools you add astool/*.ts/tools/*.tsin an OpenCode config folder, and tools another plugin defines. Only the four built-in tools that write a file or run a command (bash,edit,write,apply_patch) are checked. Seedocs/superpowers/measurements/2026-10-02-opencode-read.md(R15).
One guided installer, three layouts — pick what fits. Every path is auto-detected where
possible and overridable (--game, --profile, --toolkit, --mods, --reference,
--extensions, --xrcattool; --unpack to build reference/ immediately; --yes non-interactive;
--dry-run to see the destination and item list without writing).
Upgrading an existing install needs
--over-existing(-OverExistingon PowerShell). Without it the installer refuses, names what it found, and tells you what to type. Installing over a toolkit replaces shipped reference files, skills and agents. A changedKNOWLEDGEBASE.mdis independently retained before replacement; local notes,x4-paths.envand.claude/settings.local.jsonstay in place.Upgrading from 3.x: an edited
CLAUDE.mdis kept on disk, but no longer loaded. If yourCLAUDE.mdis not one the toolkit ever shipped (it is compared, by hash, with theCLAUDE.mdof every release tag, listed inscripts/shipped-instruction-hashes.txt), it is moved toX4-NOTES.pre-4.0.md(or a dated name, if that exists) before the 4.0 file is copied, and the installer says so. No agent reads that copy: the 4.0CLAUDE.mdreplaces it. To bring your notes back, review that copy and merge learned facts and explicit preferences intoKNOWLEDGEBASE.local.md. ExistingX4-NOTES.mdremains preserved and is also read. Unclassified imported notes are evidence, not standing instructions. An unedited shipped copy (whatever its line endings) is simply replaced.--dry-runnames the move without making it.
--yeswill also refuse an auto-detected destination: nothing named it and nobody is watching, so name it with--gameor--toolkit.
Personal .claude/settings.json edits need a reviewed upgrade. Both installers
refuse before copying or moving instructions if that file differs from the
current source and every verified shipped version. This protects permissions,
custom hooks and changed matcher/timeout settings. Use the private
upgrade-stage / upgrade-apply workflow in ADAPTING.md, which
prepares merged settings and independent rollback bytes. Unresolved custom
pre-tool policies still require ordered integration. Generic-only installs
leave Claude settings alone.
Customized .codex/hooks.json also uses reviewed migration. An unchanged JSON
definition stays byte-exact, including formatting and read-only protection.
Codex installs require a root without %, !, $, backticks, double quotes or line
breaks, which its shell command definitions cannot safely carry. Generic-only
installs do not acquire that Codex restriction.
The chosen paths are written to <toolkit>/x4-paths.env.
| Method | What it does | When to use |
|---|---|---|
| in-game | Copies the toolkit into your X4 game folder (the original model). | One game, one workspace. |
| separate | Toolkit lives in its own folder, pointed at the game via config. | Keep the game folder clean. |
| global | Installs the skills/agents into ~/.claude and writes the X4_* paths into your global Claude settings. Skills and agents only — the safety guards are not installed. |
Several mod repos — the skills/validator then work from any of them. |
# Linux / macOS / Windows (Git Bash)
bash install.sh --method separate --game "/path/to/X4 Foundations" --unpack
bash install.sh --method global # multi-repo: skills+paths into ~/.claude# Windows (PowerShell)
powershell -ExecutionPolicy Bypass -File install.ps1 -Method globalWindows note: the hooks/scripts are bash, so running the toolkit needs Git Bash — found in its standard install location, or named by
X4_BASH(see Prerequisites). The PowerShell installer finds it, runssetup.shthrough it for you, and prints thesetx X4_BASHline when it cannot.
The installer ships each agent's own files and nothing else (-Agent on PowerShell). A comma
list picks several, e.g. --agent claude,codex (each name is checked before anything is
written; all and auto stand alone):
--agent |
Instructions | Guards | Skills |
|---|---|---|---|
claude |
CLAUDE.md |
.claude/ hooks, registered in .claude/settings.json |
.claude/skills/ |
codex |
AGENTS.md |
.codex/hooks.json + a guard copy in .codex/hooks/; execpolicy rules in .codex/rules/ |
.agents/skills/ |
generic |
AGENTS.md |
none -- a generic agent runs no hooks | .agents/skills/ |
opencode |
AGENTS.md + .opencode/X4-OPENCODE.md |
deny rules in .opencode/opencode.jsonc + the .opencode/plugins/x4guard.js plugin over a guard copy in .opencode/hooks/ (best effort, CLI only) |
.opencode/skills/ |
all |
all of the above, OpenCode included |
--agent autoinstalls the agents it finds:claude/codexon yourPATH, or a marker already in the destination (CLAUDE.mdor.claude/settings.jsonfor Claude;.codex/for Codex;.opencode/or anopencode.json(c)for OpenCode, which is also detected asopencodeonPATH; a bare.claude/does not count, because every install creates one). It prints each one it found and why. If it finds none, it installsalland says nothing was detected. With--method globalit behaves asalldoes there.--codex-doc-max-bytes N(-CodexDocMaxBytes N, opt-in, Codex target only) writesproject_doc_max_bytes = N(32768..1048576) as the first line of the project's.codex/config.toml. Why: Codex reads the rootAGENTS.mdand every nested one into ONE 32,768-byte budget (MEASURED), so anAGENTS.mdof your own lower in the tree can silently cut the end of the toolkit's. An existing different value is reported and left alone, and the file never travels between installs. It takes effect once you trust the folder in Codex.- Codex runs no hook you have not reviewed, and says nothing when it skips one. After a
Codex install: run
codexin the folder, trust it, open/hooksand approve each X4 hook, then runpython scripts/x4doctor.py. The installer never trusts a folder or approves a hook for you, and it prints these steps. - An
AGENTS.mdyou wrote is never overwritten. If the destination already has one that differs from the shipped file and from everyAGENTS.mda release shipped, it is moved aside toAGENTS.pre-4.0.md(or a dated name, if that exists) and the installer says so.--dry-runnames the move without making it. --method globalis a Claude-only layout:--agent codex,genericoropencodethere is refused, and the default installs the Claude target only.- An installed toolkit is runtime-only: the
agent/source the generator reads is not copied (the release zip still carries it). - Skills for Codex and generic agents name the toolkit as
{{TOOLKIT}}; the installer renders that as$env:X4_TOOLKITon Windows (Codex runs PowerShell there) and as$X4_TOOLKITelsewhere. Linux/macOS support for the Codex target is best effort and not device-tested.
What each class of agent gets:
| Claude Code | Codex | Unknown agent | |
|---|---|---|---|
Deletes in reference\ |
blocked (hook; and the OS lock, once applied) | blocked when the hook is live; by the OS lock, once applied | blocked only by the OS lock, once applied |
Overwrites in reference\ |
blocked (hook; and the OS lock, once applied) | blocked when the hook is live; the OS lock, once applied, stops accidental ones | only the OS lock, once applied, stops accidental ones |
| Destructive shell commands elsewhere | blocked or asked (hook) | blocked when the hook is live; the worst prefix cases by rules regardless | not blocked |
| Non-save profile changes | asks (manifest edits advise) | deny with instructions when the hook is live | through a qualified adapter |
| Recognized save mutations | 1–2 saves preserved first; bulk needs bound approval | same policy when the hook is live | 1–2 saves preserved first; bulk refused through the neutral CLI, which reads no typed approval |
| A guard that crashes | asks | denies (our wrapper), unless the interpreter cannot start: then Codex runs the command | -- |
| Hooks not reviewed or changed | n/a | guards off, silently: x4doctor and the session instructions flag it |
-- |
| Post-edit validator feedback | yes | yes (when the hook is live) | no |
OpenCode's CLI plugin uses the same save policy and renders asks as refusals with approval instructions. Its runtime and matcher fixtures are tested; enforcement in a running OpenCode host remains unmeasured. For unknown hosts, Action v1 supplies policy and preservation, while the integrating host must honor the complete result before execution.
What the Codex hooks cannot see. Each of these was measured on Codex 0.160 unless it says otherwise:
- A shell
workdir. The measured hook payload supplied the session directory while Codex could execute in another folder. The guard now refuses recognized mutations whose target still depends on that missing execution directory; reads remain available. Use absolute mutation paths. On Windows, include a native drive or UNC share;/c/...is insufficient. An in-command directory change does not prove the omitted workdir. Policy conformance supplies synthetic workdir explicitly and is separate from observed native-host coverage. - Typing into a running shell (
write_stdin). A shell or REPL started with a harmless command can then be sent any command, and those lines reach no guard (0 hook events for 2 calls; a direct call is untested). This is disclosed, not blocked. - Bare
git clean/git reset --hard(both agents). These name no folder. From the game folder or another X4 folder,git cleanwith-x,-Xor-dandgit reset --hardare denied with a reason. Naming the folder (git -C "<folder>" ...) asks you first. - New tools, MCP tools and subagent controls are allowed. Their names are logged to
.codex/x4-unknown-tools.log. A subagent's own shell commands are still checked. - The
.codex/rulesfallback is narrow. It matches command prefixes only, so it repeats just two of the command guard's rules:git add -A/--all/.(blocked) andpython scripts/x4refguard.py remove(asks;python3orpytoo, but only with that relative script path -- an absolute path does not match). Whether Codex loads it in a project you have not trusted is untested. - Moving the folder turns the hooks off until you review them again in
/hooks, because the hook definitions contain the folder's absolute path. - A patch applied through the shell gets no validator feedback. It is checked before
it runs, but the post-edit validator runs for
apply_patchonly.
globalinstalls no guards. The command guard, the file guard and the automatic backup described under Safety, built in are registered in a project's.claude/settings.json, and each hook resolves its paths from the project it runs in.--method globaldoes not copy that file, so a global install gives you the skills and agents and none of the protection. This is deliberate rather than an omission: registering them globally would run them in every unrelated repository you open, on Claude's blocking pre-tool path, to guard X4 paths that are not there. To get the guards, install within-gameorseparatein the mod repo you want them in — the two layouts can coexist, and the installer prints this note too.
Open your agent in the toolkit folder and just talk. Some examples:
Editing & balance
- "Raise all L/XL shield regen by 15% — show me the dry-run first, then validate."
- "Add a new tradeable ware modeled on Energy Cells, with all the files it needs."
Porting & debugging
- "This mod was made for 7.x. Run the migration checker and fix every 9.0 break."
- "Read my debug.txt and tell me which errors are real vs benign noise."
- "My diff patch isn't doing anything in-game — check whether the sel= actually matches."
Mod-list & research
uv run x4modlist search "name" --json (from tools/x4validate) discovers Nexus
candidates (names only) with version, date and status, marked have / have? / new
against your active registry mods. It needs X4_NEXUS_KEY, reports capped/partial results,
unavailable evidence and its own coverage, and never changes your registry.
- "Triage my mod list against Nexus — what's updated, obsolete, or abandoned for 9.0?"
- "What does this Nexus mod do, and are there known 9.0 issues, before I edit it?"
Interaction analysis
- "Before I add this weapon mod — does it conflict with anything I have, and is it balanced for VRO?"
- "Why does my death-alternative mod stop the vanilla eject sequence, and what would happen if I added an escape-pod mod too?"
If it involves X4 XML, diff patches, MD/Lua scripts, the economy, or mod files, ask. The agent has the engine context loaded and will figure out the path — and validate before you burn an in-game test cycle.
- This toolkit ships no Egosoft content. You unpack your own
reference\from your own copy with XRCatTool.reference\,.cat,.dat, saves, anddebug.txtare all gitignored. - Nexus access uses your own free API key (
X4_NEXUS_KEYenv var). No key is bundled; never commit or share one.
Found a new X4 quirk or a 9.0 migration gotcha? PRs welcome — especially additions to
KNOWLEDGEBASE.md.
MIT — see LICENSE. X4: Foundations is a trademark of Egosoft GmbH.
- Claude Code by Anthropic, Codex by OpenAI, OpenCode
- x4validate built on lxml; mod metadata via the Nexus Mods API
- Sibling project: skyrimvr-claude-toolkit