adding the first version of the presentation and the initial attempt at the .meetings script

This commit is contained in:
itsamejms
2026-06-07 16:13:10 +01:00
parent 41f99557cc
commit 05c7d6aef3
34 changed files with 7623 additions and 1 deletions
@@ -0,0 +1,33 @@
Here are the extracted action items, tasks, and commitments:
1. **Who**: Unassigned
**What**: Review and revise meeting notes to ensure accuracy and completeness.
**When**: No deadline specified
**Priority**: Low
2. **Who**: Alaz
**What**: Follow up with Snorri regarding his dimensional teapot and its contents.
**When**: No deadline specified
**Priority**: Medium
3. **Who**: Tuxi
**What**: Provide Alaz with more information about the dimensional teapot's prize.
**When**: No deadline specified
**Priority**: Low
4. **Who**: Unassigned
**What**: Research and provide answers to Snorri's questions regarding the family portraits in his lounge.
**When**: No deadline specified
**Priority**: Medium
5. **Who**: Alaz
**What**: Investigate the origins of the jar image found in Snorri's lounge.
**When**: No deadline specified
**Priority**: Low
6. **Who**: Unassigned
**What**: Clarify and resolve any outstanding questions or issues from the meeting.
**When**: No deadline specified
**Priority**: High
Note: The conversation at the end of the transcript appears to be a discussion about whether anything was missed, but no specific action items were assigned.
File diff suppressed because one or more lines are too long
@@ -0,0 +1,20 @@
**Topic**: **Campaign Session Recap**
**Key Points**:
* The group participated in a combat encounter with the Beatles, using creative methods to defeat them.
* A "beat-o" was defeated by Snorri using an apple, granting him a ghostly speed ability for a week.
* The group prepared and presented three dishes: Tuxi's colorful boom-boom decoration, Snorri's sashimi, and Alaz's flamed dish.
* For dessert, the group went on a foraging task, with Tuxy preparing lychee surprise and Snorri making a lingon berry jelly.
* Alaz visited a village of awakened apes and obtained ingredients to make a rice pudding.
**Decisions**:
* The competition was decided, with Alaz winning a dimensional teapot and Snorri coming second with an arcane Rubik's gift.
* Snorri exchanged prizes with Alaz, obtaining the teapot and rubbing it three times to teleport into a different dimension.
* Snorri obtained an image of a jar from the new dimension.
**Open Questions**:
* What is the significance of the family portraits in the new dimension?
* How will the group's experience in this new dimension affect their future gameplay?
File diff suppressed because one or more lines are too long
+178
View File
@@ -0,0 +1,178 @@
# 🎤 meetings — local audio → transcript → summary + action items
A single-file shell script CLI that transcribes meeting recordings (using GGUF Whisper or Parakeet models), then generates a summary and extracts action items using Ollama. Everything runs 100% locally.
```
audio file ──▶ ffmpeg ──▶ whisper.cpp / parakeet.cpp ──▶ Ollama ──▶ report.md
│ │ │
16kHz mono WAV GGUF transcription summary + actions
```
## Quick start
```bash
# 1. Clone/download
git clone <this-repo> meetings-cli && cd meetings-cli
# 2. One-time setup (installs whisper.cpp, downloads model, pulls Ollama LLM)
./meetings setup
# 3. Process a meeting recording
./meetings recording.mp3
# 4. Check everything is healthy
./meetings doctor
```
## What it does
| Step | Tool | What happens |
|------|------|--------------|
| 1. Convert | ffmpeg | Any audio → 16kHz mono WAV |
| 2. Transcribe | whisper.cpp or parakeet.cpp | GGUF/GGML model → text transcript |
| 3. Summarize | Ollama | Transcript → structured summary (topic, key points, decisions, open questions) |
| 4. Extract | Ollama | Transcript → numbered action items (who, what, when, priority) |
## Output
For each audio file, a directory is created containing:
```
2026-06-07_1402_team_standup/
├── report.md # Combined: summary + actions + transcript
├── transcript.txt # Raw transcription
├── summary.md # LLM-generated summary
└── action_items.md # Extracted action items
```
## Requirements
| Dependency | Install | Purpose |
|------------|---------|---------|
| **ffmpeg** | `brew install ffmpeg` | Audio format conversion |
| **whisper.cpp** | `brew install whisper-cpp` | Speech-to-text (GGML models) |
| **Ollama** | [ollama.com](https://ollama.com) | LLM for summarization |
| **jq** | `brew install jq` | JSON parsing for Ollama API |
> `./meetings setup` handles all of this automatically.
## STT engines
### whisper.cpp (default, recommended)
- Battle-tested, many languages, large model ecosystem
- Models from [ggerganov/whisper.cpp](https://huggingface.co/ggerganov/whisper.cpp)
- Install: `brew install whisper-cpp`
| Model | Size | Best for |
|-------|------|----------|
| tiny.en | 75 MB | Quick tests, English |
| base.en | 142 MB | Good balance, English |
| small.en | 466 MB | **Recommended for English** |
| medium.en | 1.5 GB | High accuracy, English |
| large-v3-turbo | 809 MB | Best multilingual, fast |
| large-v3 | 2.9 GB | Best accuracy, any language |
### parakeet.cpp (alternative, faster)
- NVIDIA Parakeet models, excellent English, smaller footprint
- Models from [mudler/parakeet-cpp-gguf](https://huggingface.co/mudler/parakeet-cpp-gguf)
- Install: Build from [source](https://github.com/mudler/parakeet.cpp) or use Docker
| Model | Size | Best for |
|-------|------|----------|
| tdt_ctc-110m-q8_0 | 178 MB | Fast, good English |
| tdt_ctc-110m-f16 | 268 MB | Fast, lossless English |
| tdt-0.6b-v3-f16 | 1.4 GB | Multilingual |
## Configuration
### Environment variables
```bash
MEETINGS_DIR # Config & models directory (default: ~/.meetings)
MEETINGS_STT # STT engine: whisper | parakeet
MEETINGS_STT_MODEL # Path to GGUF/GGML model file
MEETINGS_LLM # Ollama model for summarization (default: llama3.1:8b)
MEETINGS_THREADS # Thread count for STT (default: 4)
MEETINGS_LANG # Language code (default: en; use "auto" for multilingual)
MEETINGS_OUTPUT # Output directory (default: .)
```
### CLI flags
```bash
./meetings recording.mp3 --stt whisper --llm llama3.1:8b --lang en --output ./reports
```
### Config file
Saved at `~/.meetings/config` after running `./meetings setup`:
```
STT_ENGINE=whisper
OLLAMA_MODEL=llama3.1:8b
THREADS=4
STT_MODEL=/home/user/.meetings/models/ggml-small.en.bin
```
## Commands
```bash
./meetings <audio_file> # Run the full pipeline
./meetings setup # Install deps + download model (interactive)
./meetings doctor # Check all dependencies
./meetings config # Show current configuration
./meetings help # Show help
```
## How Ollama fits in
**Ollama does NOT run the whisper/parakeet models** — those use their own inference engines (whisper.cpp / parakeet.cpp). Ollama is only used for the LLM steps:
1. **Summary generation** — sends the transcript to an Ollama model with a structured summarization prompt
2. **Action item extraction** — sends the transcript to an Ollama model with an action-item extraction prompt
You can use any Ollama model. Smaller models (llama3.2:1b, gemma3:1b) are faster; larger models (llama3.1:8b, qwen2.5-coder:7b) produce better summaries.
## Example
```bash
$ ./meetings team_standup.m4a
┌─────────────────────────────────────────────┐
│ 🎤 M E E T I N G S │
│ audio → transcript → summary + actions │
│ whisper.cpp · parakeet.cpp · ollama │
└─────────────────────────────────────────────┘
Input: team_standup.m4a
STT engine: whisper
STT model: ggml-small.en.bin
LLM model: llama3.1:8b
Language: en
Output: ./2026-06-07_1402_team_standup/
── Step 1/4 — Converting audio ──
▸ Converting audio to 16kHz mono WAV...
✓ Audio converted: 1.2M
── Step 2/4 — Transcribing with whisper ──
▸ Transcribing with whisper.cpp...
✓ Transcript: 847 words
── Step 3/4 — Summarizing (llama3.1:8b) ──
✓ Summary saved
── Step 4/4 — Extracting action items (llama3.1:8b) ──
✓ Action items saved
✓ All done! Files saved to: ./2026-06-07_1402_team_standup/
📄 Report: ./2026-06-07_1402_team_standup/report.md
📝 Transcript: ./2026-06-07_1402_team_standup/transcript.txt
📋 Summary: ./2026-06-07_1402_team_standup/summary.md
✅ Action Items: ./2026-06-07_1402_item_standup/action_items.md
```
## License
MIT
+659
View File
@@ -0,0 +1,659 @@
#!/usr/bin/env bash
#
# meetings — audio → transcript → summary + action items, all local
#
# Transcribes audio using whisper.cpp or parakeet.cpp (GGUF/GGML models),
# then uses Ollama to summarize and extract action items.
#
# Usage:
# ./meetings <audio_file> # full pipeline
# ./meetings setup # install deps + download model
# ./meetings doctor # check dependencies
# ./meetings config # show current config
#
set -euo pipefail
# ─── colours ────────────────────────────────────────────────────────
RED='\033[0;31m'; GRN='\033[0;32m'; YEL='\033[1;33m'
BLU='\033[0;34m'; CYN='\033[0;36m'; RST='\033[0m'
BOLD='\033[1m'
# ─── paths ──────────────────────────────────────────────────────────
MEETINGS_DIR="${MEETINGS_DIR:-$HOME/.meetings}"
CONFIG_FILE="$MEETINGS_DIR/config"
MODELS_DIR="$MEETINGS_DIR/models"
# ─── helpers ───────────────────────────────────────────────────────
die() { printf "${RED}error: %s${RST}\n" "$*" >&2; exit 1; }
info() { printf "${BLU}▸ %s${RST}\n" "$*" >&2; }
ok() { printf "${GRN}✓ %s${RST}\n" "$*" >&2; }
warn() { printf "${YEL}⚠ %s${RST}\n" "$*" >&2; }
step() { printf "\n${BOLD}${CYN}── %s ──${RST}\n" "$*" >&2; }
banner() {
cat <<'BAN'
┌─────────────────────────────────────────────┐
│ 🎤 M E E T I N G S │
│ audio → transcript → summary + actions │
│ whisper.cpp · parakeet.cpp · ollama │
└─────────────────────────────────────────────┘
BAN
}
# ─── load config: env vars > config file > defaults ────────────────
load_config() {
# defaults
STT_ENGINE=""
STT_MODEL=""
OLLAMA_MODEL="llama3.1:8b"
OLLAMA_HOST="http://localhost:11434"
THREADS="4"
LANGUAGE="en"
OUTPUT_DIR="."
# config file overrides defaults
[[ -f "$CONFIG_FILE" ]] && source "$CONFIG_FILE"
# env vars override everything (|| true prevents set -e exit on empty vars)
[[ -n "${MEETINGS_STT:-}" ]] && STT_ENGINE="$MEETINGS_STT" || true
[[ -n "${MEETINGS_STT_MODEL:-}" ]] && STT_MODEL="$MEETINGS_STT_MODEL" || true
[[ -n "${MEETINGS_LLM:-}" ]] && OLLAMA_MODEL="$MEETINGS_LLM" || true
[[ -n "${MEETINGS_THREADS:-}" ]] && THREADS="$MEETINGS_THREADS" || true
[[ -n "${MEETINGS_LANG:-}" ]] && LANGUAGE="$MEETINGS_LANG" || true
[[ -n "${MEETINGS_OUTPUT:-}" ]] && OUTPUT_DIR="$MEETINGS_OUTPUT" || true
}
# ─── detect STT engine ──────────────────────────────────────────────
detect_stt() {
if command -v parakeet-cli &>/dev/null; then
echo "parakeet"
elif command -v whisper-cli &>/dev/null; then
echo "whisper"
else
echo ""
fi
}
get_stt_binary() {
local engine="${1:-$STT_ENGINE}"
case "$engine" in
whisper) command -v whisper-cli 2>/dev/null || echo "" ;;
parakeet) command -v parakeet-cli 2>/dev/null || echo "" ;;
*) echo "" ;;
esac
}
find_stt_model() {
local engine="${1:-whisper}"
if [[ -n "${STT_MODEL:-}" && -f "${STT_MODEL}" ]]; then
echo "$STT_MODEL"; return 0
fi
case "$engine" in
whisper)
for f in "$MODELS_DIR"/ggml-*.bin "$MODELS_DIR"/ggml-*.gguf; do
[[ -f "$f" ]] && echo "$f" && return 0
done
;;
parakeet)
for f in "$MODELS_DIR"/*parakeet*.gguf "$MODELS_DIR"/*tdt*.gguf; do
[[ -f "$f" ]] && echo "$f" && return 0
done
;;
esac
return 1
}
# ─── doctor ─────────────────────────────────────────────────────────
cmd_doctor() {
banner >&2
local ok_count=0 total=0
total=$((total+1))
if command -v ffmpeg &>/dev/null; then
ok "ffmpeg: $(command -v ffmpeg)"; ok_count=$((ok_count+1))
else
warn "ffmpeg: not found"
fi
total=$((total+1))
if command -v ollama &>/dev/null; then
ok "ollama: $(command -v ollama)"; ok_count=$((ok_count+1))
if curl -sf "${OLLAMA_HOST:-http://localhost:11434}/api/tags" &>/dev/null; then
ok " server: running at ${OLLAMA_HOST:-http://localhost:11434}"
else
warn " server: not responding (run: ollama serve)"
fi
else
warn "ollama: not found"
fi
total=$((total+1))
local engine="${STT_ENGINE:-$(detect_stt)}"
local bin
bin="$(get_stt_binary "$engine")"
if [[ -n "$bin" ]]; then
ok "STT engine: $engine ($bin)"; ok_count=$((ok_count+1))
else
warn "STT engine: not found (run: ./meetings setup)"
fi
total=$((total+1))
local model
model="$(find_stt_model "$engine")" || true
if [[ -n "$model" ]]; then
ok "STT model: $model"; ok_count=$((ok_count+1))
else
warn "STT model: not found (run: ./meetings setup)"
fi
total=$((total+1))
local ollama_model="${OLLAMA_MODEL:-llama3.1:8b}"
local model_name
model_name="$(echo "$ollama_model" | cut -d: -f1)"
if ollama list 2>/dev/null | awk '{print $1}' | grep -qF "$model_name"; then
ok "LLM model: $ollama_model (pulled)"; ok_count=$((ok_count+1))
else
warn "LLM model: $ollama_model (not pulled — run: ollama pull $ollama_model)"
fi
echo "" >&2
if [[ $ok_count -eq $total ]]; then
ok "All good ($ok_count/$total)"
else
warn "Ready ($ok_count/$total). Run './meetings setup' to install missing pieces."
fi
}
# ─── setup ──────────────────────────────────────────────────────────
cmd_setup() {
load_config
banner >&2
mkdir -p "$MEETINGS_DIR" "$MODELS_DIR"
# ── ffmpeg ──
step "Checking ffmpeg"
if command -v ffmpeg &>/dev/null; then
ok "ffmpeg already installed"
else
info "Installing ffmpeg..."
if [[ "$(uname)" == "Darwin" ]]; then
brew install ffmpeg || die "Could not install ffmpeg"
elif command -v apt-get &>/dev/null; then
sudo apt-get update && sudo apt-get install -y ffmpeg
elif command -v dnf &>/dev/null; then
sudo dnf install -y ffmpeg
else
die "Please install ffmpeg manually: https://ffmpeg.org/download.html"
fi
fi
# ── ollama ──
step "Checking Ollama"
if command -v ollama &>/dev/null; then
ok "ollama already installed"
else
info "Installing Ollama..."
curl -fsSL https://ollama.com/install.sh | sh || die "Could not install Ollama"
fi
if ! curl -sf "${OLLAMA_HOST:-http://localhost:11434}/api/tags" &>/dev/null; then
info "Starting Ollama server..."
ollama serve &>/dev/null &
sleep 3
fi
# ── choose STT engine ──
step "Choosing STT engine"
echo "" >&2
echo " 1) whisper.cpp — battle-tested, many languages, brew install" >&2
echo " 2) parakeet.cpp — faster, great English, smaller footprint" >&2
echo "" >&2
local choice
if [[ -n "${STT_ENGINE:-}" ]]; then
choice="$STT_ENGINE"
info "Using pre-configured engine: $choice"
else
read -rp " Choose [1/2, default=1]: " choice
case "${choice:-1}" in
2|parakeet) choice="parakeet" ;;
*) choice="whisper" ;;
esac
fi
case "$choice" in
whisper) setup_whisper ;;
parakeet) setup_parakeet ;;
esac
STT_ENGINE="$choice"
grep -q "^STT_ENGINE=" "$CONFIG_FILE" 2>/dev/null \
&& sed -i '' "s/^STT_ENGINE=.*/STT_ENGINE=$choice/" "$CONFIG_FILE" 2>/dev/null \
|| echo "STT_ENGINE=$choice" >> "$CONFIG_FILE"
# ── pull LLM ──
step "Pulling LLM: ${OLLAMA_MODEL:-llama3.1:8b}"
local ollama_model="${OLLAMA_MODEL:-llama3.1:8b}"
if ollama list 2>/dev/null | awk '{print $1}' | grep -qF "$(echo "$ollama_model" | cut -d: -f1)"; then
ok "$ollama_model already pulled"
else
ollama pull "$ollama_model" || warn "Could not pull $ollama_model. Run: ollama pull $ollama_model"
fi
echo "" >&2
ok "Setup complete! Run './meetings doctor' to verify, then './meetings <audio_file>' to process."
}
setup_whisper() {
step "Installing whisper.cpp"
if command -v whisper-cli &>/dev/null; then
ok "whisper-cli already available at $(command -v whisper-cli)"
else
info "Installing via package manager..."
if [[ "$(uname)" == "Darwin" ]]; then
brew install whisper-cpp || die "Could not install whisper-cpp via brew"
else
die "Please install whisper-cli manually: https://github.com/ggml-org/whisper.cpp
On macOS: brew install whisper-cpp
Or build from source: git clone https://github.com/ggml-org/whisper.cpp && cd whisper.cpp && cmake -B build && cmake --build build -j"
fi
fi
step "Downloading Whisper model"
local model_choice
echo "" >&2
echo " Available models (from HuggingFace ggerganov/whisper.cpp):" >&2
echo " tiny.en — 75 MB (fastest, English only)" >&2
echo " base.en — 142 MB (good for quick tests)" >&2
echo " small.en — 466 MB (recommended for English) ★" >&2
echo " medium.en — 1.5 GB (high accuracy, English only)" >&2
echo " large-v3-turbo — 809 MB (best multilingual, fast)" >&2
echo " large-v3 — 2.9 GB (best accuracy, any language)" >&2
echo "" >&2
if [[ -n "${STT_MODEL:-}" && -f "${STT_MODEL}" ]]; then
info "Using existing model: $STT_MODEL"
else
read -rp " Choose model [default=small.en]: " model_choice
model_choice="${model_choice:-small.en}"
local model_file="$MODELS_DIR/ggml-${model_choice}.bin"
if [[ -f "$model_file" ]]; then
ok "Model already downloaded: $model_file"
else
info "Downloading ggml-${model_choice}.bin (this may take a moment)..."
curl -L --progress-bar \
-o "$model_file" \
"https://huggingface.co/ggerganov/whisper.cpp/resolve/main/ggml-${model_choice}.bin" \
|| die "Failed to download model"
ok "Downloaded: $model_file"
fi
STT_MODEL="$model_file"
echo "STT_MODEL=$model_file" >> "$CONFIG_FILE"
fi
}
setup_parakeet() {
step "Installing parakeet.cpp"
if command -v parakeet-cli &>/dev/null; then
ok "parakeet-cli already available at $(command -v parakeet-cli)"
else
info "Checking for pre-built binary or Docker..."
if command -v docker &>/dev/null; then
ok "Docker available — parakeet.cpp will run via container"
else
die "parakeet-cli not found and Docker not installed. Please either:
- Build parakeet.cpp: https://github.com/mudler/parakeet.cpp
- Or install Docker for the container approach"
fi
fi
step "Downloading Parakeet model"
local model_choice
echo "" >&2
echo " Available models (from HuggingFace mudler/parakeet-cpp-gguf):" >&2
echo " tdt_ctc-110m-q8_0 — 178 MB (fast, good English, smallest) ★" >&2
echo " tdt_ctc-110m-f16 — 268 MB (fast, lossless English)" >&2
echo " tdt-0.6b-v3-f16 — 1.4 GB (multilingual, recommended)" >&2
echo "" >&2
if [[ -n "${STT_MODEL:-}" && -f "${STT_MODEL}" ]]; then
info "Using existing model: $STT_MODEL"
else
read -rp " Choose model [default=tdt_ctc-110m-q8_0]: " model_choice
model_choice="${model_choice:-tdt_ctc-110m-q8_0}"
local model_file="$MODELS_DIR/${model_choice}.gguf"
if [[ -f "$model_file" ]]; then
ok "Model already downloaded: $model_file"
else
info "Downloading ${model_choice}.gguf..."
curl -L --progress-bar \
-o "$model_file" \
"https://huggingface.co/mudler/parakeet-cpp-gguf/resolve/main/${model_choice}.gguf" \
|| die "Failed to download model"
ok "Downloaded: $model_file"
fi
STT_MODEL="$model_file"
echo "STT_MODEL=$model_file" >> "$CONFIG_FILE"
fi
}
# ─── config display ─────────────────────────────────────────────────
cmd_config() {
load_config
banner >&2
echo "" >&2
if [[ -f "$CONFIG_FILE" ]]; then
echo " Saved config ($CONFIG_FILE):" >&2
echo "" >&2
while IFS='=' read -r key val; do
printf " %-16s %s\n" "$key" "$val" >&2
done < "$CONFIG_FILE"
else
echo " (no saved config — run: ./meetings setup)" >&2
fi
echo "" >&2
echo " Effective settings:" >&2
local stt_display="${STT_ENGINE:-$(detect_stt)}"
stt_display="${stt_display:-(not set)}"
echo " STT engine: $stt_display" >&2
echo " STT model: ${STT_MODEL:-(auto-detect)}" >&2
echo " LLM model: $OLLAMA_MODEL" >&2
echo " Ollama host: $OLLAMA_HOST" >&2
echo " Threads: $THREADS" >&2
echo " Language: $LANGUAGE" >&2
echo " Output dir: $OUTPUT_DIR" >&2
echo "" >&2
}
# ─── convert audio ──────────────────────────────────────────────────
convert_audio() {
local input="$1" output="$2"
info "Converting audio to 16kHz mono WAV..."
ffmpeg -y -i "$input" -ar 16000 -ac 1 -c:a pcm_s16le "$output" \
-loglevel warning 2>/dev/null \
|| die "ffmpeg conversion failed for $input"
ok "Audio converted: $(du -h "$output" | cut -f1)"
}
# ─── transcribe with whisper ───────────────────────────────────────
transcribe_whisper() {
local wav_file="$1" model="$2" binary="$3"
info "Transcribing with whisper.cpp..."
local lang_flag=""
[[ "$LANGUAGE" != "auto" ]] && lang_flag="-l $LANGUAGE"
local transcript
transcript=$("$binary" \
-m "$model" \
-f "$wav_file" \
-t "$THREADS" \
$lang_flag \
-nt \
--no-prints \
2>/dev/null) || die "Whisper transcription failed"
echo "$transcript"
}
# ─── transcribe with parakeet ──────────────────────────────────────
transcribe_parakeet() {
local wav_file="$1" model="$2" binary="$3"
info "Transcribing with parakeet.cpp..."
local decoder_flag=""
if echo "$model" | grep -qi "ctc"; then
decoder_flag="--decoder ctc"
elif echo "$model" | grep -qi "tdt"; then
decoder_flag="--decoder tdt"
fi
if [[ -z "$binary" ]] && command -v docker &>/dev/null; then
info "Using Docker for parakeet.cpp..."
docker run --rm \
-v "$model:/model.gguf:ro" \
-v "$wav_file:/audio.wav:ro" \
ghcr.io/mudler/parakeet.cpp-cli:latest \
transcribe --model /model.gguf --input /audio.wav $decoder_flag 2>/dev/null \
|| die "Docker parakeet transcription failed"
else
"$binary" transcribe --model "$model" --input "$wav_file" $decoder_flag 2>/dev/null \
|| die "Parakeet transcription failed"
fi
}
# ─── call Ollama ───────────────────────────────────────────────────
ollama_generate() {
local prompt="$1" system="${2:-}"
local json_payload
if [[ -n "$system" ]]; then
json_payload=$(jq -n \
--arg model "$OLLAMA_MODEL" \
--arg system "$system" \
--arg prompt "$prompt" \
'{model: $model, system: $system, prompt: $prompt, stream: false}')
else
json_payload=$(jq -n \
--arg model "$OLLAMA_MODEL" \
--arg prompt "$prompt" \
'{model: $model, prompt: $prompt, stream: false}')
fi
curl -sf "$OLLAMA_HOST/api/generate" \
-d "$json_payload" 2>/dev/null \
| jq -r '.response // empty' \
|| die "Ollama request failed. Is the server running? (ollama serve)"
}
# ─── main pipeline ──────────────────────────────────────────────────
cmd_process() {
local input_file="$1"
shift || true
# Load config BEFORE flag parsing, so flags can override
load_config
while [[ $# -gt 0 ]]; do
case "$1" in
--stt) STT_ENGINE="$2"; shift 2 ;;
--model) STT_MODEL="$2"; shift 2 ;;
--llm) OLLAMA_MODEL="$2"; shift 2 ;;
--lang) LANGUAGE="$2"; shift 2 ;;
--threads) THREADS="$2"; shift 2 ;;
--output) OUTPUT_DIR="$2"; shift 2 ;;
*) die "Unknown option: $1. Run: ./meetings help" ;;
esac
done
[[ -z "$input_file" ]] && die "Usage: ./meetings <audio_file>"
[[ ! -f "$input_file" ]] && die "File not found: $input_file"
STT_ENGINE="${STT_ENGINE:-$(detect_stt)}"
[[ -z "$STT_ENGINE" ]] && die "No STT engine found. Run: ./meetings setup"
local binary
binary="$(get_stt_binary "$STT_ENGINE")"
[[ -z "$binary" ]] && die "Could not find $STT_ENGINE binary. Run: ./meetings setup"
local model
if [[ -n "${STT_MODEL:-}" ]] && [[ -f "$STT_MODEL" ]]; then
model="$STT_MODEL"
else
model="$(find_stt_model "$STT_ENGINE")" \
|| die "No STT model found. Run: ./meetings setup"
fi
[[ ! -f "$model" ]] && die "Model file not found: $model"
command -v ffmpeg &>/dev/null || die "ffmpeg not found. Install it first."
curl -sf "$OLLAMA_HOST/api/tags" &>/dev/null \
|| die "Ollama server not responding at $OLLAMA_HOST. Run: ollama serve"
# ── create output directory ──
local basename
basename="$(date +%Y-%m-%d_%H%M)_$(basename "${input_file%.*}" | tr ' ' '_')"
local outdir="$OUTPUT_DIR/$basename"
mkdir -p "$outdir"
banner >&2
echo " Input: $input_file" >&2
echo " STT engine: $STT_ENGINE" >&2
echo " STT model: $(basename "$model")" >&2
echo " LLM model: $OLLAMA_MODEL" >&2
echo " Language: $LANGUAGE" >&2
echo " Output: $outdir/" >&2
echo "" >&2
# ── step 1: convert ──
step "Step 1/4 — Converting audio"
local wav_file="$outdir/audio_16k.wav"
convert_audio "$input_file" "$wav_file"
# ── step 2: transcribe ──
step "Step 2/4 — Transcribing with $STT_ENGINE"
local transcript
case "$STT_ENGINE" in
whisper) transcript="$(transcribe_whisper "$wav_file" "$model" "$binary")" ;;
parakeet) transcript="$(transcribe_parakeet "$wav_file" "$model" "$binary")" ;;
*) die "Unknown engine: $STT_ENGINE" ;;
esac
if [[ -z "$transcript" || -z "$(echo "$transcript" | tr -d '[:space:]')" ]]; then
die "Transcription produced empty output"
fi
echo "$transcript" > "$outdir/transcript.txt"
local word_count
word_count=$(echo "$transcript" | wc -w | tr -d ' ')
ok "Transcript: $word_count words"
# ── step 3: summarize ──
step "Step 3/4 — Summarizing ($OLLAMA_MODEL)"
local summarize_prompt
summarize_prompt="Please provide a clear, concise summary of the following meeting transcript.
Structure the summary with:
- **Topic**: What the meeting was about
- **Key Points**: Main topics discussed (3-5 bullet points)
- **Decisions**: Any decisions that were made
- **Open Questions**: Things left unresolved
TRANSCRIPT:
$transcript"
local summary
summary="$(ollama_generate "$summarize_prompt" "You are an expert meeting summarizer. Be concise but thorough. Always use markdown formatting.")"
echo "$summary" > "$outdir/summary.md"
ok "Summary saved"
# ── step 4: action items ──
step "Step 4/4 — Extracting action items ($OLLAMA_MODEL)"
local actions_prompt
actions_prompt="Extract all action items, tasks, and commitments from the following meeting transcript.
For each action item, provide:
- **Who**: The person responsible (or 'Unassigned' if unclear)
- **What**: The specific task or action
- **When**: Any deadline mentioned (or 'No deadline specified')
- **Priority**: High / Medium / Low (based on urgency and context)
Be thorough — capture every commitment, follow-up, and task mentioned or implied.
Format as a numbered list.
TRANSCRIPT:
$transcript"
local actions
actions="$(ollama_generate "$actions_prompt" "You are an expert at extracting action items from meeting notes. Be thorough and specific. Always use markdown formatting.")"
echo "$actions" > "$outdir/action_items.md"
ok "Action items saved"
# ── combine report ──
cat > "$outdir/report.md" <<REPORT
# Meeting Report
**Date:** $(date '+%B %d, %Y at %H:%M')
**Source:** $(basename "$input_file")
**STT:** $STT_ENGINE | **LLM:** $OLLAMA_MODEL
---
## Summary
$summary
---
## Action Items
$actions
---
## Full Transcript
$transcript
REPORT
echo "" >&2
ok "All done! Files saved to: $outdir/" >&2
echo "" >&2
echo " 📄 Report: $outdir/report.md" >&2
echo " 📝 Transcript: $outdir/transcript.txt" >&2
echo " 📋 Summary: $outdir/summary.md" >&2
echo " ✅ Action Items: $outdir/action_items.md" >&2
echo "" >&2
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" >&2
echo "" >&2
head -50 "$outdir/report.md" | tail -35 >&2
echo "" >&2
echo "(showing preview — full report at $outdir/report.md)" >&2
}
# ─── entry point ────────────────────────────────────────────────────
case "${1:-help}" in
setup) shift; cmd_setup ;;
doctor) load_config; cmd_doctor ;;
config) cmd_config ;;
help|--help|-h)
banner >&2
cat <<'HELP'
Usage:
./meetings <audio_file> Run the full pipeline
./meetings setup Install deps + download model
./meetings doctor Check all dependencies
./meetings config Show current configuration
Options (override config):
--stt <whisper|parakeet> STT engine to use
--model <path> Path to GGUF/GGML model file
--llm <ollama_model> Ollama model for summarization
--lang <code> Language code (default: en, auto for whisper)
--threads <N> Number of threads for STT
--output <dir> Output directory (default: .)
Environment variables:
MEETINGS_DIR Config & models directory (default: ~/.meetings)
MEETINGS_STT STT engine (whisper/parakeet)
MEETINGS_STT_MODEL Path to model file
MEETINGS_LLM Ollama model name (default: llama3.1:8b)
MEETINGS_THREADS Thread count (default: 4)
MEETINGS_LANG Language code (default: en)
MEETINGS_OUTPUT Output directory (default: .)
Examples:
# First time — install everything
./meetings setup
# Process a meeting recording
./meetings recording.mp3
# Use a different LLM model
./meetings meeting.wav --llm llama3.1:8b
# Use parakeet with multilingual model
./meetings call.wav --stt parakeet --lang auto
# Specify output directory
./meetings interview.m4a --output ./reports
HELP
;;
*)
cmd_process "$@"
;;
esac