diff --git a/docs/environment-variables.md b/docs/environment-variables.md index adf3d310c..d146a97f1 100644 --- a/docs/environment-variables.md +++ b/docs/environment-variables.md @@ -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 `~//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 | --- diff --git a/docs/settings.md b/docs/settings.md index a233b8191..eecd5e942 100644 --- a/docs/settings.md +++ b/docs/settings.md @@ -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 diff --git a/packages/coding-agent/CHANGELOG.md b/packages/coding-agent/CHANGELOG.md index 566b22552..d8e544f71 100644 --- a/packages/coding-agent/CHANGELOG.md +++ b/packages/coding-agent/CHANGELOG.md @@ -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 diff --git a/packages/coding-agent/src/config/settings.ts b/packages/coding-agent/src/config/settings.ts index ea2acfe32..df2513628 100644 --- a/packages/coding-agent/src/config/settings.ts +++ b/packages/coding-agent/src/config/settings.ts @@ -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) { diff --git a/packages/coding-agent/test/config-cli.test.ts b/packages/coding-agent/test/config-cli.test.ts index c2b54ad6c..c7e6ae75a 100644 --- a/packages/coding-agent/test/config-cli.test.ts +++ b/packages/coding-agent/test/config-cli.test.ts @@ -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 { + 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", + }); + }); });