recon-github 0.1.7__tar.gz → 1.0.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.
- recon_github-0.1.7/.agents/rich-ui-architect.md → recon_github-1.0.0/.github/agents/rich-ui-architect.agent.md +5 -0
- {recon_github-0.1.7 → recon_github-1.0.0}/PKG-INFO +1 -1
- {recon_github-0.1.7 → recon_github-1.0.0}/app/commands/repo.py +2 -40
- {recon_github-0.1.7 → recon_github-1.0.0}/app/ui/repo.py +76 -64
- {recon_github-0.1.7 → recon_github-1.0.0}/pyproject.toml +1 -1
- {recon_github-0.1.7 → recon_github-1.0.0}/tests/test_cli_commands.py +39 -3
- {recon_github-0.1.7 → recon_github-1.0.0}/uv.lock +1 -1
- {recon_github-0.1.7 → recon_github-1.0.0}/.github/workflows/cd.yml +0 -0
- {recon_github-0.1.7 → recon_github-1.0.0}/.github/workflows/ci.yml +0 -0
- {recon_github-0.1.7 → recon_github-1.0.0}/.gitignore +0 -0
- {recon_github-0.1.7 → recon_github-1.0.0}/.python-version +0 -0
- {recon_github-0.1.7 → recon_github-1.0.0}/LICENSE +0 -0
- {recon_github-0.1.7 → recon_github-1.0.0}/README.md +0 -0
- {recon_github-0.1.7 → recon_github-1.0.0}/ROADMAP.md +0 -0
- {recon_github-0.1.7 → recon_github-1.0.0}/app/cli.py +0 -0
- {recon_github-0.1.7 → recon_github-1.0.0}/app/commands/auth.py +0 -0
- {recon_github-0.1.7 → recon_github-1.0.0}/app/commands/me.py +0 -0
- {recon_github-0.1.7 → recon_github-1.0.0}/app/services/auth.py +0 -0
- {recon_github-0.1.7 → recon_github-1.0.0}/app/services/github.py +0 -0
- {recon_github-0.1.7 → recon_github-1.0.0}/app/services/github_errors.py +0 -0
- {recon_github-0.1.7 → recon_github-1.0.0}/app/services/storage.py +0 -0
- {recon_github-0.1.7 → recon_github-1.0.0}/app/ui/avatar.py +0 -0
- {recon_github-0.1.7 → recon_github-1.0.0}/app/ui/display.py +0 -0
- {recon_github-0.1.7 → recon_github-1.0.0}/main.py +0 -0
- {recon_github-0.1.7 → recon_github-1.0.0}/tests/test_auth_service.py +0 -0
- {recon_github-0.1.7 → recon_github-1.0.0}/tests/test_cli.py +0 -0
- {recon_github-0.1.7 → recon_github-1.0.0}/tests/test_github_errors.py +0 -0
- {recon_github-0.1.7 → recon_github-1.0.0}/tests/test_github_service.py +0 -0
- {recon_github-0.1.7 → recon_github-1.0.0}/tests/test_repo_helpers.py +0 -0
- {recon_github-0.1.7 → recon_github-1.0.0}/tests/test_storage.py +0 -0
- {recon_github-0.1.7 → recon_github-1.0.0}/tests/test_ui_repo.py +0 -0
|
@@ -1,3 +1,8 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: rich-ui-architect
|
|
3
|
+
description: "Design and implement consistent, reusable Rich terminal UI for the Recon CLI."
|
|
4
|
+
---
|
|
5
|
+
|
|
1
6
|
# Recon CLI — UI/UX & Rich Component Agent
|
|
2
7
|
|
|
3
8
|
You are the UI/UX and terminal-interface agent for **Recon CLI**, a Python CLI application for exploring GitHub profiles and repositories.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: recon-github
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 1.0.0
|
|
4
4
|
Summary: Recon is a powerful CLI companion for developers and tools for agents, turning GitHub activity and repository data into useful insights from your terminal
|
|
5
5
|
License-File: LICENSE
|
|
6
6
|
Requires-Python: >=3.12
|
|
@@ -105,7 +105,6 @@ def details(
|
|
|
105
105
|
get_repo_details,
|
|
106
106
|
get_top_contributors,
|
|
107
107
|
)
|
|
108
|
-
from app.ui.repo import display_view_header
|
|
109
108
|
|
|
110
109
|
details = get_repo_details(owner, repo)
|
|
111
110
|
|
|
@@ -153,43 +152,6 @@ def details(
|
|
|
153
152
|
get_top_contributors(owner, repo, limit=5),
|
|
154
153
|
)
|
|
155
154
|
|
|
156
|
-
|
|
155
|
+
from app.ui.repo import display_repo_details
|
|
157
156
|
|
|
158
|
-
|
|
159
|
-
typer.echo("Repository")
|
|
160
|
-
typer.echo("-----------")
|
|
161
|
-
typer.echo(f"{details.get('full_name', f'{owner}/{repo}')}") # Name
|
|
162
|
-
description = details.get("description") or "—"
|
|
163
|
-
typer.echo(f"description: {format_description(description)}") # Description
|
|
164
|
-
typer.echo(
|
|
165
|
-
f"visibility: {details.get('visibility') or ('private' if details.get('private') else 'public')}" # Visibility
|
|
166
|
-
)
|
|
167
|
-
typer.echo(
|
|
168
|
-
f"url: {details.get('html_url') or f'https://github.com/{owner}/{repo}'}"
|
|
169
|
-
) # URL
|
|
170
|
-
|
|
171
|
-
# Stats
|
|
172
|
-
typer.echo("")
|
|
173
|
-
typer.echo("Stats")
|
|
174
|
-
typer.echo("-----------")
|
|
175
|
-
typer.echo(f"stars: {details.get('stargazers_count') or 0}") # Stars
|
|
176
|
-
typer.echo(f"forks: {details.get('forks_count')}") # Forks
|
|
177
|
-
typer.echo(f"issues: {details.get('open_issues_count') or 0}") # Issues
|
|
178
|
-
typer.echo(f"size: {format_size(details.get('size'))}") # Size
|
|
179
|
-
|
|
180
|
-
typer.echo("")
|
|
181
|
-
typer.echo("Contributions")
|
|
182
|
-
typer.echo("-----------")
|
|
183
|
-
if not contributions:
|
|
184
|
-
typer.echo("No contributor data returned.")
|
|
185
|
-
else:
|
|
186
|
-
for contributor in contributions:
|
|
187
|
-
typer.echo(f"{contributor['login']}: {contributor['commits']} commits")
|
|
188
|
-
|
|
189
|
-
# Tech
|
|
190
|
-
typer.echo("")
|
|
191
|
-
typer.echo("Languages")
|
|
192
|
-
typer.echo("---------")
|
|
193
|
-
|
|
194
|
-
for language in format_languages(language_data):
|
|
195
|
-
typer.echo(language)
|
|
157
|
+
display_repo_details(details, language_data, contributions)
|
|
@@ -187,7 +187,7 @@ def format_bytes(size: int | None) -> str:
|
|
|
187
187
|
|
|
188
188
|
def display_repo_details(
|
|
189
189
|
details: dict,
|
|
190
|
-
languages:
|
|
190
|
+
languages: dict[str, int],
|
|
191
191
|
contributions: list[dict],
|
|
192
192
|
):
|
|
193
193
|
"""Display a repository overview."""
|
|
@@ -197,70 +197,51 @@ def display_repo_details(
|
|
|
197
197
|
visibility = details.get("visibility") or (
|
|
198
198
|
"private" if details.get("private") else "public"
|
|
199
199
|
)
|
|
200
|
-
primary_language = details.get("language") or "—"
|
|
201
200
|
url = details.get("html_url") or "—"
|
|
202
201
|
|
|
203
202
|
header_panel = build_view_header("Details", full_name)
|
|
204
203
|
|
|
205
|
-
# Statistics
|
|
206
|
-
stats = Table(
|
|
207
|
-
show_header=False,
|
|
208
|
-
box=None,
|
|
209
|
-
pad_edge=False,
|
|
210
|
-
)
|
|
211
|
-
|
|
212
|
-
stats.add_column("Metric", style="dim")
|
|
213
|
-
stats.add_column("Value", justify="right")
|
|
214
|
-
|
|
215
|
-
stats.add_row(
|
|
216
|
-
"★ Stars",
|
|
217
|
-
str(details.get("stargazers_count") or 0),
|
|
218
|
-
)
|
|
219
|
-
stats.add_row(
|
|
220
|
-
"⑂ Forks",
|
|
221
|
-
str(details.get("forks_count") or 0),
|
|
222
|
-
)
|
|
223
|
-
stats.add_row(
|
|
224
|
-
"! Issues",
|
|
225
|
-
str(details.get("open_issues_count") or 0),
|
|
226
|
-
)
|
|
227
|
-
stats.add_row(
|
|
228
|
-
"Size",
|
|
229
|
-
format_size(details.get("size")),
|
|
230
|
-
)
|
|
231
|
-
|
|
232
|
-
stats_panel = Panel(
|
|
233
|
-
stats,
|
|
234
|
-
title="Statistics",
|
|
235
|
-
expand=False,
|
|
236
|
-
)
|
|
237
|
-
|
|
238
204
|
# Contributions
|
|
239
205
|
contribution_table = Table(
|
|
240
206
|
show_header=False,
|
|
241
207
|
box=None,
|
|
242
208
|
pad_edge=False,
|
|
209
|
+
padding=(0, 1),
|
|
243
210
|
)
|
|
244
211
|
|
|
245
212
|
contribution_table.add_column("Contributor")
|
|
246
213
|
contribution_table.add_column("Commits", justify="right")
|
|
214
|
+
contribution_table.add_column("Share", justify="right")
|
|
247
215
|
|
|
248
|
-
|
|
249
|
-
|
|
216
|
+
ranked_contributions = contributions[:5]
|
|
217
|
+
if ranked_contributions:
|
|
218
|
+
total_commits = sum(
|
|
219
|
+
int(contributor.get("commits") or 0) for contributor in ranked_contributions
|
|
220
|
+
)
|
|
221
|
+
for index, contributor in enumerate(ranked_contributions):
|
|
222
|
+
commits = int(contributor.get("commits") or 0)
|
|
223
|
+
percentage = commits / total_commits * 100 if total_commits else 0
|
|
250
224
|
contribution_table.add_row(
|
|
251
|
-
contributor
|
|
252
|
-
|
|
225
|
+
str(contributor.get("login") or "Unknown"),
|
|
226
|
+
_format_count(commits),
|
|
227
|
+
_overview_bar_label(
|
|
228
|
+
percentage, spaced=index < len(ranked_contributions) - 1
|
|
229
|
+
),
|
|
253
230
|
)
|
|
254
231
|
else:
|
|
255
232
|
contribution_table.add_row(
|
|
256
233
|
"No contributor data",
|
|
257
234
|
"—",
|
|
235
|
+
"",
|
|
258
236
|
)
|
|
237
|
+
for _ in range(len(ranked_contributions) if ranked_contributions else 1, 5):
|
|
238
|
+
contribution_table.add_row("", "", "")
|
|
259
239
|
|
|
260
240
|
contributions_panel = Panel(
|
|
261
241
|
contribution_table,
|
|
262
|
-
title="
|
|
242
|
+
title="Contributors",
|
|
263
243
|
expand=False,
|
|
244
|
+
border_style="cyan",
|
|
264
245
|
)
|
|
265
246
|
|
|
266
247
|
# Languages
|
|
@@ -268,51 +249,82 @@ def display_repo_details(
|
|
|
268
249
|
show_header=False,
|
|
269
250
|
box=None,
|
|
270
251
|
pad_edge=False,
|
|
252
|
+
expand=True,
|
|
253
|
+
padding=(0, 1),
|
|
271
254
|
)
|
|
272
255
|
|
|
273
256
|
language_table.add_column("Language")
|
|
274
|
-
language_table.add_column("Usage")
|
|
257
|
+
language_table.add_column("Usage", justify="right")
|
|
275
258
|
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
259
|
+
if not languages:
|
|
260
|
+
language_table.add_row("No language data returned.", "")
|
|
261
|
+
else:
|
|
262
|
+
total_bytes = sum(languages.values())
|
|
263
|
+
ranked_languages = sorted(
|
|
264
|
+
languages.items(), key=lambda item: item[1], reverse=True
|
|
265
|
+
)[:5]
|
|
266
|
+
for index, (language, byte_count) in enumerate(ranked_languages):
|
|
267
|
+
percentage = byte_count / total_bytes * 100 if total_bytes else 0
|
|
268
|
+
language_table.add_row(
|
|
269
|
+
language,
|
|
270
|
+
_overview_bar_label(
|
|
271
|
+
percentage, spaced=index < len(ranked_languages) - 1
|
|
272
|
+
),
|
|
273
|
+
)
|
|
274
|
+
for _ in range(min(len(languages), 5), 5):
|
|
275
|
+
language_table.add_row("", "")
|
|
276
|
+
if not languages:
|
|
277
|
+
for _ in range(1, 5):
|
|
278
|
+
language_table.add_row("", "")
|
|
282
279
|
|
|
283
280
|
languages_panel = Panel(
|
|
284
281
|
language_table,
|
|
285
282
|
title="Languages",
|
|
286
283
|
expand=False,
|
|
284
|
+
border_style="cyan",
|
|
287
285
|
)
|
|
288
286
|
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
287
|
+
repository = Table(show_header=False, box=None, pad_edge=False, expand=True)
|
|
288
|
+
repository.add_column("Label", style="dim", no_wrap=True)
|
|
289
|
+
repository.add_column("Value", overflow="fold")
|
|
290
|
+
repository.add_row("Description", description)
|
|
291
|
+
repository.add_row("Visibility", visibility.capitalize())
|
|
292
|
+
repository.add_row("URL", url)
|
|
293
|
+
repository.add_row("★ Stars", str(details.get("stargazers_count") or 0))
|
|
294
|
+
repository.add_row("⑂ Forks", str(details.get("forks_count") or 0))
|
|
295
|
+
repository.add_row("! Issues", str(details.get("open_issues_count") or 0))
|
|
296
|
+
repository.add_row("Size", format_size(details.get("size")))
|
|
297
|
+
repository_panel = Panel(
|
|
298
|
+
repository,
|
|
299
|
+
title="Repository",
|
|
300
|
+
border_style="cyan",
|
|
301
|
+
width=60,
|
|
297
302
|
)
|
|
298
|
-
console.print()
|
|
299
303
|
|
|
304
|
+
console.print(header_panel)
|
|
305
|
+
console.print()
|
|
300
306
|
console.print(
|
|
301
307
|
Columns(
|
|
302
|
-
[
|
|
308
|
+
[contributions_panel, languages_panel],
|
|
303
309
|
expand=False,
|
|
304
310
|
equal=True,
|
|
311
|
+
padding=(0, 0),
|
|
305
312
|
)
|
|
306
313
|
)
|
|
314
|
+
console.print()
|
|
315
|
+
console.print(repository_panel)
|
|
307
316
|
|
|
308
|
-
console.print(languages_panel)
|
|
309
317
|
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
318
|
+
def _progress_bar(percentage: float, width: int = 12) -> str:
|
|
319
|
+
"""Build a compact proportional bar for overview panels."""
|
|
320
|
+
filled = round(max(0, min(100, percentage)) / 100 * width)
|
|
321
|
+
return f"[green]{'█' * filled}[/green]"
|
|
322
|
+
|
|
323
|
+
|
|
324
|
+
def _overview_bar_label(percentage: float, spaced: bool = True) -> str:
|
|
325
|
+
"""Render an overview bar with a compact line of breathing room."""
|
|
326
|
+
suffix = "\n" if spaced else ""
|
|
327
|
+
return f"{_progress_bar(percentage)} {percentage:.1f}%{suffix}"
|
|
316
328
|
|
|
317
329
|
|
|
318
330
|
def display_contributors(
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "recon-github"
|
|
3
|
-
version = "0.
|
|
3
|
+
version = "1.0.0"
|
|
4
4
|
description = "Recon is a powerful CLI companion for developers and tools for agents, turning GitHub activity and repository data into useful insights from your terminal"
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
requires-python = ">=3.12"
|
|
@@ -160,17 +160,53 @@ def test_details_rejects_both_views():
|
|
|
160
160
|
|
|
161
161
|
|
|
162
162
|
def test_details_default_calls_services_and_prints(monkeypatch):
|
|
163
|
-
details = {
|
|
163
|
+
details = {
|
|
164
|
+
"full_name": "o/r",
|
|
165
|
+
"description": "desc",
|
|
166
|
+
"visibility": "public",
|
|
167
|
+
"html_url": "https://github.com/o/r",
|
|
168
|
+
"stargazers_count": 3,
|
|
169
|
+
"forks_count": 2,
|
|
170
|
+
"open_issues_count": 1,
|
|
171
|
+
"size": 1,
|
|
172
|
+
}
|
|
164
173
|
monkeypatch.setattr(
|
|
165
174
|
"app.services.github.get_repo_details", Mock(return_value=details)
|
|
166
175
|
)
|
|
167
176
|
monkeypatch.setattr(
|
|
168
|
-
"app.services.github.get_languages",
|
|
177
|
+
"app.services.github.get_languages",
|
|
178
|
+
Mock(
|
|
179
|
+
return_value={
|
|
180
|
+
"Python": 6,
|
|
181
|
+
"Rust": 5,
|
|
182
|
+
"Go": 4,
|
|
183
|
+
"JavaScript": 3,
|
|
184
|
+
"HTML": 2,
|
|
185
|
+
"CSS": 1,
|
|
186
|
+
}
|
|
187
|
+
),
|
|
169
188
|
)
|
|
170
189
|
monkeypatch.setattr(
|
|
171
|
-
"app.services.github.get_top_contributors",
|
|
190
|
+
"app.services.github.get_top_contributors",
|
|
191
|
+
Mock(
|
|
192
|
+
return_value=[
|
|
193
|
+
{"login": "octo", "commits": 6},
|
|
194
|
+
{"login": "user2", "commits": 5},
|
|
195
|
+
{"login": "user3", "commits": 4},
|
|
196
|
+
{"login": "user4", "commits": 3},
|
|
197
|
+
{"login": "user5", "commits": 2},
|
|
198
|
+
{"login": "user6", "commits": 1},
|
|
199
|
+
]
|
|
200
|
+
),
|
|
172
201
|
)
|
|
173
202
|
result = runner.invoke(app, ["details", "o", "r"])
|
|
174
203
|
assert result.exit_code == 0
|
|
204
|
+
assert "DETAILS" in result.output
|
|
205
|
+
assert "Contributors" in result.output
|
|
175
206
|
assert "Repository" in result.output
|
|
176
207
|
assert "Languages" in result.output
|
|
208
|
+
assert "octo" in result.output
|
|
209
|
+
assert "user5" in result.output
|
|
210
|
+
assert "user6" not in result.output
|
|
211
|
+
assert "CSS" not in result.output
|
|
212
|
+
assert "Stats" not in result.output
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|