musehub-api.md markdown
259 lines 8.0 KB
Raw
sha256:5472be4fece32b606c308fad9d57295ce327955fb2b87b8beec6ea1626050473 Add published YouTube URL to Episode 00 Sonnet 5 1 hour ago

Episode 18 --- MuseHub API

Working YouTube title:
I Built A MuseHub Client In Bash (No SDK, No CLI, Just curl)

Thumbnail thought:
curl. one header. no muse binary.

Target runtime: ~8:00


[0:00--0:20] COLD OPEN

[CAMERA --- Episode 17's closing line, on screen: "what you can actually build with an API this well-specified."]

GABRIEL:

Every episode so far has used the muse binary as the client. Today it's gone. No muse hub subcommands. Just curl, one signed header, and the raw HTTP API underneath everything else this season has been built on.

[TITLE CARD --- fast]

MUSEHUB API

[Music enters.]


[0:20--1:00] THE API DESCRIBES ITSELF

[TERMINAL]

$ curl -sk https://localhost:1337/api/openapi.json | jq '.info, (.paths | length)'
{ "title": "MuseHub API", "version": "0.2.0.dev3" }
239

GABRIEL VO:

Two hundred thirty-nine documented paths, generated live from the actual FastAPI route definitions --- not hand-written docs that can drift, like the ones we caught lagging in episode 16. If you're building a real integration, this schema is the actual, current contract.


[1:00--2:30] SIGNING A REQUEST BY HAND, AND GETTING IT WRONG FIRST

[TERMINAL]

$ HEADER=$(muse sign header --method GET --path "/api/repos" --hub https://localhost:1337 --json | jq -r .header_value)
$ curl -sk "https://localhost:1337/api/repos?limit=3" -H "Authorization: $HEADER"
{ "detail": "Signature verification failed." }

[beat]

GABRIEL:

That's a real mistake, not staged. I signed /api/repos --- without the ?limit=3 I actually put in the URL. MSign signs the exact method, path, and query string; add a query parameter after signing and the signature no longer matches what the server sees. It rejects it, correctly.

$ HEADER=$(muse sign header --method GET --path "/api/repos?limit=3" --hub ... --json | jq -r .header_value)
$ curl -sk "https://localhost:1337/api/repos?limit=3" -H "Authorization: $HEADER"
gabriel/wire-episode17
gabriel/wire-demo
gabriel/wire-episode17-demo

GABRIEL VO:

Sign the full thing, and it's just... data. No muse process running on this end at all --- one signed header, one HTTP request.


[2:30--3:30] FINDING THE REAL ENDPOINT FROM THE SCHEMA, NOT A GUESS

[TERMINAL]

$ curl -sk .../openapi.json | jq '.paths | keys[] | select(contains("issues"))'
"/api/repos/{repo_id}/issues"
"/api/repos/{repo_id}/issues/{issue_number}"
...

GABRIEL:

Notice: it's repo_id, not owner/slug. My first instinct --- guess a REST-y /api/{owner}/{repo}/issues path --- was wrong, and I only know that because I checked the schema instead of assuming. That's the actual point of a machine-readable contract: stop guessing.


[3:30--5:00] A REAL WRITE, ZERO ABSTRACTION

[TERMINAL]

$ REPO_ID=$(curl ... | jq -r '.repos[] | select(.slug=="wire-episode17") | .repoId')
$ HEADER=$(muse sign header --method POST --path "/api/repos/$REPO_ID/issues" \
    --body-file issue.json --hub ... --json | jq -r .header_value)
$ curl -sk -X POST ".../api/repos/$REPO_ID/issues" \
    -H "Authorization: $HEADER" -H "Content-Type: application/json" \
    --data-binary @issue.json
{ "issueId": "sha256:b9928a...", "number": 2, "author": "gabriel", "state": "open" }

GABRIEL VO:

A real issue, in the real database, created by a script that has never once called muse. This is what "build something on top of MuseHub" actually means --- not a metaphor, an actual bash script with one dependency: something that can compute an Ed25519 signature.


[5:00--6:00] THE SIGNING PART DOESN'T HAVE TO BE MUSE EITHER

[CAMERA]

muse sign header is a convenience --- the CLI happens to hold your key. The actual requirement is just: compute this over the canonical message, using the private key you registered.

$ muse sign curl --method GET --url ".../api/repos?limit=2" --hub ...
curl -X GET \
  -H 'Authorization: MSign handle="gabriel" alg="ed25519" ts=... sig="..."' \
  https://localhost:1337/api/repos?limit=2

GABRIEL:

Prints the whole command, ready to paste, ready to put in a script you hand off to someone who's never touched Muse. Anything that can produce an Ed25519 signature over that string --- Python, Node, Rust, whatever --- is a valid MuseHub client. The muse binary is the reference implementation, not a gate.


[6:00--6:40] WHAT THIS BUYS YOU

[CAMERA]

Every write tool in Episode 16's MCP server, every muse hub subcommand this whole season, and the raw curl calls from the last five minutes all terminate at the exact same 239 routes, the exact same signature check, the exact same database. Three different front doors, one real building behind all of them.


[6:40--7:20] OUT

[TERMINAL --- fading to black]

GABRIEL VO:

Every episode this season has assumed one identity per person. Next: what happens when that gets more complicated --- organizations, attestations, an agent's genealogy back to the human who deployed it.

[beat]

That's next.

[CUT TO BLACK]

musehub.ai


Production Notes

Episode 18's spine is subtraction, not addition: every previous episode added a layer (CLI, MCP tools, wire format); this one strips everything back to curl and a signature to prove none of those layers were load-bearing magic. The failed signature at 1:00 has to stay in --- it's the most convincing three seconds in the episode, because it's a real mistake with a real, immediate, correct rejection, not a staged gotcha.

Don't Skip The Schema-Discovery Beat

The /{owner}/{repo}/issues guess being wrong (2:30) is small but important: it demonstrates the actual workflow for anyone building a real integration — check the schema, don't assume REST convention holds. Cutting it for pacing would remove the episode's only moment of "here's how you'd actually work with this if you were doing it for real," which is the whole premise.

The Three-Front-Doors Framing Closes The Loop On The Season

6:00's beat --- MCP tools, muse hub, and raw curl all hitting the same 239 routes --- is deliberately a callback to episode 16. Make the connection explicit rather than assuming the audience remembers; that's the payoff for having built the MCP episode first.

Everything Here Is Real

Every curl call, the intentional failed signature, the schema lookup, and the created issue were run against the real local MuseHub API, not a mock. wire-episode17's repo was reused from Episode 17 rather than creating a new throwaway --- intentional, since this episode's point is interoperating with what already exists, not building a new sandbox. Re-run make-api-episode18-demo.sh at record time; the exact repoId/issueId values will differ run to run, which is expected --- narrate the mechanism, not the exact hashes.

The Seed

The viewer arrives thinking:

The CLI and the MCP server are both nice wrappers around something else. What's actually underneath?

They should leave thinking:

The wrapper was never the point. A hand-rolled bash script with curl and one Ed25519 signature can do everything the official client does. If I can build a client this thin, what happens when the "client" isn't a person's script at all, but another organization, or another agent, with its own identity to prove?

That's Episode 19.

File History 1 commit
sha256:5472be4fece32b606c308fad9d57295ce327955fb2b87b8beec6ea1626050473 Add published YouTube URL to Episode 00 Sonnet 5 1 hour ago