@jeganwrites/claudash 1.0.4 → 1.0.6

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 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
- ### Fastest (via npx — no install needed)
43
+ ### Via npm (recommended)
44
44
 
45
45
  ```bash
46
- npx claudash
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
- ### Manual install (local Mac/Linux)
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
- "WHERE pattern_type='floundering' AND detected_at > ?",
749
- [cutoff]
753
+ f"SELECT COUNT(DISTINCT session_id) FROM waste_events WHERE {flounder_where}",
754
+ flounder_params
750
755
  ).fetchone()[0]
751
- flounder_rate = flounder_sessions / total_sessions
752
- flounder_score = round((1 - min(flounder_rate * 10, 1)) * 100)
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 v1.0 — Claude Code usage intelligence');
17
+ console.log('Claudash v' + VERSION + ' — Claude Code usage intelligence');
8
18
  console.log('');
9
- console.log('Usage: claudash [options]');
19
+ console.log('Usage: claudash [--port <n>]');
10
20
  console.log('');
11
- console.log('Options:');
12
- console.log(' --port <n> Port to run on (default: 8080)');
13
- console.log(' --help Show this 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
- const path = require('path');
20
- const fs = require('fs');
21
- const os = require('os');
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
- const args = process.argv.slice(2);
97
-
98
- if (args.includes('--help') || args.includes('-h')) {
99
- console.log('Claudash v' + VERSION + ' — Claude Code usage intelligence');
100
- console.log('');
101
- console.log('Usage: npx claudash [options]');
102
- console.log('');
103
- console.log('Options:');
104
- console.log(' --port=N Dashboard port (default: 8080)');
105
- console.log(' --no-browser Skip auto-opening browser');
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.4",
3
+ "version": "1.0.6",
4
4
  "description": "Claude Code usage intelligence dashboard",
5
5
  "bin": {
6
6
  "claudash": "./bin/claudash.js"
@@ -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.")