envpkt 0.9.1 → 0.10.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 +156 -3
- package/dist/index.js +4 -1
- package/package.json +3 -3
package/dist/cli.js
CHANGED
|
@@ -88,8 +88,11 @@ const computeAudit = (config, fnoxKeys, today, aliasTable) => {
|
|
|
88
88
|
const nonAliasMetaKeys = new Set(nonAliasEntries.map(([k]) => k));
|
|
89
89
|
const nonAliasHealth = nonAliasEntries.map(([key, meta]) => classifySecret(key, meta, keys, staleWarningDays, requireExpiration, requireService, now));
|
|
90
90
|
const healthByKey = new Map(nonAliasHealth.map((h) => [h.key, h]));
|
|
91
|
+
const parseTargetKey = (from_key) => {
|
|
92
|
+
return /^secret\.(.+)$/.exec(from_key)?.[1];
|
|
93
|
+
};
|
|
91
94
|
const aliasHealth = aliasEntries.map(([key, meta]) => {
|
|
92
|
-
const targetKey = (aliasTable?.entries.get(`secret.${key}`))?.targetKey;
|
|
95
|
+
const targetKey = (aliasTable?.entries.get(`secret.${key}`))?.targetKey ?? (meta.from_key !== void 0 ? parseTargetKey(meta.from_key) : void 0);
|
|
93
96
|
const targetHealth = targetKey !== void 0 ? healthByKey.get(targetKey) : void 0;
|
|
94
97
|
const targetRef = meta.from_key ?? (targetKey !== void 0 ? `secret.${targetKey}` : "");
|
|
95
98
|
if (!targetHealth) return {
|
|
@@ -547,12 +550,14 @@ const formatAuditJson = (audit) => JSON.stringify({
|
|
|
547
550
|
missing: audit.missing,
|
|
548
551
|
missing_metadata: audit.missing_metadata,
|
|
549
552
|
orphaned: audit.orphaned,
|
|
553
|
+
aliases: audit.aliases,
|
|
550
554
|
secrets: audit.secrets.map((s) => ({
|
|
551
555
|
key: s.key,
|
|
552
556
|
service: s.service.fold(() => null, (sv) => sv),
|
|
553
557
|
status: s.status,
|
|
554
558
|
days_remaining: s.days_remaining.fold(() => null, (d) => d),
|
|
555
559
|
rotation_url: s.rotation_url.fold(() => null, (u) => u),
|
|
560
|
+
alias_of: s.alias_of.fold(() => null, (a) => a),
|
|
556
561
|
purpose: s.purpose.fold(() => null, (p) => p),
|
|
557
562
|
issues: s.issues.toArray()
|
|
558
563
|
})).toArray()
|
|
@@ -871,9 +876,9 @@ const readFnoxConfig = (path) => Try(() => readFileSync(path, "utf-8")).fold((er
|
|
|
871
876
|
const extractFnoxKeys = (config) => new Set(Object.keys(config.secrets));
|
|
872
877
|
//#endregion
|
|
873
878
|
//#region src/core/alias.ts
|
|
874
|
-
const ALIAS_REF_RE = /^(secret|env)\.(.+)$/;
|
|
879
|
+
const ALIAS_REF_RE$2 = /^(secret|env)\.(.+)$/;
|
|
875
880
|
const parseRef = (raw) => {
|
|
876
|
-
const match = ALIAS_REF_RE.exec(raw);
|
|
881
|
+
const match = ALIAS_REF_RE$2.exec(raw);
|
|
877
882
|
if (!match) return Option(void 0);
|
|
878
883
|
const kind = match[1];
|
|
879
884
|
const key = match[2];
|
|
@@ -2535,6 +2540,77 @@ const runEnvRm = (name, options) => {
|
|
|
2535
2540
|
});
|
|
2536
2541
|
});
|
|
2537
2542
|
};
|
|
2543
|
+
const ALIAS_REF_RE$1 = /^(secret|env)\.(.+)$/;
|
|
2544
|
+
const buildEnvAliasBlock = (name, options) => {
|
|
2545
|
+
const lines = [`[env.${name}]`, `from_key = "${options.from}"`];
|
|
2546
|
+
if (options.purpose) lines.push(`purpose = "${options.purpose}"`);
|
|
2547
|
+
if (options.comment) lines.push(`comment = "${options.comment}"`);
|
|
2548
|
+
if (options.tags) {
|
|
2549
|
+
const pairs = options.tags.split(",").map((pair) => {
|
|
2550
|
+
const [k, v] = pair.split("=").map((s) => s.trim());
|
|
2551
|
+
return `${k} = "${v}"`;
|
|
2552
|
+
});
|
|
2553
|
+
lines.push(`tags = { ${pairs.join(", ")} }`);
|
|
2554
|
+
}
|
|
2555
|
+
return `${lines.join("\n")}\n`;
|
|
2556
|
+
};
|
|
2557
|
+
const runEnvAlias = (name, options) => {
|
|
2558
|
+
const match = ALIAS_REF_RE$1.exec(options.from);
|
|
2559
|
+
if (!match) {
|
|
2560
|
+
console.error(`${RED}Error:${RESET} --from "${options.from}" must be formatted as "secret.<KEY>" or "env.<KEY>"`);
|
|
2561
|
+
process.exit(1);
|
|
2562
|
+
}
|
|
2563
|
+
const [, targetKind, targetKey] = match;
|
|
2564
|
+
if (targetKind !== "env") {
|
|
2565
|
+
console.error(`${RED}Error:${RESET} env alias must point at another env entry — got "${options.from}". Use \`envpkt secret alias\` for secret→secret aliases.`);
|
|
2566
|
+
process.exit(1);
|
|
2567
|
+
}
|
|
2568
|
+
resolveConfigPath(options.config).fold((err) => {
|
|
2569
|
+
console.error(formatError(err));
|
|
2570
|
+
process.exit(2);
|
|
2571
|
+
}, ({ path: configPath, source }) => {
|
|
2572
|
+
const sourceMsg = formatConfigSource(configPath, source);
|
|
2573
|
+
if (sourceMsg) console.error(sourceMsg);
|
|
2574
|
+
loadConfig(configPath).fold((err) => {
|
|
2575
|
+
console.error(formatError(err));
|
|
2576
|
+
process.exit(2);
|
|
2577
|
+
}, (config) => {
|
|
2578
|
+
const envEntries = config.env ?? {};
|
|
2579
|
+
if (name === targetKey) {
|
|
2580
|
+
console.error(`${RED}Error:${RESET} alias "${name}" cannot reference itself`);
|
|
2581
|
+
process.exit(1);
|
|
2582
|
+
}
|
|
2583
|
+
const target = envEntries[targetKey];
|
|
2584
|
+
if (!target) {
|
|
2585
|
+
console.error(`${RED}Error:${RESET} alias target "${options.from}" not found in ${configPath}. Add the target env entry first.`);
|
|
2586
|
+
process.exit(1);
|
|
2587
|
+
}
|
|
2588
|
+
if (target.from_key !== void 0) {
|
|
2589
|
+
console.error(`${RED}Error:${RESET} alias target "${options.from}" is itself an alias. Chained aliases are not supported — point at the canonical entry instead.`);
|
|
2590
|
+
process.exit(1);
|
|
2591
|
+
}
|
|
2592
|
+
const existing = envEntries[name];
|
|
2593
|
+
if (existing) {
|
|
2594
|
+
if (!options.force) {
|
|
2595
|
+
console.error(`${YELLOW}Warning:${RESET} env entry "${name}" already exists in ${configPath} (${existing.from_key ? `currently alias → ${existing.from_key}` : "currently a regular entry"}).`);
|
|
2596
|
+
console.error(` Pass ${BOLD}--force${RESET} to overwrite, or use a different name.`);
|
|
2597
|
+
process.exit(1);
|
|
2598
|
+
}
|
|
2599
|
+
console.error(`${YELLOW}Warning:${RESET} overwriting existing env entry "${name}" (${existing.from_key ? `was alias → ${existing.from_key}` : "was a regular entry"})`);
|
|
2600
|
+
}
|
|
2601
|
+
const block = buildEnvAliasBlock(name, options);
|
|
2602
|
+
if (options.dryRun) {
|
|
2603
|
+
console.log(`${DIM}# Preview (--dry-run):${RESET}\n`);
|
|
2604
|
+
if (existing) console.log(`${DIM}# (would replace existing [env.${name}] block)${RESET}\n`);
|
|
2605
|
+
console.log(block);
|
|
2606
|
+
return;
|
|
2607
|
+
}
|
|
2608
|
+
const raw = readFileSync(configPath, "utf-8");
|
|
2609
|
+
writeFileSync(configPath, appendSection(existing ? removeSection(raw, `[env.${name}]`).fold(() => raw, (r) => r) : raw, block), "utf-8");
|
|
2610
|
+
console.log(`${GREEN}✓${RESET} Aliased ${BOLD}${name}${RESET} → ${BOLD}${options.from}${RESET} in ${CYAN}${configPath}${RESET}`);
|
|
2611
|
+
});
|
|
2612
|
+
});
|
|
2613
|
+
};
|
|
2538
2614
|
const runEnvRename = (oldName, newName, options) => {
|
|
2539
2615
|
withConfig$1(Option(options.config), (configPath, raw) => {
|
|
2540
2616
|
renameSection(raw, `[env.${oldName}]`, `[env.${newName}]`).fold((err) => {
|
|
@@ -2574,6 +2650,9 @@ const registerEnvCommands = (program) => {
|
|
|
2574
2650
|
env.command("rename").description("Rename an env entry, preserving all fields").argument("<old>", "Current env variable name").argument("<new>", "New env variable name").option("-c, --config <path>", "Path to envpkt.toml").option("--dry-run", "Preview the result without writing").action((oldName, newName, options) => {
|
|
2575
2651
|
runEnvRename(oldName, newName, options);
|
|
2576
2652
|
});
|
|
2653
|
+
env.command("alias").description("Create an alias entry that reuses another env entry's resolved value").argument("<name>", "Alias name (becomes the env var key)").requiredOption("--from <ref>", "Target reference — must be \"env.<KEY>\"").option("-c, --config <path>", "Path to envpkt.toml").option("--purpose <purpose>", "Why this alias exists (local metadata)").option("--comment <comment>", "Free-form annotation").option("--tags <tags>", "Comma-separated key=value tags (e.g. env=prod,team=payments)").option("--force", "Overwrite the entry if <name> already exists").option("--dry-run", "Preview the TOML block without writing").action((name, options) => {
|
|
2654
|
+
runEnvAlias(name, options);
|
|
2655
|
+
});
|
|
2577
2656
|
};
|
|
2578
2657
|
//#endregion
|
|
2579
2658
|
//#region src/cli/commands/exec.ts
|
|
@@ -3799,6 +3878,77 @@ const runSecretRename = (oldName, newName, options) => {
|
|
|
3799
3878
|
});
|
|
3800
3879
|
});
|
|
3801
3880
|
};
|
|
3881
|
+
const ALIAS_REF_RE = /^(secret|env)\.(.+)$/;
|
|
3882
|
+
const buildSecretAliasBlock = (name, options) => {
|
|
3883
|
+
const lines = [`[secret.${name}]`, `from_key = "${options.from}"`];
|
|
3884
|
+
if (options.purpose) lines.push(`purpose = "${options.purpose}"`);
|
|
3885
|
+
if (options.comment) lines.push(`comment = "${options.comment}"`);
|
|
3886
|
+
if (options.tags) {
|
|
3887
|
+
const pairs = options.tags.split(",").map((pair) => {
|
|
3888
|
+
const [k, v] = pair.split("=").map((s) => s.trim());
|
|
3889
|
+
return `${k} = "${v}"`;
|
|
3890
|
+
});
|
|
3891
|
+
lines.push(`tags = { ${pairs.join(", ")} }`);
|
|
3892
|
+
}
|
|
3893
|
+
return `${lines.join("\n")}\n`;
|
|
3894
|
+
};
|
|
3895
|
+
const runSecretAlias = (name, options) => {
|
|
3896
|
+
const match = ALIAS_REF_RE.exec(options.from);
|
|
3897
|
+
if (!match) {
|
|
3898
|
+
console.error(`${RED}Error:${RESET} --from "${options.from}" must be formatted as "secret.<KEY>" or "env.<KEY>"`);
|
|
3899
|
+
process.exit(1);
|
|
3900
|
+
}
|
|
3901
|
+
const [, targetKind, targetKey] = match;
|
|
3902
|
+
if (targetKind !== "secret") {
|
|
3903
|
+
console.error(`${RED}Error:${RESET} secret alias must point at another secret — got "${options.from}". Use \`envpkt env alias\` for env→env aliases.`);
|
|
3904
|
+
process.exit(1);
|
|
3905
|
+
}
|
|
3906
|
+
resolveConfigPath(options.config).fold((err) => {
|
|
3907
|
+
console.error(formatError(err));
|
|
3908
|
+
process.exit(2);
|
|
3909
|
+
}, ({ path: configPath, source }) => {
|
|
3910
|
+
const sourceMsg = formatConfigSource(configPath, source);
|
|
3911
|
+
if (sourceMsg) console.error(sourceMsg);
|
|
3912
|
+
loadConfig(configPath).fold((err) => {
|
|
3913
|
+
console.error(formatError(err));
|
|
3914
|
+
process.exit(2);
|
|
3915
|
+
}, (config) => {
|
|
3916
|
+
const secrets = config.secret ?? {};
|
|
3917
|
+
if (name === targetKey) {
|
|
3918
|
+
console.error(`${RED}Error:${RESET} alias "${name}" cannot reference itself`);
|
|
3919
|
+
process.exit(1);
|
|
3920
|
+
}
|
|
3921
|
+
const target = secrets[targetKey];
|
|
3922
|
+
if (!target) {
|
|
3923
|
+
console.error(`${RED}Error:${RESET} alias target "${options.from}" not found in ${configPath}. Add the target secret first.`);
|
|
3924
|
+
process.exit(1);
|
|
3925
|
+
}
|
|
3926
|
+
if (target.from_key !== void 0) {
|
|
3927
|
+
console.error(`${RED}Error:${RESET} alias target "${options.from}" is itself an alias. Chained aliases are not supported — point at the canonical entry instead.`);
|
|
3928
|
+
process.exit(1);
|
|
3929
|
+
}
|
|
3930
|
+
const existing = secrets[name];
|
|
3931
|
+
if (existing) {
|
|
3932
|
+
if (!options.force) {
|
|
3933
|
+
console.error(`${YELLOW}Warning:${RESET} secret "${name}" already exists in ${configPath} (${existing.from_key ? `currently alias → ${existing.from_key}` : "currently a regular entry"}).`);
|
|
3934
|
+
console.error(` Pass ${BOLD}--force${RESET} to overwrite, or use a different name.`);
|
|
3935
|
+
process.exit(1);
|
|
3936
|
+
}
|
|
3937
|
+
console.error(`${YELLOW}Warning:${RESET} overwriting existing entry "${name}" (${existing.from_key ? `was alias → ${existing.from_key}` : "was a regular entry"})`);
|
|
3938
|
+
}
|
|
3939
|
+
const block = buildSecretAliasBlock(name, options);
|
|
3940
|
+
if (options.dryRun) {
|
|
3941
|
+
console.log(`${DIM}# Preview (--dry-run):${RESET}\n`);
|
|
3942
|
+
if (existing) console.log(`${DIM}# (would replace existing [secret.${name}] block)${RESET}\n`);
|
|
3943
|
+
console.log(block);
|
|
3944
|
+
return;
|
|
3945
|
+
}
|
|
3946
|
+
const raw = readFileSync(configPath, "utf-8");
|
|
3947
|
+
writeFileSync(configPath, appendSection(existing ? removeSection(raw, `[secret.${name}]`).fold(() => raw, (r) => r) : raw, block), "utf-8");
|
|
3948
|
+
console.log(`${GREEN}✓${RESET} Aliased ${BOLD}${name}${RESET} → ${BOLD}${options.from}${RESET} in ${CYAN}${configPath}${RESET}`);
|
|
3949
|
+
});
|
|
3950
|
+
});
|
|
3951
|
+
};
|
|
3802
3952
|
const addSecretFlags = (cmd) => cmd.option("--service <service>", "Service this secret authenticates to").option("--purpose <purpose>", "Why this secret exists").option("--comment <comment>", "Free-form annotation").option("--expires <date>", "Expiration date (YYYY-MM-DD)").option("--capabilities <caps>", "Comma-separated capabilities (e.g. read,write)").option("--rotates <schedule>", "Rotation schedule (e.g. 90d, quarterly)").option("--rate-limit <limit>", "Rate limit info (e.g. 1000/min)").option("--model-hint <hint>", "Suggested model or tier").option("--source <source>", "Where the value originates (e.g. vault, ci)").option("--rotation-url <url>", "URL for secret rotation procedure").option("--tags <tags>", "Comma-separated key=value tags (e.g. env=prod,team=payments)");
|
|
3803
3953
|
const registerSecretCommands = (program) => {
|
|
3804
3954
|
const secret = program.command("secret").description("Manage secret entries in envpkt.toml");
|
|
@@ -3814,6 +3964,9 @@ const registerSecretCommands = (program) => {
|
|
|
3814
3964
|
secret.command("rename").description("Rename a secret entry, preserving all metadata").argument("<old>", "Current secret name").argument("<new>", "New secret name").option("-c, --config <path>", "Path to envpkt.toml").option("--dry-run", "Preview the result without writing").action((oldName, newName, options) => {
|
|
3815
3965
|
runSecretRename(oldName, newName, options);
|
|
3816
3966
|
});
|
|
3967
|
+
secret.command("alias").description("Create an alias entry that reuses another secret's resolved value").argument("<name>", "Alias name (becomes the env var key)").requiredOption("--from <ref>", "Target reference — must be \"secret.<KEY>\"").option("-c, --config <path>", "Path to envpkt.toml").option("--purpose <purpose>", "Why this alias exists (local metadata)").option("--comment <comment>", "Free-form annotation").option("--tags <tags>", "Comma-separated key=value tags (e.g. env=prod,team=payments)").option("--force", "Overwrite the entry if <name> already exists").option("--dry-run", "Preview the TOML block without writing").action((name, options) => {
|
|
3968
|
+
runSecretAlias(name, options);
|
|
3969
|
+
});
|
|
3817
3970
|
};
|
|
3818
3971
|
//#endregion
|
|
3819
3972
|
//#region src/cli/commands/shell-hook.ts
|
package/dist/index.js
CHANGED
|
@@ -650,8 +650,11 @@ const computeAudit = (config, fnoxKeys, today, aliasTable) => {
|
|
|
650
650
|
const nonAliasMetaKeys = new Set(nonAliasEntries.map(([k]) => k));
|
|
651
651
|
const nonAliasHealth = nonAliasEntries.map(([key, meta]) => classifySecret(key, meta, keys, staleWarningDays, requireExpiration, requireService, now));
|
|
652
652
|
const healthByKey = new Map(nonAliasHealth.map((h) => [h.key, h]));
|
|
653
|
+
const parseTargetKey = (from_key) => {
|
|
654
|
+
return /^secret\.(.+)$/.exec(from_key)?.[1];
|
|
655
|
+
};
|
|
653
656
|
const aliasHealth = aliasEntries.map(([key, meta]) => {
|
|
654
|
-
const targetKey = (aliasTable?.entries.get(`secret.${key}`))?.targetKey;
|
|
657
|
+
const targetKey = (aliasTable?.entries.get(`secret.${key}`))?.targetKey ?? (meta.from_key !== void 0 ? parseTargetKey(meta.from_key) : void 0);
|
|
655
658
|
const targetHealth = targetKey !== void 0 ? healthByKey.get(targetKey) : void 0;
|
|
656
659
|
const targetRef = meta.from_key ?? (targetKey !== void 0 ? `secret.${targetKey}` : "");
|
|
657
660
|
if (!targetHealth) return {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "envpkt",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.10.2",
|
|
4
4
|
"description": "Credential lifecycle and fleet management for AI agents",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"credentials",
|
|
@@ -42,8 +42,8 @@
|
|
|
42
42
|
"@modelcontextprotocol/sdk": "^1.29.0",
|
|
43
43
|
"@sinclair/typebox": "^0.34.49",
|
|
44
44
|
"commander": "^14.0.3",
|
|
45
|
-
"functype": "^0.
|
|
46
|
-
"functype-os": "^0.
|
|
45
|
+
"functype": "^0.60.0",
|
|
46
|
+
"functype-os": "^0.60.0",
|
|
47
47
|
"smol-toml": "^1.6.1"
|
|
48
48
|
},
|
|
49
49
|
"devDependencies": {
|