feat(coding-agent): export project config dir name
This commit is contained in:
@@ -2,6 +2,14 @@
|
|||||||
|
|
||||||
## [Unreleased]
|
## [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
|
||||||
|
|
||||||
- Fixed `/model` autocomplete and model selection searches to match provider/model queries regardless of whether the provider or model token is typed first.
|
- 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.
|
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()
|
### 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.
|
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
|
type ResourceLoader
|
||||||
createEventBus
|
createEventBus
|
||||||
|
|
||||||
// Helpers
|
// Constants and helpers
|
||||||
|
CONFIG_DIR_NAME
|
||||||
defineTool
|
defineTool
|
||||||
getAgentDir
|
getAgentDir
|
||||||
getPackageDir
|
getPackageDir
|
||||||
|
|||||||
@@ -42,7 +42,7 @@ import { existsSync, readFileSync } from "node:fs";
|
|||||||
import { join } from "node:path";
|
import { join } from "node:path";
|
||||||
import type { Api, Model } from "@earendil-works/pi-ai";
|
import type { Api, Model } from "@earendil-works/pi-ai";
|
||||||
import type { ExtensionAPI, ExtensionContext } from "@earendil-works/pi-coding-agent";
|
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";
|
import { Container, Key, type SelectItem, SelectList, Text } from "@earendil-works/pi-tui";
|
||||||
|
|
||||||
// Preset configuration
|
// Preset configuration
|
||||||
@@ -69,7 +69,7 @@ interface PresetsConfig {
|
|||||||
*/
|
*/
|
||||||
function loadPresets(cwd: string): PresetsConfig {
|
function loadPresets(cwd: string): PresetsConfig {
|
||||||
const globalPath = join(getAgentDir(), "presets.json");
|
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 globalPresets: PresetsConfig = {};
|
||||||
let projectPresets: PresetsConfig = {};
|
let projectPresets: PresetsConfig = {};
|
||||||
@@ -200,7 +200,10 @@ export default function presetExtension(pi: ExtensionAPI) {
|
|||||||
const presetNames = Object.keys(presets);
|
const presetNames = Object.keys(presets);
|
||||||
|
|
||||||
if (presetNames.length === 0) {
|
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;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -308,7 +311,10 @@ export default function presetExtension(pi: ExtensionAPI) {
|
|||||||
async function cyclePreset(ctx: ExtensionContext): Promise<void> {
|
async function cyclePreset(ctx: ExtensionContext): Promise<void> {
|
||||||
const presetNames = getPresetOrder();
|
const presetNames = getPresetOrder();
|
||||||
if (presetNames.length === 0) {
|
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;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,18 +1,18 @@
|
|||||||
import { appendFileSync } from "node:fs";
|
import { appendFileSync } from "node:fs";
|
||||||
import { join } from "node:path";
|
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) {
|
export default function (pi: ExtensionAPI) {
|
||||||
const logFile = join(process.cwd(), ".pi", "provider-payload.log");
|
pi.on("before_provider_request", (event, ctx) => {
|
||||||
|
const logFile = join(ctx.cwd, CONFIG_DIR_NAME, "provider-payload.log");
|
||||||
pi.on("before_provider_request", (event) => {
|
|
||||||
appendFileSync(logFile, `${JSON.stringify(event.payload, null, 2)}\n\n`, "utf8");
|
appendFileSync(logFile, `${JSON.stringify(event.payload, null, 2)}\n\n`, "utf8");
|
||||||
|
|
||||||
// Optional: replace the payload instead of only logging it.
|
// Optional: replace the payload instead of only logging it.
|
||||||
// return { ...event.payload, temperature: 0 };
|
// 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");
|
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 { join } from "node:path";
|
||||||
import { SandboxManager, type SandboxRuntimeConfig } from "@anthropic-ai/sandbox-runtime";
|
import { SandboxManager, type SandboxRuntimeConfig } from "@anthropic-ai/sandbox-runtime";
|
||||||
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
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 {
|
interface SandboxConfig extends SandboxRuntimeConfig {
|
||||||
enabled?: boolean;
|
enabled?: boolean;
|
||||||
@@ -77,7 +77,7 @@ const DEFAULT_CONFIG: SandboxConfig = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
function loadConfig(cwd: string): 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");
|
const globalConfigPath = join(getAgentDir(), "extensions", "sandbox.json");
|
||||||
|
|
||||||
let globalConfig: Partial<SandboxConfig> = {};
|
let globalConfig: Partial<SandboxConfig> = {};
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
|
|
||||||
import * as fs from "node:fs";
|
import * as fs from "node:fs";
|
||||||
import * as path from "node:path";
|
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";
|
export type AgentScope = "user" | "project" | "both";
|
||||||
|
|
||||||
@@ -85,7 +85,7 @@ function isDirectory(p: string): boolean {
|
|||||||
function findNearestProjectAgentsDir(cwd: string): string | null {
|
function findNearestProjectAgentsDir(cwd: string): string | null {
|
||||||
let currentDir = cwd;
|
let currentDir = cwd;
|
||||||
while (true) {
|
while (true) {
|
||||||
const candidate = path.join(currentDir, ".pi", "agents");
|
const candidate = path.join(currentDir, CONFIG_DIR_NAME, "agents");
|
||||||
if (isDirectory(candidate)) return candidate;
|
if (isDirectory(candidate)) return candidate;
|
||||||
|
|
||||||
const parentDir = path.dirname(currentDir);
|
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 { AgentToolResult } from "@earendil-works/pi-agent-core";
|
||||||
import type { Message } from "@earendil-works/pi-ai";
|
import type { Message } from "@earendil-works/pi-ai";
|
||||||
import { StringEnum } 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 { Container, Markdown, Spacer, Text } from "@earendil-works/pi-tui";
|
||||||
import { Type } from "typebox";
|
import { Type } from "typebox";
|
||||||
import { type AgentConfig, type AgentScope, discoverAgents } from "./agents.ts";
|
import { type AgentConfig, type AgentScope, discoverAgents } from "./agents.ts";
|
||||||
@@ -458,8 +464,8 @@ export default function (pi: ExtensionAPI) {
|
|||||||
description: [
|
description: [
|
||||||
"Delegate tasks to specialized subagents with isolated context.",
|
"Delegate tasks to specialized subagents with isolated context.",
|
||||||
"Modes: single (agent + task), parallel (tasks array), chain (sequential with {previous} placeholder).",
|
"Modes: single (agent + task), parallel (tasks array), chain (sequential with {previous} placeholder).",
|
||||||
'Default agent scope is "user" (from ~/.pi/agent/agents).',
|
`Default agent scope is "user" (from ${path.join(getAgentDir(), "agents")}).`,
|
||||||
'To enable project-local agents in .pi/agents, set agentScope: "both" (or "project").',
|
`To enable project-local agents in ${CONFIG_DIR_NAME}/agents, set agentScope: "both" (or "project").`,
|
||||||
].join(" "),
|
].join(" "),
|
||||||
parameters: SubagentParams,
|
parameters: SubagentParams,
|
||||||
|
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import { CONFIG_DIR_NAME } from "../config.ts";
|
||||||
import { emitProjectTrustEvent } from "./extensions/runner.ts";
|
import { emitProjectTrustEvent } from "./extensions/runner.ts";
|
||||||
import type { LoadExtensionsResult, ProjectTrustContext } from "./extensions/types.ts";
|
import type { LoadExtensionsResult, ProjectTrustContext } from "./extensions/types.ts";
|
||||||
import type { DefaultProjectTrust } from "./settings-manager.ts";
|
import type { DefaultProjectTrust } from "./settings-manager.ts";
|
||||||
@@ -21,7 +22,7 @@ export interface ResolveProjectTrustedOptions {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function formatProjectTrustPrompt(cwd: string): string {
|
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(
|
async function selectProjectTrustOption(
|
||||||
|
|||||||
@@ -3,7 +3,15 @@
|
|||||||
export { type Args, parseArgs } from "./cli/args.ts";
|
export { type Args, parseArgs } from "./cli/args.ts";
|
||||||
|
|
||||||
// Config paths
|
// 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 {
|
export {
|
||||||
AgentSession,
|
AgentSession,
|
||||||
type AgentSessionConfig,
|
type AgentSessionConfig,
|
||||||
|
|||||||
@@ -73,7 +73,7 @@ function formatBaseDir(baseDir: string): string {
|
|||||||
return displayPath.endsWith("/") ? displayPath : `${displayPath}/`;
|
return displayPath.endsWith("/") ? displayPath : `${displayPath}/`;
|
||||||
}
|
}
|
||||||
|
|
||||||
function getGroupLabel(metadata: PathMetadata): string {
|
function getGroupLabel(metadata: PathMetadata, agentDir: string): string {
|
||||||
if (metadata.origin === "package") {
|
if (metadata.origin === "package") {
|
||||||
return `${metadata.source} (${metadata.scope})`;
|
return `${metadata.source} (${metadata.scope})`;
|
||||||
}
|
}
|
||||||
@@ -84,12 +84,12 @@ function getGroupLabel(metadata: PathMetadata): string {
|
|||||||
? `User (${formatBaseDir(metadata.baseDir)})`
|
? `User (${formatBaseDir(metadata.baseDir)})`
|
||||||
: `Project (${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";
|
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 groupMap = new Map<string, ResourceGroup>();
|
||||||
|
|
||||||
const addToGroup = (resources: ResolvedResource[], resourceType: ResourceType) => {
|
const addToGroup = (resources: ResolvedResource[], resourceType: ResourceType) => {
|
||||||
@@ -100,7 +100,7 @@ function buildGroups(resolved: ResolvedPaths): ResourceGroup[] {
|
|||||||
if (!groupMap.has(groupKey)) {
|
if (!groupMap.has(groupKey)) {
|
||||||
groupMap.set(groupKey, {
|
groupMap.set(groupKey, {
|
||||||
key: groupKey,
|
key: groupKey,
|
||||||
label: getGroupLabel(metadata),
|
label: getGroupLabel(metadata, agentDir),
|
||||||
scope: metadata.scope,
|
scope: metadata.scope,
|
||||||
origin: metadata.origin,
|
origin: metadata.origin,
|
||||||
source: metadata.source,
|
source: metadata.source,
|
||||||
@@ -601,7 +601,7 @@ export class ConfigSelectorComponent extends Container implements Focusable {
|
|||||||
) {
|
) {
|
||||||
super();
|
super();
|
||||||
|
|
||||||
const groups = buildGroups(resolvedPaths);
|
const groups = buildGroups(resolvedPaths, agentDir);
|
||||||
|
|
||||||
// Add header
|
// Add header
|
||||||
this.addChild(new Spacer(1));
|
this.addChild(new Spacer(1));
|
||||||
|
|||||||
@@ -52,6 +52,7 @@ import { spawn, spawnSync } from "child_process";
|
|||||||
import {
|
import {
|
||||||
APP_NAME,
|
APP_NAME,
|
||||||
APP_TITLE,
|
APP_TITLE,
|
||||||
|
CONFIG_DIR_NAME,
|
||||||
getAgentDir,
|
getAgentDir,
|
||||||
getAuthPath,
|
getAuthPath,
|
||||||
getDebugLogPath,
|
getDebugLogPath,
|
||||||
@@ -3307,7 +3308,7 @@ export class InteractiveMode {
|
|||||||
new Text(
|
new Text(
|
||||||
theme.fg(
|
theme.fg(
|
||||||
"warning",
|
"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,
|
1,
|
||||||
0,
|
0,
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import { selectConfig } from "./cli/config-selector.ts";
|
|||||||
import { createProjectTrustContext } from "./cli/project-trust.ts";
|
import { createProjectTrustContext } from "./cli/project-trust.ts";
|
||||||
import {
|
import {
|
||||||
APP_NAME,
|
APP_NAME,
|
||||||
|
CONFIG_DIR_NAME,
|
||||||
detectInstallMethod,
|
detectInstallMethod,
|
||||||
getAgentDir,
|
getAgentDir,
|
||||||
getPackageDir,
|
getPackageDir,
|
||||||
@@ -93,7 +94,7 @@ function printPackageCommandHelp(command: PackageCommand): void {
|
|||||||
Install a package and add it to settings.
|
Install a package and add it to settings.
|
||||||
|
|
||||||
Options:
|
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
|
-a, --approve Trust project-local files for this command
|
||||||
-na, --no-approve Ignore 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]
|
Alias: ${APP_NAME} uninstall <source> [-l]
|
||||||
|
|
||||||
Options:
|
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
|
-a, --approve Trust project-local files for this command
|
||||||
-na, --no-approve Ignore project-local files for this command
|
-na, --no-approve Ignore project-local files for this command
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user