Skip to content

CLI AI: Add --json flag for headless NDJSON output#2913

Closed
wesleyfantinel wants to merge 12 commits intoAutomattic:trunkfrom
wesleyfantinel:add/cli-studio-ai-json-flag
Closed

CLI AI: Add --json flag for headless NDJSON output#2913
wesleyfantinel wants to merge 12 commits intoAutomattic:trunkfrom
wesleyfantinel:add/cli-studio-ai-json-flag

Conversation

@wesleyfantinel
Copy link
Copy Markdown

@wesleyfantinel wesleyfantinel commented Mar 25, 2026

Related issues

How AI was used in this PR

Claude was used to brainstorm the design, write the implementation plan, and implement the changes. All code was reviewed and validated by the author.

Proposed Changes

  • Add --json flag and positional message argument to studio ai command for headless mode
  • Introduce AiOutputAdapter interface with AiChatUI implementing it directly (no wrapper class)
  • Add JsonAdapter that streams typed NDJSON events to stdout
  • Add --auto-approve as a separate CLI flag, independent of --json (defaults to true in JSON mode, overridable with --no-auto-approve)
  • In JSON mode, agent questions are emitted as question.asked events and the process exits after cleanup
  • Define NDJSON event schema: message, progress, info, error, question.asked, turn.started, turn.completed
  • Fix: running studio ai "hello" in interactive mode now stays in the input loop after the first reply

Usage

# Single turn, streams NDJSON events to stdout
studio ai "create a theme for my site" --json

# JSON mode without auto-approving tools
studio ai "update my theme" --json --no-auto-approve

# Interactive mode with initial message (stays in input loop)
studio ai "hello, what can you do?"

# Validation: --json requires a message
studio ai --json  # Error: --json requires an initial message argument

Files changed

File Change
apps/cli/ai/json-events.ts (new) NDJSON event types and emitEvent() helper
apps/cli/ai/output-adapter.ts (new) AiOutputAdapter interface and JsonAdapter
apps/cli/ai/agent.ts Add autoApprove field to AiAgentConfig, short-circuit canUseTool
apps/cli/ai/ui.ts AiChatUI implements AiOutputAdapter
apps/cli/commands/ai/index.ts Positional message, --json and --auto-approve flags, adapter pattern, single-turn path, initial message fix
apps/cli/commands/ai/sessions/resume.ts Use AiChatUI directly instead of removed InteractiveAdapter
apps/cli/commands/ai/tests/ai.test.ts Tests for JSON mode, auto-approve flag

Testing Instructions

  1. Build the CLI: npm run cli:build
  2. Run in JSON mode:
    node apps/cli/dist/cli/main.mjs ai "hello, what can you do?" --json
  3. Verify NDJSON events are streamed to stdout (one JSON object per line)
  4. Verify turn.started is the first event and turn.completed is the last
  5. Run in interactive mode with initial message and verify it stays in the input loop after the first reply:
    node apps/cli/dist/cli/main.mjs ai "hello"
  6. Run in interactive mode (no flags) and verify the TUI works as before:
    node apps/cli/dist/cli/main.mjs ai
  7. Verify --json without a message shows an error:
    node apps/cli/dist/cli/main.mjs ai --json
  8. Verify --no-auto-approve works with --json:
    node apps/cli/dist/cli/main.mjs ai "hello" --json --no-auto-approve
  9. Run tests: npm test -- apps/cli/commands/ai/tests/ai.test.ts

Pre-merge Checklist

  • Have you checked for TypeScript, React or other console errors?

- Introduced `JsonAdapter` for emitting NDJSON events to stdout.
- Enhanced `runCommand` to support headless mode.
- Updated AI agent to handle `autoApprove` for non-interactive prompts.
- Added tests to validate NDJSON output behavior and error handling.
…andling, so that the type is conformant to zod
@wesleyfantinel wesleyfantinel force-pushed the add/cli-studio-ai-json-flag branch from 2d3ca2e to 21c2375 Compare March 25, 2026 21:08
@wojtekn wojtekn requested a review from a team March 26, 2026 13:42
Copy link
Copy Markdown

@draganescu draganescu left a comment

Choose a reason for hiding this comment

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

Edited

@wojtekn wojtekn mentioned this pull request Mar 30, 2026
wesleyfantinel and others added 2 commits April 1, 2026 09:11
Make costUsd optional since only JsonAdapter provides it, guard
replaySessionHistory behind InteractiveAdapter instanceof check.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Copy link
Copy Markdown
Member

@sejas sejas left a comment

Choose a reason for hiding this comment

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

@wesleyfantinel, thanks for creating this PR. I assume it’s necessary for “Telex.” I was also exploring the Studio AI headless implementation to build the Studio Assistant UI: #2870. I think the best path forward is to merge this PR, and then I can expand this implementation to meet our needs.

I left a few suggestions, but my main concern is implementing autoApprove as a separate parameter. Would that be ok?

Let me know how I can help with this PR.

@wesleyfantinel
Copy link
Copy Markdown
Author

Hey @sejas, thanks for the review and the suggestions. I went through all of them:

  1. autoApprove as a separate option: Done. Added --auto-approve as its own CLI flag. In --json mode it defaults to true.
  2. DEFAULT_MODEL constant: Good catch, fixed. Was hardcoded in JsonAdapter, now uses the constant from agent.ts.
  3. Drop InteractiveAdapter: Agreed with this one. AiChatUI now implements AiOutputAdapter directly. Deleted ~96 lines of pure delegation boilerplate.
  4. process.exit cleanup: Added an onBeforeExit callback to JsonAdapter. It flushes persistQueue and calls ui.stop() before exiting, so cleanup always runs.

Also fixed a bug I found along the way: running studio ai "hello" without --json was exiting after the first reply instead of staying in the interactive loop. The initialMessage path was calling process.exit() regardless of mode.

Let me know if you want any changes to the approach, specially around autoApprove, happy to adjust.

@wesleyfantinel wesleyfantinel requested a review from sejas April 7, 2026 19:01
@sejas sejas mentioned this pull request Apr 8, 2026
1 task
@sejas
Copy link
Copy Markdown
Member

sejas commented Apr 8, 2026

@wesleyfantinel , changes look good. Do you mind fixing the lint and test CI errors?
Because this is not a branch within the same repository the CI is not triggered automatically and I had to create PRs like #3007 to trigger them. To avoid that, you could continue the work in a new internal PR in this repository if you prefer so. You should have permissions.

@wesleyfantinel
Copy link
Copy Markdown
Author

Opened #3012

@sejas
Copy link
Copy Markdown
Member

sejas commented Apr 8, 2026

Awesome! Thank you Wesley!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants