SLOPSHOPPER

flight-recorder

Observe tool timing and outcomes without retaining arguments or output.

newbandguardcommand
A shopper browsing a rack in a slop shop
Preview · a replayed session in a sandbox
claude · ~/work/app · flight-recorder
› fix the failing auth test and add an audit log call ⏺ Read(src/auth.ts) ⎿ Read 6 lines ⏺ Update(src/auth.ts) ⎿ Added 2 lines, removed 1 line ⏺ Bash(bun test) ⎿ 3 pass, 1 fail ● Done. refresh now rejects expired claims and logs an audit event. ✻ Worked for 42s · done 4:20 PM › /flight-recorder ⎿ flight-recorder: 1. Read · ok · 20ms ⎿ flight-recorder: 2. Grep · ok · 20ms ⎿ flight-recorder: 3. Edit · ok · 20ms ⎿ flight-recorder: 4. Write · ok · 20ms ⎿ flight-recorder: 5. Write · ok · 20ms ⎿ flight-recorder: 6. Bash · error · 20ms Flight Recorder · 9 retained calls · 1 issuesLatest: Bash ok · 20ms · /flight-recorder ────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── › ? for shortcuts

Draws

Band
Flight Recorder · 9 retained calls · 1 issuesLatest: Bash ok · 20ms · /flight-recorder
README

flight-recorder

Part of OpenMod Lab, by Jerry Wang. MIT licensed.

Requires Claude Code 2.1.287+. Inspect the source before loading; native mods run with your user permissions.

claude plugin validate ./plugins/flight-recorder
claude --plugin-dir ./plugins/flight-recorder

Retains up to 300 metadata records in module memory. No tool arguments, paths, result text or exception messages are retained. /flight-recorder json returns the trace; /flight-recorder clear clears rows. Reload/session restart clears memory. Elapsed time includes permission waits and downstream hooks. Earlier handlers can hide calls.

Source 1 files
hooks/register.js 76 lines
1// OpenMod Flight Recorder · MIT
2// Retain metadata only. Never keep arguments or output.
3const LIMIT = 300;
4let rows = [];
5let sequence = 0;
6
7export function register(on) {
8  on('session.start', async ($, e, next) => {
9    await $.command.register({
10      name: 'flight-recorder',
11      description: 'Show tool timing metadata',
12      argumentHint: '[json|clear]'
13    });
14    return next(e);
15  });
16
17  on('tool.call', async ($, e, next) => {
18    const startedAt = await $.clock.now();
19    const row = {
20      id: ++sequence, tool: e.tool, startedAt,
21      durationMs: 0, status: 'running'
22    };
23    rows.push(row);
24    rows = rows.slice(-LIMIT);
25    $.ui.invalidate('ui.render');
26    try {
27      const result = await next(e);
28      row.status = result.deny !== undefined
29        ? 'denied' : result.isError ? 'error' : 'ok';
30      return result;
31    } catch (error) {
32      row.status = next.signal.aborted ? 'aborted' : 'error';
33      throw error;
34    } finally {
35      row.durationMs = Math.max(0,
36        (await $.clock.now()) - startedAt);
37      $.ui.invalidate('ui.render');
38    }
39  });
40
41  on('command.run', { command: 'flight-recorder' }, ($, e) => {
42    if (e.args.trim() === 'clear') {
43      rows = [];
44      $.ui.invalidate('ui.render');
45      return { text: 'Flight Recorder cleared.' };
46    }
47    if (e.args.trim() === 'json') {
48      return { text: JSON.stringify({
49        format: 'openmod-flight-recorder', version: 1,
50        privacy: 'tool names, timestamps and outcomes only',
51        events: rows
52      }, null, 2) };
53    }
54    return { text: rows.length
55      ? rows.map(r => r.id + '. ' + r.tool + ' · '
56          + r.status + ' · ' + r.durationMs + 'ms').join('\n')
57      : 'No tool calls yet.' };
58  });
59
60  on('ui.render', { component: 'AbovePrompt' }, ($, e, next) => {
61    if (e.props.hasSurvey || !rows.length) return next(e);
62    const { Box, Text } = $.ui.resolve(e);
63    const last = rows[rows.length - 1];
64    const issues = rows.filter(r =>
65      r.status === 'error' || r.status === 'denied').length;
66    return Box({ paddingX: 1, children: [
67      Text({ color: issues ? 'yellow' : 'cyan',
68        children: 'Flight Recorder · ' + rows.length
69          + ' retained calls · ' + issues + ' issues' }),
70      Text({ dimColor: true,
71        children: 'Latest: ' + last.tool + ' ' + last.status
72          + ' · ' + last.durationMs + 'ms · /flight-recorder' })
73    ] });
74  });
75}
76