Phase 9 of packages/keyman/docs/PLAN.md; closes AUDIT §5.2, §5.3, §5.4, §5.5, and the §4 one-liners no phase had claimed (§4.1–§4.4, §3.7). The README is the only document that ships (package.json files: dist, README.md, LICENSE), and it described four of nine menu entries, invented key rotation, told the user to run ssh-keygen by hand, asked them to write a .gitignore keyman now writes, and mentioned none of the command line. It is rewritten against the code: every operation, the rotate/retire sequence, the id_ prefix and what happens to keys without it, installation with the scope mapping (never a bare --registry, which would send 55 transitive dependencies to a registry that has never heard of them), the configuration semantics including which relative path resolves against what, and the Phase 5 migration for a split vault. The CLI section is helpText() verbatim, with tests/readme.test.ts asserting the two are identical and that every menu label appears — so a flag or an operation added later fails the gate instead of shipping undocumented. That is the part that keeps this from drifting again. Also: index.ts loses the bin's shebang (it is only ever imported), exports the config types so a consumer can name what loadConfig returns, and re-exports the update module wholesale rather than half of it by name — verified by importing the built dist/index.js and reading its keys. The narrow surface is now a comment stating the rule rather than an accident. AUDIT.md marks all 30 findings closed except the second half of §1.8, keeping each finding's text as the record with what closed it quoted underneath, the way DOCS-AUDIT.md does. PLAN.md gains a status section naming the three deviations. Root CLAUDE.md records the keyman architecture as it now is, including the deliberate `resolution` divergence from nopy. DOCS-AUDIT.md §2.10, §6.4 and the §7 keyman-config entry are amended in the working tree but left unstaged, since that file carries unrelated WIP. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
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-push —
lint:ci→typecheck→test: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.