Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -205,4 +205,6 @@ __marimo__/
*.bak

# Schema validation test cache
tests/fixtures/schemas/cache/
tests/fixtures/schemas/cache/

.claude/worktrees/
67 changes: 29 additions & 38 deletions src/ai_rules/cli/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -74,11 +74,9 @@ def _display_pending_symlink_changes(targets: list["ConfigTarget"]) -> bool:
Returns:
True if changes were found and displayed, False otherwise
"""
from rich.console import Console

from ai_rules.cli.display import console, print_add, print_update
from ai_rules.symlinks import check_symlink, get_content_diff

console = Console()
found_changes = False

for agent in targets:
Expand Down Expand Up @@ -111,9 +109,9 @@ def _display_pending_symlink_changes(targets: list["ConfigTarget"]) -> bool:
console.print(f"\n[bold]{agent.name}[/bold]")
for action, target, source, content_diff in agent_changes:
if action == "create":
console.print(f" [green]+[/green] Create: {target} → {source}")
print_add(f"Create: {target} → {source}", indent=2)
else:
console.print(f" [yellow]↻[/yellow] Update: {target} → {source}")
print_update(f"Update: {target} → {source}", indent=2)
if content_diff:
console.print(content_diff)

Expand All @@ -126,9 +124,7 @@ def _display_pending_plugin_changes(config: "Config") -> bool:
Returns:
True if changes were found and displayed, False otherwise
"""
from rich.console import Console

console = Console()
from ai_rules.cli.display import console, print_add, print_skipped

result = _get_plugin_status(config)
if result is None:
Expand All @@ -141,23 +137,19 @@ def _display_pending_plugin_changes(config: "Config") -> bool:
found_changes = True
console.print("\n[bold]Marketplaces[/bold]")
for marketplace in plugin_status.marketplaces_missing:
console.print(
f" [green]+[/green] Add: {marketplace['name']} ({marketplace['source']})"
)
print_add(f"Add: {marketplace['name']} ({marketplace['source']})", indent=2)

if plugin_status.pending:
found_changes = True
console.print("\n[bold]Plugins[/bold]")
for plugin in plugin_status.pending:
console.print(
f" [green]+[/green] Install: {plugin['name']}@{plugin['marketplace']}"
)
print_add(f"Install: {plugin['name']}@{plugin['marketplace']}", indent=2)

if plugin_status.extra:
if not found_changes:
console.print("\n[bold]Plugins[/bold]")
for name in sorted(plugin_status.extra):
console.print(f" [dim]○[/dim] {name} (Unmanaged)")
print_skipped(f"{name} (Unmanaged)", indent=2)

return found_changes

Expand All @@ -175,10 +167,7 @@ def check_first_run(targets: list["ConfigTarget"], force: bool) -> bool:
Returns:
True if should continue, False if should abort
"""
from rich.console import Console
from rich.prompt import Confirm

console = Console()
from ai_rules.cli.display import console, print_warning

existing_files = []

Expand All @@ -194,15 +183,17 @@ def check_first_run(targets: list["ConfigTarget"], force: bool) -> bool:
if force:
return True

console.print("\n[yellow]Warning:[/yellow] Found existing configuration files:\n")
print_warning("Found existing configuration files:\n")
for agent_name, path in existing_files:
console.print(f" [{agent_name}] {path}")

console.print(
"\n[dim]These will be replaced with symlinks (originals will be backed up).[/dim]\n"
)
from ai_rules.cli.display import print_dim

return Confirm.ask("Continue?", default=False)
console.print()
print_dim("These will be replaced with symlinks (originals will be backed up).")
console.print()

return click.confirm("Continue?", default=False)


def version_callback(ctx: click.Context, param: click.Parameter, value: bool) -> None:
Expand All @@ -213,9 +204,7 @@ def version_callback(ctx: click.Context, param: click.Parameter, value: bool) ->
param: Click parameter
value: Whether --version flag was provided
"""
from rich.console import Console

console = Console()
from ai_rules.cli.display import console, print_hint

if not value or ctx.resilient_parsing:
return
Expand All @@ -232,8 +221,10 @@ def version_callback(ctx: click.Context, param: click.Parameter, value: bool) ->
if statusline_version:
console.print(f"statusline, version {statusline_version}")
else:
from ai_rules.cli.display import dim

console.print(
"statusline, version [dim](installed, version unknown)[/dim]"
f"statusline, version {dim('(installed, version unknown)')}"
)
except Exception as e:
logger.debug(f"Failed to get statusline version: {e}")
Expand All @@ -246,7 +237,9 @@ def version_callback(ctx: click.Context, param: click.Parameter, value: bool) ->
if bm_version:
console.print(f"recall, version {bm_version}")
else:
console.print("recall, version [dim](installed, version unknown)[/dim]")
from ai_rules.cli.display import dim as _dim

console.print(f"recall, version {_dim('(installed, version unknown)')}")
except Exception as e:
logger.debug(f"Failed to get recall version: {e}")

Expand All @@ -260,7 +253,7 @@ def version_callback(ctx: click.Context, param: click.Parameter, value: bool) ->
console.print(
f"\n[cyan]Update available:[/cyan] {update_info.current_version} → {update_info.latest_version}"
)
console.print("[dim]Run 'ai-agent-rules upgrade' to install[/dim]")
print_hint("Run 'ai-agent-rules upgrade' to install")
except Exception as e:
logger.debug(f"Failed to check for updates in version callback: {e}")

Expand Down Expand Up @@ -298,11 +291,9 @@ def cleanup_deprecated_symlinks(
Returns:
Count of removed symlinks
"""
from rich.console import Console

from ai_rules.cli.display import print_would
from ai_rules.symlinks import remove_symlink

console = Console()
removed_count = 0

for agent in selected_targets:
Expand All @@ -318,15 +309,15 @@ def cleanup_deprecated_symlinks(
continue

if dry_run:
console.print(
f" [yellow]Would remove deprecated:[/yellow] {deprecated_path}"
)
print_would(f"Would remove deprecated: {deprecated_path}", indent=2)
removed_count += 1
else:
success, message = remove_symlink(target, force=True)
if success:
console.print(
f" [dim]Cleaned up deprecated symlink:[/dim] {deprecated_path}"
from ai_rules.cli.display import print_label

print_label(
"Cleaned up deprecated symlink", str(deprecated_path), indent=2
)
removed_count += 1

Expand Down
13 changes: 4 additions & 9 deletions src/ai_rules/cli/commands/diff.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,15 +33,12 @@ def _complete_components(
)
def diff(agents: str | None, component_filter: str | None) -> None:
"""Show differences between repo configs and installed symlinks."""
from rich.console import Console

from ai_rules.cli.components import DIFF_COMPONENTS
from ai_rules.cli.context import CliContext
from ai_rules.cli.runner import run_components
from ai_rules.cli.display import console, print_hint
from ai_rules.cli.runner import run_diff_parallel
from ai_rules.config import Config

console = Console()

config_dir = cli_facade.get_config_dir()
config = Config.load()
all_targets = cli_facade.get_targets(config_dir, config)
Expand All @@ -61,11 +58,9 @@ def diff(agents: str | None, component_filter: str | None) -> None:
target_filter=agents,
component_filter=parsed_filter,
)
result = run_components(DIFF_COMPONENTS, "diff", cli_ctx)
result = run_diff_parallel(DIFF_COMPONENTS, cli_ctx)

if not result.changed:
console.print("[green]No differences found - all symlinks are correct![/green]")
else:
console.print(
"[yellow]💡 Run 'ai-agent-rules install' to fix these differences[/yellow]"
)
print_hint("Run 'ai-agent-rules install' to fix these differences")
16 changes: 8 additions & 8 deletions src/ai_rules/cli/commands/install.py
Original file line number Diff line number Diff line change
Expand Up @@ -74,19 +74,16 @@ def install(
config_dir_override: str | None = None,
) -> None:
"""Install AI agent configs via symlinks."""
from rich.console import Console

from ai_rules.cli.components import INSTALL_COMPONENTS
from ai_rules.cli.context import CliContext
from ai_rules.cli.display import console, print_error
from ai_rules.cli.runner import run_install_parallel
from ai_rules.config import Config

console = Console()

if config_dir_override:
config_dir = Path(config_dir_override)
if not config_dir.exists():
console.print(f"[red]Error:[/red] Config directory not found: {config_dir}")
print_error(f"Config directory not found: {config_dir}")
sys.exit(1)
else:
config_dir = cli_facade.get_config_dir()
Expand All @@ -108,20 +105,23 @@ def install(
if profile_conflicts:
_handle_profile_conflicts(profile_conflicts, profile, user_config)
except ProfileNotFoundError as e:
console.print(f"[red]Error:[/red] {e}")
print_error(str(e))
sys.exit(1)

try:
config = Config.load(profile=profile)
except ProfileNotFoundError as e:
console.print(f"[red]Error:[/red] {e}")
print_error(str(e))
sys.exit(1)

if not dry_run:
set_active_profile(profile)

if profile and profile != "default":
console.print(f"[dim]Using profile: {profile}[/dim]\n")
from ai_rules.cli.display import print_label

print_label("Using profile", profile)
console.print()

all_targets = cli_facade.get_targets(config_dir, config)
selected_targets = cli_facade.select_targets(all_targets, agents)
Expand Down
4 changes: 1 addition & 3 deletions src/ai_rules/cli/commands/list_agents.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,12 @@
@click.command("list-agents")
def list_agents_cmd() -> None:
"""List available AI agents."""
from rich.console import Console
from rich.table import Table

from ai_rules.cli.display import console
from ai_rules.config import Config
from ai_rules.symlinks import check_symlink

console = Console()

config_dir = cli_facade.get_config_dir()
config = Config.load()
targets = cli_facade.get_targets(config_dir, config)
Expand Down
Loading