@frontmcp/utils 1.1.2 → 1.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/crypto/index.d.ts CHANGED
@@ -4,7 +4,10 @@
4
4
  * Provides cryptographic operations that work in both Node.js and browser environments.
5
5
  * Uses native crypto in Node.js and @noble/hashes + @noble/ciphers in browsers.
6
6
  */
7
+ import type { RsaJwk as RsaJwkNode, RsaKeyPair as RsaKeyPairNode } from './node';
7
8
  import type { CryptoProvider, EncBlob } from './types';
9
+ export type RsaJwk = RsaJwkNode;
10
+ export type RsaKeyPair = RsaKeyPairNode;
8
11
  export { isRsaPssAlg, jwtAlgToNodeAlg, jwtAlgToWebCryptoAlg } from './jwt-alg';
9
12
  /**
10
13
  * Get the crypto provider for the current runtime environment.
@@ -24,6 +27,50 @@ export declare function getCrypto(): CryptoProvider;
24
27
  * @returns Promise resolving to true if the signature is valid
25
28
  */
26
29
  export declare function rsaVerify(jwtAlg: string, data: Buffer | Uint8Array, publicJwk: JsonWebKey, signature: Buffer | Uint8Array): Promise<boolean>;
30
+ /**
31
+ * Sign data with an RSA private JWK and return a base64url-encoded signature.
32
+ *
33
+ * Node-only: signs synchronously using `crypto.createPrivateKey` + `crypto.sign`.
34
+ * Throws in non-Node runtimes since WebCrypto requires async key import and
35
+ * the audit signer needs a synchronous interface; production hosts that need
36
+ * RS256 audit signing run on Node.
37
+ *
38
+ * @param jwtAlg - JWT algorithm identifier (e.g. 'RS256', 'PS256')
39
+ * @param data - Bytes to sign
40
+ * @param privateJwk - Private key in JWK format
41
+ * @returns Base64url-encoded signature
42
+ */
43
+ export declare function rsaSignBase64Url(jwtAlg: string, data: Buffer | Uint8Array, privateJwk: JsonWebKey): string;
44
+ /**
45
+ * Synchronous companion to {@link rsaVerify}. Node-only — server-side audit
46
+ * verifiers and bundle signature verifiers need a sync surface so the
47
+ * `AuditSignatureVerifier` interface stays single-await for hot paths.
48
+ *
49
+ * Accepts RSA, RSA-PSS, and EdDSA (Ed25519) keys in JWK form. Returns
50
+ * `false` (never throws) on malformed keys / unsupported algs.
51
+ *
52
+ * @param jwtAlg - JWT algorithm identifier (`'RS256'`, `'PS256'`, `'EdDSA'`, ...)
53
+ * @param data - The signed data bytes
54
+ * @param publicJwk - Public key in JWK format
55
+ * @param signature - Raw signature bytes
56
+ */
57
+ export declare function rsaVerifySync(jwtAlg: string, data: Buffer | Uint8Array, publicJwk: JsonWebKey, signature: Buffer | Uint8Array): boolean;
58
+ /**
59
+ * Convert a PEM-encoded public key (SPKI) into a JWK so callers can route
60
+ * verification through {@link rsaVerifySync}. Node-only.
61
+ *
62
+ * Throws on malformed PEM — server-side callers should catch and translate
63
+ * to a structured failure reason.
64
+ */
65
+ export declare function pemToPublicJwk(pem: string): JsonWebKey;
66
+ /**
67
+ * Generate an RSA key pair (Node-only). Re-exported here so test code and
68
+ * host bootstrap don't have to reach into `node:crypto` directly.
69
+ *
70
+ * @param modulusLength - Key size in bits (default: 2048)
71
+ * @param alg - JWT algorithm (default: 'RS256')
72
+ */
73
+ export declare function generateRsaKeyPair(modulusLength?: number, alg?: string): RsaKeyPair;
27
74
  /**
28
75
  * Generate a UUID v4 string.
29
76
  */
package/crypto/node.d.ts CHANGED
@@ -49,6 +49,55 @@ export declare function generateRsaKeyPair(modulusLength?: number, alg?: string)
49
49
  */
50
50
  export declare function rsaSign(algorithm: string, data: Buffer, privateKey: crypto.KeyObject, options?: Omit<crypto.SignKeyObjectInput, 'key'>): Buffer;
51
51
  export declare function rsaVerify(jwtAlg: string, data: Buffer, publicJwk: JsonWebKey, signature: Buffer): boolean;
52
+ /**
53
+ * Sign data with an RSA private JWK and return a base64url-encoded signature.
54
+ *
55
+ * Mirror of {@link rsaVerify} on the signing side: takes a JWT alg name
56
+ * (`RS256`/`RS384`/`RS512`/`PS256`/`PS384`/`PS512`), a private key in JWK
57
+ * format, and the data bytes; returns the detached signature as a base64url
58
+ * string suitable for inline JSON envelopes (e.g. tamper-evident audit
59
+ * records that re-use the same key registry as bundle signing).
60
+ *
61
+ * Node-only — `crypto.createPrivateKey({ format: 'jwk' })` is not exposed in
62
+ * WebCrypto with the same shape, so audit signing is gated to Node runtimes.
63
+ *
64
+ * @param jwtAlg - JWT algorithm identifier (e.g. 'RS256', 'PS256')
65
+ * @param data - Bytes to sign
66
+ * @param privateJwk - Private key in JWK format
67
+ * @returns Base64url-encoded signature
68
+ */
69
+ export declare function rsaSignBase64Url(jwtAlg: string, data: Buffer | Uint8Array, privateJwk: JsonWebKey): string;
70
+ /**
71
+ * Verify an RSA / RSA-PSS / EdDSA signature synchronously.
72
+ *
73
+ * Node-only — mirrors {@link rsaSignBase64Url}'s sync, JWK-first surface so
74
+ * server-side callers (audit signer verifier, bundle signature verifier)
75
+ * have a single utility to call instead of touching `node:crypto` directly.
76
+ *
77
+ * - RS256/RS384/RS512 / PS256/PS384/PS512: pass `jwtAlg` and an RSA JWK.
78
+ * - EdDSA: pass `'EdDSA'` and an Ed25519 JWK (`kty: 'OKP'`, `crv: 'Ed25519'`).
79
+ *
80
+ * Returns `false` (never throws) for malformed keys / unsupported algs so
81
+ * callers can surface a single "verification failed" reason instead of
82
+ * branching on every internal crypto error.
83
+ *
84
+ * @param jwtAlg - JWT algorithm identifier (`'RS256'`, `'PS256'`, `'EdDSA'`, ...)
85
+ * @param data - Bytes that were signed
86
+ * @param publicJwk - Public key in JWK format
87
+ * @param signature - Signature bytes (raw, NOT base64url)
88
+ */
89
+ export declare function rsaVerifySync(jwtAlg: string, data: Buffer | Uint8Array, publicJwk: JsonWebKey, signature: Buffer | Uint8Array): boolean;
90
+ /**
91
+ * Convert a PEM-encoded public key (SPKI) to a JWK.
92
+ *
93
+ * Node-only. Used by host code that has historically configured trust roots
94
+ * as PEMs (bundle-signature trust list, audit verifier trust list) so the
95
+ * verifier path can normalize to JWK before calling {@link rsaVerifySync}.
96
+ *
97
+ * Throws if the PEM cannot be parsed — callers should catch and translate
98
+ * into a structured "malformed_public_key" reason where appropriate.
99
+ */
100
+ export declare function pemToPublicJwk(pem: string): JsonWebKey;
52
101
  /**
53
102
  * Create a JWT signed with an RSA key
54
103
  *
package/crypto/node.js CHANGED
@@ -36,8 +36,11 @@ __export(node_exports, {
36
36
  isRsaPssAlg: () => isRsaPssAlg,
37
37
  jwtAlgToNodeAlg: () => jwtAlgToNodeAlg,
38
38
  nodeCrypto: () => nodeCrypto,
39
+ pemToPublicJwk: () => pemToPublicJwk,
39
40
  rsaSign: () => rsaSign,
40
- rsaVerify: () => rsaVerify
41
+ rsaSignBase64Url: () => rsaSignBase64Url,
42
+ rsaVerify: () => rsaVerify,
43
+ rsaVerifySync: () => rsaVerifySync
41
44
  });
42
45
  module.exports = __toCommonJS(node_exports);
43
46
  var import_node_crypto = __toESM(require("node:crypto"));
@@ -154,6 +157,39 @@ function rsaVerify(jwtAlg, data, publicJwk, signature) {
154
157
  } : publicKey;
155
158
  return import_node_crypto.default.verify(nodeAlgorithm, data, verifyKey, signature);
156
159
  }
160
+ function rsaSignBase64Url(jwtAlg, data, privateJwk) {
161
+ const privateKey = import_node_crypto.default.createPrivateKey({ key: privateJwk, format: "jwk" });
162
+ const nodeAlgorithm = jwtAlgToNodeAlg(jwtAlg);
163
+ const buf = Buffer.isBuffer(data) ? data : Buffer.from(data);
164
+ const sig = isRsaPssAlg(jwtAlg) ? rsaSign(nodeAlgorithm, buf, privateKey, {
165
+ padding: import_node_crypto.default.constants.RSA_PKCS1_PSS_PADDING,
166
+ saltLength: import_node_crypto.default.constants.RSA_PSS_SALTLEN_DIGEST
167
+ }) : rsaSign(nodeAlgorithm, buf, privateKey);
168
+ return sig.toString("base64url");
169
+ }
170
+ function rsaVerifySync(jwtAlg, data, publicJwk, signature) {
171
+ try {
172
+ const publicKey = import_node_crypto.default.createPublicKey({ key: publicJwk, format: "jwk" });
173
+ const dataBuf = Buffer.isBuffer(data) ? data : Buffer.from(data);
174
+ const sigBuf = Buffer.isBuffer(signature) ? signature : Buffer.from(signature);
175
+ if (jwtAlg === "EdDSA") {
176
+ return import_node_crypto.default.verify(null, dataBuf, publicKey, sigBuf);
177
+ }
178
+ const nodeAlgorithm = jwtAlgToNodeAlg(jwtAlg);
179
+ const verifyKey = isRsaPssAlg(jwtAlg) ? {
180
+ key: publicKey,
181
+ padding: import_node_crypto.default.constants.RSA_PKCS1_PSS_PADDING,
182
+ saltLength: import_node_crypto.default.constants.RSA_PSS_SALTLEN_DIGEST
183
+ } : publicKey;
184
+ return import_node_crypto.default.verify(nodeAlgorithm, dataBuf, verifyKey, sigBuf);
185
+ } catch {
186
+ return false;
187
+ }
188
+ }
189
+ function pemToPublicJwk(pem) {
190
+ const key = import_node_crypto.default.createPublicKey({ key: pem, format: "pem" });
191
+ return key.export({ format: "jwk" });
192
+ }
157
193
  function createSignedJwt(payload, privateKey, kid, alg = "RS256") {
158
194
  const header = { alg, typ: "JWT", kid };
159
195
  const headerB64 = Buffer.from(JSON.stringify(header)).toString("base64url");
@@ -180,6 +216,9 @@ function createSignedJwt(payload, privateKey, kid, alg = "RS256") {
180
216
  isRsaPssAlg,
181
217
  jwtAlgToNodeAlg,
182
218
  nodeCrypto,
219
+ pemToPublicJwk,
183
220
  rsaSign,
184
- rsaVerify
221
+ rsaSignBase64Url,
222
+ rsaVerify,
223
+ rsaVerifySync
185
224
  });
@@ -113,6 +113,39 @@ function rsaVerify(jwtAlg, data, publicJwk, signature) {
113
113
  } : publicKey;
114
114
  return crypto.verify(nodeAlgorithm, data, verifyKey, signature);
115
115
  }
116
+ function rsaSignBase64Url(jwtAlg, data, privateJwk) {
117
+ const privateKey = crypto.createPrivateKey({ key: privateJwk, format: "jwk" });
118
+ const nodeAlgorithm = jwtAlgToNodeAlg(jwtAlg);
119
+ const buf = Buffer.isBuffer(data) ? data : Buffer.from(data);
120
+ const sig = isRsaPssAlg(jwtAlg) ? rsaSign(nodeAlgorithm, buf, privateKey, {
121
+ padding: crypto.constants.RSA_PKCS1_PSS_PADDING,
122
+ saltLength: crypto.constants.RSA_PSS_SALTLEN_DIGEST
123
+ }) : rsaSign(nodeAlgorithm, buf, privateKey);
124
+ return sig.toString("base64url");
125
+ }
126
+ function rsaVerifySync(jwtAlg, data, publicJwk, signature) {
127
+ try {
128
+ const publicKey = crypto.createPublicKey({ key: publicJwk, format: "jwk" });
129
+ const dataBuf = Buffer.isBuffer(data) ? data : Buffer.from(data);
130
+ const sigBuf = Buffer.isBuffer(signature) ? signature : Buffer.from(signature);
131
+ if (jwtAlg === "EdDSA") {
132
+ return crypto.verify(null, dataBuf, publicKey, sigBuf);
133
+ }
134
+ const nodeAlgorithm = jwtAlgToNodeAlg(jwtAlg);
135
+ const verifyKey = isRsaPssAlg(jwtAlg) ? {
136
+ key: publicKey,
137
+ padding: crypto.constants.RSA_PKCS1_PSS_PADDING,
138
+ saltLength: crypto.constants.RSA_PSS_SALTLEN_DIGEST
139
+ } : publicKey;
140
+ return crypto.verify(nodeAlgorithm, dataBuf, verifyKey, sigBuf);
141
+ } catch {
142
+ return false;
143
+ }
144
+ }
145
+ function pemToPublicJwk(pem) {
146
+ const key = crypto.createPublicKey({ key: pem, format: "pem" });
147
+ return key.export({ format: "jwk" });
148
+ }
116
149
  function createSignedJwt(payload, privateKey, kid, alg = "RS256") {
117
150
  const header = { alg, typ: "JWT", kid };
118
151
  const headerB64 = Buffer.from(JSON.stringify(header)).toString("base64url");
@@ -138,6 +171,9 @@ export {
138
171
  isRsaPssAlg,
139
172
  jwtAlgToNodeAlg,
140
173
  nodeCrypto,
174
+ pemToPublicJwk,
141
175
  rsaSign,
142
- rsaVerify
176
+ rsaSignBase64Url,
177
+ rsaVerify,
178
+ rsaVerifySync
143
179
  };
package/esm/index.mjs CHANGED
@@ -123,6 +123,14 @@ async function stat(p) {
123
123
  const fsp = getFsp();
124
124
  return fsp.stat(p);
125
125
  }
126
+ async function symlink(target, linkPath) {
127
+ const fsp = getFsp();
128
+ await fsp.symlink(target, linkPath);
129
+ }
130
+ async function realpath(p) {
131
+ const fsp = getFsp();
132
+ return fsp.realpath(p);
133
+ }
126
134
  async function copyFile(src, dest) {
127
135
  const fsp = getFsp();
128
136
  await fsp.copyFile(src, dest);
@@ -2481,8 +2489,11 @@ __export(node_exports, {
2481
2489
  isRsaPssAlg: () => isRsaPssAlg,
2482
2490
  jwtAlgToNodeAlg: () => jwtAlgToNodeAlg,
2483
2491
  nodeCrypto: () => nodeCrypto,
2492
+ pemToPublicJwk: () => pemToPublicJwk,
2484
2493
  rsaSign: () => rsaSign,
2485
- rsaVerify: () => rsaVerify
2494
+ rsaSignBase64Url: () => rsaSignBase64Url,
2495
+ rsaVerify: () => rsaVerify,
2496
+ rsaVerifySync: () => rsaVerifySync
2486
2497
  });
2487
2498
  import crypto2 from "node:crypto";
2488
2499
  function toUint8Array(buf) {
@@ -2523,6 +2534,39 @@ function rsaVerify(jwtAlg, data, publicJwk, signature) {
2523
2534
  } : publicKey;
2524
2535
  return crypto2.verify(nodeAlgorithm, data, verifyKey, signature);
2525
2536
  }
2537
+ function rsaSignBase64Url(jwtAlg, data, privateJwk) {
2538
+ const privateKey = crypto2.createPrivateKey({ key: privateJwk, format: "jwk" });
2539
+ const nodeAlgorithm = jwtAlgToNodeAlg(jwtAlg);
2540
+ const buf = Buffer.isBuffer(data) ? data : Buffer.from(data);
2541
+ const sig = isRsaPssAlg(jwtAlg) ? rsaSign(nodeAlgorithm, buf, privateKey, {
2542
+ padding: crypto2.constants.RSA_PKCS1_PSS_PADDING,
2543
+ saltLength: crypto2.constants.RSA_PSS_SALTLEN_DIGEST
2544
+ }) : rsaSign(nodeAlgorithm, buf, privateKey);
2545
+ return sig.toString("base64url");
2546
+ }
2547
+ function rsaVerifySync(jwtAlg, data, publicJwk, signature) {
2548
+ try {
2549
+ const publicKey = crypto2.createPublicKey({ key: publicJwk, format: "jwk" });
2550
+ const dataBuf = Buffer.isBuffer(data) ? data : Buffer.from(data);
2551
+ const sigBuf = Buffer.isBuffer(signature) ? signature : Buffer.from(signature);
2552
+ if (jwtAlg === "EdDSA") {
2553
+ return crypto2.verify(null, dataBuf, publicKey, sigBuf);
2554
+ }
2555
+ const nodeAlgorithm = jwtAlgToNodeAlg(jwtAlg);
2556
+ const verifyKey = isRsaPssAlg(jwtAlg) ? {
2557
+ key: publicKey,
2558
+ padding: crypto2.constants.RSA_PKCS1_PSS_PADDING,
2559
+ saltLength: crypto2.constants.RSA_PSS_SALTLEN_DIGEST
2560
+ } : publicKey;
2561
+ return crypto2.verify(nodeAlgorithm, dataBuf, verifyKey, sigBuf);
2562
+ } catch {
2563
+ return false;
2564
+ }
2565
+ }
2566
+ function pemToPublicJwk(pem) {
2567
+ const key = crypto2.createPublicKey({ key: pem, format: "pem" });
2568
+ return key.export({ format: "jwk" });
2569
+ }
2526
2570
  function createSignedJwt(payload, privateKey, kid, alg = "RS256") {
2527
2571
  const header = { alg, typ: "JWT", kid };
2528
2572
  const headerB64 = Buffer.from(JSON.stringify(header)).toString("base64url");
@@ -2733,6 +2777,30 @@ function rsaVerify2(jwtAlg, data, publicJwk, signature) {
2733
2777
  const { rsaVerifyBrowser: rsaVerifyBrowser2 } = (init_browser(), __toCommonJS(browser_exports));
2734
2778
  return rsaVerifyBrowser2(jwtAlg, data, publicJwk, signature);
2735
2779
  }
2780
+ function rsaSignBase64Url2(jwtAlg, data, privateJwk) {
2781
+ if (!isNode()) {
2782
+ throw new Error("rsaSignBase64Url is only available in Node.js runtimes");
2783
+ }
2784
+ return (init_node(), __toCommonJS(node_exports)).rsaSignBase64Url(jwtAlg, data, privateJwk);
2785
+ }
2786
+ function rsaVerifySync2(jwtAlg, data, publicJwk, signature) {
2787
+ if (!isNode()) {
2788
+ throw new Error("rsaVerifySync is only available in Node.js runtimes; use the async rsaVerify in browsers");
2789
+ }
2790
+ return (init_node(), __toCommonJS(node_exports)).rsaVerifySync(jwtAlg, data, publicJwk, signature);
2791
+ }
2792
+ function pemToPublicJwk2(pem) {
2793
+ if (!isNode()) {
2794
+ throw new Error("pemToPublicJwk is only available in Node.js runtimes");
2795
+ }
2796
+ return (init_node(), __toCommonJS(node_exports)).pemToPublicJwk(pem);
2797
+ }
2798
+ function generateRsaKeyPair2(modulusLength, alg) {
2799
+ if (!isNode()) {
2800
+ throw new Error("generateRsaKeyPair is only available in Node.js runtimes");
2801
+ }
2802
+ return (init_node(), __toCommonJS(node_exports)).generateRsaKeyPair(modulusLength, alg);
2803
+ }
2736
2804
  function randomUUID() {
2737
2805
  return getCrypto().randomUUID();
2738
2806
  }
@@ -4036,19 +4104,26 @@ function parseUriTemplate(template) {
4036
4104
  throw new Error("URI template has too many parameters (max 50)");
4037
4105
  }
4038
4106
  const paramNames = [];
4039
- let regexStr = template.replace(/[.*+?^${}()|[\]\\]/g, (match) => {
4040
- if (match === "{" || match === "}") return match;
4041
- return "\\" + match;
4042
- });
4043
- regexStr = regexStr.replace(/\{([^{}]+)\}/g, (_, paramName) => {
4044
- paramNames.push(paramName);
4045
- return "([^/]+)";
4046
- });
4107
+ const placeholder = /\{([^{}]+)\}/g;
4108
+ let regexStr = "";
4109
+ let lastIndex = 0;
4110
+ for (let m = placeholder.exec(template); m !== null; m = placeholder.exec(template)) {
4111
+ regexStr += escapeRegex(template.slice(lastIndex, m.index));
4112
+ const raw = m[1];
4113
+ const reserved = raw.startsWith("+");
4114
+ paramNames.push(reserved ? raw.slice(1) : raw);
4115
+ regexStr += reserved ? "(.+?)" : "([^/]+)";
4116
+ lastIndex = m.index + m[0].length;
4117
+ }
4118
+ regexStr += escapeRegex(template.slice(lastIndex));
4047
4119
  return {
4048
4120
  pattern: new RegExp(`^${regexStr}$`),
4049
4121
  paramNames
4050
4122
  };
4051
4123
  }
4124
+ function escapeRegex(literal) {
4125
+ return literal.replace(/[.*+?^${}()|[\]\\]/g, (c) => `\\${c}`);
4126
+ }
4052
4127
  function matchUriTemplate(template, uri) {
4053
4128
  const { pattern, paramNames } = parseUriTemplate(template);
4054
4129
  const match = uri.match(pattern);
@@ -4060,18 +4135,37 @@ function matchUriTemplate(template, uri) {
4060
4135
  return params;
4061
4136
  }
4062
4137
  function expandUriTemplate(template, params) {
4063
- return template.replace(/\{([^{}]+)\}/g, (_, paramName) => {
4138
+ return template.replace(/\{([^{}]+)\}/g, (_, raw) => {
4139
+ const reserved = raw.startsWith("+");
4140
+ const paramName = reserved ? raw.slice(1) : raw;
4064
4141
  const value = params[paramName];
4065
4142
  if (value === void 0) {
4066
4143
  throw new Error(`Missing parameter '${paramName}' for URI template '${template}'`);
4067
4144
  }
4068
- return encodeURIComponent(value);
4145
+ return reserved ? encodeReserved(value) : encodeURIComponent(value);
4069
4146
  });
4070
4147
  }
4148
+ function encodeReserved(value) {
4149
+ const decoded = safeDecode(value);
4150
+ return decoded.replace(/[^A-Za-z0-9\-._~:/?#[\]@!$&'()*+,;=]/g, (c) => encodeURIComponent(c));
4151
+ }
4152
+ function safeDecode(value) {
4153
+ try {
4154
+ return decodeURIComponent(value);
4155
+ } catch {
4156
+ return value.replace(/%[0-9A-Fa-f]{2}/g, (triplet) => {
4157
+ try {
4158
+ return decodeURIComponent(triplet);
4159
+ } catch {
4160
+ return triplet;
4161
+ }
4162
+ });
4163
+ }
4164
+ }
4071
4165
  function extractTemplateParams(template) {
4072
4166
  const params = [];
4073
- template.replace(/\{([^{}]+)\}/g, (_, paramName) => {
4074
- params.push(paramName);
4167
+ template.replace(/\{([^{}]+)\}/g, (_, raw) => {
4168
+ params.push(raw.startsWith("+") ? raw.slice(1) : raw);
4075
4169
  return "";
4076
4170
  });
4077
4171
  return params;
@@ -4090,7 +4184,7 @@ function joinPath(...parts) {
4090
4184
  }
4091
4185
 
4092
4186
  // libs/utils/src/path/node-path.ts
4093
- import { resolve, join, basename, dirname, extname } from "path";
4187
+ import { resolve, join, basename, dirname, extname, isAbsolute } from "path";
4094
4188
 
4095
4189
  // libs/utils/src/content/content.ts
4096
4190
  function sanitizeToJson(value) {
@@ -5421,6 +5515,7 @@ export {
5421
5515
  generateCodeChallenge,
5422
5516
  generateCodeVerifier,
5423
5517
  generatePkcePair,
5518
+ generateRsaKeyPair2 as generateRsaKeyPair,
5424
5519
  generateSecret,
5425
5520
  getCookie,
5426
5521
  getCrypto,
@@ -5437,6 +5532,7 @@ export {
5437
5532
  hmacSha256,
5438
5533
  idFromString,
5439
5534
  inferMimeType,
5535
+ isAbsolute,
5440
5536
  isAsymmetricKeyData,
5441
5537
  isBrowser,
5442
5538
  isDebug,
@@ -5477,6 +5573,7 @@ export {
5477
5573
  parseUriTemplate,
5478
5574
  join as pathJoin,
5479
5575
  resolve as pathResolve,
5576
+ pemToPublicJwk2 as pemToPublicJwk,
5480
5577
  processPlatformToolCalls,
5481
5578
  randomBytes,
5482
5579
  randomUUID,
@@ -5485,11 +5582,14 @@ export {
5485
5582
  readFileSync,
5486
5583
  readJSON,
5487
5584
  readdir,
5585
+ realpath,
5488
5586
  rename,
5489
5587
  resetRuntimeContext,
5490
5588
  resolveSecretPath,
5491
5589
  rm,
5590
+ rsaSignBase64Url2 as rsaSignBase64Url,
5492
5591
  rsaVerify2 as rsaVerify,
5592
+ rsaVerifySync2 as rsaVerifySync,
5493
5593
  runCmd,
5494
5594
  safeExec,
5495
5595
  safeJsonForScript,
@@ -5513,6 +5613,7 @@ export {
5513
5613
  splitWords,
5514
5614
  stat,
5515
5615
  supportsAnsi,
5616
+ symlink,
5516
5617
  timingSafeEqual,
5517
5618
  toCase,
5518
5619
  trimBoth,
package/fs/fs.d.ts CHANGED
@@ -160,6 +160,35 @@ export declare function ensureDir(p: string): Promise<void>;
160
160
  * if (stats.isDirectory()) { ... }
161
161
  */
162
162
  export declare function stat(p: string): Promise<import('fs').Stats>;
163
+ /**
164
+ * Create a symbolic link at `linkPath` pointing to `target`.
165
+ *
166
+ * **Node.js only** - throws an error if called in browser.
167
+ *
168
+ * @param target - Path the symlink should point to
169
+ * @param linkPath - Where to create the symlink
170
+ *
171
+ * @example
172
+ * await symlink('/etc/hosts', '/tmp/hosts-link');
173
+ */
174
+ export declare function symlink(target: string, linkPath: string): Promise<void>;
175
+ /**
176
+ * Resolve a path, following symbolic links to their final target.
177
+ *
178
+ * **Node.js only** - throws an error if called in browser.
179
+ *
180
+ * Unlike `pathResolve`, this returns the canonical path with symlinks
181
+ * resolved, which is what callers need when validating that a path stays
182
+ * inside a trusted root (`startsWith` checks on `pathResolve` are
183
+ * symlink-bypassable).
184
+ *
185
+ * @param p - Path to resolve
186
+ * @returns Canonical absolute path with symlinks resolved
187
+ *
188
+ * @example
189
+ * const real = await realpath('/var/log/app.log');
190
+ */
191
+ export declare function realpath(p: string): Promise<string>;
163
192
  /**
164
193
  * Copy a file.
165
194
  *
package/fs/index.d.ts CHANGED
@@ -1 +1 @@
1
- export { readFile, readFileSync, readFileBuffer, writeFile, mkdir, rename, unlink, stat, copyFile, cp, readdir, rm, mkdtemp, access, fileExists, readJSON, writeJSON, ensureDir, isDirEmpty, runCmd, } from './fs';
1
+ export { readFile, readFileSync, readFileBuffer, writeFile, mkdir, rename, unlink, stat, copyFile, cp, readdir, realpath, rm, mkdtemp, symlink, access, fileExists, readJSON, writeJSON, ensureDir, isDirEmpty, runCmd, } from './fs';
package/index.d.ts CHANGED
@@ -9,14 +9,14 @@ export { splitWords, toCase, sepFor, shortHash, ensureMaxLen, idFromString } fro
9
9
  export type { NameCase } from './naming';
10
10
  export { isValidMcpUri, extractUriScheme, isValidMcpUriTemplate, parseUriTemplate, matchUriTemplate, expandUriTemplate, extractTemplateParams, isUriTemplate, } from './uri';
11
11
  export type { ParsedUriTemplate } from './uri';
12
- export { trimSlashes, joinPath, pathResolve, pathJoin, basename, dirname, extname } from './path';
12
+ export { trimSlashes, joinPath, pathResolve, pathJoin, basename, dirname, extname, isAbsolute } from './path';
13
13
  export { sanitizeToJson, inferMimeType, findNonFiniteNumber } from './content';
14
14
  export { validateBaseUrl, buildSetCookie, parseCookies, getCookie, isLocalhost, isSecureRequest } from './http';
15
15
  export type { CookieOptions, SecureDetectionRequest } from './http';
16
- export { readFile, readFileSync, readFileBuffer, writeFile, mkdir, rename, unlink, stat, copyFile, cp, readdir, rm, mkdtemp, access, fileExists, readJSON, writeJSON, ensureDir, isDirEmpty, runCmd, } from './fs';
16
+ export { readFile, readFileSync, readFileBuffer, writeFile, mkdir, rename, unlink, stat, copyFile, cp, readdir, realpath, rm, mkdtemp, symlink, access, fileExists, readJSON, writeJSON, ensureDir, isDirEmpty, runCmd, } from './fs';
17
17
  export { escapeHtml, escapeHtmlAttr, escapeJsString, escapeScriptClose, safeJsonForScript } from './escape';
18
18
  export { safeStringify } from './serialization';
19
- export { getCrypto, rsaVerify, jwtAlgToNodeAlg, isRsaPssAlg, randomUUID, randomBytes, sha256, sha256Hex, sha256Base64url, hmacSha256, hkdfSha256, encryptAesGcm, decryptAesGcm, timingSafeEqual, bytesToHex, base64urlEncode, base64urlDecode, base64Encode, base64Decode, isNode, isBrowser, assertNode, EncryptedBlobError, encryptValue, decryptValue, tryDecryptValue, serializeBlob, deserializeBlob, tryDeserializeBlob, isValidEncryptedBlob, encryptAndSerialize, deserializeAndDecrypt, tryDeserializeAndDecrypt, generateCodeVerifier, generateCodeChallenge, verifyCodeChallenge, generatePkcePair, isValidCodeVerifier, isValidCodeChallenge, MIN_CODE_VERIFIER_LENGTH, MAX_CODE_VERIFIER_LENGTH, DEFAULT_CODE_VERIFIER_LENGTH, PkceError, type PkcePair, type SecretData, type SecretPersistenceOptions, type SecretValidationResult, secretDataSchema, validateSecretData, parseSecretData, isSecretPersistenceEnabled, resolveSecretPath, loadSecret, saveSecret, deleteSecret, generateSecret, createSecretData, getOrCreateSecret, clearCachedSecret, isSecretCached, type SignedData, type HmacSigningConfig, signData, verifyData, isSignedData, verifyOrParseData, type BaseKeyData, type SecretKeyData, type AsymmetricKeyData, type AnyKeyData, type KeyPersistenceOptions, type CreateKeyPersistenceOptions, type CreateSecretOptions, type CreateAsymmetricOptions, type KeyValidationResult, asymmetricAlgSchema, secretKeyDataSchema, asymmetricKeyDataSchema, anyKeyDataSchema, validateKeyData, parseKeyData, isSecretKeyData, isAsymmetricKeyData, KeyPersistence, createKeyPersistence, createKeyPersistenceWithStorage, } from './crypto';
19
+ export { getCrypto, rsaVerify, rsaVerifySync, pemToPublicJwk, generateRsaKeyPair, rsaSignBase64Url, type RsaJwk, type RsaKeyPair, jwtAlgToNodeAlg, isRsaPssAlg, randomUUID, randomBytes, sha256, sha256Hex, sha256Base64url, hmacSha256, hkdfSha256, encryptAesGcm, decryptAesGcm, timingSafeEqual, bytesToHex, base64urlEncode, base64urlDecode, base64Encode, base64Decode, isNode, isBrowser, assertNode, EncryptedBlobError, encryptValue, decryptValue, tryDecryptValue, serializeBlob, deserializeBlob, tryDeserializeBlob, isValidEncryptedBlob, encryptAndSerialize, deserializeAndDecrypt, tryDeserializeAndDecrypt, generateCodeVerifier, generateCodeChallenge, verifyCodeChallenge, generatePkcePair, isValidCodeVerifier, isValidCodeChallenge, MIN_CODE_VERIFIER_LENGTH, MAX_CODE_VERIFIER_LENGTH, DEFAULT_CODE_VERIFIER_LENGTH, PkceError, type PkcePair, type SecretData, type SecretPersistenceOptions, type SecretValidationResult, secretDataSchema, validateSecretData, parseSecretData, isSecretPersistenceEnabled, resolveSecretPath, loadSecret, saveSecret, deleteSecret, generateSecret, createSecretData, getOrCreateSecret, clearCachedSecret, isSecretCached, type SignedData, type HmacSigningConfig, signData, verifyData, isSignedData, verifyOrParseData, type BaseKeyData, type SecretKeyData, type AsymmetricKeyData, type AnyKeyData, type KeyPersistenceOptions, type CreateKeyPersistenceOptions, type CreateSecretOptions, type CreateAsymmetricOptions, type KeyValidationResult, asymmetricAlgSchema, secretKeyDataSchema, asymmetricKeyDataSchema, anyKeyDataSchema, validateKeyData, parseKeyData, isSecretKeyData, isAsymmetricKeyData, KeyPersistence, createKeyPersistence, createKeyPersistenceWithStorage, } from './crypto';
20
20
  export type { CryptoProvider, EncBlob, EncryptedBlob } from './crypto';
21
21
  export { AsyncLocalStorage } from './async-context';
22
22
  export { EventEmitter } from './event-emitter';
package/index.js CHANGED
@@ -128,6 +128,14 @@ async function stat(p) {
128
128
  const fsp = getFsp();
129
129
  return fsp.stat(p);
130
130
  }
131
+ async function symlink(target, linkPath) {
132
+ const fsp = getFsp();
133
+ await fsp.symlink(target, linkPath);
134
+ }
135
+ async function realpath(p) {
136
+ const fsp = getFsp();
137
+ return fsp.realpath(p);
138
+ }
131
139
  async function copyFile(src, dest) {
132
140
  const fsp = getFsp();
133
141
  await fsp.copyFile(src, dest);
@@ -2486,8 +2494,11 @@ __export(node_exports, {
2486
2494
  isRsaPssAlg: () => isRsaPssAlg,
2487
2495
  jwtAlgToNodeAlg: () => jwtAlgToNodeAlg,
2488
2496
  nodeCrypto: () => nodeCrypto,
2497
+ pemToPublicJwk: () => pemToPublicJwk,
2489
2498
  rsaSign: () => rsaSign,
2490
- rsaVerify: () => rsaVerify
2499
+ rsaSignBase64Url: () => rsaSignBase64Url,
2500
+ rsaVerify: () => rsaVerify,
2501
+ rsaVerifySync: () => rsaVerifySync
2491
2502
  });
2492
2503
  function toUint8Array(buf) {
2493
2504
  return new Uint8Array(buf.buffer, buf.byteOffset, buf.byteLength);
@@ -2527,6 +2538,39 @@ function rsaVerify(jwtAlg, data, publicJwk, signature) {
2527
2538
  } : publicKey;
2528
2539
  return import_node_crypto.default.verify(nodeAlgorithm, data, verifyKey, signature);
2529
2540
  }
2541
+ function rsaSignBase64Url(jwtAlg, data, privateJwk) {
2542
+ const privateKey = import_node_crypto.default.createPrivateKey({ key: privateJwk, format: "jwk" });
2543
+ const nodeAlgorithm = jwtAlgToNodeAlg(jwtAlg);
2544
+ const buf = Buffer.isBuffer(data) ? data : Buffer.from(data);
2545
+ const sig = isRsaPssAlg(jwtAlg) ? rsaSign(nodeAlgorithm, buf, privateKey, {
2546
+ padding: import_node_crypto.default.constants.RSA_PKCS1_PSS_PADDING,
2547
+ saltLength: import_node_crypto.default.constants.RSA_PSS_SALTLEN_DIGEST
2548
+ }) : rsaSign(nodeAlgorithm, buf, privateKey);
2549
+ return sig.toString("base64url");
2550
+ }
2551
+ function rsaVerifySync(jwtAlg, data, publicJwk, signature) {
2552
+ try {
2553
+ const publicKey = import_node_crypto.default.createPublicKey({ key: publicJwk, format: "jwk" });
2554
+ const dataBuf = Buffer.isBuffer(data) ? data : Buffer.from(data);
2555
+ const sigBuf = Buffer.isBuffer(signature) ? signature : Buffer.from(signature);
2556
+ if (jwtAlg === "EdDSA") {
2557
+ return import_node_crypto.default.verify(null, dataBuf, publicKey, sigBuf);
2558
+ }
2559
+ const nodeAlgorithm = jwtAlgToNodeAlg(jwtAlg);
2560
+ const verifyKey = isRsaPssAlg(jwtAlg) ? {
2561
+ key: publicKey,
2562
+ padding: import_node_crypto.default.constants.RSA_PKCS1_PSS_PADDING,
2563
+ saltLength: import_node_crypto.default.constants.RSA_PSS_SALTLEN_DIGEST
2564
+ } : publicKey;
2565
+ return import_node_crypto.default.verify(nodeAlgorithm, dataBuf, verifyKey, sigBuf);
2566
+ } catch {
2567
+ return false;
2568
+ }
2569
+ }
2570
+ function pemToPublicJwk(pem) {
2571
+ const key = import_node_crypto.default.createPublicKey({ key: pem, format: "pem" });
2572
+ return key.export({ format: "jwk" });
2573
+ }
2530
2574
  function createSignedJwt(payload, privateKey, kid, alg = "RS256") {
2531
2575
  const header = { alg, typ: "JWT", kid };
2532
2576
  const headerB64 = Buffer.from(JSON.stringify(header)).toString("base64url");
@@ -2737,6 +2781,30 @@ function rsaVerify2(jwtAlg, data, publicJwk, signature) {
2737
2781
  const { rsaVerifyBrowser: rsaVerifyBrowser2 } = (init_browser(), __toCommonJS(browser_exports));
2738
2782
  return rsaVerifyBrowser2(jwtAlg, data, publicJwk, signature);
2739
2783
  }
2784
+ function rsaSignBase64Url2(jwtAlg, data, privateJwk) {
2785
+ if (!isNode()) {
2786
+ throw new Error("rsaSignBase64Url is only available in Node.js runtimes");
2787
+ }
2788
+ return (init_node(), __toCommonJS(node_exports)).rsaSignBase64Url(jwtAlg, data, privateJwk);
2789
+ }
2790
+ function rsaVerifySync2(jwtAlg, data, publicJwk, signature) {
2791
+ if (!isNode()) {
2792
+ throw new Error("rsaVerifySync is only available in Node.js runtimes; use the async rsaVerify in browsers");
2793
+ }
2794
+ return (init_node(), __toCommonJS(node_exports)).rsaVerifySync(jwtAlg, data, publicJwk, signature);
2795
+ }
2796
+ function pemToPublicJwk2(pem) {
2797
+ if (!isNode()) {
2798
+ throw new Error("pemToPublicJwk is only available in Node.js runtimes");
2799
+ }
2800
+ return (init_node(), __toCommonJS(node_exports)).pemToPublicJwk(pem);
2801
+ }
2802
+ function generateRsaKeyPair2(modulusLength, alg) {
2803
+ if (!isNode()) {
2804
+ throw new Error("generateRsaKeyPair is only available in Node.js runtimes");
2805
+ }
2806
+ return (init_node(), __toCommonJS(node_exports)).generateRsaKeyPair(modulusLength, alg);
2807
+ }
2740
2808
  function randomUUID() {
2741
2809
  return getCrypto().randomUUID();
2742
2810
  }
@@ -2845,8 +2913,8 @@ var import_crypto_provider2, HKDF_SHA256_MAX_LENGTH;
2845
2913
  var init_crypto = __esm({
2846
2914
  "libs/utils/src/crypto/index.ts"() {
2847
2915
  "use strict";
2848
- init_runtime();
2849
2916
  import_crypto_provider2 = require("#crypto-provider");
2917
+ init_runtime();
2850
2918
  init_jwt_alg();
2851
2919
  init_runtime();
2852
2920
  init_encrypted_blob();
@@ -3809,6 +3877,7 @@ __export(index_exports, {
3809
3877
  generateCodeChallenge: () => generateCodeChallenge,
3810
3878
  generateCodeVerifier: () => generateCodeVerifier,
3811
3879
  generatePkcePair: () => generatePkcePair,
3880
+ generateRsaKeyPair: () => generateRsaKeyPair2,
3812
3881
  generateSecret: () => generateSecret,
3813
3882
  getCookie: () => getCookie,
3814
3883
  getCrypto: () => getCrypto,
@@ -3825,6 +3894,7 @@ __export(index_exports, {
3825
3894
  hmacSha256: () => hmacSha256,
3826
3895
  idFromString: () => idFromString,
3827
3896
  inferMimeType: () => inferMimeType,
3897
+ isAbsolute: () => import_path.isAbsolute,
3828
3898
  isAsymmetricKeyData: () => isAsymmetricKeyData,
3829
3899
  isBrowser: () => isBrowser,
3830
3900
  isDebug: () => import_env3.isDebug,
@@ -3865,6 +3935,7 @@ __export(index_exports, {
3865
3935
  parseUriTemplate: () => parseUriTemplate,
3866
3936
  pathJoin: () => import_path.join,
3867
3937
  pathResolve: () => import_path.resolve,
3938
+ pemToPublicJwk: () => pemToPublicJwk2,
3868
3939
  processPlatformToolCalls: () => processPlatformToolCalls,
3869
3940
  randomBytes: () => randomBytes,
3870
3941
  randomUUID: () => randomUUID,
@@ -3873,11 +3944,14 @@ __export(index_exports, {
3873
3944
  readFileSync: () => readFileSync,
3874
3945
  readJSON: () => readJSON,
3875
3946
  readdir: () => readdir,
3947
+ realpath: () => realpath,
3876
3948
  rename: () => rename,
3877
3949
  resetRuntimeContext: () => resetRuntimeContext,
3878
3950
  resolveSecretPath: () => resolveSecretPath,
3879
3951
  rm: () => rm,
3952
+ rsaSignBase64Url: () => rsaSignBase64Url2,
3880
3953
  rsaVerify: () => rsaVerify2,
3954
+ rsaVerifySync: () => rsaVerifySync2,
3881
3955
  runCmd: () => runCmd,
3882
3956
  safeExec: () => safeExec,
3883
3957
  safeJsonForScript: () => safeJsonForScript,
@@ -3901,6 +3975,7 @@ __export(index_exports, {
3901
3975
  splitWords: () => splitWords,
3902
3976
  stat: () => stat,
3903
3977
  supportsAnsi: () => import_env3.supportsAnsi,
3978
+ symlink: () => symlink,
3904
3979
  timingSafeEqual: () => timingSafeEqual,
3905
3980
  toCase: () => toCase,
3906
3981
  trimBoth: () => trimBoth,
@@ -4249,19 +4324,26 @@ function parseUriTemplate(template) {
4249
4324
  throw new Error("URI template has too many parameters (max 50)");
4250
4325
  }
4251
4326
  const paramNames = [];
4252
- let regexStr = template.replace(/[.*+?^${}()|[\]\\]/g, (match) => {
4253
- if (match === "{" || match === "}") return match;
4254
- return "\\" + match;
4255
- });
4256
- regexStr = regexStr.replace(/\{([^{}]+)\}/g, (_, paramName) => {
4257
- paramNames.push(paramName);
4258
- return "([^/]+)";
4259
- });
4327
+ const placeholder = /\{([^{}]+)\}/g;
4328
+ let regexStr = "";
4329
+ let lastIndex = 0;
4330
+ for (let m = placeholder.exec(template); m !== null; m = placeholder.exec(template)) {
4331
+ regexStr += escapeRegex(template.slice(lastIndex, m.index));
4332
+ const raw = m[1];
4333
+ const reserved = raw.startsWith("+");
4334
+ paramNames.push(reserved ? raw.slice(1) : raw);
4335
+ regexStr += reserved ? "(.+?)" : "([^/]+)";
4336
+ lastIndex = m.index + m[0].length;
4337
+ }
4338
+ regexStr += escapeRegex(template.slice(lastIndex));
4260
4339
  return {
4261
4340
  pattern: new RegExp(`^${regexStr}$`),
4262
4341
  paramNames
4263
4342
  };
4264
4343
  }
4344
+ function escapeRegex(literal) {
4345
+ return literal.replace(/[.*+?^${}()|[\]\\]/g, (c) => `\\${c}`);
4346
+ }
4265
4347
  function matchUriTemplate(template, uri) {
4266
4348
  const { pattern, paramNames } = parseUriTemplate(template);
4267
4349
  const match = uri.match(pattern);
@@ -4273,18 +4355,37 @@ function matchUriTemplate(template, uri) {
4273
4355
  return params;
4274
4356
  }
4275
4357
  function expandUriTemplate(template, params) {
4276
- return template.replace(/\{([^{}]+)\}/g, (_, paramName) => {
4358
+ return template.replace(/\{([^{}]+)\}/g, (_, raw) => {
4359
+ const reserved = raw.startsWith("+");
4360
+ const paramName = reserved ? raw.slice(1) : raw;
4277
4361
  const value = params[paramName];
4278
4362
  if (value === void 0) {
4279
4363
  throw new Error(`Missing parameter '${paramName}' for URI template '${template}'`);
4280
4364
  }
4281
- return encodeURIComponent(value);
4365
+ return reserved ? encodeReserved(value) : encodeURIComponent(value);
4282
4366
  });
4283
4367
  }
4368
+ function encodeReserved(value) {
4369
+ const decoded = safeDecode(value);
4370
+ return decoded.replace(/[^A-Za-z0-9\-._~:/?#[\]@!$&'()*+,;=]/g, (c) => encodeURIComponent(c));
4371
+ }
4372
+ function safeDecode(value) {
4373
+ try {
4374
+ return decodeURIComponent(value);
4375
+ } catch {
4376
+ return value.replace(/%[0-9A-Fa-f]{2}/g, (triplet) => {
4377
+ try {
4378
+ return decodeURIComponent(triplet);
4379
+ } catch {
4380
+ return triplet;
4381
+ }
4382
+ });
4383
+ }
4384
+ }
4284
4385
  function extractTemplateParams(template) {
4285
4386
  const params = [];
4286
- template.replace(/\{([^{}]+)\}/g, (_, paramName) => {
4287
- params.push(paramName);
4387
+ template.replace(/\{([^{}]+)\}/g, (_, raw) => {
4388
+ params.push(raw.startsWith("+") ? raw.slice(1) : raw);
4288
4389
  return "";
4289
4390
  });
4290
4391
  return params;
@@ -5624,6 +5725,7 @@ function setMachineIdOverride(id) {
5624
5725
  generateCodeChallenge,
5625
5726
  generateCodeVerifier,
5626
5727
  generatePkcePair,
5728
+ generateRsaKeyPair,
5627
5729
  generateSecret,
5628
5730
  getCookie,
5629
5731
  getCrypto,
@@ -5640,6 +5742,7 @@ function setMachineIdOverride(id) {
5640
5742
  hmacSha256,
5641
5743
  idFromString,
5642
5744
  inferMimeType,
5745
+ isAbsolute,
5643
5746
  isAsymmetricKeyData,
5644
5747
  isBrowser,
5645
5748
  isDebug,
@@ -5680,6 +5783,7 @@ function setMachineIdOverride(id) {
5680
5783
  parseUriTemplate,
5681
5784
  pathJoin,
5682
5785
  pathResolve,
5786
+ pemToPublicJwk,
5683
5787
  processPlatformToolCalls,
5684
5788
  randomBytes,
5685
5789
  randomUUID,
@@ -5688,11 +5792,14 @@ function setMachineIdOverride(id) {
5688
5792
  readFileSync,
5689
5793
  readJSON,
5690
5794
  readdir,
5795
+ realpath,
5691
5796
  rename,
5692
5797
  resetRuntimeContext,
5693
5798
  resolveSecretPath,
5694
5799
  rm,
5800
+ rsaSignBase64Url,
5695
5801
  rsaVerify,
5802
+ rsaVerifySync,
5696
5803
  runCmd,
5697
5804
  safeExec,
5698
5805
  safeJsonForScript,
@@ -5716,6 +5823,7 @@ function setMachineIdOverride(id) {
5716
5823
  splitWords,
5717
5824
  stat,
5718
5825
  supportsAnsi,
5826
+ symlink,
5719
5827
  timingSafeEqual,
5720
5828
  toCase,
5721
5829
  trimBoth,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@frontmcp/utils",
3
- "version": "1.1.2",
3
+ "version": "1.2.1",
4
4
  "description": "Shared utility functions for FrontMCP - string manipulation, URI handling, path utilities, and more",
5
5
  "author": "AgentFront <info@agentfront.dev>",
6
6
  "license": "Apache-2.0",
@@ -34,7 +34,7 @@
34
34
  "peerDependencies": {
35
35
  "@vercel/kv": "^2.0.0 || ^3.0.0",
36
36
  "ioredis": "^5.0.0",
37
- "@frontmcp/lazy-zod": "1.1.2"
37
+ "@frontmcp/lazy-zod": "1.2.1"
38
38
  },
39
39
  "peerDependenciesMeta": {
40
40
  "@vercel/kv": {
@@ -3,3 +3,4 @@ export declare function join(..._segments: string[]): string;
3
3
  export declare function basename(p: string, ext?: string): string;
4
4
  export declare function dirname(_p: string): string;
5
5
  export declare function extname(p: string): string;
6
+ export declare function isAbsolute(p: string): boolean;
package/path/index.d.ts CHANGED
@@ -1,2 +1,2 @@
1
1
  export { trimSlashes, joinPath } from './path';
2
- export { resolve as pathResolve, join as pathJoin, basename, dirname, extname } from '#path';
2
+ export { resolve as pathResolve, join as pathJoin, basename, dirname, extname, isAbsolute } from '#path';
@@ -1 +1 @@
1
- export { resolve, join, basename, dirname, extname } from 'path';
1
+ export { resolve, join, basename, dirname, extname, isAbsolute } from 'path';
@@ -1,9 +1,16 @@
1
1
  /**
2
- * RFC 6570 Level 1 URI Template utilities.
3
- *
4
- * Provides parsing, matching, and expansion of URI templates using simple
5
- * string substitution ({param} syntax). This implements RFC 6570 Level 1 only;
6
- * advanced operators like {+path}, {#fragment}, or {?query} are not supported.
2
+ * RFC 6570 URI Template utilities.
3
+ *
4
+ * Implements:
5
+ * - Level 1 simple string expansion (`{var}`) — value is percent-encoded,
6
+ * stops at `/`.
7
+ * - Level 2 reserved expansion (`{+var}`) — value passes reserved characters
8
+ * (including `/`) through unencoded, so a single template variable can
9
+ * span multiple path segments (e.g. `skill://{+skillPath}/SKILL.md`
10
+ * matches `skill://acme/billing/refunds/SKILL.md`).
11
+ *
12
+ * Other RFC 6570 operators (`{#frag}`, `{?query}`, `{&qcont}`, `{;path}`,
13
+ * `{.label}`, `{/path}`) are not supported.
7
14
  */
8
15
  /**
9
16
  * Parsed URI template information.
@@ -15,8 +22,8 @@ export interface ParsedUriTemplate {
15
22
  paramNames: string[];
16
23
  }
17
24
  /**
18
- * Parse a URI template (RFC 6570 Level 1) into a regex pattern and parameter names.
19
- * Supports simple string substitution: {param}
25
+ * Parse a URI template (RFC 6570 Level 1 + reserved expansion) into a regex
26
+ * pattern and parameter names.
20
27
  *
21
28
  * @param template - The URI template to parse
22
29
  * @returns Parsed template with pattern and parameter names
@@ -28,6 +35,9 @@ export interface ParsedUriTemplate {
28
35
  *
29
36
  * parseUriTemplate("users/{userId}/posts/{postId}")
30
37
  * // { pattern: /^users\/([^/]+)\/posts\/([^/]+)$/, paramNames: ["userId", "postId"] }
38
+ *
39
+ * parseUriTemplate("skill://{+skillPath}/SKILL.md")
40
+ * // { pattern: /^skill:\/\/(.+?)\/SKILL\.md$/, paramNames: ["skillPath"] }
31
41
  */
32
42
  export declare function parseUriTemplate(template: string): ParsedUriTemplate;
33
43
  /**
@@ -52,12 +62,19 @@ export declare function matchUriTemplate(template: string, uri: string): Record<
52
62
  /**
53
63
  * Expand a URI template with the given parameters.
54
64
  *
55
- * Parameter values are **percent-encoded** per RFC 6570 Level 1 using
56
- * `encodeURIComponent()`. Use {@link matchUriTemplate} to decode them back.
65
+ * Encoding depends on the operator:
66
+ * - `{var}` (simple expansion): value is percent-encoded via
67
+ * `encodeURIComponent()` so `/`, `:`, etc. become `%2F`, `%3A`.
68
+ * - `{+var}` (reserved expansion): unreserved + reserved characters
69
+ * (per RFC 3986) pass through unencoded — only characters outside
70
+ * that set are percent-encoded. This lets a single variable carry
71
+ * a multi-segment path.
72
+ *
73
+ * Use {@link matchUriTemplate} to decode them back.
57
74
  *
58
75
  * @param template - The URI template
59
76
  * @param params - Object with parameter values (raw, unencoded)
60
- * @returns Expanded URI with parameters percent-encoded and substituted
77
+ * @returns Expanded URI with parameters substituted (encoding depends on operator)
61
78
  * @throws Error if a required parameter is missing
62
79
  *
63
80
  * @example
@@ -67,9 +84,13 @@ export declare function matchUriTemplate(template: string, uri: string): Record<
67
84
  * expandUriTemplate("users/{userId}/posts/{postId}", { userId: "123", postId: "456" })
68
85
  * // "users/123/posts/456"
69
86
  *
70
- * // Special characters are percent-encoded:
87
+ * // Simple expansion percent-encodes reserved chars:
71
88
  * expandUriTemplate("notes://{id}", { id: "x-coredata://ABC/Note/1" })
72
89
  * // "notes://x-coredata%3A%2F%2FABC%2FNote%2F1"
90
+ *
91
+ * // Reserved expansion preserves them:
92
+ * expandUriTemplate("skill://{+skillPath}/SKILL.md", { skillPath: "acme/billing" })
93
+ * // "skill://acme/billing/SKILL.md"
73
94
  */
74
95
  export declare function expandUriTemplate(template: string, params: Record<string, string>): string;
75
96
  /**
package/CHANGELOG.md DELETED
@@ -1,12 +0,0 @@
1
- # Changelog
2
-
3
- ## 0.0.1
4
-
5
- - Initial release
6
- - Extracted utilities from @frontmcp/sdk, @frontmcp/cli, and @frontmcp/adapters
7
- - Added naming utilities: splitWords, toCase, sepFor, shortHash, ensureMaxLen, idFromString
8
- - Added URI utilities: isValidMcpUri, extractUriScheme, isValidMcpUriTemplate, parseUriTemplate, matchUriTemplate, expandUriTemplate, extractTemplateParams, isUriTemplate
9
- - Added path utilities: trimSlashes, joinPath
10
- - Added content utilities: sanitizeToJson, inferMimeType
11
- - Added HTTP utilities: validateBaseUrl
12
- - Added FS utilities: fileExists, readJSON, writeJSON, ensureDir, isDirEmpty, runCmd