{"_id":"envsitter","_rev":"4-18eef252d0814f854e72fcd7f4570de6","name":"envsitter","dist-tags":{"latest":"0.0.4"},"versions":{"0.0.1":{"name":"envsitter","version":"0.0.1","license":"MIT","_id":"envsitter@0.0.1","maintainers":[{"name":"boxpositron","email":"npm@boxpositron.dev"}],"bin":{"envsitter":"dist/cli.js"},"dist":{"shasum":"56308793eb5edd1af4f5fe8ab29c2c8936f7b9a9","tarball":"https://registry.npmjs.org/envsitter/-/envsitter-0.0.1.tgz","fileCount":51,"integrity":"sha512-gDJ/ZMjD0z31MSpj88IiaHNdJVfzWpXANm4uLwUD1ScKtt3LaKZYir2nY+peJOBjZM9579d5y+rTIQD+kU4lIA==","signatures":[{"sig":"MEYCIQCpbDmf/TY3Slcg1NpnJ56oyX2ErLVt8+/EQD7M8GNZPgIhALzWoiFwq7GDYcP9jdVBZ+qabW9KHbRGFEnlwMKtLD8I","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":71714},"type":"module","exports":{".":{"types":"./dist/index.d.ts","default":"./dist/index.js"}},"gitHead":"665c4adf695fec71780a4d3f13c6e12e43d098c9","private":false,"scripts":{"test":"npm run build && node scripts/run-tests.mjs","build":"tsc -p tsconfig.json","typecheck":"tsc -p tsconfig.json --noEmit"},"_npmUser":{"name":"boxpositron","email":"npm@boxpositron.dev"},"_npmVersion":"11.3.0","description":"Safely inspect and match .env secrets without exposing values","directories":{},"_nodeVersion":"22.14.0","_hasShrinkwrap":false,"devDependencies":{"typescript":"^5.7.3","@types/node":"^22.10.2"},"_npmOperationalInternal":{"tmp":"tmp/envsitter_0.0.1_1768213251606_0.5132462359875545","host":"s3://npm-registry-packages-npm-production"}},"0.0.2":{"name":"envsitter","version":"0.0.2","license":"MIT","_id":"envsitter@0.0.2","maintainers":[{"name":"boxpositron","email":"npm@boxpositron.dev"}],"bin":{"envsitter":"dist/cli.js"},"dist":{"shasum":"2145162d1647d91787bdb2669bdfb9340962f30e","tarball":"https://registry.npmjs.org/envsitter/-/envsitter-0.0.2.tgz","fileCount":51,"integrity":"sha512-2rVcSQnRrDqk//2XM5c+M85YkNc6ljFgfMC4gBTvA/U21OUGB53zfANIEZZXfOcSVNmoRHVCGcogFHLHsBjd1w==","signatures":[{"sig":"MEUCIERjV+Hil4JZ9wItgmchhn9sS8tb3gImyVz0RTQnREPbAiEAtaElqe/91c1qUZPTgn2XdIWbp/9iqX9V56cg11v6Y/4=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":91717},"type":"module","exports":{".":{"types":"./dist/index.d.ts","default":"./dist/index.js"}},"gitHead":"4568f9d4218200ebad3bd135e538594ed8d0c0a4","private":false,"scripts":{"test":"npm run build && node scripts/run-tests.mjs","build":"tsc -p tsconfig.json","prepare":"husky","typecheck":"tsc -p tsconfig.json --noEmit"},"_npmUser":{"name":"boxpositron","email":"npm@boxpositron.dev"},"_npmVersion":"11.3.0","description":"Safely inspect and match .env secrets without exposing values","directories":{},"_nodeVersion":"22.14.0","_hasShrinkwrap":false,"devDependencies":{"husky":"^9.1.7","typescript":"^5.7.3","@types/node":"^22.10.2"},"_npmOperationalInternal":{"tmp":"tmp/envsitter_0.0.2_1768238300033_0.1791796961001062","host":"s3://npm-registry-packages-npm-production"}},"0.0.3":{"name":"envsitter","version":"0.0.3","keywords":["dotenv","env","secrets","secret","security","hmac","fingerprint","audit","cli","devops","configuration","config"],"license":"MIT","_id":"envsitter@0.0.3","maintainers":[{"name":"boxpositron","email":"npm@boxpositron.dev"}],"bin":{"envsitter":"dist/cli.js"},"dist":{"shasum":"0d5de47f5e6bdf864f7cedfca435d262e27b9e2f","tarball":"https://registry.npmjs.org/envsitter/-/envsitter-0.0.3.tgz","fileCount":71,"integrity":"sha512-l7YuX/ptwODY0ooU5JuPLN5zrNpjoWU5O01Wi66t24FRYLrXK33ReEr3/TP4Fhaa+S4sQ7uBMLIe8VGmgvIs9g==","signatures":[{"sig":"MEQCIFzMv+eVEdbTrfOhq+A6+sM4mErxb5iYgCrzwlHcp7XEAiBO0l2FBD7jHMOg9rxmE3XXyUeZK3mAHqZCkf/Ph5co+A==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":165069},"type":"module","exports":{".":{"types":"./dist/index.d.ts","default":"./dist/index.js"}},"gitHead":"46c7a343556944813707e46ba9a43241617729b9","private":false,"scripts":{"test":"npm run build && node scripts/run-tests.mjs","build":"tsc -p tsconfig.json","prepare":"husky","typecheck":"tsc -p tsconfig.json --noEmit"},"_npmUser":{"name":"boxpositron","email":"npm@boxpositron.dev"},"_npmVersion":"11.3.0","description":"Safely inspect and match .env secrets without exposing values","directories":{},"_nodeVersion":"22.14.0","_hasShrinkwrap":false,"devDependencies":{"husky":"^9.1.7","typescript":"^5.7.3","@types/node":"^22.10.2"},"_npmOperationalInternal":{"tmp":"tmp/envsitter_0.0.3_1768325302891_0.003882902281124423","host":"s3://npm-registry-packages-npm-production"}},"0.0.4":{"name":"envsitter","version":"0.0.4","private":false,"type":"module","description":"Safely inspect and match .env secrets without exposing values","repository":{"type":"git","url":"git+https://github.com/boxpositron/envsitter.git"},"keywords":["dotenv","env","secrets","secret","security","hmac","fingerprint","audit","cli","devops","configuration","config"],"license":"MIT","bin":{"envsitter":"dist/cli.js"},"exports":{".":{"types":"./dist/index.d.ts","default":"./dist/index.js"}},"scripts":{"build":"tsc -p tsconfig.json","typecheck":"tsc -p tsconfig.json --noEmit","test":"npm run build && node scripts/run-tests.mjs","prepare":"husky"},"devDependencies":{"@types/node":"^22.10.2","husky":"^9.1.7","typescript":"^5.7.3"},"_id":"envsitter@0.0.4","gitHead":"55ba6d4b4dfcdda95d50ac905006812ccee45644","bugs":{"url":"https://github.com/boxpositron/envsitter/issues"},"homepage":"https://github.com/boxpositron/envsitter#readme","_nodeVersion":"22.14.0","_npmVersion":"11.3.0","dist":{"integrity":"sha512-Uxy5XpYQ9B6+70cQrIOKQMxaitKjMDxaK1uPaivhtVbYxQMRhJzdvx+6AgSrRrZBcnuPlLbnVPXrn7PXpi+veg==","shasum":"22865ca987393f731545ffebca8d0f3d4c92d9ed","tarball":"https://registry.npmjs.org/envsitter/-/envsitter-0.0.4.tgz","fileCount":75,"unpackedSize":218639,"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEQCIF4kLgDDYarBHpMUGqaA5xvppMDZCbqMFBLMPbH/h8LSAiA1MhHC6c6G/3r3g/xOxRriUWEiz8lPll7MaDbH27SaCQ=="}]},"_npmUser":{"name":"boxpositron","email":"npm@boxpositron.dev"},"directories":{},"maintainers":[{"name":"boxpositron","email":"npm@boxpositron.dev"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/envsitter_0.0.4_1768509459145_0.11189394532537844"},"_hasShrinkwrap":false}},"time":{"created":"2026-01-12T10:20:51.474Z","modified":"2026-01-15T20:37:39.369Z","0.0.1":"2026-01-12T10:20:51.758Z","0.0.2":"2026-01-12T17:18:20.179Z","0.0.3":"2026-01-13T17:28:23.073Z","0.0.4":"2026-01-15T20:37:39.270Z"},"license":"MIT","keywords":["dotenv","env","secrets","secret","security","hmac","fingerprint","audit","cli","devops","configuration","config"],"description":"Safely inspect and match .env secrets without exposing values","maintainers":[{"name":"boxpositron","email":"npm@boxpositron.dev"}],"readme":"# envsitter\n\nSafely inspect and match `.env` secrets **without ever printing values**.\n\n`envsitter` is designed for LLM/agent workflows where you want to:\n\n- List keys present in an env source (`.env` file or external provider)\n- Compare a key’s value to a candidate value you provide at runtime (\"outside-in\")\n- Do bulk matching (one candidate against many keys, or candidates-by-key)\n- Produce deterministic fingerprints for comparisons/auditing\n- Ask boolean questions about values (empty/prefix/suffix/regex/type-ish checks) without ever returning the value\n\nRelated: https://github.com/boxpositron/envsitter-guard — an OpenCode plugin that blocks agents/tools from reading or editing sensitive `.env*` files (preventing accidental secret leaks), while still allowing safe inspection via EnvSitter-style tools (keys + deterministic fingerprints; never values).\n\n## Security model (what this tool does and does not do)\n\n- Values are read in-process for comparisons, but **never returned** by the library API and **never printed** by the CLI.\n- Deterministic matching uses **HMAC-SHA-256** with a local pepper.\n  - This avoids publishing raw SHA-256 hashes that are easy to dictionary-guess.\n- Candidate secrets should be passed via stdin (`--candidate-stdin`) to avoid shell history.\n\nNon-goals:\n\n- This tool is not a secret manager.\n- This tool does not encrypt or relocate `.env` values; it operates on sources in-place.\n\n## Install\n\n```bash\nnpm install envsitter\n```\n\nOr run the CLI without installing globally:\n\n```bash\nnpx envsitter keys --file .env\n```\n\n## Pepper (required for deterministic fingerprints)\n\n`envsitter` uses a local \"pepper\" as the HMAC key.\n\nResolution order:\n\n1. `process.env.ENVSITTER_PEPPER` (or `ENV_SITTER_PEPPER`)\n2. Pepper file at `.envsitter/pepper` (auto-created if missing)\n\nThe pepper file is created with mode `0600` when possible, and `.envsitter/` is gitignored.\n\n## CLI usage\n\n### Quick reference\n\n| Command | Description |\n|---------|-------------|\n| `keys` | List all keys in an env file |\n| `fingerprint` | Get deterministic fingerprint for a key |\n| `match` | Match candidate value(s) against key(s) |\n| `match-by-key` | Bulk match candidates by key |\n| `scan` | Detect value shapes (JWT, URL, base64) |\n| `validate` | Check dotenv syntax |\n| `copy` | Copy keys between env files |\n| `format` / `reorder` | Sort and organize env files |\n| `annotate` | Add comments to keys |\n| `add` | Add new key (fails if exists) |\n| `set` | Create or update key |\n| `unset` | Set key to empty value |\n| `delete` | Remove key(s) from file |\n\n### Commands\n\n- `keys --file <path> [--filter-regex <re>]`\n- `fingerprint --file <path> --key <KEY>`\n- `match --file <path> (--key <KEY> | --keys <K1,K2> | --all-keys) [--op <op>] [--candidate <value> | --candidate-stdin]`\n- `match-by-key --file <path> (--candidates-json <json> | --candidates-stdin)`\n- `scan --file <path> [--keys-regex <re>] [--detect jwt,url,base64]`\n- `validate --file <path>`\n- `copy --from <path> --to <path> [--keys <K1,K2>] [--include-regex <re>] [--exclude-regex <re>] [--rename <A=B,C=D>] [--on-conflict error|skip|overwrite] [--write]`\n- `format --file <path> [--mode sections|global] [--sort alpha|none] [--write]`\n- `reorder --file <path> [--mode sections|global] [--sort alpha|none] [--write]`\n- `annotate --file <path> --key <KEY> --comment <text> [--line <n>] [--write]`\n- `add --file <path> --key <KEY> [--value <v> | --value-stdin] [--write]`\n- `set --file <path> --key <KEY> [--value <v> | --value-stdin] [--write]`\n- `unset --file <path> --key <KEY> [--write]`\n- `delete --file <path> (--key <KEY> | --keys <K1,K2>) [--write]`\n\nNotes for file operations:\n\n- Commands that modify files (`copy`, `format`/`reorder`, `annotate`, `add`, `set`, `unset`, `delete`) are dry-run unless `--write` is provided.\n- These commands never print secret values; output includes keys, booleans, and line numbers only.\n- When targeting example files (`.env.example`, `.env.sample`, `.env.template`, `.env.dist`, `.env.default`), a warning is emitted. Use `--no-example-warning` to suppress.\n- Non-standard env file names are fully supported (e.g., `api.env`, `database.env`, `config.env.local`).\n- Values with special characters (spaces, `#`, quotes, newlines) are automatically double-quoted with proper escaping.\n\n### List keys\n\n```bash\nenvsitter keys --file .env\n```\n\nFilter by key name (regex):\n\n```bash\nenvsitter keys --file .env --filter-regex \"/(KEY|TOKEN|SECRET)/i\"\n```\n\n### Fingerprint a single key\n\n```bash\nenvsitter fingerprint --file .env --key OPENAI_API_KEY\n```\n\nOutputs JSON containing the key’s fingerprint and metadata (never the value).\n\n### Match a candidate against a single key (recommended via stdin)\n\n```bash\nnode -e \"process.stdout.write('candidate-secret')\" \\\n  | envsitter match --file .env --key OPENAI_API_KEY --candidate-stdin --json\n```\n\nExit codes:\n\n- `0` match found\n- `1` no match\n- `2` error/usage\n\n### Match operators (for humans)\n\n`envsitter match` supports an `--op` flag.\n\n- Default: `--op is_equal`\n- When `--op is_equal` is used, EnvSitter hashes both the candidate and stored value with the local pepper (HMAC-SHA-256) and compares digests using constant-time equality.\n- Other operators evaluate against the raw value in-process, but still only return booleans/match results (no values are returned or printed).\n\nOperators:\n\n- `exists`: key is present in the source (no candidate required)\n- `is_empty`: value is exactly empty string (no candidate required)\n- `is_equal`: deterministic match against a candidate value (candidate required)\n- `partial_match_prefix`: `value.startsWith(candidate)` (candidate required)\n- `partial_match_suffix`: `value.endsWith(candidate)` (candidate required)\n- `partial_match_regex`: regex test against value (candidate required; candidate is a regex like `\"/^sk-/\"` or a raw regex body)\n- `is_number`: value parses as a finite number (no candidate required)\n- `is_boolean`: value is `true`/`false` (case-insensitive, whitespace-trimmed) (no candidate required)\n- `is_string`: value is neither `is_number` nor `is_boolean` (no candidate required)\n\nExamples:\n\n```bash\n# Prefix match\nnode -e \"process.stdout.write('sk-')\" \\\n  | envsitter match --file .env --key OPENAI_API_KEY --op partial_match_prefix --candidate-stdin\n\n# Regex match (regex literal syntax)\nnode -e \"process.stdout.write('/^sk-[a-z]+-/i')\" \\\n  | envsitter match --file .env --key OPENAI_API_KEY --op partial_match_regex --candidate-stdin\n\n# Exists (no candidate)\nenvsitter match --file .env --key OPENAI_API_KEY --op exists --json\n```\n\n### Match one candidate against multiple keys\n\n```bash\nnode -e \"process.stdout.write('candidate-secret')\" \\\n  | envsitter match --file .env --keys OPENAI_API_KEY,ANTHROPIC_API_KEY --candidate-stdin --json\n```\n\n### Match one candidate against all keys\n\n```bash\nnode -e \"process.stdout.write('candidate-secret')\" \\\n  | envsitter match --file .env --all-keys --candidate-stdin --json\n```\n\n### Match candidates-by-key (bulk assignment)\n\nProvide a JSON object mapping key -> candidate value.\n\n```bash\nenvsitter match-by-key --file .env \\\n  --candidates-json '{\"OPENAI_API_KEY\":\"sk-...\",\"ANTHROPIC_API_KEY\":\"sk-...\"}'\n```\n\nFor safer input, pass the JSON via stdin:\n\n```bash\ncat candidates.json | envsitter match-by-key --file .env --candidates-stdin\n```\n\n### Scan for value shapes (no values returned)\n\n```bash\nenvsitter scan --file .env --detect jwt,url,base64\n```\n\nOptionally restrict which keys to scan:\n\n```bash\nenvsitter scan --file .env --keys-regex \"/(JWT|URL)/\" --detect jwt,url\n```\n\n### Validate dotenv syntax\n\n```bash\nenvsitter validate --file .env\nenvsitter validate --file .env --json\n```\n\n### Copy keys between env files (production → staging)\n\nDry-run (no file is modified):\n\n```bash\nenvsitter copy --from .env.production --to .env.staging --keys API_URL,REDIS_URL --json\n```\n\nApply changes:\n\n```bash\nenvsitter copy --from .env.production --to .env.staging --keys API_URL,REDIS_URL --on-conflict overwrite --write --json\n```\n\nRename while copying:\n\n```bash\nenvsitter copy --from .env.production --to .env.staging --keys DATABASE_URL --rename DATABASE_URL=STAGING_DATABASE_URL --write\n```\n\n### Annotate keys with comments\n\n```bash\nenvsitter annotate --file .env --key DATABASE_URL --comment \"prod only\" --write\n```\n\n### Reorder/format env files\n\n```bash\nenvsitter format --file .env --mode sections --sort alpha --write\n# alias:\nenvsitter reorder --file .env --mode sections --sort alpha --write\n```\n\n### Add a new key (fails if key exists)\n\n```bash\nenvsitter add --file .env --key NEW_API_KEY --value \"sk-xxx\" --write\n# or via stdin (recommended to avoid shell history):\nnode -e \"process.stdout.write('sk-xxx')\" | envsitter add --file .env --key NEW_API_KEY --value-stdin --write\n```\n\n### Set a key (creates or updates)\n\n```bash\nenvsitter set --file .env --key API_KEY --value \"new-value\" --write\n# or via stdin:\nnode -e \"process.stdout.write('new-value')\" | envsitter set --file .env --key API_KEY --value-stdin --write\n```\n\n### Unset a key (set to empty value)\n\n```bash\nenvsitter unset --file .env --key OLD_KEY --write\n```\n\n### Delete keys\n\n```bash\n# Single key:\nenvsitter delete --file .env --key DEPRECATED_KEY --write\n\n# Multiple keys:\nenvsitter delete --file .env --keys OLD_KEY,UNUSED_KEY,LEGACY_KEY --write\n```\n\n## Output contract (for LLMs)\n\nGeneral rules:\n\n- Never output secret values; treat all values as sensitive.\n- Prefer `--candidate-stdin` over `--candidate` to avoid shell history.\n- Exit codes: `0` match found, `1` no match, `2` error/usage.\n\nJSON outputs:\n\n- `keys --json` -> `{ \"keys\": string[] }`\n- `fingerprint` -> `{ \"key\": string, \"algorithm\": \"hmac-sha256\", \"fingerprint\": string, \"length\": number, \"pepperSource\": \"env\"|\"file\", \"pepperFilePath\"?: string }`\n- `match --json` (single key) ->\n  - default op (not provided): `{ \"key\": string, \"match\": boolean }`\n  - with `--op`: `{ \"key\": string, \"op\": string, \"match\": boolean }`\n- `match --json` (bulk keys / all keys) ->\n  - default op (not provided): `{ \"matches\": Array<{ \"key\": string, \"match\": boolean }> }`\n  - with `--op`: `{ \"op\": string, \"matches\": Array<{ \"key\": string, \"match\": boolean }> }`\n- `match-by-key --json` -> `{ \"matches\": Array<{ \"key\": string, \"match\": boolean }> }`\n- `scan --json` -> `{ \"findings\": Array<{ \"key\": string, \"detections\": Array<\"jwt\"|\"url\"|\"base64\"> }> }`\n- `validate --json` -> `{ \"ok\": boolean, \"issues\": Array<{ \"line\": number, \"column\": number, \"message\": string }> }`\n- `copy --json` -> `{ \"from\": string, \"to\": string, \"onConflict\": string, \"willWrite\": boolean, \"wrote\": boolean, \"hasChanges\": boolean, \"issues\": Array<...>, \"plan\": Array<...> }`\n- `format --json` / `reorder --json` -> `{ \"file\": string, \"mode\": string, \"sort\": string, \"willWrite\": boolean, \"wrote\": boolean, \"hasChanges\": boolean, \"issues\": Array<...> }`\n- `annotate --json` -> `{ \"file\": string, \"willWrite\": boolean, \"wrote\": boolean, \"hasChanges\": boolean, \"issues\": Array<...>, \"plan\": { ... } }`\n- `add --json` / `set --json` / `unset --json` -> `{ \"file\": string, \"key\": string, \"willWrite\": boolean, \"wrote\": boolean, \"hasChanges\": boolean, \"issues\": Array<...>, \"plan\": { \"key\": string, \"action\": \"added\"|\"updated\"|\"unset\"|\"key_exists\"|\"not_found\"|\"no_change\", \"line\"?: number } }`\n- `delete --json` -> `{ \"file\": string, \"keys\": string[], \"willWrite\": boolean, \"wrote\": boolean, \"hasChanges\": boolean, \"issues\": Array<...>, \"plan\": Array<{ \"key\": string, \"action\": \"deleted\"|\"not_found\", \"line\"?: number }> }`\n\n## Library API\n\n### Basic usage\n\n```ts\nimport { EnvSitter } from 'envsitter';\n\nconst es = EnvSitter.fromDotenvFile('.env');\n\nconst keys = await es.listKeys();\nconst fp = await es.fingerprintKey('OPENAI_API_KEY');\nconst match = await es.matchCandidate('OPENAI_API_KEY', 'candidate-secret');\n```\n\n### File operations via the library\n\n```ts\nimport {\n  addEnvFileKey,\n  annotateEnvFile,\n  copyEnvFileKeys,\n  deleteEnvFileKeys,\n  formatEnvFile,\n  setEnvFileKey,\n  unsetEnvFileKey,\n  validateEnvFile\n} from 'envsitter';\n\nawait validateEnvFile('.env');\n\nawait copyEnvFileKeys({\n  from: '.env.production',\n  to: '.env.staging',\n  keys: ['API_URL', 'REDIS_URL'],\n  onConflict: 'overwrite',\n  write: true\n});\n\nawait annotateEnvFile({ file: '.env', key: 'DATABASE_URL', comment: 'prod only', write: true });\nawait formatEnvFile({ file: '.env', mode: 'sections', sort: 'alpha', write: true });\n\n// Add a new key (fails if exists)\nawait addEnvFileKey({ file: '.env', key: 'NEW_KEY', value: 'new_value', write: true });\n\n// Set a key (creates or updates)\nawait setEnvFileKey({ file: '.env', key: 'API_KEY', value: 'updated_value', write: true });\n\n// Unset a key (set to empty)\nawait unsetEnvFileKey({ file: '.env', key: 'OLD_KEY', write: true });\n\n// Delete keys\nawait deleteEnvFileKeys({ file: '.env', keys: ['DEPRECATED', 'UNUSED'], write: true });\n```\n\n### Utility functions\n\n```ts\nimport { isExampleEnvFile } from 'envsitter';\n\n// Detect example/template env files\nisExampleEnvFile('.env.example');      // true\nisExampleEnvFile('api.env.sample');    // true\nisExampleEnvFile('.env');              // false\nisExampleEnvFile('api.env');           // false\n```\n\n### Match operators via the library\n\n```ts\nimport { EnvSitter } from 'envsitter';\nimport type { EnvSitterMatcher } from 'envsitter';\n\nconst es = EnvSitter.fromDotenvFile('.env');\n\nconst matcher: EnvSitterMatcher = { op: 'partial_match_prefix', prefix: 'sk-' };\nconst ok = await es.matchKey('OPENAI_API_KEY', matcher);\n```\n\n### Bulk matching\n\n```ts\nimport { EnvSitter } from 'envsitter';\n\nconst es = EnvSitter.fromDotenvFile('.env');\n\n// One candidate tested against a set of keys\nconst matches = await es.matchCandidateBulk(['OPENAI_API_KEY', 'ANTHROPIC_API_KEY'], 'candidate-secret');\n\n// One matcher tested against a set of keys\nconst prefixMatches = await es.matchKeyBulk(['OPENAI_API_KEY', 'ANTHROPIC_API_KEY'], { op: 'partial_match_prefix', prefix: 'sk-' });\n\n// Candidates-by-key\nconst byKey = await es.matchCandidatesByKey({\n  OPENAI_API_KEY: 'sk-...',\n  ANTHROPIC_API_KEY: 'sk-...'\n});\n```\n\n### External sources (hooks)\n\nYou can load dotenv-formatted output from another tool/secret provider:\n\n```ts\nimport { EnvSitter } from 'envsitter';\n\nconst es = EnvSitter.fromExternalCommand('my-secret-provider', ['export', '--format=dotenv']);\nconst keys = await es.listKeys();\n```\n\n## Development\n\n```bash\nnpm install\nnpm run typecheck\nnpm test\n```\n\nRun a single test file:\n\n```bash\nnpm run build\nnode --test dist/test/envsitter.test.js\n```\n\nRun a single test by name:\n\n```bash\nnpm run build\nnode --test --test-name-pattern \"outside-in\" dist/test/envsitter.test.js\n```\n\n## License\n\nMIT. See `LICENSE`.\n","readmeFilename":"README.md","homepage":"https://github.com/boxpositron/envsitter#readme","repository":{"type":"git","url":"git+https://github.com/boxpositron/envsitter.git"},"bugs":{"url":"https://github.com/boxpositron/envsitter/issues"}}