Voice-note capture as the primary mobile input channel. AudioPen polishes on their cloud, POSTs to a self-hosted receiver on the desktop (exposed via Tailscale Funnel so no router ports open and nothing behind Authentik). The receiver writes to inbox/raw/; a bash normaliser wraps each drop in frontmatter and moves it to inbox/; /triage-inbox routes them from there. scripts/audiopen-ingest.sh — 80-line bash normaliser. Idempotent; safe to re-run. Extracts title, slugifies, computes capture timestamp from mtime, rewrites with fleeting-note frontmatter. scripts/audiopen-webhook/main.py — stdlib-only Python HTTP server. Zero pip deps; binds to 127.0.0.1 by default. Accepts flexible payload shapes (title/name + body/output/summary/polished/ orig_transcript) so AudioPen version drift is logged rather than silently dropped. scripts/audiopen-webhook/*.example — systemd user-unit templates for the receiver and the path/service pair that fires the ingest wrapper on inbox/raw/ changes. scripts/pyproject.toml + README.md — Python venv convention. Zero deps today; venv location reserved at scripts/.venv/ (gitignored), manifest at scripts/pyproject.toml, bootstrap documented for both plain pip and uv. Optional-dependencies groups let individual tools pull what they need without bloating the whole env. docs/setup/audiopen.md — full setup guide: generating the shared secret, installing the systemd units, pairing with Tailscale Funnel, configuring AudioPen's webhook, and the IMAP-fallback path for setups that can't run a public-reachable receiver. docs/setup/sync.md — companion guide: Gitea SSH remote (works even with Authentik gating HTTPS), obsidian-git plugin configuration, Syncthing desktop↔phone pairing with the critical .stignore patterns, and the three phone-role tiers so the user can pick Tier 1 / 2 / 3 at their own pace. .gitignore gains **/.venv/, **/venv/, **/__pycache__/, and *.pyc so nobody accidentally commits a materialised environment. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
scripts/
Operational scripts for the PKB. Everything here is side-effectful — writing files,
running webhooks, ingesting voice notes. Keep it small; anything analytical belongs
in .claude/commands/ or in the mql CLI.
Current contents
| Path | Language | Purpose |
|---|---|---|
audiopen-ingest.sh |
bash | Normalises raw AudioPen drops (inbox/raw/*.md) into properly-frontmattered fleeting notes in inbox/. Idempotent. |
audiopen-webhook/main.py |
Python (stdlib) | Tiny HTTP server that receives AudioPen webhook POSTs and writes to inbox/raw/. Designed to sit behind Tailscale Funnel. |
audiopen-webhook/*.example |
systemd | User-unit examples for the receiver + the path/service that runs audiopen-ingest.sh on file change. |
Setup guide for the full pipeline: docs/setup/audiopen.md.
Python venv convention
Python code in this repo is stdlib-only as of now. No pip-installable dependencies.
If/when a real dependency arrives (e.g. watchdog, imap-tools, anything heavier
than stdlib), the convention is:
-
Venv lives at
scripts/.venv/(gitignored — every user materialises locally). -
Dependencies tracked in
scripts/pyproject.toml(committed). -
Bootstrap on a fresh machine:
cd scripts python3 -m venv .venv .venv/bin/pip install -e .Or with
uv(recommended if available):cd scripts uv venv uv pip install -e . -
Scripts that need the venv should either:
- Invoke
scripts/.venv/bin/python3explicitly in their shebang or systemdExecStart. - Or document activation (
source scripts/.venv/bin/activate) before running.
- Invoke
Keeping the venv under scripts/ rather than the repo root keeps the Obsidian file
tree uncluttered — scripts/ is already excluded from Obsidian's index
(userIgnoreFilters in .obsidian/app.json) so nothing Python-related will ever
show in the graph or search.
Don't create the venv preemptively. When the first dependency lands, that's when you create it. Keeps the repo lighter and avoids stale venv directories.
The pyproject.toml uses [project.optional-dependencies] to split deps by tool
— e.g. pip install -e .[imap] installs only the IMAP fetcher's deps. This lets
the webhook receiver stay dep-free while heavier tools can pull in what they need.
Shell scripts
Kept in plain bash. Portable to any Linux running GNU coreutils (which Bazzite does).
No zsh-isms, no bashisms beyond set -euo pipefail and arrays. If a script grows
past ~200 lines or needs real argument parsing, rewrite it in Python (and you're now
back in the venv convention above).