Created
February 19, 2026 19:42
-
-
Save worm-emoji/794c13433a7b830e916f90d7808ce3f9 to your computer and use it in GitHub Desktop.
pmapi: local public v2 question-gen + create-market test script
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| /** | |
| * Test script for public v2 question generation + market creation. | |
| * | |
| * Usage: | |
| * bun run apps/pmapi/scripts/test-public-create-market.ts --question "Will ... ?" | |
| * bun run apps/pmapi/scripts/test-public-create-market.ts --questionId <0xquestionId> | |
| * | |
| * Optional flags: | |
| * --apiBase API base URL (default: http://localhost:3001/public/v2) | |
| * --apiKey Bearer token (default: local) | |
| * --pollMs Poll interval in ms when using --question (default: 2000) | |
| * --maxAttempts Max polling attempts when using --question (default: 45) | |
| */ | |
| import { parseArgs } from 'node:util' | |
| import { z } from 'zod' | |
| import { log } from '../src/common/log' | |
| const { values } = parseArgs({ | |
| args: process.argv.slice(2), | |
| options: { | |
| question: { type: 'string', short: 'q' }, | |
| questionId: { type: 'string', short: 'i' }, | |
| apiBase: { type: 'string', short: 'b' }, | |
| apiKey: { type: 'string', short: 'k' }, | |
| pollMs: { type: 'string' }, | |
| maxAttempts: { type: 'string' }, | |
| help: { type: 'boolean', short: 'h' }, | |
| }, | |
| }) | |
| const question = values.question ?? Bun.env.QUESTION | |
| const questionId = values.questionId ?? Bun.env.QUESTION_ID | |
| const apiBase = values.apiBase ?? 'http://localhost:3001/public/v2' | |
| const apiKey = values.apiKey ?? Bun.env.API_KEY ?? 'local' | |
| const pollMs = Number(values.pollMs ?? Bun.env.POLL_MS ?? '2000') | |
| const maxAttempts = Number(values.maxAttempts ?? Bun.env.MAX_ATTEMPTS ?? '45') | |
| const apiOrigin = new URL(apiBase).origin | |
| const normalizedApiBase = apiBase.endsWith('/') ? apiBase.slice(0, -1) : apiBase | |
| const zSubmitQuestionResponse = z.object({ | |
| submissionId: z.string(), | |
| pollUrl: z.string().optional(), | |
| status: z.string(), | |
| }) | |
| const zQuestionSubmissionResponse = z.object({ | |
| submissionId: z.string().optional(), | |
| status: z.enum(['pending', 'processing', 'completed', 'failed']), | |
| questions: z.array( | |
| z.object({ | |
| id: z.string(), | |
| text: z.string().optional(), | |
| criteria: z.string().optional(), | |
| }), | |
| ), | |
| statusUpdates: z | |
| .array( | |
| z.object({ | |
| tool: z.string(), | |
| status: z.string(), | |
| timestamp: z.string(), | |
| }), | |
| ) | |
| .default([]), | |
| }) | |
| const zCreateMarketResponse = z.object({ | |
| marketId: z.string(), | |
| txHash: z.string(), | |
| }) | |
| const printUsage = () => { | |
| log.info('Usage:') | |
| log.info( | |
| ' bun run apps/pmapi/scripts/test-public-create-market.ts --question "Will ... ?"', | |
| ) | |
| log.info( | |
| ' bun run apps/pmapi/scripts/test-public-create-market.ts --questionId <0xquestionId>', | |
| ) | |
| log.info( | |
| 'Options: --apiBase <url> --apiKey <token> --pollMs <ms> --maxAttempts <n>', | |
| ) | |
| log.info( | |
| 'Env fallback: QUESTION, QUESTION_ID, API_KEY, POLL_MS, MAX_ATTEMPTS', | |
| ) | |
| } | |
| if (values.help) { | |
| printUsage() | |
| process.exit(0) | |
| } | |
| if (!questionId && !question) { | |
| printUsage() | |
| process.exit(1) | |
| } | |
| if (!Number.isFinite(pollMs) || pollMs <= 0) { | |
| log.error('pollMs must be a positive number') | |
| process.exit(1) | |
| } | |
| if (!Number.isFinite(maxAttempts) || maxAttempts <= 0) { | |
| log.error('maxAttempts must be a positive number') | |
| process.exit(1) | |
| } | |
| const resolveUrl = (path: string) => { | |
| if (path.startsWith('http://') || path.startsWith('https://')) { | |
| return path | |
| } | |
| if (path.startsWith('/')) { | |
| return `${apiOrigin}${path}` | |
| } | |
| return `${normalizedApiBase}/${path}` | |
| } | |
| const apiRequest = async (path: string, init: RequestInit = {}) => { | |
| const response = await fetch(resolveUrl(path), { | |
| headers: { | |
| 'Content-Type': 'application/json', | |
| Authorization: `Bearer ${apiKey}`, | |
| ...init.headers, | |
| }, | |
| ...init, | |
| }) | |
| const raw = await response.text() | |
| const data = (() => { | |
| try { | |
| return JSON.parse(raw) as unknown | |
| } catch { | |
| return raw | |
| } | |
| })() | |
| return { status: response.status, data } | |
| } | |
| const sleep = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms)) | |
| const createMarket = async (id: string) => { | |
| log.info('Creating market from questionId') | |
| log.info(`questionId: ${id}`) | |
| const response = await apiRequest('markets/create', { | |
| method: 'POST', | |
| body: JSON.stringify({ questionId: id }), | |
| }) | |
| log.info(`create status: ${response.status}`) | |
| log.info('create response:') | |
| log.info(JSON.stringify(response.data, null, 2)) | |
| if (!response.status || response.status !== 200) { | |
| process.exit(1) | |
| } | |
| const parsed = zCreateMarketResponse.safeParse(response.data) | |
| if (!parsed.success) { | |
| log.error( | |
| { error: parsed.error.flatten() }, | |
| 'Invalid create response shape', | |
| ) | |
| process.exit(1) | |
| } | |
| } | |
| const runQuestionFlow = async (prompt: string) => { | |
| log.info('Submitting question') | |
| log.info(`question: ${prompt}`) | |
| const submitResponse = await apiRequest('questions', { | |
| method: 'POST', | |
| body: JSON.stringify({ question: prompt }), | |
| }) | |
| log.info(`submit status: ${submitResponse.status}`) | |
| log.info('submit response:') | |
| log.info(JSON.stringify(submitResponse.data, null, 2)) | |
| if (submitResponse.status !== 200) { | |
| process.exit(1) | |
| } | |
| const submitParsed = zSubmitQuestionResponse.safeParse(submitResponse.data) | |
| if (!submitParsed.success) { | |
| log.error( | |
| { error: submitParsed.error.flatten() }, | |
| 'Invalid submit response shape', | |
| ) | |
| process.exit(1) | |
| } | |
| const pollTarget = | |
| submitParsed.data.pollUrl ?? | |
| `questions/submissions/${submitParsed.data.submissionId}` | |
| for (let attempt = 1; attempt <= maxAttempts; attempt++) { | |
| const pollResponse = await apiRequest(pollTarget, { method: 'GET' }) | |
| if (pollResponse.status !== 200) { | |
| log.error({ pollResponse }, 'Polling failed') | |
| process.exit(1) | |
| } | |
| const pollParsed = zQuestionSubmissionResponse.safeParse(pollResponse.data) | |
| if (!pollParsed.success) { | |
| log.error( | |
| { error: pollParsed.error.flatten() }, | |
| 'Invalid polling response shape', | |
| ) | |
| process.exit(1) | |
| } | |
| const latestUpdate = | |
| pollParsed.data.statusUpdates[pollParsed.data.statusUpdates.length - 1] | |
| const updateSummary = latestUpdate | |
| ? ` | ${latestUpdate.tool}: ${latestUpdate.status}` | |
| : '' | |
| log.info( | |
| `poll ${attempt}/${maxAttempts}: status=${pollParsed.data.status}${updateSummary}`, | |
| ) | |
| if (pollParsed.data.status === 'failed') { | |
| log.error({ pollResponse: pollParsed.data }, 'Question generation failed') | |
| process.exit(1) | |
| } | |
| if (pollParsed.data.status === 'completed') { | |
| const firstQuestion = pollParsed.data.questions[0] | |
| if (!firstQuestion) { | |
| log.error('Question generation completed with no questions') | |
| process.exit(1) | |
| } | |
| if (firstQuestion.text) { | |
| log.info(`selected question: ${firstQuestion.text}`) | |
| } | |
| if (firstQuestion.criteria) { | |
| log.info(`criteria: ${firstQuestion.criteria}`) | |
| } | |
| await createMarket(firstQuestion.id) | |
| return | |
| } | |
| await sleep(pollMs) | |
| } | |
| log.error( | |
| `Question generation did not complete within ${maxAttempts} attempts`, | |
| ) | |
| process.exit(1) | |
| } | |
| const main = async () => { | |
| const redactedApiKey = | |
| apiKey.length <= 6 ? '***' : `${apiKey.slice(0, 3)}...${apiKey.slice(-2)}` | |
| log.info(`apiBase: ${apiBase}`) | |
| log.info(`auth: Bearer ${redactedApiKey}`) | |
| if (questionId) { | |
| await createMarket(questionId) | |
| return | |
| } | |
| if (question) { | |
| await runQuestionFlow(question) | |
| } | |
| } | |
| main().catch((error) => { | |
| log.error({ error }, 'Script failed') | |
| process.exit(1) | |
| }) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment