Skip to content

Add Makefile targets, cloud deployment workflow, and local file endpoints#236

Open
jpatrickfarrell wants to merge 22 commits into
stephengpope:mainfrom
jpatrickfarrell:pfarrell/updates
Open

Add Makefile targets, cloud deployment workflow, and local file endpoints#236
jpatrickfarrell wants to merge 22 commits into
stephengpope:mainfrom
jpatrickfarrell:pfarrell/updates

Conversation

@jpatrickfarrell
Copy link
Copy Markdown

  • Added complete Makefile build system with make cloud-setup and make cloud-deploy for one-command GCP Cloud Run deployment
  • Added Cloud Build support as an alternative to local Docker builds (fixes architecture mismatch on Apple Silicon)
  • Added /v1/files/upload and /v1/files/content endpoints for local file upload and retrieval
  • Added Python CLI tool (tools/nca.py) with auto-detection of local files vs URLs
  • Added local file I/O mode (make up-local) for processing without cloud storage
  • Added .gitignore entries for Python bytecode

Test plan

  • make cloud-setup enables APIs and configures IAM on a fresh GCP project
  • make cloud-deploy builds on Cloud Build and deploys to Cloud Run
  • make test URL=https://... returns healthy against deployed service
  • make up-local starts container with volume mounts for local file processing
  • /v1/files/upload accepts multipart file and returns storage URL
  • CLI python3 tools/nca.py transcribe --file ./local.mp4 uploads and transcribes

…Makefile quick-start section, and created comprehensive docs/deploy/DEPLOY_GCP.md.

  📁 FILES CREATED/MODIFIED:

  - Makefile — 12 targets: build, run, stop, clean, auth, repo, push, deploy, logs, describe, test, help
  - docs/deploy/DEPLOY_GCP.md — Full 9-step guide covering project setup, APIs, service accounts, storage buckets, Artifact Registry, deployment, Cloud Run Jobs, monitoring,
  cost optimization, and troubleshooting
  - README.md — Added "Quick Start with Makefile" section and updated GCP installation link to point to new guide
…it API.

  📁 FILES CREATED (14 total):

  Python CLI:
  - tools/nca.py — Single-file CLI with 18 subcommands covering all API endpoints. Zero external dependencies (uses only urllib and argparse). Config via NCA_API_URL +
  NCA_API_KEY env vars.

  Claude Skill:
  - tools/claude-skill/SKILL.md — Skill definition with trigger routing
  - tools/claude-skill/Workflows/Transcribe.md — Transcribe/translate media
  - tools/claude-skill/Workflows/ConvertMedia.md — Format conversion
  - tools/claude-skill/Workflows/CaptionVideo.md — Auto-caption videos
  - tools/claude-skill/Workflows/VideoOps.md — Trim, cut, split, concat, thumbnail
  - tools/claude-skill/Workflows/Screenshot.md — Webpage screenshots
  - tools/claude-skill/Workflows/Metadata.md — Media file info
  - tools/claude-skill/Workflows/Download.md — Download via yt-dlp
  - tools/claude-skill/Workflows/Silence.md — Silence detection
  - tools/claude-skill/Workflows/FFmpeg.md — Custom FFmpeg pipelines
  - tools/claude-skill/Workflows/Upload.md — S3/GCP uploads
  - tools/claude-skill/Workflows/TestConnect.md — Connectivity testing

  Installer:
  - tools/install-skill.sh — Symlinks skill to ~/.claude/skills/NCAToolkit/, prompts for env vars, adds them to shell profile
…setup command that validates credentials before saving, a config command to inspect settings, and updated the install script + skill to use the new system.

  📁 CHANGES:

  Modified:
  - tools/nca.py — Added read_config_file(), write_config_file(), updated get_config() with config file fallback, added setup and config commands, supports --profile for
  multiple environments
  - tools/install-skill.sh — Now runs python3 nca.py setup instead of manual env var prompting
  - tools/claude-skill/SKILL.md — Updated prerequisites and routing table

  Created:
  - tools/claude-skill/Workflows/Setup.md — Auth/setup workflow for the skill
  - tools/config.example — Example config file showing the format

  Config flow:
  1. python3 nca.py setup → prompts for URL + key → validates against API → saves to ~/.nca-toolkit/config (600 perms)
  2. All commands check: env vars first → config file second
  3. Multiple profiles supported: python3 nca.py setup --profile staging
…e down, added make setup for API credentials, and updated help menu with a new SETUP section.
…ress Enter to accept it when running locally. When you deploy to Cloud Run, re-run make setup and

   enter the Cloud Run URL instead.
…Ls for local vs Cloud Run, and provides a python3 -c "import secrets; ..." command to

  generate an API key if they don't have one yet.
  - make setup — Generates .env from .env.example with an auto-generated API key. This is for setting up the server to run.
  - make connect — Connects the CLI tools to a running API instance (local or Cloud Run). Saves credentials to ~/.nca-toolkit/config.

  The flow is now: make setup → make up → make connect → use the API.
  1. CLI was sending Content-Type: application/json on GET requests with no body — Flask sees the JSON content type, tries to parse the empty body, and returns 400. Fixed
  api_request() to only add Content-Type when there's a payload.
  2. Same issue in the connect validation request — removed the unnecessary header.
  3. make test was using POST instead of GET — the /v1/toolkit/test route only accepts GET.

  Try python3 tools/nca.py test again now.
…d, 0 route files touched.

  📁 CAPTURE: The key insight — file:// URIs pass jsonschema format: uri validation (because FormatChecker isn't used), so zero schema/route changes were needed.
  download_file() and upload_file() were the only two intervention points.

  ➡️  NEXT:
  1. Run make setup to generate .env (if not already done)
  2. Run make up-local to start with volume mounts
  3. Place a test file in ./local/input/
  4. Test: python3 tools/nca.py metadata --file ./local/input/test.mp4
  5. Check ./local/output/ for results

  Files changed:

  ┌─────────────────────────────┬───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
  │            File             │                                                         What Changed                                                          │
  ├─────────────────────────────┼───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┤
  │ Dockerfile                  │ Added /data/input and /data/output dirs with appuser ownership                                                                │
  ├─────────────────────────────┼───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┤
  │ services/file_management.py │ download_file() handles file:// URIs — copies local file instead of HTTP download, with path traversal protection             │
  ├─────────────────────────────┼───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┤
  │ services/cloud_storage.py   │ Added LocalStorageProvider that saves to /data/output; get_storage_provider() falls back to local instead of raising          │
  ├─────────────────────────────┼───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┤
  │ config.py                   │ Added LOCAL_OUTPUT_PATH env var                                                                                               │
  ├─────────────────────────────┼───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┤
  │ .env.example                │ Documented LOCAL_OUTPUT_PATH                                                                                                  │
  ├─────────────────────────────┼───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┤
  │ Makefile                    │ Added up-local target with -v mounts for ./local/input and ./local/output                                                     │
  ├─────────────────────────────┼───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┤
  │ tools/nca.py                │ Added --file/-f flag to 11 commands; translates to file:///data/input/<basename>; translates file:// output paths for display │
  ├─────────────────────────────┼───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┤
  │ .gitignore                  │ Added local/ to prevent committing user files                                                                                 │
…s remote API.

  What was built:

  ┌─────────────────────────────┬────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
  │            File             │                                                           Change                                                           │
  ├─────────────────────────────┼────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┤
  │ routes/v1/files/upload.py   │ New — accepts multipart POST /v1/files/upload, saves to configured storage, returns URL                                    │
  ├─────────────────────────────┼────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┤
  │ routes/v1/files/__init__.py │ New — package init                                                                                                         │
  ├─────────────────────────────┼────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┤
  │ tools/nca.py                │ Modified — resolve_file() auto-detects: local API → file:// URI, remote API → uploads via multipart then returns cloud URL │
  ├─────────────────────────────┼────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┤
  │ Makefile                    │ Modified — added --use-http2 to deploy command                                                                             │
  └─────────────────────────────┴────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘

  How it works end-to-end on Cloud Run:

  python3 tools/nca.py transcribe --file ./my-video.mp4
    ↓ CLI detects remote API (not localhost)
    Step 1: POST /v1/files/upload (multipart) → "https://bucket.../abc123.mp4"
    Step 2: POST /v1/media/transcribe {"media_url": "https://bucket.../abc123.mp4"}
    ↓ Returns transcription result

  On the 32 MB limit:
  - Cloud Run has a hard 32 MB request body limit — cannot be increased via configuration
  - Adding --use-http2 to the deploy command may bypass this (some users report success, not officially documented)
  - For files > 32 MB, you'll need Option C (signed URLs) where the CLI uploads directly to the bucket bypassing Cloud Run entirely. The endpoint architecture is designed to
  make this easy to add later
  Updated workflow files (7):
  - ConvertMedia.md — Added --file option for both convert and convert-mp3, local file examples
  - CaptionVideo.md — Added --file option with local file examples, mutual exclusivity note
  - VideoOps.md — Added --file for trim/cut/split/thumbnail and --files for concat, with local examples for all 5 commands
  - Metadata.md — Added --file option and local file example
  - Silence.md — Added --file option and local file example
  - Upload.md — Added new "File Upload (for processing)" section documenting the manual upload command and 32 MB Cloud Run limit
  - FFmpeg.md — Added local file guidance and file:///data/input/ volume mount example

  Previously updated (2):
  - SKILL.md — Local File Support section, Input Options table, updated examples
  - Transcribe.md — --file option with local file examples

  No changes needed (3):
  - Screenshot.md — Takes webpage URLs, not media files
  - Download.md — Downloads from URLs, no local file input needed
  - Setup.md / TestConnect.md — Configuration commands, not media processing
  Server-side fix (1 file):
  - services/file_management.py — Added /data/output to ALLOWED_LOCAL_DIRS so the upload-then-process flow works on localhost

  CLI overhaul (1 file, major rewrite):
  - tools/nca.py — Key changes:
    - resolve_file() — Simplified to always upload via multipart POST (removed is_local_api() branch). Any file path on disk now works.
    - download_output() — New function that downloads output files (HTTP URLs) or copies from ./local/output/ (file:// URLs)
    - extract_urls() — Recursively finds URLs in API responses for auto-download
    - handle_output() — Replaces print_result(). Text commands print text directly; file commands download and print local path
    - poll_job() — New function for polling async job status (addresses your concern about long-running tasks)
    - Global --json flag — Full JSON response for any command
    - Global --output-dir / -o flag — Control where output files are saved
    - Removed translate_output_paths() and print_result() (dead code)
    - Updated all --file help text: "uploaded automatically" instead of "must be in ./local/input/"

  Documentation (9 files):
  - SKILL.md — Updated with new workflow, global flags, generic file paths
  - Transcribe.md, ConvertMedia.md, CaptionVideo.md, VideoOps.md, Metadata.md, Silence.md, Upload.md, FFmpeg.md — All updated to use generic paths (~/videos/, ~/recordings/)
  and document output download behavior

  Regarding your concern about long-running tasks: The CLI currently blocks synchronously (10-minute timeout). I added a poll_job() function that's ready to use — it can poll
   the job status endpoint until completion. For the Claude Code workflow specifically, the synchronous blocking approach actually works well: Claude Code will wait for the
  CLI to finish and read the stdout. The 10-minute timeout should cover most transcription/conversion jobs. If you want, we can add a --poll flag that submits with a webhook
  and polls, which would be better for very long tasks — but that can be a follow-up.
…mands for polling.

  Here's how the workflow works now, Patrick:

  Synchronous (default — fine for short tasks):
  python3 tools/nca.py transcribe --file ~/audio.mp3
  # Blocks, prints transcription text when done

  Background (for long-running tasks):
  # Step 1: Kick it off — returns immediately with a job ID
  python3 tools/nca.py transcribe --file ~/audio.mp3 --bg
  # Output: 550e8400-e29b-41d4-a716-446655440000

  # Step 2: Check if it's done
  python3 tools/nca.py status 550e8400-e29b-41d4-a716-446655440000
  # Output: running  (or: queued, done, failed)

  # Step 3: Wait for completion and get the result
  python3 tools/nca.py wait 550e8400-e29b-41d4-a716-446655440000
  # Polls every 5s, then prints the transcription text when done

  For Claude Code, the typical pattern would be:
  1. Run with --bg, capture the job ID from stdout
  2. Do other work
  3. Run wait <job_id> when ready for the result

  The --bg flag works on every command (transcribe, convert, caption, etc.) — zero server-side changes needed.
…erve Docker build cache for Whisper model and Playwright
…transcribe output file support.

  Changes made:
  1. upload-s3 and upload-gcp — added parents=[shared] so --json, -o, --bg work on all commands
  2. --output-file flag on transcribe — writes transcription text (and SRT if requested) to a file, prints the path

  Usage:
  python3 tools/nca.py transcribe --file ~/audio.mp3 --output-file transcript.txt
  python3 tools/nca.py transcribe --file ~/audio.mp3 --srt --output-file transcript.txt
  python3 tools/nca.py transcribe --file ~/audio.mp3 --json   # now works after subcommand
…flow docs + SKILL.md with --json, --bg, --output-file flags.

  Changes:
  - tools/nca.py — cmd_transcribe: moved --output-file check before --json early return. When both flags are set, writes JSON to the file via json.dump().
  - tools/claude-skill/SKILL.md — Added wait command, --bg to Global Flags, new Transcribe-Specific Flags section, new examples for --output-file, --json --output-file, and
  --bg.
  - tools/claude-skill/Workflows/Transcribe.md — Full rewrite: added --output-file, --json, --bg options and examples.
  - All other workflow docs (ConvertMedia, CaptionVideo, VideoOps, Metadata, Screenshot, Download, Silence, FFmpeg, Upload) — Added --json and --bg documentation.
…/ outputs via HTTP.

  What changed:
  1. New routes/v1/files/content.py — GET endpoint that serves files from /data/output/ with path traversal protection (realpath + ALLOWED_DIRS whitelist). Requires API key
  auth.
  2. tools/nca.py download_output() — When a file:// URL is returned and the local volume mount path doesn't exist, falls back to downloading via GET
  /v1/files/content?path=/data/output/xxx.mp4.

  Flow:
  1. CLI gets file:///data/output/uuid_captioned.mp4 from API
  2. Tries ./local/output/uuid_captioned.mp4 (volume mount) — if exists, copies
  3. If not, calls GET {api_url}/v1/files/content?path=/data/output/uuid_captioned.mp4 — downloads the file over HTTP

  To test: Rebuild the container (make down && make up) and re-run the caption command.
…mis-match.

  What was wrong:
  1. No env vars passed to Cloud Run — config.py raises ValueError on import if API_KEY is missing. The container crashed immediately on every
   startup attempt.
  2. --use-http2 flag — Gunicorn sync workers don't support HTTP/2 (h2c). Cloud Run's health check couldn't communicate with the container.

  What changed in Makefile:
  - Added .env file check before deploy
  - Converts .env → temp YAML file, passes via --env-vars-file (handles commas in values safely)
  - Removed --use-http2
  - Cleans up temp file after deploy

The real blocker was architecture mismatch — arm64 image on amd64 Cloud Run.

  Two fixes applied:
  1. --platform linux/amd64 on docker build — forces cross-compilation for Cloud Run's x86_64
  2. --no-use-http2 on gcloud run deploy — clears the stuck h2c port protocol from the first deployment

  Heads up: The rebuild will be slower than usual because Docker is cross-compiling (QEMU emulation for amd64 on your arm64 Mac). The FFmpeg
  compile step especially will take a while.
  New targets:

  ┌───────────────────┬─────────────────────────────────────────────────────────────────────────────────────┐
  │      Command      │                                    What it does                                     │
  ├───────────────────┼─────────────────────────────────────────────────────────────────────────────────────┤
  │ make cloud-build  │ Builds image remotely on Cloud Build (native amd64, 32-core machine, 30min timeout) │
  ├───────────────────┼─────────────────────────────────────────────────────────────────────────────────────┤
  │ make cloud-deploy │ Cloud Build + deploy to Cloud Run (recommended for Apple Silicon)                   │
  └───────────────────┴─────────────────────────────────────────────────────────────────────────────────────┘

  Existing targets unchanged:

  ┌─────────────┬──────────────────────────────────────────────────────────────┐
  │   Command   │                         What it does                         │
  ├─────────────┼──────────────────────────────────────────────────────────────┤
  │ make deploy │ Local Docker build + push + deploy (cross-compiles on arm64) │
  └─────────────┴──────────────────────────────────────────────────────────────┘

  I'd recommend using make cloud-deploy — it avoids the cross-compilation entirely, builds on a beefy 32-core machine, and the image is
  natively amd64. The FFmpeg compile alone should be significantly faster.
  What it does:
  1. Enables Cloud Build, Cloud Run, Artifact Registry, and Container Registry APIs
  2. Creates the Artifact Registry Docker repo (skips if already exists)
  3. Configures Docker auth for pushing images

  New workflow for a fresh project:
  make setup          # Generate .env with API key
  make cloud-setup    # Enable GCP APIs + create registry
  make cloud-deploy   # Build on Cloud Build + deploy to Cloud Run

  Run make cloud-setup now, then make cloud-deploy should work.
…additions: Makefile targets, cloud deployment workflow (cloud-setup → cloud-deploy), local file upload endpoints (/v1/files/upload, /v1/files/content), local I/O mode, and CLI tool.

2. Updated README.md — Replaced the outdated Quick Start section with current commands (make setup → make cloud-setup → make cloud-deploy),
  added the two deploy paths table, added local file upload section, and linked to the new What's New doc.
@jpatrickfarrell
Copy link
Copy Markdown
Author

@stephengpope I made these changes so we can more easily use Claude Code integrations with the NCA Toolkit. I'd love to know what you think. I'm using it pretty effectively myself.

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