Files
oh-my-pi/packages/coding-agent/test/profile-alias.test.ts
T
Ogrodev 680917f02a feat: added isolated profiles with --profile and --alias
Added named OMP profiles that isolate agent state (auth credentials,
sessions, settings, model cache, history, memories, blobs, plus
config root subdirs) under `~/.omp/profiles/<name>/agent/`. Activated
via `--profile <name>` or `OMP_PROFILE=<name>`; `default` maps back to
the regular `~/.omp/agent/` tree.

Added `--alias <command>` to generate a shell shortcut (e.g.
`omp-work`) that forwards `omp --profile <name>`. Detects the active
shell (bash, zsh, fish, PowerShell, pwsh), writes a wrapper into the
correct rc file, and preserves subcommands like `update`, `--version`,
and `--model` because the wrapper passes through argv unchanged.

The `--profile`/`--alias` bootstrap pre-parser lives in
`packages/coding-agent/src/cli/profile-bootstrap.ts` and runs before
any module that touches `getAgentDir()` (notably `@oh-my-pi/pi-utils/env`,
which eagerly loads `.env` from the agent directory at its own import
time). The pre-parser mirrors `parseArgs` value-consumption rules and
honors `--`, so commands like `omp --system-prompt --profile foo` pass
the literal `--profile` through as the prompt body instead of silently
activating profile `foo`.

XDG resolution for named profiles is keyed on the profile-specific
XDG path (`$XDG_*_HOME/omp/profiles/<name>`), never the base app root,
so a profile's location is decided once at first activation and stays
stable even after `omp config init-xdg` materializes the base later.
The default profile keeps its existing base-app-root check.

`setProfile(undefined)` (and `setProfile("default")`) restores the
pre-profile `PI_CODING_AGENT_DIR` snapshot taken at first activation
instead of unconditionally deleting it. `setAgentDir` refreshes the
snapshot since that call is the user explicitly redefining the
baseline.

Validation rejects profile names that match `.`/`..`, fail
`/^[A-Za-z0-9][A-Za-z0-9._-]{0,63}$/`, or hit a Windows reserved
device name (`CON`, `PRN`, `AUX`, `NUL`, `COM0-9`, `LPT0-9`, including
dotted variants like `CON.txt`) — those would let `setProfile` accept
the input only for directory creation to fail later with confusing
errors on Windows.
2026-05-27 07:50:45 -03:00

108 lines
3.1 KiB
TypeScript

import { describe, expect, it } from "bun:test";
import { installProfileAlias } from "../src/cli/profile-alias";
describe("profile alias installer", () => {
it("writes a bash-compatible alias that forwards subcommands through omp", async () => {
const files = new Map<string, string>();
const result = await installProfileAlias({
profile: "work",
aliasName: "omp-work",
shellPath: "/bin/bash",
platform: "linux",
homeDir: "/home/me",
readFile: async filePath => files.get(filePath) ?? "",
writeFile: async (filePath, content) => {
files.set(filePath, content);
},
});
expect(result.configPath).toBe("/home/me/.bashrc");
expect(files.get("/home/me/.bashrc")).toContain("alias omp-work='command omp --profile work'");
});
it("writes a fish function that forwards argv", async () => {
const files = new Map<string, string>();
await installProfileAlias({
profile: "work",
aliasName: "omp-work",
shellPath: "/opt/homebrew/bin/fish",
platform: "darwin",
homeDir: "/Users/me",
readFile: async filePath => files.get(filePath) ?? "",
writeFile: async (filePath, content) => {
files.set(filePath, content);
},
});
const content = files.get("/Users/me/.config/fish/conf.d/omp-profiles.fish") ?? "";
expect(content).toContain("function omp-work --wraps omp");
expect(content).toContain("command omp --profile work $argv");
});
it("writes a PowerShell function because aliases cannot carry arguments", async () => {
const files = new Map<string, string>();
await installProfileAlias({
profile: "work",
aliasName: "omp-work",
shellPath: "pwsh.exe",
platform: "win32",
homeDir: "C:\\Users\\me",
readFile: async filePath => files.get(filePath) ?? "",
writeFile: async (filePath, content) => {
files.set(filePath, content);
},
});
const content = files.get("C:\\Users\\me/Documents/PowerShell/Microsoft.PowerShell_profile.ps1") ?? "";
expect(content).toContain("function omp-work");
expect(content).toContain("& omp --profile work @args");
});
it("replaces a previous block for the same alias", async () => {
const files = new Map<string, string>([
[
"/home/me/.zshrc",
[
"before",
"# >>> omp profile alias: omp-work >>>",
"alias omp-work='command omp --profile old'",
"# <<< omp profile alias: omp-work <<<",
"after",
].join("\n"),
],
]);
await installProfileAlias({
profile: "work",
aliasName: "omp-work",
shellPath: "/bin/zsh",
platform: "darwin",
homeDir: "/home/me",
readFile: async filePath => files.get(filePath) ?? "",
writeFile: async (filePath, content) => {
files.set(filePath, content);
},
});
const content = files.get("/home/me/.zshrc") ?? "";
expect(content).toContain("before");
expect(content).toContain("after");
expect(content).toContain("alias omp-work='command omp --profile work'");
expect(content).not.toContain("--profile old");
});
it("refuses to shadow the base omp command", () => {
expect(
installProfileAlias({
profile: "work",
aliasName: "omp",
shellPath: "/bin/bash",
homeDir: "/home/me",
}),
).rejects.toThrow("Refusing to shadow");
});
});