aline-ai 0.2.5__py3-none-any.whl → 0.3.0__py3-none-any.whl

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 (45) hide show
  1. {aline_ai-0.2.5.dist-info → aline_ai-0.3.0.dist-info}/METADATA +3 -1
  2. aline_ai-0.3.0.dist-info/RECORD +41 -0
  3. aline_ai-0.3.0.dist-info/entry_points.txt +3 -0
  4. realign/__init__.py +32 -1
  5. realign/cli.py +203 -19
  6. realign/commands/__init__.py +2 -2
  7. realign/commands/clean.py +149 -0
  8. realign/commands/config.py +1 -1
  9. realign/commands/export_shares.py +1785 -0
  10. realign/commands/hide.py +112 -24
  11. realign/commands/import_history.py +873 -0
  12. realign/commands/init.py +104 -217
  13. realign/commands/mirror.py +131 -0
  14. realign/commands/pull.py +101 -0
  15. realign/commands/push.py +155 -245
  16. realign/commands/review.py +216 -54
  17. realign/commands/session_utils.py +139 -4
  18. realign/commands/share.py +965 -0
  19. realign/commands/status.py +559 -0
  20. realign/commands/sync.py +91 -0
  21. realign/commands/undo.py +423 -0
  22. realign/commands/watcher.py +805 -0
  23. realign/config.py +21 -10
  24. realign/file_lock.py +3 -1
  25. realign/hash_registry.py +310 -0
  26. realign/hooks.py +368 -384
  27. realign/logging_config.py +2 -2
  28. realign/mcp_server.py +263 -549
  29. realign/mcp_watcher.py +999 -142
  30. realign/mirror_utils.py +322 -0
  31. realign/prompts/__init__.py +21 -0
  32. realign/prompts/presets.py +238 -0
  33. realign/redactor.py +168 -16
  34. realign/tracker/__init__.py +9 -0
  35. realign/tracker/git_tracker.py +1123 -0
  36. realign/watcher_daemon.py +115 -0
  37. aline_ai-0.2.5.dist-info/RECORD +0 -28
  38. aline_ai-0.2.5.dist-info/entry_points.txt +0 -5
  39. realign/commands/auto_commit.py +0 -231
  40. realign/commands/commit.py +0 -379
  41. realign/commands/search.py +0 -449
  42. realign/commands/show.py +0 -416
  43. {aline_ai-0.2.5.dist-info → aline_ai-0.3.0.dist-info}/WHEEL +0 -0
  44. {aline_ai-0.2.5.dist-info → aline_ai-0.3.0.dist-info}/licenses/LICENSE +0 -0
  45. {aline_ai-0.2.5.dist-info → aline_ai-0.3.0.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,115 @@
1
+ #!/usr/bin/env python3
2
+ """Standalone watcher daemon for ReAlign.
3
+
4
+ This daemon runs the DialogueWatcher in the background without MCP.
5
+ It monitors session files and auto-commits changes to .realign/.git.
6
+ """
7
+
8
+ import asyncio
9
+ import sys
10
+ from pathlib import Path
11
+ import signal
12
+ import atexit
13
+ import os
14
+
15
+ # Handle both relative and absolute imports for script and module execution
16
+ try:
17
+ from .mcp_watcher import DialogueWatcher
18
+ from .logging_config import setup_logger
19
+ except ImportError:
20
+ from realign.mcp_watcher import DialogueWatcher
21
+ from realign.logging_config import setup_logger
22
+
23
+ # Initialize logger
24
+ logger = setup_logger('realign.daemon', 'watcher_daemon.log')
25
+
26
+
27
+ def get_pid_file() -> Path:
28
+ """Get the path to the PID file."""
29
+ return Path.home() / '.aline/.logs/watcher.pid'
30
+
31
+
32
+ def write_pid():
33
+ """Write current process PID to file."""
34
+ pid_file = get_pid_file()
35
+ pid_file.parent.mkdir(parents=True, exist_ok=True)
36
+ pid_file.write_text(str(os.getpid()))
37
+
38
+
39
+ def remove_pid():
40
+ """Remove PID file."""
41
+ try:
42
+ get_pid_file().unlink(missing_ok=True)
43
+ except Exception as e:
44
+ logger.error(f"Failed to remove PID file: {e}")
45
+
46
+
47
+ async def run_daemon():
48
+ """Run the watcher daemon."""
49
+ watcher = None
50
+
51
+ # Shutdown handler
52
+ def handle_shutdown(signum, frame):
53
+ """Handle shutdown signals gracefully."""
54
+ logger.info(f"Received signal {signum}, shutting down...")
55
+ remove_pid()
56
+ sys.exit(0)
57
+
58
+ try:
59
+ # Write PID file
60
+ write_pid()
61
+
62
+ # Register cleanup
63
+ atexit.register(remove_pid)
64
+
65
+ # Setup signal handlers
66
+ signal.signal(signal.SIGTERM, handle_shutdown)
67
+ signal.signal(signal.SIGINT, handle_shutdown)
68
+
69
+ logger.info("Starting standalone watcher daemon")
70
+ print("[Watcher Daemon] Starting standalone watcher", file=sys.stderr)
71
+
72
+ # Create and start watcher (same as MCP mode)
73
+ watcher = DialogueWatcher()
74
+ await watcher.start()
75
+
76
+ except Exception as e:
77
+ logger.error(f"Daemon error: {e}", exc_info=True)
78
+ print(f"[Watcher Daemon] Error: {e}", file=sys.stderr)
79
+ remove_pid()
80
+ sys.exit(1)
81
+ finally:
82
+ # Clean shutdown
83
+ if watcher and watcher.running:
84
+ await watcher.stop()
85
+ remove_pid()
86
+
87
+
88
+ def main():
89
+ """Main entry point for the daemon."""
90
+ try:
91
+ # Redirect stdout/stderr to log files
92
+ log_dir = Path.home() / '.aline/.logs'
93
+ log_dir.mkdir(parents=True, exist_ok=True)
94
+
95
+ stdout_log = log_dir / 'watcher_stdout.log'
96
+ stderr_log = log_dir / 'watcher_stderr.log'
97
+
98
+ # Open log files in append mode
99
+ sys.stdout = open(stdout_log, 'a', buffering=1)
100
+ sys.stderr = open(stderr_log, 'a', buffering=1)
101
+
102
+ # Run the async daemon
103
+ asyncio.run(run_daemon())
104
+
105
+ except KeyboardInterrupt:
106
+ logger.info("Daemon interrupted by user")
107
+ remove_pid()
108
+ except Exception as e:
109
+ logger.error(f"Fatal error: {e}", exc_info=True)
110
+ remove_pid()
111
+ sys.exit(1)
112
+
113
+
114
+ if __name__ == '__main__':
115
+ main()
@@ -1,28 +0,0 @@
1
- aline_ai-0.2.5.dist-info/licenses/LICENSE,sha256=H8wTqV5IF1oHw_HbBtS1PSDU8G_q81yblEIL_JfV8Vo,1077
2
- realign/__init__.py,sha256=zx1q8jbbAtSFt2eDYbUqaqss8LohQnvmI4gRY_HWVWY,68
3
- realign/claude_detector.py,sha256=NLxI0zJWcqNxNha9jAy9AslTMwHKakCc9yPGdkrbiFE,3028
4
- realign/cli.py,sha256=WSdT9zpHLswBeDPjb5M_U2cvhIm7yrgnXPDyb7vD-jg,2798
5
- realign/codex_detector.py,sha256=RI3JbZgebrhoqpRfTBMfclYCAISN7hZAHVW3bgftJpU,4428
6
- realign/config.py,sha256=jarinbr0mA6e5DmgY19b_VpMnxk6SOYTwyvB9luq0ww,7207
7
- realign/file_lock.py,sha256=-9c3tMdMj_ZxmasK5y6hV9Gfo6KDsSO3Q7PXiTBhsu4,3369
8
- realign/hooks.py,sha256=GzkEbW35-ifwAilYbVxDpNURZ_7XrF68DTqDSQ8v7fE,50670
9
- realign/logging_config.py,sha256=KvkKktF-bkUu031y9vgUoHpsbnOw7ud25jhpzliNZwA,4929
10
- realign/mcp_server.py,sha256=Q82nuEm6LF17eKUZfHHt6exQjzOWbbBmGXLYwNIGnoo,21002
11
- realign/mcp_watcher.py,sha256=ffYOXDLuf9T6Kab3CdGNAOY3DBlAbjZrVrSjM5RdYGU,26828
12
- realign/redactor.py,sha256=FizaGSdW-QTBAQl4h-gtmMpx2mFrfd2a5DoPEPyLfRg,9989
13
- realign/commands/__init__.py,sha256=caHulsUeguKyy2ZIIa9hVwzGwNHfIbeHwZIC67C8gnI,213
14
- realign/commands/auto_commit.py,sha256=jgjAYZHqN34NmQkncZg3Vtwsl3MyAlsvucxEBwUj7ko,7450
15
- realign/commands/commit.py,sha256=mlwrv5nfTRY17WlcAdiJKKGh5uM7dGvT7sMxhdbsfkw,12605
16
- realign/commands/config.py,sha256=iiu7usqw00djKZja5bx0iDH8DB0vU2maUPMkXLdgXwI,6609
17
- realign/commands/hide.py,sha256=i_XLsmsB4duLKNIA-eRAUvniS4N6GdQUyfN92CGndEA,34138
18
- realign/commands/init.py,sha256=vOXSAveWgP8TWp0rgqZO8zNRKZMPiaxWsg4PeDZ9RVs,13586
19
- realign/commands/push.py,sha256=77GXeKggzzc-100JN1PSgDFcDhSaB4kv2jN24h5YM6I,10249
20
- realign/commands/review.py,sha256=3TY6F87RGNEbutxvUr_konr24-gCyj5fmD9usd3n4-U,15570
21
- realign/commands/search.py,sha256=xTWuX0lpjQPX8cen0ewl-BNF0FeWgjMwN06bdeesED8,18770
22
- realign/commands/session_utils.py,sha256=L1DwZIGCOBirp6tkAswACJEeDa6i9aAAfsialAs4rRY,864
23
- realign/commands/show.py,sha256=A9LvhOBcY6_HoI76irPB2rBOSgdftBuX2uZiO8IwNoU,16338
24
- aline_ai-0.2.5.dist-info/METADATA,sha256=Deqqt3Tjs66ZW_76UA7itCXB_YLnhcO-rGa6vN_6vys,1437
25
- aline_ai-0.2.5.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
26
- aline_ai-0.2.5.dist-info/entry_points.txt,sha256=h-NocHDzSueXfsepHTIdRPNQzhNZQPAztJfldd-mQTE,202
27
- aline_ai-0.2.5.dist-info/top_level.txt,sha256=yIL3s2xv9nf1GwD5n71Aq_JEIV4AfzCIDNKBzewuRm4,8
28
- aline_ai-0.2.5.dist-info/RECORD,,
@@ -1,5 +0,0 @@
1
- [console_scripts]
2
- aline = realign.cli:app
3
- aline-hook-pre-commit = realign.hooks:pre_commit_hook
4
- aline-hook-prepare-commit-msg = realign.hooks:prepare_commit_msg_hook
5
- aline-mcp = realign.mcp_server:main
@@ -1,231 +0,0 @@
1
- """ReAlign auto-commit command - Automatically commit after each chat round."""
2
-
3
- import os
4
- import subprocess
5
- import time
6
- from pathlib import Path
7
- from typing import Optional, Set
8
- import typer
9
- from rich.console import Console
10
- from rich.live import Live
11
- from rich.panel import Panel
12
- from rich.text import Text
13
-
14
- from ..config import ReAlignConfig
15
- from ..hooks import find_all_active_sessions
16
- from ..commands.init import init_command
17
- from ..commands.commit import commit_command as manual_commit
18
-
19
- console = Console()
20
-
21
-
22
- def is_realign_initialized() -> bool:
23
- """Check if ReAlign is initialized in the current repository."""
24
- try:
25
- result = subprocess.run(
26
- ["git", "rev-parse", "--show-toplevel"],
27
- capture_output=True,
28
- text=True,
29
- check=True,
30
- )
31
- repo_root = Path(result.stdout.strip())
32
-
33
- # Check if .realign directory exists
34
- realign_dir = repo_root / ".realign"
35
- if not realign_dir.exists():
36
- return False
37
-
38
- # Check if hooks are installed
39
- result = subprocess.run(
40
- ["git", "config", "core.hooksPath"],
41
- capture_output=True,
42
- text=True,
43
- )
44
- hooks_path = result.stdout.strip()
45
-
46
- return ".realign/hooks" in hooks_path
47
- except subprocess.CalledProcessError:
48
- return False
49
-
50
-
51
- def auto_initialize():
52
- """Automatically initialize ReAlign if not already initialized."""
53
- if not is_realign_initialized():
54
- console.print("[yellow]⚙️ ReAlign not initialized. Initializing...[/yellow]")
55
- try:
56
- # Call init command with auto-yes
57
- result = subprocess.run(
58
- ["realign", "init", "--yes"],
59
- capture_output=True,
60
- text=True,
61
- )
62
- if result.returncode == 0:
63
- console.print("[green]✓[/green] ReAlign initialized successfully")
64
- return True
65
- else:
66
- console.print(f"[red]✗[/red] Failed to initialize ReAlign:\n{result.stderr}")
67
- return False
68
- except Exception as e:
69
- console.print(f"[red]✗[/red] Error initializing ReAlign: {e}")
70
- return False
71
- return True
72
-
73
-
74
- def get_session_mtimes(repo_root: Path) -> dict:
75
- """Get modification times for all active session files."""
76
- config = ReAlignConfig.load()
77
- session_files = find_all_active_sessions(config, repo_root)
78
-
79
- mtimes = {}
80
- for session_file in session_files:
81
- if session_file.exists():
82
- mtimes[str(session_file)] = session_file.stat().st_mtime
83
-
84
- return mtimes
85
-
86
-
87
- def generate_commit_message() -> str:
88
- """Generate an automatic commit message."""
89
- from datetime import datetime
90
- timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
91
- return f"chore: Auto-commit MCP session ({timestamp})"
92
-
93
-
94
- def auto_commit_once(repo_root: Path, message: Optional[str] = None, silent: bool = False) -> bool:
95
- """
96
- Perform a single auto-commit.
97
-
98
- Args:
99
- repo_root: Path to the repository root
100
- message: Custom commit message (auto-generated if not provided)
101
- silent: If True, suppress console output (for watcher mode)
102
-
103
- Returns:
104
- True if commit was successful, False otherwise
105
- """
106
- commit_message = message or generate_commit_message()
107
-
108
- try:
109
- # Stage all changes including sessions
110
- subprocess.run(
111
- ["git", "add", "-A"],
112
- cwd=repo_root,
113
- check=True,
114
- capture_output=True,
115
- )
116
-
117
- # Run realign commit
118
- result = subprocess.run(
119
- ["realign", "commit", "-m", commit_message],
120
- cwd=repo_root,
121
- capture_output=True,
122
- text=True,
123
- )
124
-
125
- if result.returncode == 0:
126
- if not silent:
127
- console.print(f"[green]✓[/green] Auto-committed: {commit_message}")
128
- return True
129
- else:
130
- # Check if it's just "no changes" error
131
- if "No changes detected" in result.stdout or "No changes detected" in result.stderr:
132
- if not silent:
133
- console.print("[dim]No changes to commit[/dim]")
134
- return True
135
- else:
136
- if not silent:
137
- console.print(f"[red]✗[/red] Commit failed:\n{result.stderr}")
138
- return False
139
- except subprocess.CalledProcessError as e:
140
- if not silent:
141
- console.print(f"[red]✗[/red] Error during commit: {e}")
142
- return False
143
-
144
-
145
- def auto_commit_command(
146
- watch: bool = typer.Option(
147
- False,
148
- "--watch",
149
- "-w",
150
- help="Watch for changes and auto-commit continuously"
151
- ),
152
- interval: int = typer.Option(
153
- 5,
154
- "--interval",
155
- "-i",
156
- help="Check interval in seconds (default: 5)"
157
- ),
158
- message: Optional[str] = typer.Option(
159
- None,
160
- "--message",
161
- "-m",
162
- help="Custom commit message (auto-generated if not provided)"
163
- ),
164
- ):
165
- """
166
- Automatically commit after each chat round.
167
-
168
- By default, performs a single commit. Use --watch to continuously monitor
169
- and commit changes as they happen.
170
- """
171
- # Check if in git repository
172
- try:
173
- result = subprocess.run(
174
- ["git", "rev-parse", "--show-toplevel"],
175
- check=True,
176
- capture_output=True,
177
- text=True,
178
- )
179
- repo_root = Path(result.stdout.strip())
180
- except subprocess.CalledProcessError:
181
- console.print("[red]Error: Not in a git repository[/red]")
182
- raise typer.Exit(1)
183
-
184
- # Auto-initialize if needed
185
- if not auto_initialize():
186
- console.print("[red]Failed to initialize ReAlign[/red]")
187
- raise typer.Exit(1)
188
-
189
- if not watch:
190
- # Single commit mode
191
- console.print("[cyan]🔄 Performing auto-commit...[/cyan]")
192
- success = auto_commit_once(repo_root, message)
193
- if success:
194
- console.print("[green]✓[/green] Done")
195
- else:
196
- raise typer.Exit(1)
197
- return
198
-
199
- # Watch mode
200
- console.print("[cyan]👁️ Watching for session changes...[/cyan]")
201
- console.print(f"[dim] Check interval: {interval} seconds[/dim]")
202
- console.print("[dim] Press Ctrl+C to stop[/dim]\n")
203
-
204
- # Track last known modification times
205
- last_mtimes = get_session_mtimes(repo_root)
206
- commit_count = 0
207
-
208
- try:
209
- while True:
210
- time.sleep(interval)
211
-
212
- # Check for changes
213
- current_mtimes = get_session_mtimes(repo_root)
214
-
215
- # Detect if any session file was modified
216
- has_changes = False
217
- for session_path, mtime in current_mtimes.items():
218
- if session_path not in last_mtimes or last_mtimes[session_path] < mtime:
219
- has_changes = True
220
- console.print(f"[yellow]📝 Session change detected: {Path(session_path).name}[/yellow]")
221
- break
222
-
223
- if has_changes:
224
- console.print("[cyan]🔄 Auto-committing...[/cyan]")
225
- if auto_commit_once(repo_root, message):
226
- commit_count += 1
227
- console.print(f"[green]✓[/green] Total commits: {commit_count}\n")
228
- last_mtimes = current_mtimes
229
-
230
- except KeyboardInterrupt:
231
- console.print(f"\n[cyan]👋 Stopped watching. Total commits: {commit_count}[/cyan]")