Skip to content
Merged
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
11 changes: 11 additions & 0 deletions bun.lock
Original file line number Diff line number Diff line change
Expand Up @@ -292,6 +292,7 @@
"@standard-schema/spec": "1.0.0",
"@zip.js/zip.js": "2.7.62",
"ai": "catalog:",
"bonjour-service": "1.3.0",
"bun-pty": "0.4.2",
"chokidar": "4.0.3",
"clipboardy": "4.0.0",
Expand Down Expand Up @@ -1081,6 +1082,8 @@

"@kurkle/color": ["@kurkle/color@0.3.4", "", {}, "sha512-M5UknZPHRu3DEDWoipU6sE8PdkZ6Z/S+v4dD+Ke8IaNlpdSQah50lz1KtcFBa2vsdOnwbbnxJwVM4wty6udA5w=="],

"@leichtgewicht/ip-codec": ["@leichtgewicht/ip-codec@2.0.5", "", {}, "sha512-Vo+PSpZG2/fmgmiNzYK9qWRh8h/CHrwD0mo1h1DzL4yzHNSfWYujGTYsWGreD000gcgmZ7K4Ys6Tx9TxtsKdDw=="],

"@mdx-js/mdx": ["@mdx-js/mdx@3.1.1", "", { "dependencies": { "@types/estree": "^1.0.0", "@types/estree-jsx": "^1.0.0", "@types/hast": "^3.0.0", "@types/mdx": "^2.0.0", "acorn": "^8.0.0", "collapse-white-space": "^2.0.0", "devlop": "^1.0.0", "estree-util-is-identifier-name": "^3.0.0", "estree-util-scope": "^1.0.0", "estree-walker": "^3.0.0", "hast-util-to-jsx-runtime": "^2.0.0", "markdown-extensions": "^2.0.0", "recma-build-jsx": "^1.0.0", "recma-jsx": "^1.0.0", "recma-stringify": "^1.0.0", "rehype-recma": "^1.0.0", "remark-mdx": "^3.0.0", "remark-parse": "^11.0.0", "remark-rehype": "^11.0.0", "source-map": "^0.7.0", "unified": "^11.0.0", "unist-util-position-from-estree": "^2.0.0", "unist-util-stringify-position": "^4.0.0", "unist-util-visit": "^5.0.0", "vfile": "^6.0.0" } }, "sha512-f6ZO2ifpwAQIpzGWaBQT2TXxPv6z3RBzQKpVftEWN78Vl/YweF1uwussDx8ECAXVtr3Rs89fKyG9YlzUs9DyGQ=="],

"@mixmark-io/domino": ["@mixmark-io/domino@2.2.0", "", {}, "sha512-Y28PR25bHXUg88kCV7nivXrP2Nj2RueZ3/l/jdx6J9f8J4nsEGcgX0Qe6lt7Pa+J79+kPiJU3LguR6O/6zrLOw=="],
Expand Down Expand Up @@ -2003,6 +2006,8 @@

"body-parser": ["body-parser@1.20.3", "", { "dependencies": { "bytes": "3.1.2", "content-type": "~1.0.5", "debug": "2.6.9", "depd": "2.0.0", "destroy": "1.2.0", "http-errors": "2.0.0", "iconv-lite": "0.4.24", "on-finished": "2.4.1", "qs": "6.13.0", "raw-body": "2.5.2", "type-is": "~1.6.18", "unpipe": "1.0.0" } }, "sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g=="],

"bonjour-service": ["bonjour-service@1.3.0", "", { "dependencies": { "fast-deep-equal": "^3.1.3", "multicast-dns": "^7.2.5" } }, "sha512-3YuAUiSkWykd+2Azjgyxei8OWf8thdn8AITIog2M4UICzoqfjlqr64WIjEXZllf/W6vK1goqleSR6brGomxQqA=="],

"boolbase": ["boolbase@1.0.0", "", {}, "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww=="],

"bottleneck": ["bottleneck@2.19.5", "", {}, "sha512-VHiNCbI1lKdl44tGrhNfU3lup0Tj/ZBMJB5/2ZbNXRCPuRCO7ed2mgcK4r17y+KB2EfuYuRaVlwNbAeaWGSpbw=="],
Expand Down Expand Up @@ -2247,6 +2252,8 @@

"dlv": ["dlv@1.1.3", "", {}, "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA=="],

"dns-packet": ["dns-packet@5.6.1", "", { "dependencies": { "@leichtgewicht/ip-codec": "^2.0.1" } }, "sha512-l4gcSouhcgIKRvyy99RNVOgxXiicE+2jZoNmaNmZ6JXiGajBOJAesk1OBlJuM5k2c+eudGdLxDqXuPCKIj6kpw=="],

"dom-serializer": ["dom-serializer@2.0.0", "", { "dependencies": { "domelementtype": "^2.3.0", "domhandler": "^5.0.2", "entities": "^4.2.0" } }, "sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg=="],

"domelementtype": ["domelementtype@2.3.0", "", {}, "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw=="],
Expand Down Expand Up @@ -3023,6 +3030,8 @@

"ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="],

"multicast-dns": ["multicast-dns@7.2.5", "", { "dependencies": { "dns-packet": "^5.2.2", "thunky": "^1.0.2" }, "bin": { "multicast-dns": "cli.js" } }, "sha512-2eznPJP8z2BFLX50tf0LuODrpINqP1RVIm/CObbTcBRITQgmC/TjcREF1NeTBzIcR5XO/ukWo+YHOjBbFwIupg=="],

"mustache": ["mustache@4.2.0", "", { "bin": { "mustache": "bin/mustache" } }, "sha512-71ippSywq5Yb7/tVYyGbkBggbU8H3u5Rz56fH60jGFgr8uHwxs+aSKeqmluIVzM0m0kB7xQjKS6qPfd0b2ZoqQ=="],

"mysql2": ["mysql2@3.14.4", "", { "dependencies": { "aws-ssl-profiles": "^1.1.1", "denque": "^2.1.0", "generate-function": "^2.3.1", "iconv-lite": "^0.7.0", "long": "^5.2.1", "lru.min": "^1.0.0", "named-placeholders": "^1.1.3", "seq-queue": "^0.0.5", "sqlstring": "^2.3.2" } }, "sha512-Cs/jx3WZPNrYHVz+Iunp9ziahaG5uFMvD2R8Zlmc194AqXNxt9HBNu7ZsPYrUtmJsF0egETCWIdMIYAwOGjL1w=="],
Expand Down Expand Up @@ -3595,6 +3604,8 @@

"three": ["three@0.177.0", "", {}, "sha512-EiXv5/qWAaGI+Vz2A+JfavwYCMdGjxVsrn3oBwllUoqYeaBO75J63ZfyaQKoiLrqNHoTlUc6PFgMXnS0kI45zg=="],

"thunky": ["thunky@1.1.0", "", {}, "sha512-eHY7nBftgThBqOyHGVN+l8gF0BucP09fMo0oO/Lb0w1OF80dJv+lDVpXG60WMQvkcxAkNybKsrEIE3ZtKGmPrA=="],

"tiny-inflate": ["tiny-inflate@1.0.3", "", {}, "sha512-pkY1fj1cKHb2seWDy0B16HeWyczlJA9/WW3u3c4z/NiWDsO3DOU5D7nhTLE9CF0yXv/QZFY7sEJmj24dK+Rrqw=="],

"tiny-invariant": ["tiny-invariant@1.3.3", "", {}, "sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg=="],
Expand Down
6 changes: 3 additions & 3 deletions flake.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion nix/hashes.json
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
{
"nodeModules": "sha256-hotsyeWJA6/dP6DvZTN1Ak2RSKcsyvXlXPI/jexBHME="
"nodeModules": "sha256-okbViEKf1mRSmzbJgKdB9SJ875q84Bwu8d3ChHuaQ1g="
}
1 change: 1 addition & 0 deletions packages/opencode/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@
"@standard-schema/spec": "1.0.0",
"@zip.js/zip.js": "2.7.62",
"ai": "catalog:",
"bonjour-service": "1.3.0",
"bun-pty": "0.4.2",
"chokidar": "4.0.3",
"clipboardy": "4.0.0",
Expand Down
30 changes: 10 additions & 20 deletions packages/opencode/src/cli/cmd/acp.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,10 @@ import { bootstrap } from "../bootstrap"
import { cmd } from "./cmd"
import { AgentSideConnection, ndJsonStream } from "@agentclientprotocol/sdk"
import { ACP } from "@/acp/agent"
import { Config } from "@/config/config"
import { Server } from "@/server/server"
import { createOpencodeClient } from "@opencode-ai/sdk/v2"
import { withNetworkOptions, resolveNetworkOptions } from "../network"

const log = Log.create({ service: "acp-command" })

Expand All @@ -19,29 +21,17 @@ export const AcpCommand = cmd({
command: "acp",
describe: "start ACP (Agent Client Protocol) server",
builder: (yargs) => {
return yargs
.option("cwd", {
describe: "working directory",
type: "string",
default: process.cwd(),
})
.option("port", {
type: "number",
describe: "port to listen on",
default: 0,
})
.option("hostname", {
type: "string",
describe: "hostname to listen on",
default: "127.0.0.1",
})
return withNetworkOptions(yargs).option("cwd", {
describe: "working directory",
type: "string",
default: process.cwd(),
})
},
handler: async (args) => {
await bootstrap(process.cwd(), async () => {
const server = Server.listen({
port: args.port,
hostname: args.hostname,
})
const config = await Config.get()
const opts = resolveNetworkOptions(args, config)
const server = Server.listen(opts)

const sdk = createOpencodeClient({
baseUrl: `http://${server.hostname}:${server.port}`,
Expand Down
25 changes: 6 additions & 19 deletions packages/opencode/src/cli/cmd/serve.ts
Original file line number Diff line number Diff line change
@@ -1,29 +1,16 @@
import { Config } from "../../config/config"
import { Server } from "../../server/server"
import { cmd } from "./cmd"
import { withNetworkOptions, resolveNetworkOptions } from "../network"

export const ServeCommand = cmd({
command: "serve",
builder: (yargs) =>
yargs
.option("port", {
alias: ["p"],
type: "number",
describe: "port to listen on",
default: 0,
})
.option("hostname", {
type: "string",
describe: "hostname to listen on",
default: "127.0.0.1",
}),
builder: (yargs) => withNetworkOptions(yargs),
describe: "starts a headless opencode server",
handler: async (args) => {
const hostname = args.hostname
const port = args.port
const server = Server.listen({
port,
hostname,
})
const config = await Config.get()
const opts = resolveNetworkOptions(args, config)
const server = Server.listen(opts)
console.log(`opencode server listening on http://${server.hostname}:${server.port}`)
await new Promise(() => {})
await server.stop()
Expand Down
28 changes: 9 additions & 19 deletions packages/opencode/src/cli/cmd/tui/spawn.ts
Original file line number Diff line number Diff line change
@@ -1,33 +1,23 @@
import { cmd } from "@/cli/cmd/cmd"
import { Config } from "@/config/config"
import { Instance } from "@/project/instance"
import path from "path"
import { Server } from "@/server/server"
import { upgrade } from "@/cli/upgrade"
import { withNetworkOptions, resolveNetworkOptions } from "@/cli/network"

export const TuiSpawnCommand = cmd({
command: "spawn [project]",
builder: (yargs) =>
yargs
.positional("project", {
type: "string",
describe: "path to start opencode in",
})
.option("port", {
type: "number",
describe: "port to listen on",
default: 0,
})
.option("hostname", {
type: "string",
describe: "hostname to listen on",
default: "127.0.0.1",
}),
withNetworkOptions(yargs).positional("project", {
type: "string",
describe: "path to start opencode in",
}),
handler: async (args) => {
upgrade()
const server = Server.listen({
port: args.port,
hostname: "127.0.0.1",
})
const config = await Config.get()
const opts = resolveNetworkOptions(args, config)
const server = Server.listen(opts)
const bin = process.execPath
const cmd = []
let cwd = process.cwd()
Expand Down
22 changes: 6 additions & 16 deletions packages/opencode/src/cli/cmd/tui/thread.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ import path from "path"
import { UI } from "@/cli/ui"
import { iife } from "@/util/iife"
import { Log } from "@/util/log"
import { withNetworkOptions, resolveNetworkOptions } from "@/cli/network"
import { Config } from "@/config/config"

declare global {
const OPENCODE_WORKER_PATH: string
Expand All @@ -15,7 +17,7 @@ export const TuiThreadCommand = cmd({
command: "$0 [project]",
describe: "start opencode tui",
builder: (yargs) =>
yargs
withNetworkOptions(yargs)
.positional("project", {
type: "string",
describe: "path to start opencode in",
Expand All @@ -36,23 +38,12 @@ export const TuiThreadCommand = cmd({
describe: "session id to continue",
})
.option("prompt", {
alias: ["p"],
type: "string",
describe: "prompt to use",
})
.option("agent", {
type: "string",
describe: "agent to use",
})
.option("port", {
type: "number",
describe: "port to listen on",
default: 0,
})
.option("hostname", {
type: "string",
describe: "hostname to listen on",
default: "127.0.0.1",
}),
handler: async (args) => {
// Resolve relative paths against PWD to preserve behavior when using --cwd flag
Expand Down Expand Up @@ -87,10 +78,9 @@ export const TuiThreadCommand = cmd({
process.on("unhandledRejection", (e) => {
Log.Default.error(e)
})
const server = await client.call("server", {
port: args.port,
hostname: args.hostname,
})
const config = await Config.get()
const networkOpts = resolveNetworkOptions(args, config)
const server = await client.call("server", networkOpts)
const prompt = await iife(async () => {
const piped = !process.stdin.isTTY ? await Bun.stdin.text() : undefined
if (!args.prompt) return piped
Expand Down
2 changes: 1 addition & 1 deletion packages/opencode/src/cli/cmd/tui/worker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ process.on("uncaughtException", (e) => {

let server: Bun.Server<BunWebSocketData>
export const rpc = {
async server(input: { port: number; hostname: string }) {
async server(input: { port: number; hostname: string; mdns?: boolean }) {
if (server) await server.stop(true)
try {
server = Server.listen(input)
Expand Down
31 changes: 11 additions & 20 deletions packages/opencode/src/cli/cmd/web.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import { Config } from "../../config/config"
import { Server } from "../../server/server"
import { UI } from "../ui"
import { cmd } from "./cmd"
import { withNetworkOptions, resolveNetworkOptions } from "../network"
import open from "open"
import { networkInterfaces } from "os"

Expand Down Expand Up @@ -28,32 +30,17 @@ function getNetworkIPs() {

export const WebCommand = cmd({
command: "web",
builder: (yargs) =>
yargs
.option("port", {
alias: ["p"],
type: "number",
describe: "port to listen on",
default: 0,
})
.option("hostname", {
type: "string",
describe: "hostname to listen on",
default: "127.0.0.1",
}),
builder: (yargs) => withNetworkOptions(yargs),
describe: "starts a headless opencode server",
handler: async (args) => {
const hostname = args.hostname
const port = args.port
const server = Server.listen({
port,
hostname,
})
const config = await Config.get()
const opts = resolveNetworkOptions(args, config)
const server = Server.listen(opts)
UI.empty()
UI.println(UI.logo(" "))
UI.empty()

if (hostname === "0.0.0.0") {
if (opts.hostname === "0.0.0.0") {
// Show localhost for local access
const localhostUrl = `http://localhost:${server.port}`
UI.println(UI.Style.TEXT_INFO_BOLD + " Local access: ", UI.Style.TEXT_NORMAL, localhostUrl)
Expand All @@ -70,6 +57,10 @@ export const WebCommand = cmd({
}
}

if (opts.mdns) {
UI.println(UI.Style.TEXT_INFO_BOLD + " mDNS: ", UI.Style.TEXT_NORMAL, "opencode.local")
}

// Open localhost in browser
open(localhostUrl.toString()).catch(() => {})
} else {
Expand Down
42 changes: 42 additions & 0 deletions packages/opencode/src/cli/network.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import type { Argv, InferredOptionTypes } from "yargs"
import type { Config } from "../config/config"

const options = {
port: {
type: "number" as const,
describe: "port to listen on",
default: 0,
},
hostname: {
type: "string" as const,
describe: "hostname to listen on",
default: "127.0.0.1",
},
mdns: {
type: "boolean" as const,
describe: "enable mDNS service discovery (defaults hostname to 0.0.0.0)",
default: false,
},
}

export type NetworkOptions = InferredOptionTypes<typeof options>

export function withNetworkOptions<T>(yargs: Argv<T>) {
return yargs.options(options)
}

export function resolveNetworkOptions(args: NetworkOptions, config?: Config.Info) {
const portExplicitlySet = process.argv.includes("--port")
const hostnameExplicitlySet = process.argv.includes("--hostname")
const mdnsExplicitlySet = process.argv.includes("--mdns")

const mdns = mdnsExplicitlySet ? args.mdns : (config?.server?.mdns ?? args.mdns)
const port = portExplicitlySet ? args.port : (config?.server?.port ?? args.port)
const hostname = hostnameExplicitlySet
? args.hostname
: mdns && !config?.server?.hostname
? "0.0.0.0"
: (config?.server?.hostname ?? args.hostname)

return { hostname, port, mdns }
}
Loading