llm-cli-gateway 2.12.2 → 2.13.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,4 +1,4 @@
1
- import type { CliType } from "./session-manager.js";
1
+ import { type CliType } from "./provider-types.js";
2
2
  type ModelSource = "fallback" | "observed" | "config" | "env";
3
3
  type ModelConfidence = "low" | "medium" | "high";
4
4
  export interface ModelMetadata {
@@ -2,6 +2,7 @@ import { existsSync, readFileSync, readdirSync, statSync } from "fs";
2
2
  import { homedir } from "os";
3
3
  import path from "path";
4
4
  import { parse as parseToml } from "smol-toml";
5
+ import { CLI_TYPES } from "./provider-types.js";
5
6
  const FALLBACK_INFO = {
6
7
  claude: {
7
8
  description: "Anthropic's Claude Code CLI - best for code generation, analysis, and agentic coding tasks",
@@ -56,6 +57,15 @@ const FALLBACK_INFO = {
56
57
  },
57
58
  modelOrder: ["opus", "gpt-5.5", "swe-1.6"],
58
59
  },
60
+ cursor: {
61
+ description: "Cursor Agent CLI - agentic coding and review in Cursor's headless terminal agent",
62
+ models: {
63
+ "gpt-5": "OpenAI GPT-5 model accepted by Cursor Agent examples.",
64
+ "sonnet-4-thinking": "Claude Sonnet thinking model accepted by Cursor Agent examples.",
65
+ "claude-opus-4-8": "Parameterized Claude Opus model family accepted by Cursor Agent examples.",
66
+ },
67
+ modelOrder: ["gpt-5", "sonnet-4-thinking", "claude-opus-4-8"],
68
+ },
59
69
  };
60
70
  const MODEL_CACHE_TTL_MS = 2 * 60 * 1000;
61
71
  const MAX_GEMINI_HISTORY_FILES = 200;
@@ -77,14 +87,7 @@ export function getCliInfo(forceRefresh = false) {
77
87
  }
78
88
  export function getAvailableCliInfo(forceRefresh = false) {
79
89
  const info = getCliInfo(forceRefresh);
80
- return {
81
- claude: filterUnverifiedModelHints(info.claude),
82
- codex: filterUnverifiedModelHints(info.codex),
83
- gemini: filterUnverifiedModelHints(info.gemini),
84
- grok: filterUnverifiedModelHints(info.grok),
85
- mistral: filterUnverifiedModelHints(info.mistral),
86
- devin: filterUnverifiedModelHints(info.devin),
87
- };
90
+ return Object.fromEntries(CLI_TYPES.map(cli => [cli, filterUnverifiedModelHints(info[cli])]));
88
91
  }
89
92
  export function clearModelRegistryCache() {
90
93
  cachedInfo = null;
@@ -115,14 +118,7 @@ export function resolveModelAlias(cli, model, info) {
115
118
  return trimmed;
116
119
  }
117
120
  function buildCliInfo() {
118
- const info = {
119
- claude: cloneInfo(FALLBACK_INFO.claude),
120
- codex: cloneInfo(FALLBACK_INFO.codex),
121
- gemini: cloneInfo(FALLBACK_INFO.gemini),
122
- grok: cloneInfo(FALLBACK_INFO.grok),
123
- mistral: cloneInfo(FALLBACK_INFO.mistral),
124
- devin: cloneInfo(FALLBACK_INFO.devin),
125
- };
121
+ const info = Object.fromEntries(CLI_TYPES.map(cli => [cli, cloneInfo(FALLBACK_INFO[cli])]));
126
122
  applyClaudeOverrides(info.claude);
127
123
  applyCodexOverrides(info.codex);
128
124
  applyGeminiOverrides(info.gemini);
package/dist/oauth.js CHANGED
@@ -77,6 +77,10 @@ function readCookie(req, name) {
77
77
  }
78
78
  return null;
79
79
  }
80
+ function oauthCsrfCookie(csrf) {
81
+ const maxAgeSeconds = Math.floor(OAUTH_CODE_TTL_MS / 1000);
82
+ return `gw_oauth_csrf=${csrf}; HttpOnly; Secure; SameSite=Lax; Path=/oauth; Max-Age=${maxAgeSeconds}`;
83
+ }
80
84
  function methodNotAllowed(res) {
81
85
  res.writeHead(405, { allow: "GET, POST", "content-type": "application/json" });
82
86
  res.end(JSON.stringify({ error: "Method not allowed" }));
@@ -466,7 +470,7 @@ ${errorBlock}
466
470
  <p class="muted">Only approve if you initiated this connection.</p></div></body></html>`;
467
471
  res.writeHead(200, {
468
472
  "content-type": "text/html; charset=utf-8",
469
- "set-cookie": `gw_oauth_csrf=${csrf}; HttpOnly; SameSite=Lax; Path=/oauth`,
473
+ "set-cookie": oauthCsrfCookie(csrf),
470
474
  "cache-control": "no-store",
471
475
  });
472
476
  res.end(html);
@@ -116,6 +116,24 @@ const GUIDANCE = {
116
116
  expected: "CLI is installed and `devin auth status` reports an authenticated session",
117
117
  },
118
118
  },
119
+ cursor: {
120
+ provider: "cursor",
121
+ displayName: "Cursor Agent CLI",
122
+ install: {
123
+ summary: "Install Cursor Agent CLI using Cursor's current official installer.",
124
+ commands: ["cursor-agent update", "cursor-agent --version"],
125
+ documentationUrl: "https://cursor.com/cli",
126
+ },
127
+ login: {
128
+ summary: "Sign in through Cursor Agent's official login flow, or set CURSOR_API_KEY for headless automation.",
129
+ commands: ["cursor-agent login", "cursor-agent status"],
130
+ credentialHandling: "Let Cursor store credentials via `cursor-agent login`, or provide CURSOR_API_KEY in the process environment. Do not paste Cursor API keys into prompts or remote chats.",
131
+ },
132
+ verification: {
133
+ command: "cursor-agent status",
134
+ expected: "CLI is installed and `cursor-agent status` reports an authenticated account",
135
+ },
136
+ },
119
137
  };
120
138
  export function getProviderLoginGuidance(provider) {
121
139
  return GUIDANCE[provider];
@@ -1,4 +1,4 @@
1
- import type { CliType } from "./session-manager.js";
1
+ import { type CliType } from "./provider-types.js";
2
2
  import { type ProviderLoginGuidance } from "./provider-login-guidance.js";
3
3
  import { type ApiProviderConfig } from "./config.js";
4
4
  export type ProviderLoginStatus = "authenticated" | "not_authenticated" | "unknown" | "not_checked";
@@ -2,6 +2,7 @@ import { existsSync } from "node:fs";
2
2
  import { homedir } from "node:os";
3
3
  import { join } from "node:path";
4
4
  import { spawnSync } from "node:child_process";
5
+ import { CLI_TYPES } from "./provider-types.js";
5
6
  import { getProviderLoginGuidance } from "./provider-login-guidance.js";
6
7
  import { apiProviderKeyPresent, isApiProviderEnabled } from "./config.js";
7
8
  import { redactDiagnosticUrl } from "./endpoint-exposure.js";
@@ -18,22 +19,15 @@ export function getApiProviderStatus(provider, env = process.env) {
18
19
  enabled: isApiProviderEnabled(provider, env),
19
20
  };
20
21
  }
21
- const PROVIDERS = ["claude", "codex", "gemini", "grok", "mistral", "devin"];
22
- const VERSION_ARGS = {
23
- claude: ["--version"],
24
- codex: ["--version"],
25
- gemini: ["--version"],
26
- grok: ["--version"],
27
- mistral: ["--version"],
28
- devin: ["--version"],
29
- };
22
+ const VERSION_ARGS = Object.fromEntries(CLI_TYPES.map(provider => [provider, ["--version"]]));
30
23
  export const PROVIDER_COMMANDS = {
31
- claude: "claude",
32
- codex: "codex",
24
+ claude: providerCommandName("claude"),
25
+ codex: providerCommandName("codex"),
33
26
  gemini: providerCommandName("gemini"),
34
- grok: "grok",
27
+ grok: providerCommandName("grok"),
35
28
  mistral: providerCommandName("mistral"),
36
29
  devin: providerCommandName("devin"),
30
+ cursor: providerCommandName("cursor"),
37
31
  };
38
32
  const LOGIN_CHECKS = {
39
33
  claude: ["auth", "status", "--json"],
@@ -41,9 +35,10 @@ const LOGIN_CHECKS = {
41
35
  grok: ["inspect", "--json"],
42
36
  mistral: ["auth", "status"],
43
37
  devin: ["auth", "status"],
38
+ cursor: ["status"],
44
39
  };
45
40
  export function listProviderRuntimeStatuses() {
46
- return Object.fromEntries(PROVIDERS.map(provider => [provider, getProviderRuntimeStatus(provider)]));
41
+ return Object.fromEntries(CLI_TYPES.map(provider => [provider, getProviderRuntimeStatus(provider)]));
47
42
  }
48
43
  export function getProviderRuntimeStatus(provider) {
49
44
  const guidance = getProviderLoginGuidance(provider);
@@ -60,6 +60,10 @@ export const ACP_CONTRACT = {
60
60
  classification: "native_candidate",
61
61
  summary: "Cognition Devin CLI exposes a native ACP server via `devin acp` (stdio); Slice D1 initialize + session/new smoke passed (protocolVersion 1, third runtime pilot). Runtime routing stays config-gated.",
62
62
  },
63
+ cursor: {
64
+ classification: "native_candidate",
65
+ summary: "Cursor Agent CLI exposes a hidden native ACP stdio entrypoint via `cursor-agent acp`; initialize + session/new smoke passed locally (protocolVersion 1, session created).",
66
+ },
63
67
  },
64
68
  };
65
69
  const ACP_DOCS_REFERENCE = "docs/plans/first-class-acp-gateway-extension.dag.toml";
@@ -161,6 +165,20 @@ const ACP_CAPABILITIES = {
161
165
  ],
162
166
  docs: ACP_DOCS_REFERENCE,
163
167
  },
168
+ cursor: {
169
+ status: "native_smoke_passed",
170
+ mediation: "native",
171
+ targetVersion: "cursor-agent 2026.06.29-2ad2186",
172
+ entrypoint: { command: "cursor-agent", args: ["acp"] },
173
+ runtimeEnabled: false,
174
+ smokeSupported: true,
175
+ smokeStatus: "passed",
176
+ caveats: [
177
+ "Native ACP via hidden `cursor-agent acp` stdio JSON-RPC entrypoint; manual initialize + session/new smoke passed locally (protocolVersion 1, session created; no agentInfo returned).",
178
+ "Runtime routing stays disabled until ACP is enabled in gateway config.",
179
+ ],
180
+ docs: ACP_DOCS_REFERENCE,
181
+ },
164
182
  };
165
183
  function cloneAcpCapability(acp) {
166
184
  return {
@@ -806,6 +824,71 @@ const TOOL_CONTROLS = {
806
824
  },
807
825
  ],
808
826
  },
827
+ cursor: {
828
+ providerKind: "cli",
829
+ gatewayRequestTools: ["cursor_request", "cursor_request_async"],
830
+ summary: "Cursor Agent CLI runs a headless agentic coding/review session; the gateway passes prompt, model, mode, sandbox/trust controls, workspace roots, and session resume on the CLI transport, with ACP transport gated and fail-closed.",
831
+ controls: {
832
+ allowlist: {
833
+ supported: false,
834
+ behavior: "Cursor Agent exposes force/auto-review modes, not per-request allow lists.",
835
+ },
836
+ denylist: {
837
+ supported: false,
838
+ behavior: "Cursor Agent has no per-request deny-list flag on the tracked surface.",
839
+ },
840
+ mcpServers: {
841
+ supported: false,
842
+ requestField: "mcpServers",
843
+ behavior: "Cursor manages its own MCP configuration via `cursor-agent mcp`; the gateway does not mutate Cursor MCP config.",
844
+ },
845
+ nativeSkills: {
846
+ supported: false,
847
+ behavior: "Cursor owns rules/plugins; the gateway does not discover Cursor-native rules as skills.",
848
+ },
849
+ permissionMode: {
850
+ supported: true,
851
+ requestField: "mode/force/autoReview/sandbox/trust/approvalStrategy/approvalPolicy",
852
+ cliFlag: "--mode/--force/--auto-review/--sandbox/--trust",
853
+ behavior: 'Cursor supports read-only plan/ask modes, Smart Auto-review, force/yolo, sandbox overrides, and workspace trust in headless mode; under approvalStrategy:"mcp_managed", high-impact force/trust/sandbox-disabled requests are gated by the gateway approval manager.',
854
+ },
855
+ promptControl: {
856
+ supported: true,
857
+ requestField: "prompt",
858
+ behavior: "Prompt is passed as the positional prompt to `cursor-agent --print`.",
859
+ },
860
+ session: {
861
+ supported: true,
862
+ requestField: "sessionId/resumeLatest/createNewSession",
863
+ cliFlag: "--resume/--continue",
864
+ behavior: "Resumes a Cursor chat/session (--resume <id>) or the most recent chat (--continue); gateway-created gw-* session ids are tracking ids and are not resumable Cursor chat ids.",
865
+ },
866
+ workspace: {
867
+ supported: true,
868
+ requestField: "workspace/addDir",
869
+ cliFlag: "--workspace/--add-dir",
870
+ behavior: "Sets the Cursor workspace and additional workspace roots; remote HTTP/OAuth callers must use registered workspace aliases/roots rather than raw paths.",
871
+ },
872
+ },
873
+ features: baseFeatures({
874
+ sessionContinuity: true,
875
+ approvalAndSandboxControls: true,
876
+ promptControl: true,
877
+ workspaceControls: true,
878
+ }),
879
+ unsupportedInputs: [
880
+ {
881
+ input: "mcpServers",
882
+ behavior: "not_supported",
883
+ details: "Cursor owns MCP config via `cursor-agent mcp`; gateway request-time MCP server injection is not implemented.",
884
+ },
885
+ {
886
+ input: 'transport:"acp" with CLI-only options',
887
+ behavior: "reject",
888
+ details: "Cursor ACP routing currently accepts prompt/model/session inputs only; mode, outputFormat, workspace, addDir, force, autoReview, sandbox, trust, and prompt/response optimization are rejected instead of silently ignored.",
889
+ },
890
+ ],
891
+ },
809
892
  };
810
893
  const CAPABILITY_CACHE = new Map();
811
894
  export function getProviderToolCapabilities(queryOrCli = {}) {
@@ -1113,6 +1196,8 @@ function skillRoots(cli) {
1113
1196
  return [{ path: path.join(home, ".vibe", "skills"), source: "user" }];
1114
1197
  case "devin":
1115
1198
  return [];
1199
+ case "cursor":
1200
+ return [];
1116
1201
  }
1117
1202
  }
1118
1203
  function readSkill(cli, skillPath, fallbackName, source, warnings, query) {
@@ -0,0 +1,4 @@
1
+ export declare const CLI_TYPES: readonly ["claude", "codex", "gemini", "grok", "mistral", "devin", "cursor"];
2
+ export type CliType = (typeof CLI_TYPES)[number];
3
+ export declare const API_PROVIDER_TYPES: readonly ["grok-api"];
4
+ export type KnownApiProviderType = (typeof API_PROVIDER_TYPES)[number];
@@ -0,0 +1,10 @@
1
+ export const CLI_TYPES = [
2
+ "claude",
3
+ "codex",
4
+ "gemini",
5
+ "grok",
6
+ "mistral",
7
+ "devin",
8
+ "cursor",
9
+ ];
10
+ export const API_PROVIDER_TYPES = ["grok-api"];
@@ -1,14 +1,12 @@
1
1
  import type { Config } from "./config.js";
2
2
  import type { DatabaseConnection } from "./db.js";
3
3
  import type { Logger } from "./logger.js";
4
- export declare const CLI_TYPES: readonly ["claude", "codex", "gemini", "grok", "mistral", "devin"];
5
- export type CliType = (typeof CLI_TYPES)[number];
6
- export declare const API_PROVIDER_TYPES: readonly ["grok-api"];
7
- export type KnownApiProviderType = (typeof API_PROVIDER_TYPES)[number];
4
+ import { API_PROVIDER_TYPES, CLI_TYPES, type CliType, type KnownApiProviderType } from "./provider-types.js";
5
+ export { API_PROVIDER_TYPES, CLI_TYPES, type CliType, type KnownApiProviderType };
8
6
  export type ApiProviderType = KnownApiProviderType | (string & {});
9
7
  export type ProviderType = CliType | ApiProviderType;
10
8
  export type ProviderKind = "cli" | "api";
11
- export declare const PROVIDER_TYPES: readonly ["claude", "codex", "gemini", "grok", "mistral", "devin", "grok-api"];
9
+ export declare const PROVIDER_TYPES: readonly ["claude", "codex", "gemini", "grok", "mistral", "devin", "cursor", "grok-api"];
12
10
  export declare function isCliType(provider: string): provider is CliType;
13
11
  export declare function providerKind(provider: ProviderType): ProviderKind;
14
12
  export declare function defaultSessionDescription(provider: ProviderType): string;
@@ -5,8 +5,8 @@ import { existsSync, mkdirSync, readFileSync, writeFileSync, renameSync, openSyn
5
5
  import { DEFAULT_SESSION_TTL_SECONDS } from "./config.js";
6
6
  import { noopLogger } from "./logger.js";
7
7
  import { getRequestContext, resolveOwnerPrincipal } from "./request-context.js";
8
- export const CLI_TYPES = ["claude", "codex", "gemini", "grok", "mistral", "devin"];
9
- export const API_PROVIDER_TYPES = ["grok-api"];
8
+ import { API_PROVIDER_TYPES, CLI_TYPES, } from "./provider-types.js";
9
+ export { API_PROVIDER_TYPES, CLI_TYPES };
10
10
  export const PROVIDER_TYPES = [...CLI_TYPES, ...API_PROVIDER_TYPES];
11
11
  export function isCliType(provider) {
12
12
  return CLI_TYPES.includes(provider);
@@ -20,6 +20,8 @@ const KNOWN_SESSION_DESCRIPTIONS = {
20
20
  gemini: "Gemini Session",
21
21
  grok: "Grok Session",
22
22
  mistral: "Mistral Session",
23
+ devin: "Devin Session",
24
+ cursor: "Cursor Session",
23
25
  "grok-api": "Grok API Session",
24
26
  };
25
27
  export function defaultSessionDescription(provider) {
@@ -70,6 +70,17 @@ export const ACP_ENTRYPOINT_CONTRACTS = {
70
70
  evidence: 'Native ACP entrypoint `devin acp` (stdio JSON-RPC). Slice D1 manual initialize + session/new smoke passed (protocolVersion 1, agent "Affogato", session created). Third native runtime pilot; routing stays config-gated.',
71
71
  docsRef: "docs/plans/first-class-acp-gateway-extension.dag.toml#provider_matrix.devin",
72
72
  },
73
+ cursor: {
74
+ cli: "cursor",
75
+ displayName: "Cursor Agent CLI",
76
+ status: "native",
77
+ executable: "cursor-agent",
78
+ entrypointArgs: ["acp"],
79
+ targetVersion: "cursor-agent 2026.06.29-2ad2186",
80
+ probeArgs: [["acp", "--help"]],
81
+ evidence: "Native hidden ACP entrypoint `cursor-agent acp` (stdio JSON-RPC). `cursor-agent acp --help` was verified locally; manual initialize + session/new smoke passed locally (protocolVersion 1, session created; no agentInfo returned). Runtime routing stays config-gated.",
82
+ docsRef: "docs/plans/first-class-acp-gateway-extension.dag.toml#provider_matrix.cursor",
83
+ },
73
84
  };
74
85
  const PERMISSION_MODES = [
75
86
  "default",
@@ -1826,6 +1837,164 @@ export const UPSTREAM_CLI_CONTRACTS = {
1826
1837
  },
1827
1838
  ],
1828
1839
  },
1840
+ cursor: {
1841
+ cli: "cursor",
1842
+ executable: "cursor-agent",
1843
+ upstream: "Cursor Agent CLI",
1844
+ upstreamMetadata: {
1845
+ sourceUrls: [
1846
+ "https://cursor.com/cli",
1847
+ "https://cursor.com/docs/cli/acp",
1848
+ "https://cursor.com/docs/cli/reference/parameters",
1849
+ ],
1850
+ packageName: "cursor-agent",
1851
+ installDocsUrl: "https://cursor.com/cli",
1852
+ releaseChannel: "vendor",
1853
+ watchCategories: [
1854
+ "flags",
1855
+ "subcommands",
1856
+ "session-resume",
1857
+ "execution-modes",
1858
+ "acp-entrypoint",
1859
+ ],
1860
+ },
1861
+ helpArgs: [["--help"]],
1862
+ subcommands: {},
1863
+ maxPositionals: 1,
1864
+ mcpTools: ["cursor_request", "cursor_request_async"],
1865
+ mcpParameters: [
1866
+ "prompt",
1867
+ "model",
1868
+ "mode",
1869
+ "outputFormat",
1870
+ "force",
1871
+ "autoReview",
1872
+ "sandbox",
1873
+ "trust",
1874
+ "workspace",
1875
+ "addDir",
1876
+ "sessionId",
1877
+ "resumeLatest",
1878
+ "createNewSession",
1879
+ ],
1880
+ flags: {
1881
+ "--print": {
1882
+ arity: "none",
1883
+ description: "Print responses to console for scripts/non-interactive use",
1884
+ },
1885
+ "--output-format": {
1886
+ arity: "one",
1887
+ values: ["text", "json", "stream-json"],
1888
+ description: "Output format for --print mode",
1889
+ },
1890
+ "--model": { arity: "one", description: "Model to use for the session" },
1891
+ "--mode": {
1892
+ arity: "one",
1893
+ values: ["plan", "ask"],
1894
+ description: "Execution mode (plan or ask)",
1895
+ },
1896
+ "--force": { arity: "none", description: "Force allow commands unless denied" },
1897
+ "--auto-review": { arity: "none", description: "Use Cursor Smart Auto-review" },
1898
+ "--sandbox": {
1899
+ arity: "one",
1900
+ values: ["enabled", "disabled"],
1901
+ description: "Enable or disable Cursor sandbox mode",
1902
+ },
1903
+ "--trust": { arity: "none", description: "Trust workspace in headless mode" },
1904
+ "--workspace": { arity: "one", description: "Workspace directory or saved workspace" },
1905
+ "--add-dir": { arity: "one", description: "Additional workspace root directory" },
1906
+ "--resume": { arity: "one", description: "Resume a specific Cursor chat/session" },
1907
+ "--continue": { arity: "none", description: "Continue the latest Cursor chat" },
1908
+ },
1909
+ acknowledgedUpstreamFlags: [
1910
+ "--api-key",
1911
+ "--header",
1912
+ "-H",
1913
+ "-p",
1914
+ "--plan",
1915
+ "--yolo",
1916
+ "--approve-mcps",
1917
+ "--plugin-dir",
1918
+ "--worktree",
1919
+ "-w",
1920
+ "--worktree-base",
1921
+ "--skip-worktree-setup",
1922
+ "--stream-partial-output",
1923
+ "--list-models",
1924
+ "--version",
1925
+ "-v",
1926
+ "--help",
1927
+ "-h",
1928
+ ],
1929
+ env: {
1930
+ CURSOR_API_KEY: {
1931
+ arity: "one",
1932
+ description: "Cursor Agent API key for headless authentication",
1933
+ },
1934
+ },
1935
+ conformanceFixtures: [
1936
+ {
1937
+ id: "cursor-minimal",
1938
+ description: "Minimal print-mode prompt request",
1939
+ args: ["--print", "hello"],
1940
+ expect: "pass",
1941
+ },
1942
+ {
1943
+ id: "cursor-output-format",
1944
+ description: "--output-format json is accepted",
1945
+ args: ["--print", "--output-format", "json", "hello"],
1946
+ expect: "pass",
1947
+ },
1948
+ {
1949
+ id: "cursor-mode",
1950
+ description: "--mode plan is accepted",
1951
+ args: ["--print", "--mode", "plan", "hello"],
1952
+ expect: "pass",
1953
+ },
1954
+ {
1955
+ id: "cursor-high-impact-controls",
1956
+ description: "Model, force, auto-review, sandbox, trust, and workspace controls are accepted",
1957
+ args: [
1958
+ "--print",
1959
+ "--model",
1960
+ "gpt-5",
1961
+ "--force",
1962
+ "--auto-review",
1963
+ "--sandbox",
1964
+ "enabled",
1965
+ "--trust",
1966
+ "--workspace",
1967
+ "/tmp/workspace",
1968
+ "hello",
1969
+ ],
1970
+ expect: "pass",
1971
+ },
1972
+ {
1973
+ id: "cursor-mode-invalid",
1974
+ description: "Invalid --mode is rejected",
1975
+ args: ["--print", "--mode", "dangerous", "hello"],
1976
+ expect: "fail",
1977
+ },
1978
+ {
1979
+ id: "cursor-resume",
1980
+ description: "Resume by chat id accepted",
1981
+ args: ["--print", "--resume", "chat-123", "hello"],
1982
+ expect: "pass",
1983
+ },
1984
+ {
1985
+ id: "cursor-add-dir",
1986
+ description: "Repeatable --add-dir is accepted",
1987
+ args: ["--print", "--add-dir", "/tmp/extra", "hello"],
1988
+ expect: "pass",
1989
+ },
1990
+ {
1991
+ id: "cursor-unsupported-flag",
1992
+ description: "Unsupported flag is rejected before spawn",
1993
+ args: ["--print", "--not-a-cursor-flag", "hello"],
1994
+ expect: "fail",
1995
+ },
1996
+ ],
1997
+ },
1829
1998
  };
1830
1999
  export function validateUpstreamCliArgs(cli, args) {
1831
2000
  const contract = UPSTREAM_CLI_CONTRACTS[cli];
@@ -220,6 +220,10 @@ function buildProviderArgs(provider, prompt) {
220
220
  if (provider === "claude" || provider === "grok" || provider === "mistral") {
221
221
  return ["-p", prompt];
222
222
  }
223
+ if (provider === "devin")
224
+ return ["-p", prompt];
225
+ if (provider === "cursor")
226
+ return ["--print", "--mode", "ask", "--sandbox", "enabled", prompt];
223
227
  if (provider === "codex")
224
228
  return ["exec", "--skip-git-repo-check", prompt];
225
229
  return [prompt];
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "llm-cli-gateway",
3
- "version": "2.12.2",
3
+ "version": "2.13.1",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "llm-cli-gateway",
9
- "version": "2.12.2",
9
+ "version": "2.13.1",
10
10
  "license": "MIT",
11
11
  "dependencies": {
12
12
  "@modelcontextprotocol/sdk": "^1.29.0",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "llm-cli-gateway",
3
- "version": "2.12.2",
3
+ "version": "2.13.1",
4
4
  "mcpName": "io.github.verivus-oss/llm-cli-gateway",
5
5
  "description": "MCP server providing unified access to Claude Code, Codex, Gemini, Grok, Mistral Vibe, and Devin CLIs with session management, retry logic, async job orchestration, durable job results, and cross-LLM validation.",
6
6
  "license": "MIT",
@@ -118,7 +118,7 @@
118
118
  },
119
119
  "providers": {
120
120
  "type": "object",
121
- "required": ["claude", "codex", "gemini", "grok", "mistral"],
121
+ "minProperties": 1,
122
122
  "additionalProperties": {
123
123
  "type": "object",
124
124
  "required": [
@@ -353,7 +353,7 @@
353
353
  "catalog": { "const": "provider-tools://catalog" },
354
354
  "providers": {
355
355
  "type": "object",
356
- "required": ["claude", "codex", "gemini", "grok", "grok_api", "mistral"],
356
+ "minProperties": 1,
357
357
  "additionalProperties": { "type": "string" }
358
358
  }
359
359
  },
@@ -362,7 +362,7 @@
362
362
  "cache_ttl_ms": { "type": "integer", "minimum": 0 },
363
363
  "providers": {
364
364
  "type": "object",
365
- "required": ["claude", "codex", "gemini", "grok", "grok_api", "mistral"],
365
+ "minProperties": 1,
366
366
  "additionalProperties": {
367
367
  "type": "object",
368
368
  "required": [