@companion-ai/feynman 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.env.example +8 -0
- package/.feynman/SYSTEM.md +62 -0
- package/.feynman/agents/researcher.md +63 -0
- package/.feynman/agents/reviewer.md +84 -0
- package/.feynman/agents/verifier.md +38 -0
- package/.feynman/agents/writer.md +51 -0
- package/.feynman/settings.json +20 -0
- package/.feynman/themes/feynman.json +85 -0
- package/AGENTS.md +53 -0
- package/README.md +99 -0
- package/bin/feynman.js +2 -0
- package/dist/bootstrap/sync.js +98 -0
- package/dist/cli.js +297 -0
- package/dist/config/commands.js +71 -0
- package/dist/config/feynman-config.js +42 -0
- package/dist/config/paths.js +32 -0
- package/dist/feynman-prompt.js +63 -0
- package/dist/index.js +5 -0
- package/dist/model/catalog.js +238 -0
- package/dist/model/commands.js +165 -0
- package/dist/pi/launch.js +31 -0
- package/dist/pi/runtime.js +70 -0
- package/dist/pi/settings.js +101 -0
- package/dist/pi/web-access.js +74 -0
- package/dist/search/commands.js +12 -0
- package/dist/setup/doctor.js +126 -0
- package/dist/setup/preview.js +20 -0
- package/dist/setup/prompts.js +29 -0
- package/dist/setup/setup.js +119 -0
- package/dist/system/executables.js +38 -0
- package/dist/system/promise-polyfill.js +12 -0
- package/dist/ui/terminal.js +53 -0
- package/dist/web-search.js +1 -0
- package/extensions/research-tools/alpha.ts +212 -0
- package/extensions/research-tools/header.ts +379 -0
- package/extensions/research-tools/help.ts +93 -0
- package/extensions/research-tools/preview.ts +233 -0
- package/extensions/research-tools/project.ts +116 -0
- package/extensions/research-tools/session-search.ts +223 -0
- package/extensions/research-tools/shared.ts +46 -0
- package/extensions/research-tools.ts +25 -0
- package/metadata/commands.d.mts +46 -0
- package/metadata/commands.mjs +133 -0
- package/package.json +71 -0
- package/prompts/audit.md +15 -0
- package/prompts/autoresearch.md +63 -0
- package/prompts/compare.md +16 -0
- package/prompts/deepresearch.md +167 -0
- package/prompts/delegate.md +21 -0
- package/prompts/draft.md +16 -0
- package/prompts/jobs.md +16 -0
- package/prompts/lit.md +16 -0
- package/prompts/log.md +14 -0
- package/prompts/replicate.md +22 -0
- package/prompts/review.md +15 -0
- package/prompts/watch.md +14 -0
- package/scripts/patch-embedded-pi.mjs +319 -0
- package/skills/agentcomputer/SKILL.md +108 -0
- package/skills/agentcomputer/references/acp-flow.md +23 -0
- package/skills/agentcomputer/references/cli-cheatsheet.md +68 -0
- package/skills/autoresearch/SKILL.md +12 -0
- package/skills/deep-research/SKILL.md +12 -0
- package/skills/docker/SKILL.md +84 -0
- package/skills/jobs/SKILL.md +10 -0
- package/skills/literature-review/SKILL.md +12 -0
- package/skills/paper-code-audit/SKILL.md +12 -0
- package/skills/paper-writing/SKILL.md +12 -0
- package/skills/peer-review/SKILL.md +12 -0
- package/skills/replication/SKILL.md +14 -0
- package/skills/session-log/SKILL.md +10 -0
- package/skills/source-comparison/SKILL.md +12 -0
- package/skills/watch/SKILL.md +12 -0
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { getPiWebAccessStatus } from "../pi/web-access.js";
|
|
2
|
+
import { printInfo } from "../ui/terminal.js";
|
|
3
|
+
export function printSearchStatus() {
|
|
4
|
+
const status = getPiWebAccessStatus();
|
|
5
|
+
printInfo("Managed by: pi-web-access");
|
|
6
|
+
printInfo(`Search route: ${status.routeLabel}`);
|
|
7
|
+
printInfo(`Request route: ${status.requestProvider}`);
|
|
8
|
+
printInfo(`Perplexity API configured: ${status.perplexityConfigured ? "yes" : "no"}`);
|
|
9
|
+
printInfo(`Gemini API configured: ${status.geminiApiConfigured ? "yes" : "no"}`);
|
|
10
|
+
printInfo(`Browser profile: ${status.chromeProfile ?? "default Chromium profile"}`);
|
|
11
|
+
printInfo(`Config path: ${status.configPath}`);
|
|
12
|
+
}
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
import { AuthStorage, ModelRegistry } from "@mariozechner/pi-coding-agent";
|
|
2
|
+
import { getUserName as getAlphaUserName, isLoggedIn as isAlphaLoggedIn } from "@companion-ai/alpha-hub/lib";
|
|
3
|
+
import { formatPiWebAccessDoctorLines, getPiWebAccessStatus } from "../pi/web-access.js";
|
|
4
|
+
import { BROWSER_FALLBACK_PATHS, PANDOC_FALLBACK_PATHS, resolveExecutable } from "../system/executables.js";
|
|
5
|
+
import { readJson } from "../pi/settings.js";
|
|
6
|
+
import { validatePiInstallation } from "../pi/runtime.js";
|
|
7
|
+
import { printInfo, printPanel, printSection } from "../ui/terminal.js";
|
|
8
|
+
import { getCurrentModelSpec } from "../model/commands.js";
|
|
9
|
+
import { buildModelStatusSnapshotFromRecords, getAvailableModelRecords, getSupportedModelRecords } from "../model/catalog.js";
|
|
10
|
+
export function collectStatusSnapshot(options) {
|
|
11
|
+
const pandocPath = resolveExecutable("pandoc", PANDOC_FALLBACK_PATHS);
|
|
12
|
+
const browserPath = process.env.PUPPETEER_EXECUTABLE_PATH ?? resolveExecutable("google-chrome", BROWSER_FALLBACK_PATHS);
|
|
13
|
+
const missingPiBits = validatePiInstallation(options.appRoot);
|
|
14
|
+
const webStatus = getPiWebAccessStatus();
|
|
15
|
+
const modelStatus = buildModelStatusSnapshotFromRecords(getSupportedModelRecords(options.authPath), getAvailableModelRecords(options.authPath), getCurrentModelSpec(options.settingsPath));
|
|
16
|
+
return {
|
|
17
|
+
model: modelStatus.current,
|
|
18
|
+
modelValid: modelStatus.currentValid,
|
|
19
|
+
recommendedModel: modelStatus.recommended,
|
|
20
|
+
recommendedModelReason: modelStatus.recommendationReason,
|
|
21
|
+
authenticatedModelCount: modelStatus.availableModels.length,
|
|
22
|
+
authenticatedProviderCount: modelStatus.providers.filter((provider) => provider.configured).length,
|
|
23
|
+
modelGuidance: modelStatus.guidance,
|
|
24
|
+
alphaLoggedIn: isAlphaLoggedIn(),
|
|
25
|
+
alphaUser: isAlphaLoggedIn() ? getAlphaUserName() ?? undefined : undefined,
|
|
26
|
+
webRouteLabel: webStatus.routeLabel,
|
|
27
|
+
previewConfigured: Boolean(pandocPath),
|
|
28
|
+
sessionDir: options.sessionDir,
|
|
29
|
+
pandocReady: Boolean(pandocPath),
|
|
30
|
+
browserReady: Boolean(browserPath),
|
|
31
|
+
piReady: missingPiBits.length === 0,
|
|
32
|
+
missingPiBits,
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
export function runStatus(options) {
|
|
36
|
+
const snapshot = collectStatusSnapshot(options);
|
|
37
|
+
printPanel("Feynman Status", [
|
|
38
|
+
"Current setup summary for the research shell.",
|
|
39
|
+
]);
|
|
40
|
+
printSection("Core");
|
|
41
|
+
printInfo(`Model: ${snapshot.model ?? "not configured"}`);
|
|
42
|
+
printInfo(`Model valid: ${snapshot.modelValid ? "yes" : "no"}`);
|
|
43
|
+
printInfo(`Authenticated models: ${snapshot.authenticatedModelCount}`);
|
|
44
|
+
printInfo(`Authenticated providers: ${snapshot.authenticatedProviderCount}`);
|
|
45
|
+
printInfo(`Recommended model: ${snapshot.recommendedModel ?? "not available"}`);
|
|
46
|
+
printInfo(`alphaXiv: ${snapshot.alphaLoggedIn ? snapshot.alphaUser ?? "configured" : "not configured"}`);
|
|
47
|
+
printInfo(`Web access: pi-web-access (${snapshot.webRouteLabel})`);
|
|
48
|
+
printInfo(`Preview: ${snapshot.previewConfigured ? "configured" : "not configured"}`);
|
|
49
|
+
printSection("Paths");
|
|
50
|
+
printInfo(`Sessions: ${snapshot.sessionDir}`);
|
|
51
|
+
printSection("Runtime");
|
|
52
|
+
printInfo(`Pi runtime: ${snapshot.piReady ? "ready" : "missing files"}`);
|
|
53
|
+
printInfo(`Pandoc: ${snapshot.pandocReady ? "ready" : "missing"}`);
|
|
54
|
+
printInfo(`Browser preview: ${snapshot.browserReady ? "ready" : "missing"}`);
|
|
55
|
+
if (snapshot.missingPiBits.length > 0) {
|
|
56
|
+
for (const entry of snapshot.missingPiBits) {
|
|
57
|
+
printInfo(` missing: ${entry}`);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
if (snapshot.modelGuidance.length > 0) {
|
|
61
|
+
printSection("Next Steps");
|
|
62
|
+
for (const line of snapshot.modelGuidance) {
|
|
63
|
+
printInfo(line);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
export function runDoctor(options) {
|
|
68
|
+
const settings = readJson(options.settingsPath);
|
|
69
|
+
const modelRegistry = new ModelRegistry(AuthStorage.create(options.authPath));
|
|
70
|
+
const availableModels = modelRegistry.getAvailable();
|
|
71
|
+
const pandocPath = resolveExecutable("pandoc", PANDOC_FALLBACK_PATHS);
|
|
72
|
+
const browserPath = process.env.PUPPETEER_EXECUTABLE_PATH ?? resolveExecutable("google-chrome", BROWSER_FALLBACK_PATHS);
|
|
73
|
+
const missingPiBits = validatePiInstallation(options.appRoot);
|
|
74
|
+
printPanel("Feynman Doctor", [
|
|
75
|
+
"Checks config, auth, runtime wiring, and preview dependencies.",
|
|
76
|
+
]);
|
|
77
|
+
console.log(`working dir: ${options.workingDir}`);
|
|
78
|
+
console.log(`session dir: ${options.sessionDir}`);
|
|
79
|
+
console.log("");
|
|
80
|
+
console.log(`alphaXiv auth: ${isAlphaLoggedIn() ? "ok" : "missing"}`);
|
|
81
|
+
if (isAlphaLoggedIn()) {
|
|
82
|
+
const name = getAlphaUserName();
|
|
83
|
+
if (name) {
|
|
84
|
+
console.log(` user: ${name}`);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
console.log(`models available: ${availableModels.length}`);
|
|
88
|
+
if (availableModels.length > 0) {
|
|
89
|
+
const sample = availableModels
|
|
90
|
+
.slice(0, 6)
|
|
91
|
+
.map((model) => `${model.provider}/${model.id}`)
|
|
92
|
+
.join(", ");
|
|
93
|
+
console.log(` sample: ${sample}`);
|
|
94
|
+
}
|
|
95
|
+
console.log(`default model: ${typeof settings.defaultProvider === "string" && typeof settings.defaultModel === "string"
|
|
96
|
+
? `${settings.defaultProvider}/${settings.defaultModel}`
|
|
97
|
+
: "not set"}`);
|
|
98
|
+
const modelStatus = collectStatusSnapshot(options);
|
|
99
|
+
console.log(`default model valid: ${modelStatus.modelValid ? "yes" : "no"}`);
|
|
100
|
+
console.log(`authenticated providers: ${modelStatus.authenticatedProviderCount}`);
|
|
101
|
+
console.log(`authenticated models: ${modelStatus.authenticatedModelCount}`);
|
|
102
|
+
console.log(`recommended model: ${modelStatus.recommendedModel ?? "not available"}`);
|
|
103
|
+
if (modelStatus.recommendedModelReason) {
|
|
104
|
+
console.log(` why: ${modelStatus.recommendedModelReason}`);
|
|
105
|
+
}
|
|
106
|
+
console.log(`pandoc: ${pandocPath ?? "missing"}`);
|
|
107
|
+
console.log(`browser preview runtime: ${browserPath ?? "missing"}`);
|
|
108
|
+
for (const line of formatPiWebAccessDoctorLines()) {
|
|
109
|
+
console.log(line);
|
|
110
|
+
}
|
|
111
|
+
console.log(`quiet startup: ${settings.quietStartup === true ? "enabled" : "disabled"}`);
|
|
112
|
+
console.log(`theme: ${typeof settings.theme === "string" ? settings.theme : "not set"}`);
|
|
113
|
+
if (missingPiBits.length > 0) {
|
|
114
|
+
console.log("pi runtime: missing files");
|
|
115
|
+
for (const entry of missingPiBits) {
|
|
116
|
+
console.log(` ${entry}`);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
else {
|
|
120
|
+
console.log("pi runtime: ok");
|
|
121
|
+
}
|
|
122
|
+
for (const line of modelStatus.modelGuidance) {
|
|
123
|
+
console.log(`next step: ${line}`);
|
|
124
|
+
}
|
|
125
|
+
console.log("setup hint: feynman setup");
|
|
126
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { spawnSync } from "node:child_process";
|
|
2
|
+
import { BREW_FALLBACK_PATHS, PANDOC_FALLBACK_PATHS, resolveExecutable } from "../system/executables.js";
|
|
3
|
+
export function setupPreviewDependencies() {
|
|
4
|
+
const pandocPath = resolveExecutable("pandoc", PANDOC_FALLBACK_PATHS);
|
|
5
|
+
if (pandocPath) {
|
|
6
|
+
return { status: "ready", message: `pandoc already installed at ${pandocPath}` };
|
|
7
|
+
}
|
|
8
|
+
const brewPath = resolveExecutable("brew", BREW_FALLBACK_PATHS);
|
|
9
|
+
if (process.platform === "darwin" && brewPath) {
|
|
10
|
+
const result = spawnSync(brewPath, ["install", "pandoc"], { stdio: "inherit" });
|
|
11
|
+
if (result.status !== 0) {
|
|
12
|
+
throw new Error("Failed to install pandoc via Homebrew.");
|
|
13
|
+
}
|
|
14
|
+
return { status: "installed", message: "Preview dependency installed: pandoc" };
|
|
15
|
+
}
|
|
16
|
+
return {
|
|
17
|
+
status: "manual",
|
|
18
|
+
message: "pandoc is required for preview support. Install it manually and rerun `feynman --doctor`.",
|
|
19
|
+
};
|
|
20
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { stdin as input, stdout as output } from "node:process";
|
|
2
|
+
import { createInterface } from "node:readline/promises";
|
|
3
|
+
export async function promptText(question, defaultValue = "") {
|
|
4
|
+
if (!input.isTTY || !output.isTTY) {
|
|
5
|
+
throw new Error("feynman setup requires an interactive terminal.");
|
|
6
|
+
}
|
|
7
|
+
const rl = createInterface({ input, output });
|
|
8
|
+
try {
|
|
9
|
+
const suffix = defaultValue ? ` [${defaultValue}]` : "";
|
|
10
|
+
const value = (await rl.question(`${question}${suffix}: `)).trim();
|
|
11
|
+
return value || defaultValue;
|
|
12
|
+
}
|
|
13
|
+
finally {
|
|
14
|
+
rl.close();
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
export async function promptChoice(question, choices, defaultIndex = 0) {
|
|
18
|
+
console.log(question);
|
|
19
|
+
for (const [index, choice] of choices.entries()) {
|
|
20
|
+
const marker = index === defaultIndex ? "*" : " ";
|
|
21
|
+
console.log(` ${marker} ${index + 1}. ${choice}`);
|
|
22
|
+
}
|
|
23
|
+
const answer = await promptText("Select", String(defaultIndex + 1));
|
|
24
|
+
const parsed = Number(answer);
|
|
25
|
+
if (!Number.isFinite(parsed) || parsed < 1 || parsed > choices.length) {
|
|
26
|
+
return defaultIndex;
|
|
27
|
+
}
|
|
28
|
+
return parsed - 1;
|
|
29
|
+
}
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
import { isLoggedIn as isAlphaLoggedIn, login as loginAlpha } from "@companion-ai/alpha-hub/lib";
|
|
2
|
+
import { getDefaultSessionDir, getFeynmanHome } from "../config/paths.js";
|
|
3
|
+
import { getPiWebAccessStatus, getPiWebSearchConfigPath } from "../pi/web-access.js";
|
|
4
|
+
import { normalizeFeynmanSettings } from "../pi/settings.js";
|
|
5
|
+
import { getCurrentModelSpec, runModelSetup } from "../model/commands.js";
|
|
6
|
+
import { buildModelStatusSnapshotFromRecords, getAvailableModelRecords, getSupportedModelRecords } from "../model/catalog.js";
|
|
7
|
+
import { PANDOC_FALLBACK_PATHS, resolveExecutable } from "../system/executables.js";
|
|
8
|
+
import { promptText } from "./prompts.js";
|
|
9
|
+
import { setupPreviewDependencies } from "./preview.js";
|
|
10
|
+
import { runDoctor } from "./doctor.js";
|
|
11
|
+
import { printInfo, printPanel, printSection, printSuccess } from "../ui/terminal.js";
|
|
12
|
+
async function explainWebAccess() {
|
|
13
|
+
const status = getPiWebAccessStatus();
|
|
14
|
+
printSection("Web Access");
|
|
15
|
+
printInfo("Feynman uses the bundled `pi-web-access` package directly.");
|
|
16
|
+
printInfo("Default v1 path: sign into gemini.google.com in a supported Chromium browser.");
|
|
17
|
+
printInfo(`Current search route: ${status.routeLabel}`);
|
|
18
|
+
printInfo(`Pi config path: ${status.configPath}`);
|
|
19
|
+
printInfo("Advanced users can edit the Pi config directly if they want API keys or a different route.");
|
|
20
|
+
}
|
|
21
|
+
function isPreviewConfigured() {
|
|
22
|
+
return Boolean(resolveExecutable("pandoc", PANDOC_FALLBACK_PATHS));
|
|
23
|
+
}
|
|
24
|
+
function isInteractiveTerminal() {
|
|
25
|
+
return Boolean(process.stdin.isTTY && process.stdout.isTTY);
|
|
26
|
+
}
|
|
27
|
+
function printNonInteractiveSetupGuidance() {
|
|
28
|
+
printPanel("Feynman Setup", [
|
|
29
|
+
"Non-interactive terminal detected.",
|
|
30
|
+
]);
|
|
31
|
+
printInfo("Use the explicit commands instead of the interactive setup wizard:");
|
|
32
|
+
printInfo(" feynman status");
|
|
33
|
+
printInfo(" feynman model login <provider>");
|
|
34
|
+
printInfo(" feynman model set <provider/model>");
|
|
35
|
+
printInfo(" feynman search status");
|
|
36
|
+
printInfo(` edit ${getPiWebSearchConfigPath()} # optional advanced web config`);
|
|
37
|
+
printInfo(" feynman alpha login");
|
|
38
|
+
printInfo(" feynman doctor");
|
|
39
|
+
printInfo(" feynman # Pi's /login flow still works inside chat if you prefer it");
|
|
40
|
+
}
|
|
41
|
+
async function runPreviewSetup() {
|
|
42
|
+
const result = setupPreviewDependencies();
|
|
43
|
+
printSuccess(result.message);
|
|
44
|
+
}
|
|
45
|
+
function printConfigurationLocation(appRoot) {
|
|
46
|
+
printSection("Configuration Location");
|
|
47
|
+
printInfo(`Data folder: ${getFeynmanHome()}`);
|
|
48
|
+
printInfo(`Sessions: ${getDefaultSessionDir()}`);
|
|
49
|
+
printInfo(`Install dir: ${appRoot}`);
|
|
50
|
+
}
|
|
51
|
+
function printSetupSummary(settingsPath, authPath) {
|
|
52
|
+
const modelStatus = buildModelStatusSnapshotFromRecords(getSupportedModelRecords(authPath), getAvailableModelRecords(authPath), getCurrentModelSpec(settingsPath));
|
|
53
|
+
printSection("Setup Summary");
|
|
54
|
+
printInfo(`Model: ${getCurrentModelSpec(settingsPath) ?? "not set"}`);
|
|
55
|
+
printInfo(`Model valid: ${modelStatus.currentValid ? "yes" : "no"}`);
|
|
56
|
+
printInfo(`Recommended model: ${modelStatus.recommended ?? "not available"}`);
|
|
57
|
+
printInfo(`alphaXiv: ${isAlphaLoggedIn() ? "configured" : "missing"}`);
|
|
58
|
+
printInfo(`Web access: pi-web-access (${getPiWebAccessStatus().routeLabel})`);
|
|
59
|
+
printInfo(`Preview: ${isPreviewConfigured() ? "configured" : "not configured"}`);
|
|
60
|
+
for (const line of modelStatus.guidance) {
|
|
61
|
+
printInfo(line);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
async function runFullSetup(options) {
|
|
65
|
+
printConfigurationLocation(options.appRoot);
|
|
66
|
+
await runModelSetup(options.settingsPath, options.authPath);
|
|
67
|
+
if (!isAlphaLoggedIn()) {
|
|
68
|
+
await loginAlpha();
|
|
69
|
+
printSuccess("alphaXiv login complete");
|
|
70
|
+
}
|
|
71
|
+
else {
|
|
72
|
+
printInfo("alphaXiv login already configured");
|
|
73
|
+
}
|
|
74
|
+
await explainWebAccess();
|
|
75
|
+
await runPreviewSetup();
|
|
76
|
+
normalizeFeynmanSettings(options.settingsPath, options.bundledSettingsPath, options.defaultThinkingLevel ?? "medium", options.authPath);
|
|
77
|
+
runDoctor({
|
|
78
|
+
settingsPath: options.settingsPath,
|
|
79
|
+
authPath: options.authPath,
|
|
80
|
+
sessionDir: options.sessionDir,
|
|
81
|
+
workingDir: options.workingDir,
|
|
82
|
+
appRoot: options.appRoot,
|
|
83
|
+
});
|
|
84
|
+
printSetupSummary(options.settingsPath, options.authPath);
|
|
85
|
+
}
|
|
86
|
+
function hasExistingSetup(settingsPath, authPath) {
|
|
87
|
+
const modelStatus = buildModelStatusSnapshotFromRecords(getSupportedModelRecords(authPath), getAvailableModelRecords(authPath), getCurrentModelSpec(settingsPath));
|
|
88
|
+
return Boolean(modelStatus.current ||
|
|
89
|
+
modelStatus.availableModels.length > 0 ||
|
|
90
|
+
isAlphaLoggedIn() ||
|
|
91
|
+
isPreviewConfigured());
|
|
92
|
+
}
|
|
93
|
+
async function runDefaultInteractiveSetup(options) {
|
|
94
|
+
const existing = hasExistingSetup(options.settingsPath, options.authPath);
|
|
95
|
+
printPanel("Feynman Setup Wizard", [
|
|
96
|
+
"Guided setup for the research-first Pi agent.",
|
|
97
|
+
"Press Ctrl+C at any time to exit.",
|
|
98
|
+
]);
|
|
99
|
+
if (existing) {
|
|
100
|
+
printSection("Full Setup");
|
|
101
|
+
printInfo("Existing configuration detected. Rerunning the full guided setup.");
|
|
102
|
+
}
|
|
103
|
+
else {
|
|
104
|
+
printInfo("We'll walk you through:");
|
|
105
|
+
printInfo(" 1. Model Selection");
|
|
106
|
+
printInfo(" 2. alphaXiv Login");
|
|
107
|
+
printInfo(" 3. Preview Dependencies");
|
|
108
|
+
}
|
|
109
|
+
printInfo("Press Enter to begin, or Ctrl+C to exit.");
|
|
110
|
+
await promptText("Press Enter to start");
|
|
111
|
+
await runFullSetup(options);
|
|
112
|
+
}
|
|
113
|
+
export async function runSetup(options) {
|
|
114
|
+
if (!isInteractiveTerminal()) {
|
|
115
|
+
printNonInteractiveSetupGuidance();
|
|
116
|
+
return;
|
|
117
|
+
}
|
|
118
|
+
await runDefaultInteractiveSetup(options);
|
|
119
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { spawnSync } from "node:child_process";
|
|
2
|
+
import { existsSync } from "node:fs";
|
|
3
|
+
export const PANDOC_FALLBACK_PATHS = [
|
|
4
|
+
"/opt/homebrew/bin/pandoc",
|
|
5
|
+
"/usr/local/bin/pandoc",
|
|
6
|
+
];
|
|
7
|
+
export const BREW_FALLBACK_PATHS = [
|
|
8
|
+
"/opt/homebrew/bin/brew",
|
|
9
|
+
"/usr/local/bin/brew",
|
|
10
|
+
];
|
|
11
|
+
export const BROWSER_FALLBACK_PATHS = [
|
|
12
|
+
"/Applications/Google Chrome.app/Contents/MacOS/Google Chrome",
|
|
13
|
+
"/Applications/Chromium.app/Contents/MacOS/Chromium",
|
|
14
|
+
"/Applications/Brave Browser.app/Contents/MacOS/Brave Browser",
|
|
15
|
+
"/Applications/Microsoft Edge.app/Contents/MacOS/Microsoft Edge",
|
|
16
|
+
];
|
|
17
|
+
export const MERMAID_FALLBACK_PATHS = [
|
|
18
|
+
"/opt/homebrew/bin/mmdc",
|
|
19
|
+
"/usr/local/bin/mmdc",
|
|
20
|
+
];
|
|
21
|
+
export function resolveExecutable(name, fallbackPaths = []) {
|
|
22
|
+
for (const candidate of fallbackPaths) {
|
|
23
|
+
if (existsSync(candidate)) {
|
|
24
|
+
return candidate;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
const result = spawnSync("sh", ["-lc", `command -v ${name}`], {
|
|
28
|
+
encoding: "utf8",
|
|
29
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
30
|
+
});
|
|
31
|
+
if (result.status === 0) {
|
|
32
|
+
const resolved = result.stdout.trim();
|
|
33
|
+
if (resolved) {
|
|
34
|
+
return resolved;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
return undefined;
|
|
38
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
if (typeof Promise.withResolvers !== "function") {
|
|
2
|
+
Promise.withResolvers = function withResolvers() {
|
|
3
|
+
let resolve;
|
|
4
|
+
let reject;
|
|
5
|
+
const promise = new Promise((res, rej) => {
|
|
6
|
+
resolve = res;
|
|
7
|
+
reject = rej;
|
|
8
|
+
});
|
|
9
|
+
return { promise, resolve, reject };
|
|
10
|
+
};
|
|
11
|
+
}
|
|
12
|
+
export {};
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
const RESET = "\x1b[0m";
|
|
2
|
+
const BOLD = "\x1b[1m";
|
|
3
|
+
const DIM = "\x1b[2m";
|
|
4
|
+
function rgb(red, green, blue) {
|
|
5
|
+
return `\x1b[38;2;${red};${green};${blue}m`;
|
|
6
|
+
}
|
|
7
|
+
// Match the outer CLI to the bundled Feynman Pi theme instead of generic magenta panels.
|
|
8
|
+
const INK = rgb(211, 198, 170);
|
|
9
|
+
const STONE = rgb(157, 169, 160);
|
|
10
|
+
const ASH = rgb(133, 146, 137);
|
|
11
|
+
const DARK_ASH = rgb(92, 106, 114);
|
|
12
|
+
const SAGE = rgb(167, 192, 128);
|
|
13
|
+
const TEAL = rgb(127, 187, 179);
|
|
14
|
+
const ROSE = rgb(230, 126, 128);
|
|
15
|
+
function paint(text, ...codes) {
|
|
16
|
+
return `${codes.join("")}${text}${RESET}`;
|
|
17
|
+
}
|
|
18
|
+
export function printInfo(text) {
|
|
19
|
+
console.log(paint(` ${text}`, ASH));
|
|
20
|
+
}
|
|
21
|
+
export function printSuccess(text) {
|
|
22
|
+
console.log(paint(`✓ ${text}`, SAGE, BOLD));
|
|
23
|
+
}
|
|
24
|
+
export function printWarning(text) {
|
|
25
|
+
console.log(paint(`⚠ ${text}`, STONE, BOLD));
|
|
26
|
+
}
|
|
27
|
+
export function printError(text) {
|
|
28
|
+
console.log(paint(`✗ ${text}`, ROSE, BOLD));
|
|
29
|
+
}
|
|
30
|
+
export function printSection(title) {
|
|
31
|
+
console.log("");
|
|
32
|
+
console.log(paint(`◆ ${title}`, TEAL, BOLD));
|
|
33
|
+
}
|
|
34
|
+
export function printPanel(title, subtitleLines = []) {
|
|
35
|
+
const inner = 53;
|
|
36
|
+
const border = "─".repeat(inner + 2);
|
|
37
|
+
const renderLine = (text, color, bold = false) => {
|
|
38
|
+
const content = text.length > inner ? `${text.slice(0, inner - 3)}...` : text;
|
|
39
|
+
const codes = bold ? `${color}${BOLD}` : color;
|
|
40
|
+
return `${DARK_ASH}${BOLD}│${RESET} ${codes}${content.padEnd(inner)}${RESET} ${DARK_ASH}${BOLD}│${RESET}`;
|
|
41
|
+
};
|
|
42
|
+
console.log("");
|
|
43
|
+
console.log(paint(`┌${border}┐`, DARK_ASH, BOLD));
|
|
44
|
+
console.log(renderLine(title, TEAL, true));
|
|
45
|
+
if (subtitleLines.length > 0) {
|
|
46
|
+
console.log(paint(`├${border}┤`, DARK_ASH, BOLD));
|
|
47
|
+
for (const line of subtitleLines) {
|
|
48
|
+
console.log(renderLine(line, INK));
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
console.log(paint(`└${border}┘`, DARK_ASH, BOLD));
|
|
52
|
+
console.log("");
|
|
53
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { formatPiWebAccessDoctorLines, getPiWebAccessStatus, getPiWebSearchConfigPath, loadPiWebAccessConfig, } from "./pi/web-access.js";
|
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
import {
|
|
2
|
+
annotatePaper,
|
|
3
|
+
askPaper,
|
|
4
|
+
clearPaperAnnotation,
|
|
5
|
+
disconnect,
|
|
6
|
+
getPaper,
|
|
7
|
+
getUserName as getAlphaUserName,
|
|
8
|
+
isLoggedIn as isAlphaLoggedIn,
|
|
9
|
+
listPaperAnnotations,
|
|
10
|
+
login as loginAlpha,
|
|
11
|
+
logout as logoutAlpha,
|
|
12
|
+
readPaperCode,
|
|
13
|
+
searchPapers,
|
|
14
|
+
} from "@companion-ai/alpha-hub/lib";
|
|
15
|
+
import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
|
|
16
|
+
import { Type } from "@sinclair/typebox";
|
|
17
|
+
|
|
18
|
+
import { getExtensionCommandSpec } from "../../metadata/commands.mjs";
|
|
19
|
+
import { formatToolText } from "./shared.js";
|
|
20
|
+
|
|
21
|
+
export function registerAlphaCommands(pi: ExtensionAPI): void {
|
|
22
|
+
pi.registerCommand("alpha-login", {
|
|
23
|
+
description: getExtensionCommandSpec("alpha-login")?.description ?? "Sign in to alphaXiv from inside Feynman.",
|
|
24
|
+
handler: async (_args, ctx) => {
|
|
25
|
+
if (isAlphaLoggedIn()) {
|
|
26
|
+
const name = getAlphaUserName();
|
|
27
|
+
ctx.ui.notify(name ? `alphaXiv already connected as ${name}` : "alphaXiv already connected", "info");
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
await loginAlpha();
|
|
32
|
+
const name = getAlphaUserName();
|
|
33
|
+
ctx.ui.notify(name ? `alphaXiv connected as ${name}` : "alphaXiv login complete", "info");
|
|
34
|
+
},
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
pi.registerCommand("alpha-logout", {
|
|
38
|
+
description: getExtensionCommandSpec("alpha-logout")?.description ?? "Clear alphaXiv auth from inside Feynman.",
|
|
39
|
+
handler: async (_args, ctx) => {
|
|
40
|
+
logoutAlpha();
|
|
41
|
+
ctx.ui.notify("alphaXiv auth cleared", "info");
|
|
42
|
+
},
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
pi.registerCommand("alpha-status", {
|
|
46
|
+
description: getExtensionCommandSpec("alpha-status")?.description ?? "Show alphaXiv authentication status.",
|
|
47
|
+
handler: async (_args, ctx) => {
|
|
48
|
+
if (!isAlphaLoggedIn()) {
|
|
49
|
+
ctx.ui.notify("alphaXiv not connected", "warning");
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const name = getAlphaUserName();
|
|
54
|
+
ctx.ui.notify(name ? `alphaXiv connected as ${name}` : "alphaXiv connected", "info");
|
|
55
|
+
},
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export function registerAlphaTools(pi: ExtensionAPI): void {
|
|
60
|
+
pi.registerTool({
|
|
61
|
+
name: "alpha_search",
|
|
62
|
+
label: "Alpha Search",
|
|
63
|
+
description: "Search papers through alphaXiv using semantic, keyword, both, agentic, or all retrieval modes.",
|
|
64
|
+
parameters: Type.Object({
|
|
65
|
+
query: Type.String({ description: "Paper search query." }),
|
|
66
|
+
mode: Type.Optional(
|
|
67
|
+
Type.String({
|
|
68
|
+
description: "Search mode: semantic, keyword, both, agentic, or all.",
|
|
69
|
+
}),
|
|
70
|
+
),
|
|
71
|
+
}),
|
|
72
|
+
async execute(_toolCallId, params) {
|
|
73
|
+
try {
|
|
74
|
+
const result = await searchPapers(params.query, params.mode?.trim() || "all");
|
|
75
|
+
return {
|
|
76
|
+
content: [{ type: "text", text: formatToolText(result) }],
|
|
77
|
+
details: result,
|
|
78
|
+
};
|
|
79
|
+
} finally {
|
|
80
|
+
await disconnect();
|
|
81
|
+
}
|
|
82
|
+
},
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
pi.registerTool({
|
|
86
|
+
name: "alpha_get_paper",
|
|
87
|
+
label: "Alpha Get Paper",
|
|
88
|
+
description: "Fetch a paper report or full text, plus any local annotation, using alphaXiv.",
|
|
89
|
+
parameters: Type.Object({
|
|
90
|
+
paper: Type.String({
|
|
91
|
+
description: "arXiv ID, arXiv URL, or alphaXiv URL.",
|
|
92
|
+
}),
|
|
93
|
+
fullText: Type.Optional(
|
|
94
|
+
Type.Boolean({
|
|
95
|
+
description: "Return raw full text instead of the AI report.",
|
|
96
|
+
}),
|
|
97
|
+
),
|
|
98
|
+
}),
|
|
99
|
+
async execute(_toolCallId, params) {
|
|
100
|
+
try {
|
|
101
|
+
const result = await getPaper(params.paper, { fullText: params.fullText });
|
|
102
|
+
return {
|
|
103
|
+
content: [{ type: "text", text: formatToolText(result) }],
|
|
104
|
+
details: result,
|
|
105
|
+
};
|
|
106
|
+
} finally {
|
|
107
|
+
await disconnect();
|
|
108
|
+
}
|
|
109
|
+
},
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
pi.registerTool({
|
|
113
|
+
name: "alpha_ask_paper",
|
|
114
|
+
label: "Alpha Ask Paper",
|
|
115
|
+
description: "Ask a targeted question about a paper using alphaXiv's PDF analysis.",
|
|
116
|
+
parameters: Type.Object({
|
|
117
|
+
paper: Type.String({
|
|
118
|
+
description: "arXiv ID, arXiv URL, or alphaXiv URL.",
|
|
119
|
+
}),
|
|
120
|
+
question: Type.String({
|
|
121
|
+
description: "Question to ask about the paper.",
|
|
122
|
+
}),
|
|
123
|
+
}),
|
|
124
|
+
async execute(_toolCallId, params) {
|
|
125
|
+
try {
|
|
126
|
+
const result = await askPaper(params.paper, params.question);
|
|
127
|
+
return {
|
|
128
|
+
content: [{ type: "text", text: formatToolText(result) }],
|
|
129
|
+
details: result,
|
|
130
|
+
};
|
|
131
|
+
} finally {
|
|
132
|
+
await disconnect();
|
|
133
|
+
}
|
|
134
|
+
},
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
pi.registerTool({
|
|
138
|
+
name: "alpha_annotate_paper",
|
|
139
|
+
label: "Alpha Annotate Paper",
|
|
140
|
+
description: "Write or clear a persistent local annotation for a paper.",
|
|
141
|
+
parameters: Type.Object({
|
|
142
|
+
paper: Type.String({
|
|
143
|
+
description: "Paper ID to annotate.",
|
|
144
|
+
}),
|
|
145
|
+
note: Type.Optional(
|
|
146
|
+
Type.String({
|
|
147
|
+
description: "Annotation text. Omit when clear=true.",
|
|
148
|
+
}),
|
|
149
|
+
),
|
|
150
|
+
clear: Type.Optional(
|
|
151
|
+
Type.Boolean({
|
|
152
|
+
description: "Clear the existing annotation instead of writing one.",
|
|
153
|
+
}),
|
|
154
|
+
),
|
|
155
|
+
}),
|
|
156
|
+
async execute(_toolCallId, params) {
|
|
157
|
+
const result = params.clear
|
|
158
|
+
? await clearPaperAnnotation(params.paper)
|
|
159
|
+
: params.note
|
|
160
|
+
? await annotatePaper(params.paper, params.note)
|
|
161
|
+
: (() => {
|
|
162
|
+
throw new Error("Provide either note or clear=true.");
|
|
163
|
+
})();
|
|
164
|
+
|
|
165
|
+
return {
|
|
166
|
+
content: [{ type: "text", text: formatToolText(result) }],
|
|
167
|
+
details: result,
|
|
168
|
+
};
|
|
169
|
+
},
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
pi.registerTool({
|
|
173
|
+
name: "alpha_list_annotations",
|
|
174
|
+
label: "Alpha List Annotations",
|
|
175
|
+
description: "List all persistent local paper annotations.",
|
|
176
|
+
parameters: Type.Object({}),
|
|
177
|
+
async execute() {
|
|
178
|
+
const result = await listPaperAnnotations();
|
|
179
|
+
return {
|
|
180
|
+
content: [{ type: "text", text: formatToolText(result) }],
|
|
181
|
+
details: result,
|
|
182
|
+
};
|
|
183
|
+
},
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
pi.registerTool({
|
|
187
|
+
name: "alpha_read_code",
|
|
188
|
+
label: "Alpha Read Code",
|
|
189
|
+
description: "Read files from a paper's GitHub repository through alphaXiv.",
|
|
190
|
+
parameters: Type.Object({
|
|
191
|
+
githubUrl: Type.String({
|
|
192
|
+
description: "GitHub repository URL for the paper implementation.",
|
|
193
|
+
}),
|
|
194
|
+
path: Type.Optional(
|
|
195
|
+
Type.String({
|
|
196
|
+
description: "Repository path to inspect. Use / for the repo overview.",
|
|
197
|
+
}),
|
|
198
|
+
),
|
|
199
|
+
}),
|
|
200
|
+
async execute(_toolCallId, params) {
|
|
201
|
+
try {
|
|
202
|
+
const result = await readPaperCode(params.githubUrl, params.path?.trim() || "/");
|
|
203
|
+
return {
|
|
204
|
+
content: [{ type: "text", text: formatToolText(result) }],
|
|
205
|
+
details: result,
|
|
206
|
+
};
|
|
207
|
+
} finally {
|
|
208
|
+
await disconnect();
|
|
209
|
+
}
|
|
210
|
+
},
|
|
211
|
+
});
|
|
212
|
+
}
|