envpkt 0.11.0 → 0.11.2
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/cli.js +496 -290
- package/dist/index.d.ts +99 -113
- package/dist/index.js +143 -207
- package/package.json +8 -8
package/dist/index.js
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
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";
|
|
8
|
-
import { createDirectConsoleLogger, createDirectTestLogger, directSilentLogger, directSilentLogger as directSilentLogger$1 } from "functype-log";
|
|
8
|
+
import { createDirectConsoleLogger, createDirectTestLogger, directSilentLogger, directSilentLogger as directSilentLogger$1 } from "functype-log/direct";
|
|
9
9
|
import { execFileSync } from "node:child_process";
|
|
10
10
|
import { homedir } from "node:os";
|
|
11
11
|
import { createInterface } from "node:readline";
|
|
@@ -294,18 +294,19 @@ const loadCatalog = (catalogPath) => loadConfig(catalogPath).fold((err) => {
|
|
|
294
294
|
/** Resolve secrets by merging catalog meta with agent overrides (shallow merge) */
|
|
295
295
|
const resolveSecrets = (agentMeta, catalogMeta, agentSecrets, catalogPath) => {
|
|
296
296
|
return agentSecrets.reduce((acc, key) => acc.flatMap((resolved) => {
|
|
297
|
-
|
|
297
|
+
const catalogEntry = catalogMeta[key];
|
|
298
|
+
if (catalogEntry === void 0) return Left({
|
|
298
299
|
_tag: "SecretNotInCatalog",
|
|
299
300
|
key,
|
|
300
301
|
catalogPath
|
|
301
302
|
});
|
|
302
|
-
const
|
|
303
|
+
const merged = Option(agentMeta[key]).fold(() => catalogEntry, (override) => ({
|
|
304
|
+
...catalogEntry,
|
|
305
|
+
...override
|
|
306
|
+
}));
|
|
303
307
|
return Right({
|
|
304
308
|
...resolved,
|
|
305
|
-
[key]:
|
|
306
|
-
...catalogEntry,
|
|
307
|
-
...agentMeta[key]
|
|
308
|
-
} : catalogEntry
|
|
309
|
+
[key]: merged
|
|
309
310
|
});
|
|
310
311
|
}), Right({}));
|
|
311
312
|
};
|
|
@@ -372,12 +373,12 @@ const validateOneSecret = (key, meta, secretEntries) => {
|
|
|
372
373
|
kind: "secret",
|
|
373
374
|
field: "encrypted_value"
|
|
374
375
|
});
|
|
375
|
-
return parseRef(ref).
|
|
376
|
+
return parseRef(ref).toEither({
|
|
376
377
|
_tag: "AliasInvalidSyntax",
|
|
377
378
|
key,
|
|
378
379
|
kind: "secret",
|
|
379
380
|
value: ref
|
|
380
|
-
})
|
|
381
|
+
}).flatMap((parsed) => {
|
|
381
382
|
if (parsed.kind !== "secret") return Left({
|
|
382
383
|
_tag: "AliasCrossType",
|
|
383
384
|
key,
|
|
@@ -388,23 +389,23 @@ const validateOneSecret = (key, meta, secretEntries) => {
|
|
|
388
389
|
_tag: "AliasSelfReference",
|
|
389
390
|
key: `secret.${key}`
|
|
390
391
|
});
|
|
391
|
-
return Option(secretEntries[parsed.key]).
|
|
392
|
+
return Option(secretEntries[parsed.key]).toEither({
|
|
392
393
|
_tag: "AliasTargetMissing",
|
|
393
394
|
key: `secret.${key}`,
|
|
394
395
|
target: ref
|
|
395
|
-
})
|
|
396
|
+
}).flatMap((target) => {
|
|
396
397
|
if (target.from_key !== void 0) return Left({
|
|
397
398
|
_tag: "AliasChained",
|
|
398
399
|
key: `secret.${key}`,
|
|
399
400
|
target: ref
|
|
400
401
|
});
|
|
401
|
-
return Right(
|
|
402
|
+
return Right({
|
|
402
403
|
kind: "secret",
|
|
403
404
|
targetKind: "secret",
|
|
404
405
|
targetKey: parsed.key
|
|
405
|
-
})
|
|
406
|
+
});
|
|
406
407
|
});
|
|
407
|
-
});
|
|
408
|
+
}).map((entry) => Option(entry));
|
|
408
409
|
};
|
|
409
410
|
const validateOneEnv = (key, meta, envEntries) => {
|
|
410
411
|
if (meta.from_key === void 0) return Right(Option(void 0));
|
|
@@ -415,12 +416,12 @@ const validateOneEnv = (key, meta, envEntries) => {
|
|
|
415
416
|
kind: "env",
|
|
416
417
|
field: "value"
|
|
417
418
|
});
|
|
418
|
-
return parseRef(ref).
|
|
419
|
+
return parseRef(ref).toEither({
|
|
419
420
|
_tag: "AliasInvalidSyntax",
|
|
420
421
|
key,
|
|
421
422
|
kind: "env",
|
|
422
423
|
value: ref
|
|
423
|
-
})
|
|
424
|
+
}).flatMap((parsed) => {
|
|
424
425
|
if (parsed.kind !== "env") return Left({
|
|
425
426
|
_tag: "AliasCrossType",
|
|
426
427
|
key,
|
|
@@ -431,57 +432,32 @@ const validateOneEnv = (key, meta, envEntries) => {
|
|
|
431
432
|
_tag: "AliasSelfReference",
|
|
432
433
|
key: `env.${key}`
|
|
433
434
|
});
|
|
434
|
-
return Option(envEntries[parsed.key]).
|
|
435
|
+
return Option(envEntries[parsed.key]).toEither({
|
|
435
436
|
_tag: "AliasTargetMissing",
|
|
436
437
|
key: `env.${key}`,
|
|
437
438
|
target: ref
|
|
438
|
-
})
|
|
439
|
+
}).flatMap((target) => {
|
|
439
440
|
if (target.from_key !== void 0) return Left({
|
|
440
441
|
_tag: "AliasChained",
|
|
441
442
|
key: `env.${key}`,
|
|
442
443
|
target: ref
|
|
443
444
|
});
|
|
444
|
-
return Right(
|
|
445
|
+
return Right({
|
|
445
446
|
kind: "env",
|
|
446
447
|
targetKind: "env",
|
|
447
448
|
targetKey: parsed.key
|
|
448
|
-
})
|
|
449
|
+
});
|
|
449
450
|
});
|
|
450
|
-
});
|
|
451
|
+
}).map((entry) => Option(entry));
|
|
451
452
|
};
|
|
452
|
-
/**
|
|
453
|
-
|
|
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
|
-
*/
|
|
453
|
+
/** Fail-fast reduction: accumulates validated entries, short-circuits on first AliasError */
|
|
454
|
+
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
455
|
const validateAliases = (config) => {
|
|
467
456
|
const secretEntries = config.secret ?? {};
|
|
468
457
|
const envEntries = config.env ?? {};
|
|
469
|
-
const
|
|
470
|
-
const
|
|
471
|
-
|
|
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 });
|
|
458
|
+
const secretPairs = collectValidated(Object.entries(secretEntries), (k, m) => validateOneSecret(k, m, secretEntries), "secret");
|
|
459
|
+
const envPairs = collectValidated(Object.entries(envEntries), (k, m) => validateOneEnv(k, m, envEntries), "env");
|
|
460
|
+
return secretPairs.flatMap((secrets) => envPairs.map((envs) => [...secrets, ...envs])).map((allEntries) => ({ entries: new Map(allEntries) }));
|
|
485
461
|
};
|
|
486
462
|
/** Does this secret entry point at another entry? */
|
|
487
463
|
const isSecretAlias = (meta) => meta?.from_key !== void 0;
|
|
@@ -555,7 +531,7 @@ const formatPacket = (result, options) => {
|
|
|
555
531
|
if (envEntriesArr.length > 0) {
|
|
556
532
|
const envHeader = `env: ${envEntriesArr.length}`;
|
|
557
533
|
const envLines = envEntriesArr.map(([key, meta]) => {
|
|
558
|
-
return ` ${key}${meta.from_key ? ` [alias → ${meta.from_key}]` : ""}${meta.value
|
|
534
|
+
return ` ${key}${meta.from_key ? ` [alias → ${meta.from_key}]` : ""}${Option(meta.value).fold(() => "", (v) => ` = ${v}`)}${meta.purpose ? `\n purpose: ${meta.purpose}` : ""}`;
|
|
559
535
|
});
|
|
560
536
|
sections.push([envHeader, ...envLines].join("\n"));
|
|
561
537
|
}
|
|
@@ -632,7 +608,7 @@ const classifyAlias = (key, meta, targetHealth, targetRef) => ({
|
|
|
632
608
|
status: targetHealth.status,
|
|
633
609
|
days_remaining: targetHealth.days_remaining,
|
|
634
610
|
rotation_url: targetHealth.rotation_url,
|
|
635
|
-
purpose:
|
|
611
|
+
purpose: Option(meta.purpose).fold(() => targetHealth.purpose, (v) => Option(v)),
|
|
636
612
|
created: targetHealth.created,
|
|
637
613
|
expires: targetHealth.expires,
|
|
638
614
|
issues: List([]),
|
|
@@ -648,17 +624,18 @@ const computeAudit = (config, fnoxKeys, today, aliasTable) => {
|
|
|
648
624
|
const secretEntries = config.secret ?? {};
|
|
649
625
|
const nonAliasEntries = Object.entries(secretEntries).filter(([, meta]) => meta.from_key === void 0);
|
|
650
626
|
const aliasEntries = Object.entries(secretEntries).filter(([, meta]) => meta.from_key !== void 0);
|
|
651
|
-
const nonAliasMetaKeys =
|
|
627
|
+
const nonAliasMetaKeys = Set$1(nonAliasEntries.map(([k]) => k));
|
|
652
628
|
const nonAliasHealth = nonAliasEntries.map(([key, meta]) => classifySecret(key, meta, keys, staleWarningDays, requireExpiration, requireService, now));
|
|
653
|
-
const healthByKey =
|
|
629
|
+
const healthByKey = Map$1(nonAliasHealth.map((h) => [h.key, h]));
|
|
654
630
|
const parseTargetKey = (from_key) => {
|
|
655
|
-
return /^secret\.(.+)
|
|
631
|
+
return Option(from_key.match(/^secret\.(.+)$/)?.[1]);
|
|
656
632
|
};
|
|
657
633
|
const aliasHealth = aliasEntries.map(([key, meta]) => {
|
|
658
|
-
const
|
|
659
|
-
const
|
|
660
|
-
const
|
|
661
|
-
|
|
634
|
+
const tableEntry = aliasTable?.entries.get(`secret.${key}`);
|
|
635
|
+
const targetKey = Option(tableEntry?.targetKey).fold(() => Option(meta.from_key).flatMap(parseTargetKey), (k) => Option(k));
|
|
636
|
+
const targetHealth = targetKey.flatMap((k) => healthByKey.get(k));
|
|
637
|
+
const targetRef = Option(meta.from_key).fold(() => targetKey.map((k) => `secret.${k}`), (v) => Option(v)).orElse("");
|
|
638
|
+
return targetHealth.fold(() => ({
|
|
662
639
|
key,
|
|
663
640
|
service: Option(meta.service),
|
|
664
641
|
status: "missing",
|
|
@@ -669,11 +646,10 @@ const computeAudit = (config, fnoxKeys, today, aliasTable) => {
|
|
|
669
646
|
expires: Option(meta.expires),
|
|
670
647
|
issues: List(["Alias target not resolvable"]),
|
|
671
648
|
alias_of: Option(targetRef)
|
|
672
|
-
};
|
|
673
|
-
return classifyAlias(key, meta, targetHealth, targetRef);
|
|
649
|
+
}), (health) => classifyAlias(key, meta, health, targetRef));
|
|
674
650
|
});
|
|
675
651
|
const secrets = List([...nonAliasHealth, ...aliasHealth]);
|
|
676
|
-
const orphaned = keys.size > 0 ?
|
|
652
|
+
const orphaned = keys.size > 0 ? nonAliasMetaKeys.toArray().filter((k) => !keys.has(k)).length : 0;
|
|
677
653
|
const total = secrets.size;
|
|
678
654
|
const expired = secrets.count((s) => s.status === "expired");
|
|
679
655
|
const missing = secrets.count((s) => s.status === "missing");
|
|
@@ -699,12 +675,14 @@ const computeAudit = (config, fnoxKeys, today, aliasTable) => {
|
|
|
699
675
|
};
|
|
700
676
|
const computeEnvAudit = (config, env = process.env) => {
|
|
701
677
|
const envEntries = config.env ?? {};
|
|
678
|
+
const resolveEffectiveDefault = (entry) => {
|
|
679
|
+
return Do(function* () {
|
|
680
|
+
return yield* $(Option((yield* $(Option(envEntries[yield* $(Option((yield* $(Option(entry.from_key))).match(/^env\.(.+)$/)?.[1]))]))).value));
|
|
681
|
+
}).orElse(entry.value ?? "");
|
|
682
|
+
};
|
|
702
683
|
const entries = Object.entries(envEntries).map(([key, entry]) => {
|
|
703
684
|
const currentValue = env[key];
|
|
704
|
-
const effectiveDefault = entry
|
|
705
|
-
const targetKey = /^env\.(.+)$/.exec(entry.from_key)?.[1];
|
|
706
|
-
return (targetKey !== void 0 ? envEntries[targetKey] : void 0)?.value ?? "";
|
|
707
|
-
})() : entry.value ?? "";
|
|
685
|
+
const effectiveDefault = resolveEffectiveDefault(entry);
|
|
708
686
|
return {
|
|
709
687
|
key,
|
|
710
688
|
defaultValue: effectiveDefault,
|
|
@@ -724,7 +702,7 @@ const computeEnvAudit = (config, env = process.env) => {
|
|
|
724
702
|
};
|
|
725
703
|
//#endregion
|
|
726
704
|
//#region src/core/patterns.ts
|
|
727
|
-
const EXCLUDED_VARS =
|
|
705
|
+
const EXCLUDED_VARS = Set$1([
|
|
728
706
|
"PATH",
|
|
729
707
|
"HOME",
|
|
730
708
|
"USER",
|
|
@@ -1437,25 +1415,22 @@ const envScan = (env, options) => {
|
|
|
1437
1415
|
};
|
|
1438
1416
|
};
|
|
1439
1417
|
const parseAliasRef = (raw, expectedKind) => {
|
|
1440
|
-
const match = /^(secret|env)\.(.+)
|
|
1441
|
-
if (
|
|
1442
|
-
|
|
1443
|
-
return match[2];
|
|
1418
|
+
const match = raw.match(/^(secret|env)\.(.+)$/);
|
|
1419
|
+
if (match?.[1] !== expectedKind) return Option(void 0);
|
|
1420
|
+
return Option(match[2]);
|
|
1444
1421
|
};
|
|
1445
1422
|
/** Bidirectional drift detection between config and live environment */
|
|
1446
1423
|
const envCheck = (config, env) => {
|
|
1447
1424
|
const secretEntries = config.secret ?? {};
|
|
1448
1425
|
const metaKeys = Object.keys(secretEntries);
|
|
1449
|
-
const
|
|
1426
|
+
const metaKeysSet = Set$1(metaKeys);
|
|
1450
1427
|
const isSecretPresent = (key) => {
|
|
1451
1428
|
if (env[key] !== void 0 && env[key] !== "") return true;
|
|
1452
1429
|
const meta = secretEntries[key];
|
|
1453
1430
|
if (meta?.from_key === void 0) return false;
|
|
1454
|
-
|
|
1455
|
-
return targetKey !== void 0 && env[targetKey] !== void 0 && env[targetKey] !== "";
|
|
1431
|
+
return parseAliasRef(meta.from_key, "secret").fold(() => false, (targetKey) => env[targetKey] !== void 0 && env[targetKey] !== "");
|
|
1456
1432
|
};
|
|
1457
|
-
const secretDriftEntries =
|
|
1458
|
-
const meta = secretEntries[key];
|
|
1433
|
+
const secretDriftEntries = Object.entries(secretEntries).map(([key, meta]) => {
|
|
1459
1434
|
const present = isSecretPresent(key);
|
|
1460
1435
|
return {
|
|
1461
1436
|
envVar: key,
|
|
@@ -1469,14 +1444,9 @@ const envCheck = (config, env) => {
|
|
|
1469
1444
|
if (env[key] !== void 0 && env[key] !== "") return true;
|
|
1470
1445
|
const meta = envDefaults[key];
|
|
1471
1446
|
if (meta?.from_key === void 0) return false;
|
|
1472
|
-
|
|
1473
|
-
return targetKey !== void 0 && env[targetKey] !== void 0 && env[targetKey] !== "";
|
|
1447
|
+
return parseAliasRef(meta.from_key, "env").fold(() => false, (targetKey) => env[targetKey] !== void 0 && env[targetKey] !== "");
|
|
1474
1448
|
};
|
|
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) => {
|
|
1449
|
+
const envDefaultEntries = Object.keys(envDefaults).filter((key) => !metaKeysSet.has(key)).map((key) => {
|
|
1480
1450
|
const present = isEnvPresent(key);
|
|
1481
1451
|
return {
|
|
1482
1452
|
envVar: key,
|
|
@@ -1485,7 +1455,8 @@ const envCheck = (config, env) => {
|
|
|
1485
1455
|
confidence: Option(void 0)
|
|
1486
1456
|
};
|
|
1487
1457
|
});
|
|
1488
|
-
const
|
|
1458
|
+
const trackedKeys = Set$1([...metaKeys, ...envDefaultEntries.map((e) => e.envVar)]);
|
|
1459
|
+
const untrackedEntries = scanEnv(env).filter((match) => !trackedKeys.has(match.envVar)).map((match) => ({
|
|
1489
1460
|
envVar: match.envVar,
|
|
1490
1461
|
service: match.service,
|
|
1491
1462
|
status: "untracked",
|
|
@@ -1843,11 +1814,12 @@ const sealSecrets = (meta, values, recipient) => {
|
|
|
1843
1814
|
message: "age CLI not found on PATH"
|
|
1844
1815
|
});
|
|
1845
1816
|
return Object.entries(meta).reduce((acc, [key, secretMeta]) => acc.flatMap((result) => {
|
|
1846
|
-
|
|
1817
|
+
const value = values[key];
|
|
1818
|
+
if (value === void 0) return Right({
|
|
1847
1819
|
...result,
|
|
1848
1820
|
[key]: secretMeta
|
|
1849
1821
|
});
|
|
1850
|
-
return ageEncrypt(
|
|
1822
|
+
return ageEncrypt(value, recipient).mapLeft((err) => ({
|
|
1851
1823
|
_tag: "EncryptFailed",
|
|
1852
1824
|
key,
|
|
1853
1825
|
message: err.message
|
|
@@ -1943,11 +1915,11 @@ const bootSafe = (options) => {
|
|
|
1943
1915
|
const secretEntries = config.secret ?? {};
|
|
1944
1916
|
const envEntries = config.env ?? {};
|
|
1945
1917
|
const nonAliasSecretEntries = Object.fromEntries(Object.entries(secretEntries).filter(([, meta]) => meta.from_key === void 0));
|
|
1946
|
-
const aliasSecretKeys = Object.
|
|
1918
|
+
const aliasSecretKeys = Object.entries(secretEntries).filter(([, meta]) => meta.from_key !== void 0).map(([k]) => k);
|
|
1947
1919
|
const nonAliasEnvEntries = Object.entries(envEntries).filter(([, meta]) => meta.from_key === void 0);
|
|
1948
|
-
const aliasEnvKeys = Object.
|
|
1920
|
+
const aliasEnvKeys = Object.entries(envEntries).filter(([, meta]) => meta.from_key !== void 0).map(([k]) => k);
|
|
1949
1921
|
const nonAliasMetaKeys = Object.keys(nonAliasSecretEntries);
|
|
1950
|
-
const hasSealedValues =
|
|
1922
|
+
const hasSealedValues = Object.values(nonAliasSecretEntries).some((meta) => !!meta.encrypted_value);
|
|
1951
1923
|
const identityKeyResult = resolveIdentityKey(config, configDir);
|
|
1952
1924
|
const identityKey = identityKeyResult.fold(() => Option(void 0), (k) => k);
|
|
1953
1925
|
if (identityKeyResult.isLeft() && !hasSealedValues) return identityKeyResult.fold((err) => Left(err), () => Left({
|
|
@@ -1960,7 +1932,7 @@ const bootSafe = (options) => {
|
|
|
1960
1932
|
const injected = [];
|
|
1961
1933
|
const skipped = [];
|
|
1962
1934
|
warnings.push(...checkEnvMisclassification(config));
|
|
1963
|
-
const envDefaults = Object.fromEntries(nonAliasEnvEntries.flatMap(([key, entry]) => Option(process.env[key]).fold(() => entry.value
|
|
1935
|
+
const envDefaults = Object.fromEntries(nonAliasEnvEntries.flatMap(([key, entry]) => Option(process.env[key]).fold(() => Option(entry.value).fold(() => [], (v) => [[key, v]]), () => [])));
|
|
1964
1936
|
const overridden = nonAliasEnvEntries.flatMap(([key]) => Option(process.env[key]).fold(() => [], () => [key]));
|
|
1965
1937
|
if (inject) Object.entries(envDefaults).forEach(([key, value]) => {
|
|
1966
1938
|
process.env[key] = value;
|
|
@@ -2020,21 +1992,20 @@ const bootSafe = (options) => {
|
|
|
2020
1992
|
aliasSecretKeys.forEach((aliasKey) => {
|
|
2021
1993
|
const entry = aliasTable.entries.get(`secret.${aliasKey}`);
|
|
2022
1994
|
if (!entry) return;
|
|
2023
|
-
|
|
2024
|
-
|
|
2025
|
-
|
|
2026
|
-
injected.push(aliasKey);
|
|
2027
|
-
log.debug("phase.alias.copied", {
|
|
1995
|
+
Option(secrets[entry.targetKey]).fold(() => {
|
|
1996
|
+
skipped.push(aliasKey);
|
|
1997
|
+
log.debug("phase.alias.target_unresolved", {
|
|
2028
1998
|
alias: aliasKey,
|
|
2029
1999
|
target: entry.targetKey
|
|
2030
2000
|
});
|
|
2031
|
-
}
|
|
2032
|
-
|
|
2033
|
-
|
|
2001
|
+
}, (targetValue) => {
|
|
2002
|
+
secrets[aliasKey] = targetValue;
|
|
2003
|
+
injected.push(aliasKey);
|
|
2004
|
+
log.debug("phase.alias.copied", {
|
|
2034
2005
|
alias: aliasKey,
|
|
2035
2006
|
target: entry.targetKey
|
|
2036
2007
|
});
|
|
2037
|
-
}
|
|
2008
|
+
});
|
|
2038
2009
|
});
|
|
2039
2010
|
aliasEnvKeys.forEach((aliasKey) => {
|
|
2040
2011
|
const entry = aliasTable.entries.get(`env.${aliasKey}`);
|
|
@@ -2149,6 +2120,22 @@ const resolveValues = async (keys, profile, agentKey) => {
|
|
|
2149
2120
|
//#region src/core/toml-edit.ts
|
|
2150
2121
|
const SECTION_RE = /^\[.+\]\s*$/;
|
|
2151
2122
|
const MULTILINE_OPEN = "\"\"\"";
|
|
2123
|
+
const scanSectionBoundary = (state, line, i) => {
|
|
2124
|
+
if (state.done) return state;
|
|
2125
|
+
if (state.inMultiline) return line.includes(MULTILINE_OPEN) ? {
|
|
2126
|
+
...state,
|
|
2127
|
+
inMultiline: false
|
|
2128
|
+
} : state;
|
|
2129
|
+
if (line.includes(MULTILINE_OPEN)) return (line.slice(line.indexOf("=") + 1).trim().match(/* @__PURE__ */ new RegExp("\"\"\"", "g")) ?? []).length === 1 ? {
|
|
2130
|
+
...state,
|
|
2131
|
+
inMultiline: true
|
|
2132
|
+
} : state;
|
|
2133
|
+
return SECTION_RE.test(line) ? {
|
|
2134
|
+
...state,
|
|
2135
|
+
end: i,
|
|
2136
|
+
done: true
|
|
2137
|
+
} : state;
|
|
2138
|
+
};
|
|
2152
2139
|
/**
|
|
2153
2140
|
* Find the line range [start, end) of a TOML section by its header string.
|
|
2154
2141
|
* The range includes the header line through to (but not including) the next section header or EOF.
|
|
@@ -2157,26 +2144,14 @@ const MULTILINE_OPEN = "\"\"\"";
|
|
|
2157
2144
|
const findSectionRange = (lines, sectionHeader) => {
|
|
2158
2145
|
const start = lines.findIndex((l) => l.trim() === sectionHeader);
|
|
2159
2146
|
if (start === -1) return void 0;
|
|
2160
|
-
|
|
2161
|
-
|
|
2162
|
-
|
|
2163
|
-
|
|
2164
|
-
|
|
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
|
-
}
|
|
2147
|
+
const initial = {
|
|
2148
|
+
end: lines.length,
|
|
2149
|
+
inMultiline: false,
|
|
2150
|
+
done: false
|
|
2151
|
+
};
|
|
2177
2152
|
return {
|
|
2178
2153
|
start,
|
|
2179
|
-
end
|
|
2154
|
+
end: List(lines.slice(start + 1)).zipWithIndex().foldLeft(initial)((state, entry) => scanSectionBoundary(state, entry[0], start + 1 + entry[1])).end
|
|
2180
2155
|
};
|
|
2181
2156
|
};
|
|
2182
2157
|
/** Check whether a section header exists in the raw TOML */
|
|
@@ -2192,12 +2167,10 @@ const removeSection = (raw, sectionHeader) => {
|
|
|
2192
2167
|
_tag: "SectionNotFound",
|
|
2193
2168
|
section: sectionHeader
|
|
2194
2169
|
});
|
|
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
2170
|
const after = lines.slice(range.end);
|
|
2199
|
-
|
|
2200
|
-
const
|
|
2171
|
+
const beforeAll = lines.slice(0, range.start);
|
|
2172
|
+
const lastNonBlank = beforeAll.findLastIndex((l) => l.trim() !== "");
|
|
2173
|
+
const result = [...lastNonBlank === -1 ? [] : beforeAll.slice(0, lastNonBlank + 1), ...after].join("\n");
|
|
2201
2174
|
return Either.right(result);
|
|
2202
2175
|
};
|
|
2203
2176
|
/**
|
|
@@ -2233,55 +2206,47 @@ const updateSectionFields = (raw, sectionHeader, updates) => {
|
|
|
2233
2206
|
const before = lines.slice(0, range.start + 1);
|
|
2234
2207
|
const after = lines.slice(range.end);
|
|
2235
2208
|
const sectionBody = lines.slice(range.start + 1, range.end);
|
|
2236
|
-
const
|
|
2237
|
-
|
|
2238
|
-
|
|
2239
|
-
|
|
2240
|
-
|
|
2241
|
-
|
|
2242
|
-
|
|
2243
|
-
|
|
2244
|
-
|
|
2245
|
-
|
|
2246
|
-
|
|
2247
|
-
} else {
|
|
2248
|
-
if (updates[multilineKey] === null) continue;
|
|
2249
|
-
if (multilineKey in updates) continue;
|
|
2250
|
-
}
|
|
2251
|
-
remaining.push(line);
|
|
2252
|
-
continue;
|
|
2253
|
-
}
|
|
2209
|
+
const findClosingMultiline = (fromIdx) => {
|
|
2210
|
+
const idx = sectionBody.findIndex((l, j) => j > fromIdx && l.includes(MULTILINE_OPEN));
|
|
2211
|
+
return idx === -1 ? sectionBody.length : idx;
|
|
2212
|
+
};
|
|
2213
|
+
const initial = {
|
|
2214
|
+
remaining: [],
|
|
2215
|
+
updatedKeys: Set$1.empty(),
|
|
2216
|
+
skipUntil: -1
|
|
2217
|
+
};
|
|
2218
|
+
const step = (state, line, i) => {
|
|
2219
|
+
if (i <= state.skipUntil) return state;
|
|
2254
2220
|
const eqIdx = line.indexOf("=");
|
|
2255
|
-
|
|
2256
|
-
|
|
2257
|
-
|
|
2258
|
-
|
|
2259
|
-
|
|
2260
|
-
|
|
2261
|
-
|
|
2262
|
-
|
|
2263
|
-
|
|
2264
|
-
|
|
2265
|
-
|
|
2266
|
-
|
|
2267
|
-
|
|
2268
|
-
|
|
2269
|
-
|
|
2270
|
-
|
|
2271
|
-
|
|
2272
|
-
break;
|
|
2273
|
-
}
|
|
2274
|
-
}
|
|
2275
|
-
continue;
|
|
2276
|
-
}
|
|
2221
|
+
const isFieldLine = eqIdx > 0 && !line.trimStart().startsWith("#") && !line.trimStart().startsWith("[");
|
|
2222
|
+
const key = isFieldLine ? line.slice(0, eqIdx).trim() : "";
|
|
2223
|
+
if (isFieldLine && key in updates) {
|
|
2224
|
+
const afterEquals = line.slice(eqIdx + 1).trim();
|
|
2225
|
+
const skipUntil = afterEquals.includes(MULTILINE_OPEN) && (afterEquals.match(/* @__PURE__ */ new RegExp("\"\"\"", "g")) ?? []).length === 1 ? findClosingMultiline(i) : state.skipUntil;
|
|
2226
|
+
const updatedKeys = state.updatedKeys.add(key);
|
|
2227
|
+
const value = updates[key];
|
|
2228
|
+
if (value === null) return {
|
|
2229
|
+
...state,
|
|
2230
|
+
updatedKeys,
|
|
2231
|
+
skipUntil
|
|
2232
|
+
};
|
|
2233
|
+
return {
|
|
2234
|
+
remaining: [...state.remaining, `${key} = ${value}`],
|
|
2235
|
+
updatedKeys,
|
|
2236
|
+
skipUntil
|
|
2237
|
+
};
|
|
2277
2238
|
}
|
|
2278
|
-
|
|
2279
|
-
|
|
2280
|
-
|
|
2281
|
-
|
|
2239
|
+
return {
|
|
2240
|
+
...state,
|
|
2241
|
+
remaining: [...state.remaining, line]
|
|
2242
|
+
};
|
|
2243
|
+
};
|
|
2244
|
+
const final = List(sectionBody).zipWithIndex().foldLeft(initial)((state, entry) => step(state, entry[0], entry[1]));
|
|
2245
|
+
const newFields = Object.entries(updates).filter(([key, value]) => value !== null && !final.updatedKeys.has(key)).map(([key, value]) => `${key} = ${value}`);
|
|
2282
2246
|
const result = [
|
|
2283
2247
|
...before,
|
|
2284
|
-
...remaining,
|
|
2248
|
+
...final.remaining,
|
|
2249
|
+
...newFields,
|
|
2285
2250
|
...after
|
|
2286
2251
|
].join("\n");
|
|
2287
2252
|
return Either.right(result);
|
|
@@ -2294,31 +2259,7 @@ const appendSection = (raw, block) => `${raw.trimEnd()}\n\n${block}`;
|
|
|
2294
2259
|
//#endregion
|
|
2295
2260
|
//#region src/core/fleet.ts
|
|
2296
2261
|
const CONFIG_FILENAME = "envpkt.toml";
|
|
2297
|
-
const SKIP_DIRS =
|
|
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
|
-
]);
|
|
2262
|
+
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
2263
|
function* findEnvpktFiles(dir, maxDepth, currentDepth = 0) {
|
|
2323
2264
|
if (currentDepth > maxDepth) return;
|
|
2324
2265
|
const configPath = join(dir, CONFIG_FILENAME);
|
|
@@ -2585,24 +2526,19 @@ const handleGetSecretMeta = (args) => {
|
|
|
2585
2526
|
const secretEntries = config.secret ?? {};
|
|
2586
2527
|
return Option(secretEntries[key]).fold(() => errorResult(`Secret not found: ${key}`), (meta) => {
|
|
2587
2528
|
const { encrypted_value: _, from_key: fromKey, ...rest } = meta;
|
|
2588
|
-
if (fromKey !== void 0) {
|
|
2589
|
-
|
|
2590
|
-
|
|
2591
|
-
|
|
2592
|
-
|
|
2593
|
-
|
|
2594
|
-
key,
|
|
2595
|
-
...targetRest,
|
|
2596
|
-
...rest,
|
|
2597
|
-
alias_of: fromKey
|
|
2598
|
-
}, null, 2));
|
|
2599
|
-
}
|
|
2529
|
+
if (fromKey !== void 0) return Option(fromKey.match(/^secret\.(.+)$/)?.[1]).flatMap((k) => Option(secretEntries[k])).fold(() => textResult(JSON.stringify({
|
|
2530
|
+
key,
|
|
2531
|
+
...rest,
|
|
2532
|
+
alias_of: fromKey
|
|
2533
|
+
}, null, 2)), (t) => {
|
|
2534
|
+
const { encrypted_value: __, from_key: ___, ...targetRest } = t;
|
|
2600
2535
|
return textResult(JSON.stringify({
|
|
2601
2536
|
key,
|
|
2537
|
+
...targetRest,
|
|
2602
2538
|
...rest,
|
|
2603
2539
|
alias_of: fromKey
|
|
2604
2540
|
}, null, 2));
|
|
2605
|
-
}
|
|
2541
|
+
});
|
|
2606
2542
|
return textResult(JSON.stringify({
|
|
2607
2543
|
key,
|
|
2608
2544
|
...rest
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "envpkt",
|
|
3
|
-
"version": "0.11.
|
|
3
|
+
"version": "0.11.2",
|
|
4
4
|
"description": "Credential lifecycle and fleet management for AI agents",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"credentials",
|
|
@@ -42,16 +42,16 @@
|
|
|
42
42
|
"@modelcontextprotocol/sdk": "^1.29.0",
|
|
43
43
|
"@sinclair/typebox": "^0.34.49",
|
|
44
44
|
"commander": "^14.0.3",
|
|
45
|
-
"functype": "^0.60.
|
|
46
|
-
"functype-log": "^0.60.
|
|
47
|
-
"functype-os": "^0.60.
|
|
45
|
+
"functype": "^0.60.7",
|
|
46
|
+
"functype-log": "^0.60.7",
|
|
47
|
+
"functype-os": "^0.60.7",
|
|
48
48
|
"smol-toml": "^1.6.1"
|
|
49
49
|
},
|
|
50
50
|
"devDependencies": {
|
|
51
|
-
"@types/node": "^24.12.
|
|
52
|
-
"ts-builds": "^2.
|
|
53
|
-
"tsdown": "^0.
|
|
54
|
-
"tsx": "^4.
|
|
51
|
+
"@types/node": "^24.12.4",
|
|
52
|
+
"ts-builds": "^2.8.1",
|
|
53
|
+
"tsdown": "^0.22.0",
|
|
54
|
+
"tsx": "^4.22.3"
|
|
55
55
|
},
|
|
56
56
|
"type": "module",
|
|
57
57
|
"main": "./dist/index.js",
|