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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: aline-ai
3
- Version: 0.6.0
3
+ Version: 0.6.2
4
4
  Summary: Shared AI memory; everyone knows everything in teams
5
5
  Author: Sharemind
6
6
  License: MIT
@@ -1,8 +1,8 @@
1
- aline_ai-0.6.0.dist-info/licenses/LICENSE,sha256=H8wTqV5IF1oHw_HbBtS1PSDU8G_q81yblEIL_JfV8Vo,1077
2
- realign/__init__.py,sha256=a8wH_-YI_n2n6He2CL8jdy4zSUj9pxtT6cz9iNZgp24,1623
3
- realign/auth.py,sha256=63fdy-KsNoLZ9A6X0Mz_v-0tQOXN_1XncXBGBlEoXqE,16030
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=2BhDitD5IYUPJRGtfOXv_m0r7ZsiPU9iGtkp2pfA59M,41637
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=NNn_xlm50Ybb60--DrF9dyvzGgJ4ENQeAUbZsH7w4to,106555
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=-GOItHE0vzExB8LZK6KeHx4tt_mIqtCoUljOtEg2x8A,10105
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=2nzF2aF1p5teMJ0eV0ALEHD1K-yVj5sSh7UE8xL54ZE,12025
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=WD-UavhBTueN2TPfnZrnPC7DFYGEeptjUEF21EJn7Qo,10312
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=B1FQjaLUKYc4DsGqrpXZ3JPmsttUXhrtxxrKuOUmP4Q,10844
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=jyW6mqmItTy253CPSqInxctkWzkrGEikdy-ikuShQ14,13299
50
- realign/dashboard/tmux_manager.py,sha256=Vt_30WNtDg7c_9SEh8xdDtBLJ8kNq6bGSPh5r3VXpg0,26276
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=lpcT1zLq_p02codtHTE8KdbEzCEaNLnk1lqU3QLcXCg,10057
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=uoi3LjgYWyFE6Yr208KC5iKg0QxLcXpN6hCERlI6pBg,29069
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=0tB_chSBr44_VTFLe5_hmQk5xUuNym9JZLFSAAE9DNk,104225
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=IWYcDHGbsPtZEeDcQMWy4V-IKi5QBqpA5uuOIGy4Sls,10386
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.0.dist-info/METADATA,sha256=oukhmryjZ2KFfmgLAIPBjpr8S4_8pxboUCOAgIWLx4Y,1597
95
- aline_ai-0.6.0.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
96
- aline_ai-0.6.0.dist-info/entry_points.txt,sha256=TvYELpMoWsUTcQdMV8tBHxCbEf_LbK4sESqK3r8PM6Y,78
97
- aline_ai-0.6.0.dist-info/top_level.txt,sha256=yIL3s2xv9nf1GwD5n71Aq_JEIV4AfzCIDNKBzewuRm4,8
98
- aline_ai-0.6.0.dist-info/RECORD,,
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
@@ -3,7 +3,7 @@
3
3
  import hashlib
4
4
  from pathlib import Path
5
5
 
6
- __version__ = "0.6.0"
6
+ __version__ = "0.6.2"
7
7
 
8
8
 
9
9
  def get_realign_dir(project_root: Path) -> Path:
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
- from .dashboard.tmux_manager import bootstrap_dashboard_into_tmux
64
+ import os
65
65
 
66
- bootstrap_dashboard_into_tmux()
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
- - Restarts the watcher daemon (if running)
115
- - Restarts the worker daemon (if running)
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
- bootstrap_dashboard_into_tmux()
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
- logger.info(f"AlineDashboard initialized (dev_mode={dev_mode})")
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:
@@ -0,0 +1,6 @@
1
+ """Terminal backends for native terminal support."""
2
+
3
+ from .iterm2 import ITermBackend
4
+ from .kitty import KittyBackend
5
+
6
+ __all__ = ["ITermBackend", "KittyBackend"]