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
13 changes: 12 additions & 1 deletion src/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,11 @@ import { createGitProcessEnv } from "./core/git-process-env.js";
import {
buildKanbanRuntimeUrl,
DEFAULT_KANBAN_RUNTIME_PORT,
getKanbanRuntimeHost,
getKanbanRuntimeOrigin,
getKanbanRuntimePort,
parseRuntimePort,
setKanbanRuntimeHost,
setKanbanRuntimePort,
} from "./core/runtime-endpoint.js";
import { resolveProjectInputPath } from "./projects/project-path.js";
Expand All @@ -39,6 +41,7 @@ interface CliOptions {
noOpen: boolean;
skipShutdownCleanup: boolean;
agent: RuntimeAgentId | null;
host: string | null;
port: { mode: "fixed"; value: number } | { mode: "auto" } | null;
}

Expand Down Expand Up @@ -77,6 +80,7 @@ function parseCliPortValue(rawValue: string): { mode: "fixed"; value: number } |

interface RootCommandOptions {
agent?: RuntimeAgentId;
host?: string;
port?: { mode: "fixed"; value: number } | { mode: "auto" };
open?: boolean;
skipShutdownCleanup?: boolean;
Expand All @@ -88,7 +92,7 @@ async function isPortAvailable(port: number): Promise<boolean> {
probe.once("error", () => {
resolve(false);
});
probe.listen(port, "127.0.0.1", () => {
probe.listen(port, getKanbanRuntimeHost(), () => {
probe.close(() => {
resolve(true);
});
Expand Down Expand Up @@ -371,6 +375,11 @@ async function startServerWithAutoPortRetry(options: CliOptions): Promise<Awaite
}

async function runMainCommand(options: CliOptions): Promise<void> {
if (options.host) {
setKanbanRuntimeHost(options.host);
console.log(`Binding to host ${options.host}.`);
}

const selectedPort = await applyRuntimePortOption(options.port);
if (selectedPort !== null) {
console.log(`Using runtime port ${selectedPort}.`);
Expand Down Expand Up @@ -476,6 +485,7 @@ function createProgram(): Command {
.description("Local orchestration board for coding agents.")
.version(KANBAN_VERSION, "-v, --version", "Output the version number")
.option("--agent <id>", `Default agent ID (${CLI_AGENT_IDS.join(", ")}).`, parseCliAgentId)
.option("--host <ip>", "Host IP to bind the server to (default: 127.0.0.1).")
.option("--port <number|auto>", "Runtime port (1-65535) or auto.", parseCliPortValue)
.option("--no-open", "Do not open browser automatically.")
.option("--skip-shutdown-cleanup", "Do not move sessions to trash or delete task worktrees on shutdown.")
Expand All @@ -495,6 +505,7 @@ function createProgram(): Command {
program.action(async (options: RootCommandOptions) => {
await runMainCommand({
agent: options.agent ?? null,
host: options.host ?? null,
port: options.port ?? null,
noOpen: options.open === false,
skipShutdownCleanup: options.skipShutdownCleanup === true,
Expand Down
17 changes: 14 additions & 3 deletions src/core/runtime-endpoint.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,17 @@
export const KANBAN_RUNTIME_HOST = "127.0.0.1";
export const DEFAULT_KANBAN_RUNTIME_HOST = "127.0.0.1";
export const DEFAULT_KANBAN_RUNTIME_PORT = 3484;

let runtimeHost: string = process.env.KANBAN_RUNTIME_HOST?.trim() || DEFAULT_KANBAN_RUNTIME_HOST;

export function getKanbanRuntimeHost(): string {
return runtimeHost;
}

export function setKanbanRuntimeHost(host: string): void {
runtimeHost = host;
process.env.KANBAN_RUNTIME_HOST = host;
}

export function parseRuntimePort(rawPort: string | undefined): number {
if (!rawPort) {
return DEFAULT_KANBAN_RUNTIME_PORT;
Expand All @@ -25,11 +36,11 @@ export function setKanbanRuntimePort(port: number): void {
}

export function getKanbanRuntimeOrigin(): string {
return `http://${KANBAN_RUNTIME_HOST}:${getKanbanRuntimePort()}`;
return `http://${getKanbanRuntimeHost()}:${getKanbanRuntimePort()}`;
}

export function getKanbanRuntimeWsOrigin(): string {
return `ws://${KANBAN_RUNTIME_HOST}:${getKanbanRuntimePort()}`;
return `ws://${getKanbanRuntimeHost()}:${getKanbanRuntimePort()}`;
}

export function buildKanbanRuntimeUrl(pathname: string): string {
Expand Down
4 changes: 2 additions & 2 deletions src/server/runtime-server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import {
buildKanbanRuntimeUrl,
getKanbanRuntimeOrigin,
getKanbanRuntimePort,
KANBAN_RUNTIME_HOST,
getKanbanRuntimeHost,
} from "../core/runtime-endpoint.js";
import { loadWorkspaceContextById } from "../state/workspace-state.js";
import type { TerminalSessionManager } from "../terminal/session-manager.js";
Expand Down Expand Up @@ -224,7 +224,7 @@ export async function createRuntimeServer(deps: CreateRuntimeServerDependencies)

await new Promise<void>((resolveListen, rejectListen) => {
server.once("error", rejectListen);
server.listen(getKanbanRuntimePort(), KANBAN_RUNTIME_HOST, () => {
server.listen(getKanbanRuntimePort(), getKanbanRuntimeHost(), () => {
server.off("error", rejectListen);
resolveListen();
});
Expand Down
27 changes: 25 additions & 2 deletions test/runtime/runtime-endpoint.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,21 +4,31 @@ import {
buildKanbanRuntimeUrl,
buildKanbanRuntimeWsUrl,
DEFAULT_KANBAN_RUNTIME_PORT,
getKanbanRuntimeHost,
getKanbanRuntimePort,
parseRuntimePort,
setKanbanRuntimeHost,
setKanbanRuntimePort,
} from "../../src/core/runtime-endpoint.js";

const originalRuntimePort = getKanbanRuntimePort();
const originalRuntimeHost = getKanbanRuntimeHost();
const originalEnvPort = process.env.KANBAN_RUNTIME_PORT;
const originalEnvHost = process.env.KANBAN_RUNTIME_HOST;

afterEach(() => {
setKanbanRuntimePort(originalRuntimePort);
setKanbanRuntimeHost(originalRuntimeHost);
if (originalEnvPort === undefined) {
delete process.env.KANBAN_RUNTIME_PORT;
return;
} else {
process.env.KANBAN_RUNTIME_PORT = originalEnvPort;
}
if (originalEnvHost === undefined) {
delete process.env.KANBAN_RUNTIME_HOST;
} else {
process.env.KANBAN_RUNTIME_HOST = originalEnvHost;
}
process.env.KANBAN_RUNTIME_PORT = originalEnvPort;
});

describe("runtime-endpoint", () => {
Expand All @@ -39,4 +49,17 @@ describe("runtime-endpoint", () => {
expect(buildKanbanRuntimeUrl("/api/trpc")).toBe("http://127.0.0.1:4567/api/trpc");
expect(buildKanbanRuntimeWsUrl("api/terminal/ws")).toBe("ws://127.0.0.1:4567/api/terminal/ws");
});

it("updates runtime url builders when host changes", () => {
setKanbanRuntimeHost("100.64.0.1");
setKanbanRuntimePort(4567);
expect(getKanbanRuntimeHost()).toBe("100.64.0.1");
expect(process.env.KANBAN_RUNTIME_HOST).toBe("100.64.0.1");
expect(buildKanbanRuntimeUrl("/api/trpc")).toBe("http://100.64.0.1:4567/api/trpc");
expect(buildKanbanRuntimeWsUrl("api/terminal/ws")).toBe("ws://100.64.0.1:4567/api/terminal/ws");
});

it("defaults host to 127.0.0.1", () => {
expect(getKanbanRuntimeHost()).toBe("127.0.0.1");
});
});