@cullit/core 2.0.7 → 2.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.ts CHANGED
@@ -71,7 +71,7 @@ interface PipelineResult {
71
71
  duration: number;
72
72
  }
73
73
 
74
- declare const VERSION = "2.0.7";
74
+ declare const VERSION = "2.1.0";
75
75
  declare const DEFAULT_CATEGORIES: string[];
76
76
  declare const DEFAULT_MODELS: Record<string, string>;
77
77
  declare const AI_PROVIDERS: readonly ["anthropic", "openai", "gemini", "ollama", "none"];
@@ -82,8 +82,8 @@ declare const CHANGE_CATEGORIES: readonly ["features", "fixes", "breaking", "imp
82
82
  declare const AUDIENCES: readonly ["developer", "end-user", "executive"];
83
83
  declare const TONES: readonly ["professional", "casual", "terse", "edgy", "hype", "snarky"];
84
84
  declare const SOURCE_TYPES: readonly ["local", "jira", "linear", "gitlab", "bitbucket", "multi-repo"];
85
- declare const TIERS: readonly ["free", "basic", "pro", "team", "enterprise"];
86
- declare const PAID_TIERS: readonly ["basic", "pro", "team", "enterprise"];
85
+ declare const TIERS: readonly ["free", "pro", "team", "enterprise"];
86
+ declare const PAID_TIERS: readonly ["pro", "team", "enterprise"];
87
87
  declare const TEAM_TIERS: readonly ["team", "enterprise"];
88
88
  declare const TEAM_PLANS: readonly ["team-5", "team-10", "team-25"];
89
89
  type TeamPlan = (typeof TEAM_PLANS)[number];
@@ -219,13 +219,16 @@ declare function analyzeReleaseReadiness(cwd?: string): ReleaseAdvisory;
219
219
  /**
220
220
  * Cullit License Gating
221
221
  *
222
- * Free tier (no key): provider=none, publish to stdout/file only
223
- * Pro tier (with key): all providers, all publishers, all enrichments
222
+ * Free tier (no key): 3 AI gens/month, all providers (BYOK), publish to stdout/file only
223
+ * Pro tier (with key): 500 gens/month, all providers, all publishers, enrichments, audience/tone
224
+ * Team tier: 2000 gens/month, team management, advanced publishers
225
+ * Team 25: 5000 gens/month, 500 projects, branded widget, project templates, audit logs
226
+ * Enterprise tier: unlimited everything
224
227
  *
225
228
  * validateLicense() performs async remote validation with caching.
226
229
  * resolveLicense() remains sync for quick format-only checks (display).
227
230
  */
228
- type LicenseTier = 'free' | 'basic' | 'pro' | 'team' | 'enterprise';
231
+ type LicenseTier = 'free' | 'pro' | 'team' | 'enterprise';
229
232
  interface LicenseStatus {
230
233
  tier: LicenseTier;
231
234
  valid: boolean;
@@ -244,6 +247,7 @@ declare function resolveLicense(): LicenseStatus;
244
247
  declare function validateLicense(): Promise<LicenseStatus>;
245
248
  /**
246
249
  * Check whether the current license allows the requested provider.
250
+ * All tiers now allow AI providers (BYOK) — enforcement is via generation limits.
247
251
  */
248
252
  declare function isProviderAllowed(provider: string, license: LicenseStatus): boolean;
249
253
  /**
@@ -275,11 +279,22 @@ interface UsageLimits {
275
279
  * Get usage limits for a license tier.
276
280
  */
277
281
  declare function getTierLimits(tier: string): UsageLimits;
282
+ /**
283
+ * Get usage limits for a specific plan, falling back to tier defaults.
284
+ * Use when the plan is known (API context); use getTierLimits when only tier is known (CLI).
285
+ */
286
+ declare function getPlanLimits(plan: string, tier: string): UsageLimits;
278
287
  type TeamFeature = 'drafts' | 'approvals' | 'shared_history' | 'project_templates' | 'hosted_changelog' | 'branded_widget' | 'team_publishers' | 'org_settings' | 'audit_logs' | 'sso';
279
288
  /**
280
289
  * Check whether a license tier grants access to a Team/Enterprise feature.
281
290
  */
282
291
  declare function isFeatureAllowed(feature: TeamFeature, tier: string, valid?: boolean): boolean;
292
+ /**
293
+ * Check whether a specific plan grants access to a feature.
294
+ * Use when the plan is known (API context); enterprise always passes.
295
+ * Falls back to tier-level check for features without plan restrictions.
296
+ */
297
+ declare function isPlanFeatureAllowed(feature: TeamFeature, plan: string, tier: string, valid?: boolean): boolean;
283
298
  /**
284
299
  * Build a gating summary for a tier — which features are unlocked.
285
300
  */
@@ -396,4 +411,4 @@ declare function runPipeline(from: string, to: string, config: CullConfig, optio
396
411
  templateProfile?: string;
397
412
  }): Promise<PipelineResult>;
398
413
 
399
- export { AI_PROVIDERS, AUDIENCES, CHANGE_CATEGORIES, type ChangeCategory, type ChangeEntry, type Collector, type CollectorFactory, CoreErrorCode, type CoreErrorCodeValue, CullitError, DEFAULT_CATEGORIES, DEFAULT_MODELS, ENRICHMENT_TYPES, type EnrichedContext, type EnrichedTicket, type Enricher, type EnricherFactory, FilePublisher, type Generator, type GeneratorFactory, GitCollector, type GitCommit, type GitDiff, type LicenseStatus, type LicenseTier, type LogLevel, type Logger, MultiRepoCollector, OUTPUT_FORMATS, PAID_TIERS, PUBLISHER_TYPES, type PipelineResult, type Publisher, type PublisherFactory, type RateLimitResult, type RateLimiter, type RateLimiterOptions, type ReleaseAdvisory, type ReleaseNotes, SOURCE_TYPES, type SemverBump, StdoutPublisher, TEAM_PLANS, TEAM_PLAN_SEATS, TEAM_TIERS, TIERS, TONES, type TeamFeature, type TeamPlan, TemplateGenerator, type UsageLimits, VERSION, analyzeReleaseReadiness, createLogger, createRateLimiter, escapeHtml, fetchWithTimeout, formatNotes, getCollector, getEnricher, getFeatureGating, getFormatter, getGenerator, getLatestTag, getPublisher, getRecentTags, getTierLimits, hasCollector, hasEnricher, hasGenerator, hasPublisher, isAudienceToneAllowed, isEnrichmentAllowed, isFeatureAllowed, isProviderAllowed, isPublisherAllowed, listCollectors, listEnrichers, listFormatters, listGenerators, listPublishers, registerCollector, registerEnricher, registerFormatter, registerGenerator, registerPublisher, reportUsage, resolveLicense, runPipeline, upgradeMessage, validateLicense };
414
+ export { AI_PROVIDERS, AUDIENCES, CHANGE_CATEGORIES, type ChangeCategory, type ChangeEntry, type Collector, type CollectorFactory, CoreErrorCode, type CoreErrorCodeValue, CullitError, DEFAULT_CATEGORIES, DEFAULT_MODELS, ENRICHMENT_TYPES, type EnrichedContext, type EnrichedTicket, type Enricher, type EnricherFactory, FilePublisher, type Generator, type GeneratorFactory, GitCollector, type GitCommit, type GitDiff, type LicenseStatus, type LicenseTier, type LogLevel, type Logger, MultiRepoCollector, OUTPUT_FORMATS, PAID_TIERS, PUBLISHER_TYPES, type PipelineResult, type Publisher, type PublisherFactory, type RateLimitResult, type RateLimiter, type RateLimiterOptions, type ReleaseAdvisory, type ReleaseNotes, SOURCE_TYPES, type SemverBump, StdoutPublisher, TEAM_PLANS, TEAM_PLAN_SEATS, TEAM_TIERS, TIERS, TONES, type TeamFeature, type TeamPlan, TemplateGenerator, type UsageLimits, VERSION, analyzeReleaseReadiness, createLogger, createRateLimiter, escapeHtml, fetchWithTimeout, formatNotes, getCollector, getEnricher, getFeatureGating, getFormatter, getGenerator, getLatestTag, getPlanLimits, getPublisher, getRecentTags, getTierLimits, hasCollector, hasEnricher, hasGenerator, hasPublisher, isAudienceToneAllowed, isEnrichmentAllowed, isFeatureAllowed, isPlanFeatureAllowed, isProviderAllowed, isPublisherAllowed, listCollectors, listEnrichers, listFormatters, listGenerators, listPublishers, registerCollector, registerEnricher, registerFormatter, registerGenerator, registerPublisher, reportUsage, resolveLicense, runPipeline, upgradeMessage, validateLicense };
package/dist/index.js CHANGED
@@ -1,5 +1,5 @@
1
1
  // src/constants.ts
2
- var VERSION = "2.0.7";
2
+ var VERSION = "2.1.0";
3
3
  var DEFAULT_CATEGORIES = ["features", "fixes", "breaking", "improvements", "chores"];
4
4
  var DEFAULT_MODELS = {
5
5
  anthropic: "claude-sonnet-4-20250514",
@@ -15,8 +15,8 @@ var CHANGE_CATEGORIES = ["features", "fixes", "breaking", "improvements", "chore
15
15
  var AUDIENCES = ["developer", "end-user", "executive"];
16
16
  var TONES = ["professional", "casual", "terse", "edgy", "hype", "snarky"];
17
17
  var SOURCE_TYPES = ["local", "jira", "linear", "gitlab", "bitbucket", "multi-repo"];
18
- var TIERS = ["free", "basic", "pro", "team", "enterprise"];
19
- var PAID_TIERS = ["basic", "pro", "team", "enterprise"];
18
+ var TIERS = ["free", "pro", "team", "enterprise"];
19
+ var PAID_TIERS = ["pro", "team", "enterprise"];
20
20
  var TEAM_TIERS = ["team", "enterprise"];
21
21
  var TEAM_PLANS = ["team-5", "team-10", "team-25"];
22
22
  var TEAM_PLAN_SEATS = {
@@ -757,7 +757,6 @@ async function fetchWithTimeout(url, init, timeoutMs = DEFAULT_TIMEOUT) {
757
757
  }
758
758
 
759
759
  // src/gate.ts
760
- var FREE_PROVIDERS = /* @__PURE__ */ new Set(["none"]);
761
760
  var FREE_PUBLISHERS = /* @__PURE__ */ new Set(["stdout", "file"]);
762
761
  var TEAM_ONLY_PUBLISHERS = /* @__PURE__ */ new Set(["confluence", "notion", "teams"]);
763
762
  var LICENSE_CACHE_TTL = 24 * 60 * 60 * 1e3;
@@ -809,7 +808,7 @@ async function validateLicense() {
809
808
  if (res.ok) {
810
809
  const data = await res.json();
811
810
  const status2 = {
812
- tier: data.tier === "team" || data.tier === "enterprise" ? data.tier : data.tier === "pro" ? "pro" : data.tier === "basic" ? "basic" : "free",
811
+ tier: data.tier === "team" || data.tier === "enterprise" ? data.tier : data.tier === "pro" ? "pro" : "free",
813
812
  valid: data.valid !== false,
814
813
  message: data.message
815
814
  };
@@ -831,8 +830,8 @@ async function validateLicense() {
831
830
  }
832
831
  }
833
832
  function isProviderAllowed(provider, license) {
834
- if (license.tier !== "free" && license.valid) return true;
835
- return FREE_PROVIDERS.has(provider);
833
+ if (!license.valid) return provider === "none";
834
+ return true;
836
835
  }
837
836
  function isPublisherAllowed(publisherType, license) {
838
837
  if (TEAM_ONLY_PUBLISHERS.has(publisherType)) {
@@ -848,38 +847,59 @@ function isAudienceToneAllowed(license) {
848
847
  return (license.tier === "pro" || license.tier === "team" || license.tier === "enterprise") && license.valid;
849
848
  }
850
849
  function upgradeMessage(feature, minTier) {
851
- const tierLabel = minTier === "team" ? "a Team plan or above" : minTier === "pro" ? "a Pro plan or above" : minTier === "basic" ? "a Basic plan or above" : minTier === "enterprise" ? "an Enterprise plan" : "a paid Cullit plan";
850
+ const tierLabel = minTier === "team" ? "a Team plan or above" : minTier === "pro" ? "a Pro plan or above" : minTier === "enterprise" ? "an Enterprise plan" : "a paid Cullit plan";
852
851
  return `\u{1F512} ${feature} requires ${tierLabel}.
853
852
  Upgrade at https://cullit.io/pricing
854
853
  Then set CULLIT_API_KEY in your environment.`;
855
854
  }
856
855
  var TIER_LIMITS = {
857
- free: { generationsPerMonth: 5, maxProjects: 3 },
858
- basic: { generationsPerMonth: 50, maxProjects: 10 },
856
+ free: { generationsPerMonth: 3, maxProjects: 3 },
859
857
  pro: { generationsPerMonth: 500, maxProjects: 100 },
860
858
  team: { generationsPerMonth: 2e3, maxProjects: 250 },
861
859
  enterprise: { generationsPerMonth: Infinity, maxProjects: Infinity }
862
860
  };
861
+ var PLAN_LIMITS = {
862
+ "team-25": { generationsPerMonth: 5e3, maxProjects: 500 }
863
+ };
863
864
  function getTierLimits(tier) {
864
865
  return TIER_LIMITS[tier] || TIER_LIMITS.free;
865
866
  }
867
+ function getPlanLimits(plan, tier) {
868
+ return PLAN_LIMITS[plan] || TIER_LIMITS[tier] || TIER_LIMITS.free;
869
+ }
866
870
  var FEATURE_TIERS = {
867
871
  drafts: /* @__PURE__ */ new Set(["team", "enterprise"]),
868
872
  approvals: /* @__PURE__ */ new Set(["team", "enterprise"]),
869
873
  shared_history: /* @__PURE__ */ new Set(["team", "enterprise"]),
870
- project_templates: /* @__PURE__ */ new Set(["team", "enterprise"]),
874
+ project_templates: /* @__PURE__ */ new Set(["enterprise"]),
875
+ // plan-gated: team-25 via PLAN_FEATURES
871
876
  hosted_changelog: /* @__PURE__ */ new Set(["pro", "team", "enterprise"]),
872
- branded_widget: /* @__PURE__ */ new Set(["team", "enterprise"]),
877
+ branded_widget: /* @__PURE__ */ new Set(["enterprise"]),
878
+ // plan-gated: team-25 via PLAN_FEATURES
873
879
  team_publishers: /* @__PURE__ */ new Set(["team", "enterprise"]),
874
880
  org_settings: /* @__PURE__ */ new Set(["team", "enterprise"]),
875
881
  audit_logs: /* @__PURE__ */ new Set(["enterprise"]),
882
+ // plan-gated: team-25 via PLAN_FEATURES
876
883
  sso: /* @__PURE__ */ new Set(["enterprise"])
877
884
  };
885
+ var PLAN_FEATURES = {
886
+ branded_widget: /* @__PURE__ */ new Set(["team-25"]),
887
+ project_templates: /* @__PURE__ */ new Set(["team-25"]),
888
+ audit_logs: /* @__PURE__ */ new Set(["team-25"])
889
+ };
878
890
  function isFeatureAllowed(feature, tier, valid = true) {
879
891
  if (!valid) return false;
880
892
  const allowed = FEATURE_TIERS[feature];
881
893
  return allowed ? allowed.has(tier) : false;
882
894
  }
895
+ function isPlanFeatureAllowed(feature, plan, tier, valid = true) {
896
+ if (!valid) return false;
897
+ if (tier === "enterprise") return true;
898
+ const planSet = PLAN_FEATURES[feature];
899
+ if (planSet) return planSet.has(plan);
900
+ const tierSet = FEATURE_TIERS[feature];
901
+ return tierSet ? tierSet.has(tier) : false;
902
+ }
883
903
  function getFeatureGating(tier) {
884
904
  const result = {};
885
905
  for (const feature of Object.keys(FEATURE_TIERS)) {
@@ -1182,6 +1202,11 @@ async function runPipeline(from, to, config, options = {}) {
1182
1202
  }
1183
1203
  }
1184
1204
  const context = { diff, tickets };
1205
+ const hasCustomAudience = config.ai.audience && config.ai.audience !== "developer";
1206
+ const hasCustomTone = config.ai.tone && config.ai.tone !== "professional";
1207
+ if ((hasCustomAudience || hasCustomTone) && !isAudienceToneAllowed(license)) {
1208
+ throw new CullitError(CoreErrorCode.LICENSE_TIER_INSUFFICIENT, upgradeMessage("Audience and tone control", "pro"));
1209
+ }
1185
1210
  const providerNames = {
1186
1211
  anthropic: "Claude",
1187
1212
  openai: "OpenAI",
@@ -1306,6 +1331,7 @@ export {
1306
1331
  getFormatter,
1307
1332
  getGenerator,
1308
1333
  getLatestTag,
1334
+ getPlanLimits,
1309
1335
  getPublisher,
1310
1336
  getRecentTags,
1311
1337
  getTierLimits,
@@ -1316,6 +1342,7 @@ export {
1316
1342
  isAudienceToneAllowed,
1317
1343
  isEnrichmentAllowed,
1318
1344
  isFeatureAllowed,
1345
+ isPlanFeatureAllowed,
1319
1346
  isProviderAllowed,
1320
1347
  isPublisherAllowed,
1321
1348
  listCollectors,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cullit/core",
3
- "version": "2.0.7",
3
+ "version": "2.1.0",
4
4
  "type": "module",
5
5
  "description": "Core engine for Cullit — AI-powered release note generation.",
6
6
  "license": "SEE LICENSE IN LICENSE",
@@ -38,7 +38,7 @@
38
38
  "access": "public"
39
39
  },
40
40
  "dependencies": {
41
- "@cullit/config": "2.0.7"
41
+ "@cullit/config": "2.1.0"
42
42
  },
43
43
  "scripts": {
44
44
  "build": "tsup src/index.ts --format esm --dts --clean",