From ae8bfb9d77f827fe3586a75ba7ffcb8dedfb29e1 Mon Sep 17 00:00:00 2001 From: SondreB Date: Sun, 7 Jun 2026 16:36:37 +0200 Subject: [PATCH] Add support for Windows - Native support for Windows Terminal (cmd). --- package.json | 2 +- src/cli.ts | 64 +++++++++++++++++++++++-------- src/daemon/index.ts | 4 +- src/daemon/wallet/cocod-client.ts | 10 ++++- src/start-daemon.ts | 11 ++++-- 5 files changed, 65 insertions(+), 26 deletions(-) diff --git a/package.json b/package.json index 5423e26..32f3700 100644 --- a/package.json +++ b/package.json @@ -12,7 +12,7 @@ "monitor": "bun src/index.ts monitor", "lint": "tsc --noEmit", "test": "bun test", - "build": "mkdir -p dist/daemon && bun build src/index.ts --target=bun --outfile=dist/index.js && bun build src/daemon/index.ts --target=bun --outfile=dist/daemon/index.js", + "build": "bun build src/index.ts --target=bun --outfile=dist/index.js --external better-sqlite3 && bun build src/daemon/index.ts --target=bun --outfile=dist/daemon/index.js --external better-sqlite3", "prepublishOnly": "bun run build" }, "devDependencies": { diff --git a/src/cli.ts b/src/cli.ts index 2da87d3..53e335e 100644 --- a/src/cli.ts +++ b/src/cli.ts @@ -15,7 +15,7 @@ import { deleteClientAction, addClientAction, } from "./utils/clients"; -import { existsSync, mkdirSync } from "fs"; +import { existsSync, mkdirSync, readFileSync, statSync } from "fs"; import { execSync } from "child_process"; import { CONFIG_DIR, @@ -429,6 +429,7 @@ program const installHint = config.cocodPath ? `Configured cocod executable was not found: ${config.cocodPath}` : "cocod is not installed. Run 'routstrd onboard' first to install cocod."; + console.error(installHint); logger.error(installHint); process.exit(1); } @@ -1650,6 +1651,40 @@ function getLogFileForDate(date: Date = new Date()): string { return `${LOGS_DIR}/${year}-${month}-${day}.log`; } +function readLastLines(file: string, lines: number): string { + const content = readFileSync(file, "utf8"); + const allLines = content.replace(/\r\n/g, "\n").split("\n"); + if (allLines.at(-1) === "") allLines.pop(); + return allLines.slice(-lines).join("\n"); +} + +async function followLogFile(file: string, lines: number): Promise { + const initial = readLastLines(file, lines); + if (initial) { + console.log(initial); + } + + let position = statSync(file).size; + while (true) { + await new Promise((resolve) => setTimeout(resolve, 1000)); + if (!existsSync(file)) { + continue; + } + + const size = statSync(file).size; + if (size < position) { + position = 0; + } + if (size === position) { + continue; + } + + const text = await Bun.file(file).slice(position, size).text(); + process.stdout.write(text); + position = size; + } +} + program .command("logs") .description("View daemon logs") @@ -1675,24 +1710,19 @@ program }); if (options.follow) { - const proc = Bun.spawn(["tail", "-n", String(lines), "-f", todayFile], { - stdout: "inherit", - stderr: "inherit", - stdin: "inherit", - }); - - const exitCode = await proc.exited; - process.exit(exitCode); + await followLogFile(todayFile, lines); + return; } - const proc = Bun.spawn(["tail", "-n", String(lines), ...logFiles], { - stdout: "inherit", - stderr: "inherit", - stdin: "inherit", - }); - - const exitCode = await proc.exited; - process.exit(exitCode); + for (const file of logFiles) { + if (logFiles.length > 1) { + console.log(`==> ${file} <==`); + } + const output = readLastLines(file, lines); + if (output) { + console.log(output); + } + } }); export function cli(args: string[]) { diff --git a/src/daemon/index.ts b/src/daemon/index.ts index 8cf95fd..01b3e73 100644 --- a/src/daemon/index.ts +++ b/src/daemon/index.ts @@ -1,5 +1,5 @@ import { createServer } from "http"; -import { existsSync } from "fs"; +import { existsSync, unlinkSync } from "fs"; import { ModelManager, ProviderManager, @@ -135,7 +135,7 @@ async function main(): Promise { try { if (existsSync(SOCKET_PATH)) { - Bun.spawn(["rm", SOCKET_PATH]); + unlinkSync(SOCKET_PATH); } } catch { // Ignore diff --git a/src/daemon/wallet/cocod-client.ts b/src/daemon/wallet/cocod-client.ts index 1d85568..357a99e 100644 --- a/src/daemon/wallet/cocod-client.ts +++ b/src/daemon/wallet/cocod-client.ts @@ -1,5 +1,6 @@ import { existsSync } from "fs"; import { createHash } from "crypto"; +import { isAbsolute } from "path"; import { logger } from "../../utils/logger"; import { withCrossProcessLock } from "../../utils/process-lock"; @@ -68,13 +69,18 @@ export async function isCocodInstalled( ): Promise { const executable = resolveCocodExecutable(cocodPath); - if (executable.includes("/")) { + if ( + isAbsolute(executable) || + executable.includes("/") || + executable.includes("\\") + ) { return existsSync(executable); } try { + const command = process.platform === "win32" ? "where.exe" : "which"; const proc = Bun.spawn({ - cmd: ["which", executable], + cmd: [command, executable], stdout: "ignore", stderr: "ignore", }); diff --git a/src/start-daemon.ts b/src/start-daemon.ts index 9d9ea8f..ac55929 100644 --- a/src/start-daemon.ts +++ b/src/start-daemon.ts @@ -1,6 +1,8 @@ import { logger } from "./utils/logger"; import { CONFIG_DIR, LOGS_DIR } from "./utils/config"; import { withCrossProcessLock } from "./utils/process-lock"; +import { fileURLToPath } from "url"; +import { existsSync } from "fs"; const DAEMON_STARTUP_LOCK_PATH = `${CONFIG_DIR}/routstrd-startup.lock`; @@ -39,10 +41,11 @@ async function startDaemonUnlocked( args.push("--provider", options.provider); } - const daemonScript = new URL("./daemon/index.js", import.meta.url).pathname; - const shellCmd = `bun run "${daemonScript}" ${args.map((a) => `'${a}'`).join(" ")}`; - - const proc = Bun.spawn(["sh", "-c", shellCmd], { + let daemonScript = fileURLToPath(new URL("./daemon/index.js", import.meta.url)); + if (!existsSync(daemonScript)) { + daemonScript = fileURLToPath(new URL("./daemon/index.ts", import.meta.url)); + } + const proc = Bun.spawn(["bun", daemonScript, ...args], { stdout: "ignore", stderr: "ignore", stdin: "ignore",