Library API
envpkt is available as a TypeScript library with a functional programming API built on functype. All functions return Either<Error, Result> or Option<T> — no thrown exceptions.
import { boot, loadConfig, computeAudit, scanFleet } from "envpkt"Boot API
Section titled “Boot API”High-level API that loads config, resolves catalog, audits, and optionally injects secrets. Both
functions are async: await them. Without await, boot() returns before it injects anything, so
the next line reads process.env without the secrets. From CommonJS, use boot().then(…).
boot(options)
Section titled “boot(options)”const result = await boot({ configPath: "envpkt.toml", inject: true })console.log(result.audit.status) // "healthy" | "degraded" | "critical"Rejects with EnvpktBootError on failure.
bootSafe(options)
Section titled “bootSafe(options)”const safe = await bootSafe({ configPath: "envpkt.toml" })safe.fold( (err) => console.error("Boot failed:", err._tag), (result) => console.log(`${result.injected.length} secrets injected`),)Resolves to Either<BootError, BootResult>. An unexpected crash inside boot (a bug, or a logger
that throws) rejects with the original error instead of becoming a Left.
BootOptions
Section titled “BootOptions”type BootOptions = { readonly configPath?: string readonly profile?: string readonly inject?: boolean readonly failOnExpired?: boolean readonly warnOnly?: boolean readonly logger?: DirectLogger // from functype-log; see "Diagnostic logging" below}Diagnostic logging
Section titled “Diagnostic logging”bootSafe emits structured trace events at each resolution decision point.
Pass a DirectLogger via BootOptions.logger to observe them; omit for
silent default (zero overhead).
import { bootSafe, createDirectConsoleLogger } from "envpkt"
const logger = createDirectConsoleLogger({ level: "debug" })await bootSafe({ logger })Emitted events (all scoped with { component: "envpkt.boot" }):
| Level | Message | Metadata |
| ------- | ------------------------------- | ---------------------------------------- |
| debug | alias.validate.success | { aliases: number } |
| warn | alias.validate.failed | { tag: AliasError._tag, key?: string } |
| warn | phase.sealed.decrypt_failed | { message: string } |
| debug | phase.sealed.resolved | { key: string } |
| warn | phase.fnox.export_failed | { message: string, skipped: number } |
| debug | phase.fnox.resolved | { key: string, profile?: string } |
| debug | phase.fnox.not_in_export | { key: string, profile?: string } |
| debug | phase.fnox.unavailable | { skipped: number } |
| debug | phase.alias.copied | { alias: string, target: string } |
| debug | phase.alias.target_unresolved | { alias: string, target: string } |
For tests, use createDirectTestLogger which captures entries in memory:
import { bootSafe, createDirectTestLogger } from "envpkt"
const { logger, entries, hasEntry } = createDirectTestLogger()await bootSafe({ logger })expect(hasEntry("debug", "phase.alias.copied")).toBe(true)BootResult
Section titled “BootResult”type BootResult = { readonly audit: AuditResult readonly injected: ReadonlyArray<string> readonly skipped: ReadonlyArray<string> readonly secrets: Readonly<Record<string, string>> readonly warnings: ReadonlyArray<string> readonly envDefaults: Readonly<Record<string, string>> readonly overridden: ReadonlyArray<string> // Logical key → injected wire name (e.g. API_KEY → CIV__API_KEY). // Identity map when no [namespace] is configured. secrets/envDefaults // stay keyed by the logical name; this recovers the actual process.env name. readonly envNames: Readonly<Record<string, string>> readonly configPath: string readonly configSource: ConfigSource}ConfigSource
Section titled “ConfigSource”type ConfigSource = "flag" | "env" | "cwd" | "search"Indicates how the config was resolved: explicit --config flag, ENVPKT_CONFIG env var, current working directory, or the discovery search chain.
Config Operations
Section titled “Config Operations”loadConfig(path)
Section titled “loadConfig(path)”Load and validate an envpkt.toml file. Returns Either<ConfigError, EnvpktConfig>.
const config = loadConfig("envpkt.toml")config.fold( (err) => console.error("Failed:", err._tag), (config) => console.log(config),)resolveConfigPath(flagPath?, envVar?, cwd?)
Section titled “resolveConfigPath(flagPath?, envVar?, cwd?)”Resolve config path via priority chain: explicit flag → ENVPKT_CONFIG env var → CWD + discovery chain. Returns Either<ConfigError, ResolvedPath>.
type ResolvedPath = { readonly path: string; readonly source: ConfigSource }discoverConfig(cwd?)
Section titled “discoverConfig(cwd?)”Discover config by checking CWD, then ENVPKT_SEARCH_PATH, then built-in candidate paths (~/.envpkt/, cloud storage, etc.). Returns Option<{ path: string; source: "cwd" | "search" }>.
loadConfigFromCwd(cwd?)
Section titled “loadConfigFromCwd(cwd?)”Load config from CWD or discovery chain. Returns Either<ConfigError, { path: string; source: string; config: EnvpktConfig }>.
findConfigPath(dir)
Section titled “findConfigPath(dir)”Check for envpkt.toml in a specific directory. Returns Option<string>.
parseToml(content)
Section titled “parseToml(content)”Parse raw TOML string. Returns Either<ConfigError, unknown>.
validateConfig(data)
Section titled “validateConfig(data)”Validate parsed data against the schema. Returns Either<ConfigError, EnvpktConfig>.
Alias Resolution
Section titled “Alias Resolution”validateAliases(config)
Section titled “validateAliases(config)”Validate all from_key references in a resolved config. Returns Either<AliasError, AliasTable>. Called automatically inside bootSafe before the secret resolution passes.
import { validateAliases } from "envpkt"
validateAliases(config).fold( (err) => console.error("Alias error:", err._tag), (table) => console.log(`${table.entries.size} alias(es) resolved`),)AliasTable
Section titled “AliasTable”type AliasTable = { readonly entries: ReadonlyMap< string, { readonly kind: "secret" | "env"; readonly targetKind: "secret" | "env"; readonly targetKey: string } >}Keys are type-prefixed ("secret.<KEY>" or "env.<KEY>") so the two namespaces don’t collide.
Runtime behavior
Section titled “Runtime behavior”- Alias entries appear in
BootResult.injectedalongside their target when the target resolves; inBootResult.skippedwhen the target doesn’t. BootResult.secrets[aliasKey]holds the same value assecrets[targetKey].AuditResult.secretsincludes alias rows withalias_of: Option<string>and status inherited from the target;AuditResult.aliasescounts them separately.- Cross-type and chained aliases fail at load time with
AliasErrorvariants — see Error Types → AliasError.
MCP Header Bindings
Section titled “MCP Header Bindings”Build the HTTP headers that envpkt headers prints for Claude Code’s headersHelper.
buildMcpHeaders(config, values, server)
Section titled “buildMcpHeaders(config, values, server)”Collect every secret whose mcp list names server and merge them into one header object. values is keyed by logical secret name, as in BootResult.secrets, so aliases and namespaces are already resolved. Returns Either<McpHeadersError, Record<string, string>>. Only this server’s bindings are checked.
import { bootSafe, buildMcpHeaders, formatMcpHeadersError, loadConfig } from "envpkt"
const config = loadConfig("./envpkt.toml").orThrow()
const booted = await bootSafe({ inject: false })booted.fold( (err) => console.error(err._tag), (boot) => buildMcpHeaders(config, boot.secrets, "civala-github").fold( (err) => console.error(formatMcpHeadersError(err)), (headers) => console.log(headers), // { Authorization: "Bearer ..." } ),)validateMcpHeaders(config)
Section titled “validateMcpHeaders(config)”Check every mcp binding: header names and schemes must be HTTP tokens, and no (server, header) pair may be claimed twice. Returns Either<McpConfigError, void>. envpkt validate and the CLI write gate call it.
formatMcpHeadersError(error)
Section titled “formatMcpHeadersError(error)”One-line message for any McpHeadersError. It names the server and secret keys and never includes a secret value.
McpHeaderSchema
Section titled “McpHeaderSchema”TypeBox schema for one mcp entry: { server, header?, scheme? }.
Catalog Resolution
Section titled “Catalog Resolution”resolveConfig(config, baseDir)
Section titled “resolveConfig(config, baseDir)”Resolve catalog references. Returns Either<CatalogError, ResolveResult>.
import { loadConfig, resolveConfig } from "envpkt"import { dirname } from "node:path"
const configPath = "agents/pipeline/envpkt.toml"loadConfig(configPath).fold( (err) => console.error(err), (config) => { resolveConfig(config, dirname(configPath)).fold( (err) => console.error("Catalog error:", err._tag), (result) => { console.log("Resolved keys:", result.merged) console.log("Overridden:", result.overridden) }, ) },)ResolveResult
Section titled “ResolveResult”type ResolveResult = { readonly config: EnvpktConfig readonly catalogPath?: string readonly merged: ReadonlyArray<string> readonly overridden: ReadonlyArray<string> readonly warnings: ReadonlyArray<string>}Audit Engine
Section titled “Audit Engine”computeAudit(config)
Section titled “computeAudit(config)”Compute audit results for a config. Returns AuditResult.
const audit = computeAudit(config)console.log(audit.status) // "healthy" | "degraded" | "critical"audit.secrets.forEach((s) => { console.log(`${s.key}: ${s.status}`)})computeEnvAudit(config)
Section titled “computeEnvAudit(config)”Compute drift results for [env.*] entries. Returns EnvAuditResult.
const envAudit = computeEnvAudit(config)console.log(`${envAudit.total} env defaults, ${envAudit.defaults_applied} using default`)envAudit.entries.forEach((e) => { console.log(`${e.key}: ${e.status}`) // "default" | "overridden" | "not_set"})EnvAuditResult
Section titled “EnvAuditResult”type EnvAuditResult = { readonly entries: ReadonlyArray<EnvDriftEntry> readonly total: number readonly defaults_applied: number readonly overridden: number readonly not_set: number}EnvMeta
Section titled “EnvMeta”type EnvMeta = { readonly value: string readonly purpose?: string readonly comment?: string readonly tags?: Record<string, string>}Fleet Scanner
Section titled “Fleet Scanner”scanFleet(dir, options?)
Section titled “scanFleet(dir, options?)”Scan a directory tree for envpkt.toml files. Returns FleetHealth.
const fleet = scanFleet("/opt/agents", { maxDepth: 3 })console.log(`${fleet.total_agents} agents, ${fleet.total_secrets} secrets`)Environment Scanning
Section titled “Environment Scanning”envScan(env)
Section titled “envScan(env)”Scan an environment object for credentials. Returns ScanResult.
const scan = envScan(process.env)console.log(`Found ${scan.discovered.size} credentials`)envCheck(config, env)
Section titled “envCheck(config, env)”Bidirectional drift detection. Returns CheckResult.
const check = envCheck(config, process.env)if (!check.is_clean) { console.log(`${check.missing_from_env} missing, ${check.untracked_credentials} untracked`)}generateTomlFromScan(matches)
Section titled “generateTomlFromScan(matches)”Generate TOML [secret.*] blocks from scan results.
matchEnvVar(name, value)
Section titled “matchEnvVar(name, value)”Match a single env var against all patterns. Returns Option<MatchResult>.
matchEnvVar("OPENAI_API_KEY", "sk-test123").fold( () => console.log("Not a credential"), (m) => console.log(`Matched: ${m.confidence} confidence`),)Packet Formatting
Section titled “Packet Formatting”formatPacket(resolveResult, options?)
Section titled “formatPacket(resolveResult, options?)”Produce a human-readable text summary of a resolved config.
import { formatPacket } from "envpkt"
// Without secretsconst text = formatPacket(resolveResult)
// With masked secretsconst masked = formatPacket(resolveResult, { secrets: { DATABASE_URL: "postgres://user:pass@host/db" },})
// With plaintext secretsconst plain = formatPacket(resolveResult, { secrets: { DATABASE_URL: "postgres://user:pass@host/db" }, secretDisplay: "plaintext",})maskValue(value)
Section titled “maskValue(value)”Mask a secret value for display (e.g., postgres://user:pass@host/db → pos•••••t/db).
Keygen API
Section titled “Keygen API”resolveKeyPath()
Section titled “resolveKeyPath()”Resolve the age identity file path: -o flag > ENVPKT_AGE_KEY_FILE env var > ~/.envpkt/age-key.txt. Returns string.
resolveInlineKey()
Section titled “resolveInlineKey()”Check for inline age key in ENVPKT_AGE_KEY env var (for CI). Returns Option<string>.
generateKeypair(options?)
Section titled “generateKeypair(options?)”Generate an age keypair in-process and write it to disk with 0600 permissions. Never overwrites an
existing file. Resolves to Either<KeygenError, KeygenResult>.
import { generateKeypair } from "envpkt"
const result = await generateKeypair() // writes to resolveKeyPath()result.fold( (err) => console.error(`Keygen failed: ${err._tag}`), (res) => console.log(`Recipient: ${res.recipient}`),)Options: { outputPath?: string }. outputPath defaults to resolveKeyPath(). It is used as given,
so ~ is not expanded: pass join(homedir(), ".envpkt", "age-key.txt"), not "~/.envpkt/age-key.txt".
updateConfigIdentity(configPath, { recipient, keyFile?, name? })
Section titled “updateConfigIdentity(configPath, { recipient, keyFile?, name? })”Set identity.recipient, and identity.key_file and identity.name when given, in an envpkt.toml
file, preserving TOML structure. Creates the [identity] section if missing. Returns
Either<KeygenError, true>.
KeygenResult
Section titled “KeygenResult”type KeygenResult = { readonly recipient: string readonly identityPath: string readonly configUpdated: boolean}fnox Integration
Section titled “fnox Integration”detectFnox() / fnoxAvailable()
Section titled “detectFnox() / fnoxAvailable()”Check for fnox availability.
readFnoxConfig(path) / extractFnoxKeys(config)
Section titled “readFnoxConfig(path) / extractFnoxKeys(config)”Read and parse fnox configuration.
compareFnoxAndEnvpkt(fnoxConfig, envpktConfig)
Section titled “compareFnoxAndEnvpkt(fnoxConfig, envpktConfig)”Compare keys between fnox and envpkt for orphan detection.
fnoxGet(key, profile?) / fnoxExport(keys, profile?)
Section titled “fnoxGet(key, profile?) / fnoxExport(keys, profile?)”Retrieve secret values from fnox.
unwrapAgentKey(identityPath) / describeIdentityError(err)
Section titled “unwrapAgentKey(identityPath) / describeIdentityError(err)”unwrapAgentKey reads every secret key (AGE-SECRET-KEY-…) from a plain age key file, returning
Either<IdentityError, ReadonlyArray<string>>. Pass the keys to ageDecrypt or unsealSecrets.
A missing, unreadable, passphrase-protected, plugin or SSH key file gives a typed IdentityError
(see Error Types). describeIdentityError explains one
without printing key material.
MCP Server
Section titled “MCP Server”createServer() / startServer()
Section titled “createServer() / startServer()”Create and start the MCP server programmatically.
toolDefinitions / callTool(name, args, config, audit)
Section titled “toolDefinitions / callTool(name, args, config, audit)”MCP tool definitions and invocation.
resourceDefinitions / readResource(uri, config, audit)
Section titled “resourceDefinitions / readResource(uri, config, audit)”MCP resource definitions and reading.