| author | |
| committer | |
| log | c2c4185e175daea86f8fd6336fd8839a81cc616e |
| tree | 22b1092577f069e6c76487ab1c48060074fb6936 |
| parent | ea646c0834a3394ecb0be4a30c5d367e5a9002bd |
| signature | Signed by PGP key B5690EEEBB952194 |
Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>5 files changed, 101 insertions(+), 11 deletions(-)
apps/server/src/provider/providerMaintenance.test.ts+37| ... | @@ -25,6 +25,7 @@ import { | ... | @@ -25,6 +25,7 @@ import { |
| 25 | parseHomebrewLatestVersion, | 25 | parseHomebrewLatestVersion, |
| 26 | ProviderVersionCache, | 26 | ProviderVersionCache, |
| 27 | resolveLatestProviderVersion, | 27 | resolveLatestProviderVersion, |
| 28 | resolvePackageManagedProviderMaintenance, | ||
| 28 | resolveProviderMaintenanceCapabilitiesEffect, | 29 | resolveProviderMaintenanceCapabilitiesEffect, |
| 29 | type ProviderMaintenanceCapabilities, | 30 | type ProviderMaintenanceCapabilities, |
| 30 | } from "./providerMaintenance.ts"; | 31 | } from "./providerMaintenance.ts"; |
| ... | @@ -310,6 +311,42 @@ it.layer(NodeServices.layer)("providerMaintenance", (it) => { | ... | @@ -310,6 +311,42 @@ it.layer(NodeServices.layer)("providerMaintenance", (it) => { |
| 310 | ).toBeNull(); | 311 | ).toBeNull(); |
| 311 | }); | 312 | }); |
| 312 | 313 | ||
| 314 | // The Codex Windows installer exposes `%LOCALAPPDATA%\\Programs\\OpenAI\\Codex\\bin` | ||
| 315 | // as a junction into `%CODEX_HOME%\\packages\\standalone\\current\\bin`. Node's | ||
| 316 | // realpath follows junctions, so the real path carries the standalone marker | ||
| 317 | // even though the visible path does not. | ||
| 318 | it.effect("recognizes a Windows standalone install through its junctioned bin dir", () => | ||
| 319 | Effect.gen(function* () { | ||
| 320 | const visiblePath = | ||
| 321 | "C:\\Users\\Theo\\AppData\\Local\\Programs\\OpenAI\\Codex\\bin\\codex.exe"; | ||
| 322 | const realPath = | ||
| 323 | "C:\\Users\\Theo\\.codex\\packages\\standalone\\releases\\0.120.0-x86_64\\bin\\codex.exe"; | ||
| 324 | const capabilities = yield* resolvePackageManagedProviderMaintenance( | ||
| 325 | { | ||
| 326 | provider: driver("codex"), | ||
| 327 | npmPackageName: "@openai/codex", | ||
| 328 | nativeUpdate: { | ||
| 329 | args: ["update"], | ||
| 330 | isCommandPath: isNativeTestCommandPath("/packages/standalone/"), | ||
| 331 | }, | ||
| 332 | }, | ||
| 333 | { | ||
| 334 | binaryPath: "codex", | ||
| 335 | resolvedCommandPath: visiblePath, | ||
| 336 | realCommandPath: realPath, | ||
| 337 | env: {}, | ||
| 338 | platform: "win32", | ||
| 339 | }, | ||
| 340 | ).pipe(Effect.provideService(HostProcessPlatform, "win32")); | ||
| 341 | |||
| 342 | expect(capabilities.update).toMatchObject({ | ||
| 343 | executable: visiblePath, | ||
| 344 | args: ["update"], | ||
| 345 | lockKey: "codex-native", | ||
| 346 | }); | ||
| 347 | }), | ||
| 348 | ); | ||
| 349 | |||
| 313 | it.effect("proves Windows npm ownership from the package manifest beside the shim", () => | 350 | it.effect("proves Windows npm ownership from the package manifest beside the shim", () => |
| 314 | Effect.gen(function* () { | 351 | Effect.gen(function* () { |
| 315 | const tempDir = yield* makeTempDir("t3-npm-windows-capabilities"); | 352 | const tempDir = yield* makeTempDir("t3-npm-windows-capabilities"); |
apps/web/src/components/onboarding/WelcomeWizard.tsx+10-10| ... | @@ -42,6 +42,7 @@ import { | ... | @@ -42,6 +42,7 @@ import { |
| 42 | } from "../../onboarding/projectImport.logic"; | 42 | } from "../../onboarding/projectImport.logic"; |
| 43 | import { | 43 | import { |
| 44 | getOnboardingProviderState, | 44 | getOnboardingProviderState, |
| 45 | resolveOnboardingProviderInstallCommand, | ||
| 45 | resolveOnboardingProviderLoginCommand, | 46 | resolveOnboardingProviderLoginCommand, |
| 46 | selectOnboardingProvidersByDriver, | 47 | selectOnboardingProvidersByDriver, |
| 47 | } from "../../onboarding/providerReadiness.logic"; | 48 | } from "../../onboarding/providerReadiness.logic"; |
| ... | @@ -641,11 +642,6 @@ function PairDirectStep({ | ... | @@ -641,11 +642,6 @@ function PairDirectStep({ |
| 641 | const PRIMARY_AGENT_DRIVERS = ["claudeAgent", "codex"] as const; | 642 | const PRIMARY_AGENT_DRIVERS = ["claudeAgent", "codex"] as const; |
| 642 | type OnboardingAgentDriver = (typeof PRIMARY_AGENT_DRIVERS)[number]; | 643 | type OnboardingAgentDriver = (typeof PRIMARY_AGENT_DRIVERS)[number]; |
| 643 | 644 | ||
| 644 | const AGENT_INSTALL_COMMANDS: Record<OnboardingAgentDriver, string> = { | ||
| 645 | claudeAgent: "npm install -g @anthropic-ai/claude-code", | ||
| 646 | codex: "npm install -g @openai/codex", | ||
| 647 | }; | ||
| 648 | |||
| 649 | /** Setup values stay fixed while provider probes refresh the surrounding cards. */ | 645 | /** Setup values stay fixed while provider probes refresh the surrounding cards. */ |
| 650 | interface AgentTerminalSession { | 646 | interface AgentTerminalSession { |
| 651 | readonly environmentId: EnvironmentId; | 647 | readonly environmentId: EnvironmentId; |
| ... | @@ -657,10 +653,11 @@ interface AgentTerminalSession { | ... | @@ -657,10 +653,11 @@ interface AgentTerminalSession { |
| 657 | } | 653 | } |
| 658 | 654 | ||
| 659 | /** | 655 | /** |
| 660 | * Claude Code and Codex use live probe status. Install opens the built-in terminal inline | 656 | * Claude Code and Codex use live probe status. Install opens the built-in |
| 661 | * with the command pre-typed — the update RPC can't install a binary that | 657 | * terminal inline with the vendor's standalone installer pre-typed. The update |
| 662 | * isn't there yet (it infers the package manager from the installed binary's | 658 | * RPC can't install a binary that isn't there yet (it infers the installer from |
| 663 | * path), and the terminal also handles the interactive login that follows. | 659 | * the installed binary's path), and the terminal also handles the interactive |
| 660 | * login that follows. | ||
| 664 | */ | 661 | */ |
| 665 | function AgentsStep({ | 662 | function AgentsStep({ |
| 666 | mode, | 663 | mode, |
| ... | @@ -761,7 +758,10 @@ function ConnectedAgentsStep({ | ... | @@ -761,7 +758,10 @@ function ConnectedAgentsStep({ |
| 761 | serverConfig.settings, | 758 | serverConfig.settings, |
| 762 | serverConfig.environment.platform.os, | 759 | serverConfig.environment.platform.os, |
| 763 | ) | 760 | ) |
| 764 | : AGENT_INSTALL_COMMANDS[driver], | 761 | : resolveOnboardingProviderInstallCommand( |
| 762 | driver, | ||
| 763 | serverConfig.environment.platform.os, | ||
| 764 | ), | ||
| 765 | keybindings: serverConfig.keybindings, | 765 | keybindings: serverConfig.keybindings, |
| 766 | }); | 766 | }); |
| 767 | }} | 767 | }} |
apps/web/src/onboarding/providerReadiness.logic.test.ts+21| ... | @@ -8,6 +8,7 @@ import { describe, expect, it } from "vite-plus/test"; | ... | @@ -8,6 +8,7 @@ import { describe, expect, it } from "vite-plus/test"; |
| 8 | 8 | ||
| 9 | import { | 9 | import { |
| 10 | getOnboardingProviderState, | 10 | getOnboardingProviderState, |
| 11 | resolveOnboardingProviderInstallCommand, | ||
| 11 | resolveOnboardingProviderLoginCommand, | 12 | resolveOnboardingProviderLoginCommand, |
| 12 | selectOnboardingProvidersByDriver, | 13 | selectOnboardingProvidersByDriver, |
| 13 | } from "./providerReadiness.logic"; | 14 | } from "./providerReadiness.logic"; |
| ... | @@ -315,3 +316,23 @@ describe("resolveOnboardingProviderLoginCommand", () => { | ... | @@ -315,3 +316,23 @@ describe("resolveOnboardingProviderLoginCommand", () => { |
| 315 | ).toBe("codex login"); | 316 | ).toBe("codex login"); |
| 316 | }); | 317 | }); |
| 317 | }); | 318 | }); |
| 319 | |||
| 320 | describe("resolveOnboardingProviderInstallCommand", () => { | ||
| 321 | it("uses the PowerShell installer on Windows environments", () => { | ||
| 322 | expect(resolveOnboardingProviderInstallCommand("codex", "windows")).toBe( | ||
| 323 | "irm https://chatgpt.com/codex/install.ps1 | iex", | ||
| 324 | ); | ||
| 325 | expect(resolveOnboardingProviderInstallCommand("claudeAgent", "windows")).toBe( | ||
| 326 | "irm https://claude.ai/install.ps1 | iex", | ||
| 327 | ); | ||
| 328 | }); | ||
| 329 | |||
| 330 | it.each(["darwin", "linux", "unknown"] as const)("uses the shell installer on %s", (platform) => { | ||
| 331 | expect(resolveOnboardingProviderInstallCommand("codex", platform)).toBe( | ||
| 332 | "curl -fsSL https://chatgpt.com/codex/install.sh | sh", | ||
| 333 | ); | ||
| 334 | expect(resolveOnboardingProviderInstallCommand("claudeAgent", platform)).toBe( | ||
| 335 | "curl -fsSL https://claude.ai/install.sh | bash", | ||
| 336 | ); | ||
| 337 | }); | ||
| 338 | }); |
apps/web/src/onboarding/providerReadiness.logic.ts+30| ... | @@ -71,6 +71,36 @@ export function selectOnboardingProvidersByDriver( | ... | @@ -71,6 +71,36 @@ export function selectOnboardingProvidersByDriver( |
| 71 | return providersByDriver; | 71 | return providersByDriver; |
| 72 | } | 72 | } |
| 73 | 73 | ||
| 74 | /** | ||
| 75 | * Official standalone installers. Neither needs Node or npm, and both land in | ||
| 76 | * the paths the server's provider maintenance recognizes as native, so the | ||
| 77 | * one-click updater in Settings keeps working after install. | ||
| 78 | */ | ||
| 79 | const NATIVE_INSTALL_COMMANDS = { | ||
| 80 | claudeAgent: { | ||
| 81 | windows: "irm https://claude.ai/install.ps1 | iex", | ||
| 82 | posix: "curl -fsSL https://claude.ai/install.sh | bash", | ||
| 83 | }, | ||
| 84 | codex: { | ||
| 85 | windows: "irm https://chatgpt.com/codex/install.ps1 | iex", | ||
| 86 | posix: "curl -fsSL https://chatgpt.com/codex/install.sh | sh", | ||
| 87 | }, | ||
| 88 | } as const; | ||
| 89 | |||
| 90 | /** | ||
| 91 | * Install command for the setup terminal, keyed on the environment's platform | ||
| 92 | * (not the client's): a Windows desktop driving a WSL server gets the shell | ||
| 93 | * script. Unknown platforms get the shell script too, since the terminal there | ||
| 94 | * is a POSIX shell in practice. | ||
| 95 | */ | ||
| 96 | export function resolveOnboardingProviderInstallCommand( | ||
| 97 | driver: keyof typeof NATIVE_INSTALL_COMMANDS, | ||
| 98 | platform: ExecutionEnvironmentPlatformOs, | ||
| 99 | ): string { | ||
| 100 | const commands = NATIVE_INSTALL_COMMANDS[driver]; | ||
| 101 | return platform === "windows" ? commands.windows : commands.posix; | ||
| 102 | } | ||
| 103 | |||
| 74 | /** Use the selected provider instance's binary when the setup terminal opens its login flow. */ | 104 | /** Use the selected provider instance's binary when the setup terminal opens its login flow. */ |
| 75 | export function resolveOnboardingProviderLoginCommand( | 105 | export function resolveOnboardingProviderLoginCommand( |
| 76 | provider: ServerProvider, | 106 | provider: ServerProvider, |
docs/user/welcome-wizard.md+3-1| ... | @@ -26,7 +26,9 @@ unreadable settings with defaults. | ... | @@ -26,7 +26,9 @@ unreadable settings with defaults. |
| 26 | 26 | ||
| 27 | T3 Code checks the selected computer for Claude Code and Codex. If an agent is | 27 | T3 Code checks the selected computer for Claude Code and Codex. If an agent is |
| 28 | not installed or signed in, select its action to open a terminal with the | 28 | not installed or signed in, select its action to open a terminal with the |
| 29 | correct command ready to run. Other providers can be enabled in Settings. | 29 | correct command ready to run. Install uses the vendor's standalone installer, |
| 30 | which does not need Node or npm and keeps **Update now** working in Settings. | ||
| 31 | Other providers can be enabled in Settings. | ||
| 30 | 32 | ||
| 31 | The setup terminal uses the home directory and environment configured for the | 33 | The setup terminal uses the home directory and environment configured for the |
| 32 | selected provider instance. Sensitive values remain redacted in Settings and | 34 | selected provider instance. Sensitive values remain redacted in Settings and |