From 79e14dd79d837bcc78e9fdcf829866d250db66a5 Mon Sep 17 00:00:00 2001 From: Advait Paliwal Date: Tue, 24 Mar 2026 19:32:10 -0700 Subject: [PATCH] Fix packaged runtime startup and version flag --- package.json | 2 +- scripts/patch-embedded-pi.mjs | 61 +++++++++++++++++++++-------------- src/cli.ts | 9 ++++++ 3 files changed, 46 insertions(+), 26 deletions(-) diff --git a/package.json b/package.json index 23d7889..b78c662 100644 --- a/package.json +++ b/package.json @@ -33,7 +33,7 @@ "build": "tsc -p tsconfig.build.json", "build:native-bundle": "node ./scripts/build-native-bundle.mjs", "dev": "tsx src/index.ts", - "prepack": "node ./scripts/prepare-runtime-workspace.mjs", + "prepack": "npm run build && node ./scripts/prepare-runtime-workspace.mjs", "start": "tsx src/index.ts", "start:dist": "node ./bin/feynman.js", "test": "node --import tsx --test --test-concurrency=1 tests/*.test.ts", diff --git a/scripts/patch-embedded-pi.mjs b/scripts/patch-embedded-pi.mjs index 3afbb04..c403262 100644 --- a/scripts/patch-embedded-pi.mjs +++ b/scripts/patch-embedded-pi.mjs @@ -1,28 +1,40 @@ import { spawnSync } from "node:child_process"; import { existsSync, mkdirSync, readFileSync, rmSync, writeFileSync } from "node:fs"; +import { createRequire } from "node:module"; import { dirname, resolve } from "node:path"; import { fileURLToPath } from "node:url"; import { FEYNMAN_LOGO_HTML } from "../logo.mjs"; const here = dirname(fileURLToPath(import.meta.url)); const appRoot = resolve(here, ".."); +const appRequire = createRequire(resolve(appRoot, "package.json")); const isGlobalInstall = process.env.npm_config_global === "true" || process.env.npm_config_location === "global"; -function findNodeModules() { - let dir = appRoot; - while (dir !== dirname(dir)) { - const nm = resolve(dir, "node_modules"); - if (existsSync(nm)) return nm; - dir = dirname(dir); - } - return resolve(appRoot, "node_modules"); -} - -const nodeModules = findNodeModules(); - function findPackageRoot(packageName) { - const candidate = resolve(nodeModules, packageName); - if (existsSync(resolve(candidate, "package.json"))) return candidate; + const segments = packageName.split("/"); + let current = appRoot; + while (current !== dirname(current)) { + for (const candidate of [resolve(current, "node_modules", ...segments), resolve(current, ...segments)]) { + if (existsSync(resolve(candidate, "package.json"))) { + return candidate; + } + } + current = dirname(current); + } + + for (const spec of [`${packageName}/dist/index.js`, `${packageName}/dist/cli.js`, packageName]) { + try { + let current = dirname(appRequire.resolve(spec)); + while (current !== dirname(current)) { + if (existsSync(resolve(current, "package.json"))) { + return current; + } + current = dirname(current); + } + } catch { + continue; + } + } return null; } @@ -31,15 +43,14 @@ const piTuiRoot = findPackageRoot("@mariozechner/pi-tui"); const piAiRoot = findPackageRoot("@mariozechner/pi-ai"); if (!piPackageRoot) { - console.warn("[feynman] pi-coding-agent not found, skipping patches"); - process.exit(0); + console.warn("[feynman] pi-coding-agent not found, skipping Pi patches"); } -const packageJsonPath = resolve(piPackageRoot, "package.json"); -const cliPath = resolve(piPackageRoot, "dist", "cli.js"); -const bunCliPath = resolve(piPackageRoot, "dist", "bun", "cli.js"); -const interactiveModePath = resolve(piPackageRoot, "dist", "modes", "interactive", "interactive-mode.js"); -const interactiveThemePath = resolve(piPackageRoot, "dist", "modes", "interactive", "theme", "theme.js"); +const packageJsonPath = piPackageRoot ? resolve(piPackageRoot, "package.json") : null; +const cliPath = piPackageRoot ? resolve(piPackageRoot, "dist", "cli.js") : null; +const bunCliPath = piPackageRoot ? resolve(piPackageRoot, "dist", "bun", "cli.js") : null; +const interactiveModePath = piPackageRoot ? resolve(piPackageRoot, "dist", "modes", "interactive", "interactive-mode.js") : null; +const interactiveThemePath = piPackageRoot ? resolve(piPackageRoot, "dist", "modes", "interactive", "theme", "theme.js") : null; const editorPath = piTuiRoot ? resolve(piTuiRoot, "dist", "components", "editor.js") : null; const workspaceRoot = resolve(appRoot, ".feynman", "npm", "node_modules"); const webAccessPath = resolve(workspaceRoot, "pi-web-access", "index.ts"); @@ -219,7 +230,7 @@ function ensurePandoc() { ensurePandoc(); -if (existsSync(packageJsonPath)) { +if (packageJsonPath && existsSync(packageJsonPath)) { const pkg = JSON.parse(readFileSync(packageJsonPath, "utf8")); if (pkg.piConfig?.name !== "feynman" || pkg.piConfig?.configDir !== ".feynman") { pkg.piConfig = { @@ -231,7 +242,7 @@ if (existsSync(packageJsonPath)) { } } -for (const entryPath of [cliPath, bunCliPath]) { +for (const entryPath of [cliPath, bunCliPath].filter(Boolean)) { if (!existsSync(entryPath)) { continue; } @@ -242,7 +253,7 @@ for (const entryPath of [cliPath, bunCliPath]) { } } -if (existsSync(interactiveModePath)) { +if (interactiveModePath && existsSync(interactiveModePath)) { const interactiveModeSource = readFileSync(interactiveModePath, "utf8"); if (interactiveModeSource.includes("`π - ${sessionName} - ${cwdBasename}`")) { writeFileSync( @@ -255,7 +266,7 @@ if (existsSync(interactiveModePath)) { } } -if (existsSync(interactiveThemePath)) { +if (interactiveThemePath && existsSync(interactiveThemePath)) { let themeSource = readFileSync(interactiveThemePath, "utf8"); const desiredGetEditorTheme = [ "export function getEditorTheme() {", diff --git a/src/cli.ts b/src/cli.ts index 6e9b3fb..3f7cf99 100644 --- a/src/cli.ts +++ b/src/cli.ts @@ -293,6 +293,7 @@ export async function main(): Promise { cwd: { type: "string" }, doctor: { type: "boolean" }, help: { type: "boolean" }, + version: { type: "boolean" }, "alpha-login": { type: "boolean" }, "alpha-logout": { type: "boolean" }, "alpha-status": { type: "boolean" }, @@ -310,6 +311,14 @@ export async function main(): Promise { return; } + if (values.version) { + if (feynmanVersion) { + console.log(feynmanVersion); + return; + } + throw new Error("Unable to determine the installed Feynman version."); + } + const workingDir = resolve(values.cwd ?? process.cwd()); const sessionDir = resolve(values["session-dir"] ?? getDefaultSessionDir(feynmanHome)); const feynmanSettingsPath = resolve(feynmanAgentDir, "settings.json");