Skip to content

feat(cli): Phase 2 — CLI feature parity with REST API#446

Merged
yeldarby merged 25 commits intomainfrom
apr-2026/cli-phase2
Apr 3, 2026
Merged

feat(cli): Phase 2 — CLI feature parity with REST API#446
yeldarby merged 25 commits intomainfrom
apr-2026/cli-phase2

Conversation

@yeldarby
Copy link
Copy Markdown
Contributor

@yeldarby yeldarby commented Apr 2, 2026

Summary

Implements 25 previously-stubbed CLI commands to achieve feature parity with the existing REST API. Builds on the Phase 1 CLI modernization (PR #445).

New functionality

  • Folders: list, get, create, update, delete — project folder management
  • Annotation batches: list, get — view upload/annotation batches
  • Annotation jobs: list, get, create — manage labeling assignments
  • Workflows: list, get, create, update, version list, fork — full workflow lifecycle
  • Workspace stats: usage, plan, stats — billing and analytics
  • Version create: generate dataset versions with augmentation/preprocessing settings
  • Video status: check video inference job progress
  • Universe search: search public datasets and models on Roboflow Universe

Infrastructure improvements

  • 22 new rfapi adapter functions for all new API endpoints
  • 12 new SDK methods on Workspace and Project classes
  • resolve_ws_and_key() extracted to shared _resolver.py (DRY)
  • API key sanitization in ALL error messages (api_key=***)
  • Auto-detect plan-gated errors and add upgrade/pricing hints
  • train start auto-exports version in required format before training
  • Download shorthand project/version now works correctly
  • Deployment handler catches ConnectionError gracefully

Testing

  • 405 tests (122 new), all passing
  • make check_code_quality clean (ruff format, ruff check, mypy)
  • Fresh QA round: 24/25 (5/5 discoverability, consistency, agent-friendliness, polish; 4/5 errors)
  • Code review: 1 must-fix (Workspace.create_workflow kwargs) + 2 should-fixes addressed
  • Security review: no critical/high issues
  • 35/35 integration tests against staging API
  • Full lifecycle verified: create project → upload images → create version → export → train

Still stubbed (no backing API)

  • workflow build/run/deploy — needs inference_sdk or new API
  • batch (Batch Processing) — needs inference cloud API
  • completion — utility feature

yeldarby and others added 22 commits April 2, 2026 09:14
Add adapter functions for all REST API endpoints that Phase 2 CLI
commands will need:

- Annotation batches: list_batches, get_batch (5 functions)
- Annotation jobs: list/get/create_annotation_job
- Folders: list/get/create/update/delete_folder (5 functions)
- Workflows: list/get/create/update/list_versions/fork (6 functions)
- Workspace stats: get_billing_usage, get_plan_info, get_labeling_stats
- Video: get_video_job_status
- Universe: search_universe (no auth required)

All follow the established rfapi pattern: accept api_key + identifiers,
call requests, raise RoboflowError on error, return parsed JSON.

Pre-committed before spawning engineer team to prevent merge conflicts
(lesson from Phase 1).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add convenience methods to Workspace and Project classes so Python SDK
users get the same new capabilities as CLI users:

Workspace:
- list_folders(), create_folder() — project folder management
- list_workflows(), get_workflow(), create_workflow() — workflow management
- get_usage(), get_plan() — billing and plan info

Project:
- get_annotation_jobs(), get_annotation_job() — annotation job queries
  (get_batches, get_batch, create_annotation_job already existed)

All methods delegate to rfapi adapter functions added in the previous
commit. CLI handlers will call these SDK methods rather than rfapi
directly.

283 tests pass, all linting clean.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Replace stubs with working handlers for:
- annotation batch list/get (via rfapi)
- annotation job list/get (via rfapi)
- annotation job create (via SDK project.create_annotation_job)

All commands support --json output, use resolve_resource() for
project resolution, and follow CLI output/error conventions.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…rsion list, fork)

Replace stubs with working implementations that call rfapi workflow
endpoints. Keep build/run/deploy as stubs with descriptive hints.
Add 29 behavior tests covering all 6 implemented commands plus stubs.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Replace folder stubs with working handlers (list, get, create, update,
delete) and add workspace usage/plan/stats subcommands. All commands
support --json structured output, proper error handling with return
after output_error, and workspace/API key resolution.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Replace stubs with working implementations:
- version create: reads JSON settings file, calls project.generate_version()
- video status: calls rfapi.get_video_job_status() with structured output
- universe search: calls rfapi.search_universe() with table/JSON output

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
48 tests covering success paths, error paths, URL construction,
and parameter passing for batches, folders, workflows, annotations,
billing, labeling stats, video jobs, and universe search.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The staging API requires authentication even for universe search.
Updated rfapi.search_universe() to accept optional api_key param
and the handler to pass it through when available.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- BUG 1: folder get text shows blank - API returns {data: [folder]}
  not {group: folder}. Extract from data[0].
- BUG 2: folder list returns Not Found - API returns {data: [...]}
  not {groups: [...]}. Read from data key with groups fallback.
- BUG 3: workspace stats requires date range - add required
  --start-date/--end-date flags, pass through to rfapi.
- BUG 5: folder delete empty error - API returns 204 No Content on
  success. Fix rfapi.delete_folder to accept 200/204 and handle
  empty body.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
BUG 1: createWorkflow expects name, url, template, config as query
params (all strings). Now auto-generates url slug from name and
defaults template/config to "{}".

BUG 2: forkWorkflow expects source_workspace and source_workflow in
body, not workflowUrl. Handler now parses "ws/workflow" shorthand.

BUG 3: updateWorkflow expects id, name, url, config in body. Handler
now fetches existing workflow first to get required fields, then
applies the definition update as config.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…exit code 3

The folders API returns HTTP 404 when a workspace has no folders instead
of an empty array. The handler now treats 404 as an empty result.

Also fix folder delete to use exit code 3 (not found) instead of 1
(generic) when the folder doesn't exist, consistent with other handlers.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Align CreateWorkflow, UpdateWorkflow, and ForkWorkflow tests with
Eng3's signature changes (config/template params, required fields,
source_workspace/source_workflow kwargs). Added new test cases for
auto-generated URL slugs, dict-to-string serialization, and optional
name/url on fork.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…niverse search

Add tests verifying that video status passes job_id to the API call
and that universe search forwards --limit to rfapi.search_universe().

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- video status: API expects `job_id` not `jobId` as query param
- universe search: API ignores `limit` param, enforce client-side truncation

Verified both fixes against staging API.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…estamps

- workflow fork: read 'workflow' key from API response (was looking for 'url')
- video status: provide actionable error with hint for not-found jobs
- version create: add settings JSON example in --help epilog
- annotation batch/job: convert Firestore timestamps to ISO 8601

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…e docs

Standardize 4 annotation batch/job endpoints to use params= instead of
f-string query params for consistency with the rest of the codebase.
Update CLI-COMMANDS.md with Phase 2 command examples.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- #1 MUST-FIX: Fix Workspace.create_workflow to map definition->config
  and drop unsupported description param
- #3 SHOULD-FIX: Delegate Project.get_annotation_jobs/get_annotation_job
  to rfapi instead of inline HTTP calls
- #4 SHOULD-FIX: Extract resolve_ws_and_key to _resolver.py, deduplicate
  from folder/workspace/workflow handlers
- Update test to expect exit code 2 (auth error) for missing workspace

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…oise

1. Add _sanitize_credentials() to _output.py that strips api_key values
   from URLs in error messages before displaying to the user. This prevents
   API keys from leaking in --json error output, logs, and terminal
   history. Applied to ALL error paths through output_error().

2. Fix video infer handler to use suppress_sdk_output() — was leaking
   "loading Roboflow workspace..." to stdout in --json mode.

Before: {"error": {"message": "...?api_key=tVO5PbdMtkaS5VP92xM7&..."}}
After:  {"error": {"message": "...?api_key=***&..."}}

405 tests pass, all linting clean.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…yment crash

1. Add _detect_plan_hint() that automatically appends upgrade/pricing
   hints when error messages contain plan-related keywords (Growth plan,
   Enterprise, folder billing, Unauthorized, over_quota). Applied to ALL
   errors through output_error() when no explicit hint is provided.

2. Fix deployment handler wrapper to catch ConnectionError (not just
   SystemExit) — prevents raw Python tracebacks when the deployment
   service is unreachable.

3. Previously committed: _sanitize_credentials strips API keys from
   error messages.

405 tests pass, all linting clean.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…rmat

Replace _parse_url regex with resolve_resource() for non-URL shorthands.
The regex couldn't disambiguate "project/version" from "workspace/project"
when the second segment was numeric. Now uses the same resolver as all
other commands, which checks if the last segment is numeric (version).

Before: roboflow download test-detection/1 → workspace=test-detection, project=1 (WRONG)
After:  roboflow download test-detection/1 → workspace=default, project=test-detection, version=1 (CORRECT)

Full URLs (https://universe.roboflow.com/...) still use the URL regex.
All 4 download forms verified against staging:
- project/version (shorthand)
- project (picks latest)
- workspace/project/version (full path)
- Full URL

405 tests pass, all linting clean.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Before training, the handler now:
1. Determines the required export format for the model type using
   get_model_format() (e.g., rfdetr needs coco, yolov8 needs yolov5pytorch)
2. Checks if the version is still generating; waits with progress updates
3. Checks if the version has the required export; triggers and polls if not
4. Then starts training

Also improves the "Unknown error" from the train API — adds a hint
suggesting the version may not be exported yet.

This prevents the confusing failure mode where `train start` returns
"Unknown error" because the server expects an export that doesn't exist.

405 tests pass, all linting clean.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@yeldarby yeldarby requested a review from tonylampada April 2, 2026 16:50
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: e158cc8043

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

…ersion ID

1. annotation job create: pass resolved API key to Roboflow() instead of
   relying on global config. Fixes auth failure in CI/non-interactive
   contexts when --api-key is provided.

2. workflow fork: extract URL from nested {"workflow": {"url": "..."}}
   response instead of assigning the whole dict to new_url.

3. version create: use return value from generate_version() directly
   instead of inferring via max(versions), which is race-prone if
   another version is created concurrently.

405 tests pass, all linting clean.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@yeldarby yeldarby changed the base branch from apr-2026/cli-modernization to main April 2, 2026 21:59
Copy link
Copy Markdown
Collaborator

@tonylampada tonylampada left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM!

@yeldarby yeldarby merged commit 0b3e1c5 into main Apr 3, 2026
14 checks passed
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.

3 participants