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
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: aline-ai
3
- Version: 0.2.5
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,,
@@ -0,0 +1,3 @@
1
+ [console_scripts]
2
+ aline = realign.cli:app
3
+ aline-mcp = realign.mcp_server:main
realign/__init__.py CHANGED
@@ -1,3 +1,34 @@
1
1
  """Aline - AI Agent Chat Session Tracker."""
2
2
 
3
- __version__ = "0.2.5"
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, search, show, config, commit, auto_commit, review, hide, push
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
- app.command(name="commit")(commit.commit_command)
25
- app.command(name="auto-commit")(auto_commit.auto_commit_command)
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
- remote: str = typer.Option("origin", "--remote", "-r", help="Git remote name"),
54
- branch: Optional[str] = typer.Option(None, "--branch", "-b", help="Branch to push (default: current branch)"),
55
- force: bool = typer.Option(False, "--force", "-f", help="Skip all confirmation prompts"),
56
- dry_run: bool = typer.Option(False, "--dry-run", help="Show what would be pushed without actually pushing"),
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
- """Interactive push workflow with review and hide options.
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
- This command provides an interactive workflow:
61
- 1. Review unpushed commits
62
- 2. Optionally hide sensitive commits
63
- 3. Final confirmation before pushing
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
- exit_code = push.push_command(
66
- remote=remote,
67
- branch=branch,
68
- force=force,
69
- dry_run=dry_run
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
 
@@ -1,5 +1,5 @@
1
1
  """ReAlign commands module."""
2
2
 
3
- from . import init, search, show, config, commit, auto_commit, review, hide, push
3
+ from . import init, config, review, hide, clean, import_history
4
4
 
5
- __all__ = ["init", "search", "show", "config", "commit", "auto_commit", "review", "hide", "push"]
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
@@ -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" / "realign" / "config.yaml"
40
+ config_path = Path.home() / ".config" / "aline" / "config.yaml"
41
41
 
42
42
  if action == "init":
43
43
  # Initialize config file