SLOPSHOPPER

logstrip-gain

LogStrip global token savings in a status line and statistics report card.

newrowsguardcommandstatusprocess
★ 1v0.1.0MITupdated 2026-10-09Fazzani/claude-mods/plugins/logstrip-gain
A shopper browsing a rack in a slop shop
README

logstrip-gain

Read-only LogStrip statistics display for Claude Code. Based on rtk-gain and the official LogStrip CLI telemetry.

Install

npm install -g logstrip
claude plugin marketplace add Fazzani/claude-mods
claude plugin install logstrip-gain@claude-mods

Statistics

SurfaceBehaviour
Status lineEstimated LogStrip tokens saved, cumulative savings percentage, total CLI runs
/logstrip-gainA card with gauge, totals, last execution and up to five recent runs

The plugin reads logstrip --telemetry. LogStrip stores cumulative statistics in ~/.logstrip/telemetry.json (or LOGSTRIP_TELEMETRY_DIR). These figures include LogStrip invocations from Codex or other local programs. Token savings are LogStrip estimates, not measured model billing savings.

The status refreshes on session start and after Bash or PowerShell tool calls. If LogStrip is missing, the status remains blank, and /logstrip-gain displays installation guidance.

This mod only displays statistics. It does not compress logs, alter tool output, inject context, install packages, or send telemetry elsewhere.

Language: auto (default), en, or fr through /config.

Source 5 files
hooks/register.tsx 101 lines
1import type { EngineInterface, Register } from 'claude-code'
2
3import { type Locale, LOCALES, pickLocale } from './i18n'
4import { bar, compact, parseTelemetry } from './report'
5
6const COMMAND = 'logstrip-gain'
7
8/** Plugin setting, Claude Code language, then process locale. */
9async function resolveLocale($: EngineInterface, option: unknown): Promise<Locale> {
10  const settings = await $.settings.read()
11  return pickLocale(
12    option,
13    settings.language,
14    await $.env.get('LC_ALL'),
15    await $.env.get('LC_MESSAGES'),
16    await $.env.get('LANG'),
17  )
18}
19
20/** Read-only: telemetry belongs to the global LogStrip CLI. */
21async function refresh($: EngineInterface, locale: Locale): Promise<void> {
22  try {
23    const run = await $.process.run(['logstrip', '--telemetry'], { timeoutMs: 3000 })
24    const report = run.exitCode === 0 && !run.isStderrTruncated
25      ? parseTelemetry(run.stderr)
26      : undefined
27    $.ui.status(report
28      ? LOCALES[locale].status(compact(report.saved), report.percent.toFixed(1), report.runs)
29      : undefined)
30  } catch {
31    $.ui.status(undefined)
32  }
33}
34
35export const register: Register = (on, options) => {
36  let locale: Locale = 'en'
37  const t = () => LOCALES[locale]
38
39  on('session.start', async ($, e, next) => {
40    locale = await resolveLocale($, options.language)
41    await $.command.register({ name: COMMAND, description: t().commandDescription })
42    void refresh($, locale)
43    return next(e)
44  })
45
46  // Refresh only the display after shell commands. Preserve tool arguments and results.
47  on('tool.call', async ($, e, next) => {
48    try {
49      return await next(e)
50    } finally {
51      if (e.tool === 'Bash' || e.tool === 'PowerShell') void refresh($, locale)
52    }
53  })
54
55  on('command.run', { command: COMMAND }, async $ => {
56    try {
57      const run = await $.process.run(['logstrip', '--telemetry'], { timeoutMs: 3000 })
58      if (run.exitCode !== 0 || run.isStderrTruncated) return { text: t().failed(run.stderr) }
59      return { text: run.stderr }
60    } catch {
61      return { text: t().notInstalled }
62    }
63  })
64
65  on('ui.render', { component: 'CommandOutput', props: { command: COMMAND } }, ($, e, next) => {
66    const report = e.props.isErrored ? undefined : parseTelemetry(e.props.text)
67    if (!report) return next(e)
68
69    const m = t()
70    const { Box, Text } = $.ui.resolve(e)
71    const [fill, rest] = bar(report.percent, 24)
72
73    return (
74      <Box flexDirection="column" borderStyle="round" borderColor="cyan" paddingX={1}>
75        <Box gap={1}>
76          <Text color="cyan" bold>LogStrip</Text>
77          <Text dimColor>{m.heading}</Text>
78        </Box>
79        <Box gap={2} marginTop={1}>
80          <Text color="green" bold>{m.saved(compact(report.saved))}</Text>
81          <Box>
82            <Text color="green">{fill}</Text>
83            <Text dimColor>{rest}</Text>
84          </Box>
85          <Text color="cyan" bold>{report.percent.toFixed(1)}%</Text>
86        </Box>
87        <Text dimColor>{m.details(report.runs, compact(report.input), compact(report.output))}</Text>
88        <Text dimColor>{m.lastRun(report.lastRun)}</Text>
89        {report.recent.length > 0 && (
90          <Box flexDirection="column" marginTop={1}>
91            <Text dimColor bold>{m.recentHeading}</Text>
92            {report.recent.map(run => (
93              <Text dimColor>{m.recent(run.timestamp.replace('T', ' '), compact(run.saved), run.percent.toFixed(1))}</Text>
94            ))}
95          </Box>
96        )}
97      </Box>
98    )
99  })
100}
101
hooks/i18n.ts 40 lines
1import { en } from '../locales/en'
2import { fr } from '../locales/fr'
3
4export type { Messages } from '../locales/en'
5
6export const LOCALES = { en, fr }
7
8export type Locale = keyof typeof LOCALES
9
10function match(value: unknown): Locale | null {
11  if (typeof value !== 'string') {
12    return null
13  }
14  const tag = value.trim().toLowerCase()
15  if (tag.startsWith('fr')) {
16    return 'fr'
17  }
18  if (tag.startsWith('en')) {
19    return 'en'
20  }
21
22  return null
23}
24
25/**
26 * The first candidate naming a supported locale, English otherwise. Callers pass,
27 * in order: the plugin's `language` option, Claude Code's `language` setting,
28 * then LC_ALL, LC_MESSAGES and LANG.
29 */
30export function pickLocale(...candidates: unknown[]): Locale {
31  for (const candidate of candidates) {
32    const locale = match(candidate)
33    if (locale) {
34      return locale
35    }
36  }
37
38  return 'en'
39}
40
hooks/report.ts 62 lines
1export type RecentRun = { timestamp: string; saved: number; percent: number }
2export type Telemetry = {
3  runs: number
4  input: number
5  output: number
6  saved: number
7  percent: number
8  lastRun: string
9  recent: RecentRun[]
10}
11
12/** Parse the --telemetry report that LogStrip writes to stderr. */
13export function parseTelemetry(text: string): Telemetry | undefined {
14  const parseInteger = (value: string): number | undefined => {
15    const digits = value.replace(/[^0-9]/g, '')
16    if (!digits) return undefined
17    const n = Number(digits)
18    return Number.isSafeInteger(n) ? n : undefined
19  }
20
21  function field(name: string): number | undefined {
22    const match = text.match(new RegExp('^[ \\t]*' + name + '[ \\t]*:[ \\t]*([0-9][0-9,. \\t\\u00a0\\u202f]*)[ \\t]*$', 'mi'))
23    return match?.[1] ? parseInteger(match[1]) : undefined
24  }
25
26  const runs = field('total runs')
27  const input = field('input tokens')
28  const output = field('output tokens')
29  const saved = field('saved tokens')
30  if (runs === undefined || input === undefined || output === undefined || saved === undefined) {
31    return undefined
32  }
33
34  const lastRun = text.match(/^[ \t]*last run[ \t]*:[ \t]*(\S.*?)[ \t]*$/mi)?.[1] ?? ''
35  const recent = [...text.matchAll(
36    /^[ \t]*(\d{4}-\d\d-\d\dT\d\d:\d\d:\d\d)[ \t]+saved=([0-9][0-9,. \u00a0\u202f]*)[ \t]+\((\d+(?:\.\d+)?)%\)[ \t]*$/gm,
37  )].slice(0, 5).flatMap(([, timestamp, amount, rate]) => {
38    const saved = parseInteger(amount ?? '')
39    const percent = Number(rate)
40    return saved === undefined || !Number.isFinite(percent) || timestamp === undefined
41      ? []
42      : [{ timestamp, saved, percent }]
43  })
44
45  return {
46    runs, input, output, saved,
47    percent: input > 0 ? Math.min(100, (saved / input) * 100) : 0,
48    lastRun, recent,
49  }
50}
51
52export function compact(value: number): string {
53  if (value >= 1_000_000) return (value / 1_000_000).toFixed(1) + 'M'
54  if (value >= 1_000) return (value / 1_000).toFixed(1) + 'K'
55  return String(value)
56}
57
58export function bar(percent: number, width: number): readonly [string, string] {
59  const filled = Math.max(0, Math.min(width, Math.round(percent / 100 * width)))
60  return ['█'.repeat(filled), '░'.repeat(width - filled)]
61}
62
locales/en.ts 17 lines
1export const en = {
2  commandDescription: 'Show LogStrip token savings statistics',
3  status: (saved: string, pct: string, runs: number) =>
4    'LogStrip: ≈' + saved + ' tokens saved (' + pct + '%, ' + runs + (runs === 1 ? ' run)' : ' runs)'),
5  heading: 'estimated token savings',
6  saved: (amount: string) => '≈' + amount + ' saved',
7  details: (runs: number, input: string, output: string) =>
8    runs + ' runs · ' + input + ' in → ' + output + ' out',
9  lastRun: (value: string) => 'Last run: ' + value,
10  recentHeading: 'Recent runs',
11  recent: (at: string, amount: string, rate: string) => at + ' · ≈' + amount + ' · ' + rate + '%',
12  failed: (reason: string) => 'LogStrip telemetry failed: ' + (reason || 'unknown error'),
13  notInstalled: 'LogStrip is unavailable. Install it with npm install -g logstrip.',
14}
15
16export type Messages = typeof en
17
locales/fr.ts 17 lines
1import type { Messages } from './en'
2
3export const fr: Messages = {
4  commandDescription: 'Afficher les statistiques des tokens économisés par LogStrip',
5  status: (saved: string, pct: string, runs: number) =>
6    'LogStrip : ≈' + saved + ' tokens économisés (' + pct + ' %, ' + runs + ' exéc.)',
7  heading: 'économies estimées de tokens',
8  saved: (amount: string) => '≈' + amount + ' économisés',
9  details: (runs: number, input: string, output: string) =>
10    runs + ' exécutions · ' + input + ' entrée → ' + output + ' sortie',
11  lastRun: (value: string) => 'Dernière exécution : ' + value,
12  recentHeading: 'Dernières exécutions',
13  recent: (at: string, amount: string, rate: string) => at + ' · ≈' + amount + ' · ' + rate + ' %',
14  failed: (reason: string) => 'Échec de la télémétrie LogStrip : ' + (reason || 'erreur inconnue'),
15  notInstalled: 'LogStrip est indisponible. Installer avec npm install -g logstrip.',
16}
17