envpkt 0.11.1 → 0.11.3

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  import { FormatRegistry, Type } from "@sinclair/typebox";
2
2
  import { dirname, join, resolve } from "node:path";
3
3
  import { TypeCompiler } from "@sinclair/typebox/compiler";
4
- import { Cond, Either, Left, List, None, Option, Right, Some, Try } from "functype";
4
+ import { $, Cond, Do, Either, Left, List, Map as Map$1, None, Option, Right, Set as Set$1, Some, Try } from "functype";
5
5
  import { Env, Fs, Path, Platform } from "functype-os";
6
6
  import { TomlDate, parse } from "smol-toml";
7
7
  import { chmodSync, existsSync, mkdirSync, readFileSync, readdirSync, statSync, writeFileSync } from "node:fs";
@@ -56,6 +56,10 @@ const SecretMetaSchema = Type.Object({
56
56
  format: "date",
57
57
  description: "Date the secret was provisioned (YYYY-MM-DD)"
58
58
  })),
59
+ last_rotated_at: Type.Optional(Type.String({
60
+ format: "date",
61
+ description: "Date the secret value was most recently rotated (YYYY-MM-DD). Used by audit for staleness."
62
+ })),
59
63
  rotates: Type.Optional(Type.String({ description: "Rotation schedule (e.g. '90d', 'quarterly')" })),
60
64
  rate_limit: Type.Optional(Type.String({ description: "Rate limit or quota info (e.g. '1000/min')" })),
61
65
  model_hint: Type.Optional(Type.String({ description: "Suggested model or tier for this credential" })),
@@ -294,18 +298,19 @@ const loadCatalog = (catalogPath) => loadConfig(catalogPath).fold((err) => {
294
298
  /** Resolve secrets by merging catalog meta with agent overrides (shallow merge) */
295
299
  const resolveSecrets = (agentMeta, catalogMeta, agentSecrets, catalogPath) => {
296
300
  return agentSecrets.reduce((acc, key) => acc.flatMap((resolved) => {
297
- if (!(key in catalogMeta)) return Left({
301
+ const catalogEntry = catalogMeta[key];
302
+ if (catalogEntry === void 0) return Left({
298
303
  _tag: "SecretNotInCatalog",
299
304
  key,
300
305
  catalogPath
301
306
  });
302
- const catalogEntry = catalogMeta[key];
307
+ const merged = Option(agentMeta[key]).fold(() => catalogEntry, (override) => ({
308
+ ...catalogEntry,
309
+ ...override
310
+ }));
303
311
  return Right({
304
312
  ...resolved,
305
- [key]: key in agentMeta ? {
306
- ...catalogEntry,
307
- ...agentMeta[key]
308
- } : catalogEntry
313
+ [key]: merged
309
314
  });
310
315
  }), Right({}));
311
316
  };
@@ -372,12 +377,12 @@ const validateOneSecret = (key, meta, secretEntries) => {
372
377
  kind: "secret",
373
378
  field: "encrypted_value"
374
379
  });
375
- return parseRef(ref).fold(() => Left({
380
+ return parseRef(ref).toEither({
376
381
  _tag: "AliasInvalidSyntax",
377
382
  key,
378
383
  kind: "secret",
379
384
  value: ref
380
- }), (parsed) => {
385
+ }).flatMap((parsed) => {
381
386
  if (parsed.kind !== "secret") return Left({
382
387
  _tag: "AliasCrossType",
383
388
  key,
@@ -388,23 +393,23 @@ const validateOneSecret = (key, meta, secretEntries) => {
388
393
  _tag: "AliasSelfReference",
389
394
  key: `secret.${key}`
390
395
  });
391
- return Option(secretEntries[parsed.key]).fold(() => Left({
396
+ return Option(secretEntries[parsed.key]).toEither({
392
397
  _tag: "AliasTargetMissing",
393
398
  key: `secret.${key}`,
394
399
  target: ref
395
- }), (target) => {
400
+ }).flatMap((target) => {
396
401
  if (target.from_key !== void 0) return Left({
397
402
  _tag: "AliasChained",
398
403
  key: `secret.${key}`,
399
404
  target: ref
400
405
  });
401
- return Right(Option({
406
+ return Right({
402
407
  kind: "secret",
403
408
  targetKind: "secret",
404
409
  targetKey: parsed.key
405
- }));
410
+ });
406
411
  });
407
- });
412
+ }).map((entry) => Option(entry));
408
413
  };
409
414
  const validateOneEnv = (key, meta, envEntries) => {
410
415
  if (meta.from_key === void 0) return Right(Option(void 0));
@@ -415,12 +420,12 @@ const validateOneEnv = (key, meta, envEntries) => {
415
420
  kind: "env",
416
421
  field: "value"
417
422
  });
418
- return parseRef(ref).fold(() => Left({
423
+ return parseRef(ref).toEither({
419
424
  _tag: "AliasInvalidSyntax",
420
425
  key,
421
426
  kind: "env",
422
427
  value: ref
423
- }), (parsed) => {
428
+ }).flatMap((parsed) => {
424
429
  if (parsed.kind !== "env") return Left({
425
430
  _tag: "AliasCrossType",
426
431
  key,
@@ -431,57 +436,32 @@ const validateOneEnv = (key, meta, envEntries) => {
431
436
  _tag: "AliasSelfReference",
432
437
  key: `env.${key}`
433
438
  });
434
- return Option(envEntries[parsed.key]).fold(() => Left({
439
+ return Option(envEntries[parsed.key]).toEither({
435
440
  _tag: "AliasTargetMissing",
436
441
  key: `env.${key}`,
437
442
  target: ref
438
- }), (target) => {
443
+ }).flatMap((target) => {
439
444
  if (target.from_key !== void 0) return Left({
440
445
  _tag: "AliasChained",
441
446
  key: `env.${key}`,
442
447
  target: ref
443
448
  });
444
- return Right(Option({
449
+ return Right({
445
450
  kind: "env",
446
451
  targetKind: "env",
447
452
  targetKey: parsed.key
448
- }));
453
+ });
449
454
  });
450
- });
455
+ }).map((entry) => Option(entry));
451
456
  };
452
- /**
453
- * Validate all `from_key` references in a resolved config. Produces an
454
- * AliasTable mapping each alias to its target, or an AliasError describing
455
- * the first failure.
456
- *
457
- * Rules:
458
- * - Ref must be "secret.<KEY>" or "env.<KEY>"
459
- * - Target must exist in the same resolved config
460
- * - Target must be the same type (secret→secret, env→env only)
461
- * - Target must not itself be a from_key entry (single hop only)
462
- * - Self-reference is rejected
463
- * - An alias entry cannot also carry a value field (encrypted_value for
464
- * secrets, value for env)
465
- */
457
+ /** Fail-fast reduction: accumulates validated entries, short-circuits on first AliasError */
458
+ const collectValidated = (items, validate, prefix) => items.reduce((acc, [key, meta]) => acc.flatMap((entries) => validate(key, meta).map((opt) => opt.fold(() => entries, (entry) => [...entries, [`${prefix}.${key}`, entry]]))), Right([]));
466
459
  const validateAliases = (config) => {
467
460
  const secretEntries = config.secret ?? {};
468
461
  const envEntries = config.env ?? {};
469
- const entries = /* @__PURE__ */ new Map();
470
- const secretResults = Object.entries(secretEntries).map(([key, meta]) => [key, validateOneSecret(key, meta, secretEntries)]);
471
- for (const [key, result] of secretResults) {
472
- const outcome = result.fold((err) => err, (opt) => opt.orUndefined());
473
- if (outcome === void 0) continue;
474
- if ("_tag" in outcome) return Left(outcome);
475
- entries.set(`secret.${key}`, outcome);
476
- }
477
- const envResults = Object.entries(envEntries).map(([key, meta]) => [key, validateOneEnv(key, meta, envEntries)]);
478
- for (const [key, result] of envResults) {
479
- const outcome = result.fold((err) => err, (opt) => opt.orUndefined());
480
- if (outcome === void 0) continue;
481
- if ("_tag" in outcome) return Left(outcome);
482
- entries.set(`env.${key}`, outcome);
483
- }
484
- return Right({ entries });
462
+ const secretPairs = collectValidated(Object.entries(secretEntries), (k, m) => validateOneSecret(k, m, secretEntries), "secret");
463
+ const envPairs = collectValidated(Object.entries(envEntries), (k, m) => validateOneEnv(k, m, envEntries), "env");
464
+ return secretPairs.flatMap((secrets) => envPairs.map((envs) => [...secrets, ...envs])).map((allEntries) => ({ entries: new Map(allEntries) }));
485
465
  };
486
466
  /** Does this secret entry point at another entry? */
487
467
  const isSecretAlias = (meta) => meta?.from_key !== void 0;
@@ -555,7 +535,7 @@ const formatPacket = (result, options) => {
555
535
  if (envEntriesArr.length > 0) {
556
536
  const envHeader = `env: ${envEntriesArr.length}`;
557
537
  const envLines = envEntriesArr.map(([key, meta]) => {
558
- return ` ${key}${meta.from_key ? ` [alias → ${meta.from_key}]` : ""}${meta.value !== void 0 ? ` = ${meta.value}` : ""}${meta.purpose ? `\n purpose: ${meta.purpose}` : ""}`;
538
+ return ` ${key}${meta.from_key ? ` [alias → ${meta.from_key}]` : ""}${Option(meta.value).fold(() => "", (v) => ` = ${v}`)}${meta.purpose ? `\n purpose: ${meta.purpose}` : ""}`;
559
539
  });
560
540
  sections.push([envHeader, ...envLines].join("\n"));
561
541
  }
@@ -589,20 +569,26 @@ const classifySecret = (key, meta, fnoxKeys, staleWarningDays, requireExpiration
589
569
  const issues = [];
590
570
  const created = Option(meta.created).flatMap(parseDate);
591
571
  const expires = Option(meta.expires).flatMap(parseDate);
572
+ const lastRotated = Option(meta.last_rotated_at).flatMap(parseDate);
592
573
  const rotationUrl = Option(meta.rotation_url);
593
574
  const purpose = Option(meta.purpose);
594
575
  const service = Option(meta.service);
595
576
  const daysRemaining = expires.map((exp) => daysBetween(today, exp));
596
- const daysSinceCreated = created.map((c) => daysBetween(c, today));
577
+ const staleFromRotation = lastRotated.isSome();
578
+ const daysSinceRotation = (staleFromRotation ? lastRotated : created).map((d) => daysBetween(d, today));
597
579
  const isExpired = daysRemaining.fold(() => false, (d) => d < 0);
598
580
  const isExpiringSoon = daysRemaining.fold(() => false, (d) => d >= 0 && d <= WARN_BEFORE_DAYS);
599
- const isStale = daysSinceCreated.fold(() => false, (d) => d > staleWarningDays);
581
+ const isStale = daysSinceRotation.fold(() => false, (d) => d > staleWarningDays);
600
582
  const hasSealed = !!meta.encrypted_value;
601
583
  const isMissing = fnoxKeys.size > 0 && !fnoxKeys.has(key) && !hasSealed;
602
584
  const isMissingMetadata = requireExpiration && expires.isNone() || requireService && service.isNone();
603
585
  if (isExpired) issues.push("Secret has expired");
604
586
  if (isExpiringSoon) issues.push(`Expires in ${daysRemaining.fold(() => "?", (d) => String(d))} days`);
605
- if (isStale) issues.push("Secret is stale (no rotation detected)");
587
+ if (isStale) {
588
+ const since = daysSinceRotation.fold(() => "?", (d) => String(d));
589
+ const label = staleFromRotation ? "last rotated" : "created";
590
+ issues.push(`Secret is stale (${label} ${since} days ago)`);
591
+ }
606
592
  if (isMissing) issues.push("Key not found in fnox");
607
593
  if (isMissingMetadata) {
608
594
  if (requireExpiration && expires.isNone()) issues.push("Missing required expiration date");
@@ -617,6 +603,7 @@ const classifySecret = (key, meta, fnoxKeys, staleWarningDays, requireExpiration
617
603
  purpose,
618
604
  created: Option(meta.created),
619
605
  expires: Option(meta.expires),
606
+ last_rotated_at: Option(meta.last_rotated_at),
620
607
  issues: List(issues),
621
608
  alias_of: Option(void 0)
622
609
  };
@@ -632,9 +619,10 @@ const classifyAlias = (key, meta, targetHealth, targetRef) => ({
632
619
  status: targetHealth.status,
633
620
  days_remaining: targetHealth.days_remaining,
634
621
  rotation_url: targetHealth.rotation_url,
635
- purpose: meta.purpose !== void 0 ? Option(meta.purpose) : targetHealth.purpose,
622
+ purpose: Option(meta.purpose).fold(() => targetHealth.purpose, (v) => Option(v)),
636
623
  created: targetHealth.created,
637
624
  expires: targetHealth.expires,
625
+ last_rotated_at: targetHealth.last_rotated_at,
638
626
  issues: List([]),
639
627
  alias_of: Option(targetRef)
640
628
  });
@@ -648,17 +636,18 @@ const computeAudit = (config, fnoxKeys, today, aliasTable) => {
648
636
  const secretEntries = config.secret ?? {};
649
637
  const nonAliasEntries = Object.entries(secretEntries).filter(([, meta]) => meta.from_key === void 0);
650
638
  const aliasEntries = Object.entries(secretEntries).filter(([, meta]) => meta.from_key !== void 0);
651
- const nonAliasMetaKeys = new Set(nonAliasEntries.map(([k]) => k));
639
+ const nonAliasMetaKeys = Set$1(nonAliasEntries.map(([k]) => k));
652
640
  const nonAliasHealth = nonAliasEntries.map(([key, meta]) => classifySecret(key, meta, keys, staleWarningDays, requireExpiration, requireService, now));
653
- const healthByKey = new Map(nonAliasHealth.map((h) => [h.key, h]));
641
+ const healthByKey = Map$1(nonAliasHealth.map((h) => [h.key, h]));
654
642
  const parseTargetKey = (from_key) => {
655
- return /^secret\.(.+)$/.exec(from_key)?.[1];
643
+ return Option(from_key.match(/^secret\.(.+)$/)?.[1]);
656
644
  };
657
645
  const aliasHealth = aliasEntries.map(([key, meta]) => {
658
- const targetKey = (aliasTable?.entries.get(`secret.${key}`))?.targetKey ?? (meta.from_key !== void 0 ? parseTargetKey(meta.from_key) : void 0);
659
- const targetHealth = targetKey !== void 0 ? healthByKey.get(targetKey) : void 0;
660
- const targetRef = meta.from_key ?? (targetKey !== void 0 ? `secret.${targetKey}` : "");
661
- if (!targetHealth) return {
646
+ const tableEntry = aliasTable?.entries.get(`secret.${key}`);
647
+ const targetKey = Option(tableEntry?.targetKey).fold(() => Option(meta.from_key).flatMap(parseTargetKey), (k) => Option(k));
648
+ const targetHealth = targetKey.flatMap((k) => healthByKey.get(k));
649
+ const targetRef = Option(meta.from_key).fold(() => targetKey.map((k) => `secret.${k}`), (v) => Option(v)).orElse("");
650
+ return targetHealth.fold(() => ({
662
651
  key,
663
652
  service: Option(meta.service),
664
653
  status: "missing",
@@ -667,13 +656,13 @@ const computeAudit = (config, fnoxKeys, today, aliasTable) => {
667
656
  purpose: Option(meta.purpose),
668
657
  created: Option(meta.created),
669
658
  expires: Option(meta.expires),
659
+ last_rotated_at: Option(meta.last_rotated_at),
670
660
  issues: List(["Alias target not resolvable"]),
671
661
  alias_of: Option(targetRef)
672
- };
673
- return classifyAlias(key, meta, targetHealth, targetRef);
662
+ }), (health) => classifyAlias(key, meta, health, targetRef));
674
663
  });
675
664
  const secrets = List([...nonAliasHealth, ...aliasHealth]);
676
- const orphaned = keys.size > 0 ? [...nonAliasMetaKeys].filter((k) => !keys.has(k)).length : 0;
665
+ const orphaned = keys.size > 0 ? nonAliasMetaKeys.toArray().filter((k) => !keys.has(k)).length : 0;
677
666
  const total = secrets.size;
678
667
  const expired = secrets.count((s) => s.status === "expired");
679
668
  const missing = secrets.count((s) => s.status === "missing");
@@ -699,12 +688,14 @@ const computeAudit = (config, fnoxKeys, today, aliasTable) => {
699
688
  };
700
689
  const computeEnvAudit = (config, env = process.env) => {
701
690
  const envEntries = config.env ?? {};
691
+ const resolveEffectiveDefault = (entry) => {
692
+ return Do(function* () {
693
+ return yield* $(Option((yield* $(Option(envEntries[yield* $(Option((yield* $(Option(entry.from_key))).match(/^env\.(.+)$/)?.[1]))]))).value));
694
+ }).orElse(entry.value ?? "");
695
+ };
702
696
  const entries = Object.entries(envEntries).map(([key, entry]) => {
703
697
  const currentValue = env[key];
704
- const effectiveDefault = entry.from_key !== void 0 ? (() => {
705
- const targetKey = /^env\.(.+)$/.exec(entry.from_key)?.[1];
706
- return (targetKey !== void 0 ? envEntries[targetKey] : void 0)?.value ?? "";
707
- })() : entry.value ?? "";
698
+ const effectiveDefault = resolveEffectiveDefault(entry);
708
699
  return {
709
700
  key,
710
701
  defaultValue: effectiveDefault,
@@ -724,7 +715,7 @@ const computeEnvAudit = (config, env = process.env) => {
724
715
  };
725
716
  //#endregion
726
717
  //#region src/core/patterns.ts
727
- const EXCLUDED_VARS = new Set([
718
+ const EXCLUDED_VARS = Set$1([
728
719
  "PATH",
729
720
  "HOME",
730
721
  "USER",
@@ -1437,25 +1428,22 @@ const envScan = (env, options) => {
1437
1428
  };
1438
1429
  };
1439
1430
  const parseAliasRef = (raw, expectedKind) => {
1440
- const match = /^(secret|env)\.(.+)$/.exec(raw);
1441
- if (!match) return void 0;
1442
- if (match[1] !== expectedKind) return void 0;
1443
- return match[2];
1431
+ const match = raw.match(/^(secret|env)\.(.+)$/);
1432
+ if (match?.[1] !== expectedKind) return Option(void 0);
1433
+ return Option(match[2]);
1444
1434
  };
1445
1435
  /** Bidirectional drift detection between config and live environment */
1446
1436
  const envCheck = (config, env) => {
1447
1437
  const secretEntries = config.secret ?? {};
1448
1438
  const metaKeys = Object.keys(secretEntries);
1449
- const trackedSet = new Set(metaKeys);
1439
+ const metaKeysSet = Set$1(metaKeys);
1450
1440
  const isSecretPresent = (key) => {
1451
1441
  if (env[key] !== void 0 && env[key] !== "") return true;
1452
1442
  const meta = secretEntries[key];
1453
1443
  if (meta?.from_key === void 0) return false;
1454
- const targetKey = parseAliasRef(meta.from_key, "secret");
1455
- return targetKey !== void 0 && env[targetKey] !== void 0 && env[targetKey] !== "";
1444
+ return parseAliasRef(meta.from_key, "secret").fold(() => false, (targetKey) => env[targetKey] !== void 0 && env[targetKey] !== "");
1456
1445
  };
1457
- const secretDriftEntries = metaKeys.map((key) => {
1458
- const meta = secretEntries[key];
1446
+ const secretDriftEntries = Object.entries(secretEntries).map(([key, meta]) => {
1459
1447
  const present = isSecretPresent(key);
1460
1448
  return {
1461
1449
  envVar: key,
@@ -1469,14 +1457,9 @@ const envCheck = (config, env) => {
1469
1457
  if (env[key] !== void 0 && env[key] !== "") return true;
1470
1458
  const meta = envDefaults[key];
1471
1459
  if (meta?.from_key === void 0) return false;
1472
- const targetKey = parseAliasRef(meta.from_key, "env");
1473
- return targetKey !== void 0 && env[targetKey] !== void 0 && env[targetKey] !== "";
1460
+ return parseAliasRef(meta.from_key, "env").fold(() => false, (targetKey) => env[targetKey] !== void 0 && env[targetKey] !== "");
1474
1461
  };
1475
- const envDefaultEntries = Object.keys(envDefaults).filter((key) => {
1476
- if (trackedSet.has(key)) return false;
1477
- trackedSet.add(key);
1478
- return true;
1479
- }).map((key) => {
1462
+ const envDefaultEntries = Object.keys(envDefaults).filter((key) => !metaKeysSet.has(key)).map((key) => {
1480
1463
  const present = isEnvPresent(key);
1481
1464
  return {
1482
1465
  envVar: key,
@@ -1485,7 +1468,8 @@ const envCheck = (config, env) => {
1485
1468
  confidence: Option(void 0)
1486
1469
  };
1487
1470
  });
1488
- const untrackedEntries = scanEnv(env).filter((match) => !trackedSet.has(match.envVar)).map((match) => ({
1471
+ const trackedKeys = Set$1([...metaKeys, ...envDefaultEntries.map((e) => e.envVar)]);
1472
+ const untrackedEntries = scanEnv(env).filter((match) => !trackedKeys.has(match.envVar)).map((match) => ({
1489
1473
  envVar: match.envVar,
1490
1474
  service: match.service,
1491
1475
  status: "untracked",
@@ -1843,11 +1827,12 @@ const sealSecrets = (meta, values, recipient) => {
1843
1827
  message: "age CLI not found on PATH"
1844
1828
  });
1845
1829
  return Object.entries(meta).reduce((acc, [key, secretMeta]) => acc.flatMap((result) => {
1846
- if (!(key in values)) return Right({
1830
+ const value = values[key];
1831
+ if (value === void 0) return Right({
1847
1832
  ...result,
1848
1833
  [key]: secretMeta
1849
1834
  });
1850
- return ageEncrypt(values[key], recipient).mapLeft((err) => ({
1835
+ return ageEncrypt(value, recipient).mapLeft((err) => ({
1851
1836
  _tag: "EncryptFailed",
1852
1837
  key,
1853
1838
  message: err.message
@@ -1943,11 +1928,11 @@ const bootSafe = (options) => {
1943
1928
  const secretEntries = config.secret ?? {};
1944
1929
  const envEntries = config.env ?? {};
1945
1930
  const nonAliasSecretEntries = Object.fromEntries(Object.entries(secretEntries).filter(([, meta]) => meta.from_key === void 0));
1946
- const aliasSecretKeys = Object.keys(secretEntries).filter((k) => secretEntries[k].from_key !== void 0);
1931
+ const aliasSecretKeys = Object.entries(secretEntries).filter(([, meta]) => meta.from_key !== void 0).map(([k]) => k);
1947
1932
  const nonAliasEnvEntries = Object.entries(envEntries).filter(([, meta]) => meta.from_key === void 0);
1948
- const aliasEnvKeys = Object.keys(envEntries).filter((k) => envEntries[k].from_key !== void 0);
1933
+ const aliasEnvKeys = Object.entries(envEntries).filter(([, meta]) => meta.from_key !== void 0).map(([k]) => k);
1949
1934
  const nonAliasMetaKeys = Object.keys(nonAliasSecretEntries);
1950
- const hasSealedValues = nonAliasMetaKeys.some((k) => !!nonAliasSecretEntries[k].encrypted_value);
1935
+ const hasSealedValues = Object.values(nonAliasSecretEntries).some((meta) => !!meta.encrypted_value);
1951
1936
  const identityKeyResult = resolveIdentityKey(config, configDir);
1952
1937
  const identityKey = identityKeyResult.fold(() => Option(void 0), (k) => k);
1953
1938
  if (identityKeyResult.isLeft() && !hasSealedValues) return identityKeyResult.fold((err) => Left(err), () => Left({
@@ -1960,7 +1945,7 @@ const bootSafe = (options) => {
1960
1945
  const injected = [];
1961
1946
  const skipped = [];
1962
1947
  warnings.push(...checkEnvMisclassification(config));
1963
- const envDefaults = Object.fromEntries(nonAliasEnvEntries.flatMap(([key, entry]) => Option(process.env[key]).fold(() => entry.value !== void 0 ? [[key, entry.value]] : [], () => [])));
1948
+ const envDefaults = Object.fromEntries(nonAliasEnvEntries.flatMap(([key, entry]) => Option(process.env[key]).fold(() => Option(entry.value).fold(() => [], (v) => [[key, v]]), () => [])));
1964
1949
  const overridden = nonAliasEnvEntries.flatMap(([key]) => Option(process.env[key]).fold(() => [], () => [key]));
1965
1950
  if (inject) Object.entries(envDefaults).forEach(([key, value]) => {
1966
1951
  process.env[key] = value;
@@ -2020,21 +2005,20 @@ const bootSafe = (options) => {
2020
2005
  aliasSecretKeys.forEach((aliasKey) => {
2021
2006
  const entry = aliasTable.entries.get(`secret.${aliasKey}`);
2022
2007
  if (!entry) return;
2023
- const targetValue = secrets[entry.targetKey];
2024
- if (targetValue !== void 0) {
2025
- secrets[aliasKey] = targetValue;
2026
- injected.push(aliasKey);
2027
- log.debug("phase.alias.copied", {
2008
+ Option(secrets[entry.targetKey]).fold(() => {
2009
+ skipped.push(aliasKey);
2010
+ log.debug("phase.alias.target_unresolved", {
2028
2011
  alias: aliasKey,
2029
2012
  target: entry.targetKey
2030
2013
  });
2031
- } else {
2032
- skipped.push(aliasKey);
2033
- log.debug("phase.alias.target_unresolved", {
2014
+ }, (targetValue) => {
2015
+ secrets[aliasKey] = targetValue;
2016
+ injected.push(aliasKey);
2017
+ log.debug("phase.alias.copied", {
2034
2018
  alias: aliasKey,
2035
2019
  target: entry.targetKey
2036
2020
  });
2037
- }
2021
+ });
2038
2022
  });
2039
2023
  aliasEnvKeys.forEach((aliasKey) => {
2040
2024
  const entry = aliasTable.entries.get(`env.${aliasKey}`);
@@ -2149,6 +2133,22 @@ const resolveValues = async (keys, profile, agentKey) => {
2149
2133
  //#region src/core/toml-edit.ts
2150
2134
  const SECTION_RE = /^\[.+\]\s*$/;
2151
2135
  const MULTILINE_OPEN = "\"\"\"";
2136
+ const scanSectionBoundary = (state, line, i) => {
2137
+ if (state.done) return state;
2138
+ if (state.inMultiline) return line.includes(MULTILINE_OPEN) ? {
2139
+ ...state,
2140
+ inMultiline: false
2141
+ } : state;
2142
+ if (line.includes(MULTILINE_OPEN)) return (line.slice(line.indexOf("=") + 1).trim().match(/* @__PURE__ */ new RegExp("\"\"\"", "g")) ?? []).length === 1 ? {
2143
+ ...state,
2144
+ inMultiline: true
2145
+ } : state;
2146
+ return SECTION_RE.test(line) ? {
2147
+ ...state,
2148
+ end: i,
2149
+ done: true
2150
+ } : state;
2151
+ };
2152
2152
  /**
2153
2153
  * Find the line range [start, end) of a TOML section by its header string.
2154
2154
  * The range includes the header line through to (but not including) the next section header or EOF.
@@ -2157,26 +2157,14 @@ const MULTILINE_OPEN = "\"\"\"";
2157
2157
  const findSectionRange = (lines, sectionHeader) => {
2158
2158
  const start = lines.findIndex((l) => l.trim() === sectionHeader);
2159
2159
  if (start === -1) return void 0;
2160
- let end = lines.length;
2161
- let inMultiline = false;
2162
- for (let i = start + 1; i < lines.length; i++) {
2163
- const line = lines[i];
2164
- if (inMultiline) {
2165
- if (line.includes(MULTILINE_OPEN)) inMultiline = false;
2166
- continue;
2167
- }
2168
- if (line.includes(MULTILINE_OPEN)) {
2169
- if ((line.slice(line.indexOf("=") + 1).trim().match(/* @__PURE__ */ new RegExp("\"\"\"", "g")) ?? []).length === 1) inMultiline = true;
2170
- continue;
2171
- }
2172
- if (SECTION_RE.test(line)) {
2173
- end = i;
2174
- break;
2175
- }
2176
- }
2160
+ const initial = {
2161
+ end: lines.length,
2162
+ inMultiline: false,
2163
+ done: false
2164
+ };
2177
2165
  return {
2178
2166
  start,
2179
- end
2167
+ end: List(lines.slice(start + 1)).zipWithIndex().foldLeft(initial)((state, entry) => scanSectionBoundary(state, entry[0], start + 1 + entry[1])).end
2180
2168
  };
2181
2169
  };
2182
2170
  /** Check whether a section header exists in the raw TOML */
@@ -2192,12 +2180,10 @@ const removeSection = (raw, sectionHeader) => {
2192
2180
  _tag: "SectionNotFound",
2193
2181
  section: sectionHeader
2194
2182
  });
2195
- let removeEnd = range.end;
2196
- while (removeEnd > range.start && removeEnd - 1 >= range.start && lines[removeEnd - 1].trim() === "") removeEnd--;
2197
- const before = lines.slice(0, range.start);
2198
2183
  const after = lines.slice(range.end);
2199
- while (before.length > 0 && before[before.length - 1].trim() === "") before.pop();
2200
- const result = [...before, ...after].join("\n");
2184
+ const beforeAll = lines.slice(0, range.start);
2185
+ const lastNonBlank = beforeAll.findLastIndex((l) => l.trim() !== "");
2186
+ const result = [...lastNonBlank === -1 ? [] : beforeAll.slice(0, lastNonBlank + 1), ...after].join("\n");
2201
2187
  return Either.right(result);
2202
2188
  };
2203
2189
  /**
@@ -2233,55 +2219,47 @@ const updateSectionFields = (raw, sectionHeader, updates) => {
2233
2219
  const before = lines.slice(0, range.start + 1);
2234
2220
  const after = lines.slice(range.end);
2235
2221
  const sectionBody = lines.slice(range.start + 1, range.end);
2236
- const remaining = [];
2237
- const updatedKeys = /* @__PURE__ */ new Set();
2238
- let inMultiline = false;
2239
- let multilineKey = "";
2240
- for (let i = 0; i < sectionBody.length; i++) {
2241
- const line = sectionBody[i];
2242
- if (inMultiline) {
2243
- if (line.includes(MULTILINE_OPEN)) {
2244
- inMultiline = false;
2245
- if (updates[multilineKey] === null) continue;
2246
- if (multilineKey in updates) continue;
2247
- } else {
2248
- if (updates[multilineKey] === null) continue;
2249
- if (multilineKey in updates) continue;
2250
- }
2251
- remaining.push(line);
2252
- continue;
2253
- }
2222
+ const findClosingMultiline = (fromIdx) => {
2223
+ const idx = sectionBody.findIndex((l, j) => j > fromIdx && l.includes(MULTILINE_OPEN));
2224
+ return idx === -1 ? sectionBody.length : idx;
2225
+ };
2226
+ const initial = {
2227
+ remaining: [],
2228
+ updatedKeys: Set$1.empty(),
2229
+ skipUntil: -1
2230
+ };
2231
+ const step = (state, line, i) => {
2232
+ if (i <= state.skipUntil) return state;
2254
2233
  const eqIdx = line.indexOf("=");
2255
- if (eqIdx > 0 && !line.trimStart().startsWith("#") && !line.trimStart().startsWith("[")) {
2256
- const key = line.slice(0, eqIdx).trim();
2257
- if (key in updates) {
2258
- updatedKeys.add(key);
2259
- const afterEquals = line.slice(eqIdx + 1).trim();
2260
- if (afterEquals.includes(MULTILINE_OPEN)) {
2261
- if ((afterEquals.match(/* @__PURE__ */ new RegExp("\"\"\"", "g")) ?? []).length === 1) {
2262
- inMultiline = true;
2263
- multilineKey = key;
2264
- }
2265
- }
2266
- if (updates[key] === null) continue;
2267
- remaining.push(`${key} = ${updates[key]}`);
2268
- if (inMultiline) {
2269
- for (let j = i + 1; j < sectionBody.length; j++) if (sectionBody[j].includes(MULTILINE_OPEN)) {
2270
- i = j;
2271
- inMultiline = false;
2272
- break;
2273
- }
2274
- }
2275
- continue;
2276
- }
2234
+ const isFieldLine = eqIdx > 0 && !line.trimStart().startsWith("#") && !line.trimStart().startsWith("[");
2235
+ const key = isFieldLine ? line.slice(0, eqIdx).trim() : "";
2236
+ if (isFieldLine && key in updates) {
2237
+ const afterEquals = line.slice(eqIdx + 1).trim();
2238
+ const skipUntil = afterEquals.includes(MULTILINE_OPEN) && (afterEquals.match(/* @__PURE__ */ new RegExp("\"\"\"", "g")) ?? []).length === 1 ? findClosingMultiline(i) : state.skipUntil;
2239
+ const updatedKeys = state.updatedKeys.add(key);
2240
+ const value = updates[key];
2241
+ if (value === null) return {
2242
+ ...state,
2243
+ updatedKeys,
2244
+ skipUntil
2245
+ };
2246
+ return {
2247
+ remaining: [...state.remaining, `${key} = ${value}`],
2248
+ updatedKeys,
2249
+ skipUntil
2250
+ };
2277
2251
  }
2278
- remaining.push(line);
2279
- }
2280
- const newFields = Object.entries(updates).filter(([key, value]) => value !== null && !updatedKeys.has(key)).map(([key, value]) => `${key} = ${value}`);
2281
- remaining.push(...newFields);
2252
+ return {
2253
+ ...state,
2254
+ remaining: [...state.remaining, line]
2255
+ };
2256
+ };
2257
+ const final = List(sectionBody).zipWithIndex().foldLeft(initial)((state, entry) => step(state, entry[0], entry[1]));
2258
+ const newFields = Object.entries(updates).filter(([key, value]) => value !== null && !final.updatedKeys.has(key)).map(([key, value]) => `${key} = ${value}`);
2282
2259
  const result = [
2283
2260
  ...before,
2284
- ...remaining,
2261
+ ...final.remaining,
2262
+ ...newFields,
2285
2263
  ...after
2286
2264
  ].join("\n");
2287
2265
  return Either.right(result);
@@ -2294,31 +2272,7 @@ const appendSection = (raw, block) => `${raw.trimEnd()}\n\n${block}`;
2294
2272
  //#endregion
2295
2273
  //#region src/core/fleet.ts
2296
2274
  const CONFIG_FILENAME = "envpkt.toml";
2297
- const SKIP_DIRS = new Set([
2298
- "node_modules",
2299
- ".git",
2300
- ".hg",
2301
- ".svn",
2302
- "dist",
2303
- "build",
2304
- "lib",
2305
- ".claude",
2306
- "__pycache__",
2307
- "target",
2308
- "out",
2309
- "tmp",
2310
- ".terraform",
2311
- ".gradle",
2312
- ".cargo",
2313
- ".venv",
2314
- ".next",
2315
- ".cache",
2316
- ".tox",
2317
- "vendor",
2318
- "coverage",
2319
- ".nyc_output",
2320
- ".turbo"
2321
- ]);
2275
+ const SKIP_DIRS = Set$1.of("node_modules", ".git", ".hg", ".svn", "dist", "build", "lib", ".claude", "__pycache__", "target", "out", "tmp", ".terraform", ".gradle", ".cargo", ".venv", ".next", ".cache", ".tox", "vendor", "coverage", ".nyc_output", ".turbo");
2322
2276
  function* findEnvpktFiles(dir, maxDepth, currentDepth = 0) {
2323
2277
  if (currentDepth > maxDepth) return;
2324
2278
  const configPath = join(dir, CONFIG_FILENAME);
@@ -2585,24 +2539,19 @@ const handleGetSecretMeta = (args) => {
2585
2539
  const secretEntries = config.secret ?? {};
2586
2540
  return Option(secretEntries[key]).fold(() => errorResult(`Secret not found: ${key}`), (meta) => {
2587
2541
  const { encrypted_value: _, from_key: fromKey, ...rest } = meta;
2588
- if (fromKey !== void 0) {
2589
- const targetKey = /^secret\.(.+)$/.exec(fromKey)?.[1];
2590
- const target = targetKey !== void 0 ? secretEntries[targetKey] : void 0;
2591
- if (target) {
2592
- const { encrypted_value: __, from_key: ___, ...targetRest } = target;
2593
- return textResult(JSON.stringify({
2594
- key,
2595
- ...targetRest,
2596
- ...rest,
2597
- alias_of: fromKey
2598
- }, null, 2));
2599
- }
2542
+ if (fromKey !== void 0) return Option(fromKey.match(/^secret\.(.+)$/)?.[1]).flatMap((k) => Option(secretEntries[k])).fold(() => textResult(JSON.stringify({
2543
+ key,
2544
+ ...rest,
2545
+ alias_of: fromKey
2546
+ }, null, 2)), (t) => {
2547
+ const { encrypted_value: __, from_key: ___, ...targetRest } = t;
2600
2548
  return textResult(JSON.stringify({
2601
2549
  key,
2550
+ ...targetRest,
2602
2551
  ...rest,
2603
2552
  alias_of: fromKey
2604
2553
  }, null, 2));
2605
- }
2554
+ });
2606
2555
  return textResult(JSON.stringify({
2607
2556
  key,
2608
2557
  ...rest