Skip to content

feat(codegen): add CLI command generator with agent-ready docs from GraphQL schema#725

Open
pyramation wants to merge 4 commits intomainfrom
devin/1771313944-graphql-cli-codegen
Open

feat(codegen): add CLI command generator with agent-ready docs from GraphQL schema#725
pyramation wants to merge 4 commits intomainfrom
devin/1771313944-graphql-cli-codegen

Conversation

@pyramation
Copy link
Contributor

@pyramation pyramation commented Feb 17, 2026

feat(codegen): add CLI command generator with agent-ready docs from GraphQL schema

Summary

Adds a new cli generator target to @constructive-io/graphql-codegen, alongside the existing orm and reactQuery generators. When enabled (cli: true or cli: { toolName: 'myapp' }), the codegen emits a full set of inquirerer-based CLI command files that use the generated ORM client internally — not raw GraphQL.

Architecture: CLI → ORM → GraphQL (mirrors how React hooks use the ORM)

Generated files:

  • cli/executor.ts — initializes ORM client using appstash config-store for endpoint + auth
  • cli/commands/context.ts — kubectl-style context management (create/list/use/current/delete)
  • cli/commands/auth.ts — token management per context (set-token/status/logout)
  • cli/commands/{table}.ts — per-table CRUD (list/get/create/update/delete via ORM methods)
  • cli/commands/{operation}.ts — per-custom-operation commands (queries + mutations via ORM)
  • cli/commands.ts — command map registry (Record<string, Function>)
  • cli/README.md — human-readable CLI overview, setup, and command reference
  • cli/AGENTS.md — structured markdown for LLM/agent consumption (TOOL sections, INPUT/OUTPUT schemas, WORKFLOWS, ERROR HANDLING)
  • cli/mcp.json — MCP tool definitions with typed inputSchema (JSON Schema) per command
  • cli/skills/*.md — per-command skill files for agent systems (Devin, etc.)

Docs generation is configurable via DocsConfig:

cli: {
  toolName: 'myapp',
  docs: { readme: true, agents: true, mcp: false, skills: false }
}
  • docs: true enables all 4 formats; docs: false disables all
  • Defaults: readme + agents always on, mcp + skills opt-in

New files:

  • cli/index.ts — orchestrator
  • cli/arg-mapper.ts — converts CleanTypeRefinquirerer Question[]
  • cli/infra-generator.ts — generates context + auth commands (2055 lines of Babel AST)
  • cli/executor-generator.ts — generates ORM client initialization
  • cli/table-command-generator.ts — generates per-table CRUD commands
  • cli/custom-command-generator.ts — generates per-custom-operation commands
  • cli/command-map-generator.ts — generates command registry
  • cli/docs-generator.ts — generates README, AGENTS.md, mcp.json, and skills files

Dependencies:

  • Added komoji for toKebabCase casing
  • Uses inflekt (already present) for GraphQL casing (ucFirst, lcFirst, etc.)
  • Uses appstash@0.4.0 config-store (merged in dev-utils#63)

All code generation uses Babel AST (@babel/types + generateCode()) — no string concatenation. Docs generation uses string concatenation (intentional for markdown/JSON output).

Updates since last revision

  • Bug fix: runOrm now auto-enables when runCli is true, so { cli: true } without explicit { orm: true } no longer produces broken import { createClient } from '../orm' (caught by Devin review)
  • Snapshot tests added: cli-generator.test.ts with 13 tests and 8 snapshots covering all generated file types (executor, context, auth, table commands, custom commands, command map). Tests exercise a 2-table + 2-custom-operation example schema.
  • Docs generation added: New DocsConfig interface with 4 doc formats (README, AGENTS.md, MCP, Skills). Defaults: README + AGENTS.md always on, MCP + Skills opt-in. 22 tests, 17 snapshots covering all doc formats and config combinations.
  • All 252 tests / 92 snapshots pass across 17 test suites.

Review & Testing Checklist for Human

⚠️ Snapshot tests verify generator output is consistent, but generated code has NOT been compiled or run against a real schema.

  • Run codegen against a real schema with cli: true and verify the generated CLI code compiles (TypeScript + ESLint). This is the most important validation step — snapshots only prove the generator is deterministic, not that its output is correct.
  • Verify ORM API compatibility — check that generated ORM method calls (client.{table}.findMany({...}).execute(), client.query.{op}({...}).execute()) match the actual ORM client API shape. The table-command-generator calls methods like client.car.findMany(...) but the ORM may use a different accessor pattern.
  • Validate AGENTS.md output schemas — the agent docs include hardcoded output schemas (e.g., { name, endpoint } for context commands) that may not match actual runtime behavior. Verify these against real CLI execution.
  • Test MCP integration — if you plan to use the MCP tool definitions, validate the mcp.json structure against the MCP spec. The _meta.fields on {tool}_fields tools is non-standard and may need adjustment.
  • Spot-check infra-generator.ts Babel AST — this 2055-line file generates context + auth commands entirely via AST. Snapshots provide some coverage, but manual review of a few AST node constructions is recommended (especially around String.call(tokenValue || "") in auth.ts snapshot line 117 — this looks like a bug; should be String(tokenValue || "") without .call).
  • Test arg-mapper INPUT_OBJECT handlingbuildInputObjectQuestion only uses the first field of an INPUT_OBJECT, which may be incomplete for nested input types. Verify this works for real schemas with complex input types.
  • Verify required field logic — table-command-generator marks ALL fields as required: true for create operations (line 338). Check if this is correct for nullable fields in your schema.

Notes

  • ESLint config issue is pre-existing (not from this PR) — ESLint v9 migration incomplete across repo
  • TypeScript compilation passes for CLI generator files (no CLI-specific type errors)
  • The cli generator is opt-in via config — existing codegen behavior unchanged
  • All 252 tests pass (17 test suites, 92 snapshots)
  • Docs generation uses string concatenation (not Babel AST) — this is intentional for markdown/JSON output
  • gqlTypeToJsonSchemaType mapping is simplistic (UUID → string, Datetime → string) but reasonable for CLI args

Link to Devin run: https://app.devin.ai/sessions/e9c668834d394cdc8ebcb371b6ebf430
Requested by: @pyramation


Open with Devin

- Add cli config option (CliConfig | boolean) to GraphQLSDKConfigTarget
- Add komoji dependency for toKebabCase casing
- Create CLI generator orchestrator (cli/index.ts)
- Create arg-mapper: converts CleanTypeRef to inquirerer Question[]
- Create infra-generator: context + auth commands via Babel AST
- Create executor-generator: ORM client init with appstash credentials
- Create table-command-generator: per-table CRUD using ORM methods
- Create custom-command-generator: per-operation using ORM methods
- Create command-map-generator: command registry (Record<string, Function>)
- Wire CLI generation into generate.ts alongside ORM/React Query
- Update root barrel to include CLI exports

Architecture: CLI -> ORM -> GraphQL (not CLI -> raw GraphQL)
CLI commands: prompt for args -> call ORM method -> print JSON result
Uses appstash@0.4.0 config-store for context/credential management
@devin-ai-integration
Copy link
Contributor

🤖 Devin AI Engineer

I'll be helping with this pull request! Here's what you should know:

✅ I will automatically:

  • Address comments on this PR. Add '(aside)' to your comment to have me ignore it.
  • Look at CI failures and help fix them

Note: I can only respond to comments from users who have write access to this repository.

⚙️ Control Options:

  • Disable automatic comment and CI monitoring

Copy link
Contributor

@devin-ai-integration devin-ai-integration bot left a comment

Choose a reason for hiding this comment

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

Devin Review found 3 potential issues.

View 5 additional findings in Devin Review.

Open in Devin Review

Comment on lines 53 to 55
const runOrm =
runReactQuery || (options.orm !== undefined ? !!options.orm : false);
const runCli = !!config.cli;
Copy link
Contributor

Choose a reason for hiding this comment

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

🔴 CLI generator enabled without ORM produces broken import { createClient } from '../orm'

The generated cli/executor.ts always emits import { createClient } from '../orm' (executor-generator.ts:34). However, in generate.ts:53-55, runCli can be true while runOrm is false — a user can set { cli: true } without { orm: true }. When ORM generation is skipped, no orm/ directory is created, so the generated CLI code has a broken import that will fail at compile/runtime.

Root Cause

In graphql/codegen/src/core/generate.ts:53-55:

const runOrm =
  runReactQuery || (options.orm !== undefined ? !!options.orm : false);
const runCli = !!config.cli;

Unlike runReactQuery which forces runOrm = true, runCli does not force ORM generation. The CLI's executor (executor-generator.ts:34) unconditionally generates import { createClient } from '../orm', assuming the ORM output exists.

Impact: Any user enabling only CLI generation (e.g., { cli: true, orm: false }) will get generated code that cannot compile or run due to the missing ../orm module.

Suggested change
const runOrm =
runReactQuery || (options.orm !== undefined ? !!options.orm : false);
const runCli = !!config.cli;
const runOrm =
runReactQuery || runCli || (options.orm !== undefined ? !!options.orm : false);
Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

Comment on lines +1645 to +1659
t.callExpression(
t.memberExpression(
t.callExpression(
t.memberExpression(
t.identifier('String'),
t.identifier('call'),
),
[
t.logicalExpression(
'||',
t.identifier('tokenValue'),
t.stringLiteral(''),
),
],
),
Copy link
Contributor

Choose a reason for hiding this comment

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

🔴 String.call(value) in generated set-token handler always produces empty string

The generated handleSetToken function builds AST that produces String.call(tokenValue || "").trim(). This always evaluates to "", silently discarding the user's token input.

Root Cause

At infra-generator.ts:1647-1658, the AST constructs:

t.callExpression(
  t.memberExpression(t.identifier('String'), t.identifier('call')),
  [t.logicalExpression('||', t.identifier('tokenValue'), t.stringLiteral(''))]
)

This generates String.call(tokenValue || ""). In JavaScript, String.call(x) invokes the String function with this = x and zero arguments. String() with no arguments returns "". So the token value is always lost.

The intended code was String(tokenValue || "").trim(), which would be:

t.callExpression(t.identifier('String'), [t.logicalExpression(...)])

Impact: The generated CLI's auth set-token command will always save an empty token, making authentication silently broken.

Suggested change
t.callExpression(
t.memberExpression(
t.callExpression(
t.memberExpression(
t.identifier('String'),
t.identifier('call'),
),
[
t.logicalExpression(
'||',
t.identifier('tokenValue'),
t.stringLiteral(''),
),
],
),
t.callExpression(
t.memberExpression(
t.callExpression(
t.identifier('String'),
[
t.logicalExpression(
'||',
t.identifier('tokenValue'),
t.stringLiteral(''),
),
],
),
t.identifier('trim'),
),
[],
),
Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

Comment on lines +218 to +220
if (hasCli) {
statements.push(exportAllFrom('./cli'));
}
Copy link
Contributor

Choose a reason for hiding this comment

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

🔴 No cli/index.ts barrel generated, causing root barrel export * from './cli' to fail

The root barrel (barrel.ts:218-220) generates export * from './cli' when hasCli is true, but the CLI generator never produces a cli/index.ts file. Module resolution will fail because there is no entry point for the ./cli directory.

Root Cause

Looking at the files generated by generateCli in cli/index.ts:40-69, the output files are:

  • executor.ts
  • commands/context.ts
  • commands/auth.ts
  • commands/<table>.ts (per table)
  • commands/<custom-op>.ts (per custom op)
  • commands.ts

No index.ts barrel is generated for the cli/ directory. Compare with the ORM generator, which produces orm/index.ts via client-generator.ts:338.

When the root barrel emits export * from './cli', TypeScript/Node will try to resolve cli/index.ts (or cli/index.js), which doesn't exist.

Impact: The root index.ts barrel will fail to compile due to the unresolvable ./cli re-export.

Prompt for agents
The CLI generator needs to produce a cli/index.ts barrel file, similar to how the ORM generator produces orm/index.ts (see graphql/codegen/src/core/codegen/orm/client-generator.ts:338). Add a barrel file generation step in graphql/codegen/src/core/codegen/cli/index.ts inside the generateCli function (around line 69) that creates an index.ts file re-exporting from the appropriate CLI modules (e.g., commands.ts, executor.ts). This file should be added to the files array before the function returns.
Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

- Add cli-generator.test.ts with 13 tests covering all generated files
- Snapshot executor, context, auth, table commands, custom commands, command map
- Verify ORM method calls in table and custom commands
- Verify appstash config-store usage in executor
- Fix: runOrm now includes runCli so ORM is auto-enabled when CLI is enabled
- Add docs-generator.ts that produces README.md (overview, setup, commands)
  and COMMANDS.md (man-page style reference with synopsis, options, examples)
- Wire into CLI orchestrator so docs are always generated alongside commands
- Update snapshot tests: 15 tests, 10 snapshots covering all generated files
…CP, Skills)

- Add DocsConfig interface: { readme, agents, mcp, skills } with boolean flags
- Defaults: readme + agents on, mcp + skills opt-in. docs: true enables all.
- generateAgentsDocs(): structured AGENTS.md for LLM consumption with TOOL
  sections, INPUT/OUTPUT schemas, WORKFLOWS, and ERROR HANDLING
- generateMcpConfig(): mcp.json with typed inputSchema per command (JSON Schema)
- generateSkills(): per-command .md skill files for agent systems
- Wire into cli/index.ts orchestrator with config-driven conditional generation
- 22 tests, 17 snapshots covering all formats and config combinations
@devin-ai-integration devin-ai-integration bot changed the title feat(codegen): add CLI command generator from GraphQL schema feat(codegen): add CLI command generator with agent-ready docs from GraphQL schema Feb 17, 2026
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.

1 participant

Comments