gitglimpse 0.1.3__tar.gz → 0.1.5__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.
- gitglimpse-0.1.5/PKG-INFO +69 -0
- gitglimpse-0.1.5/PYPI_README.md +44 -0
- {gitglimpse-0.1.3 → gitglimpse-0.1.5}/pyproject.toml +6 -4
- gitglimpse-0.1.5/src/gitglimpse/__init__.py +1 -0
- {gitglimpse-0.1.3 → gitglimpse-0.1.5}/src/gitglimpse/cli.py +42 -142
- {gitglimpse-0.1.3 → gitglimpse-0.1.5}/src/gitglimpse/commands/pr.md +1 -1
- {gitglimpse-0.1.3 → gitglimpse-0.1.5}/src/gitglimpse/commands/report.md +1 -1
- {gitglimpse-0.1.3 → gitglimpse-0.1.5}/src/gitglimpse/commands/standup.md +1 -1
- {gitglimpse-0.1.3 → gitglimpse-0.1.5}/src/gitglimpse/commands/week.md +1 -1
- {gitglimpse-0.1.3 → gitglimpse-0.1.5}/src/gitglimpse/providers/claude.py +13 -4
- {gitglimpse-0.1.3 → gitglimpse-0.1.5}/src/gitglimpse/providers/gemini.py +13 -4
- {gitglimpse-0.1.3 → gitglimpse-0.1.5}/src/gitglimpse/providers/local.py +14 -2
- {gitglimpse-0.1.3 → gitglimpse-0.1.5}/src/gitglimpse/providers/openai.py +13 -4
- gitglimpse-0.1.3/PKG-INFO +0 -23
- gitglimpse-0.1.3/src/gitglimpse/__init__.py +0 -1
- {gitglimpse-0.1.3 → gitglimpse-0.1.5}/.gitignore +0 -0
- {gitglimpse-0.1.3 → gitglimpse-0.1.5}/LICENSE +0 -0
- {gitglimpse-0.1.3 → gitglimpse-0.1.5}/src/gitglimpse/commands/__init__.py +0 -0
- {gitglimpse-0.1.3 → gitglimpse-0.1.5}/src/gitglimpse/config.py +0 -0
- {gitglimpse-0.1.3 → gitglimpse-0.1.5}/src/gitglimpse/estimation.py +0 -0
- {gitglimpse-0.1.3 → gitglimpse-0.1.5}/src/gitglimpse/formatters/__init__.py +0 -0
- {gitglimpse-0.1.3 → gitglimpse-0.1.5}/src/gitglimpse/formatters/json.py +0 -0
- {gitglimpse-0.1.3 → gitglimpse-0.1.5}/src/gitglimpse/formatters/markdown.py +0 -0
- {gitglimpse-0.1.3 → gitglimpse-0.1.5}/src/gitglimpse/formatters/pr.py +0 -0
- {gitglimpse-0.1.3 → gitglimpse-0.1.5}/src/gitglimpse/formatters/template.py +0 -0
- {gitglimpse-0.1.3 → gitglimpse-0.1.5}/src/gitglimpse/git.py +0 -0
- {gitglimpse-0.1.3 → gitglimpse-0.1.5}/src/gitglimpse/grouping.py +0 -0
- {gitglimpse-0.1.3 → gitglimpse-0.1.5}/src/gitglimpse/onboarding.py +0 -0
- {gitglimpse-0.1.3 → gitglimpse-0.1.5}/src/gitglimpse/providers/__init__.py +0 -0
- {gitglimpse-0.1.3 → gitglimpse-0.1.5}/src/gitglimpse/providers/base.py +0 -0
- {gitglimpse-0.1.3 → gitglimpse-0.1.5}/src/gitglimpse/py.typed +0 -0
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: gitglimpse
|
|
3
|
+
Version: 0.1.5
|
|
4
|
+
Summary: Extract structured context from your git history — PR descriptions, standups, weekly reports, and LLM-ready JSON.
|
|
5
|
+
Project-URL: Homepage, https://github.com/dino/gitglimpse
|
|
6
|
+
Project-URL: Repository, https://github.com/dino/gitglimpse
|
|
7
|
+
Project-URL: Bug Tracker, https://github.com/dino/gitglimpse/issues
|
|
8
|
+
Author: Dino
|
|
9
|
+
License: MIT
|
|
10
|
+
License-File: LICENSE
|
|
11
|
+
Keywords: cli,developer-tools,git,standup
|
|
12
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
16
|
+
Requires-Python: >=3.11
|
|
17
|
+
Requires-Dist: inquirerpy>=0.3.4
|
|
18
|
+
Requires-Dist: platformdirs>=4.0
|
|
19
|
+
Requires-Dist: rich>=13.0
|
|
20
|
+
Requires-Dist: tomli-w>=1.2
|
|
21
|
+
Requires-Dist: typer[all]>=0.9.0
|
|
22
|
+
Provides-Extra: llm
|
|
23
|
+
Requires-Dist: httpx>=0.27; extra == 'llm'
|
|
24
|
+
Description-Content-Type: text/markdown
|
|
25
|
+
|
|
26
|
+
# gitglimpse
|
|
27
|
+
|
|
28
|
+
Extract structured context from your git history — PR descriptions, standups, weekly reports, and LLM-ready JSON.
|
|
29
|
+
|
|
30
|
+
## Install
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
pip install gitglimpse
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
## What it does
|
|
37
|
+
|
|
38
|
+
gitglimpse reads your git log, groups commits into logical tasks, filters noise, extracts ticket IDs from branch names, and outputs structured context — as formatted text or clean JSON.
|
|
39
|
+
|
|
40
|
+
## Commands
|
|
41
|
+
|
|
42
|
+
```bash
|
|
43
|
+
glimpse pr # PR summary from current branch
|
|
44
|
+
glimpse standup # daily context from recent commits
|
|
45
|
+
glimpse week # weekly summary grouped by day
|
|
46
|
+
glimpse report # markdown report with file details
|
|
47
|
+
glimpse init # generate Claude Code / Cursor slash commands
|
|
48
|
+
glimpse config setup # interactive configuration
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
## Features
|
|
52
|
+
|
|
53
|
+
- **Noise filtering** — merge commits, lock files, and formatting changes excluded by default
|
|
54
|
+
- **Ticket detection** — branch names like `feature/PROJ-123` are parsed automatically
|
|
55
|
+
- **Multi-project** — run from a parent directory to aggregate across repos
|
|
56
|
+
- **LLM-optional** — works instantly without AI, or connect Ollama / OpenAI / Anthropic / Gemini for richer output
|
|
57
|
+
- **Editor integration** — slash commands for Claude Code and Cursor
|
|
58
|
+
- **GitHub Action** — auto-generate PR context on every pull request
|
|
59
|
+
- **JSON output** — every command supports `--json` for pipelines and LLM workflows
|
|
60
|
+
|
|
61
|
+
## Links
|
|
62
|
+
|
|
63
|
+
- **Website:** [gitglimpse.com](https://gitglimpse.com)
|
|
64
|
+
- **GitHub:** [github.com/dino-zecevic/gitglimpse](https://github.com/dino-zecevic/gitglimpse)
|
|
65
|
+
- **Documentation:** [README on GitHub](https://github.com/dino-zecevic/gitglimpse#readme)
|
|
66
|
+
|
|
67
|
+
## License
|
|
68
|
+
|
|
69
|
+
MIT
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
# gitglimpse
|
|
2
|
+
|
|
3
|
+
Extract structured context from your git history — PR descriptions, standups, weekly reports, and LLM-ready JSON.
|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
pip install gitglimpse
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## What it does
|
|
12
|
+
|
|
13
|
+
gitglimpse reads your git log, groups commits into logical tasks, filters noise, extracts ticket IDs from branch names, and outputs structured context — as formatted text or clean JSON.
|
|
14
|
+
|
|
15
|
+
## Commands
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
glimpse pr # PR summary from current branch
|
|
19
|
+
glimpse standup # daily context from recent commits
|
|
20
|
+
glimpse week # weekly summary grouped by day
|
|
21
|
+
glimpse report # markdown report with file details
|
|
22
|
+
glimpse init # generate Claude Code / Cursor slash commands
|
|
23
|
+
glimpse config setup # interactive configuration
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
## Features
|
|
27
|
+
|
|
28
|
+
- **Noise filtering** — merge commits, lock files, and formatting changes excluded by default
|
|
29
|
+
- **Ticket detection** — branch names like `feature/PROJ-123` are parsed automatically
|
|
30
|
+
- **Multi-project** — run from a parent directory to aggregate across repos
|
|
31
|
+
- **LLM-optional** — works instantly without AI, or connect Ollama / OpenAI / Anthropic / Gemini for richer output
|
|
32
|
+
- **Editor integration** — slash commands for Claude Code and Cursor
|
|
33
|
+
- **GitHub Action** — auto-generate PR context on every pull request
|
|
34
|
+
- **JSON output** — every command supports `--json` for pipelines and LLM workflows
|
|
35
|
+
|
|
36
|
+
## Links
|
|
37
|
+
|
|
38
|
+
- **Website:** [gitglimpse.com](https://gitglimpse.com)
|
|
39
|
+
- **GitHub:** [github.com/dino-zecevic/gitglimpse](https://github.com/dino-zecevic/gitglimpse)
|
|
40
|
+
- **Documentation:** [README on GitHub](https://github.com/dino-zecevic/gitglimpse#readme)
|
|
41
|
+
|
|
42
|
+
## License
|
|
43
|
+
|
|
44
|
+
MIT
|
|
@@ -4,8 +4,9 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "gitglimpse"
|
|
7
|
-
version = "0.1.
|
|
8
|
-
description = "
|
|
7
|
+
version = "0.1.5"
|
|
8
|
+
description = "Extract structured context from your git history — PR descriptions, standups, weekly reports, and LLM-ready JSON."
|
|
9
|
+
readme = "PYPI_README.md"
|
|
9
10
|
authors = [{ name = "Dino" }]
|
|
10
11
|
license = { text = "MIT" }
|
|
11
12
|
requires-python = ">=3.11"
|
|
@@ -24,13 +25,14 @@ dependencies = [
|
|
|
24
25
|
"InquirerPy>=0.3.4",
|
|
25
26
|
]
|
|
26
27
|
|
|
28
|
+
[project.optional-dependencies]
|
|
29
|
+
llm = ["httpx>=0.27"]
|
|
30
|
+
|
|
27
31
|
[project.urls]
|
|
28
32
|
Homepage = "https://github.com/dino/gitglimpse"
|
|
29
33
|
Repository = "https://github.com/dino/gitglimpse"
|
|
30
34
|
"Bug Tracker" = "https://github.com/dino/gitglimpse/issues"
|
|
31
35
|
|
|
32
|
-
[project.optional-dependencies]
|
|
33
|
-
llm = ["httpx>=0.27"]
|
|
34
36
|
|
|
35
37
|
[project.scripts]
|
|
36
38
|
glimpse = "gitglimpse.cli:app"
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "0.1.5"
|
|
@@ -18,7 +18,6 @@ from gitglimpse.formatters.template import format_standup, format_week_template
|
|
|
18
18
|
from gitglimpse.git import GitError, get_branch_commits, get_commit_diff, get_commits, get_current_branch_name
|
|
19
19
|
from gitglimpse.grouping import filter_noise_commits, group_commits_into_tasks, is_vague_message
|
|
20
20
|
from gitglimpse.providers import get_provider
|
|
21
|
-
from gitglimpse.providers.local import LocalProvider
|
|
22
21
|
|
|
23
22
|
app = typer.Typer(
|
|
24
23
|
name="glimpse",
|
|
@@ -50,6 +49,7 @@ def _app_callback(
|
|
|
50
49
|
"""Analyze git history and generate standup updates, reports, and summaries."""
|
|
51
50
|
|
|
52
51
|
console = Console()
|
|
52
|
+
_stderr_console = Console(stderr=True)
|
|
53
53
|
|
|
54
54
|
# ---------------------------------------------------------------------------
|
|
55
55
|
# Helpers
|
|
@@ -214,7 +214,7 @@ def _resolve_repo_paths(
|
|
|
214
214
|
raise typer.Exit(1)
|
|
215
215
|
|
|
216
216
|
names = ", ".join(r.name for r in repos)
|
|
217
|
-
|
|
217
|
+
_stderr_console.print(f"[dim]Found {len(repos)} projects: {names}[/dim]", highlight=False)
|
|
218
218
|
return [(r, r.name) for r in repos]
|
|
219
219
|
|
|
220
220
|
|
|
@@ -275,6 +275,7 @@ def _print_status_line(
|
|
|
275
275
|
ctx_mode: str = "commits",
|
|
276
276
|
) -> None:
|
|
277
277
|
"""Print a one-line dim status showing author, context, and active model."""
|
|
278
|
+
from gitglimpse.providers.local import LocalProvider
|
|
278
279
|
from gitglimpse.providers.openai import OpenAIProvider
|
|
279
280
|
from gitglimpse.providers.claude import ClaudeProvider
|
|
280
281
|
from gitglimpse.providers.gemini import GeminiProvider
|
|
@@ -314,6 +315,8 @@ def _resolve_provider(
|
|
|
314
315
|
2. config default_mode == local-llm / api → get_provider()
|
|
315
316
|
3. anything else → None (template fallback)
|
|
316
317
|
"""
|
|
318
|
+
from gitglimpse.providers.local import LocalProvider
|
|
319
|
+
|
|
317
320
|
if use_local:
|
|
318
321
|
url = local_url_override or cfg.local_llm_url
|
|
319
322
|
model = model_override or cfg.llm_model or None
|
|
@@ -370,6 +373,14 @@ def standup(
|
|
|
370
373
|
typer.Option("--filter-noise/--no-filter-noise",
|
|
371
374
|
help="Filter out noise commits (merges, formatting, lock files)."),
|
|
372
375
|
] = None,
|
|
376
|
+
fmt: Annotated[
|
|
377
|
+
Optional[str],
|
|
378
|
+
typer.Option("--format", help="Output format: 'default' (Rich) or 'markdown'."),
|
|
379
|
+
] = None,
|
|
380
|
+
output: Annotated[
|
|
381
|
+
Optional[str],
|
|
382
|
+
typer.Option("--output", "-o", help="Save output to file instead of printing."),
|
|
383
|
+
] = None,
|
|
373
384
|
skip_setup: Annotated[
|
|
374
385
|
bool,
|
|
375
386
|
typer.Option("--skip-setup", help="Skip first-run onboarding.", hidden=True),
|
|
@@ -382,7 +393,7 @@ def standup(
|
|
|
382
393
|
glimpse standup
|
|
383
394
|
glimpse standup --since "2 days ago"
|
|
384
395
|
glimpse standup --json
|
|
385
|
-
glimpse standup --
|
|
396
|
+
glimpse standup --format markdown -o daily.md
|
|
386
397
|
glimpse standup --repos "api,frontend,landing"
|
|
387
398
|
"""
|
|
388
399
|
cfg = _load_or_onboard(skip_setup)
|
|
@@ -439,6 +450,8 @@ def standup(
|
|
|
439
450
|
print(json_str)
|
|
440
451
|
return
|
|
441
452
|
|
|
453
|
+
use_markdown = fmt and fmt.lower() == "markdown"
|
|
454
|
+
|
|
442
455
|
if filtered_count > 0:
|
|
443
456
|
console.print(f"[dim]Filtered {filtered_count} noise commits (merges, formatting, dependencies)[/dim]",
|
|
444
457
|
highlight=False)
|
|
@@ -446,6 +459,7 @@ def standup(
|
|
|
446
459
|
active_provider: object | None = None
|
|
447
460
|
llm_output: str | None = None
|
|
448
461
|
if not no_llm:
|
|
462
|
+
from gitglimpse.providers.local import LocalProvider
|
|
449
463
|
provider = _resolve_provider(cfg, local_llm, local_llm_url, model, context_mode=ctx_mode)
|
|
450
464
|
if provider is not None:
|
|
451
465
|
if isinstance(provider, LocalProvider) and not provider.is_available():
|
|
@@ -455,131 +469,28 @@ def standup(
|
|
|
455
469
|
)
|
|
456
470
|
else:
|
|
457
471
|
active_provider = provider
|
|
458
|
-
|
|
472
|
+
if use_markdown:
|
|
473
|
+
llm_output = provider.summarize_report(tasks, report_date, diff_snippets)
|
|
474
|
+
else:
|
|
475
|
+
llm_output = provider.summarize_standup(tasks, report_date, diff_snippets)
|
|
459
476
|
|
|
460
477
|
_print_status_line(resolved_author, active_provider, ctx_mode)
|
|
461
|
-
if llm_output:
|
|
462
|
-
console.print(llm_output, markup=False, highlight=False)
|
|
463
|
-
else:
|
|
464
|
-
console.print(
|
|
465
|
-
format_standup(tasks, report_date, group_by=group_by if multi else "project"),
|
|
466
|
-
highlight=False,
|
|
467
|
-
)
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
# ---------------------------------------------------------------------------
|
|
471
|
-
# report
|
|
472
|
-
# ---------------------------------------------------------------------------
|
|
473
|
-
|
|
474
|
-
@app.command()
|
|
475
|
-
def report(
|
|
476
|
-
no_llm: Annotated[bool, typer.Option("--no-llm", help="Skip LLM, use Markdown formatter.")] = False,
|
|
477
|
-
local_llm: Annotated[bool, typer.Option("--local-llm", help="Use local LLM (Ollama).")] = False,
|
|
478
|
-
local_llm_url: Annotated[
|
|
479
|
-
Optional[str],
|
|
480
|
-
typer.Option("--local-llm-url", help="Override local LLM base URL."),
|
|
481
|
-
] = None,
|
|
482
|
-
model: Annotated[
|
|
483
|
-
Optional[str],
|
|
484
|
-
typer.Option("--model", help="LLM model to use (e.g. qwen2.5-coder:latest)."),
|
|
485
|
-
] = None,
|
|
486
|
-
since: Annotated[
|
|
487
|
-
str, typer.Option("--since", help="Show commits since this date or period.")] = _SENTINEL_SINCE,
|
|
488
|
-
author: Annotated[
|
|
489
|
-
Optional[str],
|
|
490
|
-
typer.Option("--author", help="Filter by author email."),
|
|
491
|
-
] = None,
|
|
492
|
-
repo: Annotated[
|
|
493
|
-
Optional[str],
|
|
494
|
-
typer.Option("--repo", help="Path to git repository. Defaults to current directory."),
|
|
495
|
-
] = None,
|
|
496
|
-
repos: Annotated[
|
|
497
|
-
Optional[str],
|
|
498
|
-
typer.Option("--repos", help="Comma-separated list of repo paths for multi-project mode."),
|
|
499
|
-
] = None,
|
|
500
|
-
output: Annotated[
|
|
501
|
-
Optional[str],
|
|
502
|
-
typer.Option("--output", "-o", help="Save report to this file instead of printing."),
|
|
503
|
-
] = None,
|
|
504
|
-
context: Annotated[
|
|
505
|
-
Optional[str],
|
|
506
|
-
typer.Option("--context", help="LLM context: 'commits', 'diffs', or 'both'."),
|
|
507
|
-
] = None,
|
|
508
|
-
filter_noise: Annotated[
|
|
509
|
-
Optional[bool],
|
|
510
|
-
typer.Option("--filter-noise/--no-filter-noise",
|
|
511
|
-
help="Filter out noise commits (merges, formatting, lock files)."),
|
|
512
|
-
] = None,
|
|
513
|
-
skip_setup: Annotated[
|
|
514
|
-
bool,
|
|
515
|
-
typer.Option("--skip-setup", help="Skip first-run onboarding.", hidden=True),
|
|
516
|
-
] = False,
|
|
517
|
-
) -> None:
|
|
518
|
-
"""Generate a daily Markdown report from git commits.
|
|
519
|
-
|
|
520
|
-
\b
|
|
521
|
-
Examples:
|
|
522
|
-
glimpse report
|
|
523
|
-
glimpse report -o daily.md
|
|
524
|
-
glimpse report --since 2025-03-01
|
|
525
|
-
"""
|
|
526
|
-
cfg = _load_or_onboard(skip_setup)
|
|
527
|
-
effective = _effective_since(since, cfg.default_since)
|
|
528
|
-
ctx_mode = context or cfg.context_mode
|
|
529
|
-
resolved_author = _resolve_author(author, cfg.author_email)
|
|
530
|
-
do_filter = filter_noise if filter_noise is not None else cfg.filter_noise
|
|
531
|
-
|
|
532
|
-
repo_pairs = _resolve_repo_paths(repo, repos)
|
|
533
|
-
multi = len(repo_pairs) > 1
|
|
534
|
-
|
|
535
|
-
filtered_count = 0
|
|
536
|
-
if multi:
|
|
537
|
-
tasks, filtered_count = _collect_multi_project(
|
|
538
|
-
repo_pairs, effective, None, resolved_author, do_filter=do_filter,
|
|
539
|
-
)
|
|
540
|
-
else:
|
|
541
|
-
repo_path = repo_pairs[0][0] if repo_pairs[0][1] else (Path(repo) if repo else None)
|
|
542
|
-
try:
|
|
543
|
-
commits = get_commits(repo_path=repo_path, since=effective, author=resolved_author)
|
|
544
|
-
except GitError as exc:
|
|
545
|
-
console.print(f"[bold red]Error:[/bold red] {exc}")
|
|
546
|
-
raise typer.Exit(1)
|
|
547
|
-
if do_filter:
|
|
548
|
-
original_count = len(commits)
|
|
549
|
-
commits = filter_noise_commits(commits)
|
|
550
|
-
filtered_count = original_count - len(commits)
|
|
551
|
-
tasks = group_commits_into_tasks(commits)
|
|
552
|
-
|
|
553
|
-
report_date = _report_date(effective)
|
|
554
|
-
|
|
555
|
-
if filtered_count > 0:
|
|
556
|
-
console.print(f"[dim]Filtered {filtered_count} noise commits (merges, formatting, dependencies)[/dim]",
|
|
557
|
-
highlight=False)
|
|
558
|
-
|
|
559
|
-
active_provider: object | None = None
|
|
560
|
-
llm_output: str | None = None
|
|
561
|
-
if not no_llm:
|
|
562
|
-
provider = _resolve_provider(cfg, local_llm, local_llm_url, model, context_mode=ctx_mode)
|
|
563
|
-
if provider is not None:
|
|
564
|
-
if isinstance(provider, LocalProvider) and not provider.is_available():
|
|
565
|
-
if local_llm:
|
|
566
|
-
console.print(
|
|
567
|
-
"[yellow]⚠ Local LLM not reachable — falling back to Markdown formatter.[/yellow]"
|
|
568
|
-
)
|
|
569
|
-
else:
|
|
570
|
-
diff_snippets = _collect_diff_snippets(tasks, None, all_commits=True) if ctx_mode in ("diffs",
|
|
571
|
-
"both") else None
|
|
572
|
-
active_provider = provider
|
|
573
|
-
llm_output = provider.summarize_report(tasks, report_date, diff_snippets)
|
|
574
478
|
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
479
|
+
if use_markdown:
|
|
480
|
+
md = llm_output if llm_output else format_report(tasks, report_date)
|
|
481
|
+
if output:
|
|
482
|
+
Path(output).write_text(md, encoding="utf-8")
|
|
483
|
+
console.print(f"Report saved to [bold]{output}[/bold]")
|
|
484
|
+
else:
|
|
485
|
+
console.print(md, markup=False, highlight=False)
|
|
581
486
|
else:
|
|
582
|
-
|
|
487
|
+
if llm_output:
|
|
488
|
+
console.print(llm_output, markup=False, highlight=False)
|
|
489
|
+
else:
|
|
490
|
+
console.print(
|
|
491
|
+
format_standup(tasks, report_date, group_by=group_by if multi else "project"),
|
|
492
|
+
highlight=False,
|
|
493
|
+
)
|
|
583
494
|
|
|
584
495
|
|
|
585
496
|
# ---------------------------------------------------------------------------
|
|
@@ -694,6 +605,7 @@ def week(
|
|
|
694
605
|
active_provider: object | None = None
|
|
695
606
|
llm_output: str | None = None
|
|
696
607
|
if not no_llm:
|
|
608
|
+
from gitglimpse.providers.local import LocalProvider
|
|
697
609
|
provider = _resolve_provider(cfg, local_llm, local_llm_url, model, context_mode=ctx_mode)
|
|
698
610
|
if provider is not None:
|
|
699
611
|
if isinstance(provider, LocalProvider) and not provider.is_available():
|
|
@@ -825,6 +737,7 @@ def pr(
|
|
|
825
737
|
active_provider: object | None = None
|
|
826
738
|
llm_output: str | None = None
|
|
827
739
|
if not no_llm:
|
|
740
|
+
from gitglimpse.providers.local import LocalProvider
|
|
828
741
|
provider = _resolve_provider(cfg, local_llm, local_llm_url, model, context_mode=ctx_mode)
|
|
829
742
|
if provider is not None:
|
|
830
743
|
if isinstance(provider, LocalProvider) and not provider.is_available():
|
|
@@ -951,12 +864,6 @@ def init(
|
|
|
951
864
|
"""
|
|
952
865
|
root = Path(repo) if repo else Path.cwd()
|
|
953
866
|
|
|
954
|
-
try:
|
|
955
|
-
cfg = load_config()
|
|
956
|
-
context_mode = cfg.context_mode
|
|
957
|
-
except Exception:
|
|
958
|
-
context_mode = "commits"
|
|
959
|
-
|
|
960
867
|
targets: list[tuple[Path, str]] = [
|
|
961
868
|
(root / ".claude" / "commands", "Claude Code"),
|
|
962
869
|
]
|
|
@@ -966,8 +873,6 @@ def init(
|
|
|
966
873
|
created: list[Path] = []
|
|
967
874
|
skipped: list[Path] = []
|
|
968
875
|
|
|
969
|
-
console.print(f"[dim]Context mode: {context_mode} (change with: glimpse config setup)[/dim]")
|
|
970
|
-
|
|
971
876
|
for commands_dir, tool_name in targets:
|
|
972
877
|
console.print(f"\n[bold]{tool_name}[/bold] → {commands_dir}")
|
|
973
878
|
for name in _COMMAND_TEMPLATES:
|
|
@@ -977,16 +882,6 @@ def init(
|
|
|
977
882
|
except Exception as exc:
|
|
978
883
|
console.print(f" [red]Could not read template {name}: {exc}[/red]")
|
|
979
884
|
continue
|
|
980
|
-
content = content.replace(
|
|
981
|
-
"glimpse standup --json",
|
|
982
|
-
f"glimpse standup --json --context {context_mode}",
|
|
983
|
-
).replace(
|
|
984
|
-
"glimpse week --json",
|
|
985
|
-
f"glimpse week --json --context {context_mode}",
|
|
986
|
-
).replace(
|
|
987
|
-
"glimpse pr --json",
|
|
988
|
-
f"glimpse pr --json --context {context_mode}",
|
|
989
|
-
)
|
|
990
885
|
written = _write_command_file(dest, content, force=force, dry_run=False)
|
|
991
886
|
if written:
|
|
992
887
|
console.print(f" [green]✓[/green] Created {dest.relative_to(root)}")
|
|
@@ -996,9 +891,14 @@ def init(
|
|
|
996
891
|
|
|
997
892
|
console.print()
|
|
998
893
|
if created:
|
|
894
|
+
commands_dir = created[0].parent
|
|
999
895
|
console.print(
|
|
1000
896
|
f"[bold green]Done.[/bold green] "
|
|
1001
897
|
f"Created {len(created)} file{'s' if len(created) != 1 else ''}."
|
|
1002
898
|
)
|
|
899
|
+
console.print(
|
|
900
|
+
f"[dim]Commands use --context both for maximum LLM context. "
|
|
901
|
+
f"Edit the files in {commands_dir.relative_to(root)}/ to change this.[/dim]"
|
|
902
|
+
)
|
|
1003
903
|
else:
|
|
1004
904
|
console.print("[yellow]No files were created.[/yellow]")
|
|
@@ -3,7 +3,7 @@ Generate a detailed daily report from your git commits.
|
|
|
3
3
|
Run the following shell command and capture its output:
|
|
4
4
|
|
|
5
5
|
```
|
|
6
|
-
glimpse standup --json
|
|
6
|
+
glimpse standup --json --context both
|
|
7
7
|
```
|
|
8
8
|
|
|
9
9
|
Then format the JSON result into a **Markdown daily report** using this structure:
|
|
@@ -3,7 +3,7 @@ Generate a standup update from your recent git commits.
|
|
|
3
3
|
Run the following shell command and capture its output:
|
|
4
4
|
|
|
5
5
|
```
|
|
6
|
-
glimpse standup --json
|
|
6
|
+
glimpse standup --json --context both
|
|
7
7
|
```
|
|
8
8
|
|
|
9
9
|
Then format the JSON result into a clean standup update using **exactly** this structure:
|
|
@@ -5,8 +5,6 @@ from __future__ import annotations
|
|
|
5
5
|
from datetime import date
|
|
6
6
|
from typing import TYPE_CHECKING
|
|
7
7
|
|
|
8
|
-
import httpx
|
|
9
|
-
|
|
10
8
|
from gitglimpse.providers.base import BaseLLMProvider, _warn, validate_llm_output
|
|
11
9
|
|
|
12
10
|
if TYPE_CHECKING:
|
|
@@ -14,9 +12,18 @@ if TYPE_CHECKING:
|
|
|
14
12
|
|
|
15
13
|
_API_URL = "https://api.anthropic.com/v1/messages"
|
|
16
14
|
_ANTHROPIC_VERSION = "2023-06-01"
|
|
17
|
-
_TIMEOUT = httpx.Timeout(connect=30.0, read=120.0, write=30.0, pool=30.0)
|
|
18
15
|
_MAX_TOKENS = 1024
|
|
19
16
|
|
|
17
|
+
_HTTPX_INSTALL_HINT = "httpx is required for LLM features. Install with: pip install 'gitglimpse[llm]'"
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def _import_httpx():
|
|
21
|
+
try:
|
|
22
|
+
import httpx
|
|
23
|
+
return httpx
|
|
24
|
+
except ImportError:
|
|
25
|
+
raise ImportError(_HTTPX_INSTALL_HINT)
|
|
26
|
+
|
|
20
27
|
|
|
21
28
|
class ClaudeProvider(BaseLLMProvider):
|
|
22
29
|
"""Anthropic Claude messages provider."""
|
|
@@ -27,6 +34,7 @@ class ClaudeProvider(BaseLLMProvider):
|
|
|
27
34
|
self.context_mode = context_mode
|
|
28
35
|
|
|
29
36
|
def _chat(self, user_message: str, system_prompt: str | None = None) -> str | None:
|
|
37
|
+
httpx = _import_httpx()
|
|
30
38
|
if system_prompt is None:
|
|
31
39
|
system_prompt = self.get_system_prompt()
|
|
32
40
|
headers = {
|
|
@@ -40,9 +48,10 @@ class ClaudeProvider(BaseLLMProvider):
|
|
|
40
48
|
"system": system_prompt,
|
|
41
49
|
"messages": [{"role": "user", "content": user_message}],
|
|
42
50
|
}
|
|
51
|
+
_timeout = httpx.Timeout(connect=30.0, read=120.0, write=30.0, pool=30.0)
|
|
43
52
|
try:
|
|
44
53
|
with _warn.status(f"[dim]Generating with {self.model}...[/dim]"):
|
|
45
|
-
resp = httpx.post(_API_URL, json=payload, headers=headers, timeout=
|
|
54
|
+
resp = httpx.post(_API_URL, json=payload, headers=headers, timeout=_timeout)
|
|
46
55
|
if resp.status_code == 401:
|
|
47
56
|
_warn.print("[yellow]⚠ Claude: invalid API key.[/yellow]")
|
|
48
57
|
return None
|
|
@@ -5,15 +5,22 @@ from __future__ import annotations
|
|
|
5
5
|
from datetime import date
|
|
6
6
|
from typing import TYPE_CHECKING
|
|
7
7
|
|
|
8
|
-
import httpx
|
|
9
|
-
|
|
10
8
|
from gitglimpse.providers.base import BaseLLMProvider, _warn, validate_llm_output
|
|
11
9
|
|
|
12
10
|
if TYPE_CHECKING:
|
|
13
11
|
from gitglimpse.grouping import Task
|
|
14
12
|
|
|
15
13
|
_API_BASE = "https://generativelanguage.googleapis.com/v1beta/models"
|
|
16
|
-
|
|
14
|
+
|
|
15
|
+
_HTTPX_INSTALL_HINT = "httpx is required for LLM features. Install with: pip install 'gitglimpse[llm]'"
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def _import_httpx():
|
|
19
|
+
try:
|
|
20
|
+
import httpx
|
|
21
|
+
return httpx
|
|
22
|
+
except ImportError:
|
|
23
|
+
raise ImportError(_HTTPX_INSTALL_HINT)
|
|
17
24
|
|
|
18
25
|
|
|
19
26
|
class GeminiProvider(BaseLLMProvider):
|
|
@@ -25,6 +32,7 @@ class GeminiProvider(BaseLLMProvider):
|
|
|
25
32
|
self.context_mode = context_mode
|
|
26
33
|
|
|
27
34
|
def _chat(self, user_message: str, system_prompt: str | None = None) -> str | None:
|
|
35
|
+
httpx = _import_httpx()
|
|
28
36
|
if system_prompt is None:
|
|
29
37
|
system_prompt = self.get_system_prompt()
|
|
30
38
|
url = f"{_API_BASE}/{self.model}:generateContent?key={self.api_key}"
|
|
@@ -36,9 +44,10 @@ class GeminiProvider(BaseLLMProvider):
|
|
|
36
44
|
{"parts": [{"text": user_message}]}
|
|
37
45
|
],
|
|
38
46
|
}
|
|
47
|
+
_timeout = httpx.Timeout(connect=30.0, read=120.0, write=30.0, pool=30.0)
|
|
39
48
|
try:
|
|
40
49
|
with _warn.status(f"[dim]Generating with {self.model}...[/dim]"):
|
|
41
|
-
resp = httpx.post(url, json=payload, timeout=
|
|
50
|
+
resp = httpx.post(url, json=payload, timeout=_timeout)
|
|
42
51
|
if resp.status_code == 400:
|
|
43
52
|
_warn.print("[yellow]⚠ Gemini: bad request (check API key or model name).[/yellow]")
|
|
44
53
|
return None
|
|
@@ -5,8 +5,6 @@ from __future__ import annotations
|
|
|
5
5
|
from datetime import date
|
|
6
6
|
from typing import TYPE_CHECKING
|
|
7
7
|
|
|
8
|
-
import httpx
|
|
9
|
-
|
|
10
8
|
from gitglimpse.providers.base import BaseLLMProvider, _warn, validate_llm_output
|
|
11
9
|
|
|
12
10
|
if TYPE_CHECKING:
|
|
@@ -17,6 +15,16 @@ _READ_TIMEOUT = 240.0
|
|
|
17
15
|
_AVAILABILITY_TIMEOUT = 3.0
|
|
18
16
|
_DEFAULT_MODEL = "qwen2.5-coder:latest"
|
|
19
17
|
|
|
18
|
+
_HTTPX_INSTALL_HINT = "httpx is required for LLM features. Install with: pip install 'gitglimpse[llm]'"
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def _import_httpx():
|
|
22
|
+
try:
|
|
23
|
+
import httpx
|
|
24
|
+
return httpx
|
|
25
|
+
except ImportError:
|
|
26
|
+
raise ImportError(_HTTPX_INSTALL_HINT)
|
|
27
|
+
|
|
20
28
|
|
|
21
29
|
class LocalProvider(BaseLLMProvider):
|
|
22
30
|
"""OpenAI-compatible local LLM (Ollama, LM Studio, etc.)."""
|
|
@@ -27,6 +35,7 @@ class LocalProvider(BaseLLMProvider):
|
|
|
27
35
|
model: str | None = None,
|
|
28
36
|
context_mode: str = "commits",
|
|
29
37
|
) -> None:
|
|
38
|
+
httpx = _import_httpx()
|
|
30
39
|
self.base_url = base_url.rstrip("/")
|
|
31
40
|
self._explicit_model = model
|
|
32
41
|
self._model_resolved = bool(model)
|
|
@@ -41,6 +50,7 @@ class LocalProvider(BaseLLMProvider):
|
|
|
41
50
|
|
|
42
51
|
def is_available(self) -> bool:
|
|
43
52
|
"""Return True if the local server responds to a quick probe."""
|
|
53
|
+
httpx = _import_httpx()
|
|
44
54
|
try:
|
|
45
55
|
resp = httpx.get(
|
|
46
56
|
f"{self.base_url}/models",
|
|
@@ -55,6 +65,7 @@ class LocalProvider(BaseLLMProvider):
|
|
|
55
65
|
if self._model_resolved:
|
|
56
66
|
return
|
|
57
67
|
self._model_resolved = True
|
|
68
|
+
httpx = _import_httpx()
|
|
58
69
|
try:
|
|
59
70
|
resp = httpx.get(
|
|
60
71
|
f"{self.base_url}/models",
|
|
@@ -68,6 +79,7 @@ class LocalProvider(BaseLLMProvider):
|
|
|
68
79
|
pass # keep the default
|
|
69
80
|
|
|
70
81
|
def _chat(self, user_message: str, system_prompt: str | None = None) -> str | None:
|
|
82
|
+
httpx = _import_httpx()
|
|
71
83
|
self._auto_detect_model()
|
|
72
84
|
url = f"{self.base_url}/chat/completions"
|
|
73
85
|
if system_prompt is None:
|
|
@@ -5,15 +5,22 @@ from __future__ import annotations
|
|
|
5
5
|
from datetime import date
|
|
6
6
|
from typing import TYPE_CHECKING
|
|
7
7
|
|
|
8
|
-
import httpx
|
|
9
|
-
|
|
10
8
|
from gitglimpse.providers.base import BaseLLMProvider, _warn, validate_llm_output
|
|
11
9
|
|
|
12
10
|
if TYPE_CHECKING:
|
|
13
11
|
from gitglimpse.grouping import Task
|
|
14
12
|
|
|
15
13
|
_API_URL = "https://api.openai.com/v1/chat/completions"
|
|
16
|
-
|
|
14
|
+
|
|
15
|
+
_HTTPX_INSTALL_HINT = "httpx is required for LLM features. Install with: pip install 'gitglimpse[llm]'"
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def _import_httpx():
|
|
19
|
+
try:
|
|
20
|
+
import httpx
|
|
21
|
+
return httpx
|
|
22
|
+
except ImportError:
|
|
23
|
+
raise ImportError(_HTTPX_INSTALL_HINT)
|
|
17
24
|
|
|
18
25
|
|
|
19
26
|
class OpenAIProvider(BaseLLMProvider):
|
|
@@ -25,6 +32,7 @@ class OpenAIProvider(BaseLLMProvider):
|
|
|
25
32
|
self.context_mode = context_mode
|
|
26
33
|
|
|
27
34
|
def _chat(self, user_message: str, system_prompt: str | None = None) -> str | None:
|
|
35
|
+
httpx = _import_httpx()
|
|
28
36
|
if system_prompt is None:
|
|
29
37
|
system_prompt = self.get_system_prompt()
|
|
30
38
|
headers = {
|
|
@@ -38,9 +46,10 @@ class OpenAIProvider(BaseLLMProvider):
|
|
|
38
46
|
{"role": "user", "content": user_message},
|
|
39
47
|
],
|
|
40
48
|
}
|
|
49
|
+
_timeout = httpx.Timeout(connect=30.0, read=120.0, write=30.0, pool=30.0)
|
|
41
50
|
try:
|
|
42
51
|
with _warn.status(f"[dim]Generating with {self.model}...[/dim]"):
|
|
43
|
-
resp = httpx.post(_API_URL, json=payload, headers=headers, timeout=
|
|
52
|
+
resp = httpx.post(_API_URL, json=payload, headers=headers, timeout=_timeout)
|
|
44
53
|
if resp.status_code == 401:
|
|
45
54
|
_warn.print("[yellow]⚠ OpenAI: invalid API key.[/yellow]")
|
|
46
55
|
return None
|
gitglimpse-0.1.3/PKG-INFO
DELETED
|
@@ -1,23 +0,0 @@
|
|
|
1
|
-
Metadata-Version: 2.4
|
|
2
|
-
Name: gitglimpse
|
|
3
|
-
Version: 0.1.3
|
|
4
|
-
Summary: Analyze git history and generate standup updates, daily reports, and weekly summaries.
|
|
5
|
-
Project-URL: Homepage, https://github.com/dino/gitglimpse
|
|
6
|
-
Project-URL: Repository, https://github.com/dino/gitglimpse
|
|
7
|
-
Project-URL: Bug Tracker, https://github.com/dino/gitglimpse/issues
|
|
8
|
-
Author: Dino
|
|
9
|
-
License: MIT
|
|
10
|
-
License-File: LICENSE
|
|
11
|
-
Keywords: cli,developer-tools,git,standup
|
|
12
|
-
Classifier: License :: OSI Approved :: MIT License
|
|
13
|
-
Classifier: Programming Language :: Python :: 3.11
|
|
14
|
-
Classifier: Programming Language :: Python :: 3.12
|
|
15
|
-
Classifier: Programming Language :: Python :: 3.13
|
|
16
|
-
Requires-Python: >=3.11
|
|
17
|
-
Requires-Dist: inquirerpy>=0.3.4
|
|
18
|
-
Requires-Dist: platformdirs>=4.0
|
|
19
|
-
Requires-Dist: rich>=13.0
|
|
20
|
-
Requires-Dist: tomli-w>=1.2
|
|
21
|
-
Requires-Dist: typer[all]>=0.9.0
|
|
22
|
-
Provides-Extra: llm
|
|
23
|
-
Requires-Dist: httpx>=0.27; extra == 'llm'
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
__version__ = "0.1.3"
|
|
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
|