npcsh 1.0.13__py3-none-any.whl → 1.0.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.
npcsh/_state.py CHANGED
@@ -1,45 +1,35 @@
1
1
 
2
2
  from colorama import Fore, Back, Style
3
-
4
-
5
- from datetime import datetime
6
- from dotenv import load_dotenv
7
-
8
- import re
3
+ from dataclasses import dataclass, field
4
+ import filecmp
9
5
  import os
10
- from termcolor import colored
11
-
12
-
13
-
14
- from typing import Dict, List, Any
15
- import subprocess
16
- import termios
17
- import tty
6
+ import platform
18
7
  import pty
8
+ import re
19
9
  import select
10
+ import shutil
20
11
  import signal
21
- import time
22
- import os
23
- import re
24
12
  import sqlite3
25
- from datetime import datetime
13
+ import subprocess
14
+ import sys
15
+ from termcolor import colored
16
+ import termios
17
+ import time
18
+ from typing import Dict, List, Any, Tuple, Union, Optional
19
+ import tty
26
20
  import logging
27
21
  import textwrap
28
22
  from termcolor import colored
29
- import sys
30
- import platform
31
-
23
+ from npcpy.memory.command_history import (
24
+ start_new_conversation,
25
+ )
26
+ from npcpy.npc_compiler import NPC, Team
32
27
 
33
28
  def get_npc_path(npc_name: str, db_path: str) -> str:
34
- # First, check in project npc_team directory
35
29
  project_npc_team_dir = os.path.abspath("./npc_team")
36
30
  project_npc_path = os.path.join(project_npc_team_dir, f"{npc_name}.npc")
37
-
38
- # Then, check in global npc_team directory
39
31
  user_npc_team_dir = os.path.expanduser("~/.npcsh/npc_team")
40
32
  global_npc_path = os.path.join(user_npc_team_dir, f"{npc_name}.npc")
41
-
42
- # Check database for compiled NPCs
43
33
  try:
44
34
  with sqlite3.connect(db_path) as conn:
45
35
  cursor = conn.cursor()
@@ -105,9 +95,6 @@ def initialize_base_npcs_if_needed(db_path: str) -> None:
105
95
  package_dir = os.path.dirname(__file__)
106
96
  package_npc_team_dir = os.path.join(package_dir, "npc_team")
107
97
 
108
-
109
-
110
- # User's global npc_team directory
111
98
  user_npc_team_dir = os.path.expanduser("~/.npcsh/npc_team")
112
99
 
113
100
  user_jinxs_dir = os.path.join(user_npc_team_dir, "jinxs")
@@ -115,7 +102,7 @@ def initialize_base_npcs_if_needed(db_path: str) -> None:
115
102
  os.makedirs(user_npc_team_dir, exist_ok=True)
116
103
  os.makedirs(user_jinxs_dir, exist_ok=True)
117
104
  os.makedirs(user_templates_dir, exist_ok=True)
118
- # Copy NPCs from package to user directory
105
+
119
106
  for filename in os.listdir(package_npc_team_dir):
120
107
  if filename.endswith(".npc"):
121
108
  source_path = os.path.join(package_npc_team_dir, filename)
@@ -243,11 +230,11 @@ def ensure_npcshrc_exists() -> str:
243
230
  npcshrc.write("# NPCSH Configuration File\n")
244
231
  npcshrc.write("export NPCSH_INITIALIZED=0\n")
245
232
  npcshrc.write("export NPCSH_DEFAULT_MODE='agent'\n")
233
+ npcshrc.write("export NPCSH_BUILD_KG=1")
246
234
  npcshrc.write("export NPCSH_CHAT_PROVIDER='ollama'\n")
247
- npcshrc.write("export NPCSH_CHAT_MODEL='llama3.2'\n")
235
+ npcshrc.write("export NPCSH_CHAT_MODEL='gemma3:4b'\n")
248
236
  npcshrc.write("export NPCSH_REASONING_PROVIDER='ollama'\n")
249
237
  npcshrc.write("export NPCSH_REASONING_MODEL='deepseek-r1'\n")
250
-
251
238
  npcshrc.write("export NPCSH_EMBEDDING_PROVIDER='ollama'\n")
252
239
  npcshrc.write("export NPCSH_EMBEDDING_MODEL='nomic-embed-text'\n")
253
240
  npcshrc.write("export NPCSH_VISION_PROVIDER='ollama'\n")
@@ -1059,12 +1046,13 @@ NPCSH_REASONING_PROVIDER = os.environ.get("NPCSH_REASONING_PROVIDER", "ollama")
1059
1046
  NPCSH_STREAM_OUTPUT = eval(os.environ.get("NPCSH_STREAM_OUTPUT", "0")) == 1
1060
1047
  NPCSH_API_URL = os.environ.get("NPCSH_API_URL", None)
1061
1048
  NPCSH_SEARCH_PROVIDER = os.environ.get("NPCSH_SEARCH_PROVIDER", "duckduckgo")
1062
-
1049
+ NPCSH_BUILD_KG = os.environ.get("NPCSH_BUILD_KG") == "1"
1063
1050
  READLINE_HISTORY_FILE = os.path.expanduser("~/.npcsh_history")
1064
1051
 
1065
1052
 
1066
1053
 
1067
1054
  def setup_readline() -> str:
1055
+ import readline
1068
1056
  if readline is None:
1069
1057
  return None
1070
1058
  try:
@@ -1097,14 +1085,6 @@ def save_readline_history():
1097
1085
 
1098
1086
 
1099
1087
 
1100
-
1101
- from npcpy.memory.command_history import (
1102
- start_new_conversation,
1103
- )
1104
- from dataclasses import dataclass, field
1105
- from typing import Optional, List, Dict, Any, Tuple, Union
1106
- from npcpy.npc_compiler import NPC, Team
1107
- import os
1108
1088
  @dataclass
1109
1089
  class ShellState:
1110
1090
  npc: Optional[Union[NPC, str]] = None
@@ -1126,6 +1106,7 @@ class ShellState:
1126
1106
  video_gen_model: str = NPCSH_VIDEO_GEN_MODEL
1127
1107
  video_gen_provider: str = NPCSH_VIDEO_GEN_PROVIDER
1128
1108
  current_mode: str = NPCSH_DEFAULT_MODE
1109
+ build_kg: bool = NPCSH_BUILD_KG,
1129
1110
  api_key: Optional[str] = None
1130
1111
  api_url: Optional[str] = NPCSH_API_URL
1131
1112
  current_path: str = field(default_factory=os.getcwd)
@@ -1163,5 +1144,6 @@ initial_state = ShellState(
1163
1144
  image_gen_provider=NPCSH_IMAGE_GEN_PROVIDER,
1164
1145
  video_gen_model=NPCSH_VIDEO_GEN_MODEL,
1165
1146
  video_gen_provider=NPCSH_VIDEO_GEN_PROVIDER,
1147
+ build_kg=NPCSH_BUILD_KG,
1166
1148
  api_url=NPCSH_API_URL,
1167
1149
  )
npcsh/npcsh.py CHANGED
@@ -35,6 +35,7 @@ from npcsh._state import (
35
35
  ShellState,
36
36
  interactive_commands,
37
37
  BASH_COMMANDS,
38
+
38
39
  start_interactive_session,
39
40
  validate_bash_command,
40
41
  normalize_and_expand_flags,
@@ -892,9 +893,13 @@ def execute_command(
892
893
  active_provider = npc_provider or state.chat_provider
893
894
 
894
895
  if state.current_mode == 'agent':
896
+ print(len(commands), commands)
895
897
  for i, cmd_segment in enumerate(commands):
898
+
899
+ render_markdown(f'- executing command {i+1}/{len(commands)}')
896
900
  is_last_command = (i == len(commands) - 1)
897
- stream_this_segment = is_last_command and state.stream_output # Use state's stream setting
901
+
902
+ stream_this_segment = state.stream_output and not is_last_command
898
903
 
899
904
  try:
900
905
  current_state, output = process_pipeline_command(
@@ -905,17 +910,18 @@ def execute_command(
905
910
  )
906
911
 
907
912
  if is_last_command:
908
- final_output = output # Capture the output of the last command
913
+ return current_state, output
909
914
  if isinstance(output, str):
910
915
  stdin_for_next = output
911
916
  elif not isinstance(output, str):
912
917
  try:
913
- full_stream_output = print_and_process_stream_with_markdown(output,
914
- state.npc.model,
915
- state.npc.provider)
916
- stdin_for_next = full_stream_output
917
- if is_last_command:
918
- final_output = full_stream_output
918
+ if stream_this_segment:
919
+ full_stream_output = print_and_process_stream_with_markdown(output,
920
+ state.npc.model,
921
+ state.npc.provider)
922
+ stdin_for_next = full_stream_output
923
+ if is_last_command:
924
+ final_output = full_stream_output
919
925
  except:
920
926
  if output is not None: # Try converting other types to string
921
927
  try:
@@ -1064,6 +1070,13 @@ def setup_shell() -> Tuple[CommandHistory, Team, Optional[NPC]]:
1064
1070
  command_history = CommandHistory(db_path)
1065
1071
 
1066
1072
 
1073
+ if not is_npcsh_initialized():
1074
+ print("Initializing NPCSH...")
1075
+ initialize_base_npcs_if_needed(db_path)
1076
+ print("NPCSH initialization complete. Restart or source ~/.npcshrc.")
1077
+
1078
+
1079
+
1067
1080
  try:
1068
1081
  history_file = setup_readline()
1069
1082
  atexit.register(save_readline_history)
@@ -1260,39 +1273,42 @@ def process_result(
1260
1273
  )
1261
1274
 
1262
1275
  conversation_turn_text = f"User: {user_input}\nAssistant: {final_output_str}"
1263
- conn = command_history.conn
1276
+ engine = command_history.engine
1264
1277
 
1265
- try:
1266
- if not should_skip_kg_processing(user_input, final_output_str):
1267
-
1268
- npc_kg = load_kg_from_db(conn, team_name, npc_name, result_state.current_path)
1269
- evolved_npc_kg, _ = kg_evolve_incremental(
1270
- existing_kg=npc_kg,
1271
- new_content_text=conversation_turn_text,
1272
- model=active_npc.model,
1273
- provider=active_npc.provider,
1274
- get_concepts=True,
1275
- link_concepts_facts = False,
1276
- link_concepts_concepts = False,
1277
- link_facts_facts = False,
1278
1278
 
1279
-
1280
- )
1281
- save_kg_to_db(conn,
1282
- evolved_npc_kg,
1283
- team_name,
1284
- npc_name,
1285
- result_state.current_path)
1286
- except Exception as e:
1287
- print(colored(f"Error during real-time KG evolution: {e}", "red"))
1279
+ if result_state.build_kg:
1280
+ try:
1281
+ if not should_skip_kg_processing(user_input, final_output_str):
1282
+
1283
+ npc_kg = load_kg_from_db(engine, team_name, npc_name, result_state.current_path)
1284
+ evolved_npc_kg, _ = kg_evolve_incremental(
1285
+ existing_kg=npc_kg,
1286
+ new_content_text=conversation_turn_text,
1287
+ model=active_npc.model,
1288
+ provider=active_npc.provider,
1289
+ get_concepts=True,
1290
+ link_concepts_facts = False,
1291
+ link_concepts_concepts = False,
1292
+ link_facts_facts = False,
1293
+
1294
+
1295
+ )
1296
+ save_kg_to_db(engine,
1297
+ evolved_npc_kg,
1298
+ team_name,
1299
+ npc_name,
1300
+ result_state.current_path)
1301
+ except Exception as e:
1302
+ print(colored(f"Error during real-time KG evolution: {e}", "red"))
1288
1303
 
1289
1304
  # --- Part 3: Periodic Team Context Suggestions ---
1290
1305
  result_state.turn_count += 1
1306
+
1291
1307
  if result_state.turn_count > 0 and result_state.turn_count % 10 == 0:
1292
1308
  print(colored("\nChecking for potential team improvements...", "cyan"))
1293
1309
  try:
1294
1310
  summary = breathe(messages=result_state.messages[-20:],
1295
- npc=active_npc)
1311
+ npc=active_npc)
1296
1312
  characterization = summary.get('output')
1297
1313
 
1298
1314
  if characterization and result_state.team:
@@ -1300,7 +1316,7 @@ def process_result(
1300
1316
  ctx_data = {}
1301
1317
  if os.path.exists(team_ctx_path):
1302
1318
  with open(team_ctx_path, 'r') as f:
1303
- ctx_data = yaml.safe_load(f) or {}
1319
+ ctx_data = yaml.safe_load(f) or {}
1304
1320
  current_context = ctx_data.get('context', '')
1305
1321
 
1306
1322
  prompt = f"""Based on this characterization: {characterization},
@@ -1317,7 +1333,7 @@ def process_result(
1317
1333
 
1318
1334
  if suggestion:
1319
1335
  new_context = (current_context + " " + suggestion).strip()
1320
- print(colored("AI suggests updating team context:", "yellow"))
1336
+ print(colored(f"{result_state.npc.name} suggests updating team context:", "yellow"))
1321
1337
  print(f" - OLD: {current_context}\n + NEW: {new_context}")
1322
1338
  if input("Apply? [y/N]: ").strip().lower() == 'y':
1323
1339
  ctx_data['context'] = new_context
@@ -1360,7 +1376,7 @@ def run_repl(command_history: CommandHistory, initial_state: ShellState):
1360
1376
  print("\nGoodbye!")
1361
1377
  print(colored("Processing and archiving all session knowledge...", "cyan"))
1362
1378
 
1363
- conn = command_history.conn
1379
+ engine = command_history.engine
1364
1380
  integrator_npc = NPC(name="integrator", model=current_state.chat_model, provider=current_state.chat_provider)
1365
1381
 
1366
1382
  # Process each unique scope that was active during the session
@@ -1384,7 +1400,7 @@ def run_repl(command_history: CommandHistory, initial_state: ShellState):
1384
1400
  continue
1385
1401
 
1386
1402
  # Load the existing KG for this specific, real scope
1387
- current_kg = load_kg_from_db(conn, team_name, npc_name, path)
1403
+ current_kg = load_kg_from_db(engine, team_name, npc_name, path)
1388
1404
 
1389
1405
  # Evolve it with the full text from the session for this scope
1390
1406
  evolved_kg, _ = kg_evolve_incremental(
@@ -1400,7 +1416,7 @@ def run_repl(command_history: CommandHistory, initial_state: ShellState):
1400
1416
  )
1401
1417
 
1402
1418
  # Save the updated KG back to the database under the same exact scope
1403
- save_kg_to_db(conn, evolved_kg, team_name, npc_name, path)
1419
+ save_kg_to_db(engine, evolved_kg, team_name, npc_name, path)
1404
1420
 
1405
1421
  except Exception as e:
1406
1422
  import traceback
@@ -1488,6 +1504,7 @@ def main() -> None:
1488
1504
  initial_state.npc = default_npc
1489
1505
  initial_state.team = team
1490
1506
 
1507
+
1491
1508
  # add a -g global command to indicate if to use the global or project, otherwise go thru normal flow
1492
1509
 
1493
1510
  if args.command:
npcsh/routes.py CHANGED
@@ -757,12 +757,12 @@ def sleep_handler(command: str, **kwargs):
757
757
  try:
758
758
  db_path = os.getenv("NPCSH_DB_PATH", os.path.expanduser("~/npcsh_history.db"))
759
759
  command_history = CommandHistory(db_path)
760
- conn = command_history.conn
760
+ engine = command_history.engine
761
761
  except Exception as e:
762
762
  return {"output": f"Error connecting to history database for KG access: {e}", "messages": messages}
763
763
 
764
764
  try:
765
- current_kg = load_kg_from_db(conn, team_name, npc_name, current_path)
765
+ current_kg = load_kg_from_db(engine, team_name, npc_name, current_path)
766
766
 
767
767
  # FIXED: Provide a detailed and helpful message when the KG is empty
768
768
  if not current_kg or not current_kg.get('facts'):
@@ -1031,16 +1031,10 @@ def wander_handler(command: str, **kwargs):
1031
1031
  return {"output": f"Error during wander mode: {e}", "messages": messages}
1032
1032
 
1033
1033
  @router.route("yap", "Enter voice chat (yap) mode")
1034
- def whisper_handler(command: str, **kwargs):
1034
+ def yap_handler(command: str, **kwargs):
1035
1035
  try:
1036
1036
  return enter_yap_mode(
1037
- messages=safe_get(kwargs, 'messages'),
1038
- npc=safe_get(kwargs, 'npc'),
1039
- model=safe_get(kwargs, 'model', NPCSH_CHAT_MODEL),
1040
- provider=safe_get(kwargs, 'provider', NPCSH_CHAT_PROVIDER),
1041
- team=safe_get(kwargs, 'team'),
1042
- stream=safe_get(kwargs, 'stream', NPCSH_STREAM_OUTPUT),
1043
- conversation_id=safe_get(kwargs, 'conversation_id')
1037
+ ** kwargs
1044
1038
  )
1045
1039
  except Exception as e:
1046
1040
  traceback.print_exc()
npcsh/yap.py CHANGED
@@ -54,18 +54,20 @@ from npcpy.npc_compiler import (
54
54
  from npcpy.memory.command_history import CommandHistory, save_conversation_message,start_new_conversation
55
55
  from typing import Dict, Any, List
56
56
  def enter_yap_mode(
57
-
58
- model: str ,
59
- provider: str ,
60
- messages: list = None,
57
+ messages: list = None,
58
+ model: str = None,
59
+ provider: str = None ,
61
60
  npc = None,
62
- team= None,
61
+ team = None,
62
+ stream: bool = False,
63
+ api_url: str = None,
64
+ api_key: str=None,
65
+ conversation_id = None,
63
66
  tts_model="kokoro",
64
67
  voice="af_heart",
65
68
  files: List[str] = None,
66
69
  rag_similarity_threshold: float = 0.3,
67
- stream: bool = NPCSH_STREAM_OUTPUT,
68
- conversation_id = None,
70
+ **kwargs
69
71
  ) -> Dict[str, Any]:
70
72
  running = True
71
73
  is_recording = False
@@ -100,22 +102,20 @@ def enter_yap_mode(
100
102
  # Add conciseness instruction to the system message
101
103
  system_message = system_message + " " + concise_instruction
102
104
 
103
- if messages is None:
105
+ if messages is None or len(messages) == 0:
104
106
  messages = [{"role": "system", "content": system_message}]
105
107
  elif messages is not None and messages[0]['role'] != 'system':
106
108
  messages.insert(0, {"role": "system", "content": system_message})
107
109
 
108
110
  kokoro_pipeline = None
109
111
  if tts_model == "kokoro":
110
- try:
111
- from kokoro import KPipeline
112
- import soundfile as sf
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
+
113
118
 
114
- kokoro_pipeline = KPipeline(lang_code="a")
115
- print("Kokoro TTS model initialized")
116
- except ImportError:
117
- print("Kokoro not installed, falling back to gTTS")
118
- tts_model = "gtts"
119
119
 
120
120
  # Initialize PyAudio
121
121
  pyaudio_instance = pyaudio.PyAudio()
@@ -134,43 +134,45 @@ def enter_yap_mode(
134
134
  nonlocal running, audio_stream
135
135
 
136
136
  while running and speech_thread_active.is_set():
137
- try:
138
- # Get next speech item from queue
139
- if not speech_queue.empty():
140
- text_to_speak = speech_queue.get(timeout=0.1)
141
-
142
- # Only process if there's text to speak
143
- if text_to_speak.strip():
144
- # IMPORTANT: Set is_speaking flag BEFORE starting audio output
145
- is_speaking.set()
146
-
147
- # Safely close the audio input stream before speaking
148
- current_audio_stream = audio_stream
149
- audio_stream = (
150
- None # Set to None to prevent capture thread from using it
151
- )
152
-
153
- if current_audio_stream and current_audio_stream.is_active():
154
- current_audio_stream.stop_stream()
155
- current_audio_stream.close()
156
-
157
- print(f"Speaking full response...")
158
-
159
- # Generate and play speech
160
- generate_and_play_speech(text_to_speak)
161
-
162
- # Delay after speech to prevent echo
163
- time.sleep(0.005 * len(text_to_speak))
164
- print(len(text_to_speak))
165
-
166
- # Clear the speaking flag to allow listening again
167
- is_speaking.clear()
168
- else:
169
- time.sleep(0.5)
170
- except Exception as e:
171
- print(f"Error in speech thread: {e}")
172
- is_speaking.clear() # Make sure to clear the flag if there's an error
173
- time.sleep(0.1)
137
+ #try:
138
+ # Get next speech item from queue
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
+ # Only process if there's text to speak
145
+ if text_to_speak.strip():
146
+ # IMPORTANT: Set is_speaking flag BEFORE starting audio output
147
+ is_speaking.set()
148
+
149
+ # Safely close the audio input stream before speaking
150
+ current_audio_stream = audio_stream
151
+ audio_stream = (
152
+ None # Set to None to prevent capture thread from using it
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
+ # Generate and play speech
162
+ generate_and_play_speech(text_to_speak)
163
+
164
+ # Delay after speech to prevent echo
165
+ time.sleep(0.005 * len(text_to_speak))
166
+ print(len(text_to_speak))
167
+
168
+ # Clear the speaking flag to allow listening again
169
+ is_speaking.clear()
170
+ else:
171
+ time.sleep(0.5)
172
+ #except Exception as e:
173
+ # print(f"Error in speech thread: {e}")
174
+ # is_speaking.clear() # Make sure to clear the flag if there's an error
175
+ # time.sleep(0.1)
174
176
 
175
177
  def safely_close_audio_stream(stream):
176
178
  """Safely close an audio stream with error handling"""
@@ -315,10 +317,9 @@ def enter_yap_mode(
315
317
  frames_per_buffer=CHUNK,
316
318
  )
317
319
 
318
- # Initialize or reset the recording variables
319
- is_recording = False
320
- recording_data = []
321
- buffer_data = []
320
+ # Add timeout counter
321
+ timeout_counter = 0
322
+ max_timeout = 100 # About 10 seconds at 0.1s intervals
322
323
 
323
324
  print("\nListening for speech...")
324
325
 
@@ -327,49 +328,63 @@ def enter_yap_mode(
327
328
  and audio_stream
328
329
  and audio_stream.is_active()
329
330
  and not is_speaking.is_set()
331
+ and timeout_counter < max_timeout
330
332
  ):
331
333
  try:
334
+ # Add non-blocking read with timeout
332
335
  data = audio_stream.read(CHUNK, exception_on_overflow=False)
333
- if data:
334
- audio_array = np.frombuffer(data, dtype=np.int16)
335
- audio_float = audio_array.astype(np.float32) / 32768.0
336
-
337
- tensor = torch.from_numpy(audio_float).to(device)
338
- speech_prob = vad_model(tensor, RATE).item()
339
- current_time = time.time()
340
-
341
- if speech_prob > 0.5: # VAD threshold
342
- last_speech_time = current_time
343
- if not is_recording:
344
- is_recording = True
345
- print("\nSpeech detected, listening...")
346
- recording_data.extend(buffer_data)
347
- buffer_data = []
348
- recording_data.append(data)
336
+
337
+ if not data:
338
+ timeout_counter += 1
339
+ time.sleep(0.1)
340
+ continue
341
+
342
+ # Reset timeout on successful read
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
+ # Add timeout to VAD processing
353
+ speech_prob = vad_model(tensor, RATE).item()
354
+ current_time = time.time()
355
+
356
+ if speech_prob > 0.5: # VAD threshold
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
+ ): # silence duration
369
+ is_recording = False
370
+ print("Speech ended, transcribing...")
371
+
372
+ # Stop stream before transcribing
373
+ safely_close_audio_stream(audio_stream)
374
+ audio_stream = None
375
+
376
+ # Transcribe in this thread to avoid race conditions
377
+ transcription = transcribe_recording(recording_data)
378
+ if transcription:
379
+ transcription_queue.put(transcription)
380
+ recording_data = []
381
+ return True # Got speech
349
382
  else:
350
- if is_recording:
351
- if (
352
- current_time - last_speech_time > 1
353
- ): # silence duration
354
- is_recording = False
355
- print("Speech ended, transcribing...")
356
-
357
- # Stop stream before transcribing
358
- safely_close_audio_stream(audio_stream)
359
- audio_stream = None
360
-
361
- # Transcribe in this thread to avoid race conditions
362
- transcription = transcribe_recording(recording_data)
363
- if transcription:
364
- transcription_queue.put(transcription)
365
- recording_data = []
366
- return True # Got speech
367
- else:
368
- buffer_data.append(data)
369
- if len(buffer_data) > int(
370
- 0.65 * RATE / CHUNK
371
- ): # buffer duration
372
- buffer_data.pop(0)
383
+ buffer_data.append(data)
384
+ if len(buffer_data) > int(
385
+ 0.65 * RATE / CHUNK
386
+ ): # buffer duration
387
+ buffer_data.pop(0)
373
388
 
374
389
  # Check frequently if we need to stop capturing
375
390
  if is_speaking.is_set():
@@ -427,19 +442,14 @@ def enter_yap_mode(
427
442
 
428
443
 
429
444
  while running:
430
-
431
- # First check for typed input (non-blocking)
432
445
  import select
433
446
  import sys
434
-
435
- # Don't spam the console with prompts when speaking
436
447
  if not is_speaking.is_set():
437
448
  print(
438
449
  "🎤🎤🎤🎤\n Speak or type your message (or 'exit' to quit): ",
439
450
  end="",
440
451
  flush=True,
441
452
  )
442
-
443
453
  rlist, _, _ = select.select([sys.stdin], [], [], 0.1)
444
454
  if rlist:
445
455
  user_input = sys.stdin.readline().strip()
@@ -448,7 +458,7 @@ def enter_yap_mode(
448
458
  break
449
459
  if user_input:
450
460
  print(f"\nYou (typed): {user_input}")
451
- # Handle RAG context
461
+
452
462
  if loaded_content:
453
463
  context_content = ""
454
464
  for filename, content in loaded_content.items():
@@ -494,9 +504,8 @@ def enter_yap_mode(
494
504
 
495
505
 
496
506
  continue # Skip audio capture this cycle
497
-
498
- # Then try to capture some audio (if no typed input)
499
507
  if not is_speaking.is_set(): # Only capture if not currently speaking
508
+ print('capturing audio')
500
509
  got_speech = capture_audio()
501
510
 
502
511
  # If we got speech, process it
@@ -560,9 +569,9 @@ def main():
560
569
  provider = sibiji.provider
561
570
  # Enter spool mode
562
571
  enter_yap_mode(
563
- model,
564
- provider,
565
572
  messages=None,
573
+ model= model,
574
+ provider = provider,
566
575
  npc=sibiji,
567
576
  team = team,
568
577
  files=args.files,
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: npcsh
3
- Version: 1.0.13
3
+ Version: 1.0.14
4
4
  Summary: npcsh is a command-line toolkit for using AI agents in novel ways.
5
5
  Home-page: https://github.com/NPC-Worldwide/npcsh
6
6
  Author: Christopher Agostino
@@ -16,6 +16,7 @@ Requires-Dist: litellm
16
16
  Requires-Dist: docx
17
17
  Requires-Dist: scipy
18
18
  Requires-Dist: numpy
19
+ Requires-Dist: imagehash
19
20
  Requires-Dist: requests
20
21
  Requires-Dist: matplotlib
21
22
  Requires-Dist: markdown
@@ -121,14 +122,15 @@ Once installed, the following CLI tools will be available: `npcsh`, `guac`, `npc
121
122
  npcsh:🤖sibiji:gemini-2.5-flash>can you help me identify what process is listening on port 5337?
122
123
  ```
123
124
  <p align="center">
124
- <img src="https://raw.githubusercontent.com/npc-worldwide/npcsh/main/test_data/port5337.png" alt="example of running npcsh to check what processes are listening on port 5337", width=250>
125
+ <img src="https://raw.githubusercontent.com/npc-worldwide/npcsh/main/test_data/port5337.png" alt="example of running npcsh to check what processes are listening on port 5337", width=600>
125
126
  </p>
126
127
  - Edit files
127
128
 
128
129
  - **Ask a Generic Question**
129
130
  ```bash
130
- npc 'has there ever been a better pasta shape than bucatini?'
131
+ npcsh> has there ever been a better pasta shape than bucatini?
131
132
  ```
133
+
132
134
  ```
133
135
  .Loaded .env file...
134
136
  Initializing database schema...
@@ -147,7 +149,7 @@ Once installed, the following CLI tools will be available: `npcsh`, `guac`, `npc
147
149
  /search "cal golden bears football schedule" -sp perplexity
148
150
  ```
149
151
  <p align="center">
150
- <img src="https://raw.githubusercontent.com/npc-worldwide/npcsh/main/test_data/search_example.png" alt="example of search results", width=250>
152
+ <img src="https://raw.githubusercontent.com/npc-worldwide/npcsh/main/test_data/search_example.png" alt="example of search results", width=600>
151
153
  </p>
152
154
 
153
155
  - **Computer Use**
@@ -203,10 +205,10 @@ Once installed, the following CLI tools will be available: `npcsh`, `guac`, `npc
203
205
  - `/serve` - Serve an NPC Team server.
204
206
  - `/set` - Set configuration values
205
207
  - `/sleep` - Evolve knowledge graph with options for dreaming.
206
- - `/spool` - Enter interactive chat (spool) mode
208
+ - `/spool` - Enter interactive chat (spool) mode with an npc with fresh context or files for rag
207
209
  - `/trigger` - Execute a trigger command
208
- - `/vixynt` - Generate images from text descriptions
209
- - `/wander` - Enter wander mode (experimental)
210
+ - `/vixynt` - Generate and edit images from text descriptions using local models, openai, gemini
211
+ - `/wander` - A method for LLMs to think on a problem by switching between states of high temperature and low temperature
210
212
  - `/yap` - Enter voice chat (yap) mode
211
213
 
212
214
  ## Common Command-Line Flags\n\nThe shortest unambiguous prefix works (e.g., `-t` for `--temperature`).
@@ -1,21 +1,21 @@
1
1
  npcsh/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
2
- npcsh/_state.py,sha256=UEI28eLEHS4xJZOlNynSfZWnxipZV086ynGzoAdQmUY,33601
2
+ npcsh/_state.py,sha256=Xm2JoXLW4GNEbdME096XLzRCIIPfaNslh8VHc6JshHc,33436
3
3
  npcsh/alicanto.py,sha256=-muGqd0O2m8xcFBctEavSEizWbQmzuPSdcT-3YqYBhY,45043
4
4
  npcsh/guac.py,sha256=Ocmk_c4NUtGsC3JOtmkbgLvD6u-XtBPRFRYcckpgUJU,33099
5
5
  npcsh/mcp_helpers.py,sha256=Ktd2yXuBnLL2P7OMalgGLj84PXJSzaucjqmJVvWx6HA,12723
6
6
  npcsh/mcp_npcsh.py,sha256=SfmplH62GS9iI6q4vuQLVUS6tkrok6L7JxODx_iH7ps,36158
7
7
  npcsh/mcp_server.py,sha256=l2Ra0lpFrUu334pvp0Q9ajF2n73KvZswFi0FgbDhh9k,5884
8
8
  npcsh/npc.py,sha256=7ujKrMQFgkeGJ4sX5Kn_dB5tjrPN58xeC91PNt453aM,7827
9
- npcsh/npcsh.py,sha256=TB_xYN3IEFHB5Wj931ZOlgQ12cNBsVz1SoIrQsP4b4M,59088
9
+ npcsh/npcsh.py,sha256=uxs_5k-zmuDjdvKMxoBZwdefdgKGESd-EIGCXYNgx0Y,59571
10
10
  npcsh/plonk.py,sha256=7w7J2bht5QXOyV2UK045nAPDmrSrTGLX-sh56KQ3-k0,14653
11
11
  npcsh/pti.py,sha256=jGHGE5SeIcDkV8WlOEHCKQCnYAL4IPS-kUBHrUz0oDA,10019
12
- npcsh/routes.py,sha256=hEnOwRrY9onHhUTeS7lPGEytEUEzd7KZYO5GTikS0KQ,44848
12
+ npcsh/routes.py,sha256=gsKHhdbzcZnE91w86ydr3ABNTxL12Ta0Oa9z3qwEh54,44470
13
13
  npcsh/spool.py,sha256=QF1SuIhj_PWiOYNkAK31f1W_wS8yYxC5XvM2GU7VJMM,9495
14
14
  npcsh/wander.py,sha256=BiN6eYyFnEsFzo8MFLRkdZ8xS9sTKkQpjiCcy9chMcc,23225
15
- npcsh/yap.py,sha256=h5KNt9sNOrDPhGe_zfn_yFIeQhizX09zocjcPWH7m3k,20905
16
- npcsh-1.0.13.dist-info/licenses/LICENSE,sha256=IKBvAECHP-aCiJtE4cHGCE5Yl0tozYz02PomGeWS3y4,1070
17
- npcsh-1.0.13.dist-info/METADATA,sha256=xhEAZb5pELsUA-lQKspyS_nKfy6kxU2ayL4CfJxZQjg,30829
18
- npcsh-1.0.13.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
19
- npcsh-1.0.13.dist-info/entry_points.txt,sha256=qxOYTm3ym3JWyWf2nv2Mk71uMcJIdUoNEJ8VYMkyHiY,214
20
- npcsh-1.0.13.dist-info/top_level.txt,sha256=kHSNgKMCkfjV95-DH0YSp1LLBi0HXdF3w57j7MQON3E,6
21
- npcsh-1.0.13.dist-info/RECORD,,
15
+ npcsh/yap.py,sha256=ipkY3uMDw8gNrPSZ9qJFWVQ_fXtLmQ2oz_6_WZt2hew,21097
16
+ npcsh-1.0.14.dist-info/licenses/LICENSE,sha256=IKBvAECHP-aCiJtE4cHGCE5Yl0tozYz02PomGeWS3y4,1070
17
+ npcsh-1.0.14.dist-info/METADATA,sha256=W0GyU5aR_MP9qUrp7BHWV54p0cgNNrY6E7wzChyvU_4,31027
18
+ npcsh-1.0.14.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
19
+ npcsh-1.0.14.dist-info/entry_points.txt,sha256=qxOYTm3ym3JWyWf2nv2Mk71uMcJIdUoNEJ8VYMkyHiY,214
20
+ npcsh-1.0.14.dist-info/top_level.txt,sha256=kHSNgKMCkfjV95-DH0YSp1LLBi0HXdF3w57j7MQON3E,6
21
+ npcsh-1.0.14.dist-info/RECORD,,
File without changes