SLOPSHOPPER

overdrive-tools

Colors every tool row by kind (INF cyan, CHG magenta, RUN yellow) and folds finished rows; click a tag to open one, /tools folded|changes|all sets the default

newrowscommandtoast
A shopper browsing a rack in a slop shop
Preview · a replayed session in a sandbox
claude · ~/work/app · overdrive-tools
› fix the failing auth test and add an audit log call ⏺ Read(src/auth.ts) ⎿ Read 6 lines odt-undefined ▌ ⟨Claude Code's own drawing⟩ ⏺ 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 › /tools ⎿ overdrive-tools: every tool row is open. (fullscreen layout, clicks reach the tags) ────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── › ? for shortcuts

Draws

Tool row
odt-undefined ▌ ⟨Claude Code's own drawing⟩
README

<img alt="ops-overdrive — Your terminal, but louder. CLI · MODS · STATUSLINE · FLAIR." src="assets/banner/hero-v2-dark.svg" width="100%">

<img alt="Overdrive treats a local Claude Code setup as one versioned tool. It adds a loud terminal look and two Claude Code mods: a tab band above the prompt and colored, foldable tool rows." src="assets/readme/lead-v2-dark.svg" width="100%">

<img alt="11 CLI COMMANDS. 2 CLAUDE CODE MODS. 5 STATUSLINE ROWS. 68 MOD TESTS" src="assets/readme/stats-v2-dark.svg" width="100%">

<img alt="01 WHAT IT DOES. ONE RIG. ONE VERSION. ONE SERPENT." src="assets/readme/divider-what-v2.svg" width="100%">

<img alt="VERSION: The overdrive CLI versions ~/.claude as a local git repo and pins every tool repo by SHA in one lock file. MODS: overdrive-tabs draws a tab band above the prompt. overdrive-tools colors every tool row and folds it. LOOK: An output style, a session banner and a five-row statusline share one neon palette and one art language" src="assets/readme/features-v2-dark.svg" width="100%">

<img alt="One version for the setup: VERSION holds the semver, overdrive.lock pins components by SHA, and every version keeps its own log. Drift on session start: A SessionStart hook checks the pins in under 150 ms and records drift in the background. Colored tool rows: INF cyan for reads, CHG magenta for changes, RUN yellow for shell and agents. Click a tag to fold or open the row. Tab band: Code, tickets, Claude usage, overdrive state and a serpent pet, in a pink box above the prompt. Session log: Every tool appends INFO, WARN and CRITICAL lines to one crash-safe log per Claude session. Trace: overdrive trace reads a finished session: tool calls, wall time and token counts per tool" src="assets/readme/what-v2-dark.svg" width="100%">

<img alt="02 QUICK START. TWO MODS IN FOUR COMMANDS." src="assets/readme/divider-start-v2.svg" width="100%">

<img alt="Terminal: $ git clone https://github.com/VelimirMueller/ops-overdrive &amp;&amp; cd ops-overdrive | $ claude plugin marketplace add ./mods | $ claude plugin install overdrive-tools@overdrive-mods | $ claude plugin install overdrive-tabs@overdrive-mods | # then, inside Claude Code: /tools folded" src="assets/readme/start-v2-dark.svg" width="100%">

git clone https://github.com/VelimirMueller/ops-overdrive && cd ops-overdrive
claude plugin marketplace add ./mods
claude plugin install overdrive-tools@overdrive-mods
claude plugin install overdrive-tabs@overdrive-mods

<img alt="output-styles/overdrive.md: Copy to ~/.claude/output-styles/, then select it under Output style in /config. statusline-command.sh: Point statusLine.command in ~/.claude/settings.json at it. Chips for tools you lack stay empty. hooks/overdrive-banner.sh: A SessionStart hook: the pink banner and the frame reminder. tools/overdrive/overdrive: Link into your PATH. It expects ~/.claude to be a local git repo" src="assets/readme/extras-v2-dark.svg" width="100%">

<img alt="claude: Claude Code with function hooks (early access). Built on 2.1.303. bash · git · python3: For the CLI, the hooks and overdrive trace. rig tools: Optional. The tickets tab reads tix and code-context and shows empty rows without them" src="assets/readme/needs-v2-dark.svg" width="100%">

<img alt="03 HOW IT WORKS. DRAWINGS CHANGE. RECORDS DO NOT." src="assets/readme/divider-how-v2.svg" width="100%">

<img alt="TOOL CALL -&gt; OVERDRIVE-TOOLS -&gt; TRANSCRIPT ROW. The mod wraps the engine&#x27;s own drawing. The stored row and the model&#x27;s context do not change." src="assets/readme/flow-v2-dark.svg" width="100%">

<img alt="~/.claude -&gt; overdrive snapshot -&gt; overdrive.lock" src="assets/readme/lock-v2-dark.svg" width="100%">

<img alt="04 USAGE. EVERY COMMAND, ONE LINE EACH." src="assets/readme/divider-usage-v2.svg" width="100%">

<img alt="overdrive: Status: version, uncommitted changes, lock drift. Read-only. overdrive snapshot [--if-drift] [&quot;msg&quot;]: Regenerate the lock and commit if anything changed. overdrive bump patch|minor|major &quot;msg&quot;: New version, new log, commit and tag. overdrive history · changelog [N]: Versions with their messages and log lines. overdrive trace [&lt;session&gt;|--last]: Tool calls, wall time and tokens of one session. overdrive catalog · limits: Every tool with its glyph and version. Known limits and caps. overdrive session show|path|list|rm: This session&#x27;s log" src="assets/readme/cmds-v2-dark.svg" width="100%">

<img alt="/tools folded: Default. Every finished tool row starts folded. /tools changes: Fold info rows. Keep changes and runs open. /tools all: Open every row. Bare /tools toggles folded and all. click a tag: Fold or open that one row. Needs the fullscreen layout" src="assets/readme/tools-v2-dark.svg" width="100%">

<img alt="05 STATUS. STABLE ON TOP OF AN EARLY API." src="assets/readme/divider-status-v2.svg" width="100%">

<img alt="overdrive CLI and lock format: stable. Mod tests: claude plugin test, 68 pass. Function hooks API: early access, may change. Bash classifier in overdrive-tools: stub, every shell row is RUN" src="assets/readme/status-v2-dark.svg" width="100%">

<img alt="Terminal: $ claude plugin validate mods/overdrive-tools | $ claude plugin test mods/overdrive-tools | $ claude plugin test mods/overdrive-tabs | $ python3 tools/overdrive/test_trace.py" src="assets/readme/checks-v2-dark.svg" width="100%">

claude plugin validate mods/overdrive-tools
claude plugin test mods/overdrive-tools
claude plugin test mods/overdrive-tabs
python3 tools/overdrive/test_trace.py

<a href="tools/overdrive/FLAIR.md"><picture><source media="(prefers-color-scheme: light)" srcset="assets/readme/links-1-v2-light.svg"><img alt="Art language (tools/overdrive/FLAIR.md)" src="assets/readme/links-1-v2-dark.svg" width="32%"></picture></a> <a href="tools/overdrive/SESSION-LOG.md"><picture><source media="(prefers-color-scheme: light)" srcset="assets/readme/links-2-v2-light.svg"><img alt="Session log (tools/overdrive/SESSION-LOG.md)" src="assets/readme/links-2-v2-dark.svg" width="32%"></picture></a> <a href="mods/overdrive-tools/SPEC.md"><picture><source media="(prefers-color-scheme: light)" srcset="assets/readme/links-3-v2-light.svg"><img alt="Mod specs (mods/overdrive-tools/SPEC.md)" src="assets/readme/links-3-v2-dark.svg" width="32%"></picture></a>

<img alt="VM. studio / ops · public tool · MIT · look per vm-brand playbook · -- EOF -- frames in chat only --" src="assets/readme/footer-v2-dark.svg" width="100%">

Source 4 files
hooks/register.tsx 90 lines
1import { atom, memberOf, read, update } from 'claude-code'
2import type { Register } from 'claude-code'
3
4import type { Mode, RowState } from '../types'
5import { classify, summarize } from './classify'
6import type { Kind } from './classify'
7import { isMode, isOpenRow, toggledRow } from './rows'
8
9const mode = atom({ plugin: 'overdrive-tools', key: 'mode' } as const, 'folded' as Mode)
10const row = atom({ plugin: 'overdrive-tools', key: 'row' } as const, null as RowState)
11
12// Neon Overdrive palette (FLAIR.md): magenta = change, cyan = info, yellow = run.
13const COLOR: Record<Kind, string> = { info: '#00FFF7', change: '#EE4FFF', run: '#F0FF19' }
14const TAG: Record<Kind, string> = { info: 'INF', change: 'CHG', run: 'RUN' }
15
16export const register: Register = on => {
17  on('session.start', async ($, e, next) => {
18    await $.command.register({
19      name: 'tools',
20      description: 'Tool rows: /tools folded (default) closes every row, /tools changes closes info rows, /tools all opens all; click a tag to toggle one row',
21    })
22
23    return next(e)
24  })
25
26  on('command.run', { command: 'tools' }, async ($, e) => {
27    const arg = e.args.trim()
28    const current = await read($, mode)
29    const target: Mode = isMode(arg) ? arg : current === 'folded' ? 'all' : 'folded'
30    await update($, mode, () => target)
31
32    const layout = e.presentation.isFullscreen ? 'fullscreen layout, clicks reach the tags' : 'main-screen layout, clicks are not reported here'
33    const what = target === 'all' ? 'every tool row is open.' : target === 'changes' ? 'info rows are folded, changes and runs are open.' : 'every finished row is folded; click a tag to open one.'
34
35    return { text: `${what} (${layout})` }
36  })
37
38  // Every tool row: a clickable tag on the left of the engine's own drawing.
39  // Clicking the tag folds or unfolds that one row; /tools sets the default.
40  on('ui.render', { component: 'ToolUse' }, async ($, e, next) => {
41    const kind = classify(e.props.tool, e.props.input)
42    const color = e.props.isErrored ? 'error' : COLOR[kind]
43    const member = memberOf(row, e)
44    const override = await read($, member)
45    const isOpen = e.props.isRunning || isOpenRow(override, kind, await read($, mode))
46    const engine = await next(e)
47    const { Box, Button, Text } = $.ui.resolve(e)
48
49    const toggle = async () => {
50      try {
51        const current = await read($, mode)
52        await update($, member, state => toggledRow(state, kind, current))
53      } catch (error) {
54        $.ui.toast(`overdrive-tools: toggle failed: ${error instanceof Error ? error.message : String(error)}`)
55      }
56    }
57
58    const tag = (
59      <Button key={`odt-${e.props.tool_use_id}`} plain onPress={toggle}>
60        <Text color={color}>▌</Text>
61        <Text color={color} bold>{TAG[kind]}</Text>
62        <Text color={color}>{isOpen ? ' ▾' : ' ▸'}</Text>
63        {isOpen ? null : <Text dimColor> {summarize(e.props.tool, e.props.input)}</Text>}
64      </Button>
65    )
66
67    return isOpen ? (
68      <Box flexDirection="column">
69        {tag}
70        <Box flexDirection="row">
71          <Text color={color}>▌ </Text>
72          <Box flexDirection="column" flexGrow={1}>{engine}</Box>
73        </Box>
74      </Box>
75    ) : (
76      <Box flexDirection="column">{tag}</Box>
77    )
78  })
79
80  // A folded run of reads that hides a change inside is unfolded, so every
81  // change row gets its own colored line.
82  on('ui.render', { component: 'ToolGroup' }, async ($, e, next) => {
83    const hasChange = e.props.calls.some(call => classify(call.tool, call.input) !== 'info')
84
85    return hasChange && !e.props.isExpanded
86      ? next({ ...e, props: { ...e.props, isExpanded: true } })
87      : next(e)
88  })
89}
90
hooks/classify.ts 66 lines
1// What a tool call did, as the transcript colors it.
2//   info   → read-only: the row is context, nothing on disk or remote moved
3//   change → something on disk, in git, in Linear or in an MCP store changed
4//   run    → a process ran whose effect the name alone does not tell
5export type Kind = 'info' | 'change' | 'run'
6
7const INFO_TOOLS = new Set([
8  'Read', 'Grep', 'Glob', 'LS', 'WebFetch', 'WebSearch', 'ToolSearch',
9  'Skill', 'ListAgents', 'Monitor', 'LSP', 'ReadMcpResourceTool', 'ListMcpResourcesTool',
10])
11const CHANGE_TOOLS = new Set(['Edit', 'Write', 'MultiEdit', 'NotebookEdit'])
12const RUN_TOOLS = new Set(['Agent', 'Workflow'])
13
14// MCP tool names that only read: get_*, list_*, search_*, rag_*, who_imports …
15const MCP_READ = /^(get|list|search|find|read|query|describe|health|status|who_|exports_|rag_|fetch|analyze|ping|export)/
16
17export function classify(tool: string, input: unknown): Kind {
18  if (CHANGE_TOOLS.has(tool)) return 'change'
19  if (INFO_TOOLS.has(tool)) return 'info'
20  if (RUN_TOOLS.has(tool)) return 'run'
21  if (tool === 'Bash') return classifyBash(commandOf(input))
22  if (tool.startsWith('mcp__')) return classifyMcp(tool)
23  return 'run'
24}
25
26export function classifyMcp(tool: string): Kind {
27  const name = tool.split('__').pop() ?? ''
28  return MCP_READ.test(name) ? 'info' : 'change'
29}
30
31export function commandOf(input: unknown): string {
32  if (typeof input === 'object' && input !== null && 'command' in input) {
33    const c = (input as { command?: unknown }).command
34    return typeof c === 'string' ? c : ''
35  }
36  return ''
37}
38
39// TODO(velimir): your call. A Bash row is 'info' when the command only reads
40// (git status/log/diff, ls, cat, grep, rig quota, tix lint, dual-review --status,
41// relpilot scan …) and 'change' when it writes (git commit/push, tix new/comment,
42// overdrive snapshot/bump, rm/mv, npm install, sed -i …). Everything else is 'run'.
43// Keep it to a few regexes; `command` is the raw text the model sent, possibly
44// several commands joined with && or ;. Until filled, every Bash row is 'run'.
45export function classifyBash(command: string): Kind {
46  void command
47  return 'run'
48}
49
50// One short line for a folded tag: the file, the pattern or the command head.
51export function summarize(tool: string, input: unknown, max = 60): string {
52  const i = (typeof input === 'object' && input !== null ? input : {}) as Record<string, unknown>
53  const pick = (...keys: string[]) => {
54    for (const k of keys) {
55      const v = i[k]
56      if (typeof v === 'string' && v.trim() !== '') return v.trim()
57    }
58    return ''
59  }
60  const detail = tool === 'Bash'
61    ? pick('description', 'command')
62    : pick('file_path', 'pattern', 'query', 'url', 'skill', 'prompt', 'description')
63  const text = `${tool}${detail ? ` ${detail}` : ''}`.replace(/\s+/g, ' ')
64  return text.length > max ? `${text.slice(0, max - 1)}…` : text
65}
66
hooks/rows.ts 18 lines
1import type { Mode, RowState } from '../types'
2import type { Kind } from './classify'
3
4// What a row shows when nobody clicked it.
5export const defaultOpen = (kind: Kind, mode: Mode): boolean =>
6  mode === 'all' ? true : mode === 'changes' ? kind !== 'info' : false
7
8export const MODES: readonly Mode[] = ['folded', 'changes', 'all']
9export const isMode = (value: string): value is Mode => (MODES as readonly string[]).includes(value)
10
11// Whether a row is open now: its own override wins, else the mode's default.
12export const isOpenRow = (override: RowState, kind: Kind, mode: Mode): boolean =>
13  override === null ? defaultOpen(kind, mode) : override === 'open'
14
15// The override a click leaves behind: the opposite of what the row shows now.
16export const toggledRow = (override: RowState, kind: Kind, mode: Mode): RowState =>
17  isOpenRow(override, kind, mode) ? 'closed' : 'open'
18
types/index.d.ts 11 lines
1// folded: every finished row starts closed (default) · changes: info rows closed · all: every row open
2export type Mode = 'folded' | 'changes' | 'all'
3// Per-row override of the mode's default: absent (null) follows the mode.
4export type RowState = 'open' | 'closed' | null
5
6declare module 'claude-code' {
7  interface PluginState {
8    'overdrive-tools': { mode: Mode; row: StateFamily<RowState> }
9  }
10}
11