feat(cli): Phase 2 — CLI feature parity with REST API#446
Merged
Conversation
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>
There was a problem hiding this comment.
💡 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>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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
Infrastructure improvements
rfapiadapter functions for all new API endpointsWorkspaceandProjectclassesresolve_ws_and_key()extracted to shared_resolver.py(DRY)api_key=***)train startauto-exports version in required format before trainingproject/versionnow works correctlyConnectionErrorgracefullyTesting
make check_code_qualityclean (ruff format, ruff check, mypy)Still stubbed (no backing API)
workflow build/run/deploy— needs inference_sdk or new APIbatch(Batch Processing) — needs inference cloud APIcompletion— utility feature