Skip to content
Open
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
2 changes: 1 addition & 1 deletion AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ lola install <module> -a claude-code
2. **Installation**: `lola install <module>` copies modules to project's `.lola/modules/` and generates assistant-specific files
3. **Updates**: `lola update` regenerates assistant files from source modules
4. **Marketplace Registration**: `lola market add <name> <url>` fetches marketplace catalogs to `~/.lola/market/` (reference) and `~/.lola/market/cache/` (full catalog)
5. **Module Discovery**: `lola mod search <query>` searches across enabled marketplace caches; `lola install <module>` auto-adds from marketplace if not in registry
5. **Module Discovery**: `lola search <query>` searches both the local module registry and enabled marketplace caches (use `--mod` or `--market` to scope); `lola mod search <query>` is a deprecated alias for `lola search <query> --mod`; `lola install <module>` auto-adds from marketplace if not in registry

### Installation Scopes

Expand Down
3 changes: 2 additions & 1 deletion CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,8 @@
- 001-mod-init-template: Added Python 3.13 + click, rich, pyyaml, python-frontmatter
- 003-marketplace: Added complete marketplace feature
- `lola market add/ls/update/set/rm` commands for marketplace management
- `lola mod search` for cross-marketplace module discovery
- `lola search <query>` for unified discovery across the local registry and enabled marketplaces (`--mod` / `--market` to scope)
- `lola mod search <query>` as a deprecated compatibility alias for `lola search <query> --mod`
- Auto-install from marketplaces via `lola install <module>`
- Multi-marketplace conflict resolution with user prompts
- Cache recovery on missing cache files
Expand Down
10 changes: 9 additions & 1 deletion docs/cli-reference/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ Complete command reference for the Lola CLI. Use `lola --help` or `lola <command
| `lola mod add <source>` | Add a module from git, folder, zip, or tar |
| `lola mod ls` | List registered modules |
| `lola mod info <name>` | Show module details |
| `lola mod search <query>` | Search across enabled marketplaces |
| `lola mod init [name]` | Initialize a new module |
| `lola mod update [name]` | Update module(s) from source |
| `lola mod rm <name>` | Remove a module |
Expand All @@ -25,6 +24,15 @@ Complete command reference for the Lola CLI. Use `lola --help` or `lola <command
| `lola market set --disable <name>` | Disable a marketplace |
| `lola market rm <name>` | Remove a marketplace |

## Search

| Command | Description |
| -------------------------------- | ----------------------------------------------------------------- |
| `lola search <query>` | Search the local registry and enabled marketplaces |
| `lola search <query> --mod` | Search only the local module registry |
| `lola search <query> --market` | Search only enabled marketplaces |
| `lola mod search <query>` | Deprecated alias for `lola search <query> --mod` |

## Installation

| Command | Description |
Expand Down
2 changes: 1 addition & 1 deletion docs/getting-started/quick-start.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ lola market add general https://raw.githubusercontent.com/RedHatProductSecurity/

```bash
# From the marketplace
lola mod search git
lola search git
lola install git-workflow

# Or from a git repository
Expand Down
7 changes: 5 additions & 2 deletions docs/guides/marketplace.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,11 @@ lola market add general https://raw.githubusercontent.com/RedHatProductSecurity/
## Search and Install

```bash
# Search across all enabled marketplaces
lola mod search authentication
# Search the local registry and all enabled marketplaces
lola search authentication

# Limit to enabled marketplaces only
lola search authentication --market

# Install directly from marketplace (auto-adds and installs)
lola install git-workflow -a claude-code
Expand Down
2 changes: 2 additions & 0 deletions src/lola/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
)
from lola.cli.market import market
from lola.cli.mod import mod
from lola.cli.search import search_cmd
from lola.cli.sync import sync_cmd

console = Console()
Expand Down Expand Up @@ -66,6 +67,7 @@ def main(ctx, version):
main.add_command(uninstall_cmd)
main.add_command(update_cmd)
main.add_command(list_installed_cmd)
main.add_command(search_cmd)
main.add_command(sync_cmd)
main.add_command(completions_cmd)

Expand Down
35 changes: 18 additions & 17 deletions src/lola/cli/mod.py
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ def list_registered_modules() -> list[Module]:
return sorted(modules, key=lambda m: m.name)


def _count_str(count: int, singular: str) -> str:
def count_str(count: int, singular: str) -> str:
"""Format count with singular/plural form."""
return f"{count} {singular}" if count == 1 else f"{count} {singular}s"

Expand Down Expand Up @@ -850,9 +850,9 @@ def list_modules(verbose: bool):
for module in modules:
console.print(f"[cyan]{module.name}[/cyan]")

skills_str = _count_str(len(module.skills), "skill")
cmds_str = _count_str(len(module.commands), "command")
agents_str = _count_str(len(module.agents), "agent")
skills_str = count_str(len(module.skills), "skill")
cmds_str = count_str(len(module.commands), "command")
agents_str = count_str(len(module.agents), "agent")
console.print(f" [dim]{skills_str}, {cmds_str}, {agents_str}[/dim]")

if verbose:
Expand Down Expand Up @@ -1132,10 +1132,10 @@ def update_module_cmd(module_name: str | None):

console.print()
if updated > 0:
console.print(f"[green]Updated {_count_str(updated, 'module')}[/green]")
console.print(f"[green]Updated {count_str(updated, 'module')}[/green]")
if failed > 0:
console.print(
f"[yellow]Failed to update {_count_str(failed, 'module')}[/yellow]"
f"[yellow]Failed to update {count_str(failed, 'module')}[/yellow]"
)

if updated > 0:
Expand All @@ -1145,19 +1145,20 @@ def update_module_cmd(module_name: str | None):

@mod.command(name="search")
@click.argument("query")
def mod_search(query: str):
@click.pass_context
def search_compat_cmd(ctx: click.Context, query: str):
"""
Search for modules across all enabled marketplaces.
Search the local module registry (compatibility alias).

QUERY: Search term to match against module name, description, tags
Deprecated: prefer 'lola search --mod'. This forwards to
'lola search <query> --mod' so existing scripts keep working.

\b
Example:
lola mod search git
QUERY: Search term to match
"""
from lola.config import MARKET_DIR, CACHE_DIR
from lola.market.manager import MarketplaceRegistry
from lola.cli.search import search_cmd

ensure_lola_dirs()
registry = MarketplaceRegistry(MARKET_DIR, CACHE_DIR)
registry.search(query)
# Warn on stderr so stdout stays clean for scripts parsing the results.
click.echo(
"'lola mod search' is deprecated; use 'lola search --mod' instead", err=True
)
ctx.invoke(search_cmd, query=query, mod=True, market=False)
106 changes: 106 additions & 0 deletions src/lola/cli/search.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
"""
Top-level search command.

Searches both the local module registry and all enabled marketplace caches.
"""

import click
from rich.console import Console
from rich.table import Table

from lola.cli.mod import count_str, list_registered_modules
from lola.config import CACHE_DIR, MARKET_DIR
from lola.market.search import search_market
from lola.models import Module

console = Console()


def _search_local(query_lower: str) -> list[Module]:
results: list[Module] = []
for module in list_registered_modules():
haystack = [module.name, *module.skills, *module.commands, *module.agents]
if any(query_lower in item.lower() for item in haystack):
results.append(module)
return results


def _print_local(results: list[Module]) -> None:
console.print(
f"[bold]Local registry ({count_str(len(results), 'module')})[/bold]\n"
)
for module in results:
console.print(f" [cyan]{module.name}[/cyan]")
skills_str = count_str(len(module.skills), "skill")
cmds_str = count_str(len(module.commands), "command")
agents_str = count_str(len(module.agents), "agent")
console.print(f" [dim]{skills_str}, {cmds_str}, {agents_str}[/dim]")
console.print()


def _print_marketplace(results: list[dict]) -> None:
console.print(f"[bold]Marketplaces ({count_str(len(results), 'module')})[/bold]\n")
table = Table(show_header=True, header_style="bold")
table.add_column("Module")
table.add_column("Version")
table.add_column("Marketplace")
table.add_column("Description")
for r in results:
table.add_row(r["name"], r["version"], r["marketplace"], r["description"])
console.print(table)
console.print()


@click.command(name="search")
@click.argument("query")
@click.option("--mod", is_flag=True, help="Search only the local module registry")
@click.option("--market", is_flag=True, help="Search only enabled marketplaces")
def search_cmd(query: str, mod: bool, market: bool):
"""
Search modules in the local registry and enabled marketplaces.

Local matches are by module name, skill name, command name, or agent name.
Marketplace matches are by module name, description, or tag.

QUERY: Search term to match

\b
Examples:
lola search git # search both local and marketplaces
lola search git --mod # only the local module registry
lola search git --market # only enabled marketplaces
"""
if mod and market:
click.echo("Error: --mod and --market are mutually exclusive")
raise SystemExit(1)

query_lower = query.lower()

show_local = not market
show_remote = not mod

local_results = _search_local(query_lower) if show_local else []
market_results = search_market(query, MARKET_DIR, CACHE_DIR) if show_remote else []

total = len(local_results) + len(market_results)
if total == 0:
console.print(f"[yellow]No modules found matching '{query}'[/yellow]")
if not show_remote:
console.print(
"[dim]Tip: drop --mod to also search remote marketplaces[/dim]"
)
elif not show_local:
console.print(
"[dim]Tip: drop --market to also search the local registry[/dim]"
)
else:
console.print(
"[dim]Tip: check spelling or add a marketplace with 'lola market add'[/dim]"
)
return

console.print()
if local_results:
_print_local(local_results)
if market_results:
_print_marketplace(market_results)
Loading
Loading