Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 6 additions & 3 deletions docs/architecture.md
Original file line number Diff line number Diff line change
Expand Up @@ -65,13 +65,16 @@ built-ins by reusing the same slug.
- **Outputs:** FileRecord `findings[]` populated, `status: "analyzed"`,
`analysisHistory[]` appended.

Two agent backends are supported, both routed through Vercel AI Gateway
by default:
Three agent backends are supported:

| `--agent` | SDK | Default model |
| `--agent` | Runtime | Default model |
|---|---|---|
| `codex` (default) | `@openai/codex-sdk` | `gpt-5.5` |
| `claude` | `@anthropic-ai/claude-agent-sdk` | `claude-opus-4-7` |
| `cursor` | Cursor Agent CLI (`agent`) | `composer-2.5` |

`codex` and `claude` route through Vercel AI Gateway by default; `cursor`
uses Cursor credentials (`CURSOR_API_KEY` or `agent login`).

Same prompt, same JSON output schema. You can mix backends within a
project — re-process a file with a different agent and the second run's
Expand Down
2 changes: 1 addition & 1 deletion docs/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ see [`samples/webapp/deepsec.config.ts`](../samples/webapp/deepsec.config.ts).
| `projects` | `ProjectDeclaration[]` | The codebases deepsec knows about. |
| `plugins` | `DeepsecPlugin[]` | Loaded in order; later plugins override single-slot capabilities. |
| `matchers` | `{ only?: string[]; exclude?: string[] }` | Filter the matcher set used by `scan`. |
| `defaultAgent` | `string` | Default `--agent` value (`codex` or `claude`). See [models.md](models.md). |
| `defaultAgent` | `string` | Default `--agent` value (`codex`, `claude`, or `cursor`). See [models.md](models.md). |
| `dataDir` | `string` | Override the `data/` directory. Defaults to `./data`. |

## ProjectDeclaration
Expand Down
13 changes: 11 additions & 2 deletions docs/models.md
Original file line number Diff line number Diff line change
@@ -1,18 +1,23 @@
# Models

deepsec talks to LLMs through two interchangeable backends:
deepsec talks to LLMs through interchangeable agent backends:

| Backend | Default model | Used by |
|-----------------------------|-----------------------|------------------------------|
| `codex` (default) | `gpt-5.5` | `process`, `revalidate` |
| `claude` | `claude-opus-4-7` | `process`, `revalidate` |
| `cursor` | `composer-2.5` | `process`, `revalidate` |
| `claude` (triage) | `claude-sonnet-4-6` | `triage` (Claude-only) |

Both backends route through [Vercel AI Gateway](https://vercel.com/ai-gateway)
`codex` and `claude` route through [Vercel AI Gateway](https://vercel.com/ai-gateway)
by default, so a single token covers Claude **and** Codex. To use
Anthropic or OpenAI directly, point `ANTHROPIC_BASE_URL` /
`OPENAI_BASE_URL` at the provider.

`cursor` uses the [Cursor Agent CLI](https://cursor.com/docs) (`agent` on
PATH). Set `CURSOR_API_KEY` in `.env.local`, or run `agent login` on the
host. List models with `agent --list-models`.

## CLI selection

```bash
Expand All @@ -28,6 +33,10 @@ pnpm deepsec process --project-id my-app --agent codex
# Codex backend, specific model:
pnpm deepsec process --project-id my-app --agent codex --model gpt-5.4

# Cursor Agent CLI (local `agent` binary or CURSOR_API_KEY):
pnpm deepsec process --project-id my-app --agent cursor
pnpm deepsec process --project-id my-app --agent cursor --model gpt-5.3-codex-high

# Triage uses Claude; pass a cheaper model if you want:
pnpm deepsec triage --project-id my-app --model claude-haiku-4-5
```
Expand Down
23 changes: 23 additions & 0 deletions packages/deepsec/src/__tests__/preflight.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,10 +35,12 @@ describe("assertAgentCredential", () => {
OPENAI_API_KEY: process.env.OPENAI_API_KEY,
CLAUDE_HOME: process.env.CLAUDE_HOME,
CODEX_HOME: process.env.CODEX_HOME,
CURSOR_API_KEY: process.env.CURSOR_API_KEY,
PATH: process.env.PATH,
};
delete process.env.ANTHROPIC_AUTH_TOKEN;
delete process.env.OPENAI_API_KEY;
delete process.env.CURSOR_API_KEY;
// Point CLAUDE_HOME / CODEX_HOME and PATH at empty tmp dirs so the
// suite is hermetic — the dev running tests may have a real
// ~/.codex/auth.json or `claude` on $PATH, which would cause
Expand Down Expand Up @@ -115,6 +117,27 @@ describe("assertAgentCredential", () => {
expect(() => assertAgentCredential("codex")).toThrow(/AI_GATEWAY_API_KEY/);
});

it("passes for cursor when CURSOR_API_KEY is set", () => {
process.env.CURSOR_API_KEY = "x";
expect(() => assertAgentCredential("cursor")).not.toThrow();
});

it("passes for cursor when `agent` is on PATH (login mode)", () => {
writeFileSync(join(emptyPathDir, "agent"), "#!/bin/sh\n", { mode: 0o755 });
expect(() => assertAgentCredential("cursor")).not.toThrow();
});

it("throws actionable message for cursor when no key and no agent CLI", () => {
expect(() => assertAgentCredential("cursor")).toThrow(/--agent cursor/);
expect(() => assertAgentCredential("cursor")).toThrow(/CURSOR_API_KEY/);
expect(() => assertAgentCredential("cursor")).toThrow(/agent login/);
});

it("ignores cursor login auth in sandbox mode", () => {
writeFileSync(join(emptyPathDir, "agent"), "#!/bin/sh\n", { mode: 0o755 });
expect(() => assertAgentCredential("cursor", { inSandbox: true })).toThrow(/CURSOR_API_KEY/);
});

it("skips the credential check for custom plugin agents", () => {
// Tests use this so a stub agent registered via plugins[] doesn't
// require fake ANTHROPIC_AUTH_TOKEN env vars.
Expand Down
4 changes: 4 additions & 0 deletions packages/deepsec/src/__tests__/resolve-agent-type.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,4 +30,8 @@ describe("resolveAgentType", () => {
setLoadedConfig(defineConfig({ projects: [] }));
expect(resolveAgentType(undefined)).toBe("codex");
});

it("passes cursor through unchanged", () => {
expect(resolveAgentType("cursor")).toBe("cursor");
});
});
2 changes: 2 additions & 0 deletions packages/deepsec/src/agent-defaults.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ export function defaultModelForAgent(agentType: string): string {
switch (agentType) {
case "codex":
return "gpt-5.5";
case "cursor":
return "composer-2.5";
default:
return "claude-opus-4-7";
}
Expand Down
8 changes: 4 additions & 4 deletions packages/deepsec/src/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -132,11 +132,11 @@ program
.option("--run-id <id>", "Resume a specific processing run")
.option(
"--agent <type>",
"Agent plugin type: codex or claude (default: defaultAgent in deepsec.config.ts, else codex)",
"Agent plugin type: codex, claude, or cursor (default: defaultAgent in deepsec.config.ts, else codex)",
)
.option(
"--model <model>",
"Model to use (default: claude-opus-4-7 for claude, gpt-5.5 for codex)",
"Model to use (default: claude-opus-4-7 for claude, gpt-5.5 for codex, composer-2.5 for cursor)",
)
.option("--max-turns <n>", "Max conversation turns per batch (default: 150)", parseInt)
.option(
Expand Down Expand Up @@ -192,11 +192,11 @@ program
.option("--run-id <id>", "Resume a specific revalidation run")
.option(
"--agent <type>",
"Agent plugin type: codex or claude (default: defaultAgent in deepsec.config.ts, else codex)",
"Agent plugin type: codex, claude, or cursor (default: defaultAgent in deepsec.config.ts, else codex)",
)
.option(
"--model <model>",
"Model to use (default: claude-opus-4-7 for claude, gpt-5.5 for codex)",
"Model to use (default: claude-opus-4-7 for claude, gpt-5.5 for codex, composer-2.5 for cursor)",
)
.option("--max-turns <n>", "Max conversation turns per batch (default: 150)", parseInt)
.option(
Expand Down
22 changes: 21 additions & 1 deletion packages/deepsec/src/preflight.ts
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,14 @@ function isCodex(agentType: string | undefined): boolean {
return agentType === "codex";
}

function isCursor(agentType: string | undefined): boolean {
return agentType === "cursor";
}

function hasLocalCursorAgent(): boolean {
return whichSync("agent");
}

/**
* Walk `$PATH` looking for a binary. Used as a positive signal that an
* agent CLI (`claude`, `codex`) is set up on this host — if it's
Expand Down Expand Up @@ -138,7 +146,7 @@ function hasLocalCodexAgent(): boolean {
// via plugins (deepsec.config.ts → plugins: [{ agents: [...] }]) handle
// their own credential resolution, so we skip the check for anything
// other than these.
const KNOWN_BACKENDS = new Set<string>(["claude-agent-sdk", "codex"]);
const KNOWN_BACKENDS = new Set<string>(["claude-agent-sdk", "codex", "cursor"]);

/**
* Verify the orchestrator has an AI credential the chosen agent can use.
Expand Down Expand Up @@ -177,6 +185,18 @@ export function assertAgentCredential(
);
}

if (isCursor(agentType)) {
if (process.env.CURSOR_API_KEY) return;
if (!options.inSandbox && hasLocalCursorAgent()) return;
throw new Error(
`Missing AI credentials for --agent cursor.\n` +
`\n` +
` Add to .env.local: CURSOR_API_KEY=…\n` +
` Or install the Cursor CLI and run \`agent login\` on this host.\n` +
` Setup: ${SETUP_DOC_URL}`,
);
}

if (anthropic) return;
if (!options.inSandbox && hasLocalClaudeAgent()) return;
const displayAgent =
Expand Down
5 changes: 3 additions & 2 deletions packages/processor/src/__tests__/registry.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,10 +52,11 @@ describe("AgentRegistry", () => {
});

describe("createDefaultAgentRegistry", () => {
it("registers both backends", () => {
it("registers built-in backends", () => {
const registry = createDefaultAgentRegistry();
expect(registry.get("claude-agent-sdk")).toBeDefined();
expect(registry.get("codex")).toBeDefined();
expect(registry.types().sort()).toEqual(["claude-agent-sdk", "codex"]);
expect(registry.get("cursor")).toBeDefined();
expect(registry.types().sort()).toEqual(["claude-agent-sdk", "codex", "cursor"]);
});
});
Loading