codetime-cli 0.6.0 → 0.7.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.
Files changed (2) hide show
  1. package/bin/codetime.mjs +212 -46
  2. package/package.json +1 -1
package/bin/codetime.mjs CHANGED
@@ -19,10 +19,13 @@ __export(fs_exports, {
19
19
  parseJsonFile: () => parseJsonFile,
20
20
  pathExists: () => pathExists,
21
21
  readJsonIfExists: () => readJsonIfExists,
22
+ readJsonIfExistsTolerant: () => readJsonIfExistsTolerant,
22
23
  readTextIfExists: () => readTextIfExists,
24
+ writeFileAtomic: () => writeFileAtomic,
23
25
  writeGeneratedFile: () => writeGeneratedFile
24
26
  });
25
- import { mkdir, readdir, readFile, stat, writeFile } from "node:fs/promises";
27
+ import { randomBytes } from "node:crypto";
28
+ import { mkdir, open, readdir, readFile, rename, rm, stat, writeFile } from "node:fs/promises";
26
29
  import path from "node:path";
27
30
  async function pathExists(filePath) {
28
31
  try {
@@ -56,6 +59,59 @@ function parseJsonFile(filePath, text) {
56
59
  throw new Error(`Could not parse JSON file ${filePath}: ${error.message}`);
57
60
  }
58
61
  }
62
+ async function readJsonIfExistsTolerant(filePath, onCorrupt) {
63
+ const text = await readTextIfExists(filePath);
64
+ if (text === null) {
65
+ return null;
66
+ }
67
+ try {
68
+ return JSON.parse(text);
69
+ } catch (error) {
70
+ onCorrupt?.(error);
71
+ return null;
72
+ }
73
+ }
74
+ async function writeFileAtomic(filePath, data, options = {}) {
75
+ const dir = path.dirname(filePath);
76
+ await mkdir(dir, { recursive: true });
77
+ const tmpPath = path.join(
78
+ dir,
79
+ `.${path.basename(filePath)}.${process.pid}.${Date.now()}.${randomBytes(6).toString("hex")}.tmp`
80
+ );
81
+ let handle;
82
+ try {
83
+ handle = await open(tmpPath, "wx", options.mode ?? 438);
84
+ await handle.writeFile(data, "utf8");
85
+ if (options.mode !== void 0) {
86
+ await handle.chmod(options.mode);
87
+ }
88
+ await handle.close();
89
+ handle = void 0;
90
+ await renameWithRetry(tmpPath, filePath);
91
+ } catch (error) {
92
+ if (handle) {
93
+ await handle.close().catch(() => {
94
+ });
95
+ }
96
+ await rm(tmpPath, { force: true }).catch(() => {
97
+ });
98
+ throw error;
99
+ }
100
+ }
101
+ async function renameWithRetry(from, to, attempts = 5) {
102
+ for (let i = 0; ; i++) {
103
+ try {
104
+ await rename(from, to);
105
+ return;
106
+ } catch (error) {
107
+ const code = error.code;
108
+ if (i >= attempts - 1 || code !== "EPERM" && code !== "EACCES" && code !== "EBUSY") {
109
+ throw error;
110
+ }
111
+ await new Promise((resolve) => setTimeout(resolve, 10 * (i + 1)));
112
+ }
113
+ }
114
+ }
59
115
  async function writeGeneratedFile(filePath, content, { dryRun, force, onWrite }) {
60
116
  if (dryRun) {
61
117
  onWrite(`Would write ${filePath}`);
@@ -152,7 +208,7 @@ var init_fs = __esm({
152
208
 
153
209
  // src/cli.ts
154
210
  import { spawn } from "node:child_process";
155
- import { mkdir as mkdir3, rm, stat as stat6, writeFile as writeFile2 } from "node:fs/promises";
211
+ import { rm as rm2, stat as stat6 } from "node:fs/promises";
156
212
  import os3 from "node:os";
157
213
  import path13 from "node:path";
158
214
  import { fileURLToPath } from "node:url";
@@ -160,7 +216,7 @@ import { fileURLToPath } from "node:url";
160
216
  // ../shared/src/index.ts
161
217
  import { createHash } from "node:crypto";
162
218
  var AGENT_TIME_SCHEMA_VERSION = "2026-04-29";
163
- var AGENT_ROLLUP_SCHEMA_VERSION = 2;
219
+ var AGENT_ROLLUP_SCHEMA_VERSION = 3;
164
220
  var TELEMETRY_EVENT_TYPES = [
165
221
  "session.started",
166
222
  "session.ended",
@@ -974,7 +1030,7 @@ import { readFile as readFile2, stat as stat2 } from "node:fs/promises";
974
1030
  import path2 from "node:path";
975
1031
 
976
1032
  // src/lib/constants.ts
977
- var PACKAGE_VERSION = true ? "0.6.0" : "0.1.1";
1033
+ var PACKAGE_VERSION = true ? "0.7.1" : "0.1.1";
978
1034
  var DEFAULT_API_URL = "https://codetime.dev";
979
1035
  var DEFAULT_BACKFILL_BATCH_SIZE = 50;
980
1036
  var DEFAULT_BACKFILL_BATCH_BYTES = 800 * 1024;
@@ -1932,16 +1988,29 @@ function claudeUsageFromMessage(message) {
1932
1988
  const outputTokens = numberField(usage, "output_tokens") || 0;
1933
1989
  const cachedInputTokens = cacheCreationInputTokens + cacheReadInputTokens;
1934
1990
  const totalInputTokens = inputTokens + cachedInputTokens;
1991
+ const cacheCreationSplit = claudeCacheCreationSplit(usage);
1935
1992
  return {
1936
1993
  tokensInput: totalInputTokens || void 0,
1937
1994
  tokensCachedInput: cachedInputTokens || void 0,
1938
1995
  tokensCacheCreationInput: cacheCreationInputTokens || void 0,
1996
+ tokensCacheCreation5mInput: cacheCreationSplit?.fiveMinute,
1997
+ tokensCacheCreation1hInput: cacheCreationSplit?.oneHour,
1939
1998
  tokensCacheReadInput: cacheReadInputTokens || void 0,
1940
1999
  tokensOutput: outputTokens || void 0,
1941
2000
  tokensTotal: totalInputTokens + outputTokens || void 0,
1942
2001
  modelCalls: 1
1943
2002
  };
1944
2003
  }
2004
+ function claudeCacheCreationSplit(usage) {
2005
+ const breakdown = usage.cache_creation;
2006
+ if (!isPlainObject(breakdown)) {
2007
+ return void 0;
2008
+ }
2009
+ return {
2010
+ fiveMinute: numberField(breakdown, "ephemeral_5m_input_tokens") || 0,
2011
+ oneHour: numberField(breakdown, "ephemeral_1h_input_tokens") || 0
2012
+ };
2013
+ }
1945
2014
  function claudeSubagentMetrics(toolResult, fallbackDurationMs) {
1946
2015
  const usage = objectField(toolResult, "usage");
1947
2016
  const inputTokens = numberField(usage, "input_tokens") || 0;
@@ -1950,6 +2019,7 @@ function claudeSubagentMetrics(toolResult, fallbackDurationMs) {
1950
2019
  const outputTokens = numberField(usage, "output_tokens") || 0;
1951
2020
  const cachedInputTokens = cacheCreationInputTokens + cacheReadInputTokens;
1952
2021
  const totalInputTokens = inputTokens + cachedInputTokens;
2022
+ const cacheCreationSplit = claudeCacheCreationSplit(usage);
1953
2023
  const durationMs = numberField(toolResult, "totalDurationMs") || fallbackDurationMs;
1954
2024
  return {
1955
2025
  durationMs,
@@ -1958,6 +2028,8 @@ function claudeSubagentMetrics(toolResult, fallbackDurationMs) {
1958
2028
  tokensInput: totalInputTokens || void 0,
1959
2029
  tokensCachedInput: cachedInputTokens || void 0,
1960
2030
  tokensCacheCreationInput: cacheCreationInputTokens || void 0,
2031
+ tokensCacheCreation5mInput: cacheCreationSplit?.fiveMinute,
2032
+ tokensCacheCreation1hInput: cacheCreationSplit?.oneHour,
1961
2033
  tokensCacheReadInput: cacheReadInputTokens || void 0,
1962
2034
  tokensOutput: outputTokens || void 0,
1963
2035
  tokensTotal: numberField(toolResult, "totalTokens") || totalInputTokens + outputTokens || void 0
@@ -4137,6 +4209,7 @@ function buildSessionRollup(rollupKey, events) {
4137
4209
  const lastEventAt = ordered.at(-1)?.ts || startedAt;
4138
4210
  const timeBuckets = /* @__PURE__ */ new Map();
4139
4211
  const modelRollups = /* @__PURE__ */ new Map();
4212
+ const modelBuckets = /* @__PURE__ */ new Map();
4140
4213
  const toolRollups = /* @__PURE__ */ new Map();
4141
4214
  const fileRollups = /* @__PURE__ */ new Map();
4142
4215
  const turnRollups = /* @__PURE__ */ new Map();
@@ -4158,6 +4231,8 @@ function buildSessionRollup(rollupKey, events) {
4158
4231
  const eventInputTokens = Math.max(0, event.metrics?.tokensInput || 0);
4159
4232
  const eventCachedInputTokens = Math.max(0, event.metrics?.tokensCachedInput || 0);
4160
4233
  const eventCacheCreationInputTokens = Math.max(0, event.metrics?.tokensCacheCreationInput || 0);
4234
+ const eventCacheCreation5mInputTokens = Math.max(0, event.metrics?.tokensCacheCreation5mInput || 0);
4235
+ const eventCacheCreation1hInputTokens = Math.max(0, event.metrics?.tokensCacheCreation1hInput || 0);
4161
4236
  const eventCacheReadInputTokens = Math.max(0, event.metrics?.tokensCacheReadInput || 0);
4162
4237
  const eventOutputTokens = Math.max(0, event.metrics?.tokensOutput || 0);
4163
4238
  const eventReasoningOutputTokens = Math.max(0, event.metrics?.tokensReasoningOutput || 0);
@@ -4278,6 +4353,8 @@ function buildSessionRollup(rollupKey, events) {
4278
4353
  inputTokens: 0,
4279
4354
  cachedInputTokens: 0,
4280
4355
  cacheCreationInputTokens: 0,
4356
+ cacheCreation5mInputTokens: 0,
4357
+ cacheCreation1hInputTokens: 0,
4281
4358
  cacheReadInputTokens: 0,
4282
4359
  outputTokens: 0,
4283
4360
  reasoningOutputTokens: 0,
@@ -4288,12 +4365,40 @@ function buildSessionRollup(rollupKey, events) {
4288
4365
  modelRollup.inputTokens += eventInputTokens;
4289
4366
  modelRollup.cachedInputTokens += eventCachedInputTokens;
4290
4367
  modelRollup.cacheCreationInputTokens += eventCacheCreationInputTokens;
4368
+ modelRollup.cacheCreation5mInputTokens += eventCacheCreation5mInputTokens;
4369
+ modelRollup.cacheCreation1hInputTokens += eventCacheCreation1hInputTokens;
4291
4370
  modelRollup.cacheReadInputTokens += eventCacheReadInputTokens;
4292
4371
  modelRollup.outputTokens += eventOutputTokens;
4293
4372
  modelRollup.reasoningOutputTokens += eventReasoningOutputTokens;
4294
4373
  modelRollup.totalTokens += eventTotalTokens;
4295
4374
  modelRollup.estimatedCostUsd += eventCostUsd;
4296
4375
  modelRollups.set(modelKey, modelRollup);
4376
+ const modelBucketKey = `${bucketTs}\0${modelKey}`;
4377
+ const modelBucket = modelBuckets.get(modelBucketKey) || {
4378
+ ts: bucketTs,
4379
+ model: modelKey,
4380
+ callCount: 0,
4381
+ inputTokens: 0,
4382
+ cachedInputTokens: 0,
4383
+ cacheCreationInputTokens: 0,
4384
+ cacheCreation5mInputTokens: 0,
4385
+ cacheCreation1hInputTokens: 0,
4386
+ cacheReadInputTokens: 0,
4387
+ outputTokens: 0,
4388
+ reasoningOutputTokens: 0,
4389
+ totalTokens: 0
4390
+ };
4391
+ modelBucket.callCount += 1;
4392
+ modelBucket.inputTokens += eventInputTokens;
4393
+ modelBucket.cachedInputTokens += eventCachedInputTokens;
4394
+ modelBucket.cacheCreationInputTokens += eventCacheCreationInputTokens;
4395
+ modelBucket.cacheCreation5mInputTokens += eventCacheCreation5mInputTokens;
4396
+ modelBucket.cacheCreation1hInputTokens += eventCacheCreation1hInputTokens;
4397
+ modelBucket.cacheReadInputTokens += eventCacheReadInputTokens;
4398
+ modelBucket.outputTokens += eventOutputTokens;
4399
+ modelBucket.reasoningOutputTokens += eventReasoningOutputTokens;
4400
+ modelBucket.totalTokens += eventTotalTokens;
4401
+ modelBuckets.set(modelBucketKey, modelBucket);
4297
4402
  }
4298
4403
  if (event.type === "tool.started") {
4299
4404
  bucket.toolCalls += 1;
@@ -4361,11 +4466,12 @@ function buildSessionRollup(rollupKey, events) {
4361
4466
  const baseRollup = {
4362
4467
  rollupKey,
4363
4468
  payloadHash: "",
4364
- // v2 schema: trustworthy gap-clamped turn durations + billable-output token
4365
- // convention. Set on baseRollup (not after) so it participates in payloadHash:
4366
- // every historical rollup's hash changes, and a re-backfill (uploaded with
4367
- // replace=true by default) cleanly refreshes all data onto the new convention.
4368
- // This full-refresh churn is intentional.
4469
+ // v3 schema: v2 (gap-clamped turn durations + billable-output token
4470
+ // convention) plus per-model cache-creation TTL split and modelBuckets. Set on
4471
+ // baseRollup (not after) so it participates in payloadHash: every historical
4472
+ // rollup's hash changes, and a re-backfill (uploaded with replace=true by
4473
+ // default) cleanly refreshes all data onto the new convention. This
4474
+ // full-refresh churn is intentional.
4369
4475
  schemaVersion: AGENT_ROLLUP_SCHEMA_VERSION,
4370
4476
  source: first.source,
4371
4477
  project,
@@ -4390,6 +4496,8 @@ function buildSessionRollup(rollupKey, events) {
4390
4496
  durationMs: Math.max(0, Date.parse(lastEventAt) - Date.parse(startedAt)),
4391
4497
  timeBuckets: [...timeBuckets.values()].sort((a, b) => a.ts.localeCompare(b.ts)),
4392
4498
  modelRollups: [...modelRollups.values()].sort((a, b) => b.callCount - a.callCount || a.model.localeCompare(b.model)),
4499
+ // Sorted ts ascending, then model lexicographically (wire contract).
4500
+ modelBuckets: [...modelBuckets.values()].sort((a, b) => a.ts.localeCompare(b.ts) || a.model.localeCompare(b.model)),
4393
4501
  toolRollups: [...toolRollups.values()].sort((a, b) => b.callCount - a.callCount || a.tool.localeCompare(b.tool)),
4394
4502
  fileRollups: [...fileRollups.values()].sort((a, b) => b.writes - a.writes || b.reads - a.reads || a.displayPath.localeCompare(b.displayPath)),
4395
4503
  turnRollups: [...turnRollups.values()].map((rollup) => ({
@@ -4464,7 +4572,7 @@ async function installEntry(entry, options) {
4464
4572
  });
4465
4573
  }
4466
4574
  async function mergeHooksJson(filePath, content, { dryRun, force, onWrite }) {
4467
- const { mkdir: mkdir4, writeFile: writeFile3 } = await import("node:fs/promises");
4575
+ const { mkdir: mkdir3, writeFile: writeFile2 } = await import("node:fs/promises");
4468
4576
  const pathMod = await import("node:path");
4469
4577
  if (dryRun) {
4470
4578
  onWrite(`Would merge ${filePath}`);
@@ -4485,8 +4593,8 @@ async function mergeHooksJson(filePath, content, { dryRun, force, onWrite }) {
4485
4593
  onWrite(`Already installed ${filePath}`);
4486
4594
  return;
4487
4595
  }
4488
- await mkdir4(pathMod.dirname(filePath), { recursive: true });
4489
- await writeFile3(filePath, nextText, "utf8");
4596
+ await mkdir3(pathMod.dirname(filePath), { recursive: true });
4597
+ await writeFile2(filePath, nextText, "utf8");
4490
4598
  onWrite(`Installed ${filePath}`);
4491
4599
  }
4492
4600
  function mergeHookObjects(existing, addition) {
@@ -4524,7 +4632,7 @@ function hookCommandFromGroup(group) {
4524
4632
 
4525
4633
  // src/lib/config.ts
4526
4634
  import { randomUUID } from "node:crypto";
4527
- import { existsSync, mkdirSync, readFileSync, rmSync, writeFileSync } from "node:fs";
4635
+ import { chmodSync, closeSync, existsSync, mkdirSync, openSync, readFileSync, renameSync, rmSync, unlinkSync, writeSync } from "node:fs";
4528
4636
  import { homedir, hostname } from "node:os";
4529
4637
  import path11 from "node:path";
4530
4638
  function configDir(home = homedir()) {
@@ -4548,30 +4656,87 @@ function readConfig(home = homedir()) {
4548
4656
  return {};
4549
4657
  }
4550
4658
  }
4551
- function writeConfig(config, home = homedir()) {
4552
- const dir = configDir(home);
4553
- if (!existsSync(dir)) {
4554
- mkdirSync(dir, { recursive: true });
4659
+ var atomicTmpCounter = 0;
4660
+ function writeFileAtomicSync(filePath, data, mode) {
4661
+ const dir = path11.dirname(filePath);
4662
+ mkdirSync(dir, { recursive: true });
4663
+ const tmp = path11.join(dir, `.${path11.basename(filePath)}.${process.pid}.${Date.now()}.${(atomicTmpCounter++).toString(36)}.tmp`);
4664
+ let fd;
4665
+ try {
4666
+ fd = openSync(tmp, "wx", mode);
4667
+ writeSync(fd, data);
4668
+ closeSync(fd);
4669
+ fd = void 0;
4670
+ chmodSync(tmp, mode);
4671
+ renameSyncWithRetry(tmp, filePath);
4672
+ } catch (error) {
4673
+ if (fd !== void 0) {
4674
+ try {
4675
+ closeSync(fd);
4676
+ } catch {
4677
+ }
4678
+ }
4679
+ try {
4680
+ unlinkSync(tmp);
4681
+ } catch {
4682
+ }
4683
+ throw error;
4555
4684
  }
4556
- writeFileSync(configPath(home), `${JSON.stringify(config, null, 2)}
4557
- `, { mode: 384 });
4685
+ }
4686
+ function renameSyncWithRetry(from, to, attempts = 5) {
4687
+ for (let i = 0; ; i++) {
4688
+ try {
4689
+ renameSync(from, to);
4690
+ return;
4691
+ } catch (error) {
4692
+ const code = error.code;
4693
+ if (i >= attempts - 1 || code !== "EPERM" && code !== "EACCES" && code !== "EBUSY") {
4694
+ throw error;
4695
+ }
4696
+ Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, 10 * (i + 1));
4697
+ }
4698
+ }
4699
+ }
4700
+ function writeConfig(config, home = homedir()) {
4701
+ writeFileAtomicSync(configPath(home), `${JSON.stringify(config, null, 2)}
4702
+ `, 384);
4558
4703
  }
4559
4704
  function ensureLocalMachineId(home = homedir()) {
4560
4705
  const file = machineIdPath(home);
4561
- if (existsSync(file)) {
4562
- const value = readFileSync(file, "utf8").trim();
4563
- if (value.length > 0) {
4564
- return value;
4565
- }
4706
+ const existing = readMachineId(file);
4707
+ if (existing) {
4708
+ return existing;
4566
4709
  }
4710
+ mkdirSync(configDir(home), { recursive: true });
4567
4711
  const id = randomUUID();
4568
- const dir = configDir(home);
4569
- if (!existsSync(dir)) {
4570
- mkdirSync(dir, { recursive: true });
4712
+ try {
4713
+ const fd = openSync(file, "wx", 384);
4714
+ try {
4715
+ writeSync(fd, `${id}
4716
+ `);
4717
+ } finally {
4718
+ closeSync(fd);
4719
+ }
4720
+ return id;
4721
+ } catch (error) {
4722
+ if (error.code !== "EEXIST") {
4723
+ throw error;
4724
+ }
4725
+ const peer = readMachineId(file);
4726
+ if (peer) {
4727
+ return peer;
4728
+ }
4729
+ writeFileAtomicSync(file, `${id}
4730
+ `, 384);
4731
+ return id;
4571
4732
  }
4572
- writeFileSync(file, `${id}
4573
- `, { mode: 384 });
4574
- return id;
4733
+ }
4734
+ function readMachineId(file) {
4735
+ if (!existsSync(file)) {
4736
+ return null;
4737
+ }
4738
+ const value = readFileSync(file, "utf8").trim();
4739
+ return value.length > 0 ? value : null;
4575
4740
  }
4576
4741
  function defaultMachineName() {
4577
4742
  return hostname();
@@ -4581,7 +4746,7 @@ function defaultMachineName() {
4581
4746
  init_fs();
4582
4747
 
4583
4748
  // src/lib/logger.ts
4584
- import { appendFile, mkdir as mkdir2, rename, stat as stat5 } from "node:fs/promises";
4749
+ import { appendFile, mkdir as mkdir2, rename as rename2, stat as stat5 } from "node:fs/promises";
4585
4750
  import { homedir as homedir2 } from "node:os";
4586
4751
  import path12 from "node:path";
4587
4752
  var MAX_BYTES = 1 * 1024 * 1024;
@@ -4601,7 +4766,7 @@ async function rotateIfNeeded(file) {
4601
4766
  try {
4602
4767
  const info = await stat5(file);
4603
4768
  if (info.size > MAX_BYTES) {
4604
- await rename(file, `${file}.1`).catch(() => {
4769
+ await rename2(file, `${file}.1`).catch(() => {
4605
4770
  });
4606
4771
  }
4607
4772
  } catch {
@@ -5435,7 +5600,7 @@ async function importBackfillPlan(plan, options, ctx, registry) {
5435
5600
  }
5436
5601
  async function purgeForcedSources(sourceDefs, home, options, ctx) {
5437
5602
  try {
5438
- await rm(backfillIncrementalStatePath(home), { force: true });
5603
+ await rm2(backfillIncrementalStatePath(home), { force: true });
5439
5604
  } catch (error) {
5440
5605
  debug(ctx, `Failed to clear backfill watermark: ${error.message}
5441
5606
  `);
@@ -5633,7 +5798,11 @@ function syncLocalTriggerLockPath(home) {
5633
5798
  }
5634
5799
  async function readBackfillIncrementalState(home, ctx) {
5635
5800
  const statePath = backfillIncrementalStatePath(home);
5636
- const state = await readJsonIfExists(statePath);
5801
+ const state = await readJsonIfExistsTolerant(
5802
+ statePath,
5803
+ ctx ? (error) => debug(ctx, `backfill-state unreadable at ${statePath}; dropping watermarks and re-importing (${error.message})
5804
+ `) : void 0
5805
+ );
5637
5806
  if (state === null) {
5638
5807
  return { version: BACKFILL_STATE_SCHEMA_VERSION, sources: {} };
5639
5808
  }
@@ -5669,12 +5838,11 @@ async function updateBackfillIncrementalState(home, state, selectedFilesBySource
5669
5838
  state.sources[source] = { watermarkTs: latest };
5670
5839
  }
5671
5840
  const statePath = backfillIncrementalStatePath(home);
5672
- await mkdir3(path13.dirname(statePath), { recursive: true });
5673
- await writeFile2(statePath, `${JSON.stringify(state, null, 2)}
5674
- `, "utf8");
5841
+ await writeFileAtomic(statePath, `${JSON.stringify(state, null, 2)}
5842
+ `);
5675
5843
  }
5676
5844
  async function readSyncLocalTriggerState(statePath) {
5677
- const state = await readJsonIfExists(statePath);
5845
+ const state = await readJsonIfExistsTolerant(statePath);
5678
5846
  if (!isPlainObject(state)) {
5679
5847
  return { version: 1 };
5680
5848
  }
@@ -5697,12 +5865,11 @@ async function readSyncLocalTriggerState(statePath) {
5697
5865
  return nextState;
5698
5866
  }
5699
5867
  async function writeSyncLocalTriggerState(statePath, state) {
5700
- await mkdir3(path13.dirname(statePath), { recursive: true });
5701
- await writeFile2(statePath, `${JSON.stringify(state, null, 2)}
5702
- `, "utf8");
5868
+ await writeFileAtomic(statePath, `${JSON.stringify(state, null, 2)}
5869
+ `);
5703
5870
  }
5704
5871
  async function readSyncLocalLock(lockPath) {
5705
- const lock = await readJsonIfExists(lockPath);
5872
+ const lock = await readJsonIfExistsTolerant(lockPath);
5706
5873
  if (!isPlainObject(lock)) {
5707
5874
  return void 0;
5708
5875
  }
@@ -5712,13 +5879,12 @@ async function readSyncLocalLock(lockPath) {
5712
5879
  return { pid: lock.pid, startedAt: lock.startedAt };
5713
5880
  }
5714
5881
  async function writeSyncLocalLock(lockPath, lock) {
5715
- await mkdir3(path13.dirname(lockPath), { recursive: true });
5716
- await writeFile2(lockPath, `${JSON.stringify(lock, null, 2)}
5717
- `, "utf8");
5882
+ await writeFileAtomic(lockPath, `${JSON.stringify(lock, null, 2)}
5883
+ `);
5718
5884
  }
5719
5885
  async function clearSyncLocalLock(lockPath) {
5720
5886
  try {
5721
- await rm(lockPath, { force: true });
5887
+ await rm2(lockPath, { force: true });
5722
5888
  } catch (error) {
5723
5889
  if (error.code !== "ENOENT") {
5724
5890
  throw error;
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "codetime-cli",
3
3
  "type": "module",
4
- "version": "0.6.0",
4
+ "version": "0.7.1",
5
5
  "description": "codetime CLI — install AI-agent hooks (Claude Code, Codex, OpenCode, Pi) and report activity to codetime.dev.",
6
6
  "license": "MIT",
7
7
  "publishConfig": {