cc-personality 1.0.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/LICENSE +21 -0
- package/README.md +78 -0
- package/cli.mjs +405 -0
- package/package.json +34 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 yurukusa
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
# cc-personality
|
|
2
|
+
|
|
3
|
+
**What kind of Claude Code developer are you?**
|
|
4
|
+
|
|
5
|
+
Diagnoses your coding archetype from real usage patterns in your session logs.
|
|
6
|
+
|
|
7
|
+
```
|
|
8
|
+
npx cc-personality
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
Zero dependencies. Reads `~/.claude/projects/` locally. Nothing sent anywhere.
|
|
12
|
+
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
## Your archetype could be...
|
|
16
|
+
|
|
17
|
+
| Archetype | Trigger |
|
|
18
|
+
|-----------|---------|
|
|
19
|
+
| ð The Midnight Beast | 30%+ of sessions between midnight and 5 AM |
|
|
20
|
+
| ð
The Dawn Coder | 40%+ sessions in early morning (5-9 AM) |
|
|
21
|
+
| ðĪ The Unstoppable Machine | 30+ consecutive active days |
|
|
22
|
+
| âïļ The Weekend Warrior | Weekend sessions 1.8x more than weekdays |
|
|
23
|
+
| ⥠The Burst Genius | Lots of inactive days + intense bursts |
|
|
24
|
+
| ð The Disciplined Architect | Consistent daily usage without burnout |
|
|
25
|
+
| ðĨ The Session Monster | Average session over 2.5 hours |
|
|
26
|
+
| ðĶ The Micro-Shipper | 15+ sessions per active day |
|
|
27
|
+
| ðšïļ The Code Explorer | Active across 20+ different projects |
|
|
28
|
+
| ðŊ The Mono-Focused | 100+ sessions, 3 or fewer projects |
|
|
29
|
+
|
|
30
|
+
---
|
|
31
|
+
|
|
32
|
+
## Sample output
|
|
33
|
+
|
|
34
|
+
```
|
|
35
|
+
ââââââââââââââââââââââââââââââââââââââââââââââââââââââ
|
|
36
|
+
â YOUR CLAUDE CODE DEVELOPER ARCHETYPE â
|
|
37
|
+
ââââââââââââââââââââââââââââââââââââââââââââââââââââââ
|
|
38
|
+
|
|
39
|
+
ð The Midnight Beast
|
|
40
|
+
ãæ·ąåĪãŪæŠįĐã
|
|
41
|
+
|
|
42
|
+
"The compiler doesn't sleep, and neither do I."
|
|
43
|
+
|
|
44
|
+
Your code runs on moonlight and caffeine. Peak hours: 0-5 AM.
|
|
45
|
+
|
|
46
|
+
ââââââââââââââââââââââââââââââââââââââââââââââââââââââ
|
|
47
|
+
Your data:
|
|
48
|
+
âą 116h total âĒ 3,485 sessions âĒ 47 active days
|
|
49
|
+
ðĨ Longest streak: 35 days
|
|
50
|
+
ð Avg session: 120 min
|
|
51
|
+
|
|
52
|
+
Activity by hour (0h â 23h):
|
|
53
|
+
âââââââââââââââââââââ
âââ
|
|
54
|
+
0 6 12 18 23
|
|
55
|
+
|
|
56
|
+
ð Night owl (38% night sessions)
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
---
|
|
60
|
+
|
|
61
|
+
## How it works
|
|
62
|
+
|
|
63
|
+
Reads timestamps from JSONL session files in `~/.claude/projects/`.
|
|
64
|
+
Computes 10 behavioral signals and matches your dominant pattern.
|
|
65
|
+
The archetype tweet link is pre-formatted for easy sharing.
|
|
66
|
+
|
|
67
|
+
For full usage statistics: [`cc-session-stats`](https://github.com/yurukusa/cc-session-stats)
|
|
68
|
+
|
|
69
|
+
---
|
|
70
|
+
|
|
71
|
+
## Requirements
|
|
72
|
+
|
|
73
|
+
- Node.js 18+
|
|
74
|
+
- Claude Code with some session history
|
|
75
|
+
|
|
76
|
+
## License
|
|
77
|
+
|
|
78
|
+
MIT
|
package/cli.mjs
ADDED
|
@@ -0,0 +1,405 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// cc-personality â What kind of Claude Code developer are you?
|
|
4
|
+
// Zero dependencies. Reads ~/.claude/projects/ session transcripts.
|
|
5
|
+
// Diagnoses your coding archetype from real usage patterns.
|
|
6
|
+
|
|
7
|
+
import { readdir, stat, open } from 'node:fs/promises';
|
|
8
|
+
import { join } from 'node:path';
|
|
9
|
+
import { homedir } from 'node:os';
|
|
10
|
+
|
|
11
|
+
// ââ Color helpers ââââââââââââââââââââââââââââââââââââââââââââââ
|
|
12
|
+
|
|
13
|
+
const C = {
|
|
14
|
+
reset: '\x1b[0m',
|
|
15
|
+
bold: '\x1b[1m',
|
|
16
|
+
dim: '\x1b[2m',
|
|
17
|
+
red: '\x1b[31m',
|
|
18
|
+
green: '\x1b[32m',
|
|
19
|
+
yellow: '\x1b[33m',
|
|
20
|
+
blue: '\x1b[34m',
|
|
21
|
+
magenta: '\x1b[35m',
|
|
22
|
+
cyan: '\x1b[36m',
|
|
23
|
+
white: '\x1b[37m',
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
// ââ File reading (reused from cc-session-stats) ââââââââââââââââ
|
|
27
|
+
|
|
28
|
+
async function readFirstLastLine(filePath) {
|
|
29
|
+
const fh = await open(filePath, 'r');
|
|
30
|
+
try {
|
|
31
|
+
const buf = Buffer.alloc(8192);
|
|
32
|
+
const { bytesRead: firstBytes } = await fh.read(buf, 0, 8192, 0);
|
|
33
|
+
if (firstBytes === 0) return null;
|
|
34
|
+
const firstChunk = buf.toString('utf8', 0, firstBytes);
|
|
35
|
+
const firstNewline = firstChunk.indexOf('\n');
|
|
36
|
+
const firstLine = firstNewline >= 0 ? firstChunk.substring(0, firstNewline) : firstChunk;
|
|
37
|
+
|
|
38
|
+
const fileStat = await fh.stat();
|
|
39
|
+
const fileSize = fileStat.size;
|
|
40
|
+
if (fileSize < 2) return { firstLine, lastLine: firstLine };
|
|
41
|
+
|
|
42
|
+
const readSize = Math.min(65536, fileSize);
|
|
43
|
+
const tailBuf = Buffer.alloc(readSize);
|
|
44
|
+
const { bytesRead: tailBytes } = await fh.read(tailBuf, 0, readSize, fileSize - readSize);
|
|
45
|
+
const tailChunk = tailBuf.toString('utf8', 0, tailBytes);
|
|
46
|
+
const lines = tailChunk.split('\n').filter(l => l.trim());
|
|
47
|
+
const lastLine = lines[lines.length - 1] || firstLine;
|
|
48
|
+
|
|
49
|
+
return { firstLine, lastLine };
|
|
50
|
+
} finally {
|
|
51
|
+
await fh.close();
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function parseTimestamp(jsonLine) {
|
|
56
|
+
try {
|
|
57
|
+
const data = JSON.parse(jsonLine);
|
|
58
|
+
const ts = data.timestamp || data.ts;
|
|
59
|
+
if (ts) return new Date(ts);
|
|
60
|
+
} catch {}
|
|
61
|
+
return null;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// ââ Scan sessions ââââââââââââââââââââââââââââââââââââââââââââââ
|
|
65
|
+
|
|
66
|
+
const SESSION_GAP_HOURS = 0.5;
|
|
67
|
+
|
|
68
|
+
async function scanSessions(claudeDir) {
|
|
69
|
+
const projectsDir = join(claudeDir, 'projects');
|
|
70
|
+
const sessions = [];
|
|
71
|
+
|
|
72
|
+
let projectDirs;
|
|
73
|
+
try {
|
|
74
|
+
projectDirs = await readdir(projectsDir);
|
|
75
|
+
} catch {
|
|
76
|
+
return sessions;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
for (const proj of projectDirs) {
|
|
80
|
+
const projPath = join(projectsDir, proj);
|
|
81
|
+
let files;
|
|
82
|
+
try {
|
|
83
|
+
files = await readdir(projPath);
|
|
84
|
+
} catch { continue; }
|
|
85
|
+
|
|
86
|
+
const jsonlFiles = files.filter(f => f.endsWith('.jsonl'));
|
|
87
|
+
for (const file of jsonlFiles) {
|
|
88
|
+
const filePath = join(projPath, file);
|
|
89
|
+
try {
|
|
90
|
+
const lines = await readFirstLastLine(filePath);
|
|
91
|
+
if (!lines) continue;
|
|
92
|
+
const start = parseTimestamp(lines.firstLine);
|
|
93
|
+
const end = parseTimestamp(lines.lastLine);
|
|
94
|
+
if (!start) continue;
|
|
95
|
+
const endTime = end || start;
|
|
96
|
+
|
|
97
|
+
const fileStat = await stat(filePath);
|
|
98
|
+
// Split into sub-sessions if gap > SESSION_GAP_HOURS
|
|
99
|
+
const durationHours = (endTime - start) / 3600000;
|
|
100
|
+
if (durationHours < SESSION_GAP_HOURS * 2) {
|
|
101
|
+
sessions.push({ start, end: endTime, hours: Math.max(durationHours, 0.01) });
|
|
102
|
+
} else {
|
|
103
|
+
// Estimate sub-sessions
|
|
104
|
+
const numSubs = Math.max(1, Math.ceil(durationHours / 2));
|
|
105
|
+
for (let i = 0; i < numSubs; i++) {
|
|
106
|
+
const subStart = new Date(start.getTime() + (i / numSubs) * (endTime - start));
|
|
107
|
+
sessions.push({ start: subStart, end: subStart, hours: durationHours / numSubs });
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
} catch { continue; }
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
return sessions;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// ââ Personality archetypes âââââââââââââââââââââââââââââââââââââ
|
|
118
|
+
|
|
119
|
+
const ARCHETYPES = [
|
|
120
|
+
{
|
|
121
|
+
id: 'night_beast',
|
|
122
|
+
name: 'ð The Midnight Beast',
|
|
123
|
+
jp: 'æ·ąåĪãŪæŠįĐ',
|
|
124
|
+
tagline: '"The compiler doesn\'t sleep, and neither do I."',
|
|
125
|
+
condition: (s) => s.nightPct >= 0.30,
|
|
126
|
+
description: 'Your code runs on moonlight and caffeine. Peak hours: 0-5 AM.',
|
|
127
|
+
},
|
|
128
|
+
{
|
|
129
|
+
id: 'dawn_coder',
|
|
130
|
+
name: 'ð
The Dawn Coder',
|
|
131
|
+
jp: 'åĪæããŪãģãžããž',
|
|
132
|
+
tagline: '"Fresh mind, fresh commits."',
|
|
133
|
+
condition: (s) => s.morningPct >= 0.40,
|
|
134
|
+
description: 'You code when the world sleeps. Clear mind, elegant solutions.',
|
|
135
|
+
},
|
|
136
|
+
{
|
|
137
|
+
id: 'machine',
|
|
138
|
+
name: 'ðĪ The Unstoppable Machine',
|
|
139
|
+
jp: 'äļæŧ
ãŪæĐæĒ°',
|
|
140
|
+
tagline: '"Rest days are a human concept."',
|
|
141
|
+
condition: (s) => s.maxStreak >= 30,
|
|
142
|
+
description: 'Consecutive days without a break. You simply don\'t stop.',
|
|
143
|
+
},
|
|
144
|
+
{
|
|
145
|
+
id: 'weekend_warrior',
|
|
146
|
+
name: 'âïļ The Weekend Warrior',
|
|
147
|
+
jp: 'éąæŦãŪæĶåĢŦ',
|
|
148
|
+
tagline: '"Monday to Friday is warmup."',
|
|
149
|
+
condition: (s) => s.weekendRatio >= 1.8,
|
|
150
|
+
description: 'Saturday and Sunday are your real working days.',
|
|
151
|
+
},
|
|
152
|
+
{
|
|
153
|
+
id: 'burst_genius',
|
|
154
|
+
name: '⥠The Burst Genius',
|
|
155
|
+
jp: 'ããžãđãååĪĐæ',
|
|
156
|
+
tagline: '"I do nothing for days, then ship everything at once."',
|
|
157
|
+
condition: (s) => s.activeDays > 0 && (s.totalDays / s.activeDays) >= 2.5,
|
|
158
|
+
description: 'Irregular patterns. Long silences. Then explosive productivity.',
|
|
159
|
+
},
|
|
160
|
+
{
|
|
161
|
+
id: 'disciplined',
|
|
162
|
+
name: 'ð The Disciplined Architect',
|
|
163
|
+
jp: 'čĶåūãŪįģãå',
|
|
164
|
+
tagline: '"Consistency beats intensity."',
|
|
165
|
+
condition: (s) => s.activeDays > 0 && (s.totalDays / s.activeDays) < 1.3 && s.totalHours / s.activeDays < 6,
|
|
166
|
+
description: 'Steady pace every day. You finish what you start.',
|
|
167
|
+
},
|
|
168
|
+
{
|
|
169
|
+
id: 'session_monster',
|
|
170
|
+
name: 'ðĨ The Session Monster',
|
|
171
|
+
jp: 'ãŧãã·ã§ãģæŠįĢ',
|
|
172
|
+
tagline: '"One more context window..."',
|
|
173
|
+
condition: (s) => s.avgSessionHours >= 2.5,
|
|
174
|
+
description: 'Long, deep sessions. You go down the rabbit hole and don\'t come back.',
|
|
175
|
+
},
|
|
176
|
+
{
|
|
177
|
+
id: 'micro_shipper',
|
|
178
|
+
name: 'ðĶ The Micro-Shipper',
|
|
179
|
+
jp: 'čķ
éŦéåšč·č
',
|
|
180
|
+
tagline: '"Ship small, ship often."',
|
|
181
|
+
condition: (s) => s.totalSessions / s.activeDays >= 15,
|
|
182
|
+
description: 'Dozens of sessions per day. Fast, iterative, relentless.',
|
|
183
|
+
},
|
|
184
|
+
{
|
|
185
|
+
id: 'explorer',
|
|
186
|
+
name: 'ðšïļ The Code Explorer',
|
|
187
|
+
jp: 'ãģãžãæĒæĪåŪķ',
|
|
188
|
+
tagline: '"Every project is a new world."',
|
|
189
|
+
condition: (s) => s.projectCount >= 20,
|
|
190
|
+
description: 'Many projects, curious mind. You explore, you don\'t settle.',
|
|
191
|
+
},
|
|
192
|
+
{
|
|
193
|
+
id: 'mono_focused',
|
|
194
|
+
name: 'ðŊ The Mono-Focused',
|
|
195
|
+
jp: 'ãĒãããĐãžãŦãđãŪå ',
|
|
196
|
+
tagline: '"One project. Total mastery."',
|
|
197
|
+
condition: (s) => s.projectCount <= 3 && s.totalSessions >= 100,
|
|
198
|
+
description: 'Deep obsession with one thing. Mastery over breadth.',
|
|
199
|
+
},
|
|
200
|
+
];
|
|
201
|
+
|
|
202
|
+
// ââ Compute stats âââââââââââââââââââââââââââââââââââââââââââââ
|
|
203
|
+
|
|
204
|
+
function computeStats(sessions) {
|
|
205
|
+
if (sessions.length === 0) return null;
|
|
206
|
+
|
|
207
|
+
const totalSessions = sessions.length;
|
|
208
|
+
const totalHours = sessions.reduce((s, x) => s + x.hours, 0);
|
|
209
|
+
|
|
210
|
+
// Hour distribution
|
|
211
|
+
const hourBuckets = new Array(24).fill(0);
|
|
212
|
+
for (const s of sessions) {
|
|
213
|
+
hourBuckets[s.start.getHours()]++;
|
|
214
|
+
}
|
|
215
|
+
const nightPct = (hourBuckets.slice(0, 5).reduce((a, b) => a + b, 0) +
|
|
216
|
+
hourBuckets.slice(22).reduce((a, b) => a + b, 0)) / totalSessions;
|
|
217
|
+
const morningPct = hourBuckets.slice(5, 9).reduce((a, b) => a + b, 0) / totalSessions;
|
|
218
|
+
const afternoonPct = hourBuckets.slice(9, 17).reduce((a, b) => a + b, 0) / totalSessions;
|
|
219
|
+
const eveningPct = hourBuckets.slice(17, 22).reduce((a, b) => a + b, 0) / totalSessions;
|
|
220
|
+
|
|
221
|
+
// Day distribution
|
|
222
|
+
const dayBuckets = new Array(7).fill(0); // 0=Sun
|
|
223
|
+
for (const s of sessions) {
|
|
224
|
+
dayBuckets[s.start.getDay()]++;
|
|
225
|
+
}
|
|
226
|
+
const weekdayTotal = dayBuckets.slice(1, 6).reduce((a, b) => a + b, 0) / 5;
|
|
227
|
+
const weekendTotal = (dayBuckets[0] + dayBuckets[6]) / 2;
|
|
228
|
+
const weekendRatio = weekdayTotal > 0 ? weekendTotal / weekdayTotal : 0;
|
|
229
|
+
|
|
230
|
+
// Active days & streak
|
|
231
|
+
const daySet = new Set(sessions.map(s => s.start.toDateString()));
|
|
232
|
+
const activeDays = daySet.size;
|
|
233
|
+
const allDates = [...daySet].map(d => new Date(d)).sort((a, b) => a - b);
|
|
234
|
+
const firstDate = allDates[0];
|
|
235
|
+
const lastDate = allDates[allDates.length - 1];
|
|
236
|
+
const totalDays = Math.ceil((lastDate - firstDate) / 86400000) + 1;
|
|
237
|
+
|
|
238
|
+
let maxStreak = 1, curStreak = 1;
|
|
239
|
+
for (let i = 1; i < allDates.length; i++) {
|
|
240
|
+
const diff = (allDates[i] - allDates[i - 1]) / 86400000;
|
|
241
|
+
if (diff === 1) {
|
|
242
|
+
curStreak++;
|
|
243
|
+
if (curStreak > maxStreak) maxStreak = curStreak;
|
|
244
|
+
} else {
|
|
245
|
+
curStreak = 1;
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
const avgSessionHours = totalHours / totalSessions;
|
|
250
|
+
|
|
251
|
+
return {
|
|
252
|
+
totalSessions,
|
|
253
|
+
totalHours,
|
|
254
|
+
activeDays,
|
|
255
|
+
totalDays,
|
|
256
|
+
maxStreak,
|
|
257
|
+
nightPct,
|
|
258
|
+
morningPct,
|
|
259
|
+
afternoonPct,
|
|
260
|
+
eveningPct,
|
|
261
|
+
weekendRatio,
|
|
262
|
+
avgSessionHours,
|
|
263
|
+
hourBuckets,
|
|
264
|
+
dayBuckets,
|
|
265
|
+
projectCount: 0, // filled later
|
|
266
|
+
peakHour: hourBuckets.indexOf(Math.max(...hourBuckets)),
|
|
267
|
+
};
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
// ââ Determine archetype âââââââââââââââââââââââââââââââââââââââ
|
|
271
|
+
|
|
272
|
+
function getArchetype(stats) {
|
|
273
|
+
// Machine check needs maxStreak
|
|
274
|
+
const machineArchetype = {
|
|
275
|
+
id: 'machine',
|
|
276
|
+
name: 'ðĪ The Unstoppable Machine',
|
|
277
|
+
jp: 'äļæŧ
ãŪæĐæĒ°',
|
|
278
|
+
tagline: '"Rest days are a human concept."',
|
|
279
|
+
condition: (s) => s.maxStreak >= 30,
|
|
280
|
+
description: `${stats.maxStreak} consecutive days of coding. You simply don't stop.`,
|
|
281
|
+
};
|
|
282
|
+
|
|
283
|
+
const allArchetypes = ARCHETYPES.map(a =>
|
|
284
|
+
a.id === 'machine' ? machineArchetype : a
|
|
285
|
+
);
|
|
286
|
+
|
|
287
|
+
for (const archetype of allArchetypes) {
|
|
288
|
+
if (archetype.condition(stats)) {
|
|
289
|
+
return archetype;
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
// Default
|
|
294
|
+
return {
|
|
295
|
+
id: 'balanced',
|
|
296
|
+
name: 'âïļ The Balanced Developer',
|
|
297
|
+
jp: 'ããĐãģãđåéįšč
',
|
|
298
|
+
tagline: '"Sustainable velocity wins the race."',
|
|
299
|
+
description: 'You code consistently without burning out. Rare and admirable.',
|
|
300
|
+
};
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
// ââ Render ââââââââââââââââââââââââââââââââââââââââââââââââââââ
|
|
304
|
+
|
|
305
|
+
function renderHourBar(buckets, peakHour) {
|
|
306
|
+
const max = Math.max(...buckets);
|
|
307
|
+
if (max === 0) return '';
|
|
308
|
+
const blocks = ['â', 'â', 'â', 'â', 'â
', 'â', 'â', 'â'];
|
|
309
|
+
return buckets.map((v, h) => {
|
|
310
|
+
const level = Math.round((v / max) * 7);
|
|
311
|
+
const block = blocks[level];
|
|
312
|
+
if (h === peakHour) return `${C.yellow}${block}${C.reset}`;
|
|
313
|
+
if (h >= 0 && h <= 4) return `${C.dim}${block}${C.reset}`;
|
|
314
|
+
return block;
|
|
315
|
+
}).join('');
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
function pct(n) { return `${Math.round(n * 100)}%`; }
|
|
319
|
+
|
|
320
|
+
// ââ Main ââââââââââââââââââââââââââââââââââââââââââââââââââââââ
|
|
321
|
+
|
|
322
|
+
async function main() {
|
|
323
|
+
const claudeDir = join(homedir(), '.claude');
|
|
324
|
+
|
|
325
|
+
process.stdout.write(`${C.dim}Scanning your Claude Code sessions...${C.reset}\n`);
|
|
326
|
+
|
|
327
|
+
const sessions = await scanSessions(claudeDir);
|
|
328
|
+
|
|
329
|
+
if (sessions.length === 0) {
|
|
330
|
+
console.log(`\n${C.red}No Claude Code sessions found.${C.reset}`);
|
|
331
|
+
console.log('Make sure you have sessions in ~/.claude/projects/');
|
|
332
|
+
process.exit(1);
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
// Count projects
|
|
336
|
+
let projectCount = 0;
|
|
337
|
+
try {
|
|
338
|
+
const projectsDir = join(claudeDir, 'projects');
|
|
339
|
+
const dirs = await readdir(projectsDir);
|
|
340
|
+
projectCount = dirs.length;
|
|
341
|
+
} catch {}
|
|
342
|
+
|
|
343
|
+
const stats = computeStats(sessions);
|
|
344
|
+
stats.projectCount = projectCount;
|
|
345
|
+
|
|
346
|
+
const archetype = getArchetype(stats);
|
|
347
|
+
|
|
348
|
+
// Render
|
|
349
|
+
const width = 52;
|
|
350
|
+
const border = 'â'.repeat(width);
|
|
351
|
+
|
|
352
|
+
console.log(`\n${C.bold}${C.cyan}â${border}â${C.reset}`);
|
|
353
|
+
console.log(`${C.bold}${C.cyan}â${C.reset}${C.bold} YOUR CLAUDE CODE DEVELOPER ARCHETYPE ${C.cyan}â${C.reset}`);
|
|
354
|
+
console.log(`${C.bold}${C.cyan}â${border}â${C.reset}`);
|
|
355
|
+
|
|
356
|
+
console.log(`\n ${C.bold}${C.yellow}${archetype.name}${C.reset}`);
|
|
357
|
+
if (archetype.jp) {
|
|
358
|
+
console.log(` ${C.dim}ã${archetype.jp}ã${C.reset}`);
|
|
359
|
+
}
|
|
360
|
+
console.log(`\n ${C.cyan}${archetype.tagline || ''}${C.reset}`);
|
|
361
|
+
console.log(`\n ${archetype.description || ''}`);
|
|
362
|
+
|
|
363
|
+
// Stats summary
|
|
364
|
+
console.log(`\n${C.dim}${'â'.repeat(width + 2)}${C.reset}`);
|
|
365
|
+
console.log(` ${C.bold}Your data:${C.reset}`);
|
|
366
|
+
console.log(` âą ${stats.totalHours.toFixed(0)}h total âĒ ${stats.totalSessions} sessions âĒ ${stats.activeDays} active days`);
|
|
367
|
+
console.log(` ðĨ Longest streak: ${stats.maxStreak} days`);
|
|
368
|
+
console.log(` ð Avg session: ${(stats.avgSessionHours * 60).toFixed(0)} min`);
|
|
369
|
+
|
|
370
|
+
// Hour heatmap
|
|
371
|
+
console.log(`\n Activity by hour (0h â 23h):`);
|
|
372
|
+
console.log(` ${renderHourBar(stats.hourBuckets, stats.peakHour)}`);
|
|
373
|
+
console.log(` ${C.dim}0 6 12 18 23${C.reset}`);
|
|
374
|
+
|
|
375
|
+
// Time-of-day breakdown
|
|
376
|
+
const timeLabel =
|
|
377
|
+
stats.nightPct >= 0.25 ? `${C.blue}ð Night owl (${pct(stats.nightPct)} night sessions)${C.reset}` :
|
|
378
|
+
stats.morningPct >= 0.30 ? `${C.yellow}ð
Early bird (${pct(stats.morningPct)} morning sessions)${C.reset}` :
|
|
379
|
+
stats.eveningPct >= 0.40 ? `${C.magenta}ð Evening coder (${pct(stats.eveningPct)} evening)${C.reset}` :
|
|
380
|
+
`${C.green}âïļ Day coder (${pct(stats.afternoonPct)} afternoon)${C.reset}`;
|
|
381
|
+
console.log(`\n ${timeLabel}`);
|
|
382
|
+
|
|
383
|
+
// Weekend info
|
|
384
|
+
if (stats.weekendRatio >= 1.5) {
|
|
385
|
+
console.log(` âïļ Weekend warrior (${stats.weekendRatio.toFixed(1)}x weekend activity)`);
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
// Share prompt
|
|
389
|
+
const shareText = encodeURIComponent(
|
|
390
|
+
`My Claude Code archetype: ${archetype.name} (${archetype.jp})\n` +
|
|
391
|
+
`${stats.totalHours.toFixed(0)}h âĒ ${stats.totalSessions} sessions âĒ ${stats.maxStreak}-day streak\n` +
|
|
392
|
+
`What's yours? npx cc-personality\n#claudecode`
|
|
393
|
+
);
|
|
394
|
+
|
|
395
|
+
console.log(`\n${C.dim}${'â'.repeat(width + 2)}${C.reset}`);
|
|
396
|
+
console.log(` ${C.bold}Share your archetype:${C.reset}`);
|
|
397
|
+
console.log(` ${C.dim}https://x.com/intent/tweet?text=${shareText.substring(0, 80)}...${C.reset}`);
|
|
398
|
+
console.log(`\n Full stats: ${C.cyan}npx cc-session-stats${C.reset}`);
|
|
399
|
+
console.log(` GitHub: ${C.cyan}https://github.com/yurukusa/cc-personality${C.reset}\n`);
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
main().catch(err => {
|
|
403
|
+
console.error(`Error: ${err.message}`);
|
|
404
|
+
process.exit(1);
|
|
405
|
+
});
|
package/package.json
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "cc-personality",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Discover your Claude Code developer archetype. Diagnoses your coding style from real usage patterns.",
|
|
5
|
+
"bin": {
|
|
6
|
+
"cc-personality": "cli.mjs"
|
|
7
|
+
},
|
|
8
|
+
"type": "module",
|
|
9
|
+
"keywords": [
|
|
10
|
+
"claude-code",
|
|
11
|
+
"claude",
|
|
12
|
+
"ai",
|
|
13
|
+
"developer",
|
|
14
|
+
"personality",
|
|
15
|
+
"archetype",
|
|
16
|
+
"coding-style",
|
|
17
|
+
"productivity",
|
|
18
|
+
"time-tracking"
|
|
19
|
+
],
|
|
20
|
+
"author": "yurukusa",
|
|
21
|
+
"license": "MIT",
|
|
22
|
+
"repository": {
|
|
23
|
+
"type": "git",
|
|
24
|
+
"url": "git+https://github.com/yurukusa/cc-personality.git"
|
|
25
|
+
},
|
|
26
|
+
"engines": {
|
|
27
|
+
"node": ">=18"
|
|
28
|
+
},
|
|
29
|
+
"files": [
|
|
30
|
+
"cli.mjs",
|
|
31
|
+
"README.md",
|
|
32
|
+
"LICENSE"
|
|
33
|
+
]
|
|
34
|
+
}
|