redgit 1.3.0__tar.gz → 1.3.2__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 (83) hide show
  1. {redgit-1.3.0/redgit.egg-info → redgit-1.3.2}/PKG-INFO +24 -5
  2. {redgit-1.3.0 → redgit-1.3.2}/README.md +21 -4
  3. {redgit-1.3.0 → redgit-1.3.2}/pyproject.toml +5 -1
  4. redgit-1.3.2/redgit/__init__.py +1 -0
  5. {redgit-1.3.0 → redgit-1.3.2}/redgit/cli.py +15 -7
  6. redgit-1.3.2/redgit/commands/backup.py +168 -0
  7. {redgit-1.3.0 → redgit-1.3.2}/redgit/commands/ci.py +2 -2
  8. {redgit-1.3.0 → redgit-1.3.2}/redgit/commands/config.py +6 -6
  9. {redgit-1.3.0 → redgit-1.3.2}/redgit/commands/daily.py +4 -4
  10. {redgit-1.3.0 → redgit-1.3.2}/redgit/commands/init.py +2 -2
  11. {redgit-1.3.0 → redgit-1.3.2}/redgit/commands/integration.py +40 -10
  12. {redgit-1.3.0 → redgit-1.3.2}/redgit/commands/notify.py +1 -1
  13. {redgit-1.3.0 → redgit-1.3.2}/redgit/commands/plugin.py +2 -2
  14. redgit-1.3.2/redgit/commands/poker.py +1111 -0
  15. {redgit-1.3.0 → redgit-1.3.2}/redgit/commands/propose.py +1032 -452
  16. {redgit-1.3.0 → redgit-1.3.2}/redgit/commands/push.py +465 -28
  17. {redgit-1.3.0 → redgit-1.3.2}/redgit/commands/quality.py +21 -13
  18. {redgit-1.3.0 → redgit-1.3.2}/redgit/commands/scout.py +385 -2
  19. {redgit-1.3.0 → redgit-1.3.2}/redgit/commands/tap.py +7 -7
  20. redgit-1.3.2/redgit/commands/tunnel.py +138 -0
  21. redgit-1.3.2/redgit/commands/webhook.py +280 -0
  22. redgit-1.3.2/redgit/core/common/__init__.py +52 -0
  23. redgit-1.3.2/redgit/core/common/backup.py +330 -0
  24. {redgit-1.3.0/redgit/core → redgit-1.3.2/redgit/core/common}/constants.py +11 -0
  25. {redgit-1.3.0/redgit/core → redgit-1.3.2/redgit/core/common}/gitops.py +121 -17
  26. {redgit-1.3.0/redgit/core → redgit-1.3.2/redgit/core/common}/llm.py +125 -4
  27. {redgit-1.3.0/redgit/core → redgit-1.3.2/redgit/core/common}/prompt.py +67 -7
  28. redgit-1.3.2/redgit/core/daily/__init__.py +9 -0
  29. redgit-1.3.2/redgit/core/poker/__init__.py +24 -0
  30. redgit-1.3.2/redgit/core/poker/ai_voter.py +176 -0
  31. redgit-1.3.2/redgit/core/poker/client.py +456 -0
  32. redgit-1.3.2/redgit/core/poker/server.py +597 -0
  33. redgit-1.3.2/redgit/core/poker/session.py +508 -0
  34. redgit-1.3.2/redgit/core/poker/ui.py +875 -0
  35. redgit-1.3.2/redgit/core/propose/__init__.py +61 -0
  36. redgit-1.3.2/redgit/core/propose/analysis.py +500 -0
  37. redgit-1.3.2/redgit/core/propose/commit.py +169 -0
  38. redgit-1.3.2/redgit/core/propose/display.py +507 -0
  39. redgit-1.3.2/redgit/core/quality/__init__.py +29 -0
  40. {redgit-1.3.0/redgit/core → redgit-1.3.2/redgit/core/quality}/semgrep.py +31 -6
  41. {redgit-1.3.0 → redgit-1.3.2}/redgit/core/scout/__init__.py +289 -9
  42. redgit-1.3.2/redgit/core/tap/__init__.py +25 -0
  43. redgit-1.3.0/redgit/core/tap.py → redgit-1.3.2/redgit/core/tap/manager.py +1 -1
  44. redgit-1.3.2/redgit/core/webhook/__init__.py +16 -0
  45. redgit-1.3.2/redgit/core/webhook/actions.py +312 -0
  46. redgit-1.3.2/redgit/core/webhook/server.py +439 -0
  47. {redgit-1.3.0 → redgit-1.3.2}/redgit/integrations/base.py +404 -5
  48. {redgit-1.3.0 → redgit-1.3.2}/redgit/integrations/registry.py +50 -4
  49. {redgit-1.3.0 → redgit-1.3.2}/redgit/plugins/registry.py +1 -1
  50. redgit-1.3.2/redgit/prompts/commit/multi_task.md +77 -0
  51. redgit-1.3.2/redgit/utils/dependency.py +165 -0
  52. {redgit-1.3.0 → redgit-1.3.2}/redgit/utils/formatting.py +1 -1
  53. redgit-1.3.2/redgit/utils/notifications.py +639 -0
  54. {redgit-1.3.0 → redgit-1.3.2/redgit.egg-info}/PKG-INFO +24 -5
  55. {redgit-1.3.0 → redgit-1.3.2}/redgit.egg-info/SOURCES.txt +33 -9
  56. {redgit-1.3.0 → redgit-1.3.2}/redgit.egg-info/requires.txt +3 -0
  57. redgit-1.3.0/redgit/__init__.py +0 -1
  58. redgit-1.3.0/redgit/utils/notifications.py +0 -264
  59. {redgit-1.3.0 → redgit-1.3.2}/LICENSE +0 -0
  60. {redgit-1.3.0 → redgit-1.3.2}/redgit/commands/__init__.py +0 -0
  61. {redgit-1.3.0/redgit/core → redgit-1.3.2/redgit/core/common}/config.py +0 -0
  62. {redgit-1.3.0/redgit/core → redgit-1.3.2/redgit/core/common}/llm_providers.json +0 -0
  63. /redgit-1.3.0/redgit/core/daily_state.py → /redgit-1.3.2/redgit/core/daily/state.py +0 -0
  64. {redgit-1.3.0 → redgit-1.3.2}/redgit/core/scout/team.py +0 -0
  65. {redgit-1.3.0 → redgit-1.3.2}/redgit/integrations/__init__.py +0 -0
  66. {redgit-1.3.0 → redgit-1.3.2}/redgit/plugins/__init__.py +0 -0
  67. {redgit-1.3.0 → redgit-1.3.2}/redgit/plugins/base.py +0 -0
  68. {redgit-1.3.0 → redgit-1.3.2}/redgit/prompts/__init__.py +0 -0
  69. {redgit-1.3.0 → redgit-1.3.2}/redgit/prompts/commit/default.md +0 -0
  70. {redgit-1.3.0 → redgit-1.3.2}/redgit/prompts/commit/minimal.md +0 -0
  71. {redgit-1.3.0 → redgit-1.3.2}/redgit/prompts/commit/task_filtered.md +0 -0
  72. {redgit-1.3.0 → redgit-1.3.2}/redgit/prompts/daily/default.md +0 -0
  73. {redgit-1.3.0 → redgit-1.3.2}/redgit/prompts/quality/default.md +0 -0
  74. {redgit-1.3.0 → redgit-1.3.2}/redgit/splash.py +0 -0
  75. {redgit-1.3.0 → redgit-1.3.2}/redgit/utils/__init__.py +0 -0
  76. {redgit-1.3.0 → redgit-1.3.2}/redgit/utils/console.py +0 -0
  77. {redgit-1.3.0 → redgit-1.3.2}/redgit/utils/editor.py +0 -0
  78. {redgit-1.3.0 → redgit-1.3.2}/redgit/utils/logging.py +0 -0
  79. {redgit-1.3.0 → redgit-1.3.2}/redgit/utils/security.py +0 -0
  80. {redgit-1.3.0 → redgit-1.3.2}/redgit.egg-info/dependency_links.txt +0 -0
  81. {redgit-1.3.0 → redgit-1.3.2}/redgit.egg-info/entry_points.txt +0 -0
  82. {redgit-1.3.0 → redgit-1.3.2}/redgit.egg-info/top_level.txt +0 -0
  83. {redgit-1.3.0 → redgit-1.3.2}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: redgit
3
- Version: 1.3.0
3
+ Version: 1.3.2
4
4
  Summary: AI-powered Git workflow assistant with task management integration
5
5
  Author-email: Your Name <your.email@example.com>
6
6
  License-Expression: MIT
@@ -35,10 +35,12 @@ Provides-Extra: dev
35
35
  Requires-Dist: pytest>=8.0.0; extra == "dev"
36
36
  Requires-Dist: pytest-cov>=4.1.0; extra == "dev"
37
37
  Requires-Dist: pytest-mock>=3.12.0; extra == "dev"
38
+ Provides-Extra: poker
39
+ Requires-Dist: websockets>=12.0; extra == "poker"
38
40
  Dynamic: license-file
39
41
 
40
42
  <p align="center">
41
- <img src="https://raw.githubusercontent.com/ertiz82/redgit/main/assets/logo.svg?v=1.3.0" alt="RedGit Logo" width="400"/>
43
+ <img src="https://raw.githubusercontent.com/ertiz82/redgit/main/assets/logo.svg?v=1.3.2" alt="RedGit Logo" width="400"/>
42
44
  </p>
43
45
 
44
46
  <p align="center">
@@ -107,6 +109,8 @@ rg push # Push and update Jira/Linear
107
109
  | **Code Quality** | Built-in quality checks with ruff/flake8 + AI analysis |
108
110
  | **Semgrep Integration** | Multi-language static analysis (35+ languages) for security & best practices |
109
111
  | **CI/CD Integration** | Trigger and monitor pipelines from the command line |
112
+ | **Planning Poker** | Real-time sprint estimation with WebSocket, task distribution, sprint creation |
113
+ | **Tunnel Support** | Expose local ports for webhooks and remote access (ngrok, cloudflare, etc.) |
110
114
  | **Plugin System** | Framework-specific prompts (Laravel, Django, etc.) |
111
115
 
112
116
  ---
@@ -207,6 +211,7 @@ RedGit supports 30+ integrations across different categories:
207
211
  | **CI/CD** | GitHub Actions, GitLab CI, Jenkins, CircleCI |
208
212
  | **Notifications** | Slack, Discord, Telegram, MS Teams |
209
213
  | **Code Quality** | SonarQube, Snyk, Codecov, Codacy |
214
+ | **Tunnel** | ngrok, Cloudflare Tunnel, localtunnel, bore, serveo |
210
215
 
211
216
  Install integrations from [RedGit Tap](https://github.com/ertiz82/redgit-tap):
212
217
 
@@ -220,17 +225,31 @@ rg install sonarqube
220
225
 
221
226
  ## Documentation
222
227
 
228
+ ### Core Documentation
229
+
223
230
  | Section | Description |
224
231
  |---------|-------------|
225
232
  | **[Getting Started](docs/getting-started.md)** | Installation and first steps |
226
233
  | **[Commands Reference](docs/commands.md)** | All CLI commands |
227
234
  | **[Configuration](docs/configuration.md)** | Config file options |
228
- | **[Integrations](docs/integrations/index.md)** | Task management, code hosting, CI/CD |
229
- | **[Plugins](docs/plugins/index.md)** | Framework plugins and release management |
230
235
  | **[Workflows](docs/workflows.md)** | Local merge vs merge request strategies |
231
- | **[Custom Integrations](docs/integrations/custom.md)** | Build your own integrations |
232
236
  | **[Troubleshooting](docs/troubleshooting.md)** | Common issues and solutions |
233
237
 
238
+ ### Features
239
+
240
+ | Feature | Description |
241
+ |---------|-------------|
242
+ | **[Planning Poker](docs/planning-poker.md)** | Real-time sprint estimation with WebSocket |
243
+ | **[Tunnel](docs/tunnel.md)** | Expose local ports for remote access |
244
+
245
+ ### Integrations & Plugins
246
+
247
+ | Section | Description |
248
+ |---------|-------------|
249
+ | **[Integrations](docs/integrations/index.md)** | Task management, code hosting, CI/CD, notifications |
250
+ | **[Plugins](docs/plugins/index.md)** | Framework plugins and release management |
251
+ | **[Custom Integrations](docs/integrations/custom.md)** | Build your own integrations |
252
+
234
253
  ---
235
254
 
236
255
  ## LLM Support
@@ -1,5 +1,5 @@
1
1
  <p align="center">
2
- <img src="https://raw.githubusercontent.com/ertiz82/redgit/main/assets/logo.svg?v=1.3.0" alt="RedGit Logo" width="400"/>
2
+ <img src="https://raw.githubusercontent.com/ertiz82/redgit/main/assets/logo.svg?v=1.3.2" alt="RedGit Logo" width="400"/>
3
3
  </p>
4
4
 
5
5
  <p align="center">
@@ -68,6 +68,8 @@ rg push # Push and update Jira/Linear
68
68
  | **Code Quality** | Built-in quality checks with ruff/flake8 + AI analysis |
69
69
  | **Semgrep Integration** | Multi-language static analysis (35+ languages) for security & best practices |
70
70
  | **CI/CD Integration** | Trigger and monitor pipelines from the command line |
71
+ | **Planning Poker** | Real-time sprint estimation with WebSocket, task distribution, sprint creation |
72
+ | **Tunnel Support** | Expose local ports for webhooks and remote access (ngrok, cloudflare, etc.) |
71
73
  | **Plugin System** | Framework-specific prompts (Laravel, Django, etc.) |
72
74
 
73
75
  ---
@@ -168,6 +170,7 @@ RedGit supports 30+ integrations across different categories:
168
170
  | **CI/CD** | GitHub Actions, GitLab CI, Jenkins, CircleCI |
169
171
  | **Notifications** | Slack, Discord, Telegram, MS Teams |
170
172
  | **Code Quality** | SonarQube, Snyk, Codecov, Codacy |
173
+ | **Tunnel** | ngrok, Cloudflare Tunnel, localtunnel, bore, serveo |
171
174
 
172
175
  Install integrations from [RedGit Tap](https://github.com/ertiz82/redgit-tap):
173
176
 
@@ -181,17 +184,31 @@ rg install sonarqube
181
184
 
182
185
  ## Documentation
183
186
 
187
+ ### Core Documentation
188
+
184
189
  | Section | Description |
185
190
  |---------|-------------|
186
191
  | **[Getting Started](docs/getting-started.md)** | Installation and first steps |
187
192
  | **[Commands Reference](docs/commands.md)** | All CLI commands |
188
193
  | **[Configuration](docs/configuration.md)** | Config file options |
189
- | **[Integrations](docs/integrations/index.md)** | Task management, code hosting, CI/CD |
190
- | **[Plugins](docs/plugins/index.md)** | Framework plugins and release management |
191
194
  | **[Workflows](docs/workflows.md)** | Local merge vs merge request strategies |
192
- | **[Custom Integrations](docs/integrations/custom.md)** | Build your own integrations |
193
195
  | **[Troubleshooting](docs/troubleshooting.md)** | Common issues and solutions |
194
196
 
197
+ ### Features
198
+
199
+ | Feature | Description |
200
+ |---------|-------------|
201
+ | **[Planning Poker](docs/planning-poker.md)** | Real-time sprint estimation with WebSocket |
202
+ | **[Tunnel](docs/tunnel.md)** | Expose local ports for remote access |
203
+
204
+ ### Integrations & Plugins
205
+
206
+ | Section | Description |
207
+ |---------|-------------|
208
+ | **[Integrations](docs/integrations/index.md)** | Task management, code hosting, CI/CD, notifications |
209
+ | **[Plugins](docs/plugins/index.md)** | Framework plugins and release management |
210
+ | **[Custom Integrations](docs/integrations/custom.md)** | Build your own integrations |
211
+
195
212
  ---
196
213
 
197
214
  ## LLM Support
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "redgit"
7
- version = "1.3.0"
7
+ version = "1.3.2"
8
8
  description = "AI-powered Git workflow assistant with task management integration"
9
9
  readme = "README.md"
10
10
  license = "MIT"
@@ -43,6 +43,9 @@ dev = [
43
43
  "pytest-cov>=4.1.0",
44
44
  "pytest-mock>=3.12.0",
45
45
  ]
46
+ poker = [
47
+ "websockets>=12.0",
48
+ ]
46
49
 
47
50
  [project.urls]
48
51
  Homepage = "https://github.com/ertiz82/redgit"
@@ -61,6 +64,7 @@ include = ["redgit*"]
61
64
  [tool.setuptools.package-data]
62
65
  redgit = [
63
66
  "core/*.json",
67
+ "core/common/*.json",
64
68
  "integrations/*.json",
65
69
  "prompts/**/*.md"
66
70
  ]
@@ -0,0 +1 @@
1
+ __version__ = "1.3.2"
@@ -18,6 +18,10 @@ from redgit.commands.ci import ci_app
18
18
  from redgit.commands.config import config_app
19
19
  from redgit.commands.quality import quality_app
20
20
  from redgit.commands.scout import scout_app
21
+ from redgit.commands.webhook import webhook_app
22
+ from redgit.commands.tunnel import tunnel_app
23
+ from redgit.commands.poker import poker_app
24
+ from redgit.commands.backup import app as backup_app
21
25
 
22
26
 
23
27
  def version_callback(value: bool):
@@ -61,12 +65,16 @@ app.add_typer(ci_app, name="ci")
61
65
  app.add_typer(config_app, name="config")
62
66
  app.add_typer(quality_app, name="quality")
63
67
  app.add_typer(scout_app, name="scout")
68
+ app.add_typer(webhook_app, name="webhook")
69
+ app.add_typer(tunnel_app, name="tunnel")
70
+ app.add_typer(poker_app, name="poker")
71
+ app.add_typer(backup_app, name="backup")
64
72
 
65
73
 
66
74
  def _load_plugin_commands():
67
75
  """Dynamically load commands from enabled plugins."""
68
76
  try:
69
- from redgit.core.config import ConfigManager
77
+ from redgit.core.common.config import ConfigManager
70
78
  from redgit.plugins.registry import get_enabled_plugin_commands, get_all_plugin_shortcuts
71
79
 
72
80
  config = ConfigManager().load()
@@ -93,13 +101,13 @@ def _load_plugin_commands():
93
101
 
94
102
 
95
103
  def _load_integration_commands():
96
- """Dynamically load commands from active integrations."""
104
+ """Dynamically load commands from installed integrations."""
97
105
  try:
98
- from redgit.core.config import ConfigManager
99
- from redgit.integrations.registry import get_active_integration_commands
106
+ from redgit.integrations.registry import get_all_integration_commands
100
107
 
101
- config = ConfigManager().load()
102
- commands = get_active_integration_commands(config)
108
+ # Load commands for ALL installed integrations (not just active ones)
109
+ # This allows `rg jira`, `rg gitlab` etc. to work regardless of activation
110
+ commands = get_all_integration_commands()
103
111
 
104
112
  for name, cmd_app in commands.items():
105
113
  app.add_typer(cmd_app, name=name)
@@ -111,7 +119,7 @@ def _load_integration_commands():
111
119
  def _setup_logging():
112
120
  """Set up logging based on config."""
113
121
  try:
114
- from redgit.core.config import ConfigManager
122
+ from redgit.core.common.config import ConfigManager
115
123
 
116
124
  config_manager = ConfigManager()
117
125
  logging_config = config_manager.get_logging_config()
@@ -0,0 +1,168 @@
1
+ """
2
+ Backup command for RedGit.
3
+
4
+ Provides CLI interface for managing working tree backups.
5
+ """
6
+
7
+ import typer
8
+ from rich.console import Console
9
+ from rich.table import Table
10
+
11
+ from ..core.common.backup import BackupManager
12
+ from ..core.common.gitops import GitOps
13
+
14
+ app = typer.Typer(help="Manage working tree backups")
15
+ console = Console()
16
+
17
+
18
+ @app.command("list")
19
+ def list_cmd():
20
+ """List all backups."""
21
+ try:
22
+ gitops = GitOps()
23
+ except Exception:
24
+ gitops = None
25
+
26
+ backup_manager = BackupManager(gitops)
27
+ backups = backup_manager.list_backups()
28
+
29
+ if not backups:
30
+ console.print("[yellow]No backups found.[/yellow]")
31
+ return
32
+
33
+ table = Table(title="RG Backups")
34
+ table.add_column("ID", style="cyan")
35
+ table.add_column("Date", style="dim")
36
+ table.add_column("Branch")
37
+ table.add_column("Files")
38
+ table.add_column("Status", style="bold")
39
+
40
+ for backup in backups:
41
+ status = backup.get("status", "unknown")
42
+ status_color = {
43
+ "created": "yellow",
44
+ "completed": "green",
45
+ "failed": "red",
46
+ "restored": "blue"
47
+ }.get(status, "white")
48
+
49
+ table.add_row(
50
+ backup.get("id", "?"),
51
+ backup.get("created_at", "")[:19],
52
+ backup.get("base_branch", "?"),
53
+ str(len(backup.get("files", []))),
54
+ f"[{status_color}]{status}[/{status_color}]"
55
+ )
56
+
57
+ console.print(table)
58
+
59
+
60
+ @app.command("restore")
61
+ def restore_cmd(
62
+ backup_id: str = typer.Argument("latest", help="Backup ID or 'latest'")
63
+ ):
64
+ """Restore working tree from backup."""
65
+ try:
66
+ gitops = GitOps()
67
+ except Exception:
68
+ gitops = None
69
+
70
+ backup_manager = BackupManager(gitops)
71
+
72
+ # Check if backup exists
73
+ backup = backup_manager.get_backup(backup_id)
74
+ if not backup:
75
+ console.print(f"[red]Backup not found: {backup_id}[/red]")
76
+ raise typer.Exit(1)
77
+
78
+ console.print(f"[yellow]Restoring backup: {backup.get('id', backup_id)}...[/yellow]")
79
+ console.print(f" Branch: {backup.get('base_branch', '?')}")
80
+ console.print(f" Files: {len(backup.get('files', []))}")
81
+
82
+ try:
83
+ manifest = backup_manager.restore_backup(backup_id)
84
+ console.print("\n[green]✓ Backup restored successfully![/green]")
85
+ console.print("\n[dim]Run 'git status' to see restored files.[/dim]")
86
+ except Exception as e:
87
+ console.print(f"\n[red]Error restoring backup: {e}[/red]")
88
+ raise typer.Exit(1)
89
+
90
+
91
+ @app.command("show")
92
+ def show_cmd(
93
+ backup_id: str = typer.Argument("latest", help="Backup ID or 'latest'")
94
+ ):
95
+ """Show backup details."""
96
+ try:
97
+ gitops = GitOps()
98
+ except Exception:
99
+ gitops = None
100
+
101
+ backup_manager = BackupManager(gitops)
102
+ manifest = backup_manager.get_backup(backup_id)
103
+
104
+ if not manifest:
105
+ console.print(f"[red]Backup not found: {backup_id}[/red]")
106
+ raise typer.Exit(1)
107
+
108
+ status = manifest.get("status", "unknown")
109
+ status_color = {
110
+ "created": "yellow",
111
+ "completed": "green",
112
+ "failed": "red",
113
+ "restored": "blue"
114
+ }.get(status, "white")
115
+
116
+ console.print(f"\n[bold cyan]Backup: {manifest.get('id', '?')}[/bold cyan]\n")
117
+ console.print(f" Created: {manifest.get('created_at', '?')}")
118
+ console.print(f" Command: {manifest.get('command', '?')}")
119
+ console.print(f" Branch: {manifest.get('base_branch', '?')}")
120
+ console.print(f" Commit: {manifest.get('head_commit', '?')}")
121
+ console.print(f" Status: [{status_color}]{status}[/{status_color}]")
122
+
123
+ if manifest.get("error"):
124
+ console.print(f"\n [red]Error: {manifest['error']}[/red]")
125
+
126
+ files = manifest.get("files", [])
127
+ console.print(f"\n Files ({len(files)}):")
128
+ for f in files[:15]:
129
+ staged = "[S]" if f.get("staged") else " "
130
+ status_char = f.get("status", "M")
131
+ # Support both "file" and "path" keys
132
+ file_path = f.get("file") or f.get("path", "?")
133
+ console.print(f" {staged} {status_char} {file_path}")
134
+
135
+ if len(files) > 15:
136
+ console.print(f" ... and {len(files) - 15} more")
137
+
138
+
139
+ @app.command("cleanup")
140
+ def cleanup_cmd(
141
+ keep: int = typer.Option(5, "--keep", "-k", help="Number of backups to keep")
142
+ ):
143
+ """Remove old backups, keep N most recent."""
144
+ try:
145
+ gitops = GitOps()
146
+ except Exception:
147
+ gitops = None
148
+
149
+ backup_manager = BackupManager(gitops)
150
+
151
+ before = len(backup_manager.list_backups())
152
+ backup_manager.cleanup_old_backups(keep=keep)
153
+ after = len(backup_manager.list_backups())
154
+
155
+ removed = before - after
156
+ if removed > 0:
157
+ console.print(f"[green]✓ Removed {removed} old backup(s). Keeping {after}.[/green]")
158
+ else:
159
+ console.print(f"[dim]No backups to remove. Total: {after}[/dim]")
160
+
161
+
162
+ # Default command (when just 'rg backup' is called)
163
+ @app.callback(invoke_without_command=True)
164
+ def main(ctx: typer.Context):
165
+ """Manage working tree backups."""
166
+ if ctx.invoked_subcommand is None:
167
+ # Default to list
168
+ list_cmd()
@@ -15,8 +15,8 @@ from typing import Optional, List
15
15
  from rich.console import Console
16
16
  from rich.table import Table
17
17
 
18
- from ..core.config import ConfigManager
19
- from ..core.gitops import GitOps
18
+ from ..core.common.config import ConfigManager
19
+ from ..core.common.gitops import GitOps
20
20
  from ..integrations.registry import get_cicd, get_integrations_by_type, IntegrationType
21
21
 
22
22
  console = Console()
@@ -18,7 +18,7 @@ from rich.tree import Tree
18
18
  import yaml
19
19
  import os
20
20
 
21
- from ..core.config import ConfigManager, CONFIG_PATH, DEFAULT_NOTIFICATIONS, DEFAULT_QUALITY
21
+ from ..core.common.config import ConfigManager, CONFIG_PATH, DEFAULT_NOTIFICATIONS, DEFAULT_QUALITY
22
22
 
23
23
  console = Console()
24
24
  config_app = typer.Typer(help="View and modify configuration")
@@ -300,7 +300,7 @@ def reset_cmd(
300
300
  console.print(f"[green]Reset '{section}' to defaults[/green]")
301
301
 
302
302
  elif section == "workflow":
303
- from ..core.config import DEFAULT_WORKFLOW
303
+ from ..core.common.config import DEFAULT_WORKFLOW
304
304
  if not force and not Confirm.ask(f"Reset '{section}' to defaults?", default=True):
305
305
  return
306
306
 
@@ -580,8 +580,8 @@ def export_prompt_cmd(
580
580
  rg config export-prompt quality # Export quality analysis prompt
581
581
  """
582
582
  from pathlib import Path
583
- from ..core.prompt import BUILTIN_PROMPTS_DIR
584
- from ..core.config import RETGIT_DIR
583
+ from ..core.common.prompt import BUILTIN_PROMPTS_DIR
584
+ from ..core.common.config import RETGIT_DIR
585
585
  from ..plugins.registry import get_plugin_by_name, get_builtin_plugins
586
586
  from ..integrations.registry import get_all_integrations
587
587
 
@@ -750,8 +750,8 @@ def export_prompt_cmd(
750
750
  def list_prompts_cmd():
751
751
  """List available prompt templates."""
752
752
  from pathlib import Path
753
- from ..core.prompt import BUILTIN_PROMPTS_DIR
754
- from ..core.config import RETGIT_DIR
753
+ from ..core.common.prompt import BUILTIN_PROMPTS_DIR
754
+ from ..core.common.config import RETGIT_DIR
755
755
  from ..plugins.registry import get_builtin_plugins
756
756
  from ..integrations.registry import get_all_integrations
757
757
 
@@ -12,10 +12,10 @@ from rich.console import Console
12
12
  from rich.panel import Panel
13
13
  from rich.table import Table
14
14
 
15
- from ..core.config import ConfigManager, RETGIT_DIR
16
- from ..core.daily_state import DailyStateManager
17
- from ..core.gitops import GitOps, NotAGitRepoError
18
- from ..core.llm import LLMClient
15
+ from ..core.common.config import ConfigManager, RETGIT_DIR
16
+ from ..core.daily.state import DailyStateManager
17
+ from ..core.common.gitops import GitOps, NotAGitRepoError
18
+ from ..core.common.llm import LLMClient
19
19
 
20
20
  console = Console()
21
21
 
@@ -3,8 +3,8 @@ import subprocess
3
3
  import typer
4
4
  from pathlib import Path
5
5
 
6
- from ..core.config import ConfigManager, RETGIT_DIR
7
- from ..core.llm import load_providers, check_provider_available
6
+ from ..core.common.config import ConfigManager, RETGIT_DIR
7
+ from ..core.common.llm import load_providers, check_provider_available
8
8
  from ..plugins.registry import detect_project_type, get_builtin_plugins
9
9
 
10
10
  # Package source directories
@@ -2,7 +2,7 @@ import json
2
2
  import typer
3
3
  from pathlib import Path
4
4
 
5
- from ..core.config import ConfigManager
5
+ from ..core.common.config import ConfigManager
6
6
  from ..integrations.registry import (
7
7
  get_builtin_integrations,
8
8
  get_all_integrations,
@@ -46,7 +46,7 @@ def _get_installed_integrations() -> set:
46
46
  - Global directory: ~/.redgit/integrations/
47
47
  - Project directory: .redgit/integrations/
48
48
  """
49
- from ..core.config import GLOBAL_INTEGRATIONS_DIR
49
+ from ..core.common.config import GLOBAL_INTEGRATIONS_DIR
50
50
 
51
51
  installed = set()
52
52
 
@@ -139,7 +139,7 @@ def list_cmd(
139
139
 
140
140
  # Show available from taps
141
141
  if all_integrations:
142
- from ..core.tap import TapManager
142
+ from ..core.tap.manager import TapManager
143
143
 
144
144
  tap_mgr = TapManager()
145
145
  tap_integrations = tap_mgr.get_all_integrations(include_installed=True)
@@ -236,7 +236,7 @@ def configure_integration(name: str):
236
236
 
237
237
  # Collect field values (overrides defaults)
238
238
  for field in schema.get("fields", []):
239
- value = _prompt_field(field)
239
+ value = _prompt_field(field, config_values)
240
240
  if value is not None:
241
241
  field_key = field.get("key") or field.get("name") or ""
242
242
  config_values[field_key] = value
@@ -300,8 +300,11 @@ def _get_field_key(field: dict) -> str:
300
300
  return field.get("key") or field.get("name") or ""
301
301
 
302
302
 
303
- def _prompt_field(field: dict):
303
+ def _prompt_field(field: dict, config_values: dict = None):
304
304
  """Prompt user for a field value"""
305
+ if config_values is None:
306
+ config_values = {}
307
+
305
308
  key = _get_field_key(field)
306
309
  prompt_text = field.get("prompt") or field.get("label") or key
307
310
  field_type = field.get("type", "text")
@@ -312,6 +315,29 @@ def _prompt_field(field: dict):
312
315
  required = field.get("required", False)
313
316
  help_text = field.get("help") or field.get("description")
314
317
  env_var = field.get("env_var")
318
+ pre_prompt = field.get("pre_prompt", [])
319
+ dynamic_help_url = field.get("dynamic_help_url")
320
+
321
+ # Show pre_prompt instructions if available
322
+ if pre_prompt:
323
+ typer.echo("")
324
+ for line in pre_prompt:
325
+ # Replace placeholders with previously collected values
326
+ for k, v in config_values.items():
327
+ if v:
328
+ line = line.replace(f"{{{k}}}", str(v))
329
+ line = line.replace(f"BOT_TOKEN", str(v)) if k == "bot_token" else line
330
+ typer.echo(f" {line}")
331
+ typer.echo("")
332
+
333
+ # Show dynamic help URL with substituted values
334
+ if dynamic_help_url:
335
+ url = dynamic_help_url
336
+ for k, v in config_values.items():
337
+ if v:
338
+ url = url.replace(f"{{{k}}}", str(v))
339
+ typer.echo(f" 🔗 {url}")
340
+ typer.echo("")
315
341
 
316
342
  # Show help text if available
317
343
  if help_text:
@@ -330,7 +356,7 @@ def _prompt_field(field: dict):
330
356
  value = typer.prompt(f" {prompt_text} (optional)", default="")
331
357
  return value if value else None
332
358
 
333
- elif field_type == "secret":
359
+ elif field_type in ("secret", "password"):
334
360
  if required:
335
361
  value = typer.prompt(f" {prompt_text}", hide_input=True)
336
362
  else:
@@ -338,14 +364,18 @@ def _prompt_field(field: dict):
338
364
  hide_input=True, default="")
339
365
  return value if value else None
340
366
 
341
- elif field_type == "choice":
342
- choices = field.get("choices", [])
367
+ elif field_type in ("choice", "select"):
368
+ choices = field.get("choices", []) or field.get("options", [])
343
369
  typer.echo(f" {prompt_text}")
344
370
  for i, choice in enumerate(choices, 1):
345
371
  marker = ">" if choice == default else " "
346
372
  typer.echo(f" {marker} [{i}] {choice}")
347
373
 
348
- choice_idx = typer.prompt(f" Select", default=str(choices.index(default) + 1) if default else "1")
374
+ default_idx = "1"
375
+ if default and default in choices:
376
+ default_idx = str(choices.index(default) + 1)
377
+
378
+ choice_idx = typer.prompt(f" Select", default=default_idx)
349
379
  try:
350
380
  idx = int(choice_idx) - 1
351
381
  return choices[idx] if 0 <= idx < len(choices) else default
@@ -436,7 +466,7 @@ def update_cmd(
436
466
  force: bool = typer.Option(False, "--force", "-f", help="Force reinstall even if up to date")
437
467
  ):
438
468
  """Update installed integrations from taps"""
439
- from ..core.tap import TapManager, find_item_in_taps
469
+ from ..core.tap.manager import TapManager, find_item_in_taps
440
470
  from .tap import install_from_tap
441
471
 
442
472
  tap_mgr = TapManager()
@@ -13,7 +13,7 @@ Usage:
13
13
  import typer
14
14
  from typing import Optional
15
15
 
16
- from ..core.config import ConfigManager
16
+ from ..core.common.config import ConfigManager
17
17
  from ..integrations.registry import get_notification, get_integrations_by_type, IntegrationType
18
18
 
19
19
 
@@ -1,7 +1,7 @@
1
1
  import typer
2
2
  from pathlib import Path
3
3
 
4
- from ..core.config import ConfigManager, GLOBAL_PLUGINS_DIR
4
+ from ..core.common.config import ConfigManager, GLOBAL_PLUGINS_DIR
5
5
  from ..plugins.registry import get_all_plugins
6
6
 
7
7
  plugin_app = typer.Typer(help="Plugin management")
@@ -52,7 +52,7 @@ def list_cmd(
52
52
 
53
53
  # Show available from taps
54
54
  if all_plugins:
55
- from ..core.tap import TapManager
55
+ from ..core.tap.manager import TapManager
56
56
 
57
57
  tap_mgr = TapManager()
58
58
  tap_plugins = tap_mgr.get_all_plugins(include_installed=True)