@frontmcp/uipack 0.8.1 → 0.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,98 +1,47 @@
1
1
  # @frontmcp/uipack
2
2
 
3
- React-free build utilities, theming, runtime helpers, and platform adapters for FrontMCP UI development. `@frontmcp/uipack` powers HTML string templates, cacheable widgets, and discovery metadata without pulling React or DOM libraries into your server.
3
+ React-free build utilities, theming, runtime helpers, and platform adapters for FrontMCP UI development.
4
4
 
5
- ## Package Split
5
+ [![NPM](https://img.shields.io/npm/v/@frontmcp/uipack.svg)](https://www.npmjs.com/package/@frontmcp/uipack)
6
6
 
7
- | Package | Purpose | React Required |
8
- | ------------------ | --------------------------------------------------------------------- | --------------------- |
9
- | `@frontmcp/uipack` | Themes, runtime helpers, build/render pipelines, validation, adapters | No |
10
- | `@frontmcp/ui` | HTML/React components, layouts, widgets, web components | Yes (peer dependency) |
7
+ ## Package Split
11
8
 
12
- Install `@frontmcp/uipack` when you need HTML-first tooling, template validation, or platform adapters. Install `@frontmcp/ui` alongside it for ready-made components.
9
+ | Package | Purpose | React Required |
10
+ | ------------------ | --------------------------------------------------------------------- | -------------- |
11
+ | `@frontmcp/uipack` | Themes, runtime helpers, build/render pipelines, validation, adapters | No |
12
+ | `@frontmcp/ui` | HTML/React components, layouts, widgets, web components | Yes (peer dep) |
13
13
 
14
- ## Installation
14
+ ## Install
15
15
 
16
16
  ```bash
17
17
  npm install @frontmcp/uipack
18
- # or
19
- yarn add @frontmcp/uipack
20
18
  ```
21
19
 
22
20
  ## Features
23
21
 
24
- - **Theme system** Configure Tailwind-style palettes, fonts, and CDN assets, then inline or externalize scripts per platform.
25
- - **Build API** Compile tool templates with esbuild/SWC, emit static widgets, and ship cached manifests for serverless environments.
26
- - **Build modes** Choose static, dynamic, or hybrid rendering plus multi-platform bundler helpers so the same widget rehydrates on OpenAI, Claude, and Gemini.
27
- - **Runtime helpers** Wrap HTML/React/MDX templates with CSP, sanitize user content, and expose MCP Bridge metadata.
28
- - **Platform adapters** Generate OpenAI/Claude/Gemini discovery metadata, resolve serving modes, and understand host capabilities.
29
- - **Validation** Extract schema paths, validate Handlebars templates, and render error boxes before code hits production.
30
- - **Bundler/cache** File-system and Redis caches, transpile/render caches, and hashing utilities for incremental builds.
31
-
32
- ## Quick Start
33
-
34
- ### Theme + layout utilities
35
-
36
- ```ts
37
- import { DEFAULT_THEME, createTheme, buildCdnScriptsFromTheme } from '@frontmcp/uipack/theme';
38
-
39
- const customTheme = createTheme({
40
- name: 'brand',
41
- colors: {
42
- semantic: {
43
- primary: '#0BA5EC',
44
- secondary: '#6366F1',
45
- },
46
- },
47
- });
48
-
49
- const scripts = await buildCdnScriptsFromTheme(customTheme, { inline: true });
50
- ```
22
+ - **Theme system** Tailwind-style palettes, fonts, CDN assets, platform-aware inlining ([docs][docs-theme])
23
+ - **Build API** compile tool templates with esbuild/SWC, emit static widgets, cached manifests ([docs][docs-build])
24
+ - **Build modes** static, dynamic, or hybrid rendering; multi-platform bundler helpers ([docs][docs-build-modes])
25
+ - **Runtime helpers** wrap HTML/React/MDX with CSP, sanitize content, expose MCP Bridge metadata ([docs][docs-runtime])
26
+ - **Platform adapters** OpenAI/Claude/Gemini discovery metadata, serving modes, host capabilities ([docs][docs-adapters])
27
+ - **Validation** schema path extraction, Handlebars template validation, error boxes ([docs][docs-validation])
28
+ - **Bundler/cache** filesystem and Redis caches, transpile/render caches, hashing utilities ([docs][docs-bundler])
51
29
 
52
- ### Build tool UI HTML
30
+ ## Quick Example
53
31
 
54
32
  ```ts
55
33
  import { buildToolUI } from '@frontmcp/uipack/build';
56
34
 
57
35
  const result = await buildToolUI({
58
- template: '<div>{{output.temperature}} °C</div>',
59
- context: {
60
- input: { location: 'London' },
61
- output: { temperature: 18 },
62
- },
36
+ template: '<div>{{output.temperature}} C</div>',
37
+ context: { input: { location: 'London' }, output: { temperature: 18 } },
63
38
  platform: 'openai',
64
39
  });
65
-
66
- console.log(result.html);
67
40
  ```
68
41
 
69
- ### Wrap tool responses with MCP metadata
70
-
71
- ```ts
72
- import { wrapToolUI, createTemplateHelpers } from '@frontmcp/uipack/runtime';
42
+ > Full guide: [UI Overview][docs-overview]
73
43
 
74
- The helpers = createTemplateHelpers();
75
- const html = `<div>${helpers.escapeHtml(ctx.output.summary)}</div>`;
76
-
77
- const response = wrapToolUI({
78
- html,
79
- displayMode: 'inline',
80
- servingMode: 'auto',
81
- widgetDescription: 'Summarized status card',
82
- });
83
- ```
84
-
85
- ### Resolve platform capabilities
86
-
87
- ```ts
88
- import { getPlatform, canUseCdn, needsInlineScripts } from '@frontmcp/uipack/theme';
89
-
90
- const claude = getPlatform('claude');
91
- const inline = needsInlineScripts(claude);
92
- const cdnOk = canUseCdn(claude);
93
- ```
94
-
95
- ## Entry points
44
+ ## Entry Points
96
45
 
97
46
  | Path | Purpose |
98
47
  | ----------------------------- | -------------------------------------------------------- |
@@ -107,45 +56,36 @@ const cdnOk = canUseCdn(claude);
107
56
  | `@frontmcp/uipack/types` | Shared template/context types |
108
57
  | `@frontmcp/uipack/utils` | Escaping, safe stringify, helper utilities |
109
58
 
110
- ## Template validation
59
+ ## Docs
111
60
 
112
- ```ts
113
- import { validateTemplate } from '@frontmcp/uipack/validation';
114
- import { z } from 'zod';
61
+ | Topic | Link |
62
+ | ----------------- | ------------------------------- |
63
+ | Overview | [UI Overview][docs-overview] |
64
+ | Theme system | [Theming][docs-theme] |
65
+ | Build API | [Build Tools][docs-build] |
66
+ | Build modes | [Build Modes][docs-build-modes] |
67
+ | Runtime helpers | [Runtime][docs-runtime] |
68
+ | Platform adapters | [Adapters][docs-adapters] |
69
+ | Validation | [Validation][docs-validation] |
70
+ | Bundler | [Bundler][docs-bundler] |
115
71
 
116
- const outputSchema = z.object({
117
- temperature: z.number(),
118
- city: z.string(),
119
- });
72
+ ## Related Packages
120
73
 
121
- const result = validateTemplate('<div>{{output.city}}</div>', outputSchema);
122
- if (!result valid) {
123
- console.warn(result.errors);
124
- }
125
- ```
126
-
127
- ## Cache + bundler helpers
128
-
129
- ```ts
130
- import { createFilesystemBuilder } from '@frontmcp/uipack/bundler/file-cache';
74
+ - [`@frontmcp/ui`](../ui) React components that consume these helpers
75
+ - [`@frontmcp/sdk`](../sdk) — core framework and decorators
76
+ - [`@frontmcp/testing`](../testing) — UI assertions for automated testing
131
77
 
132
- const builder = await createFilesystemBuilder('.frontmcp-cache/builds');
133
- const manifest = await builder.build({ entry: './widgets/weather.tsx' });
134
- ```
135
-
136
- ## Development
137
-
138
- ```bash
139
- yarn nx build uipack
140
- yarn nx test uipack
141
- ```
142
-
143
- ## Related packages
78
+ ## License
144
79
 
145
- - [`@frontmcp/ui`](../ui/README.md) – Component library that consumes these helpers
146
- - [`@frontmcp/sdk`](../sdk/README.md) – Core SDK and decorators
147
- - [`@frontmcp/testing`](../testing/README.md) – UI assertions for automated testing
80
+ Apache-2.0 — see [LICENSE](../../LICENSE).
148
81
 
149
- ## License
82
+ <!-- links -->
150
83
 
151
- Apache-2.0
84
+ [docs-overview]: https://docs.agentfront.dev/frontmcp/ui/overview
85
+ [docs-theme]: https://docs.agentfront.dev/frontmcp/ui/theming
86
+ [docs-build]: https://docs.agentfront.dev/frontmcp/ui/build-tools
87
+ [docs-build-modes]: https://docs.agentfront.dev/frontmcp/ui/build-modes
88
+ [docs-runtime]: https://docs.agentfront.dev/frontmcp/ui/runtime
89
+ [docs-adapters]: https://docs.agentfront.dev/frontmcp/ui/adapters
90
+ [docs-validation]: https://docs.agentfront.dev/frontmcp/ui/validation
91
+ [docs-bundler]: https://docs.agentfront.dev/frontmcp/ui/bundler
package/esm/index.mjs CHANGED
@@ -797,6 +797,184 @@ var init_theme2 = __esm({
797
797
  }
798
798
  });
799
799
 
800
+ // libs/uipack/src/runtime/sanitizer.ts
801
+ import DOMPurify from "dompurify";
802
+ function isEmail(value) {
803
+ return PII_PATTERNS.email.test(value);
804
+ }
805
+ function isPhone(value) {
806
+ return PII_PATTERNS.phone.test(value);
807
+ }
808
+ function isCreditCard(value) {
809
+ const digits = value.replace(/[-\s]/g, "");
810
+ return digits.length >= 13 && digits.length <= 19 && PII_PATTERNS.creditCard.test(value);
811
+ }
812
+ function isSSN(value) {
813
+ return PII_PATTERNS.ssn.test(value);
814
+ }
815
+ function isIPv4(value) {
816
+ return PII_PATTERNS.ipv4.test(value);
817
+ }
818
+ function detectPIIType(value) {
819
+ if (isEmail(value)) return "EMAIL";
820
+ if (isCreditCard(value)) return "CARD";
821
+ if (isSSN(value)) return "ID";
822
+ if (isPhone(value)) return "PHONE";
823
+ if (isIPv4(value)) return "IP";
824
+ return null;
825
+ }
826
+ function redactPIIFromText(text) {
827
+ if (text.length > MAX_PII_TEXT_LENGTH) {
828
+ return text;
829
+ }
830
+ let result = text;
831
+ result = result.replace(PII_PATTERNS.creditCardInText, REDACTION_TOKENS.CARD);
832
+ result = result.replace(PII_PATTERNS.ssnInText, REDACTION_TOKENS.ID);
833
+ result = result.replace(PII_PATTERNS.emailInText, REDACTION_TOKENS.EMAIL);
834
+ result = result.replace(PII_PATTERNS.phoneInText, REDACTION_TOKENS.PHONE);
835
+ result = result.replace(PII_PATTERNS.ipv4InText, REDACTION_TOKENS.IP);
836
+ return result;
837
+ }
838
+ function sanitizeValue(key, value, path, options, depth, visited) {
839
+ const maxDepth = options.maxDepth ?? 10;
840
+ if (depth > maxDepth) {
841
+ return value;
842
+ }
843
+ if (value === null || value === void 0) {
844
+ return value;
845
+ }
846
+ if (typeof options.mode === "function") {
847
+ return options.mode(key, value, path);
848
+ }
849
+ if (Array.isArray(options.mode)) {
850
+ const lowerKey = key.toLowerCase();
851
+ if (options.mode.some((f) => f.toLowerCase() === lowerKey)) {
852
+ return REDACTION_TOKENS.REDACTED;
853
+ }
854
+ }
855
+ if (typeof value === "string") {
856
+ if (options.mode === true) {
857
+ const piiType = detectPIIType(value);
858
+ if (piiType) {
859
+ return REDACTION_TOKENS[piiType];
860
+ }
861
+ if (options.sanitizeInText !== false) {
862
+ const redacted = redactPIIFromText(value);
863
+ if (redacted !== value) {
864
+ return redacted;
865
+ }
866
+ }
867
+ }
868
+ return value;
869
+ }
870
+ if (Array.isArray(value)) {
871
+ if (visited.has(value)) {
872
+ return REDACTION_TOKENS.REDACTED;
873
+ }
874
+ visited.add(value);
875
+ return value.map(
876
+ (item, index) => sanitizeValue(String(index), item, [...path, String(index)], options, depth + 1, visited)
877
+ );
878
+ }
879
+ if (typeof value === "object") {
880
+ if (visited.has(value)) {
881
+ return REDACTION_TOKENS.REDACTED;
882
+ }
883
+ visited.add(value);
884
+ const result = {};
885
+ for (const [k, v] of Object.entries(value)) {
886
+ result[k] = sanitizeValue(k, v, [...path, k], options, depth + 1, visited);
887
+ }
888
+ return result;
889
+ }
890
+ return value;
891
+ }
892
+ function sanitizeInput(input, mode = true) {
893
+ if (!input || typeof input !== "object") {
894
+ return {};
895
+ }
896
+ const options = {
897
+ mode,
898
+ maxDepth: 10,
899
+ sanitizeInText: true
900
+ };
901
+ const visited = /* @__PURE__ */ new WeakSet();
902
+ return sanitizeValue("", input, [], options, 0, visited);
903
+ }
904
+ function createSanitizer(mode = true) {
905
+ return (input) => sanitizeInput(input, mode);
906
+ }
907
+ function detectPII(input, options) {
908
+ const maxDepth = options?.maxDepth ?? 10;
909
+ const fields = [];
910
+ const visited = /* @__PURE__ */ new WeakSet();
911
+ const check = (value, path, depth) => {
912
+ if (depth > maxDepth) return;
913
+ if (value === null || value === void 0) return;
914
+ if (typeof value === "string") {
915
+ if (detectPIIType(value)) {
916
+ fields.push(path.join("."));
917
+ }
918
+ return;
919
+ }
920
+ if (Array.isArray(value)) {
921
+ if (visited.has(value)) return;
922
+ visited.add(value);
923
+ value.forEach((item, index) => check(item, [...path, String(index)], depth + 1));
924
+ return;
925
+ }
926
+ if (typeof value === "object") {
927
+ if (visited.has(value)) return;
928
+ visited.add(value);
929
+ for (const [k, v] of Object.entries(value)) {
930
+ check(v, [...path, k], depth + 1);
931
+ }
932
+ }
933
+ };
934
+ check(input, [], 0);
935
+ return {
936
+ hasPII: fields.length > 0,
937
+ fields
938
+ };
939
+ }
940
+ var REDACTION_TOKENS, PII_PATTERNS, MAX_PII_TEXT_LENGTH;
941
+ var init_sanitizer = __esm({
942
+ "libs/uipack/src/runtime/sanitizer.ts"() {
943
+ "use strict";
944
+ REDACTION_TOKENS = {
945
+ EMAIL: "[EMAIL]",
946
+ PHONE: "[PHONE]",
947
+ CARD: "[CARD]",
948
+ ID: "[ID]",
949
+ IP: "[IP]",
950
+ REDACTED: "[REDACTED]"
951
+ };
952
+ PII_PATTERNS = {
953
+ // Email: user@domain.tld
954
+ email: /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/,
955
+ // Email embedded in text
956
+ emailInText: /[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}/g,
957
+ // Phone: Various formats (US-centric but flexible)
958
+ phone: /^[+]?[(]?[0-9]{1,4}[)]?[-\s.]?[0-9]{1,4}[-\s.]?[0-9]{1,9}$/,
959
+ // Phone embedded in text
960
+ phoneInText: /(?:\+?1[-.\s]?)?\(?[0-9]{3}\)?[-.\s]?[0-9]{3}[-.\s]?[0-9]{4}/g,
961
+ // Credit card: 13-19 digits (with optional separators)
962
+ creditCard: /^(?:[0-9]{4}[-\s]?){3,4}[0-9]{1,4}$/,
963
+ // Credit card embedded in text
964
+ creditCardInText: /\b(?:[0-9]{4}[-\s]?){3,4}[0-9]{1,4}\b/g,
965
+ // SSN: XXX-XX-XXXX
966
+ ssn: /^[0-9]{3}[-]?[0-9]{2}[-]?[0-9]{4}$/,
967
+ // SSN embedded in text
968
+ ssnInText: /\b[0-9]{3}[-]?[0-9]{2}[-]?[0-9]{4}\b/g,
969
+ // IPv4 address
970
+ ipv4: /^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/,
971
+ // IPv4 embedded in text
972
+ ipv4InText: /\b(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\b/g
973
+ };
974
+ MAX_PII_TEXT_LENGTH = 1e5;
975
+ }
976
+ });
977
+
800
978
  // libs/uipack/src/renderers/utils/detect.ts
801
979
  function isReactComponent(value) {
802
980
  if (typeof value !== "function") {
@@ -6491,8 +6669,14 @@ var MCP_BRIDGE_RUNTIME = `
6491
6669
  </script>
6492
6670
  `;
6493
6671
  function getMCPBridgeScript() {
6494
- const match = MCP_BRIDGE_RUNTIME.match(/<script>([\s\S]*?)<\/script>/);
6495
- return match ? match[1].trim() : "";
6672
+ const openTag = "<script>";
6673
+ const closeTag = "</script>";
6674
+ const startIdx = MCP_BRIDGE_RUNTIME.indexOf(openTag);
6675
+ const endIdx = MCP_BRIDGE_RUNTIME.lastIndexOf(closeTag);
6676
+ if (startIdx === -1 || endIdx === -1 || endIdx <= startIdx) {
6677
+ return "";
6678
+ }
6679
+ return MCP_BRIDGE_RUNTIME.slice(startIdx + openTag.length, endIdx).trim();
6496
6680
  }
6497
6681
  function isMCPBridgeSupported() {
6498
6682
  if (typeof window === "undefined") return false;
@@ -6587,179 +6771,7 @@ function sanitizeCSPDomains(domains) {
6587
6771
  // libs/uipack/src/runtime/wrapper.ts
6588
6772
  init_theme2();
6589
6773
  init_utils();
6590
-
6591
- // libs/uipack/src/runtime/sanitizer.ts
6592
- var REDACTION_TOKENS = {
6593
- EMAIL: "[EMAIL]",
6594
- PHONE: "[PHONE]",
6595
- CARD: "[CARD]",
6596
- ID: "[ID]",
6597
- IP: "[IP]",
6598
- REDACTED: "[REDACTED]"
6599
- };
6600
- var PII_PATTERNS = {
6601
- // Email: user@domain.tld
6602
- email: /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/,
6603
- // Email embedded in text
6604
- emailInText: /[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}/g,
6605
- // Phone: Various formats (US-centric but flexible)
6606
- phone: /^[+]?[(]?[0-9]{1,4}[)]?[-\s.]?[0-9]{1,4}[-\s.]?[0-9]{1,9}$/,
6607
- // Phone embedded in text
6608
- phoneInText: /(?:\+?1[-.\s]?)?\(?[0-9]{3}\)?[-.\s]?[0-9]{3}[-.\s]?[0-9]{4}/g,
6609
- // Credit card: 13-19 digits (with optional separators)
6610
- creditCard: /^(?:[0-9]{4}[-\s]?){3,4}[0-9]{1,4}$/,
6611
- // Credit card embedded in text
6612
- creditCardInText: /\b(?:[0-9]{4}[-\s]?){3,4}[0-9]{1,4}\b/g,
6613
- // SSN: XXX-XX-XXXX
6614
- ssn: /^[0-9]{3}[-]?[0-9]{2}[-]?[0-9]{4}$/,
6615
- // SSN embedded in text
6616
- ssnInText: /\b[0-9]{3}[-]?[0-9]{2}[-]?[0-9]{4}\b/g,
6617
- // IPv4 address
6618
- ipv4: /^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/,
6619
- // IPv4 embedded in text
6620
- ipv4InText: /\b(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\b/g
6621
- };
6622
- function isEmail(value) {
6623
- return PII_PATTERNS.email.test(value);
6624
- }
6625
- function isPhone(value) {
6626
- return PII_PATTERNS.phone.test(value);
6627
- }
6628
- function isCreditCard(value) {
6629
- const digits = value.replace(/[-\s]/g, "");
6630
- return digits.length >= 13 && digits.length <= 19 && PII_PATTERNS.creditCard.test(value);
6631
- }
6632
- function isSSN(value) {
6633
- return PII_PATTERNS.ssn.test(value);
6634
- }
6635
- function isIPv4(value) {
6636
- return PII_PATTERNS.ipv4.test(value);
6637
- }
6638
- function detectPIIType(value) {
6639
- if (isEmail(value)) return "EMAIL";
6640
- if (isCreditCard(value)) return "CARD";
6641
- if (isSSN(value)) return "ID";
6642
- if (isPhone(value)) return "PHONE";
6643
- if (isIPv4(value)) return "IP";
6644
- return null;
6645
- }
6646
- var MAX_PII_TEXT_LENGTH = 1e5;
6647
- function redactPIIFromText(text) {
6648
- if (text.length > MAX_PII_TEXT_LENGTH) {
6649
- return text;
6650
- }
6651
- let result = text;
6652
- result = result.replace(PII_PATTERNS.creditCardInText, REDACTION_TOKENS.CARD);
6653
- result = result.replace(PII_PATTERNS.ssnInText, REDACTION_TOKENS.ID);
6654
- result = result.replace(PII_PATTERNS.emailInText, REDACTION_TOKENS.EMAIL);
6655
- result = result.replace(PII_PATTERNS.phoneInText, REDACTION_TOKENS.PHONE);
6656
- result = result.replace(PII_PATTERNS.ipv4InText, REDACTION_TOKENS.IP);
6657
- return result;
6658
- }
6659
- function sanitizeValue(key, value, path, options, depth, visited) {
6660
- const maxDepth = options.maxDepth ?? 10;
6661
- if (depth > maxDepth) {
6662
- return value;
6663
- }
6664
- if (value === null || value === void 0) {
6665
- return value;
6666
- }
6667
- if (typeof options.mode === "function") {
6668
- return options.mode(key, value, path);
6669
- }
6670
- if (Array.isArray(options.mode)) {
6671
- const lowerKey = key.toLowerCase();
6672
- if (options.mode.some((f) => f.toLowerCase() === lowerKey)) {
6673
- return REDACTION_TOKENS.REDACTED;
6674
- }
6675
- }
6676
- if (typeof value === "string") {
6677
- if (options.mode === true) {
6678
- const piiType = detectPIIType(value);
6679
- if (piiType) {
6680
- return REDACTION_TOKENS[piiType];
6681
- }
6682
- if (options.sanitizeInText !== false) {
6683
- const redacted = redactPIIFromText(value);
6684
- if (redacted !== value) {
6685
- return redacted;
6686
- }
6687
- }
6688
- }
6689
- return value;
6690
- }
6691
- if (Array.isArray(value)) {
6692
- if (visited.has(value)) {
6693
- return REDACTION_TOKENS.REDACTED;
6694
- }
6695
- visited.add(value);
6696
- return value.map(
6697
- (item, index) => sanitizeValue(String(index), item, [...path, String(index)], options, depth + 1, visited)
6698
- );
6699
- }
6700
- if (typeof value === "object") {
6701
- if (visited.has(value)) {
6702
- return REDACTION_TOKENS.REDACTED;
6703
- }
6704
- visited.add(value);
6705
- const result = {};
6706
- for (const [k, v] of Object.entries(value)) {
6707
- result[k] = sanitizeValue(k, v, [...path, k], options, depth + 1, visited);
6708
- }
6709
- return result;
6710
- }
6711
- return value;
6712
- }
6713
- function sanitizeInput(input, mode = true) {
6714
- if (!input || typeof input !== "object") {
6715
- return {};
6716
- }
6717
- const options = {
6718
- mode,
6719
- maxDepth: 10,
6720
- sanitizeInText: true
6721
- };
6722
- const visited = /* @__PURE__ */ new WeakSet();
6723
- return sanitizeValue("", input, [], options, 0, visited);
6724
- }
6725
- function createSanitizer(mode = true) {
6726
- return (input) => sanitizeInput(input, mode);
6727
- }
6728
- function detectPII(input, options) {
6729
- const maxDepth = options?.maxDepth ?? 10;
6730
- const fields = [];
6731
- const visited = /* @__PURE__ */ new WeakSet();
6732
- const check = (value, path, depth) => {
6733
- if (depth > maxDepth) return;
6734
- if (value === null || value === void 0) return;
6735
- if (typeof value === "string") {
6736
- if (detectPIIType(value)) {
6737
- fields.push(path.join("."));
6738
- }
6739
- return;
6740
- }
6741
- if (Array.isArray(value)) {
6742
- if (visited.has(value)) return;
6743
- visited.add(value);
6744
- value.forEach((item, index) => check(item, [...path, String(index)], depth + 1));
6745
- return;
6746
- }
6747
- if (typeof value === "object") {
6748
- if (visited.has(value)) return;
6749
- visited.add(value);
6750
- for (const [k, v] of Object.entries(value)) {
6751
- check(v, [...path, k], depth + 1);
6752
- }
6753
- }
6754
- };
6755
- check(input, [], 0);
6756
- return {
6757
- hasPII: fields.length > 0,
6758
- fields
6759
- };
6760
- }
6761
-
6762
- // libs/uipack/src/runtime/wrapper.ts
6774
+ init_sanitizer();
6763
6775
  function createTemplateHelpers() {
6764
6776
  let idCounter2 = 0;
6765
6777
  return {
@@ -10909,6 +10921,9 @@ async function buildToolUIMulti(options) {
10909
10921
  };
10910
10922
  }
10911
10923
 
10924
+ // libs/uipack/src/runtime/index.ts
10925
+ init_sanitizer();
10926
+
10912
10927
  // libs/uipack/src/preview/openai-preview.ts
10913
10928
  init_utils();
10914
10929
  var DEFAULT_WIDGET_CSP = {
package/esm/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@frontmcp/uipack",
3
- "version": "0.8.1",
3
+ "version": "0.9.0",
4
4
  "description": "FrontMCP UIpack - Bundling, build tools, and platform adapters for MCP UI development (React-free core)",
5
5
  "author": "AgentFront <info@agentfront.dev>",
6
6
  "homepage": "https://docs.agentfront.dev",
@@ -58,9 +58,10 @@
58
58
  "node": ">=22.0.0"
59
59
  },
60
60
  "dependencies": {
61
- "@frontmcp/utils": "0.8.1",
61
+ "@frontmcp/utils": "0.9.0",
62
62
  "@swc/core": "^1.5.0",
63
63
  "@enclave-vm/core": "^2.10.1",
64
+ "dompurify": "^3.3.1",
64
65
  "esbuild": "^0.27.1",
65
66
  "handlebars": "^4.7.8",
66
67
  "zod": "^4.0.0"