tokelytics 0.3.3 → 0.3.5

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 CHANGED
@@ -9,7 +9,7 @@ prompts or responses.
9
9
 
10
10
  ```
11
11
  npx tokelytics@latest login # opens your browser to approve — no setup, no keys
12
- npx tokelytics@latest watch # local live updates; cloud backup every 20m
12
+ npx tokelytics@latest watch # one machine-wide watcher; cloud backup every 30m
13
13
  ```
14
14
 
15
15
  Keep `@latest` in install and onboarding commands. Agents older than 0.3.1
@@ -1014,13 +1014,34 @@ async function loadUpdateState() {
1014
1014
  async function saveUpdateState(state) {
1015
1015
  await writeJson(updatePath(), state);
1016
1016
  }
1017
- async function acquireWatchLock() {
1017
+ var AgentAlreadyRunningError = class extends Error {
1018
+ lock;
1019
+ constructor(lock, cause) {
1020
+ super(`Tokelytics is already running (PID ${lock.pid}).`, { cause });
1021
+ this.name = "AgentAlreadyRunningError";
1022
+ this.lock = lock;
1023
+ }
1024
+ };
1025
+ async function activeAgentLock() {
1026
+ const lock = await readJson(watchLockPath());
1027
+ if (!lock?.pid || !processIsAlive(lock.pid)) return null;
1028
+ return lock;
1029
+ }
1030
+ async function acquireWatchLock(owner = "watch") {
1018
1031
  await ensureDir();
1019
1032
  const file = watchLockPath();
1020
1033
  for (let attempt = 0; attempt < 2; attempt++) {
1021
1034
  try {
1022
1035
  const handle = await fs3.open(file, "wx");
1023
- await handle.writeFile(JSON.stringify({ pid: process.pid, startedAt: (/* @__PURE__ */ new Date()).toISOString() }), "utf-8");
1036
+ await handle.writeFile(
1037
+ JSON.stringify({
1038
+ pid: process.pid,
1039
+ startedAt: (/* @__PURE__ */ new Date()).toISOString(),
1040
+ owner,
1041
+ scope: "machine"
1042
+ }),
1043
+ "utf-8"
1044
+ );
1024
1045
  await handle.close();
1025
1046
  let released = false;
1026
1047
  return {
@@ -1036,7 +1057,7 @@ async function acquireWatchLock() {
1036
1057
  if (code !== "EEXIST") throw error;
1037
1058
  const lock = await readJson(file);
1038
1059
  if (lock?.pid && processIsAlive(lock.pid)) {
1039
- throw new Error(`Tokelytics is already running (PID ${lock.pid}).`, { cause: error });
1060
+ throw new AgentAlreadyRunningError(lock, error);
1040
1061
  }
1041
1062
  await fs3.rm(file, { force: true });
1042
1063
  }
@@ -1781,7 +1802,7 @@ var FirestoreSink = class {
1781
1802
  };
1782
1803
 
1783
1804
  // src/version.ts
1784
- var AGENT_VERSION = "0.3.3";
1805
+ var AGENT_VERSION = "0.3.5";
1785
1806
 
1786
1807
  // src/sync.ts
1787
1808
  async function runSync(connectors, sink, state, device, limitCollectors = [], options = {}) {
@@ -3781,8 +3802,8 @@ var DEBOUNCE_MS = 1200;
3781
3802
  var LIMIT_REFRESH_MS = 6e4;
3782
3803
  var FALLBACK_SCAN_MS = 5e3;
3783
3804
  var DEVICE_HEARTBEAT_MS = 60 * 6e4;
3784
- var CLOUD_SYNC_INTERVAL_MS = 20 * 6e4;
3785
- var MAX_CLOUD_WRITES_PER_DAY = 24;
3805
+ var CLOUD_SYNC_INTERVAL_MS = 30 * 6e4;
3806
+ var MAX_CLOUD_WRITES_PER_DAY = 16;
3786
3807
  var QUOTA_RETRY_MS = 15 * 6e4;
3787
3808
  var QUOTA_RETRY_JITTER_MS = 5 * 6e4;
3788
3809
  async function watch2(runner) {
@@ -3979,7 +4000,7 @@ var USAGE = `Tokelytics agent
3979
4000
  Usage:
3980
4001
  tokelytics login Sign in by approving in your browser
3981
4002
  tokelytics sync Run one incremental sync to your dashboard
3982
- tokelytics watch Watch usage and refresh the cloud snapshot
4003
+ tokelytics watch Watch Claude Code and Codex usage across this machine
3983
4004
  tokelytics status Show current sign-in and device
3984
4005
  tokelytics logout Forget stored credentials
3985
4006
  `;
@@ -4001,7 +4022,7 @@ async function main(argv) {
4001
4022
  case "sync": {
4002
4023
  const update = await agentUpdateMessage();
4003
4024
  if (update) console.warn(update);
4004
- const lock = await acquireWatchLock();
4025
+ const lock = await acquireWatchLock("sync");
4005
4026
  try {
4006
4027
  const runner = await createRunner();
4007
4028
  const { processedTurns, processedLimits, byProvider, publicationError } = await runner.syncOnce();
@@ -4014,7 +4035,17 @@ async function main(argv) {
4014
4035
  return 0;
4015
4036
  }
4016
4037
  case "watch": {
4017
- const lock = await acquireWatchLock();
4038
+ let lock;
4039
+ try {
4040
+ lock = await acquireWatchLock("watch");
4041
+ } catch (err) {
4042
+ if (err instanceof AgentAlreadyRunningError && err.lock.owner !== "sync") {
4043
+ console.log(`Tokelytics is already watching this machine (PID ${err.lock.pid}).`);
4044
+ console.log("Claude Code and Codex usage from every folder and repository is already covered.");
4045
+ return 0;
4046
+ }
4047
+ throw err;
4048
+ }
4018
4049
  try {
4019
4050
  const update = await agentUpdateMessage();
4020
4051
  if (update) console.warn(update);
@@ -4028,7 +4059,15 @@ async function main(argv) {
4028
4059
  case "status": {
4029
4060
  const creds = await loadCredentials();
4030
4061
  const state = await loadState();
4062
+ const lock = await activeAgentLock();
4031
4063
  console.log(`Agent version: ${AGENT_VERSION}`);
4064
+ if (lock && lock.owner !== "sync") {
4065
+ console.log(`Watcher: active machine-wide (PID ${lock.pid}); all folders and repositories are covered.`);
4066
+ } else if (lock?.owner === "sync") {
4067
+ console.log(`Watcher: sync currently in progress (PID ${lock.pid}).`);
4068
+ } else {
4069
+ console.log('Watcher: not running. Run "tokelytics watch" once on this machine.');
4070
+ }
4032
4071
  if (!creds) {
4033
4072
  console.log('Not signed in. Run "tokelytics login".');
4034
4073
  } else {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tokelytics",
3
- "version": "0.3.3",
3
+ "version": "0.3.5",
4
4
  "description": "Tokelytics sync agent — streams local AI CLI usage logs (Claude Code, Codex) to your Tokelytics dashboard.",
5
5
  "license": "MIT",
6
6
  "type": "module",