envpkt 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js ADDED
@@ -0,0 +1,1769 @@
1
+ import { FormatRegistry, Type } from "@sinclair/typebox";
2
+ import { existsSync, readFileSync, readdirSync, statSync } from "node:fs";
3
+ import { dirname, join, resolve } from "node:path";
4
+ import { TypeCompiler } from "@sinclair/typebox/compiler";
5
+ import { Cond, Left, List, Option, Right, Try } from "functype";
6
+ import { TomlDate, parse } from "smol-toml";
7
+ import { execFileSync } from "node:child_process";
8
+ import { Server } from "@modelcontextprotocol/sdk/server/index.js";
9
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
10
+ import { CallToolRequestSchema, ListResourcesRequestSchema, ListToolsRequestSchema, ReadResourceRequestSchema } from "@modelcontextprotocol/sdk/types.js";
11
+
12
+ //#region src/core/schema.ts
13
+ const DATE_RE = /^\d{4}-\d{2}-\d{2}$/;
14
+ const URI_RE = /^https?:\/\/.+/;
15
+ if (!FormatRegistry.Has("date")) FormatRegistry.Set("date", (v) => DATE_RE.test(v));
16
+ if (!FormatRegistry.Has("uri")) FormatRegistry.Set("uri", (v) => URI_RE.test(v));
17
+ const ConsumerType = Type.Union([
18
+ Type.Literal("agent"),
19
+ Type.Literal("service"),
20
+ Type.Literal("developer"),
21
+ Type.Literal("ci")
22
+ ], { description: "Classification of the agent's consumer type" });
23
+ const AgentIdentitySchema = Type.Object({
24
+ name: Type.String({ description: "Agent display name" }),
25
+ consumer: Type.Optional(ConsumerType),
26
+ description: Type.Optional(Type.String({ description: "Agent description or role" })),
27
+ capabilities: Type.Optional(Type.Array(Type.String(), { description: "List of capabilities this agent provides" })),
28
+ expires: Type.Optional(Type.String({
29
+ format: "date",
30
+ description: "Agent credential expiration date (YYYY-MM-DD)"
31
+ })),
32
+ services: Type.Optional(Type.Array(Type.String(), { description: "Service dependencies for this agent" })),
33
+ identity: Type.Optional(Type.String({ description: "Path to encrypted agent key file (relative to config directory)" })),
34
+ recipient: Type.Optional(Type.String({ description: "Agent's age public key for encryption" })),
35
+ secrets: Type.Optional(Type.Array(Type.String(), { description: "Secret keys this agent needs from the catalog" }))
36
+ }, { description: "Identity and capabilities of the AI agent using this envpkt" });
37
+ const SecretMetaSchema = Type.Object({
38
+ service: Type.Optional(Type.String({ description: "Service or system this secret authenticates to" })),
39
+ expires: Type.Optional(Type.String({
40
+ format: "date",
41
+ description: "Date the secret expires (YYYY-MM-DD)"
42
+ })),
43
+ rotation_url: Type.Optional(Type.String({
44
+ format: "uri",
45
+ description: "URL or reference for secret rotation procedure"
46
+ })),
47
+ purpose: Type.Optional(Type.String({ description: "Why this secret exists and what it enables" })),
48
+ capabilities: Type.Optional(Type.Array(Type.String(), { description: "What operations this secret grants (e.g. read, write, admin)" })),
49
+ created: Type.Optional(Type.String({
50
+ format: "date",
51
+ description: "Date the secret was provisioned (YYYY-MM-DD)"
52
+ })),
53
+ rotates: Type.Optional(Type.String({ description: "Rotation schedule (e.g. '90d', 'quarterly')" })),
54
+ rate_limit: Type.Optional(Type.String({ description: "Rate limit or quota info (e.g. '1000/min')" })),
55
+ model_hint: Type.Optional(Type.String({ description: "Suggested model or tier for this credential" })),
56
+ source: Type.Optional(Type.String({ description: "Where the secret value originates (e.g. 'vault', 'ci')" })),
57
+ required: Type.Optional(Type.Boolean({ description: "Whether this secret is required for operation" })),
58
+ tags: Type.Optional(Type.Record(Type.String(), Type.String(), { description: "Key-value tags for grouping and filtering" }))
59
+ }, { description: "Metadata about a single secret" });
60
+ const LifecycleConfigSchema = Type.Object({
61
+ stale_warning_days: Type.Optional(Type.Number({
62
+ default: 90,
63
+ description: "Days since creation to consider a secret stale"
64
+ })),
65
+ require_expiration: Type.Optional(Type.Boolean({
66
+ default: false,
67
+ description: "Require expires on all secrets"
68
+ })),
69
+ require_service: Type.Optional(Type.Boolean({
70
+ default: false,
71
+ description: "Require service on all secrets"
72
+ }))
73
+ }, { description: "Policy configuration for credential lifecycle management" });
74
+ const CallbackConfigSchema = Type.Object({
75
+ on_expiring: Type.Optional(Type.String({ description: "Command or webhook to invoke when secrets are expiring" })),
76
+ on_expired: Type.Optional(Type.String({ description: "Command or webhook to invoke when secrets have expired" })),
77
+ on_audit_fail: Type.Optional(Type.String({ description: "Command or webhook on audit failure" }))
78
+ }, { description: "Automation callbacks for lifecycle events" });
79
+ const ToolsConfigSchema = Type.Record(Type.String(), Type.Unknown(), { description: "Tool integration configuration — open namespace for third-party extensions" });
80
+ const EnvpktConfigSchema = Type.Object({
81
+ version: Type.Number({
82
+ description: "Schema version number",
83
+ default: 1
84
+ }),
85
+ catalog: Type.Optional(Type.String({ description: "Path to shared secret catalog (relative to this config file)" })),
86
+ agent: Type.Optional(AgentIdentitySchema),
87
+ meta: Type.Record(Type.String(), SecretMetaSchema, { description: "Per-secret metadata keyed by secret name" }),
88
+ lifecycle: Type.Optional(LifecycleConfigSchema),
89
+ callbacks: Type.Optional(CallbackConfigSchema),
90
+ tools: Type.Optional(ToolsConfigSchema)
91
+ }, {
92
+ $id: "envpkt",
93
+ title: "envpkt configuration",
94
+ description: "Credential lifecycle and fleet management configuration for AI agents"
95
+ });
96
+
97
+ //#endregion
98
+ //#region src/core/config.ts
99
+ const CONFIG_FILENAME$1 = "envpkt.toml";
100
+ const ENV_VAR_CONFIG = "ENVPKT_CONFIG";
101
+ const compiledSchema = TypeCompiler.Compile(EnvpktConfigSchema);
102
+ /** Recursively convert TomlDate instances to ISO date strings */
103
+ const normalizeDates = (obj) => {
104
+ if (obj instanceof TomlDate) return obj.toISOString().split("T")[0];
105
+ if (Array.isArray(obj)) return obj.map(normalizeDates);
106
+ if (obj !== null && typeof obj === "object") return Object.fromEntries(Object.entries(obj).map(([k, v]) => [k, normalizeDates(v)]));
107
+ return obj;
108
+ };
109
+ /** Find envpkt.toml in the given directory */
110
+ const findConfigPath = (dir) => {
111
+ const candidate = join(dir, CONFIG_FILENAME$1);
112
+ return existsSync(candidate) ? Option(candidate) : Option(void 0);
113
+ };
114
+ /** Read a config file, returning Either<ConfigError, string> */
115
+ const readConfigFile = (path) => {
116
+ if (!existsSync(path)) return Left({
117
+ _tag: "FileNotFound",
118
+ path
119
+ });
120
+ return Try(() => readFileSync(path, "utf-8")).fold((err) => Left({
121
+ _tag: "ReadError",
122
+ message: String(err)
123
+ }), (content) => Right(content));
124
+ };
125
+ /** Ensure required fields have defaults for valid configs (e.g. agent configs with catalog may omit meta) */
126
+ const applyDefaults = (data) => {
127
+ if (data !== null && typeof data === "object" && !Array.isArray(data)) {
128
+ const obj = data;
129
+ if (!("meta" in obj)) return {
130
+ ...obj,
131
+ meta: {}
132
+ };
133
+ }
134
+ return data;
135
+ };
136
+ /** Parse a TOML string, returning Either<ConfigError, unknown> */
137
+ const parseToml = (raw) => Try(() => parse(raw)).fold((err) => Left({
138
+ _tag: "ParseError",
139
+ message: String(err)
140
+ }), (data) => Right(applyDefaults(normalizeDates(data))));
141
+ /** Validate parsed data against the TypeBox schema */
142
+ const validateConfig = (data) => {
143
+ if (compiledSchema.Check(data)) return Right(data);
144
+ return Left({
145
+ _tag: "ValidationError",
146
+ errors: List([...compiledSchema.Errors(data)].map((e) => `${e.path}: ${e.message}`))
147
+ });
148
+ };
149
+ /** Load and validate an envpkt.toml from a file path */
150
+ const loadConfig = (path) => readConfigFile(path).flatMap(parseToml).flatMap(validateConfig);
151
+ /** Load config from CWD, returning both path and parsed config */
152
+ const loadConfigFromCwd = (cwd) => {
153
+ const dir = cwd ?? process.cwd();
154
+ return findConfigPath(dir).fold(() => Left({
155
+ _tag: "FileNotFound",
156
+ path: join(dir, CONFIG_FILENAME$1)
157
+ }), (path) => loadConfig(path).map((config) => ({
158
+ path,
159
+ config
160
+ })));
161
+ };
162
+ /**
163
+ * Resolve config path via priority chain:
164
+ * 1. Explicit flag path
165
+ * 2. ENVPKT_CONFIG env var
166
+ * 3. CWD discovery
167
+ */
168
+ const resolveConfigPath = (flagPath, envVar, cwd) => {
169
+ if (flagPath) {
170
+ const resolved = resolve(flagPath);
171
+ return existsSync(resolved) ? Right(resolved) : Left({
172
+ _tag: "FileNotFound",
173
+ path: resolved
174
+ });
175
+ }
176
+ const envPath = envVar ?? process.env[ENV_VAR_CONFIG];
177
+ if (envPath) {
178
+ const resolved = resolve(envPath);
179
+ return existsSync(resolved) ? Right(resolved) : Left({
180
+ _tag: "FileNotFound",
181
+ path: resolved
182
+ });
183
+ }
184
+ const dir = cwd ?? process.cwd();
185
+ return findConfigPath(dir).fold(() => Left({
186
+ _tag: "FileNotFound",
187
+ path: join(dir, CONFIG_FILENAME$1)
188
+ }), (path) => Right(path));
189
+ };
190
+
191
+ //#endregion
192
+ //#region src/core/catalog.ts
193
+ /** Load and validate a catalog file, mapping ConfigError → CatalogError */
194
+ const loadCatalog = (catalogPath) => loadConfig(catalogPath).fold((err) => {
195
+ if (err._tag === "FileNotFound") return Left({
196
+ _tag: "CatalogNotFound",
197
+ path: err.path
198
+ });
199
+ return Left({
200
+ _tag: "CatalogLoadError",
201
+ message: `${err._tag}: ${"message" in err ? err.message : String(err)}`
202
+ });
203
+ }, (config) => Right(config));
204
+ /** Resolve secrets by merging catalog meta with agent overrides (shallow merge) */
205
+ const resolveSecrets = (agentMeta, catalogMeta, agentSecrets, catalogPath) => {
206
+ const resolved = {};
207
+ for (const key of agentSecrets) {
208
+ const catalogEntry = catalogMeta[key];
209
+ if (!catalogEntry) return Left({
210
+ _tag: "SecretNotInCatalog",
211
+ key,
212
+ catalogPath
213
+ });
214
+ const agentOverride = agentMeta[key];
215
+ if (agentOverride) resolved[key] = {
216
+ ...catalogEntry,
217
+ ...agentOverride
218
+ };
219
+ else resolved[key] = catalogEntry;
220
+ }
221
+ return Right(resolved);
222
+ };
223
+ /** Resolve an agent config against its catalog (if any), producing a flat self-contained config */
224
+ const resolveConfig = (agentConfig, agentConfigDir) => {
225
+ if (!agentConfig.catalog) return Right({
226
+ config: agentConfig,
227
+ merged: [],
228
+ overridden: [],
229
+ warnings: []
230
+ });
231
+ if (!agentConfig.agent?.secrets || agentConfig.agent.secrets.length === 0) return Left({
232
+ _tag: "MissingSecretsList",
233
+ message: "Config has 'catalog' but agent.secrets is missing — declare which catalog secrets this agent needs"
234
+ });
235
+ const catalogPath = resolve(agentConfigDir, agentConfig.catalog);
236
+ const agentSecrets = agentConfig.agent.secrets;
237
+ return loadCatalog(catalogPath).flatMap((catalogConfig) => resolveSecrets(agentConfig.meta, catalogConfig.meta, agentSecrets, catalogPath).map((resolvedMeta) => {
238
+ const merged = [];
239
+ const overridden = [];
240
+ const warnings = [];
241
+ for (const key of agentSecrets) {
242
+ merged.push(key);
243
+ if (agentConfig.meta[key]) overridden.push(key);
244
+ }
245
+ const { catalog: _catalog, ...agentWithoutCatalog } = agentConfig;
246
+ const agentIdentity = agentConfig.agent ? (() => {
247
+ const { secrets: _secrets, ...rest } = agentConfig.agent;
248
+ return rest;
249
+ })() : void 0;
250
+ return {
251
+ config: {
252
+ ...agentWithoutCatalog,
253
+ agent: agentIdentity ? {
254
+ ...agentIdentity,
255
+ name: agentIdentity.name
256
+ } : void 0,
257
+ meta: resolvedMeta
258
+ },
259
+ catalogPath,
260
+ merged,
261
+ overridden,
262
+ warnings
263
+ };
264
+ }));
265
+ };
266
+
267
+ //#endregion
268
+ //#region src/core/format.ts
269
+ const maskValue = (value) => {
270
+ if (value.length > 8) return `${value.slice(0, 3)}${"•".repeat(5)}${value.slice(-4)}`;
271
+ return "•".repeat(5);
272
+ };
273
+ const formatSecretFields = (meta, indent) => {
274
+ const lines = [];
275
+ if (meta.purpose) lines.push(`${indent}purpose: ${meta.purpose}`);
276
+ if (meta.capabilities) lines.push(`${indent}capabilities: ${meta.capabilities.join(", ")}`);
277
+ const dateLine = [];
278
+ if (meta.created) dateLine.push(`created: ${meta.created}`);
279
+ if (meta.expires) dateLine.push(`expires: ${meta.expires}`);
280
+ if (dateLine.length > 0) lines.push(`${indent}${dateLine.join(" ")}`);
281
+ const opsLine = [];
282
+ if (meta.rotates) opsLine.push(`rotates: ${meta.rotates}`);
283
+ if (meta.rate_limit) opsLine.push(`rate_limit: ${meta.rate_limit}`);
284
+ if (opsLine.length > 0) lines.push(`${indent}${opsLine.join(" ")}`);
285
+ if (meta.source) lines.push(`${indent}source: ${meta.source}`);
286
+ if (meta.model_hint) lines.push(`${indent}model_hint: ${meta.model_hint}`);
287
+ if (meta.rotation_url) lines.push(`${indent}rotation_url: ${meta.rotation_url}`);
288
+ if (meta.required !== void 0) lines.push(`${indent}required: ${meta.required}`);
289
+ if (meta.tags) {
290
+ const tagStr = Object.entries(meta.tags).map(([k, v]) => `${k}=${v}`).join(", ");
291
+ lines.push(`${indent}tags: ${tagStr}`);
292
+ }
293
+ return lines.join("\n");
294
+ };
295
+ const formatPacket = (result, options) => {
296
+ const { config } = result;
297
+ const sections = [];
298
+ if (config.agent) {
299
+ const consumer = config.agent.consumer ? ` (${config.agent.consumer})` : "";
300
+ sections.push(`envpkt packet: ${config.agent.name}${consumer}`);
301
+ } else sections.push("envpkt packet");
302
+ if (config.agent) {
303
+ const agentLines = [];
304
+ if (config.agent.description) agentLines.push(` ${config.agent.description}`);
305
+ if (config.agent.capabilities) agentLines.push(` capabilities: ${config.agent.capabilities.join(", ")}`);
306
+ if (config.agent.services) agentLines.push(` services: ${config.agent.services.join(", ")}`);
307
+ if (config.agent.expires) agentLines.push(` expires: ${config.agent.expires}`);
308
+ if (agentLines.length > 0) sections.push(agentLines.join("\n"));
309
+ }
310
+ const metaEntries = Object.entries(config.meta);
311
+ const secretHeader = `secrets: ${metaEntries.length}`;
312
+ const secretLines = metaEntries.map(([key, meta]) => {
313
+ const service = meta.service ?? key;
314
+ const secretValue = options?.secrets?.[key];
315
+ const header = ` ${key} → ${service}${secretValue !== void 0 ? ` = ${(options?.secretDisplay ?? "encrypted") === "plaintext" ? secretValue : maskValue(secretValue)}` : ""}`;
316
+ const fields = formatSecretFields(meta, " ");
317
+ return fields ? `${header}\n${fields}` : header;
318
+ });
319
+ sections.push([secretHeader, ...secretLines].join("\n"));
320
+ if (config.lifecycle) {
321
+ const lcLines = ["lifecycle:"];
322
+ if (config.lifecycle.stale_warning_days !== void 0) lcLines.push(` stale_warning_days: ${config.lifecycle.stale_warning_days}`);
323
+ if (config.lifecycle.require_expiration !== void 0) lcLines.push(` require_expiration: ${config.lifecycle.require_expiration}`);
324
+ if (config.lifecycle.require_service !== void 0) lcLines.push(` require_service: ${config.lifecycle.require_service}`);
325
+ if (lcLines.length > 1) sections.push(lcLines.join("\n"));
326
+ }
327
+ if (result.catalogPath) {
328
+ const catLines = [`catalog: ${result.catalogPath}`];
329
+ catLines.push(` merged: ${result.merged.length} keys`);
330
+ if (result.overridden.length > 0) catLines.push(` overridden: ${result.overridden.join(", ")}`);
331
+ else catLines.push(" overridden: (none)");
332
+ if (result.warnings.length > 0) for (const w of result.warnings) catLines.push(` warning: ${w}`);
333
+ sections.push(catLines.join("\n"));
334
+ }
335
+ return sections.join("\n\n");
336
+ };
337
+
338
+ //#endregion
339
+ //#region src/core/audit.ts
340
+ const MS_PER_DAY = 864e5;
341
+ const WARN_BEFORE_DAYS = 30;
342
+ const daysBetween = (from, to) => Math.floor((to.getTime() - from.getTime()) / MS_PER_DAY);
343
+ const parseDate = (dateStr) => {
344
+ const d = /* @__PURE__ */ new Date(dateStr + "T00:00:00Z");
345
+ return Number.isNaN(d.getTime()) ? Option(void 0) : Option(d);
346
+ };
347
+ const classifySecret = (key, meta, fnoxKeys, staleWarningDays, requireExpiration, requireService, today) => {
348
+ const issues = [];
349
+ const created = Option(meta?.created).flatMap(parseDate);
350
+ const expires = Option(meta?.expires).flatMap(parseDate);
351
+ const rotationUrl = Option(meta?.rotation_url);
352
+ const purpose = Option(meta?.purpose);
353
+ const service = Option(meta?.service);
354
+ const daysRemaining = expires.map((exp) => daysBetween(today, exp));
355
+ const daysSinceCreated = created.map((c) => daysBetween(c, today));
356
+ const isExpired = daysRemaining.fold(() => false, (d) => d < 0);
357
+ const isExpiringSoon = daysRemaining.fold(() => false, (d) => d >= 0 && d <= WARN_BEFORE_DAYS);
358
+ const isStale = daysSinceCreated.fold(() => false, (d) => d > staleWarningDays);
359
+ const isMissing = fnoxKeys.size > 0 && !fnoxKeys.has(key);
360
+ const isMissingMetadata = requireExpiration && expires.isNone() || requireService && service.isNone();
361
+ if (isExpired) issues.push("Secret has expired");
362
+ if (isExpiringSoon) issues.push(`Expires in ${daysRemaining.fold(() => "?", (d) => String(d))} days`);
363
+ if (isStale) issues.push("Secret is stale (no rotation detected)");
364
+ if (isMissing) issues.push("Key not found in fnox");
365
+ if (isMissingMetadata) {
366
+ if (requireExpiration && expires.isNone()) issues.push("Missing required expiration date");
367
+ if (requireService && service.isNone()) issues.push("Missing required service");
368
+ }
369
+ return {
370
+ key,
371
+ service,
372
+ status: Cond.of().when(isExpired, "expired").elseWhen(isMissing, "missing").elseWhen(isMissingMetadata, "missing_metadata").elseWhen(isExpiringSoon, "expiring_soon").elseWhen(isStale, "stale").else("healthy"),
373
+ days_remaining: daysRemaining,
374
+ rotation_url: rotationUrl,
375
+ purpose,
376
+ created: Option(meta?.created),
377
+ expires: Option(meta?.expires),
378
+ issues: List(issues)
379
+ };
380
+ };
381
+ const computeAudit = (config, fnoxKeys, today) => {
382
+ const now = today ?? /* @__PURE__ */ new Date();
383
+ const lifecycle = config.lifecycle ?? {};
384
+ const staleWarningDays = lifecycle.stale_warning_days ?? 90;
385
+ const requireExpiration = lifecycle.require_expiration ?? false;
386
+ const requireService = lifecycle.require_service ?? false;
387
+ const keys = fnoxKeys ?? /* @__PURE__ */ new Set();
388
+ const metaKeys = new Set(Object.keys(config.meta));
389
+ const secrets = List(Object.entries(config.meta).map(([key, meta]) => classifySecret(key, meta, keys, staleWarningDays, requireExpiration, requireService, now)));
390
+ const orphaned = keys.size > 0 ? [...metaKeys].filter((k) => !keys.has(k)).length : 0;
391
+ const total = secrets.size;
392
+ const expired = secrets.count((s) => s.status === "expired");
393
+ const missing = secrets.count((s) => s.status === "missing");
394
+ const missing_metadata = secrets.count((s) => s.status === "missing_metadata");
395
+ const expiring_soon = secrets.count((s) => s.status === "expiring_soon");
396
+ const stale = secrets.count((s) => s.status === "stale");
397
+ const healthy = secrets.count((s) => s.status === "healthy");
398
+ return {
399
+ status: Cond.of().when(expired > 0 || missing > 0, "critical").elseWhen(expiring_soon > 0 || stale > 0 || missing_metadata > 0, "degraded").else("healthy"),
400
+ secrets,
401
+ total,
402
+ healthy,
403
+ expiring_soon,
404
+ expired,
405
+ stale,
406
+ missing,
407
+ missing_metadata,
408
+ orphaned,
409
+ agent: config.agent
410
+ };
411
+ };
412
+
413
+ //#endregion
414
+ //#region src/core/patterns.ts
415
+ const EXCLUDED_VARS = new Set([
416
+ "PATH",
417
+ "HOME",
418
+ "USER",
419
+ "SHELL",
420
+ "TERM",
421
+ "LANG",
422
+ "LC_ALL",
423
+ "LC_CTYPE",
424
+ "DISPLAY",
425
+ "EDITOR",
426
+ "VISUAL",
427
+ "PAGER",
428
+ "HOSTNAME",
429
+ "LOGNAME",
430
+ "MAIL",
431
+ "OLDPWD",
432
+ "PWD",
433
+ "SHLVL",
434
+ "TMPDIR",
435
+ "TZ",
436
+ "XDG_CACHE_HOME",
437
+ "XDG_CONFIG_HOME",
438
+ "XDG_DATA_HOME",
439
+ "XDG_RUNTIME_DIR",
440
+ "XDG_SESSION_TYPE",
441
+ "NODE_ENV",
442
+ "NODE_PATH",
443
+ "NODE_OPTIONS",
444
+ "NVM_DIR",
445
+ "NVM_BIN",
446
+ "NVM_INC",
447
+ "NVM_CD_FLAGS",
448
+ "NPM_CONFIG_PREFIX",
449
+ "GOPATH",
450
+ "GOROOT",
451
+ "CARGO_HOME",
452
+ "RUSTUP_HOME",
453
+ "JAVA_HOME",
454
+ "ANDROID_HOME",
455
+ "PYENV_ROOT",
456
+ "VIRTUAL_ENV",
457
+ "CONDA_PREFIX",
458
+ "CONDA_DEFAULT_ENV",
459
+ "MANPATH",
460
+ "INFOPATH",
461
+ "LESS",
462
+ "LSCOLORS",
463
+ "LS_COLORS",
464
+ "COLORTERM",
465
+ "TERM_PROGRAM",
466
+ "TERM_PROGRAM_VERSION",
467
+ "TERM_SESSION_ID",
468
+ "ITERM_SESSION_ID",
469
+ "ITERM_PROFILE",
470
+ "SSH_AUTH_SOCK",
471
+ "SSH_AGENT_PID",
472
+ "GPG_TTY",
473
+ "GNUPGHOME",
474
+ "DBUS_SESSION_BUS_ADDRESS",
475
+ "WAYLAND_DISPLAY",
476
+ "ZDOTDIR",
477
+ "ZSH",
478
+ "HISTFILE",
479
+ "HISTSIZE",
480
+ "SAVEHIST",
481
+ "_",
482
+ "__CF_USER_TEXT_ENCODING",
483
+ "Apple_PubSub_Socket_Render",
484
+ "COMMAND_MODE",
485
+ "SECURITYSESSIONID",
486
+ "LaunchInstanceID",
487
+ "PNPM_HOME",
488
+ "BUN_INSTALL",
489
+ "FNM_DIR",
490
+ "FNM_MULTISHELL_PATH",
491
+ "FNM_VERSION_FILE_STRATEGY",
492
+ "FNM_LOGLEVEL",
493
+ "FNM_NODE_DIST_MIRROR",
494
+ "FNM_ARCH",
495
+ "VOLTA_HOME"
496
+ ]);
497
+ const EXACT_NAME_PATTERNS = [
498
+ {
499
+ kind: "name",
500
+ pattern: "OPENAI_API_KEY",
501
+ service: "openai",
502
+ confidence: "high",
503
+ description: "OpenAI API key"
504
+ },
505
+ {
506
+ kind: "name",
507
+ pattern: "OPENAI_ORG_ID",
508
+ service: "openai",
509
+ confidence: "high",
510
+ description: "OpenAI org ID"
511
+ },
512
+ {
513
+ kind: "name",
514
+ pattern: "ANTHROPIC_API_KEY",
515
+ service: "anthropic",
516
+ confidence: "high",
517
+ description: "Anthropic API key"
518
+ },
519
+ {
520
+ kind: "name",
521
+ pattern: "AWS_ACCESS_KEY_ID",
522
+ service: "aws",
523
+ confidence: "high",
524
+ description: "AWS access key ID"
525
+ },
526
+ {
527
+ kind: "name",
528
+ pattern: "AWS_SECRET_ACCESS_KEY",
529
+ service: "aws",
530
+ confidence: "high",
531
+ description: "AWS secret access key"
532
+ },
533
+ {
534
+ kind: "name",
535
+ pattern: "AWS_SESSION_TOKEN",
536
+ service: "aws",
537
+ confidence: "high",
538
+ description: "AWS session token"
539
+ },
540
+ {
541
+ kind: "name",
542
+ pattern: "GOOGLE_APPLICATION_CREDENTIALS",
543
+ service: "gcp",
544
+ confidence: "high",
545
+ description: "Google Cloud service account path"
546
+ },
547
+ {
548
+ kind: "name",
549
+ pattern: "GOOGLE_API_KEY",
550
+ service: "google",
551
+ confidence: "high",
552
+ description: "Google API key"
553
+ },
554
+ {
555
+ kind: "name",
556
+ pattern: "GCP_PROJECT_ID",
557
+ service: "gcp",
558
+ confidence: "medium",
559
+ description: "GCP project ID"
560
+ },
561
+ {
562
+ kind: "name",
563
+ pattern: "AZURE_CLIENT_ID",
564
+ service: "azure",
565
+ confidence: "high",
566
+ description: "Azure client ID"
567
+ },
568
+ {
569
+ kind: "name",
570
+ pattern: "AZURE_CLIENT_SECRET",
571
+ service: "azure",
572
+ confidence: "high",
573
+ description: "Azure client secret"
574
+ },
575
+ {
576
+ kind: "name",
577
+ pattern: "AZURE_TENANT_ID",
578
+ service: "azure",
579
+ confidence: "high",
580
+ description: "Azure tenant ID"
581
+ },
582
+ {
583
+ kind: "name",
584
+ pattern: "STRIPE_SECRET_KEY",
585
+ service: "stripe",
586
+ confidence: "high",
587
+ description: "Stripe secret key"
588
+ },
589
+ {
590
+ kind: "name",
591
+ pattern: "STRIPE_PUBLISHABLE_KEY",
592
+ service: "stripe",
593
+ confidence: "high",
594
+ description: "Stripe publishable key"
595
+ },
596
+ {
597
+ kind: "name",
598
+ pattern: "STRIPE_WEBHOOK_SECRET",
599
+ service: "stripe",
600
+ confidence: "high",
601
+ description: "Stripe webhook secret"
602
+ },
603
+ {
604
+ kind: "name",
605
+ pattern: "GITHUB_TOKEN",
606
+ service: "github",
607
+ confidence: "high",
608
+ description: "GitHub token"
609
+ },
610
+ {
611
+ kind: "name",
612
+ pattern: "GH_TOKEN",
613
+ service: "github",
614
+ confidence: "high",
615
+ description: "GitHub token (gh CLI)"
616
+ },
617
+ {
618
+ kind: "name",
619
+ pattern: "SLACK_BOT_TOKEN",
620
+ service: "slack",
621
+ confidence: "high",
622
+ description: "Slack bot token"
623
+ },
624
+ {
625
+ kind: "name",
626
+ pattern: "SLACK_SIGNING_SECRET",
627
+ service: "slack",
628
+ confidence: "high",
629
+ description: "Slack signing secret"
630
+ },
631
+ {
632
+ kind: "name",
633
+ pattern: "SLACK_WEBHOOK_URL",
634
+ service: "slack",
635
+ confidence: "high",
636
+ description: "Slack webhook URL"
637
+ },
638
+ {
639
+ kind: "name",
640
+ pattern: "TWILIO_ACCOUNT_SID",
641
+ service: "twilio",
642
+ confidence: "high",
643
+ description: "Twilio account SID"
644
+ },
645
+ {
646
+ kind: "name",
647
+ pattern: "TWILIO_AUTH_TOKEN",
648
+ service: "twilio",
649
+ confidence: "high",
650
+ description: "Twilio auth token"
651
+ },
652
+ {
653
+ kind: "name",
654
+ pattern: "SENDGRID_API_KEY",
655
+ service: "sendgrid",
656
+ confidence: "high",
657
+ description: "SendGrid API key"
658
+ },
659
+ {
660
+ kind: "name",
661
+ pattern: "SUPABASE_URL",
662
+ service: "supabase",
663
+ confidence: "high",
664
+ description: "Supabase project URL"
665
+ },
666
+ {
667
+ kind: "name",
668
+ pattern: "SUPABASE_ANON_KEY",
669
+ service: "supabase",
670
+ confidence: "high",
671
+ description: "Supabase anon key"
672
+ },
673
+ {
674
+ kind: "name",
675
+ pattern: "SUPABASE_SERVICE_ROLE_KEY",
676
+ service: "supabase",
677
+ confidence: "high",
678
+ description: "Supabase service role key"
679
+ },
680
+ {
681
+ kind: "name",
682
+ pattern: "DATABASE_URL",
683
+ service: "database",
684
+ confidence: "high",
685
+ description: "Database URL"
686
+ },
687
+ {
688
+ kind: "name",
689
+ pattern: "DATABASE_PASSWORD",
690
+ service: "database",
691
+ confidence: "high",
692
+ description: "Database password"
693
+ },
694
+ {
695
+ kind: "name",
696
+ pattern: "REDIS_URL",
697
+ service: "redis",
698
+ confidence: "high",
699
+ description: "Redis URL"
700
+ },
701
+ {
702
+ kind: "name",
703
+ pattern: "MONGODB_URI",
704
+ service: "mongodb",
705
+ confidence: "high",
706
+ description: "MongoDB URI"
707
+ },
708
+ {
709
+ kind: "name",
710
+ pattern: "DD_API_KEY",
711
+ service: "datadog",
712
+ confidence: "high",
713
+ description: "Datadog API key"
714
+ },
715
+ {
716
+ kind: "name",
717
+ pattern: "DD_APP_KEY",
718
+ service: "datadog",
719
+ confidence: "high",
720
+ description: "Datadog app key"
721
+ },
722
+ {
723
+ kind: "name",
724
+ pattern: "SENTRY_DSN",
725
+ service: "sentry",
726
+ confidence: "high",
727
+ description: "Sentry DSN"
728
+ },
729
+ {
730
+ kind: "name",
731
+ pattern: "SENTRY_AUTH_TOKEN",
732
+ service: "sentry",
733
+ confidence: "high",
734
+ description: "Sentry auth token"
735
+ },
736
+ {
737
+ kind: "name",
738
+ pattern: "VERCEL_TOKEN",
739
+ service: "vercel",
740
+ confidence: "high",
741
+ description: "Vercel token"
742
+ },
743
+ {
744
+ kind: "name",
745
+ pattern: "NETLIFY_AUTH_TOKEN",
746
+ service: "netlify",
747
+ confidence: "high",
748
+ description: "Netlify auth token"
749
+ },
750
+ {
751
+ kind: "name",
752
+ pattern: "CLOUDFLARE_API_TOKEN",
753
+ service: "cloudflare",
754
+ confidence: "high",
755
+ description: "Cloudflare API token"
756
+ },
757
+ {
758
+ kind: "name",
759
+ pattern: "CF_API_TOKEN",
760
+ service: "cloudflare",
761
+ confidence: "high",
762
+ description: "Cloudflare API token"
763
+ },
764
+ {
765
+ kind: "name",
766
+ pattern: "DOCKER_PASSWORD",
767
+ service: "docker",
768
+ confidence: "high",
769
+ description: "Docker password"
770
+ },
771
+ {
772
+ kind: "name",
773
+ pattern: "DOCKER_TOKEN",
774
+ service: "docker",
775
+ confidence: "high",
776
+ description: "Docker token"
777
+ },
778
+ {
779
+ kind: "name",
780
+ pattern: "NPM_TOKEN",
781
+ service: "npm",
782
+ confidence: "high",
783
+ description: "npm token"
784
+ },
785
+ {
786
+ kind: "name",
787
+ pattern: "HF_TOKEN",
788
+ service: "huggingface",
789
+ confidence: "high",
790
+ description: "Hugging Face token"
791
+ },
792
+ {
793
+ kind: "name",
794
+ pattern: "HUGGING_FACE_HUB_TOKEN",
795
+ service: "huggingface",
796
+ confidence: "high",
797
+ description: "Hugging Face Hub token"
798
+ },
799
+ {
800
+ kind: "name",
801
+ pattern: "COHERE_API_KEY",
802
+ service: "cohere",
803
+ confidence: "high",
804
+ description: "Cohere API key"
805
+ },
806
+ {
807
+ kind: "name",
808
+ pattern: "REPLICATE_API_TOKEN",
809
+ service: "replicate",
810
+ confidence: "high",
811
+ description: "Replicate API token"
812
+ },
813
+ {
814
+ kind: "name",
815
+ pattern: "PINECONE_API_KEY",
816
+ service: "pinecone",
817
+ confidence: "high",
818
+ description: "Pinecone API key"
819
+ },
820
+ {
821
+ kind: "name",
822
+ pattern: "LINEAR_API_KEY",
823
+ service: "linear",
824
+ confidence: "high",
825
+ description: "Linear API key"
826
+ }
827
+ ];
828
+ const SUFFIX_PATTERNS = [
829
+ {
830
+ suffix: "_API_KEY",
831
+ description: "API key"
832
+ },
833
+ {
834
+ suffix: "_SECRET_KEY",
835
+ description: "Secret key"
836
+ },
837
+ {
838
+ suffix: "_SECRET",
839
+ description: "Secret"
840
+ },
841
+ {
842
+ suffix: "_TOKEN",
843
+ description: "Token"
844
+ },
845
+ {
846
+ suffix: "_PASSWORD",
847
+ description: "Password"
848
+ },
849
+ {
850
+ suffix: "_PASS",
851
+ description: "Password"
852
+ },
853
+ {
854
+ suffix: "_AUTH_TOKEN",
855
+ description: "Auth token"
856
+ },
857
+ {
858
+ suffix: "_ACCESS_TOKEN",
859
+ description: "Access token"
860
+ },
861
+ {
862
+ suffix: "_PRIVATE_KEY",
863
+ description: "Private key"
864
+ },
865
+ {
866
+ suffix: "_SIGNING_KEY",
867
+ description: "Signing key"
868
+ },
869
+ {
870
+ suffix: "_WEBHOOK_SECRET",
871
+ description: "Webhook secret"
872
+ },
873
+ {
874
+ suffix: "_DSN",
875
+ description: "DSN"
876
+ },
877
+ {
878
+ suffix: "_CONNECTION_STRING",
879
+ description: "Connection string"
880
+ }
881
+ ];
882
+ const VALUE_SHAPE_PATTERNS = [
883
+ {
884
+ prefix: "sk-ant-",
885
+ service: "anthropic",
886
+ description: "Anthropic API key"
887
+ },
888
+ {
889
+ prefix: "sk-",
890
+ service: "openai",
891
+ description: "OpenAI API key"
892
+ },
893
+ {
894
+ prefix: "sk_live_",
895
+ service: "stripe",
896
+ description: "Stripe live secret key"
897
+ },
898
+ {
899
+ prefix: "sk_test_",
900
+ service: "stripe",
901
+ description: "Stripe test secret key"
902
+ },
903
+ {
904
+ prefix: "pk_live_",
905
+ service: "stripe",
906
+ description: "Stripe live publishable key"
907
+ },
908
+ {
909
+ prefix: "pk_test_",
910
+ service: "stripe",
911
+ description: "Stripe test publishable key"
912
+ },
913
+ {
914
+ prefix: "whsec_",
915
+ service: "stripe",
916
+ description: "Stripe webhook secret"
917
+ },
918
+ {
919
+ prefix: "AKIA",
920
+ service: "aws",
921
+ description: "AWS access key ID"
922
+ },
923
+ {
924
+ prefix: "ghp_",
925
+ service: "github",
926
+ description: "GitHub personal access token"
927
+ },
928
+ {
929
+ prefix: "gho_",
930
+ service: "github",
931
+ description: "GitHub OAuth token"
932
+ },
933
+ {
934
+ prefix: "ghs_",
935
+ service: "github",
936
+ description: "GitHub server-to-server token"
937
+ },
938
+ {
939
+ prefix: "ghu_",
940
+ service: "github",
941
+ description: "GitHub user-to-server token"
942
+ },
943
+ {
944
+ prefix: "github_pat_",
945
+ service: "github",
946
+ description: "GitHub fine-grained PAT"
947
+ },
948
+ {
949
+ prefix: "xoxb-",
950
+ service: "slack",
951
+ description: "Slack bot token"
952
+ },
953
+ {
954
+ prefix: "xoxp-",
955
+ service: "slack",
956
+ description: "Slack user token"
957
+ },
958
+ {
959
+ prefix: "xoxa-",
960
+ service: "slack",
961
+ description: "Slack app token"
962
+ },
963
+ {
964
+ prefix: "xoxs-",
965
+ service: "slack",
966
+ description: "Slack legacy token"
967
+ },
968
+ {
969
+ prefix: "SG.",
970
+ service: "sendgrid",
971
+ description: "SendGrid API key"
972
+ },
973
+ {
974
+ prefix: "hf_",
975
+ service: "huggingface",
976
+ description: "Hugging Face token"
977
+ },
978
+ {
979
+ prefix: "r8_",
980
+ service: "replicate",
981
+ description: "Replicate API token"
982
+ },
983
+ {
984
+ prefix: "eyJ",
985
+ service: "jwt",
986
+ description: "JWT token"
987
+ },
988
+ {
989
+ prefix: "postgres://",
990
+ service: "postgresql",
991
+ description: "PostgreSQL connection string"
992
+ },
993
+ {
994
+ prefix: "postgresql://",
995
+ service: "postgresql",
996
+ description: "PostgreSQL connection string"
997
+ },
998
+ {
999
+ prefix: "mysql://",
1000
+ service: "mysql",
1001
+ description: "MySQL connection string"
1002
+ },
1003
+ {
1004
+ prefix: "mongodb://",
1005
+ service: "mongodb",
1006
+ description: "MongoDB connection string"
1007
+ },
1008
+ {
1009
+ prefix: "mongodb+srv://",
1010
+ service: "mongodb",
1011
+ description: "MongoDB SRV connection string"
1012
+ },
1013
+ {
1014
+ prefix: "redis://",
1015
+ service: "redis",
1016
+ description: "Redis connection string"
1017
+ },
1018
+ {
1019
+ prefix: "rediss://",
1020
+ service: "redis",
1021
+ description: "Redis TLS connection string"
1022
+ },
1023
+ {
1024
+ prefix: "amqp://",
1025
+ service: "rabbitmq",
1026
+ description: "RabbitMQ connection string"
1027
+ },
1028
+ {
1029
+ prefix: "amqps://",
1030
+ service: "rabbitmq",
1031
+ description: "RabbitMQ TLS connection string"
1032
+ }
1033
+ ];
1034
+ /** Detect service from value prefix/shape */
1035
+ const matchValueShape = (value) => {
1036
+ for (const vp of VALUE_SHAPE_PATTERNS) if (value.startsWith(vp.prefix)) return Option({
1037
+ service: vp.service,
1038
+ description: vp.description
1039
+ });
1040
+ return Option(void 0);
1041
+ };
1042
+ /** Strip common suffixes and derive a service name from an env var name */
1043
+ const deriveServiceFromName = (name) => {
1044
+ const suffixes = [
1045
+ "_API_KEY",
1046
+ "_SECRET_KEY",
1047
+ "_ACCESS_KEY",
1048
+ "_PRIVATE_KEY",
1049
+ "_SIGNING_KEY",
1050
+ "_AUTH_TOKEN",
1051
+ "_ACCESS_TOKEN",
1052
+ "_WEBHOOK_SECRET",
1053
+ "_CONNECTION_STRING",
1054
+ "_SECRET",
1055
+ "_TOKEN",
1056
+ "_PASSWORD",
1057
+ "_PASS",
1058
+ "_KEY",
1059
+ "_DSN",
1060
+ "_URL",
1061
+ "_URI"
1062
+ ];
1063
+ let stripped = name;
1064
+ for (const suffix of suffixes) if (stripped.endsWith(suffix)) {
1065
+ stripped = stripped.slice(0, -suffix.length);
1066
+ break;
1067
+ }
1068
+ return stripped.toLowerCase().replace(/_/g, "-");
1069
+ };
1070
+ /** Match a single env var against all patterns */
1071
+ const matchEnvVar = (name, value) => {
1072
+ if (EXCLUDED_VARS.has(name)) return Option(void 0);
1073
+ for (const p of EXACT_NAME_PATTERNS) if (name === p.pattern) return Option({
1074
+ envVar: name,
1075
+ value,
1076
+ service: Option(p.service),
1077
+ confidence: p.confidence,
1078
+ matchedBy: `exact:${p.pattern}`
1079
+ });
1080
+ return matchValueShape(value).fold(() => {
1081
+ for (const sp of SUFFIX_PATTERNS) if (name.endsWith(sp.suffix)) return Option({
1082
+ envVar: name,
1083
+ value,
1084
+ service: Option(deriveServiceFromName(name)),
1085
+ confidence: "medium",
1086
+ matchedBy: `suffix:${sp.suffix}`
1087
+ });
1088
+ return Option(void 0);
1089
+ }, (vm) => Option({
1090
+ envVar: name,
1091
+ value,
1092
+ service: Option(vm.service),
1093
+ confidence: "high",
1094
+ matchedBy: `value:${vm.description}`
1095
+ }));
1096
+ };
1097
+ /** Scan full env, sorted by confidence (high first) then alphabetically */
1098
+ const scanEnv = (env) => {
1099
+ const results = [];
1100
+ for (const [name, value] of Object.entries(env)) {
1101
+ if (value === void 0 || value === "") continue;
1102
+ matchEnvVar(name, value).fold(() => {}, (m) => results.push(m));
1103
+ }
1104
+ const confidenceOrder = {
1105
+ high: 0,
1106
+ medium: 1,
1107
+ low: 2
1108
+ };
1109
+ results.sort((a, b) => {
1110
+ const conf = confidenceOrder[a.confidence] - confidenceOrder[b.confidence];
1111
+ if (conf !== 0) return conf;
1112
+ return a.envVar.localeCompare(b.envVar);
1113
+ });
1114
+ return results;
1115
+ };
1116
+
1117
+ //#endregion
1118
+ //#region src/core/env.ts
1119
+ /** Scan env for credentials, returning structured results */
1120
+ const envScan = (env, options) => {
1121
+ const allMatches = scanEnv(env);
1122
+ const discovered = options?.includeUnknown ? allMatches : allMatches.filter((m) => m.service.isSome());
1123
+ const total_scanned = Object.keys(env).length;
1124
+ const high_confidence = discovered.filter((m) => m.confidence === "high").length;
1125
+ const medium_confidence = discovered.filter((m) => m.confidence === "medium").length;
1126
+ const low_confidence = discovered.filter((m) => m.confidence === "low").length;
1127
+ return {
1128
+ discovered: List(discovered),
1129
+ total_scanned,
1130
+ high_confidence,
1131
+ medium_confidence,
1132
+ low_confidence
1133
+ };
1134
+ };
1135
+ /** Bidirectional drift detection between config and live environment */
1136
+ const envCheck = (config, env) => {
1137
+ const entries = [];
1138
+ const metaKeys = Object.keys(config.meta);
1139
+ const trackedSet = new Set(metaKeys);
1140
+ for (const key of metaKeys) {
1141
+ const meta = config.meta[key];
1142
+ const present = env[key] !== void 0 && env[key] !== "";
1143
+ entries.push({
1144
+ envVar: key,
1145
+ service: Option(meta?.service),
1146
+ status: present ? "tracked" : "missing_from_env",
1147
+ confidence: Option(void 0)
1148
+ });
1149
+ }
1150
+ const envMatches = scanEnv(env);
1151
+ for (const match of envMatches) if (!trackedSet.has(match.envVar)) entries.push({
1152
+ envVar: match.envVar,
1153
+ service: match.service,
1154
+ status: "untracked",
1155
+ confidence: Option(match.confidence)
1156
+ });
1157
+ const tracked_and_present = entries.filter((e) => e.status === "tracked").length;
1158
+ const missing_from_env = entries.filter((e) => e.status === "missing_from_env").length;
1159
+ const untracked_credentials = entries.filter((e) => e.status === "untracked").length;
1160
+ return {
1161
+ entries: List(entries),
1162
+ tracked_and_present,
1163
+ missing_from_env,
1164
+ untracked_credentials,
1165
+ is_clean: missing_from_env === 0 && untracked_credentials === 0
1166
+ };
1167
+ };
1168
+ const todayIso = () => (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
1169
+ /** Generate TOML [meta.*] blocks from scan results, mirroring init.ts pattern */
1170
+ const generateTomlFromScan = (matches) => {
1171
+ const blocks = [];
1172
+ for (const match of matches) {
1173
+ const svc = match.service.fold(() => match.envVar.toLowerCase().replace(/_/g, "-"), (s) => s);
1174
+ blocks.push(`[meta.${match.envVar}]
1175
+ service = "${svc}"
1176
+ # purpose = "" # Why: what this secret enables
1177
+ # capabilities = [] # What operations this grants
1178
+ created = "${todayIso()}"
1179
+ # expires = "" # When: YYYY-MM-DD expiration date
1180
+ # rotation_url = "" # URL for rotation procedure
1181
+ # source = "" # Where the value originates (e.g. vault, ci)
1182
+ # tags = {}
1183
+ `);
1184
+ }
1185
+ return blocks.join("\n");
1186
+ };
1187
+
1188
+ //#endregion
1189
+ //#region src/fnox/cli.ts
1190
+ /** Export all secrets from fnox as key=value pairs for a given profile */
1191
+ const fnoxExport = (profile, agentKey) => {
1192
+ const args = profile ? [
1193
+ "export",
1194
+ "--profile",
1195
+ profile
1196
+ ] : ["export"];
1197
+ const env = agentKey ? {
1198
+ ...process.env,
1199
+ FNOX_AGE_KEY: agentKey
1200
+ } : void 0;
1201
+ return Try(() => execFileSync("fnox", args, {
1202
+ stdio: "pipe",
1203
+ encoding: "utf-8",
1204
+ env
1205
+ })).fold((err) => Left({
1206
+ _tag: "FnoxCliError",
1207
+ message: `fnox export failed: ${err}`
1208
+ }), (output) => {
1209
+ const entries = {};
1210
+ for (const line of output.split("\n")) {
1211
+ const eq = line.indexOf("=");
1212
+ if (eq > 0) {
1213
+ const key = line.slice(0, eq).trim();
1214
+ entries[key] = line.slice(eq + 1).trim();
1215
+ }
1216
+ }
1217
+ return Right(entries);
1218
+ });
1219
+ };
1220
+ /** Get a single secret value from fnox */
1221
+ const fnoxGet = (key, profile, agentKey) => {
1222
+ const args = profile ? [
1223
+ "get",
1224
+ key,
1225
+ "--profile",
1226
+ profile
1227
+ ] : ["get", key];
1228
+ const env = agentKey ? {
1229
+ ...process.env,
1230
+ FNOX_AGE_KEY: agentKey
1231
+ } : void 0;
1232
+ return Try(() => execFileSync("fnox", args, {
1233
+ stdio: "pipe",
1234
+ encoding: "utf-8",
1235
+ env
1236
+ })).fold((err) => Left({
1237
+ _tag: "FnoxCliError",
1238
+ message: `fnox get ${key} failed: ${err}`
1239
+ }), (output) => Right(output.trim()));
1240
+ };
1241
+
1242
+ //#endregion
1243
+ //#region src/fnox/detect.ts
1244
+ const FNOX_CONFIG = "fnox.toml";
1245
+ /** Detect fnox.toml in the given directory */
1246
+ const detectFnox = (dir) => {
1247
+ const candidate = join(dir, FNOX_CONFIG);
1248
+ return existsSync(candidate) ? Option(candidate) : Option(void 0);
1249
+ };
1250
+ /** Check if fnox CLI is available on PATH */
1251
+ const fnoxAvailable = () => Try(() => {
1252
+ execFileSync("fnox", ["--version"], { stdio: "pipe" });
1253
+ return true;
1254
+ }).fold(() => false, (v) => v);
1255
+
1256
+ //#endregion
1257
+ //#region src/fnox/identity.ts
1258
+ /** Check if the age CLI is available on PATH */
1259
+ const ageAvailable = () => Try(() => {
1260
+ execFileSync("age", ["--version"], { stdio: "pipe" });
1261
+ return true;
1262
+ }).fold(() => false, (v) => v);
1263
+ /** Unwrap an encrypted agent key using age --decrypt */
1264
+ const unwrapAgentKey = (identityPath) => {
1265
+ if (!existsSync(identityPath)) return Left({
1266
+ _tag: "IdentityNotFound",
1267
+ path: identityPath
1268
+ });
1269
+ if (!ageAvailable()) return Left({
1270
+ _tag: "AgeNotFound",
1271
+ message: "age CLI not found on PATH"
1272
+ });
1273
+ return Try(() => execFileSync("age", ["--decrypt", identityPath], {
1274
+ stdio: [
1275
+ "pipe",
1276
+ "pipe",
1277
+ "pipe"
1278
+ ],
1279
+ encoding: "utf-8"
1280
+ })).fold((err) => Left({
1281
+ _tag: "DecryptFailed",
1282
+ message: `age decrypt failed: ${err}`
1283
+ }), (output) => Right(output.trim()));
1284
+ };
1285
+
1286
+ //#endregion
1287
+ //#region src/fnox/parse.ts
1288
+ /** Read and parse fnox.toml, extracting secret keys and profiles */
1289
+ const readFnoxConfig = (path) => Try(() => readFileSync(path, "utf-8")).fold((err) => Left({
1290
+ _tag: "FnoxParseError",
1291
+ message: `Failed to read ${path}: ${err}`
1292
+ }), (content) => Try(() => parse(content)).fold((err) => Left({
1293
+ _tag: "FnoxParseError",
1294
+ message: `Failed to parse fnox.toml: ${err}`
1295
+ }), (data) => {
1296
+ const profiles = data["profiles"] && typeof data["profiles"] === "object" ? Option(data["profiles"]) : Option(void 0);
1297
+ const secrets = { ...data };
1298
+ delete secrets["profiles"];
1299
+ return Right({
1300
+ secrets,
1301
+ profiles
1302
+ });
1303
+ }));
1304
+ /** Extract the set of secret key names from a parsed fnox config */
1305
+ const extractFnoxKeys = (config) => new Set(Object.keys(config.secrets));
1306
+
1307
+ //#endregion
1308
+ //#region src/core/boot.ts
1309
+ const resolveAndLoad = (opts) => resolveConfigPath(opts.configPath).fold((err) => Left(err), (configPath) => loadConfig(configPath).fold((err) => Left(err), (config) => {
1310
+ const configDir = dirname(configPath);
1311
+ return resolveConfig(config, configDir).fold((err) => Left(err), (result) => Right({
1312
+ config: result.config,
1313
+ configPath,
1314
+ configDir
1315
+ }));
1316
+ }));
1317
+ const resolveAgentKey = (config, configDir) => {
1318
+ if (!config.agent?.identity) return Right(void 0);
1319
+ return unwrapAgentKey(resolve(configDir, config.agent.identity)).fold((err) => Left(err), (key) => Right(key));
1320
+ };
1321
+ const detectFnoxKeys = (configDir) => detectFnox(configDir).fold(() => /* @__PURE__ */ new Set(), (fnoxPath) => readFnoxConfig(fnoxPath).fold(() => /* @__PURE__ */ new Set(), (fnoxConfig) => extractFnoxKeys(fnoxConfig)));
1322
+ const checkExpiration = (audit, failOnExpired, warnOnly) => {
1323
+ const warnings = [];
1324
+ if (audit.expired > 0 && failOnExpired && !warnOnly) return Left({
1325
+ _tag: "AuditFailed",
1326
+ audit,
1327
+ message: `${audit.expired} secret(s) have expired`
1328
+ });
1329
+ if (audit.expired > 0 && warnOnly) warnings.push(`${audit.expired} secret(s) have expired (warn-only mode)`);
1330
+ return Right(warnings);
1331
+ };
1332
+ /** Programmatic boot — returns Either<BootError, BootResult> */
1333
+ const bootSafe = (options) => {
1334
+ const opts = options ?? {};
1335
+ const inject = opts.inject !== false;
1336
+ const failOnExpired = opts.failOnExpired !== false;
1337
+ const warnOnly = opts.warnOnly ?? false;
1338
+ return resolveAndLoad(opts).flatMap(({ config, configDir }) => resolveAgentKey(config, configDir).flatMap((agentKey) => {
1339
+ const audit = computeAudit(config, detectFnoxKeys(configDir));
1340
+ return checkExpiration(audit, failOnExpired, warnOnly).map((warnings) => {
1341
+ const secrets = {};
1342
+ const injected = [];
1343
+ const skipped = [];
1344
+ const metaKeys = Object.keys(config.meta);
1345
+ if (fnoxAvailable()) fnoxExport(opts.profile, agentKey).fold((err) => {
1346
+ warnings.push(`fnox export failed: ${err.message}`);
1347
+ for (const key of metaKeys) skipped.push(key);
1348
+ }, (exported) => {
1349
+ for (const key of metaKeys) if (key in exported) {
1350
+ secrets[key] = exported[key];
1351
+ injected.push(key);
1352
+ } else skipped.push(key);
1353
+ });
1354
+ else {
1355
+ warnings.push("fnox not available — no secrets injected");
1356
+ for (const key of metaKeys) skipped.push(key);
1357
+ }
1358
+ if (inject) for (const [key, value] of Object.entries(secrets)) process.env[key] = value;
1359
+ return {
1360
+ audit,
1361
+ injected,
1362
+ skipped,
1363
+ secrets,
1364
+ warnings
1365
+ };
1366
+ });
1367
+ }));
1368
+ };
1369
+ /** Programmatic boot — throws EnvpktBootError on failure */
1370
+ const boot = (options) => bootSafe(options).fold((err) => {
1371
+ throw new EnvpktBootError(err);
1372
+ }, (r) => r);
1373
+ /** Error class for boot() failures */
1374
+ var EnvpktBootError = class extends Error {
1375
+ error;
1376
+ constructor(error) {
1377
+ super(formatBootError(error));
1378
+ this.name = "EnvpktBootError";
1379
+ this.error = error;
1380
+ }
1381
+ };
1382
+ const formatBootError = (error) => {
1383
+ switch (error._tag) {
1384
+ case "FileNotFound": return `Config not found: ${error.path}`;
1385
+ case "ParseError": return `Config parse error: ${error.message}`;
1386
+ case "ValidationError": return `Config validation failed: ${error.errors.toArray().join(", ")}`;
1387
+ case "ReadError": return `Config read error: ${error.message}`;
1388
+ case "FnoxNotFound": return `fnox not found: ${error.message}`;
1389
+ case "FnoxCliError": return `fnox CLI error: ${error.message}`;
1390
+ case "FnoxParseError": return `fnox parse error: ${error.message}`;
1391
+ case "AuditFailed": return `Audit failed: ${error.message}`;
1392
+ case "CatalogNotFound": return `Catalog not found: ${error.path}`;
1393
+ case "CatalogLoadError": return `Catalog load error: ${error.message}`;
1394
+ case "SecretNotInCatalog": return `Secret "${error.key}" not found in catalog: ${error.catalogPath}`;
1395
+ case "MissingSecretsList": return `Missing secrets list: ${error.message}`;
1396
+ case "AgeNotFound": return `age not found: ${error.message}`;
1397
+ case "DecryptFailed": return `Decrypt failed: ${error.message}`;
1398
+ case "IdentityNotFound": return `Identity file not found: ${error.path}`;
1399
+ default: return `Boot error: ${JSON.stringify(error)}`;
1400
+ }
1401
+ };
1402
+
1403
+ //#endregion
1404
+ //#region src/core/fleet.ts
1405
+ const CONFIG_FILENAME = "envpkt.toml";
1406
+ const SKIP_DIRS = new Set([
1407
+ "node_modules",
1408
+ ".git",
1409
+ ".hg",
1410
+ ".svn",
1411
+ "dist",
1412
+ "build",
1413
+ "lib",
1414
+ ".claude",
1415
+ "__pycache__",
1416
+ "target",
1417
+ "out",
1418
+ "tmp",
1419
+ ".terraform",
1420
+ ".gradle",
1421
+ ".cargo",
1422
+ ".venv",
1423
+ ".next",
1424
+ ".cache",
1425
+ ".tox",
1426
+ "vendor",
1427
+ "coverage",
1428
+ ".nyc_output",
1429
+ ".turbo"
1430
+ ]);
1431
+ function* findEnvpktFiles(dir, maxDepth, currentDepth = 0) {
1432
+ if (currentDepth > maxDepth) return;
1433
+ const configPath = join(dir, CONFIG_FILENAME);
1434
+ if (Try(() => statSync(configPath).isFile()).fold(() => false, (v) => v)) yield configPath;
1435
+ if (currentDepth >= maxDepth) return;
1436
+ let entries = [];
1437
+ Try(() => readdirSync(dir, { withFileTypes: true })).fold(() => {}, (e) => {
1438
+ entries = e;
1439
+ });
1440
+ for (const entry of entries) if (entry.isDirectory() && !SKIP_DIRS.has(entry.name) && !entry.name.startsWith(".")) yield* findEnvpktFiles(join(dir, entry.name), maxDepth, currentDepth + 1);
1441
+ }
1442
+ const scanFleet = (rootDir, options) => {
1443
+ const maxDepth = options?.maxDepth ?? 3;
1444
+ const agents = [];
1445
+ for (const configPath of findEnvpktFiles(rootDir, maxDepth)) loadConfig(configPath).fold(() => {}, (config) => {
1446
+ const audit = computeAudit(config);
1447
+ agents.push({
1448
+ path: configPath,
1449
+ agent: config.agent,
1450
+ min_expiry_days: audit.secrets.toArray().reduce((min, s) => s.days_remaining.fold(() => min, (d) => min === void 0 ? d : Math.min(min, d)), void 0),
1451
+ audit
1452
+ });
1453
+ });
1454
+ const agentList = List(agents);
1455
+ const total_agents = agentList.size;
1456
+ const total_secrets = agentList.toArray().reduce((acc, a) => acc + a.audit.total, 0);
1457
+ const expired = agentList.toArray().reduce((acc, a) => acc + a.audit.expired, 0);
1458
+ const expiring_soon = agentList.toArray().reduce((acc, a) => acc + a.audit.expiring_soon, 0);
1459
+ const criticalCount = agentList.count((a) => a.audit.status === "critical");
1460
+ const degradedCount = agentList.count((a) => a.audit.status === "degraded");
1461
+ return {
1462
+ status: Cond.of().when(criticalCount > 0, "critical").elseWhen(degradedCount > 0, "degraded").else("healthy"),
1463
+ agents: agentList,
1464
+ total_agents,
1465
+ total_secrets,
1466
+ expired,
1467
+ expiring_soon
1468
+ };
1469
+ };
1470
+
1471
+ //#endregion
1472
+ //#region src/fnox/sync.ts
1473
+ /** Compare fnox keys and envpkt meta keys to find mismatches */
1474
+ const compareFnoxAndEnvpkt = (fnoxKeys, envpktKeys) => {
1475
+ return {
1476
+ missing: List([...fnoxKeys].filter((k) => !envpktKeys.has(k))),
1477
+ orphaned: List([...envpktKeys].filter((k) => !fnoxKeys.has(k)))
1478
+ };
1479
+ };
1480
+
1481
+ //#endregion
1482
+ //#region src/mcp/resources.ts
1483
+ const loadConfigSafe = () => {
1484
+ return resolveConfigPath().fold(() => void 0, (path) => loadConfig(path).fold(() => void 0, (config) => ({
1485
+ config,
1486
+ path
1487
+ })));
1488
+ };
1489
+ const resourceDefinitions = [{
1490
+ uri: "envpkt://health",
1491
+ name: "Credential Health",
1492
+ description: "Current health status of the envpkt credential packet",
1493
+ mimeType: "application/json"
1494
+ }, {
1495
+ uri: "envpkt://capabilities",
1496
+ name: "Agent Capabilities",
1497
+ description: "Capabilities declared by the agent and per-secret capability grants",
1498
+ mimeType: "application/json"
1499
+ }];
1500
+ const readHealth = () => {
1501
+ const loaded = loadConfigSafe();
1502
+ if (!loaded) return { contents: [{
1503
+ uri: "envpkt://health",
1504
+ mimeType: "application/json",
1505
+ text: JSON.stringify({ error: "No envpkt.toml found" })
1506
+ }] };
1507
+ const { config, path } = loaded;
1508
+ const audit = computeAudit(config);
1509
+ return { contents: [{
1510
+ uri: "envpkt://health",
1511
+ mimeType: "application/json",
1512
+ text: JSON.stringify({
1513
+ path,
1514
+ status: audit.status,
1515
+ total: audit.total,
1516
+ healthy: audit.healthy,
1517
+ expiring_soon: audit.expiring_soon,
1518
+ expired: audit.expired,
1519
+ stale: audit.stale,
1520
+ missing: audit.missing
1521
+ }, null, 2)
1522
+ }] };
1523
+ };
1524
+ const readCapabilities = () => {
1525
+ const loaded = loadConfigSafe();
1526
+ if (!loaded) return { contents: [{
1527
+ uri: "envpkt://capabilities",
1528
+ mimeType: "application/json",
1529
+ text: JSON.stringify({ error: "No envpkt.toml found" })
1530
+ }] };
1531
+ const { config } = loaded;
1532
+ const agentCapabilities = config.agent?.capabilities ?? [];
1533
+ const secretCapabilities = {};
1534
+ for (const [key, meta] of Object.entries(config.meta)) if (meta.capabilities && meta.capabilities.length > 0) secretCapabilities[key] = meta.capabilities;
1535
+ return { contents: [{
1536
+ uri: "envpkt://capabilities",
1537
+ mimeType: "application/json",
1538
+ text: JSON.stringify({
1539
+ agent: config.agent ? {
1540
+ name: config.agent.name,
1541
+ consumer: config.agent.consumer,
1542
+ description: config.agent.description,
1543
+ capabilities: agentCapabilities
1544
+ } : null,
1545
+ secrets: secretCapabilities
1546
+ }, null, 2)
1547
+ }] };
1548
+ };
1549
+ const resourceHandlers = {
1550
+ "envpkt://health": readHealth,
1551
+ "envpkt://capabilities": readCapabilities
1552
+ };
1553
+ const readResource = (uri) => {
1554
+ const handler = resourceHandlers[uri];
1555
+ return handler?.();
1556
+ };
1557
+
1558
+ //#endregion
1559
+ //#region src/mcp/tools.ts
1560
+ const textResult = (text) => ({ content: [{
1561
+ type: "text",
1562
+ text
1563
+ }] });
1564
+ const errorResult = (message) => ({
1565
+ content: [{
1566
+ type: "text",
1567
+ text: message
1568
+ }],
1569
+ isError: true
1570
+ });
1571
+ const loadConfigForTool = (configPath) => {
1572
+ return resolveConfigPath(configPath).fold((err) => ({
1573
+ ok: false,
1574
+ result: errorResult(`Config error: ${err._tag} — ${err._tag === "FileNotFound" ? err.path : ""}`)
1575
+ }), (path) => loadConfig(path).fold((err) => ({
1576
+ ok: false,
1577
+ result: errorResult(`Config error: ${err._tag} — ${err._tag === "ValidationError" ? err.errors.toArray().join(", ") : ""}`)
1578
+ }), (config) => ({
1579
+ ok: true,
1580
+ config,
1581
+ path
1582
+ })));
1583
+ };
1584
+ const toolDefinitions = [
1585
+ {
1586
+ name: "getPacketHealth",
1587
+ description: "Get overall health status of the envpkt credential packet — returns audit results including secret statuses, expiration info, and issues",
1588
+ inputSchema: {
1589
+ type: "object",
1590
+ properties: { configPath: {
1591
+ type: "string",
1592
+ description: "Optional path to envpkt.toml"
1593
+ } }
1594
+ }
1595
+ },
1596
+ {
1597
+ name: "listCapabilities",
1598
+ description: "List capabilities declared by the agent and per-secret capabilities",
1599
+ inputSchema: {
1600
+ type: "object",
1601
+ properties: { configPath: {
1602
+ type: "string",
1603
+ description: "Optional path to envpkt.toml"
1604
+ } }
1605
+ }
1606
+ },
1607
+ {
1608
+ name: "getSecretMeta",
1609
+ description: "Get metadata for a specific secret by key name — returns service, purpose, expiration, provisioner, and other five-W details",
1610
+ inputSchema: {
1611
+ type: "object",
1612
+ properties: {
1613
+ key: {
1614
+ type: "string",
1615
+ description: "Secret key name to look up"
1616
+ },
1617
+ configPath: {
1618
+ type: "string",
1619
+ description: "Optional path to envpkt.toml"
1620
+ }
1621
+ },
1622
+ required: ["key"]
1623
+ }
1624
+ },
1625
+ {
1626
+ name: "checkExpiration",
1627
+ description: "Check expiration status of a specific secret — returns days remaining and whether it needs rotation",
1628
+ inputSchema: {
1629
+ type: "object",
1630
+ properties: {
1631
+ key: {
1632
+ type: "string",
1633
+ description: "Secret key name to check"
1634
+ },
1635
+ configPath: {
1636
+ type: "string",
1637
+ description: "Optional path to envpkt.toml"
1638
+ }
1639
+ },
1640
+ required: ["key"]
1641
+ }
1642
+ }
1643
+ ];
1644
+ const handleGetPacketHealth = (args) => {
1645
+ const loaded = loadConfigForTool(args.configPath);
1646
+ if (!loaded.ok) return loaded.result;
1647
+ const { config, path } = loaded;
1648
+ const audit = computeAudit(config);
1649
+ const secretDetails = audit.secrets.toArray().map((s) => ({
1650
+ key: s.key,
1651
+ service: s.service.fold(() => null, (sv) => sv),
1652
+ status: s.status,
1653
+ days_remaining: s.days_remaining.fold(() => null, (d) => d),
1654
+ rotation_url: s.rotation_url.fold(() => null, (u) => u),
1655
+ issues: s.issues.toArray()
1656
+ }));
1657
+ return textResult(JSON.stringify({
1658
+ path,
1659
+ status: audit.status,
1660
+ total: audit.total,
1661
+ healthy: audit.healthy,
1662
+ expiring_soon: audit.expiring_soon,
1663
+ expired: audit.expired,
1664
+ stale: audit.stale,
1665
+ missing: audit.missing,
1666
+ secrets: secretDetails
1667
+ }, null, 2));
1668
+ };
1669
+ const handleListCapabilities = (args) => {
1670
+ const loaded = loadConfigForTool(args.configPath);
1671
+ if (!loaded.ok) return loaded.result;
1672
+ const { config } = loaded;
1673
+ const agentCapabilities = config.agent?.capabilities ?? [];
1674
+ const secretCapabilities = {};
1675
+ for (const [key, meta] of Object.entries(config.meta)) if (meta.capabilities && meta.capabilities.length > 0) secretCapabilities[key] = meta.capabilities;
1676
+ return textResult(JSON.stringify({
1677
+ agent: config.agent ? {
1678
+ name: config.agent.name,
1679
+ consumer: config.agent.consumer,
1680
+ description: config.agent.description,
1681
+ capabilities: agentCapabilities
1682
+ } : null,
1683
+ secrets: secretCapabilities
1684
+ }, null, 2));
1685
+ };
1686
+ const handleGetSecretMeta = (args) => {
1687
+ const key = args.key;
1688
+ if (!key) return errorResult("Missing required argument: key");
1689
+ const loaded = loadConfigForTool(args.configPath);
1690
+ if (!loaded.ok) return loaded.result;
1691
+ const { config } = loaded;
1692
+ const meta = config.meta[key];
1693
+ if (!meta) return errorResult(`Secret not found: ${key}`);
1694
+ return textResult(JSON.stringify({
1695
+ key,
1696
+ ...meta
1697
+ }, null, 2));
1698
+ };
1699
+ const handleCheckExpiration = (args) => {
1700
+ const key = args.key;
1701
+ if (!key) return errorResult("Missing required argument: key");
1702
+ const loaded = loadConfigForTool(args.configPath);
1703
+ if (!loaded.ok) return loaded.result;
1704
+ const { config } = loaded;
1705
+ return computeAudit(config).secrets.find((s) => s.key === key).fold(() => errorResult(`Secret not found: ${key}`), (s) => textResult(JSON.stringify({
1706
+ key: s.key,
1707
+ status: s.status,
1708
+ days_remaining: s.days_remaining.fold(() => null, (d) => d),
1709
+ expires: s.expires.fold(() => null, (e) => e),
1710
+ rotation_url: s.rotation_url.fold(() => null, (u) => u),
1711
+ needs_rotation: s.status === "expired" || s.status === "expiring_soon",
1712
+ issues: s.issues.toArray()
1713
+ }, null, 2)));
1714
+ };
1715
+ const handlers = {
1716
+ getPacketHealth: handleGetPacketHealth,
1717
+ listCapabilities: handleListCapabilities,
1718
+ getSecretMeta: handleGetSecretMeta,
1719
+ checkExpiration: handleCheckExpiration
1720
+ };
1721
+ const callTool = (name, args) => {
1722
+ const handler = handlers[name];
1723
+ if (!handler) return errorResult(`Unknown tool: ${name}`);
1724
+ return handler(args);
1725
+ };
1726
+
1727
+ //#endregion
1728
+ //#region src/mcp/server.ts
1729
+ const createServer = () => {
1730
+ const server = new Server({
1731
+ name: "envpkt",
1732
+ version: "0.1.0"
1733
+ }, {
1734
+ capabilities: {
1735
+ tools: {},
1736
+ resources: {}
1737
+ },
1738
+ instructions: "envpkt provides credential lifecycle awareness for AI agents. Use tools to check health, capabilities, and secret metadata. No secret values are ever exposed."
1739
+ });
1740
+ server.setRequestHandler(ListToolsRequestSchema, async () => ({ tools: toolDefinitions.map((t) => ({
1741
+ name: t.name,
1742
+ description: t.description,
1743
+ inputSchema: t.inputSchema
1744
+ })) }));
1745
+ server.setRequestHandler(CallToolRequestSchema, async (request) => {
1746
+ const { name, arguments: args } = request.params;
1747
+ return callTool(name, args ?? {});
1748
+ });
1749
+ server.setRequestHandler(ListResourcesRequestSchema, async () => ({ resources: [...resourceDefinitions] }));
1750
+ server.setRequestHandler(ReadResourceRequestSchema, async (request) => {
1751
+ const { uri } = request.params;
1752
+ const result = readResource(uri);
1753
+ if (!result) return { contents: [{
1754
+ uri,
1755
+ mimeType: "text/plain",
1756
+ text: `Resource not found: ${uri}`
1757
+ }] };
1758
+ return result;
1759
+ });
1760
+ return server;
1761
+ };
1762
+ const startServer = async () => {
1763
+ const server = createServer();
1764
+ const transport = new StdioServerTransport();
1765
+ await server.connect(transport);
1766
+ };
1767
+
1768
+ //#endregion
1769
+ export { AgentIdentitySchema, CallbackConfigSchema, ConsumerType, EnvpktBootError, EnvpktConfigSchema, LifecycleConfigSchema, SecretMetaSchema, ToolsConfigSchema, ageAvailable, boot, bootSafe, callTool, compareFnoxAndEnvpkt, computeAudit, createServer, deriveServiceFromName, detectFnox, envCheck, envScan, extractFnoxKeys, findConfigPath, fnoxAvailable, fnoxExport, fnoxGet, formatPacket, generateTomlFromScan, loadCatalog, loadConfig, loadConfigFromCwd, maskValue, matchEnvVar, matchValueShape, parseToml, readConfigFile, readFnoxConfig, readResource, resolveConfig, resolveConfigPath, resolveSecrets, resourceDefinitions, scanEnv, scanFleet, startServer, toolDefinitions, unwrapAgentKey, validateConfig };