| 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 | 25 | parseHomebrewLatestVersion, |
| 26 | 26 | ProviderVersionCache, |
| 27 | 27 | resolveLatestProviderVersion, |
| 28 | resolvePackageManagedProviderMaintenance, | |
| 28 | 29 | resolveProviderMaintenanceCapabilitiesEffect, |
| 29 | 30 | type ProviderMaintenanceCapabilities, |
| 30 | 31 | } from "./providerMaintenance.ts"; |
| ... | ... | @@ -310,6 +311,42 @@ it.layer(NodeServices.layer)("providerMaintenance", (it) => { |
| 310 | 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 | 350 | it.effect("proves Windows npm ownership from the package manifest beside the shim", () => |
| 314 | 351 | Effect.gen(function* () { |
| 315 | 352 | const tempDir = yield* makeTempDir("t3-npm-windows-capabilities"); |
apps/web/src/components/onboarding/WelcomeWizard.tsx+10-10| ... | ... | @@ -42,6 +42,7 @@ import { |
| 42 | 42 | } from "../../onboarding/projectImport.logic"; |
| 43 | 43 | import { |
| 44 | 44 | getOnboardingProviderState, |
| 45 | resolveOnboardingProviderInstallCommand, | |
| 45 | 46 | resolveOnboardingProviderLoginCommand, |
| 46 | 47 | selectOnboardingProvidersByDriver, |
| 47 | 48 | } from "../../onboarding/providerReadiness.logic"; |
| ... | ... | @@ -641,11 +642,6 @@ function PairDirectStep({ |
| 641 | 642 | const PRIMARY_AGENT_DRIVERS = ["claudeAgent", "codex"] as const; |
| 642 | 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 | 645 | /** Setup values stay fixed while provider probes refresh the surrounding cards. */ |
| 650 | 646 | interface AgentTerminalSession { |
| 651 | 647 | readonly environmentId: EnvironmentId; |
| ... | ... | @@ -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 | |
| 661 | * with the command pre-typed — the update RPC can't install a binary that | |
| 662 | * isn't there yet (it infers the package manager from the installed binary's | |
| 663 | * path), and the terminal also handles the interactive login that follows. | |
| 656 | * Claude Code and Codex use live probe status. Install opens the built-in | |
| 657 | * terminal inline with the vendor's standalone installer pre-typed. The update | |
| 658 | * RPC can't install a binary that isn't there yet (it infers the installer from | |
| 659 | * the installed binary's path), and the terminal also handles the interactive | |
| 660 | * login that follows. | |
| 664 | 661 | */ |
| 665 | 662 | function AgentsStep({ |
| 666 | 663 | mode, |
| ... | ... | @@ -761,7 +758,10 @@ function ConnectedAgentsStep({ |
| 761 | 758 | serverConfig.settings, |
| 762 | 759 | serverConfig.environment.platform.os, |
| 763 | 760 | ) |
| 764 | : AGENT_INSTALL_COMMANDS[driver], | |
| 761 | : resolveOnboardingProviderInstallCommand( | |
| 762 | driver, | |
| 763 | serverConfig.environment.platform.os, | |
| 764 | ), | |
| 765 | 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 | 8 | |
| 9 | 9 | import { |
| 10 | 10 | getOnboardingProviderState, |
| 11 | resolveOnboardingProviderInstallCommand, | |
| 11 | 12 | resolveOnboardingProviderLoginCommand, |
| 12 | 13 | selectOnboardingProvidersByDriver, |
| 13 | 14 | } from "./providerReadiness.logic"; |
| ... | ... | @@ -315,3 +316,23 @@ describe("resolveOnboardingProviderLoginCommand", () => { |
| 315 | 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 | 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 | 104 | /** Use the selected provider instance's binary when the setup terminal opens its login flow. */ |
| 75 | 105 | export function resolveOnboardingProviderLoginCommand( |
| 76 | 106 | provider: ServerProvider, |
docs/user/welcome-wizard.md+3-1| ... | ... | @@ -26,7 +26,9 @@ unreadable settings with defaults. |
| 26 | 26 | |
| 27 | 27 | T3 Code checks the selected computer for Claude Code and Codex. If an agent is |
| 28 | 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 | 33 | The setup terminal uses the home directory and environment configured for the |
| 32 | 34 | selected provider instance. Sensitive values remain redacted in Settings and |