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.
- package/.firebaserc +5 -0
- package/firebase.json +16 -0
- package/functions/.eslintrc.js +28 -0
- package/functions/index.js +65 -0
- package/functions/package-lock.json +2727 -0
- package/functions/package.json +12 -0
- package/package.json +2 -2
- package/skills-lock.json +65 -0
- package/src/index.js +22 -7
- package/src/license-validator.js +56 -0
- package/src/request-interceptor.js +3 -3
- package/src/session-manager.js +29 -20
package/package.json
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "token-saver-cc",
|
|
3
|
-
"version": "1.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",
|
package/skills-lock.json
ADDED
|
@@ -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
|
-
//
|
|
34
|
-
|
|
33
|
+
// Extract license key from options or environment variables
|
|
34
|
+
const licenseKey = options.licenseKey || process.env.TOKEN_SAVER_LICENSE;
|
|
35
35
|
|
|
36
|
-
// Create and
|
|
37
|
-
|
|
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
|
-
*
|
|
9
|
-
*
|
|
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}
|
package/src/session-manager.js
CHANGED
|
@@ -3,11 +3,12 @@
|
|
|
3
3
|
* Tracks file states and computes deltas to avoid re-transmission
|
|
4
4
|
*
|
|
5
5
|
* Usage:
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
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,
|
|
211
|
+
saved_estimate: cached.size,
|
|
200
212
|
});
|
|
201
213
|
|
|
202
|
-
delta.tokens_saved_estimate += Math.ceil(cached.size / 4);
|
|
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;
|
|
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);
|
|
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
|
|
350
|
+
* Clear session cache
|
|
342
351
|
*/
|
|
343
352
|
clearCache() {
|
|
344
353
|
this.db
|