gitdirector 1.2.2__tar.gz → 1.4.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 (43) hide show
  1. {gitdirector-1.2.2 → gitdirector-1.4.0}/PKG-INFO +57 -29
  2. {gitdirector-1.2.2 → gitdirector-1.4.0}/README.md +49 -20
  3. {gitdirector-1.2.2 → gitdirector-1.4.0}/pyproject.toml +15 -21
  4. {gitdirector-1.2.2 → gitdirector-1.4.0}/src/gitdirector/cli.py +14 -4
  5. {gitdirector-1.2.2 → gitdirector-1.4.0}/src/gitdirector/commands/autoclean.py +6 -19
  6. {gitdirector-1.2.2 → gitdirector-1.4.0}/src/gitdirector/commands/cd.py +2 -2
  7. {gitdirector-1.2.2 → gitdirector-1.4.0}/src/gitdirector/commands/help.py +3 -2
  8. gitdirector-1.4.0/src/gitdirector/commands/info.py +73 -0
  9. {gitdirector-1.2.2 → gitdirector-1.4.0}/src/gitdirector/commands/listt.py +15 -2
  10. {gitdirector-1.2.2 → gitdirector-1.4.0}/src/gitdirector/commands/pull.py +29 -5
  11. {gitdirector-1.2.2 → gitdirector-1.4.0}/src/gitdirector/commands/status.py +11 -3
  12. gitdirector-1.4.0/src/gitdirector/commands/tui/__init__.py +62 -0
  13. gitdirector-1.4.0/src/gitdirector/commands/tui/app.py +683 -0
  14. gitdirector-1.4.0/src/gitdirector/commands/tui/app_panels.py +364 -0
  15. gitdirector-1.4.0/src/gitdirector/commands/tui/app_repos.py +211 -0
  16. gitdirector-1.4.0/src/gitdirector/commands/tui/app_sessions.py +219 -0
  17. gitdirector-1.4.0/src/gitdirector/commands/tui/app_ui.py +395 -0
  18. {gitdirector-1.2.2 → gitdirector-1.4.0}/src/gitdirector/commands/tui/constants.py +35 -3
  19. gitdirector-1.4.0/src/gitdirector/commands/tui/panel_view.py +469 -0
  20. gitdirector-1.4.0/src/gitdirector/commands/tui/panels.py +658 -0
  21. gitdirector-1.4.0/src/gitdirector/commands/tui/screens.py +1805 -0
  22. gitdirector-1.4.0/src/gitdirector/commands/tui/terminal_widget.py +430 -0
  23. {gitdirector-1.2.2 → gitdirector-1.4.0}/src/gitdirector/commands/unlink.py +1 -1
  24. gitdirector-1.4.0/src/gitdirector/config.py +188 -0
  25. gitdirector-1.4.0/src/gitdirector/info.py +298 -0
  26. gitdirector-1.4.0/src/gitdirector/integrations/tmux/__init__.py +5 -0
  27. gitdirector-1.4.0/src/gitdirector/integrations/tmux/core.py +614 -0
  28. gitdirector-1.4.0/src/gitdirector/integrations/tmux/monitor.py +477 -0
  29. gitdirector-1.4.0/src/gitdirector/integrations/tmux/panels.py +800 -0
  30. {gitdirector-1.2.2 → gitdirector-1.4.0}/src/gitdirector/manager.py +28 -45
  31. gitdirector-1.4.0/src/gitdirector/repo.py +341 -0
  32. gitdirector-1.4.0/src/gitdirector/storage.py +87 -0
  33. gitdirector-1.4.0/src/gitdirector/ui_theme.py +89 -0
  34. gitdirector-1.2.2/src/gitdirector/commands/tui/__init__.py +0 -32
  35. gitdirector-1.2.2/src/gitdirector/commands/tui/app.py +0 -701
  36. gitdirector-1.2.2/src/gitdirector/commands/tui/screens.py +0 -318
  37. gitdirector-1.2.2/src/gitdirector/config.py +0 -81
  38. gitdirector-1.2.2/src/gitdirector/integrations/tmux.py +0 -118
  39. gitdirector-1.2.2/src/gitdirector/repo.py +0 -224
  40. {gitdirector-1.2.2 → gitdirector-1.4.0}/src/gitdirector/__init__.py +0 -0
  41. {gitdirector-1.2.2 → gitdirector-1.4.0}/src/gitdirector/commands/__init__.py +0 -0
  42. {gitdirector-1.2.2 → gitdirector-1.4.0}/src/gitdirector/commands/link.py +0 -0
  43. {gitdirector-1.2.2 → gitdirector-1.4.0}/src/gitdirector/integrations/__init__.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: gitdirector
3
- Version: 1.2.2
3
+ Version: 1.4.0
4
4
  Summary: A terminal based control plane for developers working across multiple repositories. Launch multiple AI coding agents, multiple tmux sessions and track changes across all your repos in one place.
5
5
  Keywords: git,repository,manager,cli,synchronization,batch
6
6
  Author: Anito Anto
@@ -11,7 +11,6 @@ Classifier: Intended Audience :: Developers
11
11
  Classifier: Operating System :: OS Independent
12
12
  Classifier: Programming Language :: Python
13
13
  Classifier: Programming Language :: Python :: 3
14
- Classifier: Programming Language :: Python :: 3.9
15
14
  Classifier: Programming Language :: Python :: 3.10
16
15
  Classifier: Programming Language :: Python :: 3.11
17
16
  Classifier: Programming Language :: Python :: 3.12
@@ -22,19 +21,19 @@ Classifier: Topic :: Utilities
22
21
  Requires-Dist: pyyaml>=6.0
23
22
  Requires-Dist: click>=8.1.0
24
23
  Requires-Dist: rich>=12.0
25
- Requires-Dist: libtmux>=0.46.2
26
24
  Requires-Dist: textual>=8.2.1
27
- Requires-Dist: faker>=37.12.0
25
+ Requires-Dist: tiktoken>=0.5.0
26
+ Requires-Dist: pyte>=0.8.2
27
+ Requires-Dist: black>=23.0 ; extra == 'dev'
28
28
  Requires-Dist: pytest>=7.0 ; extra == 'dev'
29
+ Requires-Dist: pytest-asyncio>=0.23 ; extra == 'dev'
29
30
  Requires-Dist: pytest-cov>=4.0 ; extra == 'dev'
30
- Requires-Dist: black>=23.0 ; extra == 'dev'
31
+ Requires-Dist: pytest-mock>=3.0 ; extra == 'dev'
32
+ Requires-Dist: pytest-xdist>=3.8.0 ; extra == 'dev'
31
33
  Requires-Dist: ruff>=0.1.0 ; extra == 'dev'
32
- Requires-Dist: mypy>=1.0 ; extra == 'dev'
33
- Requires-Dist: build>=1.0 ; extra == 'dev'
34
- Requires-Dist: twine>=4.0 ; extra == 'dev'
35
34
  Maintainer: Anito Anto
36
35
  Maintainer-email: Anito Anto <49053859+anitoanto@users.noreply.github.com>
37
- Requires-Python: >=3.9
36
+ Requires-Python: >=3.10
38
37
  Project-URL: Homepage, https://github.com/anitoanto/gitdirector
39
38
  Project-URL: Repository, https://github.com/anitoanto/gitdirector.git
40
39
  Project-URL: Issues, https://github.com/anitoanto/gitdirector/issues
@@ -58,23 +57,26 @@ GitDirector gives you a single cockpit for all of it. See every repo's status, D
58
57
  pip install gitdirector
59
58
  ```
60
59
 
60
+ Requires Python 3.10+.
61
+
61
62
  ## Support
62
63
 
63
64
  If you find GitDirector useful, please star this repository on GitHub, we need more stars to qualify for inclusion in Homebrew. Your support helps a lot, thank you!
64
65
 
65
66
  ## Usage
66
67
 
67
- | Command | Description |
68
- | --- | --- |
69
- | `gitdirector console` | Open the interactive TUI dashboard |
70
- | `gitdirector link PATH [--discover]` | Link a repository or discover all under a path |
68
+ | Command | Description |
69
+ | -------------------------------------------- | ------------------------------------------------------ |
70
+ | `gitdirector console` | Open the interactive TUI dashboard |
71
+ | `gitdirector link PATH [--discover]` | Link a repository or discover all under a path |
71
72
  | `gitdirector unlink PATH\|NAME [--discover]` | Unlink a repository by path, name, or all under a path |
72
- | `gitdirector list` | List all tracked repositories with live status |
73
- | `gitdirector status` | Show dirty repositories with staged/unstaged files |
74
- | `gitdirector pull` | Pull latest changes for all tracked repositories |
75
- | `gitdirector cd NAME` | Open or switch to a tmux session for a repository |
76
- | `gitdirector autoclean links\|sessions` | Clean broken links or stale tmux sessions |
77
- | `gitdirector help` | Show help |
73
+ | `gitdirector list` | List all tracked repositories with live status |
74
+ | `gitdirector status` | Show repositories with staged/unstaged files |
75
+ | `gitdirector pull` | Pull latest changes for all tracked repositories |
76
+ | `gitdirector cd NAME` | Open or switch to a tmux session for a repository |
77
+ | `gitdirector autoclean links\|sessions` | Clean broken links or stale tmux sessions |
78
+ | `gitdirector info PATH\|NAME [--full]` | Show file statistics for a repository |
79
+ | `gitdirector help` | Show help |
78
80
 
79
81
  ### link
80
82
 
@@ -94,14 +96,16 @@ Opens a full interactive TUI dashboard.
94
96
  Features:
95
97
 
96
98
  - Live table with sync state, branch, changes, last commit, and active tmux sessions
99
+ - `j`/`k` or arrow keys to navigate
97
100
  - `/` to filter repositories by name or path
98
101
  - `s` to cycle sort by any column
102
+ - `i` to show repository info (file count, lines, tokens, max depth, top file types)
99
103
  - `r` to refresh all statuses
100
104
  - Press `enter` on any repository to open an action menu:
101
- - **New tmux session** — create and attach a session for the repository
102
- - **Attach existing session** — switch to any already-running tmux session
103
- - **Launch AI agent** — open OpenCode, Claude Code, GitHub Copilot, or Codex in a new tmux session
104
- - **Remove session** — kill a running tmux session
105
+ - **New tmux session** — create and attach a session for the repository
106
+ - **Attach existing session** — switch to any already-running tmux session
107
+ - **Launch AI agent** — open OpenCode, Claude Code, GitHub Copilot, or Codex in a new tmux session
108
+ - **Remove session** — kill a running tmux session
105
109
 
106
110
  ### unlink
107
111
 
@@ -113,6 +117,25 @@ gitdirector unlink /path/to/folder --discover # unlink all repos under a path
113
117
 
114
118
  If multiple tracked repositories share the same name, `gitdirector` will refuse and list the conflicting paths so you can use the full path instead.
115
119
 
120
+ ### info
121
+
122
+ ```bash
123
+ gitdirector info /path/to/repo # by path
124
+ gitdirector info my-repo # by name
125
+ gitdirector info my-repo --full # show all file extensions
126
+ ```
127
+
128
+ Shows file statistics for a repository:
129
+
130
+ - Total file count, line count, token count, and max directory depth
131
+ - Top 10 file types by count with line and token breakdowns (use `--full` to show all)
132
+ - Files without extensions grouped as `(no ext)`
133
+ - Remaining types grouped as `others`
134
+ - Binary files show `-` for lines and tokens
135
+ - All operations respect `.gitignore` at every level
136
+
137
+ Also available in the TUI console by pressing `i` on a selected repository.
138
+
116
139
  ### list
117
140
 
118
141
  Displays a live table of all tracked repositories with:
@@ -124,7 +147,7 @@ Displays a live table of all tracked repositories with:
124
147
  - Tracked file size
125
148
  - Path
126
149
 
127
- Checks run concurrently (default: 10 workers).
150
+ Checks run concurrently (default: 10 workers, configurable from 1 to 32).
128
151
 
129
152
  ### status
130
153
 
@@ -132,7 +155,7 @@ Shows repositories with uncommitted changes (staged and/or unstaged files). Prin
132
155
 
133
156
  ### pull
134
157
 
135
- Pulls all tracked repositories concurrently using fast-forward only (`git pull --ff-only`). Reports success or failure per repository.
158
+ Pulls all tracked repositories concurrently using fast-forward only on each repository's current branch (`git pull --ff-only origin <current-branch>`). Reports success or failure per repository.
136
159
 
137
160
  ### cd
138
161
 
@@ -157,14 +180,19 @@ Config is stored at `~/.gitdirector/config.yaml`.
157
180
 
158
181
  ```yaml
159
182
  repositories:
160
- - /path/to/repo1
161
- - /path/to/repo2
162
- max_workers: 10 # optional, default 10
183
+ - /path/to/repo1
184
+ - /path/to/repo2
185
+ max_workers: 10 # optional, valid range 1-32, default 10
186
+ theme: rose-pine # optional, default rose-pine
163
187
  ```
164
188
 
189
+ ### Available Themes
190
+
191
+ `textual-dark`, `textual-light`, `nord`, `gruvbox`, `catppuccin-mocha`, `textual-ansi`, `dracula`, `tokyo-night`, `monokai`, `flexoki`, `catppuccin-latte`, `catppuccin-frappe`, `catppuccin-macchiato`, `solarized-light`, `solarized-dark`, `rose-pine`, `rose-pine-moon`, `rose-pine-dawn`, `atom-one-dark`, `atom-one-light`
192
+
165
193
  ## Requirements
166
194
 
167
- - Python 3.9+
195
+ - Python 3.10+
168
196
  - Git
169
197
  - [tmux](https://github.com/tmux/tmux) ≥ 3.2a (for `gitdirector cd`)
170
198
 
@@ -14,23 +14,26 @@ GitDirector gives you a single cockpit for all of it. See every repo's status, D
14
14
  pip install gitdirector
15
15
  ```
16
16
 
17
+ Requires Python 3.10+.
18
+
17
19
  ## Support
18
20
 
19
21
  If you find GitDirector useful, please star this repository on GitHub, we need more stars to qualify for inclusion in Homebrew. Your support helps a lot, thank you!
20
22
 
21
23
  ## Usage
22
24
 
23
- | Command | Description |
24
- | --- | --- |
25
- | `gitdirector console` | Open the interactive TUI dashboard |
26
- | `gitdirector link PATH [--discover]` | Link a repository or discover all under a path |
25
+ | Command | Description |
26
+ | -------------------------------------------- | ------------------------------------------------------ |
27
+ | `gitdirector console` | Open the interactive TUI dashboard |
28
+ | `gitdirector link PATH [--discover]` | Link a repository or discover all under a path |
27
29
  | `gitdirector unlink PATH\|NAME [--discover]` | Unlink a repository by path, name, or all under a path |
28
- | `gitdirector list` | List all tracked repositories with live status |
29
- | `gitdirector status` | Show dirty repositories with staged/unstaged files |
30
- | `gitdirector pull` | Pull latest changes for all tracked repositories |
31
- | `gitdirector cd NAME` | Open or switch to a tmux session for a repository |
32
- | `gitdirector autoclean links\|sessions` | Clean broken links or stale tmux sessions |
33
- | `gitdirector help` | Show help |
30
+ | `gitdirector list` | List all tracked repositories with live status |
31
+ | `gitdirector status` | Show repositories with staged/unstaged files |
32
+ | `gitdirector pull` | Pull latest changes for all tracked repositories |
33
+ | `gitdirector cd NAME` | Open or switch to a tmux session for a repository |
34
+ | `gitdirector autoclean links\|sessions` | Clean broken links or stale tmux sessions |
35
+ | `gitdirector info PATH\|NAME [--full]` | Show file statistics for a repository |
36
+ | `gitdirector help` | Show help |
34
37
 
35
38
  ### link
36
39
 
@@ -50,14 +53,16 @@ Opens a full interactive TUI dashboard.
50
53
  Features:
51
54
 
52
55
  - Live table with sync state, branch, changes, last commit, and active tmux sessions
56
+ - `j`/`k` or arrow keys to navigate
53
57
  - `/` to filter repositories by name or path
54
58
  - `s` to cycle sort by any column
59
+ - `i` to show repository info (file count, lines, tokens, max depth, top file types)
55
60
  - `r` to refresh all statuses
56
61
  - Press `enter` on any repository to open an action menu:
57
- - **New tmux session** — create and attach a session for the repository
58
- - **Attach existing session** — switch to any already-running tmux session
59
- - **Launch AI agent** — open OpenCode, Claude Code, GitHub Copilot, or Codex in a new tmux session
60
- - **Remove session** — kill a running tmux session
62
+ - **New tmux session** — create and attach a session for the repository
63
+ - **Attach existing session** — switch to any already-running tmux session
64
+ - **Launch AI agent** — open OpenCode, Claude Code, GitHub Copilot, or Codex in a new tmux session
65
+ - **Remove session** — kill a running tmux session
61
66
 
62
67
  ### unlink
63
68
 
@@ -69,6 +74,25 @@ gitdirector unlink /path/to/folder --discover # unlink all repos under a path
69
74
 
70
75
  If multiple tracked repositories share the same name, `gitdirector` will refuse and list the conflicting paths so you can use the full path instead.
71
76
 
77
+ ### info
78
+
79
+ ```bash
80
+ gitdirector info /path/to/repo # by path
81
+ gitdirector info my-repo # by name
82
+ gitdirector info my-repo --full # show all file extensions
83
+ ```
84
+
85
+ Shows file statistics for a repository:
86
+
87
+ - Total file count, line count, token count, and max directory depth
88
+ - Top 10 file types by count with line and token breakdowns (use `--full` to show all)
89
+ - Files without extensions grouped as `(no ext)`
90
+ - Remaining types grouped as `others`
91
+ - Binary files show `-` for lines and tokens
92
+ - All operations respect `.gitignore` at every level
93
+
94
+ Also available in the TUI console by pressing `i` on a selected repository.
95
+
72
96
  ### list
73
97
 
74
98
  Displays a live table of all tracked repositories with:
@@ -80,7 +104,7 @@ Displays a live table of all tracked repositories with:
80
104
  - Tracked file size
81
105
  - Path
82
106
 
83
- Checks run concurrently (default: 10 workers).
107
+ Checks run concurrently (default: 10 workers, configurable from 1 to 32).
84
108
 
85
109
  ### status
86
110
 
@@ -88,7 +112,7 @@ Shows repositories with uncommitted changes (staged and/or unstaged files). Prin
88
112
 
89
113
  ### pull
90
114
 
91
- Pulls all tracked repositories concurrently using fast-forward only (`git pull --ff-only`). Reports success or failure per repository.
115
+ Pulls all tracked repositories concurrently using fast-forward only on each repository's current branch (`git pull --ff-only origin <current-branch>`). Reports success or failure per repository.
92
116
 
93
117
  ### cd
94
118
 
@@ -113,14 +137,19 @@ Config is stored at `~/.gitdirector/config.yaml`.
113
137
 
114
138
  ```yaml
115
139
  repositories:
116
- - /path/to/repo1
117
- - /path/to/repo2
118
- max_workers: 10 # optional, default 10
140
+ - /path/to/repo1
141
+ - /path/to/repo2
142
+ max_workers: 10 # optional, valid range 1-32, default 10
143
+ theme: rose-pine # optional, default rose-pine
119
144
  ```
120
145
 
146
+ ### Available Themes
147
+
148
+ `textual-dark`, `textual-light`, `nord`, `gruvbox`, `catppuccin-mocha`, `textual-ansi`, `dracula`, `tokyo-night`, `monokai`, `flexoki`, `catppuccin-latte`, `catppuccin-frappe`, `catppuccin-macchiato`, `solarized-light`, `solarized-dark`, `rose-pine`, `rose-pine-moon`, `rose-pine-dawn`, `atom-one-dark`, `atom-one-light`
149
+
121
150
  ## Requirements
122
151
 
123
- - Python 3.9+
152
+ - Python 3.10+
124
153
  - Git
125
154
  - [tmux](https://github.com/tmux/tmux) ≥ 3.2a (for `gitdirector cd`)
126
155
 
@@ -4,7 +4,7 @@ build-backend = "uv_build"
4
4
 
5
5
  [project]
6
6
  name = "gitdirector"
7
- version = "1.2.2"
7
+ version = "1.4.0"
8
8
  description = "A terminal based control plane for developers working across multiple repositories. Launch multiple AI coding agents, multiple tmux sessions and track changes across all your repos in one place."
9
9
  readme = "README.md"
10
10
  license = { text = "MIT" }
@@ -14,7 +14,7 @@ authors = [
14
14
  maintainers = [
15
15
  { name = "Anito Anto", email = "49053859+anitoanto@users.noreply.github.com" }
16
16
  ]
17
- requires-python = ">=3.9"
17
+ requires-python = ">=3.10"
18
18
  keywords = ["git", "repository", "manager", "cli", "synchronization", "batch"]
19
19
  classifiers = [
20
20
  "Environment :: Console",
@@ -22,7 +22,6 @@ classifiers = [
22
22
  "Operating System :: OS Independent",
23
23
  "Programming Language :: Python",
24
24
  "Programming Language :: Python :: 3",
25
- "Programming Language :: Python :: 3.9",
26
25
  "Programming Language :: Python :: 3.10",
27
26
  "Programming Language :: Python :: 3.11",
28
27
  "Programming Language :: Python :: 3.12",
@@ -35,20 +34,20 @@ dependencies = [
35
34
  "pyyaml>=6.0",
36
35
  "click>=8.1.0",
37
36
  "rich>=12.0",
38
- "libtmux>=0.46.2",
39
37
  "textual>=8.2.1",
40
- "faker>=37.12.0",
38
+ "tiktoken>=0.5.0",
39
+ "pyte>=0.8.2",
41
40
  ]
42
41
 
43
42
  [project.optional-dependencies]
44
43
  dev = [
44
+ "black>=23.0",
45
45
  "pytest>=7.0",
46
+ "pytest-asyncio>=0.23",
46
47
  "pytest-cov>=4.0",
47
- "black>=23.0",
48
+ "pytest-mock>=3.0",
49
+ "pytest-xdist>=3.8.0",
48
50
  "ruff>=0.1.0",
49
- "mypy>=1.0",
50
- "build>=1.0",
51
- "twine>=4.0",
52
51
  ]
53
52
 
54
53
  [project.urls]
@@ -65,36 +64,31 @@ managed = true
65
64
 
66
65
  [dependency-groups]
67
66
  dev = [
67
+ "black>=23.0",
68
68
  "pytest>=7.0",
69
69
  "pytest-asyncio>=0.23",
70
70
  "pytest-cov>=4.0",
71
71
  "pytest-mock>=3.0",
72
- "black>=23.0",
72
+ "pytest-xdist>=3.8.0",
73
73
  "ruff>=0.1.0",
74
- "mypy>=1.0",
75
74
  ]
76
75
 
77
76
  [tool.black]
78
77
  line-length = 100
79
- target-version = ["py39", "py310", "py311", "py312"]
78
+ target-version = ["py310", "py311", "py312"]
80
79
 
81
80
  [tool.ruff]
82
81
  line-length = 100
83
- target-version = "py39"
82
+ target-version = "py310"
84
83
 
85
84
  [tool.ruff.lint]
86
85
  select = ["E", "F", "W", "I"]
86
+ ignore = ["E501"]
87
87
 
88
88
  [tool.ruff.lint.per-file-ignores]
89
- "__init__.py" = ["F401"]
90
-
91
- [tool.mypy]
92
- python_version = "3.12"
93
- warn_return_any = true
94
- warn_unused_configs = true
95
- disallow_untyped_defs = false
89
+ "__init__.py" = ["F401", "F403"]
96
90
 
97
91
  [tool.pytest.ini_options]
98
92
  testpaths = ["tests"]
99
- addopts = "--cov=src/gitdirector --cov-report=term-missing"
93
+ addopts = "-n auto --cov=src/gitdirector --cov-report=term-missing"
100
94
  asyncio_mode = "auto"
@@ -1,3 +1,5 @@
1
+ import subprocess
2
+
1
3
  import click
2
4
 
3
5
  from .commands import (
@@ -9,6 +11,7 @@ from .commands import (
9
11
  cd,
10
12
  console,
11
13
  help,
14
+ info,
12
15
  link,
13
16
  listt,
14
17
  pull,
@@ -29,7 +32,7 @@ __all__ = [
29
32
 
30
33
 
31
34
  class _HelpGroup(click.Group):
32
- def format_help(self, ctx, formatter):
35
+ def format_help(self, ctx, _formatter):
33
36
  show_help()
34
37
 
35
38
 
@@ -49,14 +52,21 @@ cd.register(cli)
49
52
  help.register(cli)
50
53
  tui.register(cli)
51
54
  autoclean.register(cli)
55
+ info.register(cli)
52
56
 
53
57
 
54
58
  def main():
55
59
  try:
56
60
  cli()
57
- except Exception as e:
58
- console.print(f"\n [red]Error:[/red] {str(e)}\n")
59
- raise SystemExit(1)
61
+ except (
62
+ click.ClickException,
63
+ OSError,
64
+ RuntimeError,
65
+ ValueError,
66
+ subprocess.SubprocessError,
67
+ ) as exc:
68
+ console.print(f"\n [red]Error:[/red] {str(exc)}\n")
69
+ raise SystemExit(1) from exc
60
70
 
61
71
 
62
72
  if __name__ == "__main__":
@@ -1,29 +1,16 @@
1
- import subprocess
2
-
3
1
  import click
4
2
 
5
3
  from ..config import Config
4
+ from ..integrations.tmux import _list_sessions, kill_tmux_session
6
5
  from . import console
7
6
 
8
7
 
9
8
  def _list_gd_sessions() -> list[str]:
10
- """List all tmux sessions whose names start with 'gd-'."""
11
- result = subprocess.run(
12
- ["tmux", "list-sessions", "-F", "#{session_name}"],
13
- capture_output=True,
14
- text=True,
15
- )
16
- if result.returncode != 0:
17
- return []
18
- return sorted(s for s in result.stdout.strip().split("\n") if s.startswith("gd-"))
9
+ return [s for s in _list_sessions() if s.startswith("gd/")]
19
10
 
20
11
 
21
12
  def _kill_session(name: str) -> bool:
22
- result = subprocess.run(
23
- ["tmux", "kill-session", "-t", name],
24
- capture_output=True,
25
- )
26
- return result.returncode == 0
13
+ return kill_tmux_session(name)
27
14
 
28
15
 
29
16
  def register(cli: click.Group):
@@ -49,7 +36,7 @@ def _autoclean_links():
49
36
  console.print()
50
37
  console.print(f" Found [yellow]{len(broken)}[/yellow] broken link(s):\n")
51
38
  for p in broken:
52
- console.print(f" [red]✕[/red] {p}")
39
+ console.print(f" [red]✕[/red] {p}", soft_wrap=True)
53
40
  console.print()
54
41
 
55
42
  if not click.confirm(" Remove these broken links?"):
@@ -77,7 +64,7 @@ def _autoclean_sessions():
77
64
  console.print()
78
65
  console.print(f" Found [yellow]{len(sessions)}[/yellow] gitdirector tmux session(s):\n")
79
66
  for s in sessions:
80
- console.print(f" [dim]•[/dim] {s}")
67
+ console.print(f" [dim]•[/dim] {s}", soft_wrap=True)
81
68
  console.print()
82
69
 
83
70
  if not click.confirm(f" Kill all {len(sessions)} session(s)?"):
@@ -91,7 +78,7 @@ def _autoclean_sessions():
91
78
  if _kill_session(s):
92
79
  killed += 1
93
80
  else:
94
- console.print(f" [red]Failed to kill:[/red] {s}")
81
+ console.print(f" [red]Failed to kill:[/red] {s}", soft_wrap=True)
95
82
 
96
83
  console.print()
97
84
  console.print(f" [green]Killed {killed} session(s).[/green]")
@@ -28,8 +28,8 @@ def register(cli: click.Group):
28
28
  from ..integrations.tmux import open_in_tmux
29
29
  except ImportError:
30
30
  console.print(
31
- "\n [red]libtmux is required for the cd command.[/red]\n"
32
- " Install it with: [dim]pip install libtmux[/dim]\n"
31
+ "\n [red]The tmux integration is unavailable for the cd command.[/red]\n"
32
+ " Reinstall gitdirector or check your installation.\n"
33
33
  )
34
34
  raise SystemExit(1)
35
35
 
@@ -1,7 +1,7 @@
1
1
  import click
2
2
  from rich.table import Table
3
3
 
4
- from . import get_version, console
4
+ from . import console, get_version
5
5
 
6
6
 
7
7
  def show_help():
@@ -25,13 +25,14 @@ def show_help():
25
25
 
26
26
  for cmd, desc in [
27
27
  ("link PATH [--discover]", "Link a repository or discover all repos under a path"),
28
- ("unlink PATH [--discover]", "Unlink a repository or all repos under a path"),
28
+ ("unlink PATH|NAME [--discover]", "Unlink a repository or all repos under a path"),
29
29
  ("list", "List all tracked repositories"),
30
30
  ("status", "Show status summary and per-repo details"),
31
31
  ("pull", "Pull latest changes for all tracked repositories"),
32
32
  ("cd NAME", "Open or switch to a tmux session for a repository"),
33
33
  ("console", "Interactive TUI for browsing and opening repositories"),
34
34
  ("autoclean links|sessions", "Clean broken links or stale tmux sessions"),
35
+ ("info PATH|NAME [--full]", "Show file statistics for a repository"),
35
36
  ("help", "Show this help message"),
36
37
  ]:
37
38
  cmd_table.add_row(cmd, desc)
@@ -0,0 +1,73 @@
1
+ from pathlib import Path
2
+
3
+ import click
4
+
5
+ from ..info import RepoInfoResult, gather_repo_info
6
+ from ..manager import RepositoryManager
7
+ from . import console
8
+
9
+
10
+ def _render_info_cli(result: RepoInfoResult, name: str, path: Path) -> None:
11
+ console.print()
12
+ console.print(f" [bold white]{name}[/bold white]")
13
+ console.print(f" [dim]{path}[/dim]")
14
+ console.print()
15
+ console.print(f" [dim]Files[/dim] [bold white]{result.total_files:,}[/bold white]")
16
+ console.print(f" [dim]Lines[/dim] [bold white]{result.total_lines:,}[/bold white]")
17
+ console.print(f" [dim]Tokens[/dim] [bold white]{result.total_tokens:,}[/bold white]")
18
+ console.print(f" [dim]Max Depth[/dim] [bold white]{result.max_depth}[/bold white]")
19
+ console.print()
20
+
21
+ if result.file_types:
22
+ console.print(
23
+ f" [dim]{'EXTENSION':<12} {'FILES':>6} {'LINES':>8} {'TOKENS':>10}[/dim]"
24
+ )
25
+ for ft in result.file_types:
26
+ lines_str = f"{ft.line_count:,}" if ft.line_count is not None else "-"
27
+ tokens_str = f"{ft.token_count:,}" if ft.token_count is not None else "-"
28
+ console.print(
29
+ f" [cyan]{ft.extension:<12}[/cyan] [white]{ft.count:>6}[/white]"
30
+ f" [dim]{lines_str:>8}[/dim]"
31
+ f" [dim]{tokens_str:>10}[/dim]"
32
+ )
33
+ console.print()
34
+
35
+
36
+ def register(cli: click.Group):
37
+ @cli.command()
38
+ @click.argument("target", metavar="PATH|NAME")
39
+ @click.option(
40
+ "--full",
41
+ is_flag=True,
42
+ default=False,
43
+ help="Show all file extensions without the top 10 cap.",
44
+ )
45
+ def info(target: str, full: bool):
46
+ """Show file statistics for a repository."""
47
+ manager = RepositoryManager()
48
+ candidate_path = Path(target).expanduser()
49
+
50
+ if candidate_path.is_dir() and (candidate_path / ".git").is_dir():
51
+ repo_path = candidate_path.resolve()
52
+ else:
53
+ repos = manager.config.repositories
54
+ target_lower = target.lower()
55
+ exact = [r for r in repos if r.name.lower() == target_lower]
56
+ if exact:
57
+ matches = exact
58
+ else:
59
+ matches = [r for r in repos if target_lower in r.name.lower()]
60
+
61
+ if not matches:
62
+ console.print(f"\n [red]Repository '{target}' not found[/red]\n")
63
+ raise SystemExit(1)
64
+ if len(matches) > 1:
65
+ console.print(f"\n [red]Multiple repositories match '{target}':[/red]")
66
+ for m in matches:
67
+ console.print(f" {m}")
68
+ console.print()
69
+ raise SystemExit(1)
70
+ repo_path = matches[0]
71
+
72
+ result = gather_repo_info(repo_path, full=full)
73
+ _render_info_cli(result, repo_path.name, repo_path)
@@ -5,6 +5,7 @@ from rich.live import Live
5
5
  from rich.spinner import Spinner
6
6
 
7
7
  from ..manager import RepositoryManager
8
+ from ..repo import RepositoryInfo, RepoStatus
8
9
  from . import _build_repo_table, console
9
10
 
10
11
 
@@ -25,7 +26,13 @@ def register(cli: click.Group):
25
26
  ) as live:
26
27
  with ThreadPoolExecutor(max_workers=manager.config.max_workers) as executor:
27
28
  futures = {
28
- executor.submit(manager.get_repository_status, path): path for path in paths
29
+ executor.submit(
30
+ manager.get_repository_status,
31
+ path,
32
+ fetch=True,
33
+ include_size=True,
34
+ ): path
35
+ for path in paths
29
36
  }
30
37
  remaining = len(futures)
31
38
  live.update(
@@ -33,7 +40,13 @@ def register(cli: click.Group):
33
40
  )
34
41
  for future in as_completed(futures):
35
42
  remaining -= 1
36
- results.append(future.result())
43
+ path = futures[future]
44
+ try:
45
+ results.append(future.result())
46
+ except Exception as exc:
47
+ results.append(
48
+ RepositoryInfo(path, path.name, RepoStatus.UNKNOWN, None, str(exc))
49
+ )
37
50
  done = len(results)
38
51
  if remaining > 0:
39
52
  live.update(