npcsh 0.3.31__py3-none-any.whl → 1.0.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- npcsh/_state.py +942 -0
- npcsh/alicanto.py +1074 -0
- npcsh/guac.py +785 -0
- npcsh/mcp_helpers.py +357 -0
- npcsh/mcp_npcsh.py +822 -0
- npcsh/mcp_server.py +184 -0
- npcsh/npc.py +218 -0
- npcsh/npcsh.py +1161 -0
- npcsh/plonk.py +387 -269
- npcsh/pti.py +234 -0
- npcsh/routes.py +958 -0
- npcsh/spool.py +315 -0
- npcsh/wander.py +550 -0
- npcsh/yap.py +573 -0
- npcsh-1.0.0.dist-info/METADATA +596 -0
- npcsh-1.0.0.dist-info/RECORD +21 -0
- {npcsh-0.3.31.dist-info → npcsh-1.0.0.dist-info}/WHEEL +1 -1
- npcsh-1.0.0.dist-info/entry_points.txt +9 -0
- {npcsh-0.3.31.dist-info → npcsh-1.0.0.dist-info}/licenses/LICENSE +1 -1
- npcsh/audio.py +0 -210
- npcsh/cli.py +0 -545
- npcsh/command_history.py +0 -566
- npcsh/conversation.py +0 -291
- npcsh/data_models.py +0 -46
- npcsh/dataframes.py +0 -163
- npcsh/embeddings.py +0 -168
- npcsh/helpers.py +0 -641
- npcsh/image.py +0 -298
- npcsh/image_gen.py +0 -79
- npcsh/knowledge_graph.py +0 -1006
- npcsh/llm_funcs.py +0 -2027
- npcsh/load_data.py +0 -83
- npcsh/main.py +0 -5
- npcsh/model_runner.py +0 -189
- npcsh/npc_compiler.py +0 -2870
- npcsh/npc_sysenv.py +0 -383
- npcsh/npc_team/assembly_lines/test_pipeline.py +0 -181
- npcsh/npc_team/corca.npc +0 -13
- npcsh/npc_team/foreman.npc +0 -7
- npcsh/npc_team/npcsh.ctx +0 -11
- npcsh/npc_team/sibiji.npc +0 -4
- npcsh/npc_team/templates/analytics/celona.npc +0 -0
- npcsh/npc_team/templates/hr_support/raone.npc +0 -0
- npcsh/npc_team/templates/humanities/eriane.npc +0 -4
- npcsh/npc_team/templates/it_support/lineru.npc +0 -0
- npcsh/npc_team/templates/marketing/slean.npc +0 -4
- npcsh/npc_team/templates/philosophy/maurawa.npc +0 -0
- npcsh/npc_team/templates/sales/turnic.npc +0 -4
- npcsh/npc_team/templates/software/welxor.npc +0 -0
- npcsh/npc_team/tools/bash_executer.tool +0 -32
- npcsh/npc_team/tools/calculator.tool +0 -8
- npcsh/npc_team/tools/code_executor.tool +0 -16
- npcsh/npc_team/tools/generic_search.tool +0 -27
- npcsh/npc_team/tools/image_generation.tool +0 -25
- npcsh/npc_team/tools/local_search.tool +0 -149
- npcsh/npc_team/tools/npcsh_executor.tool +0 -9
- npcsh/npc_team/tools/screen_cap.tool +0 -27
- npcsh/npc_team/tools/sql_executor.tool +0 -26
- npcsh/response.py +0 -623
- npcsh/search.py +0 -248
- npcsh/serve.py +0 -1460
- npcsh/shell.py +0 -538
- npcsh/shell_helpers.py +0 -3529
- npcsh/stream.py +0 -700
- npcsh/video.py +0 -49
- npcsh-0.3.31.data/data/npcsh/npc_team/bash_executer.tool +0 -32
- npcsh-0.3.31.data/data/npcsh/npc_team/calculator.tool +0 -8
- npcsh-0.3.31.data/data/npcsh/npc_team/celona.npc +0 -0
- npcsh-0.3.31.data/data/npcsh/npc_team/code_executor.tool +0 -16
- npcsh-0.3.31.data/data/npcsh/npc_team/corca.npc +0 -13
- npcsh-0.3.31.data/data/npcsh/npc_team/eriane.npc +0 -4
- npcsh-0.3.31.data/data/npcsh/npc_team/foreman.npc +0 -7
- npcsh-0.3.31.data/data/npcsh/npc_team/generic_search.tool +0 -27
- npcsh-0.3.31.data/data/npcsh/npc_team/image_generation.tool +0 -25
- npcsh-0.3.31.data/data/npcsh/npc_team/lineru.npc +0 -0
- npcsh-0.3.31.data/data/npcsh/npc_team/local_search.tool +0 -149
- npcsh-0.3.31.data/data/npcsh/npc_team/maurawa.npc +0 -0
- npcsh-0.3.31.data/data/npcsh/npc_team/npcsh.ctx +0 -11
- npcsh-0.3.31.data/data/npcsh/npc_team/npcsh_executor.tool +0 -9
- npcsh-0.3.31.data/data/npcsh/npc_team/raone.npc +0 -0
- npcsh-0.3.31.data/data/npcsh/npc_team/screen_cap.tool +0 -27
- npcsh-0.3.31.data/data/npcsh/npc_team/sibiji.npc +0 -4
- npcsh-0.3.31.data/data/npcsh/npc_team/slean.npc +0 -4
- npcsh-0.3.31.data/data/npcsh/npc_team/sql_executor.tool +0 -26
- npcsh-0.3.31.data/data/npcsh/npc_team/test_pipeline.py +0 -181
- npcsh-0.3.31.data/data/npcsh/npc_team/turnic.npc +0 -4
- npcsh-0.3.31.data/data/npcsh/npc_team/welxor.npc +0 -0
- npcsh-0.3.31.dist-info/METADATA +0 -1853
- npcsh-0.3.31.dist-info/RECORD +0 -76
- npcsh-0.3.31.dist-info/entry_points.txt +0 -3
- {npcsh-0.3.31.dist-info → npcsh-1.0.0.dist-info}/top_level.txt +0 -0
npcsh/_state.py
ADDED
|
@@ -0,0 +1,942 @@
|
|
|
1
|
+
|
|
2
|
+
from colorama import Fore, Back, Style
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
from datetime import datetime
|
|
6
|
+
from dotenv import load_dotenv
|
|
7
|
+
|
|
8
|
+
import re
|
|
9
|
+
import os
|
|
10
|
+
from termcolor import colored
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
from typing import Dict, List
|
|
15
|
+
import re
|
|
16
|
+
import sqlite3
|
|
17
|
+
from datetime import datetime
|
|
18
|
+
import logging
|
|
19
|
+
import textwrap
|
|
20
|
+
from termcolor import colored
|
|
21
|
+
import sys
|
|
22
|
+
import platform
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def get_npc_path(npc_name: str, db_path: str) -> str:
|
|
26
|
+
# First, check in project npc_team directory
|
|
27
|
+
project_npc_team_dir = os.path.abspath("./npc_team")
|
|
28
|
+
project_npc_path = os.path.join(project_npc_team_dir, f"{npc_name}.npc")
|
|
29
|
+
|
|
30
|
+
# Then, check in global npc_team directory
|
|
31
|
+
user_npc_team_dir = os.path.expanduser("~/.npcsh/npc_team")
|
|
32
|
+
global_npc_path = os.path.join(user_npc_team_dir, f"{npc_name}.npc")
|
|
33
|
+
|
|
34
|
+
# Check database for compiled NPCs
|
|
35
|
+
try:
|
|
36
|
+
with sqlite3.connect(db_path) as conn:
|
|
37
|
+
cursor = conn.cursor()
|
|
38
|
+
query = f"SELECT source_path FROM compiled_npcs WHERE name = '{npc_name}'"
|
|
39
|
+
cursor.execute(query)
|
|
40
|
+
result = cursor.fetchone()
|
|
41
|
+
if result:
|
|
42
|
+
return result[0]
|
|
43
|
+
|
|
44
|
+
except Exception as e:
|
|
45
|
+
try:
|
|
46
|
+
with sqlite3.connect(db_path) as conn:
|
|
47
|
+
cursor = conn.cursor()
|
|
48
|
+
query = f"SELECT source_path FROM compiled_npcs WHERE name = {npc_name}"
|
|
49
|
+
cursor.execute(query)
|
|
50
|
+
result = cursor.fetchone()
|
|
51
|
+
if result:
|
|
52
|
+
return result[0]
|
|
53
|
+
except Exception as e:
|
|
54
|
+
print(f"Database query error: {e}")
|
|
55
|
+
|
|
56
|
+
# Fallback to file paths
|
|
57
|
+
if os.path.exists(project_npc_path):
|
|
58
|
+
return project_npc_path
|
|
59
|
+
|
|
60
|
+
if os.path.exists(global_npc_path):
|
|
61
|
+
return global_npc_path
|
|
62
|
+
|
|
63
|
+
raise ValueError(f"NPC file not found: {npc_name}")
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def initialize_base_npcs_if_needed(db_path: str) -> None:
|
|
67
|
+
"""
|
|
68
|
+
Function Description:
|
|
69
|
+
This function initializes the base NPCs if they are not already in the database.
|
|
70
|
+
Args:
|
|
71
|
+
db_path: The path to the database file.
|
|
72
|
+
Keyword Args:
|
|
73
|
+
|
|
74
|
+
None
|
|
75
|
+
Returns:
|
|
76
|
+
None
|
|
77
|
+
"""
|
|
78
|
+
|
|
79
|
+
if is_npcsh_initialized():
|
|
80
|
+
return
|
|
81
|
+
|
|
82
|
+
conn = sqlite3.connect(db_path)
|
|
83
|
+
cursor = conn.cursor()
|
|
84
|
+
|
|
85
|
+
# Create the compiled_npcs table if it doesn't exist
|
|
86
|
+
cursor.execute(
|
|
87
|
+
"""
|
|
88
|
+
CREATE TABLE IF NOT EXISTS compiled_npcs (
|
|
89
|
+
name TEXT PRIMARY KEY,
|
|
90
|
+
source_path TEXT NOT NULL,
|
|
91
|
+
compiled_content TEXT
|
|
92
|
+
)
|
|
93
|
+
"""
|
|
94
|
+
)
|
|
95
|
+
|
|
96
|
+
# Get the path to the npc_team directory in the package
|
|
97
|
+
package_dir = os.path.dirname(__file__)
|
|
98
|
+
package_npc_team_dir = os.path.join(package_dir, "npc_team")
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
# User's global npc_team directory
|
|
103
|
+
user_npc_team_dir = os.path.expanduser("~/.npcsh/npc_team")
|
|
104
|
+
|
|
105
|
+
user_jinxs_dir = os.path.join(user_npc_team_dir, "jinxs")
|
|
106
|
+
user_templates_dir = os.path.join(user_npc_team_dir, "templates")
|
|
107
|
+
os.makedirs(user_npc_team_dir, exist_ok=True)
|
|
108
|
+
os.makedirs(user_jinxs_dir, exist_ok=True)
|
|
109
|
+
os.makedirs(user_templates_dir, exist_ok=True)
|
|
110
|
+
# Copy NPCs from package to user directory
|
|
111
|
+
for filename in os.listdir(package_npc_team_dir):
|
|
112
|
+
if filename.endswith(".npc"):
|
|
113
|
+
source_path = os.path.join(package_npc_team_dir, filename)
|
|
114
|
+
destination_path = os.path.join(user_npc_team_dir, filename)
|
|
115
|
+
if not os.path.exists(destination_path) or file_has_changed(
|
|
116
|
+
source_path, destination_path
|
|
117
|
+
):
|
|
118
|
+
shutil.copy2(source_path, destination_path)
|
|
119
|
+
print(f"Copied NPC {filename} to {destination_path}")
|
|
120
|
+
if filename.endswith(".ctx"):
|
|
121
|
+
source_path = os.path.join(package_npc_team_dir, filename)
|
|
122
|
+
destination_path = os.path.join(user_npc_team_dir, filename)
|
|
123
|
+
if not os.path.exists(destination_path) or file_has_changed(
|
|
124
|
+
source_path, destination_path
|
|
125
|
+
):
|
|
126
|
+
shutil.copy2(source_path, destination_path)
|
|
127
|
+
print(f"Copied ctx {filename} to {destination_path}")
|
|
128
|
+
|
|
129
|
+
# Copy jinxs from package to user directory
|
|
130
|
+
package_jinxs_dir = os.path.join(package_npc_team_dir, "jinxs")
|
|
131
|
+
if os.path.exists(package_jinxs_dir):
|
|
132
|
+
for filename in os.listdir(package_jinxs_dir):
|
|
133
|
+
if filename.endswith(".jinx"):
|
|
134
|
+
source_jinx_path = os.path.join(package_jinxs_dir, filename)
|
|
135
|
+
destination_jinx_path = os.path.join(user_jinxs_dir, filename)
|
|
136
|
+
if (not os.path.exists(destination_jinx_path)) or file_has_changed(
|
|
137
|
+
source_jinx_path, destination_jinx_path
|
|
138
|
+
):
|
|
139
|
+
shutil.copy2(source_jinx_path, destination_jinx_path)
|
|
140
|
+
print(f"Copied jinx {filename} to {destination_jinx_path}")
|
|
141
|
+
|
|
142
|
+
templates = os.path.join(package_npc_team_dir, "templates")
|
|
143
|
+
if os.path.exists(templates):
|
|
144
|
+
for folder in os.listdir(templates):
|
|
145
|
+
os.makedirs(os.path.join(user_templates_dir, folder), exist_ok=True)
|
|
146
|
+
for file in os.listdir(os.path.join(templates, folder)):
|
|
147
|
+
if file.endswith(".npc"):
|
|
148
|
+
source_template_path = os.path.join(templates, folder, file)
|
|
149
|
+
|
|
150
|
+
destination_template_path = os.path.join(
|
|
151
|
+
user_templates_dir, folder, file
|
|
152
|
+
)
|
|
153
|
+
if not os.path.exists(
|
|
154
|
+
destination_template_path
|
|
155
|
+
) or file_has_changed(
|
|
156
|
+
source_template_path, destination_template_path
|
|
157
|
+
):
|
|
158
|
+
shutil.copy2(source_template_path, destination_template_path)
|
|
159
|
+
print(f"Copied template {file} to {destination_template_path}")
|
|
160
|
+
conn.commit()
|
|
161
|
+
conn.close()
|
|
162
|
+
set_npcsh_initialized()
|
|
163
|
+
add_npcshrc_to_shell_config()
|
|
164
|
+
|
|
165
|
+
def get_shell_config_file() -> str:
|
|
166
|
+
"""
|
|
167
|
+
|
|
168
|
+
Function Description:
|
|
169
|
+
This function returns the path to the shell configuration file.
|
|
170
|
+
Args:
|
|
171
|
+
None
|
|
172
|
+
Keyword Args:
|
|
173
|
+
None
|
|
174
|
+
Returns:
|
|
175
|
+
The path to the shell configuration file.
|
|
176
|
+
"""
|
|
177
|
+
# Check the current shell
|
|
178
|
+
shell = os.environ.get("SHELL", "")
|
|
179
|
+
|
|
180
|
+
if "zsh" in shell:
|
|
181
|
+
return os.path.expanduser("~/.zshrc")
|
|
182
|
+
elif "bash" in shell:
|
|
183
|
+
# On macOS, use .bash_profile for login shells
|
|
184
|
+
if platform.system() == "Darwin":
|
|
185
|
+
return os.path.expanduser("~/.bash_profile")
|
|
186
|
+
else:
|
|
187
|
+
return os.path.expanduser("~/.bashrc")
|
|
188
|
+
else:
|
|
189
|
+
# Default to .bashrc if we can't determine the shell
|
|
190
|
+
return os.path.expanduser("~/.bashrc")
|
|
191
|
+
|
|
192
|
+
|
|
193
|
+
|
|
194
|
+
def add_npcshrc_to_shell_config() -> None:
|
|
195
|
+
"""
|
|
196
|
+
Function Description:
|
|
197
|
+
This function adds the sourcing of the .npcshrc file to the user's shell configuration file.
|
|
198
|
+
Args:
|
|
199
|
+
None
|
|
200
|
+
Keyword Args:
|
|
201
|
+
None
|
|
202
|
+
Returns:
|
|
203
|
+
None
|
|
204
|
+
"""
|
|
205
|
+
|
|
206
|
+
if os.getenv("NPCSH_INITIALIZED") is not None:
|
|
207
|
+
return
|
|
208
|
+
config_file = get_shell_config_file()
|
|
209
|
+
npcshrc_line = "\n# Source NPCSH configuration\nif [ -f ~/.npcshrc ]; then\n . ~/.npcshrc\nfi\n"
|
|
210
|
+
|
|
211
|
+
with open(config_file, "a+") as shell_config:
|
|
212
|
+
shell_config.seek(0)
|
|
213
|
+
content = shell_config.read()
|
|
214
|
+
if "source ~/.npcshrc" not in content and ". ~/.npcshrc" not in content:
|
|
215
|
+
shell_config.write(npcshrc_line)
|
|
216
|
+
print(f"Added .npcshrc sourcing to {config_file}")
|
|
217
|
+
else:
|
|
218
|
+
print(f".npcshrc already sourced in {config_file}")
|
|
219
|
+
|
|
220
|
+
def ensure_npcshrc_exists() -> str:
|
|
221
|
+
"""
|
|
222
|
+
Function Description:
|
|
223
|
+
This function ensures that the .npcshrc file exists in the user's home directory.
|
|
224
|
+
Args:
|
|
225
|
+
None
|
|
226
|
+
Keyword Args:
|
|
227
|
+
None
|
|
228
|
+
Returns:
|
|
229
|
+
The path to the .npcshrc file.
|
|
230
|
+
"""
|
|
231
|
+
|
|
232
|
+
npcshrc_path = os.path.expanduser("~/.npcshrc")
|
|
233
|
+
if not os.path.exists(npcshrc_path):
|
|
234
|
+
with open(npcshrc_path, "w") as npcshrc:
|
|
235
|
+
npcshrc.write("# NPCSH Configuration File\n")
|
|
236
|
+
npcshrc.write("export NPCSH_INITIALIZED=0\n")
|
|
237
|
+
npcshrc.write("export NPCSH_DEFAULT_MODE='agent'\n")
|
|
238
|
+
npcshrc.write("export NPCSH_CHAT_PROVIDER='ollama'\n")
|
|
239
|
+
npcshrc.write("export NPCSH_CHAT_MODEL='llama3.2'\n")
|
|
240
|
+
npcshrc.write("export NPCSH_REASONING_PROVIDER='ollama'\n")
|
|
241
|
+
npcshrc.write("export NPCSH_REASONING_MODEL='deepseek-r1'\n")
|
|
242
|
+
|
|
243
|
+
npcshrc.write("export NPCSH_EMBEDDING_PROVIDER='ollama'\n")
|
|
244
|
+
npcshrc.write("export NPCSH_EMBEDDING_MODEL='nomic-embed-text'\n")
|
|
245
|
+
npcshrc.write("export NPCSH_VISION_PROVIDER='ollama'\n")
|
|
246
|
+
npcshrc.write("export NPCSH_VISION_MODEL='llava7b'\n")
|
|
247
|
+
npcshrc.write(
|
|
248
|
+
"export NPCSH_IMAGE_GEN_MODEL='runwayml/stable-diffusion-v1-5'\n"
|
|
249
|
+
)
|
|
250
|
+
|
|
251
|
+
npcshrc.write("export NPCSH_IMAGE_GEN_PROVIDER='diffusers'\n")
|
|
252
|
+
npcshrc.write(
|
|
253
|
+
"export NPCSH_VIDEO_GEN_MODEL='runwayml/stable-diffusion-v1-5'\n"
|
|
254
|
+
)
|
|
255
|
+
|
|
256
|
+
npcshrc.write("export NPCSH_VIDEO_GEN_PROVIDER='diffusers'\n")
|
|
257
|
+
|
|
258
|
+
npcshrc.write("export NPCSH_API_URL=''\n")
|
|
259
|
+
npcshrc.write("export NPCSH_DB_PATH='~/npcsh_history.db'\n")
|
|
260
|
+
npcshrc.write("export NPCSH_VECTOR_DB_PATH='~/npcsh_chroma.db'\n")
|
|
261
|
+
npcshrc.write("export NPCSH_STREAM_OUTPUT=0")
|
|
262
|
+
return npcshrc_path
|
|
263
|
+
|
|
264
|
+
|
|
265
|
+
|
|
266
|
+
def setup_npcsh_config() -> None:
|
|
267
|
+
"""
|
|
268
|
+
Function Description:
|
|
269
|
+
This function initializes the NPCSH configuration.
|
|
270
|
+
Args:
|
|
271
|
+
None
|
|
272
|
+
Keyword Args:
|
|
273
|
+
None
|
|
274
|
+
Returns:
|
|
275
|
+
None
|
|
276
|
+
"""
|
|
277
|
+
|
|
278
|
+
ensure_npcshrc_exists()
|
|
279
|
+
add_npcshrc_to_shell_config()
|
|
280
|
+
|
|
281
|
+
|
|
282
|
+
BASH_COMMANDS = [
|
|
283
|
+
"npc",
|
|
284
|
+
"npm",
|
|
285
|
+
"npx",
|
|
286
|
+
"open",
|
|
287
|
+
"alias",
|
|
288
|
+
"bg",
|
|
289
|
+
"bind",
|
|
290
|
+
"break",
|
|
291
|
+
"builtin",
|
|
292
|
+
"case",
|
|
293
|
+
"command",
|
|
294
|
+
"compgen",
|
|
295
|
+
"complete",
|
|
296
|
+
"continue",
|
|
297
|
+
"declare",
|
|
298
|
+
"dirs",
|
|
299
|
+
"disown",
|
|
300
|
+
"echo",
|
|
301
|
+
"enable",
|
|
302
|
+
"eval",
|
|
303
|
+
"exec",
|
|
304
|
+
"exit",
|
|
305
|
+
"export",
|
|
306
|
+
"fc",
|
|
307
|
+
"fg",
|
|
308
|
+
"getopts",
|
|
309
|
+
"hash",
|
|
310
|
+
"help",
|
|
311
|
+
"history",
|
|
312
|
+
"if",
|
|
313
|
+
"jobs",
|
|
314
|
+
"kill",
|
|
315
|
+
"let",
|
|
316
|
+
"local",
|
|
317
|
+
"logout",
|
|
318
|
+
"ollama",
|
|
319
|
+
"popd",
|
|
320
|
+
"printf",
|
|
321
|
+
"pushd",
|
|
322
|
+
"pwd",
|
|
323
|
+
"read",
|
|
324
|
+
"readonly",
|
|
325
|
+
"return",
|
|
326
|
+
"set",
|
|
327
|
+
"shift",
|
|
328
|
+
"shopt",
|
|
329
|
+
"source",
|
|
330
|
+
"suspend",
|
|
331
|
+
"test",
|
|
332
|
+
"times",
|
|
333
|
+
"trap",
|
|
334
|
+
"type",
|
|
335
|
+
"typeset",
|
|
336
|
+
"ulimit",
|
|
337
|
+
"umask",
|
|
338
|
+
"unalias",
|
|
339
|
+
"unset",
|
|
340
|
+
"until",
|
|
341
|
+
"wait",
|
|
342
|
+
"while",
|
|
343
|
+
# Common Unix commands
|
|
344
|
+
"ls",
|
|
345
|
+
"cp",
|
|
346
|
+
"mv",
|
|
347
|
+
"rm",
|
|
348
|
+
"mkdir",
|
|
349
|
+
"rmdir",
|
|
350
|
+
"touch",
|
|
351
|
+
"cat",
|
|
352
|
+
"less",
|
|
353
|
+
"more",
|
|
354
|
+
"head",
|
|
355
|
+
"tail",
|
|
356
|
+
"grep",
|
|
357
|
+
"find",
|
|
358
|
+
"sed",
|
|
359
|
+
"awk",
|
|
360
|
+
"sort",
|
|
361
|
+
"uniq",
|
|
362
|
+
"wc",
|
|
363
|
+
"diff",
|
|
364
|
+
"chmod",
|
|
365
|
+
"chown",
|
|
366
|
+
"chgrp",
|
|
367
|
+
"ln",
|
|
368
|
+
"tar",
|
|
369
|
+
"gzip",
|
|
370
|
+
"gunzip",
|
|
371
|
+
"zip",
|
|
372
|
+
"unzip",
|
|
373
|
+
"ssh",
|
|
374
|
+
"scp",
|
|
375
|
+
"rsync",
|
|
376
|
+
"wget",
|
|
377
|
+
"curl",
|
|
378
|
+
"ping",
|
|
379
|
+
"netstat",
|
|
380
|
+
"ifconfig",
|
|
381
|
+
"route",
|
|
382
|
+
"traceroute",
|
|
383
|
+
"ps",
|
|
384
|
+
"top",
|
|
385
|
+
"htop",
|
|
386
|
+
"kill",
|
|
387
|
+
"killall",
|
|
388
|
+
"su",
|
|
389
|
+
"sudo",
|
|
390
|
+
"whoami",
|
|
391
|
+
"who",
|
|
392
|
+
"w",
|
|
393
|
+
"last",
|
|
394
|
+
"finger",
|
|
395
|
+
"uptime",
|
|
396
|
+
"free",
|
|
397
|
+
"df",
|
|
398
|
+
"du",
|
|
399
|
+
"mount",
|
|
400
|
+
"umount",
|
|
401
|
+
"fdisk",
|
|
402
|
+
"mkfs",
|
|
403
|
+
"fsck",
|
|
404
|
+
"dd",
|
|
405
|
+
"cron",
|
|
406
|
+
"at",
|
|
407
|
+
"systemctl",
|
|
408
|
+
"service",
|
|
409
|
+
"journalctl",
|
|
410
|
+
"man",
|
|
411
|
+
"info",
|
|
412
|
+
"whatis",
|
|
413
|
+
"whereis",
|
|
414
|
+
"which",
|
|
415
|
+
"date",
|
|
416
|
+
"cal",
|
|
417
|
+
"bc",
|
|
418
|
+
"expr",
|
|
419
|
+
"screen",
|
|
420
|
+
"tmux",
|
|
421
|
+
"git",
|
|
422
|
+
"vim",
|
|
423
|
+
"emacs",
|
|
424
|
+
"nano",
|
|
425
|
+
"pip",
|
|
426
|
+
]
|
|
427
|
+
|
|
428
|
+
|
|
429
|
+
interactive_commands = {
|
|
430
|
+
"ipython": ["ipython"],
|
|
431
|
+
"python": ["python", "-i"],
|
|
432
|
+
"sqlite3": ["sqlite3"],
|
|
433
|
+
"r": ["R", "--interactive"],
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
|
|
437
|
+
def start_interactive_session(command: list) -> int:
|
|
438
|
+
"""
|
|
439
|
+
Starts an interactive session. Only works on Unix. On Windows, print a message and return 1.
|
|
440
|
+
"""
|
|
441
|
+
if ON_WINDOWS or termios is None or tty is None or pty is None or select is None or signal is None:
|
|
442
|
+
print("Interactive terminal sessions are not supported on Windows.")
|
|
443
|
+
return 1
|
|
444
|
+
# Save the current terminal settings
|
|
445
|
+
old_tty = termios.tcgetattr(sys.stdin)
|
|
446
|
+
try:
|
|
447
|
+
# Create a pseudo-terminal
|
|
448
|
+
master_fd, slave_fd = pty.openpty()
|
|
449
|
+
|
|
450
|
+
# Start the process
|
|
451
|
+
p = subprocess.Popen(
|
|
452
|
+
command,
|
|
453
|
+
stdin=slave_fd,
|
|
454
|
+
stdout=slave_fd,
|
|
455
|
+
stderr=slave_fd,
|
|
456
|
+
shell=True,
|
|
457
|
+
preexec_fn=os.setsid, # Create a new process group
|
|
458
|
+
)
|
|
459
|
+
|
|
460
|
+
# Set the terminal to raw mode
|
|
461
|
+
tty.setraw(sys.stdin.fileno())
|
|
462
|
+
|
|
463
|
+
def handle_timeout(signum, frame):
|
|
464
|
+
raise TimeoutError("Process did not terminate in time")
|
|
465
|
+
|
|
466
|
+
while p.poll() is None:
|
|
467
|
+
r, w, e = select.select([sys.stdin, master_fd], [], [], 0.1)
|
|
468
|
+
if sys.stdin in r:
|
|
469
|
+
d = os.read(sys.stdin.fileno(), 10240)
|
|
470
|
+
os.write(master_fd, d)
|
|
471
|
+
elif master_fd in r:
|
|
472
|
+
o = os.read(master_fd, 10240)
|
|
473
|
+
if o:
|
|
474
|
+
os.write(sys.stdout.fileno(), o)
|
|
475
|
+
else:
|
|
476
|
+
break
|
|
477
|
+
|
|
478
|
+
# Wait for the process to terminate with a timeout
|
|
479
|
+
signal.signal(signal.SIGALRM, handle_timeout)
|
|
480
|
+
signal.alarm(5) # 5 second timeout
|
|
481
|
+
try:
|
|
482
|
+
p.wait()
|
|
483
|
+
except TimeoutError:
|
|
484
|
+
print("\nProcess did not terminate. Force killing...")
|
|
485
|
+
os.killpg(os.getpgid(p.pid), signal.SIGTERM)
|
|
486
|
+
time.sleep(1)
|
|
487
|
+
if p.poll() is None:
|
|
488
|
+
os.killpg(os.getpgid(p.pid), signal.SIGKILL)
|
|
489
|
+
finally:
|
|
490
|
+
signal.alarm(0)
|
|
491
|
+
|
|
492
|
+
finally:
|
|
493
|
+
# Restore the terminal settings
|
|
494
|
+
termios.tcsetattr(sys.stdin, termios.TCSAFLUSH, old_tty)
|
|
495
|
+
|
|
496
|
+
return p.returncode
|
|
497
|
+
|
|
498
|
+
def validate_bash_command(command_parts: list) -> bool:
|
|
499
|
+
"""
|
|
500
|
+
Function Description:
|
|
501
|
+
Validate if the command sequence is a valid bash command with proper arguments/flags.
|
|
502
|
+
Args:
|
|
503
|
+
command_parts : list : Command parts
|
|
504
|
+
Keyword Args:
|
|
505
|
+
None
|
|
506
|
+
Returns:
|
|
507
|
+
bool : bool : Boolean
|
|
508
|
+
"""
|
|
509
|
+
if not command_parts:
|
|
510
|
+
return False
|
|
511
|
+
|
|
512
|
+
COMMAND_PATTERNS = {
|
|
513
|
+
"cat": {
|
|
514
|
+
"flags": ["-n", "-b", "-E", "-T", "-s", "--number", "-A", "--show-all"],
|
|
515
|
+
"requires_arg": True,
|
|
516
|
+
},
|
|
517
|
+
"find": {
|
|
518
|
+
"flags": [
|
|
519
|
+
"-name",
|
|
520
|
+
"-type",
|
|
521
|
+
"-size",
|
|
522
|
+
"-mtime",
|
|
523
|
+
"-exec",
|
|
524
|
+
"-print",
|
|
525
|
+
"-delete",
|
|
526
|
+
"-maxdepth",
|
|
527
|
+
"-mindepth",
|
|
528
|
+
"-perm",
|
|
529
|
+
"-user",
|
|
530
|
+
"-group",
|
|
531
|
+
],
|
|
532
|
+
"requires_arg": True,
|
|
533
|
+
},
|
|
534
|
+
"who": {
|
|
535
|
+
"flags": [
|
|
536
|
+
"-a",
|
|
537
|
+
"-b",
|
|
538
|
+
"-d",
|
|
539
|
+
"-H",
|
|
540
|
+
"-l",
|
|
541
|
+
"-p",
|
|
542
|
+
"-q",
|
|
543
|
+
"-r",
|
|
544
|
+
"-s",
|
|
545
|
+
"-t",
|
|
546
|
+
"-u",
|
|
547
|
+
"--all",
|
|
548
|
+
"--count",
|
|
549
|
+
"--heading",
|
|
550
|
+
],
|
|
551
|
+
"requires_arg": True,
|
|
552
|
+
},
|
|
553
|
+
"open": {
|
|
554
|
+
"flags": ["-a", "-e", "-t", "-f", "-F", "-W", "-n", "-g", "-h"],
|
|
555
|
+
"requires_arg": True,
|
|
556
|
+
},
|
|
557
|
+
"which": {"flags": ["-a", "-s", "-v"], "requires_arg": True},
|
|
558
|
+
}
|
|
559
|
+
|
|
560
|
+
base_command = command_parts[0]
|
|
561
|
+
|
|
562
|
+
if base_command not in COMMAND_PATTERNS:
|
|
563
|
+
return True # Allow other commands to pass through
|
|
564
|
+
|
|
565
|
+
pattern = COMMAND_PATTERNS[base_command]
|
|
566
|
+
args = []
|
|
567
|
+
flags = []
|
|
568
|
+
|
|
569
|
+
for i in range(1, len(command_parts)):
|
|
570
|
+
part = command_parts[i]
|
|
571
|
+
if part.startswith("-"):
|
|
572
|
+
flags.append(part)
|
|
573
|
+
if part not in pattern["flags"]:
|
|
574
|
+
return False # Invalid flag
|
|
575
|
+
else:
|
|
576
|
+
args.append(part)
|
|
577
|
+
|
|
578
|
+
# Check if 'who' has any arguments (it shouldn't)
|
|
579
|
+
if base_command == "who" and args:
|
|
580
|
+
return False
|
|
581
|
+
|
|
582
|
+
# Handle 'which' with '-a' flag
|
|
583
|
+
if base_command == "which" and "-a" in flags:
|
|
584
|
+
return True # Allow 'which -a' with or without arguments.
|
|
585
|
+
|
|
586
|
+
# Check if any required arguments are missing
|
|
587
|
+
if pattern.get("requires_arg", False) and not args:
|
|
588
|
+
return False
|
|
589
|
+
|
|
590
|
+
return True
|
|
591
|
+
|
|
592
|
+
|
|
593
|
+
def is_npcsh_initialized() -> bool:
|
|
594
|
+
"""
|
|
595
|
+
Function Description:
|
|
596
|
+
This function checks if the NPCSH initialization flag is set.
|
|
597
|
+
Args:
|
|
598
|
+
None
|
|
599
|
+
Keyword Args:
|
|
600
|
+
None
|
|
601
|
+
Returns:
|
|
602
|
+
A boolean indicating whether NPCSH is initialized.
|
|
603
|
+
"""
|
|
604
|
+
|
|
605
|
+
return os.environ.get("NPCSH_INITIALIZED", None) == "1"
|
|
606
|
+
|
|
607
|
+
|
|
608
|
+
def execute_set_command(command: str, value: str) -> str:
|
|
609
|
+
"""
|
|
610
|
+
Function Description:
|
|
611
|
+
This function sets a configuration value in the .npcshrc file.
|
|
612
|
+
Args:
|
|
613
|
+
command: The command to execute.
|
|
614
|
+
value: The value to set.
|
|
615
|
+
Keyword Args:
|
|
616
|
+
None
|
|
617
|
+
Returns:
|
|
618
|
+
A message indicating the success or failure of the operation.
|
|
619
|
+
"""
|
|
620
|
+
|
|
621
|
+
config_path = os.path.expanduser("~/.npcshrc")
|
|
622
|
+
|
|
623
|
+
# Map command to environment variable name
|
|
624
|
+
var_map = {
|
|
625
|
+
"model": "NPCSH_CHAT_MODEL",
|
|
626
|
+
"provider": "NPCSH_CHAT_PROVIDER",
|
|
627
|
+
"db_path": "NPCSH_DB_PATH",
|
|
628
|
+
}
|
|
629
|
+
|
|
630
|
+
if command not in var_map:
|
|
631
|
+
return f"Unknown setting: {command}"
|
|
632
|
+
|
|
633
|
+
env_var = var_map[command]
|
|
634
|
+
|
|
635
|
+
# Read the current configuration
|
|
636
|
+
if os.path.exists(config_path):
|
|
637
|
+
with open(config_path, "r") as f:
|
|
638
|
+
lines = f.readlines()
|
|
639
|
+
else:
|
|
640
|
+
lines = []
|
|
641
|
+
|
|
642
|
+
# Check if the property exists and update it, or add it if it doesn't exist
|
|
643
|
+
property_exists = False
|
|
644
|
+
for i, line in enumerate(lines):
|
|
645
|
+
if line.startswith(f"export {env_var}="):
|
|
646
|
+
lines[i] = f"export {env_var}='{value}'\n"
|
|
647
|
+
property_exists = True
|
|
648
|
+
break
|
|
649
|
+
|
|
650
|
+
if not property_exists:
|
|
651
|
+
lines.append(f"export {env_var}='{value}'\n")
|
|
652
|
+
|
|
653
|
+
# Save the updated configuration
|
|
654
|
+
with open(config_path, "w") as f:
|
|
655
|
+
f.writelines(lines)
|
|
656
|
+
|
|
657
|
+
return f"{command.capitalize()} has been set to: {value}"
|
|
658
|
+
|
|
659
|
+
|
|
660
|
+
def set_npcsh_initialized() -> None:
|
|
661
|
+
"""
|
|
662
|
+
Function Description:
|
|
663
|
+
This function sets the NPCSH initialization flag in the .npcshrc file.
|
|
664
|
+
Args:
|
|
665
|
+
None
|
|
666
|
+
Keyword Args:
|
|
667
|
+
None
|
|
668
|
+
Returns:
|
|
669
|
+
|
|
670
|
+
None
|
|
671
|
+
"""
|
|
672
|
+
|
|
673
|
+
npcshrc_path = ensure_npcshrc_exists()
|
|
674
|
+
|
|
675
|
+
with open(npcshrc_path, "r+") as npcshrc:
|
|
676
|
+
content = npcshrc.read()
|
|
677
|
+
if "export NPCSH_INITIALIZED=0" in content:
|
|
678
|
+
content = content.replace(
|
|
679
|
+
"export NPCSH_INITIALIZED=0", "export NPCSH_INITIALIZED=1"
|
|
680
|
+
)
|
|
681
|
+
npcshrc.seek(0)
|
|
682
|
+
npcshrc.write(content)
|
|
683
|
+
npcshrc.truncate()
|
|
684
|
+
|
|
685
|
+
# Also set it for the current session
|
|
686
|
+
os.environ["NPCSH_INITIALIZED"] = "1"
|
|
687
|
+
print("NPCSH initialization flag set in .npcshrc")
|
|
688
|
+
|
|
689
|
+
|
|
690
|
+
|
|
691
|
+
def file_has_changed(source_path: str, destination_path: str) -> bool:
|
|
692
|
+
"""
|
|
693
|
+
Function Description:
|
|
694
|
+
This function compares two files to determine if they are different.
|
|
695
|
+
Args:
|
|
696
|
+
source_path: The path to the source file.
|
|
697
|
+
destination_path: The path to the destination file.
|
|
698
|
+
Keyword Args:
|
|
699
|
+
None
|
|
700
|
+
Returns:
|
|
701
|
+
A boolean indicating whether the files are different
|
|
702
|
+
"""
|
|
703
|
+
|
|
704
|
+
# Compare file modification times or contents to decide whether to update the file
|
|
705
|
+
return not filecmp.cmp(source_path, destination_path, shallow=False)
|
|
706
|
+
|
|
707
|
+
|
|
708
|
+
def list_directory(args: List[str]) -> None:
|
|
709
|
+
"""
|
|
710
|
+
Function Description:
|
|
711
|
+
This function lists the contents of a directory.
|
|
712
|
+
Args:
|
|
713
|
+
args: The command arguments.
|
|
714
|
+
Keyword Args:
|
|
715
|
+
None
|
|
716
|
+
Returns:
|
|
717
|
+
None
|
|
718
|
+
"""
|
|
719
|
+
directory = args[0] if args else "."
|
|
720
|
+
try:
|
|
721
|
+
files = os.listdir(directory)
|
|
722
|
+
for f in files:
|
|
723
|
+
print(f)
|
|
724
|
+
except Exception as e:
|
|
725
|
+
print(f"Error listing directory: {e}")
|
|
726
|
+
|
|
727
|
+
|
|
728
|
+
|
|
729
|
+
def change_directory(command_parts: list, messages: list) -> dict:
|
|
730
|
+
"""
|
|
731
|
+
Function Description:
|
|
732
|
+
Changes the current directory.
|
|
733
|
+
Args:
|
|
734
|
+
command_parts : list : Command parts
|
|
735
|
+
messages : list : Messages
|
|
736
|
+
Keyword Args:
|
|
737
|
+
None
|
|
738
|
+
Returns:
|
|
739
|
+
dict : dict : Dictionary
|
|
740
|
+
|
|
741
|
+
"""
|
|
742
|
+
|
|
743
|
+
try:
|
|
744
|
+
if len(command_parts) > 1:
|
|
745
|
+
new_dir = os.path.expanduser(command_parts[1])
|
|
746
|
+
else:
|
|
747
|
+
new_dir = os.path.expanduser("~")
|
|
748
|
+
os.chdir(new_dir)
|
|
749
|
+
return {
|
|
750
|
+
"messages": messages,
|
|
751
|
+
"output": f"Changed directory to {os.getcwd()}",
|
|
752
|
+
}
|
|
753
|
+
except FileNotFoundError:
|
|
754
|
+
return {
|
|
755
|
+
"messages": messages,
|
|
756
|
+
"output": f"Directory not found: {new_dir}",
|
|
757
|
+
}
|
|
758
|
+
except PermissionError:
|
|
759
|
+
return {"messages": messages, "output": f"Permission denied: {new_dir}"}
|
|
760
|
+
|
|
761
|
+
|
|
762
|
+
def orange(text: str) -> str:
|
|
763
|
+
"""
|
|
764
|
+
Function Description:
|
|
765
|
+
Returns orange text.
|
|
766
|
+
Args:
|
|
767
|
+
text : str : Text
|
|
768
|
+
Keyword Args:
|
|
769
|
+
None
|
|
770
|
+
Returns:
|
|
771
|
+
text : str : Text
|
|
772
|
+
|
|
773
|
+
"""
|
|
774
|
+
return f"\033[38;2;255;165;0m{text}{Style.RESET_ALL}"
|
|
775
|
+
|
|
776
|
+
|
|
777
|
+
def get_npcshrc_path_windows():
|
|
778
|
+
return Path.home() / ".npcshrc"
|
|
779
|
+
|
|
780
|
+
|
|
781
|
+
def read_rc_file_windows(path):
|
|
782
|
+
"""Read shell-style rc file"""
|
|
783
|
+
config = {}
|
|
784
|
+
if not path.exists():
|
|
785
|
+
return config
|
|
786
|
+
|
|
787
|
+
with open(path) as f:
|
|
788
|
+
for line in f:
|
|
789
|
+
line = line.strip()
|
|
790
|
+
if line and not line.startswith("#"):
|
|
791
|
+
# Match KEY='value' or KEY="value" format
|
|
792
|
+
match = re.match(r'^([A-Z_]+)\s*=\s*[\'"](.*?)[\'"]$', line)
|
|
793
|
+
if match:
|
|
794
|
+
key, value = match.groups()
|
|
795
|
+
config[key] = value
|
|
796
|
+
return config
|
|
797
|
+
|
|
798
|
+
|
|
799
|
+
def get_setting_windows(key, default=None):
|
|
800
|
+
# Try environment variable first
|
|
801
|
+
if env_value := os.getenv(key):
|
|
802
|
+
return env_value
|
|
803
|
+
|
|
804
|
+
# Fall back to .npcshrc file
|
|
805
|
+
config = read_rc_file_windows(get_npcshrc_path_windows())
|
|
806
|
+
return config.get(key, default)
|
|
807
|
+
|
|
808
|
+
NPCSH_CHAT_MODEL = os.environ.get("NPCSH_CHAT_MODEL", "llama3.2")
|
|
809
|
+
# print("NPCSH_CHAT_MODEL", NPCSH_CHAT_MODEL)
|
|
810
|
+
NPCSH_CHAT_PROVIDER = os.environ.get("NPCSH_CHAT_PROVIDER", "ollama")
|
|
811
|
+
# print("NPCSH_CHAT_PROVIDER", NPCSH_CHAT_PROVIDER)
|
|
812
|
+
NPCSH_DB_PATH = os.path.expanduser(
|
|
813
|
+
os.environ.get("NPCSH_DB_PATH", "~/npcsh_history.db")
|
|
814
|
+
)
|
|
815
|
+
NPCSH_VECTOR_DB_PATH = os.path.expanduser(
|
|
816
|
+
os.environ.get("NPCSH_VECTOR_DB_PATH", "~/npcsh_chroma.db")
|
|
817
|
+
)
|
|
818
|
+
#DEFAULT MODES = ['CHAT', 'AGENT', 'CODE', ]
|
|
819
|
+
|
|
820
|
+
NPCSH_DEFAULT_MODE = os.path.expanduser(os.environ.get("NPCSH_DEFAULT_MODE", "agent"))
|
|
821
|
+
NPCSH_VISION_MODEL = os.environ.get("NPCSH_VISION_MODEL", "llava:7b")
|
|
822
|
+
NPCSH_VISION_PROVIDER = os.environ.get("NPCSH_VISION_PROVIDER", "ollama")
|
|
823
|
+
NPCSH_IMAGE_GEN_MODEL = os.environ.get(
|
|
824
|
+
"NPCSH_IMAGE_GEN_MODEL", "runwayml/stable-diffusion-v1-5"
|
|
825
|
+
)
|
|
826
|
+
NPCSH_IMAGE_GEN_PROVIDER = os.environ.get("NPCSH_IMAGE_GEN_PROVIDER", "diffusers")
|
|
827
|
+
NPCSH_VIDEO_GEN_MODEL = os.environ.get(
|
|
828
|
+
"NPCSH_VIDEO_GEN_MODEL", "damo-vilab/text-to-video-ms-1.7b"
|
|
829
|
+
)
|
|
830
|
+
NPCSH_VIDEO_GEN_PROVIDER = os.environ.get("NPCSH_VIDEO_GEN_PROVIDER", "diffusers")
|
|
831
|
+
|
|
832
|
+
NPCSH_EMBEDDING_MODEL = os.environ.get("NPCSH_EMBEDDING_MODEL", "nomic-embed-text")
|
|
833
|
+
NPCSH_EMBEDDING_PROVIDER = os.environ.get("NPCSH_EMBEDDING_PROVIDER", "ollama")
|
|
834
|
+
NPCSH_REASONING_MODEL = os.environ.get("NPCSH_REASONING_MODEL", "deepseek-r1")
|
|
835
|
+
NPCSH_REASONING_PROVIDER = os.environ.get("NPCSH_REASONING_PROVIDER", "ollama")
|
|
836
|
+
NPCSH_STREAM_OUTPUT = eval(os.environ.get("NPCSH_STREAM_OUTPUT", "0")) == 1
|
|
837
|
+
NPCSH_API_URL = os.environ.get("NPCSH_API_URL", None)
|
|
838
|
+
NPCSH_SEARCH_PROVIDER = os.environ.get("NPCSH_SEARCH_PROVIDER", "duckduckgo")
|
|
839
|
+
|
|
840
|
+
READLINE_HISTORY_FILE = os.path.expanduser("~/.npcsh_history")
|
|
841
|
+
|
|
842
|
+
|
|
843
|
+
|
|
844
|
+
def setup_readline() -> str:
|
|
845
|
+
if readline is None:
|
|
846
|
+
return None
|
|
847
|
+
try:
|
|
848
|
+
readline.read_history_file(READLINE_HISTORY_FILE)
|
|
849
|
+
readline.set_history_length(1000)
|
|
850
|
+
readline.parse_and_bind("set enable-bracketed-paste on")
|
|
851
|
+
readline.parse_and_bind(r'"\e[A": history-search-backward')
|
|
852
|
+
readline.parse_and_bind(r'"\e[B": history-search-forward')
|
|
853
|
+
readline.parse_and_bind(r'"\C-r": reverse-search-history')
|
|
854
|
+
readline.parse_and_bind(r'\C-e: end-of-line')
|
|
855
|
+
readline.parse_and_bind(r'\C-a: beginning-of-line')
|
|
856
|
+
if sys.platform == "darwin":
|
|
857
|
+
readline.parse_and_bind("bind ^I rl_complete")
|
|
858
|
+
else:
|
|
859
|
+
readline.parse_and_bind("tab: complete")
|
|
860
|
+
return READLINE_HISTORY_FILE
|
|
861
|
+
except FileNotFoundError:
|
|
862
|
+
pass
|
|
863
|
+
except OSError as e:
|
|
864
|
+
print(f"Warning: Could not read readline history file {READLINE_HISTORY_FILE}: {e}")
|
|
865
|
+
|
|
866
|
+
def save_readline_history():
|
|
867
|
+
if readline is None:
|
|
868
|
+
return
|
|
869
|
+
try:
|
|
870
|
+
readline.write_history_file(READLINE_HISTORY_FILE)
|
|
871
|
+
except OSError as e:
|
|
872
|
+
print(f"Warning: Could not write readline history file {READLINE_HISTORY_FILE}: {e}")
|
|
873
|
+
|
|
874
|
+
|
|
875
|
+
|
|
876
|
+
|
|
877
|
+
|
|
878
|
+
from npcpy.memory.command_history import (
|
|
879
|
+
start_new_conversation,
|
|
880
|
+
)
|
|
881
|
+
from dataclasses import dataclass, field
|
|
882
|
+
from typing import Optional, List, Dict, Any, Tuple, Union
|
|
883
|
+
from npcpy.npc_compiler import NPC, Team
|
|
884
|
+
import os
|
|
885
|
+
@dataclass
|
|
886
|
+
class ShellState:
|
|
887
|
+
npc: Optional[Union[NPC, str]] = None
|
|
888
|
+
team: Optional[Team] = None
|
|
889
|
+
messages: List[Dict[str, Any]] = field(default_factory=list)
|
|
890
|
+
mcp_client: Optional[Any] = None
|
|
891
|
+
conversation_id: Optional[int] = None
|
|
892
|
+
chat_model: str = NPCSH_CHAT_MODEL
|
|
893
|
+
chat_provider: str = NPCSH_CHAT_PROVIDER
|
|
894
|
+
vision_model: str = NPCSH_VISION_MODEL
|
|
895
|
+
vision_provider: str = NPCSH_VISION_PROVIDER
|
|
896
|
+
embedding_model: str = NPCSH_EMBEDDING_MODEL
|
|
897
|
+
embedding_provider: str = NPCSH_EMBEDDING_PROVIDER
|
|
898
|
+
reasoning_model: str = NPCSH_REASONING_MODEL
|
|
899
|
+
reasoning_provider: str = NPCSH_REASONING_PROVIDER
|
|
900
|
+
image_gen_model: str = NPCSH_IMAGE_GEN_MODEL
|
|
901
|
+
image_gen_provider: str = NPCSH_IMAGE_GEN_PROVIDER
|
|
902
|
+
video_gen_model: str = NPCSH_VIDEO_GEN_MODEL
|
|
903
|
+
video_gen_provider: str = NPCSH_VIDEO_GEN_PROVIDER
|
|
904
|
+
current_mode: str = NPCSH_DEFAULT_MODE
|
|
905
|
+
api_key: Optional[str] = None
|
|
906
|
+
api_url: Optional[str] = NPCSH_API_URL
|
|
907
|
+
current_path: str = field(default_factory=os.getcwd)
|
|
908
|
+
stream_output: bool = NPCSH_STREAM_OUTPUT
|
|
909
|
+
attachments: Optional[List[Any]] = None
|
|
910
|
+
def get_model_for_command(self, model_type: str = "chat"):
|
|
911
|
+
if model_type == "chat":
|
|
912
|
+
return self.chat_model, self.chat_provider
|
|
913
|
+
elif model_type == "vision":
|
|
914
|
+
return self.vision_model, self.vision_provider
|
|
915
|
+
elif model_type == "embedding":
|
|
916
|
+
return self.embedding_model, self.embedding_provider
|
|
917
|
+
elif model_type == "reasoning":
|
|
918
|
+
return self.reasoning_model, self.reasoning_provider
|
|
919
|
+
elif model_type == "image_gen":
|
|
920
|
+
return self.image_gen_model, self.image_gen_provider
|
|
921
|
+
elif model_type == "video_gen":
|
|
922
|
+
return self.video_gen_model, self.video_gen_provider
|
|
923
|
+
else:
|
|
924
|
+
return self.chat_model, self.chat_provider # Default fallback
|
|
925
|
+
initial_state = ShellState(
|
|
926
|
+
conversation_id=start_new_conversation(),
|
|
927
|
+
stream_output=NPCSH_STREAM_OUTPUT,
|
|
928
|
+
current_mode=NPCSH_DEFAULT_MODE,
|
|
929
|
+
chat_model=NPCSH_CHAT_MODEL,
|
|
930
|
+
chat_provider=NPCSH_CHAT_PROVIDER,
|
|
931
|
+
vision_model=NPCSH_VISION_MODEL,
|
|
932
|
+
vision_provider=NPCSH_VISION_PROVIDER,
|
|
933
|
+
embedding_model=NPCSH_EMBEDDING_MODEL,
|
|
934
|
+
embedding_provider=NPCSH_EMBEDDING_PROVIDER,
|
|
935
|
+
reasoning_model=NPCSH_REASONING_MODEL,
|
|
936
|
+
reasoning_provider=NPCSH_REASONING_PROVIDER,
|
|
937
|
+
image_gen_model=NPCSH_IMAGE_GEN_MODEL,
|
|
938
|
+
image_gen_provider=NPCSH_IMAGE_GEN_PROVIDER,
|
|
939
|
+
video_gen_model=NPCSH_VIDEO_GEN_MODEL,
|
|
940
|
+
video_gen_provider=NPCSH_VIDEO_GEN_PROVIDER,
|
|
941
|
+
api_url=NPCSH_API_URL,
|
|
942
|
+
)
|