seed_production.py
python
| 1 | """ |
| 2 | Production seed for musehub.ai |
| 3 | |
| 4 | Creates gabriel's account + 5 code repos + 5 MIDI repos. |
| 5 | Intentionally minimal — no fake community users, no binary objects. |
| 6 | |
| 7 | Run inside the container: |
| 8 | docker compose exec musehub python3 /app/scripts/seed_production.py |
| 9 | |
| 10 | Idempotent: safe to re-run (skips existing records). |
| 11 | """ |
| 12 | from __future__ import annotations |
| 13 | |
| 14 | import asyncio |
| 15 | import hashlib |
| 16 | import uuid |
| 17 | from datetime import datetime, timedelta, timezone |
| 18 | |
| 19 | from sqlalchemy.ext.asyncio import AsyncSession, create_async_engine |
| 20 | from sqlalchemy.orm import sessionmaker |
| 21 | |
| 22 | from musehub.config import settings |
| 23 | from musehub.db.models import User |
| 24 | from musehub.db.musehub_models import ( |
| 25 | MusehubBranch, |
| 26 | MusehubCommit, |
| 27 | MusehubIssue, |
| 28 | MusehubProfile, |
| 29 | MusehubRelease, |
| 30 | MusehubRepo, |
| 31 | ) |
| 32 | from musehub.auth.tokens import generate_access_code |
| 33 | |
| 34 | UTC = timezone.utc |
| 35 | |
| 36 | |
| 37 | def _now(days: int = 0, hours: int = 0) -> datetime: |
| 38 | return datetime.now(tz=UTC) - timedelta(days=days, hours=hours) |
| 39 | |
| 40 | |
| 41 | def _sha(seed: str) -> str: |
| 42 | return hashlib.sha256(seed.encode()).hexdigest() |
| 43 | |
| 44 | |
| 45 | def _uid(seed: str) -> str: |
| 46 | return str(uuid.UUID(bytes=hashlib.md5(seed.encode()).digest())) |
| 47 | |
| 48 | |
| 49 | # ── Stable IDs ──────────────────────────────────────────────────────────────── |
| 50 | |
| 51 | GABRIEL_ID = _uid("prod-gabriel-cgcardona") |
| 52 | |
| 53 | # ── Repo definitions ────────────────────────────────────────────────────────── |
| 54 | |
| 55 | CODE_REPOS: list[dict] = [ |
| 56 | dict( |
| 57 | repo_id=_uid("prod-repo-muse"), |
| 58 | slug="muse", |
| 59 | name="muse", |
| 60 | description=( |
| 61 | "A domain-agnostic version control system for multidimensional state. " |
| 62 | "Not just code — any state space where a 'change' is a delta across " |
| 63 | "multiple axes simultaneously: MIDI (21 dims), code (AST), genomics, " |
| 64 | "3D design, climate simulation." |
| 65 | ), |
| 66 | tags=["vcs", "cli", "domain-agnostic", "open-source"], |
| 67 | domain_meta={"primary_language": "TypeScript", "languages": {"TypeScript": 68, "Python": 20, "Shell": 12}}, |
| 68 | ), |
| 69 | dict( |
| 70 | repo_id=_uid("prod-repo-musehub"), |
| 71 | slug="musehub", |
| 72 | name="musehub", |
| 73 | description=( |
| 74 | "MuseHub — the collaboration hub for Muse repositories. GitHub for " |
| 75 | "multidimensional state. Browse commits, open PRs, track issues, " |
| 76 | "discover domain plugins, and expose everything via MCP for AI agents." |
| 77 | ), |
| 78 | tags=["platform", "fastapi", "mcp", "htmx"], |
| 79 | domain_meta={"primary_language": "Python", "languages": {"Python": 55, "TypeScript": 28, "SCSS": 12, "HTML": 5}}, |
| 80 | ), |
| 81 | dict( |
| 82 | repo_id=_uid("prod-repo-agentception"), |
| 83 | slug="agentception", |
| 84 | name="agentception", |
| 85 | description=( |
| 86 | "AgentCeption — agents that build agents. A multi-agent orchestration " |
| 87 | "framework where specialized AI subagents collaborate, spawn sub-tasks, " |
| 88 | "and maintain shared state via Muse. MCP-native." |
| 89 | ), |
| 90 | tags=["ai", "agents", "mcp", "orchestration"], |
| 91 | domain_meta={"primary_language": "Python", "languages": {"Python": 78, "TypeScript": 15, "Shell": 7}}, |
| 92 | ), |
| 93 | dict( |
| 94 | repo_id=_uid("prod-repo-maestro"), |
| 95 | slug="maestro", |
| 96 | name="maestro", |
| 97 | description=( |
| 98 | "Maestro — an AI conductor for multi-model workflows. Route tasks to " |
| 99 | "the right model, blend outputs, and orchestrate long-horizon plans " |
| 100 | "across GPT-4o, Claude, Gemini, and local models." |
| 101 | ), |
| 102 | tags=["llm", "orchestration", "ai", "multi-model"], |
| 103 | domain_meta={"primary_language": "Python", "languages": {"Python": 82, "TypeScript": 12, "Shell": 6}}, |
| 104 | ), |
| 105 | dict( |
| 106 | repo_id=_uid("prod-repo-stori"), |
| 107 | slug="stori", |
| 108 | name="stori", |
| 109 | description=( |
| 110 | "Stori — a Muse-native story engine. Version-controlled narrative: " |
| 111 | "branching plotlines, character arcs, world state, and dialogue trees " |
| 112 | "tracked as multidimensional commits." |
| 113 | ), |
| 114 | tags=["storytelling", "narrative", "vcs", "game-dev"], |
| 115 | domain_meta={"primary_language": "Python", "languages": {"Python": 61, "TypeScript": 31, "HTML": 8}}, |
| 116 | ), |
| 117 | ] |
| 118 | |
| 119 | MIDI_REPOS: list[dict] = [ |
| 120 | dict( |
| 121 | repo_id=_uid("prod-repo-midi-bach-wtc"), |
| 122 | slug="well-tempered-clavier", |
| 123 | name="well-tempered-clavier", |
| 124 | description=( |
| 125 | "J.S. Bach — The Well-Tempered Clavier, Books I & II. " |
| 126 | "All 48 preludes and fugues across all 24 major and minor keys. " |
| 127 | "MIDI transcription from the public domain Mutopia Project." |
| 128 | ), |
| 129 | tags=["bach", "baroque", "piano", "fugue", "public-domain"], |
| 130 | domain_meta={"key_signature": "C major", "tempo_bpm": 72}, |
| 131 | ), |
| 132 | dict( |
| 133 | repo_id=_uid("prod-repo-midi-moonlight"), |
| 134 | slug="moonlight-sonata", |
| 135 | name="moonlight-sonata", |
| 136 | description=( |
| 137 | "Beethoven — Piano Sonata No. 14 in C# minor, Op. 27 No. 2 'Moonlight'. " |
| 138 | "All three movements with pedal and expression markings preserved." |
| 139 | ), |
| 140 | tags=["beethoven", "classical", "piano", "sonata", "public-domain"], |
| 141 | domain_meta={"key_signature": "C# minor", "tempo_bpm": 54}, |
| 142 | ), |
| 143 | dict( |
| 144 | repo_id=_uid("prod-repo-midi-neobaroque"), |
| 145 | slug="neobaroque-sketches", |
| 146 | name="neobaroque-sketches", |
| 147 | description=( |
| 148 | "Original compositions in a neo-baroque style — fugues, inventions, " |
| 149 | "and canons written with Muse. Counterpoint in a contemporary harmonic " |
| 150 | "language. All tracks generated by Muse." |
| 151 | ), |
| 152 | tags=["original", "baroque", "counterpoint", "fugue", "muse"], |
| 153 | domain_meta={"key_signature": "G major", "tempo_bpm": 84}, |
| 154 | ), |
| 155 | dict( |
| 156 | repo_id=_uid("prod-repo-midi-modal"), |
| 157 | slug="modal-sessions", |
| 158 | name="modal-sessions", |
| 159 | description=( |
| 160 | "Improvisation studies in modal jazz — Dorian, Phrygian, Lydian. " |
| 161 | "Inspired by Miles Davis 'Kind of Blue'. Each commit is a live session take." |
| 162 | ), |
| 163 | tags=["jazz", "modal", "improvisation", "miles-davis"], |
| 164 | domain_meta={"key_signature": "D Dorian", "tempo_bpm": 120}, |
| 165 | ), |
| 166 | dict( |
| 167 | repo_id=_uid("prod-repo-midi-chopin"), |
| 168 | slug="chopin-nocturnes", |
| 169 | name="chopin-nocturnes", |
| 170 | description=( |
| 171 | "Chopin — Nocturnes Op. 9 and Op. 27. MIDI transcriptions with pedal " |
| 172 | "and expression markings. Public domain, sourced from the Mutopia Project." |
| 173 | ), |
| 174 | tags=["chopin", "romantic", "piano", "nocturne", "public-domain"], |
| 175 | domain_meta={"key_signature": "B-flat minor", "tempo_bpm": 60}, |
| 176 | ), |
| 177 | ] |
| 178 | |
| 179 | # ── Commit messages ─────────────────────────────────────────────────────────── |
| 180 | |
| 181 | COMMITS: dict[str, list[tuple[str, int]]] = { |
| 182 | "muse": [ |
| 183 | ("init: scaffold domain-agnostic object model", 180), |
| 184 | ("feat: content-addressed object store (SHA-256)", 170), |
| 185 | ("feat: snapshot and commit layer", 160), |
| 186 | ("feat: branch and ref pointers", 150), |
| 187 | ("feat: muse clone over HTTP", 140), |
| 188 | ("feat: muse push — upload objects + update refs", 130), |
| 189 | ("feat: muse pull — fetch and merge remote refs", 120), |
| 190 | ("feat: MIDI domain plugin (21-dimensional state)", 110), |
| 191 | ("feat: code domain plugin (AST-based diff)", 100), |
| 192 | ("fix: handle empty repo on first push", 90), |
| 193 | ("perf: pack objects for transfer efficiency", 80), |
| 194 | ("feat: muse log — pretty commit history", 70), |
| 195 | ("feat: muse diff — dimensional delta view", 60), |
| 196 | ("feat: muse tag — annotated and lightweight", 50), |
| 197 | ("fix: ref resolution with detached HEAD", 40), |
| 198 | ("feat: muse stash save/pop", 30), |
| 199 | ("docs: getting started guide", 20), |
| 200 | ("chore: release v0.3.0", 10), |
| 201 | ], |
| 202 | "musehub": [ |
| 203 | ("init: FastAPI skeleton + Alembic migrations", 180), |
| 204 | ("feat: JWT Bearer token auth + invite-only gating", 170), |
| 205 | ("feat: repo CRUD with visibility guard", 160), |
| 206 | ("feat: commit and branch endpoints", 150), |
| 207 | ("feat: issue tracker — open/close/comment", 140), |
| 208 | ("feat: pull requests + review system", 130), |
| 209 | ("feat: MCP 2025-11-25 Streamable HTTP endpoint", 120), |
| 210 | ("feat: 37 MCP tools for repo operations", 110), |
| 211 | ("feat: 27 muse:// resource URIs", 100), |
| 212 | ("feat: HTMX UI — explore, profile, repo home", 90), |
| 213 | ("feat: MIDI domain viewer — piano roll, graph, insights", 80), |
| 214 | ("feat: domain plugin registry (V2)", 70), |
| 215 | ("feat: oEmbed + sitemap.xml", 60), |
| 216 | ("security: HSTS, CSP, CORS hardening", 50), |
| 217 | ("feat: Let's Encrypt + nginx production deploy", 40), |
| 218 | ("feat: production seed — gabriel's repos", 30), |
| 219 | ("chore: launch musehub.ai", 10), |
| 220 | ], |
| 221 | "agentception": [ |
| 222 | ("init: multi-agent orchestration framework", 180), |
| 223 | ("feat: subagent spawning via MCP tool calls", 170), |
| 224 | ("feat: shared Muse state across agents", 160), |
| 225 | ("feat: agent skill system — SKILL.md format", 150), |
| 226 | ("feat: parallel agent execution", 140), |
| 227 | ("feat: resume agent from prior context", 130), |
| 228 | ("feat: browser-use subagent type", 120), |
| 229 | ("feat: shell subagent type", 110), |
| 230 | ("feat: explore subagent — codebase analysis", 100), |
| 231 | ("feat: generalPurpose subagent — reasoning + search", 90), |
| 232 | ("fix: agent context window management", 80), |
| 233 | ("feat: streaming output from subagents", 70), |
| 234 | ("feat: agent transcript storage in Muse", 60), |
| 235 | ("perf: reduce token usage in subagent prompts", 50), |
| 236 | ("feat: Cursor IDE integration", 40), |
| 237 | ("docs: skill authoring guide", 20), |
| 238 | ("chore: release v0.2.0", 10), |
| 239 | ], |
| 240 | "maestro": [ |
| 241 | ("init: multi-model routing framework", 180), |
| 242 | ("feat: model capability registry", 170), |
| 243 | ("feat: task-based model selection (cost × quality)", 160), |
| 244 | ("feat: response blending across models", 150), |
| 245 | ("feat: fallback chain on rate limit or error", 130), |
| 246 | ("feat: streaming unified output", 120), |
| 247 | ("feat: OpenAI GPT-4o integration", 110), |
| 248 | ("feat: Anthropic Claude integration", 100), |
| 249 | ("feat: Google Gemini integration", 90), |
| 250 | ("feat: local model support via Ollama", 80), |
| 251 | ("feat: Muse state tracking for plan progress", 60), |
| 252 | ("fix: handle partial streaming errors gracefully", 50), |
| 253 | ("feat: CLI — maestro run 'task description'", 40), |
| 254 | ("docs: model comparison benchmarks", 20), |
| 255 | ("chore: release v0.1.0", 10), |
| 256 | ], |
| 257 | "stori": [ |
| 258 | ("init: narrative state engine", 180), |
| 259 | ("feat: story graph — nodes, edges, conditions", 170), |
| 260 | ("feat: character arc tracking as Muse dimension", 160), |
| 261 | ("feat: world state committed as Muse snapshot", 150), |
| 262 | ("feat: branching storylines via muse branch", 140), |
| 263 | ("feat: dialogue tree with conditional nodes", 130), |
| 264 | ("feat: scene diff — what changed between acts", 120), |
| 265 | ("feat: merge storylines — combine parallel narratives", 110), |
| 266 | ("feat: export to Ink (.ink) for game engines", 100), |
| 267 | ("feat: export to Twine (Harlowe format)", 90), |
| 268 | ("feat: AI co-author mode via MCP", 80), |
| 269 | ("fix: cyclic story graph detection", 60), |
| 270 | ("feat: story linting — unreachable nodes, dangling refs", 40), |
| 271 | ("docs: story schema reference", 20), |
| 272 | ("chore: release v0.1.0", 10), |
| 273 | ], |
| 274 | "well-tempered-clavier": [ |
| 275 | ("add: Book I — No. 1 in C major (BWV 846)", 90), |
| 276 | ("add: Book I — No. 2 in C minor (BWV 847)", 80), |
| 277 | ("add: Book I — No. 5 in D major (BWV 850)", 75), |
| 278 | ("add: Book I — No. 8 in E-flat minor (BWV 853)", 70), |
| 279 | ("add: Book I — No. 12 in F minor (BWV 857)", 60), |
| 280 | ("add: Book II — No. 1 in C major (BWV 870)", 50), |
| 281 | ("fix: velocity curves for natural dynamics", 40), |
| 282 | ("add: Book II — No. 9 in E major (BWV 878)", 30), |
| 283 | ("improve: pedal markings added to all preludes", 20), |
| 284 | ("chore: Book I complete — all 24 prelude-fugue pairs", 10), |
| 285 | ], |
| 286 | "moonlight-sonata": [ |
| 287 | ("add: Movement I — Adagio sostenuto (C# minor)", 60), |
| 288 | ("improve: Movement I — dynamics and sustain pedal refinement", 50), |
| 289 | ("add: Movement II — Allegretto (D-flat major)", 40), |
| 290 | ("add: Movement III — Presto agitato (C# minor)", 30), |
| 291 | ("fix: Movement III — tempo and velocity calibration", 20), |
| 292 | ("chore: all three movements complete", 10), |
| 293 | ], |
| 294 | "neobaroque-sketches": [ |
| 295 | ("add: Invention No. 1 in C — two-voice imitation", 90), |
| 296 | ("add: Invention No. 2 in D minor — inversion study", 80), |
| 297 | ("add: Fugue No. 1 in G major — 3-voice", 70), |
| 298 | ("revise: Fugue No. 1 — tighten episode development", 60), |
| 299 | ("add: Canon at the octave in A minor", 50), |
| 300 | ("add: Fugue No. 3 in F major — 4-voice with stretto", 30), |
| 301 | ("add: Prelude in B-flat — free improvisation study", 20), |
| 302 | ("chore: v0.1.0 — first 8 pieces complete", 10), |
| 303 | ], |
| 304 | "modal-sessions": [ |
| 305 | ("session: D Dorian — chord-scale exploration", 80), |
| 306 | ("session: A Phrygian — flamenco feel", 70), |
| 307 | ("session: G Lydian — floating, unresolved tension", 60), |
| 308 | ("session: D Dorian — walking bass variation", 50), |
| 309 | ("session: E Mixolydian — bluesy dominant", 30), |
| 310 | ("add: D Dorian — full trio arrangement", 20), |
| 311 | ("chore: v0.1.0 — six modal studies complete", 10), |
| 312 | ], |
| 313 | "chopin-nocturnes": [ |
| 314 | ("add: Nocturne Op. 9 No. 1 in B-flat minor", 60), |
| 315 | ("add: Nocturne Op. 9 No. 2 in E-flat major", 50), |
| 316 | ("add: Nocturne Op. 9 No. 3 in B major", 45), |
| 317 | ("improve: Op. 9 No. 2 — ornaments and rubato", 40), |
| 318 | ("add: Nocturne Op. 27 No. 1 in C-sharp minor", 30), |
| 319 | ("add: Nocturne Op. 27 No. 2 in D-flat major", 20), |
| 320 | ("fix: sustain pedal notation across all nocturnes", 10), |
| 321 | ], |
| 322 | } |
| 323 | |
| 324 | ISSUES: dict[str, list[tuple[str, str, list[str]]]] = { |
| 325 | "muse": [ |
| 326 | ("Support for genomics domain plugin", "open", ["enhancement"]), |
| 327 | ("muse diff: show only changed dimensions", "open", ["enhancement"]), |
| 328 | ("muse pull: conflict resolution for parallel edits", "open", ["bug"]), |
| 329 | ("Add progress bar for large object uploads", "closed", ["enhancement"]), |
| 330 | ("muse log: add --graph flag for branch visualization", "open", ["enhancement"]), |
| 331 | ], |
| 332 | "musehub": [ |
| 333 | ("Collaborator permission levels (read/write/admin)", "open", ["enhancement"]), |
| 334 | ("Webhook retry on delivery failure", "open", ["enhancement"]), |
| 335 | ("MCP: resource for domain plugin metadata", "open", ["enhancement"]), |
| 336 | ("PR review: inline comment threading", "open", ["enhancement"]), |
| 337 | ("Fix: private repo visibility for collaborators", "open", ["bug"]), |
| 338 | ], |
| 339 | "agentception": [ |
| 340 | ("Add video-review subagent type", "open", ["enhancement"]), |
| 341 | ("Subagent context handoff size limits", "open", ["bug"]), |
| 342 | ("Parallel agent result aggregation", "open", ["enhancement"]), |
| 343 | ("Timeout and kill support for hung agents", "open", ["enhancement"]), |
| 344 | ("Agent skill versioning", "open", ["enhancement"]), |
| 345 | ], |
| 346 | "maestro": [ |
| 347 | ("Add Mistral AI backend", "open", ["enhancement"]), |
| 348 | ("Cost tracking across routing decisions", "open", ["enhancement"]), |
| 349 | ("Structured output mode for all backends", "open", ["enhancement"]), |
| 350 | ("Retry logic for rate-limited models", "open", ["bug"]), |
| 351 | ("Benchmark suite for routing accuracy", "open", ["enhancement"]), |
| 352 | ], |
| 353 | "stori": [ |
| 354 | ("Export to Ren'Py visual novel format", "open", ["enhancement"]), |
| 355 | ("AI character voice consistency across branches", "open", ["enhancement"]), |
| 356 | ("World state schema validation", "open", ["enhancement"]), |
| 357 | ("Story graph cycle detection false positives", "open", ["bug"]), |
| 358 | ("Collaborative editing — multi-author merge", "open", ["enhancement"]), |
| 359 | ], |
| 360 | "well-tempered-clavier": [ |
| 361 | ("Add remaining 38 preludes/fugues from Books I & II", "open", ["enhancement"]), |
| 362 | ("Velocity humanization pass for natural feel", "open", ["enhancement"]), |
| 363 | ], |
| 364 | "moonlight-sonata": [ |
| 365 | ("Add pedal markings to Movement III", "open", ["enhancement"]), |
| 366 | ], |
| 367 | "neobaroque-sketches": [ |
| 368 | ("Add 4-voice chorale arrangements", "open", ["enhancement"]), |
| 369 | ("Transpose collection to all 12 keys", "open", ["enhancement"]), |
| 370 | ], |
| 371 | "modal-sessions": [ |
| 372 | ("Add B Locrian session", "open", ["enhancement"]), |
| 373 | ("Transcribe sessions to LilyPond sheet music", "open", ["enhancement"]), |
| 374 | ], |
| 375 | "chopin-nocturnes": [ |
| 376 | ("Add Op. 48 Nocturnes", "open", ["enhancement"]), |
| 377 | ("Dynamics pass — match Rubinstein reference recording", "open", ["enhancement"]), |
| 378 | ], |
| 379 | } |
| 380 | |
| 381 | |
| 382 | async def seed() -> None: |
| 383 | db_url = settings.database_url or "sqlite+aiosqlite:///./muse.db" |
| 384 | engine = create_async_engine(db_url) |
| 385 | Session = sessionmaker(engine, class_=AsyncSession, expire_on_commit=False) |
| 386 | |
| 387 | async with Session() as session: |
| 388 | |
| 389 | # ── 1. Gabriel's core user record ───────────────────────────────── |
| 390 | existing_user = await session.get(User, GABRIEL_ID) |
| 391 | if existing_user is None: |
| 392 | session.add(User(id=GABRIEL_ID, created_at=_now(365))) |
| 393 | print("[+] Created User: gabriel") |
| 394 | else: |
| 395 | print("[=] User gabriel already exists, skipping") |
| 396 | |
| 397 | # ── 2. Gabriel's public profile ──────────────────────────────────── |
| 398 | existing_profile = await session.get(MusehubProfile, GABRIEL_ID) |
| 399 | if existing_profile is None: |
| 400 | session.add(MusehubProfile( |
| 401 | user_id=GABRIEL_ID, |
| 402 | username="gabriel", |
| 403 | display_name="Gabriel Cardona", |
| 404 | bio=( |
| 405 | "Building Muse — a domain-agnostic VCS for multidimensional state. " |
| 406 | "Code, music, narrative, genomics. If it changes over time, Muse can version it." |
| 407 | ), |
| 408 | location="San Francisco, CA", |
| 409 | website_url="https://musehub.ai", |
| 410 | twitter_handle="cgcardona", |
| 411 | is_verified=True, |
| 412 | pinned_repo_ids=[], |
| 413 | created_at=_now(365), |
| 414 | )) |
| 415 | print("[+] Created Profile: gabriel") |
| 416 | else: |
| 417 | print("[=] Profile gabriel already exists, skipping") |
| 418 | |
| 419 | await session.flush() |
| 420 | |
| 421 | # ── 3. Repos (code + MIDI) ───────────────────────────────────────── |
| 422 | all_repos = CODE_REPOS + MIDI_REPOS |
| 423 | |
| 424 | for repo_def in all_repos: |
| 425 | repo_id = repo_def["repo_id"] |
| 426 | slug = repo_def["slug"] |
| 427 | |
| 428 | existing = await session.get(MusehubRepo, repo_id) |
| 429 | if existing: |
| 430 | print(f"[=] Repo gabriel/{slug} already exists, skipping") |
| 431 | continue |
| 432 | |
| 433 | session.add(MusehubRepo( |
| 434 | repo_id=repo_id, |
| 435 | owner="gabriel", |
| 436 | owner_user_id=GABRIEL_ID, |
| 437 | name=repo_def["name"], |
| 438 | slug=slug, |
| 439 | description=repo_def["description"], |
| 440 | visibility="public", |
| 441 | tags=repo_def.get("tags", []), |
| 442 | domain_meta=repo_def.get("domain_meta", {}), |
| 443 | settings={"default_branch": "main"}, |
| 444 | created_at=_now(180), |
| 445 | )) |
| 446 | |
| 447 | # Default branch |
| 448 | session.add(MusehubBranch( |
| 449 | branch_id=_uid(f"branch-{repo_id}-main"), |
| 450 | repo_id=repo_id, |
| 451 | name="main", |
| 452 | head_commit_id=None, |
| 453 | )) |
| 454 | |
| 455 | # Commits |
| 456 | commits = COMMITS.get(slug, []) |
| 457 | prev_sha: str | None = None |
| 458 | head_sha: str | None = None |
| 459 | for i, (msg, days_ago) in enumerate(commits): |
| 460 | sha = _sha(f"{repo_id}-commit-{i}")[:40] |
| 461 | session.add(MusehubCommit( |
| 462 | commit_id=sha, |
| 463 | repo_id=repo_id, |
| 464 | branch="main", |
| 465 | parent_ids=[prev_sha] if prev_sha else [], |
| 466 | message=msg, |
| 467 | author="gabriel", |
| 468 | timestamp=_now(days_ago), |
| 469 | snapshot_id=None, |
| 470 | created_at=_now(days_ago), |
| 471 | )) |
| 472 | prev_sha = sha |
| 473 | head_sha = sha |
| 474 | |
| 475 | # Update branch head to latest commit |
| 476 | if head_sha: |
| 477 | branch = await session.get(MusehubBranch, _uid(f"branch-{repo_id}-main")) |
| 478 | if branch: |
| 479 | branch.head_commit_id = head_sha |
| 480 | |
| 481 | # Issues |
| 482 | for j, (title, state, labels) in enumerate(ISSUES.get(slug, [])): |
| 483 | session.add(MusehubIssue( |
| 484 | issue_id=_uid(f"issue-{repo_id}-{j}"), |
| 485 | repo_id=repo_id, |
| 486 | number=j + 1, |
| 487 | title=title, |
| 488 | body=f"Tracking: {title}", |
| 489 | state=state, |
| 490 | labels=labels, |
| 491 | author="gabriel", |
| 492 | created_at=_now(60 - j * 5), |
| 493 | )) |
| 494 | |
| 495 | # Release |
| 496 | if commits: |
| 497 | session.add(MusehubRelease( |
| 498 | release_id=_uid(f"release-{repo_id}-v0"), |
| 499 | repo_id=repo_id, |
| 500 | tag="v0.1.0", |
| 501 | title="Initial release", |
| 502 | body="First public release.", |
| 503 | commit_id=head_sha, |
| 504 | download_urls={}, |
| 505 | author="gabriel", |
| 506 | is_draft=False, |
| 507 | is_prerelease=False, |
| 508 | created_at=_now(30), |
| 509 | )) |
| 510 | |
| 511 | await session.flush() |
| 512 | print(f"[+] Repo gabriel/{slug} ({len(commits)} commits, {len(ISSUES.get(slug, []))} issues)") |
| 513 | |
| 514 | # ── 4. Pin all code repos on gabriel's profile ───────────────────── |
| 515 | profile = await session.get(MusehubProfile, GABRIEL_ID) |
| 516 | if profile and not profile.pinned_repo_ids: |
| 517 | profile.pinned_repo_ids = [r["repo_id"] for r in CODE_REPOS] |
| 518 | print("[+] Pinned 5 code repos on gabriel's profile") |
| 519 | |
| 520 | await session.commit() |
| 521 | |
| 522 | print() |
| 523 | print("=" * 60) |
| 524 | print("PRODUCTION SEED COMPLETE") |
| 525 | print("=" * 60) |
| 526 | print() |
| 527 | print("Mint your admin JWT (run inside the container):") |
| 528 | print() |
| 529 | print(" docker compose exec musehub python3 -c \"") |
| 530 | print(" from musehub.auth.tokens import generate_access_code") |
| 531 | print(f" print(generate_access_code(user_id='{GABRIEL_ID}', duration_days=365, is_admin=True))") |
| 532 | print(" \"") |
| 533 | print() |
| 534 | print("Then configure your Muse CLI:") |
| 535 | print(" muse config set hub.token <token>") |
| 536 | print(" muse config set hub.url https://musehub.ai") |
| 537 | print() |
| 538 | |
| 539 | |
| 540 | if __name__ == "__main__": |
| 541 | asyncio.run(seed()) |