@cullit/core 1.18.1 → 2.0.1

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 = "1.18.1";
74
+ declare const VERSION = "2.0.1";
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,6 +82,12 @@ 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"];
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>;
85
91
 
86
92
  type LogLevel = 'quiet' | 'normal' | 'verbose';
87
93
  interface Logger {
@@ -219,7 +225,7 @@ declare function analyzeReleaseReadiness(cwd?: string): ReleaseAdvisory;
219
225
  * validateLicense() performs async remote validation with caching.
220
226
  * resolveLicense() remains sync for quick format-only checks (display).
221
227
  */
222
- type LicenseTier = 'free' | 'pro' | 'team' | 'enterprise';
228
+ type LicenseTier = 'free' | 'basic' | 'pro' | 'team' | 'enterprise';
223
229
  interface LicenseStatus {
224
230
  tier: LicenseTier;
225
231
  valid: boolean;
@@ -247,8 +253,14 @@ declare function isProviderAllowed(provider: string, license: LicenseStatus): bo
247
253
  declare function isPublisherAllowed(publisherType: string, license: LicenseStatus): boolean;
248
254
  /**
249
255
  * Check whether the current license allows enrichment (Jira/Linear).
256
+ * Requires Pro tier or above.
250
257
  */
251
258
  declare function isEnrichmentAllowed(license: LicenseStatus): boolean;
259
+ /**
260
+ * Check whether the current license allows audience & tone control.
261
+ * Requires Pro tier or above.
262
+ */
263
+ declare function isAudienceToneAllowed(license: LicenseStatus): boolean;
252
264
  /**
253
265
  * Build a human-readable upgrade message for a gated feature.
254
266
  */
@@ -315,9 +327,13 @@ declare function fetchWithTimeout(url: string, init: RequestInit, timeoutMs?: nu
315
327
  /**
316
328
  * Rate Limiter — Sliding-window rate limiter with pluggable backends.
317
329
  *
330
+ * Backends:
331
+ * - MemoryRateLimiter (default) — in-process, single instance only
332
+ * - RedisRateLimiter — shared across instances via REDIS_URL
333
+ *
318
334
  * Usage:
319
335
  * const limiter = createRateLimiter({ limit: 30, windowMs: 60_000 });
320
- * const result = limiter.check('user-ip-or-key');
336
+ * const result = await limiter.check('user-ip-or-key');
321
337
  * if (!result.allowed) { // reject }
322
338
  */
323
339
  interface RateLimitResult {
@@ -327,9 +343,9 @@ interface RateLimitResult {
327
343
  resetAt: number;
328
344
  }
329
345
  interface RateLimiter {
330
- check(key: string): RateLimitResult;
346
+ check(key: string): RateLimitResult | Promise<RateLimitResult>;
331
347
  /** Remove all tracked entries */
332
- reset(): void;
348
+ reset(): void | Promise<void>;
333
349
  }
334
350
  interface RateLimiterOptions {
335
351
  /** Max requests per window (default: 30) */
@@ -378,4 +394,4 @@ declare function runPipeline(from: string, to: string, config: CullConfig, optio
378
394
  templateProfile?: string;
379
395
  }): Promise<PipelineResult>;
380
396
 
381
- 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, PUBLISHER_TYPES, type PipelineResult, type Publisher, type PublisherFactory, type RateLimitResult, type RateLimiter, type RateLimiterOptions, type ReleaseAdvisory, type ReleaseNotes, SOURCE_TYPES, type SemverBump, StdoutPublisher, TONES, type TeamFeature, TemplateGenerator, type UsageLimits, VERSION, analyzeReleaseReadiness, createLogger, createRateLimiter, escapeHtml, fetchWithTimeout, formatNotes, getCollector, getEnricher, getFeatureGating, getFormatter, getGenerator, getLatestTag, getPublisher, getRecentTags, getTierLimits, hasCollector, hasEnricher, hasGenerator, hasPublisher, isEnrichmentAllowed, isFeatureAllowed, isProviderAllowed, isPublisherAllowed, listCollectors, listEnrichers, listFormatters, listGenerators, listPublishers, registerCollector, registerEnricher, registerFormatter, registerGenerator, registerPublisher, reportUsage, resolveLicense, runPipeline, upgradeMessage, validateLicense };
397
+ 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 };
package/dist/index.js CHANGED
@@ -1,5 +1,5 @@
1
1
  // src/constants.ts
2
- var VERSION = "1.18.1";
2
+ var VERSION = "2.0.1";
3
3
  var DEFAULT_CATEGORIES = ["features", "fixes", "breaking", "improvements", "chores"];
4
4
  var DEFAULT_MODELS = {
5
5
  anthropic: "claude-sonnet-4-20250514",
@@ -15,6 +15,15 @@ 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"];
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
+ };
18
27
 
19
28
  // src/logger.ts
20
29
  function createLogger(level = "normal") {
@@ -796,7 +805,7 @@ async function validateLicense() {
796
805
  if (res.ok) {
797
806
  const data = await res.json();
798
807
  const status2 = {
799
- tier: data.tier === "team" || data.tier === "enterprise" ? data.tier : data.tier === "pro" ? "pro" : "free",
808
+ tier: data.tier === "team" || data.tier === "enterprise" ? data.tier : data.tier === "pro" ? "pro" : data.tier === "basic" ? "basic" : "free",
800
809
  valid: data.valid !== false,
801
810
  message: data.message
802
811
  };
@@ -829,15 +838,19 @@ function isPublisherAllowed(publisherType, license) {
829
838
  return FREE_PUBLISHERS.has(publisherType);
830
839
  }
831
840
  function isEnrichmentAllowed(license) {
832
- return license.tier !== "free" && license.valid;
841
+ return (license.tier === "pro" || license.tier === "team" || license.tier === "enterprise") && license.valid;
842
+ }
843
+ function isAudienceToneAllowed(license) {
844
+ return (license.tier === "pro" || license.tier === "team" || license.tier === "enterprise") && license.valid;
833
845
  }
834
846
  function upgradeMessage(feature) {
835
- return `\u{1F512} ${feature} requires a Cullit Pro license.
836
- Get your API key at https://cullit.io/pricing
847
+ return `\u{1F512} ${feature} requires a Cullit Pro plan or above.
848
+ Upgrade at https://cullit.io/pricing
837
849
  Then set CULLIT_API_KEY in your environment.`;
838
850
  }
839
851
  var TIER_LIMITS = {
840
852
  free: { generationsPerMonth: 5, maxProjects: 3 },
853
+ basic: { generationsPerMonth: 50, maxProjects: 10 },
841
854
  pro: { generationsPerMonth: 500, maxProjects: 100 },
842
855
  team: { generationsPerMonth: 2e3, maxProjects: 250 },
843
856
  enterprise: { generationsPerMonth: Infinity, maxProjects: Infinity }
@@ -850,7 +863,7 @@ var FEATURE_TIERS = {
850
863
  approvals: /* @__PURE__ */ new Set(["team", "enterprise"]),
851
864
  shared_history: /* @__PURE__ */ new Set(["team", "enterprise"]),
852
865
  project_templates: /* @__PURE__ */ new Set(["team", "enterprise"]),
853
- hosted_changelog: /* @__PURE__ */ new Set(["pro", "team", "enterprise"]),
866
+ hosted_changelog: /* @__PURE__ */ new Set(["basic", "pro", "team", "enterprise"]),
854
867
  branded_widget: /* @__PURE__ */ new Set(["team", "enterprise"]),
855
868
  team_publishers: /* @__PURE__ */ new Set(["team", "enterprise"]),
856
869
  org_settings: /* @__PURE__ */ new Set(["team", "enterprise"]),
@@ -986,8 +999,58 @@ var MemoryRateLimiter = class {
986
999
  }
987
1000
  };
988
1001
  function createRateLimiter(opts) {
1002
+ const redisUrl = process.env["REDIS_URL"];
1003
+ if (redisUrl) {
1004
+ return new RedisRateLimiter(redisUrl, opts);
1005
+ }
989
1006
  return new MemoryRateLimiter(opts);
990
1007
  }
1008
+ var RedisRateLimiter = class {
1009
+ limit;
1010
+ windowMs;
1011
+ redisUrl;
1012
+ prefix;
1013
+ constructor(redisUrl, opts = {}) {
1014
+ this.limit = opts.limit ?? 30;
1015
+ this.windowMs = opts.windowMs ?? 6e4;
1016
+ this.redisUrl = redisUrl;
1017
+ this.prefix = "cullit:rl:";
1018
+ }
1019
+ async check(key) {
1020
+ const now = Date.now();
1021
+ const windowStart = now - this.windowMs;
1022
+ const redisKey = this.prefix + key;
1023
+ try {
1024
+ const res = await fetch(this.redisUrl, {
1025
+ method: "POST",
1026
+ headers: { "Content-Type": "application/json" },
1027
+ body: JSON.stringify([
1028
+ ["ZREMRANGEBYSCORE", redisKey, "0", String(windowStart)],
1029
+ ["ZCARD", redisKey],
1030
+ ["ZADD", redisKey, String(now), `${now}-${Math.random().toString(36).slice(2, 8)}`],
1031
+ ["PEXPIRE", redisKey, String(this.windowMs)]
1032
+ ]),
1033
+ signal: AbortSignal.timeout(3e3)
1034
+ });
1035
+ if (!res.ok) return this.fallbackAllow(now);
1036
+ const results = await res.json();
1037
+ const count = results[1]?.result ?? 0;
1038
+ const remaining = Math.max(0, this.limit - count);
1039
+ const resetAt = Math.ceil((now + this.windowMs) / 1e3);
1040
+ if (count >= this.limit) {
1041
+ return { allowed: false, remaining: 0, resetAt };
1042
+ }
1043
+ return { allowed: true, remaining: remaining - 1, resetAt };
1044
+ } catch {
1045
+ return this.fallbackAllow(now);
1046
+ }
1047
+ }
1048
+ fallbackAllow(now) {
1049
+ return { allowed: true, remaining: this.limit, resetAt: Math.ceil((now + this.windowMs) / 1e3) };
1050
+ }
1051
+ async reset() {
1052
+ }
1053
+ };
991
1054
 
992
1055
  // src/index.ts
993
1056
  registerCollector("local", (config) => new GitCollector(config.source?.repoPath));
@@ -1210,9 +1273,14 @@ export {
1210
1273
  GitCollector,
1211
1274
  MultiRepoCollector,
1212
1275
  OUTPUT_FORMATS,
1276
+ PAID_TIERS,
1213
1277
  PUBLISHER_TYPES,
1214
1278
  SOURCE_TYPES,
1215
1279
  StdoutPublisher,
1280
+ TEAM_PLANS,
1281
+ TEAM_PLAN_SEATS,
1282
+ TEAM_TIERS,
1283
+ TIERS,
1216
1284
  TONES,
1217
1285
  TemplateGenerator,
1218
1286
  VERSION,
@@ -1235,6 +1303,7 @@ export {
1235
1303
  hasEnricher,
1236
1304
  hasGenerator,
1237
1305
  hasPublisher,
1306
+ isAudienceToneAllowed,
1238
1307
  isEnrichmentAllowed,
1239
1308
  isFeatureAllowed,
1240
1309
  isProviderAllowed,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cullit/core",
3
- "version": "1.18.1",
3
+ "version": "2.0.1",
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": "1.18.1"
41
+ "@cullit/config": "2.0.1"
42
42
  },
43
43
  "scripts": {
44
44
  "build": "tsup src/index.ts --format esm --dts --clean",