Benjamin Diedrichsen 0993a4d3bb [keyman] portability, and stop keys from being silently invisible
Four things that each made keyman quietly less useful than it looked.

**Clipboard.** `pbcopy` was spawned unconditionally, with a comment admitting
it. Copy is now a list of commands per platform — pbcopy, clip, and wl-copy /
xclip / xsel tried in order on everything else, because there is no single
answer under Linux and trying them beats detecting the session type. Only an
absent tool advances to the next candidate: one that ran and refused has an
opinion. And if nothing is installed the key is printed, since "give me this
public key" is answerable without a clipboard and used to be a dead end
everywhere but macOS. Verified the round trip through real pbcopy/pbpaste.

**Home directories.** `/home/<user>` was hardcoded — wrong on the platform
this was written on. A named user is now looked for beside the current user's
home first, which is right wherever homes live together whatever that
directory is called, then in /home and /Users, and the failure names every
path tried instead of feeding a nonexistent one to readdir. For the current
user, `HOME` still wins, with `os.userInfo()` behind it: `process.env.HOME ||
''` made an unset HOME fatal, which it is not in a cron job or a container.

**Keys that are not named id_*.** A key called `deploy_ed25519` was absent
from every menu with nothing said. It still is — the vault stores
`<name minus id_>/id_<name>.age` and decrypt rebuilds the filename from the
directory, so relaxing discovery means changing the on-disk layout, which the
plan sizes as its largest single item and is not folded in here. What it does
do is say so: any file whose first line carries a private key header and whose
name lacks the prefix is now reported, per directory, with the reason. A
bounded 64-byte read, because classifying a key is no reason to load one.

**Plaintext hygiene.** A "Clear decrypted keys" entry, defaulting to no and
listing what it would delete first, and a vault `.gitignore` written on first
run covering the age identity and the tmp directory — which the README asked
the user to do by hand. Never overwritten, and silent about a configured
directory that sits outside the vault, since a .gitignore cannot speak for a
path above itself and pretending otherwise reads as protection that is absent.
2026-07-30 15:42:35 +02:00
2026-07-29 13:21:04 +02:00
2026-07-27 13:09:00 +02:00
2026-07-29 13:21:04 +02:00
2026-07-29 13:21:04 +02:00
2026-07-29 13:21:04 +02:00
2026-07-29 13:21:04 +02:00
2026-07-29 13:07:34 +02:00
2026-07-29 13:21:04 +02:00
2026-07-27 17:07:18 +02:00
2026-07-29 13:21:04 +02:00
2026-07-29 13:21:04 +02:00
2026-07-29 13:21:04 +02:00

ansiblings

Infrastructure tooling monorepo: two published CLIs plus the pyinfra "cubes" they deploy.

Path Package Binary What it is
packages/nopy @bitsquare/nopy nopy interactive pyinfra script management and execution
packages/keyman @bitsquare/keyman keyman SSH key management with age encryption
cubes/ the deployment units nopy runs
npm install -g @bitsquare/nopy @bitsquare/keyman

See each package's README for usage, and README.PUBLISH.md for how they get published.

Development

Requires Node ≥ 22 (the repo pins 24 in .nvmrc) and pnpm — the version is pinned by packageManager, so corepack enable is enough.

pnpm install
Command Does
pnpm run build compiles both packages with tsc
pnpm run typecheck tsc --build --noEmit across the workspace
pnpm run lint Biome check
pnpm run lint:fix Biome check with fixes applied
pnpm test vitest, both packages
pnpm run test:coverage vitest with the coverage gate
pnpm run coverage:summary renders the last coverage run as a Markdown table

typescript is on the 7.x native compiler, so tsc is the fast one — there is no separate tsgo binary to keep in sync. Each package also has a dev-run script (pnpm --filter @bitsquare/nopy run nopy) that executes the TypeScript sources directly through tsx.

Git hooks

Installed by simple-git-hooks on pnpm install, configured in the root package.json:

  • pre-commit — Biome check with fixes, on staged files only, re-staging what it fixed. Fast; blocks only on problems it cannot fix itself.
  • pre-pushlint:citypechecktest:coverage. This is the same gate CI runs, so a push that survives it will not surprise you on the runner.

Set SKIP_SIMPLE_GIT_HOOKS=1 to bypass either one; re-install them after changing the config with pnpm exec simple-git-hooks.

Coverage

Both packages hold a hard 85 % branch floor, enforced by coverage.thresholds in their vitest.config.ts rather than by a CI-only flag — pnpm run test:coverage fails the same way locally, in the pre-push hook, and on the runner. Barrel files and CLI argv wiring are excluded; everything with behaviour in it is not.

S
Description
No description provided
Readme MIT 1.3 MiB
Languages
TypeScript 87.7%
JavaScript 8.1%
Python 3.9%
Dockerfile 0.2%
Shell 0.1%