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/data/web.py +0 -1
- npcpy/gen/response.py +160 -2
- npcpy/memory/command_history.py +14 -5
- npcpy/ml_funcs.py +61 -16
- npcpy/npc_array.py +149 -1
- npcpy/npc_compiler.py +23 -12
- npcpy/npc_sysenv.py +183 -8
- npcpy/serve.py +846 -73
- {npcpy-1.3.16.dist-info → npcpy-1.3.18.dist-info}/METADATA +1 -1
- {npcpy-1.3.16.dist-info → npcpy-1.3.18.dist-info}/RECORD +13 -13
- {npcpy-1.3.16.dist-info → npcpy-1.3.18.dist-info}/WHEEL +1 -1
- {npcpy-1.3.16.dist-info → npcpy-1.3.18.dist-info}/licenses/LICENSE +0 -0
- {npcpy-1.3.16.dist-info → npcpy-1.3.18.dist-info}/top_level.txt +0 -0
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.
|
|
314
|
-
|
|
437
|
+
os.path.join(models_dir, 'gguf'),
|
|
438
|
+
models_dir,
|
|
315
439
|
os.path.expanduser('~/models'),
|
|
316
|
-
os.path.
|
|
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 .
|
|
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 =
|
|
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:
|