mirror of
https://github.com/badlogic/pi-mono.git
synced 2026-07-09 17:28:45 +00:00
feat(coding-agent): export project config dir name
Some checks are pending
CI / build-check-test (push) Waiting to run
Some checks are pending
CI / build-check-test (push) Waiting to run
This commit is contained in:
parent
58dd2f5996
commit
008c76f955
13 changed files with 73 additions and 27 deletions
|
|
@ -2,6 +2,14 @@
|
|||
|
||||
## [Unreleased]
|
||||
|
||||
### Added
|
||||
|
||||
- Exported `CONFIG_DIR_NAME` from the coding-agent public API so extensions can resolve project config paths without hardcoding `.pi`.
|
||||
|
||||
### Changed
|
||||
|
||||
- Updated extension docs, examples, runtime help, trust prompts, and config labels to use the configured project config directory instead of hardcoded `.pi` paths.
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fixed `/model` autocomplete and model selection searches to match provider/model queries regardless of whether the provider or model token is typed first.
|
||||
|
|
|
|||
|
|
@ -898,6 +898,20 @@ Current run mode: `"tui"`, `"rpc"`, `"json"`, or `"print"`. Use `ctx.mode === "t
|
|||
|
||||
Current working directory.
|
||||
|
||||
Use `CONFIG_DIR_NAME` instead of hardcoding `.pi` when constructing project-local config paths. Rebranded distributions can use a different config directory name.
|
||||
|
||||
```typescript
|
||||
import { CONFIG_DIR_NAME, type ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
||||
import { join } from "node:path";
|
||||
|
||||
export default function (pi: ExtensionAPI) {
|
||||
pi.on("session_start", (_event, ctx) => {
|
||||
const projectConfigPath = join(ctx.cwd, CONFIG_DIR_NAME, "my-extension.json");
|
||||
// ...
|
||||
});
|
||||
}
|
||||
```
|
||||
|
||||
### ctx.isProjectTrusted()
|
||||
|
||||
Returns whether project-local trust is active for the current session context. This includes temporary trust decisions and CLI trust overrides, not just saved decisions in the global trust store.
|
||||
|
|
|
|||
|
|
@ -1110,7 +1110,8 @@ DefaultResourceLoader
|
|||
type ResourceLoader
|
||||
createEventBus
|
||||
|
||||
// Helpers
|
||||
// Constants and helpers
|
||||
CONFIG_DIR_NAME
|
||||
defineTool
|
||||
getAgentDir
|
||||
getPackageDir
|
||||
|
|
|
|||
|
|
@ -42,7 +42,7 @@ import { existsSync, readFileSync } from "node:fs";
|
|||
import { join } from "node:path";
|
||||
import type { Api, Model } from "@earendil-works/pi-ai";
|
||||
import type { ExtensionAPI, ExtensionContext } from "@earendil-works/pi-coding-agent";
|
||||
import { DynamicBorder, getAgentDir } from "@earendil-works/pi-coding-agent";
|
||||
import { CONFIG_DIR_NAME, DynamicBorder, getAgentDir } from "@earendil-works/pi-coding-agent";
|
||||
import { Container, Key, type SelectItem, SelectList, Text } from "@earendil-works/pi-tui";
|
||||
|
||||
// Preset configuration
|
||||
|
|
@ -69,7 +69,7 @@ interface PresetsConfig {
|
|||
*/
|
||||
function loadPresets(cwd: string): PresetsConfig {
|
||||
const globalPath = join(getAgentDir(), "presets.json");
|
||||
const projectPath = join(cwd, ".pi", "presets.json");
|
||||
const projectPath = join(cwd, CONFIG_DIR_NAME, "presets.json");
|
||||
|
||||
let globalPresets: PresetsConfig = {};
|
||||
let projectPresets: PresetsConfig = {};
|
||||
|
|
@ -200,7 +200,10 @@ export default function presetExtension(pi: ExtensionAPI) {
|
|||
const presetNames = Object.keys(presets);
|
||||
|
||||
if (presetNames.length === 0) {
|
||||
ctx.ui.notify("No presets defined. Add presets to ~/.pi/agent/presets.json or .pi/presets.json", "warning");
|
||||
ctx.ui.notify(
|
||||
`No presets defined. Add presets to ${join(getAgentDir(), "presets.json")} or ${join(ctx.cwd, CONFIG_DIR_NAME, "presets.json")}`,
|
||||
"warning",
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -308,7 +311,10 @@ export default function presetExtension(pi: ExtensionAPI) {
|
|||
async function cyclePreset(ctx: ExtensionContext): Promise<void> {
|
||||
const presetNames = getPresetOrder();
|
||||
if (presetNames.length === 0) {
|
||||
ctx.ui.notify("No presets defined. Add presets to ~/.pi/agent/presets.json or .pi/presets.json", "warning");
|
||||
ctx.ui.notify(
|
||||
`No presets defined. Add presets to ${join(getAgentDir(), "presets.json")} or ${join(ctx.cwd, CONFIG_DIR_NAME, "presets.json")}`,
|
||||
"warning",
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,18 +1,18 @@
|
|||
import { appendFileSync } from "node:fs";
|
||||
import { join } from "node:path";
|
||||
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
||||
import { CONFIG_DIR_NAME, type ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
||||
|
||||
export default function (pi: ExtensionAPI) {
|
||||
const logFile = join(process.cwd(), ".pi", "provider-payload.log");
|
||||
|
||||
pi.on("before_provider_request", (event) => {
|
||||
pi.on("before_provider_request", (event, ctx) => {
|
||||
const logFile = join(ctx.cwd, CONFIG_DIR_NAME, "provider-payload.log");
|
||||
appendFileSync(logFile, `${JSON.stringify(event.payload, null, 2)}\n\n`, "utf8");
|
||||
|
||||
// Optional: replace the payload instead of only logging it.
|
||||
// return { ...event.payload, temperature: 0 };
|
||||
});
|
||||
|
||||
pi.on("after_provider_response", (event) => {
|
||||
pi.on("after_provider_response", (event, ctx) => {
|
||||
const logFile = join(ctx.cwd, CONFIG_DIR_NAME, "provider-payload.log");
|
||||
appendFileSync(logFile, `[${event.status}] ${JSON.stringify(event.headers)}\n\n`, "utf8");
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -46,7 +46,7 @@ import { existsSync, readFileSync } from "node:fs";
|
|||
import { join } from "node:path";
|
||||
import { SandboxManager, type SandboxRuntimeConfig } from "@anthropic-ai/sandbox-runtime";
|
||||
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
||||
import { type BashOperations, createBashTool, getAgentDir } from "@earendil-works/pi-coding-agent";
|
||||
import { type BashOperations, CONFIG_DIR_NAME, createBashTool, getAgentDir } from "@earendil-works/pi-coding-agent";
|
||||
|
||||
interface SandboxConfig extends SandboxRuntimeConfig {
|
||||
enabled?: boolean;
|
||||
|
|
@ -77,7 +77,7 @@ const DEFAULT_CONFIG: SandboxConfig = {
|
|||
};
|
||||
|
||||
function loadConfig(cwd: string): SandboxConfig {
|
||||
const projectConfigPath = join(cwd, ".pi", "sandbox.json");
|
||||
const projectConfigPath = join(cwd, CONFIG_DIR_NAME, "sandbox.json");
|
||||
const globalConfigPath = join(getAgentDir(), "extensions", "sandbox.json");
|
||||
|
||||
let globalConfig: Partial<SandboxConfig> = {};
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@
|
|||
|
||||
import * as fs from "node:fs";
|
||||
import * as path from "node:path";
|
||||
import { getAgentDir, parseFrontmatter } from "@earendil-works/pi-coding-agent";
|
||||
import { CONFIG_DIR_NAME, getAgentDir, parseFrontmatter } from "@earendil-works/pi-coding-agent";
|
||||
|
||||
export type AgentScope = "user" | "project" | "both";
|
||||
|
||||
|
|
@ -85,7 +85,7 @@ function isDirectory(p: string): boolean {
|
|||
function findNearestProjectAgentsDir(cwd: string): string | null {
|
||||
let currentDir = cwd;
|
||||
while (true) {
|
||||
const candidate = path.join(currentDir, ".pi", "agents");
|
||||
const candidate = path.join(currentDir, CONFIG_DIR_NAME, "agents");
|
||||
if (isDirectory(candidate)) return candidate;
|
||||
|
||||
const parentDir = path.dirname(currentDir);
|
||||
|
|
|
|||
|
|
@ -19,7 +19,13 @@ import * as path from "node:path";
|
|||
import type { AgentToolResult } from "@earendil-works/pi-agent-core";
|
||||
import type { Message } from "@earendil-works/pi-ai";
|
||||
import { StringEnum } from "@earendil-works/pi-ai";
|
||||
import { type ExtensionAPI, getMarkdownTheme, withFileMutationQueue } from "@earendil-works/pi-coding-agent";
|
||||
import {
|
||||
CONFIG_DIR_NAME,
|
||||
type ExtensionAPI,
|
||||
getAgentDir,
|
||||
getMarkdownTheme,
|
||||
withFileMutationQueue,
|
||||
} from "@earendil-works/pi-coding-agent";
|
||||
import { Container, Markdown, Spacer, Text } from "@earendil-works/pi-tui";
|
||||
import { Type } from "typebox";
|
||||
import { type AgentConfig, type AgentScope, discoverAgents } from "./agents.ts";
|
||||
|
|
@ -458,8 +464,8 @@ export default function (pi: ExtensionAPI) {
|
|||
description: [
|
||||
"Delegate tasks to specialized subagents with isolated context.",
|
||||
"Modes: single (agent + task), parallel (tasks array), chain (sequential with {previous} placeholder).",
|
||||
'Default agent scope is "user" (from ~/.pi/agent/agents).',
|
||||
'To enable project-local agents in .pi/agents, set agentScope: "both" (or "project").',
|
||||
`Default agent scope is "user" (from ${path.join(getAgentDir(), "agents")}).`,
|
||||
`To enable project-local agents in ${CONFIG_DIR_NAME}/agents, set agentScope: "both" (or "project").`,
|
||||
].join(" "),
|
||||
parameters: SubagentParams,
|
||||
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
import { CONFIG_DIR_NAME } from "../config.ts";
|
||||
import { emitProjectTrustEvent } from "./extensions/runner.ts";
|
||||
import type { LoadExtensionsResult, ProjectTrustContext } from "./extensions/types.ts";
|
||||
import type { DefaultProjectTrust } from "./settings-manager.ts";
|
||||
|
|
@ -21,7 +22,7 @@ export interface ResolveProjectTrustedOptions {
|
|||
}
|
||||
|
||||
function formatProjectTrustPrompt(cwd: string): string {
|
||||
return `Trust project folder?\n${cwd}\n\nThis allows pi to load .pi settings and resources, install missing project packages, and execute project extensions.`;
|
||||
return `Trust project folder?\n${cwd}\n\nThis allows pi to load ${CONFIG_DIR_NAME} settings and resources, install missing project packages, and execute project extensions.`;
|
||||
}
|
||||
|
||||
async function selectProjectTrustOption(
|
||||
|
|
|
|||
|
|
@ -3,7 +3,15 @@
|
|||
export { type Args, parseArgs } from "./cli/args.ts";
|
||||
|
||||
// Config paths
|
||||
export { getAgentDir, getDocsPath, getExamplesPath, getPackageDir, getReadmePath, VERSION } from "./config.ts";
|
||||
export {
|
||||
CONFIG_DIR_NAME,
|
||||
getAgentDir,
|
||||
getDocsPath,
|
||||
getExamplesPath,
|
||||
getPackageDir,
|
||||
getReadmePath,
|
||||
VERSION,
|
||||
} from "./config.ts";
|
||||
export {
|
||||
AgentSession,
|
||||
type AgentSessionConfig,
|
||||
|
|
|
|||
|
|
@ -73,7 +73,7 @@ function formatBaseDir(baseDir: string): string {
|
|||
return displayPath.endsWith("/") ? displayPath : `${displayPath}/`;
|
||||
}
|
||||
|
||||
function getGroupLabel(metadata: PathMetadata): string {
|
||||
function getGroupLabel(metadata: PathMetadata, agentDir: string): string {
|
||||
if (metadata.origin === "package") {
|
||||
return `${metadata.source} (${metadata.scope})`;
|
||||
}
|
||||
|
|
@ -84,12 +84,12 @@ function getGroupLabel(metadata: PathMetadata): string {
|
|||
? `User (${formatBaseDir(metadata.baseDir)})`
|
||||
: `Project (${formatBaseDir(metadata.baseDir)})`;
|
||||
}
|
||||
return metadata.scope === "user" ? "User (~/.pi/agent/)" : "Project (.pi/)";
|
||||
return metadata.scope === "user" ? `User (${formatBaseDir(agentDir)})` : `Project (${CONFIG_DIR_NAME}/)`;
|
||||
}
|
||||
return metadata.scope === "user" ? "User settings" : "Project settings";
|
||||
}
|
||||
|
||||
function buildGroups(resolved: ResolvedPaths): ResourceGroup[] {
|
||||
function buildGroups(resolved: ResolvedPaths, agentDir: string): ResourceGroup[] {
|
||||
const groupMap = new Map<string, ResourceGroup>();
|
||||
|
||||
const addToGroup = (resources: ResolvedResource[], resourceType: ResourceType) => {
|
||||
|
|
@ -100,7 +100,7 @@ function buildGroups(resolved: ResolvedPaths): ResourceGroup[] {
|
|||
if (!groupMap.has(groupKey)) {
|
||||
groupMap.set(groupKey, {
|
||||
key: groupKey,
|
||||
label: getGroupLabel(metadata),
|
||||
label: getGroupLabel(metadata, agentDir),
|
||||
scope: metadata.scope,
|
||||
origin: metadata.origin,
|
||||
source: metadata.source,
|
||||
|
|
@ -601,7 +601,7 @@ export class ConfigSelectorComponent extends Container implements Focusable {
|
|||
) {
|
||||
super();
|
||||
|
||||
const groups = buildGroups(resolvedPaths);
|
||||
const groups = buildGroups(resolvedPaths, agentDir);
|
||||
|
||||
// Add header
|
||||
this.addChild(new Spacer(1));
|
||||
|
|
|
|||
|
|
@ -52,6 +52,7 @@ import { spawn, spawnSync } from "child_process";
|
|||
import {
|
||||
APP_NAME,
|
||||
APP_TITLE,
|
||||
CONFIG_DIR_NAME,
|
||||
getAgentDir,
|
||||
getAuthPath,
|
||||
getDebugLogPath,
|
||||
|
|
@ -3307,7 +3308,7 @@ export class InteractiveMode {
|
|||
new Text(
|
||||
theme.fg(
|
||||
"warning",
|
||||
"This project is not trusted. Project .pi resources and packages are ignored. Use /trust to save a trust decision, then restart pi.",
|
||||
`This project is not trusted. Project ${CONFIG_DIR_NAME} resources and packages are ignored. Use /trust to save a trust decision, then restart pi.`,
|
||||
),
|
||||
1,
|
||||
0,
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import { selectConfig } from "./cli/config-selector.ts";
|
|||
import { createProjectTrustContext } from "./cli/project-trust.ts";
|
||||
import {
|
||||
APP_NAME,
|
||||
CONFIG_DIR_NAME,
|
||||
detectInstallMethod,
|
||||
getAgentDir,
|
||||
getPackageDir,
|
||||
|
|
@ -93,7 +94,7 @@ function printPackageCommandHelp(command: PackageCommand): void {
|
|||
Install a package and add it to settings.
|
||||
|
||||
Options:
|
||||
-l, --local Install project-locally (.pi/settings.json)
|
||||
-l, --local Install project-locally (${CONFIG_DIR_NAME}/settings.json)
|
||||
-a, --approve Trust project-local files for this command
|
||||
-na, --no-approve Ignore project-local files for this command
|
||||
|
||||
|
|
@ -115,7 +116,7 @@ Remove a package and its source from settings.
|
|||
Alias: ${APP_NAME} uninstall <source> [-l]
|
||||
|
||||
Options:
|
||||
-l, --local Remove from project settings (.pi/settings.json)
|
||||
-l, --local Remove from project settings (${CONFIG_DIR_NAME}/settings.json)
|
||||
-a, --approve Trust project-local files for this command
|
||||
-na, --no-approve Ignore project-local files for this command
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue