L2 v0.2.0 source

shot-inline

Modelin kaydettiği ya da okuduğu her PNG ve JPG dosyasını tool satırının altında çizen bir Claude Code Mod'u, böylece modelin baktığı screenshot'u dosyayı açmadan görürsünüz.

Ne yapar

  1. Mod üç tür tool çağrısını izler ve her birinden görsel path'ini alır:
    • bir Playwright browser_take_screenshot: sonucunun link verdiği dosya;
    • bir .png, .jpg ya da .jpeg dosyasının Read çağrısı;
    • böyle bir dosyayı adlandıran bir Bash komutu, dosya komuttan sonra varsa (en son adlandırılan önce).
  2. Bir PNG header'ından ölçülür. 4 MiB üstündeki bir PNG ve bir JPG sips ile ölçülür.
  3. Bir JPG bir kere $TMPDIR/shot-inline/<hash>.png yoluna sips -s format png ile kopyalanır, çünkü terminal yalnız PNG çizer. Hash, path'i ve değişiklik zamanını kapsar.
  4. Tool satırı resmi altında çizer: en fazla 80 kolon genişlik ve 24 satır yükseklik, resmin kendi oranında. Dosyayı terminal kendisi okur; hiçbir pixel engine'den geçmez.
  5. kitty graphics protokolü olan bir terminal (kitty, Ghostty; TERM, TERM_PROGRAM ve KITTY_WINDOW_ID üzerinden okunur) pixel'lerin kendisini çizer. Diğer her terminal aynı kutuyu half-block hücreler olarak çizer: sips tam olarak kutunun pixel'lerinden bir BMP yazar, mod satırlarını okur ve her hücre iki pixel taşır, üsttekini foreground, alttakini background olarak. BMP ve hücreler resim ve kutu başına bir kere üretilir.

iTerm2'nin kendi inline image protokolü vardır ve engine onu kullanmaz, bu yüzden iTerm2 de half-block yolunu alır. Protokol engine'in Image element'i içinde seçilir, bu yüzden hiçbir mod onu değiştiremez. Bir resmi yalnız terminal surface'i çizer.

Canlı kontrolde bir PNG'nin ve bir JPG'nin Read çağrısı satırlarının altında çizdi, JPG bir sips kopyası üzerinden, ve debug log'da reddedilen bir tree yoktu. kitty protokolü olmayan tmux'ta aynı Read 24 satır half-block hücreyi 23 foreground ve 18 background renginde çizdi.

Komut

/shot-inline            on ya da off, ve bu session'ın resimleri
/shot-inline on | off   varsayılan on

Kurulum

claude plugin marketplace add KilimcininKorOglu/claude-code-mods
claude plugin install shot-inline@kilimcininkoroglu-mods

Function hook'lar early access. Flag olmadan hiçbir şey yüklenmez. Flag'i kalıcı yapmak için ~/.claude/settings.json dosyasına ekleyin:

{ "env": { "CLAUDE_CODE_ENABLE_FUNCTION_HOOKS": "1" } }

Kurulumdan sonra

  1. Resmin kendisi için kitty ya da Ghostty kullanın. iTerm2, VS Code terminali, Terminal.app, Windows Terminal ve conhost half-block hücreleri alır, çünkü engine yalnız kitty protokolünü gönderir.
  2. sips macOS'un parçasıdır ve hem JPG kopyası hem half-block hücreler onu ister. Başka bir yerde 4 MiB'a kadar bir PNG kitty ve Ghostty'de yine çizilir, diğer her yol bir kere a picture was not drawn: ... log'lar.
  3. Claude Code'u yeniden başlatın.

Nereye uzanır

Claude Code 2.1.278 üzerinde claude plugin validate ile doğrulandı:

❯ ./register.tsx hooks: session.start, command.run{command=shot-inline}, tool.call{tool=Read}, tool.call{tool=Bash}, tool.call{tool=/^mcp__(plugin_playwright_)?playwright__browser_take_screenshot$/}, ui.render{component=ToolUse}
❯ ./register.tsx calls: $.command.register, $.env.get, $.fs.exists (via bmpCopy, pngCopy, prepare), $.fs.read (via gridFor, measure), $.fs.stat (via prepare), $.process.run (via sips, tempDir), $.session.cwd (via remember), $.store.get, $.store.set (via runCommand), $.ui.invalidate (via remember, runCommand), $.ui.log (via report), $.ui.resolve
❯ ./register.tsx env writes: nothing
❯ ./register.tsx env reads: KITTY_WINDOW_ID, TERM, TERM_PROGRAM, TMPDIR

Reach L2, process çalıştırır ve dosya yazar.

1. Okur:     Read ve Bash çağrılarının input'unu, Playwright screenshot sonucunu, adlandırılan her görsel dosyanın header'ını, kendi yazdığı BMP'nin pixel'lerini, ve TERM, TERM_PROGRAM, KITTY_WINDOW_ID ve TMPDIR
2. Çalıştırır: sips (boyut, JPG'den PNG'ye, hücrelerin BMP'si) ve mkdir -p, argv ile
3. Gönderir: modele hiçbir şey; makineden hiçbir şey çıkmaz
4. Saklar:   JPG'lerin PNG kopyalarını ve çizilen kutuların BMP'lerini $TMPDIR/shot-inline altında, on/off ayarını $.store içinde
5. Düşman girdi: bir path modelin komut metninden gelir; sips'e tek bir argv öğesi olarak ulaşır, hiçbir zaman bir shell üzerinden geçmez, ve yalnız dosyanın var olduğu görüldükten sonra

Sınırlar

  • Resimler bellekte yaşar: resume edilmiş bir session eski satırlarını onlarsız çizer.
  • Çalışma anında kurduğu bir adla (bir değişken, bir glob) görsel yazan bir Bash komutu görülmez.
  • ~ ile başlayan bir path genişletilmez.
  • $TMPDIR/shot-inline altındaki kopyalar mod tarafından silinmez; temp dizinini sistem temizler.
  • Bir half-block hücre iki pixel taşır, yani 80'e 24 bir kutu 160'a 48 pixel'dir. Resim tanınır, keskin değil.
  • Terminalin renk derinliğini engine seçer: tmux'ta 256 renk kodları yazdı, 24 bit olanları değil.
  • Half-block yolu sips ister, yani yalnız macOS'tadır. kitty yolu bir PNG için hiçbir process istemez.

Geliştirme

make install     # eslint, typescript-eslint, typescript
make lint        # complexity limiti 10, üstünde build'i düşürür
make typecheck   # /plugin-types ile üretilen .claude/types/ gerekir
make validate
make test        # claude plugin test

shot-inline

A Claude Code Mod that draws each PNG or JPG the model saves or reads under its tool row, so you see the screenshot the model looked at without opening the file.

What it does

  1. The mod watches three kinds of tool call and takes the image path from each:
    • a Playwright browser_take_screenshot: the file its result links to;
    • a Read of a .png, .jpg or .jpeg file;
    • a Bash command that names such a file, when the file exists after the command (the last one named first).
  2. A PNG is measured from its header. A PNG over 4 MiB, and a JPG, are measured with sips.
  3. A JPG is copied once to $TMPDIR/shot-inline/<hash>.png with sips -s format png, because the terminal draws PNG only. The hash covers the path and the modification time.
  4. The tool row draws the picture under itself: at most 80 columns wide and 24 rows tall, in the picture's shape. The terminal reads the file itself; no pixel crosses the engine.
  5. A terminal with the kitty graphics protocol (kitty, Ghostty, read from TERM, TERM_PROGRAM and KITTY_WINDOW_ID) draws the pixels themselves. Every other terminal draws the same box as half-block cells: sips writes a BMP of exactly the box's pixels, the mod reads its rows, and each cell holds two pixels, the upper one as its foreground and the lower one as its background. The BMP and the cells are made once per picture and box.

iTerm2 has an inline image protocol of its own, and the engine does not use it, so iTerm2 takes the half-block path as well. The protocol is chosen inside the engine's Image element, so no mod can change it. Only the terminal surface draws a picture.

In the live check a Read of a PNG and of a JPG each drew under its row, the JPG through a sips copy, with no tree refused in the debug log. In tmux, which has no kitty protocol, the same Read drew 24 rows of half-block cells in 23 foreground and 18 background colours.

Command

/shot-inline            on or off, and the pictures of this session
/shot-inline on | off   on by default

Install

claude plugin marketplace add KilimcininKorOglu/claude-code-mods
claude plugin install shot-inline@kilimcininkoroglu-mods

Function hooks are early access. Nothing loads without the flag. To keep it on, add this to ~/.claude/settings.json:

{ "env": { "CLAUDE_CODE_ENABLE_FUNCTION_HOOKS": "1" } }

After installing

  1. Use kitty or Ghostty for the picture itself. iTerm2, the VS Code terminal, Terminal.app, Windows Terminal and conhost get the half-block cells, because the engine sends the kitty protocol only.
  2. sips is part of macOS, and both the JPG copy and the half-block cells need it. Elsewhere a PNG up to 4 MiB still draws in kitty and Ghostty, and every other path logs a picture was not drawn: ... once.
  3. Restart Claude Code.

What it can reach

Validated with claude plugin validate on Claude Code 2.1.278:

❯ ./register.tsx hooks: session.start, command.run{command=shot-inline}, tool.call{tool=Read}, tool.call{tool=Bash}, tool.call{tool=/^mcp__(plugin_playwright_)?playwright__browser_take_screenshot$/}, ui.render{component=ToolUse}
❯ ./register.tsx calls: $.command.register, $.env.get, $.fs.exists (via bmpCopy, pngCopy, prepare), $.fs.read (via gridFor, measure), $.fs.stat (via prepare), $.process.run (via sips, tempDir), $.session.cwd (via remember), $.store.get, $.store.set (via runCommand), $.ui.invalidate (via remember, runCommand), $.ui.log (via report), $.ui.resolve
❯ ./register.tsx env writes: nothing
❯ ./register.tsx env reads: KITTY_WINDOW_ID, TERM, TERM_PROGRAM, TMPDIR

Reach L2, runs processes and writes files.

1. Reads:    the input of Read and Bash calls, the Playwright screenshot result, the header of each image file named, the pixels of the BMP it wrote itself, and TERM, TERM_PROGRAM, KITTY_WINDOW_ID and TMPDIR
2. Runs:     sips (size, JPG to PNG, the BMP of the cells) and mkdir -p, by argv
3. Sends:    nothing to the model; nothing leaves the machine
4. Persists: PNG copies of JPGs and the BMPs of the drawn boxes under $TMPDIR/shot-inline, and the on/off setting in $.store
5. Hostile input: a path comes from the model's command text; it reaches sips as one argv item, never through a shell, and only after the file is found to exist

Limits

  • The pictures live in memory: a resumed session draws its old rows without them.
  • A Bash command that writes an image under a name it builds at run time (a variable, a glob) is not seen.
  • A path starting with ~ is not expanded.
  • The copies under $TMPDIR/shot-inline are not deleted by the mod; the system clears the temp directory.
  • A half-block cell holds two pixels, so an 80 by 24 box is 160 by 48 pixels. The picture is recognizable, not sharp.
  • The terminal's colour depth is the engine's to pick: in tmux it wrote 256-colour codes, not 24-bit ones.
  • The half-block path needs sips, so it is macOS only. The kitty path needs no process for a PNG.

Development

make install     # eslint, typescript-eslint, typescript
make lint        # complexity limit 10, fails the build above it
make typecheck   # needs .claude/types/ from /plugin-types
make validate
make test        # claude plugin test