npcsh 1.1.4__py3-none-any.whl → 1.1.6__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 (102) hide show
  1. npcsh/_state.py +470 -367
  2. npcsh/npc_team/corca_example.png +0 -0
  3. npcsh/npc_team/jinxs/{python_executor.jinx → code/python.jinx} +1 -1
  4. npcsh/npc_team/jinxs/{bash_executer.jinx → code/sh.jinx} +1 -2
  5. npcsh/npc_team/jinxs/code/sql.jinx +16 -0
  6. npcsh/npc_team/jinxs/modes/alicanto.jinx +88 -0
  7. npcsh/npc_team/jinxs/modes/corca.jinx +28 -0
  8. npcsh/npc_team/jinxs/modes/guac.jinx +46 -0
  9. npcsh/npc_team/jinxs/modes/plonk.jinx +57 -0
  10. npcsh/npc_team/jinxs/modes/pti.jinx +28 -0
  11. npcsh/npc_team/jinxs/modes/spool.jinx +40 -0
  12. npcsh/npc_team/jinxs/modes/wander.jinx +81 -0
  13. npcsh/npc_team/jinxs/modes/yap.jinx +25 -0
  14. npcsh/npc_team/jinxs/utils/breathe.jinx +20 -0
  15. npcsh/npc_team/jinxs/utils/core/build.jinx +65 -0
  16. npcsh/npc_team/jinxs/utils/core/compile.jinx +50 -0
  17. npcsh/npc_team/jinxs/utils/core/help.jinx +52 -0
  18. npcsh/npc_team/jinxs/utils/core/init.jinx +41 -0
  19. npcsh/npc_team/jinxs/utils/core/jinxs.jinx +32 -0
  20. npcsh/npc_team/jinxs/utils/core/set.jinx +40 -0
  21. npcsh/npc_team/jinxs/{edit_file.jinx → utils/edit_file.jinx} +1 -1
  22. npcsh/npc_team/jinxs/utils/flush.jinx +39 -0
  23. npcsh/npc_team/jinxs/utils/npc-studio.jinx +77 -0
  24. npcsh/npc_team/jinxs/utils/ots.jinx +61 -0
  25. npcsh/npc_team/jinxs/utils/plan.jinx +33 -0
  26. npcsh/npc_team/jinxs/utils/roll.jinx +66 -0
  27. npcsh/npc_team/jinxs/utils/sample.jinx +56 -0
  28. npcsh/npc_team/jinxs/utils/search.jinx +130 -0
  29. npcsh/npc_team/jinxs/utils/serve.jinx +29 -0
  30. npcsh/npc_team/jinxs/utils/sleep.jinx +116 -0
  31. npcsh/npc_team/jinxs/utils/trigger.jinx +36 -0
  32. npcsh/npc_team/jinxs/utils/vixynt.jinx +117 -0
  33. npcsh/npcsh.py +13 -11
  34. npcsh/routes.py +97 -1419
  35. npcsh-1.1.6.data/data/npcsh/npc_team/alicanto.jinx +88 -0
  36. npcsh-1.1.6.data/data/npcsh/npc_team/breathe.jinx +20 -0
  37. npcsh-1.1.6.data/data/npcsh/npc_team/build.jinx +65 -0
  38. npcsh-1.1.6.data/data/npcsh/npc_team/compile.jinx +50 -0
  39. npcsh-1.1.6.data/data/npcsh/npc_team/corca.jinx +28 -0
  40. npcsh-1.1.6.data/data/npcsh/npc_team/corca_example.png +0 -0
  41. {npcsh-1.1.4.data → npcsh-1.1.6.data}/data/npcsh/npc_team/edit_file.jinx +1 -1
  42. npcsh-1.1.6.data/data/npcsh/npc_team/flush.jinx +39 -0
  43. npcsh-1.1.6.data/data/npcsh/npc_team/guac.jinx +46 -0
  44. npcsh-1.1.6.data/data/npcsh/npc_team/help.jinx +52 -0
  45. npcsh-1.1.6.data/data/npcsh/npc_team/init.jinx +41 -0
  46. npcsh-1.1.6.data/data/npcsh/npc_team/jinxs.jinx +32 -0
  47. npcsh-1.1.6.data/data/npcsh/npc_team/npc-studio.jinx +77 -0
  48. npcsh-1.1.6.data/data/npcsh/npc_team/ots.jinx +61 -0
  49. npcsh-1.1.6.data/data/npcsh/npc_team/plan.jinx +33 -0
  50. npcsh-1.1.6.data/data/npcsh/npc_team/plonk.jinx +57 -0
  51. npcsh-1.1.6.data/data/npcsh/npc_team/pti.jinx +28 -0
  52. npcsh-1.1.4.data/data/npcsh/npc_team/python_executor.jinx → npcsh-1.1.6.data/data/npcsh/npc_team/python.jinx +1 -1
  53. npcsh-1.1.6.data/data/npcsh/npc_team/roll.jinx +66 -0
  54. npcsh-1.1.6.data/data/npcsh/npc_team/sample.jinx +56 -0
  55. npcsh-1.1.6.data/data/npcsh/npc_team/search.jinx +130 -0
  56. npcsh-1.1.6.data/data/npcsh/npc_team/serve.jinx +29 -0
  57. npcsh-1.1.6.data/data/npcsh/npc_team/set.jinx +40 -0
  58. npcsh-1.1.4.data/data/npcsh/npc_team/bash_executer.jinx → npcsh-1.1.6.data/data/npcsh/npc_team/sh.jinx +1 -2
  59. npcsh-1.1.6.data/data/npcsh/npc_team/sleep.jinx +116 -0
  60. npcsh-1.1.6.data/data/npcsh/npc_team/spool.jinx +40 -0
  61. npcsh-1.1.6.data/data/npcsh/npc_team/sql.jinx +16 -0
  62. npcsh-1.1.6.data/data/npcsh/npc_team/trigger.jinx +36 -0
  63. npcsh-1.1.6.data/data/npcsh/npc_team/vixynt.jinx +117 -0
  64. npcsh-1.1.6.data/data/npcsh/npc_team/wander.jinx +81 -0
  65. npcsh-1.1.6.data/data/npcsh/npc_team/yap.jinx +25 -0
  66. {npcsh-1.1.4.dist-info → npcsh-1.1.6.dist-info}/METADATA +1 -10
  67. npcsh-1.1.6.dist-info/RECORD +124 -0
  68. npcsh/npc_team/jinxs/image_generation.jinx +0 -29
  69. npcsh/npc_team/jinxs/internet_search.jinx +0 -31
  70. npcsh/npc_team/jinxs/kg_search.jinx +0 -43
  71. npcsh/npc_team/jinxs/memory_search.jinx +0 -36
  72. npcsh/npc_team/jinxs/screen_cap.jinx +0 -25
  73. npcsh-1.1.4.data/data/npcsh/npc_team/image_generation.jinx +0 -29
  74. npcsh-1.1.4.data/data/npcsh/npc_team/internet_search.jinx +0 -31
  75. npcsh-1.1.4.data/data/npcsh/npc_team/kg_search.jinx +0 -43
  76. npcsh-1.1.4.data/data/npcsh/npc_team/memory_search.jinx +0 -36
  77. npcsh-1.1.4.data/data/npcsh/npc_team/screen_cap.jinx +0 -25
  78. npcsh-1.1.4.dist-info/RECORD +0 -78
  79. {npcsh-1.1.4.data → npcsh-1.1.6.data}/data/npcsh/npc_team/alicanto.npc +0 -0
  80. {npcsh-1.1.4.data → npcsh-1.1.6.data}/data/npcsh/npc_team/alicanto.png +0 -0
  81. {npcsh-1.1.4.data → npcsh-1.1.6.data}/data/npcsh/npc_team/corca.npc +0 -0
  82. {npcsh-1.1.4.data → npcsh-1.1.6.data}/data/npcsh/npc_team/corca.png +0 -0
  83. {npcsh-1.1.4.data → npcsh-1.1.6.data}/data/npcsh/npc_team/foreman.npc +0 -0
  84. {npcsh-1.1.4.data → npcsh-1.1.6.data}/data/npcsh/npc_team/frederic.npc +0 -0
  85. {npcsh-1.1.4.data → npcsh-1.1.6.data}/data/npcsh/npc_team/frederic4.png +0 -0
  86. {npcsh-1.1.4.data → npcsh-1.1.6.data}/data/npcsh/npc_team/guac.png +0 -0
  87. {npcsh-1.1.4.data → npcsh-1.1.6.data}/data/npcsh/npc_team/kadiefa.npc +0 -0
  88. {npcsh-1.1.4.data → npcsh-1.1.6.data}/data/npcsh/npc_team/kadiefa.png +0 -0
  89. {npcsh-1.1.4.data → npcsh-1.1.6.data}/data/npcsh/npc_team/npcsh.ctx +0 -0
  90. {npcsh-1.1.4.data → npcsh-1.1.6.data}/data/npcsh/npc_team/npcsh_sibiji.png +0 -0
  91. {npcsh-1.1.4.data → npcsh-1.1.6.data}/data/npcsh/npc_team/plonk.npc +0 -0
  92. {npcsh-1.1.4.data → npcsh-1.1.6.data}/data/npcsh/npc_team/plonk.png +0 -0
  93. {npcsh-1.1.4.data → npcsh-1.1.6.data}/data/npcsh/npc_team/plonkjr.npc +0 -0
  94. {npcsh-1.1.4.data → npcsh-1.1.6.data}/data/npcsh/npc_team/plonkjr.png +0 -0
  95. {npcsh-1.1.4.data → npcsh-1.1.6.data}/data/npcsh/npc_team/sibiji.npc +0 -0
  96. {npcsh-1.1.4.data → npcsh-1.1.6.data}/data/npcsh/npc_team/sibiji.png +0 -0
  97. {npcsh-1.1.4.data → npcsh-1.1.6.data}/data/npcsh/npc_team/spool.png +0 -0
  98. {npcsh-1.1.4.data → npcsh-1.1.6.data}/data/npcsh/npc_team/yap.png +0 -0
  99. {npcsh-1.1.4.dist-info → npcsh-1.1.6.dist-info}/WHEEL +0 -0
  100. {npcsh-1.1.4.dist-info → npcsh-1.1.6.dist-info}/entry_points.txt +0 -0
  101. {npcsh-1.1.4.dist-info → npcsh-1.1.6.dist-info}/licenses/LICENSE +0 -0
  102. {npcsh-1.1.4.dist-info → npcsh-1.1.6.dist-info}/top_level.txt +0 -0
npcsh/_state.py CHANGED
@@ -88,12 +88,18 @@ from npcpy.llm_funcs import (
88
88
  breathe,
89
89
 
90
90
  )
91
+
91
92
  from npcpy.memory.knowledge_graph import (
92
93
  kg_evolve_incremental,
93
94
 
94
95
  )
95
96
  from npcpy.gen.embeddings import get_embeddings
96
97
 
98
+ import inspect
99
+ import sys
100
+ from npcpy.memory.search import execute_rag_command, execute_brainblast_command
101
+ from npcpy.data.load import load_file_contents
102
+ from npcpy.data.web import search_web
97
103
  try:
98
104
  import readline
99
105
  except:
@@ -443,6 +449,138 @@ def get_team_ctx_path(team_path: str) -> Optional[str]:
443
449
  return str(ctx_files[0]) if ctx_files else None
444
450
 
445
451
 
452
+ from npcpy.memory.memory_processor import memory_approval_ui
453
+ from npcpy.ft.memory_trainer import MemoryTrainer
454
+ from npcpy.llm_funcs import get_facts
455
+
456
+ def get_relevant_memories(
457
+ command_history: CommandHistory,
458
+ npc_name: str,
459
+ team_name: str,
460
+ path: str,
461
+ query: Optional[str] = None,
462
+ max_memories: int = 10,
463
+ state: Optional[ShellState] = None
464
+ ) -> List[Dict]:
465
+
466
+ engine = command_history.engine
467
+
468
+ all_memories = command_history.get_memories_for_scope(
469
+ npc=npc_name,
470
+ team=team_name,
471
+ directory_path=path,
472
+ )
473
+
474
+ if not all_memories:
475
+ return []
476
+
477
+ if len(all_memories) <= max_memories and not query:
478
+ return all_memories
479
+
480
+ if query:
481
+ query_lower = query.lower()
482
+ keyword_matches = [
483
+ m for m in all_memories
484
+ if query_lower in (m.get('final_memory') or m.get('initial_memory') or '').lower()
485
+ ]
486
+
487
+ if keyword_matches:
488
+ return keyword_matches[:max_memories]
489
+
490
+ if state and state.embedding_model and state.embedding_provider:
491
+ try:
492
+ from npcpy.gen.embeddings import get_embeddings
493
+
494
+ search_text = query if query else "recent context"
495
+ query_embedding = get_embeddings(
496
+ [search_text],
497
+ state.embedding_model,
498
+ state.embedding_provider
499
+ )[0]
500
+
501
+ memory_texts = [
502
+ m.get('final_memory', '') for m in all_memories
503
+ ]
504
+ memory_embeddings = get_embeddings(
505
+ memory_texts,
506
+ state.embedding_model,
507
+ state.embedding_provider
508
+ )
509
+
510
+ import numpy as np
511
+ similarities = []
512
+ for mem_emb in memory_embeddings:
513
+ similarity = np.dot(query_embedding, mem_emb) / (
514
+ np.linalg.norm(query_embedding) *
515
+ np.linalg.norm(mem_emb)
516
+ )
517
+ similarities.append(similarity)
518
+
519
+ sorted_indices = np.argsort(similarities)[::-1]
520
+ return [all_memories[i] for i in sorted_indices[:max_memories]]
521
+
522
+ except Exception as e:
523
+ print(colored(
524
+ f"RAG search failed, using recent: {e}",
525
+ "yellow"
526
+ ))
527
+
528
+ return all_memories[-max_memories:]
529
+
530
+
531
+ def search_kg_facts(
532
+ self,
533
+ npc: str,
534
+ team: str,
535
+ directory_path: str,
536
+ query: str
537
+ ) -> List[Dict]:
538
+
539
+ kg = load_kg_from_db(
540
+ self.engine,
541
+ team,
542
+ npc,
543
+ directory_path
544
+ )
545
+
546
+ if not kg or 'facts' not in kg:
547
+ return []
548
+
549
+ query_lower = query.lower()
550
+ matching_facts = []
551
+
552
+ for fact in kg['facts']:
553
+ statement = fact.get('statement', '').lower()
554
+ if query_lower in statement:
555
+ matching_facts.append(fact)
556
+
557
+ return matching_facts
558
+
559
+ def format_memory_context(memory_examples):
560
+ if not memory_examples:
561
+ return ""
562
+
563
+ context_parts = []
564
+
565
+ approved_examples = memory_examples.get("approved", [])
566
+ rejected_examples = memory_examples.get("rejected", [])
567
+
568
+ if approved_examples:
569
+ context_parts.append("EXAMPLES OF GOOD MEMORIES:")
570
+ for ex in approved_examples[:5]:
571
+ final = ex.get("final_memory") or ex.get("initial_memory")
572
+ context_parts.append(f"- {final}")
573
+
574
+ if rejected_examples:
575
+ context_parts.append("\nEXAMPLES OF POOR MEMORIES TO AVOID:")
576
+ for ex in rejected_examples[:3]:
577
+ context_parts.append(f"- {ex.get('initial_memory')}")
578
+
579
+ if context_parts:
580
+ context_parts.append("\nLearn from these examples to generate similar high-quality memories.")
581
+ return "\n".join(context_parts)
582
+
583
+ return ""
446
584
  def add_npcshrc_to_shell_config() -> None:
447
585
  """
448
586
  Function Description:
@@ -1863,156 +2001,57 @@ def should_skip_kg_processing(user_input: str, assistant_output: str) -> bool:
1863
2001
 
1864
2002
  return False
1865
2003
 
1866
-
1867
-
1868
-
1869
2004
  def execute_slash_command(command: str,
1870
2005
  stdin_input: Optional[str],
1871
2006
  state: ShellState,
1872
2007
  stream: bool,
1873
2008
  router) -> Tuple[ShellState, Any]:
1874
- """Executes slash commands using the router or checking NPC/Team jinxs."""
1875
- all_command_parts = shlex.split(command)
2009
+ """Executes slash commands using the router."""
2010
+ try:
2011
+ all_command_parts = shlex.split(command)
2012
+ except ValueError:
2013
+ all_command_parts = command.split()
1876
2014
  command_name = all_command_parts[0].lstrip('/')
1877
2015
 
1878
-
2016
+ # --- NPC SWITCHING LOGIC ---
1879
2017
  if command_name in ['n', 'npc']:
1880
2018
  npc_to_switch_to = all_command_parts[1] if len(all_command_parts) > 1 else None
1881
2019
  if npc_to_switch_to and state.team and npc_to_switch_to in state.team.npcs:
1882
2020
  state.npc = state.team.npcs[npc_to_switch_to]
1883
- return state, f"Switched to NPC: {npc_to_switch_to}"
2021
+ return state, {"output": f"Switched to NPC: {npc_to_switch_to}", "messages": state.messages}
1884
2022
  else:
1885
2023
  available_npcs = list(state.team.npcs.keys()) if state.team else []
1886
- return state, colored(f"NPC '{npc_to_switch_to}' not found. Available NPCs: {', '.join(available_npcs)}", "red")
2024
+ return state, {"output": colored(f"NPC '{npc_to_switch_to}' not found. Available NPCs: {', '.join(available_npcs)}", "red"), "messages": state.messages}
1887
2025
 
1888
-
2026
+ # --- ROUTER LOGIC ---
1889
2027
  handler = router.get_route(command_name)
1890
2028
  if handler:
1891
- parsed_flags, positional_args = parse_generic_command_flags(all_command_parts[1:])
1892
- normalized_flags = normalize_and_expand_flags(parsed_flags)
1893
-
1894
2029
  handler_kwargs = {
1895
- 'stream': stream,
1896
- 'team': state.team,
1897
- 'messages': state.messages,
1898
- 'api_url': state.api_url,
1899
- 'api_key': state.api_key,
1900
- 'stdin_input': stdin_input,
1901
- 'positional_args': positional_args,
1902
- 'plonk_context': state.team.shared_context.get('PLONK_CONTEXT') if state.team and hasattr(state.team, 'shared_context') else None,
1903
-
1904
-
2030
+ 'stream': stream, 'team': state.team, 'messages': state.messages, 'api_url': state.api_url,
2031
+ 'api_key': state.api_key, 'stdin_input': stdin_input,
1905
2032
  'model': state.npc.model if isinstance(state.npc, NPC) and state.npc.model else state.chat_model,
1906
2033
  'provider': state.npc.provider if isinstance(state.npc, NPC) and state.npc.provider else state.chat_provider,
1907
- 'npc': state.npc,
1908
-
1909
-
1910
- 'sprovider': state.search_provider,
1911
- 'emodel': state.embedding_model,
1912
- 'eprovider': state.embedding_provider,
1913
- 'igmodel': state.image_gen_model,
1914
- 'igprovider': state.image_gen_provider,
1915
- 'vgmodel': state.video_gen_model,
1916
- 'vgprovider': state.video_gen_provider,
1917
- 'vmodel': state.vision_model,
1918
- 'vprovider': state.vision_provider,
1919
- 'rmodel': state.reasoning_model,
1920
- 'rprovider': state.reasoning_provider,
2034
+ 'npc': state.npc, 'sprovider': state.search_provider, 'emodel': state.embedding_model,
2035
+ 'eprovider': state.embedding_provider, 'igmodel': state.image_gen_model, 'igprovider': state.image_gen_provider,
2036
+ 'vmodel': state.vision_model, 'vprovider': state.vision_provider, 'rmodel': state.reasoning_model,
2037
+ 'rprovider': state.reasoning_provider, 'state': state
1921
2038
  }
1922
-
1923
- if len(normalized_flags) > 0:
1924
- kwarg_part = 'with kwargs: \n -' + '\n -'.join(f'{key}={item}' for key, item in normalized_flags.items())
1925
- else:
1926
- kwarg_part = ''
1927
-
1928
- render_markdown(f'- Calling {command_name} handler {kwarg_part} ')
1929
-
1930
-
1931
- if 'model' in normalized_flags and 'provider' not in normalized_flags:
1932
- inferred_provider = lookup_provider(normalized_flags['model'])
1933
- if inferred_provider:
1934
- handler_kwargs['provider'] = inferred_provider
1935
- print(colored(f"Info: Inferred provider '{inferred_provider}' for model '{normalized_flags['model']}'.", "cyan"))
1936
-
1937
- if 'provider' in normalized_flags and 'model' not in normalized_flags:
1938
- current_provider = lookup_provider(handler_kwargs['model'])
1939
- if current_provider != normalized_flags['provider']:
1940
- prov = normalized_flags['provider']
1941
- print(f'Please specify a model for the provider: {prov}')
1942
-
1943
- handler_kwargs.update(normalized_flags)
1944
-
1945
2039
  try:
1946
- result_dict = handler(command=command,
1947
- **handler_kwargs)
1948
- if isinstance(result_dict, dict):
1949
- state.messages = result_dict.get("messages", state.messages)
1950
- return state, result_dict
1951
- else:
1952
- return state, result_dict
2040
+ result = handler(command=command, **handler_kwargs)
2041
+ if isinstance(result, dict):
2042
+ state.messages = result.get("messages", state.messages)
2043
+ return state, result
1953
2044
  except Exception as e:
1954
2045
  import traceback
1955
- print(f"Error executing slash command '{command_name}':", file=sys.stderr)
1956
2046
  traceback.print_exc()
1957
- return state, colored(f"Error executing slash command '{command_name}': {e}", "red")
1958
-
1959
-
1960
- active_npc = state.npc if isinstance(state.npc, NPC) else None
1961
- jinx_to_execute = None
1962
- executor = None
2047
+ return state, {"output": colored(f"Error executing slash command '{command_name}': {e}", "red"), "messages": state.messages}
1963
2048
 
1964
- if active_npc and hasattr(active_npc, 'jinxs_dict') and command_name in active_npc.jinxs_dict:
1965
- jinx_to_execute = active_npc.jinxs_dict[command_name]
1966
- executor = active_npc
1967
- elif state.team and hasattr(state.team, 'jinxs_dict') and command_name in state.team.jinxs_dict:
1968
- jinx_to_execute = state.team.jinxs_dict[command_name]
1969
- executor = state.team
1970
- if jinx_to_execute:
1971
- args = all_command_parts[1:]
1972
- try:
1973
-
1974
- input_values = {}
1975
- if hasattr(jinx_to_execute, 'inputs') and jinx_to_execute.inputs:
1976
- for i, input_name in enumerate(jinx_to_execute.inputs):
1977
- if i < len(args):
1978
- input_values[input_name] = args[i]
1979
-
1980
-
1981
- if isinstance(executor, NPC):
1982
- jinx_output = jinx_to_execute.execute(
1983
- input_values=input_values,
1984
- jinxs_dict=executor.jinxs_dict if hasattr(executor, 'jinxs_dict') else {},
1985
- npc=executor,
1986
- messages=state.messages
1987
- )
1988
- else:
1989
- jinx_output = jinx_to_execute.execute(
1990
- input_values=input_values,
1991
- jinxs_dict=executor.jinxs_dict if hasattr(executor, 'jinxs_dict') else {},
1992
- npc=active_npc or state.npc,
1993
- messages=state.messages
1994
- )
1995
- if isinstance(jinx_output, dict) and 'messages' in jinx_output:
1996
- state.messages = jinx_output['messages']
1997
- return state, str(jinx_output.get('output', jinx_output))
1998
- elif isinstance(jinx_output, dict):
1999
- return state, str(jinx_output.get('output', jinx_output))
2000
- else:
2001
- return state, jinx_output
2002
-
2003
- except Exception as e:
2004
- import traceback
2005
- print(f"Error executing jinx '{command_name}':", file=sys.stderr)
2006
- traceback.print_exc()
2007
- return state, colored(f"Error executing jinx '{command_name}': {e}", "red")
2049
+ # Fallback for switching NPC by name
2008
2050
  if state.team and command_name in state.team.npcs:
2009
- new_npc = state.team.npcs[command_name]
2010
- state.npc = new_npc
2011
- return state, f"Switched to NPC: {new_npc.name}"
2012
-
2013
- return state, colored(f"Unknown slash command, jinx, or NPC: {command_name}", "red")
2014
-
2051
+ state.npc = state.team.npcs[command_name]
2052
+ return state, {"output": f"Switched to NPC: {state.npc.name}", "messages": state.messages}
2015
2053
 
2054
+ return state, {"output": colored(f"Unknown slash command or NPC: {command_name}", "red"), "messages": state.messages}
2016
2055
 
2017
2056
 
2018
2057
  def process_pipeline_command(
@@ -2023,15 +2062,14 @@ def process_pipeline_command(
2023
2062
  review = False,
2024
2063
  router = None,
2025
2064
  ) -> Tuple[ShellState, Any]:
2026
- '''
2027
- Processing command
2028
- '''
2029
2065
 
2030
2066
  if not cmd_segment:
2031
2067
  return state, stdin_input
2032
2068
 
2033
2069
  available_models_all = get_locally_available_models(state.current_path)
2034
- available_models_all_list = [item for key, item in available_models_all.items()]
2070
+ available_models_all_list = [
2071
+ item for key, item in available_models_all.items()
2072
+ ]
2035
2073
 
2036
2074
  model_override, provider_override, cmd_cleaned = get_model_and_provider(
2037
2075
  cmd_segment, available_models_all_list
@@ -2040,18 +2078,33 @@ def process_pipeline_command(
2040
2078
  if not cmd_to_process:
2041
2079
  return state, stdin_input
2042
2080
 
2043
- npc_model = state.npc.model if isinstance(state.npc, NPC) and state.npc.model else None
2044
- npc_provider = state.npc.provider if isinstance(state.npc, NPC) and state.npc.provider else None
2081
+ npc_model = (
2082
+ state.npc.model
2083
+ if isinstance(state.npc, NPC) and state.npc.model
2084
+ else None
2085
+ )
2086
+ npc_provider = (
2087
+ state.npc.provider
2088
+ if isinstance(state.npc, NPC) and state.npc.provider
2089
+ else None
2090
+ )
2045
2091
 
2046
2092
  exec_model = model_override or npc_model or state.chat_model
2047
2093
  exec_provider = provider_override or npc_provider or state.chat_provider
2048
2094
 
2049
2095
  if cmd_to_process.startswith("/"):
2050
- return execute_slash_command(cmd_to_process,
2051
- stdin_input,
2052
- state,
2053
- stream_final,
2054
- router)
2096
+ with SpinnerContext(
2097
+ f"Routing to {cmd_to_process.split()[0]}",
2098
+ style="arrow"
2099
+ ):
2100
+ result = execute_slash_command(
2101
+ cmd_to_process,
2102
+ stdin_input,
2103
+ state,
2104
+ stream_final,
2105
+ router
2106
+ )
2107
+ return result
2055
2108
 
2056
2109
  cmd_parts = parse_command_safely(cmd_to_process)
2057
2110
  if not cmd_parts:
@@ -2064,6 +2117,7 @@ def process_pipeline_command(
2064
2117
 
2065
2118
  if command_name in interactive_commands:
2066
2119
  return handle_interactive_command(cmd_parts, state)
2120
+
2067
2121
  if command_name in TERMINAL_EDITORS:
2068
2122
  print(f"Starting interactive editor: {command_name}...")
2069
2123
  full_command_str = " ".join(cmd_parts)
@@ -2071,46 +2125,107 @@ def process_pipeline_command(
2071
2125
  return state, output
2072
2126
 
2073
2127
  if validate_bash_command(cmd_parts):
2074
- success, result = handle_bash_command(cmd_parts, cmd_to_process, stdin_input, state)
2128
+ with SpinnerContext(f"Executing {command_name}", style="line"):
2129
+ success, result = handle_bash_command(
2130
+ cmd_parts,
2131
+ cmd_to_process,
2132
+ stdin_input,
2133
+ state
2134
+ )
2135
+
2075
2136
  if success:
2076
2137
  return state, result
2077
2138
  else:
2078
- print(colored(f"Bash command failed: {result}. Asking LLM for a fix...", "yellow"), file=sys.stderr)
2079
- fixer_prompt = f"The command '{cmd_to_process}' failed with the error: '{result}'. Provide the correct command."
2080
- response = execute_llm_command(
2081
- fixer_prompt,
2082
- model=exec_model,
2083
- provider=exec_provider,
2084
- npc=state.npc,
2085
- stream=stream_final,
2086
- messages=state.messages
2139
+ print(
2140
+ colored(
2141
+ f"Command failed. Consulting {exec_model}...",
2142
+ "yellow"
2143
+ ),
2144
+ file=sys.stderr
2145
+ )
2146
+ fixer_prompt = (
2147
+ f"The command '{cmd_to_process}' failed with error: "
2148
+ f"'{result}'. Provide the correct command."
2087
2149
  )
2150
+
2151
+ with SpinnerContext(
2152
+ f"{exec_model} analyzing error",
2153
+ style="brain"
2154
+ ):
2155
+ response = execute_llm_command(
2156
+ fixer_prompt,
2157
+ model=exec_model,
2158
+ provider=exec_provider,
2159
+ npc=state.npc,
2160
+ stream=stream_final,
2161
+ messages=state.messages
2162
+ )
2163
+
2088
2164
  state.messages = response['messages']
2089
2165
  return state, response['response']
2090
2166
  else:
2091
- full_llm_cmd = f"{cmd_to_process} {stdin_input}" if stdin_input else cmd_to_process
2167
+ full_llm_cmd = (
2168
+ f"{cmd_to_process} {stdin_input}"
2169
+ if stdin_input
2170
+ else cmd_to_process
2171
+ )
2092
2172
  path_cmd = 'The current working directory is: ' + state.current_path
2093
- ls_files = 'Files in the current directory (full paths):\n' + "\n".join([os.path.join(state.current_path, f) for f in os.listdir(state.current_path)]) if os.path.exists(state.current_path) else 'No files found in the current directory.'
2094
- platform_info = f"Platform: {platform.system()} {platform.release()} ({platform.machine()})"
2173
+ ls_files = (
2174
+ 'Files in the current directory (full paths):\n' +
2175
+ "\n".join([
2176
+ os.path.join(state.current_path, f)
2177
+ for f in os.listdir(state.current_path)
2178
+ ])
2179
+ if os.path.exists(state.current_path)
2180
+ else 'No files found in the current directory.'
2181
+ )
2182
+ platform_info = (
2183
+ f"Platform: {platform.system()} {platform.release()} "
2184
+ f"({platform.machine()})"
2185
+ )
2095
2186
  info = path_cmd + '\n' + ls_files + '\n' + platform_info + '\n'
2096
2187
  state.messages.append({'role':'user', 'content':full_llm_cmd})
2097
2188
 
2098
-
2099
- llm_result = check_llm_command(
2100
- full_llm_cmd,
2101
- model=exec_model,
2102
- provider=exec_provider,
2103
- api_url=state.api_url,
2104
- api_key=state.api_key,
2105
- npc=state.npc,
2106
- team=state.team,
2107
- messages=state.messages,
2108
- images=state.attachments,
2109
- stream=stream_final,
2110
- context=info,
2189
+ npc_name = (
2190
+ state.npc.name
2191
+ if isinstance(state.npc, NPC)
2192
+ else "Assistant"
2111
2193
  )
2112
-
2113
2194
 
2195
+ with SpinnerContext(
2196
+ f"{npc_name} processing with {exec_model}",
2197
+ style="dots_pulse"
2198
+ ):
2199
+ # Build extra_globals for jinx execution
2200
+ application_globals_for_jinx = {
2201
+ "CommandHistory": CommandHistory,
2202
+ "load_kg_from_db": load_kg_from_db,
2203
+ "execute_rag_command": execute_rag_command,
2204
+ "execute_brainblast_command": execute_brainblast_command,
2205
+ "load_file_contents": load_file_contents,
2206
+ "search_web": search_web,
2207
+ "get_relevant_memories": get_relevant_memories,
2208
+ "search_kg_facts": search_kg_facts,
2209
+ 'state': state
2210
+ }
2211
+ current_module = sys.modules[__name__]
2212
+ for name, func in inspect.getmembers(current_module, inspect.isfunction):
2213
+ application_globals_for_jinx[name] = func
2214
+
2215
+ llm_result = check_llm_command(
2216
+ full_llm_cmd,
2217
+ model=exec_model,
2218
+ provider=exec_provider,
2219
+ api_url=state.api_url,
2220
+ api_key=state.api_key,
2221
+ npc=state.npc,
2222
+ team=state.team,
2223
+ messages=state.messages,
2224
+ images=state.attachments,
2225
+ stream=stream_final,
2226
+ context=info,
2227
+ extra_globals=application_globals_for_jinx # NOW PASS IT
2228
+ )
2114
2229
  if not review:
2115
2230
  if isinstance(llm_result, dict):
2116
2231
  state.messages = llm_result.get("messages", state.messages)
@@ -2118,7 +2233,6 @@ def process_pipeline_command(
2118
2233
  return state, output
2119
2234
  else:
2120
2235
  return state, llm_result
2121
-
2122
2236
  else:
2123
2237
  return review_and_iterate_command(
2124
2238
  original_command=full_llm_cmd,
@@ -2129,6 +2243,8 @@ def process_pipeline_command(
2129
2243
  stream_final=stream_final,
2130
2244
  info=info
2131
2245
  )
2246
+
2247
+
2132
2248
  def review_and_iterate_command(
2133
2249
  original_command: str,
2134
2250
  initial_result: Any,
@@ -2187,6 +2303,71 @@ def check_mode_switch(command:str , state: ShellState):
2187
2303
  return True, state
2188
2304
  return False, state
2189
2305
 
2306
+ import sys
2307
+ import time
2308
+ import threading
2309
+ from itertools import cycle
2310
+
2311
+ class SpinnerContext:
2312
+ def __init__(self, message="Processing", style="dots"):
2313
+ self.message = message
2314
+ self.spinning = False
2315
+ self.thread = None
2316
+
2317
+ styles = {
2318
+ "dots": ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"],
2319
+ "line": ["-", "\\", "|", "/"],
2320
+ "arrow": ["←", "↖", "↑", "↗", "→", "↘", "↓", "↙"],
2321
+ "box": ["◰", "◳", "◲", "◱"],
2322
+ "dots_pulse": ["⣾", "⣽", "⣻", "⢿", "⡿", "⣟", "⣯", "⣷"],
2323
+ "brain": ["🧠", "💭", "🤔", "💡"],
2324
+ }
2325
+ self.frames = cycle(styles.get(style, styles["dots"]))
2326
+
2327
+ def _spin(self):
2328
+ while self.spinning:
2329
+ sys.stdout.write(
2330
+ f"\r{colored(next(self.frames), 'cyan')} "
2331
+ f"{colored(self.message, 'yellow')}..."
2332
+ )
2333
+ sys.stdout.flush()
2334
+ time.sleep(0.1)
2335
+
2336
+ def __enter__(self):
2337
+ self.spinning = True
2338
+ self.thread = threading.Thread(target=self._spin)
2339
+ self.thread.start()
2340
+ return self
2341
+
2342
+ def __exit__(self, exc_type, exc_val, exc_tb):
2343
+ self.spinning = False
2344
+ if self.thread:
2345
+ self.thread.join()
2346
+ sys.stdout.write("\r" + " " * 80 + "\r")
2347
+ sys.stdout.flush()
2348
+
2349
+ def show_thinking_animation(message="Thinking", duration=None):
2350
+ frames = ["🤔", "💭", "🧠", "💡", "✨"]
2351
+ colors = ["cyan", "blue", "magenta", "yellow", "green"]
2352
+
2353
+ start = time.time()
2354
+ i = 0
2355
+ while duration is None or (time.time() - start) < duration:
2356
+ frame = frames[i % len(frames)]
2357
+ color = colors[i % len(colors)]
2358
+ sys.stdout.write(
2359
+ f"\r{colored(frame, color)} "
2360
+ f"{colored(message, 'yellow')}..."
2361
+ )
2362
+ sys.stdout.flush()
2363
+ time.sleep(0.3)
2364
+ i += 1
2365
+ if duration and (time.time() - start) >= duration:
2366
+ break
2367
+
2368
+ sys.stdout.write("\r" + " " * 80 + "\r")
2369
+ sys.stdout.flush()
2370
+
2190
2371
  def execute_command(
2191
2372
  command: str,
2192
2373
  state: ShellState,
@@ -2200,29 +2381,51 @@ def execute_command(
2200
2381
 
2201
2382
  mode_change, state = check_mode_switch(command, state)
2202
2383
  if mode_change:
2384
+ print(colored(f"⚡ Switched to {state.current_mode} mode", "green"))
2203
2385
  return state, 'Mode changed.'
2204
2386
 
2205
- npc_name = state.npc.name if isinstance(state.npc, NPC) else "__none__"
2387
+ npc_name = (
2388
+ state.npc.name
2389
+ if isinstance(state.npc, NPC)
2390
+ else "__none__"
2391
+ )
2206
2392
  team_name = state.team.name if state.team else "__none__"
2207
2393
 
2208
-
2209
2394
  original_command_for_embedding = command
2210
2395
  commands = split_by_pipes(command)
2211
2396
 
2212
2397
  stdin_for_next = None
2213
2398
  final_output = None
2214
2399
  current_state = state
2215
- npc_model = state.npc.model if isinstance(state.npc, NPC) and state.npc.model else None
2216
- npc_provider = state.npc.provider if isinstance(state.npc, NPC) and state.npc.provider else None
2400
+ npc_model = (
2401
+ state.npc.model
2402
+ if isinstance(state.npc, NPC) and state.npc.model
2403
+ else None
2404
+ )
2405
+ npc_provider = (
2406
+ state.npc.provider
2407
+ if isinstance(state.npc, NPC) and state.npc.provider
2408
+ else None
2409
+ )
2217
2410
  active_model = npc_model or state.chat_model
2218
2411
  active_provider = npc_provider or state.chat_provider
2412
+
2219
2413
  if state.current_mode == 'agent':
2220
-
2221
-
2414
+ total_stages = len(commands)
2415
+
2222
2416
  for i, cmd_segment in enumerate(commands):
2223
- render_markdown(f'- Executing command {i+1}/{len(commands)}')
2417
+ stage_num = i + 1
2418
+ stage_emoji = ["🎯", "⚙️", "🔧", "✨", "🚀"][i % 5]
2419
+
2420
+ print(colored(
2421
+ f"\n{stage_emoji} Pipeline Stage {stage_num}/{total_stages}",
2422
+ "cyan",
2423
+ attrs=["bold"]
2424
+ ))
2425
+
2224
2426
  is_last_command = (i == len(commands) - 1)
2225
2427
  stream_this_segment = state.stream_output and not is_last_command
2428
+
2226
2429
  try:
2227
2430
  current_state, output = process_pipeline_command(
2228
2431
  cmd_segment.strip(),
@@ -2230,19 +2433,26 @@ def execute_command(
2230
2433
  current_state,
2231
2434
  stream_final=stream_this_segment,
2232
2435
  review=review,
2233
- router= router
2436
+ router=router
2234
2437
  )
2438
+
2235
2439
  if is_last_command:
2440
+ print(colored("✅ Pipeline complete", "green"))
2236
2441
  return current_state, output
2442
+
2237
2443
  if isinstance(output, str):
2238
2444
  stdin_for_next = output
2239
2445
  elif not isinstance(output, str):
2240
2446
  try:
2241
2447
  if stream_this_segment:
2242
- full_stream_output = print_and_process_stream_with_markdown(output,
2243
- state.npc.model,
2244
- state.npc.provider,
2245
- show=True)
2448
+ full_stream_output = (
2449
+ print_and_process_stream_with_markdown(
2450
+ output,
2451
+ state.npc.model,
2452
+ state.npc.provider,
2453
+ show=True
2454
+ )
2455
+ )
2246
2456
  stdin_for_next = full_stream_output
2247
2457
  if is_last_command:
2248
2458
  final_output = full_stream_output
@@ -2251,24 +2461,40 @@ def execute_command(
2251
2461
  try:
2252
2462
  stdin_for_next = str(output)
2253
2463
  except Exception:
2254
- print(f"Warning: Cannot convert output to string for piping: {type(output)}", file=sys.stderr)
2464
+ print(
2465
+ f"Warning: Cannot convert output to "
2466
+ f"string for piping: {type(output)}",
2467
+ file=sys.stderr
2468
+ )
2255
2469
  stdin_for_next = None
2256
2470
  else:
2257
2471
  stdin_for_next = None
2472
+
2473
+ print(colored(
2474
+ f" → Passing to stage {stage_num + 1}",
2475
+ "blue"
2476
+ ))
2477
+
2258
2478
  except Exception as pipeline_error:
2259
2479
  import traceback
2260
2480
  traceback.print_exc()
2261
- error_msg = colored(f"Error in pipeline stage {i+1} ('{cmd_segment[:50]}...'): {pipeline_error}", "red")
2481
+ error_msg = colored(
2482
+ f"❌ Error in stage {stage_num} "
2483
+ f"('{cmd_segment[:50]}...'): {pipeline_error}",
2484
+ "red"
2485
+ )
2262
2486
  return current_state, error_msg
2263
2487
 
2264
2488
  if final_output is not None and isinstance(final_output,str):
2265
- store_command_embeddings(original_command_for_embedding, final_output, current_state)
2489
+ store_command_embeddings(
2490
+ original_command_for_embedding,
2491
+ final_output,
2492
+ current_state
2493
+ )
2266
2494
 
2267
2495
  return current_state, final_output
2268
2496
 
2269
-
2270
2497
  elif state.current_mode == 'chat':
2271
-
2272
2498
  cmd_parts = parse_command_safely(command)
2273
2499
  is_probably_bash = (
2274
2500
  cmd_parts
@@ -2279,6 +2505,7 @@ def execute_command(
2279
2505
  or command.strip().startswith("/")
2280
2506
  )
2281
2507
  )
2508
+
2282
2509
  if is_probably_bash:
2283
2510
  try:
2284
2511
  command_name = cmd_parts[0]
@@ -2288,38 +2515,56 @@ def execute_command(
2288
2515
  return handle_cd_command(cmd_parts, state)
2289
2516
  else:
2290
2517
  try:
2291
- bash_state, bash_output = handle_bash_command(cmd_parts, command, None, state)
2518
+ bash_state, bash_output = handle_bash_command(
2519
+ cmd_parts,
2520
+ command,
2521
+ None,
2522
+ state
2523
+ )
2292
2524
  return state, bash_output
2293
2525
  except Exception as bash_err:
2294
- return state, colored(f"Bash execution failed: {bash_err}", "red")
2526
+ return state, colored(
2527
+ f"Bash execution failed: {bash_err}",
2528
+ "red"
2529
+ )
2295
2530
  except Exception:
2296
2531
  pass
2297
2532
 
2298
-
2299
- response = get_llm_response(
2300
- command,
2301
- model=active_model,
2302
- provider=active_provider,
2303
- npc=state.npc,
2304
- stream=state.stream_output,
2305
- messages=state.messages
2306
- )
2533
+ with SpinnerContext(
2534
+ f"Chatting with {active_model}",
2535
+ style="brain"
2536
+ ):
2537
+ response = get_llm_response(
2538
+ command,
2539
+ model=active_model,
2540
+ provider=active_provider,
2541
+ npc=state.npc,
2542
+ stream=state.stream_output,
2543
+ messages=state.messages
2544
+ )
2545
+
2307
2546
  state.messages = response['messages']
2308
2547
  return state, response['response']
2309
2548
 
2310
2549
  elif state.current_mode == 'cmd':
2311
-
2312
- response = execute_llm_command(command,
2313
- model=active_model,
2314
- provider=active_provider,
2315
- npc = state.npc,
2316
- stream = state.stream_output,
2317
- messages = state.messages)
2550
+ with SpinnerContext(
2551
+ f"Executing with {active_model}",
2552
+ style="dots_pulse"
2553
+ ):
2554
+ response = execute_llm_command(
2555
+ command,
2556
+ model=active_model,
2557
+ provider=active_provider,
2558
+ npc=state.npc,
2559
+ stream=state.stream_output,
2560
+ messages=state.messages
2561
+ )
2562
+
2318
2563
  state.messages = response['messages']
2319
2564
  return state, response['response']
2320
2565
 
2321
- def setup_shell() -> Tuple[CommandHistory, Team, Optional[NPC]]:
2322
2566
 
2567
+ def setup_shell() -> Tuple[CommandHistory, Team, Optional[NPC]]:
2323
2568
  setup_npcsh_config()
2324
2569
 
2325
2570
  db_path = os.getenv("NPCSH_DB_PATH", HISTORY_DB_DEFAULT_PATH)
@@ -2327,14 +2572,11 @@ def setup_shell() -> Tuple[CommandHistory, Team, Optional[NPC]]:
2327
2572
  os.makedirs(os.path.dirname(db_path), exist_ok=True)
2328
2573
  command_history = CommandHistory(db_path)
2329
2574
 
2330
-
2331
2575
  if not is_npcsh_initialized():
2332
2576
  print("Initializing NPCSH...")
2333
2577
  initialize_base_npcs_if_needed(db_path)
2334
2578
  print("NPCSH initialization complete. Restart or source ~/.npcshrc.")
2335
2579
 
2336
-
2337
-
2338
2580
  try:
2339
2581
  history_file = setup_readline()
2340
2582
  atexit.register(save_readline_history)
@@ -2397,7 +2639,6 @@ def setup_shell() -> Tuple[CommandHistory, Team, Optional[NPC]]:
2397
2639
  team_dir = global_team_path
2398
2640
  default_forenpc_name = "sibiji"
2399
2641
 
2400
-
2401
2642
  team_ctx = {}
2402
2643
  team_ctx_path = get_team_ctx_path(team_dir)
2403
2644
  if team_ctx_path:
@@ -2410,34 +2651,12 @@ def setup_shell() -> Tuple[CommandHistory, Team, Optional[NPC]]:
2410
2651
 
2411
2652
  print('forenpc_name:', forenpc_name)
2412
2653
 
2413
- if team_ctx.get("use_global_jinxs", False):
2414
- jinxs_dir = os.path.expanduser("~/.npcsh/npc_team/jinxs")
2415
- else:
2416
- jinxs_dir = os.path.join(team_dir, "jinxs")
2417
-
2418
- jinxs_list = load_jinxs_from_directory(jinxs_dir)
2419
- jinxs_dict = {jinx.jinx_name: jinx for jinx in jinxs_list}
2420
-
2421
- forenpc_obj = None
2422
2654
  forenpc_path = os.path.join(team_dir, f"{forenpc_name}.npc")
2423
-
2424
2655
  print('forenpc_path:', forenpc_path)
2425
2656
 
2426
- if os.path.exists(forenpc_path):
2427
- forenpc_obj = NPC(file = forenpc_path,
2428
- jinxs=jinxs_list,
2429
- db_conn=command_history.engine)
2430
- if forenpc_obj.model is None:
2431
- forenpc_obj.model= team_ctx.get("model", initial_state.chat_model)
2432
- if forenpc_obj.provider is None:
2433
- forenpc_obj.provider=team_ctx.get('provider', initial_state.chat_provider)
2434
-
2435
- else:
2436
- print(f"Warning: Forenpc file '{forenpc_name}.npc' not found in {team_dir}.")
2437
-
2438
- team = Team(team_path=team_dir,
2439
- forenpc=forenpc_obj,
2440
- jinxs=jinxs_dict)
2657
+ team = Team(team_path=team_dir, db_conn=command_history.engine)
2658
+
2659
+ forenpc_obj = team.forenpc if hasattr(team, 'forenpc') and team.forenpc else None
2441
2660
 
2442
2661
  for npc_name, npc_obj in team.npcs.items():
2443
2662
  if not npc_obj.model:
@@ -2445,12 +2664,12 @@ def setup_shell() -> Tuple[CommandHistory, Team, Optional[NPC]]:
2445
2664
  if not npc_obj.provider:
2446
2665
  npc_obj.provider = initial_state.chat_provider
2447
2666
 
2448
-
2449
2667
  if team.forenpc and isinstance(team.forenpc, NPC):
2450
2668
  if not team.forenpc.model:
2451
2669
  team.forenpc.model = initial_state.chat_model
2452
2670
  if not team.forenpc.provider:
2453
2671
  team.forenpc.provider = initial_state.chat_provider
2672
+
2454
2673
  team_name_from_ctx = team_ctx.get("name")
2455
2674
  if team_name_from_ctx:
2456
2675
  team.name = team_name_from_ctx
@@ -2464,145 +2683,20 @@ def setup_shell() -> Tuple[CommandHistory, Team, Optional[NPC]]:
2464
2683
  else:
2465
2684
  team.name = "npcsh"
2466
2685
 
2467
-
2468
2686
  return command_history, team, forenpc_obj
2469
2687
 
2470
-
2471
-
2472
-
2473
- from npcpy.memory.memory_processor import memory_approval_ui
2474
- from npcpy.ft.memory_trainer import MemoryTrainer
2475
- from npcpy.llm_funcs import get_facts
2476
-
2477
- def get_relevant_memories(
2478
- command_history: CommandHistory,
2479
- npc_name: str,
2480
- team_name: str,
2481
- path: str,
2482
- query: Optional[str] = None,
2483
- max_memories: int = 10,
2484
- state: Optional[ShellState] = None
2485
- ) -> List[Dict]:
2486
-
2487
- engine = command_history.engine
2488
-
2489
- all_memories = command_history.get_memories_for_scope(
2490
- npc=npc_name,
2491
- team=team_name,
2492
- directory_path=path,
2493
- status='human-approved'
2494
- )
2495
-
2496
- if not all_memories:
2497
- return []
2498
-
2499
- if len(all_memories) <= max_memories and not query:
2500
- return all_memories
2501
-
2502
- if query:
2503
- query_lower = query.lower()
2504
- keyword_matches = [
2505
- m for m in all_memories
2506
- if query_lower in (m.get('final_memory') or m.get('initial_memory') or '').lower()
2507
- ]
2508
-
2509
- if keyword_matches:
2510
- return keyword_matches[:max_memories]
2511
-
2512
- if state and state.embedding_model and state.embedding_provider:
2513
- try:
2514
- from npcpy.gen.embeddings import get_embeddings
2515
-
2516
- search_text = query if query else "recent context"
2517
- query_embedding = get_embeddings(
2518
- [search_text],
2519
- state.embedding_model,
2520
- state.embedding_provider
2521
- )[0]
2522
-
2523
- memory_texts = [
2524
- m.get('final_memory', '') for m in all_memories
2525
- ]
2526
- memory_embeddings = get_embeddings(
2527
- memory_texts,
2528
- state.embedding_model,
2529
- state.embedding_provider
2530
- )
2531
-
2532
- import numpy as np
2533
- similarities = []
2534
- for mem_emb in memory_embeddings:
2535
- similarity = np.dot(query_embedding, mem_emb) / (
2536
- np.linalg.norm(query_embedding) *
2537
- np.linalg.norm(mem_emb)
2538
- )
2539
- similarities.append(similarity)
2540
-
2541
- sorted_indices = np.argsort(similarities)[::-1]
2542
- return [all_memories[i] for i in sorted_indices[:max_memories]]
2543
-
2544
- except Exception as e:
2545
- print(colored(
2546
- f"RAG search failed, using recent: {e}",
2547
- "yellow"
2548
- ))
2688
+ def initialize_router_with_jinxs(team, router):
2689
+ """Load global and team Jinxs into router"""
2690
+ global_jinxs_dir = os.path.expanduser("~/.npcsh/npc_team/jinxs")
2691
+ router.load_jinx_routes(global_jinxs_dir)
2549
2692
 
2550
- return all_memories[-max_memories:]
2551
-
2552
-
2553
- def search_kg_facts(
2554
- self,
2555
- npc: str,
2556
- team: str,
2557
- directory_path: str,
2558
- query: str
2559
- ) -> List[Dict]:
2560
-
2561
- kg = load_kg_from_db(
2562
- self.engine,
2563
- team,
2564
- npc,
2565
- directory_path
2566
- )
2567
-
2568
- if not kg or 'facts' not in kg:
2569
- return []
2570
-
2571
- query_lower = query.lower()
2572
- matching_facts = []
2573
-
2574
- for fact in kg['facts']:
2575
- statement = fact.get('statement', '').lower()
2576
- if query_lower in statement:
2577
- matching_facts.append(fact)
2578
-
2579
- return matching_facts
2580
-
2581
- def format_memory_context(memory_examples):
2582
- if not memory_examples:
2583
- return ""
2693
+ if team and team.team_path:
2694
+ team_jinxs_dir = os.path.join(team.team_path, "jinxs")
2695
+ if os.path.exists(team_jinxs_dir):
2696
+ router.load_jinx_routes(team_jinxs_dir)
2584
2697
 
2585
- context_parts = []
2586
-
2587
- approved_examples = memory_examples.get("approved", [])
2588
- rejected_examples = memory_examples.get("rejected", [])
2589
-
2590
- if approved_examples:
2591
- context_parts.append("EXAMPLES OF GOOD MEMORIES:")
2592
- for ex in approved_examples[:5]:
2593
- final = ex.get("final_memory") or ex.get("initial_memory")
2594
- context_parts.append(f"- {final}")
2595
-
2596
- if rejected_examples:
2597
- context_parts.append("\nEXAMPLES OF POOR MEMORIES TO AVOID:")
2598
- for ex in rejected_examples[:3]:
2599
- context_parts.append(f"- {ex.get('initial_memory')}")
2600
-
2601
- if context_parts:
2602
- context_parts.append("\nLearn from these examples to generate similar high-quality memories.")
2603
- return "\n".join(context_parts)
2604
-
2605
- return ""
2698
+ return router
2699
+
2606
2700
 
2607
2701
  def process_memory_approvals(command_history, memory_queue):
2608
2702
  pending_memories = memory_queue.get_approval_batch(max_items=5)
@@ -2688,13 +2782,22 @@ def process_result(
2688
2782
  result_state.attachments = None
2689
2783
 
2690
2784
  final_output_str = None
2691
- output_content = output.get('output') if isinstance(output, dict) else output
2692
- model_for_stream = output.get('model', active_npc.model) if isinstance(output, dict) else active_npc.model
2693
- provider_for_stream = output.get('provider', active_npc.provider) if isinstance(output, dict) else active_npc.provider
2785
+
2786
+ if isinstance(output, dict):
2787
+ output_content = output.get('output')
2788
+ model_for_stream = output.get('model', active_npc.model)
2789
+ provider_for_stream = output.get('provider', active_npc.provider)
2790
+ else:
2791
+ output_content = output
2792
+ model_for_stream = active_npc.model
2793
+ provider_for_stream = active_npc.provider
2694
2794
 
2695
2795
  print('\n')
2696
2796
  if user_input == '/help':
2697
- render_markdown(output.get('output'))
2797
+ if isinstance(output_content, str):
2798
+ render_markdown(output_content)
2799
+ else:
2800
+ render_markdown(str(output_content))
2698
2801
  elif result_state.stream_output:
2699
2802
  final_output_str = print_and_process_stream_with_markdown(
2700
2803
  output_content,