npcsh 1.1.12__py3-none-any.whl → 1.1.14__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 (99) hide show
  1. npcsh/_state.py +700 -377
  2. npcsh/alicanto.py +54 -1153
  3. npcsh/completion.py +206 -0
  4. npcsh/config.py +163 -0
  5. npcsh/corca.py +35 -1462
  6. npcsh/execution.py +185 -0
  7. npcsh/guac.py +31 -1986
  8. npcsh/npc_team/jinxs/code/sh.jinx +11 -15
  9. npcsh/npc_team/jinxs/modes/alicanto.jinx +186 -80
  10. npcsh/npc_team/jinxs/modes/corca.jinx +243 -22
  11. npcsh/npc_team/jinxs/modes/guac.jinx +313 -42
  12. npcsh/npc_team/jinxs/modes/plonk.jinx +209 -48
  13. npcsh/npc_team/jinxs/modes/pti.jinx +167 -25
  14. npcsh/npc_team/jinxs/modes/spool.jinx +158 -37
  15. npcsh/npc_team/jinxs/modes/wander.jinx +179 -74
  16. npcsh/npc_team/jinxs/modes/yap.jinx +258 -21
  17. npcsh/npc_team/jinxs/utils/chat.jinx +39 -12
  18. npcsh/npc_team/jinxs/utils/cmd.jinx +44 -0
  19. npcsh/npc_team/jinxs/utils/search.jinx +3 -3
  20. npcsh/npc_team/jinxs/utils/usage.jinx +33 -0
  21. npcsh/npcsh.py +76 -20
  22. npcsh/parsing.py +118 -0
  23. npcsh/plonk.py +41 -329
  24. npcsh/pti.py +41 -201
  25. npcsh/spool.py +34 -239
  26. npcsh/ui.py +199 -0
  27. npcsh/wander.py +54 -542
  28. npcsh/yap.py +38 -570
  29. npcsh-1.1.14.data/data/npcsh/npc_team/alicanto.jinx +194 -0
  30. npcsh-1.1.14.data/data/npcsh/npc_team/chat.jinx +44 -0
  31. npcsh-1.1.14.data/data/npcsh/npc_team/cmd.jinx +44 -0
  32. npcsh-1.1.14.data/data/npcsh/npc_team/corca.jinx +249 -0
  33. npcsh-1.1.14.data/data/npcsh/npc_team/guac.jinx +317 -0
  34. npcsh-1.1.14.data/data/npcsh/npc_team/plonk.jinx +214 -0
  35. npcsh-1.1.14.data/data/npcsh/npc_team/pti.jinx +170 -0
  36. {npcsh-1.1.12.data → npcsh-1.1.14.data}/data/npcsh/npc_team/search.jinx +3 -3
  37. npcsh-1.1.14.data/data/npcsh/npc_team/sh.jinx +34 -0
  38. npcsh-1.1.14.data/data/npcsh/npc_team/spool.jinx +161 -0
  39. npcsh-1.1.14.data/data/npcsh/npc_team/usage.jinx +33 -0
  40. npcsh-1.1.14.data/data/npcsh/npc_team/wander.jinx +186 -0
  41. npcsh-1.1.14.data/data/npcsh/npc_team/yap.jinx +262 -0
  42. {npcsh-1.1.12.dist-info → npcsh-1.1.14.dist-info}/METADATA +1 -1
  43. npcsh-1.1.14.dist-info/RECORD +135 -0
  44. npcsh-1.1.12.data/data/npcsh/npc_team/alicanto.jinx +0 -88
  45. npcsh-1.1.12.data/data/npcsh/npc_team/chat.jinx +0 -17
  46. npcsh-1.1.12.data/data/npcsh/npc_team/corca.jinx +0 -28
  47. npcsh-1.1.12.data/data/npcsh/npc_team/guac.jinx +0 -46
  48. npcsh-1.1.12.data/data/npcsh/npc_team/plonk.jinx +0 -53
  49. npcsh-1.1.12.data/data/npcsh/npc_team/pti.jinx +0 -28
  50. npcsh-1.1.12.data/data/npcsh/npc_team/sh.jinx +0 -38
  51. npcsh-1.1.12.data/data/npcsh/npc_team/spool.jinx +0 -40
  52. npcsh-1.1.12.data/data/npcsh/npc_team/wander.jinx +0 -81
  53. npcsh-1.1.12.data/data/npcsh/npc_team/yap.jinx +0 -25
  54. npcsh-1.1.12.dist-info/RECORD +0 -126
  55. {npcsh-1.1.12.data → npcsh-1.1.14.data}/data/npcsh/npc_team/agent.jinx +0 -0
  56. {npcsh-1.1.12.data → npcsh-1.1.14.data}/data/npcsh/npc_team/alicanto.npc +0 -0
  57. {npcsh-1.1.12.data → npcsh-1.1.14.data}/data/npcsh/npc_team/alicanto.png +0 -0
  58. {npcsh-1.1.12.data → npcsh-1.1.14.data}/data/npcsh/npc_team/build.jinx +0 -0
  59. {npcsh-1.1.12.data → npcsh-1.1.14.data}/data/npcsh/npc_team/compile.jinx +0 -0
  60. {npcsh-1.1.12.data → npcsh-1.1.14.data}/data/npcsh/npc_team/compress.jinx +0 -0
  61. {npcsh-1.1.12.data → npcsh-1.1.14.data}/data/npcsh/npc_team/corca.npc +0 -0
  62. {npcsh-1.1.12.data → npcsh-1.1.14.data}/data/npcsh/npc_team/corca.png +0 -0
  63. {npcsh-1.1.12.data → npcsh-1.1.14.data}/data/npcsh/npc_team/corca_example.png +0 -0
  64. {npcsh-1.1.12.data → npcsh-1.1.14.data}/data/npcsh/npc_team/edit_file.jinx +0 -0
  65. {npcsh-1.1.12.data → npcsh-1.1.14.data}/data/npcsh/npc_team/foreman.npc +0 -0
  66. {npcsh-1.1.12.data → npcsh-1.1.14.data}/data/npcsh/npc_team/frederic.npc +0 -0
  67. {npcsh-1.1.12.data → npcsh-1.1.14.data}/data/npcsh/npc_team/frederic4.png +0 -0
  68. {npcsh-1.1.12.data → npcsh-1.1.14.data}/data/npcsh/npc_team/guac.png +0 -0
  69. {npcsh-1.1.12.data → npcsh-1.1.14.data}/data/npcsh/npc_team/help.jinx +0 -0
  70. {npcsh-1.1.12.data → npcsh-1.1.14.data}/data/npcsh/npc_team/init.jinx +0 -0
  71. {npcsh-1.1.12.data → npcsh-1.1.14.data}/data/npcsh/npc_team/jinxs.jinx +0 -0
  72. {npcsh-1.1.12.data → npcsh-1.1.14.data}/data/npcsh/npc_team/kadiefa.npc +0 -0
  73. {npcsh-1.1.12.data → npcsh-1.1.14.data}/data/npcsh/npc_team/kadiefa.png +0 -0
  74. {npcsh-1.1.12.data → npcsh-1.1.14.data}/data/npcsh/npc_team/load_file.jinx +0 -0
  75. {npcsh-1.1.12.data → npcsh-1.1.14.data}/data/npcsh/npc_team/npc-studio.jinx +0 -0
  76. {npcsh-1.1.12.data → npcsh-1.1.14.data}/data/npcsh/npc_team/npcsh.ctx +0 -0
  77. {npcsh-1.1.12.data → npcsh-1.1.14.data}/data/npcsh/npc_team/npcsh_sibiji.png +0 -0
  78. {npcsh-1.1.12.data → npcsh-1.1.14.data}/data/npcsh/npc_team/ots.jinx +0 -0
  79. {npcsh-1.1.12.data → npcsh-1.1.14.data}/data/npcsh/npc_team/plonk.npc +0 -0
  80. {npcsh-1.1.12.data → npcsh-1.1.14.data}/data/npcsh/npc_team/plonk.png +0 -0
  81. {npcsh-1.1.12.data → npcsh-1.1.14.data}/data/npcsh/npc_team/plonkjr.npc +0 -0
  82. {npcsh-1.1.12.data → npcsh-1.1.14.data}/data/npcsh/npc_team/plonkjr.png +0 -0
  83. {npcsh-1.1.12.data → npcsh-1.1.14.data}/data/npcsh/npc_team/python.jinx +0 -0
  84. {npcsh-1.1.12.data → npcsh-1.1.14.data}/data/npcsh/npc_team/roll.jinx +0 -0
  85. {npcsh-1.1.12.data → npcsh-1.1.14.data}/data/npcsh/npc_team/sample.jinx +0 -0
  86. {npcsh-1.1.12.data → npcsh-1.1.14.data}/data/npcsh/npc_team/serve.jinx +0 -0
  87. {npcsh-1.1.12.data → npcsh-1.1.14.data}/data/npcsh/npc_team/set.jinx +0 -0
  88. {npcsh-1.1.12.data → npcsh-1.1.14.data}/data/npcsh/npc_team/sibiji.npc +0 -0
  89. {npcsh-1.1.12.data → npcsh-1.1.14.data}/data/npcsh/npc_team/sibiji.png +0 -0
  90. {npcsh-1.1.12.data → npcsh-1.1.14.data}/data/npcsh/npc_team/sleep.jinx +0 -0
  91. {npcsh-1.1.12.data → npcsh-1.1.14.data}/data/npcsh/npc_team/spool.png +0 -0
  92. {npcsh-1.1.12.data → npcsh-1.1.14.data}/data/npcsh/npc_team/sql.jinx +0 -0
  93. {npcsh-1.1.12.data → npcsh-1.1.14.data}/data/npcsh/npc_team/trigger.jinx +0 -0
  94. {npcsh-1.1.12.data → npcsh-1.1.14.data}/data/npcsh/npc_team/vixynt.jinx +0 -0
  95. {npcsh-1.1.12.data → npcsh-1.1.14.data}/data/npcsh/npc_team/yap.png +0 -0
  96. {npcsh-1.1.12.dist-info → npcsh-1.1.14.dist-info}/WHEEL +0 -0
  97. {npcsh-1.1.12.dist-info → npcsh-1.1.14.dist-info}/entry_points.txt +0 -0
  98. {npcsh-1.1.12.dist-info → npcsh-1.1.14.dist-info}/licenses/LICENSE +0 -0
  99. {npcsh-1.1.12.dist-info → npcsh-1.1.14.dist-info}/top_level.txt +0 -0
npcsh/yap.py CHANGED
@@ -1,582 +1,50 @@
1
+ """
2
+ yap - Voice chat mode CLI entry point
1
3
 
2
- try:
3
- from faster_whisper import WhisperModel
4
- from gtts import gTTS
5
- import torch
6
- import pyaudio
7
- import wave
8
- import queue
4
+ This is a thin wrapper that executes the yap.jinx through the jinx mechanism.
5
+ """
6
+ import argparse
7
+ import os
8
+ import sys
9
9
 
10
- from npcpy.data.audio import (
11
- cleanup_temp_files,
12
- FORMAT,
13
- CHANNELS,
14
- RATE,
15
- CHUNK,
16
- transcribe_recording,
17
- convert_mp3_to_wav,
18
- )
19
- import threading
20
- import tempfile
21
- import os
22
- import re
23
- import time
24
- import numpy as np
25
-
10
+ from npcsh._state import setup_shell
26
11
 
27
- except Exception as e:
28
- print(
29
- "Exception: "
30
- + str(e)
31
- + "\n"
32
- + "Could not load the whisper package. If you want to use tts/stt features, please run `pip install npcsh[audio]` and follow the instructions in the npcsh github readme to ensure your OS can handle the audio dependencies."
33
- )
34
- from npcpy.data.load import load_csv, load_pdf
35
- from npcsh._state import (
36
- NPCSH_CHAT_MODEL,
37
- NPCSH_CHAT_PROVIDER,
38
- NPCSH_DB_PATH,
39
- NPCSH_API_URL,
40
- NPCSH_STREAM_OUTPUT
41
- )
42
12
 
43
- from npcpy.npc_sysenv import (
44
- get_system_message,
45
- print_and_process_stream_with_markdown,
46
- render_markdown,
47
- )
48
- from sqlalchemy import create_engine
49
- from npcpy.llm_funcs import check_llm_command
50
- from npcpy.data.text import rag_search
51
- from npcpy.npc_compiler import (
52
- NPC, Team
53
- )
54
- from npcpy.memory.command_history import CommandHistory, save_conversation_message,start_new_conversation
55
- from typing import Dict, Any, List
56
- def enter_yap_mode(
57
- messages: list = None,
58
- model: str = None,
59
- provider: str = None ,
60
- npc = None,
61
- team = None,
62
- stream: bool = False,
63
- api_url: str = None,
64
- api_key: str=None,
65
- conversation_id = None,
66
- tts_model="kokoro",
67
- voice="af_heart",
68
- files: List[str] = None,
69
- rag_similarity_threshold: float = 0.3,
70
- **kwargs
71
- ) -> Dict[str, Any]:
72
- running = True
73
- is_recording = False
74
- recording_data = []
75
- buffer_data = []
76
- last_speech_time = 0
77
- vad_model, _ = torch.hub.load(
78
- repo_or_dir="snakers4/silero-vad",
79
- model="silero_vad",
80
- force_reload=False,
81
- onnx=False,
82
- verbose=False,
83
-
84
- )
85
- device = 'cpu'
86
- vad_model.to(device)
87
-
88
-
89
- print("Entering yap mode. Initializing...")
90
-
91
- concise_instruction = "Please provide brief responses of 1-2 sentences unless the user specifically asks for more detailed information. Keep responses clear and concise."
92
-
93
- provider = (
94
- NPCSH_CHAT_PROVIDER if npc is None else npc.provider or NPCSH_CHAT_PROVIDER
95
- )
96
- api_url = NPCSH_API_URL if npc is None else npc.api_url or NPCSH_API_URL
97
-
98
- print(f"\nUsing model: {model} with provider: {provider}")
99
-
100
- system_message = get_system_message(npc) if npc else "You are a helpful assistant."
101
-
102
-
103
- system_message = system_message + " " + concise_instruction
104
-
105
- if messages is None or len(messages) == 0:
106
- messages = [{"role": "system", "content": system_message}]
107
- elif messages is not None and messages[0]['role'] != 'system':
108
- messages.insert(0, {"role": "system", "content": system_message})
109
-
110
- kokoro_pipeline = None
111
- if tts_model == "kokoro":
112
- from kokoro import KPipeline
113
- import soundfile as sf
114
-
115
- kokoro_pipeline = KPipeline(lang_code="a")
116
- print("Kokoro TTS model initialized")
117
-
118
-
119
-
120
-
121
- pyaudio_instance = pyaudio.PyAudio()
122
- audio_stream = None
123
- transcription_queue = queue.Queue()
124
-
125
-
126
- is_speaking = threading.Event()
127
- is_speaking.clear()
128
-
129
- speech_queue = queue.Queue(maxsize=20)
130
- speech_thread_active = threading.Event()
131
- speech_thread_active.set()
132
-
133
- def speech_playback_thread():
134
- nonlocal running, audio_stream
135
-
136
- while running and speech_thread_active.is_set():
137
-
138
-
139
- print('.', end='', flush=True)
140
- if not speech_queue.empty():
141
- print('\n')
142
- text_to_speak = speech_queue.get(timeout=0.1)
143
-
144
-
145
- if text_to_speak.strip():
146
-
147
- is_speaking.set()
148
-
149
-
150
- current_audio_stream = audio_stream
151
- audio_stream = (
152
- None
153
- )
154
-
155
- if current_audio_stream and current_audio_stream.is_active():
156
- current_audio_stream.stop_stream()
157
- current_audio_stream.close()
158
-
159
- print(f"Speaking full response...")
160
- print(text_to_speak)
161
-
162
- generate_and_play_speech(text_to_speak)
163
-
164
-
165
- time.sleep(0.005 * len(text_to_speak))
166
- print(len(text_to_speak))
167
-
168
-
169
- is_speaking.clear()
170
- else:
171
- time.sleep(0.5)
172
-
173
-
174
-
175
-
176
-
177
- def safely_close_audio_stream(stream):
178
- """Safely close an audio stream with error handling"""
179
- if stream:
180
- try:
181
- if stream.is_active():
182
- stream.stop_stream()
183
- stream.close()
184
- except Exception as e:
185
- print(f"Error closing audio stream: {e}")
186
-
187
-
188
- speech_thread = threading.Thread(target=speech_playback_thread)
189
- speech_thread.daemon = True
190
- speech_thread.start()
191
-
192
- def generate_and_play_speech(text):
193
- try:
194
-
195
- unique_id = str(time.time()).replace(".", "")
196
- temp_dir = tempfile.gettempdir()
197
- wav_file = os.path.join(temp_dir, f"temp_{unique_id}.wav")
198
-
199
-
200
- if tts_model == "kokoro" and kokoro_pipeline:
201
-
202
- generator = kokoro_pipeline(text, voice=voice)
203
-
204
-
205
- for _, _, audio in generator:
206
-
207
- import soundfile as sf
208
-
209
- sf.write(wav_file, audio, 24000)
210
- break
211
- else:
212
-
213
- mp3_file = os.path.join(temp_dir, f"temp_{unique_id}.mp3")
214
- tts = gTTS(text=text, lang="en", slow=False)
215
- tts.save(mp3_file)
216
- convert_mp3_to_wav(mp3_file, wav_file)
217
-
218
-
219
- wf = wave.open(wav_file, "rb")
220
- p = pyaudio.PyAudio()
221
-
222
- stream = p.open(
223
- format=p.get_format_from_width(wf.getsampwidth()),
224
- channels=wf.getnchannels(),
225
- rate=wf.getframerate(),
226
- output=True,
227
- )
228
-
229
- data = wf.readframes(4096)
230
- while data and running:
231
- stream.write(data)
232
- data = wf.readframes(4096)
233
-
234
- stream.stop_stream()
235
- stream.close()
236
- p.terminate()
237
-
238
-
239
- try:
240
- if os.path.exists(wav_file):
241
- os.remove(wav_file)
242
- if tts_model == "gtts" and "mp3_file" in locals():
243
- if os.path.exists(mp3_file):
244
- os.remove(mp3_file)
245
- except Exception as e:
246
- print(f"Error removing temp file: {e}")
247
-
248
- except Exception as e:
249
- print(f"Error in TTS process: {e}")
250
-
251
-
252
- def speak_text(text):
253
- speech_queue.put(text)
254
-
255
- def process_input(user_input, messages):
256
-
257
- full_response = ""
258
-
259
-
260
- check = check_llm_command(
261
- user_input,
262
- npc=npc,
263
- team=team,
264
- messages=messages,
265
- model=model,
266
- provider=provider,
267
- stream=False,
268
- )
269
-
270
-
271
- assistant_reply = check["output"]
272
- messages = check['messages']
273
-
274
-
275
-
276
- if stream and not isinstance(assistant_reply,str) and not isinstance(assistant_reply, dict):
277
- assistant_reply = print_and_process_stream_with_markdown(assistant_reply, model, provider)
278
- elif isinstance(assistant_reply,dict):
279
-
280
- assistant_reply = assistant_reply.get('output')
281
- render_markdown(assistant_reply)
282
- full_response += assistant_reply
283
-
284
- print("\n")
285
-
286
-
287
- if full_response.strip():
288
- processed_text = process_text_for_tts(full_response)
289
- speak_text(processed_text)
290
-
291
-
292
- messages.append({"role": "assistant", "content": full_response})
293
- return messages
294
-
295
-
296
-
297
-
298
-
299
-
300
-
301
- def capture_audio():
302
- nonlocal is_recording, recording_data, buffer_data, last_speech_time, running, is_speaking
303
- nonlocal audio_stream, transcription_queue
304
-
305
-
306
- if is_speaking.is_set():
307
- return False
308
-
309
- try:
310
-
311
- if audio_stream is None and not is_speaking.is_set():
312
- audio_stream = pyaudio_instance.open(
313
- format=FORMAT,
314
- channels=CHANNELS,
315
- rate=RATE,
316
- input=True,
317
- frames_per_buffer=CHUNK,
318
- )
319
-
320
-
321
- timeout_counter = 0
322
- max_timeout = 100
323
-
324
- print("\nListening for speech...")
325
-
326
- while (
327
- running
328
- and audio_stream
329
- and audio_stream.is_active()
330
- and not is_speaking.is_set()
331
- and timeout_counter < max_timeout
332
- ):
333
- try:
334
-
335
- data = audio_stream.read(CHUNK, exception_on_overflow=False)
336
-
337
- if not data:
338
- timeout_counter += 1
339
- time.sleep(0.1)
340
- continue
341
-
342
-
343
- timeout_counter = 0
344
-
345
- audio_array = np.frombuffer(data, dtype=np.int16)
346
- if len(audio_array) == 0:
347
- continue
348
-
349
- audio_float = audio_array.astype(np.float32) / 32768.0
350
- tensor = torch.from_numpy(audio_float).to(device)
351
-
352
-
353
- speech_prob = vad_model(tensor, RATE).item()
354
- current_time = time.time()
355
-
356
- if speech_prob > 0.5:
357
- last_speech_time = current_time
358
- if not is_recording:
359
- is_recording = True
360
- print("\nSpeech detected, listening...")
361
- recording_data.extend(buffer_data)
362
- buffer_data = []
363
- recording_data.append(data)
364
- else:
365
- if is_recording:
366
- if (
367
- current_time - last_speech_time > 1
368
- ):
369
- is_recording = False
370
- print("Speech ended, transcribing...")
371
-
372
-
373
- safely_close_audio_stream(audio_stream)
374
- audio_stream = None
375
-
376
-
377
- transcription = transcribe_recording(recording_data)
378
- if transcription:
379
- transcription_queue.put(transcription)
380
- recording_data = []
381
- return True
382
- else:
383
- buffer_data.append(data)
384
- if len(buffer_data) > int(
385
- 0.65 * RATE / CHUNK
386
- ):
387
- buffer_data.pop(0)
388
-
389
-
390
- if is_speaking.is_set():
391
- safely_close_audio_stream(audio_stream)
392
- audio_stream = None
393
- return False
394
-
395
- except Exception as e:
396
- print(f"Error processing audio frame: {e}")
397
- time.sleep(0.1)
398
-
399
- except Exception as e:
400
- print(f"Error in audio capture: {e}")
401
-
402
-
403
- safely_close_audio_stream(audio_stream)
404
- audio_stream = None
405
-
406
- return False
407
-
408
- def process_text_for_tts(text):
409
-
410
- text = re.sub(r"[*<>{}()\[\]&%#@^_=+~]", "", text)
411
- text = text.strip()
412
-
413
- text = re.sub(r"(\w)\.(\w)\.", r"\1 \2 ", text)
414
- text = re.sub(r"([.!?])(\w)", r"\1 \2", text)
415
- return text
416
-
417
-
418
- speak_text("Entering yap mode. Please wait.")
419
-
420
- try:
421
- loaded_content = {}
422
- if not conversation_id:
423
- conversation_id = start_new_conversation()
424
- command_history = CommandHistory()
425
-
426
- if files:
427
- for file in files:
428
- extension = os.path.splitext(file)[1].lower()
429
- try:
430
- if extension == ".pdf":
431
- content = load_pdf(file)["texts"].iloc[0]
432
- elif extension == ".csv":
433
- content = load_csv(file)
434
- else:
435
- print(f"Unsupported file type: {file}")
436
- continue
437
- loaded_content[file] = content
438
- print(f"Loaded content from: {file}")
439
- except Exception as e:
440
- print(f"Error loading {file}: {str(e)}")
441
-
442
-
443
-
444
- while running:
445
- import select
446
- import sys
447
- if not is_speaking.is_set():
448
- print(
449
- "🎤🎤🎤🎤\n Speak or type your message (or 'exit' to quit): ",
450
- end="",
451
- flush=True,
452
- )
453
- rlist, _, _ = select.select([sys.stdin], [], [], 0.1)
454
- if rlist:
455
- user_input = sys.stdin.readline().strip()
456
- if user_input.lower() in ("exit", "quit", "goodbye"):
457
- print("\nExiting yap mode.")
458
- break
459
- if user_input:
460
- print(f"\nYou (typed): {user_input}")
461
-
462
- if loaded_content:
463
- context_content = ""
464
- for filename, content in loaded_content.items():
465
- retrieved_docs = rag_search(
466
- user_input,
467
- content,
468
- similarity_threshold=rag_similarity_threshold,
469
- )
470
- if retrieved_docs:
471
- context_content += (
472
- f"\n\nLoaded content from: {filename}\n{content}\n\n"
473
- )
474
- if len(context_content) > 0:
475
- user_input += f"""
476
- Here is the loaded content that may be relevant to your query:
477
- {context_content}
478
- Please reference it explicitly in your response and use it for answering.
479
- """
480
- message_id = save_conversation_message(
481
- command_history,
482
- conversation_id,
483
- "user",
484
- user_input,
485
- wd=os.getcwd(),
486
- model=model,
487
- provider=provider,
488
- npc=npc.name if npc else None,
489
- )
490
-
491
-
492
- messages= process_input(user_input, messages)
493
-
494
- message_id = save_conversation_message(
495
- command_history,
496
- conversation_id,
497
- "assistant",
498
- messages[-1]["content"],
499
- wd=os.getcwd(),
500
- model=model,
501
- provider=provider,
502
- npc=npc.name if npc else None,
503
- )
504
-
505
-
506
- continue
507
- if not is_speaking.is_set():
508
- print('capturing audio')
509
- got_speech = capture_audio()
510
-
511
-
512
- if got_speech:
513
- try:
514
- transcription = transcription_queue.get_nowait()
515
- print(f"\nYou (spoke): {transcription}")
516
- messages = process_input(transcription, messages)
517
- except queue.Empty:
518
- pass
519
- else:
520
-
521
- time.sleep(0.1)
522
-
523
- except KeyboardInterrupt:
524
- print("\nInterrupted by user.")
13
+ def main():
14
+ parser = argparse.ArgumentParser(description="yap - Voice chat mode")
15
+ parser.add_argument("--model", "-m", type=str, help="LLM model to use")
16
+ parser.add_argument("--provider", "-p", type=str, help="LLM provider to use")
17
+ parser.add_argument("--files", "-f", nargs="*", help="Files to load for RAG context")
18
+ parser.add_argument("--tts-model", type=str, default="kokoro", help="TTS model to use")
19
+ parser.add_argument("--voice", type=str, default="af_heart", help="Voice for TTS")
20
+ args = parser.parse_args()
525
21
 
526
- finally:
527
-
528
- running = False
529
- speech_thread_active.clear()
22
+ # Setup shell to get team and default NPC
23
+ command_history, team, default_npc = setup_shell()
530
24
 
531
-
532
- safely_close_audio_stream(audio_stream)
25
+ if not team or "yap" not in team.jinxs_dict:
26
+ print("Error: yap jinx not found. Ensure npc_team/jinxs/modes/yap.jinx exists.")
27
+ sys.exit(1)
533
28
 
534
- if pyaudio_instance:
535
- pyaudio_instance.terminate()
29
+ # Build context for jinx execution
30
+ context = {
31
+ "npc": default_npc,
32
+ "team": team,
33
+ "messages": [],
34
+ "model": args.model,
35
+ "provider": args.provider,
36
+ "files": ",".join(args.files) if args.files else None,
37
+ "tts_model": args.tts_model,
38
+ "voice": args.voice,
39
+ }
536
40
 
537
- print("\nExiting yap mode.")
538
- speak_text("Exiting yap mode. Goodbye!")
539
- time.sleep(1)
540
- cleanup_temp_files()
41
+ # Execute the jinx
42
+ yap_jinx = team.jinxs_dict["yap"]
43
+ result = yap_jinx.execute(context=context, npc=default_npc)
541
44
 
542
- return {"messages": messages, "output": "yap mode session ended."}
45
+ if isinstance(result, dict) and result.get("output"):
46
+ print(result["output"])
543
47
 
544
- def main():
545
-
546
- import argparse
547
- parser = argparse.ArgumentParser(description="Enter yap mode for chatting with an NPC")
548
- parser.add_argument("--model", default=NPCSH_CHAT_MODEL, help="Model to use")
549
- parser.add_argument("--provider", default=NPCSH_CHAT_PROVIDER, help="Provider to use")
550
- parser.add_argument("--files", nargs="*", help="Files to load into context")
551
- parser.add_argument("--stream", default="true", help="Use streaming mode")
552
- parser.add_argument("--npc", type=str, default=os.path.expanduser('~/.npcsh/npc_team/sibiji.npc'), help="Path to NPC file")
553
- args = parser.parse_args()
554
- npc_db_conn = create_engine(
555
- f"sqlite:///{NPCSH_DB_PATH}")
556
-
557
- sibiji = NPC(file=args.npc, db_conn=npc_db_conn)
558
-
559
- team = Team(team_path = '~/.npcsh/npc_team/', db_conn=npc_db_conn, forenpc= sibiji)
560
- if sibiji.model is None:
561
- sibiji.model = args.model
562
- model = args.model
563
- else:
564
- model = sibiji.model
565
- if sibiji.provider is None:
566
- sibiji.provider = args.provider
567
- provider = args.provider
568
- else:
569
- provider = sibiji.provider
570
-
571
- enter_yap_mode(
572
- messages=None,
573
- model= model,
574
- provider = provider,
575
- npc=sibiji,
576
- team = team,
577
- files=args.files,
578
- stream= args.stream.lower() == "true",
579
- )
580
48
 
581
49
  if __name__ == "__main__":
582
- main()
50
+ main()