npcsh 0.3.32__py3-none-any.whl → 1.0.0__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.
Files changed (93) hide show
  1. npcsh/_state.py +942 -0
  2. npcsh/alicanto.py +1074 -0
  3. npcsh/guac.py +785 -0
  4. npcsh/mcp_helpers.py +357 -0
  5. npcsh/mcp_npcsh.py +822 -0
  6. npcsh/mcp_server.py +184 -0
  7. npcsh/npc.py +218 -0
  8. npcsh/npcsh.py +1161 -0
  9. npcsh/plonk.py +387 -269
  10. npcsh/pti.py +234 -0
  11. npcsh/routes.py +958 -0
  12. npcsh/spool.py +315 -0
  13. npcsh/wander.py +550 -0
  14. npcsh/yap.py +573 -0
  15. npcsh-1.0.0.dist-info/METADATA +596 -0
  16. npcsh-1.0.0.dist-info/RECORD +21 -0
  17. {npcsh-0.3.32.dist-info → npcsh-1.0.0.dist-info}/WHEEL +1 -1
  18. npcsh-1.0.0.dist-info/entry_points.txt +9 -0
  19. {npcsh-0.3.32.dist-info → npcsh-1.0.0.dist-info}/licenses/LICENSE +1 -1
  20. npcsh/audio.py +0 -569
  21. npcsh/audio_gen.py +0 -1
  22. npcsh/cli.py +0 -543
  23. npcsh/command_history.py +0 -566
  24. npcsh/conversation.py +0 -54
  25. npcsh/data_models.py +0 -46
  26. npcsh/dataframes.py +0 -171
  27. npcsh/embeddings.py +0 -168
  28. npcsh/helpers.py +0 -646
  29. npcsh/image.py +0 -298
  30. npcsh/image_gen.py +0 -79
  31. npcsh/knowledge_graph.py +0 -1006
  32. npcsh/llm_funcs.py +0 -2195
  33. npcsh/load_data.py +0 -83
  34. npcsh/main.py +0 -5
  35. npcsh/model_runner.py +0 -189
  36. npcsh/npc_compiler.py +0 -2879
  37. npcsh/npc_sysenv.py +0 -388
  38. npcsh/npc_team/assembly_lines/test_pipeline.py +0 -181
  39. npcsh/npc_team/corca.npc +0 -13
  40. npcsh/npc_team/foreman.npc +0 -7
  41. npcsh/npc_team/npcsh.ctx +0 -11
  42. npcsh/npc_team/sibiji.npc +0 -4
  43. npcsh/npc_team/templates/analytics/celona.npc +0 -0
  44. npcsh/npc_team/templates/hr_support/raone.npc +0 -0
  45. npcsh/npc_team/templates/humanities/eriane.npc +0 -4
  46. npcsh/npc_team/templates/it_support/lineru.npc +0 -0
  47. npcsh/npc_team/templates/marketing/slean.npc +0 -4
  48. npcsh/npc_team/templates/philosophy/maurawa.npc +0 -0
  49. npcsh/npc_team/templates/sales/turnic.npc +0 -4
  50. npcsh/npc_team/templates/software/welxor.npc +0 -0
  51. npcsh/npc_team/tools/bash_executer.tool +0 -32
  52. npcsh/npc_team/tools/calculator.tool +0 -8
  53. npcsh/npc_team/tools/code_executor.tool +0 -16
  54. npcsh/npc_team/tools/generic_search.tool +0 -27
  55. npcsh/npc_team/tools/image_generation.tool +0 -25
  56. npcsh/npc_team/tools/local_search.tool +0 -149
  57. npcsh/npc_team/tools/npcsh_executor.tool +0 -9
  58. npcsh/npc_team/tools/screen_cap.tool +0 -27
  59. npcsh/npc_team/tools/sql_executor.tool +0 -26
  60. npcsh/response.py +0 -272
  61. npcsh/search.py +0 -252
  62. npcsh/serve.py +0 -1467
  63. npcsh/shell.py +0 -524
  64. npcsh/shell_helpers.py +0 -3919
  65. npcsh/stream.py +0 -233
  66. npcsh/video.py +0 -52
  67. npcsh/video_gen.py +0 -69
  68. npcsh-0.3.32.data/data/npcsh/npc_team/bash_executer.tool +0 -32
  69. npcsh-0.3.32.data/data/npcsh/npc_team/calculator.tool +0 -8
  70. npcsh-0.3.32.data/data/npcsh/npc_team/celona.npc +0 -0
  71. npcsh-0.3.32.data/data/npcsh/npc_team/code_executor.tool +0 -16
  72. npcsh-0.3.32.data/data/npcsh/npc_team/corca.npc +0 -13
  73. npcsh-0.3.32.data/data/npcsh/npc_team/eriane.npc +0 -4
  74. npcsh-0.3.32.data/data/npcsh/npc_team/foreman.npc +0 -7
  75. npcsh-0.3.32.data/data/npcsh/npc_team/generic_search.tool +0 -27
  76. npcsh-0.3.32.data/data/npcsh/npc_team/image_generation.tool +0 -25
  77. npcsh-0.3.32.data/data/npcsh/npc_team/lineru.npc +0 -0
  78. npcsh-0.3.32.data/data/npcsh/npc_team/local_search.tool +0 -149
  79. npcsh-0.3.32.data/data/npcsh/npc_team/maurawa.npc +0 -0
  80. npcsh-0.3.32.data/data/npcsh/npc_team/npcsh.ctx +0 -11
  81. npcsh-0.3.32.data/data/npcsh/npc_team/npcsh_executor.tool +0 -9
  82. npcsh-0.3.32.data/data/npcsh/npc_team/raone.npc +0 -0
  83. npcsh-0.3.32.data/data/npcsh/npc_team/screen_cap.tool +0 -27
  84. npcsh-0.3.32.data/data/npcsh/npc_team/sibiji.npc +0 -4
  85. npcsh-0.3.32.data/data/npcsh/npc_team/slean.npc +0 -4
  86. npcsh-0.3.32.data/data/npcsh/npc_team/sql_executor.tool +0 -26
  87. npcsh-0.3.32.data/data/npcsh/npc_team/test_pipeline.py +0 -181
  88. npcsh-0.3.32.data/data/npcsh/npc_team/turnic.npc +0 -4
  89. npcsh-0.3.32.data/data/npcsh/npc_team/welxor.npc +0 -0
  90. npcsh-0.3.32.dist-info/METADATA +0 -779
  91. npcsh-0.3.32.dist-info/RECORD +0 -78
  92. npcsh-0.3.32.dist-info/entry_points.txt +0 -3
  93. {npcsh-0.3.32.dist-info → npcsh-1.0.0.dist-info}/top_level.txt +0 -0
npcsh/mcp_server.py ADDED
@@ -0,0 +1,184 @@
1
+ #!/usr/bin/env python
2
+ """
3
+ Enhanced MCP server that incorporates functionality from npcpy.routes,
4
+ npcpy.llm_funcs, and npcpy.npc_compiler as tools.
5
+ """
6
+
7
+ import os
8
+ import subprocess
9
+ import json
10
+ import asyncio
11
+
12
+ from typing import Optional, Dict, Any, List, Union, Callable
13
+ # MCP imports
14
+ from mcp.server.fastmcp import FastMCP
15
+ import importlib
16
+ # npcpy imports
17
+ from npcpy.gen.response import get_litellm_response
18
+
19
+
20
+ import os
21
+ import subprocess
22
+ import json
23
+ import asyncio
24
+ try:
25
+ import inspect
26
+ except:
27
+ pass
28
+ from typing import Optional, Dict, Any, List, Union, Callable, get_type_hints
29
+ # Add these imports to the top of your file
30
+ from functools import wraps
31
+ # Initialize the MCP server
32
+ mcp = FastMCP("npcpy_enhanced")
33
+
34
+ # Define the default workspace
35
+ DEFAULT_WORKSPACE = os.path.join(os.getcwd(), "workspace")
36
+ os.makedirs(DEFAULT_WORKSPACE, exist_ok=True)
37
+
38
+ # ==================== SYSTEM TOOLS ====================
39
+
40
+ @mcp.tool()
41
+ async def run_server_command(command: str) -> str:
42
+ """
43
+ Run a terminal command in the workspace.
44
+
45
+ Args:
46
+ command: The shell command to run
47
+
48
+ Returns:
49
+ The command output or an error message.
50
+ """
51
+ try:
52
+ result = subprocess.run(
53
+ command,
54
+ cwd=DEFAULT_WORKSPACE,
55
+ shell=True,
56
+ capture_output=True,
57
+ text=True
58
+ )
59
+ return result.stdout or result.stderr
60
+ except Exception as e:
61
+ return str(e)
62
+ def make_async_wrapper(func: Callable) -> Callable:
63
+ """Create an async wrapper for sync functions that fixes schema validation issues."""
64
+
65
+ @wraps(func)
66
+ async def async_wrapper(*args, **kwargs):
67
+ # Direct parameter dict case (most common failure)
68
+ if len(args) == 1 and isinstance(args[0], dict):
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)
81
+
82
+ # Normal function call or other cases
83
+ if asyncio.iscoroutinefunction(func):
84
+ return await func(*args, **kwargs)
85
+ else:
86
+ return await asyncio.to_thread(func, *args, **kwargs)
87
+
88
+ # Preserve function metadata
89
+ async_wrapper.__name__ = func.__name__
90
+ async_wrapper.__doc__ = func.__doc__
91
+ async_wrapper.__annotations__ = func.__annotations__
92
+
93
+ return async_wrapper
94
+
95
+ # Update your register_module_tools function to use this improved wrapper
96
+ def register_module_tools(module_name: str) -> None:
97
+ """
98
+ Register all suitable functions from a module as MCP tools with improved argument handling.
99
+ """
100
+ functions = load_module_functions(module_name)
101
+ for func in functions:
102
+ # Skip functions that don't have docstrings
103
+ if not func.__doc__:
104
+ print(f"Skipping function without docstring: {func.__name__}")
105
+ continue
106
+
107
+ # Create async wrapper with improved argument handling
108
+ async_func = make_async_wrapper(func)
109
+
110
+ # Register as MCP tool
111
+ try:
112
+ mcp.tool()(async_func)
113
+ print(f"Registered tool: {func.__name__}")
114
+ except Exception as e:
115
+ print(f"Failed to register {func.__name__}: {e}")
116
+ def load_module_functions(module_name: str) -> List[Callable]:
117
+ """
118
+ Dynamically load functions from a module.
119
+ """
120
+ try:
121
+ module = importlib.import_module(module_name)
122
+ # Get all callables from the module that don't start with underscore
123
+ functions = []
124
+ for name, func in inspect.getmembers(module, callable):
125
+ if not name.startswith('_'):
126
+ # Check if it's a function, not a class
127
+ if inspect.isfunction(func) or inspect.ismethod(func):
128
+ functions.append(func)
129
+ return functions
130
+ except ImportError as e:
131
+ print(f"Warning: Could not import module {module_name}: {e}")
132
+ return []
133
+
134
+ print("Loading tools from npcpy modules...")
135
+
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
+
150
+
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
+
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
+
165
+ register_module_tools("npcpy.work.plan")
166
+ register_module_tools("npcpy.work.trigger")
167
+ register_module_tools("npcpy.work.desktop")
168
+
169
+ #print("Loading functions from npcpy.command_history...")
170
+ #register_module_tools("npcpy.memory.command_history")
171
+
172
+
173
+ #print("Loading functions from npcpy.npc_sysenv...")
174
+ #register_module_tools("npcpy.llm_funcs")
175
+
176
+
177
+ # ==================== MAIN ENTRY POINT ====================
178
+
179
+ if __name__ == "__main__":
180
+ print(f"Starting enhanced NPCPY MCP server...")
181
+ print(f"Workspace: {DEFAULT_WORKSPACE}")
182
+
183
+ # Run the server
184
+ mcp.run(transport="stdio")
npcsh/npc.py ADDED
@@ -0,0 +1,218 @@
1
+ import argparse
2
+ import sys
3
+ import os
4
+ import sqlite3
5
+ import traceback
6
+ from typing import Optional
7
+
8
+ from npcsh._state import (
9
+ NPCSH_CHAT_MODEL,
10
+ NPCSH_CHAT_PROVIDER,
11
+ NPCSH_API_URL,
12
+ NPCSH_DB_PATH,
13
+ NPCSH_STREAM_OUTPUT,
14
+ )
15
+ from npcpy.npc_sysenv import (
16
+ print_and_process_stream_with_markdown,
17
+ render_markdown,
18
+ )
19
+ from npcpy.npc_compiler import NPC, Team
20
+ from npcpy.routes import router
21
+ from npcpy.llm_funcs import check_llm_command
22
+
23
+ def load_npc_by_name(npc_name: str = "sibiji", db_path: str = NPCSH_DB_PATH) -> Optional[NPC]:
24
+ if not npc_name:
25
+ npc_name = "sibiji"
26
+
27
+ project_npc_path = os.path.abspath(f"./npc_team/{npc_name}.npc")
28
+ global_npc_path = os.path.expanduser(f"~/.npcsh/npc_team/{npc_name}.npc")
29
+
30
+ chosen_path = None
31
+ if os.path.exists(project_npc_path):
32
+ chosen_path = project_npc_path
33
+ elif os.path.exists(global_npc_path):
34
+ chosen_path = global_npc_path
35
+ elif os.path.exists(f"npcs/{npc_name}.npc"):
36
+ chosen_path = f"npcs/{npc_name}.npc"
37
+
38
+ if chosen_path:
39
+ try:
40
+ db_conn = sqlite3.connect(db_path)
41
+ npc = NPC(file=chosen_path, db_conn=db_conn)
42
+ return npc
43
+ except Exception as e:
44
+ print(f"Warning: Failed to load NPC '{npc_name}' from {chosen_path}: {e}", file=sys.stderr)
45
+ return None
46
+ else:
47
+ print(f"Warning: NPC file for '{npc_name}' not found in project or global paths.", file=sys.stderr)
48
+ if npc_name != "sibiji":
49
+ return load_npc_by_name("sibiji", db_path)
50
+ return None
51
+
52
+ def main():
53
+ parser = argparse.ArgumentParser(
54
+ description="NPC Command Line Utilities. Call a command or provide a prompt for the default NPC.",
55
+ usage="npc <command> [command_args...] | <prompt> [--npc NAME] [--model MODEL] [--provider PROV]"
56
+ )
57
+ parser.add_argument(
58
+ "--model", "-m", help="LLM model to use (overrides NPC/defaults)", type=str, default=None
59
+ )
60
+ parser.add_argument(
61
+ "--provider", "-pr", help="LLM provider to use (overrides NPC/defaults)", type=str, default=None
62
+ )
63
+ parser.add_argument(
64
+ "-n", "--npc", help="Name of the NPC to use (default: sibiji)", type=str, default="sibiji"
65
+ )
66
+
67
+ # No subparsers setup at first - we'll conditionally create them
68
+
69
+ # First, get any arguments without parsing commands
70
+ args, all_args = parser.parse_known_args()
71
+ global_model = args.model
72
+ global_provider = args.provider
73
+
74
+ # Check if the first argument is a known command
75
+ is_valid_command = False
76
+ command_name = None
77
+ if all_args and all_args[0] in router.get_commands():
78
+ is_valid_command = True
79
+ command_name = all_args[0]
80
+ all_args = all_args[1:] # Remove the command from arguments
81
+
82
+ # Only set up subparsers if we have a valid command
83
+ if is_valid_command:
84
+ subparsers = parser.add_subparsers(dest="command", title="Available Commands",
85
+ help="Run 'npc <command> --help' for command-specific help")
86
+
87
+ for cmd_name, help_text in router.help_info.items():
88
+ if router.shell_only.get(cmd_name, False):
89
+ continue
90
+
91
+ cmd_parser = subparsers.add_parser(cmd_name, help=help_text, add_help=False)
92
+ cmd_parser.add_argument('command_args', nargs=argparse.REMAINDER,
93
+ help='Arguments passed directly to the command handler')
94
+
95
+ # Re-parse with command subparsers
96
+ args = parser.parse_args([command_name] + all_args)
97
+ command_args = args.command_args if hasattr(args, 'command_args') else []
98
+ unknown_args = []
99
+ else:
100
+ # Treat all arguments as a prompt
101
+ args.command = None
102
+ command_args = []
103
+ unknown_args = all_args
104
+
105
+ if args.model is None:
106
+ args.model = global_model
107
+ if args.provider is None:
108
+ args.provider = global_provider
109
+ # --- END OF FIX ---
110
+ npc_instance = load_npc_by_name(args.npc, NPCSH_DB_PATH)
111
+
112
+ effective_model = args.model or NPCSH_CHAT_MODEL
113
+ effective_provider = args.provider or NPCSH_CHAT_PROVIDER
114
+
115
+
116
+
117
+ extras = {}
118
+
119
+ # Process command args if we have a valid command
120
+ if is_valid_command:
121
+ # Parse command args properly
122
+ if command_args:
123
+ i = 0
124
+ while i < len(command_args):
125
+ arg = command_args[i]
126
+ if arg.startswith("--"):
127
+ param = arg[2:] # Remove --
128
+ if "=" in param:
129
+ param_name, param_value = param.split("=", 1)
130
+ extras[param_name] = param_value
131
+ i += 1
132
+ elif i + 1 < len(command_args) and not command_args[i+1].startswith("--"):
133
+ extras[param] = command_args[i+1]
134
+ i += 2
135
+ else:
136
+ extras[param] = True
137
+ i += 1
138
+ else:
139
+ i += 1
140
+
141
+ handler = router.get_route(command_name)
142
+ if not handler:
143
+ print(f"Error: Command '{command_name}' recognized but no handler found.", file=sys.stderr)
144
+ sys.exit(1)
145
+
146
+ full_command_str = command_name
147
+ if command_args:
148
+ full_command_str += " " + " ".join(command_args)
149
+
150
+ handler_kwargs = {
151
+ "model": effective_model,
152
+ "provider": effective_provider,
153
+ "npc": npc_instance,
154
+ "api_url": NPCSH_API_URL,
155
+ "stream": NPCSH_STREAM_OUTPUT,
156
+ "messages": [],
157
+ "team": None,
158
+ "current_path": os.getcwd(),
159
+ **extras
160
+ }
161
+
162
+ try:
163
+ result = handler(command=full_command_str, **handler_kwargs)
164
+
165
+ if isinstance(result, dict):
166
+ output = result.get("output") or result.get("response")
167
+
168
+ if NPCSH_STREAM_OUTPUT and not isinstance(output, str):
169
+ print_and_process_stream_with_markdown(output, effective_model, effective_provider)
170
+ elif output is not None:
171
+ render_markdown(str(output))
172
+ elif result is not None:
173
+ render_markdown(str(result))
174
+ else:
175
+ print(f"Command '{command_name}' executed.")
176
+
177
+ except Exception as e:
178
+ print(f"Error executing command '{command_name}': {e}", file=sys.stderr)
179
+ traceback.print_exc()
180
+ sys.exit(1)
181
+ else:
182
+ # Process as a prompt
183
+ prompt = " ".join(unknown_args)
184
+
185
+ if not prompt:
186
+ # If no prompt and no command, show help
187
+ parser.print_help()
188
+ sys.exit(1)
189
+
190
+ print(f"Processing prompt: '{prompt}' with NPC: '{args.npc}'...")
191
+ try:
192
+ response_data = check_llm_command(
193
+ command=prompt,
194
+ model=effective_model,
195
+ provider=effective_provider,
196
+ npc=npc_instance,
197
+ stream=NPCSH_STREAM_OUTPUT,
198
+ messages=[],
199
+ team=None,
200
+ api_url=NPCSH_API_URL,
201
+ )
202
+
203
+ if isinstance(response_data, dict):
204
+ output = response_data.get("output")
205
+ if NPCSH_STREAM_OUTPUT and hasattr(output, '__iter__') and not isinstance(output, (str, bytes, dict, list)):
206
+ print_and_process_stream_with_markdown(output, effective_model, effective_provider)
207
+ elif output is not None:
208
+ render_markdown(str(output))
209
+ elif response_data is not None:
210
+ render_markdown(str(response_data))
211
+
212
+ except Exception as e:
213
+ print(f"Error processing prompt: {e}", file=sys.stderr)
214
+ traceback.print_exc()
215
+ sys.exit(1)
216
+
217
+ if __name__ == "__main__":
218
+ main()