diff --git a/packages/app/src/context/server.test.ts b/packages/app/src/context/server.test.ts new file mode 100644 index 00000000000..d8c112245b7 --- /dev/null +++ b/packages/app/src/context/server.test.ts @@ -0,0 +1,19 @@ +import { describe, expect, test } from "bun:test" +import { normalizeWorktree } from "./server" + +describe("normalizeWorktree", () => { + test("trims and removes trailing separators", () => { + expect(normalizeWorktree(" /tmp/repo/ ")).toBe("/tmp/repo") + expect(normalizeWorktree("C:\\repo\\")).toBe("C:\\repo") + }) + + test("keeps root separators stable", () => { + expect(normalizeWorktree("/")).toBe("/") + expect(normalizeWorktree("\\")).toBe("\\") + }) + + test("keeps values without trailing separators", () => { + expect(normalizeWorktree("/tmp/repo")).toBe("/tmp/repo") + expect(normalizeWorktree("C:\\repo")).toBe("C:\\repo") + }) +}) diff --git a/packages/app/src/context/server.tsx b/packages/app/src/context/server.tsx index 4ff777e2eb2..4f10b252ea3 100644 --- a/packages/app/src/context/server.tsx +++ b/packages/app/src/context/server.tsx @@ -34,6 +34,15 @@ function isLocalHost(url: string) { if (host === "localhost" || host === "127.0.0.1") return "local" } +export function normalizeWorktree(input: string) { + const value = input.trim() + const next = value.replace(/[\/\\]+$/, "") + if (next) return next + if (value.startsWith("\\")) return "\\" + if (value.startsWith("/")) return "/" + return value +} + export namespace ServerConnection { type Base = { displayName?: string } @@ -243,7 +252,8 @@ export const { use: useServer, provider: ServerProvider } = createSimpleContext( const key = origin() if (!key) return const current = store.projects[key] ?? [] - if (current.find((x) => x.worktree === directory)) return + const target = normalizeWorktree(directory) + if (current.find((x) => normalizeWorktree(x.worktree) === target)) return setStore("projects", key, [{ worktree: directory, expanded: true }, ...current]) }, close(directory: string) { diff --git a/packages/opencode/src/cli/cmd/desktop.ts b/packages/opencode/src/cli/cmd/desktop.ts new file mode 100644 index 00000000000..47640a76c7e --- /dev/null +++ b/packages/opencode/src/cli/cmd/desktop.ts @@ -0,0 +1,27 @@ +import { UI } from "../ui" +import { cmd } from "./cmd" +import open from "open" +import path from "path" +import { Filesystem } from "../../util/filesystem" + +export const DesktopCommand = cmd({ + command: "desktop [path]", + describe: "open path in opencode desktop app", + builder: (yargs) => { + return yargs.positional("path", { + describe: "path to open", + type: "string", + default: ".", + }) + }, + handler: async (args) => { + const targetPath = path.resolve(process.cwd(), args.path) + if (!(await Filesystem.exists(targetPath))) { + UI.error(`Path not found: ${targetPath}`) + process.exit(1) + } + + const url = `opencode://open-project?directory=${encodeURIComponent(targetPath)}` + await open(url) + }, +}) diff --git a/packages/opencode/src/index.ts b/packages/opencode/src/index.ts index 9af79278c06..5aaa8d335f6 100644 --- a/packages/opencode/src/index.ts +++ b/packages/opencode/src/index.ts @@ -26,6 +26,7 @@ import { TuiThreadCommand } from "./cli/cmd/tui/thread" import { AcpCommand } from "./cli/cmd/acp" import { EOL } from "os" import { WebCommand } from "./cli/cmd/web" +import { DesktopCommand } from "./cli/cmd/desktop" import { PrCommand } from "./cli/cmd/pr" import { SessionCommand } from "./cli/cmd/session" import { DbCommand } from "./cli/cmd/db" @@ -134,6 +135,7 @@ let cli = yargs(hideBin(process.argv)) .command(UninstallCommand) .command(ServeCommand) .command(WebCommand) + .command(DesktopCommand) .command(ModelsCommand) .command(StatsCommand) .command(ExportCommand)