@compilr-dev/sdk 0.9.18 → 0.9.20
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.
|
@@ -65,7 +65,14 @@ export class EntitlementCache {
|
|
|
65
65
|
*/
|
|
66
66
|
checkLimit(field, currentCount) {
|
|
67
67
|
if (!this.entitlements) {
|
|
68
|
-
|
|
68
|
+
// No entitlements loaded — use offline fallback limits (fail closed, not open)
|
|
69
|
+
const fallbackLimit = OFFLINE_FALLBACK_LIMITS[field];
|
|
70
|
+
if (fallbackLimit === UNLIMITED)
|
|
71
|
+
return { allowed: true };
|
|
72
|
+
if (currentCount >= fallbackLimit) {
|
|
73
|
+
return { allowed: false, reason: 'Entitlements not loaded', current: currentCount, limit: fallbackLimit };
|
|
74
|
+
}
|
|
75
|
+
return { allowed: true, current: currentCount, limit: fallbackLimit };
|
|
69
76
|
}
|
|
70
77
|
const limit = this.entitlements.limits[field];
|
|
71
78
|
// Unlimited
|
|
@@ -169,7 +176,10 @@ export class EntitlementCache {
|
|
|
169
176
|
const age = Date.now() - parsed.fetchedAtWall;
|
|
170
177
|
if (age < this.offlineGraceMs) {
|
|
171
178
|
this.entitlements = parsed.response;
|
|
172
|
-
|
|
179
|
+
// Compute monotonic offset from wall-clock age so isWithinOfflineGrace()
|
|
180
|
+
// returns the correct remaining time (not a fresh 24h window on every restart)
|
|
181
|
+
const ageNs = BigInt(age) * 1000000n;
|
|
182
|
+
this.fetchedAtMonotonic = process.hrtime.bigint() - ageNs;
|
|
173
183
|
this.fetchedAtWall = parsed.fetchedAtWall;
|
|
174
184
|
return this.entitlements.limits;
|
|
175
185
|
}
|
|
@@ -180,6 +190,20 @@ export class EntitlementCache {
|
|
|
180
190
|
}
|
|
181
191
|
}
|
|
182
192
|
// 3. Offline grace expired or no store — degrade to minimal fallback
|
|
193
|
+
// MUST set this.entitlements so checkLimit() doesn't fail open
|
|
194
|
+
this.entitlements = {
|
|
195
|
+
tier: 'free',
|
|
196
|
+
beta: false,
|
|
197
|
+
limits: OFFLINE_FALLBACK_LIMITS,
|
|
198
|
+
pendingPayment: false,
|
|
199
|
+
trialEndsAt: null,
|
|
200
|
+
features: {},
|
|
201
|
+
issuedAt: new Date().toISOString(),
|
|
202
|
+
userId: '',
|
|
203
|
+
signature: '',
|
|
204
|
+
};
|
|
205
|
+
this.fetchedAtMonotonic = process.hrtime.bigint();
|
|
206
|
+
this.fetchedAtWall = Date.now();
|
|
183
207
|
return OFFLINE_FALLBACK_LIMITS;
|
|
184
208
|
}
|
|
185
209
|
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Gating Helpers — Shared utilities for limit enforcement.
|
|
3
|
+
*
|
|
4
|
+
* DailyCounter: In-memory counter that resets at midnight UTC.
|
|
5
|
+
* Format helpers: Human-readable limit messages and time-until-reset.
|
|
6
|
+
*/
|
|
7
|
+
/**
|
|
8
|
+
* In-memory counter that resets daily at midnight UTC.
|
|
9
|
+
*
|
|
10
|
+
* Used for maxConversationsPerDay and maxAgentMessagesPerDay.
|
|
11
|
+
* Can be seeded from server-reported usage on startup.
|
|
12
|
+
*/
|
|
13
|
+
export declare class DailyCounter {
|
|
14
|
+
private count;
|
|
15
|
+
private resetDate;
|
|
16
|
+
/**
|
|
17
|
+
* Increment the counter and return the new value.
|
|
18
|
+
* Auto-resets if the UTC date has changed.
|
|
19
|
+
*/
|
|
20
|
+
increment(): number;
|
|
21
|
+
/**
|
|
22
|
+
* Get the current count without incrementing.
|
|
23
|
+
*/
|
|
24
|
+
getCount(): number;
|
|
25
|
+
/**
|
|
26
|
+
* Seed the counter with a server-reported value.
|
|
27
|
+
* Called on startup to prevent restart-based abuse.
|
|
28
|
+
*/
|
|
29
|
+
seed(count: number, date?: string): void;
|
|
30
|
+
/**
|
|
31
|
+
* Reset the counter to zero (e.g., for testing).
|
|
32
|
+
*/
|
|
33
|
+
reset(): void;
|
|
34
|
+
private ensureCurrentDay;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Format a human-readable limit message.
|
|
38
|
+
*
|
|
39
|
+
* @example
|
|
40
|
+
* formatLimitMessage('projects', 2, 2)
|
|
41
|
+
* // "You've reached your project limit (2/2)"
|
|
42
|
+
*
|
|
43
|
+
* formatLimitMessage('daily messages', 100, 100)
|
|
44
|
+
* // "You've reached your daily message limit (100/100)"
|
|
45
|
+
*/
|
|
46
|
+
export declare function formatLimitMessage(limitName: string, current: number, limit: number): string;
|
|
47
|
+
/**
|
|
48
|
+
* Format the time remaining until daily counters reset (midnight UTC).
|
|
49
|
+
*
|
|
50
|
+
* @example
|
|
51
|
+
* formatTimeUntilReset()
|
|
52
|
+
* // "Resets in 4h 23m" or "Resets in 59m"
|
|
53
|
+
*/
|
|
54
|
+
export declare function formatTimeUntilReset(): string;
|
|
55
|
+
/**
|
|
56
|
+
* Format an upgrade hint with the limit context.
|
|
57
|
+
*/
|
|
58
|
+
export declare function formatUpgradeHint(limitName: string): string;
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Gating Helpers — Shared utilities for limit enforcement.
|
|
3
|
+
*
|
|
4
|
+
* DailyCounter: In-memory counter that resets at midnight UTC.
|
|
5
|
+
* Format helpers: Human-readable limit messages and time-until-reset.
|
|
6
|
+
*/
|
|
7
|
+
// =============================================================================
|
|
8
|
+
// Daily Counter
|
|
9
|
+
// =============================================================================
|
|
10
|
+
/**
|
|
11
|
+
* In-memory counter that resets daily at midnight UTC.
|
|
12
|
+
*
|
|
13
|
+
* Used for maxConversationsPerDay and maxAgentMessagesPerDay.
|
|
14
|
+
* Can be seeded from server-reported usage on startup.
|
|
15
|
+
*/
|
|
16
|
+
export class DailyCounter {
|
|
17
|
+
count = 0;
|
|
18
|
+
resetDate = ''; // 'YYYY-MM-DD' in UTC
|
|
19
|
+
/**
|
|
20
|
+
* Increment the counter and return the new value.
|
|
21
|
+
* Auto-resets if the UTC date has changed.
|
|
22
|
+
*/
|
|
23
|
+
increment() {
|
|
24
|
+
this.ensureCurrentDay();
|
|
25
|
+
this.count++;
|
|
26
|
+
return this.count;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Get the current count without incrementing.
|
|
30
|
+
*/
|
|
31
|
+
getCount() {
|
|
32
|
+
this.ensureCurrentDay();
|
|
33
|
+
return this.count;
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Seed the counter with a server-reported value.
|
|
37
|
+
* Called on startup to prevent restart-based abuse.
|
|
38
|
+
*/
|
|
39
|
+
seed(count, date) {
|
|
40
|
+
const today = new Date().toISOString().slice(0, 10);
|
|
41
|
+
const seedDate = date ?? today;
|
|
42
|
+
if (seedDate === today) {
|
|
43
|
+
this.count = count;
|
|
44
|
+
this.resetDate = today;
|
|
45
|
+
}
|
|
46
|
+
// If seed date is not today, ignore (stale data)
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Reset the counter to zero (e.g., for testing).
|
|
50
|
+
*/
|
|
51
|
+
reset() {
|
|
52
|
+
this.count = 0;
|
|
53
|
+
this.resetDate = '';
|
|
54
|
+
}
|
|
55
|
+
ensureCurrentDay() {
|
|
56
|
+
const today = new Date().toISOString().slice(0, 10);
|
|
57
|
+
if (today !== this.resetDate) {
|
|
58
|
+
this.count = 0;
|
|
59
|
+
this.resetDate = today;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
// =============================================================================
|
|
64
|
+
// Format Helpers
|
|
65
|
+
// =============================================================================
|
|
66
|
+
/**
|
|
67
|
+
* Format a human-readable limit message.
|
|
68
|
+
*
|
|
69
|
+
* @example
|
|
70
|
+
* formatLimitMessage('projects', 2, 2)
|
|
71
|
+
* // "You've reached your project limit (2/2)"
|
|
72
|
+
*
|
|
73
|
+
* formatLimitMessage('daily messages', 100, 100)
|
|
74
|
+
* // "You've reached your daily message limit (100/100)"
|
|
75
|
+
*/
|
|
76
|
+
export function formatLimitMessage(limitName, current, limit) {
|
|
77
|
+
return `You've reached your ${limitName} limit (${String(current)}/${String(limit)})`;
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Format the time remaining until daily counters reset (midnight UTC).
|
|
81
|
+
*
|
|
82
|
+
* @example
|
|
83
|
+
* formatTimeUntilReset()
|
|
84
|
+
* // "Resets in 4h 23m" or "Resets in 59m"
|
|
85
|
+
*/
|
|
86
|
+
export function formatTimeUntilReset() {
|
|
87
|
+
const now = new Date();
|
|
88
|
+
const midnight = new Date(now);
|
|
89
|
+
midnight.setUTCDate(midnight.getUTCDate() + 1);
|
|
90
|
+
midnight.setUTCHours(0, 0, 0, 0);
|
|
91
|
+
const diffMs = midnight.getTime() - now.getTime();
|
|
92
|
+
const hours = Math.floor(diffMs / (1000 * 60 * 60));
|
|
93
|
+
const minutes = Math.floor((diffMs % (1000 * 60 * 60)) / (1000 * 60));
|
|
94
|
+
if (hours > 0) {
|
|
95
|
+
return `Resets in ${String(hours)}h ${String(minutes)}m`;
|
|
96
|
+
}
|
|
97
|
+
return `Resets in ${String(minutes)}m`;
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* Format an upgrade hint with the limit context.
|
|
101
|
+
*/
|
|
102
|
+
export function formatUpgradeHint(limitName) {
|
|
103
|
+
return `Upgrade to Pro for unlimited ${limitName}`;
|
|
104
|
+
}
|
|
@@ -5,3 +5,4 @@ export type { TierLimits, EntitlementResponse, LimitCheckResult, } from './types
|
|
|
5
5
|
export { UNLIMITED, OFFLINE_FALLBACK_LIMITS, } from './types.js';
|
|
6
6
|
export type { IEntitlementStore, EntitlementCacheConfig, } from './cache.js';
|
|
7
7
|
export { EntitlementCache } from './cache.js';
|
|
8
|
+
export { DailyCounter, formatLimitMessage, formatTimeUntilReset, formatUpgradeHint, } from './gating.js';
|
package/dist/index.d.ts
CHANGED
|
@@ -58,7 +58,7 @@ export { createSQLiteRepositories, SQLiteProjectRepository, SQLiteWorkItemReposi
|
|
|
58
58
|
export type { SQLiteRepositories, CreateSQLiteRepositoriesOptions, ProjectDeleteHooks, ProjectRecord, WorkItemRecord, ProjectDocumentRecord, WorkItemCommentRecord, } from './platform/index.js';
|
|
59
59
|
export { createAskUserTool, createAskUserSimpleTool, createProposeAlternativesTool } from './tools/index.js';
|
|
60
60
|
export type { AskUserQuestion, AskUserInput, AskUserResult, AskUserHandler, AskUserSimpleInput, AskUserSimpleResult, AskUserSimpleHandler, Alternative, ProposeAlternativesInput, ProposeAlternativesResult, ProposeAlternativesHandler, } from './tools/index.js';
|
|
61
|
-
export { EntitlementCache, UNLIMITED, OFFLINE_FALLBACK_LIMITS } from './entitlements/index.js';
|
|
61
|
+
export { EntitlementCache, UNLIMITED, OFFLINE_FALLBACK_LIMITS, DailyCounter, formatLimitMessage, formatTimeUntilReset, formatUpgradeHint, } from './entitlements/index.js';
|
|
62
62
|
export type { TierLimits, EntitlementResponse, LimitCheckResult, IEntitlementStore, EntitlementCacheConfig, } from './entitlements/index.js';
|
|
63
63
|
export { detectProject, suggestProjectType, detectCommon } from './detection/index.js';
|
|
64
64
|
export type { DetectProjectOptions, DetectionResult, ContentSummary } from './detection/index.js';
|
package/dist/index.js
CHANGED
|
@@ -135,7 +135,7 @@ export { createAskUserTool, createAskUserSimpleTool, createProposeAlternativesTo
|
|
|
135
135
|
// =============================================================================
|
|
136
136
|
// Entitlements (server-driven tier management)
|
|
137
137
|
// =============================================================================
|
|
138
|
-
export { EntitlementCache, UNLIMITED, OFFLINE_FALLBACK_LIMITS } from './entitlements/index.js';
|
|
138
|
+
export { EntitlementCache, UNLIMITED, OFFLINE_FALLBACK_LIMITS, DailyCounter, formatLimitMessage, formatTimeUntilReset, formatUpgradeHint, } from './entitlements/index.js';
|
|
139
139
|
// =============================================================================
|
|
140
140
|
// Project Detection (universal project content detection)
|
|
141
141
|
// =============================================================================
|