|
| 1 | +--- |
| 2 | +title: "Background Cursor agent using the Cursor CLI" |
| 3 | +sidebarTitle: "Cursor background agent" |
| 4 | +description: "Run Cursor's headless CLI agent in a Trigger.dev task and stream the live output to the frontend using Trigger.dev Realtime Streams." |
| 5 | +--- |
| 6 | + |
| 7 | +import RealtimeLearnMore from "/snippets/realtime-learn-more.mdx"; |
| 8 | + |
| 9 | +## Overview |
| 10 | + |
| 11 | +This example runs [Cursor's headless CLI](https://cursor.com/cli) in a Trigger.dev task. The agent spawns as a child process, and its NDJSON stdout is parsed and piped to the browser in real-time using [Realtime Streams](/realtime/react-hooks/streams). The result is a live terminal UI that renders each Cursor event (system messages, assistant responses, tool calls, results) as it happens. |
| 12 | + |
| 13 | +**Tech stack:** |
| 14 | + |
| 15 | +- **[Next.js](https://nextjs.org/)** for the web app (App Router with server actions) |
| 16 | +- **[Cursor CLI](https://cursor.com/cli)** for the headless AI coding agent |
| 17 | +- **[Trigger.dev](https://trigger.dev)** for task orchestration, real-time streaming, and deployment |
| 18 | + |
| 19 | +## Video |
| 20 | + |
| 21 | +<video |
| 22 | + controls |
| 23 | + className="w-full aspect-video" |
| 24 | + src="https://github.com/user-attachments/assets/459aa160-6659-478e-868f-32e74f79d21a" |
| 25 | +></video> |
| 26 | + |
| 27 | +**Features:** |
| 28 | + |
| 29 | +- **Build extensions**: Installs the `cursor-agent` binary into the task container image using `addLayer`, demonstrating how to ship system binaries with your tasks |
| 30 | +- **Realtime Streams v2**: NDJSON from a child process stdout is parsed and piped directly to the browser using `streams.define()` and `.pipe()` |
| 31 | +- **Live terminal rendering**: Each Cursor event renders as a distinct row with auto-scroll |
| 32 | +- **Long-running tasks**: Cursor agent runs for minutes; Trigger.dev handles lifecycle, timeouts, and retries automatically |
| 33 | +- **Machine selection**: Uses the `medium-2x` preset for resource-intensive CLI tools |
| 34 | +- **LLM model picker**: Switch between models from the UI before triggering a run |
| 35 | + |
| 36 | +## GitHub repo |
| 37 | + |
| 38 | +<Card |
| 39 | + title="View the Cursor background agent repo" |
| 40 | + icon="GitHub" |
| 41 | + href="https://github.com/triggerdotdev/examples/tree/main/cursor-cli-demo" |
| 42 | +> |
| 43 | + Click here to view the full code for this project in our examples repository on GitHub. You can |
| 44 | + fork it and use it as a starting point for your own project. |
| 45 | +</Card> |
| 46 | + |
| 47 | +## How it works |
| 48 | + |
| 49 | +### Task orchestration |
| 50 | + |
| 51 | +The task spawns the Cursor CLI as a child process and streams its output to the frontend: |
| 52 | + |
| 53 | +1. A Next.js server action triggers the `cursor-agent` task with the user's prompt and selected model |
| 54 | +2. The task spawns the Cursor CLI binary using a helper that returns a typed NDJSON stream and a `waitUntilExit()` promise |
| 55 | +3. Each line of NDJSON stdout is parsed into typed Cursor events and piped to a Realtime Stream |
| 56 | +4. The frontend subscribes to the stream using `useRealtimeRunWithStreams` and renders each event in a terminal UI |
| 57 | +5. The task waits for the CLI process to exit and returns the result |
| 58 | + |
| 59 | +### Build extension for system binaries |
| 60 | + |
| 61 | +The example includes a custom build extension that installs the `cursor-agent` binary into the container image using `addLayer`. At runtime, the binary is copied to `/tmp` and given execute permissions; this is a workaround needed when the container runtime strips execute permissions from added layers. |
| 62 | + |
| 63 | +```ts extensions/cursor-cli.ts |
| 64 | +export const cursorCli = defineExtension({ |
| 65 | + name: "cursor-cli", |
| 66 | + onBuildComplete(params) { |
| 67 | + params.addLayer({ |
| 68 | + id: "cursor-cli", |
| 69 | + image: { |
| 70 | + instructions: [ |
| 71 | + `COPY cursor-agent /usr/local/bin/cursor-agent`, |
| 72 | + `RUN chmod +x /usr/local/bin/cursor-agent`, |
| 73 | + ], |
| 74 | + }, |
| 75 | + }); |
| 76 | + }, |
| 77 | +}); |
| 78 | +``` |
| 79 | + |
| 80 | +### Streaming with Realtime Streams v2 |
| 81 | + |
| 82 | +The stream is defined with a typed schema and piped from the child process: |
| 83 | + |
| 84 | +```ts trigger/cursor-stream.ts |
| 85 | +export const cursorStream = streams.define("cursor", cursorEventSchema); |
| 86 | +``` |
| 87 | + |
| 88 | +```ts trigger/cursor-agent.ts |
| 89 | +const { stream, waitUntilExit } = spawnCursorAgent({ prompt, model }); |
| 90 | +cursorStream.pipe(stream); |
| 91 | +await waitUntilExit(); |
| 92 | +``` |
| 93 | + |
| 94 | +On the frontend, the `useRealtimeRunWithStreams` hook subscribes to these events and renders them as they arrive. |
| 95 | + |
| 96 | +## Relevant code |
| 97 | + |
| 98 | +- **Build extension + spawn helper**: [extensions/cursor-cli.ts](https://github.com/triggerdotdev/examples/blob/main/cursor-cli-demo/extensions/cursor-cli.ts): installs the binary and provides a typed NDJSON stream with `waitUntilExit()` |
| 99 | +- **Task definition**: [trigger/cursor-agent.ts](https://github.com/triggerdotdev/examples/blob/main/cursor-cli-demo/trigger/cursor-agent.ts): spawns the CLI, pipes the stream, waits for exit |
| 100 | +- **Stream definition**: [trigger/cursor-stream.ts](https://github.com/triggerdotdev/examples/blob/main/cursor-cli-demo/trigger/cursor-stream.ts): Realtime Streams v2 stream with typed schema |
| 101 | +- **Terminal UI**: [components/terminal.tsx](https://github.com/triggerdotdev/examples/blob/main/cursor-cli-demo/components/terminal.tsx): renders live events using `useRealtimeRunWithStreams` |
| 102 | +- **Event types**: [lib/cursor-events.ts](https://github.com/triggerdotdev/examples/blob/main/cursor-cli-demo/lib/cursor-events.ts): TypeScript types and parsers for Cursor NDJSON events |
| 103 | +- **Trigger config**: [trigger.config.ts](https://github.com/triggerdotdev/examples/blob/main/cursor-cli-demo/trigger.config.ts): project config with the cursor CLI build extension |
| 104 | + |
| 105 | +<RealtimeLearnMore /> |
0 commit comments