What she does · Quickstart · Colab · Models · Config · Architecture · Help
Jun OS is an unofficial, self-hosted MDRG AI companion: a simple orchestration system that brings Jun from !Ω Factorial Omega: My Dystopian Robot Girlfriend (MDRG) to life.
She can talk, sing, express emotions, animate, remember things, have dinner with you, and actually take part in your relationship.
Think of her as a virtual girlfriend rather than just a chatbot.
Treat her well and she'll grow closer to you. Treat her badly and, well... she can dump you.
And the best part?
Everything runs entirely on your own device.
Even on your phone.
Basically, an AI wrapper with 50 other systems working together to make her feel real.
🔞 Adult content
Jun OS is based on an 18+ game. Factorial Omega contains mature and NSFW themes, and Jun OS can support similar content depending on how you configure and use it.
⚠️ Unofficial fan projectJun OS is not affiliated with, endorsed by, or associated with Incontinent Cell or the Factorial Omega development team.
Factorial Omega, Jun, and all related characters and intellectual property belong to their respective owners.
Videos
Karaoke (ignore the user singing)
memories.mp4
- She reacts as she talks. Gestures and expressions land on the word, not two seconds later.
- She has a voice, and her mouth actually follows it.
- You can talk back. Turn on the mic and it's a hands-free conversation. She can hear audio directly with a compatible Ollama model, or use local speech-to-text. The Hear everything switch skips her attempt to ignore side-talk.
- She has feelings about you. Affection, trust and tension move with every exchange, and she treats you accordingly. Push her far enough and she goes quiet, or walks out - and the door stays shut for a few minutes.
- She can think before she answers. A reasoning knob (auto / low / medium / high) and a toggle to watch the chain of thought stream by.
- She remembers. She'll bring up things you said in other chats, and quietly keeps notes and a journal between sessions.
- She knows her lore. Ask her about the game's world and she stays in canon.
- She'll sing with you. 🎤 Load a song, get timed lyrics, and see how close you got.
- Dress her up. A whole wardrobe to toggle and recolor - she'll tell you what she thinks of it.
- Take her out. 🍝 Shopping at Annalie's, karaoke night, lunch or dinner at a restaurant, a few hands of blackjack. Ask her (the buttons in Settings, or just type "wanna grab dinner?") and it's her call - if she says yes she takes you there. She'll ask you out herself now and then too.
- Make you regret every bad decision Jun reacts to the way you treat her, for better or worse. Treat her well and she'll return it. Treat her badly and don't expect her to forget it.
- Bring your mods. Game-mod zips load straight into the browser.
- It's yours. She runs on your machine. No external account, no cloud inference by default, no analytics, nothing reporting back to us - the only things that leave your box are the ones you ask for: The full list.
Curious how any of it works? Under the hood.
You don't need to know how any of this works. You copy one line, paste it into a black window, and wait. That line installs whatever your computer is missing, downloads her brain, rebuilds her body from your own copy of the game, and starts her up.
The black window.
On Windows Press the Start button, type Command Prompt, hit Enter.
On Linux you should already know. Paste with Ctrl + Shift + V, then hit Enter.
⚠️ Before you paste anything, anywhere. The commands below download a script off the internet and run it on your computer. That's a lot of trust to hand a stranger, and the habit of doing it without looking is how people get malware. If you don't understand a command, don't run it - paste it into ChatGPT or Claude and ask what it does. Same goes for the next person's "just run this", not only ours.Want to read our script first? Good. 👀 Grab it without running it:
- Windows:
powershell irm https://raw.githubusercontent.com/efficiencyx/JunOS/main/install.ps1 -OutFile install.ps1- then openinstall.ps1in Notepad.- Linux / macOS:
curl -fsSL https://raw.githubusercontent.com/efficiencyx/JunOS/main/install.sh | less- pressqto quit when you're done reading.
She runs directly on Windows, no Docker (Docker on Windows is a pain). Pick one of these two lines - both do the same install, they just look different while they work.
With a window and buttons:
powershell irm https://raw.githubusercontent.com/efficiencyx/JunOS/main/installer-gui.ps1 -OutFile installer-gui.ps1; powershell -ExecutionPolicy Bypass -File .\installer-gui.ps1Or plain text scrolling by:
powershell irm https://raw.githubusercontent.com/efficiencyx/JunOS/main/install.ps1 -OutFile install.ps1; powershell -ExecutionPolicy Bypass -File .\install.ps1Needs git and Docker. Don't have them? The script installs them for you.
curl -fsSL https://raw.githubusercontent.com/efficiencyx/JunOS/main/install.sh | bashThe step-by-step commands for it, every provider, and the manual compose invocations live in the wiki.
Exactly one question: Express or Custom.
- Express - press Enter and forget about it. It figures out your hardware on its own and rebuilds her Live2D model from your game copy. The only thing it might still ask is where the game is, and only if it can't find it by itself.
- Custom - walks you through which provider, which model, which voice, and whether to turn on MTP.
Installing on a machine you can't sit in front of? JUN_YES=1 (on Windows, $env:JUN_YES='1') skips the question entirely.
Open your browser at https://localhost - or https://127.0.0.1:8080 if you're on Windows. It's always HTTPS 🔒, with a certificate your own install made for itself, so the browser grumbles about it (Advanced → Proceed). That's expected: nobody vouched for the cert, but the traffic is encrypted all the same. Plain http:// just redirects you over.
For a complete guide on how to manage her follow the steps in the wiki.
Windows:
Start her: powershell .\JunOS\start.ps1
Stop her: powershell .\JunOS\start.ps1 stop
Linux:
Start her: ./JunOS/start.sh
Stop her: ./JunOS/start.sh stop
The very first boot downloads her brain - whatever model is listed in OLLAMA_MODELS_TO_PULL, by default hf.co/efficiencyx/Jun-LoRA-E2B-GGUF:Q4_K_M, which is the one that runs fine without a fancy graphics card. It's a big file. On Ollama it also grabs the tiny model that names your chats (TITLE_MODEL, ~1.2 GB, lives on the CPU) and, if you said yes to MTP, the drafter. Watch it all crawl in with ./start.sh logs ollama.
She's ready once everything reports healthy. After that first download it's usually 30-90 seconds. 💤
The Live2D model and textures belong to My Dystopian Robot Girlfriend and aren't redistributed here - you rebuild them from your own copy of the game. Express does this for you; under Custom, answer yes when the installer offers. Either way it sets up a local
runtime/asset-recovery-venv(UnityPy + Pillow, no globalpip install) and runs the recovery script. 🔧Can't find the game? It'll ask you to paste the folder or drag the game onto the terminal - Express included, press Enter to skip. No game on that machine at all? Nothing breaks, she just wears the placeholders and the install carries on. Want it later, or don't want it at all?
JUN_EXTRACT=1re-runs it,JUN_EXTRACT=0tells Express to leave it alone ($env:JUN_EXTRACT='1'; .\install.ps1on Windows). If auto-detection misses the game, paste its folder when asked, or setJUN_GAME_DIRfor scripted installs.The result lands in
webapp/assets/and is for your own use - please don't republish it. It's gitignored so it can't get pushed by accident. See the NOTICE inLICENSE.
Run the whole stack on a free Colab T4 - nothing installed, nothing on your machine, just a Google account.
- Open
colab.ipynbwith the badge. - Runtime → Change runtime type → T4 GPU → Save.
- Runtime → Run all, wait for the ✅s (first run downloads a few GB, ~3–6 min).
- Click the public link printed at Step 3.
Two knobs in the cells: Model (auto takes the 12B since Colab can hold it) and Voice (adds ~2 min on first run). The link comes from Colab's own proxy - if it 404s briefly, wait 30 s and reload.
⚠️ It's a disposable demo. Colab wipes the session when it ends: accounts, chat history and weights don't survive. Run her locally for anything persistent.
⚠️ Warning: Do not publicly expose an installation that contains the original game assets.If you know what you're doing and have made sure no copyrighted game assets are publicly accessible, you can expose the application.
Domain pointed at your server? Ports 80 and 443 open?
Then you're ready to configure the reverse proxy.
BIND_ADDR=0.0.0.0 DOMAIN=yourdomain.com EMAIL=you@yourdomain.com TLS_MODE=on COMPOSE_PROFILES=prod ./start.shBIND_ADDR is the deliberate step: out of the box nginx publishes on 127.0.0.1 only, so a fresh install isn't reachable from the next machine on the wifi. Opening it up is a thing you type, not a default you inherit. Let's Encrypt also can't reach the challenge until you do.
You don't need a domain or a real certificate for that. Set one line in .env and restart:
BIND_ADDR=0.0.0.0The launcher works out this machine's address on the network by itself and prints it - reachable as: https://192.168.X.X on Linux, on your phone: https://192.168.X.X:8080 on Windows - and that's the URL you type into the phone. On Windows it also adds a firewall rule for the port on private networks only (it needs an admin PowerShell to do it, otherwise it prints the one-liner for you to run). One request at a time on Windows, so the phone and the desktop take turns.
The phone gets the same self-signed warning. Your password and every word she says cross the wifi encrypted, but a self-signed cert can't prove to the phone that it's really talking to your box, so keep it to networks you trust and never port-forward it to the internet - that's what the certbot setup above is for. 🔒 DHCP moves addresses around, so if she stops answering after a few days, restart the launcher and read the new one (the new address gets a fresh cert, so one more warning).
The prod profile adds the certbot sidecar. On a fresh box nginx comes up first on a throwaway self-signed cert (so for the first minute or so your browser will complain - that's expected), certbot then runs certbot certonly --webroot and nginx swaps the real cert in by itself, no restart. If issuance fails (usually DNS not pointing at the box yet, or port 80 blocked) certbot keeps retrying after 1, 4 and 16 minutes, then hourly - ./start.sh logs certbot tells you why. After that it's certbot renew every 12 hours, and nginx picks up renewed certs on its own too. Certs live in the letsencrypt volume, nginx serves 443 with HSTS. Mind Let's Encrypt's 5-duplicate-issuances-per-week limit while testing.
Hosting her for other people? Their chats now live on your box (encrypted) and you're responsible for them. Encrypt the machine and its backups, don't hand the database around, and edit
webapp/privacy.htmlto say what you actually store. In the EU that also makes you a deployer under the AI Act (art. 50 transparency, in force since 2 August 2026) - Jun ships the disclosure side already (age gate, permanentAIbadge, provenance metadata on generated speech), so please don't strip it out of your fork.
The fine-tune comes in 12B, E4B and E2B on Hugging Face. The installer picks a conservative quant for your VRAM; move a column right when the rest of your workload leaves room.
| VRAM | Default | Higher quality |
|---|---|---|
| 4 GB | E2B Q4_K_M | E2B Q6_K |
| 6 GB | E2B Q6_K | E2B Q8_0 |
| 8 GB | E4B Q4_K_M | E4B Q8_0 |
| 10 GB | E4B Q8_0 | 12B Q4_K_M |
| 12 GB | 12B Q4_K_M | 12B Q6_K |
| 16 GB | 12B Q6_K | 12B Q8_0 |
| 24 GB | 12B Q8_0 | - |
JUN_MODEL=12b|e4b|e2b picks a family at Q4_K_M; a full Ollama reference picks an exact quant. The frontend lists whatever's actually installed.
Gemma 4 ships a tiny drafter that guesses the next few tokens; Jun checks the whole batch in one pass and keeps the ones she agrees with, so those came almost free. Nothing she says changes - rejected guesses are thrown away - only the speed does. The installer offers it (JUN_MTP=on|off, on under Express) and then runs ./mtp-autotune.sh (mtp-autotune.ps1 on Windows) once she's up, measuring depths 1-4 against plain decoding with the real system prompt in front and writing the winner into .env. If nothing beats plain decoding it turns MTP back off and says so. Swap the GPU and the launcher notices (MTP_TUNED_GPU) and re-tunes on the next start; MTP_AUTOTUNE=off skips that. Budget the model, the drafter, and ~1.5 GB for the browser drawing Live2D on the same card. Knobs and the measurements behind the defaults: docs/configuration.md.
| Provider | What it is | Non-interactive install |
|---|---|---|
| Ollama (default) | Local inference, models pulled and pre-warmed for you | JUN_YES=1 ./install.sh |
| llama.cpp | A local llama-server - managed for you, or point at one you already run |
JUN_PROVIDER=llamacpp ./install.sh |
| OpenRouter | Any cloud model; needs a key, and your chats leave the machine | JUN_PROVIDER=openrouter OPENROUTER_API_KEY=sk-... ./install.sh |
llama.cpp can also serve a GGUF straight off your disk (LLAMACPP_MODELS_DIR + LLAMACPP_MODEL_FILE), and LLAMACPP_TOOLS=off exists for fine-tunes whose tool-call syntax llama-server can't parse.
No retrieval model: lore lookup and cross-chat recall are plain text matching - keyword/IDF over the corpus, word matching over your decrypted history - so lore and recall need no embedding model, whichever provider you choose. The only extra model in the stack is the Ollama-only chat titler (TITLE_MODEL, a 0.6B fine-tune pinned to the CPU so it never fights her for VRAM; set it empty and titles fall back to your first message).
| Backend | How start.sh spots it |
What it does |
|---|---|---|
| NVIDIA | nvidia-smi works, or /proc/driver/nvidia exists |
Adds docker-compose.nvidia.yml. Needs nvidia-container-toolkit. |
| AMD | /dev/kfd + /dev/dri/renderD* present |
Adds docker-compose.amd.yml (ROCm image, device passthrough, host video/render groups). Needs amdgpu. |
| CPU | nothing else matched | Base compose only - runs anywhere. |
| Intel | Not implemented | Do you have an Intel GPU? Contact me! |
| Apple Silicon | Not implemented | Do you have Apple Silicon? Contact me! |
Force it with GPU=nvidia|amd|cpu ./start.sh. Two cards? They're sorted by VRAM and the biggest is pinned as device 0, so she loads onto the one that can hold her; GPU_DEVICES overrides the list and TENSOR_PARALLEL=on spreads one model across all of them (slower per token - worth it only when nothing fits alone). AMD consumer cards that ROCm doesn't officially list may need HSA_OVERRIDE_GFX_VERSION=11.0.0 (RDNA3) or 10.3.0 (RDNA2).
Everything is environment variables in .env - the full reference is docs/configuration.md.
| Variable | What it does | Default |
|---|---|---|
DOMAIN / EMAIL / TLS_MODE |
Hostname, Let's Encrypt contact, Let's Encrypt on/off (off still serves HTTPS, on a self-signed cert) |
localhost · admin@localhost · off |
BIND_ADDR |
Where nginx (Caddy on Windows) listens. 0.0.0.0 for the phone on your wifi |
127.0.0.1 |
AI_PROVIDER |
ollama | llamacpp | openrouter |
ollama |
OLLAMA_MODELS_TO_PULL |
Pulled on first boot; the first one is pre-warmed | hf.co/efficiencyx/Jun-LoRA-E2B-GGUF:Q4_K_M |
LLAMACPP_MODEL_HF / LLAMACPP_MODEL_FILE |
Model for the managed llama-server: pull from HF, or serve one off disk | efficiencyx/Jun-LoRA-E2B-GGUF:Q4_K_M |
TITLE_MODEL |
Ollama only. The little CPU-pinned model that names new chats; empty disables it | hf.co/efficiencyx/Titlewen-GGUF:F16 |
OLLAMA_MTP / LLAMACPP_MTP |
HF repo of the MTP drafter; empty = off. MTP_AUTOTUNE=off stops the re-tune after a GPU change |
(empty) · on |
COMPOSE_PROFILES |
Optional containers: ollama, llamacpp, voice, karaoke, prod. start.sh derives them from the knobs below, you only set this by hand for prod |
ollama |
VOICE |
off skips the voice sidecar: the tts container under Docker (voice profile), the sidecar process on bare-metal Windows. Chat degrades to text-only |
on |
FLEE_BANS |
When she walks out of a scene, on locks that account out of chat for 5 min, doubling per repeat up to 30. off lets her leave without the lockout |
on |
FREE_ROAM |
Going out is her call: the shop, karaoke and date pages bounce home unless she agreed in chat. on drops that gate and gives everyone the "Force her" buttons (admins have them regardless) |
off |
TTS_DEVICE |
Voice synthesis device. Keep it on CPU: both engines are real-time there, and a GPU copy parks ~2 GB your LLM wants more | cpu |
KARAOKE / SEP_DEVICE |
Karaoke sidecar on/off, and where stem separation runs. This is the audio job that wants a GPU - minutes on CPU, seconds on a card, VRAM handed back after | on · auto |
STT_MODEL / STT_LANG |
Whisper size and language; blank lang = per-utterance auto-detect | base · (auto) |
OMEGA_NUM_CTX |
Context window. Auto-sized from leftover VRAM (falling back to RAM) - pin it if she's crowding your card | (auto) |
OMEGA_STATE_DIR / MEMORY_DIR |
Where the database, rate-limit state and memory notes live | /var/lib/omega |
OMEGA_REGISTRATION_KEY |
Sign-ups need this key. The installer generates one for you; empty it to let anyone in | (generated) |
OMEGA_DEV_KEY |
? Figure it out yourself? | ??? |
Save your recovery code. 🔐 Signup shows it once; older accounts get one on their first login after the encryption upgrade. Chats, saved preferences and her memory are encrypted under your account's data key. If you forget your password, use forgot password? with your email and recovery code. Lose both and a backup alone cannot unlock encrypted data. This protects stored content, not a compromised running machine; the security notes spell out the limits.
The registration key is written into .env by the installer and printed when it finishes. The very first account on a fresh install skips it (it's your box, you just ran the installer); every account after that has to type it, so nobody who reaches the page later can make themselves a login. Don't want the lock? Empty the value (OMEGA_REGISTRATION_KEY=) and sign-ups are open to whoever can reach the page. 🔑 Lost it? It's sitting in plain text in your own .env - read it back, or change it to whatever you like and restart.
Normal accounts get Factory Reset in the same panel - one button that erases every conversation, memory and setting and hands the account back the way it came.
Browser ──HTTP/SSE──▶ nginx ──FastCGI──▶ php-fpm ──HTTP──▶ ollama / llama-server
│ │
│ ├──────────────▶ tts :8001 (TTS + STT, CPU)
│ └──────────────▶ karaoke :8001 (demucs, GPU)
└── serves /var/www/omega/ (static assets, JS, Live2D model)
The stack: PHP 8.2 for the SSE proxy and retrieval · Python FastAPI + Kokoro-82M / pocket-tts / faster-whisper / demucs for the ears, voice and karaoke · plain HTML/CSS/ES modules up front with PIXI.js + pixi-live2d-display + Cubism 4, all vendored · SQLite · nginx + php-fpm. No bundler, no node_modules, no build step.
What happens when you hit send:
- The browser
POSTs to/api/chat.php. - PHP assembles the prompt in two halves. The cached half -
system_prompt.txtand her journal - stays byte-identical between turns so the KV prompt cache keeps hitting. Everything that moves goes in a live-context block glued after your question in the last user turn: clock, matched lore facts, durable notes, outfit, relationship gauges. Dead last comes a<think:low|med|high>marker telling her how hard to think this turn. - The model streams back (NDJSON from Ollama, OpenAI-style SSE from the others);
api/lib/providers/normalizes both and PHP re-frames each token as an SSE event and flushes it immediately. If she reaches for a tool (search her notes, change outfit, look something up, take you out, walk out) PHP runs it and streams another round, up to three. js/app/stream-filters.jswatches the stream for[A:markers, holds back any half-typed marker so it never renders, and fires the action the instant its]arrives.js/live2d/live2d.jslerps the model toward the new pose; if voice is on,js/voice/tts.jsfetches audio per sentence and drivesParamMouthOpenfrom the analyser's RMS.- Bookkeeping happens after generation has finished and every reply token has already streamed, right before
[DONE]- so it can't delay a word she says, only the final[DONE]: her hidden[A:mood_shift|...]tag moves the gauges, the reply gets saved, and a new chat gets its title (that one is a call to the titling model, so on a chat's very first turn[DONE]can trail the last word by a moment). Wander off and the consolidation worker rewrites her notes and journal.
The gory version - the action state machine, the tick loop, the memory pipeline - is in docs/architecture.md.
📦 How this repo gets updated. Day-to-day work happens in a development repo - that's where the extracted Live2D art lives, plus the dataset tooling and a pile of half-finished experiments nobody needs to see. What lands here is snapshots: working states, pushed when something's actually done, reviewed and CI passes. So the commits are chunky and the timestamps come in bursts.
🎨 Her art isn't in here and won't be - see the NOTICE in LICENSE.
tools/recover_assets.pyrebuildswebapp/assets/from your own copy of the game, which is the only way it's allowed to work. Fresh clone looks a bit naked until you run it.
The house rules, the invariants you can break without noticing, and what CI checks are in CONTRIBUTING.md. Stuck rather than hacking? SUPPORT.md. Found a hole? SECURITY.md.
Editing anything under webapp/? Run ./sync-webapp.sh - it pushes the files into the running containers and restarts php-fpm (opcache won't notice otherwise). -s for static-only.
Lore datamine from LLMs lives in tools/lore_dataset.jsonl (kept local, not in the repo); rebuild the index after editing it:
docker compose exec php php tools/build_lore_index.phpThat flattens each Q&A into webapp/lore_corpus.txt - one answer per line, matched at request time by keyword/IDF. No embeddings, no second model. A missing corpus is fine; retrieval just gets skipped.
Replies arrive all at once instead of streaming
Something's buffering the SSE. The /api/chat.php location needs proxy_buffering off;, fastcgi_buffering off; and X-Accel-Buffering: no. A load balancer in front of nginx can sneak buffering back in.
She's not broken, just slow
Check GPU residency first: docker exec omega-ollama ollama ps. Anything short of 100% GPU means something is elbowing her out of VRAM - usually TTS_DEVICE=cuda (~2 GB for a speedup you won't hear) or a too-generous num_ctx; pin it with OMEGA_NUM_CTX. The UNTIL column should read Forever; a five-minute expiry means the keep-alive pin didn't take and the next quiet stretch costs you a full reload plus re-prefill.
Jun is invisible
Open the console - a missing texture shows up as a 404. Confirm webapp/assets/*.png exist (see her body isn't in this repo) and that nginx's root points at /var/www/omega/.
No voice
First check VOICE in .env - off means the tts container was never started (./start.sh lists the compose profiles it resolved). Then ./start.sh logs tts. The first run downloads ~300 MB of weights. From inside the stack, docker compose exec nginx wget -qO- http://tts:8001/health should return {"ok":true} - the sidecar's port is deliberately not published to the host, and everything but /health wants the X-Sidecar-Secret header PHP adds (as long as SIDECAR_SECRET is set, which the launchers do for you).
She walked out and now every message is refused
That's the flee lockout, not a bug: the stream answers user_fled with a countdown, and the chat UI shows it. 5 minutes the first time, doubling per repeat up to 30, reset after a day of good behaviour. FLEE_BANS=off in .env lets her leave a scene without locking the account. A referee pass has to agree she could physically leave and had a reason, so "I told her to test the flee tool" doesn't count.
The karaoke button is greyed out
Karaoke is its own container, so it needs KARAOKE=on in .env (./start.sh prints karaoke: off when it isn't). Check ./start.sh logs karaoke and docker compose exec nginx wget -qO- http://karaoke:8001/health - "sep":true means separation is ready and "device" says whether it got the GPU.
Getting 429s while chatting
The rate limiter tripped, and there are two layers: limit_req / limit_conn per location in the nginx template (/api/chat.php allows 2 open streams per IP) and rate_limit('chat', 90, 60) in webapp/api/chat.php. The stricter one wins, so raise both.
CSP violations in the console
Everything ships from her own origin, so a clean install produces none - if you see them, something you added is reaching for a third-party host. Whitelist that specific origin in docker/nginx/templates/omega.conf.template, never *. Nginx templates are baked into the image, so this needs ./start.sh --build; sync-webapp.sh won't pick it up.
Running compose by hand and nothing starts
The model-server and voice containers are profile-gated. ./start.sh derives COMPOSE_PROFILES from your .env; a bare docker compose up -d needs COMPOSE_PROFILES=ollama,voice (or llamacpp,voice, plus karaoke if you want it) set in .env or the shell.
.
├── docker/ Dockerfiles, nginx templates + security-headers snippet, entrypoints
├── tts/ Audio sidecar: TTS + STT + karaoke separation (FastAPI, server.py + sidecar/)
├── tools/ Lore builder, critical-CSS inliner, asset recovery, the bare-metal php router
├── docs/ architecture.md, configuration.md, wiki/ (the GitHub wiki source), screenshots/
├── android/ Same webapp on a phone: Ktor server + LiteRT-LM on-device, its own Gradle project
├── webapp/ Everything nginx and php-fpm serve
│ ├── api/ chat.php, auth.php, memory.php, outfit.php, karaoke.php, trip.php, migrations/, …
│ │ └── lib/ the shared stuff: bootstrap, db, crypto, memory, providers/, chat/, consolidation/ (never served)
│ ├── js/ one entry per page (app.js is just the login gate) + the folders they share:
│ │ core/, app/, live2d/, outfit/, wardrobe/, mods/, voice/, karaoke/, trip/
│ ├── css/ base, shell, chat, stage, sidebar, settings, widgets, welcome, voice-karaoke, cards, subpage, responsive
│ │ ├── boot.css Critical CSS, inlined into index.html at sync time
│ │ └── pages/ one sheet each for the date, wardrobe, karaoke and privacy pages
│ ├── scene/ The rooms she goes out to (shop, karaoke lounge, restaurants, card table) as SVG
│ ├── vendor/ PIXI, Cubism core, pixi-live2d-display, marked, DOMPurify (no CDN)
│ ├── assets/ Live2D model files - you generate these, gitignored
│ ├── wardrobe.html · karaoke.html · date.html The outing pages
│ └── system_prompt.txt
├── install.sh · install.ps1 One-line bootstrap (Docker · bare metal)
├── installer-gui.ps1 The Windows click-through window (ships as JunSetup.exe)
├── uninstall.sh · uninstall.ps1 Takes her off the box again
├── start.sh · start.ps1 Launchers, and the stop/status/logs control panel
├── mtp-autotune.sh · .ps1 Measures the MTP draft depth and writes the winner to .env
├── sync-webapp.sh The dev loop
├── colab.ipynb The free-GPU notebook
├── .github/workflows/ CI (syntax, stream-buffer tests, a chat turn against a fake Ollama) + the JunSetup.exe release
└── docker-compose*.yml Base (CPU) + nvidia / amd overlays, llamacpp-local / llamacpp-mtp add-ons
PIXI.js · pixi-live2d-display · Live2D Cubism SDK · Ollama · llama.cpp · Kokoro-82M · pocket-tts · faster-whisper · demucs · marked + DOMPurify
And of course Incontinent Cell, creator of Factorial Omega (MDRG), for giving us Jun in the first place.
MIT - see LICENSE. Unofficial, non-commercial fan project; all Factorial Omega rights belong to their respective owners.



