- Posted on
- • Artificial Intelligence
Artificial Intelligence Mod Management
- Author
-
-
- User
- linuxbash
- Posts by this author
- Posts by this author
-
Artificial Intelligence Mod Management on Linux (with Bash)
Tired of spending nights juggling mods, guessing load orders, and chasing down conflicts? What if you could keep the deterministic, scriptable power of Bash while offloading the fuzzy “what should go first?” work to an AI that reads your metadata and makes a reasoned suggestion?
This post shows you how to build a simple, reproducible mod management pipeline on Linux using Bash plus a local AI model. You’ll:
Index mods and extract metadata/readmes
Detect file conflicts deterministically
Ask a local LLM for a recommended load order
Apply the chosen order via symlinks for quick rollbacks
No more hand-waving; you’ll own the pipeline, version it, and re-run it any time you add or update mods.
Why this matters
Mods are messy: readmes, vague dependencies, inconsistent manifests, overlapping files.
Bash excels at reproducible pipelines: hashing, indexing, conflict detection, and repeatability.
AI excels at text reasoning: dependency notes, patch chains, soft compatibility, and prioritization logic.
Combined: reproducible data + AI reasoning = faster, safer modding with documented decisions.
What we’ll build
A workspace with:
mods/archives (zip/7z)index/machine-readable metadataanalysis/conflict reports and AI outputsbuild/a layered “virtual install” via symlinks based on chosen load order
Scripts:
mods-index.shto parse archives, extract metadata, and build JSONmods-conflicts.shto detect file overlapsmods-ai-order.shto ask a local LLM for an ordered listmods-apply.shto materialize the load order into a single directory
You can adapt this to any game or engine (Skyrim, Stardew, Minecraft, etc.) by adjusting the “data root” expectations and patterns.
Prerequisites and installation
We’ll use standard CLI tools. Install them using your distro’s package manager.
Required: jq, ripgrep (rg), fd, 7z, unzip, Python 3 (optional), git, curl.
- Ubuntu/Debian (apt):
sudo apt update
sudo apt install -y jq ripgrep fd-find p7zip-full unzip python3 python3-pip git curl
# fd is named fdfind on Debian/Ubuntu; add a convenience alias:
echo 'alias fd=fdfind' >> ~/.bashrc && source ~/.bashrc
- Fedora (dnf):
sudo dnf install -y jq ripgrep fd-find p7zip p7zip-plugins unzip python3 python3-pip git curl
- openSUSE (zypper):
sudo zypper refresh
sudo zypper install -y jq ripgrep fd p7zip unzip python3 python3-pip git curl
Optional: a local LLM runner (Ollama) to keep your workflow offline:
curl -fsSL https://ollama.com/install.sh | sh
# Then pull a suitable model (pick one you like):
ollama pull llama3:instruct
# or
ollama pull qwen2.5:7b-instruct
Note: If you prefer a different local model/runtime, adapt the mods-ai-order.sh script accordingly.
Step 1 — Create your workspace
mkdir -p ~/ai-mods/{mods,index,analysis,build,workspace}
cd ~/ai-mods
Drop your .zip or .7z mod archives into mods/.
We’ll assume “data-root” is the top of each archive (e.g., Data/, assets/, etc.). You can tweak the scripts to your game’s conventions.
Step 2 — Index mods and gather metadata
The script below:
Lists contents of each archive (7z)
Ingests any likely metadata or readmes
Normalizes a per-mod JSON record
Emits one combined
index/mods.json
Save as mods-index.sh and make it executable.
#!/usr/bin/env bash
set -euo pipefail
ROOT="${1:-$PWD}"
MODDIR="$ROOT/mods"
INDEX="$ROOT/index"
TMP="$ROOT/workspace"
mkdir -p "$INDEX" "$TMP"
# Helper: prefer fd or fdfind transparently
if command -v fd >/dev/null 2>&1; then
FD=fd
elif command -v fdfind >/dev/null 2>&1; then
FD=fdfind
else
echo "fd/fdfind not found. Install fd (see instructions above)." >&2
exit 1
fi
# List mod archives
mapfile -t ARCHIVES < <($FD -e zip -e 7z . "$MODDIR" -x 'index' -x 'workspace' -x 'analysis')
mods_json="[]"
for arc in "${ARCHIVES[@]}"; do
name="$(basename "$arc")"
sha="$(sha256sum "$arc" | awk '{print $1}')"
# List files in archive
mapfile -t files < <(7z l -ba -slt "$arc" 2>/dev/null | awk -F'= ' '/^Path = /{print $2}')
# Try to extract a readme-ish file to text
readme_text=""
for rpat in README README.txt readme.txt readme.md CHANGELOG.txt; do
if printf '%s\n' "${files[@]}" | grep -qiE "^$rpat$|/$rpat$"; then
# Attempt to extract first match to stdout
readme_text="$(7z x -so "$arc" "$(printf '%s\n' "${files[@]}" | grep -iE "$rpat" | head -n1)" 2>/dev/null | sed 's/\r$//')"
break
fi
done
# Heuristic depends_on (scan readme for "Requires:")
depends=$(printf '%s\n' "$readme_text" | grep -iE '^\s*(requires|dependency|depends on)[: ]' || true)
# Normalize to a simple array of names (very naive; adjust per game)
if [ -n "$depends" ]; then
dep_array=$(printf '%s\n' "$depends" | sed -E 's/.*:(.*)$/\1/i' | tr ',' '\n' | sed -E 's/^\s+|\s+$//g' | sed -E '/^$/d' | jq -R . | jq -s .)
else
dep_array="[]"
fi
# Provide a normalized list of "game files" (filter out obvious docs)
game_files=$(printf '%s\n' "${files[@]}" \
| grep -viE '\.(txt|md|pdf|rtf)$' \
| grep -viE '(readme|license|changelog)' \
| jq -R . | jq -s .)
# Build per-mod JSON
mod_json=$(jq -n \
--arg name "$name" \
--arg path "$arc" \
--arg sha256 "$sha" \
--arg readme "$readme_text" \
--argjson files "$game_files" \
--argjson depends "$dep_array" \
'{
name: $name,
archive: $path,
sha256: $sha256,
depends_on: $depends,
provides: $files,
notes: (if ($readme|length)>0 then "readme-present" else "no-readme" end)
}')
mods_json=$(jq -n --argjson arr "$mods_json" --argjson item "$mod_json" '$arr + [$item]')
done
printf '%s\n' "$mods_json" | jq '.' > "$INDEX/mods.json"
echo "Wrote $INDEX/mods.json"
Run it:
chmod +x mods-index.sh
./mods-index.sh
You should now have index/mods.json with an array of mod descriptors.
Step 3 — Detect conflicts deterministically
We’ll compute which file paths are provided by more than one mod.
Save as mods-conflicts.sh:
#!/usr/bin/env bash
set -euo pipefail
ROOT="${1:-$PWD}"
IDX="$ROOT/index/mods.json"
OUT="$ROOT/analysis/conflicts.json"
mkdir -p "$(dirname "$OUT")"
if [ ! -f "$IDX" ]; then
echo "Run mods-index.sh first to generate $IDX" >&2
exit 1
fi
# Build a map: file -> [mods...]
# Normalize file paths to lowercase for case-insensitive collisions
jq -r '.[] | .name as $n | .provides[] | [$n, (ascii_downcase(.))] | @tsv' "$IDX" \
| awk -F'\t' '
{ file=$2; mod=$1;
key=file;
files[key]=files[key] ? files[key] "," mod : mod
}
END{
print "{"
first=1
for (k in files){
split(files[k], arr, ",")
if (length(arr)>1){
if (!first) { print "," }
printf "\"%s\": [", k
for (i=1; i<=length(arr); i++){
printf "%s\"%s\"", (i>1?",":""), arr[i]
}
printf "]"
first=0
}
}
print "\n}"
}' | jq '.' > "$OUT"
echo "Wrote $OUT"
Run it:
chmod +x mods-conflicts.sh
./mods-conflicts.sh
Open analysis/conflicts.json. Any key with multiple mods is a collision that needs order or a patch.
Step 4 — Ask a local LLM for a recommended load order
We’ll prompt the model with:
A compact summary of conflicts
Each mod’s readme-derived hints and dependencies
Instructions to output a JSON load order and notes
First, ensure Ollama is installed and you’ve pulled a model:
# Install (works on most distros)
curl -fsSL https://ollama.com/install.sh | sh
# Pull a small instruct model
ollama pull llama3:instruct
Now save this as mods-ai-order.sh:
#!/usr/bin/env bash
set -euo pipefail
ROOT="${1:-$PWD}"
IDX="$ROOT/index/mods.json"
CONFLICTS="$ROOT/analysis/conflicts.json"
AI_OUT="$ROOT/analysis/ai_order_raw.txt"
ORDER_JSON="$ROOT/analysis/ai_order.json"
MODEL="${MODEL:-llama3:instruct}"
if ! command -v ollama >/dev/null 2>&1; then
echo "ollama not found. Install it: curl -fsSL https://ollama.com/install.sh | sh" >&2
exit 1
fi
if [ ! -f "$IDX" ] || [ ! -f "$CONFLICTS" ]; then
echo "Missing index or conflicts. Run: ./mods-index.sh && ./mods-conflicts.sh" >&2
exit 1
fi
# Compact context to avoid overly long prompts
mods_compact=$(jq '[.[] | {name, depends_on, notes, provides: (.provides | map(ascii_downcase) | .[0:100])}]' "$IDX")
conflicts_compact=$(jq 'to_entries | map({file: .key, mods: .value}) | .[0:500]' "$CONFLICTS")
read -r -d '' PROMPT <<'EOF'
You are helping organize a mod load order. Output strict JSON only with this schema:
{
"load_order": ["mod1.zip", "mod2.7z", "..."], // descending priority: later overrides earlier
"rationale": "short explanation of key decisions",
"warnings": ["any potential issues users should check"]
}
Guidelines:
- Respect declared dependencies: a dependency must load BEFORE the depender.
- If two mods conflict on the same file, prefer patches or newer/HD versions later.
- If a mod looks like a patch/fix for another, place it AFTER the base mod.
- If unsure, group similar mods and order by specificity (patches last).
- Do not invent mod names. Only use names provided.
Return JSON only. No code fences, no commentary.
EOF
full_prompt=$(jq -n \
--arg sys "$PROMPT" \
--argjson mods "$mods_compact" \
--argjson conflicts "$conflicts_compact" \
'{
instruction: $sys,
mods: $mods,
conflicts: $conflicts
}' | jq -c .)
# Ask the model
ollama run "$MODEL" -p "$full_prompt" > "$AI_OUT" || true
# Extract JSON from model output (strip accidental code fences/markdown)
clean=$(cat "$AI_OUT" \
| sed -e 's/^```json$//' -e 's/^```$//' -e 's/```//g' \
| awk 'BEGIN{p=0}{if ($0 ~ /^{/) p=1; if(p) print}' )
# Validate JSON and only keep known mods in the order
if echo "$clean" | jq . >/dev/null 2>&1; then
known=$(jq -r '.[].name' "$IDX")
# Filter to known mods
echo "$clean" \
| jq --argjson names "$(jq -r '.[].name' "$IDX" | jq -R . | jq -s .)" '
.load_order = (.load_order | map(select(IN(. ; $names[]))))
' > "$ORDER_JSON"
echo "Wrote $ORDER_JSON"
else
echo "Model did not return valid JSON. See $AI_OUT for details." >&2
exit 1
fi
Run it:
chmod +x mods-ai-order.sh
./mods-ai-order.sh
Check analysis/ai_order.json for:
load_order: array of mod archive names (earliest to latest; latest wins)rationaleandwarningsto keep as documentation in your repo
Tip: Commit both the prompt input and the model’s output so the decision trail is transparent and reproducible.
Step 5 — Apply the load order via symlinks
We’ll construct a final “merged” directory where later mods override earlier ones by re-linking files in order. This is fast and undoable.
Save as mods-apply.sh:
#!/usr/bin/env bash
set -euo pipefail
ROOT="${1:-$PWD}"
ORDER_JSON="$ROOT/analysis/ai_order.json"
BUILD="$ROOT/build"
TMP="$ROOT/workspace/extract"
if [ ! -f "$ORDER_JSON" ]; then
echo "Missing $ORDER_JSON. Run ./mods-ai-order.sh first." >&2
exit 1
fi
mkdir -p "$BUILD" "$TMP"
rm -rf "$BUILD"/*
rm -rf "$TMP"/*
# Extract each mod in order to a temp directory and then lay down symlinks
mapfile -t ORDER < <(jq -r '.load_order[]' "$ORDER_JSON")
for modname in "${ORDER[@]}"; do
arc=$(jq -r --arg n "$modname" '.[] | select(.name==$n) | .archive' "$ROOT/index/mods.json")
[ -n "$arc" ] || { echo "Archive not found for $modname"; exit 1; }
dest="$TMP/$modname.extract"
mkdir -p "$dest"
# Extract preserving paths
7z x -y -o"$dest" "$arc" >/dev/null
# Link files into build, overriding earlier links
# Adjust filters to your game’s "data root" if needed
while IFS= read -r -d '' f; do
rel="${f#$dest/}"
mkdir -p "$BUILD/$(dirname "$rel")"
# Overwrite any existing link/file
ln -snf "$f" "$BUILD/$rel"
done < <(find "$dest" -type f -print0)
done
echo "Build ready at $BUILD"
Run it:
chmod +x mods-apply.sh
./mods-apply.sh
Point your game/mod loader to build/ as the content directory, or copy files from there if symlinks aren’t supported.
Real-world example pattern
- “HD Textures.zip” vs “HD Textures Patch.zip” vs “Quest Fixes.7z”
- Conflicts show several
.ddsfiles present in both “HD Textures” and “HD Textures Patch” - The AI suggests:
- Load order:
HD Textures.zip,HD Textures Patch.zip,Quest Fixes.7z - Rationale: The patch should override the base textures; quest fixes don’t overlap textures
- Warnings: Verify patch version matches base textures version X.Y
- Conflicts show several
You retain the reproducible artifacts:
index/mods.jsoncaptures what was scanned and howanalysis/conflicts.jsoncaptures objective overlapsanalysis/ai_order.jsoncaptures the AI recommendation and reasoningbuild/reflects the chosen order at a glance
Tips, guardrails, and extensions
Treat AI as an advisor. Always review its output. Keep “rationale” and “warnings” checked into version control.
Pin your model and keep the exact prompt; this helps reproducibility.
Add rules: a local “policy.json” with known mandatory orderings or blacklists; merge those with AI output.
Enrich indexing: parse game-specific manifests (e.g.,
modinfo.json,manifest.json) and prioritize them over readme heuristics.Continuous integration: run
mods-index.sh,mods-conflicts.sh, and a non-interactive AI job on PRs that add/update archives.
Conclusion and next steps
You now have a practical, Linux-first workflow that blends Bash’s determinism with AI’s reasoning to tame mod chaos:
- Index mods → Detect conflicts → Ask AI → Apply order via symlinks
Next steps:
Drop your mod archives into
mods/and run the four scripts in sequence.Customize conflict rules and data-root filters for your specific game.
Version-control
index/andanalysis/so your modlist is auditable and shareable.
If this helped, adapt the scripts to your favorite game and share your tweaks—let’s make Linux modding both smart and reproducible.