gitdirector 1.7.0__tar.gz → 1.7.3__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.
- {gitdirector-1.7.0 → gitdirector-1.7.3}/PKG-INFO +31 -7
- {gitdirector-1.7.0 → gitdirector-1.7.3}/README.md +29 -6
- {gitdirector-1.7.0 → gitdirector-1.7.3}/pyproject.toml +2 -1
- {gitdirector-1.7.0 → gitdirector-1.7.3}/src/gitdirector/cli.py +2 -0
- {gitdirector-1.7.0 → gitdirector-1.7.3}/src/gitdirector/commands/capture.py +1 -2
- gitdirector-1.7.3/src/gitdirector/commands/completion.py +74 -0
- {gitdirector-1.7.0 → gitdirector-1.7.3}/src/gitdirector/commands/gd_send.py +2 -1
- {gitdirector-1.7.0 → gitdirector-1.7.3}/src/gitdirector/commands/gd_tmux.py +6 -19
- {gitdirector-1.7.0 → gitdirector-1.7.3}/src/gitdirector/commands/help.py +8 -3
- {gitdirector-1.7.0 → gitdirector-1.7.3}/src/gitdirector/commands/info.py +11 -20
- {gitdirector-1.7.0 → gitdirector-1.7.3}/src/gitdirector/commands/link.py +15 -0
- gitdirector-1.7.3/src/gitdirector/commands/reset.py +82 -0
- {gitdirector-1.7.0 → gitdirector-1.7.3}/src/gitdirector/commands/tui/__init__.py +1 -2
- {gitdirector-1.7.0 → gitdirector-1.7.3}/src/gitdirector/commands/tui/app.py +60 -27
- {gitdirector-1.7.0 → gitdirector-1.7.3}/src/gitdirector/commands/tui/app_groups.py +1 -1
- {gitdirector-1.7.0 → gitdirector-1.7.3}/src/gitdirector/commands/tui/app_panels.py +16 -15
- {gitdirector-1.7.0 → gitdirector-1.7.3}/src/gitdirector/commands/tui/app_repos.py +16 -8
- {gitdirector-1.7.0 → gitdirector-1.7.3}/src/gitdirector/commands/tui/app_sessions.py +5 -7
- {gitdirector-1.7.0 → gitdirector-1.7.3}/src/gitdirector/commands/tui/app_ui.py +12 -0
- gitdirector-1.7.3/src/gitdirector/commands/tui/panel_view.py +211 -0
- {gitdirector-1.7.0 → gitdirector-1.7.3}/src/gitdirector/commands/tui/panels.py +112 -16
- {gitdirector-1.7.0 → gitdirector-1.7.3}/src/gitdirector/commands/tui/screens/commit.py +38 -13
- {gitdirector-1.7.0 → gitdirector-1.7.3}/src/gitdirector/commands/tui/screens/diff.py +1 -0
- {gitdirector-1.7.0 → gitdirector-1.7.3}/src/gitdirector/commands/tui/screens/panels.py +17 -7
- {gitdirector-1.7.0 → gitdirector-1.7.3}/src/gitdirector/commands/tui/screens/repos.py +7 -7
- {gitdirector-1.7.0 → gitdirector-1.7.3}/src/gitdirector/commands/tui/terminal_caps.py +4 -0
- {gitdirector-1.7.0 → gitdirector-1.7.3}/src/gitdirector/commands/tui/terminal_widget.py +343 -22
- gitdirector-1.7.3/src/gitdirector/commands/unlink.py +48 -0
- {gitdirector-1.7.0 → gitdirector-1.7.3}/src/gitdirector/config.py +122 -57
- gitdirector-1.7.3/src/gitdirector/integrations/tmux/__init__.py +106 -0
- {gitdirector-1.7.0 → gitdirector-1.7.3}/src/gitdirector/integrations/tmux/core.py +142 -12
- {gitdirector-1.7.0 → gitdirector-1.7.3}/src/gitdirector/integrations/tmux/monitor.py +8 -2
- {gitdirector-1.7.0 → gitdirector-1.7.3}/src/gitdirector/integrations/tmux/panels.py +85 -21
- {gitdirector-1.7.0 → gitdirector-1.7.3}/src/gitdirector/manager.py +83 -1
- {gitdirector-1.7.0 → gitdirector-1.7.3}/src/gitdirector/repo.py +11 -0
- gitdirector-1.7.0/src/gitdirector/commands/completion.py +0 -41
- gitdirector-1.7.0/src/gitdirector/commands/tui/panel_view.py +0 -478
- gitdirector-1.7.0/src/gitdirector/commands/unlink.py +0 -48
- gitdirector-1.7.0/src/gitdirector/integrations/tmux/__init__.py +0 -5
- {gitdirector-1.7.0 → gitdirector-1.7.3}/src/gitdirector/__init__.py +0 -0
- {gitdirector-1.7.0 → gitdirector-1.7.3}/src/gitdirector/commands/__init__.py +0 -0
- {gitdirector-1.7.0 → gitdirector-1.7.3}/src/gitdirector/commands/autoclean.py +0 -0
- {gitdirector-1.7.0 → gitdirector-1.7.3}/src/gitdirector/commands/cd.py +0 -0
- {gitdirector-1.7.0 → gitdirector-1.7.3}/src/gitdirector/commands/listt.py +0 -0
- {gitdirector-1.7.0 → gitdirector-1.7.3}/src/gitdirector/commands/pull.py +0 -0
- {gitdirector-1.7.0 → gitdirector-1.7.3}/src/gitdirector/commands/status.py +0 -0
- {gitdirector-1.7.0 → gitdirector-1.7.3}/src/gitdirector/commands/tui/constants.py +0 -0
- {gitdirector-1.7.0 → gitdirector-1.7.3}/src/gitdirector/commands/tui/diff_renderer.py +0 -0
- {gitdirector-1.7.0 → gitdirector-1.7.3}/src/gitdirector/commands/tui/screens/__init__.py +0 -0
- {gitdirector-1.7.0 → gitdirector-1.7.3}/src/gitdirector/commands/tui/screens/_shared.py +0 -0
- {gitdirector-1.7.0 → gitdirector-1.7.3}/src/gitdirector/commands/tui/screens/diff_files.py +0 -0
- {gitdirector-1.7.0 → gitdirector-1.7.3}/src/gitdirector/commands/tui/screens/groups.py +0 -0
- {gitdirector-1.7.0 → gitdirector-1.7.3}/src/gitdirector/commands/tui/screens/session_actions.py +0 -0
- {gitdirector-1.7.0 → gitdirector-1.7.3}/src/gitdirector/commands/tui/screens/sessions.py +0 -0
- {gitdirector-1.7.0 → gitdirector-1.7.3}/src/gitdirector/commands/tui/table_text.py +0 -0
- {gitdirector-1.7.0 → gitdirector-1.7.3}/src/gitdirector/github_credential_helper.py +0 -0
- {gitdirector-1.7.0 → gitdirector-1.7.3}/src/gitdirector/info.py +0 -0
- {gitdirector-1.7.0 → gitdirector-1.7.3}/src/gitdirector/integrations/__init__.py +0 -0
- {gitdirector-1.7.0 → gitdirector-1.7.3}/src/gitdirector/storage.py +0 -0
- {gitdirector-1.7.0 → gitdirector-1.7.3}/src/gitdirector/ui_theme.py +0 -0
- {gitdirector-1.7.0 → gitdirector-1.7.3}/src/gitdirector/version_check.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.3
|
|
2
2
|
Name: gitdirector
|
|
3
|
-
Version: 1.7.
|
|
3
|
+
Version: 1.7.3
|
|
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
|
|
@@ -24,6 +24,7 @@ Requires-Dist: rich>=12.0
|
|
|
24
24
|
Requires-Dist: textual>=8.2.1
|
|
25
25
|
Requires-Dist: tiktoken>=0.5.0
|
|
26
26
|
Requires-Dist: pyte>=0.8.2
|
|
27
|
+
Requires-Dist: pathspec>=1.0.0
|
|
27
28
|
Requires-Dist: pytest>=7.0 ; extra == 'dev'
|
|
28
29
|
Requires-Dist: pytest-asyncio>=0.23 ; extra == 'dev'
|
|
29
30
|
Requires-Dist: pytest-cov>=4.0 ; extra == 'dev'
|
|
@@ -67,13 +68,14 @@ If you find GitDirector useful, please star this repository on GitHub, we need m
|
|
|
67
68
|
| Command | Description |
|
|
68
69
|
| ---------------------------------------------------------- | ------------------------------------------------------ |
|
|
69
70
|
| `gitdirector console` | Open the interactive TUI dashboard |
|
|
70
|
-
| `gitdirector link PATH [--discover]` | Link a repository or discover all under a path
|
|
71
|
+
| `gitdirector link PATH [--discover]` | Link a repository or discover all under a path (no-op if none) |
|
|
71
72
|
| `gitdirector unlink PATH\|NAME [--discover]` | Unlink a repository by path, name, or all under a path |
|
|
72
73
|
| `gitdirector list` | List all tracked repositories with live status |
|
|
73
74
|
| `gitdirector status` | Show repositories with staged/unstaged files |
|
|
74
75
|
| `gitdirector pull [--yes]` | Pull latest changes for all tracked repositories |
|
|
75
76
|
| `gitdirector cd NAME` | Open or switch to a tmux session for a repository |
|
|
76
77
|
| `gitdirector autoclean` | Remove broken repository links from tracking |
|
|
78
|
+
| `gitdirector reset [--yes]` | Kill all sessions and panels, wipe ~/.gitdirector, and recreate the config |
|
|
77
79
|
| `gitdirector info PATH\|NAME [--full]` | Show file statistics for a repository |
|
|
78
80
|
| `gitdirector gd-tmux PATH\|NAME "command" [--description TEXT]` | Create a gd tmux session and run a command in it |
|
|
79
81
|
| `gitdirector gd-capture SESSION [--lines N] [--full]` | Print the scrollback of a live gd tmux session |
|
|
@@ -88,6 +90,8 @@ gitdirector link /path/to/repo
|
|
|
88
90
|
gitdirector link /path/to/folder --discover # recursively find and link all repos
|
|
89
91
|
```
|
|
90
92
|
|
|
93
|
+
`link --discover` succeeds without changes when the path contains no new repositories.
|
|
94
|
+
|
|
91
95
|
### console
|
|
92
96
|
|
|
93
97
|
```bash
|
|
@@ -104,17 +108,17 @@ Features:
|
|
|
104
108
|
- `/` to filter the active tab
|
|
105
109
|
- `s` to sort the active table
|
|
106
110
|
- `i` to show repository info (file count, lines, tokens, max depth, top file types)
|
|
107
|
-
- `g` on the Repositories tab to open Git operations: status, timeline, branches, remotes, pull, and
|
|
111
|
+
- `g` on the Repositories tab to open Git operations: status, timeline, branches, remotes, pull, push, and a **Diff Viewer** section with **Review Diff** for uncommitted changes
|
|
108
112
|
- `r` to refresh all statuses
|
|
109
113
|
- Press `enter` on any repository to open an action menu:
|
|
110
114
|
- **New tmux session** — create and attach a session for the repository
|
|
111
|
-
- **Review Diff** — open a two-pane viewer for uncommitted changes (left: file list with GitHub-style status pills and +/- counts; right: per-file unified diff with GitHub-dark syntax highlighting, line numbers, and green/red backgrounds for added/removed lines). Navigate files with `j`/`k`, `n`/`p`, `g`/`G`, or `]`/`[`. Switch the focused panel with `tab`. `r` refreshes, `esc` closes.
|
|
112
115
|
- **Attach existing session** — switch to any already-running tmux session
|
|
113
116
|
- **Launch AI agent** — open OpenCode, Claude Code, GitHub Copilot, Codex, or Pi in a new tmux session
|
|
114
117
|
- **Remove session** — kill a running tmux session
|
|
118
|
+
- **Review Diff** (from the `g` Git menu) — open a two-pane viewer for uncommitted changes (left: file list with GitHub-style status pills and +/- counts; right: per-file unified diff with GitHub-dark syntax highlighting, line numbers, and green/red backgrounds for added/removed lines). Navigate files with `j`/`k`, `n`/`p`, `g`/`G`, or `]`/`[`. Switch the focused panel with `tab`. `r` refreshes, `esc` closes.
|
|
115
119
|
- **Repository groups** appear directly in the Repositories tab when linked repositories share the same parent directory. Group rows can be expanded/collapsed with `space`; press `enter` on a group to start, attach, remove, or launch AI-agent sessions rooted at that parent directory.
|
|
116
120
|
- **Sessions tab** (press `2`) lists every active `gd/*` tmux session with its status, purpose, repository, **description**, and full session name. The description column is free-form text stored on the session (default `"-"`), with a width that scales to your terminal and wraps long text. Highlight a row and press `d` to edit its description.
|
|
117
|
-
- **Panels tab** (press `3`) manages reusable tmux panel layouts. Press `n` to create a panel, or `enter` on a panel to open, reconfigure, rename, or delete it.
|
|
121
|
+
- **Panels tab** (press `3`) manages reusable tmux panel layouts. Press `n` to create a panel, or `enter` on a panel to open, reconfigure, rename, or delete it. Inside a panel you can **drag with the mouse to select text and copy it to the OS clipboard** on release; pressing `y` copies the entire visible pane as a fallback. GitDirector prefers native clipboard tools such as `pbcopy`, `wl-copy`, `xclip`, `xsel`, or `clip.exe`, and falls back to OSC 52 when needed.
|
|
118
122
|
|
|
119
123
|
### unlink
|
|
120
124
|
|
|
@@ -223,6 +227,15 @@ gitdirector autoclean
|
|
|
223
227
|
|
|
224
228
|
Scans the tracked repositories in `~/.gitdirector/config.yaml` for paths that no longer exist on disk and removes them from the config. Lists any broken links for review and asks for confirmation before deleting them.
|
|
225
229
|
|
|
230
|
+
### reset
|
|
231
|
+
|
|
232
|
+
```bash
|
|
233
|
+
gitdirector reset # asks for confirmation
|
|
234
|
+
gitdirector reset --yes # skip the confirmation prompt
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
Destructive cleanup. Kills every `gd/*` tmux session (regular sessions and panels), wipes the entire `~/.gitdirector/` directory, and recreates an empty `config.yaml` with defaults. Useful when the local state is corrupted or you want to start fresh — all linked repositories, panel layouts, version-check cache, and tmux theme files are removed in one step.
|
|
238
|
+
|
|
226
239
|
### gd-capture
|
|
227
240
|
|
|
228
241
|
```bash
|
|
@@ -263,7 +276,7 @@ If you are an AI coding agent (Claude Code, OpenCode, GitHub Copilot, Codex, Pi,
|
|
|
263
276
|
|
|
264
277
|
## Configuration
|
|
265
278
|
|
|
266
|
-
|
|
279
|
+
Main config is stored at `~/.gitdirector/config.yaml`:
|
|
267
280
|
|
|
268
281
|
```yaml
|
|
269
282
|
repositories:
|
|
@@ -271,6 +284,11 @@ repositories:
|
|
|
271
284
|
- /path/to/repo2
|
|
272
285
|
max_workers: 10 # optional, valid range 1-32, default 10
|
|
273
286
|
theme: rose-pine # optional, default rose-pine
|
|
287
|
+
```
|
|
288
|
+
|
|
289
|
+
Secrets (GitHub credentials) are stored in a separate file at `~/.gitdirector/secrets.yaml`:
|
|
290
|
+
|
|
291
|
+
```yaml
|
|
274
292
|
github_username: your-github-username # optional
|
|
275
293
|
github_PAT: github_pat_or_classic_pat # optional
|
|
276
294
|
```
|
|
@@ -281,7 +299,7 @@ GitDirector first runs git commands using your normal local git credentials. If
|
|
|
281
299
|
|
|
282
300
|
- This only applies to HTTPS GitHub remotes.
|
|
283
301
|
- SSH remotes still use your normal SSH key/agent setup.
|
|
284
|
-
- The PAT is stored in plaintext in `~/.gitdirector/
|
|
302
|
+
- The PAT is stored in plaintext in `~/.gitdirector/secrets.yaml`; use an appropriately scoped token and protect that file.
|
|
285
303
|
- The PAT is not passed on the command line or shown in TUI command output.
|
|
286
304
|
|
|
287
305
|
### Available Themes
|
|
@@ -304,6 +322,12 @@ eval "$(gitdirector completion zsh)"
|
|
|
304
322
|
gitdirector completion fish | source
|
|
305
323
|
```
|
|
306
324
|
|
|
325
|
+
The `zsh` script auto-loads `compinit` if `compdef` is not yet available, so the `eval` form works in fresh shells. For the fastest setup (no subprocess on every `TAB`), write the script to a file in your `$fpath` (commonly `~/.zsh/completions/_gitdirector`) and let `compinit` autoload it:
|
|
326
|
+
|
|
327
|
+
```bash
|
|
328
|
+
gitdirector completion zsh > "${fpath[1]}/_gitdirector"
|
|
329
|
+
```
|
|
330
|
+
|
|
307
331
|
## License
|
|
308
332
|
|
|
309
333
|
MIT
|
|
@@ -25,13 +25,14 @@ If you find GitDirector useful, please star this repository on GitHub, we need m
|
|
|
25
25
|
| Command | Description |
|
|
26
26
|
| ---------------------------------------------------------- | ------------------------------------------------------ |
|
|
27
27
|
| `gitdirector console` | Open the interactive TUI dashboard |
|
|
28
|
-
| `gitdirector link PATH [--discover]` | Link a repository or discover all under a path
|
|
28
|
+
| `gitdirector link PATH [--discover]` | Link a repository or discover all under a path (no-op if none) |
|
|
29
29
|
| `gitdirector unlink PATH\|NAME [--discover]` | Unlink a repository by path, name, or all under a path |
|
|
30
30
|
| `gitdirector list` | List all tracked repositories with live status |
|
|
31
31
|
| `gitdirector status` | Show repositories with staged/unstaged files |
|
|
32
32
|
| `gitdirector pull [--yes]` | Pull latest changes for all tracked repositories |
|
|
33
33
|
| `gitdirector cd NAME` | Open or switch to a tmux session for a repository |
|
|
34
34
|
| `gitdirector autoclean` | Remove broken repository links from tracking |
|
|
35
|
+
| `gitdirector reset [--yes]` | Kill all sessions and panels, wipe ~/.gitdirector, and recreate the config |
|
|
35
36
|
| `gitdirector info PATH\|NAME [--full]` | Show file statistics for a repository |
|
|
36
37
|
| `gitdirector gd-tmux PATH\|NAME "command" [--description TEXT]` | Create a gd tmux session and run a command in it |
|
|
37
38
|
| `gitdirector gd-capture SESSION [--lines N] [--full]` | Print the scrollback of a live gd tmux session |
|
|
@@ -46,6 +47,8 @@ gitdirector link /path/to/repo
|
|
|
46
47
|
gitdirector link /path/to/folder --discover # recursively find and link all repos
|
|
47
48
|
```
|
|
48
49
|
|
|
50
|
+
`link --discover` succeeds without changes when the path contains no new repositories.
|
|
51
|
+
|
|
49
52
|
### console
|
|
50
53
|
|
|
51
54
|
```bash
|
|
@@ -62,17 +65,17 @@ Features:
|
|
|
62
65
|
- `/` to filter the active tab
|
|
63
66
|
- `s` to sort the active table
|
|
64
67
|
- `i` to show repository info (file count, lines, tokens, max depth, top file types)
|
|
65
|
-
- `g` on the Repositories tab to open Git operations: status, timeline, branches, remotes, pull, and
|
|
68
|
+
- `g` on the Repositories tab to open Git operations: status, timeline, branches, remotes, pull, push, and a **Diff Viewer** section with **Review Diff** for uncommitted changes
|
|
66
69
|
- `r` to refresh all statuses
|
|
67
70
|
- Press `enter` on any repository to open an action menu:
|
|
68
71
|
- **New tmux session** — create and attach a session for the repository
|
|
69
|
-
- **Review Diff** — open a two-pane viewer for uncommitted changes (left: file list with GitHub-style status pills and +/- counts; right: per-file unified diff with GitHub-dark syntax highlighting, line numbers, and green/red backgrounds for added/removed lines). Navigate files with `j`/`k`, `n`/`p`, `g`/`G`, or `]`/`[`. Switch the focused panel with `tab`. `r` refreshes, `esc` closes.
|
|
70
72
|
- **Attach existing session** — switch to any already-running tmux session
|
|
71
73
|
- **Launch AI agent** — open OpenCode, Claude Code, GitHub Copilot, Codex, or Pi in a new tmux session
|
|
72
74
|
- **Remove session** — kill a running tmux session
|
|
75
|
+
- **Review Diff** (from the `g` Git menu) — open a two-pane viewer for uncommitted changes (left: file list with GitHub-style status pills and +/- counts; right: per-file unified diff with GitHub-dark syntax highlighting, line numbers, and green/red backgrounds for added/removed lines). Navigate files with `j`/`k`, `n`/`p`, `g`/`G`, or `]`/`[`. Switch the focused panel with `tab`. `r` refreshes, `esc` closes.
|
|
73
76
|
- **Repository groups** appear directly in the Repositories tab when linked repositories share the same parent directory. Group rows can be expanded/collapsed with `space`; press `enter` on a group to start, attach, remove, or launch AI-agent sessions rooted at that parent directory.
|
|
74
77
|
- **Sessions tab** (press `2`) lists every active `gd/*` tmux session with its status, purpose, repository, **description**, and full session name. The description column is free-form text stored on the session (default `"-"`), with a width that scales to your terminal and wraps long text. Highlight a row and press `d` to edit its description.
|
|
75
|
-
- **Panels tab** (press `3`) manages reusable tmux panel layouts. Press `n` to create a panel, or `enter` on a panel to open, reconfigure, rename, or delete it.
|
|
78
|
+
- **Panels tab** (press `3`) manages reusable tmux panel layouts. Press `n` to create a panel, or `enter` on a panel to open, reconfigure, rename, or delete it. Inside a panel you can **drag with the mouse to select text and copy it to the OS clipboard** on release; pressing `y` copies the entire visible pane as a fallback. GitDirector prefers native clipboard tools such as `pbcopy`, `wl-copy`, `xclip`, `xsel`, or `clip.exe`, and falls back to OSC 52 when needed.
|
|
76
79
|
|
|
77
80
|
### unlink
|
|
78
81
|
|
|
@@ -181,6 +184,15 @@ gitdirector autoclean
|
|
|
181
184
|
|
|
182
185
|
Scans the tracked repositories in `~/.gitdirector/config.yaml` for paths that no longer exist on disk and removes them from the config. Lists any broken links for review and asks for confirmation before deleting them.
|
|
183
186
|
|
|
187
|
+
### reset
|
|
188
|
+
|
|
189
|
+
```bash
|
|
190
|
+
gitdirector reset # asks for confirmation
|
|
191
|
+
gitdirector reset --yes # skip the confirmation prompt
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
Destructive cleanup. Kills every `gd/*` tmux session (regular sessions and panels), wipes the entire `~/.gitdirector/` directory, and recreates an empty `config.yaml` with defaults. Useful when the local state is corrupted or you want to start fresh — all linked repositories, panel layouts, version-check cache, and tmux theme files are removed in one step.
|
|
195
|
+
|
|
184
196
|
### gd-capture
|
|
185
197
|
|
|
186
198
|
```bash
|
|
@@ -221,7 +233,7 @@ If you are an AI coding agent (Claude Code, OpenCode, GitHub Copilot, Codex, Pi,
|
|
|
221
233
|
|
|
222
234
|
## Configuration
|
|
223
235
|
|
|
224
|
-
|
|
236
|
+
Main config is stored at `~/.gitdirector/config.yaml`:
|
|
225
237
|
|
|
226
238
|
```yaml
|
|
227
239
|
repositories:
|
|
@@ -229,6 +241,11 @@ repositories:
|
|
|
229
241
|
- /path/to/repo2
|
|
230
242
|
max_workers: 10 # optional, valid range 1-32, default 10
|
|
231
243
|
theme: rose-pine # optional, default rose-pine
|
|
244
|
+
```
|
|
245
|
+
|
|
246
|
+
Secrets (GitHub credentials) are stored in a separate file at `~/.gitdirector/secrets.yaml`:
|
|
247
|
+
|
|
248
|
+
```yaml
|
|
232
249
|
github_username: your-github-username # optional
|
|
233
250
|
github_PAT: github_pat_or_classic_pat # optional
|
|
234
251
|
```
|
|
@@ -239,7 +256,7 @@ GitDirector first runs git commands using your normal local git credentials. If
|
|
|
239
256
|
|
|
240
257
|
- This only applies to HTTPS GitHub remotes.
|
|
241
258
|
- SSH remotes still use your normal SSH key/agent setup.
|
|
242
|
-
- The PAT is stored in plaintext in `~/.gitdirector/
|
|
259
|
+
- The PAT is stored in plaintext in `~/.gitdirector/secrets.yaml`; use an appropriately scoped token and protect that file.
|
|
243
260
|
- The PAT is not passed on the command line or shown in TUI command output.
|
|
244
261
|
|
|
245
262
|
### Available Themes
|
|
@@ -262,6 +279,12 @@ eval "$(gitdirector completion zsh)"
|
|
|
262
279
|
gitdirector completion fish | source
|
|
263
280
|
```
|
|
264
281
|
|
|
282
|
+
The `zsh` script auto-loads `compinit` if `compdef` is not yet available, so the `eval` form works in fresh shells. For the fastest setup (no subprocess on every `TAB`), write the script to a file in your `$fpath` (commonly `~/.zsh/completions/_gitdirector`) and let `compinit` autoload it:
|
|
283
|
+
|
|
284
|
+
```bash
|
|
285
|
+
gitdirector completion zsh > "${fpath[1]}/_gitdirector"
|
|
286
|
+
```
|
|
287
|
+
|
|
265
288
|
## License
|
|
266
289
|
|
|
267
290
|
MIT
|
|
@@ -4,7 +4,7 @@ build-backend = "uv_build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "gitdirector"
|
|
7
|
-
version = "1.7.
|
|
7
|
+
version = "1.7.3"
|
|
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" }
|
|
@@ -37,6 +37,7 @@ dependencies = [
|
|
|
37
37
|
"textual>=8.2.1",
|
|
38
38
|
"tiktoken>=0.5.0",
|
|
39
39
|
"pyte>=0.8.2",
|
|
40
|
+
"pathspec>=1.0.0",
|
|
40
41
|
]
|
|
41
42
|
|
|
42
43
|
[project.optional-dependencies]
|
|
@@ -20,6 +20,7 @@ from .commands import (
|
|
|
20
20
|
listt,
|
|
21
21
|
print_update_notice,
|
|
22
22
|
pull,
|
|
23
|
+
reset,
|
|
23
24
|
status,
|
|
24
25
|
tui,
|
|
25
26
|
unlink,
|
|
@@ -59,6 +60,7 @@ cd.register(cli)
|
|
|
59
60
|
help.register(cli)
|
|
60
61
|
tui.register(cli)
|
|
61
62
|
autoclean.register(cli)
|
|
63
|
+
reset.register(cli)
|
|
62
64
|
info.register(cli)
|
|
63
65
|
gd_tmux.register(cli)
|
|
64
66
|
capture.register(cli)
|
|
@@ -18,6 +18,7 @@ from __future__ import annotations
|
|
|
18
18
|
import click
|
|
19
19
|
|
|
20
20
|
from ..integrations.tmux import capture_pane
|
|
21
|
+
from ..integrations.tmux.core import _parse_gd_session_name
|
|
21
22
|
|
|
22
23
|
|
|
23
24
|
def _resolve_session_name(name: str) -> str:
|
|
@@ -26,8 +27,6 @@ def _resolve_session_name(name: str) -> str:
|
|
|
26
27
|
gd-capture refuses anything that doesn't match the ``gd/{repo}/{purpose}/{N}``
|
|
27
28
|
shape — that way a typo can't be silently routed to the wrong session.
|
|
28
29
|
"""
|
|
29
|
-
from ..integrations.tmux import _parse_gd_session_name
|
|
30
|
-
|
|
31
30
|
parsed = _parse_gd_session_name(name)
|
|
32
31
|
if parsed is None:
|
|
33
32
|
raise click.BadParameter(
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import click
|
|
2
|
+
from click.shell_completion import CompletionItem, get_completion_class
|
|
3
|
+
|
|
4
|
+
from ..config import Config
|
|
5
|
+
|
|
6
|
+
SUPPORTED_SHELLS = ("bash", "zsh", "fish")
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def _completion_var(prog_name: str) -> str:
|
|
10
|
+
return f"_{prog_name.replace('-', '_').replace('.', '_').upper()}_COMPLETE"
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
_ZSH_COMPINIT_BOOTSTRAP = (
|
|
14
|
+
"\n"
|
|
15
|
+
"# Ensure ``compdef`` is available when this script is ``eval``'d in a\n"
|
|
16
|
+
"# shell that has not yet loaded ``compinit``. Without this guard the\n"
|
|
17
|
+
"# ``compdef`` call below fails with ``command not found: compdef``.\n"
|
|
18
|
+
"if ! typeset -f compdef >/dev/null 2>&1; then\n"
|
|
19
|
+
" autoload -U +X compinit\n"
|
|
20
|
+
" compinit\n"
|
|
21
|
+
"fi\n"
|
|
22
|
+
)
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def _patch_zsh_source(source: str) -> str:
|
|
26
|
+
"""Inject a compinit bootstrap so ``eval`` works without prior setup.
|
|
27
|
+
|
|
28
|
+
Click's zsh output ends with ``compdef ...`` which requires the
|
|
29
|
+
``compdef`` builtin provided by ``compinit``. When the script is
|
|
30
|
+
eval'd in a fresh zsh that hasn't loaded ``compinit`` yet, that
|
|
31
|
+
final call fails. Inserting a bootstrap right after the
|
|
32
|
+
``#compdef`` directive ensures ``compdef`` is available without
|
|
33
|
+
changing behavior when the script is dropped into ``$fpath`` and
|
|
34
|
+
autoloaded by ``compinit`` (in which case ``compdef`` already
|
|
35
|
+
exists and the bootstrap is a no-op).
|
|
36
|
+
"""
|
|
37
|
+
marker = "\n_gitdirector_completion() {"
|
|
38
|
+
if marker not in source:
|
|
39
|
+
return source
|
|
40
|
+
return source.replace(marker, _ZSH_COMPINIT_BOOTSTRAP + "\n_gitdirector_completion() {", 1)
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def completion_source(cli: click.Group, shell: str, *, prog_name: str = "gitdirector") -> str:
|
|
44
|
+
complete_cls = get_completion_class(shell)
|
|
45
|
+
if complete_cls is None:
|
|
46
|
+
raise click.ClickException(f"Unsupported shell: {shell}")
|
|
47
|
+
source = complete_cls(cli, {}, prog_name, _completion_var(prog_name)).source()
|
|
48
|
+
if shell == "zsh":
|
|
49
|
+
source = _patch_zsh_source(source)
|
|
50
|
+
return source
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def complete_repository_names(
|
|
54
|
+
_ctx: click.Context, _param: click.Parameter, incomplete: str
|
|
55
|
+
) -> list[CompletionItem]:
|
|
56
|
+
try:
|
|
57
|
+
repositories = Config().repositories
|
|
58
|
+
except (OSError, RuntimeError, ValueError):
|
|
59
|
+
return []
|
|
60
|
+
|
|
61
|
+
prefix = incomplete.lower()
|
|
62
|
+
return [
|
|
63
|
+
CompletionItem(repo.name, help=str(repo))
|
|
64
|
+
for repo in sorted(repositories, key=lambda path: (path.name.lower(), str(path)))
|
|
65
|
+
if repo.name.lower().startswith(prefix)
|
|
66
|
+
]
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def register(cli: click.Group):
|
|
70
|
+
@cli.command()
|
|
71
|
+
@click.argument("shell", type=click.Choice(SUPPORTED_SHELLS))
|
|
72
|
+
def completion(shell: str):
|
|
73
|
+
"""Print shell completion setup for bash, zsh, or fish."""
|
|
74
|
+
click.echo(completion_source(cli, shell), nl=False)
|
|
@@ -2,7 +2,8 @@ from __future__ import annotations
|
|
|
2
2
|
|
|
3
3
|
import click
|
|
4
4
|
|
|
5
|
-
from ..integrations.tmux import
|
|
5
|
+
from ..integrations.tmux import send_key_to_session, send_text_to_session
|
|
6
|
+
from ..integrations.tmux.core import _parse_gd_session_name
|
|
6
7
|
|
|
7
8
|
SUPPORTED_KEYS = ("C-c",)
|
|
8
9
|
|
|
@@ -3,7 +3,6 @@ from pathlib import Path
|
|
|
3
3
|
import click
|
|
4
4
|
|
|
5
5
|
from ..manager import RepositoryManager
|
|
6
|
-
from ..storage import normalize_repository_path
|
|
7
6
|
from . import console
|
|
8
7
|
from .completion import complete_repository_names
|
|
9
8
|
|
|
@@ -17,24 +16,12 @@ def _resolve_repo(target: str) -> Path | None:
|
|
|
17
16
|
or ambiguous targets.
|
|
18
17
|
"""
|
|
19
18
|
manager = RepositoryManager()
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
or candidate.is_absolute()
|
|
26
|
-
or target.startswith("~")
|
|
27
|
-
or candidate.exists()
|
|
28
|
-
)
|
|
29
|
-
|
|
30
|
-
if is_path_like:
|
|
31
|
-
normalized = normalize_repository_path(candidate)
|
|
32
|
-
if manager.config.has_repository(normalized):
|
|
33
|
-
return normalized
|
|
34
|
-
console.print(f"\n [red]No tracked repository at path: {normalized}[/red]\n")
|
|
19
|
+
repo_path, matches, path_attempted = manager.resolve_repository_target(target)
|
|
20
|
+
if repo_path is not None:
|
|
21
|
+
return repo_path
|
|
22
|
+
if path_attempted:
|
|
23
|
+
console.print(f"\n [red]No tracked repository at path: {target}[/red]\n")
|
|
35
24
|
return None
|
|
36
|
-
|
|
37
|
-
matches = [r for r in manager.config.repositories if r.name == target]
|
|
38
25
|
if not matches:
|
|
39
26
|
console.print(f"\n [red]No tracked repository named: {target}[/red]\n")
|
|
40
27
|
return None
|
|
@@ -45,7 +32,7 @@ def _resolve_repo(target: str) -> Path | None:
|
|
|
45
32
|
f"{paths_list}\n"
|
|
46
33
|
)
|
|
47
34
|
return None
|
|
48
|
-
return
|
|
35
|
+
return None
|
|
49
36
|
|
|
50
37
|
|
|
51
38
|
def register(cli: click.Group):
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import click
|
|
2
2
|
from rich.table import Table
|
|
3
|
+
from rich.text import Text
|
|
3
4
|
|
|
4
5
|
from . import console, get_version, print_update_notice
|
|
5
6
|
|
|
@@ -25,7 +26,10 @@ def show_help():
|
|
|
25
26
|
cmd_table.add_column("desc", style="dim")
|
|
26
27
|
|
|
27
28
|
for cmd, desc in [
|
|
28
|
-
(
|
|
29
|
+
(
|
|
30
|
+
"link PATH [--discover]",
|
|
31
|
+
"Link a repository or discover all repos under a path (no-op if none)",
|
|
32
|
+
),
|
|
29
33
|
("unlink PATH|NAME [--discover]", "Unlink a repository or all repos under a path"),
|
|
30
34
|
("list", "List all tracked repositories"),
|
|
31
35
|
("status", "Show status summary and per-repo details"),
|
|
@@ -33,6 +37,7 @@ def show_help():
|
|
|
33
37
|
("cd NAME", "Open or switch to a tmux session for a repository"),
|
|
34
38
|
("console", "Interactive TUI for browsing and opening repositories"),
|
|
35
39
|
("autoclean", "Remove broken repository links from tracking"),
|
|
40
|
+
("reset [--yes]", "Kill all sessions and panels, wipe ~/.gitdirector, recreate config"),
|
|
36
41
|
("info PATH|NAME [--full]", "Show file statistics for a repository"),
|
|
37
42
|
(
|
|
38
43
|
'gd-tmux PATH|NAME "cmd" [--description "..."]',
|
|
@@ -42,11 +47,11 @@ def show_help():
|
|
|
42
47
|
"gd-capture SESSION [--lines N] [--full]",
|
|
43
48
|
"Print the current scrollback of a live gd tmux session",
|
|
44
49
|
),
|
|
45
|
-
("gd-send SESSION [TEXT] [--enter
|
|
50
|
+
("gd-send SESSION [TEXT] [--enter | --key C-c]", "Send text or C-c to a gd session"),
|
|
46
51
|
("completion SHELL", "Print shell completion setup for bash, zsh, or fish"),
|
|
47
52
|
("help", "Show this help message"),
|
|
48
53
|
]:
|
|
49
|
-
cmd_table.add_row(cmd, desc)
|
|
54
|
+
cmd_table.add_row(Text(cmd), desc)
|
|
50
55
|
|
|
51
56
|
console.print(cmd_table)
|
|
52
57
|
|
|
@@ -46,29 +46,20 @@ def register(cli: click.Group):
|
|
|
46
46
|
def info(target: str, full: bool):
|
|
47
47
|
"""Show file statistics for a repository."""
|
|
48
48
|
manager = RepositoryManager()
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
target_lower = target.lower()
|
|
56
|
-
exact = [r for r in repos if r.name.lower() == target_lower]
|
|
57
|
-
if exact:
|
|
58
|
-
matches = exact
|
|
59
|
-
else:
|
|
60
|
-
matches = [r for r in repos if target_lower in r.name.lower()]
|
|
61
|
-
|
|
49
|
+
repo_path, matches, _path_attempted = manager.resolve_repository_target(
|
|
50
|
+
target,
|
|
51
|
+
allow_untracked_git_path=True,
|
|
52
|
+
fuzzy_names=True,
|
|
53
|
+
)
|
|
54
|
+
if repo_path is None:
|
|
62
55
|
if not matches:
|
|
63
56
|
console.print(f"\n [red]Repository '{target}' not found[/red]\n")
|
|
64
57
|
raise SystemExit(1)
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
raise SystemExit(1)
|
|
71
|
-
repo_path = matches[0]
|
|
58
|
+
console.print(f"\n [red]Multiple repositories match '{target}':[/red]")
|
|
59
|
+
for match in matches:
|
|
60
|
+
console.print(f" {match}")
|
|
61
|
+
console.print()
|
|
62
|
+
raise SystemExit(1)
|
|
72
63
|
|
|
73
64
|
result = gather_repo_info(repo_path, full=full)
|
|
74
65
|
_render_info_cli(result, repo_path.name, repo_path)
|
|
@@ -28,6 +28,21 @@ def register(cli: click.Group):
|
|
|
28
28
|
else:
|
|
29
29
|
for repo_path in repos:
|
|
30
30
|
console.print(f" [green]+[/green] {repo_path}")
|
|
31
|
+
elif (
|
|
32
|
+
discover
|
|
33
|
+
and not repos
|
|
34
|
+
and message
|
|
35
|
+
in {
|
|
36
|
+
"No git repositories found",
|
|
37
|
+
"No new repositories found",
|
|
38
|
+
}
|
|
39
|
+
):
|
|
40
|
+
console.print(f" {message}")
|
|
41
|
+
for repo_path in skipped:
|
|
42
|
+
console.print(
|
|
43
|
+
f" [dim yellow]\\[skipped][/dim yellow] "
|
|
44
|
+
f"[bright_black]{repo_path}[/bright_black]"
|
|
45
|
+
)
|
|
31
46
|
else:
|
|
32
47
|
console.print(f" [red]{message}[/red]")
|
|
33
48
|
console.print()
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import shutil
|
|
2
|
+
from pathlib import Path
|
|
3
|
+
|
|
4
|
+
import click
|
|
5
|
+
|
|
6
|
+
from ..config import Config
|
|
7
|
+
from . import console
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def register(cli: click.Group):
|
|
11
|
+
@cli.command()
|
|
12
|
+
@click.option(
|
|
13
|
+
"--yes",
|
|
14
|
+
"-y",
|
|
15
|
+
is_flag=True,
|
|
16
|
+
help="Skip the confirmation prompt and reset immediately.",
|
|
17
|
+
)
|
|
18
|
+
def reset(yes: bool):
|
|
19
|
+
"""Kill all sessions and panels, wipe ~/.gitdirector, and recreate config."""
|
|
20
|
+
_reset(confirm=not yes)
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def _reset(*, confirm: bool = True) -> None:
|
|
24
|
+
config_dir = Path.home() / ".gitdirector"
|
|
25
|
+
|
|
26
|
+
if confirm and not click.confirm(
|
|
27
|
+
f" This will kill every gd tmux session and permanently delete {config_dir}. Continue?",
|
|
28
|
+
default=False,
|
|
29
|
+
):
|
|
30
|
+
console.print()
|
|
31
|
+
console.print(" [dim]Cancelled.[/dim]")
|
|
32
|
+
console.print()
|
|
33
|
+
return
|
|
34
|
+
|
|
35
|
+
console.print()
|
|
36
|
+
|
|
37
|
+
killed = _kill_all_sessions()
|
|
38
|
+
if killed:
|
|
39
|
+
console.print(f" [green]Killed {len(killed)} session(s):[/green]")
|
|
40
|
+
for name in killed:
|
|
41
|
+
console.print(f" [dim]-[/dim] {name}")
|
|
42
|
+
else:
|
|
43
|
+
console.print(" [dim]No active gd tmux sessions to kill.[/dim]")
|
|
44
|
+
|
|
45
|
+
_wipe_config_dir(config_dir)
|
|
46
|
+
|
|
47
|
+
config = _recreate_config()
|
|
48
|
+
|
|
49
|
+
console.print()
|
|
50
|
+
console.print(f" [green]Wiped and recreated:[/green] {config.config_file}")
|
|
51
|
+
console.print()
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def _kill_all_sessions() -> list[str]:
|
|
55
|
+
try:
|
|
56
|
+
from ..integrations.tmux import kill_all_gd_sessions
|
|
57
|
+
except ImportError:
|
|
58
|
+
console.print(" [yellow]tmux integration unavailable; skipping session kill.[/yellow]")
|
|
59
|
+
return []
|
|
60
|
+
|
|
61
|
+
try:
|
|
62
|
+
return kill_all_gd_sessions()
|
|
63
|
+
except Exception as exc:
|
|
64
|
+
console.print(f" [yellow]Failed to kill some sessions: {exc}[/yellow]")
|
|
65
|
+
return []
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def _wipe_config_dir(config_dir: Path) -> None:
|
|
69
|
+
if not config_dir.exists():
|
|
70
|
+
console.print(f" [dim]No existing {config_dir} to remove.[/dim]")
|
|
71
|
+
return
|
|
72
|
+
|
|
73
|
+
try:
|
|
74
|
+
shutil.rmtree(config_dir)
|
|
75
|
+
except OSError as exc:
|
|
76
|
+
raise RuntimeError(f"Failed to remove {config_dir}: {exc}") from exc
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def _recreate_config() -> Config:
|
|
80
|
+
config = Config()
|
|
81
|
+
config.clear()
|
|
82
|
+
return config
|
|
@@ -13,7 +13,7 @@ from .constants import (
|
|
|
13
13
|
_changes_label,
|
|
14
14
|
_changes_sort_key,
|
|
15
15
|
)
|
|
16
|
-
from .panel_view import
|
|
16
|
+
from .panel_view import PaneWidget
|
|
17
17
|
from .panels import Panel, PanelStore
|
|
18
18
|
from .screens import SortMenuScreen
|
|
19
19
|
from .screens.diff import DiffReviewScreen
|
|
@@ -51,7 +51,6 @@ __all__ = [
|
|
|
51
51
|
"Panel",
|
|
52
52
|
"PanelActionMenuScreen",
|
|
53
53
|
"PanelStore",
|
|
54
|
-
"PanelViewScreen",
|
|
55
54
|
"_PANELS_SORT_COLUMN_NAMES",
|
|
56
55
|
"PullLoadingScreen",
|
|
57
56
|
"PullResultScreen",
|