claude-roi 0.8.6 → 0.8.7
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/package.json +1 -1
- package/src/index.js +29 -16
- package/src/server.js +3 -3
package/package.json
CHANGED
package/src/index.js
CHANGED
|
@@ -15,6 +15,20 @@ const { version: VERSION } = JSON.parse(
|
|
|
15
15
|
readFileSync(new URL('../package.json', import.meta.url), 'utf8')
|
|
16
16
|
);
|
|
17
17
|
|
|
18
|
+
// ── pretty CLI output helpers ──
|
|
19
|
+
const c = {
|
|
20
|
+
reset: '\x1b[0m', bold: '\x1b[1m', dim: '\x1b[2m',
|
|
21
|
+
green: '\x1b[32m', yellow: '\x1b[33m', cyan: '\x1b[36m', red: '\x1b[31m',
|
|
22
|
+
};
|
|
23
|
+
const icon = {
|
|
24
|
+
ok: `${c.green}✔${c.reset}`,
|
|
25
|
+
dot: `${c.cyan}◆${c.reset}`,
|
|
26
|
+
arrow: `${c.cyan}▸${c.reset}`,
|
|
27
|
+
warn: `${c.yellow}⚠${c.reset}`,
|
|
28
|
+
err: `${c.red}✖${c.reset}`,
|
|
29
|
+
};
|
|
30
|
+
const fmt = (ms) => ms >= 1000 ? `${(ms / 1000).toFixed(1)}s` : `${ms}ms`;
|
|
31
|
+
|
|
18
32
|
async function buildPayload(claudeDir, days, project, forceRefresh = false) {
|
|
19
33
|
// Step 1: Parse sessions (with caching)
|
|
20
34
|
let sessions;
|
|
@@ -23,7 +37,7 @@ async function buildPayload(claudeDir, days, project, forceRefresh = false) {
|
|
|
23
37
|
|
|
24
38
|
if (forceRefresh) {
|
|
25
39
|
deleteCache();
|
|
26
|
-
console.log(
|
|
40
|
+
console.log(` ${icon.arrow} Cache cleared, performing full parse...`);
|
|
27
41
|
}
|
|
28
42
|
|
|
29
43
|
const cached = forceRefresh ? null : loadCache();
|
|
@@ -38,18 +52,18 @@ async function buildPayload(claudeDir, days, project, forceRefresh = false) {
|
|
|
38
52
|
if (newCount === 0 && modifiedCount === 0 && deletedCount === 0) {
|
|
39
53
|
sessions = cached.sessions;
|
|
40
54
|
fileIndex = cached.fileIndex;
|
|
41
|
-
console.log(`Parsing sessions
|
|
55
|
+
console.log(` ${icon.ok} Parsing sessions ${c.dim}── ${cached.sessions.length} cached (${fmt(Date.now() - startParse)})${c.reset}`);
|
|
42
56
|
} else {
|
|
43
57
|
const { sessions: freshSessions, fileIndex: freshIndex } = await parseAllProjects(claudeDir, days, project);
|
|
44
58
|
sessions = freshSessions;
|
|
45
59
|
fileIndex = freshIndex;
|
|
46
|
-
console.log(`Parsing sessions
|
|
60
|
+
console.log(` ${icon.ok} Parsing sessions ${c.dim}── ${newCount} new, ${modifiedCount} updated, ${Math.max(0, cachedCount)} cached (${fmt(Date.now() - startParse)})${c.reset}`);
|
|
47
61
|
}
|
|
48
62
|
} else {
|
|
49
63
|
const result = await parseAllProjects(claudeDir, days, project);
|
|
50
64
|
sessions = result.sessions;
|
|
51
65
|
fileIndex = result.fileIndex;
|
|
52
|
-
console.log(`Parsing sessions
|
|
66
|
+
console.log(` ${icon.ok} Parsing sessions ${c.dim}── ${sessions.length} parsed (${fmt(Date.now() - startParse)})${c.reset}`);
|
|
53
67
|
}
|
|
54
68
|
|
|
55
69
|
if (sessions.length === 0) {
|
|
@@ -63,11 +77,11 @@ async function buildPayload(claudeDir, days, project, forceRefresh = false) {
|
|
|
63
77
|
for (const repoPath of repoPathsSet) {
|
|
64
78
|
commitsByRepo[repoPath] = analyzeGitRepo(repoPath, days);
|
|
65
79
|
}
|
|
66
|
-
console.log(`Analyzing ${repoPathsSet.size}
|
|
80
|
+
console.log(` ${icon.ok} Analyzing git repos ${c.dim}── ${repoPathsSet.size} repos (${fmt(Date.now() - startGit)})${c.reset}`);
|
|
67
81
|
|
|
68
82
|
// Step 3: Correlate sessions with commits
|
|
69
83
|
const { correlatedSessions, organicCommits } = correlateSessions(sessions, commitsByRepo);
|
|
70
|
-
console.log(
|
|
84
|
+
console.log(` ${icon.ok} Correlating sessions ${c.dim}── done${c.reset}`);
|
|
71
85
|
|
|
72
86
|
// Step 4: Compute metrics
|
|
73
87
|
const payload = computeMetrics(correlatedSessions, organicCommits, commitsByRepo, days);
|
|
@@ -100,11 +114,10 @@ async function main() {
|
|
|
100
114
|
|
|
101
115
|
const invokedAs = path.basename(process.argv[1]);
|
|
102
116
|
if (invokedAs.includes('claude-roi')) {
|
|
103
|
-
console.log(
|
|
104
|
-
console.log(`
|
|
105
|
-
console.log('');
|
|
117
|
+
console.log(` ${icon.warn} ${c.yellow}claude-roi has been renamed to codelens-ai${c.reset}`);
|
|
118
|
+
console.log(` Switch to: ${c.cyan}npx codelens-ai${c.reset}\n`);
|
|
106
119
|
}
|
|
107
|
-
console.log(
|
|
120
|
+
console.log(`${icon.dot} ${c.bold}${c.cyan}codelens-ai${c.reset} v${VERSION}\n`);
|
|
108
121
|
|
|
109
122
|
const claudeDir = path.join(os.homedir(), '.claude', 'projects');
|
|
110
123
|
|
|
@@ -112,8 +125,8 @@ async function main() {
|
|
|
112
125
|
if (payload) payload.meta.invokedAs = invokedAs.includes('claude-roi') ? 'claude-roi' : 'codelens-ai';
|
|
113
126
|
|
|
114
127
|
if (!payload) {
|
|
115
|
-
console.log(
|
|
116
|
-
console.log(
|
|
128
|
+
console.log(` ${icon.warn} ${c.yellow}No Claude Code sessions found.${c.reset}`);
|
|
129
|
+
console.log(` Make sure you have used Claude Code and session files exist in ~/.claude/projects/`);
|
|
117
130
|
process.exit(0);
|
|
118
131
|
}
|
|
119
132
|
|
|
@@ -138,7 +151,7 @@ async function main() {
|
|
|
138
151
|
console.log(` ${line}`);
|
|
139
152
|
if (am.topVerificationCommands.length > 0) {
|
|
140
153
|
const top3 = am.topVerificationCommands.slice(0, 3)
|
|
141
|
-
.map(
|
|
154
|
+
.map(cmd => `${cmd.command} (${cmd.count})`).join(', ');
|
|
142
155
|
console.log(` Top Tests: ${top3}`);
|
|
143
156
|
}
|
|
144
157
|
console.log('');
|
|
@@ -150,7 +163,7 @@ async function main() {
|
|
|
150
163
|
const app = createServer(payload, rebuild);
|
|
151
164
|
const server = app.listen(port, () => {
|
|
152
165
|
const url = `http://localhost:${port}`;
|
|
153
|
-
console.log(`\
|
|
166
|
+
console.log(`\n ${icon.ok} ${c.green}Dashboard:${c.reset} ${c.bold}${url}${c.reset}`);
|
|
154
167
|
|
|
155
168
|
if (opts.open !== false) {
|
|
156
169
|
import('open').then(mod => mod.default(url)).catch(() => {
|
|
@@ -161,7 +174,7 @@ async function main() {
|
|
|
161
174
|
|
|
162
175
|
server.on('error', (err) => {
|
|
163
176
|
if (err.code === 'EADDRINUSE') {
|
|
164
|
-
console.error(
|
|
177
|
+
console.error(` ${icon.err} ${c.red}Port ${port} is already in use.${c.reset} Try: codelens-ai --port ${port + 1}`);
|
|
165
178
|
process.exit(1);
|
|
166
179
|
}
|
|
167
180
|
throw err;
|
|
@@ -169,6 +182,6 @@ async function main() {
|
|
|
169
182
|
}
|
|
170
183
|
|
|
171
184
|
main().catch(err => {
|
|
172
|
-
console.error(
|
|
185
|
+
console.error(` ${icon.err} ${c.red}Error:${c.reset}`, err.message);
|
|
173
186
|
process.exit(1);
|
|
174
187
|
});
|
package/src/server.js
CHANGED
|
@@ -25,14 +25,14 @@ export function createServer(initialPayload, rebuildFn) {
|
|
|
25
25
|
app.post('/api/refresh', async (req, res) => {
|
|
26
26
|
if (!rebuildFn) return res.status(501).json({ error: 'Refresh not available' });
|
|
27
27
|
try {
|
|
28
|
-
console.log('\x1b[36m[refresh]\x1b[0m Re-parsing sessions and recomputing metrics...');
|
|
28
|
+
console.log(' \x1b[36m▸\x1b[0m \x1b[36m[refresh]\x1b[0m Re-parsing sessions and recomputing metrics...');
|
|
29
29
|
const newPayload = await rebuildFn();
|
|
30
30
|
if (!newPayload) return res.status(404).json({ error: 'No sessions found after refresh' });
|
|
31
31
|
payload = newPayload;
|
|
32
|
-
console.log('\x1b[32m[refresh]\x1b[0m Done');
|
|
32
|
+
console.log(' \x1b[32m✔\x1b[0m \x1b[32m[refresh]\x1b[0m Done');
|
|
33
33
|
res.json({ ok: true });
|
|
34
34
|
} catch (err) {
|
|
35
|
-
console.error('\x1b[31m[refresh]\x1b[0m Error:', err.message);
|
|
35
|
+
console.error(' \x1b[31m✖\x1b[0m \x1b[31m[refresh]\x1b[0m Error:', err.message);
|
|
36
36
|
res.status(500).json({ error: err.message });
|
|
37
37
|
}
|
|
38
38
|
});
|