Sergey Kopanev: you sleep — agents ship

Go Back
No Bullshit Pipeline · Part 10

Local CLI Processing. No UI. Just Output.


I said the series was done.

Then I kept opening the app just to run a pipeline on a file I already had.

Click. Wait. Click. Wait. Copy.

Five steps for a job that should be one command.

What NBP Is

NBP is a macOS app that records audio and runs it through a local processing pipeline. Whisper transcribes on-device. LLM steps extract, summarize, deliver. Everything stays on your machine. No server. No cloud. No backend.

I built it because I did not want meeting data leaving my disk. The first article explains why.

The desktop app handles recording and configuration. That part works.

The problem was everything after recording.

The GUI Tax

I had 40+ recordings from two weeks of calls. I needed action items extracted from all of them.

The app does this. One recording at a time. Open. Select pipeline. Run. Wait. Copy output. Next.

Forty times.

I tried scripting around it. Wrote a shell wrapper that called the pipeline logic directly from Rust. Worked for one file. Broke on batch paths. Config loading assumed the GUI context.

Then I tried exporting the pipeline as a library crate and calling it from a minimal binary. That worked on the third attempt.

The processing engine didn’t need a window. It needed arguments.

What the CLI Does

nbp-cli takes a file and runs pipeline steps against it. No window. No menubar icon. Flags and stdout.

nbp-cli process --input ~/nbp-data/8f3d2a10/transcript.md \
  --pipeline summarize

Output goes to your terminal. Pipe it wherever.

nbp-cli process --input ./transcript.md \
  --pipeline summarize > summary.md

The file existed. The pipeline ran. The output arrived. No app launch. No click.

Same Privacy. Zero Network.

The CLI makes the same network calls as the desktop app: zero, unless you configured an external LLM.

nbp-cli transcribe --input ./meeting.ogg --model large-v3

Local Whisper. Model file on disk. Audio stays on disk. Transcript stays on disk.

Run it offline. Run it in a Faraday cage. Same result.

The privacy model is architectural. The CLI inherits it by default.

Forty Recordings. One Command.

This is what made the CLI worth building.

for f in ~/nbp-data/*/transcript.md; do
  nbp-cli process --input "$f" --pipeline extract-actions \
    >> all-actions.md
done

Forty meetings. Every action item. One file. Under three minutes.

The GUI version of this job took me an afternoon.

find ~/nbp-data -name "*.ogg" -mtime -7 | while read f; do
  nbp-cli transcribe --input "$f" --model base
  dir=$(dirname "$f")
  nbp-cli process --input "$dir/transcript.md" --pipeline summarize
done

Last week’s recordings. Transcribed and summarized. Unattended.

Runs While You Sleep

The CLI is a cron-friendly binary.

0 6 * * * /usr/local/bin/nbp-cli batch \
  --dir ~/nbp-data \
  --since yesterday \
  --pipeline summarize \
  --output ~/summaries/

You wake up. Yesterday’s meetings are summarized. You didn’t open anything.

Combine it with folder watching:

fswatch -o ~/Downloads/*.ogg | while read event; do
  latest=$(ls -t ~/Downloads/*.ogg | head -1)
  nbp-cli transcribe --input "$latest" --model base
done

Drop an audio file in Downloads. Transcript appears. No manual step.

Unix Composability

nbp-cli transcribe --input call.ogg --model base | \
  nbp-cli process --stdin --pipeline summarize | \
  curl -X POST -d @- "$SLACK_WEBHOOK"

One line. Three steps. Audio to Slack summary.

The desktop app can do this too — with connectors. The CLI version is scriptable. Debuggable. Versionable in a Makefile.

nbp-cli process --input transcript.md \
  --pipeline extract-actions-json | \
  jq -c '.[]' | \
  while read item; do
    curl -s -X POST https://api.linear.app/graphql \
      -H "Authorization: $LINEAR_KEY" \
      -d "$item"
  done

The pipeline is a shell script now. You already know how to extend it.

What the CLI Doesn’t Do

No recording. Microphone capture requires macOS audio permissions tied to a GUI app bundle. The CLI processes. It doesn’t capture.

No real-time display. No waveform. No progress bar. It runs, prints output, exits.

No pipeline editor. Pipelines live in the same config files the desktop app uses. The CLI reads them. It doesn’t modify them.

Desktop app: capture and configuration.

CLI: processing and automation.

They share config. They share data. They don’t compete.

The Trade-Off

You lose visual feedback. No live transcript scrolling. No click-to-rerun.

You gain automation surface. Batch. Cron. Pipes. Scripts.

The desktop app made recording simple. The CLI made everything after recording simple.

Takeaway

A GUI optimizes for interaction. A CLI optimizes for output.

Same pipeline. Same privacy. Same config. One runs when you click. The other runs when you tell it to.

Own the pipeline. Then script it.


This concludes the No Bullshit Pipeline series.