How to run everything.
One repo, fourteen pages, four live watchers, zero frameworks. This page is the operator's manual: how to serve the site, use the Twin OS, run the CLIP Interrogator and Refael, drive the chart pipeline and song indexer, run the tests, and understand what the LaunchAgents are doing on this machine.
Quickstart
Everything is static files. Clone, serve the repo root, open a page.
Requirements
- macOS (the LaunchAgents are launchd-based), python3 (any recent), node ≥ 18 for the e2e tests.
- uv only for the CLIP Interrogator (install). Everything else is stdlib.
- MuseScore 4 (mscore) + the bundled MS Basic.sf3 only for the chart render step. ffmpeg + ffprobe for the reel pipeline — the daily run's plates become vertical videos with them.
- Apple Silicon + 24 GB unified memory for the FLUX.2 renderer — local MPS text-to-image, torch on the CPU/MPS wheel index (no CUDA). npm run flux2:setup then npm run render:flux2 -- --prompt "…".
- A logged-in X session in Chrome only for posting to X — npm run x:capture reads it into a jar outside the repo. Reference: docs/X-COOKIES.md.
- A logged-in Midjourney session in Chrome for the daily content run — it types into midjourney.com through a real browser, because the CDN answers curl and headless clients with 403. Reference: docs/DAILY-CONTENT.md.
Serve the site
git clone https://github.com/kajica2/digital-twin.git
cd digital-twin
node lib/serve.js --port 5180
# open http://127.0.0.1:5180/pages/landing.htmlRepo-root serving is the one true convention — it matches GitHub Pages, the launchd dev server, and CI's PR-mode server. If a page 404s, you're serving the wrong root.
Page map
| Page | What it's for |
|---|---|
pages/landing.html | The public face — three A/B-testable variants (?v=1|2|3). |
pages/twin-os/ | The Twin OS PWA shell — Today / Songs / Twin panels, installable, IndexedDB-backed. |
pages/cognitive-twin.html | The 4-layer architecture narrative (Scanner → Model → Reasoner → Orchestrator). |
pages/how-to.html | This page — the operator's manual. |
pages/clip-interrogator.html | CLIP tool page — image→prompt + image analysis (Python backend). |
pages/refael-mp4-maker.html | Offline MP3→MP4 maker with mood-keyword auto-covers. |
pages/loopable-video-segmenter.html | MP4 → N beat-aligned mirror-loopable clips (Python backend). |
pages/agent-dashboard.html | Live view of the persistent agent loop — run rewrites it on pause/exit and every 10 iterations; dashboard --out by hand. |
pages/marketplace.html | Mood-frame listing — eight music-video stills, three studio extras, twelve cover studies and ten System Error plates in the house sound-tones, each with its marketplace blurb, plus the Daily Frames section the nightly run fills. |
pages/mj-sheets.html | Midjourney comparison sheet — one brief rendered at twelve camera angles, each tile labelled with the clause that produced it. Built by lib/mj-contact-sheet.js; reference: docs/MJ-CONTACT-SHEET.md. |
pages/temple-of-control.html | Three temperature sliders (heat / breath / glow) driving a meditation visual; MIDI-driven, pluggable on any page via lib/meditate-temperature.js. |
pages/floating-toolset.html | The floating toolset — a draggable TODAY / NOW PLAYING / CODE / PATTERNS panel; drop-in on any page via lib/floating-toolset.js. Reference: docs/FLOATING-TOOLSET.md. |
pages/integrations.html | Connect external services — enter API keys and cookies for Midjourney, Instagram, YouTube. Styled like Desktop Commander's welcome page. |
pages/persona-social-pack.html | Persona Social Pack — a URL of persona descriptions in, three hashtag tiers + YouTube metadata + four social posts per persona out. Built by lib/persona-social-pack.js; reference: docs/PERSONA-SOCIAL-PACK.md. |
Twin OS
The actual app. Three panels, local-first, zero backend.
open pages/twin-os/ # or serve the repo root and visit /pages/twin-os/- Today — the agenda. Seeded items are static; add your own via the textfield (persisted in localStorage["dt-agenda-items"]), remove with ×.
- Songs — the audio catalog. Three states: loading, configured-empty (tells you the configured roots), and catalog (grouped by label, searchable). Tools grid below it links MuScriptor, CLIP Interrogator, and Refael.
- Twin — your twin panel (pattern cards + stats).
Installable as a PWA (manifest + service worker: cache-first shell, network-first catalog). Theme toggle persists to localStorage["dt-theme-pref"] with a no-flash bootstrap.
CLIP Interrogator
Turn an image into a text prompt, or analyze it into label tables. Python package in lib/clip-interrogator/, uv-managed, device pinned to CPU (deterministic; MPS crashes the label tables).
Setup (once)
npm run clip:setup # uv sync --project lib/clip-interrogator
npm run clip:check # import + device + registry, downloads NO weightsWeights download on first real use (~1-2 GB) into your HF cache and the vocab cache at ~/.cache/clip-interrogator.
UI
npm run clip:ui # gradio app on http://127.0.0.1:7860 (two tabs: Prompt / Analyze)CLI
npm run clip:interrogate -- path/to/image.jpg --model vit-l --mode best
npm run clip:interrogate -- path/to/image.jpg --model vit-h --mode classic --json
npm run clip:interrogate -- path/to/image.jpg --model vit-l --mode fast --json- Models: vit-l (default, ViT-L/14) and vit-h (ViT-H, materializes only when selected — --model vit-h never downloads ViT-L).
- Modes: best / fast / classic / negative. --json keeps stdout parseable (library banners go to stderr).
- analyze verb prints five label tables (medium / artist / movement / trending / flavor); --top 5 default.
Refael MP4 Maker
MP3 → MP4 with mood-keyword auto-covers, at the resolution you choose (HD · 1920×1080 by default, or Regular · 1280×720 from the Resolution dropdown). Single self-contained HTML file, works from file://.
open pages/refael-mp4-maker.html
# or served: http://127.0.0.1:5180/pages/refael-mp4-maker.html- Drop an MP3, pick a track name (or 🎲 random), choose a render mode + resolution.
- Three engines: ⚡ Fast (WebCodecs, fully offline — the default, zero network calls), 🛡 FFmpeg.wasm (loads the repo's vendored, sha256-pinned core from /vendor/ffmpeg — no CDN, works offline), 🐢 Real-time (MediaRecorder). Resolution is consumed by all three: WebCodecs codec string + bitrate, FFmpeg -vf scale, MediaRecorder videoBitsPerSecond.
- Single / Custom image / Batch tabs; deterministic cover art from track + artist (dark/light/fire/love/ocean/earth, EN + SR-Cyrillic).
- ?selftest=1 runs a built-in end-to-end render self-test (manual smoke lever — WebCodecs is flaky in headless CI).
Chart pipeline
MusicXML in → full chart package out: per-part PDFs, whole-score PDF, Demo MP3, muted backing tracks, multi-track MIDI.
One-shot export
npm run export-all -- /path/to/song.musicxml
# produces: Full_Score.pdf + per-part PDFs (canonical band order) +
# Demo.mp3 + No_Trumpet.mp3 + No_Sax.mp3 + song.mid- Part splitting + selective muting are automated; MuseScore renders the PDFs/MP3s (bundled MS Basic.sf3 soundfont).
- --chords '{"m1":["Dm7","G7sus"]}' injects chord-symbol harmony into the MusicXML.
- npm run export-mp3 -- song.musicxml re-renders audio only (~2× faster). ffprobe validates every MP3 duration when present.
- Engraving conventions live in docs/COLTRANE-SHAW-ENGRAVING.md — post-bop/modal jazz rules (staff order, Trane slurs, Shaw pops, don't over-notate drums).
Watched inbox (the zero-touch path)
cp new-chart.musicxml chart-inbox/
# the chart-watcher LaunchAgent (live) picks it up within ~2s,
# exports the full package, moves the input to chart-inbox/processed/,
# and logs to logs/chart-watcher.log. Retries once on failure.Manual run: node lib/chart-watcher.js --once. Poll loop, not fs.watch — 2s is calm and predictable.
Song indexer
Walks your audio folders, reads MP3 ID3v2 / WAV / AIFF metadata, writes a searchable catalog for the Songs panel.
Manual rebuild
# point lib/sources.config.json at where audio actually lives, then:
npm run index-songs # writes data/songs/catalog.json
# absolute paths are stripped from shipped output by defaultWatched inbox
cp new-track.mp3 audio-inbox/
# the songs-watcher LaunchAgent (live) runs the indexer automatically,
# logs to logs/songs-watcher.logTesting
Puppeteer specs for pages, unit tests for the watchers and the agent loop. Every sprint ships green.
cd e2e && npm install
node lib/serve.js --port 5180 & # repo-root server, one true convention
npm run verify # landing + twin-os + twin-os-songs
npm run e2e:ct # 8 cognitive-twin specs (needs Chrome)
npm run e2e:clip # CLIP page contract (no Python deps)
npm run e2e:refael # Refael page contract
npm run test:all # 19 unit suites: watchers, MJ, jazz-solos, agent-loop, toolset- CI runs the deployed URL on push (deploy-test loop: deploy is not done until the e2e hits the live site) and the local repo-root server on PRs.
- Watcher specs are filesystem-only — run locally, not in CI (they touch LaunchAgent state).
- 0 console errors is a hard contract. If a spec reddens, fix the page, not the test — unless the test itself is wrong.
LaunchAgents
The twin runs itself via launchd. State as of this writing:
| Agent | Job | State |
|---|---|---|
digital-twin.server | Keeps the repo served on :5173 (node lib/serve.js) | live |
digital-twin.chart-watcher | Auto-exports anything dropped in chart-inbox/ | live |
digital-twin.songs-watcher | Auto-rebuilds the catalog from audio-inbox/ | live |
digital-twin.mj-watcher | Submits Midjourney prompts dropped in the MJ inbox | live |
digital-twin.boot | At login: auto-update → ensure server → open Twin OS → Apple Notes status | at login |
digital-twin.log-rotate | Daily 03:00 — rotates logs/*.log, keeps 7 gzipped days | 03:00 daily |
# verify a watcher is actually alive (PID, not just "loaded")
pgrep -fl "chart-watcher|songs-watcher|mj-watcher"
# taps
tail -f logs/chart-watcher.log logs/songs-watcher.log
# teardown / reinstall per-service docs
launchctl bootout gui/$(id -u) ~/Library/LaunchAgents/com.kaidjuric.digital-twin.<service>.plistPer-service install + verify steps: docs/CHART-WATCHER.md, docs/SONGS-WATCHER.md, docs/MJ-WATCHER.md.
cd ~/digital-twin && git pull --ff-only \
&& launchctl kickstart -k gui/$(id -u)/com.kaidjuric.digital-twin.serverIf a page 404s on :5173, the operational clone is stale — pull it, don't edit the plists. The watchers all live there too (chart-inbox/, audio-inbox/, prompts-inbox/).
Troubleshooting
| Problem | Fix |
|---|---|
| Port already in use | lsof -ti :5180 | xargs kill -9 (or :5173), then restart the server. |
| Page 404s locally | You're serving pages/ instead of the repo root. Serve --directory . — every page lives under /pages/. |
| Songs panel says "empty" | Point lib/sources.config.json at real audio, re-run npm run index-songs. |
| MuseScore exits non-zero but PDFs/MP3s exist | Known macOS Qt quirk — the pipeline validates by file existence + ffprobe, not exit code. Check the artifacts. |
| CLIP crashes on MPS | By design it pins CPU (fp32 determinism). Don't "fix" it to MPS — see the callout in §03. |
fails: No module named 'clip_interrogator_app' | Stale uv cache. uv sync --project lib/clip-interrogator --reinstall --no-cache. |
| e2e says 0 console errors broken | A missing favicon makes Chrome auto-request /favicon.ico (404). Every page declares a data-URI favicon — keep it that way. |
| Committing for this repo | Use the repo author: -c user.name="Kajica Djuric" -c user.email="kai.djuric@gmail.com". |
Memory
Project intelligence lives in .kai/ (gitignored, local-only): conventions, ADRs, tech-debt register, prevention rules. Read .kai/memory.yaml first on any hard problem. Never store secrets there.