npcsh 1.0.16__py3-none-any.whl → 1.0.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.
- npcsh/_state.py +1541 -78
- npcsh/corca.py +709 -0
- npcsh/guac.py +1433 -596
- npcsh/mcp_server.py +64 -60
- npcsh/npc.py +5 -4
- npcsh/npcsh.py +27 -1334
- npcsh/pti.py +195 -215
- npcsh/routes.py +99 -26
- npcsh/spool.py +138 -144
- npcsh-1.0.18.dist-info/METADATA +483 -0
- npcsh-1.0.18.dist-info/RECORD +21 -0
- {npcsh-1.0.16.dist-info → npcsh-1.0.18.dist-info}/entry_points.txt +1 -1
- npcsh/mcp_npcsh.py +0 -822
- npcsh-1.0.16.dist-info/METADATA +0 -825
- npcsh-1.0.16.dist-info/RECORD +0 -21
- {npcsh-1.0.16.dist-info → npcsh-1.0.18.dist-info}/WHEEL +0 -0
- {npcsh-1.0.16.dist-info → npcsh-1.0.18.dist-info}/licenses/LICENSE +0 -0
- {npcsh-1.0.16.dist-info → npcsh-1.0.18.dist-info}/top_level.txt +0 -0
npcsh/mcp_server.py
CHANGED
|
@@ -14,7 +14,6 @@ from typing import Optional, Dict, Any, List, Union, Callable
|
|
|
14
14
|
from mcp.server.fastmcp import FastMCP
|
|
15
15
|
import importlib
|
|
16
16
|
# npcpy imports
|
|
17
|
-
from npcpy.gen.response import get_litellm_response
|
|
18
17
|
|
|
19
18
|
|
|
20
19
|
import os
|
|
@@ -28,15 +27,27 @@ except:
|
|
|
28
27
|
from typing import Optional, Dict, Any, List, Union, Callable, get_type_hints
|
|
29
28
|
# Add these imports to the top of your file
|
|
30
29
|
from functools import wraps
|
|
30
|
+
import sys
|
|
31
|
+
|
|
32
|
+
from npcpy.llm_funcs import generate_group_candidates, abstract, extract_facts, zoom_in, execute_llm_command, gen_image
|
|
33
|
+
from npcpy.memory.search import search_similar_texts, execute_search_command, execute_rag_command, answer_with_rag, execute_brainblast_command
|
|
34
|
+
from npcpy.data.load import load_file_contents
|
|
35
|
+
from npcpy.memory.command_history import CommandHistory
|
|
36
|
+
from npcpy.data.image import capture_screenshot
|
|
37
|
+
from npcpy.data.web import search_web
|
|
38
|
+
|
|
39
|
+
from npcsh._state import NPCSH_DB_PATH
|
|
40
|
+
|
|
41
|
+
command_history = CommandHistory(db=NPCSH_DB_PATH)
|
|
42
|
+
|
|
31
43
|
# Initialize the MCP server
|
|
32
|
-
mcp = FastMCP("
|
|
44
|
+
mcp = FastMCP("npcsh_mcp")
|
|
33
45
|
|
|
34
46
|
# Define the default workspace
|
|
35
47
|
DEFAULT_WORKSPACE = os.path.join(os.getcwd(), "workspace")
|
|
36
48
|
os.makedirs(DEFAULT_WORKSPACE, exist_ok=True)
|
|
37
49
|
|
|
38
50
|
# ==================== SYSTEM TOOLS ====================
|
|
39
|
-
|
|
40
51
|
@mcp.tool()
|
|
41
52
|
async def run_server_command(command: str) -> str:
|
|
42
53
|
"""
|
|
@@ -54,45 +65,44 @@ async def run_server_command(command: str) -> str:
|
|
|
54
65
|
cwd=DEFAULT_WORKSPACE,
|
|
55
66
|
shell=True,
|
|
56
67
|
capture_output=True,
|
|
57
|
-
text=True
|
|
68
|
+
text=True,
|
|
69
|
+
timeout=30 # Add timeout to prevent hanging
|
|
58
70
|
)
|
|
59
|
-
return result.stdout or result.stderr
|
|
71
|
+
return result.stdout or result.stderr or "Command completed with no output"
|
|
72
|
+
except subprocess.TimeoutExpired:
|
|
73
|
+
return "Command timed out after 30 seconds"
|
|
60
74
|
except Exception as e:
|
|
61
75
|
return str(e)
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
|
|
62
79
|
def make_async_wrapper(func: Callable) -> Callable:
|
|
63
|
-
"""Create an async wrapper for sync functions
|
|
80
|
+
"""Create an async wrapper for sync functions."""
|
|
64
81
|
|
|
65
82
|
@wraps(func)
|
|
66
|
-
async def async_wrapper(
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
params = args[0]
|
|
70
|
-
|
|
71
|
-
# Fix for search_web - add required kwargs parameter
|
|
72
|
-
if "kwargs" not in params:
|
|
73
|
-
# Create a new dict with the kwargs parameter added
|
|
74
|
-
params = {**params, "kwargs": ""}
|
|
75
|
-
|
|
76
|
-
# Call the function with the parameters
|
|
77
|
-
if asyncio.iscoroutinefunction(func):
|
|
78
|
-
return await func(**params)
|
|
79
|
-
else:
|
|
80
|
-
return await asyncio.to_thread(func, **params)
|
|
83
|
+
async def async_wrapper(**kwargs):
|
|
84
|
+
func_name = func.__name__
|
|
85
|
+
print(f"MCP SERVER DEBUG: {func_name} called with kwargs={kwargs}", flush=True)
|
|
81
86
|
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
+
try:
|
|
88
|
+
result = func(**kwargs)
|
|
89
|
+
print(f"MCP SERVER DEBUG: {func_name} returned type={type(result)}, result={result[:500] if isinstance(result, str) else result}", flush=True)
|
|
90
|
+
return result
|
|
91
|
+
|
|
92
|
+
except Exception as e:
|
|
93
|
+
print(f"MCP SERVER DEBUG: {func_name} exception: {e}", flush=True)
|
|
94
|
+
import traceback
|
|
95
|
+
traceback.print_exc()
|
|
96
|
+
return f"Error in {func_name}: {e}"
|
|
87
97
|
|
|
88
|
-
# Preserve function metadata
|
|
89
98
|
async_wrapper.__name__ = func.__name__
|
|
90
99
|
async_wrapper.__doc__ = func.__doc__
|
|
91
100
|
async_wrapper.__annotations__ = func.__annotations__
|
|
92
101
|
|
|
93
102
|
return async_wrapper
|
|
94
103
|
|
|
95
|
-
|
|
104
|
+
|
|
105
|
+
|
|
96
106
|
def register_module_tools(module_name: str) -> None:
|
|
97
107
|
"""
|
|
98
108
|
Register all suitable functions from a module as MCP tools with improved argument handling.
|
|
@@ -133,45 +143,39 @@ def load_module_functions(module_name: str) -> List[Callable]:
|
|
|
133
143
|
|
|
134
144
|
print("Loading tools from npcpy modules...")
|
|
135
145
|
|
|
136
|
-
# Load modules from npcpy.routes
|
|
137
|
-
try:
|
|
138
|
-
from npcpy.routes import routes
|
|
139
|
-
for route_name, route_func in routes.items():
|
|
140
|
-
if callable(route_func):
|
|
141
|
-
async_func = make_async_wrapper(route_func)
|
|
142
|
-
try:
|
|
143
|
-
mcp.tool()(async_func)
|
|
144
|
-
print(f"Registered route: {route_name}")
|
|
145
|
-
except Exception as e:
|
|
146
|
-
print(f"Failed to register route {route_name}: {e}")
|
|
147
|
-
except ImportError as e:
|
|
148
|
-
print(f"Warning: Could not import routes: {e}")
|
|
149
146
|
|
|
150
147
|
|
|
151
|
-
# Load npc_compiler functions
|
|
152
|
-
print("Loading functions from npcpy.npc_compiler...")
|
|
153
|
-
try:
|
|
154
|
-
import importlib.util
|
|
155
|
-
if importlib.util.find_spec("npcpy.npc_compiler"):
|
|
156
|
-
register_module_tools("npcpy.npc_compiler")
|
|
157
|
-
except ImportError:
|
|
158
|
-
print("npcpy.npc_compiler not found, skipping...")
|
|
159
148
|
|
|
160
|
-
# Load npc_sysenv functions
|
|
161
|
-
#print("Loading functions from npcpy.npc_sysenv...")
|
|
162
|
-
#register_module_tools("npcpy.npc_sysenv")
|
|
163
|
-
register_module_tools("npcpy.memory.search")
|
|
164
149
|
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
150
|
+
def register_selected_npcpy_tools():
|
|
151
|
+
tools = [generate_group_candidates,
|
|
152
|
+
abstract,
|
|
153
|
+
extract_facts,
|
|
154
|
+
zoom_in,
|
|
155
|
+
execute_llm_command,
|
|
156
|
+
gen_image,
|
|
157
|
+
load_file_contents,
|
|
158
|
+
capture_screenshot,
|
|
159
|
+
search_web, ]
|
|
168
160
|
|
|
169
|
-
|
|
170
|
-
#
|
|
161
|
+
for func in tools:
|
|
162
|
+
# Ensure a docstring exists for schema generation
|
|
163
|
+
if not (getattr(func, "__doc__", None) and func.__doc__.strip()):
|
|
164
|
+
fallback_doc = f"Tool wrapper for {func.__name__}."
|
|
165
|
+
try:
|
|
166
|
+
func.__doc__ = fallback_doc
|
|
167
|
+
except Exception:
|
|
168
|
+
pass # Some builtins may not allow setting __doc__
|
|
169
|
+
|
|
170
|
+
try:
|
|
171
|
+
async_func = make_async_wrapper(func)
|
|
172
|
+
mcp.tool()(async_func)
|
|
173
|
+
print(f"Registered npcpy tool: {func.__name__}")
|
|
174
|
+
except Exception as e:
|
|
175
|
+
print(f"Failed to register npcpy tool {func.__name__}: {e}")
|
|
176
|
+
register_selected_npcpy_tools()
|
|
171
177
|
|
|
172
178
|
|
|
173
|
-
#print("Loading functions from npcpy.npc_sysenv...")
|
|
174
|
-
#register_module_tools("npcpy.llm_funcs")
|
|
175
179
|
|
|
176
180
|
|
|
177
181
|
# ==================== MAIN ENTRY POINT ====================
|
npcsh/npc.py
CHANGED
|
@@ -22,11 +22,10 @@ from npcpy.llm_funcs import check_llm_command
|
|
|
22
22
|
from sqlalchemy import create_engine
|
|
23
23
|
|
|
24
24
|
# Import the key functions from npcsh
|
|
25
|
-
from npcsh.
|
|
25
|
+
from npcsh._state import (
|
|
26
26
|
setup_shell,
|
|
27
27
|
execute_slash_command,
|
|
28
28
|
execute_command,
|
|
29
|
-
process_pipeline_command,
|
|
30
29
|
)
|
|
31
30
|
|
|
32
31
|
def load_npc_by_name(npc_name: str = "sibiji", db_path: str = NPCSH_DB_PATH) -> Optional[NPC]:
|
|
@@ -60,6 +59,8 @@ def load_npc_by_name(npc_name: str = "sibiji", db_path: str = NPCSH_DB_PATH) ->
|
|
|
60
59
|
return None
|
|
61
60
|
|
|
62
61
|
def main():
|
|
62
|
+
from npcsh.routes import router
|
|
63
|
+
|
|
63
64
|
parser = argparse.ArgumentParser(
|
|
64
65
|
description="NPC Command Line Utilities. Call a command or provide a prompt for the default NPC.",
|
|
65
66
|
usage="npc <command> [command_args...] | <prompt> [--npc NAME] [--model MODEL] [--provider PROV]"
|
|
@@ -173,7 +174,6 @@ def main():
|
|
|
173
174
|
npc_instance.model = effective_model
|
|
174
175
|
if args.provider:
|
|
175
176
|
npc_instance.provider = effective_provider
|
|
176
|
-
|
|
177
177
|
try:
|
|
178
178
|
if is_valid_command:
|
|
179
179
|
# Handle slash command using npcsh's execute_slash_command
|
|
@@ -187,7 +187,8 @@ def main():
|
|
|
187
187
|
full_command_str,
|
|
188
188
|
stdin_input=None,
|
|
189
189
|
state=shell_state,
|
|
190
|
-
stream=NPCSH_STREAM_OUTPUT
|
|
190
|
+
stream=NPCSH_STREAM_OUTPUT,
|
|
191
|
+
router = router
|
|
191
192
|
)
|
|
192
193
|
|
|
193
194
|
# Process and display the result
|