token-saver-cc 1.0.0 → 1.0.2

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.
@@ -0,0 +1,12 @@
1
+ {
2
+ "name": "functions",
3
+ "description": "Cloud Functions for Firebase",
4
+ "engines": {
5
+ "node": "22"
6
+ },
7
+ "main": "index.js",
8
+ "dependencies": {
9
+ "firebase-admin": "^13.8.0",
10
+ "firebase-functions": "^7.2.5"
11
+ }
12
+ }
package/package.json CHANGED
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "name": "token-saver-cc",
3
- "version": "1.0.0",
3
+ "version": "1.0.2",
4
4
  "description": "Transparent token optimization middleware for Claude Code. Save 80-95% tokens without changing your workflow.",
5
- "main": "index.js",
5
+ "main": "src/index.js",
6
6
  "scripts": {
7
7
  "test": "jest",
8
8
  "test:watch": "jest --watch",
@@ -0,0 +1,65 @@
1
+ {
2
+ "version": 1,
3
+ "skills": {
4
+ "developing-genkit-dart": {
5
+ "source": "firebase/agent-skills",
6
+ "sourceType": "github",
7
+ "computedHash": "ba0025db33c95d8b66cd6de1f4c25b8b70456fb2789ec6c97cd88d432eed3fb3"
8
+ },
9
+ "developing-genkit-go": {
10
+ "source": "firebase/agent-skills",
11
+ "sourceType": "github",
12
+ "computedHash": "14dde37f197a606c7978a0876ca836002cae799dfc677d8da31b2f25ddee74e1"
13
+ },
14
+ "developing-genkit-js": {
15
+ "source": "firebase/agent-skills",
16
+ "sourceType": "github",
17
+ "computedHash": "4d3de47b5be279ab3b0ee6055ae541f28d1cc45f8e8986c0b5df2f8d89f4ea30"
18
+ },
19
+ "firebase-ai-logic": {
20
+ "source": "firebase/agent-skills",
21
+ "sourceType": "github",
22
+ "computedHash": "6ddf74ac9b3366d396e5fa85e695a7c5871e39edffc385533eaee2be159fcf03"
23
+ },
24
+ "firebase-app-hosting-basics": {
25
+ "source": "firebase/agent-skills",
26
+ "sourceType": "github",
27
+ "computedHash": "a3ba088442abf5b97281bcf3e4dd11e1f7bdefdc5bd404b4fb30f344d19742d2"
28
+ },
29
+ "firebase-auth-basics": {
30
+ "source": "firebase/agent-skills",
31
+ "sourceType": "github",
32
+ "computedHash": "a8fb7b1bfaf9f7d1bd1ebd7e1d2718f42f51361c8b2776ed1cdc17548334fe51"
33
+ },
34
+ "firebase-basics": {
35
+ "source": "firebase/agent-skills",
36
+ "sourceType": "github",
37
+ "computedHash": "98d865baea6ebd70723a17c5ca6bb7a9a504fbffddfb10525b08c9d45f7d9a31"
38
+ },
39
+ "firebase-data-connect": {
40
+ "source": "firebase/agent-skills",
41
+ "sourceType": "github",
42
+ "computedHash": "94bc7d16a4c368c9f3b29b3b26cea952a6a016c0e36b13b5805878ef2df1f7b6"
43
+ },
44
+ "firebase-firestore-enterprise-native-mode": {
45
+ "source": "firebase/agent-skills",
46
+ "sourceType": "github",
47
+ "computedHash": "26a8074d01189118a59c0e70dbe6492945e446c3d0d721e60f5dcc5004d1bf97"
48
+ },
49
+ "firebase-firestore-standard": {
50
+ "source": "firebase/agent-skills",
51
+ "sourceType": "github",
52
+ "computedHash": "82820bddbe8acd17df8b9d0555e79cd1783ffd3cdba1306a0c794c35cc5c1919"
53
+ },
54
+ "firebase-hosting-basics": {
55
+ "source": "firebase/agent-skills",
56
+ "sourceType": "github",
57
+ "computedHash": "0688a8ad6cb72149924352cc47e6cc8bf815e944b636a67b9da225cc9b025afc"
58
+ },
59
+ "firestore-security-rules-auditor": {
60
+ "source": "firebase/agent-skills",
61
+ "sourceType": "github",
62
+ "computedHash": "f3e47896a49b76db787f47ee97f8cb2e23c51e4b18a73cd2ded32d6cc8c02869"
63
+ }
64
+ }
65
+ }
package/src/index.js CHANGED
@@ -30,11 +30,22 @@ function install(options = {}) {
30
30
  }
31
31
 
32
32
  try {
33
- // Create session manager
34
- globalSessionManager = new SessionManager(options.sessionId);
33
+ // Extract license key from options or environment variables
34
+ const licenseKey = options.licenseKey || process.env.TOKEN_SAVER_LICENSE;
35
35
 
36
- // Create and install interceptor
37
- globalInterceptor = new RequestInterceptor(globalSessionManager);
36
+ // Create session manager with sessionId and license
37
+ globalSessionManager = new SessionManager(
38
+ options.sessionId || null,
39
+ licenseKey,
40
+ );
41
+
42
+ // Create interceptor (licenseKey is 1st arg, sessionManager is 2nd)
43
+ globalInterceptor = new RequestInterceptor(
44
+ licenseKey,
45
+ globalSessionManager,
46
+ );
47
+
48
+ // Install the global hook into the Anthropic SDK
38
49
  const installed = globalInterceptor.install();
39
50
 
40
51
  if (!installed) {
@@ -44,13 +55,17 @@ function install(options = {}) {
44
55
  return null;
45
56
  }
46
57
 
58
+ // Get tier info for the display
59
+ const tierInfo = globalInterceptor.getTierInfo();
60
+
47
61
  console.log(`
48
62
  ╔════════════════════════════════════════╗
49
- ║ Token Saver Installed ✓
63
+ ║ Token Saver Installed ✓
50
64
  ╠════════════════════════════════════════╣
51
65
  ║ Claude Code will use less tokens. ║
52
66
  ║ No action needed — working silently. ║
53
67
  ║ ║
68
+ ║ Tier: ${tierInfo.tier.toUpperCase()}
54
69
  ║ Session: ${globalSessionManager.getSessionId()}
55
70
  ║ Tracking: ${options.trackingEnabled !== false ? "ON" : "OFF"}
56
71
  ╚════════════════════════════════════════╝
@@ -62,7 +77,6 @@ function install(options = {}) {
62
77
  return null;
63
78
  }
64
79
  }
65
-
66
80
  /**
67
81
  * Get current statistics
68
82
  */
@@ -106,10 +120,11 @@ function getSessionId() {
106
120
 
107
121
  module.exports = {
108
122
  install,
123
+ getSessionId,
109
124
  getStats,
125
+ getTier: () => globalSessionManager?.getTierInfo() || { tier: "free" },
110
126
  printSummary,
111
127
  uninstall,
112
- getSessionId,
113
128
  SessionManager,
114
129
  RequestInterceptor,
115
130
  };
@@ -0,0 +1,56 @@
1
+ /**
2
+ * License Validator using Firebase Cloud Function
3
+ */
4
+
5
+ class LicenseValidator {
6
+ constructor(firebaseUrl = null) {
7
+ // Replace YOUR-PROJECT-ID with your Firebase project ID
8
+ this.firebaseUrl =
9
+ firebaseUrl ||
10
+ "https://us-central1-tokensavercc.cloudfunctions.net/validateLicense";
11
+ this.cache = new Map();
12
+ }
13
+
14
+ async validate(licenseKey) {
15
+ if (!licenseKey) {
16
+ return { valid: false, tier: "free" };
17
+ }
18
+
19
+ // Check cache (5 minutes)
20
+ const cacheKey = `license-${licenseKey}`;
21
+ const cached = this.cache.get(cacheKey);
22
+
23
+ if (cached && Date.now() - cached.timestamp < 300000) {
24
+ return cached.data;
25
+ }
26
+
27
+ try {
28
+ // Call your Firebase Cloud Function
29
+ const response = await fetch(this.firebaseUrl, {
30
+ method: "POST",
31
+ headers: { "Content-Type": "application/json" },
32
+ body: JSON.stringify({ key: licenseKey }),
33
+ });
34
+
35
+ const result = await response.json();
36
+
37
+ // Cache the result
38
+ this.cache.set(cacheKey, {
39
+ data: result,
40
+ timestamp: Date.now(),
41
+ });
42
+
43
+ return result;
44
+ } catch (error) {
45
+ console.warn("[Token Saver] License validation failed:", error.message);
46
+ // Fail safe to free tier
47
+ return { valid: false, tier: "free" };
48
+ }
49
+ }
50
+
51
+ clearCache() {
52
+ this.cache.clear();
53
+ }
54
+ }
55
+
56
+ module.exports = LicenseValidator;
@@ -5,8 +5,8 @@
5
5
  * Installed globally so every Claude Code call gets optimized
6
6
  *
7
7
  * Usage:
8
- * const interceptor = new RequestInterceptor();
9
- * interceptor.install(); // Hook into Anthropic SDK
8
+ * const interceptor = new RequestInterceptor();
9
+ * interceptor.install(); // Hook into Anthropic SDK
10
10
  */
11
11
 
12
12
  const SessionManager = require("./session-manager");
@@ -277,7 +277,7 @@ class RequestInterceptor {
277
277
  const stats = this.getStats();
278
278
  console.log(`
279
279
  ╔════════════════════════════════════════╗
280
- ║ Token Saver Summary
280
+ ║ Token Saver Summary
281
281
  ╠════════════════════════════════════════╣
282
282
  ║ Session ID: ${stats.session_stats.session_id}
283
283
  ║ Messages: ${stats.total_requests}
@@ -3,11 +3,12 @@
3
3
  * Tracks file states and computes deltas to avoid re-transmission
4
4
  *
5
5
  * Usage:
6
- * const mgr = new SessionManager();
7
- * mgr.cacheFileRead('src/auth.ts', authFileContent);
8
- * const delta = mgr.computeDelta(currentFiles);
9
- * const sessionId = mgr.getSessionId();
6
+ * const mgr = new SessionManager(null, 'your-license-key');
7
+ * mgr.cacheFileRead('src/auth.ts', authFileContent);
8
+ * const delta = mgr.computeDelta(currentFiles);
9
+ * const sessionId = mgr.getSessionId();
10
10
  */
11
+ const LicenseValidator = require("./license-validator");
11
12
 
12
13
  const crypto = require("crypto");
13
14
  const Database = require("better-sqlite3");
@@ -16,7 +17,7 @@ const os = require("os");
16
17
  const fs = require("fs");
17
18
 
18
19
  class SessionManager {
19
- constructor(sessionId = null) {
20
+ constructor(sessionId = null, licenseKey = null) {
20
21
  // Create ~/.token-saver directory for cache
21
22
  this.cacheDir = path.join(os.homedir(), ".token-saver");
22
23
  if (!fs.existsSync(this.cacheDir)) {
@@ -40,6 +41,24 @@ class SessionManager {
40
41
  this._loadSessionSnapshot();
41
42
 
42
43
  console.log(`[Token Saver] Session ${this.sessionId} initialized`);
44
+
45
+ // License and Tier Management
46
+ this.licenseKey = licenseKey;
47
+ this.licenseValidator = new LicenseValidator();
48
+ this.isProTier = false;
49
+
50
+ if (licenseKey) {
51
+ this.licenseValidator.validate(licenseKey).then((result) => {
52
+ this.isProTier = result.tier === "pro";
53
+ });
54
+ }
55
+ }
56
+
57
+ /**
58
+ * Returns the current tier status
59
+ */
60
+ getTierInfo() {
61
+ return { tier: this.isProTier ? "pro" : "free" };
43
62
  }
44
63
 
45
64
  /**
@@ -153,13 +172,6 @@ class SessionManager {
153
172
 
154
173
  /**
155
174
  * Compute delta: what changed since last snapshot?
156
- *
157
- * Returns:
158
- * {
159
- * changed_files: [{ filepath, status: 'modified'|'new', delta: ... }],
160
- * unchanged_files: [{ filepath, hash }],
161
- * tokens_saved_estimate: number
162
- * }
163
175
  */
164
176
  computeDelta(currentFiles) {
165
177
  const delta = {
@@ -196,10 +208,10 @@ class SessionManager {
196
208
  old_hash: cached.hash,
197
209
  new_hash: hash,
198
210
  size: content.length,
199
- saved_estimate: cached.size, // We won't resend this file's old state
211
+ saved_estimate: cached.size,
200
212
  });
201
213
 
202
- delta.tokens_saved_estimate += Math.ceil(cached.size / 4); // Rough estimate
214
+ delta.tokens_saved_estimate += Math.ceil(cached.size / 4);
203
215
  } else {
204
216
  // Unchanged
205
217
  delta.unchanged_files.push({
@@ -207,7 +219,6 @@ class SessionManager {
207
219
  hash: cached.hash,
208
220
  });
209
221
 
210
- // Estimate tokens saved by not re-transmitting unchanged file
211
222
  delta.tokens_saved_estimate += Math.ceil(cached.size / 4);
212
223
  }
213
224
  }
@@ -317,17 +328,15 @@ class SessionManager {
317
328
 
318
329
  /**
319
330
  * Summarize conversation for compression
320
- * Returns bullet points of key decisions/code changes
321
331
  */
322
332
  getSummary() {
323
333
  const conv = this.conversationLog;
324
334
 
325
- if (conv.length < 5) return null; // Too short to summarize
335
+ if (conv.length < 5) return null;
326
336
 
327
- // Extract assistant messages (code/decisions)
328
337
  const assistantMessages = conv
329
338
  .filter((m) => m.role === "assistant")
330
- .slice(0, -2); // Keep last 2 messages fresh
339
+ .slice(0, -2);
331
340
 
332
341
  return {
333
342
  message_count: assistantMessages.length,
@@ -338,7 +347,7 @@ class SessionManager {
338
347
  }
339
348
 
340
349
  /**
341
- * Clear session cache (useful for long sessions)
350
+ * Clear session cache
342
351
  */
343
352
  clearCache() {
344
353
  this.db