claudectx 1.1.1 → 1.1.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/dist/index.js CHANGED
@@ -124,15 +124,15 @@ __export(session_store_exports, {
124
124
  readAllEvents: () => readAllEvents
125
125
  });
126
126
  function getStoreDirPath() {
127
- return path8.join(os2.homedir(), ".claudectx");
127
+ return path9.join(os3.homedir(), ".claudectx");
128
128
  }
129
129
  function getReadsFilePath_() {
130
- return path8.join(getStoreDirPath(), "reads.jsonl");
130
+ return path9.join(getStoreDirPath(), "reads.jsonl");
131
131
  }
132
132
  function ensureStoreDir() {
133
133
  const dir = getStoreDirPath();
134
- if (!fs8.existsSync(dir)) {
135
- fs8.mkdirSync(dir, { recursive: true });
134
+ if (!fs9.existsSync(dir)) {
135
+ fs9.mkdirSync(dir, { recursive: true });
136
136
  }
137
137
  }
138
138
  function appendFileRead(filePath, sessionId) {
@@ -142,12 +142,12 @@ function appendFileRead(filePath, sessionId) {
142
142
  filePath,
143
143
  sessionId
144
144
  };
145
- fs8.appendFileSync(getReadsFilePath_(), JSON.stringify(event) + "\n", "utf-8");
145
+ fs9.appendFileSync(getReadsFilePath_(), JSON.stringify(event) + "\n", "utf-8");
146
146
  }
147
147
  function readAllEvents() {
148
148
  const readsFile = getReadsFilePath_();
149
- if (!fs8.existsSync(readsFile)) return [];
150
- const lines = fs8.readFileSync(readsFile, "utf-8").trim().split("\n").filter(Boolean);
149
+ if (!fs9.existsSync(readsFile)) return [];
150
+ const lines = fs9.readFileSync(readsFile, "utf-8").trim().split("\n").filter(Boolean);
151
151
  return lines.map((line) => {
152
152
  try {
153
153
  return JSON.parse(line);
@@ -176,8 +176,8 @@ function aggregateStats(events) {
176
176
  }
177
177
  function clearStore() {
178
178
  const readsFile = getReadsFilePath_();
179
- if (fs8.existsSync(readsFile)) {
180
- fs8.writeFileSync(readsFile, "", "utf-8");
179
+ if (fs9.existsSync(readsFile)) {
180
+ fs9.writeFileSync(readsFile, "", "utf-8");
181
181
  }
182
182
  }
183
183
  function getReadsFilePath() {
@@ -186,37 +186,37 @@ function getReadsFilePath() {
186
186
  function getStoreDir() {
187
187
  return getStoreDirPath();
188
188
  }
189
- var fs8, os2, path8;
189
+ var fs9, os3, path9;
190
190
  var init_session_store = __esm({
191
191
  "src/watcher/session-store.ts"() {
192
192
  "use strict";
193
193
  init_cjs_shims();
194
- fs8 = __toESM(require("fs"));
195
- os2 = __toESM(require("os"));
196
- path8 = __toESM(require("path"));
194
+ fs9 = __toESM(require("fs"));
195
+ os3 = __toESM(require("os"));
196
+ path9 = __toESM(require("path"));
197
197
  }
198
198
  });
199
199
 
200
200
  // src/watcher/session-reader.ts
201
201
  function listSessionFiles() {
202
- if (!fs9.existsSync(CLAUDE_PROJECTS_DIR)) return [];
202
+ if (!fs10.existsSync(CLAUDE_PROJECTS_DIR)) return [];
203
203
  const results = [];
204
204
  try {
205
- const projectDirs = fs9.readdirSync(CLAUDE_PROJECTS_DIR);
205
+ const projectDirs = fs10.readdirSync(CLAUDE_PROJECTS_DIR);
206
206
  for (const projectDir of projectDirs) {
207
- const projectPath = path9.join(CLAUDE_PROJECTS_DIR, projectDir);
207
+ const projectPath = path10.join(CLAUDE_PROJECTS_DIR, projectDir);
208
208
  try {
209
- const stat = fs9.statSync(projectPath);
209
+ const stat = fs10.statSync(projectPath);
210
210
  if (!stat.isDirectory()) continue;
211
- const files = fs9.readdirSync(projectPath).filter((f) => f.endsWith(".jsonl"));
211
+ const files = fs10.readdirSync(projectPath).filter((f) => f.endsWith(".jsonl"));
212
212
  for (const file of files) {
213
- const filePath = path9.join(projectPath, file);
213
+ const filePath = path10.join(projectPath, file);
214
214
  try {
215
- const fstat = fs9.statSync(filePath);
215
+ const fstat = fs10.statSync(filePath);
216
216
  results.push({
217
217
  filePath,
218
218
  mtimeMs: fstat.mtimeMs,
219
- sessionId: path9.basename(file, ".jsonl"),
219
+ sessionId: path10.basename(file, ".jsonl"),
220
220
  projectDir
221
221
  });
222
222
  } catch {
@@ -239,7 +239,7 @@ function findSessionFile(sessionId) {
239
239
  }
240
240
  return files[0]?.filePath ?? null;
241
241
  }
242
- function readSessionUsage(sessionFilePath) {
242
+ async function readSessionUsage(sessionFilePath) {
243
243
  const result = {
244
244
  inputTokens: 0,
245
245
  outputTokens: 0,
@@ -247,41 +247,45 @@ function readSessionUsage(sessionFilePath) {
247
247
  cacheReadTokens: 0,
248
248
  requestCount: 0
249
249
  };
250
- if (!fs9.existsSync(sessionFilePath)) return result;
251
- let content;
250
+ if (!fs10.existsSync(sessionFilePath)) return result;
251
+ const { createReadStream } = await import("fs");
252
+ const { createInterface } = await import("readline");
252
253
  try {
253
- content = fs9.readFileSync(sessionFilePath, "utf-8");
254
- } catch {
255
- return result;
256
- }
257
- const lines = content.trim().split("\n").filter(Boolean);
258
- for (const line of lines) {
259
- try {
260
- const entry = JSON.parse(line);
261
- const usage = entry.usage ?? entry.message?.usage;
262
- if (!usage) continue;
263
- const isAssistant = entry.type === "assistant" || entry.message?.role === "assistant";
264
- if (isAssistant) {
265
- result.inputTokens += usage.input_tokens ?? 0;
266
- result.outputTokens += usage.output_tokens ?? 0;
267
- result.cacheCreationTokens += usage.cache_creation_input_tokens ?? 0;
268
- result.cacheReadTokens += usage.cache_read_input_tokens ?? 0;
269
- result.requestCount++;
254
+ const rl = createInterface({
255
+ input: createReadStream(sessionFilePath, { encoding: "utf-8" }),
256
+ crlfDelay: Infinity
257
+ });
258
+ for await (const line of rl) {
259
+ if (!line.trim()) continue;
260
+ try {
261
+ const entry = JSON.parse(line);
262
+ const usage = entry.usage ?? entry.message?.usage;
263
+ if (!usage) continue;
264
+ const isAssistant = entry.type === "assistant" || entry.message?.role === "assistant";
265
+ if (isAssistant) {
266
+ result.inputTokens += usage.input_tokens ?? 0;
267
+ result.outputTokens += usage.output_tokens ?? 0;
268
+ result.cacheCreationTokens += usage.cache_creation_input_tokens ?? 0;
269
+ result.cacheReadTokens += usage.cache_read_input_tokens ?? 0;
270
+ result.requestCount++;
271
+ }
272
+ } catch {
270
273
  }
271
- } catch {
272
274
  }
275
+ } catch {
276
+ return result;
273
277
  }
274
278
  return result;
275
279
  }
276
- var fs9, os3, path9, CLAUDE_PROJECTS_DIR;
280
+ var fs10, os4, path10, CLAUDE_PROJECTS_DIR;
277
281
  var init_session_reader = __esm({
278
282
  "src/watcher/session-reader.ts"() {
279
283
  "use strict";
280
284
  init_cjs_shims();
281
- fs9 = __toESM(require("fs"));
282
- os3 = __toESM(require("os"));
283
- path9 = __toESM(require("path"));
284
- CLAUDE_PROJECTS_DIR = path9.join(os3.homedir(), ".claude", "projects");
285
+ fs10 = __toESM(require("fs"));
286
+ os4 = __toESM(require("os"));
287
+ path10 = __toESM(require("path"));
288
+ CLAUDE_PROJECTS_DIR = path10.join(os4.homedir(), ".claude", "projects");
285
289
  }
286
290
  });
287
291
 
@@ -299,7 +303,7 @@ function fmtCost(tokens, model) {
299
303
  return `$${cost.toFixed(4)}`;
300
304
  }
301
305
  function shortPath(filePath) {
302
- const parts = filePath.split(path10.sep);
306
+ const parts = filePath.split(path11.sep);
303
307
  if (parts.length <= 3) return filePath;
304
308
  return "\u2026/" + parts.slice(-3).join("/");
305
309
  }
@@ -397,20 +401,23 @@ function Dashboard({
397
401
  const events = readAllEvents();
398
402
  const fileStats2 = aggregateStats(events);
399
403
  const sessionFile2 = sessionId ? findSessionFile(sessionId) : findSessionFile();
400
- const usage2 = sessionFile2 ? readSessionUsage(sessionFile2) : {
404
+ const usagePromise = sessionFile2 ? readSessionUsage(sessionFile2) : Promise.resolve({
401
405
  inputTokens: 0,
402
406
  outputTokens: 0,
403
407
  cacheCreationTokens: 0,
404
408
  cacheReadTokens: 0,
405
409
  requestCount: 0
406
- };
407
- setState((prev) => ({
408
- fileStats: fileStats2,
409
- usage: usage2,
410
- sessionFile: sessionFile2,
411
- lastUpdated: /* @__PURE__ */ new Date(),
412
- tickCount: prev.tickCount + 1
413
- }));
410
+ });
411
+ usagePromise.then((usage2) => {
412
+ setState((prev) => ({
413
+ fileStats: fileStats2,
414
+ usage: usage2,
415
+ sessionFile: sessionFile2,
416
+ lastUpdated: /* @__PURE__ */ new Date(),
417
+ tickCount: prev.tickCount + 1
418
+ }));
419
+ }).catch(() => {
420
+ });
414
421
  }, [sessionId]);
415
422
  (0, import_react.useEffect)(() => {
416
423
  refresh();
@@ -418,9 +425,9 @@ function Dashboard({
418
425
  const readsFile = getReadsFilePath();
419
426
  let watcher = null;
420
427
  const tryWatch = () => {
421
- if (fs10.existsSync(readsFile)) {
428
+ if (fs11.existsSync(readsFile)) {
422
429
  try {
423
- watcher = fs10.watch(readsFile, () => refresh());
430
+ watcher = fs11.watch(readsFile, () => refresh());
424
431
  } catch {
425
432
  }
426
433
  }
@@ -458,7 +465,7 @@ function Dashboard({
458
465
  ] }),
459
466
  sessionFile && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_ink.Text, { dimColor: true, children: [
460
467
  " \u2014 ",
461
- path10.basename(sessionFile, ".jsonl").slice(0, 8),
468
+ path11.basename(sessionFile, ".jsonl").slice(0, 8),
462
469
  "\u2026"
463
470
  ] }),
464
471
  !sessionFile && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_ink.Text, { dimColor: true, children: " \u2014 no session file found" })
@@ -474,7 +481,7 @@ function Dashboard({
474
481
  ] })
475
482
  ] });
476
483
  }
477
- var import_react, import_ink, fs10, path10, import_jsx_runtime;
484
+ var import_react, import_ink, fs11, path11, import_jsx_runtime;
478
485
  var init_Dashboard = __esm({
479
486
  "src/components/Dashboard.tsx"() {
480
487
  "use strict";
@@ -484,15 +491,15 @@ var init_Dashboard = __esm({
484
491
  init_session_store();
485
492
  init_session_reader();
486
493
  init_models();
487
- fs10 = __toESM(require("fs"));
488
- path10 = __toESM(require("path"));
494
+ fs11 = __toESM(require("fs"));
495
+ path11 = __toESM(require("path"));
489
496
  import_jsx_runtime = require("react/jsx-runtime");
490
497
  }
491
498
  });
492
499
 
493
500
  // src/mcp/smart-reader.ts
494
501
  function detectLanguage(filePath) {
495
- const ext = path13.extname(filePath).toLowerCase();
502
+ const ext = path14.extname(filePath).toLowerCase();
496
503
  switch (ext) {
497
504
  case ".ts":
498
505
  case ".tsx":
@@ -509,8 +516,8 @@ function detectLanguage(filePath) {
509
516
  }
510
517
  }
511
518
  function findSymbol(filePath, symbolName) {
512
- if (!fs12.existsSync(filePath)) return null;
513
- const content = fs12.readFileSync(filePath, "utf-8");
519
+ if (!fs13.existsSync(filePath)) return null;
520
+ const content = fs13.readFileSync(filePath, "utf-8");
514
521
  const lines = content.split("\n");
515
522
  const lang = detectLanguage(filePath);
516
523
  const patterns = lang === "python" ? PYTHON_PATTERNS : TS_JS_PATTERNS;
@@ -571,8 +578,8 @@ function findPythonBlockEnd(lines, startIdx) {
571
578
  return lines.length;
572
579
  }
573
580
  function extractLineRange(filePath, startLine, endLine, contextLines = 0) {
574
- if (!fs12.existsSync(filePath)) return null;
575
- const allLines = fs12.readFileSync(filePath, "utf-8").split("\n");
581
+ if (!fs13.existsSync(filePath)) return null;
582
+ const allLines = fs13.readFileSync(filePath, "utf-8").split("\n");
576
583
  const totalLines = allLines.length;
577
584
  const from = Math.max(0, startLine - 1 - contextLines);
578
585
  const to = Math.min(totalLines, endLine + contextLines);
@@ -587,7 +594,7 @@ function extractLineRange(filePath, startLine, endLine, contextLines = 0) {
587
594
  };
588
595
  }
589
596
  function smartRead(filePath, symbol, startLine, endLine, contextLines = 3) {
590
- if (!fs12.existsSync(filePath)) {
597
+ if (!fs13.existsSync(filePath)) {
591
598
  throw new Error(`File not found: ${filePath}`);
592
599
  }
593
600
  if (symbol) {
@@ -599,7 +606,7 @@ function smartRead(filePath, symbol, startLine, endLine, contextLines = 3) {
599
606
  filePath,
600
607
  startLine: extracted.startLine,
601
608
  endLine: extracted.endLine,
602
- totalLines: fs12.readFileSync(filePath, "utf-8").split("\n").length,
609
+ totalLines: fs13.readFileSync(filePath, "utf-8").split("\n").length,
603
610
  truncated: false,
604
611
  symbolName: symbol
605
612
  };
@@ -611,7 +618,7 @@ function smartRead(filePath, symbol, startLine, endLine, contextLines = 3) {
611
618
  return { ...result, truncated: false };
612
619
  }
613
620
  }
614
- const fullContent = fs12.readFileSync(filePath, "utf-8");
621
+ const fullContent = fs13.readFileSync(filePath, "utf-8");
615
622
  const allLines = fullContent.split("\n");
616
623
  const totalLines = allLines.length;
617
624
  const fullTokens = countTokens(fullContent);
@@ -647,13 +654,13 @@ function smartRead(filePath, symbol, startLine, endLine, contextLines = 3) {
647
654
  truncated: true
648
655
  };
649
656
  }
650
- var fs12, path13, TS_JS_PATTERNS, PYTHON_PATTERNS, MAX_FULL_FILE_TOKENS;
657
+ var fs13, path14, TS_JS_PATTERNS, PYTHON_PATTERNS, MAX_FULL_FILE_TOKENS;
651
658
  var init_smart_reader = __esm({
652
659
  "src/mcp/smart-reader.ts"() {
653
660
  "use strict";
654
661
  init_cjs_shims();
655
- fs12 = __toESM(require("fs"));
656
- path13 = __toESM(require("path"));
662
+ fs13 = __toESM(require("fs"));
663
+ path14 = __toESM(require("path"));
657
664
  init_tokenizer();
658
665
  TS_JS_PATTERNS = [
659
666
  // export async function name / export function name / function name
@@ -692,7 +699,7 @@ function extractSymbolsFromFile(filePath) {
692
699
  if (lang === "other") return [];
693
700
  let content;
694
701
  try {
695
- content = fs13.readFileSync(filePath, "utf-8");
702
+ content = fs14.readFileSync(filePath, "utf-8");
696
703
  } catch {
697
704
  return [];
698
705
  }
@@ -721,13 +728,13 @@ function extractSymbolsFromFile(filePath) {
721
728
  }
722
729
  return results;
723
730
  }
724
- var fs13, path14, import_glob, TS_JS_EXTRACTORS, PYTHON_EXTRACTORS, SOURCE_GLOBS, IGNORE_DIRS, SymbolIndex, globalIndex;
731
+ var fs14, path15, import_glob, TS_JS_EXTRACTORS, PYTHON_EXTRACTORS, SOURCE_GLOBS, IGNORE_DIRS, SymbolIndex, globalIndex;
725
732
  var init_symbol_index = __esm({
726
733
  "src/mcp/symbol-index.ts"() {
727
734
  "use strict";
728
735
  init_cjs_shims();
729
- fs13 = __toESM(require("fs"));
730
- path14 = __toESM(require("path"));
736
+ fs14 = __toESM(require("fs"));
737
+ path15 = __toESM(require("path"));
731
738
  import_glob = require("glob");
732
739
  init_smart_reader();
733
740
  TS_JS_EXTRACTORS = [
@@ -780,8 +787,8 @@ var init_symbol_index = __esm({
780
787
  this.entries = [];
781
788
  let files = [];
782
789
  try {
783
- files = await (0, import_glob.glob)(SOURCE_GLOBS.map((g) => path14.join(projectRoot, g)), {
784
- ignore: IGNORE_DIRS.map((g) => path14.join(projectRoot, g)),
790
+ files = await (0, import_glob.glob)(SOURCE_GLOBS.map((g) => path15.join(projectRoot, g)), {
791
+ ignore: IGNORE_DIRS.map((g) => path15.join(projectRoot, g)),
785
792
  absolute: true
786
793
  });
787
794
  } catch {
@@ -835,7 +842,7 @@ __export(server_exports, {
835
842
  startMcpServer: () => startMcpServer
836
843
  });
837
844
  function handleSmartRead(args) {
838
- const filePath = path15.resolve(args.file);
845
+ const filePath = path16.resolve(args.file);
839
846
  const result = smartRead(
840
847
  filePath,
841
848
  args.symbol,
@@ -871,7 +878,7 @@ Example: index_project({ "project_root": "${process.cwd()}" })`;
871
878
  ];
872
879
  for (let i = 0; i < results.length; i++) {
873
880
  const r = results[i];
874
- const rel = path15.relative(process.cwd(), r.filePath);
881
+ const rel = path16.relative(process.cwd(), r.filePath);
875
882
  lines.push(`${i + 1}. [${r.type}] ${r.name}`);
876
883
  lines.push(` ${rel}:${r.lineStart}`);
877
884
  lines.push(` ${r.signature.trim()}`);
@@ -883,7 +890,7 @@ Example: index_project({ "project_root": "${process.cwd()}" })`;
883
890
  return lines.join("\n");
884
891
  }
885
892
  async function handleIndexProject(args) {
886
- const projectRoot = args.project_root ? path15.resolve(args.project_root) : process.cwd();
893
+ const projectRoot = args.project_root ? path16.resolve(args.project_root) : process.cwd();
887
894
  const fn = args.rebuild ? () => globalIndex.rebuild(projectRoot) : () => globalIndex.build(projectRoot);
888
895
  const { fileCount, symbolCount } = await fn();
889
896
  if (fileCount === 0 && globalIndex.isReady) {
@@ -931,12 +938,12 @@ async function startMcpServer() {
931
938
  await server.connect(transport);
932
939
  process.stderr.write("[claudectx mcp] Server started (stdio)\n");
933
940
  }
934
- var path15, import_server, import_stdio, import_types, TOOLS;
941
+ var path16, import_server, import_stdio, import_types, TOOLS;
935
942
  var init_server = __esm({
936
943
  "src/mcp/server.ts"() {
937
944
  "use strict";
938
945
  init_cjs_shims();
939
- path15 = __toESM(require("path"));
946
+ path16 = __toESM(require("path"));
940
947
  import_server = require("@modelcontextprotocol/sdk/server/index.js");
941
948
  import_stdio = require("@modelcontextprotocol/sdk/server/stdio.js");
942
949
  import_types = require("@modelcontextprotocol/sdk/types.js");
@@ -1574,8 +1581,8 @@ async function analyzeCommand(options) {
1574
1581
 
1575
1582
  // src/commands/optimize.ts
1576
1583
  init_cjs_shims();
1577
- var fs7 = __toESM(require("fs"));
1578
- var path7 = __toESM(require("path"));
1584
+ var fs8 = __toESM(require("fs"));
1585
+ var path8 = __toESM(require("path"));
1579
1586
  var import_chalk3 = __toESM(require("chalk"));
1580
1587
  var import_boxen2 = __toESM(require("boxen"));
1581
1588
  var import_prompts = require("@inquirer/prompts");
@@ -1738,9 +1745,132 @@ function writeIgnorefile(result) {
1738
1745
 
1739
1746
  // src/optimizer/claudemd-splitter.ts
1740
1747
  init_cjs_shims();
1748
+ var fs5 = __toESM(require("fs"));
1749
+ var path6 = __toESM(require("path"));
1750
+ init_tokenizer();
1751
+
1752
+ // src/shared/backup-manager.ts
1753
+ init_cjs_shims();
1741
1754
  var fs4 = __toESM(require("fs"));
1755
+ var os2 = __toESM(require("os"));
1742
1756
  var path5 = __toESM(require("path"));
1743
- init_tokenizer();
1757
+ var MAX_BACKUPS = 50;
1758
+ var _backupDirOverride = null;
1759
+ function getBackupDir() {
1760
+ return _backupDirOverride ?? path5.join(os2.homedir(), ".claudectx", "backups");
1761
+ }
1762
+ var BACKUP_DIR = path5.join(os2.homedir(), ".claudectx", "backups");
1763
+ function ensureBackupDir() {
1764
+ const dir = getBackupDir();
1765
+ if (!fs4.existsSync(dir)) {
1766
+ fs4.mkdirSync(dir, { recursive: true });
1767
+ }
1768
+ }
1769
+ function getManifestPath() {
1770
+ return path5.join(getBackupDir(), "manifest.json");
1771
+ }
1772
+ function readManifest() {
1773
+ ensureBackupDir();
1774
+ const manifestPath = getManifestPath();
1775
+ if (!fs4.existsSync(manifestPath)) {
1776
+ return { version: "1", entries: [] };
1777
+ }
1778
+ try {
1779
+ return JSON.parse(fs4.readFileSync(manifestPath, "utf-8"));
1780
+ } catch {
1781
+ return { version: "1", entries: [] };
1782
+ }
1783
+ }
1784
+ function writeManifest(manifest) {
1785
+ ensureBackupDir();
1786
+ const manifestPath = getManifestPath();
1787
+ const tmpPath = `${manifestPath}.tmp-${Date.now()}`;
1788
+ try {
1789
+ fs4.writeFileSync(tmpPath, JSON.stringify(manifest, null, 2) + "\n", "utf-8");
1790
+ fs4.renameSync(tmpPath, manifestPath);
1791
+ } catch {
1792
+ try {
1793
+ fs4.unlinkSync(tmpPath);
1794
+ } catch {
1795
+ }
1796
+ throw new Error(`Failed to write backup manifest at ${manifestPath}`);
1797
+ }
1798
+ }
1799
+ function generateId(originalPath) {
1800
+ const now = /* @__PURE__ */ new Date();
1801
+ const ts = now.toISOString().replace(/[-:]/g, "").replace("T", "T").replace(".", "m").replace("Z", "");
1802
+ const rand = Math.floor(Math.random() * 9e3 + 1e3);
1803
+ const basename10 = path5.basename(originalPath);
1804
+ return `${ts}-${rand}-${basename10}`;
1805
+ }
1806
+ async function backupFile(filePath, command) {
1807
+ const resolved = path5.resolve(filePath);
1808
+ if (!fs4.existsSync(resolved)) {
1809
+ throw new Error(`Cannot back up "${resolved}": file does not exist.`);
1810
+ }
1811
+ ensureBackupDir();
1812
+ const id = generateId(resolved);
1813
+ const backupPath = path5.join(getBackupDir(), id);
1814
+ fs4.copyFileSync(resolved, backupPath);
1815
+ const stat = fs4.statSync(backupPath);
1816
+ const entry = {
1817
+ id,
1818
+ originalPath: resolved,
1819
+ backupPath,
1820
+ command,
1821
+ createdAt: (/* @__PURE__ */ new Date()).toISOString(),
1822
+ sizeBytes: stat.size
1823
+ };
1824
+ const manifest = readManifest();
1825
+ manifest.entries.unshift(entry);
1826
+ writeManifest(manifest);
1827
+ if (manifest.entries.length > MAX_BACKUPS) {
1828
+ await pruneOldBackups();
1829
+ }
1830
+ return entry;
1831
+ }
1832
+ async function listBackups(filterPath) {
1833
+ const manifest = readManifest();
1834
+ const entries = manifest.entries;
1835
+ if (!filterPath) return entries;
1836
+ const resolved = path5.resolve(filterPath);
1837
+ return entries.filter((e) => e.originalPath === resolved);
1838
+ }
1839
+ async function restoreBackup(backupId) {
1840
+ const manifest = readManifest();
1841
+ const entry = manifest.entries.find((e) => e.id === backupId);
1842
+ if (!entry) {
1843
+ throw new Error(`Backup "${backupId}" not found. Run "claudectx revert --list" to see available backups.`);
1844
+ }
1845
+ if (!fs4.existsSync(entry.backupPath)) {
1846
+ throw new Error(`Backup file missing at "${entry.backupPath}". It may have been deleted manually.`);
1847
+ }
1848
+ let undoEntry = null;
1849
+ if (fs4.existsSync(entry.originalPath)) {
1850
+ undoEntry = await backupFile(entry.originalPath, "revert");
1851
+ }
1852
+ const targetDir = path5.dirname(entry.originalPath);
1853
+ if (!fs4.existsSync(targetDir)) {
1854
+ fs4.mkdirSync(targetDir, { recursive: true });
1855
+ }
1856
+ fs4.copyFileSync(entry.backupPath, entry.originalPath);
1857
+ return { entry, undoEntry };
1858
+ }
1859
+ async function pruneOldBackups() {
1860
+ const manifest = readManifest();
1861
+ if (manifest.entries.length <= MAX_BACKUPS) return 0;
1862
+ const toRemove = manifest.entries.splice(MAX_BACKUPS);
1863
+ for (const entry of toRemove) {
1864
+ try {
1865
+ fs4.unlinkSync(entry.backupPath);
1866
+ } catch {
1867
+ }
1868
+ }
1869
+ writeManifest(manifest);
1870
+ return toRemove.length;
1871
+ }
1872
+
1873
+ // src/optimizer/claudemd-splitter.ts
1744
1874
  var SPLIT_MIN_TOKENS = 300;
1745
1875
  function slugify(title) {
1746
1876
  return title.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
@@ -1775,9 +1905,9 @@ function parseSections(content) {
1775
1905
  return sections;
1776
1906
  }
1777
1907
  function planSplit(claudeMdPath, sectionsToExtract) {
1778
- const content = fs4.readFileSync(claudeMdPath, "utf-8");
1908
+ const content = fs5.readFileSync(claudeMdPath, "utf-8");
1779
1909
  const sections = parseSections(content);
1780
- const claudeDir = path5.join(path5.dirname(claudeMdPath), ".claude");
1910
+ const claudeDir = path6.join(path6.dirname(claudeMdPath), ".claude");
1781
1911
  const extractedFiles = [];
1782
1912
  let newContent = "";
1783
1913
  let tokensSaved = 0;
@@ -1790,7 +1920,7 @@ function planSplit(claudeMdPath, sectionsToExtract) {
1790
1920
  usedSlugs.set(slug, count + 1);
1791
1921
  const filename = `${slug}.md`;
1792
1922
  const relRefPath = `.claude/${filename}`;
1793
- const filePath = path5.join(claudeDir, filename);
1923
+ const filePath = path6.join(claudeDir, filename);
1794
1924
  const refBlock = `## ${section.title}
1795
1925
 
1796
1926
  @${relRefPath}
@@ -1814,21 +1944,24 @@ function planSplit(claudeMdPath, sectionsToExtract) {
1814
1944
  tokensSaved: Math.max(0, tokensSaved)
1815
1945
  };
1816
1946
  }
1817
- function applySplit(result) {
1947
+ async function applySplit(result) {
1818
1948
  if (result.extractedFiles.length === 0) return;
1819
- const claudeDir = path5.dirname(result.extractedFiles[0].filePath);
1820
- if (!fs4.existsSync(claudeDir)) {
1821
- fs4.mkdirSync(claudeDir, { recursive: true });
1949
+ if (fs5.existsSync(result.claudeMdPath)) {
1950
+ await backupFile(result.claudeMdPath, "optimize");
1951
+ }
1952
+ const claudeDir = path6.dirname(result.extractedFiles[0].filePath);
1953
+ if (!fs5.existsSync(claudeDir)) {
1954
+ fs5.mkdirSync(claudeDir, { recursive: true });
1822
1955
  }
1823
1956
  for (const file of result.extractedFiles) {
1824
- fs4.writeFileSync(file.filePath, file.content, "utf-8");
1957
+ fs5.writeFileSync(file.filePath, file.content, "utf-8");
1825
1958
  }
1826
- fs4.writeFileSync(result.claudeMdPath, result.newClaudeMd, "utf-8");
1959
+ fs5.writeFileSync(result.claudeMdPath, result.newClaudeMd, "utf-8");
1827
1960
  }
1828
1961
 
1829
1962
  // src/optimizer/cache-applier.ts
1830
1963
  init_cjs_shims();
1831
- var fs5 = __toESM(require("fs"));
1964
+ var fs6 = __toESM(require("fs"));
1832
1965
  function findCacheBusters(content) {
1833
1966
  const fixes = [];
1834
1967
  const lines = content.split("\n");
@@ -1858,18 +1991,21 @@ function applyCacheFixes(content, fixes) {
1858
1991
  return lines.join("\n");
1859
1992
  }
1860
1993
  function planCacheFixes(claudeMdPath) {
1861
- const content = fs5.readFileSync(claudeMdPath, "utf-8");
1994
+ const content = fs6.readFileSync(claudeMdPath, "utf-8");
1862
1995
  const fixes = findCacheBusters(content);
1863
1996
  return { fixes, newContent: applyCacheFixes(content, fixes) };
1864
1997
  }
1865
- function applyAndWriteCacheFixes(claudeMdPath, result) {
1866
- fs5.writeFileSync(claudeMdPath, result.newContent, "utf-8");
1998
+ async function applyAndWriteCacheFixes(claudeMdPath, result) {
1999
+ if (fs6.existsSync(claudeMdPath)) {
2000
+ await backupFile(claudeMdPath, "optimize");
2001
+ }
2002
+ fs6.writeFileSync(claudeMdPath, result.newContent, "utf-8");
1867
2003
  }
1868
2004
 
1869
2005
  // src/optimizer/hooks-installer.ts
1870
2006
  init_cjs_shims();
1871
- var fs6 = __toESM(require("fs"));
1872
- var path6 = __toESM(require("path"));
2007
+ var fs7 = __toESM(require("fs"));
2008
+ var path7 = __toESM(require("path"));
1873
2009
  var CLAUDECTX_HOOKS = {
1874
2010
  PostToolUse: [
1875
2011
  {
@@ -1887,13 +2023,13 @@ var CLAUDECTX_HOOKS = {
1887
2023
  ]
1888
2024
  };
1889
2025
  function planHooksInstall(projectRoot) {
1890
- const claudeDir = path6.join(projectRoot, ".claude");
1891
- const settingsPath = path6.join(claudeDir, "settings.local.json");
1892
- const existed = fs6.existsSync(settingsPath);
2026
+ const claudeDir = path7.join(projectRoot, ".claude");
2027
+ const settingsPath = path7.join(claudeDir, "settings.local.json");
2028
+ const existed = fs7.existsSync(settingsPath);
1893
2029
  let existing = {};
1894
2030
  if (existed) {
1895
2031
  try {
1896
- existing = JSON.parse(fs6.readFileSync(settingsPath, "utf-8"));
2032
+ existing = JSON.parse(fs7.readFileSync(settingsPath, "utf-8"));
1897
2033
  } catch {
1898
2034
  existing = {};
1899
2035
  }
@@ -1914,25 +2050,25 @@ function planHooksInstall(projectRoot) {
1914
2050
  return { settingsPath, existed, mergedSettings };
1915
2051
  }
1916
2052
  function applyHooksInstall(result) {
1917
- const dir = path6.dirname(result.settingsPath);
1918
- if (!fs6.existsSync(dir)) {
1919
- fs6.mkdirSync(dir, { recursive: true });
2053
+ const dir = path7.dirname(result.settingsPath);
2054
+ if (!fs7.existsSync(dir)) {
2055
+ fs7.mkdirSync(dir, { recursive: true });
1920
2056
  }
1921
- fs6.writeFileSync(result.settingsPath, JSON.stringify(result.mergedSettings, null, 2) + "\n", "utf-8");
2057
+ fs7.writeFileSync(result.settingsPath, JSON.stringify(result.mergedSettings, null, 2) + "\n", "utf-8");
1922
2058
  }
1923
2059
  function writeHooksSettings(projectRoot, mergedSettings) {
1924
- const settingsPath = path6.join(projectRoot, ".claude", "settings.local.json");
1925
- const dir = path6.dirname(settingsPath);
1926
- if (!fs6.existsSync(dir)) {
1927
- fs6.mkdirSync(dir, { recursive: true });
2060
+ const settingsPath = path7.join(projectRoot, ".claude", "settings.local.json");
2061
+ const dir = path7.dirname(settingsPath);
2062
+ if (!fs7.existsSync(dir)) {
2063
+ fs7.mkdirSync(dir, { recursive: true });
1928
2064
  }
1929
- fs6.writeFileSync(settingsPath, JSON.stringify(mergedSettings, null, 2) + "\n", "utf-8");
2065
+ fs7.writeFileSync(settingsPath, JSON.stringify(mergedSettings, null, 2) + "\n", "utf-8");
1930
2066
  }
1931
2067
  function isAlreadyInstalled(projectRoot) {
1932
- const settingsPath = path6.join(projectRoot, ".claude", "settings.local.json");
1933
- if (!fs6.existsSync(settingsPath)) return false;
2068
+ const settingsPath = path7.join(projectRoot, ".claude", "settings.local.json");
2069
+ if (!fs7.existsSync(settingsPath)) return false;
1934
2070
  try {
1935
- const settings = JSON.parse(fs6.readFileSync(settingsPath, "utf-8"));
2071
+ const settings = JSON.parse(fs7.readFileSync(settingsPath, "utf-8"));
1936
2072
  const postToolUse = settings?.hooks?.PostToolUse ?? [];
1937
2073
  return postToolUse.some((h) => h.matcher === "Read");
1938
2074
  } catch {
@@ -1942,7 +2078,7 @@ function isAlreadyInstalled(projectRoot) {
1942
2078
 
1943
2079
  // src/commands/optimize.ts
1944
2080
  async function optimizeCommand(options) {
1945
- const projectPath = options.path ? path7.resolve(options.path) : findProjectRoot() ?? process.cwd();
2081
+ const projectPath = options.path ? path8.resolve(options.path) : findProjectRoot() ?? process.cwd();
1946
2082
  const dryRun = options.dryRun ?? false;
1947
2083
  const autoApply = options.apply ?? false;
1948
2084
  const specificMode = options.claudemd || options.ignorefile || options.cache || options.hooks;
@@ -2067,12 +2203,12 @@ async function runIgnorefile(projectRoot, dryRun, autoApply) {
2067
2203
  }
2068
2204
  async function runClaudeMdSplit(projectRoot, report, dryRun, autoApply) {
2069
2205
  printSectionHeader("CLAUDE.md \u2192 @files");
2070
- const claudeMdPath = path7.join(projectRoot, "CLAUDE.md");
2071
- if (!fs7.existsSync(claudeMdPath)) {
2206
+ const claudeMdPath = path8.join(projectRoot, "CLAUDE.md");
2207
+ if (!fs8.existsSync(claudeMdPath)) {
2072
2208
  logger.warn("No CLAUDE.md found \u2014 skipping.");
2073
2209
  return;
2074
2210
  }
2075
- const content = fs7.readFileSync(claudeMdPath, "utf-8");
2211
+ const content = fs8.readFileSync(claudeMdPath, "utf-8");
2076
2212
  const sections = parseSections(content);
2077
2213
  const largeSections = sections.filter(
2078
2214
  (s) => !s.isPreamble && s.tokens >= SPLIT_MIN_TOKENS
@@ -2128,18 +2264,18 @@ Would extract ${splitResult.extractedFiles.length} section(s) to .claude/`
2128
2264
  logger.info("Skipped.");
2129
2265
  return;
2130
2266
  }
2131
- applySplit(splitResult);
2267
+ await applySplit(splitResult);
2132
2268
  logger.success(
2133
2269
  `Extracted ${splitResult.extractedFiles.length} section(s). Saved ~${splitResult.tokensSaved} tokens/request.`
2134
2270
  );
2135
2271
  for (const f of splitResult.extractedFiles) {
2136
- logger.info(` Created: ${import_chalk3.default.cyan(path7.relative(projectRoot, f.filePath))}`);
2272
+ logger.info(` Created: ${import_chalk3.default.cyan(path8.relative(projectRoot, f.filePath))}`);
2137
2273
  }
2138
2274
  }
2139
2275
  async function runCacheOptimization(projectRoot, dryRun, autoApply) {
2140
2276
  printSectionHeader("Prompt cache optimisation");
2141
- const claudeMdPath = path7.join(projectRoot, "CLAUDE.md");
2142
- if (!fs7.existsSync(claudeMdPath)) {
2277
+ const claudeMdPath = path8.join(projectRoot, "CLAUDE.md");
2278
+ if (!fs8.existsSync(claudeMdPath)) {
2143
2279
  logger.warn("No CLAUDE.md found \u2014 skipping.");
2144
2280
  return;
2145
2281
  }
@@ -2167,14 +2303,14 @@ async function runCacheOptimization(projectRoot, dryRun, autoApply) {
2167
2303
  logger.info("Skipped.");
2168
2304
  return;
2169
2305
  }
2170
- applyAndWriteCacheFixes(claudeMdPath, result);
2306
+ await applyAndWriteCacheFixes(claudeMdPath, result);
2171
2307
  logger.success(`Fixed ${result.fixes.length} cache-busting pattern(s) in CLAUDE.md.`);
2172
2308
  }
2173
2309
  async function runHooks(projectRoot, dryRun, autoApply) {
2174
2310
  printSectionHeader("Session hooks");
2175
2311
  const result = planHooksInstall(projectRoot);
2176
2312
  logger.info(
2177
- `Settings file: ${import_chalk3.default.cyan(path7.relative(projectRoot, result.settingsPath))}`
2313
+ `Settings file: ${import_chalk3.default.cyan(path8.relative(projectRoot, result.settingsPath))}`
2178
2314
  );
2179
2315
  logger.info(result.existed ? "Will merge with existing settings." : "Will create new file.");
2180
2316
  console.log(import_chalk3.default.dim("\n Hooks to install:"));
@@ -2189,7 +2325,7 @@ async function runHooks(projectRoot, dryRun, autoApply) {
2189
2325
  }
2190
2326
  applyHooksInstall(result);
2191
2327
  logger.success(
2192
- `Hooks installed \u2192 ${import_chalk3.default.cyan(path7.relative(projectRoot, result.settingsPath))}`
2328
+ `Hooks installed \u2192 ${import_chalk3.default.cyan(path8.relative(projectRoot, result.settingsPath))}`
2193
2329
  );
2194
2330
  }
2195
2331
  function printSectionHeader(title) {
@@ -2199,7 +2335,7 @@ function printSectionHeader(title) {
2199
2335
 
2200
2336
  // src/commands/watch.ts
2201
2337
  init_cjs_shims();
2202
- var path11 = __toESM(require("path"));
2338
+ var path12 = __toESM(require("path"));
2203
2339
  init_session_store();
2204
2340
  init_models();
2205
2341
  async function watchCommand(options) {
@@ -2237,30 +2373,30 @@ async function handleLogStdin() {
2237
2373
  const payload = JSON.parse(raw);
2238
2374
  const filePath = payload.tool_input?.file_path;
2239
2375
  if (filePath) {
2240
- appendFileRead(path11.resolve(filePath), payload.session_id);
2376
+ appendFileRead(path12.resolve(filePath), payload.session_id);
2241
2377
  }
2242
2378
  } catch {
2243
2379
  }
2244
2380
  }
2245
2381
  function readStdin() {
2246
- return new Promise((resolve12) => {
2382
+ return new Promise((resolve13) => {
2247
2383
  let data = "";
2248
2384
  process.stdin.setEncoding("utf-8");
2249
2385
  process.stdin.on("data", (chunk) => data += chunk);
2250
- process.stdin.on("end", () => resolve12(data));
2251
- setTimeout(() => resolve12(data), 500);
2386
+ process.stdin.on("end", () => resolve13(data));
2387
+ setTimeout(() => resolve13(data), 500);
2252
2388
  });
2253
2389
  }
2254
2390
 
2255
2391
  // src/commands/mcp.ts
2256
2392
  init_cjs_shims();
2257
- var path16 = __toESM(require("path"));
2393
+ var path17 = __toESM(require("path"));
2258
2394
  var import_chalk4 = __toESM(require("chalk"));
2259
2395
 
2260
2396
  // src/mcp/installer.ts
2261
2397
  init_cjs_shims();
2262
- var fs11 = __toESM(require("fs"));
2263
- var path12 = __toESM(require("path"));
2398
+ var fs12 = __toESM(require("fs"));
2399
+ var path13 = __toESM(require("path"));
2264
2400
  var SERVER_NAME = "claudectx";
2265
2401
  var SERVER_ENTRY = {
2266
2402
  command: "claudectx",
@@ -2268,13 +2404,13 @@ var SERVER_ENTRY = {
2268
2404
  type: "stdio"
2269
2405
  };
2270
2406
  function planInstall(projectRoot) {
2271
- const claudeDir = path12.join(projectRoot, ".claude");
2272
- const settingsPath = path12.join(claudeDir, "settings.json");
2273
- const existed = fs11.existsSync(settingsPath);
2407
+ const claudeDir = path13.join(projectRoot, ".claude");
2408
+ const settingsPath = path13.join(claudeDir, "settings.json");
2409
+ const existed = fs12.existsSync(settingsPath);
2274
2410
  let existing = {};
2275
2411
  if (existed) {
2276
2412
  try {
2277
- existing = JSON.parse(fs11.readFileSync(settingsPath, "utf-8"));
2413
+ existing = JSON.parse(fs12.readFileSync(settingsPath, "utf-8"));
2278
2414
  } catch {
2279
2415
  existing = {};
2280
2416
  }
@@ -2291,21 +2427,21 @@ function planInstall(projectRoot) {
2291
2427
  return { settingsPath, existed, alreadyInstalled, mergedSettings };
2292
2428
  }
2293
2429
  function applyInstall(result) {
2294
- const dir = path12.dirname(result.settingsPath);
2295
- if (!fs11.existsSync(dir)) {
2296
- fs11.mkdirSync(dir, { recursive: true });
2430
+ const dir = path13.dirname(result.settingsPath);
2431
+ if (!fs12.existsSync(dir)) {
2432
+ fs12.mkdirSync(dir, { recursive: true });
2297
2433
  }
2298
- fs11.writeFileSync(
2434
+ fs12.writeFileSync(
2299
2435
  result.settingsPath,
2300
2436
  JSON.stringify(result.mergedSettings, null, 2) + "\n",
2301
2437
  "utf-8"
2302
2438
  );
2303
2439
  }
2304
2440
  function isInstalled(projectRoot) {
2305
- const settingsPath = path12.join(projectRoot, ".claude", "settings.json");
2306
- if (!fs11.existsSync(settingsPath)) return false;
2441
+ const settingsPath = path13.join(projectRoot, ".claude", "settings.json");
2442
+ if (!fs12.existsSync(settingsPath)) return false;
2307
2443
  try {
2308
- const settings = JSON.parse(fs11.readFileSync(settingsPath, "utf-8"));
2444
+ const settings = JSON.parse(fs12.readFileSync(settingsPath, "utf-8"));
2309
2445
  return SERVER_NAME in (settings.mcpServers ?? {});
2310
2446
  } catch {
2311
2447
  return false;
@@ -2314,7 +2450,7 @@ function isInstalled(projectRoot) {
2314
2450
 
2315
2451
  // src/commands/mcp.ts
2316
2452
  async function mcpCommand(options) {
2317
- const projectRoot = options.path ? path16.resolve(options.path) : process.cwd();
2453
+ const projectRoot = options.path ? path17.resolve(options.path) : process.cwd();
2318
2454
  if (options.install) {
2319
2455
  await runInstall(projectRoot);
2320
2456
  return;
@@ -2362,13 +2498,13 @@ async function runInstall(projectRoot) {
2362
2498
 
2363
2499
  // src/commands/compress.ts
2364
2500
  init_cjs_shims();
2365
- var path18 = __toESM(require("path"));
2366
- var fs16 = __toESM(require("fs"));
2501
+ var path19 = __toESM(require("path"));
2502
+ var fs17 = __toESM(require("fs"));
2367
2503
  init_session_reader();
2368
2504
 
2369
2505
  // src/compressor/session-parser.ts
2370
2506
  init_cjs_shims();
2371
- var fs14 = __toESM(require("fs"));
2507
+ var fs15 = __toESM(require("fs"));
2372
2508
  function extractText(content) {
2373
2509
  if (!content) return "";
2374
2510
  if (typeof content === "string") return content;
@@ -2379,10 +2515,10 @@ function extractToolCalls(content) {
2379
2515
  return content.filter((b) => b.type === "tool_use" && b.name).map((b) => ({ tool: b.name, input: b.input ?? {} }));
2380
2516
  }
2381
2517
  function parseSessionFile(sessionFilePath) {
2382
- if (!fs14.existsSync(sessionFilePath)) return null;
2518
+ if (!fs15.existsSync(sessionFilePath)) return null;
2383
2519
  let content;
2384
2520
  try {
2385
- content = fs14.readFileSync(sessionFilePath, "utf-8");
2521
+ content = fs15.readFileSync(sessionFilePath, "utf-8");
2386
2522
  } catch {
2387
2523
  return null;
2388
2524
  }
@@ -2582,13 +2718,13 @@ function calcCost(inputTokens, outputTokens) {
2582
2718
 
2583
2719
  // src/compressor/memory-writer.ts
2584
2720
  init_cjs_shims();
2585
- var fs15 = __toESM(require("fs"));
2586
- var path17 = __toESM(require("path"));
2721
+ var fs16 = __toESM(require("fs"));
2722
+ var path18 = __toESM(require("path"));
2587
2723
  function parseMemoryFile(filePath) {
2588
- if (!fs15.existsSync(filePath)) {
2724
+ if (!fs16.existsSync(filePath)) {
2589
2725
  return { preamble: "", entries: [] };
2590
2726
  }
2591
- const content = fs15.readFileSync(filePath, "utf-8");
2727
+ const content = fs16.readFileSync(filePath, "utf-8");
2592
2728
  const markerRegex = /<!-- claudectx-entry: (\d{4}-\d{2}-\d{2}) \| session: ([a-z0-9-]+) -->/g;
2593
2729
  const indices = [];
2594
2730
  let match;
@@ -2639,15 +2775,15 @@ function appendEntry(memoryFilePath, sessionId, summaryText, date = /* @__PURE__
2639
2775
  const newBlock = buildEntryBlock(sessionId, summaryText, date);
2640
2776
  const allBlocks = [...entries.map((e) => e.raw), newBlock];
2641
2777
  const newContent = (preamble.trimEnd() ? preamble.trimEnd() + "\n\n" : "") + allBlocks.join("\n\n") + "\n";
2642
- const dir = path17.dirname(memoryFilePath);
2643
- if (!fs15.existsSync(dir)) {
2644
- fs15.mkdirSync(dir, { recursive: true });
2778
+ const dir = path18.dirname(memoryFilePath);
2779
+ if (!fs16.existsSync(dir)) {
2780
+ fs16.mkdirSync(dir, { recursive: true });
2645
2781
  }
2646
- fs15.writeFileSync(memoryFilePath, newContent, "utf-8");
2782
+ fs16.writeFileSync(memoryFilePath, newContent, "utf-8");
2647
2783
  return newContent;
2648
2784
  }
2649
2785
  function pruneOldEntries(memoryFilePath, days) {
2650
- if (!fs15.existsSync(memoryFilePath)) {
2786
+ if (!fs16.existsSync(memoryFilePath)) {
2651
2787
  return { removed: 0, kept: 0, removedEntries: [] };
2652
2788
  }
2653
2789
  const { preamble, entries } = parseMemoryFile(memoryFilePath);
@@ -2660,7 +2796,7 @@ function pruneOldEntries(memoryFilePath, days) {
2660
2796
  return { removed: 0, kept: kept.length, removedEntries: [] };
2661
2797
  }
2662
2798
  const newContent = (preamble.trimEnd() ? preamble.trimEnd() + "\n\n" : "") + kept.map((e) => e.raw).join("\n\n") + (kept.length > 0 ? "\n" : "");
2663
- fs15.writeFileSync(memoryFilePath, newContent, "utf-8");
2799
+ fs16.writeFileSync(memoryFilePath, newContent, "utf-8");
2664
2800
  return { removed: removed.length, kept: kept.length, removedEntries: removed };
2665
2801
  }
2666
2802
  function isAlreadyCompressed(memoryFilePath, sessionId) {
@@ -2671,8 +2807,8 @@ function isAlreadyCompressed(memoryFilePath, sessionId) {
2671
2807
  // src/commands/compress.ts
2672
2808
  async function compressCommand(options) {
2673
2809
  const chalk5 = (await import("chalk")).default;
2674
- const projectRoot = options.path ? path18.resolve(options.path) : process.cwd();
2675
- const memoryFilePath = path18.join(projectRoot, "MEMORY.md");
2810
+ const projectRoot = options.path ? path19.resolve(options.path) : process.cwd();
2811
+ const memoryFilePath = path19.join(projectRoot, "MEMORY.md");
2676
2812
  const sessionFiles = listSessionFiles();
2677
2813
  if (sessionFiles.length === 0) {
2678
2814
  process.stdout.write(chalk5.red("No Claude Code sessions found.\n"));
@@ -2697,7 +2833,7 @@ async function compressCommand(options) {
2697
2833
  } else {
2698
2834
  targetFile = sessionFiles[0].filePath;
2699
2835
  }
2700
- const sessionId = path18.basename(targetFile, ".jsonl");
2836
+ const sessionId = path19.basename(targetFile, ".jsonl");
2701
2837
  if (isAlreadyCompressed(memoryFilePath, sessionId)) {
2702
2838
  if (!options.auto) {
2703
2839
  process.stdout.write(chalk5.yellow(`Session ${sessionId.slice(0, 8)}\u2026 is already in MEMORY.md \u2014 skipping.
@@ -2745,11 +2881,27 @@ async function compressCommand(options) {
2745
2881
  }
2746
2882
  if (options.prune) {
2747
2883
  const days = parseInt(options.days ?? "30", 10);
2748
- if (!fs16.existsSync(memoryFilePath)) return;
2884
+ if (!fs17.existsSync(memoryFilePath)) return;
2885
+ if (!options.auto) {
2886
+ let confirmed = true;
2887
+ try {
2888
+ const { confirm: confirm2 } = await import("@inquirer/prompts");
2889
+ confirmed = await confirm2({
2890
+ message: `Prune MEMORY.md entries older than ${days} days? Run 'claudectx revert' to undo.`,
2891
+ default: false
2892
+ });
2893
+ } catch {
2894
+ }
2895
+ if (!confirmed) {
2896
+ process.stdout.write(chalk5.dim("Prune skipped.\n"));
2897
+ return;
2898
+ }
2899
+ }
2900
+ await backupFile(memoryFilePath, "compress");
2749
2901
  const pruned = pruneOldEntries(memoryFilePath, days);
2750
2902
  if (pruned.removed > 0 && !options.auto) {
2751
2903
  process.stdout.write(
2752
- chalk5.dim(`Pruned ${pruned.removed} entr${pruned.removed === 1 ? "y" : "ies"} older than ${days} days.
2904
+ chalk5.dim(`Pruned ${pruned.removed} entr${pruned.removed === 1 ? "y" : "ies"} older than ${days} days. Run 'claudectx revert' to undo.
2753
2905
  `)
2754
2906
  );
2755
2907
  }
@@ -2767,9 +2919,9 @@ init_models();
2767
2919
  function isoDate(d) {
2768
2920
  return d.toISOString().slice(0, 10);
2769
2921
  }
2770
- function calcCost2(inputTokens, outputTokens, model) {
2922
+ function calcCost2(inputTokens, outputTokens, cacheCreationTokens, cacheReadTokens, model) {
2771
2923
  const p = MODEL_PRICING[model];
2772
- return inputTokens / 1e6 * p.inputPerMillion + outputTokens / 1e6 * p.outputPerMillion;
2924
+ return inputTokens / 1e6 * p.inputPerMillion + outputTokens / 1e6 * p.outputPerMillion + cacheCreationTokens / 1e6 * p.cacheWritePerMillion + cacheReadTokens / 1e6 * p.cacheReadPerMillion;
2773
2925
  }
2774
2926
  async function aggregateUsage(days, model = "claude-sonnet-4-6") {
2775
2927
  const now = /* @__PURE__ */ new Date();
@@ -2788,6 +2940,7 @@ async function aggregateUsage(days, model = "claude-sonnet-4-6") {
2788
2940
  inputTokens: 0,
2789
2941
  outputTokens: 0,
2790
2942
  cacheReadTokens: 0,
2943
+ cacheCreationTokens: 0,
2791
2944
  requests: 0,
2792
2945
  costUsd: 0
2793
2946
  });
@@ -2796,20 +2949,23 @@ async function aggregateUsage(days, model = "claude-sonnet-4-6") {
2796
2949
  let totalInput = 0;
2797
2950
  let totalOutput = 0;
2798
2951
  let totalCacheRead = 0;
2952
+ let totalCacheCreation = 0;
2799
2953
  for (const sf of sessionFiles) {
2800
2954
  const dateStr = isoDate(new Date(sf.mtimeMs));
2801
2955
  const bucket = bucketMap.get(dateStr);
2802
2956
  if (!bucket) continue;
2803
- const usage = readSessionUsage(sf.filePath);
2957
+ const usage = await readSessionUsage(sf.filePath);
2804
2958
  bucket.sessions++;
2805
2959
  bucket.inputTokens += usage.inputTokens;
2806
2960
  bucket.outputTokens += usage.outputTokens;
2807
2961
  bucket.cacheReadTokens += usage.cacheReadTokens;
2962
+ bucket.cacheCreationTokens += usage.cacheCreationTokens;
2808
2963
  bucket.requests += usage.requestCount;
2809
- bucket.costUsd += calcCost2(usage.inputTokens, usage.outputTokens, model);
2964
+ bucket.costUsd += calcCost2(usage.inputTokens, usage.outputTokens, usage.cacheCreationTokens, usage.cacheReadTokens, model);
2810
2965
  totalInput += usage.inputTokens;
2811
2966
  totalOutput += usage.outputTokens;
2812
2967
  totalCacheRead += usage.cacheReadTokens;
2968
+ totalCacheCreation += usage.cacheCreationTokens;
2813
2969
  totalRequests += usage.requestCount;
2814
2970
  }
2815
2971
  const fileEvents = readAllEvents().filter(
@@ -2820,10 +2976,12 @@ async function aggregateUsage(days, model = "claude-sonnet-4-6") {
2820
2976
  filePath: s.filePath,
2821
2977
  readCount: s.readCount
2822
2978
  }));
2823
- const totalCost = calcCost2(totalInput, totalOutput, model);
2979
+ const totalCost = calcCost2(totalInput, totalOutput, totalCacheCreation, totalCacheRead, model);
2824
2980
  const cacheHitRate = totalInput > 0 ? Math.round(totalCacheRead / totalInput * 100) : 0;
2825
2981
  const byDay = [...bucketMap.values()].sort((a, b) => a.date.localeCompare(b.date));
2826
2982
  const uniqueSessions = new Set(sessionFiles.map((f) => f.sessionId)).size;
2983
+ const dailyAvgCostUsd = days > 0 ? totalCost / days : 0;
2984
+ const projectedMonthlyUsd = dailyAvgCostUsd * 30;
2827
2985
  return {
2828
2986
  periodDays: days,
2829
2987
  startDate: isoDate(cutoff),
@@ -2833,10 +2991,13 @@ async function aggregateUsage(days, model = "claude-sonnet-4-6") {
2833
2991
  totalInputTokens: totalInput,
2834
2992
  totalOutputTokens: totalOutput,
2835
2993
  totalCacheReadTokens: totalCacheRead,
2994
+ totalCacheCreationTokens: totalCacheCreation,
2836
2995
  cacheHitRate,
2837
2996
  totalCostUsd: totalCost,
2838
2997
  avgCostPerSession: uniqueSessions > 0 ? totalCost / uniqueSessions : 0,
2839
2998
  avgTokensPerRequest: totalRequests > 0 ? Math.round(totalInput / totalRequests) : 0,
2999
+ dailyAvgCostUsd,
3000
+ projectedMonthlyUsd,
2840
3001
  byDay,
2841
3002
  topFiles,
2842
3003
  model,
@@ -2879,9 +3040,12 @@ function formatText(data) {
2879
3040
  lines.push(` Input tokens: ${fmtNum2(data.totalInputTokens)}`);
2880
3041
  lines.push(` Output tokens: ${fmtNum2(data.totalOutputTokens)}`);
2881
3042
  lines.push(` Cache reads: ${fmtNum2(data.totalCacheReadTokens)} (${data.cacheHitRate}% hit rate)`);
3043
+ lines.push(` Cache writes: ${fmtNum2(data.totalCacheCreationTokens)}`);
2882
3044
  lines.push(` Total cost (est.): ${fmtCost2(data.totalCostUsd)}`);
2883
3045
  lines.push(` Avg cost/session: ${fmtCost2(data.avgCostPerSession)}`);
2884
3046
  lines.push(` Avg tokens/request: ${fmtNum2(data.avgTokensPerRequest)}`);
3047
+ lines.push(` Daily avg cost: ${fmtCost2(data.dailyAvgCostUsd)}`);
3048
+ lines.push(` Projected (30-day): ${fmtCost2(data.projectedMonthlyUsd)}`);
2885
3049
  lines.push(` Model: ${data.model}`);
2886
3050
  lines.push("");
2887
3051
  const activeDays = data.byDay.filter((d) => d.sessions > 0);
@@ -2950,9 +3114,12 @@ function formatMarkdown(data) {
2950
3114
  lines.push(`| Input tokens | ${fmtNum2(data.totalInputTokens)} |`);
2951
3115
  lines.push(`| Output tokens | ${fmtNum2(data.totalOutputTokens)} |`);
2952
3116
  lines.push(`| Cache hit rate | ${data.cacheHitRate}% |`);
3117
+ lines.push(`| Cache writes | ${fmtNum2(data.totalCacheCreationTokens)} tokens |`);
2953
3118
  lines.push(`| Total cost (est.) | ${fmtCost2(data.totalCostUsd)} |`);
2954
3119
  lines.push(`| Avg cost/session | ${fmtCost2(data.avgCostPerSession)} |`);
2955
3120
  lines.push(`| Avg tokens/request | ${fmtNum2(data.avgTokensPerRequest)} |`);
3121
+ lines.push(`| Daily avg cost | ${fmtCost2(data.dailyAvgCostUsd)} |`);
3122
+ lines.push(`| Projected (30-day) | ${fmtCost2(data.projectedMonthlyUsd)} |`);
2956
3123
  lines.push(`| Model | \`${data.model}\` |`);
2957
3124
  lines.push("");
2958
3125
  const activeDays = data.byDay.filter((d) => d.sessions > 0);
@@ -3012,12 +3179,12 @@ async function reportCommand(options) {
3012
3179
 
3013
3180
  // src/commands/budget.ts
3014
3181
  init_cjs_shims();
3015
- var path20 = __toESM(require("path"));
3182
+ var path21 = __toESM(require("path"));
3016
3183
 
3017
3184
  // src/analyzer/budget-estimator.ts
3018
3185
  init_cjs_shims();
3019
- var fs17 = __toESM(require("fs"));
3020
- var path19 = __toESM(require("path"));
3186
+ var fs18 = __toESM(require("fs"));
3187
+ var path20 = __toESM(require("path"));
3021
3188
  var import_glob2 = require("glob");
3022
3189
  init_tokenizer();
3023
3190
  init_session_store();
@@ -3043,20 +3210,20 @@ function classifyCacheHit(recentReadCount) {
3043
3210
  return "low";
3044
3211
  }
3045
3212
  function suggestClaudeignoreAdditions(files, projectRoot) {
3046
- const ignorePath = path19.join(projectRoot, ".claudeignore");
3213
+ const ignorePath = path20.join(projectRoot, ".claudeignore");
3047
3214
  let ignorePatterns = [];
3048
3215
  try {
3049
- const content = fs17.readFileSync(ignorePath, "utf-8");
3216
+ const content = fs18.readFileSync(ignorePath, "utf-8");
3050
3217
  ignorePatterns = content.split("\n").filter(Boolean);
3051
3218
  } catch {
3052
3219
  }
3053
3220
  const recommendations = [];
3054
3221
  for (const file of files) {
3055
3222
  if (file.tokenCount <= WASTE_THRESHOLDS.MAX_REFERENCE_FILE_TOKENS) continue;
3056
- const rel = path19.relative(projectRoot, file.filePath);
3223
+ const rel = path20.relative(projectRoot, file.filePath);
3057
3224
  const alreadyIgnored = ignorePatterns.some((pattern) => {
3058
3225
  const cleanPattern = pattern.replace(/^!/, "");
3059
- return rel.startsWith(cleanPattern.replace(/\*/g, "").replace(/\//g, path19.sep));
3226
+ return rel.startsWith(cleanPattern.replace(/\*/g, "").replace(/\//g, path20.sep));
3060
3227
  });
3061
3228
  if (!alreadyIgnored) {
3062
3229
  recommendations.push(rel);
@@ -3076,7 +3243,7 @@ async function estimateBudget(globs, projectRoot, model, thresholdTokens) {
3076
3243
  for (const filePath of filePaths) {
3077
3244
  let content = "";
3078
3245
  try {
3079
- content = fs17.readFileSync(filePath, "utf-8");
3246
+ content = fs18.readFileSync(filePath, "utf-8");
3080
3247
  } catch {
3081
3248
  continue;
3082
3249
  }
@@ -3125,7 +3292,7 @@ function formatBudgetReport(report) {
3125
3292
  }
3126
3293
  const LIKELIHOOD_ICON = { high: "\u{1F7E2}", medium: "\u{1F7E1}", low: "\u{1F534}" };
3127
3294
  const maxPathLen = Math.min(
3128
- Math.max(...report.files.map((f) => path19.basename(f.filePath).length)),
3295
+ Math.max(...report.files.map((f) => path20.basename(f.filePath).length)),
3129
3296
  40
3130
3297
  );
3131
3298
  lines.push(
@@ -3133,7 +3300,7 @@ function formatBudgetReport(report) {
3133
3300
  );
3134
3301
  lines.push("\u2500".repeat(50));
3135
3302
  for (const file of report.files.slice(0, 20)) {
3136
- const name = path19.basename(file.filePath).slice(0, maxPathLen).padEnd(maxPathLen);
3303
+ const name = path20.basename(file.filePath).slice(0, maxPathLen).padEnd(maxPathLen);
3137
3304
  const tokens = file.tokenCount.toLocaleString().padStart(7);
3138
3305
  const cache = `${LIKELIHOOD_ICON[file.cacheHitLikelihood]} ${file.cacheHitLikelihood.padEnd(6)}`;
3139
3306
  const cost = formatCost(file.estimatedCostUsd).padStart(7);
@@ -3162,7 +3329,7 @@ function formatBudgetReport(report) {
3162
3329
  // src/commands/budget.ts
3163
3330
  init_models();
3164
3331
  async function budgetCommand(globs, options) {
3165
- const projectPath = options.path ? path20.resolve(options.path) : process.cwd();
3332
+ const projectPath = options.path ? path21.resolve(options.path) : process.cwd();
3166
3333
  const projectRoot = findProjectRoot(projectPath) ?? projectPath;
3167
3334
  const model = resolveModel(options.model ?? "sonnet");
3168
3335
  const thresholdTokens = parseInt(options.threshold ?? "10000", 10);
@@ -3181,7 +3348,7 @@ async function budgetCommand(globs, options) {
3181
3348
 
3182
3349
  // src/commands/warmup.ts
3183
3350
  init_cjs_shims();
3184
- var path21 = __toESM(require("path"));
3351
+ var path22 = __toESM(require("path"));
3185
3352
  var import_sdk = __toESM(require("@anthropic-ai/sdk"));
3186
3353
  init_models();
3187
3354
  var import_fs3 = __toESM(require("fs"));
@@ -3255,6 +3422,19 @@ async function installCron(cronExpr) {
3255
3422
  );
3256
3423
  process.exit(1);
3257
3424
  }
3425
+ let confirmed = true;
3426
+ try {
3427
+ const { confirm: confirm2 } = await import("@inquirer/prompts");
3428
+ confirmed = await confirm2({
3429
+ message: `Install cron job "${cronExpr} claudectx warmup" in your system crontab?`,
3430
+ default: false
3431
+ });
3432
+ } catch {
3433
+ }
3434
+ if (!confirmed) {
3435
+ process.stdout.write(" Cron install cancelled.\n");
3436
+ return;
3437
+ }
3258
3438
  const { execSync: execSync3 } = await import("child_process");
3259
3439
  const command = `claudectx warmup`;
3260
3440
  const cronLine = `${cronExpr} ${command}`;
@@ -3277,16 +3457,16 @@ async function installCron(cronExpr) {
3277
3457
  ${marker}
3278
3458
  ${cronLine}
3279
3459
  `;
3280
- const { writeFileSync: writeFileSync11, unlinkSync: unlinkSync2 } = await import("fs");
3460
+ const { writeFileSync: writeFileSync12, unlinkSync: unlinkSync3 } = await import("fs");
3281
3461
  const { tmpdir } = await import("os");
3282
- const { join: join17 } = await import("path");
3283
- const tmpFile = join17(tmpdir(), `claudectx-cron-${Date.now()}.txt`);
3462
+ const { join: join18 } = await import("path");
3463
+ const tmpFile = join18(tmpdir(), `claudectx-cron-${Date.now()}.txt`);
3284
3464
  try {
3285
- writeFileSync11(tmpFile, newCrontab, "utf-8");
3465
+ writeFileSync12(tmpFile, newCrontab, "utf-8");
3286
3466
  execSync3(`crontab ${tmpFile}`, { stdio: ["pipe", "pipe", "pipe"] });
3287
3467
  } finally {
3288
3468
  try {
3289
- unlinkSync2(tmpFile);
3469
+ unlinkSync3(tmpFile);
3290
3470
  } catch {
3291
3471
  }
3292
3472
  }
@@ -3303,7 +3483,7 @@ ${cronLine}
3303
3483
  }
3304
3484
  }
3305
3485
  async function warmupCommand(options) {
3306
- const projectPath = options.path ? path21.resolve(options.path) : process.cwd();
3486
+ const projectPath = options.path ? path22.resolve(options.path) : process.cwd();
3307
3487
  const projectRoot = findProjectRoot(projectPath) ?? projectPath;
3308
3488
  const model = resolveModel(options.model ?? "haiku");
3309
3489
  const ttl = options.ttl === "60" ? 60 : 5;
@@ -3315,7 +3495,7 @@ async function warmupCommand(options) {
3315
3495
  process.exit(1);
3316
3496
  }
3317
3497
  let claudeMdContent = "";
3318
- const claudeMdPath = path21.join(projectRoot, "CLAUDE.md");
3498
+ const claudeMdPath = path22.join(projectRoot, "CLAUDE.md");
3319
3499
  try {
3320
3500
  claudeMdContent = import_fs3.default.readFileSync(claudeMdPath, "utf-8");
3321
3501
  } catch {
@@ -3375,13 +3555,13 @@ async function warmupCommand(options) {
3375
3555
 
3376
3556
  // src/commands/drift.ts
3377
3557
  init_cjs_shims();
3378
- var path23 = __toESM(require("path"));
3379
- var fs20 = __toESM(require("fs"));
3558
+ var path24 = __toESM(require("path"));
3559
+ var fs21 = __toESM(require("fs"));
3380
3560
 
3381
3561
  // src/analyzer/drift-detector.ts
3382
3562
  init_cjs_shims();
3383
- var fs19 = __toESM(require("fs"));
3384
- var path22 = __toESM(require("path"));
3563
+ var fs20 = __toESM(require("fs"));
3564
+ var path23 = __toESM(require("path"));
3385
3565
  var childProcess = __toESM(require("child_process"));
3386
3566
  init_tokenizer();
3387
3567
  init_session_store();
@@ -3394,8 +3574,8 @@ function findDeadAtReferences(content, projectRoot) {
3394
3574
  const match = lines[i].match(AT_REF_RE);
3395
3575
  if (!match) continue;
3396
3576
  const ref = match[1].trim();
3397
- const absPath = path22.isAbsolute(ref) ? ref : path22.join(projectRoot, ref);
3398
- if (!fs19.existsSync(absPath)) {
3577
+ const absPath = path23.isAbsolute(ref) ? ref : path23.join(projectRoot, ref);
3578
+ if (!fs20.existsSync(absPath)) {
3399
3579
  const lineText = lines[i];
3400
3580
  issues.push({
3401
3581
  type: "dead-ref",
@@ -3428,15 +3608,15 @@ async function findGitDeletedMentions(content, projectRoot) {
3428
3608
  for (let i = 0; i < lines.length; i++) {
3429
3609
  const line = lines[i];
3430
3610
  for (const deleted of deletedFiles) {
3431
- const basename8 = path22.basename(deleted);
3432
- if (line.includes(basename8) || line.includes(deleted)) {
3611
+ const basename10 = path23.basename(deleted);
3612
+ if (line.includes(basename10) || line.includes(deleted)) {
3433
3613
  issues.push({
3434
3614
  type: "git-deleted",
3435
3615
  line: i + 1,
3436
3616
  text: line.trim(),
3437
3617
  severity: "warning",
3438
3618
  estimatedTokenWaste: countTokens(line),
3439
- suggestion: `References "${basename8}" which was deleted from git. Consider removing this mention.`
3619
+ suggestion: `References "${basename10}" which was deleted from git. Consider removing this mention.`
3440
3620
  });
3441
3621
  break;
3442
3622
  }
@@ -3501,8 +3681,8 @@ function findDeadInlinePaths(content, projectRoot) {
3501
3681
  const rawPath = match[1].trim();
3502
3682
  if (seen.has(rawPath)) continue;
3503
3683
  seen.add(rawPath);
3504
- const absPath = path22.isAbsolute(rawPath) ? rawPath : path22.join(projectRoot, rawPath);
3505
- if (!fs19.existsSync(absPath)) {
3684
+ const absPath = path23.isAbsolute(rawPath) ? rawPath : path23.join(projectRoot, rawPath);
3685
+ if (!fs20.existsSync(absPath)) {
3506
3686
  issues.push({
3507
3687
  type: "dead-inline-path",
3508
3688
  line: i + 1,
@@ -3518,10 +3698,10 @@ function findDeadInlinePaths(content, projectRoot) {
3518
3698
  return issues;
3519
3699
  }
3520
3700
  async function detectDrift(projectRoot, dayWindow) {
3521
- const claudeMdPath = path22.join(projectRoot, "CLAUDE.md");
3701
+ const claudeMdPath = path23.join(projectRoot, "CLAUDE.md");
3522
3702
  let content = "";
3523
3703
  try {
3524
- content = fs19.readFileSync(claudeMdPath, "utf-8");
3704
+ content = fs20.readFileSync(claudeMdPath, "utf-8");
3525
3705
  } catch {
3526
3706
  return {
3527
3707
  claudeMdPath,
@@ -3563,7 +3743,7 @@ var TYPE_LABEL = {
3563
3743
  "dead-inline-path": "Dead path"
3564
3744
  };
3565
3745
  async function driftCommand(options) {
3566
- const projectPath = options.path ? path23.resolve(options.path) : process.cwd();
3746
+ const projectPath = options.path ? path24.resolve(options.path) : process.cwd();
3567
3747
  const projectRoot = findProjectRoot(projectPath) ?? projectPath;
3568
3748
  const dayWindow = parseInt(options.days ?? "30", 10);
3569
3749
  const report = await detectDrift(projectRoot, dayWindow);
@@ -3639,25 +3819,25 @@ async function applyFix(claudeMdPath, issues) {
3639
3819
  process.stdout.write("No lines selected. Nothing changed.\n");
3640
3820
  return;
3641
3821
  }
3642
- const content = fs20.readFileSync(claudeMdPath, "utf-8");
3822
+ const content = fs21.readFileSync(claudeMdPath, "utf-8");
3643
3823
  const lines = content.split("\n");
3644
3824
  const lineSet = new Set(selectedLines.map((l) => l - 1));
3645
3825
  const newLines = lines.filter((_, i) => !lineSet.has(i));
3646
3826
  const newContent = newLines.join("\n");
3647
3827
  const backupPath = `${claudeMdPath}.bak`;
3648
- fs20.writeFileSync(backupPath, content, "utf-8");
3649
- const os5 = await import("os");
3828
+ fs21.writeFileSync(backupPath, content, "utf-8");
3829
+ const os6 = await import("os");
3650
3830
  const tmpPath = `${claudeMdPath}.tmp-${Date.now()}`;
3651
3831
  try {
3652
- fs20.writeFileSync(tmpPath, newContent, "utf-8");
3653
- fs20.renameSync(tmpPath, claudeMdPath);
3832
+ fs21.writeFileSync(tmpPath, newContent, "utf-8");
3833
+ fs21.renameSync(tmpPath, claudeMdPath);
3654
3834
  } catch (err) {
3655
3835
  try {
3656
- fs20.copyFileSync(backupPath, claudeMdPath);
3836
+ fs21.copyFileSync(backupPath, claudeMdPath);
3657
3837
  } catch {
3658
3838
  }
3659
3839
  try {
3660
- fs20.unlinkSync(tmpPath);
3840
+ fs21.unlinkSync(tmpPath);
3661
3841
  } catch {
3662
3842
  }
3663
3843
  process.stderr.write(`Error writing CLAUDE.md: ${err instanceof Error ? err.message : String(err)}
@@ -3665,18 +3845,18 @@ async function applyFix(claudeMdPath, issues) {
3665
3845
  process.exit(1);
3666
3846
  }
3667
3847
  process.stdout.write(`
3668
- \u2713 Removed ${selectedLines.length} line(s) from ${path23.basename(claudeMdPath)}
3848
+ \u2713 Removed ${selectedLines.length} line(s) from ${path24.basename(claudeMdPath)}
3669
3849
  `);
3670
3850
  process.stdout.write(` \u2713 Backup saved to ${backupPath}
3671
3851
 
3672
3852
  `);
3673
- void os5;
3853
+ void os6;
3674
3854
  }
3675
3855
 
3676
3856
  // src/commands/hooks.ts
3677
3857
  init_cjs_shims();
3678
- var fs21 = __toESM(require("fs"));
3679
- var path24 = __toESM(require("path"));
3858
+ var fs22 = __toESM(require("fs"));
3859
+ var path25 = __toESM(require("path"));
3680
3860
 
3681
3861
  // src/hooks/registry.ts
3682
3862
  init_cjs_shims();
@@ -3752,17 +3932,17 @@ function buildHookEntry(def, config) {
3752
3932
 
3753
3933
  // src/commands/hooks.ts
3754
3934
  function readInstalledHooks(projectRoot) {
3755
- const settingsPath = path24.join(projectRoot, ".claude", "settings.local.json");
3756
- if (!fs21.existsSync(settingsPath)) return {};
3935
+ const settingsPath = path25.join(projectRoot, ".claude", "settings.local.json");
3936
+ if (!fs22.existsSync(settingsPath)) return {};
3757
3937
  try {
3758
- return JSON.parse(fs21.readFileSync(settingsPath, "utf-8"));
3938
+ return JSON.parse(fs22.readFileSync(settingsPath, "utf-8"));
3759
3939
  } catch {
3760
3940
  process.stderr.write(
3761
3941
  `Warning: ${settingsPath} exists but contains invalid JSON. Existing settings will be preserved as a backup.
3762
3942
  `
3763
3943
  );
3764
3944
  try {
3765
- fs21.copyFileSync(settingsPath, `${settingsPath}.bak`);
3945
+ fs22.copyFileSync(settingsPath, `${settingsPath}.bak`);
3766
3946
  } catch {
3767
3947
  }
3768
3948
  return {};
@@ -3882,9 +4062,26 @@ async function hooksAdd(name, projectRoot, configPairs) {
3882
4062
  }
3883
4063
  async function hooksRemove(name, projectRoot) {
3884
4064
  const settings = readInstalledHooks(projectRoot);
4065
+ let confirmed = true;
4066
+ try {
4067
+ const { confirm: confirm2 } = await import("@inquirer/prompts");
4068
+ confirmed = await confirm2({
4069
+ message: `Remove hook "${name}"? Run 'claudectx revert' to undo.`,
4070
+ default: false
4071
+ });
4072
+ } catch {
4073
+ }
4074
+ if (!confirmed) {
4075
+ process.stdout.write(" Cancelled.\n\n");
4076
+ return;
4077
+ }
4078
+ const settingsPath = path25.join(projectRoot, ".claude", "settings.local.json");
4079
+ if (fs22.existsSync(settingsPath)) {
4080
+ await backupFile(settingsPath, "hooks");
4081
+ }
3885
4082
  const updated = removeHookByName(settings, name);
3886
4083
  writeHooksSettings(projectRoot, updated);
3887
- process.stdout.write(` \u2713 Hook "${name}" removed.
4084
+ process.stdout.write(` \u2713 Hook "${name}" removed. Run 'claudectx revert --list' to undo.
3888
4085
 
3889
4086
  `);
3890
4087
  }
@@ -3909,7 +4106,7 @@ async function hooksStatus(projectRoot) {
3909
4106
  process.stdout.write("\n");
3910
4107
  }
3911
4108
  async function hooksCommand(subcommand, options) {
3912
- const projectPath = options.path ? path24.resolve(options.path) : process.cwd();
4109
+ const projectPath = options.path ? path25.resolve(options.path) : process.cwd();
3913
4110
  const projectRoot = findProjectRoot(projectPath) ?? projectPath;
3914
4111
  const sub = subcommand ?? "list";
3915
4112
  switch (sub) {
@@ -3948,8 +4145,8 @@ async function hooksCommand(subcommand, options) {
3948
4145
 
3949
4146
  // src/commands/convert.ts
3950
4147
  init_cjs_shims();
3951
- var fs22 = __toESM(require("fs"));
3952
- var path25 = __toESM(require("path"));
4148
+ var fs23 = __toESM(require("fs"));
4149
+ var path26 = __toESM(require("path"));
3953
4150
  function slugify2(text) {
3954
4151
  return text.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
3955
4152
  }
@@ -3998,7 +4195,7 @@ function claudeMdToWindsurf(content) {
3998
4195
  return content.split("\n").filter((line) => !line.match(/^@.+$/)).join("\n").replace(/\n{3,}/g, "\n\n").trim();
3999
4196
  }
4000
4197
  async function convertCommand(options) {
4001
- const projectPath = options.path ? path25.resolve(options.path) : process.cwd();
4198
+ const projectPath = options.path ? path26.resolve(options.path) : process.cwd();
4002
4199
  const projectRoot = findProjectRoot(projectPath) ?? projectPath;
4003
4200
  const from = options.from ?? "claude";
4004
4201
  const to = options.to;
@@ -4007,10 +4204,10 @@ async function convertCommand(options) {
4007
4204
  `);
4008
4205
  process.exit(1);
4009
4206
  }
4010
- const claudeMdPath = path25.join(projectRoot, "CLAUDE.md");
4207
+ const claudeMdPath = path26.join(projectRoot, "CLAUDE.md");
4011
4208
  let content = "";
4012
4209
  try {
4013
- content = fs22.readFileSync(claudeMdPath, "utf-8");
4210
+ content = fs23.readFileSync(claudeMdPath, "utf-8");
4014
4211
  } catch {
4015
4212
  process.stderr.write(`Error: CLAUDE.md not found at ${claudeMdPath}
4016
4213
  `);
@@ -4018,33 +4215,45 @@ async function convertCommand(options) {
4018
4215
  }
4019
4216
  if (to === "cursor") {
4020
4217
  const files = claudeMdToCursorRules(content);
4021
- const targetDir = path25.join(projectRoot, ".cursor", "rules");
4218
+ const targetDir = path26.join(projectRoot, ".cursor", "rules");
4022
4219
  process.stdout.write(`
4023
4220
  Converting CLAUDE.md \u2192 ${files.length} Cursor rule file(s)
4024
-
4025
4221
  `);
4222
+ if (!options.dryRun) {
4223
+ process.stdout.write(` \u26A0 Existing .mdc files will be overwritten. A backup is saved automatically.
4224
+ `);
4225
+ process.stdout.write(` Run 'claudectx revert --list' to see backups.
4226
+ `);
4227
+ }
4228
+ process.stdout.write("\n");
4026
4229
  for (const file of files) {
4027
- const filePath = path25.join(targetDir, file.filename);
4028
- const exists = fs22.existsSync(filePath);
4230
+ const filePath = path26.join(targetDir, file.filename);
4231
+ const exists = fs23.existsSync(filePath);
4029
4232
  const prefix = options.dryRun ? "[dry-run] " : exists ? "[overwrite] " : "";
4030
4233
  process.stdout.write(` ${prefix}\u2192 .cursor/rules/${file.filename}
4031
4234
  `);
4032
4235
  if (!options.dryRun) {
4033
- fs22.mkdirSync(targetDir, { recursive: true });
4034
- fs22.writeFileSync(filePath, file.content, "utf-8");
4236
+ fs23.mkdirSync(targetDir, { recursive: true });
4237
+ if (exists) await backupFile(filePath, "convert");
4238
+ fs23.writeFileSync(filePath, file.content, "utf-8");
4035
4239
  }
4036
4240
  }
4037
4241
  process.stdout.write("\n");
4038
4242
  } else if (to === "copilot") {
4039
4243
  const converted = claudeMdToCopilot(content);
4040
- const targetPath = path25.join(projectRoot, ".github", "copilot-instructions.md");
4041
- const exists = fs22.existsSync(targetPath);
4244
+ const targetPath = path26.join(projectRoot, ".github", "copilot-instructions.md");
4245
+ const exists = fs23.existsSync(targetPath);
4042
4246
  process.stdout.write(`
4043
4247
  Converting CLAUDE.md \u2192 .github/copilot-instructions.md${exists ? " [overwrite]" : ""}
4044
4248
  `);
4249
+ if (!options.dryRun && exists) {
4250
+ process.stdout.write(` \u26A0 Existing file will be overwritten. Run 'claudectx revert --list' to undo.
4251
+ `);
4252
+ }
4045
4253
  if (!options.dryRun) {
4046
- fs22.mkdirSync(path25.dirname(targetPath), { recursive: true });
4047
- fs22.writeFileSync(targetPath, converted, "utf-8");
4254
+ fs23.mkdirSync(path26.dirname(targetPath), { recursive: true });
4255
+ if (exists) await backupFile(targetPath, "convert");
4256
+ fs23.writeFileSync(targetPath, converted, "utf-8");
4048
4257
  process.stdout.write(` \u2713 Written to ${targetPath}
4049
4258
 
4050
4259
  `);
@@ -4055,13 +4264,18 @@ Converting CLAUDE.md \u2192 .github/copilot-instructions.md${exists ? " [overwri
4055
4264
  }
4056
4265
  } else if (to === "windsurf") {
4057
4266
  const converted = claudeMdToWindsurf(content);
4058
- const targetPath = path25.join(projectRoot, ".windsurfrules");
4059
- const exists = fs22.existsSync(targetPath);
4267
+ const targetPath = path26.join(projectRoot, ".windsurfrules");
4268
+ const exists = fs23.existsSync(targetPath);
4060
4269
  process.stdout.write(`
4061
4270
  Converting CLAUDE.md \u2192 .windsurfrules${exists ? " [overwrite]" : ""}
4062
4271
  `);
4272
+ if (!options.dryRun && exists) {
4273
+ process.stdout.write(` \u26A0 Existing file will be overwritten. Run 'claudectx revert --list' to undo.
4274
+ `);
4275
+ }
4063
4276
  if (!options.dryRun) {
4064
- fs22.writeFileSync(targetPath, converted, "utf-8");
4277
+ if (exists) await backupFile(targetPath, "convert");
4278
+ fs23.writeFileSync(targetPath, converted, "utf-8");
4065
4279
  process.stdout.write(` \u2713 Written to ${targetPath}
4066
4280
 
4067
4281
  `);
@@ -4079,15 +4293,15 @@ Converting CLAUDE.md \u2192 .windsurfrules${exists ? " [overwrite]" : ""}
4079
4293
 
4080
4294
  // src/commands/teams.ts
4081
4295
  init_cjs_shims();
4082
- var path27 = __toESM(require("path"));
4083
- var fs24 = __toESM(require("fs"));
4296
+ var path28 = __toESM(require("path"));
4297
+ var fs25 = __toESM(require("fs"));
4084
4298
  init_models();
4085
4299
 
4086
4300
  // src/reporter/team-aggregator.ts
4087
4301
  init_cjs_shims();
4088
- var fs23 = __toESM(require("fs"));
4089
- var path26 = __toESM(require("path"));
4090
- var os4 = __toESM(require("os"));
4302
+ var fs24 = __toESM(require("fs"));
4303
+ var path27 = __toESM(require("path"));
4304
+ var os5 = __toESM(require("os"));
4091
4305
  var childProcess2 = __toESM(require("child_process"));
4092
4306
  init_session_reader();
4093
4307
  init_session_store();
@@ -4098,11 +4312,11 @@ function getDeveloperIdentity() {
4098
4312
  if (email) return email;
4099
4313
  } catch {
4100
4314
  }
4101
- return os4.hostname();
4315
+ return os5.hostname();
4102
4316
  }
4103
- function calcCost3(inputTokens, outputTokens, model) {
4317
+ function calcCost3(inputTokens, outputTokens, cacheCreationTokens, cacheReadTokens, model) {
4104
4318
  const p = MODEL_PRICING[model];
4105
- return inputTokens / 1e6 * p.inputPerMillion + outputTokens / 1e6 * p.outputPerMillion;
4319
+ return inputTokens / 1e6 * p.inputPerMillion + outputTokens / 1e6 * p.outputPerMillion + cacheCreationTokens / 1e6 * p.cacheWritePerMillion + cacheReadTokens / 1e6 * p.cacheReadPerMillion;
4106
4320
  }
4107
4321
  function isoDate2(d) {
4108
4322
  return d.toISOString().slice(0, 10);
@@ -4133,6 +4347,7 @@ async function buildTeamExport(days, model, anonymize) {
4133
4347
  inputTokens: 0,
4134
4348
  outputTokens: 0,
4135
4349
  cacheReadTokens: 0,
4350
+ cacheCreationTokens: 0,
4136
4351
  requests: 0,
4137
4352
  costUsd: 0
4138
4353
  });
@@ -4144,14 +4359,15 @@ async function buildTeamExport(days, model, anonymize) {
4144
4359
  for (const sf of sessionFiles) {
4145
4360
  const dateStr = isoDate2(new Date(sf.mtimeMs));
4146
4361
  const bucket = bucketMap.get(dateStr);
4147
- const usage = readSessionUsage(sf.filePath);
4362
+ const usage = await readSessionUsage(sf.filePath);
4148
4363
  if (bucket) {
4149
4364
  bucket.sessions++;
4150
4365
  bucket.inputTokens += usage.inputTokens;
4151
4366
  bucket.outputTokens += usage.outputTokens;
4152
4367
  bucket.cacheReadTokens += usage.cacheReadTokens;
4368
+ bucket.cacheCreationTokens += usage.cacheCreationTokens;
4153
4369
  bucket.requests += usage.requestCount;
4154
- bucket.costUsd += calcCost3(usage.inputTokens, usage.outputTokens, model);
4370
+ bucket.costUsd += calcCost3(usage.inputTokens, usage.outputTokens, usage.cacheCreationTokens, usage.cacheReadTokens, model);
4155
4371
  }
4156
4372
  totalInput += usage.inputTokens;
4157
4373
  totalOutput += usage.outputTokens;
@@ -4162,7 +4378,7 @@ async function buildTeamExport(days, model, anonymize) {
4162
4378
  (e) => new Date(e.timestamp).getTime() >= cutoffMs
4163
4379
  );
4164
4380
  const topWasteFiles = aggregateStats(fileEvents).slice(0, 10).map((s) => ({ filePath: s.filePath, readCount: s.readCount }));
4165
- const totalCostUsd = calcCost3(totalInput, totalOutput, model);
4381
+ const totalCostUsd = calcCost3(totalInput, totalOutput, 0, totalCacheRead, model);
4166
4382
  const cacheHitRate = totalInput > 0 ? Math.round(totalCacheRead / totalInput * 100) : 0;
4167
4383
  const uniqueSessions = new Set(sessionFiles.map((f) => f.sessionId)).size;
4168
4384
  const developer = {
@@ -4213,19 +4429,19 @@ function aggregateTeamReports(exports2) {
4213
4429
  }
4214
4430
  function writeTeamExport(exportData) {
4215
4431
  const storeDir = getStoreDir();
4216
- if (!fs23.existsSync(storeDir)) fs23.mkdirSync(storeDir, { recursive: true });
4432
+ if (!fs24.existsSync(storeDir)) fs24.mkdirSync(storeDir, { recursive: true });
4217
4433
  const date = isoDate2(/* @__PURE__ */ new Date());
4218
- const filePath = path26.join(storeDir, `team-export-${date}.json`);
4219
- fs23.writeFileSync(filePath, JSON.stringify(exportData, null, 2), "utf-8");
4434
+ const filePath = path27.join(storeDir, `team-export-${date}.json`);
4435
+ fs24.writeFileSync(filePath, JSON.stringify(exportData, null, 2), "utf-8");
4220
4436
  return filePath;
4221
4437
  }
4222
4438
  function readTeamExports(dir) {
4223
4439
  const exports2 = [];
4224
- if (!fs23.existsSync(dir)) return exports2;
4225
- const files = fs23.readdirSync(dir).filter((f) => f.match(/^team-export-.*\.json$/));
4440
+ if (!fs24.existsSync(dir)) return exports2;
4441
+ const files = fs24.readdirSync(dir).filter((f) => f.match(/^team-export-.*\.json$/));
4226
4442
  for (const file of files) {
4227
4443
  try {
4228
- const raw = fs23.readFileSync(path26.join(dir, file), "utf-8");
4444
+ const raw = fs24.readFileSync(path27.join(dir, file), "utf-8");
4229
4445
  exports2.push(JSON.parse(raw));
4230
4446
  } catch {
4231
4447
  }
@@ -4312,7 +4528,7 @@ Run "claudectx teams export" on each developer machine first.
4312
4528
  process.stdout.write(" Top shared files (by read count across team):\n");
4313
4529
  for (const f of report.topWasteFiles.slice(0, 5)) {
4314
4530
  const devList = f.developers.slice(0, 3).join(", ");
4315
- process.stdout.write(` ${f.readCount}x ${path27.basename(f.filePath)} (${devList})
4531
+ process.stdout.write(` ${f.readCount}x ${path28.basename(f.filePath)} (${devList})
4316
4532
  `);
4317
4533
  }
4318
4534
  process.stdout.write("\n");
@@ -4325,24 +4541,24 @@ async function teamsShare(options) {
4325
4541
  process.exit(1);
4326
4542
  }
4327
4543
  const storeDir = getStoreDir();
4328
- const exportFiles = fs24.readdirSync(storeDir).filter((f) => f.match(/^team-export-.*\.json$/)).sort().reverse();
4544
+ const exportFiles = fs25.readdirSync(storeDir).filter((f) => f.match(/^team-export-.*\.json$/)).sort().reverse();
4329
4545
  if (exportFiles.length === 0) {
4330
4546
  process.stderr.write('No team export files found. Run "claudectx teams export" first.\n');
4331
4547
  process.exit(1);
4332
4548
  }
4333
4549
  const latest = exportFiles[0];
4334
- const src = path27.join(storeDir, latest);
4550
+ const src = path28.join(storeDir, latest);
4335
4551
  let destPath;
4336
4552
  try {
4337
- const stat = fs24.statSync(dest);
4338
- destPath = stat.isDirectory() ? path27.join(dest, latest) : dest;
4553
+ const stat = fs25.statSync(dest);
4554
+ destPath = stat.isDirectory() ? path28.join(dest, latest) : dest;
4339
4555
  } catch {
4340
4556
  destPath = dest;
4341
4557
  }
4342
- const destDir = path27.dirname(path27.resolve(destPath));
4558
+ const destDir = path28.dirname(path28.resolve(destPath));
4343
4559
  let resolvedDir;
4344
4560
  try {
4345
- resolvedDir = fs24.realpathSync(destDir);
4561
+ resolvedDir = fs25.realpathSync(destDir);
4346
4562
  } catch {
4347
4563
  resolvedDir = destDir;
4348
4564
  }
@@ -4352,7 +4568,7 @@ async function teamsShare(options) {
4352
4568
  `);
4353
4569
  process.exit(1);
4354
4570
  }
4355
- fs24.copyFileSync(src, destPath);
4571
+ fs25.copyFileSync(src, destPath);
4356
4572
  process.stdout.write(` \u2713 Copied ${latest} \u2192 ${destPath}
4357
4573
 
4358
4574
  `);
@@ -4377,8 +4593,148 @@ async function teamsCommand(subcommand, options) {
4377
4593
  }
4378
4594
  }
4379
4595
 
4596
+ // src/commands/revert.ts
4597
+ init_cjs_shims();
4598
+ var path29 = __toESM(require("path"));
4599
+ function timeAgo(isoString) {
4600
+ const diffMs = Date.now() - new Date(isoString).getTime();
4601
+ const minutes = Math.floor(diffMs / 6e4);
4602
+ if (minutes < 1) return "just now";
4603
+ if (minutes < 60) return `${minutes} min ago`;
4604
+ const hours = Math.floor(minutes / 60);
4605
+ if (hours < 24) return `${hours} hour${hours === 1 ? "" : "s"} ago`;
4606
+ const days = Math.floor(hours / 24);
4607
+ return `${days} day${days === 1 ? "" : "s"} ago`;
4608
+ }
4609
+ function formatBytes(bytes) {
4610
+ if (bytes < 1024) return `${bytes} B`;
4611
+ return `${Math.round(bytes / 1024)} KB`;
4612
+ }
4613
+ function printBackupTable(entries) {
4614
+ if (entries.length === 0) {
4615
+ process.stdout.write(" No backups found.\n");
4616
+ process.stdout.write(` Backups are created automatically when claudectx modifies your files.
4617
+ `);
4618
+ process.stdout.write(` Backup directory: ${BACKUP_DIR}
4619
+
4620
+ `);
4621
+ return;
4622
+ }
4623
+ const idWidth = 26;
4624
+ const fileWidth = 20;
4625
+ const cmdWidth = 10;
4626
+ const timeWidth = 14;
4627
+ const sizeWidth = 7;
4628
+ const hr = "\u2550".repeat(idWidth + fileWidth + cmdWidth + timeWidth + sizeWidth + 16);
4629
+ process.stdout.write("\n");
4630
+ process.stdout.write("claudectx \u2014 Backup History\n");
4631
+ process.stdout.write(hr + "\n");
4632
+ process.stdout.write(
4633
+ ` ${"ID".padEnd(idWidth)} ${"File".padEnd(fileWidth)} ${"Command".padEnd(cmdWidth)} ${"When".padEnd(timeWidth)} ${"Size".padEnd(sizeWidth)}
4634
+ `
4635
+ );
4636
+ process.stdout.write("\u2500".repeat(idWidth + fileWidth + cmdWidth + timeWidth + sizeWidth + 16) + "\n");
4637
+ for (const entry of entries) {
4638
+ const id = entry.id.slice(0, idWidth).padEnd(idWidth);
4639
+ const file = path29.basename(entry.originalPath).slice(0, fileWidth).padEnd(fileWidth);
4640
+ const cmd = entry.command.slice(0, cmdWidth).padEnd(cmdWidth);
4641
+ const when = timeAgo(entry.createdAt).slice(0, timeWidth).padEnd(timeWidth);
4642
+ const size = formatBytes(entry.sizeBytes).padEnd(sizeWidth);
4643
+ process.stdout.write(` ${id} ${file} ${cmd} ${when} ${size}
4644
+ `);
4645
+ }
4646
+ process.stdout.write("\n");
4647
+ process.stdout.write(` Backup directory: ${BACKUP_DIR}
4648
+ `);
4649
+ process.stdout.write(" To restore: claudectx revert --id <ID>\n\n");
4650
+ }
4651
+ async function interactivePick(entries) {
4652
+ try {
4653
+ const { select } = await import("@inquirer/prompts");
4654
+ const choices = entries.map((e) => ({
4655
+ name: `${timeAgo(e.createdAt).padEnd(14)} ${path29.basename(e.originalPath).padEnd(16)} [${e.command}] ${e.id}`,
4656
+ value: e.id
4657
+ }));
4658
+ choices.push({ name: "Cancel", value: "" });
4659
+ return await select({ message: "Choose a backup to restore:", choices });
4660
+ } catch {
4661
+ process.stderr.write("Interactive mode unavailable. Use --id <id> to restore a specific backup.\n");
4662
+ return null;
4663
+ }
4664
+ }
4665
+ async function doRestore(id) {
4666
+ const chalk5 = (await import("chalk")).default;
4667
+ process.stdout.write("\n");
4668
+ try {
4669
+ const entries = await listBackups();
4670
+ const entry = entries.find((e) => e.id === id);
4671
+ if (!entry) {
4672
+ process.stderr.write(chalk5.red(`Backup "${id}" not found.
4673
+ `));
4674
+ process.stderr.write('Run "claudectx revert --list" to see available backups.\n');
4675
+ process.exitCode = 1;
4676
+ return;
4677
+ }
4678
+ process.stdout.write(chalk5.yellow(`\u26A0 This will overwrite: ${entry.originalPath}
4679
+ `));
4680
+ process.stdout.write(` Backup from: ${timeAgo(entry.createdAt)} (${entry.command})
4681
+ `);
4682
+ process.stdout.write(` Your current file will be backed up first (so you can undo this).
4683
+
4684
+ `);
4685
+ let confirmed = true;
4686
+ try {
4687
+ const { confirm: confirm2 } = await import("@inquirer/prompts");
4688
+ confirmed = await confirm2({ message: "Restore this backup?", default: false });
4689
+ } catch {
4690
+ }
4691
+ if (!confirmed) {
4692
+ process.stdout.write(" Cancelled.\n\n");
4693
+ return;
4694
+ }
4695
+ const { undoEntry } = await restoreBackup(id);
4696
+ process.stdout.write(chalk5.green(" \u2713 ") + `Restored to ${entry.originalPath}
4697
+ `);
4698
+ if (undoEntry) {
4699
+ process.stdout.write(
4700
+ chalk5.dim(` Your previous version was saved as backup "${undoEntry.id}" \u2014 run 'claudectx revert --id ${undoEntry.id}' to undo.
4701
+ `)
4702
+ );
4703
+ }
4704
+ process.stdout.write("\n");
4705
+ } catch (err) {
4706
+ process.stderr.write(chalk5.red(`Error: ${err instanceof Error ? err.message : String(err)}
4707
+ `));
4708
+ process.exitCode = 1;
4709
+ }
4710
+ }
4711
+ async function revertCommand(options) {
4712
+ const entries = await listBackups(options.file);
4713
+ if (options.json) {
4714
+ process.stdout.write(JSON.stringify({ backups: entries }, null, 2) + "\n");
4715
+ return;
4716
+ }
4717
+ if (options.list) {
4718
+ printBackupTable(entries);
4719
+ return;
4720
+ }
4721
+ if (options.id) {
4722
+ await doRestore(options.id);
4723
+ return;
4724
+ }
4725
+ if (entries.length === 0) {
4726
+ process.stdout.write("\n No backups found. Backups are created automatically when claudectx modifies your files.\n\n");
4727
+ return;
4728
+ }
4729
+ printBackupTable(entries);
4730
+ const picked = await interactivePick(entries);
4731
+ if (picked) {
4732
+ await doRestore(picked);
4733
+ }
4734
+ }
4735
+
4380
4736
  // src/index.ts
4381
- var VERSION = "1.0.0";
4737
+ var VERSION = "1.1.3";
4382
4738
  var DESCRIPTION = "Reduce Claude Code token usage by up to 80%. Context analyzer, auto-optimizer, live dashboard, and smart MCP tools.";
4383
4739
  var program = new import_commander.Command();
4384
4740
  program.name("claudectx").description(DESCRIPTION).version(VERSION);
@@ -4418,5 +4774,8 @@ program.command("convert").description("Convert CLAUDE.md to another AI assistan
4418
4774
  program.command("teams [subcommand]").description("Multi-developer cost attribution (export | aggregate | share)").option("--days <n>", "Days to include", "30").option("-m, --model <model>", "Model", "sonnet").option("--anonymize", "Replace identities with Dev 1, Dev 2...").option("--dir <path>", "Directory with team export JSON files").option("--to <path>", "Destination for share sub-command").option("--json", "JSON output").action(async (subcommand, options) => {
4419
4775
  await teamsCommand(subcommand ?? "export", options);
4420
4776
  });
4777
+ program.command("revert").description("List and restore backups created automatically by claudectx commands").option("--list", "Show all backups").option("--id <id>", "Restore a specific backup by ID").option("--file <path>", "Filter backups by original file path").option("--json", "JSON output").action(async (options) => {
4778
+ await revertCommand(options);
4779
+ });
4421
4780
  program.parse();
4422
4781
  //# sourceMappingURL=index.js.map