Remove flask ASCII art and update harness internals

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Advait Paliwal
2026-03-23 11:35:12 -07:00
parent 46810f97b7
commit d23e679331
7 changed files with 254 additions and 298 deletions

View File

@@ -44,16 +44,7 @@ const FEYNMAN_AGENT_LOGO = [
"╚═╝ ╚══════╝ ╚═╝ ╚═╝ ╚═══╝╚═╝ ╚═╝╚═╝ ╚═╝╚═╝ ╚═══╝", "╚═╝ ╚══════╝ ╚═╝ ╚═╝ ╚═══╝╚═╝ ╚═╝╚═╝ ╚═╝╚═╝ ╚═══╝",
]; ];
const FEYNMAN_MARK_ART = [ const FEYNMAN_MARK_ART: string[] = [];
" .-.",
" /___\\\\",
" |:::|",
" |:::|",
" .-'`:::::`'-.",
" /:::::::::::::\\\\",
" \\\\:::::::::::://",
" '-.______.-'",
];
const FEYNMAN_RESEARCH_TOOLS = [ const FEYNMAN_RESEARCH_TOOLS = [
"alpha_search", "alpha_search",
@@ -754,34 +745,6 @@ Recommended contents:
`; `;
} }
type HelpCommand = {
usage: string;
description: string;
};
function buildFeynmanHelpSections(pi: ExtensionAPI): Array<{ title: string; commands: HelpCommand[] }> {
const commands = pi.getCommands();
const promptCommands = sortCommands(commands.filter((command) => command.source === "prompt")).map((command) => ({
usage: `/${command.name}`,
description: command.description ?? "Prompt workflow",
}));
const extensionCommands = sortCommands(commands.filter((command) => command.source === "extension")).map((command) => ({
usage: `/${command.name}`,
description: command.description ?? "Extension command",
}));
return [
{
title: "Prompt Workflows",
commands: promptCommands,
},
{
title: "Commands",
commands: extensionCommands,
},
];
}
export default function researchTools(pi: ExtensionAPI): void { export default function researchTools(pi: ExtensionAPI): void {
let skillSummaryPromise: Promise<CatalogSummary> | undefined; let skillSummaryPromise: Promise<CatalogSummary> | undefined;
let agentSummaryPromise: Promise<CatalogSummary> | undefined; let agentSummaryPromise: Promise<CatalogSummary> | undefined;
@@ -801,186 +764,168 @@ export default function researchTools(pi: ExtensionAPI): void {
ctx.ui.setHeader((_tui, theme) => ({ ctx.ui.setHeader((_tui, theme) => ({
render(width: number): string[] { render(width: number): string[] {
const maxAvailableWidth = Math.max(width - 2, 1); const maxAvailableWidth = Math.max(width - 2, 1);
const preferredWidth = Math.min(136, Math.max(72, maxAvailableWidth)); const preferredWidth = Math.min(136, Math.max(72, maxAvailableWidth));
const cardWidth = Math.min(maxAvailableWidth, preferredWidth); const cardWidth = Math.min(maxAvailableWidth, preferredWidth);
const innerWidth = cardWidth - 2; const innerWidth = cardWidth - 2;
const outerPadding = " ".repeat(Math.max(0, Math.floor((width - cardWidth) / 2))); const outerPadding = " ".repeat(Math.max(0, Math.floor((width - cardWidth) / 2)));
const title = truncateForWidth(` Feynman Research Agent v${FEYNMAN_VERSION} `, innerWidth); const title = truncateForWidth(` Feynman Research Agent v${FEYNMAN_VERSION} `, innerWidth);
const titledBorder = buildTitledBorder(innerWidth, title); const titledBorder = buildTitledBorder(innerWidth, title);
const modelLabel = getCurrentModelLabel(ctx); const modelLabel = getCurrentModelLabel(ctx);
const sessionLabel = ctx.sessionManager.getSessionName()?.trim() || ctx.sessionManager.getSessionId(); const sessionLabel = ctx.sessionManager.getSessionName()?.trim() || ctx.sessionManager.getSessionId();
const directoryLabel = formatHeaderPath(ctx.cwd); const directoryLabel = formatHeaderPath(ctx.cwd);
const recentActivity = getRecentActivitySummary(ctx); const recentActivity = getRecentActivitySummary(ctx);
const lines: string[] = []; const lines: string[] = [];
const push = (line: string): void => { const push = (line: string): void => {
lines.push(`${outerPadding}${line}`); lines.push(`${outerPadding}${line}`);
};
const renderBoxLine = (content: string): string =>
`${theme.fg("borderMuted", "│")}${content}${theme.fg("borderMuted", "│")}`;
const renderDivider = (): string =>
`${theme.fg("borderMuted", "├")}${theme.fg("borderMuted", "─".repeat(innerWidth))}${theme.fg("borderMuted", "┤")}`;
const styleAccentCell = (text: string, cellWidth: number): string =>
theme.fg("accent", theme.bold(padCell(text, cellWidth)));
const styleMutedCell = (text: string, cellWidth: number): string =>
theme.fg("muted", padCell(text, cellWidth));
const styleSuccessCell = (text: string, cellWidth: number): string =>
theme.fg("success", theme.bold(padCell(text, cellWidth)));
const styleWarningCell = (text: string, cellWidth: number): string =>
theme.fg("warning", theme.bold(padCell(text, cellWidth)));
push("");
for (const logoLine of FEYNMAN_AGENT_LOGO) {
push(theme.fg("accent", theme.bold(centerText(logoLine, cardWidth))));
}
push("");
push(
theme.fg("borderMuted", `${titledBorder.left}`) +
theme.fg("accent", theme.bold(title)) +
theme.fg("borderMuted", `${titledBorder.right}`),
);
if (innerWidth < 72) {
const activityLines = wrapForWidth(recentActivity, innerWidth, 2);
push(renderBoxLine(padCell("", innerWidth)));
push(renderBoxLine(theme.fg("accent", theme.bold(padCell("Research session ready", innerWidth)))));
push(renderBoxLine(padCell(`model: ${modelLabel}`, innerWidth)));
push(renderBoxLine(padCell(`session: ${sessionLabel}`, innerWidth)));
push(renderBoxLine(padCell(`directory: ${directoryLabel}`, innerWidth)));
push(renderDivider());
push(renderBoxLine(theme.fg("accent", theme.bold(padCell("Available tools", innerWidth)))));
for (const toolLine of toolSummary.lines.slice(0, 4)) {
push(renderBoxLine(padCell(toolLine, innerWidth)));
}
push(renderDivider());
push(renderBoxLine(theme.fg("accent", theme.bold(padCell("Slash Commands", innerWidth)))));
for (const commandLine of commandSummary.lines.slice(0, 4)) {
push(renderBoxLine(padCell(commandLine, innerWidth)));
}
push(renderDivider());
push(renderBoxLine(theme.fg("success", theme.bold(padCell("Research Skills", innerWidth)))));
for (const skillLine of skillSummary.lines.slice(0, 4)) {
push(renderBoxLine(padCell(skillLine, innerWidth)));
}
if (agentSummary.lines.length > 0) {
push(renderDivider());
push(renderBoxLine(theme.fg("warning", theme.bold(padCell("Project Agents", innerWidth)))));
for (const agentLine of agentSummary.lines.slice(0, 3)) {
push(renderBoxLine(padCell(agentLine, innerWidth)));
}
}
push(renderDivider());
push(renderBoxLine(theme.fg("accent", theme.bold(padCell("Recent activity", innerWidth)))));
for (const activityLine of activityLines.length > 0 ? activityLines : ["No messages yet in this session."]) {
push(renderBoxLine(padCell(activityLine, innerWidth)));
}
push(renderDivider());
push(
renderBoxLine(
padCell(
`${toolSummary.count} tools · ${commandSummary.count} commands · ${skillSummary.count} skills · /help`,
innerWidth,
),
),
);
} else {
const leftWidth = Math.min(44, Math.max(30, Math.floor(innerWidth * 0.36)));
const rightWidth = innerWidth - leftWidth - 3;
const activityLines = wrapForWidth(recentActivity, innerWidth, 2);
const wrappedToolLines = toolSummary.lines.flatMap((line) => wrapForWidth(line, rightWidth, 3));
const wrappedCommandLines = commandSummary.lines.flatMap((line) => wrapForWidth(line, rightWidth, 4));
const wrappedSkillLines = skillSummary.lines.flatMap((line) => wrapForWidth(line, rightWidth, 4));
const wrappedAgentLines = agentSummary.lines.flatMap((line) => wrapForWidth(line, rightWidth, 4));
const wrappedModelLines = wrapForWidth(`model: ${modelLabel}`, leftWidth, 2);
const wrappedDirectoryLines = wrapForWidth(`directory: ${directoryLabel}`, leftWidth, 2);
const wrappedSessionLines = wrapForWidth(`session: ${sessionLabel}`, leftWidth, 2);
const wrappedFooterLines = wrapForWidth(
`${toolSummary.count} tools · ${commandSummary.count} commands · ${skillSummary.count} skills · /help`,
leftWidth,
2,
);
const leftLines = [
...FEYNMAN_MARK_ART.map((line) => centerText(line, leftWidth)),
"",
centerText("Research shell ready", leftWidth),
"",
...wrappedModelLines,
...wrappedDirectoryLines,
...wrappedSessionLines,
"",
...wrappedFooterLines,
];
const rightLines = [
"Available Tools",
...wrappedToolLines,
"",
"Slash Commands",
...wrappedCommandLines,
"",
"Research Skills",
...wrappedSkillLines,
...(wrappedAgentLines.length > 0 ? ["", "Project Agents", ...wrappedAgentLines] : []),
"",
"Recent Activity",
...(activityLines.length > 0 ? activityLines : ["No messages yet in this session."]),
];
const row = (
left: string,
right: string,
options?: { leftAccent?: boolean; rightAccent?: boolean; leftMuted?: boolean; rightMuted?: boolean },
): string => {
const leftCell = options?.leftAccent
? styleAccentCell(left, leftWidth)
: options?.leftMuted
? styleMutedCell(left, leftWidth)
: padCell(left, leftWidth);
const rightCell = options?.rightAccent
? styleAccentCell(right, rightWidth)
: options?.rightMuted
? styleMutedCell(right, rightWidth)
: padCell(right, rightWidth);
return renderBoxLine(`${leftCell}${theme.fg("borderMuted", " │ ")}${rightCell}`);
}; };
push(renderBoxLine(padCell("", innerWidth))); const renderBoxLine = (content: string): string =>
for (let index = 0; index < Math.max(leftLines.length, rightLines.length); index += 1) { `${theme.fg("borderMuted", "│")}${content}${theme.fg("borderMuted", "│")}`;
const left = leftLines[index] ?? ""; const renderDivider = (): string =>
const right = rightLines[index] ?? ""; `${theme.fg("borderMuted", "├")}${theme.fg("borderMuted", "─".repeat(innerWidth))}${theme.fg("borderMuted", "┤")}`;
const isLogoLine = index < FEYNMAN_MARK_ART.length; const styleAccentCell = (text: string, cellWidth: number): string =>
const isRightSectionHeading = theme.fg("accent", theme.bold(padCell(text, cellWidth)));
right === "Available Tools" || right === "Slash Commands" || right === "Research Skills" || right === "Project Agents" || const styleMutedCell = (text: string, cellWidth: number): string =>
right === "Recent Activity"; theme.fg("muted", padCell(text, cellWidth));
const isResearchHeading = right === "Research Skills"; const styleSuccessCell = (text: string, cellWidth: number): string =>
const isAgentHeading = right === "Project Agents"; theme.fg("success", theme.bold(padCell(text, cellWidth)));
const isFooterLine = left.includes("/help"); const styleWarningCell = (text: string, cellWidth: number): string =>
push( theme.fg("warning", theme.bold(padCell(text, cellWidth)));
(() => {
const leftCell = isLogoLine
? styleAccentCell(left, leftWidth)
: !isFooterLine && index >= FEYNMAN_MARK_ART.length + 2
? styleMutedCell(left, leftWidth)
: padCell(left, leftWidth);
const rightCell = isResearchHeading
? styleSuccessCell(right, rightWidth)
: isAgentHeading
? styleWarningCell(right, rightWidth)
: isRightSectionHeading
? styleAccentCell(right, rightWidth)
: right.length > 0
? styleMutedCell(right, rightWidth)
: padCell(right, rightWidth);
return renderBoxLine(`${leftCell}${theme.fg("borderMuted", " │ ")}${rightCell}`);
})(),
);
}
}
push(theme.fg("borderMuted", `${"─".repeat(innerWidth)}`)); push("");
push(""); for (const logoLine of FEYNMAN_AGENT_LOGO) {
return lines; push(theme.fg("accent", theme.bold(centerText(logoLine, cardWidth))));
}, }
invalidate() {}, push("");
push(
theme.fg("borderMuted", `${titledBorder.left}`) +
theme.fg("accent", theme.bold(title)) +
theme.fg("borderMuted", `${titledBorder.right}`),
);
if (innerWidth < 72) {
const activityLines = wrapForWidth(recentActivity, innerWidth, 2);
push(renderBoxLine(padCell("", innerWidth)));
push(renderBoxLine(theme.fg("accent", theme.bold(padCell("Research session ready", innerWidth)))));
push(renderBoxLine(padCell(`model: ${modelLabel}`, innerWidth)));
push(renderBoxLine(padCell(`session: ${sessionLabel}`, innerWidth)));
push(renderBoxLine(padCell(`directory: ${directoryLabel}`, innerWidth)));
push(renderDivider());
push(renderBoxLine(theme.fg("accent", theme.bold(padCell("Available tools", innerWidth)))));
for (const toolLine of toolSummary.lines.slice(0, 4)) {
push(renderBoxLine(padCell(toolLine, innerWidth)));
}
push(renderDivider());
push(renderBoxLine(theme.fg("accent", theme.bold(padCell("Slash Commands", innerWidth)))));
for (const commandLine of commandSummary.lines.slice(0, 4)) {
push(renderBoxLine(padCell(commandLine, innerWidth)));
}
push(renderDivider());
push(renderBoxLine(theme.fg("success", theme.bold(padCell("Research Skills", innerWidth)))));
for (const skillLine of skillSummary.lines.slice(0, 4)) {
push(renderBoxLine(padCell(skillLine, innerWidth)));
}
if (agentSummary.lines.length > 0) {
push(renderDivider());
push(renderBoxLine(theme.fg("warning", theme.bold(padCell("Project Agents", innerWidth)))));
for (const agentLine of agentSummary.lines.slice(0, 3)) {
push(renderBoxLine(padCell(agentLine, innerWidth)));
}
}
push(renderDivider());
push(renderBoxLine(theme.fg("accent", theme.bold(padCell("Recent activity", innerWidth)))));
for (const activityLine of activityLines.length > 0 ? activityLines : ["No messages yet in this session."]) {
push(renderBoxLine(padCell(activityLine, innerWidth)));
}
push(renderDivider());
push(
renderBoxLine(
padCell(
`${toolSummary.count} tools · ${commandSummary.count} commands · ${skillSummary.count} skills`,
innerWidth,
),
),
);
} else {
const leftWidth = Math.min(44, Math.max(30, Math.floor(innerWidth * 0.36)));
const rightWidth = innerWidth - leftWidth - 3;
const activityLines = wrapForWidth(recentActivity, innerWidth, 2);
const wrappedToolLines = toolSummary.lines.flatMap((line) => wrapForWidth(line, rightWidth, 3));
const wrappedCommandLines = commandSummary.lines.flatMap((line) => wrapForWidth(line, rightWidth, 4));
const wrappedSkillLines = skillSummary.lines.flatMap((line) => wrapForWidth(line, rightWidth, 4));
const wrappedAgentLines = agentSummary.lines.flatMap((line) => wrapForWidth(line, rightWidth, 4));
const wrappedModelLines = wrapForWidth(`model: ${modelLabel}`, leftWidth, 2);
const wrappedDirectoryLines = wrapForWidth(`directory: ${directoryLabel}`, leftWidth, 2);
const wrappedSessionLines = wrapForWidth(`session: ${sessionLabel}`, leftWidth, 2);
const wrappedFooterLines = wrapForWidth(
`${toolSummary.count} tools · ${commandSummary.count} commands · ${skillSummary.count} skills`,
leftWidth,
2,
);
const leftLines = [
...FEYNMAN_MARK_ART.map((line) => centerText(line, leftWidth)),
"",
centerText("Research shell ready", leftWidth),
"",
...wrappedModelLines,
...wrappedDirectoryLines,
...wrappedSessionLines,
"",
...wrappedFooterLines,
];
const rightLines = [
"Available Tools",
...wrappedToolLines,
"",
"Slash Commands",
...wrappedCommandLines,
"",
"Research Skills",
...wrappedSkillLines,
...(wrappedAgentLines.length > 0 ? ["", "Project Agents", ...wrappedAgentLines] : []),
"",
"Recent Activity",
...(activityLines.length > 0 ? activityLines : ["No messages yet in this session."]),
];
push(renderBoxLine(padCell("", innerWidth)));
for (let index = 0; index < Math.max(leftLines.length, rightLines.length); index += 1) {
const left = leftLines[index] ?? "";
const right = rightLines[index] ?? "";
const isLogoLine = index < FEYNMAN_MARK_ART.length;
const isRightSectionHeading =
right === "Available Tools" || right === "Slash Commands" || right === "Research Skills" || right === "Project Agents" ||
right === "Recent Activity";
const isResearchHeading = right === "Research Skills";
const isAgentHeading = right === "Project Agents";
push(
(() => {
const leftCell = isLogoLine
? styleAccentCell(left, leftWidth)
: index >= FEYNMAN_MARK_ART.length + 2
? styleMutedCell(left, leftWidth)
: padCell(left, leftWidth);
const rightCell = isResearchHeading
? styleSuccessCell(right, rightWidth)
: isAgentHeading
? styleWarningCell(right, rightWidth)
: isRightSectionHeading
? styleAccentCell(right, rightWidth)
: right.length > 0
? styleMutedCell(right, rightWidth)
: padCell(right, rightWidth);
return renderBoxLine(`${leftCell}${theme.fg("borderMuted", " │ ")}${rightCell}`);
})(),
);
}
}
push(theme.fg("borderMuted", `${"─".repeat(innerWidth)}`));
push("");
return lines;
},
invalidate() {},
})); }));
} }
@@ -1028,31 +973,6 @@ export default function researchTools(pi: ExtensionAPI): void {
}, },
}); });
pi.registerCommand("help", {
description: "Show grouped Feynman commands and prefill the editor with a selected command.",
handler: async (_args, ctx) => {
const sections = buildFeynmanHelpSections(pi);
const items = sections.flatMap((section) => [
`--- ${section.title} ---`,
...section.commands.map((command) => `${command.usage}${command.description}`),
]).filter((item, index, array) => {
if (!item.startsWith("---")) {
return true;
}
return array[index + 1] !== undefined && !array[index + 1].startsWith("---");
});
const selected = await ctx.ui.select("Feynman Help", items);
if (!selected || selected.startsWith("---")) {
return;
}
const usage = selected.split(" — ")[0];
ctx.ui.setEditorText(usage);
ctx.ui.notify(`Prefilled ${usage}`, "info");
},
});
pi.registerCommand("init", { pi.registerCommand("init", {
description: "Initialize AGENTS.md and session-log folders for a research project.", description: "Initialize AGENTS.md and session-log folders for a research project.",
handler: async (_args, ctx) => { handler: async (_args, ctx) => {

68
package-lock.json generated
View File

@@ -10,21 +10,21 @@
"hasInstallScript": true, "hasInstallScript": true,
"dependencies": { "dependencies": {
"@companion-ai/alpha-hub": "^0.1.2", "@companion-ai/alpha-hub": "^0.1.2",
"@mariozechner/pi-ai": "^0.56.1", "@mariozechner/pi-ai": "^0.62.0",
"@mariozechner/pi-coding-agent": "^0.56.1", "@mariozechner/pi-coding-agent": "^0.62.0",
"@sinclair/typebox": "^0.34.41", "@sinclair/typebox": "^0.34.48",
"dotenv": "^16.4.7" "dotenv": "^17.3.1"
}, },
"bin": { "bin": {
"feynman": "bin/feynman.js" "feynman": "bin/feynman.js"
}, },
"devDependencies": { "devDependencies": {
"@types/node": "^24.3.0", "@types/node": "^25.5.0",
"tsx": "^4.20.5", "tsx": "^4.21.0",
"typescript": "^5.7.3" "typescript": "^5.9.3"
}, },
"engines": { "engines": {
"node": ">=20.6.0" "node": ">=20.18.1"
} }
}, },
"../alpha-hub/cli": { "../alpha-hub/cli": {
@@ -1441,21 +1441,21 @@
} }
}, },
"node_modules/@mariozechner/pi-agent-core": { "node_modules/@mariozechner/pi-agent-core": {
"version": "0.56.3", "version": "0.62.0",
"resolved": "https://registry.npmjs.org/@mariozechner/pi-agent-core/-/pi-agent-core-0.56.3.tgz", "resolved": "https://registry.npmjs.org/@mariozechner/pi-agent-core/-/pi-agent-core-0.62.0.tgz",
"integrity": "sha512-TsI1zENf3wqqKPaERnj486Q4i6Y/y6lAZipLNcfDYUDxDrLwNfQ9EW9xukkbJfTZ8zjG3VZ2pBZe3C7wM51dVQ==", "integrity": "sha512-SBjqgDrgKOaC+IGzFGB3jXQErv9H1QMYnWFvUg6ra6dG0ZgWFBUZb6unidngWLsmaxSDWes6KeKiVFMsr2VSEQ==",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@mariozechner/pi-ai": "^0.56.3" "@mariozechner/pi-ai": "^0.62.0"
}, },
"engines": { "engines": {
"node": ">=20.0.0" "node": ">=20.0.0"
} }
}, },
"node_modules/@mariozechner/pi-ai": { "node_modules/@mariozechner/pi-ai": {
"version": "0.56.3", "version": "0.62.0",
"resolved": "https://registry.npmjs.org/@mariozechner/pi-ai/-/pi-ai-0.56.3.tgz", "resolved": "https://registry.npmjs.org/@mariozechner/pi-ai/-/pi-ai-0.62.0.tgz",
"integrity": "sha512-l4J+cVyVeBLAlGOY/osGDvsbTz0DySCQmR171G6SdbPvIeLGhIi6siZ+zHwq91GJYjv/wtu/08M08ag2mGZKeA==", "integrity": "sha512-mJgryZ5RgBQG++tiETMtCQQJoH2MAhKetCfqI98NMvGydu7L9x2qC2JekQlRaAgIlTgv4MRH1UXHMEs4UweE/Q==",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@anthropic-ai/sdk": "^0.73.0", "@anthropic-ai/sdk": "^0.73.0",
@@ -1480,15 +1480,15 @@
} }
}, },
"node_modules/@mariozechner/pi-coding-agent": { "node_modules/@mariozechner/pi-coding-agent": {
"version": "0.56.3", "version": "0.62.0",
"resolved": "https://registry.npmjs.org/@mariozechner/pi-coding-agent/-/pi-coding-agent-0.56.3.tgz", "resolved": "https://registry.npmjs.org/@mariozechner/pi-coding-agent/-/pi-coding-agent-0.62.0.tgz",
"integrity": "sha512-yHgnadye+TT/4NWKBirZUjw/LWdNWTa7M4HJdX2RxRbwuj4q7RZ0Aqy+lQbOHEPDQYhxK3kZb9hjiAbbGficZQ==", "integrity": "sha512-f1NnExqsHuA6w8UVlBtPsvTBhdkMc0h1JD9SzGCdWTLou5GHJr2JIP6DlwV9IKWAnM+sAelaoFez+14wLP2zOQ==",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@mariozechner/jiti": "^2.6.2", "@mariozechner/jiti": "^2.6.2",
"@mariozechner/pi-agent-core": "^0.56.3", "@mariozechner/pi-agent-core": "^0.62.0",
"@mariozechner/pi-ai": "^0.56.3", "@mariozechner/pi-ai": "^0.62.0",
"@mariozechner/pi-tui": "^0.56.3", "@mariozechner/pi-tui": "^0.62.0",
"@silvia-odwyer/photon-node": "^0.3.4", "@silvia-odwyer/photon-node": "^0.3.4",
"chalk": "^5.5.0", "chalk": "^5.5.0",
"cli-highlight": "^2.1.11", "cli-highlight": "^2.1.11",
@@ -1516,9 +1516,9 @@
} }
}, },
"node_modules/@mariozechner/pi-tui": { "node_modules/@mariozechner/pi-tui": {
"version": "0.56.3", "version": "0.62.0",
"resolved": "https://registry.npmjs.org/@mariozechner/pi-tui/-/pi-tui-0.56.3.tgz", "resolved": "https://registry.npmjs.org/@mariozechner/pi-tui/-/pi-tui-0.62.0.tgz",
"integrity": "sha512-eZ1P9QRKHp78hwx+lITr/mujZqe+eCwL/bOS9vXXkFP070RW4VYum0j7TJ4BrFEH/nNkXRS1tYCXYU05une1bA==", "integrity": "sha512-/At11PPe8l319MnUoK4wN5L/uVCU6bDdiIUzH8Ez0stOkjSF6isRXScZ+RMM+6iCKsD4muBTX8Cmcif+3/UWHA==",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@types/mime-types": "^2.1.4", "@types/mime-types": "^2.1.4",
@@ -2307,12 +2307,12 @@
"license": "MIT" "license": "MIT"
}, },
"node_modules/@types/node": { "node_modules/@types/node": {
"version": "24.12.0", "version": "25.5.0",
"resolved": "https://registry.npmjs.org/@types/node/-/node-24.12.0.tgz", "resolved": "https://registry.npmjs.org/@types/node/-/node-25.5.0.tgz",
"integrity": "sha512-GYDxsZi3ChgmckRT9HPU0WEhKLP08ev/Yfcq2AstjrDASOYCSXeyjDsHg4v5t4jOj7cyDX3vmprafKlWIG9MXQ==", "integrity": "sha512-jp2P3tQMSxWugkCUKLRPVUpGaL5MVFwF8RDuSRztfwgN1wmqJeMSbKlnEtQqU8UrhTmzEmZdu2I6v2dpp7XIxw==",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"undici-types": "~7.16.0" "undici-types": "~7.18.0"
} }
}, },
"node_modules/@types/retry": { "node_modules/@types/retry": {
@@ -2647,9 +2647,9 @@
} }
}, },
"node_modules/dotenv": { "node_modules/dotenv": {
"version": "16.6.1", "version": "17.3.1",
"resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.6.1.tgz", "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-17.3.1.tgz",
"integrity": "sha512-uBq4egWHTcTt33a72vpSG0z3HnPuIl6NqYcTrKEg2azoEyl2hpW0zqlxysq2pK9HlDIHyHyakeYaYnSAwd8bow==", "integrity": "sha512-IO8C/dzEb6O3F9/twg6ZLXz164a2fhTnEWb95H23Dm4OuN+92NmEAlTrupP9VW6Jm3sO26tQlqyvyi4CsnY9GA==",
"license": "BSD-2-Clause", "license": "BSD-2-Clause",
"engines": { "engines": {
"node": ">=12" "node": ">=12"
@@ -3947,9 +3947,9 @@
} }
}, },
"node_modules/undici-types": { "node_modules/undici-types": {
"version": "7.16.0", "version": "7.18.2",
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.18.2.tgz",
"integrity": "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==", "integrity": "sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w==",
"license": "MIT" "license": "MIT"
}, },
"node_modules/web-streams-polyfill": { "node_modules/web-streams-polyfill": {

View File

@@ -47,18 +47,18 @@
}, },
"dependencies": { "dependencies": {
"@companion-ai/alpha-hub": "^0.1.2", "@companion-ai/alpha-hub": "^0.1.2",
"@mariozechner/pi-ai": "^0.56.1", "@mariozechner/pi-ai": "^0.62.0",
"@mariozechner/pi-coding-agent": "^0.56.1", "@mariozechner/pi-coding-agent": "^0.62.0",
"@sinclair/typebox": "^0.34.41", "@sinclair/typebox": "^0.34.48",
"dotenv": "^16.4.7" "dotenv": "^17.3.1"
}, },
"devDependencies": { "devDependencies": {
"@types/node": "^24.3.0", "@types/node": "^25.5.0",
"tsx": "^4.20.5", "tsx": "^4.21.0",
"typescript": "^5.7.3" "typescript": "^5.9.3"
}, },
"engines": { "engines": {
"node": ">=20.6.0" "node": ">=20.18.1"
}, },
"repository": { "repository": {
"type": "git", "type": "git",

View File

@@ -8,6 +8,7 @@ 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 bunCliPath = resolve(piPackageRoot, "dist", "bun", "cli.js");
const interactiveModePath = resolve(piPackageRoot, "dist", "modes", "interactive", "interactive-mode.js"); const interactiveModePath = resolve(piPackageRoot, "dist", "modes", "interactive", "interactive-mode.js");
const interactiveThemePath = resolve(piPackageRoot, "dist", "modes", "interactive", "theme", "theme.js"); const interactiveThemePath = resolve(piPackageRoot, "dist", "modes", "interactive", "theme", "theme.js");
const piTuiRoot = resolve(appRoot, "node_modules", "@mariozechner", "pi-tui"); const piTuiRoot = resolve(appRoot, "node_modules", "@mariozechner", "pi-tui");
@@ -85,10 +86,14 @@ if (existsSync(packageJsonPath)) {
} }
} }
if (existsSync(cliPath)) { for (const entryPath of [cliPath, bunCliPath]) {
const cliSource = readFileSync(cliPath, "utf8"); if (!existsSync(entryPath)) {
continue;
}
const cliSource = readFileSync(entryPath, "utf8");
if (cliSource.includes('process.title = "pi";')) { if (cliSource.includes('process.title = "pi";')) {
writeFileSync(cliPath, cliSource.replace('process.title = "pi";', 'process.title = "feynman";'), "utf8"); writeFileSync(entryPath, cliSource.replace('process.title = "pi";', 'process.title = "feynman";'), "utf8");
} }
} }
@@ -112,7 +117,7 @@ if (existsSync(interactiveThemePath)) {
" return {", " return {",
' borderColor: (text) => " ".repeat(text.length),', ' borderColor: (text) => " ".repeat(text.length),',
' bgColor: (text) => theme.bg("userMessageBg", text),', ' bgColor: (text) => theme.bg("userMessageBg", text),',
' placeholderText: "Type your message or /help for commands",', ' placeholderText: "Type your message",',
' placeholder: (text) => theme.fg("dim", text),', ' placeholder: (text) => theme.fg("dim", text),',
" selectList: getSelectListTheme(),", " selectList: getSelectListTheme(),",
" };", " };",
@@ -127,9 +132,11 @@ if (existsSync(interactiveThemePath)) {
if (existsSync(editorPath)) { if (existsSync(editorPath)) {
let editorSource = readFileSync(editorPath, "utf8"); let editorSource = readFileSync(editorPath, "utf8");
const importOriginal = 'import { getSegmenter, isPunctuationChar, isWhitespaceChar, visibleWidth } from "../utils.js";'; const importOriginal =
const importReplacement = 'import { applyBackgroundToLine, getSegmenter, isPunctuationChar, isWhitespaceChar, visibleWidth } from "../utils.js";'; 'import { getSegmenter, isPunctuationChar, isWhitespaceChar, truncateToWidth, visibleWidth } from "../utils.js";';
if (!editorSource.includes("applyBackgroundToLine") && editorSource.includes(importOriginal)) { const importReplacement =
'import { applyBackgroundToLine, getSegmenter, isPunctuationChar, isWhitespaceChar, truncateToWidth, visibleWidth } from "../utils.js";';
if (editorSource.includes(importOriginal)) {
editorSource = editorSource.replace(importOriginal, importReplacement); editorSource = editorSource.replace(importOriginal, importReplacement);
} }
const desiredRender = [ const desiredRender = [
@@ -168,6 +175,13 @@ if (existsSync(editorPath)) {
" const result = [];", " const result = [];",
' const leftPadding = " ".repeat(paddingX);', ' const leftPadding = " ".repeat(paddingX);',
" const rightPadding = leftPadding;", " const rightPadding = leftPadding;",
" const renderBorderLine = (indicator) => {",
" const remaining = width - visibleWidth(indicator);",
" if (remaining >= 0) {",
' return this.borderColor(indicator + "─".repeat(remaining));',
" }",
" return this.borderColor(truncateToWidth(indicator, width));",
" };",
" // Render top padding row. When background fill is active, mimic the user-message block", " // Render top padding row. When background fill is active, mimic the user-message block",
" // instead of the stock editor chrome.", " // instead of the stock editor chrome.",
" if (bgColor) {", " if (bgColor) {",
@@ -181,8 +195,7 @@ if (existsSync(editorPath)) {
" }", " }",
" else if (this.scrollOffset > 0) {", " else if (this.scrollOffset > 0) {",
" const indicator = `─── ↑ ${this.scrollOffset} more `;", " const indicator = `─── ↑ ${this.scrollOffset} more `;",
" const remaining = width - visibleWidth(indicator);", " result.push(renderBorderLine(indicator));",
' result.push(this.borderColor(indicator + "─".repeat(Math.max(0, remaining))));',
" }", " }",
" else {", " else {",
" result.push(horizontal.repeat(width));", " result.push(horizontal.repeat(width));",
@@ -205,7 +218,7 @@ if (existsSync(editorPath)) {
" if (isPlaceholderLine) {", " if (isPlaceholderLine) {",
" const marker = emitCursorMarker ? CURSOR_MARKER : \"\";", " const marker = emitCursorMarker ? CURSOR_MARKER : \"\";",
" const rawPlaceholder = this.theme.placeholderText;", " const rawPlaceholder = this.theme.placeholderText;",
" const graphemes = [...segmenter.segment(rawPlaceholder)];", " const graphemes = [...this.segment(rawPlaceholder)];",
' const firstGrapheme = graphemes[0]?.segment ?? " ";', ' const firstGrapheme = graphemes[0]?.segment ?? " ";',
" const restRaw = rawPlaceholder.slice(firstGrapheme.length);", " const restRaw = rawPlaceholder.slice(firstGrapheme.length);",
' const restStyled = typeof this.theme.placeholder === "function"', ' const restStyled = typeof this.theme.placeholder === "function"',
@@ -222,7 +235,7 @@ if (existsSync(editorPath)) {
" if (after.length > 0) {", " if (after.length > 0) {",
" // Cursor is on a character (grapheme) - replace it with highlighted version", " // Cursor is on a character (grapheme) - replace it with highlighted version",
" // Get the first grapheme from 'after'", " // Get the first grapheme from 'after'",
" const afterGraphemes = [...segmenter.segment(after)];", " const afterGraphemes = [...this.segment(after)];",
' const firstGrapheme = afterGraphemes[0]?.segment || "";', ' const firstGrapheme = afterGraphemes[0]?.segment || "";',
" const restAfter = after.slice(firstGrapheme.length);", " const restAfter = after.slice(firstGrapheme.length);",
' const cursor = `\\x1b[7m${firstGrapheme}\\x1b[27m`;', ' const cursor = `\\x1b[7m${firstGrapheme}\\x1b[27m`;',
@@ -260,8 +273,7 @@ if (existsSync(editorPath)) {
" }", " }",
" else if (linesBelow > 0) {", " else if (linesBelow > 0) {",
" const indicator = `─── ↓ ${linesBelow} more `;", " const indicator = `─── ↓ ${linesBelow} more `;",
" const remaining = width - visibleWidth(indicator);", " const bottomLine = renderBorderLine(indicator);",
' const bottomLine = this.borderColor(indicator + "─".repeat(Math.max(0, remaining)));',
" result.push(bottomLine);", " result.push(bottomLine);",
" }", " }",
" else {", " else {",

View File

@@ -105,7 +105,7 @@ function printHelp(): void {
printInfo("--alpha-logout Clear alphaXiv auth and exit"); printInfo("--alpha-logout Clear alphaXiv auth and exit");
printInfo("--alpha-status Show alphaXiv auth status and exit"); printInfo("--alpha-status Show alphaXiv auth status and exit");
printInfo("--model provider:model Force a specific model"); printInfo("--model provider:model Force a specific model");
printInfo("--thinking level off | low | medium | high"); printInfo("--thinking level off | minimal | low | medium | high | xhigh");
printInfo("--cwd /path/to/workdir Working directory for tools"); printInfo("--cwd /path/to/workdir Working directory for tools");
printInfo("--session-dir /path Session storage directory"); printInfo("--session-dir /path Session storage directory");
printInfo("--doctor Alias for `feynman doctor`"); printInfo("--doctor Alias for `feynman doctor`");
@@ -113,7 +113,6 @@ function printHelp(): void {
printSection("REPL"); printSection("REPL");
printInfo("Inside the REPL, slash workflows come from the live prompt-template and extension command set."); printInfo("Inside the REPL, slash workflows come from the live prompt-template and extension command set.");
printInfo("Use `/help` in chat to browse the commands actually loaded in this session.");
} }
async function handleAlphaCommand(action: string | undefined): Promise<void> { async function handleAlphaCommand(action: string | undefined): Promise<void> {

View File

@@ -3,7 +3,7 @@ import { dirname } from "node:path";
import { AuthStorage, ModelRegistry } from "@mariozechner/pi-coding-agent"; import { AuthStorage, ModelRegistry } from "@mariozechner/pi-coding-agent";
export type ThinkingLevel = "off" | "low" | "medium" | "high"; export type ThinkingLevel = "off" | "minimal" | "low" | "medium" | "high" | "xhigh";
export function parseModelSpec(spec: string, modelRegistry: ModelRegistry) { export function parseModelSpec(spec: string, modelRegistry: ModelRegistry) {
const trimmed = spec.trim(); const trimmed = spec.trim();
@@ -27,7 +27,14 @@ export function normalizeThinkingLevel(value: string | undefined): ThinkingLevel
} }
const normalized = value.toLowerCase(); const normalized = value.toLowerCase();
if (normalized === "off" || normalized === "low" || normalized === "medium" || normalized === "high") { if (
normalized === "off" ||
normalized === "minimal" ||
normalized === "low" ||
normalized === "medium" ||
normalized === "high" ||
normalized === "xhigh"
) {
return normalized; return normalized;
} }

18
tests/pi-settings.test.ts Normal file
View File

@@ -0,0 +1,18 @@
import assert from "node:assert/strict";
import test from "node:test";
import { normalizeThinkingLevel } from "../src/pi/settings.js";
test("normalizeThinkingLevel accepts the latest Pi thinking levels", () => {
assert.equal(normalizeThinkingLevel("off"), "off");
assert.equal(normalizeThinkingLevel("minimal"), "minimal");
assert.equal(normalizeThinkingLevel("low"), "low");
assert.equal(normalizeThinkingLevel("medium"), "medium");
assert.equal(normalizeThinkingLevel("high"), "high");
assert.equal(normalizeThinkingLevel("xhigh"), "xhigh");
});
test("normalizeThinkingLevel rejects unknown values", () => {
assert.equal(normalizeThinkingLevel("turbo"), undefined);
assert.equal(normalizeThinkingLevel(undefined), undefined);
});