tokentracker-cli 0.48.0 → 0.49.0

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.
Files changed (53) hide show
  1. package/README.ja.md +3 -1
  2. package/README.ko.md +3 -1
  3. package/README.md +3 -1
  4. package/README.zh-CN.md +3 -1
  5. package/dashboard/dist/assets/{ActivityHeatmap-BW7r63JV.js → ActivityHeatmap-CItg7FNN.js} +1 -1
  6. package/dashboard/dist/assets/{Card-BWgji0yz.js → Card-B9jWqeQm.js} +1 -1
  7. package/dashboard/dist/assets/{DashboardPage-fwmRrxV3.js → DashboardPage-D-UdQdmb.js} +1 -1
  8. package/dashboard/dist/assets/{DevicePage-rxxuM-iC.js → DevicePage-BmhKFgDo.js} +1 -1
  9. package/dashboard/dist/assets/{DialogTitle-ltZ7viR5.js → DialogTitle-Dcuz0ACc.js} +1 -1
  10. package/dashboard/dist/assets/{FadeIn-DkGtOY3l.js → FadeIn-B-oMQCv_.js} +1 -1
  11. package/dashboard/dist/assets/{HeaderGithubStar-CUKVYH9j.js → HeaderGithubStar-C6x-l5U0.js} +1 -1
  12. package/dashboard/dist/assets/IpCheckPage-D1tltH7T.js +20 -0
  13. package/dashboard/dist/assets/{LandingPage-Y2ovBPVG.js → LandingPage-DnjQLNxt.js} +1 -1
  14. package/dashboard/dist/assets/{LeaderboardAvatar-DSGfEE93.js → LeaderboardAvatar-PNgWQxaR.js} +1 -1
  15. package/dashboard/dist/assets/{LeaderboardPage-BX4cvtg7.js → LeaderboardPage-DRCSwReV.js} +3 -3
  16. package/dashboard/dist/assets/{LeaderboardProfileModal-CJfefuSR.js → LeaderboardProfileModal-DvbGnWDm.js} +1 -1
  17. package/dashboard/dist/assets/{LeaderboardProfilePage-Xy0K-ppJ.js → LeaderboardProfilePage-Bmk16GFd.js} +1 -1
  18. package/dashboard/dist/assets/{LimitsPage-utdNxAg_.js → LimitsPage-DsdyKs5Z.js} +1 -1
  19. package/dashboard/dist/assets/{LocalOnlyNotice-CwGk0Fn3.js → LocalOnlyNotice-CIa2X9wJ.js} +1 -1
  20. package/dashboard/dist/assets/{LoginPage-BHlX2ZHJ.js → LoginPage-CnDLx50g.js} +1 -1
  21. package/dashboard/dist/assets/{PopoverPopup-BC4rHujH.js → PopoverPopup-ZsSFlvKU.js} +1 -1
  22. package/dashboard/dist/assets/{Select-BpDJKpfl.js → Select-CY2FgOFv.js} +1 -1
  23. package/dashboard/dist/assets/{SelectItemText-D1_H3hHS.js → SelectItemText-DI_GTNc2.js} +1 -1
  24. package/dashboard/dist/assets/{SettingsPage-DCVw-3Cv.js → SettingsPage-D1DZkCMo.js} +1 -1
  25. package/dashboard/dist/assets/{SkillsPage-B-9_Ufpw.js → SkillsPage-DorZCQ0W.js} +1 -1
  26. package/dashboard/dist/assets/{WidgetsPage-DJ4i97QU.js → WidgetsPage-BDLa_UaU.js} +1 -1
  27. package/dashboard/dist/assets/{WrappedPage-W8pPcYr2.js → WrappedPage-BKn5Q7iM.js} +1 -1
  28. package/dashboard/dist/assets/{agent-logos-yJi-7lhN.js → agent-logos-CM4Rt9bu.js} +1 -1
  29. package/dashboard/dist/assets/{arrow-up-right-BXhFthH9.js → arrow-up-right-jP0XdZdv.js} +1 -1
  30. package/dashboard/dist/assets/{download-D32KO7Ua.js → download-D8Hvx1kr.js} +1 -1
  31. package/dashboard/dist/assets/{info-C8QLdyUg.js → info-vv2jU1_C.js} +1 -1
  32. package/dashboard/dist/assets/{main-BmTIgbZL.js → main-9P4Ny5Xr.js} +15 -15
  33. package/dashboard/dist/assets/main-Br5SsufY.css +1 -0
  34. package/dashboard/dist/assets/{use-limits-display-prefs-DcmWcNeA.js → use-limits-display-prefs-BZVYlv9P.js} +1 -1
  35. package/dashboard/dist/assets/{use-native-settings-BLZi1heD.js → use-native-settings-CR_f7uLH.js} +1 -1
  36. package/dashboard/dist/assets/use-usage-limits-CvgpaBd_.js +1 -0
  37. package/dashboard/dist/assets/{useCurrency-ERbWumqM.js → useCurrency-Cq0FIlJZ.js} +1 -1
  38. package/dashboard/dist/assets/{useScrollLock-PJWfD6AS.js → useScrollLock-B5U1SJQV.js} +1 -1
  39. package/dashboard/dist/brand-logos/hermes.svg +1 -1
  40. package/dashboard/dist/index.html +2 -2
  41. package/dashboard/dist/share.html +2 -2
  42. package/package.json +1 -1
  43. package/src/commands/device-login.js +19 -3
  44. package/src/commands/sync.js +538 -348
  45. package/src/lib/local-api.js +11 -2
  46. package/src/lib/pricing/curated-overrides.json +2 -1
  47. package/src/lib/pricing/seed-snapshot.json +1 -1
  48. package/src/lib/rollout.js +98 -16
  49. package/src/lib/usage-limits.js +118 -25
  50. package/src/lib/wrapped-aggregator.js +12 -1
  51. package/dashboard/dist/assets/IpCheckPage-DqrupqCq.js +0 -15
  52. package/dashboard/dist/assets/main-BfK9LoKV.css +0 -1
  53. package/dashboard/dist/assets/use-usage-limits-BIJk3Nmb.js +0 -1
@@ -2649,13 +2649,32 @@ async function parseCursorApiIncremental({
2649
2649
  const total = records.length;
2650
2650
 
2651
2651
  if (records.length > 0) {
2652
- for (const [key, bucket] of Object.entries(hourlyState.buckets || {})) {
2653
- const parsed = parseBucketKey(key);
2654
- const sourceKey = normalizeSourceInput(parsed.source) || DEFAULT_SOURCE;
2655
- if (sourceKey !== defaultSource) continue;
2656
- if (!bucket?.totals) continue;
2657
- bucket.totals = initTotals();
2658
- touchedBuckets.add(key);
2652
+ // Guard (2026-06 audit): only wipe buckets the fetched export can
2653
+ // actually rebuild. The wipe-then-refill design assumes the CSV is a
2654
+ // FULL-history export; if Cursor ever windows or truncates the export,
2655
+ // unconditionally zeroing every bucket would erase (and upload zeros
2656
+ // over) all history older than the response. Wiping from the earliest
2657
+ // record onward is identical for full exports and fail-safe for
2658
+ // partial ones.
2659
+ let earliestBucketStart = null;
2660
+ for (const record of records) {
2661
+ if (!record?.date) continue;
2662
+ const b = toUtcHalfHourStart(record.date);
2663
+ if (b && (!earliestBucketStart || b < earliestBucketStart)) earliestBucketStart = b;
2664
+ }
2665
+ // No parseable record date at all means the export is malformed —
2666
+ // refilling would add nothing, so wiping would zero out (and upload
2667
+ // zeros over) the entire history. Skip the wipe entirely.
2668
+ if (earliestBucketStart) {
2669
+ for (const [key, bucket] of Object.entries(hourlyState.buckets || {})) {
2670
+ const parsed = parseBucketKey(key);
2671
+ const sourceKey = normalizeSourceInput(parsed.source) || DEFAULT_SOURCE;
2672
+ if (sourceKey !== defaultSource) continue;
2673
+ if (!bucket?.totals) continue;
2674
+ if (parsed.hourStart && parsed.hourStart < earliestBucketStart) continue;
2675
+ bucket.totals = initTotals();
2676
+ touchedBuckets.add(key);
2677
+ }
2659
2678
  }
2660
2679
  }
2661
2680
 
@@ -3106,7 +3125,7 @@ function readHermesSessions(dbPath, lastCompletedEpoch, unfinishedSessionIds = [
3106
3125
  // in-progress (ended_at IS NULL), OR sessions that were previously observed
3107
3126
  // unfinished. Hermes updates token counts in real-time, including a final
3108
3127
  // delta when an active session later gets ended_at set.
3109
- const sql = `SELECT id, model, started_at, ended_at, input_tokens, output_tokens, cache_read_tokens, cache_write_tokens, reasoning_tokens, message_count FROM sessions WHERE (started_at >= ${since} OR ended_at IS NULL${forceIncludeSql}) AND (input_tokens > 0 OR output_tokens > 0 OR cache_read_tokens > 0 OR reasoning_tokens > 0) ORDER BY started_at ASC`;
3128
+ const sql = `SELECT id, model, started_at, ended_at, input_tokens, output_tokens, cache_read_tokens, cache_write_tokens, reasoning_tokens, message_count FROM sessions WHERE (started_at >= ${since} OR ended_at IS NULL${forceIncludeSql}) AND (input_tokens > 0 OR output_tokens > 0 OR cache_read_tokens > 0 OR cache_write_tokens > 0 OR reasoning_tokens > 0) ORDER BY started_at ASC`;
3110
3129
 
3111
3130
  let snapshot = null;
3112
3131
  let effectiveDbPath = dbPath;
@@ -3196,7 +3215,13 @@ async function parseHermesIncremental({ hermesPath, dbPath, cursors, queuePath,
3196
3215
  const cacheWrite = toNonNegativeInt(row.cache_write_tokens);
3197
3216
  const reasoning = toNonNegativeInt(row.reasoning_tokens);
3198
3217
  const messageCount = toNonNegativeInt(row.message_count);
3199
- if (inputTokens === 0 && outputTokens === 0 && cacheRead === 0 && reasoning === 0) continue;
3218
+ if (
3219
+ inputTokens === 0 &&
3220
+ outputTokens === 0 &&
3221
+ cacheRead === 0 &&
3222
+ cacheWrite === 0 &&
3223
+ reasoning === 0
3224
+ ) continue;
3200
3225
 
3201
3226
  // Save current snapshot for next sync
3202
3227
  nextSnapshots[row.id] = { in: inputTokens, out: outputTokens, cacheRead, cacheWrite, reasoning, message_count: messageCount };
@@ -3880,13 +3905,26 @@ async function parseKiroCliIncremental({ sessionFiles, cursors, queuePath, onPro
3880
3905
  const updatedAt = new Date().toISOString();
3881
3906
  hourlyState.updatedAt = updatedAt;
3882
3907
  cursors.hourly = hourlyState;
3883
- cursors.kiroCli = { ...kiroCliState, requests: cappedEarly, updatedAt };
3908
+ cursors.kiroCli = {
3909
+ ...kiroCliState,
3910
+ requests: cappedEarly.requests,
3911
+ watermarkMs: Math.max(Number(kiroCliState.watermarkMs) || 0, cappedEarly.watermarkMs),
3912
+ updatedAt,
3913
+ };
3884
3914
  return { recordsProcessed: 0, eventsAggregated: 0, bucketsQueued };
3885
3915
  }
3886
3916
  const cb = typeof onProgress === "function" ? onProgress : null;
3887
3917
  let recordsProcessed = 0;
3888
3918
  let eventsAggregated = 0;
3889
3919
 
3920
+ // 2026-06 audit fix: requests older than the persisted prune watermark were
3921
+ // already counted once and had their cursor entry pruned by
3922
+ // clampAndCapKiroCliState — re-processing them (prev === undefined) re-ADDED
3923
+ // their tokens to the same bucket on every sync, inflating old buckets
3924
+ // without bound. Skip them; the watermark only ever advances, and starts at
3925
+ // 0 so a first-ever parse still ingests the full DB history.
3926
+ const kiroCliWatermarkMs = Number(kiroCliState.watermarkMs) || 0;
3927
+
3890
3928
  for (let i = 0; i < flat.length; i++) {
3891
3929
  const r = flat[i];
3892
3930
  recordsProcessed++;
@@ -3901,6 +3939,7 @@ async function parseKiroCliIncremental({ sessionFiles, cursors, queuePath, onPro
3901
3939
 
3902
3940
  const tsMs = Number(r.request_start_timestamp_ms);
3903
3941
  if (!Number.isFinite(tsMs) || tsMs <= 0) continue;
3942
+ if (tsMs < kiroCliWatermarkMs) continue;
3904
3943
  const bucketStart = toUtcHalfHourStart(new Date(tsMs).toISOString());
3905
3944
  if (!bucketStart) continue;
3906
3945
 
@@ -3982,7 +4021,12 @@ async function parseKiroCliIncremental({ sessionFiles, cursors, queuePath, onPro
3982
4021
  const updatedAt = new Date().toISOString();
3983
4022
  hourlyState.updatedAt = updatedAt;
3984
4023
  cursors.hourly = hourlyState;
3985
- cursors.kiroCli = { ...kiroCliState, requests: cappedState, updatedAt };
4024
+ cursors.kiroCli = {
4025
+ ...kiroCliState,
4026
+ requests: cappedState.requests,
4027
+ watermarkMs: Math.max(kiroCliWatermarkMs, cappedState.watermarkMs),
4028
+ updatedAt,
4029
+ };
3986
4030
 
3987
4031
  return { recordsProcessed, eventsAggregated, bucketsQueued };
3988
4032
  }
@@ -4008,6 +4052,14 @@ function clampAndCapKiroCliState({ requestState, hourlyState, touchedBuckets })
4008
4052
  }
4009
4053
  // TASK-004: cap cursors.kiroCli.requests by age + count. Runs LAST so
4010
4054
  // nothing active or just-retracted is pruned mid-flight.
4055
+ //
4056
+ // 2026-06 audit fix: pruning an entry while readKiroCliRequests has no
4057
+ // time floor meant the same request came back next sync with
4058
+ // `prev === undefined` and was re-ADDED to its (old) bucket — every sync,
4059
+ // forever. The returned watermarkMs records how far this prune reached;
4060
+ // the parse loop skips any request older than the persisted watermark, so
4061
+ // a pruned request can never be re-counted. First-ever parse still counts
4062
+ // arbitrarily old history (watermark starts at 0).
4011
4063
  const ageCutoffMs = Date.now() - KIRO_CLI_CURSOR_MAX_AGE_MS;
4012
4064
  const cappedEntries = [];
4013
4065
  for (const [reqId, entry] of Object.entries(requestState)) {
@@ -4016,13 +4068,22 @@ function clampAndCapKiroCliState({ requestState, hourlyState, touchedBuckets })
4016
4068
  if (!Number.isFinite(ts) || ts < ageCutoffMs) continue;
4017
4069
  cappedEntries.push([reqId, entry, ts]);
4018
4070
  }
4071
+ // +30min margin: entries are pruned by bucketStart (half-hour floor) while
4072
+ // the parse loop skips by raw request ts, which can sit up to 30 minutes
4073
+ // after its bucketStart. Without the margin a request whose bucket just
4074
+ // crossed the cutoff would be pruned yet still pass the skip, re-adding for
4075
+ // a few syncs until the watermark catches up.
4076
+ let watermarkMs = ageCutoffMs + 30 * 60 * 1000;
4019
4077
  if (cappedEntries.length > KIRO_CLI_CURSOR_MAX_ENTRIES) {
4020
4078
  cappedEntries.sort((a, b) => b[2] - a[2]); // newest first
4079
+ // Newest EVICTED entry sits at index MAX_ENTRIES after the sort; the
4080
+ // watermark must clear it so count-capped evictions can't re-add either.
4081
+ watermarkMs = Math.max(watermarkMs, cappedEntries[KIRO_CLI_CURSOR_MAX_ENTRIES][2] + 1);
4021
4082
  cappedEntries.length = KIRO_CLI_CURSOR_MAX_ENTRIES;
4022
4083
  }
4023
4084
  const capped = {};
4024
4085
  for (const [reqId, entry] of cappedEntries) capped[reqId] = entry;
4025
- return capped;
4086
+ return { requests: capped, watermarkMs };
4026
4087
  }
4027
4088
 
4028
4089
  // Back-compat path: per-session .json files (the old fixture shape). Emits
@@ -5204,7 +5265,13 @@ async function parseRoocodeIncremental({
5204
5265
  const cacheReads = toNonNegativeInt(payload.cacheReads);
5205
5266
  const cacheWrites = toNonNegativeInt(payload.cacheWrites);
5206
5267
  if (tokensIn === 0 && tokensOut === 0 && cacheReads === 0 && cacheWrites === 0) {
5207
- seenIds.add(dedupKey);
5268
+ // Cline-family extensions write `api_req_started` at request START
5269
+ // (zero tokens) and back-fill the SAME message in place (same ts)
5270
+ // once the request completes. Marking the zero placeholder as seen
5271
+ // would skip the back-filled tokens forever — a sync racing an
5272
+ // in-flight request silently under-counted that turn. Leave it
5273
+ // unseen; the file-level mtime gate re-evaluates it when the task
5274
+ // file is rewritten.
5208
5275
  continue;
5209
5276
  }
5210
5277
 
@@ -6473,7 +6540,10 @@ async function parseKilocodeIncremental({
6473
6540
  const cacheReads = toNonNegativeInt(payload.cacheReads);
6474
6541
  const cacheWrites = toNonNegativeInt(payload.cacheWrites);
6475
6542
  if (tokensIn === 0 && tokensOut === 0 && cacheReads === 0 && cacheWrites === 0) {
6476
- seenIds.add(dedupKey);
6543
+ // See the roocode parser: `api_req_started` is written at request
6544
+ // START with zero tokens and back-filled in place (same ts) on
6545
+ // completion. Marking the placeholder seen would drop the
6546
+ // back-filled tokens forever when a sync races an in-flight request.
6477
6547
  continue;
6478
6548
  }
6479
6549
 
@@ -6613,7 +6683,13 @@ async function parseOmpIncremental({
6613
6683
  const cacheWrite = toNonNegativeInt(usage.cacheWrite);
6614
6684
  const reasoningTokens = toNonNegativeInt(usage.reasoningTokens);
6615
6685
 
6616
- if (input === 0 && output === 0 && cacheRead === 0 && cacheWrite === 0) {
6686
+ if (
6687
+ input === 0 &&
6688
+ output === 0 &&
6689
+ cacheRead === 0 &&
6690
+ cacheWrite === 0 &&
6691
+ reasoningTokens === 0
6692
+ ) {
6617
6693
  seenIds.add(entryId);
6618
6694
  continue;
6619
6695
  }
@@ -6856,7 +6932,13 @@ async function parsePiIncremental({
6856
6932
  const cacheWrite = toNonNegativeInt(usage.cacheWrite);
6857
6933
  const reasoningTokens = toNonNegativeInt(usage.reasoningTokens);
6858
6934
 
6859
- if (input === 0 && output === 0 && cacheRead === 0 && cacheWrite === 0) {
6935
+ if (
6936
+ input === 0 &&
6937
+ output === 0 &&
6938
+ cacheRead === 0 &&
6939
+ cacheWrite === 0 &&
6940
+ reasoningTokens === 0
6941
+ ) {
6860
6942
  seenIds.add(entryId);
6861
6943
  continue;
6862
6944
  }
@@ -677,8 +677,8 @@ function expandGeminiExecutableCandidates({ home } = {}) {
677
677
  return candidates;
678
678
  }
679
679
 
680
- function extractGeminiOauthClientCredentials({ commandRunner, home } = {}) {
681
- const result = runCommand(commandRunner, "which", ["gemini"], { timeout: 2000 });
680
+ async function extractGeminiOauthClientCredentials({ commandRunner, home } = {}) {
681
+ const result = await runCommand(commandRunner, "which", ["gemini"], { timeout: 2000 });
682
682
  const geminiPath = typeof result?.stdout === "string" ? result.stdout.trim() : "";
683
683
 
684
684
  const geminiPaths = [
@@ -732,7 +732,7 @@ async function refreshGeminiAccessToken({
732
732
  fetchImpl = fetch,
733
733
  commandRunner,
734
734
  }) {
735
- const oauthClient = extractGeminiOauthClientCredentials({ commandRunner, home });
735
+ const oauthClient = await extractGeminiOauthClientCredentials({ commandRunner, home });
736
736
  if (!oauthClient?.clientId || !oauthClient?.clientSecret) {
737
737
  throw new Error("Gemini API error: Could not find Gemini CLI OAuth configuration");
738
738
  }
@@ -940,24 +940,97 @@ async function fetchGeminiLimits({ home, env, fetchImpl = fetch, commandRunner }
940
940
  }
941
941
  }
942
942
 
943
+ // Async command runner. Previously this wrapped `cp.spawnSync`, which blocked the
944
+ // Node event loop for the full command duration (up to 20s for Kiro) and froze every
945
+ // other local-api endpoint plus the other providers' withProviderTimeout races.
946
+ // Returns a promise for a spawnSync-shaped result: { status, stdout, stderr, error? }.
947
+ // Injected runners (tests) may stay synchronous — their return value is wrapped in
948
+ // Promise.resolve so both sync and async runners work.
943
949
  function runCommand(commandRunner, command, args, options = {}) {
944
- const runner = typeof commandRunner === "function" ? commandRunner : cp.spawnSync;
945
- return runner(command, args, {
950
+ const merged = {
946
951
  encoding: "utf8",
947
952
  maxBuffer: 10 * 1024 * 1024,
948
953
  ...options,
954
+ };
955
+ if (typeof commandRunner === "function") {
956
+ return Promise.resolve(commandRunner(command, args, merged));
957
+ }
958
+
959
+ const { timeout, maxBuffer, ...spawnOptions } = merged;
960
+ return new Promise((resolve) => {
961
+ let child;
962
+ try {
963
+ child = cp.spawn(command, args, { ...spawnOptions, stdio: ["ignore", "pipe", "pipe"] });
964
+ } catch (error) {
965
+ resolve({ status: null, stdout: "", stderr: "", error });
966
+ return;
967
+ }
968
+
969
+ let stdout = "";
970
+ let stderr = "";
971
+ let settled = false;
972
+ let timedOut = false;
973
+ let timer = null;
974
+ let hardTimer = null;
975
+
976
+ const settle = ({ status = null, error = null } = {}) => {
977
+ if (settled) return;
978
+ settled = true;
979
+ if (timer) clearTimeout(timer);
980
+ if (hardTimer) clearTimeout(hardTimer);
981
+ let finalError = error;
982
+ if (!finalError && timedOut) {
983
+ finalError = new Error(`spawn ${command} ETIMEDOUT`);
984
+ finalError.code = "ETIMEDOUT";
985
+ }
986
+ const result = { status, stdout, stderr };
987
+ if (finalError) result.error = finalError;
988
+ resolve(result);
989
+ };
990
+
991
+ if (Number.isFinite(timeout) && timeout > 0) {
992
+ timer = setTimeout(() => {
993
+ timedOut = true;
994
+ try { child.kill("SIGTERM"); } catch (_error) {}
995
+ // Guarantee settlement even if the child ignores SIGTERM or keeps stdio open.
996
+ hardTimer = setTimeout(() => {
997
+ try { child.kill("SIGKILL"); } catch (_error) {}
998
+ settle({ status: null });
999
+ }, 1000);
1000
+ if (typeof hardTimer.unref === "function") hardTimer.unref();
1001
+ }, timeout);
1002
+ }
1003
+
1004
+ const collect = (stream, append) => {
1005
+ if (!stream) return;
1006
+ stream.setEncoding("utf8");
1007
+ stream.on("data", (chunk) => {
1008
+ append(chunk);
1009
+ if (stdout.length + stderr.length > maxBuffer) {
1010
+ const error = new Error(`spawn ${command} maxBuffer length exceeded`);
1011
+ error.code = "ERR_CHILD_PROCESS_STDIO_MAXBUFFER";
1012
+ try { child.kill("SIGKILL"); } catch (_error) {}
1013
+ settle({ status: null, error });
1014
+ }
1015
+ });
1016
+ };
1017
+ collect(child.stdout, (chunk) => { stdout += chunk; });
1018
+ collect(child.stderr, (chunk) => { stderr += chunk; });
1019
+
1020
+ child.on("error", (error) => settle({ status: null, error }));
1021
+ child.on("close", (code) => settle({ status: timedOut ? null : code }));
949
1022
  });
950
1023
  }
951
1024
 
952
- function whichBinary(binary, { commandRunner } = {}) {
953
- const result = runCommand(commandRunner, "which", [binary], { timeout: 2000 });
1025
+ async function whichBinary(binary, { commandRunner } = {}) {
1026
+ const result = await runCommand(commandRunner, "which", [binary], { timeout: 2000 });
954
1027
  if (result?.error || result?.status !== 0) return null;
955
1028
  const stdout = typeof result?.stdout === "string" ? result.stdout.trim() : "";
956
1029
  return stdout ? stdout.split("\n")[0] : null;
957
1030
  }
958
1031
 
959
- function isBinaryAvailable(binary, { commandRunner } = {}) {
960
- return whichBinary(binary, { commandRunner }) !== null;
1032
+ async function isBinaryAvailable(binary, { commandRunner } = {}) {
1033
+ return (await whichBinary(binary, { commandRunner })) !== null;
961
1034
  }
962
1035
 
963
1036
  function stripAnsi(text) {
@@ -1223,12 +1296,12 @@ async function fetchCopilotLimits({ home, env = process.env, fetchImpl = fetch }
1223
1296
  }
1224
1297
  }
1225
1298
 
1226
- function fetchKiroLimits({ commandRunner, now = new Date() } = {}) {
1227
- if (!isBinaryAvailable("kiro-cli", { commandRunner })) {
1299
+ async function fetchKiroLimits({ commandRunner, now = new Date() } = {}) {
1300
+ if (!(await isBinaryAvailable("kiro-cli", { commandRunner }))) {
1228
1301
  return { configured: false };
1229
1302
  }
1230
1303
 
1231
- const result = runCommand(
1304
+ const result = await runCommand(
1232
1305
  commandRunner,
1233
1306
  "kiro-cli",
1234
1307
  ["chat", "--no-interactive", "/usage"],
@@ -1291,8 +1364,8 @@ function extractCommandFlag(command, flag) {
1291
1364
  return match?.[1] || null;
1292
1365
  }
1293
1366
 
1294
- function detectAntigravityProcess({ commandRunner } = {}) {
1295
- const result = runCommand(commandRunner, "/bin/ps", ["-ax", "-o", "pid=,command="], {
1367
+ async function detectAntigravityProcess({ commandRunner } = {}) {
1368
+ const result = await runCommand(commandRunner, "/bin/ps", ["-ax", "-o", "pid=,command="], {
1296
1369
  timeout: 4000,
1297
1370
  });
1298
1371
  const lines = String(result?.stdout || "").split("\n");
@@ -1494,7 +1567,7 @@ function clearClaudeRateLimitCooldown({ home } = {}) {
1494
1567
  } catch (_error) {}
1495
1568
  }
1496
1569
 
1497
- function resolveLsofBinary({ commandRunner } = {}) {
1570
+ async function resolveLsofBinary({ commandRunner } = {}) {
1498
1571
  for (const candidate of ["/usr/sbin/lsof", "/usr/bin/lsof"]) {
1499
1572
  if (fs.existsSync(candidate)) return candidate;
1500
1573
  }
@@ -1513,12 +1586,12 @@ function parseListeningPorts(output) {
1513
1586
  return Array.from(ports).sort((a, b) => a - b);
1514
1587
  }
1515
1588
 
1516
- function listAntigravityPorts(pid, { commandRunner } = {}) {
1517
- const lsof = resolveLsofBinary({ commandRunner });
1589
+ async function listAntigravityPorts(pid, { commandRunner } = {}) {
1590
+ const lsof = await resolveLsofBinary({ commandRunner });
1518
1591
  if (!lsof) {
1519
1592
  throw new Error("Antigravity port detection needs lsof. Install it, then retry.");
1520
1593
  }
1521
- const result = runCommand(
1594
+ const result = await runCommand(
1522
1595
  commandRunner,
1523
1596
  lsof,
1524
1597
  ["-nP", "-iTCP", "-sTCP:LISTEN", "-a", "-p", String(pid)],
@@ -1768,7 +1841,7 @@ async function probeAntigravityPort(port, csrfToken, { timeoutMs, requestFn } =
1768
1841
  }
1769
1842
 
1770
1843
  async function fetchAntigravityLimits({ home, commandRunner, requestFn, timeoutMs = 8000, nowMs = Date.now() } = {}) {
1771
- const processInfo = detectAntigravityProcess({ commandRunner });
1844
+ const processInfo = await detectAntigravityProcess({ commandRunner });
1772
1845
  if (!processInfo.configured) {
1773
1846
  return readAntigravityLimitsCache({ home, nowMs }) || { configured: false };
1774
1847
  }
@@ -1787,7 +1860,7 @@ async function fetchAntigravityLimits({ home, commandRunner, requestFn, timeoutM
1787
1860
  };
1788
1861
 
1789
1862
  try {
1790
- const ports = listAntigravityPorts(processInfo.pid, { commandRunner });
1863
+ const ports = await listAntigravityPorts(processInfo.pid, { commandRunner });
1791
1864
  let workingPort = null;
1792
1865
  for (const port of ports) {
1793
1866
  if (await probeAntigravityPort(port, processInfo.csrfToken, { timeoutMs, requestFn })) {
@@ -1864,7 +1937,29 @@ function withPlanLabel(obj, raw, brand) {
1864
1937
  return { ...obj, plan_label: normalizePlanLabel(raw, brand) };
1865
1938
  }
1866
1939
 
1867
- async function getUsageLimits({
1940
+ // Single-flight guard: concurrent cache misses share one upstream fetch instead of
1941
+ // each triggering the full 9-provider round (Claude's OAuth usage endpoint 429s when
1942
+ // hammered). Survives an external resetUsageLimitsCache() (refresh=1 path in
1943
+ // local-api.js): a refresh arriving while a fetch is already running reuses that
1944
+ // in-flight fetch and returns its result.
1945
+ let inFlightFetch = null;
1946
+
1947
+ async function getUsageLimits(options = {}) {
1948
+ const nowMs = Date.now();
1949
+ if (cache.data && nowMs - cache.fetchedAt < CACHE_TTL_MS) {
1950
+ return cache.data;
1951
+ }
1952
+ if (inFlightFetch) {
1953
+ return inFlightFetch;
1954
+ }
1955
+ const promise = fetchUsageLimitsUncached(options).finally(() => {
1956
+ if (inFlightFetch === promise) inFlightFetch = null;
1957
+ });
1958
+ inFlightFetch = promise;
1959
+ return promise;
1960
+ }
1961
+
1962
+ async function fetchUsageLimitsUncached({
1868
1963
  home,
1869
1964
  env,
1870
1965
  platform,
@@ -1876,9 +1971,6 @@ async function getUsageLimits({
1876
1971
  providerTimeoutMs = DEFAULT_PROVIDER_TIMEOUT_MS,
1877
1972
  } = {}) {
1878
1973
  const nowMs = Date.now();
1879
- if (cache.data && nowMs - cache.fetchedAt < CACHE_TTL_MS) {
1880
- return cache.data;
1881
- }
1882
1974
 
1883
1975
  const [claudeToken, claudeSubscription, codexAuth] = await Promise.all([
1884
1976
  Promise.resolve().then(() => readClaudeCodeAccessToken({ platform, securityRunner, home })),
@@ -1949,7 +2041,7 @@ async function getUsageLimits({
1949
2041
  .catch((reason) => ({ configured: true, error: reason?.message || "Unknown error" })),
1950
2042
  withProviderTimeout(fetchGeminiLimits({ home, env, fetchImpl: providerFetch, commandRunner }), "Gemini", providerTimeoutMs)
1951
2043
  .catch((reason) => ({ configured: true, error: reason?.message || "Unknown error" })),
1952
- Promise.resolve().then(() => fetchKiroLimits({ commandRunner, now })),
2044
+ fetchKiroLimits({ commandRunner, now }),
1953
2045
  fetchAntigravityLimits({ home, commandRunner, requestFn, nowMs }),
1954
2046
  withProviderTimeout(fetchCopilotLimits({ home, env, fetchImpl: providerFetch }), "GitHub Copilot", providerTimeoutMs)
1955
2047
  .catch((reason) => ({ configured: true, error: reason?.message || "Unknown error" })),
@@ -2043,6 +2135,7 @@ module.exports = {
2043
2135
  getUsageLimits,
2044
2136
  normalizePlanLabel,
2045
2137
  resetUsageLimitsCache,
2138
+ runCommand,
2046
2139
  extractGeminiOauthClientCredentials,
2047
2140
  loadKimiCredentials,
2048
2141
  normalizeCursorUsageSummary,
@@ -14,6 +14,14 @@
14
14
  * React Wrapped page.
15
15
  */
16
16
 
17
+ // Lazy require to avoid loading the full local-api module (and its pricing
18
+ // tables) until rows are actually aggregated. local-api.js itself requires
19
+ // this module lazily inside a handler, so there is no top-level cycle either
20
+ // way — but keeping this lazy preserves wrapped-aggregator as a light import.
21
+ function normalizeQueueRow(row) {
22
+ return require("./local-api").normalizeQueueRow(row);
23
+ }
24
+
17
25
  function isFiniteNumber(n) {
18
26
  return typeof n === "number" && Number.isFinite(n);
19
27
  }
@@ -70,7 +78,10 @@ function aggregateWrapped(rows, opts = {}) {
70
78
  for (const row of Array.isArray(rows) ? rows : []) {
71
79
  if (!row || typeof row !== "object") continue;
72
80
  const key = `${row.source || ""}|${row.model || ""}|${row.hour_start || ""}`;
73
- dedup.set(key, row);
81
+ // Apply the same legacy-row corrections (codex inclusive-input, cursor
82
+ // billable=0) as local-api.js readQueueData so `tracker wrapped` matches
83
+ // the dashboard for identical data.
84
+ dedup.set(key, normalizeQueueRow(row));
74
85
  }
75
86
  const all = Array.from(dedup.values());
76
87
 
@@ -1,15 +0,0 @@
1
- import{r as ce,G as t,j as n}from"./main-BmTIgbZL.js";const Ge=typeof window<"u"&&(window.location.hostname==="localhost"||window.location.hostname==="127.0.0.1"),$=Ge?"/proxy/ipcheck":"https://ip.net.coffee",le="claude_ip_history",be=6,Ue=["CN","HK","MO","RU","KP","IR","SY","CU","BY","VE"],ze={CN:"Asia/Shanghai",TW:"Asia/Taipei",HK:"Asia/Hong_Kong",MO:"Asia/Macau",JP:"Asia/Tokyo",KR:"Asia/Seoul",SG:"Asia/Singapore",MY:"Asia/Kuala_Lumpur",TH:"Asia/Bangkok",VN:"Asia/Ho_Chi_Minh",ID:"Asia/Jakarta",PH:"Asia/Manila",IN:"Asia/Kolkata",PK:"Asia/Karachi",BD:"Asia/Dhaka",IR:"Asia/Tehran",IL:"Asia/Jerusalem",AE:"Asia/Dubai",SA:"Asia/Riyadh",TR:"Europe/Istanbul",RU:"Europe/Moscow",UA:"Europe/Kyiv",GB:"Europe/London",IE:"Europe/Dublin",FR:"Europe/Paris",DE:"Europe/Berlin",IT:"Europe/Rome",ES:"Europe/Madrid",PT:"Europe/Lisbon",NL:"Europe/Amsterdam",BE:"Europe/Brussels",CH:"Europe/Zurich",AT:"Europe/Vienna",SE:"Europe/Stockholm",NO:"Europe/Oslo",DK:"Europe/Copenhagen",FI:"Europe/Helsinki",PL:"Europe/Warsaw",CZ:"Europe/Prague",GR:"Europe/Athens",RO:"Europe/Bucharest",US:"America/Los_Angeles",CA:"America/Toronto",MX:"America/Mexico_City",BR:"America/Sao_Paulo",AR:"America/Argentina/Buenos_Aires",CL:"America/Santiago",AU:"Australia/Sydney",NZ:"Pacific/Auckland",ZA:"Africa/Johannesburg",EG:"Africa/Cairo",NG:"Africa/Lagos",KE:"Africa/Nairobi"},We={CN:["zh"],TW:["zh"],HK:["zh","en"],MO:["zh","en"],JP:["ja"],KR:["ko"],TH:["th"],VN:["vi"],SG:["en","zh","ms","ta"],MY:["ms","en","zh","ta"],ID:["id","en"],PH:["en","tl","fil"],IN:["en","hi"],PK:["ur","en"],BD:["bn","en"],LK:["si","ta","en"],NP:["ne","en"],US:["en"],GB:["en"],IE:["en","ga"],AU:["en"],NZ:["en","mi"],CA:["en","fr"],DE:["de"],AT:["de"],CH:["de","fr","it","rm"],BE:["nl","fr","de"],FR:["fr"],IT:["it"],ES:["es","ca","gl","eu"],PT:["pt"],NL:["nl","fy"],LU:["lb","fr","de"],SE:["sv"],NO:["no","nb","nn"],DK:["da"],FI:["fi","sv"],IS:["is","en"],PL:["pl"],CZ:["cs"],SK:["sk"],HU:["hu"],RO:["ro"],BG:["bg"],GR:["el"],RU:["ru"],UA:["uk","ru"],BY:["be","ru"],TR:["tr"],IL:["he","ar","en"],SA:["ar"],AE:["ar","en"],EG:["ar"],IR:["fa"],IQ:["ar","ku"],ZA:["en","af","zu","xh"],KE:["en","sw"],NG:["en"],ET:["am","en"],BR:["pt"],AR:["es"],MX:["es"],CL:["es"],CO:["es"],PE:["es"],VE:["es"]},Be={jp:"Japan",tw:"Taiwan",hk:"Hong Kong",sg:"Singapore",us:"United States",de:"Germany",kr:"South Korea",fr:"France",nl:"Netherlands",gb:"United Kingdom",au:"Australia",ca:"Canada",br:"Brazil",in:"India"};function Z(d){return!!d&&d.includes(":")}function Ke(d){return!Z(d)||d.length<=20?d:d.substring(0,18)+"..."}function v(d){return d==null?"":String(d).replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;").replace(/"/g,"&quot;").replace(/'/g,"&#39;")}function W(d){if(!d||!/^[a-zA-Z]{2}$/.test(d))return"";const k=d.toLowerCase();return`<img src="${k==="cn"?`${$}/favicons/cn.png`:k==="tw"?`${$}/favicons/flags/tw.png`:`${$}/favicons/flags/${k}.png`}" alt="${k}" class="inline-block h-4 w-auto rounded-sm align-[-2px]" onerror="this.onerror=null;this.style.display='none'">`}function Fe(d){if(!d)return"";const k=String(d).trim();if(/^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$/.test(k)){const _=k.split(".");return _[0]+"."+_[1]+".*.*"}if(k.includes("…")){const _=k.lastIndexOf("…");return k.substring(0,_+1)+"*"}if(k.includes(":")){const _=k.split(":");if(_.length>=2)return _[0]+":"+_[1]+":*"}return k}function Ve(d){if(!d)return null;try{const _=new Intl.DateTimeFormat("en-US",{timeZone:d,timeZoneName:"longOffset"}).formatToParts(new Date).find(J=>J.type==="timeZoneName");if(!_)return null;if(_.value==="GMT"||_.value==="UTC")return 0;const N=_.value.match(/GMT([+-])(\d{1,2}):?(\d{0,2})/);return N?(N[1]==="+"?1:-1)*(parseInt(N[2],10)*60+parseInt(N[3]||"0",10)):null}catch{return null}}function Ye(d){if(d==null)return"";const k=d>=0?"+":"-",_=Math.abs(d),N=Math.floor(_/60),O=_%60;return"UTC"+k+N+(O?":"+String(O).padStart(2,"0"):"")}const we={safe:"bg-emerald-50 text-emerald-700 dark:bg-emerald-500/15 dark:text-emerald-300",warn:"bg-amber-50 text-amber-800 dark:bg-amber-500/15 dark:text-amber-300",danger:"bg-red-50 text-red-700 dark:bg-red-500/15 dark:text-red-300",info:"bg-blue-50 text-blue-700 dark:bg-blue-500/15 dark:text-blue-300",neutral:"bg-oai-gray-100 text-oai-gray-600 dark:bg-oai-gray-800 dark:text-oai-gray-400"};function qe(){const d=ce.useRef(null),[k,_]=ce.useState(!1);ce.useEffect(()=>{const L=d.current;if(!L)return;const b=a=>L.querySelector(`#${a}`);let ae=!1;const e={unknown:t("ipcheck.common.unknown"),failed:t("ipcheck.ip.failed"),regionAccessible:t("ipcheck.trust.region.accessible"),regionWarnTitle:a=>t("ipcheck.trust.region_warn.title",{region:a}),regionWarnBody:t("ipcheck.trust.region_warn.body"),regionRestrictedText:t("ipcheck.trust.text.restricted"),noData:t("ipcheck.trust.no_data"),noIp:t("ipcheck.trust.no_ip"),noScore:t("ipcheck.trust.no_score"),score:{pristine:t("ipcheck.trust.label.pristine"),clean:t("ipcheck.trust.label.clean"),good:t("ipcheck.trust.label.good"),neutral:t("ipcheck.trust.label.neutral"),suspicious:t("ipcheck.trust.label.suspicious"),unreachable:t("ipcheck.trust.label.unreachable")},scoreText:{excellent:t("ipcheck.trust.text.excellent"),great:t("ipcheck.trust.text.great"),minor:t("ipcheck.trust.text.minor"),moderate:t("ipcheck.trust.text.moderate"),severe:t("ipcheck.trust.text.severe")},propsRegion:t("ipcheck.props.region"),propsCity:t("ipcheck.props.city"),propsType:t("ipcheck.props.type"),propsAsn:t("ipcheck.props.asn"),propsOrg:t("ipcheck.props.org"),propsResidential:t("ipcheck.props.residential"),propsDatacenter:t("ipcheck.props.datacenter"),secVpn:t("ipcheck.security.vpn"),secProxy:t("ipcheck.security.proxy"),secTor:t("ipcheck.security.tor"),secCrawler:t("ipcheck.security.crawler"),secAbuser:t("ipcheck.security.abuser"),secProxyFlag:t("ipcheck.security.proxy_flag"),secCrawlerYes:t("ipcheck.security.crawler_yes"),secCrawlerNo:t("ipcheck.security.crawler_no"),secAbuserYes:t("ipcheck.security.abuser_yes"),secAbuserNo:t("ipcheck.security.abuser_no"),secClean:t("ipcheck.security.clean"),availSvc:t("ipcheck.avail.svc_row"),availLat:{normal:t("ipcheck.avail.latency.normal"),good:t("ipcheck.avail.latency.good"),slow:t("ipcheck.avail.latency.slow"),unreachable:t("ipcheck.avail.latency.unreachable")},availSvcStatus:{none:t("ipcheck.avail.svc.none"),minor:t("ipcheck.avail.svc.minor"),major:t("ipcheck.avail.svc.major"),critical:t("ipcheck.avail.svc.critical"),maintenance:t("ipcheck.avail.svc.maintenance"),other:t("ipcheck.avail.svc.other")},dnsStatus:t("ipcheck.dns.status"),dnsOutlet:t("ipcheck.dns.outlet"),dnsOutletIp:t("ipcheck.dns.outlet_ip"),dnsIsp:t("ipcheck.dns.isp"),dnsLeaked:t("ipcheck.dns.leaked"),dnsNoLeak:t("ipcheck.dns.no_leak"),dnsEncrypted:t("ipcheck.dns.encrypted"),dnsCnTag:t("ipcheck.dns.cn_tag"),udpStatus:t("ipcheck.udp.status"),udpOutlet:t("ipcheck.udp.outlet"),udpOutletIp:t("ipcheck.udp.outlet_ip"),udpOrigin:t("ipcheck.udp.origin"),udpDisabled:t("ipcheck.udp.disabled"),udpNoLeak:t("ipcheck.udp.no_leak"),udpLeaked:t("ipcheck.udp.leaked"),udpAnomaly:t("ipcheck.udp.anomaly"),devTz:t("ipcheck.device.tz"),devLang:t("ipcheck.device.lang"),devOs:t("ipcheck.device.os"),devTouch:t("ipcheck.device.touch"),devNet:t("ipcheck.device.net"),devDnt:t("ipcheck.device.dnt"),devWebglRender:t("ipcheck.device.webgl_render"),devCanvasFp:t("ipcheck.device.canvas_fp"),devWebglFp:t("ipcheck.device.webgl_fp"),devMatch:t("ipcheck.device.match"),devMismatch:t("ipcheck.device.mismatch"),devLocal:t("ipcheck.device.local"),devEstSuffix:t("ipcheck.device.estimate_suffix"),devDiffEqual:t("ipcheck.device.diff_equal"),devDiffAhead:a=>t("ipcheck.device.diff_ahead",{h:a}),devDiffBehind:a=>t("ipcheck.device.diff_behind",{h:a}),devLangExpected:t("ipcheck.device.lang_expected"),devTouchYes:t("ipcheck.device.touch_yes"),devTouchNo:t("ipcheck.device.touch_no"),devDntOn:t("ipcheck.device.dnt_on"),devDntOff:t("ipcheck.device.dnt_off"),devDntUnset:t("ipcheck.device.dnt_unset"),devNetUnsupported:t("ipcheck.device.net_unsupported"),devUnsupported:t("ipcheck.device.unsupported"),histEmpty:t("ipcheck.history.empty"),histCurrent:t("ipcheck.history.current"),histLoading:t("ipcheck.ip.loading")};function g(a,i="neutral"){return`<span class="inline-flex items-center rounded-full px-2 py-0.5 text-xs font-medium ${we[i]||we.neutral}">${a}</span>`}function q(a,i,s){return a===!0?g(i,"danger"):a===!1?g(s,"safe"):g(e.unknown,"neutral")}function ue(a){if(!a)return`<span class="text-oai-gray-400 dark:text-oai-gray-500">${v(e.failed)}</span>`;const i=v(a);return Z(a)?`<span class="ip-mask-target truncate" title="${i}">${v(Ke(a))}</span>`:`<span class="ip-mask-target">${i}</span>`}function B(a){return a?`<span class="ip-link">${ue(a)}</span>`:`<span class="text-oai-gray-400 dark:text-oai-gray-500">${v(e.failed)}</span>`}function f(a,i){return`<div class="flex items-center justify-between py-3 gap-3">
2
- <span class="text-sm text-oai-gray-500 dark:text-oai-gray-400 shrink-0">${a}</span>
3
- <span class="text-sm font-medium text-oai-black dark:text-white text-right min-w-0">${i===void 0?'<span class="block h-3 w-20 rounded bg-oai-gray-200 dark:bg-oai-gray-800 animate-pulse"></span>':i}</span>
4
- </div>`}function Y(a){return a.map(i=>f(i,void 0)).join("")}function ne(){return'<div class="h-7 w-44 rounded bg-oai-gray-200 dark:bg-oai-gray-800 animate-pulse"></div>'}function ie(){return'<div class="h-3 w-52 mt-2 rounded bg-oai-gray-200 dark:bg-oai-gray-800 animate-pulse"></div>'}function _e(a){return a>=95?{text:e.score.pristine,variant:"safe"}:a>=80?{text:e.score.clean,variant:"safe"}:a>=50?{text:e.score.good,variant:"info"}:a>=25?{text:e.score.neutral,variant:"warn"}:{text:e.score.suspicious,variant:"danger"}}const w={ip:null,ippure:null,ipapis:null,claudeGeo:null,claudeRisk:null,cfGeo:""};function pe(){const a=(w.claudeRisk?.countryCode||"").toUpperCase();return!a||!Ue.includes(a)?null:t(`ipcheck.region.${a}`)}function se(){const a=b("ipv6Warn");a&&a.classList.remove("hidden")}function Q(a,i){const s=b(a);s&&(s.textContent=i||"",s.classList.remove("animate-pulse","bg-oai-gray-200","dark:bg-oai-gray-800","w-52","h-3","mt-2","rounded"))}const Te={ipAddrCN:ne(),ipGeoCN:ie(),ipAddr:ne(),ipGeo:ie(),ipAddrClaude:ne(),ipGeoClaude:ie(),propsContent:Y([e.propsRegion,e.propsCity,e.propsType,e.propsAsn,e.propsOrg]),securityContent:Y([e.secVpn,e.secProxy,e.secTor,e.secCrawler,e.secAbuser]),claudeAvailContent:Y(["claude.ai","anthropic.com"]),dnsLeakContent:Y([e.dnsStatus,e.dnsOutletIp]),udpLeakContent:Y([e.udpStatus,e.udpOutletIp]),deviceContent:Y([e.devTz,e.devLang,e.devOs,e.devTouch,e.devNet,e.devDnt,e.devWebglRender,e.devCanvasFp]),ipHistoryContent:`<span class="text-sm text-oai-gray-400 dark:text-oai-gray-500">${e.histLoading}</span>`};Object.entries(Te).forEach(([a,i])=>{const s=b(a);s&&(s.innerHTML=i)});let ge=!1;function Se(a){if(!a)return;a.dataset.realText||(a.dataset.realText=(a.textContent||"").trim());const i=a.dataset.realText,s=ge?Fe(i):i;a.textContent!==s&&(a.textContent=s)}function fe(){L.querySelectorAll(".ip-link, .ip-mask-target").forEach(Se)}const he=new MutationObserver(()=>requestAnimationFrame(fe));["ipHeroCN","ipHero","ipHeroClaude","ipHistoryContent","dnsLeakContent","udpLeakContent"].forEach(a=>{const i=b(a);i&&he.observe(i,{childList:!0,subtree:!0})}),L.__setMaskOn=a=>{ge=!!a,fe()};async function Le(){try{const s=(await(await fetch("https://1.1.1.1/cdn-cgi/trace",{signal:AbortSignal.timeout(5e3)})).text()).match(/ip=([^\n]+)/);s&&(w.ip=s[1].trim())}catch{}}async function Ae(){try{const i=await(await fetch("https://claude.ai/cdn-cgi/trace",{cache:"no-store",signal:AbortSignal.timeout(8e3)})).text(),s=Object.fromEntries(i.trim().split(`
5
- `).map(u=>u.split("=")));return{ip:s.ip||null,loc:s.loc?.toLowerCase()||null}}catch{return null}}async function $e(){try{const s=(await(await fetch("https://2026.ip138.com/",{signal:AbortSignal.timeout(5e3)})).text()).match(/(\d+\.\d+\.\d+\.\d+)/);if(s)return{ip:s[1]}}catch{}try{const s=(await(await fetch("https://my.ip.cn/",{signal:AbortSignal.timeout(5e3)})).text()).match(/(\d+\.\d+\.\d+\.\d+)/);if(s)return{ip:s[1]}}catch{}return null}async function Ne(a,i,s,u){const p=b(a),o=b(i);if(!(!p||!o)){if(!s){p.innerHTML=`<span class="text-oai-gray-400 dark:text-oai-gray-500">${e.failed}</span>`,o.textContent="";return}Z(s)&&se(),p.innerHTML=`${W(u)+" "}${B(s)}`;try{const l=await fetch(`${$}/api/geoip/${s}`,{signal:AbortSignal.timeout(5e3)});if(l.ok){const h=await l.json(),r=[h.country,h.region,h.city,h.isp].filter(Boolean).join(" · "),m=h.country_code||u||"";p.innerHTML=`${W(m)} ${B(s)}`,Q(i,r)}}catch{}}}function je(){const a=w.ippure,i=w.claudeRisk,s=w.claudeGeo,u=w.ipapis,p=i?.asn||a?.asn||"",o=i?.asOrganization||a?.asOrganization||u?.company?.name||"",l=pe(),h=!!(i&&i.ip);let r,m=null;h?l?(r=0,m=e.regionRestrictedText):r=typeof i.trust_score=="number"?i.trust_score:null:r=null;const C=b("gaugePointer"),c=b("gaugeScore"),H=b("gaugeText");if(r===null)c.innerHTML=`<span class="text-oai-gray-400 dark:text-oai-gray-500">—</span> ${g(e.noData,"neutral")}`,C.style.left="0%",C.style.opacity="0.3",H.textContent=h?e.noScore:e.noIp;else{const S=l?{text:e.score.unreachable,variant:"danger"}:_e(r),A=r>=50?"text-emerald-500":r>=25?"text-amber-500":"text-red-500";c.innerHTML=`<span class="${A}">${r}</span> ${g(S.text,S.variant)}`,C.style.left=Math.min(r,100)+"%",C.style.opacity="1",H.textContent=m||(r>=95?e.scoreText.excellent:r>=80?e.scoreText.great:r>=50?e.scoreText.minor:r>=25?e.scoreText.moderate:e.scoreText.severe)}const x=b("statusDotClaude");if(x){let S,A;r===null?(S="animate-ping absolute inline-flex h-full w-full rounded-full bg-oai-gray-300 dark:bg-oai-gray-600 opacity-75",A="relative inline-flex h-2 w-2 rounded-full bg-oai-gray-400 dark:bg-oai-gray-500"):l||r<25?(S="animate-ping absolute inline-flex h-full w-full rounded-full bg-red-400 opacity-75",A="relative inline-flex h-2 w-2 rounded-full bg-red-500"):r<50?(S="animate-ping absolute inline-flex h-full w-full rounded-full bg-amber-400 opacity-75",A="relative inline-flex h-2 w-2 rounded-full bg-amber-500"):(S="animate-ping absolute inline-flex h-full w-full rounded-full bg-emerald-400 opacity-75",A="relative inline-flex h-2 w-2 rounded-full bg-emerald-500"),x.innerHTML=`<span class="${S}"></span><span class="${A}"></span>`}const I=b("regionWarn");I&&(l?(I.innerHTML=`<span class="font-medium">⚠️ ${e.regionWarnTitle(l)}</span><br><span class="text-[13px] font-normal mt-1 block opacity-90">${e.regionWarnBody}</span>`,I.classList.remove("hidden")):I.classList.add("hidden"));const D=b("claudeRegionSupportRow"),K=b("claudeRegionSupport");if(D&&K)if(l)D.classList.add("hidden");else{const S=!!(i&&i.ip),A=!!(i&&i.countryCode);K.innerHTML=!S||!A?g(e.unknown,"neutral"):g(e.regionAccessible,"safe"),D.classList.remove("hidden")}const G=i?.isResidential??(u?!u.is_datacenter:null),R=u?.company?.type||"",U=s?.country||i?.country||"",z=s?.city||i?.city||"";let T;if(G===!0?T=g(e.propsResidential,"safe"):G===!1?T=g(e.propsDatacenter,"warn"):T=g(e.unknown,"neutral"),R){const A={hosting:"Hosting",isp:"ISP",business:"Business",education:"Education"}[R]||v(R);T+=` <span class="text-xs text-oai-gray-500 dark:text-oai-gray-400">${A}</span>`}b("propsContent").innerHTML=f(e.propsRegion,U?v(U):g(e.unknown,"neutral"))+f(e.propsCity,z?v(z):g(e.unknown,"neutral"))+f(e.propsType,T)+f(e.propsAsn,p?`<span class="font-mono tabular-nums">AS${v(p)}</span>`:g(e.unknown,"neutral"))+f(e.propsOrg,o?v(o):g(e.unknown,"neutral"));const j=u||{};b("securityContent").innerHTML=f(e.secVpn,q(j.is_vpn,e.secVpn,e.secClean))+f(e.secProxy,q(j.is_proxy,e.secProxyFlag,e.secClean))+f(e.secTor,q(j.is_tor,e.secTor,e.secClean))+f(e.secCrawler,q(j.is_crawler,e.secCrawlerYes,e.secCrawlerNo))+f(e.secAbuser,q(j.is_abuser,e.secAbuserYes,e.secAbuserNo))}async function Ee(){const a=b("dnsLeakContent");if(!a)return;const i=Math.random().toString(36).slice(2)+Math.random().toString(36).slice(2);for(let r=1;r<=2;r++)await new Promise(m=>{const C=new Image,c=setTimeout(m,2e3);C.onload=C.onerror=()=>{clearTimeout(c),m()},C.src=`http://${i}-${r}.d.ip.net.coffee/pixel.gif?_=${Date.now()}`});await new Promise(r=>setTimeout(r,1500));let s=[];for(let r=0;r<2;r++){try{const m=await fetch(`${$}/api/dns/result/${i}`,{signal:AbortSignal.timeout(3e3)});if(m.ok&&(s=(await m.json()).dns_servers||[],s.length>0))break}catch{}r===0&&await new Promise(m=>setTimeout(m,1500))}if(s.length===0){a.innerHTML=f(e.dnsStatus,g(e.dnsEncrypted,"safe"));return}const u=(w.claudeRisk?.country||"").toLowerCase(),p=u.includes("china")||u.includes("中国");let o=null,l=!1;for(const r of s){let m=null;try{const x=await fetch(`${$}/api/geoip/${r}`,{signal:AbortSignal.timeout(3e3)});x.ok&&(m=await x.json())}catch{}const C=m?.country_code||"",c=m?.isp||"";if(C==="cn"&&!p){o={ip:r,cc:C,isp:c,leaked:!0},l=!0;break}o||(o={ip:r,cc:C,isp:c,leaked:!1})}let h=f(e.dnsStatus,l?g(e.dnsLeaked,"warn"):g(e.dnsNoLeak,"safe"));if(o){const r=`${W(o.cc)} <span class="ip-mask-target font-mono tabular-nums">${v(o.ip)}</span>${o.leaked?" "+g(e.dnsCnTag,"warn"):""}`;h+=f(e.dnsOutlet,r),o.isp&&(h+=f(e.dnsIsp,`<span class="text-xs text-oai-gray-500 dark:text-oai-gray-400 font-normal truncate inline-block max-w-[12rem] align-bottom">${v(o.isp)}</span>`))}a.innerHTML=h}async function Me(){const a=b("udpLeakContent");if(!a)return;const i=new Set;try{const x=new RTCPeerConnection({iceServers:[{urls:"stun:stun.l.google.com:19302"},{urls:"stun:stun.cloudflare.com:3478"}]});x.createDataChannel("");const I=await x.createOffer();await x.setLocalDescription(I),await new Promise(D=>{const K=setTimeout(()=>{x.close(),D()},5e3);x.onicecandidate=G=>{if(!G.candidate){clearTimeout(K),x.close(),D();return}const R=G.candidate.candidate.match(/([0-9]{1,3}\.){3}[0-9]{1,3}/);if(R){const z=R[0];!z.startsWith("0.")&&!z.startsWith("127.")&&z!=="0.0.0.0"&&i.add(z)}const U=G.candidate.candidate.match(/([a-f0-9]{1,4}:){2,7}[a-f0-9]{1,4}/i);U&&i.add(U[0])}})}catch{}const s=w.claudeRisk?.ip||"",u=[...i],p=u.filter(x=>!Z(x)&&!x.startsWith("192.168.")&&!x.startsWith("10.")&&!x.startsWith("172.")&&!x.startsWith("198.18.")&&!x.startsWith("198.19.")&&!x.startsWith("100.64.")&&!x.startsWith("127.")&&!x.startsWith("0."));if(p.length===0&&u.length===0){a.innerHTML=f(e.udpStatus,g(e.udpDisabled,"safe"));return}if(p.length===0){a.innerHTML=f(e.udpStatus,g(e.udpNoLeak,"safe"));return}let o=p.find(x=>x===s)||p[0];const l=new Set(p).size>1,h=o===s,r=l&&!h;let m=f(e.udpStatus,r?g(e.udpLeaked,"warn"):g(e.udpNoLeak,"safe")),C="",c="";try{const x=await fetch(`${$}/api/geoip/${o}`,{signal:AbortSignal.timeout(5e3)});if(x.ok){const I=await x.json();C=I.country_code||"",c=I.country||""}}catch{}const H=`${W(C)} <span class="font-mono tabular-nums">${ue(o)}</span>${h?"":r?" "+g(e.udpAnomaly,"warn"):""}`;m+=f(e.udpOutlet,H),c&&(m+=f(e.udpOrigin,`<span class="text-xs text-oai-gray-500 dark:text-oai-gray-400 font-normal">${v(c)}</span>`)),a.innerHTML=m}async function me(){const a=b("claudeAvailContent");if(!a)return;a.innerHTML=Y(["claude.ai","anthropic.com"]);const i=[{name:"claude.ai",url:"https://claude.ai/cdn-cgi/trace"},{name:"anthropic.com",url:"https://www.anthropic.com/favicon.ico"}],s=await Promise.allSettled(i.map(async o=>{const l=performance.now();try{return await fetch(o.url,{mode:"no-cors",signal:AbortSignal.timeout(6e3)}),{name:o.name,ms:Math.round(performance.now()-l),ok:!0}}catch{return{name:o.name,ms:-1,ok:!1}}})),u=pe();let p="";s.forEach(o=>{const l=o.value;if(u)p+=f(l.name,g(e.availLat.unreachable,"danger"));else if(l.ok){const h=l.ms<250?"safe":l.ms<500?"info":"warn",r=l.ms<250?e.availLat.normal:l.ms<500?e.availLat.good:e.availLat.slow;p+=f(l.name,`${g(r,h)} <span class="text-xs text-oai-gray-500 dark:text-oai-gray-400 font-normal">${l.ms}ms</span>`)}else p+=f(l.name,g(e.availLat.unreachable,"danger"))});try{const o=await fetch(`${$}/claude/status.json`,{signal:AbortSignal.timeout(3e3)});if(o.ok){const h=(await o.json()).overall_indicator||"none",r=e.availSvcStatus[h]||e.availSvcStatus.other,m={none:"safe",minor:"warn",major:"danger",critical:"danger",maintenance:"warn"};p+=f(e.availSvc,g(r,m[h]||"warn"))}}catch{}a.innerHTML=p}L.__detectClaudeAvail=me;function Ie(){const a=Intl.DateTimeFormat().resolvedOptions().timeZone||e.unknown,i=-(new Date().getTimezoneOffset()/60),s="UTC"+(i>=0?"+":"")+i,u=w.claudeRisk,p=(u?.countryCode||"").toUpperCase(),o=u?.timezone||ze[p]||"",l=!!u?.timezone,h=Ve(o),r=-new Date().getTimezoneOffset();let m=null;o&&h!=null&&(m=Math.abs(h-r)<=60);let C;const c=v(a),H=v(o);if(m===!0)C=`${g(e.devMatch,"safe")} <span class="text-xs text-oai-gray-500 dark:text-oai-gray-400">${c} (${s})</span>`;else if(m===!1){const E=Ye(h),y=Math.round((h-r)/60),M=y===0?e.devDiffEqual:y>0?e.devDiffAhead(y):e.devDiffBehind(-y),F=l?H:`${H} ${e.devEstSuffix}`;C=`${g(e.devMismatch,"warn")}<br><span class="text-xs text-oai-gray-500 dark:text-oai-gray-400 font-normal">${e.devLocal} ${c} (${s})<br>Claude ${F} (${E}) · ${M}</span>`}else C=`<span class="text-xs text-oai-gray-500 dark:text-oai-gray-400 font-normal">${c} (${s})</span>`;const x=navigator.languages||[navigator.language],I=x.join(", ")||e.unknown,D=(x[0]||"").split("-")[0].toLowerCase(),K=We[p]||[],G=D&&K.length?K.includes(D):null;let R;const U=v(I),z=v(K.join(" / "));G===!0?R=`${g(e.devMatch,"safe")} <span class="text-xs text-oai-gray-500 dark:text-oai-gray-400">${U}</span>`:G===!1?R=`${g(e.devMismatch,"warn")}<br><span class="text-xs text-oai-gray-500 dark:text-oai-gray-400 font-normal">${e.devLocal} ${U}<br>${e.devLangExpected} ${z}</span>`:R=`<span class="text-xs text-oai-gray-500 dark:text-oai-gray-400 font-normal">${U}</span>`;const T=navigator.userAgent;let j=e.unknown,S=e.unknown;T.includes("Windows")?j="Windows":T.includes("Mac OS")?j="macOS":T.includes("iPhone")||T.includes("iPad")?j="iOS":T.includes("Android")?j="Android":T.includes("Linux")&&(j="Linux"),T.includes("Edg/")?S="Edge "+(T.match(/Edg\/([\d.]+)/)||[])[1]:T.includes("Chrome/")?S="Chrome "+(T.match(/Chrome\/([\d.]+)/)||[])[1]:T.includes("Firefox/")?S="Firefox "+(T.match(/Firefox\/([\d.]+)/)||[])[1]:T.includes("Safari/")&&!T.includes("Chrome")&&(S="Safari "+(T.match(/Version\/([\d.]+)/)||[])[1]);let A=e.devUnsupported;try{const E=document.createElement("canvas"),y=E.getContext("webgl")||E.getContext("experimental-webgl");if(y){const M=y.getExtension("WEBGL_debug_renderer_info");M&&(A=y.getParameter(M.UNMASKED_RENDERER_WEBGL))}}catch{}let ye=e.devUnsupported;try{const E=document.createElement("canvas"),y=E.getContext("webgl")||E.getContext("experimental-webgl");if(y){const M=y.getExtension("WEBGL_debug_renderer_info"),F=M?y.getParameter(M.UNMASKED_VENDOR_WEBGL):"",X=M?y.getParameter(M.UNMASKED_RENDERER_WEBGL):"",ve=F+"~"+X+"~"+y.getParameter(y.VERSION)+"~"+y.getParameter(y.SHADING_LANGUAGE_VERSION);let te=0;for(let oe=0;oe<ve.length;oe++)te=(te<<5)-te+ve.charCodeAt(oe)|0;ye=(te>>>0).toString(16).toUpperCase()}}catch{}let ke=e.devUnsupported;try{const E=document.createElement("canvas");E.width=200,E.height=50;const y=E.getContext("2d");y.textBaseline="top",y.font="14px Arial",y.fillStyle="#f60",y.fillRect(50,0,100,50),y.fillStyle="#069",y.fillText("net.coffee",2,15),y.fillStyle="rgba(102,204,0,0.7)",y.fillText("canvas fp",4,30);const M=E.toDataURL();let F=0;for(let X=0;X<M.length;X++)F=(F<<5)-F+M.charCodeAt(X)|0;ke=(F>>>0).toString(16).toUpperCase()}catch{}const Oe=navigator.maxTouchPoints>0,re=navigator.connection||navigator.mozConnection||navigator.webkitConnection,Pe=re?re.effectiveType||re.type||e.unknown:e.devNetUnsupported,De=navigator.doNotTrack==="1"?e.devDntOn:navigator.doNotTrack==="0"?e.devDntOff:e.devDntUnset;b("deviceContent").innerHTML=f(e.devTz,C)+f(e.devLang,R)+f(e.devOs,`${v(j)} <span class="text-oai-gray-400 dark:text-oai-gray-500">/</span> ${v(S)}`)+f(e.devTouch,Oe?g(e.devTouchYes,"info"):g(e.devTouchNo,"neutral"))+f(e.devNet,`<span class="font-mono tabular-nums uppercase">${v(Pe)}</span>`)+f(e.devDnt,v(De))+f(e.devWebglRender,`<span class="text-xs font-normal text-oai-gray-600 dark:text-oai-gray-400 break-all">${v(A)}</span>`)+f(e.devCanvasFp,`<span class="font-mono tabular-nums tracking-wider text-oai-gray-700 dark:text-oai-gray-300">${v(ke)}</span>`)+f(e.devWebglFp,`<span class="font-mono tabular-nums tracking-wider text-oai-gray-700 dark:text-oai-gray-300">${v(ye)}</span>`)}function xe(){try{return JSON.parse(localStorage.getItem(le))||[]}catch{return[]}}function Re(){const a=w.claudeRisk?.ip||"",i=(w.claudeRisk?.countryCode||"").toLowerCase(),s=w.claudeRisk?.city||"";if(!a){ee();return}const u=xe(),p=new Date,o={ip:a,cc:i,geo:s,time:p.toISOString()};if(u.length>0){const l=u[0],h=(p-new Date(l.time))/(1e3*60*60);if(l.ip===a&&h<24){ee();return}}u.unshift(o),u.length>be&&(u.length=be),localStorage.setItem(le,JSON.stringify(u)),ee()}function ee(){const a=b("ipHistoryContent");if(!a)return;const i=xe();if(i.length===0){a.innerHTML=`<span class="text-sm text-oai-gray-500 dark:text-oai-gray-400">${e.histEmpty}</span>`;return}a.innerHTML=`<ul class="divide-y divide-oai-gray-100 dark:divide-oai-gray-800">${i.map((s,u)=>{const p=new Date(s.time),o=`${p.getMonth()+1}-${p.getDate()} ${String(p.getHours()).padStart(2,"0")}:${String(p.getMinutes()).padStart(2,"0")}`,l=u===0;return`<li class="flex items-center justify-between py-3 gap-4">
6
- <div class="flex items-center gap-2 min-w-0">
7
- <span class="text-sm tabular-nums ${l?"text-oai-black dark:text-white":"text-oai-gray-500 dark:text-oai-gray-400"}">${o}</span>
8
- ${l?`<span class="inline-flex items-center rounded-full bg-emerald-50 text-emerald-700 dark:bg-emerald-500/15 dark:text-emerald-300 px-1.5 py-0.5 text-[10px] font-medium">${v(e.histCurrent)}</span>`:""}
9
- </div>
10
- <div class="flex items-center gap-2 text-sm font-medium text-oai-black dark:text-white min-w-0">
11
- ${W(s.cc)}
12
- ${B(s.ip)}
13
- ${s.geo?`<span class="text-xs text-oai-gray-500 dark:text-oai-gray-400 font-normal truncate">${v(s.geo)}</span>`:""}
14
- </div>
15
- </li>`}).join("")}</ul>`}L.__clearIPHistory=()=>{localStorage.removeItem(le),ee()};async function He(){const[,a,i]=await Promise.allSettled([Le(),$e(),Ae()]);if(ae)return;const s=a.status==="fulfilled"?a.value:null,u=i.status==="fulfilled"?i.value:null,p=[];p.push(Ne("ipAddrCN","ipGeoCN",s?.ip,"cn")),p.push((async()=>{const o=b("ipAddr"),l=b("ipGeo");if(!w.ip){o&&(o.innerHTML=`<span class="text-oai-gray-400 dark:text-oai-gray-500">${e.failed}</span>`),l&&(l.textContent="");return}Z(w.ip)&&se(),o&&(o.innerHTML=B(w.ip));try{const h=await fetch(`${$}/api/geoip/${w.ip}`,{signal:AbortSignal.timeout(5e3)});if(h.ok){const r=await h.json(),m=(r.country_code||"").toLowerCase();w.ippure={ip:w.ip,country:r.country,countryCode:(r.country_code||"").toUpperCase(),region:r.region,city:r.city},w.cfGeo=[r.country,r.region,r.city,r.isp].filter(Boolean).join(" · "),o&&(o.innerHTML=`${W(m)} ${B(w.ip)}`),Q("ipGeo",w.cfGeo)}}catch{}})()),p.push((async()=>{const o=u?.ip,l=b("ipAddrClaude"),h=b("ipGeoClaude");if(!o){l&&(l.innerHTML=`<span class="text-oai-gray-400 dark:text-oai-gray-500">${e.failed}</span>`),h&&(h.textContent="");return}Z(o)&&se(),l&&(l.innerHTML=`${W(u.loc||"")} ${B(o)}`);const[r,m]=await Promise.allSettled([fetch(`${$}/api/iprisk/${o}`,{signal:AbortSignal.timeout(1e4)}),fetch(`${$}/api/geoip/${o}`,{signal:AbortSignal.timeout(5e3)})]);let C=!1;if(m.status==="fulfilled"&&m.value.ok)try{const c=await m.value.json();if(c.country){w.claudeGeo={country:c.country,region:c.region,city:c.city,isp:c.isp,country_code:c.country_code};const H=[c.country,c.region,c.city,c.isp].filter(Boolean).join(" · ");l&&(l.innerHTML=`${W(c.country_code||u.loc||"")} ${B(o)}`),Q("ipGeoClaude",H),C=!0}}catch{}if(!C&&u.loc&&(l&&(l.innerHTML=`${W(u.loc)} ${B(o)}`),Q("ipGeoClaude",Be[u.loc]||u.loc.toUpperCase())),r.status==="fulfilled"&&r.value.ok){const c=await r.value.json();w.ipapis={is_datacenter:c.is_datacenter,is_vpn:c.is_vpn,is_proxy:c.is_proxy,is_tor:c.is_tor,is_crawler:c.is_crawler,is_abuser:c.is_abuser,is_mobile:c.is_mobile,company:{type:c.company_type,name:c.company_name},abuser_score:c.abuser_score,datacenter_name:c.datacenter_name},w.claudeRisk={ip:o,asn:c.asn,asOrganization:c.asOrganization,country:c.country,countryCode:c.countryCode,region:c.region,city:c.city,isResidential:c.isResidential,isBroadcast:c.isBroadcast,trust_score:c.trust_score,timezone:c.timezone}}})()),await Promise.allSettled(p),!ae&&(je(),Promise.allSettled([Ee(),Me(),me()]),Ie(),Re())}return He(),()=>{ae=!0,he.disconnect(),delete L.__setMaskOn,delete L.__clearIPHistory,delete L.__detectClaudeAvail}},[]);const N=L=>{_(L),d.current?.__setMaskOn?.(L)},O=()=>d.current?.__clearIPHistory?.(),J=()=>d.current?.__detectClaudeAvail?.(),P={__html:""};return n.jsx("div",{ref:d,className:"flex flex-col flex-1 text-oai-black dark:text-oai-white font-oai antialiased",children:n.jsx("main",{className:"flex-1 pt-8 sm:pt-10 pb-12 sm:pb-16",children:n.jsxs("div",{className:"mx-auto max-w-6xl px-4 sm:px-6",children:[n.jsxs("div",{className:"flex items-start justify-between gap-4 mb-8",children:[n.jsxs("div",{className:"min-w-0",children:[n.jsx("h1",{className:"text-3xl sm:text-4xl font-semibold tracking-tight text-oai-black dark:text-white mb-3",children:t("ipcheck.page.title")}),n.jsx("p",{className:"text-oai-gray-500 dark:text-oai-gray-400 text-sm sm:text-base max-w-2xl",children:t("ipcheck.page.subtitle")})]}),n.jsxs("label",{className:"shrink-0 inline-flex items-center gap-2 cursor-pointer select-none",children:[n.jsx("span",{className:"text-xs text-oai-gray-500 dark:text-oai-gray-400",children:t("ipcheck.mask.toggle")}),n.jsxs("span",{className:"relative inline-block w-9 h-5",children:[n.jsx("input",{type:"checkbox",checked:k,onChange:L=>N(L.target.checked),className:"peer sr-only"}),n.jsx("span",{className:"absolute inset-0 rounded-full bg-oai-gray-200 dark:bg-oai-gray-800 peer-checked:bg-oai-brand-500 transition-colors"}),n.jsx("span",{className:"absolute top-0.5 left-0.5 h-4 w-4 rounded-full bg-white shadow-sm transition-transform peer-checked:translate-x-4"})]})]})]}),n.jsx("div",{id:"ipv6Warn",className:"hidden mb-4 rounded-lg border border-amber-200 bg-amber-50 dark:border-amber-500/30 dark:bg-amber-500/10 px-4 py-3 text-sm text-amber-800 dark:text-amber-200",children:t("ipcheck.ipv6.warn")}),n.jsxs("div",{className:"space-y-4",children:[n.jsxs("div",{className:"grid grid-cols-1 md:grid-cols-3 gap-4",children:[n.jsx(de,{id:"CN",label:t("ipcheck.ip.cn"),icon:null}),n.jsx(de,{id:"",label:t("ipcheck.ip.cloudflare"),icon:n.jsx("img",{src:`${$}/favicons/cloudflare.webp`,alt:"",className:"h-4 w-4"})}),n.jsx(de,{id:"Claude",label:t("ipcheck.ip.claude"),icon:n.jsx("img",{src:`${$}/favicons/claude.webp`,alt:"",className:"h-4 w-4"}),statusDot:!0})]}),n.jsxs("div",{className:"grid grid-cols-1 lg:grid-cols-3 gap-4",children:[n.jsx(V,{title:t("ipcheck.trust.title"),subtitle:t("ipcheck.trust.subtitle"),children:n.jsxs("div",{className:"px-1 py-2",children:[n.jsx("div",{className:"flex items-baseline justify-between mb-2 gap-3",children:n.jsx("div",{id:"gaugeScore",className:"text-3xl font-semibold tabular-nums leading-none flex items-center gap-2",dangerouslySetInnerHTML:P})}),n.jsx("div",{id:"gaugeText",className:"text-xs text-oai-gray-500 dark:text-oai-gray-400 mb-4 min-h-[1rem]"}),n.jsxs("div",{className:"relative",children:[n.jsx("div",{className:"h-1.5 rounded-full",style:{background:"linear-gradient(90deg, #ef4444 0%, #f59e0b 50%, #10b981 100%)"}}),n.jsx("span",{id:"gaugePointer",className:"absolute -top-1 h-3.5 w-[3px] rounded-sm bg-oai-black dark:bg-white shadow-[0_0_0_1px_rgba(255,255,255,0.6)] dark:shadow-[0_0_0_1px_rgba(0,0,0,0.6)] -translate-x-1/2 transition-[left] duration-700 ease-out",style:{left:"0%"}}),n.jsxs("div",{className:"mt-2 flex justify-between text-[10px] uppercase tracking-wider text-oai-gray-400 dark:text-oai-gray-500 font-medium",children:[n.jsx("span",{children:t("ipcheck.trust.gauge.low")}),n.jsx("span",{children:"25"}),n.jsx("span",{children:"50"}),n.jsx("span",{children:"75"}),n.jsx("span",{children:t("ipcheck.trust.gauge.high")})]})]}),n.jsx("div",{id:"regionWarn",className:"hidden mt-4 rounded-lg border border-red-200 bg-red-50 dark:border-red-500/30 dark:bg-red-500/10 px-3 py-2 text-xs text-red-800 dark:text-red-300"}),n.jsxs("div",{id:"claudeRegionSupportRow",className:"hidden mt-4 pt-4 border-t border-oai-gray-100 dark:border-oai-gray-800 flex items-center justify-between",children:[n.jsx("span",{className:"text-sm text-oai-gray-500 dark:text-oai-gray-400",children:t("ipcheck.trust.region_support")}),n.jsx("span",{id:"claudeRegionSupport",className:"text-sm font-medium text-oai-black dark:text-white",dangerouslySetInnerHTML:P})]})]})}),n.jsx(V,{title:t("ipcheck.props.title"),children:n.jsx("div",{id:"propsContent",className:"divide-y divide-oai-gray-100 dark:divide-oai-gray-800",dangerouslySetInnerHTML:P})}),n.jsx(V,{title:t("ipcheck.security.title"),children:n.jsx("div",{id:"securityContent",className:"divide-y divide-oai-gray-100 dark:divide-oai-gray-800",dangerouslySetInnerHTML:P})})]}),n.jsxs("div",{className:"grid grid-cols-1 lg:grid-cols-3 gap-4",children:[n.jsx(V,{title:n.jsxs("span",{className:"flex items-center gap-1.5",children:[t("ipcheck.avail.title"),n.jsx(Ze,{text:t("ipcheck.avail.tooltip")})]}),action:n.jsx(Ce,{label:t("ipcheck.avail.refresh"),onClick:J}),children:n.jsx("div",{id:"claudeAvailContent",className:"divide-y divide-oai-gray-100 dark:divide-oai-gray-800",dangerouslySetInnerHTML:P})}),n.jsx(V,{title:t("ipcheck.dns.title"),children:n.jsx("div",{id:"dnsLeakContent",className:"divide-y divide-oai-gray-100 dark:divide-oai-gray-800",dangerouslySetInnerHTML:P})}),n.jsx(V,{title:t("ipcheck.udp.title"),children:n.jsx("div",{id:"udpLeakContent",className:"divide-y divide-oai-gray-100 dark:divide-oai-gray-800",dangerouslySetInnerHTML:P})})]}),n.jsx(V,{title:t("ipcheck.device.title"),children:n.jsx("div",{id:"deviceContent",className:"divide-y divide-oai-gray-100 dark:divide-oai-gray-800",dangerouslySetInnerHTML:P})}),n.jsx(V,{title:t("ipcheck.history.title"),subtitle:t("ipcheck.history.subtitle"),action:n.jsx(Ce,{label:t("ipcheck.history.clear"),onClick:O}),children:n.jsx("div",{id:"ipHistoryContent",dangerouslySetInnerHTML:P})})]})]})})})}function V({title:d,subtitle:k,action:_,children:N,className:O=""}){return n.jsxs("section",{className:`rounded-xl border border-oai-gray-200 dark:border-oai-gray-800 bg-white dark:bg-oai-gray-900 p-5 sm:p-6 ${O}`,children:[n.jsxs("div",{className:"flex items-start justify-between gap-3 mb-4",children:[n.jsxs("div",{className:"min-w-0",children:[n.jsx("h2",{className:"text-[15px] font-semibold text-oai-black dark:text-white",children:d}),k?n.jsx("p",{className:"mt-0.5 text-xs text-oai-gray-500 dark:text-oai-gray-400",children:k}):null]}),_]}),N]})}function Ce({label:d,onClick:k}){return n.jsx("button",{type:"button",onClick:k,className:"shrink-0 inline-flex h-7 items-center px-2.5 rounded-md text-xs font-medium text-oai-gray-600 dark:text-oai-gray-300 border border-oai-gray-200 dark:border-oai-gray-700 hover:bg-oai-gray-50 dark:hover:bg-oai-gray-800 hover:text-oai-black dark:hover:text-white transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-oai-brand-500",children:d})}function Ze({text:d}){return n.jsxs("span",{className:"relative inline-flex items-center group",children:[n.jsx("svg",{viewBox:"0 0 16 16",className:"h-3.5 w-3.5 text-oai-gray-400 dark:text-oai-gray-500",fill:"currentColor","aria-hidden":!0,children:n.jsx("path",{d:"M8 1.5a6.5 6.5 0 1 0 0 13 6.5 6.5 0 0 0 0-13zM7.25 6.75a.75.75 0 0 1 1.5 0V11a.75.75 0 0 1-1.5 0V6.75zM8 4a.85.85 0 1 1 0 1.7A.85.85 0 0 1 8 4z"})}),n.jsx("span",{className:"pointer-events-none absolute left-1/2 top-full z-10 mt-1.5 -translate-x-1/2 w-56 rounded-md bg-oai-gray-900 dark:bg-oai-gray-800 px-2.5 py-2 text-[11px] font-normal text-white opacity-0 transition-opacity group-hover:opacity-100 leading-relaxed shadow-lg",children:d})]})}function de({id:d,label:k,icon:_,statusDot:N}){const O=`ipAddr${d}`,J=`ipGeo${d}`;return n.jsxs("article",{id:`ipHero${d}`,className:"rounded-xl border border-oai-gray-200 dark:border-oai-gray-800 bg-white dark:bg-oai-gray-900 p-5 sm:p-6",children:[n.jsxs("div",{className:"flex items-center gap-1.5 text-[11px] uppercase tracking-wider font-medium text-oai-gray-500 dark:text-oai-gray-400 mb-2",children:[_,n.jsx("span",{children:k}),N?n.jsxs("span",{id:`statusDot${d}`,className:"relative inline-flex h-2 w-2 ml-1.5","aria-hidden":!0,children:[n.jsx("span",{className:"animate-ping absolute inline-flex h-full w-full rounded-full bg-oai-gray-300 dark:bg-oai-gray-600 opacity-75"}),n.jsx("span",{className:"relative inline-flex h-2 w-2 rounded-full bg-oai-gray-400 dark:bg-oai-gray-500"})]}):null]}),n.jsx("div",{id:O,className:"text-xl sm:text-2xl font-semibold tabular-nums text-oai-black dark:text-white flex items-center gap-2 min-h-[2rem]",dangerouslySetInnerHTML:{__html:""}}),n.jsx("div",{id:J,className:"mt-1 text-xs text-oai-gray-500 dark:text-oai-gray-400 truncate min-h-[1rem]"})]})}export{qe as default};