feat(ai): add Cursor CLI provider

This commit is contained in:
Guoyu Su authored and GitHub committed 2026-08-08 11:08:18 +08:00
1 parent 25fd5677c7
commit 36e5fa4f4c
36 files changed
+1173 -36

No files matched your search

@@ -2553,7 +2553,7 @@ async function saveMaxAgentTurnsSetting() {
}
// Max Retries (global). Default 2, range 0–10. Applied to all API-backed
// AI providers. CLI providers (Claude Code, Codex, OpenCode, Pi) are unaffected
// AI providers. CLI providers are unaffected
// because they use their own retry logic.
const editMaxRetries = ref<number | undefined>(undefined);
const maxRetriesSaving = ref(false);
@@ -2604,8 +2604,9 @@ function normalizeMaxRetries(value: number | undefined): number {
const aiDeleteConfirmOpen = ref(false);
const aiDeleteConfigId = ref<string | null>(null);
const CLI_AI_PROVIDERS = new Set<AiProvider>(["claude-code-cli", "codex-cli", "opencode-cli", "pi-agent-cli"]);
const CLI_AI_PROVIDERS = new Set<AiProvider>(["claude-code-cli", "codex-cli", "opencode-cli", "pi-agent-cli", "cursor-cli"]);
const OPENCODE_CONTROL_ENV = new Set(["OPENCODE_CONFIG", "OPENCODE_CONFIG_CONTENT", "OPENCODE_CONFIG_DIR", "OPENCODE_DB", "OPENCODE_PERMISSION", "OPENCODE_DISABLE_PROJECT_CONFIG"]);
const CURSOR_CONTROL_ENV = new Set(["CURSOR_CONFIG_DIR", "CURSOR_DATA_DIR"]);
const aiProviderOptions = computed(() => Object.values(AI_PROVIDER_PRESETS).filter((provider) => !isWeb || !CLI_AI_PROVIDERS.has(provider.provider)));
const selectedAiProviderPreset = computed(() => AI_PROVIDER_PRESETS[aiEditProvider.value]);
@@ -2629,6 +2630,8 @@ const aiEditPiAgentCliPath = ref("");
const aiEditPiAgentCliEnvRows = ref<AiEnvRow[]>([]);
const aiEditOpenCodeCliPath = ref("");
const aiEditOpenCodeCliEnvRows = ref<AiEnvRow[]>([]);
const aiEditCursorCliPath = ref("");
const aiEditCursorCliEnvRows = ref<AiEnvRow[]>([]);
const aiAnthropicMessagesMode = computed(() => aiEditApiStyle.value === "anthropic-messages");
@@ -2660,18 +2663,21 @@ const aiIsCodexCli = computed(() => aiEditProvider.value === "codex-cli");
const aiIsClaudeCodeCli = computed(() => aiEditProvider.value === "claude-code-cli");
const aiIsPiAgentCli = computed(() => aiEditProvider.value === "pi-agent-cli");
const aiIsOpenCodeCli = computed(() => aiEditProvider.value === "opencode-cli");
const aiIsCursorCli = computed(() => aiEditProvider.value === "cursor-cli");
const aiIsCliProvider = computed(() => CLI_AI_PROVIDERS.has(aiEditProvider.value));
const aiCliProviderLabel = computed(() => selectedAiProviderPreset.value.label);
const aiCliCommandName = computed(() => {
if (aiIsClaudeCodeCli.value) return "claude";
if (aiIsPiAgentCli.value) return "pi";
if (aiIsOpenCodeCli.value) return "opencode";
if (aiIsCursorCli.value) return "agent";
return "codex";
});
const aiCliLoginCommand = computed(() => {
if (aiIsClaudeCodeCli.value) return "claude auth login";
if (aiIsPiAgentCli.value) return "pi";
if (aiIsOpenCodeCli.value) return "opencode auth login";
if (aiIsCursorCli.value) return "agent login";
return "codex login";
});
const aiEditCliPath = computed({
@@ -2679,6 +2685,7 @@ const aiEditCliPath = computed({
if (aiIsClaudeCodeCli.value) return aiEditClaudeCodeCliPath.value;
if (aiIsPiAgentCli.value) return aiEditPiAgentCliPath.value;
if (aiIsOpenCodeCli.value) return aiEditOpenCodeCliPath.value;
if (aiIsCursorCli.value) return aiEditCursorCliPath.value;
return aiEditCodexCliPath.value;
},
set: (value: string) => {
@@ -2688,6 +2695,8 @@ const aiEditCliPath = computed({
aiEditPiAgentCliPath.value = value;
} else if (aiIsOpenCodeCli.value) {
aiEditOpenCodeCliPath.value = value;
} else if (aiIsCursorCli.value) {
aiEditCursorCliPath.value = value;
} else {
aiEditCodexCliPath.value = value;
}
@@ -2697,6 +2706,7 @@ const aiEditCliEnvRows = computed(() => {
if (aiIsClaudeCodeCli.value) return aiEditClaudeCodeCliEnvRows.value;
if (aiIsPiAgentCli.value) return aiEditPiAgentCliEnvRows.value;
if (aiIsOpenCodeCli.value) return aiEditOpenCodeCliEnvRows.value;
if (aiIsCursorCli.value) return aiEditCursorCliEnvRows.value;
return aiEditCodexCliEnvRows.value;
});
watch(aiIsCliProvider, (isCliProvider) => {
@@ -2773,7 +2783,7 @@ function cliEnvValidationError(): string {
const key = row.key.trim();
if (key && !/^[A-Za-z_][A-Za-z0-9_]*$/.test(key)) return t("ai.cliEnvInvalidName", { name: key });
const upper = key.toUpperCase();
if (upper.startsWith("DBX_MCP_") || (aiIsPiAgentCli.value && upper.startsWith("DBX_PI_")) || (aiIsOpenCodeCli.value && OPENCODE_CONTROL_ENV.has(upper))) {
if (upper.startsWith("DBX_MCP_") || (aiIsPiAgentCli.value && upper.startsWith("DBX_PI_")) || (aiIsOpenCodeCli.value && OPENCODE_CONTROL_ENV.has(upper)) || (aiIsCursorCli.value && CURSOR_CONTROL_ENV.has(upper))) {
return t("ai.cliEnvReservedName", { name: key });
}
}
@@ -2791,6 +2801,8 @@ function removeCliEnvRow(id: string) {
aiEditPiAgentCliEnvRows.value = aiEditPiAgentCliEnvRows.value.filter((row) => row.id !== id);
} else if (aiIsOpenCodeCli.value) {
aiEditOpenCodeCliEnvRows.value = aiEditOpenCodeCliEnvRows.value.filter((row) => row.id !== id);
} else if (aiIsCursorCli.value) {
aiEditCursorCliEnvRows.value = aiEditCursorCliEnvRows.value.filter((row) => row.id !== id);
} else {
aiEditCodexCliEnvRows.value = aiEditCodexCliEnvRows.value.filter((row) => row.id !== id);
}
@@ -2821,6 +2833,8 @@ function currentAiEditConfig() {
piAgentCliEnv: aiIsPiAgentCli.value ? cliEnvFromRows(aiEditPiAgentCliEnvRows.value) : {},
opencodeCliPath: aiEditOpenCodeCliPath.value.trim() || undefined,
opencodeCliEnv: aiIsOpenCodeCli.value ? cliEnvFromRows(aiEditOpenCodeCliEnvRows.value) : {},
cursorCliPath: aiEditCursorCliPath.value.trim() || undefined,
cursorCliEnv: aiIsCursorCli.value ? cliEnvFromRows(aiEditCursorCliEnvRows.value) : {},
};
}
@@ -2892,6 +2906,8 @@ function aiEnterEditMode(configId?: string) {
aiEditPiAgentCliEnvRows.value = aiEnvRowsFromConfig(config.piAgentCliEnv);
aiEditOpenCodeCliPath.value = config.opencodeCliPath ?? "";
aiEditOpenCodeCliEnvRows.value = aiEnvRowsFromConfig(config.opencodeCliEnv);
aiEditCursorCliPath.value = config.cursorCliPath ?? "";
aiEditCursorCliEnvRows.value = aiEnvRowsFromConfig(config.cursorCliEnv);
}
} else {
aiEditConfigName.value = "";
@@ -2915,6 +2931,8 @@ function aiEnterEditMode(configId?: string) {
aiEditPiAgentCliEnvRows.value = [];
aiEditOpenCodeCliPath.value = "";
aiEditOpenCodeCliEnvRows.value = [];
aiEditCursorCliPath.value = "";
aiEditCursorCliEnvRows.value = [];
}
}
@@ -21,7 +21,7 @@ watch(
},
);
const usesWhiteDarkIcon = computed(() => props.provider === "claude" || props.provider === "anthropic-compatible" || props.provider === "ollama" || props.provider === "openai" || props.provider === "openai-compatible" || props.provider === "opencode-cli");
const usesWhiteDarkIcon = computed(() => props.provider === "claude" || props.provider === "anthropic-compatible" || props.provider === "ollama" || props.provider === "openai" || props.provider === "openai-compatible" || props.provider === "opencode-cli" || props.provider === "cursor-cli");
const localIconUrl = computed(() => {
if (props.provider === "openai-compatible") return webPath("/icons/ai/openai.svg");
return props.iconSlug ? webPath(`/icons/ai/${props.iconSlug}.svg`) : "";