Add CLI api command for REST API calls to server - #1066
Merged
Conversation
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>
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>
- 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>
|
Review the following changes in direct dependencies. Learn more about Socket for GitHub.
|
…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>
api command for raw endpoint calls
api command for raw endpoint callsapi command for REST API calls to server
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
marked this pull request as ready for review
July 24, 2026 16:10
ehoogerbeets
left a comment
Contributor
There was a problem hiding this comment.
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.)
ehoogerbeets
approved these changes
Jul 30, 2026
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
Added a
mojito apicommand (similar togh api) that allows executing authenticated calls to the Mojito REST API, plus auto-generated OpenAPI spec served at/api-docsfor 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
jqor 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
gh api- this approach was inspired by GitHub CLIapicommand (docs)gh apiwhich auto-switches to POST when fields are present. This avoids accidental mutations since Mojito uses the same paths for GET (list) and POST (create).--inputalso requires explicit-X.ConsoleWriterentirely; we should consider refactoring CLI output implementation in future to allow structured output for existing commands).-F/-fsupports simplekey=valueandkey[]=value(arrays). Complex nested structures (e.g. creating a repository with locales, posting screenshot runs) should use--inputwith pre-constructed JSON instead. This avoids the complexity of replicatinggh api's deep nesting parser for a use case better served byjq+ stdin.--paginateis used, the command inspects the first response to determine which pagination style to use (Spring DataPageenvelope vs bare JSON array), so--paginate-styledoesn't need to be specified. Page size defaults to 10 to match server defaults.springdoc.api-docs.enabled=falseinapplication.properties. Enable withspringdoc.api-docs.enabled=truefor deployments that want agent discoverability.-apiartifact (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 headersAsync operation support:
--wait-- detects two async patterns and polls to completion:id+allFinished(top-level or nestedpollableTaskfield). Used by most server-side async operations.pollingToken.requestId. Poll URL is taken frompollingToken.pollUrlif present, otherwise derived as{requestPath}/results/{requestId}. Any endpoint can adopt this pattern. 120-attempt timeout.Pagination:
--paginateenables automatic fetching of all pages. The pagination style is auto-detected from the first response:content+hasNext-> page mode (Spring Data Page/size)--paginate, only a single request is made (even if the endpoint supports pagination)--paginate-styleoptional override:auto(default),page, oroffset--page-size(default: 10, matches server defaults),--max-pages(default: 10, 0=unlimited),--start-page(default: 0)--slurpto merge all pages into a single JSON arrayOutput control:
--pretty-- pretty-print JSON--silent-- suppress response body--include-- print HTTP status line and headers before bodyAPI discoverability:
springdoc-openapiauto-generates an OpenAPI spec from existing@RestControllerannotations, scoped to/api/**, with@JsonViewsupport and serves it at/api-docsmojito api --specfetches and prints the spec JSON (more idiomatic than a separatecurl, encapsulates server's configured URL)Usage examples
Test plan
--waitwith non-pollable responses, custom headers, silent mode, pretty print, include headerscoerceValuetype conversion (booleans, integers, nulls, raw mode),@filefield values, array field construction, polling token detection, slurp merge output, stdin conflict validation (rawFields+--input)