@cullit/core 2.3.1 → 2.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.
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.3.1";
74
+ declare const VERSION = "2.4.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"];
@@ -85,9 +85,9 @@ declare const SOURCE_TYPES: readonly ["local", "jira", "linear", "gitlab", "bitb
85
85
  declare const TIERS: readonly ["free", "pro", "team", "enterprise"];
86
86
  declare const PAID_TIERS: readonly ["pro", "team", "enterprise"];
87
87
  declare const TEAM_TIERS: readonly ["team", "enterprise"];
88
- declare const TEAM_PLANS: readonly ["team-5", "team-10", "team-25"];
89
- type TeamPlan = (typeof TEAM_PLANS)[number];
90
- declare const TEAM_PLAN_SEATS: Record<TeamPlan, number>;
88
+ declare const TEAM_SEAT_PRICE = 8;
89
+ declare const TEAM_MIN_SEATS = 5;
90
+ declare const TEAM_ANNUAL_DISCOUNT = 0.15;
91
91
 
92
92
  type LogLevel = 'quiet' | 'normal' | 'verbose';
93
93
  interface Logger {
@@ -221,8 +221,7 @@ declare function analyzeReleaseReadiness(cwd?: string): ReleaseAdvisory;
221
221
  *
222
222
  * Free tier (no key): 3 AI gens/month, all providers (BYOK), publish to stdout/file only
223
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
224
+ * Team tier (5+ seats): all team features, dynamic limits based on seat count
226
225
  * Enterprise tier: unlimited everything
227
226
  *
228
227
  * validateLicense() performs async remote validation with caching.
@@ -280,24 +279,22 @@ interface UsageLimits {
280
279
  */
281
280
  declare function getTierLimits(tier: string): UsageLimits;
282
281
  /**
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).
282
+ * Get usage limits scaled by seat count for team plans.
283
+ * Seats scale limits: 100 gens/seat, 5 projects/seat (with tier base as minimum).
285
284
  */
286
- declare function getPlanLimits(plan: string, tier: string): UsageLimits;
285
+ declare function getTeamLimits(seats: number): UsageLimits;
287
286
  type TeamFeature = 'drafts' | 'approvals' | 'shared_history' | 'project_templates' | 'hosted_changelog' | 'branded_widget' | 'team_publishers' | 'org_settings' | 'audit_logs' | 'team_analytics' | 'sso';
288
287
  /**
289
288
  * Check whether a license tier grants access to a Team/Enterprise feature.
290
289
  */
291
290
  declare function isFeatureAllowed(feature: TeamFeature, tier: string, valid?: boolean): boolean;
292
291
  /**
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.
292
+ * Check whether a plan/tier grants access to a feature.
293
+ * Enterprise gets everything. Team gets all team features.
296
294
  */
297
295
  declare function isPlanFeatureAllowed(feature: TeamFeature, plan: string, tier: string, valid?: boolean): boolean;
298
296
  /**
299
297
  * Build a gating summary for a tier — which features are unlocked.
300
- * When plan is provided, uses plan-aware feature checks.
301
298
  */
302
299
  declare function getFeatureGating(tier: string, plan?: string): Record<TeamFeature, boolean>;
303
300
  /**
@@ -412,4 +409,4 @@ declare function runPipeline(from: string, to: string, config: CullConfig, optio
412
409
  templateProfile?: string;
413
410
  }): Promise<PipelineResult>;
414
411
 
415
- 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 };
412
+ 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_ANNUAL_DISCOUNT, TEAM_MIN_SEATS, TEAM_SEAT_PRICE, TEAM_TIERS, TIERS, TONES, type TeamFeature, TemplateGenerator, type UsageLimits, VERSION, analyzeReleaseReadiness, createLogger, createRateLimiter, escapeHtml, fetchWithTimeout, formatNotes, getCollector, getEnricher, getFeatureGating, getFormatter, getGenerator, getLatestTag, getPublisher, getRecentTags, getTeamLimits, 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.3.1";
2
+ var VERSION = "2.4.0";
3
3
  var DEFAULT_CATEGORIES = ["features", "fixes", "breaking", "improvements", "chores"];
4
4
  var DEFAULT_MODELS = {
5
5
  anthropic: "claude-sonnet-4-20250514",
@@ -18,12 +18,9 @@ var SOURCE_TYPES = ["local", "jira", "linear", "gitlab", "bitbucket", "multi-rep
18
18
  var TIERS = ["free", "pro", "team", "enterprise"];
19
19
  var PAID_TIERS = ["pro", "team", "enterprise"];
20
20
  var TEAM_TIERS = ["team", "enterprise"];
21
- var TEAM_PLANS = ["team-5", "team-10", "team-25"];
22
- var TEAM_PLAN_SEATS = {
23
- "team-5": 5,
24
- "team-10": 10,
25
- "team-25": 25
26
- };
21
+ var TEAM_SEAT_PRICE = 8;
22
+ var TEAM_MIN_SEATS = 5;
23
+ var TEAM_ANNUAL_DISCOUNT = 0.15;
27
24
 
28
25
  // src/logger.ts
29
26
  function createLogger(level = "normal") {
@@ -103,7 +100,7 @@ var GitCollector = class {
103
100
  };
104
101
  }
105
102
  getLog(from, to) {
106
- const format = "%H|%h|%an|%aI|%s|%b";
103
+ const format = "%H%x1e%h%x1e%an%x1e%aI%x1e%s%x1e%b";
107
104
  const separator = "---CULLIT_COMMIT---";
108
105
  try {
109
106
  return execFileSync(
@@ -126,9 +123,9 @@ var GitCollector = class {
126
123
  const separator = "---CULLIT_COMMIT---";
127
124
  const entries = log.split(separator).filter((e) => e.trim());
128
125
  return entries.map((entry) => {
129
- const parts = entry.trim().split("|");
126
+ const parts = entry.trim().split("");
130
127
  const [hash, shortHash, author, date, message, ...bodyParts] = parts;
131
- const body = bodyParts.join("|").trim() || void 0;
128
+ const body = bodyParts.join("").trim() || void 0;
132
129
  const fullMessage = body ? `${message}
133
130
  ${body}` : message;
134
131
  return {
@@ -207,7 +204,7 @@ function getLatestTag(cwd = process.cwd()) {
207
204
  function getCommitsSince(from, to, cwd = process.cwd()) {
208
205
  validateRef(from);
209
206
  validateRef(to);
210
- const format = "%H|%h|%an|%aI|%s";
207
+ const format = "%H%x1e%h%x1e%an%x1e%aI%x1e%s";
211
208
  const separator = "---CULLIT_COMMIT---";
212
209
  const log = execFileSync(
213
210
  "git",
@@ -216,13 +213,13 @@ function getCommitsSince(from, to, cwd = process.cwd()) {
216
213
  );
217
214
  if (!log.trim()) return [];
218
215
  return log.split(separator).filter((e) => e.trim()).map((entry) => {
219
- const [hash, shortHash, author, date, ...msgParts] = entry.trim().split("|");
216
+ const [hash, shortHash, author, date, ...msgParts] = entry.trim().split("");
220
217
  return {
221
218
  hash: hash.trim(),
222
219
  shortHash: shortHash.trim(),
223
220
  author: author.trim(),
224
221
  date: date.trim(),
225
- message: msgParts.join("|").trim()
222
+ message: msgParts.join("").trim()
226
223
  };
227
224
  });
228
225
  }
@@ -762,6 +759,13 @@ var TEAM_ONLY_PUBLISHERS = /* @__PURE__ */ new Set(["confluence", "notion", "tea
762
759
  var LICENSE_CACHE_TTL = 24 * 60 * 60 * 1e3;
763
760
  var LICENSE_FAILURE_CACHE_TTL = 5 * 60 * 1e3;
764
761
  var cachedValidation = null;
762
+ function isInternalHost(hostname) {
763
+ const h = hostname.toLowerCase();
764
+ if (h === "0.0.0.0" || h === "127.0.0.1" || h.startsWith("10.") || h.startsWith("192.168.") || h.startsWith("169.254.") || /^172\.(1[6-9]|2\d|3[01])\./.test(h)) return true;
765
+ if (h === "[::]" || h === "[::1]" || h.startsWith("[::ffff:") || h.startsWith("[fc") || h.startsWith("[fd") || h.startsWith("[fe80:") || h.startsWith("[fe80")) return true;
766
+ if (h === "localhost" || h.endsWith(".local") || h.endsWith(".internal")) return true;
767
+ return false;
768
+ }
765
769
  function resolveLicense() {
766
770
  const key = process.env.CULLIT_API_KEY?.trim();
767
771
  if (!key) {
@@ -792,8 +796,7 @@ async function validateLicense() {
792
796
  if (parsed.protocol !== "https:" && !(parsed.protocol === "http:" && parsed.hostname === "localhost")) {
793
797
  return { tier: "pro", valid: true, message: "CULLIT_LICENSE_URL must use https." };
794
798
  }
795
- const h = parsed.hostname;
796
- if (h === "0.0.0.0" || h === "[::]" || h === "[::1]" || h === "127.0.0.1" || h.startsWith("10.") || h.startsWith("192.168.") || h.startsWith("169.254.") || /^172\.(1[6-9]|2\d|3[01])\./.test(h) || h.endsWith(".local") || h.endsWith(".internal")) {
799
+ if (isInternalHost(parsed.hostname)) {
797
800
  return { tier: "pro", valid: true, message: "CULLIT_LICENSE_URL must not point to internal addresses." };
798
801
  }
799
802
  } catch {
@@ -858,39 +861,31 @@ var TIER_LIMITS = {
858
861
  team: { generationsPerMonth: 2e3, maxProjects: 250 },
859
862
  enterprise: { generationsPerMonth: Infinity, maxProjects: Infinity }
860
863
  };
861
- var PLAN_LIMITS = {
862
- "team-10": { generationsPerMonth: 4e3, maxProjects: 350 },
863
- "team-25": { generationsPerMonth: 5e3, maxProjects: 500 }
864
- };
865
864
  function getTierLimits(tier) {
866
865
  return TIER_LIMITS[tier] || TIER_LIMITS.free;
867
866
  }
868
- function getPlanLimits(plan, tier) {
869
- return PLAN_LIMITS[plan] || TIER_LIMITS[tier] || TIER_LIMITS.free;
867
+ function getTeamLimits(seats) {
868
+ const base = TIER_LIMITS.team;
869
+ if (seats <= TEAM_MIN_SEATS) return base;
870
+ return {
871
+ generationsPerMonth: Math.max(base.generationsPerMonth, seats * 100),
872
+ maxProjects: Math.max(base.maxProjects, seats * 5)
873
+ };
870
874
  }
871
875
  var FEATURE_TIERS = {
872
876
  drafts: /* @__PURE__ */ new Set(["team", "enterprise"]),
873
877
  approvals: /* @__PURE__ */ new Set(["team", "enterprise"]),
874
878
  shared_history: /* @__PURE__ */ new Set(["team", "enterprise"]),
875
- project_templates: /* @__PURE__ */ new Set(["enterprise"]),
876
- // plan-gated: team-25 via PLAN_FEATURES
879
+ project_templates: /* @__PURE__ */ new Set(["team", "enterprise"]),
877
880
  hosted_changelog: /* @__PURE__ */ new Set(["pro", "team", "enterprise"]),
878
- branded_widget: /* @__PURE__ */ new Set(["enterprise"]),
879
- // plan-gated: team-25 via PLAN_FEATURES
881
+ branded_widget: /* @__PURE__ */ new Set(["team", "enterprise"]),
880
882
  team_publishers: /* @__PURE__ */ new Set(["team", "enterprise"]),
881
883
  org_settings: /* @__PURE__ */ new Set(["team", "enterprise"]),
882
- audit_logs: /* @__PURE__ */ new Set(["enterprise"]),
883
- // plan-gated: team-25 via PLAN_FEATURES
884
- team_analytics: /* @__PURE__ */ new Set(["enterprise"]),
885
- // plan-gated: team-25 via PLAN_FEATURES
884
+ audit_logs: /* @__PURE__ */ new Set(["team", "enterprise"]),
885
+ team_analytics: /* @__PURE__ */ new Set(["team", "enterprise"]),
886
+ // all team plans get analytics now
886
887
  sso: /* @__PURE__ */ new Set(["enterprise"])
887
888
  };
888
- var PLAN_FEATURES = {
889
- branded_widget: /* @__PURE__ */ new Set(["team-25"]),
890
- project_templates: /* @__PURE__ */ new Set(["team-25"]),
891
- audit_logs: /* @__PURE__ */ new Set(["team-25"]),
892
- team_analytics: /* @__PURE__ */ new Set(["team-25"])
893
- };
894
889
  function isFeatureAllowed(feature, tier, valid = true) {
895
890
  if (!valid) return false;
896
891
  const allowed = FEATURE_TIERS[feature];
@@ -899,8 +894,6 @@ function isFeatureAllowed(feature, tier, valid = true) {
899
894
  function isPlanFeatureAllowed(feature, plan, tier, valid = true) {
900
895
  if (!valid) return false;
901
896
  if (tier === "enterprise") return true;
902
- const planSet = PLAN_FEATURES[feature];
903
- if (planSet) return planSet.has(plan);
904
897
  const tierSet = FEATURE_TIERS[feature];
905
898
  return tierSet ? tierSet.has(tier) : false;
906
899
  }
@@ -915,6 +908,12 @@ async function reportUsage(project = "default") {
915
908
  const key = process.env.CULLIT_API_KEY?.trim();
916
909
  const meterUrl = process.env.CULLIT_METER_URL?.trim();
917
910
  if (!meterUrl || !key) return;
911
+ try {
912
+ const parsed = new URL(meterUrl);
913
+ if (isInternalHost(parsed.hostname)) return;
914
+ } catch {
915
+ return;
916
+ }
918
917
  try {
919
918
  await fetchWithTimeout(meterUrl, {
920
919
  method: "POST",
@@ -1316,8 +1315,9 @@ export {
1316
1315
  PUBLISHER_TYPES,
1317
1316
  SOURCE_TYPES,
1318
1317
  StdoutPublisher,
1319
- TEAM_PLANS,
1320
- TEAM_PLAN_SEATS,
1318
+ TEAM_ANNUAL_DISCOUNT,
1319
+ TEAM_MIN_SEATS,
1320
+ TEAM_SEAT_PRICE,
1321
1321
  TEAM_TIERS,
1322
1322
  TIERS,
1323
1323
  TONES,
@@ -1335,9 +1335,9 @@ export {
1335
1335
  getFormatter,
1336
1336
  getGenerator,
1337
1337
  getLatestTag,
1338
- getPlanLimits,
1339
1338
  getPublisher,
1340
1339
  getRecentTags,
1340
+ getTeamLimits,
1341
1341
  getTierLimits,
1342
1342
  hasCollector,
1343
1343
  hasEnricher,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cullit/core",
3
- "version": "2.3.1",
3
+ "version": "2.4.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.3.1"
41
+ "@cullit/config": "2.4.0"
42
42
  },
43
43
  "scripts": {
44
44
  "build": "tsup src/index.ts --format esm --dts --clean",