feat: add api subcommand for raw GraphQL access#121
feat: add api subcommand for raw GraphQL access#121bendrucker wants to merge 8 commits intoschpet:mainfrom
api subcommand for raw GraphQL access#121Conversation
|
Fixing CI... (forgot to re-generate the skill) Also backing out some of those unrelated changes in the skill refs, just the |
|
@bendrucker nice one! i'll take this for a spin tomorrow |
Use `after` instead of `endCursor` as the injected pagination variable to match Linear's GraphQL schema conventions. Add tests for: no API key, null/false coercion, values containing equals signs, single-page pagination, non-connection pagination with --paginate, and file-not-found errors for -F @path.
|
❤️ I also need to kick the tires on this a bit, feel free to review but I'm gonna convert back to a draft pending a bit more manual testing (and CI fix). |
schpet
left a comment
There was a problem hiding this comment.
i think in general, at least to start, going with a more json oriented api might be more simple and easy? open to ideas but that's my hunch
| "-F, --typed-field <field:string>", | ||
| "Typed variable in key=value format (coerces booleans, numbers, null; @file reads from path)", | ||
| { collect: true }, | ||
| ) |
There was a problem hiding this comment.
i feel like just requiring json inputs for variables is a lot less likely to cause grief, WYT?
i assume in this day and age, this feature will mostly be used by agents and i would expect them to have an easier time with something like this
linear api query "$(cat /tmp/query.graphql)" --variables "$(cat /tmp/variables.json)"than with this:
linear api query "$(cat /tmp/query.graphql)" -f foo=bar -f abc=123There was a problem hiding this comment.
easier time with something like this... than with this
Why though? If the agent is actually writing the variables to a temporary file, that's an extra tool call. If it's passing a JSON string directly, it's negligibly fewer tokens. JSON itself is more, but eliminating the repeated -f flags makes it come out ahead by a token or two for the example.
In any case, this came from gh, where the api subcommand deals with both REST and GraphQL. The field flags make a lot more sense with REST. Since Linear is just GQL, it probably does make sense to diverge. But I'd still tend to expect --variable foo=bar --variable abc=123 would perform equal or better to --variables '{"foo":"bar","abc":123}' for that simple case of a few variables.
Having --variables is going to be most beneficial when the variables come from some data. If they are already on disk or otherwise are emitted as the output of some command, having to split them into repeated flags is extra work.
There was a problem hiding this comment.
https://github.com/hasura/graphqurl
Some more design inspiration. I don't want to overload an agent with options either, but --variable and --variable-json is an option to allow for both cases.
There was a problem hiding this comment.
got it, yeah i think you see a clear design here! i am just averse to string parsing, but i think you make a good case for it. i'm onboard.
There was a problem hiding this comment.
Will definitely think through this more, arguably the most important tradeoff is this:
- Linear is just GraphQL,
gh apiis designed for REST and adapted for GraphQL, there's no such need to span both styles here ghis heavily represented in LLM training data and even in system prompts (e.g., Claude Code), though probably rarely with-f
That said this gets hard to pin down, because similarity to popular tools could be beneficial, but could also be confusing to an agent trying to apply known patterns to a tool with similar but subtly different semantics.
Rather than try to infer how this might work by squinting at it, I'm just going to end up just testing this on a variety of natural language queries to try to back up some of these idea with some limited amount of data. If there's an obvious winner on performance that's the answer, otherwise if the agent is equally happy with any format there's more room for human-friendly syntax.
There was a problem hiding this comment.
cool, i'm very into the 'desire path' (thank you yegge for coining that!) approach.
i'm familiar with tools like graphiql which make you use json for variables so maybe that informs my interest in that option, too.
appreciate your consideration around this, looking forward to improving the plumbing.
There was a problem hiding this comment.
Added notes from a quick ad-hoc eval below, --variables-json and --variable seems like a winner. Provides flexibility to do things like have JSON variables in a file but a single override. Agent still does a good job of picking plain flags for simple cases and JSON for complex ones.
There was a problem hiding this comment.
awesome, thanks so much! i'll aim to try this out tonight. please feel free to drop the 'draft' status when you consider this PR to be merge-ready.
| export const apiCommand = new Command() | ||
| .name("api") | ||
| .description("Make a raw GraphQL API request") | ||
| .arguments("[query:string]") |
There was a problem hiding this comment.
would be good imo to support mutations, too
There was a problem hiding this comment.
I think this should be supported and just needs clearer docs, will update
Redesign the api subcommand variable interface for GraphQL-native semantics: - --variable key=value (repeated) with type coercion and @file support - --variables-json for complex nested objects - --variable takes precedence over --variables-json on key conflicts - Custom Cliffy Type for key=value parsing instead of hand-rolled split - Fix deno fmt issue on api-filter.json fixture
|
Ran Results
Sample OutputsSimple Variablelinear api 'query($id: String\!) { issue(id: $id) { id identifier title } }' --variable id=abc-123Complex Filterlinear api 'query($filter: IssueFilter\!) { issues(filter: $filter) { nodes { identifier title } } }' \
--variables-json '{"filter": {"state": {"name": {"eq": "In Progress"}}}}'Multiple Variableslinear api 'mutation($title: String\!, $teamId: String\!) { issueCreate(input: { title: $title, teamId: $teamId }) { success issue { id identifier title } } }' \
--variable title='Fix login bug' --variable teamId=team-xyzMethodology
|
The --silent flag was not suppressing output when the API returned HTTP 400+ status codes in both executeSingle and executePaginated paths.
|
Ready for review! Did another self-review/testing pass on this, and only came up with 9647a7d. |
Adds a
linear apisubcommand for making raw GraphQL requests, mirroringgh apiconventions.Changes
-, or via auto-detected piped input--variable key=valuefor typed variable coercion (booleans, numbers, null,@filefor file reads,@-for stdin)--variables-json '{"key": "value"}'for passing all variables as a JSON object (merged with--variable, which takes precedence)--paginatewalkspageInfo.endCursorautomatically and outputs concatenatednodesarray--silentsuppresses response output while exit code still reflects errorsjqfetchso users see the exact server response including bothdataanderrorsfieldsTesting
MockLinearServercover query resolution, variable handling (type coercion,@file,--variables-json, precedence), output modes, pagination (multi-page, single-page, non-connection), auth errors, and--silentbehavior for both successful and HTTP error responses--paginate), non-existent issue lookup, variable type mismatch errors, stdin pipingRelated
apisubcommand for raw GraphQL access #123