tokentracker-cli 0.96.0 → 0.96.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.de.md +2 -2
- package/README.ja.md +2 -2
- package/README.ko.md +2 -2
- package/README.md +4 -2
- package/README.zh-CN.md +3 -3
- package/dashboard/dist/assets/{AchievementBadge-Bb2ljquW.js → AchievementBadge-CgEitoX1.js} +1 -1
- package/dashboard/dist/assets/{AchievementsPage-ByUP2x8a.js → AchievementsPage-dnnea0JH.js} +2 -2
- package/dashboard/dist/assets/{AchievementsSection-_L6sCLTw.js → AchievementsSection-CJqG0UP7.js} +1 -1
- package/dashboard/dist/assets/{BadgeDetailModal-B9n_-45B.js → BadgeDetailModal-BLV6SJoI.js} +1 -1
- package/dashboard/dist/assets/{BotAnimated-lWmg-aZg.js → BotAnimated-Cm78yEbz.js} +1 -1
- package/dashboard/dist/assets/{Card-aSp259T0.js → Card-IPGSYT6O.js} +1 -1
- package/dashboard/dist/assets/{ClawdAnimated-Cy-XaTXW.js → ClawdAnimated-04w1U1sm.js} +2 -2
- package/dashboard/dist/assets/{CommandPalette-CNwtKCUm.js → CommandPalette-gSSa1V_s.js} +1 -1
- package/dashboard/dist/assets/{CommunityStatsModal-DJSezaPV.js → CommunityStatsModal-CICwUz14.js} +1 -1
- package/dashboard/dist/assets/{ConfirmModal-CVjmhZd6.js → ConfirmModal-DaWC0cY7.js} +1 -1
- package/dashboard/dist/assets/{Controls-BIj6yVxE.js → Controls-BDihA03F.js} +1 -1
- package/dashboard/dist/assets/DashboardPage-hV7wncKF.js +1 -0
- package/dashboard/dist/assets/{DevicePage-ptsI2fZc.js → DevicePage-BPpo_eiY.js} +1 -1
- package/dashboard/dist/assets/{DialogClose-D1pLoZ56.js → DialogClose-C_dF-r3s.js} +1 -1
- package/dashboard/dist/assets/DialogDescription-GClOSCaE.js +1 -0
- package/dashboard/dist/assets/{DialogTitle-DfSBUjWf.js → DialogTitle-Buh_5688.js} +1 -1
- package/dashboard/dist/assets/{FadeIn-DBn4L_BV.js → FadeIn-DmI9PRLa.js} +1 -1
- package/dashboard/dist/assets/{HeaderGithubStar-EJLBwEqp.js → HeaderGithubStar-CBO4cY7i.js} +1 -1
- package/dashboard/dist/assets/{HoverTooltip-DaSrZhYd.js → HoverTooltip-CPFGhi9K.js} +1 -1
- package/dashboard/dist/assets/{IpCheckPage-diQTVaYt.js → IpCheckPage-Do7w_6z6.js} +1 -1
- package/dashboard/dist/assets/{LandingPage-BqCxjwar.js → LandingPage-at_Ti1ZE.js} +2 -2
- package/dashboard/dist/assets/{LeaderboardAvatar-CQImCsPN.js → LeaderboardAvatar-BH1cnKPp.js} +1 -1
- package/dashboard/dist/assets/{LeaderboardPage-D2ZDUi_O.js → LeaderboardPage-Cd30aXwL.js} +2 -2
- package/dashboard/dist/assets/{LeaderboardProfileModal-DHvADX1d.js → LeaderboardProfileModal-CVsti_HN.js} +3 -3
- package/dashboard/dist/assets/{LeaderboardProfilePage-CJsedB28.js → LeaderboardProfilePage-BfSpDM9e.js} +1 -1
- package/dashboard/dist/assets/LimitsPage-_-BadDAj.js +8 -0
- package/dashboard/dist/assets/{LocalOnlyNotice-Cmnvu9tI.js → LocalOnlyNotice-9-vRgsdn.js} +1 -1
- package/dashboard/dist/assets/{LoginCard-BQmKkD7X.js → LoginCard-DM_ZdFNl.js} +1 -1
- package/dashboard/dist/assets/{LoginModal-2oPROePZ.js → LoginModal-BaNMznwh.js} +1 -1
- package/dashboard/dist/assets/{LoginPage-aqdUJoQK.js → LoginPage-BMMthUf4.js} +1 -1
- package/dashboard/dist/assets/{PetPage-Bn3p_o5r.js → PetPage-Iq8Hswai.js} +1 -1
- package/dashboard/dist/assets/{PopoverPopup-DXD_YdiB.js → PopoverPopup-DXr3JxVV.js} +1 -1
- package/dashboard/dist/assets/{ProviderIcon-BZZ45-gY.js → ProviderIcon-sb2keSY2.js} +1 -1
- package/dashboard/dist/assets/{ResetPasswordPage-Bn5ITiHQ.js → ResetPasswordPage-Ca4SULSV.js} +1 -1
- package/dashboard/dist/assets/{SegmentedControl-hGXboQZy.js → SegmentedControl-Bbsn-qvZ.js} +1 -1
- package/dashboard/dist/assets/{Select-BP8aVRZ1.js → Select-vD-yj-qa.js} +1 -1
- package/dashboard/dist/assets/{SelectItemText-Odx47kND.js → SelectItemText-CRtFEuvg.js} +1 -1
- package/dashboard/dist/assets/{ServiceStatusPage-DUueam0N.js → ServiceStatusPage-BPS8KJ3a.js} +1 -1
- package/dashboard/dist/assets/{SessionsPage-BA9IFqQ1.js → SessionsPage-C1yt3TMZ.js} +1 -1
- package/dashboard/dist/assets/SettingsPage-CWVHrMer.js +2 -0
- package/dashboard/dist/assets/{SkillsPage-Ds2vH_PG.js → SkillsPage-DGWEH1xm.js} +1 -1
- package/dashboard/dist/assets/{TokenGalaxy-CWBR4jnB.js → TokenGalaxy-DKvbJbnv.js} +1 -1
- package/dashboard/dist/assets/{ToolbarRootContext-Bmg56vYz.js → ToolbarRootContext-D8yov1dW.js} +1 -1
- package/dashboard/dist/assets/{TrendMonitor-BQzcllvg.js → TrendMonitor-YF5C_-L1.js} +1 -1
- package/dashboard/dist/assets/{WidgetsPage-Bu3tHDjO.js → WidgetsPage-BKV3DgXg.js} +1 -1
- package/dashboard/dist/assets/{WrappedPage-BrAMJhgy.js → WrappedPage-s4IRoLf7.js} +1 -1
- package/dashboard/dist/assets/__vite-browser-external-CvMMIjhi.css +1 -0
- package/dashboard/dist/assets/__vite-browser-external-_LUatzwU.js +45 -0
- package/dashboard/dist/assets/{agent-logos-B8lT-HcI.js → agent-logos-Hbt-wyl2.js} +1 -1
- package/dashboard/dist/assets/{arrow-up-right-rpBCOTg2.js → arrow-up-right-KeIpKlw3.js} +1 -1
- package/dashboard/dist/assets/{check-DIKw7TYH.js → check-_QKyY1v_.js} +1 -1
- package/dashboard/dist/assets/{composite-_NeXO0lv.js → composite-BidfcMeb.js} +1 -1
- package/dashboard/dist/assets/{copy-CsfAtL7P.js → copy-DpLM3QMD.js} +1 -1
- package/dashboard/dist/assets/{download-DM1W9pIW.js → download-C4NX-JnW.js} +1 -1
- package/dashboard/dist/assets/{external-link-CwbuMbeQ.js → external-link-CG-7FcOF.js} +1 -1
- package/dashboard/dist/assets/{flame-DcYbLdhE.js → flame-CkWid7Ji.js} +1 -1
- package/dashboard/dist/assets/{format-tokens-CEvr3dNy.js → format-tokens-CbwWhioZ.js} +1 -1
- package/dashboard/dist/assets/{icons-DaGMVsJ5.js → icons-BZq17rU3.js} +1 -1
- package/dashboard/dist/assets/{index-ChZkJjpk.js → index-CR9tDiiC.js} +1 -1
- package/dashboard/dist/assets/{index-BVVLPj3N.js → index-CSwEiUn4.js} +1 -1
- package/dashboard/dist/assets/{index-T7OjmEuX.js → index-DDGLE0dz.js} +1 -1
- package/dashboard/dist/assets/{info-CAjN3dX4.js → info-mkeRjzqc.js} +1 -1
- package/dashboard/dist/assets/limits-providers-DRkcWQup.js +1 -0
- package/dashboard/dist/assets/{link-2-DaWH2Tyc.js → link-2-BjMw8csC.js} +1 -1
- package/dashboard/dist/assets/{loader-circle-CnF8qPi1.js → loader-circle-cGWOJnWn.js} +1 -1
- package/dashboard/dist/assets/{maximize-2-B-2Pnj6g.js → maximize-2-Cg_S73tD.js} +1 -1
- package/dashboard/dist/assets/{modifiers.esm-BoDOvUKl.js → modifiers.esm-DNxTWxId.js} +1 -1
- package/dashboard/dist/assets/{monitor-smartphone-DDgwxG_E.js → monitor-smartphone-BvE9Tt2U.js} +1 -1
- package/dashboard/dist/assets/{plus-DIc1YUWB.js → plus-BC2wdPRr.js} +1 -1
- package/dashboard/dist/assets/{provider-display-yeNsVCO_.js → provider-display-B7GggHQy.js} +1 -1
- package/dashboard/dist/assets/{react-DZqS6YTX.js → react-D0kcslTz.js} +1 -1
- package/dashboard/dist/assets/{search-D6tEppYJ.js → search-BuBNF3E3.js} +1 -1
- package/dashboard/dist/assets/{skills-api-vsA1rc1Z.js → skills-api-DxAN5pNx.js} +1 -1
- package/dashboard/dist/assets/{sortable.esm-DDCnS1HI.js → sortable.esm-DoqMcEX-.js} +1 -1
- package/dashboard/dist/assets/{store-Crne4DqF.js → store-Cg1HeUFM.js} +1 -1
- package/dashboard/dist/assets/{terminal-Ca7dkLMF.js → terminal-COWFUJdU.js} +1 -1
- package/dashboard/dist/assets/{trash-2-CjEAS5ic.js → trash-2-Cqjki3RI.js} +1 -1
- package/dashboard/dist/assets/{use-community-stats-BHx2YxyK.js → use-community-stats-DsrlOPDM.js} +1 -1
- package/dashboard/dist/assets/{use-latest-request-guard-BLGbXQdf.js → use-latest-request-guard-ryRFL2gx.js} +1 -1
- package/dashboard/dist/assets/{use-limits-display-prefs-DBkAp6ob.js → use-limits-display-prefs-DkSuu12m.js} +1 -1
- package/dashboard/dist/assets/{use-ordered-list-BTfW5M9u.js → use-ordered-list-D-Q86R1b.js} +1 -1
- package/dashboard/dist/assets/use-reduced-motion-D7NNqavo.js +1 -0
- package/dashboard/dist/assets/{use-session-efficiency-pref-DBmd-MqH.js → use-session-efficiency-pref-CrNz45y7.js} +1 -1
- package/dashboard/dist/assets/{use-transform-Bp5XrwaQ.js → use-transform-CoGM21px.js} +1 -1
- package/dashboard/dist/assets/{use-usage-limits-BmqRcyhH.js → use-usage-limits-C_6L1sO5.js} +1 -1
- package/dashboard/dist/assets/{useCurrency-B0YsRDj-.js → useCurrency-CgCfDktb.js} +1 -1
- package/dashboard/dist/assets/{useTokenFormat-PP3D0Ket.js → useTokenFormat-QKtzCKTL.js} +1 -1
- package/dashboard/dist/assets/{zap-BVOm1aBH.js → zap-CTIobzgU.js} +1 -1
- package/dashboard/dist/brand-logos/commandcode.svg +1 -0
- package/dashboard/dist/index.html +5 -5
- package/dashboard/dist/ip-check.html +2 -2
- package/dashboard/dist/leaderboard.html +2 -2
- package/dashboard/dist/share.html +2 -2
- package/package.json +1 -1
- package/src/commands/status.js +4 -4
- package/src/commands/sync.js +5 -3
- package/src/lib/ark-agent-plan-limits.js +4 -3
- package/src/lib/ark-coding-plan-limits.js +6 -8
- package/src/lib/codex-rollout-parser.js +2 -2
- package/src/lib/command-runner.js +13 -0
- package/src/lib/commandcode-limits.js +317 -0
- package/src/lib/local-api.js +3 -0
- package/src/lib/pricing/curated-overrides.json +11 -0
- package/src/lib/pricing/index.js +6 -7
- package/src/lib/pricing/seed-snapshot.json +1 -1
- package/src/lib/rollout.js +183 -11
- package/src/lib/session-analytics.js +2 -2
- package/src/lib/usage-limits.js +153 -14
- package/dashboard/dist/assets/DashboardPage-BjYZLmqQ.js +0 -1
- package/dashboard/dist/assets/DialogDescription-KY7X94CG.js +0 -1
- package/dashboard/dist/assets/LimitsPage-CAwff24b.js +0 -6
- package/dashboard/dist/assets/SettingsPage-DLOHHB4f.js +0 -2
- package/dashboard/dist/assets/__vite-browser-external-4IGm4Djn.js +0 -45
- package/dashboard/dist/assets/__vite-browser-external-CCMdjCX2.css +0 -1
- package/dashboard/dist/assets/limits-providers-jn7Yln_V.js +0 -1
- package/dashboard/dist/assets/use-reduced-motion-D_kUnDs4.js +0 -1
|
@@ -0,0 +1,317 @@
|
|
|
1
|
+
// CommandCode subscription usage limits.
|
|
2
|
+
//
|
|
3
|
+
// Data source (authoritative, mirrors the CLI's own `/usage` overlay in
|
|
4
|
+
// command-code 1.49.1):
|
|
5
|
+
//
|
|
6
|
+
// GET https://api.commandcode.ai/alpha/whoami?limits=1
|
|
7
|
+
// GET https://api.commandcode.ai/alpha/billing/credits[?orgId=...]
|
|
8
|
+
// GET https://api.commandcode.ai/alpha/billing/subscriptions[?orgId=...]
|
|
9
|
+
//
|
|
10
|
+
// with `Authorization: Bearer <apiKey>`. The credits payload carries
|
|
11
|
+
// `windowLimits: { limited, fiveHour: { used, cap, resetAt }, weekly: {...} }`
|
|
12
|
+
// (resetAt is epoch milliseconds) plus a `credits` balance object; the
|
|
13
|
+
// subscription payload carries `{ data: { planId, status, ... } }`.
|
|
14
|
+
//
|
|
15
|
+
// Auth resolution mirrors the CLI: `COMMAND_CODE_API_KEY` wins, otherwise the
|
|
16
|
+
// `apiKey` field of `~/.commandcode/auth.json` (prod; written 0600 by
|
|
17
|
+
// `cmd login`, never edited by hand). No key -> `{ configured: false }` so the
|
|
18
|
+
// Limits panel hides the section instead of erroring.
|
|
19
|
+
//
|
|
20
|
+
// The API exposes 5h + weekly rolling caps; extra pay-as-you-go credits
|
|
21
|
+
// bypass the caps server-side, so the reported used/cap needs no adjustment.
|
|
22
|
+
|
|
23
|
+
const fs = require("node:fs");
|
|
24
|
+
const path = require("node:path");
|
|
25
|
+
|
|
26
|
+
const COMMANDCODE_API_BASE_URL = "https://api.commandcode.ai";
|
|
27
|
+
const COMMANDCODE_API_KEY_ENV = "COMMAND_CODE_API_KEY";
|
|
28
|
+
const COMMANDCODE_AUTH_DIRNAME = ".commandcode";
|
|
29
|
+
const COMMANDCODE_AUTH_FILENAME = "auth.json";
|
|
30
|
+
|
|
31
|
+
// Request timeouts are enforced by the caller: usage-limits.js passes a
|
|
32
|
+
// withFetchTimeout-wrapped fetchImpl (per-request AbortSignal timeout), so this
|
|
33
|
+
// module takes no timeout of its own.
|
|
34
|
+
|
|
35
|
+
// Rolling window lengths are plan-independent and documented
|
|
36
|
+
// (https://commandcode.ai/docs/resources/usage-limits): 5h + 7d.
|
|
37
|
+
const COMMANDCODE_SESSION_WINDOW_SECONDS = 5 * 60 * 60;
|
|
38
|
+
const COMMANDCODE_WEEKLY_WINDOW_SECONDS = 7 * 24 * 60 * 60;
|
|
39
|
+
|
|
40
|
+
function isNonEmptyString(value) {
|
|
41
|
+
return typeof value === "string" && value.trim().length > 0;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// Explicit `home`/env HOME only — never os.homedir() — so a synthetic test env
|
|
45
|
+
// (no home, no HOME) discovers nothing and tests stay isolated from the
|
|
46
|
+
// developer's real ~/.commandcode (cf. resolveOpencodeDataDir).
|
|
47
|
+
function resolveCommandcodeHome({ home, env = process.env } = {}) {
|
|
48
|
+
if (isNonEmptyString(home)) return home.trim();
|
|
49
|
+
const envHome = env && typeof env === "object" ? env.HOME : null;
|
|
50
|
+
if (isNonEmptyString(envHome)) return envHome.trim();
|
|
51
|
+
return null;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function resolveCommandcodeAuthPath({ home, env = process.env } = {}) {
|
|
55
|
+
const dir = resolveCommandcodeHome({ home, env });
|
|
56
|
+
if (!dir) return null;
|
|
57
|
+
return path.join(dir, COMMANDCODE_AUTH_DIRNAME, COMMANDCODE_AUTH_FILENAME);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function readCommandcodeApiKey({ home, env = process.env } = {}) {
|
|
61
|
+
const fromEnv =
|
|
62
|
+
env && typeof env === "object" ? env[COMMANDCODE_API_KEY_ENV] : null;
|
|
63
|
+
if (isNonEmptyString(fromEnv)) return fromEnv.trim();
|
|
64
|
+
const authPath = resolveCommandcodeAuthPath({ home, env });
|
|
65
|
+
if (!authPath) return null;
|
|
66
|
+
let raw;
|
|
67
|
+
try {
|
|
68
|
+
raw = fs.readFileSync(authPath, "utf8");
|
|
69
|
+
} catch {
|
|
70
|
+
return null;
|
|
71
|
+
}
|
|
72
|
+
let parsed;
|
|
73
|
+
try {
|
|
74
|
+
parsed = JSON.parse(raw);
|
|
75
|
+
} catch {
|
|
76
|
+
return null;
|
|
77
|
+
}
|
|
78
|
+
return isNonEmptyString(parsed?.apiKey) ? parsed.apiKey.trim() : null;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function normalizeResetAt(value) {
|
|
82
|
+
if (value === null || value === undefined || value === "") return null;
|
|
83
|
+
if (
|
|
84
|
+
typeof value === "number" ||
|
|
85
|
+
/^\d+(?:\.\d+)?$/.test(String(value).trim())
|
|
86
|
+
) {
|
|
87
|
+
const raw = Number(value);
|
|
88
|
+
if (!Number.isFinite(raw) || raw <= 0) return null;
|
|
89
|
+
const milliseconds = raw > 10_000_000_000 ? raw : raw * 1000;
|
|
90
|
+
const date = new Date(milliseconds);
|
|
91
|
+
return Number.isFinite(date.getTime()) ? date.toISOString() : null;
|
|
92
|
+
}
|
|
93
|
+
const timestamp = Date.parse(String(value));
|
|
94
|
+
return Number.isFinite(timestamp) ? new Date(timestamp).toISOString() : null;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function clampPercent(value) {
|
|
98
|
+
if (value === null || value === undefined || value === "") return null;
|
|
99
|
+
const n = Number(value);
|
|
100
|
+
if (!Number.isFinite(n)) return null;
|
|
101
|
+
if (n <= 0) return 0;
|
|
102
|
+
if (n >= 100) return 100;
|
|
103
|
+
return n;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
function buildWindow({ usedPercent, resetAt, windowSeconds = null }) {
|
|
107
|
+
const pct = clampPercent(usedPercent);
|
|
108
|
+
if (pct === null) return null;
|
|
109
|
+
const window = {
|
|
110
|
+
used_percent: pct,
|
|
111
|
+
reset_at: typeof resetAt === "string" && resetAt ? resetAt : null,
|
|
112
|
+
};
|
|
113
|
+
if (Number.isFinite(windowSeconds) && windowSeconds > 0) {
|
|
114
|
+
window.limit_window_seconds = windowSeconds;
|
|
115
|
+
}
|
|
116
|
+
return window;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// Upstream window rows are `{ used, cap, resetAt }` (epoch ms). Tolerate the
|
|
120
|
+
// snake_case spellings other providers use so a server rename stays a miss on
|
|
121
|
+
// one window, not a crash.
|
|
122
|
+
function normalizeCommandcodeWindow(raw, windowSeconds) {
|
|
123
|
+
if (!raw || typeof raw !== "object") return null;
|
|
124
|
+
// Missing or malformed usage is unknown, not an unused (0%) window.
|
|
125
|
+
const numeric = (value) => typeof value === "number"
|
|
126
|
+
|| (typeof value === "string" && value.trim() !== "" && Number.isFinite(Number(value)));
|
|
127
|
+
if (!numeric(raw.used) || !numeric(raw.cap ?? raw.total ?? raw.limit)) return null;
|
|
128
|
+
const used = Number(raw.used);
|
|
129
|
+
const cap = Number(raw.cap ?? raw.total ?? raw.limit);
|
|
130
|
+
if (!Number.isFinite(used) || !Number.isFinite(cap) || cap <= 0 || used < 0)
|
|
131
|
+
return null;
|
|
132
|
+
return buildWindow({
|
|
133
|
+
usedPercent: (used / cap) * 100,
|
|
134
|
+
resetAt: normalizeResetAt(raw.resetAt ?? raw.reset_at ?? raw.reset),
|
|
135
|
+
windowSeconds,
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// Plan tiers (longest prefix first, mirroring the CLI's own getPlanInfo
|
|
140
|
+
// matching in command-code). Unknown ids -> null, so the Limits panel renders
|
|
141
|
+
// the bare "Command Code" brand (cf. Kimi/Grok — a raw id like
|
|
142
|
+
// `individual-pro-v1` reads as garbage).
|
|
143
|
+
const COMMANDCODE_PLAN_TIERS = [
|
|
144
|
+
["individual-pro-v1", "Pro"],
|
|
145
|
+
["individual-provider", "Provider"],
|
|
146
|
+
["individual-goat", "GOAT"],
|
|
147
|
+
["individual-go", "Go"],
|
|
148
|
+
["individual-pro", "Pro"],
|
|
149
|
+
["individual-max", "Max"],
|
|
150
|
+
["individual-ultra", "Ultra"],
|
|
151
|
+
["teams-pro", "Teams Pro"],
|
|
152
|
+
];
|
|
153
|
+
|
|
154
|
+
function deriveCommandcodePlanLabel(rawPlanId) {
|
|
155
|
+
if (!isNonEmptyString(rawPlanId)) return null;
|
|
156
|
+
const normalized = rawPlanId.trim().toLowerCase().replace(/_/g, "-");
|
|
157
|
+
for (const [prefix, label] of COMMANDCODE_PLAN_TIERS) {
|
|
158
|
+
if (normalized.startsWith(prefix)) return label;
|
|
159
|
+
}
|
|
160
|
+
return null;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
function normalizeCommandcodeWindowLimits(windowLimits) {
|
|
164
|
+
if (!windowLimits || typeof windowLimits !== "object") return null;
|
|
165
|
+
const fiveHour = normalizeCommandcodeWindow(
|
|
166
|
+
windowLimits.fiveHour ?? windowLimits.five_hour,
|
|
167
|
+
COMMANDCODE_SESSION_WINDOW_SECONDS,
|
|
168
|
+
);
|
|
169
|
+
const weekly = normalizeCommandcodeWindow(
|
|
170
|
+
windowLimits.weekly,
|
|
171
|
+
COMMANDCODE_WEEKLY_WINDOW_SECONDS,
|
|
172
|
+
);
|
|
173
|
+
if (!fiveHour && !weekly) return null;
|
|
174
|
+
|
|
175
|
+
return { fiveHour, weekly };
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
// The Bearer apiKey travels on every request, so the origin must be https.
|
|
179
|
+
// Plain http is accepted for loopback only (local staging/dev servers).
|
|
180
|
+
function resolveCommandcodeOrigin(baseUrl) {
|
|
181
|
+
const origin = String(baseUrl || COMMANDCODE_API_BASE_URL).replace(
|
|
182
|
+
/\/+$/,
|
|
183
|
+
"",
|
|
184
|
+
);
|
|
185
|
+
let parsed;
|
|
186
|
+
try {
|
|
187
|
+
parsed = new URL(origin);
|
|
188
|
+
} catch {
|
|
189
|
+
throw new Error("CommandCode API base URL is invalid.");
|
|
190
|
+
}
|
|
191
|
+
const loopback = /^(localhost|127\.\d+\.\d+\.\d+|\[::1\])$/i.test(
|
|
192
|
+
parsed.hostname,
|
|
193
|
+
);
|
|
194
|
+
if (parsed.protocol !== "https:" && !loopback) {
|
|
195
|
+
throw new Error(
|
|
196
|
+
"CommandCode API base URL must use https (http is loopback-only).",
|
|
197
|
+
);
|
|
198
|
+
}
|
|
199
|
+
return origin;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
function commandcodeHeaders(apiKey) {
|
|
203
|
+
return {
|
|
204
|
+
Accept: "application/json",
|
|
205
|
+
Authorization: `Bearer ${apiKey}`,
|
|
206
|
+
};
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
async function fetchCommandcodeJson({ url, apiKey, fetchImpl, label }) {
|
|
210
|
+
let response;
|
|
211
|
+
try {
|
|
212
|
+
response = await fetchImpl(url, { headers: commandcodeHeaders(apiKey) });
|
|
213
|
+
} catch (error) {
|
|
214
|
+
throw new Error(
|
|
215
|
+
`CommandCode ${label} request failed: ${error?.message || "network error"}`,
|
|
216
|
+
);
|
|
217
|
+
}
|
|
218
|
+
if (response?.status === 401 || response?.status === 403) {
|
|
219
|
+
const error = new Error(
|
|
220
|
+
"CommandCode token expired — run `cmd login` (macOS/Linux) or `cmdc auth login` (Windows) to refresh.",
|
|
221
|
+
);
|
|
222
|
+
error.code = "AUTH_EXPIRED";
|
|
223
|
+
throw error;
|
|
224
|
+
}
|
|
225
|
+
if (!response?.ok) {
|
|
226
|
+
throw new Error(
|
|
227
|
+
`CommandCode ${label} request failed (HTTP ${response?.status ?? "?"}).`,
|
|
228
|
+
);
|
|
229
|
+
}
|
|
230
|
+
try {
|
|
231
|
+
return await response.json();
|
|
232
|
+
} catch {
|
|
233
|
+
throw new Error(`${label} response was not JSON.`);
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
function withOrgParam(route, orgId) {
|
|
238
|
+
if (orgId === null || orgId === undefined || String(orgId).trim() === "")
|
|
239
|
+
return route;
|
|
240
|
+
return `${route}?orgId=${encodeURIComponent(String(orgId).trim())}`;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
// Returns `{ configured: false }` without credentials, or the normalized limits
|
|
244
|
+
// object on success. Auth expiry throws with `code: "AUTH_EXPIRED"` and every
|
|
245
|
+
// other transport/schema failure throws a readable Error — the aggregator in
|
|
246
|
+
// usage-limits.js converts both into the panel row.
|
|
247
|
+
async function fetchCommandcodeLimits({
|
|
248
|
+
home,
|
|
249
|
+
env = process.env,
|
|
250
|
+
fetchImpl = fetch,
|
|
251
|
+
baseUrl = COMMANDCODE_API_BASE_URL,
|
|
252
|
+
} = {}) {
|
|
253
|
+
const apiKey = readCommandcodeApiKey({ home, env });
|
|
254
|
+
if (!apiKey) return { configured: false };
|
|
255
|
+
|
|
256
|
+
const origin = resolveCommandcodeOrigin(baseUrl);
|
|
257
|
+
const whoami = await fetchCommandcodeJson({
|
|
258
|
+
url: `${origin}/alpha/whoami?limits=1`,
|
|
259
|
+
apiKey,
|
|
260
|
+
fetchImpl,
|
|
261
|
+
label: "account lookup",
|
|
262
|
+
});
|
|
263
|
+
const orgId = whoami?.data?.org?.id ?? whoami?.org?.id ?? null;
|
|
264
|
+
|
|
265
|
+
const [creditsBody, subscriptionBody] = await Promise.all([
|
|
266
|
+
fetchCommandcodeJson({
|
|
267
|
+
url: `${origin}${withOrgParam("/alpha/billing/credits", orgId)}`,
|
|
268
|
+
apiKey,
|
|
269
|
+
fetchImpl,
|
|
270
|
+
label: "credits lookup",
|
|
271
|
+
}),
|
|
272
|
+
fetchCommandcodeJson({
|
|
273
|
+
url: `${origin}${withOrgParam("/alpha/billing/subscriptions", orgId)}`,
|
|
274
|
+
apiKey,
|
|
275
|
+
fetchImpl,
|
|
276
|
+
label: "subscription lookup",
|
|
277
|
+
}),
|
|
278
|
+
]);
|
|
279
|
+
|
|
280
|
+
const windows = normalizeCommandcodeWindowLimits(creditsBody?.windowLimits);
|
|
281
|
+
if (!windows) {
|
|
282
|
+
throw new Error("CommandCode credits response is missing windowLimits.");
|
|
283
|
+
}
|
|
284
|
+
const subscription = subscriptionBody?.data ?? subscriptionBody ?? null;
|
|
285
|
+
const planId = isNonEmptyString(subscription?.planId)
|
|
286
|
+
? subscription.planId.trim()
|
|
287
|
+
: null;
|
|
288
|
+
|
|
289
|
+
return {
|
|
290
|
+
configured: true,
|
|
291
|
+
error: null,
|
|
292
|
+
plan_label: deriveCommandcodePlanLabel(planId),
|
|
293
|
+
subscription_status: isNonEmptyString(subscription?.status)
|
|
294
|
+
? subscription.status.trim()
|
|
295
|
+
: null,
|
|
296
|
+
primary_window: windows.fiveHour,
|
|
297
|
+
secondary_window: windows.weekly,
|
|
298
|
+
stale: false,
|
|
299
|
+
cached_at: new Date().toISOString(),
|
|
300
|
+
};
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
module.exports = {
|
|
304
|
+
COMMANDCODE_API_BASE_URL,
|
|
305
|
+
COMMANDCODE_API_KEY_ENV,
|
|
306
|
+
COMMANDCODE_SESSION_WINDOW_SECONDS,
|
|
307
|
+
COMMANDCODE_WEEKLY_WINDOW_SECONDS,
|
|
308
|
+
resolveCommandcodeHome,
|
|
309
|
+
resolveCommandcodeAuthPath,
|
|
310
|
+
readCommandcodeApiKey,
|
|
311
|
+
deriveCommandcodePlanLabel,
|
|
312
|
+
normalizeResetAt,
|
|
313
|
+
normalizeCommandcodeWindow,
|
|
314
|
+
normalizeCommandcodeWindowLimits,
|
|
315
|
+
resolveCommandcodeOrigin,
|
|
316
|
+
fetchCommandcodeLimits,
|
|
317
|
+
};
|
package/src/lib/local-api.js
CHANGED
|
@@ -2019,6 +2019,9 @@ function createLocalApiHandler({ queuePath }) {
|
|
|
2019
2019
|
const publishAccount =
|
|
2020
2020
|
background && body.publishAccount === true && getCloudSyncPref();
|
|
2021
2021
|
const allLocalSources = background && body.allLocalSources === true;
|
|
2022
|
+
if (background && body.nativeOnlyWsl === true) {
|
|
2023
|
+
extraEnv.TOKENTRACKER_WSL_MODE = "native-only";
|
|
2024
|
+
}
|
|
2022
2025
|
if (typeof body.deviceToken === "string" && body.deviceToken.trim()) {
|
|
2023
2026
|
extraEnv.TOKENTRACKER_DEVICE_TOKEN = body.deviceToken.trim();
|
|
2024
2027
|
}
|
|
@@ -47,6 +47,13 @@
|
|
|
47
47
|
"cache_write": 1.25,
|
|
48
48
|
"note": "Canonical undated alias emitted by Cursor. Pin to the same rates as claude-haiku-4-5-20251001 so local cost does not depend on which dated LiteLLM aliases are present in the current cache."
|
|
49
49
|
},
|
|
50
|
+
"gpt-6-astra": {
|
|
51
|
+
"input": 10,
|
|
52
|
+
"output": 50,
|
|
53
|
+
"cache_read": 1,
|
|
54
|
+
"cache_write": 12.5,
|
|
55
|
+
"note": "Official Standard USD/MTok rates verified 2026-09-07: https://developers.openai.com/api/docs/models/gpt-6-astra. Requests above 272K input tokens use 2x input/cache and 1.5x output rates; computeRowCost applies this only to observed long-context request subsets. Cloud aggregates use Standard short-context estimates because they do not retain per-request context or service tier. Reasoning-effort suffixes use the same base rates."
|
|
56
|
+
},
|
|
50
57
|
"gpt-5.6-sol": {
|
|
51
58
|
"input": 4,
|
|
52
59
|
"output": 20,
|
|
@@ -414,6 +421,10 @@
|
|
|
414
421
|
"auto": "composer-1"
|
|
415
422
|
},
|
|
416
423
|
"fuzzy": [
|
|
424
|
+
{
|
|
425
|
+
"match": "gpt-6-astra",
|
|
426
|
+
"ref": "gpt-6-astra"
|
|
427
|
+
},
|
|
417
428
|
{
|
|
418
429
|
"match": "gpt-5.6-sol",
|
|
419
430
|
"ref": "gpt-5.6-sol"
|
package/src/lib/pricing/index.js
CHANGED
|
@@ -25,10 +25,9 @@ const PI_SUBSCRIPTION_SOURCES = new Set([
|
|
|
25
25
|
// server (including LM Link). Secure Cloud usage has a separate billing path
|
|
26
26
|
// and is not present in these logs.
|
|
27
27
|
const LOCAL_INFERENCE_SOURCES = new Set(["lmstudio"]);
|
|
28
|
-
//
|
|
29
|
-
//
|
|
30
|
-
//
|
|
31
|
-
// repricing stays in place for when OpenAI ships a larger Codex window.
|
|
28
|
+
// OpenAI long-context pricing depends on a single request's raw input,
|
|
29
|
+
// including cached tokens, never a session/day aggregate. Astra supports
|
|
30
|
+
// a larger context window; only observed request subsets receive the premium.
|
|
32
31
|
const OPENAI_LONG_CONTEXT_INPUT_THRESHOLD = 272_000;
|
|
33
32
|
const SOURCES_WITH_AUTHORITATIVE_COST = new Set(["grok"]);
|
|
34
33
|
const SEED_SNAPSHOT_PATH = path.resolve(__dirname, "seed-snapshot.json");
|
|
@@ -207,9 +206,9 @@ function computeRowCost(row) {
|
|
|
207
206
|
);
|
|
208
207
|
|
|
209
208
|
const model = String(row?.model || "").toLowerCase();
|
|
210
|
-
const
|
|
211
|
-
model === "gpt-5.6" || model.includes("gpt-5.6-sol");
|
|
212
|
-
if (!
|
|
209
|
+
const usesOpenAILongContextTier =
|
|
210
|
+
model === "gpt-5.6" || model.includes("gpt-5.6-sol") || model.includes("gpt-6-astra");
|
|
211
|
+
if (!usesOpenAILongContextTier) return baseCost;
|
|
213
212
|
|
|
214
213
|
const bounded = (value, total) => Math.min(
|
|
215
214
|
Math.max(0, Number(value) || 0),
|