aline-ai 0.6.0__py3-none-any.whl → 0.6.2__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.0.dist-info → aline_ai-0.6.2.dist-info}/METADATA +1 -1
- {aline_ai-0.6.0.dist-info → aline_ai-0.6.2.dist-info}/RECORD +25 -20
- realign/__init__.py +1 -1
- realign/auth.py +21 -0
- realign/claude_hooks/stop_hook.py +35 -0
- realign/claude_hooks/user_prompt_submit_hook.py +5 -0
- realign/cli.py +76 -34
- realign/commands/auth.py +9 -0
- realign/dashboard/app.py +69 -6
- realign/dashboard/backends/__init__.py +6 -0
- realign/dashboard/backends/iterm2.py +599 -0
- realign/dashboard/backends/kitty.py +372 -0
- realign/dashboard/layout.py +320 -0
- realign/dashboard/screens/create_agent.py +41 -4
- realign/dashboard/terminal_backend.py +110 -0
- realign/dashboard/tmux_manager.py +17 -0
- realign/dashboard/widgets/terminal_panel.py +587 -110
- realign/db/sqlite_db.py +18 -0
- realign/events/session_summarizer.py +17 -2
- realign/watcher_core.py +56 -22
- realign/worker_core.py +2 -0
- {aline_ai-0.6.0.dist-info → aline_ai-0.6.2.dist-info}/WHEEL +0 -0
- {aline_ai-0.6.0.dist-info → aline_ai-0.6.2.dist-info}/entry_points.txt +0 -0
- {aline_ai-0.6.0.dist-info → aline_ai-0.6.2.dist-info}/licenses/LICENSE +0 -0
- {aline_ai-0.6.0.dist-info → aline_ai-0.6.2.dist-info}/top_level.txt +0 -0
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
aline_ai-0.6.
|
|
2
|
-
realign/__init__.py,sha256=
|
|
3
|
-
realign/auth.py,sha256=
|
|
1
|
+
aline_ai-0.6.2.dist-info/licenses/LICENSE,sha256=H8wTqV5IF1oHw_HbBtS1PSDU8G_q81yblEIL_JfV8Vo,1077
|
|
2
|
+
realign/__init__.py,sha256=WCultxtZIKlfMhrDP35gb3Jtyvc7vOLpx-I1JO6nPAc,1623
|
|
3
|
+
realign/auth.py,sha256=d_1yvCwluN5iIrdgjtuSKpOYAksDzrzNgntKacLVJrw,16583
|
|
4
4
|
realign/claude_detector.py,sha256=ZLSJacMo6zzQclXByABKA70UNpstxqIv3fPGqdpA934,2792
|
|
5
|
-
realign/cli.py,sha256=
|
|
5
|
+
realign/cli.py,sha256=M8lpAkDaE2ZjESzrlksFk8M8cpbFKkIzK3y8qNnZmJo,43793
|
|
6
6
|
realign/codex_detector.py,sha256=N9ulgMgvTzDfXE4s4vLd6OoS0hT7R6h2bDFFXWa-2hE,4183
|
|
7
7
|
realign/config.py,sha256=d8HQ6v1xju6cjNGbR7LlfHaMyvFPcMDofhGbepxpQq8,11634
|
|
8
8
|
realign/context.py,sha256=8hzgNOg-7_eMW22wt7OM5H9IsmMveKXCv0epG7E0G7w,13917
|
|
@@ -13,9 +13,9 @@ realign/logging_config.py,sha256=LCAigKFhTj86PSJm4-kUl3Ag9h_GENh3x2iPnMv7qUI,487
|
|
|
13
13
|
realign/mcp_server.py,sha256=LWiQ2qukYoNLsoV2ID2f0vF9jkJlBvB587HpM5jymgE,10193
|
|
14
14
|
realign/mcp_watcher.py,sha256=aK4jWStv7CoCroS4tXFHgZ_y_-q4QDjrpWgm4DxcEj4,1260
|
|
15
15
|
realign/redactor.py,sha256=Zsoi5HfYak2yPmck20JArhm-1cPSB78IdkBJiNVXfrc,17096
|
|
16
|
-
realign/watcher_core.py,sha256=
|
|
16
|
+
realign/watcher_core.py,sha256=k9esacTBfHpa77MPEXAsiMDiFa4Ml52vWfGGT-IucAM,108135
|
|
17
17
|
realign/watcher_daemon.py,sha256=OHUQ9P1LlagKJHfrf6uRnzO-zDtBRXIxt8ydMFHf5S8,3475
|
|
18
|
-
realign/worker_core.py,sha256
|
|
18
|
+
realign/worker_core.py,sha256=TXioUVJlOO-8EgmKssCTLIyuh0aaupRLb1sh9s3kSuc,10194
|
|
19
19
|
realign/worker_daemon.py,sha256=X7Xyjw_u6m6KG4E84nx0HpDFw4cWMv8ja1G8btc9PiM,3957
|
|
20
20
|
realign/adapters/__init__.py,sha256=bpDm5aBxMdq4OA_beYahoUb4zfNaq3KOG6KghQJruRc,827
|
|
21
21
|
realign/adapters/antigravity.py,sha256=geaYxAEswpgsVtERqsQ1OwvPFsy5tRkyjx2yQ-Uq9nM,5461
|
|
@@ -27,14 +27,14 @@ realign/adapters/registry.py,sha256=yM6nf9nGTJ1vaK2Uixp-VacseK7PmxZkCdKedmWI8MA,
|
|
|
27
27
|
realign/claude_hooks/__init__.py,sha256=MT9c8TWjLO23xDCM-uBBMy_mOThNd7O-AgN_Khn30qs,594
|
|
28
28
|
realign/claude_hooks/permission_request_hook.py,sha256=jMN7UtL6bMqHObUCP5A5ysvFrooDEcd9KxtmF2-3nCw,6448
|
|
29
29
|
realign/claude_hooks/permission_request_hook_installer.py,sha256=_8Wr_L5MES7iGukJzcaj4bqR0BH8kFL44U_X4iKtw2Y,7791
|
|
30
|
-
realign/claude_hooks/stop_hook.py,sha256=
|
|
30
|
+
realign/claude_hooks/stop_hook.py,sha256=GWADlzaTGzV8_BUKLLGhHmwJDIXSLQGVUUBuP_rdJ0o,13431
|
|
31
31
|
realign/claude_hooks/stop_hook_installer.py,sha256=uyqKOqpix7CQP64ERBvvh7viSPp_wx_JVGNAX18rKh0,7228
|
|
32
32
|
realign/claude_hooks/terminal_state.py,sha256=i8B6b_2_9ttPEemp7SrGdFRJSa-vm5lc7YSTRTvAWNg,5397
|
|
33
|
-
realign/claude_hooks/user_prompt_submit_hook.py,sha256=
|
|
33
|
+
realign/claude_hooks/user_prompt_submit_hook.py,sha256=kMrmhAVtfV41oTX7JZcq2HPXjgQQ5gX26iOJoHJkfqA,10474
|
|
34
34
|
realign/claude_hooks/user_prompt_submit_hook_installer.py,sha256=2xLF8yZcE7Iwib9gU-xCkA1NWxNH9Nc5CFKPYK7rtXw,5371
|
|
35
35
|
realign/commands/__init__.py,sha256=sx_ck55oxaoiF4N3LugG0ZXwonUDxeEZ5uHbBKCC7K8,89
|
|
36
36
|
realign/commands/add.py,sha256=njZgg3paUmOw-sb-sWkXr_eUaf5bD-hBEiRectaphPs,24332
|
|
37
|
-
realign/commands/auth.py,sha256=
|
|
37
|
+
realign/commands/auth.py,sha256=QrPukpP-ogYEDSwztV0NOYI-HDgn5fPxlCQ1-e2n7gU,11082
|
|
38
38
|
realign/commands/config.py,sha256=nYnu_h2pk7GODcrzrV04K51D-s7v06FlRXHJ0HJ-gvU,6732
|
|
39
39
|
realign/commands/context.py,sha256=pM2KfZHVkB-ou4nBhFvKSwnYliLBzwN3zerLyBAbhfE,7095
|
|
40
40
|
realign/commands/export_shares.py,sha256=g2SxlPEb7FPMarjTZcoZntviZo5JdqEe-M28vggc2-s,136601
|
|
@@ -46,10 +46,15 @@ realign/commands/upgrade.py,sha256=L3PLOUIN5qAQTbkfoVtSsIbbzEezA_xjjk9F1GMVfjw,1
|
|
|
46
46
|
realign/commands/watcher.py,sha256=rB3x2diC7scqghl7wLAeBNgMj4NLXUKbr0ou1_VVUIU,136292
|
|
47
47
|
realign/commands/worker.py,sha256=jTu7Pj60nTnn7SsH3oNCNnO6zl4TIFCJVNSC1OoQ_0o,23363
|
|
48
48
|
realign/dashboard/__init__.py,sha256=QZkHTsGityH8UkF8rmvA3xW7dMXNe0swEWr443qfgCM,128
|
|
49
|
-
realign/dashboard/app.py,sha256=
|
|
50
|
-
realign/dashboard/
|
|
49
|
+
realign/dashboard/app.py,sha256=yy2rjMQuAfpukFgrA6udAnAbCXQoAHr9WeeuZ9bCSa4,16064
|
|
50
|
+
realign/dashboard/layout.py,sha256=sZxmFj6QTbkois9MHTvBEMMcnaRVehCDqugdbiFx10k,9072
|
|
51
|
+
realign/dashboard/terminal_backend.py,sha256=MlDfwtqhftyQK6jDNizQGFjAWIo5Bx2TDpSnP3MCZVM,3375
|
|
52
|
+
realign/dashboard/tmux_manager.py,sha256=K8sjzSBtISLuWF7s4g4YieJ4oiE5wIgSztFvmhJZd0M,26849
|
|
53
|
+
realign/dashboard/backends/__init__.py,sha256=POROX7YKtukYZcLB1pi_kO0sSEpuO3y-hwmF3WIN1Kk,163
|
|
54
|
+
realign/dashboard/backends/iterm2.py,sha256=XYYJT5lrrp4pW_MyEqPZYkRI0qyKUwJlezwMidgnsHc,21390
|
|
55
|
+
realign/dashboard/backends/kitty.py,sha256=5jdkR1f2PwB8a4SnS3EG6uOQ2XU-PB7-cpKBfIJq3hU,12066
|
|
51
56
|
realign/dashboard/screens/__init__.py,sha256=US6sAmQs5VVkH2tFkH_z0WDT4H8cVhLL-JckfSR1yQY,446
|
|
52
|
-
realign/dashboard/screens/create_agent.py,sha256=
|
|
57
|
+
realign/dashboard/screens/create_agent.py,sha256=06uiQYvz-Xvn4Xm689o3tdhzb2HQ0gdzAA1WHVEwziM,11706
|
|
53
58
|
realign/dashboard/screens/create_event.py,sha256=oiQY1zKpUYnQU-5fQLeuZH9BV5NClE5B5XZIVBYG5A8,5506
|
|
54
59
|
realign/dashboard/screens/event_detail.py,sha256=OLaL3-FgAohDdzVlfuUw5yh2SR49IHIpCtiqXJhBTc0,20992
|
|
55
60
|
realign/dashboard/screens/help_screen.py,sha256=Icrcvbgyz49R2tBiu8vBZ4CLm6iYclv_-FTa2pCFRRQ,3398
|
|
@@ -63,7 +68,7 @@ realign/dashboard/widgets/header.py,sha256=0HHCFXX7F3C6HII-WDwOJwWkJrajmKPWmdoMW
|
|
|
63
68
|
realign/dashboard/widgets/openable_table.py,sha256=GeJPDEYp0kRHShqvmPMzAePpYXRZHUNqcWNnxqsqxjA,1963
|
|
64
69
|
realign/dashboard/widgets/search_panel.py,sha256=ZNJDfwDSxUFnCeltYQYsQsPJ6t4HDeNWpENoTOoBdVM,8951
|
|
65
70
|
realign/dashboard/widgets/sessions_table.py,sha256=GyaWzvt-elKx1iE2YR94CBNPyjqM8B7g0j7zsukiXi0,36517
|
|
66
|
-
realign/dashboard/widgets/terminal_panel.py,sha256=
|
|
71
|
+
realign/dashboard/widgets/terminal_panel.py,sha256=BZgh7lo9rhuJdvSGBhUPlWjS3KjcXDJP7SSnNngmFaY,45805
|
|
67
72
|
realign/dashboard/widgets/watcher_panel.py,sha256=O_mdDacgc87xA-5KEfta53Ik_Xsk_B2OfwenMOTtGw8,19722
|
|
68
73
|
realign/dashboard/widgets/worker_panel.py,sha256=F_jKWABuCNmjQgeeuCr4KnFRKdY4CLTNcEXMYwsNaSk,18691
|
|
69
74
|
realign/db/__init__.py,sha256=65LsNdsq_rkwNC1eg1OAr3HC0ORXtelOh0I8MhNGr-g,3288
|
|
@@ -72,11 +77,11 @@ realign/db/locks.py,sha256=yzCiPJZ4eOQX-Q4mXB6s76U2U7lXAzIBBy1t59w-AVU,1698
|
|
|
72
77
|
realign/db/migrate_agents.py,sha256=cDeVUzKW950dJ0lV74QObHuONqKwErSrXI5akU2vBmQ,9633
|
|
73
78
|
realign/db/migration.py,sha256=af1QFEfIh_qX0pFyXzm5gWFVbQn0sKOUNLSJHlr__FU,13405
|
|
74
79
|
realign/db/schema.py,sha256=YHj5PGZWbCl0VG0epnMF_Ofg3jRiLHq6SLHCi1q34eQ,30181
|
|
75
|
-
realign/db/sqlite_db.py,sha256=
|
|
80
|
+
realign/db/sqlite_db.py,sha256=AJb05gWN8L7K79-f9TJ0UGqGnoHUED1z-yjufGQTyQE,104929
|
|
76
81
|
realign/events/__init__.py,sha256=IM-NxF4Zk2hYFD07k4WrfNRuuiC9ihGjf4GBpJhjd2E,35
|
|
77
82
|
realign/events/debouncer.py,sha256=U3Q7dYpnMsAgWsW_E_IbSC4lrdEoi6H_SFLGLOAazs4,3062
|
|
78
83
|
realign/events/event_summarizer.py,sha256=ZLiwOXWN8eawep3cQs3Wh9QLSypvU1SRbe8GTJXJQaY,8272
|
|
79
|
-
realign/events/session_summarizer.py,sha256=
|
|
84
|
+
realign/events/session_summarizer.py,sha256=W5J1zZs1ZrH0MvWgvPObwB1KFdxXEZMLctAmFQC_rok,10994
|
|
80
85
|
realign/models/event.py,sha256=ypz74D4l6U2U0RhgL8fzEhiq7iQjhHybmAdLUNDY7P4,5521
|
|
81
86
|
realign/prompts/__init__.py,sha256=PpYR7f-T96fd-QyNYJDRS1U6h9O0rIt_SMsREy9i3aA,443
|
|
82
87
|
realign/prompts/presets.py,sha256=h9oEy0XP4JQ4DCnp8HN_FfF0LmI-yOV6xWJLknIghJ8,7256
|
|
@@ -91,8 +96,8 @@ realign/triggers/next_turn_trigger.py,sha256=BpP0PWn4mU1MZd6mv89jWcjs8Jtv0zEWapW
|
|
|
91
96
|
realign/triggers/registry.py,sha256=cb-AVLbYB2pqwfWL3q1DQxLv4kOw7g7m-GshTdfFESc,3827
|
|
92
97
|
realign/triggers/turn_status.py,sha256=wAZEhXDAmDoX5F-ohWfSnZZ0eA6DAJ9svSPiSv_f6sg,6041
|
|
93
98
|
realign/triggers/turn_summary.py,sha256=f3hEUshgv9skJ9AbfWpoYs417lsv_HK2A_vpPjgryO4,4467
|
|
94
|
-
aline_ai-0.6.
|
|
95
|
-
aline_ai-0.6.
|
|
96
|
-
aline_ai-0.6.
|
|
97
|
-
aline_ai-0.6.
|
|
98
|
-
aline_ai-0.6.
|
|
99
|
+
aline_ai-0.6.2.dist-info/METADATA,sha256=KJbwjx2uZdXBhG2HyeVUs77RT72msrkJLRYBlCrldVc,1597
|
|
100
|
+
aline_ai-0.6.2.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
|
|
101
|
+
aline_ai-0.6.2.dist-info/entry_points.txt,sha256=TvYELpMoWsUTcQdMV8tBHxCbEf_LbK4sESqK3r8PM6Y,78
|
|
102
|
+
aline_ai-0.6.2.dist-info/top_level.txt,sha256=yIL3s2xv9nf1GwD5n71Aq_JEIV4AfzCIDNKBzewuRm4,8
|
|
103
|
+
aline_ai-0.6.2.dist-info/RECORD,,
|
realign/__init__.py
CHANGED
realign/auth.py
CHANGED
|
@@ -44,6 +44,7 @@ logger = setup_logger("realign.auth", "auth.log")
|
|
|
44
44
|
# Hardcoded Supabase/backend configuration
|
|
45
45
|
DEFAULT_AUTH_URL = "https://realign-server.vercel.app"
|
|
46
46
|
CLI_LOGIN_PATH = "/cli-login"
|
|
47
|
+
CLI_LOGOUT_PATH = "/cli-logout"
|
|
47
48
|
CLI_VALIDATE_PATH = "/api/auth/cli/validate"
|
|
48
49
|
CLI_REFRESH_PATH = "/api/auth/cli/refresh"
|
|
49
50
|
|
|
@@ -394,6 +395,26 @@ def open_login_page(callback_port: Optional[int] = None) -> str:
|
|
|
394
395
|
return login_url
|
|
395
396
|
|
|
396
397
|
|
|
398
|
+
def open_logout_page() -> str:
|
|
399
|
+
"""
|
|
400
|
+
Open the web logout page in browser to sign out from web session.
|
|
401
|
+
|
|
402
|
+
Returns:
|
|
403
|
+
The URL that was opened
|
|
404
|
+
"""
|
|
405
|
+
config = ReAlignConfig.load()
|
|
406
|
+
backend_url = config.share_backend_url or DEFAULT_AUTH_URL
|
|
407
|
+
logout_url = f"{backend_url}{CLI_LOGOUT_PATH}"
|
|
408
|
+
|
|
409
|
+
try:
|
|
410
|
+
webbrowser.open(logout_url)
|
|
411
|
+
logger.info(f"Opened logout page: {logout_url}")
|
|
412
|
+
except Exception as e:
|
|
413
|
+
logger.warning(f"Failed to open browser: {e}")
|
|
414
|
+
|
|
415
|
+
return logout_url
|
|
416
|
+
|
|
417
|
+
|
|
397
418
|
# Local callback server for automatic token receipt
|
|
398
419
|
_received_token: Optional[str] = None
|
|
399
420
|
_server_error: Optional[str] = None
|
|
@@ -98,6 +98,9 @@ def main():
|
|
|
98
98
|
signal_file = signal_dir / f"{session_id}_{timestamp_ms}.signal"
|
|
99
99
|
tmp_file = signal_dir / f"{session_id}_{timestamp_ms}.signal.tmp"
|
|
100
100
|
|
|
101
|
+
# Check for no-track mode
|
|
102
|
+
no_track = os.environ.get("ALINE_NO_TRACK", "") == "1"
|
|
103
|
+
|
|
101
104
|
signal_data = {
|
|
102
105
|
"session_id": session_id,
|
|
103
106
|
"terminal_id": terminal_id,
|
|
@@ -107,6 +110,8 @@ def main():
|
|
|
107
110
|
"timestamp": time.time(),
|
|
108
111
|
"hook_event": "Stop",
|
|
109
112
|
}
|
|
113
|
+
if no_track:
|
|
114
|
+
signal_data["no_track"] = True
|
|
110
115
|
|
|
111
116
|
# Write atomically to avoid watcher reading a partial JSON file.
|
|
112
117
|
tmp_file.write_text(json.dumps(signal_data, indent=2))
|
|
@@ -193,6 +198,22 @@ def main():
|
|
|
193
198
|
],
|
|
194
199
|
check=False,
|
|
195
200
|
)
|
|
201
|
+
# Set no-track flag if applicable
|
|
202
|
+
if no_track:
|
|
203
|
+
subprocess.run(
|
|
204
|
+
[
|
|
205
|
+
"tmux",
|
|
206
|
+
"-L",
|
|
207
|
+
inner_socket,
|
|
208
|
+
"set-option",
|
|
209
|
+
"-w",
|
|
210
|
+
"-t",
|
|
211
|
+
window_id,
|
|
212
|
+
"@aline_no_track",
|
|
213
|
+
"1",
|
|
214
|
+
],
|
|
215
|
+
check=False,
|
|
216
|
+
)
|
|
196
217
|
if transcript_path:
|
|
197
218
|
subprocess.run(
|
|
198
219
|
[
|
|
@@ -285,6 +306,20 @@ def main():
|
|
|
285
306
|
],
|
|
286
307
|
check=False,
|
|
287
308
|
)
|
|
309
|
+
# Set no-track flag if applicable
|
|
310
|
+
if no_track:
|
|
311
|
+
subprocess.run(
|
|
312
|
+
[
|
|
313
|
+
"tmux",
|
|
314
|
+
"set-option",
|
|
315
|
+
"-w",
|
|
316
|
+
"-t",
|
|
317
|
+
window_id,
|
|
318
|
+
"@aline_no_track",
|
|
319
|
+
"1",
|
|
320
|
+
],
|
|
321
|
+
check=False,
|
|
322
|
+
)
|
|
288
323
|
if transcript_path:
|
|
289
324
|
subprocess.run(
|
|
290
325
|
[
|
|
@@ -80,6 +80,9 @@ def main() -> None:
|
|
|
80
80
|
signal_file = signal_dir / f"{session_id}_{timestamp_ms}.signal"
|
|
81
81
|
tmp_file = signal_dir / f"{session_id}_{timestamp_ms}.signal.tmp"
|
|
82
82
|
|
|
83
|
+
# Check for no-track mode
|
|
84
|
+
no_track = os.environ.get("ALINE_NO_TRACK", "") == "1"
|
|
85
|
+
|
|
83
86
|
signal_data = {
|
|
84
87
|
"session_id": session_id,
|
|
85
88
|
"terminal_id": terminal_id,
|
|
@@ -90,6 +93,8 @@ def main() -> None:
|
|
|
90
93
|
"timestamp": time.time(),
|
|
91
94
|
"hook_event": "UserPromptSubmit",
|
|
92
95
|
}
|
|
96
|
+
if no_track:
|
|
97
|
+
signal_data["no_track"] = True
|
|
93
98
|
|
|
94
99
|
tmp_file.write_text(json.dumps(signal_data, indent=2))
|
|
95
100
|
tmp_file.replace(signal_file)
|
realign/cli.py
CHANGED
|
@@ -61,13 +61,33 @@ def main(
|
|
|
61
61
|
raise typer.Exit(0)
|
|
62
62
|
|
|
63
63
|
# Launch the dashboard when no subcommand is provided
|
|
64
|
-
|
|
64
|
+
import os
|
|
65
65
|
|
|
66
|
-
|
|
66
|
+
terminal_mode = os.environ.get("ALINE_TERMINAL_MODE", "").strip().lower()
|
|
67
|
+
use_native_terminal = terminal_mode in {"native", "iterm2", "iterm", "kitty"}
|
|
68
|
+
|
|
69
|
+
right_pane_session_id = None
|
|
70
|
+
|
|
71
|
+
if not use_native_terminal:
|
|
72
|
+
# Only bootstrap tmux for tmux mode (default)
|
|
73
|
+
from .dashboard.tmux_manager import bootstrap_dashboard_into_tmux
|
|
74
|
+
|
|
75
|
+
bootstrap_dashboard_into_tmux()
|
|
76
|
+
elif terminal_mode in {"iterm2", "iterm"}:
|
|
77
|
+
# Set up split pane layout for iTerm2
|
|
78
|
+
try:
|
|
79
|
+
from .dashboard.backends.iterm2 import setup_split_pane_layout_sync
|
|
80
|
+
|
|
81
|
+
right_pane_session_id = setup_split_pane_layout_sync()
|
|
82
|
+
if right_pane_session_id:
|
|
83
|
+
# Store in environment for dashboard to pick up
|
|
84
|
+
os.environ["ALINE_ITERM2_RIGHT_PANE"] = right_pane_session_id
|
|
85
|
+
except Exception as e:
|
|
86
|
+
console.print(f"[yellow]Warning: Could not set up split pane: {e}[/yellow]")
|
|
67
87
|
|
|
68
88
|
from .dashboard.app import AlineDashboard
|
|
69
89
|
|
|
70
|
-
dashboard = AlineDashboard(dev_mode=dev)
|
|
90
|
+
dashboard = AlineDashboard(dev_mode=dev, use_native_terminal=use_native_terminal)
|
|
71
91
|
dashboard.run()
|
|
72
92
|
|
|
73
93
|
|
|
@@ -111,8 +131,8 @@ def doctor_cli(
|
|
|
111
131
|
- Clears Python bytecode cache (.pyc files)
|
|
112
132
|
- Updates Claude Code hooks (Stop, UserPromptSubmit, PermissionRequest)
|
|
113
133
|
- Updates skills to latest version
|
|
114
|
-
-
|
|
115
|
-
-
|
|
134
|
+
- Ensures watcher daemon is running (restarts if running, starts if not)
|
|
135
|
+
- Ensures worker daemon is running (restarts if running, starts if not)
|
|
116
136
|
|
|
117
137
|
Run this after pulling new code to ensure everything uses the latest version.
|
|
118
138
|
"""
|
|
@@ -265,20 +285,22 @@ def doctor_cli(
|
|
|
265
285
|
|
|
266
286
|
if watcher_was_running:
|
|
267
287
|
console.print(" [dim]Starting watcher daemon...[/dim]")
|
|
268
|
-
try:
|
|
269
|
-
subprocess.Popen(
|
|
270
|
-
["python", "-m", "src.realign.watcher_daemon"],
|
|
271
|
-
stdout=subprocess.DEVNULL,
|
|
272
|
-
stderr=subprocess.DEVNULL,
|
|
273
|
-
start_new_session=True,
|
|
274
|
-
cwd=str(project_root),
|
|
275
|
-
)
|
|
276
|
-
time.sleep(2)
|
|
277
|
-
console.print(" [green]✓[/green] Watcher daemon restarted")
|
|
278
|
-
except Exception as e:
|
|
279
|
-
console.print(f" [red]✗[/red] Failed to restart watcher: {e}")
|
|
280
288
|
else:
|
|
281
|
-
console.print(" [dim]Watcher daemon was not running[/dim]")
|
|
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}")
|
|
282
304
|
|
|
283
305
|
# 5. Restart worker daemon
|
|
284
306
|
console.print("\n[bold]5. Checking worker daemon...[/bold]")
|
|
@@ -309,20 +331,22 @@ def doctor_cli(
|
|
|
309
331
|
|
|
310
332
|
if worker_was_running:
|
|
311
333
|
console.print(" [dim]Starting worker daemon...[/dim]")
|
|
312
|
-
try:
|
|
313
|
-
subprocess.Popen(
|
|
314
|
-
["python", "-m", "src.realign.worker_daemon"],
|
|
315
|
-
stdout=subprocess.DEVNULL,
|
|
316
|
-
stderr=subprocess.DEVNULL,
|
|
317
|
-
start_new_session=True,
|
|
318
|
-
cwd=str(project_root),
|
|
319
|
-
)
|
|
320
|
-
time.sleep(2)
|
|
321
|
-
console.print(" [green]✓[/green] Worker daemon restarted")
|
|
322
|
-
except Exception as e:
|
|
323
|
-
console.print(f" [red]✗[/red] Failed to restart worker: {e}")
|
|
324
334
|
else:
|
|
325
|
-
console.print(" [dim]Worker daemon was not running[/dim]")
|
|
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}")
|
|
326
350
|
|
|
327
351
|
console.print("\n[green]Done![/green] Aline is ready with the latest code.")
|
|
328
352
|
raise typer.Exit(code=0)
|
|
@@ -1100,7 +1124,6 @@ def dashboard(
|
|
|
1100
1124
|
if debug:
|
|
1101
1125
|
os.environ["REALIGN_LOG_LEVEL"] = "DEBUG"
|
|
1102
1126
|
|
|
1103
|
-
from .dashboard.tmux_manager import bootstrap_dashboard_into_tmux
|
|
1104
1127
|
from .logging_config import setup_logger
|
|
1105
1128
|
|
|
1106
1129
|
# Initialize logger before dashboard
|
|
@@ -1108,13 +1131,32 @@ def dashboard(
|
|
|
1108
1131
|
logger.info(f"Dashboard command invoked (dev={dev}, debug={debug})")
|
|
1109
1132
|
|
|
1110
1133
|
try:
|
|
1111
|
-
|
|
1134
|
+
# Check terminal mode
|
|
1135
|
+
terminal_mode = os.environ.get("ALINE_TERMINAL_MODE", "").strip().lower()
|
|
1136
|
+
use_native_terminal = terminal_mode in {"native", "iterm2", "iterm", "kitty"}
|
|
1137
|
+
|
|
1138
|
+
if not use_native_terminal:
|
|
1139
|
+
# Only bootstrap tmux for tmux mode (default)
|
|
1140
|
+
from .dashboard.tmux_manager import bootstrap_dashboard_into_tmux
|
|
1141
|
+
|
|
1142
|
+
bootstrap_dashboard_into_tmux()
|
|
1143
|
+
elif terminal_mode in {"iterm2", "iterm"}:
|
|
1144
|
+
# Set up split pane layout for iTerm2
|
|
1145
|
+
try:
|
|
1146
|
+
from .dashboard.backends.iterm2 import setup_split_pane_layout_sync
|
|
1147
|
+
|
|
1148
|
+
right_pane_session_id = setup_split_pane_layout_sync()
|
|
1149
|
+
if right_pane_session_id:
|
|
1150
|
+
os.environ["ALINE_ITERM2_RIGHT_PANE"] = right_pane_session_id
|
|
1151
|
+
logger.info(f"Set up split pane with right pane: {right_pane_session_id}")
|
|
1152
|
+
except Exception as e:
|
|
1153
|
+
logger.warning(f"Could not set up split pane: {e}")
|
|
1112
1154
|
|
|
1113
1155
|
from .dashboard.app import AlineDashboard
|
|
1114
1156
|
|
|
1115
1157
|
# Use dev flag from this command or inherit from parent context
|
|
1116
1158
|
dev_mode = dev or (ctx.obj.get("dev", False) if ctx.obj else False)
|
|
1117
|
-
dash = AlineDashboard(dev_mode=dev_mode)
|
|
1159
|
+
dash = AlineDashboard(dev_mode=dev_mode, use_native_terminal=use_native_terminal)
|
|
1118
1160
|
dash.run()
|
|
1119
1161
|
except Exception as e:
|
|
1120
1162
|
logger.error(f"Dashboard crashed: {e}\n{traceback.format_exc()}")
|
realign/commands/auth.py
CHANGED
|
@@ -25,6 +25,7 @@ from ..auth import (
|
|
|
25
25
|
save_credentials,
|
|
26
26
|
clear_credentials,
|
|
27
27
|
open_login_page,
|
|
28
|
+
open_logout_page,
|
|
28
29
|
validate_cli_token,
|
|
29
30
|
find_free_port,
|
|
30
31
|
start_callback_server,
|
|
@@ -207,6 +208,14 @@ def logout_command() -> int:
|
|
|
207
208
|
print("Error: Failed to clear credentials", file=sys.stderr)
|
|
208
209
|
return 1
|
|
209
210
|
|
|
211
|
+
# Open browser to sign out from web session
|
|
212
|
+
if console:
|
|
213
|
+
console.print("[dim]Signing out from web session...[/dim]")
|
|
214
|
+
else:
|
|
215
|
+
print("Signing out from web session...")
|
|
216
|
+
|
|
217
|
+
open_logout_page()
|
|
218
|
+
|
|
210
219
|
if console:
|
|
211
220
|
console.print(f"[green]Logged out successfully.[/green]")
|
|
212
221
|
console.print(f"Cleared credentials for: {email}")
|
realign/dashboard/app.py
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
"""Aline Dashboard - Main Application."""
|
|
2
2
|
|
|
3
|
+
import os
|
|
3
4
|
import subprocess
|
|
4
5
|
import sys
|
|
5
6
|
import time
|
|
@@ -22,6 +23,9 @@ from .widgets import (
|
|
|
22
23
|
TerminalPanel,
|
|
23
24
|
)
|
|
24
25
|
|
|
26
|
+
# Environment variable to control terminal mode
|
|
27
|
+
ENV_TERMINAL_MODE = "ALINE_TERMINAL_MODE"
|
|
28
|
+
|
|
25
29
|
# Set up dashboard logger - logs to ~/.aline/.logs/dashboard.log
|
|
26
30
|
logger = setup_logger("realign.dashboard", "dashboard.log")
|
|
27
31
|
|
|
@@ -57,6 +61,7 @@ class AlineDashboard(App):
|
|
|
57
61
|
|
|
58
62
|
CSS_PATH = "styles/dashboard.tcss"
|
|
59
63
|
TITLE = "Aline Dashboard"
|
|
64
|
+
ENABLE_COMMAND_PALETTE = False
|
|
60
65
|
|
|
61
66
|
BINDINGS = [
|
|
62
67
|
Binding("r", "refresh", "Refresh", show=False),
|
|
@@ -74,15 +79,31 @@ class AlineDashboard(App):
|
|
|
74
79
|
|
|
75
80
|
_quit_confirm_window_s: float = 1.2
|
|
76
81
|
|
|
77
|
-
def __init__(self, dev_mode: bool = False):
|
|
82
|
+
def __init__(self, dev_mode: bool = False, use_native_terminal: bool | None = None):
|
|
78
83
|
"""Initialize the dashboard.
|
|
79
84
|
|
|
80
85
|
Args:
|
|
81
86
|
dev_mode: If True, shows developer tabs (Watcher, Worker).
|
|
87
|
+
use_native_terminal: If True, use native terminal backend (iTerm2/Kitty).
|
|
88
|
+
If False, use tmux.
|
|
89
|
+
If None (default), auto-detect from ALINE_TERMINAL_MODE env var.
|
|
82
90
|
"""
|
|
83
91
|
super().__init__()
|
|
84
92
|
self.dev_mode = dev_mode
|
|
85
|
-
|
|
93
|
+
self.use_native_terminal = use_native_terminal
|
|
94
|
+
self._native_terminal_mode = self._detect_native_mode()
|
|
95
|
+
logger.info(
|
|
96
|
+
f"AlineDashboard initialized (dev_mode={dev_mode}, "
|
|
97
|
+
f"native_terminal={self._native_terminal_mode})"
|
|
98
|
+
)
|
|
99
|
+
|
|
100
|
+
def _detect_native_mode(self) -> bool:
|
|
101
|
+
"""Detect if native terminal mode should be used."""
|
|
102
|
+
if self.use_native_terminal is not None:
|
|
103
|
+
return self.use_native_terminal
|
|
104
|
+
|
|
105
|
+
mode = os.environ.get(ENV_TERMINAL_MODE, "").strip().lower()
|
|
106
|
+
return mode in {"native", "iterm2", "iterm", "kitty"}
|
|
86
107
|
|
|
87
108
|
def compose(self) -> ComposeResult:
|
|
88
109
|
"""Compose the dashboard layout."""
|
|
@@ -92,7 +113,7 @@ class AlineDashboard(App):
|
|
|
92
113
|
tab_ids = self._tab_ids()
|
|
93
114
|
with TabbedContent(initial=tab_ids[0] if tab_ids else "terminal"):
|
|
94
115
|
with TabPane("Agents", id="terminal"):
|
|
95
|
-
yield TerminalPanel()
|
|
116
|
+
yield TerminalPanel(use_native_terminal=self.use_native_terminal)
|
|
96
117
|
if self.dev_mode:
|
|
97
118
|
with TabPane("Watcher", id="watcher"):
|
|
98
119
|
yield WatcherPanel()
|
|
@@ -125,11 +146,47 @@ class AlineDashboard(App):
|
|
|
125
146
|
# Check for system theme changes every 2 seconds
|
|
126
147
|
self.set_interval(2, self._sync_theme)
|
|
127
148
|
self._quit_confirm_deadline: float | None = None
|
|
149
|
+
|
|
150
|
+
# Set up side-by-side layout for native terminal mode
|
|
151
|
+
if self._native_terminal_mode:
|
|
152
|
+
self._setup_native_terminal_layout()
|
|
153
|
+
|
|
128
154
|
logger.info("on_mount() completed successfully")
|
|
129
155
|
except Exception as e:
|
|
130
156
|
logger.error(f"on_mount() failed: {e}\n{traceback.format_exc()}")
|
|
131
157
|
raise
|
|
132
158
|
|
|
159
|
+
def _setup_native_terminal_layout(self) -> None:
|
|
160
|
+
"""Set up side-by-side layout for Dashboard and native terminal."""
|
|
161
|
+
# Skip if using iTerm2 split pane mode (already set up by CLI)
|
|
162
|
+
if os.environ.get("ALINE_ITERM2_RIGHT_PANE"):
|
|
163
|
+
logger.info("Using iTerm2 split pane mode, skipping window layout")
|
|
164
|
+
return
|
|
165
|
+
|
|
166
|
+
try:
|
|
167
|
+
from .layout import setup_side_by_side_layout
|
|
168
|
+
|
|
169
|
+
# Determine the target terminal app
|
|
170
|
+
mode = os.environ.get(ENV_TERMINAL_MODE, "").strip().lower()
|
|
171
|
+
if mode == "kitty":
|
|
172
|
+
terminal_app = "Kitty"
|
|
173
|
+
else:
|
|
174
|
+
terminal_app = "iTerm2"
|
|
175
|
+
|
|
176
|
+
# Set up side-by-side layout (Dashboard on left, terminal on right)
|
|
177
|
+
success = setup_side_by_side_layout(
|
|
178
|
+
terminal_app=terminal_app,
|
|
179
|
+
dashboard_on_left=True,
|
|
180
|
+
dashboard_width_percent=40, # Dashboard takes 40%, terminal takes 60%
|
|
181
|
+
)
|
|
182
|
+
|
|
183
|
+
if success:
|
|
184
|
+
logger.info(f"Set up side-by-side layout with {terminal_app}")
|
|
185
|
+
else:
|
|
186
|
+
logger.warning("Failed to set up side-by-side layout")
|
|
187
|
+
except Exception as e:
|
|
188
|
+
logger.warning(f"Could not set up native terminal layout: {e}")
|
|
189
|
+
|
|
133
190
|
def _sync_theme(self) -> None:
|
|
134
191
|
"""Sync app theme with system theme."""
|
|
135
192
|
target_theme = "textual-dark" if _detect_system_dark_mode() else "textual-light"
|
|
@@ -357,11 +414,17 @@ class AlineDashboard(App):
|
|
|
357
414
|
self.notify(f"Loaded {what} into {context_id}", title="Load Context", timeout=3)
|
|
358
415
|
|
|
359
416
|
|
|
360
|
-
def run_dashboard() -> None:
|
|
361
|
-
"""Run the Aline Dashboard.
|
|
417
|
+
def run_dashboard(use_native_terminal: bool | None = None) -> None:
|
|
418
|
+
"""Run the Aline Dashboard.
|
|
419
|
+
|
|
420
|
+
Args:
|
|
421
|
+
use_native_terminal: If True, use native terminal backend (iTerm2/Kitty).
|
|
422
|
+
If False, use tmux.
|
|
423
|
+
If None (default), auto-detect from ALINE_TERMINAL_MODE env var.
|
|
424
|
+
"""
|
|
362
425
|
logger.info("Starting Aline Dashboard")
|
|
363
426
|
try:
|
|
364
|
-
app = AlineDashboard()
|
|
427
|
+
app = AlineDashboard(use_native_terminal=use_native_terminal)
|
|
365
428
|
app.run()
|
|
366
429
|
logger.info("Aline Dashboard exited normally")
|
|
367
430
|
except Exception as e:
|