Skip to content

Commit 9e34b46

Browse files
fix: credibility + UX pass (iteration 1)
- mobile share card: stat bars no longer collapse to 0 width (own row) - vendor chart.js + html-to-image locally (no third-party JS on the parsing page) - privacy copy now literally true (share URL = 6 scores + 3 totals, never text) - lead with the RPG character-sheet hook; demote "status check" - web card honestly labeled as a rough estimate, not comparable to CLI - tighten web heuristic regexes (word boundaries) - CLI not-found error prints checked paths + how to find transcripts - favicon, full npx command visible, canvas aria-label, watermark contrast Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
1 parent 20936ae commit 9e34b46

5 files changed

Lines changed: 85 additions & 28 deletions

File tree

README.md

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,8 @@ How to get the file: claude.ai → Settings → Privacy → Export data → the
4646

4747
- **CLI:** transcripts are read locally and never leave your machine. Only the 6 aggregate scores plus three totals (sessions, tool calls, message count) are encoded into the share URL hash — the level shown on the card is derived from those totals. Nothing else travels.
4848
- **Web upload:** `conversations.json` is parsed entirely in your browser (client-side JavaScript). There is no backend and no upload endpoint — there is nowhere for the file to go.
49-
- **Share URL:** contains only the aggregated numbers, base64url-encoded in the `#s=` hash. Anyone with the link sees your stat card, never your conversations.
49+
- **Share URL:** contains only the aggregated numbers (6 scores + 3 totals), base64url-encoded in the `#s=` hash. Anyone with the link sees your stat card, never your conversations.
50+
- The web app vendors its JS libraries locally — opening the page makes no third-party API calls except Google Fonts (CSS only).
5051

5152
## How scoring works
5253

@@ -97,13 +98,13 @@ Your top axis decides your class (ties break by axis order):
9798
Levels follow a square-root curve against total volume — they're meant to climb slowly. The anchors are calibrated against a very heavy user, so a "normal" usage pattern landing at level 5–15 is expected, not a bug.
9899

99100
**Does this upload my chats anywhere?**
100-
No. There is no server. CLI parsing happens on your machine; web parsing happens in your browser. The only thing that ever travels is the share URL you choose to copy, which contains 6 aggregate scores and totals.
101+
No. There is no server. CLI parsing happens on your machine; web parsing happens in your browser. The only thing that ever travels is the share URL you choose to copy, which contains 6 scores + 3 totals (sessions, tool calls, message count) — never message text.
101102

102103
**The web upload says my file format isn't recognized.**
103104
The claude.ai parser is beta. Export formats can change. Please open an issue with a description of the file's top-level structure.
104105

105106
**Can I compare with friends?**
106-
Yes — scores use fixed anchors (not self-relative), so two people's cards are directly comparable. Swap share URLs or PNG cards.
107+
CLI cards compare to CLI cards — scores use fixed anchors (not self-relative), so two Claude Code users' cards are directly comparable. The web (claude.ai) card is a **rough text-inferred estimate** and is **not** comparable to a CLI card; the `CLI` / `WEB · BETA` badge next to the level tells you which kind you're looking at.
107108

108109
---
109110

bin/cli.js

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -152,9 +152,19 @@ async function main() {
152152

153153
const dataDir = resolveDataDir(args);
154154
if (!dataDir) {
155+
const checked = [];
156+
if (args.dir) checked.push(args.dir);
157+
else {
158+
if (process.env.CLAUDE_CONFIG_DIR) checked.push(path.join(process.env.CLAUDE_CONFIG_DIR, 'projects'));
159+
checked.push(path.join(os.homedir(), '.claude', 'projects'));
160+
}
155161
console.error('Could not find a Claude Code projects directory.');
156-
console.error('Looked for: ' + (args.dir ? args.dir : '$CLAUDE_CONFIG_DIR/projects and ~/.claude/projects'));
157-
console.error('If your data lives elsewhere, point at it with: --dir <path>');
162+
console.error('Checked these path(s):');
163+
for (const c of checked) console.error(' - ' + c);
164+
console.error('');
165+
console.error('If you use a custom config dir, run with --dir <path>. Your transcripts live in the');
166+
console.error("'projects' folder next to Claude Code's settings.json (commonly ~/.claude/projects,");
167+
console.error('or $CLAUDE_CONFIG_DIR/projects).');
158168
process.exit(1);
159169
}
160170

@@ -210,7 +220,7 @@ async function main() {
210220

211221
const lines = [];
212222
lines.push('');
213-
lines.push(' CLAUDE STATUS CHECK');
223+
lines.push(' CLAUDE CHARACTER SHEET');
214224
lines.push(' ' + '-'.repeat(46));
215225
lines.push(' ' + cls[0] + ' LV ' + lv);
216226
lines.push(' ' + (cls[2] || cls[1]));
@@ -229,7 +239,7 @@ async function main() {
229239

230240
if (args.share) {
231241
console.log('');
232-
console.log(' Share your card (only the 6 aggregate scores are in this URL):');
242+
console.log(' Share your card (URL carries only 6 scores + 3 totals, never message text):');
233243
console.log(' ' + shareUrl);
234244
}
235245
console.log('');

index.html

Lines changed: 39 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,10 @@
55
<meta name="viewport" content="width=device-width, initial-scale=1">
66
<title>Claude Status Check</title>
77
<meta name="description" content="เช็คสเตตัสการใช้ Claude ของคุณ — radar 6 สาย + class + level สไตล์ MMORPG · ข้อมูลไม่ออกจากเครื่อง">
8+
<link rel="icon" href="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 100'%3E%3Crect width='100' height='100' rx='20' fill='%230a120e'/%3E%3Cpolygon points='50,15 80,35 72,75 28,75 20,35' fill='none' stroke='%2334d399' stroke-width='6'/%3E%3C/svg%3E">
89
<link href="https://fonts.googleapis.com/css2?family=Kanit:wght@300;400;600;800&family=JetBrains+Mono:wght@400;700&display=swap" rel="stylesheet">
9-
<script src="https://cdn.jsdelivr.net/npm/chart.js@4.4.1/dist/chart.umd.min.js"></script>
10-
<script src="https://cdn.jsdelivr.net/npm/html-to-image@1.11.11/dist/html-to-image.min.js"></script>
10+
<script src="lib/chart.umd.min.js"></script>
11+
<script src="lib/html-to-image.min.js"></script>
1112
<script src="lib/scoring.js"></script>
1213
<style>
1314
* { margin: 0; padding: 0; box-sizing: border-box; }
@@ -45,7 +46,7 @@
4546
.cmd {
4647
flex: 1; font-family: 'JetBrains Mono', monospace; font-size: 12.5px; color: #6ee7b7;
4748
background: #122019; border: 1px solid #1f4435; border-radius: 8px; padding: 11px 12px;
48-
white-space: nowrap; overflow-x: auto;
49+
white-space: normal; word-break: break-all; line-height: 1.4;
4950
}
5051
.btn {
5152
font-family: 'Kanit', sans-serif; font-size: 13px; font-weight: 600; cursor: pointer;
@@ -100,7 +101,7 @@
100101
.stats-list { list-style: none; display: flex; flex-direction: column; gap: 10px; }
101102
.stats-list li { display: grid; grid-template-columns: 110px 1fr 44px; align-items: center; gap: 12px; }
102103
.stat-label { font-size: 14px; font-weight: 600; color: #c9e8d8; }
103-
.stat-label small { display: block; font-size: 10.5px; font-weight: 300; color: #5d8371; }
104+
.stat-label small { display: block; font-size: 10.5px; font-weight: 300; color: #7ba894; }
104105
.bar { height: 7px; background: #122019; border-radius: 4px; overflow: hidden; }
105106
.bar i { display: block; height: 100%; border-radius: 4px;
106107
background: linear-gradient(90deg, #10b981, #6ee7b7); }
@@ -110,7 +111,7 @@
110111
.totals { display: flex; gap: 26px; }
111112
.totals div { font-family: 'JetBrains Mono', monospace; font-size: 12px; color: #5d8371; }
112113
.totals b { display: block; font-size: 17px; color: #e8f5ee; font-weight: 700; }
113-
.brand { font-size: 11px; letter-spacing: 2px; color: #3d5c4c; }
114+
.brand { font-size: 11px; letter-spacing: 2px; color: #6f9886; font-weight: 600; }
114115

115116
.card-actions { display: flex; gap: 10px; justify-content: center; margin-top: 18px; flex-wrap: wrap; }
116117

@@ -143,6 +144,10 @@
143144
.totals { flex-wrap: wrap; gap: 16px; }
144145
.brand { word-break: break-all; }
145146
.class-name { font-size: 27px; }
147+
.stats-list li { grid-template-columns: 1fr auto; grid-template-areas: "label val" "bar bar"; row-gap: 7px; column-gap: 12px; align-items: center; }
148+
.stats-list .stat-label { grid-area: label; }
149+
.stats-list .stat-val { grid-area: val; }
150+
.stats-list .bar { grid-area: bar; width: 100%; }
146151
}
147152
</style>
148153
</head>
@@ -152,8 +157,8 @@
152157

153158
<div id="landing" class="landing hidden">
154159
<div class="hero">
155-
<div class="kicker">MMORPG Character Sheet · For Claude Users</div>
156-
<h1>Claude Status Check</h1>
160+
<div class="kicker" id="kicker">MMORPG-style stats · earned from your real usage, not a quiz</div>
161+
<h1 id="h1">Your Claude Character Sheet</h1>
157162
<p id="heroDesc">คุณใช้ Claude สายไหน? เช็คสเตตัส 6 ด้าน รับ class + level ของตัวเอง — ข้อมูลดิบไม่ออกจากเครื่องคุณ</p>
158163
</div>
159164
<div class="entry-grid">
@@ -164,7 +169,8 @@ <h2 id="entry1Title">สาย Claude Code</h2>
164169
<div class="cmd" id="npxCmd">npx github:MankhongGarden/claude-stats</div>
165170
<button class="btn" id="copyCmdBtn" type="button">คัดลอก</button>
166171
</div>
167-
<div class="privacy"><b>Privacy:</b> <span id="privacy1Text">สแกนในเครื่องเท่านั้น · ลิงก์แชร์มีแค่ตัวเลขสรุป 6 ค่า</span></div>
172+
<div class="steps" id="nodeNote">Node 18+ · อ่าน source ได้ ~250 บรรทัด</div>
173+
<div class="privacy"><b>Privacy:</b> <span id="privacy1Text">ไฟล์ในเครื่องถูกอ่านในเครื่องเท่านั้น ไม่อัปโหลด · ลิงก์แชร์มีแค่คะแนนสรุป</span></div>
168174
</div>
169175
<div class="entry">
170176
<h2><span id="entry2Title">สาย claude.ai</span> <span class="badge">BETA</span></h2>
@@ -206,7 +212,7 @@ <h2><span id="entry2Title">สาย claude.ai</span> <span class="badge">BETA</
206212
<div class="card-actions">
207213
<button class="btn" id="pngBtn" type="button">ดาวน์โหลดการ์ด PNG</button>
208214
<button class="btn ghost" id="copyLinkBtn" type="button">คัดลอกลิงก์แชร์</button>
209-
<button class="btn ghost" id="resetBtn" type="button">เริ่มใหม่</button>
215+
<button class="btn" id="resetBtn" type="button">เริ่มใหม่</button>
210216
</div>
211217
</div>
212218

@@ -227,10 +233,13 @@ <h2><span id="entry2Title">สาย claude.ai</span> <span class="badge">BETA</
227233

228234
var STR = {
229235
th: {
236+
kicker: 'สเตตัสสไตล์ MMORPG · คำนวณจากการใช้งานจริง ไม่ใช่แบบทดสอบ',
237+
h1: 'การ์ดตัวละคร Claude ของคุณ',
230238
heroDesc: 'คุณใช้ Claude สายไหน? เช็คสเตตัส 6 ด้าน รับ class + level ของตัวเอง — ข้อมูลดิบไม่ออกจากเครื่องคุณ',
231239
entry1Title: 'สาย Claude Code',
232240
entry1Sub: 'รันคำสั่งเดียวใน terminal — สแกน transcript ในเครื่อง แล้วได้ลิงก์การ์ดทันที',
233-
privacy1Text: 'สแกนในเครื่องเท่านั้น · ลิงก์แชร์มีแค่ตัวเลขสรุป 6 ค่า',
241+
nodeNote: 'Node 18+ · อ่าน source ได้ ~250 บรรทัด',
242+
privacy1Text: 'ไฟล์ในเครื่องถูกอ่านในเครื่องเท่านั้น ไม่อัปโหลด · ลิงก์แชร์มีแค่คะแนนสรุป',
234243
entry2Title: 'สาย claude.ai',
235244
entry2Sub: 'ใช้ไฟล์ export จาก claude.ai — วิเคราะห์ในเบราว์เซอร์นี้ทั้งหมด',
236245
dropBig: 'วางไฟล์ conversations.json ที่นี่',
@@ -241,10 +250,11 @@ <h2><span id="entry2Title">สาย claude.ai</span> <span class="badge">BETA</
241250
copyCmdBtn: 'คัดลอก',
242251
pngBtn: 'ดาวน์โหลดการ์ด PNG',
243252
copyLinkBtn: 'คัดลอกลิงก์แชร์',
244-
resetBtn: 'เริ่มใหม่',
253+
resetBtn: 'สร้างการ์ดของคุณ →',
254+
webEstimate: '⚠ ประเมินคร่าวๆ จากข้อความ · เทียบกับการ์ดสาย CLI ไม่ได้',
245255
badLink: 'ลิงก์ไม่ถูกต้องหรือเสียหาย — กลับสู่หน้าแรก',
246256
copiedCmd: 'คัดลอกคำสั่งแล้ว — ไปวางใน terminal ได้เลย',
247-
copiedLink: 'คัดลอกลิงก์แล้ว — ในลิงก์มีแค่ตัวเลขสรุป 6 ค่า',
257+
copiedLink: 'คัดลอกลิงก์แล้ว — ในลิงก์มีแค่คะแนน 6 ค่า + ยอดรวม 3 ตัว (session · tool call · ข้อความ)',
248258
copyFail: 'คัดลอกไม่สำเร็จ — copy เองได้เลย: ',
249259
readFail: 'อ่านไฟล์ไม่สำเร็จ ลองใหม่อีกครั้ง',
250260
badFormat: 'รูปแบบไฟล์ไม่ตรงที่คาด (parser ยังเป็น beta)',
@@ -255,10 +265,13 @@ <h2><span id="entry2Title">สาย claude.ai</span> <span class="badge">BETA</
255265
scoringFail: 'โหลด lib/scoring.js ไม่สำเร็จ'
256266
},
257267
en: {
268+
kicker: 'MMORPG-style stats · earned from your real usage, not a quiz',
269+
h1: 'Your Claude Character Sheet',
258270
heroDesc: 'Which Claude build are you? Check your 6 stats and get your class + level — raw data never leaves your machine.',
259271
entry1Title: 'Claude Code users',
260272
entry1Sub: 'One command in your terminal — scans local transcripts, prints your card and a share link.',
261-
privacy1Text: 'Scans locally only · the share link carries just 6 aggregate numbers',
273+
nodeNote: 'Node 18+ · source is ~250 lines, read before you run',
274+
privacy1Text: 'Local files are read on your machine and never uploaded · the share link carries only summary scores',
262275
entry2Title: 'claude.ai users',
263276
entry2Sub: 'Use your claude.ai data export — analyzed entirely in this browser.',
264277
dropBig: 'Drop conversations.json here',
@@ -269,10 +282,11 @@ <h2><span id="entry2Title">สาย claude.ai</span> <span class="badge">BETA</
269282
copyCmdBtn: 'Copy',
270283
pngBtn: 'Download card PNG',
271284
copyLinkBtn: 'Copy share link',
272-
resetBtn: 'Start over',
285+
resetBtn: 'Make your own →',
286+
webEstimate: '⚠ Rough estimate inferred from text · not comparable to CLI cards',
273287
badLink: 'Invalid or corrupted link — back to home',
274288
copiedCmd: 'Command copied — paste it in your terminal',
275-
copiedLink: 'Link copied — it carries only the 6 aggregate numbers',
289+
copiedLink: 'Link copied — it carries only the 6 scores + 3 totals (sessions · tool calls · messages)',
276290
copyFail: 'Copy failed — grab it manually: ',
277291
readFail: 'Could not read the file, please try again',
278292
badFormat: 'Unexpected file format (the parser is still beta)',
@@ -298,7 +312,7 @@ <h2><span id="entry2Title">สาย claude.ai</span> <span class="badge">BETA</
298312

299313
function applyLang() {
300314
document.documentElement.lang = lang;
301-
['heroDesc', 'entry1Title', 'entry1Sub', 'privacy1Text', 'entry2Title', 'entry2Sub',
315+
['kicker', 'h1', 'heroDesc', 'entry1Title', 'entry1Sub', 'nodeNote', 'privacy1Text', 'entry2Title', 'entry2Sub',
302316
'dropBig', 'dropHint', 'privacy2Text', 'demoLink',
303317
'copyCmdBtn', 'pngBtn', 'copyLinkBtn', 'resetBtn'].forEach(function (id) {
304318
var node = document.getElementById(id);
@@ -379,7 +393,7 @@ <h2><span id="entry2Title">สาย claude.ai</span> <span class="badge">BETA</
379393
var lv = S.level(effort);
380394

381395
document.getElementById('className').textContent = cls[0];
382-
document.getElementById('classSub').textContent = (lang === 'th' ? cls[1] : cls[2]) || cls[1];
396+
document.getElementById('classSub').textContent = isWeb ? t('webEstimate') : ((lang === 'th' ? cls[1] : cls[2]) || cls[1]);
383397
document.getElementById('lv').textContent = String(lv);
384398
document.getElementById('srcBadge').textContent = isWeb ? 'WEB · BETA' : 'CLI';
385399

@@ -443,6 +457,10 @@ <h2><span id="entry2Title">สาย claude.ai</span> <span class="badge">BETA</
443457
},
444458
},
445459
});
460+
461+
var c = document.getElementById('radar');
462+
c.setAttribute('role', 'img');
463+
c.setAttribute('aria-label', S.AXES.map(function (k) { return S.META[k].en + ' ' + stats[k]; }).join(', '));
446464
}
447465

448466
/* ---------- routing ---------- */
@@ -470,10 +488,10 @@ <h2><span id="entry2Title">สาย claude.ai</span> <span class="badge">BETA</
470488
return '';
471489
}
472490

473-
var RX_RESEARCH = /\?|||search|why|||https?:\/\//i;
474-
var RX_AUTO = /script|automation|cron|bot|workflow|/i;
475-
var RX_INTEG = /api|integration|mcp|webhook|/i;
476-
var RX_PILOT = /browser|selenium|playwright|/i;
491+
var RX_RESEARCH = /\?|\b(search|why|how|find)\b||||https?:\/\//i;
492+
var RX_AUTO = /\b(script|automation|cron|bot|workflow)\b|/i;
493+
var RX_INTEG = /\b(api|integration|mcp|webhook)\b|/i;
494+
var RX_PILOT = /\b(browser|selenium|playwright)\b|/i;
477495

478496
function parseClaudeExport(json) {
479497
var convs = null;

0 commit comments

Comments
 (0)