Files
jpmschweitzerandClaude Opus 4.7 2c8df9425b add AudioPen ingest pipeline and sync architecture setup docs
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>
2026-04-19 20:17:48 +02:00

70 lines
3.0 KiB
Markdown

# audiopen-webhook
Zero-dependency Python HTTP server that receives AudioPen webhook POSTs and
writes bare markdown files to `inbox/raw/`. The separate `scripts/audiopen-ingest.sh`
wrapper normalises those files into proper fleeting notes under `inbox/`.
## Why this shape
- **Receiver is minimal (stdlib-only http.server).** No Flask, no runtime to keep
updated, ~150 LOC. Reads AudioPen's JSON payload, writes one file, logs to
stderr. Anything fancier belongs in the ingest wrapper.
- **Two-step raw → normalised.** Keeps the receiver stupid; all schema decisions
(frontmatter, slug format, filename) live in one shell script you can read
in 30 seconds.
- **Designed for Tailscale Funnel.** Binds to 127.0.0.1 by default. Tailscale
Funnel (or Cloudflare Tunnel, or an SSH remote forward, or ngrok) terminates
the public endpoint and proxies to 127.0.0.1:8765. No ports opened on your
router; nothing behind Authentik.
## Setup
Full step-by-step lives in [`docs/setup/audiopen.md`](../../docs/setup/audiopen.md).
Quick version:
1. **Generate a secret**: `head -c 24 /dev/urandom | base64 | tr -d '/+='`.
2. **Configure AudioPen** to POST JSON to `https://<your-funnel>.ts.net/audiopen/<secret>`.
3. **Install the systemd user units**:
```sh
mkdir -p ~/.config/audiopen-webhook ~/.config/systemd/user
echo "AUDIOPEN_WEBHOOK_SECRET=<secret>" > ~/.config/audiopen-webhook/env
chmod 600 ~/.config/audiopen-webhook/env
cp audiopen-webhook.service.example ~/.config/systemd/user/audiopen-webhook.service
# create two more systemd files for the ingest path/service per
# audiopen-ingest.path.example
systemctl --user daemon-reload
systemctl --user enable --now audiopen-webhook.service audiopen-ingest.path
```
4. **Expose via Tailscale Funnel**:
```sh
tailscale funnel --bg 8765
```
5. **Test**:
```sh
curl -X POST -H 'Content-Type: application/json' \
-d '{"title":"Test","body":"Hello from curl"}' \
https://<your-funnel>.ts.net/audiopen/<secret>
# → OK
ls ~/path/to/vault/inbox/raw # should see the test file
# wait a beat for the inotify path unit to fire
ls ~/path/to/vault/inbox # should see the normalised file
```
## Payload shape
The receiver accepts any of these JSON keys for the title: `title`, `name`.
For the body: `body`, `output`, `summary`, `polished`, `orig_transcript`. Unknown
keys are ignored; the raw payload is logged to stderr so you can see what
AudioPen actually sent if shape drifts.
If AudioPen's webhook format changes in a way the current extractor misses,
check `journalctl --user -u audiopen-webhook.service` for the logged shape and
update the key list in `extract_content()` at the top of `main.py`.
## Alternative: email + IMAP fetch
If running a public-reachable receiver isn't viable, the plan has an email-path
fallback documented in `docs/setup/audiopen.md`. It's entirely outbound: AudioPen
emails each note to a dedicated address, and a cron on the desktop pulls via
IMAP and drops files in `inbox/raw/`. Same ingest wrapper handles the rest.