Speed up install with --prefer-offline --no-audit --no-fund, fix postinstall path resolution
Install down from 60s to ~10s. Core packages only (11), heavy optional packages available via feynman packages install. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
72
src/cli.ts
72
src/cli.ts
@@ -16,6 +16,7 @@ import { AuthStorage, DefaultPackageManager, ModelRegistry, SettingsManager } fr
|
||||
import { syncBundledAssets } from "./bootstrap/sync.js";
|
||||
import { ensureFeynmanHome, getDefaultSessionDir, getFeynmanAgentDir, getFeynmanHome } from "./config/paths.js";
|
||||
import { launchPiChat } from "./pi/launch.js";
|
||||
import { CORE_PACKAGE_SOURCES, getOptionalPackagePresetSources, listOptionalPackagePresets } from "./pi/package-presets.js";
|
||||
import { normalizeFeynmanSettings, normalizeThinkingLevel, parseModelSpec } from "./pi/settings.js";
|
||||
import {
|
||||
loginModelProvider,
|
||||
@@ -166,6 +167,72 @@ async function handleUpdateCommand(workingDir: string, feynmanAgentDir: string,
|
||||
console.log("All packages up to date.");
|
||||
}
|
||||
|
||||
async function handlePackagesCommand(subcommand: string | undefined, args: string[], workingDir: string, feynmanAgentDir: string): Promise<void> {
|
||||
const settingsManager = SettingsManager.create(workingDir, feynmanAgentDir);
|
||||
const configuredSources = new Set(
|
||||
settingsManager
|
||||
.getPackages()
|
||||
.map((entry) => (typeof entry === "string" ? entry : entry.source))
|
||||
.filter((entry): entry is string => typeof entry === "string"),
|
||||
);
|
||||
|
||||
if (!subcommand || subcommand === "list") {
|
||||
printPanel("Feynman Packages", [
|
||||
"Core packages are installed by default to keep first-run setup fast.",
|
||||
]);
|
||||
printSection("Core");
|
||||
for (const source of CORE_PACKAGE_SOURCES) {
|
||||
printInfo(source);
|
||||
}
|
||||
printSection("Optional");
|
||||
for (const preset of listOptionalPackagePresets()) {
|
||||
const installed = preset.sources.every((source) => configuredSources.has(source));
|
||||
printInfo(`${preset.name}${installed ? " (installed)" : ""} ${preset.description}`);
|
||||
}
|
||||
printInfo("Install with: feynman packages install <preset>");
|
||||
return;
|
||||
}
|
||||
|
||||
if (subcommand !== "install") {
|
||||
throw new Error(`Unknown packages command: ${subcommand}`);
|
||||
}
|
||||
|
||||
const target = args[0];
|
||||
if (!target) {
|
||||
throw new Error("Usage: feynman packages install <generative-ui|memory|session-search|all-extras>");
|
||||
}
|
||||
|
||||
const sources = getOptionalPackagePresetSources(target);
|
||||
if (!sources) {
|
||||
throw new Error(`Unknown package preset: ${target}`);
|
||||
}
|
||||
|
||||
const packageManager = new DefaultPackageManager({
|
||||
cwd: workingDir,
|
||||
agentDir: feynmanAgentDir,
|
||||
settingsManager,
|
||||
});
|
||||
packageManager.setProgressCallback((event) => {
|
||||
if (event.type === "start") {
|
||||
console.log(`Installing ${event.source}...`);
|
||||
} else if (event.type === "complete") {
|
||||
console.log(`Installed ${event.source}`);
|
||||
} else if (event.type === "error") {
|
||||
console.error(`Failed to install ${event.source}: ${event.message ?? "unknown error"}`);
|
||||
}
|
||||
});
|
||||
|
||||
for (const source of sources) {
|
||||
if (configuredSources.has(source)) {
|
||||
console.log(`${source} already installed`);
|
||||
continue;
|
||||
}
|
||||
await packageManager.install(source);
|
||||
}
|
||||
await settingsManager.flush();
|
||||
console.log("Optional packages installed.");
|
||||
}
|
||||
|
||||
function handleSearchCommand(subcommand: string | undefined): void {
|
||||
if (!subcommand || subcommand === "status") {
|
||||
printSearchStatus();
|
||||
@@ -333,6 +400,11 @@ export async function main(): Promise<void> {
|
||||
return;
|
||||
}
|
||||
|
||||
if (command === "packages") {
|
||||
await handlePackagesCommand(rest[0], rest.slice(1), workingDir, feynmanAgentDir);
|
||||
return;
|
||||
}
|
||||
|
||||
if (command === "update") {
|
||||
await handleUpdateCommand(workingDir, feynmanAgentDir, rest[0]);
|
||||
return;
|
||||
|
||||
87
src/pi/package-presets.ts
Normal file
87
src/pi/package-presets.ts
Normal file
@@ -0,0 +1,87 @@
|
||||
import type { PackageSource } from "@mariozechner/pi-coding-agent";
|
||||
|
||||
export const CORE_PACKAGE_SOURCES = [
|
||||
"npm:pi-subagents",
|
||||
"npm:pi-btw",
|
||||
"npm:pi-docparser",
|
||||
"npm:pi-web-access",
|
||||
"npm:pi-markdown-preview",
|
||||
"npm:@walterra/pi-charts",
|
||||
"npm:pi-mermaid",
|
||||
"npm:@aliou/pi-processes",
|
||||
"npm:pi-zotero",
|
||||
"npm:pi-schedule-prompt",
|
||||
"npm:@tmustier/pi-ralph-wiggum",
|
||||
] as const;
|
||||
|
||||
export const OPTIONAL_PACKAGE_PRESETS = {
|
||||
"generative-ui": {
|
||||
description: "Interactive Glimpse UI widgets.",
|
||||
sources: ["npm:pi-generative-ui"],
|
||||
},
|
||||
memory: {
|
||||
description: "Cross-session memory and preference recall.",
|
||||
sources: ["npm:@samfp/pi-memory"],
|
||||
},
|
||||
"session-search": {
|
||||
description: "Indexed session recall with SQLite-backed search.",
|
||||
sources: ["npm:@kaiserlich-dev/pi-session-search"],
|
||||
},
|
||||
"all-extras": {
|
||||
description: "Install all optional packages.",
|
||||
sources: ["npm:pi-generative-ui", "npm:@samfp/pi-memory", "npm:@kaiserlich-dev/pi-session-search"],
|
||||
},
|
||||
} as const;
|
||||
|
||||
const LEGACY_DEFAULT_PACKAGE_SOURCES = [
|
||||
...CORE_PACKAGE_SOURCES,
|
||||
"npm:pi-generative-ui",
|
||||
"npm:@kaiserlich-dev/pi-session-search",
|
||||
"npm:@samfp/pi-memory",
|
||||
] as const;
|
||||
|
||||
export type OptionalPackagePresetName = keyof typeof OPTIONAL_PACKAGE_PRESETS;
|
||||
|
||||
function arraysMatchAsSets(left: readonly string[], right: readonly string[]): boolean {
|
||||
if (left.length !== right.length) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const rightSet = new Set(right);
|
||||
return left.every((entry) => rightSet.has(entry));
|
||||
}
|
||||
|
||||
export function shouldPruneLegacyDefaultPackages(packages: PackageSource[] | undefined): boolean {
|
||||
if (!Array.isArray(packages)) {
|
||||
return false;
|
||||
}
|
||||
if (packages.some((entry) => typeof entry !== "string")) {
|
||||
return false;
|
||||
}
|
||||
return arraysMatchAsSets(packages as string[], LEGACY_DEFAULT_PACKAGE_SOURCES);
|
||||
}
|
||||
|
||||
export function getOptionalPackagePresetSources(name: string): string[] | undefined {
|
||||
const normalized = name.trim().toLowerCase();
|
||||
if (normalized === "ui") {
|
||||
return [...OPTIONAL_PACKAGE_PRESETS["generative-ui"].sources];
|
||||
}
|
||||
if (normalized === "search") {
|
||||
return [...OPTIONAL_PACKAGE_PRESETS["session-search"].sources];
|
||||
}
|
||||
|
||||
const preset = OPTIONAL_PACKAGE_PRESETS[normalized as OptionalPackagePresetName];
|
||||
return preset ? [...preset.sources] : undefined;
|
||||
}
|
||||
|
||||
export function listOptionalPackagePresets(): Array<{
|
||||
name: OptionalPackagePresetName;
|
||||
description: string;
|
||||
sources: string[];
|
||||
}> {
|
||||
return Object.entries(OPTIONAL_PACKAGE_PRESETS).map(([name, preset]) => ({
|
||||
name: name as OptionalPackagePresetName,
|
||||
description: preset.description,
|
||||
sources: [...preset.sources],
|
||||
}));
|
||||
}
|
||||
@@ -1,7 +1,9 @@
|
||||
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
||||
import { dirname } from "node:path";
|
||||
|
||||
import { AuthStorage, ModelRegistry } from "@mariozechner/pi-coding-agent";
|
||||
import { AuthStorage, ModelRegistry, type PackageSource } from "@mariozechner/pi-coding-agent";
|
||||
|
||||
import { CORE_PACKAGE_SOURCES, shouldPruneLegacyDefaultPackages } from "./package-presets.js";
|
||||
|
||||
export type ThinkingLevel = "off" | "minimal" | "low" | "medium" | "high" | "xhigh";
|
||||
|
||||
@@ -107,6 +109,11 @@ export function normalizeFeynmanSettings(
|
||||
settings.theme = "feynman";
|
||||
settings.quietStartup = true;
|
||||
settings.collapseChangelog = true;
|
||||
if (!Array.isArray(settings.packages) || settings.packages.length === 0) {
|
||||
settings.packages = [...CORE_PACKAGE_SOURCES];
|
||||
} else if (shouldPruneLegacyDefaultPackages(settings.packages as PackageSource[])) {
|
||||
settings.packages = [...CORE_PACKAGE_SOURCES];
|
||||
}
|
||||
|
||||
const authStorage = AuthStorage.create(authPath);
|
||||
const modelRegistry = new ModelRegistry(authStorage);
|
||||
|
||||
Reference in New Issue
Block a user