clutch-cli 0.2.0__tar.gz → 0.3.0__tar.gz

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.
Files changed (34) hide show
  1. {clutch_cli-0.2.0 → clutch_cli-0.3.0}/PKG-INFO +10 -10
  2. {clutch_cli-0.2.0 → clutch_cli-0.3.0}/README.md +9 -9
  3. clutch_cli-0.3.0/clutch_cli/activity/__init__.py +0 -0
  4. {clutch_cli-0.2.0/clutch_cli → clutch_cli-0.3.0/clutch_cli/activity}/patterns.py +18 -33
  5. {clutch_cli-0.2.0/clutch_cli → clutch_cli-0.3.0/clutch_cli/activity}/stats.py +11 -21
  6. clutch_cli-0.3.0/clutch_cli/activity/streak.py +54 -0
  7. clutch_cli-0.3.0/clutch_cli/authentication/__init__.py +0 -0
  8. clutch_cli-0.3.0/clutch_cli/authentication/login.py +110 -0
  9. clutch_cli-0.3.0/clutch_cli/authentication/logout.py +13 -0
  10. clutch_cli-0.3.0/clutch_cli/authentication/whoami.py +11 -0
  11. clutch_cli-0.3.0/clutch_cli/insights/__init__.py +0 -0
  12. clutch_cli-0.2.0/clutch_cli/insight.py → clutch_cli-0.3.0/clutch_cli/insights/weekly.py +13 -21
  13. clutch_cli-0.3.0/clutch_cli/main.py +59 -0
  14. clutch_cli-0.3.0/clutch_cli/repositories/__init__.py +0 -0
  15. clutch_cli-0.2.0/clutch_cli/repos.py → clutch_cli-0.3.0/clutch_cli/repositories/list.py +9 -30
  16. clutch_cli-0.3.0/clutch_cli/system/__init__.py +0 -0
  17. clutch_cli-0.3.0/clutch_cli/system/status.py +45 -0
  18. clutch_cli-0.3.0/clutch_cli/theme.py +42 -0
  19. {clutch_cli-0.2.0 → clutch_cli-0.3.0}/clutch_cli.egg-info/PKG-INFO +10 -10
  20. clutch_cli-0.3.0/clutch_cli.egg-info/SOURCES.txt +27 -0
  21. {clutch_cli-0.2.0 → clutch_cli-0.3.0}/pyproject.toml +1 -1
  22. clutch_cli-0.2.0/clutch_cli/auth.py +0 -132
  23. clutch_cli-0.2.0/clutch_cli/main.py +0 -43
  24. clutch_cli-0.2.0/clutch_cli/status.py +0 -54
  25. clutch_cli-0.2.0/clutch_cli/streak.py +0 -80
  26. clutch_cli-0.2.0/clutch_cli.egg-info/SOURCES.txt +0 -19
  27. {clutch_cli-0.2.0 → clutch_cli-0.3.0}/clutch_cli/__init__.py +0 -0
  28. {clutch_cli-0.2.0 → clutch_cli-0.3.0}/clutch_cli/api.py +0 -0
  29. {clutch_cli-0.2.0 → clutch_cli-0.3.0}/clutch_cli/config.py +0 -0
  30. {clutch_cli-0.2.0 → clutch_cli-0.3.0}/clutch_cli.egg-info/dependency_links.txt +0 -0
  31. {clutch_cli-0.2.0 → clutch_cli-0.3.0}/clutch_cli.egg-info/entry_points.txt +0 -0
  32. {clutch_cli-0.2.0 → clutch_cli-0.3.0}/clutch_cli.egg-info/requires.txt +0 -0
  33. {clutch_cli-0.2.0 → clutch_cli-0.3.0}/clutch_cli.egg-info/top_level.txt +0 -0
  34. {clutch_cli-0.2.0 → clutch_cli-0.3.0}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: clutch-cli
3
- Version: 0.2.0
3
+ Version: 0.3.0
4
4
  Summary: GitHub tracks your work. Clutch tracks you.
5
5
  Author-email: Lay Patel <lay.patel.1313@gmail.com>
6
6
  License: MIT
@@ -35,7 +35,7 @@ Requires-Dist: rich>=13.0.0
35
35
 
36
36
  ---
37
37
 
38
- `clutch-cli` is the terminal companion for [Clutch](https://clutch-laypatel.netlify.app) — an open-source AI-powered developer activity dashboard. Get your GitHub streaks, stats, coding patterns, and AI insights without leaving your terminal.
38
+ `clutch-cli` is the terminal companion for [Clutch](https://clutch-woad.vercel.app) — an open-source AI-powered developer activity dashboard. Get your GitHub streaks, stats, coding patterns, and AI insights without leaving your terminal.
39
39
 
40
40
  ## Installation
41
41
 
@@ -47,7 +47,7 @@ pip install clutch-cli
47
47
 
48
48
  ```bash
49
49
  # Login via GitHub (opens browser automatically, no copy-paste needed)
50
- clutch auth login
50
+ clutch login
51
51
 
52
52
  # Check your streak
53
53
  clutch streak
@@ -63,9 +63,9 @@ clutch insight
63
63
 
64
64
  | Command | Description |
65
65
  |---|---|
66
- | `clutch auth login` | Login via GitHub OAuth (fully automatic) |
67
- | `clutch auth logout` | Logout and clear saved credentials |
68
- | `clutch auth whoami` | Show currently logged-in user |
66
+ | `clutch login` | Login via GitHub OAuth (fully automatic) |
67
+ | `clutch logout` | Logout and clear saved credentials |
68
+ | `clutch whoami` | Show currently logged-in user |
69
69
  | `clutch streak` | Current and longest commit streak |
70
70
  | `clutch stats` | Activity stats — commits, PRs, issues, active days |
71
71
  | `clutch stats --days 7` | Stats for a custom time range |
@@ -77,10 +77,10 @@ clutch insight
77
77
 
78
78
  ## How Login Works
79
79
 
80
- `clutch auth login` spins up a temporary local server on port `9876`, opens GitHub OAuth in your browser, and automatically captures the token when GitHub redirects back. No copy-pasting required.
80
+ `clutch login` spins up a temporary local server on port `9876`, opens GitHub OAuth in your browser, and automatically captures the token when GitHub redirects back. No copy-pasting required.
81
81
 
82
82
  ```
83
- $ clutch auth login
83
+ $ clutch login
84
84
 
85
85
  ⚡ Clutch Login
86
86
  Opening GitHub in your browser...
@@ -98,12 +98,12 @@ By default the CLI talks to the hosted Clutch API. To point it at a local backen
98
98
 
99
99
  ```bash
100
100
  export CLUTCH_API_URL=http://localhost:8000
101
- clutch auth login
101
+ clutch login
102
102
  ```
103
103
 
104
104
  ## Links
105
105
 
106
- - 🌐 [Live Dashboard](https://clutch-laypatel.netlify.app)
106
+ - 🌐 [Live Dashboard](https://clutch-woad.vercel.app)
107
107
  - 📖 [Full Documentation](https://github.com/laypatel13/clutch)
108
108
  - 🐛 [Report a Bug](https://github.com/laypatel13/clutch/issues)
109
109
 
@@ -12,7 +12,7 @@
12
12
 
13
13
  ---
14
14
 
15
- `clutch-cli` is the terminal companion for [Clutch](https://clutch-laypatel.netlify.app) — an open-source AI-powered developer activity dashboard. Get your GitHub streaks, stats, coding patterns, and AI insights without leaving your terminal.
15
+ `clutch-cli` is the terminal companion for [Clutch](https://clutch-woad.vercel.app) — an open-source AI-powered developer activity dashboard. Get your GitHub streaks, stats, coding patterns, and AI insights without leaving your terminal.
16
16
 
17
17
  ## Installation
18
18
 
@@ -24,7 +24,7 @@ pip install clutch-cli
24
24
 
25
25
  ```bash
26
26
  # Login via GitHub (opens browser automatically, no copy-paste needed)
27
- clutch auth login
27
+ clutch login
28
28
 
29
29
  # Check your streak
30
30
  clutch streak
@@ -40,9 +40,9 @@ clutch insight
40
40
 
41
41
  | Command | Description |
42
42
  |---|---|
43
- | `clutch auth login` | Login via GitHub OAuth (fully automatic) |
44
- | `clutch auth logout` | Logout and clear saved credentials |
45
- | `clutch auth whoami` | Show currently logged-in user |
43
+ | `clutch login` | Login via GitHub OAuth (fully automatic) |
44
+ | `clutch logout` | Logout and clear saved credentials |
45
+ | `clutch whoami` | Show currently logged-in user |
46
46
  | `clutch streak` | Current and longest commit streak |
47
47
  | `clutch stats` | Activity stats — commits, PRs, issues, active days |
48
48
  | `clutch stats --days 7` | Stats for a custom time range |
@@ -54,10 +54,10 @@ clutch insight
54
54
 
55
55
  ## How Login Works
56
56
 
57
- `clutch auth login` spins up a temporary local server on port `9876`, opens GitHub OAuth in your browser, and automatically captures the token when GitHub redirects back. No copy-pasting required.
57
+ `clutch login` spins up a temporary local server on port `9876`, opens GitHub OAuth in your browser, and automatically captures the token when GitHub redirects back. No copy-pasting required.
58
58
 
59
59
  ```
60
- $ clutch auth login
60
+ $ clutch login
61
61
 
62
62
  ⚡ Clutch Login
63
63
  Opening GitHub in your browser...
@@ -75,12 +75,12 @@ By default the CLI talks to the hosted Clutch API. To point it at a local backen
75
75
 
76
76
  ```bash
77
77
  export CLUTCH_API_URL=http://localhost:8000
78
- clutch auth login
78
+ clutch login
79
79
  ```
80
80
 
81
81
  ## Links
82
82
 
83
- - 🌐 [Live Dashboard](https://clutch-laypatel.netlify.app)
83
+ - 🌐 [Live Dashboard](https://clutch-woad.vercel.app)
84
84
  - 📖 [Full Documentation](https://github.com/laypatel13/clutch)
85
85
  - 🐛 [Report a Bug](https://github.com/laypatel13/clutch/issues)
86
86
 
File without changes
@@ -1,10 +1,8 @@
1
1
  import typer
2
- from rich.console import Console
3
2
  from rich.table import Table
4
3
  from rich import box
5
4
  from clutch_cli.api import get_client
6
-
7
- console = Console()
5
+ from clutch_cli.theme import console, ACCENT, DIM, SUCCESS, WARNING, ERROR, header, footer, bar
8
6
 
9
7
 
10
8
  def patterns():
@@ -12,81 +10,68 @@ def patterns():
12
10
  with get_client() as client:
13
11
  try:
14
12
  console.print()
15
- console.print("[dim]Analyzing patterns...[/dim]")
13
+ console.print(f"[{DIM}]Analyzing patterns...[/{DIM}]")
16
14
 
17
15
  response = client.get("/insights/patterns")
18
16
  if response.status_code != 200:
19
- console.print("[red]Error: Failed to fetch patterns.[/red]")
17
+ console.print("[bold red]Error: Failed to fetch patterns.[/bold red]")
20
18
  raise typer.Exit(1)
21
19
 
22
20
  data = response.json()
23
21
 
24
22
  if "message" in data:
25
- console.print(f"[yellow]{data['message']}[/yellow]")
23
+ console.print(f"[{WARNING}]{data['message']}[/{WARNING}]")
26
24
  raise typer.Exit()
27
25
 
28
- console.print()
29
- console.rule("[bold blue]⚡ CLUTCH — CODING PATTERNS[/bold blue]")
30
- console.print()
26
+ header("CODING PATTERNS")
31
27
 
32
- # Summary table
33
28
  score = data["consistency_score"]
34
- score_color = "green" if score >= 70 else "yellow" if score >= 40 else "red"
29
+ score_style = SUCCESS if score >= 70 else WARNING if score >= 40 else ERROR
35
30
 
36
31
  summary = Table(box=box.SIMPLE, show_header=False, pad_edge=False)
37
- summary.add_column("Label", style="dim", width=22)
32
+ summary.add_column("Label", style=DIM, width=22)
38
33
  summary.add_column("Value", style="bold white")
39
34
 
40
- summary.add_row("Best Day", f"[green]{data['best_day']}[/green]")
41
- summary.add_row("Worst Day", f"[dim]{data['worst_day']}[/dim]")
35
+ summary.add_row("Best Day", f"[{ACCENT}]{data['best_day']}[/{ACCENT}]")
36
+ summary.add_row("Worst Day", f"[{DIM}]{data['worst_day']}[/{DIM}]")
42
37
  summary.add_row("Avg Daily Commits", str(data["avg_daily_commits"]))
43
38
  summary.add_row("Total Active Days", str(data["total_active_days"]))
44
- summary.add_row(
45
- "Consistency Score",
46
- f"[{score_color}]{score}%[/{score_color}]",
47
- )
39
+ summary.add_row("Consistency Score", f"[{score_style}]{score}%[/{score_style}]")
48
40
 
49
41
  console.print(summary)
50
42
  console.print()
51
- console.rule("[dim]Activity by Day[/dim]", style="dim")
43
+ console.rule(f"[{DIM}]Activity by Day[/{DIM}]", style=DIM)
52
44
  console.print()
53
45
 
54
- # Day distribution bar chart
55
46
  day_table = Table(box=box.SIMPLE, show_header=False, pad_edge=False)
56
- day_table.add_column("Day", style="dim", width=12)
47
+ day_table.add_column("Day", style=DIM, width=12)
57
48
  day_table.add_column("Bar", width=32)
58
49
  day_table.add_column("Count", justify="right", style="bold white", width=6)
59
50
 
60
51
  max_commits = max(data["day_distribution"].values()) or 1
61
52
  for day, commits in data["day_distribution"].items():
62
- filled = int((commits / max_commits) * 28)
63
53
  is_best = day == data["best_day"]
64
- bar_color = "blue" if is_best else "white"
65
- bar = f"[{bar_color}]{'█' * filled}[/{bar_color}][dim]{'░' * (28 - filled)}[/dim]"
66
- day_label = f"[blue]{day}[/blue]" if is_best else day
67
- day_table.add_row(day_label, bar, str(commits))
54
+ day_label = f"[{ACCENT}]{day}[/{ACCENT}]" if is_best else day
55
+ day_table.add_row(day_label, bar(commits, max_commits), str(commits))
68
56
 
69
57
  console.print(day_table)
70
58
 
71
- # Top repos
72
59
  if data.get("top_repos"):
73
60
  console.print()
74
- console.rule("[dim]Most Active Repos[/dim]", style="dim")
61
+ console.rule(f"[{DIM}]Most Active Repos[/{DIM}]", style=DIM)
75
62
  console.print()
76
63
 
77
64
  repo_table = Table(box=box.SIMPLE, show_header=False, pad_edge=False)
78
65
  repo_table.add_column("Repo", style="bold white", width=30)
79
- repo_table.add_column("Active Days", justify="right", style="dim", width=12)
66
+ repo_table.add_column("Active Days", justify="right", style=DIM, width=12)
80
67
 
81
68
  for repo in data["top_repos"]:
82
69
  repo_table.add_row(repo["repo"], str(repo["days_active"]))
83
70
 
84
71
  console.print(repo_table)
85
72
 
86
- console.print()
87
- console.rule(style="dim")
88
- console.print()
73
+ footer()
89
74
 
90
75
  except Exception:
91
- console.print("[red]Error: Could not connect to Clutch API.[/red]")
76
+ console.print("[bold red]Error: Could not connect to Clutch API.[/bold red]")
92
77
  raise typer.Exit(1)
@@ -1,10 +1,8 @@
1
1
  import typer
2
- from rich.console import Console
3
2
  from rich.table import Table
4
3
  from rich import box
5
4
  from clutch_cli.api import get_client
6
-
7
- console = Console()
5
+ from clutch_cli.theme import console, ACCENT, DIM, SUCCESS, header, footer, bar
8
6
 
9
7
 
10
8
  def stats(days: int = typer.Option(30, help="Number of days to look back.")):
@@ -13,7 +11,7 @@ def stats(days: int = typer.Option(30, help="Number of days to look back.")):
13
11
  try:
14
12
  response = client.get(f"/github/activity?days={days}")
15
13
  if response.status_code != 200:
16
- console.print("[red]Error: Failed to fetch activity data.[/red]")
14
+ console.print("[bold red]Error: Failed to fetch activity data.[/bold red]")
17
15
  raise typer.Exit(1)
18
16
 
19
17
  data = response.json()
@@ -22,34 +20,26 @@ def stats(days: int = typer.Option(30, help="Number of days to look back.")):
22
20
  issues = data["total_issues"]
23
21
  active = data["active_days"]
24
22
 
25
- console.print()
26
- console.rule(f"[bold blue]⚡ CLUTCH — STATS · LAST {days} DAYS[/bold blue]")
27
- console.print()
23
+ header(f"STATS · LAST {days} DAYS")
28
24
 
29
25
  table = Table(box=box.SIMPLE, show_header=True, pad_edge=False)
30
- table.add_column("Metric", style="dim", width=22)
26
+ table.add_column("Metric", style=DIM, width=22)
31
27
  table.add_column("Count", justify="right", style="bold white", width=10)
32
28
  table.add_column("Bar", width=32)
33
29
 
34
30
  max_val = max(commits, prs, issues, active, 1)
35
31
 
36
- def bar(val):
37
- filled = int((val / max_val) * 28)
38
- return f"[blue]{'█' * filled}[/blue][dim]{'░' * (28 - filled)}[/dim]"
39
-
40
32
  def color(val):
41
- return "bold green" if val > 0 else "dim"
33
+ return SUCCESS if val > 0 else DIM
42
34
 
43
- table.add_row("Commits", f"[{color(commits)}]{commits}[/{color(commits)}]", bar(commits))
44
- table.add_row("Pull Requests", f"[{color(prs)}]{prs}[/{color(prs)}]", bar(prs))
45
- table.add_row("Issues", f"[{color(issues)}]{issues}[/{color(issues)}]", bar(issues))
46
- table.add_row("Active Days", f"[{color(active)}]{active}[/{color(active)}]", bar(active))
35
+ table.add_row("Commits", f"[{color(commits)}]{commits}[/{color(commits)}]", bar(commits, max_val))
36
+ table.add_row("Pull Requests", f"[{color(prs)}]{prs}[/{color(prs)}]", bar(prs, max_val))
37
+ table.add_row("Issues", f"[{color(issues)}]{issues}[/{color(issues)}]", bar(issues, max_val))
38
+ table.add_row("Active Days", f"[{color(active)}]{active}[/{color(active)}]", bar(active, max_val))
47
39
 
48
40
  console.print(table)
49
- console.print()
50
- console.rule(style="dim")
51
- console.print()
41
+ footer()
52
42
 
53
43
  except Exception:
54
- console.print("[red]Error: Could not connect to Clutch API.[/red]")
44
+ console.print("[bold red]Error: Could not connect to Clutch API.[/bold red]")
55
45
  raise typer.Exit(1)
@@ -0,0 +1,54 @@
1
+ import typer
2
+ from rich.table import Table
3
+ from rich import box
4
+ from clutch_cli.api import get_client
5
+ from clutch_cli.theme import console, ACCENT, DIM, SUCCESS, WARNING, header, footer, bar
6
+
7
+
8
+ def streak():
9
+ """Show your current and longest commit streak."""
10
+ with get_client() as client:
11
+ try:
12
+ response = client.get("/github/streak")
13
+ if response.status_code != 200:
14
+ console.print("[bold red]Error: Failed to fetch streak data.[/bold red]")
15
+ raise typer.Exit(1)
16
+
17
+ data = response.json()
18
+ current = data["current_streak"]
19
+ longest = data["longest_streak"]
20
+ total_active = data["total_active_days"]
21
+
22
+ if current >= 14:
23
+ status = "STRONG"
24
+ status_style = SUCCESS
25
+ elif current >= 7:
26
+ status = "BUILDING"
27
+ status_style = ACCENT
28
+ elif current > 0:
29
+ status = "ACTIVE"
30
+ status_style = WARNING
31
+ else:
32
+ status = "INACTIVE"
33
+ status_style = DIM
34
+
35
+ header("STREAK")
36
+
37
+ table = Table(box=box.SIMPLE, show_header=False, pad_edge=False)
38
+ table.add_column("Label", style=DIM, width=22)
39
+ table.add_column("Value", style="bold white")
40
+ table.add_column("Badge", justify="right")
41
+
42
+ table.add_row("Current Streak", f"[{ACCENT}]{current} days[/{ACCENT}]", f"[{status_style}]{status}[/{status_style}]")
43
+ table.add_row("Longest Streak", f"{longest} days", "")
44
+ table.add_row("Total Active Days", f"{total_active} days", "")
45
+
46
+ console.print(table)
47
+ if longest > 0:
48
+ console.print(f" [{DIM}]Progress to longest[/{DIM}] {bar(current, longest)} [{DIM}]{current}/{longest}[/{DIM}]")
49
+
50
+ footer()
51
+
52
+ except Exception:
53
+ console.print("[bold red]Error: Could not connect to Clutch API.[/bold red]")
54
+ raise typer.Exit(1)
File without changes
@@ -0,0 +1,110 @@
1
+ import webbrowser
2
+ from http.server import BaseHTTPRequestHandler, HTTPServer
3
+ from urllib.parse import parse_qs, urlparse
4
+
5
+ import httpx
6
+ from clutch_cli.config import API_BASE_URL, save_token
7
+ from clutch_cli.theme import console, ACCENT, ERROR, DIM, SUCCESS
8
+
9
+ CLI_CALLBACK_PORT = 9876
10
+ _captured_token: dict = {}
11
+
12
+
13
+ class _CallbackHandler(BaseHTTPRequestHandler):
14
+ def do_GET(self):
15
+ parsed = urlparse(self.path)
16
+ if parsed.path == "/callback":
17
+ params = parse_qs(parsed.query)
18
+ token = params.get("token", [None])[0]
19
+ if token:
20
+ _captured_token["value"] = token
21
+ self._respond(200, _success_page())
22
+ else:
23
+ self._respond(400, _error_page("No token received."))
24
+ else:
25
+ self._respond(404, b"Not found")
26
+
27
+ def _respond(self, status: int, body: bytes) -> None:
28
+ self.send_response(status)
29
+ self.send_header("Content-Type", "text/html; charset=utf-8")
30
+ self.end_headers()
31
+ self.wfile.write(body)
32
+
33
+ def log_message(self, *args):
34
+ pass
35
+
36
+
37
+ def _success_page() -> bytes:
38
+ return """<!DOCTYPE html>
39
+ <html>
40
+ <head>
41
+ <title>Clutch</title>
42
+ <link rel="icon" href="data:image/svg+xml,<svg xmlns=%22http://www.w3.org/2000/svg%22 viewBox=%220 0 100 100%22><text y=%22.9em%22 font-size=%2290%22>&#9881;&#65039;</text></svg>" />
43
+ </head>
44
+ <body style="font-family:-apple-system,sans-serif;text-align:center;padding:80px;background:#0a0a0a;color:#ffffff">
45
+ <h1 style="font-weight:800;letter-spacing:1px">CLUTCH</h1>
46
+ <p style="color:#ffffff;font-size:1.1rem;opacity:0.9">&#10003; Login successful &mdash; you can close this tab.</p>
47
+ </body>
48
+ </html>""".encode()
49
+
50
+
51
+ def _error_page(msg: str) -> bytes:
52
+ return f"""<!DOCTYPE html>
53
+ <html>
54
+ <head>
55
+ <title>Clutch</title>
56
+ <link rel="icon" href="data:image/svg+xml,<svg xmlns=%22http://www.w3.org/2000/svg%22 viewBox=%220 0 100 100%22><text y=%22.9em%22 font-size=%2290%22>&#9881;&#65039;</text></svg>" />
57
+ </head>
58
+ <body style="font-family:-apple-system,sans-serif;text-align:center;padding:80px;background:#0a0a0a;color:#ffffff">
59
+ <h1 style="font-weight:800;letter-spacing:1px">CLUTCH</h1>
60
+ <p style="opacity:0.8">Login failed: {msg}</p>
61
+ </body>
62
+ </html>""".encode()
63
+
64
+
65
+ def login():
66
+ """Login to Clutch via GitHub OAuth (browser-based, fully automatic)."""
67
+ console.print()
68
+ console.rule(f"[{ACCENT}]⚡ CLUTCH — LOGIN[/{ACCENT}]")
69
+ console.print(f"[{DIM}]Starting local callback listener...[/{DIM}]")
70
+
71
+ _captured_token.clear()
72
+ server = HTTPServer(("localhost", CLI_CALLBACK_PORT), _CallbackHandler)
73
+
74
+ login_url = f"{API_BASE_URL}/auth/github?cli=true"
75
+ console.print(f"[{DIM}]Opening GitHub in your browser...[/{DIM}]\n")
76
+ webbrowser.open(login_url)
77
+
78
+ console.print(f"[{ACCENT}]Waiting for GitHub authorization...[/{ACCENT}]")
79
+ console.print(f"[{DIM}](If your browser didn't open, visit:)[/{DIM}]")
80
+ console.print(f"[{DIM}]{login_url}[/{DIM}]\n")
81
+
82
+ server.handle_request()
83
+ server.server_close()
84
+
85
+ token = _captured_token.get("value")
86
+ if not token:
87
+ console.print(f"[{ERROR}]Login failed — no token received.[/{ERROR}]")
88
+ raise SystemExit(1)
89
+
90
+ try:
91
+ response = httpx.get(
92
+ f"{API_BASE_URL}/users/me",
93
+ headers={"Authorization": f"Bearer {token}"},
94
+ timeout=10,
95
+ )
96
+ if response.status_code != 200:
97
+ console.print(f"[{ERROR}]Token validation failed.[/{ERROR}]")
98
+ raise SystemExit(1)
99
+
100
+ user = response.json()
101
+ save_token(token, user["username"])
102
+ console.print(f"[{SUCCESS}]Logged in as @{user['username']}[/{SUCCESS}]")
103
+ console.print(f"[{DIM}]Welcome to Clutch, {user.get('name') or user['username']}.[/{DIM}]")
104
+ console.print()
105
+ console.rule(style=DIM)
106
+ console.print()
107
+
108
+ except httpx.RequestError:
109
+ console.print(f"[{ERROR}]Could not connect to Clutch API.[/{ERROR}]")
110
+ raise SystemExit(1)
@@ -0,0 +1,13 @@
1
+ from clutch_cli.config import clear_config, get_username
2
+ from clutch_cli.theme import console, DIM, SUCCESS, WARNING
3
+
4
+
5
+ def logout():
6
+ """Logout from Clutch."""
7
+ username = get_username()
8
+ if not username:
9
+ console.print(f"[{WARNING}]You are not logged in.[/{WARNING}]")
10
+ raise SystemExit()
11
+
12
+ clear_config()
13
+ console.print(f"[{SUCCESS}]Logged out successfully.[/{SUCCESS}]")
@@ -0,0 +1,11 @@
1
+ from clutch_cli.config import get_username
2
+ from clutch_cli.theme import console, ACCENT, WARNING
3
+
4
+
5
+ def whoami():
6
+ """Show the currently logged-in user."""
7
+ username = get_username()
8
+ if not username:
9
+ console.print(f"[{WARNING}]Not logged in. Run: clutch login[/{WARNING}]")
10
+ raise SystemExit()
11
+ console.print(f"[{ACCENT}]Logged in as @{username}[/{ACCENT}]")
File without changes
@@ -1,11 +1,9 @@
1
1
  import typer
2
- from rich.console import Console
3
2
  from rich.table import Table
4
3
  from rich.text import Text
5
4
  from rich import box
6
5
  from clutch_cli.api import get_client
7
-
8
- console = Console()
6
+ from clutch_cli.theme import console, DIM, SUCCESS, WARNING, header, footer
9
7
 
10
8
 
11
9
  def insight():
@@ -13,51 +11,45 @@ def insight():
13
11
  with get_client() as client:
14
12
  try:
15
13
  console.print()
16
- console.print("[dim]Generating insight...[/dim]")
14
+ console.print(f"[{DIM}]Generating insight...[/{DIM}]")
17
15
 
18
16
  response = client.get("/insights/weekly")
19
17
  if response.status_code != 200:
20
- console.print("[red]Error: Failed to fetch insight.[/red]")
18
+ console.print("[bold red]Error: Failed to fetch insight.[/bold red]")
21
19
  raise typer.Exit(1)
22
20
 
23
21
  data = response.json()
24
22
 
25
23
  if "message" in data:
26
- console.print(f"[yellow]{data['message']}[/yellow]")
24
+ console.print(f"[{WARNING}]{data['message']}[/{WARNING}]")
27
25
  raise typer.Exit()
28
26
 
29
27
  stats = data["stats"]
30
28
  summary = data["ai_summary"]
31
29
 
32
- console.print()
33
- console.rule("[bold blue]⚡ CLUTCH — WEEKLY INSIGHT[/bold blue]")
34
- console.print()
30
+ header("WEEKLY INSIGHT")
35
31
 
36
- # AI summary block
37
- console.print(f"[dim]Week of[/dim] [white]{data['week_start']}[/white]")
32
+ console.print(f"[{DIM}]Week of[/{DIM}] [bold white]{data['week_start']}[/bold white]")
38
33
  console.print()
39
34
  console.print(Text(summary, style="white"), soft_wrap=True)
40
35
  console.print()
41
- console.rule(style="dim")
36
+ console.rule(style=DIM)
42
37
  console.print()
43
38
 
44
- # Stats row
45
39
  table = Table(box=box.SIMPLE, show_header=False, pad_edge=False)
46
- table.add_column("Label", style="dim", width=22)
40
+ table.add_column("Label", style=DIM, width=22)
47
41
  table.add_column("Value", style="bold white")
48
42
 
49
- table.add_row("Commits", f"[green]{stats['total_commits']}[/green]")
50
- table.add_row("Active Days", f"[green]{stats['active_days']}[/green]")
43
+ table.add_row("Commits", f"[{SUCCESS}]{stats['total_commits']}[/{SUCCESS}]")
44
+ table.add_row("Active Days", f"[{SUCCESS}]{stats['active_days']}[/{SUCCESS}]")
51
45
  table.add_row("Pull Requests", str(stats["total_prs"]))
52
46
  table.add_row("Issues", str(stats["total_issues"]))
53
47
  table.add_row("Best Day", str(stats["best_day"]))
54
- table.add_row("Generated by", f"[dim]{data['generated_by']}[/dim]")
48
+ table.add_row("Generated by", f"[{DIM}]{data['generated_by']}[/{DIM}]")
55
49
 
56
50
  console.print(table)
57
- console.print()
58
- console.rule(style="dim")
59
- console.print()
51
+ footer()
60
52
 
61
53
  except Exception:
62
- console.print("[red]Error: Could not connect to Clutch API.[/red]")
54
+ console.print("[bold red]Error: Could not connect to Clutch API.[/bold red]")
63
55
  raise typer.Exit(1)
@@ -0,0 +1,59 @@
1
+ import typer
2
+
3
+ from clutch_cli.authentication import login, logout, whoami
4
+ from clutch_cli.activity import streak, stats, patterns
5
+ from clutch_cli.repositories import list as repositories_list
6
+ from clutch_cli.insights import weekly
7
+ from clutch_cli.system import status
8
+
9
+ __version__ = "0.3.0"
10
+
11
+ app = typer.Typer(
12
+ name="clutch",
13
+ help="GitHub tracks your work. Clutch tracks you.",
14
+ no_args_is_help=True,
15
+ )
16
+
17
+
18
+ def _version_callback(value: bool):
19
+ if value:
20
+ typer.echo(f"clutch v{__version__}")
21
+ raise typer.Exit()
22
+
23
+
24
+ @app.callback()
25
+ def main(
26
+ version: bool = typer.Option(
27
+ None,
28
+ "--version",
29
+ "-v",
30
+ help="Show version and exit.",
31
+ callback=_version_callback,
32
+ is_eager=True,
33
+ ),
34
+ ):
35
+ pass
36
+
37
+
38
+ # Authentication
39
+ app.command(name="login")(login.login)
40
+ app.command(name="logout")(logout.logout)
41
+ app.command(name="whoami")(whoami.whoami)
42
+
43
+ # Activity
44
+ app.command(name="streak")(streak.streak)
45
+ app.command(name="stats")(stats.stats)
46
+ app.command(name="patterns")(patterns.patterns)
47
+
48
+ # Repositories
49
+ app.command(name="repos")(repositories_list.repos)
50
+
51
+ # Insights
52
+ app.command(name="insight")(weekly.insight)
53
+
54
+ # System
55
+ app.command(name="status")(status.status)
56
+
57
+
58
+ if __name__ == "__main__":
59
+ app()
File without changes
@@ -1,24 +1,8 @@
1
1
  import typer
2
- from rich.console import Console
3
2
  from rich.table import Table
4
3
  from rich import box
5
4
  from clutch_cli.api import get_client
6
-
7
- console = Console()
8
-
9
- LANG_COLORS = {
10
- "Python": "blue",
11
- "TypeScript": "cyan",
12
- "JavaScript": "yellow",
13
- "Go": "cyan",
14
- "Rust": "red",
15
- "Java": "red",
16
- "C++": "blue",
17
- "C": "blue",
18
- "Shell": "green",
19
- "HTML": "red",
20
- "CSS": "blue",
21
- }
5
+ from clutch_cli.theme import console, ACCENT, DIM, WARNING, header, footer
22
6
 
23
7
 
24
8
  def repos():
@@ -27,41 +11,36 @@ def repos():
27
11
  try:
28
12
  response = client.get("/github/repos")
29
13
  if response.status_code != 200:
30
- console.print("[red]Error: Failed to fetch repositories.[/red]")
14
+ console.print("[bold red]Error: Failed to fetch repositories.[/bold red]")
31
15
  raise typer.Exit(1)
32
16
 
33
17
  repos_data = response.json()
34
18
 
35
- console.print()
36
- console.rule("[bold blue]⚡ CLUTCH — REPOSITORIES[/bold blue]")
37
- console.print()
19
+ header("REPOSITORIES")
38
20
 
39
21
  table = Table(box=box.SIMPLE, show_header=True, pad_edge=False)
40
22
  table.add_column("Repository", style="bold white", width=28)
41
23
  table.add_column("Language", width=14)
42
24
  table.add_column("Stars", justify="right", width=7)
43
25
  table.add_column("Forks", justify="right", width=7)
44
- table.add_column("Updated", style="dim", width=12)
26
+ table.add_column("Updated", style=DIM, width=12)
45
27
 
46
28
  for repo in repos_data[:10]:
47
29
  lang = repo.get("language") or "—"
48
- lang_color = LANG_COLORS.get(lang, "white")
49
30
  stars = repo.get("stargazers_count", 0)
50
31
  forks = repo.get("forks_count", 0)
51
32
 
52
33
  table.add_row(
53
34
  repo["name"],
54
- f"[{lang_color}]{lang}[/{lang_color}]",
55
- f"[yellow]{stars}[/yellow]" if stars > 0 else "[dim]0[/dim]",
56
- str(forks) if forks > 0 else "[dim]0[/dim]",
35
+ lang,
36
+ f"[{WARNING}]{stars}[/{WARNING}]" if stars > 0 else f"[{DIM}]0[/{DIM}]",
37
+ str(forks) if forks > 0 else f"[{DIM}]0[/{DIM}]",
57
38
  repo["updated_at"][:10],
58
39
  )
59
40
 
60
41
  console.print(table)
61
- console.print()
62
- console.rule(style="dim")
63
- console.print()
42
+ footer()
64
43
 
65
44
  except Exception:
66
- console.print("[red]Error: Could not connect to Clutch API.[/red]")
45
+ console.print("[bold red]Error: Could not connect to Clutch API.[/bold red]")
67
46
  raise typer.Exit(1)
File without changes
@@ -0,0 +1,45 @@
1
+ import httpx
2
+ from rich.table import Table
3
+ from rich import box
4
+ from clutch_cli.config import API_BASE_URL, get_token, get_username
5
+ from clutch_cli.theme import console, ACCENT, DIM, SUCCESS, ERROR, WARNING, header, footer
6
+
7
+
8
+ def status():
9
+ """Show login status and API health."""
10
+ username = get_username()
11
+ token = get_token()
12
+
13
+ header("STATUS")
14
+
15
+ table = Table(box=box.SIMPLE, show_header=False, pad_edge=False)
16
+ table.add_column("Check", style=DIM, width=18)
17
+ table.add_column("Result", style="bold white")
18
+
19
+ if not username or not token:
20
+ table.add_row("Auth", f"[{ERROR}]Not logged in[/{ERROR}]")
21
+ table.add_row("Hint", f"[{DIM}]Run: clutch login[/{DIM}]")
22
+ console.print(table)
23
+ footer()
24
+ raise SystemExit()
25
+
26
+ table.add_row("User", f"[{ACCENT}]@{username}[/{ACCENT}]")
27
+
28
+ try:
29
+ response = httpx.get(
30
+ f"{API_BASE_URL}/users/me",
31
+ headers={"Authorization": f"Bearer {token}"},
32
+ timeout=8,
33
+ )
34
+ if response.status_code == 200:
35
+ table.add_row("Token", f"[{SUCCESS}]Valid[/{SUCCESS}]")
36
+ table.add_row("API", f"[{SUCCESS}]Reachable[/{SUCCESS}] [{DIM}]{API_BASE_URL}[/{DIM}]")
37
+ else:
38
+ table.add_row("Token", f"[{WARNING}]Expired ({response.status_code})[/{WARNING}]")
39
+ table.add_row("Hint", f"[{DIM}]Run: clutch login[/{DIM}]")
40
+ except httpx.RequestError:
41
+ table.add_row("Token", f"[{SUCCESS}]Saved[/{SUCCESS}]")
42
+ table.add_row("API", f"[{ERROR}]Unreachable[/{ERROR}]")
43
+
44
+ console.print(table)
45
+ footer()
@@ -0,0 +1,42 @@
1
+ """Shared visual theme for all Clutch CLI output.
2
+
3
+ One bold, monochrome accent system — no rainbow, no gradients.
4
+ Primary: bold white. Accent: bold (terminal default bright). Dim: grey.
5
+ Status: green (positive only), red (errors only), yellow (warnings only).
6
+ """
7
+
8
+ from rich.console import Console
9
+
10
+ console = Console()
11
+
12
+ # Text styles
13
+ PRIMARY = "bold white"
14
+ ACCENT = "bold" # bright/bold default terminal color — reads as black/white bold
15
+ DIM = "dim"
16
+ SUCCESS = "bold green"
17
+ ERROR = "bold red"
18
+ WARNING = "bold yellow"
19
+
20
+ BRAND = "⚡ CLUTCH"
21
+
22
+
23
+ def header(title: str) -> None:
24
+ """Print the standard Clutch section header."""
25
+ console.print()
26
+ console.rule(f"[{ACCENT}]{BRAND} — {title.upper()}[/{ACCENT}]")
27
+ console.print()
28
+
29
+
30
+ def footer() -> None:
31
+ """Print the standard Clutch section footer."""
32
+ console.print()
33
+ console.rule(style=DIM)
34
+ console.print()
35
+
36
+
37
+ def bar(value: float, max_value: float, width: int = 28) -> str:
38
+ """Render a solid/empty block bar, bold white filled + dim empty."""
39
+ max_value = max_value or 1
40
+ filled = int((value / max_value) * width)
41
+ filled = max(0, min(width, filled))
42
+ return f"[{ACCENT}]{'█' * filled}[/{ACCENT}][{DIM}]{'░' * (width - filled)}[/{DIM}]"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: clutch-cli
3
- Version: 0.2.0
3
+ Version: 0.3.0
4
4
  Summary: GitHub tracks your work. Clutch tracks you.
5
5
  Author-email: Lay Patel <lay.patel.1313@gmail.com>
6
6
  License: MIT
@@ -35,7 +35,7 @@ Requires-Dist: rich>=13.0.0
35
35
 
36
36
  ---
37
37
 
38
- `clutch-cli` is the terminal companion for [Clutch](https://clutch-laypatel.netlify.app) — an open-source AI-powered developer activity dashboard. Get your GitHub streaks, stats, coding patterns, and AI insights without leaving your terminal.
38
+ `clutch-cli` is the terminal companion for [Clutch](https://clutch-woad.vercel.app) — an open-source AI-powered developer activity dashboard. Get your GitHub streaks, stats, coding patterns, and AI insights without leaving your terminal.
39
39
 
40
40
  ## Installation
41
41
 
@@ -47,7 +47,7 @@ pip install clutch-cli
47
47
 
48
48
  ```bash
49
49
  # Login via GitHub (opens browser automatically, no copy-paste needed)
50
- clutch auth login
50
+ clutch login
51
51
 
52
52
  # Check your streak
53
53
  clutch streak
@@ -63,9 +63,9 @@ clutch insight
63
63
 
64
64
  | Command | Description |
65
65
  |---|---|
66
- | `clutch auth login` | Login via GitHub OAuth (fully automatic) |
67
- | `clutch auth logout` | Logout and clear saved credentials |
68
- | `clutch auth whoami` | Show currently logged-in user |
66
+ | `clutch login` | Login via GitHub OAuth (fully automatic) |
67
+ | `clutch logout` | Logout and clear saved credentials |
68
+ | `clutch whoami` | Show currently logged-in user |
69
69
  | `clutch streak` | Current and longest commit streak |
70
70
  | `clutch stats` | Activity stats — commits, PRs, issues, active days |
71
71
  | `clutch stats --days 7` | Stats for a custom time range |
@@ -77,10 +77,10 @@ clutch insight
77
77
 
78
78
  ## How Login Works
79
79
 
80
- `clutch auth login` spins up a temporary local server on port `9876`, opens GitHub OAuth in your browser, and automatically captures the token when GitHub redirects back. No copy-pasting required.
80
+ `clutch login` spins up a temporary local server on port `9876`, opens GitHub OAuth in your browser, and automatically captures the token when GitHub redirects back. No copy-pasting required.
81
81
 
82
82
  ```
83
- $ clutch auth login
83
+ $ clutch login
84
84
 
85
85
  ⚡ Clutch Login
86
86
  Opening GitHub in your browser...
@@ -98,12 +98,12 @@ By default the CLI talks to the hosted Clutch API. To point it at a local backen
98
98
 
99
99
  ```bash
100
100
  export CLUTCH_API_URL=http://localhost:8000
101
- clutch auth login
101
+ clutch login
102
102
  ```
103
103
 
104
104
  ## Links
105
105
 
106
- - 🌐 [Live Dashboard](https://clutch-laypatel.netlify.app)
106
+ - 🌐 [Live Dashboard](https://clutch-woad.vercel.app)
107
107
  - 📖 [Full Documentation](https://github.com/laypatel13/clutch)
108
108
  - 🐛 [Report a Bug](https://github.com/laypatel13/clutch/issues)
109
109
 
@@ -0,0 +1,27 @@
1
+ README.md
2
+ pyproject.toml
3
+ clutch_cli/__init__.py
4
+ clutch_cli/api.py
5
+ clutch_cli/config.py
6
+ clutch_cli/main.py
7
+ clutch_cli/theme.py
8
+ clutch_cli.egg-info/PKG-INFO
9
+ clutch_cli.egg-info/SOURCES.txt
10
+ clutch_cli.egg-info/dependency_links.txt
11
+ clutch_cli.egg-info/entry_points.txt
12
+ clutch_cli.egg-info/requires.txt
13
+ clutch_cli.egg-info/top_level.txt
14
+ clutch_cli/activity/__init__.py
15
+ clutch_cli/activity/patterns.py
16
+ clutch_cli/activity/stats.py
17
+ clutch_cli/activity/streak.py
18
+ clutch_cli/authentication/__init__.py
19
+ clutch_cli/authentication/login.py
20
+ clutch_cli/authentication/logout.py
21
+ clutch_cli/authentication/whoami.py
22
+ clutch_cli/insights/__init__.py
23
+ clutch_cli/insights/weekly.py
24
+ clutch_cli/repositories/__init__.py
25
+ clutch_cli/repositories/list.py
26
+ clutch_cli/system/__init__.py
27
+ clutch_cli/system/status.py
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "clutch-cli"
7
- version = "0.2.0"
7
+ version = "0.3.0"
8
8
  description = "GitHub tracks your work. Clutch tracks you."
9
9
  readme = "README.md"
10
10
  license = { text = "MIT" }
@@ -1,132 +0,0 @@
1
- import webbrowser
2
- from http.server import BaseHTTPRequestHandler, HTTPServer
3
- from urllib.parse import parse_qs, urlparse
4
-
5
- import httpx
6
- import typer
7
- from rich.console import Console
8
-
9
- from clutch_cli.config import API_BASE_URL, clear_config, get_username, save_token
10
-
11
- app = typer.Typer(help="Authentication commands.")
12
- console = Console()
13
-
14
- CLI_CALLBACK_PORT = 9876
15
- _captured_token: dict = {}
16
-
17
-
18
- class _CallbackHandler(BaseHTTPRequestHandler):
19
- """Minimal HTTP handler that captures the JWT from the OAuth redirect."""
20
-
21
- def do_GET(self):
22
- parsed = urlparse(self.path)
23
- if parsed.path == "/callback":
24
- params = parse_qs(parsed.query)
25
- token = params.get("token", [None])[0]
26
- if token:
27
- _captured_token["value"] = token
28
- self._respond(200, _success_page())
29
- else:
30
- self._respond(400, _error_page("No token received."))
31
- else:
32
- self._respond(404, b"Not found")
33
-
34
- def _respond(self, status: int, body: bytes) -> None:
35
- self.send_response(status)
36
- self.send_header("Content-Type", "text/html; charset=utf-8")
37
- self.end_headers()
38
- self.wfile.write(body)
39
-
40
- def log_message(self, *args):
41
- pass # silence default request logging
42
-
43
-
44
- def _success_page() -> bytes:
45
- return b"""<!DOCTYPE html>
46
- <html>
47
- <head><title>Clutch Login</title></head>
48
- <body style="font-family:sans-serif;text-align:center;padding:60px;background:#0d1117;color:#58a6ff">
49
- <h1>&#9889; Clutch</h1>
50
- <p style="color:#3fb950;font-size:1.2rem">&#10003; Login successful! You can close this tab.</p>
51
- </body>
52
- </html>"""
53
-
54
-
55
- def _error_page(msg: str) -> bytes:
56
- return f"""<!DOCTYPE html>
57
- <html>
58
- <head><title>Clutch Login Error</title></head>
59
- <body style="font-family:sans-serif;text-align:center;padding:60px;background:#0d1117;color:#f85149">
60
- <h1>&#9889; Clutch</h1>
61
- <p>Login failed: {msg}</p>
62
- </body>
63
- </html>""".encode()
64
-
65
-
66
- @app.command()
67
- def login():
68
- """Login to Clutch via GitHub OAuth (browser-based, fully automatic)."""
69
- console.print("\n[bold green]⚡ Clutch Login[/bold green]")
70
- console.print("[dim]Starting local callback listener...[/dim]")
71
-
72
- _captured_token.clear()
73
- server = HTTPServer(("localhost", CLI_CALLBACK_PORT), _CallbackHandler)
74
-
75
- login_url = f"{API_BASE_URL}/auth/github?cli=true"
76
- console.print("[dim]Opening GitHub in your browser...[/dim]\n")
77
- webbrowser.open(login_url)
78
-
79
- console.print("[yellow]Waiting for GitHub authorization...[/yellow]")
80
- console.print("[dim](If your browser didn't open, visit:)[/dim]")
81
- console.print(f"[dim]{login_url}[/dim]\n")
82
-
83
- # Block on main thread until the OAuth callback hits localhost:9876/callback
84
- server.handle_request()
85
- server.server_close()
86
-
87
- token = _captured_token.get("value")
88
- if not token:
89
- console.print("[red]❌ Login failed — no token received.[/red]")
90
- raise typer.Exit(1)
91
-
92
- # Verify token and fetch user info
93
- try:
94
- response = httpx.get(
95
- f"{API_BASE_URL}/users/me",
96
- headers={"Authorization": f"Bearer {token}"},
97
- timeout=10,
98
- )
99
- if response.status_code != 200:
100
- console.print("[red]❌ Token validation failed.[/red]")
101
- raise typer.Exit(1)
102
-
103
- user = response.json()
104
- save_token(token, user["username"])
105
- console.print(f"[bold green]✅ Logged in as @{user['username']}[/bold green]")
106
- console.print(f"[dim]Welcome to Clutch, {user.get('name') or user['username']}![/dim]\n")
107
-
108
- except httpx.RequestError:
109
- console.print("[red]❌ Could not connect to Clutch API.[/red]")
110
- raise typer.Exit(1)
111
-
112
-
113
- @app.command()
114
- def logout():
115
- """Logout from Clutch."""
116
- username = get_username()
117
- if not username:
118
- console.print("[yellow]You are not logged in.[/yellow]")
119
- raise typer.Exit()
120
-
121
- clear_config()
122
- console.print("[bold green]✅ Logged out successfully.[/bold green]")
123
-
124
-
125
- @app.command()
126
- def whoami():
127
- """Show the currently logged-in user."""
128
- username = get_username()
129
- if not username:
130
- console.print("[yellow]Not logged in. Run: clutch auth login[/yellow]")
131
- raise typer.Exit()
132
- console.print(f"[bold green]Logged in as @{username}[/bold green]")
@@ -1,43 +0,0 @@
1
- import typer
2
- from clutch_cli import auth, streak, stats, insight, repos, patterns, status
3
-
4
- __version__ = "0.2.0"
5
-
6
- app = typer.Typer(
7
- name="clutch",
8
- help="GitHub tracks your work. Clutch tracks you.",
9
- no_args_is_help=True,
10
- )
11
-
12
-
13
- def _version_callback(value: bool):
14
- if value:
15
- typer.echo(f"clutch v{__version__}")
16
- raise typer.Exit()
17
-
18
-
19
- @app.callback()
20
- def main(
21
- version: bool = typer.Option(
22
- None,
23
- "--version",
24
- "-v",
25
- help="Show version and exit.",
26
- callback=_version_callback,
27
- is_eager=True,
28
- ),
29
- ):
30
- pass
31
-
32
-
33
- app.add_typer(auth.app, name="auth")
34
- app.command()(streak.streak)
35
- app.command()(stats.stats)
36
- app.command()(insight.insight)
37
- app.command()(repos.repos)
38
- app.command()(patterns.patterns)
39
- app.command()(status.status)
40
-
41
-
42
- if __name__ == "__main__":
43
- app()
@@ -1,54 +0,0 @@
1
- import httpx
2
- import typer
3
- from rich.console import Console
4
- from rich.table import Table
5
- from rich import box
6
- from clutch_cli.config import API_BASE_URL, get_token, get_username
7
-
8
- console = Console()
9
-
10
-
11
- def status():
12
- """Show login status and API health."""
13
- username = get_username()
14
- token = get_token()
15
-
16
- console.print()
17
- console.rule("[bold blue]⚡ CLUTCH — STATUS[/bold blue]")
18
- console.print()
19
-
20
- table = Table(box=box.SIMPLE, show_header=False, pad_edge=False)
21
- table.add_column("Check", style="dim", width=18)
22
- table.add_column("Result", style="bold white")
23
-
24
- if not username or not token:
25
- table.add_row("Auth", "[red]Not logged in[/red]")
26
- table.add_row("Hint", "[dim]Run: clutch auth login[/dim]")
27
- console.print(table)
28
- console.print()
29
- console.rule(style="dim")
30
- console.print()
31
- raise typer.Exit()
32
-
33
- table.add_row("User", f"[blue]@{username}[/blue]")
34
-
35
- try:
36
- response = httpx.get(
37
- f"{API_BASE_URL}/users/me",
38
- headers={"Authorization": f"Bearer {token}"},
39
- timeout=8,
40
- )
41
- if response.status_code == 200:
42
- table.add_row("Token", "[green]Valid[/green]")
43
- table.add_row("API", f"[green]Reachable[/green] [dim]{API_BASE_URL}[/dim]")
44
- else:
45
- table.add_row("Token", f"[yellow]Expired ({response.status_code})[/yellow]")
46
- table.add_row("Hint", "[dim]Run: clutch auth login[/dim]")
47
- except httpx.RequestError:
48
- table.add_row("Token", "[green]Saved[/green]")
49
- table.add_row("API", "[red]Unreachable[/red]")
50
-
51
- console.print(table)
52
- console.print()
53
- console.rule(style="dim")
54
- console.print()
@@ -1,80 +0,0 @@
1
- import typer
2
- from rich.console import Console
3
- from rich.table import Table
4
- from rich import box
5
- from clutch_cli.api import get_client
6
-
7
- console = Console()
8
-
9
-
10
- def streak():
11
- """Show your current and longest commit streak."""
12
- with get_client() as client:
13
- try:
14
- response = client.get("/github/streak")
15
- if response.status_code != 200:
16
- console.print("[red]Error: Failed to fetch streak data.[/red]")
17
- raise typer.Exit(1)
18
-
19
- data = response.json()
20
- current = data["current_streak"]
21
- longest = data["longest_streak"]
22
- total_active = data["total_active_days"]
23
-
24
- if current >= 30:
25
- streak_color = "bold green"
26
- status = "ON FIRE"
27
- elif current >= 14:
28
- streak_color = "bold green"
29
- status = "STRONG"
30
- elif current >= 7:
31
- streak_color = "bold blue"
32
- status = "BUILDING"
33
- elif current > 0:
34
- streak_color = "bold yellow"
35
- status = "ACTIVE"
36
- else:
37
- streak_color = "dim"
38
- status = "INACTIVE"
39
-
40
- console.print()
41
- console.rule("[bold blue]⚡ CLUTCH — STREAK[/bold blue]")
42
- console.print()
43
-
44
- table = Table(box=box.SIMPLE, show_header=False, pad_edge=False)
45
- table.add_column("Label", style="dim", width=22)
46
- table.add_column("Value", style="bold white")
47
- table.add_column("Badge", justify="right")
48
-
49
- table.add_row(
50
- "Current Streak",
51
- f"[{streak_color}]{current} DAY'S[/{streak_color}]",
52
- f"[blue]{status}[/blue]",
53
- )
54
- table.add_row(
55
- "Longest Streak",
56
- f"{longest} DAY'S",
57
- "",
58
- )
59
- table.add_row(
60
- "Total Active Days",
61
- f"{total_active} DAY'S",
62
- "",
63
- )
64
-
65
- # Visual bar: current vs longest
66
- if longest > 0:
67
- filled = int((current / longest) * 30)
68
- bar = "█" * filled + "░" * (30 - filled)
69
- console.print(table)
70
- console.print(f" [dim]Progress to longest[/dim] [blue]{bar}[/blue] [dim]{current}/{longest}[/dim]")
71
- else:
72
- console.print(table)
73
-
74
- console.print()
75
- console.rule(style="dim")
76
- console.print()
77
-
78
- except Exception:
79
- console.print("[red]Error: Could not connect to Clutch API.[/red]")
80
- raise typer.Exit(1)
@@ -1,19 +0,0 @@
1
- README.md
2
- pyproject.toml
3
- clutch_cli/__init__.py
4
- clutch_cli/api.py
5
- clutch_cli/auth.py
6
- clutch_cli/config.py
7
- clutch_cli/insight.py
8
- clutch_cli/main.py
9
- clutch_cli/patterns.py
10
- clutch_cli/repos.py
11
- clutch_cli/stats.py
12
- clutch_cli/status.py
13
- clutch_cli/streak.py
14
- clutch_cli.egg-info/PKG-INFO
15
- clutch_cli.egg-info/SOURCES.txt
16
- clutch_cli.egg-info/dependency_links.txt
17
- clutch_cli.egg-info/entry_points.txt
18
- clutch_cli.egg-info/requires.txt
19
- clutch_cli.egg-info/top_level.txt
File without changes
File without changes