feat: Hermes prompt callback endpoint + API key management

- Add /api/prompt/<rid> callback endpoint with dual auth:
  per-request signed URL token + Bearer API key.
- Add HERMES_API_KEY config and runtime key helpers in app.py.
- Update admin request page to copy request details + callback URL.
- Add API key management to admin settings: regenerate, mask, show-once.
- Update .env.example, docker-compose.yml, README, REVIEW docs.
This commit is contained in:
Troll (Hermes Agent) 2026-08-03 17:16:56 +00:00
parent e46893e0b4
commit 6a60aa686c
8 changed files with 242 additions and 35 deletions

View file

@ -322,7 +322,10 @@
<script>
function copyPromptForHermes() {
const callbackUrl = {{ callback_url | tojson }};
const data = {
request_id: {{ req.id | tojson }},
email: {{ req.email | tojson }},
name: {{ req.name | tojson }},
hobbies: {{ req.hobbies | tojson }},
notable_facts: {{ req.notable_facts | tojson }},
@ -330,8 +333,8 @@
vocal_gender: {{ req.vocal_gender | tojson }},
extra_requests: {{ req.extra_requests | tojson }}
};
const text = `Please write a Suno Custom Mode prompt for this customer. Suggest a song title too.\n\nCustomer data:\nName: ${data.name}\nHobbies: ${data.hobbies || '-'}\nNotable facts: ${data.notable_facts || '-'}\nStyle / genre: ${data.style_genre || '-'}\nPreferred singer voice / gender: ${data.vocal_gender || 'No preference'}\nExtra requests: ${data.extra_requests || '-'}\n\nReply with exactly this format:\n\nTitle: [suggested title]\n\nStyle:\n[the style description here]\n\nLyrics:\n[the lyrics here, with metatags]`;
navigator.clipboard.writeText(text).then(() => alert("Copied! Paste Hermes response into the Title, Style, and Lyrics fields."));
const text = `Booth request: ${data.request_id}\nCustomer email: ${data.email}\nCallback URL: ${callbackUrl}\n\nGenerate a Suno Custom Mode prompt for this customer and POST it back to the Callback URL as JSON.\n\nCustomer data:\nName: ${data.name}\nHobbies: ${data.hobbies || '-'}\nNotable facts: ${data.notable_facts || '-'}\nStyle / genre: ${data.style_genre || '-'}\nPreferred singer voice / gender: ${data.vocal_gender || 'No preference'}\nExtra requests: ${data.extra_requests || '-'}\n\nExpected JSON response format:\n{\n \"request_id\": ${data.request_id},\n \"email\": \"${data.email}\",\n \"suno_title\": \"...\",\n \"suno_style\": \"...\",\n \"suno_lyrics\": \"...\"\n}`;
navigator.clipboard.writeText(text).then(() => alert("Copied! Paste into Hermes. Hermes will POST the generated prompt back to the Callback URL."));
}
function copyToClipboard(elementId, label) {

View file

@ -251,6 +251,28 @@
</form>
</div>
<!-- Email / SMTP configuration -->
<div class="section">
<h2>Hermes API Key</h2>
<p class="copy-hint">Hermes uses this key in the Authorization header when POSTing generated Suno prompts back to the callback endpoint.</p>
<p><strong>Current key:</strong> <code>{{ hermes_key_masked }}</code></p>
{% if hermes_key_just_generated %}
<div class="flash success" style="background:#064e3b">
<p><strong>New key (copy it now — it will not be shown again):</strong></p>
<p class="path" id="new-hermes-key">{{ hermes_key_just_generated }}</p>
<button type="button" onclick="copyHermesKey()">Copy to clipboard</button>
</div>
{% elif not hermes_key_set %}
<p class="status-bad">⚠️ No Hermes API key is configured. Generate one before using the callback workflow.</p>
{% endif %}
<form method="POST" onsubmit="return confirm('Regenerating the key will invalidate the old one. Hermes will need the new key. Continue?')">
<input type="hidden" name="action" value="regenerate_hermes_key">
<button type="submit">Regenerate API Key</button>
</form>
</div>
<!-- Email / SMTP configuration -->
<div class="section">
<h2>Email (SMTP) Settings</h2>
@ -351,8 +373,22 @@
<button type="submit">Reset System</button>
</form>
</div>
</div>
</div>
</div>
</div>
</body>
</html>
<script>
function copyHermesKey() {
const el = document.getElementById('new-hermes-key');
if (!el) return;
const range = document.createRange();
range.selectNode(el);
const selection = window.getSelection();
selection.removeAllRanges();
selection.addRange(range);
navigator.clipboard.writeText(el.textContent).then(() => {
alert('Hermes API key copied!');
});
}
</script>
</body>
</html>