{"id":50966889,"url":"https://github.com/kyletolle/batcave","last_synced_at":"2026-06-18T21:03:08.314Z","repository":{"id":365626003,"uuid":"1265633592","full_name":"kyletolle/batcave","owner":"kyletolle","description":"Tooling from a personal knowledge system: an Obsidian vault, a VPS, and an AI agent that lives on it. The accumulated output of months of daily human/AI collaboration.","archived":false,"fork":false,"pushed_at":"2026-06-18T06:30:24.000Z","size":167,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-06-18T07:31:22.567Z","etag":null,"topics":["agentic-engineering","ai-agent","claude-code","knowledge-management","obsidian","personal-tooling"],"latest_commit_sha":null,"homepage":"","language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/kyletolle.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-06-11T00:30:05.000Z","updated_at":"2026-06-18T06:30:28.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/kyletolle/batcave","commit_stats":null,"previous_names":["kyletolle/batcave"],"tags_count":null,"template":false,"template_full_name":null,"purl":"pkg:github/kyletolle/batcave","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kyletolle%2Fbatcave","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kyletolle%2Fbatcave/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kyletolle%2Fbatcave/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kyletolle%2Fbatcave/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/kyletolle","download_url":"https://codeload.github.com/kyletolle/batcave/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kyletolle%2Fbatcave/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":34507160,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-06-18T02:00:06.871Z","response_time":128,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["agentic-engineering","ai-agent","claude-code","knowledge-management","obsidian","personal-tooling"],"created_at":"2026-06-18T21:03:05.527Z","updated_at":"2026-06-18T21:03:08.309Z","avatar_url":"https://github.com/kyletolle.png","language":"Python","funding_links":[],"categories":[],"sub_categories":[],"readme":"# batcave\n\nTooling from a personal knowledge system: an Obsidian vault, a VPS, and an AI agent that lives on it.\n\nThis repo is the code half of an ongoing human/AI collaboration. I'm Kyle. The agent is Bruce (Claude, running via Claude Code on a Hetzner VPS, with persistent memory and standing access to my vault, Todoist, Readwise, and calendar). Nearly every line here was written by Bruce, working from decisions we made together in conversation. I direct, review, and veto; Bruce designs, implements, and maintains.\n\nThat makes this repo two things at once: a working toolbox, and a demonstration of what agentic engineering looks like in practice (not a demo built for show, but the actual accumulated output of months of daily use).\n\n## How this code gets made\n\nThe workflow behind every tool here follows the same arc:\n\n1. **A real friction surfaces.** I notice something tedious (triaging Todoist, turning articles into audio, logging sleep times) and describe it in plain language, usually via speech-to-text.\n2. **We design in dialogue.** Bruce asks the questions that matter, proposes options with trade-offs, and we settle the decisions before any code exists. Plans live as notes in the vault.\n3. **Bruce implements.** I review behavior, not diffs, on the first pass. Tests come along where the logic warrants them (see `scripts/tests/`).\n4. **Lessons get encoded, not just remembered.** When something goes wrong, the fix becomes structure: the Todoist CLI logs every mutation to an audit trail because we once needed one and didn't have it. Mutations are forbidden outside that CLI because raw API calls bypass the log. The guardrails in this code are scar tissue from real incidents.\n\n## What's here\n\n### `bin/` — command wrappers\n\nSmall entry points installed to `~/.local/bin`. Most handle environment sourcing and delegate to `scripts/`; some are self-contained (`pagerduty-alert`, `ob-sync-check`, `qmd-query`).\n\n### `scripts/` — the tools\n\n| Area | Scripts | What they do |\n|------|---------|--------------|\n| Task management | `todoist.py`, `todoist_brief.py` | Full Todoist CLI (list/add/complete/search/bulk) with a JSONL audit trail of every mutation, plus a sanitized four-tier brief generator for morning summaries |\n| Text-to-speech | `read_aloud.py`, `tts.sh`, `daily_read_aloud.sh` | URL or document → cleaned text → chunked multi-provider TTS (OpenAI/Deepgram/ElevenLabs) → MP3s. Includes a daily cron digest |\n| Bat-Speaker | `batspeaker_hook.py`, `batspeaker_server.py` | Claude Code Stop-hook that auto-converts agent responses to audio in a rolling \"listen\" note. Toggleable, multi-engine, queue-based |\n| Readwise | `send_to_readwise.py`, `send_ai_note.py`, `readwise_classify.py`, `readwise_tag_apply.py`, `readwise_reader_cleanup.py` | Send any vault note to Reader as clean HTML; classify and tag the Reader library by topic |\n| Weekly review | `weekly_review_llm.py`, `llm_panel.py`, `llm_common.py` | Send a baked weekly reflection to a panel of LLMs (different vendors, same prompt) and collect their takes into one note |\n| Sleep tracking | `sleep_duration.py`, `sleep_update.py` | Duration math across day boundaries; ISO-week routing to write fields into the right weekly note |\n| Vault maintenance | `vault_health.py`, `shard_gardens_audit.py`, `add_day_and_publish_to_daily_notes.py`, `apply_taxonomy_v2.py` | Link/frontmatter health checks; LanguageTool Premium audit over published notes with caching and a custom dictionary |\n| Search infra | `qmd-sync.sh`, `patch-qmd-reranker.sh` | Keep a local semantic search index (QMD) in sync with the vault |\n\n### `vps/` — provisioning\n\nScripts to create, configure, and verify the box itself: `provision-vps.sh` (Hetzner create from CLI), `setup-vps.sh` (hardening: Tailscale-only SSH, UFW, services), `bootstrap-vps.sh`, `verify-vps.sh`, plus monitoring glue (`disk-watch.sh`, `notify-failed-service.sh`).\n\n## Caveats\n\nThis is personal tooling, published as reference rather than product. Paths assume my vault layout (`~/vault`), secrets load from `~/.env.sh`, and nothing here is packaged for installation. Read it for the patterns: audited mutation layers, provider abstractions, cron-friendly gather scripts that keep deterministic work out of the LLM's context window.\n\nSystem dependencies beyond Python: `curl` and `jq` (most scripts), and `ffmpeg` for the audio path — `tts.sh` and `read_aloud.py` chunk text past the providers' per-request limit and concat the resulting MP3s with ffmpeg, so long responses need it on the box. `vps/setup-vps.sh` installs all three; `vps/verify-vps.sh` checks for them.\n\n## License\n\nMIT\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fkyletolle%2Fbatcave","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fkyletolle%2Fbatcave","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fkyletolle%2Fbatcave/lists"}