From claude-skills
Refresh the Miatrix indexer API token in Prowlarr by logging into Miatrix via browser automation and extracting the current API key. Use this skill whenever Miatrix keeps re-downloading the same episodes or shows, when Prowlarr reports Miatrix indexer errors or failures, or when a Miatrix API key rotation is suspected. The skill handles the full workflow automatically: gathers credentials, logs into Miatrix, grabs the API key from the profile page, and updates Prowlarr's indexer config — no manual steps required.
How this skill is triggered — by the user, by Claude, or both
Slash command
/claude-skills:refresh-miatrix-tokenThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
Fixes the "re-downloading the same show" symptom caused by Miatrix API key
Fixes the "re-downloading the same show" symptom caused by Miatrix API key rotation. When Miatrix rotates its keys, Prowlarr's cached key goes stale and Miatrix starts returning duplicate or incorrect results — causing the Arr stack to re-queue already-downloaded episodes.
You need four values before proceeding:
| Variable | Description |
|---|---|
MIATRIX_USER | Miatrix username or email |
MIATRIX_PASS | Miatrix password |
PROWLARR_URL | Base URL of the Prowlarr instance (e.g. https://prowlarr.example.com) |
PROWLARR_KEY | Prowlarr API key (Settings → General → API Key) |
Resolve them in this order of preference:
env | grep -E 'MIATRIX|PROWLARR'/run/vault-agent/token exists, use it:
export VAULT_TOKEN=$(cat /run/vault-agent/token)
# Path varies by project — check CLAUDE.md or .env.example for the kv path
bao kv get -field=username secret/services/miatrix # example
bao kv get -field=password secret/services/miatrix
bao kv get -field=api_key secret/services/prowlarr
If the machine token file doesn't exist, try BAO_TOKEN / VAULT_TOKEN env vars.pass, etc. Check
the project's CLAUDE.md or .env.example for the path.Once resolved, assign them:
MIATRIX_USER="<value>"
MIATRIX_PASS="<value>"
PROWLARR_URL="<value>" # no trailing slash
PROWLARR_KEY="<value>"
Never log, echo, or display credential values. If any value cannot be resolved, stop and report which one is missing.
Open a fresh browser tab and navigate to the login page.
new_page → get the page_id
navigate_page(page_id, "https://miatrix.com/login")
wait_for(page_id, ["Login", "Username", "Email", "Password"])
take_snapshot(page_id)
Fill in the credentials and submit. Common selector patterns for the form:
input[name="username"], input[type="email"], #username, #emailinput[type="password"]button[type="submit"], input[type="submit"]fill(page_id, <username_selector>, MIATRIX_USER)
fill(page_id, <password_selector>, MIATRIX_PASS)
click(page_id, <submit_selector>)
wait_for(page_id, ["networkidle", "Dashboard", "Profile", "Welcome"])
Verify login succeeded: the URL should no longer be /login and a snapshot
should show the user's account name or a dashboard, not an error message. If
still on the login page, stop immediately and report:
"Miatrix login failed — verify MIATRIX_USER and MIATRIX_PASS"
Do not retry with modified credentials.
navigate_page(page_id, "https://miatrix.com/profile")
wait_for(page_id, ["networkidle", "API", "Key", "Token"])
take_snapshot(page_id)
The API key is visible as a text value or readonly input labelled "Site
Api/Rss Key" or similar. Try extracting it with evaluate_script:
(function() {
const selectors = [
'input[name*="api"]', 'input[id*="api"]',
'input[name*="key"]', 'input[id*="key"]',
'input[name*="token"]',
'.api-key', '.apikey', '[data-apikey]', '[data-api-key]',
];
for (const sel of selectors) {
const el = document.querySelector(sel);
if (el) return el.value || el.textContent.trim();
}
// Fall back: scan code/pre blocks for a plausible API key string
for (const el of document.querySelectorAll('code, pre, .token, .key')) {
const text = el.textContent.trim();
if (text.length > 16 && /^[a-zA-Z0-9_\-]+$/.test(text)) return text;
}
return null;
})()
If the script returns null, read the snapshot — the key may be in a button-revealed modal or "copy" widget. Click a "Show" or "Reveal" button first, then re-evaluate. If you still cannot extract it, take a screenshot and report the page structure so the selector can be updated.
Store the result as NEW_API_KEY. Close the browser tab when done:
close_page(page_id)
Find the Miatrix indexer(s):
curl -s -H "X-Api-Key: $PROWLARR_KEY" \
"$PROWLARR_URL/api/v1/indexer" \
| python3 -c "
import sys, json
indexers = json.load(sys.stdin)
for i in indexers:
name = i.get('name','').lower()
defn = i.get('definitionName','').lower()
if 'miatrix' in name or 'miatrix' in defn:
print(i['id'], i['name'])
"
If no indexer is found, list all indexer names and report — the name in Prowlarr may differ from "Miatrix".
For each Miatrix indexer found, GET the full config, patch the API key field, and PUT it back. Use temp files to keep the shell pipeline simple:
# Clean up temp files on exit (even on error)
trap 'rm -f /tmp/prowlarr_indexer.json /tmp/prowlarr_patched.json' EXIT
# GET full config
curl -sf -H "X-Api-Key: $PROWLARR_KEY" \
"$PROWLARR_URL/api/v1/indexer/$INDEXER_ID" \
> /tmp/prowlarr_indexer.json
# Patch the apiKey field (commonly named apiKey, key, api_key, passkey, or token)
# Single-quoted PYEOF suppresses shell expansion; key passed as argv[1] to avoid injection
python3 - "$NEW_API_KEY" <<'PYEOF' > /tmp/prowlarr_patched.json
import json, sys
with open('/tmp/prowlarr_indexer.json') as f:
data = json.load(f)
new_key = sys.argv[1]
key_fields = {'apiKey', 'key', 'api_key', 'passkey', 'apikey', 'token'}
updated = False
for field in data.get('fields', []):
if field.get('name', '').lower() in key_fields:
field['value'] = new_key
updated = True
break
if not updated:
print('Available fields:', [f['name'] for f in data.get('fields', [])], file=sys.stderr)
sys.exit(1)
with open('/tmp/prowlarr_patched.json', 'w') as out:
json.dump(data, out)
PYEOF
# PUT back and capture HTTP status
HTTP_STATUS=$(curl -s -o /dev/null -w "%{http_code}" \
-X PUT \
-H "X-Api-Key: $PROWLARR_KEY" \
-H "Content-Type: application/json" \
-d @/tmp/prowlarr_patched.json \
"$PROWLARR_URL/api/v1/indexer/$INDEXER_ID")
echo "Prowlarr responded: $HTTP_STATUS"
A 200 or 202 response means success. If the Python script exits with error
(field not found), print the available field names so the skill can be updated
with the correct field name.
Test the updated indexer directly and check isValid:
curl -sf -X POST \
-H "X-Api-Key: $PROWLARR_KEY" \
"$PROWLARR_URL/api/v1/indexer/$INDEXER_ID/test" \
| python3 -c "import sys,json; r=json.load(sys.stdin); print('isValid:', r.get('isValid', r))"
Report the outcome clearly:
✓ Retrieved new Miatrix API key from https://miatrix.com/profile
✓ Updated Prowlarr indexer "<name>" (ID: N) at <PROWLARR_URL>
→ Prowlarr responded: 202
→ Indexer test: passed
On any failure, state the exact step that failed and the error. Do not include credential values in the output.
Builds a throwaway prototype to answer a design question about UI appearance or state/logic behavior. Guides you through two branches: interactive terminal app for logic validation, or multiple UI variations for visual exploration.
npx claudepluginhub joestump/claude-skills --plugin claude-skills