diff --git a/apps/server/src/process/externalLauncher.test.ts b/apps/server/src/process/externalLauncher.test.ts
index f714a70f783d..17c1c34b44d8 100644
--- a/apps/server/src/process/externalLauncher.test.ts
+++ b/apps/server/src/process/externalLauncher.test.ts
@@ -989,6 +989,12 @@ for (const { platform, installPath, editor, args } of [
editor: "zed",
args: ["/workspace with spaces/file.ts:12:4"],
},
+ {
+ platform: "linux",
+ installPath: ".local/bin/px0",
+ editor: "px0",
+ args: ["/workspace with spaces/file.ts:12:4"],
+ },
] as const) {
it.effect.skipIf(windowsHost && platform !== "win32")(
`discovers and launches ${editor} outside PATH on ${platform}`,
@@ -1028,6 +1034,46 @@ for (const { platform, installPath, editor, args } of [
);
}
+// px0 has no app bundle or Windows installer location: it relies on the PATH
+// that server startup hydrates from the login shell (macOS) or the known CLI
+// dirs such as %USERPROFILE%\.local\bin (Windows).
+for (const { platform, binary } of [
+ { platform: "darwin", binary: "px0" },
+ { platform: "win32", binary: "px0.exe" },
+] as const) {
+ it.effect.skipIf(windowsHost && platform !== "win32")(
+ `discovers and launches px0 from PATH on ${platform}`,
+ () =>
+ Effect.gen(function* () {
+ const fs = yield* FileSystem.FileSystem;
+ const path = yield* Path.Path;
+ const binDir = yield* fs.makeTempDirectoryScoped({ prefix: "t3-px0-" });
+ yield* fs.writeFileString(path.join(binDir, binary), "#!/bin/sh\n");
+ yield* fs.chmod(path.join(binDir, binary), 0o755);
+ let spawned: ChildProcess.StandardCommand | undefined;
+ yield* Effect.gen(function* () {
+ const launcher = yield* ExternalLauncher.ExternalLauncher;
+ assert.include(yield* launcher.resolveAvailableEditors(), "px0");
+ yield* launcher.launchEditor({ editor: "px0", cwd: "/workspace/file.ts:12:4" });
+ }).pipe(
+ Effect.provide(
+ testLayer({
+ platform,
+ env: { PATH: binDir, PATHEXT: ".COM;.EXE;.BAT;.CMD" },
+ onSpawn: (command) => {
+ spawned = command;
+ },
+ }),
+ ),
+ );
+ assert.ok(spawned);
+ assert.equal(spawned.command, "px0");
+ assert.deepEqual(spawned.args, ["/workspace/file.ts:12:4"]);
+ assert.equal(spawned.options.shell, false);
+ }).pipe(Effect.scoped, Effect.provide(NodeServices.layer)),
+ );
+}
+
it.effect.skipIf(windowsHost)("ignores unusable app bundles and keeps PATH launchers first", () =>
Effect.gen(function* () {
const fs = yield* FileSystem.FileSystem;
diff --git a/apps/web/src/components/Icons.tsx b/apps/web/src/components/Icons.tsx
index e4d41d53c3a8..2445e78ad3ac 100644
--- a/apps/web/src/components/Icons.tsx
+++ b/apps/web/src/components/Icons.tsx
@@ -709,6 +709,14 @@ export const AntigravityIcon: Icon = (props) => (
);
+export const Px0Icon: Icon = (props) => (
+
+);
+
export const OpenCodeIcon: Icon = (props) => (