fix: register bot at name confirmation, show credentials in setup step

- Bot registered immediately after name check (poll mode default)
- bot_id + secret available in bot-setup step with COPY GUIDE + CREDENTIALS
- Webhook mode updates URL on existing bot instead of re-registering
- Collapsible guide preview with credentials injected
- Resets guide content when switching webhook/polling mode
- Fix secret text overflow with break-all on profile page

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Dorian
2026-03-13 17:36:18 +00:00
co-authored by Claude Opus 4.6
parent d17f6970b9
commit 42b1642932
2 changed files with 64 additions and 54 deletions
+2 -2
View File
@@ -850,8 +850,8 @@ const tierClass = (t: number) => `tier-${t}`
<div class="p-2.5 border border-neon-green/30 bg-neon-green/5"> <div class="p-2.5 border border-neon-green/30 bg-neon-green/5">
<p class="font-display font-bold text-[10px] tracking-wider text-neon-green mb-2">NEW SECRET GENERATED</p> <p class="font-display font-bold text-[10px] tracking-wider text-neon-green mb-2">NEW SECRET GENERATED</p>
<div class="font-mono text-[10px] text-text-muted space-y-1"> <div class="font-mono text-[10px] text-text-muted space-y-1">
<div>BOT_ID=<span class="text-neon-cyan select-all">{{ regeneratedBotId }}</span></div> <div>BOT_ID=<span class="text-neon-cyan select-all break-all">{{ regeneratedBotId }}</span></div>
<div>BOT_SECRET=<span class="text-neon-cyan select-all">{{ regeneratedSecret }}</span></div> <div>BOT_SECRET=<span class="text-neon-cyan select-all break-all">{{ regeneratedSecret }}</span></div>
</div> </div>
<p class="font-mono text-[9px] text-ko mt-2">Save this now. It will not be shown again after you leave this page.</p> <p class="font-mono text-[9px] text-ko mt-2">Save this now. It will not be shown again after you leave this page.</p>
</div> </div>
+60 -50
View File
@@ -1,5 +1,5 @@
<script setup lang="ts"> <script setup lang="ts">
import { ref, onMounted, onUnmounted } from 'vue' import { ref, watch, onMounted, onUnmounted } from 'vue'
import { useRouter, onBeforeRouteLeave } from 'vue-router' import { useRouter, onBeforeRouteLeave } from 'vue-router'
import { useNostr } from '../composables/useNostr' import { useNostr } from '../composables/useNostr'
import { useWallet } from '../composables/useWallet' import { useWallet } from '../composables/useWallet'
@@ -10,7 +10,7 @@ import WalletConnect from '../components/WalletConnect.vue'
import { authFetch } from '../lib/nostr-auth' import { authFetch } from '../lib/nostr-auth'
const router = useRouter() const router = useRouter()
const { pubkey, bot, profilePicUrl, isLoggedIn, hasExtension, hasStoredKey, isLoading, login, waitForSigner, generateLogin, loginWithNsec, registerBot, registerHuman, getStoredNsec, persistKey, logout, initiateNip55Login, processNip55Return, hasAndroidSigner } = useNostr() const { pubkey, bot, profilePicUrl, isLoggedIn, hasExtension, hasStoredKey, isLoading, login, waitForSigner, generateLogin, loginWithNsec, registerBot, registerHuman, updateWebhook, getStoredNsec, persistKey, logout, initiateNip55Login, processNip55Return, hasAndroidSigner } = useNostr()
const showNsecBackup = ref(false) const showNsecBackup = ref(false)
const generatedNsec = ref('') const generatedNsec = ref('')
const generatedNsecHex = ref('') const generatedNsecHex = ref('')
@@ -388,20 +388,16 @@ async function confirmName() {
error.value = '' error.value = ''
isCheckingName.value = true isCheckingName.value = true
try { try {
const res = await fetch(`/api/auth/check-name/${encodeURIComponent(name)}`) // Register the bot immediately (poll mode default — webhook updated later)
if (res.ok) { const result = await registerBot(name, '', selectedArchetype.value)
const data = await res.json() botId.value = result.bot.id
if (!data.available) { botSecret.value = result.secret
error.value = 'That name is taken. Pick another.' step.value = 'bot-setup'
return } catch (e) {
} handleError(e, 'Registration failed.')
}
} catch {
// If check fails, proceed anyway — server will catch it at registration
} finally { } finally {
isCheckingName.value = false isCheckingName.value = false
} }
step.value = 'bot-setup'
} }
const isChoosingMode = ref(false) const isChoosingMode = ref(false)
@@ -504,9 +500,7 @@ async function confirmWebhook() {
error.value = '' error.value = ''
isVerifyingWebhook.value = true isVerifyingWebhook.value = true
try { try {
const result = await registerBot(botName.value.trim(), url, selectedArchetype.value) await updateWebhook(url)
botId.value = result.bot.id
botSecret.value = result.secret
step.value = 'ready' step.value = 'ready'
} catch (e) { } catch (e) {
handleError(e, 'Webhook verification failed. Check your URL and try again.') handleError(e, 'Webhook verification failed. Check your URL and try again.')
@@ -515,22 +509,15 @@ async function confirmWebhook() {
} }
} }
async function confirmPolling() { function confirmPolling() {
error.value = ''
try {
const result = await registerBot(botName.value.trim(), '', selectedArchetype.value)
botId.value = result.bot.id
botSecret.value = result.secret
step.value = 'ready' step.value = 'ready'
} catch (e) {
handleError(e, 'Registration failed.')
}
} }
const showSetupContent = ref(false) const showSetupContent = ref(false)
const setupContent = ref('') const setupContent = ref('')
const setupContentLoading = ref(false) const setupContentLoading = ref(false)
const setupContentCopied = ref(false) const setupContentCopied = ref(false)
watch(connectionMode, () => { setupContent.value = ''; showSetupContent.value = false })
function setupDocPath() { function setupDocPath() {
return connectionMode.value === 'polling' ? '/docs/BOTFIGHTS-POLLING.md' : '/docs/BOTFIGHTS-WEBHOOK.md' return connectionMode.value === 'polling' ? '/docs/BOTFIGHTS-POLLING.md' : '/docs/BOTFIGHTS-WEBHOOK.md'
@@ -1009,35 +996,58 @@ function handleSignOut() {
</button> </button>
</div> </div>
<!-- Safety callout --> <!-- Credentials + setup guide -->
<div class="p-2.5 border border-neon-cyan/20 bg-neon-cyan/5 mb-4"> <div v-if="botSecret" class="mb-4 p-3 border border-neon-cyan/20 bg-neon-cyan/5">
<p class="font-display font-bold text-[10px] tracking-wider text-neon-cyan mb-1"> <p class="font-display font-bold text-[10px] tracking-wider text-neon-cyan mb-2">
SAFE & SIMPLE COPY THIS TO YOUR AI
</p> </p>
<ul class="font-mono text-[10px] text-text-muted space-y-0.5 leading-relaxed">
<li v-if="connectionMode === 'webhook'"> <div class="bg-bg border border-border p-2.5 font-mono text-[10px] text-text-muted leading-relaxed select-all mb-2">
We only send <span class="text-text-secondary">POST</span> requests with fight questions <div>BOT_ID=<span class="text-neon-cyan break-all">{{ botId }}</span></div>
</li> <div>BOT_SECRET=<span class="text-neon-cyan break-all">{{ botSecret }}</span></div>
<li v-if="connectionMode === 'polling'">
<span class="text-text-secondary">No incoming connections</span> outbound only
</li>
<li>Your <span class="text-text-secondary">API keys stay on your machine</span></li>
<li>Private IPs <span class="text-text-secondary">blocked</span>, payloads <span class="text-text-secondary">&lt;2KB</span></li>
</ul>
</div> </div>
<!-- What's happening --> <button
<details class="mb-4 group"> class="w-full py-2 border border-border text-[10px] font-display font-bold tracking-wider
<summary class="font-display font-bold text-[10px] tracking-wider text-text-secondary cursor-pointer hover:border-neon-cyan/40 hover:text-neon-cyan transition-all"
hover:text-neon-purple transition-colors select-none"> :class="setupGuideCopied ? 'text-neon-green border-neon-green/40' : 'text-text-muted'"
WHAT'S ACTUALLY HAPPENING? @click="copyFullPrompt"
</summary> >
<p class="mt-2 font-mono text-[10px] text-text-muted leading-relaxed"> {{ setupGuideCopied ? 'COPIED' : 'COPY GUIDE + CREDENTIALS' }}
Your AI runs a small server that receives fight challenges from BOTFIGHTS. </button>
When a challenge comes in, it uses Claude to figure out the answer and fires it back.
5-10 rounds per fight, scored on correctness and speed. <p class="font-mono text-[9px] text-text-muted/60 leading-relaxed mt-2">
Paste into your AI includes full setup guide with your credentials.
</p> </p>
</details>
<!-- Collapsible guide preview -->
<button
class="w-full mt-2 py-1.5 border border-border text-[9px] font-display font-bold tracking-wider
text-text-muted hover:border-neon-cyan/40 hover:text-neon-cyan transition-all"
@click="toggleSetupContent"
>
{{ showSetupContent ? 'HIDE' : 'VIEW' }} SETUP GUIDE
</button>
<div v-if="showSetupContent" class="mt-2 border border-border bg-black/40 overflow-hidden">
<div class="flex items-center justify-between px-3 py-2 border-b border-border/50 bg-surface-raised/30">
<span class="font-display font-bold text-[9px] tracking-wider text-text-muted">{{ setupDocName() }}</span>
<button
class="font-display font-bold text-[9px] tracking-wider px-2 py-0.5 border transition-all"
:class="setupContentCopied
? 'border-neon-green/40 text-neon-green'
: 'border-border text-text-muted hover:border-neon-cyan/40 hover:text-neon-cyan'"
@click="copySetupContent"
>
{{ setupContentCopied ? 'COPIED' : 'COPY ALL' }}
</button>
</div>
<div v-if="setupContentLoading" class="p-4 text-center">
<p class="font-mono text-[10px] text-text-muted animate-pulse">Loading...</p>
</div>
<pre v-else class="p-3 font-mono text-[10px] text-text-secondary leading-relaxed
overflow-x-auto max-h-80 overflow-y-auto whitespace-pre-wrap break-words select-all">{{ setupContent }}</pre>
</div>
</div>
<div class="flex gap-2"> <div class="flex gap-2">
<button <button