pyvegh 0.7.0__tar.gz → 0.8.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 (29) hide show
  1. {pyvegh-0.7.0 → pyvegh-0.8.0}/Cargo.lock +1 -1
  2. {pyvegh-0.7.0 → pyvegh-0.8.0}/Cargo.toml +1 -1
  3. {pyvegh-0.7.0 → pyvegh-0.8.0}/PKG-INFO +18 -25
  4. {pyvegh-0.7.0 → pyvegh-0.8.0}/README.md +17 -24
  5. {pyvegh-0.7.0 → pyvegh-0.8.0}/pyproject.toml +1 -1
  6. {pyvegh-0.7.0 → pyvegh-0.8.0}/python/vegh/__init__.py +3 -1
  7. pyvegh-0.8.0/python/vegh/analytics.py +281 -0
  8. pyvegh-0.8.0/python/vegh/cli.py +6 -0
  9. pyvegh-0.7.0/python/vegh/cli.py → pyvegh-0.8.0/python/vegh/cli_commands.py +115 -581
  10. pyvegh-0.8.0/python/vegh/cli_config.py +81 -0
  11. pyvegh-0.8.0/python/vegh/cli_helpers.py +124 -0
  12. pyvegh-0.8.0/python/vegh/cli_hooks.py +48 -0
  13. pyvegh-0.8.0/python/vegh/cli_main.py +98 -0
  14. pyvegh-0.8.0/python/vegh/cli_repo.py +90 -0
  15. pyvegh-0.8.0/python/vegh/config.jsonc +1116 -0
  16. pyvegh-0.8.0/python/vegh/jsonc.py +53 -0
  17. {pyvegh-0.7.0 → pyvegh-0.8.0}/src/core.rs +11 -9
  18. {pyvegh-0.7.0 → pyvegh-0.8.0}/src/lib.rs +49 -20
  19. {pyvegh-0.7.0 → pyvegh-0.8.0}/src/storage.rs +39 -37
  20. pyvegh-0.7.0/python/vegh/analytics.py +0 -621
  21. {pyvegh-0.7.0 → pyvegh-0.8.0}/.github/workflows/ci.yml +0 -0
  22. {pyvegh-0.7.0 → pyvegh-0.8.0}/.github/workflows/release.yml +0 -0
  23. {pyvegh-0.7.0 → pyvegh-0.8.0}/.github/workflows/rust-clippy.yml +0 -0
  24. {pyvegh-0.7.0 → pyvegh-0.8.0}/.gitignore +0 -0
  25. {pyvegh-0.7.0 → pyvegh-0.8.0}/LICENSE +0 -0
  26. {pyvegh-0.7.0 → pyvegh-0.8.0}/src/hash.rs +0 -0
  27. {pyvegh-0.7.0 → pyvegh-0.8.0}/tests/integration_test.sh +0 -0
  28. {pyvegh-0.7.0 → pyvegh-0.8.0}/tests/test_smoke.py +0 -0
  29. {pyvegh-0.7.0 → pyvegh-0.8.0}/uv.lock +0 -0
@@ -540,7 +540,7 @@ dependencies = [
540
540
 
541
541
  [[package]]
542
542
  name = "pyvegh"
543
- version = "0.7.0"
543
+ version = "0.8.0"
544
544
  dependencies = [
545
545
  "anyhow",
546
546
  "bincode",
@@ -1,6 +1,6 @@
1
1
  [package]
2
2
  name = "pyvegh"
3
- version = "0.7.0"
3
+ version = "0.8.0"
4
4
  edition = "2024"
5
5
  authors = ["CodeTease"]
6
6
  readme = "README.md"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pyvegh
3
- Version: 0.7.0
3
+ Version: 0.8.0
4
4
  Classifier: Programming Language :: Python :: 3.10
5
5
  Classifier: Programming Language :: Python :: 3.11
6
6
  Classifier: Programming Language :: Python :: 3.12
@@ -49,13 +49,16 @@ It delivers the raw performance of Rust (Zstd multithreaded compression, Tar arc
49
49
  ## Installation
50
50
 
51
51
  Install directly from PyPI:
52
- ```bash
52
+ ```shell
53
53
  pip install pyvegh
54
+
55
+ # Or via uv
56
+ uv pip install pyvegh
54
57
  ```
55
58
 
56
59
  Or build from source (requires Rust):
57
60
 
58
- ```bash
61
+ ```shell
59
62
  maturin develop --release
60
63
  ```
61
64
 
@@ -67,7 +70,7 @@ PyVegh provides a powerful command-line interface via the `vegh` (or `pyvegh`) c
67
70
 
68
71
  Set up your default server URL and Auth Token so you don't have to type them every time.
69
72
 
70
- ```bash
73
+ ```shell
71
74
  vegh config
72
75
  # Or one-liner:
73
76
  vegh config send --url https://api.teaserverse.online/test --auth YOUR_TOKEN
@@ -83,7 +86,7 @@ vegh config reset
83
86
 
84
87
  Pack a directory into a highly compressed snapshot.
85
88
 
86
- ```bash
89
+ ```shell
87
90
  # Basic snapshot
88
91
  vegh snap ./my-project --output backup.vegh
89
92
 
@@ -95,7 +98,7 @@ vegh snap ./my-project --dry-run
95
98
 
96
99
  View the Analytics Dashboard to break down your project by language and lines of code.
97
100
 
98
- ```bash
101
+ ```shell
99
102
  vegh loc backup.vegh
100
103
 
101
104
  # Show Source Lines of Code (SLOC) instead of total LOC
@@ -106,7 +109,7 @@ vegh loc backup.vegh --sloc
106
109
  ### 4\. Prompt
107
110
 
108
111
  Generate a structured XML context of your codebase to feed directly into ChatGPT, Claude, or Gemini.
109
- ```bash
112
+ ```shell
110
113
  # Generate XML context to stdout
111
114
  vegh prompt .
112
115
 
@@ -125,7 +128,7 @@ vegh prompt . --clean --output context.xml
125
128
 
126
129
  Clean up old snapshots to free disk space.
127
130
 
128
- ```bash
131
+ ```shell
129
132
  # Keep only the 5 most recent snapshots in the current directory
130
133
  vegh prune --keep 5
131
134
 
@@ -137,7 +140,7 @@ vegh prune --keep 1 --force
137
140
 
138
141
  Check file integrity (Blake3) and view embedded metadata.
139
142
 
140
- ```bash
143
+ ```shell
141
144
  vegh check backup.vegh
142
145
  ```
143
146
 
@@ -145,7 +148,7 @@ vegh check backup.vegh
145
148
 
146
149
  Restore the snapshot to a target directory. Supports **Partial Restore**.
147
150
 
148
- ```bash
151
+ ```shell
149
152
  # Full restore
150
153
  vegh restore backup.vegh ./restored-folder
151
154
 
@@ -160,7 +163,7 @@ vegh restore backup.vegh ./restored-folder --flatten
160
163
 
161
164
  Inspect content without extracting.
162
165
 
163
- ```bash
166
+ ```shell
164
167
  # View a file's content inside the snapshot
165
168
  vegh cat backup.vegh src/main.rs
166
169
 
@@ -175,7 +178,7 @@ vegh diff backup.vegh ./current-project
175
178
 
176
179
  Send the snapshot to a remote server. Supports **Chunked Uploads** for reliability.
177
180
 
178
- ```bash
181
+ ```shell
179
182
  # Auto-detects if chunking is needed, or force it:
180
183
  vegh send backup.vegh --force-chunk
181
184
  ```
@@ -184,7 +187,7 @@ vegh send backup.vegh --force-chunk
184
187
 
185
188
  Check your environment and installation health.
186
189
 
187
- ```bash
190
+ ```shell
188
191
  vegh doctor
189
192
  ```
190
193
 
@@ -194,18 +197,8 @@ Create a `.veghhooks.json` in your workspace.
194
197
 
195
198
  ```json
196
199
  {
197
- "hooks": {
198
- "pre": [
199
- "echo '🚀 Pre-snap hook started...'",
200
- "echo 'Preparing to backup...'",
201
- "echo 'Backing up database...'",
202
- "echo 'Backup completed.'"
203
- ],
204
- "post": [
205
- "echo '🎉 Snapshot completed!'",
206
- "echo 'Cleaning up...'"
207
- ]
208
- }
200
+ "pre": ["echo 'Checking...'", "ruff check -e"],
201
+ "post": ["echo 'Clean up...'"]
209
202
  }
210
203
  ```
211
204
 
@@ -21,13 +21,16 @@ It delivers the raw performance of Rust (Zstd multithreaded compression, Tar arc
21
21
  ## Installation
22
22
 
23
23
  Install directly from PyPI:
24
- ```bash
24
+ ```shell
25
25
  pip install pyvegh
26
+
27
+ # Or via uv
28
+ uv pip install pyvegh
26
29
  ```
27
30
 
28
31
  Or build from source (requires Rust):
29
32
 
30
- ```bash
33
+ ```shell
31
34
  maturin develop --release
32
35
  ```
33
36
 
@@ -39,7 +42,7 @@ PyVegh provides a powerful command-line interface via the `vegh` (or `pyvegh`) c
39
42
 
40
43
  Set up your default server URL and Auth Token so you don't have to type them every time.
41
44
 
42
- ```bash
45
+ ```shell
43
46
  vegh config
44
47
  # Or one-liner:
45
48
  vegh config send --url https://api.teaserverse.online/test --auth YOUR_TOKEN
@@ -55,7 +58,7 @@ vegh config reset
55
58
 
56
59
  Pack a directory into a highly compressed snapshot.
57
60
 
58
- ```bash
61
+ ```shell
59
62
  # Basic snapshot
60
63
  vegh snap ./my-project --output backup.vegh
61
64
 
@@ -67,7 +70,7 @@ vegh snap ./my-project --dry-run
67
70
 
68
71
  View the Analytics Dashboard to break down your project by language and lines of code.
69
72
 
70
- ```bash
73
+ ```shell
71
74
  vegh loc backup.vegh
72
75
 
73
76
  # Show Source Lines of Code (SLOC) instead of total LOC
@@ -78,7 +81,7 @@ vegh loc backup.vegh --sloc
78
81
  ### 4\. Prompt
79
82
 
80
83
  Generate a structured XML context of your codebase to feed directly into ChatGPT, Claude, or Gemini.
81
- ```bash
84
+ ```shell
82
85
  # Generate XML context to stdout
83
86
  vegh prompt .
84
87
 
@@ -97,7 +100,7 @@ vegh prompt . --clean --output context.xml
97
100
 
98
101
  Clean up old snapshots to free disk space.
99
102
 
100
- ```bash
103
+ ```shell
101
104
  # Keep only the 5 most recent snapshots in the current directory
102
105
  vegh prune --keep 5
103
106
 
@@ -109,7 +112,7 @@ vegh prune --keep 1 --force
109
112
 
110
113
  Check file integrity (Blake3) and view embedded metadata.
111
114
 
112
- ```bash
115
+ ```shell
113
116
  vegh check backup.vegh
114
117
  ```
115
118
 
@@ -117,7 +120,7 @@ vegh check backup.vegh
117
120
 
118
121
  Restore the snapshot to a target directory. Supports **Partial Restore**.
119
122
 
120
- ```bash
123
+ ```shell
121
124
  # Full restore
122
125
  vegh restore backup.vegh ./restored-folder
123
126
 
@@ -132,7 +135,7 @@ vegh restore backup.vegh ./restored-folder --flatten
132
135
 
133
136
  Inspect content without extracting.
134
137
 
135
- ```bash
138
+ ```shell
136
139
  # View a file's content inside the snapshot
137
140
  vegh cat backup.vegh src/main.rs
138
141
 
@@ -147,7 +150,7 @@ vegh diff backup.vegh ./current-project
147
150
 
148
151
  Send the snapshot to a remote server. Supports **Chunked Uploads** for reliability.
149
152
 
150
- ```bash
153
+ ```shell
151
154
  # Auto-detects if chunking is needed, or force it:
152
155
  vegh send backup.vegh --force-chunk
153
156
  ```
@@ -156,7 +159,7 @@ vegh send backup.vegh --force-chunk
156
159
 
157
160
  Check your environment and installation health.
158
161
 
159
- ```bash
162
+ ```shell
160
163
  vegh doctor
161
164
  ```
162
165
 
@@ -166,18 +169,8 @@ Create a `.veghhooks.json` in your workspace.
166
169
 
167
170
  ```json
168
171
  {
169
- "hooks": {
170
- "pre": [
171
- "echo '🚀 Pre-snap hook started...'",
172
- "echo 'Preparing to backup...'",
173
- "echo 'Backing up database...'",
174
- "echo 'Backup completed.'"
175
- ],
176
- "post": [
177
- "echo '🎉 Snapshot completed!'",
178
- "echo 'Cleaning up...'"
179
- ]
180
- }
172
+ "pre": ["echo 'Checking...'", "ruff check -e"],
173
+ "post": ["echo 'Clean up...'"]
181
174
  }
182
175
  ```
183
176
 
@@ -4,7 +4,7 @@ build-backend = "maturin"
4
4
 
5
5
  [project]
6
6
  name = "pyvegh"
7
- version = "0.7.0"
7
+ version = "0.8.0"
8
8
  description = "Python bindings for Vegh - The Snapshot Tool."
9
9
  authors = [{name = "CodeTease"}]
10
10
  readme = "README.md"
@@ -9,9 +9,10 @@ from ._core import (
9
9
  get_metadata,
10
10
  count_locs,
11
11
  scan_locs_dir,
12
+ read_snapshot_text,
12
13
  )
13
14
 
14
- __version__ = "0.7.0"
15
+ __version__ = "0.8.0"
15
16
  __all__ = [
16
17
  "create_snap",
17
18
  "dry_run_snap",
@@ -20,5 +21,6 @@ __all__ = [
20
21
  "get_metadata",
21
22
  "count_locs",
22
23
  "scan_locs_dir",
24
+ "read_snapshot_text",
23
25
  "__version__",
24
26
  ]
@@ -0,0 +1,281 @@
1
+ from pathlib import Path
2
+ from typing import List, Tuple, Dict
3
+ from rich.console import Console
4
+ from rich.table import Table
5
+ from rich.panel import Panel
6
+ from rich.text import Text
7
+ from rich import box
8
+ from rich.layout import Layout
9
+ from rich.align import Align
10
+
11
+ from .jsonc import parse
12
+
13
+ # Load configuration from JSON
14
+ config_path = Path(__file__).parent / "config.jsonc"
15
+ with open(config_path, "r", encoding="utf-8") as f:
16
+ config = parse(f.read())
17
+
18
+ LANG_MAP = {k: tuple(v) for k, v in config["LANG_MAP"].items()}
19
+ SLOC_IGNORE_EXTS = set(config["SLOC_IGNORE_EXTS"])
20
+ COMMENT_MAP = config["COMMENT_MAP"]
21
+ FILENAME_MAP = {k: tuple(v) for k, v in config["FILENAME_MAP"].items()}
22
+
23
+
24
+ class ProjectStats:
25
+ def __init__(self):
26
+ self.total_files = 0
27
+ self.total_loc = 0
28
+ self.lang_stats: Dict[str, Dict] = {}
29
+
30
+ def add_file(self, path_str: str, loc: int):
31
+ self.total_files += 1
32
+ self.total_loc += loc
33
+
34
+ path = Path(path_str)
35
+ # .lower() handles both .s and .S
36
+ ext = path.suffix.lower()
37
+ name = path.name.lower()
38
+
39
+ # Identify Language
40
+ lang, color = "Other", "white"
41
+
42
+ if name in FILENAME_MAP:
43
+ lang, color = FILENAME_MAP[name]
44
+ elif ext in LANG_MAP:
45
+ lang, color = LANG_MAP[ext]
46
+
47
+ # Update Stats
48
+ if lang not in self.lang_stats:
49
+ self.lang_stats[lang] = {"files": 0, "loc": 0, "color": color}
50
+
51
+ self.lang_stats[lang]["files"] += 1
52
+ self.lang_stats[lang]["loc"] += loc
53
+
54
+
55
+ def _make_bar(label: str, percent: float, color: str, width: int = 30) -> Text:
56
+ """Manually renders a progress bar using unicode blocks."""
57
+ filled_len = int((percent / 100.0) * width)
58
+ unfilled_len = width - filled_len
59
+
60
+ bar_str = ("█" * filled_len) + ("░" * unfilled_len)
61
+
62
+ text = Text()
63
+ text.append(f"{label:<20}", style=f"bold {color}")
64
+ text.append(f"{bar_str} ", style=color)
65
+ text.append(f"{percent:>5.1f}%", style="bold white")
66
+ return text
67
+
68
+
69
+ def scan_sloc(path: str) -> List[Tuple[str, int]]:
70
+ """Scans a directory for SLOC, using core dry_run_snap for traversal."""
71
+ # We need to import dry_run_snap here or pass it in.
72
+ # To avoid circular imports, we'll try to import it inside the function
73
+ from ._core import dry_run_snap
74
+
75
+ results = []
76
+
77
+ # Use dry_run_snap to get the file list (respecting .gitignore/veghignore)
78
+ # dry_run_snap returns (path, size). We just want the path.
79
+ files = dry_run_snap(path)
80
+
81
+ base_path = Path(path)
82
+
83
+ for relative_path, _ in files:
84
+ full_path = base_path / relative_path
85
+ sloc = calculate_sloc(str(full_path))
86
+ results.append((relative_path, sloc))
87
+
88
+ return results
89
+
90
+
91
+ def count_sloc_from_text(content: str, ext: str) -> int:
92
+ """Core logic to count SLOC from a string content."""
93
+ if ext in SLOC_IGNORE_EXTS:
94
+ return 0
95
+
96
+ comment_prefix = COMMENT_MAP.get(ext)
97
+ count = 0
98
+
99
+ # Process line by line
100
+ for line in content.splitlines():
101
+ line = line.strip()
102
+ if not line:
103
+ continue
104
+ if comment_prefix and line.startswith(comment_prefix):
105
+ continue
106
+ count += 1
107
+
108
+ return count
109
+
110
+
111
+ def calculate_sloc(file_path: str) -> int:
112
+ """Calculates Source Lines of Code (SLOC) from a file on disk."""
113
+ path = Path(file_path)
114
+ ext = path.suffix.lower()
115
+
116
+ if ext in SLOC_IGNORE_EXTS:
117
+ return 0
118
+
119
+ try:
120
+ # Check if file is binary
121
+ with open(file_path, "rb") as f:
122
+ chunk = f.read(512)
123
+ if b'\x00' in chunk:
124
+ return 0
125
+
126
+ # Read file with error handling
127
+ with open(file_path, "r", encoding="utf-8", errors="ignore") as f:
128
+ content = f.read()
129
+ return count_sloc_from_text(content, ext)
130
+ except Exception:
131
+ return 0
132
+
133
+
134
+ def render_dashboard(
135
+ console: Console,
136
+ file_name: str,
137
+ raw_results: List[Tuple[str, int]],
138
+ metric_name: str = "LOC",
139
+ ):
140
+ """Draws the beautiful CodeTease Analytics Dashboard."""
141
+
142
+ # 1. Process Data
143
+ stats = ProjectStats()
144
+ for path, loc in raw_results:
145
+ if loc > 0:
146
+ stats.add_file(path, loc)
147
+
148
+ if stats.total_loc == 0:
149
+ console.print(
150
+ "[yellow]No code detected (or binary only). Is this a ghost project?[/yellow]"
151
+ )
152
+ return
153
+
154
+ sorted_langs = sorted(
155
+ stats.lang_stats.items(), key=lambda item: item[1]["loc"], reverse=True
156
+ )
157
+
158
+ # 2. Build Layout
159
+ layout = Layout()
160
+ layout.split(
161
+ Layout(name="header", size=3),
162
+ Layout(name="body", ratio=1),
163
+ Layout(name="footer", size=3),
164
+ )
165
+
166
+ layout["body"].split_row(
167
+ Layout(name="left", ratio=1), Layout(name="right", ratio=1)
168
+ )
169
+
170
+ # --- Header ---
171
+ title_text = Text(
172
+ f"📊 Vegh Analytics ({metric_name}): {file_name}",
173
+ style="bold white on blue",
174
+ justify="center",
175
+ )
176
+ layout["header"].update(Panel(title_text, box=box.HEAVY))
177
+
178
+ # --- Left: Detailed Table ---
179
+ table = Table(box=box.SIMPLE_HEAD, expand=True)
180
+ table.add_column("Lang", style="bold")
181
+ table.add_column("Files", justify="right")
182
+ table.add_column(metric_name, justify="right", style="green")
183
+ table.add_column("%", justify="right")
184
+
185
+ for lang, data in sorted_langs:
186
+ percent = (data["loc"] / stats.total_loc) * 100
187
+ table.add_row(
188
+ f"[{data['color']}]{lang}[/{data['color']}]",
189
+ str(data["files"]),
190
+ f"{data['loc']:,}",
191
+ f"{percent:.1f}%",
192
+ )
193
+
194
+ layout["left"].update(
195
+ Panel(table, title="[bold]Breakdown[/bold]", border_style="cyan")
196
+ )
197
+
198
+ # --- Right: Custom Manual Bar Chart ---
199
+ chart_content = Text()
200
+
201
+ # Take Top 12 languages
202
+ for i, (lang, data) in enumerate(sorted_langs[:12]):
203
+ percent = (data["loc"] / stats.total_loc) * 100
204
+ bar = _make_bar(lang, percent, data["color"])
205
+ chart_content.append(bar)
206
+ chart_content.append("\n")
207
+
208
+ if len(sorted_langs) > 12:
209
+ chart_content.append(
210
+ f"\n... and {len(sorted_langs) - 12} others", style="dim italic"
211
+ )
212
+
213
+ layout["right"].update(
214
+ Panel(
215
+ Align.center(chart_content, vertical="middle"),
216
+ title="[bold]Distribution[/bold]",
217
+ border_style="green",
218
+ )
219
+ )
220
+
221
+ # --- Footer: Summary & Fun Comment ---
222
+ if sorted_langs:
223
+ top_lang = sorted_langs[0][0]
224
+ else:
225
+ top_lang = "Other"
226
+
227
+ comment = "Code Hard, Play Hard! 🚀"
228
+
229
+ # Logic Fun Comment
230
+ if top_lang == "Rust":
231
+ comment = "Blazingly Fast! 🦀"
232
+ elif top_lang == "Python":
233
+ comment = "Snake Charmer! 🐍"
234
+ elif top_lang == "Haskell":
235
+ comment = "Purely Functional... and confusing! 😵‍💫"
236
+ elif top_lang == "Mojo":
237
+ comment = "AI Speedster! 🔥"
238
+ elif top_lang == "Solidity":
239
+ comment = "Wen Lambo? 🏎️"
240
+ elif top_lang == "Elixir":
241
+ comment = "Scalability God! 💜"
242
+ elif top_lang == "Astro":
243
+ comment = "To the stars! 🚀"
244
+ elif top_lang == "CSS":
245
+ comment = "Center a div? Good luck! 🎨"
246
+ elif "React" in top_lang:
247
+ comment = "Component Heaven! ⚛️"
248
+ elif top_lang in ["JavaScript", "TypeScript", "Vue", "Svelte"]:
249
+ comment = "Web Scale! 🌐"
250
+ elif top_lang in ["Assembly", "C", "C++"]:
251
+ comment = "Low Level Wizardry! 🧙‍♂️"
252
+ elif top_lang in ["FDON", "FWON", "BXSON"]:
253
+ comment = "Teasers! ⚡"
254
+ elif top_lang == "HTML":
255
+ comment = "How To Meet Ladies? 😉"
256
+ elif top_lang == "Go":
257
+ comment = "Gopher it! 🐹"
258
+ elif top_lang == "Java":
259
+ comment = "Enterprise Grade! ☕"
260
+ elif top_lang == "C#":
261
+ comment = "Microsoft Magic! 🪟"
262
+ elif top_lang == "PHP":
263
+ comment = "Elephant in the room! 🐘"
264
+ elif top_lang == "Swift":
265
+ comment = "Feeling Swift? 🍏"
266
+ elif top_lang == "Dart":
267
+ comment = "Fluttering away! 🐦"
268
+ elif top_lang == "SQL":
269
+ comment = "DROP TABLE production; 💀"
270
+ elif top_lang == "Terraform":
271
+ comment = "Infrastructure as Code! 🏗️"
272
+ elif top_lang == "Dockerfile":
273
+ comment = "Containerized! 🐳"
274
+
275
+ summary = f"[bold]Total {metric_name}:[/bold] [green]{stats.total_loc:,}[/green] | [bold]Analyzed Files:[/bold] {stats.total_files} | [italic]{comment}[/italic]"
276
+
277
+ layout["footer"].update(
278
+ Panel(Text.from_markup(summary, justify="center"), border_style="blue")
279
+ )
280
+
281
+ console.print(layout)
@@ -0,0 +1,6 @@
1
+ from .cli_main import app
2
+ # noqa: F401 to expose app at package level
3
+ from . import cli_commands
4
+
5
+ if __name__ == "__main__":
6
+ app()