npcpy 1.0.26__py3-none-any.whl → 1.2.32__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/__init__.py +0 -7
- npcpy/data/audio.py +16 -99
- npcpy/data/image.py +43 -42
- npcpy/data/load.py +83 -124
- npcpy/data/text.py +28 -28
- npcpy/data/video.py +8 -32
- npcpy/data/web.py +51 -23
- npcpy/ft/diff.py +110 -0
- npcpy/ft/ge.py +115 -0
- npcpy/ft/memory_trainer.py +171 -0
- npcpy/ft/model_ensembler.py +357 -0
- npcpy/ft/rl.py +360 -0
- npcpy/ft/sft.py +248 -0
- npcpy/ft/usft.py +128 -0
- npcpy/gen/audio_gen.py +24 -0
- npcpy/gen/embeddings.py +13 -13
- npcpy/gen/image_gen.py +262 -117
- npcpy/gen/response.py +615 -415
- npcpy/gen/video_gen.py +53 -7
- npcpy/llm_funcs.py +1869 -437
- npcpy/main.py +1 -1
- npcpy/memory/command_history.py +844 -510
- npcpy/memory/kg_vis.py +833 -0
- npcpy/memory/knowledge_graph.py +892 -1845
- npcpy/memory/memory_processor.py +81 -0
- npcpy/memory/search.py +188 -90
- npcpy/mix/debate.py +192 -3
- npcpy/npc_compiler.py +1672 -801
- npcpy/npc_sysenv.py +593 -1266
- npcpy/serve.py +3120 -0
- npcpy/sql/ai_function_tools.py +257 -0
- npcpy/sql/database_ai_adapters.py +186 -0
- npcpy/sql/database_ai_functions.py +163 -0
- npcpy/sql/model_runner.py +19 -19
- npcpy/sql/npcsql.py +706 -507
- npcpy/sql/sql_model_compiler.py +156 -0
- npcpy/tools.py +183 -0
- npcpy/work/plan.py +13 -279
- npcpy/work/trigger.py +3 -3
- npcpy-1.2.32.dist-info/METADATA +803 -0
- npcpy-1.2.32.dist-info/RECORD +54 -0
- npcpy/data/dataframes.py +0 -171
- npcpy/memory/deep_research.py +0 -125
- npcpy/memory/sleep.py +0 -557
- npcpy/modes/_state.py +0 -78
- npcpy/modes/alicanto.py +0 -1075
- npcpy/modes/guac.py +0 -785
- npcpy/modes/mcp_npcsh.py +0 -822
- npcpy/modes/npc.py +0 -213
- npcpy/modes/npcsh.py +0 -1158
- npcpy/modes/plonk.py +0 -409
- npcpy/modes/pti.py +0 -234
- npcpy/modes/serve.py +0 -1637
- npcpy/modes/spool.py +0 -312
- npcpy/modes/wander.py +0 -549
- npcpy/modes/yap.py +0 -572
- npcpy/npc_team/alicanto.npc +0 -2
- npcpy/npc_team/alicanto.png +0 -0
- npcpy/npc_team/assembly_lines/test_pipeline.py +0 -181
- npcpy/npc_team/corca.npc +0 -13
- npcpy/npc_team/foreman.npc +0 -7
- npcpy/npc_team/frederic.npc +0 -6
- npcpy/npc_team/frederic4.png +0 -0
- npcpy/npc_team/guac.png +0 -0
- npcpy/npc_team/jinxs/automator.jinx +0 -18
- npcpy/npc_team/jinxs/bash_executer.jinx +0 -31
- npcpy/npc_team/jinxs/calculator.jinx +0 -11
- npcpy/npc_team/jinxs/edit_file.jinx +0 -96
- npcpy/npc_team/jinxs/file_chat.jinx +0 -14
- npcpy/npc_team/jinxs/gui_controller.jinx +0 -28
- npcpy/npc_team/jinxs/image_generation.jinx +0 -29
- npcpy/npc_team/jinxs/internet_search.jinx +0 -30
- npcpy/npc_team/jinxs/local_search.jinx +0 -152
- npcpy/npc_team/jinxs/npcsh_executor.jinx +0 -31
- npcpy/npc_team/jinxs/python_executor.jinx +0 -8
- npcpy/npc_team/jinxs/screen_cap.jinx +0 -25
- npcpy/npc_team/jinxs/sql_executor.jinx +0 -33
- npcpy/npc_team/kadiefa.npc +0 -3
- npcpy/npc_team/kadiefa.png +0 -0
- npcpy/npc_team/npcsh.ctx +0 -9
- npcpy/npc_team/npcsh_sibiji.png +0 -0
- npcpy/npc_team/plonk.npc +0 -2
- npcpy/npc_team/plonk.png +0 -0
- npcpy/npc_team/plonkjr.npc +0 -2
- npcpy/npc_team/plonkjr.png +0 -0
- npcpy/npc_team/sibiji.npc +0 -5
- npcpy/npc_team/sibiji.png +0 -0
- npcpy/npc_team/spool.png +0 -0
- npcpy/npc_team/templates/analytics/celona.npc +0 -0
- npcpy/npc_team/templates/hr_support/raone.npc +0 -0
- npcpy/npc_team/templates/humanities/eriane.npc +0 -4
- npcpy/npc_team/templates/it_support/lineru.npc +0 -0
- npcpy/npc_team/templates/marketing/slean.npc +0 -4
- npcpy/npc_team/templates/philosophy/maurawa.npc +0 -0
- npcpy/npc_team/templates/sales/turnic.npc +0 -4
- npcpy/npc_team/templates/software/welxor.npc +0 -0
- npcpy/npc_team/yap.png +0 -0
- npcpy/routes.py +0 -958
- npcpy/work/mcp_helpers.py +0 -357
- npcpy/work/mcp_server.py +0 -194
- npcpy-1.0.26.data/data/npcpy/npc_team/alicanto.npc +0 -2
- npcpy-1.0.26.data/data/npcpy/npc_team/alicanto.png +0 -0
- npcpy-1.0.26.data/data/npcpy/npc_team/automator.jinx +0 -18
- npcpy-1.0.26.data/data/npcpy/npc_team/bash_executer.jinx +0 -31
- npcpy-1.0.26.data/data/npcpy/npc_team/calculator.jinx +0 -11
- npcpy-1.0.26.data/data/npcpy/npc_team/celona.npc +0 -0
- npcpy-1.0.26.data/data/npcpy/npc_team/corca.npc +0 -13
- npcpy-1.0.26.data/data/npcpy/npc_team/edit_file.jinx +0 -96
- npcpy-1.0.26.data/data/npcpy/npc_team/eriane.npc +0 -4
- npcpy-1.0.26.data/data/npcpy/npc_team/file_chat.jinx +0 -14
- npcpy-1.0.26.data/data/npcpy/npc_team/foreman.npc +0 -7
- npcpy-1.0.26.data/data/npcpy/npc_team/frederic.npc +0 -6
- npcpy-1.0.26.data/data/npcpy/npc_team/frederic4.png +0 -0
- npcpy-1.0.26.data/data/npcpy/npc_team/guac.png +0 -0
- npcpy-1.0.26.data/data/npcpy/npc_team/gui_controller.jinx +0 -28
- npcpy-1.0.26.data/data/npcpy/npc_team/image_generation.jinx +0 -29
- npcpy-1.0.26.data/data/npcpy/npc_team/internet_search.jinx +0 -30
- npcpy-1.0.26.data/data/npcpy/npc_team/kadiefa.npc +0 -3
- npcpy-1.0.26.data/data/npcpy/npc_team/kadiefa.png +0 -0
- npcpy-1.0.26.data/data/npcpy/npc_team/lineru.npc +0 -0
- npcpy-1.0.26.data/data/npcpy/npc_team/local_search.jinx +0 -152
- npcpy-1.0.26.data/data/npcpy/npc_team/maurawa.npc +0 -0
- npcpy-1.0.26.data/data/npcpy/npc_team/npcsh.ctx +0 -9
- npcpy-1.0.26.data/data/npcpy/npc_team/npcsh_executor.jinx +0 -31
- npcpy-1.0.26.data/data/npcpy/npc_team/npcsh_sibiji.png +0 -0
- npcpy-1.0.26.data/data/npcpy/npc_team/plonk.npc +0 -2
- npcpy-1.0.26.data/data/npcpy/npc_team/plonk.png +0 -0
- npcpy-1.0.26.data/data/npcpy/npc_team/plonkjr.npc +0 -2
- npcpy-1.0.26.data/data/npcpy/npc_team/plonkjr.png +0 -0
- npcpy-1.0.26.data/data/npcpy/npc_team/python_executor.jinx +0 -8
- npcpy-1.0.26.data/data/npcpy/npc_team/raone.npc +0 -0
- npcpy-1.0.26.data/data/npcpy/npc_team/screen_cap.jinx +0 -25
- npcpy-1.0.26.data/data/npcpy/npc_team/sibiji.npc +0 -5
- npcpy-1.0.26.data/data/npcpy/npc_team/sibiji.png +0 -0
- npcpy-1.0.26.data/data/npcpy/npc_team/slean.npc +0 -4
- npcpy-1.0.26.data/data/npcpy/npc_team/spool.png +0 -0
- npcpy-1.0.26.data/data/npcpy/npc_team/sql_executor.jinx +0 -33
- npcpy-1.0.26.data/data/npcpy/npc_team/test_pipeline.py +0 -181
- npcpy-1.0.26.data/data/npcpy/npc_team/turnic.npc +0 -4
- npcpy-1.0.26.data/data/npcpy/npc_team/welxor.npc +0 -0
- npcpy-1.0.26.data/data/npcpy/npc_team/yap.png +0 -0
- npcpy-1.0.26.dist-info/METADATA +0 -827
- npcpy-1.0.26.dist-info/RECORD +0 -139
- npcpy-1.0.26.dist-info/entry_points.txt +0 -11
- /npcpy/{modes → ft}/__init__.py +0 -0
- {npcpy-1.0.26.dist-info → npcpy-1.2.32.dist-info}/WHEEL +0 -0
- {npcpy-1.0.26.dist-info → npcpy-1.2.32.dist-info}/licenses/LICENSE +0 -0
- {npcpy-1.0.26.dist-info → npcpy-1.2.32.dist-info}/top_level.txt +0 -0
npcpy/routes.py
DELETED
|
@@ -1,958 +0,0 @@
|
|
|
1
|
-
# --- START OF FILE routes.py ---
|
|
2
|
-
|
|
3
|
-
from typing import Callable, Dict, Any, List, Optional, Union
|
|
4
|
-
import functools
|
|
5
|
-
import os
|
|
6
|
-
import traceback
|
|
7
|
-
import shlex
|
|
8
|
-
import time
|
|
9
|
-
from datetime import datetime
|
|
10
|
-
from sqlalchemy import create_engine
|
|
11
|
-
import logging
|
|
12
|
-
|
|
13
|
-
from npcpy.npc_sysenv import (
|
|
14
|
-
render_code_block, render_markdown,
|
|
15
|
-
NPCSH_VISION_MODEL, NPCSH_VISION_PROVIDER, NPCSH_API_URL,
|
|
16
|
-
NPCSH_CHAT_MODEL, NPCSH_CHAT_PROVIDER, NPCSH_STREAM_OUTPUT,
|
|
17
|
-
NPCSH_IMAGE_GEN_MODEL, NPCSH_IMAGE_GEN_PROVIDER,
|
|
18
|
-
NPCSH_EMBEDDING_MODEL, NPCSH_EMBEDDING_PROVIDER,
|
|
19
|
-
NPCSH_REASONING_MODEL, NPCSH_REASONING_PROVIDER,
|
|
20
|
-
NPCSH_SEARCH_PROVIDER,
|
|
21
|
-
)
|
|
22
|
-
from npcpy.data.load import load_file_contents
|
|
23
|
-
|
|
24
|
-
from npcpy.llm_funcs import (
|
|
25
|
-
get_llm_response,
|
|
26
|
-
gen_image,
|
|
27
|
-
gen_video,
|
|
28
|
-
)
|
|
29
|
-
from npcpy.npc_compiler import NPC, Team, Jinx
|
|
30
|
-
from npcpy.npc_compiler import initialize_npc_project
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
from npcpy.work.plan import execute_plan_command
|
|
34
|
-
from npcpy.work.trigger import execute_trigger_command
|
|
35
|
-
from npcpy.work.desktop import perform_action
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
from npcpy.memory.search import execute_rag_command, execute_search_command, execute_brainblast_command
|
|
39
|
-
from npcpy.memory.command_history import CommandHistory
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
from npcpy.memory.knowledge_graph import breathe
|
|
43
|
-
from npcpy.memory.sleep import sleep, forget
|
|
44
|
-
|
|
45
|
-
from npcpy.modes.guac import enter_guac_mode
|
|
46
|
-
from npcpy.modes.plonk import execute_plonk_command
|
|
47
|
-
from npcpy.modes.serve import start_flask_server
|
|
48
|
-
from npcpy.modes.alicanto import alicanto
|
|
49
|
-
from npcpy.modes.spool import enter_spool_mode
|
|
50
|
-
from npcpy.modes.wander import enter_wander_mode
|
|
51
|
-
from npcpy.modes.yap import enter_yap_mode
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
from npcpy.mix.debate import run_debate
|
|
56
|
-
from npcpy.data.image import capture_screenshot
|
|
57
|
-
from npcpy.npc_compiler import NPC, Team, Jinx
|
|
58
|
-
from npcpy.npc_compiler import initialize_npc_project
|
|
59
|
-
from npcpy.data.web import search_web
|
|
60
|
-
|
|
61
|
-
class CommandRouter:
|
|
62
|
-
def __init__(self):
|
|
63
|
-
self.routes = {}
|
|
64
|
-
self.help_info = {}
|
|
65
|
-
self.shell_only = {}
|
|
66
|
-
|
|
67
|
-
def route(self, command: str, help_text: str = "", shell_only: bool = False) -> Callable:
|
|
68
|
-
def wrapper(func):
|
|
69
|
-
self.routes[command] = func
|
|
70
|
-
self.help_info[command] = help_text
|
|
71
|
-
self.shell_only[command] = shell_only
|
|
72
|
-
|
|
73
|
-
@functools.wraps(func)
|
|
74
|
-
def wrapped_func(*args, **kwargs):
|
|
75
|
-
return func(*args, **kwargs)
|
|
76
|
-
|
|
77
|
-
return wrapped_func
|
|
78
|
-
return wrapper
|
|
79
|
-
|
|
80
|
-
def get_route(self, command: str) -> Optional[Callable]:
|
|
81
|
-
return self.routes.get(command)
|
|
82
|
-
|
|
83
|
-
def execute(self, command_str: str, **kwargs) -> Any:
|
|
84
|
-
command_name = command_str.split()[0].lstrip('/')
|
|
85
|
-
route_func = self.get_route(command_name)
|
|
86
|
-
if route_func:
|
|
87
|
-
return route_func(command=command_str, **kwargs)
|
|
88
|
-
return None
|
|
89
|
-
|
|
90
|
-
def get_commands(self) -> List[str]:
|
|
91
|
-
return list(self.routes.keys())
|
|
92
|
-
|
|
93
|
-
def get_help(self, command: str = None) -> Dict[str, str]:
|
|
94
|
-
if command:
|
|
95
|
-
if command in self.help_info:
|
|
96
|
-
return {command: self.help_info[command]}
|
|
97
|
-
return {}
|
|
98
|
-
return self.help_info
|
|
99
|
-
|
|
100
|
-
router = CommandRouter()
|
|
101
|
-
|
|
102
|
-
def get_help_text():
|
|
103
|
-
commands = router.get_commands()
|
|
104
|
-
help_info = router.help_info
|
|
105
|
-
shell_only = router.shell_only
|
|
106
|
-
commands.sort()
|
|
107
|
-
output = "# Available Commands\n\n"
|
|
108
|
-
for cmd in commands:
|
|
109
|
-
help_text = help_info.get(cmd, "")
|
|
110
|
-
shell_only_text = " (Shell only)" if shell_only.get(cmd, False) else ""
|
|
111
|
-
output += f"/{cmd}{shell_only_text} - {help_text}\n\n"
|
|
112
|
-
output += """
|
|
113
|
-
# Note
|
|
114
|
-
- Bash commands and programs can be executed directly (try bash first, then LLM).
|
|
115
|
-
- Use '/exit' or '/quit' to exit the current NPC mode or the npcsh shell.
|
|
116
|
-
- Jinxs defined for the current NPC or Team can also be used like commands (e.g., /screenshot).
|
|
117
|
-
"""
|
|
118
|
-
return output
|
|
119
|
-
|
|
120
|
-
def safe_get(kwargs, key, default=None):
|
|
121
|
-
return kwargs.get(key, default)
|
|
122
|
-
|
|
123
|
-
@router.route("breathe", "Condense context on a regular cadence", shell_only=True)
|
|
124
|
-
def breathe_handler(command: str, **kwargs):
|
|
125
|
-
messages = safe_get(kwargs, "messages", [])
|
|
126
|
-
npc = safe_get(kwargs, "npc")
|
|
127
|
-
try:
|
|
128
|
-
result = run_breathe_cycle(messages=messages, npc=npc, **kwargs)
|
|
129
|
-
if isinstance(result, dict): return result
|
|
130
|
-
return {"output": str(result), "messages": messages}
|
|
131
|
-
except NameError:
|
|
132
|
-
return {"output": "Breathe function (run_breathe_cycle) not available.", "messages": messages}
|
|
133
|
-
except Exception as e:
|
|
134
|
-
traceback.print_exc()
|
|
135
|
-
return {"output": f"Error during breathe: {e}", "messages": messages}
|
|
136
|
-
|
|
137
|
-
@router.route("compile", "Compile NPC profiles")
|
|
138
|
-
def compile_handler(command: str, **kwargs):
|
|
139
|
-
messages = safe_get(kwargs, "messages", [])
|
|
140
|
-
npc_team_dir = safe_get(kwargs, 'current_path', './npc_team')
|
|
141
|
-
parts = command.split()
|
|
142
|
-
npc_file_path_arg = parts[1] if len(parts) > 1 else None
|
|
143
|
-
output = ""
|
|
144
|
-
try:
|
|
145
|
-
if npc_file_path_arg:
|
|
146
|
-
npc_full_path = os.path.abspath(npc_file_path_arg)
|
|
147
|
-
if os.path.exists(npc_full_path):
|
|
148
|
-
npc = NPC(npc_full_path)
|
|
149
|
-
output = f"Compiled NPC: {npc_full_path}"
|
|
150
|
-
else:
|
|
151
|
-
output = f"Error: NPC file not found: {npc_full_path}"
|
|
152
|
-
else:
|
|
153
|
-
npc = NPC(npc_full_path)
|
|
154
|
-
|
|
155
|
-
output = f"Compiled all NPCs in directory: {npc_team_dir}"
|
|
156
|
-
except NameError:
|
|
157
|
-
output = "Compile functions (compile_npc_file, compile_team_npcs) not available."
|
|
158
|
-
except Exception as e:
|
|
159
|
-
traceback.print_exc()
|
|
160
|
-
output = f"Error compiling: {e}"
|
|
161
|
-
return {"output": output, "messages": messages, "npc": npc}
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
@router.route("flush", "Flush the last N messages", shell_only=True)
|
|
166
|
-
def flush_handler(command: str, **kwargs):
|
|
167
|
-
messages = safe_get(kwargs, "messages", [])
|
|
168
|
-
try:
|
|
169
|
-
parts = command.split()
|
|
170
|
-
n = int(parts[1]) if len(parts) > 1 else 1
|
|
171
|
-
except (ValueError, IndexError):
|
|
172
|
-
return {"output": "Usage: /flush [number_of_messages_to_flush]", "messages": messages}
|
|
173
|
-
|
|
174
|
-
if n <= 0:
|
|
175
|
-
return {"output": "Error: Number of messages must be positive.", "messages": messages}
|
|
176
|
-
|
|
177
|
-
new_messages = list(messages)
|
|
178
|
-
original_len = len(new_messages)
|
|
179
|
-
removed_count = 0
|
|
180
|
-
|
|
181
|
-
if new_messages and new_messages[0].get("role") == "system":
|
|
182
|
-
system_message = new_messages[0]
|
|
183
|
-
working_messages = new_messages[1:]
|
|
184
|
-
num_to_remove = min(n, len(working_messages))
|
|
185
|
-
if num_to_remove > 0:
|
|
186
|
-
final_messages = [system_message] + working_messages[:-num_to_remove]
|
|
187
|
-
removed_count = num_to_remove
|
|
188
|
-
else:
|
|
189
|
-
final_messages = [system_message]
|
|
190
|
-
else:
|
|
191
|
-
num_to_remove = min(n, original_len)
|
|
192
|
-
if num_to_remove > 0:
|
|
193
|
-
final_messages = new_messages[:-num_to_remove]
|
|
194
|
-
removed_count = num_to_remove
|
|
195
|
-
else:
|
|
196
|
-
final_messages = []
|
|
197
|
-
|
|
198
|
-
output = f"Flushed {removed_count} message(s). Context is now {len(final_messages)} messages."
|
|
199
|
-
return {"output": output, "messages": final_messages}
|
|
200
|
-
|
|
201
|
-
@router.route("guac", "Enter guac mode")
|
|
202
|
-
def guac_handler(command, **kwargs):
|
|
203
|
-
'''
|
|
204
|
-
Guac ignores input npc and npc_team dirs and manually sets them to be at ~/.npcsh/guac/
|
|
205
|
-
|
|
206
|
-
'''
|
|
207
|
-
config_dir = safe_get(kwargs, 'config_dir', None)
|
|
208
|
-
plots_dir = safe_get(kwargs, 'plots_dir', None)
|
|
209
|
-
refresh_period = safe_get(kwargs, 'refresh_period', 100)
|
|
210
|
-
lang = safe_get(kwargs, 'lang', None)
|
|
211
|
-
messages = safe_get(kwargs, "messages", [])
|
|
212
|
-
db_conn = safe_get(kwargs, 'db_conn', create_engine('sqlite:///'+os.path.expanduser('~/npcsh_history.db')))
|
|
213
|
-
|
|
214
|
-
npc_file = '~/.npcsh/guac/npc_team/guac.npc'
|
|
215
|
-
npc_team_dir = os.path.expanduser('~/.npcsh/guac/npc_team/')
|
|
216
|
-
|
|
217
|
-
npc = NPC(file=npc_file, db_conn=db_conn)
|
|
218
|
-
|
|
219
|
-
team = Team(npc_team_dir, db_conn=db_conn)
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
enter_guac_mode(npc=npc,
|
|
223
|
-
team=team,
|
|
224
|
-
config_dir=config_dir,
|
|
225
|
-
plots_dir=plots_dir,
|
|
226
|
-
npc_team_dir=npc_team_dir,
|
|
227
|
-
refresh_period=refresh_period, lang=lang)
|
|
228
|
-
|
|
229
|
-
return {"output": 'Exiting Guac Mode', "messages": safe_get(kwargs, "messages", [])}
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
@router.route("help", "Show help information")
|
|
233
|
-
def help_handler(command, **kwargs):
|
|
234
|
-
return {"output": get_help_text(), "messages": safe_get(kwargs, "messages", [])}
|
|
235
|
-
|
|
236
|
-
@router.route("init", "Initialize NPC project")
|
|
237
|
-
def init_handler(command: str, **kwargs):
|
|
238
|
-
messages = safe_get(kwargs, "messages", [])
|
|
239
|
-
try:
|
|
240
|
-
parts = shlex.split(command)
|
|
241
|
-
directory = "."
|
|
242
|
-
templates = None
|
|
243
|
-
context = None
|
|
244
|
-
# Basic parsing example (needs improvement for robust flag handling)
|
|
245
|
-
if len(parts) > 1 and not parts[1].startswith("-"):
|
|
246
|
-
directory = parts[1]
|
|
247
|
-
# Add logic here to parse -t, -ctx flags if needed
|
|
248
|
-
|
|
249
|
-
initialize_npc_project(
|
|
250
|
-
directory=directory,
|
|
251
|
-
templates=templates,
|
|
252
|
-
context=context,
|
|
253
|
-
model=safe_get(kwargs, 'model'),
|
|
254
|
-
provider=safe_get(kwargs, 'provider')
|
|
255
|
-
)
|
|
256
|
-
output = f"NPC project initialized in {os.path.abspath(directory)}."
|
|
257
|
-
except NameError:
|
|
258
|
-
output = "Init function (initialize_npc_project) not available."
|
|
259
|
-
except Exception as e:
|
|
260
|
-
traceback.print_exc()
|
|
261
|
-
output = f"Error initializing project: {e}"
|
|
262
|
-
return {"output": output, "messages": messages}
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
@router.route("ots", "Take screenshot and optionally analyze with vision model")
|
|
266
|
-
def ots_handler(command: str, **kwargs):
|
|
267
|
-
command_parts = command.split()
|
|
268
|
-
image_paths = []
|
|
269
|
-
npc = safe_get(kwargs, 'npc')
|
|
270
|
-
vision_model = safe_get(kwargs, 'model', NPCSH_VISION_MODEL)
|
|
271
|
-
vision_provider = safe_get(kwargs, 'provider', NPCSH_VISION_PROVIDER)
|
|
272
|
-
if vision_model == NPCSH_CHAT_MODEL: vision_model = NPCSH_VISION_MODEL
|
|
273
|
-
if vision_provider == NPCSH_CHAT_PROVIDER: vision_provider = NPCSH_VISION_PROVIDER
|
|
274
|
-
|
|
275
|
-
messages = safe_get(kwargs, 'messages', [])
|
|
276
|
-
stream = safe_get(kwargs, 'stream', NPCSH_STREAM_OUTPUT)
|
|
277
|
-
|
|
278
|
-
try:
|
|
279
|
-
if len(command_parts) > 1:
|
|
280
|
-
for img_path_arg in command_parts[1:]:
|
|
281
|
-
full_path = os.path.abspath(img_path_arg)
|
|
282
|
-
if os.path.exists(full_path):
|
|
283
|
-
image_paths.append(full_path)
|
|
284
|
-
else:
|
|
285
|
-
return {"output": f"Error: Image file not found at {full_path}", "messages": messages}
|
|
286
|
-
else:
|
|
287
|
-
screenshot_info = capture_screenshot(npc=npc)
|
|
288
|
-
if screenshot_info and "file_path" in screenshot_info:
|
|
289
|
-
image_paths.append(screenshot_info["file_path"])
|
|
290
|
-
print(f"Screenshot captured: {screenshot_info.get('filename', os.path.basename(screenshot_info['file_path']))}")
|
|
291
|
-
else:
|
|
292
|
-
return {"output": "Error: Failed to capture screenshot.", "messages": messages}
|
|
293
|
-
|
|
294
|
-
if not image_paths:
|
|
295
|
-
return {"output": "No valid images found or captured.", "messages": messages}
|
|
296
|
-
|
|
297
|
-
user_prompt = safe_get(kwargs, 'stdin_input')
|
|
298
|
-
if user_prompt is None:
|
|
299
|
-
try:
|
|
300
|
-
user_prompt = input(
|
|
301
|
-
"Enter a prompt for the LLM about these images (or press Enter to skip): "
|
|
302
|
-
)
|
|
303
|
-
except EOFError:
|
|
304
|
-
user_prompt = "Describe the image(s)."
|
|
305
|
-
|
|
306
|
-
if not user_prompt or not user_prompt.strip():
|
|
307
|
-
user_prompt = "Describe the image(s)."
|
|
308
|
-
|
|
309
|
-
response_data = get_llm_response(
|
|
310
|
-
prompt=user_prompt,
|
|
311
|
-
model=vision_model,
|
|
312
|
-
provider=vision_provider,
|
|
313
|
-
messages=messages,
|
|
314
|
-
images=image_paths,
|
|
315
|
-
stream=stream,
|
|
316
|
-
npc=npc,
|
|
317
|
-
api_url=safe_get(kwargs, 'api_url'),
|
|
318
|
-
api_key=safe_get(kwargs, 'api_key')
|
|
319
|
-
)
|
|
320
|
-
return {"output": response_data.get('response'), "messages": response_data.get('messages')}
|
|
321
|
-
|
|
322
|
-
except Exception as e:
|
|
323
|
-
traceback.print_exc()
|
|
324
|
-
return {"output": f"Error during /ots command: {e}", "messages": messages}
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
@router.route("plan", "Execute a plan command")
|
|
328
|
-
def plan_handler(command: str, **kwargs):
|
|
329
|
-
messages = safe_get(kwargs, "messages", [])
|
|
330
|
-
user_command = " ".join(command.split()[1:])
|
|
331
|
-
if not user_command:
|
|
332
|
-
return {"output": "Usage: /plan <description_of_plan>", "messages": messages}
|
|
333
|
-
try:
|
|
334
|
-
return execute_plan_command(command=user_command, **kwargs)
|
|
335
|
-
except NameError:
|
|
336
|
-
return {"output": "Plan function (execute_plan_command) not available.", "messages": messages}
|
|
337
|
-
except Exception as e:
|
|
338
|
-
traceback.print_exc()
|
|
339
|
-
return {"output": f"Error executing plan: {e}", "messages": messages}
|
|
340
|
-
|
|
341
|
-
@router.route("pti", "Use pardon-the-interruption mode to interact with the LLM")
|
|
342
|
-
def plonk_handler(command: str, **kwargs):
|
|
343
|
-
return
|
|
344
|
-
|
|
345
|
-
@router.route("plonk", "Use vision model to interact with GUI")
|
|
346
|
-
def plonk_handler(command: str, **kwargs):
|
|
347
|
-
messages = safe_get(kwargs, "messages", [])
|
|
348
|
-
request_str = " ".join(command.split()[1:])
|
|
349
|
-
if not request_str:
|
|
350
|
-
return {"output": "Usage: /plonk <task_description>", "messages": messages}
|
|
351
|
-
|
|
352
|
-
action_space = {
|
|
353
|
-
"click": {"x": "int (0-100)", "y": "int (0-100)"},
|
|
354
|
-
"type": {"text": "string"},
|
|
355
|
-
"scroll": {"direction": "up/down/left/right", "amount": "int"},
|
|
356
|
-
"bash": {"command": "string"},
|
|
357
|
-
"wait": {"duration": "int (seconds)"}
|
|
358
|
-
}
|
|
359
|
-
try:
|
|
360
|
-
result = execute_plonk_command(
|
|
361
|
-
request=request_str,
|
|
362
|
-
action_space=action_space,
|
|
363
|
-
model=safe_get(kwargs, 'model', NPCSH_VISION_MODEL),
|
|
364
|
-
provider=safe_get(kwargs, 'provider', NPCSH_VISION_PROVIDER),
|
|
365
|
-
npc=safe_get(kwargs, 'npc')
|
|
366
|
-
)
|
|
367
|
-
if isinstance(result, dict) and "output" in result:
|
|
368
|
-
result_messages = result.get("messages", messages)
|
|
369
|
-
return {"output": result["output"], "messages": result_messages}
|
|
370
|
-
else:
|
|
371
|
-
return {"output": str(result), "messages": messages}
|
|
372
|
-
except NameError:
|
|
373
|
-
return {"output": "Plonk function (execute_plonk_command) not available.", "messages": messages}
|
|
374
|
-
except Exception as e:
|
|
375
|
-
traceback.print_exc()
|
|
376
|
-
return {"output": f"Error executing plonk command: {e}", "messages": messages}
|
|
377
|
-
@router.route("brainblast", "Execute an advanced chunked search on command history")
|
|
378
|
-
def brainblast_handler(command: str, **kwargs):
|
|
379
|
-
messages = safe_get(kwargs, "messages", [])
|
|
380
|
-
|
|
381
|
-
# Parse command to get the search query
|
|
382
|
-
parts = shlex.split(command)
|
|
383
|
-
search_query = " ".join(parts[1:]) if len(parts) > 1 else ""
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
if not search_query:
|
|
387
|
-
return {"output": "Usage: /brainblast <search_terms>", "messages": messages}
|
|
388
|
-
|
|
389
|
-
# Get the command history instance
|
|
390
|
-
command_history = kwargs.get('command_history')
|
|
391
|
-
if not command_history:
|
|
392
|
-
# Create a new one if not provided
|
|
393
|
-
db_path = safe_get(kwargs, "history_db_path", os.path.expanduser('~/npcsh_history.db'))
|
|
394
|
-
try:
|
|
395
|
-
command_history = CommandHistory(db_path)
|
|
396
|
-
except Exception as e:
|
|
397
|
-
return {"output": f"Error connecting to command history: {e}", "messages": messages}
|
|
398
|
-
|
|
399
|
-
try:
|
|
400
|
-
# Remove messages from kwargs to avoid duplicate argument error
|
|
401
|
-
if 'messages' in kwargs:
|
|
402
|
-
del kwargs['messages']
|
|
403
|
-
|
|
404
|
-
# Execute the brainblast command
|
|
405
|
-
return execute_brainblast_command(
|
|
406
|
-
command=search_query,
|
|
407
|
-
command_history=command_history,
|
|
408
|
-
messages=messages,
|
|
409
|
-
top_k=safe_get(kwargs, 'top_k', 5),
|
|
410
|
-
**kwargs
|
|
411
|
-
)
|
|
412
|
-
|
|
413
|
-
except Exception as e:
|
|
414
|
-
traceback.print_exc()
|
|
415
|
-
return {"output": f"Error executing brainblast command: {e}", "messages": messages}
|
|
416
|
-
|
|
417
|
-
@router.route("rag", "Execute a RAG command using ChromaDB embeddings with optional file input (-f/--file)")
|
|
418
|
-
def rag_handler(command: str, **kwargs):
|
|
419
|
-
messages = safe_get(kwargs, "messages", [])
|
|
420
|
-
|
|
421
|
-
# Parse command with shlex to properly handle quoted strings
|
|
422
|
-
parts = shlex.split(command)
|
|
423
|
-
user_command = []
|
|
424
|
-
file_paths = []
|
|
425
|
-
|
|
426
|
-
# Process arguments
|
|
427
|
-
i = 1 # Skip the first element which is "rag"
|
|
428
|
-
while i < len(parts):
|
|
429
|
-
if parts[i] == "-f" or parts[i] == "--file":
|
|
430
|
-
# We found a file flag, get the file path
|
|
431
|
-
if i + 1 < len(parts):
|
|
432
|
-
file_paths.append(parts[i + 1])
|
|
433
|
-
i += 2 # Skip both the flag and the path
|
|
434
|
-
else:
|
|
435
|
-
return {"output": "Error: -f/--file flag needs a file path", "messages": messages}
|
|
436
|
-
else:
|
|
437
|
-
# This is part of the user query
|
|
438
|
-
user_command.append(parts[i])
|
|
439
|
-
i += 1
|
|
440
|
-
|
|
441
|
-
user_command = " ".join(user_command)
|
|
442
|
-
|
|
443
|
-
vector_db_path = safe_get(kwargs, "vector_db_path", os.path.expanduser('~/npcsh_chroma.db'))
|
|
444
|
-
embedding_model = safe_get(kwargs, "embedding_model", NPCSH_EMBEDDING_MODEL)
|
|
445
|
-
embedding_provider = safe_get(kwargs, "embedding_provider", NPCSH_EMBEDDING_PROVIDER)
|
|
446
|
-
|
|
447
|
-
if not user_command and not file_paths:
|
|
448
|
-
return {"output": "Usage: /rag [-f file_path] <query>", "messages": messages}
|
|
449
|
-
|
|
450
|
-
try:
|
|
451
|
-
# Process files if provided
|
|
452
|
-
file_contents = []
|
|
453
|
-
for file_path in file_paths:
|
|
454
|
-
try:
|
|
455
|
-
chunks = load_file_contents(file_path)
|
|
456
|
-
file_name = os.path.basename(file_path)
|
|
457
|
-
file_contents.extend([f"[{file_name}] {chunk}" for chunk in chunks])
|
|
458
|
-
except Exception as file_err:
|
|
459
|
-
file_contents.append(f"Error processing file {file_path}: {str(file_err)}")
|
|
460
|
-
|
|
461
|
-
# Execute the RAG command
|
|
462
|
-
return execute_rag_command(
|
|
463
|
-
command=user_command,
|
|
464
|
-
vector_db_path=vector_db_path,
|
|
465
|
-
embedding_model=embedding_model,
|
|
466
|
-
embedding_provider=embedding_provider,
|
|
467
|
-
file_contents=file_contents if file_paths else None,
|
|
468
|
-
**kwargs
|
|
469
|
-
)
|
|
470
|
-
|
|
471
|
-
except Exception as e:
|
|
472
|
-
traceback.print_exc()
|
|
473
|
-
return {"output": f"Error executing RAG command: {e}", "messages": messages}
|
|
474
|
-
@router.route("roll", "generate a video")
|
|
475
|
-
def roll_handler(command: str, **kwargs):
|
|
476
|
-
messages = safe_get(kwargs, "messages", [])
|
|
477
|
-
prompt = " ".join(command.split()[1:])
|
|
478
|
-
num_frames = safe_get(kwargs, 'num_frames', 125)
|
|
479
|
-
width = safe_get(kwargs, 'width', 256)
|
|
480
|
-
height = safe_get(kwargs, 'height', 256)
|
|
481
|
-
output_path = safe_get(kwargs, 'output_path', "output.mp4")
|
|
482
|
-
if not prompt:
|
|
483
|
-
return {"output": "Usage: /roll <your prompt>", "messages": messages}
|
|
484
|
-
try:
|
|
485
|
-
result = gen_video(
|
|
486
|
-
prompt=prompt,
|
|
487
|
-
model=safe_get(kwargs, 'model', NPCSH_VISION_MODEL),
|
|
488
|
-
provider=safe_get(kwargs, 'provider', NPCSH_VISION_PROVIDER),
|
|
489
|
-
npc=safe_get(kwargs, 'npc'),
|
|
490
|
-
num_frames = num_frames,
|
|
491
|
-
width = width,
|
|
492
|
-
height = height,
|
|
493
|
-
output_path=output_path,
|
|
494
|
-
|
|
495
|
-
**safe_get(kwargs, 'api_kwargs', {})
|
|
496
|
-
)
|
|
497
|
-
return result
|
|
498
|
-
except Exception as e:
|
|
499
|
-
traceback.print_exc()
|
|
500
|
-
return {"output": f"Error generating video: {e}", "messages": messages}
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
@router.route("sample", "Send a prompt directly to the LLM")
|
|
504
|
-
def sample_handler(command: str, **kwargs):
|
|
505
|
-
messages = safe_get(kwargs, "messages", [])
|
|
506
|
-
prompt = " ".join(command.split()[1:])
|
|
507
|
-
if not prompt:
|
|
508
|
-
return {"output": "Usage: /sample <your prompt>", "messages": messages}
|
|
509
|
-
|
|
510
|
-
try:
|
|
511
|
-
result = get_llm_response(
|
|
512
|
-
prompt=prompt,
|
|
513
|
-
provider=safe_get(kwargs, 'provider'),
|
|
514
|
-
model=safe_get(kwargs, 'model'),
|
|
515
|
-
images=safe_get(kwargs, 'attachments'),
|
|
516
|
-
npc=safe_get(kwargs, 'npc'),
|
|
517
|
-
team=safe_get(kwargs, 'team'),
|
|
518
|
-
messages=messages,
|
|
519
|
-
api_url=safe_get(kwargs, 'api_url'),
|
|
520
|
-
api_key=safe_get(kwargs, 'api_key'),
|
|
521
|
-
context=safe_get(kwargs, 'context'),
|
|
522
|
-
stream=safe_get(kwargs, 'stream')
|
|
523
|
-
)
|
|
524
|
-
return result
|
|
525
|
-
except Exception as e:
|
|
526
|
-
traceback.print_exc()
|
|
527
|
-
return {"output": f"Error sampling LLM: {e}", "messages": messages}
|
|
528
|
-
|
|
529
|
-
@router.route("search", "Execute a web search command")
|
|
530
|
-
def search_handler(command: str, **kwargs):
|
|
531
|
-
"""
|
|
532
|
-
Executes a search command.
|
|
533
|
-
# search commands will bel ike :
|
|
534
|
-
# '/search -p default = google "search term" '
|
|
535
|
-
# '/search -p perplexity ..
|
|
536
|
-
# '/search -p google ..
|
|
537
|
-
# extract provider if its there
|
|
538
|
-
# check for either -p or --p
|
|
539
|
-
"""
|
|
540
|
-
messages = safe_get(kwargs, "messages", [])
|
|
541
|
-
query = " ".join(command.split()[1:])
|
|
542
|
-
|
|
543
|
-
if not query:
|
|
544
|
-
return {"output": "Usage: /search <query>", "messages": messages}
|
|
545
|
-
search_provider = safe_get(kwargs, 'search_provider', NPCSH_SEARCH_PROVIDER)
|
|
546
|
-
try:
|
|
547
|
-
search_results = search_web(query, provider=search_provider)
|
|
548
|
-
output = "\n".join([f"- {res}" for res in search_results]) if search_results else "No results found."
|
|
549
|
-
except Exception as e:
|
|
550
|
-
traceback.print_exc()
|
|
551
|
-
output = f"Error during web search: {e}"
|
|
552
|
-
return {"output": output, "messages": messages}
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
@router.route("serve", "Set configuration values")
|
|
557
|
-
def serve_handler(command: str, **kwargs):
|
|
558
|
-
#print('calling serve handler')
|
|
559
|
-
#print(kwargs)
|
|
560
|
-
|
|
561
|
-
port = safe_get(kwargs, "port", 5337)
|
|
562
|
-
#print(port, type(port))
|
|
563
|
-
messages = safe_get(kwargs, "messages", [])
|
|
564
|
-
cors = safe_get(kwargs, "cors", None)
|
|
565
|
-
if cors:
|
|
566
|
-
cors_origins = [origin.strip() for origin in cors.split(",")]
|
|
567
|
-
else:
|
|
568
|
-
cors_origins = None
|
|
569
|
-
|
|
570
|
-
start_flask_server(
|
|
571
|
-
port=port,
|
|
572
|
-
cors_origins=cors_origins,
|
|
573
|
-
)
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
return {"output": None, "messages": messages}
|
|
577
|
-
|
|
578
|
-
@router.route("set", "Set configuration values")
|
|
579
|
-
def set_handler(command: str, **kwargs):
|
|
580
|
-
messages = safe_get(kwargs, "messages", [])
|
|
581
|
-
parts = command.split(maxsplit=1)
|
|
582
|
-
if len(parts) < 2 or '=' not in parts[1]:
|
|
583
|
-
return {"output": "Usage: /set <key>=<value>", "messages": messages}
|
|
584
|
-
|
|
585
|
-
key_value = parts[1]
|
|
586
|
-
key, value = key_value.split('=', 1)
|
|
587
|
-
key = key.strip()
|
|
588
|
-
value = value.strip().strip('"\'')
|
|
589
|
-
|
|
590
|
-
try:
|
|
591
|
-
set_npcsh_config_value(key, value)
|
|
592
|
-
output = f"Configuration value '{key}' set."
|
|
593
|
-
except NameError:
|
|
594
|
-
output = "Set function (set_npcsh_config_value) not available."
|
|
595
|
-
except Exception as e:
|
|
596
|
-
traceback.print_exc()
|
|
597
|
-
output = f"Error setting configuration '{key}': {e}"
|
|
598
|
-
return {"output": output, "messages": messages}
|
|
599
|
-
|
|
600
|
-
@router.route("sleep", "Pause execution for N seconds")
|
|
601
|
-
def sleep_handler(command: str, **kwargs):
|
|
602
|
-
messages = safe_get(kwargs, "messages", [])
|
|
603
|
-
parts = command.split()
|
|
604
|
-
try:
|
|
605
|
-
seconds = float(parts[1]) if len(parts) > 1 else 1.0
|
|
606
|
-
if seconds < 0: raise ValueError("Duration must be non-negative")
|
|
607
|
-
time.sleep(seconds)
|
|
608
|
-
output = f"Slept for {seconds} seconds."
|
|
609
|
-
except (ValueError, IndexError):
|
|
610
|
-
output = "Usage: /sleep <seconds>"
|
|
611
|
-
except Exception as e:
|
|
612
|
-
traceback.print_exc()
|
|
613
|
-
output = f"Error during sleep: {e}"
|
|
614
|
-
return {"output": output, "messages": messages}
|
|
615
|
-
|
|
616
|
-
@router.route("spool", "Enter interactive chat (spool) mode")
|
|
617
|
-
def spool_handler(command: str, **kwargs):
|
|
618
|
-
try:
|
|
619
|
-
return enter_spool_mode(
|
|
620
|
-
model=safe_get(kwargs, 'model', NPCSH_CHAT_MODEL),
|
|
621
|
-
provider=safe_get(kwargs, 'provider', NPCSH_CHAT_PROVIDER),
|
|
622
|
-
npc=safe_get(kwargs, 'npc'),
|
|
623
|
-
messages=safe_get(kwargs, 'messages'),
|
|
624
|
-
conversation_id=safe_get(kwargs, 'conversation_id'),
|
|
625
|
-
stream=safe_get(kwargs, 'stream', NPCSH_STREAM_OUTPUT),
|
|
626
|
-
files=safe_get(kwargs, 'files'),
|
|
627
|
-
)
|
|
628
|
-
except Exception as e:
|
|
629
|
-
traceback.print_exc()
|
|
630
|
-
return {"output": f"Error entering spool mode: {e}", "messages": safe_get(kwargs, "messages", [])}
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
@router.route("jinxs", "Show available jinxs for the current NPC/Team")
|
|
634
|
-
def jinxs_handler(command: str, **kwargs):
|
|
635
|
-
npc = safe_get(kwargs, 'npc')
|
|
636
|
-
team = safe_get(kwargs, 'team')
|
|
637
|
-
output = "Available Jinxs:\n"
|
|
638
|
-
jinxs_listed = set()
|
|
639
|
-
|
|
640
|
-
def format_jinx(name, jinx_obj):
|
|
641
|
-
desc = getattr(jinx_obj, 'description', 'No description available.')
|
|
642
|
-
return f"- /{name}: {desc}\n"
|
|
643
|
-
|
|
644
|
-
if npc and isinstance(npc, NPC) and hasattr(npc, 'jinxs_dict') and npc.jinxs_dict:
|
|
645
|
-
output += f"\n--- Jinxs for NPC: {npc.name} ---\n"
|
|
646
|
-
for name, jinx in sorted(npc.jinxs_dict.items()):
|
|
647
|
-
output += format_jinx(name, jinx)
|
|
648
|
-
jinxs_listed.add(name)
|
|
649
|
-
|
|
650
|
-
if team and hasattr(team, 'jinxs_dict') and team.jinxs_dict:
|
|
651
|
-
team_has_jinxs = False
|
|
652
|
-
team_output = ""
|
|
653
|
-
for name, jinx in sorted(team.jinxs_dict.items()):
|
|
654
|
-
if name not in jinxs_listed:
|
|
655
|
-
team_output += format_jinx(name, jinx)
|
|
656
|
-
team_has_jinxs = True
|
|
657
|
-
if team_has_jinxs:
|
|
658
|
-
output += f"\n--- Jinxs for Team: {getattr(team, 'name', 'Unnamed Team')} ---\n"
|
|
659
|
-
output += team_output
|
|
660
|
-
|
|
661
|
-
if not jinxs_listed and not (team and hasattr(team, 'jinxs_dict') and team.jinxs_dict):
|
|
662
|
-
output = "No jinxs available for the current context."
|
|
663
|
-
|
|
664
|
-
return {"output": output.strip(), "messages": safe_get(kwargs, "messages", [])}
|
|
665
|
-
|
|
666
|
-
@router.route("trigger", "Execute a trigger command")
|
|
667
|
-
def trigger_handler(command: str, **kwargs):
|
|
668
|
-
messages = safe_get(kwargs, "messages", [])
|
|
669
|
-
user_command = " ".join(command.split()[1:])
|
|
670
|
-
if not user_command:
|
|
671
|
-
return {"output": "Usage: /trigger <trigger_description>", "messages": messages}
|
|
672
|
-
try:
|
|
673
|
-
return execute_trigger_command(command=user_command, **kwargs)
|
|
674
|
-
except NameError:
|
|
675
|
-
return {"output": "Trigger function (execute_trigger_command) not available.", "messages": messages}
|
|
676
|
-
except Exception as e:
|
|
677
|
-
traceback.print_exc()
|
|
678
|
-
return {"output": f"Error executing trigger: {e}", "messages": messages}
|
|
679
|
-
@router.route("vixynt", "Generate images from text descriptions")
|
|
680
|
-
def vixynt_handler(command: str, **kwargs):
|
|
681
|
-
npc = safe_get(kwargs, 'npc')
|
|
682
|
-
model = safe_get(kwargs, 'model', NPCSH_IMAGE_GEN_MODEL)
|
|
683
|
-
provider = safe_get(kwargs, 'provider', NPCSH_IMAGE_GEN_PROVIDER)
|
|
684
|
-
height = safe_get(kwargs, 'height', 1024)
|
|
685
|
-
width = safe_get(kwargs, 'width', 1024)
|
|
686
|
-
filename = safe_get(kwargs, 'output_filename', None)
|
|
687
|
-
attachments = None
|
|
688
|
-
if model == NPCSH_CHAT_MODEL: model = NPCSH_IMAGE_GEN_MODEL
|
|
689
|
-
if provider == NPCSH_CHAT_PROVIDER: provider = NPCSH_IMAGE_GEN_PROVIDER
|
|
690
|
-
|
|
691
|
-
messages = safe_get(kwargs, 'messages', [])
|
|
692
|
-
|
|
693
|
-
filename = None
|
|
694
|
-
|
|
695
|
-
prompt_parts = []
|
|
696
|
-
try:
|
|
697
|
-
parts = shlex.split(command)
|
|
698
|
-
for part in parts[1:]:
|
|
699
|
-
if part.startswith("filename="):
|
|
700
|
-
filename = part.split("=", 1)[1]
|
|
701
|
-
elif part.startswith("height="):
|
|
702
|
-
try:
|
|
703
|
-
height = int(part.split("=", 1)[1])
|
|
704
|
-
except ValueError:
|
|
705
|
-
pass
|
|
706
|
-
elif part.startswith("width="):
|
|
707
|
-
try:
|
|
708
|
-
width = int(part.split("=", 1)[1])
|
|
709
|
-
except ValueError:
|
|
710
|
-
pass
|
|
711
|
-
elif part.startswith("attachments="): # New parameter for image editing
|
|
712
|
-
# split at comma
|
|
713
|
-
attachments = part.split("=", 1)[1].split(",")
|
|
714
|
-
|
|
715
|
-
else:
|
|
716
|
-
prompt_parts.append(part)
|
|
717
|
-
except Exception as parse_err:
|
|
718
|
-
return {"output": f"Error parsing arguments: {parse_err}. Usage: /vixynt <prompt> [filename=...] [height=...] [width=...] [input=...for editing]", "messages": messages}
|
|
719
|
-
user_prompt = " ".join(prompt_parts)
|
|
720
|
-
if not user_prompt:
|
|
721
|
-
return {"output": "Usage: /vixynt <prompt> [filename=...] [height=...] [width=...] [attachments=... for editing]", "messages": messages}
|
|
722
|
-
|
|
723
|
-
try:
|
|
724
|
-
image = gen_image(
|
|
725
|
-
prompt=user_prompt,
|
|
726
|
-
model=model,
|
|
727
|
-
provider=provider,
|
|
728
|
-
npc=npc,
|
|
729
|
-
height=height,
|
|
730
|
-
width=width,
|
|
731
|
-
input_images=attachments
|
|
732
|
-
)
|
|
733
|
-
if filename is None:
|
|
734
|
-
# Generate a filename based on the prompt and the date time
|
|
735
|
-
os.makedirs(os.path.expanduser("~/.npcsh/images/"), exist_ok=True)
|
|
736
|
-
filename = (
|
|
737
|
-
os.path.expanduser("~/.npcsh/images/")
|
|
738
|
-
+ f"image_{datetime.now().strftime('%Y%m%d_%H%M%S')}.png"
|
|
739
|
-
)
|
|
740
|
-
image.save(filename)
|
|
741
|
-
image.show()
|
|
742
|
-
|
|
743
|
-
if attachments:
|
|
744
|
-
output = f"Image edited and saved to: {filename}"
|
|
745
|
-
else:
|
|
746
|
-
output = f"Image generated and saved to: {filename}"
|
|
747
|
-
except Exception as e:
|
|
748
|
-
traceback.print_exc()
|
|
749
|
-
output = f"Error {'editing' if attachments else 'generating'} image: {e}"
|
|
750
|
-
|
|
751
|
-
return {"output": output, "messages": messages}
|
|
752
|
-
# --- THIS IS THE FINAL, CORRECTED wander_handler in routes.py ---
|
|
753
|
-
@router.route("wander", "Enter wander mode (experimental)")
|
|
754
|
-
def wander_handler(command: str, **kwargs):
|
|
755
|
-
messages = safe_get(kwargs, "messages", [])
|
|
756
|
-
|
|
757
|
-
# General parser for key=value arguments
|
|
758
|
-
try:
|
|
759
|
-
parts = shlex.split(command)
|
|
760
|
-
problem_parts = []
|
|
761
|
-
wander_params = {}
|
|
762
|
-
|
|
763
|
-
i = 1 # Start after the 'wander' command name
|
|
764
|
-
while i < len(parts):
|
|
765
|
-
part = parts[i]
|
|
766
|
-
|
|
767
|
-
if '=' in part:
|
|
768
|
-
# This is the start of a key=value pair
|
|
769
|
-
key, initial_value = part.split('=', 1)
|
|
770
|
-
|
|
771
|
-
# Consume all subsequent parts that do NOT contain '=' as part of this value
|
|
772
|
-
value_parts = [initial_value]
|
|
773
|
-
j = i + 1
|
|
774
|
-
while j < len(parts) and '=' not in parts[j]:
|
|
775
|
-
value_parts.append(parts[j])
|
|
776
|
-
j += 1
|
|
777
|
-
|
|
778
|
-
# Join the reconstructed value and store it
|
|
779
|
-
wander_params[key] = " ".join(value_parts)
|
|
780
|
-
# Advance the main loop index past the consumed parts
|
|
781
|
-
i = j
|
|
782
|
-
else:
|
|
783
|
-
# This part belongs to the problem string
|
|
784
|
-
problem_parts.append(part)
|
|
785
|
-
i += 1
|
|
786
|
-
|
|
787
|
-
problem = " ".join(problem_parts)
|
|
788
|
-
except Exception as e:
|
|
789
|
-
return {"output": f"Error parsing arguments: {e}", "messages": messages}
|
|
790
|
-
|
|
791
|
-
if not problem:
|
|
792
|
-
return {"output": "Usage: /wander <problem> [key=value...]", "messages": messages}
|
|
793
|
-
|
|
794
|
-
try:
|
|
795
|
-
# Build the argument list for enter_wander_mode
|
|
796
|
-
mode_args = {
|
|
797
|
-
'problem': problem,
|
|
798
|
-
'npc': safe_get(kwargs, 'npc'),
|
|
799
|
-
'model': safe_get(kwargs, 'model'),
|
|
800
|
-
'provider': safe_get(kwargs, 'provider'),
|
|
801
|
-
# Use parsed params with defaults
|
|
802
|
-
'environment': wander_params.get('environment'),
|
|
803
|
-
'low_temp': float(wander_params.get('low-temp', 0.5)),
|
|
804
|
-
'high_temp': float(wander_params.get('high-temp', 1.9)),
|
|
805
|
-
'interruption_likelihood': float(wander_params.get('interruption-likelihood', 1)),
|
|
806
|
-
'sample_rate': float(wander_params.get('sample-rate', 0.4)),
|
|
807
|
-
'n_high_temp_streams': int(wander_params.get('n-high-temp-streams', 5)),
|
|
808
|
-
'include_events': bool(wander_params.get('include-events', False)),
|
|
809
|
-
'num_events': int(wander_params.get('num-events', 3))
|
|
810
|
-
}
|
|
811
|
-
|
|
812
|
-
result = enter_wander_mode(**mode_args)
|
|
813
|
-
|
|
814
|
-
if isinstance(result, list) and result:
|
|
815
|
-
output = result[-1].get("insight", "Wander mode session complete.")
|
|
816
|
-
else:
|
|
817
|
-
output = str(result) if result else "Wander mode session complete."
|
|
818
|
-
|
|
819
|
-
messages.append({"role": "assistant", "content": output})
|
|
820
|
-
return {"output": output, "messages": messages}
|
|
821
|
-
|
|
822
|
-
except Exception as e:
|
|
823
|
-
traceback.print_exc()
|
|
824
|
-
return {"output": f"Error during wander mode: {e}", "messages": messages}
|
|
825
|
-
|
|
826
|
-
@router.route("yap", "Enter voice chat (yap) mode", shell_only=True)
|
|
827
|
-
def whisper_handler(command: str, **kwargs):
|
|
828
|
-
try:
|
|
829
|
-
return enter_yap_mode(
|
|
830
|
-
messages=safe_get(kwargs, 'messages'),
|
|
831
|
-
npc=safe_get(kwargs, 'npc'),
|
|
832
|
-
model=safe_get(kwargs, 'model', NPCSH_CHAT_MODEL),
|
|
833
|
-
provider=safe_get(kwargs, 'provider', NPCSH_CHAT_PROVIDER),
|
|
834
|
-
team=safe_get(kwargs, 'team'),
|
|
835
|
-
stream=safe_get(kwargs, 'stream', NPCSH_STREAM_OUTPUT),
|
|
836
|
-
conversation_id=safe_get(kwargs, 'conversation_id')
|
|
837
|
-
)
|
|
838
|
-
except Exception as e:
|
|
839
|
-
traceback.print_exc()
|
|
840
|
-
return {"output": f"Error entering yap mode: {e}", "messages": safe_get(kwargs, "messages", [])}
|
|
841
|
-
|
|
842
|
-
@router.route("alicanto", "Conduct deep research with multiple perspectives, identifying gold insights and cliff warnings")
|
|
843
|
-
def alicanto_handler(command: str, **kwargs):
|
|
844
|
-
messages = safe_get(kwargs, "messages", [])
|
|
845
|
-
|
|
846
|
-
# Parse command with shlex to properly handle quoted strings
|
|
847
|
-
parts = shlex.split(command)
|
|
848
|
-
|
|
849
|
-
# Process arguments
|
|
850
|
-
query = ""
|
|
851
|
-
num_npcs = safe_get(kwargs, 'num_npcs', 5)
|
|
852
|
-
depth = safe_get(kwargs, 'depth', 3)
|
|
853
|
-
exploration_factor = safe_get(kwargs, 'exploration', 0.3)
|
|
854
|
-
creativity_factor = safe_get(kwargs, 'creativity', 0.5)
|
|
855
|
-
output_format = safe_get(kwargs, 'format', 'report')
|
|
856
|
-
|
|
857
|
-
# Parse command-line arguments
|
|
858
|
-
i = 1 # Skip "alicanto" command
|
|
859
|
-
while i < len(parts):
|
|
860
|
-
if parts[i].startswith('--'):
|
|
861
|
-
option = parts[i][2:] # Remove '--'
|
|
862
|
-
if option in ['num-npcs', 'npcs']:
|
|
863
|
-
if i + 1 < len(parts) and parts[i + 1].isdigit():
|
|
864
|
-
num_npcs = int(parts[i + 1])
|
|
865
|
-
i += 2
|
|
866
|
-
else:
|
|
867
|
-
i += 1
|
|
868
|
-
elif option in ['depth', 'd']:
|
|
869
|
-
if i + 1 < len(parts) and parts[i + 1].isdigit():
|
|
870
|
-
depth = int(parts[i + 1])
|
|
871
|
-
i += 2
|
|
872
|
-
else:
|
|
873
|
-
i += 1
|
|
874
|
-
elif option in ['exploration', 'e']:
|
|
875
|
-
if i + 1 < len(parts) and parts[i + 1].replace('.', '', 1).isdigit():
|
|
876
|
-
exploration_factor = float(parts[i + 1])
|
|
877
|
-
i += 2
|
|
878
|
-
else:
|
|
879
|
-
i += 1
|
|
880
|
-
elif option in ['creativity', 'c']:
|
|
881
|
-
if i + 1 < len(parts) and parts[i + 1].replace('.', '', 1).isdigit():
|
|
882
|
-
creativity_factor = float(parts[i + 1])
|
|
883
|
-
i += 2
|
|
884
|
-
else:
|
|
885
|
-
i += 1
|
|
886
|
-
elif option in ['format', 'f']:
|
|
887
|
-
if i + 1 < len(parts):
|
|
888
|
-
output_format = parts[i + 1]
|
|
889
|
-
i += 2
|
|
890
|
-
else:
|
|
891
|
-
i += 1
|
|
892
|
-
else:
|
|
893
|
-
# Skip unknown option
|
|
894
|
-
i += 1
|
|
895
|
-
else:
|
|
896
|
-
# This is part of the request
|
|
897
|
-
query += parts[i] + " "
|
|
898
|
-
i += 1
|
|
899
|
-
|
|
900
|
-
query = query.strip()
|
|
901
|
-
|
|
902
|
-
# Also apply any kwargs that were passed directly (these override command line args)
|
|
903
|
-
if 'num_npcs' in kwargs:
|
|
904
|
-
try:
|
|
905
|
-
num_npcs = int(kwargs['num_npcs'])
|
|
906
|
-
except ValueError:
|
|
907
|
-
return {"output": "Error: num_npcs must be an integer", "messages": messages}
|
|
908
|
-
|
|
909
|
-
if 'depth' in kwargs:
|
|
910
|
-
try:
|
|
911
|
-
depth = int(kwargs['depth'])
|
|
912
|
-
except ValueError:
|
|
913
|
-
return {"output": "Error: depth must be an integer", "messages": messages}
|
|
914
|
-
|
|
915
|
-
if 'exploration' in kwargs:
|
|
916
|
-
try:
|
|
917
|
-
exploration_factor = float(kwargs['exploration'])
|
|
918
|
-
except ValueError:
|
|
919
|
-
return {"output": "Error: exploration must be a float", "messages": messages}
|
|
920
|
-
|
|
921
|
-
if 'creativity' in kwargs:
|
|
922
|
-
try:
|
|
923
|
-
creativity_factor = float(kwargs['creativity'])
|
|
924
|
-
except ValueError:
|
|
925
|
-
return {"output": "Error: creativity must be a float", "messages": messages}
|
|
926
|
-
|
|
927
|
-
if not query:
|
|
928
|
-
return {"output": "Usage: /alicanto <research query> [--num-npcs N] [--depth N] [--exploration 0.3] [--creativity 0.5] [--format report|summary|full]", "messages": messages}
|
|
929
|
-
|
|
930
|
-
try:
|
|
931
|
-
logging.info(f"Starting Alicanto research on: {query}")
|
|
932
|
-
result = alicanto(
|
|
933
|
-
request=query,
|
|
934
|
-
num_npcs=num_npcs,
|
|
935
|
-
depth=depth,
|
|
936
|
-
memory=3,
|
|
937
|
-
context=None,
|
|
938
|
-
model=safe_get(kwargs, 'model', NPCSH_CHAT_MODEL),
|
|
939
|
-
provider=safe_get(kwargs, 'provider', NPCSH_CHAT_PROVIDER),
|
|
940
|
-
exploration_factor=exploration_factor,
|
|
941
|
-
creativity_factor=creativity_factor,
|
|
942
|
-
output_format=output_format
|
|
943
|
-
)
|
|
944
|
-
|
|
945
|
-
# Format the output based on the result type
|
|
946
|
-
if isinstance(result, dict):
|
|
947
|
-
if "integration" in result:
|
|
948
|
-
output = result["integration"]
|
|
949
|
-
else:
|
|
950
|
-
output = "Alicanto research completed. Full results available in returned data."
|
|
951
|
-
else:
|
|
952
|
-
output = result
|
|
953
|
-
|
|
954
|
-
return {"output": output, "messages": messages, "alicanto_result": result}
|
|
955
|
-
except Exception as e:
|
|
956
|
-
traceback.print_exc()
|
|
957
|
-
logging.error(f"Error during Alicanto research: {e}")
|
|
958
|
-
return {"output": f"Error during Alicanto research: {e}", "messages": messages}
|