Skip to content

Add a CLI (and agent tool contract) sharing the web app's engine - #60

Open
moamen1358 wants to merge 3 commits into
averygan:mainfrom
moamen1358:add-cli
Open

Add a CLI (and agent tool contract) sharing the web app's engine#60
moamen1358 wants to merge 3 commits into
averygan:mainfrom
moamen1358:add-cli

Conversation

@moamen1358

Copy link
Copy Markdown

What this adds

A command-line interface for ReClip that shares the exact engine behind the web UI - so scripts, cron jobs, and AI agents can use ReClip, not just a person in a browser:

pipx install git+https://github.com/averygan/reclip

reclip info "<url>" --json          # title, duration, available qualities
reclip download "<url>" --quality 1080 -o ~/Videos
reclip download "<url>" --audio     # MP3
reclip playlist "<playlist-url>"    # expand to video URLs
reclip transcript "<url>"           # subtitles as plain text

Human-readable output by default, --json for machine consumption, exit 0/1 contract, per-URL results for bulk downloads, --timeout override.

How it's structured (3 commits)

  1. core.py - all yt-dlp logic extracted from the Flask handlers into one dependency-free module (probe, expand_playlist, download, transcript). Every expected failure (bad URL, timeout, unreadable yt-dlp output, unwritable directory, missing binary) is normalized to a single ReclipError. app.py keeps its exact routes, JSON shapes, and jobs/polling model - it just delegates now. The web UI behavior is unchanged.
  2. cli.py + pyproject.toml - the reclip command (console script cli:main). Runs its bundled yt-dlp via python -m yt_dlp, so a pipx install is fully self-contained. Runtime deps stay exactly flask + yt-dlp.
  3. skill/SKILL.md + README - a machine-readable tool contract (commands, output shapes, exit codes) that AI agents can consume, plus a README CLI section.

Hardening included along the way

The extraction fixed several latent issues that apply to the web app too: glob-metacharacter output paths no longer break file discovery; filename collisions get an atomic (n) suffix instead of silently overwriting; format entries without format_id or with null formats/entries no longer crash; URLs are passed after -- so a crafted "URL" can't be parsed as a yt-dlp option (same concern as PR #58); multi-video JSON output parses correctly (extends the fix in 1d161d1).

Verification

  • 62 unit tests (engine parsing, download selection/sanitization, VTT-to-text, all Flask routes, full CLI contract), all passing; the suite mocks subprocess so it runs offline.
  • Live end-to-end tested against real YouTube: downloads (ffprobe-verified MP4/MP3), quality caps, playlists, transcripts (manual + auto captions), bulk with partial failure, error paths, plus the full web-app HTTP flow to confirm zero behavior change.
  • An independent 48-scenario functional test pass (every flag combination, rejection path, and edge case - unwritable dirs, glob-metachar dirs, path-traversal attempts in --name) found no crashes, no tracebacks, no data loss.

Deliberate scope limits

  • Flat modules (core.py, cli.py beside app.py) to keep this diff reviewable; documented as pipx-only install.
  • transcript is subtitles-only (no Whisper) to avoid new dependencies.
  • No changes to the web UI, Docker behavior, or existing quirks (in-memory jobs dict etc.) - kept out of scope on purpose.

Disclosure

This was built with AI assistance (Claude Code driving the implementation through an automated review/test pipeline, with every change human-reviewed and live-tested). Happy to adjust anything - naming, layout, scope - to fit the project's direction.

🤖 Generated with Claude Code

moamen1358 added 3 commits July 10, 2026 17:04
Move probe/playlist/download/subtitle logic out of the Flask handlers
into core.py - pure functions, no Flask, no threads, every expected
failure normalized to a single ReclipError. app.py keeps the exact same
routes, JSON shapes, and jobs/polling model, now delegating to core.
Adds unit tests for the engine and the routes.
New 'reclip' console command sharing the same core engine as the web
app: download (MP4/MP3, --quality, --timeout, bulk with per-URL
results), info, playlist, and transcript (subtitles as text). Human
readable by default, --json for machine output, exit 0/1 contract.
Packaged via pyproject.toml (pipx-installable, runs its bundled yt-dlp
via python -m yt_dlp so no PATH dependency); deps stay flask + yt-dlp.
Docker comments updated to match the module-invocation behavior.
skill/SKILL.md documents the CLI as a tool contract for AI agents
(commands, JSON output shapes, exit codes); README gains a CLI section.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant