From 2dd9947516ae96e3bfa5951d23af637b6156dc8b Mon Sep 17 00:00:00 2001 From: Dorian Date: Thu, 30 Jul 2026 22:24:48 -0400 Subject: [PATCH] feat(09-03): point every in-app setup surface at the one prompt, add copy affordances - JoinBoutPage.vue: setupDocPath()/setupDocName() collapse to /docs/BOTFIGHTS.md regardless of connection mode; keep YOUR_BOT_ID/YOUR_BOT_SECRET substitution and add a third substitution for {{ARENA_URL}} -> window.location.origin - BotProfilePage.vue: same collapse; the webhook/polling guide-type selector is gone (there's only one guide now) -- replaced with a single 'LOAD SETUP GUIDE' button, same credential + {{ARENA_URL}} substitution - DocsPage.vue: new 'GIVE THIS TO YOUR AI' panel at the top of the page -- copy-to-clipboard for the full /api/docs/prompt response, plus the literal URL shown so a user can hand an agent the link instead of the text. Existing API reference / webhook tester tabs unchanged below it - e2e/signup-bot.spec.ts: two new tests -- docs page shows the copy affordance, and GET /api/docs/prompt (via page.request) returns 200 with the registration endpoint and no leftover {{ARENA_URL}} token --- e2e/signup-bot.spec.ts | 17 ++++++++ frontend/src/pages/BotProfilePage.vue | 47 +++++++-------------- frontend/src/pages/DocsPage.vue | 60 +++++++++++++++++++++++++++ frontend/src/pages/JoinBoutPage.vue | 6 ++- 4 files changed, 95 insertions(+), 35 deletions(-) diff --git a/e2e/signup-bot.spec.ts b/e2e/signup-bot.spec.ts index 4d878c8..0103c20 100644 --- a/e2e/signup-bot.spec.ts +++ b/e2e/signup-bot.spec.ts @@ -41,3 +41,20 @@ test.describe('bot registration flow', () => { await expect(page.getByText(/choose your fighter/i).first()).toBeVisible({ timeout: 5_000 }) }) }) + +test.describe('unified AI bot-setup prompt (BOT-02)', () => { + test('docs page shows the "give this to your AI" copy affordance', async ({ page }) => { + await page.goto('/docs') + await expect(page.getByText(/give this to your ai/i).first()).toBeVisible({ timeout: 10_000 }) + await expect(page.getByText(/copy full prompt/i).first()).toBeVisible({ timeout: 5_000 }) + }) + + test('GET /api/docs/prompt returns the self-contained prompt an agent could consume', async ({ page }) => { + await page.goto('/docs') + const res = await page.request.get('/api/docs/prompt') + expect(res.status()).toBe(200) + const body = await res.text() + expect(body).toContain('/api/bots') + expect(body).not.toContain('{{ARENA_URL}}') + }) +}) diff --git a/frontend/src/pages/BotProfilePage.vue b/frontend/src/pages/BotProfilePage.vue index b0fedcd..708d1c8 100644 --- a/frontend/src/pages/BotProfilePage.vue +++ b/frontend/src/pages/BotProfilePage.vue @@ -115,7 +115,6 @@ const regenError = ref('') const guideContent = ref('') const guideLoading = ref(false) const guideCopied = ref(false) -const guideMode = ref<'webhook' | 'polling'>('webhook') const ARCHETYPES = [ 'standard', 'lobster', 'sheep', 'cyborg', 'blob', 'tank', 'dog', 'cat', @@ -294,17 +293,16 @@ async function handleRegenerateSecret() { isRegenerating.value = false } -async function loadGuide(mode: 'webhook' | 'polling') { - guideMode.value = mode +async function loadGuide() { guideLoading.value = true guideContent.value = '' guideCopied.value = false - const path = mode === 'polling' ? '/docs/BOTFIGHTS-POLLING.md' : '/docs/BOTFIGHTS-WEBHOOK.md' try { - const res = await fetch(path) + const res = await fetch('/docs/BOTFIGHTS.md') let content = await res.text() content = content.replace(/YOUR_BOT_ID/g, regeneratedBotId.value) content = content.replace(/YOUR_BOT_SECRET/g, regeneratedSecret.value) + content = content.replace(/\{\{ARENA_URL\}\}/g, window.location.origin) guideContent.value = content } catch { guideContent.value = '# Failed to load setup guide' @@ -856,33 +854,22 @@ const tierClass = (t: number) => `tier-${t}`

Save this now. It will not be shown again after you leave this page.

- -
- - -
+ +
- {{ guideMode === 'polling' ? 'BOTFIGHTS-POLLING.md' : 'BOTFIGHTS-WEBHOOK.md' }} + BOTFIGHTS.md
-
-

Loading...

-
-

- Choose webhook or polling above to view the setup guide. -

diff --git a/frontend/src/pages/DocsPage.vue b/frontend/src/pages/DocsPage.vue index 8075132..dbb77b0 100644 --- a/frontend/src/pages/DocsPage.vue +++ b/frontend/src/pages/DocsPage.vue @@ -36,6 +36,32 @@ function copyText(text: string, id: string) { setTimeout(() => { if (copiedId.value === id) copiedId.value = '' }, 2000) } +// ── "Give this to your AI" — the single self-contained setup prompt (BOT-02) ── +const promptUrl = computed(() => `${window.location.origin}/api/docs/prompt`) +const promptLoading = ref(false) +const promptCopied = ref<'' | 'url' | 'text'>('') + +function copyPromptUrl() { + navigator.clipboard.writeText(promptUrl.value) + promptCopied.value = 'url' + setTimeout(() => { if (promptCopied.value === 'url') promptCopied.value = '' }, 2000) +} + +async function copyFullPromptText() { + promptLoading.value = true + try { + const res = await fetch('/api/docs/prompt') + const text = await res.text() + navigator.clipboard.writeText(text) + promptCopied.value = 'text' + setTimeout(() => { if (promptCopied.value === 'text') promptCopied.value = '' }, 2000) + } catch { + // no-op — user can retry + } finally { + promptLoading.value = false + } +} + const tabs = ['quickstart', 'api', 'challenges', 'scoring', 'security', 'testing'] as const // ── Code examples ── @@ -542,6 +568,40 @@ async function runWebhookTest() { {{ error }}
+ +
+

+ GIVE THIS TO YOUR AI +

+

+ One self-contained prompt covers everything: registration, credentials, both + protocols, every endpoint. Paste it into your AI, or hand it the URL below — no other + docs required. +

+
+ {{ promptUrl }} + +
+ +
+