diff --git a/browse/src/server.ts b/browse/src/server.ts index 580bd67e..5e76f421 100644 --- a/browse/src/server.ts +++ b/browse/src/server.ts @@ -18,6 +18,8 @@ import { handleReadCommand } from './read-commands'; import { handleWriteCommand } from './write-commands'; import { handleMetaCommand } from './meta-commands'; import { handleCookiePickerRoute } from './cookie-picker-routes'; +import { COMMAND_DESCRIPTIONS } from './commands'; +import { SNAPSHOT_FLAGS } from './snapshot'; import { resolveConfig, ensureStateDir, readVersionHash } from './config'; import * as fs from 'fs'; import * as path from 'path'; @@ -37,6 +39,47 @@ function validateAuth(req: Request): boolean { return header === `Bearer ${AUTH_TOKEN}`; } +// ─── Help text (auto-generated from COMMAND_DESCRIPTIONS) ──────── +function generateHelpText(): string { + // Group commands by category + const groups = new Map(); + for (const [cmd, meta] of Object.entries(COMMAND_DESCRIPTIONS)) { + const display = meta.usage || cmd; + const list = groups.get(meta.category) || []; + list.push(display); + groups.set(meta.category, list); + } + + const categoryOrder = [ + 'Navigation', 'Reading', 'Interaction', 'Inspection', + 'Visual', 'Snapshot', 'Meta', 'Tabs', 'Server', + ]; + + const lines = ['gstack browse — headless browser for AI agents', '', 'Commands:']; + for (const cat of categoryOrder) { + const cmds = groups.get(cat); + if (!cmds) continue; + lines.push(` ${(cat + ':').padEnd(15)}${cmds.join(', ')}`); + } + + // Snapshot flags from source of truth + lines.push(''); + lines.push('Snapshot flags:'); + const flagPairs: string[] = []; + for (const flag of SNAPSHOT_FLAGS) { + const label = flag.valueHint ? `${flag.short} ${flag.valueHint}` : flag.short; + flagPairs.push(`${label} ${flag.long}`); + } + // Print two flags per line for compact display + for (let i = 0; i < flagPairs.length; i += 2) { + const left = flagPairs[i].padEnd(28); + const right = flagPairs[i + 1] || ''; + lines.push(` ${left}${right}`); + } + + return lines.join('\n'); +} + // ─── Buffer (from buffers.ts) ──────────────────────────────────── import { consoleBuffer, networkBuffer, dialogBuffer, addConsoleEntry, addNetworkEntry, addDialogEntry, type LogEntry, type NetworkEntry, type DialogEntry } from './buffers'; export { consoleBuffer, networkBuffer, dialogBuffer, addConsoleEntry, addNetworkEntry, addDialogEntry, type LogEntry, type NetworkEntry, type DialogEntry }; @@ -191,29 +234,7 @@ async function handleCommand(body: any): Promise { } else if (META_COMMANDS.has(command)) { result = await handleMetaCommand(command, args, browserManager, shutdown); } else if (command === 'help') { - const helpText = [ - 'gstack browse — headless browser for AI agents', - '', - 'Commands:', - ' Navigation: goto , back, forward, reload', - ' Interaction: click , fill , select , hover, type, press, scroll, wait', - ' Read: text [sel], html [sel], links, forms, accessibility, cookies, storage, console, network, perf', - ' Evaluate: js , eval , css , attrs , is ', - ' Snapshot: snapshot [-i] [-c] [-d N] [-s sel] [-D] [-a] [-o path] [-C]', - ' Screenshot: screenshot [path], pdf [path], responsive ', - ' Tabs: tabs, tab , newtab [url], closetab [id]', - ' State: cookie , cookie-import , cookie-import-browser [browser]', - ' Headers: header [name] [value], useragent [string]', - ' Upload: upload [file2...]', - ' Dialogs: dialog, dialog-accept [text], dialog-dismiss', - ' Meta: status, stop, restart, diff, chain, help', - '', - 'Snapshot flags:', - ' -i interactive only -c compact (remove empty nodes)', - ' -d N limit depth -s sel scope to CSS selector', - ' -D diff vs previous -a annotated screenshot with ref labels', - ' -o path output file -C cursor-interactive elements', - ].join('\n'); + const helpText = generateHelpText(); return new Response(helpText, { status: 200, headers: { 'Content-Type': 'text/plain' },