diff --git a/.gitignore b/.gitignore index e6b1ddd..de55996 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,6 @@ node_modules .env .feynman +dist outputs/* !outputs/.gitkeep diff --git a/README.md b/README.md index e8403fd..9d2ee55 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # Feynman -`feynman` is a research-first agent scaffold built on `@mariozechner/pi-coding-agent`. +`feynman` is a research-first CLI built on `@mariozechner/pi-coding-agent`. It keeps the useful parts of a coding agent: - file access @@ -19,7 +19,20 @@ But it biases the runtime toward research work: The primary paper backend is `@companion-ai/alpha-hub` and your alphaXiv account. The rest of the workflow is augmented through a curated `.pi/settings.json` package stack. -## Setup +## Install + +```bash +npm install -g @companion-ai/feynman +``` + +Then authenticate alphaXiv and start the CLI: + +```bash +feynman --alpha-login +feynman +``` + +For local development: ```bash cd /Users/advaitpaliwal/Companion/Code/feynman @@ -28,13 +41,7 @@ cp .env.example .env npm run start ``` -If you already use `pi`, Feynman will reuse the usual auth/config locations for model access. - -Before deep paper work, make sure alphaXiv auth is set up: - -```bash -npx @companion-ai/alpha-hub login -``` +Feynman uses Pi under the hood, but the user-facing entrypoint is `feynman`, not `pi`. ## Commands diff --git a/bin/feynman.js b/bin/feynman.js new file mode 100755 index 0000000..6cf1a59 --- /dev/null +++ b/bin/feynman.js @@ -0,0 +1,2 @@ +#!/usr/bin/env node +import "../dist/index.js"; diff --git a/package-lock.json b/package-lock.json index e2d8171..bd6f987 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,19 +1,22 @@ { - "name": "feynman", + "name": "@companion-ai/feynman", "version": "0.1.0", "lockfileVersion": 3, "requires": true, "packages": { "": { - "name": "feynman", + "name": "@companion-ai/feynman", "version": "0.1.0", "dependencies": { - "@companion-ai/alpha-hub": "file:../alpha-hub/cli", + "@companion-ai/alpha-hub": "^0.1.1", "@mariozechner/pi-ai": "^0.56.1", "@mariozechner/pi-coding-agent": "^0.56.1", "@sinclair/typebox": "^0.34.41", "dotenv": "^16.4.7" }, + "bin": { + "feynman": "bin/feynman.js" + }, "devDependencies": { "@types/node": "^24.3.0", "tsx": "^4.20.5", @@ -25,7 +28,7 @@ }, "../alpha-hub/cli": { "name": "@companion-ai/alpha-hub", - "version": "0.1.0", + "version": "0.1.1", "license": "MIT", "dependencies": { "@modelcontextprotocol/sdk": "^1.27.1", diff --git a/package.json b/package.json index 58f9e10..3e26d3c 100644 --- a/package.json +++ b/package.json @@ -1,12 +1,26 @@ { - "name": "feynman", + "name": "@companion-ai/feynman", "version": "0.1.0", - "private": true, - "description": "Research-first agent built on pi-coding-agent", + "description": "Research-first CLI agent built on Pi and alphaXiv", "type": "module", + "bin": { + "feynman": "./bin/feynman.js" + }, + "files": [ + "bin/", + "dist/", + ".pi/", + "extensions/", + "prompts/", + "skills/", + "README.md", + ".env.example" + ], "scripts": { + "build": "tsc -p tsconfig.build.json", "dev": "tsx src/index.ts", "start": "tsx src/index.ts", + "start:dist": "node ./bin/feynman.js", "typecheck": "tsc --noEmit" }, "keywords": [ @@ -27,7 +41,7 @@ ] }, "dependencies": { - "@companion-ai/alpha-hub": "^0.1.0", + "@companion-ai/alpha-hub": "^0.1.1", "@mariozechner/pi-ai": "^0.56.1", "@mariozechner/pi-coding-agent": "^0.56.1", "@sinclair/typebox": "^0.34.41", @@ -40,5 +54,13 @@ }, "engines": { "node": ">=20.6.0" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/getcompanion-ai/feynman.git" + }, + "homepage": "https://github.com/getcompanion-ai/feynman#readme", + "bugs": { + "url": "https://github.com/getcompanion-ai/feynman/issues" } } diff --git a/src/index.ts b/src/index.ts index 4a1abed..43fbce9 100644 --- a/src/index.ts +++ b/src/index.ts @@ -7,6 +7,12 @@ import { parseArgs } from "node:util"; import { fileURLToPath } from "node:url"; import readline from "node:readline/promises"; +import { + getUserName as getAlphaUserName, + isLoggedIn as isAlphaLoggedIn, + login as loginAlpha, + logout as logoutAlpha, +} from "@companion-ai/alpha-hub/lib"; import { AuthStorage, createAgentSession, @@ -24,6 +30,9 @@ type ThinkingLevel = "off" | "low" | "medium" | "high"; function printHelp(): void { console.log(`Feynman commands: /help Show this help + /alpha-login Sign in to alphaXiv + /alpha-logout Clear alphaXiv auth + /alpha-status Show alphaXiv auth status /new Start a fresh persisted session /exit Quit the REPL /lit-review Expand the literature review prompt template @@ -34,6 +43,9 @@ function printHelp(): void { CLI flags: --prompt "" Run one prompt and exit + --alpha-login Sign in to alphaXiv and exit + --alpha-logout Clear alphaXiv auth and exit + --alpha-status Show alphaXiv auth status and exit --model provider:model Force a specific model --thinking level off | low | medium | high --cwd /path/to/workdir Working directory for tools @@ -78,6 +90,9 @@ async function main(): Promise { options: { cwd: { type: "string" }, help: { type: "boolean" }, + "alpha-login": { type: "boolean" }, + "alpha-logout": { type: "boolean" }, + "alpha-status": { type: "boolean" }, model: { type: "string" }, "new-session": { type: "boolean" }, prompt: { type: "string" }, @@ -91,6 +106,35 @@ async function main(): Promise { return; } + if (values["alpha-login"]) { + const result = await loginAlpha(); + const name = + (result.userInfo && + typeof result.userInfo === "object" && + "name" in result.userInfo && + typeof result.userInfo.name === "string") + ? result.userInfo.name + : getAlphaUserName(); + console.log(name ? `alphaXiv login complete: ${name}` : "alphaXiv login complete"); + return; + } + + if (values["alpha-logout"]) { + logoutAlpha(); + console.log("alphaXiv auth cleared"); + return; + } + + if (values["alpha-status"]) { + if (isAlphaLoggedIn()) { + const name = getAlphaUserName(); + console.log(name ? `alphaXiv logged in as ${name}` : "alphaXiv logged in"); + } else { + console.log("alphaXiv not logged in"); + } + return; + } + const workingDir = resolve(values.cwd ?? process.cwd()); const sessionDir = resolve(values["session-dir"] ?? resolve(appRoot, ".feynman", "sessions")); mkdirSync(sessionDir, { recursive: true }); @@ -191,6 +235,35 @@ async function main(): Promise { continue; } + if (line === "/alpha-login") { + const result = await loginAlpha(); + const name = + (result.userInfo && + typeof result.userInfo === "object" && + "name" in result.userInfo && + typeof result.userInfo.name === "string") + ? result.userInfo.name + : getAlphaUserName(); + console.log(name ? `alphaXiv login complete: ${name}` : "alphaXiv login complete"); + continue; + } + + if (line === "/alpha-logout") { + logoutAlpha(); + console.log("alphaXiv auth cleared"); + continue; + } + + if (line === "/alpha-status") { + if (isAlphaLoggedIn()) { + const name = getAlphaUserName(); + console.log(name ? `alphaXiv logged in as ${name}` : "alphaXiv logged in"); + } else { + console.log("alphaXiv not logged in"); + } + continue; + } + if (line === "/new") { await session.newSession(); console.log("started a new session"); diff --git a/tsconfig.build.json b/tsconfig.build.json new file mode 100644 index 0000000..f4cf049 --- /dev/null +++ b/tsconfig.build.json @@ -0,0 +1,12 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "noEmit": false, + "outDir": "./dist", + "declaration": false, + "sourceMap": false + }, + "include": [ + "src/**/*.ts" + ] +}