tokmon 0.28.1 → 0.28.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.
@@ -1,13 +1,13 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  createDaemonRpcClient
4
- } from "./chunk-3RTWFGGD.js";
4
+ } from "./chunk-M7XMV36F.js";
5
5
  import {
6
6
  accountsByProvider,
7
7
  buildAccounts,
8
8
  fetchPeak,
9
9
  modelColor
10
- } from "./chunk-FMP3P2WV.js";
10
+ } from "./chunk-44ZSQEOS.js";
11
11
  import {
12
12
  PROVIDERS,
13
13
  PROVIDER_ORDER,
@@ -24,7 +24,8 @@ import {
24
24
  time,
25
25
  tokens,
26
26
  withTimeout
27
- } from "./chunk-CVKCVHS7.js";
27
+ } from "./chunk-LCAHTRGP.js";
28
+ import "./chunk-WUK3MQUB.js";
28
29
  import {
29
30
  COLOR_PALETTE,
30
31
  DEFAULTS,
@@ -3560,7 +3561,7 @@ function App({ interval: cliInterval, initialConfig, baseUrl = null, mode = "deg
3560
3561
  if (webStartingRef.current) return;
3561
3562
  webStartingRef.current = true;
3562
3563
  try {
3563
- const { startWebServer } = await import("./server-EUO7CWYH.js");
3564
+ const { startWebServer } = await import("./server-GXXOWUVZ.js");
3564
3565
  const ctrl = await startWebServer({ config: cfg, log: false });
3565
3566
  webRef.current = ctrl;
3566
3567
  openUrl(ctrl.url);
@@ -5,7 +5,7 @@ import {
5
5
  identityFromIdToken,
6
6
  readClaudeIdentity,
7
7
  readJson
8
- } from "./chunk-CVKCVHS7.js";
8
+ } from "./chunk-LCAHTRGP.js";
9
9
  import {
10
10
  expandHome,
11
11
  slugify
@@ -1,28 +1,23 @@
1
1
  #!/usr/bin/env node
2
- import {
3
- appVersion,
4
- findWebRoot,
5
- send,
6
- sendJson,
7
- serveStatic
8
- } from "./chunk-SMPY52EV.js";
9
2
  import {
10
3
  buildAccounts,
11
4
  colorHex,
12
5
  fetchPeak,
13
6
  namedHex
14
- } from "./chunk-FMP3P2WV.js";
7
+ } from "./chunk-44ZSQEOS.js";
15
8
  import {
16
9
  PROVIDERS,
10
+ detectProviders,
11
+ resolveTimezone,
12
+ withTimeout
13
+ } from "./chunk-LCAHTRGP.js";
14
+ import {
17
15
  TOKMON_CAPABILITIES,
18
16
  TOKMON_PROTOCOL_VERSION,
19
17
  TOKMON_WS_METHODS,
20
18
  TOKMON_WS_PATH,
21
- TokmonRpcGroup,
22
- detectProviders,
23
- resolveTimezone,
24
- withTimeout
25
- } from "./chunk-CVKCVHS7.js";
19
+ TokmonRpcGroup
20
+ } from "./chunk-WUK3MQUB.js";
26
21
  import {
27
22
  cacheDir,
28
23
  expandHome,
@@ -118,11 +113,127 @@ function tzFor(config) {
118
113
  return resolveTimezone(config.timezone);
119
114
  }
120
115
 
116
+ // src/web/static.ts
117
+ import { createReadStream, existsSync, readFileSync } from "fs";
118
+ import { stat } from "fs/promises";
119
+ import { fileURLToPath } from "url";
120
+ import { extname, join, normalize, sep } from "path";
121
+ var MIME = {
122
+ ".html": "text/html; charset=utf-8",
123
+ ".js": "text/javascript; charset=utf-8",
124
+ ".mjs": "text/javascript; charset=utf-8",
125
+ ".css": "text/css; charset=utf-8",
126
+ ".json": "application/json; charset=utf-8",
127
+ ".svg": "image/svg+xml",
128
+ ".png": "image/png",
129
+ ".jpg": "image/jpeg",
130
+ ".jpeg": "image/jpeg",
131
+ ".gif": "image/gif",
132
+ ".ico": "image/x-icon",
133
+ ".woff": "font/woff",
134
+ ".woff2": "font/woff2",
135
+ ".ttf": "font/ttf",
136
+ ".otf": "font/otf",
137
+ ".map": "application/json; charset=utf-8",
138
+ ".webmanifest": "application/manifest+json"
139
+ };
140
+ function findWebRoot() {
141
+ const candidates = ["./web/", "../web/", "../dist/web/", "../../dist/web/"];
142
+ for (const rel of candidates) {
143
+ try {
144
+ const dir = fileURLToPath(new URL(rel, import.meta.url));
145
+ if (existsSync(join(dir, "index.html"))) return dir.replace(/[\\/]+$/, "");
146
+ } catch {
147
+ }
148
+ }
149
+ return null;
150
+ }
151
+ function resolveStaticPath(webRoot, urlPath) {
152
+ let clean;
153
+ try {
154
+ clean = decodeURIComponent(urlPath.split("?")[0]);
155
+ } catch {
156
+ return null;
157
+ }
158
+ const rel = normalize(clean).replace(/^(\.\.[/\\])+/, "").replace(/^[/\\]+/, "");
159
+ const full = join(webRoot, rel);
160
+ if (full !== webRoot && !full.startsWith(webRoot + sep)) return null;
161
+ return full;
162
+ }
163
+ function send(res, status, type, body) {
164
+ res.writeHead(status, {
165
+ "Content-Type": type,
166
+ "Cache-Control": "no-store",
167
+ "Referrer-Policy": "no-referrer",
168
+ "X-Content-Type-Options": "nosniff"
169
+ });
170
+ res.end(body);
171
+ }
172
+ function sendJson(res, status, data) {
173
+ send(res, status, "application/json; charset=utf-8", JSON.stringify(data));
174
+ }
175
+ function serveStatic(webRoot, urlPath, res) {
176
+ const path = urlPath.split("?")[0];
177
+ const filePath = resolveStaticPath(webRoot, path === "/" ? "/index.html" : path);
178
+ if (!filePath) {
179
+ send(res, 403, "text/plain", "forbidden");
180
+ return;
181
+ }
182
+ void stat(filePath).then((st) => {
183
+ if (st.isFile()) {
184
+ const type = MIME[extname(filePath).toLowerCase()] || "application/octet-stream";
185
+ const immutable = filePath.includes(`${sep}assets${sep}`);
186
+ res.writeHead(200, {
187
+ "Content-Type": type,
188
+ "Cache-Control": immutable ? "public, max-age=31536000, immutable" : "no-cache",
189
+ "Referrer-Policy": "no-referrer",
190
+ "X-Content-Type-Options": "nosniff"
191
+ });
192
+ createReadStream(filePath).pipe(res);
193
+ } else {
194
+ throw new Error("not a file");
195
+ }
196
+ }).catch(() => {
197
+ if (extname(path)) {
198
+ send(res, 404, "text/plain", "not found");
199
+ return;
200
+ }
201
+ const indexPath = join(webRoot, "index.html");
202
+ if (!existsSync(indexPath)) {
203
+ send(res, 404, "text/plain", "not found");
204
+ return;
205
+ }
206
+ res.writeHead(200, {
207
+ "Content-Type": MIME[".html"],
208
+ "Cache-Control": "no-cache",
209
+ "Referrer-Policy": "no-referrer",
210
+ "X-Content-Type-Options": "nosniff"
211
+ });
212
+ createReadStream(indexPath).on("error", () => {
213
+ try {
214
+ res.destroy();
215
+ } catch {
216
+ }
217
+ }).pipe(res);
218
+ });
219
+ }
220
+ function appVersion() {
221
+ for (const rel of ["../package.json", "../../package.json"]) {
222
+ try {
223
+ const p = fileURLToPath(new URL(rel, import.meta.url));
224
+ const pkg = JSON.parse(readFileSync(p, "utf-8"));
225
+ if (typeof pkg.version === "string") return pkg.version;
226
+ } catch {
227
+ }
228
+ }
229
+ return "";
230
+ }
231
+
121
232
  // src/web/vite-dev.ts
122
- import { existsSync } from "fs";
123
- import { fileURLToPath, pathToFileURL } from "url";
233
+ import { existsSync as existsSync2 } from "fs";
234
+ import { fileURLToPath as fileURLToPath2, pathToFileURL } from "url";
124
235
  import { createRequire } from "module";
125
- import { join } from "path";
236
+ import { join as join2 } from "path";
126
237
  function isDevMode() {
127
238
  const forced = process.env.TOKMON_WEB_MODE;
128
239
  if (forced === "dev") return true;
@@ -132,8 +243,8 @@ function isDevMode() {
132
243
  function findWebSource() {
133
244
  for (const rel of ["../../web/", "../web/", "./web/"]) {
134
245
  try {
135
- const dir = fileURLToPath(new URL(rel, import.meta.url));
136
- if (existsSync(join(dir, "vite.config.ts")) && existsSync(join(dir, "index.html"))) {
246
+ const dir = fileURLToPath2(new URL(rel, import.meta.url));
247
+ if (existsSync2(join2(dir, "vite.config.ts")) && existsSync2(join2(dir, "index.html"))) {
137
248
  return dir.replace(/[\\/]+$/, "");
138
249
  }
139
250
  } catch {
@@ -148,12 +259,12 @@ async function createViteDevServer(httpServer, log) {
148
259
  return null;
149
260
  }
150
261
  try {
151
- const req = createRequire(pathToFileURL(join(root, "package.json")).href);
262
+ const req = createRequire(pathToFileURL(join2(root, "package.json")).href);
152
263
  const vitePath = req.resolve("vite");
153
264
  const vite = await import(pathToFileURL(vitePath).href);
154
265
  const dev = await vite.createServer({
155
266
  root,
156
- configFile: join(root, "vite.config.ts"),
267
+ configFile: join2(root, "vite.config.ts"),
157
268
  server: { middlewareMode: true, hmr: { server: httpServer } },
158
269
  appType: "spa",
159
270
  clearScreen: false,
@@ -177,7 +288,7 @@ code{color:#e6b450}</style></head><body>
177
288
  </body></html>`;
178
289
 
179
290
  // src/web/data-engine.ts
180
- import { readFileSync, writeFileSync, mkdirSync, renameSync } from "fs";
291
+ import { readFileSync as readFileSync2, writeFileSync, mkdirSync, renameSync } from "fs";
181
292
 
182
293
  // src/web/refresh-queue.ts
183
294
  function deferred() {
@@ -335,7 +446,7 @@ function createDataEngine(opts) {
335
446
  });
336
447
  const hydrateFromCache = () => {
337
448
  try {
338
- const cached = JSON.parse(readFileSync(snapshotCacheFile(), "utf-8"));
449
+ const cached = JSON.parse(readFileSync2(snapshotCacheFile(), "utf-8"));
339
450
  if (!cached || !Array.isArray(cached.accounts)) return;
340
451
  for (const a of cached.accounts) {
341
452
  if (a.dashboard || a.table) {
@@ -703,11 +814,11 @@ import { Effect, Exit, Layer, Queue, Scope, Stream } from "effect";
703
814
  import { RpcSerialization, RpcServer } from "effect/unstable/rpc";
704
815
 
705
816
  // src/web/fs.ts
706
- import { readdir, stat, realpath } from "fs/promises";
817
+ import { readdir, stat as stat2, realpath } from "fs/promises";
707
818
  import { homedir } from "os";
708
- import { join as join2, resolve as resolvePath, isAbsolute, sep } from "path";
819
+ import { join as join3, resolve as resolvePath, isAbsolute, sep as sep2 } from "path";
709
820
  function isContained(root, target) {
710
- return target === root || target.startsWith(root + sep);
821
+ return target === root || target.startsWith(root + sep2);
711
822
  }
712
823
  function parentFor(root, abs) {
713
824
  const parentResolved = resolvePath(abs, "..");
@@ -727,7 +838,7 @@ async function listHomeDirectory(rawPath) {
727
838
  const abs = isContained(root, real) ? real : root;
728
839
  let st;
729
840
  try {
730
- st = await stat(abs);
841
+ st = await stat2(abs);
731
842
  } catch {
732
843
  return { path: abs, parent: parentFor(root, abs), entries: [] };
733
844
  }
@@ -742,7 +853,7 @@ async function listHomeDirectory(rawPath) {
742
853
  for (const d of dirents) {
743
854
  if (d.name.startsWith(".")) continue;
744
855
  let dir = d.isDirectory();
745
- const full = join2(abs, d.name);
856
+ const full = join3(abs, d.name);
746
857
  if (d.isSymbolicLink()) {
747
858
  let real2;
748
859
  try {
@@ -752,7 +863,7 @@ async function listHomeDirectory(rawPath) {
752
863
  }
753
864
  if (!isContained(root, real2)) continue;
754
865
  try {
755
- dir = (await stat(full)).isDirectory();
866
+ dir = (await stat2(full)).isDirectory();
756
867
  } catch {
757
868
  continue;
758
869
  }
@@ -957,7 +1068,7 @@ function guardSameOrigin(req, res) {
957
1068
  }
958
1069
  return true;
959
1070
  }
960
- function createRouter(engine, state, vite, webRoot, wsToken, version) {
1071
+ function createRouter(engine, state, vite, webRoot, wsToken, version, protocolVersion, capabilities, ownerKind) {
961
1072
  return (req, res) => {
962
1073
  const url = req.url || "/";
963
1074
  const path = url.split("?")[0];
@@ -973,6 +1084,9 @@ function createRouter(engine, state, vite, webRoot, wsToken, version) {
973
1084
  ok: true,
974
1085
  ready: engine.snapshot() !== null,
975
1086
  version,
1087
+ protocolVersion,
1088
+ capabilities,
1089
+ ownerKind,
976
1090
  // Discovery requires this proof; public health checks retain their useful 200 response.
977
1091
  owner: tokenMatches(header2(req, "x-tokmon-token"), wsToken)
978
1092
  });
@@ -1004,6 +1118,9 @@ async function startWebServer(opts) {
1004
1118
  const state = { config: opts.config };
1005
1119
  const tz = tzFor(state.config);
1006
1120
  const version = appVersion();
1121
+ const protocolVersion = opts.protocolVersion ?? TOKMON_PROTOCOL_VERSION;
1122
+ const capabilities = opts.capabilities ?? TOKMON_CAPABILITIES;
1123
+ const ownerKind = opts.ownerKind ?? "cli";
1007
1124
  const summaryIntervalMs = summaryIntervalFor(state.config);
1008
1125
  const billingIntervalMs = billingIntervalFor(state.config);
1009
1126
  const wsToken = opts.wsToken ?? randomBytes(32).toString("base64url");
@@ -1020,7 +1137,17 @@ async function startWebServer(opts) {
1020
1137
  const webRoot = vite ? null : findWebRoot();
1021
1138
  if (!vite && !webRoot) log(" \u26A0 no dashboard available \u2014 see the page for build/dev instructions");
1022
1139
  engine = createDataEngine({ version, config: state.config, tz, summaryIntervalMs, billingIntervalMs, resolved });
1023
- server.addListener("request", createRouter(engine, state, vite, webRoot, wsToken, version));
1140
+ server.addListener("request", createRouter(
1141
+ engine,
1142
+ state,
1143
+ vite,
1144
+ webRoot,
1145
+ wsToken,
1146
+ version,
1147
+ protocolVersion,
1148
+ capabilities,
1149
+ ownerKind
1150
+ ));
1024
1151
  closeWsRpc = await mountWsRpc(server, { engine, state });
1025
1152
  const bindHost = state.config.allowNetworkAccess ? NETWORK_HOST : LOOPBACK_HOST;
1026
1153
  const port = await listenWithFallback(server, opts.port ?? DEFAULT_PORT, bindHost);
@@ -1116,5 +1243,6 @@ function listenWithFallback(server, startPort, host) {
1116
1243
  }
1117
1244
 
1118
1245
  export {
1246
+ appVersion,
1119
1247
  startWebServer
1120
1248
  };
@@ -23,7 +23,7 @@ function isLoopbackUrl(value) {
23
23
  }
24
24
  function validLock(value) {
25
25
  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.startedAt === "number" && typeof lock.ownerId === "string" && lock.ownerId.length >= 32 && (lock.state === "starting" || lock.state === "ready");
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");
27
27
  }
28
28
  function readLock(opts = {}) {
29
29
  try {
@@ -161,7 +161,10 @@ function isAlive(pid) {
161
161
  return err.code === "EPERM";
162
162
  }
163
163
  }
164
- async function probeHealth(url, token, version, timeoutMs = 500) {
164
+ function sameCapabilities(actual, expected) {
165
+ return Array.isArray(actual) && actual.length === expected.length && actual.every((capability, index) => capability === expected[index]);
166
+ }
167
+ async function probeHealth(url, token, expected = {}, timeoutMs = 500) {
165
168
  if (!isLoopbackUrl(url) || !token) return false;
166
169
  try {
167
170
  const res = await fetch(`${url.replace(/\/+$/, "")}/healthz`, {
@@ -170,14 +173,14 @@ async function probeHealth(url, token, version, timeoutMs = 500) {
170
173
  });
171
174
  if (!res.ok) return false;
172
175
  const body = await res.json();
173
- return body.ok === true && body.owner === true && (version === void 0 || body.version === version);
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);
174
177
  } catch {
175
178
  return false;
176
179
  }
177
180
  }
178
- async function verifyLock(lock, version, timeoutMs) {
179
- if (!lock || lock.state !== "ready" || lock.version !== version || !isAlive(lock.pid)) return null;
180
- return await probeHealth(lock.url, lock.wsToken, version, timeoutMs) ? lock : null;
181
+ async function verifyLock(lock, protocolVersion, timeoutMs) {
182
+ if (!lock || lock.state !== "ready" || lock.protocolVersion !== protocolVersion || !isAlive(lock.pid)) return null;
183
+ return await probeHealth(lock.url, lock.wsToken, lock, timeoutMs) ? lock : null;
181
184
  }
182
185
 
183
186
  export {