claudectx 1.1.2 → 1.1.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +73 -2
- package/dist/index.js +565 -230
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +560 -225
- 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
|
}
|
|
@@ -3034,12 +3179,12 @@ async function reportCommand(options) {
|
|
|
3034
3179
|
|
|
3035
3180
|
// src/commands/budget.ts
|
|
3036
3181
|
init_cjs_shims();
|
|
3037
|
-
var
|
|
3182
|
+
var path21 = __toESM(require("path"));
|
|
3038
3183
|
|
|
3039
3184
|
// src/analyzer/budget-estimator.ts
|
|
3040
3185
|
init_cjs_shims();
|
|
3041
|
-
var
|
|
3042
|
-
var
|
|
3186
|
+
var fs18 = __toESM(require("fs"));
|
|
3187
|
+
var path20 = __toESM(require("path"));
|
|
3043
3188
|
var import_glob2 = require("glob");
|
|
3044
3189
|
init_tokenizer();
|
|
3045
3190
|
init_session_store();
|
|
@@ -3065,20 +3210,20 @@ function classifyCacheHit(recentReadCount) {
|
|
|
3065
3210
|
return "low";
|
|
3066
3211
|
}
|
|
3067
3212
|
function suggestClaudeignoreAdditions(files, projectRoot) {
|
|
3068
|
-
const ignorePath =
|
|
3213
|
+
const ignorePath = path20.join(projectRoot, ".claudeignore");
|
|
3069
3214
|
let ignorePatterns = [];
|
|
3070
3215
|
try {
|
|
3071
|
-
const content =
|
|
3216
|
+
const content = fs18.readFileSync(ignorePath, "utf-8");
|
|
3072
3217
|
ignorePatterns = content.split("\n").filter(Boolean);
|
|
3073
3218
|
} catch {
|
|
3074
3219
|
}
|
|
3075
3220
|
const recommendations = [];
|
|
3076
3221
|
for (const file of files) {
|
|
3077
3222
|
if (file.tokenCount <= WASTE_THRESHOLDS.MAX_REFERENCE_FILE_TOKENS) continue;
|
|
3078
|
-
const rel =
|
|
3223
|
+
const rel = path20.relative(projectRoot, file.filePath);
|
|
3079
3224
|
const alreadyIgnored = ignorePatterns.some((pattern) => {
|
|
3080
3225
|
const cleanPattern = pattern.replace(/^!/, "");
|
|
3081
|
-
return rel.startsWith(cleanPattern.replace(/\*/g, "").replace(/\//g,
|
|
3226
|
+
return rel.startsWith(cleanPattern.replace(/\*/g, "").replace(/\//g, path20.sep));
|
|
3082
3227
|
});
|
|
3083
3228
|
if (!alreadyIgnored) {
|
|
3084
3229
|
recommendations.push(rel);
|
|
@@ -3098,7 +3243,7 @@ async function estimateBudget(globs, projectRoot, model, thresholdTokens) {
|
|
|
3098
3243
|
for (const filePath of filePaths) {
|
|
3099
3244
|
let content = "";
|
|
3100
3245
|
try {
|
|
3101
|
-
content =
|
|
3246
|
+
content = fs18.readFileSync(filePath, "utf-8");
|
|
3102
3247
|
} catch {
|
|
3103
3248
|
continue;
|
|
3104
3249
|
}
|
|
@@ -3147,7 +3292,7 @@ function formatBudgetReport(report) {
|
|
|
3147
3292
|
}
|
|
3148
3293
|
const LIKELIHOOD_ICON = { high: "\u{1F7E2}", medium: "\u{1F7E1}", low: "\u{1F534}" };
|
|
3149
3294
|
const maxPathLen = Math.min(
|
|
3150
|
-
Math.max(...report.files.map((f) =>
|
|
3295
|
+
Math.max(...report.files.map((f) => path20.basename(f.filePath).length)),
|
|
3151
3296
|
40
|
|
3152
3297
|
);
|
|
3153
3298
|
lines.push(
|
|
@@ -3155,7 +3300,7 @@ function formatBudgetReport(report) {
|
|
|
3155
3300
|
);
|
|
3156
3301
|
lines.push("\u2500".repeat(50));
|
|
3157
3302
|
for (const file of report.files.slice(0, 20)) {
|
|
3158
|
-
const name =
|
|
3303
|
+
const name = path20.basename(file.filePath).slice(0, maxPathLen).padEnd(maxPathLen);
|
|
3159
3304
|
const tokens = file.tokenCount.toLocaleString().padStart(7);
|
|
3160
3305
|
const cache = `${LIKELIHOOD_ICON[file.cacheHitLikelihood]} ${file.cacheHitLikelihood.padEnd(6)}`;
|
|
3161
3306
|
const cost = formatCost(file.estimatedCostUsd).padStart(7);
|
|
@@ -3184,7 +3329,7 @@ function formatBudgetReport(report) {
|
|
|
3184
3329
|
// src/commands/budget.ts
|
|
3185
3330
|
init_models();
|
|
3186
3331
|
async function budgetCommand(globs, options) {
|
|
3187
|
-
const projectPath = options.path ?
|
|
3332
|
+
const projectPath = options.path ? path21.resolve(options.path) : process.cwd();
|
|
3188
3333
|
const projectRoot = findProjectRoot(projectPath) ?? projectPath;
|
|
3189
3334
|
const model = resolveModel(options.model ?? "sonnet");
|
|
3190
3335
|
const thresholdTokens = parseInt(options.threshold ?? "10000", 10);
|
|
@@ -3203,7 +3348,7 @@ async function budgetCommand(globs, options) {
|
|
|
3203
3348
|
|
|
3204
3349
|
// src/commands/warmup.ts
|
|
3205
3350
|
init_cjs_shims();
|
|
3206
|
-
var
|
|
3351
|
+
var path22 = __toESM(require("path"));
|
|
3207
3352
|
var import_sdk = __toESM(require("@anthropic-ai/sdk"));
|
|
3208
3353
|
init_models();
|
|
3209
3354
|
var import_fs3 = __toESM(require("fs"));
|
|
@@ -3277,6 +3422,19 @@ async function installCron(cronExpr) {
|
|
|
3277
3422
|
);
|
|
3278
3423
|
process.exit(1);
|
|
3279
3424
|
}
|
|
3425
|
+
let confirmed = true;
|
|
3426
|
+
try {
|
|
3427
|
+
const { confirm: confirm2 } = await import("@inquirer/prompts");
|
|
3428
|
+
confirmed = await confirm2({
|
|
3429
|
+
message: `Install cron job "${cronExpr} claudectx warmup" in your system crontab?`,
|
|
3430
|
+
default: false
|
|
3431
|
+
});
|
|
3432
|
+
} catch {
|
|
3433
|
+
}
|
|
3434
|
+
if (!confirmed) {
|
|
3435
|
+
process.stdout.write(" Cron install cancelled.\n");
|
|
3436
|
+
return;
|
|
3437
|
+
}
|
|
3280
3438
|
const { execSync: execSync3 } = await import("child_process");
|
|
3281
3439
|
const command = `claudectx warmup`;
|
|
3282
3440
|
const cronLine = `${cronExpr} ${command}`;
|
|
@@ -3299,16 +3457,16 @@ async function installCron(cronExpr) {
|
|
|
3299
3457
|
${marker}
|
|
3300
3458
|
${cronLine}
|
|
3301
3459
|
`;
|
|
3302
|
-
const { writeFileSync:
|
|
3460
|
+
const { writeFileSync: writeFileSync12, unlinkSync: unlinkSync3 } = await import("fs");
|
|
3303
3461
|
const { tmpdir } = await import("os");
|
|
3304
|
-
const { join:
|
|
3305
|
-
const tmpFile =
|
|
3462
|
+
const { join: join18 } = await import("path");
|
|
3463
|
+
const tmpFile = join18(tmpdir(), `claudectx-cron-${Date.now()}.txt`);
|
|
3306
3464
|
try {
|
|
3307
|
-
|
|
3465
|
+
writeFileSync12(tmpFile, newCrontab, "utf-8");
|
|
3308
3466
|
execSync3(`crontab ${tmpFile}`, { stdio: ["pipe", "pipe", "pipe"] });
|
|
3309
3467
|
} finally {
|
|
3310
3468
|
try {
|
|
3311
|
-
|
|
3469
|
+
unlinkSync3(tmpFile);
|
|
3312
3470
|
} catch {
|
|
3313
3471
|
}
|
|
3314
3472
|
}
|
|
@@ -3325,7 +3483,7 @@ ${cronLine}
|
|
|
3325
3483
|
}
|
|
3326
3484
|
}
|
|
3327
3485
|
async function warmupCommand(options) {
|
|
3328
|
-
const projectPath = options.path ?
|
|
3486
|
+
const projectPath = options.path ? path22.resolve(options.path) : process.cwd();
|
|
3329
3487
|
const projectRoot = findProjectRoot(projectPath) ?? projectPath;
|
|
3330
3488
|
const model = resolveModel(options.model ?? "haiku");
|
|
3331
3489
|
const ttl = options.ttl === "60" ? 60 : 5;
|
|
@@ -3337,7 +3495,7 @@ async function warmupCommand(options) {
|
|
|
3337
3495
|
process.exit(1);
|
|
3338
3496
|
}
|
|
3339
3497
|
let claudeMdContent = "";
|
|
3340
|
-
const claudeMdPath =
|
|
3498
|
+
const claudeMdPath = path22.join(projectRoot, "CLAUDE.md");
|
|
3341
3499
|
try {
|
|
3342
3500
|
claudeMdContent = import_fs3.default.readFileSync(claudeMdPath, "utf-8");
|
|
3343
3501
|
} catch {
|
|
@@ -3397,13 +3555,13 @@ async function warmupCommand(options) {
|
|
|
3397
3555
|
|
|
3398
3556
|
// src/commands/drift.ts
|
|
3399
3557
|
init_cjs_shims();
|
|
3400
|
-
var
|
|
3401
|
-
var
|
|
3558
|
+
var path24 = __toESM(require("path"));
|
|
3559
|
+
var fs21 = __toESM(require("fs"));
|
|
3402
3560
|
|
|
3403
3561
|
// src/analyzer/drift-detector.ts
|
|
3404
3562
|
init_cjs_shims();
|
|
3405
|
-
var
|
|
3406
|
-
var
|
|
3563
|
+
var fs20 = __toESM(require("fs"));
|
|
3564
|
+
var path23 = __toESM(require("path"));
|
|
3407
3565
|
var childProcess = __toESM(require("child_process"));
|
|
3408
3566
|
init_tokenizer();
|
|
3409
3567
|
init_session_store();
|
|
@@ -3416,8 +3574,8 @@ function findDeadAtReferences(content, projectRoot) {
|
|
|
3416
3574
|
const match = lines[i].match(AT_REF_RE);
|
|
3417
3575
|
if (!match) continue;
|
|
3418
3576
|
const ref = match[1].trim();
|
|
3419
|
-
const absPath =
|
|
3420
|
-
if (!
|
|
3577
|
+
const absPath = path23.isAbsolute(ref) ? ref : path23.join(projectRoot, ref);
|
|
3578
|
+
if (!fs20.existsSync(absPath)) {
|
|
3421
3579
|
const lineText = lines[i];
|
|
3422
3580
|
issues.push({
|
|
3423
3581
|
type: "dead-ref",
|
|
@@ -3450,15 +3608,15 @@ async function findGitDeletedMentions(content, projectRoot) {
|
|
|
3450
3608
|
for (let i = 0; i < lines.length; i++) {
|
|
3451
3609
|
const line = lines[i];
|
|
3452
3610
|
for (const deleted of deletedFiles) {
|
|
3453
|
-
const
|
|
3454
|
-
if (line.includes(
|
|
3611
|
+
const basename10 = path23.basename(deleted);
|
|
3612
|
+
if (line.includes(basename10) || line.includes(deleted)) {
|
|
3455
3613
|
issues.push({
|
|
3456
3614
|
type: "git-deleted",
|
|
3457
3615
|
line: i + 1,
|
|
3458
3616
|
text: line.trim(),
|
|
3459
3617
|
severity: "warning",
|
|
3460
3618
|
estimatedTokenWaste: countTokens(line),
|
|
3461
|
-
suggestion: `References "${
|
|
3619
|
+
suggestion: `References "${basename10}" which was deleted from git. Consider removing this mention.`
|
|
3462
3620
|
});
|
|
3463
3621
|
break;
|
|
3464
3622
|
}
|
|
@@ -3523,8 +3681,8 @@ function findDeadInlinePaths(content, projectRoot) {
|
|
|
3523
3681
|
const rawPath = match[1].trim();
|
|
3524
3682
|
if (seen.has(rawPath)) continue;
|
|
3525
3683
|
seen.add(rawPath);
|
|
3526
|
-
const absPath =
|
|
3527
|
-
if (!
|
|
3684
|
+
const absPath = path23.isAbsolute(rawPath) ? rawPath : path23.join(projectRoot, rawPath);
|
|
3685
|
+
if (!fs20.existsSync(absPath)) {
|
|
3528
3686
|
issues.push({
|
|
3529
3687
|
type: "dead-inline-path",
|
|
3530
3688
|
line: i + 1,
|
|
@@ -3540,10 +3698,10 @@ function findDeadInlinePaths(content, projectRoot) {
|
|
|
3540
3698
|
return issues;
|
|
3541
3699
|
}
|
|
3542
3700
|
async function detectDrift(projectRoot, dayWindow) {
|
|
3543
|
-
const claudeMdPath =
|
|
3701
|
+
const claudeMdPath = path23.join(projectRoot, "CLAUDE.md");
|
|
3544
3702
|
let content = "";
|
|
3545
3703
|
try {
|
|
3546
|
-
content =
|
|
3704
|
+
content = fs20.readFileSync(claudeMdPath, "utf-8");
|
|
3547
3705
|
} catch {
|
|
3548
3706
|
return {
|
|
3549
3707
|
claudeMdPath,
|
|
@@ -3585,7 +3743,7 @@ var TYPE_LABEL = {
|
|
|
3585
3743
|
"dead-inline-path": "Dead path"
|
|
3586
3744
|
};
|
|
3587
3745
|
async function driftCommand(options) {
|
|
3588
|
-
const projectPath = options.path ?
|
|
3746
|
+
const projectPath = options.path ? path24.resolve(options.path) : process.cwd();
|
|
3589
3747
|
const projectRoot = findProjectRoot(projectPath) ?? projectPath;
|
|
3590
3748
|
const dayWindow = parseInt(options.days ?? "30", 10);
|
|
3591
3749
|
const report = await detectDrift(projectRoot, dayWindow);
|
|
@@ -3661,25 +3819,25 @@ async function applyFix(claudeMdPath, issues) {
|
|
|
3661
3819
|
process.stdout.write("No lines selected. Nothing changed.\n");
|
|
3662
3820
|
return;
|
|
3663
3821
|
}
|
|
3664
|
-
const content =
|
|
3822
|
+
const content = fs21.readFileSync(claudeMdPath, "utf-8");
|
|
3665
3823
|
const lines = content.split("\n");
|
|
3666
3824
|
const lineSet = new Set(selectedLines.map((l) => l - 1));
|
|
3667
3825
|
const newLines = lines.filter((_, i) => !lineSet.has(i));
|
|
3668
3826
|
const newContent = newLines.join("\n");
|
|
3669
3827
|
const backupPath = `${claudeMdPath}.bak`;
|
|
3670
|
-
|
|
3671
|
-
const
|
|
3828
|
+
fs21.writeFileSync(backupPath, content, "utf-8");
|
|
3829
|
+
const os6 = await import("os");
|
|
3672
3830
|
const tmpPath = `${claudeMdPath}.tmp-${Date.now()}`;
|
|
3673
3831
|
try {
|
|
3674
|
-
|
|
3675
|
-
|
|
3832
|
+
fs21.writeFileSync(tmpPath, newContent, "utf-8");
|
|
3833
|
+
fs21.renameSync(tmpPath, claudeMdPath);
|
|
3676
3834
|
} catch (err) {
|
|
3677
3835
|
try {
|
|
3678
|
-
|
|
3836
|
+
fs21.copyFileSync(backupPath, claudeMdPath);
|
|
3679
3837
|
} catch {
|
|
3680
3838
|
}
|
|
3681
3839
|
try {
|
|
3682
|
-
|
|
3840
|
+
fs21.unlinkSync(tmpPath);
|
|
3683
3841
|
} catch {
|
|
3684
3842
|
}
|
|
3685
3843
|
process.stderr.write(`Error writing CLAUDE.md: ${err instanceof Error ? err.message : String(err)}
|
|
@@ -3687,18 +3845,18 @@ async function applyFix(claudeMdPath, issues) {
|
|
|
3687
3845
|
process.exit(1);
|
|
3688
3846
|
}
|
|
3689
3847
|
process.stdout.write(`
|
|
3690
|
-
\u2713 Removed ${selectedLines.length} line(s) from ${
|
|
3848
|
+
\u2713 Removed ${selectedLines.length} line(s) from ${path24.basename(claudeMdPath)}
|
|
3691
3849
|
`);
|
|
3692
3850
|
process.stdout.write(` \u2713 Backup saved to ${backupPath}
|
|
3693
3851
|
|
|
3694
3852
|
`);
|
|
3695
|
-
void
|
|
3853
|
+
void os6;
|
|
3696
3854
|
}
|
|
3697
3855
|
|
|
3698
3856
|
// src/commands/hooks.ts
|
|
3699
3857
|
init_cjs_shims();
|
|
3700
|
-
var
|
|
3701
|
-
var
|
|
3858
|
+
var fs22 = __toESM(require("fs"));
|
|
3859
|
+
var path25 = __toESM(require("path"));
|
|
3702
3860
|
|
|
3703
3861
|
// src/hooks/registry.ts
|
|
3704
3862
|
init_cjs_shims();
|
|
@@ -3774,17 +3932,17 @@ function buildHookEntry(def, config) {
|
|
|
3774
3932
|
|
|
3775
3933
|
// src/commands/hooks.ts
|
|
3776
3934
|
function readInstalledHooks(projectRoot) {
|
|
3777
|
-
const settingsPath =
|
|
3778
|
-
if (!
|
|
3935
|
+
const settingsPath = path25.join(projectRoot, ".claude", "settings.local.json");
|
|
3936
|
+
if (!fs22.existsSync(settingsPath)) return {};
|
|
3779
3937
|
try {
|
|
3780
|
-
return JSON.parse(
|
|
3938
|
+
return JSON.parse(fs22.readFileSync(settingsPath, "utf-8"));
|
|
3781
3939
|
} catch {
|
|
3782
3940
|
process.stderr.write(
|
|
3783
3941
|
`Warning: ${settingsPath} exists but contains invalid JSON. Existing settings will be preserved as a backup.
|
|
3784
3942
|
`
|
|
3785
3943
|
);
|
|
3786
3944
|
try {
|
|
3787
|
-
|
|
3945
|
+
fs22.copyFileSync(settingsPath, `${settingsPath}.bak`);
|
|
3788
3946
|
} catch {
|
|
3789
3947
|
}
|
|
3790
3948
|
return {};
|
|
@@ -3904,9 +4062,26 @@ async function hooksAdd(name, projectRoot, configPairs) {
|
|
|
3904
4062
|
}
|
|
3905
4063
|
async function hooksRemove(name, projectRoot) {
|
|
3906
4064
|
const settings = readInstalledHooks(projectRoot);
|
|
4065
|
+
let confirmed = true;
|
|
4066
|
+
try {
|
|
4067
|
+
const { confirm: confirm2 } = await import("@inquirer/prompts");
|
|
4068
|
+
confirmed = await confirm2({
|
|
4069
|
+
message: `Remove hook "${name}"? Run 'claudectx revert' to undo.`,
|
|
4070
|
+
default: false
|
|
4071
|
+
});
|
|
4072
|
+
} catch {
|
|
4073
|
+
}
|
|
4074
|
+
if (!confirmed) {
|
|
4075
|
+
process.stdout.write(" Cancelled.\n\n");
|
|
4076
|
+
return;
|
|
4077
|
+
}
|
|
4078
|
+
const settingsPath = path25.join(projectRoot, ".claude", "settings.local.json");
|
|
4079
|
+
if (fs22.existsSync(settingsPath)) {
|
|
4080
|
+
await backupFile(settingsPath, "hooks");
|
|
4081
|
+
}
|
|
3907
4082
|
const updated = removeHookByName(settings, name);
|
|
3908
4083
|
writeHooksSettings(projectRoot, updated);
|
|
3909
|
-
process.stdout.write(` \u2713 Hook "${name}" removed.
|
|
4084
|
+
process.stdout.write(` \u2713 Hook "${name}" removed. Run 'claudectx revert --list' to undo.
|
|
3910
4085
|
|
|
3911
4086
|
`);
|
|
3912
4087
|
}
|
|
@@ -3931,7 +4106,7 @@ async function hooksStatus(projectRoot) {
|
|
|
3931
4106
|
process.stdout.write("\n");
|
|
3932
4107
|
}
|
|
3933
4108
|
async function hooksCommand(subcommand, options) {
|
|
3934
|
-
const projectPath = options.path ?
|
|
4109
|
+
const projectPath = options.path ? path25.resolve(options.path) : process.cwd();
|
|
3935
4110
|
const projectRoot = findProjectRoot(projectPath) ?? projectPath;
|
|
3936
4111
|
const sub = subcommand ?? "list";
|
|
3937
4112
|
switch (sub) {
|
|
@@ -3970,8 +4145,8 @@ async function hooksCommand(subcommand, options) {
|
|
|
3970
4145
|
|
|
3971
4146
|
// src/commands/convert.ts
|
|
3972
4147
|
init_cjs_shims();
|
|
3973
|
-
var
|
|
3974
|
-
var
|
|
4148
|
+
var fs23 = __toESM(require("fs"));
|
|
4149
|
+
var path26 = __toESM(require("path"));
|
|
3975
4150
|
function slugify2(text) {
|
|
3976
4151
|
return text.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
|
|
3977
4152
|
}
|
|
@@ -4020,7 +4195,7 @@ function claudeMdToWindsurf(content) {
|
|
|
4020
4195
|
return content.split("\n").filter((line) => !line.match(/^@.+$/)).join("\n").replace(/\n{3,}/g, "\n\n").trim();
|
|
4021
4196
|
}
|
|
4022
4197
|
async function convertCommand(options) {
|
|
4023
|
-
const projectPath = options.path ?
|
|
4198
|
+
const projectPath = options.path ? path26.resolve(options.path) : process.cwd();
|
|
4024
4199
|
const projectRoot = findProjectRoot(projectPath) ?? projectPath;
|
|
4025
4200
|
const from = options.from ?? "claude";
|
|
4026
4201
|
const to = options.to;
|
|
@@ -4029,10 +4204,10 @@ async function convertCommand(options) {
|
|
|
4029
4204
|
`);
|
|
4030
4205
|
process.exit(1);
|
|
4031
4206
|
}
|
|
4032
|
-
const claudeMdPath =
|
|
4207
|
+
const claudeMdPath = path26.join(projectRoot, "CLAUDE.md");
|
|
4033
4208
|
let content = "";
|
|
4034
4209
|
try {
|
|
4035
|
-
content =
|
|
4210
|
+
content = fs23.readFileSync(claudeMdPath, "utf-8");
|
|
4036
4211
|
} catch {
|
|
4037
4212
|
process.stderr.write(`Error: CLAUDE.md not found at ${claudeMdPath}
|
|
4038
4213
|
`);
|
|
@@ -4040,33 +4215,45 @@ async function convertCommand(options) {
|
|
|
4040
4215
|
}
|
|
4041
4216
|
if (to === "cursor") {
|
|
4042
4217
|
const files = claudeMdToCursorRules(content);
|
|
4043
|
-
const targetDir =
|
|
4218
|
+
const targetDir = path26.join(projectRoot, ".cursor", "rules");
|
|
4044
4219
|
process.stdout.write(`
|
|
4045
4220
|
Converting CLAUDE.md \u2192 ${files.length} Cursor rule file(s)
|
|
4046
|
-
|
|
4047
4221
|
`);
|
|
4222
|
+
if (!options.dryRun) {
|
|
4223
|
+
process.stdout.write(` \u26A0 Existing .mdc files will be overwritten. A backup is saved automatically.
|
|
4224
|
+
`);
|
|
4225
|
+
process.stdout.write(` Run 'claudectx revert --list' to see backups.
|
|
4226
|
+
`);
|
|
4227
|
+
}
|
|
4228
|
+
process.stdout.write("\n");
|
|
4048
4229
|
for (const file of files) {
|
|
4049
|
-
const filePath =
|
|
4050
|
-
const exists =
|
|
4230
|
+
const filePath = path26.join(targetDir, file.filename);
|
|
4231
|
+
const exists = fs23.existsSync(filePath);
|
|
4051
4232
|
const prefix = options.dryRun ? "[dry-run] " : exists ? "[overwrite] " : "";
|
|
4052
4233
|
process.stdout.write(` ${prefix}\u2192 .cursor/rules/${file.filename}
|
|
4053
4234
|
`);
|
|
4054
4235
|
if (!options.dryRun) {
|
|
4055
|
-
|
|
4056
|
-
|
|
4236
|
+
fs23.mkdirSync(targetDir, { recursive: true });
|
|
4237
|
+
if (exists) await backupFile(filePath, "convert");
|
|
4238
|
+
fs23.writeFileSync(filePath, file.content, "utf-8");
|
|
4057
4239
|
}
|
|
4058
4240
|
}
|
|
4059
4241
|
process.stdout.write("\n");
|
|
4060
4242
|
} else if (to === "copilot") {
|
|
4061
4243
|
const converted = claudeMdToCopilot(content);
|
|
4062
|
-
const targetPath =
|
|
4063
|
-
const exists =
|
|
4244
|
+
const targetPath = path26.join(projectRoot, ".github", "copilot-instructions.md");
|
|
4245
|
+
const exists = fs23.existsSync(targetPath);
|
|
4064
4246
|
process.stdout.write(`
|
|
4065
4247
|
Converting CLAUDE.md \u2192 .github/copilot-instructions.md${exists ? " [overwrite]" : ""}
|
|
4066
4248
|
`);
|
|
4249
|
+
if (!options.dryRun && exists) {
|
|
4250
|
+
process.stdout.write(` \u26A0 Existing file will be overwritten. Run 'claudectx revert --list' to undo.
|
|
4251
|
+
`);
|
|
4252
|
+
}
|
|
4067
4253
|
if (!options.dryRun) {
|
|
4068
|
-
|
|
4069
|
-
|
|
4254
|
+
fs23.mkdirSync(path26.dirname(targetPath), { recursive: true });
|
|
4255
|
+
if (exists) await backupFile(targetPath, "convert");
|
|
4256
|
+
fs23.writeFileSync(targetPath, converted, "utf-8");
|
|
4070
4257
|
process.stdout.write(` \u2713 Written to ${targetPath}
|
|
4071
4258
|
|
|
4072
4259
|
`);
|
|
@@ -4077,13 +4264,18 @@ Converting CLAUDE.md \u2192 .github/copilot-instructions.md${exists ? " [overwri
|
|
|
4077
4264
|
}
|
|
4078
4265
|
} else if (to === "windsurf") {
|
|
4079
4266
|
const converted = claudeMdToWindsurf(content);
|
|
4080
|
-
const targetPath =
|
|
4081
|
-
const exists =
|
|
4267
|
+
const targetPath = path26.join(projectRoot, ".windsurfrules");
|
|
4268
|
+
const exists = fs23.existsSync(targetPath);
|
|
4082
4269
|
process.stdout.write(`
|
|
4083
4270
|
Converting CLAUDE.md \u2192 .windsurfrules${exists ? " [overwrite]" : ""}
|
|
4084
4271
|
`);
|
|
4272
|
+
if (!options.dryRun && exists) {
|
|
4273
|
+
process.stdout.write(` \u26A0 Existing file will be overwritten. Run 'claudectx revert --list' to undo.
|
|
4274
|
+
`);
|
|
4275
|
+
}
|
|
4085
4276
|
if (!options.dryRun) {
|
|
4086
|
-
|
|
4277
|
+
if (exists) await backupFile(targetPath, "convert");
|
|
4278
|
+
fs23.writeFileSync(targetPath, converted, "utf-8");
|
|
4087
4279
|
process.stdout.write(` \u2713 Written to ${targetPath}
|
|
4088
4280
|
|
|
4089
4281
|
`);
|
|
@@ -4101,15 +4293,15 @@ Converting CLAUDE.md \u2192 .windsurfrules${exists ? " [overwrite]" : ""}
|
|
|
4101
4293
|
|
|
4102
4294
|
// src/commands/teams.ts
|
|
4103
4295
|
init_cjs_shims();
|
|
4104
|
-
var
|
|
4105
|
-
var
|
|
4296
|
+
var path28 = __toESM(require("path"));
|
|
4297
|
+
var fs25 = __toESM(require("fs"));
|
|
4106
4298
|
init_models();
|
|
4107
4299
|
|
|
4108
4300
|
// src/reporter/team-aggregator.ts
|
|
4109
4301
|
init_cjs_shims();
|
|
4110
|
-
var
|
|
4111
|
-
var
|
|
4112
|
-
var
|
|
4302
|
+
var fs24 = __toESM(require("fs"));
|
|
4303
|
+
var path27 = __toESM(require("path"));
|
|
4304
|
+
var os5 = __toESM(require("os"));
|
|
4113
4305
|
var childProcess2 = __toESM(require("child_process"));
|
|
4114
4306
|
init_session_reader();
|
|
4115
4307
|
init_session_store();
|
|
@@ -4120,7 +4312,7 @@ function getDeveloperIdentity() {
|
|
|
4120
4312
|
if (email) return email;
|
|
4121
4313
|
} catch {
|
|
4122
4314
|
}
|
|
4123
|
-
return
|
|
4315
|
+
return os5.hostname();
|
|
4124
4316
|
}
|
|
4125
4317
|
function calcCost3(inputTokens, outputTokens, cacheCreationTokens, cacheReadTokens, model) {
|
|
4126
4318
|
const p = MODEL_PRICING[model];
|
|
@@ -4237,19 +4429,19 @@ function aggregateTeamReports(exports2) {
|
|
|
4237
4429
|
}
|
|
4238
4430
|
function writeTeamExport(exportData) {
|
|
4239
4431
|
const storeDir = getStoreDir();
|
|
4240
|
-
if (!
|
|
4432
|
+
if (!fs24.existsSync(storeDir)) fs24.mkdirSync(storeDir, { recursive: true });
|
|
4241
4433
|
const date = isoDate2(/* @__PURE__ */ new Date());
|
|
4242
|
-
const filePath =
|
|
4243
|
-
|
|
4434
|
+
const filePath = path27.join(storeDir, `team-export-${date}.json`);
|
|
4435
|
+
fs24.writeFileSync(filePath, JSON.stringify(exportData, null, 2), "utf-8");
|
|
4244
4436
|
return filePath;
|
|
4245
4437
|
}
|
|
4246
4438
|
function readTeamExports(dir) {
|
|
4247
4439
|
const exports2 = [];
|
|
4248
|
-
if (!
|
|
4249
|
-
const files =
|
|
4440
|
+
if (!fs24.existsSync(dir)) return exports2;
|
|
4441
|
+
const files = fs24.readdirSync(dir).filter((f) => f.match(/^team-export-.*\.json$/));
|
|
4250
4442
|
for (const file of files) {
|
|
4251
4443
|
try {
|
|
4252
|
-
const raw =
|
|
4444
|
+
const raw = fs24.readFileSync(path27.join(dir, file), "utf-8");
|
|
4253
4445
|
exports2.push(JSON.parse(raw));
|
|
4254
4446
|
} catch {
|
|
4255
4447
|
}
|
|
@@ -4336,7 +4528,7 @@ Run "claudectx teams export" on each developer machine first.
|
|
|
4336
4528
|
process.stdout.write(" Top shared files (by read count across team):\n");
|
|
4337
4529
|
for (const f of report.topWasteFiles.slice(0, 5)) {
|
|
4338
4530
|
const devList = f.developers.slice(0, 3).join(", ");
|
|
4339
|
-
process.stdout.write(` ${f.readCount}x ${
|
|
4531
|
+
process.stdout.write(` ${f.readCount}x ${path28.basename(f.filePath)} (${devList})
|
|
4340
4532
|
`);
|
|
4341
4533
|
}
|
|
4342
4534
|
process.stdout.write("\n");
|
|
@@ -4349,24 +4541,24 @@ async function teamsShare(options) {
|
|
|
4349
4541
|
process.exit(1);
|
|
4350
4542
|
}
|
|
4351
4543
|
const storeDir = getStoreDir();
|
|
4352
|
-
const exportFiles =
|
|
4544
|
+
const exportFiles = fs25.readdirSync(storeDir).filter((f) => f.match(/^team-export-.*\.json$/)).sort().reverse();
|
|
4353
4545
|
if (exportFiles.length === 0) {
|
|
4354
4546
|
process.stderr.write('No team export files found. Run "claudectx teams export" first.\n');
|
|
4355
4547
|
process.exit(1);
|
|
4356
4548
|
}
|
|
4357
4549
|
const latest = exportFiles[0];
|
|
4358
|
-
const src =
|
|
4550
|
+
const src = path28.join(storeDir, latest);
|
|
4359
4551
|
let destPath;
|
|
4360
4552
|
try {
|
|
4361
|
-
const stat =
|
|
4362
|
-
destPath = stat.isDirectory() ?
|
|
4553
|
+
const stat = fs25.statSync(dest);
|
|
4554
|
+
destPath = stat.isDirectory() ? path28.join(dest, latest) : dest;
|
|
4363
4555
|
} catch {
|
|
4364
4556
|
destPath = dest;
|
|
4365
4557
|
}
|
|
4366
|
-
const destDir =
|
|
4558
|
+
const destDir = path28.dirname(path28.resolve(destPath));
|
|
4367
4559
|
let resolvedDir;
|
|
4368
4560
|
try {
|
|
4369
|
-
resolvedDir =
|
|
4561
|
+
resolvedDir = fs25.realpathSync(destDir);
|
|
4370
4562
|
} catch {
|
|
4371
4563
|
resolvedDir = destDir;
|
|
4372
4564
|
}
|
|
@@ -4376,7 +4568,7 @@ async function teamsShare(options) {
|
|
|
4376
4568
|
`);
|
|
4377
4569
|
process.exit(1);
|
|
4378
4570
|
}
|
|
4379
|
-
|
|
4571
|
+
fs25.copyFileSync(src, destPath);
|
|
4380
4572
|
process.stdout.write(` \u2713 Copied ${latest} \u2192 ${destPath}
|
|
4381
4573
|
|
|
4382
4574
|
`);
|
|
@@ -4401,8 +4593,148 @@ async function teamsCommand(subcommand, options) {
|
|
|
4401
4593
|
}
|
|
4402
4594
|
}
|
|
4403
4595
|
|
|
4596
|
+
// src/commands/revert.ts
|
|
4597
|
+
init_cjs_shims();
|
|
4598
|
+
var path29 = __toESM(require("path"));
|
|
4599
|
+
function timeAgo(isoString) {
|
|
4600
|
+
const diffMs = Date.now() - new Date(isoString).getTime();
|
|
4601
|
+
const minutes = Math.floor(diffMs / 6e4);
|
|
4602
|
+
if (minutes < 1) return "just now";
|
|
4603
|
+
if (minutes < 60) return `${minutes} min ago`;
|
|
4604
|
+
const hours = Math.floor(minutes / 60);
|
|
4605
|
+
if (hours < 24) return `${hours} hour${hours === 1 ? "" : "s"} ago`;
|
|
4606
|
+
const days = Math.floor(hours / 24);
|
|
4607
|
+
return `${days} day${days === 1 ? "" : "s"} ago`;
|
|
4608
|
+
}
|
|
4609
|
+
function formatBytes(bytes) {
|
|
4610
|
+
if (bytes < 1024) return `${bytes} B`;
|
|
4611
|
+
return `${Math.round(bytes / 1024)} KB`;
|
|
4612
|
+
}
|
|
4613
|
+
function printBackupTable(entries) {
|
|
4614
|
+
if (entries.length === 0) {
|
|
4615
|
+
process.stdout.write(" No backups found.\n");
|
|
4616
|
+
process.stdout.write(` Backups are created automatically when claudectx modifies your files.
|
|
4617
|
+
`);
|
|
4618
|
+
process.stdout.write(` Backup directory: ${BACKUP_DIR}
|
|
4619
|
+
|
|
4620
|
+
`);
|
|
4621
|
+
return;
|
|
4622
|
+
}
|
|
4623
|
+
const idWidth = 26;
|
|
4624
|
+
const fileWidth = 20;
|
|
4625
|
+
const cmdWidth = 10;
|
|
4626
|
+
const timeWidth = 14;
|
|
4627
|
+
const sizeWidth = 7;
|
|
4628
|
+
const hr = "\u2550".repeat(idWidth + fileWidth + cmdWidth + timeWidth + sizeWidth + 16);
|
|
4629
|
+
process.stdout.write("\n");
|
|
4630
|
+
process.stdout.write("claudectx \u2014 Backup History\n");
|
|
4631
|
+
process.stdout.write(hr + "\n");
|
|
4632
|
+
process.stdout.write(
|
|
4633
|
+
` ${"ID".padEnd(idWidth)} ${"File".padEnd(fileWidth)} ${"Command".padEnd(cmdWidth)} ${"When".padEnd(timeWidth)} ${"Size".padEnd(sizeWidth)}
|
|
4634
|
+
`
|
|
4635
|
+
);
|
|
4636
|
+
process.stdout.write("\u2500".repeat(idWidth + fileWidth + cmdWidth + timeWidth + sizeWidth + 16) + "\n");
|
|
4637
|
+
for (const entry of entries) {
|
|
4638
|
+
const id = entry.id.slice(0, idWidth).padEnd(idWidth);
|
|
4639
|
+
const file = path29.basename(entry.originalPath).slice(0, fileWidth).padEnd(fileWidth);
|
|
4640
|
+
const cmd = entry.command.slice(0, cmdWidth).padEnd(cmdWidth);
|
|
4641
|
+
const when = timeAgo(entry.createdAt).slice(0, timeWidth).padEnd(timeWidth);
|
|
4642
|
+
const size = formatBytes(entry.sizeBytes).padEnd(sizeWidth);
|
|
4643
|
+
process.stdout.write(` ${id} ${file} ${cmd} ${when} ${size}
|
|
4644
|
+
`);
|
|
4645
|
+
}
|
|
4646
|
+
process.stdout.write("\n");
|
|
4647
|
+
process.stdout.write(` Backup directory: ${BACKUP_DIR}
|
|
4648
|
+
`);
|
|
4649
|
+
process.stdout.write(" To restore: claudectx revert --id <ID>\n\n");
|
|
4650
|
+
}
|
|
4651
|
+
async function interactivePick(entries) {
|
|
4652
|
+
try {
|
|
4653
|
+
const { select } = await import("@inquirer/prompts");
|
|
4654
|
+
const choices = entries.map((e) => ({
|
|
4655
|
+
name: `${timeAgo(e.createdAt).padEnd(14)} ${path29.basename(e.originalPath).padEnd(16)} [${e.command}] ${e.id}`,
|
|
4656
|
+
value: e.id
|
|
4657
|
+
}));
|
|
4658
|
+
choices.push({ name: "Cancel", value: "" });
|
|
4659
|
+
return await select({ message: "Choose a backup to restore:", choices });
|
|
4660
|
+
} catch {
|
|
4661
|
+
process.stderr.write("Interactive mode unavailable. Use --id <id> to restore a specific backup.\n");
|
|
4662
|
+
return null;
|
|
4663
|
+
}
|
|
4664
|
+
}
|
|
4665
|
+
async function doRestore(id) {
|
|
4666
|
+
const chalk5 = (await import("chalk")).default;
|
|
4667
|
+
process.stdout.write("\n");
|
|
4668
|
+
try {
|
|
4669
|
+
const entries = await listBackups();
|
|
4670
|
+
const entry = entries.find((e) => e.id === id);
|
|
4671
|
+
if (!entry) {
|
|
4672
|
+
process.stderr.write(chalk5.red(`Backup "${id}" not found.
|
|
4673
|
+
`));
|
|
4674
|
+
process.stderr.write('Run "claudectx revert --list" to see available backups.\n');
|
|
4675
|
+
process.exitCode = 1;
|
|
4676
|
+
return;
|
|
4677
|
+
}
|
|
4678
|
+
process.stdout.write(chalk5.yellow(`\u26A0 This will overwrite: ${entry.originalPath}
|
|
4679
|
+
`));
|
|
4680
|
+
process.stdout.write(` Backup from: ${timeAgo(entry.createdAt)} (${entry.command})
|
|
4681
|
+
`);
|
|
4682
|
+
process.stdout.write(` Your current file will be backed up first (so you can undo this).
|
|
4683
|
+
|
|
4684
|
+
`);
|
|
4685
|
+
let confirmed = true;
|
|
4686
|
+
try {
|
|
4687
|
+
const { confirm: confirm2 } = await import("@inquirer/prompts");
|
|
4688
|
+
confirmed = await confirm2({ message: "Restore this backup?", default: false });
|
|
4689
|
+
} catch {
|
|
4690
|
+
}
|
|
4691
|
+
if (!confirmed) {
|
|
4692
|
+
process.stdout.write(" Cancelled.\n\n");
|
|
4693
|
+
return;
|
|
4694
|
+
}
|
|
4695
|
+
const { undoEntry } = await restoreBackup(id);
|
|
4696
|
+
process.stdout.write(chalk5.green(" \u2713 ") + `Restored to ${entry.originalPath}
|
|
4697
|
+
`);
|
|
4698
|
+
if (undoEntry) {
|
|
4699
|
+
process.stdout.write(
|
|
4700
|
+
chalk5.dim(` Your previous version was saved as backup "${undoEntry.id}" \u2014 run 'claudectx revert --id ${undoEntry.id}' to undo.
|
|
4701
|
+
`)
|
|
4702
|
+
);
|
|
4703
|
+
}
|
|
4704
|
+
process.stdout.write("\n");
|
|
4705
|
+
} catch (err) {
|
|
4706
|
+
process.stderr.write(chalk5.red(`Error: ${err instanceof Error ? err.message : String(err)}
|
|
4707
|
+
`));
|
|
4708
|
+
process.exitCode = 1;
|
|
4709
|
+
}
|
|
4710
|
+
}
|
|
4711
|
+
async function revertCommand(options) {
|
|
4712
|
+
const entries = await listBackups(options.file);
|
|
4713
|
+
if (options.json) {
|
|
4714
|
+
process.stdout.write(JSON.stringify({ backups: entries }, null, 2) + "\n");
|
|
4715
|
+
return;
|
|
4716
|
+
}
|
|
4717
|
+
if (options.list) {
|
|
4718
|
+
printBackupTable(entries);
|
|
4719
|
+
return;
|
|
4720
|
+
}
|
|
4721
|
+
if (options.id) {
|
|
4722
|
+
await doRestore(options.id);
|
|
4723
|
+
return;
|
|
4724
|
+
}
|
|
4725
|
+
if (entries.length === 0) {
|
|
4726
|
+
process.stdout.write("\n No backups found. Backups are created automatically when claudectx modifies your files.\n\n");
|
|
4727
|
+
return;
|
|
4728
|
+
}
|
|
4729
|
+
printBackupTable(entries);
|
|
4730
|
+
const picked = await interactivePick(entries);
|
|
4731
|
+
if (picked) {
|
|
4732
|
+
await doRestore(picked);
|
|
4733
|
+
}
|
|
4734
|
+
}
|
|
4735
|
+
|
|
4404
4736
|
// src/index.ts
|
|
4405
|
-
var VERSION = "1.1.
|
|
4737
|
+
var VERSION = "1.1.3";
|
|
4406
4738
|
var DESCRIPTION = "Reduce Claude Code token usage by up to 80%. Context analyzer, auto-optimizer, live dashboard, and smart MCP tools.";
|
|
4407
4739
|
var program = new import_commander.Command();
|
|
4408
4740
|
program.name("claudectx").description(DESCRIPTION).version(VERSION);
|
|
@@ -4442,5 +4774,8 @@ program.command("convert").description("Convert CLAUDE.md to another AI assistan
|
|
|
4442
4774
|
program.command("teams [subcommand]").description("Multi-developer cost attribution (export | aggregate | share)").option("--days <n>", "Days to include", "30").option("-m, --model <model>", "Model", "sonnet").option("--anonymize", "Replace identities with Dev 1, Dev 2...").option("--dir <path>", "Directory with team export JSON files").option("--to <path>", "Destination for share sub-command").option("--json", "JSON output").action(async (subcommand, options) => {
|
|
4443
4775
|
await teamsCommand(subcommand ?? "export", options);
|
|
4444
4776
|
});
|
|
4777
|
+
program.command("revert").description("List and restore backups created automatically by claudectx commands").option("--list", "Show all backups").option("--id <id>", "Restore a specific backup by ID").option("--file <path>", "Filter backups by original file path").option("--json", "JSON output").action(async (options) => {
|
|
4778
|
+
await revertCommand(options);
|
|
4779
|
+
});
|
|
4445
4780
|
program.parse();
|
|
4446
4781
|
//# sourceMappingURL=index.js.map
|