chekk 0.1.0 → 0.2.0

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/bin/chekk.js CHANGED
@@ -7,11 +7,12 @@ const program = new Command();
7
7
 
8
8
  program
9
9
  .name('chekk')
10
- .description('See how you prompt. Analyze your AI coding workflow.')
11
- .version('0.1.0')
12
- .option('--offline', 'Skip prose generation and show raw metrics only')
10
+ .description('The engineering capability score. See how you prompt.')
11
+ .version('0.2.0')
12
+ .option('--offline', 'Skip AI prose generation, show data-driven output')
13
+ .option('--verbose', 'Show detailed per-project and per-metric breakdowns')
13
14
  .option('--json', 'Output raw metrics as JSON')
14
- .option('--no-upload', 'Skip the claim/upload prompt')
15
+ .option('--no-upload', 'Skip the claim prompt')
15
16
  .action(async (options) => {
16
17
  await run(options);
17
18
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "chekk",
3
- "version": "0.1.0",
3
+ "version": "0.2.0",
4
4
  "description": "See how you prompt. Chekk analyzes your AI coding workflow and tells you what kind of engineer you are.",
5
5
  "bin": {
6
6
  "chekk": "./bin/chekk.js"
package/src/display.js CHANGED
@@ -1,137 +1,371 @@
1
1
  import chalk from 'chalk';
2
2
 
3
- /**
4
- * Display detection results.
5
- */
6
- export function displayDetection(tools) {
3
+ // ── Color palette ──
4
+ const gold = chalk.hex('#FFD700');
5
+ const purple = chalk.hex('#A855F7');
6
+ const blue = chalk.hex('#3B82F6');
7
+ const green = chalk.hex('#22C55E');
8
+ const cyan = chalk.hex('#06B6D4');
9
+ const dim = chalk.dim;
10
+ const bold = chalk.bold;
11
+ const white = chalk.white;
12
+
13
+ function tierColor(tier) {
14
+ if (tier === 'LEGENDARY') return gold;
15
+ if (tier === 'RARE') return purple;
16
+ if (tier === 'UNCOMMON') return blue;
17
+ return dim;
18
+ }
19
+
20
+ function scoreColor(score) {
21
+ if (score >= 85) return green;
22
+ if (score >= 70) return cyan;
23
+ if (score >= 55) return chalk.hex('#EAB308');
24
+ if (score >= 40) return chalk.hex('#F97316');
25
+ return chalk.hex('#EF4444');
26
+ }
27
+
28
+ function progressBar(score, width = 18) {
29
+ const filled = Math.round((score / 100) * width);
30
+ const empty = width - filled;
31
+ return scoreColor(score)('\u2588'.repeat(filled)) + dim('\u2591'.repeat(empty));
32
+ }
33
+
34
+ function numberFormat(n) {
35
+ if (n >= 1000) return (n / 1000).toFixed(1).replace(/\.0$/, '') + 'k';
36
+ return String(n);
37
+ }
38
+
39
+ function pad(str, len) {
40
+ const visible = str.replace(/\u001b\[[0-9;]*m/g, '');
41
+ return str + ' '.repeat(Math.max(0, len - visible.length));
42
+ }
43
+
44
+ // ── Box drawing ──
45
+
46
+ function box(lines, width = 43) {
47
+ const out = [];
48
+ out.push(dim(' \u250C' + '\u2500'.repeat(width) + '\u2510'));
49
+ for (const line of lines) {
50
+ const visible = line.replace(/\u001b\[[0-9;]*m/g, '');
51
+ const padding = Math.max(0, width - visible.length);
52
+ out.push(dim(' \u2502') + line + ' '.repeat(padding) + dim('\u2502'));
53
+ }
54
+ out.push(dim(' \u2514' + '\u2500'.repeat(width) + '\u2518'));
55
+ return out;
56
+ }
57
+
58
+ function doubleRule(width = 47) {
59
+ return dim(' ' + '\u2550'.repeat(width));
60
+ }
61
+
62
+ // ══════════════════════════════════════════════
63
+ // HEADER
64
+ // ══════════════════════════════════════════════
65
+
66
+ export function displayHeader() {
67
+ console.log();
68
+ const lines = [
69
+ '',
70
+ ` ${bold.white('chekk')}${dim(' v0.2.0')}`,
71
+ ` ${dim('the engineering capability score')}`,
72
+ '',
73
+ ];
74
+ for (const l of box(lines)) console.log(l);
7
75
  console.log();
76
+ }
77
+
78
+ // ══════════════════════════════════════════════
79
+ // SCAN RESULTS
80
+ // ══════════════════════════════════════════════
81
+
82
+ export function displayScan(tools) {
83
+ console.log(dim(' Scanning local AI tools...\n'));
8
84
 
9
85
  for (const tool of tools) {
10
86
  if (tool.status === 'detected_not_supported') {
11
- console.log(chalk.dim(` Found ${tool.tool} ${tool.message}`));
87
+ console.log(` ${dim('\u25CB')} ${dim(tool.tool)}${dim(' detected \u2014 V2')}`);
12
88
  } else {
89
+ const sessions = numberFormat(tool.sessions);
90
+ const projects = tool.projects.length;
91
+ const exchanges = numberFormat(tool.estimatedPrompts);
13
92
  console.log(
14
- chalk.green(` Found ${tool.tool}`) +
15
- chalk.dim(` (${tool.sessions} sessions across ${tool.projects.length} projects)`)
93
+ ` ${cyan('\u2726')} ${bold.white(tool.tool)} ` +
94
+ dim(`${sessions} sessions \u00B7 ${projects} projects`)
16
95
  );
17
96
  }
18
97
  }
19
98
  console.log();
20
99
  }
21
100
 
22
- /**
23
- * Display a progress bar.
24
- */
25
- function progressBar(score, width = 20) {
26
- const filled = Math.round((score / 100) * width);
27
- const empty = width - filled;
101
+ // ══════════════════════════════════════════════
102
+ // ANALYSIS PROGRESS BAR
103
+ // ══════════════════════════════════════════════
104
+
105
+ export function displayAnalysisStart(dateRange) {
106
+ console.log(` ${dim('Analyzing')} ${white(dateRange)}${dim('...\n')}`);
107
+ }
28
108
 
29
- const color = score >= 80 ? chalk.green :
30
- score >= 60 ? chalk.yellow :
31
- score >= 40 ? chalk.hex('#FFA500') :
32
- chalk.red;
109
+ export async function displayProgressBar(durationMs = 2000) {
110
+ const width = 40;
111
+ const steps = 40;
112
+ const stepTime = durationMs / steps;
33
113
 
34
- return color('\u2588'.repeat(filled)) + chalk.dim('\u2591'.repeat(empty));
114
+ for (let i = 0; i <= steps; i++) {
115
+ const filled = i;
116
+ const empty = width - filled;
117
+ const pct = Math.round((i / steps) * 100);
118
+ const bar = green('\u2588'.repeat(filled)) + dim('\u2591'.repeat(empty));
119
+ process.stdout.write(`\r ${bar} ${dim(pct + '%')}`);
120
+ if (i < steps) await sleep(stepTime);
121
+ }
122
+ console.log();
123
+ console.log();
124
+ console.log(` ${green('\u2713')} ${dim('Score generated.')}`);
125
+ console.log();
35
126
  }
36
127
 
37
- /**
38
- * Display the final Chekk results with prose from the API.
39
- */
40
- export function displayResults(result, prose) {
41
- const { overall, archetype, tier } = result;
128
+ // ══════════════════════════════════════════════
129
+ // MAIN SCORE DISPLAY
130
+ // ══════════════════════════════════════════════
42
131
 
43
- const tierColor = tier === 'LEGENDARY' ? chalk.hex('#FFD700') :
44
- tier === 'RARE' ? chalk.hex('#A855F7') :
45
- tier === 'UNCOMMON' ? chalk.hex('#3B82F6') :
46
- chalk.dim;
132
+ export function displayScore(result, prose) {
133
+ const { overall, scores, archetype, tier } = result;
134
+ const tc = tierColor(tier);
135
+ const sc = scoreColor(overall);
47
136
 
48
- console.log(chalk.dim('\u2500'.repeat(50)));
137
+ // Big score block
138
+ console.log(doubleRule());
139
+ console.log();
140
+ console.log(dim(' YOUR CHEKK SCORE'));
141
+ console.log();
142
+ console.log(` ${sc.bold(String(overall))}`);
143
+ console.log(` ${tc('\u2500\u2500 ' + tier + ' \u2500\u2500')}`);
49
144
  console.log();
145
+ console.log(` ${dim('Archetype:')} ${bold.white(archetype.name)}`);
50
146
 
51
- // Prose sections
147
+ if (prose && prose.tagline) {
148
+ console.log(` ${dim('"' + prose.tagline + '"')}`);
149
+ }
150
+ console.log();
151
+ console.log(doubleRule());
152
+ console.log();
153
+
154
+ // Dimensions box
155
+ console.log(dim(' DIMENSIONS\n'));
156
+ const dimLines = [
157
+ '',
158
+ ` ${pad(bold('Thinking'), 20)} ${progressBar(scores.decomposition)} ${bold(String(scores.decomposition))}`,
159
+ ` ${pad(bold('Debugging'), 20)} ${progressBar(scores.debugCycles)} ${bold(String(scores.debugCycles))}`,
160
+ ` ${pad(bold('AI Leverage'), 20)}${progressBar(scores.aiLeverage)} ${bold(String(scores.aiLeverage))}`,
161
+ ` ${pad(bold('Workflow'), 20)} ${progressBar(scores.sessionStructure)} ${bold(String(scores.sessionStructure))}`,
162
+ '',
163
+ ];
164
+ for (const l of box(dimLines)) console.log(l);
165
+ console.log();
166
+ }
167
+
168
+ // ══════════════════════════════════════════════
169
+ // DIMENSION NARRATIVES (data-driven, punchy)
170
+ // ══════════════════════════════════════════════
171
+
172
+ export function displayNarratives(metrics, prose) {
52
173
  if (prose && prose.sections) {
174
+ // Use DeepSeek-generated prose
53
175
  for (const section of prose.sections) {
54
- console.log(` ${section.emoji} ${chalk.bold(section.title)}`);
55
- // Word-wrap the description
56
- const lines = wordWrap(section.description, 45);
176
+ console.log(` ${section.emoji} ${bold(section.title.toUpperCase())}`);
177
+ const lines = section.description.split('\n').filter(l => l.trim());
57
178
  for (const line of lines) {
58
- console.log(chalk.italic(` ${chalk.dim('"')}${line}${chalk.dim('"')}`));
179
+ console.log(` ${dim(line.trim())}`);
59
180
  }
60
181
  console.log();
61
182
  }
183
+ } else {
184
+ // Fallback: data-driven bullet points
185
+ displayDataNarratives(metrics);
62
186
  }
187
+ }
188
+
189
+ function displayDataNarratives(metrics) {
190
+ const d = metrics.decomposition.details;
191
+ const db = metrics.debugCycles.details;
192
+ const ai = metrics.aiLeverage.details;
193
+ const ss = metrics.sessionStructure.details;
63
194
 
64
- console.log(chalk.dim('\u2500'.repeat(50)));
195
+ // Thinking
196
+ console.log(` ${bold('\uD83E\uDDE0 THINKING')}`);
197
+ const exchPerSession = d.avgExchangesPerSession;
198
+ console.log(` ${dim(exchPerSession > 20 ? `${exchPerSession} avg exchanges/session \u2014 marathon builder` : exchPerSession > 8 ? `${exchPerSession} avg exchanges/session \u2014 iterative` : `${exchPerSession} avg exchanges/session \u2014 concise`)}`);
199
+ console.log(` ${dim(d.avgPromptLength > 500 ? `${numberFormat(d.avgPromptLength)} char avg prompt \u2014 thinks out loud` : `${d.avgPromptLength} char avg prompt \u2014 concise communicator`)}`);
200
+ console.log(` ${dim(d.multiStepSessions > d.singleShotSessions * 2 ? 'Multi-step decomposition over single-shot' : 'Mix of multi-step and single-shot sessions')}`);
65
201
  console.log();
66
202
 
67
- // Archetype + tier
68
- console.log(` ${chalk.bold('\u2192')} You're ${chalk.bold.white(archetype.name)}`);
69
- if (prose && prose.tagline) {
70
- console.log(` ${chalk.dim('"' + prose.tagline + '"')}`);
71
- }
203
+ // Debugging
204
+ console.log(` ${bold('\u26A1 DEBUGGING')}`);
205
+ const turns = db.avgTurnsToResolve;
206
+ console.log(` ${dim(turns <= 2 ? `${turns} turns to resolve \u2014 surgical` : turns <= 4 ? `${turns} turns to resolve \u2014 efficient` : `${turns} turns to resolve \u2014 iterative`)}`);
207
+ console.log(` ${dim(`${db.specificReportRatio}% specific error reports`)}`);
208
+ console.log(` ${dim(db.longLoops === 0 ? 'Zero extended debug loops detected' : `${db.longLoops} extended debug loops`)}`);
209
+ console.log();
210
+
211
+ // AI Leverage
212
+ console.log(` ${bold('\uD83D\uDD27 AI LEVERAGE')}`);
213
+ console.log(` ${dim(`${ai.highLevelRatio}% architecture prompts \u2014 ${ai.highLevelRatio > 25 ? 'high-level partner usage' : 'room to leverage AI more strategically'}`)}`);
214
+ const codingRatio = ai.toolDiversity.coding > ai.toolDiversity.research ? 'Coding-heavy' : 'Research-heavy';
215
+ console.log(` ${dim(`${codingRatio} over ${ai.toolDiversity.coding > ai.toolDiversity.research ? 'research' : 'coding'}-heavy`)}`);
216
+ console.log(` ${dim(`${ai.architecturalPrompts} architectural, ${ai.planningPrompts} planning, ${ai.exploratoryPrompts} exploratory`)}`);
72
217
  console.log();
73
- console.log(` ${chalk.bold('\u2192')} Tier: ${tierColor(tier)} ${progressBar(overall)} ${chalk.bold(overall)}`);
218
+
219
+ // Workflow
220
+ console.log(` ${bold('\uD83D\uDCD0 WORKFLOW')}`);
221
+ console.log(` ${dim(`${ss.contextSetRatio}% context-setting rate \u2014 ${ss.contextSetRatio > 50 ? 'deliberate' : ss.contextSetRatio > 25 ? 'moderate' : 'low'}`)}`);
222
+ console.log(` ${dim(`${ss.reviewEndRatio}% sessions end with review`)}`);
223
+ console.log(` ${dim(`${ss.refinementRatio}% refinement rate \u2014 ${ss.refinementRatio > 20 ? 'critical eye' : 'accepts readily'}`)}`);
74
224
  console.log();
75
225
  }
76
226
 
77
- /**
78
- * Display offline/raw metric results (no API call).
79
- */
80
- export function displayOfflineResults(result) {
81
- const { overall, scores, archetype, tier } = result;
227
+ // ══════════════════════════════════════════════
228
+ // EASTER EGGS
229
+ // ══════════════════════════════════════════════
82
230
 
83
- const tierColor = tier === 'LEGENDARY' ? chalk.hex('#FFD700') :
84
- tier === 'RARE' ? chalk.hex('#A855F7') :
85
- tier === 'UNCOMMON' ? chalk.hex('#3B82F6') :
86
- chalk.dim;
231
+ export function displayEasterEggs(result, metrics) {
232
+ const { overall } = result;
233
+ const db = metrics.debugCycles.details;
87
234
 
88
- console.log(chalk.dim('\u2500'.repeat(50)));
89
- console.log();
235
+ const eggs = [];
236
+
237
+ if (overall >= 90) {
238
+ eggs.push('Top-tier. Companies should be applying to you.');
239
+ }
90
240
 
91
- console.log(` ${chalk.bold('Decomposition')} ${progressBar(scores.decomposition)} ${scores.decomposition}`);
92
- console.log(` ${chalk.bold('Debug Efficiency')} ${progressBar(scores.debugCycles)} ${scores.debugCycles}`);
93
- console.log(` ${chalk.bold('AI Leverage')} ${progressBar(scores.aiLeverage)} ${scores.aiLeverage}`);
94
- console.log(` ${chalk.bold('Workflow')} ${progressBar(scores.sessionStructure)} ${scores.sessionStructure}`);
241
+ if (db.longLoops === 0 && db.totalDebugSequences > 10) {
242
+ eggs.push('Zero debug spirals. Either you\'re exceptional or your AI is.');
243
+ }
244
+
245
+ if (db.vagueReports === 0 && db.totalDebugSequences > 20) {
246
+ eggs.push('Never once said "it\'s broken, fix it." Respect.');
247
+ }
248
+
249
+ if (eggs.length > 0) {
250
+ for (const egg of eggs) {
251
+ console.log(` ${dim('\u2727')} ${dim.italic(egg)}`);
252
+ }
253
+ console.log();
254
+ }
255
+ }
256
+
257
+ // ══════════════════════════════════════════════
258
+ // VIRAL ENDING
259
+ // ══════════════════════════════════════════════
260
+
261
+ export function displayEnding(result) {
262
+ const { overall, archetype, tier } = result;
263
+ const tc = tierColor(tier);
264
+
265
+ console.log(doubleRule());
95
266
  console.log();
96
- console.log(chalk.dim('\u2500'.repeat(50)));
267
+ console.log(` ${dim('\u2192 Claim your profile:')} ${cyan.underline('chekk.dev/claim')}`);
268
+ console.log(` ${dim('\u2192 Leaderboard:')} ${cyan.underline('chekk.dev/leaderboard')}`);
97
269
  console.log();
98
270
 
99
- console.log(` ${chalk.bold('\u2192')} You're ${chalk.bold.white(archetype.name)}`);
100
- console.log(` ${chalk.bold('\u2192')} Tier: ${tierColor(tier)} ${progressBar(overall)} ${chalk.bold(overall)}`);
271
+ // Copy-paste share line
272
+ const shareLine = `${overall} \u2014 ${tier} \u2014 ${archetype.name}`;
273
+ console.log(` ${dim('"')}${tc(shareLine)}${dim('"')}`);
274
+ console.log(` ${dim('\u2191 Copy this to share')}`);
101
275
  console.log();
102
- console.log(chalk.dim(' Run without --offline for personalized AI-generated insights'));
276
+ console.log(doubleRule());
277
+ console.log(` ${dim('chekk.dev \u2014 the engineering capability score')}`);
103
278
  console.log();
104
279
  }
105
280
 
106
- /**
107
- * Display the claim prompt.
108
- */
109
- export function displayClaimPrompt(claimUrl) {
110
- console.log(chalk.dim('\u2500'.repeat(50)));
281
+ // ══════════════════════════════════════════════
282
+ // VERBOSE: DETAILED BREAKDOWN
283
+ // ══════════════════════════════════════════════
284
+
285
+ export function displayVerbose(metrics, sessions) {
286
+ console.log(doubleRule());
287
+ console.log(dim('\n DETAILED BREAKDOWN\n'));
288
+
289
+ // Per-project stats
290
+ const projects = {};
291
+ for (const s of sessions) {
292
+ const p = s.project || 'unknown';
293
+ if (!projects[p]) projects[p] = { sessions: 0, exchanges: 0, minutes: 0 };
294
+ projects[p].sessions++;
295
+ projects[p].exchanges += s.exchangeCount;
296
+ projects[p].minutes += s.durationMinutes || 0;
297
+ }
298
+
299
+ console.log(bold(' PROJECTS'));
300
+ for (const [name, data] of Object.entries(projects).sort((a, b) => b[1].exchanges - a[1].exchanges)) {
301
+ const shortName = name.length > 30 ? '...' + name.slice(-27) : name;
302
+ console.log(` ${dim(pad(shortName, 32))} ${dim(data.sessions + ' sessions')} ${dim(numberFormat(data.exchanges) + ' exchanges')}`);
303
+ }
111
304
  console.log();
112
- console.log(` ${chalk.bold('Claim your profile:')} ${chalk.cyan.underline(claimUrl)}`);
305
+
306
+ // Full metric details
307
+ console.log(bold(' DECOMPOSITION DETAILS'));
308
+ for (const [k, v] of Object.entries(metrics.decomposition.details)) {
309
+ console.log(` ${dim(pad(k, 30))} ${dim(String(v) + (typeof v === 'number' && k.includes('Ratio') ? '%' : ''))}`);
310
+ }
113
311
  console.log();
114
- console.log(chalk.dim(' Connect GitHub + LinkedIn to make your profile'));
115
- console.log(chalk.dim(' visible to companies hiring AI-native engineers.'));
312
+
313
+ console.log(bold(' DEBUG CYCLE DETAILS'));
314
+ for (const [k, v] of Object.entries(metrics.debugCycles.details)) {
315
+ console.log(` ${dim(pad(k, 30))} ${dim(String(v))}`);
316
+ }
116
317
  console.log();
117
- }
118
318
 
119
- /**
120
- * Word wrap text to a max width.
121
- */
122
- function wordWrap(text, maxWidth) {
123
- const words = text.split(' ');
124
- const lines = [];
125
- let current = '';
319
+ console.log(bold(' AI LEVERAGE DETAILS'));
320
+ for (const [k, v] of Object.entries(metrics.aiLeverage.details)) {
321
+ if (typeof v === 'object') {
322
+ for (const [k2, v2] of Object.entries(v)) {
323
+ console.log(` ${dim(pad(` ${k}.${k2}`, 30))} ${dim(String(v2))}`);
324
+ }
325
+ } else {
326
+ console.log(` ${dim(pad(k, 30))} ${dim(String(v))}`);
327
+ }
328
+ }
329
+ console.log();
126
330
 
127
- for (const word of words) {
128
- if (current.length + word.length + 1 > maxWidth && current.length > 0) {
129
- lines.push(current);
130
- current = word;
331
+ console.log(bold(' SESSION STRUCTURE DETAILS'));
332
+ for (const [k, v] of Object.entries(metrics.sessionStructure.details)) {
333
+ if (typeof v === 'object') {
334
+ for (const [k2, v2] of Object.entries(v)) {
335
+ console.log(` ${dim(pad(` ${k}.${k2}`, 30))} ${dim(String(v2))}`);
336
+ }
131
337
  } else {
132
- current = current ? current + ' ' + word : word;
338
+ console.log(` ${dim(pad(k, 30))} ${dim(String(v) + (typeof v === 'number' && k.includes('Ratio') ? '%' : ''))}`);
133
339
  }
134
340
  }
135
- if (current) lines.push(current);
136
- return lines;
341
+ console.log();
342
+ }
343
+
344
+ // ══════════════════════════════════════════════
345
+ // OFFLINE DISPLAY (no API)
346
+ // ══════════════════════════════════════════════
347
+
348
+ export function displayOffline(result, metrics) {
349
+ displayScore(result, null);
350
+ displayDataNarratives(metrics);
351
+ displayEasterEggs(result, metrics);
352
+ console.log(dim(' Run without --offline for personalized AI-generated insights\n'));
353
+ displayEnding(result);
354
+ }
355
+
356
+ // ══════════════════════════════════════════════
357
+ // FULL ONLINE DISPLAY
358
+ // ══════════════════════════════════════════════
359
+
360
+ export function displayFull(result, metrics, prose) {
361
+ displayScore(result, prose);
362
+ displayNarratives(metrics, prose);
363
+ displayEasterEggs(result, metrics);
364
+ displayEnding(result);
365
+ }
366
+
367
+ // ── Utility ──
368
+
369
+ function sleep(ms) {
370
+ return new Promise(resolve => setTimeout(resolve, ms));
137
371
  }
package/src/index.js CHANGED
@@ -1,5 +1,4 @@
1
1
  import chalk from 'chalk';
2
- import ora from 'ora';
3
2
  import { detectTools } from './detect.js';
4
3
  import { parseAllProjects } from './parsers/claude-code.js';
5
4
  import { computeDecomposition } from './metrics/decomposition.js';
@@ -7,81 +6,76 @@ import { computeDebugCycles } from './metrics/debug-cycles.js';
7
6
  import { computeAILeverage } from './metrics/ai-leverage.js';
8
7
  import { computeSessionStructure } from './metrics/session-structure.js';
9
8
  import { computeOverallScore } from './scorer.js';
10
- import { displayDetection, displayResults, displayOfflineResults, displayClaimPrompt } from './display.js';
9
+ import {
10
+ displayHeader,
11
+ displayScan,
12
+ displayAnalysisStart,
13
+ displayProgressBar,
14
+ displayFull,
15
+ displayOffline,
16
+ displayVerbose,
17
+ } from './display.js';
11
18
  import { generateProse, askClaim, uploadAndClaim } from './upload.js';
12
19
 
13
20
  export async function run(options = {}) {
14
- console.log();
15
- console.log(chalk.bold(' chekk') + chalk.dim(' — see how you prompt'));
16
- console.log();
21
+ // ── Header ──
22
+ displayHeader();
17
23
 
18
- // Step 1: Detect tools
19
- const spinner = ora({ text: 'Scanning for AI coding tools...', indent: 3 }).start();
24
+ // ── Step 1: Detect tools ──
20
25
  const tools = detectTools();
21
26
 
22
27
  if (tools.length === 0) {
23
- spinner.fail('No AI coding tools detected');
24
- console.log();
25
- console.log(chalk.dim(' Chekk currently supports Claude Code.'));
26
- console.log(chalk.dim(' Cursor and Codex support coming in V2.'));
27
- console.log();
28
+ console.log(chalk.dim(' No AI coding tools detected.'));
29
+ console.log(chalk.dim(' Chekk currently supports Claude Code.'));
30
+ console.log(chalk.dim(' Cursor and Codex coming in V2.\n'));
28
31
  process.exit(1);
29
32
  }
30
33
 
31
34
  const supported = tools.filter(t => t.status !== 'detected_not_supported');
32
35
  if (supported.length === 0) {
33
- spinner.fail('No supported AI tools found');
34
- console.log();
36
+ console.log(chalk.dim(' No supported tools found.\n'));
35
37
  for (const t of tools) {
36
- console.log(chalk.dim(` Found ${t.tool} ${t.message}`));
38
+ console.log(chalk.dim(` ${t.tool} \u2014 ${t.message}`));
37
39
  }
38
40
  console.log();
39
41
  process.exit(1);
40
42
  }
41
43
 
42
- spinner.succeed('Found AI coding tools');
43
- displayDetection(tools);
44
-
45
- // Step 2: Parse sessions
46
- const parseSpinner = ora({ text: 'Parsing session history...', indent: 3 }).start();
44
+ displayScan(tools);
47
45
 
46
+ // ── Step 2: Parse sessions ──
48
47
  let allSessions = [];
49
48
  for (const tool of supported) {
50
49
  if (tool.tool === 'Claude Code') {
51
- const sessions = parseAllProjects(tool.basePath);
52
- allSessions.push(...sessions);
50
+ allSessions.push(...parseAllProjects(tool.basePath));
53
51
  }
54
52
  }
55
53
 
56
54
  if (allSessions.length === 0) {
57
- parseSpinner.fail('No sessions found to analyze');
58
- console.log();
55
+ console.log(chalk.dim(' No sessions found to analyze.\n'));
59
56
  process.exit(1);
60
57
  }
61
58
 
62
- // Count stats
59
+ // Stats
63
60
  const totalExchanges = allSessions.reduce((sum, s) => sum + s.exchangeCount, 0);
64
61
  const projects = [...new Set(allSessions.map(s => s.project))];
65
-
66
- // Date range
67
62
  const allTimestamps = allSessions
68
63
  .map(s => s.startTime)
69
64
  .filter(Boolean)
70
65
  .map(t => new Date(t))
71
66
  .sort((a, b) => a - b);
72
- const dateRange = allTimestamps.length >= 2
67
+
68
+ const dateRangeShort = allTimestamps.length >= 2
69
+ ? formatDateRange(allTimestamps[0], allTimestamps[allTimestamps.length - 1])
70
+ : 'recent history';
71
+
72
+ const dateRangeFull = allTimestamps.length >= 2
73
73
  ? `${allTimestamps[0].toLocaleDateString()} to ${allTimestamps[allTimestamps.length - 1].toLocaleDateString()}`
74
74
  : 'unknown';
75
75
 
76
- parseSpinner.succeed(
77
- `Parsed ${chalk.bold(allSessions.length)} sessions, ${chalk.bold(totalExchanges)} exchanges`
78
- );
79
- console.log(chalk.dim(` ${projects.length} projects — ${dateRange}`));
80
- console.log();
81
-
82
- // Step 3: Compute metrics
83
- const metricsSpinner = ora({ text: 'Analyzing your workflow patterns...', indent: 3 }).start();
76
+ displayAnalysisStart(dateRangeShort);
84
77
 
78
+ // ── Step 3: Compute metrics ──
85
79
  const metrics = {
86
80
  decomposition: computeDecomposition(allSessions),
87
81
  debugCycles: computeDebugCycles(allSessions),
@@ -91,64 +85,73 @@ export async function run(options = {}) {
91
85
 
92
86
  const result = computeOverallScore(metrics);
93
87
 
94
- metricsSpinner.succeed('Analysis complete');
95
- console.log();
96
-
97
88
  const sessionStats = {
98
89
  totalSessions: allSessions.length,
99
90
  totalExchanges,
100
91
  projectCount: projects.length,
101
- dateRange,
92
+ dateRange: dateRangeFull,
102
93
  tools: supported.map(t => t.tool),
103
94
  };
104
95
 
105
- // JSON output mode
96
+ // Progress bar animation
97
+ await displayProgressBar(1500);
98
+
99
+ // ── JSON output ──
106
100
  if (options.json) {
107
101
  console.log(JSON.stringify({ metrics, result, sessionStats }, null, 2));
108
102
  return;
109
103
  }
110
104
 
111
- // Step 4: Generate prose (unless offline)
105
+ // ── Step 4: Display results ──
112
106
  if (options.offline) {
113
- displayOfflineResults(result);
107
+ displayOffline(result, metrics);
114
108
  } else {
115
- const proseSpinner = ora({ text: 'Generating your personalized profile...', indent: 3 }).start();
116
-
109
+ // Generate prose from API
110
+ let prose = null;
117
111
  try {
118
- const proseResponse = await generateProse(metrics, result, sessionStats);
119
- proseSpinner.succeed('Profile generated');
120
- console.log();
121
- displayResults(result, proseResponse);
122
- } catch (err) {
123
- proseSpinner.warn('Could not reach chekk.dev — showing raw scores');
124
- console.log();
125
- displayOfflineResults(result);
112
+ prose = await generateProse(metrics, result, sessionStats);
113
+ } catch {
114
+ // Silently fall back to data-driven display
126
115
  }
116
+ displayFull(result, metrics, prose);
127
117
  }
128
118
 
129
- // Step 5: Claim prompt
119
+ // ── Verbose breakdown ──
120
+ if (options.verbose) {
121
+ displayVerbose(metrics, allSessions);
122
+ }
123
+
124
+ // ── Step 5: Claim prompt ──
130
125
  if (options.upload !== false) {
131
126
  try {
132
127
  const wantsClaim = await askClaim();
133
128
  if (wantsClaim) {
134
- const claimSpinner = ora({ text: 'Creating your profile...', indent: 3 }).start();
135
129
  try {
136
130
  const claimResult = await uploadAndClaim(metrics, result, sessionStats);
137
- claimSpinner.succeed('Profile created');
138
- displayClaimPrompt(claimResult.claimUrl || 'https://chekk.dev/claim');
131
+ console.log();
132
+ console.log(` ${chalk.green('\u2713')} ${chalk.dim('Profile created:')} ${chalk.cyan.underline(claimResult.claimUrl || 'https://chekk.dev/claim')}`);
133
+ console.log();
139
134
  } catch (err) {
140
- claimSpinner.fail('Could not create profile — try again later');
141
- console.log(chalk.dim(` ${err.message}`));
135
+ console.log();
136
+ console.log(chalk.dim(` Could not create profile \u2014 try again later`));
142
137
  console.log();
143
138
  }
144
139
  } else {
145
140
  console.log();
146
- console.log(chalk.dim(' No worries. Run `npx chekk` again anytime to claim your score.'));
147
- console.log();
141
+ console.log(chalk.dim(' Run `npx chekk` again anytime.\n'));
148
142
  }
149
143
  } catch {
150
- // stdin not available (piped), skip claim
151
144
  console.log();
152
145
  }
153
146
  }
154
147
  }
148
+
149
+ function formatDateRange(start, end) {
150
+ const months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];
151
+ const s = `${months[start.getMonth()]} ${start.getDate()}`;
152
+ const e = `${months[end.getMonth()]} ${end.getDate()}, ${end.getFullYear()}`;
153
+ if (start.getFullYear() === end.getFullYear()) {
154
+ return `${s} \u2013 ${e}`;
155
+ }
156
+ return `${s}, ${start.getFullYear()} \u2013 ${e}`;
157
+ }