Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 19 additions & 0 deletions packages/app/src/context/server.test.ts
Original file line number Diff line number Diff line change
@@ -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")
})
})
12 changes: 11 additions & 1 deletion packages/app/src/context/server.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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 }

Expand Down Expand Up @@ -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) {
Expand Down
27 changes: 27 additions & 0 deletions packages/opencode/src/cli/cmd/desktop.ts
Original file line number Diff line number Diff line change
@@ -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)
},
})
2 changes: 2 additions & 0 deletions packages/opencode/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -134,6 +135,7 @@ let cli = yargs(hideBin(process.argv))
.command(UninstallCommand)
.command(ServeCommand)
.command(WebCommand)
.command(DesktopCommand)
.command(ModelsCommand)
.command(StatsCommand)
.command(ExportCommand)
Expand Down
Loading