diff --git a/packages/opencode/src/cli/cmd/serve.ts b/packages/opencode/src/cli/cmd/serve.ts index 657f9196c96..f2ef44945ae 100644 --- a/packages/opencode/src/cli/cmd/serve.ts +++ b/packages/opencode/src/cli/cmd/serve.ts @@ -1,14 +1,21 @@ import { Server } from "../../server/server" import { cmd } from "./cmd" import { withNetworkOptions, resolveNetworkOptions } from "../network" +import path from "path" export const ServeCommand = cmd({ command: "serve", - builder: (yargs) => withNetworkOptions(yargs), + builder: (yargs) => + withNetworkOptions(yargs).option("cwd", { + describe: "working directory", + type: "string", + }), describe: "starts a headless opencode server", handler: async (args) => { const opts = await resolveNetworkOptions(args) - const server = Server.listen(opts) + const base = process.env.PWD ?? process.cwd() + const cwd = args.cwd ? path.resolve(base, args.cwd) : undefined + const server = Server.listen({ ...opts, directory: cwd }) console.log(`opencode server listening on http://${server.hostname}:${server.port}`) await new Promise(() => {}) await server.stop() diff --git a/packages/opencode/src/project/instance.ts b/packages/opencode/src/project/instance.ts index d668037c016..882e03c3271 100644 --- a/packages/opencode/src/project/instance.ts +++ b/packages/opencode/src/project/instance.ts @@ -14,12 +14,12 @@ const context = Context.create("instance") const cache = new Map>() export const Instance = { - async provide(input: { directory: string; init?: () => Promise; fn: () => R }): Promise { + async provide(input: { directory: string; root?: string; init?: () => Promise; fn: () => R }): Promise { let existing = cache.get(input.directory) if (!existing) { Log.Default.info("creating instance", { directory: input.directory }) existing = iife(async () => { - const { project, sandbox } = await Project.fromDirectory(input.directory) + const { project, sandbox } = await Project.fromDirectory(input.directory, { root: input.root }) const ctx = { directory: input.directory, worktree: sandbox, diff --git a/packages/opencode/src/project/project.ts b/packages/opencode/src/project/project.ts index 35fdd4717b2..56a7067984f 100644 --- a/packages/opencode/src/project/project.ts +++ b/packages/opencode/src/project/project.ts @@ -16,6 +16,9 @@ import { existsSync } from "fs" export namespace Project { const log = Log.create({ service: "project" }) + type Root = { + root?: string + } export const Info = z .object({ id: z.string(), @@ -44,8 +47,9 @@ export namespace Project { Updated: BusEvent.define("project.updated", Info), } - export async function fromDirectory(directory: string) { - log.info("fromDirectory", { directory }) + export async function fromDirectory(directory: string, input?: Root) { + const root = input?.root ? path.resolve(input.root) : undefined + log.info("fromDirectory", { directory, root }) const { id, sandbox, worktree, vcs } = await iife(async () => { const matches = Filesystem.up({ targets: [".git"], start: directory }) @@ -161,6 +165,16 @@ export namespace Project { } } + if (root) { + const id = `root-${Bun.hash.xxHash32(root)}` + return { + id, + worktree: root, + sandbox: root, + vcs: Info.shape.vcs.parse(Flag.OPENCODE_FAKE_VCS), + } + } + return { id: "global", worktree: "/", diff --git a/packages/opencode/src/server/server.ts b/packages/opencode/src/server/server.ts index 04ec4673ec4..cf7b3acbb67 100644 --- a/packages/opencode/src/server/server.ts +++ b/packages/opencode/src/server/server.ts @@ -60,6 +60,8 @@ export namespace Server { let _url: URL | undefined let _corsWhitelist: string[] = [] + let _defaultDirectory: string | undefined + let _defaultRoot: string | undefined export function url(): URL { return _url ?? new URL("http://localhost:4096") @@ -246,9 +248,13 @@ export namespace Server { }, ) .use(async (c, next) => { - const directory = c.req.query("directory") || c.req.header("x-opencode-directory") || process.cwd() + const queryDirectory = c.req.query("directory") + const headerDirectory = c.req.header("x-opencode-directory") + const directory = queryDirectory || headerDirectory || _defaultDirectory || process.cwd() + const root = queryDirectory || headerDirectory ? directory : _defaultRoot return Instance.provide({ directory, + root, init: InstanceBootstrap, async fn() { return next() @@ -2829,8 +2835,10 @@ export namespace Server { return result } - export function listen(opts: { port: number; hostname: string; mdns?: boolean; cors?: string[] }) { + export function listen(opts: { port: number; hostname: string; mdns?: boolean; cors?: string[]; directory?: string }) { _corsWhitelist = opts.cors ?? [] + _defaultDirectory = opts.directory ?? process.cwd() + _defaultRoot = opts.directory ? _defaultDirectory : undefined const args = { hostname: opts.hostname, diff --git a/packages/sdk/js/src/server.ts b/packages/sdk/js/src/server.ts index 174131ccfd5..d2dc0a5369f 100644 --- a/packages/sdk/js/src/server.ts +++ b/packages/sdk/js/src/server.ts @@ -6,6 +6,7 @@ export type ServerOptions = { port?: number signal?: AbortSignal timeout?: number + cwd?: string config?: Config } @@ -29,6 +30,7 @@ export async function createOpencodeServer(options?: ServerOptions) { ) const args = [`serve`, `--hostname=${options.hostname}`, `--port=${options.port}`] + if (options.cwd) args.push(`--cwd=${options.cwd}`) if (options.config?.logLevel) args.push(`--log-level=${options.config.logLevel}`) const proc = spawn(`opencode`, args, { diff --git a/packages/sdk/js/src/v2/server.ts b/packages/sdk/js/src/v2/server.ts index 174131ccfd5..d2dc0a5369f 100644 --- a/packages/sdk/js/src/v2/server.ts +++ b/packages/sdk/js/src/v2/server.ts @@ -6,6 +6,7 @@ export type ServerOptions = { port?: number signal?: AbortSignal timeout?: number + cwd?: string config?: Config } @@ -29,6 +30,7 @@ export async function createOpencodeServer(options?: ServerOptions) { ) const args = [`serve`, `--hostname=${options.hostname}`, `--port=${options.port}`] + if (options.cwd) args.push(`--cwd=${options.cwd}`) if (options.config?.logLevel) args.push(`--log-level=${options.config.logLevel}`) const proc = spawn(`opencode`, args, {