@curatedmcp/tokenshield 1.0.1 → 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/dist/dashboard.js CHANGED
@@ -16,9 +16,15 @@ export function dashboardHtml(opts) {
16
16
  --text: #e7e9ee;
17
17
  --muted: #8b94a3;
18
18
  --accent: #6dd3a8;
19
- --accent-dim: rgba(109,211,168,0.15);
20
19
  --warn: #f0b35e;
20
+ --warn-bg: rgba(240,179,94,0.08);
21
+ --warn-border: rgba(240,179,94,0.35);
21
22
  --bad: #ef6868;
23
+ --bad-bg: rgba(239,104,104,0.08);
24
+ --bad-border: rgba(239,104,104,0.35);
25
+ --info: #7eb8ff;
26
+ --info-bg: rgba(126,184,255,0.08);
27
+ --info-border: rgba(126,184,255,0.35);
22
28
  --link: #7eb8ff;
23
29
  }
24
30
  * { box-sizing: border-box }
@@ -26,35 +32,54 @@ html, body { margin: 0; background: var(--bg); color: var(--text); font: 14px/1.
26
32
  .wrap { max-width: 1100px; margin: 0 auto; padding: 24px; }
27
33
 
28
34
  /* ── header ── */
29
- header { display: flex; align-items: center; justify-content: space-between; margin-bottom: 20px; gap: 12px; }
30
- .brand { display: flex; align-items: baseline; gap: 10px; }
35
+ header { display: flex; align-items: center; justify-content: space-between; margin-bottom: 16px; gap: 12px; flex-wrap: wrap; }
36
+ .brand { display: flex; align-items: baseline; gap: 10px; flex-wrap: wrap; }
31
37
  .brand-name { font-size: 18px; font-weight: 700; letter-spacing: 0.02em; color: var(--text); text-decoration: none; }
32
38
  .brand-name:hover { color: var(--accent); }
33
39
  .brand-by { font-size: 11px; color: var(--muted); font-weight: 400; letter-spacing: 0.02em; }
34
40
  .brand-by a { color: var(--accent); text-decoration: none; font-weight: 500; }
35
41
  .brand-by a:hover { text-decoration: underline; }
36
42
  .brand-ver { font-size: 12px; color: var(--muted); font-weight: 400; }
43
+ .mode-pill { font-size: 10px; text-transform: uppercase; letter-spacing: 0.08em; padding: 2px 7px; border-radius: 999px; background: var(--info-bg); border: 1px solid var(--info-border); color: var(--info); font-weight: 600; cursor: help; }
37
44
  .status { display: flex; align-items: center; gap: 14px; }
38
45
  .status-pill { font-size: 12px; color: var(--muted); display: flex; align-items: center; gap: 6px; }
39
46
  .dot { display: inline-block; width: 8px; height: 8px; border-radius: 999px; background: var(--accent); flex-shrink: 0; }
40
47
  .dot.pulse { animation: pulse 2s ease-in-out infinite; }
41
48
  @keyframes pulse { 0%,100% { opacity:1 } 50% { opacity:.45 } }
42
49
 
50
+ /* ── diagnostic banner ── */
51
+ .diag { display: none; margin-bottom: 18px; border-radius: 10px; padding: 14px 16px; font-size: 13px; line-height: 1.55; border: 1px solid; }
52
+ .diag.show { display: block; }
53
+ .diag.info { background: var(--info-bg); border-color: var(--info-border); color: var(--text); }
54
+ .diag.warn { background: var(--warn-bg); border-color: var(--warn-border); color: var(--text); }
55
+ .diag.bad { background: var(--bad-bg); border-color: var(--bad-border); color: var(--text); }
56
+ .diag-title { font-weight: 600; margin-bottom: 6px; display: flex; align-items: center; gap: 8px; }
57
+ .diag-title .icon { display: inline-flex; width: 18px; height: 18px; align-items: center; justify-content: center; border-radius: 999px; font-size: 11px; font-weight: 700; }
58
+ .diag.info .diag-title { color: var(--info); }
59
+ .diag.info .icon { background: var(--info); color: #0b0d10; }
60
+ .diag.warn .diag-title { color: var(--warn); }
61
+ .diag.warn .icon { background: var(--warn); color: #0b0d10; }
62
+ .diag.bad .diag-title { color: var(--bad); }
63
+ .diag.bad .icon { background: var(--bad); color: #0b0d10; }
64
+ .diag code { font-family: ui-monospace, "SF Mono", Menlo, monospace; font-size: 12px; background: var(--panel-2); padding: 1px 6px; border-radius: 4px; border: 1px solid var(--border); }
65
+ .diag a { color: var(--link); }
66
+
43
67
  /* ── metric cards ── */
44
68
  .grid { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: 12px; }
45
69
  .card { background: var(--panel); border: 1px solid var(--border); border-radius: 10px; padding: 14px; }
46
70
  .card .label { font-size: 11px; text-transform: uppercase; letter-spacing: 0.08em; color: var(--muted); }
47
71
  .card .value { font-size: 22px; font-weight: 600; margin-top: 6px; font-variant-numeric: tabular-nums; }
48
72
  .card .sub { font-size: 11px; color: var(--muted); margin-top: 4px; }
49
-
50
- /* ── savings highlight card ── */
51
73
  .card.savings { border-color: rgba(109,211,168,0.25); background: linear-gradient(135deg, var(--panel) 0%, rgba(109,211,168,0.06) 100%); }
52
74
  .card.savings .value { color: var(--accent); }
53
75
  .card.savings .label { color: rgba(109,211,168,0.7); }
54
76
 
55
77
  /* ── tables ── */
56
78
  .section { margin-top: 24px; }
57
- .section h2 { font-size: 13px; text-transform: uppercase; letter-spacing: 0.08em; color: var(--muted); margin: 0 0 10px; }
79
+ .section-head { display: flex; justify-content: space-between; align-items: center; margin: 0 0 10px; }
80
+ .section h2 { font-size: 13px; text-transform: uppercase; letter-spacing: 0.08em; color: var(--muted); margin: 0; }
81
+ .toggle-link { font-size: 11px; color: var(--muted); cursor: pointer; user-select: none; background: none; border: none; padding: 0; font-family: inherit; }
82
+ .toggle-link:hover { color: var(--accent); }
58
83
  table { width: 100%; border-collapse: collapse; font-variant-numeric: tabular-nums; }
59
84
  th, td { text-align: left; padding: 8px 10px; border-bottom: 1px solid var(--border); font-size: 13px; }
60
85
  th { color: var(--muted); font-weight: 500; font-size: 11px; text-transform: uppercase; letter-spacing: 0.06em; }
@@ -63,18 +88,21 @@ tr:last-child td { border-bottom: 0; }
63
88
  .num { text-align: right; }
64
89
  .pill { display: inline-block; padding: 1px 8px; border-radius: 999px; font-size: 11px; border: 1px solid var(--border); }
65
90
  .pill.ok { color: var(--accent); border-color: rgba(109,211,168,0.35); }
66
- .pill.err { color: var(--bad); border-color: rgba(239,104,104,0.35); }
91
+ .pill.warn { color: var(--warn); border-color: var(--warn-border); }
92
+ .pill.err { color: var(--bad); border-color: var(--bad-border); }
93
+ .row-faded td { opacity: 0.55; }
67
94
 
68
95
  /* ── misc ── */
69
- .cmd { background: var(--panel-2); border: 1px solid var(--border); border-radius: 6px; padding: 10px 12px; font-family: ui-monospace, "SF Mono", Menlo, monospace; font-size: 12px; }
96
+ .cmd { background: var(--panel-2); border: 1px solid var(--border); border-radius: 6px; padding: 6px 10px; font-family: ui-monospace, "SF Mono", Menlo, monospace; font-size: 12px; display: inline-block; }
70
97
  .kbd { font-family: ui-monospace, monospace; background: var(--panel-2); border: 1px solid var(--border); border-radius: 4px; padding: 1px 5px; font-size: 11px; }
71
98
  a { color: var(--link); text-decoration: none; }
72
99
  a:hover { text-decoration: underline; }
73
100
 
74
101
  /* ── footer ── */
75
- .footer { margin-top: 28px; padding-top: 16px; border-top: 1px solid var(--border); display: flex; justify-content: space-between; align-items: flex-start; gap: 16px; flex-wrap: wrap; }
76
- .footer-privacy { color: var(--muted); font-size: 12px; flex: 1 1 400px; }
77
- .footer-links { display: flex; align-items: center; gap: 14px; flex-shrink: 0; flex-wrap: wrap; }
102
+ .footer { margin-top: 28px; padding-top: 16px; border-top: 1px solid var(--border); display: flex; justify-content: space-between; align-items: flex-start; gap: 20px; flex-wrap: wrap; }
103
+ .footer-privacy { color: var(--muted); font-size: 12px; flex: 1 1 380px; line-height: 1.7; }
104
+ .footer-privacy .cmd { margin: 4px 0; }
105
+ .footer-links { display: flex; align-items: center; gap: 12px; flex-shrink: 0; flex-wrap: wrap; }
78
106
  .footer-links a { font-size: 12px; color: var(--muted); text-decoration: none; white-space: nowrap; }
79
107
  .footer-links a:hover { color: var(--accent); text-decoration: none; }
80
108
  .footer-links .sep { color: var(--border); user-select: none; }
@@ -89,20 +117,23 @@ a:hover { text-decoration: underline; }
89
117
  <a class="brand-name" href="https://www.curatedmcp.com/tokenshield" target="_blank" rel="noopener">TokenShield</a>
90
118
  <span class="brand-ver">v${opts.version}</span>
91
119
  <span class="brand-by">by <a href="https://www.curatedmcp.com" target="_blank" rel="noopener">CuratedMCP</a></span>
120
+ <span class="mode-pill" title="Estimate mode measures your spend. Active compression processors (dedup, cache, diff) ship in upcoming releases — track at curatedmcp.com/tokenshield.">Estimate mode</span>
92
121
  </div>
93
122
  <div class="status">
94
123
  <div class="status-pill">
95
- <span class="dot pulse"></span>
124
+ <span class="dot pulse" id="status-dot"></span>
96
125
  <span id="status-text">proxy live · ${opts.bind}:${opts.proxyPort}</span>
97
126
  </div>
98
127
  </div>
99
128
  </header>
100
129
 
130
+ <div id="diag" class="diag"></div>
131
+
101
132
  <div class="grid">
102
133
  <div class="card savings">
103
134
  <div class="label">Spent (24h)</div>
104
135
  <div class="value" id="dollars-spent">$0.00</div>
105
- <div class="sub" id="request-count">0 requests</div>
136
+ <div class="sub" id="request-count">0 requests · 0 successful</div>
106
137
  </div>
107
138
  <div class="card">
108
139
  <div class="label">Input tokens (24h)</div>
@@ -138,7 +169,10 @@ a:hover { text-decoration: underline; }
138
169
  </div>
139
170
 
140
171
  <div class="section">
141
- <h2>Recent requests</h2>
172
+ <div class="section-head">
173
+ <h2>Recent requests</h2>
174
+ <button class="toggle-link" id="toggle-noise" onclick="toggleNoise()">Show probes</button>
175
+ </div>
142
176
  <table id="recent">
143
177
  <thead>
144
178
  <tr>
@@ -158,7 +192,7 @@ a:hover { text-decoration: underline; }
158
192
 
159
193
  <div class="footer">
160
194
  <div class="footer-privacy">
161
- Privacy: TokenShield never stores prompt content. Your Anthropic API key stays in process memory.
195
+ Privacy: TokenShield never stores prompt content. Your Anthropic API key stays in process memory.<br>
162
196
  Set <span class="cmd">export ANTHROPIC_BASE_URL=${proxyBase}</span> in the shell you run Claude Code from.
163
197
  </div>
164
198
  <div class="footer-links">
@@ -178,6 +212,88 @@ const fmtDollars = (d) => '$' + Number(d || 0).toFixed(Math.abs(d) < 1 ? 4 : 2);
178
212
  const fmtTime = (ms) => new Date(ms).toLocaleTimeString();
179
213
  const fmtMs = (ms) => ms < 1000 ? ms + 'ms' : (ms / 1000).toFixed(1) + 's';
180
214
 
215
+ // Treat /v1/* requests as real Anthropic traffic; everything else (e.g. GET /, probes) is noise.
216
+ const isApiCall = (r) => typeof r.endpoint === 'string' && r.endpoint.startsWith('/v1/');
217
+
218
+ let showNoise = false;
219
+ function toggleNoise() {
220
+ showNoise = !showNoise;
221
+ document.getElementById('toggle-noise').textContent = showNoise ? 'Hide probes' : 'Show probes';
222
+ // Re-render on next tick from cached state
223
+ if (window.__lastRec) renderRecent(window.__lastRec);
224
+ }
225
+
226
+ function renderDiagnostic(rec) {
227
+ const diag = document.getElementById('diag');
228
+ const apiCalls = rec.filter(isApiCall);
229
+ const failed = apiCalls.filter((r) => r.upstreamStatus === 401 || r.upstreamStatus === 403);
230
+ const fiveXx = apiCalls.filter((r) => r.upstreamStatus >= 500);
231
+ const ok = apiCalls.filter((r) => r.upstreamStatus >= 200 && r.upstreamStatus < 300);
232
+
233
+ let html = '';
234
+ let cls = '';
235
+
236
+ if (apiCalls.length === 0) {
237
+ cls = 'info';
238
+ html = '<div class="diag-title"><span class="icon">i</span>Waiting for traffic</div>' +
239
+ 'No Anthropic API calls yet. In the shell that runs Claude Code, set:<br>' +
240
+ '<code>export ANTHROPIC_API_KEY=sk-ant-…</code> &nbsp; <code>export ANTHROPIC_BASE_URL=${proxyBase}</code><br>' +
241
+ 'Then run <code>claude</code> in that same shell.';
242
+ } else if (failed.length >= apiCalls.length * 0.5 && failed.length > 0) {
243
+ cls = 'bad';
244
+ const pct = Math.round((failed.length / apiCalls.length) * 100);
245
+ html = '<div class="diag-title"><span class="icon">!</span>' + pct + '% of API calls failing with 401/403 — your API key isn\\'t reaching Anthropic</div>' +
246
+ 'The proxy is forwarding requests fine, but Anthropic is rejecting them. In the <strong>shell that runs Claude Code</strong> (not this one), check:<br>' +
247
+ '<code>echo $ANTHROPIC_API_KEY</code> — should start with <code>sk-ant-</code><br>' +
248
+ '<code>echo $ANTHROPIC_BASE_URL</code> — should be <code>${proxyBase}</code><br>' +
249
+ 'Then restart Claude Code in that shell.';
250
+ } else if (fiveXx.length >= apiCalls.length * 0.3 && fiveXx.length > 0) {
251
+ cls = 'warn';
252
+ html = '<div class="diag-title"><span class="icon">!</span>Upstream errors from Anthropic</div>' +
253
+ fiveXx.length + ' of ' + apiCalls.length + ' recent requests returned 5xx. Check <a href="https://status.anthropic.com" target="_blank" rel="noopener">status.anthropic.com</a>.';
254
+ } else if (ok.length > 0) {
255
+ // Healthy — hide the banner entirely
256
+ diag.className = 'diag';
257
+ diag.innerHTML = '';
258
+ return;
259
+ }
260
+
261
+ diag.className = 'diag show ' + cls;
262
+ diag.innerHTML = html;
263
+ }
264
+
265
+ function renderRecent(rec) {
266
+ const recBody = document.querySelector('#recent tbody');
267
+ const filtered = showNoise ? rec : rec.filter(isApiCall);
268
+
269
+ if (filtered.length === 0) {
270
+ const msg = rec.length === 0
271
+ ? 'No requests recorded yet.'
272
+ : 'No Anthropic API calls yet (' + rec.length + ' non-API probe' + (rec.length === 1 ? '' : 's') + ' hidden — click "Show probes" to view).';
273
+ recBody.innerHTML = '<tr><td colspan="8" class="muted">' + msg + '</td></tr>';
274
+ return;
275
+ }
276
+
277
+ recBody.innerHTML = filtered.map((r) => {
278
+ const status = Number(r.upstreamStatus) || 0;
279
+ const isOk = status >= 200 && status < 300;
280
+ const isAuthFail = status === 401 || status === 403;
281
+ const pillCls = isOk ? 'ok' : (isAuthFail ? 'warn' : 'err');
282
+ const isProbe = !isApiCall(r);
283
+ const modelDisplay = (r.model === 'unknown' && isProbe) ? '<span class="muted">—</span>' : r.model;
284
+ return '<tr class="' + (isProbe ? 'row-faded' : '') + '">' +
285
+ '<td class="muted">' + fmtTime(r.timestamp) + '</td>' +
286
+ '<td>' + modelDisplay + '</td>' +
287
+ '<td class="muted">' + r.endpoint + '</td>' +
288
+ '<td class="num">' + fmtNum(r.usageRaw.inputTokens) + '</td>' +
289
+ '<td class="num">' + fmtNum(r.usageRaw.outputTokens) + '</td>' +
290
+ '<td class="num muted">' + fmtMs(r.durationMs) + '</td>' +
291
+ '<td class="num">' + fmtDollars(r.dollarsRaw) + '</td>' +
292
+ '<td><span class="pill ' + pillCls + '">' + status + '</span></td>' +
293
+ '</tr>';
294
+ }).join('');
295
+ }
296
+
181
297
  async function refresh() {
182
298
  try {
183
299
  const [sumRes, recRes] = await Promise.all([
@@ -189,12 +305,19 @@ async function refresh() {
189
305
  }
190
306
  const sum = await sumRes.json();
191
307
  const rec = await recRes.json();
308
+ window.__lastRec = rec;
309
+
192
310
  // Successful refresh — clear any stale error and restore the live indicator
193
311
  const statusEl = document.getElementById('status-text');
194
312
  if (statusEl) statusEl.textContent = 'live · ${opts.bind}:${opts.proxyPort}';
195
313
 
314
+ // Count successful API calls for the spent card
315
+ const apiCalls = rec.filter(isApiCall);
316
+ const okCount = apiCalls.filter((r) => r.upstreamStatus >= 200 && r.upstreamStatus < 300).length;
317
+
196
318
  document.getElementById('dollars-spent').textContent = fmtDollars(sum.dollarsRaw);
197
- document.getElementById('request-count').textContent = fmtNum(sum.requestCount) + ' requests';
319
+ document.getElementById('request-count').textContent =
320
+ fmtNum(sum.requestCount) + ' total · ' + okCount + ' successful';
198
321
  document.getElementById('input-tokens').textContent = fmtNum(sum.totalInputTokensRaw);
199
322
  document.getElementById('output-tokens').textContent = fmtNum(sum.totalOutputTokensRaw);
200
323
 
@@ -203,32 +326,21 @@ async function refresh() {
203
326
  document.getElementById('weekly-projected').textContent = fmtDollars(weeklyProjection);
204
327
 
205
328
  const modelBody = document.querySelector('#by-model tbody');
206
- if (sum.byModel && sum.byModel.length > 0) {
207
- modelBody.innerHTML = sum.byModel.map((m) =>
329
+ const realModels = (sum.byModel || []).filter((m) => m.model && m.model !== 'unknown');
330
+ if (realModels.length > 0) {
331
+ modelBody.innerHTML = realModels.map((m) =>
208
332
  '<tr><td>' + m.model + '</td>' +
209
333
  '<td class="num">' + fmtNum(m.requests) + '</td>' +
210
334
  '<td class="num">' + fmtNum(m.inputTokens) + '</td>' +
211
335
  '<td class="num">' + fmtNum(m.outputTokens) + '</td>' +
212
336
  '<td class="num">' + fmtDollars(m.dollars) + '</td></tr>'
213
337
  ).join('');
338
+ } else {
339
+ modelBody.innerHTML = '<tr><td colspan="5" class="muted">No model traffic yet. Run Claude Code with <span class="kbd">ANTHROPIC_BASE_URL=${proxyBase}</span>.</td></tr>';
214
340
  }
215
341
 
216
- const recBody = document.querySelector('#recent tbody');
217
- if (rec && rec.length > 0) {
218
- recBody.innerHTML = rec.map((r) => {
219
- const ok = r.upstreamStatus >= 200 && r.upstreamStatus < 300;
220
- return '<tr>' +
221
- '<td class="muted">' + fmtTime(r.timestamp) + '</td>' +
222
- '<td>' + r.model + '</td>' +
223
- '<td class="muted">' + r.endpoint + '</td>' +
224
- '<td class="num">' + fmtNum(r.usageRaw.inputTokens) + '</td>' +
225
- '<td class="num">' + fmtNum(r.usageRaw.outputTokens) + '</td>' +
226
- '<td class="num muted">' + fmtMs(r.durationMs) + '</td>' +
227
- '<td class="num">' + fmtDollars(r.dollarsRaw) + '</td>' +
228
- '<td><span class="pill ' + (ok ? 'ok' : 'err') + '">' + r.upstreamStatus + '</span></td>' +
229
- '</tr>';
230
- }).join('');
231
- }
342
+ renderRecent(rec);
343
+ renderDiagnostic(rec);
232
344
  } catch (e) {
233
345
  document.getElementById('status-text').textContent = 'error: ' + e.message;
234
346
  }
@@ -1 +1 @@
1
- {"version":3,"file":"dashboard.js","sourceRoot":"","sources":["../src/dashboard.ts"],"names":[],"mappings":"AAAA,MAAM,UAAU,aAAa,CAAC,IAA0D;IACtF,MAAM,SAAS,GAAG,UAAU,IAAI,CAAC,IAAI,IAAI,IAAI,CAAC,SAAS,EAAE,CAAC;IAC1D,OAAO;;;;;qBAKY,IAAI,CAAC,OAAO;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iCAkFA,IAAI,CAAC,OAAO;;;;;;8CAMC,IAAI,CAAC,IAAI,IAAI,IAAI,CAAC,SAAS;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;2HAwCkD,SAAS;;;;;;;;;;;;;;;;;;;;;;;;;;wDA0B5E,SAAS;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;mDAgCd,IAAI,CAAC,IAAI,IAAI,IAAI,CAAC,SAAS;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;QA+CtE,CAAC;AACT,CAAC"}
1
+ {"version":3,"file":"dashboard.js","sourceRoot":"","sources":["../src/dashboard.ts"],"names":[],"mappings":"AAAA,MAAM,UAAU,aAAa,CAAC,IAA0D;IACtF,MAAM,SAAS,GAAG,UAAU,IAAI,CAAC,IAAI,IAAI,IAAI,CAAC,SAAS,EAAE,CAAC;IAC1D,OAAO;;;;;qBAKY,IAAI,CAAC,OAAO;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iCA8GA,IAAI,CAAC,OAAO;;;;;;;8CAOC,IAAI,CAAC,IAAI,IAAI,IAAI,CAAC,SAAS;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;2HA0CkD,SAAS;;;;;;;;;;;;;;;;;;;;;;;;;;;;;wDA6B5E,SAAS;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;+FA4C8B,SAAS;;;;;;;;iEAQvC,SAAS;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;mDAgEvB,IAAI,CAAC,IAAI,IAAI,IAAI,CAAC,SAAS;;;;;;;;;;;;;;;;;;;;;;;;;;;iJA2BmE,SAAS;;;;;;;;;;;;;;QAclJ,CAAC;AACT,CAAC"}
package/dist/version.d.ts CHANGED
@@ -1 +1 @@
1
- export declare const VERSION = "1.0.1";
1
+ export declare const VERSION = "1.0.2";
package/dist/version.js CHANGED
@@ -1,4 +1,4 @@
1
1
  // Single source of version truth for the CLI.
2
2
  // Keep in sync with version.txt via: npm run sync-version
3
- export const VERSION = "1.0.1";
3
+ export const VERSION = "1.0.2";
4
4
  //# sourceMappingURL=version.js.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@curatedmcp/tokenshield",
3
- "version": "1.0.1",
3
+ "version": "1.0.2",
4
4
  "description": "TokenShield CLI — cut your Claude Code bill 40–70%. Local proxy. Your API key never leaves your machine.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -32,7 +32,7 @@
32
32
  },
33
33
  "license": "MIT",
34
34
  "dependencies": {
35
- "@curatedmcp/tokenshield-core": "^1.0.1",
35
+ "@curatedmcp/tokenshield-core": "^1.0.2",
36
36
  "commander": "^12.0.0"
37
37
  },
38
38
  "devDependencies": {