Fix postinstall patches for hoisted dependencies, add CI publish workflow

- Postinstall now walks up to find node_modules (works when deps are hoisted)
- All patches verified: piConfig, process.title, OAuth page, editor theme
- Add GitHub Actions workflow to publish on version bump

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Advait Paliwal
2026-03-23 19:05:24 -07:00
parent 6c9d629b5d
commit 58a515c168
4 changed files with 66 additions and 9 deletions

31
.github/workflows/publish.yml vendored Normal file
View File

@@ -0,0 +1,31 @@
name: Publish to npm
on:
push:
branches: [main]
jobs:
publish:
runs-on: ubuntu-latest
permissions:
contents: read
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 20
registry-url: https://registry.npmjs.org
- run: npm ci
- run: npm run build
- run: npm test
- name: Publish if version changed
run: |
CURRENT=$(npm view @companion-ai/feynman version 2>/dev/null || echo "0.0.0")
LOCAL=$(node -p "require('./package.json').version")
if [ "$CURRENT" != "$LOCAL" ]; then
npm publish --access public
else
echo "Version $LOCAL already published, skipping"
fi
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}

4
package-lock.json generated
View File

@@ -1,12 +1,12 @@
{ {
"name": "@companion-ai/feynman", "name": "@companion-ai/feynman",
"version": "0.2.3", "version": "0.2.4",
"lockfileVersion": 3, "lockfileVersion": 3,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "@companion-ai/feynman", "name": "@companion-ai/feynman",
"version": "0.2.3", "version": "0.2.4",
"hasInstallScript": true, "hasInstallScript": true,
"dependencies": { "dependencies": {
"@companion-ai/alpha-hub": "^0.1.2", "@companion-ai/alpha-hub": "^0.1.2",

View File

@@ -1,6 +1,6 @@
{ {
"name": "@companion-ai/feynman", "name": "@companion-ai/feynman",
"version": "0.2.3", "version": "0.2.4",
"description": "Research-first CLI agent built on Pi and alphaXiv", "description": "Research-first CLI agent built on Pi and alphaXiv",
"type": "module", "type": "module",
"bin": { "bin": {

View File

@@ -5,14 +5,40 @@ import { fileURLToPath } from "node:url";
const here = dirname(fileURLToPath(import.meta.url)); const here = dirname(fileURLToPath(import.meta.url));
const appRoot = resolve(here, ".."); const appRoot = resolve(here, "..");
const piPackageRoot = resolve(appRoot, "node_modules", "@mariozechner", "pi-coding-agent");
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;
return null;
}
const piPackageRoot = findPackageRoot("@mariozechner/pi-coding-agent");
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);
}
const packageJsonPath = resolve(piPackageRoot, "package.json"); const packageJsonPath = resolve(piPackageRoot, "package.json");
const cliPath = resolve(piPackageRoot, "dist", "cli.js"); const cliPath = resolve(piPackageRoot, "dist", "cli.js");
const bunCliPath = resolve(piPackageRoot, "dist", "bun", "cli.js"); const bunCliPath = resolve(piPackageRoot, "dist", "bun", "cli.js");
const interactiveModePath = resolve(piPackageRoot, "dist", "modes", "interactive", "interactive-mode.js"); const interactiveModePath = resolve(piPackageRoot, "dist", "modes", "interactive", "interactive-mode.js");
const interactiveThemePath = resolve(piPackageRoot, "dist", "modes", "interactive", "theme", "theme.js"); const interactiveThemePath = resolve(piPackageRoot, "dist", "modes", "interactive", "theme", "theme.js");
const piTuiRoot = resolve(appRoot, "node_modules", "@mariozechner", "pi-tui"); const editorPath = piTuiRoot ? resolve(piTuiRoot, "dist", "components", "editor.js") : null;
const editorPath = resolve(piTuiRoot, "dist", "components", "editor.js");
const workspaceRoot = resolve(appRoot, ".feynman", "npm", "node_modules"); const workspaceRoot = resolve(appRoot, ".feynman", "npm", "node_modules");
const webAccessPath = resolve(workspaceRoot, "pi-web-access", "index.ts"); const webAccessPath = resolve(workspaceRoot, "pi-web-access", "index.ts");
const sessionSearchIndexerPath = resolve( const sessionSearchIndexerPath = resolve(
@@ -85,7 +111,7 @@ if (existsSync(interactiveThemePath)) {
writeFileSync(interactiveThemePath, themeSource, "utf8"); writeFileSync(interactiveThemePath, themeSource, "utf8");
} }
if (existsSync(editorPath)) { if (editorPath && existsSync(editorPath)) {
let editorSource = readFileSync(editorPath, "utf8"); let editorSource = readFileSync(editorPath, "utf8");
const importOriginal = const importOriginal =
'import { getSegmenter, isPunctuationChar, isWhitespaceChar, truncateToWidth, visibleWidth } from "../utils.js";'; 'import { getSegmenter, isPunctuationChar, isWhitespaceChar, truncateToWidth, visibleWidth } from "../utils.js";';
@@ -250,9 +276,9 @@ if (existsSync(sessionSearchIndexerPath)) {
} }
} }
const oauthPagePath = resolve(appRoot, "node_modules", "@mariozechner", "pi-ai", "dist", "utils", "oauth", "oauth-page.js"); const oauthPagePath = piAiRoot ? resolve(piAiRoot, "dist", "utils", "oauth", "oauth-page.js") : null;
if (existsSync(oauthPagePath)) { if (oauthPagePath && existsSync(oauthPagePath)) {
let source = readFileSync(oauthPagePath, "utf8"); let source = readFileSync(oauthPagePath, "utf8");
const piLogo = 'const LOGO_SVG = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 800 800" aria-hidden="true"><path fill="#fff" fill-rule="evenodd" d="M165.29 165.29 H517.36 V400 H400 V517.36 H282.65 V634.72 H165.29 Z M282.65 282.65 V400 H400 V282.65 Z"/><path fill="#fff" d="M517.36 400 H634.72 V634.72 H517.36 Z"/></svg>`;'; const piLogo = 'const LOGO_SVG = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 800 800" aria-hidden="true"><path fill="#fff" fill-rule="evenodd" d="M165.29 165.29 H517.36 V400 H400 V517.36 H282.65 V634.72 H165.29 Z M282.65 282.65 V400 H400 V282.65 Z"/><path fill="#fff" d="M517.36 400 H634.72 V634.72 H517.36 Z"/></svg>`;';
if (source.includes(piLogo)) { if (source.includes(piLogo)) {