{"slug": "show-hn-stash-turn-your-instagram-saves-into-notes-claude-finds-on-its-own", "title": "Show HN: Stash, turn your Instagram saves into notes Claude finds on its own", "summary": "Stash, a new open-source tool, converts saved Instagram posts into searchable markdown notes that Claude can access unprompted, addressing the gap between capturing content and using it later. The tool, which uses Groq's Whisper for transcription and qwen3.6-27b for extraction, was tested on real saved posts in August 2026 and works without Instagram cookies for public content. It integrates with Claude Code via a skill and MCP, and includes a Cloudflare Worker-based pipeline for phone-to-Mac capture.", "body_md": "Turn the Instagram posts you save into notes Claude finds on its own.\n\nYou already capture reliably — share, tap your own account, done. What fails is everything after: a URL tells you nothing about what was in the post, so you never triage it, and nothing surfaces it when it would be useful. This is not a bookmarking tool. It transcribes what you saved and hands it back unprompted while you work.\n\n## demo-compressed.mp4\n\n```\nshare sheet ─┐\n             ├─► /ingest ─► queue ─► fetch ─► transcribe ─► frame gate ─► extract ─► vault/*.md + FTS5\ndata export ─┘                                                                              │\n                                                            Claude Code skill · MCP · weekly digest ◄┘\ncd stash\n/opt/homebrew/bin/python3.12 -m venv .venv\n.venv/bin/pip install -e \".[mcp]\"\ncp .env.example .env\n.venv/bin/python -m stash doctor\n```\n\n`doctor`\n\nnames anything missing and the exact command to fix it. Three things\nmatter:\n\n**1. brew install ffmpeg yt-dlp**\n\n**2. A Groq key.** Get one at\n[console.groq.com/keys](https://console.groq.com/keys) and put it in `.env`\n\n.\nGroq Whisper transcribes reel/video audio; `qwen/qwen3.6-27b`\n\nreads static posts,\ncarousel slides, and selected reel frames, then returns the final JSON note.\nNo Claude call is made while processing. For transcription only, the offline\nalternative remains `.venv/bin/pip install -e \".[local-whisper]\"`\n\n.\n\n**3. Instagram cookies — you probably don't need any.** Both cookie settings\nship blank on purpose. Public reels download fine anonymously; measured on real\nsaved posts in August 2026, metadata and full video+audio both came back with no\nsession at all.\n\nVerify it yourself before trusting this:\n\n```\nyt-dlp --skip-download --print \"%(id)s | %(uploader)s\" \"https://www.instagram.com/reel/XXXX/\"\n```\n\nSetting a cookie source when you don't need one is actively worse than leaving it\nblank — `--cookies-from-browser chrome`\n\ntriggers a keychain prompt, and an\nunanswered prompt fails the download that would otherwise have succeeded.\n\nIf you do hit login walls (private accounts, sustained rate limiting), add a session then, cheapest first:\n\n— a cookies.txt scoped to instagram.com, exported with the \"Get cookies.txt LOCALLY\" extension,`STASH_COOKIES_FILE`\n\n`chmod 600`\n\n. yt-dlp gets your Instagram session and nothing else.— no keychain grant needed on macOS.`STASH_COOKIES_FROM_BROWSER=firefox`\n\n— last resort. Chrome's cookies are AES-encrypted with a login keychain key that decrypts`=chrome`\n\n*every*site you are signed into, not just Instagram. Standing permission for an unattended worker to use that is a far wider blast radius than this task warrants.\n\nA cookies file is your Instagram login in plain text. Keep it out of git; revoke it from Instagram → Settings → Security → where you're logged in.\n\nStatic posts use one vision image. Carousels preserve slide order and send every slide; Groq's per-request image limit is handled automatically in ordered batches. Mixed carousels also transcribe each video slide. Reels keep the existing Whisper plus selective-frame path.\n\n```\n.venv/bin/python -m stash add \"https://www.instagram.com/reel/...\" -n \"why I saved it\"\n.venv/bin/python -m stash process\n.venv/bin/python -m stash search \"agent memory\"\n.venv/bin/python -m stash status\n```\n\nNotes land in `vault/`\n\nas markdown with YAML frontmatter. That's the durable\nartifact — the SQLite index is derived and `stash reindex`\n\nrebuilds it from disk.\n\nThree pieces: a Shortcut that posts to Cloudflare, a Worker that holds the queue, and a daemon on the Mac that drains it. Set up in that order.\n\nThis is what makes capture independent of your Mac — the phone talks to Cloudflare over HTTPS from anywhere: cellular, a café, lid shut.\n\n```\ncd worker && npm install\nnpx wrangler login                    # browser, free account, no card\nnpx wrangler d1 create stash          # paste the id into wrangler.toml\nnpx wrangler d1 migrations apply stash --remote\nnpx wrangler secret put STASH_SECRET  # any long random string\nnpx wrangler deploy\n```\n\nPut the deployed URL and the same secret into `.env`\n\nas `STASH_WORKER_URL`\n\nand\n`STASH_SECRET`\n\n. Everything downstream switches to the remote queue on its own.\n\nFree tier throughout: Workers 100k req/day, D1 5 GB. **No R2 and therefore no\npayment method** — R2 is the one product here that asks for a card, and\nnothing currently running needs it, so it is left unconfigured.\n\n```\nsh shortcuts/build.sh\n```\n\nRenders `shortcuts/Stash.cherri.template`\n\nwith your Worker URL and secret,\ncompiles it with [Cherri](https://cherrilang.org), and signs it. AirDrop the\nresulting `Stash.shortcut`\n\nto your phone, or open it on a Mac on the same Apple\nID and it syncs.\n\n**Delete every older Stash shortcut.** Several near-identical entries in the\nshare sheet, some pointing at endpoints that no longer exist, is how this\nsilently broke before.\n\nThe Shortcut says **\"Queued\"**, not \"saved\" — deliberately. It only knows the\nrequest reached Cloudflare. Confirmation that a note actually exists comes\nseparately, from step 4.\n\n```\nsh stash/launchd/install.sh   # starts at login, restarts on crash\n```\n\nOr `stash daemon`\n\nin a terminal to watch it work. Either way:\n\n```\nstash status    # says ALIVE/DOWN, checking pid liveness AND heartbeat age\n```\n\nThat check exists because a receiver process once died quietly and nothing said so. A hung-but-not-exited daemon is reported down too, not just a dead one.\n\n```\nstash notify          # sends a test; --fail for the failure shape\n```\n\nSet `STASH_NOTIFY`\n\nto `ntfy`\n\nor `imessage`\n\nin `.env`\n\nfirst — see the comments\nthere. Both are free; `ntfy`\n\nis verified working, `imessage`\n\nneeds no app\ninstall but does need a one-time Automation grant, so test it before trusting it.\n\nProcessing. A save is never lost — it sits in D1 — but the note appears when the Mac is next awake with the daemon running. Capture is the part that no longer depends on anything.\n\nThe old iCloud-file path (`shortcuts/Stash-icloud.cherri`\n\n+ `stash watch`\n\n) still\nworks and needs no accounts at all, if you'd rather have zero infrastructure.\n\nNo API has ever exposed Instagram Saved collections. The data export is the only route, and Meta takes hours to days to produce it — request it now:\n\nAccounts Center → Your information and permissions → Export your information →\n\nJSON\n\n```\n.venv/bin/python scripts/import_export.py ~/Downloads/instagram-export --dry-run\n.venv/bin/python scripts/import_export.py ~/Downloads/instagram-export\n```\n\nThen drain slowly. Pushing a few hundred permalinks through yt-dlp back-to-back is the fastest way to get rate-limited, which breaks live capture too:\n\n```\nwhile .venv/bin/python -m stash process --limit 1 | grep -q wrote; do sleep 45; done\n```\n\nThis is the part that decides whether you still use it in a month.\n\n**MCP server** — `stash/mcp_server.py`\n\n, registered at **user** scope:\n\n```\nclaude mcp add --scope user stash -- /path/to/stash/.venv/bin/python -m stash.mcp_server\n```\n\nUser scope, not project scope — it's connected in every Claude Code session\nregardless of which repo you're in, not only inside this one. Gives Claude\n`search_stash`\n\n, `get_stash_note`\n\n, `list_stash_topics`\n\n, `recent_stash`\n\n,\n`mark_stash_used`\n\n. `search_stash`\n\nreturns compact hits (short id, title,\none-line summary, tools) to keep it cheap to call often; `get_stash_note`\n\npulls full detail — transcript, next step, permalink — for whichever hit\nturns out to matter.\n\n**Claude Code skill** — `~/.claude/skills/stash-recall/`\n\n, also user scope and\nfor the same reason: the material in the vault isn't specific to any one\nproject, so the skill needs to fire wherever you happen to be working. It's\nwritten to trigger when you *start* technical work, not when you ask it to\nsearch, because you will never think to ask.\n\n** mark_stash_used** looks optional and isn't.\n\n`used`\n\nvs `unused`\n\nis the only\nmeasure of whether this is a knowledge base or a graveyard. If nothing is ever\nmarked used after a month, change how recall works rather than filing more notes.**Search is hybrid** — FTS5 (exact tokens: a repo name, a product name) fused\nwith local vector search (meaning) via weighted Reciprocal Rank Fusion. Plain\nkeyword search alone missed things by paraphrase: *\"how do I make videos\nautomatically\"* ranked the two notes actually about generating video 6th and\n8th, behind a WhatsApp chatbot, because BM25 has no notion that \"automatically\"\nand \"generation\" are related ideas. Embeddings run locally via\n[fastembed](https://github.com/qdrant/fastembed) + `BAAI/bge-small-en-v1.5`\n\n(no\nAPI key, no network, no PyTorch) and RRF fusion runs through\n[sqlite-vec](https://github.com/asg017/sqlite-vec) inside the same SQLite file\nas everything else — no separate vector database. Degrades to keyword-only,\nwith a one-time warning, if either is unavailable; a capture can never fail\nover search infrastructure. `stash doctor`\n\nreports vector status and flags if\nstored vectors were embedded under a different model than the one currently\nconfigured.\n\nWhisper is mandatory on Instagram — reels have auto-captions in the app but\nInstagram doesn't expose them to downloaders\n([yt-dlp#15874](https://github.com/yt-dlp/yt-dlp/issues/15874)), so the\nsubtitles-first shortcut other tools lead with never fires here.\n\nGiven a transcript, frames are pulled **only** where the audio stopped being\nself-sufficient — segments Whisper was unsure about, and phrases like \"run this\ncommand\" or \"link in bio\" that point at the screen. Idea borrowed from\n[media-mcp](https://github.com/woosal1337/media-mcp). A talking head gets one\nframe; a screen recording gets frames at the moments that matter; a silent reel\nfalls back to even sampling. Capped at 5 — frames dominate token cost.\n\nTwo vision limits worth knowing before you tune anything, both measured against the live API rather than assumed:\n\n**3 images per request is a hard API cap**, not a choice. A 4th returns HTTP 400.`_describe_visuals`\n\nbatches around it.**512px is the optimum, and bigger is worse.** Token cost is flat across resolution (Groq normalises images to a fixed budget), but on a dense screenshot 512px transcribed 1328 chars including \"MIT license\" while 1024px managed ~700 and garbled text the smaller version read correctly. Raising it looks free and isn't.\n\n```\n.venv/bin/python -m pytest tests/ -q\nstash/\n  config.py       env + paths\n  db.py           capture queue + FTS5 note index\n  fetch.py        CDN direct / yt-dlp / cobalt\n  transcribe.py   groq or faster-whisper, with per-segment confidence\n  frames.py       the confidence gate\n  extract.py      groq vision + structured JSON\n  vault.py        markdown + frontmatter\n  pipeline.py     orchestration\n  remote.py       Worker-backed queue client\n  daemon.py       poll loop + heartbeat (the durable capture path)\n  notify.py       iMessage / ntfy confirmation\n  local_receiver.py  LAN receiver (superseded by the Worker)\n  watch.py        iCloud-file watcher (zero-infrastructure fallback)\n  mcp_server.py   recall tools\n  launchd/        run the daemon at login\nworker/           Cloudflare Worker + D1 (no R2 — see above)\nshortcuts/        Cherri source for the iOS Shortcut\nscripts/          data-export backfill\n```\n\n", "url": "https://wpnews.pro/news/show-hn-stash-turn-your-instagram-saves-into-notes-claude-finds-on-its-own", "canonical_source": "https://github.com/Parthuss/stash", "published_at": "2026-08-22 16:42:39+00:00", "updated_at": "2026-08-22 17:14:23.052108+00:00", "lang": "en", "topics": ["ai-tools", "artificial-intelligence", "natural-language-processing"], "entities": ["Stash", "Instagram", "Claude", "Groq", "Whisper", "qwen3.6-27b", "Cloudflare", "yt-dlp"], "alternates": {"html": "https://wpnews.pro/news/show-hn-stash-turn-your-instagram-saves-into-notes-claude-finds-on-its-own", "markdown": "https://wpnews.pro/news/show-hn-stash-turn-your-instagram-saves-into-notes-claude-finds-on-its-own.md", "text": "https://wpnews.pro/news/show-hn-stash-turn-your-instagram-saves-into-notes-claude-finds-on-its-own.txt", "jsonld": "https://wpnews.pro/news/show-hn-stash-turn-your-instagram-saves-into-notes-claude-finds-on-its-own.jsonld"}}