tokmon 0.28.2 → 0.28.3
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.md +156 -234
- package/THIRD_PARTY_NOTICES.md +62 -0
- package/dist/{bootstrap-ink-IMT3S5HU.js → bootstrap-ink-WG4WG3R2.js} +1579 -1136
- package/dist/chunk-7CH6S7BR.js +447 -0
- package/dist/{chunk-LCAHTRGP.js → chunk-CFKUZCMB.js} +56 -29
- package/dist/chunk-HNWEJ6MS.js +1094 -0
- package/dist/{chunk-WUK3MQUB.js → chunk-JZSZEHVD.js} +167 -10
- package/dist/{chunk-6ZEW2M6P.js → chunk-P57DVFNH.js} +125 -25
- package/dist/{chunk-LKYOWBD4.js → chunk-P5JN5DDX.js} +21 -42
- package/dist/chunk-TN6V7XSL.js +512 -0
- package/dist/{chunk-J3JW3RFP.js → chunk-USKOQIE3.js} +95 -13
- package/dist/chunk-UYPDMVW5.js +21 -0
- package/dist/{cli-command-DCENRCO3.js → cli-command-MD3RJRP4.js} +249 -15
- package/dist/cli.js +6 -6
- package/dist/{config-HXFJTNLM.js → config-3DGZ47F7.js} +23 -3
- package/dist/daemon-EMOA36WB.js +377 -0
- package/dist/daemon-handle-JHE5MKMI.js +11 -0
- package/dist/server-EPSH52DR.js +12 -0
- package/dist/web/assets/{Area-CMXvOw33.js → Area-stRnp8Km.js} +1 -1
- package/dist/web/assets/analytics-moZZt9L_.js +2 -0
- package/dist/web/assets/breakdown-km9w7myU.js +4 -0
- package/dist/web/assets/button-DrX04Cc2.js +1 -0
- package/dist/web/assets/{chart-cZoLgpmG.js → chart-D0CtuL2k.js} +1 -1
- package/dist/web/assets/explore-B11uNJV1.js +22 -0
- package/dist/web/assets/index-RDhXsCNG.js +83 -0
- package/dist/web/assets/index-ZmOS8p9O.css +1 -0
- package/dist/web/assets/models-DYbZnY34.js +2 -0
- package/dist/web/assets/overview-CruICbfJ.js +2 -0
- package/dist/web/assets/panel-xckM3_GC.js +1 -0
- package/dist/web/assets/settings-sheet-G-BTSIF6.js +1 -0
- package/dist/web/assets/share-sheet-weIdd4DW.js +2 -0
- package/dist/web/assets/timeline-BHgniliZ.js +1 -0
- package/dist/web/index.html +30 -4
- package/package.json +13 -4
- package/site/THIRD_PARTY_NOTICES.md +83 -0
- package/site/public/fonts/OFL.txt +93 -0
- package/dist/chunk-44ZSQEOS.js +0 -299
- package/dist/chunk-HP5UZCXP.js +0 -436
- package/dist/chunk-M7XMV36F.js +0 -275
- package/dist/daemon-6Y4O7NXS.js +0 -241
- package/dist/daemon-handle-GVCYVDPO.js +0 -10
- package/dist/server-GXXOWUVZ.js +0 -11
- package/dist/web/assets/analytics-DqdZXOJL.js +0 -2
- package/dist/web/assets/breakdown-kOSSJ3mI.js +0 -4
- package/dist/web/assets/explore-vcsM8K9I.js +0 -22
- package/dist/web/assets/index-BErCXT7f.css +0 -1
- package/dist/web/assets/index-DH3dOnbg.js +0 -83
- package/dist/web/assets/models-B5ikm83_.js +0 -2
- package/dist/web/assets/overview-DD1kP8CP.js +0 -2
- package/dist/web/assets/panel-DTQBwExW.js +0 -1
- package/dist/web/assets/primitives-C36qJlA2.js +0 -1
- package/dist/web/assets/settings-sheet-_i2zTQjf.js +0 -1
- package/dist/web/assets/share-sheet-BLHmrFbm.js +0 -2
- package/dist/web/assets/timeline-B4uG-Emo.js +0 -1
- package/dist/web/assets/use-dialog-trap-bfaXEMz9.js +0 -1
|
@@ -1,20 +1,21 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
|
-
isAlive,
|
|
4
|
-
probeHealth,
|
|
5
3
|
readLock,
|
|
6
|
-
|
|
4
|
+
retireIncompatibleCliOwner,
|
|
7
5
|
verifyLock
|
|
8
|
-
} from "./chunk-
|
|
6
|
+
} from "./chunk-USKOQIE3.js";
|
|
7
|
+
import {
|
|
8
|
+
daemonChannelFromWire,
|
|
9
|
+
resolveDaemonChannel
|
|
10
|
+
} from "./chunk-UYPDMVW5.js";
|
|
9
11
|
import {
|
|
10
12
|
TOKMON_PROTOCOL_VERSION
|
|
11
|
-
} from "./chunk-
|
|
13
|
+
} from "./chunk-JZSZEHVD.js";
|
|
12
14
|
|
|
13
15
|
// src/client/daemon-handle.ts
|
|
14
16
|
import { spawn } from "child_process";
|
|
15
17
|
import { extname } from "path";
|
|
16
18
|
var HANDSHAKE_TIMEOUT_MS = process.platform === "win32" ? 15e3 : 1e4;
|
|
17
|
-
var UPGRADE_SHUTDOWN_TIMEOUT_MS = 5e3;
|
|
18
19
|
function runtimeExecArgv(entry, override) {
|
|
19
20
|
if (override) return override;
|
|
20
21
|
const ext = extname(entry).toLowerCase();
|
|
@@ -37,7 +38,7 @@ function runtimeExecArgv(entry, override) {
|
|
|
37
38
|
function parseHandshake(line) {
|
|
38
39
|
try {
|
|
39
40
|
const value = JSON.parse(line);
|
|
40
|
-
return value?.ready === 1 && typeof value.url === "string" && typeof value.wsToken === "string" && typeof value.version === "string" && typeof value.protocolVersion === "number" && Array.isArray(value.capabilities) && value.capabilities.every((capability) => typeof capability === "string") && (value.ownerKind === "cli" || value.ownerKind === "desktop") ? value : null;
|
|
41
|
+
return value?.ready === 1 && typeof value.url === "string" && typeof value.wsToken === "string" && typeof value.version === "string" && typeof value.protocolVersion === "number" && Array.isArray(value.capabilities) && value.capabilities.every((capability) => typeof capability === "string") && (value.ownerKind === "cli" || value.ownerKind === "desktop") && daemonChannelFromWire(value.channel) !== null ? { ...value, channel: daemonChannelFromWire(value.channel) } : null;
|
|
41
42
|
} catch {
|
|
42
43
|
return null;
|
|
43
44
|
}
|
|
@@ -50,48 +51,26 @@ async function attach(opts, protocolVersion) {
|
|
|
50
51
|
const lock = await verifyLock(readLock(opts), protocolVersion);
|
|
51
52
|
return lock ? connected(lock.url) : null;
|
|
52
53
|
}
|
|
53
|
-
var delay = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
54
|
-
async function retireIncompatibleDaemon(opts, protocolVersion, timeoutMs) {
|
|
55
|
-
const lock = readLock(opts);
|
|
56
|
-
if (!lock || lock.state !== "ready" || lock.protocolVersion === protocolVersion || lock.ownerKind === "desktop" || lock.pid === process.pid || !isAlive(lock.pid)) return;
|
|
57
|
-
const authenticated = await probeHealth(
|
|
58
|
-
lock.url,
|
|
59
|
-
lock.wsToken,
|
|
60
|
-
lock,
|
|
61
|
-
Math.min(1e3, timeoutMs)
|
|
62
|
-
);
|
|
63
|
-
if (!authenticated) return;
|
|
64
|
-
try {
|
|
65
|
-
process.kill(lock.pid, "SIGTERM");
|
|
66
|
-
} catch {
|
|
67
|
-
if (!isAlive(lock.pid)) reclaimDeadLock(opts);
|
|
68
|
-
return;
|
|
69
|
-
}
|
|
70
|
-
const deadline = Date.now() + Math.min(timeoutMs, UPGRADE_SHUTDOWN_TIMEOUT_MS);
|
|
71
|
-
while (Date.now() < deadline) {
|
|
72
|
-
const current = readLock(opts);
|
|
73
|
-
if (!current || current.ownerId !== lock.ownerId) return;
|
|
74
|
-
if (!isAlive(lock.pid)) {
|
|
75
|
-
reclaimDeadLock(opts);
|
|
76
|
-
return;
|
|
77
|
-
}
|
|
78
|
-
await delay(50);
|
|
79
|
-
}
|
|
80
|
-
}
|
|
81
54
|
async function attachOrSpawn(opts = {}) {
|
|
82
55
|
const protocolVersion = TOKMON_PROTOCOL_VERSION;
|
|
83
56
|
const timeoutMs = opts.timeoutMs ?? HANDSHAKE_TIMEOUT_MS;
|
|
84
|
-
const
|
|
57
|
+
const baseEnv = opts.env ?? process.env;
|
|
58
|
+
const channel = resolveDaemonChannel(opts.channel, baseEnv);
|
|
59
|
+
const lockOpts = { cachePath: opts.cachePath, channel };
|
|
60
|
+
const existing = await attach(lockOpts, protocolVersion);
|
|
85
61
|
if (existing) return existing;
|
|
86
|
-
await
|
|
87
|
-
const upgraded = await attach(
|
|
62
|
+
await retireIncompatibleCliOwner(lockOpts, protocolVersion, timeoutMs);
|
|
63
|
+
const upgraded = await attach(lockOpts, protocolVersion);
|
|
88
64
|
if (upgraded) return upgraded;
|
|
89
65
|
const entry = opts.entry ?? process.argv[1];
|
|
90
66
|
if (!entry) return degraded();
|
|
91
67
|
const execPath = opts.execPath ?? process.execPath;
|
|
92
68
|
const args = ["__daemon", "--port", "0", "--no-open"];
|
|
93
|
-
const
|
|
94
|
-
|
|
69
|
+
const env = {
|
|
70
|
+
...baseEnv,
|
|
71
|
+
TOKMON_CHANNEL: channel,
|
|
72
|
+
...opts.cachePath ? { TOKMON_DAEMON_CACHE_DIR: opts.cachePath } : {}
|
|
73
|
+
};
|
|
95
74
|
return new Promise((resolve) => {
|
|
96
75
|
let child;
|
|
97
76
|
try {
|
|
@@ -117,7 +96,7 @@ async function attachOrSpawn(opts = {}) {
|
|
|
117
96
|
resolve(handle);
|
|
118
97
|
};
|
|
119
98
|
const tryAttach = (final = false) => {
|
|
120
|
-
void attach(
|
|
99
|
+
void attach(lockOpts, protocolVersion).then((found) => {
|
|
121
100
|
if (found) {
|
|
122
101
|
finish(found);
|
|
123
102
|
return;
|
|
@@ -143,7 +122,7 @@ async function attachOrSpawn(opts = {}) {
|
|
|
143
122
|
const line = stdout.slice(0, newline).trim();
|
|
144
123
|
stdout = stdout.slice(newline + 1);
|
|
145
124
|
const handshake = parseHandshake(line);
|
|
146
|
-
if (!handshake || handshake.protocolVersion !== protocolVersion) continue;
|
|
125
|
+
if (!handshake || handshake.protocolVersion !== protocolVersion || handshake.channel !== channel) continue;
|
|
147
126
|
tryAttach();
|
|
148
127
|
return;
|
|
149
128
|
}
|
|
@@ -0,0 +1,512 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
PROVIDERS,
|
|
4
|
+
PROVIDER_ORDER,
|
|
5
|
+
identityFromIdToken,
|
|
6
|
+
readClaudeIdentity,
|
|
7
|
+
readJson
|
|
8
|
+
} from "./chunk-CFKUZCMB.js";
|
|
9
|
+
import {
|
|
10
|
+
containsEmail,
|
|
11
|
+
expandHome,
|
|
12
|
+
redactEmail,
|
|
13
|
+
slugify
|
|
14
|
+
} from "./chunk-HNWEJ6MS.js";
|
|
15
|
+
|
|
16
|
+
// src/accounts.ts
|
|
17
|
+
import { existsSync, readdirSync, readFileSync as readFileSync2, statSync } from "fs";
|
|
18
|
+
import { homedir } from "os";
|
|
19
|
+
import { basename, join as join2, resolve } from "path";
|
|
20
|
+
|
|
21
|
+
// src/providers/codex/identity.ts
|
|
22
|
+
import { readFileSync } from "fs";
|
|
23
|
+
import { join } from "path";
|
|
24
|
+
function codexAuthPaths(homeDir) {
|
|
25
|
+
return [join(homeDir, ".codex", "auth.json"), join(homeDir, "auth.json")];
|
|
26
|
+
}
|
|
27
|
+
function readCodexIdentity(homeDir) {
|
|
28
|
+
for (const path of codexAuthPaths(homeDir)) {
|
|
29
|
+
try {
|
|
30
|
+
const parsed = JSON.parse(readFileSync(path, "utf-8"));
|
|
31
|
+
const { email, displayName, payload } = identityFromIdToken(parsed?.tokens?.id_token);
|
|
32
|
+
if (!payload) continue;
|
|
33
|
+
return { email, displayName };
|
|
34
|
+
} catch {
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
return {};
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// src/accounts.ts
|
|
41
|
+
function accountKey(providerId, homeDir) {
|
|
42
|
+
return `${providerId}:${homeDir ? resolve(expandHome(homeDir)) : homedir()}`;
|
|
43
|
+
}
|
|
44
|
+
function uniqueId(base, used) {
|
|
45
|
+
let id = slugify(base) || "account";
|
|
46
|
+
if (!used.has(id)) {
|
|
47
|
+
used.add(id);
|
|
48
|
+
return id;
|
|
49
|
+
}
|
|
50
|
+
for (let i = 2; i < 1e3; i++) {
|
|
51
|
+
const next = `${id}_${i}`;
|
|
52
|
+
if (!used.has(next)) {
|
|
53
|
+
used.add(next);
|
|
54
|
+
return next;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
id = `${id}_${Date.now()}`;
|
|
58
|
+
used.add(id);
|
|
59
|
+
return id;
|
|
60
|
+
}
|
|
61
|
+
function hasClaudeState(homeDir) {
|
|
62
|
+
return existsSync(join2(homeDir, ".claude.json")) || existsSync(join2(homeDir, ".claude", ".credentials.json")) || existsSync(join2(homeDir, ".claude", "projects")) || existsSync(join2(homeDir, ".config", "claude", ".credentials.json")) || existsSync(join2(homeDir, ".config", "claude", "projects"));
|
|
63
|
+
}
|
|
64
|
+
function candidateAlternateHomes(prefix) {
|
|
65
|
+
const home = homedir();
|
|
66
|
+
let entries;
|
|
67
|
+
try {
|
|
68
|
+
entries = readdirSync(home);
|
|
69
|
+
} catch {
|
|
70
|
+
return [];
|
|
71
|
+
}
|
|
72
|
+
const out = [];
|
|
73
|
+
const pattern = new RegExp(`^\\.${prefix}[_-]`);
|
|
74
|
+
for (const name of entries) {
|
|
75
|
+
if (!pattern.test(name)) continue;
|
|
76
|
+
const path = join2(home, name);
|
|
77
|
+
try {
|
|
78
|
+
if (!statSync(path).isDirectory()) continue;
|
|
79
|
+
out.push(path);
|
|
80
|
+
} catch {
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
return out.sort();
|
|
84
|
+
}
|
|
85
|
+
function labelForClaudeHome(homeDir) {
|
|
86
|
+
const identity = readClaudeIdentity(homeDir);
|
|
87
|
+
if (identity.email) return `Claude ${identity.email}`;
|
|
88
|
+
if (identity.displayName) return `Claude ${identity.displayName}`;
|
|
89
|
+
const raw = basename(homeDir).replace(/^\.claude[_-]?/, "").replace(/[_-]+/g, " ").trim();
|
|
90
|
+
return raw ? `Claude ${raw}` : "Claude";
|
|
91
|
+
}
|
|
92
|
+
function hasCodexAuth(homeDir) {
|
|
93
|
+
for (const path of codexAuthPaths(homeDir)) {
|
|
94
|
+
try {
|
|
95
|
+
const parsed = JSON.parse(readFileSync2(path, "utf-8"));
|
|
96
|
+
const accessToken = parsed?.tokens?.access_token;
|
|
97
|
+
if (typeof accessToken === "string" && accessToken.trim()) return true;
|
|
98
|
+
} catch {
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
return false;
|
|
102
|
+
}
|
|
103
|
+
function labelForCodexHome(homeDir) {
|
|
104
|
+
const identity = readCodexIdentity(homeDir);
|
|
105
|
+
if (identity.email) return `Codex ${identity.email}`;
|
|
106
|
+
if (identity.displayName) return `Codex ${identity.displayName}`;
|
|
107
|
+
const raw = basename(homeDir).replace(/^\.codex[_-]?/, "").replace(/[_-]+/g, " ").trim();
|
|
108
|
+
return raw ? `Codex ${raw}` : "Codex";
|
|
109
|
+
}
|
|
110
|
+
function discoverClaudeAccounts(usedIds) {
|
|
111
|
+
const provider = PROVIDERS.claude;
|
|
112
|
+
const out = [];
|
|
113
|
+
for (const homeDir of candidateAlternateHomes("claude")) {
|
|
114
|
+
if (!hasClaudeState(homeDir)) continue;
|
|
115
|
+
const suffix = basename(homeDir).replace(/^\.claude[_-]?/, "") || basename(homeDir);
|
|
116
|
+
out.push({
|
|
117
|
+
id: uniqueId(`claude_${suffix}`, usedIds),
|
|
118
|
+
providerId: "claude",
|
|
119
|
+
name: labelForClaudeHome(homeDir),
|
|
120
|
+
color: provider.color,
|
|
121
|
+
homeDir
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
return out;
|
|
125
|
+
}
|
|
126
|
+
function discoverCodexAccounts(usedIds) {
|
|
127
|
+
const provider = PROVIDERS.codex;
|
|
128
|
+
const out = [];
|
|
129
|
+
for (const homeDir of candidateAlternateHomes("codex")) {
|
|
130
|
+
if (!hasCodexAuth(homeDir)) continue;
|
|
131
|
+
const suffix = basename(homeDir).replace(/^\.codex[_-]?/, "") || basename(homeDir);
|
|
132
|
+
out.push({
|
|
133
|
+
id: uniqueId(`codex_${suffix}`, usedIds),
|
|
134
|
+
providerId: "codex",
|
|
135
|
+
name: labelForCodexHome(homeDir),
|
|
136
|
+
color: provider.color,
|
|
137
|
+
homeDir
|
|
138
|
+
});
|
|
139
|
+
}
|
|
140
|
+
return out;
|
|
141
|
+
}
|
|
142
|
+
function discoverProviderAccounts(providerId, usedIds) {
|
|
143
|
+
if (providerId === "claude") return discoverClaudeAccounts(usedIds);
|
|
144
|
+
if (providerId === "codex") return discoverCodexAccounts(usedIds);
|
|
145
|
+
return [];
|
|
146
|
+
}
|
|
147
|
+
function buildAccounts(config, detected) {
|
|
148
|
+
const out = [];
|
|
149
|
+
const usedIds = new Set(config.accounts.map((a) => a.id));
|
|
150
|
+
const seenKeys = /* @__PURE__ */ new Set();
|
|
151
|
+
const excludedKeys = new Set(
|
|
152
|
+
config.accountDetection.excludedAccounts.map((ref) => accountKey(ref.providerId, ref.homeDir))
|
|
153
|
+
);
|
|
154
|
+
const add = (account) => {
|
|
155
|
+
const key = accountKey(account.providerId, account.homeDir);
|
|
156
|
+
if (seenKeys.has(key)) return;
|
|
157
|
+
seenKeys.add(key);
|
|
158
|
+
out.push(account);
|
|
159
|
+
};
|
|
160
|
+
for (const pid of PROVIDER_ORDER) {
|
|
161
|
+
if (config.disabledProviders.includes(pid)) continue;
|
|
162
|
+
const provider = PROVIDERS[pid];
|
|
163
|
+
const configured = config.accounts.filter((a) => a.providerId === pid);
|
|
164
|
+
for (const a of configured) {
|
|
165
|
+
add({
|
|
166
|
+
id: a.id,
|
|
167
|
+
providerId: pid,
|
|
168
|
+
name: a.name,
|
|
169
|
+
color: a.color || provider.color,
|
|
170
|
+
homeDir: a.homeDir && a.homeDir !== "~" ? expandHome(a.homeDir) : void 0
|
|
171
|
+
});
|
|
172
|
+
}
|
|
173
|
+
if (!config.accountDetection.enabled || config.accountDetection.disabledProviders.includes(pid)) continue;
|
|
174
|
+
const discovered = discoverProviderAccounts(pid, usedIds);
|
|
175
|
+
if (detected.includes(pid)) {
|
|
176
|
+
const account = { id: pid, providerId: pid, name: provider.name, color: provider.color, homeDir: void 0 };
|
|
177
|
+
if (!excludedKeys.has(accountKey(pid))) add(account);
|
|
178
|
+
}
|
|
179
|
+
for (const account of discovered) {
|
|
180
|
+
if (excludedKeys.has(accountKey(account.providerId, account.homeDir))) continue;
|
|
181
|
+
add(account);
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
return out;
|
|
185
|
+
}
|
|
186
|
+
function accountsByProvider(accounts) {
|
|
187
|
+
const groups = [];
|
|
188
|
+
for (const pid of PROVIDER_ORDER) {
|
|
189
|
+
const list = accounts.filter((a) => a.providerId === pid);
|
|
190
|
+
if (list.length > 0) groups.push({ provider: pid, accounts: list });
|
|
191
|
+
}
|
|
192
|
+
return groups;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
// src/shared/colors.ts
|
|
196
|
+
var FALLBACK_HEX = "#8d9090";
|
|
197
|
+
var NAMED_HEX = {
|
|
198
|
+
green: "#6caa71",
|
|
199
|
+
greenBright: "#79be7e",
|
|
200
|
+
cyan: "#7ccbcd",
|
|
201
|
+
cyanBright: "#84dde0",
|
|
202
|
+
blue: "#6d96b4",
|
|
203
|
+
blueBright: "#67b5ed",
|
|
204
|
+
magenta: "#bd7bcd",
|
|
205
|
+
magentaBright: "#d389e5",
|
|
206
|
+
yellow: "#c4ac62",
|
|
207
|
+
yellowBright: "#d9c074",
|
|
208
|
+
red: "#b45648",
|
|
209
|
+
redBright: "#cf6a5a",
|
|
210
|
+
white: "#dee5eb",
|
|
211
|
+
whiteBright: "#f3f5f5",
|
|
212
|
+
gray: FALLBACK_HEX,
|
|
213
|
+
grey: FALLBACK_HEX
|
|
214
|
+
};
|
|
215
|
+
var PROVIDER_HEX = {
|
|
216
|
+
claude: NAMED_HEX.green,
|
|
217
|
+
codex: NAMED_HEX.cyan,
|
|
218
|
+
cursor: NAMED_HEX.magenta,
|
|
219
|
+
copilot: NAMED_HEX.white,
|
|
220
|
+
pi: NAMED_HEX.blue,
|
|
221
|
+
opencode: NAMED_HEX.yellow,
|
|
222
|
+
antigravity: NAMED_HEX.red,
|
|
223
|
+
gemini: NAMED_HEX.greenBright,
|
|
224
|
+
grok: NAMED_HEX.yellowBright
|
|
225
|
+
};
|
|
226
|
+
function namedHex(name) {
|
|
227
|
+
if (!name) return FALLBACK_HEX;
|
|
228
|
+
if (name.startsWith("#")) return name;
|
|
229
|
+
return NAMED_HEX[name] ?? FALLBACK_HEX;
|
|
230
|
+
}
|
|
231
|
+
function colorHex(accountColor, providerColorName) {
|
|
232
|
+
if (accountColor) {
|
|
233
|
+
if (accountColor.startsWith("#")) return accountColor;
|
|
234
|
+
const named = NAMED_HEX[accountColor];
|
|
235
|
+
if (named) return named;
|
|
236
|
+
}
|
|
237
|
+
return namedHex(providerColorName);
|
|
238
|
+
}
|
|
239
|
+
var MODEL_PALETTE = [
|
|
240
|
+
"#00d7ff",
|
|
241
|
+
"#00d787",
|
|
242
|
+
"#e6b450",
|
|
243
|
+
"#d75f87",
|
|
244
|
+
"#5f87ff",
|
|
245
|
+
"#af87ff",
|
|
246
|
+
"#5fd7a7",
|
|
247
|
+
"#ff8787",
|
|
248
|
+
"#d7af5f",
|
|
249
|
+
"#87d7ff",
|
|
250
|
+
"#d787d7",
|
|
251
|
+
"#9ee493",
|
|
252
|
+
"#ffb454",
|
|
253
|
+
"#7aa2f7",
|
|
254
|
+
"#bb9af7"
|
|
255
|
+
];
|
|
256
|
+
var modelColorCache = /* @__PURE__ */ new Map();
|
|
257
|
+
function modelColor(name) {
|
|
258
|
+
const cached = modelColorCache.get(name);
|
|
259
|
+
if (cached) return cached;
|
|
260
|
+
let hash = 0;
|
|
261
|
+
for (let i = 0; i < name.length; i++) hash = hash * 31 + name.charCodeAt(i) >>> 0;
|
|
262
|
+
const color = MODEL_PALETTE[hash % MODEL_PALETTE.length];
|
|
263
|
+
modelColorCache.set(name, color);
|
|
264
|
+
return color;
|
|
265
|
+
}
|
|
266
|
+
var TOKEN_BUCKET = {
|
|
267
|
+
input: NAMED_HEX.blue,
|
|
268
|
+
output: NAMED_HEX.green,
|
|
269
|
+
cacheCreate: NAMED_HEX.yellow,
|
|
270
|
+
cacheRead: NAMED_HEX.cyan
|
|
271
|
+
};
|
|
272
|
+
|
|
273
|
+
// src/usage-semantics.ts
|
|
274
|
+
var clampPct = (value) => Math.max(0, Math.min(100, value));
|
|
275
|
+
function inferRole(metric) {
|
|
276
|
+
if (metric.role) return metric.role;
|
|
277
|
+
if (metric.format.kind !== "percent" && !(metric.limit != null && metric.limit > 0)) return "unbounded";
|
|
278
|
+
const label = metric.label.trim().toLowerCase();
|
|
279
|
+
if (label === "session" || label.includes("five hour")) return "session";
|
|
280
|
+
if (label === "weekly" || label.includes("seven day") || label.includes("weekly all")) return "weekly";
|
|
281
|
+
if (metric.modelId || !metric.primary && metric.format.kind === "percent" && !["usage", "api", "auto"].includes(label)) return "model";
|
|
282
|
+
return "other";
|
|
283
|
+
}
|
|
284
|
+
function valueText(metric, usedPct) {
|
|
285
|
+
if (usedPct !== null) return `${Math.round(usedPct)}% used`;
|
|
286
|
+
if (!Number.isFinite(metric.used)) return "\u2014";
|
|
287
|
+
if (metric.format.kind === "dollars") {
|
|
288
|
+
try {
|
|
289
|
+
return new Intl.NumberFormat("en-US", { style: "currency", currency: metric.format.currency ?? "USD", maximumFractionDigits: 2 }).format(metric.used);
|
|
290
|
+
} catch {
|
|
291
|
+
return `$${metric.used.toFixed(2)}`;
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
if (metric.format.kind === "count") {
|
|
295
|
+
const suffix = metric.format.suffix?.trim();
|
|
296
|
+
return `${Math.round(metric.used).toLocaleString("en-US")}${suffix ? ` ${suffix}` : ""}`;
|
|
297
|
+
}
|
|
298
|
+
return `${Math.round(metric.used)}% used`;
|
|
299
|
+
}
|
|
300
|
+
var roleOrder = (role) => role === "session" ? 0 : role === "weekly" ? 1 : role === "model" ? 2 : role === "other" ? 3 : 4;
|
|
301
|
+
function deriveQuotaView(metric, sourceIndex = 0) {
|
|
302
|
+
const bounded = metric.format.kind === "percent" || metric.limit != null && Number.isFinite(metric.limit) && metric.limit > 0;
|
|
303
|
+
const usedPct = metric.format.kind === "percent" ? metric.used : metric.limit != null && metric.limit > 0 ? metric.used / metric.limit * 100 : null;
|
|
304
|
+
const finiteUsedPct = usedPct == null || !Number.isFinite(usedPct) ? null : clampPct(usedPct);
|
|
305
|
+
const resets = metric.resetsAt ? Date.parse(metric.resetsAt) : Number.NaN;
|
|
306
|
+
return {
|
|
307
|
+
key: metric.key ?? `${metric.label}:${sourceIndex}`,
|
|
308
|
+
label: metric.label.replace(/\s*(limit|usage)$/i, "").trim() || metric.label,
|
|
309
|
+
role: inferRole(metric),
|
|
310
|
+
modelId: metric.modelId ?? null,
|
|
311
|
+
usedPct: finiteUsedPct,
|
|
312
|
+
remainingPct: bounded && finiteUsedPct != null ? clampPct(100 - finiteUsedPct) : null,
|
|
313
|
+
resetsAt: Number.isFinite(resets) ? resets : null,
|
|
314
|
+
bounded,
|
|
315
|
+
primary: metric.primary === true,
|
|
316
|
+
active: metric.active === true,
|
|
317
|
+
displayOrder: sourceIndex,
|
|
318
|
+
valueText: valueText(metric, finiteUsedPct)
|
|
319
|
+
};
|
|
320
|
+
}
|
|
321
|
+
function deriveQuotaViews(metrics) {
|
|
322
|
+
return metrics.map(deriveQuotaView).sort((a, b) => roleOrder(a.role) - roleOrder(b.role) || (a.role === "model" ? a.label.localeCompare(b.label) : a.displayOrder - b.displayOrder) || a.key.localeCompare(b.key)).map((quota, displayOrder) => ({ ...quota, displayOrder }));
|
|
323
|
+
}
|
|
324
|
+
function resolveQuotaViews(input) {
|
|
325
|
+
return input.quotas == null ? deriveQuotaViews(input.metrics ?? []) : [...input.quotas];
|
|
326
|
+
}
|
|
327
|
+
function usageFromHeadroom(value) {
|
|
328
|
+
return value === null || !Number.isFinite(value) ? null : clampPct(100 - value);
|
|
329
|
+
}
|
|
330
|
+
function severity(remaining) {
|
|
331
|
+
if (remaining === null || !Number.isFinite(remaining)) return "unknown";
|
|
332
|
+
if (remaining <= 10) return "crit";
|
|
333
|
+
if (remaining <= 25) return "warn";
|
|
334
|
+
return "ok";
|
|
335
|
+
}
|
|
336
|
+
function deriveAccountIdentity(input) {
|
|
337
|
+
const registered = input.name.trim() || input.providerName;
|
|
338
|
+
const email = input.email?.trim() || null;
|
|
339
|
+
const displayName = input.displayName?.trim() || null;
|
|
340
|
+
if (input.privacyMode && (containsEmail(registered) || containsEmail(email))) {
|
|
341
|
+
const title2 = `${input.providerName} account ${input.ordinal}`;
|
|
342
|
+
return { title: title2, subtitle: null, accessibleLabel: title2, redacted: true };
|
|
343
|
+
}
|
|
344
|
+
const title = input.privacyMode ? redactEmail(registered) : registered;
|
|
345
|
+
const subtitleCandidate = !input.privacyMode ? email && !registered.toLowerCase().includes(email.toLowerCase()) ? email : displayName : displayName && !containsEmail(displayName) ? displayName : null;
|
|
346
|
+
const subtitle = subtitleCandidate && subtitleCandidate !== title ? subtitleCandidate : null;
|
|
347
|
+
return { title, subtitle, accessibleLabel: subtitle ? `${title}, ${subtitle}` : title, redacted: input.privacyMode && title !== registered };
|
|
348
|
+
}
|
|
349
|
+
function accountIdentityText(account, providerName) {
|
|
350
|
+
const identity = account.identity;
|
|
351
|
+
if (!identity) return account.name || providerName;
|
|
352
|
+
const parts = [identity.title, identity.subtitle].filter((value) => !!value?.trim()).filter((value, index, values) => values.indexOf(value) === index).filter((value) => value.toLocaleLowerCase() !== providerName.toLocaleLowerCase());
|
|
353
|
+
return parts.join(" \xB7 ") || identity.title || providerName;
|
|
354
|
+
}
|
|
355
|
+
function blendHeadroom(a, b) {
|
|
356
|
+
const low = Math.min(clampPct(a), clampPct(b));
|
|
357
|
+
const high = Math.max(clampPct(a), clampPct(b));
|
|
358
|
+
return low * (0.9 + 0.1 * high / 100);
|
|
359
|
+
}
|
|
360
|
+
function tightestQuotaView(quotas) {
|
|
361
|
+
return quotas.filter((q) => q.remainingPct !== null).sort((a, b) => a.remainingPct - b.remainingPct || (a.resetsAt ?? Infinity) - (b.resetsAt ?? Infinity) || a.key.localeCompare(b.key))[0] ?? null;
|
|
362
|
+
}
|
|
363
|
+
function accountHeadroom(account) {
|
|
364
|
+
const session = account.quotas.find((q) => q.role === "session" && q.remainingPct !== null);
|
|
365
|
+
const models = account.quotas.filter((q) => q.role === "model" && q.remainingPct !== null);
|
|
366
|
+
const activeModel = models.find((q) => q.active) ?? null;
|
|
367
|
+
const weekly = account.quotas.find((q) => q.role === "weekly" && q.remainingPct !== null);
|
|
368
|
+
const factors = [];
|
|
369
|
+
let value = null;
|
|
370
|
+
let mode = "unavailable";
|
|
371
|
+
if (session && activeModel) {
|
|
372
|
+
value = blendHeadroom(session.remainingPct, activeModel.remainingPct);
|
|
373
|
+
mode = "smart";
|
|
374
|
+
factors.push(
|
|
375
|
+
{ key: session.key, label: session.label, role: session.role, remainingPct: session.remainingPct, included: true, reason: "session" },
|
|
376
|
+
{ key: activeModel.key, label: activeModel.label, role: activeModel.role, remainingPct: activeModel.remainingPct, included: true, reason: "active-model" }
|
|
377
|
+
);
|
|
378
|
+
} else if (session || activeModel) {
|
|
379
|
+
const only = session ?? activeModel;
|
|
380
|
+
value = only.remainingPct;
|
|
381
|
+
mode = "single-window";
|
|
382
|
+
factors.push({ key: only.key, label: only.label, role: only.role, remainingPct: only.remainingPct, included: true, reason: session ? "session" : "active-model" });
|
|
383
|
+
} else {
|
|
384
|
+
const primary = account.quotas.find((q) => q.primary && q.remainingPct !== null) ?? null;
|
|
385
|
+
if (primary) {
|
|
386
|
+
value = primary.remainingPct;
|
|
387
|
+
mode = "single-window";
|
|
388
|
+
factors.push({ key: primary.key, label: primary.label, role: primary.role, remainingPct: primary.remainingPct, included: true, reason: "primary" });
|
|
389
|
+
} else {
|
|
390
|
+
const floor = tightestQuotaView(account.quotas);
|
|
391
|
+
if (floor) {
|
|
392
|
+
value = floor.remainingPct;
|
|
393
|
+
mode = "fallback-floor";
|
|
394
|
+
factors.push({ key: floor.key, label: floor.label, role: floor.role, remainingPct: floor.remainingPct, included: true, reason: "fallback-floor" });
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
if (value !== null && weekly) {
|
|
399
|
+
const close = weekly.remainingPct <= 30 || weekly.remainingPct <= value + 10;
|
|
400
|
+
value = Math.min(value, weekly.remainingPct);
|
|
401
|
+
factors.push({ key: weekly.key, label: weekly.label, role: weekly.role, remainingPct: weekly.remainingPct, included: close, reason: "weekly-cap" });
|
|
402
|
+
}
|
|
403
|
+
const included = factors.filter((f) => f.included);
|
|
404
|
+
return {
|
|
405
|
+
value,
|
|
406
|
+
unit: "index-100",
|
|
407
|
+
mode,
|
|
408
|
+
factors,
|
|
409
|
+
explanation: value === null ? "No bounded quota data" : `Based on ${included.map((f) => f.label).join(" + ") || "available quota data"}`
|
|
410
|
+
};
|
|
411
|
+
}
|
|
412
|
+
function cumulativeQuotaViews(accounts) {
|
|
413
|
+
const groups = /* @__PURE__ */ new Map();
|
|
414
|
+
for (const account of accounts) {
|
|
415
|
+
for (const quota of account.quotas) {
|
|
416
|
+
if (quota.remainingPct === null) continue;
|
|
417
|
+
const identity = quota.role === "session" || quota.role === "weekly" ? quota.role : quota.role === "model" ? `${quota.role}:${quota.modelId ?? quota.label.toLowerCase()}` : `${quota.role}:${quota.key}`;
|
|
418
|
+
const rows = groups.get(identity) ?? [];
|
|
419
|
+
rows.push(quota);
|
|
420
|
+
groups.set(identity, rows);
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
return [...groups.values()].filter((rows) => rows[0].role !== "model" || rows.length > 1 || accounts.length === 1).map((rows) => {
|
|
424
|
+
const first = rows[0];
|
|
425
|
+
const remainingPct = rows.reduce((sum, quota) => sum + quota.remainingPct, 0) / rows.length;
|
|
426
|
+
const usedPct = 100 - remainingPct;
|
|
427
|
+
const resets = rows.map((quota) => quota.resetsAt).filter((value) => value !== null);
|
|
428
|
+
return {
|
|
429
|
+
...first,
|
|
430
|
+
usedPct,
|
|
431
|
+
remainingPct,
|
|
432
|
+
resetsAt: resets.length > 0 ? Math.min(...resets) : null,
|
|
433
|
+
primary: rows.some((quota) => quota.primary),
|
|
434
|
+
active: rows.some((quota) => quota.active),
|
|
435
|
+
displayOrder: Math.min(...rows.map((quota) => quota.displayOrder)),
|
|
436
|
+
valueText: `${Math.round(usedPct)}% used`
|
|
437
|
+
};
|
|
438
|
+
}).sort((a, b) => roleOrder(a.role) - roleOrder(b.role) || a.displayOrder - b.displayOrder || a.key.localeCompare(b.key));
|
|
439
|
+
}
|
|
440
|
+
function deriveProviderHeadroom(accounts, activeTimeoutMin, now, displayMetric = "smartHeadroom") {
|
|
441
|
+
const activeCutoff = now - activeTimeoutMin * 6e4;
|
|
442
|
+
const activeIds = accounts.filter((a) => a.lastActivityAt !== null && a.lastActivityAt >= activeCutoff).map((a) => a.id);
|
|
443
|
+
if (displayMetric === "smartHeadroom" && accounts.length > 1) {
|
|
444
|
+
const pooled = accountHeadroom({ id: "cumulative", lastActivityAt: null, quotas: cumulativeQuotaViews(accounts) });
|
|
445
|
+
if (pooled.value === null) return { ...pooled, basis: "unavailable", representativeAccountId: null, activeAccountIds: activeIds };
|
|
446
|
+
const dataCount = accounts.filter((account) => account.quotas.some((quota) => quota.remainingPct !== null)).length;
|
|
447
|
+
return {
|
|
448
|
+
...pooled,
|
|
449
|
+
basis: activeIds.length > 0 ? "active" : "idle-runway",
|
|
450
|
+
representativeAccountId: null,
|
|
451
|
+
activeAccountIds: activeIds,
|
|
452
|
+
explanation: `${pooled.explanation}; ${dataCount} accounts combined`
|
|
453
|
+
};
|
|
454
|
+
}
|
|
455
|
+
const rows = accounts.map((account) => {
|
|
456
|
+
const smart = accountHeadroom(account);
|
|
457
|
+
if (displayMetric === "smartHeadroom") return { account, value: smart.value, view: smart };
|
|
458
|
+
const floor = tightestQuotaView(account.quotas);
|
|
459
|
+
const value = floor?.remainingPct ?? null;
|
|
460
|
+
return { account, value, view: { value, unit: "index-100", mode: floor ? "fallback-floor" : "unavailable", factors: floor ? [{ key: floor.key, label: floor.label, role: floor.role, remainingPct: value, included: true, reason: "fallback-floor" }] : [], explanation: floor ? `Based on ${floor.label}` : "No bounded quota data" } };
|
|
461
|
+
}).filter((row) => row.value !== null);
|
|
462
|
+
if (rows.length === 0) return { value: null, unit: "index-100", mode: "unavailable", basis: "unavailable", representativeAccountId: null, activeAccountIds: activeIds, factors: [], explanation: "No bounded quota data" };
|
|
463
|
+
const active = rows.filter((row) => activeIds.includes(row.account.id));
|
|
464
|
+
const candidates = active.length > 0 ? active : rows;
|
|
465
|
+
candidates.sort((a, b) => a.value !== b.value ? active.length > 0 ? a.value - b.value : b.value - a.value : active.length > 0 && a.account.lastActivityAt !== b.account.lastActivityAt ? (b.account.lastActivityAt ?? 0) - (a.account.lastActivityAt ?? 0) : a.account.id.localeCompare(b.account.id));
|
|
466
|
+
const selected = candidates[0];
|
|
467
|
+
const basis = active.length > 0 ? "active" : "idle-runway";
|
|
468
|
+
return { ...selected.view, basis, representativeAccountId: selected.account.id, activeAccountIds: activeIds, explanation: `${selected.view.explanation}; ${basis === "active" ? "active account" : "best available account"}` };
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
// src/peak.ts
|
|
472
|
+
async function fetchPeak() {
|
|
473
|
+
try {
|
|
474
|
+
const res = await fetch("https://promoclock.co/api/status", {
|
|
475
|
+
headers: { "Accept": "application/json", "User-Agent": "tokmon" },
|
|
476
|
+
signal: AbortSignal.timeout(3e3)
|
|
477
|
+
});
|
|
478
|
+
if (!res.ok) return null;
|
|
479
|
+
const data = await readJson(res);
|
|
480
|
+
if (!data) return null;
|
|
481
|
+
let state;
|
|
482
|
+
if (data.isPeak === true || data.status === "peak") state = "peak";
|
|
483
|
+
else if (data.isWeekend === true || data.status === "weekend") state = "weekend";
|
|
484
|
+
else if (data.isOffPeak === true || data.status === "off_peak" || data.status === "off-peak") state = "off-peak";
|
|
485
|
+
else return null;
|
|
486
|
+
const minutesUntilChange = typeof data.minutesUntilChange === "number" && Number.isFinite(data.minutesUntilChange) ? data.minutesUntilChange : null;
|
|
487
|
+
return {
|
|
488
|
+
state,
|
|
489
|
+
label: state === "peak" ? "Peak" : state === "weekend" ? "Weekend" : "Off-Peak",
|
|
490
|
+
minutesUntilChange,
|
|
491
|
+
changesAt: minutesUntilChange !== null ? new Date(Date.now() + minutesUntilChange * 6e4).toISOString() : null
|
|
492
|
+
};
|
|
493
|
+
} catch {
|
|
494
|
+
return null;
|
|
495
|
+
}
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
export {
|
|
499
|
+
buildAccounts,
|
|
500
|
+
accountsByProvider,
|
|
501
|
+
namedHex,
|
|
502
|
+
colorHex,
|
|
503
|
+
modelColor,
|
|
504
|
+
deriveQuotaViews,
|
|
505
|
+
resolveQuotaViews,
|
|
506
|
+
usageFromHeadroom,
|
|
507
|
+
severity,
|
|
508
|
+
deriveAccountIdentity,
|
|
509
|
+
accountIdentityText,
|
|
510
|
+
deriveProviderHeadroom,
|
|
511
|
+
fetchPeak
|
|
512
|
+
};
|