Skip to content

Add CLI api command for REST API calls to server - #1066

Merged
ehoogerbeets merged 16 commits into
upstream-patchedfrom
cli-raw-command
Jul 30, 2026
Merged

Add CLI api command for REST API calls to server#1066
ehoogerbeets merged 16 commits into
upstream-patchedfrom
cli-raw-command

Conversation

@wadimw

@wadimw wadimw commented Jul 23, 2026

Copy link
Copy Markdown

Summary

Added a mojito api command (similar to gh api) that allows executing authenticated calls to the Mojito REST API, plus auto-generated OpenAPI spec served at /api-docs for endpoint discoverability.

The command makes authenticated HTTP requests to any Mojito endpoint, encapsulating auth, instance config, pollable task waiting, and pagination. Outputs clean JSON to stdout with diagnostics on stderr, suitable for piping to jq or consumption by scripts or AI agents.

API spec is generated automatically using springdoc-openapi. It's not a public API - there are no strict versioning policies, backwards compatibility guarantees etc.. This is exposed primarily to provide an AI agent with an entrypoint to discover existing capabilities without it deep-diving into Mojito's source code upfront.

Key design decisions

  • Based on gh api - this approach was inspired by GitHub CLI api command (docs)
  • Default method is always GET -- unlike gh api which auto-switches to POST when fields are present. This avoids accidental mutations since Mojito uses the same paths for GET (list) and POST (create). --input also requires explicit -X.
  • stdout/stderr separation -- response body (including error bodies) always goes to stdout; error summaries, wait progress, and pagination resume hints go to stderr. (Compared to implementation of other Mojito CLI commands, this one bypasses ConsoleWriter entirely; we should consider refactoring CLI output implementation in future to allow structured output for existing commands).
  • Field construction is intentionally flat -- -F/-f supports simple key=value and key[]=value (arrays). Complex nested structures (e.g. creating a repository with locales, posting screenshot runs) should use --input with pre-constructed JSON instead. This avoids the complexity of replicating gh api's deep nesting parser for a use case better served by jq + stdin.
  • Pagination style auto-detection -- when --paginate is used, the command inspects the first response to determine which pagination style to use (Spring Data Page envelope vs bare JSON array), so --paginate-style doesn't need to be specified. Page size defaults to 10 to match server defaults.
  • OpenAPI spec disabled by default -- springdoc.api-docs.enabled=false in application.properties. Enable with springdoc.api-docs.enabled=true for deployments that want agent discoverability.
  • springdoc 2.2.0 -- pinned for Spring Boot 3.1.x compatibility. Uses -api artifact (no Swagger UI).

Features

Request construction:

  • -X/--method -- explicit HTTP method (default: GET)
  • -F/--field key=value -- typed fields (booleans, integers, nulls auto-converted; @file/@- for file/stdin)
  • -f/--raw-field key=value -- string-only fields (no type conversion)
  • key[]=value -- array construction (e.g. -F repositoryIds[]=1 -F repositoryIds[]=2)
  • --input file -- pre-constructed body from file (- for stdin), requires explicit -X. Use for complex nested JSON.
  • --binary -- raw byte mode for --input (e.g. image uploads)
  • -H/--header key:value -- custom HTTP headers

Async operation support:

  • --wait -- detects two async patterns and polls to completion:
    • PollableTask: responses with id + allFinished (top-level or nested pollableTask field). Used by most server-side async operations.
    • Polling token: responses with pollingToken.requestId. Poll URL is taken from pollingToken.pollUrl if present, otherwise derived as {requestPath}/results/{requestId}. Any endpoint can adopt this pattern. 120-attempt timeout.
  • Gracefully passes through non-async responses (no error)

Pagination:

  • --paginate enables automatic fetching of all pages. The pagination style is auto-detected from the first response:
    • JSON object with content + hasNext -> page mode (Spring Data Page/size)
    • JSON array -> offset mode (offset/limit, used by text unit search)
    • Neither -> not a paginated response, prints as-is and stops
  • Without --paginate, only a single request is made (even if the endpoint supports pagination)
  • --paginate-style optional override: auto (default), page, or offset
  • --page-size (default: 10, matches server defaults), --max-pages (default: 10, 0=unlimited), --start-page (default: 0)
  • --slurp to merge all pages into a single JSON array
  • Resume hint printed to stderr when max-pages cap is reached

Output control:

  • --pretty -- pretty-print JSON
  • --silent -- suppress response body
  • --include -- print HTTP status line and headers before body

API discoverability:

  • springdoc-openapi auto-generates an OpenAPI spec from existing @RestController annotations, scoped to /api/**, with @JsonView support and serves it at /api-docs
  • mojito api --spec fetches and prints the spec JSON (more idiomatic than a separate curl, encapsulates server's configured URL)

Usage examples

# Discover available endpoints
mojito api --spec --pretty

# List repositories
mojito api repositories

# Filter by name (fields go to query string on GET)
mojito api repositories -f name=my-repo

# Create a repository (explicit POST required, complex body via --input)
echo '{"name":"new-repo","description":"Test","repositoryLocales":[...]}' | \
  mojito api repositories -X POST --input -

# Simple POST with flat fields
mojito api repositories -X POST -f name=new-repo -f description="Test"

# Search text units (pagination auto-detected as offset style)
mojito api textunits/search -X POST --paginate --slurp \
  -F repositoryIds[]=42 -F searchType=CONTAINS -F source=hello

# Paginate drops (auto-detected as page style)
mojito api drops --paginate --page-size 20

# Wait for an async operation
mojito api thirdparty/sync -X POST -F repositoryId=42 --wait

# Upload a binary file
mojito api images/screenshot.png -X PUT --input screenshot.png --binary

Test plan

  • Integration tests (38 total): GET, POST with raw/typed fields, explicit method override, field-to-query-string default, error responses (body on stdout, summary on stderr), page-style pagination, offset-style pagination, pagination auto-detection (both styles), --wait with non-pollable responses, custom headers, silent mode, pretty print, include headers
  • Unit tests: endpoint normalization, PollableTask detection (top-level, nested, non-pollable), coerceValue type conversion (booleans, integers, nulls, raw mode), @file field values, array field construction, polling token detection, slurp merge output, stdin conflict validation (rawFields + --input)

wadimw and others added 3 commits July 23, 2026 14:08
gh-api-style command that makes authenticated HTTP requests to the Mojito
REST API, encapsulating auth, instance config, pollable task waiting, and
pagination. Outputs clean JSON to stdout with diagnostics on stderr.

Flags: -X, -F, -f, --input, -H, --wait, --paginate, --slurp,
--page-size, --max-pages, --start-page, --include, --pretty, --silent.

Co-authored-by: Cursor <cursoragent@cursor.com>
- Support key[]=value (arrays) and key[subkey]=value (nested objects) in
  -F/-f field construction, matching gh api semantics.
- Detect hybrid search 202 pollingToken responses and poll until results
  are ready when --wait is used.
- Add --binary flag for raw byte uploads via --input (e.g. image PUT).
- Detect nested pollableTask fields in response objects (SourceAsset,
  CancelDropConfig, etc.), not just top-level PollableTask responses.
- Remove --paginate GET-only restriction to support POST-based search.
- Use System.out/err directly instead of capturing at construction time.

Co-authored-by: Cursor <cursoragent@cursor.com>
Both pagination styles are now first-class: --paginate-style page (default,
Spring Data Page envelope with hasNext) and --paginate-style offset (bare
array responses with offset/limit, used by text unit search).

In offset mode, --page-size sets the limit, --start-page selects the
starting batch (offset = N * page-size), and for POST requests the offset
and limit are injected into the JSON body automatically.

Co-authored-by: Cursor <cursoragent@cursor.com>
@wadimw wadimw added the upstream-patched Experimental features ported from legacy branch label Jul 23, 2026
@wadimw
wadimw requested a review from ehoogerbeets July 23, 2026 12:56
Add springdoc-openapi to auto-generate an OpenAPI 3.0 spec from existing
Spring MVC annotations. Served at /api-docs (unauthenticated) scoped to
/api/** endpoints with @JSONVIEW support enabled.

Uses springdoc-openapi-starter-webmvc-api 2.2.0 (API-only, no Swagger UI)
which is the version compatible with Spring Boot 3.1.x.

Add --spec flag to the api command so agents can fetch the spec via
`mojito api --spec` without needing to know the server URL.

Co-authored-by: Cursor <cursoragent@cursor.com>
@wadimw
wadimw force-pushed the cli-raw-command branch from 5204f47 to 9297625 Compare July 23, 2026 13:01
wadimw and others added 3 commits July 23, 2026 15:09
- Collapse 6 duplication pairs: unified executePaginated method,
  coerceValue for type conversion, doExchange for HTTP error handling,
  single readFileAsString, buildFieldsBody convenience wrapper,
  countStdinRefsInFields helper. File reduced from 1012 to 954 lines.
- Add max-retry cap (120 attempts) to maybeWaitForPollingToken to
  prevent infinite blocking on unresponsive search results.
- Gate OpenAPI spec behind springdoc.api-docs.enabled=false by default;
  must be explicitly enabled for deployments that want agent
  discoverability.
- Fix countStdinReaders to also check rawFields for @- references.
- Fix test assertions that expected implicit POST (now require -X POST).

Co-authored-by: Cursor <cursoragent@cursor.com>
…validation

Co-authored-by: Cursor <cursoragent@cursor.com>
Document the stdout/stderr output contract, gh api behavioral differences,
GET-default safety rationale, PollableTask detection heuristic, pagination
style differences, error handling strategy, and poll timeout semantics.

Co-authored-by: Cursor <cursoragent@cursor.com>
@socket-security

socket-security Bot commented Jul 23, 2026

Copy link
Copy Markdown

Review the following changes in direct dependencies. Learn more about Socket for GitHub.

Diff Package Supply Chain
Security
Vulnerability Quality Maintenance License
Addedmaven/​org.springdoc/​springdoc-openapi-starter-webmvc-api@​2.2.09510090100100

View full report

wadimw and others added 2 commits July 23, 2026 15:23
…summary

- Don't append fields to path in execute() when paginating; let each
  pagination method handle field injection to avoid doubling params.
- Page-style pagination now adds fields to query string itself (also
  removes dead body parameter from executePageRequest).
- Change appendFieldsToQueryString to use fromUriString instead of
  fromPath so paths with existing query strings are parsed correctly.
- Remove redundant printErrorSummary call in maybeWaitForPollingToken
  since doExchange already prints it.

Co-authored-by: Cursor <cursoragent@cursor.com>
- Add extractJson() helper to parse JSON from stdout that may contain
  interleaved server log lines (only happens in integration tests where
  the server runs in-process, not in real CLI usage).
- Replace outputCapture.toString() with getStdout() in validation error
  tests since System.out is redirected in @before.
- Fix testCountStdinReadersCatchesRawFields to set endpoint and method
  before calling validateArgs (avoids early endpoint-required error).

Co-authored-by: Cursor <cursoragent@cursor.com>
@wadimw wadimw changed the title Add CLI api command for agentic orchestration Add CLI api command for raw endpoint calls Jul 24, 2026
@wadimw wadimw changed the title Add CLI api command for raw endpoint calls Add CLI api command for REST API calls to server Jul 24, 2026
wadimw and others added 5 commits July 24, 2026 15:52
Only keep key[]=value for arrays (covers repositoryIds, localeTags, etc).
Complex nested structures should use --input with pre-constructed JSON
instead of trying to replicate gh api's deep nesting parser.

Co-authored-by: Cursor <cursoragent@cursor.com>
Instead of requiring --paginate-style, the command now auto-detects:
- JSON object with content+hasNext -> page mode (Spring Data Page)
- JSON array -> offset mode (bare array endpoints like text unit search)
- Neither -> not paginated, print response and stop

First request in auto mode sends both page/size and offset/limit params
(servers ignore whichever set they don't use). --paginate-style is kept
as an optional override with 'auto' as the new default.

Also lower --page-size default from 100 to 10 to match server defaults
and avoid overloading endpoints.

Co-authored-by: Cursor <cursoragent@cursor.com>
Remove hardcoded /api/textunits/search-hybrid/results/ path from
maybeWaitForPollingToken. The poll URL is now derived from:
1. pollingToken.pollUrl if present in the response (explicit)
2. requestPath + /results/ + requestId as fallback (convention)

This lets any future endpoint adopt the same polling-token pattern
without changes to the api command.

Co-authored-by: Cursor <cursoragent@cursor.com>
- Replace findAndRegisterModules() with explicit JavaTimeModule
  registration to avoid ServiceLoader discovering Hibernate modules
  that aren't on the CLI classpath.
- Support both Spring Data's standard Page response (uses "last" field)
  and Mojito's custom PageView (uses "hasNext" field) for page-style
  pagination detection and iteration.

Co-authored-by: Cursor <cursoragent@cursor.com>
Switch from a private createObjectMapper() to @Autowired Mojito
ObjectMapper for consistency with other CLI commands. Convert
buildFieldsBody unit tests to integration tests that exercise field
construction through the full command (via L10nJCommander.run), matching
the pattern used by other command test classes.

Co-authored-by: Cursor <cursoragent@cursor.com>
@wadimw
wadimw marked this pull request as ready for review July 24, 2026 16:10

@ehoogerbeets ehoogerbeets left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

We are going to have to add @operation and @Schema tags throughout the backend API so that spring docs can extract it. We should start with the textunit search, repositories, pollable tasks APIs. (Should probably be a separate PR actually.)

Comment thread cli/src/main/java/com/box/l10n/mojito/cli/command/ApiCommand.java Outdated
Comment thread cli/src/main/java/com/box/l10n/mojito/cli/command/ApiCommand.java
Comment thread webapp/src/main/resources/config/application.properties Outdated
@wadimw
wadimw requested a review from ehoogerbeets July 30, 2026 16:43
@ehoogerbeets
ehoogerbeets merged commit 9eae970 into upstream-patched Jul 30, 2026
6 checks passed
@ehoogerbeets
ehoogerbeets deleted the cli-raw-command branch July 30, 2026 16:52
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

upstream-patched Experimental features ported from legacy branch

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants