Merge PR #5687: feat(config): add PI_CONFIG_FILES settings overlay env var (@roboomp)

This commit is contained in:
can1357
2026-07-18 20:12:48 +02:00
5 changed files with 54 additions and 13 deletions
+2 -1
View File
@@ -351,12 +351,13 @@ Extra conditional behavior:
## 6) Storage and config root paths
These are consumed via `@oh-my-pi/pi-utils/dirs` and affect where coding-agent stores data.
These affect where coding-agent stores data and which process-local settings overlays it loads.
| Variable | Default / behavior |
| --------------------- | ----------------------------------------------------------------------------- |
| `PI_CONFIG_DIR` | Config root dirname under home (default `.omp`) |
| `PI_CODING_AGENT_DIR` | Full override for agent directory (default `~/<PI_CONFIG_DIR or .omp>/agent`) |
| `PI_CONFIG_FILES` | Platform path-list of settings overlays (`:` on Unix, `;` on Windows); loaded in order before explicit `--config` overlays |
| `PWD` | Used when matching canonical current working directory in path helpers |
---
+5
View File
@@ -122,6 +122,7 @@ Environment variables are **not** a single settings layer. Each is read by the f
| `OMP_AUTH_BROKER_URL` | `auth.broker.url` | Env value takes precedence over config. |
| `OMP_AUTH_BROKER_TOKEN` | `auth.broker.token` | Env value takes precedence over config. |
| `PI_CODING_AGENT_DIR` | (relocates agent dir) | Moves `config.yml`, `agent.db`, and the whole agent base. |
| `PI_CONFIG_FILES` | CLI config overlays | Platform path-list (`:` on Unix, `;` on Windows); files load in order before `--config` overlays. |
Provider API keys are resolved separately (stored auth, OAuth, `models.yml`, environment, and `.env` files); see [Providers](./providers.md) and the full [Environment variables](./environment-variables.md) reference.
@@ -217,6 +218,10 @@ omp --config ./local/ci-settings.yml "check this failure"
omp --config ./base.yml --config ./experiment.yml "try this model"
```
`--config` is accepted by the default launch command, `acp`, and `models`.
Wrappers may instead set `PI_CONFIG_FILES` to a platform-delimited path list (`:` on Unix, `;` on Windows). Environment overlays load in listed order before explicit `--config` overlays.
Overlay paths are resolved relative to the process working directory (and `~` is expanded). Each overlay must parse as a YAML mapping; a missing file, invalid YAML, or a top-level array/scalar is a hard error — it does **not** silently fall back to lower-precedence settings.
## Path-scoped arrays
+3
View File
@@ -123,6 +123,9 @@
- Isolated the CLIProxyAPI auth-broker import tests from ambient broker configuration so fixture credentials cannot be uploaded to a live broker ([#5782](https://github.com/can1357/oh-my-pi/issues/5782)).
- Fixed disabling **Show Inline Images** leaving previously rendered Kitty graphics over the Ghostty/tmux transcript. The runtime toggle now updates tool and assistant image owners, deletes tracked terminal graphics before replay, and retains hidden read images so they can return when re-enabled.
### Added
- Added `PI_CONFIG_FILES`, a platform-delimited (`:` on Unix, `;` on Windows) environment path-list of settings overlays loaded before `--config` overlays, so wrapper scripts can inject settings without argv surgery ([#5685](https://github.com/can1357/oh-my-pi/issues/5685)).
## [17.0.1] - 2026-07-16
+3 -1
View File
@@ -355,7 +355,9 @@ export class Settings {
this.#cwd = path.normalize(options.cwd ?? getProjectDir());
this.#agentDir = path.normalize(options.agentDir ?? getAgentDir());
this.#configPath = options.inMemory ? null : path.join(this.#agentDir, MAIN_CONFIG_FILENAMES[0]);
this.#configFiles = options.configFiles?.map(file => path.resolve(this.#cwd, expandTilde(file))) ?? [];
const configFiles = process.env.PI_CONFIG_FILES?.split(path.delimiter).filter(Boolean) ?? [];
if (options.configFiles) configFiles.push(...options.configFiles);
this.#configFiles = configFiles.map(file => path.resolve(this.#cwd, expandTilde(file)));
this.#persist = !options.inMemory && options.readOnly !== true;
if (options.overrides) {
+41 -11
View File
@@ -10,6 +10,24 @@ const originalAgentDir = process.env.PI_CODING_AGENT_DIR;
const fallbackAgentDir = path.join(getConfigRootDir(), "agent");
const cliEntry = path.join(import.meta.dir, "..", "src", "cli.ts");
interface CliProcessResult {
exitCode: number;
output: string;
error: string;
}
async function runCliProcess(args: string[], env: NodeJS.ProcessEnv): Promise<CliProcessResult> {
const proc = Bun.spawn([process.execPath, cliEntry, ...args], {
stdout: "pipe",
stderr: "pipe",
env: { ...process.env, NO_COLOR: "1", ...env },
});
const stdout = new Response(proc.stdout).text();
const stderr = new Response(proc.stderr).text();
const [exitCode, output, error] = await Promise.all([proc.exited, stdout, stderr]);
return { exitCode, output, error };
}
beforeEach(() => {
resetSettingsForTest();
testAgentDir = TempDir.createSync("@omp-config-cli-");
@@ -169,18 +187,9 @@ describe("config CLI schema coverage", () => {
});
it("fully flushes JSON larger than a pipe buffer", async () => {
if (!testAgentDir) throw new Error("Test agent directory was not initialized");
const proc = Bun.spawn([process.execPath, cliEntry, "config", "list", "--json"], {
stdout: "pipe",
stderr: "pipe",
env: {
...process.env,
NO_COLOR: "1",
PI_CODING_AGENT_DIR: testAgentDir.path(),
},
const { exitCode, output, error } = await runCliProcess(["config", "list", "--json"], {
PI_CODING_AGENT_DIR: testAgentDir.path(),
});
const stdout = new Response(proc.stdout).text();
const stderr = new Response(proc.stderr).text();
const [exitCode, output, error] = await Promise.all([proc.exited, stdout, stderr]);
expect(exitCode).toBe(0);
expect(error).toBe("");
@@ -188,4 +197,25 @@ describe("config CLI schema coverage", () => {
const parsed: unknown = JSON.parse(output);
expect(parsed).toMatchObject({ modelRoles: { type: "record" } });
});
it("loads PI_CONFIG_FILES overlays in path-list order", async () => {
if (!testAgentDir) throw new Error("Test agent directory was not initialized");
const baseOverlayPath = path.join(testAgentDir.path(), "base-overlay.yml");
const finalOverlayPath = path.join(testAgentDir.path(), "final-overlay.yml");
await Promise.all([
Bun.write(baseOverlayPath, "defaultThinkingLevel: high\n"),
Bun.write(finalOverlayPath, "defaultThinkingLevel: max\n"),
]);
const { exitCode, output, error } = await runCliProcess(["config", "get", "defaultThinkingLevel", "--json"], {
PI_CODING_AGENT_DIR: testAgentDir.path(),
PI_CONFIG_FILES: [baseOverlayPath, finalOverlayPath].join(path.delimiter),
});
expect(exitCode).toBe(0);
expect(error).toBe("");
expect(JSON.parse(output)).toMatchObject({
key: "defaultThinkingLevel",
value: "max",
type: "enum",
});
});
});