aline-ai 0.5.4__py3-none-any.whl → 0.5.6__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (82) hide show
  1. {aline_ai-0.5.4.dist-info → aline_ai-0.5.6.dist-info}/METADATA +1 -1
  2. aline_ai-0.5.6.dist-info/RECORD +95 -0
  3. realign/__init__.py +1 -1
  4. realign/adapters/antigravity.py +28 -20
  5. realign/adapters/base.py +46 -50
  6. realign/adapters/claude.py +14 -14
  7. realign/adapters/codex.py +7 -7
  8. realign/adapters/gemini.py +11 -11
  9. realign/adapters/registry.py +14 -10
  10. realign/claude_detector.py +2 -2
  11. realign/claude_hooks/__init__.py +3 -3
  12. realign/claude_hooks/permission_request_hook_installer.py +31 -32
  13. realign/claude_hooks/stop_hook.py +4 -1
  14. realign/claude_hooks/stop_hook_installer.py +30 -31
  15. realign/cli.py +23 -4
  16. realign/codex_detector.py +11 -11
  17. realign/commands/add.py +88 -65
  18. realign/commands/config.py +3 -12
  19. realign/commands/context.py +3 -1
  20. realign/commands/export_shares.py +86 -127
  21. realign/commands/import_shares.py +145 -155
  22. realign/commands/init.py +166 -30
  23. realign/commands/restore.py +18 -6
  24. realign/commands/search.py +14 -42
  25. realign/commands/upgrade.py +155 -11
  26. realign/commands/watcher.py +98 -219
  27. realign/commands/worker.py +29 -6
  28. realign/config.py +25 -20
  29. realign/context.py +1 -3
  30. realign/dashboard/app.py +34 -24
  31. realign/dashboard/screens/__init__.py +10 -1
  32. realign/dashboard/screens/create_agent.py +244 -0
  33. realign/dashboard/screens/create_event.py +3 -1
  34. realign/dashboard/screens/event_detail.py +14 -6
  35. realign/dashboard/screens/help_screen.py +114 -0
  36. realign/dashboard/screens/session_detail.py +3 -1
  37. realign/dashboard/screens/share_import.py +7 -3
  38. realign/dashboard/tmux_manager.py +54 -9
  39. realign/dashboard/widgets/config_panel.py +85 -1
  40. realign/dashboard/widgets/events_table.py +314 -70
  41. realign/dashboard/widgets/header.py +2 -1
  42. realign/dashboard/widgets/search_panel.py +37 -27
  43. realign/dashboard/widgets/sessions_table.py +404 -85
  44. realign/dashboard/widgets/terminal_panel.py +155 -175
  45. realign/dashboard/widgets/watcher_panel.py +6 -2
  46. realign/dashboard/widgets/worker_panel.py +10 -1
  47. realign/db/__init__.py +1 -1
  48. realign/db/base.py +5 -15
  49. realign/db/locks.py +0 -1
  50. realign/db/migration.py +82 -76
  51. realign/db/schema.py +2 -6
  52. realign/db/sqlite_db.py +23 -41
  53. realign/events/__init__.py +0 -1
  54. realign/events/event_summarizer.py +27 -15
  55. realign/events/session_summarizer.py +29 -15
  56. realign/file_lock.py +1 -0
  57. realign/hooks.py +150 -60
  58. realign/logging_config.py +12 -15
  59. realign/mcp_server.py +30 -51
  60. realign/mcp_watcher.py +0 -1
  61. realign/models/event.py +29 -20
  62. realign/prompts/__init__.py +7 -7
  63. realign/prompts/presets.py +15 -11
  64. realign/redactor.py +99 -59
  65. realign/triggers/__init__.py +9 -9
  66. realign/triggers/antigravity_trigger.py +30 -28
  67. realign/triggers/base.py +4 -3
  68. realign/triggers/claude_trigger.py +104 -85
  69. realign/triggers/codex_trigger.py +15 -5
  70. realign/triggers/gemini_trigger.py +57 -47
  71. realign/triggers/next_turn_trigger.py +3 -1
  72. realign/triggers/registry.py +6 -2
  73. realign/triggers/turn_status.py +3 -1
  74. realign/watcher_core.py +306 -131
  75. realign/watcher_daemon.py +8 -8
  76. realign/worker_core.py +3 -1
  77. realign/worker_daemon.py +3 -1
  78. aline_ai-0.5.4.dist-info/RECORD +0 -93
  79. {aline_ai-0.5.4.dist-info → aline_ai-0.5.6.dist-info}/WHEEL +0 -0
  80. {aline_ai-0.5.4.dist-info → aline_ai-0.5.6.dist-info}/entry_points.txt +0 -0
  81. {aline_ai-0.5.4.dist-info → aline_ai-0.5.6.dist-info}/licenses/LICENSE +0 -0
  82. {aline_ai-0.5.4.dist-info → aline_ai-0.5.6.dist-info}/top_level.txt +0 -0
realign/commands/add.py CHANGED
@@ -516,32 +516,49 @@ def add_tmux_command() -> int:
516
516
  _source_aline_tmux_conf(tmux_conf)
517
517
 
518
518
  console.print(f"[green]✓[/green] tmux installed and config ready: [cyan]{tmux_conf}[/cyan]")
519
- console.print("[dim]Tip: in the Aline dashboard tmux session, mouse drag will copy to clipboard.[/dim]")
519
+ console.print(
520
+ "[dim]Tip: in the Aline dashboard tmux session, mouse drag will copy to clipboard.[/dim]"
521
+ )
520
522
  return 0
521
523
 
522
524
 
523
- def _install_skill_to_path(skill_root: Path, skill_name: str, skill_content: str) -> Path:
524
- """Install a skill to the specified root directory.
525
+ def _ensure_symlink(target_link: Path, source_file: Path, force: bool = False) -> bool:
526
+ """Create a symlink at target_link pointing to source_file.
525
527
 
526
528
  Args:
527
- skill_root: Root directory for skills (e.g., ~/.claude/skills)
528
- skill_name: Name of the skill (e.g., "aline")
529
- skill_content: Content of the SKILL.md file
529
+ target_link: The path where the symlink should be created (e.g., ~/.claude/skills/aline/SKILL.md)
530
+ source_file: The actual file to link to (e.g., ~/.aline/skills/aline/SKILL.md)
531
+ force: Whether to overwrite existing files/links
530
532
 
531
533
  Returns:
532
- Path to the installed SKILL.md file
534
+ True if a new link was created or updated, False if skipped (already exists)
533
535
  """
534
- skill_dir = skill_root / skill_name
535
- skill_dir.mkdir(parents=True, exist_ok=True)
536
- skill_file = skill_dir / "SKILL.md"
537
- skill_file.write_text(skill_content, encoding="utf-8")
538
- return skill_file
536
+ if target_link.exists() or target_link.is_symlink():
537
+ if not force:
538
+ # Check if it already points to the right place
539
+ try:
540
+ if target_link.is_symlink() and target_link.resolve() == source_file.resolve():
541
+ return False # Already correct
542
+ except Exception:
543
+ pass
544
+ return False # Exists and not forced
545
+
546
+ # Force: remove existing
547
+ if target_link.is_dir() and not target_link.is_symlink():
548
+ shutil.rmtree(target_link)
549
+ else:
550
+ target_link.unlink()
551
+
552
+ target_link.parent.mkdir(parents=True, exist_ok=True)
553
+ target_link.symlink_to(source_file)
554
+ return True
539
555
 
540
556
 
541
557
  def add_skills_command(force: bool = False) -> int:
542
- """Install Aline skills for Claude Code.
558
+ """Install Aline skills for Claude Code and Codex.
543
559
 
544
- Installs all skills from SKILLS_REGISTRY to ~/.claude/skills/
560
+ 1. Writes built-in skills to ~/.aline/skills/
561
+ 2. Creates symlinks in ~/.claude/skills/ and ~/.codex/skills/
545
562
 
546
563
  Args:
547
564
  force: Overwrite existing skills if they exist
@@ -549,42 +566,54 @@ def add_skills_command(force: bool = False) -> int:
549
566
  Returns:
550
567
  Exit code (0 for success, 1 for failure)
551
568
  """
552
- claude_skill_root = Path.home() / ".claude" / "skills"
569
+ aline_skill_root = Path.home() / ".aline" / "skills"
570
+ targets = [
571
+ ("Claude", Path.home() / ".claude" / "skills"),
572
+ ("Codex", Path.home() / ".codex" / "skills"),
573
+ ("OpenCode", Path.home() / ".config" / "opencode" / "skill"),
574
+ ]
575
+
553
576
  installed_skills: list[str] = []
554
577
  skipped_skills: list[str] = []
555
578
  failed_skills: list[tuple[str, str]] = []
556
579
 
557
580
  for skill_name, skill_content in SKILLS_REGISTRY.items():
558
- skill_path = claude_skill_root / skill_name / "SKILL.md"
559
-
560
- # Check if skill already exists
561
- if skill_path.exists() and not force:
562
- skipped_skills.append(skill_name)
563
- continue
564
-
581
+ # 1. Update master copy in ~/.aline/skills
582
+ master_path = aline_skill_root / skill_name / "SKILL.md"
565
583
  try:
566
- _install_skill_to_path(claude_skill_root, skill_name, skill_content)
567
- installed_skills.append(skill_name)
584
+ master_path.parent.mkdir(parents=True, exist_ok=True)
585
+ master_path.write_text(skill_content, encoding="utf-8")
568
586
  except Exception as e:
569
- failed_skills.append((skill_name, str(e)))
587
+ failed_skills.append((f"{skill_name} (storage)", str(e)))
588
+ continue
589
+
590
+ # 2. Link to targets
591
+ for tool_name, tool_root in targets:
592
+ dest_path = tool_root / skill_name / "SKILL.md"
593
+
594
+ try:
595
+ updated = _ensure_symlink(dest_path, master_path, force)
596
+ if updated:
597
+ installed_skills.append(f"{tool_name}/{skill_name}")
598
+ else:
599
+ skipped_skills.append(f"{tool_name}/{skill_name}")
600
+ except Exception as e:
601
+ failed_skills.append((f"{tool_name}/{skill_name}", str(e)))
570
602
 
571
603
  # Report results
572
- for skill_name in installed_skills:
573
- skill_path = claude_skill_root / skill_name / "SKILL.md"
574
- console.print(f"[green]✓[/green] Installed: [cyan]{skill_path}[/cyan]")
604
+ for item in installed_skills:
605
+ console.print(f"[green]✓[/green] Installed: [cyan]{item}[/cyan]")
575
606
 
576
- for skill_name in skipped_skills:
577
- skill_path = claude_skill_root / skill_name / "SKILL.md"
578
- console.print(f"[yellow]⊘[/yellow] Already exists: [dim]{skill_path}[/dim]")
607
+ for item in skipped_skills:
608
+ console.print(f"[yellow]⊘[/yellow] Already exists: [dim]{item}[/dim]")
579
609
 
580
- for skill_name, error in failed_skills:
581
- console.print(f"[red]✗[/red] Failed to install {skill_name}: {error}")
610
+ for item, error in failed_skills:
611
+ console.print(f"[red]✗[/red] Failed to install {item}: {error}")
582
612
 
583
613
  if skipped_skills and not installed_skills:
584
614
  console.print("[dim]Use --force to overwrite existing skills[/dim]")
585
615
  elif installed_skills:
586
- skill_names = ", ".join(f"/{s}" for s in installed_skills)
587
- console.print(f"[dim]Restart Claude Code to activate: {skill_names}[/dim]")
616
+ console.print("[dim]Restart your AI tools to activate new skills[/dim]")
588
617
 
589
618
  return 1 if failed_skills else 0
590
619
 
@@ -592,11 +621,7 @@ def add_skills_command(force: bool = False) -> int:
592
621
  def add_skills_dev_command(force: bool = False) -> int:
593
622
  """Install developer skills from skill-dev/ directory.
594
623
 
595
- Scans the skill-dev/ folder in the project root for SKILL.md files
596
- and installs them to ~/.claude/skills/
597
-
598
- This is for developer use only - skills in development that are not
599
- yet bundled into the package.
624
+ Symlinks skills from ./skill-dev/ to ~/.claude/skills/ and ~/.codex/skills/
600
625
 
601
626
  Args:
602
627
  force: Overwrite existing skills if they exist
@@ -605,7 +630,6 @@ def add_skills_dev_command(force: bool = False) -> int:
605
630
  Exit code (0 for success, 1 for failure)
606
631
  """
607
632
  # Find skill-dev directory relative to this file's package location
608
- # Go up from src/realign/commands/add.py to project root
609
633
  package_root = Path(__file__).parent.parent.parent.parent
610
634
  skill_dev_dir = package_root / "skill-dev"
611
635
 
@@ -614,7 +638,12 @@ def add_skills_dev_command(force: bool = False) -> int:
614
638
  console.print("[dim]This command is for developer use only.[/dim]")
615
639
  return 1
616
640
 
617
- claude_skill_root = Path.home() / ".claude" / "skills"
641
+ targets = [
642
+ ("Claude", Path.home() / ".claude" / "skills"),
643
+ ("Codex", Path.home() / ".codex" / "skills"),
644
+ ("OpenCode", Path.home() / ".config" / "opencode" / "skill"),
645
+ ]
646
+
618
647
  installed_skills: list[str] = []
619
648
  skipped_skills: list[str] = []
620
649
  failed_skills: list[tuple[str, str]] = []
@@ -629,42 +658,36 @@ def add_skills_dev_command(force: bool = False) -> int:
629
658
  continue
630
659
 
631
660
  skill_name = skill_dir.name
632
- dest_path = claude_skill_root / skill_name / "SKILL.md"
633
661
 
634
- # Check if skill already exists
635
- if dest_path.exists() and not force:
636
- skipped_skills.append(skill_name)
637
- continue
662
+ for tool_name, tool_root in targets:
663
+ dest_path = tool_root / skill_name / "SKILL.md"
638
664
 
639
- try:
640
- skill_content = skill_file.read_text(encoding="utf-8")
641
- _install_skill_to_path(claude_skill_root, skill_name, skill_content)
642
- installed_skills.append(skill_name)
643
- except Exception as e:
644
- failed_skills.append((skill_name, str(e)))
665
+ try:
666
+ updated = _ensure_symlink(dest_path, skill_file, force)
667
+ if updated:
668
+ installed_skills.append(f"{tool_name}/{skill_name}")
669
+ else:
670
+ skipped_skills.append(f"{tool_name}/{skill_name}")
671
+ except Exception as e:
672
+ failed_skills.append((f"{tool_name}/{skill_name}", str(e)))
645
673
 
646
674
  if not installed_skills and not skipped_skills and not failed_skills:
647
675
  console.print("[yellow]No skills found in skill-dev/[/yellow]")
648
- console.print("[dim]Each skill should be in its own directory with a SKILL.md file.[/dim]")
649
676
  return 0
650
677
 
651
678
  # Report results
652
- for skill_name in installed_skills:
653
- skill_path = claude_skill_root / skill_name / "SKILL.md"
654
- console.print(f"[green]✓[/green] Installed: [cyan]{skill_path}[/cyan]")
679
+ for item in installed_skills:
680
+ console.print(f"[green]✓[/green] Linked: [cyan]{item}[/cyan]")
655
681
 
656
- for skill_name in skipped_skills:
657
- skill_path = claude_skill_root / skill_name / "SKILL.md"
658
- console.print(f"[yellow]⊘[/yellow] Already exists: [dim]{skill_path}[/dim]")
682
+ for item in skipped_skills:
683
+ console.print(f"[yellow]⊘[/yellow] Already exists: [dim]{item}[/dim]")
659
684
 
660
- for skill_name, error in failed_skills:
661
- console.print(f"[red]✗[/red] Failed to install {skill_name}: {error}")
685
+ for item, error in failed_skills:
686
+ console.print(f"[red]✗[/red] Failed to install {item}: {error}")
662
687
 
663
688
  if skipped_skills and not installed_skills:
664
689
  console.print("[dim]Use --force to overwrite existing skills[/dim]")
665
690
  elif installed_skills:
666
- skill_names = ", ".join(f"/{s}" for s in installed_skills)
667
- console.print(f"[dim]Restart Claude Code to activate: {skill_names}[/dim]")
691
+ console.print("[dim]Restart your AI tools to activate new skills[/dim]")
668
692
 
669
693
  return 1 if failed_skills else 0
670
-
@@ -13,18 +13,9 @@ console = Console()
13
13
 
14
14
 
15
15
  def config_command(
16
- action: str = typer.Argument(
17
- ...,
18
- help="Action to perform: 'get', 'set', or 'init'"
19
- ),
20
- key: Optional[str] = typer.Argument(
21
- None,
22
- help="Configuration key (for get/set operations)"
23
- ),
24
- value: Optional[str] = typer.Argument(
25
- None,
26
- help="Configuration value (for set operation)"
27
- ),
16
+ action: str = typer.Argument(..., help="Action to perform: 'get', 'set', or 'init'"),
17
+ key: Optional[str] = typer.Argument(None, help="Configuration key (for get/set operations)"),
18
+ value: Optional[str] = typer.Argument(None, help="Configuration value (for set operation)"),
28
19
  ):
29
20
  """
30
21
  Manage ReAlign configuration.
@@ -146,7 +146,9 @@ def context_show_command(
146
146
  console.print("[dim]No active context.[/dim]")
147
147
  env_val = os.environ.get(CONTEXT_ID_ENV_VAR)
148
148
  if env_val:
149
- console.print(f"[dim]ALINE_CONTEXT_ID is set to '{env_val}' but no matching context found.[/dim]")
149
+ console.print(
150
+ f"[dim]ALINE_CONTEXT_ID is set to '{env_val}' but no matching context found.[/dim]"
151
+ )
150
152
  console.print("[dim]Searches will include all sessions/events.[/dim]")
151
153
  return 0
152
154