envpkt 0.4.2 → 0.5.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/dist/cli.js +176 -19
- package/dist/index.d.ts +35 -1
- package/dist/index.js +126 -20
- package/package.json +3 -2
package/dist/cli.js
CHANGED
|
@@ -1,14 +1,15 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { existsSync, readFileSync, readdirSync, statSync, writeFileSync } from "node:fs";
|
|
2
|
+
import { chmodSync, existsSync, mkdirSync, readFileSync, readdirSync, statSync, writeFileSync } from "node:fs";
|
|
3
3
|
import { dirname, join, resolve } from "node:path";
|
|
4
4
|
import { fileURLToPath } from "node:url";
|
|
5
5
|
import { Command } from "commander";
|
|
6
6
|
import { Cond, Left, List, Option, Right, Try } from "functype";
|
|
7
|
-
import { homedir } from "node:os";
|
|
8
7
|
import { TypeCompiler } from "@sinclair/typebox/compiler";
|
|
8
|
+
import { Env, Fs, Path } from "functype-os";
|
|
9
9
|
import { TomlDate, parse, stringify } from "smol-toml";
|
|
10
10
|
import { FormatRegistry, Type } from "@sinclair/typebox";
|
|
11
11
|
import { execFileSync } from "node:child_process";
|
|
12
|
+
import { homedir } from "node:os";
|
|
12
13
|
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
13
14
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
14
15
|
import { CallToolRequestSchema, ListResourcesRequestSchema, ListToolsRequestSchema, ReadResourceRequestSchema } from "@modelcontextprotocol/sdk/types.js";
|
|
@@ -219,11 +220,11 @@ const normalizeDates = (obj) => {
|
|
|
219
220
|
if (obj !== null && typeof obj === "object") return Object.fromEntries(Object.entries(obj).map(([k, v]) => [k, normalizeDates(v)]));
|
|
220
221
|
return obj;
|
|
221
222
|
};
|
|
222
|
-
/** Expand ~ and $ENV_VAR / ${ENV_VAR} in a path string */
|
|
223
|
+
/** Expand ~ and $ENV_VAR / ${ENV_VAR} in a path string (silent — unresolved vars become "") */
|
|
223
224
|
const expandPath = (p) => {
|
|
224
|
-
return
|
|
225
|
+
return Path.expandTilde(p).replace(/\$\{(\w+)\}|\$(\w+)/g, (_, braced, bare) => {
|
|
225
226
|
const name = braced ?? bare ?? "";
|
|
226
|
-
return
|
|
227
|
+
return Env.getOrDefault(name, "");
|
|
227
228
|
});
|
|
228
229
|
};
|
|
229
230
|
/**
|
|
@@ -232,16 +233,16 @@ const expandPath = (p) => {
|
|
|
232
233
|
* Non-glob paths return a single-element array if they exist.
|
|
233
234
|
*/
|
|
234
235
|
const expandGlobPath = (expanded) => {
|
|
235
|
-
if (!expanded.includes("*")) return existsSync(expanded) ? [expanded] : [];
|
|
236
|
+
if (!expanded.includes("*")) return Fs.existsSync(expanded) ? [expanded] : [];
|
|
236
237
|
const segments = expanded.split("/");
|
|
237
238
|
const globIdx = segments.findIndex((s) => s.includes("*"));
|
|
238
239
|
if (globIdx < 0) return [];
|
|
239
240
|
const parentDir = segments.slice(0, globIdx).join("/");
|
|
240
241
|
const globSegment = segments[globIdx];
|
|
241
242
|
const suffix = segments.slice(globIdx + 1).join("/");
|
|
242
|
-
if (!existsSync(parentDir)) return [];
|
|
243
|
+
if (!Fs.existsSync(parentDir)) return [];
|
|
243
244
|
const prefix = globSegment.replace(/\*.*$/, "");
|
|
244
|
-
return readdirSync(parentDir).filter((entry) => entry.startsWith(prefix)).map((entry) => join(parentDir, entry, suffix)).filter((p) => existsSync(p));
|
|
245
|
+
return Fs.readdirSync(parentDir).fold(() => [], (entries) => entries.filter((entry) => entry.startsWith(prefix)).map((entry) => join(parentDir, entry, suffix)).filter((p) => Fs.existsSync(p)).toArray());
|
|
245
246
|
};
|
|
246
247
|
/** Ordered candidate paths for config discovery beyond CWD */
|
|
247
248
|
const CONFIG_SEARCH_PATHS = [
|
|
@@ -267,11 +268,11 @@ const CONFIG_SEARCH_PATHS = [
|
|
|
267
268
|
/** Discover config by checking CWD, then ENVPKT_SEARCH_PATH, then built-in candidate paths */
|
|
268
269
|
const discoverConfig = (cwd) => {
|
|
269
270
|
const cwdCandidate = join(cwd ?? process.cwd(), CONFIG_FILENAME$2);
|
|
270
|
-
if (existsSync(cwdCandidate)) return Option({
|
|
271
|
+
if (Fs.existsSync(cwdCandidate)) return Option({
|
|
271
272
|
path: cwdCandidate,
|
|
272
273
|
source: "cwd"
|
|
273
274
|
});
|
|
274
|
-
const customPaths =
|
|
275
|
+
const customPaths = Env.get("ENVPKT_SEARCH_PATH").fold(() => [], (v) => v.split(":").filter(Boolean));
|
|
275
276
|
for (const template of [...customPaths, ...CONFIG_SEARCH_PATHS]) {
|
|
276
277
|
const expanded = expandPath(template);
|
|
277
278
|
if (!expanded || expanded.startsWith("/.envpkt")) continue;
|
|
@@ -285,14 +286,14 @@ const discoverConfig = (cwd) => {
|
|
|
285
286
|
};
|
|
286
287
|
/** Read a config file, returning Either<ConfigError, string> */
|
|
287
288
|
const readConfigFile = (path) => {
|
|
288
|
-
if (!existsSync(path)) return Left({
|
|
289
|
+
if (!Fs.existsSync(path)) return Left({
|
|
289
290
|
_tag: "FileNotFound",
|
|
290
291
|
path
|
|
291
292
|
});
|
|
292
|
-
return
|
|
293
|
+
return Fs.readFileSync(path, "utf-8").mapLeft((err) => ({
|
|
293
294
|
_tag: "ReadError",
|
|
294
|
-
message:
|
|
295
|
-
})
|
|
295
|
+
message: err.message
|
|
296
|
+
}));
|
|
296
297
|
};
|
|
297
298
|
/** Ensure required fields have defaults for valid configs (e.g. agent configs with catalog may omit secret) */
|
|
298
299
|
const applyDefaults = (data) => {
|
|
@@ -332,19 +333,19 @@ const resolveConfigPath = (flagPath, envVar, cwd) => {
|
|
|
332
333
|
path: resolved,
|
|
333
334
|
source: "flag"
|
|
334
335
|
};
|
|
335
|
-
return existsSync(resolved) ? Right(result) : Left({
|
|
336
|
+
return Fs.existsSync(resolved) ? Right(result) : Left({
|
|
336
337
|
_tag: "FileNotFound",
|
|
337
338
|
path: resolved
|
|
338
339
|
});
|
|
339
340
|
}
|
|
340
|
-
const envPath = envVar ??
|
|
341
|
+
const envPath = envVar ?? Env.get(ENV_VAR_CONFIG).fold(() => void 0, (v) => v);
|
|
341
342
|
if (envPath) {
|
|
342
343
|
const resolved = resolve(envPath);
|
|
343
344
|
const result = {
|
|
344
345
|
path: resolved,
|
|
345
346
|
source: "env"
|
|
346
347
|
};
|
|
347
|
-
return existsSync(resolved) ? Right(result) : Left({
|
|
348
|
+
return Fs.existsSync(resolved) ? Right(result) : Left({
|
|
348
349
|
_tag: "FileNotFound",
|
|
349
350
|
path: resolved
|
|
350
351
|
});
|
|
@@ -542,6 +543,10 @@ const formatError = (error) => {
|
|
|
542
543
|
case "CatalogLoadError": return `${RED}Error:${RESET} Catalog load error: ${error.message}`;
|
|
543
544
|
case "SecretNotInCatalog": return `${RED}Error:${RESET} Secret "${error.key}" not found in catalog: ${error.path}`;
|
|
544
545
|
case "MissingSecretsList": return `${RED}Error:${RESET} ${error.message}`;
|
|
546
|
+
case "KeygenFailed": return `${RED}Error:${RESET} Keygen failed: ${error.message}`;
|
|
547
|
+
case "KeyExists": return `${YELLOW}Warning:${RESET} Identity file already exists: ${error.path}\n${DIM}Use --force to overwrite.${RESET}`;
|
|
548
|
+
case "WriteError": return `${RED}Error:${RESET} Write failed: ${error.message}`;
|
|
549
|
+
case "ConfigUpdateError": return `${RED}Error:${RESET} Config update failed: ${error.message}`;
|
|
545
550
|
default: return `${RED}Error:${RESET} ${error.message ?? tag}`;
|
|
546
551
|
}
|
|
547
552
|
};
|
|
@@ -1812,6 +1817,12 @@ created = "${todayIso$1()}"
|
|
|
1812
1817
|
|
|
1813
1818
|
//#endregion
|
|
1814
1819
|
//#region src/cli/commands/env.ts
|
|
1820
|
+
const printPostWriteGuidance = () => {
|
|
1821
|
+
console.log(`\n${DIM}Note: Secret values are NOT stored — only metadata.${RESET}`);
|
|
1822
|
+
console.log(`${BOLD}Next steps:${RESET}`);
|
|
1823
|
+
console.log(` ${DIM}1.${RESET} envpkt keygen ${DIM}# generate age key (if no recipient configured)${RESET}`);
|
|
1824
|
+
console.log(` ${DIM}2.${RESET} envpkt seal ${DIM}# encrypt secret values into envpkt.toml${RESET}`);
|
|
1825
|
+
};
|
|
1815
1826
|
const runEnvScan = (options) => {
|
|
1816
1827
|
const scan = envScan(process.env, { includeUnknown: options.includeUnknown });
|
|
1817
1828
|
if (scan.discovered.size === 0) {
|
|
@@ -1841,6 +1852,7 @@ const runEnvScan = (options) => {
|
|
|
1841
1852
|
process.exit(1);
|
|
1842
1853
|
}, () => {
|
|
1843
1854
|
console.log(`\n${GREEN}✓${RESET} Appended ${BOLD}${newEntries.length}${RESET} new entry/entries to ${CYAN}${configPath}${RESET}`);
|
|
1855
|
+
printPostWriteGuidance();
|
|
1844
1856
|
});
|
|
1845
1857
|
} else {
|
|
1846
1858
|
const header = `#:schema https://raw.githubusercontent.com/jordanburke/envpkt/main/schemas/envpkt.schema.json\n\nversion = 1\n\n[lifecycle]\nstale_warning_days = 90\n\n`;
|
|
@@ -1849,6 +1861,7 @@ const runEnvScan = (options) => {
|
|
|
1849
1861
|
process.exit(1);
|
|
1850
1862
|
}, () => {
|
|
1851
1863
|
console.log(`\n${GREEN}✓${RESET} Created ${CYAN}${configPath}${RESET} with ${BOLD}${scan.discovered.size}${RESET} credential(s)`);
|
|
1864
|
+
printPostWriteGuidance();
|
|
1852
1865
|
});
|
|
1853
1866
|
}
|
|
1854
1867
|
}
|
|
@@ -2271,6 +2284,143 @@ const runInspect = (options) => {
|
|
|
2271
2284
|
});
|
|
2272
2285
|
};
|
|
2273
2286
|
|
|
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
|
+
//#endregion
|
|
2388
|
+
//#region src/cli/commands/keygen.ts
|
|
2389
|
+
const runKeygen = (options) => {
|
|
2390
|
+
const outputPath = options.output ?? resolveKeyPath();
|
|
2391
|
+
generateKeypair({
|
|
2392
|
+
force: options.force,
|
|
2393
|
+
outputPath
|
|
2394
|
+
}).fold((err) => {
|
|
2395
|
+
if (err._tag === "KeyExists") {
|
|
2396
|
+
console.error(`${YELLOW}Warning:${RESET} Identity file already exists: ${CYAN}${err.path}${RESET}`);
|
|
2397
|
+
console.error(`${DIM}Use --force to overwrite.${RESET}`);
|
|
2398
|
+
process.exit(1);
|
|
2399
|
+
}
|
|
2400
|
+
console.error(formatError(err));
|
|
2401
|
+
process.exit(2);
|
|
2402
|
+
}, ({ recipient, identityPath }) => {
|
|
2403
|
+
console.log(`${GREEN}Generated${RESET} age identity: ${CYAN}${identityPath}${RESET}`);
|
|
2404
|
+
console.log(`${BOLD}Recipient:${RESET} ${recipient}`);
|
|
2405
|
+
console.log("");
|
|
2406
|
+
const configPath = resolve(options.config ?? join(process.cwd(), "envpkt.toml"));
|
|
2407
|
+
if (existsSync(configPath)) updateConfigRecipient(configPath, recipient).fold((err) => {
|
|
2408
|
+
console.error(`${YELLOW}Warning:${RESET} Could not update config: ${"message" in err ? err.message : err._tag}`);
|
|
2409
|
+
console.log(`${DIM}Manually add to your envpkt.toml:${RESET}`);
|
|
2410
|
+
console.log(` [agent]`);
|
|
2411
|
+
console.log(` recipient = "${recipient}"`);
|
|
2412
|
+
}, () => {
|
|
2413
|
+
console.log(`${GREEN}Updated${RESET} ${CYAN}${configPath}${RESET} with agent.recipient`);
|
|
2414
|
+
});
|
|
2415
|
+
else {
|
|
2416
|
+
console.log(`${BOLD}Next steps:${RESET}`);
|
|
2417
|
+
console.log(` ${DIM}1.${RESET} envpkt init ${DIM}# create envpkt.toml${RESET}`);
|
|
2418
|
+
console.log(` ${DIM}2.${RESET} envpkt env scan --write ${DIM}# discover credentials${RESET}`);
|
|
2419
|
+
console.log(` ${DIM}3.${RESET} envpkt seal ${DIM}# encrypt secret values${RESET}`);
|
|
2420
|
+
}
|
|
2421
|
+
});
|
|
2422
|
+
};
|
|
2423
|
+
|
|
2274
2424
|
//#endregion
|
|
2275
2425
|
//#region src/mcp/resources.ts
|
|
2276
2426
|
const loadConfigSafe = () => {
|
|
@@ -2747,7 +2897,11 @@ const runSeal = async (options) => {
|
|
|
2747
2897
|
}, (c) => c);
|
|
2748
2898
|
if (!config.agent?.recipient) {
|
|
2749
2899
|
console.error(`${RED}Error:${RESET} agent.recipient is required for sealing (age public key)`);
|
|
2750
|
-
console.error(
|
|
2900
|
+
console.error("");
|
|
2901
|
+
console.error(`${BOLD}Quick fix:${RESET} run ${CYAN}envpkt keygen${RESET} to generate a key and auto-configure recipient`);
|
|
2902
|
+
console.error(`${DIM}Or manually add to your envpkt.toml:${RESET}`);
|
|
2903
|
+
console.error(`${DIM} [agent]${RESET}`);
|
|
2904
|
+
console.error(`${DIM} recipient = "age1..."${RESET}`);
|
|
2751
2905
|
process.exit(2);
|
|
2752
2906
|
}
|
|
2753
2907
|
const { recipient } = config.agent;
|
|
@@ -2844,7 +2998,7 @@ const runShellHook = (shell) => {
|
|
|
2844
2998
|
//#endregion
|
|
2845
2999
|
//#region src/cli/index.ts
|
|
2846
3000
|
const program = new Command();
|
|
2847
|
-
program.name("envpkt").description("Credential lifecycle and fleet management for AI agents\n\n Developer workflow: env scan →
|
|
3001
|
+
program.name("envpkt").description("Credential lifecycle and fleet management for AI agents\n\n Developer workflow: env scan → keygen → seal → eval $(envpkt env export)\n Agent / CI workflow: catalog → audit --strict → seal → exec --strict → fleet").version((() => {
|
|
2848
3002
|
const findPkgJson = (dir) => {
|
|
2849
3003
|
if (existsSync(join(dir, "package.json"))) return join(dir, "package.json");
|
|
2850
3004
|
const parent = dirname(dir);
|
|
@@ -2856,6 +3010,9 @@ program.name("envpkt").description("Credential lifecycle and fleet management fo
|
|
|
2856
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) => {
|
|
2857
3011
|
runInit(process.cwd(), options);
|
|
2858
3012
|
});
|
|
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) => {
|
|
3014
|
+
runKeygen(options);
|
|
3015
|
+
});
|
|
2859
3016
|
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) => {
|
|
2860
3017
|
runAudit(options);
|
|
2861
3018
|
});
|
package/dist/index.d.ts
CHANGED
|
@@ -272,6 +272,27 @@ type SealError = {
|
|
|
272
272
|
readonly _tag: "NoRecipient";
|
|
273
273
|
readonly message: string;
|
|
274
274
|
};
|
|
275
|
+
type KeygenError = {
|
|
276
|
+
readonly _tag: "AgeNotFound";
|
|
277
|
+
readonly message: string;
|
|
278
|
+
} | {
|
|
279
|
+
readonly _tag: "KeygenFailed";
|
|
280
|
+
readonly message: string;
|
|
281
|
+
} | {
|
|
282
|
+
readonly _tag: "KeyExists";
|
|
283
|
+
readonly path: string;
|
|
284
|
+
} | {
|
|
285
|
+
readonly _tag: "WriteError";
|
|
286
|
+
readonly message: string;
|
|
287
|
+
} | {
|
|
288
|
+
readonly _tag: "ConfigUpdateError";
|
|
289
|
+
readonly message: string;
|
|
290
|
+
};
|
|
291
|
+
type KeygenResult = {
|
|
292
|
+
readonly recipient: string;
|
|
293
|
+
readonly identityPath: string;
|
|
294
|
+
readonly configUpdated: boolean;
|
|
295
|
+
};
|
|
275
296
|
//#endregion
|
|
276
297
|
//#region src/core/config.d.ts
|
|
277
298
|
/** Find envpkt.toml in the given directory */
|
|
@@ -406,6 +427,19 @@ declare const sealSecrets: (meta: Readonly<Record<string, SecretMeta>>, values:
|
|
|
406
427
|
/** Unseal secrets: decrypt encrypted_value for each meta entry that has one */
|
|
407
428
|
declare const unsealSecrets: (meta: Readonly<Record<string, SecretMeta>>, identityPath: string) => Either<SealError, Record<string, string>>;
|
|
408
429
|
//#endregion
|
|
430
|
+
//#region src/core/keygen.d.ts
|
|
431
|
+
/** Resolve the age identity file path: ENVPKT_AGE_KEY_FILE env var > ~/.envpkt/age-key.txt */
|
|
432
|
+
declare const resolveKeyPath: () => string;
|
|
433
|
+
/** Resolve an inline age key from ENVPKT_AGE_KEY env var (for CI) */
|
|
434
|
+
declare const resolveInlineKey: () => Option<string>;
|
|
435
|
+
/** Generate an age keypair and write to disk */
|
|
436
|
+
declare const generateKeypair: (options?: {
|
|
437
|
+
readonly force?: boolean;
|
|
438
|
+
readonly outputPath?: string;
|
|
439
|
+
}) => Either<KeygenError, KeygenResult>;
|
|
440
|
+
/** Update agent.recipient in an envpkt.toml file, preserving structure */
|
|
441
|
+
declare const updateConfigRecipient: (configPath: string, recipient: string) => Either<KeygenError, true>;
|
|
442
|
+
//#endregion
|
|
409
443
|
//#region src/core/resolve-values.d.ts
|
|
410
444
|
/** Resolve plaintext values for the given keys via cascade: fnox → env → interactive prompt */
|
|
411
445
|
declare const resolveValues: (keys: ReadonlyArray<string>, profile?: string, agentKey?: string) => Promise<Record<string, string>>;
|
|
@@ -467,4 +501,4 @@ type ToolDef = {
|
|
|
467
501
|
declare const toolDefinitions: readonly ToolDef[];
|
|
468
502
|
declare const callTool: (name: string, args: Record<string, unknown>) => CallToolResult;
|
|
469
503
|
//#endregion
|
|
470
|
-
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 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, generateTomlFromScan, loadCatalog, loadConfig, loadConfigFromCwd, maskValue, matchEnvVar, matchValueShape, parseToml, readConfigFile, readFnoxConfig, readResource, resolveConfig, resolveConfigPath, resolveSecrets, resolveValues, resourceDefinitions, scanEnv, scanFleet, sealSecrets, startServer, toolDefinitions, unsealSecrets, unwrapAgentKey, validateConfig };
|
|
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 };
|
package/dist/index.js
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
import { FormatRegistry, Type } from "@sinclair/typebox";
|
|
2
|
-
import { existsSync, readFileSync, readdirSync, statSync } from "node:fs";
|
|
3
|
-
import { homedir } from "node:os";
|
|
4
2
|
import { dirname, join, resolve } from "node:path";
|
|
5
3
|
import { TypeCompiler } from "@sinclair/typebox/compiler";
|
|
6
|
-
import { Cond, Left, List, Option, Right, Try } from "functype";
|
|
4
|
+
import { Cond, Left, List, None, Option, Right, Some, Try } from "functype";
|
|
5
|
+
import { Env, Fs, Path } from "functype-os";
|
|
7
6
|
import { TomlDate, parse } from "smol-toml";
|
|
8
7
|
import { execFileSync } from "node:child_process";
|
|
8
|
+
import { chmodSync, existsSync, mkdirSync, readFileSync, readdirSync, statSync, writeFileSync } from "node:fs";
|
|
9
|
+
import { homedir } from "node:os";
|
|
9
10
|
import { createInterface } from "node:readline";
|
|
10
11
|
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
11
12
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
@@ -117,17 +118,17 @@ const normalizeDates = (obj) => {
|
|
|
117
118
|
if (obj !== null && typeof obj === "object") return Object.fromEntries(Object.entries(obj).map(([k, v]) => [k, normalizeDates(v)]));
|
|
118
119
|
return obj;
|
|
119
120
|
};
|
|
120
|
-
/** Expand ~ and $ENV_VAR / ${ENV_VAR} in a path string */
|
|
121
|
+
/** Expand ~ and $ENV_VAR / ${ENV_VAR} in a path string (silent — unresolved vars become "") */
|
|
121
122
|
const expandPath = (p) => {
|
|
122
|
-
return
|
|
123
|
+
return Path.expandTilde(p).replace(/\$\{(\w+)\}|\$(\w+)/g, (_, braced, bare) => {
|
|
123
124
|
const name = braced ?? bare ?? "";
|
|
124
|
-
return
|
|
125
|
+
return Env.getOrDefault(name, "");
|
|
125
126
|
});
|
|
126
127
|
};
|
|
127
128
|
/** Find envpkt.toml in the given directory */
|
|
128
129
|
const findConfigPath = (dir) => {
|
|
129
130
|
const candidate = join(dir, CONFIG_FILENAME$1);
|
|
130
|
-
return existsSync(candidate) ? Option(candidate) : Option(void 0);
|
|
131
|
+
return Fs.existsSync(candidate) ? Option(candidate) : Option(void 0);
|
|
131
132
|
};
|
|
132
133
|
/**
|
|
133
134
|
* Expand a path template that may contain a single `*` glob segment.
|
|
@@ -135,16 +136,16 @@ const findConfigPath = (dir) => {
|
|
|
135
136
|
* Non-glob paths return a single-element array if they exist.
|
|
136
137
|
*/
|
|
137
138
|
const expandGlobPath = (expanded) => {
|
|
138
|
-
if (!expanded.includes("*")) return existsSync(expanded) ? [expanded] : [];
|
|
139
|
+
if (!expanded.includes("*")) return Fs.existsSync(expanded) ? [expanded] : [];
|
|
139
140
|
const segments = expanded.split("/");
|
|
140
141
|
const globIdx = segments.findIndex((s) => s.includes("*"));
|
|
141
142
|
if (globIdx < 0) return [];
|
|
142
143
|
const parentDir = segments.slice(0, globIdx).join("/");
|
|
143
144
|
const globSegment = segments[globIdx];
|
|
144
145
|
const suffix = segments.slice(globIdx + 1).join("/");
|
|
145
|
-
if (!existsSync(parentDir)) return [];
|
|
146
|
+
if (!Fs.existsSync(parentDir)) return [];
|
|
146
147
|
const prefix = globSegment.replace(/\*.*$/, "");
|
|
147
|
-
return readdirSync(parentDir).filter((entry) => entry.startsWith(prefix)).map((entry) => join(parentDir, entry, suffix)).filter((p) => existsSync(p));
|
|
148
|
+
return Fs.readdirSync(parentDir).fold(() => [], (entries) => entries.filter((entry) => entry.startsWith(prefix)).map((entry) => join(parentDir, entry, suffix)).filter((p) => Fs.existsSync(p)).toArray());
|
|
148
149
|
};
|
|
149
150
|
/** Ordered candidate paths for config discovery beyond CWD */
|
|
150
151
|
const CONFIG_SEARCH_PATHS = [
|
|
@@ -170,11 +171,11 @@ const CONFIG_SEARCH_PATHS = [
|
|
|
170
171
|
/** Discover config by checking CWD, then ENVPKT_SEARCH_PATH, then built-in candidate paths */
|
|
171
172
|
const discoverConfig = (cwd) => {
|
|
172
173
|
const cwdCandidate = join(cwd ?? process.cwd(), CONFIG_FILENAME$1);
|
|
173
|
-
if (existsSync(cwdCandidate)) return Option({
|
|
174
|
+
if (Fs.existsSync(cwdCandidate)) return Option({
|
|
174
175
|
path: cwdCandidate,
|
|
175
176
|
source: "cwd"
|
|
176
177
|
});
|
|
177
|
-
const customPaths =
|
|
178
|
+
const customPaths = Env.get("ENVPKT_SEARCH_PATH").fold(() => [], (v) => v.split(":").filter(Boolean));
|
|
178
179
|
for (const template of [...customPaths, ...CONFIG_SEARCH_PATHS]) {
|
|
179
180
|
const expanded = expandPath(template);
|
|
180
181
|
if (!expanded || expanded.startsWith("/.envpkt")) continue;
|
|
@@ -188,14 +189,14 @@ const discoverConfig = (cwd) => {
|
|
|
188
189
|
};
|
|
189
190
|
/** Read a config file, returning Either<ConfigError, string> */
|
|
190
191
|
const readConfigFile = (path) => {
|
|
191
|
-
if (!existsSync(path)) return Left({
|
|
192
|
+
if (!Fs.existsSync(path)) return Left({
|
|
192
193
|
_tag: "FileNotFound",
|
|
193
194
|
path
|
|
194
195
|
});
|
|
195
|
-
return
|
|
196
|
+
return Fs.readFileSync(path, "utf-8").mapLeft((err) => ({
|
|
196
197
|
_tag: "ReadError",
|
|
197
|
-
message:
|
|
198
|
-
})
|
|
198
|
+
message: err.message
|
|
199
|
+
}));
|
|
199
200
|
};
|
|
200
201
|
/** Ensure required fields have defaults for valid configs (e.g. agent configs with catalog may omit secret) */
|
|
201
202
|
const applyDefaults = (data) => {
|
|
@@ -244,19 +245,19 @@ const resolveConfigPath = (flagPath, envVar, cwd) => {
|
|
|
244
245
|
path: resolved,
|
|
245
246
|
source: "flag"
|
|
246
247
|
};
|
|
247
|
-
return existsSync(resolved) ? Right(result) : Left({
|
|
248
|
+
return Fs.existsSync(resolved) ? Right(result) : Left({
|
|
248
249
|
_tag: "FileNotFound",
|
|
249
250
|
path: resolved
|
|
250
251
|
});
|
|
251
252
|
}
|
|
252
|
-
const envPath = envVar ??
|
|
253
|
+
const envPath = envVar ?? Env.get(ENV_VAR_CONFIG).fold(() => void 0, (v) => v);
|
|
253
254
|
if (envPath) {
|
|
254
255
|
const resolved = resolve(envPath);
|
|
255
256
|
const result = {
|
|
256
257
|
path: resolved,
|
|
257
258
|
source: "env"
|
|
258
259
|
};
|
|
259
|
-
return existsSync(resolved) ? Right(result) : Left({
|
|
260
|
+
return Fs.existsSync(resolved) ? Right(result) : Left({
|
|
260
261
|
_tag: "FileNotFound",
|
|
261
262
|
path: resolved
|
|
262
263
|
});
|
|
@@ -1666,6 +1667,111 @@ const formatBootError = (error) => {
|
|
|
1666
1667
|
}
|
|
1667
1668
|
};
|
|
1668
1669
|
|
|
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
|
+
|
|
1669
1775
|
//#endregion
|
|
1670
1776
|
//#region src/core/resolve-values.ts
|
|
1671
1777
|
/** Resolve plaintext values for the given keys via cascade: fnox → env → interactive prompt */
|
|
@@ -2088,4 +2194,4 @@ const startServer = async () => {
|
|
|
2088
2194
|
};
|
|
2089
2195
|
|
|
2090
2196
|
//#endregion
|
|
2091
|
-
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 };
|
|
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 };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "envpkt",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.0",
|
|
4
4
|
"description": "Credential lifecycle and fleet management for AI agents",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"credentials",
|
|
@@ -42,7 +42,8 @@
|
|
|
42
42
|
"@modelcontextprotocol/sdk": "^1.27.1",
|
|
43
43
|
"@sinclair/typebox": "^0.34.48",
|
|
44
44
|
"commander": "^14.0.3",
|
|
45
|
-
"functype": "^0.
|
|
45
|
+
"functype": "^0.49.0",
|
|
46
|
+
"functype-os": "^0.2.0",
|
|
46
47
|
"smol-toml": "^1.6.0"
|
|
47
48
|
},
|
|
48
49
|
"devDependencies": {
|