Releases: pathorsAI/patchbay
Release list
patchbay v0.7.0
Added
-
The vault is now something an agent looks in. Sixty-five keys were in it,
and the agent working in your repo still asked you for
CLOUDFLARE_API_TOKEN— or wrote<your-token-here>and moved on, or
reported the variable as missing. Nothing refused it. It never looked, and
two things made that the rational move. A key was named only by slug
(cf-gh-actions-deploy), so the name code actually reads had no path to the
entry that holds it; the mapping existed as prose inside a--purpose
string, which is not an index. And an agent that did look dead-ended anyway,
becauseget_keyis gated and there was no way to use a key without
reading it. A vault you cannot search and cannot spend gets worked around.So keys have a second name.
KeyEntry.envis the environment variable the
key is exposed as —CLOUDFLARE_API_TOKEN,NEON_API_KEY— validated as
UPPER_SNAKE_CASE (A–Z, digits,_, starting with a letter, at most 64
characters), which is the shape a shell and every dotenv parser accept. A
refusal spells the corrected name rather than restating the rule, so
cf-tokencomes back with "tryCF_TOKEN" and the fix is a copy-paste. It
is optional on purpose: an Apple issuer id or a D-U-N-S number belongs in the
vault as something you would otherwise go hunting for, but it is not a
variable any program reads and inventing a name for it would be inventing a
fact. Two entries may share a name — a dev and a production
LANGFUSE_SECRET_KEYare two secrets under one identifier, which is the
normal shape — so nothing refuses it and a lookup returns both. Akeys.json
written before the field existed parses unchanged, and the field is omitted
from the JSON when unset.The convention the docs now state, because a name only helps if it is the
name the other end already uses: use the name code already reads. If the
repo, the CI secret or the vendor's SDK spells itCLOUDFLARE_API_TOKEN,
that is the name; patchbay's job is to be findable by what exists, not to
impose a taxonomy on it. Only when nothing has named it yet do you compose
one as<PROVIDER>_<THING>_<KIND>—NEON_API_KEY,APPLE_ASC_ISSUER_ID,
GITHUB_APP_PRIVATE_KEY,R2_SECRET_ACCESS_KEY— with the kind drawn from
_API_KEY,_API_TOKEN,_SECRET,_SECRET_KEY,_PRIVATE_KEY,
_PASSWORD,_ID,_URL. Theidstays the lowercase slug and stays what
you type in a command;envis what everything else searches by. -
pb key run <id>... -- <cmd>— spend a key without reading it. The only
way a value came out of the vault waspb key copy, which goes to the
clipboard: right for a human with a browser tab open, useless to a process,
and not something an agent should be doing at all.runresolves each id,
takes the value from the keychain, and starts the command with those
variables added to the environment it inherits. The value goes keychain →
child and touches nothing else — not stdout, not a log, not argv, not a file,
and not the context of whatever model asked for it. stderr names the
variables and the ids they came from, so you can see what was injected
without seeing what was injected.--as NAME=<id>covers the case where this
program wants a different name than the entry's, and an id with noenvand
no--asis refused by name with thepb key edit … --envthat fixes it,
because a run that silently dropped a credential fails somewhere much less
obvious. This is the second way a value leaves the vault, and the split is
deliberate:copyfor the human,runfor everything else. -
pb key edit <id>— metadata, and only metadata. Every key registered
before this release has noenvname, and a vault is worth the fraction of
it that is findable, so backfilling had to be one command that is obviously
safe to run.editnever opens the keychain and cannot rotate anything:
--env NAME/--no-env, plus--provider,--label,--purpose,
--scopes,--expires,--endpoint, with--no-purpose,--no-expires
and--no-endpointto clear the nullable ones.id,last4and the
registration date stay uneditable — they describe the value in the keychain,
and editing them here would only make the registry lie about it. Rotation
stayspb key add --overwrite, where a secret is actually being handled and
the command should look like it. -
pb key listfilters.--env NAMEanswers "who holds the name my code
reads",--provider Pnarrows to one issuer, and--grep TEXTmatches id,
label, purpose, provider and env name. The table gained an ENV column. A
listing you have to read in full is a listing an agent reads in full. -
MCP:
resolve_env_vars,update_key,list_keysfilters, andenvon
store_key.resolve_env_varsis the tool the two rules above hang off:
give it the variable names the code reads and it answers per name withkeys
(exactenvmatches),suggestionswhen there are none (entries whose
purpose, label or id mention the name or its non-generic tokens),projects
(env-vault environments defining it), astatusofkey/project_var/
both/suggested/missing, anduse— the exactpb key run …or
pb env run …that supplies it. It reads two local JSON files and returns no
value, which is why it is ungated, and it is the reason the answer to "I need
this credential" can be a command rather than a secret.update_keyis
pb key edit's twin over MCP — metadata only, with aclearlist for
purpose,expires_at,endpointandenv— ungated for the same reason:
it cannot reach the keychain. Rotation staysstore_keywithoverwrite.
list_keystakesprovider,envandquery. -
Two rules in the MCP server's instructions. "LOOK HERE BEFORE ASKING FOR
A CREDENTIAL": before asking the human, writing a placeholder or calling a
variable missing, callresolve_env_vars; the answer is how to use the key
viapb key run, never the value itself. And "NAME THE VARIABLE": register
withenvset, following the convention above, and backfill what lacks one
withupdate_key. Instructions are the only part of an MCP server a model
reads before deciding what to do, and every gate in this vault was already
strong enough — what was missing was the sentence telling it to try.
Changed
- The manifest's
KeyRecordcarriesenv.pb manifestexists so a new
machine's agent can plan against what the old one used, and "which variable
is this key" is exactly the kind of thing that plan needs. It is a name, not
a value, so it changes nothing about what the file is safe to commit.
What's Changed
- [feature] key vault: env names, pb key run, and MCP rules that make an agent look here first by @YJack0000 in #25
Full Changelog: v0.6.0...v0.7.0
patchbay v0.6.0
Added
-
pb manifest— the record of what this machine uses, with no credential in
it.manifest.jsonalready existed and already had the right shape: no
secret by construction, and the thingpb plan --manifestplans against. But
it only ever existed inside an encrypted bundle, so getting the readable
half meant producing the dangerous half first and then unpacking it. The one
artifact that was safe to commit was the one you could not get without
encrypting every credential on the machine.pb manifestwrites it on its own, to stdout or-o <file>. It opens no
credential file — not an optimisation, but the point: reading every
credential to produce a file that will hold none of them is exactly the
handling this command exists to avoid. The vault is listed and never
unlocked, MCP servers are named with their env/header variable NAMES and
never their values, andcarriedis empty everywhere because nothing was
carried.Manifests now say which kind they are —
"kind": "inventory"here,
"bundle"inside an export, defaulting tobundleso an older file still
reads. An inventory that claimed things had travelled would be the one lie
this format must never tell.The intended shape: keep it in a repo you sync, and a new machine's whole
setup ispb plan --manifest setup/manifest.json— install this, log into
that — or the same list over MCP, worked one item at a time by an agent. -
write_manifestMCP tool. The one part of a machine move an agent can do
unsupervised, because it touches no credential.pb exportandpb import
stay in the CLI, where the human and the passphrase are.
Fixed
- MCP records no longer claim to have been carried when they were not.
collect_mcpmarked a registrationcarried: truewhenever its spec was
readable, which was true for a bundle and wrong for anything that does not
carry values. Found while building the inventory path; it never affected a
real export, where the two happened to coincide.
What's Changed
- [feature] pb manifest:不含祕密的機器清單,可以進版控 by @YJack0000 in #24
Full Changelog: v0.5.0...v0.6.0
patchbay v0.5.0
Added
-
pb env pullcan read a folder inside an Infisical project, not just its
root. Infisical's secrets are a tree, and a project holding one folder per
service is the ordinary shape — but patchbay had no idea such a thing
existed and always exported from/. A pull aimed at the root of a project
that keeps everything under/outboxdoes not fail: it succeeds, returns
nothing, and reports0 variables, which reads exactly like a project nobody
has filled in yet.pathorsAI/coldmailcould therefore not use the env vault
at all and raninfisical run --path /outbox -- <cmd>by hand.pb env link --project-id <id> --path /outboxnow pins the folder alongside
the account,pb env pullpasses it to the CLI, and every place the sync
config is visible says which folder it is: thesecret path:line under
pb env linkandpb env init, the SYNC column ofpb env projects, the
secret_pathfield on a pull's result, andsync.secret_pathin the MCP
list_env_projects. A pull that comes back empty now names the folder it
read and the command that repoints it, rather than leaving0to be
interpreted.Nothing changes for a project that pulls from the root, which is still the
default and still adds no flag to theinfisicalcommand line. Registries
written by earlier versions have no such field and are read as/, so
projects.jsonneeds no migration and no version bump — the folder inside
the remote is the same string on every machine, so it travels in the portable
manifest exactly like the remote project id beside it.
What's Changed
- [feature] env vault 認得 Infisical 的子路徑 by @YJack0000 in #23
Full Changelog: v0.4.1...v0.5.0
patchbay v0.4.1
Fixed
- The panel no longer claims installed CLIs are "not available on PATH".
macOS launches GUI apps — and the MCP servers GUI clients spawn — with the
bare launchdPATH(/usr/bin:/bin:/usr/sbin:/sbin), so the panel could
not see a gcloud living in~/google-cloud-sdk/binor a Homebrew-installed
gh, and every tier-2 button answered "install it" for a tool that works
fine in the terminal. The panel andpatchbay-mcpnow detect that bare
inheritance at startup and adopt the login shell'sPATH(asked of the
user's own shell, with a hard timeout), so they resolve exactly the
binaries a terminal would. Terminal launches are untouched: aPATHwith
any user entry on it is left alone.
What's Changed
- [chore] migrate Sonar analysis to self-hosted SonarQube by @YJack0000 in #20
- [feature] leave a sticky Quality Gate comment on PRs by @YJack0000 in #21
- [fix] adopt the login shell PATH when launched outside a terminal by @YJack0000 in #22
Full Changelog: v0.4.0...v0.4.1
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