Upgrade Feynman research runtime and setup
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -3,6 +3,7 @@ node_modules
|
|||||||
.feynman
|
.feynman
|
||||||
.pi/npm
|
.pi/npm
|
||||||
.pi/git
|
.pi/git
|
||||||
|
.pi/schedule-prompts.json
|
||||||
dist
|
dist
|
||||||
*.tgz
|
*.tgz
|
||||||
outputs/*
|
outputs/*
|
||||||
|
|||||||
@@ -4,8 +4,14 @@
|
|||||||
"npm:pi-docparser",
|
"npm:pi-docparser",
|
||||||
"npm:pi-web-access",
|
"npm:pi-web-access",
|
||||||
"npm:pi-markdown-preview",
|
"npm:pi-markdown-preview",
|
||||||
|
"npm:@walterra/pi-charts",
|
||||||
|
"npm:pi-generative-ui",
|
||||||
|
"npm:pi-mermaid",
|
||||||
"npm:@aliou/pi-processes",
|
"npm:@aliou/pi-processes",
|
||||||
"npm:pi-zotero"
|
"npm:pi-zotero",
|
||||||
|
"npm:@kaiserlich-dev/pi-session-search",
|
||||||
|
"npm:pi-schedule-prompt",
|
||||||
|
"npm:@samfp/pi-memory"
|
||||||
],
|
],
|
||||||
"quietStartup": true,
|
"quietStartup": true,
|
||||||
"collapseChangelog": true
|
"collapseChangelog": true
|
||||||
|
|||||||
38
README.md
38
README.md
@@ -9,12 +9,14 @@ It keeps the useful parts of a coding agent:
|
|||||||
- skills
|
- skills
|
||||||
- custom extensions
|
- custom extensions
|
||||||
|
|
||||||
But it biases the runtime toward research work:
|
But it biases the runtime toward general research work:
|
||||||
- literature review
|
- literature review
|
||||||
- paper lookup
|
- source discovery and paper lookup
|
||||||
- source comparison
|
- source comparison
|
||||||
- research memo writing
|
- research memo writing
|
||||||
- paper and report drafting
|
- paper and report drafting
|
||||||
|
- session recall and durable research memory
|
||||||
|
- recurring and deferred research jobs
|
||||||
- replication planning when relevant
|
- replication planning when relevant
|
||||||
|
|
||||||
The primary paper backend is `@companion-ai/alpha-hub` and your alphaXiv account.
|
The primary paper backend is `@companion-ai/alpha-hub` and your alphaXiv account.
|
||||||
@@ -29,7 +31,7 @@ npm install -g @companion-ai/feynman
|
|||||||
Then authenticate alphaXiv and start the CLI:
|
Then authenticate alphaXiv and start the CLI:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
feynman --alpha-login
|
feynman setup
|
||||||
feynman
|
feynman
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -43,7 +45,12 @@ npm run start
|
|||||||
```
|
```
|
||||||
|
|
||||||
Feynman uses Pi under the hood, but the user-facing entrypoint is `feynman`, not `pi`.
|
Feynman uses Pi under the hood, but the user-facing entrypoint is `feynman`, not `pi`.
|
||||||
When you run `feynman`, it launches the real Pi interactive TUI with Feynman's research extensions, skills, prompts, and package stack preloaded.
|
When you run `feynman`, it launches the real Pi interactive TUI with Feynman's research extensions, skills, prompts, package stack, memory snapshot, and branded defaults preloaded.
|
||||||
|
|
||||||
|
Most users should not need slash commands. The intended default is:
|
||||||
|
- ask naturally
|
||||||
|
- let Feynman route into the right workflow
|
||||||
|
- use slash commands only as explicit shortcuts or overrides
|
||||||
|
|
||||||
## Commands
|
## Commands
|
||||||
|
|
||||||
@@ -56,10 +63,19 @@ Inside the REPL:
|
|||||||
- `/replicate <paper or claim>` expands the replication prompt template
|
- `/replicate <paper or claim>` expands the replication prompt template
|
||||||
- `/reading-list <topic>` expands the reading-list prompt template
|
- `/reading-list <topic>` expands the reading-list prompt template
|
||||||
- `/research-memo <topic>` expands the general research memo prompt template
|
- `/research-memo <topic>` expands the general research memo prompt template
|
||||||
|
- `/deepresearch <topic>` expands the thorough source-heavy research prompt template
|
||||||
|
- `/autoresearch <idea>` expands the end-to-end idea-to-paper prompt template
|
||||||
- `/compare-sources <topic>` expands the source comparison prompt template
|
- `/compare-sources <topic>` expands the source comparison prompt template
|
||||||
- `/paper-code-audit <item>` expands the paper/code audit prompt template
|
- `/paper-code-audit <item>` expands the paper/code audit prompt template
|
||||||
- `/paper-draft <topic>` expands the paper-style writing prompt template
|
- `/paper-draft <topic>` expands the paper-style writing prompt template
|
||||||
- `/research-memo <topic>` expands the general research memo prompt template
|
|
||||||
|
Outside the REPL:
|
||||||
|
|
||||||
|
- `feynman setup` configures alpha login, web research, and preview deps
|
||||||
|
- `feynman --alpha-login` signs in to alphaXiv
|
||||||
|
- `feynman --alpha-status` checks alphaXiv auth
|
||||||
|
- `feynman --doctor` checks models, auth, preview dependencies, and branded settings
|
||||||
|
- `feynman --setup-preview` installs `pandoc` automatically on macOS/Homebrew systems when preview support is missing
|
||||||
|
|
||||||
## Custom Tools
|
## Custom Tools
|
||||||
|
|
||||||
@@ -71,6 +87,8 @@ The starter extension adds:
|
|||||||
- `alpha_annotate_paper` for persistent local notes
|
- `alpha_annotate_paper` for persistent local notes
|
||||||
- `alpha_list_annotations` for recall across sessions
|
- `alpha_list_annotations` for recall across sessions
|
||||||
- `alpha_read_code` for reading a paper repository
|
- `alpha_read_code` for reading a paper repository
|
||||||
|
- `session_search` for recovering prior Feynman work from stored transcripts
|
||||||
|
- `preview_file` for browser/PDF review of generated artifacts
|
||||||
|
|
||||||
Feynman uses `@companion-ai/alpha-hub` directly in-process rather than shelling out to the CLI.
|
Feynman uses `@companion-ai/alpha-hub` directly in-process rather than shelling out to the CLI.
|
||||||
|
|
||||||
@@ -82,10 +100,16 @@ Feynman loads a lean research stack from [.pi/settings.json](/Users/advaitpaliwa
|
|||||||
- `pi-docparser` for PDFs, Office docs, spreadsheets, and images
|
- `pi-docparser` for PDFs, Office docs, spreadsheets, and images
|
||||||
- `pi-web-access` for broader web, GitHub, PDF, and media access
|
- `pi-web-access` for broader web, GitHub, PDF, and media access
|
||||||
- `pi-markdown-preview` for polished Markdown and LaTeX-heavy research writeups
|
- `pi-markdown-preview` for polished Markdown and LaTeX-heavy research writeups
|
||||||
|
- `@walterra/pi-charts` for charts and quantitative visualizations
|
||||||
|
- `pi-generative-ui` for interactive HTML-style widgets
|
||||||
|
- `pi-mermaid` for diagrams in the TUI
|
||||||
- `@aliou/pi-processes` for long-running experiments and log tails
|
- `@aliou/pi-processes` for long-running experiments and log tails
|
||||||
- `pi-zotero` for citation-library workflows
|
- `pi-zotero` for citation-library workflows
|
||||||
|
- `@kaiserlich-dev/pi-session-search` for indexed session recall and summarize/resume UI
|
||||||
|
- `pi-schedule-prompt` for recurring and deferred research jobs
|
||||||
|
- `@samfp/pi-memory` for automatic preference/correction memory across sessions
|
||||||
|
|
||||||
The default expectation is source-grounded outputs with explicit `Sources` sections containing direct URLs.
|
The default expectation is source-grounded outputs with explicit `Sources` sections containing direct URLs and durable artifacts written to `outputs/`, `notes/`, `experiments/`, or `papers/`.
|
||||||
|
|
||||||
## Layout
|
## Layout
|
||||||
|
|
||||||
@@ -95,5 +119,5 @@ feynman/
|
|||||||
├── papers/ # Polished paper-style drafts and writeups
|
├── papers/ # Polished paper-style drafts and writeups
|
||||||
├── prompts/ # Slash-style prompt templates
|
├── prompts/ # Slash-style prompt templates
|
||||||
├── skills/ # Research workflows
|
├── skills/ # Research workflows
|
||||||
└── src/ # Minimal REPL wrapper around pi-coding-agent
|
└── src/ # Branded launcher around the embedded Pi TUI
|
||||||
```
|
```
|
||||||
|
|||||||
@@ -1,3 +1,9 @@
|
|||||||
|
import { execFile, spawn } from "node:child_process";
|
||||||
|
import { mkdir, mkdtemp, readFile, readdir, stat, writeFile } from "node:fs/promises";
|
||||||
|
import { homedir, tmpdir } from "node:os";
|
||||||
|
import { basename, dirname, extname, join, resolve as resolvePath } from "node:path";
|
||||||
|
import { pathToFileURL } from "node:url";
|
||||||
|
import { promisify } from "node:util";
|
||||||
import {
|
import {
|
||||||
annotatePaper,
|
annotatePaper,
|
||||||
askPaper,
|
askPaper,
|
||||||
@@ -8,14 +14,456 @@ import {
|
|||||||
readPaperCode,
|
readPaperCode,
|
||||||
searchPapers,
|
searchPapers,
|
||||||
} from "@companion-ai/alpha-hub/lib";
|
} from "@companion-ai/alpha-hub/lib";
|
||||||
import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
|
import type { ExtensionAPI, ExtensionContext } from "@mariozechner/pi-coding-agent";
|
||||||
import { Type } from "@sinclair/typebox";
|
import { Type } from "@sinclair/typebox";
|
||||||
|
|
||||||
|
const execFileAsync = promisify(execFile);
|
||||||
|
|
||||||
function formatToolText(result: unknown): string {
|
function formatToolText(result: unknown): string {
|
||||||
return typeof result === "string" ? result : JSON.stringify(result, null, 2);
|
return typeof result === "string" ? result : JSON.stringify(result, null, 2);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getFeynmanHome(): string {
|
||||||
|
const agentDir = process.env.FEYNMAN_CODING_AGENT_DIR ??
|
||||||
|
process.env.PI_CODING_AGENT_DIR ??
|
||||||
|
resolvePath(homedir(), ".feynman", "agent");
|
||||||
|
return dirname(agentDir);
|
||||||
|
}
|
||||||
|
|
||||||
|
function extractMessageText(message: unknown): string {
|
||||||
|
if (!message || typeof message !== "object") {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
const content = (message as { content?: unknown }).content;
|
||||||
|
if (typeof content === "string") {
|
||||||
|
return content;
|
||||||
|
}
|
||||||
|
if (!Array.isArray(content)) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
return content
|
||||||
|
.map((item) => {
|
||||||
|
if (!item || typeof item !== "object") {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
const record = item as { type?: string; text?: unknown; arguments?: unknown; name?: unknown };
|
||||||
|
if (record.type === "text" && typeof record.text === "string") {
|
||||||
|
return record.text;
|
||||||
|
}
|
||||||
|
if (record.type === "toolCall") {
|
||||||
|
const name = typeof record.name === "string" ? record.name : "tool";
|
||||||
|
const args =
|
||||||
|
typeof record.arguments === "string"
|
||||||
|
? record.arguments
|
||||||
|
: record.arguments
|
||||||
|
? JSON.stringify(record.arguments)
|
||||||
|
: "";
|
||||||
|
return `[tool:${name}] ${args}`;
|
||||||
|
}
|
||||||
|
return "";
|
||||||
|
})
|
||||||
|
.filter(Boolean)
|
||||||
|
.join("\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
function buildExcerpt(text: string, query: string, radius = 180): string {
|
||||||
|
const normalizedText = text.replace(/\s+/g, " ").trim();
|
||||||
|
if (!normalizedText) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
const lower = normalizedText.toLowerCase();
|
||||||
|
const q = query.toLowerCase();
|
||||||
|
const index = lower.indexOf(q);
|
||||||
|
if (index === -1) {
|
||||||
|
return normalizedText.slice(0, radius * 2) + (normalizedText.length > radius * 2 ? "..." : "");
|
||||||
|
}
|
||||||
|
|
||||||
|
const start = Math.max(0, index - radius);
|
||||||
|
const end = Math.min(normalizedText.length, index + q.length + radius);
|
||||||
|
const prefix = start > 0 ? "..." : "";
|
||||||
|
const suffix = end < normalizedText.length ? "..." : "";
|
||||||
|
return `${prefix}${normalizedText.slice(start, end)}${suffix}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function searchSessionTranscripts(query: string, limit: number): Promise<{
|
||||||
|
query: string;
|
||||||
|
results: Array<{
|
||||||
|
sessionId: string;
|
||||||
|
sessionFile: string;
|
||||||
|
startedAt?: string;
|
||||||
|
cwd?: string;
|
||||||
|
matchCount: number;
|
||||||
|
topMatches: Array<{ role: string; timestamp?: string; excerpt: string }>;
|
||||||
|
}>;
|
||||||
|
}> {
|
||||||
|
const packageRoot = process.env.FEYNMAN_PI_NPM_ROOT;
|
||||||
|
if (packageRoot) {
|
||||||
|
try {
|
||||||
|
const indexerPath = pathToFileURL(
|
||||||
|
join(packageRoot, "@kaiserlich-dev", "pi-session-search", "extensions", "indexer.ts"),
|
||||||
|
).href;
|
||||||
|
const indexer = await import(indexerPath) as {
|
||||||
|
updateIndex?: (onProgress?: (msg: string) => void) => Promise<number>;
|
||||||
|
search?: (query: string, limit?: number) => Array<{
|
||||||
|
sessionPath: string;
|
||||||
|
project: string;
|
||||||
|
timestamp: string;
|
||||||
|
snippet: string;
|
||||||
|
rank: number;
|
||||||
|
title: string | null;
|
||||||
|
}>;
|
||||||
|
getSessionSnippets?: (sessionPath: string, query: string, limit?: number) => string[];
|
||||||
|
};
|
||||||
|
|
||||||
|
await indexer.updateIndex?.();
|
||||||
|
const results = indexer.search?.(query, limit) ?? [];
|
||||||
|
if (results.length > 0) {
|
||||||
|
return {
|
||||||
|
query,
|
||||||
|
results: results.map((result) => ({
|
||||||
|
sessionId: basename(result.sessionPath),
|
||||||
|
sessionFile: result.sessionPath,
|
||||||
|
startedAt: result.timestamp,
|
||||||
|
cwd: result.project,
|
||||||
|
matchCount: 1,
|
||||||
|
topMatches: (indexer.getSessionSnippets?.(result.sessionPath, query, 4) ?? [result.snippet])
|
||||||
|
.filter(Boolean)
|
||||||
|
.map((excerpt) => ({
|
||||||
|
role: "match",
|
||||||
|
excerpt,
|
||||||
|
})),
|
||||||
|
})),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
// Fall back to direct JSONL scanning below.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const sessionDir = join(getFeynmanHome(), "sessions");
|
||||||
|
const terms = query
|
||||||
|
.toLowerCase()
|
||||||
|
.split(/\s+/)
|
||||||
|
.map((term) => term.trim())
|
||||||
|
.filter((term) => term.length >= 2);
|
||||||
|
const needle = query.toLowerCase();
|
||||||
|
|
||||||
|
let files: string[] = [];
|
||||||
|
try {
|
||||||
|
files = (await readdir(sessionDir))
|
||||||
|
.filter((entry) => entry.endsWith(".jsonl"))
|
||||||
|
.map((entry) => join(sessionDir, entry));
|
||||||
|
} catch {
|
||||||
|
return { query, results: [] };
|
||||||
|
}
|
||||||
|
|
||||||
|
const sessions = [];
|
||||||
|
for (const file of files) {
|
||||||
|
const raw = await readFile(file, "utf8").catch(() => "");
|
||||||
|
if (!raw) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
let sessionId = basename(file);
|
||||||
|
let startedAt: string | undefined;
|
||||||
|
let cwd: string | undefined;
|
||||||
|
const matches: Array<{ role: string; timestamp?: string; excerpt: string }> = [];
|
||||||
|
|
||||||
|
for (const line of raw.split("\n")) {
|
||||||
|
if (!line.trim()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
const record = JSON.parse(line) as {
|
||||||
|
type?: string;
|
||||||
|
id?: string;
|
||||||
|
timestamp?: string;
|
||||||
|
cwd?: string;
|
||||||
|
message?: { role?: string; content?: unknown };
|
||||||
|
};
|
||||||
|
if (record.type === "session") {
|
||||||
|
sessionId = record.id ?? sessionId;
|
||||||
|
startedAt = record.timestamp;
|
||||||
|
cwd = record.cwd;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (record.type !== "message" || !record.message) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const text = extractMessageText(record.message);
|
||||||
|
if (!text) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
const lower = text.toLowerCase();
|
||||||
|
const matched = lower.includes(needle) || terms.some((term) => lower.includes(term));
|
||||||
|
if (!matched) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
matches.push({
|
||||||
|
role: record.message.role ?? "unknown",
|
||||||
|
timestamp: record.timestamp,
|
||||||
|
excerpt: buildExcerpt(text, query),
|
||||||
|
});
|
||||||
|
} catch {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (matches.length === 0) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
let mtime = 0;
|
||||||
|
try {
|
||||||
|
mtime = (await stat(file)).mtimeMs;
|
||||||
|
} catch {
|
||||||
|
mtime = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
sessions.push({
|
||||||
|
sessionId,
|
||||||
|
sessionFile: file,
|
||||||
|
startedAt,
|
||||||
|
cwd,
|
||||||
|
matchCount: matches.length,
|
||||||
|
topMatches: matches.slice(0, 4),
|
||||||
|
mtime,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
sessions.sort((a, b) => {
|
||||||
|
if (b.matchCount !== a.matchCount) {
|
||||||
|
return b.matchCount - a.matchCount;
|
||||||
|
}
|
||||||
|
return b.mtime - a.mtime;
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
query,
|
||||||
|
results: sessions.slice(0, limit).map(({ mtime: _mtime, ...session }) => session),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function isMarkdownPath(path: string): boolean {
|
||||||
|
return [".md", ".markdown", ".txt"].includes(extname(path).toLowerCase());
|
||||||
|
}
|
||||||
|
|
||||||
|
function isLatexPath(path: string): boolean {
|
||||||
|
return extname(path).toLowerCase() === ".tex";
|
||||||
|
}
|
||||||
|
|
||||||
|
function wrapCodeAsMarkdown(source: string, filePath: string): string {
|
||||||
|
const language = extname(filePath).replace(/^\./, "") || "text";
|
||||||
|
return `# ${basename(filePath)}\n\n\`\`\`${language}\n${source}\n\`\`\`\n`;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function openWithDefaultApp(targetPath: string): Promise<void> {
|
||||||
|
const target = pathToFileURL(targetPath).href;
|
||||||
|
if (process.platform === "darwin") {
|
||||||
|
await execFileAsync("open", [target]);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (process.platform === "win32") {
|
||||||
|
await execFileAsync("cmd", ["/c", "start", "", target]);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
await execFileAsync("xdg-open", [target]);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function runCommandWithInput(
|
||||||
|
command: string,
|
||||||
|
args: string[],
|
||||||
|
input: string,
|
||||||
|
): Promise<{ stdout: string; stderr: string }> {
|
||||||
|
return await new Promise((resolve, reject) => {
|
||||||
|
const child = spawn(command, args, { stdio: ["pipe", "pipe", "pipe"] });
|
||||||
|
const stdoutChunks: Buffer[] = [];
|
||||||
|
const stderrChunks: Buffer[] = [];
|
||||||
|
|
||||||
|
child.stdout.on("data", (chunk: Buffer | string) => {
|
||||||
|
stdoutChunks.push(typeof chunk === "string" ? Buffer.from(chunk) : chunk);
|
||||||
|
});
|
||||||
|
child.stderr.on("data", (chunk: Buffer | string) => {
|
||||||
|
stderrChunks.push(typeof chunk === "string" ? Buffer.from(chunk) : chunk);
|
||||||
|
});
|
||||||
|
|
||||||
|
child.once("error", reject);
|
||||||
|
child.once("close", (code) => {
|
||||||
|
const stdout = Buffer.concat(stdoutChunks).toString("utf8");
|
||||||
|
const stderr = Buffer.concat(stderrChunks).toString("utf8");
|
||||||
|
if (code === 0) {
|
||||||
|
resolve({ stdout, stderr });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
reject(new Error(`${command} failed with exit code ${code}${stderr ? `: ${stderr.trim()}` : ""}`));
|
||||||
|
});
|
||||||
|
|
||||||
|
child.stdin.end(input);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async function renderHtmlPreview(filePath: string): Promise<string> {
|
||||||
|
const source = await readFile(filePath, "utf8");
|
||||||
|
const pandocCommand = process.env.PANDOC_PATH?.trim() || "pandoc";
|
||||||
|
const inputFormat = isLatexPath(filePath)
|
||||||
|
? "latex"
|
||||||
|
: "markdown+lists_without_preceding_blankline+tex_math_dollars+autolink_bare_uris-raw_html";
|
||||||
|
const markdown = isLatexPath(filePath) || isMarkdownPath(filePath) ? source : wrapCodeAsMarkdown(source, filePath);
|
||||||
|
const args = ["-f", inputFormat, "-t", "html5", "--mathml", "--wrap=none", `--resource-path=${dirname(filePath)}`];
|
||||||
|
const { stdout } = await runCommandWithInput(pandocCommand, args, markdown);
|
||||||
|
const html = `<!doctype html><html><head><meta charset="utf-8" /><base href="${pathToFileURL(dirname(filePath) + "/").href}" /><title>${basename(filePath)}</title><style>
|
||||||
|
:root{
|
||||||
|
--bg:#faf7f2;
|
||||||
|
--paper:#fffdf9;
|
||||||
|
--border:#d7cec1;
|
||||||
|
--text:#1f1c18;
|
||||||
|
--muted:#6c645a;
|
||||||
|
--code:#f3eee6;
|
||||||
|
--link:#0f6d8c;
|
||||||
|
--quote:#8b7f70;
|
||||||
|
}
|
||||||
|
@media (prefers-color-scheme: dark){
|
||||||
|
:root{
|
||||||
|
--bg:#161311;
|
||||||
|
--paper:#1d1916;
|
||||||
|
--border:#3b342d;
|
||||||
|
--text:#ebe3d6;
|
||||||
|
--muted:#b4ab9f;
|
||||||
|
--code:#221d19;
|
||||||
|
--link:#8ac6d6;
|
||||||
|
--quote:#a89d8f;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
body{
|
||||||
|
font-family:Charter,"Iowan Old Style","Palatino Linotype","Book Antiqua",Palatino,Georgia,serif;
|
||||||
|
margin:0;
|
||||||
|
background:var(--bg);
|
||||||
|
color:var(--text);
|
||||||
|
line-height:1.7;
|
||||||
|
}
|
||||||
|
main{
|
||||||
|
max-width:900px;
|
||||||
|
margin:2rem auto 4rem;
|
||||||
|
padding:2.5rem 3rem;
|
||||||
|
background:var(--paper);
|
||||||
|
border:1px solid var(--border);
|
||||||
|
border-radius:18px;
|
||||||
|
box-shadow:0 12px 40px rgba(0,0,0,.06);
|
||||||
|
}
|
||||||
|
h1,h2,h3,h4,h5,h6{
|
||||||
|
font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;
|
||||||
|
line-height:1.2;
|
||||||
|
margin-top:1.5em;
|
||||||
|
}
|
||||||
|
h1{font-size:2.2rem;border-bottom:1px solid var(--border);padding-bottom:.35rem;}
|
||||||
|
h2{font-size:1.6rem;border-bottom:1px solid var(--border);padding-bottom:.25rem;}
|
||||||
|
p,ul,ol,blockquote,table{margin:1rem 0;}
|
||||||
|
pre,code{font-family:ui-monospace,SFMono-Regular,Menlo,monospace}
|
||||||
|
pre{
|
||||||
|
background:var(--code);
|
||||||
|
border:1px solid var(--border);
|
||||||
|
border-radius:12px;
|
||||||
|
padding:1rem 1.1rem;
|
||||||
|
overflow:auto;
|
||||||
|
}
|
||||||
|
code{
|
||||||
|
background:var(--code);
|
||||||
|
padding:.12rem .28rem;
|
||||||
|
border-radius:6px;
|
||||||
|
}
|
||||||
|
a{color:var(--link);text-decoration:none}
|
||||||
|
a:hover{text-decoration:underline}
|
||||||
|
img{max-width:100%}
|
||||||
|
blockquote{
|
||||||
|
border-left:4px solid var(--border);
|
||||||
|
padding-left:1rem;
|
||||||
|
color:var(--quote);
|
||||||
|
}
|
||||||
|
table{border-collapse:collapse;width:100%}
|
||||||
|
th,td{border:1px solid var(--border);padding:.55rem .7rem;text-align:left}
|
||||||
|
</style></head><body><main>${stdout}</main></body></html>`;
|
||||||
|
const tempDir = await mkdtemp(join(tmpdir(), "feynman-preview-"));
|
||||||
|
const htmlPath = join(tempDir, `${basename(filePath)}.html`);
|
||||||
|
await writeFile(htmlPath, html, "utf8");
|
||||||
|
return htmlPath;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function renderPdfPreview(filePath: string): Promise<string> {
|
||||||
|
const source = await readFile(filePath, "utf8");
|
||||||
|
const pandocCommand = process.env.PANDOC_PATH?.trim() || "pandoc";
|
||||||
|
const pdfEngine = process.env.PANDOC_PDF_ENGINE?.trim() || "xelatex";
|
||||||
|
const inputFormat = isLatexPath(filePath)
|
||||||
|
? "latex"
|
||||||
|
: "markdown+lists_without_preceding_blankline+tex_math_dollars+autolink_bare_uris-raw_html";
|
||||||
|
const markdown = isLatexPath(filePath) || isMarkdownPath(filePath) ? source : wrapCodeAsMarkdown(source, filePath);
|
||||||
|
const tempDir = await mkdtemp(join(tmpdir(), "feynman-preview-"));
|
||||||
|
const pdfPath = join(tempDir, `${basename(filePath)}.pdf`);
|
||||||
|
const args = [
|
||||||
|
"-f",
|
||||||
|
inputFormat,
|
||||||
|
"-o",
|
||||||
|
pdfPath,
|
||||||
|
`--pdf-engine=${pdfEngine}`,
|
||||||
|
`--resource-path=${dirname(filePath)}`,
|
||||||
|
];
|
||||||
|
await runCommandWithInput(pandocCommand, args, markdown);
|
||||||
|
return pdfPath;
|
||||||
|
}
|
||||||
|
|
||||||
export default function researchTools(pi: ExtensionAPI): void {
|
export default function researchTools(pi: ExtensionAPI): void {
|
||||||
|
function installFeynmanHeader(ctx: ExtensionContext): void {
|
||||||
|
if (!ctx.hasUI) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.ui.setHeader((_tui, theme) => ({
|
||||||
|
render(_width: number): string[] {
|
||||||
|
return [
|
||||||
|
"",
|
||||||
|
`${theme.fg("accent", theme.bold("Feynman"))}${theme.fg("muted", " research agent")}`,
|
||||||
|
theme.fg("dim", "sources first • memory on • scheduled research ready"),
|
||||||
|
"",
|
||||||
|
];
|
||||||
|
},
|
||||||
|
invalidate() {},
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
pi.on("session_start", async (_event, ctx) => {
|
||||||
|
installFeynmanHeader(ctx);
|
||||||
|
});
|
||||||
|
|
||||||
|
pi.on("session_switch", async (_event, ctx) => {
|
||||||
|
installFeynmanHeader(ctx);
|
||||||
|
});
|
||||||
|
|
||||||
|
pi.registerTool({
|
||||||
|
name: "session_search",
|
||||||
|
label: "Session Search",
|
||||||
|
description: "Search prior Feynman session transcripts to recover what was done, said, or written before.",
|
||||||
|
parameters: Type.Object({
|
||||||
|
query: Type.String({
|
||||||
|
description: "Search query to look for in past sessions.",
|
||||||
|
}),
|
||||||
|
limit: Type.Optional(
|
||||||
|
Type.Number({
|
||||||
|
description: "Maximum number of sessions to return. Defaults to 3.",
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
}),
|
||||||
|
async execute(_toolCallId, params) {
|
||||||
|
const result = await searchSessionTranscripts(params.query, Math.max(1, Math.min(params.limit ?? 3, 8)));
|
||||||
|
return {
|
||||||
|
content: [{ type: "text", text: formatToolText(result) }],
|
||||||
|
details: result,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
pi.registerTool({
|
pi.registerTool({
|
||||||
name: "alpha_search",
|
name: "alpha_search",
|
||||||
label: "Alpha Search",
|
label: "Alpha Search",
|
||||||
@@ -168,4 +616,47 @@ export default function researchTools(pi: ExtensionAPI): void {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
pi.registerTool({
|
||||||
|
name: "preview_file",
|
||||||
|
label: "Preview File",
|
||||||
|
description: "Open a markdown, LaTeX, PDF, or code artifact in the browser or a PDF viewer for human review.",
|
||||||
|
parameters: Type.Object({
|
||||||
|
path: Type.String({
|
||||||
|
description: "Path to the file to preview.",
|
||||||
|
}),
|
||||||
|
target: Type.Optional(
|
||||||
|
Type.String({
|
||||||
|
description: "Preview target: browser or pdf. Defaults to browser.",
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
}),
|
||||||
|
async execute(_toolCallId, params, _signal, _onUpdate, ctx) {
|
||||||
|
const target = (params.target?.trim().toLowerCase() || "browser");
|
||||||
|
if (target !== "browser" && target !== "pdf") {
|
||||||
|
throw new Error("target must be browser or pdf");
|
||||||
|
}
|
||||||
|
|
||||||
|
const resolvedPath = resolvePath(ctx.cwd, params.path);
|
||||||
|
const openedPath =
|
||||||
|
extname(resolvedPath).toLowerCase() === ".pdf" && target === "pdf"
|
||||||
|
? resolvedPath
|
||||||
|
: target === "pdf"
|
||||||
|
? await renderPdfPreview(resolvedPath)
|
||||||
|
: await renderHtmlPreview(resolvedPath);
|
||||||
|
|
||||||
|
await mkdir(dirname(openedPath), { recursive: true }).catch(() => {});
|
||||||
|
await openWithDefaultApp(openedPath);
|
||||||
|
|
||||||
|
const result = {
|
||||||
|
sourcePath: resolvedPath,
|
||||||
|
target,
|
||||||
|
openedPath,
|
||||||
|
};
|
||||||
|
return {
|
||||||
|
content: [{ type: "text", text: formatToolText(result) }],
|
||||||
|
details: result,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
4
package-lock.json
generated
4
package-lock.json
generated
@@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "@companion-ai/feynman",
|
"name": "@companion-ai/feynman",
|
||||||
"version": "0.1.1",
|
"version": "0.1.5",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "@companion-ai/feynman",
|
"name": "@companion-ai/feynman",
|
||||||
"version": "0.1.1",
|
"version": "0.1.5",
|
||||||
"hasInstallScript": true,
|
"hasInstallScript": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@companion-ai/alpha-hub": "^0.1.2",
|
"@companion-ai/alpha-hub": "^0.1.2",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@companion-ai/feynman",
|
"name": "@companion-ai/feynman",
|
||||||
"version": "0.1.1",
|
"version": "0.1.5",
|
||||||
"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": {
|
||||||
@@ -9,9 +9,11 @@
|
|||||||
"files": [
|
"files": [
|
||||||
"bin/",
|
"bin/",
|
||||||
"dist/",
|
"dist/",
|
||||||
".pi/",
|
".pi/settings.json",
|
||||||
|
".pi/themes/",
|
||||||
"extensions/",
|
"extensions/",
|
||||||
"prompts/",
|
"prompts/",
|
||||||
|
"scripts/",
|
||||||
"skills/",
|
"skills/",
|
||||||
"README.md",
|
"README.md",
|
||||||
".env.example"
|
".env.example"
|
||||||
|
|||||||
16
prompts/autoresearch.md
Normal file
16
prompts/autoresearch.md
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
---
|
||||||
|
description: Turn a research idea into a paper-oriented end-to-end run with literature, hypotheses, experiments when possible, and a draft artifact.
|
||||||
|
---
|
||||||
|
Run an autoresearch workflow for: $@
|
||||||
|
|
||||||
|
Requirements:
|
||||||
|
- Start by clarifying the research objective, scope, and target contribution.
|
||||||
|
- Search for the strongest relevant primary sources first.
|
||||||
|
- If the topic is current, product-oriented, market-facing, or asks about latest developments, start with `web_search` and `fetch_content`.
|
||||||
|
- Use `alpha_search` for academic background or paper-centric parts of the topic, but do not rely on it alone for current topics.
|
||||||
|
- Build a compact evidence table before committing to a paper narrative.
|
||||||
|
- If experiments are feasible in the current environment, design and run the smallest experiment that materially reduces uncertainty.
|
||||||
|
- If experiments are not feasible, produce a paper-style draft that is explicit about missing validation and limitations.
|
||||||
|
- Save intermediate planning or synthesis artifacts to `notes/` or `outputs/`.
|
||||||
|
- Save the final paper-style draft to `papers/`.
|
||||||
|
- End with a `Sources` section containing direct URLs for every source used.
|
||||||
@@ -5,6 +5,8 @@ Compare sources for: $@
|
|||||||
|
|
||||||
Requirements:
|
Requirements:
|
||||||
- Identify the strongest relevant primary sources first.
|
- Identify the strongest relevant primary sources first.
|
||||||
|
- For current or market-facing topics, use `web_search` and `fetch_content` to gather up-to-date primary sources before comparing them.
|
||||||
|
- For academic claims, use `alpha_search` and inspect the strongest papers directly.
|
||||||
- Inspect the top sources directly before comparing them.
|
- Inspect the top sources directly before comparing them.
|
||||||
- Build a comparison matrix covering:
|
- Build a comparison matrix covering:
|
||||||
- source
|
- source
|
||||||
|
|||||||
13
prompts/deepresearch.md
Normal file
13
prompts/deepresearch.md
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
---
|
||||||
|
description: Run a thorough, source-heavy investigation on a topic and produce a durable research brief with explicit evidence and source links.
|
||||||
|
---
|
||||||
|
Run a deep research workflow for: $@
|
||||||
|
|
||||||
|
Requirements:
|
||||||
|
- If the topic is current, product-oriented, market-facing, regulatory, or asks about latest developments, start with `web_search` and `fetch_content`.
|
||||||
|
- If the topic has an academic literature component, use `alpha_search`, `alpha_get_paper`, and `alpha_ask_paper` for the strongest papers.
|
||||||
|
- Do not rely on a single source type when the topic spans both current reality and academic background.
|
||||||
|
- Build a compact evidence table before synthesizing conclusions.
|
||||||
|
- Distinguish clearly between established facts, plausible inferences, disagreements, and unresolved questions.
|
||||||
|
- Produce a durable markdown artifact in `outputs/`.
|
||||||
|
- End with a `Sources` section containing direct URLs for every source used.
|
||||||
@@ -4,7 +4,8 @@ description: Run a literature review on a topic using paper search and primary-s
|
|||||||
Investigate the following topic as a literature review: $@
|
Investigate the following topic as a literature review: $@
|
||||||
|
|
||||||
Requirements:
|
Requirements:
|
||||||
- Use `alpha_search` first.
|
- If the topic is academic or paper-centric, use `alpha_search` first.
|
||||||
|
- If the topic is current, product-oriented, market-facing, or asks about latest developments, use `web_search` and `fetch_content` first, then use `alpha_search` only for academic background.
|
||||||
- Use `alpha_get_paper` on the most relevant papers before making strong claims.
|
- Use `alpha_get_paper` on the most relevant papers before making strong claims.
|
||||||
- Use `alpha_ask_paper` for targeted follow-up questions when the report is not enough.
|
- Use `alpha_ask_paper` for targeted follow-up questions when the report is not enough.
|
||||||
- Prefer primary sources and note when something appears to be a preprint or secondary summary.
|
- Prefer primary sources and note when something appears to be a preprint or secondary summary.
|
||||||
|
|||||||
@@ -4,8 +4,9 @@ description: Build a prioritized reading list on a research topic with rationale
|
|||||||
Create a research reading list for: $@
|
Create a research reading list for: $@
|
||||||
|
|
||||||
Requirements:
|
Requirements:
|
||||||
- Use `alpha_search` with `all` mode.
|
- If the topic is academic, use `alpha_search` with `all` mode.
|
||||||
- Inspect the strongest papers with `alpha_get_paper`.
|
- If the topic is current, product-oriented, or asks for the latest landscape, use `web_search` and `fetch_content` first, then add `alpha_search` for academic background when relevant.
|
||||||
|
- Inspect the strongest papers or primary sources directly before recommending them.
|
||||||
- Use `alpha_ask_paper` when a paper's fit is unclear.
|
- Use `alpha_ask_paper` when a paper's fit is unclear.
|
||||||
- Group papers by role when useful: foundational, strongest recent work, methods, benchmarks, critiques, replication targets.
|
- Group papers by role when useful: foundational, strongest recent work, methods, benchmarks, critiques, replication targets.
|
||||||
- For each paper, explain why it is on the list.
|
- For each paper, explain why it is on the list.
|
||||||
|
|||||||
@@ -5,6 +5,8 @@ Write a research memo about: $@
|
|||||||
|
|
||||||
Requirements:
|
Requirements:
|
||||||
- Start by finding the strongest relevant sources.
|
- Start by finding the strongest relevant sources.
|
||||||
|
- If the topic is current, market-facing, product-oriented, regulatory, or asks about latest developments, use `web_search` and `fetch_content` first.
|
||||||
|
- Use `alpha_search` for academic background where relevant, but do not rely on it alone for current topics.
|
||||||
- Read or inspect the top sources directly before making strong claims.
|
- Read or inspect the top sources directly before making strong claims.
|
||||||
- Distinguish facts, interpretations, and open questions.
|
- Distinguish facts, interpretations, and open questions.
|
||||||
- End with a `Sources` section containing direct URLs for every source used.
|
- End with a `Sources` section containing direct URLs for every source used.
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { existsSync, readFileSync, writeFileSync } from "node:fs";
|
import { spawnSync } from "node:child_process";
|
||||||
|
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
||||||
import { dirname, resolve } from "node:path";
|
import { dirname, resolve } from "node:path";
|
||||||
import { fileURLToPath } from "node:url";
|
import { fileURLToPath } from "node:url";
|
||||||
|
|
||||||
@@ -7,6 +8,68 @@ const appRoot = resolve(here, "..");
|
|||||||
const piPackageRoot = resolve(appRoot, "node_modules", "@mariozechner", "pi-coding-agent");
|
const piPackageRoot = resolve(appRoot, "node_modules", "@mariozechner", "pi-coding-agent");
|
||||||
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 interactiveModePath = resolve(piPackageRoot, "dist", "modes", "interactive", "interactive-mode.js");
|
||||||
|
const workspaceRoot = resolve(appRoot, ".pi", "npm", "node_modules");
|
||||||
|
const webAccessPath = resolve(workspaceRoot, "pi-web-access", "index.ts");
|
||||||
|
const sessionSearchIndexerPath = resolve(
|
||||||
|
workspaceRoot,
|
||||||
|
"@kaiserlich-dev",
|
||||||
|
"pi-session-search",
|
||||||
|
"extensions",
|
||||||
|
"indexer.ts",
|
||||||
|
);
|
||||||
|
const piMemoryPath = resolve(workspaceRoot, "@samfp", "pi-memory", "src", "index.ts");
|
||||||
|
const settingsPath = resolve(appRoot, ".pi", "settings.json");
|
||||||
|
const workspaceDir = resolve(appRoot, ".pi", "npm");
|
||||||
|
const workspacePackageJsonPath = resolve(workspaceDir, "package.json");
|
||||||
|
|
||||||
|
function ensurePackageWorkspace() {
|
||||||
|
if (!existsSync(settingsPath)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const settings = JSON.parse(readFileSync(settingsPath, "utf8"));
|
||||||
|
const packageSpecs = Array.isArray(settings.packages)
|
||||||
|
? settings.packages
|
||||||
|
.filter((value) => typeof value === "string" && value.startsWith("npm:"))
|
||||||
|
.map((value) => value.slice(4))
|
||||||
|
: [];
|
||||||
|
|
||||||
|
if (packageSpecs.length === 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
mkdirSync(workspaceDir, { recursive: true });
|
||||||
|
|
||||||
|
writeFileSync(
|
||||||
|
workspacePackageJsonPath,
|
||||||
|
JSON.stringify(
|
||||||
|
{
|
||||||
|
name: "pi-extensions",
|
||||||
|
private: true,
|
||||||
|
dependencies: Object.fromEntries(packageSpecs.map((spec) => [spec, "latest"])),
|
||||||
|
},
|
||||||
|
null,
|
||||||
|
2,
|
||||||
|
) + "\n",
|
||||||
|
"utf8",
|
||||||
|
);
|
||||||
|
|
||||||
|
const npmExec = process.env.npm_execpath;
|
||||||
|
const install = npmExec
|
||||||
|
? spawnSync(process.execPath, [npmExec, "install", "--prefix", workspaceDir, ...packageSpecs], {
|
||||||
|
stdio: "inherit",
|
||||||
|
})
|
||||||
|
: spawnSync("npm", ["install", "--prefix", workspaceDir, ...packageSpecs], {
|
||||||
|
stdio: "inherit",
|
||||||
|
});
|
||||||
|
|
||||||
|
if (install.status !== 0) {
|
||||||
|
console.warn("[feynman] warning: failed to preinstall default Pi packages into .pi/npm");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ensurePackageWorkspace();
|
||||||
|
|
||||||
if (existsSync(packageJsonPath)) {
|
if (existsSync(packageJsonPath)) {
|
||||||
const pkg = JSON.parse(readFileSync(packageJsonPath, "utf8"));
|
const pkg = JSON.parse(readFileSync(packageJsonPath, "utf8"));
|
||||||
@@ -25,3 +88,59 @@ if (existsSync(cliPath)) {
|
|||||||
writeFileSync(cliPath, cliSource.replace('process.title = "pi";', 'process.title = "feynman";'), "utf8");
|
writeFileSync(cliPath, cliSource.replace('process.title = "pi";', 'process.title = "feynman";'), "utf8");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (existsSync(interactiveModePath)) {
|
||||||
|
const interactiveModeSource = readFileSync(interactiveModePath, "utf8");
|
||||||
|
if (interactiveModeSource.includes("`π - ${sessionName} - ${cwdBasename}`")) {
|
||||||
|
writeFileSync(
|
||||||
|
interactiveModePath,
|
||||||
|
interactiveModeSource
|
||||||
|
.replace("`π - ${sessionName} - ${cwdBasename}`", "`feynman - ${sessionName} - ${cwdBasename}`")
|
||||||
|
.replace("`π - ${cwdBasename}`", "`feynman - ${cwdBasename}`"),
|
||||||
|
"utf8",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (existsSync(webAccessPath)) {
|
||||||
|
const source = readFileSync(webAccessPath, "utf8");
|
||||||
|
if (source.includes('pi.registerCommand("search",')) {
|
||||||
|
writeFileSync(
|
||||||
|
webAccessPath,
|
||||||
|
source.replace('pi.registerCommand("search",', 'pi.registerCommand("web-results",'),
|
||||||
|
"utf8",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (existsSync(sessionSearchIndexerPath)) {
|
||||||
|
const source = readFileSync(sessionSearchIndexerPath, "utf8");
|
||||||
|
const original = 'const sessionsDir = path.join(os.homedir(), ".pi", "agent", "sessions");';
|
||||||
|
const replacement =
|
||||||
|
'const sessionsDir = process.env.FEYNMAN_SESSION_DIR ?? process.env.PI_SESSION_DIR ?? path.join(os.homedir(), ".pi", "agent", "sessions");';
|
||||||
|
if (source.includes(original)) {
|
||||||
|
writeFileSync(sessionSearchIndexerPath, source.replace(original, replacement), "utf8");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (existsSync(piMemoryPath)) {
|
||||||
|
let source = readFileSync(piMemoryPath, "utf8");
|
||||||
|
const memoryOriginal = 'const MEMORY_DIR = join(homedir(), ".pi", "memory");';
|
||||||
|
const memoryReplacement =
|
||||||
|
'const MEMORY_DIR = process.env.FEYNMAN_MEMORY_DIR ?? process.env.PI_MEMORY_DIR ?? join(homedir(), ".pi", "memory");';
|
||||||
|
if (source.includes(memoryOriginal)) {
|
||||||
|
source = source.replace(memoryOriginal, memoryReplacement);
|
||||||
|
}
|
||||||
|
const execOriginal = 'const result = await pi.exec("pi", ["-p", prompt, "--print"], {';
|
||||||
|
const execReplacement = [
|
||||||
|
'const execBinary = process.env.FEYNMAN_NODE_EXECUTABLE || process.env.FEYNMAN_EXECUTABLE || "pi";',
|
||||||
|
' const execArgs = process.env.FEYNMAN_BIN_PATH',
|
||||||
|
' ? [process.env.FEYNMAN_BIN_PATH, "--prompt", prompt]',
|
||||||
|
' : ["-p", prompt, "--print"];',
|
||||||
|
' const result = await pi.exec(execBinary, execArgs, {',
|
||||||
|
].join("\n");
|
||||||
|
if (source.includes(execOriginal)) {
|
||||||
|
source = source.replace(execOriginal, execReplacement);
|
||||||
|
}
|
||||||
|
writeFileSync(piMemoryPath, source, "utf8");
|
||||||
|
}
|
||||||
|
|||||||
56
skills/research/autoresearch/SKILL.md
Normal file
56
skills/research/autoresearch/SKILL.md
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
---
|
||||||
|
name: autoresearch
|
||||||
|
description: Use this when the user wants an end-to-end idea-to-paper run, from problem framing through literature, experiments if feasible, and a paper-style draft.
|
||||||
|
---
|
||||||
|
|
||||||
|
# AutoResearch
|
||||||
|
|
||||||
|
## When To Use
|
||||||
|
|
||||||
|
Use this skill when the user wants:
|
||||||
|
- an idea turned into a paper-style draft
|
||||||
|
- a full research workflow, not just a memo or reading list
|
||||||
|
- autonomous progress from topic framing to deliverable
|
||||||
|
|
||||||
|
## Procedure
|
||||||
|
|
||||||
|
1. Restate the idea as a concrete research question and identify the likely contribution type:
|
||||||
|
- empirical result
|
||||||
|
- synthesis or review
|
||||||
|
- method proposal
|
||||||
|
- benchmark or audit
|
||||||
|
2. Search for relevant primary sources first.
|
||||||
|
3. If the topic is current, product-oriented, market-facing, or asks about latest developments, start with `web_search` and `fetch_content`.
|
||||||
|
4. Use `alpha_search`, `alpha_get_paper`, and `alpha_ask_paper` for academic background or paper-centric parts of the topic.
|
||||||
|
5. Build a compact evidence table in `notes/` or `outputs/` before deciding on the paper narrative.
|
||||||
|
6. Decide whether experiments are feasible in the current environment:
|
||||||
|
- if yes, design and run the smallest experiment that materially reduces uncertainty
|
||||||
|
- if no, continue with a literature-grounded or theory-grounded draft and state the limitation clearly
|
||||||
|
7. Produce at least two artifacts:
|
||||||
|
- an intermediate artifact (research memo, evidence table, or experiment log)
|
||||||
|
- a final paper-style draft in `papers/`
|
||||||
|
8. Structure the final draft with:
|
||||||
|
- title
|
||||||
|
- abstract
|
||||||
|
- introduction
|
||||||
|
- related work
|
||||||
|
- method or synthesis
|
||||||
|
- evidence or experiments
|
||||||
|
- limitations
|
||||||
|
- conclusion
|
||||||
|
9. End with a `Sources` section containing direct URLs for every source used.
|
||||||
|
|
||||||
|
## Pitfalls
|
||||||
|
|
||||||
|
- Do not jump straight to drafting before checking the literature.
|
||||||
|
- Do not treat a current topic as if papers alone are enough.
|
||||||
|
- Do not fake experiments when the environment cannot support them.
|
||||||
|
- Do not present speculative contributions as established results.
|
||||||
|
- Do not omit limitations or missing validation.
|
||||||
|
|
||||||
|
## Deliverable
|
||||||
|
|
||||||
|
A complete idea-to-paper run should leave behind:
|
||||||
|
- one intermediate artifact in `notes/` or `outputs/`
|
||||||
|
- one final paper-style draft in `papers/`
|
||||||
|
- a source list with direct URLs
|
||||||
39
skills/research/context-recall/SKILL.md
Normal file
39
skills/research/context-recall/SKILL.md
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
---
|
||||||
|
name: context-recall
|
||||||
|
description: Use this when the user asks what was done before, refers to earlier sessions, wants prior artifacts, or expects Feynman to remember past work.
|
||||||
|
---
|
||||||
|
|
||||||
|
# Context Recall
|
||||||
|
|
||||||
|
## When To Use
|
||||||
|
|
||||||
|
Use this skill when the user:
|
||||||
|
- asks what was done previously
|
||||||
|
- refers to an earlier paper, memo, or artifact
|
||||||
|
- expects cross-session continuity
|
||||||
|
- asks what has already been tried or written
|
||||||
|
|
||||||
|
## Procedure
|
||||||
|
|
||||||
|
1. Read durable memory first with `memory_search` or `memory_lessons`.
|
||||||
|
2. Search prior sessions with `session_search`.
|
||||||
|
3. If needed, inspect the current workspace for artifacts in `outputs/`, `notes/`, `experiments/`, and `papers/`.
|
||||||
|
4. Distinguish clearly between:
|
||||||
|
- durable remembered facts
|
||||||
|
- session transcript recall
|
||||||
|
- currently present files on disk
|
||||||
|
5. If you find a stable correction or preference that should persist, save it with `memory_remember`.
|
||||||
|
|
||||||
|
## Pitfalls
|
||||||
|
|
||||||
|
- Do not claim to remember something without checking memory or session history.
|
||||||
|
- Do not confuse durable memory with transient task progress.
|
||||||
|
- Do not summarize prior work from vague impressions; recover evidence first.
|
||||||
|
|
||||||
|
## Deliverable
|
||||||
|
|
||||||
|
Include:
|
||||||
|
- what was previously done
|
||||||
|
- where the evidence came from
|
||||||
|
- which artifacts or files exist now
|
||||||
|
- any gaps or uncertainty
|
||||||
54
skills/research/deep-research/SKILL.md
Normal file
54
skills/research/deep-research/SKILL.md
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
---
|
||||||
|
name: deep-research
|
||||||
|
description: Use this when the user wants a broad, thorough investigation with strong sourcing, explicit evidence tables, and a durable research brief.
|
||||||
|
---
|
||||||
|
|
||||||
|
# Deep Research
|
||||||
|
|
||||||
|
## When To Use
|
||||||
|
|
||||||
|
Use this skill when the user wants:
|
||||||
|
- a thorough investigation rather than a quick memo
|
||||||
|
- a broad landscape analysis
|
||||||
|
- careful source comparison across multiple source types
|
||||||
|
- a durable research brief with explicit evidence
|
||||||
|
|
||||||
|
## Procedure
|
||||||
|
|
||||||
|
1. Clarify the exact scope and what decision or question the research should support.
|
||||||
|
2. Choose the right retrieval mix:
|
||||||
|
- use `web_search` and `fetch_content` first for current, product, market, regulatory, or latest topics
|
||||||
|
- use `alpha_search`, `alpha_get_paper`, and `alpha_ask_paper` for academic background or paper-centric claims
|
||||||
|
- use both when the topic spans current reality and academic literature
|
||||||
|
3. Gather enough high-quality sources before synthesizing.
|
||||||
|
4. Build an evidence table covering:
|
||||||
|
- source
|
||||||
|
- claim
|
||||||
|
- evidence type
|
||||||
|
- caveats
|
||||||
|
- relevance
|
||||||
|
5. Synthesize:
|
||||||
|
- strongest findings
|
||||||
|
- disagreements
|
||||||
|
- open questions
|
||||||
|
- what would change the conclusion
|
||||||
|
6. Save a durable markdown brief to `outputs/`.
|
||||||
|
7. End with a `Sources` section containing direct URLs for every source used.
|
||||||
|
|
||||||
|
## Pitfalls
|
||||||
|
|
||||||
|
- Do not answer a current topic from papers alone.
|
||||||
|
- Do not answer an academic topic from search snippets alone.
|
||||||
|
- Do not collapse disagreement into fake consensus.
|
||||||
|
- Do not omit the evidence table on broad or high-stakes topics.
|
||||||
|
|
||||||
|
## Deliverable
|
||||||
|
|
||||||
|
Include:
|
||||||
|
- scope
|
||||||
|
- evidence table
|
||||||
|
- key findings
|
||||||
|
- disagreements or caveats
|
||||||
|
- open questions
|
||||||
|
- recommendation or next step
|
||||||
|
- sources
|
||||||
@@ -22,12 +22,13 @@ Use this skill when the user has:
|
|||||||
- success metrics
|
- success metrics
|
||||||
- baselines
|
- baselines
|
||||||
- constraints
|
- constraints
|
||||||
3. Search for prior work first with `alpha_search` so you do not reinvent an obviously flawed setup.
|
3. Search for prior work first.
|
||||||
4. Use `alpha_get_paper` and `alpha_ask_paper` on the strongest references.
|
4. If the setup is tied to current products, APIs, model offerings, pricing, or market behavior, use `web_search` and `fetch_content` first.
|
||||||
5. Prefer the smallest experiment that can meaningfully reduce uncertainty.
|
5. Use `alpha_search`, `alpha_get_paper`, and `alpha_ask_paper` for academic baselines and prior experiments.
|
||||||
6. List confounders and failure modes up front.
|
6. Prefer the smallest experiment that can meaningfully reduce uncertainty.
|
||||||
7. If implementation is requested, create the scripts, configs, and logging plan.
|
7. List confounders and failure modes up front.
|
||||||
8. Write the plan to disk before running expensive work.
|
8. If implementation is requested, create the scripts, configs, and logging plan.
|
||||||
|
9. Write the plan to disk before running expensive work.
|
||||||
|
|
||||||
## Pitfalls
|
## Pitfalls
|
||||||
|
|
||||||
|
|||||||
@@ -16,24 +16,26 @@ Use this skill when the user wants:
|
|||||||
|
|
||||||
## Procedure
|
## Procedure
|
||||||
|
|
||||||
1. Search broadly first with `alpha_search`.
|
1. Search broadly first.
|
||||||
2. Pick the strongest candidates by direct relevance, recency, citations, and venue quality.
|
2. If the topic is primarily academic or paper-centric, start with `alpha_search`.
|
||||||
3. Inspect the top papers with `alpha_get_paper` before making concrete claims.
|
3. If the topic includes current products, companies, markets, software, or "latest/current" framing, start with `web_search` and `fetch_content`, then use `alpha_search` only for academic background.
|
||||||
4. Use `alpha_ask_paper` for missing methodological or experimental details.
|
4. Pick the strongest candidates by direct relevance, recency, citations, venue quality, and source quality.
|
||||||
5. Build a compact evidence table:
|
5. Inspect the top papers with `alpha_get_paper` before making concrete claims.
|
||||||
|
6. Use `alpha_ask_paper` for missing methodological or experimental details.
|
||||||
|
7. Build a compact evidence table:
|
||||||
- title
|
- title
|
||||||
- year
|
- year
|
||||||
- authors
|
- authors
|
||||||
- venue
|
- venue
|
||||||
- claim or contribution
|
- claim or contribution
|
||||||
- important caveats
|
- important caveats
|
||||||
6. Distinguish:
|
8. Distinguish:
|
||||||
- what multiple sources agree on
|
- what multiple sources agree on
|
||||||
- where methods or findings differ
|
- where methods or findings differ
|
||||||
- what remains unresolved
|
- what remains unresolved
|
||||||
7. If the user wants a durable artifact, write a markdown brief to disk.
|
9. If the user wants a durable artifact, write a markdown brief to disk.
|
||||||
8. If you discover an important gotcha about a paper, save it with `alpha_annotate_paper`.
|
10. If you discover an important gotcha about a paper, save it with `alpha_annotate_paper`.
|
||||||
9. End with a `Sources` section that lists direct URLs, not just titles.
|
11. End with a `Sources` section that lists direct URLs, not just titles.
|
||||||
|
|
||||||
## Pitfalls
|
## Pitfalls
|
||||||
|
|
||||||
@@ -41,6 +43,7 @@ Use this skill when the user wants:
|
|||||||
- Do not flatten disagreements into fake consensus.
|
- Do not flatten disagreements into fake consensus.
|
||||||
- Do not treat recent preprints as established facts without saying so.
|
- Do not treat recent preprints as established facts without saying so.
|
||||||
- Do not cite secondary commentary when a primary source is available.
|
- Do not cite secondary commentary when a primary source is available.
|
||||||
|
- Do not treat a current product or market topic as if it were a paper-only topic.
|
||||||
|
|
||||||
## Output Shape
|
## Output Shape
|
||||||
|
|
||||||
|
|||||||
@@ -15,30 +15,33 @@ Use this skill for:
|
|||||||
|
|
||||||
## Procedure
|
## Procedure
|
||||||
|
|
||||||
1. Start with `alpha_search` in `all` mode.
|
1. Start with source discovery that matches the topic.
|
||||||
2. Inspect the strongest candidates with `alpha_get_paper`.
|
2. For academic topics, use `alpha_search` in `all` mode.
|
||||||
3. Use `alpha_ask_paper` for fit questions like:
|
3. For current, product-oriented, or market-facing topics, use `web_search` and `fetch_content` first, then use `alpha_search` for background literature if needed.
|
||||||
|
4. Inspect the strongest candidates directly before recommending them.
|
||||||
|
5. Use `alpha_ask_paper` for fit questions like:
|
||||||
- what problem does this really solve
|
- what problem does this really solve
|
||||||
- what assumptions does it rely on
|
- what assumptions does it rely on
|
||||||
- what prior work does it build on
|
- what prior work does it build on
|
||||||
4. Classify papers into roles:
|
6. Classify papers or sources into roles:
|
||||||
- foundational
|
- foundational
|
||||||
- key recent advances
|
- key recent advances
|
||||||
- evaluation or benchmark references
|
- evaluation or benchmark references
|
||||||
- critiques or limitations
|
- critiques or limitations
|
||||||
- likely replication targets
|
- likely replication targets
|
||||||
5. Order the list intentionally:
|
7. Order the list intentionally:
|
||||||
- start with orientation
|
- start with orientation
|
||||||
- move to strongest methods
|
- move to strongest methods
|
||||||
- finish with edges, critiques, or adjacent work
|
- finish with edges, critiques, or adjacent work
|
||||||
6. Write the final list as a durable markdown artifact in `outputs/`.
|
8. Write the final list as a durable markdown artifact in `outputs/`.
|
||||||
7. For every paper, include a direct URL.
|
9. For every source, include a direct URL.
|
||||||
|
|
||||||
## Pitfalls
|
## Pitfalls
|
||||||
|
|
||||||
- Do not sort purely by citations.
|
- Do not sort purely by citations.
|
||||||
- Do not over-index on recency when fundamentals matter.
|
- Do not over-index on recency when fundamentals matter.
|
||||||
- Do not include papers you have not inspected at all.
|
- Do not include papers you have not inspected at all.
|
||||||
|
- Do not force everything into papers when the user actually needs current docs, products, or market sources.
|
||||||
|
|
||||||
## Deliverable
|
## Deliverable
|
||||||
|
|
||||||
|
|||||||
@@ -17,20 +17,23 @@ Use this skill for:
|
|||||||
## Procedure
|
## Procedure
|
||||||
|
|
||||||
1. Find relevant sources first.
|
1. Find relevant sources first.
|
||||||
2. Inspect the strongest sources directly before synthesizing.
|
2. If the topic is current, product-oriented, market-facing, or asks about latest developments, use `web_search` and `fetch_content` first.
|
||||||
3. Separate:
|
3. If there is an academic literature component, use `alpha_search` and inspect the strongest papers directly.
|
||||||
|
4. Inspect the strongest sources directly before synthesizing.
|
||||||
|
5. Separate:
|
||||||
- established facts
|
- established facts
|
||||||
- plausible inferences
|
- plausible inferences
|
||||||
- unresolved questions
|
- unresolved questions
|
||||||
4. Write a memo with clear sections and a concise narrative.
|
6. Write a memo with clear sections and a concise narrative.
|
||||||
5. End with a `Sources` section containing direct links.
|
7. End with a `Sources` section containing direct links.
|
||||||
6. Save the memo to `outputs/` when the user wants a durable artifact.
|
8. Save the memo to `outputs/` when the user wants a durable artifact.
|
||||||
|
|
||||||
## Pitfalls
|
## Pitfalls
|
||||||
|
|
||||||
- Do not summarize from search snippets alone.
|
- Do not summarize from search snippets alone.
|
||||||
- Do not omit the source list.
|
- Do not omit the source list.
|
||||||
- Do not present inference as fact.
|
- Do not present inference as fact.
|
||||||
|
- Do not rely on paper search alone for latest/current topics.
|
||||||
|
|
||||||
## Deliverable
|
## Deliverable
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
export const FEYNMAN_SYSTEM_PROMPT = `You are Feynman, a research-first AI agent.
|
export function buildFeynmanSystemPrompt(): string {
|
||||||
|
return `You are Feynman, a research-first AI agent.
|
||||||
|
|
||||||
Your job is to investigate questions, read primary sources, design experiments, run them when useful, and produce reproducible written artifacts.
|
Your job is to investigate questions, read primary sources, compare evidence, design experiments when useful, and produce reproducible written artifacts.
|
||||||
|
|
||||||
Operating rules:
|
Operating rules:
|
||||||
- Evidence over fluency.
|
- Evidence over fluency.
|
||||||
@@ -9,18 +10,32 @@ Operating rules:
|
|||||||
- State uncertainty explicitly.
|
- State uncertainty explicitly.
|
||||||
- When a claim depends on recent literature or unstable facts, use tools before answering.
|
- When a claim depends on recent literature or unstable facts, use tools before answering.
|
||||||
- When discussing papers, cite title, year, and identifier or URL when possible.
|
- When discussing papers, cite title, year, and identifier or URL when possible.
|
||||||
- Use the alpha-backed research tools first for literature search, paper reading, paper Q&A, and persistent annotations.
|
- Use the alpha-backed research tools for academic paper search, paper reading, paper Q&A, repository inspection, and persistent annotations.
|
||||||
- Use the installed Pi research packages for broader web/PDF access, document parsing, session recall, background processes, experiment tracking, citations, and delegated subtasks when they reduce friction.
|
- Use \`web_search\`, \`fetch_content\`, and \`get_search_content\` first for current topics: products, companies, markets, regulations, software releases, model availability, model pricing, benchmarks, docs, or anything phrased as latest/current/recent/today.
|
||||||
|
- For mixed topics, combine both: use web sources for current reality and paper sources for background literature.
|
||||||
|
- Never answer a latest/current question from arXiv or alpha-backed paper search alone.
|
||||||
|
- For AI model or product claims, prefer official docs/vendor pages plus recent web sources over old papers.
|
||||||
|
- Use the installed Pi research packages for broader web/PDF access, document parsing, citation workflows, background processes, memory, session recall, and delegated subtasks when they reduce friction.
|
||||||
|
- Use the visualization packages when a chart, diagram, or interactive widget would materially improve understanding. Prefer charts for quantitative comparisons, Mermaid for simple process/architecture diagrams, and interactive HTML widgets for exploratory visual explanations.
|
||||||
|
- Persistent memory is package-backed. Use \`memory_search\` to recall prior preferences and lessons, \`memory_remember\` to store explicit durable facts, and \`memory_lessons\` when prior corrections matter.
|
||||||
|
- If the user says "remember", states a stable preference, or asks for something to be the default in future sessions, call \`memory_remember\`. Do not just say you will remember it.
|
||||||
|
- Session recall is package-backed. Use \`session_search\` when the user references prior work, asks what has been done before, or when you suspect relevant past context exists.
|
||||||
|
- Feynman is intended to support always-on research work. Use the scheduling package when recurring or deferred work is appropriate instead of telling the user to remember manually.
|
||||||
|
- Use \`schedule_prompt\` for recurring scans, delayed follow-ups, reminders, and periodic research jobs.
|
||||||
|
- If the user asks you to remind, check later, run something nightly, or keep watching something over time, call \`schedule_prompt\`. Do not just promise to do it later.
|
||||||
|
- Prefer the smallest investigation or experiment that can materially reduce uncertainty before escalating to broader work.
|
||||||
- When an experiment is warranted, write the code or scripts, run them, capture outputs, and save artifacts to disk.
|
- When an experiment is warranted, write the code or scripts, run them, capture outputs, and save artifacts to disk.
|
||||||
- Treat polished scientific communication as part of the job: structure reports cleanly, use Markdown deliberately, and use LaTeX math when equations clarify the argument.
|
- Treat polished scientific communication as part of the job: structure reports cleanly, use Markdown deliberately, and use LaTeX math when equations clarify the argument.
|
||||||
- For any source-based answer, include an explicit Sources section with direct URLs, not just paper titles.
|
- For any source-based answer, include an explicit Sources section with direct URLs, not just paper titles.
|
||||||
- When citing papers from alpha-backed tools, prefer direct arXiv or alphaXiv links and include the arXiv ID.
|
- When citing papers from alpha-backed tools, prefer direct arXiv or alphaXiv links and include the arXiv ID.
|
||||||
|
- After writing a polished artifact, use \`preview_file\` when the user wants to review it in a browser or PDF viewer.
|
||||||
|
- Default toward delivering a concrete artifact when the task naturally calls for one: reading list, memo, audit, experiment log, or draft.
|
||||||
- Default artifact locations:
|
- Default artifact locations:
|
||||||
- outputs/ for reviews, reading lists, and summaries
|
- outputs/ for reviews, reading lists, and summaries
|
||||||
- experiments/ for runnable experiment code and result logs
|
- experiments/ for runnable experiment code and result logs
|
||||||
- notes/ for scratch notes and intermediate synthesis
|
- notes/ for scratch notes and intermediate synthesis
|
||||||
- papers/ for polished paper-style drafts and writeups
|
- papers/ for polished paper-style drafts and writeups
|
||||||
- Default deliverables should include: summary, strongest evidence, disagreements or gaps, open questions, and recommended next steps.
|
- Default deliverables should include: summary, strongest evidence, disagreements or gaps, open questions, recommended next steps, and links to the source material.
|
||||||
|
|
||||||
Default workflow:
|
Default workflow:
|
||||||
1. Clarify the research objective if needed.
|
1. Clarify the research objective if needed.
|
||||||
@@ -33,4 +48,6 @@ Default workflow:
|
|||||||
Style:
|
Style:
|
||||||
- Concise, skeptical, and explicit.
|
- Concise, skeptical, and explicit.
|
||||||
- Avoid fake certainty.
|
- Avoid fake certainty.
|
||||||
- Do not present unverified claims as facts.`;
|
- Do not present unverified claims as facts.
|
||||||
|
- When greeting, introducing yourself, or answering "who are you", identify yourself explicitly as Feynman.`;
|
||||||
|
}
|
||||||
|
|||||||
377
src/index.ts
377
src/index.ts
@@ -1,9 +1,11 @@
|
|||||||
import "dotenv/config";
|
import "dotenv/config";
|
||||||
|
|
||||||
import { spawn } from "node:child_process";
|
import { spawn, spawnSync } from "node:child_process";
|
||||||
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
||||||
import { homedir } from "node:os";
|
import { homedir } from "node:os";
|
||||||
import { dirname, resolve } from "node:path";
|
import { dirname, resolve } from "node:path";
|
||||||
|
import { stdin as input, stdout as output } from "node:process";
|
||||||
|
import { createInterface } from "node:readline/promises";
|
||||||
import { parseArgs } from "node:util";
|
import { parseArgs } from "node:util";
|
||||||
import { fileURLToPath } from "node:url";
|
import { fileURLToPath } from "node:url";
|
||||||
|
|
||||||
@@ -18,7 +20,7 @@ import {
|
|||||||
AuthStorage,
|
AuthStorage,
|
||||||
} from "@mariozechner/pi-coding-agent";
|
} from "@mariozechner/pi-coding-agent";
|
||||||
|
|
||||||
import { FEYNMAN_SYSTEM_PROMPT } from "./feynman-prompt.js";
|
import { buildFeynmanSystemPrompt } from "./feynman-prompt.js";
|
||||||
|
|
||||||
type ThinkingLevel = "off" | "low" | "medium" | "high";
|
type ThinkingLevel = "off" | "low" | "medium" | "high";
|
||||||
|
|
||||||
@@ -34,6 +36,8 @@ function printHelp(): void {
|
|||||||
/replicate <paper> Expand the replication prompt template
|
/replicate <paper> Expand the replication prompt template
|
||||||
/reading-list <topic> Expand the reading list prompt template
|
/reading-list <topic> Expand the reading list prompt template
|
||||||
/research-memo <topic> Expand the general research memo prompt template
|
/research-memo <topic> Expand the general research memo prompt template
|
||||||
|
/deepresearch <topic> Expand the thorough source-heavy research prompt template
|
||||||
|
/autoresearch <idea> Expand the idea-to-paper autoresearch prompt template
|
||||||
/compare-sources <topic> Expand the source comparison prompt template
|
/compare-sources <topic> Expand the source comparison prompt template
|
||||||
/paper-code-audit <item> Expand the paper/code audit prompt template
|
/paper-code-audit <item> Expand the paper/code audit prompt template
|
||||||
/paper-draft <topic> Expand the paper-style writing prompt template
|
/paper-draft <topic> Expand the paper-style writing prompt template
|
||||||
@@ -46,7 +50,15 @@ function printHelp(): void {
|
|||||||
--model provider:model Force a specific model
|
--model provider:model Force a specific model
|
||||||
--thinking level off | low | medium | high
|
--thinking level off | low | medium | high
|
||||||
--cwd /path/to/workdir Working directory for tools
|
--cwd /path/to/workdir Working directory for tools
|
||||||
--session-dir /path Session storage directory`);
|
--session-dir /path Session storage directory
|
||||||
|
--doctor Check Feynman auth, models, preview tools, and paths
|
||||||
|
--setup-preview Install preview dependencies when possible
|
||||||
|
|
||||||
|
Top-level:
|
||||||
|
feynman setup Configure alpha login, web search, and preview deps
|
||||||
|
feynman setup alpha Configure alphaXiv login
|
||||||
|
feynman setup web Configure web search provider
|
||||||
|
feynman setup preview Install preview dependencies`);
|
||||||
}
|
}
|
||||||
|
|
||||||
function parseModelSpec(spec: string, modelRegistry: ModelRegistry) {
|
function parseModelSpec(spec: string, modelRegistry: ModelRegistry) {
|
||||||
@@ -78,9 +90,32 @@ function normalizeThinkingLevel(value: string | undefined): ThinkingLevel | unde
|
|||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function resolveExecutable(name: string, fallbackPaths: string[] = []): string | undefined {
|
||||||
|
for (const candidate of fallbackPaths) {
|
||||||
|
if (existsSync(candidate)) {
|
||||||
|
return candidate;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = spawnSync("sh", ["-lc", `command -v ${name}`], {
|
||||||
|
encoding: "utf8",
|
||||||
|
stdio: ["ignore", "pipe", "ignore"],
|
||||||
|
});
|
||||||
|
|
||||||
|
if (result.status === 0) {
|
||||||
|
const resolved = result.stdout.trim();
|
||||||
|
if (resolved) {
|
||||||
|
return resolved;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
function patchEmbeddedPiBranding(piPackageRoot: string): void {
|
function patchEmbeddedPiBranding(piPackageRoot: string): void {
|
||||||
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 interactiveModePath = resolve(piPackageRoot, "dist", "modes", "interactive", "interactive-mode.js");
|
||||||
|
|
||||||
if (existsSync(packageJsonPath)) {
|
if (existsSync(packageJsonPath)) {
|
||||||
const pkg = JSON.parse(readFileSync(packageJsonPath, "utf8")) as {
|
const pkg = JSON.parse(readFileSync(packageJsonPath, "utf8")) as {
|
||||||
@@ -101,6 +136,77 @@ function patchEmbeddedPiBranding(piPackageRoot: string): void {
|
|||||||
writeFileSync(cliPath, cliSource.replace('process.title = "pi";', 'process.title = "feynman";'), "utf8");
|
writeFileSync(cliPath, cliSource.replace('process.title = "pi";', 'process.title = "feynman";'), "utf8");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (existsSync(interactiveModePath)) {
|
||||||
|
const interactiveModeSource = readFileSync(interactiveModePath, "utf8");
|
||||||
|
if (interactiveModeSource.includes("`π - ${sessionName} - ${cwdBasename}`")) {
|
||||||
|
writeFileSync(
|
||||||
|
interactiveModePath,
|
||||||
|
interactiveModeSource
|
||||||
|
.replace("`π - ${sessionName} - ${cwdBasename}`", "`feynman - ${sessionName} - ${cwdBasename}`")
|
||||||
|
.replace("`π - ${cwdBasename}`", "`feynman - ${cwdBasename}`"),
|
||||||
|
"utf8",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function patchPackageWorkspace(appRoot: string): void {
|
||||||
|
const workspaceRoot = resolve(appRoot, ".pi", "npm", "node_modules");
|
||||||
|
const webAccessPath = resolve(workspaceRoot, "pi-web-access", "index.ts");
|
||||||
|
const sessionSearchIndexerPath = resolve(
|
||||||
|
workspaceRoot,
|
||||||
|
"@kaiserlich-dev",
|
||||||
|
"pi-session-search",
|
||||||
|
"extensions",
|
||||||
|
"indexer.ts",
|
||||||
|
);
|
||||||
|
const piMemoryPath = resolve(workspaceRoot, "@samfp", "pi-memory", "src", "index.ts");
|
||||||
|
|
||||||
|
if (existsSync(webAccessPath)) {
|
||||||
|
const source = readFileSync(webAccessPath, "utf8");
|
||||||
|
if (source.includes('pi.registerCommand("search",')) {
|
||||||
|
writeFileSync(
|
||||||
|
webAccessPath,
|
||||||
|
source.replace('pi.registerCommand("search",', 'pi.registerCommand("web-results",'),
|
||||||
|
"utf8",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (existsSync(sessionSearchIndexerPath)) {
|
||||||
|
const source = readFileSync(sessionSearchIndexerPath, "utf8");
|
||||||
|
const original = 'const sessionsDir = path.join(os.homedir(), ".pi", "agent", "sessions");';
|
||||||
|
const replacement =
|
||||||
|
'const sessionsDir = process.env.FEYNMAN_SESSION_DIR ?? process.env.PI_SESSION_DIR ?? path.join(os.homedir(), ".pi", "agent", "sessions");';
|
||||||
|
if (source.includes(original)) {
|
||||||
|
writeFileSync(sessionSearchIndexerPath, source.replace(original, replacement), "utf8");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (existsSync(piMemoryPath)) {
|
||||||
|
let source = readFileSync(piMemoryPath, "utf8");
|
||||||
|
const memoryOriginal = 'const MEMORY_DIR = join(homedir(), ".pi", "memory");';
|
||||||
|
const memoryReplacement =
|
||||||
|
'const MEMORY_DIR = process.env.FEYNMAN_MEMORY_DIR ?? process.env.PI_MEMORY_DIR ?? join(homedir(), ".pi", "memory");';
|
||||||
|
if (source.includes(memoryOriginal)) {
|
||||||
|
source = source.replace(memoryOriginal, memoryReplacement);
|
||||||
|
}
|
||||||
|
|
||||||
|
const execOriginal = 'const result = await pi.exec("pi", ["-p", prompt, "--print"], {';
|
||||||
|
const execReplacement = [
|
||||||
|
'const execBinary = process.env.FEYNMAN_NODE_EXECUTABLE || process.env.FEYNMAN_EXECUTABLE || "pi";',
|
||||||
|
' const execArgs = process.env.FEYNMAN_BIN_PATH',
|
||||||
|
' ? [process.env.FEYNMAN_BIN_PATH, "--prompt", prompt]',
|
||||||
|
' : ["-p", prompt, "--print"];',
|
||||||
|
' const result = await pi.exec(execBinary, execArgs, {',
|
||||||
|
].join("\n");
|
||||||
|
if (source.includes(execOriginal)) {
|
||||||
|
source = source.replace(execOriginal, execReplacement);
|
||||||
|
}
|
||||||
|
|
||||||
|
writeFileSync(piMemoryPath, source, "utf8");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function choosePreferredModel(
|
function choosePreferredModel(
|
||||||
@@ -149,12 +255,6 @@ function normalizeFeynmanSettings(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (Array.isArray(settings.packages)) {
|
|
||||||
settings.packages = settings.packages.filter(
|
|
||||||
(entry) => entry !== "npm:@kaiserlich-dev/pi-session-search",
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!settings.defaultThinkingLevel) {
|
if (!settings.defaultThinkingLevel) {
|
||||||
settings.defaultThinkingLevel = defaultThinkingLevel;
|
settings.defaultThinkingLevel = defaultThinkingLevel;
|
||||||
}
|
}
|
||||||
@@ -180,6 +280,217 @@ function normalizeFeynmanSettings(
|
|||||||
writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + "\n", "utf8");
|
writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + "\n", "utf8");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function readJson(path: string): Record<string, unknown> {
|
||||||
|
if (!existsSync(path)) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
return JSON.parse(readFileSync(path, "utf8"));
|
||||||
|
} catch {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function getWebSearchConfigPath(): string {
|
||||||
|
return resolve(homedir(), ".pi", "web-search.json");
|
||||||
|
}
|
||||||
|
|
||||||
|
function loadWebSearchConfig(): Record<string, unknown> {
|
||||||
|
return readJson(getWebSearchConfigPath());
|
||||||
|
}
|
||||||
|
|
||||||
|
function saveWebSearchConfig(config: Record<string, unknown>): void {
|
||||||
|
const path = getWebSearchConfigPath();
|
||||||
|
mkdirSync(dirname(path), { recursive: true });
|
||||||
|
writeFileSync(path, JSON.stringify(config, null, 2) + "\n", "utf8");
|
||||||
|
}
|
||||||
|
|
||||||
|
function hasConfiguredWebProvider(): boolean {
|
||||||
|
const config = loadWebSearchConfig();
|
||||||
|
return typeof config.perplexityApiKey === "string" && config.perplexityApiKey.trim().length > 0
|
||||||
|
|| typeof config.geminiApiKey === "string" && config.geminiApiKey.trim().length > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function promptText(question: string, defaultValue = ""): Promise<string> {
|
||||||
|
if (!input.isTTY || !output.isTTY) {
|
||||||
|
throw new Error("feynman setup requires an interactive terminal.");
|
||||||
|
}
|
||||||
|
const rl = createInterface({ input, output });
|
||||||
|
try {
|
||||||
|
const suffix = defaultValue ? ` [${defaultValue}]` : "";
|
||||||
|
const value = (await rl.question(`${question}${suffix}: `)).trim();
|
||||||
|
return value || defaultValue;
|
||||||
|
} finally {
|
||||||
|
rl.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function promptChoice(question: string, choices: string[], defaultIndex = 0): Promise<number> {
|
||||||
|
console.log(question);
|
||||||
|
for (const [index, choice] of choices.entries()) {
|
||||||
|
const marker = index === defaultIndex ? "*" : " ";
|
||||||
|
console.log(` ${marker} ${index + 1}. ${choice}`);
|
||||||
|
}
|
||||||
|
const answer = await promptText("Select", String(defaultIndex + 1));
|
||||||
|
const parsed = Number(answer);
|
||||||
|
if (!Number.isFinite(parsed) || parsed < 1 || parsed > choices.length) {
|
||||||
|
return defaultIndex;
|
||||||
|
}
|
||||||
|
return parsed - 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function setupWebProvider(): Promise<void> {
|
||||||
|
const config = loadWebSearchConfig();
|
||||||
|
const choices = [
|
||||||
|
"Gemini API key",
|
||||||
|
"Perplexity API key",
|
||||||
|
"Browser Gemini (manual sign-in only)",
|
||||||
|
"Skip",
|
||||||
|
];
|
||||||
|
const selection = await promptChoice("Choose a web search provider for Feynman:", choices, hasConfiguredWebProvider() ? 3 : 0);
|
||||||
|
|
||||||
|
if (selection === 0) {
|
||||||
|
const key = await promptText("Gemini API key");
|
||||||
|
if (key) {
|
||||||
|
config.geminiApiKey = key;
|
||||||
|
delete config.perplexityApiKey;
|
||||||
|
saveWebSearchConfig(config);
|
||||||
|
console.log("Saved Gemini API key to ~/.pi/web-search.json");
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (selection === 1) {
|
||||||
|
const key = await promptText("Perplexity API key");
|
||||||
|
if (key) {
|
||||||
|
config.perplexityApiKey = key;
|
||||||
|
delete config.geminiApiKey;
|
||||||
|
saveWebSearchConfig(config);
|
||||||
|
console.log("Saved Perplexity API key to ~/.pi/web-search.json");
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (selection === 2) {
|
||||||
|
console.log("Sign into gemini.google.com in Chrome, Chromium, Brave, or Edge, then restart Feynman.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function runSetup(
|
||||||
|
section: string | undefined,
|
||||||
|
settingsPath: string,
|
||||||
|
bundledSettingsPath: string,
|
||||||
|
authPath: string,
|
||||||
|
workingDir: string,
|
||||||
|
sessionDir: string,
|
||||||
|
): Promise<void> {
|
||||||
|
if (section === "alpha" || !section) {
|
||||||
|
if (!isAlphaLoggedIn()) {
|
||||||
|
await loginAlpha();
|
||||||
|
console.log("alphaXiv login complete");
|
||||||
|
} else {
|
||||||
|
console.log("alphaXiv login already configured");
|
||||||
|
}
|
||||||
|
if (section === "alpha") return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (section === "web" || !section) {
|
||||||
|
await setupWebProvider();
|
||||||
|
if (section === "web") return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (section === "preview" || !section) {
|
||||||
|
setupPreviewDependencies();
|
||||||
|
if (section === "preview") return;
|
||||||
|
}
|
||||||
|
|
||||||
|
normalizeFeynmanSettings(settingsPath, bundledSettingsPath, "medium", authPath);
|
||||||
|
runDoctor(settingsPath, authPath, sessionDir, workingDir);
|
||||||
|
}
|
||||||
|
|
||||||
|
function runDoctor(
|
||||||
|
settingsPath: string,
|
||||||
|
authPath: string,
|
||||||
|
sessionDir: string,
|
||||||
|
workingDir: string,
|
||||||
|
): void {
|
||||||
|
const settings = readJson(settingsPath);
|
||||||
|
const modelRegistry = new ModelRegistry(AuthStorage.create(authPath));
|
||||||
|
const availableModels = modelRegistry.getAvailable();
|
||||||
|
const pandocPath = resolveExecutable("pandoc", [
|
||||||
|
"/opt/homebrew/bin/pandoc",
|
||||||
|
"/usr/local/bin/pandoc",
|
||||||
|
]);
|
||||||
|
const browserPath =
|
||||||
|
process.env.PUPPETEER_EXECUTABLE_PATH ??
|
||||||
|
resolveExecutable("google-chrome", [
|
||||||
|
"/Applications/Google Chrome.app/Contents/MacOS/Google Chrome",
|
||||||
|
"/Applications/Chromium.app/Contents/MacOS/Chromium",
|
||||||
|
"/Applications/Brave Browser.app/Contents/MacOS/Brave Browser",
|
||||||
|
"/Applications/Microsoft Edge.app/Contents/MacOS/Microsoft Edge",
|
||||||
|
]);
|
||||||
|
|
||||||
|
console.log("Feynman doctor");
|
||||||
|
console.log("");
|
||||||
|
console.log(`working dir: ${workingDir}`);
|
||||||
|
console.log(`session dir: ${sessionDir}`);
|
||||||
|
console.log("");
|
||||||
|
console.log(`alphaXiv auth: ${isAlphaLoggedIn() ? "ok" : "missing"}`);
|
||||||
|
if (isAlphaLoggedIn()) {
|
||||||
|
const name = getAlphaUserName();
|
||||||
|
if (name) {
|
||||||
|
console.log(` user: ${name}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
console.log(`models available: ${availableModels.length}`);
|
||||||
|
if (availableModels.length > 0) {
|
||||||
|
const sample = availableModels
|
||||||
|
.slice(0, 6)
|
||||||
|
.map((model) => `${model.provider}/${model.id}`)
|
||||||
|
.join(", ");
|
||||||
|
console.log(` sample: ${sample}`);
|
||||||
|
}
|
||||||
|
console.log(
|
||||||
|
`default model: ${typeof settings.defaultProvider === "string" && typeof settings.defaultModel === "string"
|
||||||
|
? `${settings.defaultProvider}/${settings.defaultModel}`
|
||||||
|
: "not set"}`,
|
||||||
|
);
|
||||||
|
console.log(`pandoc: ${pandocPath ?? "missing"}`);
|
||||||
|
console.log(`browser preview runtime: ${browserPath ?? "missing"}`);
|
||||||
|
console.log(`web research provider: ${hasConfiguredWebProvider() ? "configured" : "missing"}`);
|
||||||
|
console.log(`quiet startup: ${settings.quietStartup === true ? "enabled" : "disabled"}`);
|
||||||
|
console.log(`theme: ${typeof settings.theme === "string" ? settings.theme : "not set"}`);
|
||||||
|
console.log(`setup hint: feynman setup`);
|
||||||
|
}
|
||||||
|
|
||||||
|
function setupPreviewDependencies(): void {
|
||||||
|
const pandocPath = resolveExecutable("pandoc", [
|
||||||
|
"/opt/homebrew/bin/pandoc",
|
||||||
|
"/usr/local/bin/pandoc",
|
||||||
|
]);
|
||||||
|
if (pandocPath) {
|
||||||
|
console.log(`pandoc already installed at ${pandocPath}`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const brewPath = resolveExecutable("brew", [
|
||||||
|
"/opt/homebrew/bin/brew",
|
||||||
|
"/usr/local/bin/brew",
|
||||||
|
]);
|
||||||
|
if (process.platform === "darwin" && brewPath) {
|
||||||
|
const result = spawnSync(brewPath, ["install", "pandoc"], { stdio: "inherit" });
|
||||||
|
if (result.status !== 0) {
|
||||||
|
throw new Error("Failed to install pandoc via Homebrew.");
|
||||||
|
}
|
||||||
|
console.log("Preview dependency installed: pandoc");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new Error("Automatic preview setup is only supported on macOS with Homebrew right now.");
|
||||||
|
}
|
||||||
|
|
||||||
function syncFeynmanTheme(appRoot: string, agentDir: string): void {
|
function syncFeynmanTheme(appRoot: string, agentDir: string): void {
|
||||||
const sourceThemePath = resolve(appRoot, ".pi", "themes", "feynman.json");
|
const sourceThemePath = resolve(appRoot, ".pi", "themes", "feynman.json");
|
||||||
const targetThemeDir = resolve(agentDir, "themes");
|
const targetThemeDir = resolve(agentDir, "themes");
|
||||||
@@ -201,11 +512,13 @@ async function main(): Promise<void> {
|
|||||||
const feynmanAgentDir = resolve(homedir(), ".feynman", "agent");
|
const feynmanAgentDir = resolve(homedir(), ".feynman", "agent");
|
||||||
const bundledSettingsPath = resolve(appRoot, ".pi", "settings.json");
|
const bundledSettingsPath = resolve(appRoot, ".pi", "settings.json");
|
||||||
patchEmbeddedPiBranding(piPackageRoot);
|
patchEmbeddedPiBranding(piPackageRoot);
|
||||||
|
patchPackageWorkspace(appRoot);
|
||||||
|
|
||||||
const { values, positionals } = parseArgs({
|
const { values, positionals } = parseArgs({
|
||||||
allowPositionals: true,
|
allowPositionals: true,
|
||||||
options: {
|
options: {
|
||||||
cwd: { type: "string" },
|
cwd: { type: "string" },
|
||||||
|
doctor: { type: "boolean" },
|
||||||
help: { type: "boolean" },
|
help: { type: "boolean" },
|
||||||
"alpha-login": { type: "boolean" },
|
"alpha-login": { type: "boolean" },
|
||||||
"alpha-logout": { type: "boolean" },
|
"alpha-logout": { type: "boolean" },
|
||||||
@@ -214,6 +527,7 @@ async function main(): Promise<void> {
|
|||||||
"new-session": { type: "boolean" },
|
"new-session": { type: "boolean" },
|
||||||
prompt: { type: "string" },
|
prompt: { type: "string" },
|
||||||
"session-dir": { type: "string" },
|
"session-dir": { type: "string" },
|
||||||
|
"setup-preview": { type: "boolean" },
|
||||||
thinking: { type: "string" },
|
thinking: { type: "string" },
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
@@ -233,6 +547,21 @@ async function main(): Promise<void> {
|
|||||||
const thinkingLevel = normalizeThinkingLevel(values.thinking ?? process.env.FEYNMAN_THINKING) ?? "medium";
|
const thinkingLevel = normalizeThinkingLevel(values.thinking ?? process.env.FEYNMAN_THINKING) ?? "medium";
|
||||||
normalizeFeynmanSettings(feynmanSettingsPath, bundledSettingsPath, thinkingLevel, feynmanAuthPath);
|
normalizeFeynmanSettings(feynmanSettingsPath, bundledSettingsPath, thinkingLevel, feynmanAuthPath);
|
||||||
|
|
||||||
|
if (positionals[0] === "setup") {
|
||||||
|
await runSetup(positionals[1], feynmanSettingsPath, bundledSettingsPath, feynmanAuthPath, workingDir, sessionDir);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (values.doctor) {
|
||||||
|
runDoctor(feynmanSettingsPath, feynmanAuthPath, sessionDir, workingDir);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (values["setup-preview"]) {
|
||||||
|
setupPreviewDependencies();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (values["alpha-login"]) {
|
if (values["alpha-login"]) {
|
||||||
const result = await loginAlpha();
|
const result = await loginAlpha();
|
||||||
normalizeFeynmanSettings(feynmanSettingsPath, bundledSettingsPath, thinkingLevel, feynmanAuthPath);
|
normalizeFeynmanSettings(feynmanSettingsPath, bundledSettingsPath, thinkingLevel, feynmanAuthPath);
|
||||||
@@ -273,6 +602,7 @@ async function main(): Promise<void> {
|
|||||||
}
|
}
|
||||||
const oneShotPrompt = values.prompt;
|
const oneShotPrompt = values.prompt;
|
||||||
const initialPrompt = oneShotPrompt ?? (positionals.length > 0 ? positionals.join(" ") : undefined);
|
const initialPrompt = oneShotPrompt ?? (positionals.length > 0 ? positionals.join(" ") : undefined);
|
||||||
|
const systemPrompt = buildFeynmanSystemPrompt();
|
||||||
|
|
||||||
const piArgs = [
|
const piArgs = [
|
||||||
"--session-dir",
|
"--session-dir",
|
||||||
@@ -284,7 +614,7 @@ async function main(): Promise<void> {
|
|||||||
"--prompt-template",
|
"--prompt-template",
|
||||||
resolve(appRoot, "prompts"),
|
resolve(appRoot, "prompts"),
|
||||||
"--system-prompt",
|
"--system-prompt",
|
||||||
FEYNMAN_SYSTEM_PROMPT,
|
systemPrompt,
|
||||||
];
|
];
|
||||||
|
|
||||||
if (explicitModelSpec) {
|
if (explicitModelSpec) {
|
||||||
@@ -307,6 +637,33 @@ async function main(): Promise<void> {
|
|||||||
...process.env,
|
...process.env,
|
||||||
PI_CODING_AGENT_DIR: feynmanAgentDir,
|
PI_CODING_AGENT_DIR: feynmanAgentDir,
|
||||||
FEYNMAN_CODING_AGENT_DIR: feynmanAgentDir,
|
FEYNMAN_CODING_AGENT_DIR: feynmanAgentDir,
|
||||||
|
FEYNMAN_PI_NPM_ROOT: resolve(appRoot, ".pi", "npm", "node_modules"),
|
||||||
|
FEYNMAN_SESSION_DIR: sessionDir,
|
||||||
|
PI_SESSION_DIR: sessionDir,
|
||||||
|
FEYNMAN_MEMORY_DIR: resolve(dirname(feynmanAgentDir), "memory"),
|
||||||
|
FEYNMAN_NODE_EXECUTABLE: process.execPath,
|
||||||
|
FEYNMAN_BIN_PATH: resolve(appRoot, "bin", "feynman.js"),
|
||||||
|
PANDOC_PATH:
|
||||||
|
process.env.PANDOC_PATH ??
|
||||||
|
resolveExecutable("pandoc", [
|
||||||
|
"/opt/homebrew/bin/pandoc",
|
||||||
|
"/usr/local/bin/pandoc",
|
||||||
|
]),
|
||||||
|
PI_SKIP_VERSION_CHECK: process.env.PI_SKIP_VERSION_CHECK ?? "1",
|
||||||
|
MERMAID_CLI_PATH:
|
||||||
|
process.env.MERMAID_CLI_PATH ??
|
||||||
|
resolveExecutable("mmdc", [
|
||||||
|
"/opt/homebrew/bin/mmdc",
|
||||||
|
"/usr/local/bin/mmdc",
|
||||||
|
]),
|
||||||
|
PUPPETEER_EXECUTABLE_PATH:
|
||||||
|
process.env.PUPPETEER_EXECUTABLE_PATH ??
|
||||||
|
resolveExecutable("google-chrome", [
|
||||||
|
"/Applications/Google Chrome.app/Contents/MacOS/Google Chrome",
|
||||||
|
"/Applications/Chromium.app/Contents/MacOS/Chromium",
|
||||||
|
"/Applications/Brave Browser.app/Contents/MacOS/Brave Browser",
|
||||||
|
"/Applications/Microsoft Edge.app/Contents/MacOS/Microsoft Edge",
|
||||||
|
]),
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user