mirror of
https://github.com/Kilo-Org/kilocode.git
synced 2026-08-30 17:14:40 +08:00
feat(server,daemon): show local and network URLs for 0.0.0.0 binds
Extract shared serverUrls helper in cli/server-urls.ts that computes local, network, and bind URLs based on the resolved hostname and port. Include urls on the Listener type returned by server.listen(). Update serve, web, daemon, and console commands to print Local/Network lines when logging a non-loopback bind, and fall back to the existing single-line format for loopback binds. Add a changeset for the user-visible output change.
This commit is contained in:
committed by
Catriel Müller
parent
c1589777ff
commit
a6ded9b60a
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"@kilocode/cli": minor
|
||||
---
|
||||
|
||||
Display local and network URLs when the server binds to 0.0.0.0
|
||||
@@ -1,5 +1,6 @@
|
||||
import { Effect } from "effect"
|
||||
import { Server } from "../../server/server"
|
||||
import { serverUrls } from "../server-urls"
|
||||
import { effectCmd } from "../effect-cmd"
|
||||
import { withNetworkOptions, resolveNetworkOptions } from "../network"
|
||||
import { Flag } from "@opencode-ai/core/flag/flag"
|
||||
@@ -18,7 +19,13 @@ export const ServeCommand = effectCmd({
|
||||
}
|
||||
const opts = yield* resolveNetworkOptions(args)
|
||||
const server = yield* Effect.promise(() => Server.listen(opts))
|
||||
console.log(`kilo server listening on http://${server.hostname}:${server.port}`) // kilocode_change
|
||||
const urls = serverUrls(server.hostname, server.port)
|
||||
|
||||
console.log(`kilo server listening on ${urls.bind}`)
|
||||
if (urls.network) {
|
||||
console.log(` Local: ${urls.local}`)
|
||||
console.log(` Network: ${urls.network}`)
|
||||
}
|
||||
|
||||
// kilocode_change start - graceful signal shutdown
|
||||
// yield* Effect.never
|
||||
|
||||
@@ -1,34 +1,12 @@
|
||||
import { Effect } from "effect"
|
||||
import { Server } from "../../server/server"
|
||||
import { serverUrls } from "../server-urls"
|
||||
import { UI } from "../ui"
|
||||
import { effectCmd } from "../effect-cmd"
|
||||
import { withNetworkOptions, resolveNetworkOptions } from "../network"
|
||||
import { Flag } from "@opencode-ai/core/flag/flag"
|
||||
import { InstanceRuntime } from "../../project/instance-runtime" // kilocode_change
|
||||
import open from "open"
|
||||
import { networkInterfaces } from "os"
|
||||
|
||||
function getNetworkIPs() {
|
||||
const nets = networkInterfaces()
|
||||
const results: string[] = []
|
||||
|
||||
for (const name of Object.keys(nets)) {
|
||||
const net = nets[name]
|
||||
if (!net) continue
|
||||
|
||||
for (const netInfo of net) {
|
||||
// Skip internal and non-IPv4 addresses
|
||||
if (netInfo.internal || netInfo.family !== "IPv4") continue
|
||||
|
||||
// Skip Docker bridge networks (typically 172.x.x.x)
|
||||
if (netInfo.address.startsWith("172.")) continue
|
||||
|
||||
results.push(netInfo.address)
|
||||
}
|
||||
}
|
||||
|
||||
return results
|
||||
}
|
||||
|
||||
export const WebCommand = effectCmd({
|
||||
command: "web",
|
||||
@@ -47,39 +25,23 @@ export const WebCommand = effectCmd({
|
||||
UI.println(UI.logo(" "))
|
||||
UI.empty()
|
||||
|
||||
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)
|
||||
const urls = serverUrls(server.hostname, server.port)
|
||||
|
||||
// Show network IPs for remote access
|
||||
const networkIPs = getNetworkIPs()
|
||||
if (networkIPs.length > 0) {
|
||||
for (const ip of networkIPs) {
|
||||
UI.println(
|
||||
UI.Style.TEXT_INFO_BOLD + " Network access: ",
|
||||
UI.Style.TEXT_NORMAL,
|
||||
`http://${ip}:${server.port}`,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
if (opts.mdns) {
|
||||
UI.println(
|
||||
UI.Style.TEXT_INFO_BOLD + " mDNS: ",
|
||||
UI.Style.TEXT_NORMAL,
|
||||
`${opts.mdnsDomain}:${server.port}`,
|
||||
)
|
||||
}
|
||||
|
||||
// Open localhost in browser
|
||||
open(localhostUrl).catch(() => {})
|
||||
} else {
|
||||
const displayUrl = server.url.toString()
|
||||
UI.println(UI.Style.TEXT_INFO_BOLD + " Web interface: ", UI.Style.TEXT_NORMAL, displayUrl)
|
||||
open(displayUrl).catch(() => {})
|
||||
UI.println(UI.Style.TEXT_INFO_BOLD + " Local: ", UI.Style.TEXT_NORMAL, urls.local)
|
||||
if (urls.network) {
|
||||
UI.println(UI.Style.TEXT_INFO_BOLD + " Network: ", UI.Style.TEXT_NORMAL, urls.network)
|
||||
}
|
||||
|
||||
if (opts.mdns) {
|
||||
UI.println(
|
||||
UI.Style.TEXT_INFO_BOLD + " mDNS: ",
|
||||
UI.Style.TEXT_NORMAL,
|
||||
`${opts.mdnsDomain}:${server.port}`,
|
||||
)
|
||||
}
|
||||
|
||||
open(urls.local).catch(() => {})
|
||||
|
||||
// kilocode_change start - graceful signal shutdown
|
||||
yield* Effect.promise(
|
||||
() =>
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
import { networkInterfaces } from "os"
|
||||
|
||||
export function getNetworkIPs() {
|
||||
const nets = networkInterfaces()
|
||||
const results: string[] = []
|
||||
|
||||
for (const name of Object.keys(nets)) {
|
||||
const net = nets[name]
|
||||
if (!net) continue
|
||||
for (const netInfo of net) {
|
||||
if (netInfo.internal || netInfo.family !== "IPv4") continue
|
||||
if (netInfo.address.startsWith("172.")) continue
|
||||
results.push(netInfo.address)
|
||||
}
|
||||
}
|
||||
|
||||
return results
|
||||
}
|
||||
|
||||
export function serverUrls(hostname: string, port: number) {
|
||||
const local = `http://localhost:${port}`
|
||||
const bindStr = `http://${hostname}:${port}`
|
||||
|
||||
const isLoopback = hostname === "127.0.0.1" || hostname === "localhost" || hostname === "::1"
|
||||
const ips = getNetworkIPs()
|
||||
const lanUrl = ips.length > 0 ? `http://${ips[0]}:${port}` : undefined
|
||||
|
||||
return {
|
||||
local,
|
||||
network: isLoopback ? lanUrl : bindStr,
|
||||
bind: bindStr,
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
import open from "open"
|
||||
import { cmd } from "@/cli/cmd/cmd"
|
||||
import { explicitNetworkOptions, withNetworkOptions, resolveNetworkOptions } from "@/cli/network"
|
||||
import { serverUrls } from "@/cli/server-urls"
|
||||
import { AppRuntime } from "@/effect/app-runtime"
|
||||
import { Daemon } from "@/kilocode/daemon/daemon"
|
||||
import { warnPort } from "@/kilocode/cli/port-warning"
|
||||
@@ -48,10 +49,15 @@ export const KiloConsoleCommand = cmd({
|
||||
const state = daemon.result.state
|
||||
if (!state) throw new Error("Kilo daemon did not provide connection state")
|
||||
|
||||
const url = publicUrl(state)
|
||||
const urls = serverUrls(state.hostname, state.port)
|
||||
const consoleLocal = `${urls.local}/console`
|
||||
const consoleNetwork = urls.network ? `${urls.network}/console` : undefined
|
||||
|
||||
await launch(browserUrl(state)).catch((err) => {
|
||||
console.warn(`Could not open browser automatically: ${err instanceof Error ? err.message : String(err)}`)
|
||||
})
|
||||
console.log(`Kilo Console: ${url}`)
|
||||
console.log("Kilo Console:")
|
||||
console.log(` Local: ${consoleLocal}`)
|
||||
if (consoleNetwork) console.log(` Network: ${consoleNetwork}`)
|
||||
},
|
||||
})
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import type { Argv } from "yargs"
|
||||
import { cmd } from "@/cli/cmd/cmd"
|
||||
import { explicitNetworkOptions, withNetworkOptions, resolveNetworkOptions } from "@/cli/network"
|
||||
import { serverUrls } from "@/cli/server-urls"
|
||||
import { AppRuntime } from "@/effect/app-runtime"
|
||||
import { Daemon } from "@/kilocode/daemon/daemon"
|
||||
import { warnPort } from "@/kilocode/cli/port-warning"
|
||||
@@ -19,6 +20,7 @@ function safe(input: Daemon.State | undefined) {
|
||||
hostname: input.hostname,
|
||||
port: input.port,
|
||||
url: input.url,
|
||||
urls: input.urls,
|
||||
username: input.username,
|
||||
version: input.version,
|
||||
startedAt: input.startedAt,
|
||||
@@ -47,7 +49,13 @@ function print(input: Daemon.Status, json?: boolean) {
|
||||
return
|
||||
}
|
||||
console.log(`kilo daemon running`)
|
||||
console.log(`url: ${input.state?.url}`)
|
||||
if (input.state?.urls) {
|
||||
const urls = input.state.urls
|
||||
console.log(`local: ${urls.local}`)
|
||||
if (urls.network) console.log(`network: ${urls.network}`)
|
||||
} else {
|
||||
console.log(`url: ${input.state?.url}`)
|
||||
}
|
||||
console.log(`pid: ${input.state?.pid}`)
|
||||
console.log(`version: ${input.health?.version ?? input.state?.version}`)
|
||||
console.log(`auth: enabled`)
|
||||
|
||||
@@ -9,6 +9,7 @@ import { Flock } from "@opencode-ai/core/util/flock"
|
||||
import { InstallationVersion } from "@opencode-ai/core/installation/version"
|
||||
import { Filesystem } from "@/util/filesystem"
|
||||
import { Process } from "@/util/process"
|
||||
import { serverUrls } from "@/cli/server-urls"
|
||||
|
||||
export namespace Daemon {
|
||||
const username = "kilo"
|
||||
@@ -30,6 +31,13 @@ export namespace Daemon {
|
||||
hostname: z.string(),
|
||||
port: z.number().int().positive(),
|
||||
url: z.string(),
|
||||
urls: z
|
||||
.object({
|
||||
local: z.string(),
|
||||
network: z.string().optional(),
|
||||
bind: z.string(),
|
||||
})
|
||||
.optional(),
|
||||
username: z.string(),
|
||||
password: z.string(),
|
||||
token: z.string(),
|
||||
@@ -205,6 +213,7 @@ export namespace Daemon {
|
||||
hostname: ready.hostname,
|
||||
port: ready.port,
|
||||
url: `http://${host(ready.hostname)}:${ready.port}`,
|
||||
urls: serverUrls(ready.hostname, ready.port),
|
||||
username,
|
||||
password,
|
||||
token,
|
||||
|
||||
@@ -2,6 +2,7 @@ import "./init-projectors"
|
||||
|
||||
import { NodeHttpServer } from "@effect/platform-node"
|
||||
import * as Log from "@opencode-ai/core/util/log"
|
||||
import { serverUrls } from "../cli/server-urls"
|
||||
import { ConfigProvider, Context, Effect, Exit, Layer, Scope } from "effect"
|
||||
import { HttpRouter, HttpServer } from "effect/unstable/http"
|
||||
import { OpenApi } from "effect/unstable/httpapi"
|
||||
@@ -24,6 +25,11 @@ export type Listener = {
|
||||
hostname: string
|
||||
port: number
|
||||
url: URL
|
||||
urls: {
|
||||
local: string
|
||||
network?: string
|
||||
bind: string
|
||||
}
|
||||
stop: (close?: boolean) => Promise<void>
|
||||
}
|
||||
|
||||
@@ -79,6 +85,7 @@ export async function listen(opts: ListenOptions): Promise<Listener> {
|
||||
hostname: listener.hostname,
|
||||
port: listener.port,
|
||||
url: listener.url,
|
||||
urls: listener.urls,
|
||||
stop: (close?: boolean) => Effect.runPromiseExit(listener.stop(close)).then(() => undefined),
|
||||
}
|
||||
}
|
||||
@@ -96,6 +103,7 @@ const listenEffect: (opts: ListenOptions) => Effect.Effect<EffectListener, unkno
|
||||
hostname: opts.hostname,
|
||||
port: address.port,
|
||||
url: listenerUrl,
|
||||
urls: serverUrls(opts.hostname, address.port),
|
||||
stop: yield* makeStop(state, unpublishMdns),
|
||||
}
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user