npcpy 1.3.16__py3-none-any.whl → 1.3.18__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.
npcpy/npc_sysenv.py CHANGED
@@ -15,6 +15,129 @@ import json
15
15
 
16
16
  import requests
17
17
  ON_WINDOWS = platform.system() == "Windows"
18
+ ON_MACOS = platform.system() == "Darwin"
19
+
20
+
21
+ # ==================== XDG/Platform-Specific Paths ====================
22
+
23
+ def get_data_dir() -> str:
24
+ """
25
+ Get the platform-specific data directory for npcsh.
26
+
27
+ Returns:
28
+ - Linux: $XDG_DATA_HOME/npcsh or ~/.local/share/npcsh
29
+ - macOS: ~/Library/Application Support/npcsh
30
+ - Windows: %LOCALAPPDATA%/npcsh or ~/AppData/Local/npcsh
31
+
32
+ Falls back to ~/.npcsh for backwards compatibility if the new location
33
+ doesn't exist but the old one does.
34
+ """
35
+ if ON_WINDOWS:
36
+ base = os.environ.get('LOCALAPPDATA', os.path.expanduser('~/AppData/Local'))
37
+ new_path = os.path.join(base, 'npcsh')
38
+ elif ON_MACOS:
39
+ new_path = os.path.expanduser('~/Library/Application Support/npcsh')
40
+ else:
41
+ # Linux/Unix - use XDG Base Directory Specification
42
+ xdg_data = os.environ.get('XDG_DATA_HOME', os.path.expanduser('~/.local/share'))
43
+ new_path = os.path.join(xdg_data, 'npcsh')
44
+
45
+ # Backwards compatibility: if old path exists but new doesn't, use old
46
+ old_path = os.path.expanduser('~/.npcsh')
47
+ if os.path.exists(old_path) and not os.path.exists(new_path):
48
+ return old_path
49
+
50
+ return new_path
51
+
52
+
53
+ def get_config_dir() -> str:
54
+ """
55
+ Get the platform-specific config directory for npcsh.
56
+
57
+ Returns:
58
+ - Linux: $XDG_CONFIG_HOME/npcsh or ~/.config/npcsh
59
+ - macOS: ~/Library/Application Support/npcsh (same as data on macOS)
60
+ - Windows: %APPDATA%/npcsh or ~/AppData/Roaming/npcsh
61
+
62
+ Falls back to ~/.npcsh for backwards compatibility if the new location
63
+ doesn't exist but the old one does.
64
+ """
65
+ if ON_WINDOWS:
66
+ base = os.environ.get('APPDATA', os.path.expanduser('~/AppData/Roaming'))
67
+ new_path = os.path.join(base, 'npcsh')
68
+ elif ON_MACOS:
69
+ new_path = os.path.expanduser('~/Library/Application Support/npcsh')
70
+ else:
71
+ # Linux/Unix - use XDG Base Directory Specification
72
+ xdg_config = os.environ.get('XDG_CONFIG_HOME', os.path.expanduser('~/.config'))
73
+ new_path = os.path.join(xdg_config, 'npcsh')
74
+
75
+ # Backwards compatibility: if old path exists but new doesn't, use old
76
+ old_path = os.path.expanduser('~/.npcsh')
77
+ if os.path.exists(old_path) and not os.path.exists(new_path):
78
+ return old_path
79
+
80
+ return new_path
81
+
82
+
83
+ def get_cache_dir() -> str:
84
+ """
85
+ Get the platform-specific cache directory for npcsh.
86
+
87
+ Returns:
88
+ - Linux: $XDG_CACHE_HOME/npcsh or ~/.cache/npcsh
89
+ - macOS: ~/Library/Caches/npcsh
90
+ - Windows: %LOCALAPPDATA%/npcsh/cache
91
+ """
92
+ if ON_WINDOWS:
93
+ base = os.environ.get('LOCALAPPDATA', os.path.expanduser('~/AppData/Local'))
94
+ return os.path.join(base, 'npcsh', 'cache')
95
+ elif ON_MACOS:
96
+ return os.path.expanduser('~/Library/Caches/npcsh')
97
+ else:
98
+ xdg_cache = os.environ.get('XDG_CACHE_HOME', os.path.expanduser('~/.cache'))
99
+ return os.path.join(xdg_cache, 'npcsh')
100
+
101
+
102
+ def get_npcshrc_path() -> str:
103
+ """
104
+ Get the path to the npcshrc config file.
105
+
106
+ Returns the platform-appropriate config file path.
107
+ Falls back to ~/.npcshrc for backwards compatibility.
108
+ """
109
+ old_path = os.path.expanduser('~/.npcshrc')
110
+ if os.path.exists(old_path):
111
+ return old_path
112
+
113
+ config_dir = get_config_dir()
114
+ return os.path.join(config_dir, 'npcshrc')
115
+
116
+
117
+ def get_history_db_path() -> str:
118
+ """
119
+ Get the path to the history database.
120
+
121
+ Returns the platform-appropriate database path.
122
+ Falls back to ~/npcsh_history.db for backwards compatibility.
123
+ """
124
+ old_path = os.path.expanduser('~/npcsh_history.db')
125
+ if os.path.exists(old_path):
126
+ return old_path
127
+
128
+ data_dir = get_data_dir()
129
+ return os.path.join(data_dir, 'history.db')
130
+
131
+
132
+ def get_models_dir() -> str:
133
+ """Get the directory for storing models."""
134
+ return os.path.join(get_data_dir(), 'models')
135
+
136
+
137
+ def ensure_npcsh_dirs() -> None:
138
+ """Ensure all npcsh directories exist."""
139
+ for dir_path in [get_data_dir(), get_config_dir(), get_cache_dir(), get_models_dir()]:
140
+ os.makedirs(dir_path, exist_ok=True)
18
141
 
19
142
  try:
20
143
  if not ON_WINDOWS:
@@ -309,11 +432,13 @@ def get_locally_available_models(project_directory, airplane_mode=False):
309
432
  logging.info(f"Error loading Ollama models or timed out: {e}")
310
433
 
311
434
  # Scan for local GGUF/GGML models
435
+ models_dir = get_models_dir()
312
436
  gguf_dirs = [
313
- os.path.expanduser('~/.npcsh/models/gguf'),
314
- os.path.expanduser('~/.npcsh/models'),
437
+ os.path.join(models_dir, 'gguf'),
438
+ models_dir,
315
439
  os.path.expanduser('~/models'),
316
- os.path.expanduser('~/.cache/huggingface/hub'),
440
+ os.path.join(get_cache_dir(), 'huggingface/hub'),
441
+ os.path.expanduser('~/.cache/huggingface/hub'), # Fallback for existing installs
317
442
  ]
318
443
  env_gguf_dir = os.environ.get('NPCSH_GGUF_DIR')
319
444
  if env_gguf_dir:
@@ -358,6 +483,50 @@ def get_locally_available_models(project_directory, airplane_mode=False):
358
483
  except Exception as e:
359
484
  logging.debug(f"llama.cpp server not available: {e}")
360
485
 
486
+ # Check for MLX server (OpenAI-compatible API on port 8000)
487
+ try:
488
+ import requests
489
+ response = requests.get('http://127.0.0.1:8000/v1/models', timeout=1)
490
+ if response.ok:
491
+ data = response.json()
492
+ for model in data.get('data', []):
493
+ model_id = model.get('id', model.get('name', 'unknown'))
494
+ available_models[model_id] = "mlx"
495
+ except Exception as e:
496
+ logging.debug(f"MLX server not available: {e}")
497
+
498
+ # Also check common alternative MLX port 5000
499
+ try:
500
+ import requests
501
+ response = requests.get('http://127.0.0.1:5000/v1/models', timeout=1)
502
+ if response.ok:
503
+ data = response.json()
504
+ for model in data.get('data', []):
505
+ model_id = model.get('id', model.get('name', 'unknown'))
506
+ if model_id not in available_models: # Avoid duplicates
507
+ available_models[model_id] = "mlx"
508
+ except Exception as e:
509
+ logging.debug(f"MLX server (port 5000) not available: {e}")
510
+
511
+ # Scan for LoRA adapters (fine-tuned models with adapter_config.json)
512
+ lora_dirs = [
513
+ os.path.expanduser('~/.npcsh/models'),
514
+ ]
515
+ for scan_dir in lora_dirs:
516
+ if not os.path.isdir(scan_dir):
517
+ continue
518
+ try:
519
+ for item in os.listdir(scan_dir):
520
+ item_path = os.path.join(scan_dir, item)
521
+ if os.path.isdir(item_path):
522
+ adapter_config = os.path.join(item_path, 'adapter_config.json')
523
+ if os.path.exists(adapter_config):
524
+ # This is a LoRA adapter
525
+ available_models[item_path] = "lora"
526
+ logging.debug(f"Found LoRA adapter: {item_path}")
527
+ except Exception as e:
528
+ logging.debug(f"Error scanning LoRA directory {scan_dir}: {e}")
529
+
361
530
  return available_models
362
531
 
363
532
 
@@ -959,13 +1128,19 @@ def lookup_provider(model: str) -> str:
959
1128
  """
960
1129
  Determine the provider based on the model name.
961
1130
  Checks custom providers first, then falls back to known providers.
962
-
1131
+
963
1132
  Args:
964
1133
  model: The model name
965
-
1134
+
966
1135
  Returns:
967
1136
  The provider name or None if not found
968
1137
  """
1138
+ # Check if model is a LoRA adapter path
1139
+ if model and os.path.isdir(os.path.expanduser(model)):
1140
+ adapter_config = os.path.join(os.path.expanduser(model), 'adapter_config.json')
1141
+ if os.path.exists(adapter_config):
1142
+ return "lora"
1143
+
969
1144
  custom_providers = load_custom_providers()
970
1145
 
971
1146
  for provider_name, config in custom_providers.items():
@@ -1030,13 +1205,13 @@ def lookup_provider(model: str) -> str:
1030
1205
 
1031
1206
  def load_custom_providers():
1032
1207
  """
1033
- Load custom provider configurations from .npcshrc
1034
-
1208
+ Load custom provider configurations from npcshrc config file.
1209
+
1035
1210
  Returns:
1036
1211
  dict: Custom provider configurations keyed by provider name
1037
1212
  """
1038
1213
  custom_providers = {}
1039
- npcshrc_path = os.path.expanduser("~/.npcshrc")
1214
+ npcshrc_path = get_npcshrc_path()
1040
1215
 
1041
1216
  if os.path.exists(npcshrc_path):
1042
1217
  with open(npcshrc_path, "r") as f: