gabriel / musehub public
seed_production.py python
541 lines 23.2 KB
7f1d07e8 feat: domains, MCP expansion, MIDI player, and production hardening (#8) Gabriel Cardona <cgcardona@gmail.com> 4d ago
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())