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,14 +1,20 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
daemonChannelFromWire,
|
|
4
|
+
resolveDaemonChannel
|
|
5
|
+
} from "./chunk-UYPDMVW5.js";
|
|
2
6
|
import {
|
|
3
7
|
cacheDir
|
|
4
|
-
} from "./chunk-
|
|
8
|
+
} from "./chunk-HNWEJ6MS.js";
|
|
5
9
|
|
|
6
10
|
// src/web/lockfile.ts
|
|
7
11
|
import { closeSync, fchmodSync, fsyncSync, lstatSync, mkdirSync, openSync, readFileSync, renameSync, statSync, unlinkSync, writeFileSync } from "fs";
|
|
8
12
|
import { isAbsolute, join } from "path";
|
|
9
13
|
function lockDir(opts = {}) {
|
|
10
14
|
const injected = opts.cachePath ?? process.env.TOKMON_DAEMON_CACHE_DIR;
|
|
11
|
-
|
|
15
|
+
if (injected && isAbsolute(injected)) return injected;
|
|
16
|
+
const root = cacheDir();
|
|
17
|
+
return resolveDaemonChannel(opts.channel) === "dev" ? join(root, "dev") : root;
|
|
12
18
|
}
|
|
13
19
|
function lockfilePath(opts = {}) {
|
|
14
20
|
return join(lockDir(opts), "daemon.json");
|
|
@@ -23,7 +29,7 @@ function isLoopbackUrl(value) {
|
|
|
23
29
|
}
|
|
24
30
|
function validLock(value) {
|
|
25
31
|
const lock = value;
|
|
26
|
-
return !!lock && typeof lock.pid === "number" && Number.isInteger(lock.pid) && lock.pid > 0 && typeof lock.port === "number" && Number.isInteger(lock.port) && lock.port >= 0 && lock.port <= 65535 && typeof lock.url === "string" && (lock.state === "starting" || isLoopbackUrl(lock.url)) && typeof lock.wsToken === "string" && lock.wsToken.length >= 32 && typeof lock.version === "string" && typeof lock.protocolVersion === "number" && Number.isSafeInteger(lock.protocolVersion) && lock.protocolVersion >= 1 && Array.isArray(lock.capabilities) && lock.capabilities.every((capability) => typeof capability === "string") && (lock.ownerKind === "cli" || lock.ownerKind === "desktop") && typeof lock.startedAt === "number" && typeof lock.ownerId === "string" && lock.ownerId.length >= 32 && (lock.state === "starting" || lock.state === "ready");
|
|
32
|
+
return !!lock && typeof lock.pid === "number" && Number.isInteger(lock.pid) && lock.pid > 0 && typeof lock.port === "number" && Number.isInteger(lock.port) && lock.port >= 0 && lock.port <= 65535 && typeof lock.url === "string" && (lock.state === "starting" || isLoopbackUrl(lock.url)) && typeof lock.wsToken === "string" && lock.wsToken.length >= 32 && typeof lock.version === "string" && typeof lock.protocolVersion === "number" && Number.isSafeInteger(lock.protocolVersion) && lock.protocolVersion >= 1 && Array.isArray(lock.capabilities) && lock.capabilities.every((capability) => typeof capability === "string") && (lock.ownerKind === "cli" || lock.ownerKind === "desktop") && daemonChannelFromWire(lock.channel) !== null && typeof lock.startedAt === "number" && typeof lock.ownerId === "string" && lock.ownerId.length >= 32 && (lock.state === "starting" || lock.state === "ready");
|
|
27
33
|
}
|
|
28
34
|
function readLock(opts = {}) {
|
|
29
35
|
try {
|
|
@@ -31,7 +37,37 @@ function readLock(opts = {}) {
|
|
|
31
37
|
const stat = statSync(path);
|
|
32
38
|
if (!stat.isFile() || (stat.mode & 63) !== 0) return null;
|
|
33
39
|
const parsed = JSON.parse(readFileSync(path, "utf-8"));
|
|
34
|
-
|
|
40
|
+
if (!validLock(parsed)) return null;
|
|
41
|
+
const channel = daemonChannelFromWire(parsed.channel);
|
|
42
|
+
return channel === resolveDaemonChannel(opts.channel) ? { ...parsed, channel } : null;
|
|
43
|
+
} catch {
|
|
44
|
+
return null;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
function readForeignLock(opts = {}) {
|
|
48
|
+
try {
|
|
49
|
+
const path = lockfilePath(opts);
|
|
50
|
+
const stat = statSync(path);
|
|
51
|
+
if (!stat.isFile() || (stat.mode & 63) !== 0) return null;
|
|
52
|
+
const value = JSON.parse(readFileSync(path, "utf-8"));
|
|
53
|
+
const channel = daemonChannelFromWire(value.channel);
|
|
54
|
+
if (!Number.isInteger(value.pid) || value.pid <= 0 || typeof value.url !== "string" || !isLoopbackUrl(value.url) || typeof value.wsToken !== "string" || value.wsToken.length < 32 || value.state !== "ready" || channel !== resolveDaemonChannel(opts.channel)) return null;
|
|
55
|
+
if (value.ownerKind !== void 0 && value.ownerKind !== "cli" && value.ownerKind !== "desktop") return null;
|
|
56
|
+
if (value.protocolVersion !== void 0 && (!Number.isSafeInteger(value.protocolVersion) || value.protocolVersion < 1)) return null;
|
|
57
|
+
return {
|
|
58
|
+
pid: value.pid,
|
|
59
|
+
...Number.isInteger(value.port) ? { port: value.port } : {},
|
|
60
|
+
url: value.url,
|
|
61
|
+
wsToken: value.wsToken,
|
|
62
|
+
...typeof value.version === "string" ? { version: value.version } : {},
|
|
63
|
+
...typeof value.protocolVersion === "number" ? { protocolVersion: value.protocolVersion } : {},
|
|
64
|
+
...Array.isArray(value.capabilities) && value.capabilities.every((item) => typeof item === "string") ? { capabilities: value.capabilities } : {},
|
|
65
|
+
...value.ownerKind === "cli" || value.ownerKind === "desktop" ? { ownerKind: value.ownerKind } : {},
|
|
66
|
+
channel,
|
|
67
|
+
...typeof value.startedAt === "number" ? { startedAt: value.startedAt } : {},
|
|
68
|
+
...typeof value.ownerId === "string" ? { ownerId: value.ownerId } : {},
|
|
69
|
+
state: "ready"
|
|
70
|
+
};
|
|
35
71
|
} catch {
|
|
36
72
|
return null;
|
|
37
73
|
}
|
|
@@ -75,6 +111,7 @@ function writeNew(path, lock) {
|
|
|
75
111
|
}
|
|
76
112
|
}
|
|
77
113
|
function acquireLock(lock, opts = {}) {
|
|
114
|
+
if (lock.channel !== resolveDaemonChannel(opts.channel)) return false;
|
|
78
115
|
try {
|
|
79
116
|
ensureLockDir(opts);
|
|
80
117
|
return writeNew(lockfilePath(opts), lock);
|
|
@@ -84,7 +121,7 @@ function acquireLock(lock, opts = {}) {
|
|
|
84
121
|
}
|
|
85
122
|
function writeLock(lock, opts = {}) {
|
|
86
123
|
const current = readLock(opts);
|
|
87
|
-
if (!current || current.ownerId !== lock.ownerId || current.pid !== process.pid) return false;
|
|
124
|
+
if (!current || current.ownerId !== lock.ownerId || current.pid !== process.pid || lock.channel !== current.channel) return false;
|
|
88
125
|
const path = lockfilePath(opts);
|
|
89
126
|
const tmp = join(lockDir(opts), `daemon.json.${process.pid}.${lock.ownerId}.tmp`);
|
|
90
127
|
let fd;
|
|
@@ -164,33 +201,78 @@ function isAlive(pid) {
|
|
|
164
201
|
function sameCapabilities(actual, expected) {
|
|
165
202
|
return Array.isArray(actual) && actual.length === expected.length && actual.every((capability, index) => capability === expected[index]);
|
|
166
203
|
}
|
|
167
|
-
async function
|
|
168
|
-
if (!isLoopbackUrl(url) || !token) return
|
|
204
|
+
async function ownerHealth(url, token, timeoutMs = 500) {
|
|
205
|
+
if (!isLoopbackUrl(url) || !token) return null;
|
|
169
206
|
try {
|
|
170
207
|
const res = await fetch(`${url.replace(/\/+$/, "")}/healthz`, {
|
|
171
208
|
headers: { "x-tokmon-token": token },
|
|
172
209
|
signal: AbortSignal.timeout(timeoutMs)
|
|
173
210
|
});
|
|
174
|
-
if (!res.ok) return
|
|
175
|
-
|
|
176
|
-
return body.ok === true && body.owner === true && (expected.version === void 0 || body.version === expected.version) && (expected.protocolVersion === void 0 || body.protocolVersion === expected.protocolVersion) && (expected.capabilities === void 0 || sameCapabilities(body.capabilities, expected.capabilities)) && (expected.ownerKind === void 0 || body.ownerKind === expected.ownerKind);
|
|
211
|
+
if (!res.ok) return null;
|
|
212
|
+
return await res.json();
|
|
177
213
|
} catch {
|
|
178
|
-
return
|
|
214
|
+
return null;
|
|
179
215
|
}
|
|
180
216
|
}
|
|
217
|
+
async function probeHealth(url, token, expected = {}, timeoutMs = 500) {
|
|
218
|
+
const body = await ownerHealth(url, token, timeoutMs);
|
|
219
|
+
const bodyChannel = daemonChannelFromWire(body?.channel);
|
|
220
|
+
return body !== null && body.ok === true && body.owner === true && (expected.version === void 0 || body.version === expected.version) && (expected.protocolVersion === void 0 || body.protocolVersion === expected.protocolVersion) && (expected.capabilities === void 0 || sameCapabilities(body.capabilities, expected.capabilities)) && (expected.ownerKind === void 0 || body.ownerKind === expected.ownerKind) && (expected.channel === void 0 || bodyChannel === expected.channel);
|
|
221
|
+
}
|
|
181
222
|
async function verifyLock(lock, protocolVersion, timeoutMs) {
|
|
182
223
|
if (!lock || lock.state !== "ready" || lock.protocolVersion !== protocolVersion || !isAlive(lock.pid)) return null;
|
|
183
224
|
return await probeHealth(lock.url, lock.wsToken, lock, timeoutMs) ? lock : null;
|
|
184
225
|
}
|
|
226
|
+
var TAKEOVER_TIMEOUT_MS = 5e3;
|
|
227
|
+
var takeoverDelay = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
228
|
+
function sameForeignOwner(left, right) {
|
|
229
|
+
return left.pid === right.pid && left.wsToken === right.wsToken && left.ownerId === right.ownerId;
|
|
230
|
+
}
|
|
231
|
+
async function retireIncompatibleCliOwner(opts, protocolVersion, timeoutMs = TAKEOVER_TIMEOUT_MS) {
|
|
232
|
+
const compatible = readLock(opts);
|
|
233
|
+
if (compatible?.protocolVersion === protocolVersion) return false;
|
|
234
|
+
const foreign = readForeignLock(opts);
|
|
235
|
+
const legacyCli = foreign?.ownerKind === void 0 && foreign?.protocolVersion === void 0;
|
|
236
|
+
if (!foreign || foreign.state !== "ready" || foreign.ownerKind === "desktop" || foreign.ownerKind !== "cli" && !legacyCli || foreign.pid === process.pid || !isAlive(foreign.pid)) return false;
|
|
237
|
+
const health = await ownerHealth(foreign.url, foreign.wsToken, Math.min(1e3, timeoutMs));
|
|
238
|
+
const healthChannel = daemonChannelFromWire(health?.channel);
|
|
239
|
+
if (health?.ok !== true || health.owner !== true || health.ownerKind !== void 0 && health.ownerKind !== "cli" || healthChannel !== foreign.channel) return false;
|
|
240
|
+
try {
|
|
241
|
+
process.kill(foreign.pid, "SIGTERM");
|
|
242
|
+
} catch {
|
|
243
|
+
if (!isAlive(foreign.pid)) {
|
|
244
|
+
reclaimAbandonedLock(opts, 0);
|
|
245
|
+
return true;
|
|
246
|
+
}
|
|
247
|
+
return false;
|
|
248
|
+
}
|
|
249
|
+
const deadline = Date.now() + Math.max(0, timeoutMs);
|
|
250
|
+
while (Date.now() < deadline) {
|
|
251
|
+
const current = readForeignLock(opts);
|
|
252
|
+
if (!current || !sameForeignOwner(current, foreign)) return true;
|
|
253
|
+
if (!isAlive(foreign.pid)) {
|
|
254
|
+
reclaimAbandonedLock(opts, 0);
|
|
255
|
+
return true;
|
|
256
|
+
}
|
|
257
|
+
await takeoverDelay(50);
|
|
258
|
+
}
|
|
259
|
+
if (!isAlive(foreign.pid)) {
|
|
260
|
+
reclaimAbandonedLock(opts, 0);
|
|
261
|
+
return true;
|
|
262
|
+
}
|
|
263
|
+
return false;
|
|
264
|
+
}
|
|
185
265
|
|
|
186
266
|
export {
|
|
267
|
+
lockfilePath,
|
|
187
268
|
readLock,
|
|
269
|
+
readForeignLock,
|
|
188
270
|
acquireLock,
|
|
189
271
|
writeLock,
|
|
190
272
|
unlinkLock,
|
|
191
273
|
reclaimDeadLock,
|
|
192
274
|
reclaimAbandonedLock,
|
|
193
275
|
isAlive,
|
|
194
|
-
|
|
195
|
-
|
|
276
|
+
verifyLock,
|
|
277
|
+
retireIncompatibleCliOwner
|
|
196
278
|
};
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// src/web/daemon-channel.ts
|
|
4
|
+
var RELEASE_DAEMON_CHANNEL = "release";
|
|
5
|
+
var DEV_DAEMON_CHANNEL = "dev";
|
|
6
|
+
function resolveDaemonChannel(explicit, env = process.env) {
|
|
7
|
+
if (explicit) return explicit;
|
|
8
|
+
const configured = env.TOKMON_CHANNEL ?? env.TOKMON_RUNTIME_CHANNEL ?? env.TOKMON_RUNTIME_TAG;
|
|
9
|
+
return configured === DEV_DAEMON_CHANNEL ? DEV_DAEMON_CHANNEL : RELEASE_DAEMON_CHANNEL;
|
|
10
|
+
}
|
|
11
|
+
function daemonChannelFromWire(value) {
|
|
12
|
+
if (value === void 0) return RELEASE_DAEMON_CHANNEL;
|
|
13
|
+
if (value === RELEASE_DAEMON_CHANNEL) return RELEASE_DAEMON_CHANNEL;
|
|
14
|
+
if (value === DEV_DAEMON_CHANNEL) return DEV_DAEMON_CHANNEL;
|
|
15
|
+
return null;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export {
|
|
19
|
+
resolveDaemonChannel,
|
|
20
|
+
daemonChannelFromWire
|
|
21
|
+
};
|
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
3
|
attachOrSpawn
|
|
4
|
-
} from "./chunk-
|
|
5
|
-
import "./chunk-
|
|
4
|
+
} from "./chunk-P5JN5DDX.js";
|
|
5
|
+
import "./chunk-USKOQIE3.js";
|
|
6
|
+
import "./chunk-UYPDMVW5.js";
|
|
6
7
|
import {
|
|
7
8
|
createDaemonRpcClient
|
|
8
|
-
} from "./chunk-
|
|
9
|
+
} from "./chunk-7CH6S7BR.js";
|
|
9
10
|
import {
|
|
10
11
|
PROVIDERS,
|
|
11
12
|
antigravityStateDb,
|
|
@@ -25,12 +26,13 @@ import {
|
|
|
25
26
|
startOfMonth,
|
|
26
27
|
startOfWeek,
|
|
27
28
|
withTimeout
|
|
28
|
-
} from "./chunk-
|
|
29
|
-
import "./chunk-
|
|
29
|
+
} from "./chunk-CFKUZCMB.js";
|
|
30
|
+
import "./chunk-JZSZEHVD.js";
|
|
30
31
|
import {
|
|
32
|
+
DESKTOP_GRAPH_RANGES,
|
|
31
33
|
PROVIDER_IDS,
|
|
32
34
|
configLocation
|
|
33
|
-
} from "./chunk-
|
|
35
|
+
} from "./chunk-HNWEJ6MS.js";
|
|
34
36
|
|
|
35
37
|
// src/provider-locations.ts
|
|
36
38
|
import { access } from "fs/promises";
|
|
@@ -133,7 +135,7 @@ async function cliSource(account) {
|
|
|
133
135
|
providerId: account.providerId,
|
|
134
136
|
provider: PROVIDERS[account.providerId].name,
|
|
135
137
|
accountId: account.id,
|
|
136
|
-
account: account.email || account.displayName || account.name,
|
|
138
|
+
account: account.identity?.accessibleLabel ?? (account.email || account.displayName || account.name),
|
|
137
139
|
homeDir: account.homeDir,
|
|
138
140
|
locations: await providerLocations(account.providerId, account.homeDir ?? void 0)
|
|
139
141
|
};
|
|
@@ -341,15 +343,38 @@ Options:
|
|
|
341
343
|
--timeout <seconds> Startup/refresh timeout (default: 45)
|
|
342
344
|
-h, --help Show this help
|
|
343
345
|
`;
|
|
344
|
-
var CONFIG_HELP = `tokmon config -
|
|
346
|
+
var CONFIG_HELP = `tokmon config - Read and update tokmon settings
|
|
345
347
|
|
|
346
348
|
Usage:
|
|
347
349
|
tokmon config [path]
|
|
348
350
|
tokmon config --json
|
|
351
|
+
tokmon config get [--json]
|
|
352
|
+
tokmon config set <setting> <value> [--json]
|
|
353
|
+
|
|
354
|
+
Settings:
|
|
355
|
+
privacy <on|off>
|
|
356
|
+
privacy-key <char>
|
|
357
|
+
menu-bar-pins <ids|none> Comma-separated provider ids; at most 2
|
|
358
|
+
menu-bar-text <on|off>
|
|
359
|
+
summary-mode <smart|tightest>
|
|
360
|
+
expanded-providers <ids|none> Comma-separated provider ids
|
|
361
|
+
active-window <minutes> 1..1440
|
|
362
|
+
graph-range <7|14|30> Desktop spend graph days
|
|
363
|
+
auto-detect <on|off> Automatic account discovery
|
|
364
|
+
auto-detect-providers <ids|none>
|
|
365
|
+
launch-at-login <on|off>
|
|
349
366
|
|
|
350
367
|
Options:
|
|
351
|
-
--json Emit
|
|
368
|
+
--json Emit machine-readable JSON
|
|
369
|
+
--compact Emit compact JSON
|
|
370
|
+
--timeout <seconds> Daemon connection timeout (default: 45)
|
|
352
371
|
-h, --help Show this help
|
|
372
|
+
|
|
373
|
+
Examples:
|
|
374
|
+
tokmon config get
|
|
375
|
+
tokmon config set privacy off
|
|
376
|
+
tokmon config set menu-bar-pins claude,codex
|
|
377
|
+
tokmon config set summary-mode smart --json
|
|
353
378
|
`;
|
|
354
379
|
function valueAfter(args, index, name) {
|
|
355
380
|
const value = args[index + 1];
|
|
@@ -430,6 +455,15 @@ function isSnapshot(value) {
|
|
|
430
455
|
var delay = (ms) => new Promise((resolve) => {
|
|
431
456
|
setTimeout(resolve, ms);
|
|
432
457
|
});
|
|
458
|
+
async function connectDaemonConfig(timeoutMs) {
|
|
459
|
+
const handle = await attachOrSpawn({ timeoutMs });
|
|
460
|
+
if (handle.kind !== "spawned" || !handle.baseUrl) throw new Error("tokmon daemon is unavailable");
|
|
461
|
+
return createDaemonRpcClient(handle.baseUrl, {
|
|
462
|
+
transport: "node",
|
|
463
|
+
reconnectAttempts: 2,
|
|
464
|
+
reconnectBaseDelayMs: 100
|
|
465
|
+
});
|
|
466
|
+
}
|
|
433
467
|
async function fetchDaemonSnapshot(timeoutMs, refresh) {
|
|
434
468
|
const handle = await attachOrSpawn({ timeoutMs });
|
|
435
469
|
if (handle.kind !== "spawned" || !handle.baseUrl) throw new Error("tokmon daemon is unavailable");
|
|
@@ -475,6 +509,209 @@ function rejectsOption(args, names) {
|
|
|
475
509
|
}
|
|
476
510
|
return null;
|
|
477
511
|
}
|
|
512
|
+
var CONFIG_SETTINGS = [
|
|
513
|
+
"privacy",
|
|
514
|
+
"privacy-key",
|
|
515
|
+
"menu-bar-pins",
|
|
516
|
+
"menu-bar-text",
|
|
517
|
+
"summary-mode",
|
|
518
|
+
"expanded-providers",
|
|
519
|
+
"active-window",
|
|
520
|
+
"graph-range",
|
|
521
|
+
"auto-detect",
|
|
522
|
+
"auto-detect-providers",
|
|
523
|
+
"launch-at-login"
|
|
524
|
+
];
|
|
525
|
+
function configReport(config) {
|
|
526
|
+
return {
|
|
527
|
+
revision: config.revision,
|
|
528
|
+
privacy: config.privacyMode ? "on" : "off",
|
|
529
|
+
privacyKey: config.privacyToggleKey,
|
|
530
|
+
menuBarPins: [...config.tray.pinnedProviders],
|
|
531
|
+
menuBarText: config.tray.showMenuBarText ? "on" : "off",
|
|
532
|
+
summaryMode: config.tray.displayMetric === "smartHeadroom" ? "smart" : "tightest",
|
|
533
|
+
expandedProviders: [...config.desktop.expandedProviders],
|
|
534
|
+
activeWindowMinutes: config.tray.activeTimeoutMin,
|
|
535
|
+
graphRangeDays: config.desktop.graphRangeDays,
|
|
536
|
+
autoDetect: config.accountDetection.enabled ? "on" : "off",
|
|
537
|
+
autoDetectProviders: PROVIDER_IDS.filter((id) => !config.accountDetection.disabledProviders.includes(id)),
|
|
538
|
+
launchAtLogin: config.tray.launchAtLogin ? "on" : "off"
|
|
539
|
+
};
|
|
540
|
+
}
|
|
541
|
+
function formatConfigReport(report) {
|
|
542
|
+
const list = (values) => values.length > 0 ? values.join(",") : "none";
|
|
543
|
+
return [
|
|
544
|
+
`privacy ${report.privacy}`,
|
|
545
|
+
`privacy-key ${report.privacyKey}`,
|
|
546
|
+
`menu-bar-pins ${list(report.menuBarPins)}`,
|
|
547
|
+
`menu-bar-text ${report.menuBarText}`,
|
|
548
|
+
`summary-mode ${report.summaryMode}`,
|
|
549
|
+
`expanded-providers ${list(report.expandedProviders)}`,
|
|
550
|
+
`active-window ${report.activeWindowMinutes}m`,
|
|
551
|
+
`graph-range ${report.graphRangeDays}d`,
|
|
552
|
+
`auto-detect ${report.autoDetect}`,
|
|
553
|
+
`auto-detect-providers ${list(report.autoDetectProviders)}`,
|
|
554
|
+
`launch-at-login ${report.launchAtLogin}`
|
|
555
|
+
].join("\n") + "\n";
|
|
556
|
+
}
|
|
557
|
+
function onOff(value, setting) {
|
|
558
|
+
if (value === "on") return true;
|
|
559
|
+
if (value === "off") return false;
|
|
560
|
+
throw new Error(`${setting} must be on or off`);
|
|
561
|
+
}
|
|
562
|
+
function providerList(value, setting, max = PROVIDER_IDS.length) {
|
|
563
|
+
if (value === "none") return [];
|
|
564
|
+
const raw = value.split(",").map((item) => item.trim());
|
|
565
|
+
if (raw.length === 0 || raw.some((item) => item.length === 0)) {
|
|
566
|
+
throw new Error(`${setting} must be a comma-separated provider list or none`);
|
|
567
|
+
}
|
|
568
|
+
const unique2 = [...new Set(raw)];
|
|
569
|
+
for (const provider of unique2) {
|
|
570
|
+
if (!PROVIDER_IDS.includes(provider)) throw new Error(`unknown provider: ${provider}`);
|
|
571
|
+
}
|
|
572
|
+
if (unique2.length > max) throw new Error(`${setting} accepts at most ${max} providers`);
|
|
573
|
+
return unique2;
|
|
574
|
+
}
|
|
575
|
+
function settingMutation(setting, value) {
|
|
576
|
+
if (setting === "privacy") {
|
|
577
|
+
const enabled2 = onOff(value, setting);
|
|
578
|
+
return { mutate: (config) => ({ ...config, privacyMode: enabled2 }), display: value };
|
|
579
|
+
}
|
|
580
|
+
if (setting === "privacy-key") {
|
|
581
|
+
if (value.length !== 1 || new RegExp("\\s|\\p{Cc}", "u").test(value)) throw new Error("privacy-key must be one printable non-whitespace character");
|
|
582
|
+
return { mutate: (config) => ({ ...config, privacyToggleKey: value }), display: value };
|
|
583
|
+
}
|
|
584
|
+
if (setting === "menu-bar-pins") {
|
|
585
|
+
const providers = providerList(value, setting, 2);
|
|
586
|
+
return {
|
|
587
|
+
mutate: (config) => ({
|
|
588
|
+
...config,
|
|
589
|
+
tray: { ...config.tray, pinnedProviders: providers, pins: [], pinnedAccount: null }
|
|
590
|
+
}),
|
|
591
|
+
display: providers
|
|
592
|
+
};
|
|
593
|
+
}
|
|
594
|
+
if (setting === "menu-bar-text") {
|
|
595
|
+
const enabled2 = onOff(value, setting);
|
|
596
|
+
return { mutate: (config) => ({ ...config, tray: { ...config.tray, showMenuBarText: enabled2 } }), display: value };
|
|
597
|
+
}
|
|
598
|
+
if (setting === "summary-mode") {
|
|
599
|
+
if (value !== "smart" && value !== "tightest") throw new Error("summary-mode must be smart or tightest");
|
|
600
|
+
const displayMetric = value === "smart" ? "smartHeadroom" : "tightestRemaining";
|
|
601
|
+
return { mutate: (config) => ({ ...config, tray: { ...config.tray, displayMetric } }), display: value };
|
|
602
|
+
}
|
|
603
|
+
if (setting === "expanded-providers") {
|
|
604
|
+
const providers = providerList(value, setting);
|
|
605
|
+
return {
|
|
606
|
+
mutate: (config) => ({ ...config, desktop: { ...config.desktop, expandedProviders: providers } }),
|
|
607
|
+
display: providers
|
|
608
|
+
};
|
|
609
|
+
}
|
|
610
|
+
if (setting === "active-window") {
|
|
611
|
+
const minutes = Number(value);
|
|
612
|
+
if (!Number.isInteger(minutes) || minutes < 1 || minutes > 1440) throw new Error("active-window must be an integer from 1 to 1440 minutes");
|
|
613
|
+
return { mutate: (config) => ({ ...config, tray: { ...config.tray, activeTimeoutMin: minutes } }), display: minutes };
|
|
614
|
+
}
|
|
615
|
+
if (setting === "graph-range") {
|
|
616
|
+
const days = Number(value);
|
|
617
|
+
if (!DESKTOP_GRAPH_RANGES.includes(days)) throw new Error("graph-range must be 7, 14, or 30 days");
|
|
618
|
+
return {
|
|
619
|
+
mutate: (config) => ({ ...config, desktop: { ...config.desktop, graphRangeDays: days } }),
|
|
620
|
+
display: days
|
|
621
|
+
};
|
|
622
|
+
}
|
|
623
|
+
if (setting === "auto-detect") {
|
|
624
|
+
const enabled2 = onOff(value, setting);
|
|
625
|
+
return {
|
|
626
|
+
mutate: (config) => ({ ...config, accountDetection: { ...config.accountDetection, enabled: enabled2 } }),
|
|
627
|
+
display: value
|
|
628
|
+
};
|
|
629
|
+
}
|
|
630
|
+
if (setting === "auto-detect-providers") {
|
|
631
|
+
const enabled2 = providerList(value, setting);
|
|
632
|
+
return {
|
|
633
|
+
mutate: (config) => ({
|
|
634
|
+
...config,
|
|
635
|
+
accountDetection: {
|
|
636
|
+
...config.accountDetection,
|
|
637
|
+
disabledProviders: PROVIDER_IDS.filter((provider) => !enabled2.includes(provider))
|
|
638
|
+
}
|
|
639
|
+
}),
|
|
640
|
+
display: enabled2
|
|
641
|
+
};
|
|
642
|
+
}
|
|
643
|
+
if (setting !== "launch-at-login") throw new Error(`unsupported config setting: ${setting}`);
|
|
644
|
+
const enabled = onOff(value, setting);
|
|
645
|
+
return { mutate: (config) => ({ ...config, tray: { ...config.tray, launchAtLogin: enabled } }), display: value };
|
|
646
|
+
}
|
|
647
|
+
function isConfigConflict(cause) {
|
|
648
|
+
return !!cause && typeof cause === "object" && cause.kind === "conflict";
|
|
649
|
+
}
|
|
650
|
+
async function readDaemonConfig(timeoutMs, connect) {
|
|
651
|
+
const client = await connect(timeoutMs);
|
|
652
|
+
try {
|
|
653
|
+
const state = await withTimeout(client.getConfig(), timeoutMs);
|
|
654
|
+
return {
|
|
655
|
+
state,
|
|
656
|
+
close: () => client.close(),
|
|
657
|
+
get: () => withTimeout(client.getConfig(), timeoutMs),
|
|
658
|
+
set: (update) => withTimeout(client.setConfig(update), timeoutMs)
|
|
659
|
+
};
|
|
660
|
+
} catch (cause) {
|
|
661
|
+
await client.close().catch(() => {
|
|
662
|
+
});
|
|
663
|
+
throw cause;
|
|
664
|
+
}
|
|
665
|
+
}
|
|
666
|
+
async function runConfigCommand(args, configPath, connect) {
|
|
667
|
+
const [action, settingName, value, ...extra] = args.positionals;
|
|
668
|
+
if (!action || action === "path") {
|
|
669
|
+
if (settingName || value || extra.length > 0) throw new Error("usage: tokmon config [path] [--json]");
|
|
670
|
+
return args.json ? json({ path: configPath() }, args.compact) : `${configPath()}
|
|
671
|
+
`;
|
|
672
|
+
}
|
|
673
|
+
if (action !== "get" && action !== "set") throw new Error("usage: tokmon config [path|get|set]");
|
|
674
|
+
if (action === "get") {
|
|
675
|
+
if (settingName || value || extra.length > 0) throw new Error("usage: tokmon config get [--json]");
|
|
676
|
+
const daemon2 = await readDaemonConfig(args.timeoutMs, connect);
|
|
677
|
+
try {
|
|
678
|
+
const report = configReport(daemon2.state.config);
|
|
679
|
+
return args.json ? json(report, args.compact) : formatConfigReport(report);
|
|
680
|
+
} finally {
|
|
681
|
+
await daemon2.close().catch(() => {
|
|
682
|
+
});
|
|
683
|
+
}
|
|
684
|
+
}
|
|
685
|
+
if (!settingName || !CONFIG_SETTINGS.includes(settingName) || value === void 0 || extra.length > 0) {
|
|
686
|
+
throw new Error("usage: tokmon config set <setting> <value>");
|
|
687
|
+
}
|
|
688
|
+
const setting = settingName;
|
|
689
|
+
const update = settingMutation(setting, value);
|
|
690
|
+
const daemon = await readDaemonConfig(args.timeoutMs, connect);
|
|
691
|
+
try {
|
|
692
|
+
let base = daemon.state;
|
|
693
|
+
let saved = null;
|
|
694
|
+
for (let attempt = 0; attempt < 2; attempt++) {
|
|
695
|
+
try {
|
|
696
|
+
saved = await daemon.set({
|
|
697
|
+
expectedRevision: base.config.revision,
|
|
698
|
+
config: update.mutate(base.config)
|
|
699
|
+
});
|
|
700
|
+
break;
|
|
701
|
+
} catch (cause) {
|
|
702
|
+
if (attempt > 0 || !isConfigConflict(cause)) throw cause;
|
|
703
|
+
base = await daemon.get();
|
|
704
|
+
}
|
|
705
|
+
}
|
|
706
|
+
if (!saved) throw new Error("config update failed");
|
|
707
|
+
const result = { setting, value: update.display, revision: saved.config.revision };
|
|
708
|
+
return args.json ? json(result, args.compact) : `${setting} ${Array.isArray(update.display) ? update.display.join(",") || "none" : update.display}
|
|
709
|
+
`;
|
|
710
|
+
} finally {
|
|
711
|
+
await daemon.close().catch(() => {
|
|
712
|
+
});
|
|
713
|
+
}
|
|
714
|
+
}
|
|
478
715
|
function queryHelp(command) {
|
|
479
716
|
if (command === "providers") return PROVIDERS_HELP;
|
|
480
717
|
if (command === "snapshot") return SNAPSHOT_HELP;
|
|
@@ -486,13 +723,9 @@ async function runQueryCommand(command, args, dependencies = {}) {
|
|
|
486
723
|
if (parsed.help) return queryHelp(command);
|
|
487
724
|
const getConfigPath = dependencies.configPath ?? configLocation;
|
|
488
725
|
if (command === "config") {
|
|
489
|
-
const invalid = rejectsOption(args, ["--period", "--provider", "--account", "--model", "--refresh", "--cached", "--no-refresh"
|
|
726
|
+
const invalid = rejectsOption(args, ["--period", "--provider", "--account", "--model", "--refresh", "--cached", "--no-refresh"]);
|
|
490
727
|
if (invalid) throw new Error(`${invalid} is not valid for tokmon config`);
|
|
491
|
-
|
|
492
|
-
throw new Error("usage: tokmon config [path] [--json]");
|
|
493
|
-
}
|
|
494
|
-
return parsed.json ? json({ path: getConfigPath() }, parsed.compact) : `${getConfigPath()}
|
|
495
|
-
`;
|
|
728
|
+
return runConfigCommand(parsed, getConfigPath, dependencies.connectConfig ?? connectDaemonConfig);
|
|
496
729
|
}
|
|
497
730
|
if (command === "providers" || command === "snapshot") {
|
|
498
731
|
const invalid = rejectsOption(args, ["--period", "--provider", "--account", "--model", "--cached", "--no-refresh"]);
|
|
@@ -519,6 +752,7 @@ async function runQueryCommand(command, args, dependencies = {}) {
|
|
|
519
752
|
`;
|
|
520
753
|
}
|
|
521
754
|
export {
|
|
755
|
+
connectDaemonConfig,
|
|
522
756
|
fetchDaemonSnapshot,
|
|
523
757
|
parseQueryArgs,
|
|
524
758
|
queryHelp,
|
package/dist/cli.js
CHANGED
|
@@ -41,7 +41,7 @@ function validateServeArgs(serveArgs) {
|
|
|
41
41
|
}
|
|
42
42
|
async function main() {
|
|
43
43
|
if (subcommand && ["usage", "models", "query", "providers", "snapshot", "config"].includes(subcommand)) {
|
|
44
|
-
const { runQueryCommand } = await import("./cli-command-
|
|
44
|
+
const { runQueryCommand } = await import("./cli-command-MD3RJRP4.js");
|
|
45
45
|
try {
|
|
46
46
|
const output = await runQueryCommand(
|
|
47
47
|
subcommand,
|
|
@@ -60,14 +60,14 @@ Run tokmon ${subcommand} --help for usage.
|
|
|
60
60
|
return;
|
|
61
61
|
}
|
|
62
62
|
if (subcommand === "__daemon") {
|
|
63
|
-
const { runDaemon } = await import("./daemon-
|
|
63
|
+
const { runDaemon } = await import("./daemon-EMOA36WB.js");
|
|
64
64
|
await runDaemon(args.slice(1), { foreground: false });
|
|
65
65
|
process.exitCode ??= 0;
|
|
66
66
|
return;
|
|
67
67
|
}
|
|
68
68
|
if (subcommand === "serve" || subcommand === "web") {
|
|
69
69
|
validateServeArgs(args.slice(1));
|
|
70
|
-
const { runDaemon } = await import("./daemon-
|
|
70
|
+
const { runDaemon } = await import("./daemon-EMOA36WB.js");
|
|
71
71
|
await runDaemon(args.slice(1), { foreground: true });
|
|
72
72
|
process.exitCode ??= 0;
|
|
73
73
|
return;
|
|
@@ -115,9 +115,9 @@ Run tokmon ${subcommand} --help for usage.
|
|
|
115
115
|
return;
|
|
116
116
|
}
|
|
117
117
|
}
|
|
118
|
-
const { loadConfig } = await import("./config-
|
|
118
|
+
const { loadConfig } = await import("./config-3DGZ47F7.js");
|
|
119
119
|
const { resolveGlyphs, setGlyphs } = await import("./glyphs-NKCSZLGO.js");
|
|
120
|
-
const { attachOrSpawn } = await import("./daemon-handle-
|
|
120
|
+
const { attachOrSpawn } = await import("./daemon-handle-JHE5MKMI.js");
|
|
121
121
|
const config = await loadConfig();
|
|
122
122
|
setGlyphs(resolveGlyphs({
|
|
123
123
|
flag: asciiFlag,
|
|
@@ -128,7 +128,7 @@ Run tokmon ${subcommand} --help for usage.
|
|
|
128
128
|
}));
|
|
129
129
|
const daemon = await attachOrSpawn();
|
|
130
130
|
const mode = daemon.kind === "spawned" ? "connected" : "degraded";
|
|
131
|
-
const { bootstrapInk } = await import("./bootstrap-ink-
|
|
131
|
+
const { bootstrapInk } = await import("./bootstrap-ink-WG4WG3R2.js");
|
|
132
132
|
await bootstrapInk({ interval, config, daemon, mode });
|
|
133
133
|
}
|
|
134
134
|
void main().catch((error) => {
|
|
@@ -3,10 +3,15 @@ import {
|
|
|
3
3
|
ACCENT_COLORS,
|
|
4
4
|
COLOR_PALETTE,
|
|
5
5
|
DEFAULTS,
|
|
6
|
+
DEFAULT_ACCOUNT_DETECTION_CONFIG,
|
|
7
|
+
DEFAULT_TRAY_CONFIG,
|
|
8
|
+
DESKTOP_GRAPH_RANGES,
|
|
9
|
+
MAX_PINNED_PROVIDERS,
|
|
6
10
|
PROVIDER_IDS,
|
|
7
11
|
PROVIDER_META,
|
|
8
12
|
cacheDir,
|
|
9
13
|
clampNum,
|
|
14
|
+
cleanProviderSelection,
|
|
10
15
|
configLocation,
|
|
11
16
|
containsEmail,
|
|
12
17
|
envDir,
|
|
@@ -15,26 +20,36 @@ import {
|
|
|
15
20
|
generateAccountId,
|
|
16
21
|
getTrackedAccountRows,
|
|
17
22
|
loadConfig,
|
|
23
|
+
moveProviderSelection,
|
|
18
24
|
normalizeAllowedHost,
|
|
19
25
|
normalizeAllowedHosts,
|
|
20
26
|
normalizeConfig,
|
|
21
27
|
pickAccentColor,
|
|
28
|
+
providerDetectionEnabled,
|
|
22
29
|
redactEmail,
|
|
23
30
|
repairConfig,
|
|
24
31
|
sanitizeTyped,
|
|
25
32
|
saveConfig,
|
|
26
33
|
saveConfigSync,
|
|
34
|
+
setDetectedAccountExcluded,
|
|
35
|
+
setProviderDetectionEnabled,
|
|
27
36
|
slugify,
|
|
28
|
-
snapshotCacheFile
|
|
29
|
-
|
|
37
|
+
snapshotCacheFile,
|
|
38
|
+
toggleProviderSelection
|
|
39
|
+
} from "./chunk-HNWEJ6MS.js";
|
|
30
40
|
export {
|
|
31
41
|
ACCENT_COLORS,
|
|
32
42
|
COLOR_PALETTE,
|
|
33
43
|
DEFAULTS,
|
|
44
|
+
DEFAULT_ACCOUNT_DETECTION_CONFIG,
|
|
45
|
+
DEFAULT_TRAY_CONFIG,
|
|
46
|
+
DESKTOP_GRAPH_RANGES,
|
|
47
|
+
MAX_PINNED_PROVIDERS,
|
|
34
48
|
PROVIDER_IDS,
|
|
35
49
|
PROVIDER_META,
|
|
36
50
|
cacheDir,
|
|
37
51
|
clampNum,
|
|
52
|
+
cleanProviderSelection,
|
|
38
53
|
configLocation,
|
|
39
54
|
containsEmail,
|
|
40
55
|
envDir,
|
|
@@ -43,15 +58,20 @@ export {
|
|
|
43
58
|
generateAccountId,
|
|
44
59
|
getTrackedAccountRows,
|
|
45
60
|
loadConfig,
|
|
61
|
+
moveProviderSelection,
|
|
46
62
|
normalizeAllowedHost,
|
|
47
63
|
normalizeAllowedHosts,
|
|
48
64
|
normalizeConfig,
|
|
49
65
|
pickAccentColor,
|
|
66
|
+
providerDetectionEnabled,
|
|
50
67
|
redactEmail,
|
|
51
68
|
repairConfig,
|
|
52
69
|
sanitizeTyped,
|
|
53
70
|
saveConfig,
|
|
54
71
|
saveConfigSync,
|
|
72
|
+
setDetectedAccountExcluded,
|
|
73
|
+
setProviderDetectionEnabled,
|
|
55
74
|
slugify,
|
|
56
|
-
snapshotCacheFile
|
|
75
|
+
snapshotCacheFile,
|
|
76
|
+
toggleProviderSelection
|
|
57
77
|
};
|