npcsh 1.1.4__py3-none-any.whl → 1.1.6__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 +470 -367
- npcsh/npc_team/corca_example.png +0 -0
- npcsh/npc_team/jinxs/{python_executor.jinx → code/python.jinx} +1 -1
- npcsh/npc_team/jinxs/{bash_executer.jinx → code/sh.jinx} +1 -2
- npcsh/npc_team/jinxs/code/sql.jinx +16 -0
- npcsh/npc_team/jinxs/modes/alicanto.jinx +88 -0
- npcsh/npc_team/jinxs/modes/corca.jinx +28 -0
- npcsh/npc_team/jinxs/modes/guac.jinx +46 -0
- npcsh/npc_team/jinxs/modes/plonk.jinx +57 -0
- npcsh/npc_team/jinxs/modes/pti.jinx +28 -0
- npcsh/npc_team/jinxs/modes/spool.jinx +40 -0
- npcsh/npc_team/jinxs/modes/wander.jinx +81 -0
- npcsh/npc_team/jinxs/modes/yap.jinx +25 -0
- npcsh/npc_team/jinxs/utils/breathe.jinx +20 -0
- npcsh/npc_team/jinxs/utils/core/build.jinx +65 -0
- npcsh/npc_team/jinxs/utils/core/compile.jinx +50 -0
- npcsh/npc_team/jinxs/utils/core/help.jinx +52 -0
- npcsh/npc_team/jinxs/utils/core/init.jinx +41 -0
- npcsh/npc_team/jinxs/utils/core/jinxs.jinx +32 -0
- npcsh/npc_team/jinxs/utils/core/set.jinx +40 -0
- npcsh/npc_team/jinxs/{edit_file.jinx → utils/edit_file.jinx} +1 -1
- npcsh/npc_team/jinxs/utils/flush.jinx +39 -0
- npcsh/npc_team/jinxs/utils/npc-studio.jinx +77 -0
- npcsh/npc_team/jinxs/utils/ots.jinx +61 -0
- npcsh/npc_team/jinxs/utils/plan.jinx +33 -0
- npcsh/npc_team/jinxs/utils/roll.jinx +66 -0
- npcsh/npc_team/jinxs/utils/sample.jinx +56 -0
- npcsh/npc_team/jinxs/utils/search.jinx +130 -0
- npcsh/npc_team/jinxs/utils/serve.jinx +29 -0
- npcsh/npc_team/jinxs/utils/sleep.jinx +116 -0
- npcsh/npc_team/jinxs/utils/trigger.jinx +36 -0
- npcsh/npc_team/jinxs/utils/vixynt.jinx +117 -0
- npcsh/npcsh.py +13 -11
- npcsh/routes.py +97 -1419
- npcsh-1.1.6.data/data/npcsh/npc_team/alicanto.jinx +88 -0
- npcsh-1.1.6.data/data/npcsh/npc_team/breathe.jinx +20 -0
- npcsh-1.1.6.data/data/npcsh/npc_team/build.jinx +65 -0
- npcsh-1.1.6.data/data/npcsh/npc_team/compile.jinx +50 -0
- npcsh-1.1.6.data/data/npcsh/npc_team/corca.jinx +28 -0
- npcsh-1.1.6.data/data/npcsh/npc_team/corca_example.png +0 -0
- {npcsh-1.1.4.data → npcsh-1.1.6.data}/data/npcsh/npc_team/edit_file.jinx +1 -1
- npcsh-1.1.6.data/data/npcsh/npc_team/flush.jinx +39 -0
- npcsh-1.1.6.data/data/npcsh/npc_team/guac.jinx +46 -0
- npcsh-1.1.6.data/data/npcsh/npc_team/help.jinx +52 -0
- npcsh-1.1.6.data/data/npcsh/npc_team/init.jinx +41 -0
- npcsh-1.1.6.data/data/npcsh/npc_team/jinxs.jinx +32 -0
- npcsh-1.1.6.data/data/npcsh/npc_team/npc-studio.jinx +77 -0
- npcsh-1.1.6.data/data/npcsh/npc_team/ots.jinx +61 -0
- npcsh-1.1.6.data/data/npcsh/npc_team/plan.jinx +33 -0
- npcsh-1.1.6.data/data/npcsh/npc_team/plonk.jinx +57 -0
- npcsh-1.1.6.data/data/npcsh/npc_team/pti.jinx +28 -0
- npcsh-1.1.4.data/data/npcsh/npc_team/python_executor.jinx → npcsh-1.1.6.data/data/npcsh/npc_team/python.jinx +1 -1
- npcsh-1.1.6.data/data/npcsh/npc_team/roll.jinx +66 -0
- npcsh-1.1.6.data/data/npcsh/npc_team/sample.jinx +56 -0
- npcsh-1.1.6.data/data/npcsh/npc_team/search.jinx +130 -0
- npcsh-1.1.6.data/data/npcsh/npc_team/serve.jinx +29 -0
- npcsh-1.1.6.data/data/npcsh/npc_team/set.jinx +40 -0
- npcsh-1.1.4.data/data/npcsh/npc_team/bash_executer.jinx → npcsh-1.1.6.data/data/npcsh/npc_team/sh.jinx +1 -2
- npcsh-1.1.6.data/data/npcsh/npc_team/sleep.jinx +116 -0
- npcsh-1.1.6.data/data/npcsh/npc_team/spool.jinx +40 -0
- npcsh-1.1.6.data/data/npcsh/npc_team/sql.jinx +16 -0
- npcsh-1.1.6.data/data/npcsh/npc_team/trigger.jinx +36 -0
- npcsh-1.1.6.data/data/npcsh/npc_team/vixynt.jinx +117 -0
- npcsh-1.1.6.data/data/npcsh/npc_team/wander.jinx +81 -0
- npcsh-1.1.6.data/data/npcsh/npc_team/yap.jinx +25 -0
- {npcsh-1.1.4.dist-info → npcsh-1.1.6.dist-info}/METADATA +1 -10
- npcsh-1.1.6.dist-info/RECORD +124 -0
- npcsh/npc_team/jinxs/image_generation.jinx +0 -29
- npcsh/npc_team/jinxs/internet_search.jinx +0 -31
- npcsh/npc_team/jinxs/kg_search.jinx +0 -43
- npcsh/npc_team/jinxs/memory_search.jinx +0 -36
- npcsh/npc_team/jinxs/screen_cap.jinx +0 -25
- npcsh-1.1.4.data/data/npcsh/npc_team/image_generation.jinx +0 -29
- npcsh-1.1.4.data/data/npcsh/npc_team/internet_search.jinx +0 -31
- npcsh-1.1.4.data/data/npcsh/npc_team/kg_search.jinx +0 -43
- npcsh-1.1.4.data/data/npcsh/npc_team/memory_search.jinx +0 -36
- npcsh-1.1.4.data/data/npcsh/npc_team/screen_cap.jinx +0 -25
- npcsh-1.1.4.dist-info/RECORD +0 -78
- {npcsh-1.1.4.data → npcsh-1.1.6.data}/data/npcsh/npc_team/alicanto.npc +0 -0
- {npcsh-1.1.4.data → npcsh-1.1.6.data}/data/npcsh/npc_team/alicanto.png +0 -0
- {npcsh-1.1.4.data → npcsh-1.1.6.data}/data/npcsh/npc_team/corca.npc +0 -0
- {npcsh-1.1.4.data → npcsh-1.1.6.data}/data/npcsh/npc_team/corca.png +0 -0
- {npcsh-1.1.4.data → npcsh-1.1.6.data}/data/npcsh/npc_team/foreman.npc +0 -0
- {npcsh-1.1.4.data → npcsh-1.1.6.data}/data/npcsh/npc_team/frederic.npc +0 -0
- {npcsh-1.1.4.data → npcsh-1.1.6.data}/data/npcsh/npc_team/frederic4.png +0 -0
- {npcsh-1.1.4.data → npcsh-1.1.6.data}/data/npcsh/npc_team/guac.png +0 -0
- {npcsh-1.1.4.data → npcsh-1.1.6.data}/data/npcsh/npc_team/kadiefa.npc +0 -0
- {npcsh-1.1.4.data → npcsh-1.1.6.data}/data/npcsh/npc_team/kadiefa.png +0 -0
- {npcsh-1.1.4.data → npcsh-1.1.6.data}/data/npcsh/npc_team/npcsh.ctx +0 -0
- {npcsh-1.1.4.data → npcsh-1.1.6.data}/data/npcsh/npc_team/npcsh_sibiji.png +0 -0
- {npcsh-1.1.4.data → npcsh-1.1.6.data}/data/npcsh/npc_team/plonk.npc +0 -0
- {npcsh-1.1.4.data → npcsh-1.1.6.data}/data/npcsh/npc_team/plonk.png +0 -0
- {npcsh-1.1.4.data → npcsh-1.1.6.data}/data/npcsh/npc_team/plonkjr.npc +0 -0
- {npcsh-1.1.4.data → npcsh-1.1.6.data}/data/npcsh/npc_team/plonkjr.png +0 -0
- {npcsh-1.1.4.data → npcsh-1.1.6.data}/data/npcsh/npc_team/sibiji.npc +0 -0
- {npcsh-1.1.4.data → npcsh-1.1.6.data}/data/npcsh/npc_team/sibiji.png +0 -0
- {npcsh-1.1.4.data → npcsh-1.1.6.data}/data/npcsh/npc_team/spool.png +0 -0
- {npcsh-1.1.4.data → npcsh-1.1.6.data}/data/npcsh/npc_team/yap.png +0 -0
- {npcsh-1.1.4.dist-info → npcsh-1.1.6.dist-info}/WHEEL +0 -0
- {npcsh-1.1.4.dist-info → npcsh-1.1.6.dist-info}/entry_points.txt +0 -0
- {npcsh-1.1.4.dist-info → npcsh-1.1.6.dist-info}/licenses/LICENSE +0 -0
- {npcsh-1.1.4.dist-info → npcsh-1.1.6.dist-info}/top_level.txt +0 -0
npcsh/routes.py
CHANGED
|
@@ -1,83 +1,19 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
from typing import Callable, Dict, Any, List, Optional, Union
|
|
1
|
+
from typing import Callable, Dict, Any, List, Optional
|
|
4
2
|
import functools
|
|
5
3
|
import os
|
|
6
|
-
import
|
|
4
|
+
import traceback
|
|
7
5
|
import sys
|
|
6
|
+
import inspect
|
|
8
7
|
from pathlib import Path
|
|
9
8
|
|
|
10
|
-
import
|
|
11
|
-
import shlex
|
|
12
|
-
import time
|
|
13
|
-
from datetime import datetime
|
|
14
|
-
from sqlalchemy import create_engine
|
|
15
|
-
import logging
|
|
16
|
-
import json
|
|
17
|
-
from npcpy.data.load import load_file_contents
|
|
18
|
-
|
|
19
|
-
from npcpy.llm_funcs import (
|
|
20
|
-
get_llm_response,
|
|
21
|
-
gen_image,
|
|
22
|
-
gen_video,
|
|
23
|
-
breathe,
|
|
24
|
-
)
|
|
25
|
-
from npcpy.npc_compiler import initialize_npc_project
|
|
26
|
-
from npcpy.npc_sysenv import render_markdown
|
|
27
|
-
from npcpy.work.plan import execute_plan_command
|
|
28
|
-
from npcpy.work.trigger import execute_trigger_command
|
|
29
|
-
from npcpy.work.desktop import perform_action
|
|
30
|
-
from npcpy.memory.search import execute_rag_command, execute_search_command, execute_brainblast_command
|
|
31
|
-
from npcpy.memory.command_history import CommandHistory, load_kg_from_db, save_kg_to_db
|
|
32
|
-
from npcpy.serve import start_flask_server
|
|
33
|
-
from npcpy.mix.debate import run_debate
|
|
34
|
-
from npcpy.data.image import capture_screenshot
|
|
35
|
-
from npcpy.npc_compiler import NPC, Team, Jinx,initialize_npc_project
|
|
36
|
-
from npcpy.data.web import search_web
|
|
37
|
-
from npcpy.memory.knowledge_graph import kg_sleep_process, kg_dream_process
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
from npcsh._state import (
|
|
41
|
-
NPCSH_VISION_MODEL,
|
|
42
|
-
NPCSH_VISION_PROVIDER,
|
|
43
|
-
set_npcsh_config_value,
|
|
44
|
-
NPCSH_API_URL,
|
|
45
|
-
NPCSH_CHAT_MODEL,
|
|
46
|
-
NPCSH_CHAT_PROVIDER,
|
|
47
|
-
NPCSH_STREAM_OUTPUT,
|
|
48
|
-
NPCSH_IMAGE_GEN_MODEL,
|
|
49
|
-
NPCSH_IMAGE_GEN_PROVIDER,
|
|
50
|
-
NPCSH_VIDEO_GEN_MODEL,
|
|
51
|
-
NPCSH_VIDEO_GEN_PROVIDER,
|
|
52
|
-
NPCSH_EMBEDDING_MODEL,
|
|
53
|
-
NPCSH_EMBEDDING_PROVIDER,
|
|
54
|
-
NPCSH_REASONING_MODEL,
|
|
55
|
-
NPCSH_REASONING_PROVIDER,
|
|
56
|
-
NPCSH_SEARCH_PROVIDER,
|
|
57
|
-
CANONICAL_ARGS,
|
|
58
|
-
normalize_and_expand_flags,
|
|
59
|
-
get_argument_help,
|
|
60
|
-
get_relevant_memories
|
|
61
|
-
|
|
62
|
-
)
|
|
63
|
-
from npcsh.corca import enter_corca_mode
|
|
64
|
-
from npcsh.guac import enter_guac_mode
|
|
65
|
-
from npcsh.plonk import execute_plonk_command, format_plonk_summary
|
|
66
|
-
from npcsh.alicanto import alicanto
|
|
67
|
-
from npcsh.pti import enter_pti_mode
|
|
68
|
-
from npcsh.spool import enter_spool_mode
|
|
69
|
-
from npcsh.wander import enter_wander_mode
|
|
70
|
-
from npcsh.yap import enter_yap_mode
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
NPC_STUDIO_DIR = Path.home() / ".npcsh" / "npc-studio"
|
|
9
|
+
from npcpy.npc_compiler import Jinx, load_jinxs_from_directory, extract_jinx_inputs
|
|
75
10
|
|
|
76
11
|
|
|
77
12
|
class CommandRouter:
|
|
78
13
|
def __init__(self):
|
|
79
14
|
self.routes = {}
|
|
80
15
|
self.help_info = {}
|
|
16
|
+
self.jinx_routes = {}
|
|
81
17
|
|
|
82
18
|
def route(self, command: str, help_text: str = "") -> Callable:
|
|
83
19
|
def wrapper(func):
|
|
@@ -91,1372 +27,114 @@ class CommandRouter:
|
|
|
91
27
|
return wrapped_func
|
|
92
28
|
return wrapper
|
|
93
29
|
|
|
94
|
-
def
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
command_name = command_str.split()[0].lstrip('/')
|
|
99
|
-
route_func = self.get_route(command_name)
|
|
100
|
-
if route_func:
|
|
101
|
-
return route_func(command=command_str, **kwargs)
|
|
102
|
-
return None
|
|
103
|
-
|
|
104
|
-
def get_commands(self) -> List[str]:
|
|
105
|
-
return list(self.routes.keys())
|
|
106
|
-
|
|
107
|
-
def get_help(self, command: str = None) -> Dict[str, str]:
|
|
108
|
-
if command:
|
|
109
|
-
if command in self.help_info:
|
|
110
|
-
return {command: self.help_info[command]}
|
|
111
|
-
return {}
|
|
112
|
-
return self.help_info
|
|
113
|
-
|
|
114
|
-
router = CommandRouter()
|
|
115
|
-
def get_help_text():
|
|
116
|
-
commands = router.get_commands()
|
|
117
|
-
help_info = router.help_info
|
|
118
|
-
|
|
119
|
-
commands.sort()
|
|
120
|
-
output = "# Available Commands\n\n"
|
|
121
|
-
for cmd in commands:
|
|
122
|
-
help_text = help_info.get(cmd, "")
|
|
123
|
-
output += f"/{cmd} - {help_text}\n\n"
|
|
124
|
-
|
|
125
|
-
arg_help_map = get_argument_help()
|
|
126
|
-
if arg_help_map:
|
|
127
|
-
output += "## Common Command-Line Flags\n\n"
|
|
128
|
-
output += "The shortest unambiguous prefix works (e.g., `-t` for `--temperature`).\n\n"
|
|
30
|
+
def load_jinx_routes(self, jinxs_dir: str):
|
|
31
|
+
if not os.path.exists(jinxs_dir):
|
|
32
|
+
print(f"Jinxs directory not found: {jinxs_dir}")
|
|
33
|
+
return
|
|
129
34
|
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
ALIAS_WIDTH = 12
|
|
140
|
-
COLUMN_SEPARATOR = " | "
|
|
141
|
-
|
|
142
|
-
rows_per_column = (len(all_args_to_show) + NUM_COLUMNS - 1) // NUM_COLUMNS
|
|
143
|
-
columns = [all_args_to_show[i:i + rows_per_column] for i in range(0, len(all_args_to_show), rows_per_column)]
|
|
144
|
-
|
|
145
|
-
def get_shortest_alias(arg):
|
|
146
|
-
if arg in arg_help_map and arg_help_map[arg]:
|
|
147
|
-
return min(arg_help_map[arg], key=len)
|
|
148
|
-
return ""
|
|
149
|
-
|
|
150
|
-
header_parts = []
|
|
151
|
-
for _ in range(NUM_COLUMNS):
|
|
152
|
-
flag_header = "Flag".ljust(FLAG_WIDTH)
|
|
153
|
-
alias_header = "Shorthand".ljust(ALIAS_WIDTH)
|
|
154
|
-
header_parts.append(f"{flag_header}{alias_header}")
|
|
155
|
-
output += COLUMN_SEPARATOR.join(header_parts) + "\n"
|
|
156
|
-
|
|
157
|
-
divider_parts = []
|
|
158
|
-
for _ in range(NUM_COLUMNS):
|
|
159
|
-
|
|
160
|
-
divider_part = "-" * (FLAG_WIDTH + ALIAS_WIDTH)
|
|
161
|
-
divider_parts.append(divider_part)
|
|
162
|
-
output += COLUMN_SEPARATOR.join(divider_parts) + "\n"
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
for i in range(rows_per_column):
|
|
166
|
-
row_parts = []
|
|
167
|
-
for col_idx in range(NUM_COLUMNS):
|
|
168
|
-
if col_idx < len(columns) and i < len(columns[col_idx]):
|
|
169
|
-
arg = columns[col_idx][i]
|
|
170
|
-
alias = get_shortest_alias(arg)
|
|
171
|
-
alias_display = f"(-{alias})" if alias else ""
|
|
172
|
-
|
|
173
|
-
flag_part = f"--{arg}".ljust(FLAG_WIDTH)
|
|
174
|
-
alias_part = alias_display.ljust(ALIAS_WIDTH)
|
|
175
|
-
row_parts.append(f"{flag_part}{alias_part}")
|
|
176
|
-
else:
|
|
177
|
-
|
|
178
|
-
row_parts.append(" " * (FLAG_WIDTH + ALIAS_WIDTH))
|
|
179
|
-
|
|
180
|
-
output += COLUMN_SEPARATOR.join(row_parts) + "\n"
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
output += "```\n"
|
|
184
|
-
|
|
185
|
-
output += """
|
|
186
|
-
\n## Note
|
|
187
|
-
- Bash commands and programs can be executed directly (try bash first, then LLM).
|
|
188
|
-
- Use '/exit' or '/quit' to exit the current NPC mode or the npcsh shell.
|
|
189
|
-
- Jinxs defined for the current NPC or Team can also be used like commands (e.g., /screenshot).
|
|
190
|
-
"""
|
|
191
|
-
return output
|
|
192
|
-
def safe_get(kwargs, key, default=None):
|
|
193
|
-
return kwargs.get(key, default)
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
@router.route("build", "Build deployment artifacts for NPC team")
|
|
197
|
-
def build_handler(command: str, **kwargs):
|
|
198
|
-
parts = shlex.split(command)
|
|
199
|
-
|
|
200
|
-
target = safe_get(kwargs, 'target', 'flask')
|
|
201
|
-
output_dir = safe_get(kwargs, 'output', './build')
|
|
202
|
-
team_path = safe_get(kwargs, 'team', './npc_team')
|
|
203
|
-
|
|
204
|
-
if len(parts) > 1:
|
|
205
|
-
target = parts[1]
|
|
206
|
-
|
|
207
|
-
build_config = {
|
|
208
|
-
'team_path': os.path.abspath(team_path),
|
|
209
|
-
'output_dir': os.path.abspath(output_dir),
|
|
210
|
-
'target': target,
|
|
211
|
-
'port': safe_get(kwargs, 'port', 5337),
|
|
212
|
-
'cors_origins': safe_get(kwargs, 'cors', None),
|
|
213
|
-
}
|
|
214
|
-
|
|
215
|
-
builders = {
|
|
216
|
-
'flask': build_flask_server,
|
|
217
|
-
'docker': build_docker_compose,
|
|
218
|
-
'cli': build_cli_executable,
|
|
219
|
-
'static': build_static_site,
|
|
220
|
-
}
|
|
221
|
-
|
|
222
|
-
if target not in builders:
|
|
223
|
-
return {
|
|
224
|
-
"output": f"Unknown target: {target}. Available: {list(builders.keys())}",
|
|
225
|
-
"messages": kwargs.get('messages', [])
|
|
226
|
-
}
|
|
227
|
-
|
|
228
|
-
return builders[target](build_config, **kwargs)
|
|
229
|
-
|
|
230
|
-
@router.route("breathe", "Condense context on a regular cadence")
|
|
231
|
-
def breathe_handler(command: str, **kwargs):
|
|
232
|
-
|
|
233
|
-
result = breathe(**kwargs)
|
|
234
|
-
if isinstance(result, dict):
|
|
235
|
-
return result
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
@router.route("compile", "Compile NPC profiles")
|
|
243
|
-
def compile_handler(command: str, **kwargs):
|
|
244
|
-
messages = safe_get(kwargs, "messages", [])
|
|
245
|
-
npc_team_dir = safe_get(kwargs, 'current_path', './npc_team')
|
|
246
|
-
parts = command.split()
|
|
247
|
-
npc_file_path_arg = parts[1] if len(parts) > 1 else None
|
|
248
|
-
output = ""
|
|
249
|
-
try:
|
|
250
|
-
if npc_file_path_arg:
|
|
251
|
-
npc_full_path = os.path.abspath(npc_file_path_arg)
|
|
252
|
-
if os.path.exists(npc_full_path):
|
|
253
|
-
npc = NPC(npc_full_path)
|
|
254
|
-
output = f"Compiled NPC: {npc_full_path}"
|
|
255
|
-
else:
|
|
256
|
-
output = f"Error: NPC file not found: {npc_full_path}"
|
|
257
|
-
else:
|
|
258
|
-
npc = NPC(npc_full_path)
|
|
259
|
-
|
|
260
|
-
output = f"Compiled all NPCs in directory: {npc_team_dir}"
|
|
261
|
-
except NameError:
|
|
262
|
-
output = "Compile functions (compile_npc_file, compile_team_npcs) not available."
|
|
263
|
-
except Exception as e:
|
|
264
|
-
traceback.print_exc()
|
|
265
|
-
output = f"Error compiling: {e}"
|
|
266
|
-
return {"output": output, "messages": messages, "npc": npc}
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
@router.route("corca", "Enter the Corca MCP-powered agentic shell. Usage: /corca [--mcp-server-path path]")
|
|
271
|
-
def corca_handler(command: str, **kwargs):
|
|
272
|
-
from npcsh._state import initial_state, setup_shell
|
|
273
|
-
command_history, team, default_npc = setup_shell()
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
return enter_corca_mode(command=command,
|
|
277
|
-
command_history = command_history,
|
|
278
|
-
shell_state=initial_state)
|
|
279
|
-
|
|
280
|
-
@router.route("flush", "Flush the last N messages")
|
|
281
|
-
def flush_handler(command: str, **kwargs):
|
|
282
|
-
messages = safe_get(kwargs, "messages", [])
|
|
283
|
-
try:
|
|
284
|
-
parts = command.split()
|
|
285
|
-
n = int(parts[1]) if len(parts) > 1 else 1
|
|
286
|
-
except (ValueError, IndexError):
|
|
287
|
-
return {"output": "Usage: /flush [number_of_messages_to_flush]", "messages": messages}
|
|
288
|
-
|
|
289
|
-
if n <= 0:
|
|
290
|
-
return {"output": "Error: Number of messages must be positive.", "messages": messages}
|
|
291
|
-
|
|
292
|
-
new_messages = list(messages)
|
|
293
|
-
original_len = len(new_messages)
|
|
294
|
-
removed_count = 0
|
|
295
|
-
|
|
296
|
-
if new_messages and new_messages[0].get("role") == "system":
|
|
297
|
-
system_message = new_messages[0]
|
|
298
|
-
working_messages = new_messages[1:]
|
|
299
|
-
num_to_remove = min(n, len(working_messages))
|
|
300
|
-
if num_to_remove > 0:
|
|
301
|
-
final_messages = [system_message] + working_messages[:-num_to_remove]
|
|
302
|
-
removed_count = num_to_remove
|
|
303
|
-
else:
|
|
304
|
-
final_messages = [system_message]
|
|
305
|
-
else:
|
|
306
|
-
num_to_remove = min(n, original_len)
|
|
307
|
-
if num_to_remove > 0:
|
|
308
|
-
final_messages = new_messages[:-num_to_remove]
|
|
309
|
-
removed_count = num_to_remove
|
|
310
|
-
else:
|
|
311
|
-
final_messages = []
|
|
312
|
-
|
|
313
|
-
output = f"Flushed {removed_count} message(s). Context is now {len(final_messages)} messages."
|
|
314
|
-
return {"output": output, "messages": final_messages}
|
|
315
|
-
|
|
316
|
-
@router.route("guac", "Enter guac mode")
|
|
317
|
-
def guac_handler(command, **kwargs):
|
|
318
|
-
'''
|
|
319
|
-
Guac ignores input npc and npc_team dirs and manually sets them to be at ~/.npcsh/guac/
|
|
320
|
-
|
|
321
|
-
'''
|
|
322
|
-
config_dir = safe_get(kwargs, 'config_dir', None)
|
|
323
|
-
plots_dir = safe_get(kwargs, 'plots_dir', None)
|
|
324
|
-
refresh_period = safe_get(kwargs, 'refresh_period', 100)
|
|
325
|
-
lang = safe_get(kwargs, 'lang', None)
|
|
326
|
-
messages = safe_get(kwargs, "messages", [])
|
|
327
|
-
db_conn = safe_get(kwargs, 'db_conn', create_engine('sqlite:///'+os.path.expanduser('~/npcsh_history.db')))
|
|
328
|
-
|
|
329
|
-
npc_file = '~/.npcsh/guac/npc_team/guac.npc'
|
|
330
|
-
npc_team_dir = os.path.expanduser('~/.npcsh/guac/npc_team/')
|
|
331
|
-
|
|
332
|
-
npc = NPC(file=npc_file, db_conn=db_conn)
|
|
333
|
-
|
|
334
|
-
team = Team(npc_team_dir, db_conn=db_conn)
|
|
335
|
-
|
|
35
|
+
for root, dirs, files in os.walk(jinxs_dir):
|
|
36
|
+
for filename in files:
|
|
37
|
+
if filename.endswith('.jinx'):
|
|
38
|
+
jinx_path = os.path.join(root, filename)
|
|
39
|
+
try:
|
|
40
|
+
jinx = Jinx(jinx_path=jinx_path)
|
|
41
|
+
self.register_jinx(jinx)
|
|
42
|
+
except Exception as e:
|
|
43
|
+
print(f"Error loading jinx {filename}: {e}")
|
|
336
44
|
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
team=team,
|
|
340
|
-
config_dir=config_dir,
|
|
341
|
-
plots_dir=plots_dir,
|
|
342
|
-
npc_team_dir=npc_team_dir,
|
|
343
|
-
refresh_period=refresh_period, lang=lang)
|
|
344
|
-
|
|
345
|
-
return {"output": 'Exiting Guac Mode', "messages": safe_get(kwargs, "messages", [])}
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
@router.route("help", "Show help for commands, NPCs, or Jinxs. Usage: /help [topic]")
|
|
349
|
-
def help_handler(command: str, **kwargs):
|
|
350
|
-
messages = safe_get(kwargs, "messages", [])
|
|
351
|
-
parts = shlex.split(command)
|
|
352
|
-
if len(parts) < 2:
|
|
353
|
-
return {"output": get_help_text(), "messages": messages}
|
|
354
|
-
target = parts[1].lstrip('/')
|
|
355
|
-
output = ""
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
if target in router.get_commands():
|
|
360
|
-
help_text = router.get_help(target).get(target, "No description available.")
|
|
361
|
-
output = f"## Help for Command: `/{target}`\n\n- **Description**: {help_text}"
|
|
362
|
-
return {"output": output, "messages": messages}
|
|
363
|
-
|
|
364
|
-
team = safe_get(kwargs, 'team')
|
|
365
|
-
if team and target in team.npcs:
|
|
366
|
-
npc_obj = team.npcs[target]
|
|
367
|
-
output = f"## Help for NPC: `{target}`\n\n"
|
|
368
|
-
output += f"- **Primary Directive**: {npc_obj.primary_directive}\n"
|
|
369
|
-
output += f"- **Default Model**: `{npc_obj.model}`\n"
|
|
370
|
-
output += f"- **Default Provider**: `{npc_obj.provider}`\n"
|
|
371
|
-
if hasattr(npc_obj, 'jinxs_dict') and npc_obj.jinxs_dict:
|
|
372
|
-
jinx_names = ", ".join([f"`{j}`" for j in npc_obj.jinxs_dict.keys()])
|
|
373
|
-
output += f"- **Associated Jinxs**: {jinx_names}\n"
|
|
374
|
-
return {"output": output, "messages": messages}
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
npc = safe_get(kwargs, 'npc')
|
|
378
|
-
jinx_obj = None
|
|
379
|
-
source = ""
|
|
380
|
-
if npc and hasattr(npc, 'jinxs_dict') and target in npc.jinxs_dict:
|
|
381
|
-
jinx_obj = npc.jinxs_dict[target]
|
|
382
|
-
source = f" (from NPC: `{npc.name}`)"
|
|
383
|
-
elif team and hasattr(team, 'jinxs_dict') and target in team.jinxs_dict:
|
|
384
|
-
jinx_obj = team.jinxs_dict[target]
|
|
385
|
-
source = f" (from Team: `{team.name}`)"
|
|
386
|
-
|
|
387
|
-
if jinx_obj:
|
|
388
|
-
output = f"## Help for Jinx: `/{target}`{source}\n\n"
|
|
389
|
-
output += f"- **Description**: {jinx_obj.description}\n"
|
|
390
|
-
if hasattr(jinx_obj, 'inputs') and jinx_obj.inputs:
|
|
391
|
-
inputs_str = json.dumps(jinx_obj.inputs, indent=2)
|
|
392
|
-
output += f"- **Inputs**:\n```json\n{inputs_str}\n```\n"
|
|
393
|
-
return {"output": output, "messages": messages}
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
return {"output": f"Sorry, no help topic found for `{target}`.", "messages": messages}
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
@router.route("init", "Initialize NPC project")
|
|
402
|
-
def init_handler(command: str, **kwargs):
|
|
403
|
-
messages = safe_get(kwargs, "messages", [])
|
|
404
|
-
try:
|
|
405
|
-
parts = shlex.split(command)
|
|
406
|
-
directory = "."
|
|
407
|
-
templates = None
|
|
408
|
-
context = None
|
|
409
|
-
|
|
410
|
-
if len(parts) > 1 and not parts[1].startswith("-"):
|
|
411
|
-
directory = parts[1]
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
initialize_npc_project(
|
|
415
|
-
directory=directory,
|
|
416
|
-
templates=templates,
|
|
417
|
-
context=context,
|
|
418
|
-
model=safe_get(kwargs, 'model'),
|
|
419
|
-
provider=safe_get(kwargs, 'provider')
|
|
420
|
-
)
|
|
421
|
-
output = f"NPC project initialized in {os.path.abspath(directory)}."
|
|
422
|
-
except NameError:
|
|
423
|
-
output = "Init function (initialize_npc_project) not available."
|
|
424
|
-
except Exception as e:
|
|
425
|
-
traceback.print_exc()
|
|
426
|
-
output = f"Error initializing project: {e}"
|
|
427
|
-
return {"output": output, "messages": messages}
|
|
428
|
-
|
|
429
|
-
def ensure_repo():
|
|
430
|
-
"""Clone or update the npc-studio repo."""
|
|
431
|
-
if not NPC_STUDIO_DIR.exists():
|
|
432
|
-
os.makedirs(NPC_STUDIO_DIR.parent, exist_ok=True)
|
|
433
|
-
subprocess.check_call([
|
|
434
|
-
"git", "clone",
|
|
435
|
-
"https://github.com/npc-worldwide/npc-studio.git",
|
|
436
|
-
str(NPC_STUDIO_DIR)
|
|
437
|
-
])
|
|
438
|
-
else:
|
|
439
|
-
subprocess.check_call(
|
|
440
|
-
["git", "pull"],
|
|
441
|
-
cwd=NPC_STUDIO_DIR
|
|
442
|
-
)
|
|
443
|
-
|
|
444
|
-
def install_dependencies():
|
|
445
|
-
"""Install npm and pip dependencies."""
|
|
446
|
-
|
|
447
|
-
subprocess.check_call(["npm", "install"], cwd=NPC_STUDIO_DIR)
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
req_file = NPC_STUDIO_DIR / "requirements.txt"
|
|
451
|
-
if req_file.exists():
|
|
452
|
-
subprocess.check_call([sys.executable, "-m", "pip", "install", "-r", str(req_file)])
|
|
453
|
-
def launch_npc_studio(path_to_open: str = None):
|
|
454
|
-
"""
|
|
455
|
-
Launch the NPC Studio backend + frontend.
|
|
456
|
-
Returns PIDs for processes.
|
|
457
|
-
"""
|
|
458
|
-
ensure_repo()
|
|
459
|
-
install_dependencies()
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
backend = subprocess.Popen(
|
|
463
|
-
[sys.executable, "npc_studio_serve.py"],
|
|
464
|
-
cwd=NPC_STUDIO_DIR,
|
|
465
|
-
shell = False
|
|
466
|
-
)
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
dev_server = subprocess.Popen(
|
|
470
|
-
["npm", "run", "dev"],
|
|
471
|
-
cwd=NPC_STUDIO_DIR,
|
|
472
|
-
shell=False
|
|
473
|
-
)
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
frontend = subprocess.Popen(
|
|
477
|
-
["npm", "start"],
|
|
478
|
-
cwd=NPC_STUDIO_DIR,
|
|
479
|
-
shell=False
|
|
480
|
-
)
|
|
481
|
-
|
|
482
|
-
return backend, dev_server, frontend
|
|
483
|
-
|
|
484
|
-
@router.route("npc-studio", "Start npc studio")
|
|
485
|
-
def npc_studio_handler(command: str, **kwargs):
|
|
486
|
-
messages = kwargs.get("messages", [])
|
|
487
|
-
user_command = " ".join(command.split()[1:])
|
|
488
|
-
|
|
489
|
-
try:
|
|
490
|
-
backend, electron, frontend = launch_npc_studio(user_command or None)
|
|
491
|
-
return {
|
|
492
|
-
"output": f"NPC Studio started!\nBackend PID={backend.pid}, Electron PID={electron.pid} Frontend PID={frontend.pid}",
|
|
493
|
-
"messages": messages
|
|
494
|
-
}
|
|
495
|
-
except Exception as e:
|
|
496
|
-
return {
|
|
497
|
-
"output": f"Failed to start NPC Studio: {e}",
|
|
498
|
-
"messages": messages
|
|
499
|
-
}
|
|
500
|
-
@router.route("ots", "Take screenshot and analyze with vision model")
|
|
501
|
-
def ots_handler(command: str, **kwargs):
|
|
502
|
-
command_parts = command.split()
|
|
503
|
-
image_paths = []
|
|
504
|
-
npc = safe_get(kwargs, 'npc')
|
|
505
|
-
vision_model = safe_get(kwargs,
|
|
506
|
-
'vmodel',
|
|
507
|
-
NPCSH_VISION_MODEL)
|
|
508
|
-
vision_provider = safe_get(kwargs,
|
|
509
|
-
'vprovider',
|
|
510
|
-
NPCSH_VISION_PROVIDER)
|
|
511
|
-
messages = safe_get(kwargs,
|
|
512
|
-
'messages',
|
|
513
|
-
[])
|
|
514
|
-
stream = safe_get(kwargs,
|
|
515
|
-
'stream',
|
|
516
|
-
NPCSH_STREAM_OUTPUT)
|
|
517
|
-
|
|
518
|
-
try:
|
|
519
|
-
if len(command_parts) > 1:
|
|
520
|
-
for img_path_arg in command_parts[1:]:
|
|
521
|
-
full_path = os.path.abspath(img_path_arg)
|
|
522
|
-
if os.path.exists(full_path):
|
|
523
|
-
image_paths.append(full_path)
|
|
524
|
-
else:
|
|
525
|
-
return {"output": f"Error: Image file not found at {full_path}", "messages": messages}
|
|
526
|
-
else:
|
|
527
|
-
screenshot_info = capture_screenshot(full=False)
|
|
528
|
-
if screenshot_info and "file_path" in screenshot_info:
|
|
529
|
-
image_paths.append(screenshot_info["file_path"])
|
|
530
|
-
print(f"Screenshot captured: {screenshot_info.get('filename', os.path.basename(screenshot_info['file_path']))}")
|
|
531
|
-
else:
|
|
532
|
-
return {"output": "Error: Failed to capture screenshot.", "messages": messages}
|
|
533
|
-
|
|
534
|
-
if not image_paths:
|
|
535
|
-
return {"output": "No valid images found or captured.", "messages": messages}
|
|
536
|
-
|
|
537
|
-
user_prompt = safe_get(kwargs, 'stdin_input')
|
|
538
|
-
if user_prompt is None:
|
|
539
|
-
try:
|
|
540
|
-
user_prompt = input(
|
|
541
|
-
"Enter a prompt for the LLM about these images (or press Enter to skip): "
|
|
542
|
-
)
|
|
543
|
-
except EOFError:
|
|
544
|
-
user_prompt = "Describe the image(s)."
|
|
545
|
-
|
|
546
|
-
if not user_prompt or not user_prompt.strip():
|
|
547
|
-
user_prompt = "Describe the image(s)."
|
|
548
|
-
|
|
549
|
-
response_data = get_llm_response(
|
|
550
|
-
prompt=user_prompt,
|
|
551
|
-
model=vision_model,
|
|
552
|
-
provider=vision_provider,
|
|
553
|
-
messages=messages,
|
|
554
|
-
images=image_paths,
|
|
555
|
-
stream=stream,
|
|
556
|
-
npc=npc,
|
|
557
|
-
api_url=safe_get(kwargs, 'api_url'),
|
|
558
|
-
api_key=safe_get(kwargs, 'api_key')
|
|
559
|
-
)
|
|
560
|
-
return {"output": response_data.get('response'), "messages": response_data.get('messages'), "model": vision_model, "provider": vision_provider}
|
|
561
|
-
|
|
562
|
-
except Exception as e:
|
|
563
|
-
traceback.print_exc()
|
|
564
|
-
return {"output": f"Error during /ots command: {e}", "messages": messages}
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
@router.route("plan", "Execute a plan command")
|
|
570
|
-
def plan_handler(command: str, **kwargs):
|
|
571
|
-
messages = safe_get(kwargs, "messages", [])
|
|
572
|
-
user_command = " ".join(command.split()[1:])
|
|
573
|
-
if not user_command:
|
|
574
|
-
return {"output": "Usage: /plan <description_of_plan>", "messages": messages}
|
|
575
|
-
|
|
576
|
-
return execute_plan_command(command=user_command, **kwargs)
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
@router.route("pti", "Enter Pardon-The-Interruption mode for human-in-the-loop reasoning.")
|
|
584
|
-
def pti_handler(command: str, **kwargs):
|
|
585
|
-
return enter_pti_mode(command=command, **kwargs)
|
|
586
|
-
|
|
587
|
-
@router.route("plonk", "Use vision model to interact with GUI. Usage: /plonk <task description>")
|
|
588
|
-
def plonk_handler(command: str, **kwargs):
|
|
589
|
-
messages = safe_get(kwargs, "messages", [])
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
positional_args = safe_get(kwargs, 'positional_args', [])
|
|
594
|
-
request_str = " ".join(positional_args)
|
|
595
|
-
|
|
596
|
-
if not request_str:
|
|
597
|
-
return {"output": "Usage: /plonk <task_description> [--vmodel model_name] [--vprovider provider_name]", "messages": messages}
|
|
598
|
-
|
|
599
|
-
try:
|
|
600
|
-
plonk_context = safe_get(kwargs, 'plonk_context')
|
|
45
|
+
def register_jinx(self, jinx: Jinx):
|
|
46
|
+
command_name = jinx.jinx_name
|
|
601
47
|
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
request=request_str,
|
|
605
|
-
model=safe_get(kwargs, 'vmodel', NPCSH_VISION_MODEL),
|
|
606
|
-
provider=safe_get(kwargs, 'vprovider', NPCSH_VISION_PROVIDER),
|
|
607
|
-
npc=safe_get(kwargs, 'npc'),
|
|
608
|
-
plonk_context=plonk_context,
|
|
609
|
-
debug=True
|
|
610
|
-
)
|
|
48
|
+
def jinx_handler(command: str, **kwargs):
|
|
49
|
+
return self._execute_jinx(jinx, command, **kwargs)
|
|
611
50
|
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
return {"output": output_report, "messages": messages}
|
|
615
|
-
else:
|
|
616
|
-
return {"output": "Plonk command did not complete within the maximum number of iterations.", "messages": messages}
|
|
617
|
-
|
|
618
|
-
except Exception as e:
|
|
619
|
-
traceback.print_exc()
|
|
620
|
-
return {"output": f"Error executing plonk command: {e}", "messages": messages}
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
@router.route("brainblast", "Execute an advanced chunked search on command history")
|
|
624
|
-
def brainblast_handler(command: str, **kwargs):
|
|
625
|
-
messages = safe_get(kwargs, "messages", [])
|
|
626
|
-
parts = shlex.split(command)
|
|
627
|
-
search_query = " ".join(parts[1:]) if len(parts) > 1 else ""
|
|
628
|
-
|
|
629
|
-
if not search_query:
|
|
630
|
-
return {"output": "Usage: /brainblast <search_terms>", "messages": messages}
|
|
51
|
+
self.jinx_routes[command_name] = jinx_handler
|
|
52
|
+
self.help_info[command_name] = jinx.description or "Jinx command"
|
|
631
53
|
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
db_path = safe_get(kwargs, "history_db_path", os.path.expanduser('~/npcsh_history.db'))
|
|
54
|
+
def _execute_jinx(self, jinx: Jinx, command: str, **kwargs):
|
|
55
|
+
messages = kwargs.get("messages", [])
|
|
56
|
+
npc = kwargs.get('npc')
|
|
57
|
+
|
|
638
58
|
try:
|
|
639
|
-
|
|
640
|
-
kwargs['command_history'] = command_history
|
|
641
|
-
except Exception as e:
|
|
642
|
-
return {"output": f"Error connecting to command history: {e}", "messages": messages}
|
|
643
|
-
|
|
644
|
-
try:
|
|
645
|
-
|
|
646
|
-
if 'messages' in kwargs:
|
|
647
|
-
del kwargs['messages']
|
|
59
|
+
import shlex
|
|
648
60
|
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
command=search_query,
|
|
652
|
-
**kwargs)
|
|
653
|
-
except Exception as e:
|
|
654
|
-
traceback.print_exc()
|
|
655
|
-
return {"output": f"Error executing brainblast command: {e}", "messages": messages}
|
|
656
|
-
|
|
657
|
-
@router.route("rag", "Execute a RAG command using ChromaDB embeddings with optional file input (-f/--file)")
|
|
658
|
-
def rag_handler(command: str, **kwargs):
|
|
659
|
-
parts = shlex.split(command)
|
|
660
|
-
user_command = []
|
|
661
|
-
file_paths = []
|
|
662
|
-
|
|
663
|
-
i = 1
|
|
664
|
-
while i < len(parts):
|
|
665
|
-
if parts[i] == "-f" or parts[i] == "--file":
|
|
666
|
-
|
|
667
|
-
if i + 1 < len(parts):
|
|
668
|
-
file_paths.append(parts[i + 1])
|
|
669
|
-
i += 2
|
|
670
|
-
else:
|
|
671
|
-
return {"output": "Error: -f/--file flag needs a file path", "messages": messages}
|
|
672
|
-
else:
|
|
673
|
-
|
|
674
|
-
user_command.append(parts[i])
|
|
675
|
-
i += 1
|
|
676
|
-
|
|
677
|
-
user_command = " ".join(user_command)
|
|
678
|
-
|
|
679
|
-
vector_db_path = safe_get(kwargs, "vector_db_path", os.path.expanduser('~/npcsh_chroma.db'))
|
|
680
|
-
embedding_model = safe_get(kwargs, "emodel", NPCSH_EMBEDDING_MODEL)
|
|
681
|
-
embedding_provider = safe_get(kwargs, "eprovider", NPCSH_EMBEDDING_PROVIDER)
|
|
682
|
-
|
|
683
|
-
if not user_command and not file_paths:
|
|
684
|
-
return {"output": "Usage: /rag [-f file_path] <query>", "messages": kwargs.get('messages', [])}
|
|
685
|
-
|
|
686
|
-
try:
|
|
687
|
-
|
|
688
|
-
file_contents = []
|
|
689
|
-
for file_path in file_paths:
|
|
690
|
-
try:
|
|
691
|
-
chunks = load_file_contents(file_path)
|
|
692
|
-
file_name = os.path.basename(file_path)
|
|
693
|
-
file_contents.extend([f"[{file_name}] {chunk}" for chunk in chunks])
|
|
694
|
-
except Exception as file_err:
|
|
695
|
-
file_contents.append(f"Error processing file {file_path}: {str(file_err)}")
|
|
696
|
-
exe_rag = execute_rag_command(
|
|
697
|
-
command=user_command,
|
|
698
|
-
vector_db_path=vector_db_path,
|
|
699
|
-
embedding_model=embedding_model,
|
|
700
|
-
embedding_provider=embedding_provider,
|
|
701
|
-
file_contents=file_contents if file_paths else None,
|
|
702
|
-
**kwargs
|
|
703
|
-
)
|
|
704
|
-
return {'output':exe_rag.get('response'), 'messages': exe_rag.get('messages', kwargs.get('messages', []))}
|
|
705
|
-
|
|
706
|
-
except Exception as e:
|
|
707
|
-
traceback.print_exc()
|
|
708
|
-
return {"output": f"Error executing RAG command: {e}", "messages": kwargs.get('messages', [])}
|
|
709
|
-
@router.route("roll", "generate a video")
|
|
710
|
-
def roll_handler(command: str, **kwargs):
|
|
711
|
-
messages = safe_get(kwargs, "messages", [])
|
|
712
|
-
prompt = " ".join(command.split()[1:])
|
|
713
|
-
num_frames = safe_get(kwargs, 'num_frames', 125)
|
|
714
|
-
width = safe_get(kwargs, 'width', 256)
|
|
715
|
-
height = safe_get(kwargs, 'height', 256)
|
|
716
|
-
output_path = safe_get(kwargs, 'output_path', "output.mp4")
|
|
717
|
-
if not prompt:
|
|
718
|
-
return {"output": "Usage: /roll <your prompt>", "messages": messages}
|
|
719
|
-
try:
|
|
720
|
-
result = gen_video(
|
|
721
|
-
prompt=prompt,
|
|
722
|
-
model=safe_get(kwargs, 'vgmodel', NPCSH_VIDEO_GEN_MODEL),
|
|
723
|
-
provider=safe_get(kwargs, 'vgprovider', NPCSH_VIDEO_GEN_PROVIDER),
|
|
724
|
-
npc=safe_get(kwargs, 'npc'),
|
|
725
|
-
num_frames = num_frames,
|
|
726
|
-
width = width,
|
|
727
|
-
height = height,
|
|
728
|
-
output_path=output_path,
|
|
61
|
+
parts = shlex.split(command)
|
|
62
|
+
args = parts[1:] if len(parts) > 1 else []
|
|
729
63
|
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
return {"output": "Usage: /sample <your prompt> [-m --model] model [-p --provider] provider",
|
|
748
|
-
"messages": messages}
|
|
749
|
-
|
|
750
|
-
try:
|
|
751
|
-
result = get_llm_response(
|
|
752
|
-
prompt=prompt,
|
|
753
|
-
**kwargs
|
|
754
|
-
)
|
|
755
|
-
if result and isinstance(result, dict):
|
|
756
|
-
return {
|
|
757
|
-
"output": result.get('response'),
|
|
758
|
-
"messages": result.get('messages', messages),
|
|
759
|
-
"model": kwargs.get('model'),
|
|
760
|
-
"provider":kwargs.get('provider'),
|
|
761
|
-
"npc":kwargs.get("npc"),
|
|
762
|
-
}
|
|
763
|
-
else:
|
|
764
|
-
|
|
765
|
-
return {"output": str(result), "messages": messages}
|
|
766
|
-
|
|
767
|
-
except Exception as e:
|
|
768
|
-
traceback.print_exc()
|
|
769
|
-
return {"output": f"Error sampling LLM: {e}", "messages": messages}
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
@router.route("search", "Execute web search or memory/KG search")
|
|
774
|
-
def search_handler(command: str, **kwargs):
|
|
775
|
-
messages = safe_get(kwargs, "messages", [])
|
|
776
|
-
|
|
777
|
-
positional_args = safe_get(kwargs, 'positional_args', [])
|
|
778
|
-
|
|
779
|
-
search_type = None
|
|
780
|
-
query_parts = []
|
|
781
|
-
|
|
782
|
-
i = 0
|
|
783
|
-
while i < len(positional_args):
|
|
784
|
-
arg = positional_args[i]
|
|
785
|
-
if arg in ['-m', '-mem', '--memory']:
|
|
786
|
-
search_type = 'memory'
|
|
787
|
-
i += 1
|
|
788
|
-
elif arg in ['-kg', '--knowledge-graph']:
|
|
789
|
-
search_type = 'kg'
|
|
790
|
-
i += 1
|
|
791
|
-
else:
|
|
792
|
-
query_parts.append(arg)
|
|
793
|
-
i += 1
|
|
794
|
-
|
|
795
|
-
query = " ".join(query_parts)
|
|
796
|
-
|
|
797
|
-
if not query:
|
|
798
|
-
return {
|
|
799
|
-
"output": (
|
|
800
|
-
"Usage:\n"
|
|
801
|
-
" /search <query> - Web search\n"
|
|
802
|
-
" /search -m <query> - Memory search\n"
|
|
803
|
-
" /search -kg <query> - Knowledge graph search"
|
|
804
|
-
),
|
|
805
|
-
"messages": messages
|
|
806
|
-
}
|
|
807
|
-
|
|
808
|
-
if search_type == 'memory':
|
|
809
|
-
return search_memories(query, kwargs, messages)
|
|
810
|
-
elif search_type == 'kg':
|
|
811
|
-
return search_knowledge_graph(query, kwargs, messages)
|
|
812
|
-
else:
|
|
813
|
-
return search_web_default(query, kwargs, messages)
|
|
814
|
-
|
|
815
|
-
def search_memories(query: str, kwargs: dict, messages: list):
|
|
816
|
-
command_history = kwargs.get('command_history')
|
|
817
|
-
|
|
818
|
-
if not command_history:
|
|
819
|
-
db_path = safe_get(
|
|
820
|
-
kwargs,
|
|
821
|
-
"history_db_path",
|
|
822
|
-
os.path.expanduser('~/npcsh_history.db')
|
|
823
|
-
)
|
|
824
|
-
try:
|
|
825
|
-
command_history = CommandHistory(db_path)
|
|
826
|
-
except Exception as e:
|
|
827
|
-
return {
|
|
828
|
-
"output": f"Error connecting to history: {e}",
|
|
829
|
-
"messages": messages
|
|
830
|
-
}
|
|
831
|
-
|
|
832
|
-
state = kwargs.get('state')
|
|
833
|
-
npc = safe_get(kwargs, 'npc')
|
|
834
|
-
team = safe_get(kwargs, 'team')
|
|
835
|
-
|
|
836
|
-
npc_name = npc.name if isinstance(npc, NPC) else "__none__"
|
|
837
|
-
team_name = team.name if team else "__none__"
|
|
838
|
-
current_path = safe_get(kwargs, 'current_path', os.getcwd())
|
|
839
|
-
|
|
840
|
-
try:
|
|
841
|
-
memories = get_relevant_memories(
|
|
842
|
-
command_history=command_history,
|
|
843
|
-
npc_name=npc_name,
|
|
844
|
-
team_name=team_name,
|
|
845
|
-
path=current_path,
|
|
846
|
-
query=query,
|
|
847
|
-
max_memories=10,
|
|
848
|
-
state=state
|
|
849
|
-
)
|
|
850
|
-
|
|
851
|
-
if not memories:
|
|
852
|
-
output = f"No memories found for query: '{query}'"
|
|
853
|
-
else:
|
|
854
|
-
output = f"Found {len(memories)} memories:\n\n"
|
|
855
|
-
for i, mem in enumerate(memories, 1):
|
|
856
|
-
final_mem = (
|
|
857
|
-
mem.get('final_memory') or
|
|
858
|
-
mem.get('initial_memory')
|
|
859
|
-
)
|
|
860
|
-
timestamp = mem.get('timestamp', 'unknown')
|
|
861
|
-
output += f"{i}. [{timestamp}] {final_mem}\n"
|
|
862
|
-
|
|
863
|
-
return {"output": output, "messages": messages}
|
|
864
|
-
|
|
865
|
-
except Exception as e:
|
|
866
|
-
import traceback
|
|
867
|
-
traceback.print_exc()
|
|
868
|
-
return {
|
|
869
|
-
"output": f"Error searching memories: {e}",
|
|
870
|
-
"messages": messages
|
|
871
|
-
}
|
|
872
|
-
|
|
873
|
-
def search_knowledge_graph(query: str, kwargs: dict, messages: list):
|
|
874
|
-
command_history = kwargs.get('command_history')
|
|
875
|
-
|
|
876
|
-
if not command_history:
|
|
877
|
-
db_path = safe_get(
|
|
878
|
-
kwargs,
|
|
879
|
-
"history_db_path",
|
|
880
|
-
os.path.expanduser('~/npcsh_history.db')
|
|
881
|
-
)
|
|
882
|
-
try:
|
|
883
|
-
command_history = CommandHistory(db_path)
|
|
884
|
-
except Exception as e:
|
|
885
|
-
return {
|
|
886
|
-
"output": f"Error connecting to history: {e}",
|
|
887
|
-
"messages": messages
|
|
888
|
-
}
|
|
889
|
-
|
|
890
|
-
npc = safe_get(kwargs, 'npc')
|
|
891
|
-
team = safe_get(kwargs, 'team')
|
|
892
|
-
|
|
893
|
-
npc_name = npc.name if isinstance(npc, NPC) else "__none__"
|
|
894
|
-
team_name = team.name if team else "__none__"
|
|
895
|
-
current_path = safe_get(kwargs, 'current_path', os.getcwd())
|
|
896
|
-
|
|
897
|
-
try:
|
|
898
|
-
engine = command_history.engine
|
|
899
|
-
kg = load_kg_from_db(
|
|
900
|
-
engine,
|
|
901
|
-
team_name,
|
|
902
|
-
npc_name,
|
|
903
|
-
current_path
|
|
904
|
-
)
|
|
905
|
-
|
|
906
|
-
if not kg or not kg.get('facts'):
|
|
907
|
-
return {
|
|
908
|
-
"output": (
|
|
909
|
-
f"No knowledge graph found for current scope.\n"
|
|
910
|
-
f"Scope: Team='{team_name}', "
|
|
911
|
-
f"NPC='{npc_name}', Path='{current_path}'"
|
|
912
|
-
),
|
|
913
|
-
"messages": messages
|
|
64
|
+
# Use extract_jinx_inputs
|
|
65
|
+
input_values = extract_jinx_inputs(args, jinx)
|
|
66
|
+
|
|
67
|
+
# Build extra_globals for jinx execution
|
|
68
|
+
from npcpy.memory.command_history import CommandHistory, load_kg_from_db
|
|
69
|
+
from npcpy.memory.search import execute_rag_command, execute_brainblast_command
|
|
70
|
+
from npcpy.data.load import load_file_contents
|
|
71
|
+
from npcpy.data.web import search_web
|
|
72
|
+
|
|
73
|
+
application_globals_for_jinx = {
|
|
74
|
+
"CommandHistory": CommandHistory,
|
|
75
|
+
"load_kg_from_db": load_kg_from_db,
|
|
76
|
+
"execute_rag_command": execute_rag_command,
|
|
77
|
+
"execute_brainblast_command": execute_brainblast_command,
|
|
78
|
+
"load_file_contents": load_file_contents,
|
|
79
|
+
"search_web": search_web,
|
|
80
|
+
'state': kwargs.get('state')
|
|
914
81
|
}
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
output = f"Knowledge Graph Search Results for '{query}':\n\n"
|
|
932
|
-
|
|
933
|
-
if matching_facts:
|
|
934
|
-
output += f"## Facts ({len(matching_facts)}):\n"
|
|
935
|
-
for i, fact in enumerate(matching_facts, 1):
|
|
936
|
-
output += f"{i}. {fact.get('statement')}\n"
|
|
937
|
-
output += "\n"
|
|
938
|
-
|
|
939
|
-
if matching_concepts:
|
|
940
|
-
output += f"## Concepts ({len(matching_concepts)}):\n"
|
|
941
|
-
for i, concept in enumerate(matching_concepts, 1):
|
|
942
|
-
name = concept.get('name')
|
|
943
|
-
desc = concept.get('description', '')
|
|
944
|
-
output += f"{i}. {name}: {desc}\n"
|
|
945
|
-
|
|
946
|
-
if not matching_facts and not matching_concepts:
|
|
947
|
-
output += "No matching facts or concepts found."
|
|
948
|
-
|
|
949
|
-
return {"output": output, "messages": messages}
|
|
950
|
-
|
|
951
|
-
except Exception as e:
|
|
952
|
-
import traceback
|
|
953
|
-
traceback.print_exc()
|
|
954
|
-
return {
|
|
955
|
-
"output": f"Error searching KG: {e}",
|
|
956
|
-
"messages": messages
|
|
957
|
-
}
|
|
958
|
-
|
|
959
|
-
def search_web_default(query: str, kwargs: dict, messages: list):
|
|
960
|
-
search_provider = safe_get(kwargs, 'sprovider', NPCSH_SEARCH_PROVIDER)
|
|
961
|
-
render_markdown(f'- Searching {search_provider} for "{query}"')
|
|
962
|
-
|
|
963
|
-
try:
|
|
964
|
-
search_results = search_web(query, provider=search_provider)
|
|
965
|
-
output = (
|
|
966
|
-
"\n".join([f"- {res}" for res in search_results])
|
|
967
|
-
if search_results
|
|
968
|
-
else "No results found."
|
|
969
|
-
)
|
|
970
|
-
except Exception as e:
|
|
971
|
-
import traceback
|
|
972
|
-
traceback.print_exc()
|
|
973
|
-
output = f"Error during web search: {e}"
|
|
974
|
-
|
|
975
|
-
return {"output": output, "messages": messages}
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
@router.route("serve", "Serve an NPC Team")
|
|
980
|
-
def serve_handler(command: str, **kwargs):
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
port = safe_get(kwargs, "port", 5337)
|
|
985
|
-
|
|
986
|
-
messages = safe_get(kwargs, "messages", [])
|
|
987
|
-
cors = safe_get(kwargs, "cors", None)
|
|
988
|
-
if cors:
|
|
989
|
-
cors_origins = [origin.strip() for origin in cors.split(",")]
|
|
990
|
-
else:
|
|
991
|
-
cors_origins = None
|
|
992
|
-
|
|
993
|
-
start_flask_server(
|
|
994
|
-
port=port,
|
|
995
|
-
cors_origins=cors_origins,
|
|
996
|
-
)
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
return {"output": None, "messages": messages}
|
|
1000
|
-
|
|
1001
|
-
@router.route("set", "Set configuration values")
|
|
1002
|
-
def set_handler(command: str, **kwargs):
|
|
1003
|
-
messages = safe_get(kwargs, "messages", [])
|
|
1004
|
-
parts = command.split(maxsplit=1)
|
|
1005
|
-
if len(parts) < 2 or '=' not in parts[1]:
|
|
1006
|
-
return {"output": "Usage: /set <key>=<value>", "messages": messages}
|
|
1007
|
-
|
|
1008
|
-
key_value = parts[1]
|
|
1009
|
-
key, value = key_value.split('=', 1)
|
|
1010
|
-
key = key.strip()
|
|
1011
|
-
value = value.strip().strip('"\'')
|
|
1012
|
-
|
|
1013
|
-
try:
|
|
1014
|
-
set_npcsh_config_value(key, value)
|
|
1015
|
-
output = f"Configuration value '{key}' set."
|
|
1016
|
-
except NameError:
|
|
1017
|
-
output = "Set function (set_npcsh_config_value) not available."
|
|
1018
|
-
except Exception as e:
|
|
1019
|
-
traceback.print_exc()
|
|
1020
|
-
output = f"Error setting configuration '{key}': {e}"
|
|
1021
|
-
return {"output": output, "messages": messages}
|
|
1022
|
-
|
|
1023
|
-
@router.route("sleep", "Evolve knowledge graph. Use --dream to also run creative synthesis.")
|
|
1024
|
-
def sleep_handler(command: str, **kwargs):
|
|
1025
|
-
messages = safe_get(kwargs, "messages", [])
|
|
1026
|
-
npc = safe_get(kwargs, 'npc')
|
|
1027
|
-
team = safe_get(kwargs, 'team')
|
|
1028
|
-
model = safe_get(kwargs, 'model')
|
|
1029
|
-
provider = safe_get(kwargs, 'provider')
|
|
1030
|
-
|
|
1031
|
-
is_dreaming = safe_get(kwargs, 'dream', False)
|
|
1032
|
-
operations_str = safe_get(kwargs, 'ops')
|
|
1033
|
-
|
|
1034
|
-
operations_config = None
|
|
1035
|
-
if operations_str and isinstance(operations_str, str):
|
|
1036
|
-
operations_config = [op.strip() for op in operations_str.split(',')]
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
team_name = team.name if team else "__none__"
|
|
1040
|
-
npc_name = npc.name if isinstance(npc, NPC) else "__none__"
|
|
1041
|
-
current_path = os.getcwd()
|
|
1042
|
-
scope_str = f"Team: '{team_name}', NPC: '{npc_name}', Path: '{current_path}'"
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
render_markdown(f"- Checking knowledge graph for scope: {scope_str}")
|
|
1046
|
-
|
|
1047
|
-
try:
|
|
1048
|
-
db_path = os.getenv("NPCSH_DB_PATH", os.path.expanduser("~/npcsh_history.db"))
|
|
1049
|
-
command_history = CommandHistory(db_path)
|
|
1050
|
-
engine = command_history.engine
|
|
1051
|
-
except Exception as e:
|
|
1052
|
-
return {"output": f"Error connecting to history database for KG access: {e}", "messages": messages}
|
|
1053
|
-
|
|
1054
|
-
try:
|
|
1055
|
-
current_kg = load_kg_from_db(engine, team_name, npc_name, current_path)
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
if not current_kg or not current_kg.get('facts'):
|
|
1059
|
-
output_msg = f"Knowledge graph for the current scope is empty. Nothing to process.\n"
|
|
1060
|
-
output_msg += f" - Scope Checked: {scope_str}\n\n"
|
|
1061
|
-
output_msg += "**Hint:** Have a conversation or run some commands first to build up knowledge in this specific context. The KG is unique to each combination of Team, NPC, and directory."
|
|
1062
|
-
return {"output": output_msg, "messages": messages}
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
original_facts = len(current_kg.get('facts', []))
|
|
1066
|
-
original_concepts = len(current_kg.get('concepts', []))
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
process_type = "Sleep"
|
|
1072
|
-
ops_display = f"with operations: {operations_config}" if operations_config else "with random operations"
|
|
1073
|
-
render_markdown(f"- Initiating sleep process {ops_display}")
|
|
1074
|
-
|
|
1075
|
-
evolved_kg, _ = kg_sleep_process(
|
|
1076
|
-
existing_kg=current_kg,
|
|
1077
|
-
model=model,
|
|
1078
|
-
provider=provider,
|
|
1079
|
-
npc=npc,
|
|
1080
|
-
operations_config=operations_config
|
|
1081
|
-
)
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
if is_dreaming:
|
|
1085
|
-
process_type += " & Dream"
|
|
1086
|
-
render_markdown(f"- Initiating dream process on the evolved KG...")
|
|
1087
|
-
evolved_kg, _ = kg_dream_process(
|
|
1088
|
-
existing_kg=evolved_kg,
|
|
1089
|
-
model=model,
|
|
1090
|
-
provider=provider,
|
|
1091
|
-
npc=npc
|
|
82
|
+
|
|
83
|
+
# Add functions from _state module if available
|
|
84
|
+
try:
|
|
85
|
+
from npcsh import _state
|
|
86
|
+
for name, func in inspect.getmembers(_state, inspect.isfunction):
|
|
87
|
+
application_globals_for_jinx[name] = func
|
|
88
|
+
except:
|
|
89
|
+
pass
|
|
90
|
+
|
|
91
|
+
jinx_output = jinx.execute(
|
|
92
|
+
input_values=input_values,
|
|
93
|
+
jinxs_dict=kwargs.get('jinxs_dict', {}),
|
|
94
|
+
npc=npc,
|
|
95
|
+
messages=messages,
|
|
96
|
+
extra_globals=application_globals_for_jinx
|
|
1092
97
|
)
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
save_kg_to_db(conn, evolved_kg, team_name, npc_name, current_path)
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
new_facts = len(evolved_kg.get('facts', []))
|
|
1099
|
-
new_concepts = len(evolved_kg.get('concepts', []))
|
|
1100
|
-
|
|
1101
|
-
output = f"{process_type} process complete.\n"
|
|
1102
|
-
output += f"- Facts: {original_facts} -> {new_facts} ({new_facts - original_facts:+})\n"
|
|
1103
|
-
output += f"- Concepts: {original_concepts} -> {new_concepts} ({new_concepts - original_concepts:+})"
|
|
1104
|
-
|
|
1105
|
-
print(evolved_kg.get('facts'))
|
|
1106
|
-
print(evolved_kg.get('concepts'))
|
|
1107
|
-
|
|
1108
|
-
return {"output": output, "messages": messages}
|
|
1109
|
-
|
|
1110
|
-
except Exception as e:
|
|
1111
|
-
import traceback
|
|
1112
|
-
traceback.print_exc()
|
|
1113
|
-
return {"output": f"Error during KG evolution process: {e}", "messages": messages}
|
|
1114
|
-
finally:
|
|
1115
|
-
if 'command_history' in locals() and command_history:
|
|
1116
|
-
command_history.close()
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
@router.route("spool", "Enter interactive chat (spool) mode")
|
|
1122
|
-
def spool_handler(command: str, **kwargs):
|
|
1123
|
-
try:
|
|
1124
|
-
npc = safe_get(kwargs, 'npc')
|
|
1125
|
-
team = safe_get(kwargs, 'team')
|
|
1126
|
-
|
|
1127
|
-
if isinstance(npc, str) and team:
|
|
1128
|
-
npc_name = npc
|
|
1129
|
-
if npc_name in team.npcs:
|
|
1130
|
-
npc = team.npcs[npc_name]
|
|
1131
|
-
else:
|
|
1132
|
-
return {"output": f"Error: NPC '{npc_name}' not found in team. Available NPCs: {', '.join(team.npcs.keys())}", "messages": safe_get(kwargs, "messages", [])}
|
|
1133
|
-
kwargs['npc'] = npc
|
|
1134
|
-
return enter_spool_mode(
|
|
1135
|
-
**kwargs)
|
|
1136
|
-
except Exception as e:
|
|
1137
|
-
traceback.print_exc()
|
|
1138
|
-
return {"output": f"Error entering spool mode: {e}", "messages": safe_get(kwargs, "messages", [])}
|
|
1139
|
-
|
|
1140
|
-
@router.route("jinxs", "Show available jinxs for the current NPC/Team")
|
|
1141
|
-
def jinxs_handler(command: str, **kwargs):
|
|
1142
|
-
npc = safe_get(kwargs, 'npc')
|
|
1143
|
-
team = safe_get(kwargs, 'team')
|
|
1144
|
-
output = "Available Jinxs:\n"
|
|
1145
|
-
jinxs_listed = set()
|
|
1146
|
-
|
|
1147
|
-
def format_jinx(name, jinx_obj):
|
|
1148
|
-
desc = getattr(jinx_obj, 'description', 'No description available.')
|
|
1149
|
-
return f"- /{name}: {desc}\n"
|
|
1150
|
-
|
|
1151
|
-
if npc and isinstance(npc, NPC) and hasattr(npc, 'jinxs_dict') and npc.jinxs_dict:
|
|
1152
|
-
output += f"\n--- Jinxs for NPC: {npc.name} ---\n"
|
|
1153
|
-
for name, jinx in sorted(npc.jinxs_dict.items()):
|
|
1154
|
-
output += format_jinx(name, jinx)
|
|
1155
|
-
jinxs_listed.add(name)
|
|
1156
|
-
|
|
1157
|
-
if team and hasattr(team, 'jinxs_dict') and team.jinxs_dict:
|
|
1158
|
-
team_has_jinxs = False
|
|
1159
|
-
team_output = ""
|
|
1160
|
-
for name, jinx in sorted(team.jinxs_dict.items()):
|
|
1161
|
-
if name not in jinxs_listed:
|
|
1162
|
-
team_output += format_jinx(name, jinx)
|
|
1163
|
-
team_has_jinxs = True
|
|
1164
|
-
if team_has_jinxs:
|
|
1165
|
-
output += f"\n--- Jinxs for Team: {getattr(team, 'name', 'Unnamed Team')} ---\n"
|
|
1166
|
-
output += team_output
|
|
1167
|
-
|
|
1168
|
-
if not jinxs_listed and not (team and hasattr(team, 'jinxs_dict') and team.jinxs_dict):
|
|
1169
|
-
output = "No jinxs available for the current context."
|
|
1170
|
-
|
|
1171
|
-
return {"output": output.strip(), "messages": safe_get(kwargs, "messages", [])}
|
|
1172
|
-
|
|
1173
|
-
@router.route("trigger", "Execute a trigger command")
|
|
1174
|
-
def trigger_handler(command: str, **kwargs):
|
|
1175
|
-
messages = safe_get(kwargs, "messages", [])
|
|
1176
|
-
user_command = " ".join(command.split()[1:])
|
|
1177
|
-
if not user_command:
|
|
1178
|
-
return {"output": "Usage: /trigger <trigger_description>", "messages": messages}
|
|
1179
|
-
try:
|
|
1180
|
-
return execute_trigger_command(command=user_command, **kwargs)
|
|
1181
|
-
except NameError:
|
|
1182
|
-
return {"output": "Trigger function (execute_trigger_command) not available.", "messages": messages}
|
|
1183
|
-
except Exception as e:
|
|
1184
|
-
traceback.print_exc()
|
|
1185
|
-
return {"output": f"Error executing trigger: {e}", "messages": messages}
|
|
1186
|
-
@router.route("vixynt", "Generate images from text descriptions")
|
|
1187
|
-
def vixynt_handler(command: str, **kwargs):
|
|
1188
|
-
npc = safe_get(kwargs, 'npc')
|
|
1189
|
-
model = safe_get(kwargs, 'igmodel', NPCSH_IMAGE_GEN_MODEL)
|
|
1190
|
-
provider = safe_get(kwargs, 'igprovider', NPCSH_IMAGE_GEN_PROVIDER)
|
|
1191
|
-
height = safe_get(kwargs, 'height', 1024)
|
|
1192
|
-
width = safe_get(kwargs, 'width', 1024)
|
|
1193
|
-
output_file_base = safe_get(kwargs, 'output_file')
|
|
1194
|
-
attachments = safe_get(kwargs, 'attachments')
|
|
1195
|
-
n_images = safe_get(kwargs, 'n_images', 1)
|
|
1196
|
-
if isinstance(attachments, str):
|
|
1197
|
-
attachments = attachments.split(',')
|
|
1198
|
-
|
|
1199
|
-
messages = safe_get(kwargs, 'messages', [])
|
|
1200
|
-
|
|
1201
|
-
user_prompt = " ".join(safe_get(kwargs, 'positional_args', []))
|
|
1202
|
-
|
|
1203
|
-
if not user_prompt:
|
|
1204
|
-
return {"output": "Usage: /vixynt <prompt> [--output_file path] [--attachments path] [--n_images num]", "messages": messages}
|
|
1205
|
-
|
|
1206
|
-
try:
|
|
1207
|
-
|
|
1208
|
-
images_list = gen_image(
|
|
1209
|
-
prompt=user_prompt,
|
|
1210
|
-
model=model,
|
|
1211
|
-
provider=provider,
|
|
1212
|
-
npc=npc,
|
|
1213
|
-
height=height,
|
|
1214
|
-
width=width,
|
|
1215
|
-
n_images=n_images,
|
|
1216
|
-
input_images=attachments
|
|
1217
|
-
)
|
|
1218
|
-
|
|
1219
|
-
saved_files = []
|
|
1220
|
-
if not isinstance(images_list, list):
|
|
1221
|
-
images_list = [images_list] if images_list is not None else []
|
|
1222
|
-
|
|
1223
|
-
for i, image in enumerate(images_list):
|
|
1224
|
-
if image is None:
|
|
1225
|
-
continue
|
|
1226
|
-
|
|
1227
|
-
if output_file_base is None:
|
|
1228
|
-
os.makedirs(os.path.expanduser("~/.npcsh/images/"), exist_ok=True)
|
|
1229
|
-
current_output_file = (
|
|
1230
|
-
os.path.expanduser("~/.npcsh/images/")
|
|
1231
|
-
+ f"image_{datetime.now().strftime('%Y%m%d_%H%M%S')}_{i}.png"
|
|
1232
|
-
)
|
|
1233
|
-
else:
|
|
1234
|
-
base_name, ext = os.path.splitext(os.path.expanduser(output_file_base))
|
|
1235
|
-
current_output_file = f"{base_name}_{i}{ext}"
|
|
1236
98
|
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
if attachments:
|
|
1243
|
-
output = f"Image(s) edited and saved to: {', '.join(saved_files)}"
|
|
99
|
+
if isinstance(jinx_output, dict):
|
|
100
|
+
return {
|
|
101
|
+
"output": jinx_output.get('output', str(jinx_output)),
|
|
102
|
+
"messages": jinx_output.get('messages', messages)
|
|
103
|
+
}
|
|
1244
104
|
else:
|
|
1245
|
-
output
|
|
1246
|
-
else:
|
|
1247
|
-
output = f"No images {'edited' if attachments else 'generated'}."
|
|
1248
|
-
|
|
1249
|
-
except Exception as e:
|
|
1250
|
-
traceback.print_exc()
|
|
1251
|
-
output = f"Error {'editing' if attachments else 'generating'} image: {e}"
|
|
1252
|
-
|
|
1253
|
-
return {
|
|
1254
|
-
"output": output,
|
|
1255
|
-
"messages": messages,
|
|
1256
|
-
"model": model,
|
|
1257
|
-
"provider": provider
|
|
1258
|
-
}
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
@router.route("wander", "Enter wander mode (experimental)")
|
|
1262
|
-
def wander_handler(command: str, **kwargs):
|
|
1263
|
-
messages = safe_get(kwargs, "messages", [])
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
try:
|
|
1267
|
-
parts = shlex.split(command)
|
|
1268
|
-
problem_parts = []
|
|
1269
|
-
wander_params = {}
|
|
1270
|
-
|
|
1271
|
-
i = 1
|
|
1272
|
-
while i < len(parts):
|
|
1273
|
-
part = parts[i]
|
|
1274
|
-
|
|
1275
|
-
if '=' in part:
|
|
1276
|
-
|
|
1277
|
-
key, initial_value = part.split('=', 1)
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
value_parts = [initial_value]
|
|
1281
|
-
j = i + 1
|
|
1282
|
-
while j < len(parts) and '=' not in parts[j]:
|
|
1283
|
-
value_parts.append(parts[j])
|
|
1284
|
-
j += 1
|
|
105
|
+
return {"output": str(jinx_output), "messages": messages}
|
|
1285
106
|
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
problem_parts.append(part)
|
|
1293
|
-
i += 1
|
|
1294
|
-
|
|
1295
|
-
problem = " ".join(problem_parts)
|
|
1296
|
-
except Exception as e:
|
|
1297
|
-
return {"output": f"Error parsing arguments: {e}", "messages": messages}
|
|
1298
|
-
|
|
1299
|
-
if not problem:
|
|
1300
|
-
return {"output": "Usage: /wander <problem> [key=value...]", "messages": messages}
|
|
1301
|
-
|
|
1302
|
-
try:
|
|
1303
|
-
|
|
1304
|
-
mode_args = {
|
|
1305
|
-
'problem': problem,
|
|
1306
|
-
'npc': safe_get(kwargs, 'npc'),
|
|
1307
|
-
'model': safe_get(kwargs, 'model'),
|
|
1308
|
-
'provider': safe_get(kwargs, 'provider'),
|
|
1309
|
-
|
|
1310
|
-
'environment': wander_params.get('environment'),
|
|
1311
|
-
'low_temp': float(wander_params.get('low-temp', 0.5)),
|
|
1312
|
-
'high_temp': float(wander_params.get('high-temp', 1.9)),
|
|
1313
|
-
'interruption_likelihood': float(wander_params.get('interruption-likelihood', 1)),
|
|
1314
|
-
'sample_rate': float(wander_params.get('sample-rate', 0.4)),
|
|
1315
|
-
'n_high_temp_streams': int(wander_params.get('n-high-temp-streams', 5)),
|
|
1316
|
-
'include_events': bool(wander_params.get('include-events', False)),
|
|
1317
|
-
'num_events': int(wander_params.get('num-events', 3))
|
|
1318
|
-
}
|
|
1319
|
-
|
|
1320
|
-
result = enter_wander_mode(**mode_args)
|
|
1321
|
-
|
|
1322
|
-
if isinstance(result, list) and result:
|
|
1323
|
-
output = result[-1].get("insight", "Wander mode session complete.")
|
|
1324
|
-
else:
|
|
1325
|
-
output = str(result) if result else "Wander mode session complete."
|
|
1326
|
-
|
|
1327
|
-
messages.append({"role": "assistant", "content": output})
|
|
1328
|
-
return {"output": output, "messages": messages}
|
|
1329
|
-
|
|
1330
|
-
except Exception as e:
|
|
1331
|
-
traceback.print_exc()
|
|
1332
|
-
return {"output": f"Error during wander mode: {e}", "messages": messages}
|
|
107
|
+
except Exception as e:
|
|
108
|
+
traceback.print_exc()
|
|
109
|
+
return {
|
|
110
|
+
"output": f"Error executing jinx '{jinx.jinx_name}': {e}",
|
|
111
|
+
"messages": messages
|
|
112
|
+
}
|
|
1333
113
|
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
|
|
1337
|
-
|
|
1338
|
-
|
|
1339
|
-
|
|
1340
|
-
except Exception as e:
|
|
1341
|
-
traceback.print_exc()
|
|
1342
|
-
return {"output": f"Error entering yap mode: {e}", "messages": safe_get(kwargs, "messages", [])}
|
|
114
|
+
def get_route(self, command: str) -> Optional[Callable]:
|
|
115
|
+
if command in self.routes:
|
|
116
|
+
return self.routes[command]
|
|
117
|
+
elif command in self.jinx_routes:
|
|
118
|
+
return self.jinx_routes[command]
|
|
119
|
+
return None
|
|
1343
120
|
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
num_npcs = safe_get(kwargs, 'num_npcs', 5)
|
|
1351
|
-
depth = safe_get(kwargs, 'depth', 3)
|
|
1352
|
-
|
|
1353
|
-
i = 1
|
|
1354
|
-
while i < len(parts):
|
|
1355
|
-
if parts[i].startswith('--'):
|
|
1356
|
-
option = parts[i][2:]
|
|
1357
|
-
if option in ['num-npcs', 'npcs']:
|
|
1358
|
-
if i + 1 < len(parts) and parts[i + 1].isdigit():
|
|
1359
|
-
num_npcs = int(parts[i + 1])
|
|
1360
|
-
i += 2
|
|
1361
|
-
else:
|
|
1362
|
-
i += 1
|
|
1363
|
-
elif option in ['depth', 'd']:
|
|
1364
|
-
if i + 1 < len(parts) and parts[i + 1].isdigit():
|
|
1365
|
-
depth = int(parts[i + 1])
|
|
1366
|
-
i += 2
|
|
1367
|
-
else:
|
|
1368
|
-
i += 1
|
|
1369
|
-
elif option in ['exploration', 'e']:
|
|
1370
|
-
if i + 1 < len(parts) and parts[i + 1].replace('.', '', 1).isdigit():
|
|
1371
|
-
exploration_factor = float(parts[i + 1])
|
|
1372
|
-
i += 2
|
|
1373
|
-
else:
|
|
1374
|
-
i += 1
|
|
1375
|
-
elif option in ['creativity', 'c']:
|
|
1376
|
-
if i + 1 < len(parts) and parts[i + 1].replace('.', '', 1).isdigit():
|
|
1377
|
-
creativity_factor = float(parts[i + 1])
|
|
1378
|
-
i += 2
|
|
1379
|
-
else:
|
|
1380
|
-
i += 1
|
|
1381
|
-
elif option in ['format', 'f']:
|
|
1382
|
-
if i + 1 < len(parts):
|
|
1383
|
-
output_format = parts[i + 1]
|
|
1384
|
-
i += 2
|
|
1385
|
-
else:
|
|
1386
|
-
i += 1
|
|
1387
|
-
else:
|
|
1388
|
-
|
|
1389
|
-
i += 1
|
|
1390
|
-
else:
|
|
1391
|
-
|
|
1392
|
-
query += parts[i] + " "
|
|
1393
|
-
i += 1
|
|
1394
|
-
|
|
1395
|
-
query = query.strip()
|
|
1396
|
-
|
|
1397
|
-
|
|
1398
|
-
if 'num_npcs' in kwargs:
|
|
1399
|
-
try:
|
|
1400
|
-
num_npcs = int(kwargs['num_npcs'])
|
|
1401
|
-
except ValueError:
|
|
1402
|
-
return {"output": "Error: num_npcs must be an integer", "messages": messages}
|
|
1403
|
-
|
|
1404
|
-
if 'depth' in kwargs:
|
|
1405
|
-
try:
|
|
1406
|
-
depth = int(kwargs['depth'])
|
|
1407
|
-
except ValueError:
|
|
1408
|
-
return {"output": "Error: depth must be an integer", "messages": messages}
|
|
1409
|
-
|
|
1410
|
-
if 'exploration' in kwargs:
|
|
1411
|
-
try:
|
|
1412
|
-
exploration_factor = float(kwargs['exploration'])
|
|
1413
|
-
except ValueError:
|
|
1414
|
-
return {"output": "Error: exploration must be a float", "messages": messages}
|
|
1415
|
-
|
|
1416
|
-
if 'creativity' in kwargs:
|
|
1417
|
-
try:
|
|
1418
|
-
creativity_factor = float(kwargs['creativity'])
|
|
1419
|
-
except ValueError:
|
|
1420
|
-
return {"output": "Error: creativity must be a float", "messages": messages}
|
|
1421
|
-
|
|
1422
|
-
if not query:
|
|
1423
|
-
return {"output": "Usage: /alicanto <research query> [--num-npcs N] [--depth N] [--exploration 0.3] [--creativity 0.5] [--format report|summary|full]", "messages": messages}
|
|
1424
|
-
|
|
1425
|
-
try:
|
|
1426
|
-
logging.info(f"Starting Alicanto research on: {query}")
|
|
1427
|
-
model = safe_get(kwargs, 'model')
|
|
1428
|
-
if len(model) == 0 :
|
|
1429
|
-
model = NPCSH_CHAT_MODEL
|
|
1430
|
-
provider = safe_get(kwargs, 'provider')
|
|
1431
|
-
if len(provider) == 0 :
|
|
1432
|
-
provider = NPCSH_CHAT_PROVIDER
|
|
121
|
+
def execute(self, command_str: str, **kwargs) -> Any:
|
|
122
|
+
command_name = command_str.split()[0].lstrip('/')
|
|
123
|
+
route_func = self.get_route(command_name)
|
|
124
|
+
if route_func:
|
|
125
|
+
return route_func(command=command_str, **kwargs)
|
|
126
|
+
return None
|
|
1433
127
|
|
|
128
|
+
def get_commands(self) -> List[str]:
|
|
129
|
+
all_commands = list(self.routes.keys()) + list(self.jinx_routes.keys())
|
|
130
|
+
return sorted(set(all_commands))
|
|
1434
131
|
|
|
1435
|
-
|
|
1436
|
-
|
|
132
|
+
def get_help(self, command: str = None) -> Dict[str, str]:
|
|
133
|
+
if command:
|
|
134
|
+
if command in self.help_info:
|
|
135
|
+
return {command: self.help_info[command]}
|
|
136
|
+
return {}
|
|
137
|
+
return self.help_info
|
|
1437
138
|
|
|
1438
|
-
result = alicanto(
|
|
1439
|
-
query,
|
|
1440
|
-
num_npcs=num_npcs,
|
|
1441
|
-
depth=depth,
|
|
1442
|
-
model=model,
|
|
1443
|
-
provider=provider,
|
|
1444
|
-
max_steps = safe_get(kwargs, 'max_steps', 20),
|
|
1445
|
-
skip_research = skip_research
|
|
1446
139
|
|
|
1447
|
-
|
|
1448
|
-
|
|
1449
|
-
|
|
1450
|
-
if isinstance(result, dict):
|
|
1451
|
-
if "integration" in result:
|
|
1452
|
-
output = result["integration"]
|
|
1453
|
-
else:
|
|
1454
|
-
output = "Alicanto research completed. Full results available in returned data."
|
|
1455
|
-
else:
|
|
1456
|
-
output = result
|
|
1457
|
-
|
|
1458
|
-
return {"output": output, "messages": messages, "alicanto_result": result}
|
|
1459
|
-
except Exception as e:
|
|
1460
|
-
traceback.print_exc()
|
|
1461
|
-
logging.error(f"Error during Alicanto research: {e}")
|
|
1462
|
-
return {"output": f"Error during Alicanto research: {e}", "messages": messages}
|
|
140
|
+
router = CommandRouter()
|