envpkt 0.9.1 → 0.10.1
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 +150 -2
- package/package.json +3 -3
package/dist/cli.js
CHANGED
|
@@ -871,9 +871,9 @@ const readFnoxConfig = (path) => Try(() => readFileSync(path, "utf-8")).fold((er
|
|
|
871
871
|
const extractFnoxKeys = (config) => new Set(Object.keys(config.secrets));
|
|
872
872
|
//#endregion
|
|
873
873
|
//#region src/core/alias.ts
|
|
874
|
-
const ALIAS_REF_RE = /^(secret|env)\.(.+)$/;
|
|
874
|
+
const ALIAS_REF_RE$2 = /^(secret|env)\.(.+)$/;
|
|
875
875
|
const parseRef = (raw) => {
|
|
876
|
-
const match = ALIAS_REF_RE.exec(raw);
|
|
876
|
+
const match = ALIAS_REF_RE$2.exec(raw);
|
|
877
877
|
if (!match) return Option(void 0);
|
|
878
878
|
const kind = match[1];
|
|
879
879
|
const key = match[2];
|
|
@@ -2535,6 +2535,77 @@ const runEnvRm = (name, options) => {
|
|
|
2535
2535
|
});
|
|
2536
2536
|
});
|
|
2537
2537
|
};
|
|
2538
|
+
const ALIAS_REF_RE$1 = /^(secret|env)\.(.+)$/;
|
|
2539
|
+
const buildEnvAliasBlock = (name, options) => {
|
|
2540
|
+
const lines = [`[env.${name}]`, `from_key = "${options.from}"`];
|
|
2541
|
+
if (options.purpose) lines.push(`purpose = "${options.purpose}"`);
|
|
2542
|
+
if (options.comment) lines.push(`comment = "${options.comment}"`);
|
|
2543
|
+
if (options.tags) {
|
|
2544
|
+
const pairs = options.tags.split(",").map((pair) => {
|
|
2545
|
+
const [k, v] = pair.split("=").map((s) => s.trim());
|
|
2546
|
+
return `${k} = "${v}"`;
|
|
2547
|
+
});
|
|
2548
|
+
lines.push(`tags = { ${pairs.join(", ")} }`);
|
|
2549
|
+
}
|
|
2550
|
+
return `${lines.join("\n")}\n`;
|
|
2551
|
+
};
|
|
2552
|
+
const runEnvAlias = (name, options) => {
|
|
2553
|
+
const match = ALIAS_REF_RE$1.exec(options.from);
|
|
2554
|
+
if (!match) {
|
|
2555
|
+
console.error(`${RED}Error:${RESET} --from "${options.from}" must be formatted as "secret.<KEY>" or "env.<KEY>"`);
|
|
2556
|
+
process.exit(1);
|
|
2557
|
+
}
|
|
2558
|
+
const [, targetKind, targetKey] = match;
|
|
2559
|
+
if (targetKind !== "env") {
|
|
2560
|
+
console.error(`${RED}Error:${RESET} env alias must point at another env entry — got "${options.from}". Use \`envpkt secret alias\` for secret→secret aliases.`);
|
|
2561
|
+
process.exit(1);
|
|
2562
|
+
}
|
|
2563
|
+
resolveConfigPath(options.config).fold((err) => {
|
|
2564
|
+
console.error(formatError(err));
|
|
2565
|
+
process.exit(2);
|
|
2566
|
+
}, ({ path: configPath, source }) => {
|
|
2567
|
+
const sourceMsg = formatConfigSource(configPath, source);
|
|
2568
|
+
if (sourceMsg) console.error(sourceMsg);
|
|
2569
|
+
loadConfig(configPath).fold((err) => {
|
|
2570
|
+
console.error(formatError(err));
|
|
2571
|
+
process.exit(2);
|
|
2572
|
+
}, (config) => {
|
|
2573
|
+
const envEntries = config.env ?? {};
|
|
2574
|
+
if (name === targetKey) {
|
|
2575
|
+
console.error(`${RED}Error:${RESET} alias "${name}" cannot reference itself`);
|
|
2576
|
+
process.exit(1);
|
|
2577
|
+
}
|
|
2578
|
+
const target = envEntries[targetKey];
|
|
2579
|
+
if (!target) {
|
|
2580
|
+
console.error(`${RED}Error:${RESET} alias target "${options.from}" not found in ${configPath}. Add the target env entry first.`);
|
|
2581
|
+
process.exit(1);
|
|
2582
|
+
}
|
|
2583
|
+
if (target.from_key !== void 0) {
|
|
2584
|
+
console.error(`${RED}Error:${RESET} alias target "${options.from}" is itself an alias. Chained aliases are not supported — point at the canonical entry instead.`);
|
|
2585
|
+
process.exit(1);
|
|
2586
|
+
}
|
|
2587
|
+
const existing = envEntries[name];
|
|
2588
|
+
if (existing) {
|
|
2589
|
+
if (!options.force) {
|
|
2590
|
+
console.error(`${YELLOW}Warning:${RESET} env entry "${name}" already exists in ${configPath} (${existing.from_key ? `currently alias → ${existing.from_key}` : "currently a regular entry"}).`);
|
|
2591
|
+
console.error(` Pass ${BOLD}--force${RESET} to overwrite, or use a different name.`);
|
|
2592
|
+
process.exit(1);
|
|
2593
|
+
}
|
|
2594
|
+
console.error(`${YELLOW}Warning:${RESET} overwriting existing env entry "${name}" (${existing.from_key ? `was alias → ${existing.from_key}` : "was a regular entry"})`);
|
|
2595
|
+
}
|
|
2596
|
+
const block = buildEnvAliasBlock(name, options);
|
|
2597
|
+
if (options.dryRun) {
|
|
2598
|
+
console.log(`${DIM}# Preview (--dry-run):${RESET}\n`);
|
|
2599
|
+
if (existing) console.log(`${DIM}# (would replace existing [env.${name}] block)${RESET}\n`);
|
|
2600
|
+
console.log(block);
|
|
2601
|
+
return;
|
|
2602
|
+
}
|
|
2603
|
+
const raw = readFileSync(configPath, "utf-8");
|
|
2604
|
+
writeFileSync(configPath, appendSection(existing ? removeSection(raw, `[env.${name}]`).fold(() => raw, (r) => r) : raw, block), "utf-8");
|
|
2605
|
+
console.log(`${GREEN}✓${RESET} Aliased ${BOLD}${name}${RESET} → ${BOLD}${options.from}${RESET} in ${CYAN}${configPath}${RESET}`);
|
|
2606
|
+
});
|
|
2607
|
+
});
|
|
2608
|
+
};
|
|
2538
2609
|
const runEnvRename = (oldName, newName, options) => {
|
|
2539
2610
|
withConfig$1(Option(options.config), (configPath, raw) => {
|
|
2540
2611
|
renameSection(raw, `[env.${oldName}]`, `[env.${newName}]`).fold((err) => {
|
|
@@ -2574,6 +2645,9 @@ const registerEnvCommands = (program) => {
|
|
|
2574
2645
|
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
2646
|
runEnvRename(oldName, newName, options);
|
|
2576
2647
|
});
|
|
2648
|
+
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) => {
|
|
2649
|
+
runEnvAlias(name, options);
|
|
2650
|
+
});
|
|
2577
2651
|
};
|
|
2578
2652
|
//#endregion
|
|
2579
2653
|
//#region src/cli/commands/exec.ts
|
|
@@ -3799,6 +3873,77 @@ const runSecretRename = (oldName, newName, options) => {
|
|
|
3799
3873
|
});
|
|
3800
3874
|
});
|
|
3801
3875
|
};
|
|
3876
|
+
const ALIAS_REF_RE = /^(secret|env)\.(.+)$/;
|
|
3877
|
+
const buildSecretAliasBlock = (name, options) => {
|
|
3878
|
+
const lines = [`[secret.${name}]`, `from_key = "${options.from}"`];
|
|
3879
|
+
if (options.purpose) lines.push(`purpose = "${options.purpose}"`);
|
|
3880
|
+
if (options.comment) lines.push(`comment = "${options.comment}"`);
|
|
3881
|
+
if (options.tags) {
|
|
3882
|
+
const pairs = options.tags.split(",").map((pair) => {
|
|
3883
|
+
const [k, v] = pair.split("=").map((s) => s.trim());
|
|
3884
|
+
return `${k} = "${v}"`;
|
|
3885
|
+
});
|
|
3886
|
+
lines.push(`tags = { ${pairs.join(", ")} }`);
|
|
3887
|
+
}
|
|
3888
|
+
return `${lines.join("\n")}\n`;
|
|
3889
|
+
};
|
|
3890
|
+
const runSecretAlias = (name, options) => {
|
|
3891
|
+
const match = ALIAS_REF_RE.exec(options.from);
|
|
3892
|
+
if (!match) {
|
|
3893
|
+
console.error(`${RED}Error:${RESET} --from "${options.from}" must be formatted as "secret.<KEY>" or "env.<KEY>"`);
|
|
3894
|
+
process.exit(1);
|
|
3895
|
+
}
|
|
3896
|
+
const [, targetKind, targetKey] = match;
|
|
3897
|
+
if (targetKind !== "secret") {
|
|
3898
|
+
console.error(`${RED}Error:${RESET} secret alias must point at another secret — got "${options.from}". Use \`envpkt env alias\` for env→env aliases.`);
|
|
3899
|
+
process.exit(1);
|
|
3900
|
+
}
|
|
3901
|
+
resolveConfigPath(options.config).fold((err) => {
|
|
3902
|
+
console.error(formatError(err));
|
|
3903
|
+
process.exit(2);
|
|
3904
|
+
}, ({ path: configPath, source }) => {
|
|
3905
|
+
const sourceMsg = formatConfigSource(configPath, source);
|
|
3906
|
+
if (sourceMsg) console.error(sourceMsg);
|
|
3907
|
+
loadConfig(configPath).fold((err) => {
|
|
3908
|
+
console.error(formatError(err));
|
|
3909
|
+
process.exit(2);
|
|
3910
|
+
}, (config) => {
|
|
3911
|
+
const secrets = config.secret ?? {};
|
|
3912
|
+
if (name === targetKey) {
|
|
3913
|
+
console.error(`${RED}Error:${RESET} alias "${name}" cannot reference itself`);
|
|
3914
|
+
process.exit(1);
|
|
3915
|
+
}
|
|
3916
|
+
const target = secrets[targetKey];
|
|
3917
|
+
if (!target) {
|
|
3918
|
+
console.error(`${RED}Error:${RESET} alias target "${options.from}" not found in ${configPath}. Add the target secret first.`);
|
|
3919
|
+
process.exit(1);
|
|
3920
|
+
}
|
|
3921
|
+
if (target.from_key !== void 0) {
|
|
3922
|
+
console.error(`${RED}Error:${RESET} alias target "${options.from}" is itself an alias. Chained aliases are not supported — point at the canonical entry instead.`);
|
|
3923
|
+
process.exit(1);
|
|
3924
|
+
}
|
|
3925
|
+
const existing = secrets[name];
|
|
3926
|
+
if (existing) {
|
|
3927
|
+
if (!options.force) {
|
|
3928
|
+
console.error(`${YELLOW}Warning:${RESET} secret "${name}" already exists in ${configPath} (${existing.from_key ? `currently alias → ${existing.from_key}` : "currently a regular entry"}).`);
|
|
3929
|
+
console.error(` Pass ${BOLD}--force${RESET} to overwrite, or use a different name.`);
|
|
3930
|
+
process.exit(1);
|
|
3931
|
+
}
|
|
3932
|
+
console.error(`${YELLOW}Warning:${RESET} overwriting existing entry "${name}" (${existing.from_key ? `was alias → ${existing.from_key}` : "was a regular entry"})`);
|
|
3933
|
+
}
|
|
3934
|
+
const block = buildSecretAliasBlock(name, options);
|
|
3935
|
+
if (options.dryRun) {
|
|
3936
|
+
console.log(`${DIM}# Preview (--dry-run):${RESET}\n`);
|
|
3937
|
+
if (existing) console.log(`${DIM}# (would replace existing [secret.${name}] block)${RESET}\n`);
|
|
3938
|
+
console.log(block);
|
|
3939
|
+
return;
|
|
3940
|
+
}
|
|
3941
|
+
const raw = readFileSync(configPath, "utf-8");
|
|
3942
|
+
writeFileSync(configPath, appendSection(existing ? removeSection(raw, `[secret.${name}]`).fold(() => raw, (r) => r) : raw, block), "utf-8");
|
|
3943
|
+
console.log(`${GREEN}✓${RESET} Aliased ${BOLD}${name}${RESET} → ${BOLD}${options.from}${RESET} in ${CYAN}${configPath}${RESET}`);
|
|
3944
|
+
});
|
|
3945
|
+
});
|
|
3946
|
+
};
|
|
3802
3947
|
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
3948
|
const registerSecretCommands = (program) => {
|
|
3804
3949
|
const secret = program.command("secret").description("Manage secret entries in envpkt.toml");
|
|
@@ -3814,6 +3959,9 @@ const registerSecretCommands = (program) => {
|
|
|
3814
3959
|
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
3960
|
runSecretRename(oldName, newName, options);
|
|
3816
3961
|
});
|
|
3962
|
+
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) => {
|
|
3963
|
+
runSecretAlias(name, options);
|
|
3964
|
+
});
|
|
3817
3965
|
};
|
|
3818
3966
|
//#endregion
|
|
3819
3967
|
//#region src/cli/commands/shell-hook.ts
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "envpkt",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.10.1",
|
|
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": {
|