Releases: pathorsAI/patchbay
Release list
patchbay v0.4.0
Added
-
Nine CLIs patchbay refused to verify are now verified.
wrangler,
vercel,neon,supabase,flyctl,doctl,huggingface,stripeand
firebaseused to answerverifywith an excuse and a command to paste —
"the CLI is node-based and slow to start", "that is a network call". Slow is
not a reason:verifyonly runs when you press the button or typepb verify. Each now runs the tool's own check and reports the identity it
answers with, so you can hold it against what the board claimed: the
Cloudflare accounts behind a wrangler token, the Vercel username, the Neon
account and plan, the Supabase projects and org, the Fly and DigitalOcean
accounts, the Hub user and orgs, the Stripe account and key expiry, the
firebase-tools accounts.Failures say one actionable sentence rather than pasting the tool's error
paragraph, and they distinguish three states that used to be one: logged out,
credential rejected, and the network was unreachable — the last of which is
no longer reported as a bad login, because nothing about the credential was
established. Where a check is local rather than a round trip (stripe and
firebase have no read-only command that both names the account and exercises
the credential) the answer says so instead of letting a tick imply more than
it proved.Two hazards are handled rather than discovered later:
neon mestarts a
browser login when there is no credential, so that state is answered from the
tier-1 read without executing anything; andfly auth whoamioffers an
interactive login unless--jsonis passed, so it is. -
kubectl, az and aws answer what a credential can do. The scoped
permissions seam gcloud opened is now filled by three more probes, and
because the panel,pb permsand the MCP tools discover the capability from
the probe rather than from a list, all three arrive with a picker and a
reading and no wiring of their own.kubectlasks per namespace: the picker merges the namespaces the kubeconfig
names withkubectl get namespaceswhen the cluster answers — the kubeconfig
half matters because where~/.kube/configis a directory of per-cluster
files, kubectl's own loader refuses it and patchbay's parse is the only thing
that still has the list. The reading iskubectl auth can-i --list, tried in
-o jsonand falling back to parsing the table for the versions that have no
such flag, rendered asget,list,watch pods.azasks per subscription, and
that list costs nothing:azureProfile.jsonalready holds it, so unlike
gcloud's the picker needs no grant to populate. Roles come fromaz role assignment list --all --include-inherited, and one granted on a single
resource group is labelledContributor on resourceGroups/webrather than
being flattened into the subscription it is not held across.awshas no picker, because an AWS credential's permissions are not asked
about anywhere — the identity is what it is. It resolves that identity
throughsts:GetCallerIdentity, which needs no permission, then reads the
attached and inline policies. When that read is refused the report says so
exactly: the identity is known, and listing its policies needs
iam:ListAttachedUserPolicies, which is itself a permission most keys lack.
An empty list would have read as "this credential can do nothing", which is
the opposite of what is known. An SSO or assumed-role identity is named as
the role it is instead of being asked a user-policy question it cannot
answer.Every failure on these paths comes back as one actionable sentence. That is
not cosmetic: a GKE context whose gcloud login has gone stale answersauth can-iwith twenty lines of klog headers, a nestedconfig-helper
transcript, and gcloud's own four-line "Please run:" block, none of which is
the answer.
Changed
-
Notes carry a severity.
ToolStatus.noteswas aVec<String>— an
untyped dumping ground that the panel rendered one way: every line behind the
same amber warning triangle. So "docker has no active registry (normal for
docker)" looked exactly like "credentials.db is unreadable", and a board of
healthy tools read as a wall of complaints. Each note is now aNotewith a
kindofinfo,warnorproblem.infodraws no glyph and does not
count towards the card's badge;warnkeeps the amber triangle;problem
gets a red one.ToolStatus::note()is gone, replaced byinfo(),warn()
andproblem()so the judgement has to be made at every call site.Breaking JSON change.
notesinpb --json, in every MCP tool result,
and onPermissionsReportand the MCP-client report, is now an array of
{"kind": "info"|"warn"|"problem", "text": "…"}rather than an array of
strings. There is deliberately no back-compat shim: a consumer that keeps
treating notes as strings should fail loudly rather than print[object Object]. -
Expiry carries its own state.
expires_at: nullmeant three unrelated
things — this never expires, this expires but the timestamp is somewhere
patchbay will not read, and this expires but the CLI renews it silently — and
thirteen probes each wrote their own paragraph of prose explaining which one
applied.Profile.expiryis now anExpiry:at,no_expiry,
unknown { reason }orrefreshable { access_token_expires }. The panel
shows "no expiry", "expiry unknown" or "auto-renewed" accordingly, with the
reason as the chip's tooltip, and only a real deadline takes a colour.Profile.expires_atis still in the JSON, unchanged in meaning: a timestamp
for a real deadline,nullfor the other three. It is now derived from
expiryrather than stored beside it, so the two can never disagree. -
"This tool has no active X" is a property, not a note. New
ToolStatus.active_concept. rclone, npm, docker, ssh, stripe, flyctl, op and
supabase say it once, in the type; the panel renders an em dash with the
explanation as a tooltip instead of eight tools each filing a warning about
working as designed. -
patchbay's own execution switch no longer surfaces as a caveat about your
login. Five sites reportedcommand execution is disabled for this probe
as a user-facing reason. NewSwitchOutcome::ExecDisabled/
VerifyOutcome::ExecDisabledstates instead: the panel greys the button and
explains in a tooltip, and the CLI prints one short line. -
Notes that only restated something already on the row are gone: profile
counts, tunnel-name counts, MCP-server counts, "AWS_PROFILE is not set, so
the default profile is in effect" (inverted — it now speaks up only when the
variable is set), and the neon config-directory trivia the advisory already
covers. -
infisicalstill reports no permissions, but says why in one clause — its
CLI has no command that reports a member's role — instead of sending you off
to click around in a dashboard.
Fixed
-
The panel was silently dropping every advisory. Core has always
serializedadvisoriesonToolStatusand the CLI has always rendered them,
but the panel's TypeScriptToolStatusdid not declare the field — so a tool
that had been removed or abandoned looked identical to a healthy one. The
drawer now has an advisories section above the notes, with the source link
and a louder treatment for the blocking kinds. -
A purely informational MCP message was wearing the red error banner. The
project-scope note in the MCP server drawer explains that patchbay declines
to write another project's config — a deliberate boundary, not a failure. It
now renders as a quiet notice.
What's Changed
- [refactor] notes carry a severity; expiry carries its own state by @YJack0000 in #18
- [feature] nine CLIs that patchbay refused to verify are now verified by @YJack0000 in #16
- [feature] kubectl, az and aws answer what a credential can do by @YJack0000 in #17
- [fix] kubectl offers a namespace picker even when no context is current by @YJack0000 in #19
Full Changelog: v0.3.4...v0.4.0
patchbay v0.3.4
Added
-
MCP servers can be added, edited, copied and removed from the panel. The
matrix could already show you that Cursor was missing the server Claude Code
has, and then leave you to go and fix it somewhere else. A row now opens a
drawer: the clients that have that server as chips, an editable form for the
one you picked — transport, command and arguments or url, env vars, headers —
a "copy here" beside every client that is missing it, and a remove.+ add serverin the head opens the same drawer empty, with a checkbox per client
to say where it should land.The form always shows exactly one client's copy and says whose, because that
is the truth: six clients keep six files and Cursor's definition of a server
can differ from Codex's, so a save writes one file rather than flattening the
difference. Every write goes through the same core path the CLI uses, keeping
the rolling backup, the parse–modify–serialize round trip and the atomic
rename; the report says which file was written, where the backup is, and
repeats core's caveats, the restart hint included. Saving is an explicit
button — nothing autosaves, and closing a dirty drawer asks first. A Claude
Code entry in a project scope is shown and explained rather than offered:
patchbay does not write that scope, and now says so where you would try.The value boundary the matrix was built on is unchanged.
mcp_list, which
fills the table and refreshes after every write, still reports env var and
header names and a count of arguments. Values are read by one new command,
for one named server of one named client, because you opened its drawer —
and a copy still reports which values travelled between files by name. -
gcloud permissions are read, not described.
permissionsfor gcloud used
to answer "IAM roles are per-project and per-resource; patchbay does not
resolve them yet" and hand back agcloud projects get-iam-policyline to
paste — a bare command where an action would have worked, which is the one
thing CONTRIBUTING says the panel does not do. It now runs the read itself
and reports the account's roles on the project.That needed a shape the report did not have, because IAM grants live on the
resource: a Google account has no roles of its own, only roles on a
project. So permissions became optionally scoped, followingverify_profile
exactly.Probegainspermission_scopes()andpermissions_in(scope),
both defaulted, so the other 24 probes are untouched and gh and wrangler
behave as before;PermissionsReportgainsscope, omitted from JSON when
there is none. The panel shows a searchable project picker (type to filter,
arrows and enter to choose, the configured project preselected) that appears
only once the backend says the tool has scopes — listing them execs gcloud,
so nothing runs until you press the button.pb permsgains--scopeand
--list-scopes; the MCPget_permissionsgains an optionalscope, beside
a newlist_permission_scopestool.Two things that stayed deliberate. The unscoped read resolves the active
configuration'score/projectand reads that, the same moveverifymakes
with the active profile, rather than answering a question it could work out
for itself. And the copyable line survives in exactly one place — when there
is no gcloud onPATHto run, so patchbay genuinely cannot answer. -
The frontend's hardcoded
PERMISSIONS_TOOLSset is gone. Which tools can
report permissions is the backend's fact, answered bysupported, not a list
in the UI that goes stale the moment a probe learns a new trick.
Fixed
-
The main pane no longer scrolls sideways. 0.3.3 stopped the window
sliding as one sheet but left the board scrolling in both axes, so reaching
the last client column of the MCP matrix dragged the view's title, its notes
and its "Config paths" list off to the left with it — everything you needed
as a reference left the screen at the moment you needed it. The board now
scrolls down only; the tables scroll inside their own frame, which is what
that frame was always for. Errors wrap instead of running off the edge, since
a clipped error is worse than a wide one.The MCP table also stopped asking for width it never used: its floor was
760px against the 686px of pane a 900px window gives, so the matrix scrolled
even when it would have fitted. Six client columns of one glyph each carried
10px of side padding around nothing; at 7px, and with the floor at 640px —
the same as the vault's, and above the ~613px the headers themselves need —
a typical matrix comes to 666px and fits the smallest window with room over.
What's Changed
- [fix] main pane never scrolls sideways; wide tables scroll in their own container by @YJack0000 in #12
- [feature] MCP servers: add, edit, copy and remove from the panel by @YJack0000 in #14
- [feature] gcloud permissions: pick a project, read its IAM roles in-panel by @YJack0000 in #15
Full Changelog: v0.3.3...v0.3.4
patchbay v0.3.3
Fixed
-
wranglerandrclonehad the same borrowed-hour bug 0.3.2 fixed in three
other probes, and 0.3.2's sweep stopped at the probes that happened to be
on the reporter's board. Both store an OAuth access token's expiry and both
renew that token themselves — wrangler on the next command, rclone by
refreshing and rewritingrclone.conf— so a live Cloudflare grant read
"expired 154d" and Drive remotes read expired for as long as you had not used
them. Each now keeps the timestamp only where nothing can renew it: wrangler
when the config has norefresh_token, rclone when the remote's token blob
has none. rclone learns that by reducingrefresh_tokento a bool at the
point it reads the blob, so no part of the token outlives the parse.The full audit, so the next reader does not have to redo it:
aws(SSO
session — a real deadline, kept),stripe(stripe loginkeys expire and are
not renewed, kept),az/gh/claude/cloudflared/flyctl(already
reported unknown).huggingfacestoresexpires_atbeside an optional
refresh_tokenand is the one case left unverified — see below. -
The window no longer scrolls as one sheet.
.bodycarriedmin-height: 0
but notmin-width: 0, and as a grid item it therefore took its min-content
width — which for a flex container is content-driven even when its children
may shrink. One wide row of cards, or one long unbroken path in a note, sized
that column past the window and took the header, the sidebar and the title
bar scrolling sideways with it. Measured at a 420px viewport: the document
was 506px wide. Containment is now structural —html/body/#rootare
overflow: hidden,.bodyismin-width: 0and hidden, and the board
scrolls itself in both axes — so the chrome stays put and only the region you
are reading moves.
Changed
- Opening a tool now verifies it. Every profile in the drawer that has no
verdict yet is checked on the way in, rather than showing a button and
waiting to be asked a second time — the click that opened the drawer is the
consent for the tier-2 call. Once per tool: a row that has been checked, or
is being checked, is skipped, so the 30s board poll cannot turn it into a
loop, and re-checking on demand is still the row's own button. The board
itself stays tier 1 — file reads only, no network — because a status surface
that shells out to twenty-five CLIs every thirty seconds is a different
program.
What's Changed
- [fix] finish the refreshable-token sweep; contain the panel's scrolling by @YJack0000 in #11
Full Changelog: v0.3.2...v0.3.3
patchbay v0.3.2
Fixed
-
Verifying a profile now verifies that profile. The panel's per-row
check has always sent the profile id, and the Tauri command has always thrown
it away and asked about whichever profile was active — so on agcloudboard
with two configurations, pressing "verify" on the inactive one reported the
active account's answer under the inactive one's row. Two rows, one truth,
filed under both. The command now callsRegistry::verify_profile, and
GcloudProbeimplements it:gcloud auth print-access-token --account=<the account that configuration names>, which is per-invocation and so never
activates what it checks. -
gcloudno longer reports a login as expired because its access token
cache went stale.access_tokens.dbhas atoken_expirycolumn, and it is
not the answer it looks like: it dates a one-hour OAuth access token that
gcloud refreshes silently on the next call. Read as the profile's expiry it
marked a working login "expired 3d" the moment you stopped using it for an
afternoon, counted it in the board's expired tally, and — because a token
minted this second is one hour out, well inside the 24h attention window —
meantgcloudcould never once read Connected. What actually ends a gcloud
session is revocation or an org reauthentication policy, and neither is
written to this machine, so the expiry is nowNone(unknown, as it is) with
a note saying so and pointing at verify.pb verify gcloudis what answers
the question that column was pretending to. -
A failed
gcloudcheck reads as one sentence instead of gcloud's error.
A reauth failure is four lines of shell instructions, which the panel joined
into…non-interactive execution.; Please run:; $ gcloud auth login; to obtain…. Reauthentication, a revoked credential and a missing credential are
now each named in a line that ends with the command that fixes it, for the
account it is actually about; anything else keeps gcloud's own first line
minus the prefix that only repeats what patchbay just ran. An account with no
row incredentials.dbis answered from tier 1 without spawning anything,
and agcloudthat is not onPATHisunsupportedrather than an error. -
gcloud's IAM hint is a command you can run. It carried<project>and
<account>placeholders while patchbay had both values on the row directly
above, and its--flatten=bindings[].memberswas unquoted — a glob, which
zsh answers withno matches foundbefore gcloud ever starts. Now filled in,
quoted, and the report names the account as its subject. -
firebaseandneonwere dating the same borrowed hour. Both stored an
OAuth access token's expiry as the profile's, and both papered over it with a
note — so the board readfirebase — expired 235dandneon — expired 12d
for two logins that work, and counted them in the expired tally on every
refresh. Neither now reports an expiry it cannot stand behind, and both draw
the line at the thing that actually decides it:firebasekeeps the hour
only for a grant with norefresh_tokenbeside it (presence read through
serde::de::IgnoredAny, so the value is still never held), andneonkeeps
it only for a grant without anofflinescope, where the hour really is the
whole login. The notes say what is unknown instead of apologising for what
was shown. -
The permissions button no longer offers to "re-read scopes" for a tool
patchbay has no scope reader for, where a second press cannot say anything
the first did not.
What's Changed
- [fix] an hourly token is not a login's expiry; verify the profile you pressed by @YJack0000 in #10
Full Changelog: v0.3.1...v0.3.2
patchbay v0.3.1
Security
-
Every third-party action in CI and the release workflow is pinned to a full
commit SHA, with the tag it came from kept in a trailing comment. A moving
tag is a standing invitation: whoever controls the action's repository can
change what runs in a job that holds the Apple signing certificate. One trap
worth recording, because it turns a hardening into a broken build if you miss
it —dtolnay/rust-toolchainderives the channel it installs from
github.action_ref, which is exactly how@stablemeans stable. Pinned to a
SHA that default becomes the SHA, so every use now namestoolchain: stable
outright. -
bun installruns with--ignore-scripts. Nothing in the front end's
tree needs a lifecycle script — esbuild and the tauri CLI ship their platform
binaries as optional dependencies rather than postinstall downloads — so the
scripts were only ever an unused path from a compromised package to a runner
that holds the release secrets. -
release.ymliscontents: readat the workflow level, and only the
releasejob raises itself tocontents: write. The three jobs that build
and sign now carry a token that cannot publish a release. -
Every
cargoinvocation takes--locked, so CI fails on a lockfile that
has drifted from the manifests instead of quietly resolving a different
dependency tree than the one that was reviewed. -
The keychain script fetches Apple's CA anchors over HTTPS only
(--proto '=https' --proto-redir '=https' --tlsv1.2). It passes-L, so
without the redirect guard a 302 intohttp://would have been followed and
the trust anchors taken in the clear.
Added
- A
core (Linux)CI job, retiring a TODO that turned out to be wrong about
its own premise.patchbay-corehas nocfg(target_os)branch anywhere: the
macOS-shaped tool locations are plain strings built under a home directory the
caller supplies, and the suite supplies a synthetic one; the Keychain path is
covered throughMemoryKeystore, so no test shells out tosecurity. The
core suite was already portable, and this job is what keeps it that way — it
goes red the moment core reaches for a real$HOMEor a platform cfg. It is
not a claim that patchbay runs on Linux.pb, the panel and the probes are
still macOS-only, which is why exactly one crate is built there.
Fixed
-
The panel's clickable surfaces are real controls. The tool card, the
profile row's switch target, the copyable command and the detail scrim were
each a non-interactive element wearingrole="button", atabIndexand a
hand-written Enter/Space handler; they are now<button>s, and the detail
drawer is a native<dialog>— the same pair the key vault's drawer has used
since it landed. The focus ring, the keyboard behaviour and the announced role
come from the element instead of being re-implemented beside it. Nothing moves
on screen: a button may only hold phrasing content, so the layout elements
inside became spans and the stylesheet carries the boxes. -
The boot splash's light-mode text sat at 4.1:1 on its background, just under
WCAG AA, while being the only thing on screen. It is now 5.3:1. -
headlineExpirysorts with an explicit comparator. The default sort compares
UTF-16 code units, which happens to be right for RFC 3339 stamps but said so
nowhere.
What's Changed
- [docs] readme: drop the duplicated unlinked Migrate bullet by @YJack0000 in #7
- [fix] clear the SonarCloud board: CI supply-chain hardening + native controls in the panel by @YJack0000 in #8
- [chore] release 0.3.1 by @YJack0000 in #9
Full Changelog: v0.3.0...v0.3.1
patchbay v0.3.0
Added
-
patchbay updates itself — the panel checks the signed release feed a
couple of seconds after launch and, when a newer build exists, says so in a
slim banner above the board:update and relaunch, ornot now. Applying is
always a click — patchbay is a thing you open to answer a question about your
logins, and an update must never be what happens instead — and the banner sits
in the flow rather than over the board, so the answer you came for is never
covered. The download is verified against a minisign public key compiled into
the app before anything is installed, which is the whole reason this is
allowed to be automatic at all. A failed check is silent: an offline machine
has no update to offer, and that is not news.not nowlasts for the session
and is written nowhere, because something you have neither accepted nor
refused should be asked again next launch.pb check-updatesgained the
matching row: patchbay reports its own installed version (the build answering
the question, not whicheverpbis onPATH) against the newest GitHub
release, on the same 24-hour cache and the same shared rate limit as every
other tool. ItsUPDATE WITHis a human instruction — download the DMG, curl
the CLI tarball — the waygcloud's is, rather than a command that does not
exist. -
The panel writes to the key vault —
add keyopens a form (id, provider,
label, a masked secret field, with purpose, scopes, expiry, endpoint and the
rotation checkbox folded away), and every row gets a trash affordance behind
an inline confirm that says what removing does not do: the entry and its
keychain value go, the credential keeps working until you revoke it at the
provider. Both commands (key_add,key_remove) are thin wrappers over the
sameKeyRegistrycalls the CLI makes, so the registry's rules — duplicate id
refused unless you are rotating, empty secret refused, both-or-neither writes —
and its error strings reach the panel verbatim. The vault view no longer tells
you to go and use the command line. The secret exists in the field, the invoke
payload andKeyRegistry::add, and nowhere else: it is cleared on submit,
never logged, never echoed back. The panel still cannot read a value —
get_secretis not wired up, there is no reveal and no copy, andpb key copy
remains the only way one leaves the vault. The old CLI-only rule was about
argv (ps, shell history), and a password field in a native window has
neither. -
The project env vault (
pb env) — 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.envanywhere. 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,
whichpb env pullreplaces wholesale from Infisical, andlocal, which you
set by hand and which wins on merge. Those are.env.localsemantics, 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'sDATABASE_URLinto 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
last4is recorded: four characters oftrueor5432is not a hint, it is
the value.pb env pullalso 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.tomlholding
project = "<id>", written bypb env initunless--no-marker— resolves
a checkout by its content, so a freshgit cloneworks 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'sprojects.jsonrather 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: bring
projects.jsonover, clone the
repo (the marker travels with it),pb env pull.pb exportcarries that
manifest inside the bundle andpb importregisters what is not here yet, so
the migration path is the normal route and copying the file by hand is the
fallback; a project id the destination already has is skipped with a note
rather than overwritten, because the machine in front of you may be the newer
one. What does not travel:attachments.json, since those are paths from
a machine that is not this one; every variable value in either layer; and the
local layer's variable names along with them, because a name with no value
behind it would makepb env listpromise whatpb env runcould not
deliver.SETUP.md, thepb planchecklist and theplan_setupMCP tool
each carry onepb env pull --project <id>per linked project, marked
auto: falseand naming the pinned account — a pull under the wrong
machine-global infisical login fails confusingly, sopb use infisical <email>may have to come first. A project the old machine had unlinked but
with a synced layer is a gap instead: nothing on the new machine can rebuild
it. -
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.initin a worktree of a project this machine already
knows attaches it instead of failing on the duplicate id;forgettakes the
project, its Keychain blobs and this machine's attachments, and leaves
committed markers alone (rm .patchbay.toml).listanddiffanswer from
the name lists alone and never touch the Keychain;settakes 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.envinto 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_envand
set_env_var.list_env_projectsreports each project's machine-local
rootsalongside 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_envexecutes the Infisical CLI but its outcome
carries counts and names, not values, so it is ungated;
set_env_varis open likestore_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,
andpb env runin your own terminal is the answer instead.
What's Changed
- [feature] project env vault: pb env with synced/local layers, pull-only Infisical sync, portable manifest by @YJack0000 in #2
- [feature] panel: key vault add/remove — masked secret entry, delete with confirm by @YJack0000 in #3
- [feature] migration: carry the env vault's portable manifest in pb export / import by @YJack0000 in #4
- [feature] panel self-update via tauri-plugin-updater; pb check-updates reports patchbay itself by @YJack0000 in #5
- [chore] release 0.3.0 by @YJack0000 in #6
New Contributors
- @YJack0000 made their first contribution in #2
Full Changelog: v0.2.0...v0.3.0
patchbay v0.2.0
Added
-
ngrok (
network) — the agent's authentication, read fromngrok.ymlat
the v3 platform location, the legacy~/.ngrok2one, or$NGROK_CONFIG.
One profile: the authenticated agent. Namedtunnels:are forwarding rules on
that one account, not identities, so they are reported as a count and a list
inmeta.pb verify ngrokrunsngrok config checkand, when anapi_key
is configured, asks the ngrok API whether it is live — through the key vault's
own HTTP seam, so the check is testable without the network. Neither the
authtoken nor the api_key is ever reported, not even as a last4. -
cloudflared (
network) — Cloudflare Tunnel origin certificates, and which
one is in force. Profiles are the certificates, because that is the identity
dimension: with two*.pemfiles on disk, every command usescert.pem
unlessTUNNEL_ORIGIN_CERTor--origincertnames the other, and nothing on
the machine tells you which. That silent-wrong-account trap gets its own note.
Tunnel credentials are read for their ids and account tags only —
TunnelSecrethas no field to deserialize into — andcert.pemis never
parsed at all, since it carries a private key.config.yml(or
$TUNNEL_CONFIG) contributes the tunnel name and ingress-rule count. -
A registered Cloudflare API token now puts a caveat on the
wranglerrow:
it is a different credential from wrangler's OAuth login, with different
reach, whichwrangler logoutdoes not revoke andwrangler whoamicannot
see. -
Grafana key verification —
pb key verifyand the MCPverify_keynow
understand--provider grafana, asking{endpoint}/api/organd reporting the
org the token belongs to. Grafana tokens are only meaningful against the
instance that issued them, soKeyEntrygains an optionalendpoint
(pb key add --endpoint https://<you>.grafana.net, and the same field on the
MCPstore_key); akeys.jsonwritten before it existed keeps parsing, and
keys without one never grow the field. Agrafanakey deliberately maps to no
probe — there is no Grafana CLI for it to sit beside. -
Machine migration —
pb exportpacks the logins that survive a machine
move into oneage-encrypted.pbxbundle: the portable credential files,
optionally the key vault (--keys, off by default), a secret-free
manifest.json, and a generatedSETUP.mdthat tells a machine with no
patchbay on it how to install one.pb importputs it back, backing up
anything it would replace to<path>.patchbay-bak, and--dry-runprints the
whole plan without writing a byte. Running an import twice produces the same
machine. -
A portability policy per tool (
patchbay_core::migrate::policy). Every
probe declaresPortable,DeviceBoundorPointerOnlywith the reason in
the table —gh's token is in the keychain,tailscale's node key is this
device,ssh's private keys are never touched. A probe added without a policy
fails the test suite. Files are collected and restored throughPathson both
machines independently, so an override on either side is honoured. -
pb plan,pb status --diff <manifest>, and theplan_setup/
mark_setup_doneMCP tools — the gap list, re-derived from the machine in
front of you rather than copied out of the manifest. Each item carries whether
patchbay can close it itself, the exact command, and whether that command
opens a browser.mark_setup_donere-probes rather than believing the agent
that claims it ran something. -
Cloud-sync destinations (iCloud Drive, Dropbox, Google Drive, OneDrive, Box,
pCloud) are refused without--force, with the reason: copying credential
files is the technique sessions get hijacked with, and a bundle in a sync
folder hands a service the whole set. -
No secret value leaves the encrypted payload — not into the manifest,
SETUP.md, a log line, an error,--jsonor an MCP response. Decryption is
in memory only: there is no staging directory, and each file goes straight to
its destination through a temp file in that destination's own directory. -
pb use infisical <email>— the Infisical CLI'suser switchis an
arrow-key picker with no non-interactive form, so patchbay makes the same
change itself: it repointsloggedInUserEmailandLoggedInUserDomainat one
of the accounts already inloggedInUsers. Switching to an email that has
never logged in is refused with the list of ones that have. No credential
moves — every user's JWT stays in the vault backend, so the switch is followed
by a note to runinfisical login statusif the JWT's freshness matters. -
MCP client management — one board for the MCP servers registered across
the AI clients on this machine: Claude Code (~/.claude.json, user and
project scopes), Claude Desktop, Cursor, Codex CLI (config.toml), Windsurf
and VS Code.patchbay_core::mcp_clientsreads all six formats into one
model and writes three of the operations back. -
pb mcp—pb mcp list(the server × client matrix, or--json),
pb mcp add <client> <name>,pb mcp copy <name> --from … --to …(translates
between the JSON and TOML dialects),pb mcp rm <client> <name>. -
MCP tools —
list_mcp_clients,add_mcp_server,copy_mcp_server,
remove_mcp_server, so an agent that just set a server up can register it in
every client the user has. -
Write safety for other tools' config files: a rolling
<path>.patchbay-bak
backup before every change, atomic temp-file-and-rename writes, and
parse-modify-serialize so unknown keys, JSON key order and TOML comments all
survive. Claude Code's project scopes are read and labelled but never written. -
The board reports
env_keys,header_keysand an argument count — never
values, since those fields routinely hold API keys. Acopydoes carry values
(a server that cannot authenticate is useless) and names what travelled.
Changed
patchbay_core::utilnow owns the write-safety machinery MCP client
management introduced —backup,write_atomicand a new
serialize_json_preserving_style— so every probe that edits another tool's
config gets the same rolling.patchbay-bak, the same atomic rename and the
same house style on the way out. The style part is not cosmetic: the Infisical
CLI writes one compact line with": "separators, and re-serializing it
serde_json's way would rewrite every byte of the file for a one-field change.
Fixed
-
kubectl — a
~/.kube/configthat is a directory of per-cluster
kubeconfigs (a common layout, and one kubectl itself chokes on) is now
scanned instead of reported as zero contexts. The*.yaml/*.ymlfiles
directly inside it are merged first-file-wins, each context records the file
it came from, files that are not kubeconfigs are skipped by name, and the
note now spells out theexport KUBECONFIG=…that makes a shell agree.
Because every file carries its owncurrent-context, no active context is
reported unless exactly one file defines contexts. -
15 new probes, taking the board from 8 tools to 23:
vercel,firebase,
neon,docker,tailscale,ssh,stripe,supabase,flyctl,
doctl,npm,op,ollama,huggingface,claude. -
Four new categories —
containers,network,payments,ai. The
panel's sidebar picks them up from the JSON. -
Custom config paths. Every probe honours the environment variable its own
CLI honours, and patchbay gained an optional~/.config/patchbay/config.toml
with a[paths]table for the case where there is no shell environment to
inherit (the panel launched from Finder) or the state lives on another
volume. Precedence is tool variable →[paths]→ platform default, and an
override in effect is named in the tool'snotes. See "Custom paths" in the
README.
Fixed
ghandrclonenow honourXDG_CONFIG_HOME, as those CLIs do.gcloud
deliberately still does not, because it does not either.
Full Changelog: v0.1.0...v0.2.0
patchbay v0.1.0
First public cut. macOS only, and deliberately narrow: read local state, report
it accurately, and never touch a secret value.
Added
patchbay-core— the probe model. Each supported tool has an adapter
that parses that tool's own on-disk state (INI, YAML, TOML, JSON, SQLite) and
reports profiles, which one is active, expiry, and caveats. Tier-1 reads are
file-only and take milliseconds; anything that shells out or reaches the
network is a separate, explicitly-requested tier-2 call.- Probes for 8 tools —
gcloud,aws,gh,infisical,kubectl,
wrangler,rclone,az. Each reports profiles and active profile; where
the tool's state file says so, also token expiry and granted scopes. pbCLI —pb status(the board, as a table or--json),pb use <tool> <profile>(switch the active profile),pb verify <tool>(prove the active
credential still works),pb perms <tool>(what the active credential is
allowed to do).patchbay-mcp— an MCP stdio server overrmcp, exposing
list_connections,get_status,switch_profile,verifyand
get_permissions, with tool descriptions that tell an agent which calls are
cheap, which are expensive, and which mutate machine-global state.- Desktop panel — a Tauri 2 app (React 19 + Vite) with the status board,
per-tool detail, switch, verify and permissions views. - App icon — patch-panel mark, full icon set for every bundle target.
Security
- Probes report metadata about credentials only. Token, secret and passphrase
values are never copied into a status struct, never logged, and never
included in error messages — probes parse the fields they need (expiry,
scopes, account) and drop the rest. - Release artifacts are signed with a Developer ID Application identity. The
.dmgis notarized by Apple with the notarization ticket stapled to it, so it
verifies locally and opens without a Gatekeeper prompt even offline; thepb
andpatchbay-mcpbinaries are signed with a secure timestamp and the
hardened runtime. Forks build unsigned — they have no access to the signing
secrets — and that path is kept working on purpose.
Full Changelog: https://github.com/pathorsAI/patchbay/commits/v0.1.0