envpkt 0.5.0 → 0.6.0

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/README.md CHANGED
@@ -97,7 +97,7 @@ And a more complete one:
97
97
 
98
98
  version = 1
99
99
 
100
- [agent]
100
+ [identity]
101
101
  name = "billing-service"
102
102
  consumer = "agent"
103
103
  description = "Payment processing agent"
@@ -158,13 +158,13 @@ age-keygen -o identity.txt
158
158
  Add the public key to your config and the identity file to `.gitignore`:
159
159
 
160
160
  ```toml
161
- [agent]
161
+ [identity]
162
162
  name = "my-agent"
163
163
  recipient = "age1ql3z7hjy54pw3hyww5ayyfg7zqgvc7w3j2elw8zmrj2kg5sfn9aqmcac8p"
164
- identity = "identity.txt"
164
+ key_file = "identity.txt"
165
165
  ```
166
166
 
167
- The `identity` path supports `~` expansion and environment variables (`$VAR`, `${VAR}`), so you can use paths like `~/keys/identity.txt` or `$KEYS_DIR/identity.txt`. Relative paths are resolved from the config file's directory.
167
+ The `key_file` path supports `~` expansion and environment variables (`$VAR`, `${VAR}`), so you can use paths like `~/keys/identity.txt` or `$KEYS_DIR/identity.txt`. Relative paths are resolved from the config file's directory. When omitted, envpkt falls back to `ENVPKT_AGE_KEY_FILE` env var, then `~/.envpkt/age-key.txt`.
168
168
 
169
169
  ### Seal
170
170
 
@@ -233,7 +233,7 @@ expires = "2026-11-01"
233
233
  version = 1
234
234
  catalog = "../../infra/envpkt.toml"
235
235
 
236
- [agent]
236
+ [identity]
237
237
  name = "data-pipeline"
238
238
  consumer = "agent"
239
239
  secrets = ["DATABASE_URL", "REDIS_URL"]
@@ -255,7 +255,7 @@ This produces a self-contained config with catalog metadata merged in and agent
255
255
 
256
256
  - Each field in the agent's `[secret.KEY]` override **replaces** the catalog field (shallow merge)
257
257
  - Omitted fields keep the catalog value
258
- - `agent.secrets` is the source of truth for which keys the agent needs
258
+ - `identity.secrets` is the source of truth for which keys the agent needs
259
259
 
260
260
  ## How envpkt Compares
261
261
 
@@ -267,7 +267,7 @@ The agentic credential space is splitting into approaches. Here's where envpkt f
267
267
  | **What agents see** | Structured metadata (capabilities, constraints, expiration) | Raw secret values | Nothing (proxy handles it) | Nothing (autofill handles it) | Raw secret values |
268
268
  | **MCP server** | Yes | Yes | No | No | Yes |
269
269
  | **Encryption at rest** | age sealed packets | Git-crypt | N/A (proxy model) | Vault encryption | Vault encryption |
270
- | **Per-agent scoping** | Yes (agent.secrets, capabilities) | Yes (policies) | Yes (proxy rules) | No | Yes (policies) |
270
+ | **Per-agent scoping** | Yes (identity.secrets, capabilities) | Yes (policies) | Yes (proxy rules) | No | Yes (policies) |
271
271
  | **Fleet health monitoring** | Yes (fleet scan, aggregated audit) | No | No | No | No |
272
272
  | **Credential metadata** | Rich (purpose, capabilities, rotation, lifecycle) | Minimal | Minimal | Minimal | Moderate |
273
273
  | **Adoption path** | Scan existing env vars, add metadata incrementally | New secret storage workflow | Proxy configuration | Browser extension | API integration |
@@ -285,9 +285,9 @@ Generate an `envpkt.toml` template in the current directory.
285
285
  ```bash
286
286
  envpkt init # Basic template
287
287
  envpkt init --from-fnox # Scaffold from fnox.toml
288
- envpkt init --agent --name "my-agent" # Include agent identity
288
+ envpkt init --identity --name "my-agent" # Include identity section
289
289
  envpkt init --catalog "../infra/envpkt.toml" # Reference a shared catalog
290
- envpkt init --agent --name "bot" --capabilities "read,write" --expires "2027-01-01"
290
+ envpkt init --identity --name "bot" --capabilities "read,write" --expires "2027-01-01"
291
291
  ```
292
292
 
293
293
  ### `envpkt audit`
@@ -354,13 +354,13 @@ envpkt seal -c path/to/envpkt.toml # Specify config path
354
354
  envpkt seal --profile staging # Use a specific fnox profile for value resolution
355
355
  ```
356
356
 
357
- Requires `agent.recipient` (age public key) in your config. Values are resolved via cascade:
357
+ Requires `identity.recipient` (age public key) in your config. Values are resolved via cascade:
358
358
 
359
359
  1. **fnox** (if available)
360
360
  2. **Environment variables** (e.g. `OPENAI_API_KEY` in your shell)
361
361
  3. **Interactive prompt** (asks you to paste each value)
362
362
 
363
- After sealing, each secret gets an `encrypted_value` field. At boot time, `envpkt exec` or `boot()` automatically decrypts sealed values using the `agent.identity` file.
363
+ After sealing, each secret gets an `encrypted_value` field. At boot time, `envpkt exec` or `boot()` automatically decrypts sealed values using the `identity.key_file` path (or the default `~/.envpkt/age-key.txt`).
364
364
 
365
365
  See [`examples/sealed-agent.toml`](./examples/sealed-agent.toml) for a complete example.
366
366
 
@@ -645,12 +645,12 @@ Each `[secret.<KEY>]` section describes a secret:
645
645
  | **Sealed** | `encrypted_value` | Age-encrypted secret value (safe to commit) |
646
646
  | **Enforcement** | `required`, `tags` | Filtering, grouping, and policy |
647
647
 
648
- ### Agent Identity
648
+ ### Identity
649
649
 
650
- The optional `[agent]` section identifies the AI agent:
650
+ The optional `[identity]` section identifies the consumer of these credentials:
651
651
 
652
652
  ```toml
653
- [agent]
653
+ [identity]
654
654
  name = "data-pipeline-agent"
655
655
  consumer = "agent" # agent | service | developer | ci
656
656
  description = "ETL pipeline processor"
package/dist/cli.js CHANGED
@@ -87,7 +87,7 @@ const computeAudit = (config, fnoxKeys, today) => {
87
87
  missing,
88
88
  missing_metadata,
89
89
  orphaned,
90
- agent: config.agent
90
+ identity: config.identity
91
91
  };
92
92
  };
93
93
  const computeEnvAudit = (config, env = process.env) => {
@@ -125,20 +125,20 @@ const ConsumerType = Type.Union([
125
125
  Type.Literal("developer"),
126
126
  Type.Literal("ci")
127
127
  ], { description: "Classification of the agent's consumer type" });
128
- const AgentIdentitySchema = Type.Object({
129
- name: Type.String({ description: "Agent display name" }),
128
+ const IdentitySchema = Type.Object({
129
+ name: Type.String({ description: "Display name" }),
130
130
  consumer: Type.Optional(ConsumerType),
131
- description: Type.Optional(Type.String({ description: "Agent description or role" })),
132
- capabilities: Type.Optional(Type.Array(Type.String(), { description: "List of capabilities this agent provides" })),
131
+ description: Type.Optional(Type.String({ description: "Description or role" })),
132
+ capabilities: Type.Optional(Type.Array(Type.String(), { description: "List of capabilities this identity provides" })),
133
133
  expires: Type.Optional(Type.String({
134
134
  format: "date",
135
- description: "Agent credential expiration date (YYYY-MM-DD)"
135
+ description: "Credential expiration date (YYYY-MM-DD)"
136
136
  })),
137
- services: Type.Optional(Type.Array(Type.String(), { description: "Service dependencies for this agent" })),
138
- identity: Type.Optional(Type.String({ description: "Path to encrypted agent key file (relative to config directory)" })),
139
- recipient: Type.Optional(Type.String({ description: "Agent's age public key for encryption" })),
140
- secrets: Type.Optional(Type.Array(Type.String(), { description: "Secret keys this agent needs from the catalog" }))
141
- }, { description: "Identity and capabilities of the AI agent using this envpkt" });
137
+ services: Type.Optional(Type.Array(Type.String(), { description: "Service dependencies" })),
138
+ key_file: Type.Optional(Type.String({ description: "Path to age identity file (relative to config directory)" })),
139
+ recipient: Type.Optional(Type.String({ description: "Age public key for encryption" })),
140
+ secrets: Type.Optional(Type.Array(Type.String(), { description: "Secret keys needed from the catalog" }))
141
+ }, { description: "Identity and capabilities of the principal using this envpkt" });
142
142
  const SecretMetaSchema = Type.Object({
143
143
  service: Type.Optional(Type.String({ description: "Service or system this secret authenticates to" })),
144
144
  expires: Type.Optional(Type.String({
@@ -196,7 +196,7 @@ const EnvpktConfigSchema = Type.Object({
196
196
  default: 1
197
197
  }),
198
198
  catalog: Type.Optional(Type.String({ description: "Path to shared secret catalog (relative to this config file)" })),
199
- agent: Type.Optional(AgentIdentitySchema),
199
+ identity: Type.Optional(IdentitySchema),
200
200
  secret: Type.Optional(Type.Record(Type.String(), SecretMetaSchema, { description: "Per-secret metadata keyed by secret name" })),
201
201
  env: Type.Optional(Type.Record(Type.String(), EnvMetaSchema, { description: "Plaintext environment defaults keyed by variable name" })),
202
202
  lifecycle: Type.Optional(LifecycleConfigSchema),
@@ -399,12 +399,12 @@ const resolveConfig = (agentConfig, agentConfigDir) => {
399
399
  overridden: [],
400
400
  warnings: []
401
401
  });
402
- if (!agentConfig.agent?.secrets || agentConfig.agent.secrets.length === 0) return Left({
402
+ if (!agentConfig.identity?.secrets || agentConfig.identity.secrets.length === 0) return Left({
403
403
  _tag: "MissingSecretsList",
404
- message: "Config has 'catalog' but agent.secrets is missing — declare which catalog secrets this agent needs"
404
+ message: "Config has 'catalog' but identity.secrets is missing — declare which catalog secrets this agent needs"
405
405
  });
406
406
  const catalogPath = resolve(agentConfigDir, agentConfig.catalog);
407
- const agentSecrets = agentConfig.agent.secrets;
407
+ const agentSecrets = agentConfig.identity.secrets;
408
408
  const agentSecretEntries = agentConfig.secret ?? {};
409
409
  return loadCatalog(catalogPath).flatMap((catalogConfig) => resolveSecrets(agentSecretEntries, catalogConfig.secret ?? {}, agentSecrets, catalogPath).map((resolvedMeta) => {
410
410
  const merged = [];
@@ -415,16 +415,16 @@ const resolveConfig = (agentConfig, agentConfigDir) => {
415
415
  if (agentSecretEntries[key]) overridden.push(key);
416
416
  }
417
417
  const { catalog: _catalog, ...agentWithoutCatalog } = agentConfig;
418
- const agentIdentity = agentConfig.agent ? (() => {
419
- const { secrets: _secrets, ...rest } = agentConfig.agent;
418
+ const identityData = agentConfig.identity ? (() => {
419
+ const { secrets: _secrets, ...rest } = agentConfig.identity;
420
420
  return rest;
421
421
  })() : void 0;
422
422
  return {
423
423
  config: {
424
424
  ...agentWithoutCatalog,
425
- agent: agentIdentity ? {
426
- ...agentIdentity,
427
- name: agentIdentity.name
425
+ identity: identityData ? {
426
+ ...identityData,
427
+ name: identityData.name
428
428
  } : void 0,
429
429
  secret: resolvedMeta
430
430
  },
@@ -521,9 +521,9 @@ const formatFleetJson = (fleet) => JSON.stringify({
521
521
  expiring_soon: fleet.expiring_soon,
522
522
  agents: fleet.agents.map((a) => ({
523
523
  path: a.path,
524
- name: a.agent?.name ?? null,
525
- consumer: a.agent?.consumer ?? null,
526
- description: a.agent?.description ?? null,
524
+ name: a.identity?.name ?? null,
525
+ consumer: a.identity?.consumer ?? null,
526
+ description: a.identity?.description ?? null,
527
527
  status: a.audit.status,
528
528
  secrets: a.audit.total
529
529
  })).toArray()
@@ -820,6 +820,106 @@ const readFnoxConfig = (path) => Try(() => readFileSync(path, "utf-8")).fold((er
820
820
  /** Extract the set of secret key names from a parsed fnox config */
821
821
  const extractFnoxKeys = (config) => new Set(Object.keys(config.secrets));
822
822
 
823
+ //#endregion
824
+ //#region src/core/keygen.ts
825
+ /** Resolve the age identity file path: ENVPKT_AGE_KEY_FILE env var > ~/.envpkt/age-key.txt */
826
+ const resolveKeyPath = () => process.env["ENVPKT_AGE_KEY_FILE"] ?? join(homedir(), ".envpkt", "age-key.txt");
827
+ /** Generate an age keypair and write to disk */
828
+ const generateKeypair = (options) => {
829
+ if (!ageAvailable()) return Left({
830
+ _tag: "AgeNotFound",
831
+ message: "age-keygen CLI not found on PATH. Install age: https://github.com/FiloSottile/age"
832
+ });
833
+ const outputPath = options?.outputPath ?? resolveKeyPath();
834
+ if (existsSync(outputPath) && !options?.force) return Left({
835
+ _tag: "KeyExists",
836
+ path: outputPath
837
+ });
838
+ return Try(() => execFileSync("age-keygen", [], {
839
+ stdio: [
840
+ "pipe",
841
+ "pipe",
842
+ "pipe"
843
+ ],
844
+ encoding: "utf-8"
845
+ })).fold((err) => Left({
846
+ _tag: "KeygenFailed",
847
+ message: `age-keygen failed: ${err}`
848
+ }), (output) => {
849
+ const recipientLine = output.split("\n").find((l) => l.startsWith("# public key:"));
850
+ if (!recipientLine) return Left({
851
+ _tag: "KeygenFailed",
852
+ message: "Could not parse public key from age-keygen output"
853
+ });
854
+ const recipient = recipientLine.replace("# public key: ", "").trim();
855
+ const dir = dirname(outputPath);
856
+ const mkdirFailed = Try(() => {
857
+ if (!existsSync(dir)) mkdirSync(dir, { recursive: true });
858
+ }).fold((err) => ({
859
+ _tag: "WriteError",
860
+ message: `Failed to create directory ${dir}: ${err}`
861
+ }), () => void 0);
862
+ if (mkdirFailed) return Left(mkdirFailed);
863
+ return Try(() => {
864
+ writeFileSync(outputPath, output, { mode: 384 });
865
+ chmodSync(outputPath, 384);
866
+ }).fold((err) => Left({
867
+ _tag: "WriteError",
868
+ message: `Failed to write identity file: ${err}`
869
+ }), () => Right({
870
+ recipient,
871
+ identityPath: outputPath,
872
+ configUpdated: false
873
+ }));
874
+ });
875
+ };
876
+ /** Update identity.recipient in an envpkt.toml file, preserving structure */
877
+ const updateConfigRecipient = (configPath, recipient) => {
878
+ return Try(() => readFileSync(configPath, "utf-8")).fold((err) => Left({
879
+ _tag: "ConfigUpdateError",
880
+ message: `Failed to read config: ${err}`
881
+ }), (raw) => {
882
+ const lines = raw.split("\n");
883
+ const output = [];
884
+ let inIdentitySection = false;
885
+ let recipientUpdated = false;
886
+ let hasIdentitySection = false;
887
+ for (const line of lines) {
888
+ if (/^\[identity\]\s*$/.test(line)) {
889
+ inIdentitySection = true;
890
+ hasIdentitySection = true;
891
+ output.push(line);
892
+ continue;
893
+ }
894
+ if (/^\[/.test(line) && !/^\[identity\]\s*$/.test(line)) {
895
+ if (inIdentitySection && !recipientUpdated) {
896
+ output.push(`recipient = "${recipient}"`);
897
+ recipientUpdated = true;
898
+ }
899
+ inIdentitySection = false;
900
+ output.push(line);
901
+ continue;
902
+ }
903
+ if (inIdentitySection && /^recipient\s*=/.test(line)) {
904
+ output.push(`recipient = "${recipient}"`);
905
+ recipientUpdated = true;
906
+ continue;
907
+ }
908
+ output.push(line);
909
+ }
910
+ if (inIdentitySection && !recipientUpdated) output.push(`recipient = "${recipient}"`);
911
+ if (!hasIdentitySection) {
912
+ output.push("");
913
+ output.push("[identity]");
914
+ output.push(`recipient = "${recipient}"`);
915
+ }
916
+ return Try(() => writeFileSync(configPath, output.join("\n"))).fold((err) => Left({
917
+ _tag: "ConfigUpdateError",
918
+ message: `Failed to write config: ${err}`
919
+ }), () => Right(true));
920
+ });
921
+ };
922
+
823
923
  //#endregion
824
924
  //#region src/core/seal.ts
825
925
  /** Encrypt a plaintext string using age with the given recipient public key (armored output) */
@@ -931,9 +1031,17 @@ const resolveAndLoad = (opts) => resolveConfigPath(opts.configPath).fold((err) =
931
1031
  configSource
932
1032
  }));
933
1033
  }));
934
- const resolveAgentKey = (config, configDir) => {
935
- if (!config.agent?.identity) return Right(void 0);
936
- return unwrapAgentKey(resolve(configDir, expandPath(config.agent.identity))).fold((err) => Left(err), (key) => Right(key));
1034
+ /** Resolve identity file path with explicit fallback control */
1035
+ const resolveIdentityFilePath = (config, configDir, useDefaultFallback) => {
1036
+ if (config.identity?.key_file) return resolve(configDir, expandPath(config.identity.key_file));
1037
+ if (!useDefaultFallback) return void 0;
1038
+ const defaultPath = resolveKeyPath();
1039
+ return existsSync(defaultPath) ? defaultPath : void 0;
1040
+ };
1041
+ const resolveIdentityKey = (config, configDir) => {
1042
+ const identityPath = resolveIdentityFilePath(config, configDir, false);
1043
+ if (!identityPath) return Right(void 0);
1044
+ return unwrapAgentKey(identityPath).fold((err) => Left(err), (key) => Right(key));
937
1045
  };
938
1046
  const detectFnoxKeys = (configDir) => detectFnox(configDir).fold(() => /* @__PURE__ */ new Set(), (fnoxPath) => readFnoxConfig(fnoxPath).fold(() => /* @__PURE__ */ new Set(), (fnoxConfig) => extractFnoxKeys(fnoxConfig)));
939
1047
  const checkExpiration = (audit, failOnExpired, warnOnly) => {
@@ -976,10 +1084,10 @@ const bootSafe = (options) => {
976
1084
  const secretEntries = config.secret ?? {};
977
1085
  const metaKeys = Object.keys(secretEntries);
978
1086
  const hasSealedValues = metaKeys.some((k) => !!secretEntries[k]?.encrypted_value);
979
- const agentKeyResult = resolveAgentKey(config, configDir);
980
- const agentKey = agentKeyResult.fold(() => void 0, (k) => k);
981
- const agentKeyError = agentKeyResult.fold((err) => err, () => void 0);
982
- if (agentKeyError && !hasSealedValues) return Left(agentKeyError);
1087
+ const identityKeyResult = resolveIdentityKey(config, configDir);
1088
+ const identityKey = identityKeyResult.fold(() => void 0, (k) => k);
1089
+ const identityKeyError = identityKeyResult.fold((err) => err, () => void 0);
1090
+ if (identityKeyError && !hasSealedValues) return Left(identityKeyError);
983
1091
  const audit = computeAudit(config, detectFnoxKeys(configDir));
984
1092
  return checkExpiration(audit, failOnExpired, warnOnly).map((warnings) => {
985
1093
  const secrets = {};
@@ -994,7 +1102,8 @@ const bootSafe = (options) => {
994
1102
  if (inject) process.env[key] = entry.value;
995
1103
  } else overridden.push(key);
996
1104
  const sealedKeys = /* @__PURE__ */ new Set();
997
- if (hasSealedValues && config.agent?.identity) unsealSecrets(secretEntries, resolve(configDir, expandPath(config.agent.identity))).fold((err) => {
1105
+ const identityFilePath = resolveIdentityFilePath(config, configDir, true);
1106
+ if (hasSealedValues && identityFilePath) unsealSecrets(secretEntries, identityFilePath).fold((err) => {
998
1107
  warnings.push(`Sealed value decryption failed: ${err.message}`);
999
1108
  }, (unsealed) => {
1000
1109
  for (const [key, value] of Object.entries(unsealed)) {
@@ -1004,7 +1113,7 @@ const bootSafe = (options) => {
1004
1113
  }
1005
1114
  });
1006
1115
  const remainingKeys = metaKeys.filter((k) => !sealedKeys.has(k));
1007
- if (remainingKeys.length > 0) if (fnoxAvailable()) fnoxExport(opts.profile, agentKey).fold((err) => {
1116
+ if (remainingKeys.length > 0) if (fnoxAvailable()) fnoxExport(opts.profile, identityKey).fold((err) => {
1008
1117
  warnings.push(`fnox export failed: ${err.message}`);
1009
1118
  for (const key of remainingKeys) skipped.push(key);
1010
1119
  }, (exported) => {
@@ -2005,7 +2114,7 @@ const scanFleet = (rootDir, options) => {
2005
2114
  const audit = computeAudit(config);
2006
2115
  agents.push({
2007
2116
  path: configPath,
2008
- agent: config.agent,
2117
+ identity: config.identity,
2009
2118
  min_expiry_days: audit.secrets.toArray().reduce((min, s) => s.days_remaining.fold(() => min, (d) => min === void 0 ? d : Math.min(min, d)), void 0),
2010
2119
  audit
2011
2120
  });
@@ -2050,7 +2159,7 @@ const runFleet = (options) => {
2050
2159
  if (fleet.expiring_soon > 0) console.log(` ${YELLOW}${fleet.expiring_soon}${RESET} expiring soon`);
2051
2160
  console.log("");
2052
2161
  for (const agent of agents) {
2053
- const name = agent.agent?.name ? BOLD + agent.agent.name + RESET : DIM + agent.path + RESET;
2162
+ const name = agent.identity?.name ? BOLD + agent.identity.name + RESET : DIM + agent.path + RESET;
2054
2163
  const icon = statusIcon(agent.audit.status);
2055
2164
  console.log(` ${icon} ${name} ${DIM}(${agent.audit.total} secrets)${RESET}`);
2056
2165
  }
@@ -2073,8 +2182,8 @@ created = "${todayIso()}"
2073
2182
  # tags = {}
2074
2183
  `;
2075
2184
  };
2076
- const generateAgentSection = (name, capabilities, expires) => {
2077
- return `[agent]
2185
+ const generateIdentitySection = (name, capabilities, expires) => {
2186
+ return `[identity]
2078
2187
  name = "${name}"
2079
2188
  # consumer = "agent" # agent | service | developer | ci${capabilities ? `\ncapabilities = [${capabilities.split(",").map((c) => `"${c.trim()}"`).join(", ")}]` : ""}${expires ? `\nexpires = "${expires}"` : ""}
2080
2189
  `;
@@ -2089,8 +2198,8 @@ const generateTemplate = (options, fnoxKeys) => {
2089
2198
  lines.push(`catalog = "${options.catalog}"`);
2090
2199
  lines.push(``);
2091
2200
  }
2092
- if (options.agent && options.name) {
2093
- lines.push(generateAgentSection(options.name, options.capabilities, options.expires));
2201
+ if (options.identity && options.name) {
2202
+ lines.push(generateIdentitySection(options.name, options.capabilities, options.expires));
2094
2203
  if (options.catalog) lines.push(`secrets = [] # Add catalog secret keys this agent needs`);
2095
2204
  lines.push(``);
2096
2205
  }
@@ -2203,14 +2312,14 @@ const printConfig = (config, path, resolveResult, opts) => {
2203
2312
  if (resolveResult?.catalogPath) console.log(`${DIM}Catalog: ${CYAN}${resolveResult.catalogPath}${RESET}`);
2204
2313
  console.log(`version: ${config.version}`);
2205
2314
  console.log("");
2206
- if (config.agent) {
2207
- console.log(`${BOLD}Agent:${RESET} ${config.agent.name}`);
2208
- if (config.agent.consumer) console.log(` consumer: ${config.agent.consumer}`);
2209
- if (config.agent.description) console.log(` description: ${config.agent.description}`);
2210
- if (config.agent.capabilities) console.log(` capabilities: ${config.agent.capabilities.join(", ")}`);
2211
- if (config.agent.expires) console.log(` expires: ${config.agent.expires}`);
2212
- if (config.agent.services) console.log(` services: ${config.agent.services.join(", ")}`);
2213
- if (config.agent.secrets) console.log(` secrets: ${config.agent.secrets.join(", ")}`);
2315
+ if (config.identity) {
2316
+ console.log(`${BOLD}Identity:${RESET} ${config.identity.name}`);
2317
+ if (config.identity.consumer) console.log(` consumer: ${config.identity.consumer}`);
2318
+ if (config.identity.description) console.log(` description: ${config.identity.description}`);
2319
+ if (config.identity.capabilities) console.log(` capabilities: ${config.identity.capabilities.join(", ")}`);
2320
+ if (config.identity.expires) console.log(` expires: ${config.identity.expires}`);
2321
+ if (config.identity.services) console.log(` services: ${config.identity.services.join(", ")}`);
2322
+ if (config.identity.secrets) console.log(` secrets: ${config.identity.secrets.join(", ")}`);
2214
2323
  console.log("");
2215
2324
  }
2216
2325
  const secretEntries = config.secret ?? {};
@@ -2284,106 +2393,6 @@ const runInspect = (options) => {
2284
2393
  });
2285
2394
  };
2286
2395
 
2287
- //#endregion
2288
- //#region src/core/keygen.ts
2289
- /** Resolve the age identity file path: ENVPKT_AGE_KEY_FILE env var > ~/.envpkt/age-key.txt */
2290
- const resolveKeyPath = () => process.env["ENVPKT_AGE_KEY_FILE"] ?? join(homedir(), ".envpkt", "age-key.txt");
2291
- /** Generate an age keypair and write to disk */
2292
- const generateKeypair = (options) => {
2293
- if (!ageAvailable()) return Left({
2294
- _tag: "AgeNotFound",
2295
- message: "age-keygen CLI not found on PATH. Install age: https://github.com/FiloSottile/age"
2296
- });
2297
- const outputPath = options?.outputPath ?? resolveKeyPath();
2298
- if (existsSync(outputPath) && !options?.force) return Left({
2299
- _tag: "KeyExists",
2300
- path: outputPath
2301
- });
2302
- return Try(() => execFileSync("age-keygen", [], {
2303
- stdio: [
2304
- "pipe",
2305
- "pipe",
2306
- "pipe"
2307
- ],
2308
- encoding: "utf-8"
2309
- })).fold((err) => Left({
2310
- _tag: "KeygenFailed",
2311
- message: `age-keygen failed: ${err}`
2312
- }), (output) => {
2313
- const recipientLine = output.split("\n").find((l) => l.startsWith("# public key:"));
2314
- if (!recipientLine) return Left({
2315
- _tag: "KeygenFailed",
2316
- message: "Could not parse public key from age-keygen output"
2317
- });
2318
- const recipient = recipientLine.replace("# public key: ", "").trim();
2319
- const dir = dirname(outputPath);
2320
- const mkdirFailed = Try(() => {
2321
- if (!existsSync(dir)) mkdirSync(dir, { recursive: true });
2322
- }).fold((err) => ({
2323
- _tag: "WriteError",
2324
- message: `Failed to create directory ${dir}: ${err}`
2325
- }), () => void 0);
2326
- if (mkdirFailed) return Left(mkdirFailed);
2327
- return Try(() => {
2328
- writeFileSync(outputPath, output, { mode: 384 });
2329
- chmodSync(outputPath, 384);
2330
- }).fold((err) => Left({
2331
- _tag: "WriteError",
2332
- message: `Failed to write identity file: ${err}`
2333
- }), () => Right({
2334
- recipient,
2335
- identityPath: outputPath,
2336
- configUpdated: false
2337
- }));
2338
- });
2339
- };
2340
- /** Update agent.recipient in an envpkt.toml file, preserving structure */
2341
- const updateConfigRecipient = (configPath, recipient) => {
2342
- return Try(() => readFileSync(configPath, "utf-8")).fold((err) => Left({
2343
- _tag: "ConfigUpdateError",
2344
- message: `Failed to read config: ${err}`
2345
- }), (raw) => {
2346
- const lines = raw.split("\n");
2347
- const output = [];
2348
- let inAgentSection = false;
2349
- let recipientUpdated = false;
2350
- let hasAgentSection = false;
2351
- for (const line of lines) {
2352
- if (/^\[agent\]\s*$/.test(line)) {
2353
- inAgentSection = true;
2354
- hasAgentSection = true;
2355
- output.push(line);
2356
- continue;
2357
- }
2358
- if (/^\[/.test(line) && !/^\[agent\]\s*$/.test(line)) {
2359
- if (inAgentSection && !recipientUpdated) {
2360
- output.push(`recipient = "${recipient}"`);
2361
- recipientUpdated = true;
2362
- }
2363
- inAgentSection = false;
2364
- output.push(line);
2365
- continue;
2366
- }
2367
- if (inAgentSection && /^recipient\s*=/.test(line)) {
2368
- output.push(`recipient = "${recipient}"`);
2369
- recipientUpdated = true;
2370
- continue;
2371
- }
2372
- output.push(line);
2373
- }
2374
- if (inAgentSection && !recipientUpdated) output.push(`recipient = "${recipient}"`);
2375
- if (!hasAgentSection) {
2376
- output.push("");
2377
- output.push("[agent]");
2378
- output.push(`recipient = "${recipient}"`);
2379
- }
2380
- return Try(() => writeFileSync(configPath, output.join("\n"))).fold((err) => Left({
2381
- _tag: "ConfigUpdateError",
2382
- message: `Failed to write config: ${err}`
2383
- }), () => Right(true));
2384
- });
2385
- };
2386
-
2387
2396
  //#endregion
2388
2397
  //#region src/cli/commands/keygen.ts
2389
2398
  const runKeygen = (options) => {
@@ -2407,10 +2416,10 @@ const runKeygen = (options) => {
2407
2416
  if (existsSync(configPath)) updateConfigRecipient(configPath, recipient).fold((err) => {
2408
2417
  console.error(`${YELLOW}Warning:${RESET} Could not update config: ${"message" in err ? err.message : err._tag}`);
2409
2418
  console.log(`${DIM}Manually add to your envpkt.toml:${RESET}`);
2410
- console.log(` [agent]`);
2419
+ console.log(` [identity]`);
2411
2420
  console.log(` recipient = "${recipient}"`);
2412
2421
  }, () => {
2413
- console.log(`${GREEN}Updated${RESET} ${CYAN}${configPath}${RESET} with agent.recipient`);
2422
+ console.log(`${GREEN}Updated${RESET} ${CYAN}${configPath}${RESET} with identity.recipient`);
2414
2423
  });
2415
2424
  else {
2416
2425
  console.log(`${BOLD}Next steps:${RESET}`);
@@ -2472,7 +2481,7 @@ const readCapabilities = () => {
2472
2481
  text: JSON.stringify({ error: "No envpkt.toml found" })
2473
2482
  }] };
2474
2483
  const { config } = loaded;
2475
- const agentCapabilities = config.agent?.capabilities ?? [];
2484
+ const agentCapabilities = config.identity?.capabilities ?? [];
2476
2485
  const secretCapabilities = {};
2477
2486
  const secretEntries = config.secret ?? {};
2478
2487
  for (const [key, meta] of Object.entries(secretEntries)) if (meta.capabilities && meta.capabilities.length > 0) secretCapabilities[key] = meta.capabilities;
@@ -2480,10 +2489,10 @@ const readCapabilities = () => {
2480
2489
  uri: "envpkt://capabilities",
2481
2490
  mimeType: "application/json",
2482
2491
  text: JSON.stringify({
2483
- agent: config.agent ? {
2484
- name: config.agent.name,
2485
- consumer: config.agent.consumer,
2486
- description: config.agent.description,
2492
+ identity: config.identity ? {
2493
+ name: config.identity.name,
2494
+ consumer: config.identity.consumer,
2495
+ description: config.identity.description,
2487
2496
  capabilities: agentCapabilities
2488
2497
  } : null,
2489
2498
  secrets: secretCapabilities
@@ -2625,15 +2634,15 @@ const handleListCapabilities = (args) => {
2625
2634
  const loaded = loadConfigForTool(args.configPath);
2626
2635
  if (!loaded.ok) return loaded.result;
2627
2636
  const { config } = loaded;
2628
- const agentCapabilities = config.agent?.capabilities ?? [];
2637
+ const agentCapabilities = config.identity?.capabilities ?? [];
2629
2638
  const secretCapabilities = {};
2630
2639
  const secretEntries = config.secret ?? {};
2631
2640
  for (const [key, meta] of Object.entries(secretEntries)) if (meta.capabilities && meta.capabilities.length > 0) secretCapabilities[key] = meta.capabilities;
2632
2641
  return textResult(JSON.stringify({
2633
- agent: config.agent ? {
2634
- name: config.agent.name,
2635
- consumer: config.agent.consumer,
2636
- description: config.agent.description,
2642
+ identity: config.identity ? {
2643
+ name: config.identity.name,
2644
+ consumer: config.identity.consumer,
2645
+ description: config.identity.description,
2637
2646
  capabilities: agentCapabilities
2638
2647
  } : null,
2639
2648
  secrets: secretCapabilities,
@@ -2895,16 +2904,16 @@ const runSeal = async (options) => {
2895
2904
  console.error(formatError(err));
2896
2905
  process.exit(2);
2897
2906
  }, (c) => c);
2898
- if (!config.agent?.recipient) {
2899
- console.error(`${RED}Error:${RESET} agent.recipient is required for sealing (age public key)`);
2907
+ if (!config.identity?.recipient) {
2908
+ console.error(`${RED}Error:${RESET} identity.recipient is required for sealing (age public key)`);
2900
2909
  console.error("");
2901
2910
  console.error(`${BOLD}Quick fix:${RESET} run ${CYAN}envpkt keygen${RESET} to generate a key and auto-configure recipient`);
2902
2911
  console.error(`${DIM}Or manually add to your envpkt.toml:${RESET}`);
2903
- console.error(`${DIM} [agent]${RESET}`);
2912
+ console.error(`${DIM} [identity]${RESET}`);
2904
2913
  console.error(`${DIM} recipient = "age1..."${RESET}`);
2905
2914
  process.exit(2);
2906
2915
  }
2907
- const { recipient } = config.agent;
2916
+ const { recipient } = config.identity;
2908
2917
  const configDir = dirname(configPath);
2909
2918
  const envEntries = config.env ?? {};
2910
2919
  const secretEntries0 = config.secret ?? {};
@@ -2914,7 +2923,7 @@ const runSeal = async (options) => {
2914
2923
  console.error(`${DIM}Move these to [secret.*] only, or remove from [env.*] before sealing.${RESET}`);
2915
2924
  process.exit(2);
2916
2925
  }
2917
- const agentKey = config.agent.identity ? unwrapAgentKey(resolve(configDir, expandPath(config.agent.identity))).fold((err) => {
2926
+ const identityKey = config.identity.key_file ? unwrapAgentKey(resolve(configDir, expandPath(config.identity.key_file))).fold((err) => {
2918
2927
  const msg = err._tag === "IdentityNotFound" ? `not found: ${err.path}` : err.message;
2919
2928
  console.error(`${YELLOW}Warning:${RESET} Could not unwrap agent key: ${msg}`);
2920
2929
  }, (k) => k) : void 0;
@@ -2934,7 +2943,7 @@ const runSeal = async (options) => {
2934
2943
  const metaKeys = targetKeys;
2935
2944
  console.log(`${BOLD}Sealing ${metaKeys.length} secret(s)${RESET} with recipient ${CYAN}${recipient.slice(0, 20)}...${RESET}`);
2936
2945
  console.log("");
2937
- const values = await resolveValues(metaKeys, options.profile, agentKey);
2946
+ const values = await resolveValues(metaKeys, options.profile, identityKey);
2938
2947
  const resolved = Object.keys(values).length;
2939
2948
  const skipped = metaKeys.length - resolved;
2940
2949
  if (resolved === 0) {
@@ -3007,10 +3016,10 @@ program.name("envpkt").description("Credential lifecycle and fleet management fo
3007
3016
  const pkgPath = findPkgJson(dirname(fileURLToPath(import.meta.url)));
3008
3017
  return pkgPath ? JSON.parse(readFileSync(pkgPath, "utf-8")).version : "0.0.0";
3009
3018
  })());
3010
- 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("--agent", "Include [agent] section").option("--name <name>", "Agent name (requires --agent)").option("--capabilities <caps>", "Comma-separated capabilities (requires --agent)").option("--expires <date>", "Agent credential expiration YYYY-MM-DD (requires --agent)").option("--force", "Overwrite existing envpkt.toml").action((options) => {
3019
+ 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) => {
3011
3020
  runInit(process.cwd(), options);
3012
3021
  });
3013
- 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 agent.recipient if found)").option("--force", "Overwrite existing identity file").option("-o, --output <path>", "Output path for identity file (default: ~/.envpkt/age-key.txt)").action((options) => {
3022
+ 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("--force", "Overwrite existing identity file").option("-o, --output <path>", "Output path for identity file (default: ~/.envpkt/age-key.txt)").action((options) => {
3014
3023
  runKeygen(options);
3015
3024
  });
3016
3025
  program.command("audit").description("Audit credential health from envpkt.toml (use --strict in CI pipelines to gate deploys)").option("-c, --config <path>", "Path to envpkt.toml").option("--format <format>", "Output format: table | json | minimal", "table").option("--expiring <days>", "Show secrets expiring within N days", parseInt).option("--status <status>", "Filter by status: healthy | expiring_soon | expired | stale | missing").option("--strict", "Exit non-zero on any non-healthy secret").option("--all", "Show both secrets and env defaults").option("--env-only", "Show only env defaults (drift detection)").option("--sealed", "Show only secrets with encrypted_value").option("--external", "Show only secrets without encrypted_value").action((options) => {
package/dist/index.d.ts CHANGED
@@ -7,6 +7,19 @@ import { CallToolResult, ReadResourceResult, Resource } from "@modelcontextproto
7
7
  //#region src/core/schema.d.ts
8
8
  declare const ConsumerType: _sinclair_typebox0.TUnion<[_sinclair_typebox0.TLiteral<"agent">, _sinclair_typebox0.TLiteral<"service">, _sinclair_typebox0.TLiteral<"developer">, _sinclair_typebox0.TLiteral<"ci">]>;
9
9
  type ConsumerType = Static<typeof ConsumerType>;
10
+ declare const IdentitySchema: _sinclair_typebox0.TObject<{
11
+ name: _sinclair_typebox0.TString;
12
+ consumer: _sinclair_typebox0.TOptional<_sinclair_typebox0.TUnion<[_sinclair_typebox0.TLiteral<"agent">, _sinclair_typebox0.TLiteral<"service">, _sinclair_typebox0.TLiteral<"developer">, _sinclair_typebox0.TLiteral<"ci">]>>;
13
+ description: _sinclair_typebox0.TOptional<_sinclair_typebox0.TString>;
14
+ capabilities: _sinclair_typebox0.TOptional<_sinclair_typebox0.TArray<_sinclair_typebox0.TString>>;
15
+ expires: _sinclair_typebox0.TOptional<_sinclair_typebox0.TString>;
16
+ services: _sinclair_typebox0.TOptional<_sinclair_typebox0.TArray<_sinclair_typebox0.TString>>;
17
+ key_file: _sinclair_typebox0.TOptional<_sinclair_typebox0.TString>;
18
+ recipient: _sinclair_typebox0.TOptional<_sinclair_typebox0.TString>;
19
+ secrets: _sinclair_typebox0.TOptional<_sinclair_typebox0.TArray<_sinclair_typebox0.TString>>;
20
+ }>;
21
+ type Identity = Static<typeof IdentitySchema>;
22
+ /** @deprecated Use `IdentitySchema` instead */
10
23
  declare const AgentIdentitySchema: _sinclair_typebox0.TObject<{
11
24
  name: _sinclair_typebox0.TString;
12
25
  consumer: _sinclair_typebox0.TOptional<_sinclair_typebox0.TUnion<[_sinclair_typebox0.TLiteral<"agent">, _sinclair_typebox0.TLiteral<"service">, _sinclair_typebox0.TLiteral<"developer">, _sinclair_typebox0.TLiteral<"ci">]>>;
@@ -14,11 +27,10 @@ declare const AgentIdentitySchema: _sinclair_typebox0.TObject<{
14
27
  capabilities: _sinclair_typebox0.TOptional<_sinclair_typebox0.TArray<_sinclair_typebox0.TString>>;
15
28
  expires: _sinclair_typebox0.TOptional<_sinclair_typebox0.TString>;
16
29
  services: _sinclair_typebox0.TOptional<_sinclair_typebox0.TArray<_sinclair_typebox0.TString>>;
17
- identity: _sinclair_typebox0.TOptional<_sinclair_typebox0.TString>;
30
+ key_file: _sinclair_typebox0.TOptional<_sinclair_typebox0.TString>;
18
31
  recipient: _sinclair_typebox0.TOptional<_sinclair_typebox0.TString>;
19
32
  secrets: _sinclair_typebox0.TOptional<_sinclair_typebox0.TArray<_sinclair_typebox0.TString>>;
20
33
  }>;
21
- type AgentIdentity = Static<typeof AgentIdentitySchema>;
22
34
  declare const SecretMetaSchema: _sinclair_typebox0.TObject<{
23
35
  service: _sinclair_typebox0.TOptional<_sinclair_typebox0.TString>;
24
36
  expires: _sinclair_typebox0.TOptional<_sinclair_typebox0.TString>;
@@ -60,14 +72,14 @@ type EnvMeta = Static<typeof EnvMetaSchema>;
60
72
  declare const EnvpktConfigSchema: _sinclair_typebox0.TObject<{
61
73
  version: _sinclair_typebox0.TNumber;
62
74
  catalog: _sinclair_typebox0.TOptional<_sinclair_typebox0.TString>;
63
- agent: _sinclair_typebox0.TOptional<_sinclair_typebox0.TObject<{
75
+ identity: _sinclair_typebox0.TOptional<_sinclair_typebox0.TObject<{
64
76
  name: _sinclair_typebox0.TString;
65
77
  consumer: _sinclair_typebox0.TOptional<_sinclair_typebox0.TUnion<[_sinclair_typebox0.TLiteral<"agent">, _sinclair_typebox0.TLiteral<"service">, _sinclair_typebox0.TLiteral<"developer">, _sinclair_typebox0.TLiteral<"ci">]>>;
66
78
  description: _sinclair_typebox0.TOptional<_sinclair_typebox0.TString>;
67
79
  capabilities: _sinclair_typebox0.TOptional<_sinclair_typebox0.TArray<_sinclair_typebox0.TString>>;
68
80
  expires: _sinclair_typebox0.TOptional<_sinclair_typebox0.TString>;
69
81
  services: _sinclair_typebox0.TOptional<_sinclair_typebox0.TArray<_sinclair_typebox0.TString>>;
70
- identity: _sinclair_typebox0.TOptional<_sinclair_typebox0.TString>;
82
+ key_file: _sinclair_typebox0.TOptional<_sinclair_typebox0.TString>;
71
83
  recipient: _sinclair_typebox0.TOptional<_sinclair_typebox0.TString>;
72
84
  secrets: _sinclair_typebox0.TOptional<_sinclair_typebox0.TArray<_sinclair_typebox0.TString>>;
73
85
  }>>;
@@ -108,6 +120,8 @@ declare const EnvpktConfigSchema: _sinclair_typebox0.TObject<{
108
120
  type EnvpktConfig = Static<typeof EnvpktConfigSchema>;
109
121
  //#endregion
110
122
  //#region src/core/types.d.ts
123
+ /** @deprecated Use `Identity` instead */
124
+ type AgentIdentity = Identity;
111
125
  type HealthStatus = "healthy" | "degraded" | "critical";
112
126
  type SecretStatus = "healthy" | "expiring_soon" | "expired" | "stale" | "missing" | "missing_metadata";
113
127
  type SecretHealth = {
@@ -132,7 +146,7 @@ type AuditResult = {
132
146
  readonly missing: number;
133
147
  readonly missing_metadata: number;
134
148
  readonly orphaned: number;
135
- readonly agent?: AgentIdentity;
149
+ readonly identity?: Identity;
136
150
  };
137
151
  type EnvDriftStatus = "default" | "overridden" | "missing";
138
152
  type EnvDriftEntry = {
@@ -151,7 +165,7 @@ type EnvAuditResult = {
151
165
  };
152
166
  type FleetAgent = {
153
167
  readonly path: string;
154
- readonly agent?: AgentIdentity;
168
+ readonly identity?: Identity;
155
169
  readonly min_expiry_days?: number;
156
170
  readonly audit: AuditResult;
157
171
  };
@@ -437,7 +451,7 @@ declare const generateKeypair: (options?: {
437
451
  readonly force?: boolean;
438
452
  readonly outputPath?: string;
439
453
  }) => Either<KeygenError, KeygenResult>;
440
- /** Update agent.recipient in an envpkt.toml file, preserving structure */
454
+ /** Update identity.recipient in an envpkt.toml file, preserving structure */
441
455
  declare const updateConfigRecipient: (configPath: string, recipient: string) => Either<KeygenError, true>;
442
456
  //#endregion
443
457
  //#region src/core/resolve-values.d.ts
@@ -501,4 +515,4 @@ type ToolDef = {
501
515
  declare const toolDefinitions: readonly ToolDef[];
502
516
  declare const callTool: (name: string, args: Record<string, unknown>) => CallToolResult;
503
517
  //#endregion
504
- export { type AgentIdentity, AgentIdentitySchema, type AuditResult, type BootError, type BootOptions, type BootResult, type CallbackConfig, CallbackConfigSchema, type CatalogError, type CheckResult, type ConfidenceLevel, type ConfigError, type ConfigSource, type ConsumerType, type CredentialPattern, type DriftEntry, type DriftStatus, type EnvAuditResult, type EnvDriftEntry, type EnvDriftStatus, type EnvMeta, EnvMetaSchema, EnvpktBootError, type EnvpktConfig, EnvpktConfigSchema, type FleetAgent, type FleetHealth, type FnoxConfig, type FnoxError, type FnoxSecret, type FormatPacketOptions, type HealthStatus, type IdentityError, type KeygenError, type KeygenResult, type LifecycleConfig, LifecycleConfigSchema, type MatchResult, type ResolveOptions, type ResolveResult, type ResolvedPath, type ScanOptions, type ScanResult, type SealError, type SecretDisplay, type SecretHealth, type SecretMeta, SecretMetaSchema, type SecretStatus, type ToolsConfig, ToolsConfigSchema, ageAvailable, ageDecrypt, ageEncrypt, boot, bootSafe, callTool, compareFnoxAndEnvpkt, computeAudit, computeEnvAudit, createServer, deriveServiceFromName, detectFnox, discoverConfig, envCheck, envScan, extractFnoxKeys, findConfigPath, fnoxAvailable, fnoxExport, fnoxGet, formatPacket, generateKeypair, generateTomlFromScan, loadCatalog, loadConfig, loadConfigFromCwd, maskValue, matchEnvVar, matchValueShape, parseToml, readConfigFile, readFnoxConfig, readResource, resolveConfig, resolveConfigPath, resolveInlineKey, resolveKeyPath, resolveSecrets, resolveValues, resourceDefinitions, scanEnv, scanFleet, sealSecrets, startServer, toolDefinitions, unsealSecrets, unwrapAgentKey, updateConfigRecipient, validateConfig };
518
+ export { type AgentIdentity, AgentIdentitySchema, type AuditResult, type BootError, type BootOptions, type BootResult, type CallbackConfig, CallbackConfigSchema, type CatalogError, type CheckResult, type ConfidenceLevel, type ConfigError, type ConfigSource, type ConsumerType, type CredentialPattern, type DriftEntry, type DriftStatus, type EnvAuditResult, type EnvDriftEntry, type EnvDriftStatus, type EnvMeta, EnvMetaSchema, EnvpktBootError, type EnvpktConfig, EnvpktConfigSchema, type FleetAgent, type FleetHealth, type FnoxConfig, type FnoxError, type FnoxSecret, type FormatPacketOptions, type HealthStatus, type Identity, type IdentityError, IdentitySchema, type KeygenError, type KeygenResult, type LifecycleConfig, LifecycleConfigSchema, type MatchResult, type ResolveOptions, type ResolveResult, type ResolvedPath, type ScanOptions, type ScanResult, type SealError, type SecretDisplay, type SecretHealth, type SecretMeta, SecretMetaSchema, type SecretStatus, type ToolsConfig, ToolsConfigSchema, ageAvailable, ageDecrypt, ageEncrypt, boot, bootSafe, callTool, compareFnoxAndEnvpkt, computeAudit, computeEnvAudit, createServer, deriveServiceFromName, detectFnox, discoverConfig, envCheck, envScan, extractFnoxKeys, findConfigPath, fnoxAvailable, fnoxExport, fnoxGet, formatPacket, generateKeypair, generateTomlFromScan, loadCatalog, loadConfig, loadConfigFromCwd, maskValue, matchEnvVar, matchValueShape, parseToml, readConfigFile, readFnoxConfig, readResource, resolveConfig, resolveConfigPath, resolveInlineKey, resolveKeyPath, resolveSecrets, resolveValues, resourceDefinitions, scanEnv, scanFleet, sealSecrets, startServer, toolDefinitions, unsealSecrets, unwrapAgentKey, updateConfigRecipient, validateConfig };
package/dist/index.js CHANGED
@@ -4,8 +4,8 @@ import { TypeCompiler } from "@sinclair/typebox/compiler";
4
4
  import { Cond, Left, List, None, Option, Right, Some, Try } from "functype";
5
5
  import { Env, Fs, Path } from "functype-os";
6
6
  import { TomlDate, parse } from "smol-toml";
7
- import { execFileSync } from "node:child_process";
8
7
  import { chmodSync, existsSync, mkdirSync, readFileSync, readdirSync, statSync, writeFileSync } from "node:fs";
8
+ import { execFileSync } from "node:child_process";
9
9
  import { homedir } from "node:os";
10
10
  import { createInterface } from "node:readline";
11
11
  import { Server } from "@modelcontextprotocol/sdk/server/index.js";
@@ -23,20 +23,22 @@ const ConsumerType = Type.Union([
23
23
  Type.Literal("developer"),
24
24
  Type.Literal("ci")
25
25
  ], { description: "Classification of the agent's consumer type" });
26
- const AgentIdentitySchema = Type.Object({
27
- name: Type.String({ description: "Agent display name" }),
26
+ const IdentitySchema = Type.Object({
27
+ name: Type.String({ description: "Display name" }),
28
28
  consumer: Type.Optional(ConsumerType),
29
- description: Type.Optional(Type.String({ description: "Agent description or role" })),
30
- capabilities: Type.Optional(Type.Array(Type.String(), { description: "List of capabilities this agent provides" })),
29
+ description: Type.Optional(Type.String({ description: "Description or role" })),
30
+ capabilities: Type.Optional(Type.Array(Type.String(), { description: "List of capabilities this identity provides" })),
31
31
  expires: Type.Optional(Type.String({
32
32
  format: "date",
33
- description: "Agent credential expiration date (YYYY-MM-DD)"
33
+ description: "Credential expiration date (YYYY-MM-DD)"
34
34
  })),
35
- services: Type.Optional(Type.Array(Type.String(), { description: "Service dependencies for this agent" })),
36
- identity: Type.Optional(Type.String({ description: "Path to encrypted agent key file (relative to config directory)" })),
37
- recipient: Type.Optional(Type.String({ description: "Agent's age public key for encryption" })),
38
- secrets: Type.Optional(Type.Array(Type.String(), { description: "Secret keys this agent needs from the catalog" }))
39
- }, { description: "Identity and capabilities of the AI agent using this envpkt" });
35
+ services: Type.Optional(Type.Array(Type.String(), { description: "Service dependencies" })),
36
+ key_file: Type.Optional(Type.String({ description: "Path to age identity file (relative to config directory)" })),
37
+ recipient: Type.Optional(Type.String({ description: "Age public key for encryption" })),
38
+ secrets: Type.Optional(Type.Array(Type.String(), { description: "Secret keys needed from the catalog" }))
39
+ }, { description: "Identity and capabilities of the principal using this envpkt" });
40
+ /** @deprecated Use `IdentitySchema` instead */
41
+ const AgentIdentitySchema = IdentitySchema;
40
42
  const SecretMetaSchema = Type.Object({
41
43
  service: Type.Optional(Type.String({ description: "Service or system this secret authenticates to" })),
42
44
  expires: Type.Optional(Type.String({
@@ -94,7 +96,7 @@ const EnvpktConfigSchema = Type.Object({
94
96
  default: 1
95
97
  }),
96
98
  catalog: Type.Optional(Type.String({ description: "Path to shared secret catalog (relative to this config file)" })),
97
- agent: Type.Optional(AgentIdentitySchema),
99
+ identity: Type.Optional(IdentitySchema),
98
100
  secret: Type.Optional(Type.Record(Type.String(), SecretMetaSchema, { description: "Per-secret metadata keyed by secret name" })),
99
101
  env: Type.Optional(Type.Record(Type.String(), EnvMetaSchema, { description: "Plaintext environment defaults keyed by variable name" })),
100
102
  lifecycle: Type.Optional(LifecycleConfigSchema),
@@ -311,12 +313,12 @@ const resolveConfig = (agentConfig, agentConfigDir) => {
311
313
  overridden: [],
312
314
  warnings: []
313
315
  });
314
- if (!agentConfig.agent?.secrets || agentConfig.agent.secrets.length === 0) return Left({
316
+ if (!agentConfig.identity?.secrets || agentConfig.identity.secrets.length === 0) return Left({
315
317
  _tag: "MissingSecretsList",
316
- message: "Config has 'catalog' but agent.secrets is missing — declare which catalog secrets this agent needs"
318
+ message: "Config has 'catalog' but identity.secrets is missing — declare which catalog secrets this agent needs"
317
319
  });
318
320
  const catalogPath = resolve(agentConfigDir, agentConfig.catalog);
319
- const agentSecrets = agentConfig.agent.secrets;
321
+ const agentSecrets = agentConfig.identity.secrets;
320
322
  const agentSecretEntries = agentConfig.secret ?? {};
321
323
  return loadCatalog(catalogPath).flatMap((catalogConfig) => resolveSecrets(agentSecretEntries, catalogConfig.secret ?? {}, agentSecrets, catalogPath).map((resolvedMeta) => {
322
324
  const merged = [];
@@ -327,16 +329,16 @@ const resolveConfig = (agentConfig, agentConfigDir) => {
327
329
  if (agentSecretEntries[key]) overridden.push(key);
328
330
  }
329
331
  const { catalog: _catalog, ...agentWithoutCatalog } = agentConfig;
330
- const agentIdentity = agentConfig.agent ? (() => {
331
- const { secrets: _secrets, ...rest } = agentConfig.agent;
332
+ const identityData = agentConfig.identity ? (() => {
333
+ const { secrets: _secrets, ...rest } = agentConfig.identity;
332
334
  return rest;
333
335
  })() : void 0;
334
336
  return {
335
337
  config: {
336
338
  ...agentWithoutCatalog,
337
- agent: agentIdentity ? {
338
- ...agentIdentity,
339
- name: agentIdentity.name
339
+ identity: identityData ? {
340
+ ...identityData,
341
+ name: identityData.name
340
342
  } : void 0,
341
343
  secret: resolvedMeta
342
344
  },
@@ -379,16 +381,16 @@ const formatSecretFields = (meta, indent) => {
379
381
  const formatPacket = (result, options) => {
380
382
  const { config } = result;
381
383
  const sections = [];
382
- if (config.agent) {
383
- const consumer = config.agent.consumer ? ` (${config.agent.consumer})` : "";
384
- sections.push(`envpkt packet: ${config.agent.name}${consumer}`);
384
+ if (config.identity) {
385
+ const consumer = config.identity.consumer ? ` (${config.identity.consumer})` : "";
386
+ sections.push(`envpkt packet: ${config.identity.name}${consumer}`);
385
387
  } else sections.push("envpkt packet");
386
- if (config.agent) {
388
+ if (config.identity) {
387
389
  const agentLines = [];
388
- if (config.agent.description) agentLines.push(` ${config.agent.description}`);
389
- if (config.agent.capabilities) agentLines.push(` capabilities: ${config.agent.capabilities.join(", ")}`);
390
- if (config.agent.services) agentLines.push(` services: ${config.agent.services.join(", ")}`);
391
- if (config.agent.expires) agentLines.push(` expires: ${config.agent.expires}`);
390
+ if (config.identity.description) agentLines.push(` ${config.identity.description}`);
391
+ if (config.identity.capabilities) agentLines.push(` capabilities: ${config.identity.capabilities.join(", ")}`);
392
+ if (config.identity.services) agentLines.push(` services: ${config.identity.services.join(", ")}`);
393
+ if (config.identity.expires) agentLines.push(` expires: ${config.identity.expires}`);
392
394
  if (agentLines.length > 0) sections.push(agentLines.join("\n"));
393
395
  }
394
396
  const secretConfig = config.secret ?? {};
@@ -494,7 +496,7 @@ const computeAudit = (config, fnoxKeys, today) => {
494
496
  missing,
495
497
  missing_metadata,
496
498
  orphaned,
497
- agent: config.agent
499
+ identity: config.identity
498
500
  };
499
501
  };
500
502
  const computeEnvAudit = (config, env = process.env) => {
@@ -1421,6 +1423,111 @@ const readFnoxConfig = (path) => Try(() => readFileSync(path, "utf-8")).fold((er
1421
1423
  /** Extract the set of secret key names from a parsed fnox config */
1422
1424
  const extractFnoxKeys = (config) => new Set(Object.keys(config.secrets));
1423
1425
 
1426
+ //#endregion
1427
+ //#region src/core/keygen.ts
1428
+ /** Resolve the age identity file path: ENVPKT_AGE_KEY_FILE env var > ~/.envpkt/age-key.txt */
1429
+ const resolveKeyPath = () => process.env["ENVPKT_AGE_KEY_FILE"] ?? join(homedir(), ".envpkt", "age-key.txt");
1430
+ /** Resolve an inline age key from ENVPKT_AGE_KEY env var (for CI) */
1431
+ const resolveInlineKey = () => {
1432
+ const key = process.env["ENVPKT_AGE_KEY"];
1433
+ return key ? Some(key) : None();
1434
+ };
1435
+ /** Generate an age keypair and write to disk */
1436
+ const generateKeypair = (options) => {
1437
+ if (!ageAvailable()) return Left({
1438
+ _tag: "AgeNotFound",
1439
+ message: "age-keygen CLI not found on PATH. Install age: https://github.com/FiloSottile/age"
1440
+ });
1441
+ const outputPath = options?.outputPath ?? resolveKeyPath();
1442
+ if (existsSync(outputPath) && !options?.force) return Left({
1443
+ _tag: "KeyExists",
1444
+ path: outputPath
1445
+ });
1446
+ return Try(() => execFileSync("age-keygen", [], {
1447
+ stdio: [
1448
+ "pipe",
1449
+ "pipe",
1450
+ "pipe"
1451
+ ],
1452
+ encoding: "utf-8"
1453
+ })).fold((err) => Left({
1454
+ _tag: "KeygenFailed",
1455
+ message: `age-keygen failed: ${err}`
1456
+ }), (output) => {
1457
+ const recipientLine = output.split("\n").find((l) => l.startsWith("# public key:"));
1458
+ if (!recipientLine) return Left({
1459
+ _tag: "KeygenFailed",
1460
+ message: "Could not parse public key from age-keygen output"
1461
+ });
1462
+ const recipient = recipientLine.replace("# public key: ", "").trim();
1463
+ const dir = dirname(outputPath);
1464
+ const mkdirFailed = Try(() => {
1465
+ if (!existsSync(dir)) mkdirSync(dir, { recursive: true });
1466
+ }).fold((err) => ({
1467
+ _tag: "WriteError",
1468
+ message: `Failed to create directory ${dir}: ${err}`
1469
+ }), () => void 0);
1470
+ if (mkdirFailed) return Left(mkdirFailed);
1471
+ return Try(() => {
1472
+ writeFileSync(outputPath, output, { mode: 384 });
1473
+ chmodSync(outputPath, 384);
1474
+ }).fold((err) => Left({
1475
+ _tag: "WriteError",
1476
+ message: `Failed to write identity file: ${err}`
1477
+ }), () => Right({
1478
+ recipient,
1479
+ identityPath: outputPath,
1480
+ configUpdated: false
1481
+ }));
1482
+ });
1483
+ };
1484
+ /** Update identity.recipient in an envpkt.toml file, preserving structure */
1485
+ const updateConfigRecipient = (configPath, recipient) => {
1486
+ return Try(() => readFileSync(configPath, "utf-8")).fold((err) => Left({
1487
+ _tag: "ConfigUpdateError",
1488
+ message: `Failed to read config: ${err}`
1489
+ }), (raw) => {
1490
+ const lines = raw.split("\n");
1491
+ const output = [];
1492
+ let inIdentitySection = false;
1493
+ let recipientUpdated = false;
1494
+ let hasIdentitySection = false;
1495
+ for (const line of lines) {
1496
+ if (/^\[identity\]\s*$/.test(line)) {
1497
+ inIdentitySection = true;
1498
+ hasIdentitySection = true;
1499
+ output.push(line);
1500
+ continue;
1501
+ }
1502
+ if (/^\[/.test(line) && !/^\[identity\]\s*$/.test(line)) {
1503
+ if (inIdentitySection && !recipientUpdated) {
1504
+ output.push(`recipient = "${recipient}"`);
1505
+ recipientUpdated = true;
1506
+ }
1507
+ inIdentitySection = false;
1508
+ output.push(line);
1509
+ continue;
1510
+ }
1511
+ if (inIdentitySection && /^recipient\s*=/.test(line)) {
1512
+ output.push(`recipient = "${recipient}"`);
1513
+ recipientUpdated = true;
1514
+ continue;
1515
+ }
1516
+ output.push(line);
1517
+ }
1518
+ if (inIdentitySection && !recipientUpdated) output.push(`recipient = "${recipient}"`);
1519
+ if (!hasIdentitySection) {
1520
+ output.push("");
1521
+ output.push("[identity]");
1522
+ output.push(`recipient = "${recipient}"`);
1523
+ }
1524
+ return Try(() => writeFileSync(configPath, output.join("\n"))).fold((err) => Left({
1525
+ _tag: "ConfigUpdateError",
1526
+ message: `Failed to write config: ${err}`
1527
+ }), () => Right(true));
1528
+ });
1529
+ };
1530
+
1424
1531
  //#endregion
1425
1532
  //#region src/core/seal.ts
1426
1533
  /** Encrypt a plaintext string using age with the given recipient public key (armored output) */
@@ -1532,9 +1639,17 @@ const resolveAndLoad = (opts) => resolveConfigPath(opts.configPath).fold((err) =
1532
1639
  configSource
1533
1640
  }));
1534
1641
  }));
1535
- const resolveAgentKey = (config, configDir) => {
1536
- if (!config.agent?.identity) return Right(void 0);
1537
- return unwrapAgentKey(resolve(configDir, expandPath(config.agent.identity))).fold((err) => Left(err), (key) => Right(key));
1642
+ /** Resolve identity file path with explicit fallback control */
1643
+ const resolveIdentityFilePath = (config, configDir, useDefaultFallback) => {
1644
+ if (config.identity?.key_file) return resolve(configDir, expandPath(config.identity.key_file));
1645
+ if (!useDefaultFallback) return void 0;
1646
+ const defaultPath = resolveKeyPath();
1647
+ return existsSync(defaultPath) ? defaultPath : void 0;
1648
+ };
1649
+ const resolveIdentityKey = (config, configDir) => {
1650
+ const identityPath = resolveIdentityFilePath(config, configDir, false);
1651
+ if (!identityPath) return Right(void 0);
1652
+ return unwrapAgentKey(identityPath).fold((err) => Left(err), (key) => Right(key));
1538
1653
  };
1539
1654
  const detectFnoxKeys = (configDir) => detectFnox(configDir).fold(() => /* @__PURE__ */ new Set(), (fnoxPath) => readFnoxConfig(fnoxPath).fold(() => /* @__PURE__ */ new Set(), (fnoxConfig) => extractFnoxKeys(fnoxConfig)));
1540
1655
  const checkExpiration = (audit, failOnExpired, warnOnly) => {
@@ -1577,10 +1692,10 @@ const bootSafe = (options) => {
1577
1692
  const secretEntries = config.secret ?? {};
1578
1693
  const metaKeys = Object.keys(secretEntries);
1579
1694
  const hasSealedValues = metaKeys.some((k) => !!secretEntries[k]?.encrypted_value);
1580
- const agentKeyResult = resolveAgentKey(config, configDir);
1581
- const agentKey = agentKeyResult.fold(() => void 0, (k) => k);
1582
- const agentKeyError = agentKeyResult.fold((err) => err, () => void 0);
1583
- if (agentKeyError && !hasSealedValues) return Left(agentKeyError);
1695
+ const identityKeyResult = resolveIdentityKey(config, configDir);
1696
+ const identityKey = identityKeyResult.fold(() => void 0, (k) => k);
1697
+ const identityKeyError = identityKeyResult.fold((err) => err, () => void 0);
1698
+ if (identityKeyError && !hasSealedValues) return Left(identityKeyError);
1584
1699
  const audit = computeAudit(config, detectFnoxKeys(configDir));
1585
1700
  return checkExpiration(audit, failOnExpired, warnOnly).map((warnings) => {
1586
1701
  const secrets = {};
@@ -1595,7 +1710,8 @@ const bootSafe = (options) => {
1595
1710
  if (inject) process.env[key] = entry.value;
1596
1711
  } else overridden.push(key);
1597
1712
  const sealedKeys = /* @__PURE__ */ new Set();
1598
- if (hasSealedValues && config.agent?.identity) unsealSecrets(secretEntries, resolve(configDir, expandPath(config.agent.identity))).fold((err) => {
1713
+ const identityFilePath = resolveIdentityFilePath(config, configDir, true);
1714
+ if (hasSealedValues && identityFilePath) unsealSecrets(secretEntries, identityFilePath).fold((err) => {
1599
1715
  warnings.push(`Sealed value decryption failed: ${err.message}`);
1600
1716
  }, (unsealed) => {
1601
1717
  for (const [key, value] of Object.entries(unsealed)) {
@@ -1605,7 +1721,7 @@ const bootSafe = (options) => {
1605
1721
  }
1606
1722
  });
1607
1723
  const remainingKeys = metaKeys.filter((k) => !sealedKeys.has(k));
1608
- if (remainingKeys.length > 0) if (fnoxAvailable()) fnoxExport(opts.profile, agentKey).fold((err) => {
1724
+ if (remainingKeys.length > 0) if (fnoxAvailable()) fnoxExport(opts.profile, identityKey).fold((err) => {
1609
1725
  warnings.push(`fnox export failed: ${err.message}`);
1610
1726
  for (const key of remainingKeys) skipped.push(key);
1611
1727
  }, (exported) => {
@@ -1667,111 +1783,6 @@ const formatBootError = (error) => {
1667
1783
  }
1668
1784
  };
1669
1785
 
1670
- //#endregion
1671
- //#region src/core/keygen.ts
1672
- /** Resolve the age identity file path: ENVPKT_AGE_KEY_FILE env var > ~/.envpkt/age-key.txt */
1673
- const resolveKeyPath = () => process.env["ENVPKT_AGE_KEY_FILE"] ?? join(homedir(), ".envpkt", "age-key.txt");
1674
- /** Resolve an inline age key from ENVPKT_AGE_KEY env var (for CI) */
1675
- const resolveInlineKey = () => {
1676
- const key = process.env["ENVPKT_AGE_KEY"];
1677
- return key ? Some(key) : None();
1678
- };
1679
- /** Generate an age keypair and write to disk */
1680
- const generateKeypair = (options) => {
1681
- if (!ageAvailable()) return Left({
1682
- _tag: "AgeNotFound",
1683
- message: "age-keygen CLI not found on PATH. Install age: https://github.com/FiloSottile/age"
1684
- });
1685
- const outputPath = options?.outputPath ?? resolveKeyPath();
1686
- if (existsSync(outputPath) && !options?.force) return Left({
1687
- _tag: "KeyExists",
1688
- path: outputPath
1689
- });
1690
- return Try(() => execFileSync("age-keygen", [], {
1691
- stdio: [
1692
- "pipe",
1693
- "pipe",
1694
- "pipe"
1695
- ],
1696
- encoding: "utf-8"
1697
- })).fold((err) => Left({
1698
- _tag: "KeygenFailed",
1699
- message: `age-keygen failed: ${err}`
1700
- }), (output) => {
1701
- const recipientLine = output.split("\n").find((l) => l.startsWith("# public key:"));
1702
- if (!recipientLine) return Left({
1703
- _tag: "KeygenFailed",
1704
- message: "Could not parse public key from age-keygen output"
1705
- });
1706
- const recipient = recipientLine.replace("# public key: ", "").trim();
1707
- const dir = dirname(outputPath);
1708
- const mkdirFailed = Try(() => {
1709
- if (!existsSync(dir)) mkdirSync(dir, { recursive: true });
1710
- }).fold((err) => ({
1711
- _tag: "WriteError",
1712
- message: `Failed to create directory ${dir}: ${err}`
1713
- }), () => void 0);
1714
- if (mkdirFailed) return Left(mkdirFailed);
1715
- return Try(() => {
1716
- writeFileSync(outputPath, output, { mode: 384 });
1717
- chmodSync(outputPath, 384);
1718
- }).fold((err) => Left({
1719
- _tag: "WriteError",
1720
- message: `Failed to write identity file: ${err}`
1721
- }), () => Right({
1722
- recipient,
1723
- identityPath: outputPath,
1724
- configUpdated: false
1725
- }));
1726
- });
1727
- };
1728
- /** Update agent.recipient in an envpkt.toml file, preserving structure */
1729
- const updateConfigRecipient = (configPath, recipient) => {
1730
- return Try(() => readFileSync(configPath, "utf-8")).fold((err) => Left({
1731
- _tag: "ConfigUpdateError",
1732
- message: `Failed to read config: ${err}`
1733
- }), (raw) => {
1734
- const lines = raw.split("\n");
1735
- const output = [];
1736
- let inAgentSection = false;
1737
- let recipientUpdated = false;
1738
- let hasAgentSection = false;
1739
- for (const line of lines) {
1740
- if (/^\[agent\]\s*$/.test(line)) {
1741
- inAgentSection = true;
1742
- hasAgentSection = true;
1743
- output.push(line);
1744
- continue;
1745
- }
1746
- if (/^\[/.test(line) && !/^\[agent\]\s*$/.test(line)) {
1747
- if (inAgentSection && !recipientUpdated) {
1748
- output.push(`recipient = "${recipient}"`);
1749
- recipientUpdated = true;
1750
- }
1751
- inAgentSection = false;
1752
- output.push(line);
1753
- continue;
1754
- }
1755
- if (inAgentSection && /^recipient\s*=/.test(line)) {
1756
- output.push(`recipient = "${recipient}"`);
1757
- recipientUpdated = true;
1758
- continue;
1759
- }
1760
- output.push(line);
1761
- }
1762
- if (inAgentSection && !recipientUpdated) output.push(`recipient = "${recipient}"`);
1763
- if (!hasAgentSection) {
1764
- output.push("");
1765
- output.push("[agent]");
1766
- output.push(`recipient = "${recipient}"`);
1767
- }
1768
- return Try(() => writeFileSync(configPath, output.join("\n"))).fold((err) => Left({
1769
- _tag: "ConfigUpdateError",
1770
- message: `Failed to write config: ${err}`
1771
- }), () => Right(true));
1772
- });
1773
- };
1774
-
1775
1786
  //#endregion
1776
1787
  //#region src/core/resolve-values.ts
1777
1788
  /** Resolve plaintext values for the given keys via cascade: fnox → env → interactive prompt */
@@ -1851,7 +1862,7 @@ const scanFleet = (rootDir, options) => {
1851
1862
  const audit = computeAudit(config);
1852
1863
  agents.push({
1853
1864
  path: configPath,
1854
- agent: config.agent,
1865
+ identity: config.identity,
1855
1866
  min_expiry_days: audit.secrets.toArray().reduce((min, s) => s.days_remaining.fold(() => min, (d) => min === void 0 ? d : Math.min(min, d)), void 0),
1856
1867
  audit
1857
1868
  });
@@ -1934,7 +1945,7 @@ const readCapabilities = () => {
1934
1945
  text: JSON.stringify({ error: "No envpkt.toml found" })
1935
1946
  }] };
1936
1947
  const { config } = loaded;
1937
- const agentCapabilities = config.agent?.capabilities ?? [];
1948
+ const agentCapabilities = config.identity?.capabilities ?? [];
1938
1949
  const secretCapabilities = {};
1939
1950
  const secretEntries = config.secret ?? {};
1940
1951
  for (const [key, meta] of Object.entries(secretEntries)) if (meta.capabilities && meta.capabilities.length > 0) secretCapabilities[key] = meta.capabilities;
@@ -1942,10 +1953,10 @@ const readCapabilities = () => {
1942
1953
  uri: "envpkt://capabilities",
1943
1954
  mimeType: "application/json",
1944
1955
  text: JSON.stringify({
1945
- agent: config.agent ? {
1946
- name: config.agent.name,
1947
- consumer: config.agent.consumer,
1948
- description: config.agent.description,
1956
+ identity: config.identity ? {
1957
+ name: config.identity.name,
1958
+ consumer: config.identity.consumer,
1959
+ description: config.identity.description,
1949
1960
  capabilities: agentCapabilities
1950
1961
  } : null,
1951
1962
  secrets: secretCapabilities
@@ -2087,15 +2098,15 @@ const handleListCapabilities = (args) => {
2087
2098
  const loaded = loadConfigForTool(args.configPath);
2088
2099
  if (!loaded.ok) return loaded.result;
2089
2100
  const { config } = loaded;
2090
- const agentCapabilities = config.agent?.capabilities ?? [];
2101
+ const agentCapabilities = config.identity?.capabilities ?? [];
2091
2102
  const secretCapabilities = {};
2092
2103
  const secretEntries = config.secret ?? {};
2093
2104
  for (const [key, meta] of Object.entries(secretEntries)) if (meta.capabilities && meta.capabilities.length > 0) secretCapabilities[key] = meta.capabilities;
2094
2105
  return textResult(JSON.stringify({
2095
- agent: config.agent ? {
2096
- name: config.agent.name,
2097
- consumer: config.agent.consumer,
2098
- description: config.agent.description,
2106
+ identity: config.identity ? {
2107
+ name: config.identity.name,
2108
+ consumer: config.identity.consumer,
2109
+ description: config.identity.description,
2099
2110
  capabilities: agentCapabilities
2100
2111
  } : null,
2101
2112
  secrets: secretCapabilities,
@@ -2194,4 +2205,4 @@ const startServer = async () => {
2194
2205
  };
2195
2206
 
2196
2207
  //#endregion
2197
- export { AgentIdentitySchema, CallbackConfigSchema, ConsumerType, EnvMetaSchema, EnvpktBootError, EnvpktConfigSchema, LifecycleConfigSchema, SecretMetaSchema, ToolsConfigSchema, ageAvailable, ageDecrypt, ageEncrypt, boot, bootSafe, callTool, compareFnoxAndEnvpkt, computeAudit, computeEnvAudit, createServer, deriveServiceFromName, detectFnox, discoverConfig, envCheck, envScan, extractFnoxKeys, findConfigPath, fnoxAvailable, fnoxExport, fnoxGet, formatPacket, generateKeypair, generateTomlFromScan, loadCatalog, loadConfig, loadConfigFromCwd, maskValue, matchEnvVar, matchValueShape, parseToml, readConfigFile, readFnoxConfig, readResource, resolveConfig, resolveConfigPath, resolveInlineKey, resolveKeyPath, resolveSecrets, resolveValues, resourceDefinitions, scanEnv, scanFleet, sealSecrets, startServer, toolDefinitions, unsealSecrets, unwrapAgentKey, updateConfigRecipient, validateConfig };
2208
+ export { AgentIdentitySchema, CallbackConfigSchema, ConsumerType, EnvMetaSchema, EnvpktBootError, EnvpktConfigSchema, IdentitySchema, LifecycleConfigSchema, SecretMetaSchema, ToolsConfigSchema, ageAvailable, ageDecrypt, ageEncrypt, boot, bootSafe, callTool, compareFnoxAndEnvpkt, computeAudit, computeEnvAudit, createServer, deriveServiceFromName, detectFnox, discoverConfig, envCheck, envScan, extractFnoxKeys, findConfigPath, fnoxAvailable, fnoxExport, fnoxGet, formatPacket, generateKeypair, generateTomlFromScan, loadCatalog, loadConfig, loadConfigFromCwd, maskValue, matchEnvVar, matchValueShape, parseToml, readConfigFile, readFnoxConfig, readResource, resolveConfig, resolveConfigPath, resolveInlineKey, resolveKeyPath, resolveSecrets, resolveValues, resourceDefinitions, scanEnv, scanFleet, sealSecrets, startServer, toolDefinitions, unsealSecrets, unwrapAgentKey, updateConfigRecipient, validateConfig };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "envpkt",
3
- "version": "0.5.0",
3
+ "version": "0.6.0",
4
4
  "description": "Credential lifecycle and fleet management for AI agents",
5
5
  "keywords": [
6
6
  "credentials",
@@ -17,15 +17,15 @@
17
17
  "description": "Path to shared secret catalog (relative to this config file)",
18
18
  "type": "string"
19
19
  },
20
- "agent": {
21
- "description": "Identity and capabilities of the AI agent using this envpkt",
20
+ "identity": {
21
+ "description": "Identity and capabilities of the principal using this envpkt",
22
22
  "type": "object",
23
23
  "required": [
24
24
  "name"
25
25
  ],
26
26
  "properties": {
27
27
  "name": {
28
- "description": "Agent display name",
28
+ "description": "Display name",
29
29
  "type": "string"
30
30
  },
31
31
  "consumer": {
@@ -50,11 +50,11 @@
50
50
  ]
51
51
  },
52
52
  "description": {
53
- "description": "Agent description or role",
53
+ "description": "Description or role",
54
54
  "type": "string"
55
55
  },
56
56
  "capabilities": {
57
- "description": "List of capabilities this agent provides",
57
+ "description": "List of capabilities this identity provides",
58
58
  "type": "array",
59
59
  "items": {
60
60
  "type": "string"
@@ -62,26 +62,26 @@
62
62
  },
63
63
  "expires": {
64
64
  "format": "date",
65
- "description": "Agent credential expiration date (YYYY-MM-DD)",
65
+ "description": "Credential expiration date (YYYY-MM-DD)",
66
66
  "type": "string"
67
67
  },
68
68
  "services": {
69
- "description": "Service dependencies for this agent",
69
+ "description": "Service dependencies",
70
70
  "type": "array",
71
71
  "items": {
72
72
  "type": "string"
73
73
  }
74
74
  },
75
- "identity": {
76
- "description": "Path to encrypted agent key file (relative to config directory)",
75
+ "key_file": {
76
+ "description": "Path to age identity file (relative to config directory)",
77
77
  "type": "string"
78
78
  },
79
79
  "recipient": {
80
- "description": "Agent's age public key for encryption",
80
+ "description": "Age public key for encryption",
81
81
  "type": "string"
82
82
  },
83
83
  "secrets": {
84
- "description": "Secret keys this agent needs from the catalog",
84
+ "description": "Secret keys needed from the catalog",
85
85
  "type": "array",
86
86
  "items": {
87
87
  "type": "string"