aline-ai 0.5.5__py3-none-any.whl → 0.5.7__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.5.5.dist-info → aline_ai-0.5.7.dist-info}/METADATA +1 -1
- {aline_ai-0.5.5.dist-info → aline_ai-0.5.7.dist-info}/RECORD +18 -16
- realign/__init__.py +1 -1
- realign/adapters/claude.py +13 -7
- realign/cli.py +16 -4
- realign/commands/init.py +31 -5
- realign/dashboard/app.py +32 -22
- realign/dashboard/screens/__init__.py +10 -1
- realign/dashboard/screens/create_agent.py +244 -0
- realign/dashboard/screens/help_screen.py +114 -0
- realign/dashboard/widgets/events_table.py +311 -69
- realign/dashboard/widgets/header.py +1 -1
- realign/dashboard/widgets/sessions_table.py +380 -70
- realign/dashboard/widgets/terminal_panel.py +132 -196
- {aline_ai-0.5.5.dist-info → aline_ai-0.5.7.dist-info}/WHEEL +0 -0
- {aline_ai-0.5.5.dist-info → aline_ai-0.5.7.dist-info}/entry_points.txt +0 -0
- {aline_ai-0.5.5.dist-info → aline_ai-0.5.7.dist-info}/licenses/LICENSE +0 -0
- {aline_ai-0.5.5.dist-info → aline_ai-0.5.7.dist-info}/top_level.txt +0 -0
|
@@ -11,7 +11,6 @@ import asyncio
|
|
|
11
11
|
import os
|
|
12
12
|
import re
|
|
13
13
|
import shlex
|
|
14
|
-
import subprocess
|
|
15
14
|
from pathlib import Path
|
|
16
15
|
from typing import Callable
|
|
17
16
|
|
|
@@ -295,30 +294,15 @@ class TerminalPanel(Container, can_focus=True):
|
|
|
295
294
|
controls_enabled = self.supported()
|
|
296
295
|
with Horizontal(classes="summary"):
|
|
297
296
|
yield Button(
|
|
298
|
-
"+
|
|
299
|
-
id="new-
|
|
297
|
+
"+ Create",
|
|
298
|
+
id="new-agent",
|
|
300
299
|
variant="primary",
|
|
301
300
|
disabled=not controls_enabled,
|
|
302
301
|
)
|
|
303
|
-
yield Button(
|
|
304
|
-
"+ Codex",
|
|
305
|
-
id="new-codex",
|
|
306
|
-
variant="primary",
|
|
307
|
-
disabled=not controls_enabled,
|
|
308
|
-
)
|
|
309
|
-
yield Button(
|
|
310
|
-
"+ Opencode",
|
|
311
|
-
id="new-opencode",
|
|
312
|
-
variant="primary",
|
|
313
|
-
disabled=not controls_enabled,
|
|
314
|
-
)
|
|
315
|
-
yield Button("+ zsh", id="new-zsh", variant="primary", disabled=not controls_enabled)
|
|
316
|
-
yield Button("↻", id="refresh")
|
|
317
|
-
yield Static("", id="status", classes="status")
|
|
318
302
|
with Vertical(id="terminals", classes="list"):
|
|
319
303
|
if controls_enabled:
|
|
320
304
|
yield Static(
|
|
321
|
-
"No terminals yet. Click '
|
|
305
|
+
"No terminals yet. Click 'Create' to open a new agent terminal."
|
|
322
306
|
)
|
|
323
307
|
else:
|
|
324
308
|
yield Static(self._support_message())
|
|
@@ -373,21 +357,15 @@ class TerminalPanel(Container, can_focus=True):
|
|
|
373
357
|
async with self._refresh_lock:
|
|
374
358
|
try:
|
|
375
359
|
supported = self.supported()
|
|
376
|
-
except Exception
|
|
377
|
-
self._set_status(f"Terminal support check failed: {e}")
|
|
360
|
+
except Exception:
|
|
378
361
|
return
|
|
379
362
|
|
|
380
363
|
if not supported:
|
|
381
|
-
try:
|
|
382
|
-
self._set_status(self._support_message())
|
|
383
|
-
except Exception:
|
|
384
|
-
self._set_status("Terminal not supported")
|
|
385
364
|
return
|
|
386
365
|
|
|
387
366
|
try:
|
|
388
367
|
windows = tmux_manager.list_inner_windows()
|
|
389
|
-
except Exception
|
|
390
|
-
self._set_status(f"Failed to query tmux windows: {e}")
|
|
368
|
+
except Exception:
|
|
391
369
|
return
|
|
392
370
|
active_window_id = next((w.window_id for w in windows if w.active), None)
|
|
393
371
|
if self._expanded_window_id and self._expanded_window_id != active_window_id:
|
|
@@ -419,10 +397,8 @@ class TerminalPanel(Container, can_focus=True):
|
|
|
419
397
|
|
|
420
398
|
try:
|
|
421
399
|
await self._render_terminals(windows, titles, context_info_by_context_id)
|
|
422
|
-
except Exception
|
|
423
|
-
self._set_status(f"Failed to render terminal list: {e}")
|
|
400
|
+
except Exception:
|
|
424
401
|
return
|
|
425
|
-
self._set_status(f"{len(windows)} terminals")
|
|
426
402
|
|
|
427
403
|
def _fetch_claude_session_titles(self, session_ids: list[str]) -> dict[str, str]:
|
|
428
404
|
if not session_ids:
|
|
@@ -487,12 +463,6 @@ class TerminalPanel(Container, can_focus=True):
|
|
|
487
463
|
except Exception:
|
|
488
464
|
return ([], 0, 0)
|
|
489
465
|
|
|
490
|
-
def _set_status(self, text: str) -> None:
|
|
491
|
-
try:
|
|
492
|
-
self.query_one("#status", Static).update(text)
|
|
493
|
-
except Exception:
|
|
494
|
-
pass
|
|
495
|
-
|
|
496
466
|
async def _render_terminals(
|
|
497
467
|
self,
|
|
498
468
|
windows: list[tmux_manager.InnerWindow],
|
|
@@ -504,7 +474,7 @@ class TerminalPanel(Container, can_focus=True):
|
|
|
504
474
|
|
|
505
475
|
if not windows:
|
|
506
476
|
await container.mount(
|
|
507
|
-
Static("No terminals yet. Click '
|
|
477
|
+
Static("No terminals yet. Click 'Create' to open a new agent terminal.")
|
|
508
478
|
)
|
|
509
479
|
return
|
|
510
480
|
|
|
@@ -539,9 +509,7 @@ class TerminalPanel(Container, can_focus=True):
|
|
|
539
509
|
if w.active and can_toggle_ctx:
|
|
540
510
|
await row.mount(
|
|
541
511
|
Button(
|
|
542
|
-
|
|
543
|
-
# initial layout / tab switching).
|
|
544
|
-
"⮟" if expanded else "⮞",
|
|
512
|
+
"▼" if expanded else "▶",
|
|
545
513
|
id=f"toggle-{safe}",
|
|
546
514
|
name=w.window_id,
|
|
547
515
|
variant="default",
|
|
@@ -609,9 +577,8 @@ class TerminalPanel(Container, can_focus=True):
|
|
|
609
577
|
details.append(header)
|
|
610
578
|
details.append("\n")
|
|
611
579
|
|
|
612
|
-
#
|
|
613
|
-
|
|
614
|
-
detail_line = f"[{window_name}]" if window_name else "claude"
|
|
580
|
+
# Build detail line with Claude label
|
|
581
|
+
detail_line = "[Claude]"
|
|
615
582
|
if w.session_id:
|
|
616
583
|
detail_line = f"{detail_line} #{self._short_id(w.session_id)}"
|
|
617
584
|
if w.active:
|
|
@@ -641,180 +608,149 @@ class TerminalPanel(Container, can_focus=True):
|
|
|
641
608
|
return f"w-{safe}"
|
|
642
609
|
return safe
|
|
643
610
|
|
|
644
|
-
async def _select_workspace(self, prompt: str = "Select workspace") -> str | None:
|
|
645
|
-
"""Open macOS folder picker and return selected path, or None if cancelled."""
|
|
646
|
-
try:
|
|
647
|
-
default_path = os.getcwd()
|
|
648
|
-
except Exception:
|
|
649
|
-
# Can happen if the original working directory was deleted/moved.
|
|
650
|
-
default_path = str(Path.home())
|
|
651
|
-
# Use osascript to invoke macOS native folder picker
|
|
652
|
-
default_path_escaped = default_path.replace('"', '\\"')
|
|
653
|
-
prompt_escaped = prompt.replace('"', '\\"')
|
|
654
|
-
script = f"""
|
|
655
|
-
set defaultFolder to POSIX file "{default_path_escaped}" as alias
|
|
656
|
-
try
|
|
657
|
-
set selectedFolder to choose folder with prompt "{prompt_escaped}" default location defaultFolder
|
|
658
|
-
return POSIX path of selectedFolder
|
|
659
|
-
on error
|
|
660
|
-
return ""
|
|
661
|
-
end try
|
|
662
|
-
"""
|
|
663
|
-
try:
|
|
664
|
-
proc = await asyncio.get_event_loop().run_in_executor(
|
|
665
|
-
None,
|
|
666
|
-
lambda: subprocess.run(
|
|
667
|
-
["osascript", "-e", script],
|
|
668
|
-
capture_output=True,
|
|
669
|
-
text=True,
|
|
670
|
-
timeout=120,
|
|
671
|
-
),
|
|
672
|
-
)
|
|
673
|
-
result = (proc.stdout or "").strip()
|
|
674
|
-
if result and os.path.isdir(result):
|
|
675
|
-
return result
|
|
676
|
-
return None
|
|
677
|
-
except Exception:
|
|
678
|
-
return None
|
|
679
|
-
|
|
680
611
|
@staticmethod
|
|
681
612
|
def _command_in_directory(command: str, directory: str) -> str:
|
|
682
613
|
"""Wrap a command to run in a specific directory."""
|
|
683
614
|
return f"cd {shlex.quote(directory)} && {command}"
|
|
684
615
|
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
if button_id == "refresh":
|
|
689
|
-
await self.refresh_data()
|
|
690
|
-
return
|
|
691
|
-
|
|
692
|
-
if not self.supported():
|
|
693
|
-
self.app.notify(
|
|
694
|
-
self._support_message(),
|
|
695
|
-
title="Terminal",
|
|
696
|
-
severity="warning",
|
|
697
|
-
)
|
|
616
|
+
def _on_create_agent_result(self, result: tuple[str, str] | None) -> None:
|
|
617
|
+
"""Handle the result from CreateAgentScreen modal."""
|
|
618
|
+
if result is None:
|
|
698
619
|
return
|
|
699
620
|
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
context_id = tmux_manager.new_context_id("cc")
|
|
707
|
-
env = {
|
|
708
|
-
tmux_manager.ENV_TERMINAL_ID: terminal_id,
|
|
709
|
-
tmux_manager.ENV_TERMINAL_PROVIDER: "claude",
|
|
710
|
-
tmux_manager.ENV_INNER_SOCKET: tmux_manager.INNER_SOCKET,
|
|
711
|
-
tmux_manager.ENV_INNER_SESSION: tmux_manager.INNER_SESSION,
|
|
712
|
-
tmux_manager.ENV_CONTEXT_ID: context_id,
|
|
713
|
-
}
|
|
714
|
-
|
|
715
|
-
try:
|
|
716
|
-
from pathlib import Path
|
|
717
|
-
|
|
718
|
-
from ...claude_hooks.stop_hook_installer import (
|
|
719
|
-
ensure_stop_hook_installed,
|
|
720
|
-
get_settings_path as get_stop_settings_path,
|
|
721
|
-
install_stop_hook,
|
|
722
|
-
)
|
|
723
|
-
from ...claude_hooks.user_prompt_submit_hook_installer import (
|
|
724
|
-
ensure_user_prompt_submit_hook_installed,
|
|
725
|
-
get_settings_path as get_submit_settings_path,
|
|
726
|
-
install_user_prompt_submit_hook,
|
|
727
|
-
)
|
|
728
|
-
from ...claude_hooks.permission_request_hook_installer import (
|
|
729
|
-
ensure_permission_request_hook_installed,
|
|
730
|
-
get_settings_path as get_permission_settings_path,
|
|
731
|
-
install_permission_request_hook,
|
|
732
|
-
)
|
|
733
|
-
|
|
734
|
-
ok_global_stop = ensure_stop_hook_installed(quiet=True)
|
|
735
|
-
ok_global_submit = ensure_user_prompt_submit_hook_installed(quiet=True)
|
|
736
|
-
ok_global_permission = ensure_permission_request_hook_installed(quiet=True)
|
|
737
|
-
|
|
738
|
-
project_root = Path(workspace)
|
|
739
|
-
ok_project_stop = install_stop_hook(
|
|
740
|
-
get_stop_settings_path(project_root), quiet=True
|
|
741
|
-
)
|
|
742
|
-
ok_project_submit = install_user_prompt_submit_hook(
|
|
743
|
-
get_submit_settings_path(project_root), quiet=True
|
|
744
|
-
)
|
|
745
|
-
ok_project_permission = install_permission_request_hook(
|
|
746
|
-
get_permission_settings_path(project_root), quiet=True
|
|
747
|
-
)
|
|
621
|
+
agent_type, workspace = result
|
|
622
|
+
self.run_worker(
|
|
623
|
+
self._create_agent(agent_type, workspace),
|
|
624
|
+
group="terminal-panel-create",
|
|
625
|
+
exclusive=True,
|
|
626
|
+
)
|
|
748
627
|
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
628
|
+
async def _create_agent(self, agent_type: str, workspace: str) -> None:
|
|
629
|
+
"""Create a new agent terminal based on the selected type and workspace."""
|
|
630
|
+
if agent_type == "claude":
|
|
631
|
+
await self._create_claude_terminal(workspace)
|
|
632
|
+
elif agent_type == "codex":
|
|
633
|
+
await self._create_codex_terminal(workspace)
|
|
634
|
+
elif agent_type == "opencode":
|
|
635
|
+
await self._create_opencode_terminal(workspace)
|
|
636
|
+
elif agent_type == "zsh":
|
|
637
|
+
await self._create_zsh_terminal(workspace)
|
|
638
|
+
await self.refresh_data()
|
|
639
|
+
|
|
640
|
+
async def _create_claude_terminal(self, workspace: str) -> None:
|
|
641
|
+
"""Create a new Claude terminal."""
|
|
642
|
+
terminal_id = tmux_manager.new_terminal_id()
|
|
643
|
+
context_id = tmux_manager.new_context_id("cc")
|
|
644
|
+
env = {
|
|
645
|
+
tmux_manager.ENV_TERMINAL_ID: terminal_id,
|
|
646
|
+
tmux_manager.ENV_TERMINAL_PROVIDER: "claude",
|
|
647
|
+
tmux_manager.ENV_INNER_SOCKET: tmux_manager.INNER_SOCKET,
|
|
648
|
+
tmux_manager.ENV_INNER_SESSION: tmux_manager.INNER_SESSION,
|
|
649
|
+
tmux_manager.ENV_CONTEXT_ID: context_id,
|
|
650
|
+
}
|
|
765
651
|
|
|
766
|
-
|
|
767
|
-
|
|
652
|
+
try:
|
|
653
|
+
from ...claude_hooks.stop_hook_installer import (
|
|
654
|
+
ensure_stop_hook_installed,
|
|
655
|
+
get_settings_path as get_stop_settings_path,
|
|
656
|
+
install_stop_hook,
|
|
768
657
|
)
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
658
|
+
from ...claude_hooks.user_prompt_submit_hook_installer import (
|
|
659
|
+
ensure_user_prompt_submit_hook_installed,
|
|
660
|
+
get_settings_path as get_submit_settings_path,
|
|
661
|
+
install_user_prompt_submit_hook,
|
|
662
|
+
)
|
|
663
|
+
from ...claude_hooks.permission_request_hook_installer import (
|
|
664
|
+
ensure_permission_request_hook_installed,
|
|
665
|
+
get_settings_path as get_permission_settings_path,
|
|
666
|
+
install_permission_request_hook,
|
|
775
667
|
)
|
|
776
|
-
if not created:
|
|
777
|
-
self.app.notify("Failed to open cc terminal", title="Terminal", severity="error")
|
|
778
|
-
await self.refresh_data()
|
|
779
|
-
return
|
|
780
668
|
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
return
|
|
669
|
+
ok_global_stop = ensure_stop_hook_installed(quiet=True)
|
|
670
|
+
ok_global_submit = ensure_user_prompt_submit_hook_installed(quiet=True)
|
|
671
|
+
ok_global_permission = ensure_permission_request_hook_installed(quiet=True)
|
|
785
672
|
|
|
786
|
-
|
|
787
|
-
|
|
673
|
+
project_root = Path(workspace)
|
|
674
|
+
ok_project_stop = install_stop_hook(
|
|
675
|
+
get_stop_settings_path(project_root), quiet=True
|
|
676
|
+
)
|
|
677
|
+
ok_project_submit = install_user_prompt_submit_hook(
|
|
678
|
+
get_submit_settings_path(project_root), quiet=True
|
|
679
|
+
)
|
|
680
|
+
ok_project_permission = install_permission_request_hook(
|
|
681
|
+
get_permission_settings_path(project_root), quiet=True
|
|
788
682
|
)
|
|
789
|
-
created = tmux_manager.create_inner_window("codex", command)
|
|
790
|
-
if not created:
|
|
791
|
-
self.app.notify("Failed to open codex terminal", title="Terminal", severity="error")
|
|
792
|
-
await self.refresh_data()
|
|
793
|
-
return
|
|
794
|
-
|
|
795
|
-
if button_id == "new-opencode":
|
|
796
|
-
workspace = await self._select_workspace("Select workspace for Opencode")
|
|
797
|
-
if not workspace:
|
|
798
|
-
return
|
|
799
683
|
|
|
800
|
-
|
|
801
|
-
|
|
684
|
+
all_hooks_ok = (
|
|
685
|
+
ok_global_stop
|
|
686
|
+
and ok_global_submit
|
|
687
|
+
and ok_global_permission
|
|
688
|
+
and ok_project_stop
|
|
689
|
+
and ok_project_submit
|
|
690
|
+
and ok_project_permission
|
|
802
691
|
)
|
|
803
|
-
|
|
804
|
-
if not created:
|
|
692
|
+
if not all_hooks_ok:
|
|
805
693
|
self.app.notify(
|
|
806
|
-
"
|
|
694
|
+
"Claude hooks not fully installed; session id/title may not update",
|
|
807
695
|
title="Terminal",
|
|
808
|
-
severity="
|
|
696
|
+
severity="warning",
|
|
809
697
|
)
|
|
810
|
-
|
|
698
|
+
except Exception:
|
|
699
|
+
pass
|
|
700
|
+
|
|
701
|
+
command = self._command_in_directory(
|
|
702
|
+
tmux_manager.zsh_run_and_keep_open("claude"), workspace
|
|
703
|
+
)
|
|
704
|
+
created = tmux_manager.create_inner_window(
|
|
705
|
+
"cc",
|
|
706
|
+
tmux_manager.shell_command_with_env(command, env),
|
|
707
|
+
terminal_id=terminal_id,
|
|
708
|
+
provider="claude",
|
|
709
|
+
context_id=context_id,
|
|
710
|
+
)
|
|
711
|
+
if not created:
|
|
712
|
+
self.app.notify("Failed to open Claude terminal", title="Terminal", severity="error")
|
|
713
|
+
|
|
714
|
+
async def _create_codex_terminal(self, workspace: str) -> None:
|
|
715
|
+
"""Create a new Codex terminal."""
|
|
716
|
+
command = self._command_in_directory(
|
|
717
|
+
tmux_manager.zsh_run_and_keep_open("codex"), workspace
|
|
718
|
+
)
|
|
719
|
+
created = tmux_manager.create_inner_window("codex", command)
|
|
720
|
+
if not created:
|
|
721
|
+
self.app.notify("Failed to open Codex terminal", title="Terminal", severity="error")
|
|
722
|
+
|
|
723
|
+
async def _create_opencode_terminal(self, workspace: str) -> None:
|
|
724
|
+
"""Create a new Opencode terminal."""
|
|
725
|
+
command = self._command_in_directory(
|
|
726
|
+
tmux_manager.zsh_run_and_keep_open("opencode"), workspace
|
|
727
|
+
)
|
|
728
|
+
created = tmux_manager.create_inner_window("opencode", command)
|
|
729
|
+
if not created:
|
|
730
|
+
self.app.notify("Failed to open Opencode terminal", title="Terminal", severity="error")
|
|
731
|
+
|
|
732
|
+
async def _create_zsh_terminal(self, workspace: str) -> None:
|
|
733
|
+
"""Create a new zsh terminal."""
|
|
734
|
+
command = self._command_in_directory("zsh", workspace)
|
|
735
|
+
created = tmux_manager.create_inner_window("zsh", command)
|
|
736
|
+
if not created:
|
|
737
|
+
self.app.notify("Failed to open zsh terminal", title="Terminal", severity="error")
|
|
738
|
+
|
|
739
|
+
async def on_button_pressed(self, event: Button.Pressed) -> None:
|
|
740
|
+
button_id = event.button.id or ""
|
|
741
|
+
|
|
742
|
+
if not self.supported():
|
|
743
|
+
self.app.notify(
|
|
744
|
+
self._support_message(),
|
|
745
|
+
title="Terminal",
|
|
746
|
+
severity="warning",
|
|
747
|
+
)
|
|
811
748
|
return
|
|
812
749
|
|
|
813
|
-
if button_id == "new-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
await self.refresh_data()
|
|
750
|
+
if button_id == "new-agent":
|
|
751
|
+
from ..screens import CreateAgentScreen
|
|
752
|
+
|
|
753
|
+
self.app.push_screen(CreateAgentScreen(), self._on_create_agent_result)
|
|
818
754
|
return
|
|
819
755
|
|
|
820
756
|
if button_id.startswith("switch-"):
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|