Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 11 additions & 2 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,21 @@ GROQ_API_KEY=
OPENAI_API_KEY=

# ── Optional overrides ────────────────────────────────────────────────────────
# RECMP3_PROVIDER=groq
# RECMP3_PROVIDER=groq # groq | openai | local-whisper
# RECMP3_MODEL=whisper-large-v3-turbo
# RECMP3_SOURCE=default
# RECMP3_LANG=
# RECMP3_OUTDIR=
# RECMP3_SKIP_INIT=1

# ── Local Whisper (no upload) ─────────────────────────────────────────────────
# RECMP3_WHISPER_BIN=/usr/local/bin/whisper-cli
# RECMP3_WHISPER_MODEL=/models/ggml-base.en.bin

# ── Agent / scripting mode ────────────────────────────────────────────────────
# RECMP3_JSON=1 # always emit JSON envelopes
# RECMP3_YES=1 # skip all prompts (consent, setup)
# RECMP3_QUIET=1 # suppress stderr chatter
# RECMP3_SKIP_CONSENT=1 # legacy alias for skipping upload consent
# RECMP3_PLAIN=1

# ── Dev / Debug ───────────────────────────────────────────────────────────────
Expand Down
17 changes: 17 additions & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# Enforce LF line endings for all text files on all platforms.
# Prevents Biome from failing the format check on Windows (CRLF vs LF diff).
* text=auto eol=lf

# Binary files — never touch line endings
*.png binary
*.jpg binary
*.jpeg binary
*.gif binary
*.ico binary
*.wav binary
*.mp3 binary
*.ogg binary
*.flac binary
*.m4a binary
*.webm binary
*.node binary
2 changes: 2 additions & 0 deletions .github/CODEOWNERS
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
# Default owner for everything in this repo
* @aedneth
23 changes: 16 additions & 7 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,20 +2,26 @@ name: CI

on:
push:
branches: [main]
branches: [master]
pull_request:
branches: [main]
branches: [master]

jobs:
build:
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest, macos-latest, windows-latest]
node: ['20', '22']

runs-on: ${{ matrix.os }}

steps:
- uses: actions/checkout@v4

- uses: actions/setup-node@v4
with:
node-version: '20'
node-version: ${{ matrix.node }}
cache: 'npm'

- name: Install dependencies
Expand All @@ -24,8 +30,11 @@ jobs:
- name: Type check
run: npm run typecheck

- name: Build
run: npm run build

- name: Lint
run: npm run lint

- name: Test
run: npm test

- name: Build
run: npm run build
38 changes: 38 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
name: Release

on:
push:
tags:
- 'v*'

jobs:
publish:
runs-on: ubuntu-latest
permissions:
contents: read
id-token: write
steps:
- uses: actions/checkout@v4

- uses: actions/setup-node@v4
with:
node-version: '20'
registry-url: 'https://registry.npmjs.org'
cache: 'npm'

- name: Install dependencies
run: npm ci

- name: Type check
run: npm run typecheck

- name: Test
run: npm test

- name: Build
run: npm run build

- name: Publish to npm
run: npm publish --provenance --access public
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -28,3 +28,6 @@ coverage/

# Per-project CKIS second brain (personal tooling — not part of the public repo)
.brain/

# Private architecture planning — strip before public merges
docs/planning/
11 changes: 9 additions & 2 deletions AI-RULES.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ Rules for AI agents (Claude Code, Codex, Gemini CLI, etc.) working in this repo.

- **ESM only.** `"type": "module"` in package.json. All imports must use `.js` extensions. No CommonJS `require()`.
- **Node.js 20+.** Use native `fetch`, `FormData`, `Blob`, `AbortSignal.timeout()` — no polyfills.
- **No new runtime dependencies** without explicit approval. Current dep count is intentionally minimal.
- **No new runtime dependencies** without explicit approval. Current dep count is intentionally minimal. (`@modelcontextprotocol/sdk` was approved for the MCP server; `keytar` is an optional, lazily-loaded native dep.)
- **tsup is the bundler.** Do not introduce webpack, rollup, esbuild directly, or vite.
- **No `any`.** TypeScript strict mode is on. Use `unknown` and narrow — do not cast to `any`.

Expand All @@ -20,7 +20,14 @@ Platform audio backends live in `src/audio/`. `getAudioFactory()` in `src/audio/

## Error handling

All user-facing errors go through the typed error classes in `src/errors.ts`. Each class has a specific `exitCode`. Don't `process.exit()` directly from business logic — throw the appropriate error class and let `handleError()` in `src/index.ts` catch it.
All user-facing errors go through the typed error classes in `src/errors.ts`. Each class has a specific `exitCode` (see the exported `ExitCode` map). Don't `process.exit()` directly from business logic — throw the appropriate error class and let `handleError()` in `src/index.ts` catch it.

## Agent-native layer (do not break)

- Every command receives an `AgentContext` (`src/agent/context.ts`) and emits results via `ctx.ok(command, payload, humanRender?)` — never write results straight to stdout. Human-only chatter goes through `ctx.note()` (stderr, suppressed by `--quiet`).
- The JSON envelope shape in `src/agent/output.ts` and per-command `data` payloads are a public contract. Bump `SCHEMA_VERSION` on any breaking change.
- `src/agent/manifest.ts` is the single source of truth for the command/tool surface; the MCP server (`src/agent/mcp.ts`) derives tools from it. Keep them in sync.
- Local providers must not trigger upload consent — gate `ensureUploadConsent()` behind `providerUploads()`.

## Security rules

Expand Down
26 changes: 26 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,32 @@

All notable changes are documented here. This project follows [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) and [Semantic Versioning](https://semver.org/).

## [0.2.0] — 2026-05-31

### Added

- **Agent-native layer** — every command works fully non-interactively for AI agents and scripts:
- Global `--json` flag (and `RECMP3_JSON=1`) emits a stable, versioned JSON envelope on stdout.
- Global `--yes` (and `RECMP3_YES=1`) skips all prompts; global `--quiet` and `--no-color`.
- Documented, deterministic POSIX exit-code contract (0/1/2/3/4/5/6/7/130).
- `stdin`/`stdout` composability: `transcribe -` reads audio from stdin, `prompt -` reads text from stdin.
- `recmp3 manifest` — discoverable command/tool manifest (`--json` for machine form).
- `recmp3 mcp` — Model Context Protocol server over stdio exposing `recmp3_*` tools.
- Headless recording — `recmp3 record --duration <s>` / `--no-tui` capture without the Ink TUI.
- Non-interactive `recmp3 config init` (flag-driven) and new `recmp3 config set-key` (OS keychain).
- **Local Whisper backend** — `--provider local-whisper` runs a whisper.cpp binary with no upload.
- **OS keychain key storage** via `keytar` (env vars still take precedence; graceful fallback).
- Expanded vitest + msw test suite; CI now runs tests on an OS matrix (Linux/macOS/Windows × Node 20/22).
- `release.yml` workflow to publish to npm on `v*` tags; `docs/AGENTS.md` agent-integration guide.

### Fixed

- `package.json` license corrected from `MIT` to `AGPL-3.0-or-later` to match the LICENSE file.

### Changed

- `getApiKey` and provider construction are now async (keychain-aware). Internal only; CLI behavior unchanged.

## [0.1.0] — 2026-05-10

### Added
Expand Down
66 changes: 59 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,11 @@

[![CI](https://github.com/aedneth/recmp3-cli/actions/workflows/ci.yml/badge.svg)](https://github.com/aedneth/recmp3-cli/actions/workflows/ci.yml)
[![License: AGPL v3](https://img.shields.io/badge/License-AGPL_v3-blue.svg)](https://www.gnu.org/licenses/agpl-3.0)
[![version](https://img.shields.io/badge/version-0.1.0-blue)](https://github.com/aedneth/recmp3-cli/releases)
[![version](https://img.shields.io/badge/version-0.2.0-blue)](https://github.com/aedneth/recmp3-cli/releases)

Record audio from any terminal, transcribe with Groq Whisper, get developer-ready output.
A first-class tool for **both humans and terminal AI agents** — every interactive flow has a
fully non-interactive, JSON-emitting equivalent, plus a built-in MCP server.

```
recmp3 record --name "my standup"
Expand All @@ -17,6 +19,8 @@ recmp3 prompt standup.wav --template claude-code | pbcopy
- **Transcribes** via Groq `whisper-large-v3-turbo` (or OpenAI Whisper)
- **Formats** output with 7 developer templates: `claude-code`, `prd`, `bug`, `meeting-notes`, `todo`, `commit-message`, `raw`
- **Cross-platform:** Linux (PulseAudio/PipeWire), macOS (AVFoundation), Windows (DirectShow)
- **Agent-native:** global `--json` envelopes, `--yes`, deterministic exit codes, stdin/stdout piping, a discoverable `manifest`, and an MCP server — see [Agent & scripting use](#agent--scripting-use)
- **Local option:** `--provider local-whisper` transcribes on-device via whisper.cpp (no upload)

## Requirements

Expand Down Expand Up @@ -105,10 +109,52 @@ Run 8 system checks: Node version, platform support, ffmpeg version, audio backe
### `recmp3 config`

```bash
recmp3 config init # Interactive setup wizard
recmp3 config init # Setup (interactive, or flag-driven: --provider/--lang/--outdir/--key)
recmp3 config show # Display current config (API key redacted)
recmp3 config path # Print config file path
recmp3 config set <k> <v> # Set a config key
recmp3 config set <k> <v> # Set a config key
recmp3 config set-key groq --key gsk_... # Store an API key in the OS keychain
```

## Agent & scripting use

Every command is usable by AI agents (Claude Code, Codex, Gemini CLI, …) and shell scripts
with no TTY and no prompts. See [`docs/AGENTS.md`](docs/AGENTS.md) for the full reference.

```bash
# Stable JSON envelope on stdout; chatter on stderr
recmp3 transcribe meeting.wav --json --yes | jq -r .data.text

# Compose via pipes: transcribe → template
recmp3 transcribe meeting.wav --json --yes | jq -r .data.text | recmp3 prompt - --template prd

# Headless recording (no Ink TUI)
recmp3 record --duration 5 --json --yes

# Discover the command/tool surface
recmp3 manifest --json
```

**Exit codes:** `0` success · `1` unknown · `2` config · `3` audio/ffmpeg · `4` transcription ·
`5` network · `6` local-whisper · `7` input · `130` user abort.

### MCP server

`recmp3` ships a Model Context Protocol server over stdio. Register it with any MCP client:

```jsonc
{ "mcpServers": { "recmp3": { "command": "recmp3", "args": ["mcp"] } } }
```

Tools: `recmp3_transcribe`, `recmp3_prompt`, `recmp3_sources`, `recmp3_doctor`,
`recmp3_config_show`, `recmp3_record`, `recmp3_manifest`.

### Local, no-upload transcription

```bash
export RECMP3_WHISPER_BIN=/usr/local/bin/whisper-cli # whisper.cpp binary
export RECMP3_WHISPER_MODEL=/models/ggml-base.en.bin
recmp3 transcribe clip.wav --provider local-whisper --json
```

## Use Cases
Expand Down Expand Up @@ -146,14 +192,20 @@ Environment variables override config file values:
| `RECMP3_FFMPEG_PATH` | Path to ffmpeg binary |
| `RECMP3_OUTDIR` | Default recordings output directory |
| `RECMP3_LANG` | Default language hint (e.g. `es`, `en`) |
| `RECMP3_WHISPER_BIN` | Path to a whisper.cpp binary (for `local-whisper`) |
| `RECMP3_WHISPER_MODEL` | Path to a GGML model file (for `local-whisper`) |
| `RECMP3_JSON` | `1` to always emit JSON envelopes |
| `RECMP3_YES` | `1` to skip all prompts |
| `RECMP3_QUIET` | `1` to suppress stderr chatter |
| `RECMP3_SKIP_CONSENT` | `1` to skip upload consent prompt |

## Providers

| Provider | Default model | Max file size |
|---|---|---|
| Groq | `whisper-large-v3-turbo` | 25 MB |
| OpenAI | `whisper-1` | 25 MB |
| Provider | Default model | Max file size | Upload |
|---|---|---|---|
| Groq | `whisper-large-v3-turbo` | 25 MB | yes |
| OpenAI | `whisper-1` | 25 MB | yes |
| local-whisper | whisper.cpp GGML model | unlimited | **no (on-device)** |

Audio is captured as WAV 16kHz mono (~1 MB/min), so the 25 MB limit covers ~25 minutes per recording. Longer recordings are chunked automatically.

Expand Down
22 changes: 14 additions & 8 deletions TODO.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,15 +29,21 @@
- [x] CKIS documentation: `02-projects/recmp3-cli/_overview.md` created
- [x] CKIS documentation: `04-resources/tools/pop-os-audio-mp3-ffmpeg.md` superseded

## v0.2.0
## v0.2.0 — agent-native retrofit

- [ ] OS keychain storage via `keytar` (API keys currently env-var only)
- [ ] `recmp3 config init` — store key in keychain instead of instructing user to set env var
- [ ] Local Whisper backend (no upload) — add `LocalWhisperProvider` class
- [ ] `recmp3 sources` — auto-detect physical mic vs `default` virtual device (mark physical as recommended)
- [ ] Integration test: full record → transcribe → prompt pipeline with mock ffmpeg
- [ ] `recmp3 record --watch` — continuous recording mode (auto-split by silence)
- [ ] Publish to npm
- [x] Agent-native layer: global `--json`/`--yes`/`--quiet`, stable JSON envelope, exit-code contract
- [x] stdin/stdout composability (`transcribe -`, `prompt -`)
- [x] `recmp3 manifest` (discoverable command/tool surface)
- [x] `recmp3 mcp` (Model Context Protocol stdio server)
- [x] Headless `recmp3 record --duration` / `--no-tui`
- [x] OS keychain storage via `keytar` + `recmp3 config set-key`
- [x] Non-interactive `recmp3 config init` (flag-driven)
- [x] Local Whisper backend (`LocalWhisperProvider`, whisper.cpp, no upload)
- [x] Expanded vitest + msw suite; CI test step + OS matrix; npm release workflow
- [x] License fixed to AGPL-3.0-or-later in package.json
- [ ] `recmp3 sources` — auto-detect physical mic vs `default` virtual device (moved to v0.3.0)
- [ ] `recmp3 record --watch` — continuous recording mode (moved to v0.3.0)
- [ ] Publish to npm (run the release workflow by tagging `v0.2.0`)

## Backlog / ideas

Expand Down
6 changes: 5 additions & 1 deletion biome.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,17 @@
"recommended": true,
"suspicious": {
"noExplicitAny": "warn"
},
"style": {
"noNonNullAssertion": "warn"
}
}
},
"formatter": {
"enabled": true,
"indentStyle": "space",
"indentWidth": 2
"indentWidth": 2,
"lineEnding": "lf"
},
"javascript": {
"formatter": {
Expand Down
Loading
Loading