claudectx 1.1.2 → 1.1.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +73 -2
- package/dist/index.js +584 -233
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +579 -228
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/dist/index.mjs
CHANGED
|
@@ -104,19 +104,19 @@ __export(session_store_exports, {
|
|
|
104
104
|
getStoreDir: () => getStoreDir,
|
|
105
105
|
readAllEvents: () => readAllEvents
|
|
106
106
|
});
|
|
107
|
-
import * as
|
|
108
|
-
import * as
|
|
109
|
-
import * as
|
|
107
|
+
import * as fs9 from "fs";
|
|
108
|
+
import * as os3 from "os";
|
|
109
|
+
import * as path10 from "path";
|
|
110
110
|
function getStoreDirPath() {
|
|
111
|
-
return
|
|
111
|
+
return path10.join(os3.homedir(), ".claudectx");
|
|
112
112
|
}
|
|
113
113
|
function getReadsFilePath_() {
|
|
114
|
-
return
|
|
114
|
+
return path10.join(getStoreDirPath(), "reads.jsonl");
|
|
115
115
|
}
|
|
116
116
|
function ensureStoreDir() {
|
|
117
117
|
const dir = getStoreDirPath();
|
|
118
|
-
if (!
|
|
119
|
-
|
|
118
|
+
if (!fs9.existsSync(dir)) {
|
|
119
|
+
fs9.mkdirSync(dir, { recursive: true });
|
|
120
120
|
}
|
|
121
121
|
}
|
|
122
122
|
function appendFileRead(filePath, sessionId) {
|
|
@@ -126,12 +126,12 @@ function appendFileRead(filePath, sessionId) {
|
|
|
126
126
|
filePath,
|
|
127
127
|
sessionId
|
|
128
128
|
};
|
|
129
|
-
|
|
129
|
+
fs9.appendFileSync(getReadsFilePath_(), JSON.stringify(event) + "\n", "utf-8");
|
|
130
130
|
}
|
|
131
131
|
function readAllEvents() {
|
|
132
132
|
const readsFile = getReadsFilePath_();
|
|
133
|
-
if (!
|
|
134
|
-
const lines =
|
|
133
|
+
if (!fs9.existsSync(readsFile)) return [];
|
|
134
|
+
const lines = fs9.readFileSync(readsFile, "utf-8").trim().split("\n").filter(Boolean);
|
|
135
135
|
return lines.map((line) => {
|
|
136
136
|
try {
|
|
137
137
|
return JSON.parse(line);
|
|
@@ -160,8 +160,8 @@ function aggregateStats(events) {
|
|
|
160
160
|
}
|
|
161
161
|
function clearStore() {
|
|
162
162
|
const readsFile = getReadsFilePath_();
|
|
163
|
-
if (
|
|
164
|
-
|
|
163
|
+
if (fs9.existsSync(readsFile)) {
|
|
164
|
+
fs9.writeFileSync(readsFile, "", "utf-8");
|
|
165
165
|
}
|
|
166
166
|
}
|
|
167
167
|
function getReadsFilePath() {
|
|
@@ -178,28 +178,28 @@ var init_session_store = __esm({
|
|
|
178
178
|
});
|
|
179
179
|
|
|
180
180
|
// src/watcher/session-reader.ts
|
|
181
|
-
import * as
|
|
182
|
-
import * as
|
|
183
|
-
import * as
|
|
181
|
+
import * as fs10 from "fs";
|
|
182
|
+
import * as os4 from "os";
|
|
183
|
+
import * as path11 from "path";
|
|
184
184
|
function listSessionFiles() {
|
|
185
|
-
if (!
|
|
185
|
+
if (!fs10.existsSync(CLAUDE_PROJECTS_DIR)) return [];
|
|
186
186
|
const results = [];
|
|
187
187
|
try {
|
|
188
|
-
const projectDirs =
|
|
188
|
+
const projectDirs = fs10.readdirSync(CLAUDE_PROJECTS_DIR);
|
|
189
189
|
for (const projectDir of projectDirs) {
|
|
190
|
-
const projectPath =
|
|
190
|
+
const projectPath = path11.join(CLAUDE_PROJECTS_DIR, projectDir);
|
|
191
191
|
try {
|
|
192
|
-
const stat =
|
|
192
|
+
const stat = fs10.statSync(projectPath);
|
|
193
193
|
if (!stat.isDirectory()) continue;
|
|
194
|
-
const files =
|
|
194
|
+
const files = fs10.readdirSync(projectPath).filter((f) => f.endsWith(".jsonl"));
|
|
195
195
|
for (const file of files) {
|
|
196
|
-
const filePath =
|
|
196
|
+
const filePath = path11.join(projectPath, file);
|
|
197
197
|
try {
|
|
198
|
-
const fstat =
|
|
198
|
+
const fstat = fs10.statSync(filePath);
|
|
199
199
|
results.push({
|
|
200
200
|
filePath,
|
|
201
201
|
mtimeMs: fstat.mtimeMs,
|
|
202
|
-
sessionId:
|
|
202
|
+
sessionId: path11.basename(file, ".jsonl"),
|
|
203
203
|
projectDir
|
|
204
204
|
});
|
|
205
205
|
} catch {
|
|
@@ -230,7 +230,7 @@ async function readSessionUsage(sessionFilePath) {
|
|
|
230
230
|
cacheReadTokens: 0,
|
|
231
231
|
requestCount: 0
|
|
232
232
|
};
|
|
233
|
-
if (!
|
|
233
|
+
if (!fs10.existsSync(sessionFilePath)) return result;
|
|
234
234
|
const { createReadStream } = await import("fs");
|
|
235
235
|
const { createInterface } = await import("readline");
|
|
236
236
|
try {
|
|
@@ -265,7 +265,7 @@ var init_session_reader = __esm({
|
|
|
265
265
|
"src/watcher/session-reader.ts"() {
|
|
266
266
|
"use strict";
|
|
267
267
|
init_esm_shims();
|
|
268
|
-
CLAUDE_PROJECTS_DIR =
|
|
268
|
+
CLAUDE_PROJECTS_DIR = path11.join(os4.homedir(), ".claude", "projects");
|
|
269
269
|
}
|
|
270
270
|
});
|
|
271
271
|
|
|
@@ -276,8 +276,8 @@ __export(Dashboard_exports, {
|
|
|
276
276
|
});
|
|
277
277
|
import { useState, useEffect, useCallback } from "react";
|
|
278
278
|
import { Box, Text, useApp, useInput } from "ink";
|
|
279
|
-
import * as
|
|
280
|
-
import * as
|
|
279
|
+
import * as fs11 from "fs";
|
|
280
|
+
import * as path12 from "path";
|
|
281
281
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
282
282
|
function fmtNum(n) {
|
|
283
283
|
return n.toLocaleString();
|
|
@@ -288,7 +288,7 @@ function fmtCost(tokens, model) {
|
|
|
288
288
|
return `$${cost.toFixed(4)}`;
|
|
289
289
|
}
|
|
290
290
|
function shortPath(filePath) {
|
|
291
|
-
const parts = filePath.split(
|
|
291
|
+
const parts = filePath.split(path12.sep);
|
|
292
292
|
if (parts.length <= 3) return filePath;
|
|
293
293
|
return "\u2026/" + parts.slice(-3).join("/");
|
|
294
294
|
}
|
|
@@ -410,9 +410,9 @@ function Dashboard({
|
|
|
410
410
|
const readsFile = getReadsFilePath();
|
|
411
411
|
let watcher = null;
|
|
412
412
|
const tryWatch = () => {
|
|
413
|
-
if (
|
|
413
|
+
if (fs11.existsSync(readsFile)) {
|
|
414
414
|
try {
|
|
415
|
-
watcher =
|
|
415
|
+
watcher = fs11.watch(readsFile, () => refresh());
|
|
416
416
|
} catch {
|
|
417
417
|
}
|
|
418
418
|
}
|
|
@@ -450,7 +450,7 @@ function Dashboard({
|
|
|
450
450
|
] }),
|
|
451
451
|
sessionFile && /* @__PURE__ */ jsxs(Text, { dimColor: true, children: [
|
|
452
452
|
" \u2014 ",
|
|
453
|
-
|
|
453
|
+
path12.basename(sessionFile, ".jsonl").slice(0, 8),
|
|
454
454
|
"\u2026"
|
|
455
455
|
] }),
|
|
456
456
|
!sessionFile && /* @__PURE__ */ jsx(Text, { dimColor: true, children: " \u2014 no session file found" })
|
|
@@ -477,10 +477,10 @@ var init_Dashboard = __esm({
|
|
|
477
477
|
});
|
|
478
478
|
|
|
479
479
|
// src/mcp/smart-reader.ts
|
|
480
|
-
import * as
|
|
481
|
-
import * as
|
|
480
|
+
import * as fs13 from "fs";
|
|
481
|
+
import * as path15 from "path";
|
|
482
482
|
function detectLanguage(filePath) {
|
|
483
|
-
const ext =
|
|
483
|
+
const ext = path15.extname(filePath).toLowerCase();
|
|
484
484
|
switch (ext) {
|
|
485
485
|
case ".ts":
|
|
486
486
|
case ".tsx":
|
|
@@ -497,8 +497,8 @@ function detectLanguage(filePath) {
|
|
|
497
497
|
}
|
|
498
498
|
}
|
|
499
499
|
function findSymbol(filePath, symbolName) {
|
|
500
|
-
if (!
|
|
501
|
-
const content =
|
|
500
|
+
if (!fs13.existsSync(filePath)) return null;
|
|
501
|
+
const content = fs13.readFileSync(filePath, "utf-8");
|
|
502
502
|
const lines = content.split("\n");
|
|
503
503
|
const lang = detectLanguage(filePath);
|
|
504
504
|
const patterns = lang === "python" ? PYTHON_PATTERNS : TS_JS_PATTERNS;
|
|
@@ -559,8 +559,8 @@ function findPythonBlockEnd(lines, startIdx) {
|
|
|
559
559
|
return lines.length;
|
|
560
560
|
}
|
|
561
561
|
function extractLineRange(filePath, startLine, endLine, contextLines = 0) {
|
|
562
|
-
if (!
|
|
563
|
-
const allLines =
|
|
562
|
+
if (!fs13.existsSync(filePath)) return null;
|
|
563
|
+
const allLines = fs13.readFileSync(filePath, "utf-8").split("\n");
|
|
564
564
|
const totalLines = allLines.length;
|
|
565
565
|
const from = Math.max(0, startLine - 1 - contextLines);
|
|
566
566
|
const to = Math.min(totalLines, endLine + contextLines);
|
|
@@ -575,7 +575,7 @@ function extractLineRange(filePath, startLine, endLine, contextLines = 0) {
|
|
|
575
575
|
};
|
|
576
576
|
}
|
|
577
577
|
function smartRead(filePath, symbol, startLine, endLine, contextLines = 3) {
|
|
578
|
-
if (!
|
|
578
|
+
if (!fs13.existsSync(filePath)) {
|
|
579
579
|
throw new Error(`File not found: ${filePath}`);
|
|
580
580
|
}
|
|
581
581
|
if (symbol) {
|
|
@@ -587,7 +587,7 @@ function smartRead(filePath, symbol, startLine, endLine, contextLines = 3) {
|
|
|
587
587
|
filePath,
|
|
588
588
|
startLine: extracted.startLine,
|
|
589
589
|
endLine: extracted.endLine,
|
|
590
|
-
totalLines:
|
|
590
|
+
totalLines: fs13.readFileSync(filePath, "utf-8").split("\n").length,
|
|
591
591
|
truncated: false,
|
|
592
592
|
symbolName: symbol
|
|
593
593
|
};
|
|
@@ -599,7 +599,7 @@ function smartRead(filePath, symbol, startLine, endLine, contextLines = 3) {
|
|
|
599
599
|
return { ...result, truncated: false };
|
|
600
600
|
}
|
|
601
601
|
}
|
|
602
|
-
const fullContent =
|
|
602
|
+
const fullContent = fs13.readFileSync(filePath, "utf-8");
|
|
603
603
|
const allLines = fullContent.split("\n");
|
|
604
604
|
const totalLines = allLines.length;
|
|
605
605
|
const fullTokens = countTokens(fullContent);
|
|
@@ -673,15 +673,15 @@ var init_smart_reader = __esm({
|
|
|
673
673
|
});
|
|
674
674
|
|
|
675
675
|
// src/mcp/symbol-index.ts
|
|
676
|
-
import * as
|
|
677
|
-
import * as
|
|
676
|
+
import * as fs14 from "fs";
|
|
677
|
+
import * as path16 from "path";
|
|
678
678
|
import { glob } from "glob";
|
|
679
679
|
function extractSymbolsFromFile(filePath) {
|
|
680
680
|
const lang = detectLanguage(filePath);
|
|
681
681
|
if (lang === "other") return [];
|
|
682
682
|
let content;
|
|
683
683
|
try {
|
|
684
|
-
content =
|
|
684
|
+
content = fs14.readFileSync(filePath, "utf-8");
|
|
685
685
|
} catch {
|
|
686
686
|
return [];
|
|
687
687
|
}
|
|
@@ -766,8 +766,8 @@ var init_symbol_index = __esm({
|
|
|
766
766
|
this.entries = [];
|
|
767
767
|
let files = [];
|
|
768
768
|
try {
|
|
769
|
-
files = await glob(SOURCE_GLOBS.map((g) =>
|
|
770
|
-
ignore: IGNORE_DIRS.map((g) =>
|
|
769
|
+
files = await glob(SOURCE_GLOBS.map((g) => path16.join(projectRoot, g)), {
|
|
770
|
+
ignore: IGNORE_DIRS.map((g) => path16.join(projectRoot, g)),
|
|
771
771
|
absolute: true
|
|
772
772
|
});
|
|
773
773
|
} catch {
|
|
@@ -820,7 +820,7 @@ var server_exports = {};
|
|
|
820
820
|
__export(server_exports, {
|
|
821
821
|
startMcpServer: () => startMcpServer
|
|
822
822
|
});
|
|
823
|
-
import * as
|
|
823
|
+
import * as path17 from "path";
|
|
824
824
|
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
825
825
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
826
826
|
import {
|
|
@@ -828,7 +828,7 @@ import {
|
|
|
828
828
|
ListToolsRequestSchema
|
|
829
829
|
} from "@modelcontextprotocol/sdk/types.js";
|
|
830
830
|
function handleSmartRead(args) {
|
|
831
|
-
const filePath =
|
|
831
|
+
const filePath = path17.resolve(args.file);
|
|
832
832
|
const result = smartRead(
|
|
833
833
|
filePath,
|
|
834
834
|
args.symbol,
|
|
@@ -864,7 +864,7 @@ Example: index_project({ "project_root": "${process.cwd()}" })`;
|
|
|
864
864
|
];
|
|
865
865
|
for (let i = 0; i < results.length; i++) {
|
|
866
866
|
const r = results[i];
|
|
867
|
-
const rel =
|
|
867
|
+
const rel = path17.relative(process.cwd(), r.filePath);
|
|
868
868
|
lines.push(`${i + 1}. [${r.type}] ${r.name}`);
|
|
869
869
|
lines.push(` ${rel}:${r.lineStart}`);
|
|
870
870
|
lines.push(` ${r.signature.trim()}`);
|
|
@@ -876,7 +876,7 @@ Example: index_project({ "project_root": "${process.cwd()}" })`;
|
|
|
876
876
|
return lines.join("\n");
|
|
877
877
|
}
|
|
878
878
|
async function handleIndexProject(args) {
|
|
879
|
-
const projectRoot = args.project_root ?
|
|
879
|
+
const projectRoot = args.project_root ? path17.resolve(args.project_root) : process.cwd();
|
|
880
880
|
const fn = args.rebuild ? () => globalIndex.rebuild(projectRoot) : () => globalIndex.build(projectRoot);
|
|
881
881
|
const { fileCount, symbolCount } = await fn();
|
|
882
882
|
if (fileCount === 0 && globalIndex.isReady) {
|
|
@@ -1563,8 +1563,8 @@ async function analyzeCommand(options) {
|
|
|
1563
1563
|
|
|
1564
1564
|
// src/commands/optimize.ts
|
|
1565
1565
|
init_esm_shims();
|
|
1566
|
-
import * as
|
|
1567
|
-
import * as
|
|
1566
|
+
import * as fs8 from "fs";
|
|
1567
|
+
import * as path9 from "path";
|
|
1568
1568
|
import chalk3 from "chalk";
|
|
1569
1569
|
import boxen2 from "boxen";
|
|
1570
1570
|
import { checkbox, confirm } from "@inquirer/prompts";
|
|
@@ -1728,8 +1728,131 @@ function writeIgnorefile(result) {
|
|
|
1728
1728
|
// src/optimizer/claudemd-splitter.ts
|
|
1729
1729
|
init_esm_shims();
|
|
1730
1730
|
init_tokenizer();
|
|
1731
|
+
import * as fs5 from "fs";
|
|
1732
|
+
import * as path7 from "path";
|
|
1733
|
+
|
|
1734
|
+
// src/shared/backup-manager.ts
|
|
1735
|
+
init_esm_shims();
|
|
1731
1736
|
import * as fs4 from "fs";
|
|
1737
|
+
import * as os2 from "os";
|
|
1732
1738
|
import * as path6 from "path";
|
|
1739
|
+
var MAX_BACKUPS = 50;
|
|
1740
|
+
var _backupDirOverride = null;
|
|
1741
|
+
function getBackupDir() {
|
|
1742
|
+
return _backupDirOverride ?? path6.join(os2.homedir(), ".claudectx", "backups");
|
|
1743
|
+
}
|
|
1744
|
+
var BACKUP_DIR = path6.join(os2.homedir(), ".claudectx", "backups");
|
|
1745
|
+
function ensureBackupDir() {
|
|
1746
|
+
const dir = getBackupDir();
|
|
1747
|
+
if (!fs4.existsSync(dir)) {
|
|
1748
|
+
fs4.mkdirSync(dir, { recursive: true });
|
|
1749
|
+
}
|
|
1750
|
+
}
|
|
1751
|
+
function getManifestPath() {
|
|
1752
|
+
return path6.join(getBackupDir(), "manifest.json");
|
|
1753
|
+
}
|
|
1754
|
+
function readManifest() {
|
|
1755
|
+
ensureBackupDir();
|
|
1756
|
+
const manifestPath = getManifestPath();
|
|
1757
|
+
if (!fs4.existsSync(manifestPath)) {
|
|
1758
|
+
return { version: "1", entries: [] };
|
|
1759
|
+
}
|
|
1760
|
+
try {
|
|
1761
|
+
return JSON.parse(fs4.readFileSync(manifestPath, "utf-8"));
|
|
1762
|
+
} catch {
|
|
1763
|
+
return { version: "1", entries: [] };
|
|
1764
|
+
}
|
|
1765
|
+
}
|
|
1766
|
+
function writeManifest(manifest) {
|
|
1767
|
+
ensureBackupDir();
|
|
1768
|
+
const manifestPath = getManifestPath();
|
|
1769
|
+
const tmpPath = `${manifestPath}.tmp-${Date.now()}`;
|
|
1770
|
+
try {
|
|
1771
|
+
fs4.writeFileSync(tmpPath, JSON.stringify(manifest, null, 2) + "\n", "utf-8");
|
|
1772
|
+
fs4.renameSync(tmpPath, manifestPath);
|
|
1773
|
+
} catch {
|
|
1774
|
+
try {
|
|
1775
|
+
fs4.unlinkSync(tmpPath);
|
|
1776
|
+
} catch {
|
|
1777
|
+
}
|
|
1778
|
+
throw new Error(`Failed to write backup manifest at ${manifestPath}`);
|
|
1779
|
+
}
|
|
1780
|
+
}
|
|
1781
|
+
function generateId(originalPath) {
|
|
1782
|
+
const now = /* @__PURE__ */ new Date();
|
|
1783
|
+
const ts = now.toISOString().replace(/[-:]/g, "").replace("T", "T").replace(".", "m").replace("Z", "");
|
|
1784
|
+
const rand = Math.floor(Math.random() * 9e3 + 1e3);
|
|
1785
|
+
const basename10 = path6.basename(originalPath);
|
|
1786
|
+
return `${ts}-${rand}-${basename10}`;
|
|
1787
|
+
}
|
|
1788
|
+
async function backupFile(filePath, command) {
|
|
1789
|
+
const resolved = path6.resolve(filePath);
|
|
1790
|
+
if (!fs4.existsSync(resolved)) {
|
|
1791
|
+
throw new Error(`Cannot back up "${resolved}": file does not exist.`);
|
|
1792
|
+
}
|
|
1793
|
+
ensureBackupDir();
|
|
1794
|
+
const id = generateId(resolved);
|
|
1795
|
+
const backupPath = path6.join(getBackupDir(), id);
|
|
1796
|
+
fs4.copyFileSync(resolved, backupPath);
|
|
1797
|
+
const stat = fs4.statSync(backupPath);
|
|
1798
|
+
const entry = {
|
|
1799
|
+
id,
|
|
1800
|
+
originalPath: resolved,
|
|
1801
|
+
backupPath,
|
|
1802
|
+
command,
|
|
1803
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1804
|
+
sizeBytes: stat.size
|
|
1805
|
+
};
|
|
1806
|
+
const manifest = readManifest();
|
|
1807
|
+
manifest.entries.unshift(entry);
|
|
1808
|
+
writeManifest(manifest);
|
|
1809
|
+
if (manifest.entries.length > MAX_BACKUPS) {
|
|
1810
|
+
await pruneOldBackups();
|
|
1811
|
+
}
|
|
1812
|
+
return entry;
|
|
1813
|
+
}
|
|
1814
|
+
async function listBackups(filterPath) {
|
|
1815
|
+
const manifest = readManifest();
|
|
1816
|
+
const entries = manifest.entries;
|
|
1817
|
+
if (!filterPath) return entries;
|
|
1818
|
+
const resolved = path6.resolve(filterPath);
|
|
1819
|
+
return entries.filter((e) => e.originalPath === resolved);
|
|
1820
|
+
}
|
|
1821
|
+
async function restoreBackup(backupId) {
|
|
1822
|
+
const manifest = readManifest();
|
|
1823
|
+
const entry = manifest.entries.find((e) => e.id === backupId);
|
|
1824
|
+
if (!entry) {
|
|
1825
|
+
throw new Error(`Backup "${backupId}" not found. Run "claudectx revert --list" to see available backups.`);
|
|
1826
|
+
}
|
|
1827
|
+
if (!fs4.existsSync(entry.backupPath)) {
|
|
1828
|
+
throw new Error(`Backup file missing at "${entry.backupPath}". It may have been deleted manually.`);
|
|
1829
|
+
}
|
|
1830
|
+
let undoEntry = null;
|
|
1831
|
+
if (fs4.existsSync(entry.originalPath)) {
|
|
1832
|
+
undoEntry = await backupFile(entry.originalPath, "revert");
|
|
1833
|
+
}
|
|
1834
|
+
const targetDir = path6.dirname(entry.originalPath);
|
|
1835
|
+
if (!fs4.existsSync(targetDir)) {
|
|
1836
|
+
fs4.mkdirSync(targetDir, { recursive: true });
|
|
1837
|
+
}
|
|
1838
|
+
fs4.copyFileSync(entry.backupPath, entry.originalPath);
|
|
1839
|
+
return { entry, undoEntry };
|
|
1840
|
+
}
|
|
1841
|
+
async function pruneOldBackups() {
|
|
1842
|
+
const manifest = readManifest();
|
|
1843
|
+
if (manifest.entries.length <= MAX_BACKUPS) return 0;
|
|
1844
|
+
const toRemove = manifest.entries.splice(MAX_BACKUPS);
|
|
1845
|
+
for (const entry of toRemove) {
|
|
1846
|
+
try {
|
|
1847
|
+
fs4.unlinkSync(entry.backupPath);
|
|
1848
|
+
} catch {
|
|
1849
|
+
}
|
|
1850
|
+
}
|
|
1851
|
+
writeManifest(manifest);
|
|
1852
|
+
return toRemove.length;
|
|
1853
|
+
}
|
|
1854
|
+
|
|
1855
|
+
// src/optimizer/claudemd-splitter.ts
|
|
1733
1856
|
var SPLIT_MIN_TOKENS = 300;
|
|
1734
1857
|
function slugify(title) {
|
|
1735
1858
|
return title.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
|
|
@@ -1764,9 +1887,9 @@ function parseSections(content) {
|
|
|
1764
1887
|
return sections;
|
|
1765
1888
|
}
|
|
1766
1889
|
function planSplit(claudeMdPath, sectionsToExtract) {
|
|
1767
|
-
const content =
|
|
1890
|
+
const content = fs5.readFileSync(claudeMdPath, "utf-8");
|
|
1768
1891
|
const sections = parseSections(content);
|
|
1769
|
-
const claudeDir =
|
|
1892
|
+
const claudeDir = path7.join(path7.dirname(claudeMdPath), ".claude");
|
|
1770
1893
|
const extractedFiles = [];
|
|
1771
1894
|
let newContent = "";
|
|
1772
1895
|
let tokensSaved = 0;
|
|
@@ -1779,7 +1902,7 @@ function planSplit(claudeMdPath, sectionsToExtract) {
|
|
|
1779
1902
|
usedSlugs.set(slug, count + 1);
|
|
1780
1903
|
const filename = `${slug}.md`;
|
|
1781
1904
|
const relRefPath = `.claude/${filename}`;
|
|
1782
|
-
const filePath =
|
|
1905
|
+
const filePath = path7.join(claudeDir, filename);
|
|
1783
1906
|
const refBlock = `## ${section.title}
|
|
1784
1907
|
|
|
1785
1908
|
@${relRefPath}
|
|
@@ -1803,21 +1926,24 @@ function planSplit(claudeMdPath, sectionsToExtract) {
|
|
|
1803
1926
|
tokensSaved: Math.max(0, tokensSaved)
|
|
1804
1927
|
};
|
|
1805
1928
|
}
|
|
1806
|
-
function applySplit(result) {
|
|
1929
|
+
async function applySplit(result) {
|
|
1807
1930
|
if (result.extractedFiles.length === 0) return;
|
|
1808
|
-
|
|
1809
|
-
|
|
1810
|
-
|
|
1931
|
+
if (fs5.existsSync(result.claudeMdPath)) {
|
|
1932
|
+
await backupFile(result.claudeMdPath, "optimize");
|
|
1933
|
+
}
|
|
1934
|
+
const claudeDir = path7.dirname(result.extractedFiles[0].filePath);
|
|
1935
|
+
if (!fs5.existsSync(claudeDir)) {
|
|
1936
|
+
fs5.mkdirSync(claudeDir, { recursive: true });
|
|
1811
1937
|
}
|
|
1812
1938
|
for (const file of result.extractedFiles) {
|
|
1813
|
-
|
|
1939
|
+
fs5.writeFileSync(file.filePath, file.content, "utf-8");
|
|
1814
1940
|
}
|
|
1815
|
-
|
|
1941
|
+
fs5.writeFileSync(result.claudeMdPath, result.newClaudeMd, "utf-8");
|
|
1816
1942
|
}
|
|
1817
1943
|
|
|
1818
1944
|
// src/optimizer/cache-applier.ts
|
|
1819
1945
|
init_esm_shims();
|
|
1820
|
-
import * as
|
|
1946
|
+
import * as fs6 from "fs";
|
|
1821
1947
|
function findCacheBusters(content) {
|
|
1822
1948
|
const fixes = [];
|
|
1823
1949
|
const lines = content.split("\n");
|
|
@@ -1847,18 +1973,21 @@ function applyCacheFixes(content, fixes) {
|
|
|
1847
1973
|
return lines.join("\n");
|
|
1848
1974
|
}
|
|
1849
1975
|
function planCacheFixes(claudeMdPath) {
|
|
1850
|
-
const content =
|
|
1976
|
+
const content = fs6.readFileSync(claudeMdPath, "utf-8");
|
|
1851
1977
|
const fixes = findCacheBusters(content);
|
|
1852
1978
|
return { fixes, newContent: applyCacheFixes(content, fixes) };
|
|
1853
1979
|
}
|
|
1854
|
-
function applyAndWriteCacheFixes(claudeMdPath, result) {
|
|
1855
|
-
|
|
1980
|
+
async function applyAndWriteCacheFixes(claudeMdPath, result) {
|
|
1981
|
+
if (fs6.existsSync(claudeMdPath)) {
|
|
1982
|
+
await backupFile(claudeMdPath, "optimize");
|
|
1983
|
+
}
|
|
1984
|
+
fs6.writeFileSync(claudeMdPath, result.newContent, "utf-8");
|
|
1856
1985
|
}
|
|
1857
1986
|
|
|
1858
1987
|
// src/optimizer/hooks-installer.ts
|
|
1859
1988
|
init_esm_shims();
|
|
1860
|
-
import * as
|
|
1861
|
-
import * as
|
|
1989
|
+
import * as fs7 from "fs";
|
|
1990
|
+
import * as path8 from "path";
|
|
1862
1991
|
var CLAUDECTX_HOOKS = {
|
|
1863
1992
|
PostToolUse: [
|
|
1864
1993
|
{
|
|
@@ -1876,13 +2005,13 @@ var CLAUDECTX_HOOKS = {
|
|
|
1876
2005
|
]
|
|
1877
2006
|
};
|
|
1878
2007
|
function planHooksInstall(projectRoot) {
|
|
1879
|
-
const claudeDir =
|
|
1880
|
-
const settingsPath =
|
|
1881
|
-
const existed =
|
|
2008
|
+
const claudeDir = path8.join(projectRoot, ".claude");
|
|
2009
|
+
const settingsPath = path8.join(claudeDir, "settings.local.json");
|
|
2010
|
+
const existed = fs7.existsSync(settingsPath);
|
|
1882
2011
|
let existing = {};
|
|
1883
2012
|
if (existed) {
|
|
1884
2013
|
try {
|
|
1885
|
-
existing = JSON.parse(
|
|
2014
|
+
existing = JSON.parse(fs7.readFileSync(settingsPath, "utf-8"));
|
|
1886
2015
|
} catch {
|
|
1887
2016
|
existing = {};
|
|
1888
2017
|
}
|
|
@@ -1903,25 +2032,25 @@ function planHooksInstall(projectRoot) {
|
|
|
1903
2032
|
return { settingsPath, existed, mergedSettings };
|
|
1904
2033
|
}
|
|
1905
2034
|
function applyHooksInstall(result) {
|
|
1906
|
-
const dir =
|
|
1907
|
-
if (!
|
|
1908
|
-
|
|
2035
|
+
const dir = path8.dirname(result.settingsPath);
|
|
2036
|
+
if (!fs7.existsSync(dir)) {
|
|
2037
|
+
fs7.mkdirSync(dir, { recursive: true });
|
|
1909
2038
|
}
|
|
1910
|
-
|
|
2039
|
+
fs7.writeFileSync(result.settingsPath, JSON.stringify(result.mergedSettings, null, 2) + "\n", "utf-8");
|
|
1911
2040
|
}
|
|
1912
2041
|
function writeHooksSettings(projectRoot, mergedSettings) {
|
|
1913
|
-
const settingsPath =
|
|
1914
|
-
const dir =
|
|
1915
|
-
if (!
|
|
1916
|
-
|
|
2042
|
+
const settingsPath = path8.join(projectRoot, ".claude", "settings.local.json");
|
|
2043
|
+
const dir = path8.dirname(settingsPath);
|
|
2044
|
+
if (!fs7.existsSync(dir)) {
|
|
2045
|
+
fs7.mkdirSync(dir, { recursive: true });
|
|
1917
2046
|
}
|
|
1918
|
-
|
|
2047
|
+
fs7.writeFileSync(settingsPath, JSON.stringify(mergedSettings, null, 2) + "\n", "utf-8");
|
|
1919
2048
|
}
|
|
1920
2049
|
function isAlreadyInstalled(projectRoot) {
|
|
1921
|
-
const settingsPath =
|
|
1922
|
-
if (!
|
|
2050
|
+
const settingsPath = path8.join(projectRoot, ".claude", "settings.local.json");
|
|
2051
|
+
if (!fs7.existsSync(settingsPath)) return false;
|
|
1923
2052
|
try {
|
|
1924
|
-
const settings = JSON.parse(
|
|
2053
|
+
const settings = JSON.parse(fs7.readFileSync(settingsPath, "utf-8"));
|
|
1925
2054
|
const postToolUse = settings?.hooks?.PostToolUse ?? [];
|
|
1926
2055
|
return postToolUse.some((h) => h.matcher === "Read");
|
|
1927
2056
|
} catch {
|
|
@@ -1931,7 +2060,7 @@ function isAlreadyInstalled(projectRoot) {
|
|
|
1931
2060
|
|
|
1932
2061
|
// src/commands/optimize.ts
|
|
1933
2062
|
async function optimizeCommand(options) {
|
|
1934
|
-
const projectPath = options.path ?
|
|
2063
|
+
const projectPath = options.path ? path9.resolve(options.path) : findProjectRoot() ?? process.cwd();
|
|
1935
2064
|
const dryRun = options.dryRun ?? false;
|
|
1936
2065
|
const autoApply = options.apply ?? false;
|
|
1937
2066
|
const specificMode = options.claudemd || options.ignorefile || options.cache || options.hooks;
|
|
@@ -2056,12 +2185,12 @@ async function runIgnorefile(projectRoot, dryRun, autoApply) {
|
|
|
2056
2185
|
}
|
|
2057
2186
|
async function runClaudeMdSplit(projectRoot, report, dryRun, autoApply) {
|
|
2058
2187
|
printSectionHeader("CLAUDE.md \u2192 @files");
|
|
2059
|
-
const claudeMdPath =
|
|
2060
|
-
if (!
|
|
2188
|
+
const claudeMdPath = path9.join(projectRoot, "CLAUDE.md");
|
|
2189
|
+
if (!fs8.existsSync(claudeMdPath)) {
|
|
2061
2190
|
logger.warn("No CLAUDE.md found \u2014 skipping.");
|
|
2062
2191
|
return;
|
|
2063
2192
|
}
|
|
2064
|
-
const content =
|
|
2193
|
+
const content = fs8.readFileSync(claudeMdPath, "utf-8");
|
|
2065
2194
|
const sections = parseSections(content);
|
|
2066
2195
|
const largeSections = sections.filter(
|
|
2067
2196
|
(s) => !s.isPreamble && s.tokens >= SPLIT_MIN_TOKENS
|
|
@@ -2117,18 +2246,18 @@ Would extract ${splitResult.extractedFiles.length} section(s) to .claude/`
|
|
|
2117
2246
|
logger.info("Skipped.");
|
|
2118
2247
|
return;
|
|
2119
2248
|
}
|
|
2120
|
-
applySplit(splitResult);
|
|
2249
|
+
await applySplit(splitResult);
|
|
2121
2250
|
logger.success(
|
|
2122
2251
|
`Extracted ${splitResult.extractedFiles.length} section(s). Saved ~${splitResult.tokensSaved} tokens/request.`
|
|
2123
2252
|
);
|
|
2124
2253
|
for (const f of splitResult.extractedFiles) {
|
|
2125
|
-
logger.info(` Created: ${chalk3.cyan(
|
|
2254
|
+
logger.info(` Created: ${chalk3.cyan(path9.relative(projectRoot, f.filePath))}`);
|
|
2126
2255
|
}
|
|
2127
2256
|
}
|
|
2128
2257
|
async function runCacheOptimization(projectRoot, dryRun, autoApply) {
|
|
2129
2258
|
printSectionHeader("Prompt cache optimisation");
|
|
2130
|
-
const claudeMdPath =
|
|
2131
|
-
if (!
|
|
2259
|
+
const claudeMdPath = path9.join(projectRoot, "CLAUDE.md");
|
|
2260
|
+
if (!fs8.existsSync(claudeMdPath)) {
|
|
2132
2261
|
logger.warn("No CLAUDE.md found \u2014 skipping.");
|
|
2133
2262
|
return;
|
|
2134
2263
|
}
|
|
@@ -2156,14 +2285,14 @@ async function runCacheOptimization(projectRoot, dryRun, autoApply) {
|
|
|
2156
2285
|
logger.info("Skipped.");
|
|
2157
2286
|
return;
|
|
2158
2287
|
}
|
|
2159
|
-
applyAndWriteCacheFixes(claudeMdPath, result);
|
|
2288
|
+
await applyAndWriteCacheFixes(claudeMdPath, result);
|
|
2160
2289
|
logger.success(`Fixed ${result.fixes.length} cache-busting pattern(s) in CLAUDE.md.`);
|
|
2161
2290
|
}
|
|
2162
2291
|
async function runHooks(projectRoot, dryRun, autoApply) {
|
|
2163
2292
|
printSectionHeader("Session hooks");
|
|
2164
2293
|
const result = planHooksInstall(projectRoot);
|
|
2165
2294
|
logger.info(
|
|
2166
|
-
`Settings file: ${chalk3.cyan(
|
|
2295
|
+
`Settings file: ${chalk3.cyan(path9.relative(projectRoot, result.settingsPath))}`
|
|
2167
2296
|
);
|
|
2168
2297
|
logger.info(result.existed ? "Will merge with existing settings." : "Will create new file.");
|
|
2169
2298
|
console.log(chalk3.dim("\n Hooks to install:"));
|
|
@@ -2178,7 +2307,7 @@ async function runHooks(projectRoot, dryRun, autoApply) {
|
|
|
2178
2307
|
}
|
|
2179
2308
|
applyHooksInstall(result);
|
|
2180
2309
|
logger.success(
|
|
2181
|
-
`Hooks installed \u2192 ${chalk3.cyan(
|
|
2310
|
+
`Hooks installed \u2192 ${chalk3.cyan(path9.relative(projectRoot, result.settingsPath))}`
|
|
2182
2311
|
);
|
|
2183
2312
|
}
|
|
2184
2313
|
function printSectionHeader(title) {
|
|
@@ -2190,7 +2319,7 @@ function printSectionHeader(title) {
|
|
|
2190
2319
|
init_esm_shims();
|
|
2191
2320
|
init_session_store();
|
|
2192
2321
|
init_models();
|
|
2193
|
-
import * as
|
|
2322
|
+
import * as path13 from "path";
|
|
2194
2323
|
async function watchCommand(options) {
|
|
2195
2324
|
if (options.logStdin) {
|
|
2196
2325
|
await handleLogStdin();
|
|
@@ -2226,30 +2355,30 @@ async function handleLogStdin() {
|
|
|
2226
2355
|
const payload = JSON.parse(raw);
|
|
2227
2356
|
const filePath = payload.tool_input?.file_path;
|
|
2228
2357
|
if (filePath) {
|
|
2229
|
-
appendFileRead(
|
|
2358
|
+
appendFileRead(path13.resolve(filePath), payload.session_id);
|
|
2230
2359
|
}
|
|
2231
2360
|
} catch {
|
|
2232
2361
|
}
|
|
2233
2362
|
}
|
|
2234
2363
|
function readStdin() {
|
|
2235
|
-
return new Promise((
|
|
2364
|
+
return new Promise((resolve13) => {
|
|
2236
2365
|
let data = "";
|
|
2237
2366
|
process.stdin.setEncoding("utf-8");
|
|
2238
2367
|
process.stdin.on("data", (chunk) => data += chunk);
|
|
2239
|
-
process.stdin.on("end", () =>
|
|
2240
|
-
setTimeout(() =>
|
|
2368
|
+
process.stdin.on("end", () => resolve13(data));
|
|
2369
|
+
setTimeout(() => resolve13(data), 500);
|
|
2241
2370
|
});
|
|
2242
2371
|
}
|
|
2243
2372
|
|
|
2244
2373
|
// src/commands/mcp.ts
|
|
2245
2374
|
init_esm_shims();
|
|
2246
|
-
import * as
|
|
2375
|
+
import * as path18 from "path";
|
|
2247
2376
|
import chalk4 from "chalk";
|
|
2248
2377
|
|
|
2249
2378
|
// src/mcp/installer.ts
|
|
2250
2379
|
init_esm_shims();
|
|
2251
|
-
import * as
|
|
2252
|
-
import * as
|
|
2380
|
+
import * as fs12 from "fs";
|
|
2381
|
+
import * as path14 from "path";
|
|
2253
2382
|
var SERVER_NAME = "claudectx";
|
|
2254
2383
|
var SERVER_ENTRY = {
|
|
2255
2384
|
command: "claudectx",
|
|
@@ -2257,13 +2386,13 @@ var SERVER_ENTRY = {
|
|
|
2257
2386
|
type: "stdio"
|
|
2258
2387
|
};
|
|
2259
2388
|
function planInstall(projectRoot) {
|
|
2260
|
-
const claudeDir =
|
|
2261
|
-
const settingsPath =
|
|
2262
|
-
const existed =
|
|
2389
|
+
const claudeDir = path14.join(projectRoot, ".claude");
|
|
2390
|
+
const settingsPath = path14.join(claudeDir, "settings.json");
|
|
2391
|
+
const existed = fs12.existsSync(settingsPath);
|
|
2263
2392
|
let existing = {};
|
|
2264
2393
|
if (existed) {
|
|
2265
2394
|
try {
|
|
2266
|
-
existing = JSON.parse(
|
|
2395
|
+
existing = JSON.parse(fs12.readFileSync(settingsPath, "utf-8"));
|
|
2267
2396
|
} catch {
|
|
2268
2397
|
existing = {};
|
|
2269
2398
|
}
|
|
@@ -2280,21 +2409,21 @@ function planInstall(projectRoot) {
|
|
|
2280
2409
|
return { settingsPath, existed, alreadyInstalled, mergedSettings };
|
|
2281
2410
|
}
|
|
2282
2411
|
function applyInstall(result) {
|
|
2283
|
-
const dir =
|
|
2284
|
-
if (!
|
|
2285
|
-
|
|
2412
|
+
const dir = path14.dirname(result.settingsPath);
|
|
2413
|
+
if (!fs12.existsSync(dir)) {
|
|
2414
|
+
fs12.mkdirSync(dir, { recursive: true });
|
|
2286
2415
|
}
|
|
2287
|
-
|
|
2416
|
+
fs12.writeFileSync(
|
|
2288
2417
|
result.settingsPath,
|
|
2289
2418
|
JSON.stringify(result.mergedSettings, null, 2) + "\n",
|
|
2290
2419
|
"utf-8"
|
|
2291
2420
|
);
|
|
2292
2421
|
}
|
|
2293
2422
|
function isInstalled(projectRoot) {
|
|
2294
|
-
const settingsPath =
|
|
2295
|
-
if (!
|
|
2423
|
+
const settingsPath = path14.join(projectRoot, ".claude", "settings.json");
|
|
2424
|
+
if (!fs12.existsSync(settingsPath)) return false;
|
|
2296
2425
|
try {
|
|
2297
|
-
const settings = JSON.parse(
|
|
2426
|
+
const settings = JSON.parse(fs12.readFileSync(settingsPath, "utf-8"));
|
|
2298
2427
|
return SERVER_NAME in (settings.mcpServers ?? {});
|
|
2299
2428
|
} catch {
|
|
2300
2429
|
return false;
|
|
@@ -2303,7 +2432,7 @@ function isInstalled(projectRoot) {
|
|
|
2303
2432
|
|
|
2304
2433
|
// src/commands/mcp.ts
|
|
2305
2434
|
async function mcpCommand(options) {
|
|
2306
|
-
const projectRoot = options.path ?
|
|
2435
|
+
const projectRoot = options.path ? path18.resolve(options.path) : process.cwd();
|
|
2307
2436
|
if (options.install) {
|
|
2308
2437
|
await runInstall(projectRoot);
|
|
2309
2438
|
return;
|
|
@@ -2352,12 +2481,12 @@ async function runInstall(projectRoot) {
|
|
|
2352
2481
|
// src/commands/compress.ts
|
|
2353
2482
|
init_esm_shims();
|
|
2354
2483
|
init_session_reader();
|
|
2355
|
-
import * as
|
|
2356
|
-
import * as
|
|
2484
|
+
import * as path20 from "path";
|
|
2485
|
+
import * as fs17 from "fs";
|
|
2357
2486
|
|
|
2358
2487
|
// src/compressor/session-parser.ts
|
|
2359
2488
|
init_esm_shims();
|
|
2360
|
-
import * as
|
|
2489
|
+
import * as fs15 from "fs";
|
|
2361
2490
|
function extractText(content) {
|
|
2362
2491
|
if (!content) return "";
|
|
2363
2492
|
if (typeof content === "string") return content;
|
|
@@ -2368,10 +2497,10 @@ function extractToolCalls(content) {
|
|
|
2368
2497
|
return content.filter((b) => b.type === "tool_use" && b.name).map((b) => ({ tool: b.name, input: b.input ?? {} }));
|
|
2369
2498
|
}
|
|
2370
2499
|
function parseSessionFile(sessionFilePath) {
|
|
2371
|
-
if (!
|
|
2500
|
+
if (!fs15.existsSync(sessionFilePath)) return null;
|
|
2372
2501
|
let content;
|
|
2373
2502
|
try {
|
|
2374
|
-
content =
|
|
2503
|
+
content = fs15.readFileSync(sessionFilePath, "utf-8");
|
|
2375
2504
|
} catch {
|
|
2376
2505
|
return null;
|
|
2377
2506
|
}
|
|
@@ -2571,13 +2700,13 @@ function calcCost(inputTokens, outputTokens) {
|
|
|
2571
2700
|
|
|
2572
2701
|
// src/compressor/memory-writer.ts
|
|
2573
2702
|
init_esm_shims();
|
|
2574
|
-
import * as
|
|
2575
|
-
import * as
|
|
2703
|
+
import * as fs16 from "fs";
|
|
2704
|
+
import * as path19 from "path";
|
|
2576
2705
|
function parseMemoryFile(filePath) {
|
|
2577
|
-
if (!
|
|
2706
|
+
if (!fs16.existsSync(filePath)) {
|
|
2578
2707
|
return { preamble: "", entries: [] };
|
|
2579
2708
|
}
|
|
2580
|
-
const content =
|
|
2709
|
+
const content = fs16.readFileSync(filePath, "utf-8");
|
|
2581
2710
|
const markerRegex = /<!-- claudectx-entry: (\d{4}-\d{2}-\d{2}) \| session: ([a-z0-9-]+) -->/g;
|
|
2582
2711
|
const indices = [];
|
|
2583
2712
|
let match;
|
|
@@ -2628,15 +2757,15 @@ function appendEntry(memoryFilePath, sessionId, summaryText, date = /* @__PURE__
|
|
|
2628
2757
|
const newBlock = buildEntryBlock(sessionId, summaryText, date);
|
|
2629
2758
|
const allBlocks = [...entries.map((e) => e.raw), newBlock];
|
|
2630
2759
|
const newContent = (preamble.trimEnd() ? preamble.trimEnd() + "\n\n" : "") + allBlocks.join("\n\n") + "\n";
|
|
2631
|
-
const dir =
|
|
2632
|
-
if (!
|
|
2633
|
-
|
|
2760
|
+
const dir = path19.dirname(memoryFilePath);
|
|
2761
|
+
if (!fs16.existsSync(dir)) {
|
|
2762
|
+
fs16.mkdirSync(dir, { recursive: true });
|
|
2634
2763
|
}
|
|
2635
|
-
|
|
2764
|
+
fs16.writeFileSync(memoryFilePath, newContent, "utf-8");
|
|
2636
2765
|
return newContent;
|
|
2637
2766
|
}
|
|
2638
2767
|
function pruneOldEntries(memoryFilePath, days) {
|
|
2639
|
-
if (!
|
|
2768
|
+
if (!fs16.existsSync(memoryFilePath)) {
|
|
2640
2769
|
return { removed: 0, kept: 0, removedEntries: [] };
|
|
2641
2770
|
}
|
|
2642
2771
|
const { preamble, entries } = parseMemoryFile(memoryFilePath);
|
|
@@ -2649,7 +2778,7 @@ function pruneOldEntries(memoryFilePath, days) {
|
|
|
2649
2778
|
return { removed: 0, kept: kept.length, removedEntries: [] };
|
|
2650
2779
|
}
|
|
2651
2780
|
const newContent = (preamble.trimEnd() ? preamble.trimEnd() + "\n\n" : "") + kept.map((e) => e.raw).join("\n\n") + (kept.length > 0 ? "\n" : "");
|
|
2652
|
-
|
|
2781
|
+
fs16.writeFileSync(memoryFilePath, newContent, "utf-8");
|
|
2653
2782
|
return { removed: removed.length, kept: kept.length, removedEntries: removed };
|
|
2654
2783
|
}
|
|
2655
2784
|
function isAlreadyCompressed(memoryFilePath, sessionId) {
|
|
@@ -2660,8 +2789,8 @@ function isAlreadyCompressed(memoryFilePath, sessionId) {
|
|
|
2660
2789
|
// src/commands/compress.ts
|
|
2661
2790
|
async function compressCommand(options) {
|
|
2662
2791
|
const chalk5 = (await import("chalk")).default;
|
|
2663
|
-
const projectRoot = options.path ?
|
|
2664
|
-
const memoryFilePath =
|
|
2792
|
+
const projectRoot = options.path ? path20.resolve(options.path) : process.cwd();
|
|
2793
|
+
const memoryFilePath = path20.join(projectRoot, "MEMORY.md");
|
|
2665
2794
|
const sessionFiles = listSessionFiles();
|
|
2666
2795
|
if (sessionFiles.length === 0) {
|
|
2667
2796
|
process.stdout.write(chalk5.red("No Claude Code sessions found.\n"));
|
|
@@ -2686,7 +2815,7 @@ async function compressCommand(options) {
|
|
|
2686
2815
|
} else {
|
|
2687
2816
|
targetFile = sessionFiles[0].filePath;
|
|
2688
2817
|
}
|
|
2689
|
-
const sessionId =
|
|
2818
|
+
const sessionId = path20.basename(targetFile, ".jsonl");
|
|
2690
2819
|
if (isAlreadyCompressed(memoryFilePath, sessionId)) {
|
|
2691
2820
|
if (!options.auto) {
|
|
2692
2821
|
process.stdout.write(chalk5.yellow(`Session ${sessionId.slice(0, 8)}\u2026 is already in MEMORY.md \u2014 skipping.
|
|
@@ -2734,11 +2863,27 @@ async function compressCommand(options) {
|
|
|
2734
2863
|
}
|
|
2735
2864
|
if (options.prune) {
|
|
2736
2865
|
const days = parseInt(options.days ?? "30", 10);
|
|
2737
|
-
if (!
|
|
2866
|
+
if (!fs17.existsSync(memoryFilePath)) return;
|
|
2867
|
+
if (!options.auto) {
|
|
2868
|
+
let confirmed = true;
|
|
2869
|
+
try {
|
|
2870
|
+
const { confirm: confirm2 } = await import("@inquirer/prompts");
|
|
2871
|
+
confirmed = await confirm2({
|
|
2872
|
+
message: `Prune MEMORY.md entries older than ${days} days? Run 'claudectx revert' to undo.`,
|
|
2873
|
+
default: false
|
|
2874
|
+
});
|
|
2875
|
+
} catch {
|
|
2876
|
+
}
|
|
2877
|
+
if (!confirmed) {
|
|
2878
|
+
process.stdout.write(chalk5.dim("Prune skipped.\n"));
|
|
2879
|
+
return;
|
|
2880
|
+
}
|
|
2881
|
+
}
|
|
2882
|
+
await backupFile(memoryFilePath, "compress");
|
|
2738
2883
|
const pruned = pruneOldEntries(memoryFilePath, days);
|
|
2739
2884
|
if (pruned.removed > 0 && !options.auto) {
|
|
2740
2885
|
process.stdout.write(
|
|
2741
|
-
chalk5.dim(`Pruned ${pruned.removed} entr${pruned.removed === 1 ? "y" : "ies"} older than ${days} days.
|
|
2886
|
+
chalk5.dim(`Pruned ${pruned.removed} entr${pruned.removed === 1 ? "y" : "ies"} older than ${days} days. Run 'claudectx revert' to undo.
|
|
2742
2887
|
`)
|
|
2743
2888
|
);
|
|
2744
2889
|
}
|
|
@@ -2814,7 +2959,8 @@ async function aggregateUsage(days, model = "claude-sonnet-4-6") {
|
|
|
2814
2959
|
readCount: s.readCount
|
|
2815
2960
|
}));
|
|
2816
2961
|
const totalCost = calcCost2(totalInput, totalOutput, totalCacheCreation, totalCacheRead, model);
|
|
2817
|
-
const
|
|
2962
|
+
const totalContext = totalInput + totalCacheRead;
|
|
2963
|
+
const cacheHitRate = totalContext > 0 ? Math.round(totalCacheRead / totalContext * 100) : 0;
|
|
2818
2964
|
const byDay = [...bucketMap.values()].sort((a, b) => a.date.localeCompare(b.date));
|
|
2819
2965
|
const uniqueSessions = new Set(sessionFiles.map((f) => f.sessionId)).size;
|
|
2820
2966
|
const dailyAvgCostUsd = days > 0 ? totalCost / days : 0;
|
|
@@ -3016,13 +3162,13 @@ async function reportCommand(options) {
|
|
|
3016
3162
|
|
|
3017
3163
|
// src/commands/budget.ts
|
|
3018
3164
|
init_esm_shims();
|
|
3019
|
-
import * as
|
|
3165
|
+
import * as path22 from "path";
|
|
3020
3166
|
|
|
3021
3167
|
// src/analyzer/budget-estimator.ts
|
|
3022
3168
|
init_esm_shims();
|
|
3023
3169
|
init_tokenizer();
|
|
3024
|
-
import * as
|
|
3025
|
-
import * as
|
|
3170
|
+
import * as fs18 from "fs";
|
|
3171
|
+
import * as path21 from "path";
|
|
3026
3172
|
import { glob as glob2 } from "glob";
|
|
3027
3173
|
init_session_store();
|
|
3028
3174
|
function resolveGlobs(globs, projectRoot) {
|
|
@@ -3047,20 +3193,20 @@ function classifyCacheHit(recentReadCount) {
|
|
|
3047
3193
|
return "low";
|
|
3048
3194
|
}
|
|
3049
3195
|
function suggestClaudeignoreAdditions(files, projectRoot) {
|
|
3050
|
-
const ignorePath =
|
|
3196
|
+
const ignorePath = path21.join(projectRoot, ".claudeignore");
|
|
3051
3197
|
let ignorePatterns = [];
|
|
3052
3198
|
try {
|
|
3053
|
-
const content =
|
|
3199
|
+
const content = fs18.readFileSync(ignorePath, "utf-8");
|
|
3054
3200
|
ignorePatterns = content.split("\n").filter(Boolean);
|
|
3055
3201
|
} catch {
|
|
3056
3202
|
}
|
|
3057
3203
|
const recommendations = [];
|
|
3058
3204
|
for (const file of files) {
|
|
3059
3205
|
if (file.tokenCount <= WASTE_THRESHOLDS.MAX_REFERENCE_FILE_TOKENS) continue;
|
|
3060
|
-
const rel =
|
|
3206
|
+
const rel = path21.relative(projectRoot, file.filePath);
|
|
3061
3207
|
const alreadyIgnored = ignorePatterns.some((pattern) => {
|
|
3062
3208
|
const cleanPattern = pattern.replace(/^!/, "");
|
|
3063
|
-
return rel.startsWith(cleanPattern.replace(/\*/g, "").replace(/\//g,
|
|
3209
|
+
return rel.startsWith(cleanPattern.replace(/\*/g, "").replace(/\//g, path21.sep));
|
|
3064
3210
|
});
|
|
3065
3211
|
if (!alreadyIgnored) {
|
|
3066
3212
|
recommendations.push(rel);
|
|
@@ -3080,7 +3226,7 @@ async function estimateBudget(globs, projectRoot, model, thresholdTokens) {
|
|
|
3080
3226
|
for (const filePath of filePaths) {
|
|
3081
3227
|
let content = "";
|
|
3082
3228
|
try {
|
|
3083
|
-
content =
|
|
3229
|
+
content = fs18.readFileSync(filePath, "utf-8");
|
|
3084
3230
|
} catch {
|
|
3085
3231
|
continue;
|
|
3086
3232
|
}
|
|
@@ -3129,7 +3275,7 @@ function formatBudgetReport(report) {
|
|
|
3129
3275
|
}
|
|
3130
3276
|
const LIKELIHOOD_ICON = { high: "\u{1F7E2}", medium: "\u{1F7E1}", low: "\u{1F534}" };
|
|
3131
3277
|
const maxPathLen = Math.min(
|
|
3132
|
-
Math.max(...report.files.map((f) =>
|
|
3278
|
+
Math.max(...report.files.map((f) => path21.basename(f.filePath).length)),
|
|
3133
3279
|
40
|
|
3134
3280
|
);
|
|
3135
3281
|
lines.push(
|
|
@@ -3137,7 +3283,7 @@ function formatBudgetReport(report) {
|
|
|
3137
3283
|
);
|
|
3138
3284
|
lines.push("\u2500".repeat(50));
|
|
3139
3285
|
for (const file of report.files.slice(0, 20)) {
|
|
3140
|
-
const name =
|
|
3286
|
+
const name = path21.basename(file.filePath).slice(0, maxPathLen).padEnd(maxPathLen);
|
|
3141
3287
|
const tokens = file.tokenCount.toLocaleString().padStart(7);
|
|
3142
3288
|
const cache = `${LIKELIHOOD_ICON[file.cacheHitLikelihood]} ${file.cacheHitLikelihood.padEnd(6)}`;
|
|
3143
3289
|
const cost = formatCost(file.estimatedCostUsd).padStart(7);
|
|
@@ -3166,7 +3312,7 @@ function formatBudgetReport(report) {
|
|
|
3166
3312
|
// src/commands/budget.ts
|
|
3167
3313
|
init_models();
|
|
3168
3314
|
async function budgetCommand(globs, options) {
|
|
3169
|
-
const projectPath = options.path ?
|
|
3315
|
+
const projectPath = options.path ? path22.resolve(options.path) : process.cwd();
|
|
3170
3316
|
const projectRoot = findProjectRoot(projectPath) ?? projectPath;
|
|
3171
3317
|
const model = resolveModel(options.model ?? "sonnet");
|
|
3172
3318
|
const thresholdTokens = parseInt(options.threshold ?? "10000", 10);
|
|
@@ -3185,10 +3331,10 @@ async function budgetCommand(globs, options) {
|
|
|
3185
3331
|
|
|
3186
3332
|
// src/commands/warmup.ts
|
|
3187
3333
|
init_esm_shims();
|
|
3188
|
-
import * as
|
|
3334
|
+
import * as path23 from "path";
|
|
3189
3335
|
import Anthropic from "@anthropic-ai/sdk";
|
|
3190
3336
|
init_models();
|
|
3191
|
-
import
|
|
3337
|
+
import fs19 from "fs";
|
|
3192
3338
|
function buildWarmupMessages(claudeMdContent) {
|
|
3193
3339
|
const systemBlock = {
|
|
3194
3340
|
type: "text",
|
|
@@ -3259,6 +3405,19 @@ async function installCron(cronExpr) {
|
|
|
3259
3405
|
);
|
|
3260
3406
|
process.exit(1);
|
|
3261
3407
|
}
|
|
3408
|
+
let confirmed = true;
|
|
3409
|
+
try {
|
|
3410
|
+
const { confirm: confirm2 } = await import("@inquirer/prompts");
|
|
3411
|
+
confirmed = await confirm2({
|
|
3412
|
+
message: `Install cron job "${cronExpr} claudectx warmup" in your system crontab?`,
|
|
3413
|
+
default: false
|
|
3414
|
+
});
|
|
3415
|
+
} catch {
|
|
3416
|
+
}
|
|
3417
|
+
if (!confirmed) {
|
|
3418
|
+
process.stdout.write(" Cron install cancelled.\n");
|
|
3419
|
+
return;
|
|
3420
|
+
}
|
|
3262
3421
|
const { execSync: execSync3 } = await import("child_process");
|
|
3263
3422
|
const command = `claudectx warmup`;
|
|
3264
3423
|
const cronLine = `${cronExpr} ${command}`;
|
|
@@ -3281,16 +3440,16 @@ async function installCron(cronExpr) {
|
|
|
3281
3440
|
${marker}
|
|
3282
3441
|
${cronLine}
|
|
3283
3442
|
`;
|
|
3284
|
-
const { writeFileSync:
|
|
3443
|
+
const { writeFileSync: writeFileSync12, unlinkSync: unlinkSync3 } = await import("fs");
|
|
3285
3444
|
const { tmpdir } = await import("os");
|
|
3286
|
-
const { join:
|
|
3287
|
-
const tmpFile =
|
|
3445
|
+
const { join: join18 } = await import("path");
|
|
3446
|
+
const tmpFile = join18(tmpdir(), `claudectx-cron-${Date.now()}.txt`);
|
|
3288
3447
|
try {
|
|
3289
|
-
|
|
3448
|
+
writeFileSync12(tmpFile, newCrontab, "utf-8");
|
|
3290
3449
|
execSync3(`crontab ${tmpFile}`, { stdio: ["pipe", "pipe", "pipe"] });
|
|
3291
3450
|
} finally {
|
|
3292
3451
|
try {
|
|
3293
|
-
|
|
3452
|
+
unlinkSync3(tmpFile);
|
|
3294
3453
|
} catch {
|
|
3295
3454
|
}
|
|
3296
3455
|
}
|
|
@@ -3307,7 +3466,7 @@ ${cronLine}
|
|
|
3307
3466
|
}
|
|
3308
3467
|
}
|
|
3309
3468
|
async function warmupCommand(options) {
|
|
3310
|
-
const projectPath = options.path ?
|
|
3469
|
+
const projectPath = options.path ? path23.resolve(options.path) : process.cwd();
|
|
3311
3470
|
const projectRoot = findProjectRoot(projectPath) ?? projectPath;
|
|
3312
3471
|
const model = resolveModel(options.model ?? "haiku");
|
|
3313
3472
|
const ttl = options.ttl === "60" ? 60 : 5;
|
|
@@ -3319,9 +3478,9 @@ async function warmupCommand(options) {
|
|
|
3319
3478
|
process.exit(1);
|
|
3320
3479
|
}
|
|
3321
3480
|
let claudeMdContent = "";
|
|
3322
|
-
const claudeMdPath =
|
|
3481
|
+
const claudeMdPath = path23.join(projectRoot, "CLAUDE.md");
|
|
3323
3482
|
try {
|
|
3324
|
-
claudeMdContent =
|
|
3483
|
+
claudeMdContent = fs19.readFileSync(claudeMdPath, "utf-8");
|
|
3325
3484
|
} catch {
|
|
3326
3485
|
process.stderr.write(`Warning: No CLAUDE.md found at ${claudeMdPath}
|
|
3327
3486
|
`);
|
|
@@ -3379,15 +3538,15 @@ async function warmupCommand(options) {
|
|
|
3379
3538
|
|
|
3380
3539
|
// src/commands/drift.ts
|
|
3381
3540
|
init_esm_shims();
|
|
3382
|
-
import * as
|
|
3383
|
-
import * as
|
|
3541
|
+
import * as path25 from "path";
|
|
3542
|
+
import * as fs21 from "fs";
|
|
3384
3543
|
|
|
3385
3544
|
// src/analyzer/drift-detector.ts
|
|
3386
3545
|
init_esm_shims();
|
|
3387
3546
|
init_tokenizer();
|
|
3388
3547
|
init_session_store();
|
|
3389
|
-
import * as
|
|
3390
|
-
import * as
|
|
3548
|
+
import * as fs20 from "fs";
|
|
3549
|
+
import * as path24 from "path";
|
|
3391
3550
|
import * as childProcess from "child_process";
|
|
3392
3551
|
var INLINE_PATH_RE = /(?:^|\s)((?:\.{1,2}\/|src\/|lib\/|docs\/|app\/|tests?\/)\S+\.\w{1,6})/gm;
|
|
3393
3552
|
var AT_REF_RE = /^@(.+)$/;
|
|
@@ -3398,8 +3557,8 @@ function findDeadAtReferences(content, projectRoot) {
|
|
|
3398
3557
|
const match = lines[i].match(AT_REF_RE);
|
|
3399
3558
|
if (!match) continue;
|
|
3400
3559
|
const ref = match[1].trim();
|
|
3401
|
-
const absPath =
|
|
3402
|
-
if (!
|
|
3560
|
+
const absPath = path24.isAbsolute(ref) ? ref : path24.join(projectRoot, ref);
|
|
3561
|
+
if (!fs20.existsSync(absPath)) {
|
|
3403
3562
|
const lineText = lines[i];
|
|
3404
3563
|
issues.push({
|
|
3405
3564
|
type: "dead-ref",
|
|
@@ -3428,19 +3587,33 @@ async function findGitDeletedMentions(content, projectRoot) {
|
|
|
3428
3587
|
return [];
|
|
3429
3588
|
}
|
|
3430
3589
|
if (deletedFiles.size === 0) return [];
|
|
3590
|
+
const deletedTerms = [];
|
|
3591
|
+
for (const deleted of deletedFiles) {
|
|
3592
|
+
const stem = path24.basename(deleted, path24.extname(deleted));
|
|
3593
|
+
const basenameWithExt = path24.basename(deleted);
|
|
3594
|
+
if (stem.length < 4) continue;
|
|
3595
|
+
deletedTerms.push({ basename: basenameWithExt, fullPath: deleted });
|
|
3596
|
+
}
|
|
3597
|
+
if (deletedTerms.length === 0) return [];
|
|
3431
3598
|
const lines = content.split("\n");
|
|
3432
3599
|
for (let i = 0; i < lines.length; i++) {
|
|
3433
3600
|
const line = lines[i];
|
|
3434
|
-
|
|
3435
|
-
|
|
3436
|
-
|
|
3601
|
+
const lineLower = line.toLowerCase();
|
|
3602
|
+
for (const { basename: basename10, fullPath } of deletedTerms) {
|
|
3603
|
+
const matchesFullPath = lineLower.includes(fullPath.toLowerCase());
|
|
3604
|
+
const basenameLower = basename10.toLowerCase();
|
|
3605
|
+
const idx = lineLower.indexOf(basenameLower);
|
|
3606
|
+
const matchesBasename = idx !== -1 && // Check left boundary: start of string, space, slash, quote, backtick, or `@`
|
|
3607
|
+
(idx === 0 || /[\s/`'"@(]/.test(line[idx - 1])) && // Check right boundary: end of string, space, punctuation, or extension dot
|
|
3608
|
+
(idx + basenameLower.length >= line.length || /[\s/`'",.):@]/.test(line[idx + basenameLower.length]));
|
|
3609
|
+
if (matchesFullPath || matchesBasename) {
|
|
3437
3610
|
issues.push({
|
|
3438
3611
|
type: "git-deleted",
|
|
3439
3612
|
line: i + 1,
|
|
3440
3613
|
text: line.trim(),
|
|
3441
3614
|
severity: "warning",
|
|
3442
3615
|
estimatedTokenWaste: countTokens(line),
|
|
3443
|
-
suggestion: `References "${
|
|
3616
|
+
suggestion: `References "${basename10}" which was deleted from git. Consider removing this mention.`
|
|
3444
3617
|
});
|
|
3445
3618
|
break;
|
|
3446
3619
|
}
|
|
@@ -3505,8 +3678,8 @@ function findDeadInlinePaths(content, projectRoot) {
|
|
|
3505
3678
|
const rawPath = match[1].trim();
|
|
3506
3679
|
if (seen.has(rawPath)) continue;
|
|
3507
3680
|
seen.add(rawPath);
|
|
3508
|
-
const absPath =
|
|
3509
|
-
if (!
|
|
3681
|
+
const absPath = path24.isAbsolute(rawPath) ? rawPath : path24.join(projectRoot, rawPath);
|
|
3682
|
+
if (!fs20.existsSync(absPath)) {
|
|
3510
3683
|
issues.push({
|
|
3511
3684
|
type: "dead-inline-path",
|
|
3512
3685
|
line: i + 1,
|
|
@@ -3522,10 +3695,10 @@ function findDeadInlinePaths(content, projectRoot) {
|
|
|
3522
3695
|
return issues;
|
|
3523
3696
|
}
|
|
3524
3697
|
async function detectDrift(projectRoot, dayWindow) {
|
|
3525
|
-
const claudeMdPath =
|
|
3698
|
+
const claudeMdPath = path24.join(projectRoot, "CLAUDE.md");
|
|
3526
3699
|
let content = "";
|
|
3527
3700
|
try {
|
|
3528
|
-
content =
|
|
3701
|
+
content = fs20.readFileSync(claudeMdPath, "utf-8");
|
|
3529
3702
|
} catch {
|
|
3530
3703
|
return {
|
|
3531
3704
|
claudeMdPath,
|
|
@@ -3567,7 +3740,7 @@ var TYPE_LABEL = {
|
|
|
3567
3740
|
"dead-inline-path": "Dead path"
|
|
3568
3741
|
};
|
|
3569
3742
|
async function driftCommand(options) {
|
|
3570
|
-
const projectPath = options.path ?
|
|
3743
|
+
const projectPath = options.path ? path25.resolve(options.path) : process.cwd();
|
|
3571
3744
|
const projectRoot = findProjectRoot(projectPath) ?? projectPath;
|
|
3572
3745
|
const dayWindow = parseInt(options.days ?? "30", 10);
|
|
3573
3746
|
const report = await detectDrift(projectRoot, dayWindow);
|
|
@@ -3643,25 +3816,25 @@ async function applyFix(claudeMdPath, issues) {
|
|
|
3643
3816
|
process.stdout.write("No lines selected. Nothing changed.\n");
|
|
3644
3817
|
return;
|
|
3645
3818
|
}
|
|
3646
|
-
const content =
|
|
3819
|
+
const content = fs21.readFileSync(claudeMdPath, "utf-8");
|
|
3647
3820
|
const lines = content.split("\n");
|
|
3648
3821
|
const lineSet = new Set(selectedLines.map((l) => l - 1));
|
|
3649
3822
|
const newLines = lines.filter((_, i) => !lineSet.has(i));
|
|
3650
3823
|
const newContent = newLines.join("\n");
|
|
3651
3824
|
const backupPath = `${claudeMdPath}.bak`;
|
|
3652
|
-
|
|
3653
|
-
const
|
|
3825
|
+
fs21.writeFileSync(backupPath, content, "utf-8");
|
|
3826
|
+
const os6 = await import("os");
|
|
3654
3827
|
const tmpPath = `${claudeMdPath}.tmp-${Date.now()}`;
|
|
3655
3828
|
try {
|
|
3656
|
-
|
|
3657
|
-
|
|
3829
|
+
fs21.writeFileSync(tmpPath, newContent, "utf-8");
|
|
3830
|
+
fs21.renameSync(tmpPath, claudeMdPath);
|
|
3658
3831
|
} catch (err) {
|
|
3659
3832
|
try {
|
|
3660
|
-
|
|
3833
|
+
fs21.copyFileSync(backupPath, claudeMdPath);
|
|
3661
3834
|
} catch {
|
|
3662
3835
|
}
|
|
3663
3836
|
try {
|
|
3664
|
-
|
|
3837
|
+
fs21.unlinkSync(tmpPath);
|
|
3665
3838
|
} catch {
|
|
3666
3839
|
}
|
|
3667
3840
|
process.stderr.write(`Error writing CLAUDE.md: ${err instanceof Error ? err.message : String(err)}
|
|
@@ -3669,18 +3842,18 @@ async function applyFix(claudeMdPath, issues) {
|
|
|
3669
3842
|
process.exit(1);
|
|
3670
3843
|
}
|
|
3671
3844
|
process.stdout.write(`
|
|
3672
|
-
\u2713 Removed ${selectedLines.length} line(s) from ${
|
|
3845
|
+
\u2713 Removed ${selectedLines.length} line(s) from ${path25.basename(claudeMdPath)}
|
|
3673
3846
|
`);
|
|
3674
3847
|
process.stdout.write(` \u2713 Backup saved to ${backupPath}
|
|
3675
3848
|
|
|
3676
3849
|
`);
|
|
3677
|
-
void
|
|
3850
|
+
void os6;
|
|
3678
3851
|
}
|
|
3679
3852
|
|
|
3680
3853
|
// src/commands/hooks.ts
|
|
3681
3854
|
init_esm_shims();
|
|
3682
|
-
import * as
|
|
3683
|
-
import * as
|
|
3855
|
+
import * as fs22 from "fs";
|
|
3856
|
+
import * as path26 from "path";
|
|
3684
3857
|
|
|
3685
3858
|
// src/hooks/registry.ts
|
|
3686
3859
|
init_esm_shims();
|
|
@@ -3756,17 +3929,17 @@ function buildHookEntry(def, config) {
|
|
|
3756
3929
|
|
|
3757
3930
|
// src/commands/hooks.ts
|
|
3758
3931
|
function readInstalledHooks(projectRoot) {
|
|
3759
|
-
const settingsPath =
|
|
3760
|
-
if (!
|
|
3932
|
+
const settingsPath = path26.join(projectRoot, ".claude", "settings.local.json");
|
|
3933
|
+
if (!fs22.existsSync(settingsPath)) return {};
|
|
3761
3934
|
try {
|
|
3762
|
-
return JSON.parse(
|
|
3935
|
+
return JSON.parse(fs22.readFileSync(settingsPath, "utf-8"));
|
|
3763
3936
|
} catch {
|
|
3764
3937
|
process.stderr.write(
|
|
3765
3938
|
`Warning: ${settingsPath} exists but contains invalid JSON. Existing settings will be preserved as a backup.
|
|
3766
3939
|
`
|
|
3767
3940
|
);
|
|
3768
3941
|
try {
|
|
3769
|
-
|
|
3942
|
+
fs22.copyFileSync(settingsPath, `${settingsPath}.bak`);
|
|
3770
3943
|
} catch {
|
|
3771
3944
|
}
|
|
3772
3945
|
return {};
|
|
@@ -3886,9 +4059,26 @@ async function hooksAdd(name, projectRoot, configPairs) {
|
|
|
3886
4059
|
}
|
|
3887
4060
|
async function hooksRemove(name, projectRoot) {
|
|
3888
4061
|
const settings = readInstalledHooks(projectRoot);
|
|
4062
|
+
let confirmed = true;
|
|
4063
|
+
try {
|
|
4064
|
+
const { confirm: confirm2 } = await import("@inquirer/prompts");
|
|
4065
|
+
confirmed = await confirm2({
|
|
4066
|
+
message: `Remove hook "${name}"? Run 'claudectx revert' to undo.`,
|
|
4067
|
+
default: false
|
|
4068
|
+
});
|
|
4069
|
+
} catch {
|
|
4070
|
+
}
|
|
4071
|
+
if (!confirmed) {
|
|
4072
|
+
process.stdout.write(" Cancelled.\n\n");
|
|
4073
|
+
return;
|
|
4074
|
+
}
|
|
4075
|
+
const settingsPath = path26.join(projectRoot, ".claude", "settings.local.json");
|
|
4076
|
+
if (fs22.existsSync(settingsPath)) {
|
|
4077
|
+
await backupFile(settingsPath, "hooks");
|
|
4078
|
+
}
|
|
3889
4079
|
const updated = removeHookByName(settings, name);
|
|
3890
4080
|
writeHooksSettings(projectRoot, updated);
|
|
3891
|
-
process.stdout.write(` \u2713 Hook "${name}" removed.
|
|
4081
|
+
process.stdout.write(` \u2713 Hook "${name}" removed. Run 'claudectx revert --list' to undo.
|
|
3892
4082
|
|
|
3893
4083
|
`);
|
|
3894
4084
|
}
|
|
@@ -3913,7 +4103,7 @@ async function hooksStatus(projectRoot) {
|
|
|
3913
4103
|
process.stdout.write("\n");
|
|
3914
4104
|
}
|
|
3915
4105
|
async function hooksCommand(subcommand, options) {
|
|
3916
|
-
const projectPath = options.path ?
|
|
4106
|
+
const projectPath = options.path ? path26.resolve(options.path) : process.cwd();
|
|
3917
4107
|
const projectRoot = findProjectRoot(projectPath) ?? projectPath;
|
|
3918
4108
|
const sub = subcommand ?? "list";
|
|
3919
4109
|
switch (sub) {
|
|
@@ -3952,8 +4142,8 @@ async function hooksCommand(subcommand, options) {
|
|
|
3952
4142
|
|
|
3953
4143
|
// src/commands/convert.ts
|
|
3954
4144
|
init_esm_shims();
|
|
3955
|
-
import * as
|
|
3956
|
-
import * as
|
|
4145
|
+
import * as fs23 from "fs";
|
|
4146
|
+
import * as path27 from "path";
|
|
3957
4147
|
function slugify2(text) {
|
|
3958
4148
|
return text.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
|
|
3959
4149
|
}
|
|
@@ -4002,7 +4192,7 @@ function claudeMdToWindsurf(content) {
|
|
|
4002
4192
|
return content.split("\n").filter((line) => !line.match(/^@.+$/)).join("\n").replace(/\n{3,}/g, "\n\n").trim();
|
|
4003
4193
|
}
|
|
4004
4194
|
async function convertCommand(options) {
|
|
4005
|
-
const projectPath = options.path ?
|
|
4195
|
+
const projectPath = options.path ? path27.resolve(options.path) : process.cwd();
|
|
4006
4196
|
const projectRoot = findProjectRoot(projectPath) ?? projectPath;
|
|
4007
4197
|
const from = options.from ?? "claude";
|
|
4008
4198
|
const to = options.to;
|
|
@@ -4011,10 +4201,10 @@ async function convertCommand(options) {
|
|
|
4011
4201
|
`);
|
|
4012
4202
|
process.exit(1);
|
|
4013
4203
|
}
|
|
4014
|
-
const claudeMdPath =
|
|
4204
|
+
const claudeMdPath = path27.join(projectRoot, "CLAUDE.md");
|
|
4015
4205
|
let content = "";
|
|
4016
4206
|
try {
|
|
4017
|
-
content =
|
|
4207
|
+
content = fs23.readFileSync(claudeMdPath, "utf-8");
|
|
4018
4208
|
} catch {
|
|
4019
4209
|
process.stderr.write(`Error: CLAUDE.md not found at ${claudeMdPath}
|
|
4020
4210
|
`);
|
|
@@ -4022,33 +4212,45 @@ async function convertCommand(options) {
|
|
|
4022
4212
|
}
|
|
4023
4213
|
if (to === "cursor") {
|
|
4024
4214
|
const files = claudeMdToCursorRules(content);
|
|
4025
|
-
const targetDir =
|
|
4215
|
+
const targetDir = path27.join(projectRoot, ".cursor", "rules");
|
|
4026
4216
|
process.stdout.write(`
|
|
4027
4217
|
Converting CLAUDE.md \u2192 ${files.length} Cursor rule file(s)
|
|
4028
|
-
|
|
4029
4218
|
`);
|
|
4219
|
+
if (!options.dryRun) {
|
|
4220
|
+
process.stdout.write(` \u26A0 Existing .mdc files will be overwritten. A backup is saved automatically.
|
|
4221
|
+
`);
|
|
4222
|
+
process.stdout.write(` Run 'claudectx revert --list' to see backups.
|
|
4223
|
+
`);
|
|
4224
|
+
}
|
|
4225
|
+
process.stdout.write("\n");
|
|
4030
4226
|
for (const file of files) {
|
|
4031
|
-
const filePath =
|
|
4032
|
-
const exists =
|
|
4227
|
+
const filePath = path27.join(targetDir, file.filename);
|
|
4228
|
+
const exists = fs23.existsSync(filePath);
|
|
4033
4229
|
const prefix = options.dryRun ? "[dry-run] " : exists ? "[overwrite] " : "";
|
|
4034
4230
|
process.stdout.write(` ${prefix}\u2192 .cursor/rules/${file.filename}
|
|
4035
4231
|
`);
|
|
4036
4232
|
if (!options.dryRun) {
|
|
4037
|
-
|
|
4038
|
-
|
|
4233
|
+
fs23.mkdirSync(targetDir, { recursive: true });
|
|
4234
|
+
if (exists) await backupFile(filePath, "convert");
|
|
4235
|
+
fs23.writeFileSync(filePath, file.content, "utf-8");
|
|
4039
4236
|
}
|
|
4040
4237
|
}
|
|
4041
4238
|
process.stdout.write("\n");
|
|
4042
4239
|
} else if (to === "copilot") {
|
|
4043
4240
|
const converted = claudeMdToCopilot(content);
|
|
4044
|
-
const targetPath =
|
|
4045
|
-
const exists =
|
|
4241
|
+
const targetPath = path27.join(projectRoot, ".github", "copilot-instructions.md");
|
|
4242
|
+
const exists = fs23.existsSync(targetPath);
|
|
4046
4243
|
process.stdout.write(`
|
|
4047
4244
|
Converting CLAUDE.md \u2192 .github/copilot-instructions.md${exists ? " [overwrite]" : ""}
|
|
4048
4245
|
`);
|
|
4246
|
+
if (!options.dryRun && exists) {
|
|
4247
|
+
process.stdout.write(` \u26A0 Existing file will be overwritten. Run 'claudectx revert --list' to undo.
|
|
4248
|
+
`);
|
|
4249
|
+
}
|
|
4049
4250
|
if (!options.dryRun) {
|
|
4050
|
-
|
|
4051
|
-
|
|
4251
|
+
fs23.mkdirSync(path27.dirname(targetPath), { recursive: true });
|
|
4252
|
+
if (exists) await backupFile(targetPath, "convert");
|
|
4253
|
+
fs23.writeFileSync(targetPath, converted, "utf-8");
|
|
4052
4254
|
process.stdout.write(` \u2713 Written to ${targetPath}
|
|
4053
4255
|
|
|
4054
4256
|
`);
|
|
@@ -4059,13 +4261,18 @@ Converting CLAUDE.md \u2192 .github/copilot-instructions.md${exists ? " [overwri
|
|
|
4059
4261
|
}
|
|
4060
4262
|
} else if (to === "windsurf") {
|
|
4061
4263
|
const converted = claudeMdToWindsurf(content);
|
|
4062
|
-
const targetPath =
|
|
4063
|
-
const exists =
|
|
4264
|
+
const targetPath = path27.join(projectRoot, ".windsurfrules");
|
|
4265
|
+
const exists = fs23.existsSync(targetPath);
|
|
4064
4266
|
process.stdout.write(`
|
|
4065
4267
|
Converting CLAUDE.md \u2192 .windsurfrules${exists ? " [overwrite]" : ""}
|
|
4066
4268
|
`);
|
|
4269
|
+
if (!options.dryRun && exists) {
|
|
4270
|
+
process.stdout.write(` \u26A0 Existing file will be overwritten. Run 'claudectx revert --list' to undo.
|
|
4271
|
+
`);
|
|
4272
|
+
}
|
|
4067
4273
|
if (!options.dryRun) {
|
|
4068
|
-
|
|
4274
|
+
if (exists) await backupFile(targetPath, "convert");
|
|
4275
|
+
fs23.writeFileSync(targetPath, converted, "utf-8");
|
|
4069
4276
|
process.stdout.write(` \u2713 Written to ${targetPath}
|
|
4070
4277
|
|
|
4071
4278
|
`);
|
|
@@ -4084,17 +4291,17 @@ Converting CLAUDE.md \u2192 .windsurfrules${exists ? " [overwrite]" : ""}
|
|
|
4084
4291
|
// src/commands/teams.ts
|
|
4085
4292
|
init_esm_shims();
|
|
4086
4293
|
init_models();
|
|
4087
|
-
import * as
|
|
4088
|
-
import * as
|
|
4294
|
+
import * as path29 from "path";
|
|
4295
|
+
import * as fs25 from "fs";
|
|
4089
4296
|
|
|
4090
4297
|
// src/reporter/team-aggregator.ts
|
|
4091
4298
|
init_esm_shims();
|
|
4092
4299
|
init_session_reader();
|
|
4093
4300
|
init_session_store();
|
|
4094
4301
|
init_models();
|
|
4095
|
-
import * as
|
|
4096
|
-
import * as
|
|
4097
|
-
import * as
|
|
4302
|
+
import * as fs24 from "fs";
|
|
4303
|
+
import * as path28 from "path";
|
|
4304
|
+
import * as os5 from "os";
|
|
4098
4305
|
import * as childProcess2 from "child_process";
|
|
4099
4306
|
function getDeveloperIdentity() {
|
|
4100
4307
|
try {
|
|
@@ -4102,7 +4309,7 @@ function getDeveloperIdentity() {
|
|
|
4102
4309
|
if (email) return email;
|
|
4103
4310
|
} catch {
|
|
4104
4311
|
}
|
|
4105
|
-
return
|
|
4312
|
+
return os5.hostname();
|
|
4106
4313
|
}
|
|
4107
4314
|
function calcCost3(inputTokens, outputTokens, cacheCreationTokens, cacheReadTokens, model) {
|
|
4108
4315
|
const p = MODEL_PRICING[model];
|
|
@@ -4169,7 +4376,8 @@ async function buildTeamExport(days, model, anonymize) {
|
|
|
4169
4376
|
);
|
|
4170
4377
|
const topWasteFiles = aggregateStats(fileEvents).slice(0, 10).map((s) => ({ filePath: s.filePath, readCount: s.readCount }));
|
|
4171
4378
|
const totalCostUsd = calcCost3(totalInput, totalOutput, 0, totalCacheRead, model);
|
|
4172
|
-
const
|
|
4379
|
+
const totalContext = totalInput + totalCacheRead;
|
|
4380
|
+
const cacheHitRate = totalContext > 0 ? Math.round(totalCacheRead / totalContext * 100) : 0;
|
|
4173
4381
|
const uniqueSessions = new Set(sessionFiles.map((f) => f.sessionId)).size;
|
|
4174
4382
|
const developer = {
|
|
4175
4383
|
identity: getDeveloperIdentity(),
|
|
@@ -4219,19 +4427,19 @@ function aggregateTeamReports(exports) {
|
|
|
4219
4427
|
}
|
|
4220
4428
|
function writeTeamExport(exportData) {
|
|
4221
4429
|
const storeDir = getStoreDir();
|
|
4222
|
-
if (!
|
|
4430
|
+
if (!fs24.existsSync(storeDir)) fs24.mkdirSync(storeDir, { recursive: true });
|
|
4223
4431
|
const date = isoDate2(/* @__PURE__ */ new Date());
|
|
4224
|
-
const filePath =
|
|
4225
|
-
|
|
4432
|
+
const filePath = path28.join(storeDir, `team-export-${date}.json`);
|
|
4433
|
+
fs24.writeFileSync(filePath, JSON.stringify(exportData, null, 2), "utf-8");
|
|
4226
4434
|
return filePath;
|
|
4227
4435
|
}
|
|
4228
4436
|
function readTeamExports(dir) {
|
|
4229
4437
|
const exports = [];
|
|
4230
|
-
if (!
|
|
4231
|
-
const files =
|
|
4438
|
+
if (!fs24.existsSync(dir)) return exports;
|
|
4439
|
+
const files = fs24.readdirSync(dir).filter((f) => f.match(/^team-export-.*\.json$/));
|
|
4232
4440
|
for (const file of files) {
|
|
4233
4441
|
try {
|
|
4234
|
-
const raw =
|
|
4442
|
+
const raw = fs24.readFileSync(path28.join(dir, file), "utf-8");
|
|
4235
4443
|
exports.push(JSON.parse(raw));
|
|
4236
4444
|
} catch {
|
|
4237
4445
|
}
|
|
@@ -4318,7 +4526,7 @@ Run "claudectx teams export" on each developer machine first.
|
|
|
4318
4526
|
process.stdout.write(" Top shared files (by read count across team):\n");
|
|
4319
4527
|
for (const f of report.topWasteFiles.slice(0, 5)) {
|
|
4320
4528
|
const devList = f.developers.slice(0, 3).join(", ");
|
|
4321
|
-
process.stdout.write(` ${f.readCount}x ${
|
|
4529
|
+
process.stdout.write(` ${f.readCount}x ${path29.basename(f.filePath)} (${devList})
|
|
4322
4530
|
`);
|
|
4323
4531
|
}
|
|
4324
4532
|
process.stdout.write("\n");
|
|
@@ -4331,24 +4539,24 @@ async function teamsShare(options) {
|
|
|
4331
4539
|
process.exit(1);
|
|
4332
4540
|
}
|
|
4333
4541
|
const storeDir = getStoreDir();
|
|
4334
|
-
const exportFiles =
|
|
4542
|
+
const exportFiles = fs25.readdirSync(storeDir).filter((f) => f.match(/^team-export-.*\.json$/)).sort().reverse();
|
|
4335
4543
|
if (exportFiles.length === 0) {
|
|
4336
4544
|
process.stderr.write('No team export files found. Run "claudectx teams export" first.\n');
|
|
4337
4545
|
process.exit(1);
|
|
4338
4546
|
}
|
|
4339
4547
|
const latest = exportFiles[0];
|
|
4340
|
-
const src =
|
|
4548
|
+
const src = path29.join(storeDir, latest);
|
|
4341
4549
|
let destPath;
|
|
4342
4550
|
try {
|
|
4343
|
-
const stat =
|
|
4344
|
-
destPath = stat.isDirectory() ?
|
|
4551
|
+
const stat = fs25.statSync(dest);
|
|
4552
|
+
destPath = stat.isDirectory() ? path29.join(dest, latest) : dest;
|
|
4345
4553
|
} catch {
|
|
4346
4554
|
destPath = dest;
|
|
4347
4555
|
}
|
|
4348
|
-
const destDir =
|
|
4556
|
+
const destDir = path29.dirname(path29.resolve(destPath));
|
|
4349
4557
|
let resolvedDir;
|
|
4350
4558
|
try {
|
|
4351
|
-
resolvedDir =
|
|
4559
|
+
resolvedDir = fs25.realpathSync(destDir);
|
|
4352
4560
|
} catch {
|
|
4353
4561
|
resolvedDir = destDir;
|
|
4354
4562
|
}
|
|
@@ -4358,7 +4566,7 @@ async function teamsShare(options) {
|
|
|
4358
4566
|
`);
|
|
4359
4567
|
process.exit(1);
|
|
4360
4568
|
}
|
|
4361
|
-
|
|
4569
|
+
fs25.copyFileSync(src, destPath);
|
|
4362
4570
|
process.stdout.write(` \u2713 Copied ${latest} \u2192 ${destPath}
|
|
4363
4571
|
|
|
4364
4572
|
`);
|
|
@@ -4383,8 +4591,148 @@ async function teamsCommand(subcommand, options) {
|
|
|
4383
4591
|
}
|
|
4384
4592
|
}
|
|
4385
4593
|
|
|
4594
|
+
// src/commands/revert.ts
|
|
4595
|
+
init_esm_shims();
|
|
4596
|
+
import * as path30 from "path";
|
|
4597
|
+
function timeAgo(isoString) {
|
|
4598
|
+
const diffMs = Date.now() - new Date(isoString).getTime();
|
|
4599
|
+
const minutes = Math.floor(diffMs / 6e4);
|
|
4600
|
+
if (minutes < 1) return "just now";
|
|
4601
|
+
if (minutes < 60) return `${minutes} min ago`;
|
|
4602
|
+
const hours = Math.floor(minutes / 60);
|
|
4603
|
+
if (hours < 24) return `${hours} hour${hours === 1 ? "" : "s"} ago`;
|
|
4604
|
+
const days = Math.floor(hours / 24);
|
|
4605
|
+
return `${days} day${days === 1 ? "" : "s"} ago`;
|
|
4606
|
+
}
|
|
4607
|
+
function formatBytes(bytes) {
|
|
4608
|
+
if (bytes < 1024) return `${bytes} B`;
|
|
4609
|
+
return `${Math.round(bytes / 1024)} KB`;
|
|
4610
|
+
}
|
|
4611
|
+
function printBackupTable(entries) {
|
|
4612
|
+
if (entries.length === 0) {
|
|
4613
|
+
process.stdout.write(" No backups found.\n");
|
|
4614
|
+
process.stdout.write(` Backups are created automatically when claudectx modifies your files.
|
|
4615
|
+
`);
|
|
4616
|
+
process.stdout.write(` Backup directory: ${BACKUP_DIR}
|
|
4617
|
+
|
|
4618
|
+
`);
|
|
4619
|
+
return;
|
|
4620
|
+
}
|
|
4621
|
+
const idWidth = 26;
|
|
4622
|
+
const fileWidth = 20;
|
|
4623
|
+
const cmdWidth = 10;
|
|
4624
|
+
const timeWidth = 14;
|
|
4625
|
+
const sizeWidth = 7;
|
|
4626
|
+
const hr = "\u2550".repeat(idWidth + fileWidth + cmdWidth + timeWidth + sizeWidth + 16);
|
|
4627
|
+
process.stdout.write("\n");
|
|
4628
|
+
process.stdout.write("claudectx \u2014 Backup History\n");
|
|
4629
|
+
process.stdout.write(hr + "\n");
|
|
4630
|
+
process.stdout.write(
|
|
4631
|
+
` ${"ID".padEnd(idWidth)} ${"File".padEnd(fileWidth)} ${"Command".padEnd(cmdWidth)} ${"When".padEnd(timeWidth)} ${"Size".padEnd(sizeWidth)}
|
|
4632
|
+
`
|
|
4633
|
+
);
|
|
4634
|
+
process.stdout.write("\u2500".repeat(idWidth + fileWidth + cmdWidth + timeWidth + sizeWidth + 16) + "\n");
|
|
4635
|
+
for (const entry of entries) {
|
|
4636
|
+
const id = entry.id.slice(0, idWidth).padEnd(idWidth);
|
|
4637
|
+
const file = path30.basename(entry.originalPath).slice(0, fileWidth).padEnd(fileWidth);
|
|
4638
|
+
const cmd = entry.command.slice(0, cmdWidth).padEnd(cmdWidth);
|
|
4639
|
+
const when = timeAgo(entry.createdAt).slice(0, timeWidth).padEnd(timeWidth);
|
|
4640
|
+
const size = formatBytes(entry.sizeBytes).padEnd(sizeWidth);
|
|
4641
|
+
process.stdout.write(` ${id} ${file} ${cmd} ${when} ${size}
|
|
4642
|
+
`);
|
|
4643
|
+
}
|
|
4644
|
+
process.stdout.write("\n");
|
|
4645
|
+
process.stdout.write(` Backup directory: ${BACKUP_DIR}
|
|
4646
|
+
`);
|
|
4647
|
+
process.stdout.write(" To restore: claudectx revert --id <ID>\n\n");
|
|
4648
|
+
}
|
|
4649
|
+
async function interactivePick(entries) {
|
|
4650
|
+
try {
|
|
4651
|
+
const { select } = await import("@inquirer/prompts");
|
|
4652
|
+
const choices = entries.map((e) => ({
|
|
4653
|
+
name: `${timeAgo(e.createdAt).padEnd(14)} ${path30.basename(e.originalPath).padEnd(16)} [${e.command}] ${e.id}`,
|
|
4654
|
+
value: e.id
|
|
4655
|
+
}));
|
|
4656
|
+
choices.push({ name: "Cancel", value: "" });
|
|
4657
|
+
return await select({ message: "Choose a backup to restore:", choices });
|
|
4658
|
+
} catch {
|
|
4659
|
+
process.stderr.write("Interactive mode unavailable. Use --id <id> to restore a specific backup.\n");
|
|
4660
|
+
return null;
|
|
4661
|
+
}
|
|
4662
|
+
}
|
|
4663
|
+
async function doRestore(id) {
|
|
4664
|
+
const chalk5 = (await import("chalk")).default;
|
|
4665
|
+
process.stdout.write("\n");
|
|
4666
|
+
try {
|
|
4667
|
+
const entries = await listBackups();
|
|
4668
|
+
const entry = entries.find((e) => e.id === id);
|
|
4669
|
+
if (!entry) {
|
|
4670
|
+
process.stderr.write(chalk5.red(`Backup "${id}" not found.
|
|
4671
|
+
`));
|
|
4672
|
+
process.stderr.write('Run "claudectx revert --list" to see available backups.\n');
|
|
4673
|
+
process.exitCode = 1;
|
|
4674
|
+
return;
|
|
4675
|
+
}
|
|
4676
|
+
process.stdout.write(chalk5.yellow(`\u26A0 This will overwrite: ${entry.originalPath}
|
|
4677
|
+
`));
|
|
4678
|
+
process.stdout.write(` Backup from: ${timeAgo(entry.createdAt)} (${entry.command})
|
|
4679
|
+
`);
|
|
4680
|
+
process.stdout.write(` Your current file will be backed up first (so you can undo this).
|
|
4681
|
+
|
|
4682
|
+
`);
|
|
4683
|
+
let confirmed = true;
|
|
4684
|
+
try {
|
|
4685
|
+
const { confirm: confirm2 } = await import("@inquirer/prompts");
|
|
4686
|
+
confirmed = await confirm2({ message: "Restore this backup?", default: false });
|
|
4687
|
+
} catch {
|
|
4688
|
+
}
|
|
4689
|
+
if (!confirmed) {
|
|
4690
|
+
process.stdout.write(" Cancelled.\n\n");
|
|
4691
|
+
return;
|
|
4692
|
+
}
|
|
4693
|
+
const { undoEntry } = await restoreBackup(id);
|
|
4694
|
+
process.stdout.write(chalk5.green(" \u2713 ") + `Restored to ${entry.originalPath}
|
|
4695
|
+
`);
|
|
4696
|
+
if (undoEntry) {
|
|
4697
|
+
process.stdout.write(
|
|
4698
|
+
chalk5.dim(` Your previous version was saved as backup "${undoEntry.id}" \u2014 run 'claudectx revert --id ${undoEntry.id}' to undo.
|
|
4699
|
+
`)
|
|
4700
|
+
);
|
|
4701
|
+
}
|
|
4702
|
+
process.stdout.write("\n");
|
|
4703
|
+
} catch (err) {
|
|
4704
|
+
process.stderr.write(chalk5.red(`Error: ${err instanceof Error ? err.message : String(err)}
|
|
4705
|
+
`));
|
|
4706
|
+
process.exitCode = 1;
|
|
4707
|
+
}
|
|
4708
|
+
}
|
|
4709
|
+
async function revertCommand(options) {
|
|
4710
|
+
const entries = await listBackups(options.file);
|
|
4711
|
+
if (options.json) {
|
|
4712
|
+
process.stdout.write(JSON.stringify({ backups: entries }, null, 2) + "\n");
|
|
4713
|
+
return;
|
|
4714
|
+
}
|
|
4715
|
+
if (options.list) {
|
|
4716
|
+
printBackupTable(entries);
|
|
4717
|
+
return;
|
|
4718
|
+
}
|
|
4719
|
+
if (options.id) {
|
|
4720
|
+
await doRestore(options.id);
|
|
4721
|
+
return;
|
|
4722
|
+
}
|
|
4723
|
+
if (entries.length === 0) {
|
|
4724
|
+
process.stdout.write("\n No backups found. Backups are created automatically when claudectx modifies your files.\n\n");
|
|
4725
|
+
return;
|
|
4726
|
+
}
|
|
4727
|
+
printBackupTable(entries);
|
|
4728
|
+
const picked = await interactivePick(entries);
|
|
4729
|
+
if (picked) {
|
|
4730
|
+
await doRestore(picked);
|
|
4731
|
+
}
|
|
4732
|
+
}
|
|
4733
|
+
|
|
4386
4734
|
// src/index.ts
|
|
4387
|
-
var VERSION = "1.1.
|
|
4735
|
+
var VERSION = "1.1.4";
|
|
4388
4736
|
var DESCRIPTION = "Reduce Claude Code token usage by up to 80%. Context analyzer, auto-optimizer, live dashboard, and smart MCP tools.";
|
|
4389
4737
|
var program = new Command();
|
|
4390
4738
|
program.name("claudectx").description(DESCRIPTION).version(VERSION);
|
|
@@ -4424,5 +4772,8 @@ program.command("convert").description("Convert CLAUDE.md to another AI assistan
|
|
|
4424
4772
|
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) => {
|
|
4425
4773
|
await teamsCommand(subcommand ?? "export", options);
|
|
4426
4774
|
});
|
|
4775
|
+
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) => {
|
|
4776
|
+
await revertCommand(options);
|
|
4777
|
+
});
|
|
4427
4778
|
program.parse();
|
|
4428
4779
|
//# sourceMappingURL=index.mjs.map
|