import { cliCommandMetas } from '@rnx/skills/cli' import { renderCliCommandHelp, renderCliGroupHelp, renderCliIndexHelp, } from '@rnx/skills/cli/renderers' import { formatFlagHelp } from 'sootsim-engine/settings/cli-flags' import { DEFAULT_SOOTSIM_BRIDGE_PORT, DEFAULT_SOOTSIM_BRIDGE_URL, } from '../src/bridge-constants' import { getRnxCommandAvailability, getRnxFirstPositional, getRnxCommandRegistration, RNX_COMMAND_REGISTRY, } from './command-registry' import type { RnxBackendKind } from './command-contract' export interface RnxHelpOptions { backend: RnxBackendKind command?: string | null commandArgs?: string[] prefer?: 'command' | 'verb' group?: string | null } const HELP_CONTEXT = { bridgePort: DEFAULT_SOOTSIM_BRIDGE_PORT, defaultShellUrl: DEFAULT_SOOTSIM_BRIDGE_URL, } function backendLabel(command: string, args: readonly string[]): string { const available = (['node', 'browser'] satisfies RnxBackendKind[]).filter( (backend) => getRnxCommandAvailability(command, args, backend).available, ) return `available backends: ${available.join(', ')}` } function renderPreviewHelp(command: 'web' | 'ios' | 'android'): string { return `rnx ${command} — select or ensure the current branch's existing ${command} preview usage: rnx ${command} The browser backend selects the Factory-owned ${command} iframe. It never launches a host process or Cloudflare runtime.` } function renderFactoryBrowserLeaves(command: string): string | null { switch (command) { case 'do': return `Factory browser leaves: rnx do navigate /route` case 'shell': return `Factory browser leaves: rnx shell appearance light|dark` case 'assert': return `Factory browser leaves: rnx assert find (--testid |--text |--role ) [--exists|--empty] [--contains ]` case 'screenshot': return `Factory browser leaves: rnx screenshot rnx screenshot ask rnx screenshot appearance light|dark /route --by --value rnx screenshot motion /route --trigger-by --trigger-value --result-by --result-value [--duration-ms <100..2000>]` default: return null } } function renderRootAvailability(backend: RnxBackendKind): string { const visibleCommands = new Set([ ...cliCommandMetas.filter((meta) => !meta.hidden).map((meta) => meta.id), 'web', 'ios', 'android', ]) const visibleRegistry = RNX_COMMAND_REGISTRY.filter((entry) => visibleCommands.has(entry.name), ) const available = visibleRegistry .filter((entry) => getRnxCommandAvailability(entry.name, [], backend).available) .map((entry) => entry.name) const unavailable = visibleRegistry .filter((entry) => !getRnxCommandAvailability(entry.name, [], backend).available) .map((entry) => entry.name) return `backend availability (${backend}): available: ${available.join(', ')} unavailable: ${unavailable.join(', ')} Run \`rnx --help\` for leaf availability and usage.` } export function renderRnxHelp(options: RnxHelpOptions): string { const command = options.command ?? null const commandArgs = options.commandArgs ?? [] if (!command) { return `${renderCliIndexHelp(HELP_CONTEXT, formatFlagHelp()).trimEnd()} browser preview selectors: rnx web select the existing Factory web preview rnx ios select the existing Factory iOS preview rnx android select the existing Factory Android preview ${renderRootAvailability(options.backend)}` } let body: string | null if (command === 'web' || command === 'ios' || command === 'android') { body = renderPreviewHelp(command) } else if (options.group === command && !options.prefer) { body = renderCliGroupHelp(command, HELP_CONTEXT, formatFlagHelp()) } else { const subVerb = getRnxFirstPositional(commandArgs) const grouping = new Set(['do', 'get', 'debug', 'wait']) const helpName = options.prefer === 'verb' && subVerb ? subVerb : command body = renderCliCommandHelp(helpName, HELP_CONTEXT, { prefer: options.prefer, group: options.group, }) if (!body && grouping.has(command)) { body = renderCliGroupHelp(command, HELP_CONTEXT, formatFlagHelp()) } } if (!body) return `unknown command: ${command}` const registration = getRnxCommandRegistration(command) if (!registration) return body const current = getRnxCommandAvailability(command, commandArgs, options.backend) const currentLine = current.available ? `${options.backend} backend: available` : `${options.backend} backend: ${current.reason}` const factoryLeaves = options.backend === 'browser' ? renderFactoryBrowserLeaves(command) : null return `${body.trimEnd()} ${factoryLeaves ? `${factoryLeaves}\n\n` : ''}${backendLabel(command, commandArgs)} ${currentLine}` }