mirror of
https://github.com/getcompanion-ai/co-mono.git
synced 2026-04-15 16:04:03 +00:00
feat(coding-agent): configurable app name and config dir for forks (#95)
- Add piConfig to package.json for app name and config directory - Consolidate paths.ts into config.ts with clearer naming - Fix Bun binary detection (changed from %7EBUN to $bunfs) - Update all hardcoded paths to use config.ts exports - getThemesDir() for built-in themes, getCustomThemesDir() for user themes
This commit is contained in:
parent
9d5fe1fe85
commit
e82fb0fc83
17 changed files with 241 additions and 167 deletions
|
|
@ -96,4 +96,4 @@ export function getNewEntries(entries: ChangelogEntry[], lastVersion: string): C
|
|||
}
|
||||
|
||||
// Re-export getChangelogPath from paths.ts for convenience
|
||||
export { getChangelogPath } from "./paths.js";
|
||||
export { getChangelogPath } from "./config.js";
|
||||
|
|
|
|||
131
packages/coding-agent/src/config.ts
Normal file
131
packages/coding-agent/src/config.ts
Normal file
|
|
@ -0,0 +1,131 @@
|
|||
import { existsSync, readFileSync } from "fs";
|
||||
import { homedir } from "os";
|
||||
import { dirname, join, resolve } from "path";
|
||||
import { fileURLToPath } from "url";
|
||||
|
||||
// =============================================================================
|
||||
// Package Detection
|
||||
// =============================================================================
|
||||
|
||||
const __filename = fileURLToPath(import.meta.url);
|
||||
const __dirname = dirname(__filename);
|
||||
|
||||
/**
|
||||
* Detect if we're running as a Bun compiled binary.
|
||||
* Bun binaries have import.meta.url containing "$bunfs" (Bun's virtual filesystem path)
|
||||
*/
|
||||
export const isBunBinary = import.meta.url.includes("$bunfs");
|
||||
|
||||
// =============================================================================
|
||||
// Package Asset Paths (shipped with executable)
|
||||
// =============================================================================
|
||||
|
||||
/**
|
||||
* Get the base directory for resolving package assets (themes, package.json, README.md, CHANGELOG.md).
|
||||
* - For Bun binary: returns the directory containing the executable
|
||||
* - For Node.js (dist/): returns __dirname (the dist/ directory)
|
||||
* - For tsx (src/): returns parent directory (the package root)
|
||||
*/
|
||||
export function getPackageDir(): string {
|
||||
if (isBunBinary) {
|
||||
// Bun binary: process.execPath points to the compiled executable
|
||||
return dirname(process.execPath);
|
||||
}
|
||||
// Node.js: check if package.json exists in __dirname (dist/) or parent (src/ case)
|
||||
if (existsSync(join(__dirname, "package.json"))) {
|
||||
return __dirname;
|
||||
}
|
||||
// Running from src/ via tsx - go up one level to package root
|
||||
return dirname(__dirname);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get path to built-in themes directory (shipped with package)
|
||||
* - For Bun binary: theme/ next to executable
|
||||
* - For Node.js (dist/): dist/theme/
|
||||
* - For tsx (src/): src/theme/
|
||||
*/
|
||||
export function getThemesDir(): string {
|
||||
if (isBunBinary) {
|
||||
return join(dirname(process.execPath), "theme");
|
||||
}
|
||||
// __dirname is either dist/ or src/ - theme is always a subdirectory
|
||||
return join(__dirname, "theme");
|
||||
}
|
||||
|
||||
/** Get path to package.json */
|
||||
export function getPackageJsonPath(): string {
|
||||
return join(getPackageDir(), "package.json");
|
||||
}
|
||||
|
||||
/** Get path to README.md */
|
||||
export function getReadmePath(): string {
|
||||
return resolve(join(getPackageDir(), "README.md"));
|
||||
}
|
||||
|
||||
/** Get path to CHANGELOG.md */
|
||||
export function getChangelogPath(): string {
|
||||
return resolve(join(getPackageDir(), "CHANGELOG.md"));
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// App Config (from package.json piConfig)
|
||||
// =============================================================================
|
||||
|
||||
const pkg = JSON.parse(readFileSync(getPackageJsonPath(), "utf-8"));
|
||||
|
||||
export const APP_NAME: string = pkg.piConfig?.name || "pi";
|
||||
export const CONFIG_DIR_NAME: string = pkg.piConfig?.configDir || ".pi";
|
||||
export const VERSION: string = pkg.version;
|
||||
|
||||
// e.g., PI_CODING_AGENT_DIR or TAU_CODING_AGENT_DIR
|
||||
export const ENV_AGENT_DIR = `${APP_NAME.toUpperCase()}_CODING_AGENT_DIR`;
|
||||
|
||||
// =============================================================================
|
||||
// User Config Paths (~/.pi/agent/*)
|
||||
// =============================================================================
|
||||
|
||||
/** Get the agent config directory (e.g., ~/.pi/agent/) */
|
||||
export function getAgentDir(): string {
|
||||
return process.env[ENV_AGENT_DIR] || join(homedir(), CONFIG_DIR_NAME, "agent");
|
||||
}
|
||||
|
||||
/** Get path to user's custom themes directory */
|
||||
export function getCustomThemesDir(): string {
|
||||
return join(getAgentDir(), "themes");
|
||||
}
|
||||
|
||||
/** Get path to models.json */
|
||||
export function getModelsPath(): string {
|
||||
return join(getAgentDir(), "models.json");
|
||||
}
|
||||
|
||||
/** Get path to oauth.json */
|
||||
export function getOAuthPath(): string {
|
||||
return join(getAgentDir(), "oauth.json");
|
||||
}
|
||||
|
||||
/** Get path to settings.json */
|
||||
export function getSettingsPath(): string {
|
||||
return join(getAgentDir(), "settings.json");
|
||||
}
|
||||
|
||||
/** Get path to tools directory */
|
||||
export function getToolsDir(): string {
|
||||
return join(getAgentDir(), "tools");
|
||||
}
|
||||
|
||||
/** Get path to slash commands directory */
|
||||
export function getCommandsDir(): string {
|
||||
return join(getAgentDir(), "commands");
|
||||
}
|
||||
|
||||
/** Get path to sessions directory */
|
||||
export function getSessionsDir(): string {
|
||||
return join(getAgentDir(), "sessions");
|
||||
}
|
||||
|
||||
/** Get path to debug log file */
|
||||
export function getDebugLogPath(): string {
|
||||
return join(getAgentDir(), `${APP_NAME}-debug.log`);
|
||||
}
|
||||
|
|
@ -3,13 +3,9 @@ import type { AssistantMessage, Message, ToolResultMessage, UserMessage } from "
|
|||
import { existsSync, readFileSync, writeFileSync } from "fs";
|
||||
import { homedir } from "os";
|
||||
import { basename } from "path";
|
||||
import { getPackageJsonPath } from "./paths.js";
|
||||
import { VERSION } from "./config.js";
|
||||
import type { SessionManager } from "./session-manager.js";
|
||||
|
||||
// Get version from package.json
|
||||
const packageJson = JSON.parse(readFileSync(getPackageJsonPath(), "utf-8"));
|
||||
const VERSION = packageJson.version;
|
||||
|
||||
/**
|
||||
* TUI Color scheme (matching exact RGB values from TUI components)
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -6,9 +6,17 @@ import { existsSync, readFileSync, statSync } from "fs";
|
|||
import { homedir } from "os";
|
||||
import { extname, join, resolve } from "path";
|
||||
import { getChangelogPath, getNewEntries, parseChangelog } from "./changelog.js";
|
||||
import {
|
||||
APP_NAME,
|
||||
CONFIG_DIR_NAME,
|
||||
ENV_AGENT_DIR,
|
||||
getAgentDir,
|
||||
getModelsPath,
|
||||
getReadmePath,
|
||||
VERSION,
|
||||
} from "./config.js";
|
||||
import { exportFromFile } from "./export-html.js";
|
||||
import { findModel, getApiKeyForModel, getAvailableModels } from "./model-config.js";
|
||||
import { getPackageJsonPath, getReadmePath } from "./paths.js";
|
||||
import { SessionManager } from "./session-manager.js";
|
||||
import { SettingsManager } from "./settings-manager.js";
|
||||
import { expandSlashCommand, loadSlashCommands } from "./slash-commands.js";
|
||||
|
|
@ -18,10 +26,6 @@ import { ensureTool } from "./tools-manager.js";
|
|||
import { SessionSelectorComponent } from "./tui/session-selector.js";
|
||||
import { TuiRenderer } from "./tui/tui-renderer.js";
|
||||
|
||||
// Get version from package.json
|
||||
const packageJson = JSON.parse(readFileSync(getPackageJsonPath(), "utf-8"));
|
||||
const VERSION = packageJson.version;
|
||||
|
||||
const defaultModelPerProvider: Record<KnownProvider, string> = {
|
||||
anthropic: "claude-sonnet-4-5",
|
||||
openai: "gpt-5.1-codex",
|
||||
|
|
@ -220,10 +224,10 @@ function processFileArguments(fileArgs: string[]): { textContent: string; imageA
|
|||
}
|
||||
|
||||
function printHelp() {
|
||||
console.log(`${chalk.bold("pi")} - AI coding assistant with read, bash, edit, write tools
|
||||
console.log(`${chalk.bold(APP_NAME)} - AI coding assistant with read, bash, edit, write tools
|
||||
|
||||
${chalk.bold("Usage:")}
|
||||
pi [options] [@files...] [messages...]
|
||||
${APP_NAME} [options] [@files...] [messages...]
|
||||
|
||||
${chalk.bold("Options:")}
|
||||
--provider <name> Provider name (default: google)
|
||||
|
|
@ -245,41 +249,41 @@ ${chalk.bold("Options:")}
|
|||
|
||||
${chalk.bold("Examples:")}
|
||||
# Interactive mode
|
||||
pi
|
||||
${APP_NAME}
|
||||
|
||||
# Interactive mode with initial prompt
|
||||
pi "List all .ts files in src/"
|
||||
${APP_NAME} "List all .ts files in src/"
|
||||
|
||||
# Include files in initial message
|
||||
pi @prompt.md @image.png "What color is the sky?"
|
||||
${APP_NAME} @prompt.md @image.png "What color is the sky?"
|
||||
|
||||
# Non-interactive mode (process and exit)
|
||||
pi -p "List all .ts files in src/"
|
||||
${APP_NAME} -p "List all .ts files in src/"
|
||||
|
||||
# Multiple messages (interactive)
|
||||
pi "Read package.json" "What dependencies do we have?"
|
||||
${APP_NAME} "Read package.json" "What dependencies do we have?"
|
||||
|
||||
# Continue previous session
|
||||
pi --continue "What did we discuss?"
|
||||
${APP_NAME} --continue "What did we discuss?"
|
||||
|
||||
# Use different model
|
||||
pi --provider openai --model gpt-4o-mini "Help me refactor this code"
|
||||
${APP_NAME} --provider openai --model gpt-4o-mini "Help me refactor this code"
|
||||
|
||||
# Limit model cycling to specific models
|
||||
pi --models claude-sonnet,claude-haiku,gpt-4o
|
||||
${APP_NAME} --models claude-sonnet,claude-haiku,gpt-4o
|
||||
|
||||
# Cycle models with fixed thinking levels
|
||||
pi --models sonnet:high,haiku:low
|
||||
${APP_NAME} --models sonnet:high,haiku:low
|
||||
|
||||
# Start with a specific thinking level
|
||||
pi --thinking high "Solve this complex problem"
|
||||
${APP_NAME} --thinking high "Solve this complex problem"
|
||||
|
||||
# Read-only mode (no file modifications possible)
|
||||
pi --tools read,grep,find,ls -p "Review the code in src/"
|
||||
${APP_NAME} --tools read,grep,find,ls -p "Review the code in src/"
|
||||
|
||||
# Export a session file to HTML
|
||||
pi --export ~/.pi/agent/sessions/--path--/session.jsonl
|
||||
pi --export session.jsonl output.html
|
||||
${APP_NAME} --export ~/${CONFIG_DIR_NAME}/agent/sessions/--path--/session.jsonl
|
||||
${APP_NAME} --export session.jsonl output.html
|
||||
|
||||
${chalk.bold("Environment Variables:")}
|
||||
ANTHROPIC_API_KEY - Anthropic Claude API key
|
||||
|
|
@ -291,7 +295,7 @@ ${chalk.bold("Environment Variables:")}
|
|||
XAI_API_KEY - xAI Grok API key
|
||||
OPENROUTER_API_KEY - OpenRouter API key
|
||||
ZAI_API_KEY - ZAI API key
|
||||
PI_CODING_AGENT_DIR - Session storage directory (default: ~/.pi/agent)
|
||||
${ENV_AGENT_DIR.padEnd(23)} - Session storage directory (default: ~/${CONFIG_DIR_NAME}/agent)
|
||||
|
||||
${chalk.bold("Available Tools (default: read, bash, edit, write):")}
|
||||
read - Read file contents
|
||||
|
|
@ -488,16 +492,15 @@ function loadContextFileFromDir(dir: string): { path: string; content: string }
|
|||
|
||||
/**
|
||||
* Load all project context files in order:
|
||||
* 1. Global: ~/.pi/agent/AGENTS.md or CLAUDE.md
|
||||
* 1. Global: ~/{CONFIG_DIR_NAME}/agent/AGENTS.md or CLAUDE.md
|
||||
* 2. Parent directories (top-most first) down to cwd
|
||||
* Each returns {path, content} for separate messages
|
||||
*/
|
||||
function loadProjectContextFiles(): Array<{ path: string; content: string }> {
|
||||
const contextFiles: Array<{ path: string; content: string }> = [];
|
||||
|
||||
// 1. Load global context from ~/.pi/agent/
|
||||
const homeDir = homedir();
|
||||
const globalContextDir = resolve(process.env.PI_CODING_AGENT_DIR || join(homeDir, ".pi/agent/"));
|
||||
// 1. Load global context from ~/{CONFIG_DIR_NAME}/agent/
|
||||
const globalContextDir = getAgentDir();
|
||||
const globalContext = loadContextFileFromDir(globalContextDir);
|
||||
if (globalContext) {
|
||||
contextFiles.push(globalContext);
|
||||
|
|
@ -1028,7 +1031,7 @@ export async function main(args: string[]) {
|
|||
console.error(chalk.red("No models available."));
|
||||
console.error(chalk.yellow("\nSet an API key environment variable:"));
|
||||
console.error(" ANTHROPIC_API_KEY, OPENAI_API_KEY, GEMINI_API_KEY, etc.");
|
||||
console.error(chalk.yellow("\nOr create ~/.pi/agent/models.json"));
|
||||
console.error(chalk.yellow(`\nOr create ${getModelsPath()}`));
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
|
|
@ -1108,7 +1111,7 @@ export async function main(args: string[]) {
|
|||
console.error(chalk.red("\nNo models available."));
|
||||
console.error(chalk.yellow("Set an API key environment variable:"));
|
||||
console.error(" ANTHROPIC_API_KEY, OPENAI_API_KEY, GEMINI_API_KEY, etc.");
|
||||
console.error(chalk.yellow("\nOr create ~/.pi/agent/models.json"));
|
||||
console.error(chalk.yellow(`\nOr create ${getModelsPath()}`));
|
||||
}
|
||||
process.exit(1);
|
||||
}
|
||||
|
|
@ -1153,7 +1156,7 @@ export async function main(args: string[]) {
|
|||
const key = await getApiKeyForModel(currentModel);
|
||||
if (!key) {
|
||||
throw new Error(
|
||||
`No API key found for provider "${currentModel.provider}". Please set the appropriate environment variable or update ~/.pi/agent/models.json`,
|
||||
`No API key found for provider "${currentModel.provider}". Please set the appropriate environment variable or update ${getModelsPath()}`,
|
||||
);
|
||||
}
|
||||
return key;
|
||||
|
|
|
|||
|
|
@ -2,8 +2,7 @@ import { type Api, getApiKey, getModels, getProviders, type KnownProvider, type
|
|||
import { type Static, Type } from "@sinclair/typebox";
|
||||
import AjvModule from "ajv";
|
||||
import { existsSync, readFileSync } from "fs";
|
||||
import { homedir } from "os";
|
||||
import { join } from "path";
|
||||
import { getModelsPath } from "./config.js";
|
||||
import { getOAuthToken, type SupportedOAuthProvider } from "./oauth/index.js";
|
||||
import { loadOAuthCredentials } from "./oauth/storage.js";
|
||||
|
||||
|
|
@ -75,11 +74,11 @@ export function resolveApiKey(keyConfig: string): string | undefined {
|
|||
}
|
||||
|
||||
/**
|
||||
* Load custom models from ~/.pi/agent/models.json
|
||||
* Load custom models from models.json in agent config dir
|
||||
* Returns { models, error } - either models array or error message
|
||||
*/
|
||||
function loadCustomModels(): { models: Model<Api>[]; error: string | null } {
|
||||
const configPath = join(homedir(), ".pi", "agent", "models.json");
|
||||
const configPath = getModelsPath();
|
||||
if (!existsSync(configPath)) {
|
||||
return { models: [], error: null };
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,5 @@
|
|||
import { chmodSync, existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
|
||||
import { homedir } from "os";
|
||||
import { join } from "path";
|
||||
import { getAgentDir, getOAuthPath } from "../config.js";
|
||||
|
||||
export interface OAuthCredentials {
|
||||
type: "oauth";
|
||||
|
|
@ -13,19 +12,11 @@ interface OAuthStorageFormat {
|
|||
[provider: string]: OAuthCredentials;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get path to oauth.json
|
||||
*/
|
||||
function getOAuthFilePath(): string {
|
||||
const configDir = join(homedir(), ".pi", "agent");
|
||||
return join(configDir, "oauth.json");
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensure the config directory exists
|
||||
*/
|
||||
function ensureConfigDir(): void {
|
||||
const configDir = join(homedir(), ".pi", "agent");
|
||||
const configDir = getAgentDir();
|
||||
if (!existsSync(configDir)) {
|
||||
mkdirSync(configDir, { recursive: true, mode: 0o700 });
|
||||
}
|
||||
|
|
@ -35,7 +26,7 @@ function ensureConfigDir(): void {
|
|||
* Load all OAuth credentials from oauth.json
|
||||
*/
|
||||
function loadStorage(): OAuthStorageFormat {
|
||||
const filePath = getOAuthFilePath();
|
||||
const filePath = getOAuthPath();
|
||||
if (!existsSync(filePath)) {
|
||||
return {};
|
||||
}
|
||||
|
|
@ -54,7 +45,7 @@ function loadStorage(): OAuthStorageFormat {
|
|||
*/
|
||||
function saveStorage(storage: OAuthStorageFormat): void {
|
||||
ensureConfigDir();
|
||||
const filePath = getOAuthFilePath();
|
||||
const filePath = getOAuthPath();
|
||||
writeFileSync(filePath, JSON.stringify(storage, null, 2), "utf-8");
|
||||
// Set permissions to owner read/write only
|
||||
chmodSync(filePath, 0o600);
|
||||
|
|
|
|||
|
|
@ -1,71 +0,0 @@
|
|||
import { existsSync } from "fs";
|
||||
import { dirname, join, resolve } from "path";
|
||||
import { fileURLToPath } from "url";
|
||||
|
||||
const __filename = fileURLToPath(import.meta.url);
|
||||
const __dirname = dirname(__filename);
|
||||
|
||||
/**
|
||||
* Detect if we're running as a Bun compiled binary.
|
||||
* Bun binaries have import.meta.url containing "%7EBUN" (URL-encoded ~BUN virtual filesystem path)
|
||||
*/
|
||||
export const isBunBinary = import.meta.url.includes("%7EBUN");
|
||||
|
||||
/**
|
||||
* Type definition for Bun global (only available when running via Bun)
|
||||
*/
|
||||
declare const Bun: { main: string } | undefined;
|
||||
|
||||
/**
|
||||
* Get the base directory for resolving package assets (themes, package.json, README.md, CHANGELOG.md).
|
||||
* - For Bun binary: returns the directory containing the executable
|
||||
* - For Node.js (dist/): returns __dirname (the dist/ directory)
|
||||
* - For tsx (src/): returns parent directory (the package root)
|
||||
*/
|
||||
export function getPackageDir(): string {
|
||||
if (isBunBinary) {
|
||||
// Bun binary: process.execPath points to the compiled executable
|
||||
return dirname(process.execPath);
|
||||
}
|
||||
// Node.js: check if package.json exists in __dirname (dist/) or parent (src/ case)
|
||||
if (existsSync(join(__dirname, "package.json"))) {
|
||||
return __dirname;
|
||||
}
|
||||
// Running from src/ via tsx - go up one level to package root
|
||||
return dirname(__dirname);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get path to the theme directory
|
||||
* - For Bun binary: theme/ next to executable
|
||||
* - For Node.js (dist/): dist/theme/
|
||||
* - For tsx (src/): src/theme/
|
||||
*/
|
||||
export function getThemeDir(): string {
|
||||
if (isBunBinary) {
|
||||
return join(dirname(process.execPath), "theme");
|
||||
}
|
||||
// __dirname is either dist/ or src/ - theme is always a subdirectory
|
||||
return join(__dirname, "theme");
|
||||
}
|
||||
|
||||
/**
|
||||
* Get path to package.json
|
||||
*/
|
||||
export function getPackageJsonPath(): string {
|
||||
return join(getPackageDir(), "package.json");
|
||||
}
|
||||
|
||||
/**
|
||||
* Get path to README.md
|
||||
*/
|
||||
export function getReadmePath(): string {
|
||||
return resolve(join(getPackageDir(), "README.md"));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get path to CHANGELOG.md
|
||||
*/
|
||||
export function getChangelogPath(): string {
|
||||
return resolve(join(getPackageDir(), "CHANGELOG.md"));
|
||||
}
|
||||
|
|
@ -1,8 +1,8 @@
|
|||
import type { AgentState, AppMessage } from "@mariozechner/pi-agent-core";
|
||||
import { randomBytes } from "crypto";
|
||||
import { appendFileSync, existsSync, mkdirSync, readdirSync, readFileSync, statSync } from "fs";
|
||||
import { homedir } from "os";
|
||||
import { join, resolve } from "path";
|
||||
import { getAgentDir } from "./config.js";
|
||||
|
||||
function uuidv4(): string {
|
||||
const bytes = randomBytes(16);
|
||||
|
|
@ -83,7 +83,7 @@ export class SessionManager {
|
|||
// Replace all path separators and colons (for Windows drive letters) with dashes
|
||||
const safePath = "--" + cwd.replace(/^[/\\]/, "").replace(/[/\\:]/g, "-") + "--";
|
||||
|
||||
const configDir = resolve(process.env.PI_CODING_AGENT_DIR || join(homedir(), ".pi/agent/"));
|
||||
const configDir = getAgentDir();
|
||||
const sessionDir = join(configDir, "sessions", safePath);
|
||||
if (!existsSync(sessionDir)) {
|
||||
mkdirSync(sessionDir, { recursive: true });
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
|
||||
import { homedir } from "os";
|
||||
import { dirname, join } from "path";
|
||||
import { getAgentDir } from "./config.js";
|
||||
|
||||
export interface Settings {
|
||||
lastChangelogVersion?: string;
|
||||
|
|
@ -16,7 +16,7 @@ export class SettingsManager {
|
|||
private settings: Settings;
|
||||
|
||||
constructor(baseDir?: string) {
|
||||
const dir = baseDir || join(homedir(), ".pi", "agent");
|
||||
const dir = baseDir || getAgentDir();
|
||||
this.settingsPath = join(dir, "settings.json");
|
||||
this.settings = this.load();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import { existsSync, readdirSync, readFileSync } from "fs";
|
||||
import { homedir } from "os";
|
||||
import { join, resolve } from "path";
|
||||
import { CONFIG_DIR_NAME, getCommandsDir } from "./config.js";
|
||||
|
||||
/**
|
||||
* Represents a custom slash command loaded from a file
|
||||
|
|
@ -167,19 +167,18 @@ function loadCommandsFromDir(dir: string, source: "user" | "project", subdir: st
|
|||
|
||||
/**
|
||||
* Load all custom slash commands from:
|
||||
* 1. Global: ~/.pi/agent/commands/
|
||||
* 2. Project: ./.pi/commands/
|
||||
* 1. Global: ~/{CONFIG_DIR_NAME}/agent/commands/
|
||||
* 2. Project: ./{CONFIG_DIR_NAME}/commands/
|
||||
*/
|
||||
export function loadSlashCommands(): FileSlashCommand[] {
|
||||
const commands: FileSlashCommand[] = [];
|
||||
|
||||
// 1. Load global commands from ~/.pi/agent/commands/
|
||||
const homeDir = homedir();
|
||||
const globalCommandsDir = resolve(process.env.PI_CODING_AGENT_DIR || join(homeDir, ".pi/agent/"), "commands");
|
||||
// 1. Load global commands from ~/{CONFIG_DIR_NAME}/agent/commands/
|
||||
const globalCommandsDir = getCommandsDir();
|
||||
commands.push(...loadCommandsFromDir(globalCommandsDir, "user"));
|
||||
|
||||
// 2. Load project commands from ./.pi/commands/
|
||||
const projectCommandsDir = resolve(process.cwd(), ".pi/commands");
|
||||
// 2. Load project commands from ./{CONFIG_DIR_NAME}/commands/
|
||||
const projectCommandsDir = resolve(process.cwd(), CONFIG_DIR_NAME, "commands");
|
||||
commands.push(...loadCommandsFromDir(projectCommandsDir, "project"));
|
||||
|
||||
return commands;
|
||||
|
|
|
|||
|
|
@ -1,11 +1,10 @@
|
|||
import * as fs from "node:fs";
|
||||
import * as os from "node:os";
|
||||
import * as path from "node:path";
|
||||
import type { EditorTheme, MarkdownTheme, SelectListTheme } from "@mariozechner/pi-tui";
|
||||
import { type Static, Type } from "@sinclair/typebox";
|
||||
import { TypeCompiler } from "@sinclair/typebox/compiler";
|
||||
import chalk from "chalk";
|
||||
import { getThemeDir } from "../paths.js";
|
||||
import { getCustomThemesDir, getThemesDir } from "../config.js";
|
||||
|
||||
// ============================================================================
|
||||
// Types & Schema
|
||||
|
|
@ -323,9 +322,9 @@ let BUILTIN_THEMES: Record<string, ThemeJson> | undefined;
|
|||
|
||||
function getBuiltinThemes(): Record<string, ThemeJson> {
|
||||
if (!BUILTIN_THEMES) {
|
||||
const themeDir = getThemeDir();
|
||||
const darkPath = path.join(themeDir, "dark.json");
|
||||
const lightPath = path.join(themeDir, "light.json");
|
||||
const themesDir = getThemesDir();
|
||||
const darkPath = path.join(themesDir, "dark.json");
|
||||
const lightPath = path.join(themesDir, "light.json");
|
||||
BUILTIN_THEMES = {
|
||||
dark: JSON.parse(fs.readFileSync(darkPath, "utf-8")) as ThemeJson,
|
||||
light: JSON.parse(fs.readFileSync(lightPath, "utf-8")) as ThemeJson,
|
||||
|
|
@ -334,15 +333,11 @@ function getBuiltinThemes(): Record<string, ThemeJson> {
|
|||
return BUILTIN_THEMES;
|
||||
}
|
||||
|
||||
function getThemesDir(): string {
|
||||
return path.join(os.homedir(), ".pi", "agent", "themes");
|
||||
}
|
||||
|
||||
export function getAvailableThemes(): string[] {
|
||||
const themes = new Set<string>(Object.keys(getBuiltinThemes()));
|
||||
const themesDir = getThemesDir();
|
||||
if (fs.existsSync(themesDir)) {
|
||||
const files = fs.readdirSync(themesDir);
|
||||
const customThemesDir = getCustomThemesDir();
|
||||
if (fs.existsSync(customThemesDir)) {
|
||||
const files = fs.readdirSync(customThemesDir);
|
||||
for (const file of files) {
|
||||
if (file.endsWith(".json")) {
|
||||
themes.add(file.slice(0, -5));
|
||||
|
|
@ -357,8 +352,8 @@ function loadThemeJson(name: string): ThemeJson {
|
|||
if (name in builtinThemes) {
|
||||
return builtinThemes[name];
|
||||
}
|
||||
const themesDir = getThemesDir();
|
||||
const themePath = path.join(themesDir, `${name}.json`);
|
||||
const customThemesDir = getCustomThemesDir();
|
||||
const themePath = path.join(customThemesDir, `${name}.json`);
|
||||
if (!fs.existsSync(themePath)) {
|
||||
throw new Error(`Theme not found: ${name}`);
|
||||
}
|
||||
|
|
@ -474,8 +469,8 @@ function startThemeWatcher(): void {
|
|||
return;
|
||||
}
|
||||
|
||||
const themesDir = getThemesDir();
|
||||
const themeFile = path.join(themesDir, `${currentThemeName}.json`);
|
||||
const customThemesDir = getCustomThemesDir();
|
||||
const themeFile = path.join(customThemesDir, `${currentThemeName}.json`);
|
||||
|
||||
// Only watch if the file exists
|
||||
if (!fs.existsSync(themeFile)) {
|
||||
|
|
|
|||
|
|
@ -1,12 +1,13 @@
|
|||
import chalk from "chalk";
|
||||
import { spawnSync } from "child_process";
|
||||
import { chmodSync, createWriteStream, existsSync, mkdirSync, renameSync, rmSync } from "fs";
|
||||
import { arch, homedir, platform } from "os";
|
||||
import { arch, platform } from "os";
|
||||
import { join } from "path";
|
||||
import { Readable } from "stream";
|
||||
import { finished } from "stream/promises";
|
||||
import { getToolsDir } from "./config.js";
|
||||
|
||||
const TOOLS_DIR = join(homedir(), ".pi", "agent", "tools");
|
||||
const TOOLS_DIR = getToolsDir();
|
||||
|
||||
interface ToolConfig {
|
||||
name: string;
|
||||
|
|
|
|||
|
|
@ -82,7 +82,7 @@ export class ModelSelectorComponent extends Container {
|
|||
}
|
||||
|
||||
private async loadModels(): Promise<void> {
|
||||
// Load available models fresh (includes custom models from ~/.pi/agent/models.json)
|
||||
// Load available models fresh (includes custom models from models.json)
|
||||
const { models: availableModels, error } = await getAvailableModels();
|
||||
|
||||
// If there's an error loading models.json, we'll show it via the "no models" path
|
||||
|
|
|
|||
|
|
@ -1,5 +1,4 @@
|
|||
import * as fs from "node:fs";
|
||||
import * as os from "node:os";
|
||||
import * as path from "node:path";
|
||||
import type { Agent, AgentEvent, AgentState, ThinkingLevel } from "@mariozechner/pi-agent-core";
|
||||
import type { AssistantMessage, Message, Model } from "@mariozechner/pi-ai";
|
||||
|
|
@ -19,6 +18,7 @@ import {
|
|||
} from "@mariozechner/pi-tui";
|
||||
import { exec } from "child_process";
|
||||
import { getChangelogPath, parseChangelog } from "../changelog.js";
|
||||
import { APP_NAME, getDebugLogPath, getModelsPath, getOAuthPath } from "../config.js";
|
||||
import { exportSessionToHtml } from "../export-html.js";
|
||||
import { getApiKeyForModel, getAvailableModels, invalidateOAuthCache } from "../model-config.js";
|
||||
import { listOAuthProviders, login, logout } from "../oauth/index.js";
|
||||
|
|
@ -221,7 +221,7 @@ export class TuiRenderer {
|
|||
if (this.isInitialized) return;
|
||||
|
||||
// Add header with logo and instructions
|
||||
const logo = theme.bold(theme.fg("accent", "pi")) + theme.fg("dim", ` v${this.version}`);
|
||||
const logo = theme.bold(theme.fg("accent", APP_NAME)) + theme.fg("dim", ` v${this.version}`);
|
||||
const instructions =
|
||||
theme.fg("dim", "esc") +
|
||||
theme.fg("muted", " to interrupt") +
|
||||
|
|
@ -434,7 +434,7 @@ export class TuiRenderer {
|
|||
this.showError(
|
||||
"No model selected.\n\n" +
|
||||
"Set an API key (ANTHROPIC_API_KEY, OPENAI_API_KEY, etc.)\n" +
|
||||
"or create ~/.pi/agent/models.json\n\n" +
|
||||
`or create ${getModelsPath()}\n\n` +
|
||||
"Then use /model to select a model.",
|
||||
);
|
||||
return;
|
||||
|
|
@ -445,7 +445,7 @@ export class TuiRenderer {
|
|||
if (!apiKey) {
|
||||
this.showError(
|
||||
`No API key found for ${currentModel.provider}.\n\n` +
|
||||
`Set the appropriate environment variable or update ~/.pi/agent/models.json`,
|
||||
`Set the appropriate environment variable or update ${getModelsPath()}`,
|
||||
);
|
||||
this.editor.setText(text);
|
||||
return;
|
||||
|
|
@ -1334,9 +1334,7 @@ export class TuiRenderer {
|
|||
this.chatContainer.addChild(
|
||||
new Text(theme.fg("success", `✓ Successfully logged in to ${providerId}`), 1, 0),
|
||||
);
|
||||
this.chatContainer.addChild(
|
||||
new Text(theme.fg("dim", `Tokens saved to ~/.pi/agent/oauth.json`), 1, 0),
|
||||
);
|
||||
this.chatContainer.addChild(new Text(theme.fg("dim", `Tokens saved to ${getOAuthPath()}`), 1, 0));
|
||||
this.ui.requestRender();
|
||||
} catch (error: any) {
|
||||
this.showError(`Login failed: ${error.message}`);
|
||||
|
|
@ -1353,7 +1351,7 @@ export class TuiRenderer {
|
|||
new Text(theme.fg("success", `✓ Successfully logged out of ${providerId}`), 1, 0),
|
||||
);
|
||||
this.chatContainer.addChild(
|
||||
new Text(theme.fg("dim", `Credentials removed from ~/.pi/agent/oauth.json`), 1, 0),
|
||||
new Text(theme.fg("dim", `Credentials removed from ${getOAuthPath()}`), 1, 0),
|
||||
);
|
||||
this.ui.requestRender();
|
||||
} catch (error: any) {
|
||||
|
|
@ -1545,7 +1543,7 @@ export class TuiRenderer {
|
|||
const width = (this.ui as any).terminal.columns;
|
||||
const allLines = this.ui.render(width);
|
||||
|
||||
const debugLogPath = path.join(os.homedir(), ".pi", "agent", "pi-debug.log");
|
||||
const debugLogPath = getDebugLogPath();
|
||||
const debugData = [
|
||||
`Debug output at ${new Date().toISOString()}`,
|
||||
`Terminal width: ${width}`,
|
||||
|
|
@ -1566,11 +1564,7 @@ export class TuiRenderer {
|
|||
// Show confirmation
|
||||
this.chatContainer.addChild(new Spacer(1));
|
||||
this.chatContainer.addChild(
|
||||
new Text(
|
||||
theme.fg("accent", "✓ Debug log written") + "\n" + theme.fg("muted", `~/.pi/agent/pi-debug.log`),
|
||||
1,
|
||||
1,
|
||||
),
|
||||
new Text(theme.fg("accent", "✓ Debug log written") + "\n" + theme.fg("muted", debugLogPath), 1, 1),
|
||||
);
|
||||
|
||||
this.ui.requestRender();
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue