____ ____
/ __/_ ______ / __/___ _
/ /_/ / / / __ \/ /_/ __ `/
/ __/ /_/ / / / / __/ /_/ /
/_/ \__,_/_/ /_/_/ \__,_/
A Linux gaming optimizer powered by an LLM agent.
Tell it a game. It researches your hardware and the community fixes, drafts a tuning plan, asks you to approve it, then hands you a string to paste into Steam. You launch the game the way you always do.
Why · Features · How it works · Getting started · Usage · Configuration · Editing the agent · Roadmap
Table of Contents
Getting a particular game to behave on Linux usually means stitching together
half a dozen sources: a ProtonDB report from 2023, a PCGamingWiki tweak, a
GitHub issue on doitsujin/dxvk, a forum thread about a specific GPU driver
bug. Every existing launcher — Lutris, Heroic, Bottles, Steam Tinker Launch —
gives you somewhere to put the answer but not the answer itself.
funfa does that research pass for you up front, before you sit down to play. The output is a markdown plan plus a single launch-options string. Nothing runs in the hot path of your session.
Status: Phase-1 dogfood. One CLI command, one user, one validation game (Assassin's Creed Brotherhood, appid
48190).
- One-shot optimizer, never a wrapper — runs once, prints a Steam launch-options string, exits. No daemon, no shim, no
funfa launch. - Skill-driven agent — the model's behavior lives in markdown files, not TypeScript. Edit a skill, rebuild, run. No code change needed to retune the agent.
- Plan-then-accept consent — reads only probes your machine until you approve the plan. After acceptance, applies everything automatically (no per-step prompts).
- Full auto-apply — installs Proton-GE, DXVK, community mods, and patches into your game prefix. Downloads verified and gated behind an SSRF-safe permission layer.
- Steam-safe — never writes Steam config while Steam is running. Launch options and Proton version are printed as manual steps.
- Community research built in — searches ProtonDB, PCGamingWiki, Reddit, and forums; reads broadly for consensus, verifies provenance before installing.
flowchart LR
A["funfa 'Game Name'"] --> B[PHASE 0<br/>Check existing plans]
B --> C[PHASE 1<br/>Investigate]
C --> C1["load_skill(hardware)"]
C --> C2["load_skill(game-research)"]
C1 --> D["uname, glxinfo,<br/>/etc/os-release"]
C2 --> E["ProtonDB, PCGamingWiki,<br/>Reddit, forums"]
D --> F
E --> F[PHASE 2<br/>Propose]
F --> G["submit_plan(appid,<br/>plan_markdown)"]
G --> H{Accept?}
H -- No --> I[Plan saved to disk,<br/>declined. Exit.]
H -- Yes --> J[PHASE 4<br/>Apply]
J --> K["Locate game<br/>(libraryfolders.vdf)"]
K --> L["apply_command: download,<br/>verify checksum, install"]
L --> M[Print launch-options<br/>string. Done.]
The model orchestrates all four phases inside a single pi session call.
submit_plan doubles as the consent gate — once accepted, apply_command
runs with no further prompts.
- Bun 1.3.13 (the runtime and package manager)
- A pi credential: either a Claude Pro/Max or ChatGPT login, or a provider API key that pi recognizes
- (Optional) A Parallel Search API key for live web search — without it, the agent falls back to reading canonical sources directly with
fetch_page
git clone https://github.com/gustavohariel/funfa.git
cd funfa
bun install
# Compile the standalone binary (~71MB, includes Bun runtime)
bun run --filter cli compile # → ./apps/cli/funfa
# (Optional) symlink somewhere on your PATH
ln -s "$PWD/apps/cli/funfa" ~/.local/bin/funfafunfa uses pi for LLM provider auth. Three ways to set it up:
1. In-process OAuth login (recommended):
funfa auth # interactive — picks a provider
funfa auth anthropic # or specify: claude, chatgpt, copilotThis drives pi's OAuth flow in your browser. Credentials stored in
~/.pi/agent/auth.json.
2. Standalone pi login:
npx @earendil-works/pi-coding-agent
# then type: /login (ChatGPT or Claude Pro/Max)3. Provider API key:
Set any key pi recognizes (e.g. ANTHROPIC_API_KEY, OPENAI_API_KEY).
funfa doesn't scan for keys itself — pi handles that.
funfa <game> positional, free-form name (Steam, GOG, Epic, etc.)
--json stream JSON-lines progress events to stdout
--bypass-confirms skip the approval prompt (opt-in, never default)
funfa auth [provider] log in to an LLM provider via OAuth (claude, chatgpt, copilot)
Example session:
$ funfa "Assassin's Creed Brotherhood"
── PHASE 1: Investigating hardware & game ──
✓ Kernel: 6.12.9-arch1-1
✓ GPU: AMD Radeon RX 6800 XT (RADV NAVI21)
✓ Distro: CachyOS
✓ Found 12 ProtonDB reports, 3 DXVK issues, 2 forum threads
── PHASE 2: Drafting plan ──
✓ Plan written to ~/.config/funfa/games/48190.md
── Plan Review ──
Accept this plan? (y/N)
── PHASE 4: Applying changes ──
✓ Game located: ~/.steam/steam/steamapps/common/AC Brotherhood
✓ Proton prefix: ~/.steam/steam/compatdata/48190/pfx
↓ Downloading Proton-GE 9-25… done
✓ Extracted to ~/.steam/root/compatibilitytools.d/
↓ Applying DXVK 2.5.1… done
── Launch options (paste into Steam) ──
PROTON_VERSION="GE-Proton9-25" DXVK_HUD=0 WINEDLLOVERRIDES="dinput8=n,b" %command%
Re-running on the same game runs the agent fresh. The plan file on disk is read-only from funfa's perspective and is kept even if you decline, so you always have a trace of what was proposed.
| Variable | Purpose | Default | Required |
|---|---|---|---|
FUNFA_PI_MODEL |
Override pi's default model — "<provider>/<model-id>", e.g. anthropic/claude-opus-4-5 |
pi's configured default | optional |
PARALLEL_API_KEY |
Live web search via the Parallel Search API | — (falls back to fetch_page) |
optional |
FUNFA_FETCH_ALLOWLIST |
Comma-separated domains to restrict fetch_page to (subdomains allowed) |
all public https hosts (SSRF-guarded) | optional |
FUNFA_DOWNLOAD_ALLOWLIST |
Comma-separated hosts to restrict apply_command downloads to |
all public https hosts (SSRF-guarded) | optional |
FUNFA_FAKE_HARDWARE |
Inject verified hardware facts and skip probes (dev / off-Linux testing) | — | optional |
XDG_CONFIG_HOME |
Where plan files land | ~/.config |
optional |
Authentication is pi's concern, not funfa's. See the Authentication
section above. Provider API keys (e.g. ANTHROPIC_API_KEY) are read by pi
from the environment, not by funfa.
The agent's behavior lives in markdown, not TypeScript:
apps/cli/src/skills/
funfa/SKILL.md ← base prompt / orchestration loop
hardware/SKILL.md ← which shell commands run for hardware probes
game-research/SKILL.md ← where to search, what to trust
plan/SKILL.md ← exact markdown shape for the final plan
execute/SKILL.md ← PHASE 4: game location, install commands
Edit a skill file, rebuild, run. No code change required to retune the agent.
These files are inlined into the compiled binary at build time via Bun text
imports, so the distributed funfa ships with its skills baked in.
To add a new skill: drop a <name>/SKILL.md and add one line in
apps/cli/src/skills/registry.ts.
apps/
cli/ # The funfa binary — single <game> command
src/
__tests__/ # All tests (cli, compile, plan, propose,
│ # provider, review-plan, prompt, permission,
│ # tools, auth, fetch-page, web-search-providers)
agent/ # pi auth, propose loop, permission gate
tools/ # submit_plan, load_skill, fetch_page,
# web_search, apply_command
core/ # CLI surface (Effect CLI), plan I/O, deps
services/ # Stderr service tag
skills/ # Product skills — inlined into the binary
funfa/SKILL.md
hardware/SKILL.md
game-research/SKILL.md
plan/SKILL.md
execute/SKILL.md
registry.ts
index.ts # Wiring + bin entry
package.json
All source is in one Bun workspace at apps/cli. No @funfa/* internal
packages — everything is local imports. The Plan shape lives in markdown
only (plan/SKILL.md); the CLI never parses it.
funfa reads untrusted text (forum posts, wiki pages, GitHub issues) and runs local processes. The guardrails today:
- Two-tier plans. Low-tier (env vars, launch args — fully reversible by clearing the Steam field) vs. high-tier (anything touching your prefix, installing binaries, editing the registry). funfa can now apply high-tier steps that live at the filesystem level (e.g. installing Proton-GE), but never silently.
- Permission gate. Every apply goes through a permission gate that prompts
you for consent (default deny) and blocks any download whose host is not
on a pinned trusted-upstream allow-list, or that uses plain
http. Theapply_commandtool runs a single argv array viaexecFile— no shell, no expansion. - SSRF guard.
fetch_pageandapply_commandblock private/local hosts (loopback, RFC-1918, link-local, cloud metadata,.local/.internal). - Steam stays hands-off. Steam-client state (launch options, Proton version) is never written from the CLI; those steps are printed for you to apply in Steam yourself.
Residual risk: a misaligned model could still misuse the read-only bash
probe tool. OS-level confinement (bubblewrap) around apply_command is future
work.
--bypass-confirms auto-allows applies. It is opt-in, for scripting only,
never default.
- Phase 0 — manual prototype: one hard game tuned by hand to learn what "good" looks like
- Phase 1 — CLI MVP (you're here), dogfooded on one game, with gated execution of the plan's filesystem steps
- Phase 2 — Decky Loader plugin: Game Mode UI shells out to the CLI
- Phase 3 — auto-detection, multi-launcher integration, an OS-level
sandbox (bubblewrap) around
apply_command, optional community plan sharing
The bar for keeping the project alive: the author reaches for funfa when
setting up a new game on Linux, instead of opening four browser tabs. If that
doesn't hold after a few weekends, kill it. Cheap to do — no Decky plugin to
throw away yet.
| Runtime | Bun 1.3.13 |
| Language | TypeScript ^6 |
| Monorepo / build | Turborepo + tsdown |
| Agent loop | embedded pi — createAgentSession |
| Effects / errors | Effect-TS 3.21 |
| CLI framework | @effect/cli |
| User prompts | @inquirer/prompts |
| Web search | Parallel Search API (optional) |
| Lint / format | Ultracite (Biome preset) |
| Command | What it does |
|---|---|
bun install |
Install all dependencies |
bun run dev:cli |
CLI in hot-reload mode |
bun run test |
Full test suite (bun test across all apps) |
bun run check-types |
TypeScript typecheck (tsc --noEmit) |
bun run check |
Ultracite lint + format check |
bun run fix |
Ultracite lint + format fix |
bun run --filter cli compile |
Produce the standalone apps/cli/funfa binary |
See CLAUDE.md for the deeper architecture notes and
commit conventions.
PRs welcome. For anything non-trivial, open an issue first — CLAUDE.md
documents the architectural commitments worth preserving:
- One-shot optimizer, never a launch wrapper
- Skill-driven agent loop (edit markdown, not code)
- Plan-then-accept consent model
- Steam config stays hands-off
MIT — see LICENSE.
funfa stands on prior art it does not try to replace:
- umu-protonfixes · umu-launcher
- Steam Tinker Launch
- ProtonDB · PCGamingWiki
- Lutris · Heroic · Bottles
- Decky Loader
README structure adapted from othneildrew/Best-README-Template.