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.
- package/README.ja.md +3 -1
- package/README.ko.md +3 -1
- package/README.md +3 -1
- package/README.zh-CN.md +3 -1
- package/dashboard/dist/assets/{ActivityHeatmap-BW7r63JV.js → ActivityHeatmap-CItg7FNN.js} +1 -1
- package/dashboard/dist/assets/{Card-BWgji0yz.js → Card-B9jWqeQm.js} +1 -1
- package/dashboard/dist/assets/{DashboardPage-fwmRrxV3.js → DashboardPage-D-UdQdmb.js} +1 -1
- package/dashboard/dist/assets/{DevicePage-rxxuM-iC.js → DevicePage-BmhKFgDo.js} +1 -1
- package/dashboard/dist/assets/{DialogTitle-ltZ7viR5.js → DialogTitle-Dcuz0ACc.js} +1 -1
- package/dashboard/dist/assets/{FadeIn-DkGtOY3l.js → FadeIn-B-oMQCv_.js} +1 -1
- package/dashboard/dist/assets/{HeaderGithubStar-CUKVYH9j.js → HeaderGithubStar-C6x-l5U0.js} +1 -1
- package/dashboard/dist/assets/IpCheckPage-D1tltH7T.js +20 -0
- package/dashboard/dist/assets/{LandingPage-Y2ovBPVG.js → LandingPage-DnjQLNxt.js} +1 -1
- package/dashboard/dist/assets/{LeaderboardAvatar-DSGfEE93.js → LeaderboardAvatar-PNgWQxaR.js} +1 -1
- package/dashboard/dist/assets/{LeaderboardPage-BX4cvtg7.js → LeaderboardPage-DRCSwReV.js} +3 -3
- package/dashboard/dist/assets/{LeaderboardProfileModal-CJfefuSR.js → LeaderboardProfileModal-DvbGnWDm.js} +1 -1
- package/dashboard/dist/assets/{LeaderboardProfilePage-Xy0K-ppJ.js → LeaderboardProfilePage-Bmk16GFd.js} +1 -1
- package/dashboard/dist/assets/{LimitsPage-utdNxAg_.js → LimitsPage-DsdyKs5Z.js} +1 -1
- package/dashboard/dist/assets/{LocalOnlyNotice-CwGk0Fn3.js → LocalOnlyNotice-CIa2X9wJ.js} +1 -1
- package/dashboard/dist/assets/{LoginPage-BHlX2ZHJ.js → LoginPage-CnDLx50g.js} +1 -1
- package/dashboard/dist/assets/{PopoverPopup-BC4rHujH.js → PopoverPopup-ZsSFlvKU.js} +1 -1
- package/dashboard/dist/assets/{Select-BpDJKpfl.js → Select-CY2FgOFv.js} +1 -1
- package/dashboard/dist/assets/{SelectItemText-D1_H3hHS.js → SelectItemText-DI_GTNc2.js} +1 -1
- package/dashboard/dist/assets/{SettingsPage-DCVw-3Cv.js → SettingsPage-D1DZkCMo.js} +1 -1
- package/dashboard/dist/assets/{SkillsPage-B-9_Ufpw.js → SkillsPage-DorZCQ0W.js} +1 -1
- package/dashboard/dist/assets/{WidgetsPage-DJ4i97QU.js → WidgetsPage-BDLa_UaU.js} +1 -1
- package/dashboard/dist/assets/{WrappedPage-W8pPcYr2.js → WrappedPage-BKn5Q7iM.js} +1 -1
- package/dashboard/dist/assets/{agent-logos-yJi-7lhN.js → agent-logos-CM4Rt9bu.js} +1 -1
- package/dashboard/dist/assets/{arrow-up-right-BXhFthH9.js → arrow-up-right-jP0XdZdv.js} +1 -1
- package/dashboard/dist/assets/{download-D32KO7Ua.js → download-D8Hvx1kr.js} +1 -1
- package/dashboard/dist/assets/{info-C8QLdyUg.js → info-vv2jU1_C.js} +1 -1
- package/dashboard/dist/assets/{main-BmTIgbZL.js → main-9P4Ny5Xr.js} +15 -15
- package/dashboard/dist/assets/main-Br5SsufY.css +1 -0
- package/dashboard/dist/assets/{use-limits-display-prefs-DcmWcNeA.js → use-limits-display-prefs-BZVYlv9P.js} +1 -1
- package/dashboard/dist/assets/{use-native-settings-BLZi1heD.js → use-native-settings-CR_f7uLH.js} +1 -1
- package/dashboard/dist/assets/use-usage-limits-CvgpaBd_.js +1 -0
- package/dashboard/dist/assets/{useCurrency-ERbWumqM.js → useCurrency-Cq0FIlJZ.js} +1 -1
- package/dashboard/dist/assets/{useScrollLock-PJWfD6AS.js → useScrollLock-B5U1SJQV.js} +1 -1
- package/dashboard/dist/brand-logos/hermes.svg +1 -1
- package/dashboard/dist/index.html +2 -2
- package/dashboard/dist/share.html +2 -2
- package/package.json +1 -1
- package/src/commands/device-login.js +19 -3
- package/src/commands/sync.js +538 -348
- package/src/lib/local-api.js +11 -2
- package/src/lib/pricing/curated-overrides.json +2 -1
- package/src/lib/pricing/seed-snapshot.json +1 -1
- package/src/lib/rollout.js +98 -16
- package/src/lib/usage-limits.js +118 -25
- package/src/lib/wrapped-aggregator.js +12 -1
- package/dashboard/dist/assets/IpCheckPage-DqrupqCq.js +0 -15
- package/dashboard/dist/assets/main-BfK9LoKV.css +0 -1
- package/dashboard/dist/assets/use-usage-limits-BIJk3Nmb.js +0 -1
package/src/lib/rollout.js
CHANGED
|
@@ -2649,13 +2649,32 @@ async function parseCursorApiIncremental({
|
|
|
2649
2649
|
const total = records.length;
|
|
2650
2650
|
|
|
2651
2651
|
if (records.length > 0) {
|
|
2652
|
-
|
|
2653
|
-
|
|
2654
|
-
|
|
2655
|
-
|
|
2656
|
-
|
|
2657
|
-
|
|
2658
|
-
|
|
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 (
|
|
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 = {
|
|
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 = {
|
|
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
|
-
|
|
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
|
-
|
|
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 (
|
|
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 (
|
|
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
|
}
|
package/src/lib/usage-limits.js
CHANGED
|
@@ -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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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,"&").replace(/</g,"<").replace(/>/g,">").replace(/"/g,""").replace(/'/g,"'")}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};
|