-
-
Notifications
You must be signed in to change notification settings - Fork 5
Ability to manage agent profiles #124
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,70 @@ | ||
| # Credential Profiles | ||
|
|
||
| Profiles let you keep multiple credential sets per agent and switch between them | ||
| at run time — for example a Claude subscription login, a separate API-key setup, | ||
| and an environment prepared for Ollama. | ||
|
|
||
| A profile only switches the **credential directories** that get mounted into the | ||
| agent container. Everything else (skills, allowed directories, proxy, logging) | ||
| stays shared. Environment variables such as `ANTHROPIC_API_KEY` are still | ||
| configured via `agents.<agent>.env` or `-e` flags — combine them with a profile | ||
| via a project config (see below). | ||
|
|
||
| ## Layout | ||
|
|
||
| ``` | ||
| ~/.config/vibepod/ | ||
| agents/<agent>/ # the built-in "default" profile | ||
| profiles/<name>/agents/<agent>/ # named profiles | ||
| ``` | ||
|
Comment on lines
+15
to
+19
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win Specify the layout fence language. Line 15 violates markdownlint MD040. Use 🧰 Tools🪛 markdownlint-cli2 (0.23.1)[warning] 15-15: Fenced code blocks should have a language specified (MD040, fenced-code-language) 🤖 Prompt for AI AgentsSource: Linters/SAST tools |
||
|
|
||
| Your existing credentials in `~/.config/vibepod/agents/` are the `default` | ||
| profile — nothing moves when you start using profiles. | ||
|
|
||
| ## Managing profiles | ||
|
|
||
| ```bash | ||
| vp profile list # list profiles; * marks the active one, agents with | ||
| # stored data (config, caches, credentials) in parentheses | ||
| vp profile create work # create an empty profile | ||
| vp profile remove work # delete a profile and its credentials (asks first) | ||
| ``` | ||
|
|
||
| Profile names are lowercase slugs: letters, digits, `-` and `_`. | ||
| The `default` profile always exists and cannot be removed. | ||
|
|
||
| ## Using a profile | ||
|
|
||
| ```bash | ||
| vp run claude --profile work | ||
| vp task create claude "summarize the diff" --profile work | ||
| vp doctor claude --profile work | ||
| ``` | ||
|
|
||
| The first run with a fresh profile starts unauthenticated — log in once and the | ||
| credentials are persisted inside that profile. | ||
|
|
||
| ## Selecting a profile without the flag | ||
|
|
||
| Resolution order (first match wins): | ||
|
|
||
| 1. `--profile` flag | ||
| 2. `VP_PROFILE` environment variable | ||
| 3. `profile:` key in the merged config (global `~/.config/vibepod/config.yaml` | ||
| or project `.vibepod/config.yaml`) | ||
| 4. `default` | ||
|
|
||
| Pinning a profile per project pairs well with per-project env vars: | ||
|
|
||
| ```yaml | ||
| # .vibepod/config.yaml — a project wired to a local Ollama | ||
| version: 1 | ||
| profile: ollama | ||
| agents: | ||
| claude: | ||
| env: | ||
| ANTHROPIC_BASE_URL: http://host.docker.internal:11434 | ||
| ``` | ||
|
|
||
| Referencing a profile that does not exist is a hard error — create it first | ||
| with `vp profile create <name>`. | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,109 @@ | ||
| """Profile subcommands: manage named agent credential environments.""" | ||
|
|
||
| from __future__ import annotations | ||
|
|
||
| import os | ||
| from typing import Annotated | ||
|
|
||
| import typer | ||
|
|
||
| from vibepod.constants import SUPPORTED_AGENTS | ||
| from vibepod.core.config import get_config | ||
| from vibepod.core.profiles import ( | ||
| DEFAULT_PROFILE, | ||
| create_profile, | ||
| list_profiles, | ||
| profile_agents_root, | ||
| profile_exists, | ||
| remove_profile, | ||
| resolve_profile, | ||
| validate_profile_name, | ||
| ) | ||
| from vibepod.utils.console import console, error, success, warning | ||
|
|
||
| app = typer.Typer(help="Manage credential profiles (separate agent logins per environment)") | ||
|
|
||
|
|
||
| def _agents_with_data(profile: str) -> list[str]: | ||
| """Agents with any stored data (config, caches, credentials) in the profile.""" | ||
| root = profile_agents_root(profile) | ||
| found: list[str] = [] | ||
| for agent in sorted(SUPPORTED_AGENTS): | ||
| agent_dir = root / agent | ||
| if agent_dir.is_dir() and any(agent_dir.iterdir()): | ||
| found.append(agent) | ||
|
Comment on lines
+33
to
+34
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
When a fresh unauthenticated agent writes any cache, settings, session, or Xauthority file into its profile directory, this nonempty-directory check reports that agent under Useful? React with 👍 / 👎. |
||
| return found | ||
|
|
||
|
|
||
| def _active_profile() -> str | None: | ||
| """Resolved active profile, or None when the current selection is broken.""" | ||
| try: | ||
| return resolve_profile(None, get_config()) | ||
| except ValueError as exc: | ||
| warning(str(exc)) | ||
| return None | ||
|
|
||
|
|
||
| @app.command("list") | ||
| def list_() -> None: | ||
| """List profiles, the active one, and which agents have stored data.""" | ||
| active = _active_profile() | ||
| for name in list_profiles(): | ||
| marker = "*" if name == active else " " | ||
| agents = ", ".join(_agents_with_data(name)) | ||
| suffix = f" ({agents})" if agents else "" | ||
| console.print(f"{marker} {name}{suffix}") | ||
|
|
||
|
|
||
| @app.command("create") | ||
| def create( | ||
| name: Annotated[str, typer.Argument(help="Profile name (lowercase slug)")], | ||
| ) -> None: | ||
| """Create a new empty profile.""" | ||
| try: | ||
| path = create_profile(name) | ||
| except ValueError as exc: | ||
| error(str(exc)) | ||
| raise typer.Exit(code=1) from exc | ||
| except OSError as exc: | ||
| error(f"Could not create profile '{name}': {exc}") | ||
| raise typer.Exit(code=1) from exc | ||
| success(f"Created profile '{name}' at {path.parent}") | ||
|
|
||
|
|
||
| @app.command("remove") | ||
| def remove( | ||
| name: Annotated[str, typer.Argument(help="Profile to remove")], | ||
| yes: Annotated[bool, typer.Option("--yes", "-y", help="Skip confirmation")] = False, | ||
| ) -> None: | ||
| """Remove a profile and its stored credentials.""" | ||
| if name == DEFAULT_PROFILE: | ||
| error("Profile 'default' cannot be removed.") | ||
| raise typer.Exit(code=1) | ||
| try: | ||
| validate_profile_name(name) | ||
| except ValueError as exc: | ||
| error(str(exc)) | ||
| raise typer.Exit(code=1) from exc | ||
| if not profile_exists(name): | ||
| error(f"Profile '{name}' does not exist.") | ||
| raise typer.Exit(code=1) | ||
| if not yes: | ||
| typer.confirm( | ||
| f"Remove profile '{name}' and all credentials stored in it?", abort=True | ||
| ) | ||
| try: | ||
| remove_profile(name) | ||
| except ValueError as exc: | ||
| error(str(exc)) | ||
| raise typer.Exit(code=1) from exc | ||
| except OSError as exc: | ||
| error( | ||
| f"Could not remove profile '{name}': {exc}. The profile may be partially " | ||
| "deleted. Files created by agent containers can be owned by another user; " | ||
| "fix ownership (e.g. `sudo chown -R $USER ...`) and retry." | ||
| ) | ||
| raise typer.Exit(code=1) from exc | ||
| if os.environ.get("VP_PROFILE") == name: | ||
| console.print(f"Note: VP_PROFILE still points at removed profile '{name}'.") | ||
| success(f"Removed profile '{name}'") | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win
Clarify that the displayed path is default-profile-only.
Line 3 says credentials persist at
~/.config/vibepod/agents/<agent>/on every run, but named profiles use~/.config/vibepod/profiles/<name>/agents/<agent>/. Describe the shown path as thedefaultprofile location.🤖 Prompt for AI Agents