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.
- {aline_ai-0.2.5.dist-info → aline_ai-0.3.0.dist-info}/METADATA +3 -1
- aline_ai-0.3.0.dist-info/RECORD +41 -0
- aline_ai-0.3.0.dist-info/entry_points.txt +3 -0
- realign/__init__.py +32 -1
- realign/cli.py +203 -19
- realign/commands/__init__.py +2 -2
- realign/commands/clean.py +149 -0
- realign/commands/config.py +1 -1
- realign/commands/export_shares.py +1785 -0
- realign/commands/hide.py +112 -24
- realign/commands/import_history.py +873 -0
- realign/commands/init.py +104 -217
- realign/commands/mirror.py +131 -0
- realign/commands/pull.py +101 -0
- realign/commands/push.py +155 -245
- realign/commands/review.py +216 -54
- realign/commands/session_utils.py +139 -4
- realign/commands/share.py +965 -0
- realign/commands/status.py +559 -0
- realign/commands/sync.py +91 -0
- realign/commands/undo.py +423 -0
- realign/commands/watcher.py +805 -0
- realign/config.py +21 -10
- realign/file_lock.py +3 -1
- realign/hash_registry.py +310 -0
- realign/hooks.py +368 -384
- realign/logging_config.py +2 -2
- realign/mcp_server.py +263 -549
- realign/mcp_watcher.py +999 -142
- realign/mirror_utils.py +322 -0
- realign/prompts/__init__.py +21 -0
- realign/prompts/presets.py +238 -0
- realign/redactor.py +168 -16
- realign/tracker/__init__.py +9 -0
- realign/tracker/git_tracker.py +1123 -0
- realign/watcher_daemon.py +115 -0
- aline_ai-0.2.5.dist-info/RECORD +0 -28
- aline_ai-0.2.5.dist-info/entry_points.txt +0 -5
- realign/commands/auto_commit.py +0 -231
- realign/commands/commit.py +0 -379
- realign/commands/search.py +0 -449
- realign/commands/show.py +0 -416
- {aline_ai-0.2.5.dist-info → aline_ai-0.3.0.dist-info}/WHEEL +0 -0
- {aline_ai-0.2.5.dist-info → aline_ai-0.3.0.dist-info}/licenses/LICENSE +0 -0
- {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()
|
aline_ai-0.2.5.dist-info/RECORD
DELETED
|
@@ -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,,
|
realign/commands/auto_commit.py
DELETED
|
@@ -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]")
|