npcsh 0.1.2__py3-none-any.whl → 1.1.13__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 +3508 -0
- npcsh/alicanto.py +65 -0
- npcsh/build.py +291 -0
- npcsh/completion.py +206 -0
- npcsh/config.py +163 -0
- npcsh/corca.py +50 -0
- npcsh/execution.py +185 -0
- npcsh/guac.py +46 -0
- npcsh/mcp_helpers.py +357 -0
- npcsh/mcp_server.py +299 -0
- npcsh/npc.py +323 -0
- npcsh/npc_team/alicanto.npc +2 -0
- npcsh/npc_team/alicanto.png +0 -0
- npcsh/npc_team/corca.npc +12 -0
- npcsh/npc_team/corca.png +0 -0
- npcsh/npc_team/corca_example.png +0 -0
- npcsh/npc_team/foreman.npc +7 -0
- npcsh/npc_team/frederic.npc +6 -0
- npcsh/npc_team/frederic4.png +0 -0
- npcsh/npc_team/guac.png +0 -0
- npcsh/npc_team/jinxs/code/python.jinx +11 -0
- npcsh/npc_team/jinxs/code/sh.jinx +34 -0
- npcsh/npc_team/jinxs/code/sql.jinx +16 -0
- npcsh/npc_team/jinxs/modes/alicanto.jinx +194 -0
- npcsh/npc_team/jinxs/modes/corca.jinx +249 -0
- npcsh/npc_team/jinxs/modes/guac.jinx +317 -0
- npcsh/npc_team/jinxs/modes/plonk.jinx +214 -0
- npcsh/npc_team/jinxs/modes/pti.jinx +170 -0
- npcsh/npc_team/jinxs/modes/spool.jinx +161 -0
- npcsh/npc_team/jinxs/modes/wander.jinx +186 -0
- npcsh/npc_team/jinxs/modes/yap.jinx +262 -0
- npcsh/npc_team/jinxs/npc_studio/npc-studio.jinx +77 -0
- npcsh/npc_team/jinxs/utils/agent.jinx +17 -0
- npcsh/npc_team/jinxs/utils/chat.jinx +44 -0
- npcsh/npc_team/jinxs/utils/cmd.jinx +44 -0
- npcsh/npc_team/jinxs/utils/compress.jinx +140 -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/utils/edit_file.jinx +94 -0
- npcsh/npc_team/jinxs/utils/load_file.jinx +35 -0
- npcsh/npc_team/jinxs/utils/ots.jinx +61 -0
- npcsh/npc_team/jinxs/utils/roll.jinx +68 -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 +26 -0
- npcsh/npc_team/jinxs/utils/sleep.jinx +116 -0
- npcsh/npc_team/jinxs/utils/trigger.jinx +61 -0
- npcsh/npc_team/jinxs/utils/usage.jinx +33 -0
- npcsh/npc_team/jinxs/utils/vixynt.jinx +144 -0
- npcsh/npc_team/kadiefa.npc +3 -0
- npcsh/npc_team/kadiefa.png +0 -0
- npcsh/npc_team/npcsh.ctx +18 -0
- npcsh/npc_team/npcsh_sibiji.png +0 -0
- npcsh/npc_team/plonk.npc +2 -0
- npcsh/npc_team/plonk.png +0 -0
- npcsh/npc_team/plonkjr.npc +2 -0
- npcsh/npc_team/plonkjr.png +0 -0
- npcsh/npc_team/sibiji.npc +3 -0
- npcsh/npc_team/sibiji.png +0 -0
- npcsh/npc_team/spool.png +0 -0
- npcsh/npc_team/yap.png +0 -0
- npcsh/npcsh.py +296 -112
- npcsh/parsing.py +118 -0
- npcsh/plonk.py +54 -0
- npcsh/pti.py +54 -0
- npcsh/routes.py +139 -0
- npcsh/spool.py +48 -0
- npcsh/ui.py +199 -0
- npcsh/wander.py +62 -0
- npcsh/yap.py +50 -0
- npcsh-1.1.13.data/data/npcsh/npc_team/agent.jinx +17 -0
- npcsh-1.1.13.data/data/npcsh/npc_team/alicanto.jinx +194 -0
- npcsh-1.1.13.data/data/npcsh/npc_team/alicanto.npc +2 -0
- npcsh-1.1.13.data/data/npcsh/npc_team/alicanto.png +0 -0
- npcsh-1.1.13.data/data/npcsh/npc_team/build.jinx +65 -0
- npcsh-1.1.13.data/data/npcsh/npc_team/chat.jinx +44 -0
- npcsh-1.1.13.data/data/npcsh/npc_team/cmd.jinx +44 -0
- npcsh-1.1.13.data/data/npcsh/npc_team/compile.jinx +50 -0
- npcsh-1.1.13.data/data/npcsh/npc_team/compress.jinx +140 -0
- npcsh-1.1.13.data/data/npcsh/npc_team/corca.jinx +249 -0
- npcsh-1.1.13.data/data/npcsh/npc_team/corca.npc +12 -0
- npcsh-1.1.13.data/data/npcsh/npc_team/corca.png +0 -0
- npcsh-1.1.13.data/data/npcsh/npc_team/corca_example.png +0 -0
- npcsh-1.1.13.data/data/npcsh/npc_team/edit_file.jinx +94 -0
- npcsh-1.1.13.data/data/npcsh/npc_team/foreman.npc +7 -0
- npcsh-1.1.13.data/data/npcsh/npc_team/frederic.npc +6 -0
- npcsh-1.1.13.data/data/npcsh/npc_team/frederic4.png +0 -0
- npcsh-1.1.13.data/data/npcsh/npc_team/guac.jinx +317 -0
- npcsh-1.1.13.data/data/npcsh/npc_team/guac.png +0 -0
- npcsh-1.1.13.data/data/npcsh/npc_team/help.jinx +52 -0
- npcsh-1.1.13.data/data/npcsh/npc_team/init.jinx +41 -0
- npcsh-1.1.13.data/data/npcsh/npc_team/jinxs.jinx +32 -0
- npcsh-1.1.13.data/data/npcsh/npc_team/kadiefa.npc +3 -0
- npcsh-1.1.13.data/data/npcsh/npc_team/kadiefa.png +0 -0
- npcsh-1.1.13.data/data/npcsh/npc_team/load_file.jinx +35 -0
- npcsh-1.1.13.data/data/npcsh/npc_team/npc-studio.jinx +77 -0
- npcsh-1.1.13.data/data/npcsh/npc_team/npcsh.ctx +18 -0
- npcsh-1.1.13.data/data/npcsh/npc_team/npcsh_sibiji.png +0 -0
- npcsh-1.1.13.data/data/npcsh/npc_team/ots.jinx +61 -0
- npcsh-1.1.13.data/data/npcsh/npc_team/plonk.jinx +214 -0
- npcsh-1.1.13.data/data/npcsh/npc_team/plonk.npc +2 -0
- npcsh-1.1.13.data/data/npcsh/npc_team/plonk.png +0 -0
- npcsh-1.1.13.data/data/npcsh/npc_team/plonkjr.npc +2 -0
- npcsh-1.1.13.data/data/npcsh/npc_team/plonkjr.png +0 -0
- npcsh-1.1.13.data/data/npcsh/npc_team/pti.jinx +170 -0
- npcsh-1.1.13.data/data/npcsh/npc_team/python.jinx +11 -0
- npcsh-1.1.13.data/data/npcsh/npc_team/roll.jinx +68 -0
- npcsh-1.1.13.data/data/npcsh/npc_team/sample.jinx +56 -0
- npcsh-1.1.13.data/data/npcsh/npc_team/search.jinx +130 -0
- npcsh-1.1.13.data/data/npcsh/npc_team/serve.jinx +26 -0
- npcsh-1.1.13.data/data/npcsh/npc_team/set.jinx +40 -0
- npcsh-1.1.13.data/data/npcsh/npc_team/sh.jinx +34 -0
- npcsh-1.1.13.data/data/npcsh/npc_team/sibiji.npc +3 -0
- npcsh-1.1.13.data/data/npcsh/npc_team/sibiji.png +0 -0
- npcsh-1.1.13.data/data/npcsh/npc_team/sleep.jinx +116 -0
- npcsh-1.1.13.data/data/npcsh/npc_team/spool.jinx +161 -0
- npcsh-1.1.13.data/data/npcsh/npc_team/spool.png +0 -0
- npcsh-1.1.13.data/data/npcsh/npc_team/sql.jinx +16 -0
- npcsh-1.1.13.data/data/npcsh/npc_team/trigger.jinx +61 -0
- npcsh-1.1.13.data/data/npcsh/npc_team/usage.jinx +33 -0
- npcsh-1.1.13.data/data/npcsh/npc_team/vixynt.jinx +144 -0
- npcsh-1.1.13.data/data/npcsh/npc_team/wander.jinx +186 -0
- npcsh-1.1.13.data/data/npcsh/npc_team/yap.jinx +262 -0
- npcsh-1.1.13.data/data/npcsh/npc_team/yap.png +0 -0
- npcsh-1.1.13.dist-info/METADATA +522 -0
- npcsh-1.1.13.dist-info/RECORD +135 -0
- {npcsh-0.1.2.dist-info → npcsh-1.1.13.dist-info}/WHEEL +1 -1
- npcsh-1.1.13.dist-info/entry_points.txt +9 -0
- {npcsh-0.1.2.dist-info → npcsh-1.1.13.dist-info/licenses}/LICENSE +1 -1
- npcsh/command_history.py +0 -81
- npcsh/helpers.py +0 -36
- npcsh/llm_funcs.py +0 -295
- npcsh/main.py +0 -5
- npcsh/modes.py +0 -343
- npcsh/npc_compiler.py +0 -124
- npcsh-0.1.2.dist-info/METADATA +0 -99
- npcsh-0.1.2.dist-info/RECORD +0 -14
- npcsh-0.1.2.dist-info/entry_points.txt +0 -2
- {npcsh-0.1.2.dist-info → npcsh-1.1.13.dist-info}/top_level.txt +0 -0
npcsh/modes.py
DELETED
|
@@ -1,343 +0,0 @@
|
|
|
1
|
-
# modes.py
|
|
2
|
-
import os
|
|
3
|
-
import subprocess
|
|
4
|
-
from .llm_funcs import (
|
|
5
|
-
get_ollama_conversation,
|
|
6
|
-
get_llm_response,
|
|
7
|
-
execute_data_operations,
|
|
8
|
-
)
|
|
9
|
-
import sqlite3
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
def enter_bash_mode():
|
|
13
|
-
print("Entering bash mode. Type '/bq' to exit bash mode.")
|
|
14
|
-
current_dir = os.getcwd()
|
|
15
|
-
bash_output = []
|
|
16
|
-
while True:
|
|
17
|
-
try:
|
|
18
|
-
bash_input = input(f"bash {current_dir}> ").strip()
|
|
19
|
-
if bash_input == "/bq":
|
|
20
|
-
print("Exiting bash mode.")
|
|
21
|
-
break
|
|
22
|
-
else:
|
|
23
|
-
try:
|
|
24
|
-
if bash_input.startswith("cd "):
|
|
25
|
-
new_dir = bash_input[3:].strip()
|
|
26
|
-
try:
|
|
27
|
-
os.chdir(os.path.expanduser(new_dir))
|
|
28
|
-
current_dir = os.getcwd()
|
|
29
|
-
bash_output.append(f"Changed directory to {current_dir}")
|
|
30
|
-
print(f"Changed directory to {current_dir}")
|
|
31
|
-
except FileNotFoundError:
|
|
32
|
-
bash_output.append(
|
|
33
|
-
f"bash: cd: {new_dir}: No such file or directory"
|
|
34
|
-
)
|
|
35
|
-
else:
|
|
36
|
-
result = subprocess.run(
|
|
37
|
-
bash_input,
|
|
38
|
-
shell=True,
|
|
39
|
-
text=True,
|
|
40
|
-
capture_output=True,
|
|
41
|
-
cwd=current_dir,
|
|
42
|
-
)
|
|
43
|
-
if result.stdout:
|
|
44
|
-
print(result.stdout.strip())
|
|
45
|
-
bash_output.append(result.stdout.strip())
|
|
46
|
-
if result.stderr:
|
|
47
|
-
bash_output.append(f"Error: {result.stderr.strip()}")
|
|
48
|
-
except Exception as e:
|
|
49
|
-
bash_output.append(f"Error executing bash command: {e}")
|
|
50
|
-
except (KeyboardInterrupt, EOFError):
|
|
51
|
-
print("\nExiting bash mode.")
|
|
52
|
-
break
|
|
53
|
-
os.chdir(current_dir)
|
|
54
|
-
return "\n".join(bash_output)
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
import whisper
|
|
58
|
-
import pyaudio
|
|
59
|
-
import wave
|
|
60
|
-
import numpy as np
|
|
61
|
-
import tempfile
|
|
62
|
-
import os
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
def record_audio(duration=5, sample_rate=16000):
|
|
66
|
-
p = pyaudio.PyAudio()
|
|
67
|
-
stream = p.open(
|
|
68
|
-
format=pyaudio.paInt16,
|
|
69
|
-
channels=1,
|
|
70
|
-
rate=sample_rate,
|
|
71
|
-
input=True,
|
|
72
|
-
frames_per_buffer=1024,
|
|
73
|
-
)
|
|
74
|
-
|
|
75
|
-
print("Recording...")
|
|
76
|
-
frames = []
|
|
77
|
-
for _ in range(0, int(sample_rate / 1024 * duration)):
|
|
78
|
-
data = stream.read(1024)
|
|
79
|
-
frames.append(data)
|
|
80
|
-
print("Recording finished.")
|
|
81
|
-
|
|
82
|
-
stream.stop_stream()
|
|
83
|
-
stream.close()
|
|
84
|
-
p.terminate()
|
|
85
|
-
|
|
86
|
-
return b"".join(frames)
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
def is_silent(audio_data, threshold=500):
|
|
90
|
-
"""Check if the audio chunk is silent."""
|
|
91
|
-
return np.max(np.abs(np.frombuffer(audio_data, dtype=np.int16))) < threshold
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
def record_audio(sample_rate=16000):
|
|
95
|
-
p = pyaudio.PyAudio()
|
|
96
|
-
stream = p.open(
|
|
97
|
-
format=pyaudio.paInt16,
|
|
98
|
-
channels=1,
|
|
99
|
-
rate=sample_rate,
|
|
100
|
-
input=True,
|
|
101
|
-
frames_per_buffer=1024,
|
|
102
|
-
)
|
|
103
|
-
|
|
104
|
-
print("Listening... (speak now)")
|
|
105
|
-
frames = []
|
|
106
|
-
silent_chunks = 0
|
|
107
|
-
has_speech = False
|
|
108
|
-
max_silent_chunks = int(sample_rate * 1.5 / 1024) # 1.5 seconds of silence
|
|
109
|
-
|
|
110
|
-
while True:
|
|
111
|
-
data = stream.read(1024)
|
|
112
|
-
frames.append(data)
|
|
113
|
-
|
|
114
|
-
if is_silent(data):
|
|
115
|
-
silent_chunks += 1
|
|
116
|
-
if has_speech and silent_chunks > max_silent_chunks:
|
|
117
|
-
break
|
|
118
|
-
else:
|
|
119
|
-
silent_chunks = 0
|
|
120
|
-
has_speech = True
|
|
121
|
-
|
|
122
|
-
if len(frames) % 10 == 0: # Print a dot every ~0.5 seconds
|
|
123
|
-
print(".", end="", flush=True)
|
|
124
|
-
|
|
125
|
-
print("\nProcessing...")
|
|
126
|
-
|
|
127
|
-
stream.stop_stream()
|
|
128
|
-
stream.close()
|
|
129
|
-
p.terminate()
|
|
130
|
-
|
|
131
|
-
return b"".join(frames)
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
def enter_whisper_mode(command_history):
|
|
135
|
-
model = whisper.load_model("base")
|
|
136
|
-
whisper_output = []
|
|
137
|
-
|
|
138
|
-
print(
|
|
139
|
-
"Entering whisper mode. Speak after seeing 'Listening...'. Say 'exit' or type '/wq' to quit."
|
|
140
|
-
)
|
|
141
|
-
|
|
142
|
-
while True:
|
|
143
|
-
try:
|
|
144
|
-
audio_data = record_audio()
|
|
145
|
-
|
|
146
|
-
with tempfile.NamedTemporaryFile(delete=False, suffix=".wav") as temp_audio:
|
|
147
|
-
wf = wave.open(temp_audio.name, "wb")
|
|
148
|
-
wf.setnchannels(1)
|
|
149
|
-
wf.setsampwidth(2)
|
|
150
|
-
wf.setframerate(16000)
|
|
151
|
-
wf.writeframes(audio_data)
|
|
152
|
-
wf.close()
|
|
153
|
-
|
|
154
|
-
result = model.transcribe(temp_audio.name)
|
|
155
|
-
text = result["text"].strip()
|
|
156
|
-
|
|
157
|
-
os.unlink(temp_audio.name)
|
|
158
|
-
|
|
159
|
-
print(f"You said: {text}")
|
|
160
|
-
whisper_output.append(f"User: {text}")
|
|
161
|
-
|
|
162
|
-
if text.lower() == "exit":
|
|
163
|
-
print("Exiting whisper mode.")
|
|
164
|
-
break
|
|
165
|
-
|
|
166
|
-
# Pass the recognized text to the LLM
|
|
167
|
-
llm_response = get_llm_response(text)
|
|
168
|
-
print(f"LLM response: {llm_response}")
|
|
169
|
-
whisper_output.append(f"LLM: {llm_response}")
|
|
170
|
-
|
|
171
|
-
# Add to command history
|
|
172
|
-
command_history.add(text, ["whisper"], llm_response, os.getcwd())
|
|
173
|
-
|
|
174
|
-
print("\nPress Enter to speak again, or type '/wq' to quit.")
|
|
175
|
-
user_input = input()
|
|
176
|
-
if user_input.lower() == "/wq":
|
|
177
|
-
print("Exiting whisper mode.")
|
|
178
|
-
break
|
|
179
|
-
|
|
180
|
-
except (KeyboardInterrupt, EOFError):
|
|
181
|
-
print("\nExiting whisper mode.")
|
|
182
|
-
break
|
|
183
|
-
|
|
184
|
-
return "\n".join(whisper_output)
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
def enter_notes_mode(command_history):
|
|
188
|
-
print("Entering notes mode. Type '/nq' to exit.")
|
|
189
|
-
|
|
190
|
-
while True:
|
|
191
|
-
note = input("Enter your note (or '/nq' to quit): ").strip()
|
|
192
|
-
|
|
193
|
-
if note.lower() == "/nq":
|
|
194
|
-
break
|
|
195
|
-
|
|
196
|
-
save_note(note, command_history)
|
|
197
|
-
|
|
198
|
-
print("Exiting notes mode.")
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
def save_note(note, command_history):
|
|
202
|
-
current_dir = os.getcwd()
|
|
203
|
-
readme_path = os.path.join(current_dir, "README.md")
|
|
204
|
-
|
|
205
|
-
with open(readme_path, "a") as f:
|
|
206
|
-
f.write(f"\n- {note}\n")
|
|
207
|
-
|
|
208
|
-
print("Note added to README.md")
|
|
209
|
-
command_history.add(f"/note {note}", ["note"], "", current_dir)
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
# Usage in your main script:
|
|
213
|
-
# enter_notes_mode()
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
def initial_table_print(cursor):
|
|
217
|
-
cursor.execute(
|
|
218
|
-
"SELECT name FROM sqlite_master WHERE type='table' AND name != 'command_history'"
|
|
219
|
-
)
|
|
220
|
-
tables = cursor.fetchall()
|
|
221
|
-
|
|
222
|
-
print("\nAvailable tables:")
|
|
223
|
-
for i, table in enumerate(tables, 1):
|
|
224
|
-
print(f"{i}. {table[0]}")
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
def enter_observation_mode(command_history):
|
|
228
|
-
conn = command_history.conn
|
|
229
|
-
cursor = command_history.cursor
|
|
230
|
-
|
|
231
|
-
print("Entering observation mode. Type '/dq' or to exit.")
|
|
232
|
-
n_times = 0
|
|
233
|
-
while True:
|
|
234
|
-
# Show available tables
|
|
235
|
-
if n_times == 0:
|
|
236
|
-
initial_table_print(cursor)
|
|
237
|
-
|
|
238
|
-
user_query = input(
|
|
239
|
-
"""
|
|
240
|
-
Enter a plain-text request or one using the dataframe manipulation framework of your choice.
|
|
241
|
-
You can also have the data NPC ingest data into your database by pointing it to the right files.
|
|
242
|
-
data>"""
|
|
243
|
-
)
|
|
244
|
-
|
|
245
|
-
else:
|
|
246
|
-
user_query = input(
|
|
247
|
-
"""
|
|
248
|
-
data>"""
|
|
249
|
-
)
|
|
250
|
-
print(user_query)
|
|
251
|
-
|
|
252
|
-
response = execute_data_operations(user_query, command_history)
|
|
253
|
-
|
|
254
|
-
answer_prompt = f"""
|
|
255
|
-
|
|
256
|
-
Here is an input from the user:
|
|
257
|
-
{user_query}
|
|
258
|
-
Here is some useful data relevant to the query:
|
|
259
|
-
{response}
|
|
260
|
-
|
|
261
|
-
Now write a query to write a final response to be delivered to the user.
|
|
262
|
-
|
|
263
|
-
Your answer must be in the format:
|
|
264
|
-
{{"response": "Your response here."}}
|
|
265
|
-
|
|
266
|
-
"""
|
|
267
|
-
final_response = get_llm_response(answer_prompt, format="json")
|
|
268
|
-
print(final_response["response"])
|
|
269
|
-
n_times += 1
|
|
270
|
-
|
|
271
|
-
conn.close()
|
|
272
|
-
print("Exiting observation mode.")
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
def enter_spool_mode(command_history, inherit_last=0, model="llama3.1"):
|
|
276
|
-
print("Entering spool mode. Type '/sq' to exit spool mode.")
|
|
277
|
-
spool_context = []
|
|
278
|
-
|
|
279
|
-
# Inherit last n messages if specified
|
|
280
|
-
if inherit_last > 0:
|
|
281
|
-
last_commands = command_history.get_all(limit=inherit_last)
|
|
282
|
-
for cmd in reversed(last_commands):
|
|
283
|
-
spool_context.append({"role": "user", "content": cmd[2]}) # command
|
|
284
|
-
spool_context.append({"role": "assistant", "content": cmd[4]}) # output
|
|
285
|
-
|
|
286
|
-
while True:
|
|
287
|
-
try:
|
|
288
|
-
user_input = input("spool> ").strip()
|
|
289
|
-
if user_input.lower() == "/sq":
|
|
290
|
-
print("Exiting spool mode.")
|
|
291
|
-
break
|
|
292
|
-
|
|
293
|
-
# Add user input to spool context
|
|
294
|
-
spool_context.append({"role": "user", "content": user_input})
|
|
295
|
-
|
|
296
|
-
# Process the spool context with LLM
|
|
297
|
-
spool_context = get_ollama_conversation(spool_context, model=model)
|
|
298
|
-
|
|
299
|
-
command_history.add(
|
|
300
|
-
user_input, ["spool"], spool_context[-1]["content"], os.getcwd()
|
|
301
|
-
)
|
|
302
|
-
print(spool_context[-1]["content"])
|
|
303
|
-
except (KeyboardInterrupt, EOFError):
|
|
304
|
-
print("\nExiting spool mode.")
|
|
305
|
-
break
|
|
306
|
-
|
|
307
|
-
return "\n".join(
|
|
308
|
-
[msg["content"] for msg in spool_context if msg["role"] == "assistant"]
|
|
309
|
-
)
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
def create_new_table(cursor, conn):
|
|
313
|
-
table_name = input("Enter new table name: ").strip()
|
|
314
|
-
columns = input("Enter column names separated by commas: ").strip()
|
|
315
|
-
|
|
316
|
-
create_query = (
|
|
317
|
-
f"CREATE TABLE {table_name} (id INTEGER PRIMARY KEY AUTOINCREMENT, {columns})"
|
|
318
|
-
)
|
|
319
|
-
cursor.execute(create_query)
|
|
320
|
-
conn.commit()
|
|
321
|
-
print(f"Table '{table_name}' created successfully.")
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
def delete_table(cursor, conn):
|
|
325
|
-
table_name = input("Enter table name to delete: ").strip()
|
|
326
|
-
cursor.execute(f"DROP TABLE IF EXISTS {table_name}")
|
|
327
|
-
conn.commit()
|
|
328
|
-
print(f"Table '{table_name}' deleted successfully.")
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
def add_observation(cursor, conn, table_name):
|
|
332
|
-
cursor.execute(f"PRAGMA table_info({table_name})")
|
|
333
|
-
columns = [column[1] for column in cursor.fetchall() if column[1] != "id"]
|
|
334
|
-
|
|
335
|
-
values = []
|
|
336
|
-
for column in columns:
|
|
337
|
-
value = input(f"Enter value for {column}: ").strip()
|
|
338
|
-
values.append(value)
|
|
339
|
-
|
|
340
|
-
insert_query = f"INSERT INTO {table_name} ({','.join(columns)}) VALUES ({','.join(['?' for _ in columns])})"
|
|
341
|
-
cursor.execute(insert_query, values)
|
|
342
|
-
conn.commit()
|
|
343
|
-
print("Observation added successfully.")
|
npcsh/npc_compiler.py
DELETED
|
@@ -1,124 +0,0 @@
|
|
|
1
|
-
import os
|
|
2
|
-
import subprocess
|
|
3
|
-
import sqlite3
|
|
4
|
-
import yaml
|
|
5
|
-
from jinja2 import Environment, FileSystemLoader, Template
|
|
6
|
-
|
|
7
|
-
import os
|
|
8
|
-
import yaml
|
|
9
|
-
from jinja2 import Environment, FileSystemLoader, TemplateError, Template, Undefined
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
class SilentUndefined(Undefined):
|
|
13
|
-
def _fail_with_undefined_error(self, *args, **kwargs):
|
|
14
|
-
return ""
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
class NPCCompiler:
|
|
18
|
-
def __init__(self, npc_directory):
|
|
19
|
-
self.npc_directory = npc_directory
|
|
20
|
-
self.jinja_env = Environment(
|
|
21
|
-
loader=FileSystemLoader(self.npc_directory), undefined=SilentUndefined
|
|
22
|
-
)
|
|
23
|
-
self.npc_cache = {}
|
|
24
|
-
self.resolved_npcs = {}
|
|
25
|
-
|
|
26
|
-
def compile(self, npc_file: str):
|
|
27
|
-
if not npc_file.endswith(".npc"):
|
|
28
|
-
raise ValueError("File must have .npc extension")
|
|
29
|
-
|
|
30
|
-
try:
|
|
31
|
-
# First pass: parse all NPC files without resolving Jinja templates
|
|
32
|
-
self.parse_all_npcs()
|
|
33
|
-
|
|
34
|
-
# Second pass: resolve Jinja templates and merge inherited properties
|
|
35
|
-
self.resolve_all_npcs()
|
|
36
|
-
|
|
37
|
-
# Final pass: resolve any remaining references
|
|
38
|
-
parsed_content = self.finalize_npc_profile(npc_file)
|
|
39
|
-
return parsed_content
|
|
40
|
-
except Exception as e:
|
|
41
|
-
raise
|
|
42
|
-
|
|
43
|
-
def parse_all_npcs(self):
|
|
44
|
-
for filename in os.listdir(self.npc_directory):
|
|
45
|
-
if filename.endswith(".npc"):
|
|
46
|
-
self.parse_npc_file(filename)
|
|
47
|
-
|
|
48
|
-
def parse_npc_file(self, npc_file: str):
|
|
49
|
-
if npc_file in self.npc_cache:
|
|
50
|
-
return self.npc_cache[npc_file]
|
|
51
|
-
|
|
52
|
-
try:
|
|
53
|
-
with open(os.path.join(self.npc_directory, npc_file), "r") as f:
|
|
54
|
-
npc_content = f.read()
|
|
55
|
-
|
|
56
|
-
# Parse YAML without resolving Jinja templates
|
|
57
|
-
profile = yaml.safe_load(npc_content)
|
|
58
|
-
self.npc_cache[npc_file] = profile
|
|
59
|
-
|
|
60
|
-
return profile
|
|
61
|
-
except yaml.YAMLError as e:
|
|
62
|
-
raise ValueError(f"Invalid YAML in NPC profile {npc_file}: {str(e)}")
|
|
63
|
-
|
|
64
|
-
def resolve_all_npcs(self):
|
|
65
|
-
for npc_file in self.npc_cache:
|
|
66
|
-
self.resolve_npc_profile(npc_file)
|
|
67
|
-
|
|
68
|
-
def resolve_npc_profile(self, npc_file: str):
|
|
69
|
-
if npc_file in self.resolved_npcs:
|
|
70
|
-
return self.resolved_npcs[npc_file]
|
|
71
|
-
|
|
72
|
-
profile = self.npc_cache[npc_file].copy()
|
|
73
|
-
|
|
74
|
-
# Resolve Jinja templates
|
|
75
|
-
for key, value in profile.items():
|
|
76
|
-
if isinstance(value, str):
|
|
77
|
-
template = self.jinja_env.from_string(value)
|
|
78
|
-
profile[key] = template.render(self.npc_cache)
|
|
79
|
-
|
|
80
|
-
# Handle inheritance
|
|
81
|
-
if "inherits_from" in profile:
|
|
82
|
-
parent_profile = self.resolve_npc_profile(profile["inherits_from"] + ".npc")
|
|
83
|
-
profile = self.merge_profiles(parent_profile, profile)
|
|
84
|
-
|
|
85
|
-
self.resolved_npcs[npc_file] = profile
|
|
86
|
-
return profile
|
|
87
|
-
|
|
88
|
-
def finalize_npc_profile(self, npc_file: str):
|
|
89
|
-
profile = self.resolved_npcs[npc_file].copy()
|
|
90
|
-
|
|
91
|
-
# Resolve any remaining references
|
|
92
|
-
for key, value in profile.items():
|
|
93
|
-
if isinstance(value, str):
|
|
94
|
-
template = self.jinja_env.from_string(value)
|
|
95
|
-
profile[key] = template.render(self.resolved_npcs)
|
|
96
|
-
|
|
97
|
-
required_keys = [
|
|
98
|
-
"name",
|
|
99
|
-
"primary_directive",
|
|
100
|
-
"suggested_tools_to_use",
|
|
101
|
-
"restrictions",
|
|
102
|
-
"model",
|
|
103
|
-
]
|
|
104
|
-
for key in required_keys:
|
|
105
|
-
if key not in profile:
|
|
106
|
-
raise ValueError(f"Missing required key in NPC profile: {key}")
|
|
107
|
-
|
|
108
|
-
return profile
|
|
109
|
-
|
|
110
|
-
def merge_profiles(self, parent, child):
|
|
111
|
-
merged = parent.copy()
|
|
112
|
-
for key, value in child.items():
|
|
113
|
-
if isinstance(value, list) and key in merged:
|
|
114
|
-
merged[key] = merged[key] + value
|
|
115
|
-
elif isinstance(value, dict) and key in merged:
|
|
116
|
-
merged[key] = self.merge_profiles(merged[key], value)
|
|
117
|
-
else:
|
|
118
|
-
merged[key] = value
|
|
119
|
-
return merged
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
# Usage:
|
|
123
|
-
# compiler = NPCCompiler('/path/to/npc/directory')
|
|
124
|
-
# compiled_script = compiler.compile('your_npc_file.npc')
|
npcsh-0.1.2.dist-info/METADATA
DELETED
|
@@ -1,99 +0,0 @@
|
|
|
1
|
-
Metadata-Version: 2.1
|
|
2
|
-
Name: npcsh
|
|
3
|
-
Version: 0.1.2
|
|
4
|
-
Summary: A way to use npcsh
|
|
5
|
-
Home-page: https://github.com/cagostino/npcsh
|
|
6
|
-
Author: Christopher Agostino
|
|
7
|
-
Author-email: cjp.agostino@example.com
|
|
8
|
-
Classifier: Programming Language :: Python :: 3
|
|
9
|
-
Classifier: License :: OSI Approved :: MIT License
|
|
10
|
-
Requires-Python: >=3.10
|
|
11
|
-
Description-Content-Type: text/markdown
|
|
12
|
-
License-File: LICENSE
|
|
13
|
-
Requires-Dist: jinja2
|
|
14
|
-
Requires-Dist: pandas
|
|
15
|
-
Requires-Dist: ollama
|
|
16
|
-
Requires-Dist: requests
|
|
17
|
-
Requires-Dist: PyYAML
|
|
18
|
-
Requires-Dist: openai-whisper
|
|
19
|
-
Requires-Dist: pyaudio
|
|
20
|
-
|
|
21
|
-
# npcsh
|
|
22
|
-
|
|
23
|
-
Welcome to npcsh, the shell for interacting with NPCs (LLM-powered AI agents). npcsh is meant to be a drop-in replacement shell for any kind of bash/zsh/powershell and allows the user to directly operate their machine through the use of the LLM-powered shell.
|
|
24
|
-
|
|
25
|
-
Additionally, npcsh introduces a new paradigm of programming for LLMs: npcsh allows users to set up NPC profiles (a la npc_profile.npc) where a user sets the primary directive of the NPC, the tools they want the NPC to use, and other properties of the NPC. NPCs can interact with each other and their primary directives and properties make these relationships explicit through jinja references.
|
|
26
|
-
|
|
27
|
-
## Dependencies
|
|
28
|
-
- ollama
|
|
29
|
-
- python >3.10
|
|
30
|
-
|
|
31
|
-
The default model is currently phi3. The user can change the model by setting the environment variable `NPCSH_MODEL` to the desired model name and to change the provider by setting the environment variable `NPCSH_PROVIDER` to the desired provider name.
|
|
32
|
-
|
|
33
|
-
The provider must be one of ['ollama', 'openai', 'anthropic'] and the model must be one available from those providers.
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
## compilation
|
|
37
|
-
|
|
38
|
-
Each NPC can be compiled to accomplish their primary directive and then any issues faced will be recorded and associated with the NPC so that it can reference it later through vector search. In any of the modes where a user requests input from an NPC, the NPC will include RAG search results before carrying out the request.
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
## Base npcsh
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
In the base npcsh shell, inputs are processed by an LLM. The LLM first determines what kind of a request the user is making and decides which of the available tools or modes will best enable it to accomplish the request.
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
### spool mode
|
|
48
|
-
|
|
49
|
-
Spool mode allows the users to have threaded conversations in the shell, i.e. conversations where context is retained over the course of several turns.
|
|
50
|
-
Users can speak with specific NPCs in spool mode by doing ```/spool <npc_name>``` and can exit spool mode by doing ```/exit```.
|
|
51
|
-
|
|
52
|
-
## Built-in NPCs
|
|
53
|
-
Built-in NPCs are NPCs that should offer broad utility to the user and allow them to create more complicated NPCs. These built-in NPCs facilitate the carrying out of many common data processing tasks as well as the ability to run commands and to execute and test programs.
|
|
54
|
-
|
|
55
|
-
### Bash NPC
|
|
56
|
-
The bash NPC is an NPC focused on running bash commands and scripts. The bash NPC can be used to run bash commands and the user can converse with the bash NPC by doing ```/spool bash``` to interrogate it about the commands it has run and the output it has produced.
|
|
57
|
-
A user can enter bash mode by typing ```/bash``` and can exit bash mode by typing ```/bq```.
|
|
58
|
-
Use the Bash NPC in the profiles of other NPCs by referencing it like ```{{bash}}```.
|
|
59
|
-
|
|
60
|
-
### Command NPC
|
|
61
|
-
|
|
62
|
-
The LLM or specific NPC will take the user's request and try to write a command or a script to accomplish the task and then attempt to run it and to tweak it until it works or it's exceeded the number of retries (default=5).
|
|
63
|
-
|
|
64
|
-
Use the Command NPC by typing ```/cmd <command>```. Chat with the Command NPC in spool mode by typing ```/spool cmd```.
|
|
65
|
-
Use the Command NPC in the profiles of other NPCs by referencing it like ```{{cmd}}```.
|
|
66
|
-
|
|
67
|
-
### Data NPC
|
|
68
|
-
|
|
69
|
-
Users can create schemas for recording observations and for exploring and analyzing data.
|
|
70
|
-
|
|
71
|
-
The Data NPC will asily facilitate the recording of data for individuals in essentially any realm (e.g. recipe testing, one's own blood pressure or weight, books read, movies watched, daily mood, etc.) without needing to use a tangled web of applications to do so. Observations can be referenced by the generic npcsh LLM shell or by specific NPCs.
|
|
72
|
-
Use the Observation NPC by typing ```/data <observation>```.
|
|
73
|
-
Chat with the Observation NPC in spool mode by typing ```/spool obs```.
|
|
74
|
-
Use the Observation NPC in the profiles of other NPCs by referencing it like ```{{obs}}```. Exit by typing ```/dq```.
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
### Question NPC
|
|
78
|
-
|
|
79
|
-
The user can submit a 1-shot question to a general LLM or to a specific NPC.
|
|
80
|
-
Use it like
|
|
81
|
-
```/question <question> <npc_name>```
|
|
82
|
-
or
|
|
83
|
-
```/question <question>```
|
|
84
|
-
|
|
85
|
-
You can also chat with the Question NPC in spool mode by typing ```/spool question```.
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
### thought mode
|
|
90
|
-
|
|
91
|
-
This will be like a way to write out some general thoughts to get some 1-shot feedback from a general LLM or a specific NPC.
|
|
92
|
-
|
|
93
|
-
Use it like
|
|
94
|
-
```/thought <thought> <npc_name>```
|
|
95
|
-
or
|
|
96
|
-
```/thought <thought>```
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
You can also chat with the Thought NPC in spool mode by typing ```/spool thought```.
|
npcsh-0.1.2.dist-info/RECORD
DELETED
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
npcsh/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
2
|
-
npcsh/command_history.py,sha256=HG5uAyigKu4lmBAxdk32GQXVpK_Y9mEMykYJGpKskFw,2309
|
|
3
|
-
npcsh/helpers.py,sha256=jeQKzyq_YXE0wzkxY8QOby-3yUd_FTT-7q3OHTWbA-I,781
|
|
4
|
-
npcsh/llm_funcs.py,sha256=NeOD39cHjGun0mW-OJWWIRSO3jzK9uXWH6NiIhpkNv0,10023
|
|
5
|
-
npcsh/main.py,sha256=rpf_2ysx3cR3eHsrvZApprJ-3D3-OrWcJ15bM1bc97I,81
|
|
6
|
-
npcsh/modes.py,sha256=9flrHoZnFQlREYA71hs4ByjRf4z5zoc1FCPWZzPbWvM,10198
|
|
7
|
-
npcsh/npc_compiler.py,sha256=uD0mAtSDuZN4D-x9jjGi5ULBLYdiaarLdzE35PcS8lg,4076
|
|
8
|
-
npcsh/npcsh.py,sha256=irgQxTA-T1E5ZvDZ8fgV-f6kchYxJtQ2-TARtbTHSUU,4137
|
|
9
|
-
npcsh-0.1.2.dist-info/LICENSE,sha256=j0YPvce7Ng9e32zYOu0EmXjXeJ0Nwawd0RA3uSGGH4E,1070
|
|
10
|
-
npcsh-0.1.2.dist-info/METADATA,sha256=oQsuoR99HEvfjlybAkCzhqInprd-cdrCLbMBeOkkFCQ,4989
|
|
11
|
-
npcsh-0.1.2.dist-info/WHEEL,sha256=GV9aMThwP_4oNCtvEC2ec3qUYutgWeAzklro_0m4WJQ,91
|
|
12
|
-
npcsh-0.1.2.dist-info/entry_points.txt,sha256=_n86Qhl0Dl5RFsV4XJcRv3GZoW8TbPF46UZq-SZavEY,43
|
|
13
|
-
npcsh-0.1.2.dist-info/top_level.txt,sha256=kHSNgKMCkfjV95-DH0YSp1LLBi0HXdF3w57j7MQON3E,6
|
|
14
|
-
npcsh-0.1.2.dist-info/RECORD,,
|
|
File without changes
|