#!/usr/bin/env bash # Drives the raw MuseHub REST API for Episode 18 ("MuseHub API") -- no # `muse hub` subcommands, no MCP, just curl and a hand-signed MSign header. # Requires the local MuseHub dev stack running. set -euo pipefail HUB="https://localhost:1337" echo "=== Part 1: the API describes itself ===" curl -sk "$HUB/api/openapi.json" | python3 -c " import json, sys d = json.load(sys.stdin) print('title:', d['info']['title']) print('version:', d['info']['version']) print('paths:', len(d['paths'])) " echo echo "=== Part 2: a raw GET, signed by hand, no muse hub involved ===" echo "--- mistake: forgot to sign the query string ---" HEADER=$(muse sign header --method GET --path "/api/repos" --hub "$HUB" --json | python3 -c "import json,sys; print(json.load(sys.stdin)['header_value'])") curl -sk -X GET "$HUB/api/repos?limit=3" -H "Authorization: $HEADER" | python3 -m json.tool echo "--- fix: sign the full path including the query string ---" HEADER=$(muse sign header --method GET --path "/api/repos?limit=3" --hub "$HUB" --json | python3 -c "import json,sys; print(json.load(sys.stdin)['header_value'])") curl -sk -X GET "$HUB/api/repos?limit=3" -H "Authorization: $HEADER" | python3 -c " import json, sys d = json.load(sys.stdin) for r in d['repos']: print(' -', r['owner'] + '/' + r['slug']) " echo echo "=== Part 3: finding the real write endpoint from the schema, not guesswork ===" curl -sk "$HUB/api/openapi.json" | python3 -c " import json, sys d = json.load(sys.stdin) for path, methods in d['paths'].items(): if path == '/api/repos/{repo_id}/issues': print(list(methods.keys()), path) " REPO_ID=$(curl -sk "$HUB/api/repos?limit=100" \ -H "Authorization: $(muse sign header --method GET --path '/api/repos?limit=100' --hub "$HUB" --json | python3 -c 'import json,sys; print(json.load(sys.stdin)["header_value"])')" \ | python3 -c " import json, sys d = json.load(sys.stdin) for r in d['repos']: if r['slug'] == 'wire-episode17': print(r['repoId']) break ") echo "resolved repo_id: $REPO_ID" echo echo "=== Part 4: a raw signed POST -- creating a real issue ===" ENCODED=$(python3 -c "import urllib.parse,sys; print(urllib.parse.quote(sys.argv[1], safe=''))" "$REPO_ID") BODY='{"title":"Real issue created via raw curl + MSign","body":"No muse CLI, no MCP -- just curl and a hand-signed header."}' printf '%s' "$BODY" > /tmp/ep18-issue-body.json PATH_ONLY="/api/repos/$ENCODED/issues" HEADER=$(muse sign header --method POST --path "$PATH_ONLY" --hub "$HUB" --body-file /tmp/ep18-issue-body.json --json | python3 -c "import json,sys; print(json.load(sys.stdin)['header_value'])") curl -sk -X POST "$HUB$PATH_ONLY" \ -H "Authorization: $HEADER" \ -H "Content-Type: application/json" \ --data-binary @/tmp/ep18-issue-body.json | python3 -m json.tool rm -f /tmp/ep18-issue-body.json echo echo "=== Part 5: muse sign curl -- the convenience wrapper ===" muse sign curl --method GET --url "$HUB/api/repos?limit=2" --hub "$HUB" echo echo "Demo complete."