envpkt 0.1.0 → 0.4.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 +296 -107
- package/dist/cli.js +803 -198
- package/dist/index.d.ts +85 -7
- package/dist/index.js +369 -75
- package/package.json +22 -20
- package/schemas/envpkt.schema.json +46 -3
package/dist/index.js
CHANGED
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
import { FormatRegistry, Type } from "@sinclair/typebox";
|
|
2
2
|
import { existsSync, readFileSync, readdirSync, statSync } from "node:fs";
|
|
3
|
+
import { homedir } from "node:os";
|
|
3
4
|
import { dirname, join, resolve } from "node:path";
|
|
4
5
|
import { TypeCompiler } from "@sinclair/typebox/compiler";
|
|
5
6
|
import { Cond, Left, List, Option, Right, Try } from "functype";
|
|
6
7
|
import { TomlDate, parse } from "smol-toml";
|
|
7
8
|
import { execFileSync } from "node:child_process";
|
|
9
|
+
import { createInterface } from "node:readline";
|
|
8
10
|
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
9
11
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
10
12
|
import { CallToolRequestSchema, ListResourcesRequestSchema, ListToolsRequestSchema, ReadResourceRequestSchema } from "@modelcontextprotocol/sdk/types.js";
|
|
@@ -45,6 +47,7 @@ const SecretMetaSchema = Type.Object({
|
|
|
45
47
|
description: "URL or reference for secret rotation procedure"
|
|
46
48
|
})),
|
|
47
49
|
purpose: Type.Optional(Type.String({ description: "Why this secret exists and what it enables" })),
|
|
50
|
+
comment: Type.Optional(Type.String({ description: "Free-form annotation or note" })),
|
|
48
51
|
capabilities: Type.Optional(Type.Array(Type.String(), { description: "What operations this secret grants (e.g. read, write, admin)" })),
|
|
49
52
|
created: Type.Optional(Type.String({
|
|
50
53
|
format: "date",
|
|
@@ -54,6 +57,7 @@ const SecretMetaSchema = Type.Object({
|
|
|
54
57
|
rate_limit: Type.Optional(Type.String({ description: "Rate limit or quota info (e.g. '1000/min')" })),
|
|
55
58
|
model_hint: Type.Optional(Type.String({ description: "Suggested model or tier for this credential" })),
|
|
56
59
|
source: Type.Optional(Type.String({ description: "Where the secret value originates (e.g. 'vault', 'ci')" })),
|
|
60
|
+
encrypted_value: Type.Optional(Type.String({ description: "Age-encrypted secret value (armored ciphertext, safe to commit)" })),
|
|
57
61
|
required: Type.Optional(Type.Boolean({ description: "Whether this secret is required for operation" })),
|
|
58
62
|
tags: Type.Optional(Type.Record(Type.String(), Type.String(), { description: "Key-value tags for grouping and filtering" }))
|
|
59
63
|
}, { description: "Metadata about a single secret" });
|
|
@@ -77,6 +81,12 @@ const CallbackConfigSchema = Type.Object({
|
|
|
77
81
|
on_audit_fail: Type.Optional(Type.String({ description: "Command or webhook on audit failure" }))
|
|
78
82
|
}, { description: "Automation callbacks for lifecycle events" });
|
|
79
83
|
const ToolsConfigSchema = Type.Record(Type.String(), Type.Unknown(), { description: "Tool integration configuration — open namespace for third-party extensions" });
|
|
84
|
+
const EnvMetaSchema = Type.Object({
|
|
85
|
+
value: Type.String({ description: "Default value for this environment variable" }),
|
|
86
|
+
purpose: Type.Optional(Type.String({ description: "Why this env var exists" })),
|
|
87
|
+
comment: Type.Optional(Type.String({ description: "Free-form annotation or note" })),
|
|
88
|
+
tags: Type.Optional(Type.Record(Type.String(), Type.String(), { description: "Key-value tags for grouping and filtering" }))
|
|
89
|
+
}, { description: "Metadata for a plaintext environment default (non-secret)" });
|
|
80
90
|
const EnvpktConfigSchema = Type.Object({
|
|
81
91
|
version: Type.Number({
|
|
82
92
|
description: "Schema version number",
|
|
@@ -84,7 +94,8 @@ const EnvpktConfigSchema = Type.Object({
|
|
|
84
94
|
}),
|
|
85
95
|
catalog: Type.Optional(Type.String({ description: "Path to shared secret catalog (relative to this config file)" })),
|
|
86
96
|
agent: Type.Optional(AgentIdentitySchema),
|
|
87
|
-
|
|
97
|
+
secret: Type.Optional(Type.Record(Type.String(), SecretMetaSchema, { description: "Per-secret metadata keyed by secret name" })),
|
|
98
|
+
env: Type.Optional(Type.Record(Type.String(), EnvMetaSchema, { description: "Plaintext environment defaults keyed by variable name" })),
|
|
88
99
|
lifecycle: Type.Optional(LifecycleConfigSchema),
|
|
89
100
|
callbacks: Type.Optional(CallbackConfigSchema),
|
|
90
101
|
tools: Type.Optional(ToolsConfigSchema)
|
|
@@ -106,11 +117,47 @@ const normalizeDates = (obj) => {
|
|
|
106
117
|
if (obj !== null && typeof obj === "object") return Object.fromEntries(Object.entries(obj).map(([k, v]) => [k, normalizeDates(v)]));
|
|
107
118
|
return obj;
|
|
108
119
|
};
|
|
120
|
+
/** Expand ~ and $ENV_VAR / ${ENV_VAR} in a path string */
|
|
121
|
+
const expandPath = (p) => {
|
|
122
|
+
return (p.startsWith("~/") || p === "~" ? join(homedir(), p.slice(1)) : p).replace(/\$\{(\w+)\}|\$(\w+)/g, (_, braced, bare) => {
|
|
123
|
+
const name = braced ?? bare ?? "";
|
|
124
|
+
return process.env[name] ?? "";
|
|
125
|
+
});
|
|
126
|
+
};
|
|
109
127
|
/** Find envpkt.toml in the given directory */
|
|
110
128
|
const findConfigPath = (dir) => {
|
|
111
129
|
const candidate = join(dir, CONFIG_FILENAME$1);
|
|
112
130
|
return existsSync(candidate) ? Option(candidate) : Option(void 0);
|
|
113
131
|
};
|
|
132
|
+
/** Ordered candidate paths for config discovery beyond CWD */
|
|
133
|
+
const CONFIG_SEARCH_PATHS = [
|
|
134
|
+
"~/.envpkt/envpkt.toml",
|
|
135
|
+
"$WINHOME/OneDrive/.envpkt/envpkt.toml",
|
|
136
|
+
"$USERPROFILE/OneDrive/.envpkt/envpkt.toml",
|
|
137
|
+
"~/Library/Mobile Documents/com~apple~CloudDocs/.envpkt/envpkt.toml",
|
|
138
|
+
"~/Dropbox/.envpkt/envpkt.toml",
|
|
139
|
+
"$DROPBOX_PATH/.envpkt/envpkt.toml",
|
|
140
|
+
"$GOOGLE_DRIVE/.envpkt/envpkt.toml",
|
|
141
|
+
"$WINHOME/.envpkt/envpkt.toml",
|
|
142
|
+
"$USERPROFILE/.envpkt/envpkt.toml"
|
|
143
|
+
];
|
|
144
|
+
/** Discover config by checking CWD, then ENVPKT_SEARCH_PATH, then built-in candidate paths */
|
|
145
|
+
const discoverConfig = (cwd) => {
|
|
146
|
+
const cwdCandidate = join(cwd ?? process.cwd(), CONFIG_FILENAME$1);
|
|
147
|
+
if (existsSync(cwdCandidate)) return Option({
|
|
148
|
+
path: cwdCandidate,
|
|
149
|
+
source: "cwd"
|
|
150
|
+
});
|
|
151
|
+
const customPaths = process.env.ENVPKT_SEARCH_PATH?.split(":").filter(Boolean) ?? [];
|
|
152
|
+
for (const template of [...customPaths, ...CONFIG_SEARCH_PATHS]) {
|
|
153
|
+
const expanded = expandPath(template);
|
|
154
|
+
if (expanded && !expanded.startsWith("/.envpkt") && existsSync(expanded)) return Option({
|
|
155
|
+
path: expanded,
|
|
156
|
+
source: "search"
|
|
157
|
+
});
|
|
158
|
+
}
|
|
159
|
+
return Option(void 0);
|
|
160
|
+
};
|
|
114
161
|
/** Read a config file, returning Either<ConfigError, string> */
|
|
115
162
|
const readConfigFile = (path) => {
|
|
116
163
|
if (!existsSync(path)) return Left({
|
|
@@ -122,14 +169,13 @@ const readConfigFile = (path) => {
|
|
|
122
169
|
message: String(err)
|
|
123
170
|
}), (content) => Right(content));
|
|
124
171
|
};
|
|
125
|
-
/** Ensure required fields have defaults for valid configs (e.g. agent configs with catalog may omit
|
|
172
|
+
/** Ensure required fields have defaults for valid configs (e.g. agent configs with catalog may omit secret) */
|
|
126
173
|
const applyDefaults = (data) => {
|
|
127
174
|
if (data !== null && typeof data === "object" && !Array.isArray(data)) {
|
|
128
|
-
const
|
|
129
|
-
if (!("
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
};
|
|
175
|
+
const result = { ...data };
|
|
176
|
+
if (!("secret" in result)) result.secret = {};
|
|
177
|
+
if (!("env" in result)) result.env = {};
|
|
178
|
+
return result;
|
|
133
179
|
}
|
|
134
180
|
return data;
|
|
135
181
|
};
|
|
@@ -148,27 +194,29 @@ const validateConfig = (data) => {
|
|
|
148
194
|
};
|
|
149
195
|
/** Load and validate an envpkt.toml from a file path */
|
|
150
196
|
const loadConfig = (path) => readConfigFile(path).flatMap(parseToml).flatMap(validateConfig);
|
|
151
|
-
/** Load config from CWD, returning
|
|
152
|
-
const loadConfigFromCwd = (cwd) => {
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
})));
|
|
161
|
-
};
|
|
197
|
+
/** Load config from CWD or discovery chain, returning path, source, and parsed config */
|
|
198
|
+
const loadConfigFromCwd = (cwd) => discoverConfig(cwd).fold(() => Left({
|
|
199
|
+
_tag: "FileNotFound",
|
|
200
|
+
path: join(cwd ?? process.cwd(), CONFIG_FILENAME$1)
|
|
201
|
+
}), ({ path, source }) => loadConfig(path).map((config) => ({
|
|
202
|
+
path,
|
|
203
|
+
source,
|
|
204
|
+
config
|
|
205
|
+
})));
|
|
162
206
|
/**
|
|
163
207
|
* Resolve config path via priority chain:
|
|
164
208
|
* 1. Explicit flag path
|
|
165
209
|
* 2. ENVPKT_CONFIG env var
|
|
166
|
-
* 3. CWD discovery
|
|
210
|
+
* 3. CWD + discovery chain (home dir, cloud storage, custom search paths)
|
|
167
211
|
*/
|
|
168
212
|
const resolveConfigPath = (flagPath, envVar, cwd) => {
|
|
169
213
|
if (flagPath) {
|
|
170
214
|
const resolved = resolve(flagPath);
|
|
171
|
-
|
|
215
|
+
const result = {
|
|
216
|
+
path: resolved,
|
|
217
|
+
source: "flag"
|
|
218
|
+
};
|
|
219
|
+
return existsSync(resolved) ? Right(result) : Left({
|
|
172
220
|
_tag: "FileNotFound",
|
|
173
221
|
path: resolved
|
|
174
222
|
});
|
|
@@ -176,16 +224,22 @@ const resolveConfigPath = (flagPath, envVar, cwd) => {
|
|
|
176
224
|
const envPath = envVar ?? process.env[ENV_VAR_CONFIG];
|
|
177
225
|
if (envPath) {
|
|
178
226
|
const resolved = resolve(envPath);
|
|
179
|
-
|
|
227
|
+
const result = {
|
|
228
|
+
path: resolved,
|
|
229
|
+
source: "env"
|
|
230
|
+
};
|
|
231
|
+
return existsSync(resolved) ? Right(result) : Left({
|
|
180
232
|
_tag: "FileNotFound",
|
|
181
233
|
path: resolved
|
|
182
234
|
});
|
|
183
235
|
}
|
|
184
|
-
|
|
185
|
-
return findConfigPath(dir).fold(() => Left({
|
|
236
|
+
return discoverConfig(cwd).fold(() => Left({
|
|
186
237
|
_tag: "FileNotFound",
|
|
187
|
-
path: join(
|
|
188
|
-
}), (path) => Right(
|
|
238
|
+
path: join(cwd ?? process.cwd(), CONFIG_FILENAME$1)
|
|
239
|
+
}), ({ path, source }) => Right({
|
|
240
|
+
path,
|
|
241
|
+
source
|
|
242
|
+
}));
|
|
189
243
|
};
|
|
190
244
|
|
|
191
245
|
//#endregion
|
|
@@ -234,13 +288,14 @@ const resolveConfig = (agentConfig, agentConfigDir) => {
|
|
|
234
288
|
});
|
|
235
289
|
const catalogPath = resolve(agentConfigDir, agentConfig.catalog);
|
|
236
290
|
const agentSecrets = agentConfig.agent.secrets;
|
|
237
|
-
|
|
291
|
+
const agentSecretEntries = agentConfig.secret ?? {};
|
|
292
|
+
return loadCatalog(catalogPath).flatMap((catalogConfig) => resolveSecrets(agentSecretEntries, catalogConfig.secret ?? {}, agentSecrets, catalogPath).map((resolvedMeta) => {
|
|
238
293
|
const merged = [];
|
|
239
294
|
const overridden = [];
|
|
240
295
|
const warnings = [];
|
|
241
296
|
for (const key of agentSecrets) {
|
|
242
297
|
merged.push(key);
|
|
243
|
-
if (
|
|
298
|
+
if (agentSecretEntries[key]) overridden.push(key);
|
|
244
299
|
}
|
|
245
300
|
const { catalog: _catalog, ...agentWithoutCatalog } = agentConfig;
|
|
246
301
|
const agentIdentity = agentConfig.agent ? (() => {
|
|
@@ -254,7 +309,7 @@ const resolveConfig = (agentConfig, agentConfigDir) => {
|
|
|
254
309
|
...agentIdentity,
|
|
255
310
|
name: agentIdentity.name
|
|
256
311
|
} : void 0,
|
|
257
|
-
|
|
312
|
+
secret: resolvedMeta
|
|
258
313
|
},
|
|
259
314
|
catalogPath,
|
|
260
315
|
merged,
|
|
@@ -307,12 +362,14 @@ const formatPacket = (result, options) => {
|
|
|
307
362
|
if (config.agent.expires) agentLines.push(` expires: ${config.agent.expires}`);
|
|
308
363
|
if (agentLines.length > 0) sections.push(agentLines.join("\n"));
|
|
309
364
|
}
|
|
310
|
-
const
|
|
365
|
+
const secretConfig = config.secret ?? {};
|
|
366
|
+
const metaEntries = Object.entries(secretConfig);
|
|
311
367
|
const secretHeader = `secrets: ${metaEntries.length}`;
|
|
312
368
|
const secretLines = metaEntries.map(([key, meta]) => {
|
|
313
369
|
const service = meta.service ?? key;
|
|
370
|
+
const sealedTag = meta.encrypted_value ? " [sealed]" : "";
|
|
314
371
|
const secretValue = options?.secrets?.[key];
|
|
315
|
-
const header = ` ${key} → ${service}${secretValue !== void 0 ? ` = ${(options?.secretDisplay ?? "encrypted") === "plaintext" ? secretValue : maskValue(secretValue)}` : ""}`;
|
|
372
|
+
const header = ` ${key} → ${service}${sealedTag}${secretValue !== void 0 ? ` = ${(options?.secretDisplay ?? "encrypted") === "plaintext" ? secretValue : maskValue(secretValue)}` : ""}`;
|
|
316
373
|
const fields = formatSecretFields(meta, " ");
|
|
317
374
|
return fields ? `${header}\n${fields}` : header;
|
|
318
375
|
});
|
|
@@ -341,7 +398,7 @@ const MS_PER_DAY = 864e5;
|
|
|
341
398
|
const WARN_BEFORE_DAYS = 30;
|
|
342
399
|
const daysBetween = (from, to) => Math.floor((to.getTime() - from.getTime()) / MS_PER_DAY);
|
|
343
400
|
const parseDate = (dateStr) => {
|
|
344
|
-
const d = /* @__PURE__ */ new Date(dateStr
|
|
401
|
+
const d = /* @__PURE__ */ new Date(`${dateStr}T00:00:00Z`);
|
|
345
402
|
return Number.isNaN(d.getTime()) ? Option(void 0) : Option(d);
|
|
346
403
|
};
|
|
347
404
|
const classifySecret = (key, meta, fnoxKeys, staleWarningDays, requireExpiration, requireService, today) => {
|
|
@@ -356,7 +413,8 @@ const classifySecret = (key, meta, fnoxKeys, staleWarningDays, requireExpiration
|
|
|
356
413
|
const isExpired = daysRemaining.fold(() => false, (d) => d < 0);
|
|
357
414
|
const isExpiringSoon = daysRemaining.fold(() => false, (d) => d >= 0 && d <= WARN_BEFORE_DAYS);
|
|
358
415
|
const isStale = daysSinceCreated.fold(() => false, (d) => d > staleWarningDays);
|
|
359
|
-
const
|
|
416
|
+
const hasSealed = !!meta?.encrypted_value;
|
|
417
|
+
const isMissing = fnoxKeys.size > 0 && !fnoxKeys.has(key) && !hasSealed;
|
|
360
418
|
const isMissingMetadata = requireExpiration && expires.isNone() || requireService && service.isNone();
|
|
361
419
|
if (isExpired) issues.push("Secret has expired");
|
|
362
420
|
if (isExpiringSoon) issues.push(`Expires in ${daysRemaining.fold(() => "?", (d) => String(d))} days`);
|
|
@@ -385,8 +443,9 @@ const computeAudit = (config, fnoxKeys, today) => {
|
|
|
385
443
|
const requireExpiration = lifecycle.require_expiration ?? false;
|
|
386
444
|
const requireService = lifecycle.require_service ?? false;
|
|
387
445
|
const keys = fnoxKeys ?? /* @__PURE__ */ new Set();
|
|
388
|
-
const
|
|
389
|
-
const
|
|
446
|
+
const secretEntries = config.secret ?? {};
|
|
447
|
+
const metaKeys = new Set(Object.keys(secretEntries));
|
|
448
|
+
const secrets = List(Object.entries(secretEntries).map(([key, meta]) => classifySecret(key, meta, keys, staleWarningDays, requireExpiration, requireService, now)));
|
|
390
449
|
const orphaned = keys.size > 0 ? [...metaKeys].filter((k) => !keys.has(k)).length : 0;
|
|
391
450
|
const total = secrets.size;
|
|
392
451
|
const expired = secrets.count((s) => s.status === "expired");
|
|
@@ -409,6 +468,28 @@ const computeAudit = (config, fnoxKeys, today) => {
|
|
|
409
468
|
agent: config.agent
|
|
410
469
|
};
|
|
411
470
|
};
|
|
471
|
+
const computeEnvAudit = (config, env = process.env) => {
|
|
472
|
+
const envEntries = config.env ?? {};
|
|
473
|
+
const entries = [];
|
|
474
|
+
for (const [key, entry] of Object.entries(envEntries)) {
|
|
475
|
+
const currentValue = env[key];
|
|
476
|
+
const status = Cond.of().when(currentValue === void 0, "missing").elseWhen(currentValue !== entry.value, "overridden").else("default");
|
|
477
|
+
entries.push({
|
|
478
|
+
key,
|
|
479
|
+
defaultValue: entry.value,
|
|
480
|
+
currentValue,
|
|
481
|
+
status,
|
|
482
|
+
purpose: entry.purpose
|
|
483
|
+
});
|
|
484
|
+
}
|
|
485
|
+
return {
|
|
486
|
+
entries,
|
|
487
|
+
total: entries.length,
|
|
488
|
+
defaults_applied: entries.filter((e) => e.status === "default").length,
|
|
489
|
+
overridden: entries.filter((e) => e.status === "overridden").length,
|
|
490
|
+
missing: entries.filter((e) => e.status === "missing").length
|
|
491
|
+
};
|
|
492
|
+
};
|
|
412
493
|
|
|
413
494
|
//#endregion
|
|
414
495
|
//#region src/core/patterns.ts
|
|
@@ -1041,7 +1122,7 @@ const matchValueShape = (value) => {
|
|
|
1041
1122
|
};
|
|
1042
1123
|
/** Strip common suffixes and derive a service name from an env var name */
|
|
1043
1124
|
const deriveServiceFromName = (name) => {
|
|
1044
|
-
const
|
|
1125
|
+
const matchedSuffix = [
|
|
1045
1126
|
"_API_KEY",
|
|
1046
1127
|
"_SECRET_KEY",
|
|
1047
1128
|
"_ACCESS_KEY",
|
|
@@ -1059,13 +1140,8 @@ const deriveServiceFromName = (name) => {
|
|
|
1059
1140
|
"_DSN",
|
|
1060
1141
|
"_URL",
|
|
1061
1142
|
"_URI"
|
|
1062
|
-
];
|
|
1063
|
-
|
|
1064
|
-
for (const suffix of suffixes) if (stripped.endsWith(suffix)) {
|
|
1065
|
-
stripped = stripped.slice(0, -suffix.length);
|
|
1066
|
-
break;
|
|
1067
|
-
}
|
|
1068
|
-
return stripped.toLowerCase().replace(/_/g, "-");
|
|
1143
|
+
].find((s) => name.endsWith(s));
|
|
1144
|
+
return (matchedSuffix ? name.slice(0, -matchedSuffix.length) : name).toLowerCase().replace(/_/g, "-");
|
|
1069
1145
|
};
|
|
1070
1146
|
/** Match a single env var against all patterns */
|
|
1071
1147
|
const matchEnvVar = (name, value) => {
|
|
@@ -1135,10 +1211,11 @@ const envScan = (env, options) => {
|
|
|
1135
1211
|
/** Bidirectional drift detection between config and live environment */
|
|
1136
1212
|
const envCheck = (config, env) => {
|
|
1137
1213
|
const entries = [];
|
|
1138
|
-
const
|
|
1214
|
+
const secretEntries = config.secret ?? {};
|
|
1215
|
+
const metaKeys = Object.keys(secretEntries);
|
|
1139
1216
|
const trackedSet = new Set(metaKeys);
|
|
1140
1217
|
for (const key of metaKeys) {
|
|
1141
|
-
const meta =
|
|
1218
|
+
const meta = secretEntries[key];
|
|
1142
1219
|
const present = env[key] !== void 0 && env[key] !== "";
|
|
1143
1220
|
entries.push({
|
|
1144
1221
|
envVar: key,
|
|
@@ -1147,6 +1224,17 @@ const envCheck = (config, env) => {
|
|
|
1147
1224
|
confidence: Option(void 0)
|
|
1148
1225
|
});
|
|
1149
1226
|
}
|
|
1227
|
+
const envDefaults = config.env ?? {};
|
|
1228
|
+
for (const key of Object.keys(envDefaults)) if (!trackedSet.has(key)) {
|
|
1229
|
+
trackedSet.add(key);
|
|
1230
|
+
const present = env[key] !== void 0 && env[key] !== "";
|
|
1231
|
+
entries.push({
|
|
1232
|
+
envVar: key,
|
|
1233
|
+
service: Option(void 0),
|
|
1234
|
+
status: present ? "tracked" : "missing_from_env",
|
|
1235
|
+
confidence: Option(void 0)
|
|
1236
|
+
});
|
|
1237
|
+
}
|
|
1150
1238
|
const envMatches = scanEnv(env);
|
|
1151
1239
|
for (const match of envMatches) if (!trackedSet.has(match.envVar)) entries.push({
|
|
1152
1240
|
envVar: match.envVar,
|
|
@@ -1166,12 +1254,12 @@ const envCheck = (config, env) => {
|
|
|
1166
1254
|
};
|
|
1167
1255
|
};
|
|
1168
1256
|
const todayIso = () => (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
|
|
1169
|
-
/** Generate TOML [
|
|
1257
|
+
/** Generate TOML [secret.*] blocks from scan results, mirroring init.ts pattern */
|
|
1170
1258
|
const generateTomlFromScan = (matches) => {
|
|
1171
1259
|
const blocks = [];
|
|
1172
1260
|
for (const match of matches) {
|
|
1173
1261
|
const svc = match.service.fold(() => match.envVar.toLowerCase().replace(/_/g, "-"), (s) => s);
|
|
1174
|
-
blocks.push(`[
|
|
1262
|
+
blocks.push(`[secret.${match.envVar}]
|
|
1175
1263
|
service = "${svc}"
|
|
1176
1264
|
# purpose = "" # Why: what this secret enables
|
|
1177
1265
|
# capabilities = [] # What operations this grants
|
|
@@ -1304,19 +1392,120 @@ const readFnoxConfig = (path) => Try(() => readFileSync(path, "utf-8")).fold((er
|
|
|
1304
1392
|
/** Extract the set of secret key names from a parsed fnox config */
|
|
1305
1393
|
const extractFnoxKeys = (config) => new Set(Object.keys(config.secrets));
|
|
1306
1394
|
|
|
1395
|
+
//#endregion
|
|
1396
|
+
//#region src/core/seal.ts
|
|
1397
|
+
/** Encrypt a plaintext string using age with the given recipient public key (armored output) */
|
|
1398
|
+
const ageEncrypt = (plaintext, recipient) => {
|
|
1399
|
+
if (!ageAvailable()) return Left({
|
|
1400
|
+
_tag: "AgeNotFound",
|
|
1401
|
+
message: "age CLI not found on PATH"
|
|
1402
|
+
});
|
|
1403
|
+
return Try(() => execFileSync("age", [
|
|
1404
|
+
"--encrypt",
|
|
1405
|
+
"--recipient",
|
|
1406
|
+
recipient,
|
|
1407
|
+
"--armor"
|
|
1408
|
+
], {
|
|
1409
|
+
input: plaintext,
|
|
1410
|
+
stdio: [
|
|
1411
|
+
"pipe",
|
|
1412
|
+
"pipe",
|
|
1413
|
+
"pipe"
|
|
1414
|
+
],
|
|
1415
|
+
encoding: "utf-8"
|
|
1416
|
+
})).fold((err) => Left({
|
|
1417
|
+
_tag: "EncryptFailed",
|
|
1418
|
+
key: "",
|
|
1419
|
+
message: `age encrypt failed: ${err}`
|
|
1420
|
+
}), (output) => Right(output.trim()));
|
|
1421
|
+
};
|
|
1422
|
+
/** Decrypt an age-armored ciphertext using the given identity file */
|
|
1423
|
+
const ageDecrypt = (ciphertext, identityPath) => {
|
|
1424
|
+
if (!ageAvailable()) return Left({
|
|
1425
|
+
_tag: "AgeNotFound",
|
|
1426
|
+
message: "age CLI not found on PATH"
|
|
1427
|
+
});
|
|
1428
|
+
return Try(() => execFileSync("age", [
|
|
1429
|
+
"--decrypt",
|
|
1430
|
+
"--identity",
|
|
1431
|
+
identityPath
|
|
1432
|
+
], {
|
|
1433
|
+
input: ciphertext,
|
|
1434
|
+
stdio: [
|
|
1435
|
+
"pipe",
|
|
1436
|
+
"pipe",
|
|
1437
|
+
"pipe"
|
|
1438
|
+
],
|
|
1439
|
+
encoding: "utf-8"
|
|
1440
|
+
})).fold((err) => Left({
|
|
1441
|
+
_tag: "DecryptFailed",
|
|
1442
|
+
key: "",
|
|
1443
|
+
message: `age decrypt failed: ${err}`
|
|
1444
|
+
}), (output) => Right(output.trim()));
|
|
1445
|
+
};
|
|
1446
|
+
/** Seal multiple secrets: encrypt each value with the recipient key and set encrypted_value on meta */
|
|
1447
|
+
const sealSecrets = (meta, values, recipient) => {
|
|
1448
|
+
if (!ageAvailable()) return Left({
|
|
1449
|
+
_tag: "AgeNotFound",
|
|
1450
|
+
message: "age CLI not found on PATH"
|
|
1451
|
+
});
|
|
1452
|
+
const result = {};
|
|
1453
|
+
for (const [key, secretMeta] of Object.entries(meta)) {
|
|
1454
|
+
const plaintext = values[key];
|
|
1455
|
+
if (plaintext === void 0) {
|
|
1456
|
+
result[key] = secretMeta;
|
|
1457
|
+
continue;
|
|
1458
|
+
}
|
|
1459
|
+
const outcome = ageEncrypt(plaintext, recipient).fold((err) => Left({
|
|
1460
|
+
_tag: "EncryptFailed",
|
|
1461
|
+
key,
|
|
1462
|
+
message: err.message
|
|
1463
|
+
}), (ciphertext) => Right(ciphertext));
|
|
1464
|
+
const failed = outcome.fold((err) => err, () => void 0);
|
|
1465
|
+
if (failed) return Left(failed);
|
|
1466
|
+
const ciphertext = outcome.fold(() => "", (v) => v);
|
|
1467
|
+
result[key] = {
|
|
1468
|
+
...secretMeta,
|
|
1469
|
+
encrypted_value: ciphertext
|
|
1470
|
+
};
|
|
1471
|
+
}
|
|
1472
|
+
return Right(result);
|
|
1473
|
+
};
|
|
1474
|
+
/** Unseal secrets: decrypt encrypted_value for each meta entry that has one */
|
|
1475
|
+
const unsealSecrets = (meta, identityPath) => {
|
|
1476
|
+
if (!ageAvailable()) return Left({
|
|
1477
|
+
_tag: "AgeNotFound",
|
|
1478
|
+
message: "age CLI not found on PATH"
|
|
1479
|
+
});
|
|
1480
|
+
const result = {};
|
|
1481
|
+
for (const [key, secretMeta] of Object.entries(meta)) {
|
|
1482
|
+
if (!secretMeta.encrypted_value) continue;
|
|
1483
|
+
const outcome = ageDecrypt(secretMeta.encrypted_value, identityPath).fold((err) => Left({
|
|
1484
|
+
_tag: "DecryptFailed",
|
|
1485
|
+
key,
|
|
1486
|
+
message: err.message
|
|
1487
|
+
}), (plaintext) => Right(plaintext));
|
|
1488
|
+
const failed = outcome.fold((err) => err, () => void 0);
|
|
1489
|
+
if (failed) return Left(failed);
|
|
1490
|
+
result[key] = outcome.fold(() => "", (v) => v);
|
|
1491
|
+
}
|
|
1492
|
+
return Right(result);
|
|
1493
|
+
};
|
|
1494
|
+
|
|
1307
1495
|
//#endregion
|
|
1308
1496
|
//#region src/core/boot.ts
|
|
1309
|
-
const resolveAndLoad = (opts) => resolveConfigPath(opts.configPath).fold((err) => Left(err), (configPath) => loadConfig(configPath).fold((err) => Left(err), (config) => {
|
|
1497
|
+
const resolveAndLoad = (opts) => resolveConfigPath(opts.configPath).fold((err) => Left(err), ({ path: configPath, source: configSource }) => loadConfig(configPath).fold((err) => Left(err), (config) => {
|
|
1310
1498
|
const configDir = dirname(configPath);
|
|
1311
1499
|
return resolveConfig(config, configDir).fold((err) => Left(err), (result) => Right({
|
|
1312
1500
|
config: result.config,
|
|
1313
1501
|
configPath,
|
|
1314
|
-
configDir
|
|
1502
|
+
configDir,
|
|
1503
|
+
configSource
|
|
1315
1504
|
}));
|
|
1316
1505
|
}));
|
|
1317
1506
|
const resolveAgentKey = (config, configDir) => {
|
|
1318
1507
|
if (!config.agent?.identity) return Right(void 0);
|
|
1319
|
-
return unwrapAgentKey(resolve(configDir, config.agent.identity)).fold((err) => Left(err), (key) => Right(key));
|
|
1508
|
+
return unwrapAgentKey(resolve(configDir, expandPath(config.agent.identity))).fold((err) => Left(err), (key) => Right(key));
|
|
1320
1509
|
};
|
|
1321
1510
|
const detectFnoxKeys = (configDir) => detectFnox(configDir).fold(() => /* @__PURE__ */ new Set(), (fnoxPath) => readFnoxConfig(fnoxPath).fold(() => /* @__PURE__ */ new Set(), (fnoxConfig) => extractFnoxKeys(fnoxConfig)));
|
|
1322
1511
|
const checkExpiration = (audit, failOnExpired, warnOnly) => {
|
|
@@ -1329,31 +1518,76 @@ const checkExpiration = (audit, failOnExpired, warnOnly) => {
|
|
|
1329
1518
|
if (audit.expired > 0 && warnOnly) warnings.push(`${audit.expired} secret(s) have expired (warn-only mode)`);
|
|
1330
1519
|
return Right(warnings);
|
|
1331
1520
|
};
|
|
1521
|
+
const SECRET_PATTERNS = [
|
|
1522
|
+
/^sk-/,
|
|
1523
|
+
/^ghp_/,
|
|
1524
|
+
/^ghu_/,
|
|
1525
|
+
/^AKIA[0-9A-Z]{16}/,
|
|
1526
|
+
/^xox[bpras]-/,
|
|
1527
|
+
/:\/\/[^:]+:[^@]+@/,
|
|
1528
|
+
/^ey[A-Za-z0-9_-]{20,}\.[A-Za-z0-9_-]{20,}/
|
|
1529
|
+
];
|
|
1530
|
+
const looksLikeSecret = (value) => {
|
|
1531
|
+
if (SECRET_PATTERNS.some((p) => p.test(value))) return true;
|
|
1532
|
+
if (value.length > 40 && /^[A-Za-z0-9+/=]+$/.test(value)) return true;
|
|
1533
|
+
return false;
|
|
1534
|
+
};
|
|
1535
|
+
const checkEnvMisclassification = (config) => {
|
|
1536
|
+
const warnings = [];
|
|
1537
|
+
const envEntries = config.env ?? {};
|
|
1538
|
+
for (const [key, entry] of Object.entries(envEntries)) if (looksLikeSecret(entry.value)) warnings.push(`[env.${key}] value looks like a secret — consider moving to [secret.${key}]`);
|
|
1539
|
+
return warnings;
|
|
1540
|
+
};
|
|
1332
1541
|
/** Programmatic boot — returns Either<BootError, BootResult> */
|
|
1333
1542
|
const bootSafe = (options) => {
|
|
1334
1543
|
const opts = options ?? {};
|
|
1335
1544
|
const inject = opts.inject !== false;
|
|
1336
1545
|
const failOnExpired = opts.failOnExpired !== false;
|
|
1337
1546
|
const warnOnly = opts.warnOnly ?? false;
|
|
1338
|
-
return resolveAndLoad(opts).flatMap(({ config, configDir }) =>
|
|
1547
|
+
return resolveAndLoad(opts).flatMap(({ config, configPath, configDir, configSource }) => {
|
|
1548
|
+
const secretEntries = config.secret ?? {};
|
|
1549
|
+
const metaKeys = Object.keys(secretEntries);
|
|
1550
|
+
const hasSealedValues = metaKeys.some((k) => !!secretEntries[k]?.encrypted_value);
|
|
1551
|
+
const agentKeyResult = resolveAgentKey(config, configDir);
|
|
1552
|
+
const agentKey = agentKeyResult.fold(() => void 0, (k) => k);
|
|
1553
|
+
const agentKeyError = agentKeyResult.fold((err) => err, () => void 0);
|
|
1554
|
+
if (agentKeyError && !hasSealedValues) return Left(agentKeyError);
|
|
1339
1555
|
const audit = computeAudit(config, detectFnoxKeys(configDir));
|
|
1340
1556
|
return checkExpiration(audit, failOnExpired, warnOnly).map((warnings) => {
|
|
1341
1557
|
const secrets = {};
|
|
1342
1558
|
const injected = [];
|
|
1343
1559
|
const skipped = [];
|
|
1344
|
-
|
|
1345
|
-
|
|
1560
|
+
warnings.push(...checkEnvMisclassification(config));
|
|
1561
|
+
const envEntries = config.env ?? {};
|
|
1562
|
+
const envDefaults = {};
|
|
1563
|
+
const overridden = [];
|
|
1564
|
+
for (const [key, entry] of Object.entries(envEntries)) if (process.env[key] === void 0) {
|
|
1565
|
+
envDefaults[key] = entry.value;
|
|
1566
|
+
if (inject) process.env[key] = entry.value;
|
|
1567
|
+
} else overridden.push(key);
|
|
1568
|
+
const sealedKeys = /* @__PURE__ */ new Set();
|
|
1569
|
+
if (hasSealedValues && config.agent?.identity) unsealSecrets(secretEntries, resolve(configDir, expandPath(config.agent.identity))).fold((err) => {
|
|
1570
|
+
warnings.push(`Sealed value decryption failed: ${err.message}`);
|
|
1571
|
+
}, (unsealed) => {
|
|
1572
|
+
for (const [key, value] of Object.entries(unsealed)) {
|
|
1573
|
+
secrets[key] = value;
|
|
1574
|
+
injected.push(key);
|
|
1575
|
+
sealedKeys.add(key);
|
|
1576
|
+
}
|
|
1577
|
+
});
|
|
1578
|
+
const remainingKeys = metaKeys.filter((k) => !sealedKeys.has(k));
|
|
1579
|
+
if (remainingKeys.length > 0) if (fnoxAvailable()) fnoxExport(opts.profile, agentKey).fold((err) => {
|
|
1346
1580
|
warnings.push(`fnox export failed: ${err.message}`);
|
|
1347
|
-
for (const key of
|
|
1581
|
+
for (const key of remainingKeys) skipped.push(key);
|
|
1348
1582
|
}, (exported) => {
|
|
1349
|
-
for (const key of
|
|
1583
|
+
for (const key of remainingKeys) if (key in exported) {
|
|
1350
1584
|
secrets[key] = exported[key];
|
|
1351
1585
|
injected.push(key);
|
|
1352
1586
|
} else skipped.push(key);
|
|
1353
1587
|
});
|
|
1354
1588
|
else {
|
|
1355
|
-
warnings.push("fnox not available — no secrets injected");
|
|
1356
|
-
for (const key of
|
|
1589
|
+
if (!hasSealedValues) warnings.push("fnox not available — no secrets injected");
|
|
1590
|
+
for (const key of remainingKeys) skipped.push(key);
|
|
1357
1591
|
}
|
|
1358
1592
|
if (inject) for (const [key, value] of Object.entries(secrets)) process.env[key] = value;
|
|
1359
1593
|
return {
|
|
@@ -1361,10 +1595,14 @@ const bootSafe = (options) => {
|
|
|
1361
1595
|
injected,
|
|
1362
1596
|
skipped,
|
|
1363
1597
|
secrets,
|
|
1364
|
-
warnings
|
|
1598
|
+
warnings,
|
|
1599
|
+
envDefaults,
|
|
1600
|
+
overridden,
|
|
1601
|
+
configPath,
|
|
1602
|
+
configSource
|
|
1365
1603
|
};
|
|
1366
1604
|
});
|
|
1367
|
-
})
|
|
1605
|
+
});
|
|
1368
1606
|
};
|
|
1369
1607
|
/** Programmatic boot — throws EnvpktBootError on failure */
|
|
1370
1608
|
const boot = (options) => bootSafe(options).fold((err) => {
|
|
@@ -1400,6 +1638,42 @@ const formatBootError = (error) => {
|
|
|
1400
1638
|
}
|
|
1401
1639
|
};
|
|
1402
1640
|
|
|
1641
|
+
//#endregion
|
|
1642
|
+
//#region src/core/resolve-values.ts
|
|
1643
|
+
/** Resolve plaintext values for the given keys via cascade: fnox → env → interactive prompt */
|
|
1644
|
+
const resolveValues = async (keys, profile, agentKey) => {
|
|
1645
|
+
const result = {};
|
|
1646
|
+
const remaining = new Set(keys);
|
|
1647
|
+
if (fnoxAvailable()) fnoxExport(profile, agentKey).fold(() => {}, (exported) => {
|
|
1648
|
+
for (const key of [...remaining]) if (key in exported) {
|
|
1649
|
+
result[key] = exported[key];
|
|
1650
|
+
remaining.delete(key);
|
|
1651
|
+
}
|
|
1652
|
+
});
|
|
1653
|
+
for (const key of [...remaining]) {
|
|
1654
|
+
const envValue = process.env[key];
|
|
1655
|
+
if (envValue !== void 0 && envValue !== "") {
|
|
1656
|
+
result[key] = envValue;
|
|
1657
|
+
remaining.delete(key);
|
|
1658
|
+
}
|
|
1659
|
+
}
|
|
1660
|
+
if (remaining.size > 0 && process.stdin.isTTY) {
|
|
1661
|
+
const rl = createInterface({
|
|
1662
|
+
input: process.stdin,
|
|
1663
|
+
output: process.stderr
|
|
1664
|
+
});
|
|
1665
|
+
const prompt = (question) => new Promise((resolve) => {
|
|
1666
|
+
rl.question(question, (answer) => resolve(answer));
|
|
1667
|
+
});
|
|
1668
|
+
for (const key of remaining) {
|
|
1669
|
+
const value = await prompt(`Enter value for ${key}: `);
|
|
1670
|
+
if (value !== "") result[key] = value;
|
|
1671
|
+
}
|
|
1672
|
+
rl.close();
|
|
1673
|
+
}
|
|
1674
|
+
return result;
|
|
1675
|
+
};
|
|
1676
|
+
|
|
1403
1677
|
//#endregion
|
|
1404
1678
|
//#region src/core/fleet.ts
|
|
1405
1679
|
const CONFIG_FILENAME = "envpkt.toml";
|
|
@@ -1433,10 +1707,7 @@ function* findEnvpktFiles(dir, maxDepth, currentDepth = 0) {
|
|
|
1433
1707
|
const configPath = join(dir, CONFIG_FILENAME);
|
|
1434
1708
|
if (Try(() => statSync(configPath).isFile()).fold(() => false, (v) => v)) yield configPath;
|
|
1435
1709
|
if (currentDepth >= maxDepth) return;
|
|
1436
|
-
|
|
1437
|
-
Try(() => readdirSync(dir, { withFileTypes: true })).fold(() => {}, (e) => {
|
|
1438
|
-
entries = e;
|
|
1439
|
-
});
|
|
1710
|
+
const entries = Try(() => readdirSync(dir, { withFileTypes: true })).fold(() => [], (e) => e);
|
|
1440
1711
|
for (const entry of entries) if (entry.isDirectory() && !SKIP_DIRS.has(entry.name) && !entry.name.startsWith(".")) yield* findEnvpktFiles(join(dir, entry.name), maxDepth, currentDepth + 1);
|
|
1441
1712
|
}
|
|
1442
1713
|
const scanFleet = (rootDir, options) => {
|
|
@@ -1481,7 +1752,7 @@ const compareFnoxAndEnvpkt = (fnoxKeys, envpktKeys) => {
|
|
|
1481
1752
|
//#endregion
|
|
1482
1753
|
//#region src/mcp/resources.ts
|
|
1483
1754
|
const loadConfigSafe = () => {
|
|
1484
|
-
return resolveConfigPath().fold(() => void 0, (path) => loadConfig(path).fold(() => void 0, (config) => ({
|
|
1755
|
+
return resolveConfigPath().fold(() => void 0, ({ path }) => loadConfig(path).fold(() => void 0, (config) => ({
|
|
1485
1756
|
config,
|
|
1486
1757
|
path
|
|
1487
1758
|
})));
|
|
@@ -1531,7 +1802,8 @@ const readCapabilities = () => {
|
|
|
1531
1802
|
const { config } = loaded;
|
|
1532
1803
|
const agentCapabilities = config.agent?.capabilities ?? [];
|
|
1533
1804
|
const secretCapabilities = {};
|
|
1534
|
-
|
|
1805
|
+
const secretEntries = config.secret ?? {};
|
|
1806
|
+
for (const [key, meta] of Object.entries(secretEntries)) if (meta.capabilities && meta.capabilities.length > 0) secretCapabilities[key] = meta.capabilities;
|
|
1535
1807
|
return { contents: [{
|
|
1536
1808
|
uri: "envpkt://capabilities",
|
|
1537
1809
|
mimeType: "application/json",
|
|
@@ -1572,7 +1844,7 @@ const loadConfigForTool = (configPath) => {
|
|
|
1572
1844
|
return resolveConfigPath(configPath).fold((err) => ({
|
|
1573
1845
|
ok: false,
|
|
1574
1846
|
result: errorResult(`Config error: ${err._tag} — ${err._tag === "FileNotFound" ? err.path : ""}`)
|
|
1575
|
-
}), (path) => loadConfig(path).fold((err) => ({
|
|
1847
|
+
}), ({ path }) => loadConfig(path).fold((err) => ({
|
|
1576
1848
|
ok: false,
|
|
1577
1849
|
result: errorResult(`Config error: ${err._tag} — ${err._tag === "ValidationError" ? err.errors.toArray().join(", ") : ""}`)
|
|
1578
1850
|
}), (config) => ({
|
|
@@ -1639,6 +1911,17 @@ const toolDefinitions = [
|
|
|
1639
1911
|
},
|
|
1640
1912
|
required: ["key"]
|
|
1641
1913
|
}
|
|
1914
|
+
},
|
|
1915
|
+
{
|
|
1916
|
+
name: "getEnvMeta",
|
|
1917
|
+
description: "Get metadata for environment defaults — returns configured default values, purposes, and current drift status",
|
|
1918
|
+
inputSchema: {
|
|
1919
|
+
type: "object",
|
|
1920
|
+
properties: { configPath: {
|
|
1921
|
+
type: "string",
|
|
1922
|
+
description: "Optional path to envpkt.toml"
|
|
1923
|
+
} }
|
|
1924
|
+
}
|
|
1642
1925
|
}
|
|
1643
1926
|
];
|
|
1644
1927
|
const handleGetPacketHealth = (args) => {
|
|
@@ -1672,7 +1955,8 @@ const handleListCapabilities = (args) => {
|
|
|
1672
1955
|
const { config } = loaded;
|
|
1673
1956
|
const agentCapabilities = config.agent?.capabilities ?? [];
|
|
1674
1957
|
const secretCapabilities = {};
|
|
1675
|
-
|
|
1958
|
+
const secretEntries = config.secret ?? {};
|
|
1959
|
+
for (const [key, meta] of Object.entries(secretEntries)) if (meta.capabilities && meta.capabilities.length > 0) secretCapabilities[key] = meta.capabilities;
|
|
1676
1960
|
return textResult(JSON.stringify({
|
|
1677
1961
|
agent: config.agent ? {
|
|
1678
1962
|
name: config.agent.name,
|
|
@@ -1680,7 +1964,8 @@ const handleListCapabilities = (args) => {
|
|
|
1680
1964
|
description: config.agent.description,
|
|
1681
1965
|
capabilities: agentCapabilities
|
|
1682
1966
|
} : null,
|
|
1683
|
-
secrets: secretCapabilities
|
|
1967
|
+
secrets: secretCapabilities,
|
|
1968
|
+
env_defaults: Object.keys(config.env ?? {}).length
|
|
1684
1969
|
}, null, 2));
|
|
1685
1970
|
};
|
|
1686
1971
|
const handleGetSecretMeta = (args) => {
|
|
@@ -1689,11 +1974,12 @@ const handleGetSecretMeta = (args) => {
|
|
|
1689
1974
|
const loaded = loadConfigForTool(args.configPath);
|
|
1690
1975
|
if (!loaded.ok) return loaded.result;
|
|
1691
1976
|
const { config } = loaded;
|
|
1692
|
-
const meta = config.
|
|
1977
|
+
const meta = (config.secret ?? {})[key];
|
|
1693
1978
|
if (!meta) return errorResult(`Secret not found: ${key}`);
|
|
1979
|
+
const { encrypted_value: _, ...safeMeta } = meta;
|
|
1694
1980
|
return textResult(JSON.stringify({
|
|
1695
1981
|
key,
|
|
1696
|
-
...
|
|
1982
|
+
...safeMeta
|
|
1697
1983
|
}, null, 2));
|
|
1698
1984
|
};
|
|
1699
1985
|
const handleCheckExpiration = (args) => {
|
|
@@ -1712,11 +1998,19 @@ const handleCheckExpiration = (args) => {
|
|
|
1712
1998
|
issues: s.issues.toArray()
|
|
1713
1999
|
}, null, 2)));
|
|
1714
2000
|
};
|
|
2001
|
+
const handleGetEnvMeta = (args) => {
|
|
2002
|
+
const loaded = loadConfigForTool(args.configPath);
|
|
2003
|
+
if (!loaded.ok) return loaded.result;
|
|
2004
|
+
const { config } = loaded;
|
|
2005
|
+
const envAudit = computeEnvAudit(config);
|
|
2006
|
+
return textResult(JSON.stringify(envAudit, null, 2));
|
|
2007
|
+
};
|
|
1715
2008
|
const handlers = {
|
|
1716
2009
|
getPacketHealth: handleGetPacketHealth,
|
|
1717
2010
|
listCapabilities: handleListCapabilities,
|
|
1718
2011
|
getSecretMeta: handleGetSecretMeta,
|
|
1719
|
-
checkExpiration: handleCheckExpiration
|
|
2012
|
+
checkExpiration: handleCheckExpiration,
|
|
2013
|
+
getEnvMeta: handleGetEnvMeta
|
|
1720
2014
|
};
|
|
1721
2015
|
const callTool = (name, args) => {
|
|
1722
2016
|
const handler = handlers[name];
|
|
@@ -1737,17 +2031,17 @@ const createServer = () => {
|
|
|
1737
2031
|
},
|
|
1738
2032
|
instructions: "envpkt provides credential lifecycle awareness for AI agents. Use tools to check health, capabilities, and secret metadata. No secret values are ever exposed."
|
|
1739
2033
|
});
|
|
1740
|
-
server.setRequestHandler(ListToolsRequestSchema,
|
|
2034
|
+
server.setRequestHandler(ListToolsRequestSchema, () => ({ tools: toolDefinitions.map((t) => ({
|
|
1741
2035
|
name: t.name,
|
|
1742
2036
|
description: t.description,
|
|
1743
2037
|
inputSchema: t.inputSchema
|
|
1744
2038
|
})) }));
|
|
1745
|
-
server.setRequestHandler(CallToolRequestSchema,
|
|
2039
|
+
server.setRequestHandler(CallToolRequestSchema, (request) => {
|
|
1746
2040
|
const { name, arguments: args } = request.params;
|
|
1747
2041
|
return callTool(name, args ?? {});
|
|
1748
2042
|
});
|
|
1749
|
-
server.setRequestHandler(ListResourcesRequestSchema,
|
|
1750
|
-
server.setRequestHandler(ReadResourceRequestSchema,
|
|
2043
|
+
server.setRequestHandler(ListResourcesRequestSchema, () => ({ resources: [...resourceDefinitions] }));
|
|
2044
|
+
server.setRequestHandler(ReadResourceRequestSchema, (request) => {
|
|
1751
2045
|
const { uri } = request.params;
|
|
1752
2046
|
const result = readResource(uri);
|
|
1753
2047
|
if (!result) return { contents: [{
|
|
@@ -1766,4 +2060,4 @@ const startServer = async () => {
|
|
|
1766
2060
|
};
|
|
1767
2061
|
|
|
1768
2062
|
//#endregion
|
|
1769
|
-
export { AgentIdentitySchema, CallbackConfigSchema, ConsumerType, EnvpktBootError, EnvpktConfigSchema, LifecycleConfigSchema, SecretMetaSchema, ToolsConfigSchema, ageAvailable, boot, bootSafe, callTool, compareFnoxAndEnvpkt, computeAudit, createServer, deriveServiceFromName, detectFnox, envCheck, envScan, extractFnoxKeys, findConfigPath, fnoxAvailable, fnoxExport, fnoxGet, formatPacket, generateTomlFromScan, loadCatalog, loadConfig, loadConfigFromCwd, maskValue, matchEnvVar, matchValueShape, parseToml, readConfigFile, readFnoxConfig, readResource, resolveConfig, resolveConfigPath, resolveSecrets, resourceDefinitions, scanEnv, scanFleet, startServer, toolDefinitions, unwrapAgentKey, validateConfig };
|
|
2063
|
+
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, generateTomlFromScan, loadCatalog, loadConfig, loadConfigFromCwd, maskValue, matchEnvVar, matchValueShape, parseToml, readConfigFile, readFnoxConfig, readResource, resolveConfig, resolveConfigPath, resolveSecrets, resolveValues, resourceDefinitions, scanEnv, scanFleet, sealSecrets, startServer, toolDefinitions, unsealSecrets, unwrapAgentKey, validateConfig };
|