app
Shotlog - Screenshot Log
Public Made by Adomby adom
See the shots your AI never showed you, and know it actually SAW them. adom-shotlog streams every screenshot the AI gathers into a live gallery (webview/pup/browser/phone), reports whether a human is really watching and on which surface, and ingests Adom Bridge child-window arrays via --meta.
← Commit history
Publish 0.23.4
10 files changed
+55−12
Cargo.lock+1−1@@ -10,7 +10,7 @@ checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" [[package]] name = "adom-shotlog"-version = "0.23.3"+version = "0.23.4" dependencies = [ "axum", "base64",
Cargo.toml+1−1@@ -1,6 +1,6 @@ [package] name = "adom-shotlog"-version = "0.23.3"+version = "0.23.4" edition = "2021" description = "Screenshot log viewer and CLI for Adom" license = "MIT"
SKILL.md+15@@ -135,6 +135,21 @@ the shot. Ways through: none of `--orig-w`+`--orig-h` / `--orig-file` / `--native` / `--meta`, the inject is refused with exit 1 and a hint. +### HTTP-only inject: `POST /api/paste`++No CLI on your machine? POST a data URL. **Declare your `source`** (same values as+`-s`: `pup`, `ad-window`, `hd`, ...); undeclared defaults to `api` (a neutral,+chip-less deliberate inject). Do NOT claim `clipboard` - that chip means a HUMAN+pasted with Ctrl+V, and only the viewer's paste handler sends it. `thread` and+`context` are accepted too and power the viewer's attribution and filters.++```bash+curl -s -X POST localhost:8820/api/paste -H 'Content-Type: application/json' -d '{+ "channel":"proj", "dataUrl":"data:image/png;base64,...",+ "description":"what you see and why", "source":"pup",+ "thread":"my thread title", "context":"the prompt being served"}'+```+ ## `--meta` - Adom Desktop child-window arrays As of adom-desktop 1.8.177, `desktop_screenshot_window {hwnd}` returns the primary
VERSION+1−1@@ -1 +1 @@-0.23.3+0.23.4
bin/adom-shotlog⋯ 1 unchanged line ⋯
dev-skills/adom-shotlog-dev/SKILL.md+17@@ -238,3 +238,20 @@ rebuild** after editing them. Bump `Cargo.toml` + `VERSION` together. match a grep for `activate`) before building a workaround. - It's **per-container**: threads in a different container get their own server. - `shotlog channels` prints the live overview - how every thread is using it.++## Metadata display rule (John, 2026-07-17)+When the human asks to see metadata, show ALL of it: every entry field plus the+entire sidecar flattened (meta.coordMap.*, popups[n].*, png_meta.*) plus the raw+JSON, inline. Never a curated subset. "Almost nothing" summaries got this exact+complaint: "when i ask you to show me metadata i mean all of it".++## Broker process naming trap (2026-07-17)+The read-hook's self-heal starts the broker as `shotlog serve` (argv[0]=shotlog),+NOT `adom-shotlog serve`. Managing the broker means checking BOTH names:+`pgrep -x adom-shotlog; pgrep -x shotlog`. And `pgrep -f "adom-shotlog serve"`+MATCHES YOUR OWN SHELL WRAPPER (the shell-snapshot eval embeds the pattern) —+killing head -1 of that killed my own shell (exit 144). Kill by exact name or+explicit PID from ps, never by -f pattern. Also: any deploy-restart window can+be resurrected mid-window by a hook self-heal running the OLD recovery state —+after migrating on-disk data, verify the SERVING process started AFTER the+migration (check prod-srv.log recovery line), not just that the port answers.
package.json+1−1@@ -1,6 +1,6 @@ { "slug": "adom-shotlog",- "version": "0.23.3",+ "version": "0.23.4", "type": "app", "title": "Adom Shotlog", "description": "See the shots your AI never showed you, and know it actually SAW them. adom-shotlog streams every screenshot the AI gathers into a live gallery (webview/pup/browser/phone), reports whether a human is really watching and on which surface, and ingests Adom Desktop child-window arrays via --meta.",
page.json+1−1@@ -1,6 +1,6 @@ { "slug": "adom-shotlog",- "version": "0.23.3",+ "version": "0.23.4", "type": "app", "title": "Adom Shotlog", "description": "See the shots your AI never showed you, and know it actually SAW them. adom-shotlog streams every screenshot the AI gathers into a live gallery (webview/pup/browser/phone), reports whether a human is really watching and on which surface, and ingests Adom Desktop child-window arrays via --meta.",
src/html/index.html+4−2@@ -645,7 +645,9 @@ header { 'clipboard':{label:'paste',cls:'',tip:'Pasted from the clipboard by the human (Ctrl+V in the viewer).'}, }; function srcChips(e){- if(!e.source || e.source==='cli') return [];+ // 'cli' and 'api' are deliberate injects with no capture endpoint declared —+ // chip-less on purpose. Only DECLARED provenance gets a chip.+ if(!e.source || e.source==='cli' || e.source==='api') return []; return e.source.split('/').map(part=>{ const m=SRC_MAP[part]; return m || {label:part, cls:'', tip:'Capture source: '+part};@@ -1333,7 +1335,7 @@ header { pasteOverlay.classList.add('active'); try { const dataUrl = await new Promise((res,rej)=>{ const r=new FileReader(); r.onload=()=>res(r.result); r.onerror=rej; r.readAsDataURL(blob); });- const res = await fetch('api/paste',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({channel,dataUrl})});+ const res = await fetch('api/paste',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({channel,dataUrl,source:'clipboard'})}); const r = await res.json(); if (r.ok) { let oi = (r.original_w&&r.original_h)?`<div class="detail">Original: <span class="val">${r.original_w}×${r.original_h}</span></div>`:'';
src/server.rs+14−5@@ -975,6 +975,13 @@ struct PasteRequest { #[serde(rename = "dataUrl")] data_url: String, description: Option<String>,+ // Provenance is DECLARED, never assumed. The viewer's Ctrl+V handler sends+ // source:"clipboard"; programmatic clients say who they are (or default to+ // "api", which renders chip-less like a CLI inject). Stamping every paste+ // "clipboard" mislabeled AI-pushed shots as human pastes.+ source: Option<String>,+ thread: Option<String>,+ context: Option<String>, } async fn post_paste(@@ -992,15 +999,17 @@ async fn post_paste( let was_resized = w != orig_w || h != orig_h; + let source = req.source.clone().unwrap_or_else(|| "api".into());+ let kind = if source == "clipboard" { "Clipboard paste" } else { "API paste" }; let description = req.description.unwrap_or_else(|| { if was_resized { format!(- "Clipboard paste (resized from {}x{}, {:.0} KB to {}x{}, {:.0} KB)",- orig_w, orig_h, orig_bytes as f64 / 1024.0,+ "{} (resized from {}x{}, {:.0} KB to {}x{}, {:.0} KB)",+ kind, orig_w, orig_h, orig_bytes as f64 / 1024.0, w, h, bytes.len() as f64 / 1024.0, ) } else {- format!("Clipboard paste ({}x{}, {:.0} KB)", w, h, bytes.len() as f64 / 1024.0)+ format!("{} ({}x{}, {:.0} KB)", kind, w, h, bytes.len() as f64 / 1024.0) } }); @@ -1011,10 +1020,10 @@ async fn post_paste( let entry = state .store .add_entry(- &req.channel, bytes, filename, description, "clipboard".into(), w, h,+ &req.channel, bytes, filename, description, source, w, h, if was_resized { Some(orig_w) } else { None }, if was_resized { Some(orig_h) } else { None },- None, None, None, None, None,+ None, None, req.thread.clone(), req.context.clone(), None, ) .await;