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.
Files changed (143) hide show
  1. npcsh/_state.py +3508 -0
  2. npcsh/alicanto.py +65 -0
  3. npcsh/build.py +291 -0
  4. npcsh/completion.py +206 -0
  5. npcsh/config.py +163 -0
  6. npcsh/corca.py +50 -0
  7. npcsh/execution.py +185 -0
  8. npcsh/guac.py +46 -0
  9. npcsh/mcp_helpers.py +357 -0
  10. npcsh/mcp_server.py +299 -0
  11. npcsh/npc.py +323 -0
  12. npcsh/npc_team/alicanto.npc +2 -0
  13. npcsh/npc_team/alicanto.png +0 -0
  14. npcsh/npc_team/corca.npc +12 -0
  15. npcsh/npc_team/corca.png +0 -0
  16. npcsh/npc_team/corca_example.png +0 -0
  17. npcsh/npc_team/foreman.npc +7 -0
  18. npcsh/npc_team/frederic.npc +6 -0
  19. npcsh/npc_team/frederic4.png +0 -0
  20. npcsh/npc_team/guac.png +0 -0
  21. npcsh/npc_team/jinxs/code/python.jinx +11 -0
  22. npcsh/npc_team/jinxs/code/sh.jinx +34 -0
  23. npcsh/npc_team/jinxs/code/sql.jinx +16 -0
  24. npcsh/npc_team/jinxs/modes/alicanto.jinx +194 -0
  25. npcsh/npc_team/jinxs/modes/corca.jinx +249 -0
  26. npcsh/npc_team/jinxs/modes/guac.jinx +317 -0
  27. npcsh/npc_team/jinxs/modes/plonk.jinx +214 -0
  28. npcsh/npc_team/jinxs/modes/pti.jinx +170 -0
  29. npcsh/npc_team/jinxs/modes/spool.jinx +161 -0
  30. npcsh/npc_team/jinxs/modes/wander.jinx +186 -0
  31. npcsh/npc_team/jinxs/modes/yap.jinx +262 -0
  32. npcsh/npc_team/jinxs/npc_studio/npc-studio.jinx +77 -0
  33. npcsh/npc_team/jinxs/utils/agent.jinx +17 -0
  34. npcsh/npc_team/jinxs/utils/chat.jinx +44 -0
  35. npcsh/npc_team/jinxs/utils/cmd.jinx +44 -0
  36. npcsh/npc_team/jinxs/utils/compress.jinx +140 -0
  37. npcsh/npc_team/jinxs/utils/core/build.jinx +65 -0
  38. npcsh/npc_team/jinxs/utils/core/compile.jinx +50 -0
  39. npcsh/npc_team/jinxs/utils/core/help.jinx +52 -0
  40. npcsh/npc_team/jinxs/utils/core/init.jinx +41 -0
  41. npcsh/npc_team/jinxs/utils/core/jinxs.jinx +32 -0
  42. npcsh/npc_team/jinxs/utils/core/set.jinx +40 -0
  43. npcsh/npc_team/jinxs/utils/edit_file.jinx +94 -0
  44. npcsh/npc_team/jinxs/utils/load_file.jinx +35 -0
  45. npcsh/npc_team/jinxs/utils/ots.jinx +61 -0
  46. npcsh/npc_team/jinxs/utils/roll.jinx +68 -0
  47. npcsh/npc_team/jinxs/utils/sample.jinx +56 -0
  48. npcsh/npc_team/jinxs/utils/search.jinx +130 -0
  49. npcsh/npc_team/jinxs/utils/serve.jinx +26 -0
  50. npcsh/npc_team/jinxs/utils/sleep.jinx +116 -0
  51. npcsh/npc_team/jinxs/utils/trigger.jinx +61 -0
  52. npcsh/npc_team/jinxs/utils/usage.jinx +33 -0
  53. npcsh/npc_team/jinxs/utils/vixynt.jinx +144 -0
  54. npcsh/npc_team/kadiefa.npc +3 -0
  55. npcsh/npc_team/kadiefa.png +0 -0
  56. npcsh/npc_team/npcsh.ctx +18 -0
  57. npcsh/npc_team/npcsh_sibiji.png +0 -0
  58. npcsh/npc_team/plonk.npc +2 -0
  59. npcsh/npc_team/plonk.png +0 -0
  60. npcsh/npc_team/plonkjr.npc +2 -0
  61. npcsh/npc_team/plonkjr.png +0 -0
  62. npcsh/npc_team/sibiji.npc +3 -0
  63. npcsh/npc_team/sibiji.png +0 -0
  64. npcsh/npc_team/spool.png +0 -0
  65. npcsh/npc_team/yap.png +0 -0
  66. npcsh/npcsh.py +296 -112
  67. npcsh/parsing.py +118 -0
  68. npcsh/plonk.py +54 -0
  69. npcsh/pti.py +54 -0
  70. npcsh/routes.py +139 -0
  71. npcsh/spool.py +48 -0
  72. npcsh/ui.py +199 -0
  73. npcsh/wander.py +62 -0
  74. npcsh/yap.py +50 -0
  75. npcsh-1.1.13.data/data/npcsh/npc_team/agent.jinx +17 -0
  76. npcsh-1.1.13.data/data/npcsh/npc_team/alicanto.jinx +194 -0
  77. npcsh-1.1.13.data/data/npcsh/npc_team/alicanto.npc +2 -0
  78. npcsh-1.1.13.data/data/npcsh/npc_team/alicanto.png +0 -0
  79. npcsh-1.1.13.data/data/npcsh/npc_team/build.jinx +65 -0
  80. npcsh-1.1.13.data/data/npcsh/npc_team/chat.jinx +44 -0
  81. npcsh-1.1.13.data/data/npcsh/npc_team/cmd.jinx +44 -0
  82. npcsh-1.1.13.data/data/npcsh/npc_team/compile.jinx +50 -0
  83. npcsh-1.1.13.data/data/npcsh/npc_team/compress.jinx +140 -0
  84. npcsh-1.1.13.data/data/npcsh/npc_team/corca.jinx +249 -0
  85. npcsh-1.1.13.data/data/npcsh/npc_team/corca.npc +12 -0
  86. npcsh-1.1.13.data/data/npcsh/npc_team/corca.png +0 -0
  87. npcsh-1.1.13.data/data/npcsh/npc_team/corca_example.png +0 -0
  88. npcsh-1.1.13.data/data/npcsh/npc_team/edit_file.jinx +94 -0
  89. npcsh-1.1.13.data/data/npcsh/npc_team/foreman.npc +7 -0
  90. npcsh-1.1.13.data/data/npcsh/npc_team/frederic.npc +6 -0
  91. npcsh-1.1.13.data/data/npcsh/npc_team/frederic4.png +0 -0
  92. npcsh-1.1.13.data/data/npcsh/npc_team/guac.jinx +317 -0
  93. npcsh-1.1.13.data/data/npcsh/npc_team/guac.png +0 -0
  94. npcsh-1.1.13.data/data/npcsh/npc_team/help.jinx +52 -0
  95. npcsh-1.1.13.data/data/npcsh/npc_team/init.jinx +41 -0
  96. npcsh-1.1.13.data/data/npcsh/npc_team/jinxs.jinx +32 -0
  97. npcsh-1.1.13.data/data/npcsh/npc_team/kadiefa.npc +3 -0
  98. npcsh-1.1.13.data/data/npcsh/npc_team/kadiefa.png +0 -0
  99. npcsh-1.1.13.data/data/npcsh/npc_team/load_file.jinx +35 -0
  100. npcsh-1.1.13.data/data/npcsh/npc_team/npc-studio.jinx +77 -0
  101. npcsh-1.1.13.data/data/npcsh/npc_team/npcsh.ctx +18 -0
  102. npcsh-1.1.13.data/data/npcsh/npc_team/npcsh_sibiji.png +0 -0
  103. npcsh-1.1.13.data/data/npcsh/npc_team/ots.jinx +61 -0
  104. npcsh-1.1.13.data/data/npcsh/npc_team/plonk.jinx +214 -0
  105. npcsh-1.1.13.data/data/npcsh/npc_team/plonk.npc +2 -0
  106. npcsh-1.1.13.data/data/npcsh/npc_team/plonk.png +0 -0
  107. npcsh-1.1.13.data/data/npcsh/npc_team/plonkjr.npc +2 -0
  108. npcsh-1.1.13.data/data/npcsh/npc_team/plonkjr.png +0 -0
  109. npcsh-1.1.13.data/data/npcsh/npc_team/pti.jinx +170 -0
  110. npcsh-1.1.13.data/data/npcsh/npc_team/python.jinx +11 -0
  111. npcsh-1.1.13.data/data/npcsh/npc_team/roll.jinx +68 -0
  112. npcsh-1.1.13.data/data/npcsh/npc_team/sample.jinx +56 -0
  113. npcsh-1.1.13.data/data/npcsh/npc_team/search.jinx +130 -0
  114. npcsh-1.1.13.data/data/npcsh/npc_team/serve.jinx +26 -0
  115. npcsh-1.1.13.data/data/npcsh/npc_team/set.jinx +40 -0
  116. npcsh-1.1.13.data/data/npcsh/npc_team/sh.jinx +34 -0
  117. npcsh-1.1.13.data/data/npcsh/npc_team/sibiji.npc +3 -0
  118. npcsh-1.1.13.data/data/npcsh/npc_team/sibiji.png +0 -0
  119. npcsh-1.1.13.data/data/npcsh/npc_team/sleep.jinx +116 -0
  120. npcsh-1.1.13.data/data/npcsh/npc_team/spool.jinx +161 -0
  121. npcsh-1.1.13.data/data/npcsh/npc_team/spool.png +0 -0
  122. npcsh-1.1.13.data/data/npcsh/npc_team/sql.jinx +16 -0
  123. npcsh-1.1.13.data/data/npcsh/npc_team/trigger.jinx +61 -0
  124. npcsh-1.1.13.data/data/npcsh/npc_team/usage.jinx +33 -0
  125. npcsh-1.1.13.data/data/npcsh/npc_team/vixynt.jinx +144 -0
  126. npcsh-1.1.13.data/data/npcsh/npc_team/wander.jinx +186 -0
  127. npcsh-1.1.13.data/data/npcsh/npc_team/yap.jinx +262 -0
  128. npcsh-1.1.13.data/data/npcsh/npc_team/yap.png +0 -0
  129. npcsh-1.1.13.dist-info/METADATA +522 -0
  130. npcsh-1.1.13.dist-info/RECORD +135 -0
  131. {npcsh-0.1.2.dist-info → npcsh-1.1.13.dist-info}/WHEEL +1 -1
  132. npcsh-1.1.13.dist-info/entry_points.txt +9 -0
  133. {npcsh-0.1.2.dist-info → npcsh-1.1.13.dist-info/licenses}/LICENSE +1 -1
  134. npcsh/command_history.py +0 -81
  135. npcsh/helpers.py +0 -36
  136. npcsh/llm_funcs.py +0 -295
  137. npcsh/main.py +0 -5
  138. npcsh/modes.py +0 -343
  139. npcsh/npc_compiler.py +0 -124
  140. npcsh-0.1.2.dist-info/METADATA +0 -99
  141. npcsh-0.1.2.dist-info/RECORD +0 -14
  142. npcsh-0.1.2.dist-info/entry_points.txt +0 -2
  143. {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')
@@ -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```.
@@ -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,,
@@ -1,2 +0,0 @@
1
- [console_scripts]
2
- npcsh = npcsh.npcsh:main