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.js
CHANGED
|
@@ -124,15 +124,15 @@ __export(session_store_exports, {
|
|
|
124
124
|
readAllEvents: () => readAllEvents
|
|
125
125
|
});
|
|
126
126
|
function getStoreDirPath() {
|
|
127
|
-
return
|
|
127
|
+
return path9.join(os3.homedir(), ".claudectx");
|
|
128
128
|
}
|
|
129
129
|
function getReadsFilePath_() {
|
|
130
|
-
return
|
|
130
|
+
return path9.join(getStoreDirPath(), "reads.jsonl");
|
|
131
131
|
}
|
|
132
132
|
function ensureStoreDir() {
|
|
133
133
|
const dir = getStoreDirPath();
|
|
134
|
-
if (!
|
|
135
|
-
|
|
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
|
-
|
|
145
|
+
fs9.appendFileSync(getReadsFilePath_(), JSON.stringify(event) + "\n", "utf-8");
|
|
146
146
|
}
|
|
147
147
|
function readAllEvents() {
|
|
148
148
|
const readsFile = getReadsFilePath_();
|
|
149
|
-
if (!
|
|
150
|
-
const lines =
|
|
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 (
|
|
180
|
-
|
|
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
|
|
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
|
-
|
|
195
|
-
|
|
196
|
-
|
|
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 (!
|
|
202
|
+
if (!fs10.existsSync(CLAUDE_PROJECTS_DIR)) return [];
|
|
203
203
|
const results = [];
|
|
204
204
|
try {
|
|
205
|
-
const projectDirs =
|
|
205
|
+
const projectDirs = fs10.readdirSync(CLAUDE_PROJECTS_DIR);
|
|
206
206
|
for (const projectDir of projectDirs) {
|
|
207
|
-
const projectPath =
|
|
207
|
+
const projectPath = path10.join(CLAUDE_PROJECTS_DIR, projectDir);
|
|
208
208
|
try {
|
|
209
|
-
const stat =
|
|
209
|
+
const stat = fs10.statSync(projectPath);
|
|
210
210
|
if (!stat.isDirectory()) continue;
|
|
211
|
-
const files =
|
|
211
|
+
const files = fs10.readdirSync(projectPath).filter((f) => f.endsWith(".jsonl"));
|
|
212
212
|
for (const file of files) {
|
|
213
|
-
const filePath =
|
|
213
|
+
const filePath = path10.join(projectPath, file);
|
|
214
214
|
try {
|
|
215
|
-
const fstat =
|
|
215
|
+
const fstat = fs10.statSync(filePath);
|
|
216
216
|
results.push({
|
|
217
217
|
filePath,
|
|
218
218
|
mtimeMs: fstat.mtimeMs,
|
|
219
|
-
sessionId:
|
|
219
|
+
sessionId: path10.basename(file, ".jsonl"),
|
|
220
220
|
projectDir
|
|
221
221
|
});
|
|
222
222
|
} catch {
|
|
@@ -247,7 +247,7 @@ async function readSessionUsage(sessionFilePath) {
|
|
|
247
247
|
cacheReadTokens: 0,
|
|
248
248
|
requestCount: 0
|
|
249
249
|
};
|
|
250
|
-
if (!
|
|
250
|
+
if (!fs10.existsSync(sessionFilePath)) return result;
|
|
251
251
|
const { createReadStream } = await import("fs");
|
|
252
252
|
const { createInterface } = await import("readline");
|
|
253
253
|
try {
|
|
@@ -277,15 +277,15 @@ async function readSessionUsage(sessionFilePath) {
|
|
|
277
277
|
}
|
|
278
278
|
return result;
|
|
279
279
|
}
|
|
280
|
-
var
|
|
280
|
+
var fs10, os4, path10, CLAUDE_PROJECTS_DIR;
|
|
281
281
|
var init_session_reader = __esm({
|
|
282
282
|
"src/watcher/session-reader.ts"() {
|
|
283
283
|
"use strict";
|
|
284
284
|
init_cjs_shims();
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
CLAUDE_PROJECTS_DIR =
|
|
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");
|
|
289
289
|
}
|
|
290
290
|
});
|
|
291
291
|
|
|
@@ -303,7 +303,7 @@ function fmtCost(tokens, model) {
|
|
|
303
303
|
return `$${cost.toFixed(4)}`;
|
|
304
304
|
}
|
|
305
305
|
function shortPath(filePath) {
|
|
306
|
-
const parts = filePath.split(
|
|
306
|
+
const parts = filePath.split(path11.sep);
|
|
307
307
|
if (parts.length <= 3) return filePath;
|
|
308
308
|
return "\u2026/" + parts.slice(-3).join("/");
|
|
309
309
|
}
|
|
@@ -425,9 +425,9 @@ function Dashboard({
|
|
|
425
425
|
const readsFile = getReadsFilePath();
|
|
426
426
|
let watcher = null;
|
|
427
427
|
const tryWatch = () => {
|
|
428
|
-
if (
|
|
428
|
+
if (fs11.existsSync(readsFile)) {
|
|
429
429
|
try {
|
|
430
|
-
watcher =
|
|
430
|
+
watcher = fs11.watch(readsFile, () => refresh());
|
|
431
431
|
} catch {
|
|
432
432
|
}
|
|
433
433
|
}
|
|
@@ -465,7 +465,7 @@ function Dashboard({
|
|
|
465
465
|
] }),
|
|
466
466
|
sessionFile && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_ink.Text, { dimColor: true, children: [
|
|
467
467
|
" \u2014 ",
|
|
468
|
-
|
|
468
|
+
path11.basename(sessionFile, ".jsonl").slice(0, 8),
|
|
469
469
|
"\u2026"
|
|
470
470
|
] }),
|
|
471
471
|
!sessionFile && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_ink.Text, { dimColor: true, children: " \u2014 no session file found" })
|
|
@@ -481,7 +481,7 @@ function Dashboard({
|
|
|
481
481
|
] })
|
|
482
482
|
] });
|
|
483
483
|
}
|
|
484
|
-
var import_react, import_ink,
|
|
484
|
+
var import_react, import_ink, fs11, path11, import_jsx_runtime;
|
|
485
485
|
var init_Dashboard = __esm({
|
|
486
486
|
"src/components/Dashboard.tsx"() {
|
|
487
487
|
"use strict";
|
|
@@ -491,15 +491,15 @@ var init_Dashboard = __esm({
|
|
|
491
491
|
init_session_store();
|
|
492
492
|
init_session_reader();
|
|
493
493
|
init_models();
|
|
494
|
-
|
|
495
|
-
|
|
494
|
+
fs11 = __toESM(require("fs"));
|
|
495
|
+
path11 = __toESM(require("path"));
|
|
496
496
|
import_jsx_runtime = require("react/jsx-runtime");
|
|
497
497
|
}
|
|
498
498
|
});
|
|
499
499
|
|
|
500
500
|
// src/mcp/smart-reader.ts
|
|
501
501
|
function detectLanguage(filePath) {
|
|
502
|
-
const ext =
|
|
502
|
+
const ext = path14.extname(filePath).toLowerCase();
|
|
503
503
|
switch (ext) {
|
|
504
504
|
case ".ts":
|
|
505
505
|
case ".tsx":
|
|
@@ -516,8 +516,8 @@ function detectLanguage(filePath) {
|
|
|
516
516
|
}
|
|
517
517
|
}
|
|
518
518
|
function findSymbol(filePath, symbolName) {
|
|
519
|
-
if (!
|
|
520
|
-
const content =
|
|
519
|
+
if (!fs13.existsSync(filePath)) return null;
|
|
520
|
+
const content = fs13.readFileSync(filePath, "utf-8");
|
|
521
521
|
const lines = content.split("\n");
|
|
522
522
|
const lang = detectLanguage(filePath);
|
|
523
523
|
const patterns = lang === "python" ? PYTHON_PATTERNS : TS_JS_PATTERNS;
|
|
@@ -578,8 +578,8 @@ function findPythonBlockEnd(lines, startIdx) {
|
|
|
578
578
|
return lines.length;
|
|
579
579
|
}
|
|
580
580
|
function extractLineRange(filePath, startLine, endLine, contextLines = 0) {
|
|
581
|
-
if (!
|
|
582
|
-
const allLines =
|
|
581
|
+
if (!fs13.existsSync(filePath)) return null;
|
|
582
|
+
const allLines = fs13.readFileSync(filePath, "utf-8").split("\n");
|
|
583
583
|
const totalLines = allLines.length;
|
|
584
584
|
const from = Math.max(0, startLine - 1 - contextLines);
|
|
585
585
|
const to = Math.min(totalLines, endLine + contextLines);
|
|
@@ -594,7 +594,7 @@ function extractLineRange(filePath, startLine, endLine, contextLines = 0) {
|
|
|
594
594
|
};
|
|
595
595
|
}
|
|
596
596
|
function smartRead(filePath, symbol, startLine, endLine, contextLines = 3) {
|
|
597
|
-
if (!
|
|
597
|
+
if (!fs13.existsSync(filePath)) {
|
|
598
598
|
throw new Error(`File not found: ${filePath}`);
|
|
599
599
|
}
|
|
600
600
|
if (symbol) {
|
|
@@ -606,7 +606,7 @@ function smartRead(filePath, symbol, startLine, endLine, contextLines = 3) {
|
|
|
606
606
|
filePath,
|
|
607
607
|
startLine: extracted.startLine,
|
|
608
608
|
endLine: extracted.endLine,
|
|
609
|
-
totalLines:
|
|
609
|
+
totalLines: fs13.readFileSync(filePath, "utf-8").split("\n").length,
|
|
610
610
|
truncated: false,
|
|
611
611
|
symbolName: symbol
|
|
612
612
|
};
|
|
@@ -618,7 +618,7 @@ function smartRead(filePath, symbol, startLine, endLine, contextLines = 3) {
|
|
|
618
618
|
return { ...result, truncated: false };
|
|
619
619
|
}
|
|
620
620
|
}
|
|
621
|
-
const fullContent =
|
|
621
|
+
const fullContent = fs13.readFileSync(filePath, "utf-8");
|
|
622
622
|
const allLines = fullContent.split("\n");
|
|
623
623
|
const totalLines = allLines.length;
|
|
624
624
|
const fullTokens = countTokens(fullContent);
|
|
@@ -654,13 +654,13 @@ function smartRead(filePath, symbol, startLine, endLine, contextLines = 3) {
|
|
|
654
654
|
truncated: true
|
|
655
655
|
};
|
|
656
656
|
}
|
|
657
|
-
var
|
|
657
|
+
var fs13, path14, TS_JS_PATTERNS, PYTHON_PATTERNS, MAX_FULL_FILE_TOKENS;
|
|
658
658
|
var init_smart_reader = __esm({
|
|
659
659
|
"src/mcp/smart-reader.ts"() {
|
|
660
660
|
"use strict";
|
|
661
661
|
init_cjs_shims();
|
|
662
|
-
|
|
663
|
-
|
|
662
|
+
fs13 = __toESM(require("fs"));
|
|
663
|
+
path14 = __toESM(require("path"));
|
|
664
664
|
init_tokenizer();
|
|
665
665
|
TS_JS_PATTERNS = [
|
|
666
666
|
// export async function name / export function name / function name
|
|
@@ -699,7 +699,7 @@ function extractSymbolsFromFile(filePath) {
|
|
|
699
699
|
if (lang === "other") return [];
|
|
700
700
|
let content;
|
|
701
701
|
try {
|
|
702
|
-
content =
|
|
702
|
+
content = fs14.readFileSync(filePath, "utf-8");
|
|
703
703
|
} catch {
|
|
704
704
|
return [];
|
|
705
705
|
}
|
|
@@ -728,13 +728,13 @@ function extractSymbolsFromFile(filePath) {
|
|
|
728
728
|
}
|
|
729
729
|
return results;
|
|
730
730
|
}
|
|
731
|
-
var
|
|
731
|
+
var fs14, path15, import_glob, TS_JS_EXTRACTORS, PYTHON_EXTRACTORS, SOURCE_GLOBS, IGNORE_DIRS, SymbolIndex, globalIndex;
|
|
732
732
|
var init_symbol_index = __esm({
|
|
733
733
|
"src/mcp/symbol-index.ts"() {
|
|
734
734
|
"use strict";
|
|
735
735
|
init_cjs_shims();
|
|
736
|
-
|
|
737
|
-
|
|
736
|
+
fs14 = __toESM(require("fs"));
|
|
737
|
+
path15 = __toESM(require("path"));
|
|
738
738
|
import_glob = require("glob");
|
|
739
739
|
init_smart_reader();
|
|
740
740
|
TS_JS_EXTRACTORS = [
|
|
@@ -787,8 +787,8 @@ var init_symbol_index = __esm({
|
|
|
787
787
|
this.entries = [];
|
|
788
788
|
let files = [];
|
|
789
789
|
try {
|
|
790
|
-
files = await (0, import_glob.glob)(SOURCE_GLOBS.map((g) =>
|
|
791
|
-
ignore: IGNORE_DIRS.map((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)),
|
|
792
792
|
absolute: true
|
|
793
793
|
});
|
|
794
794
|
} catch {
|
|
@@ -842,7 +842,7 @@ __export(server_exports, {
|
|
|
842
842
|
startMcpServer: () => startMcpServer
|
|
843
843
|
});
|
|
844
844
|
function handleSmartRead(args) {
|
|
845
|
-
const filePath =
|
|
845
|
+
const filePath = path16.resolve(args.file);
|
|
846
846
|
const result = smartRead(
|
|
847
847
|
filePath,
|
|
848
848
|
args.symbol,
|
|
@@ -878,7 +878,7 @@ Example: index_project({ "project_root": "${process.cwd()}" })`;
|
|
|
878
878
|
];
|
|
879
879
|
for (let i = 0; i < results.length; i++) {
|
|
880
880
|
const r = results[i];
|
|
881
|
-
const rel =
|
|
881
|
+
const rel = path16.relative(process.cwd(), r.filePath);
|
|
882
882
|
lines.push(`${i + 1}. [${r.type}] ${r.name}`);
|
|
883
883
|
lines.push(` ${rel}:${r.lineStart}`);
|
|
884
884
|
lines.push(` ${r.signature.trim()}`);
|
|
@@ -890,7 +890,7 @@ Example: index_project({ "project_root": "${process.cwd()}" })`;
|
|
|
890
890
|
return lines.join("\n");
|
|
891
891
|
}
|
|
892
892
|
async function handleIndexProject(args) {
|
|
893
|
-
const projectRoot = args.project_root ?
|
|
893
|
+
const projectRoot = args.project_root ? path16.resolve(args.project_root) : process.cwd();
|
|
894
894
|
const fn = args.rebuild ? () => globalIndex.rebuild(projectRoot) : () => globalIndex.build(projectRoot);
|
|
895
895
|
const { fileCount, symbolCount } = await fn();
|
|
896
896
|
if (fileCount === 0 && globalIndex.isReady) {
|
|
@@ -938,12 +938,12 @@ async function startMcpServer() {
|
|
|
938
938
|
await server.connect(transport);
|
|
939
939
|
process.stderr.write("[claudectx mcp] Server started (stdio)\n");
|
|
940
940
|
}
|
|
941
|
-
var
|
|
941
|
+
var path16, import_server, import_stdio, import_types, TOOLS;
|
|
942
942
|
var init_server = __esm({
|
|
943
943
|
"src/mcp/server.ts"() {
|
|
944
944
|
"use strict";
|
|
945
945
|
init_cjs_shims();
|
|
946
|
-
|
|
946
|
+
path16 = __toESM(require("path"));
|
|
947
947
|
import_server = require("@modelcontextprotocol/sdk/server/index.js");
|
|
948
948
|
import_stdio = require("@modelcontextprotocol/sdk/server/stdio.js");
|
|
949
949
|
import_types = require("@modelcontextprotocol/sdk/types.js");
|
|
@@ -1581,8 +1581,8 @@ async function analyzeCommand(options) {
|
|
|
1581
1581
|
|
|
1582
1582
|
// src/commands/optimize.ts
|
|
1583
1583
|
init_cjs_shims();
|
|
1584
|
-
var
|
|
1585
|
-
var
|
|
1584
|
+
var fs8 = __toESM(require("fs"));
|
|
1585
|
+
var path8 = __toESM(require("path"));
|
|
1586
1586
|
var import_chalk3 = __toESM(require("chalk"));
|
|
1587
1587
|
var import_boxen2 = __toESM(require("boxen"));
|
|
1588
1588
|
var import_prompts = require("@inquirer/prompts");
|
|
@@ -1745,9 +1745,132 @@ function writeIgnorefile(result) {
|
|
|
1745
1745
|
|
|
1746
1746
|
// src/optimizer/claudemd-splitter.ts
|
|
1747
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();
|
|
1748
1754
|
var fs4 = __toESM(require("fs"));
|
|
1755
|
+
var os2 = __toESM(require("os"));
|
|
1749
1756
|
var path5 = __toESM(require("path"));
|
|
1750
|
-
|
|
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
|
|
1751
1874
|
var SPLIT_MIN_TOKENS = 300;
|
|
1752
1875
|
function slugify(title) {
|
|
1753
1876
|
return title.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
|
|
@@ -1782,9 +1905,9 @@ function parseSections(content) {
|
|
|
1782
1905
|
return sections;
|
|
1783
1906
|
}
|
|
1784
1907
|
function planSplit(claudeMdPath, sectionsToExtract) {
|
|
1785
|
-
const content =
|
|
1908
|
+
const content = fs5.readFileSync(claudeMdPath, "utf-8");
|
|
1786
1909
|
const sections = parseSections(content);
|
|
1787
|
-
const claudeDir =
|
|
1910
|
+
const claudeDir = path6.join(path6.dirname(claudeMdPath), ".claude");
|
|
1788
1911
|
const extractedFiles = [];
|
|
1789
1912
|
let newContent = "";
|
|
1790
1913
|
let tokensSaved = 0;
|
|
@@ -1797,7 +1920,7 @@ function planSplit(claudeMdPath, sectionsToExtract) {
|
|
|
1797
1920
|
usedSlugs.set(slug, count + 1);
|
|
1798
1921
|
const filename = `${slug}.md`;
|
|
1799
1922
|
const relRefPath = `.claude/${filename}`;
|
|
1800
|
-
const filePath =
|
|
1923
|
+
const filePath = path6.join(claudeDir, filename);
|
|
1801
1924
|
const refBlock = `## ${section.title}
|
|
1802
1925
|
|
|
1803
1926
|
@${relRefPath}
|
|
@@ -1821,21 +1944,24 @@ function planSplit(claudeMdPath, sectionsToExtract) {
|
|
|
1821
1944
|
tokensSaved: Math.max(0, tokensSaved)
|
|
1822
1945
|
};
|
|
1823
1946
|
}
|
|
1824
|
-
function applySplit(result) {
|
|
1947
|
+
async function applySplit(result) {
|
|
1825
1948
|
if (result.extractedFiles.length === 0) return;
|
|
1826
|
-
|
|
1827
|
-
|
|
1828
|
-
|
|
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 });
|
|
1829
1955
|
}
|
|
1830
1956
|
for (const file of result.extractedFiles) {
|
|
1831
|
-
|
|
1957
|
+
fs5.writeFileSync(file.filePath, file.content, "utf-8");
|
|
1832
1958
|
}
|
|
1833
|
-
|
|
1959
|
+
fs5.writeFileSync(result.claudeMdPath, result.newClaudeMd, "utf-8");
|
|
1834
1960
|
}
|
|
1835
1961
|
|
|
1836
1962
|
// src/optimizer/cache-applier.ts
|
|
1837
1963
|
init_cjs_shims();
|
|
1838
|
-
var
|
|
1964
|
+
var fs6 = __toESM(require("fs"));
|
|
1839
1965
|
function findCacheBusters(content) {
|
|
1840
1966
|
const fixes = [];
|
|
1841
1967
|
const lines = content.split("\n");
|
|
@@ -1865,18 +1991,21 @@ function applyCacheFixes(content, fixes) {
|
|
|
1865
1991
|
return lines.join("\n");
|
|
1866
1992
|
}
|
|
1867
1993
|
function planCacheFixes(claudeMdPath) {
|
|
1868
|
-
const content =
|
|
1994
|
+
const content = fs6.readFileSync(claudeMdPath, "utf-8");
|
|
1869
1995
|
const fixes = findCacheBusters(content);
|
|
1870
1996
|
return { fixes, newContent: applyCacheFixes(content, fixes) };
|
|
1871
1997
|
}
|
|
1872
|
-
function applyAndWriteCacheFixes(claudeMdPath, result) {
|
|
1873
|
-
|
|
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");
|
|
1874
2003
|
}
|
|
1875
2004
|
|
|
1876
2005
|
// src/optimizer/hooks-installer.ts
|
|
1877
2006
|
init_cjs_shims();
|
|
1878
|
-
var
|
|
1879
|
-
var
|
|
2007
|
+
var fs7 = __toESM(require("fs"));
|
|
2008
|
+
var path7 = __toESM(require("path"));
|
|
1880
2009
|
var CLAUDECTX_HOOKS = {
|
|
1881
2010
|
PostToolUse: [
|
|
1882
2011
|
{
|
|
@@ -1894,13 +2023,13 @@ var CLAUDECTX_HOOKS = {
|
|
|
1894
2023
|
]
|
|
1895
2024
|
};
|
|
1896
2025
|
function planHooksInstall(projectRoot) {
|
|
1897
|
-
const claudeDir =
|
|
1898
|
-
const settingsPath =
|
|
1899
|
-
const existed =
|
|
2026
|
+
const claudeDir = path7.join(projectRoot, ".claude");
|
|
2027
|
+
const settingsPath = path7.join(claudeDir, "settings.local.json");
|
|
2028
|
+
const existed = fs7.existsSync(settingsPath);
|
|
1900
2029
|
let existing = {};
|
|
1901
2030
|
if (existed) {
|
|
1902
2031
|
try {
|
|
1903
|
-
existing = JSON.parse(
|
|
2032
|
+
existing = JSON.parse(fs7.readFileSync(settingsPath, "utf-8"));
|
|
1904
2033
|
} catch {
|
|
1905
2034
|
existing = {};
|
|
1906
2035
|
}
|
|
@@ -1921,25 +2050,25 @@ function planHooksInstall(projectRoot) {
|
|
|
1921
2050
|
return { settingsPath, existed, mergedSettings };
|
|
1922
2051
|
}
|
|
1923
2052
|
function applyHooksInstall(result) {
|
|
1924
|
-
const dir =
|
|
1925
|
-
if (!
|
|
1926
|
-
|
|
2053
|
+
const dir = path7.dirname(result.settingsPath);
|
|
2054
|
+
if (!fs7.existsSync(dir)) {
|
|
2055
|
+
fs7.mkdirSync(dir, { recursive: true });
|
|
1927
2056
|
}
|
|
1928
|
-
|
|
2057
|
+
fs7.writeFileSync(result.settingsPath, JSON.stringify(result.mergedSettings, null, 2) + "\n", "utf-8");
|
|
1929
2058
|
}
|
|
1930
2059
|
function writeHooksSettings(projectRoot, mergedSettings) {
|
|
1931
|
-
const settingsPath =
|
|
1932
|
-
const dir =
|
|
1933
|
-
if (!
|
|
1934
|
-
|
|
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 });
|
|
1935
2064
|
}
|
|
1936
|
-
|
|
2065
|
+
fs7.writeFileSync(settingsPath, JSON.stringify(mergedSettings, null, 2) + "\n", "utf-8");
|
|
1937
2066
|
}
|
|
1938
2067
|
function isAlreadyInstalled(projectRoot) {
|
|
1939
|
-
const settingsPath =
|
|
1940
|
-
if (!
|
|
2068
|
+
const settingsPath = path7.join(projectRoot, ".claude", "settings.local.json");
|
|
2069
|
+
if (!fs7.existsSync(settingsPath)) return false;
|
|
1941
2070
|
try {
|
|
1942
|
-
const settings = JSON.parse(
|
|
2071
|
+
const settings = JSON.parse(fs7.readFileSync(settingsPath, "utf-8"));
|
|
1943
2072
|
const postToolUse = settings?.hooks?.PostToolUse ?? [];
|
|
1944
2073
|
return postToolUse.some((h) => h.matcher === "Read");
|
|
1945
2074
|
} catch {
|
|
@@ -1949,7 +2078,7 @@ function isAlreadyInstalled(projectRoot) {
|
|
|
1949
2078
|
|
|
1950
2079
|
// src/commands/optimize.ts
|
|
1951
2080
|
async function optimizeCommand(options) {
|
|
1952
|
-
const projectPath = options.path ?
|
|
2081
|
+
const projectPath = options.path ? path8.resolve(options.path) : findProjectRoot() ?? process.cwd();
|
|
1953
2082
|
const dryRun = options.dryRun ?? false;
|
|
1954
2083
|
const autoApply = options.apply ?? false;
|
|
1955
2084
|
const specificMode = options.claudemd || options.ignorefile || options.cache || options.hooks;
|
|
@@ -2074,12 +2203,12 @@ async function runIgnorefile(projectRoot, dryRun, autoApply) {
|
|
|
2074
2203
|
}
|
|
2075
2204
|
async function runClaudeMdSplit(projectRoot, report, dryRun, autoApply) {
|
|
2076
2205
|
printSectionHeader("CLAUDE.md \u2192 @files");
|
|
2077
|
-
const claudeMdPath =
|
|
2078
|
-
if (!
|
|
2206
|
+
const claudeMdPath = path8.join(projectRoot, "CLAUDE.md");
|
|
2207
|
+
if (!fs8.existsSync(claudeMdPath)) {
|
|
2079
2208
|
logger.warn("No CLAUDE.md found \u2014 skipping.");
|
|
2080
2209
|
return;
|
|
2081
2210
|
}
|
|
2082
|
-
const content =
|
|
2211
|
+
const content = fs8.readFileSync(claudeMdPath, "utf-8");
|
|
2083
2212
|
const sections = parseSections(content);
|
|
2084
2213
|
const largeSections = sections.filter(
|
|
2085
2214
|
(s) => !s.isPreamble && s.tokens >= SPLIT_MIN_TOKENS
|
|
@@ -2135,18 +2264,18 @@ Would extract ${splitResult.extractedFiles.length} section(s) to .claude/`
|
|
|
2135
2264
|
logger.info("Skipped.");
|
|
2136
2265
|
return;
|
|
2137
2266
|
}
|
|
2138
|
-
applySplit(splitResult);
|
|
2267
|
+
await applySplit(splitResult);
|
|
2139
2268
|
logger.success(
|
|
2140
2269
|
`Extracted ${splitResult.extractedFiles.length} section(s). Saved ~${splitResult.tokensSaved} tokens/request.`
|
|
2141
2270
|
);
|
|
2142
2271
|
for (const f of splitResult.extractedFiles) {
|
|
2143
|
-
logger.info(` Created: ${import_chalk3.default.cyan(
|
|
2272
|
+
logger.info(` Created: ${import_chalk3.default.cyan(path8.relative(projectRoot, f.filePath))}`);
|
|
2144
2273
|
}
|
|
2145
2274
|
}
|
|
2146
2275
|
async function runCacheOptimization(projectRoot, dryRun, autoApply) {
|
|
2147
2276
|
printSectionHeader("Prompt cache optimisation");
|
|
2148
|
-
const claudeMdPath =
|
|
2149
|
-
if (!
|
|
2277
|
+
const claudeMdPath = path8.join(projectRoot, "CLAUDE.md");
|
|
2278
|
+
if (!fs8.existsSync(claudeMdPath)) {
|
|
2150
2279
|
logger.warn("No CLAUDE.md found \u2014 skipping.");
|
|
2151
2280
|
return;
|
|
2152
2281
|
}
|
|
@@ -2174,14 +2303,14 @@ async function runCacheOptimization(projectRoot, dryRun, autoApply) {
|
|
|
2174
2303
|
logger.info("Skipped.");
|
|
2175
2304
|
return;
|
|
2176
2305
|
}
|
|
2177
|
-
applyAndWriteCacheFixes(claudeMdPath, result);
|
|
2306
|
+
await applyAndWriteCacheFixes(claudeMdPath, result);
|
|
2178
2307
|
logger.success(`Fixed ${result.fixes.length} cache-busting pattern(s) in CLAUDE.md.`);
|
|
2179
2308
|
}
|
|
2180
2309
|
async function runHooks(projectRoot, dryRun, autoApply) {
|
|
2181
2310
|
printSectionHeader("Session hooks");
|
|
2182
2311
|
const result = planHooksInstall(projectRoot);
|
|
2183
2312
|
logger.info(
|
|
2184
|
-
`Settings file: ${import_chalk3.default.cyan(
|
|
2313
|
+
`Settings file: ${import_chalk3.default.cyan(path8.relative(projectRoot, result.settingsPath))}`
|
|
2185
2314
|
);
|
|
2186
2315
|
logger.info(result.existed ? "Will merge with existing settings." : "Will create new file.");
|
|
2187
2316
|
console.log(import_chalk3.default.dim("\n Hooks to install:"));
|
|
@@ -2196,7 +2325,7 @@ async function runHooks(projectRoot, dryRun, autoApply) {
|
|
|
2196
2325
|
}
|
|
2197
2326
|
applyHooksInstall(result);
|
|
2198
2327
|
logger.success(
|
|
2199
|
-
`Hooks installed \u2192 ${import_chalk3.default.cyan(
|
|
2328
|
+
`Hooks installed \u2192 ${import_chalk3.default.cyan(path8.relative(projectRoot, result.settingsPath))}`
|
|
2200
2329
|
);
|
|
2201
2330
|
}
|
|
2202
2331
|
function printSectionHeader(title) {
|
|
@@ -2206,7 +2335,7 @@ function printSectionHeader(title) {
|
|
|
2206
2335
|
|
|
2207
2336
|
// src/commands/watch.ts
|
|
2208
2337
|
init_cjs_shims();
|
|
2209
|
-
var
|
|
2338
|
+
var path12 = __toESM(require("path"));
|
|
2210
2339
|
init_session_store();
|
|
2211
2340
|
init_models();
|
|
2212
2341
|
async function watchCommand(options) {
|
|
@@ -2244,30 +2373,30 @@ async function handleLogStdin() {
|
|
|
2244
2373
|
const payload = JSON.parse(raw);
|
|
2245
2374
|
const filePath = payload.tool_input?.file_path;
|
|
2246
2375
|
if (filePath) {
|
|
2247
|
-
appendFileRead(
|
|
2376
|
+
appendFileRead(path12.resolve(filePath), payload.session_id);
|
|
2248
2377
|
}
|
|
2249
2378
|
} catch {
|
|
2250
2379
|
}
|
|
2251
2380
|
}
|
|
2252
2381
|
function readStdin() {
|
|
2253
|
-
return new Promise((
|
|
2382
|
+
return new Promise((resolve13) => {
|
|
2254
2383
|
let data = "";
|
|
2255
2384
|
process.stdin.setEncoding("utf-8");
|
|
2256
2385
|
process.stdin.on("data", (chunk) => data += chunk);
|
|
2257
|
-
process.stdin.on("end", () =>
|
|
2258
|
-
setTimeout(() =>
|
|
2386
|
+
process.stdin.on("end", () => resolve13(data));
|
|
2387
|
+
setTimeout(() => resolve13(data), 500);
|
|
2259
2388
|
});
|
|
2260
2389
|
}
|
|
2261
2390
|
|
|
2262
2391
|
// src/commands/mcp.ts
|
|
2263
2392
|
init_cjs_shims();
|
|
2264
|
-
var
|
|
2393
|
+
var path17 = __toESM(require("path"));
|
|
2265
2394
|
var import_chalk4 = __toESM(require("chalk"));
|
|
2266
2395
|
|
|
2267
2396
|
// src/mcp/installer.ts
|
|
2268
2397
|
init_cjs_shims();
|
|
2269
|
-
var
|
|
2270
|
-
var
|
|
2398
|
+
var fs12 = __toESM(require("fs"));
|
|
2399
|
+
var path13 = __toESM(require("path"));
|
|
2271
2400
|
var SERVER_NAME = "claudectx";
|
|
2272
2401
|
var SERVER_ENTRY = {
|
|
2273
2402
|
command: "claudectx",
|
|
@@ -2275,13 +2404,13 @@ var SERVER_ENTRY = {
|
|
|
2275
2404
|
type: "stdio"
|
|
2276
2405
|
};
|
|
2277
2406
|
function planInstall(projectRoot) {
|
|
2278
|
-
const claudeDir =
|
|
2279
|
-
const settingsPath =
|
|
2280
|
-
const existed =
|
|
2407
|
+
const claudeDir = path13.join(projectRoot, ".claude");
|
|
2408
|
+
const settingsPath = path13.join(claudeDir, "settings.json");
|
|
2409
|
+
const existed = fs12.existsSync(settingsPath);
|
|
2281
2410
|
let existing = {};
|
|
2282
2411
|
if (existed) {
|
|
2283
2412
|
try {
|
|
2284
|
-
existing = JSON.parse(
|
|
2413
|
+
existing = JSON.parse(fs12.readFileSync(settingsPath, "utf-8"));
|
|
2285
2414
|
} catch {
|
|
2286
2415
|
existing = {};
|
|
2287
2416
|
}
|
|
@@ -2298,21 +2427,21 @@ function planInstall(projectRoot) {
|
|
|
2298
2427
|
return { settingsPath, existed, alreadyInstalled, mergedSettings };
|
|
2299
2428
|
}
|
|
2300
2429
|
function applyInstall(result) {
|
|
2301
|
-
const dir =
|
|
2302
|
-
if (!
|
|
2303
|
-
|
|
2430
|
+
const dir = path13.dirname(result.settingsPath);
|
|
2431
|
+
if (!fs12.existsSync(dir)) {
|
|
2432
|
+
fs12.mkdirSync(dir, { recursive: true });
|
|
2304
2433
|
}
|
|
2305
|
-
|
|
2434
|
+
fs12.writeFileSync(
|
|
2306
2435
|
result.settingsPath,
|
|
2307
2436
|
JSON.stringify(result.mergedSettings, null, 2) + "\n",
|
|
2308
2437
|
"utf-8"
|
|
2309
2438
|
);
|
|
2310
2439
|
}
|
|
2311
2440
|
function isInstalled(projectRoot) {
|
|
2312
|
-
const settingsPath =
|
|
2313
|
-
if (!
|
|
2441
|
+
const settingsPath = path13.join(projectRoot, ".claude", "settings.json");
|
|
2442
|
+
if (!fs12.existsSync(settingsPath)) return false;
|
|
2314
2443
|
try {
|
|
2315
|
-
const settings = JSON.parse(
|
|
2444
|
+
const settings = JSON.parse(fs12.readFileSync(settingsPath, "utf-8"));
|
|
2316
2445
|
return SERVER_NAME in (settings.mcpServers ?? {});
|
|
2317
2446
|
} catch {
|
|
2318
2447
|
return false;
|
|
@@ -2321,7 +2450,7 @@ function isInstalled(projectRoot) {
|
|
|
2321
2450
|
|
|
2322
2451
|
// src/commands/mcp.ts
|
|
2323
2452
|
async function mcpCommand(options) {
|
|
2324
|
-
const projectRoot = options.path ?
|
|
2453
|
+
const projectRoot = options.path ? path17.resolve(options.path) : process.cwd();
|
|
2325
2454
|
if (options.install) {
|
|
2326
2455
|
await runInstall(projectRoot);
|
|
2327
2456
|
return;
|
|
@@ -2369,13 +2498,13 @@ async function runInstall(projectRoot) {
|
|
|
2369
2498
|
|
|
2370
2499
|
// src/commands/compress.ts
|
|
2371
2500
|
init_cjs_shims();
|
|
2372
|
-
var
|
|
2373
|
-
var
|
|
2501
|
+
var path19 = __toESM(require("path"));
|
|
2502
|
+
var fs17 = __toESM(require("fs"));
|
|
2374
2503
|
init_session_reader();
|
|
2375
2504
|
|
|
2376
2505
|
// src/compressor/session-parser.ts
|
|
2377
2506
|
init_cjs_shims();
|
|
2378
|
-
var
|
|
2507
|
+
var fs15 = __toESM(require("fs"));
|
|
2379
2508
|
function extractText(content) {
|
|
2380
2509
|
if (!content) return "";
|
|
2381
2510
|
if (typeof content === "string") return content;
|
|
@@ -2386,10 +2515,10 @@ function extractToolCalls(content) {
|
|
|
2386
2515
|
return content.filter((b) => b.type === "tool_use" && b.name).map((b) => ({ tool: b.name, input: b.input ?? {} }));
|
|
2387
2516
|
}
|
|
2388
2517
|
function parseSessionFile(sessionFilePath) {
|
|
2389
|
-
if (!
|
|
2518
|
+
if (!fs15.existsSync(sessionFilePath)) return null;
|
|
2390
2519
|
let content;
|
|
2391
2520
|
try {
|
|
2392
|
-
content =
|
|
2521
|
+
content = fs15.readFileSync(sessionFilePath, "utf-8");
|
|
2393
2522
|
} catch {
|
|
2394
2523
|
return null;
|
|
2395
2524
|
}
|
|
@@ -2589,13 +2718,13 @@ function calcCost(inputTokens, outputTokens) {
|
|
|
2589
2718
|
|
|
2590
2719
|
// src/compressor/memory-writer.ts
|
|
2591
2720
|
init_cjs_shims();
|
|
2592
|
-
var
|
|
2593
|
-
var
|
|
2721
|
+
var fs16 = __toESM(require("fs"));
|
|
2722
|
+
var path18 = __toESM(require("path"));
|
|
2594
2723
|
function parseMemoryFile(filePath) {
|
|
2595
|
-
if (!
|
|
2724
|
+
if (!fs16.existsSync(filePath)) {
|
|
2596
2725
|
return { preamble: "", entries: [] };
|
|
2597
2726
|
}
|
|
2598
|
-
const content =
|
|
2727
|
+
const content = fs16.readFileSync(filePath, "utf-8");
|
|
2599
2728
|
const markerRegex = /<!-- claudectx-entry: (\d{4}-\d{2}-\d{2}) \| session: ([a-z0-9-]+) -->/g;
|
|
2600
2729
|
const indices = [];
|
|
2601
2730
|
let match;
|
|
@@ -2646,15 +2775,15 @@ function appendEntry(memoryFilePath, sessionId, summaryText, date = /* @__PURE__
|
|
|
2646
2775
|
const newBlock = buildEntryBlock(sessionId, summaryText, date);
|
|
2647
2776
|
const allBlocks = [...entries.map((e) => e.raw), newBlock];
|
|
2648
2777
|
const newContent = (preamble.trimEnd() ? preamble.trimEnd() + "\n\n" : "") + allBlocks.join("\n\n") + "\n";
|
|
2649
|
-
const dir =
|
|
2650
|
-
if (!
|
|
2651
|
-
|
|
2778
|
+
const dir = path18.dirname(memoryFilePath);
|
|
2779
|
+
if (!fs16.existsSync(dir)) {
|
|
2780
|
+
fs16.mkdirSync(dir, { recursive: true });
|
|
2652
2781
|
}
|
|
2653
|
-
|
|
2782
|
+
fs16.writeFileSync(memoryFilePath, newContent, "utf-8");
|
|
2654
2783
|
return newContent;
|
|
2655
2784
|
}
|
|
2656
2785
|
function pruneOldEntries(memoryFilePath, days) {
|
|
2657
|
-
if (!
|
|
2786
|
+
if (!fs16.existsSync(memoryFilePath)) {
|
|
2658
2787
|
return { removed: 0, kept: 0, removedEntries: [] };
|
|
2659
2788
|
}
|
|
2660
2789
|
const { preamble, entries } = parseMemoryFile(memoryFilePath);
|
|
@@ -2667,7 +2796,7 @@ function pruneOldEntries(memoryFilePath, days) {
|
|
|
2667
2796
|
return { removed: 0, kept: kept.length, removedEntries: [] };
|
|
2668
2797
|
}
|
|
2669
2798
|
const newContent = (preamble.trimEnd() ? preamble.trimEnd() + "\n\n" : "") + kept.map((e) => e.raw).join("\n\n") + (kept.length > 0 ? "\n" : "");
|
|
2670
|
-
|
|
2799
|
+
fs16.writeFileSync(memoryFilePath, newContent, "utf-8");
|
|
2671
2800
|
return { removed: removed.length, kept: kept.length, removedEntries: removed };
|
|
2672
2801
|
}
|
|
2673
2802
|
function isAlreadyCompressed(memoryFilePath, sessionId) {
|
|
@@ -2678,8 +2807,8 @@ function isAlreadyCompressed(memoryFilePath, sessionId) {
|
|
|
2678
2807
|
// src/commands/compress.ts
|
|
2679
2808
|
async function compressCommand(options) {
|
|
2680
2809
|
const chalk5 = (await import("chalk")).default;
|
|
2681
|
-
const projectRoot = options.path ?
|
|
2682
|
-
const memoryFilePath =
|
|
2810
|
+
const projectRoot = options.path ? path19.resolve(options.path) : process.cwd();
|
|
2811
|
+
const memoryFilePath = path19.join(projectRoot, "MEMORY.md");
|
|
2683
2812
|
const sessionFiles = listSessionFiles();
|
|
2684
2813
|
if (sessionFiles.length === 0) {
|
|
2685
2814
|
process.stdout.write(chalk5.red("No Claude Code sessions found.\n"));
|
|
@@ -2704,7 +2833,7 @@ async function compressCommand(options) {
|
|
|
2704
2833
|
} else {
|
|
2705
2834
|
targetFile = sessionFiles[0].filePath;
|
|
2706
2835
|
}
|
|
2707
|
-
const sessionId =
|
|
2836
|
+
const sessionId = path19.basename(targetFile, ".jsonl");
|
|
2708
2837
|
if (isAlreadyCompressed(memoryFilePath, sessionId)) {
|
|
2709
2838
|
if (!options.auto) {
|
|
2710
2839
|
process.stdout.write(chalk5.yellow(`Session ${sessionId.slice(0, 8)}\u2026 is already in MEMORY.md \u2014 skipping.
|
|
@@ -2752,11 +2881,27 @@ async function compressCommand(options) {
|
|
|
2752
2881
|
}
|
|
2753
2882
|
if (options.prune) {
|
|
2754
2883
|
const days = parseInt(options.days ?? "30", 10);
|
|
2755
|
-
if (!
|
|
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");
|
|
2756
2901
|
const pruned = pruneOldEntries(memoryFilePath, days);
|
|
2757
2902
|
if (pruned.removed > 0 && !options.auto) {
|
|
2758
2903
|
process.stdout.write(
|
|
2759
|
-
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.
|
|
2760
2905
|
`)
|
|
2761
2906
|
);
|
|
2762
2907
|
}
|
|
@@ -2832,7 +2977,8 @@ async function aggregateUsage(days, model = "claude-sonnet-4-6") {
|
|
|
2832
2977
|
readCount: s.readCount
|
|
2833
2978
|
}));
|
|
2834
2979
|
const totalCost = calcCost2(totalInput, totalOutput, totalCacheCreation, totalCacheRead, model);
|
|
2835
|
-
const
|
|
2980
|
+
const totalContext = totalInput + totalCacheRead;
|
|
2981
|
+
const cacheHitRate = totalContext > 0 ? Math.round(totalCacheRead / totalContext * 100) : 0;
|
|
2836
2982
|
const byDay = [...bucketMap.values()].sort((a, b) => a.date.localeCompare(b.date));
|
|
2837
2983
|
const uniqueSessions = new Set(sessionFiles.map((f) => f.sessionId)).size;
|
|
2838
2984
|
const dailyAvgCostUsd = days > 0 ? totalCost / days : 0;
|
|
@@ -3034,12 +3180,12 @@ async function reportCommand(options) {
|
|
|
3034
3180
|
|
|
3035
3181
|
// src/commands/budget.ts
|
|
3036
3182
|
init_cjs_shims();
|
|
3037
|
-
var
|
|
3183
|
+
var path21 = __toESM(require("path"));
|
|
3038
3184
|
|
|
3039
3185
|
// src/analyzer/budget-estimator.ts
|
|
3040
3186
|
init_cjs_shims();
|
|
3041
|
-
var
|
|
3042
|
-
var
|
|
3187
|
+
var fs18 = __toESM(require("fs"));
|
|
3188
|
+
var path20 = __toESM(require("path"));
|
|
3043
3189
|
var import_glob2 = require("glob");
|
|
3044
3190
|
init_tokenizer();
|
|
3045
3191
|
init_session_store();
|
|
@@ -3065,20 +3211,20 @@ function classifyCacheHit(recentReadCount) {
|
|
|
3065
3211
|
return "low";
|
|
3066
3212
|
}
|
|
3067
3213
|
function suggestClaudeignoreAdditions(files, projectRoot) {
|
|
3068
|
-
const ignorePath =
|
|
3214
|
+
const ignorePath = path20.join(projectRoot, ".claudeignore");
|
|
3069
3215
|
let ignorePatterns = [];
|
|
3070
3216
|
try {
|
|
3071
|
-
const content =
|
|
3217
|
+
const content = fs18.readFileSync(ignorePath, "utf-8");
|
|
3072
3218
|
ignorePatterns = content.split("\n").filter(Boolean);
|
|
3073
3219
|
} catch {
|
|
3074
3220
|
}
|
|
3075
3221
|
const recommendations = [];
|
|
3076
3222
|
for (const file of files) {
|
|
3077
3223
|
if (file.tokenCount <= WASTE_THRESHOLDS.MAX_REFERENCE_FILE_TOKENS) continue;
|
|
3078
|
-
const rel =
|
|
3224
|
+
const rel = path20.relative(projectRoot, file.filePath);
|
|
3079
3225
|
const alreadyIgnored = ignorePatterns.some((pattern) => {
|
|
3080
3226
|
const cleanPattern = pattern.replace(/^!/, "");
|
|
3081
|
-
return rel.startsWith(cleanPattern.replace(/\*/g, "").replace(/\//g,
|
|
3227
|
+
return rel.startsWith(cleanPattern.replace(/\*/g, "").replace(/\//g, path20.sep));
|
|
3082
3228
|
});
|
|
3083
3229
|
if (!alreadyIgnored) {
|
|
3084
3230
|
recommendations.push(rel);
|
|
@@ -3098,7 +3244,7 @@ async function estimateBudget(globs, projectRoot, model, thresholdTokens) {
|
|
|
3098
3244
|
for (const filePath of filePaths) {
|
|
3099
3245
|
let content = "";
|
|
3100
3246
|
try {
|
|
3101
|
-
content =
|
|
3247
|
+
content = fs18.readFileSync(filePath, "utf-8");
|
|
3102
3248
|
} catch {
|
|
3103
3249
|
continue;
|
|
3104
3250
|
}
|
|
@@ -3147,7 +3293,7 @@ function formatBudgetReport(report) {
|
|
|
3147
3293
|
}
|
|
3148
3294
|
const LIKELIHOOD_ICON = { high: "\u{1F7E2}", medium: "\u{1F7E1}", low: "\u{1F534}" };
|
|
3149
3295
|
const maxPathLen = Math.min(
|
|
3150
|
-
Math.max(...report.files.map((f) =>
|
|
3296
|
+
Math.max(...report.files.map((f) => path20.basename(f.filePath).length)),
|
|
3151
3297
|
40
|
|
3152
3298
|
);
|
|
3153
3299
|
lines.push(
|
|
@@ -3155,7 +3301,7 @@ function formatBudgetReport(report) {
|
|
|
3155
3301
|
);
|
|
3156
3302
|
lines.push("\u2500".repeat(50));
|
|
3157
3303
|
for (const file of report.files.slice(0, 20)) {
|
|
3158
|
-
const name =
|
|
3304
|
+
const name = path20.basename(file.filePath).slice(0, maxPathLen).padEnd(maxPathLen);
|
|
3159
3305
|
const tokens = file.tokenCount.toLocaleString().padStart(7);
|
|
3160
3306
|
const cache = `${LIKELIHOOD_ICON[file.cacheHitLikelihood]} ${file.cacheHitLikelihood.padEnd(6)}`;
|
|
3161
3307
|
const cost = formatCost(file.estimatedCostUsd).padStart(7);
|
|
@@ -3184,7 +3330,7 @@ function formatBudgetReport(report) {
|
|
|
3184
3330
|
// src/commands/budget.ts
|
|
3185
3331
|
init_models();
|
|
3186
3332
|
async function budgetCommand(globs, options) {
|
|
3187
|
-
const projectPath = options.path ?
|
|
3333
|
+
const projectPath = options.path ? path21.resolve(options.path) : process.cwd();
|
|
3188
3334
|
const projectRoot = findProjectRoot(projectPath) ?? projectPath;
|
|
3189
3335
|
const model = resolveModel(options.model ?? "sonnet");
|
|
3190
3336
|
const thresholdTokens = parseInt(options.threshold ?? "10000", 10);
|
|
@@ -3203,7 +3349,7 @@ async function budgetCommand(globs, options) {
|
|
|
3203
3349
|
|
|
3204
3350
|
// src/commands/warmup.ts
|
|
3205
3351
|
init_cjs_shims();
|
|
3206
|
-
var
|
|
3352
|
+
var path22 = __toESM(require("path"));
|
|
3207
3353
|
var import_sdk = __toESM(require("@anthropic-ai/sdk"));
|
|
3208
3354
|
init_models();
|
|
3209
3355
|
var import_fs3 = __toESM(require("fs"));
|
|
@@ -3277,6 +3423,19 @@ async function installCron(cronExpr) {
|
|
|
3277
3423
|
);
|
|
3278
3424
|
process.exit(1);
|
|
3279
3425
|
}
|
|
3426
|
+
let confirmed = true;
|
|
3427
|
+
try {
|
|
3428
|
+
const { confirm: confirm2 } = await import("@inquirer/prompts");
|
|
3429
|
+
confirmed = await confirm2({
|
|
3430
|
+
message: `Install cron job "${cronExpr} claudectx warmup" in your system crontab?`,
|
|
3431
|
+
default: false
|
|
3432
|
+
});
|
|
3433
|
+
} catch {
|
|
3434
|
+
}
|
|
3435
|
+
if (!confirmed) {
|
|
3436
|
+
process.stdout.write(" Cron install cancelled.\n");
|
|
3437
|
+
return;
|
|
3438
|
+
}
|
|
3280
3439
|
const { execSync: execSync3 } = await import("child_process");
|
|
3281
3440
|
const command = `claudectx warmup`;
|
|
3282
3441
|
const cronLine = `${cronExpr} ${command}`;
|
|
@@ -3299,16 +3458,16 @@ async function installCron(cronExpr) {
|
|
|
3299
3458
|
${marker}
|
|
3300
3459
|
${cronLine}
|
|
3301
3460
|
`;
|
|
3302
|
-
const { writeFileSync:
|
|
3461
|
+
const { writeFileSync: writeFileSync12, unlinkSync: unlinkSync3 } = await import("fs");
|
|
3303
3462
|
const { tmpdir } = await import("os");
|
|
3304
|
-
const { join:
|
|
3305
|
-
const tmpFile =
|
|
3463
|
+
const { join: join18 } = await import("path");
|
|
3464
|
+
const tmpFile = join18(tmpdir(), `claudectx-cron-${Date.now()}.txt`);
|
|
3306
3465
|
try {
|
|
3307
|
-
|
|
3466
|
+
writeFileSync12(tmpFile, newCrontab, "utf-8");
|
|
3308
3467
|
execSync3(`crontab ${tmpFile}`, { stdio: ["pipe", "pipe", "pipe"] });
|
|
3309
3468
|
} finally {
|
|
3310
3469
|
try {
|
|
3311
|
-
|
|
3470
|
+
unlinkSync3(tmpFile);
|
|
3312
3471
|
} catch {
|
|
3313
3472
|
}
|
|
3314
3473
|
}
|
|
@@ -3325,7 +3484,7 @@ ${cronLine}
|
|
|
3325
3484
|
}
|
|
3326
3485
|
}
|
|
3327
3486
|
async function warmupCommand(options) {
|
|
3328
|
-
const projectPath = options.path ?
|
|
3487
|
+
const projectPath = options.path ? path22.resolve(options.path) : process.cwd();
|
|
3329
3488
|
const projectRoot = findProjectRoot(projectPath) ?? projectPath;
|
|
3330
3489
|
const model = resolveModel(options.model ?? "haiku");
|
|
3331
3490
|
const ttl = options.ttl === "60" ? 60 : 5;
|
|
@@ -3337,7 +3496,7 @@ async function warmupCommand(options) {
|
|
|
3337
3496
|
process.exit(1);
|
|
3338
3497
|
}
|
|
3339
3498
|
let claudeMdContent = "";
|
|
3340
|
-
const claudeMdPath =
|
|
3499
|
+
const claudeMdPath = path22.join(projectRoot, "CLAUDE.md");
|
|
3341
3500
|
try {
|
|
3342
3501
|
claudeMdContent = import_fs3.default.readFileSync(claudeMdPath, "utf-8");
|
|
3343
3502
|
} catch {
|
|
@@ -3397,13 +3556,13 @@ async function warmupCommand(options) {
|
|
|
3397
3556
|
|
|
3398
3557
|
// src/commands/drift.ts
|
|
3399
3558
|
init_cjs_shims();
|
|
3400
|
-
var
|
|
3401
|
-
var
|
|
3559
|
+
var path24 = __toESM(require("path"));
|
|
3560
|
+
var fs21 = __toESM(require("fs"));
|
|
3402
3561
|
|
|
3403
3562
|
// src/analyzer/drift-detector.ts
|
|
3404
3563
|
init_cjs_shims();
|
|
3405
|
-
var
|
|
3406
|
-
var
|
|
3564
|
+
var fs20 = __toESM(require("fs"));
|
|
3565
|
+
var path23 = __toESM(require("path"));
|
|
3407
3566
|
var childProcess = __toESM(require("child_process"));
|
|
3408
3567
|
init_tokenizer();
|
|
3409
3568
|
init_session_store();
|
|
@@ -3416,8 +3575,8 @@ function findDeadAtReferences(content, projectRoot) {
|
|
|
3416
3575
|
const match = lines[i].match(AT_REF_RE);
|
|
3417
3576
|
if (!match) continue;
|
|
3418
3577
|
const ref = match[1].trim();
|
|
3419
|
-
const absPath =
|
|
3420
|
-
if (!
|
|
3578
|
+
const absPath = path23.isAbsolute(ref) ? ref : path23.join(projectRoot, ref);
|
|
3579
|
+
if (!fs20.existsSync(absPath)) {
|
|
3421
3580
|
const lineText = lines[i];
|
|
3422
3581
|
issues.push({
|
|
3423
3582
|
type: "dead-ref",
|
|
@@ -3446,19 +3605,33 @@ async function findGitDeletedMentions(content, projectRoot) {
|
|
|
3446
3605
|
return [];
|
|
3447
3606
|
}
|
|
3448
3607
|
if (deletedFiles.size === 0) return [];
|
|
3608
|
+
const deletedTerms = [];
|
|
3609
|
+
for (const deleted of deletedFiles) {
|
|
3610
|
+
const stem = path23.basename(deleted, path23.extname(deleted));
|
|
3611
|
+
const basenameWithExt = path23.basename(deleted);
|
|
3612
|
+
if (stem.length < 4) continue;
|
|
3613
|
+
deletedTerms.push({ basename: basenameWithExt, fullPath: deleted });
|
|
3614
|
+
}
|
|
3615
|
+
if (deletedTerms.length === 0) return [];
|
|
3449
3616
|
const lines = content.split("\n");
|
|
3450
3617
|
for (let i = 0; i < lines.length; i++) {
|
|
3451
3618
|
const line = lines[i];
|
|
3452
|
-
|
|
3453
|
-
|
|
3454
|
-
|
|
3619
|
+
const lineLower = line.toLowerCase();
|
|
3620
|
+
for (const { basename: basename10, fullPath } of deletedTerms) {
|
|
3621
|
+
const matchesFullPath = lineLower.includes(fullPath.toLowerCase());
|
|
3622
|
+
const basenameLower = basename10.toLowerCase();
|
|
3623
|
+
const idx = lineLower.indexOf(basenameLower);
|
|
3624
|
+
const matchesBasename = idx !== -1 && // Check left boundary: start of string, space, slash, quote, backtick, or `@`
|
|
3625
|
+
(idx === 0 || /[\s/`'"@(]/.test(line[idx - 1])) && // Check right boundary: end of string, space, punctuation, or extension dot
|
|
3626
|
+
(idx + basenameLower.length >= line.length || /[\s/`'",.):@]/.test(line[idx + basenameLower.length]));
|
|
3627
|
+
if (matchesFullPath || matchesBasename) {
|
|
3455
3628
|
issues.push({
|
|
3456
3629
|
type: "git-deleted",
|
|
3457
3630
|
line: i + 1,
|
|
3458
3631
|
text: line.trim(),
|
|
3459
3632
|
severity: "warning",
|
|
3460
3633
|
estimatedTokenWaste: countTokens(line),
|
|
3461
|
-
suggestion: `References "${
|
|
3634
|
+
suggestion: `References "${basename10}" which was deleted from git. Consider removing this mention.`
|
|
3462
3635
|
});
|
|
3463
3636
|
break;
|
|
3464
3637
|
}
|
|
@@ -3523,8 +3696,8 @@ function findDeadInlinePaths(content, projectRoot) {
|
|
|
3523
3696
|
const rawPath = match[1].trim();
|
|
3524
3697
|
if (seen.has(rawPath)) continue;
|
|
3525
3698
|
seen.add(rawPath);
|
|
3526
|
-
const absPath =
|
|
3527
|
-
if (!
|
|
3699
|
+
const absPath = path23.isAbsolute(rawPath) ? rawPath : path23.join(projectRoot, rawPath);
|
|
3700
|
+
if (!fs20.existsSync(absPath)) {
|
|
3528
3701
|
issues.push({
|
|
3529
3702
|
type: "dead-inline-path",
|
|
3530
3703
|
line: i + 1,
|
|
@@ -3540,10 +3713,10 @@ function findDeadInlinePaths(content, projectRoot) {
|
|
|
3540
3713
|
return issues;
|
|
3541
3714
|
}
|
|
3542
3715
|
async function detectDrift(projectRoot, dayWindow) {
|
|
3543
|
-
const claudeMdPath =
|
|
3716
|
+
const claudeMdPath = path23.join(projectRoot, "CLAUDE.md");
|
|
3544
3717
|
let content = "";
|
|
3545
3718
|
try {
|
|
3546
|
-
content =
|
|
3719
|
+
content = fs20.readFileSync(claudeMdPath, "utf-8");
|
|
3547
3720
|
} catch {
|
|
3548
3721
|
return {
|
|
3549
3722
|
claudeMdPath,
|
|
@@ -3585,7 +3758,7 @@ var TYPE_LABEL = {
|
|
|
3585
3758
|
"dead-inline-path": "Dead path"
|
|
3586
3759
|
};
|
|
3587
3760
|
async function driftCommand(options) {
|
|
3588
|
-
const projectPath = options.path ?
|
|
3761
|
+
const projectPath = options.path ? path24.resolve(options.path) : process.cwd();
|
|
3589
3762
|
const projectRoot = findProjectRoot(projectPath) ?? projectPath;
|
|
3590
3763
|
const dayWindow = parseInt(options.days ?? "30", 10);
|
|
3591
3764
|
const report = await detectDrift(projectRoot, dayWindow);
|
|
@@ -3661,25 +3834,25 @@ async function applyFix(claudeMdPath, issues) {
|
|
|
3661
3834
|
process.stdout.write("No lines selected. Nothing changed.\n");
|
|
3662
3835
|
return;
|
|
3663
3836
|
}
|
|
3664
|
-
const content =
|
|
3837
|
+
const content = fs21.readFileSync(claudeMdPath, "utf-8");
|
|
3665
3838
|
const lines = content.split("\n");
|
|
3666
3839
|
const lineSet = new Set(selectedLines.map((l) => l - 1));
|
|
3667
3840
|
const newLines = lines.filter((_, i) => !lineSet.has(i));
|
|
3668
3841
|
const newContent = newLines.join("\n");
|
|
3669
3842
|
const backupPath = `${claudeMdPath}.bak`;
|
|
3670
|
-
|
|
3671
|
-
const
|
|
3843
|
+
fs21.writeFileSync(backupPath, content, "utf-8");
|
|
3844
|
+
const os6 = await import("os");
|
|
3672
3845
|
const tmpPath = `${claudeMdPath}.tmp-${Date.now()}`;
|
|
3673
3846
|
try {
|
|
3674
|
-
|
|
3675
|
-
|
|
3847
|
+
fs21.writeFileSync(tmpPath, newContent, "utf-8");
|
|
3848
|
+
fs21.renameSync(tmpPath, claudeMdPath);
|
|
3676
3849
|
} catch (err) {
|
|
3677
3850
|
try {
|
|
3678
|
-
|
|
3851
|
+
fs21.copyFileSync(backupPath, claudeMdPath);
|
|
3679
3852
|
} catch {
|
|
3680
3853
|
}
|
|
3681
3854
|
try {
|
|
3682
|
-
|
|
3855
|
+
fs21.unlinkSync(tmpPath);
|
|
3683
3856
|
} catch {
|
|
3684
3857
|
}
|
|
3685
3858
|
process.stderr.write(`Error writing CLAUDE.md: ${err instanceof Error ? err.message : String(err)}
|
|
@@ -3687,18 +3860,18 @@ async function applyFix(claudeMdPath, issues) {
|
|
|
3687
3860
|
process.exit(1);
|
|
3688
3861
|
}
|
|
3689
3862
|
process.stdout.write(`
|
|
3690
|
-
\u2713 Removed ${selectedLines.length} line(s) from ${
|
|
3863
|
+
\u2713 Removed ${selectedLines.length} line(s) from ${path24.basename(claudeMdPath)}
|
|
3691
3864
|
`);
|
|
3692
3865
|
process.stdout.write(` \u2713 Backup saved to ${backupPath}
|
|
3693
3866
|
|
|
3694
3867
|
`);
|
|
3695
|
-
void
|
|
3868
|
+
void os6;
|
|
3696
3869
|
}
|
|
3697
3870
|
|
|
3698
3871
|
// src/commands/hooks.ts
|
|
3699
3872
|
init_cjs_shims();
|
|
3700
|
-
var
|
|
3701
|
-
var
|
|
3873
|
+
var fs22 = __toESM(require("fs"));
|
|
3874
|
+
var path25 = __toESM(require("path"));
|
|
3702
3875
|
|
|
3703
3876
|
// src/hooks/registry.ts
|
|
3704
3877
|
init_cjs_shims();
|
|
@@ -3774,17 +3947,17 @@ function buildHookEntry(def, config) {
|
|
|
3774
3947
|
|
|
3775
3948
|
// src/commands/hooks.ts
|
|
3776
3949
|
function readInstalledHooks(projectRoot) {
|
|
3777
|
-
const settingsPath =
|
|
3778
|
-
if (!
|
|
3950
|
+
const settingsPath = path25.join(projectRoot, ".claude", "settings.local.json");
|
|
3951
|
+
if (!fs22.existsSync(settingsPath)) return {};
|
|
3779
3952
|
try {
|
|
3780
|
-
return JSON.parse(
|
|
3953
|
+
return JSON.parse(fs22.readFileSync(settingsPath, "utf-8"));
|
|
3781
3954
|
} catch {
|
|
3782
3955
|
process.stderr.write(
|
|
3783
3956
|
`Warning: ${settingsPath} exists but contains invalid JSON. Existing settings will be preserved as a backup.
|
|
3784
3957
|
`
|
|
3785
3958
|
);
|
|
3786
3959
|
try {
|
|
3787
|
-
|
|
3960
|
+
fs22.copyFileSync(settingsPath, `${settingsPath}.bak`);
|
|
3788
3961
|
} catch {
|
|
3789
3962
|
}
|
|
3790
3963
|
return {};
|
|
@@ -3904,9 +4077,26 @@ async function hooksAdd(name, projectRoot, configPairs) {
|
|
|
3904
4077
|
}
|
|
3905
4078
|
async function hooksRemove(name, projectRoot) {
|
|
3906
4079
|
const settings = readInstalledHooks(projectRoot);
|
|
4080
|
+
let confirmed = true;
|
|
4081
|
+
try {
|
|
4082
|
+
const { confirm: confirm2 } = await import("@inquirer/prompts");
|
|
4083
|
+
confirmed = await confirm2({
|
|
4084
|
+
message: `Remove hook "${name}"? Run 'claudectx revert' to undo.`,
|
|
4085
|
+
default: false
|
|
4086
|
+
});
|
|
4087
|
+
} catch {
|
|
4088
|
+
}
|
|
4089
|
+
if (!confirmed) {
|
|
4090
|
+
process.stdout.write(" Cancelled.\n\n");
|
|
4091
|
+
return;
|
|
4092
|
+
}
|
|
4093
|
+
const settingsPath = path25.join(projectRoot, ".claude", "settings.local.json");
|
|
4094
|
+
if (fs22.existsSync(settingsPath)) {
|
|
4095
|
+
await backupFile(settingsPath, "hooks");
|
|
4096
|
+
}
|
|
3907
4097
|
const updated = removeHookByName(settings, name);
|
|
3908
4098
|
writeHooksSettings(projectRoot, updated);
|
|
3909
|
-
process.stdout.write(` \u2713 Hook "${name}" removed.
|
|
4099
|
+
process.stdout.write(` \u2713 Hook "${name}" removed. Run 'claudectx revert --list' to undo.
|
|
3910
4100
|
|
|
3911
4101
|
`);
|
|
3912
4102
|
}
|
|
@@ -3931,7 +4121,7 @@ async function hooksStatus(projectRoot) {
|
|
|
3931
4121
|
process.stdout.write("\n");
|
|
3932
4122
|
}
|
|
3933
4123
|
async function hooksCommand(subcommand, options) {
|
|
3934
|
-
const projectPath = options.path ?
|
|
4124
|
+
const projectPath = options.path ? path25.resolve(options.path) : process.cwd();
|
|
3935
4125
|
const projectRoot = findProjectRoot(projectPath) ?? projectPath;
|
|
3936
4126
|
const sub = subcommand ?? "list";
|
|
3937
4127
|
switch (sub) {
|
|
@@ -3970,8 +4160,8 @@ async function hooksCommand(subcommand, options) {
|
|
|
3970
4160
|
|
|
3971
4161
|
// src/commands/convert.ts
|
|
3972
4162
|
init_cjs_shims();
|
|
3973
|
-
var
|
|
3974
|
-
var
|
|
4163
|
+
var fs23 = __toESM(require("fs"));
|
|
4164
|
+
var path26 = __toESM(require("path"));
|
|
3975
4165
|
function slugify2(text) {
|
|
3976
4166
|
return text.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
|
|
3977
4167
|
}
|
|
@@ -4020,7 +4210,7 @@ function claudeMdToWindsurf(content) {
|
|
|
4020
4210
|
return content.split("\n").filter((line) => !line.match(/^@.+$/)).join("\n").replace(/\n{3,}/g, "\n\n").trim();
|
|
4021
4211
|
}
|
|
4022
4212
|
async function convertCommand(options) {
|
|
4023
|
-
const projectPath = options.path ?
|
|
4213
|
+
const projectPath = options.path ? path26.resolve(options.path) : process.cwd();
|
|
4024
4214
|
const projectRoot = findProjectRoot(projectPath) ?? projectPath;
|
|
4025
4215
|
const from = options.from ?? "claude";
|
|
4026
4216
|
const to = options.to;
|
|
@@ -4029,10 +4219,10 @@ async function convertCommand(options) {
|
|
|
4029
4219
|
`);
|
|
4030
4220
|
process.exit(1);
|
|
4031
4221
|
}
|
|
4032
|
-
const claudeMdPath =
|
|
4222
|
+
const claudeMdPath = path26.join(projectRoot, "CLAUDE.md");
|
|
4033
4223
|
let content = "";
|
|
4034
4224
|
try {
|
|
4035
|
-
content =
|
|
4225
|
+
content = fs23.readFileSync(claudeMdPath, "utf-8");
|
|
4036
4226
|
} catch {
|
|
4037
4227
|
process.stderr.write(`Error: CLAUDE.md not found at ${claudeMdPath}
|
|
4038
4228
|
`);
|
|
@@ -4040,33 +4230,45 @@ async function convertCommand(options) {
|
|
|
4040
4230
|
}
|
|
4041
4231
|
if (to === "cursor") {
|
|
4042
4232
|
const files = claudeMdToCursorRules(content);
|
|
4043
|
-
const targetDir =
|
|
4233
|
+
const targetDir = path26.join(projectRoot, ".cursor", "rules");
|
|
4044
4234
|
process.stdout.write(`
|
|
4045
4235
|
Converting CLAUDE.md \u2192 ${files.length} Cursor rule file(s)
|
|
4046
|
-
|
|
4047
4236
|
`);
|
|
4237
|
+
if (!options.dryRun) {
|
|
4238
|
+
process.stdout.write(` \u26A0 Existing .mdc files will be overwritten. A backup is saved automatically.
|
|
4239
|
+
`);
|
|
4240
|
+
process.stdout.write(` Run 'claudectx revert --list' to see backups.
|
|
4241
|
+
`);
|
|
4242
|
+
}
|
|
4243
|
+
process.stdout.write("\n");
|
|
4048
4244
|
for (const file of files) {
|
|
4049
|
-
const filePath =
|
|
4050
|
-
const exists =
|
|
4245
|
+
const filePath = path26.join(targetDir, file.filename);
|
|
4246
|
+
const exists = fs23.existsSync(filePath);
|
|
4051
4247
|
const prefix = options.dryRun ? "[dry-run] " : exists ? "[overwrite] " : "";
|
|
4052
4248
|
process.stdout.write(` ${prefix}\u2192 .cursor/rules/${file.filename}
|
|
4053
4249
|
`);
|
|
4054
4250
|
if (!options.dryRun) {
|
|
4055
|
-
|
|
4056
|
-
|
|
4251
|
+
fs23.mkdirSync(targetDir, { recursive: true });
|
|
4252
|
+
if (exists) await backupFile(filePath, "convert");
|
|
4253
|
+
fs23.writeFileSync(filePath, file.content, "utf-8");
|
|
4057
4254
|
}
|
|
4058
4255
|
}
|
|
4059
4256
|
process.stdout.write("\n");
|
|
4060
4257
|
} else if (to === "copilot") {
|
|
4061
4258
|
const converted = claudeMdToCopilot(content);
|
|
4062
|
-
const targetPath =
|
|
4063
|
-
const exists =
|
|
4259
|
+
const targetPath = path26.join(projectRoot, ".github", "copilot-instructions.md");
|
|
4260
|
+
const exists = fs23.existsSync(targetPath);
|
|
4064
4261
|
process.stdout.write(`
|
|
4065
4262
|
Converting CLAUDE.md \u2192 .github/copilot-instructions.md${exists ? " [overwrite]" : ""}
|
|
4066
4263
|
`);
|
|
4264
|
+
if (!options.dryRun && exists) {
|
|
4265
|
+
process.stdout.write(` \u26A0 Existing file will be overwritten. Run 'claudectx revert --list' to undo.
|
|
4266
|
+
`);
|
|
4267
|
+
}
|
|
4067
4268
|
if (!options.dryRun) {
|
|
4068
|
-
|
|
4069
|
-
|
|
4269
|
+
fs23.mkdirSync(path26.dirname(targetPath), { recursive: true });
|
|
4270
|
+
if (exists) await backupFile(targetPath, "convert");
|
|
4271
|
+
fs23.writeFileSync(targetPath, converted, "utf-8");
|
|
4070
4272
|
process.stdout.write(` \u2713 Written to ${targetPath}
|
|
4071
4273
|
|
|
4072
4274
|
`);
|
|
@@ -4077,13 +4279,18 @@ Converting CLAUDE.md \u2192 .github/copilot-instructions.md${exists ? " [overwri
|
|
|
4077
4279
|
}
|
|
4078
4280
|
} else if (to === "windsurf") {
|
|
4079
4281
|
const converted = claudeMdToWindsurf(content);
|
|
4080
|
-
const targetPath =
|
|
4081
|
-
const exists =
|
|
4282
|
+
const targetPath = path26.join(projectRoot, ".windsurfrules");
|
|
4283
|
+
const exists = fs23.existsSync(targetPath);
|
|
4082
4284
|
process.stdout.write(`
|
|
4083
4285
|
Converting CLAUDE.md \u2192 .windsurfrules${exists ? " [overwrite]" : ""}
|
|
4084
4286
|
`);
|
|
4287
|
+
if (!options.dryRun && exists) {
|
|
4288
|
+
process.stdout.write(` \u26A0 Existing file will be overwritten. Run 'claudectx revert --list' to undo.
|
|
4289
|
+
`);
|
|
4290
|
+
}
|
|
4085
4291
|
if (!options.dryRun) {
|
|
4086
|
-
|
|
4292
|
+
if (exists) await backupFile(targetPath, "convert");
|
|
4293
|
+
fs23.writeFileSync(targetPath, converted, "utf-8");
|
|
4087
4294
|
process.stdout.write(` \u2713 Written to ${targetPath}
|
|
4088
4295
|
|
|
4089
4296
|
`);
|
|
@@ -4101,15 +4308,15 @@ Converting CLAUDE.md \u2192 .windsurfrules${exists ? " [overwrite]" : ""}
|
|
|
4101
4308
|
|
|
4102
4309
|
// src/commands/teams.ts
|
|
4103
4310
|
init_cjs_shims();
|
|
4104
|
-
var
|
|
4105
|
-
var
|
|
4311
|
+
var path28 = __toESM(require("path"));
|
|
4312
|
+
var fs25 = __toESM(require("fs"));
|
|
4106
4313
|
init_models();
|
|
4107
4314
|
|
|
4108
4315
|
// src/reporter/team-aggregator.ts
|
|
4109
4316
|
init_cjs_shims();
|
|
4110
|
-
var
|
|
4111
|
-
var
|
|
4112
|
-
var
|
|
4317
|
+
var fs24 = __toESM(require("fs"));
|
|
4318
|
+
var path27 = __toESM(require("path"));
|
|
4319
|
+
var os5 = __toESM(require("os"));
|
|
4113
4320
|
var childProcess2 = __toESM(require("child_process"));
|
|
4114
4321
|
init_session_reader();
|
|
4115
4322
|
init_session_store();
|
|
@@ -4120,7 +4327,7 @@ function getDeveloperIdentity() {
|
|
|
4120
4327
|
if (email) return email;
|
|
4121
4328
|
} catch {
|
|
4122
4329
|
}
|
|
4123
|
-
return
|
|
4330
|
+
return os5.hostname();
|
|
4124
4331
|
}
|
|
4125
4332
|
function calcCost3(inputTokens, outputTokens, cacheCreationTokens, cacheReadTokens, model) {
|
|
4126
4333
|
const p = MODEL_PRICING[model];
|
|
@@ -4187,7 +4394,8 @@ async function buildTeamExport(days, model, anonymize) {
|
|
|
4187
4394
|
);
|
|
4188
4395
|
const topWasteFiles = aggregateStats(fileEvents).slice(0, 10).map((s) => ({ filePath: s.filePath, readCount: s.readCount }));
|
|
4189
4396
|
const totalCostUsd = calcCost3(totalInput, totalOutput, 0, totalCacheRead, model);
|
|
4190
|
-
const
|
|
4397
|
+
const totalContext = totalInput + totalCacheRead;
|
|
4398
|
+
const cacheHitRate = totalContext > 0 ? Math.round(totalCacheRead / totalContext * 100) : 0;
|
|
4191
4399
|
const uniqueSessions = new Set(sessionFiles.map((f) => f.sessionId)).size;
|
|
4192
4400
|
const developer = {
|
|
4193
4401
|
identity: getDeveloperIdentity(),
|
|
@@ -4237,19 +4445,19 @@ function aggregateTeamReports(exports2) {
|
|
|
4237
4445
|
}
|
|
4238
4446
|
function writeTeamExport(exportData) {
|
|
4239
4447
|
const storeDir = getStoreDir();
|
|
4240
|
-
if (!
|
|
4448
|
+
if (!fs24.existsSync(storeDir)) fs24.mkdirSync(storeDir, { recursive: true });
|
|
4241
4449
|
const date = isoDate2(/* @__PURE__ */ new Date());
|
|
4242
|
-
const filePath =
|
|
4243
|
-
|
|
4450
|
+
const filePath = path27.join(storeDir, `team-export-${date}.json`);
|
|
4451
|
+
fs24.writeFileSync(filePath, JSON.stringify(exportData, null, 2), "utf-8");
|
|
4244
4452
|
return filePath;
|
|
4245
4453
|
}
|
|
4246
4454
|
function readTeamExports(dir) {
|
|
4247
4455
|
const exports2 = [];
|
|
4248
|
-
if (!
|
|
4249
|
-
const files =
|
|
4456
|
+
if (!fs24.existsSync(dir)) return exports2;
|
|
4457
|
+
const files = fs24.readdirSync(dir).filter((f) => f.match(/^team-export-.*\.json$/));
|
|
4250
4458
|
for (const file of files) {
|
|
4251
4459
|
try {
|
|
4252
|
-
const raw =
|
|
4460
|
+
const raw = fs24.readFileSync(path27.join(dir, file), "utf-8");
|
|
4253
4461
|
exports2.push(JSON.parse(raw));
|
|
4254
4462
|
} catch {
|
|
4255
4463
|
}
|
|
@@ -4336,7 +4544,7 @@ Run "claudectx teams export" on each developer machine first.
|
|
|
4336
4544
|
process.stdout.write(" Top shared files (by read count across team):\n");
|
|
4337
4545
|
for (const f of report.topWasteFiles.slice(0, 5)) {
|
|
4338
4546
|
const devList = f.developers.slice(0, 3).join(", ");
|
|
4339
|
-
process.stdout.write(` ${f.readCount}x ${
|
|
4547
|
+
process.stdout.write(` ${f.readCount}x ${path28.basename(f.filePath)} (${devList})
|
|
4340
4548
|
`);
|
|
4341
4549
|
}
|
|
4342
4550
|
process.stdout.write("\n");
|
|
@@ -4349,24 +4557,24 @@ async function teamsShare(options) {
|
|
|
4349
4557
|
process.exit(1);
|
|
4350
4558
|
}
|
|
4351
4559
|
const storeDir = getStoreDir();
|
|
4352
|
-
const exportFiles =
|
|
4560
|
+
const exportFiles = fs25.readdirSync(storeDir).filter((f) => f.match(/^team-export-.*\.json$/)).sort().reverse();
|
|
4353
4561
|
if (exportFiles.length === 0) {
|
|
4354
4562
|
process.stderr.write('No team export files found. Run "claudectx teams export" first.\n');
|
|
4355
4563
|
process.exit(1);
|
|
4356
4564
|
}
|
|
4357
4565
|
const latest = exportFiles[0];
|
|
4358
|
-
const src =
|
|
4566
|
+
const src = path28.join(storeDir, latest);
|
|
4359
4567
|
let destPath;
|
|
4360
4568
|
try {
|
|
4361
|
-
const stat =
|
|
4362
|
-
destPath = stat.isDirectory() ?
|
|
4569
|
+
const stat = fs25.statSync(dest);
|
|
4570
|
+
destPath = stat.isDirectory() ? path28.join(dest, latest) : dest;
|
|
4363
4571
|
} catch {
|
|
4364
4572
|
destPath = dest;
|
|
4365
4573
|
}
|
|
4366
|
-
const destDir =
|
|
4574
|
+
const destDir = path28.dirname(path28.resolve(destPath));
|
|
4367
4575
|
let resolvedDir;
|
|
4368
4576
|
try {
|
|
4369
|
-
resolvedDir =
|
|
4577
|
+
resolvedDir = fs25.realpathSync(destDir);
|
|
4370
4578
|
} catch {
|
|
4371
4579
|
resolvedDir = destDir;
|
|
4372
4580
|
}
|
|
@@ -4376,7 +4584,7 @@ async function teamsShare(options) {
|
|
|
4376
4584
|
`);
|
|
4377
4585
|
process.exit(1);
|
|
4378
4586
|
}
|
|
4379
|
-
|
|
4587
|
+
fs25.copyFileSync(src, destPath);
|
|
4380
4588
|
process.stdout.write(` \u2713 Copied ${latest} \u2192 ${destPath}
|
|
4381
4589
|
|
|
4382
4590
|
`);
|
|
@@ -4401,8 +4609,148 @@ async function teamsCommand(subcommand, options) {
|
|
|
4401
4609
|
}
|
|
4402
4610
|
}
|
|
4403
4611
|
|
|
4612
|
+
// src/commands/revert.ts
|
|
4613
|
+
init_cjs_shims();
|
|
4614
|
+
var path29 = __toESM(require("path"));
|
|
4615
|
+
function timeAgo(isoString) {
|
|
4616
|
+
const diffMs = Date.now() - new Date(isoString).getTime();
|
|
4617
|
+
const minutes = Math.floor(diffMs / 6e4);
|
|
4618
|
+
if (minutes < 1) return "just now";
|
|
4619
|
+
if (minutes < 60) return `${minutes} min ago`;
|
|
4620
|
+
const hours = Math.floor(minutes / 60);
|
|
4621
|
+
if (hours < 24) return `${hours} hour${hours === 1 ? "" : "s"} ago`;
|
|
4622
|
+
const days = Math.floor(hours / 24);
|
|
4623
|
+
return `${days} day${days === 1 ? "" : "s"} ago`;
|
|
4624
|
+
}
|
|
4625
|
+
function formatBytes(bytes) {
|
|
4626
|
+
if (bytes < 1024) return `${bytes} B`;
|
|
4627
|
+
return `${Math.round(bytes / 1024)} KB`;
|
|
4628
|
+
}
|
|
4629
|
+
function printBackupTable(entries) {
|
|
4630
|
+
if (entries.length === 0) {
|
|
4631
|
+
process.stdout.write(" No backups found.\n");
|
|
4632
|
+
process.stdout.write(` Backups are created automatically when claudectx modifies your files.
|
|
4633
|
+
`);
|
|
4634
|
+
process.stdout.write(` Backup directory: ${BACKUP_DIR}
|
|
4635
|
+
|
|
4636
|
+
`);
|
|
4637
|
+
return;
|
|
4638
|
+
}
|
|
4639
|
+
const idWidth = 26;
|
|
4640
|
+
const fileWidth = 20;
|
|
4641
|
+
const cmdWidth = 10;
|
|
4642
|
+
const timeWidth = 14;
|
|
4643
|
+
const sizeWidth = 7;
|
|
4644
|
+
const hr = "\u2550".repeat(idWidth + fileWidth + cmdWidth + timeWidth + sizeWidth + 16);
|
|
4645
|
+
process.stdout.write("\n");
|
|
4646
|
+
process.stdout.write("claudectx \u2014 Backup History\n");
|
|
4647
|
+
process.stdout.write(hr + "\n");
|
|
4648
|
+
process.stdout.write(
|
|
4649
|
+
` ${"ID".padEnd(idWidth)} ${"File".padEnd(fileWidth)} ${"Command".padEnd(cmdWidth)} ${"When".padEnd(timeWidth)} ${"Size".padEnd(sizeWidth)}
|
|
4650
|
+
`
|
|
4651
|
+
);
|
|
4652
|
+
process.stdout.write("\u2500".repeat(idWidth + fileWidth + cmdWidth + timeWidth + sizeWidth + 16) + "\n");
|
|
4653
|
+
for (const entry of entries) {
|
|
4654
|
+
const id = entry.id.slice(0, idWidth).padEnd(idWidth);
|
|
4655
|
+
const file = path29.basename(entry.originalPath).slice(0, fileWidth).padEnd(fileWidth);
|
|
4656
|
+
const cmd = entry.command.slice(0, cmdWidth).padEnd(cmdWidth);
|
|
4657
|
+
const when = timeAgo(entry.createdAt).slice(0, timeWidth).padEnd(timeWidth);
|
|
4658
|
+
const size = formatBytes(entry.sizeBytes).padEnd(sizeWidth);
|
|
4659
|
+
process.stdout.write(` ${id} ${file} ${cmd} ${when} ${size}
|
|
4660
|
+
`);
|
|
4661
|
+
}
|
|
4662
|
+
process.stdout.write("\n");
|
|
4663
|
+
process.stdout.write(` Backup directory: ${BACKUP_DIR}
|
|
4664
|
+
`);
|
|
4665
|
+
process.stdout.write(" To restore: claudectx revert --id <ID>\n\n");
|
|
4666
|
+
}
|
|
4667
|
+
async function interactivePick(entries) {
|
|
4668
|
+
try {
|
|
4669
|
+
const { select } = await import("@inquirer/prompts");
|
|
4670
|
+
const choices = entries.map((e) => ({
|
|
4671
|
+
name: `${timeAgo(e.createdAt).padEnd(14)} ${path29.basename(e.originalPath).padEnd(16)} [${e.command}] ${e.id}`,
|
|
4672
|
+
value: e.id
|
|
4673
|
+
}));
|
|
4674
|
+
choices.push({ name: "Cancel", value: "" });
|
|
4675
|
+
return await select({ message: "Choose a backup to restore:", choices });
|
|
4676
|
+
} catch {
|
|
4677
|
+
process.stderr.write("Interactive mode unavailable. Use --id <id> to restore a specific backup.\n");
|
|
4678
|
+
return null;
|
|
4679
|
+
}
|
|
4680
|
+
}
|
|
4681
|
+
async function doRestore(id) {
|
|
4682
|
+
const chalk5 = (await import("chalk")).default;
|
|
4683
|
+
process.stdout.write("\n");
|
|
4684
|
+
try {
|
|
4685
|
+
const entries = await listBackups();
|
|
4686
|
+
const entry = entries.find((e) => e.id === id);
|
|
4687
|
+
if (!entry) {
|
|
4688
|
+
process.stderr.write(chalk5.red(`Backup "${id}" not found.
|
|
4689
|
+
`));
|
|
4690
|
+
process.stderr.write('Run "claudectx revert --list" to see available backups.\n');
|
|
4691
|
+
process.exitCode = 1;
|
|
4692
|
+
return;
|
|
4693
|
+
}
|
|
4694
|
+
process.stdout.write(chalk5.yellow(`\u26A0 This will overwrite: ${entry.originalPath}
|
|
4695
|
+
`));
|
|
4696
|
+
process.stdout.write(` Backup from: ${timeAgo(entry.createdAt)} (${entry.command})
|
|
4697
|
+
`);
|
|
4698
|
+
process.stdout.write(` Your current file will be backed up first (so you can undo this).
|
|
4699
|
+
|
|
4700
|
+
`);
|
|
4701
|
+
let confirmed = true;
|
|
4702
|
+
try {
|
|
4703
|
+
const { confirm: confirm2 } = await import("@inquirer/prompts");
|
|
4704
|
+
confirmed = await confirm2({ message: "Restore this backup?", default: false });
|
|
4705
|
+
} catch {
|
|
4706
|
+
}
|
|
4707
|
+
if (!confirmed) {
|
|
4708
|
+
process.stdout.write(" Cancelled.\n\n");
|
|
4709
|
+
return;
|
|
4710
|
+
}
|
|
4711
|
+
const { undoEntry } = await restoreBackup(id);
|
|
4712
|
+
process.stdout.write(chalk5.green(" \u2713 ") + `Restored to ${entry.originalPath}
|
|
4713
|
+
`);
|
|
4714
|
+
if (undoEntry) {
|
|
4715
|
+
process.stdout.write(
|
|
4716
|
+
chalk5.dim(` Your previous version was saved as backup "${undoEntry.id}" \u2014 run 'claudectx revert --id ${undoEntry.id}' to undo.
|
|
4717
|
+
`)
|
|
4718
|
+
);
|
|
4719
|
+
}
|
|
4720
|
+
process.stdout.write("\n");
|
|
4721
|
+
} catch (err) {
|
|
4722
|
+
process.stderr.write(chalk5.red(`Error: ${err instanceof Error ? err.message : String(err)}
|
|
4723
|
+
`));
|
|
4724
|
+
process.exitCode = 1;
|
|
4725
|
+
}
|
|
4726
|
+
}
|
|
4727
|
+
async function revertCommand(options) {
|
|
4728
|
+
const entries = await listBackups(options.file);
|
|
4729
|
+
if (options.json) {
|
|
4730
|
+
process.stdout.write(JSON.stringify({ backups: entries }, null, 2) + "\n");
|
|
4731
|
+
return;
|
|
4732
|
+
}
|
|
4733
|
+
if (options.list) {
|
|
4734
|
+
printBackupTable(entries);
|
|
4735
|
+
return;
|
|
4736
|
+
}
|
|
4737
|
+
if (options.id) {
|
|
4738
|
+
await doRestore(options.id);
|
|
4739
|
+
return;
|
|
4740
|
+
}
|
|
4741
|
+
if (entries.length === 0) {
|
|
4742
|
+
process.stdout.write("\n No backups found. Backups are created automatically when claudectx modifies your files.\n\n");
|
|
4743
|
+
return;
|
|
4744
|
+
}
|
|
4745
|
+
printBackupTable(entries);
|
|
4746
|
+
const picked = await interactivePick(entries);
|
|
4747
|
+
if (picked) {
|
|
4748
|
+
await doRestore(picked);
|
|
4749
|
+
}
|
|
4750
|
+
}
|
|
4751
|
+
|
|
4404
4752
|
// src/index.ts
|
|
4405
|
-
var VERSION = "1.1.
|
|
4753
|
+
var VERSION = "1.1.4";
|
|
4406
4754
|
var DESCRIPTION = "Reduce Claude Code token usage by up to 80%. Context analyzer, auto-optimizer, live dashboard, and smart MCP tools.";
|
|
4407
4755
|
var program = new import_commander.Command();
|
|
4408
4756
|
program.name("claudectx").description(DESCRIPTION).version(VERSION);
|
|
@@ -4442,5 +4790,8 @@ program.command("convert").description("Convert CLAUDE.md to another AI assistan
|
|
|
4442
4790
|
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) => {
|
|
4443
4791
|
await teamsCommand(subcommand ?? "export", options);
|
|
4444
4792
|
});
|
|
4793
|
+
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) => {
|
|
4794
|
+
await revertCommand(options);
|
|
4795
|
+
});
|
|
4445
4796
|
program.parse();
|
|
4446
4797
|
//# sourceMappingURL=index.js.map
|