1import {
2 ClaudeSettings,
3 CodexSettings,
4 type ExecutionEnvironmentPlatformOs,
5 type ServerProvider,
6 type ServerSettings,
7} from "@t3tools/contracts";
8import * as Option from "effect/Option";
9import * as Schema from "effect/Schema";
10
11const decodeClaudeSettings = Schema.decodeUnknownOption(ClaudeSettings);
12const decodeCodexSettings = Schema.decodeUnknownOption(CodexSettings);
13const SAFE_SHELL_BINARY_PATTERN = /^[A-Za-z0-9_./:\\-]+$/;
14
15function quoteProviderBinary(
16 binaryPath: string,
17 fallback: string,
18 platform: ExecutionEnvironmentPlatformOs,
19): string {
20 if (
21 SAFE_SHELL_BINARY_PATTERN.test(binaryPath) &&
22 (platform === "windows" || !binaryPath.includes("\\"))
23 ) {
24 return binaryPath;
25 }
26 if (platform === "windows") return `& '${binaryPath.replaceAll("'", "''")}'`;
27 if (platform === "darwin" || platform === "linux") {
28 if (binaryPath.startsWith("~/") || binaryPath.startsWith("~\\")) {
29 return `~/'${binaryPath.slice(2).replaceAll("'", `'"'"'`)}'`;
30 }
31 return `'${binaryPath.replaceAll("'", `'"'"'`)}'`;
32 }
33 return fallback;
34}
35
36export function getOnboardingProviderState(provider: ServerProvider | undefined) {
37 if (provider === undefined) return "checking";
38 if (!provider.enabled || provider.status === "disabled") return "disabled";
39 if (!provider.installed && provider.status === "warning" && provider.auth.status === "unknown")
40 return "checking";
41 if (!provider.installed) return "install";
42 if (provider.auth.status === "unauthenticated") return "signIn";
43 if (provider.status === "ready") return "ready";
44 return "attention";
45}
46
47const PROVIDER_STATE_PRIORITY = {
48 checking: 0,
49 disabled: 1,
50 install: 2,
51 attention: 3,
52 signIn: 4,
53 ready: 5,
54} as const;
55
56/** Select the most usable configured instance for each provider driver. */
57export function selectOnboardingProvidersByDriver(
58 providers: ReadonlyArray<ServerProvider> | null | undefined,
59) {
60 const providersByDriver = new Map<string, ServerProvider>();
61
62 for (const provider of providers ?? []) {
63 const existing = providersByDriver.get(provider.driver);
64 if (
65 existing === undefined ||
66 PROVIDER_STATE_PRIORITY[getOnboardingProviderState(provider)] >
67 PROVIDER_STATE_PRIORITY[getOnboardingProviderState(existing)]
68 ) {
69 providersByDriver.set(provider.driver, provider);
70 }
71 }
72
73 return providersByDriver;
74}
75
76/**
77 * Official standalone installers. Neither needs Node or npm, and both land in
78 * the paths the server's provider maintenance recognizes as native, so the
79 * one-click updater in Settings keeps working after install.
80 */
81const NATIVE_INSTALL_COMMANDS = {
82 claudeAgent: {
83 windows: "irm https://claude.ai/install.ps1 | iex",
84 posix: "curl -fsSL https://claude.ai/install.sh | bash",
85 },
86 codex: {
87 windows: "irm https://chatgpt.com/codex/install.ps1 | iex",
88 posix: "curl -fsSL https://chatgpt.com/codex/install.sh | sh",
89 },
90} as const;
91
92/**
93 * Install command for the setup terminal, keyed on the environment's platform
94 * (not the client's): a Windows desktop driving a WSL server gets the shell
95 * script. Unknown platforms get the shell script too, since the terminal there
96 * is a POSIX shell in practice.
97 */
98export function resolveOnboardingProviderInstallCommand(
99 driver: keyof typeof NATIVE_INSTALL_COMMANDS,
100 platform: ExecutionEnvironmentPlatformOs,
101): string {
102 const commands = NATIVE_INSTALL_COMMANDS[driver];
103 return platform === "windows" ? commands.windows : commands.posix;
104}
105
106/** Use the selected provider instance's binary when the setup terminal opens its login flow. */
107export function resolveOnboardingProviderLoginCommand(
108 provider: ServerProvider,
109 settings: ServerSettings,
110 platform: ExecutionEnvironmentPlatformOs,
111): string {
112 const instance = settings.providerInstances[provider.instanceId];
113
114 if (provider.driver === "claudeAgent") {
115 const config = decodeClaudeSettings(instance?.config ?? {});
116 const binaryPath = Option.isSome(config) ? config.value.binaryPath : "claude";
117 return `${quoteProviderBinary(binaryPath, "claude", platform)} auth login`;
118 }
119
120 if (provider.driver === "codex") {
121 const config = decodeCodexSettings(instance?.config ?? {});
122 const binaryPath = Option.isSome(config) ? config.value.binaryPath : "codex";
123 return `${quoteProviderBinary(binaryPath, "codex", platform)} login`;
124 }
125
126 return provider.driver;
127}