tokmon 0.23.0 → 0.23.1

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.
@@ -188,5 +188,6 @@ export {
188
188
  reclaimDeadLock,
189
189
  reclaimAbandonedLock,
190
190
  isAlive,
191
+ probeHealth,
191
192
  verifyLock
192
193
  };
package/dist/cli.js CHANGED
@@ -41,14 +41,14 @@ function validateServeArgs(serveArgs) {
41
41
  }
42
42
  async function main() {
43
43
  if (subcommand === "__daemon") {
44
- const { runDaemon } = await import("./daemon-AW3YHFSJ.js");
44
+ const { runDaemon } = await import("./daemon-QGCI46US.js");
45
45
  await runDaemon(args.slice(1), { foreground: false });
46
46
  process.exitCode ??= 0;
47
47
  return;
48
48
  }
49
49
  if (subcommand === "serve" || subcommand === "web") {
50
50
  validateServeArgs(args.slice(1));
51
- const { runDaemon } = await import("./daemon-AW3YHFSJ.js");
51
+ const { runDaemon } = await import("./daemon-QGCI46US.js");
52
52
  await runDaemon(args.slice(1), { foreground: true });
53
53
  process.exitCode ??= 0;
54
54
  return;
@@ -92,7 +92,7 @@ async function main() {
92
92
  }
93
93
  const { loadConfig } = await import("./config-5CNJN3P7.js");
94
94
  const { resolveGlyphs, setGlyphs } = await import("./glyphs-NKCSZLGO.js");
95
- const { attachOrSpawn } = await import("./daemon-handle-EYAXNNKZ.js");
95
+ const { attachOrSpawn } = await import("./daemon-handle-4F4TXGXJ.js");
96
96
  const config = await loadConfig();
97
97
  setGlyphs(resolveGlyphs({
98
98
  flag: asciiFlag,
@@ -11,7 +11,7 @@ import {
11
11
  unlinkLock,
12
12
  verifyLock,
13
13
  writeLock
14
- } from "./chunk-HV6AYQDB.js";
14
+ } from "./chunk-RM3ZE44Z.js";
15
15
  import {
16
16
  appVersion,
17
17
  browserUrl,
@@ -1,8 +1,11 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
+ isAlive,
4
+ probeHealth,
3
5
  readLock,
6
+ reclaimDeadLock,
4
7
  verifyLock
5
- } from "./chunk-HV6AYQDB.js";
8
+ } from "./chunk-RM3ZE44Z.js";
6
9
  import {
7
10
  appVersion,
8
11
  browserUrl
@@ -13,6 +16,7 @@ import "./chunk-DJPUYMZM.js";
13
16
  import { spawn } from "child_process";
14
17
  import { extname } from "path";
15
18
  var HANDSHAKE_TIMEOUT_MS = process.platform === "win32" ? 15e3 : 1e4;
19
+ var UPGRADE_SHUTDOWN_TIMEOUT_MS = 5e3;
16
20
  function runtimeExecArgv(entry, override) {
17
21
  if (override) return override;
18
22
  const ext = extname(entry).toLowerCase();
@@ -44,19 +48,52 @@ function connected(url, wsToken) {
44
48
  return { kind: "spawned", baseUrl: browserUrl(url, wsToken), wsToken, stop: () => {
45
49
  } };
46
50
  }
47
- async function attach(opts) {
48
- const lock = await verifyLock(readLock(opts), appVersion());
51
+ async function attach(opts, version) {
52
+ const lock = await verifyLock(readLock(opts), version);
49
53
  return lock ? connected(lock.url, lock.wsToken) : null;
50
54
  }
55
+ var delay = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
56
+ async function retireIncompatibleDaemon(opts, version, timeoutMs) {
57
+ const lock = readLock(opts);
58
+ if (!lock || lock.state !== "ready" || lock.version === version || lock.pid === process.pid || !isAlive(lock.pid)) return;
59
+ const authenticated = await probeHealth(
60
+ lock.url,
61
+ lock.wsToken,
62
+ lock.version,
63
+ Math.min(1e3, timeoutMs)
64
+ );
65
+ if (!authenticated) return;
66
+ try {
67
+ process.kill(lock.pid, "SIGTERM");
68
+ } catch {
69
+ if (!isAlive(lock.pid)) reclaimDeadLock(opts);
70
+ return;
71
+ }
72
+ const deadline = Date.now() + Math.min(timeoutMs, UPGRADE_SHUTDOWN_TIMEOUT_MS);
73
+ while (Date.now() < deadline) {
74
+ const current = readLock(opts);
75
+ if (!current || current.ownerId !== lock.ownerId) return;
76
+ if (!isAlive(lock.pid)) {
77
+ reclaimDeadLock(opts);
78
+ return;
79
+ }
80
+ await delay(50);
81
+ }
82
+ }
51
83
  async function attachOrSpawn(opts = {}) {
52
- const existing = await attach(opts);
84
+ const version = appVersion();
85
+ const timeoutMs = opts.timeoutMs ?? HANDSHAKE_TIMEOUT_MS;
86
+ const existing = await attach(opts, version);
53
87
  if (existing) return existing;
88
+ await retireIncompatibleDaemon(opts, version, timeoutMs);
89
+ const upgraded = await attach(opts, version);
90
+ if (upgraded) return upgraded;
54
91
  const entry = opts.entry ?? process.argv[1];
55
92
  if (!entry) return degraded();
56
93
  const execPath = opts.execPath ?? process.execPath;
57
- const timeoutMs = opts.timeoutMs ?? HANDSHAKE_TIMEOUT_MS;
58
94
  const args = ["__daemon", "--port", "0", "--no-open"];
59
- const env = opts.cachePath ? { ...process.env, TOKMON_DAEMON_CACHE_DIR: opts.cachePath } : process.env;
95
+ const baseEnv = opts.env ?? process.env;
96
+ const env = opts.cachePath ? { ...baseEnv, TOKMON_DAEMON_CACHE_DIR: opts.cachePath } : baseEnv;
60
97
  return new Promise((resolve) => {
61
98
  let child;
62
99
  try {
@@ -82,7 +119,7 @@ async function attachOrSpawn(opts = {}) {
82
119
  resolve(handle);
83
120
  };
84
121
  const tryAttach = (final = false) => {
85
- void attach(opts).then((found) => {
122
+ void attach(opts, version).then((found) => {
86
123
  if (found) {
87
124
  finish(found);
88
125
  return;
@@ -108,7 +145,7 @@ async function attachOrSpawn(opts = {}) {
108
145
  const line = stdout.slice(0, newline).trim();
109
146
  stdout = stdout.slice(newline + 1);
110
147
  const handshake = parseHandshake(line);
111
- if (!handshake || handshake.version !== appVersion()) continue;
148
+ if (!handshake || handshake.version !== version) continue;
112
149
  tryAttach();
113
150
  return;
114
151
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tokmon",
3
- "version": "0.23.0",
3
+ "version": "0.23.1",
4
4
  "description": "Terminal + web dashboard for Claude Code, Codex, Cursor, Grok, opencode, pi, Copilot, Gemini & more — usage, limits, and costs",
5
5
  "type": "module",
6
6
  "bin": {