@frontmcp/utils 1.2.1 → 1.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.
@@ -1,28 +1,60 @@
1
1
  /**
2
2
  * Runtime context detection and entry availability matching (Browser version).
3
3
  *
4
- * Returns safe defaults for browser environments.
5
- * The isEntryAvailable matcher works identically to the Node version.
4
+ * Returns safe defaults for browser environments. The matcher and
5
+ * `EntryAvailability` shape track the Node version (issue #417 added
6
+ * `os`/`provider`/`target`/`surface`).
6
7
  */
7
8
  export interface RuntimeContext {
9
+ /** Issue #417: renamed from `platform`. */
10
+ os: string;
11
+ /** @deprecated Use `os`. */
8
12
  platform: string;
9
13
  runtime: string;
10
14
  deployment: string;
15
+ /** Issue #417 — deploy provider. Always `'bare'` in browser. */
16
+ provider: string;
17
+ /** Issue #417 — build target. Always `'browser'` in browser. */
18
+ target: string;
11
19
  env: string;
12
20
  }
21
+ export type Surface = 'mcp' | 'cli' | 'http-trigger' | 'job' | 'agent';
22
+ export interface CallContext {
23
+ surface?: Surface;
24
+ }
13
25
  export interface EntryAvailability {
26
+ /** Issue #417: renamed from `platform`. */
27
+ os?: string[];
28
+ /** @deprecated Use `os`. */
14
29
  platform?: string[];
15
30
  runtime?: string[];
16
31
  deployment?: string[];
32
+ provider?: string[];
33
+ target?: string[];
34
+ surface?: Surface[];
17
35
  env?: string[];
18
36
  }
19
37
  export declare const entryAvailabilitySchema: import("@frontmcp/lazy-zod").ZodObject<{
38
+ os: import("@frontmcp/lazy-zod").ZodOptional<import("@frontmcp/lazy-zod").ZodArray<import("@frontmcp/lazy-zod").ZodString>>;
20
39
  platform: import("@frontmcp/lazy-zod").ZodOptional<import("@frontmcp/lazy-zod").ZodArray<import("@frontmcp/lazy-zod").ZodString>>;
21
40
  runtime: import("@frontmcp/lazy-zod").ZodOptional<import("@frontmcp/lazy-zod").ZodArray<import("@frontmcp/lazy-zod").ZodString>>;
22
41
  deployment: import("@frontmcp/lazy-zod").ZodOptional<import("@frontmcp/lazy-zod").ZodArray<import("@frontmcp/lazy-zod").ZodString>>;
42
+ provider: import("@frontmcp/lazy-zod").ZodOptional<import("@frontmcp/lazy-zod").ZodArray<import("@frontmcp/lazy-zod").ZodString>>;
43
+ target: import("@frontmcp/lazy-zod").ZodOptional<import("@frontmcp/lazy-zod").ZodArray<import("@frontmcp/lazy-zod").ZodString>>;
44
+ surface: import("@frontmcp/lazy-zod").ZodOptional<import("@frontmcp/lazy-zod").ZodArray<import("@frontmcp/lazy-zod").ZodEnum<{
45
+ cli: "cli";
46
+ mcp: "mcp";
47
+ "http-trigger": "http-trigger";
48
+ job: "job";
49
+ agent: "agent";
50
+ }>>>;
23
51
  env: import("@frontmcp/lazy-zod").ZodOptional<import("@frontmcp/lazy-zod").ZodArray<import("@frontmcp/lazy-zod").ZodString>>;
24
52
  }, import("zod/v4/core").$strict>;
25
- export declare function isEntryAvailable(availability: EntryAvailability | undefined, ctx: RuntimeContext): boolean;
53
+ export declare function isEntryAvailable(availability: EntryAvailability | undefined, ctx: RuntimeContext, callCtx?: CallContext): boolean;
54
+ export declare function checkEntryAvailability(availability: EntryAvailability | undefined, ctx: RuntimeContext, callCtx?: CallContext): {
55
+ available: boolean;
56
+ missingAxes: (keyof EntryAvailability)[];
57
+ };
26
58
  export declare function detectRuntimeContext(): RuntimeContext;
27
59
  export declare function getRuntimeContext(): RuntimeContext;
28
60
  export declare function resetRuntimeContext(): void;
@@ -0,0 +1,27 @@
1
+ /**
2
+ * Build-target detection (issue #417).
3
+ *
4
+ * The CLI's `build` command knows the target name (`cli`, `node`,
5
+ * `vercel`, `lambda`, `cloudflare`, `browser`, `sdk`, `mcpb`,
6
+ * `distributed`) but doesn't tell the runtime today. This helper exposes
7
+ * it so `@Tool({ availableWhen: { target: ['cli'] } })` can express
8
+ * "this tool is only valid in the compiled CLI bundle."
9
+ *
10
+ * Resolution order:
11
+ *
12
+ * 1. `globalThis.FRONTMCP_BUILD_TARGET` — preferred. Build adapters
13
+ * inline `const FRONTMCP_BUILD_TARGET = '<target>';` into the
14
+ * emitted entry shim before any user code runs, so this constant
15
+ * is correct from the very first line of imports.
16
+ * 2. `process.env.FRONTMCP_BUILD_TARGET` — set by the build adapter
17
+ * via the runtime env when inlining isn't viable (e.g. SEA).
18
+ * 3. `'unknown'` — dev mode, tests, or any context the build command
19
+ * hasn't touched.
20
+ *
21
+ * The supported values match `frontmcp build --target <name>` plus
22
+ * `'unknown'` for the dev path.
23
+ */
24
+ export type BuildTarget = 'unknown' | 'node' | 'distributed' | 'cli' | 'vercel' | 'lambda' | 'cloudflare' | 'browser' | 'sdk' | 'mcpb';
25
+ export declare function getBuildTarget(): BuildTarget;
26
+ /** Test helper — reset the cache between specs. */
27
+ export declare function resetBuildTargetCacheForTesting(): void;
package/env/index.d.ts CHANGED
@@ -1,3 +1,7 @@
1
1
  export { getEnv, getCwd, isProduction, isDevelopment, getEnvFlag, isDebug, setEnv, isEdgeRuntime, isServerless, supportsAnsi, } from '#env';
2
- export { getRuntimeContext, resetRuntimeContext, detectRuntimeContext, isEntryAvailable, entryAvailabilitySchema, } from '#runtime-context';
3
- export type { RuntimeContext, EntryAvailability } from '#runtime-context';
2
+ export { getRuntimeContext, resetRuntimeContext, detectRuntimeContext, isEntryAvailable, checkEntryAvailability, entryAvailabilitySchema, } from '#runtime-context';
3
+ export type { RuntimeContext, EntryAvailability, Surface, CallContext } from '#runtime-context';
4
+ export { detectProvider, resetProviderCacheForTesting } from './provider';
5
+ export type { DeployProvider } from './provider';
6
+ export { getBuildTarget, resetBuildTargetCacheForTesting } from './build-target';
7
+ export type { BuildTarget } from './build-target';
@@ -0,0 +1,33 @@
1
+ /**
2
+ * Deploy-provider detection (issue #417).
3
+ *
4
+ * `isServerless()` collapses every serverless env to a single boolean;
5
+ * this returns the discriminated identity so `@Tool({ availableWhen: {
6
+ * provider: ['vercel'] } })` can express provider-specific rules.
7
+ *
8
+ * Order matters — first match wins. A user can override detection by
9
+ * setting `FRONTMCP_PROVIDER=<name>` explicitly (used by tests and by
10
+ * Docker images that don't expose a discriminating env var).
11
+ */
12
+ export type DeployProvider = 'bare' | 'docker' | 'vercel' | 'lambda' | 'cloudflare' | 'netlify' | 'azure' | 'gcp' | 'fly' | 'render' | 'railway';
13
+ /**
14
+ * Detect the current deploy provider. Cached per process.
15
+ *
16
+ * Detection rules (first match wins):
17
+ *
18
+ * 1. `FRONTMCP_PROVIDER` env var (override; trust whatever the operator set).
19
+ * 2. `VERCEL` env var → `'vercel'`.
20
+ * 3. `AWS_LAMBDA_FUNCTION_NAME` → `'lambda'`.
21
+ * 4. `CF_PAGES` → `'cloudflare'`.
22
+ * 5. `NETLIFY` → `'netlify'`.
23
+ * 6. `AZURE_FUNCTIONS_ENVIRONMENT` → `'azure'`.
24
+ * 7. `K_SERVICE` (Cloud Run / Knative) → `'gcp'`.
25
+ * 8. `FLY_APP_NAME` → `'fly'`.
26
+ * 9. `RENDER` → `'render'`.
27
+ * 10. `RAILWAY_ENVIRONMENT` → `'railway'`.
28
+ * 11. `/.dockerenv` exists → `'docker'`.
29
+ * 12. Fallback: `'bare'`.
30
+ */
31
+ export declare function detectProvider(): DeployProvider;
32
+ /** Test helper — reset the cache between specs. */
33
+ export declare function resetProviderCacheForTesting(): void;
@@ -2,25 +2,48 @@
2
2
  * Runtime context detection and entry availability matching (Node.js version).
3
3
  *
4
4
  * Provides:
5
- * - `RuntimeContext` — snapshot of the current platform, runtime, deployment, and env
5
+ * - `RuntimeContext` — snapshot of the current platform, runtime, deployment, env, provider, target
6
6
  * - `EntryAvailability` — declarative constraint for when an entry is available
7
7
  * - `getRuntimeContext()` — lazy singleton returning the detected context
8
8
  * - `isEntryAvailable()` — pure matcher: does the constraint match the context?
9
+ * - `checkEntryAvailability()` — structured matcher returning `{available, missingAxes}`
10
+ *
11
+ * Issue #417 adds:
12
+ * - `os` as the new name for `platform` (alias preserved for back-compat)
13
+ * - `provider` (vercel / lambda / cloudflare / netlify / docker / bare / …)
14
+ * - `target` (the build output produced by `frontmcp build --target <x>`)
15
+ * - `surface` (per-call axis: mcp / cli / agent / job / http-trigger)
9
16
  */
10
17
  /**
11
- * Snapshot of the current runtime environment.
12
- * Detected once and cached for the lifetime of the process.
18
+ * Snapshot of the current runtime environment. Detected once and cached
19
+ * for the lifetime of the process.
13
20
  */
14
21
  export interface RuntimeContext {
15
- /** OS platform: 'darwin', 'linux', 'win32', 'freebsd', etc. */
22
+ /** OS platform: 'darwin', 'linux', 'win32', etc. (issue #417: renamed from platform) */
23
+ os: string;
24
+ /** @deprecated Use `os` instead. Legacy alias preserved for back-compat. */
16
25
  platform: string;
17
26
  /** JavaScript runtime: 'node', 'bun', 'deno', 'edge', 'browser' */
18
27
  runtime: string;
19
28
  /** Deployment mode: 'distributed', 'serverless', 'standalone', or 'browser' */
20
29
  deployment: string;
30
+ /** Deploy provider (issue #417): 'vercel', 'lambda', 'cloudflare', 'docker', 'bare', … */
31
+ provider: string;
32
+ /** Build target produced by `frontmcp build` (issue #417). `'unknown'` in dev. */
33
+ target: string;
21
34
  /** NODE_ENV value: 'production', 'development', 'test', etc. */
22
35
  env: string;
23
36
  }
37
+ /**
38
+ * Per-call surface — `surface` is the only axis that varies per request.
39
+ * Set by transport adapters / agent dispatchers / job runners on the
40
+ * outgoing `ctx` so registry filtering can fork on call origin.
41
+ */
42
+ export type Surface = 'mcp' | 'cli' | 'http-trigger' | 'job' | 'agent';
43
+ /** Call-context wrapper threaded through `findTool` for per-call filtering. */
44
+ export interface CallContext {
45
+ surface?: Surface;
46
+ }
24
47
  /**
25
48
  * Declarative constraint for entry availability.
26
49
  *
@@ -33,21 +56,34 @@ export interface RuntimeContext {
33
56
  *
34
57
  * @example macOS only
35
58
  * ```typescript
36
- * { platform: ['darwin'] }
59
+ * { os: ['darwin'] }
60
+ * ```
61
+ *
62
+ * @example CLI binary on Linux only (issue #417)
63
+ * ```typescript
64
+ * { target: ['cli'], os: ['linux'] }
37
65
  * ```
38
66
  *
39
- * @example Node.js or Bun, production only
67
+ * @example MCP-only — block CLI / agent / job invocations (issue #417)
40
68
  * ```typescript
41
- * { runtime: ['node', 'bun'], env: ['production'] }
69
+ * { surface: ['mcp'] }
42
70
  * ```
43
71
  */
44
72
  export interface EntryAvailability {
45
- /** OS platform constraint: 'darwin', 'linux', 'win32', etc. */
73
+ /** OS constraint (issue #417: renamed from `platform`). */
74
+ os?: string[];
75
+ /** @deprecated Use `os` instead. Legacy alias preserved for back-compat. */
46
76
  platform?: string[];
47
77
  /** Runtime constraint: 'node', 'browser', 'edge', 'bun', 'deno' */
48
78
  runtime?: string[];
49
79
  /** Deployment constraint: 'distributed', 'serverless', 'standalone', 'browser' */
50
80
  deployment?: string[];
81
+ /** Deploy-provider constraint (issue #417). */
82
+ provider?: string[];
83
+ /** Build-target constraint (issue #417). */
84
+ target?: string[];
85
+ /** Surface constraint (issue #417): 'mcp', 'cli', 'agent', 'job', 'http-trigger'. */
86
+ surface?: Surface[];
51
87
  /** Environment constraint: 'production', 'development', 'test', etc. */
52
88
  env?: string[];
53
89
  }
@@ -55,19 +91,43 @@ export interface EntryAvailability {
55
91
  * Zod schema for validating EntryAvailability in metadata.
56
92
  */
57
93
  export declare const entryAvailabilitySchema: import("@frontmcp/lazy-zod").ZodObject<{
94
+ os: import("@frontmcp/lazy-zod").ZodOptional<import("@frontmcp/lazy-zod").ZodArray<import("@frontmcp/lazy-zod").ZodString>>;
58
95
  platform: import("@frontmcp/lazy-zod").ZodOptional<import("@frontmcp/lazy-zod").ZodArray<import("@frontmcp/lazy-zod").ZodString>>;
59
96
  runtime: import("@frontmcp/lazy-zod").ZodOptional<import("@frontmcp/lazy-zod").ZodArray<import("@frontmcp/lazy-zod").ZodString>>;
60
97
  deployment: import("@frontmcp/lazy-zod").ZodOptional<import("@frontmcp/lazy-zod").ZodArray<import("@frontmcp/lazy-zod").ZodString>>;
98
+ provider: import("@frontmcp/lazy-zod").ZodOptional<import("@frontmcp/lazy-zod").ZodArray<import("@frontmcp/lazy-zod").ZodString>>;
99
+ target: import("@frontmcp/lazy-zod").ZodOptional<import("@frontmcp/lazy-zod").ZodArray<import("@frontmcp/lazy-zod").ZodString>>;
100
+ surface: import("@frontmcp/lazy-zod").ZodOptional<import("@frontmcp/lazy-zod").ZodArray<import("@frontmcp/lazy-zod").ZodEnum<{
101
+ cli: "cli";
102
+ mcp: "mcp";
103
+ "http-trigger": "http-trigger";
104
+ job: "job";
105
+ agent: "agent";
106
+ }>>>;
61
107
  env: import("@frontmcp/lazy-zod").ZodOptional<import("@frontmcp/lazy-zod").ZodArray<import("@frontmcp/lazy-zod").ZodString>>;
62
108
  }, import("zod/v4/core").$strict>;
63
109
  /**
64
110
  * Check if an entry is available in the given runtime context.
65
111
  *
112
+ * Surface is checked separately because it's a per-call axis, not a
113
+ * process-global one. Pass it via `callCtx.surface` to enforce
114
+ * `availableWhen.surface` at call time.
115
+ *
66
116
  * @param availability - The constraint (undefined = always available)
67
117
  * @param ctx - The current runtime context
118
+ * @param callCtx - Optional per-call context (surface, …)
68
119
  * @returns true if the entry should be available
69
120
  */
70
- export declare function isEntryAvailable(availability: EntryAvailability | undefined, ctx: RuntimeContext): boolean;
121
+ export declare function isEntryAvailable(availability: EntryAvailability | undefined, ctx: RuntimeContext, callCtx?: CallContext): boolean;
122
+ /**
123
+ * Structured variant of `isEntryAvailable` — returns the list of axes
124
+ * that failed so flow-level call sites can build a `missingAxes`-shaped
125
+ * error response (issue #417).
126
+ */
127
+ export declare function checkEntryAvailability(availability: EntryAvailability | undefined, ctx: RuntimeContext, callCtx?: CallContext): {
128
+ available: boolean;
129
+ missingAxes: (keyof EntryAvailability)[];
130
+ };
71
131
  /**
72
132
  * Detect the current runtime context from the environment.
73
133
  */
@@ -1,3 +1,5 @@
1
+ import { createRequire as __frontmcpCreateRequire } from 'module';
2
+ const require = __frontmcpCreateRequire(import.meta.url);
1
3
  // libs/utils/src/async-context/browser-async-context.ts
2
4
  var AsyncLocalStorage = class {
3
5
  stack = [];
@@ -1,3 +1,5 @@
1
+ import { createRequire as __frontmcpCreateRequire } from 'module';
2
+ const require = __frontmcpCreateRequire(import.meta.url);
1
3
  // libs/utils/src/async-context/node-async-context.ts
2
4
  import { AsyncLocalStorage } from "node:async_hooks";
3
5
  export {
@@ -1,3 +1,5 @@
1
+ import { createRequire as __frontmcpCreateRequire } from 'module';
2
+ const require = __frontmcpCreateRequire(import.meta.url);
1
3
  // libs/utils/src/crypto/browser.ts
2
4
  import { sha256 as sha256Hash } from "@noble/hashes/sha2.js";
3
5
  import { hmac } from "@noble/hashes/hmac.js";
@@ -1,3 +1,5 @@
1
+ import { createRequire as __frontmcpCreateRequire } from 'module';
2
+ const require = __frontmcpCreateRequire(import.meta.url);
1
3
  // libs/utils/src/crypto/node.ts
2
4
  import crypto from "node:crypto";
3
5
 
@@ -1,3 +1,5 @@
1
+ import { createRequire as __frontmcpCreateRequire } from 'module';
2
+ const require = __frontmcpCreateRequire(import.meta.url);
1
3
  // libs/utils/src/env/browser-env.ts
2
4
  function getEnv(_key, defaultValue) {
3
5
  return defaultValue;
@@ -1,3 +1,5 @@
1
+ import { createRequire as __frontmcpCreateRequire } from 'module';
2
+ const require = __frontmcpCreateRequire(import.meta.url);
1
3
  // libs/utils/src/env/node-env.ts
2
4
  function getEnv(key, defaultValue) {
3
5
  return process.env[key] ?? defaultValue;
@@ -1,3 +1,5 @@
1
+ import { createRequire as __frontmcpCreateRequire } from 'module';
2
+ const require = __frontmcpCreateRequire(import.meta.url);
1
3
  // libs/utils/src/event-emitter/browser-event-emitter.ts
2
4
  var EventEmitter = class {
3
5
  events = /* @__PURE__ */ new Map();
@@ -1,3 +1,5 @@
1
+ import { createRequire as __frontmcpCreateRequire } from 'module';
2
+ const require = __frontmcpCreateRequire(import.meta.url);
1
3
  // libs/utils/src/event-emitter/node-event-emitter.ts
2
4
  import { EventEmitter } from "events";
3
5
  export {
package/esm/index.mjs CHANGED
@@ -1,3 +1,5 @@
1
+ import { createRequire as __frontmcpCreateRequire } from 'module';
2
+ const require = __frontmcpCreateRequire(import.meta.url);
1
3
  var __defProp = Object.defineProperty;
2
4
  var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
3
5
  var __getOwnPropNames = Object.getOwnPropertyNames;
@@ -65,13 +67,16 @@ function getSpawn() {
65
67
  }
66
68
  return _spawn;
67
69
  }
70
+ function getSpawnFn() {
71
+ return getSpawn();
72
+ }
68
73
  async function readFile(p, encoding = "utf8") {
69
74
  const fsp = getFsp();
70
75
  return fsp.readFile(p, encoding);
71
76
  }
72
77
  function readFileSync(p, encoding = "utf8") {
73
- const fs = getFs();
74
- return fs.readFileSync(p, encoding);
78
+ const fs2 = getFs();
79
+ return fs2.readFileSync(p, encoding);
75
80
  }
76
81
  async function readFileBuffer(p) {
77
82
  const fsp = getFsp();
@@ -85,6 +90,10 @@ async function mkdir(p, options) {
85
90
  const fsp = getFsp();
86
91
  await fsp.mkdir(p, options);
87
92
  }
93
+ function ensureDirSync(p) {
94
+ const fs2 = getFs();
95
+ fs2.mkdirSync(p, { recursive: true });
96
+ }
88
97
  async function rename(oldPath, newPath) {
89
98
  const fsp = getFsp();
90
99
  await fsp.rename(oldPath, newPath);
@@ -143,6 +152,10 @@ async function readdir(p) {
143
152
  const fsp = getFsp();
144
153
  return fsp.readdir(p);
145
154
  }
155
+ function readdirSync(p) {
156
+ const fs2 = getFs();
157
+ return fs2.readdirSync(p);
158
+ }
146
159
  async function rm(p, options) {
147
160
  const fsp = getFsp();
148
161
  await fsp.rm(p, options);
@@ -173,6 +186,20 @@ function runCmd(cmd, args, opts = {}) {
173
186
  child.on("error", reject);
174
187
  });
175
188
  }
189
+ function watchFile(p, onChange, options) {
190
+ const fs2 = getFs();
191
+ const watcher = fs2.watch(p, { persistent: false, recursive: options?.recursive === true }, (eventType, filename) => {
192
+ onChange(eventType, filename);
193
+ });
194
+ return {
195
+ close() {
196
+ watcher.close();
197
+ },
198
+ onError(handler) {
199
+ watcher.on("error", handler);
200
+ }
201
+ };
202
+ }
176
203
  var _fsp, _fs, _spawn, F_OK;
177
204
  var init_fs = __esm({
178
205
  "libs/utils/src/fs/fs.ts"() {
@@ -562,9 +589,9 @@ function createSecretData(options) {
562
589
  }
563
590
  async function getOrCreateSecret(options) {
564
591
  const cacheKey = resolveSecretPath(options);
565
- const cached2 = secretCache.get(cacheKey);
566
- if (cached2) {
567
- return cached2;
592
+ const cached4 = secretCache.get(cacheKey);
593
+ if (cached4) {
594
+ return cached4;
568
595
  }
569
596
  const existing = generationPromises.get(cacheKey);
570
597
  if (existing) {
@@ -801,8 +828,8 @@ var init_key_persistence = __esm({
801
828
  */
802
829
  async get(kid) {
803
830
  if (this.enableCache) {
804
- const cached2 = this.cache.get(kid);
805
- if (cached2) return cached2;
831
+ const cached4 = this.cache.get(kid);
832
+ if (cached4) return cached4;
806
833
  }
807
834
  const raw = await this.storage.get(kid);
808
835
  if (!raw) return null;
@@ -4184,7 +4211,8 @@ function joinPath(...parts) {
4184
4211
  }
4185
4212
 
4186
4213
  // libs/utils/src/path/node-path.ts
4187
- import { resolve, join, basename, dirname, extname, isAbsolute } from "path";
4214
+ import { resolve, join, basename, dirname, extname, isAbsolute, sep } from "path";
4215
+ import { pathToFileURL } from "url";
4188
4216
 
4189
4217
  // libs/utils/src/content/content.ts
4190
4218
  function sanitizeToJson(value) {
@@ -4483,23 +4511,114 @@ import {
4483
4511
  // libs/utils/src/env/runtime-context.ts
4484
4512
  import { z as z3 } from "@frontmcp/lazy-zod";
4485
4513
  import { isEdgeRuntime, isServerless } from "#env";
4514
+
4515
+ // libs/utils/src/env/build-target.ts
4516
+ var cached;
4517
+ function getBuildTarget() {
4518
+ if (cached) return cached;
4519
+ cached = resolveBuildTarget();
4520
+ return cached;
4521
+ }
4522
+ function resolveBuildTarget() {
4523
+ const globalAny = globalThis;
4524
+ const fromGlobal = globalAny.FRONTMCP_BUILD_TARGET;
4525
+ if (typeof fromGlobal === "string" && isKnownTarget(fromGlobal)) return fromGlobal;
4526
+ const fromEnv = typeof process !== "undefined" ? process.env?.["FRONTMCP_BUILD_TARGET"] : void 0;
4527
+ if (typeof fromEnv === "string" && isKnownTarget(fromEnv)) return fromEnv;
4528
+ return "unknown";
4529
+ }
4530
+ function isKnownTarget(value) {
4531
+ return value === "unknown" || value === "node" || value === "distributed" || value === "cli" || value === "vercel" || value === "lambda" || value === "cloudflare" || value === "browser" || value === "sdk" || value === "mcpb";
4532
+ }
4533
+
4534
+ // libs/utils/src/env/provider.ts
4535
+ import * as fs from "node:fs";
4536
+ var cached2;
4537
+ function detectProvider() {
4538
+ if (cached2) return cached2;
4539
+ cached2 = detectProviderUncached();
4540
+ return cached2;
4541
+ }
4542
+ function detectProviderUncached() {
4543
+ const explicit = process.env["FRONTMCP_PROVIDER"];
4544
+ if (explicit && isKnownProvider(explicit)) return explicit;
4545
+ if (process.env["VERCEL"]) return "vercel";
4546
+ if (process.env["AWS_LAMBDA_FUNCTION_NAME"]) return "lambda";
4547
+ if (process.env["CF_PAGES"]) return "cloudflare";
4548
+ if (process.env["NETLIFY"]) return "netlify";
4549
+ if (process.env["AZURE_FUNCTIONS_ENVIRONMENT"]) return "azure";
4550
+ if (process.env["K_SERVICE"]) return "gcp";
4551
+ if (process.env["FLY_APP_NAME"]) return "fly";
4552
+ if (process.env["RENDER"]) return "render";
4553
+ if (process.env["RAILWAY_ENVIRONMENT"]) return "railway";
4554
+ try {
4555
+ if (fs.existsSync("/.dockerenv")) return "docker";
4556
+ } catch {
4557
+ }
4558
+ return "bare";
4559
+ }
4560
+ function isKnownProvider(value) {
4561
+ return value === "bare" || value === "docker" || value === "vercel" || value === "lambda" || value === "cloudflare" || value === "netlify" || value === "azure" || value === "gcp" || value === "fly" || value === "render" || value === "railway";
4562
+ }
4563
+
4564
+ // libs/utils/src/env/runtime-context.ts
4486
4565
  var entryAvailabilitySchema = z3.object({
4566
+ os: z3.array(z3.string().min(1)).optional(),
4487
4567
  platform: z3.array(z3.string().min(1)).optional(),
4488
4568
  runtime: z3.array(z3.string().min(1)).optional(),
4489
4569
  deployment: z3.array(z3.string().min(1)).optional(),
4570
+ provider: z3.array(z3.string().min(1)).optional(),
4571
+ target: z3.array(z3.string().min(1)).optional(),
4572
+ surface: z3.array(z3.enum(["mcp", "cli", "http-trigger", "job", "agent"])).optional(),
4490
4573
  env: z3.array(z3.string().min(1)).optional()
4491
4574
  }).strict();
4492
- function isEntryAvailable(availability, ctx) {
4575
+ var PROCESS_AXES = [
4576
+ // `os` and `platform` both compare against the same RuntimeContext value
4577
+ // — keeping `platform` as a deprecated alias lets pre-#417 metadata
4578
+ // continue to match without any code change.
4579
+ { key: "os", ctxKey: "os" },
4580
+ { key: "platform", ctxKey: "platform" },
4581
+ { key: "runtime", ctxKey: "runtime" },
4582
+ { key: "deployment", ctxKey: "deployment" },
4583
+ { key: "provider", ctxKey: "provider" },
4584
+ { key: "target", ctxKey: "target" },
4585
+ { key: "env", ctxKey: "env" }
4586
+ ];
4587
+ function isEntryAvailable(availability, ctx, callCtx) {
4493
4588
  if (!availability) return true;
4494
- const fields = ["platform", "runtime", "deployment", "env"];
4495
- for (const field of fields) {
4496
- const allowed = availability[field];
4589
+ for (const { key, ctxKey } of PROCESS_AXES) {
4590
+ const allowed = availability[key];
4497
4591
  if (allowed === void 0) continue;
4498
4592
  if (allowed.length === 0) return false;
4499
- if (!allowed.includes(ctx[field])) return false;
4593
+ if (!allowed.includes(ctx[ctxKey])) return false;
4594
+ }
4595
+ if (availability.surface !== void 0) {
4596
+ if (availability.surface.length === 0) return false;
4597
+ if (callCtx?.surface !== void 0 && !availability.surface.includes(callCtx.surface)) {
4598
+ return false;
4599
+ }
4500
4600
  }
4501
4601
  return true;
4502
4602
  }
4603
+ function checkEntryAvailability(availability, ctx, callCtx) {
4604
+ if (!availability) return { available: true, missingAxes: [] };
4605
+ const missing = [];
4606
+ for (const { key, ctxKey } of PROCESS_AXES) {
4607
+ const allowed = availability[key];
4608
+ if (allowed === void 0) continue;
4609
+ if (allowed.length === 0 || !allowed.includes(ctx[ctxKey])) {
4610
+ missing.push(key);
4611
+ }
4612
+ }
4613
+ if (availability.surface !== void 0) {
4614
+ if (availability.surface.length === 0) {
4615
+ missing.push("surface");
4616
+ } else if (callCtx?.surface !== void 0 && !availability.surface.includes(callCtx.surface)) {
4617
+ missing.push("surface");
4618
+ }
4619
+ }
4620
+ return { available: missing.length === 0, missingAxes: missing };
4621
+ }
4503
4622
  function detectRuntime() {
4504
4623
  if (typeof globalThis !== "undefined" && "Bun" in globalThis) return "bun";
4505
4624
  if (typeof globalThis !== "undefined" && "Deno" in globalThis) return "deno";
@@ -4516,22 +4635,26 @@ function detectDeployment(runtime) {
4516
4635
  }
4517
4636
  function detectRuntimeContext() {
4518
4637
  const runtime = detectRuntime();
4638
+ const os = process.platform;
4519
4639
  return {
4520
- platform: process.platform,
4640
+ os,
4641
+ platform: os,
4521
4642
  runtime,
4522
4643
  deployment: detectDeployment(runtime),
4644
+ provider: detectProvider(),
4645
+ target: getBuildTarget(),
4523
4646
  env: process.env["NODE_ENV"] || "development"
4524
4647
  };
4525
4648
  }
4526
- var cached;
4649
+ var cached3;
4527
4650
  function getRuntimeContext() {
4528
- if (!cached) {
4529
- cached = detectRuntimeContext();
4651
+ if (!cached3) {
4652
+ cached3 = detectRuntimeContext();
4530
4653
  }
4531
- return cached;
4654
+ return cached3;
4532
4655
  }
4533
4656
  function resetRuntimeContext() {
4534
- cached = void 0;
4657
+ cached3 = void 0;
4535
4658
  }
4536
4659
 
4537
4660
  // libs/utils/src/storage/factory.ts
@@ -5372,8 +5495,8 @@ function loadMachineIdSync() {
5372
5495
  if (!isDevPersistenceEnabled()) return null;
5373
5496
  const machineIdPath = resolveMachineIdPath();
5374
5497
  try {
5375
- const fs = __require("fs");
5376
- const content = fs.readFileSync(machineIdPath, "utf8").trim();
5498
+ const fs2 = __require("fs");
5499
+ const content = fs2.readFileSync(machineIdPath, "utf8").trim();
5377
5500
  if (/^[0-9a-f-]{32,36}$/i.test(content)) return content;
5378
5501
  console.warn(`[MachineId] Invalid format at ${machineIdPath}, will regenerate`);
5379
5502
  return null;
@@ -5472,6 +5595,7 @@ export {
5472
5595
  buildPrefix,
5473
5596
  buildSetCookie,
5474
5597
  bytesToHex,
5598
+ checkEntryAvailability,
5475
5599
  clearCachedSecret,
5476
5600
  collapseChar,
5477
5601
  collapseWhitespace,
@@ -5496,6 +5620,7 @@ export {
5496
5620
  encryptAndSerialize,
5497
5621
  encryptValue,
5498
5622
  ensureDir,
5623
+ ensureDirSync,
5499
5624
  ensureMaxLen,
5500
5625
  entryAvailabilitySchema,
5501
5626
  escapeGlob,
@@ -5526,6 +5651,7 @@ export {
5526
5651
  getMachineId,
5527
5652
  getOrCreateSecret,
5528
5653
  getRuntimeContext,
5654
+ getSpawnFn,
5529
5655
  globToRegex,
5530
5656
  hasTemplatePlaceholders,
5531
5657
  hkdfSha256,
@@ -5573,6 +5699,7 @@ export {
5573
5699
  parseUriTemplate,
5574
5700
  join as pathJoin,
5575
5701
  resolve as pathResolve,
5702
+ pathToFileURL,
5576
5703
  pemToPublicJwk2 as pemToPublicJwk,
5577
5704
  processPlatformToolCalls,
5578
5705
  randomBytes,
@@ -5582,6 +5709,7 @@ export {
5582
5709
  readFileSync,
5583
5710
  readJSON,
5584
5711
  readdir,
5712
+ readdirSync,
5585
5713
  realpath,
5586
5714
  rename,
5587
5715
  resetRuntimeContext,
@@ -5601,6 +5729,7 @@ export {
5601
5729
  saveSecret,
5602
5730
  secretDataSchema,
5603
5731
  secretKeyDataSchema,
5732
+ sep,
5604
5733
  sepFor,
5605
5734
  serializeBlob,
5606
5735
  setEnv,
@@ -5635,6 +5764,7 @@ export {
5635
5764
  verifyCodeChallenge,
5636
5765
  verifyData,
5637
5766
  verifyOrParseData,
5767
+ watchFile,
5638
5768
  writeFile,
5639
5769
  writeJSON
5640
5770
  };
package/fs/fs.d.ts CHANGED
@@ -6,6 +6,16 @@
6
6
  * **Note:** These functions are Node.js only and will throw an error
7
7
  * if called in a browser environment.
8
8
  */
9
+ /**
10
+ * Lazily resolve `child_process.spawn` for callers that need to spawn a
11
+ * detached worker (CLI task runners, dev-server supervisors, etc.). Throws
12
+ * via `assertNode` on non-Node runtimes.
13
+ *
14
+ * **Node.js only.** Exposed because consumers (e.g. `CliTaskRunner` in the
15
+ * SDK) need a single place to reach `spawn` without a top-level
16
+ * `import 'node:child_process'` that V8 isolates would choke on.
17
+ */
18
+ export declare function getSpawnFn(): typeof import('child_process').spawn;
9
19
  /**
10
20
  * Read a file's contents as a string.
11
21
  *
@@ -77,6 +87,21 @@ export declare function mkdir(p: string, options?: {
77
87
  recursive?: boolean;
78
88
  mode?: number;
79
89
  }): Promise<void>;
90
+ /**
91
+ * Ensure a directory exists, creating it recursively if needed (synchronous).
92
+ *
93
+ * **Node.js only** - throws an error if called in browser.
94
+ *
95
+ * Use this only when async operations are not possible (e.g., a synchronous
96
+ * constructor that must open a file path before the first I/O). Prefer the
97
+ * async `ensureDir` in most cases.
98
+ *
99
+ * @param p - Path to directory
100
+ *
101
+ * @example
102
+ * ensureDirSync('/path/to/new/directory');
103
+ */
104
+ export declare function ensureDirSync(p: string): void;
80
105
  /**
81
106
  * Rename/move a file or directory.
82
107
  *
@@ -228,6 +253,21 @@ export declare function cp(src: string, dest: string, options?: {
228
253
  * const files = await readdir('/path/to/dir');
229
254
  */
230
255
  export declare function readdir(p: string): Promise<string[]>;
256
+ /**
257
+ * List directory contents synchronously.
258
+ *
259
+ * **Node.js only** - throws an error if called in browser.
260
+ *
261
+ * Use this only when async operations are not possible (e.g., per-scrape
262
+ * synchronous metrics collectors). Prefer the async `readdir` in most cases.
263
+ *
264
+ * @param p - Path to directory
265
+ * @returns Array of file/directory names
266
+ *
267
+ * @example
268
+ * const files = readdirSync('/proc/self/fd');
269
+ */
270
+ export declare function readdirSync(p: string): string[];
231
271
  /**
232
272
  * Remove a file or directory recursively.
233
273
  *
@@ -296,4 +336,36 @@ export declare function access(p: string, mode?: number): Promise<void>;
296
336
  */
297
337
  export declare function runCmd(cmd: string, args: string[], opts?: {
298
338
  cwd?: string;
339
+ env?: NodeJS.ProcessEnv;
299
340
  }): Promise<void>;
341
+ /**
342
+ * Handle returned by {@link watchFile}. `close()` stops the watch and is safe
343
+ * to call multiple times. `onError` registers an error handler — useful for
344
+ * platforms (Linux watch-count limits, network filesystems) that surface
345
+ * mid-stream failures.
346
+ */
347
+ export interface FileWatcherHandle {
348
+ close(): void;
349
+ onError(handler: (err: Error) => void): void;
350
+ }
351
+ /** Options for {@link watchFile}. Mirrors a subset of Node `fs.watch`. */
352
+ export interface WatchFileOptions {
353
+ /** Watch a directory recursively. Node-specific; default false. */
354
+ recursive?: boolean;
355
+ }
356
+ /** Callback fired on each filesystem change event. */
357
+ export type WatchFileListener = (eventType: 'rename' | 'change', filename: string | null) => void;
358
+ /**
359
+ * Watch a file or directory for changes. Returns a handle the caller closes
360
+ * to stop watching.
361
+ *
362
+ * Accepts either a no-arg listener (the simple case) or one that receives
363
+ * the Node-shaped `(eventType, filename)` pair — both are tolerated so
364
+ * shallow callers stay terse while filesystem-walking callers get the full
365
+ * signal.
366
+ *
367
+ * **Node.js only** — calls `assertNode` at invocation time. Build-time env
368
+ * selection (`frontmcp build --target <env>`) keeps this off non-Node
369
+ * targets; runtime callers in V8 isolates will hit the assertion.
370
+ */
371
+ export declare function watchFile(p: string, onChange: WatchFileListener | (() => void), options?: WatchFileOptions): FileWatcherHandle;
package/fs/index.d.ts CHANGED
@@ -1 +1,2 @@
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';
1
+ export { readFile, readFileSync, readFileBuffer, writeFile, mkdir, rename, unlink, stat, copyFile, cp, readdir, readdirSync, realpath, rm, mkdtemp, symlink, access, fileExists, readJSON, writeJSON, ensureDir, ensureDirSync, isDirEmpty, runCmd, watchFile, getSpawnFn, } from './fs';
2
+ export type { FileWatcherHandle } from './fs';
package/index.d.ts CHANGED
@@ -9,18 +9,19 @@ 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, isAbsolute } from './path';
12
+ export { trimSlashes, joinPath, pathResolve, pathJoin, basename, dirname, extname, isAbsolute, pathToFileURL, sep, } 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, realpath, rm, mkdtemp, symlink, access, fileExists, readJSON, writeJSON, ensureDir, isDirEmpty, runCmd, } from './fs';
16
+ export { readFile, readFileSync, readFileBuffer, writeFile, mkdir, rename, unlink, stat, copyFile, cp, readdir, readdirSync, realpath, rm, mkdtemp, symlink, access, fileExists, readJSON, writeJSON, ensureDir, ensureDirSync, isDirEmpty, runCmd, watchFile, getSpawnFn, } from './fs';
17
+ export type { FileWatcherHandle } from './fs';
17
18
  export { escapeHtml, escapeHtmlAttr, escapeJsString, escapeScriptClose, safeJsonForScript } from './escape';
18
19
  export { safeStringify } from './serialization';
19
20
  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
21
  export type { CryptoProvider, EncBlob, EncryptedBlob } from './crypto';
21
22
  export { AsyncLocalStorage } from './async-context';
22
23
  export { EventEmitter } from './event-emitter';
23
- export { getEnv, getCwd, isProduction, isDevelopment, getEnvFlag, isDebug, setEnv, isEdgeRuntime, isServerless, supportsAnsi, getRuntimeContext, resetRuntimeContext, detectRuntimeContext, isEntryAvailable, entryAvailabilitySchema, } from './env';
24
+ export { getEnv, getCwd, isProduction, isDevelopment, getEnvFlag, isDebug, setEnv, isEdgeRuntime, isServerless, supportsAnsi, getRuntimeContext, resetRuntimeContext, detectRuntimeContext, isEntryAvailable, checkEntryAvailability, entryAvailabilitySchema, } from './env';
24
25
  export type { RuntimeContext, EntryAvailability } from './env';
25
26
  export { analyzePattern, isPatternSafe, createSafeRegExp, safeTest, safeMatch, safeReplace, safeExec, isInputLengthSafe, DEFAULT_MAX_INPUT_LENGTH, REDOS_THRESHOLDS, trimLeading, trimTrailing, trimBoth, trimChars, extractBracedParams, expandTemplate, hasTemplatePlaceholders, collapseChar, collapseWhitespace, type SafeRegexOptions, type PatternAnalysisResult, } from './regex';
26
27
  export { createStorage, createMemoryStorage, getDetectedStorageType, NamespacedStorageImpl, createRootStorage, createNamespacedStorage, buildPrefix, NAMESPACE_SEPARATOR, StorageError, StorageConnectionError, StorageOperationError, StorageNotSupportedError, StorageConfigError, StorageTTLError, StoragePatternError, StorageNotConnectedError, EncryptedStorageError, BaseStorageAdapter, MemoryStorageAdapter, RedisStorageAdapter, VercelKvStorageAdapter, UpstashStorageAdapter, FileSystemStorageAdapter, type FileSystemAdapterOptions, TypedStorage, type TypedStorageOptions, type TypedSetOptions, type TypedSetEntry, EncryptedTypedStorage, type EncryptedTypedStorageOptions, type EncryptedSetOptions, type EncryptedSetEntry, type EncryptionKey, type StoredEncryptedBlob, type ClientKeyBinding, globToRegex, matchesPattern, validatePattern, escapeGlob, MAX_TTL_SECONDS, validateTTL, validateOptionalTTL, ttlToExpiresAt, expiresAtToTTL, isExpired, normalizeTTL, } from './storage';
package/index.js CHANGED
@@ -70,13 +70,16 @@ function getSpawn() {
70
70
  }
71
71
  return _spawn;
72
72
  }
73
+ function getSpawnFn() {
74
+ return getSpawn();
75
+ }
73
76
  async function readFile(p, encoding = "utf8") {
74
77
  const fsp = getFsp();
75
78
  return fsp.readFile(p, encoding);
76
79
  }
77
80
  function readFileSync(p, encoding = "utf8") {
78
- const fs = getFs();
79
- return fs.readFileSync(p, encoding);
81
+ const fs2 = getFs();
82
+ return fs2.readFileSync(p, encoding);
80
83
  }
81
84
  async function readFileBuffer(p) {
82
85
  const fsp = getFsp();
@@ -90,6 +93,10 @@ async function mkdir(p, options) {
90
93
  const fsp = getFsp();
91
94
  await fsp.mkdir(p, options);
92
95
  }
96
+ function ensureDirSync(p) {
97
+ const fs2 = getFs();
98
+ fs2.mkdirSync(p, { recursive: true });
99
+ }
93
100
  async function rename(oldPath, newPath) {
94
101
  const fsp = getFsp();
95
102
  await fsp.rename(oldPath, newPath);
@@ -148,6 +155,10 @@ async function readdir(p) {
148
155
  const fsp = getFsp();
149
156
  return fsp.readdir(p);
150
157
  }
158
+ function readdirSync(p) {
159
+ const fs2 = getFs();
160
+ return fs2.readdirSync(p);
161
+ }
151
162
  async function rm(p, options) {
152
163
  const fsp = getFsp();
153
164
  await fsp.rm(p, options);
@@ -178,6 +189,20 @@ function runCmd(cmd, args, opts = {}) {
178
189
  child.on("error", reject);
179
190
  });
180
191
  }
192
+ function watchFile(p, onChange, options) {
193
+ const fs2 = getFs();
194
+ const watcher = fs2.watch(p, { persistent: false, recursive: options?.recursive === true }, (eventType, filename) => {
195
+ onChange(eventType, filename);
196
+ });
197
+ return {
198
+ close() {
199
+ watcher.close();
200
+ },
201
+ onError(handler) {
202
+ watcher.on("error", handler);
203
+ }
204
+ };
205
+ }
181
206
  var _fsp, _fs, _spawn, F_OK;
182
207
  var init_fs = __esm({
183
208
  "libs/utils/src/fs/fs.ts"() {
@@ -566,9 +591,9 @@ function createSecretData(options) {
566
591
  }
567
592
  async function getOrCreateSecret(options) {
568
593
  const cacheKey = resolveSecretPath(options);
569
- const cached2 = secretCache.get(cacheKey);
570
- if (cached2) {
571
- return cached2;
594
+ const cached4 = secretCache.get(cacheKey);
595
+ if (cached4) {
596
+ return cached4;
572
597
  }
573
598
  const existing = generationPromises.get(cacheKey);
574
599
  if (existing) {
@@ -806,8 +831,8 @@ var init_key_persistence = __esm({
806
831
  */
807
832
  async get(kid) {
808
833
  if (this.enableCache) {
809
- const cached2 = this.cache.get(kid);
810
- if (cached2) return cached2;
834
+ const cached4 = this.cache.get(kid);
835
+ if (cached4) return cached4;
811
836
  }
812
837
  const raw = await this.storage.get(kid);
813
838
  if (!raw) return null;
@@ -3834,6 +3859,7 @@ __export(index_exports, {
3834
3859
  buildPrefix: () => buildPrefix,
3835
3860
  buildSetCookie: () => buildSetCookie,
3836
3861
  bytesToHex: () => bytesToHex,
3862
+ checkEntryAvailability: () => checkEntryAvailability,
3837
3863
  clearCachedSecret: () => clearCachedSecret,
3838
3864
  collapseChar: () => collapseChar,
3839
3865
  collapseWhitespace: () => collapseWhitespace,
@@ -3858,6 +3884,7 @@ __export(index_exports, {
3858
3884
  encryptAndSerialize: () => encryptAndSerialize,
3859
3885
  encryptValue: () => encryptValue,
3860
3886
  ensureDir: () => ensureDir,
3887
+ ensureDirSync: () => ensureDirSync,
3861
3888
  ensureMaxLen: () => ensureMaxLen,
3862
3889
  entryAvailabilitySchema: () => entryAvailabilitySchema,
3863
3890
  escapeGlob: () => escapeGlob,
@@ -3888,6 +3915,7 @@ __export(index_exports, {
3888
3915
  getMachineId: () => getMachineId,
3889
3916
  getOrCreateSecret: () => getOrCreateSecret,
3890
3917
  getRuntimeContext: () => getRuntimeContext,
3918
+ getSpawnFn: () => getSpawnFn,
3891
3919
  globToRegex: () => globToRegex,
3892
3920
  hasTemplatePlaceholders: () => hasTemplatePlaceholders,
3893
3921
  hkdfSha256: () => hkdfSha256,
@@ -3935,6 +3963,7 @@ __export(index_exports, {
3935
3963
  parseUriTemplate: () => parseUriTemplate,
3936
3964
  pathJoin: () => import_path.join,
3937
3965
  pathResolve: () => import_path.resolve,
3966
+ pathToFileURL: () => import_url.pathToFileURL,
3938
3967
  pemToPublicJwk: () => pemToPublicJwk2,
3939
3968
  processPlatformToolCalls: () => processPlatformToolCalls,
3940
3969
  randomBytes: () => randomBytes,
@@ -3944,6 +3973,7 @@ __export(index_exports, {
3944
3973
  readFileSync: () => readFileSync,
3945
3974
  readJSON: () => readJSON,
3946
3975
  readdir: () => readdir,
3976
+ readdirSync: () => readdirSync,
3947
3977
  realpath: () => realpath,
3948
3978
  rename: () => rename,
3949
3979
  resetRuntimeContext: () => resetRuntimeContext,
@@ -3963,6 +3993,7 @@ __export(index_exports, {
3963
3993
  saveSecret: () => saveSecret,
3964
3994
  secretDataSchema: () => secretDataSchema,
3965
3995
  secretKeyDataSchema: () => secretKeyDataSchema,
3996
+ sep: () => import_path.sep,
3966
3997
  sepFor: () => sepFor,
3967
3998
  serializeBlob: () => serializeBlob,
3968
3999
  setEnv: () => import_env3.setEnv,
@@ -3997,6 +4028,7 @@ __export(index_exports, {
3997
4028
  verifyCodeChallenge: () => verifyCodeChallenge,
3998
4029
  verifyData: () => verifyData,
3999
4030
  verifyOrParseData: () => verifyOrParseData,
4031
+ watchFile: () => watchFile,
4000
4032
  writeFile: () => writeFile,
4001
4033
  writeJSON: () => writeJSON
4002
4034
  });
@@ -4405,6 +4437,7 @@ function joinPath(...parts) {
4405
4437
 
4406
4438
  // libs/utils/src/path/node-path.ts
4407
4439
  var import_path = require("path");
4440
+ var import_url = require("url");
4408
4441
 
4409
4442
  // libs/utils/src/content/content.ts
4410
4443
  function sanitizeToJson(value) {
@@ -4692,23 +4725,114 @@ var import_env3 = require("#env");
4692
4725
  // libs/utils/src/env/runtime-context.ts
4693
4726
  var import_lazy_zod3 = require("@frontmcp/lazy-zod");
4694
4727
  var import_env2 = require("#env");
4728
+
4729
+ // libs/utils/src/env/build-target.ts
4730
+ var cached;
4731
+ function getBuildTarget() {
4732
+ if (cached) return cached;
4733
+ cached = resolveBuildTarget();
4734
+ return cached;
4735
+ }
4736
+ function resolveBuildTarget() {
4737
+ const globalAny = globalThis;
4738
+ const fromGlobal = globalAny.FRONTMCP_BUILD_TARGET;
4739
+ if (typeof fromGlobal === "string" && isKnownTarget(fromGlobal)) return fromGlobal;
4740
+ const fromEnv = typeof process !== "undefined" ? process.env?.["FRONTMCP_BUILD_TARGET"] : void 0;
4741
+ if (typeof fromEnv === "string" && isKnownTarget(fromEnv)) return fromEnv;
4742
+ return "unknown";
4743
+ }
4744
+ function isKnownTarget(value) {
4745
+ return value === "unknown" || value === "node" || value === "distributed" || value === "cli" || value === "vercel" || value === "lambda" || value === "cloudflare" || value === "browser" || value === "sdk" || value === "mcpb";
4746
+ }
4747
+
4748
+ // libs/utils/src/env/provider.ts
4749
+ var fs = __toESM(require("node:fs"));
4750
+ var cached2;
4751
+ function detectProvider() {
4752
+ if (cached2) return cached2;
4753
+ cached2 = detectProviderUncached();
4754
+ return cached2;
4755
+ }
4756
+ function detectProviderUncached() {
4757
+ const explicit = process.env["FRONTMCP_PROVIDER"];
4758
+ if (explicit && isKnownProvider(explicit)) return explicit;
4759
+ if (process.env["VERCEL"]) return "vercel";
4760
+ if (process.env["AWS_LAMBDA_FUNCTION_NAME"]) return "lambda";
4761
+ if (process.env["CF_PAGES"]) return "cloudflare";
4762
+ if (process.env["NETLIFY"]) return "netlify";
4763
+ if (process.env["AZURE_FUNCTIONS_ENVIRONMENT"]) return "azure";
4764
+ if (process.env["K_SERVICE"]) return "gcp";
4765
+ if (process.env["FLY_APP_NAME"]) return "fly";
4766
+ if (process.env["RENDER"]) return "render";
4767
+ if (process.env["RAILWAY_ENVIRONMENT"]) return "railway";
4768
+ try {
4769
+ if (fs.existsSync("/.dockerenv")) return "docker";
4770
+ } catch {
4771
+ }
4772
+ return "bare";
4773
+ }
4774
+ function isKnownProvider(value) {
4775
+ return value === "bare" || value === "docker" || value === "vercel" || value === "lambda" || value === "cloudflare" || value === "netlify" || value === "azure" || value === "gcp" || value === "fly" || value === "render" || value === "railway";
4776
+ }
4777
+
4778
+ // libs/utils/src/env/runtime-context.ts
4695
4779
  var entryAvailabilitySchema = import_lazy_zod3.z.object({
4780
+ os: import_lazy_zod3.z.array(import_lazy_zod3.z.string().min(1)).optional(),
4696
4781
  platform: import_lazy_zod3.z.array(import_lazy_zod3.z.string().min(1)).optional(),
4697
4782
  runtime: import_lazy_zod3.z.array(import_lazy_zod3.z.string().min(1)).optional(),
4698
4783
  deployment: import_lazy_zod3.z.array(import_lazy_zod3.z.string().min(1)).optional(),
4784
+ provider: import_lazy_zod3.z.array(import_lazy_zod3.z.string().min(1)).optional(),
4785
+ target: import_lazy_zod3.z.array(import_lazy_zod3.z.string().min(1)).optional(),
4786
+ surface: import_lazy_zod3.z.array(import_lazy_zod3.z.enum(["mcp", "cli", "http-trigger", "job", "agent"])).optional(),
4699
4787
  env: import_lazy_zod3.z.array(import_lazy_zod3.z.string().min(1)).optional()
4700
4788
  }).strict();
4701
- function isEntryAvailable(availability, ctx) {
4789
+ var PROCESS_AXES = [
4790
+ // `os` and `platform` both compare against the same RuntimeContext value
4791
+ // — keeping `platform` as a deprecated alias lets pre-#417 metadata
4792
+ // continue to match without any code change.
4793
+ { key: "os", ctxKey: "os" },
4794
+ { key: "platform", ctxKey: "platform" },
4795
+ { key: "runtime", ctxKey: "runtime" },
4796
+ { key: "deployment", ctxKey: "deployment" },
4797
+ { key: "provider", ctxKey: "provider" },
4798
+ { key: "target", ctxKey: "target" },
4799
+ { key: "env", ctxKey: "env" }
4800
+ ];
4801
+ function isEntryAvailable(availability, ctx, callCtx) {
4702
4802
  if (!availability) return true;
4703
- const fields = ["platform", "runtime", "deployment", "env"];
4704
- for (const field of fields) {
4705
- const allowed = availability[field];
4803
+ for (const { key, ctxKey } of PROCESS_AXES) {
4804
+ const allowed = availability[key];
4706
4805
  if (allowed === void 0) continue;
4707
4806
  if (allowed.length === 0) return false;
4708
- if (!allowed.includes(ctx[field])) return false;
4807
+ if (!allowed.includes(ctx[ctxKey])) return false;
4808
+ }
4809
+ if (availability.surface !== void 0) {
4810
+ if (availability.surface.length === 0) return false;
4811
+ if (callCtx?.surface !== void 0 && !availability.surface.includes(callCtx.surface)) {
4812
+ return false;
4813
+ }
4709
4814
  }
4710
4815
  return true;
4711
4816
  }
4817
+ function checkEntryAvailability(availability, ctx, callCtx) {
4818
+ if (!availability) return { available: true, missingAxes: [] };
4819
+ const missing = [];
4820
+ for (const { key, ctxKey } of PROCESS_AXES) {
4821
+ const allowed = availability[key];
4822
+ if (allowed === void 0) continue;
4823
+ if (allowed.length === 0 || !allowed.includes(ctx[ctxKey])) {
4824
+ missing.push(key);
4825
+ }
4826
+ }
4827
+ if (availability.surface !== void 0) {
4828
+ if (availability.surface.length === 0) {
4829
+ missing.push("surface");
4830
+ } else if (callCtx?.surface !== void 0 && !availability.surface.includes(callCtx.surface)) {
4831
+ missing.push("surface");
4832
+ }
4833
+ }
4834
+ return { available: missing.length === 0, missingAxes: missing };
4835
+ }
4712
4836
  function detectRuntime() {
4713
4837
  if (typeof globalThis !== "undefined" && "Bun" in globalThis) return "bun";
4714
4838
  if (typeof globalThis !== "undefined" && "Deno" in globalThis) return "deno";
@@ -4725,22 +4849,26 @@ function detectDeployment(runtime) {
4725
4849
  }
4726
4850
  function detectRuntimeContext() {
4727
4851
  const runtime = detectRuntime();
4852
+ const os = process.platform;
4728
4853
  return {
4729
- platform: process.platform,
4854
+ os,
4855
+ platform: os,
4730
4856
  runtime,
4731
4857
  deployment: detectDeployment(runtime),
4858
+ provider: detectProvider(),
4859
+ target: getBuildTarget(),
4732
4860
  env: process.env["NODE_ENV"] || "development"
4733
4861
  };
4734
4862
  }
4735
- var cached;
4863
+ var cached3;
4736
4864
  function getRuntimeContext() {
4737
- if (!cached) {
4738
- cached = detectRuntimeContext();
4865
+ if (!cached3) {
4866
+ cached3 = detectRuntimeContext();
4739
4867
  }
4740
- return cached;
4868
+ return cached3;
4741
4869
  }
4742
4870
  function resetRuntimeContext() {
4743
- cached = void 0;
4871
+ cached3 = void 0;
4744
4872
  }
4745
4873
 
4746
4874
  // libs/utils/src/storage/factory.ts
@@ -5581,8 +5709,8 @@ function loadMachineIdSync() {
5581
5709
  if (!isDevPersistenceEnabled()) return null;
5582
5710
  const machineIdPath = resolveMachineIdPath();
5583
5711
  try {
5584
- const fs = require("fs");
5585
- const content = fs.readFileSync(machineIdPath, "utf8").trim();
5712
+ const fs2 = require("fs");
5713
+ const content = fs2.readFileSync(machineIdPath, "utf8").trim();
5586
5714
  if (/^[0-9a-f-]{32,36}$/i.test(content)) return content;
5587
5715
  console.warn(`[MachineId] Invalid format at ${machineIdPath}, will regenerate`);
5588
5716
  return null;
@@ -5682,6 +5810,7 @@ function setMachineIdOverride(id) {
5682
5810
  buildPrefix,
5683
5811
  buildSetCookie,
5684
5812
  bytesToHex,
5813
+ checkEntryAvailability,
5685
5814
  clearCachedSecret,
5686
5815
  collapseChar,
5687
5816
  collapseWhitespace,
@@ -5706,6 +5835,7 @@ function setMachineIdOverride(id) {
5706
5835
  encryptAndSerialize,
5707
5836
  encryptValue,
5708
5837
  ensureDir,
5838
+ ensureDirSync,
5709
5839
  ensureMaxLen,
5710
5840
  entryAvailabilitySchema,
5711
5841
  escapeGlob,
@@ -5736,6 +5866,7 @@ function setMachineIdOverride(id) {
5736
5866
  getMachineId,
5737
5867
  getOrCreateSecret,
5738
5868
  getRuntimeContext,
5869
+ getSpawnFn,
5739
5870
  globToRegex,
5740
5871
  hasTemplatePlaceholders,
5741
5872
  hkdfSha256,
@@ -5783,6 +5914,7 @@ function setMachineIdOverride(id) {
5783
5914
  parseUriTemplate,
5784
5915
  pathJoin,
5785
5916
  pathResolve,
5917
+ pathToFileURL,
5786
5918
  pemToPublicJwk,
5787
5919
  processPlatformToolCalls,
5788
5920
  randomBytes,
@@ -5792,6 +5924,7 @@ function setMachineIdOverride(id) {
5792
5924
  readFileSync,
5793
5925
  readJSON,
5794
5926
  readdir,
5927
+ readdirSync,
5795
5928
  realpath,
5796
5929
  rename,
5797
5930
  resetRuntimeContext,
@@ -5811,6 +5944,7 @@ function setMachineIdOverride(id) {
5811
5944
  saveSecret,
5812
5945
  secretDataSchema,
5813
5946
  secretKeyDataSchema,
5947
+ sep,
5814
5948
  sepFor,
5815
5949
  serializeBlob,
5816
5950
  setEnv,
@@ -5845,6 +5979,7 @@ function setMachineIdOverride(id) {
5845
5979
  verifyCodeChallenge,
5846
5980
  verifyData,
5847
5981
  verifyOrParseData,
5982
+ watchFile,
5848
5983
  writeFile,
5849
5984
  writeJSON
5850
5985
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@frontmcp/utils",
3
- "version": "1.2.1",
3
+ "version": "1.4.0",
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.2.1"
37
+ "@frontmcp/lazy-zod": "1.4.0"
38
38
  },
39
39
  "peerDependenciesMeta": {
40
40
  "@vercel/kv": {
@@ -4,3 +4,10 @@ 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
6
  export declare function isAbsolute(p: string): boolean;
7
+ export declare function pathToFileURL(_p: string): URL;
8
+ /**
9
+ * Platform-specific path separator. In browser/Worker builds we mirror POSIX
10
+ * (`/`) — these targets never read a real filesystem so the value is only
11
+ * used for string parsing, and POSIX matches the runtime's URL conventions.
12
+ */
13
+ export declare const sep = "/";
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, isAbsolute } from '#path';
2
+ export { resolve as pathResolve, join as pathJoin, basename, dirname, extname, isAbsolute, pathToFileURL, sep, } from '#path';
@@ -1 +1,2 @@
1
- export { resolve, join, basename, dirname, extname, isAbsolute } from 'path';
1
+ export { resolve, join, basename, dirname, extname, isAbsolute, sep } from 'path';
2
+ export { pathToFileURL } from 'url';