Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
67 changes: 67 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,73 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
values, since those fields routinely hold API keys. A `copy` does carry values
(a server that cannot authenticate is useless) and names what travelled.

- **The project env vault** ([`pb env`](docs/env-vault.md)) — the variables a
*project* needs, held the way the key vault holds credentials: names and
provenance in `~/.config/patchbay/projects.json` (`0600`), values in the macOS
Keychain, and no plaintext `.env` anywhere. A project is a portable **name**,
not a path: the manifest holds ids, environments and sync config and no
absolute path at all, so copying it to another machine is the supported way to
take your projects with you. Each of its environments has two layers: `synced`,
which `pb env pull` replaces wholesale from Infisical, and `local`, which you
set by hand and which wins on merge. Those are `.env.local` semantics, and
they only hold because **patchbay never pushes** — there is no code path that
writes a variable to a remote, so a local override is invisible to the cloud
by construction rather than by policy, and a pull can never carry your
container's `DATABASE_URL` into the team's shared set. Values are stored one
Keychain item per project × environment × layer (account
`env:<project>/<env>/<local|synced>`), holding the whole layer as one JSON
blob, so an export is one Keychain round trip and not one per variable. No
`last4` is recorded: four characters of `true` or `5432` is not a hint, it is
the value. `pb env pull` also pins the account a project belongs to and checks
it before spending a subprocess — the Infisical CLI's active login is
machine-global, and under the wrong one the API answers 403 with "project does
not belong to your selected organization", which reads like a problem with the
project rather than with the login; patchbay refuses first and names
`pb use infisical <email>` instead.
- **Two ways a directory resolves to a project**, in that order. An
**attachment** (`pb env attach <id>` / `pb env detach`) binds a directory on
this machine, in `~/.config/patchbay/attachments.json` — deepest attached
ancestor wins, several roots per project, so every worktree and second clone
shares one vault. A **marker** — a committed `.patchbay.toml` holding
`project = "<id>"`, written by `pb env init` unless `--no-marker` — resolves
a checkout by its content, so a fresh `git clone` works on any machine whose
registry holds that project, with no attach step. An attachment always beats a
marker: a deliberate local act outranks whatever the repo ships, and nothing
in a repo can take that override back. A marker can only *name* a project the
machine already has, and one that names an unknown project is a loud error
pointing at the machine's `projects.json` rather than a silent miss. The
tradeoff, taken deliberately: repo content selects which registered project's
variables the tooling hands out, which assumes you run repos you trust.
- **Moving to a new machine** is therefore: copy `projects.json`, clone the repo
(the marker travels with it), `pb env pull`. Attachments deliberately do not
travel — they are paths from a machine that is not this one — and neither does
the local layer, since a `DATABASE_URL` pointing at a container on the old
laptop is exactly what must not follow you.
- **`pb env`** — `init` (registers the project, attaches this directory, leaves
a marker to commit, picking up `.infisical.json`), `attach`, `detach`,
`link`, `projects`, `list`, `pull`, `set`, `unset`, `import`, `diff`, `run`,
`export`, `forget`. `init` in a worktree of a project this machine already
knows attaches it instead of failing on the duplicate id; `forget` takes the
project, its Keychain blobs and this machine's attachments, and leaves
committed markers alone (`rm .patchbay.toml`). `list` and `diff` answer from
the name lists alone and never touch the Keychain; `set` takes its value from
stdin or a hidden prompt, never argv; `run -- <cmd>` injects the merged
environment into one child process and is the blessed read path, with
`export` (dotenv or JSON, TTY warning) there for the cases where a file is
genuinely what you need.
`import <file>` bulk-loads an existing `.env` into the local layer,
all-or-nothing, reporting a bad line by number and never by content.
- **MCP tools** — `list_env_projects`, `list_env_vars`, `pull_env` and
`set_env_var`. `list_env_projects` reports each project's machine-local
`roots` alongside its environments, and says what an empty list means, so an
agent does not read a path there as where the user is working. The first two
are metadata only; `pull_env` executes the Infisical CLI but its outcome
carries counts and names, not values, so it is ungated;
`set_env_var` is open like `store_key`, so an agent that creates a
project credential registers it. Nothing reads a value back — not even behind
`PATCHBAY_ALLOW_SECRET_READ`. An environment is dozens of secrets at once,
and `pb env run` in your own terminal is the answer instead.

### Changed

- `patchbay_core::util` now owns the write-safety machinery MCP client
Expand Down
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
- **Permissions** — see what your tokens can actually do (`gh` scopes today) and fix missing scopes with one hint.
- **[MCP client management](docs/mcp-clients.md)** — every MCP server registered in Claude Code, Claude Desktop, Cursor, Codex, Windsurf and VS Code in one matrix; copy a server between clients without hand-editing four files in two formats.
- **[Key vault](docs/key-vault.md)** — standalone API keys no CLI tracks: values in the macOS Keychain, metadata on disk, provider-aware `pb key verify`, and AI registration over MCP.
- **[Project env vault](docs/env-vault.md)** — a project's environment variables without a plaintext `.env`: pull from Infisical, keep hand-set local overrides that never sync back, run a command with the merged result. A project is a portable name, not a path — copy one file to a new machine, clone the repo, pull.
- **[Keeping CLIs current](#keeping-clis-current)** — which tools are outdated, which were renamed out from under you, and the exact command to update each one.
- **Migrate** — export to a new machine; whatever can't travel, your AI walks you through re-authing.
- **[Migrate](docs/migration.md)** — export to a new machine; whatever can't travel, your AI walks you through re-authing.
Expand All @@ -52,6 +53,7 @@ pb status # the whole board in your terminal
pb use gcloud work # switch a profile
pb verify gh # actually check a token against its API
pb key list # your registered API keys
pb env run -- bun dev # this directory's env vars, from the Keychain, no .env file
```

Or just open the panel: search with `/`, filter by category or connection state, click a card to operate that tool.
Expand Down Expand Up @@ -129,6 +131,7 @@ cd app && bun install && bun run tauri dev # the panel (Tauri 2 + React

- [Moving to a new machine](docs/migration.md)
- [Key vault — security model](docs/key-vault.md)
- [Project env vault — two layers, pull-only](docs/env-vault.md)
- [MCP client management](docs/mcp-clients.md)
- [Contributing & development](CONTRIBUTING.md)
- [Changelog](CHANGELOG.md)
Expand Down
Loading
Loading