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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-roi",
3
- "version": "0.8.6",
3
+ "version": "0.8.7",
4
4
  "description": "Correlate AI coding agent token usage with git output to measure ROI",
5
5
  "type": "module",
6
6
  "bin": {
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('Cache cleared, performing full parse...');
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... ${cached.sessions.length} cached (${Date.now() - startParse}ms)`);
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... ${newCount} new, ${modifiedCount} updated, ${Math.max(0, cachedCount)} cached (${Date.now() - startParse}ms)`);
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... ${sessions.length} parsed (${Date.now() - startParse}ms)`);
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} git repo(s)... done (${Date.now() - startGit}ms)`);
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('Correlating sessions with commits... done');
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(`\x1b[33m⚠ claude-roi has been renamed to codelens-ai\x1b[0m`);
104
- console.log(` Switch to: \x1b[36mnpx codelens-ai\x1b[0m`);
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(`\x1b[36mcodelens-ai\x1b[0m v${VERSION}`);
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('\x1b[33mNo Claude Code sessions found.\x1b[0m');
116
- console.log('Make sure you have used Claude Code and session files exist in ~/.claude/projects/');
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(c => `${c.command} (${c.count})`).join(', ');
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(`\x1b[32mDashboard:\x1b[0m ${url}`);
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(`\x1b[31mPort ${port} is already in use.\x1b[0m Try: codelens-ai --port ${port + 1}`);
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('\x1b[31mError:\x1b[0m', err.message);
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
  });