aline-ai 0.6.3__py3-none-any.whl → 0.6.5__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.6.3.dist-info → aline_ai-0.6.5.dist-info}/METADATA +1 -1
- {aline_ai-0.6.3.dist-info → aline_ai-0.6.5.dist-info}/RECORD +26 -23
- realign/__init__.py +1 -1
- realign/adapters/codex.py +14 -9
- realign/cli.py +42 -235
- realign/codex_detector.py +72 -32
- realign/codex_home.py +85 -0
- realign/codex_terminal_linker.py +172 -0
- realign/commands/__init__.py +2 -2
- realign/commands/add.py +89 -9
- realign/commands/doctor.py +497 -0
- realign/commands/init.py +66 -4
- realign/commands/watcher.py +2 -1
- realign/config.py +10 -1
- realign/dashboard/app.py +2 -149
- realign/dashboard/tmux_manager.py +171 -5
- realign/dashboard/widgets/config_panel.py +91 -11
- realign/dashboard/widgets/sessions_table.py +1 -1
- realign/dashboard/widgets/terminal_panel.py +400 -35
- realign/db/sqlite_db.py +76 -0
- realign/hooks.py +6 -128
- realign/watcher_core.py +50 -0
- {aline_ai-0.6.3.dist-info → aline_ai-0.6.5.dist-info}/WHEEL +0 -0
- {aline_ai-0.6.3.dist-info → aline_ai-0.6.5.dist-info}/entry_points.txt +0 -0
- {aline_ai-0.6.3.dist-info → aline_ai-0.6.5.dist-info}/licenses/LICENSE +0 -0
- {aline_ai-0.6.3.dist-info → aline_ai-0.6.5.dist-info}/top_level.txt +0 -0
|
@@ -1,26 +1,28 @@
|
|
|
1
|
-
aline_ai-0.6.
|
|
2
|
-
realign/__init__.py,sha256=
|
|
1
|
+
aline_ai-0.6.5.dist-info/licenses/LICENSE,sha256=H8wTqV5IF1oHw_HbBtS1PSDU8G_q81yblEIL_JfV8Vo,1077
|
|
2
|
+
realign/__init__.py,sha256=02FiDcPQx1TGbGJO98rtDO7k-JAA9WrZKtygoavnEY8,1623
|
|
3
3
|
realign/auth.py,sha256=d_1yvCwluN5iIrdgjtuSKpOYAksDzrzNgntKacLVJrw,16583
|
|
4
4
|
realign/claude_detector.py,sha256=ZLSJacMo6zzQclXByABKA70UNpstxqIv3fPGqdpA934,2792
|
|
5
|
-
realign/cli.py,sha256=
|
|
6
|
-
realign/codex_detector.py,sha256=
|
|
7
|
-
realign/
|
|
5
|
+
realign/cli.py,sha256=HZ_1Rm50z1oszCwvPAZcAdPt0Gl-dj0S0NMLy2sWu_4,35665
|
|
6
|
+
realign/codex_detector.py,sha256=xTpYgMfUwL6UL76xeHl8xF2ZBPHdjwmgXmbmZkvHA0A,5523
|
|
7
|
+
realign/codex_home.py,sha256=gAAosBDru4jfz0QCn12A2bZEC_lZxytpOAzk7GOXTpI,2512
|
|
8
|
+
realign/codex_terminal_linker.py,sha256=9cDUHhN7MhCIUOfb-3kApPY-l6s91jq2qq_WI0ccexY,5926
|
|
9
|
+
realign/config.py,sha256=Znfs43AjiK90LGWnArDPWyrE859sdZQAPIb0KAcU3Ig,9252
|
|
8
10
|
realign/context.py,sha256=8hzgNOg-7_eMW22wt7OM5H9IsmMveKXCv0epG7E0G7w,13917
|
|
9
11
|
realign/file_lock.py,sha256=kLNm1Rra4TCrTMyPM5fwjVascq-CUz2Bzh9HHKtCKOE,3444
|
|
10
|
-
realign/hooks.py,sha256=
|
|
12
|
+
realign/hooks.py,sha256=wSSIjS5x9w7fm9LUcL63Lf7bglEfb75dHFja_znKDDQ,65134
|
|
11
13
|
realign/llm_client.py,sha256=QqMPDFE-aXm7oz0QAkB90CN0Qn0uz7JOxpWbUUlHNgU,11141
|
|
12
14
|
realign/logging_config.py,sha256=LCAigKFhTj86PSJm4-kUl3Ag9h_GENh3x2iPnMv7qUI,4871
|
|
13
15
|
realign/mcp_server.py,sha256=LWiQ2qukYoNLsoV2ID2f0vF9jkJlBvB587HpM5jymgE,10193
|
|
14
16
|
realign/mcp_watcher.py,sha256=aK4jWStv7CoCroS4tXFHgZ_y_-q4QDjrpWgm4DxcEj4,1260
|
|
15
17
|
realign/redactor.py,sha256=Zsoi5HfYak2yPmck20JArhm-1cPSB78IdkBJiNVXfrc,17096
|
|
16
|
-
realign/watcher_core.py,sha256=
|
|
18
|
+
realign/watcher_core.py,sha256=0XCoA5giuie-Ytc_tlPTVbZH8EFRPmODu7DUYRhRBGo,108598
|
|
17
19
|
realign/watcher_daemon.py,sha256=OHUQ9P1LlagKJHfrf6uRnzO-zDtBRXIxt8ydMFHf5S8,3475
|
|
18
20
|
realign/worker_core.py,sha256=TXioUVJlOO-8EgmKssCTLIyuh0aaupRLb1sh9s3kSuc,10194
|
|
19
21
|
realign/worker_daemon.py,sha256=X7Xyjw_u6m6KG4E84nx0HpDFw4cWMv8ja1G8btc9PiM,3957
|
|
20
22
|
realign/adapters/__init__.py,sha256=alkJr7DRn_CrJecSJRjRJOHHnkz9EnZ5TnsU8n1Bb0k,719
|
|
21
23
|
realign/adapters/base.py,sha256=2IdAZKGjg5gPB3YLf_8r3V4XAdbK7fHpj06GjjsYEFY,7409
|
|
22
24
|
realign/adapters/claude.py,sha256=ksTRwC5Z8AzUcB21LFjx6DETP08cv__fjgBzm-TeZdI,5444
|
|
23
|
-
realign/adapters/codex.py,sha256=
|
|
25
|
+
realign/adapters/codex.py,sha256=VJgmrRzOO5a6GNG6xL7gJwzcvA2CBmHdilYkj0qffBw,2233
|
|
24
26
|
realign/adapters/gemini.py,sha256=NvtXQPWUtEY-DaAAMvLGvQW4FalTG-g0pD514HYnzF0,2540
|
|
25
27
|
realign/adapters/registry.py,sha256=yM6nf9nGTJ1vaK2Uixp-VacseK7PmxZkCdKedmWI8MA,3255
|
|
26
28
|
realign/claude_hooks/__init__.py,sha256=MT9c8TWjLO23xDCM-uBBMy_mOThNd7O-AgN_Khn30qs,594
|
|
@@ -31,24 +33,25 @@ realign/claude_hooks/stop_hook_installer.py,sha256=uyqKOqpix7CQP64ERBvvh7viSPp_w
|
|
|
31
33
|
realign/claude_hooks/terminal_state.py,sha256=i8B6b_2_9ttPEemp7SrGdFRJSa-vm5lc7YSTRTvAWNg,5397
|
|
32
34
|
realign/claude_hooks/user_prompt_submit_hook.py,sha256=kMrmhAVtfV41oTX7JZcq2HPXjgQQ5gX26iOJoHJkfqA,10474
|
|
33
35
|
realign/claude_hooks/user_prompt_submit_hook_installer.py,sha256=2xLF8yZcE7Iwib9gU-xCkA1NWxNH9Nc5CFKPYK7rtXw,5371
|
|
34
|
-
realign/commands/__init__.py,sha256=
|
|
35
|
-
realign/commands/add.py,sha256=
|
|
36
|
+
realign/commands/__init__.py,sha256=WVaVT1orM2Z0PYaG3X6tkKb_t2v3n_3siCadh1qd_QA,107
|
|
37
|
+
realign/commands/add.py,sha256=_Xzt9P15mwndA3JvBBVrki8tn9Cc0UP6SiLwM4RS8Nc,27232
|
|
36
38
|
realign/commands/auth.py,sha256=QrPukpP-ogYEDSwztV0NOYI-HDgn5fPxlCQ1-e2n7gU,11082
|
|
37
39
|
realign/commands/config.py,sha256=nYnu_h2pk7GODcrzrV04K51D-s7v06FlRXHJ0HJ-gvU,6732
|
|
38
40
|
realign/commands/context.py,sha256=pM2KfZHVkB-ou4nBhFvKSwnYliLBzwN3zerLyBAbhfE,7095
|
|
41
|
+
realign/commands/doctor.py,sha256=q5UOrUR5Uai4AxgaeOnK1Hig5I5UX7m3Vt00tPnUllg,18289
|
|
39
42
|
realign/commands/export_shares.py,sha256=WNOR7FBE2om9qPO_28edZKhs94lyUAcbRgP_kNaDi5M,132574
|
|
40
43
|
realign/commands/import_shares.py,sha256=HiswLlYHqR0dR3wgB7Rs54_WownqahIs5IdyJOHuot8,25572
|
|
41
|
-
realign/commands/init.py,sha256=
|
|
44
|
+
realign/commands/init.py,sha256=6rBr1LVIrQLbUH_UvoDhkF1qXmMh2xkjNWCYAUz5Tho,35274
|
|
42
45
|
realign/commands/restore.py,sha256=s2BxQZHxQw9r12NzRVsK20KlGafy5AIoSjWMo5PcnHY,11173
|
|
43
46
|
realign/commands/search.py,sha256=QJrC0hln9sCDFxXbpo0nPGMHXrud18qA5QfRyD0z6fQ,25926
|
|
44
47
|
realign/commands/upgrade.py,sha256=L3PLOUIN5qAQTbkfoVtSsIbbzEezA_xjjk9F1GMVfjw,12781
|
|
45
|
-
realign/commands/watcher.py,sha256=
|
|
48
|
+
realign/commands/watcher.py,sha256=4WTThIgr-Z5guKh_JqGDcPmerr97XiHrVaaijmckHsA,134350
|
|
46
49
|
realign/commands/worker.py,sha256=jTu7Pj60nTnn7SsH3oNCNnO6zl4TIFCJVNSC1OoQ_0o,23363
|
|
47
50
|
realign/dashboard/__init__.py,sha256=QZkHTsGityH8UkF8rmvA3xW7dMXNe0swEWr443qfgCM,128
|
|
48
|
-
realign/dashboard/app.py,sha256=
|
|
51
|
+
realign/dashboard/app.py,sha256=aB1pvuJu-qJ94UqNegB4lvIxUzQJovuC82WQjFnQIFc,10464
|
|
49
52
|
realign/dashboard/layout.py,sha256=sZxmFj6QTbkois9MHTvBEMMcnaRVehCDqugdbiFx10k,9072
|
|
50
53
|
realign/dashboard/terminal_backend.py,sha256=MlDfwtqhftyQK6jDNizQGFjAWIo5Bx2TDpSnP3MCZVM,3375
|
|
51
|
-
realign/dashboard/tmux_manager.py,sha256=
|
|
54
|
+
realign/dashboard/tmux_manager.py,sha256=Fc6OQbnOO4YV47BnrIkcr0SHnQuSFwUSqhepNkpqKLs,32942
|
|
52
55
|
realign/dashboard/backends/__init__.py,sha256=POROX7YKtukYZcLB1pi_kO0sSEpuO3y-hwmF3WIN1Kk,163
|
|
53
56
|
realign/dashboard/backends/iterm2.py,sha256=XYYJT5lrrp4pW_MyEqPZYkRI0qyKUwJlezwMidgnsHc,21390
|
|
54
57
|
realign/dashboard/backends/kitty.py,sha256=5jdkR1f2PwB8a4SnS3EG6uOQ2XU-PB7-cpKBfIJq3hU,12066
|
|
@@ -61,13 +64,13 @@ realign/dashboard/screens/session_detail.py,sha256=TBkHqSHyMxsLB2QdZq9m1EoiH8oRV
|
|
|
61
64
|
realign/dashboard/screens/share_import.py,sha256=hl2x0yGVycsoUI76AmdZTAV-br3Q6191g5xHHrZ8hOA,6318
|
|
62
65
|
realign/dashboard/styles/dashboard.tcss,sha256=ewonevBGLN-dfSsgxUk4VBCPchtxY4rx_vj1u6Ox2Fw,3454
|
|
63
66
|
realign/dashboard/widgets/__init__.py,sha256=3Pf2_K9obrertgv_psfxradgkI9RXlmjoXYQH7oBKm0,583
|
|
64
|
-
realign/dashboard/widgets/config_panel.py,sha256=
|
|
67
|
+
realign/dashboard/widgets/config_panel.py,sha256=eRJRuqImQ8eJIKCEj4O8EvYxI-ht_anrcYbT5JskWyU,15972
|
|
65
68
|
realign/dashboard/widgets/events_table.py,sha256=MKB1G1_xdQCujEhmMz_GKI4hs-PeEiqGEAH7Y3ZGanE,30852
|
|
66
69
|
realign/dashboard/widgets/header.py,sha256=0HHCFXX7F3C6HII-WDwOJwWkJrajmKPWmdoMWyOkn9E,1587
|
|
67
70
|
realign/dashboard/widgets/openable_table.py,sha256=GeJPDEYp0kRHShqvmPMzAePpYXRZHUNqcWNnxqsqxjA,1963
|
|
68
71
|
realign/dashboard/widgets/search_panel.py,sha256=ZNJDfwDSxUFnCeltYQYsQsPJ6t4HDeNWpENoTOoBdVM,8951
|
|
69
|
-
realign/dashboard/widgets/sessions_table.py,sha256=
|
|
70
|
-
realign/dashboard/widgets/terminal_panel.py,sha256=
|
|
72
|
+
realign/dashboard/widgets/sessions_table.py,sha256=oMkYhQ55pUGOGYxEXM5P37mpGYA350BK8Rb8fVq9AS4,34008
|
|
73
|
+
realign/dashboard/widgets/terminal_panel.py,sha256=8WX2_EewlyFlxJYokw2akEqkJUjNt_-F8tzE7St3084,60132
|
|
71
74
|
realign/dashboard/widgets/watcher_panel.py,sha256=emVY1-aot9Dnf5UI9yyNeEmp4d2Gb-lrC28DjkeLjKA,19575
|
|
72
75
|
realign/dashboard/widgets/worker_panel.py,sha256=F_jKWABuCNmjQgeeuCr4KnFRKdY4CLTNcEXMYwsNaSk,18691
|
|
73
76
|
realign/db/__init__.py,sha256=65LsNdsq_rkwNC1eg1OAr3HC0ORXtelOh0I8MhNGr-g,3288
|
|
@@ -76,7 +79,7 @@ realign/db/locks.py,sha256=yzCiPJZ4eOQX-Q4mXB6s76U2U7lXAzIBBy1t59w-AVU,1698
|
|
|
76
79
|
realign/db/migrate_agents.py,sha256=cDeVUzKW950dJ0lV74QObHuONqKwErSrXI5akU2vBmQ,9633
|
|
77
80
|
realign/db/migration.py,sha256=af1QFEfIh_qX0pFyXzm5gWFVbQn0sKOUNLSJHlr__FU,13405
|
|
78
81
|
realign/db/schema.py,sha256=YHj5PGZWbCl0VG0epnMF_Ofg3jRiLHq6SLHCi1q34eQ,30181
|
|
79
|
-
realign/db/sqlite_db.py,sha256=
|
|
82
|
+
realign/db/sqlite_db.py,sha256=nihEZ71wg1BXiVG1QU488ed9Q-ZasoVKYVS4j20hhtY,107223
|
|
80
83
|
realign/events/__init__.py,sha256=IM-NxF4Zk2hYFD07k4WrfNRuuiC9ihGjf4GBpJhjd2E,35
|
|
81
84
|
realign/events/debouncer.py,sha256=U3Q7dYpnMsAgWsW_E_IbSC4lrdEoi6H_SFLGLOAazs4,3062
|
|
82
85
|
realign/events/event_summarizer.py,sha256=jJtWM8UWtsG4KGdzYicMqcTxrncWzGNEQs5vdBJPyew,10185
|
|
@@ -94,8 +97,8 @@ realign/triggers/next_turn_trigger.py,sha256=-x80_I-WmIjXXzQHEPBykgx_GQW6oKaLDQx
|
|
|
94
97
|
realign/triggers/registry.py,sha256=dkIjSd8Bg-hF0nxaO2Fi2K-0Zipqv6vVjc-HYSrA_fY,3656
|
|
95
98
|
realign/triggers/turn_status.py,sha256=wAZEhXDAmDoX5F-ohWfSnZZ0eA6DAJ9svSPiSv_f6sg,6041
|
|
96
99
|
realign/triggers/turn_summary.py,sha256=f3hEUshgv9skJ9AbfWpoYs417lsv_HK2A_vpPjgryO4,4467
|
|
97
|
-
aline_ai-0.6.
|
|
98
|
-
aline_ai-0.6.
|
|
99
|
-
aline_ai-0.6.
|
|
100
|
-
aline_ai-0.6.
|
|
101
|
-
aline_ai-0.6.
|
|
100
|
+
aline_ai-0.6.5.dist-info/METADATA,sha256=RmD0VjSn_0nGStyFKgkNYUL3i2foaqg8UWUT3kOUTOc,1597
|
|
101
|
+
aline_ai-0.6.5.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
|
|
102
|
+
aline_ai-0.6.5.dist-info/entry_points.txt,sha256=TvYELpMoWsUTcQdMV8tBHxCbEf_LbK4sESqK3r8PM6Y,78
|
|
103
|
+
aline_ai-0.6.5.dist-info/top_level.txt,sha256=yIL3s2xv9nf1GwD5n71Aq_JEIV4AfzCIDNKBzewuRm4,8
|
|
104
|
+
aline_ai-0.6.5.dist-info/RECORD,,
|
realign/__init__.py
CHANGED
realign/adapters/codex.py
CHANGED
|
@@ -21,17 +21,22 @@ class CodexAdapter(SessionAdapter):
|
|
|
21
21
|
|
|
22
22
|
def discover_sessions(self) -> List[Path]:
|
|
23
23
|
"""Find all Codex sessions."""
|
|
24
|
-
sessions = []
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
if not codex_sessions_base.exists():
|
|
28
|
-
return sessions
|
|
29
|
-
|
|
30
|
-
# Find all session files recursively
|
|
24
|
+
sessions: list[Path] = []
|
|
25
|
+
roots: list[Path] = []
|
|
31
26
|
try:
|
|
32
|
-
|
|
27
|
+
from ..codex_detector import _codex_session_roots # type: ignore[attr-defined]
|
|
28
|
+
|
|
29
|
+
roots = _codex_session_roots()
|
|
33
30
|
except Exception:
|
|
34
|
-
|
|
31
|
+
roots = [Path.home() / ".codex" / "sessions"]
|
|
32
|
+
|
|
33
|
+
for root in roots:
|
|
34
|
+
if not root.exists():
|
|
35
|
+
continue
|
|
36
|
+
try:
|
|
37
|
+
sessions.extend(root.rglob("rollout-*.jsonl"))
|
|
38
|
+
except Exception:
|
|
39
|
+
continue
|
|
35
40
|
|
|
36
41
|
return sessions
|
|
37
42
|
|
realign/cli.py
CHANGED
|
@@ -7,7 +7,19 @@ from typing import Optional
|
|
|
7
7
|
from rich.console import Console
|
|
8
8
|
from rich.syntax import Syntax
|
|
9
9
|
|
|
10
|
-
from .commands import
|
|
10
|
+
from .commands import (
|
|
11
|
+
init,
|
|
12
|
+
config,
|
|
13
|
+
watcher,
|
|
14
|
+
worker,
|
|
15
|
+
export_shares,
|
|
16
|
+
search,
|
|
17
|
+
upgrade,
|
|
18
|
+
restore,
|
|
19
|
+
add,
|
|
20
|
+
auth,
|
|
21
|
+
doctor,
|
|
22
|
+
)
|
|
11
23
|
|
|
12
24
|
app = typer.Typer(
|
|
13
25
|
name="realign",
|
|
@@ -33,6 +45,22 @@ def main(
|
|
|
33
45
|
ctx.obj["dev"] = dev
|
|
34
46
|
|
|
35
47
|
if ctx.invoked_subcommand is None:
|
|
48
|
+
def _needs_global_init() -> bool:
|
|
49
|
+
config_path = Path.home() / ".aline" / "config.yaml"
|
|
50
|
+
if not config_path.exists():
|
|
51
|
+
return True
|
|
52
|
+
try:
|
|
53
|
+
from .config import ReAlignConfig
|
|
54
|
+
|
|
55
|
+
cfg = ReAlignConfig.load(config_path)
|
|
56
|
+
db_path = Path(cfg.sqlite_db_path).expanduser()
|
|
57
|
+
if not db_path.exists():
|
|
58
|
+
return True
|
|
59
|
+
except Exception:
|
|
60
|
+
return True
|
|
61
|
+
prompts_dir = Path.home() / ".aline" / "prompts"
|
|
62
|
+
return not prompts_dir.exists()
|
|
63
|
+
|
|
36
64
|
# Check login status before launching dashboard
|
|
37
65
|
from .auth import is_logged_in, get_current_user
|
|
38
66
|
|
|
@@ -53,6 +81,17 @@ def main(
|
|
|
53
81
|
|
|
54
82
|
console.print() # Add spacing before dashboard launch
|
|
55
83
|
|
|
84
|
+
# First run after install/upgrade: ensure global artifacts exist.
|
|
85
|
+
if _needs_global_init():
|
|
86
|
+
console.print("[dim]First run detected. Running 'aline init'...[/dim]\n")
|
|
87
|
+
try:
|
|
88
|
+
from .commands import init as init_cmd
|
|
89
|
+
|
|
90
|
+
init_cmd.init_command(force=False, start_watcher=None)
|
|
91
|
+
except typer.Exit as e:
|
|
92
|
+
if getattr(e, "exit_code", 1) != 0:
|
|
93
|
+
raise
|
|
94
|
+
|
|
56
95
|
# Check for updates before launching dashboard
|
|
57
96
|
from .commands.upgrade import check_and_prompt_update
|
|
58
97
|
|
|
@@ -95,6 +134,7 @@ def main(
|
|
|
95
134
|
app.command(name="init")(init.init_command)
|
|
96
135
|
app.command(name="config")(config.config_command)
|
|
97
136
|
app.command(name="upgrade")(upgrade.upgrade_command)
|
|
137
|
+
app.command(name="doctor")(doctor.doctor_command)
|
|
98
138
|
|
|
99
139
|
|
|
100
140
|
# Auth commands
|
|
@@ -119,239 +159,6 @@ def whoami_cli():
|
|
|
119
159
|
raise typer.Exit(code=exit_code)
|
|
120
160
|
|
|
121
161
|
|
|
122
|
-
@app.command(name="doctor")
|
|
123
|
-
def doctor_cli(
|
|
124
|
-
no_restart: bool = typer.Option(False, "--no-restart", help="Only clear cache, don't restart daemons"),
|
|
125
|
-
verbose: bool = typer.Option(False, "--verbose", "-v", help="Show detailed output"),
|
|
126
|
-
):
|
|
127
|
-
"""
|
|
128
|
-
Fix common issues after code updates.
|
|
129
|
-
|
|
130
|
-
This command:
|
|
131
|
-
- Clears Python bytecode cache (.pyc files)
|
|
132
|
-
- Updates Claude Code hooks (Stop, UserPromptSubmit, PermissionRequest)
|
|
133
|
-
- Updates skills to latest version
|
|
134
|
-
- Ensures watcher daemon is running (restarts if running, starts if not)
|
|
135
|
-
- Ensures worker daemon is running (restarts if running, starts if not)
|
|
136
|
-
|
|
137
|
-
Run this after pulling new code to ensure everything uses the latest version.
|
|
138
|
-
"""
|
|
139
|
-
import shutil
|
|
140
|
-
import subprocess
|
|
141
|
-
import signal
|
|
142
|
-
import time
|
|
143
|
-
|
|
144
|
-
# Find the project root (where src/realign is)
|
|
145
|
-
project_root = Path(__file__).parent.parent.parent
|
|
146
|
-
|
|
147
|
-
# 1. Clear Python cache
|
|
148
|
-
console.print("[bold]1. Clearing Python cache...[/bold]")
|
|
149
|
-
pyc_count = 0
|
|
150
|
-
pycache_count = 0
|
|
151
|
-
|
|
152
|
-
for pyc_file in project_root.rglob("*.pyc"):
|
|
153
|
-
try:
|
|
154
|
-
pyc_file.unlink()
|
|
155
|
-
pyc_count += 1
|
|
156
|
-
if verbose:
|
|
157
|
-
console.print(f" [dim]Removed: {pyc_file}[/dim]")
|
|
158
|
-
except Exception as e:
|
|
159
|
-
if verbose:
|
|
160
|
-
console.print(f" [yellow]Failed to remove {pyc_file}: {e}[/yellow]")
|
|
161
|
-
|
|
162
|
-
for pycache_dir in project_root.rglob("__pycache__"):
|
|
163
|
-
if pycache_dir.is_dir():
|
|
164
|
-
try:
|
|
165
|
-
shutil.rmtree(pycache_dir)
|
|
166
|
-
pycache_count += 1
|
|
167
|
-
if verbose:
|
|
168
|
-
console.print(f" [dim]Removed: {pycache_dir}[/dim]")
|
|
169
|
-
except Exception as e:
|
|
170
|
-
if verbose:
|
|
171
|
-
console.print(f" [yellow]Failed to remove {pycache_dir}: {e}[/yellow]")
|
|
172
|
-
|
|
173
|
-
console.print(f" [green]✓[/green] Cleared {pyc_count} .pyc files, {pycache_count} __pycache__ directories")
|
|
174
|
-
|
|
175
|
-
# 2. Update Claude Code hooks
|
|
176
|
-
console.print("\n[bold]2. Updating Claude Code hooks...[/bold]")
|
|
177
|
-
hooks_updated = []
|
|
178
|
-
hooks_failed = []
|
|
179
|
-
|
|
180
|
-
# Stop hook
|
|
181
|
-
try:
|
|
182
|
-
from .claude_hooks.stop_hook_installer import install_stop_hook, get_settings_path
|
|
183
|
-
if install_stop_hook(get_settings_path(), quiet=True, force=True):
|
|
184
|
-
hooks_updated.append("Stop")
|
|
185
|
-
if verbose:
|
|
186
|
-
console.print(" [dim]Stop hook updated[/dim]")
|
|
187
|
-
else:
|
|
188
|
-
hooks_failed.append("Stop")
|
|
189
|
-
except Exception as e:
|
|
190
|
-
hooks_failed.append("Stop")
|
|
191
|
-
if verbose:
|
|
192
|
-
console.print(f" [yellow]Stop hook failed: {e}[/yellow]")
|
|
193
|
-
|
|
194
|
-
# UserPromptSubmit hook
|
|
195
|
-
try:
|
|
196
|
-
from .claude_hooks.user_prompt_submit_hook_installer import install_user_prompt_submit_hook, get_settings_path as get_submit_settings_path
|
|
197
|
-
if install_user_prompt_submit_hook(get_submit_settings_path(), quiet=True, force=True):
|
|
198
|
-
hooks_updated.append("UserPromptSubmit")
|
|
199
|
-
if verbose:
|
|
200
|
-
console.print(" [dim]UserPromptSubmit hook updated[/dim]")
|
|
201
|
-
else:
|
|
202
|
-
hooks_failed.append("UserPromptSubmit")
|
|
203
|
-
except Exception as e:
|
|
204
|
-
hooks_failed.append("UserPromptSubmit")
|
|
205
|
-
if verbose:
|
|
206
|
-
console.print(f" [yellow]UserPromptSubmit hook failed: {e}[/yellow]")
|
|
207
|
-
|
|
208
|
-
# PermissionRequest hook
|
|
209
|
-
try:
|
|
210
|
-
from .claude_hooks.permission_request_hook_installer import install_permission_request_hook, get_settings_path as get_permission_settings_path
|
|
211
|
-
if install_permission_request_hook(get_permission_settings_path(), quiet=True, force=True):
|
|
212
|
-
hooks_updated.append("PermissionRequest")
|
|
213
|
-
if verbose:
|
|
214
|
-
console.print(" [dim]PermissionRequest hook updated[/dim]")
|
|
215
|
-
else:
|
|
216
|
-
hooks_failed.append("PermissionRequest")
|
|
217
|
-
except Exception as e:
|
|
218
|
-
hooks_failed.append("PermissionRequest")
|
|
219
|
-
if verbose:
|
|
220
|
-
console.print(f" [yellow]PermissionRequest hook failed: {e}[/yellow]")
|
|
221
|
-
|
|
222
|
-
if hooks_updated:
|
|
223
|
-
console.print(f" [green]✓[/green] Updated hooks: {', '.join(hooks_updated)}")
|
|
224
|
-
if hooks_failed:
|
|
225
|
-
console.print(f" [yellow]![/yellow] Failed hooks: {', '.join(hooks_failed)}")
|
|
226
|
-
|
|
227
|
-
# 3. Update skills
|
|
228
|
-
console.print("\n[bold]3. Updating skills...[/bold]")
|
|
229
|
-
try:
|
|
230
|
-
from .commands.add import add_skills_command
|
|
231
|
-
# Capture output by redirecting - use force=True to update
|
|
232
|
-
import io
|
|
233
|
-
import contextlib
|
|
234
|
-
|
|
235
|
-
stdout_capture = io.StringIO()
|
|
236
|
-
with contextlib.redirect_stdout(stdout_capture):
|
|
237
|
-
add_skills_command(force=True)
|
|
238
|
-
|
|
239
|
-
output = stdout_capture.getvalue()
|
|
240
|
-
# Count updated skills from output
|
|
241
|
-
updated_count = output.count("✓")
|
|
242
|
-
if updated_count > 0:
|
|
243
|
-
console.print(f" [green]✓[/green] Updated {updated_count} skill(s)")
|
|
244
|
-
else:
|
|
245
|
-
console.print(" [green]✓[/green] Skills are up to date")
|
|
246
|
-
if verbose and output.strip():
|
|
247
|
-
for line in output.strip().split("\n"):
|
|
248
|
-
console.print(f" [dim]{line}[/dim]")
|
|
249
|
-
except Exception as e:
|
|
250
|
-
console.print(f" [yellow]![/yellow] Failed to update skills: {e}")
|
|
251
|
-
|
|
252
|
-
if no_restart:
|
|
253
|
-
console.print("\n[dim]Skipping daemon restart (--no-restart)[/dim]")
|
|
254
|
-
console.print("\n[green]Done![/green] Aline is ready with the latest code.")
|
|
255
|
-
raise typer.Exit(code=0)
|
|
256
|
-
|
|
257
|
-
# 4. Restart watcher daemon
|
|
258
|
-
console.print("\n[bold]4. Checking watcher daemon...[/bold]")
|
|
259
|
-
pid_file = Path.home() / ".aline" / ".logs" / "watcher.pid"
|
|
260
|
-
watcher_was_running = False
|
|
261
|
-
|
|
262
|
-
if pid_file.exists():
|
|
263
|
-
try:
|
|
264
|
-
pid = int(pid_file.read_text().strip())
|
|
265
|
-
# Check if process is running
|
|
266
|
-
try:
|
|
267
|
-
import os
|
|
268
|
-
os.kill(pid, 0) # Signal 0 just checks if process exists
|
|
269
|
-
watcher_was_running = True
|
|
270
|
-
console.print(f" [dim]Found watcher daemon (PID {pid}), stopping...[/dim]")
|
|
271
|
-
os.kill(pid, signal.SIGTERM)
|
|
272
|
-
time.sleep(1)
|
|
273
|
-
# Force kill if still running
|
|
274
|
-
try:
|
|
275
|
-
os.kill(pid, 0)
|
|
276
|
-
os.kill(pid, signal.SIGKILL)
|
|
277
|
-
time.sleep(0.5)
|
|
278
|
-
except ProcessLookupError:
|
|
279
|
-
pass
|
|
280
|
-
except ProcessLookupError:
|
|
281
|
-
console.print(" [dim]Watcher daemon not running (stale PID file)[/dim]")
|
|
282
|
-
except Exception as e:
|
|
283
|
-
if verbose:
|
|
284
|
-
console.print(f" [yellow]Error checking watcher: {e}[/yellow]")
|
|
285
|
-
|
|
286
|
-
if watcher_was_running:
|
|
287
|
-
console.print(" [dim]Starting watcher daemon...[/dim]")
|
|
288
|
-
else:
|
|
289
|
-
console.print(" [dim]Watcher daemon was not running, starting...[/dim]")
|
|
290
|
-
try:
|
|
291
|
-
subprocess.Popen(
|
|
292
|
-
["python", "-m", "src.realign.watcher_daemon"],
|
|
293
|
-
stdout=subprocess.DEVNULL,
|
|
294
|
-
stderr=subprocess.DEVNULL,
|
|
295
|
-
start_new_session=True,
|
|
296
|
-
cwd=str(project_root),
|
|
297
|
-
)
|
|
298
|
-
time.sleep(2)
|
|
299
|
-
action = "restarted" if watcher_was_running else "started"
|
|
300
|
-
console.print(f" [green]✓[/green] Watcher daemon {action}")
|
|
301
|
-
except Exception as e:
|
|
302
|
-
action = "restart" if watcher_was_running else "start"
|
|
303
|
-
console.print(f" [red]✗[/red] Failed to {action} watcher: {e}")
|
|
304
|
-
|
|
305
|
-
# 5. Restart worker daemon
|
|
306
|
-
console.print("\n[bold]5. Checking worker daemon...[/bold]")
|
|
307
|
-
worker_pid_file = Path.home() / ".aline" / ".logs" / "worker.pid"
|
|
308
|
-
worker_was_running = False
|
|
309
|
-
|
|
310
|
-
if worker_pid_file.exists():
|
|
311
|
-
try:
|
|
312
|
-
pid = int(worker_pid_file.read_text().strip())
|
|
313
|
-
try:
|
|
314
|
-
import os
|
|
315
|
-
os.kill(pid, 0)
|
|
316
|
-
worker_was_running = True
|
|
317
|
-
console.print(f" [dim]Found worker daemon (PID {pid}), stopping...[/dim]")
|
|
318
|
-
os.kill(pid, signal.SIGTERM)
|
|
319
|
-
time.sleep(1)
|
|
320
|
-
try:
|
|
321
|
-
os.kill(pid, 0)
|
|
322
|
-
os.kill(pid, signal.SIGKILL)
|
|
323
|
-
time.sleep(0.5)
|
|
324
|
-
except ProcessLookupError:
|
|
325
|
-
pass
|
|
326
|
-
except ProcessLookupError:
|
|
327
|
-
console.print(" [dim]Worker daemon not running (stale PID file)[/dim]")
|
|
328
|
-
except Exception as e:
|
|
329
|
-
if verbose:
|
|
330
|
-
console.print(f" [yellow]Error checking worker: {e}[/yellow]")
|
|
331
|
-
|
|
332
|
-
if worker_was_running:
|
|
333
|
-
console.print(" [dim]Starting worker daemon...[/dim]")
|
|
334
|
-
else:
|
|
335
|
-
console.print(" [dim]Worker daemon was not running, starting...[/dim]")
|
|
336
|
-
try:
|
|
337
|
-
subprocess.Popen(
|
|
338
|
-
["python", "-m", "src.realign.worker_daemon"],
|
|
339
|
-
stdout=subprocess.DEVNULL,
|
|
340
|
-
stderr=subprocess.DEVNULL,
|
|
341
|
-
start_new_session=True,
|
|
342
|
-
cwd=str(project_root),
|
|
343
|
-
)
|
|
344
|
-
time.sleep(2)
|
|
345
|
-
action = "restarted" if worker_was_running else "started"
|
|
346
|
-
console.print(f" [green]✓[/green] Worker daemon {action}")
|
|
347
|
-
except Exception as e:
|
|
348
|
-
action = "restart" if worker_was_running else "start"
|
|
349
|
-
console.print(f" [red]✗[/red] Failed to {action} worker: {e}")
|
|
350
|
-
|
|
351
|
-
console.print("\n[green]Done![/green] Aline is ready with the latest code.")
|
|
352
|
-
raise typer.Exit(code=0)
|
|
353
|
-
|
|
354
|
-
|
|
355
162
|
@app.command(name="search")
|
|
356
163
|
def search_cli(
|
|
357
164
|
query: str = typer.Argument(..., help="Search query (keywords or regex pattern)"),
|
|
@@ -445,7 +252,7 @@ app.add_typer(add_app, name="add")
|
|
|
445
252
|
@add_app.command(name="tmux")
|
|
446
253
|
def add_tmux_cli():
|
|
447
254
|
"""Install tmux via Homebrew and set up Aline tmux clipboard bindings."""
|
|
448
|
-
exit_code = add.add_tmux_command()
|
|
255
|
+
exit_code = add.add_tmux_command(install_brew=True)
|
|
449
256
|
raise typer.Exit(code=exit_code)
|
|
450
257
|
|
|
451
258
|
|
realign/codex_detector.py
CHANGED
|
@@ -7,6 +7,42 @@ from pathlib import Path
|
|
|
7
7
|
from typing import Optional, List
|
|
8
8
|
|
|
9
9
|
|
|
10
|
+
def _codex_session_roots() -> list[Path]:
|
|
11
|
+
"""Return all Codex session root directories to scan (best-effort)."""
|
|
12
|
+
roots: list[Path] = []
|
|
13
|
+
|
|
14
|
+
# Default Codex home: ~/.codex/sessions
|
|
15
|
+
roots.append(Path.home() / ".codex" / "sessions")
|
|
16
|
+
|
|
17
|
+
# Aline-managed per-terminal CODEX_HOME isolation: ~/.aline/codex_homes/*/sessions
|
|
18
|
+
try:
|
|
19
|
+
from .codex_home import aline_codex_homes_dir, codex_sessions_dir_for_home
|
|
20
|
+
|
|
21
|
+
homes = aline_codex_homes_dir()
|
|
22
|
+
if homes.exists():
|
|
23
|
+
for child in homes.iterdir():
|
|
24
|
+
if child.is_dir():
|
|
25
|
+
roots.append(codex_sessions_dir_for_home(child))
|
|
26
|
+
except Exception:
|
|
27
|
+
pass
|
|
28
|
+
|
|
29
|
+
# De-dup + only keep existing directories
|
|
30
|
+
out: list[Path] = []
|
|
31
|
+
seen: set[str] = set()
|
|
32
|
+
for r in roots:
|
|
33
|
+
try:
|
|
34
|
+
rr = r.expanduser()
|
|
35
|
+
except Exception:
|
|
36
|
+
rr = r
|
|
37
|
+
key = str(rr)
|
|
38
|
+
if key in seen:
|
|
39
|
+
continue
|
|
40
|
+
seen.add(key)
|
|
41
|
+
if rr.exists():
|
|
42
|
+
out.append(rr)
|
|
43
|
+
return out
|
|
44
|
+
|
|
45
|
+
|
|
10
46
|
def find_codex_sessions_for_project(project_path: Path, days_back: int = 7) -> List[Path]:
|
|
11
47
|
"""
|
|
12
48
|
Find Codex sessions for a given project path.
|
|
@@ -22,9 +58,8 @@ def find_codex_sessions_for_project(project_path: Path, days_back: int = 7) -> L
|
|
|
22
58
|
Returns:
|
|
23
59
|
List of session file paths that match the project, sorted by timestamp (newest first)
|
|
24
60
|
"""
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
if not codex_sessions_base.exists():
|
|
61
|
+
codex_session_roots = _codex_session_roots()
|
|
62
|
+
if not codex_session_roots:
|
|
28
63
|
return []
|
|
29
64
|
|
|
30
65
|
# Normalize project path for comparison
|
|
@@ -32,36 +67,37 @@ def find_codex_sessions_for_project(project_path: Path, days_back: int = 7) -> L
|
|
|
32
67
|
|
|
33
68
|
matching_sessions = []
|
|
34
69
|
|
|
35
|
-
# Search through recent days
|
|
36
|
-
for
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
# Check all session files in this date directory
|
|
49
|
-
for session_file in date_path.glob("rollout-*.jsonl"):
|
|
50
|
-
try:
|
|
51
|
-
# Read first line to get session metadata
|
|
52
|
-
with open(session_file, "r", encoding="utf-8") as f:
|
|
53
|
-
first_line = f.readline()
|
|
54
|
-
if first_line:
|
|
55
|
-
data = json.loads(first_line)
|
|
56
|
-
if data.get("type") == "session_meta":
|
|
57
|
-
session_cwd = data.get("payload", {}).get("cwd", "")
|
|
58
|
-
# Match the project path
|
|
59
|
-
if session_cwd == abs_project_path:
|
|
60
|
-
matching_sessions.append(session_file)
|
|
61
|
-
except (json.JSONDecodeError, IOError):
|
|
62
|
-
# Skip malformed or unreadable files
|
|
70
|
+
# Search through recent days in each root (YYYY/MM/DD layout).
|
|
71
|
+
for root in codex_session_roots:
|
|
72
|
+
for days_ago in range(days_back + 1):
|
|
73
|
+
target_date = datetime.now() - timedelta(days=days_ago)
|
|
74
|
+
date_path = (
|
|
75
|
+
root
|
|
76
|
+
/ str(target_date.year)
|
|
77
|
+
/ f"{target_date.month:02d}"
|
|
78
|
+
/ f"{target_date.day:02d}"
|
|
79
|
+
)
|
|
80
|
+
|
|
81
|
+
if not date_path.exists():
|
|
63
82
|
continue
|
|
64
83
|
|
|
84
|
+
# Check all session files in this date directory
|
|
85
|
+
for session_file in date_path.glob("rollout-*.jsonl"):
|
|
86
|
+
try:
|
|
87
|
+
# Read first line to get session metadata
|
|
88
|
+
with open(session_file, "r", encoding="utf-8") as f:
|
|
89
|
+
first_line = f.readline()
|
|
90
|
+
if first_line:
|
|
91
|
+
data = json.loads(first_line)
|
|
92
|
+
if data.get("type") == "session_meta":
|
|
93
|
+
session_cwd = data.get("payload", {}).get("cwd", "")
|
|
94
|
+
# Match the project path
|
|
95
|
+
if session_cwd == abs_project_path:
|
|
96
|
+
matching_sessions.append(session_file)
|
|
97
|
+
except (json.JSONDecodeError, IOError):
|
|
98
|
+
# Skip malformed or unreadable files
|
|
99
|
+
continue
|
|
100
|
+
|
|
65
101
|
# Sort by modification time, newest first
|
|
66
102
|
matching_sessions.sort(key=lambda p: p.stat().st_mtime, reverse=True)
|
|
67
103
|
|
|
@@ -90,8 +126,12 @@ def get_codex_sessions_dir() -> Optional[Path]:
|
|
|
90
126
|
Returns:
|
|
91
127
|
Path to ~/.codex/sessions if it exists, None otherwise
|
|
92
128
|
"""
|
|
129
|
+
# Preserve old API: return default location if present, else the first discovered root.
|
|
93
130
|
codex_sessions = Path.home() / ".codex" / "sessions"
|
|
94
|
-
|
|
131
|
+
if codex_sessions.exists():
|
|
132
|
+
return codex_sessions
|
|
133
|
+
roots = _codex_session_roots()
|
|
134
|
+
return roots[0] if roots else None
|
|
95
135
|
|
|
96
136
|
|
|
97
137
|
def auto_detect_codex_sessions(
|