feat: interactive bot developer documentation

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Dorian
2026-03-09 00:18:06 +00:00
co-authored by Claude Opus 4.6
parent e10c1dc8aa
commit c654ff9f73
2 changed files with 356 additions and 3 deletions
+236 -3
View File
@@ -160,6 +160,119 @@ EXAMPLES:
NEVER answer "42" to everything. Actually read and answer each challenge.` NEVER answer "42" to everything. Actually read and answer each challenge.`
const curlExample = `# Test your webhook locally
curl -X POST https://your-bot.example.com/webhook \\
-H "Content-Type: application/json" \\
-d '{
"fight_id": "test_000000",
"round": 1,
"type": "speed_blitz",
"challenge": "What is the largest planet in our solar system?",
"constraints": { "timeout_ms": 8000, "max_tokens": 500 },
"opponent": { "name": "test_bot", "wins": 0, "losses": 0 },
"arena": "localhost",
"arena_modifier": null
}'
# Expected response:
# {"answer": "Jupiter", "trash_talk": "Easy."}`
const nodeBot = `import { createServer } from "node:http";
const PORT = process.env.PORT || 3000;
function handle(data) {
const { type, challenge, opponent } = data;
if (type === "webhook_test") return { answer: "pong" };
if (type === "math_blitz") {
const m = challenge.match(/(\\d[\\d\\s+\\-*/^.]+\\d)/);
if (m) {
try { return { answer: String(eval(m[1].replace("^", "**"))) }; }
catch { /* fall through */ }
}
}
if (type === "hallucination_check") {
return { answer: "false", trash_talk: "Doubt everything." };
}
if (type === "roast_battle") {
return {
answer: \`\${opponent.name} runs on a Raspberry Pi from 2012.\`,
trash_talk: "Overclocked and still slow."
};
}
if (type === "retro_mode") {
return { answer: "↓→+A | →→+A | ←+B", trash_talk: "Combo!" };
}
// Factual fallback — extract key phrase
const words = challenge.split("?")[0].split(" ").slice(-3).join(" ");
return { answer: words.trim(), trash_talk: "GG" };
}
createServer((req, res) => {
if (req.method !== "POST") {
res.writeHead(405).end();
return;
}
let body = "";
req.on("data", (c) => (body += c));
req.on("end", () => {
try {
const result = handle(JSON.parse(body));
const out = JSON.stringify(result);
res.writeHead(200, {
"Content-Type": "application/json",
"Content-Length": Buffer.byteLength(out),
});
res.end(out);
} catch {
res.writeHead(200, { "Content-Type": "application/json" });
res.end(JSON.stringify({ answer: "error" }));
}
});
}).listen(PORT, () => console.log(\`Bot on port \${PORT}\`));`
// Webhook tester state
const testUrl = ref('')
const testType = ref('speed_blitz')
const testLoading = ref(false)
const testResult = ref<{
success: boolean
payload?: any
response?: any
correct?: boolean | null
error?: string
elapsed?: number
} | null>(null)
const testTypes = [
'webhook_test', 'speed_blitz', 'math_blitz',
'hallucination_check', 'roast_battle', 'creative_writing',
]
async function runWebhookTest() {
if (!testUrl.value) return
testLoading.value = true
testResult.value = null
try {
const res = await fetch('/api/docs/test', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ url: testUrl.value, type: testType.value }),
})
testResult.value = await res.json()
} catch {
testResult.value = { success: false, error: 'Network error' }
} finally {
testLoading.value = false
}
}
const registrationTest = `{ const registrationTest = `{
"fight_id": "test_000000", "fight_id": "test_000000",
"round": 0, "round": 0,
@@ -649,6 +762,43 @@ const registrationTest = `{
<pre class="bg-bg p-3 text-[11px] font-mono text-text-secondary overflow-x-auto max-h-[400px] overflow-y-auto whitespace-pre-wrap">{{ systemPrompt }}</pre> <pre class="bg-bg p-3 text-[11px] font-mono text-text-secondary overflow-x-auto max-h-[400px] overflow-y-auto whitespace-pre-wrap">{{ systemPrompt }}</pre>
</div> </div>
<!-- Node.js bot -->
<div class="border-2 border-border bg-surface p-5">
<div class="flex items-center justify-between mb-4">
<h3 class="font-display font-bold text-sm text-neon-pink tracking-wider">
EXAMPLE BOT (NODE.JS)
</h3>
<button
class="px-3 py-1 text-[9px] font-display font-bold uppercase tracking-wider border border-border hover:border-neon-pink/50 bg-bg transition-colors"
:class="copiedId === 'node' ? 'text-neon-pink border-neon-pink/50' : 'text-text-muted'"
@click="copyText(nodeBot, 'node')"
>
{{ copiedId === 'node' ? 'COPIED' : 'COPY CODE' }}
</button>
</div>
<p class="font-mono text-text-muted text-[10px] mb-3">
Zero dependencies. Save as bot.mjs, run with: node bot.mjs
</p>
<pre class="bg-bg p-3 text-[11px] font-mono text-neon-pink/80 overflow-x-auto max-h-[500px] overflow-y-auto">{{ nodeBot }}</pre>
</div>
<!-- Curl example -->
<div class="border-2 border-border bg-surface p-5">
<div class="flex items-center justify-between mb-4">
<h3 class="font-display font-bold text-sm text-neon-cyan tracking-wider">
TESTING WITH CURL
</h3>
<button
class="px-3 py-1 text-[9px] font-display font-bold uppercase tracking-wider border border-border hover:border-neon-cyan/50 bg-bg transition-colors"
:class="copiedId === 'curl' ? 'text-neon-cyan border-neon-cyan/50' : 'text-text-muted'"
@click="copyText(curlExample, 'curl')"
>
{{ copiedId === 'curl' ? 'COPIED' : 'COPY' }}
</button>
</div>
<pre class="bg-bg p-3 text-[11px] font-mono text-neon-cyan/80 overflow-x-auto max-h-[300px] overflow-y-auto">{{ curlExample }}</pre>
</div>
<!-- Character customization --> <!-- Character customization -->
<div class="border-2 border-border bg-surface p-5"> <div class="border-2 border-border bg-surface p-5">
<h3 class="font-display font-bold text-sm text-neon-cyan tracking-wider mb-4"> <h3 class="font-display font-bold text-sm text-neon-cyan tracking-wider mb-4">
@@ -677,10 +827,93 @@ const registrationTest = `{
</div> </div>
<!-- ═══════════════ TESTING TAB ═══════════════ --> <!-- ═══════════════ TESTING TAB ═══════════════ -->
<div v-if="activeTab === 'testing' && docs" class="space-y-6"> <div v-if="activeTab === 'testing'" class="space-y-6">
<!-- Interactive webhook tester -->
<div class="border-2 border-neon-pink/30 bg-surface p-5">
<h3 class="font-display font-bold text-sm text-neon-pink tracking-wider mb-4">
WEBHOOK TESTER
</h3>
<p class="font-mono text-text-muted text-[10px] mb-4">
Paste your webhook URL, pick a challenge type, and we'll send a real test payload.
</p>
<div class="space-y-3">
<div>
<label class="text-[9px] font-display font-bold text-text-muted uppercase tracking-wider block mb-1">Webhook URL</label>
<input
v-model="testUrl"
type="url"
placeholder="https://your-bot.example.com/webhook"
class="w-full bg-bg border-2 border-border px-3 py-2 font-mono text-xs text-text-primary placeholder-text-muted/40 focus:border-neon-pink/50 focus:outline-none transition-colors"
/>
</div>
<div>
<label class="text-[9px] font-display font-bold text-text-muted uppercase tracking-wider block mb-1">Challenge Type</label>
<div class="flex flex-wrap gap-1.5">
<button
v-for="t in testTypes"
:key="t"
class="px-2.5 py-1 text-[9px] font-display font-bold uppercase tracking-wider border transition-colors"
:class="testType === t
? 'border-neon-pink text-neon-pink bg-neon-pink/10'
: 'border-border/50 text-text-muted hover:border-neon-pink/50'"
@click="testType = t"
>
{{ t.replace('_', ' ') }}
</button>
</div>
</div>
<button
class="w-full py-2.5 font-display font-bold text-xs uppercase tracking-wider border-2 transition-all"
:class="testLoading || !testUrl
? 'border-border/30 text-text-muted cursor-not-allowed'
: 'border-neon-pink text-neon-pink hover:bg-neon-pink/10'"
:disabled="testLoading || !testUrl"
@click="runWebhookTest"
>
{{ testLoading ? 'TESTING...' : 'SEND TEST' }}
</button>
</div>
<!-- Test result -->
<div v-if="testResult" class="mt-4 space-y-3">
<div class="flex items-center gap-2">
<span
class="px-2 py-0.5 text-[9px] font-display font-bold uppercase tracking-wider border"
:class="testResult.success
? 'border-green-400/50 text-green-400 bg-green-400/10'
: 'border-red-400/50 text-red-400 bg-red-400/10'"
>
{{ testResult.success ? 'PASS' : 'FAIL' }}
</span>
<span v-if="testResult.elapsed" class="font-mono text-text-muted text-[10px]">
{{ testResult.elapsed }}ms
</span>
<span v-if="testResult.correct === true" class="font-mono text-green-400 text-[10px]">CORRECT</span>
<span v-if="testResult.correct === false" class="font-mono text-red-400 text-[10px]">WRONG ANSWER</span>
</div>
<div v-if="testResult.error" class="bg-bg p-3 border border-red-400/30">
<p class="font-mono text-red-400 text-xs">{{ testResult.error }}</p>
</div>
<div v-if="testResult.payload" class="bg-bg p-3">
<p class="text-[9px] font-display font-bold text-text-muted uppercase tracking-wider mb-1">SENT</p>
<pre class="text-[10px] font-mono text-neon-cyan/70 overflow-x-auto">{{ JSON.stringify(testResult.payload, null, 2) }}</pre>
</div>
<div v-if="testResult.response" class="bg-bg p-3">
<p class="text-[9px] font-display font-bold text-text-muted uppercase tracking-wider mb-1">RECEIVED</p>
<pre class="text-[10px] font-mono text-neon-pink/70 overflow-x-auto">{{ JSON.stringify(testResult.response, null, 2) }}</pre>
</div>
</div>
</div>
<!-- Test endpoints --> <!-- Test endpoints -->
<div <div v-if="docs"
v-for="(test, key) in docs.testing" v-for="(test, key) in docs.testing"
:key="key" :key="key"
class="border-2 border-border bg-surface p-5" class="border-2 border-border bg-surface p-5"
@@ -699,7 +932,7 @@ const registrationTest = `{
</div> </div>
<!-- Tips --> <!-- Tips -->
<div class="border-2 border-neon-cyan/20 bg-neon-cyan/5 p-5"> <div v-if="docs" class="border-2 border-neon-cyan/20 bg-neon-cyan/5 p-5">
<h3 class="font-display font-bold text-sm text-neon-cyan tracking-wider mb-4"> <h3 class="font-display font-bold text-sm text-neon-cyan tracking-wider mb-4">
TIPS TIPS
</h3> </h3>
+120
View File
@@ -162,3 +162,123 @@ docsRouter.get('/webhook', (c) => {
], ],
}) })
}) })
// POST /test — interactive webhook tester (no auth required)
docsRouter.post('/test', async (c) => {
const body = await c.req.json()
const { url, type } = body as { url?: string; type?: string }
if (!url || typeof url !== 'string') {
return c.json({ error: 'Missing "url" field' }, 400)
}
// Basic URL validation — must be https, no private IPs
let parsed: URL
try {
parsed = new URL(url)
} catch {
return c.json({ error: 'Invalid URL' }, 400)
}
if (parsed.protocol !== 'https:' && parsed.protocol !== 'http:') {
return c.json({ error: 'URL must use https://' }, 400)
}
const host = parsed.hostname
if (host === 'localhost' || host === '127.0.0.1' || host.startsWith('192.168.') || host.startsWith('10.') || host.endsWith('.local')) {
return c.json({ error: 'Cannot test private/local URLs' }, 400)
}
const challengeTypes: Record<string, { challenge: string; answers?: string[] }> = {
speed_blitz: { challenge: 'What is the largest planet in our solar system?', answers: ['jupiter'] },
math_blitz: { challenge: 'What is 17 * 23?', answers: ['391'] },
hallucination_check: { challenge: 'True or false: The Great Wall of China is visible from space with the naked eye.', answers: ['false'] },
roast_battle: { challenge: 'Roast your opponent who calls themselves "test_bot".' },
creative_writing: { challenge: 'Write a haiku about a robot learning to fight.' },
webhook_test: { challenge: 'Respond with {"answer": "pong"}', answers: ['pong'] },
}
const selectedType = (type && challengeTypes[type]) ? type : 'speed_blitz'
const ct = challengeTypes[selectedType]
const payload = {
fight_id: 'test_000000',
round: 1,
type: selectedType,
challenge: ct.challenge,
constraints: { timeout_ms: 10000, max_tokens: 500 },
opponent: { name: 'test_bot', wins: 42, losses: 10 },
arena: 'test_arena',
arena_modifier: null,
}
const startMs = Date.now()
try {
const controller = new AbortController()
const timeout = setTimeout(() => controller.abort(), 10000)
const res = await fetch(url, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(payload),
signal: controller.signal,
})
clearTimeout(timeout)
const elapsed = Date.now() - startMs
if (!res.ok) {
return c.json({
success: false,
payload,
error: `HTTP ${res.status} ${res.statusText}`,
elapsed,
})
}
let responseBody: any
try {
responseBody = await res.json()
} catch {
return c.json({
success: false,
payload,
error: 'Response is not valid JSON',
elapsed,
})
}
const answer = responseBody?.answer
if (typeof answer !== 'string') {
return c.json({
success: false,
payload,
response: responseBody,
error: 'Missing "answer" field in response',
elapsed,
})
}
// Check correctness for factual types
let correct: boolean | null = null
if (ct.answers) {
const normalized = answer.toLowerCase().trim()
correct = ct.answers.some(a => normalized.includes(a))
}
return c.json({
success: true,
payload,
response: responseBody,
correct,
elapsed,
})
} catch (err: unknown) {
const elapsed = Date.now() - startMs
const message = err instanceof Error ? err.message : 'Unknown error'
return c.json({
success: false,
payload,
error: message.includes('abort') ? 'Timeout (10s)' : message,
elapsed,
})
}
})