* Add SSH keys and config to credential sharing
Private keys have lived as plaintext-on-disk files guarded only by a
passphrase. This puts them in the same end-to-end-encrypted vaults as
.env secrets, and adds an agent path so a machine can use a key without
ever writing one to its disk.
- `ssh` provider: ~/.ssh as a value bag. Files are picked by sniffing
contents (PRIVATE KEY blocks, ssh-*/ecdsa-*/sk-* public keys) plus
config, config.d/* and allowed_signers. known_hosts and
authorized_keys are host-specific and access-granting, so they need
an explicit --include.
- Each file is one secret carrying a JSON envelope of path, mode and
body. The engine only hands write() the secrets that CHANGED, so a
separate manifest secret would be absent whenever a key's contents
change but the file list doesn't — self-describing values keep every
restore total.
- `logicsrc secrets ssh push|pull|list|agent`, addressed by PERSON not
project: the vault is ssh--<username>, which teams vaults reads as
project ssh, env <username>. One teammate's keys never land in
another's restore; sharing stays a deliberate teams grant.
- Both directions hold back anything that would overwrite a file that
already differs, and say what they skipped. --force opts in. A
restore onto a machine with its own keys is otherwise a way to lose
them.
- Restores chmod each file back to its recorded mode; writeFileSync's
mode applies only on create, so an existing world-readable key would
otherwise stay world-readable. The adapter declares delete:false.
- push warns about passphrase-less private keys before they go up.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* Add worked examples to secrets and secrets ssh help
Commander's usage line shows only the first alias, so `logicsrc secrets`
— the spelling people actually type — was invisible in its own help.
The examples carry it, alongside the flows worth copying: link/up/down,
the ssh backup round trip, and a plan → dry-run → approve sync.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* Advertise the ssh provider on the marketing page
The marketing-drift contract failed the build because `ssh` shipped in the
provider registry with no entry in MARKETING_PROOF -- which is the test
working: it exists so a provider cannot ship while the pages people
actually land on still describe the tool without it.
The proof regex is `/~\/\.ssh|SSH key/` rather than a bare `/SSH/` on
purpose. The provider grid renders every registry `name`, and this one is
"Local SSH directory", so `/SSH/` would already be satisfied by the
generated grid and the provider could ship with no copy written about it
at all -- passing the test while failing its intent. Requiring the path or
the phrase means a human wrote a sentence.
That sentence is the new block in the credential-sharing band: ~/.ssh is a
directory of files whose permission bits are load-bearing, not a set of
KEY=VALUE lines, which is the part that makes this provider different from
the other six. README already named ~/.ssh keys, so it needed no change.
apps/logicsrc-web: 75/75 contract tests pass (was 74 passed, 1 failed).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
The last three PRs all fixed the same class of bug. /credential-sharing
and README.md are hand-written copy; the providers they advertise are a
real registry in @logicsrc/plugin-credential-sharing. Nothing connected
the two, so the `team` provider shipped on 2026-07-13 and three weeks
later both surfaces still described a five-provider tool with no mention
of teams. The docs were right the whole time -- only the pages people
actually land on had gone stale, which is worse, because it reads as
"the product cannot do this" rather than as a documentation gap.
Assert it instead. For every provider in the registry, the Credential
Sharing section and the README must say something that counts as
advertising it. The registry's own `name` cannot be the proof -- `env`
is "Local .env file" and `team` is "LogicSRC Team Vault", neither of
which is how the copy reads -- so each provider declares its own
pattern, and a provider with no declaration fails too. That way adding
a provider forces a deliberate answer about the customer-facing copy.
Verified against the bug it is meant to catch: reverting the team copy
reproduces "These providers ship but /credential-sharing never mentions
them: team", and reverting the README line reproduces the same for
sh1pt and team.
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>