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>
79 lines
2.2 KiB
Bash
Executable File
79 lines
2.2 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
# audiopen-ingest.sh
|
|
#
|
|
# Normalises raw AudioPen drops in inbox/raw/ into proper fleeting notes in inbox/.
|
|
# Adds frontmatter, renames to timestamp-slug format, moves to inbox/.
|
|
# Idempotent: safe to re-run. Destructive on inbox/raw/ (files are moved, not copied).
|
|
#
|
|
# Environment:
|
|
# COUNCIL_REPO — vault root. Default: /var/mnt/data/projects/council
|
|
#
|
|
# Exit status:
|
|
# 0 = success (even if zero files were processed)
|
|
# 1 = unexpected error
|
|
|
|
set -euo pipefail
|
|
|
|
REPO="${COUNCIL_REPO:-/var/mnt/data/projects/council}"
|
|
RAW="$REPO/inbox/raw"
|
|
OUT="$REPO/inbox"
|
|
|
|
if [ ! -d "$RAW" ]; then
|
|
echo "audiopen-ingest: raw dir $RAW does not exist; nothing to do." >&2
|
|
exit 0
|
|
fi
|
|
mkdir -p "$OUT"
|
|
|
|
shopt -s nullglob
|
|
count=0
|
|
for f in "$RAW"/*.md; do
|
|
# mtime as ISO-8601 (date -r works on GNU coreutils; Bazzite ships it)
|
|
captured=$(date -r "$f" -Iseconds)
|
|
ts=$(date -r "$f" +%Y-%m-%d-%H%M)
|
|
|
|
# Title: first `# ` line, else filename (without extension)
|
|
title=$(grep -m1 '^# ' "$f" 2>/dev/null | sed 's/^# *//' || true)
|
|
if [ -z "$title" ]; then
|
|
title=$(basename "$f" .md)
|
|
fi
|
|
|
|
# Slugify: lowercase, non-alnum → dash, collapse dashes, trim, cap at 60
|
|
slug=$(printf '%s' "$title" | tr '[:upper:]' '[:lower:]' \
|
|
| sed 's/[^a-z0-9]/-/g' \
|
|
| sed 's/--*/-/g' \
|
|
| sed 's/^-*//;s/-*$//' \
|
|
| cut -c1-60)
|
|
[ -z "$slug" ] && slug="untitled"
|
|
|
|
out="$OUT/${ts}_${slug}.md"
|
|
|
|
# If target exists (same timestamp + slug), append a numeric suffix
|
|
i=2
|
|
while [ -e "$out" ]; do
|
|
out="$OUT/${ts}_${slug}_${i}.md"
|
|
i=$((i+1))
|
|
done
|
|
|
|
# Body: strip the first `# title` line so we regenerate it in the normalised form
|
|
content=$(awk 'BEGIN{stripped=0} /^# / && !stripped { stripped=1; next } { print }' "$f")
|
|
|
|
{
|
|
echo "---"
|
|
echo "type: fleeting"
|
|
echo "source: audiopen"
|
|
echo "captured: $captured"
|
|
echo "slug: $slug"
|
|
echo "---"
|
|
echo
|
|
echo "# $title"
|
|
echo
|
|
printf '%s\n' "$content"
|
|
} > "$out"
|
|
|
|
rm "$f"
|
|
echo "ingested: $(basename "$f") → $(basename "$out")"
|
|
count=$((count+1))
|
|
done
|
|
|
|
echo "audiopen-ingest: $count file(s) normalised."
|