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.
Files changed (31) hide show
  1. gitglimpse-0.1.5/PKG-INFO +69 -0
  2. gitglimpse-0.1.5/PYPI_README.md +44 -0
  3. {gitglimpse-0.1.3 → gitglimpse-0.1.5}/pyproject.toml +6 -4
  4. gitglimpse-0.1.5/src/gitglimpse/__init__.py +1 -0
  5. {gitglimpse-0.1.3 → gitglimpse-0.1.5}/src/gitglimpse/cli.py +42 -142
  6. {gitglimpse-0.1.3 → gitglimpse-0.1.5}/src/gitglimpse/commands/pr.md +1 -1
  7. {gitglimpse-0.1.3 → gitglimpse-0.1.5}/src/gitglimpse/commands/report.md +1 -1
  8. {gitglimpse-0.1.3 → gitglimpse-0.1.5}/src/gitglimpse/commands/standup.md +1 -1
  9. {gitglimpse-0.1.3 → gitglimpse-0.1.5}/src/gitglimpse/commands/week.md +1 -1
  10. {gitglimpse-0.1.3 → gitglimpse-0.1.5}/src/gitglimpse/providers/claude.py +13 -4
  11. {gitglimpse-0.1.3 → gitglimpse-0.1.5}/src/gitglimpse/providers/gemini.py +13 -4
  12. {gitglimpse-0.1.3 → gitglimpse-0.1.5}/src/gitglimpse/providers/local.py +14 -2
  13. {gitglimpse-0.1.3 → gitglimpse-0.1.5}/src/gitglimpse/providers/openai.py +13 -4
  14. gitglimpse-0.1.3/PKG-INFO +0 -23
  15. gitglimpse-0.1.3/src/gitglimpse/__init__.py +0 -1
  16. {gitglimpse-0.1.3 → gitglimpse-0.1.5}/.gitignore +0 -0
  17. {gitglimpse-0.1.3 → gitglimpse-0.1.5}/LICENSE +0 -0
  18. {gitglimpse-0.1.3 → gitglimpse-0.1.5}/src/gitglimpse/commands/__init__.py +0 -0
  19. {gitglimpse-0.1.3 → gitglimpse-0.1.5}/src/gitglimpse/config.py +0 -0
  20. {gitglimpse-0.1.3 → gitglimpse-0.1.5}/src/gitglimpse/estimation.py +0 -0
  21. {gitglimpse-0.1.3 → gitglimpse-0.1.5}/src/gitglimpse/formatters/__init__.py +0 -0
  22. {gitglimpse-0.1.3 → gitglimpse-0.1.5}/src/gitglimpse/formatters/json.py +0 -0
  23. {gitglimpse-0.1.3 → gitglimpse-0.1.5}/src/gitglimpse/formatters/markdown.py +0 -0
  24. {gitglimpse-0.1.3 → gitglimpse-0.1.5}/src/gitglimpse/formatters/pr.py +0 -0
  25. {gitglimpse-0.1.3 → gitglimpse-0.1.5}/src/gitglimpse/formatters/template.py +0 -0
  26. {gitglimpse-0.1.3 → gitglimpse-0.1.5}/src/gitglimpse/git.py +0 -0
  27. {gitglimpse-0.1.3 → gitglimpse-0.1.5}/src/gitglimpse/grouping.py +0 -0
  28. {gitglimpse-0.1.3 → gitglimpse-0.1.5}/src/gitglimpse/onboarding.py +0 -0
  29. {gitglimpse-0.1.3 → gitglimpse-0.1.5}/src/gitglimpse/providers/__init__.py +0 -0
  30. {gitglimpse-0.1.3 → gitglimpse-0.1.5}/src/gitglimpse/providers/base.py +0 -0
  31. {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.3"
8
- description = "Analyze git history and generate standup updates, daily reports, and weekly summaries."
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
- console.print(f"[dim]Found {len(repos)} projects: {names}[/dim]", highlight=False)
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 --context diffs
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
- llm_output = provider.summarize_standup(tasks, report_date, diff_snippets)
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
- md = llm_output if llm_output else format_report(tasks, report_date)
576
-
577
- _print_status_line(resolved_author, active_provider, ctx_mode)
578
- if output:
579
- Path(output).write_text(md, encoding="utf-8")
580
- console.print(f"Report saved to [bold]{output}[/bold]")
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
- console.print(md, markup=False, highlight=False)
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 pull request summary from your current branch.
3
3
  Run the following shell command and capture its output:
4
4
 
5
5
  ```
6
- glimpse pr --json
6
+ glimpse pr --json --context both
7
7
  ```
8
8
 
9
9
  Then format the JSON result into a clean PR description using this structure:
@@ -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:
@@ -3,7 +3,7 @@ Generate a weekly summary of your git activity.
3
3
  Run the following shell command and capture its output:
4
4
 
5
5
  ```
6
- glimpse week --json
6
+ glimpse week --json --context both
7
7
  ```
8
8
 
9
9
  Then format the JSON result into a **weekly summary** using 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=_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
- _TIMEOUT = httpx.Timeout(connect=30.0, read=120.0, write=30.0, pool=30.0)
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=_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
- _TIMEOUT = httpx.Timeout(connect=30.0, read=120.0, write=30.0, pool=30.0)
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=_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