This commit is contained in:
nose
2025-12-11 19:04:02 -08:00
parent 6863c6c7ea
commit 16d8a763cd
103 changed files with 4759 additions and 9156 deletions

93
CLI.py
View File

@@ -15,7 +15,7 @@ from typing import Any, Dict, List, Optional, Sequence, Set, TextIO, TYPE_CHECKI
import time
import threading
from helper.logger import debug
from SYS.logger import debug
try:
import typer
@@ -48,17 +48,17 @@ except ImportError: # pragma: no cover - optional dependency
try:
from helper.worker_manager import WorkerManager
from SYS.worker_manager import WorkerManager
except ImportError: # pragma: no cover - optional dependency
WorkerManager = None # type: ignore
try:
from helper.background_notifier import ensure_background_notifier
from SYS.background_notifier import ensure_background_notifier
except ImportError: # pragma: no cover - optional dependency
ensure_background_notifier = lambda *_, **__: None # type: ignore
if TYPE_CHECKING: # pragma: no cover - typing helper
from helper.worker_manager import WorkerManager as WorkerManagerType
from SYS.worker_manager import WorkerManager as WorkerManagerType
else:
WorkerManagerType = Any
@@ -68,7 +68,7 @@ from typing import Callable
from config import get_local_storage_path, load_config
from helper.cmdlet_catalog import (
from cmdlets.catalog import (
import_cmd_module as _catalog_import_cmd_module,
list_cmdlet_metadata as _catalog_list_cmdlet_metadata,
list_cmdlet_names as _catalog_list_cmdlet_names,
@@ -507,8 +507,8 @@ def _get_arg_choices(cmd_name: str, arg_name: str) -> List[str]:
# Support both "storage" and "store" argument names
if normalized_arg in ("storage", "store"):
try:
from helper.store import FileStorage
storage = FileStorage(_load_cli_config(), suppress_debug=True)
from Store import Store
storage = Store(_load_cli_config(), suppress_debug=True)
backends = storage.list_backends()
if backends:
return backends
@@ -518,15 +518,15 @@ def _get_arg_choices(cmd_name: str, arg_name: str) -> List[str]:
# Dynamic search providers
if normalized_arg == "provider":
try:
from helper.provider import list_providers
providers = list_providers(_load_cli_config())
from Provider.registry import list_search_providers
providers = list_search_providers(_load_cli_config())
available = [name for name, is_ready in providers.items() if is_ready]
provider_choices = sorted(available) if available else sorted(providers.keys())
except Exception:
provider_choices = []
try:
from helper.metadata_search import list_metadata_providers
from Provider.metadata_provider import list_metadata_providers
meta_providers = list_metadata_providers(_load_cli_config())
meta_available = [n for n, ready in meta_providers.items() if ready]
meta_choices = sorted(meta_available) if meta_available else sorted(meta_providers.keys())
@@ -539,7 +539,7 @@ def _get_arg_choices(cmd_name: str, arg_name: str) -> List[str]:
if normalized_arg == "scrape":
try:
from helper.metadata_search import list_metadata_providers
from Provider.metadata_provider import list_metadata_providers
meta_providers = list_metadata_providers(_load_cli_config())
if meta_providers:
return sorted(meta_providers.keys())
@@ -687,7 +687,7 @@ def _create_cmdlet_cli():
# Initialize debug logging if enabled
if config:
from helper.logger import set_debug
from SYS.logger import set_debug
debug_enabled = config.get("debug", False)
set_debug(debug_enabled)
@@ -772,14 +772,14 @@ def _create_cmdlet_cli():
try:
if config:
from helper.logger import set_debug, debug
from SYS.logger import set_debug, debug
debug_enabled = config.get("debug", False)
set_debug(debug_enabled)
if debug_enabled:
debug("✓ Debug logging enabled")
try:
from helper.hydrus import get_client
from API.HydrusNetwork import get_client
# get_client(config) # Pre-acquire and cache session key
# debug("✓ Hydrus session key acquired")
except RuntimeError:
@@ -859,7 +859,7 @@ def _create_cmdlet_cli():
except Exception as e:
if config:
from helper.logger import debug # local import to avoid failing when debug disabled
from SYS.logger import debug # local import to avoid failing when debug disabled
debug(f"⚠ Could not check service availability: {e}")
except Exception:
pass # Silently ignore if config loading fails
@@ -1263,16 +1263,24 @@ def _execute_pipeline(tokens: list):
if table_for_stage:
ctx.set_current_stage_table(table_for_stage)
# Special check for YouTube search results BEFORE command expansion
# If we are selecting from a YouTube search, we want to force auto-piping to .pipe
# Special check for table-specific behavior BEFORE command expansion
# If we are selecting from a YouTube or Soulseek search, we want to force auto-piping to .pipe
# instead of trying to expand to a command (which search-file doesn't support well for re-execution)
source_cmd = ctx.get_current_stage_table_source_command()
source_args = ctx.get_current_stage_table_source_args()
if source_cmd == 'search-file' and source_args and 'youtube' in source_args:
# Check table property
current_table = ctx.get_current_stage_table()
table_type = current_table.table if current_table and hasattr(current_table, 'table') else None
# Logic based on table type
if table_type == 'youtube' or table_type == 'soulseek':
# Force fallback to item-based selection so we can auto-pipe
command_expanded = False
# Skip the command expansion block below
elif source_cmd == 'search-file' and source_args and 'youtube' in source_args:
# Legacy check for youtube
command_expanded = False
else:
# Try command-based expansion first if we have source command info
command_expanded = False
@@ -1335,16 +1343,29 @@ def _execute_pipeline(tokens: list):
log_msg = f"Applied @N selection {' | '.join(selection_parts)}"
worker_manager.log_step(pipeline_session.worker_id, log_msg) if pipeline_session and worker_manager else None
# Special case for youtube search results in fallback mode: auto-pipe to .pipe
# Special case for table-specific auto-piping
# This handles the case where @N is the ONLY stage (e.g. user typed "@1")
# In this case, stages is [['@1']], but we are in the fallback block because command_expanded is False
# We need to check if the source was youtube search
# Check table type
current_table = ctx.get_current_stage_table()
if not current_table:
current_table = ctx.get_last_result_table()
table_type = current_table.table if current_table and hasattr(current_table, 'table') else None
source_cmd = ctx.get_last_result_table_source_command()
source_args = ctx.get_last_result_table_source_args()
if source_cmd == 'search-file' and source_args and 'youtube' in source_args:
# Only auto-pipe if no other stages follow (stages is empty because we popped the selection)
if not stages:
if not stages:
if table_type == 'youtube':
print(f"Auto-piping YouTube selection to .pipe")
stages.append(['.pipe'])
elif table_type == 'soulseek':
print(f"Auto-piping Soulseek selection to download-provider")
stages.append(['download-provider'])
elif source_cmd == 'search-file' and source_args and 'youtube' in source_args:
# Legacy check
print(f"Auto-piping YouTube selection to .pipe")
stages.append(['.pipe'])
@@ -1606,8 +1627,30 @@ def _execute_pipeline(tokens: list):
else:
if cmd_name in selectable_commands:
table = ResultTable(table_title)
# Detect table type from items
first_table = None
consistent = True
for emitted in pipeline_ctx.emits:
table.add_result(emitted)
# Check for table property
item_table = None
if isinstance(emitted, dict):
item_table = emitted.get('table')
else:
item_table = getattr(emitted, 'table', None)
if item_table:
if first_table is None:
first_table = item_table
elif first_table != item_table:
consistent = False
if consistent and first_table:
table.set_table(first_table)
table.set_source_command(cmd_name, stage_args)
ctx.set_last_result_table(table, pipeline_ctx.emits)
elif cmd_name in display_only_commands:
@@ -1772,7 +1815,7 @@ def _execute_cmdlet(cmd_name: str, args: list):
# Ensure native commands (cmdnats) are loaded
try:
from helper.cmdlet_catalog import ensure_registry_loaded as _ensure_registry_loaded
from cmdlets.catalog import ensure_registry_loaded as _ensure_registry_loaded
_ensure_registry_loaded()
except Exception:
pass
@@ -1781,7 +1824,7 @@ def _execute_cmdlet(cmd_name: str, args: list):
cmd_fn = REGISTRY.get(cmd_name)
if not cmd_fn:
# Attempt lazy import of the module and retry
from helper.cmdlet_catalog import import_cmd_module as _catalog_import
from cmdlets.catalog import import_cmd_module as _catalog_import
try:
mod = _catalog_import(cmd_name)
data = getattr(mod, "CMDLET", None) if mod else None