aline-ai 0.2.6__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.6.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 +115 -411
- realign/logging_config.py +2 -2
- realign/mcp_server.py +263 -549
- realign/mcp_watcher.py +997 -139
- 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.6.dist-info/RECORD +0 -28
- aline_ai-0.2.6.dist-info/entry_points.txt +0 -5
- realign/commands/auto_commit.py +0 -242
- realign/commands/commit.py +0 -379
- realign/commands/search.py +0 -449
- realign/commands/show.py +0 -416
- {aline_ai-0.2.6.dist-info → aline_ai-0.3.0.dist-info}/WHEEL +0 -0
- {aline_ai-0.2.6.dist-info → aline_ai-0.3.0.dist-info}/licenses/LICENSE +0 -0
- {aline_ai-0.2.6.dist-info → aline_ai-0.3.0.dist-info}/top_level.txt +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: aline-ai
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.3.0
|
|
4
4
|
Summary: Shared AI memory; everyone knows everything in teams
|
|
5
5
|
Author: Sharemind
|
|
6
6
|
License: MIT
|
|
@@ -27,6 +27,8 @@ Requires-Dist: anthropic>=0.18.0
|
|
|
27
27
|
Requires-Dist: openai>=1.0.0
|
|
28
28
|
Requires-Dist: detect-secrets>=1.4.0
|
|
29
29
|
Requires-Dist: git-filter-repo>=2.38.0
|
|
30
|
+
Requires-Dist: cryptography>=41.0.0
|
|
31
|
+
Requires-Dist: httpx>=0.24.0
|
|
30
32
|
Provides-Extra: dev
|
|
31
33
|
Requires-Dist: pytest>=7.0.0; extra == "dev"
|
|
32
34
|
Requires-Dist: pytest-cov>=4.0.0; extra == "dev"
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
aline_ai-0.3.0.dist-info/licenses/LICENSE,sha256=H8wTqV5IF1oHw_HbBtS1PSDU8G_q81yblEIL_JfV8Vo,1077
|
|
2
|
+
realign/__init__.py,sha256=efYn1D4eHTA-wxUXWDWlMTtZUHs1o_SVeUxEVLl7QeY,962
|
|
3
|
+
realign/claude_detector.py,sha256=NLxI0zJWcqNxNha9jAy9AslTMwHKakCc9yPGdkrbiFE,3028
|
|
4
|
+
realign/cli.py,sha256=SqnVuzessnx-JcL7F8O2_d-WaLyekHECWuwc_1RiGI8,9877
|
|
5
|
+
realign/codex_detector.py,sha256=RI3JbZgebrhoqpRfTBMfclYCAISN7hZAHVW3bgftJpU,4428
|
|
6
|
+
realign/config.py,sha256=bY1YS-E9oxf4DrteHVyhiIbSCj_GMM1LaZPLGcHFUkg,8190
|
|
7
|
+
realign/file_lock.py,sha256=ff02cxCPOqauF5BVqRvdtfbm30czUhla6MTuwsURHTM,3443
|
|
8
|
+
realign/hash_registry.py,sha256=L-TFxFQ2oAriN0eZuhiW3SxUB95uXCggnR-peRPTDM4,10691
|
|
9
|
+
realign/hooks.py,sha256=b6Rncwvu1G9fQfZvBVP7YgNpzQaV8-a7WuoSw6lORp0,51920
|
|
10
|
+
realign/logging_config.py,sha256=pw0RgG_pneUUAFCsLFm03x7n8syBMSJfwZMAFTiXgXg,4925
|
|
11
|
+
realign/mcp_server.py,sha256=3rhuHtUeo4XXPan9QoXltrIrqB2-yMy1JNkYzgGPSJ8,10401
|
|
12
|
+
realign/mcp_watcher.py,sha256=K1D_D2FDb3mpTnEC3VIbsdEFJVn9MYjUWuaFVv6mxI0,64453
|
|
13
|
+
realign/mirror_utils.py,sha256=L2SLCyiLgkN-hZ1Gqv0a_abZfCh3oKoKUrJ6J3hmTdg,10041
|
|
14
|
+
realign/redactor.py,sha256=M3jgJHs93nlaCFDLJkH58i9i_vWQEO06zCC2cktbU3c,16627
|
|
15
|
+
realign/watcher_daemon.py,sha256=77KeSxCHMIyugkXZsW8_Hy0fiF6x4oiJmLb9VHmEZTs,3119
|
|
16
|
+
realign/commands/__init__.py,sha256=ROqpYxaV3cOSwib0IaqTkdwbWb20Qs_RJatYPY1dQ8o,171
|
|
17
|
+
realign/commands/clean.py,sha256=LOpycrW34L6y4G0yJeazkWqlRlUpNPnaPT6f8jxMGOM,4896
|
|
18
|
+
realign/commands/config.py,sha256=YrvgIKE7mX57sFqsa5_aO2H3Yi_E4IHBOPMu3R4ttbg,6607
|
|
19
|
+
realign/commands/export_shares.py,sha256=SH5omO7y0l6FrBw_vSYVOA-Z25w1qY3vHuqUdpBpDHM,62744
|
|
20
|
+
realign/commands/hide.py,sha256=OuYPpEcc7VL_EP38K2dU1dAX5428gZkHbfD753YOLO8,38444
|
|
21
|
+
realign/commands/import_history.py,sha256=KOYKhylMyRxQqavq7yqC3ET7Q-3viyRBJN3AzjojMBI,32100
|
|
22
|
+
realign/commands/init.py,sha256=-L6CTvX655iGdC-fw_vj-WJ9a4EQssoXQbqj2CXH9cY,9206
|
|
23
|
+
realign/commands/mirror.py,sha256=GCEFwKdTtVcSC9iSHQnLdETbEPU0cOk6P8cby_DImcM,4543
|
|
24
|
+
realign/commands/pull.py,sha256=RaWdED2n31JB-tU9XdHJLgYfJSXLd6Rh6Lz1zdg1kzY,2977
|
|
25
|
+
realign/commands/push.py,sha256=ilnce6MSA_zzzllUMGq5wYLbaQVa8GhzDsL2zyrFLqA,6121
|
|
26
|
+
realign/commands/review.py,sha256=SnQliauVjzr_RA1S1eyqEZNyefulyykSIgVLHlMknmc,21393
|
|
27
|
+
realign/commands/session_utils.py,sha256=w4YCbeLDZ7oTzP2En3sgwqhvUebGSb2ffKs2etUVuck,5905
|
|
28
|
+
realign/commands/share.py,sha256=hGBbntMNkj4MXHH0JnZzgeSwmdnhnSpqRtDb7KCvx1c,29761
|
|
29
|
+
realign/commands/status.py,sha256=lKEBQ9ycrBd1uLHkNPGyP0gpfM-Tj-B6MaE2pyEwVHA,18827
|
|
30
|
+
realign/commands/sync.py,sha256=7cFTck6Yz9v_7XrCYm1Ujf1s6QTCc3J4cjLfw-Fz6H0,2511
|
|
31
|
+
realign/commands/undo.py,sha256=-sxdYIEYpollMa_3HRbGAlICJ1g8RPxZNjoAyS15VnQ,15505
|
|
32
|
+
realign/commands/watcher.py,sha256=IUkmehHPo94xRMbamNfNiEPLmr2vHdMImfKFDprg_dQ,28393
|
|
33
|
+
realign/prompts/__init__.py,sha256=Qzp4j2sf_OF_n34p-uI1BtfCIfJlka3NfEUCS9Fdqvs,443
|
|
34
|
+
realign/prompts/presets.py,sha256=NH3u2z3oxUHy0YctvWKEt91mG8388DhEOX5QfBSWncY,7188
|
|
35
|
+
realign/tracker/__init__.py,sha256=NEboDDhK0YSureNgC4BZr4qYjT4wDPG27qfXYRo2bsU,289
|
|
36
|
+
realign/tracker/git_tracker.py,sha256=lnRIL6tv0XpEF3Ifm8jKjZ2ib8FqZJh3HfpbLf_2xz4,38145
|
|
37
|
+
aline_ai-0.3.0.dist-info/METADATA,sha256=eN87ve4OL5FeTX3-x7qeDvujEdBxHewCk3QxhJromrc,1502
|
|
38
|
+
aline_ai-0.3.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
39
|
+
aline_ai-0.3.0.dist-info/entry_points.txt,sha256=TvYELpMoWsUTcQdMV8tBHxCbEf_LbK4sESqK3r8PM6Y,78
|
|
40
|
+
aline_ai-0.3.0.dist-info/top_level.txt,sha256=yIL3s2xv9nf1GwD5n71Aq_JEIV4AfzCIDNKBzewuRm4,8
|
|
41
|
+
aline_ai-0.3.0.dist-info/RECORD,,
|
realign/__init__.py
CHANGED
|
@@ -1,3 +1,34 @@
|
|
|
1
1
|
"""Aline - AI Agent Chat Session Tracker."""
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
|
|
5
|
+
__version__ = "0.3.0"
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def get_realign_dir(project_root: Path) -> Path:
|
|
9
|
+
"""
|
|
10
|
+
Get the ReAlign directory path for a project.
|
|
11
|
+
|
|
12
|
+
The directory is located at ~/.aline/{project_name}/ instead of
|
|
13
|
+
in the project directory itself.
|
|
14
|
+
|
|
15
|
+
First checks for .realign-config file in project root,
|
|
16
|
+
otherwise uses default location.
|
|
17
|
+
|
|
18
|
+
Args:
|
|
19
|
+
project_root: Root directory of the project
|
|
20
|
+
|
|
21
|
+
Returns:
|
|
22
|
+
Path to the ReAlign directory for this project
|
|
23
|
+
"""
|
|
24
|
+
project_root = Path(project_root).resolve()
|
|
25
|
+
config_marker = project_root / ".realign-config"
|
|
26
|
+
|
|
27
|
+
if config_marker.exists():
|
|
28
|
+
# Read the configured path
|
|
29
|
+
configured_path = config_marker.read_text(encoding="utf-8").strip()
|
|
30
|
+
return Path(configured_path)
|
|
31
|
+
else:
|
|
32
|
+
# Use default location
|
|
33
|
+
project_name = project_root.name
|
|
34
|
+
return Path.home() / ".aline" / project_name
|
realign/cli.py
CHANGED
|
@@ -7,7 +7,7 @@ from typing import Optional
|
|
|
7
7
|
from rich.console import Console
|
|
8
8
|
from rich.syntax import Syntax
|
|
9
9
|
|
|
10
|
-
from .commands import init,
|
|
10
|
+
from .commands import init, config, review, hide, status, watcher, push, pull, share, sync, mirror, clean, import_history, undo, export_shares
|
|
11
11
|
|
|
12
12
|
app = typer.Typer(
|
|
13
13
|
name="realign",
|
|
@@ -18,11 +18,24 @@ console = Console()
|
|
|
18
18
|
|
|
19
19
|
# Register commands
|
|
20
20
|
app.command(name="init")(init.init_command)
|
|
21
|
-
app.command(name="search")(search.search_command)
|
|
22
|
-
app.command(name="show")(show.show_command)
|
|
23
21
|
app.command(name="config")(config.config_command)
|
|
24
|
-
|
|
25
|
-
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
@app.command(name="import-history")
|
|
25
|
+
def import_history_cli(
|
|
26
|
+
verbose: bool = typer.Option(False, "--verbose", "-v", help="Show full user messages instead of previews"),
|
|
27
|
+
limit: Optional[int] = typer.Option(None, "--limit", "-l", help="Maximum number of turns to process"),
|
|
28
|
+
commit: bool = typer.Option(False, "--commit", help="Actually commit turns to git"),
|
|
29
|
+
no_llm: bool = typer.Option(False, "--no-llm", help="Skip LLM summary generation (use simple commit messages)"),
|
|
30
|
+
):
|
|
31
|
+
"""Discover and display (or commit) historical sessions for import. By default, --commit uses LLM to generate summaries."""
|
|
32
|
+
exit_code = import_history.import_history_command(
|
|
33
|
+
verbose=verbose,
|
|
34
|
+
limit=limit,
|
|
35
|
+
commit=commit,
|
|
36
|
+
no_llm=no_llm
|
|
37
|
+
)
|
|
38
|
+
raise typer.Exit(code=exit_code)
|
|
26
39
|
|
|
27
40
|
|
|
28
41
|
@app.command(name="review")
|
|
@@ -48,25 +61,196 @@ def hide_cli(
|
|
|
48
61
|
raise typer.Exit(code=exit_code)
|
|
49
62
|
|
|
50
63
|
|
|
64
|
+
@app.command(name="status")
|
|
65
|
+
def status_cli(
|
|
66
|
+
watch: bool = typer.Option(False, "--watch", "-w", help="Continuously monitor status"),
|
|
67
|
+
):
|
|
68
|
+
"""Display ReAlign system status and activity."""
|
|
69
|
+
exit_code = status.status_command(watch=watch)
|
|
70
|
+
raise typer.Exit(code=exit_code)
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
# Create watcher subcommand group
|
|
74
|
+
watcher_app = typer.Typer(help="Manage MCP watcher process")
|
|
75
|
+
app.add_typer(watcher_app, name="watcher")
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
@watcher_app.command(name="status")
|
|
79
|
+
def watcher_status_cli(
|
|
80
|
+
verbose: bool = typer.Option(False, "--verbose", "-v", help="Show detailed session tracking information"),
|
|
81
|
+
):
|
|
82
|
+
"""Display MCP watcher status."""
|
|
83
|
+
exit_code = watcher.watcher_status_command(verbose=verbose)
|
|
84
|
+
raise typer.Exit(code=exit_code)
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
@watcher_app.command(name="start")
|
|
88
|
+
def watcher_start_cli():
|
|
89
|
+
"""Start the MCP watcher process."""
|
|
90
|
+
exit_code = watcher.watcher_start_command()
|
|
91
|
+
raise typer.Exit(code=exit_code)
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
@watcher_app.command(name="stop")
|
|
95
|
+
def watcher_stop_cli():
|
|
96
|
+
"""Stop the MCP watcher process."""
|
|
97
|
+
exit_code = watcher.watcher_stop_command()
|
|
98
|
+
raise typer.Exit(code=exit_code)
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
# Push/Pull commands
|
|
51
102
|
@app.command(name="push")
|
|
52
103
|
def push_cli(
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
104
|
+
force: bool = typer.Option(False, "--force", "-f", help="Force push (use with caution)"),
|
|
105
|
+
):
|
|
106
|
+
"""Push session commits to remote repository."""
|
|
107
|
+
exit_code = push.push_command(force=force)
|
|
108
|
+
raise typer.Exit(code=exit_code)
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
@app.command(name="pull")
|
|
112
|
+
def pull_cli():
|
|
113
|
+
"""Pull session updates from remote repository."""
|
|
114
|
+
exit_code = pull.pull_command()
|
|
115
|
+
raise typer.Exit(code=exit_code)
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
@app.command(name="sync")
|
|
119
|
+
def sync_cli():
|
|
120
|
+
"""Synchronize with remote (pull + push)."""
|
|
121
|
+
exit_code = sync.sync_command()
|
|
122
|
+
raise typer.Exit(code=exit_code)
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
# Share command group
|
|
126
|
+
share_app = typer.Typer(help="Manage session sharing and collaboration")
|
|
127
|
+
app.add_typer(share_app, name="share")
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
@share_app.command(name="configure")
|
|
131
|
+
def share_configure_cli(
|
|
132
|
+
remote: str = typer.Argument(..., help="Remote repository (e.g., user/repo or full URL)"),
|
|
133
|
+
token: Optional[str] = typer.Option(None, "--token", help="GitHub access token"),
|
|
57
134
|
):
|
|
58
|
-
"""
|
|
135
|
+
"""Manually configure remote repository for sharing."""
|
|
136
|
+
exit_code = share.share_configure_command(remote=remote, token=token)
|
|
137
|
+
raise typer.Exit(code=exit_code)
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
@share_app.command(name="status")
|
|
141
|
+
def share_status_cli():
|
|
142
|
+
"""Show current sharing configuration."""
|
|
143
|
+
exit_code = share.share_status_command()
|
|
144
|
+
raise typer.Exit(code=exit_code)
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
@share_app.command(name="invite")
|
|
148
|
+
def share_invite_cli(
|
|
149
|
+
email: Optional[str] = typer.Argument(None, help="Email address to invite"),
|
|
150
|
+
):
|
|
151
|
+
"""Invite collaborator to shared repository."""
|
|
152
|
+
exit_code = share.share_invite_command(email=email)
|
|
153
|
+
raise typer.Exit(code=exit_code)
|
|
154
|
+
|
|
59
155
|
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
156
|
+
@share_app.command(name="link")
|
|
157
|
+
def share_link_cli():
|
|
158
|
+
"""Get shareable link for teammates to join."""
|
|
159
|
+
exit_code = share.share_link_command()
|
|
160
|
+
raise typer.Exit(code=exit_code)
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
@share_app.command(name="disable")
|
|
164
|
+
def share_disable_cli():
|
|
165
|
+
"""Disable sharing (keeps history intact)."""
|
|
166
|
+
exit_code = share.share_disable_command()
|
|
167
|
+
raise typer.Exit(code=exit_code)
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
@share_app.command(name="export")
|
|
171
|
+
def share_export_cli(
|
|
172
|
+
indices: Optional[str] = typer.Option(None, "--indices", "-i", help="Commit indices to export (e.g., '1,3,5-7' or 'all')"),
|
|
173
|
+
local: bool = typer.Option(False, "--local", help="Export to local JSON only (skip interactive web upload)"),
|
|
174
|
+
interactive: bool = typer.Option(False, "--interactive", help="[DEPRECATED] Interactive mode is now default"),
|
|
175
|
+
username: Optional[str] = typer.Option(None, "--username", "-u", help="Username for local export file"),
|
|
176
|
+
output_dir: Optional[str] = typer.Option(None, "--output", "-o", help="Custom output directory for local export"),
|
|
177
|
+
password: Optional[str] = typer.Option(None, "--password", "-p", help="Password for encrypted share (auto-generated if not provided)"),
|
|
178
|
+
expiry_days: int = typer.Option(7, "--expiry", help="Number of days before share expires"),
|
|
179
|
+
max_views: int = typer.Option(100, "--max-views", help="Maximum number of views allowed"),
|
|
180
|
+
no_preview: bool = typer.Option(False, "--no-preview", help="Skip UI preview and editing (auto-accept LLM-generated content)"),
|
|
181
|
+
preset: Optional[str] = typer.Option(None, "--preset", help="Prompt preset ID (default, work-report, knowledge-agent, personality-analyzer)"),
|
|
182
|
+
mcp: bool = typer.Option(True, "--mcp/--no-mcp", help="Include MCP usage instructions for agent-to-agent communication (default: enabled)"),
|
|
183
|
+
):
|
|
64
184
|
"""
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
185
|
+
Export chat history as encrypted shareable link (default) or local JSON.
|
|
186
|
+
|
|
187
|
+
Interactive mode (default): Creates encrypted web-accessible chatbot
|
|
188
|
+
Local mode (--local): Exports to JSON file only
|
|
189
|
+
"""
|
|
190
|
+
# Deprecation warning
|
|
191
|
+
if interactive:
|
|
192
|
+
print("⚠️ Warning: --interactive flag is deprecated.")
|
|
193
|
+
print(" Interactive mode is now default. Use --local for JSON export.\n")
|
|
194
|
+
|
|
195
|
+
# Determine mode (backward compatible)
|
|
196
|
+
use_local_mode = local or username or output_dir
|
|
197
|
+
|
|
198
|
+
if use_local_mode:
|
|
199
|
+
# Local JSON export
|
|
200
|
+
output_path = Path(output_dir) if output_dir else None
|
|
201
|
+
exit_code = export_shares.export_shares_command(
|
|
202
|
+
indices=indices,
|
|
203
|
+
username=username,
|
|
204
|
+
output_dir=output_path
|
|
205
|
+
)
|
|
206
|
+
else:
|
|
207
|
+
# Interactive web export (DEFAULT)
|
|
208
|
+
exit_code = export_shares.export_shares_interactive_command(
|
|
209
|
+
indices=indices,
|
|
210
|
+
password=password,
|
|
211
|
+
expiry_days=expiry_days,
|
|
212
|
+
max_views=max_views,
|
|
213
|
+
enable_preview=not no_preview,
|
|
214
|
+
preset=preset,
|
|
215
|
+
enable_mcp=mcp
|
|
216
|
+
)
|
|
217
|
+
raise typer.Exit(code=exit_code)
|
|
218
|
+
|
|
219
|
+
|
|
220
|
+
@app.command(name="mirror")
|
|
221
|
+
def mirror_cli(
|
|
222
|
+
verbose: bool = typer.Option(False, "--verbose", "-v", help="Show detailed progress"),
|
|
223
|
+
path: Optional[str] = typer.Argument(None, help="Project path (defaults to current directory)"),
|
|
224
|
+
):
|
|
225
|
+
"""Mirror all project files to the shadow git repository."""
|
|
226
|
+
mirror.mirror_command(verbose=verbose, path=path)
|
|
227
|
+
|
|
228
|
+
|
|
229
|
+
@app.command(name="clean")
|
|
230
|
+
def clean_cli(
|
|
231
|
+
force: bool = typer.Option(False, "--force", "-f", help="Skip confirmation prompt"),
|
|
232
|
+
dry_run: bool = typer.Option(False, "--dry-run", help="Show what would be deleted without deleting"),
|
|
233
|
+
):
|
|
234
|
+
"""Clean up temporary and test project directories."""
|
|
235
|
+
exit_code = clean.clean_command(force=force, dry_run=dry_run)
|
|
236
|
+
raise typer.Exit(code=exit_code)
|
|
237
|
+
|
|
238
|
+
|
|
239
|
+
@app.command(name="undo")
|
|
240
|
+
def undo_cli(
|
|
241
|
+
commit_hash: str = typer.Argument(..., help="Commit hash to undo to"),
|
|
242
|
+
dry_run: bool = typer.Option(False, "--dry-run", help="Preview changes without executing"),
|
|
243
|
+
no_backup: bool = typer.Option(False, "--no-backup", help="Skip backup creation"),
|
|
244
|
+
delete: str = typer.Option("keep", "--delete", help="Handle extra files: keep|delete|backup"),
|
|
245
|
+
force: bool = typer.Option(False, "--force", "-f", help="Skip confirmation prompts"),
|
|
246
|
+
):
|
|
247
|
+
"""Undo project and session state to a specific commit."""
|
|
248
|
+
exit_code = undo.undo_command(
|
|
249
|
+
commit_hash=commit_hash,
|
|
250
|
+
dry_run=dry_run,
|
|
251
|
+
no_backup=no_backup,
|
|
252
|
+
deletion_strategy=delete,
|
|
253
|
+
force=force
|
|
70
254
|
)
|
|
71
255
|
raise typer.Exit(code=exit_code)
|
|
72
256
|
|
realign/commands/__init__.py
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
"""ReAlign commands module."""
|
|
2
2
|
|
|
3
|
-
from . import init,
|
|
3
|
+
from . import init, config, review, hide, clean, import_history
|
|
4
4
|
|
|
5
|
-
__all__ = ["init", "
|
|
5
|
+
__all__ = ["init", "config", "review", "hide", "clean", "import_history"]
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Clean up temporary and test project directories from ~/.aline."""
|
|
3
|
+
|
|
4
|
+
import shutil
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
from typing import List
|
|
7
|
+
|
|
8
|
+
from rich.console import Console
|
|
9
|
+
from rich.table import Table
|
|
10
|
+
|
|
11
|
+
from ..logging_config import setup_logger
|
|
12
|
+
|
|
13
|
+
logger = setup_logger('realign.clean', 'clean.log')
|
|
14
|
+
console = Console()
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def find_temp_projects() -> List[Path]:
|
|
18
|
+
"""
|
|
19
|
+
Find all temporary/test project directories in ~/.aline.
|
|
20
|
+
|
|
21
|
+
Returns:
|
|
22
|
+
List of paths to temporary project directories
|
|
23
|
+
"""
|
|
24
|
+
aline_dir = Path.home() / '.aline'
|
|
25
|
+
if not aline_dir.exists():
|
|
26
|
+
return []
|
|
27
|
+
|
|
28
|
+
temp_projects = []
|
|
29
|
+
try:
|
|
30
|
+
for project_dir in aline_dir.iterdir():
|
|
31
|
+
if not project_dir.is_dir():
|
|
32
|
+
continue
|
|
33
|
+
|
|
34
|
+
# Skip system directories
|
|
35
|
+
if project_dir.name in ['.logs', '.cache']:
|
|
36
|
+
continue
|
|
37
|
+
|
|
38
|
+
# Identify temporary/test directories
|
|
39
|
+
if project_dir.name.startswith(('tmp', 'test_')):
|
|
40
|
+
temp_projects.append(project_dir)
|
|
41
|
+
|
|
42
|
+
except Exception as e:
|
|
43
|
+
logger.error(f"Failed to scan for temporary projects: {e}")
|
|
44
|
+
console.print(f"[red]Error: {e}[/red]")
|
|
45
|
+
|
|
46
|
+
return temp_projects
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def clean_command(force: bool = False, dry_run: bool = False) -> int:
|
|
50
|
+
"""
|
|
51
|
+
Clean up temporary and test project directories.
|
|
52
|
+
|
|
53
|
+
Args:
|
|
54
|
+
force: Skip confirmation prompt
|
|
55
|
+
dry_run: Show what would be deleted without actually deleting
|
|
56
|
+
|
|
57
|
+
Returns:
|
|
58
|
+
Exit code (0 = success, 1 = error)
|
|
59
|
+
"""
|
|
60
|
+
try:
|
|
61
|
+
console.print("\n[bold cyan]ReAlign Cleanup[/bold cyan]")
|
|
62
|
+
|
|
63
|
+
# Find temporary projects
|
|
64
|
+
temp_projects = find_temp_projects()
|
|
65
|
+
|
|
66
|
+
if not temp_projects:
|
|
67
|
+
console.print("[green]✓ No temporary projects found[/green]")
|
|
68
|
+
console.print("[dim]All clean![/dim]\n")
|
|
69
|
+
return 0
|
|
70
|
+
|
|
71
|
+
# Display what will be cleaned
|
|
72
|
+
console.print(f"\n[yellow]Found {len(temp_projects)} temporary project(s):[/yellow]\n")
|
|
73
|
+
|
|
74
|
+
table = Table(show_header=True, header_style="bold magenta")
|
|
75
|
+
table.add_column("Project", style="cyan")
|
|
76
|
+
table.add_column("Size", justify="right", style="dim")
|
|
77
|
+
|
|
78
|
+
total_size = 0
|
|
79
|
+
for project in temp_projects:
|
|
80
|
+
# Calculate directory size
|
|
81
|
+
try:
|
|
82
|
+
size = sum(f.stat().st_size for f in project.rglob('*') if f.is_file())
|
|
83
|
+
total_size += size
|
|
84
|
+
|
|
85
|
+
if size < 1024:
|
|
86
|
+
size_str = f"{size}B"
|
|
87
|
+
elif size < 1024 * 1024:
|
|
88
|
+
size_str = f"{size / 1024:.1f}KB"
|
|
89
|
+
else:
|
|
90
|
+
size_str = f"{size / (1024 * 1024):.1f}MB"
|
|
91
|
+
|
|
92
|
+
table.add_row(project.name, size_str)
|
|
93
|
+
except Exception as e:
|
|
94
|
+
logger.warning(f"Failed to calculate size for {project.name}: {e}")
|
|
95
|
+
table.add_row(project.name, "N/A")
|
|
96
|
+
|
|
97
|
+
console.print(table)
|
|
98
|
+
|
|
99
|
+
# Display total size
|
|
100
|
+
if total_size < 1024 * 1024:
|
|
101
|
+
total_str = f"{total_size / 1024:.1f}KB"
|
|
102
|
+
else:
|
|
103
|
+
total_str = f"{total_size / (1024 * 1024):.1f}MB"
|
|
104
|
+
|
|
105
|
+
console.print(f"\n[bold]Total size: {total_str}[/bold]")
|
|
106
|
+
|
|
107
|
+
# Dry run mode - just show what would be deleted
|
|
108
|
+
if dry_run:
|
|
109
|
+
console.print("\n[dim]Dry run mode - no files were deleted[/dim]")
|
|
110
|
+
console.print("[dim]Run without --dry-run to actually delete these directories[/dim]\n")
|
|
111
|
+
return 0
|
|
112
|
+
|
|
113
|
+
# Confirmation prompt (unless force flag is set)
|
|
114
|
+
if not force:
|
|
115
|
+
console.print(f"\n[yellow]⚠ This will permanently delete {len(temp_projects)} directory(ies)[/yellow]")
|
|
116
|
+
|
|
117
|
+
from rich.prompt import Confirm
|
|
118
|
+
if not Confirm.ask("Continue?", default=False):
|
|
119
|
+
console.print("[dim]Cleanup cancelled[/dim]\n")
|
|
120
|
+
return 0
|
|
121
|
+
|
|
122
|
+
# Delete temporary projects
|
|
123
|
+
console.print("")
|
|
124
|
+
deleted_count = 0
|
|
125
|
+
failed_count = 0
|
|
126
|
+
|
|
127
|
+
for project in temp_projects:
|
|
128
|
+
try:
|
|
129
|
+
shutil.rmtree(project)
|
|
130
|
+
console.print(f"[green]✓[/green] Deleted {project.name}")
|
|
131
|
+
deleted_count += 1
|
|
132
|
+
except Exception as e:
|
|
133
|
+
console.print(f"[red]✗[/red] Failed to delete {project.name}: {e}")
|
|
134
|
+
logger.error(f"Failed to delete {project}: {e}")
|
|
135
|
+
failed_count += 1
|
|
136
|
+
|
|
137
|
+
# Summary
|
|
138
|
+
console.print(f"\n[green]✓ Cleanup complete[/green]")
|
|
139
|
+
console.print(f" Deleted: {deleted_count}")
|
|
140
|
+
if failed_count > 0:
|
|
141
|
+
console.print(f" [red]Failed: {failed_count}[/red]")
|
|
142
|
+
console.print(f" Freed: {total_str}\n")
|
|
143
|
+
|
|
144
|
+
return 0 if failed_count == 0 else 1
|
|
145
|
+
|
|
146
|
+
except Exception as e:
|
|
147
|
+
logger.error(f"Error in clean command: {e}", exc_info=True)
|
|
148
|
+
console.print(f"[red]Error: {e}[/red]")
|
|
149
|
+
return 1
|
realign/commands/config.py
CHANGED
|
@@ -37,7 +37,7 @@ def config_command(
|
|
|
37
37
|
aline config set llm_provider openai # Set LLM provider to OpenAI
|
|
38
38
|
aline config set llm_provider auto # Set LLM provider to auto
|
|
39
39
|
"""
|
|
40
|
-
config_path = Path.home() / ".config" / "
|
|
40
|
+
config_path = Path.home() / ".config" / "aline" / "config.yaml"
|
|
41
41
|
|
|
42
42
|
if action == "init":
|
|
43
43
|
# Initialize config file
|