tokentracker-cli 0.5.80 → 0.5.81
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/dashboard/dist/assets/{Card-Cv4eTIKD.js → Card-CvRCwmts.js} +1 -1
- package/dashboard/dist/assets/{DashboardPage-BLFOnvMn.js → DashboardPage-Q3m4isBc.js} +1 -1
- package/dashboard/dist/assets/{FadeIn-CIeY4GXM.js → FadeIn-7Sp96sG1.js} +1 -1
- package/dashboard/dist/assets/{IpCheckPage-IsYc44dj.js → IpCheckPage-CU5zi7km.js} +1 -1
- package/dashboard/dist/assets/{LeaderboardPage-Su4flsQc.js → LeaderboardPage-C9g0lTkN.js} +1 -1
- package/dashboard/dist/assets/{LeaderboardProfilePage-bt2zrvtb.js → LeaderboardProfilePage-U3Oww62u.js} +1 -1
- package/dashboard/dist/assets/{LimitsPage-CojL1PZS.js → LimitsPage-DW-j3hsV.js} +1 -1
- package/dashboard/dist/assets/{ProviderIcon-C2Qp69XI.js → ProviderIcon-BJl3TO7B.js} +1 -1
- package/dashboard/dist/assets/{SettingsPage-FSVM_ozY.js → SettingsPage-DnSupHC-.js} +1 -1
- package/dashboard/dist/assets/{WidgetsPage-CeLvw5tR.js → WidgetsPage-DwSoxQql.js} +1 -1
- package/dashboard/dist/assets/{download-BK4EqMpL.js → download-DtuvJNeF.js} +1 -1
- package/dashboard/dist/assets/{leaderboard-columns-CxdAz5_V.js → leaderboard-columns-B1_Q_WZh.js} +1 -1
- package/dashboard/dist/assets/{main-CPsqG3PW.js → main-MimZZsgW.js} +188 -188
- package/dashboard/dist/assets/{use-limits-display-prefs-C-Y8vFA9.js → use-limits-display-prefs-CPfPbjXC.js} +1 -1
- package/dashboard/dist/assets/{use-usage-limits-CiHD5lbg.js → use-usage-limits-BvCjl0IL.js} +1 -1
- package/dashboard/dist/index.html +1 -1
- package/dashboard/dist/share.html +1 -1
- package/package.json +1 -1
- package/src/commands/serve.js +14 -4
- package/src/lib/local-api.js +135 -26
- package/src/lib/static-server.js +0 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
import{r as a}from"./main-
|
|
1
|
+
import{r as a}from"./main-MimZZsgW.js";const d=["claude","codex","cursor","gemini","kiro","copilot","antigravity"],v={claude:"Claude",codex:"Codex",cursor:"Cursor",gemini:"Gemini",kiro:"Kiro",copilot:"GitHub Copilot",antigravity:"Antigravity"},k={claude:"/brand-logos/claude-code.svg",codex:"/brand-logos/codex.svg",cursor:"/brand-logos/cursor.svg",gemini:"/brand-logos/gemini.svg",kiro:"/brand-logos/kiro.svg",copilot:"/brand-logos/copilot.svg",antigravity:"/brand-logos/antigravity.svg"},l="tt.limits.providerOrder",g="tt.limits.providerVisibility";function y(){if(typeof window>"u")return[...d];try{const r=window.localStorage.getItem(l);if(!r)return[...d];const s=JSON.parse(r);if(!Array.isArray(s))return[...d];const n=s.filter(c=>d.includes(c));for(const c of d)n.includes(c)||n.push(c);return n}catch{return[...d]}}function m(){const r=Object.fromEntries(d.map(s=>[s,!0]));if(typeof window>"u")return r;try{const s=window.localStorage.getItem(g);if(!s)return r;const n=JSON.parse(s);if(!n||typeof n!="object")return r;const c={...r};for(const u of d)typeof n[u]=="boolean"&&(c[u]=n[u]);return c}catch{return r}}function C(){const[r,s]=a.useState(y),[n,c]=a.useState(m);a.useEffect(()=>{if(!(typeof window>"u"))try{window.localStorage.setItem(l,JSON.stringify(r))}catch{}},[r]),a.useEffect(()=>{if(!(typeof window>"u"))try{window.localStorage.setItem(g,JSON.stringify(n))}catch{}},[n]),a.useEffect(()=>{if(typeof window>"u")return;const o=t=>{t.key===l&&s(y()),t.key===g&&c(m())};return window.addEventListener("storage",o),()=>window.removeEventListener("storage",o)},[]);const u=a.useCallback(o=>{c(t=>({...t,[o]:!t[o]}))},[]),b=a.useCallback(o=>{s(t=>{const e=t.indexOf(o);if(e<=0)return t;const i=[...t];return[i[e-1],i[e]]=[i[e],i[e-1]],i})},[]),p=a.useCallback(o=>{s(t=>{const e=t.indexOf(o);if(e<0||e>=t.length-1)return t;const i=[...t];return[i[e],i[e+1]]=[i[e+1],i[e]],i})},[]),O=a.useCallback((o,t)=>{o!==t&&s(e=>{const i=e.indexOf(o),w=e.indexOf(t);if(i<0||w<0)return e;const f=[...e],[E]=f.splice(i,1);return f.splice(w,0,E),f})},[]),x=a.useCallback(()=>{s([...d]),c(Object.fromEntries(d.map(o=>[o,!0])))},[]),I=a.useMemo(()=>r.filter(o=>n[o]!==!1),[r,n]);return{order:r,visibility:n,visibleOrdered:I,toggle:u,moveUp:b,moveDown:p,moveToward:O,reset:x}}export{k as L,v as a,C as u};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{r,aM as l}from"./main-
|
|
1
|
+
import{r,aM as l}from"./main-MimZZsgW.js";function y(i){const[o,a]=r.useState(null),[u,s]=r.useState(null),[c,f]=r.useState(!0),n=!!i?.initialRefresh,g=r.useCallback(async()=>{try{const e=await l({refresh:!0});a(e&&typeof e=="object"?e:null),s(null)}catch(e){s(e?.message||String(e))}},[]);return r.useEffect(()=>{let e=!1;return(async()=>{try{const t=await l(n?{refresh:!0}:{});if(e)return;a(t&&typeof t=="object"?t:null),s(null)}catch(t){if(e)return;s(t?.message||String(t))}finally{e||f(!1)}})(),()=>{e=!0}},[n]),{data:o,error:u,isLoading:c,refresh:g}}export{y as u};
|
|
@@ -135,7 +135,7 @@
|
|
|
135
135
|
]
|
|
136
136
|
}
|
|
137
137
|
</script>
|
|
138
|
-
<script type="module" crossorigin src="/assets/main-
|
|
138
|
+
<script type="module" crossorigin src="/assets/main-MimZZsgW.js"></script>
|
|
139
139
|
<link rel="stylesheet" crossorigin href="/assets/main-DRf20yyJ.css">
|
|
140
140
|
</head>
|
|
141
141
|
<body>
|
|
@@ -51,7 +51,7 @@
|
|
|
51
51
|
"description": "Shareable Token Tracker dashboard snapshot."
|
|
52
52
|
}
|
|
53
53
|
</script>
|
|
54
|
-
<script type="module" crossorigin src="/assets/main-
|
|
54
|
+
<script type="module" crossorigin src="/assets/main-MimZZsgW.js"></script>
|
|
55
55
|
<link rel="stylesheet" crossorigin href="/assets/main-DRf20yyJ.css">
|
|
56
56
|
</head>
|
|
57
57
|
<body>
|
package/package.json
CHANGED
package/src/commands/serve.js
CHANGED
|
@@ -11,11 +11,16 @@ const { openInBrowser } = require("../lib/browser-auth");
|
|
|
11
11
|
|
|
12
12
|
const DEFAULT_PORT = 7680;
|
|
13
13
|
const NPM_PACKAGE_NAME = "tokentracker-cli";
|
|
14
|
+
const LOCAL_BIND_HOST = "127.0.0.1";
|
|
14
15
|
|
|
15
16
|
function buildPortInUseHint(port) {
|
|
16
17
|
return `Port ${port} is still in use after cleanup. Try: npx ${NPM_PACKAGE_NAME} serve --port ${port + 1}\n`;
|
|
17
18
|
}
|
|
18
19
|
|
|
20
|
+
function getLocalServerUrl(port) {
|
|
21
|
+
return `http://${LOCAL_BIND_HOST}:${port}`;
|
|
22
|
+
}
|
|
23
|
+
|
|
19
24
|
async function cmdServe(argv) {
|
|
20
25
|
const opts = parseArgs(argv);
|
|
21
26
|
|
|
@@ -85,7 +90,6 @@ async function cmdServe(argv) {
|
|
|
85
90
|
// CORS preflight
|
|
86
91
|
if (req.method === "OPTIONS") {
|
|
87
92
|
res.writeHead(204, {
|
|
88
|
-
"Access-Control-Allow-Origin": "*",
|
|
89
93
|
"Access-Control-Allow-Methods": "GET, POST, OPTIONS",
|
|
90
94
|
"Access-Control-Allow-Headers": "Content-Type, Authorization",
|
|
91
95
|
});
|
|
@@ -116,8 +120,8 @@ async function cmdServe(argv) {
|
|
|
116
120
|
// 4. Listen (kill stale process on same port if needed)
|
|
117
121
|
const port = opts.port;
|
|
118
122
|
await ensurePortFree(port);
|
|
119
|
-
server.listen(port, () => {
|
|
120
|
-
const url =
|
|
123
|
+
server.listen(port, LOCAL_BIND_HOST, () => {
|
|
124
|
+
const url = getLocalServerUrl(port);
|
|
121
125
|
process.stdout.write(
|
|
122
126
|
[
|
|
123
127
|
"",
|
|
@@ -226,4 +230,10 @@ function parseArgs(argv) {
|
|
|
226
230
|
return opts;
|
|
227
231
|
}
|
|
228
232
|
|
|
229
|
-
module.exports = {
|
|
233
|
+
module.exports = {
|
|
234
|
+
cmdServe,
|
|
235
|
+
buildPortInUseHint,
|
|
236
|
+
NPM_PACKAGE_NAME,
|
|
237
|
+
LOCAL_BIND_HOST,
|
|
238
|
+
getLocalServerUrl,
|
|
239
|
+
};
|
package/src/lib/local-api.js
CHANGED
|
@@ -2,6 +2,8 @@ const fs = require("node:fs");
|
|
|
2
2
|
const os = require("node:os");
|
|
3
3
|
const path = require("node:path");
|
|
4
4
|
const { spawn } = require("node:child_process");
|
|
5
|
+
const crypto = require("node:crypto");
|
|
6
|
+
const { DEFAULT_BASE_URL, resolveRuntimeConfig } = require("./runtime-config");
|
|
5
7
|
|
|
6
8
|
const SYNC_TIMEOUT_MS = 120_000;
|
|
7
9
|
const TRACKER_BIN = path.resolve(__dirname, "../../bin/tracker.js");
|
|
@@ -159,6 +161,29 @@ function readProjectQueueData(projectQueuePath) {
|
|
|
159
161
|
return Array.from(seen.values());
|
|
160
162
|
}
|
|
161
163
|
|
|
164
|
+
function isLegacyInclusiveCodexRow(row) {
|
|
165
|
+
if (!row || (row.source !== "codex" && row.source !== "every-code")) return false;
|
|
166
|
+
const inputTokens = Number(row.input_tokens || 0);
|
|
167
|
+
const cachedInputTokens = Number(row.cached_input_tokens || 0);
|
|
168
|
+
const outputTokens = Number(row.output_tokens || 0);
|
|
169
|
+
const totalTokens = Number(row.total_tokens || 0);
|
|
170
|
+
if (!Number.isFinite(inputTokens) || !Number.isFinite(cachedInputTokens)) return false;
|
|
171
|
+
if (cachedInputTokens <= 0 || inputTokens < cachedInputTokens) return false;
|
|
172
|
+
// Legacy Codex queue rows stored input inclusive of cache reads, while
|
|
173
|
+
// total_tokens remained input + output. Canonical rows keep input as pure
|
|
174
|
+
// non-cached input, so cache-heavy legacy rows can be identified by this
|
|
175
|
+
// exact invariant.
|
|
176
|
+
return totalTokens === inputTokens + outputTokens;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
function normalizeQueueRow(row) {
|
|
180
|
+
if (!isLegacyInclusiveCodexRow(row)) return row;
|
|
181
|
+
return {
|
|
182
|
+
...row,
|
|
183
|
+
input_tokens: Number(row.input_tokens || 0) - Number(row.cached_input_tokens || 0),
|
|
184
|
+
};
|
|
185
|
+
}
|
|
186
|
+
|
|
162
187
|
function readQueueData(queuePath) {
|
|
163
188
|
let raw;
|
|
164
189
|
try {
|
|
@@ -194,7 +219,7 @@ function readQueueData(queuePath) {
|
|
|
194
219
|
const seen = new Map();
|
|
195
220
|
for (const row of parsed) {
|
|
196
221
|
const key = `${row.source || ""}|${row.model || ""}|${row.hour_start || ""}`;
|
|
197
|
-
seen.set(key, row);
|
|
222
|
+
seen.set(key, normalizeQueueRow(row));
|
|
198
223
|
}
|
|
199
224
|
return Array.from(seen.values());
|
|
200
225
|
}
|
|
@@ -361,6 +386,67 @@ function trimOutput(value, max = 4000) {
|
|
|
361
386
|
return t.length <= max ? t : t.slice(t.length - max);
|
|
362
387
|
}
|
|
363
388
|
|
|
389
|
+
function normalizeRemoteHttpBaseUrl(value) {
|
|
390
|
+
if (typeof value !== "string") return null;
|
|
391
|
+
const trimmed = value.trim();
|
|
392
|
+
if (!trimmed) return null;
|
|
393
|
+
try {
|
|
394
|
+
const url = new URL(trimmed);
|
|
395
|
+
if (url.protocol !== "http:" && url.protocol !== "https:") return null;
|
|
396
|
+
url.username = "";
|
|
397
|
+
url.password = "";
|
|
398
|
+
url.hash = "";
|
|
399
|
+
return url.toString().replace(/\/$/, "");
|
|
400
|
+
} catch (_e) {
|
|
401
|
+
return null;
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
function resolveAllowedInsforgeBaseUrl(value) {
|
|
406
|
+
const requested = normalizeRemoteHttpBaseUrl(value);
|
|
407
|
+
if (!requested) return null;
|
|
408
|
+
|
|
409
|
+
const runtime = resolveRuntimeConfig();
|
|
410
|
+
const allowed = new Set(
|
|
411
|
+
[runtime.baseUrl, DEFAULT_BASE_URL]
|
|
412
|
+
.map((entry) => normalizeRemoteHttpBaseUrl(entry))
|
|
413
|
+
.filter(Boolean),
|
|
414
|
+
);
|
|
415
|
+
|
|
416
|
+
return allowed.has(requested) ? requested : null;
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
function parseCookieHeader(value) {
|
|
420
|
+
const out = new Map();
|
|
421
|
+
if (typeof value !== "string" || !value.trim()) return out;
|
|
422
|
+
for (const part of value.split(";")) {
|
|
423
|
+
const idx = part.indexOf("=");
|
|
424
|
+
if (idx < 1) continue;
|
|
425
|
+
const key = part.slice(0, idx).trim();
|
|
426
|
+
const rawValue = part.slice(idx + 1).trim();
|
|
427
|
+
if (key) out.set(key, rawValue);
|
|
428
|
+
}
|
|
429
|
+
return out;
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
function isLoopbackHostname(hostname) {
|
|
433
|
+
return hostname === "127.0.0.1" || hostname === "localhost" || hostname === "::1" || hostname === "[::1]";
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
function hasAllowedLoopbackOrigin(headers = {}) {
|
|
437
|
+
const candidates = [headers.origin, headers.referer];
|
|
438
|
+
for (const raw of candidates) {
|
|
439
|
+
if (raw == null || raw === "") continue;
|
|
440
|
+
try {
|
|
441
|
+
const url = new URL(String(raw));
|
|
442
|
+
if (url.protocol !== "http:" || !isLoopbackHostname(url.hostname)) return false;
|
|
443
|
+
} catch (_e) {
|
|
444
|
+
return false;
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
return true;
|
|
448
|
+
}
|
|
449
|
+
|
|
364
450
|
function readJsonBody(req) {
|
|
365
451
|
return new Promise((resolve, reject) => {
|
|
366
452
|
const chunks = [];
|
|
@@ -526,7 +612,7 @@ function scanClaudeProjects(projectMap) {
|
|
|
526
612
|
// ---------------------------------------------------------------------------
|
|
527
613
|
|
|
528
614
|
function json(res, data, status) {
|
|
529
|
-
res.writeHead(status || 200, { "Content-Type": "application/json"
|
|
615
|
+
res.writeHead(status || 200, { "Content-Type": "application/json" });
|
|
530
616
|
res.end(JSON.stringify(data));
|
|
531
617
|
}
|
|
532
618
|
|
|
@@ -541,6 +627,7 @@ function createLocalApiHandler({ queuePath }) {
|
|
|
541
627
|
// so that both browser and WKWebView share the same login session via the proxy.
|
|
542
628
|
// Persisted to disk so cookies survive server restarts.
|
|
543
629
|
let relayCookies = new Map();
|
|
630
|
+
const localAuthToken = crypto.randomBytes(24).toString("hex");
|
|
544
631
|
const trackerDataDir = path.join(os.homedir(), ".tokentracker", "tracker");
|
|
545
632
|
const cookiePath = path.join(trackerDataDir, "relay-cookies.json");
|
|
546
633
|
|
|
@@ -653,13 +740,40 @@ function createLocalApiHandler({ queuePath }) {
|
|
|
653
740
|
let _nativeAuthPending = false;
|
|
654
741
|
let _nativeAuthExpiry = 0;
|
|
655
742
|
|
|
743
|
+
function isAuthorizedLocalMutation(req) {
|
|
744
|
+
const headerToken = req?.headers?.["x-tokentracker-local-auth"];
|
|
745
|
+
const cookieToken = parseCookieHeader(req?.headers?.cookie).get("tokentracker_local_auth");
|
|
746
|
+
const token = typeof headerToken === "string" && headerToken.trim()
|
|
747
|
+
? headerToken.trim()
|
|
748
|
+
: cookieToken || "";
|
|
749
|
+
if (!token || token !== localAuthToken) return false;
|
|
750
|
+
return hasAllowedLoopbackOrigin(req?.headers || {});
|
|
751
|
+
}
|
|
752
|
+
|
|
656
753
|
return async function handleLocalApi(req, res, url) {
|
|
657
754
|
const p = url.pathname;
|
|
658
755
|
|
|
756
|
+
if (p === "/api/local-auth") {
|
|
757
|
+
if (String(req.method || "GET").toUpperCase() !== "GET") {
|
|
758
|
+
json(res, { error: "Method Not Allowed" }, 405);
|
|
759
|
+
return true;
|
|
760
|
+
}
|
|
761
|
+
res.writeHead(200, {
|
|
762
|
+
"Content-Type": "application/json",
|
|
763
|
+
"Cache-Control": "no-store",
|
|
764
|
+
});
|
|
765
|
+
res.end(JSON.stringify({ token: localAuthToken }));
|
|
766
|
+
return true;
|
|
767
|
+
}
|
|
768
|
+
|
|
659
769
|
// --- Auth bridge: native OAuth flag (WebView ↔ system browser) ---
|
|
660
770
|
if (p === "/api/auth-bridge/verifier") {
|
|
661
771
|
const method = String(req.method || "GET").toUpperCase();
|
|
662
772
|
if (method === "PUT" || method === "POST") {
|
|
773
|
+
if (!isAuthorizedLocalMutation(req)) {
|
|
774
|
+
json(res, { error: "Unauthorized" }, 401);
|
|
775
|
+
return true;
|
|
776
|
+
}
|
|
663
777
|
const body = await readJsonBody(req);
|
|
664
778
|
_nativeAuthPending = Boolean(body?.native);
|
|
665
779
|
_nativeAuthExpiry = Date.now() + 5 * 60 * 1000; // 5 min TTL
|
|
@@ -679,20 +793,8 @@ function createLocalApiHandler({ queuePath }) {
|
|
|
679
793
|
|
|
680
794
|
// --- auth proxy: forward /api/auth/* to InsForge cloud ---
|
|
681
795
|
if (p.startsWith("/api/auth/")) {
|
|
682
|
-
const
|
|
683
|
-
|
|
684
|
-
|| process.env.INSFORGE_BASE_URL
|
|
685
|
-
|| "";
|
|
686
|
-
if (!insforgeBase) {
|
|
687
|
-
try {
|
|
688
|
-
const cfgPath = path.join(os.homedir(), ".tokentracker", "tracker", "config.json");
|
|
689
|
-
const cfg = JSON.parse(fs.readFileSync(cfgPath, "utf8"));
|
|
690
|
-
insforgeBase = cfg?.baseUrl || "";
|
|
691
|
-
} catch { /* ignore */ }
|
|
692
|
-
}
|
|
693
|
-
if (!insforgeBase) {
|
|
694
|
-
insforgeBase = DEFAULT_BASE_URL;
|
|
695
|
-
}
|
|
796
|
+
const runtime = resolveRuntimeConfig();
|
|
797
|
+
const insforgeBase = runtime.baseUrl || DEFAULT_BASE_URL;
|
|
696
798
|
try {
|
|
697
799
|
const targetUrl = `${insforgeBase.replace(/\/$/, "")}${p}${url.search || ""}`;
|
|
698
800
|
const proxyHeaders = {};
|
|
@@ -758,6 +860,10 @@ function createLocalApiHandler({ queuePath }) {
|
|
|
758
860
|
json(res, { ok: false, error: "Method Not Allowed" }, 405);
|
|
759
861
|
return true;
|
|
760
862
|
}
|
|
863
|
+
if (!isAuthorizedLocalMutation(req)) {
|
|
864
|
+
json(res, { ok: false, error: "Unauthorized" }, 401);
|
|
865
|
+
return true;
|
|
866
|
+
}
|
|
761
867
|
try {
|
|
762
868
|
let body = {};
|
|
763
869
|
try {
|
|
@@ -769,8 +875,13 @@ function createLocalApiHandler({ queuePath }) {
|
|
|
769
875
|
if (typeof body.deviceToken === "string" && body.deviceToken.trim()) {
|
|
770
876
|
extraEnv.TOKENTRACKER_DEVICE_TOKEN = body.deviceToken.trim();
|
|
771
877
|
}
|
|
772
|
-
if (
|
|
773
|
-
|
|
878
|
+
if (body.insforgeBaseUrl != null) {
|
|
879
|
+
const allowedBaseUrl = resolveAllowedInsforgeBaseUrl(body.insforgeBaseUrl);
|
|
880
|
+
if (!allowedBaseUrl) {
|
|
881
|
+
json(res, { ok: false, error: "Unsupported insforgeBaseUrl override" }, 400);
|
|
882
|
+
return true;
|
|
883
|
+
}
|
|
884
|
+
extraEnv.TOKENTRACKER_INSFORGE_BASE_URL = allowedBaseUrl;
|
|
774
885
|
}
|
|
775
886
|
const result = await runSyncCommand(extraEnv);
|
|
776
887
|
try {
|
|
@@ -949,14 +1060,11 @@ function createLocalApiHandler({ queuePath }) {
|
|
|
949
1060
|
const sources = Array.from(bySource.values()).map((s) => {
|
|
950
1061
|
s.models = Array.from(s.models.values())
|
|
951
1062
|
.map((m) => {
|
|
952
|
-
const
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
(m.totals.cache_creation_input_tokens || 0) * (p.cache_write || 0) +
|
|
958
|
-
(m.totals.reasoning_output_tokens || 0) * (p.output || 0)) /
|
|
959
|
-
1_000_000;
|
|
1063
|
+
const cost = computeRowCost({
|
|
1064
|
+
...m.totals,
|
|
1065
|
+
model: m.model,
|
|
1066
|
+
source: s.source,
|
|
1067
|
+
});
|
|
960
1068
|
return { ...m, totals: { ...m.totals, total_cost_usd: cost.toFixed(6) } };
|
|
961
1069
|
})
|
|
962
1070
|
.sort((a, b) => b.totals.total_tokens - a.totals.total_tokens);
|
|
@@ -1119,6 +1227,7 @@ function createLocalApiHandler({ queuePath }) {
|
|
|
1119
1227
|
|
|
1120
1228
|
module.exports = {
|
|
1121
1229
|
createLocalApiHandler,
|
|
1230
|
+
resolveAllowedInsforgeBaseUrl,
|
|
1122
1231
|
resolveQueuePath,
|
|
1123
1232
|
// Exported for cross-consumer tests (pricing + native contract lock).
|
|
1124
1233
|
MODEL_PRICING,
|
package/src/lib/static-server.js
CHANGED
|
@@ -46,7 +46,6 @@ async function serveStaticFile(baseDir, pathname, res) {
|
|
|
46
46
|
"Content-Type": contentType,
|
|
47
47
|
"Content-Length": stat.size,
|
|
48
48
|
"Cache-Control": isHtml ? "no-cache" : "public, max-age=31536000, immutable",
|
|
49
|
-
"Access-Control-Allow-Origin": "*",
|
|
50
49
|
});
|
|
51
50
|
|
|
52
51
|
const stream = fs.createReadStream(filePath);
|