envpkt 0.7.2 → 0.8.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.
Files changed (4) hide show
  1. package/dist/cli.js +475 -470
  2. package/dist/index.d.ts +108 -99
  3. package/dist/index.js +292 -303
  4. package/package.json +27 -29
package/dist/cli.js CHANGED
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env node
2
2
  import { chmodSync, existsSync, mkdirSync, readFileSync, readdirSync, statSync, writeFileSync } from "node:fs";
3
- import { dirname, join, resolve } from "node:path";
3
+ import { basename, dirname, join, resolve } from "node:path";
4
4
  import { fileURLToPath } from "node:url";
5
5
  import { Command } from "commander";
6
6
  import { Cond, Either, Left, List, Option, Right, Try } from "functype";
@@ -14,7 +14,6 @@ import { Server } from "@modelcontextprotocol/sdk/server/index.js";
14
14
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
15
15
  import { CallToolRequestSchema, ListResourcesRequestSchema, ListToolsRequestSchema, ReadResourceRequestSchema } from "@modelcontextprotocol/sdk/types.js";
16
16
  import { createInterface } from "node:readline";
17
-
18
17
  //#region src/core/audit.ts
19
18
  const MS_PER_DAY = 864e5;
20
19
  const WARN_BEFORE_DAYS = 30;
@@ -25,17 +24,17 @@ const parseDate = (dateStr) => {
25
24
  };
26
25
  const classifySecret = (key, meta, fnoxKeys, staleWarningDays, requireExpiration, requireService, today) => {
27
26
  const issues = [];
28
- const created = Option(meta?.created).flatMap(parseDate);
29
- const expires = Option(meta?.expires).flatMap(parseDate);
30
- const rotationUrl = Option(meta?.rotation_url);
31
- const purpose = Option(meta?.purpose);
32
- const service = Option(meta?.service);
27
+ const created = Option(meta.created).flatMap(parseDate);
28
+ const expires = Option(meta.expires).flatMap(parseDate);
29
+ const rotationUrl = Option(meta.rotation_url);
30
+ const purpose = Option(meta.purpose);
31
+ const service = Option(meta.service);
33
32
  const daysRemaining = expires.map((exp) => daysBetween(today, exp));
34
33
  const daysSinceCreated = created.map((c) => daysBetween(c, today));
35
34
  const isExpired = daysRemaining.fold(() => false, (d) => d < 0);
36
35
  const isExpiringSoon = daysRemaining.fold(() => false, (d) => d >= 0 && d <= WARN_BEFORE_DAYS);
37
36
  const isStale = daysSinceCreated.fold(() => false, (d) => d > staleWarningDays);
38
- const hasSealed = !!meta?.encrypted_value;
37
+ const hasSealed = !!meta.encrypted_value;
39
38
  const isMissing = fnoxKeys.size > 0 && !fnoxKeys.has(key) && !hasSealed;
40
39
  const isMissingMetadata = requireExpiration && expires.isNone() || requireService && service.isNone();
41
40
  if (isExpired) issues.push("Secret has expired");
@@ -53,8 +52,8 @@ const classifySecret = (key, meta, fnoxKeys, staleWarningDays, requireExpiration
53
52
  days_remaining: daysRemaining,
54
53
  rotation_url: rotationUrl,
55
54
  purpose,
56
- created: Option(meta?.created),
57
- expires: Option(meta?.expires),
55
+ created: Option(meta.created),
56
+ expires: Option(meta.expires),
58
57
  issues: List(issues)
59
58
  };
60
59
  };
@@ -92,18 +91,17 @@ const computeAudit = (config, fnoxKeys, today) => {
92
91
  };
93
92
  const computeEnvAudit = (config, env = process.env) => {
94
93
  const envEntries = config.env ?? {};
95
- const entries = [];
96
- for (const [key, entry] of Object.entries(envEntries)) {
94
+ const entries = Object.entries(envEntries).map(([key, entry]) => {
97
95
  const currentValue = env[key];
98
96
  const status = Cond.of().when(currentValue === void 0, "missing").elseWhen(currentValue !== entry.value, "overridden").else("default");
99
- entries.push({
97
+ return {
100
98
  key,
101
99
  defaultValue: entry.value,
102
100
  currentValue,
103
101
  status,
104
102
  purpose: entry.purpose
105
- });
106
- }
103
+ };
104
+ });
107
105
  return {
108
106
  entries,
109
107
  total: entries.length,
@@ -112,7 +110,6 @@ const computeEnvAudit = (config, env = process.env) => {
112
110
  missing: entries.filter((e) => e.status === "missing").length
113
111
  };
114
112
  };
115
-
116
113
  //#endregion
117
114
  //#region src/core/schema.ts
118
115
  const DATE_RE$1 = /^\d{4}-\d{2}-\d{2}$/;
@@ -207,7 +204,6 @@ const EnvpktConfigSchema = Type.Object({
207
204
  title: "envpkt configuration",
208
205
  description: "Credential lifecycle and fleet management configuration for AI agents"
209
206
  });
210
-
211
207
  //#endregion
212
208
  //#region src/core/config.ts
213
209
  const CONFIG_FILENAME$2 = "envpkt.toml";
@@ -223,7 +219,7 @@ const normalizeDates = (obj) => {
223
219
  /** Expand ~ and $ENV_VAR / ${ENV_VAR} in a path string (silent — unresolved vars become "") */
224
220
  const expandPath = (p) => {
225
221
  return Path.expandTilde(p).replace(/\$\{(\w+)\}|\$(\w+)/g, (_, braced, bare) => {
226
- const name = braced ?? bare ?? "";
222
+ const name = Option(braced).fold(() => Option(bare).fold(() => "", (b) => b), (b) => b);
227
223
  return Env.getOrDefault(name, "");
228
224
  });
229
225
  };
@@ -258,10 +254,9 @@ const ENV_FALLBACK_PATHS = [
258
254
  ];
259
255
  /** Build discovery paths dynamically from Platform home and cloud storage detection */
260
256
  const buildSearchPaths = () => {
261
- const paths = [];
262
- for (const home of Platform.homeDirs().toArray()) paths.push(join(home, ".envpkt", CONFIG_FILENAME$2));
263
- for (const cloud of Platform.cloudStorageDirs().toArray()) paths.push(join(cloud.path, ".envpkt", CONFIG_FILENAME$2));
264
- return paths;
257
+ const homePaths = Platform.homeDirs().toArray().map((home) => join(home, ".envpkt", CONFIG_FILENAME$2));
258
+ const cloudPaths = Platform.cloudStorageDirs().toArray().map((cloud) => join(cloud.path, ".envpkt", CONFIG_FILENAME$2));
259
+ return [...homePaths, ...cloudPaths];
265
260
  };
266
261
  /** Discover config by checking CWD, then ENVPKT_SEARCH_PATH, then dynamic Platform paths */
267
262
  const discoverConfig = (cwd) => {
@@ -270,28 +265,24 @@ const discoverConfig = (cwd) => {
270
265
  path: cwdCandidate,
271
266
  source: "cwd"
272
267
  });
273
- const customPaths = Env.get("ENVPKT_SEARCH_PATH").fold(() => [], (v) => v.split(":").filter(Boolean));
274
- for (const template of customPaths) {
275
- const expanded = expandPath(template);
276
- if (!expanded || expanded.startsWith("/.envpkt")) continue;
277
- const matches = expandGlobPath(expanded);
278
- if (matches.length > 0) return Option({
279
- path: matches[0],
280
- source: "search"
281
- });
282
- }
283
- for (const p of buildSearchPaths()) if (Fs.existsSync(p)) return Option({
284
- path: p,
268
+ const customMatch = Env.get("ENVPKT_SEARCH_PATH").fold(() => [], (v) => v.split(":").filter(Boolean)).map((template) => ({
269
+ template,
270
+ expanded: expandPath(template)
271
+ })).filter(({ expanded }) => expanded !== "" && !expanded.startsWith("/.envpkt")).map(({ expanded }) => expandGlobPath(expanded)).find((matches) => matches.length > 0);
272
+ if (customMatch) return Option({
273
+ path: customMatch[0],
274
+ source: "search"
275
+ });
276
+ const platformMatch = buildSearchPaths().find((p) => Fs.existsSync(p));
277
+ if (platformMatch) return Option({
278
+ path: platformMatch,
279
+ source: "search"
280
+ });
281
+ const fallbackMatch = ENV_FALLBACK_PATHS.map((template) => expandPath(template)).filter((expanded) => expanded !== "" && !expanded.startsWith("/.envpkt")).find((expanded) => Fs.existsSync(expanded));
282
+ if (fallbackMatch) return Option({
283
+ path: fallbackMatch,
285
284
  source: "search"
286
285
  });
287
- for (const template of ENV_FALLBACK_PATHS) {
288
- const expanded = expandPath(template);
289
- if (!expanded || expanded.startsWith("/.envpkt")) continue;
290
- if (Fs.existsSync(expanded)) return Option({
291
- path: expanded,
292
- source: "search"
293
- });
294
- }
295
286
  return Option(void 0);
296
287
  };
297
288
  /** Read a config file, returning Either<ConfigError, string> */
@@ -368,7 +359,6 @@ const resolveConfigPath = (flagPath, envVar, cwd) => {
368
359
  source
369
360
  }));
370
361
  };
371
-
372
362
  //#endregion
373
363
  //#region src/core/catalog.ts
374
364
  /** Load and validate a catalog file, mapping ConfigError → CatalogError */
@@ -384,22 +374,21 @@ const loadCatalog = (catalogPath) => loadConfig(catalogPath).fold((err) => {
384
374
  }, (config) => Right(config));
385
375
  /** Resolve secrets by merging catalog meta with agent overrides (shallow merge) */
386
376
  const resolveSecrets = (agentMeta, catalogMeta, agentSecrets, catalogPath) => {
387
- const resolved = {};
388
- for (const key of agentSecrets) {
389
- const catalogEntry = catalogMeta[key];
390
- if (!catalogEntry) return Left({
377
+ return agentSecrets.reduce((acc, key) => acc.flatMap((resolved) => {
378
+ if (!(key in catalogMeta)) return Left({
391
379
  _tag: "SecretNotInCatalog",
392
380
  key,
393
381
  catalogPath
394
382
  });
395
- const agentOverride = agentMeta[key];
396
- if (agentOverride) resolved[key] = {
397
- ...catalogEntry,
398
- ...agentOverride
399
- };
400
- else resolved[key] = catalogEntry;
401
- }
402
- return Right(resolved);
383
+ const catalogEntry = catalogMeta[key];
384
+ return Right({
385
+ ...resolved,
386
+ [key]: key in agentMeta ? {
387
+ ...catalogEntry,
388
+ ...agentMeta[key]
389
+ } : catalogEntry
390
+ });
391
+ }), Right({}));
403
392
  };
404
393
  /** Resolve an agent config against its catalog (if any), producing a flat self-contained config */
405
394
  const resolveConfig = (agentConfig, agentConfigDir) => {
@@ -417,13 +406,9 @@ const resolveConfig = (agentConfig, agentConfigDir) => {
417
406
  const agentSecrets = agentConfig.identity.secrets;
418
407
  const agentSecretEntries = agentConfig.secret ?? {};
419
408
  return loadCatalog(catalogPath).flatMap((catalogConfig) => resolveSecrets(agentSecretEntries, catalogConfig.secret ?? {}, agentSecrets, catalogPath).map((resolvedMeta) => {
420
- const merged = [];
421
- const overridden = [];
409
+ const merged = [...agentSecrets];
410
+ const overridden = agentSecrets.filter((key) => key in agentSecretEntries);
422
411
  const warnings = [];
423
- for (const key of agentSecrets) {
424
- merged.push(key);
425
- if (agentSecretEntries[key]) overridden.push(key);
426
- }
427
412
  const { catalog: _catalog, ...agentWithoutCatalog } = agentConfig;
428
413
  const identityData = agentConfig.identity ? (() => {
429
414
  const { secrets: _secrets, ...rest } = agentConfig.identity;
@@ -445,7 +430,6 @@ const resolveConfig = (agentConfig, agentConfigDir) => {
445
430
  };
446
431
  }));
447
432
  };
448
-
449
433
  //#endregion
450
434
  //#region src/cli/output.ts
451
435
  const RESET = "\x1B[0m";
@@ -454,6 +438,8 @@ const DIM = "\x1B[2m";
454
438
  const RED = "\x1B[31m";
455
439
  const GREEN = "\x1B[32m";
456
440
  const YELLOW = "\x1B[33m";
441
+ const BLUE = "\x1B[34m";
442
+ const MAGENTA = "\x1B[35m";
457
443
  const CYAN = "\x1B[36m";
458
444
  const statusColor = (status) => {
459
445
  switch (status) {
@@ -652,7 +638,6 @@ const formatConfigSource = (path, source) => {
652
638
  if (source === "cwd") return "";
653
639
  return `${DIM}envpkt: loaded ${path}${RESET}`;
654
640
  };
655
-
656
641
  //#endregion
657
642
  //#region src/cli/commands/audit.ts
658
643
  const runAudit = (options) => {
@@ -683,11 +668,11 @@ const formatEnvAuditTable = (config) => {
683
668
  return;
684
669
  }
685
670
  console.log(`\n${BOLD}Environment Defaults${RESET} (${envAudit.total} entries)`);
686
- for (const entry of envAudit.entries) {
671
+ envAudit.entries.forEach((entry) => {
687
672
  const statusIcon = entry.status === "default" ? `${GREEN}=${RESET}` : entry.status === "overridden" ? `${YELLOW}~${RESET}` : `${RED}!${RESET}`;
688
673
  const statusLabel = entry.status === "default" ? `${DIM}using default${RESET}` : entry.status === "overridden" ? `${YELLOW}overridden${RESET} (${entry.currentValue})` : `${RED}not set${RESET}`;
689
674
  console.log(` ${statusIcon} ${BOLD}${entry.key}${RESET} = "${entry.defaultValue}" ${statusLabel}`);
690
- }
675
+ });
691
676
  };
692
677
  const formatEnvAuditJson = (config) => {
693
678
  const envAudit = computeEnvAudit(config);
@@ -705,24 +690,24 @@ const runAuditOnConfig = (config, options) => {
705
690
  const secretEntries = config.secret ?? {};
706
691
  return {
707
692
  ...audit,
708
- secrets: audit.secrets.filter((s) => !!secretEntries[s.key]?.encrypted_value)
693
+ secrets: audit.secrets.filter((s) => !!secretEntries[s.key].encrypted_value)
709
694
  };
710
695
  })() : audit;
711
696
  const afterExternal = options.external ? (() => {
712
697
  const secretEntries = config.secret ?? {};
713
698
  return {
714
699
  ...afterSealed,
715
- secrets: afterSealed.secrets.filter((s) => !secretEntries[s.key]?.encrypted_value)
700
+ secrets: afterSealed.secrets.filter((s) => !secretEntries[s.key].encrypted_value)
716
701
  };
717
702
  })() : afterSealed;
718
703
  const afterStatus = options.status ? {
719
704
  ...afterExternal,
720
705
  secrets: afterExternal.secrets.filter((s) => s.status === options.status)
721
706
  } : afterExternal;
722
- const filtered = options.expiring !== void 0 ? {
707
+ const filtered = Option(options.expiring).fold(() => afterStatus, (expiring) => ({
723
708
  ...afterStatus,
724
- secrets: afterStatus.secrets.filter((s) => s.days_remaining.fold(() => false, (d) => d >= 0 && d <= options.expiring))
725
- } : afterStatus;
709
+ secrets: afterStatus.secrets.filter((s) => s.days_remaining.fold(() => false, (d) => d >= 0 && d <= expiring))
710
+ }));
726
711
  if (options.format === "json") console.log(formatAuditJson(filtered));
727
712
  else if (options.format === "minimal") console.log(formatAuditMinimal(filtered));
728
713
  else console.log(formatAudit(filtered));
@@ -731,7 +716,6 @@ const runAuditOnConfig = (config, options) => {
731
716
  const code = options.strict ? exitCodeForAudit(audit) : audit.status === "critical" ? 2 : 0;
732
717
  process.exit(code);
733
718
  };
734
-
735
719
  //#endregion
736
720
  //#region src/fnox/cli.ts
737
721
  /** Export all secrets from fnox as key=value pairs for a given profile */
@@ -754,17 +738,16 @@ const fnoxExport = (profile, agentKey) => {
754
738
  message: `fnox export failed: ${err}`
755
739
  }), (output) => {
756
740
  const entries = {};
757
- for (const line of output.split("\n")) {
741
+ output.split("\n").forEach((line) => {
758
742
  const eq = line.indexOf("=");
759
743
  if (eq > 0) {
760
744
  const key = line.slice(0, eq).trim();
761
745
  entries[key] = line.slice(eq + 1).trim();
762
746
  }
763
- }
747
+ });
764
748
  return Right(entries);
765
749
  });
766
750
  };
767
-
768
751
  //#endregion
769
752
  //#region src/fnox/detect.ts
770
753
  const FNOX_CONFIG = "fnox.toml";
@@ -778,7 +761,6 @@ const fnoxAvailable = () => Try(() => {
778
761
  execFileSync("fnox", ["--version"], { stdio: "pipe" });
779
762
  return true;
780
763
  }).fold(() => false, (v) => v);
781
-
782
764
  //#endregion
783
765
  //#region src/fnox/identity.ts
784
766
  /** Check if the age CLI is available on PATH */
@@ -786,29 +768,39 @@ const ageAvailable = () => Try(() => {
786
768
  execFileSync("age", ["--version"], { stdio: "pipe" });
787
769
  return true;
788
770
  }).fold(() => false, (v) => v);
789
- /** Unwrap an encrypted agent key using age --decrypt */
771
+ /**
772
+ * Extract the secret key from an age identity file (plain or encrypted).
773
+ * - Plain identity files (from `age-keygen`) contain `AGE-SECRET-KEY-*` lines directly
774
+ * - Encrypted identity files need `age --decrypt` to unwrap
775
+ */
790
776
  const unwrapAgentKey = (identityPath) => {
791
777
  if (!existsSync(identityPath)) return Left({
792
778
  _tag: "IdentityNotFound",
793
779
  path: identityPath
794
780
  });
795
- if (!ageAvailable()) return Left({
796
- _tag: "AgeNotFound",
797
- message: "age CLI not found on PATH"
798
- });
799
- return Try(() => execFileSync("age", ["--decrypt", identityPath], {
800
- stdio: [
801
- "pipe",
802
- "pipe",
803
- "pipe"
804
- ],
805
- encoding: "utf-8"
806
- })).fold((err) => Left({
781
+ return Try(() => readFileSync(identityPath, "utf-8")).fold((err) => Left({
807
782
  _tag: "DecryptFailed",
808
- message: `age decrypt failed: ${err}`
809
- }), (output) => Right(output.trim()));
783
+ message: `Failed to read identity file: ${err}`
784
+ }), (content) => {
785
+ const secretKeyLine = content.split("\n").find((l) => l.startsWith("AGE-SECRET-KEY-"));
786
+ if (secretKeyLine) return Right(secretKeyLine.trim());
787
+ if (!ageAvailable()) return Left({
788
+ _tag: "AgeNotFound",
789
+ message: "age CLI not found on PATH"
790
+ });
791
+ return Try(() => execFileSync("age", ["--decrypt", identityPath], {
792
+ stdio: [
793
+ "pipe",
794
+ "pipe",
795
+ "pipe"
796
+ ],
797
+ encoding: "utf-8"
798
+ })).fold((err) => Left({
799
+ _tag: "DecryptFailed",
800
+ message: `age decrypt failed: ${err}`
801
+ }), (output) => Right(output.trim()));
802
+ });
810
803
  };
811
-
812
804
  //#endregion
813
805
  //#region src/fnox/parse.ts
814
806
  /** Read and parse fnox.toml, extracting secret keys and profiles */
@@ -829,7 +821,6 @@ const readFnoxConfig = (path) => Try(() => readFileSync(path, "utf-8")).fold((er
829
821
  }));
830
822
  /** Extract the set of secret key names from a parsed fnox config */
831
823
  const extractFnoxKeys = (config) => new Set(Object.keys(config.secrets));
832
-
833
824
  //#endregion
834
825
  //#region src/core/keygen.ts
835
826
  /** Resolve the age identity file path: ENVPKT_AGE_KEY_FILE env var > ~/.envpkt/age-key.txt */
@@ -883,53 +874,83 @@ const generateKeypair = (options) => {
883
874
  }));
884
875
  });
885
876
  };
886
- /** Update identity.recipient in an envpkt.toml file, preserving structure */
887
- const updateConfigRecipient = (configPath, recipient) => {
888
- return Try(() => readFileSync(configPath, "utf-8")).fold((err) => Left({
877
+ /** Update identity fields (recipient, key_file, name) in an envpkt.toml file, preserving structure */
878
+ const updateConfigIdentity = (configPath, options) => {
879
+ const readResult = Try(() => readFileSync(configPath, "utf-8"));
880
+ const fieldUpdaters = [
881
+ {
882
+ re: /^recipient\s*=/,
883
+ line: `recipient = "${options.recipient}"`
884
+ },
885
+ ...options.name ? [{
886
+ re: /^name\s*=/,
887
+ line: `name = "${options.name}"`
888
+ }] : [],
889
+ ...options.keyFile ? [{
890
+ re: /^key_file\s*=/,
891
+ line: `key_file = "${options.keyFile}"`
892
+ }] : []
893
+ ];
894
+ return readResult.fold((err) => Left({
889
895
  _tag: "ConfigUpdateError",
890
896
  message: `Failed to read config: ${err}`
891
897
  }), (raw) => {
892
898
  const lines = raw.split("\n");
893
- const output = [];
894
- let inIdentitySection = false;
895
- let recipientUpdated = false;
896
- let hasIdentitySection = false;
897
- for (const line of lines) {
898
- if (/^\[identity\]\s*$/.test(line)) {
899
- inIdentitySection = true;
900
- hasIdentitySection = true;
901
- output.push(line);
902
- continue;
903
- }
899
+ const updatedFields = /* @__PURE__ */ new Set();
900
+ const acc = lines.reduce((state, line) => {
901
+ if (/^\[identity\]\s*$/.test(line)) return {
902
+ ...state,
903
+ output: [...state.output, line],
904
+ inIdentitySection: true,
905
+ hasIdentitySection: true
906
+ };
904
907
  if (/^\[/.test(line) && !/^\[identity\]\s*$/.test(line)) {
905
- if (inIdentitySection && !recipientUpdated) {
906
- output.push(`recipient = "${recipient}"`);
907
- recipientUpdated = true;
908
- }
909
- inIdentitySection = false;
910
- output.push(line);
911
- continue;
908
+ const missing = state.inIdentitySection ? fieldUpdaters.filter((f) => !updatedFields.has(f.re.source)).map((f) => f.line) : [];
909
+ missing.forEach((l) => updatedFields.add(l));
910
+ return {
911
+ ...state,
912
+ output: [
913
+ ...state.output,
914
+ ...missing,
915
+ line
916
+ ],
917
+ inIdentitySection: false
918
+ };
912
919
  }
913
- if (inIdentitySection && /^recipient\s*=/.test(line)) {
914
- output.push(`recipient = "${recipient}"`);
915
- recipientUpdated = true;
916
- continue;
920
+ if (state.inIdentitySection) {
921
+ const match = fieldUpdaters.find((f) => f.re.test(line));
922
+ if (match) {
923
+ updatedFields.add(match.re.source);
924
+ return {
925
+ ...state,
926
+ output: [...state.output, match.line]
927
+ };
928
+ }
917
929
  }
918
- output.push(line);
919
- }
920
- if (inIdentitySection && !recipientUpdated) output.push(`recipient = "${recipient}"`);
921
- if (!hasIdentitySection) {
922
- output.push("");
923
- output.push("[identity]");
924
- output.push(`recipient = "${recipient}"`);
925
- }
930
+ return {
931
+ ...state,
932
+ output: [...state.output, line]
933
+ };
934
+ }, {
935
+ output: [],
936
+ inIdentitySection: false,
937
+ hasIdentitySection: false
938
+ });
939
+ const missingAtEof = acc.inIdentitySection ? fieldUpdaters.filter((f) => !updatedFields.has(f.re.source)).map((f) => f.line) : [];
940
+ const afterEof = [...acc.output, ...missingAtEof];
941
+ const identityLines = fieldUpdaters.map((f) => f.line);
942
+ const output = !acc.hasIdentitySection ? [
943
+ ...afterEof,
944
+ "",
945
+ "[identity]",
946
+ ...identityLines
947
+ ] : afterEof;
926
948
  return Try(() => writeFileSync(configPath, output.join("\n"))).fold((err) => Left({
927
949
  _tag: "ConfigUpdateError",
928
950
  message: `Failed to write config: ${err}`
929
951
  }), () => Right(true));
930
952
  });
931
953
  };
932
-
933
954
  //#endregion
934
955
  //#region src/core/seal.ts
935
956
  /** Encrypt a plaintext string using age with the given recipient public key (armored output) */
@@ -987,27 +1008,23 @@ const sealSecrets = (meta, values, recipient) => {
987
1008
  _tag: "AgeNotFound",
988
1009
  message: "age CLI not found on PATH"
989
1010
  });
990
- const result = {};
991
- for (const [key, secretMeta] of Object.entries(meta)) {
992
- const plaintext = values[key];
993
- if (plaintext === void 0) {
994
- result[key] = secretMeta;
995
- continue;
996
- }
997
- const outcome = ageEncrypt(plaintext, recipient).fold((err) => Left({
1011
+ return Object.entries(meta).reduce((acc, [key, secretMeta]) => acc.flatMap((result) => {
1012
+ if (!(key in values)) return Right({
1013
+ ...result,
1014
+ [key]: secretMeta
1015
+ });
1016
+ return ageEncrypt(values[key], recipient).mapLeft((err) => ({
998
1017
  _tag: "EncryptFailed",
999
1018
  key,
1000
1019
  message: err.message
1001
- }), (ciphertext) => Right(ciphertext));
1002
- const failed = outcome.fold((err) => err, () => void 0);
1003
- if (failed) return Left(failed);
1004
- const ciphertext = outcome.fold(() => "", (v) => v);
1005
- result[key] = {
1006
- ...secretMeta,
1007
- encrypted_value: ciphertext
1008
- };
1009
- }
1010
- return Right(result);
1020
+ })).map((ciphertext) => ({
1021
+ ...result,
1022
+ [key]: {
1023
+ ...secretMeta,
1024
+ encrypted_value: ciphertext
1025
+ }
1026
+ }));
1027
+ }), Right({}));
1011
1028
  };
1012
1029
  /** Unseal secrets: decrypt encrypted_value for each meta entry that has one */
1013
1030
  const unsealSecrets = (meta, identityPath) => {
@@ -1015,21 +1032,15 @@ const unsealSecrets = (meta, identityPath) => {
1015
1032
  _tag: "AgeNotFound",
1016
1033
  message: "age CLI not found on PATH"
1017
1034
  });
1018
- const result = {};
1019
- for (const [key, secretMeta] of Object.entries(meta)) {
1020
- if (!secretMeta.encrypted_value) continue;
1021
- const outcome = ageDecrypt(secretMeta.encrypted_value, identityPath).fold((err) => Left({
1022
- _tag: "DecryptFailed",
1023
- key,
1024
- message: err.message
1025
- }), (plaintext) => Right(plaintext));
1026
- const failed = outcome.fold((err) => err, () => void 0);
1027
- if (failed) return Left(failed);
1028
- result[key] = outcome.fold(() => "", (v) => v);
1029
- }
1030
- return Right(result);
1035
+ return Object.entries(meta).filter(([, secretMeta]) => secretMeta.encrypted_value !== void 0 && secretMeta.encrypted_value !== "").reduce((acc, [key, secretMeta]) => acc.flatMap((result) => ageDecrypt(secretMeta.encrypted_value, identityPath).mapLeft((err) => ({
1036
+ _tag: "DecryptFailed",
1037
+ key,
1038
+ message: err.message
1039
+ })).map((plaintext) => ({
1040
+ ...result,
1041
+ [key]: plaintext
1042
+ }))), Right({}));
1031
1043
  };
1032
-
1033
1044
  //#endregion
1034
1045
  //#region src/core/boot.ts
1035
1046
  const resolveAndLoad = (opts) => resolveConfigPath(opts.configPath).fold((err) => Left(err), ({ path: configPath, source: configSource }) => loadConfig(configPath).fold((err) => Left(err), (config) => {
@@ -1043,15 +1054,13 @@ const resolveAndLoad = (opts) => resolveConfigPath(opts.configPath).fold((err) =
1043
1054
  }));
1044
1055
  /** Resolve identity file path with explicit fallback control */
1045
1056
  const resolveIdentityFilePath = (config, configDir, useDefaultFallback) => {
1046
- if (config.identity?.key_file) return resolve(configDir, expandPath(config.identity.key_file));
1047
- if (!useDefaultFallback) return void 0;
1057
+ if (config.identity?.key_file) return Option(resolve(configDir, expandPath(config.identity.key_file)));
1058
+ if (!useDefaultFallback) return Option(void 0);
1048
1059
  const defaultPath = resolveKeyPath();
1049
- return existsSync(defaultPath) ? defaultPath : void 0;
1060
+ return existsSync(defaultPath) ? Option(defaultPath) : Option(void 0);
1050
1061
  };
1051
1062
  const resolveIdentityKey = (config, configDir) => {
1052
- const identityPath = resolveIdentityFilePath(config, configDir, false);
1053
- if (!identityPath) return Right(void 0);
1054
- return unwrapAgentKey(identityPath).fold((err) => Left(err), (key) => Right(key));
1063
+ return resolveIdentityFilePath(config, configDir, false).fold(() => Right(Option(void 0)), (path) => unwrapAgentKey(path).fold((err) => Left(err), (key) => Right(Option(key))));
1055
1064
  };
1056
1065
  const detectFnoxKeys = (configDir) => detectFnox(configDir).fold(() => /* @__PURE__ */ new Set(), (fnoxPath) => readFnoxConfig(fnoxPath).fold(() => /* @__PURE__ */ new Set(), (fnoxConfig) => extractFnoxKeys(fnoxConfig)));
1057
1066
  const checkExpiration = (audit, failOnExpired, warnOnly) => {
@@ -1079,10 +1088,8 @@ const looksLikeSecret = (value) => {
1079
1088
  return false;
1080
1089
  };
1081
1090
  const checkEnvMisclassification = (config) => {
1082
- const warnings = [];
1083
1091
  const envEntries = config.env ?? {};
1084
- for (const [key, entry] of Object.entries(envEntries)) if (looksLikeSecret(entry.value)) warnings.push(`[env.${key}] value looks like a secret — consider moving to [secret.${key}]`);
1085
- return warnings;
1092
+ return Object.entries(envEntries).filter(([, entry]) => looksLikeSecret(entry.value)).map(([key]) => `[env.${key}] value looks like a secret — consider moving to [secret.${key}]`);
1086
1093
  };
1087
1094
  /** Programmatic boot — returns Either<BootError, BootResult> */
1088
1095
  const bootSafe = (options) => {
@@ -1093,11 +1100,13 @@ const bootSafe = (options) => {
1093
1100
  return resolveAndLoad(opts).flatMap(({ config, configPath, configDir, configSource }) => {
1094
1101
  const secretEntries = config.secret ?? {};
1095
1102
  const metaKeys = Object.keys(secretEntries);
1096
- const hasSealedValues = metaKeys.some((k) => !!secretEntries[k]?.encrypted_value);
1103
+ const hasSealedValues = metaKeys.some((k) => !!secretEntries[k].encrypted_value);
1097
1104
  const identityKeyResult = resolveIdentityKey(config, configDir);
1098
- const identityKey = identityKeyResult.fold(() => void 0, (k) => k);
1099
- const identityKeyError = identityKeyResult.fold((err) => err, () => void 0);
1100
- if (identityKeyError && !hasSealedValues) return Left(identityKeyError);
1105
+ const identityKey = identityKeyResult.fold(() => Option(void 0), (k) => k);
1106
+ if (identityKeyResult.isLeft() && !hasSealedValues) return identityKeyResult.fold((err) => Left(err), () => Left({
1107
+ _tag: "ReadError",
1108
+ message: "unexpected"
1109
+ }));
1101
1110
  const audit = computeAudit(config, detectFnoxKeys(configDir));
1102
1111
  return checkExpiration(audit, failOnExpired, warnOnly).map((warnings) => {
1103
1112
  const secrets = {};
@@ -1105,40 +1114,47 @@ const bootSafe = (options) => {
1105
1114
  const skipped = [];
1106
1115
  warnings.push(...checkEnvMisclassification(config));
1107
1116
  const envEntries = config.env ?? {};
1108
- const envDefaults = {};
1109
- const overridden = [];
1110
- for (const [key, entry] of Object.entries(envEntries)) if (process.env[key] === void 0) {
1111
- envDefaults[key] = entry.value;
1112
- if (inject) process.env[key] = entry.value;
1113
- } else overridden.push(key);
1117
+ const envEntriesArr = Object.entries(envEntries);
1118
+ const envDefaults = Object.fromEntries(envEntriesArr.flatMap(([key, entry]) => Option(process.env[key]).fold(() => [[key, entry.value]], () => [])));
1119
+ const overridden = envEntriesArr.flatMap(([key]) => Option(process.env[key]).fold(() => [], () => [key]));
1120
+ if (inject) Object.entries(envDefaults).forEach(([key, value]) => {
1121
+ process.env[key] = value;
1122
+ });
1114
1123
  const sealedKeys = /* @__PURE__ */ new Set();
1115
1124
  const identityFilePath = resolveIdentityFilePath(config, configDir, true);
1116
- if (hasSealedValues && identityFilePath) unsealSecrets(secretEntries, identityFilePath).fold((err) => {
1117
- warnings.push(`Sealed value decryption failed: ${err.message}`);
1118
- }, (unsealed) => {
1119
- for (const [key, value] of Object.entries(unsealed)) {
1120
- secrets[key] = value;
1121
- injected.push(key);
1122
- sealedKeys.add(key);
1123
- }
1125
+ if (hasSealedValues) identityFilePath.fold(() => {
1126
+ warnings.push("Sealed values found but no identity file available for decryption");
1127
+ }, (idPath) => {
1128
+ unsealSecrets(secretEntries, idPath).fold((err) => {
1129
+ warnings.push(`Sealed value decryption failed: ${err.message}`);
1130
+ }, (unsealed) => {
1131
+ const unsealedEntries = Object.entries(unsealed);
1132
+ Object.assign(secrets, unsealed);
1133
+ injected.push(...unsealedEntries.map(([key]) => key));
1134
+ unsealedEntries.map(([key]) => key).forEach((key) => sealedKeys.add(key));
1135
+ });
1124
1136
  });
1125
- else if (hasSealedValues && !identityFilePath) warnings.push("Sealed values found but no identity file available for decryption");
1126
1137
  const remainingKeys = metaKeys.filter((k) => !sealedKeys.has(k));
1127
- if (remainingKeys.length > 0) if (fnoxAvailable()) fnoxExport(opts.profile, identityKey).fold((err) => {
1138
+ if (remainingKeys.length > 0) if (fnoxAvailable()) fnoxExport(opts.profile, identityKey.orUndefined()).fold((err) => {
1128
1139
  warnings.push(`fnox export failed: ${err.message}`);
1129
- for (const key of remainingKeys) skipped.push(key);
1140
+ skipped.push(...remainingKeys);
1130
1141
  }, (exported) => {
1131
- for (const key of remainingKeys) if (key in exported) {
1142
+ const found = remainingKeys.filter((key) => key in exported);
1143
+ const notFound = remainingKeys.filter((key) => !(key in exported));
1144
+ found.forEach((key) => {
1132
1145
  secrets[key] = exported[key];
1133
- injected.push(key);
1134
- } else skipped.push(key);
1146
+ });
1147
+ injected.push(...found);
1148
+ skipped.push(...notFound);
1135
1149
  });
1136
1150
  else {
1137
1151
  if (!hasSealedValues) warnings.push("fnox not available — no secrets injected");
1138
1152
  else warnings.push("fnox not available — unsealed secrets could not be resolved");
1139
- for (const key of remainingKeys) skipped.push(key);
1153
+ skipped.push(...remainingKeys);
1140
1154
  }
1141
- if (inject) for (const [key, value] of Object.entries(secrets)) process.env[key] = value;
1155
+ if (inject) Object.entries(secrets).forEach(([key, value]) => {
1156
+ process.env[key] = value;
1157
+ });
1142
1158
  return {
1143
1159
  audit,
1144
1160
  injected,
@@ -1153,7 +1169,6 @@ const bootSafe = (options) => {
1153
1169
  });
1154
1170
  });
1155
1171
  };
1156
-
1157
1172
  //#endregion
1158
1173
  //#region src/core/patterns.ts
1159
1174
  const EXCLUDED_VARS = new Set([
@@ -1777,11 +1792,10 @@ const VALUE_SHAPE_PATTERNS = [
1777
1792
  ];
1778
1793
  /** Detect service from value prefix/shape */
1779
1794
  const matchValueShape = (value) => {
1780
- for (const vp of VALUE_SHAPE_PATTERNS) if (value.startsWith(vp.prefix)) return Option({
1795
+ return Option(VALUE_SHAPE_PATTERNS.find((vp) => value.startsWith(vp.prefix))).map((vp) => ({
1781
1796
  service: vp.service,
1782
1797
  description: vp.description
1783
- });
1784
- return Option(void 0);
1798
+ }));
1785
1799
  };
1786
1800
  /** Strip common suffixes and derive a service name from an env var name */
1787
1801
  const deriveServiceFromName = (name) => {
@@ -1809,22 +1823,22 @@ const deriveServiceFromName = (name) => {
1809
1823
  /** Match a single env var against all patterns */
1810
1824
  const matchEnvVar = (name, value) => {
1811
1825
  if (EXCLUDED_VARS.has(name)) return Option(void 0);
1812
- for (const p of EXACT_NAME_PATTERNS) if (name === p.pattern) return Option({
1826
+ const exactMatch = EXACT_NAME_PATTERNS.find((p) => name === p.pattern);
1827
+ if (exactMatch) return Option({
1813
1828
  envVar: name,
1814
1829
  value,
1815
- service: Option(p.service),
1816
- confidence: p.confidence,
1817
- matchedBy: `exact:${p.pattern}`
1830
+ service: Option(exactMatch.service),
1831
+ confidence: exactMatch.confidence,
1832
+ matchedBy: `exact:${exactMatch.pattern}`
1818
1833
  });
1819
1834
  return matchValueShape(value).fold(() => {
1820
- for (const sp of SUFFIX_PATTERNS) if (name.endsWith(sp.suffix)) return Option({
1835
+ return Option(SUFFIX_PATTERNS.find((sp) => name.endsWith(sp.suffix))).map((sp) => ({
1821
1836
  envVar: name,
1822
1837
  value,
1823
1838
  service: Option(deriveServiceFromName(name)),
1824
1839
  confidence: "medium",
1825
1840
  matchedBy: `suffix:${sp.suffix}`
1826
- });
1827
- return Option(void 0);
1841
+ }));
1828
1842
  }, (vm) => Option({
1829
1843
  envVar: name,
1830
1844
  value,
@@ -1835,11 +1849,10 @@ const matchEnvVar = (name, value) => {
1835
1849
  };
1836
1850
  /** Scan full env, sorted by confidence (high first) then alphabetically */
1837
1851
  const scanEnv = (env) => {
1838
- const results = [];
1839
- for (const [name, value] of Object.entries(env)) {
1840
- if (value === void 0 || value === "") continue;
1841
- matchEnvVar(name, value).fold(() => {}, (m) => results.push(m));
1842
- }
1852
+ const results = Object.entries(env).flatMap(([name, value]) => {
1853
+ if (value === void 0 || value === "") return [];
1854
+ return matchEnvVar(name, value).fold(() => [], (m) => [m]);
1855
+ });
1843
1856
  const confidenceOrder = {
1844
1857
  high: 0,
1845
1858
  medium: 1,
@@ -1852,7 +1865,6 @@ const scanEnv = (env) => {
1852
1865
  });
1853
1866
  return results;
1854
1867
  };
1855
-
1856
1868
  //#endregion
1857
1869
  //#region src/core/env.ts
1858
1870
  /** Scan env for credentials, returning structured results */
@@ -1873,38 +1885,44 @@ const envScan = (env, options) => {
1873
1885
  };
1874
1886
  /** Bidirectional drift detection between config and live environment */
1875
1887
  const envCheck = (config, env) => {
1876
- const entries = [];
1877
1888
  const secretEntries = config.secret ?? {};
1878
1889
  const metaKeys = Object.keys(secretEntries);
1879
1890
  const trackedSet = new Set(metaKeys);
1880
- for (const key of metaKeys) {
1891
+ const secretDriftEntries = metaKeys.map((key) => {
1881
1892
  const meta = secretEntries[key];
1882
1893
  const present = env[key] !== void 0 && env[key] !== "";
1883
- entries.push({
1894
+ return {
1884
1895
  envVar: key,
1885
- service: Option(meta?.service),
1896
+ service: Option(meta.service),
1886
1897
  status: present ? "tracked" : "missing_from_env",
1887
1898
  confidence: Option(void 0)
1888
- });
1889
- }
1899
+ };
1900
+ });
1890
1901
  const envDefaults = config.env ?? {};
1891
- for (const key of Object.keys(envDefaults)) if (!trackedSet.has(key)) {
1902
+ const envDefaultEntries = Object.keys(envDefaults).filter((key) => {
1903
+ if (trackedSet.has(key)) return false;
1892
1904
  trackedSet.add(key);
1905
+ return true;
1906
+ }).map((key) => {
1893
1907
  const present = env[key] !== void 0 && env[key] !== "";
1894
- entries.push({
1908
+ return {
1895
1909
  envVar: key,
1896
1910
  service: Option(void 0),
1897
1911
  status: present ? "tracked" : "missing_from_env",
1898
1912
  confidence: Option(void 0)
1899
- });
1900
- }
1901
- const envMatches = scanEnv(env);
1902
- for (const match of envMatches) if (!trackedSet.has(match.envVar)) entries.push({
1913
+ };
1914
+ });
1915
+ const untrackedEntries = scanEnv(env).filter((match) => !trackedSet.has(match.envVar)).map((match) => ({
1903
1916
  envVar: match.envVar,
1904
1917
  service: match.service,
1905
1918
  status: "untracked",
1906
1919
  confidence: Option(match.confidence)
1907
- });
1920
+ }));
1921
+ const entries = [
1922
+ ...secretDriftEntries,
1923
+ ...envDefaultEntries,
1924
+ ...untrackedEntries
1925
+ ];
1908
1926
  const tracked_and_present = entries.filter((e) => e.status === "tracked").length;
1909
1927
  const missing_from_env = entries.filter((e) => e.status === "missing_from_env").length;
1910
1928
  const untracked_credentials = entries.filter((e) => e.status === "untracked").length;
@@ -1919,10 +1937,9 @@ const envCheck = (config, env) => {
1919
1937
  const todayIso$1 = () => (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
1920
1938
  /** Generate TOML [secret.*] blocks from scan results, mirroring init.ts pattern */
1921
1939
  const generateTomlFromScan = (matches) => {
1922
- const blocks = [];
1923
- for (const match of matches) {
1940
+ return matches.map((match) => {
1924
1941
  const svc = match.service.fold(() => match.envVar.toLowerCase().replace(/_/g, "-"), (s) => s);
1925
- blocks.push(`[secret.${match.envVar}]
1942
+ return `[secret.${match.envVar}]
1926
1943
  service = "${svc}"
1927
1944
  # purpose = "" # Why: what this secret enables
1928
1945
  # capabilities = [] # What operations this grants
@@ -1931,11 +1948,9 @@ created = "${todayIso$1()}"
1931
1948
  # rotation_url = "" # URL for rotation procedure
1932
1949
  # source = "" # Where the value originates (e.g. vault, ci)
1933
1950
  # tags = {}
1934
- `);
1935
- }
1936
- return blocks.join("\n");
1951
+ `;
1952
+ }).join("\n");
1937
1953
  };
1938
-
1939
1954
  //#endregion
1940
1955
  //#region src/core/toml-edit.ts
1941
1956
  const SECTION_RE = /^\[.+\]\s*$/;
@@ -1946,11 +1961,7 @@ const MULTILINE_OPEN = "\"\"\"";
1946
1961
  * Handles multiline `"""..."""` values when scanning for section boundaries.
1947
1962
  */
1948
1963
  const findSectionRange = (lines, sectionHeader) => {
1949
- let start = -1;
1950
- for (let i = 0; i < lines.length; i++) if (lines[i].trim() === sectionHeader) {
1951
- start = i;
1952
- break;
1953
- }
1964
+ const start = lines.findIndex((l) => l.trim() === sectionHeader);
1954
1965
  if (start === -1) return void 0;
1955
1966
  let end = lines.length;
1956
1967
  let inMultiline = false;
@@ -2072,7 +2083,8 @@ const updateSectionFields = (raw, sectionHeader, updates) => {
2072
2083
  }
2073
2084
  remaining.push(line);
2074
2085
  }
2075
- for (const [key, value] of Object.entries(updates)) if (value !== null && !updatedKeys.has(key)) remaining.push(`${key} = ${value}`);
2086
+ const newFields = Object.entries(updates).filter(([key, value]) => value !== null && !updatedKeys.has(key)).map(([key, value]) => `${key} = ${value}`);
2087
+ remaining.push(...newFields);
2076
2088
  const result = [
2077
2089
  ...before,
2078
2090
  ...remaining,
@@ -2085,7 +2097,6 @@ const updateSectionFields = (raw, sectionHeader, updates) => {
2085
2097
  * Ensures proper spacing (double newline before the block).
2086
2098
  */
2087
2099
  const appendSection = (raw, block) => `${raw.trimEnd()}\n\n${block}`;
2088
-
2089
2100
  //#endregion
2090
2101
  //#region src/cli/commands/env.ts
2091
2102
  const printPostWriteGuidance = () => {
@@ -2173,13 +2184,21 @@ const runEnvExport = (options) => {
2173
2184
  }, (boot) => {
2174
2185
  const sourceMsg = formatConfigSource(boot.configPath, boot.configSource);
2175
2186
  if (sourceMsg) console.error(sourceMsg);
2176
- for (const warning of boot.warnings) console.error(`${YELLOW}Warning:${RESET} ${warning}`);
2177
- for (const [key, value] of Object.entries(boot.envDefaults)) console.log(`export ${key}='${shellEscape(value)}'`);
2187
+ boot.warnings.forEach((warning) => {
2188
+ console.error(`${YELLOW}Warning:${RESET} ${warning}`);
2189
+ });
2190
+ Object.entries(boot.envDefaults).forEach(([key, value]) => {
2191
+ console.log(`export ${key}='${shellEscape(value)}'`);
2192
+ });
2178
2193
  if (boot.overridden.length > 0) loadConfig(boot.configPath).fold(() => {}, (config) => {
2179
2194
  const envEntries = config.env ?? {};
2180
- for (const key of boot.overridden) if (key in envEntries) console.log(`export ${key}='${shellEscape(envEntries[key].value)}'`);
2195
+ boot.overridden.forEach((key) => {
2196
+ if (key in envEntries) console.log(`export ${key}='${shellEscape(envEntries[key].value)}'`);
2197
+ });
2198
+ });
2199
+ Object.entries(boot.secrets).forEach(([key, value]) => {
2200
+ console.log(`export ${key}='${shellEscape(value)}'`);
2181
2201
  });
2182
- for (const [key, value] of Object.entries(boot.secrets)) console.log(`export ${key}='${shellEscape(value)}'`);
2183
2202
  });
2184
2203
  };
2185
2204
  const buildEnvBlock = (name, value, options) => {
@@ -2196,7 +2215,7 @@ const buildEnvBlock = (name, value, options) => {
2196
2215
  return `${lines.join("\n")}\n`;
2197
2216
  };
2198
2217
  const withConfig$1 = (configFlag, fn) => {
2199
- resolveConfigPath(configFlag).fold((err) => {
2218
+ resolveConfigPath(configFlag.orUndefined()).fold((err) => {
2200
2219
  console.error(formatError(err));
2201
2220
  process.exit(2);
2202
2221
  }, ({ path: configPath, source }) => {
@@ -2232,7 +2251,7 @@ const runEnvAdd = (name, value, options) => {
2232
2251
  });
2233
2252
  };
2234
2253
  const runEnvEdit = (name, options) => {
2235
- withConfig$1(options.config, (configPath, raw) => {
2254
+ withConfig$1(Option(options.config), (configPath, raw) => {
2236
2255
  loadConfig(configPath).fold((err) => {
2237
2256
  console.error(formatError(err));
2238
2257
  process.exit(2);
@@ -2269,7 +2288,7 @@ const runEnvEdit = (name, options) => {
2269
2288
  });
2270
2289
  };
2271
2290
  const runEnvRm = (name, options) => {
2272
- withConfig$1(options.config, (configPath, raw) => {
2291
+ withConfig$1(Option(options.config), (configPath, raw) => {
2273
2292
  removeSection(raw, `[env.${name}]`).fold((err) => {
2274
2293
  console.error(`${RED}Error:${RESET} ${err._tag}: ${err.section}`);
2275
2294
  process.exit(1);
@@ -2285,7 +2304,7 @@ const runEnvRm = (name, options) => {
2285
2304
  });
2286
2305
  };
2287
2306
  const runEnvRename = (oldName, newName, options) => {
2288
- withConfig$1(options.config, (configPath, raw) => {
2307
+ withConfig$1(Option(options.config), (configPath, raw) => {
2289
2308
  renameSection(raw, `[env.${oldName}]`, `[env.${newName}]`).fold((err) => {
2290
2309
  console.error(`${RED}Error:${RESET} ${err._tag}: ${err.section}`);
2291
2310
  process.exit(1);
@@ -2324,7 +2343,6 @@ const registerEnvCommands = (program) => {
2324
2343
  runEnvRename(oldName, newName, options);
2325
2344
  });
2326
2345
  };
2327
-
2328
2346
  //#endregion
2329
2347
  //#region src/cli/commands/exec.ts
2330
2348
  const runExec = (args, options) => {
@@ -2363,22 +2381,25 @@ const runExec = (args, options) => {
2363
2381
  }
2364
2382
  if (boot.audit.status === "critical" && options.warnOnly) console.error(`${YELLOW}Warning:${RESET} Proceeding despite critical audit status (--warn-only)`);
2365
2383
  }
2366
- for (const warning of boot.warnings) console.error(`${YELLOW}Warning:${RESET} ${warning}`);
2384
+ boot.warnings.forEach((warning) => {
2385
+ console.error(`${YELLOW}Warning:${RESET} ${warning}`);
2386
+ });
2367
2387
  const env = { ...process.env };
2368
- for (const [key, value] of Object.entries(boot.envDefaults)) if (!(key in env)) env[key] = value;
2369
- for (const [key, value] of Object.entries(boot.secrets)) env[key] = value;
2388
+ Object.entries(boot.envDefaults).forEach(([key, value]) => {
2389
+ if (!(key in env)) env[key] = value;
2390
+ });
2391
+ Object.entries(boot.secrets).forEach(([key, value]) => {
2392
+ env[key] = value;
2393
+ });
2370
2394
  const [cmd, ...cmdArgs] = args;
2371
- try {
2372
- execFileSync(cmd, cmdArgs, {
2373
- env,
2374
- stdio: "inherit"
2375
- });
2376
- } catch (err) {
2395
+ Try(() => execFileSync(cmd, cmdArgs, {
2396
+ env,
2397
+ stdio: "inherit"
2398
+ })).fold((err) => {
2377
2399
  const exitCode = err.status ?? 1;
2378
2400
  process.exit(exitCode);
2379
- }
2401
+ }, () => {});
2380
2402
  };
2381
-
2382
2403
  //#endregion
2383
2404
  //#region src/core/fleet.ts
2384
2405
  const CONFIG_FILENAME$1 = "envpkt.toml";
@@ -2417,17 +2438,15 @@ function* findEnvpktFiles(dir, maxDepth, currentDepth = 0) {
2417
2438
  }
2418
2439
  const scanFleet = (rootDir, options) => {
2419
2440
  const maxDepth = options?.maxDepth ?? 3;
2420
- const agents = [];
2421
- for (const configPath of findEnvpktFiles(rootDir, maxDepth)) loadConfig(configPath).fold(() => {}, (config) => {
2441
+ const agentList = List(Array.from(findEnvpktFiles(rootDir, maxDepth)).flatMap((configPath) => loadConfig(configPath).fold(() => [], (config) => {
2422
2442
  const audit = computeAudit(config);
2423
- agents.push({
2443
+ return [{
2424
2444
  path: configPath,
2425
2445
  identity: config.identity,
2426
- 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),
2446
+ min_expiry_days: audit.secrets.toArray().reduce((min, s) => s.days_remaining.fold(() => min, (d) => min.fold(() => Option(d), (m) => Option(Math.min(m, d)))), Option(void 0)).orUndefined(),
2427
2447
  audit
2428
- });
2429
- });
2430
- const agentList = List(agents);
2448
+ }];
2449
+ })));
2431
2450
  const total_agents = agentList.size;
2432
2451
  const total_secrets = agentList.toArray().reduce((acc, a) => acc + a.audit.total, 0);
2433
2452
  const expired = agentList.toArray().reduce((acc, a) => acc + a.audit.expired, 0);
@@ -2443,7 +2462,6 @@ const scanFleet = (rootDir, options) => {
2443
2462
  expiring_soon
2444
2463
  };
2445
2464
  };
2446
-
2447
2465
  //#endregion
2448
2466
  //#region src/cli/commands/fleet.ts
2449
2467
  const statusIcon = (status) => {
@@ -2460,20 +2478,18 @@ const runFleet = (options) => {
2460
2478
  process.exit(fleet.status === "critical" ? 2 : 0);
2461
2479
  return;
2462
2480
  }
2463
- const statusFilter = options.status;
2464
- const agents = statusFilter ? fleet.agents.filter((a) => a.audit.status === statusFilter) : fleet.agents;
2481
+ const agents = Option(options.status).fold(() => fleet.agents, (s) => fleet.agents.filter((a) => a.audit.status === s));
2465
2482
  console.log(`${statusIcon(fleet.status)} ${BOLD}Fleet: ${fleet.status.toUpperCase()}${RESET} — ${fleet.total_agents} agents, ${fleet.total_secrets} secrets`);
2466
2483
  if (fleet.expired > 0) console.log(` ${RED}${fleet.expired}${RESET} expired`);
2467
2484
  if (fleet.expiring_soon > 0) console.log(` ${YELLOW}${fleet.expiring_soon}${RESET} expiring soon`);
2468
2485
  console.log("");
2469
- for (const agent of agents) {
2486
+ agents.forEach((agent) => {
2470
2487
  const name = agent.identity?.name ? BOLD + agent.identity.name + RESET : DIM + agent.path + RESET;
2471
2488
  const icon = statusIcon(agent.audit.status);
2472
2489
  console.log(` ${icon} ${name} ${DIM}(${agent.audit.total} secrets)${RESET}`);
2473
- }
2490
+ });
2474
2491
  process.exit(fleet.status === "critical" ? 2 : 0);
2475
2492
  };
2476
-
2477
2493
  //#endregion
2478
2494
  //#region src/cli/commands/init.ts
2479
2495
  const CONFIG_FILENAME = "envpkt.toml";
@@ -2528,7 +2544,7 @@ const generateTemplate = (options, fnoxKeys) => {
2528
2544
  lines.push(``);
2529
2545
  if (fnoxKeys && fnoxKeys.length > 0) {
2530
2546
  lines.push(`# Secrets detected from fnox.toml`);
2531
- for (const key of fnoxKeys) lines.push(generateSecretBlock(key));
2547
+ lines.push(...fnoxKeys.map((key) => generateSecretBlock(key)));
2532
2548
  } else {
2533
2549
  lines.push(`# Add your secret metadata below.`);
2534
2550
  lines.push(`# Each [secret.<key>] describes a secret your agent needs.`);
@@ -2563,8 +2579,8 @@ const runInit = (dir, options) => {
2563
2579
  console.error(`${RED}Error:${RESET} ${CONFIG_FILENAME} already exists. Use --force to overwrite.`);
2564
2580
  process.exit(1);
2565
2581
  }
2566
- const fnoxKeys = options.fromFnox ? (() => {
2567
- const fnoxPath = options.fromFnox === "true" || options.fromFnox === "" ? join(dir, "fnox.toml") : options.fromFnox;
2582
+ const fnoxKeys = Option(options.fromFnox).map((fromFnox) => {
2583
+ const fnoxPath = fromFnox === "true" || fromFnox === "" ? join(dir, "fnox.toml") : fromFnox;
2568
2584
  if (!existsSync(fnoxPath)) {
2569
2585
  console.error(`${RED}Error:${RESET} fnox.toml not found at ${fnoxPath}`);
2570
2586
  process.exit(1);
@@ -2572,98 +2588,100 @@ const runInit = (dir, options) => {
2572
2588
  return readFnoxKeys(fnoxPath).fold((err) => {
2573
2589
  console.error(`${RED}Error:${RESET} Failed to read fnox.toml: ${formatConfigError(err)}`);
2574
2590
  process.exit(1);
2591
+ return [];
2575
2592
  }, (keys) => keys);
2576
- })() : void 0;
2577
- const content = generateTemplate(options, fnoxKeys);
2593
+ });
2594
+ const content = generateTemplate(options, fnoxKeys.orUndefined());
2578
2595
  Try(() => writeFileSync(outPath, content, "utf-8")).fold((err) => {
2579
2596
  console.error(`${RED}Error:${RESET} Failed to write ${CONFIG_FILENAME}: ${err}`);
2580
2597
  process.exit(1);
2581
2598
  }, () => {
2582
2599
  console.log(`${GREEN}✓${RESET} Created ${BOLD}${CONFIG_FILENAME}${RESET} in ${CYAN}${dir}${RESET}`);
2583
- if (fnoxKeys) console.log(` Scaffolded ${fnoxKeys.length} secret(s) from fnox.toml`);
2600
+ fnoxKeys.forEach((keys) => {
2601
+ console.log(` Scaffolded ${keys.length} secret(s) from fnox.toml`);
2602
+ });
2584
2603
  console.log(` ${BOLD}Next:${RESET} Fill in metadata for each secret`);
2585
2604
  });
2586
2605
  };
2587
-
2588
2606
  //#endregion
2589
2607
  //#region src/core/format.ts
2590
2608
  const maskValue = (value) => {
2591
2609
  if (value.length > 8) return `${value.slice(0, 3)}${"•".repeat(5)}${value.slice(-4)}`;
2592
2610
  return "•".repeat(5);
2593
2611
  };
2594
-
2595
2612
  //#endregion
2596
2613
  //#region src/cli/commands/inspect.ts
2597
2614
  const printSecretMeta = (meta, indent) => {
2598
- if (meta.purpose) console.log(`${indent}purpose: ${meta.purpose}`);
2599
- if (meta.comment) console.log(`${indent}comment: ${DIM}${meta.comment}${RESET}`);
2600
- if (meta.capabilities) console.log(`${indent}capabilities: ${DIM}${meta.capabilities.join(", ")}${RESET}`);
2615
+ if (meta.purpose) console.log(`${indent}${DIM}purpose:${RESET} ${meta.purpose}`);
2616
+ if (meta.comment) console.log(`${indent}${DIM}comment:${RESET} ${DIM}${meta.comment}${RESET}`);
2617
+ if (meta.capabilities) console.log(`${indent}${DIM}capabilities:${RESET} ${meta.capabilities.map((c) => `${MAGENTA}${c}${RESET}`).join(", ")}`);
2601
2618
  const dateParts = [];
2602
- if (meta.created) dateParts.push(`created: ${meta.created}`);
2603
- if (meta.expires) dateParts.push(`expires: ${meta.expires}`);
2619
+ if (meta.created) dateParts.push(`${DIM}created:${RESET} ${BLUE}${meta.created}${RESET}`);
2620
+ if (meta.expires) dateParts.push(`${DIM}expires:${RESET} ${YELLOW}${meta.expires}${RESET}`);
2604
2621
  if (dateParts.length > 0) console.log(`${indent}${dateParts.join(" ")}`);
2605
2622
  const opsParts = [];
2606
- if (meta.rotates) opsParts.push(`rotates: ${meta.rotates}`);
2607
- if (meta.rate_limit) opsParts.push(`rate_limit: ${meta.rate_limit}`);
2623
+ if (meta.rotates) opsParts.push(`${DIM}rotates:${RESET} ${CYAN}${meta.rotates}${RESET}`);
2624
+ if (meta.rate_limit) opsParts.push(`${DIM}rate_limit:${RESET} ${CYAN}${meta.rate_limit}${RESET}`);
2608
2625
  if (opsParts.length > 0) console.log(`${indent}${opsParts.join(" ")}`);
2609
- if (meta.source) console.log(`${indent}source: ${meta.source}`);
2610
- if (meta.model_hint) console.log(`${indent}model_hint: ${meta.model_hint}`);
2611
- if (meta.rotation_url) console.log(`${indent}rotation_url: ${DIM}${meta.rotation_url}${RESET}`);
2612
- if (meta.required !== void 0) console.log(`${indent}required: ${meta.required}`);
2626
+ if (meta.source) console.log(`${indent}${DIM}source:${RESET} ${BLUE}${meta.source}${RESET}`);
2627
+ if (meta.model_hint) console.log(`${indent}${DIM}model_hint:${RESET} ${MAGENTA}${meta.model_hint}${RESET}`);
2628
+ if (meta.rotation_url) console.log(`${indent}${DIM}rotation_url:${RESET} ${DIM}${meta.rotation_url}${RESET}`);
2629
+ if (meta.required !== void 0) console.log(`${indent}${DIM}required:${RESET} ${meta.required ? `${GREEN}true${RESET}` : `${DIM}false${RESET}`}`);
2613
2630
  if (meta.tags) {
2614
- const tagStr = Object.entries(meta.tags).map(([k, v]) => `${k}=${v}`).join(", ");
2615
- console.log(`${indent}tags: ${tagStr}`);
2631
+ const tagStr = Object.entries(meta.tags).map(([k, v]) => `${CYAN}${k}${RESET}=${DIM}${v}${RESET}`).join(", ");
2632
+ console.log(`${indent}${DIM}tags:${RESET} ${tagStr}`);
2616
2633
  }
2617
2634
  };
2618
2635
  const printConfig = (config, path, resolveResult, opts) => {
2619
2636
  console.log(`${BOLD}envpkt.toml${RESET} ${DIM}(${path})${RESET}`);
2620
2637
  if (resolveResult?.catalogPath) console.log(`${DIM}Catalog: ${CYAN}${resolveResult.catalogPath}${RESET}`);
2621
- console.log(`version: ${config.version}`);
2638
+ console.log(`${DIM}version:${RESET} ${config.version}`);
2622
2639
  console.log("");
2623
2640
  if (config.identity) {
2624
- console.log(`${BOLD}Identity:${RESET} ${config.identity.name}`);
2625
- if (config.identity.consumer) console.log(` consumer: ${config.identity.consumer}`);
2626
- if (config.identity.description) console.log(` description: ${config.identity.description}`);
2627
- if (config.identity.capabilities) console.log(` capabilities: ${config.identity.capabilities.join(", ")}`);
2628
- if (config.identity.expires) console.log(` expires: ${config.identity.expires}`);
2629
- if (config.identity.services) console.log(` services: ${config.identity.services.join(", ")}`);
2630
- if (config.identity.secrets) console.log(` secrets: ${config.identity.secrets.join(", ")}`);
2641
+ console.log(`${BOLD}Identity:${RESET} ${GREEN}${config.identity.name}${RESET}`);
2642
+ if (config.identity.consumer) console.log(` ${DIM}consumer:${RESET} ${MAGENTA}${config.identity.consumer}${RESET}`);
2643
+ if (config.identity.description) console.log(` ${DIM}description:${RESET} ${config.identity.description}`);
2644
+ if (config.identity.capabilities) console.log(` ${DIM}capabilities:${RESET} ${config.identity.capabilities.map((c) => `${MAGENTA}${c}${RESET}`).join(", ")}`);
2645
+ if (config.identity.expires) console.log(` ${DIM}expires:${RESET} ${YELLOW}${config.identity.expires}${RESET}`);
2646
+ if (config.identity.services) console.log(` ${DIM}services:${RESET} ${config.identity.services.map((s) => `${CYAN}${s}${RESET}`).join(", ")}`);
2647
+ if (config.identity.secrets) console.log(` ${DIM}secrets:${RESET} ${config.identity.secrets.map((s) => `${BOLD}${s}${RESET}`).join(", ")}`);
2631
2648
  console.log("");
2632
2649
  }
2633
2650
  const secretEntries = config.secret ?? {};
2634
2651
  console.log(`${BOLD}Secrets:${RESET} ${Object.keys(secretEntries).length}`);
2635
- for (const [key, meta] of Object.entries(secretEntries)) {
2636
- const secretValue = opts?.secrets?.[key];
2637
- const valueSuffix = secretValue !== void 0 ? ` = ${YELLOW}${(opts?.secretDisplay ?? "encrypted") === "plaintext" ? secretValue : maskValue(secretValue)}${RESET}` : "";
2652
+ Object.entries(secretEntries).forEach(([key, meta]) => {
2653
+ const valueSuffix = Option(opts?.secrets?.[key]).fold(() => "", (secretValue) => ` = ${YELLOW}${(opts?.secretDisplay ?? "encrypted") === "plaintext" ? secretValue : maskValue(secretValue)}${RESET}`);
2638
2654
  const sealedTag = meta.encrypted_value ? ` ${CYAN}[sealed]${RESET}` : "";
2639
2655
  console.log(` ${BOLD}${key}${RESET} → ${meta.service ?? key}${sealedTag}${valueSuffix}`);
2640
2656
  printSecretMeta(meta, " ");
2641
- }
2657
+ });
2642
2658
  const envEntries = config.env ?? {};
2643
2659
  const envKeys = Object.keys(envEntries);
2644
2660
  if (envKeys.length > 0) {
2645
2661
  console.log("");
2646
2662
  console.log(`${BOLD}Environment Defaults:${RESET} ${envKeys.length}`);
2647
- for (const [key, entry] of Object.entries(envEntries)) {
2648
- console.log(` ${BOLD}${key}${RESET} = "${entry.value}"`);
2649
- if (entry.purpose) console.log(` purpose: ${entry.purpose}`);
2650
- if (entry.comment) console.log(` comment: ${DIM}${entry.comment}${RESET}`);
2651
- }
2663
+ Object.entries(envEntries).forEach(([key, entry]) => {
2664
+ console.log(` ${BOLD}${key}${RESET} = ${GREEN}"${entry.value}"${RESET}`);
2665
+ if (entry.purpose) console.log(` ${DIM}purpose:${RESET} ${entry.purpose}`);
2666
+ if (entry.comment) console.log(` ${DIM}comment:${RESET} ${DIM}${entry.comment}${RESET}`);
2667
+ });
2652
2668
  }
2653
2669
  if (config.lifecycle) {
2654
2670
  console.log("");
2655
2671
  console.log(`${BOLD}Lifecycle:${RESET}`);
2656
- if (config.lifecycle.stale_warning_days !== void 0) console.log(` stale_warning_days: ${config.lifecycle.stale_warning_days}`);
2657
- if (config.lifecycle.require_expiration !== void 0) console.log(` require_expiration: ${config.lifecycle.require_expiration}`);
2658
- if (config.lifecycle.require_service !== void 0) console.log(` require_service: ${config.lifecycle.require_service}`);
2672
+ if (config.lifecycle.stale_warning_days !== void 0) console.log(` ${DIM}stale_warning_days:${RESET} ${YELLOW}${config.lifecycle.stale_warning_days}${RESET}`);
2673
+ if (config.lifecycle.require_expiration !== void 0) console.log(` ${DIM}require_expiration:${RESET} ${config.lifecycle.require_expiration ? `${GREEN}true${RESET}` : `${DIM}false${RESET}`}`);
2674
+ if (config.lifecycle.require_service !== void 0) console.log(` ${DIM}require_service:${RESET} ${config.lifecycle.require_service ? `${GREEN}true${RESET}` : `${DIM}false${RESET}`}`);
2659
2675
  }
2660
2676
  if (resolveResult?.catalogPath) {
2661
2677
  console.log("");
2662
2678
  console.log(`${BOLD}Catalog Resolution:${RESET}`);
2663
- console.log(` merged: ${resolveResult.merged.length} keys`);
2664
- if (resolveResult.overridden.length > 0) console.log(` overridden: ${resolveResult.overridden.join(", ")}`);
2665
- else console.log(` overridden: ${DIM}(none)${RESET}`);
2666
- for (const w of resolveResult.warnings) console.log(` ${YELLOW}warning:${RESET} ${w}`);
2679
+ console.log(` ${DIM}merged:${RESET} ${GREEN}${resolveResult.merged.length}${RESET} keys`);
2680
+ if (resolveResult.overridden.length > 0) console.log(` ${DIM}overridden:${RESET} ${resolveResult.overridden.map((k) => `${YELLOW}${k}${RESET}`).join(", ")}`);
2681
+ else console.log(` ${DIM}overridden: (none)${RESET}`);
2682
+ resolveResult.warnings.forEach((w) => {
2683
+ console.log(` ${YELLOW}warning:${RESET} ${w}`);
2684
+ });
2667
2685
  }
2668
2686
  };
2669
2687
  const runInspect = (options) => {
@@ -2711,9 +2729,15 @@ const runInspect = (options) => {
2711
2729
  });
2712
2730
  });
2713
2731
  };
2714
-
2715
2732
  //#endregion
2716
2733
  //#region src/cli/commands/keygen.ts
2734
+ /** Shorten a path under $HOME to use ~ prefix */
2735
+ const tildeShorten = (p) => {
2736
+ const home = homedir();
2737
+ return p.startsWith(home) ? `~${p.slice(home.length)}` : p;
2738
+ };
2739
+ /** Derive a default identity name from the config path's parent directory */
2740
+ const deriveIdentityName = (configPath) => basename(dirname(resolve(configPath)));
2717
2741
  const runKeygen = (options) => {
2718
2742
  const outputPath = options.output ?? resolveKeyPath();
2719
2743
  generateKeypair({
@@ -2732,15 +2756,24 @@ const runKeygen = (options) => {
2732
2756
  console.log(`${BOLD}Recipient:${RESET} ${recipient}`);
2733
2757
  console.log("");
2734
2758
  const configPath = resolve(options.config ?? join(process.cwd(), "envpkt.toml"));
2735
- if (existsSync(configPath)) updateConfigRecipient(configPath, recipient).fold((err) => {
2736
- console.error(`${YELLOW}Warning:${RESET} Could not update config: ${"message" in err ? err.message : err._tag}`);
2737
- console.log(`${DIM}Manually add to your envpkt.toml:${RESET}`);
2738
- console.log(` [identity]`);
2739
- console.log(` recipient = "${recipient}"`);
2740
- }, () => {
2741
- console.log(`${GREEN}Updated${RESET} ${CYAN}${configPath}${RESET} with identity.recipient`);
2742
- });
2743
- else {
2759
+ if (existsSync(configPath)) {
2760
+ const name = deriveIdentityName(configPath);
2761
+ const keyFile = tildeShorten(identityPath);
2762
+ updateConfigIdentity(configPath, {
2763
+ recipient,
2764
+ name,
2765
+ keyFile
2766
+ }).fold((err) => {
2767
+ console.error(`${YELLOW}Warning:${RESET} Could not update config: ${"message" in err ? err.message : err._tag}`);
2768
+ console.log(`${DIM}Manually add to your envpkt.toml:${RESET}`);
2769
+ console.log(` [identity]`);
2770
+ console.log(` name = "${name}"`);
2771
+ console.log(` recipient = "${recipient}"`);
2772
+ console.log(` key_file = "${keyFile}"`);
2773
+ }, () => {
2774
+ console.log(`${GREEN}Updated${RESET} ${CYAN}${configPath}${RESET} with identity (name, recipient, key_file)`);
2775
+ });
2776
+ } else {
2744
2777
  console.log(`${BOLD}Next steps:${RESET}`);
2745
2778
  console.log(` ${DIM}1.${RESET} envpkt init ${DIM}# create envpkt.toml${RESET}`);
2746
2779
  console.log(` ${DIM}2.${RESET} envpkt env scan --write ${DIM}# discover credentials${RESET}`);
@@ -2748,11 +2781,10 @@ const runKeygen = (options) => {
2748
2781
  }
2749
2782
  });
2750
2783
  };
2751
-
2752
2784
  //#endregion
2753
2785
  //#region src/mcp/resources.ts
2754
2786
  const loadConfigSafe = () => {
2755
- return resolveConfigPath().fold(() => void 0, ({ path }) => loadConfig(path).fold(() => void 0, (config) => ({
2787
+ return resolveConfigPath().fold(() => Option.none(), ({ path }) => loadConfig(path).fold(() => Option.none(), (config) => Option({
2756
2788
  config,
2757
2789
  path
2758
2790
  })));
@@ -2768,14 +2800,11 @@ const resourceDefinitions = [{
2768
2800
  description: "Capabilities declared by the agent and per-secret capability grants",
2769
2801
  mimeType: "application/json"
2770
2802
  }];
2771
- const readHealth = () => {
2772
- const loaded = loadConfigSafe();
2773
- if (!loaded) return { contents: [{
2774
- uri: "envpkt://health",
2775
- mimeType: "application/json",
2776
- text: JSON.stringify({ error: "No envpkt.toml found" })
2777
- }] };
2778
- const { config, path } = loaded;
2803
+ const readHealth = () => loadConfigSafe().fold(() => ({ contents: [{
2804
+ uri: "envpkt://health",
2805
+ mimeType: "application/json",
2806
+ text: JSON.stringify({ error: "No envpkt.toml found" })
2807
+ }] }), ({ config, path }) => {
2779
2808
  const audit = computeAudit(config);
2780
2809
  return { contents: [{
2781
2810
  uri: "envpkt://health",
@@ -2791,19 +2820,17 @@ const readHealth = () => {
2791
2820
  missing: audit.missing
2792
2821
  }, null, 2)
2793
2822
  }] };
2794
- };
2795
- const readCapabilities = () => {
2796
- const loaded = loadConfigSafe();
2797
- if (!loaded) return { contents: [{
2798
- uri: "envpkt://capabilities",
2799
- mimeType: "application/json",
2800
- text: JSON.stringify({ error: "No envpkt.toml found" })
2801
- }] };
2802
- const { config } = loaded;
2823
+ });
2824
+ const readCapabilities = () => loadConfigSafe().fold(() => ({ contents: [{
2825
+ uri: "envpkt://capabilities",
2826
+ mimeType: "application/json",
2827
+ text: JSON.stringify({ error: "No envpkt.toml found" })
2828
+ }] }), ({ config }) => {
2803
2829
  const agentCapabilities = config.identity?.capabilities ?? [];
2804
2830
  const secretCapabilities = {};
2805
- const secretEntries = config.secret ?? {};
2806
- for (const [key, meta] of Object.entries(secretEntries)) if (meta.capabilities && meta.capabilities.length > 0) secretCapabilities[key] = meta.capabilities;
2831
+ Object.entries(config.secret ?? {}).forEach(([key, meta]) => {
2832
+ if (meta.capabilities && meta.capabilities.length > 0) secretCapabilities[key] = meta.capabilities;
2833
+ });
2807
2834
  return { contents: [{
2808
2835
  uri: "envpkt://capabilities",
2809
2836
  mimeType: "application/json",
@@ -2817,16 +2844,12 @@ const readCapabilities = () => {
2817
2844
  secrets: secretCapabilities
2818
2845
  }, null, 2)
2819
2846
  }] };
2820
- };
2847
+ });
2821
2848
  const resourceHandlers = {
2822
2849
  "envpkt://health": readHealth,
2823
2850
  "envpkt://capabilities": readCapabilities
2824
2851
  };
2825
- const readResource = (uri) => {
2826
- const handler = resourceHandlers[uri];
2827
- return handler?.();
2828
- };
2829
-
2852
+ const readResource = (uri) => Option(resourceHandlers[uri]).map((handler) => handler());
2830
2853
  //#endregion
2831
2854
  //#region src/mcp/tools.ts
2832
2855
  const textResult = (text) => ({ content: [{
@@ -2841,7 +2864,7 @@ const errorResult = (message) => ({
2841
2864
  isError: true
2842
2865
  });
2843
2866
  const loadConfigForTool = (configPath) => {
2844
- return resolveConfigPath(configPath).fold((err) => ({
2867
+ return resolveConfigPath(configPath.fold(() => void 0, (v) => v)).fold((err) => ({
2845
2868
  ok: false,
2846
2869
  result: errorResult(`Config error: ${err._tag} — ${err._tag === "FileNotFound" ? err.path : ""}`)
2847
2870
  }), ({ path }) => loadConfig(path).fold((err) => ({
@@ -2924,8 +2947,9 @@ const toolDefinitions = [
2924
2947
  }
2925
2948
  }
2926
2949
  ];
2950
+ const configPathArg = (args) => Option(typeof args.configPath === "string" ? args.configPath : null);
2927
2951
  const handleGetPacketHealth = (args) => {
2928
- const loaded = loadConfigForTool(args.configPath);
2952
+ const loaded = loadConfigForTool(configPathArg(args));
2929
2953
  if (!loaded.ok) return loaded.result;
2930
2954
  const { config, path } = loaded;
2931
2955
  const audit = computeAudit(config);
@@ -2950,13 +2974,14 @@ const handleGetPacketHealth = (args) => {
2950
2974
  }, null, 2));
2951
2975
  };
2952
2976
  const handleListCapabilities = (args) => {
2953
- const loaded = loadConfigForTool(args.configPath);
2977
+ const loaded = loadConfigForTool(configPathArg(args));
2954
2978
  if (!loaded.ok) return loaded.result;
2955
2979
  const { config } = loaded;
2956
2980
  const agentCapabilities = config.identity?.capabilities ?? [];
2957
2981
  const secretCapabilities = {};
2958
- const secretEntries = config.secret ?? {};
2959
- for (const [key, meta] of Object.entries(secretEntries)) if (meta.capabilities && meta.capabilities.length > 0) secretCapabilities[key] = meta.capabilities;
2982
+ Object.entries(config.secret ?? {}).forEach(([key, meta]) => {
2983
+ if (meta.capabilities && meta.capabilities.length > 0) secretCapabilities[key] = meta.capabilities;
2984
+ });
2960
2985
  return textResult(JSON.stringify({
2961
2986
  identity: config.identity ? {
2962
2987
  name: config.identity.name,
@@ -2971,21 +2996,21 @@ const handleListCapabilities = (args) => {
2971
2996
  const handleGetSecretMeta = (args) => {
2972
2997
  const key = args.key;
2973
2998
  if (!key) return errorResult("Missing required argument: key");
2974
- const loaded = loadConfigForTool(args.configPath);
2999
+ const loaded = loadConfigForTool(configPathArg(args));
2975
3000
  if (!loaded.ok) return loaded.result;
2976
3001
  const { config } = loaded;
2977
- const meta = (config.secret ?? {})[key];
2978
- if (!meta) return errorResult(`Secret not found: ${key}`);
2979
- const { encrypted_value: _, ...safeMeta } = meta;
2980
- return textResult(JSON.stringify({
2981
- key,
2982
- ...safeMeta
2983
- }, null, 2));
3002
+ return Option((config.secret ?? {})[key]).fold(() => errorResult(`Secret not found: ${key}`), (meta) => {
3003
+ const { encrypted_value: _, ...safeMeta } = meta;
3004
+ return textResult(JSON.stringify({
3005
+ key,
3006
+ ...safeMeta
3007
+ }, null, 2));
3008
+ });
2984
3009
  };
2985
3010
  const handleCheckExpiration = (args) => {
2986
3011
  const key = args.key;
2987
3012
  if (!key) return errorResult("Missing required argument: key");
2988
- const loaded = loadConfigForTool(args.configPath);
3013
+ const loaded = loadConfigForTool(configPathArg(args));
2989
3014
  if (!loaded.ok) return loaded.result;
2990
3015
  const { config } = loaded;
2991
3016
  return computeAudit(config).secrets.find((s) => s.key === key).fold(() => errorResult(`Secret not found: ${key}`), (s) => textResult(JSON.stringify({
@@ -2999,7 +3024,7 @@ const handleCheckExpiration = (args) => {
2999
3024
  }, null, 2)));
3000
3025
  };
3001
3026
  const handleGetEnvMeta = (args) => {
3002
- const loaded = loadConfigForTool(args.configPath);
3027
+ const loaded = loadConfigForTool(configPathArg(args));
3003
3028
  if (!loaded.ok) return loaded.result;
3004
3029
  const { config } = loaded;
3005
3030
  const envAudit = computeEnvAudit(config);
@@ -3012,12 +3037,7 @@ const handlers = {
3012
3037
  checkExpiration: handleCheckExpiration,
3013
3038
  getEnvMeta: handleGetEnvMeta
3014
3039
  };
3015
- const callTool = (name, args) => {
3016
- const handler = handlers[name];
3017
- if (!handler) return errorResult(`Unknown tool: ${name}`);
3018
- return handler(args);
3019
- };
3020
-
3040
+ const callTool = (name, args) => Option(handlers[name]).fold(() => errorResult(`Unknown tool: ${name}`), (handler) => handler(args));
3021
3041
  //#endregion
3022
3042
  //#region src/mcp/server.ts
3023
3043
  const createServer = () => {
@@ -3043,13 +3063,11 @@ const createServer = () => {
3043
3063
  server.setRequestHandler(ListResourcesRequestSchema, () => ({ resources: [...resourceDefinitions] }));
3044
3064
  server.setRequestHandler(ReadResourceRequestSchema, (request) => {
3045
3065
  const { uri } = request.params;
3046
- const result = readResource(uri);
3047
- if (!result) return { contents: [{
3066
+ return readResource(uri).fold(() => ({ contents: [{
3048
3067
  uri,
3049
3068
  mimeType: "text/plain",
3050
3069
  text: `Resource not found: ${uri}`
3051
- }] };
3052
- return result;
3070
+ }] }), (result) => result);
3053
3071
  });
3054
3072
  return server;
3055
3073
  };
@@ -3058,7 +3076,6 @@ const startServer = async () => {
3058
3076
  const transport = new StdioServerTransport();
3059
3077
  await server.connect(transport);
3060
3078
  };
3061
-
3062
3079
  //#endregion
3063
3080
  //#region src/cli/commands/mcp.ts
3064
3081
  const runMcp = (_options) => {
@@ -3067,7 +3084,6 @@ const runMcp = (_options) => {
3067
3084
  process.exit(1);
3068
3085
  });
3069
3086
  };
3070
-
3071
3087
  //#endregion
3072
3088
  //#region src/cli/commands/resolve.ts
3073
3089
  const runResolve = (options) => {
@@ -3095,14 +3111,15 @@ const runResolve = (options) => {
3095
3111
  } else process.stdout.write(content);
3096
3112
  if (result.catalogPath) {
3097
3113
  const summaryTarget = options.output ? process.stdout : process.stderr;
3098
- summaryTarget.write(`\n${CYAN}Catalog:${RESET} ${result.catalogPath}\n${GREEN}Merged:${RESET} ${result.merged.length} key(s)${result.overridden.length > 0 ? ` ${YELLOW}(${result.overridden.length} overridden: ${result.overridden.join(", ")})${RESET}` : ""}\n`);
3099
- for (const w of result.warnings) summaryTarget.write(`${RED}Warning:${RESET} ${w}\n`);
3114
+ summaryTarget.write(`\n${BOLD}${CYAN}Catalog:${RESET} ${BLUE}${result.catalogPath}${RESET}\n${GREEN}Merged:${RESET} ${BOLD}${result.merged.length}${RESET} key(s)${result.overridden.length > 0 ? ` ${YELLOW}(${result.overridden.length} overridden: ${BOLD}${result.overridden.join(`${RESET}${YELLOW}, ${BOLD}`)}${RESET}${YELLOW})${RESET}` : ""}\n`);
3115
+ result.warnings.forEach((w) => {
3116
+ summaryTarget.write(`${RED}Warning:${RESET} ${w}\n`);
3117
+ });
3100
3118
  }
3101
3119
  });
3102
3120
  });
3103
3121
  });
3104
3122
  };
3105
-
3106
3123
  //#endregion
3107
3124
  //#region src/core/resolve-values.ts
3108
3125
  /** Resolve plaintext values for the given keys via cascade: fnox → env → interactive prompt */
@@ -3110,18 +3127,20 @@ const resolveValues = async (keys, profile, agentKey) => {
3110
3127
  const result = {};
3111
3128
  const remaining = new Set(keys);
3112
3129
  if (fnoxAvailable()) fnoxExport(profile, agentKey).fold(() => {}, (exported) => {
3113
- for (const key of [...remaining]) if (key in exported) {
3114
- result[key] = exported[key];
3115
- remaining.delete(key);
3116
- }
3130
+ [...remaining].forEach((key) => {
3131
+ if (key in exported) {
3132
+ result[key] = exported[key];
3133
+ remaining.delete(key);
3134
+ }
3135
+ });
3117
3136
  });
3118
- for (const key of [...remaining]) {
3137
+ [...remaining].forEach((key) => {
3119
3138
  const envValue = process.env[key];
3120
3139
  if (envValue !== void 0 && envValue !== "") {
3121
3140
  result[key] = envValue;
3122
3141
  remaining.delete(key);
3123
3142
  }
3124
- }
3143
+ });
3125
3144
  if (remaining.size > 0 && process.stdin.isTTY) {
3126
3145
  const rl = createInterface({
3127
3146
  input: process.stdin,
@@ -3138,60 +3157,61 @@ const resolveValues = async (keys, profile, agentKey) => {
3138
3157
  }
3139
3158
  return result;
3140
3159
  };
3141
-
3142
3160
  //#endregion
3143
3161
  //#region src/cli/commands/seal.ts
3144
3162
  /** Write sealed values back into the TOML file, preserving structure */
3145
3163
  const writeSealedToml = (configPath, sealedMeta) => {
3146
3164
  const lines = readFileSync(configPath, "utf-8").split("\n");
3147
3165
  const output = [];
3148
- let currentMetaKey;
3166
+ let currentMetaKey = Option.none();
3149
3167
  let insideMetaBlock = false;
3150
3168
  let hasEncryptedValue = false;
3151
3169
  const pendingSeals = /* @__PURE__ */ new Map();
3152
- for (const [key, meta] of Object.entries(sealedMeta)) if (meta.encrypted_value) pendingSeals.set(key, meta.encrypted_value);
3170
+ Object.entries(sealedMeta).forEach(([key, meta]) => {
3171
+ if (meta.encrypted_value) pendingSeals.set(key, meta.encrypted_value);
3172
+ });
3153
3173
  const metaSectionRe = /^\[secret\.(.+)\]\s*$/;
3154
3174
  const encryptedValueRe = /^encrypted_value\s*=/;
3155
3175
  const newSectionRe = /^\[/;
3156
- for (let i = 0; i < lines.length; i++) {
3157
- const line = lines[i];
3158
- const metaMatch = metaSectionRe.exec(line);
3159
- if (metaMatch) {
3160
- if (currentMetaKey && !hasEncryptedValue && pendingSeals.has(currentMetaKey)) {
3176
+ const flushPending = () => {
3177
+ currentMetaKey.forEach((key) => {
3178
+ if (!hasEncryptedValue && pendingSeals.has(key)) {
3161
3179
  output.push(`encrypted_value = """`);
3162
- output.push(pendingSeals.get(currentMetaKey));
3180
+ output.push(pendingSeals.get(key));
3163
3181
  output.push(`"""`);
3164
3182
  output.push("");
3165
- pendingSeals.delete(currentMetaKey);
3183
+ pendingSeals.delete(key);
3166
3184
  }
3167
- currentMetaKey = metaMatch[1];
3185
+ });
3186
+ };
3187
+ for (let i = 0; i < lines.length; i++) {
3188
+ const line = lines[i];
3189
+ const metaMatch = metaSectionRe.exec(line);
3190
+ if (metaMatch) {
3191
+ flushPending();
3192
+ currentMetaKey = Option(metaMatch[1]);
3168
3193
  insideMetaBlock = true;
3169
3194
  hasEncryptedValue = false;
3170
3195
  output.push(line);
3171
3196
  continue;
3172
3197
  }
3173
3198
  if (insideMetaBlock && newSectionRe.test(line) && !metaSectionRe.test(line)) {
3174
- if (currentMetaKey && !hasEncryptedValue && pendingSeals.has(currentMetaKey)) {
3175
- output.push(`encrypted_value = """`);
3176
- output.push(pendingSeals.get(currentMetaKey));
3177
- output.push(`"""`);
3178
- output.push("");
3179
- pendingSeals.delete(currentMetaKey);
3180
- }
3199
+ flushPending();
3181
3200
  insideMetaBlock = false;
3182
- currentMetaKey = void 0;
3201
+ currentMetaKey = Option.none();
3183
3202
  output.push(line);
3184
3203
  continue;
3185
3204
  }
3186
3205
  if (insideMetaBlock && encryptedValueRe.test(line)) {
3187
3206
  hasEncryptedValue = true;
3188
- const replacing = !!(currentMetaKey && pendingSeals.has(currentMetaKey));
3189
- if (replacing) {
3207
+ const replacing = currentMetaKey.fold(() => false, (key) => pendingSeals.has(key));
3208
+ if (replacing) currentMetaKey.forEach((key) => {
3190
3209
  output.push(`encrypted_value = """`);
3191
- output.push(pendingSeals.get(currentMetaKey));
3210
+ output.push(pendingSeals.get(key));
3192
3211
  output.push(`"""`);
3193
- pendingSeals.delete(currentMetaKey);
3194
- } else output.push(line);
3212
+ pendingSeals.delete(key);
3213
+ });
3214
+ else output.push(line);
3195
3215
  if (line.slice(line.indexOf("=") + 1).trim().includes("\"\"\"")) {
3196
3216
  while (i + 1 < lines.length && !lines[i + 1].includes("\"\"\"")) {
3197
3217
  if (!replacing) output.push(lines[i + 1]);
@@ -3206,12 +3226,7 @@ const writeSealedToml = (configPath, sealedMeta) => {
3206
3226
  }
3207
3227
  output.push(line);
3208
3228
  }
3209
- if (currentMetaKey && !hasEncryptedValue && pendingSeals.has(currentMetaKey)) {
3210
- output.push(`encrypted_value = """`);
3211
- output.push(pendingSeals.get(currentMetaKey));
3212
- output.push(`"""`);
3213
- pendingSeals.delete(currentMetaKey);
3214
- }
3229
+ flushPending();
3215
3230
  writeFileSync(configPath, output.join("\n"));
3216
3231
  };
3217
3232
  const runSeal = async (options) => {
@@ -3248,10 +3263,13 @@ const runSeal = async (options) => {
3248
3263
  console.error(`${DIM}Move these to [secret.*] only, or remove from [env.*] before sealing.${RESET}`);
3249
3264
  process.exit(2);
3250
3265
  }
3251
- const identityKey = config.identity.key_file ? unwrapAgentKey(resolve(configDir, expandPath(config.identity.key_file))).fold((err) => {
3252
- const msg = err._tag === "IdentityNotFound" ? `not found: ${err.path}` : err.message;
3253
- console.error(`${YELLOW}Warning:${RESET} Could not unwrap agent key: ${msg}`);
3254
- }, (k) => k) : void 0;
3266
+ const identityKey = Option(config.identity.key_file).flatMap((keyFile) => {
3267
+ return unwrapAgentKey(resolve(configDir, expandPath(keyFile))).fold((err) => {
3268
+ const msg = err._tag === "IdentityNotFound" ? `not found: ${err.path}` : err.message;
3269
+ console.error(`${YELLOW}Warning:${RESET} Could not unwrap agent key: ${msg}`);
3270
+ return Option.none();
3271
+ }, (k) => Option(k));
3272
+ });
3255
3273
  const editKeys = options.edit ? options.edit.split(",").map((k) => k.trim()).filter((k) => k.length > 0) : [];
3256
3274
  if (editKeys.length > 0) {
3257
3275
  const allSecretEntries = config.secret ?? {};
@@ -3300,8 +3318,8 @@ const runSeal = async (options) => {
3300
3318
  }
3301
3319
  const allSecretEntries = config.secret ?? {};
3302
3320
  const allKeys = Object.keys(allSecretEntries);
3303
- const alreadySealed = allKeys.filter((k) => allSecretEntries[k]?.encrypted_value);
3304
- const unsealed = allKeys.filter((k) => !allSecretEntries[k]?.encrypted_value);
3321
+ const alreadySealed = allKeys.filter((k) => allSecretEntries[k].encrypted_value);
3322
+ const unsealed = allKeys.filter((k) => !allSecretEntries[k].encrypted_value);
3305
3323
  if (!options.reseal && alreadySealed.length > 0) {
3306
3324
  if (unsealed.length === 0) {
3307
3325
  console.log(`${GREEN}✓${RESET} All ${BOLD}${alreadySealed.length}${RESET} secret(s) already sealed. Use ${CYAN}--reseal${RESET} to re-encrypt.`);
@@ -3334,13 +3352,13 @@ const runSeal = async (options) => {
3334
3352
  process.exit(2);
3335
3353
  return {};
3336
3354
  }, (d) => d);
3337
- const newValues = unsealed.length > 0 ? await resolveValues(unsealed, options.profile, identityKey) : {};
3355
+ const newValues = unsealed.length > 0 ? await resolveValues(unsealed, options.profile, identityKey.orUndefined()) : {};
3338
3356
  return {
3339
3357
  ...decrypted,
3340
3358
  ...newValues
3341
3359
  };
3342
3360
  }
3343
- return resolveValues(metaKeys, options.profile, identityKey);
3361
+ return resolveValues(metaKeys, options.profile, identityKey.orUndefined());
3344
3362
  })();
3345
3363
  const resolved = Object.keys(values).length;
3346
3364
  const skipped = metaKeys.length - resolved;
@@ -3363,7 +3381,6 @@ const runSeal = async (options) => {
3363
3381
  console.log(`${GREEN}Sealed${RESET} ${sealedCount} secret(s) into ${DIM}${configPath}${RESET}${summary}`);
3364
3382
  });
3365
3383
  };
3366
-
3367
3384
  //#endregion
3368
3385
  //#region src/cli/commands/secret.ts
3369
3386
  const DATE_RE = /^\d{4}-\d{2}-\d{2}$/;
@@ -3414,7 +3431,7 @@ const buildFieldUpdates = (options) => {
3414
3431
  return updates;
3415
3432
  };
3416
3433
  const withConfig = (configFlag, fn) => {
3417
- resolveConfigPath(configFlag).fold((err) => {
3434
+ resolveConfigPath(configFlag.orUndefined()).fold((err) => {
3418
3435
  console.error(formatError(err));
3419
3436
  process.exit(2);
3420
3437
  }, ({ path: configPath, source }) => {
@@ -3458,7 +3475,7 @@ const runSecretEdit = (name, options) => {
3458
3475
  console.error(`${RED}Error:${RESET} Invalid date format for --expires: "${options.expires}" (expected YYYY-MM-DD)`);
3459
3476
  process.exit(1);
3460
3477
  }
3461
- withConfig(options.config, (configPath, raw) => {
3478
+ withConfig(Option(options.config), (configPath, raw) => {
3462
3479
  loadConfig(configPath).fold((err) => {
3463
3480
  console.error(formatError(err));
3464
3481
  process.exit(2);
@@ -3488,7 +3505,7 @@ const runSecretEdit = (name, options) => {
3488
3505
  });
3489
3506
  };
3490
3507
  const runSecretRm = (name, options) => {
3491
- withConfig(options.config, (configPath, raw) => {
3508
+ withConfig(Option(options.config), (configPath, raw) => {
3492
3509
  removeSection(raw, `[secret.${name}]`).fold((err) => {
3493
3510
  console.error(`${RED}Error:${RESET} ${err._tag}: ${err.section}`);
3494
3511
  process.exit(1);
@@ -3504,7 +3521,7 @@ const runSecretRm = (name, options) => {
3504
3521
  });
3505
3522
  };
3506
3523
  const runSecretRename = (oldName, newName, options) => {
3507
- withConfig(options.config, (configPath, raw) => {
3524
+ withConfig(Option(options.config), (configPath, raw) => {
3508
3525
  renameSection(raw, `[secret.${oldName}]`, `[secret.${newName}]`).fold((err) => {
3509
3526
  console.error(`${RED}Error:${RESET} ${err._tag}: ${err.section}`);
3510
3527
  process.exit(1);
@@ -3535,7 +3552,6 @@ const registerSecretCommands = (program) => {
3535
3552
  runSecretRename(oldName, newName, options);
3536
3553
  });
3537
3554
  };
3538
-
3539
3555
  //#endregion
3540
3556
  //#region src/cli/commands/shell-hook.ts
3541
3557
  const ZSH_HOOK = `# envpkt shell hook — add to your .zshrc
@@ -3577,45 +3593,36 @@ const runShellHook = (shell) => {
3577
3593
  process.exit(1);
3578
3594
  }
3579
3595
  };
3580
-
3581
3596
  //#endregion
3582
3597
  //#region src/cli/commands/upgrade.ts
3583
- const getCurrentVersion = () => {
3584
- try {
3585
- return execFileSync("npm", [
3586
- "list",
3587
- "-g",
3588
- "envpkt",
3589
- "--json"
3590
- ], {
3591
- encoding: "utf-8",
3592
- stdio: [
3593
- "pipe",
3594
- "pipe",
3595
- "pipe"
3596
- ]
3597
- }).match(/"envpkt":\s*\{\s*"version":\s*"([^"]+)"/)?.[1] ?? "unknown";
3598
- } catch {
3599
- return "unknown";
3600
- }
3601
- };
3598
+ const getCurrentVersion = () => Try(() => execFileSync("npm", [
3599
+ "list",
3600
+ "-g",
3601
+ "envpkt",
3602
+ "--json"
3603
+ ], {
3604
+ encoding: "utf-8",
3605
+ stdio: [
3606
+ "pipe",
3607
+ "pipe",
3608
+ "pipe"
3609
+ ]
3610
+ })).fold(() => "unknown", (output) => output.match(/"envpkt":\s*\{\s*"version":\s*"([^"]+)"/)?.[1] ?? "unknown");
3602
3611
  const runUpgrade = () => {
3603
3612
  const before = getCurrentVersion();
3604
3613
  console.log(`${DIM}Current version: ${before}${RESET}`);
3605
3614
  console.log(`${CYAN}Upgrading envpkt...${RESET}\n`);
3606
- try {
3607
- execFileSync("npm", [
3608
- "install",
3609
- "-g",
3610
- "envpkt@latest",
3611
- "--prefer-online"
3612
- ], {
3613
- stdio: "inherit",
3614
- encoding: "utf-8"
3615
- });
3616
- } catch {
3615
+ Try(() => execFileSync("npm", [
3616
+ "install",
3617
+ "-g",
3618
+ "envpkt@latest",
3619
+ "--prefer-online"
3620
+ ], {
3621
+ stdio: "inherit",
3622
+ encoding: "utf-8"
3623
+ })).fold(() => {
3617
3624
  console.error(`\n${RED}Error:${RESET} npm install failed. Trying with cache clean...`);
3618
- try {
3625
+ Try(() => {
3619
3626
  execFileSync("npm", [
3620
3627
  "cache",
3621
3628
  "clean",
@@ -3629,17 +3636,16 @@ const runUpgrade = () => {
3629
3636
  stdio: "inherit",
3630
3637
  encoding: "utf-8"
3631
3638
  });
3632
- } catch {
3639
+ }).fold(() => {
3633
3640
  console.error(`${RED}Error:${RESET} Upgrade failed. Try manually:`);
3634
3641
  console.error(` ${BOLD}sudo npm install -g envpkt@latest --prefer-online${RESET}`);
3635
3642
  process.exit(1);
3636
- }
3637
- }
3643
+ }, () => {});
3644
+ }, () => {});
3638
3645
  const after = getCurrentVersion();
3639
3646
  if (before === after && before !== "unknown") console.log(`\n${GREEN}✓${RESET} Already on latest version ${BOLD}${after}${RESET}`);
3640
3647
  else console.log(`\n${GREEN}✓${RESET} Upgraded ${YELLOW}${before}${RESET} → ${BOLD}${after}${RESET}`);
3641
3648
  };
3642
-
3643
3649
  //#endregion
3644
3650
  //#region src/cli/index.ts
3645
3651
  const program = new Command();
@@ -3688,6 +3694,5 @@ program.command("shell-hook").description("Output shell function for ambient cre
3688
3694
  runShellHook(shell);
3689
3695
  });
3690
3696
  program.parse();
3691
-
3692
3697
  //#endregion
3693
- export { };
3698
+ export {};