// rnx timeline — control the semantic agent-facing event log // // `rnx record` is for video/screen capture; `rnx debug record` is // for the engine-internal debug-channel ring; this command is the third // surface and explicitly distinct: it manages the SootSim.bridges.timeline API, // the unified, agent-facing semantic event stream across host / shell // worker / tenant worker. // // usage: // rnx timeline status # what's enabled // rnx timeline start [kinds] # enable kinds (default: opt-in defaults) // rnx timeline stop [kinds] # disable kinds (or 'all') // rnx timeline clear # drop all events + cursors // rnx timeline dump # write current ring to JSON // // see `rnx what-happened` for the read side. import { writeFileSync } from 'fs' import { rnxExit } from '../run-rnx' import { callInBridge, createBridgeFromParsed, parseBridgeCliArgs } from '../ws-bridge' interface TimelineOptions { port?: number verbose?: boolean } function printHelp() { console.log(` rnx timeline — control the semantic event timeline usage: rnx timeline [args] subcommands: status show enabled kinds and ring sizes start [kinds] enable kinds (csv); 'all' for every opt-in stop [kinds] disable kinds (csv); 'all' for every kind clear drop all events + cursors dump write current merged ring to JSON note: cheap kinds (console, fetch, app-launch, keyboard, screen, alert, toast, notification, route, shell, actionsheet, picker) record at all times. start/stop enables opt-in kinds (react-commit, scroll, gesture, text-input, layout, reanimated, animation). read the timeline with: rnx what-happened `) } export async function runTimeline(args: string[], opts: TimelineOptions) { const parsed = parseBridgeCliArgs(args, { port: opts.port, stripBooleanFlags: ['--json', '--help', '-h'], }) const sub = parsed.positional[0] if (!sub || args.includes('--help') || args.includes('-h')) { printHelp() rnxExit(0) } const json = args.includes('--json') const bridge = createBridgeFromParsed(parsed) const rest = parsed.positional.slice(1) const kinds = rest[0] ? rest[0] .split(',') .map((s) => s.trim()) .filter(Boolean) : [] try { switch (sub) { case 'status': { const status = await callInBridge(bridge, 'SootSim.bridges.timeline.status') console.log(JSON.stringify(status, null, json ? 0 : 2)) break } case 'start': { if (kinds.length === 0) kinds.push('all') await callInBridge(bridge, 'SootSim.bridges.timeline.enable', ...kinds) const status = await callInBridge(bridge, 'SootSim.bridges.timeline.status') console.log(JSON.stringify(status, null, json ? 0 : 2)) break } case 'stop': { if (kinds.length === 0) kinds.push('all') await callInBridge(bridge, 'SootSim.bridges.timeline.disable', ...kinds) const status = await callInBridge(bridge, 'SootSim.bridges.timeline.status') console.log(JSON.stringify(status, null, json ? 0 : 2)) break } case 'clear': { await callInBridge(bridge, 'SootSim.bridges.timeline.clear') console.log(' cleared') break } case 'dump': { const outPath = rest[0] if (!outPath) { console.error(' usage: rnx timeline dump ') rnxExit(1) } const result = await callInBridge<{ events: unknown[] }>( bridge, 'SootSim.bridges.timeline.recent', { limit: 1_000_000 }, ) writeFileSync(outPath, JSON.stringify(result.events, null, 2) + '\n') console.log(` wrote ${result.events.length} event(s) to ${outPath}`) break } default: { console.error(` unknown subcommand: ${sub}`) printHelp() rnxExit(1) } } } finally { bridge.close() } }