c15t 2.0.0 → 2.0.4

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/CHANGELOG.md CHANGED
@@ -1,5 +1,13 @@
1
1
  # c15t
2
2
 
3
+ ## 2.0.4
4
+
5
+ ### Patch Changes
6
+
7
+ - 748536a: Refine policy category scope handling.
8
+ - Updated dependencies [748536a]
9
+ - @c15t/schema@2.0.1
10
+
3
11
  ## 2.0.0
4
12
 
5
13
  ### Major Changes
package/README.md CHANGED
@@ -1,14 +1,14 @@
1
1
  <p align="center">
2
- <a href="https://c15t.com?utm_source=github&utm_medium=repopage_c15t" target="_blank" rel="noopener noreferrer">
2
+ <a href="https://c15t.com?utm_source=npm&utm_medium=readme&utm_campaign=oss_readme&utm_content=c15t" target="_blank" rel="noopener noreferrer">
3
3
  <picture>
4
4
  <source media="(prefers-color-scheme: dark)" srcset="../../docs/assets/c15t-banner-readme-dark.svg" type="image/svg+xml">
5
5
  <img src="../../docs/assets/c15t-banner-readme-light.svg" alt="c15t Banner" type="image/svg+xml">
6
6
  </picture>
7
7
  </a>
8
- <br />
9
- <h1 align="center">c15t: Developer-First Consent Management Platform</h1>
10
8
  </p>
11
9
 
10
+ # c15t: Developer-First Consent Management Platform
11
+
12
12
  [![GitHub stars](https://img.shields.io/github/stars/c15t/c15t?style=flat-square)](https://github.com/c15t/c15t)
13
13
  [![CI](https://img.shields.io/github/actions/workflow/status/c15t/c15t/ci.yml?style=flat-square)](https://github.com/c15t/c15t/actions/workflows/ci.yml)
14
14
  [![License](https://img.shields.io/badge/license-Apache%202.0-blue.svg?style=flat-square)](https://github.com/c15t/c15t/blob/main/LICENSE.md)
@@ -18,7 +18,7 @@
18
18
  [![Last Commit](https://img.shields.io/github/last-commit/c15t/c15t?style=flat-square)](https://github.com/c15t/c15t/commits/main)
19
19
  [![Open Issues](https://img.shields.io/github/issues/c15t/c15t?style=flat-square)](https://github.com/c15t/c15t/issues)
20
20
 
21
- Developer-first CMP for JavaScript: cookie banner, consent manager, preferences centre. GDPR ready with minimal setup and rich customization
21
+ Headless cookie banner, consent manager & preference center for JavaScript / TypeScript. GDPR, CCPA, LGPD and IAB TCF compliant.
22
22
 
23
23
  ## Key Features
24
24
 
@@ -34,18 +34,18 @@ Developer-first CMP for JavaScript: cookie banner, consent manager, preferences
34
34
  - JavaScript or TypeScript project
35
35
  - Node.js 18.17.0 or later
36
36
  - npm, pnpm, or yarn package manager
37
- - A hosted [c15t instance](https://consent.io) (free sign-up) or [self-hosted deployment](https://c15t.com/docs/self-host/v2)
37
+ - A hosted [c15t instance](https://inth.com) (free sign-up) or [self-hosted deployment](https://c15t.com/docs/self-host/v2)
38
38
 
39
39
  ## Quick Start
40
40
 
41
41
  Easiest setup with @c15t/cli:
42
42
 
43
43
  ```bash
44
- # Generate schema and code
45
- pnpm dlx @c15t/cli generate
44
+ # Set up c15t in your project
45
+ pnpm dlx @c15t/cli setup
46
46
  # Alternatives:
47
- # npx @c15t/cli generate
48
- # bunx --bun @c15t/cli generate
47
+ # npx @c15t/cli setup
48
+ # bunx --bun @c15t/cli setup
49
49
  ```
50
50
 
51
51
  The CLI will:
@@ -88,24 +88,24 @@ For further information, guides, and examples visit the [reference documentation
88
88
 
89
89
  - Join our [Discord community](https://c15t.link/discord)
90
90
  - Open an issue on our [GitHub repository](https://github.com/c15t/c15t/issues)
91
- - Visit [consent.io](https://consent.io) and use the chat widget
92
- - Contact our support team via email [support@consent.io](mailto:support@consent.io)
91
+ - Visit [inth.com](https://inth.com) and use the chat widget
92
+ - Contact our support team via email [support@inth.com](mailto:support@inth.com)
93
93
 
94
94
  ## Contributing
95
95
 
96
- - We're open to all community contributions!
96
+ - We're open to all community contributions.
97
97
  - Read our [Contribution Guidelines](https://c15t.com/docs/oss/contributing)
98
98
  - Review our [Code of Conduct](https://c15t.com/docs/oss/code-of-conduct)
99
99
  - Fork the repository
100
100
  - Create a new branch for your feature
101
101
  - Submit a pull request
102
- - **All contributions, big or small, are welcome and appreciated!**
102
+ - **All contributions, big or small, are welcome and appreciated.**
103
103
 
104
104
  ## Security
105
105
 
106
106
  If you believe you have found a security vulnerability in c15t, we encourage you to **_responsibly disclose this and NOT open a public issue_**. We will investigate all legitimate reports.
107
107
 
108
- Our preference is that you make use of GitHub's private vulnerability reporting feature to disclose potential security vulnerabilities in our Open Source Software. To do this, please visit [https://github.com/c15t/c15t/security](https://github.com/c15t/c15t/security) and click the "Report a vulnerability" button.
108
+ Our preference is that you make use of GitHub's private vulnerability reporting feature to disclose potential security vulnerabilities in our open-source software. To do this, please visit [https://github.com/c15t/c15t/security](https://github.com/c15t/c15t/security) and click the "Report a vulnerability" button.
109
109
 
110
110
  ### Security Policy
111
111
 
@@ -120,4 +120,4 @@ Our preference is that you make use of GitHub's private vulnerability reporting
120
120
 
121
121
  ---
122
122
 
123
- **Built by [Inth](https://inth.com?utm_source=github&utm_medium=repopage_c15t)**
123
+ **Built by [Inth](https://inth.com?utm_source=npm&utm_medium=readme&utm_campaign=oss_readme&utm_content=c15t)**
package/dist/index.cjs CHANGED
@@ -607,7 +607,7 @@ const initial_state_initialState = {
607
607
  debug: false,
608
608
  config: {
609
609
  pkg: 'c15t',
610
- version: "2.0.0",
610
+ version: "2.0.4",
611
611
  mode: 'Unknown'
612
612
  },
613
613
  consents: consent_types_consentTypes.reduce((acc, consent)=>{
@@ -1796,19 +1796,11 @@ function filterConsentCategoriesByPolicy(categories, allowedPurposeIds) {
1796
1796
  if (!filtered.includes('necessary')) filtered.unshift('necessary');
1797
1797
  return filtered;
1798
1798
  }
1799
- function applyPolicyScopeForRuntimeGating(consents, allowedPurposeIds, scopeMode = 'permissive') {
1800
- if ('strict' === scopeMode) return consents;
1801
- if (!allowedPurposeIds || 0 === allowedPurposeIds.length || allowedPurposeIds.includes('*')) return consents;
1802
- const allowedCategories = new Set([
1803
- 'necessary',
1804
- ...allowedPurposeIds.filter(isConsentCategory)
1805
- ]);
1806
- const next = {
1807
- ...consents
1808
- };
1809
- for (const category of allConsentNames)if (!allowedCategories.has(category)) next[category] = true;
1810
- next.necessary = true;
1811
- return next;
1799
+ function shouldEnforcePolicyCategoryScope(allowedPurposeIds, scopeMode = 'permissive') {
1800
+ return 'strict' === scopeMode && Array.isArray(allowedPurposeIds) && allowedPurposeIds.length > 0 && !allowedPurposeIds.includes('*');
1801
+ }
1802
+ function applyPolicyScopeForRuntimeGating(consents, _allowedPurposeIds, _scopeMode = 'permissive') {
1803
+ return consents;
1812
1804
  }
1813
1805
  function getEffectivePolicy(initData) {
1814
1806
  return initData?.policy;
@@ -2811,9 +2803,11 @@ async function saveConsents({ manager, type, get, set, options, emitConsentChang
2811
2803
  if ('all' === type) {
2812
2804
  for (const consent of consentTypes)if (consentCategories.includes(consent.name)) newConsents[consent.name] = true;
2813
2805
  } else if ('necessary' === type) for (const consent of consentTypes)newConsents[consent.name] = true === consent.disabled ? consent.defaultValue : false;
2814
- const policyCategories = getEffectivePolicy(lastBannerFetchData)?.consent?.categories;
2815
- const effectiveConsents = applyPolicyPurposeAllowlist(newConsents, policyCategories);
2816
- const requestPreferences = stripDisallowedPreferenceKeys(effectiveConsents, policyCategories);
2806
+ const effectivePolicy = getEffectivePolicy(lastBannerFetchData);
2807
+ const policyCategories = effectivePolicy?.consent?.categories;
2808
+ const shouldEnforcePolicyScope = shouldEnforcePolicyCategoryScope(policyCategories, effectivePolicy?.consent?.scopeMode ?? null);
2809
+ const effectiveConsents = shouldEnforcePolicyScope ? applyPolicyPurposeAllowlist(newConsents, policyCategories) : newConsents;
2810
+ const requestPreferences = shouldEnforcePolicyScope ? stripDisallowedPreferenceKeys(effectiveConsents, policyCategories) : effectiveConsents;
2817
2811
  const didChange = haveConsentsChanged(previousConsents, effectiveConsents, consentTypes);
2818
2812
  const nextConsentCategoryLists = getConsentCategoryLists(effectiveConsents, consentCategories, consentTypes);
2819
2813
  const previousConsentCategoryLists = getConsentCategoryLists(previousConsents, consentCategories, consentTypes);
@@ -3014,8 +3008,8 @@ function buildStoreUpdate(data, config, effectiveIABEnabled, initSourceMetadata)
3014
3008
  update.selectedConsents = autoGrantedConsents;
3015
3009
  }
3016
3010
  const policyCategories = data.policy?.consent?.categories;
3017
- const hasPolicyCategoryAllowlist = Array.isArray(policyCategories) && policyCategories.length > 0 && !policyCategories.includes('*');
3018
- if (hasPolicyCategoryAllowlist) {
3011
+ const hasStrictPolicyCategoryAllowlist = shouldEnforcePolicyCategoryScope(policyCategories, data.policy?.consent?.scopeMode ?? null);
3012
+ if (hasStrictPolicyCategoryAllowlist) {
3019
3013
  const uniqueAllowedCategories = filterConsentCategoriesByPolicy(allConsentNames, policyCategories);
3020
3014
  update.consentCategories = uniqueAllowedCategories;
3021
3015
  update.consents = applyPolicyPurposeAllowlist(update.consents ?? get().consents, uniqueAllowedCategories);
@@ -3024,7 +3018,8 @@ function buildStoreUpdate(data, config, effectiveIABEnabled, initSourceMetadata)
3024
3018
  const preselectedCategories = data.policy?.consent?.preselectedCategories;
3025
3019
  const shouldApplyPreselectedCategories = null === consentInfo && !autoGrantedConsents && Array.isArray(preselectedCategories) && preselectedCategories.length > 0;
3026
3020
  if (shouldApplyPreselectedCategories) {
3027
- const preselectedScope = hasPolicyCategoryAllowlist ? filterConsentCategoriesByPolicy(allConsentNames, policyCategories) : allConsentNames;
3021
+ const displayedConsentNames = update.consentCategories ?? get().consentCategories;
3022
+ const preselectedScope = hasStrictPolicyCategoryAllowlist ? filterConsentCategoriesByPolicy(displayedConsentNames, policyCategories) : displayedConsentNames;
3028
3023
  const allowedPreselectedCategories = filterConsentCategoriesByPolicy(preselectedScope, preselectedCategories);
3029
3024
  const preselectedSet = new Set(allowedPreselectedCategories);
3030
3025
  const selectedConsentBaseline = update.selectedConsents ?? get().selectedConsents;
@@ -3737,8 +3732,14 @@ const createConsentManagerStore = (manager, options = {})=>{
3737
3732
  return resetState;
3738
3733
  });
3739
3734
  },
3740
- setConsentCategories: (types)=>set({
3741
- consentCategories: filterConsentCategoriesByPolicy(types, get().policyCategories)
3735
+ setConsentCategories: (types)=>set(()=>{
3736
+ const { policyCategories, policyScopeMode } = get();
3737
+ if (shouldEnforcePolicyCategoryScope(policyCategories, policyScopeMode)) return {
3738
+ consentCategories: filterConsentCategoriesByPolicy(types, policyCategories)
3739
+ };
3740
+ return {
3741
+ consentCategories: Array.from(new Set(types))
3742
+ };
3742
3743
  }),
3743
3744
  setCallback: (name, callback)=>{
3744
3745
  const currentState = get();
@@ -3810,13 +3811,15 @@ const createConsentManagerStore = (manager, options = {})=>{
3810
3811
  });
3811
3812
  },
3812
3813
  updateConsentCategories: (newCategories)=>{
3814
+ const { consentCategories: currentConsentCategories, policyCategories, policyScopeMode } = get();
3813
3815
  const allCategoriesSet = new Set([
3814
- ...get().consentCategories,
3816
+ ...currentConsentCategories,
3815
3817
  ...newCategories
3816
3818
  ]);
3817
- const allCategories = filterConsentCategoriesByPolicy(Array.from(allCategoriesSet), get().policyCategories);
3819
+ let consentCategories;
3820
+ consentCategories = shouldEnforcePolicyCategoryScope(policyCategories, policyScopeMode) ? filterConsentCategoriesByPolicy(Array.from(allCategoriesSet), policyCategories) : Array.from(allCategoriesSet);
3818
3821
  set({
3819
- consentCategories: allCategories
3822
+ consentCategories
3820
3823
  });
3821
3824
  },
3822
3825
  identifyUser: async (user)=>{
@@ -4070,7 +4073,7 @@ function getOrCreateConsentRuntime(options, pkgInfo) {
4070
4073
  config: {
4071
4074
  ...userConfig ?? {},
4072
4075
  pkg: pkgInfo?.pkg || 'c15t',
4073
- version: pkgInfo?.version || "2.0.0",
4076
+ version: pkgInfo?.version || "2.0.4",
4074
4077
  mode: normalizedMode,
4075
4078
  meta: {
4076
4079
  ...userConfig?.meta ?? {},
package/dist/index.js CHANGED
@@ -531,7 +531,7 @@ const initial_state_initialState = {
531
531
  debug: false,
532
532
  config: {
533
533
  pkg: 'c15t',
534
- version: "2.0.0",
534
+ version: "2.0.4",
535
535
  mode: 'Unknown'
536
536
  },
537
537
  consents: consent_types_consentTypes.reduce((acc, consent)=>{
@@ -1720,19 +1720,11 @@ function filterConsentCategoriesByPolicy(categories, allowedPurposeIds) {
1720
1720
  if (!filtered.includes('necessary')) filtered.unshift('necessary');
1721
1721
  return filtered;
1722
1722
  }
1723
- function applyPolicyScopeForRuntimeGating(consents, allowedPurposeIds, scopeMode = 'permissive') {
1724
- if ('strict' === scopeMode) return consents;
1725
- if (!allowedPurposeIds || 0 === allowedPurposeIds.length || allowedPurposeIds.includes('*')) return consents;
1726
- const allowedCategories = new Set([
1727
- 'necessary',
1728
- ...allowedPurposeIds.filter(isConsentCategory)
1729
- ]);
1730
- const next = {
1731
- ...consents
1732
- };
1733
- for (const category of allConsentNames)if (!allowedCategories.has(category)) next[category] = true;
1734
- next.necessary = true;
1735
- return next;
1723
+ function shouldEnforcePolicyCategoryScope(allowedPurposeIds, scopeMode = 'permissive') {
1724
+ return 'strict' === scopeMode && Array.isArray(allowedPurposeIds) && allowedPurposeIds.length > 0 && !allowedPurposeIds.includes('*');
1725
+ }
1726
+ function applyPolicyScopeForRuntimeGating(consents, _allowedPurposeIds, _scopeMode = 'permissive') {
1727
+ return consents;
1736
1728
  }
1737
1729
  function getEffectivePolicy(initData) {
1738
1730
  return initData?.policy;
@@ -2734,9 +2726,11 @@ async function saveConsents({ manager, type, get, set, options, emitConsentChang
2734
2726
  if ('all' === type) {
2735
2727
  for (const consent of consentTypes)if (consentCategories.includes(consent.name)) newConsents[consent.name] = true;
2736
2728
  } else if ('necessary' === type) for (const consent of consentTypes)newConsents[consent.name] = true === consent.disabled ? consent.defaultValue : false;
2737
- const policyCategories = getEffectivePolicy(lastBannerFetchData)?.consent?.categories;
2738
- const effectiveConsents = applyPolicyPurposeAllowlist(newConsents, policyCategories);
2739
- const requestPreferences = stripDisallowedPreferenceKeys(effectiveConsents, policyCategories);
2729
+ const effectivePolicy = getEffectivePolicy(lastBannerFetchData);
2730
+ const policyCategories = effectivePolicy?.consent?.categories;
2731
+ const shouldEnforcePolicyScope = shouldEnforcePolicyCategoryScope(policyCategories, effectivePolicy?.consent?.scopeMode ?? null);
2732
+ const effectiveConsents = shouldEnforcePolicyScope ? applyPolicyPurposeAllowlist(newConsents, policyCategories) : newConsents;
2733
+ const requestPreferences = shouldEnforcePolicyScope ? stripDisallowedPreferenceKeys(effectiveConsents, policyCategories) : effectiveConsents;
2740
2734
  const didChange = haveConsentsChanged(previousConsents, effectiveConsents, consentTypes);
2741
2735
  const nextConsentCategoryLists = getConsentCategoryLists(effectiveConsents, consentCategories, consentTypes);
2742
2736
  const previousConsentCategoryLists = getConsentCategoryLists(previousConsents, consentCategories, consentTypes);
@@ -2937,8 +2931,8 @@ function buildStoreUpdate(data, config, effectiveIABEnabled, initSourceMetadata)
2937
2931
  update.selectedConsents = autoGrantedConsents;
2938
2932
  }
2939
2933
  const policyCategories = data.policy?.consent?.categories;
2940
- const hasPolicyCategoryAllowlist = Array.isArray(policyCategories) && policyCategories.length > 0 && !policyCategories.includes('*');
2941
- if (hasPolicyCategoryAllowlist) {
2934
+ const hasStrictPolicyCategoryAllowlist = shouldEnforcePolicyCategoryScope(policyCategories, data.policy?.consent?.scopeMode ?? null);
2935
+ if (hasStrictPolicyCategoryAllowlist) {
2942
2936
  const uniqueAllowedCategories = filterConsentCategoriesByPolicy(allConsentNames, policyCategories);
2943
2937
  update.consentCategories = uniqueAllowedCategories;
2944
2938
  update.consents = applyPolicyPurposeAllowlist(update.consents ?? get().consents, uniqueAllowedCategories);
@@ -2947,7 +2941,8 @@ function buildStoreUpdate(data, config, effectiveIABEnabled, initSourceMetadata)
2947
2941
  const preselectedCategories = data.policy?.consent?.preselectedCategories;
2948
2942
  const shouldApplyPreselectedCategories = null === consentInfo && !autoGrantedConsents && Array.isArray(preselectedCategories) && preselectedCategories.length > 0;
2949
2943
  if (shouldApplyPreselectedCategories) {
2950
- const preselectedScope = hasPolicyCategoryAllowlist ? filterConsentCategoriesByPolicy(allConsentNames, policyCategories) : allConsentNames;
2944
+ const displayedConsentNames = update.consentCategories ?? get().consentCategories;
2945
+ const preselectedScope = hasStrictPolicyCategoryAllowlist ? filterConsentCategoriesByPolicy(displayedConsentNames, policyCategories) : displayedConsentNames;
2951
2946
  const allowedPreselectedCategories = filterConsentCategoriesByPolicy(preselectedScope, preselectedCategories);
2952
2947
  const preselectedSet = new Set(allowedPreselectedCategories);
2953
2948
  const selectedConsentBaseline = update.selectedConsents ?? get().selectedConsents;
@@ -3660,8 +3655,14 @@ const createConsentManagerStore = (manager, options = {})=>{
3660
3655
  return resetState;
3661
3656
  });
3662
3657
  },
3663
- setConsentCategories: (types)=>set({
3664
- consentCategories: filterConsentCategoriesByPolicy(types, get().policyCategories)
3658
+ setConsentCategories: (types)=>set(()=>{
3659
+ const { policyCategories, policyScopeMode } = get();
3660
+ if (shouldEnforcePolicyCategoryScope(policyCategories, policyScopeMode)) return {
3661
+ consentCategories: filterConsentCategoriesByPolicy(types, policyCategories)
3662
+ };
3663
+ return {
3664
+ consentCategories: Array.from(new Set(types))
3665
+ };
3665
3666
  }),
3666
3667
  setCallback: (name, callback)=>{
3667
3668
  const currentState = get();
@@ -3733,13 +3734,15 @@ const createConsentManagerStore = (manager, options = {})=>{
3733
3734
  });
3734
3735
  },
3735
3736
  updateConsentCategories: (newCategories)=>{
3737
+ const { consentCategories: currentConsentCategories, policyCategories, policyScopeMode } = get();
3736
3738
  const allCategoriesSet = new Set([
3737
- ...get().consentCategories,
3739
+ ...currentConsentCategories,
3738
3740
  ...newCategories
3739
3741
  ]);
3740
- const allCategories = filterConsentCategoriesByPolicy(Array.from(allCategoriesSet), get().policyCategories);
3742
+ let consentCategories;
3743
+ consentCategories = shouldEnforcePolicyCategoryScope(policyCategories, policyScopeMode) ? filterConsentCategoriesByPolicy(Array.from(allCategoriesSet), policyCategories) : Array.from(allCategoriesSet);
3741
3744
  set({
3742
- consentCategories: allCategories
3745
+ consentCategories
3743
3746
  });
3744
3747
  },
3745
3748
  identifyUser: async (user)=>{
@@ -3993,7 +3996,7 @@ function getOrCreateConsentRuntime(options, pkgInfo) {
3993
3996
  config: {
3994
3997
  ...userConfig ?? {},
3995
3998
  pkg: pkgInfo?.pkg || 'c15t',
3996
- version: pkgInfo?.version || "2.0.0",
3999
+ version: pkgInfo?.version || "2.0.4",
3997
4000
  mode: normalizedMode,
3998
4001
  meta: {
3999
4002
  ...userConfig?.meta ?? {},
@@ -5,7 +5,7 @@ import type { IABFallbackConfig } from './types';
5
5
  /**
6
6
  * Provides offline mode fallback for showConsentBanner API.
7
7
  * Simulates the behavior of OfflineClient when API requests fail.
8
- * In fallback mode, fetches GVL from gvl.consent.io when IAB is enabled.
8
+ * In fallback mode, fetches GVL from gvl.inth.app when IAB is enabled.
9
9
  * @internal
10
10
  */
11
11
  export declare function offlineFallbackForConsentBanner(options?: FetchOptions<InitResponse>, iabConfig?: IABFallbackConfig): Promise<ResponseContext<InitResponse>>;
@@ -59,7 +59,7 @@ export interface C15tInternalClientOptions {
59
59
  /**
60
60
  * IAB configuration for offline/fallback mode.
61
61
  * When the backend is unavailable and IAB is enabled,
62
- * the client will fetch GVL from gvl.consent.io with these settings.
62
+ * the client will fetch GVL from gvl.inth.app with these settings.
63
63
  */
64
64
  iabConfig?: IABFallbackConfig;
65
65
  }
@@ -6,7 +6,7 @@ import type { IABFallbackConfig } from '../hosted/types';
6
6
  export interface OfflineClientOptions {
7
7
  /**
8
8
  * IAB configuration for offline mode.
9
- * When IAB is enabled, the client will fetch GVL from gvl.consent.io.
9
+ * When IAB is enabled, the client will fetch GVL from gvl.inth.app.
10
10
  */
11
11
  iabConfig?: IABFallbackConfig;
12
12
  /**
@@ -137,7 +137,7 @@ export type IABManager = IABState & IABActions;
137
137
  * @internal
138
138
  */
139
139
  export interface CMPApiConfig {
140
- /** CMP ID registered with IAB Europe. Provided by the backend (consent.io) or client config. */
140
+ /** CMP ID registered with IAB Europe. Provided by the backend (inth.com) or client config. */
141
141
  cmpId?: number;
142
142
  /** CMP version (default: package version from ~/cmp-defaults) */
143
143
  cmpVersion?: number | string;
@@ -216,7 +216,7 @@ export interface IABConfig {
216
216
  * Enable IAB TCF 2.3 mode.
217
217
  *
218
218
  * When enabled, c15t will:
219
- * - Fetch GVL from gvl.consent.io
219
+ * - Fetch GVL from gvl.inth.app
220
220
  * - Initialize __tcfapi CMP API
221
221
  * - Generate TC Strings for IAB compliance
222
222
  *
@@ -236,7 +236,7 @@ export interface IABConfig {
236
236
  /**
237
237
  * CMP ID registered with IAB Europe.
238
238
  *
239
- * When using consent.io as the backend, this is automatically provided
239
+ * When using inth.com as the backend, this is automatically provided
240
240
  * via the `/init` endpoint — no client-side configuration needed.
241
241
  *
242
242
  * Only set this if you self-host and have your own CMP registration.
@@ -325,4 +325,4 @@ export declare const IAB_STORAGE_KEYS: {
325
325
  *
326
326
  * @internal
327
327
  */
328
- export declare const GVL_ENDPOINT = "https://gvl.consent.io";
328
+ export declare const GVL_ENDPOINT = "https://gvl.inth.app";
@@ -40,12 +40,15 @@ export declare function stripDisallowedPreferenceKeys<T extends Record<string, b
40
40
  */
41
41
  export declare function filterConsentCategoriesByPolicy(categories: AllConsentNames[], allowedPurposeIds?: string[] | null): AllConsentNames[];
42
42
  /**
43
- * Applies policy scope to runtime gating behavior.
44
- *
45
- * Out-of-policy categories are treated as permissive by c15t runtime and are
46
- * therefore granted for gating decisions (scripts/iframes load normally).
43
+ * Returns whether policy category scope should be enforced as a hard allowlist.
44
+ */
45
+ export declare function shouldEnforcePolicyCategoryScope(allowedPurposeIds?: string[] | null, scopeMode?: 'strict' | 'permissive' | null): boolean;
46
+ /**
47
+ * @deprecated No-op retained for API compatibility. Runtime gating respects
48
+ * the current consent state directly; policy scope is enforced at category
49
+ * discovery, render, and save time instead.
47
50
  */
48
- export declare function applyPolicyScopeForRuntimeGating(consents: ConsentState, allowedPurposeIds?: string[] | null, scopeMode?: 'strict' | 'permissive' | null): ConsentState;
51
+ export declare function applyPolicyScopeForRuntimeGating(consents: ConsentState, _allowedPurposeIds?: string[] | null, _scopeMode?: 'strict' | 'permissive' | null): ConsentState;
49
52
  /**
50
53
  * Gets the runtime policy returned by /init, if present.
51
54
  */
@@ -245,7 +245,7 @@ export interface SSRInitialData {
245
245
  /**
246
246
  * Global Vendor List data for IAB TCF mode.
247
247
  * - `undefined` means IAB is not active for the request or not enabled on server
248
- * - `null` means the user is in a non-IAB region (204 response from gvl.consent.io)
248
+ * - `null` means the user is in a non-IAB region (204 response from gvl.inth.app)
249
249
  * - `GlobalVendorList` contains the vendor list data from init response
250
250
  *
251
251
  * Note: When init returns 200 without gvl, client IAB settings are overridden to disabled.
@@ -469,7 +469,7 @@ export interface StoreOptions extends Partial<StoreConfig> {
469
469
  * Note: If the server returns 200 without GVL, client IAB settings are
470
470
  * automatically overridden to disabled (server takes precedence).
471
471
  *
472
- * In offline/fallback mode, GVL is fetched from gvl.consent.io.
472
+ * In offline/fallback mode, GVL is fetched from gvl.inth.app.
473
473
  *
474
474
  * This is an opt-in feature with zero bundle impact when not enabled.
475
475
  *
@@ -1 +1 @@
1
- export declare const version = "2.0.0";
1
+ export declare const version = "2.0.4";
@@ -0,0 +1,111 @@
1
+ ---
2
+ title: AI Agents
3
+ description: Integrate c15t with AI coding assistants using the docs bundled in each package and c15t agent skills. Give agents version-matched local docs for consent management, banners, script loading, callbacks, and integrations.
4
+ lastModified: 2026-03-24
5
+ ---
6
+ ## Bundled Docs
7
+
8
+ Every supported c15t package now ships docs inside the installed package itself.
9
+
10
+ ### Where to find them
11
+
12
+ * `node_modules/c15t/docs/README.md`
13
+ * `node_modules/@c15t/react/docs/README.md`
14
+ * `node_modules/@c15t/nextjs/docs/README.md`
15
+ * `node_modules/@c15t/backend/docs/README.md`
16
+
17
+ Start with the package `README.md`, then follow its linked pages for the relevant workflow.
18
+
19
+ These docs are version-matched to the exact c15t package version in your project, including generated reference content like prop and type tables.
20
+
21
+ ### Why use them
22
+
23
+ If your app uses multiple c15t packages, use the docs from each relevant installed package instead of relying on stale model knowledge.
24
+
25
+ ### Agent philosophy
26
+
27
+ When an AI tool is helping with c15t behavior, it should read the installed c15t docs first and use model knowledge second. That keeps consent flows, script gating, banner behavior, and integrations aligned with the exact version you have installed.
28
+
29
+ ### Customization ladder for agents
30
+
31
+ When an agent is working on consent UI, it should choose the lowest-power tool that solves the task:
32
+
33
+ 1. Start with the pre-built component and its existing props or provider options
34
+ 2. Use `theme` tokens for semantic visual changes
35
+ 3. Use `theme.slots` for targeted styling of specific parts
36
+ 4. Use CSS variables or className-level overrides only when integrating with external styles
37
+ 5. Use compound components only when the markup order must change
38
+ 6. Use `noStyle` only when c15t structure is still correct but all styling must be replaced
39
+ 7. Use headless hooks only when markup and behavior both need to be rebuilt
40
+
41
+ For common tasks:
42
+
43
+ * Banner footer background -> `theme.colors.surfaceHover`
44
+ * Banner card background -> `theme.colors.surface`
45
+ * Banner card/footer/title tweaks -> banner slots
46
+ * Stock action styling -> `theme.consentActions`
47
+ * Copy changes -> `ConsentManagerProvider.options.i18n`
48
+
49
+ If a token appears not to work, the agent should verify the token-to-component mapping before suggesting CSS overrides, `!important`, `noStyle`, or headless mode.
50
+
51
+ ***
52
+
53
+ ## Agent Skills
54
+
55
+ c15t publishes agent skills that give AI coding assistants deep knowledge of c15t's APIs, components, and configuration. Skills are reusable workflows and tool-specific guidance, not version-matched local docs.
56
+
57
+ ### Installation
58
+
59
+ Via the c15t CLI:
60
+
61
+ |Package manager|Command|
62
+ |:--|:--|
63
+ |npm|`npx @c15t/cli install-skills`|
64
+ |pnpm|`pnpm dlx @c15t/cli install-skills`|
65
+ |yarn|`yarn dlx @c15t/cli install-skills`|
66
+ |bun|`bunx @c15t/cli install-skills`|
67
+
68
+ Or directly:
69
+
70
+ |Package manager|Command|
71
+ |:--|:--|
72
+ |npm|`npx skills add c15t/skills`|
73
+ |pnpm|`pnpm dlx skills add c15t/skills`|
74
+ |yarn|`yarn dlx skills add c15t/skills`|
75
+ |bun|`bunx skills add c15t/skills`|
76
+
77
+ ### What skills provide
78
+
79
+ * **Styling customization** — strict escalation guidance across props, tokens, slots, CSS variables, compound components, `noStyle`, and headless
80
+ * **Internationalization** — translation setup, locale routing integration
81
+ * **Script management** — configuring third-party scripts with consent categories
82
+ * **Component setup** — ConsentBanner, ConsentDialog, provider configuration
83
+
84
+ ### Supported tools
85
+
86
+ * Claude Code
87
+ * Cursor
88
+ * GitHub Copilot (via `.github/skills`)
89
+ * Any agent that supports the skills format
90
+
91
+ ***
92
+
93
+ ## When to use which
94
+
95
+ Use bundled docs when:
96
+
97
+ * Your agent can read files in the local project
98
+ * You want version-matched docs from the installed c15t packages
99
+ * You want a package-local README that tells the agent which detailed docs to read first
100
+ * You want concrete guidance for consent management, cookie banners, consent dialogs, preference centers, script loading, callbacks, and integrations
101
+
102
+ Use agent skills when:
103
+
104
+ * Your tool supports the skills ecosystem
105
+ * You want reusable workflows and tool-specific guidance that can point back to the installed package README files
106
+
107
+ Use both when:
108
+
109
+ * Your tool supports both local file context and skills
110
+ * You want local package docs plus reusable setup and configuration help
111
+ * You want the bundled package docs as the source of truth plus a reusable decision tree for customization
@@ -15,12 +15,12 @@ When your site participates in the IAB ecosystem (ad exchanges, SSPs, DSPs, DMPs
15
15
 
16
16
  ## CMP Registration
17
17
 
18
- [inth.com](https://inth.com) is pending validation as an IAB Europe-registered CMP for c15t. Once approved, when you use inth.com as your backend, the correct CMP ID will be automatically provided to your client via the `/init` endpoint — no client-side configuration needed.
18
+ [Inth](https://inth.com), c15t's hosted platform, is IAB TCF certified. When you use Inth as your backend with c15t's prebuilt IAB UI, the correct CMP ID is automatically provided to your client via the `/init` endpoint — no client-side configuration needed.
19
19
 
20
- If you self-host the c15t backend and have your own CMP registration with IAB Europe, you can configure your CMP ID on the backend via `advanced.iab.cmpId` or on the client via the `iab.cmpId` option. A valid (non-zero) CMP ID is required for IAB TCF compliance.
20
+ If you self-host the c15t backend or want to operate as your own CMP, register your own CMP with IAB Europe and configure your CMP ID on the backend via `advanced.iab.cmpId` or on the client via the `iab.cmpId` option. Registering your own CMP may also involve IAB Europe fees, so check IAB Europe's current CMP registration terms and pricing before choosing this route. A valid (non-zero) CMP ID is required for IAB TCF compliance.
21
21
 
22
22
  > ℹ️ **Info:**
23
- > If you heavily customize or build your own IAB banner or dialog (rather than using the default IABConsentBanner and IABConsentDialog components), you cannot use inth.com's CMP ID. You must register your own CMP with IAB Europe and use your own CMP ID.
23
+ > If you heavily customize or build your own IAB banner or dialog instead of using the default IABConsentBanner and IABConsentDialog components, you cannot use Inth's CMP ID. You must register your own CMP with IAB Europe and use your own CMP ID.
24
24
 
25
25
  ## How c15t Implements TCF
26
26
 
@@ -95,7 +95,7 @@ Configure IAB mode with `iab({ ... })` from `@c15t/iab`. The factory enables the
95
95
 
96
96
  |Option|Type|Description|
97
97
  |--|--|--|
98
- |`cmpId`|`number`|CMP ID registered with IAB Europe. Automatically provided by the backend when using inth.com. Only set this if you have your own CMP registration.|
98
+ |`cmpId`|`number`|CMP ID registered with IAB Europe. Automatically provided by the backend when using Inth with the prebuilt IAB UI. Only set this if you have your own CMP registration.|
99
99
  |`vendors`|`number[]`|IAB vendor IDs that your site works with|
100
100
  |`customVendors`|`NonIABVendor[]`|Custom vendors not in the IAB registry|
101
101
 
package/package.json CHANGED
@@ -1,26 +1,36 @@
1
1
  {
2
2
  "name": "c15t",
3
- "version": "2.0.0",
4
- "description": "Developer-first CMP for JavaScript: cookie banner, consent manager, preferences centre. GDPR ready with minimal setup and rich customization",
3
+ "version": "2.0.4",
4
+ "description": "Headless cookie banner, consent manager & preference center for JavaScript / TypeScript. GDPR, CCPA, LGPD and IAB TCF compliant.",
5
5
  "keywords": [
6
- "nextjs",
7
6
  "consent",
8
7
  "privacy",
9
8
  "gdpr",
10
9
  "ccpa",
11
10
  "lgpd",
12
- "server-side rendering",
13
- "react",
14
- "typescript",
15
- "cookie-banner",
16
- "consent-management-platform",
11
+ "tcf",
12
+ "iab",
17
13
  "cmp",
14
+ "consent-management",
15
+ "consent-management-platform",
16
+ "cookie-banner",
17
+ "cookie-consent",
18
+ "cookies",
18
19
  "consent-banner",
19
- "user-consent",
20
- "privacy-compliance",
21
- "web-privacy"
20
+ "consent-manager",
21
+ "preference-center",
22
+ "headless",
23
+ "javascript",
24
+ "typescript",
25
+ "ssr",
26
+ "server-side-rendering",
27
+ "tracking-consent",
28
+ "eu-cookie-law"
22
29
  ],
23
30
  "homepage": "https://c15t.com/docs/frameworks/javascript/quickstart",
31
+ "bugs": {
32
+ "url": "https://github.com/c15t/c15t/issues"
33
+ },
24
34
  "repository": {
25
35
  "type": "git",
26
36
  "url": "https://github.com/c15t/c15t.git",
@@ -51,10 +61,10 @@
51
61
  "build:agent-docs": "bun ../../scripts/agent-docs/generate-package-docs.ts c15t",
52
62
  "check-types": "bun prebuild && tsc --noEmit",
53
63
  "check-types:test": "tsc -p tsconfig.test.json",
54
- "dev": "bun prebuild && rslib build && bun ../../scripts/normalize-dist-types.mjs",
64
+ "dev": "sh -c 'bun prebuild && rslib build --no-dts --no-clean && rslib build --watch --no-dts --no-clean'",
55
65
  "fmt": "bun biome format --write . && bun biome check --formatter-enabled=false --linter-enabled=false --write",
56
66
  "lint": "bun biome lint ./src",
57
- "prepack": "cd ../.. && bunx turbo run build --filter=c15t",
67
+ "prepack": "bun ../../scripts/verify-package-artifacts.ts",
58
68
  "test": "bun prebuild && vitest run",
59
69
  "test:watch": "bun prebuild && vitest"
60
70
  },
@@ -64,7 +74,7 @@
64
74
  "not op_mini all"
65
75
  ],
66
76
  "dependencies": {
67
- "@c15t/schema": "2.0.0",
77
+ "@c15t/schema": "2.0.1",
68
78
  "@c15t/translations": "2.0.0",
69
79
  "zustand": "5.0.12"
70
80
  },
package/readme.json CHANGED
@@ -12,7 +12,7 @@
12
12
  "JavaScript or TypeScript project",
13
13
  "Node.js 18.17.0 or later",
14
14
  "npm, pnpm, or yarn package manager",
15
- "A hosted [c15t instance](https://consent.io) (free sign-up) or [self-hosted deployment](https://c15t.com/docs/self-host/v2)"
15
+ "A hosted [c15t instance](https://inth.com) (free sign-up) or [self-hosted deployment](https://c15t.com/docs/self-host/v2)"
16
16
  ],
17
17
  "manualInstallation": [
18
18
  "",