tokentracker-cli 0.71.0 → 0.72.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.de.md +2 -1
- package/README.ja.md +2 -1
- package/README.ko.md +2 -1
- package/README.md +2 -1
- package/README.zh-CN.md +2 -1
- package/dashboard/dist/assets/{ActivityHeatmap-Dk9JY1ug.js → ActivityHeatmap-xXPEf7cL.js} +1 -1
- package/dashboard/dist/assets/{Card-CNWjr8mp.js → Card-SvNODc3B.js} +1 -1
- package/dashboard/dist/assets/{CommunityStatsModal-C2qyGffN.js → CommunityStatsModal-DQVjsjuv.js} +1 -1
- package/dashboard/dist/assets/{ConfirmModal-DSu8Tu1r.js → ConfirmModal-BaWPDQpw.js} +1 -1
- package/dashboard/dist/assets/{DashboardPage-DIf7YTQ9.js → DashboardPage-Byj7sLJ0.js} +1 -1
- package/dashboard/dist/assets/{DevicePage-CXQPPyMG.js → DevicePage-mpqMx8oV.js} +1 -1
- package/dashboard/dist/assets/{DialogClose-Cl6zG-Oy.js → DialogClose-CWzNkEbl.js} +1 -1
- package/dashboard/dist/assets/DialogDescription-D15uXWxt.js +1 -0
- package/dashboard/dist/assets/{DialogTitle-BRCsGR2a.js → DialogTitle-DAobFTUO.js} +1 -1
- package/dashboard/dist/assets/{FadeIn-B3oaxBze.js → FadeIn-D9ysa5Ls.js} +1 -1
- package/dashboard/dist/assets/{HeaderGithubStar-Dl3VFKq_.js → HeaderGithubStar-ByPo4mzv.js} +1 -1
- package/dashboard/dist/assets/{IpCheckPage-BJGOP6DZ.js → IpCheckPage-D5O_V9Ng.js} +1 -1
- package/dashboard/dist/assets/{LandingPage-CM30tqFJ.js → LandingPage-NXu1Hrnf.js} +1 -1
- package/dashboard/dist/assets/{LeaderboardAvatar-P3H4JJH7.js → LeaderboardAvatar-DR9Gca2Q.js} +1 -1
- package/dashboard/dist/assets/{LeaderboardPage-CMgku-JV.js → LeaderboardPage-B5OEb0j0.js} +2 -2
- package/dashboard/dist/assets/{LeaderboardProfileModal-1pDzkqlc.js → LeaderboardProfileModal-BP1VXIGY.js} +1 -1
- package/dashboard/dist/assets/{LeaderboardProfilePage-CXv6v78X.js → LeaderboardProfilePage-CkU4G9rM.js} +1 -1
- package/dashboard/dist/assets/{LimitsPage-DPmTlaMP.js → LimitsPage-PvibsSfB.js} +1 -1
- package/dashboard/dist/assets/{LocalOnlyNotice-DZ2JWpUi.js → LocalOnlyNotice-BPkaxkkB.js} +1 -1
- package/dashboard/dist/assets/{LoginPage-yZfMo0Bn.js → LoginPage-B4EzkwXW.js} +1 -1
- package/dashboard/dist/assets/{PopoverPopup-C7ClU-a4.js → PopoverPopup-MwcAIRI1.js} +1 -1
- package/dashboard/dist/assets/{ResetPasswordPage-DRxzMEet.js → ResetPasswordPage-Cjobkt-M.js} +1 -1
- package/dashboard/dist/assets/{Select-ShDjR08e.js → Select-ZtaYokvF.js} +1 -1
- package/dashboard/dist/assets/{SelectItemText-Cdb3AE2d.js → SelectItemText-CdrhC29S.js} +1 -1
- package/dashboard/dist/assets/{SettingsPage-KhtHkiXb.js → SettingsPage-DI0HkBW_.js} +1 -1
- package/dashboard/dist/assets/{SkillsPage-BfvTyaan.js → SkillsPage-CWFTNZQ0.js} +1 -1
- package/dashboard/dist/assets/{WidgetsPage-B9WHyvqX.js → WidgetsPage-CEP1Ew6X.js} +1 -1
- package/dashboard/dist/assets/{WrappedPage-BVY8Z7e8.js → WrappedPage-fiNsURtg.js} +1 -1
- package/dashboard/dist/assets/{agent-logos-w8DphLG5.js → agent-logos-B8iedQIH.js} +1 -1
- package/dashboard/dist/assets/{arrow-up-right-CCe_6-J8.js → arrow-up-right-Cjb3EZGs.js} +1 -1
- package/dashboard/dist/assets/check-D1HYoTs4.js +1 -0
- package/dashboard/dist/assets/{copy-B6Uad-NU.js → copy-DlDViZxV.js} +1 -1
- package/dashboard/dist/assets/{download-BYcyu1Bg.js → download-C5qoAiwD.js} +1 -1
- package/dashboard/dist/assets/{format-tokens-BFn66jct.js → format-tokens-e55_685N.js} +1 -1
- package/dashboard/dist/assets/{info-CTKpV2vu.js → info-BWJddEKJ.js} +1 -1
- package/dashboard/dist/assets/{main-DbLbrnIE.js → main-CjFvb-Kv.js} +2 -2
- package/dashboard/dist/assets/{use-community-stats-Bumdx05F.js → use-community-stats-Ds7fV_8y.js} +1 -1
- package/dashboard/dist/assets/{use-limits-display-prefs-DeSmHt-l.js → use-limits-display-prefs-CWumBLAN.js} +1 -1
- package/dashboard/dist/assets/{use-native-settings-CdTOidJ4.js → use-native-settings-D5oSEEA_.js} +1 -1
- package/dashboard/dist/assets/{use-ordered-list-DQoW_9df.js → use-ordered-list-DFEm87T2.js} +1 -1
- package/dashboard/dist/assets/{use-quality-per-dollar-pref-CwFs7F06.js → use-quality-per-dollar-pref-D5bAGtCm.js} +1 -1
- package/dashboard/dist/assets/{use-usage-limits-CKuCunJc.js → use-usage-limits-D5q4cnNd.js} +1 -1
- package/dashboard/dist/assets/{useCurrency-CG5fqlMR.js → useCurrency-CHAjKv53.js} +1 -1
- package/dashboard/dist/assets/{useScrollLock-DsnK0M2M.js → useScrollLock-DDedroUH.js} +1 -1
- package/dashboard/dist/index.html +1 -1
- package/dashboard/dist/share.html +1 -1
- package/package.json +1 -1
- package/src/commands/serve.js +13 -0
- package/src/commands/sync.js +8 -0
- package/src/lib/pricing/seed-snapshot.json +1 -1
- package/src/lib/telemetry.js +192 -0
- package/dashboard/dist/assets/DialogDescription-Bg5Mv-UG.js +0 -1
- package/dashboard/dist/assets/check-BuuQsfIt.js +0 -1
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Anonymous daily heartbeat for counting active installs.
|
|
5
|
+
*
|
|
6
|
+
* Privacy contract (documented in README "Privacy" section — keep in sync):
|
|
7
|
+
* the payload carries exactly four fields — a one-way sha256 hash of the
|
|
8
|
+
* machine id, the app version, `process.platform`, and the app shell
|
|
9
|
+
* (cli / macos / windows). No token counts, no model names, no usernames,
|
|
10
|
+
* no paths. Opt out with TOKENTRACKER_NO_TELEMETRY=1, the DO_NOT_TRACK
|
|
11
|
+
* standard, or `"telemetry": false` in config.json.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
const path = require("node:path");
|
|
15
|
+
const crypto = require("node:crypto");
|
|
16
|
+
|
|
17
|
+
const pkg = require("../../package.json");
|
|
18
|
+
const { readJson, writeJson } = require("./fs");
|
|
19
|
+
const { getOrCreateMachineId } = require("./machine-id");
|
|
20
|
+
const { resolveRuntimeConfig } = require("./runtime-config");
|
|
21
|
+
const {
|
|
22
|
+
decideAutoUpload,
|
|
23
|
+
recordUploadSuccess,
|
|
24
|
+
recordUploadFailure,
|
|
25
|
+
} = require("./upload-throttle");
|
|
26
|
+
|
|
27
|
+
const HEARTBEAT_FUNCTION_SLUG = "tokentracker-telemetry";
|
|
28
|
+
const HEARTBEAT_STATE_FILE = "telemetry.heartbeat.json";
|
|
29
|
+
const HEARTBEAT_INTERVAL_MS = 24 * 60 * 60 * 1000;
|
|
30
|
+
// The hash namespace is versioned so the anonymous id can be rotated fleet-wide
|
|
31
|
+
// by bumping the string. sha256(namespace + machineId) cannot be joined back to
|
|
32
|
+
// the cloud device rows (those store the machineId itself).
|
|
33
|
+
const MACHINE_HASH_NAMESPACE = "tokentracker-telemetry-v1:";
|
|
34
|
+
// Generous because some networks need 3-4s just for the TLS handshake to the
|
|
35
|
+
// cloud host; sync awaits this at most once per day so the worst-case stall
|
|
36
|
+
// is bounded and rare.
|
|
37
|
+
const DEFAULT_TIMEOUT_MS = 10_000;
|
|
38
|
+
const VALID_SHELLS = new Set(["cli", "macos", "windows"]);
|
|
39
|
+
|
|
40
|
+
const THROTTLE_CONFIG = {
|
|
41
|
+
intervalMs: HEARTBEAT_INTERVAL_MS,
|
|
42
|
+
jitterMsMax: 60_000,
|
|
43
|
+
backoffInitialMs: 60 * 60_000, // failures retry hourly at most
|
|
44
|
+
backoffMaxMs: HEARTBEAT_INTERVAL_MS,
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
function isTelemetryDisabled({ env = process.env, config = {} } = {}) {
|
|
48
|
+
if (isTruthyFlag(env?.TOKENTRACKER_NO_TELEMETRY)) return true;
|
|
49
|
+
if (isTruthyFlag(env?.DO_NOT_TRACK)) return true;
|
|
50
|
+
if (config && config.telemetry === false) return true;
|
|
51
|
+
return false;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function isTruthyFlag(value) {
|
|
55
|
+
if (typeof value !== "string") return false;
|
|
56
|
+
const v = value.trim().toLowerCase();
|
|
57
|
+
return v === "1" || v === "true";
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function hashMachineId(machineId) {
|
|
61
|
+
return crypto
|
|
62
|
+
.createHash("sha256")
|
|
63
|
+
.update(`${MACHINE_HASH_NAMESPACE}${machineId}`)
|
|
64
|
+
.digest("hex");
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function resolveShell(env = process.env) {
|
|
68
|
+
const raw = typeof env?.TOKENTRACKER_APP_SHELL === "string"
|
|
69
|
+
? env.TOKENTRACKER_APP_SHELL.trim().toLowerCase()
|
|
70
|
+
: "";
|
|
71
|
+
return VALID_SHELLS.has(raw) ? raw : "cli";
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function buildHeartbeatPayload({ machineId, version = pkg.version, platform = process.platform, env = process.env }) {
|
|
75
|
+
return {
|
|
76
|
+
machine_hash: hashMachineId(machineId),
|
|
77
|
+
app_version: String(version || "").slice(0, 32),
|
|
78
|
+
platform: String(platform || "unknown").slice(0, 16),
|
|
79
|
+
shell: resolveShell(env),
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Send the daily heartbeat if allowed. Never throws; every failure path is a
|
|
85
|
+
* silent no-op so serve/sync flow is never affected. Returns a small status
|
|
86
|
+
* object for tests and debug logging.
|
|
87
|
+
*/
|
|
88
|
+
async function maybeSendHeartbeat({
|
|
89
|
+
trackerDir,
|
|
90
|
+
fetchImpl = fetch,
|
|
91
|
+
nowMs = Date.now(),
|
|
92
|
+
env = process.env,
|
|
93
|
+
timeoutMs = DEFAULT_TIMEOUT_MS,
|
|
94
|
+
version,
|
|
95
|
+
} = {}) {
|
|
96
|
+
try {
|
|
97
|
+
if (!trackerDir) return { sent: false, reason: "no-tracker-dir" };
|
|
98
|
+
|
|
99
|
+
// Never phone home from the node test runner: many tests drive cmdSync /
|
|
100
|
+
// cmdServe directly from sandboxed temp homes, and their fresh throttle
|
|
101
|
+
// state would let a real POST through to production on every run. Tests
|
|
102
|
+
// that exercise this module inject their own env without this marker.
|
|
103
|
+
if (env?.NODE_TEST_CONTEXT !== undefined) {
|
|
104
|
+
return { sent: false, reason: "test-env" };
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
const configPath = path.join(trackerDir, "config.json");
|
|
108
|
+
const config = (await readJson(configPath)) || {};
|
|
109
|
+
if (isTelemetryDisabled({ env, config })) {
|
|
110
|
+
return { sent: false, reason: "opt-out" };
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
const statePath = path.join(trackerDir, HEARTBEAT_STATE_FILE);
|
|
114
|
+
const state = await readJson(statePath);
|
|
115
|
+
const decision = decideAutoUpload({
|
|
116
|
+
nowMs,
|
|
117
|
+
pendingBytes: 1, // heartbeats always have "one ping pending"
|
|
118
|
+
state,
|
|
119
|
+
config: THROTTLE_CONFIG,
|
|
120
|
+
});
|
|
121
|
+
if (!decision.allowed) {
|
|
122
|
+
return { sent: false, reason: "throttled", blockedUntilMs: decision.blockedUntilMs };
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
const machineId = getOrCreateMachineId(path.join(trackerDir, "queue.jsonl"));
|
|
126
|
+
if (!machineId) return { sent: false, reason: "no-machine-id" };
|
|
127
|
+
|
|
128
|
+
const runtime = resolveRuntimeConfig({ config, env });
|
|
129
|
+
const payload = buildHeartbeatPayload({ machineId, version, env });
|
|
130
|
+
|
|
131
|
+
const controller = new AbortController();
|
|
132
|
+
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
133
|
+
let response;
|
|
134
|
+
try {
|
|
135
|
+
response = await fetchImpl(`${runtime.baseUrl}/functions/${HEARTBEAT_FUNCTION_SLUG}`, {
|
|
136
|
+
method: "POST",
|
|
137
|
+
headers: {
|
|
138
|
+
"Content-Type": "application/json",
|
|
139
|
+
apikey: runtime.anonKey,
|
|
140
|
+
},
|
|
141
|
+
body: JSON.stringify(payload),
|
|
142
|
+
signal: controller.signal,
|
|
143
|
+
});
|
|
144
|
+
} finally {
|
|
145
|
+
clearTimeout(timer);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
if (!response || !response.ok) {
|
|
149
|
+
const failedState = recordUploadFailure({
|
|
150
|
+
nowMs,
|
|
151
|
+
state,
|
|
152
|
+
error: { message: `heartbeat http ${response?.status || "error"}`, status: response?.status },
|
|
153
|
+
config: THROTTLE_CONFIG,
|
|
154
|
+
});
|
|
155
|
+
await writeJson(statePath, failedState);
|
|
156
|
+
return { sent: false, reason: "http-error", status: response?.status };
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
const nextState = recordUploadSuccess({ nowMs, state, config: THROTTLE_CONFIG });
|
|
160
|
+
await writeJson(statePath, nextState);
|
|
161
|
+
return { sent: true, reason: "sent" };
|
|
162
|
+
} catch (e) {
|
|
163
|
+
// Includes abort/network errors: record backoff if we can, stay silent.
|
|
164
|
+
try {
|
|
165
|
+
const statePath = path.join(trackerDir, HEARTBEAT_STATE_FILE);
|
|
166
|
+
const state = await readJson(statePath);
|
|
167
|
+
await writeJson(
|
|
168
|
+
statePath,
|
|
169
|
+
recordUploadFailure({
|
|
170
|
+
nowMs,
|
|
171
|
+
state,
|
|
172
|
+
error: { message: String(e?.message || e) },
|
|
173
|
+
config: THROTTLE_CONFIG,
|
|
174
|
+
}),
|
|
175
|
+
);
|
|
176
|
+
} catch {
|
|
177
|
+
// Even state persistence failed — nothing else to do.
|
|
178
|
+
}
|
|
179
|
+
return { sent: false, reason: "error" };
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
module.exports = {
|
|
184
|
+
HEARTBEAT_FUNCTION_SLUG,
|
|
185
|
+
HEARTBEAT_STATE_FILE,
|
|
186
|
+
HEARTBEAT_INTERVAL_MS,
|
|
187
|
+
isTelemetryDisabled,
|
|
188
|
+
hashMachineId,
|
|
189
|
+
resolveShell,
|
|
190
|
+
buildHeartbeatPayload,
|
|
191
|
+
maybeSendHeartbeat,
|
|
192
|
+
};
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import{r as n,b9 as a}from"./main-DbLbrnIE.js";import{u as p}from"./DialogTitle-BRCsGR2a.js";import{u as c}from"./useScrollLock-DsnK0M2M.js";const x=n.forwardRef(function(e,r){const{render:l,className:u,style:m,id:s,...t}=e,{store:i}=p(),o=c(s);return i.useSyncedValueWithCleanup("descriptionElementId",o),a("p",e,{ref:r,props:[{id:o},t]})});export{x as D};
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import{c}from"./main-DbLbrnIE.js";const e=[["path",{d:"M20 6 9 17l-5-5",key:"1gmf2c"}]],t=c("check",e);export{t as C};
|