diff --git a/packages/app/src/electron/windows/main-window.ts b/packages/app/src/electron/windows/main-window.ts
index 46d6145a..54930fe2 100644
--- a/packages/app/src/electron/windows/main-window.ts
+++ b/packages/app/src/electron/windows/main-window.ts
@@ -1,4 +1,8 @@
import { inDevelopment } from "@/shared/constants/dev";
+import {
+ createRendererStartupProviderArgument,
+ parseStartupProvider,
+} from "@/shared/startup-provider";
import {
BrowserWindow,
BrowserWindowConstructorOptions,
@@ -29,6 +33,7 @@ function createPlatformSpecificConfig(): BrowserWindowConstructorOptions {
const height = Math.round(screenHeight * 0.8);
const x = Math.round((screenWidth - width) / 2);
const y = Math.round((screenHeight - height) / 2);
+ const startupProvider = parseStartupProvider(process.argv);
const baseConfig: BrowserWindowConstructorOptions = {
width,
@@ -47,6 +52,9 @@ function createPlatformSpecificConfig(): BrowserWindowConstructorOptions {
// inside Electron's restricted sandbox where those imports fail.
sandbox: false,
preload: path.join(__dirname, "preload.js"),
+ additionalArguments: startupProvider
+ ? [createRendererStartupProviderArgument(startupProvider)]
+ : undefined,
},
show: false,
titleBarStyle: "hiddenInset",
diff --git a/packages/app/src/preload.ts b/packages/app/src/preload.ts
index ed76fd34..f1878287 100644
--- a/packages/app/src/preload.ts
+++ b/packages/app/src/preload.ts
@@ -6,14 +6,17 @@ import { exposeLoggerContext } from "./electro-bridge/ipc/logger-context";
import { exposeLocalAIContext } from "./electro-bridge/ipc/local-ai-context";
import { exposeMCPContext } from "./electro-bridge/ipc/mcp-context";
import { createAgentHostAPI } from "./electro-bridge/ipc/agent-host-api";
+import { parseStartupProvider } from "./shared/startup-provider";
// SOURCE(Sma1lboy): https://www.electronjs.org/docs/latest/tutorial/process-model
// expose electronAPI to renderer process
const electronAPI = createElectronAPI(ipcRenderer);
+const startupProvider = parseStartupProvider(process.argv);
// Add navigate-to-settings listener
const extendedAPI = {
...electronAPI,
+ getStartupProvider: () => startupProvider,
onNavigateToSettings: (callback: () => void) => {
const handler = () => callback();
ipcRenderer.on("navigate-to-settings", handler);
diff --git a/packages/app/src/renderer/components/settings/pages/general-page.tsx b/packages/app/src/renderer/components/settings/pages/general-page.tsx
index 434e1ed3..8c412ceb 100644
--- a/packages/app/src/renderer/components/settings/pages/general-page.tsx
+++ b/packages/app/src/renderer/components/settings/pages/general-page.tsx
@@ -6,6 +6,7 @@ import { describeProviderStatus } from "@/renderer/libs/provider-status";
import {
DEFAULT_LOCAL_AI_MODEL_ID,
isLocalAIProviderId,
+ type LocalAIProviderId,
} from "@/renderer/libs/local-ai";
import { useModelConfigStore } from "@/renderer/libs/stores/model-config-store";
import { useSettingsStore } from "@/renderer/libs/stores/settings-store";
@@ -41,6 +42,29 @@ export const MEMORY_PROVIDER_OPTIONS = LOCAL_AI_MEMORY_PROVIDERS.map(
}),
);
+export const CLI_RUNTIME_OPTIONS = [
+ { value: "codex-cli", label: "Codex" },
+ { value: "claude-code", label: "Claude Code" },
+] as const satisfies ReadonlyArray<{
+ value: LocalAIProviderId;
+ label: string;
+}>;
+
+export function isCliRuntimeProvider(
+ value: string,
+): value is (typeof CLI_RUNTIME_OPTIONS)[number]["value"] {
+ return CLI_RUNTIME_OPTIONS.some((option) => option.value === value);
+}
+
+export function selectDefaultCliRuntime(
+ value: string,
+ setDefaultModel: (providerId: string, modelId: string) => void,
+): boolean {
+ if (!isCliRuntimeProvider(value)) return false;
+ setDefaultModel(value, DEFAULT_LOCAL_AI_MODEL_ID);
+ return true;
+}
+
export function createMemoryProviderUpdate(
value: string,
): LocalAIMemorySettingsUpdate | null {
@@ -450,52 +474,53 @@ export function GeneralSettingsPage() {
-
- {providers.map((provider) => {
- const isSelected = provider.id === defaultConfigId;
- const status = describeProviderStatus(provider, providersLoading);
- const canSelect =
- status.ready && isLocalAIProviderId(provider.id);
-
- return (
-
diff --git a/packages/app/src/renderer/libs/db/ui-state.ts b/packages/app/src/renderer/libs/db/ui-state.ts
index fee4ad78..08addb00 100644
--- a/packages/app/src/renderer/libs/db/ui-state.ts
+++ b/packages/app/src/renderer/libs/db/ui-state.ts
@@ -39,6 +39,12 @@ const CURRENT_CONVERSATION_KEY = "current-conversation-id";
const storage: Pick | undefined =
typeof localStorage === "undefined" ? undefined : localStorage;
+const startupProviderId =
+ typeof window === "undefined"
+ ? null
+ : (window.electronAPI?.getStartupProvider?.() ?? null);
+const initialProviderId = startupProviderId ?? DEFAULT_LOCAL_AI_PROVIDER_ID;
+
interface SelectionState {
// Currently selected items
currentConversationId: string | null;
@@ -61,9 +67,9 @@ export const useSelectionStore = create((set, get) => ({
currentConversationId: storage?.getItem(CURRENT_CONVERSATION_KEY) ?? null,
conversationSelectionVersion: 0,
selectedAgentId: null,
- selectedConfigId: DEFAULT_LOCAL_AI_PROVIDER_ID,
+ selectedConfigId: initialProviderId,
selectedModelId: DEFAULT_LOCAL_AI_MODEL_ID,
- defaultConfigId: DEFAULT_LOCAL_AI_PROVIDER_ID,
+ defaultConfigId: initialProviderId,
defaultModelId: DEFAULT_LOCAL_AI_MODEL_ID,
setCurrentConversation: (id) => {
@@ -143,7 +149,19 @@ export const useSelectionStore = create((set, get) => ({
void Promise.all([
db.settings.get("local-ai-default-selection"),
db.settings.get("local-ai-selection"),
-]).then(([currentRecord, legacyRecord]) => {
+]).then(async ([currentRecord, legacyRecord]) => {
+ if (startupProviderId) {
+ await db.settings.put({
+ key: "local-ai-default-selection",
+ value: {
+ configId: startupProviderId,
+ modelId: DEFAULT_LOCAL_AI_MODEL_ID,
+ },
+ updatedAt: new Date(),
+ });
+ return;
+ }
+
const record = currentRecord ?? legacyRecord;
const value = record?.value;
if (
diff --git a/packages/app/src/shared/startup-provider.test.ts b/packages/app/src/shared/startup-provider.test.ts
new file mode 100644
index 00000000..3a78b273
--- /dev/null
+++ b/packages/app/src/shared/startup-provider.test.ts
@@ -0,0 +1,38 @@
+import { describe, expect, it } from "vitest";
+import {
+ createRendererStartupProviderArgument,
+ parseStartupProvider,
+} from "./startup-provider";
+
+describe("startup provider arguments", () => {
+ it.each([
+ ["cc", "claude-code"],
+ ["--cc", "claude-code"],
+ ["codex", "codex-cli"],
+ ["--codex", "codex-cli"],
+ ] as const)("maps %s to %s", (argument, expected) => {
+ expect(parseStartupProvider(["electron", ".", argument])).toBe(expected);
+ });
+
+ it("ignores unrelated and lookalike arguments", () => {
+ expect(parseStartupProvider(["electron", ".", "CC", "codex.json"])).toBe(
+ null,
+ );
+ });
+
+ it("uses the last recognized provider argument", () => {
+ expect(parseStartupProvider(["cc", "codex"])).toBe("codex-cli");
+ expect(parseStartupProvider(["codex", "cc"])).toBe("claude-code");
+ });
+
+ it("round-trips the internal renderer argument", () => {
+ const argument = createRendererStartupProviderArgument("claude-code");
+ expect(parseStartupProvider([argument])).toBe("claude-code");
+ });
+
+ it("rejects an unsupported internal provider", () => {
+ expect(
+ parseStartupProvider(["--convera-startup-provider=openai-api"]),
+ ).toBe(null);
+ });
+});
diff --git a/packages/app/src/shared/startup-provider.ts b/packages/app/src/shared/startup-provider.ts
new file mode 100644
index 00000000..8b7535f6
--- /dev/null
+++ b/packages/app/src/shared/startup-provider.ts
@@ -0,0 +1,44 @@
+import type { LocalAIProviderKind } from "./types/local-ai";
+
+export type StartupProviderId = Extract<
+ LocalAIProviderKind,
+ "claude-code" | "codex-cli"
+>;
+
+const RENDERER_ARGUMENT_PREFIX = "--convera-startup-provider=";
+
+const STARTUP_PROVIDER_ALIASES: Readonly> = {
+ cc: "claude-code",
+ "--cc": "claude-code",
+ codex: "codex-cli",
+ "--codex": "codex-cli",
+};
+
+export function parseStartupProvider(
+ argv: readonly string[],
+): StartupProviderId | null {
+ let providerId: StartupProviderId | null = null;
+
+ for (const argument of argv) {
+ const alias = STARTUP_PROVIDER_ALIASES[argument];
+ if (alias) {
+ providerId = alias;
+ continue;
+ }
+
+ if (argument.startsWith(RENDERER_ARGUMENT_PREFIX)) {
+ const value = argument.slice(RENDERER_ARGUMENT_PREFIX.length);
+ if (value === "claude-code" || value === "codex-cli") {
+ providerId = value;
+ }
+ }
+ }
+
+ return providerId;
+}
+
+export function createRendererStartupProviderArgument(
+ providerId: StartupProviderId,
+): string {
+ return `${RENDERER_ARGUMENT_PREFIX}${providerId}`;
+}
diff --git a/packages/app/src/shared/types/electron.d.ts b/packages/app/src/shared/types/electron.d.ts
index 0e0c562b..32642a3a 100644
--- a/packages/app/src/shared/types/electron.d.ts
+++ b/packages/app/src/shared/types/electron.d.ts
@@ -4,6 +4,7 @@ import { WindowSizeConfig } from "@/electron/windows/window-size";
import type { ILocalAIAPI } from "./local-ai";
import type { IMcpAPI } from "./mcp";
import type { IAgentHostAPI } from "./agent-host";
+import type { StartupProviderId } from "../startup-provider";
// Enum for window types
export type WindowType = "settings" | "history" | "main" | "chat";
@@ -45,6 +46,7 @@ export interface IElectronAPI {
// App functionality
getClipboardText: () => Promise;
setInputText: (text: string) => Promise;
+ getStartupProvider: () => StartupProviderId | null;
// Unified Theme Control
setTheme: (mode: ThemeMode) => Promise;