envpkt 0.13.0 → 0.13.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.
Files changed (2) hide show
  1. package/dist/cli.js +111 -82
  2. package/package.json +1 -1
package/dist/cli.js CHANGED
@@ -662,6 +662,17 @@ const formatError = (error) => {
662
662
  case "AgeNotFound": return `${RED}Error:${RESET} age CLI not found: ${error.message}`;
663
663
  case "DecryptFailed": return `${RED}Error:${RESET} Decrypt failed: ${error.message}`;
664
664
  case "IdentityNotFound": return `${RED}Error:${RESET} Identity file not found: ${error.path}`;
665
+ case "SealKeyUnavailable": {
666
+ const e = error;
667
+ return [
668
+ `${RED}Error:${RESET} ${e.sealedKeys.length} sealed secret(s) can't be decrypted — no age key found.`,
669
+ `${DIM}Searched (in order):${RESET}`,
670
+ e.searched.map((l) => ` • ${l}`).join("\n"),
671
+ `${DIM}Fix one:${RESET}`,
672
+ ` • Restore your key to ~/.envpkt/age-key.txt (or set ENVPKT_AGE_KEY_FILE / ENVPKT_AGE_KEY)`,
673
+ ` • Re-provision from source: envpkt seal --edit <KEY>`
674
+ ].join("\n");
675
+ }
665
676
  case "AuditFailed": return `${RED}Error:${RESET} Audit failed: ${error.message}`;
666
677
  case "CatalogNotFound": return `${RED}Error:${RESET} Catalog not found: ${error.path}`;
667
678
  case "CatalogLoadError": return `${RED}Error:${RESET} Catalog load error: ${error.message}`;
@@ -2886,12 +2897,11 @@ const runEnvExport = (options) => {
2886
2897
  console.error(formatError(err));
2887
2898
  process.exit(2);
2888
2899
  }, (boot) => {
2889
- emitWarnings(boot);
2900
+ if (!options.track) emitWarnings(boot);
2890
2901
  const scope = loadConfig(boot.configPath).fold(() => "exec", (config) => config.scope ?? "exec");
2902
+ const gateSecrets = options.track === true && scope !== "shell";
2891
2903
  const entries = collectEmitEntries(boot);
2892
- const emit = scope === "shell" ? entries : entries.filter((e) => !e.secret);
2893
- const withheld = entries.length - emit.length;
2894
- if (withheld > 0) console.error(`${DIM}${withheld} secret(s) withheld (scope="${scope}") — use \`envpkt exec\` or set top-level scope="shell".${RESET}`);
2904
+ const emit = gateSecrets ? entries.filter((e) => !e.secret) : entries;
2895
2905
  emit.forEach(({ name, value }) => {
2896
2906
  console.log(options.track ? `_ENVPKT_HAD_${name}=\${${name}+1}; _ENVPKT_PREV_${name}="\${${name}-}"; export ${name}='${shellEscape(value)}'` : `export ${name}='${shellEscape(value)}'`);
2897
2907
  });
@@ -3291,6 +3301,8 @@ const runFleet = (options) => {
3291
3301
  //#endregion
3292
3302
  //#region src/cli/commands/init.ts
3293
3303
  const CONFIG_FILENAME = "envpkt.toml";
3304
+ /** Resolve the top-level `scope` to scaffold: explicit --scope wins; --global implies "shell". */
3305
+ const resolveInitScope = (options) => options.scope ?? (options.global ? "shell" : void 0);
3294
3306
  const todayIso = () => (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
3295
3307
  const generateSecretBlock = (key, service) => {
3296
3308
  return `[secret.${key}]
@@ -3315,6 +3327,8 @@ const generateTemplate = (options, fnoxKeys) => {
3315
3327
  lines.push(`#:schema https://raw.githubusercontent.com/jordanburke/envpkt/main/schemas/envpkt.schema.json`);
3316
3328
  lines.push(``);
3317
3329
  lines.push(`version = 1`);
3330
+ const scope = resolveInitScope(options);
3331
+ if (scope) lines.push(`scope = "${scope}" # shell = secrets load ambiently on cd/eval; exec = only via envpkt exec`);
3318
3332
  lines.push(``);
3319
3333
  if (options.catalog) {
3320
3334
  lines.push(`catalog = "${options.catalog}"`);
@@ -3373,6 +3387,10 @@ const formatConfigError = (err) => {
3373
3387
  };
3374
3388
  const runInit = (dir, options) => {
3375
3389
  const outPath = join(dir, CONFIG_FILENAME);
3390
+ if (options.scope !== void 0 && options.scope !== "shell" && options.scope !== "exec") {
3391
+ console.error(`${RED}Error:${RESET} --scope must be "shell" or "exec" (got "${options.scope}")`);
3392
+ process.exit(1);
3393
+ }
3376
3394
  if (existsSync(outPath) && !options.force) {
3377
3395
  console.error(`${RED}Error:${RESET} ${CONFIG_FILENAME} already exists. Use --force to overwrite.`);
3378
3396
  process.exit(1);
@@ -4636,85 +4654,96 @@ const registerSecretCommands = (program) => {
4636
4654
  };
4637
4655
  //#endregion
4638
4656
  //#region src/cli/commands/shell-hook.ts
4639
- const ZSH_HOOK = `# envpkt shell hook — add to ~/.zshrc: eval "$(envpkt shell-hook zsh)"
4640
- # Loads a project envpkt.toml on cd (secrets only for scope="shell" packages), restores the
4641
- # prior environment on leave, and warns on credential health. Use \`envpkt exec\` for scope="exec".
4642
- _envpkt_restore() {
4643
- [[ -n "$_ENVPKT_INJECTED" ]] || return
4644
- local k had prev
4645
- for k in \${(s: :)_ENVPKT_INJECTED}; do
4646
- had="_ENVPKT_HAD_$k"
4647
- prev="_ENVPKT_PREV_$k"
4648
- if [[ -n "\${(P)had}" ]]; then
4649
- export "$k=\${(P)prev}"
4650
- else
4651
- unset "$k"
4652
- fi
4653
- unset "$had" "$prev"
4654
- done
4655
- unset _ENVPKT_INJECTED
4656
- }
4657
-
4658
- _envpkt_chpwd() {
4659
- local cfg
4660
- cfg="$(envpkt config-path 2>/dev/null)"
4661
- [[ "$cfg" == "$_ENVPKT_DIR" ]] && return
4662
- _envpkt_restore
4663
- _ENVPKT_DIR="$cfg"
4664
- [[ -z "$cfg" ]] && return
4665
- eval "$(envpkt env export --track 2>/dev/null)"
4666
- envpkt audit --format minimal 2>/dev/null
4667
- }
4668
-
4669
- autoload -Uz add-zsh-hook
4670
- add-zsh-hook chpwd _envpkt_chpwd
4671
- _envpkt_chpwd
4672
- `;
4673
- const BASH_HOOK = `# envpkt shell hook — add to ~/.bashrc: eval "$(envpkt shell-hook bash)"
4674
- # Loads a project envpkt.toml on cd (secrets only for scope="shell" packages), restores the
4675
- # prior environment on leave, and warns on credential health. Use \`envpkt exec\` for scope="exec".
4676
- _envpkt_restore() {
4677
- [ -n "$_ENVPKT_INJECTED" ] || return
4678
- local k had prev
4679
- for k in $_ENVPKT_INJECTED; do
4680
- had="_ENVPKT_HAD_$k"
4681
- prev="_ENVPKT_PREV_$k"
4682
- if [ -n "\${!had}" ]; then
4683
- export "$k=\${!prev}"
4684
- else
4685
- unset "$k"
4686
- fi
4687
- unset "$had" "$prev"
4688
- done
4689
- unset _ENVPKT_INJECTED
4690
- }
4691
-
4692
- _envpkt_prompt() {
4693
- [ "$PWD" = "$_ENVPKT_PWD" ] && return
4694
- _ENVPKT_PWD="$PWD"
4695
- local cfg
4696
- cfg="$(envpkt config-path 2>/dev/null)"
4697
- [ "$cfg" = "$_ENVPKT_DIR" ] && return
4698
- _envpkt_restore
4699
- _ENVPKT_DIR="$cfg"
4700
- [ -z "$cfg" ] && return
4701
- eval "$(envpkt env export --track 2>/dev/null)"
4702
- envpkt audit --format minimal 2>/dev/null
4703
- }
4704
-
4705
- case "$PROMPT_COMMAND" in
4706
- *_envpkt_prompt*) ;;
4707
- *) PROMPT_COMMAND="_envpkt_prompt\${PROMPT_COMMAND:+;$PROMPT_COMMAND}" ;;
4708
- esac
4709
- _envpkt_prompt
4710
- `;
4711
- const runShellHook = (shell) => {
4657
+ const zshHook = (audit) => [
4658
+ "# envpkt shell hook add to ~/.zshrc: eval \"$(envpkt shell-hook zsh)\"",
4659
+ "# Loads the current directory package on cd, restores the prior env on leave, warns on health.",
4660
+ "_envpkt_restore() {",
4661
+ " [[ -n \"$_ENVPKT_INJECTED\" ]] || return",
4662
+ " local k had prev",
4663
+ " for k in ${(s: :)_ENVPKT_INJECTED}; do",
4664
+ " had=\"_ENVPKT_HAD_$k\"",
4665
+ " prev=\"_ENVPKT_PREV_$k\"",
4666
+ " if [[ -n \"${(P)had}\" ]]; then",
4667
+ " export \"$k=${(P)prev}\"",
4668
+ " else",
4669
+ " unset \"$k\"",
4670
+ " fi",
4671
+ " unset \"$had\" \"$prev\"",
4672
+ " done",
4673
+ " unset _ENVPKT_INJECTED",
4674
+ "}",
4675
+ "",
4676
+ "_envpkt_chpwd() {",
4677
+ " local cfg",
4678
+ " cfg=\"$(envpkt config-path 2>/dev/null)\"",
4679
+ " [[ \"$cfg\" == \"$_ENVPKT_DIR\" ]] && return",
4680
+ " _envpkt_restore",
4681
+ " _ENVPKT_DIR=\"$cfg\"",
4682
+ " [[ -z \"$cfg\" ]] && return",
4683
+ " eval \"$(envpkt env export --track)\"",
4684
+ ...audit ? [" envpkt audit --format minimal 2>/dev/null"] : [],
4685
+ "}",
4686
+ "",
4687
+ "autoload -Uz add-zsh-hook",
4688
+ "add-zsh-hook chpwd _envpkt_chpwd",
4689
+ "_envpkt_chpwd",
4690
+ ""
4691
+ ].join("\n");
4692
+ const bashHook = (audit) => [
4693
+ "# envpkt shell hook add to ~/.bashrc: eval \"$(envpkt shell-hook bash)\"",
4694
+ "# Loads the current directory package on cd, restores the prior env on leave, warns on health.",
4695
+ "_envpkt_restore() {",
4696
+ " [ -n \"$_ENVPKT_INJECTED\" ] || return",
4697
+ " local k had prev",
4698
+ " for k in $_ENVPKT_INJECTED; do",
4699
+ " had=\"_ENVPKT_HAD_$k\"",
4700
+ " prev=\"_ENVPKT_PREV_$k\"",
4701
+ " if [ -n \"${!had}\" ]; then",
4702
+ " export \"$k=${!prev}\"",
4703
+ " else",
4704
+ " unset \"$k\"",
4705
+ " fi",
4706
+ " unset \"$had\" \"$prev\"",
4707
+ " done",
4708
+ " unset _ENVPKT_INJECTED",
4709
+ "}",
4710
+ "",
4711
+ "_envpkt_prompt() {",
4712
+ " [ \"$PWD\" = \"$_ENVPKT_PWD\" ] && return",
4713
+ " _ENVPKT_PWD=\"$PWD\"",
4714
+ " local cfg",
4715
+ " cfg=\"$(envpkt config-path 2>/dev/null)\"",
4716
+ " [ \"$cfg\" = \"$_ENVPKT_DIR\" ] && return",
4717
+ " _envpkt_restore",
4718
+ " _ENVPKT_DIR=\"$cfg\"",
4719
+ " [ -z \"$cfg\" ] && return",
4720
+ " eval \"$(envpkt env export --track)\"",
4721
+ ...audit ? [" envpkt audit --format minimal 2>/dev/null"] : [],
4722
+ "}",
4723
+ "",
4724
+ "# Register on PROMPT_COMMAND, handling both the string and (bash 5.1+) array forms.",
4725
+ "if [[ \"$(declare -p PROMPT_COMMAND 2>/dev/null)\" == \"declare -a\"* ]]; then",
4726
+ " case \" ${PROMPT_COMMAND[*]} \" in",
4727
+ " *\" _envpkt_prompt \"*) ;;",
4728
+ " *) PROMPT_COMMAND+=(_envpkt_prompt) ;;",
4729
+ " esac",
4730
+ "else",
4731
+ " case \"$PROMPT_COMMAND\" in",
4732
+ " *_envpkt_prompt*) ;;",
4733
+ " *) PROMPT_COMMAND=\"_envpkt_prompt${PROMPT_COMMAND:+;$PROMPT_COMMAND}\" ;;",
4734
+ " esac",
4735
+ "fi",
4736
+ "_envpkt_prompt",
4737
+ ""
4738
+ ].join("\n");
4739
+ const runShellHook = (shell, options) => {
4740
+ const audit = options?.audit !== false;
4712
4741
  switch (shell) {
4713
4742
  case "zsh":
4714
- console.log(ZSH_HOOK);
4743
+ console.log(zshHook(audit));
4715
4744
  break;
4716
4745
  case "bash":
4717
- console.log(BASH_HOOK);
4746
+ console.log(bashHook(audit));
4718
4747
  break;
4719
4748
  default:
4720
4749
  console.error(`${RED}Error:${RESET} Unsupported shell: ${shell}. Use "zsh" or "bash".`);
@@ -5011,7 +5040,7 @@ program.name("envpkt").description("Credential lifecycle and fleet management fo
5011
5040
  const pkgPath = findPkgJson(dirname(fileURLToPath(import.meta.url)));
5012
5041
  return pkgPath ? JSON.parse(readFileSync(pkgPath, "utf-8")).version : "0.0.0";
5013
5042
  })());
5014
- program.command("init").description("Initialize a new envpkt.toml in the current directory").option("--from-fnox [path]", "Scaffold from fnox.toml (optionally specify path)").option("--catalog <path>", "Path to shared secret catalog").option("--identity", "Include [identity] section").option("--name <name>", "Identity name (requires --identity)").option("--capabilities <caps>", "Comma-separated capabilities (requires --identity)").option("--expires <date>", "Credential expiration YYYY-MM-DD (requires --identity)").option("--force", "Overwrite existing envpkt.toml").action((options) => {
5043
+ program.command("init").description("Initialize a new envpkt.toml in the current directory").option("--from-fnox [path]", "Scaffold from fnox.toml (optionally specify path)").option("--catalog <path>", "Path to shared secret catalog").option("--identity", "Include [identity] section").option("--name <name>", "Identity name (requires --identity)").option("--capabilities <caps>", "Comma-separated capabilities (requires --identity)").option("--expires <date>", "Credential expiration YYYY-MM-DD (requires --identity)").option("--scope <scope>", "Top-level scope: \"shell\" (secrets load ambiently) or \"exec\" (only via envpkt exec)").option("--global", "Scaffold a global/ambient package (implies scope = \"shell\")").option("--force", "Overwrite existing envpkt.toml").action((options) => {
5015
5044
  runInit(process.cwd(), options);
5016
5045
  });
5017
5046
  program.command("keygen").description("Generate an age keypair for sealing secrets — run this before `seal` if you don't have a key yet").option("-c, --config <path>", "Path to envpkt.toml (updates identity.recipient if found)").option("-o, --output <path>", "Output path for identity file (default: ~/.envpkt/<project>-key.txt)").option("--global", "Write key to the shared default path (~/.envpkt/age-key.txt) instead of a project-specific one").action((options) => {
@@ -5052,8 +5081,8 @@ program.command("upgrade").description("Upgrade envpkt to the latest version (np
5052
5081
  program.command("config-path").description("Print the envpkt.toml path resolved for the current directory (empty if none). Resolve-only — no decryption.").option("-c, --config <path>", "Path to envpkt.toml").action((options) => {
5053
5082
  runConfigPath(options);
5054
5083
  });
5055
- program.command("shell-hook").description("Output shell function for ambient credential warnings on cd combine with env export for full setup").argument("<shell>", "Shell type: zsh | bash").action((shell) => {
5056
- runShellHook(shell);
5084
+ program.command("shell-hook").description("Output a shell hook that loads a project's credentials on cd and restores them on leave").argument("<shell>", "Shell type: zsh | bash").option("--no-audit", "Omit the credential-health audit line from the emitted hook").action((shell, options) => {
5085
+ runShellHook(shell, options);
5057
5086
  });
5058
5087
  program.parse();
5059
5088
  //#endregion
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "envpkt",
3
- "version": "0.13.0",
3
+ "version": "0.13.1",
4
4
  "description": "Credential lifecycle and fleet management for AI agents",
5
5
  "keywords": [
6
6
  "credentials",