SLOPSHOPPER

dieta-wyjsc

Przycina ogromne wyniki Basha, PowerShella i MCP do początku, końca i linijek z błędami; pełny wynik zapisuje do pliku

newguardcommandstatus
A shopper browsing a rack in a slop shop
Preview · a replayed session in a sandbox
claude · ~/work/app · dieta-wyjsc
› 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 › /dieta ⎿ dieta-wyjsc: Dieta wyłączona: wyniki narzędzi idą w całości. ────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── › ? for shortcuts ⚠ dieta-wyjsc: dieta: wyłączona
README

Radek Wajbkoduje — darmowe rzeczy z kanału

Tu leży wszystko, co rozdaję na kanale. Za darmo, bez zapisywania się na cokolwiek.

Jak pobrać: kliknij zielony przycisk Code u góry, potem Download ZIP. Rozpakuj na pulpit. Nie musisz wiedzieć, co to GitHub — wystarczy ten jeden przycisk.


⚠️ Zanim cokolwiek zainstalujesz — sprawdź to

Skill to instrukcja, którą AI wykona. W czymkolwiek ściągniętym z internetu może być napisane wszystko. Sprawdzaj każdą paczkę, zanim ją wrzucisz. Moją też.

Paczka leży jeszcze w zwykłym folderze na pulpicie, więc otwórz nową rozmowę z AI w tym folderze i wklej:

Zanim tego użyję: przeczytaj te pliki jak zwykły tekst i powiedz mi po ludzku,
co ta instrukcja każe robić. Czy każe coś kasować, wysyłać gdzieś dane,
uruchamiać komendy albo grzebać poza swoim folderem?

Zasada kciuka: jeśli w instrukcji jest „wyślij", „skasuj" albo „uruchom" coś, czego nie rozumiesz — nie instalujesz. Nie musisz rozumieć wszystkiego. Wystarczy, że wiesz, kiedy powiedzieć „nie".

To nie jest kontrola pancerna, ale wyłapuje zdecydowaną większość śmieci. Różnica jak między wzięciem cukierka od obcego a przeczytaniem składu.


Co tu jest

FolderCo to robiOdcinek
ghost/AI pisze maile i wiadomości twoim stylem, a nie jak robot„Nauczyłem AI pisać jak ja"
handoff/Kończysz rozmowę tak, żeby nowa wiedziała, na czym stanęliście — mniej zużytego limitu„Znowu masz limit"
krotko/Jedno słowo i z długiej odpowiedzi AI zostaje kilka zdań po ludzku„AI gada za dużo"
mody-limit/Trzech pomocników w Claude Code: pasek zużycia w kolorach, przycinanie gadatliwych wyników, stop dla AI kręcącego się w kółko(odcinek w przygotowaniu)

Każdy folder ma własny README.md z instrukcją krok po kroku.

Prywatność — dwie rzeczy, które obowiązują zawsze

  • To, co wklejasz do AI, leci na serwer firmy, która je robi. Nie wklejaj haseł, numerów kart ani cudzych danych.
  • Twoje pliki z tych paczek (np. profil stylu pisania) zostają u ciebie na dysku. Nigdzie ich nie wysyłasz i nikt ich nie widzi.

Coś nie działa?

Napisz w komentarzu pod odcinkiem — czytam wszystkie i z powtarzających się pytań robię następne filmy.

Source 2 files
hooks/register.ts 151 lines
1import { atom, update } from 'claude-code'
2import type { EngineInterface, Register, ToolCallResult } from 'claude-code'
3
4// Poniżej tego rozmiaru wynik idzie do Claude'a w całości.
5const MIN_CHARS = 8000
6// Co zostaje z dużego wyniku: początek, koniec i linijki z błędami ze środka.
7const HEAD_LINES = 40
8const HEAD_CHARS = 3000
9const TAIL_LINES = 60
10const TAIL_CHARS = 4000
11const MATCH_LINES = 30
12const MATCH_LINE_CHARS = 300
13const IMPORTANT = /error|warn|fail|fatal|exception|traceback|błąd|blad/i
14// Pełne wyniki krążą po 50 plikach, żeby folder nie puchł.
15const SLOTS = 50
16
17// Komendy, którymi Claude celowo czyta treść (plik, diff) — tych nie tniemy.
18const READS_CONTENT = /^\s*(cat|head|tail|sed|less|more|type|gc|get-content|jq|git\s+(diff|show))\b/i
19
20const savedChars = atom({ plugin: 'dieta-wyjsc', key: 'savedChars' } as const, 0)
21
22export const register: Register = on => {
23  on('session.start', async ($, e, next) => {
24    await $.command.register({
25      name: 'dieta',
26      description: 'Włącz albo wyłącz przycinanie dużych wyników narzędzi',
27    })
28
29    return next(e)
30  })
31
32  on('command.run', { command: 'dieta' }, async $ => {
33    const isOff = !((await $.store.get('isOff')) === true)
34    await $.store.set('isOff', isOff)
35    $.ui.status(isOff ? 'dieta: wyłączona' : undefined)
36
37    return { text: isOff ? 'Dieta wyłączona: wyniki narzędzi idą w całości.' : 'Dieta włączona.' }
38  })
39
40  on('tool.call', async ($, e, next) => {
41    const isShell = e.tool === 'Bash' || e.tool === 'PowerShell'
42    if (!isShell && !e.tool.startsWith('mcp__')) {
43      return next(e)
44    }
45    if (isShell && READS_CONTENT.test(String(e.command ?? ''))) {
46      return next(e)
47    }
48
49    const ran = await next(e)
50    if (ran.deny !== undefined || ran.isError === true || (await $.store.get('isOff')) === true) {
51      return ran
52    }
53
54    return isShell ? dietShell($, e.tool, ran) : dietMcp($, e.tool, ran)
55  })
56}
57
58type Shell = { stdout: string; stderr: string; isImage?: boolean; backgroundTaskId?: string; persistedOutputPath?: string }
59
60async function dietShell($: EngineInterface, tool: string, ran: ToolCallResult): Promise<ToolCallResult> {
61  const result = ran.result as Shell
62  // Obraz, zadanie w tle albo wynik, który Claude Code sam już odłożył do pliku.
63  if (typeof result?.stdout !== 'string' || result.isImage || result.backgroundTaskId || result.persistedOutputPath) {
64    return ran
65  }
66
67  const full = result.stderr ? `${result.stdout}\n--- stderr ---\n${result.stderr}` : result.stdout
68  const cut = await cutAndSave($, tool, full)
69
70  return cut === null ? ran : { result: { ...result, stdout: cut, stderr: '' } }
71}
72
73async function dietMcp($: EngineInterface, tool: string, ran: ToolCallResult): Promise<ToolCallResult> {
74  const cut = await cutAndSave($, tool, ran.text ?? '')
75
76  return cut === null ? ran : { result: cut }
77}
78
79// Przycięty tekst albo null, gdy nie ma czego ciąć lub nie udało się zapisać całości.
80async function cutAndSave($: EngineInterface, tool: string, full: string): Promise<string | null> {
81  if (full.length < MIN_CHARS) {
82    return null
83  }
84
85  const path = await saveFull($, tool, full)
86  if (path === null) {
87    return null
88  }
89
90  const cut = trim(full, path)
91  const saved = full.length - cut.length
92  if (saved <= 0) {
93    return null
94  }
95
96  const total = await update($, savedChars, n => n + saved)
97  $.ui.status(`dieta: −${Math.round(total / 4000)}k tokenów`)
98
99  return cut
100}
101
102async function saveFull($: EngineInterface, tool: string, full: string): Promise<string | null> {
103  try {
104    const home = (await $.env.get('USERPROFILE')) ?? (await $.env.get('HOME'))
105    if (home === undefined) {
106      return null
107    }
108    const slot = Number((await $.store.get('slot')) ?? 0)
109    await $.store.set('slot', (slot + 1) % SLOTS)
110    const path = `${home.replace(/\\/g, '/')}/.claude/dieta/wynik-${String(slot).padStart(2, '0')}.log`
111    const stamp = new Date(await $.clock.now()).toISOString()
112    await $.fs.write(path, `# ${tool} · ${stamp}\n${full}`)
113    return path
114  } catch {
115    return null
116  }
117}
118
119export function trim(full: string, path: string): string {
120  const lines = full.split('\n')
121
122  // Dużo linii (log): tniemy po liniach. Mało, a długich (JSON w jednej linii): po znakach.
123  let head: string
124  let tail: string
125  let middle: string[]
126  if (lines.length > HEAD_LINES + TAIL_LINES) {
127    head = lines.slice(0, HEAD_LINES).join('\n').slice(0, HEAD_CHARS)
128    tail = lines.slice(-TAIL_LINES).join('\n').slice(-TAIL_CHARS)
129    middle = lines.slice(HEAD_LINES, -TAIL_LINES)
130  } else {
131    head = full.slice(0, HEAD_CHARS)
132    tail = full.slice(-TAIL_CHARS)
133    middle = full.slice(HEAD_CHARS, -TAIL_CHARS).split('\n')
134  }
135
136  const important = middle.filter(line => IMPORTANT.test(line))
137  const shown = important.slice(0, MATCH_LINES).map(line => line.slice(0, MATCH_LINE_CHARS))
138  const cutK = Math.round((full.length - head.length - tail.length) / 1000)
139
140  return [
141    head,
142    shown.length > 0
143      ? `… [dieta: wycięłam ~${cutK}k znaków ze środka; ${shown.length} z ${important.length} linijek z error/warn/fail poniżej]`
144      : `… [dieta: wycięłam ~${cutK}k znaków ze środka; nie było w nich error/warn/fail]`,
145    ...shown,
146    ...(shown.length > 0 ? ['… [dieta: dalej koniec wyniku]'] : []),
147    tail,
148    `[dieta: pełny wynik (${full.length} znaków) jest w ${path} — przeczytaj go Read albo grep, jeśli czegoś tu brakuje]`,
149  ].join('\n')
150}
151
types/index.d.ts 12 lines
1/** Liczba znaków, które dieta wycięła z wyników narzędzi. */
2export type SavedChars = number
3
4declare module 'claude-code' {
5  interface PluginState {
6    'dieta-wyjsc': {
7      /** Ile znaków dieta wycięła w tej sesji (do licznika na pasku statusu). */
8      savedChars: SavedChars
9    }
10  }
11}
12