@jeganwrites/claudash 1.0.4 → 1.0.5
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/README.md +14 -3
- package/analyzer.py +11 -5
- package/bin/claudash.js +50 -27
- package/cli.py +9 -0
- package/package.json +2 -8
- package/tools/hooks/post-session.sh +15 -0
- package/tools/sync-daemon.py +54 -0
package/README.md
CHANGED
|
@@ -40,16 +40,17 @@ Zero pip dependencies. Single SQLite file. Single HTML page.
|
|
|
40
40
|
- Claude Code installed and at least one session run
|
|
41
41
|
- macOS or Linux (Windows: core features work, browser tracking not supported)
|
|
42
42
|
|
|
43
|
-
###
|
|
43
|
+
### Via npm (recommended)
|
|
44
44
|
|
|
45
45
|
```bash
|
|
46
|
-
|
|
46
|
+
npm install -g @jeganwrites/claudash
|
|
47
|
+
claudash
|
|
47
48
|
```
|
|
48
49
|
|
|
49
50
|
Requires Node.js 16+ and Python 3.8+.
|
|
50
51
|
Auto-installs, opens browser, detects your Claude Code data.
|
|
51
52
|
|
|
52
|
-
###
|
|
53
|
+
### Or git clone
|
|
53
54
|
|
|
54
55
|
```bash
|
|
55
56
|
git clone https://github.com/pnjegan/claudash
|
|
@@ -90,6 +91,14 @@ python3 tools/mac-sync.py
|
|
|
90
91
|
# */5 * * * * python3 /path/to/oauth_sync.py
|
|
91
92
|
```
|
|
92
93
|
|
|
94
|
+
### Auto-sync daemon (runs every 5 minutes)
|
|
95
|
+
|
|
96
|
+
```bash
|
|
97
|
+
python3 cli.py sync-daemon
|
|
98
|
+
# Or run in background:
|
|
99
|
+
nohup python3 cli.py sync-daemon > /tmp/claudash-sync.log 2>&1 &
|
|
100
|
+
```
|
|
101
|
+
|
|
93
102
|
Full setup walkthrough in [SETUP.md](SETUP.md).
|
|
94
103
|
|
|
95
104
|
## Keeping it running
|
|
@@ -161,6 +170,7 @@ page to modify accounts.
|
|
|
161
170
|
| `python3 cli.py mcp` | Print the MCP settings.json snippet + smoke-test the server |
|
|
162
171
|
| `python3 cli.py keys` | Print `dashboard_key` and `sync_token` (sensitive) |
|
|
163
172
|
| `python3 cli.py claude-ai` | Show claude.ai browser tracking status |
|
|
173
|
+
| `python3 cli.py sync-daemon` | Auto-sync browser data every 5 min (foreground) |
|
|
164
174
|
|
|
165
175
|
## How Claudash differs from similar tools
|
|
166
176
|
|
|
@@ -236,6 +246,7 @@ Every mutating endpoint requires `X-Dashboard-Key` (from `python3 cli.py keys`).
|
|
|
236
246
|
## Documentation
|
|
237
247
|
|
|
238
248
|
- [SETUP.md](SETUP.md) — first-time setup guide
|
|
249
|
+
- [docs/HOOKS_SETUP.md](docs/HOOKS_SETUP.md) — Claude Code hooks integration
|
|
239
250
|
- [CHANGELOG.md](CHANGELOG.md) — version history
|
|
240
251
|
- [CONTRIBUTING.md](CONTRIBUTING.md) — how to contribute
|
|
241
252
|
|
package/analyzer.py
CHANGED
|
@@ -739,17 +739,23 @@ def compute_efficiency_score(conn, account="all"):
|
|
|
739
739
|
window_score = max(0, min(100, window_score))
|
|
740
740
|
|
|
741
741
|
# Dimension 4: Floundering rate
|
|
742
|
+
# Count DISTINCT sessions (not events) with floundering, filtered by account
|
|
742
743
|
total_sessions = conn.execute(
|
|
743
744
|
f"SELECT COUNT(DISTINCT session_id) FROM sessions WHERE {where}",
|
|
744
745
|
params
|
|
745
746
|
).fetchone()[0] or 1
|
|
747
|
+
flounder_where = "pattern_type='floundering' AND detected_at > ?"
|
|
748
|
+
flounder_params = [cutoff]
|
|
749
|
+
if account and account != "all":
|
|
750
|
+
flounder_where += " AND account = ?"
|
|
751
|
+
flounder_params.append(account)
|
|
746
752
|
flounder_sessions = conn.execute(
|
|
747
|
-
"SELECT COUNT(DISTINCT session_id) FROM waste_events "
|
|
748
|
-
|
|
749
|
-
[cutoff]
|
|
753
|
+
f"SELECT COUNT(DISTINCT session_id) FROM waste_events WHERE {flounder_where}",
|
|
754
|
+
flounder_params
|
|
750
755
|
).fetchone()[0]
|
|
751
|
-
|
|
752
|
-
|
|
756
|
+
# Linear penalty: 0% flounder = 100, 1% = 90, 5% = 50, 10%+ = 0
|
|
757
|
+
flounder_pct = flounder_sessions / total_sessions * 100
|
|
758
|
+
flounder_score = max(0, round(100 - flounder_pct * 10))
|
|
753
759
|
|
|
754
760
|
# Dimension 5: Compaction discipline
|
|
755
761
|
compact_events = conn.execute(
|
package/bin/claudash.js
CHANGED
|
@@ -1,28 +1,36 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
const { execSync, spawn } = require('child_process');
|
|
4
|
+
const path = require('path');
|
|
5
|
+
const fs = require('fs');
|
|
6
|
+
const os = require('os');
|
|
4
7
|
|
|
8
|
+
const VERSION = '1.0.5';
|
|
9
|
+
const REPO = 'https://github.com/pnjegan/claudash';
|
|
10
|
+
const INSTALL_DIR = path.join(os.homedir(), '.claudash');
|
|
11
|
+
|
|
12
|
+
// Parse args FIRST — before any other code runs.
|
|
13
|
+
// This prevents macOS `open` from ever receiving --help and misinterpreting it.
|
|
5
14
|
const args = process.argv.slice(2);
|
|
15
|
+
|
|
6
16
|
if (args.includes('--help') || args.includes('-h')) {
|
|
7
|
-
console.log('Claudash
|
|
17
|
+
console.log('Claudash v' + VERSION + ' — Claude Code usage intelligence');
|
|
8
18
|
console.log('');
|
|
9
|
-
console.log('Usage: claudash [
|
|
19
|
+
console.log('Usage: claudash [--port <n>]');
|
|
10
20
|
console.log('');
|
|
11
|
-
console.log('
|
|
12
|
-
console.log(' --
|
|
13
|
-
console.log(' --help
|
|
21
|
+
console.log(' --port <n> Port to serve on (default: 8080)');
|
|
22
|
+
console.log(' --no-browser Skip auto-opening browser');
|
|
23
|
+
console.log(' --help, -h Show this help');
|
|
14
24
|
console.log('');
|
|
15
25
|
console.log('GitHub: https://github.com/pnjegan/claudash');
|
|
26
|
+
console.log('npm: npm install -g @jeganwrites/claudash');
|
|
16
27
|
process.exit(0);
|
|
17
28
|
}
|
|
18
29
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
const VERSION = '1.0.0';
|
|
24
|
-
const REPO = 'https://github.com/pnjegan/claudash';
|
|
25
|
-
const INSTALL_DIR = path.join(os.homedir(), '.claudash');
|
|
30
|
+
if (args.includes('--version') || args.includes('-v')) {
|
|
31
|
+
console.log(VERSION);
|
|
32
|
+
process.exit(0);
|
|
33
|
+
}
|
|
26
34
|
|
|
27
35
|
function checkPython() {
|
|
28
36
|
try {
|
|
@@ -58,6 +66,19 @@ function checkClaudeData() {
|
|
|
58
66
|
return found;
|
|
59
67
|
}
|
|
60
68
|
|
|
69
|
+
function isPortInUse(port) {
|
|
70
|
+
try {
|
|
71
|
+
execSync(
|
|
72
|
+
'lsof -ti:' + port + ' 2>/dev/null || ' +
|
|
73
|
+
'netstat -an 2>/dev/null | grep ":' + port + ' " | grep LISTEN',
|
|
74
|
+
{ stdio: 'pipe' }
|
|
75
|
+
);
|
|
76
|
+
return true;
|
|
77
|
+
} catch (e) {
|
|
78
|
+
return false;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
61
82
|
function installClaudash() {
|
|
62
83
|
if (fs.existsSync(path.join(INSTALL_DIR, 'cli.py'))) {
|
|
63
84
|
try {
|
|
@@ -93,22 +114,17 @@ function openBrowser(port) {
|
|
|
93
114
|
}
|
|
94
115
|
|
|
95
116
|
function main() {
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
console.log(' --help Show this help');
|
|
107
|
-
process.exit(0);
|
|
117
|
+
// Support both --port=N and --port N
|
|
118
|
+
let port = '8080';
|
|
119
|
+
const portEq = args.find(a => a.startsWith('--port='));
|
|
120
|
+
if (portEq) {
|
|
121
|
+
port = portEq.split('=')[1];
|
|
122
|
+
} else {
|
|
123
|
+
const portIdx = args.indexOf('--port');
|
|
124
|
+
if (portIdx !== -1 && args[portIdx + 1]) {
|
|
125
|
+
port = args[portIdx + 1];
|
|
126
|
+
}
|
|
108
127
|
}
|
|
109
|
-
|
|
110
|
-
const portArg = args.find(a => a.startsWith('--port='));
|
|
111
|
-
const port = portArg ? portArg.split('=')[1] : '8080';
|
|
112
128
|
const noBrowser = args.includes('--no-browser');
|
|
113
129
|
|
|
114
130
|
console.log('Claudash v' + VERSION);
|
|
@@ -118,6 +134,13 @@ function main() {
|
|
|
118
134
|
checkClaudeData();
|
|
119
135
|
installClaudash();
|
|
120
136
|
|
|
137
|
+
if (isPortInUse(port)) {
|
|
138
|
+
console.log('Port ' + port + ' is already in use.');
|
|
139
|
+
console.log('Try: claudash --port 8081');
|
|
140
|
+
console.log('Or kill the process: lsof -ti:' + port + ' | xargs kill');
|
|
141
|
+
process.exit(1);
|
|
142
|
+
}
|
|
143
|
+
|
|
121
144
|
console.log('Starting dashboard on http://localhost:' + port + ' ...');
|
|
122
145
|
if (!noBrowser) {
|
|
123
146
|
openBrowser(port);
|
package/cli.py
CHANGED
|
@@ -42,6 +42,7 @@ Commands:
|
|
|
42
42
|
keys --rotate Regenerate dashboard_key (invalidates existing browser sessions)
|
|
43
43
|
init First-run setup wizard (3 questions, then start)
|
|
44
44
|
claude-ai Show claude.ai browser tracking status
|
|
45
|
+
sync-daemon Auto-sync browser data every 5 minutes (background)
|
|
45
46
|
claude-ai --sync-token Print sync token (for tools/mac-sync.py)
|
|
46
47
|
claude-ai --setup <account_id> Paste a claude.ai session key interactively
|
|
47
48
|
|
|
@@ -882,6 +883,13 @@ def cmd_export():
|
|
|
882
883
|
print(f"Exported {len(rows)} rows to {outpath}")
|
|
883
884
|
|
|
884
885
|
|
|
886
|
+
def cmd_sync_daemon():
|
|
887
|
+
"""Run the sync daemon that pushes claude.ai browser data every 5 min."""
|
|
888
|
+
daemon = os.path.join(os.path.dirname(os.path.abspath(__file__)),
|
|
889
|
+
"tools", "sync-daemon.py")
|
|
890
|
+
os.execv(sys.executable, [sys.executable, daemon])
|
|
891
|
+
|
|
892
|
+
|
|
885
893
|
def cmd_keys():
|
|
886
894
|
"""Print dashboard_key and sync_token. Sensitive — do not paste into
|
|
887
895
|
screenshots, chat transcripts, or shared terminals."""
|
|
@@ -1019,6 +1027,7 @@ def main():
|
|
|
1019
1027
|
"mcp": cmd_mcp,
|
|
1020
1028
|
"keys": cmd_keys,
|
|
1021
1029
|
"claude-ai": cmd_claude_ai,
|
|
1030
|
+
"sync-daemon": cmd_sync_daemon,
|
|
1022
1031
|
}
|
|
1023
1032
|
|
|
1024
1033
|
handler = commands.get(cmd)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jeganwrites/claudash",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.5",
|
|
4
4
|
"description": "Claude Code usage intelligence dashboard",
|
|
5
5
|
"bin": {
|
|
6
6
|
"claudash": "./bin/claudash.js"
|
|
@@ -8,13 +8,7 @@
|
|
|
8
8
|
"scripts": {
|
|
9
9
|
"start": "node bin/claudash.js"
|
|
10
10
|
},
|
|
11
|
-
"keywords": [
|
|
12
|
-
"claude",
|
|
13
|
-
"claude-code",
|
|
14
|
-
"usage",
|
|
15
|
-
"dashboard",
|
|
16
|
-
"ai"
|
|
17
|
-
],
|
|
11
|
+
"keywords": ["claude", "claude-code", "usage", "dashboard", "ai"],
|
|
18
12
|
"author": "Jegan Nagarajan",
|
|
19
13
|
"license": "MIT",
|
|
20
14
|
"repository": {
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# Claudash post-session hook
|
|
3
|
+
# Add to ~/.claude/settings.json PostToolUse hooks
|
|
4
|
+
# Records session cost after each Claude Code session
|
|
5
|
+
|
|
6
|
+
CLAUDASH_DIR="${CLAUDASH_DIR:-$HOME/.claudash}"
|
|
7
|
+
DASHBOARD_URL="${CLAUDASH_URL:-http://localhost:8080}"
|
|
8
|
+
|
|
9
|
+
# Trigger a scan to pick up the new session
|
|
10
|
+
KEY=$(python3 "$CLAUDASH_DIR/cli.py" keys 2>/dev/null | grep dashboard_key | awk '{print $3}')
|
|
11
|
+
curl -s -X POST "$DASHBOARD_URL/api/scan" \
|
|
12
|
+
-H "X-Dashboard-Key: $KEY" \
|
|
13
|
+
> /dev/null 2>&1
|
|
14
|
+
|
|
15
|
+
echo "[claudash] Session recorded"
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
Claudash sync daemon — runs in background,
|
|
4
|
+
pushes claude.ai browser data every 5 minutes.
|
|
5
|
+
Works on macOS (uses mac-sync.py) or any OS (uses oauth_sync.py).
|
|
6
|
+
"""
|
|
7
|
+
import time
|
|
8
|
+
import os
|
|
9
|
+
import sys
|
|
10
|
+
import subprocess
|
|
11
|
+
import platform
|
|
12
|
+
|
|
13
|
+
INTERVAL = 300 # 5 minutes
|
|
14
|
+
SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__))
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def get_sync_script():
|
|
18
|
+
if platform.system() == "Darwin":
|
|
19
|
+
mac_sync = os.path.join(SCRIPT_DIR, "mac-sync.py")
|
|
20
|
+
if os.path.exists(mac_sync):
|
|
21
|
+
return mac_sync
|
|
22
|
+
return os.path.join(SCRIPT_DIR, "oauth_sync.py")
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def run_sync():
|
|
26
|
+
script = get_sync_script()
|
|
27
|
+
try:
|
|
28
|
+
result = subprocess.run(
|
|
29
|
+
[sys.executable, script],
|
|
30
|
+
capture_output=True, text=True, timeout=30
|
|
31
|
+
)
|
|
32
|
+
if result.returncode == 0:
|
|
33
|
+
print(f"[sync] OK: {result.stdout.strip()[:200]}")
|
|
34
|
+
else:
|
|
35
|
+
print(f"[sync] Error: {result.stderr.strip()[:200]}")
|
|
36
|
+
except subprocess.TimeoutExpired:
|
|
37
|
+
print("[sync] Timeout after 30s")
|
|
38
|
+
except Exception as e:
|
|
39
|
+
print(f"[sync] Failed: {e}")
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
if __name__ == "__main__":
|
|
43
|
+
print(f"Claudash sync daemon starting (every {INTERVAL//60} min)")
|
|
44
|
+
print(f"Using: {get_sync_script()}")
|
|
45
|
+
print("Press Ctrl+C to stop")
|
|
46
|
+
print()
|
|
47
|
+
|
|
48
|
+
run_sync() # run immediately on start
|
|
49
|
+
try:
|
|
50
|
+
while True:
|
|
51
|
+
time.sleep(INTERVAL)
|
|
52
|
+
run_sync()
|
|
53
|
+
except KeyboardInterrupt:
|
|
54
|
+
print("\nSync daemon stopped.")
|