Skip to content

Cursor cli demo#96

Merged
D-K-P merged 8 commits intomainfrom
cursor-cli-demo
Feb 5, 2026
Merged

Cursor cli demo#96
D-K-P merged 8 commits intomainfrom
cursor-cli-demo

Conversation

@D-K-P
Copy link
Member

@D-K-P D-K-P commented Feb 5, 2026

Summary by CodeRabbit

  • New Features

    • Web interface to run the Cursor CLI agent with real-time terminal UI, run controls, model picker, and live event rendering (tool calls, progress, results).
    • API endpoint to start agent runs with a prompt and optional model.
  • Documentation

    • Added README with setup and usage instructions; included example environment config.
  • Chores

    • Project configuration and build setup; expanded ignore patterns for cleaner repos.

@coderabbitai
Copy link

coderabbitai bot commented Feb 5, 2026

Walkthrough

This PR adds a standalone Next.js 16 demo app that runs Cursor's CLI agent as a Trigger.dev task and streams its NDJSON output to a browser terminal. Changes include a Trigger.dev build extension to bundle the cursor-agent, a runtime spawn helper that exposes a ReadableStream of CursorEvent, Trigger task and stream definitions, API route to start runs, React UI components (control bar, terminal, event renderers, agent runner), TypeScript event schemas, configs (Next, Tailwind, PostCSS, Trigger), and README and env example files.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

🚥 Pre-merge checks | ✅ 1 | ❌ 2
❌ Failed checks (1 warning, 1 inconclusive)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 22.73% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Title check ❓ Inconclusive The title 'Cursor cli demo' is vague and generic, using non-descriptive terms that don't convey meaningful information about what is being added or changed in this changeset. Use a more specific title that describes the main change, such as 'Add Cursor CLI agent demo with Trigger.dev integration' or 'Create cursor-cli-demo project with streaming terminal UI'.
✅ Passed checks (1 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch cursor-cli-demo

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@D-K-P
Copy link
Member Author

D-K-P commented Feb 5, 2026

@CodeRabbit review

@coderabbitai
Copy link

coderabbitai bot commented Feb 5, 2026

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

🤖 Fix all issues with AI agents
In `@cursor-cli-demo/app/api/trigger/route.ts`:
- Around line 4-11: The POST handler currently calls req.json() directly which
will throw on malformed JSON; wrap the parsing in a try/catch inside the POST
function (around the req.json() call) and on parse failure return
Response.json({ error: "invalid JSON" }, { status: 400 }); then proceed to set
prompt and model as before; reference the POST function and the prompt/model
variables so you update the block that sets const body = await req.json() and
the subsequent prompt/model validation to handle parse errors cleanly.

In `@cursor-cli-demo/components/terminal.tsx`:
- Around line 48-69: Hooks useRef and useEffect are currently called after an
early return when error is truthy, breaking the Rules of Hooks; move the hook
calls so they're invoked unconditionally before the early return: create the
notified ref via useRef(false) and the useEffect (which watches isComplete,
isFailed, and onComplete and sets notified.current and calls onComplete) above
the error check/return, ensuring useRef, useEffect, and any derived status
variables (status, isRunning, isQueued, isFailed, isComplete) are declared in
the same order on every render so hooks are always called.

In `@cursor-cli-demo/README.md`:
- Around line 15-24: Update the Markdown fenced code blocks in README.md so they
include a language identifier (use "text") instead of plain ```; specifically
change the diagram block starting with "[Browser] <-- Realtime Streams v2 -->
..." and the file tree block starting with "├── app/" (and the similar block
around lines 73-88) from ``` to ```text so markdownlint stops flagging missing
language identifiers and the blocks render with the intended plain-text
formatting.
🧹 Nitpick comments (4)
cursor-cli-demo/trigger/cursor-agent.ts (1)

113-117: Dead code: stderr will always be undefined in the return value.

The condition exitCode !== 0 on line 116 is unreachable because line 108-110 already throws when exitCode !== 0. At this point, exitCode is guaranteed to be 0, so stderr will always be undefined.

🧹 Simplify the return statement
     return {
       exitCode,
       prompt: payload.prompt,
-      stderr: exitCode !== 0 ? stderr : undefined,
     };
cursor-cli-demo/extensions/cursor-cli.ts (1)

65-66: Consider validating bundled node binary exists before copying.

The code validates that entryPoint exists but doesn't check if bundledNode exists before calling copyFileSync. If the bundled node binary is missing, the error will be less informative than the custom error thrown for a missing entry point.

🛡️ Add existence check for bundledNode
+  if (!existsSync(bundledNode)) {
+    throw new Error(
+      `Bundled node binary not found at ${bundledNode}. Contents: ${readdirSync(CURSOR_AGENT_DIR).join(", ")}`,
+    );
+  }
+
   copyFileSync(bundledNode, tmpNode);
   chmodSync(tmpNode, 0o755);
cursor-cli-demo/trigger.config.ts (1)

5-5: Non-null assertion on environment variable may cause confusing errors.

If TRIGGER_PROJECT_REF is not set, process.env.TRIGGER_PROJECT_REF! evaluates to undefined, which may cause confusing errors downstream rather than a clear "missing configuration" error.

🛡️ Add explicit validation
+const projectRef = process.env.TRIGGER_PROJECT_REF;
+if (!projectRef) {
+  throw new Error("TRIGGER_PROJECT_REF environment variable is required");
+}
+
 export default defineConfig({
-  project: process.env.TRIGGER_PROJECT_REF!,
+  project: projectRef,
   runtime: "node",
cursor-cli-demo/components/control-bar.tsx (1)

60-133: Add accessible labels for the form controls (optional).
Input/select rely on placeholder text only; screen readers benefit from explicit labels.

♿ Suggested a11y tweak
         <input
           type="text"
           value={prompt}
           onChange={(e) => setPrompt(e.target.value)}
           disabled={isDisabled}
           placeholder="Describe what to create..."
+          aria-label="Prompt"
           className="flex-1 bg-[var(--terminal-bg)] border border-[var(--terminal-border)] rounded-lg px-4 py-2.5 text-sm text-white placeholder:text-white/30 focus:outline-none focus:border-white/30 disabled:opacity-50 font-[family-name:var(--font-geist-mono)]"
         />

         <select
           value={model}
           onChange={(e) => setModel(e.target.value)}
           disabled={isDisabled}
+          aria-label="Model"
           className="bg-[var(--terminal-bg)] border border-[var(--terminal-border)] rounded-lg px-3 py-2.5 text-sm text-white/80 focus:outline-none focus:border-white/30 disabled:opacity-50"
         >

D-K-P added 5 commits February 5, 2026 10:54
Static header markup now renders server-side instead of shipping
as client JS.
…dexedAccess

- Use type guard instead of `as` casts in parseCursorEvent
- Narrow run output via `in` checks instead of casting to Record
- Enable noUncheckedIndexedAccess in tsconfig for safer indexing
@D-K-P D-K-P marked this pull request as ready for review February 5, 2026 22:33
@D-K-P D-K-P merged commit ca504f7 into main Feb 5, 2026
1 check was pending
@D-K-P D-K-P deleted the cursor-cli-demo branch February 5, 2026 22:35
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 5

🤖 Fix all issues with AI agents
In `@cursor-cli-demo/app/api/trigger/route.ts`:
- Around line 4-25: Add a minimal shared-secret auth gate to the POST handler:
read an authorization header (e.g., req.headers.get("authorization") or a custom
header) and compare it to a secret stored in environment (e.g.,
process.env.DEMO_SHARED_SECRET); if missing or mismatched, return a 401/403 JSON
response before proceeding to parse body or call tasks.trigger; keep the rest of
POST (parsing body, validating prompt, calling tasks.trigger and returning
handle.id and handle.publicAccessToken) unchanged and ensure the comparison
happens prior to invoking cursorAgentTask via tasks.trigger.

In `@cursor-cli-demo/extensions/cursor-cli.ts`:
- Around line 17-25: Replace the unpinned "curl -fsSL https://cursor.com/install
| bash" layer in the context.addLayer block with a deterministic
fetch-and-verify flow: download a specific Cursor CLI release artifact
(referencing the release version), save it to a file, verify its
checksum/signature against a pinned value, then install or extract into
CURSOR_AGENT_DIR; update the RUN line that currently invokes curl | bash to
instead perform the download, checksum/signature verification, and controlled
install (or vendor the binary into the repo and copy it into CURSOR_AGENT_DIR)
to eliminate the supply-chain risk.

In `@cursor-cli-demo/lib/cursor-events.ts`:
- Around line 69-78: isCursorEvent currently only verifies that data.type is a
known string, which allows malformed payloads with missing per-type fields to
pass; update isCursorEvent to perform minimal shape validation per event type
(e.g., for type "move" require numeric x and y, for "enter"/"leave" require a
string userId, for "message" require a string text, etc.) so it returns true
only when required fields exist and have correct primitive types, and adjust
parseCursorEvent to continue returning data when isCursorEvent passes; reference
isCursorEvent and parseCursorEvent and the knownTypes set when adding these
per-type checks.
- Around line 81-90: The getToolName and getToolArgs helpers are unsafely
casting Object.keys to ToolCallKind and returning arbitrary keys; update
getToolName to iterate Object.keys(event.tool_call) and return the first key
that is present in the known ToolCallKind union (e.g., compare against a Set or
array of allowed ToolCallKind values) otherwise return "unknown", and update
getToolArgs to check the name !== "unknown" and defensively validate that
event.tool_call[name]?.args is an object (Record<string, unknown>) before
returning it, falling back to {} if the shape is invalid; refer to getToolName,
getToolArgs, ToolCallEvent, ToolCallKind and event.tool_call when locating the
changes.

In `@cursor-cli-demo/trigger/cursor-agent.ts`:
- Around line 20-31: Add a precondition in run (CursorAgentPayload) to fail fast
if the CURSOR_API_KEY env var is missing: check process.env.CURSOR_API_KEY at
the start of run, log an error via logger (include workspace and model context
from existing variables), and throw or return an error/exit before calling
spawnCursorAgent so the process does not proceed to spawnCursorAgent(...)
without a key; reference the run function, process.env.CURSOR_API_KEY, logger,
and spawnCursorAgent when implementing this guard.
🧹 Nitpick comments (1)
cursor-cli-demo/components/agent-runner.tsx (1)

9-27: Show the terminal on failed runs to preserve logs.

Hiding the terminal on failure removes run output that could help debug. Consider including the failed state in the terminal visibility check.

🔧 Proposed tweak
-  const showTerminal = runState.status === "running" || runState.status === "complete";
+  const showTerminal =
+    runState.status === "running" ||
+    runState.status === "complete" ||
+    runState.status === "failed";

Comment on lines +4 to +25
export async function POST(req: Request) {
let body: unknown;
try {
body = await req.json();
} catch {
return Response.json({ error: "invalid JSON" }, { status: 400 });
}

const parsed = body as Record<string, unknown>;
const prompt = typeof parsed.prompt === "string" ? parsed.prompt.trim() : "";
const model = typeof parsed.model === "string" ? parsed.model : undefined;

if (!prompt) {
return Response.json({ error: "prompt is required" }, { status: 400 });
}

const handle = await tasks.trigger<typeof cursorAgentTask>("cursor-agent", { prompt, model });

return Response.json({
runId: handle.id,
publicAccessToken: handle.publicAccessToken,
});
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Add an auth gate to prevent unauthenticated task triggers.

The endpoint currently allows anyone to trigger runs, which is a security/cost risk for any deployed demo.

🛡️ Minimal shared-secret guard
 export async function POST(req: Request) {
+  const expectedToken = process.env.CURSOR_DEMO_API_TOKEN;
+  if (!expectedToken) {
+    return Response.json({ error: "server misconfigured" }, { status: 500 });
+  }
+
+  const authHeader = req.headers.get("authorization");
+  if (authHeader !== `Bearer ${expectedToken}`) {
+    return Response.json({ error: "unauthorized" }, { status: 401 });
+  }
+
   let body: unknown;
   try {
     body = await req.json();
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
export async function POST(req: Request) {
let body: unknown;
try {
body = await req.json();
} catch {
return Response.json({ error: "invalid JSON" }, { status: 400 });
}
const parsed = body as Record<string, unknown>;
const prompt = typeof parsed.prompt === "string" ? parsed.prompt.trim() : "";
const model = typeof parsed.model === "string" ? parsed.model : undefined;
if (!prompt) {
return Response.json({ error: "prompt is required" }, { status: 400 });
}
const handle = await tasks.trigger<typeof cursorAgentTask>("cursor-agent", { prompt, model });
return Response.json({
runId: handle.id,
publicAccessToken: handle.publicAccessToken,
});
export async function POST(req: Request) {
const expectedToken = process.env.CURSOR_DEMO_API_TOKEN;
if (!expectedToken) {
return Response.json({ error: "server misconfigured" }, { status: 500 });
}
const authHeader = req.headers.get("authorization");
if (authHeader !== `Bearer ${expectedToken}`) {
return Response.json({ error: "unauthorized" }, { status: 401 });
}
let body: unknown;
try {
body = await req.json();
} catch {
return Response.json({ error: "invalid JSON" }, { status: 400 });
}
const parsed = body as Record<string, unknown>;
const prompt = typeof parsed.prompt === "string" ? parsed.prompt.trim() : "";
const model = typeof parsed.model === "string" ? parsed.model : undefined;
if (!prompt) {
return Response.json({ error: "prompt is required" }, { status: 400 });
}
const handle = await tasks.trigger<typeof cursorAgentTask>("cursor-agent", { prompt, model });
return Response.json({
runId: handle.id,
publicAccessToken: handle.publicAccessToken,
});
}
🤖 Prompt for AI Agents
In `@cursor-cli-demo/app/api/trigger/route.ts` around lines 4 - 25, Add a minimal
shared-secret auth gate to the POST handler: read an authorization header (e.g.,
req.headers.get("authorization") or a custom header) and compare it to a secret
stored in environment (e.g., process.env.DEMO_SHARED_SECRET); if missing or
mismatched, return a 401/403 JSON response before proceeding to parse body or
call tasks.trigger; keep the rest of POST (parsing body, validating prompt,
calling tasks.trigger and returning handle.id and handle.publicAccessToken)
unchanged and ensure the comparison happens prior to invoking cursorAgentTask
via tasks.trigger.

Comment on lines +17 to +25
context.addLayer({
id: "cursor-cli",
image: {
instructions: [
"RUN apt-get update && apt-get install -y curl ca-certificates && rm -rf /var/lib/apt/lists/*",
'ENV PATH="/root/.local/bin:$PATH"',
"RUN curl -fsSL https://cursor.com/install | bash",
// Copy the resolved index.js + deps to a fixed path so we can invoke with process.execPath at runtime
`RUN cp -r $(dirname $(readlink -f /root/.local/bin/cursor-agent)) ${CURSOR_AGENT_DIR}`,
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🌐 Web query:

Cursor CLI official installation method Linux versioning checksums

💡 Result:

Official install method (Linux)

  • Run the installer script:
    curl -fsS https://cursor.com/install | bash  [1][2]

Versioning / where the version is defined

  • The installer currently hard-codes a build version in the download path and install directory (example shown in the script: 2026.01.28-fd13201). It installs into:
    ~/.local/share/cursor-agent/versions/<version>/ and then symlinks ~/.local/bin/agent and ~/.local/bin/cursor-agent to that version. [3]

How to check the installed version

  • Official verification command:
    cursor-agent --version  [1]

Checksums / integrity verification

  • The official installer script downloads the tarball over HTTPS and extracts it, but (as written) does not perform any checksum/signature verification (no SHA256/SHA512/GPG step in the script). [3]

Sources: [1] Cursor docs (CLI installation) • [2] Cursor CLI page • [3] https://cursor.com/install installer script content


🌐 Web query:

cursor-agent Linux installation documentation github

💡 Result:

  • Official Cursor Agent (cursor-agent) Linux install docs: Cursor CLI “Installation” page (macOS/Linux/Windows WSL) shows the one-liner installer and verification step (cursor-agent --version). [1]
  • Official install command (same as docs, on cursor.com): curl https://cursor.com/install -fsS | bash (Cursor CLI page). [2]
  • If you specifically want a GitHub-based Linux installer script (community): watzon/cursor-linux-installer provides an install script that sets up a cursor command and handles updates. [3]

Sources: [1] [2] [3]


Avoid unpinned curl | bash installs in the build layer.

The official Cursor CLI installer (curl -fsS https://cursor.com/install | bash) downloads and executes without checksum or signature verification, creating a supply-chain risk. Prefer downloading a specific version and validating its checksum, or vendor the binary in the repo.

🤖 Prompt for AI Agents
In `@cursor-cli-demo/extensions/cursor-cli.ts` around lines 17 - 25, Replace the
unpinned "curl -fsSL https://cursor.com/install | bash" layer in the
context.addLayer block with a deterministic fetch-and-verify flow: download a
specific Cursor CLI release artifact (referencing the release version), save it
to a file, verify its checksum/signature against a pinned value, then install or
extract into CURSOR_AGENT_DIR; update the RUN line that currently invokes curl |
bash to instead perform the download, checksum/signature verification, and
controlled install (or vendor the binary into the repo and copy it into
CURSOR_AGENT_DIR) to eliminate the supply-chain risk.

Comment on lines +69 to +78
function isCursorEvent(data: unknown): data is CursorEvent {
if (typeof data !== "object" || data === null) return false;
if (!("type" in data)) return false;
return typeof data.type === "string" && knownTypes.has(data.type);
}

/** Parse raw JSON into a CursorEvent, returns null for unknown types */
export function parseCursorEvent(data: unknown): CursorEvent | null {
return isCursorEvent(data) ? data : null;
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Tighten runtime validation before accepting external input.
Line 69-73 only checks type, so malformed payloads with a valid type will pass and can crash downstream renderers when required fields are accessed. Add minimal per-type shape checks before returning true.

🔧 Suggested hardening for isCursorEvent
 function isCursorEvent(data: unknown): data is CursorEvent {
   if (typeof data !== "object" || data === null) return false;
   if (!("type" in data)) return false;
-  return typeof data.type === "string" && knownTypes.has(data.type);
+  if (typeof (data as any).type !== "string" || !knownTypes.has((data as any).type)) return false;
+  switch ((data as any).type) {
+    case "system":
+      return (data as any).subtype === "init" && typeof (data as any).session_id === "string";
+    case "user":
+    case "assistant":
+      return (
+        typeof (data as any).session_id === "string" &&
+        (data as any).message?.role === (data as any).type &&
+        Array.isArray((data as any).message?.content)
+      );
+    case "tool_call":
+      return (
+        typeof (data as any).session_id === "string" &&
+        typeof (data as any).call_id === "string" &&
+        typeof (data as any).tool_call === "object" &&
+        (data as any).tool_call !== null
+      );
+    case "result":
+      return (
+        typeof (data as any).session_id === "string" &&
+        typeof (data as any).result === "string" &&
+        typeof (data as any).duration_ms === "number"
+      );
+    default:
+      return false;
+  }
 }
🤖 Prompt for AI Agents
In `@cursor-cli-demo/lib/cursor-events.ts` around lines 69 - 78, isCursorEvent
currently only verifies that data.type is a known string, which allows malformed
payloads with missing per-type fields to pass; update isCursorEvent to perform
minimal shape validation per event type (e.g., for type "move" require numeric x
and y, for "enter"/"leave" require a string userId, for "message" require a
string text, etc.) so it returns true only when required fields exist and have
correct primitive types, and adjust parseCursorEvent to continue returning data
when isCursorEvent passes; reference isCursorEvent and parseCursorEvent and the
knownTypes set when adding these per-type checks.

Comment on lines +81 to +90
export function getToolName(event: ToolCallEvent): ToolCallKind | "unknown" {
const keys = Object.keys(event.tool_call) as ToolCallKind[];
return keys[0] ?? "unknown";
}

/** Extract the tool args from a tool_call event */
export function getToolArgs(event: ToolCallEvent): Record<string, unknown> {
const name = getToolName(event);
if (name === "unknown") return {};
return event.tool_call[name]?.args ?? {};
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Avoid unsafe key casting in tool-call helpers.
Line 82 casts Object.keys() to ToolCallKind[], so an unexpected key can be returned as a valid tool kind. Filter against the allowed set and defensively validate args.

✅ Safer tool key selection and args shape guard
 export function getToolName(event: ToolCallEvent): ToolCallKind | "unknown" {
-  const keys = Object.keys(event.tool_call) as ToolCallKind[];
-  return keys[0] ?? "unknown";
+  const toolCallKinds: ToolCallKind[] = [
+    "shellToolCall",
+    "readToolCall",
+    "editToolCall",
+    "writeToolCall",
+    "deleteToolCall",
+    "grepToolCall",
+    "lsToolCall",
+    "globToolCall",
+    "todoToolCall",
+  ];
+  const key = Object.keys(event.tool_call).find(
+    (candidate): candidate is ToolCallKind =>
+      toolCallKinds.includes(candidate as ToolCallKind)
+  );
+  return key ?? "unknown";
 }
 
 export function getToolArgs(event: ToolCallEvent): Record<string, unknown> {
   const name = getToolName(event);
   if (name === "unknown") return {};
-  return event.tool_call[name]?.args ?? {};
+  const args = event.tool_call[name]?.args;
+  return args && typeof args === "object" && !Array.isArray(args) ? args : {};
 }
🤖 Prompt for AI Agents
In `@cursor-cli-demo/lib/cursor-events.ts` around lines 81 - 90, The getToolName
and getToolArgs helpers are unsafely casting Object.keys to ToolCallKind and
returning arbitrary keys; update getToolName to iterate
Object.keys(event.tool_call) and return the first key that is present in the
known ToolCallKind union (e.g., compare against a Set or array of allowed
ToolCallKind values) otherwise return "unknown", and update getToolArgs to check
the name !== "unknown" and defensively validate that event.tool_call[name]?.args
is an object (Record<string, unknown>) before returning it, falling back to {}
if the shape is invalid; refer to getToolName, getToolArgs, ToolCallEvent,
ToolCallKind and event.tool_call when locating the changes.

Comment on lines +20 to +31
run: async (payload: CursorAgentPayload) => {
const workspace = `/tmp/workspace-${Date.now()}`;
mkdirSync(workspace, { recursive: true });

const model = payload.model ?? "sonnet-4.5";

logger.info("Spawning cursor-agent", { workspace, model });

const agent = spawnCursorAgent(
["-p", "--force", "--output-format", "stream-json", "--model", model, payload.prompt],
{ cwd: workspace, env: { CURSOR_API_KEY: process.env.CURSOR_API_KEY } },
);
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Fail fast when CURSOR_API_KEY is missing.

Without an early check, runs will fail later with a less clear error.

🧯 Proposed guard
   run: async (payload: CursorAgentPayload) => {
     const workspace = `/tmp/workspace-${Date.now()}`;
     mkdirSync(workspace, { recursive: true });
 
     const model = payload.model ?? "sonnet-4.5";
+    const cursorApiKey = process.env.CURSOR_API_KEY;
+    if (!cursorApiKey) {
+      throw new Error("CURSOR_API_KEY is required");
+    }
 
     logger.info("Spawning cursor-agent", { workspace, model });
 
     const agent = spawnCursorAgent(
       ["-p", "--force", "--output-format", "stream-json", "--model", model, payload.prompt],
-      { cwd: workspace, env: { CURSOR_API_KEY: process.env.CURSOR_API_KEY } },
+      { cwd: workspace, env: { CURSOR_API_KEY: cursorApiKey } },
     );
🤖 Prompt for AI Agents
In `@cursor-cli-demo/trigger/cursor-agent.ts` around lines 20 - 31, Add a
precondition in run (CursorAgentPayload) to fail fast if the CURSOR_API_KEY env
var is missing: check process.env.CURSOR_API_KEY at the start of run, log an
error via logger (include workspace and model context from existing variables),
and throw or return an error/exit before calling spawnCursorAgent so the process
does not proceed to spawnCursorAgent(...) without a key; reference the run
function, process.env.CURSOR_API_KEY, logger, and spawnCursorAgent when
implementing this guard.

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.

2 participants