npcsh 1.0.37__tar.gz → 1.1.2__tar.gz

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 (54) hide show
  1. {npcsh-1.0.37 → npcsh-1.1.2}/PKG-INFO +1 -1
  2. {npcsh-1.0.37 → npcsh-1.1.2}/npcsh/_state.py +310 -100
  3. {npcsh-1.0.37 → npcsh-1.1.2}/npcsh/corca.py +16 -15
  4. {npcsh-1.0.37 → npcsh-1.1.2}/npcsh/npc_team/corca.npc +1 -2
  5. npcsh-1.1.2/npcsh/npc_team/jinxs/kg_search.jinx +43 -0
  6. npcsh-1.1.2/npcsh/npc_team/jinxs/memory_search.jinx +36 -0
  7. {npcsh-1.0.37 → npcsh-1.1.2}/npcsh/npcsh.py +14 -5
  8. {npcsh-1.0.37 → npcsh-1.1.2}/npcsh.egg-info/PKG-INFO +1 -1
  9. {npcsh-1.0.37 → npcsh-1.1.2}/npcsh.egg-info/SOURCES.txt +2 -0
  10. {npcsh-1.0.37 → npcsh-1.1.2}/setup.py +1 -1
  11. {npcsh-1.0.37 → npcsh-1.1.2}/LICENSE +0 -0
  12. {npcsh-1.0.37 → npcsh-1.1.2}/README.md +0 -0
  13. {npcsh-1.0.37 → npcsh-1.1.2}/npcsh/__init__.py +0 -0
  14. {npcsh-1.0.37 → npcsh-1.1.2}/npcsh/alicanto.py +0 -0
  15. {npcsh-1.0.37 → npcsh-1.1.2}/npcsh/guac.py +0 -0
  16. {npcsh-1.0.37 → npcsh-1.1.2}/npcsh/mcp_helpers.py +0 -0
  17. {npcsh-1.0.37 → npcsh-1.1.2}/npcsh/mcp_server.py +0 -0
  18. {npcsh-1.0.37 → npcsh-1.1.2}/npcsh/npc.py +0 -0
  19. {npcsh-1.0.37 → npcsh-1.1.2}/npcsh/npc_team/alicanto.npc +0 -0
  20. {npcsh-1.0.37 → npcsh-1.1.2}/npcsh/npc_team/alicanto.png +0 -0
  21. {npcsh-1.0.37 → npcsh-1.1.2}/npcsh/npc_team/corca.png +0 -0
  22. {npcsh-1.0.37 → npcsh-1.1.2}/npcsh/npc_team/foreman.npc +0 -0
  23. {npcsh-1.0.37 → npcsh-1.1.2}/npcsh/npc_team/frederic.npc +0 -0
  24. {npcsh-1.0.37 → npcsh-1.1.2}/npcsh/npc_team/frederic4.png +0 -0
  25. {npcsh-1.0.37 → npcsh-1.1.2}/npcsh/npc_team/guac.png +0 -0
  26. {npcsh-1.0.37 → npcsh-1.1.2}/npcsh/npc_team/jinxs/bash_executer.jinx +0 -0
  27. {npcsh-1.0.37 → npcsh-1.1.2}/npcsh/npc_team/jinxs/edit_file.jinx +0 -0
  28. {npcsh-1.0.37 → npcsh-1.1.2}/npcsh/npc_team/jinxs/image_generation.jinx +0 -0
  29. {npcsh-1.0.37 → npcsh-1.1.2}/npcsh/npc_team/jinxs/internet_search.jinx +0 -0
  30. {npcsh-1.0.37 → npcsh-1.1.2}/npcsh/npc_team/jinxs/python_executor.jinx +0 -0
  31. {npcsh-1.0.37 → npcsh-1.1.2}/npcsh/npc_team/jinxs/screen_cap.jinx +0 -0
  32. {npcsh-1.0.37 → npcsh-1.1.2}/npcsh/npc_team/kadiefa.npc +0 -0
  33. {npcsh-1.0.37 → npcsh-1.1.2}/npcsh/npc_team/kadiefa.png +0 -0
  34. {npcsh-1.0.37 → npcsh-1.1.2}/npcsh/npc_team/npcsh.ctx +0 -0
  35. {npcsh-1.0.37 → npcsh-1.1.2}/npcsh/npc_team/npcsh_sibiji.png +0 -0
  36. {npcsh-1.0.37 → npcsh-1.1.2}/npcsh/npc_team/plonk.npc +0 -0
  37. {npcsh-1.0.37 → npcsh-1.1.2}/npcsh/npc_team/plonk.png +0 -0
  38. {npcsh-1.0.37 → npcsh-1.1.2}/npcsh/npc_team/plonkjr.npc +0 -0
  39. {npcsh-1.0.37 → npcsh-1.1.2}/npcsh/npc_team/plonkjr.png +0 -0
  40. {npcsh-1.0.37 → npcsh-1.1.2}/npcsh/npc_team/sibiji.npc +0 -0
  41. {npcsh-1.0.37 → npcsh-1.1.2}/npcsh/npc_team/sibiji.png +0 -0
  42. {npcsh-1.0.37 → npcsh-1.1.2}/npcsh/npc_team/spool.png +0 -0
  43. {npcsh-1.0.37 → npcsh-1.1.2}/npcsh/npc_team/yap.png +0 -0
  44. {npcsh-1.0.37 → npcsh-1.1.2}/npcsh/plonk.py +0 -0
  45. {npcsh-1.0.37 → npcsh-1.1.2}/npcsh/pti.py +0 -0
  46. {npcsh-1.0.37 → npcsh-1.1.2}/npcsh/routes.py +0 -0
  47. {npcsh-1.0.37 → npcsh-1.1.2}/npcsh/spool.py +0 -0
  48. {npcsh-1.0.37 → npcsh-1.1.2}/npcsh/wander.py +0 -0
  49. {npcsh-1.0.37 → npcsh-1.1.2}/npcsh/yap.py +0 -0
  50. {npcsh-1.0.37 → npcsh-1.1.2}/npcsh.egg-info/dependency_links.txt +0 -0
  51. {npcsh-1.0.37 → npcsh-1.1.2}/npcsh.egg-info/entry_points.txt +0 -0
  52. {npcsh-1.0.37 → npcsh-1.1.2}/npcsh.egg-info/requires.txt +0 -0
  53. {npcsh-1.0.37 → npcsh-1.1.2}/npcsh.egg-info/top_level.txt +0 -0
  54. {npcsh-1.0.37 → npcsh-1.1.2}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: npcsh
3
- Version: 1.0.37
3
+ Version: 1.1.2
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
@@ -2193,16 +2193,51 @@ def execute_command(
2193
2193
  state: ShellState,
2194
2194
  review = True,
2195
2195
  router = None,
2196
+ command_history = None,
2196
2197
  ) -> Tuple[ShellState, Any]:
2197
2198
 
2198
2199
  if not command.strip():
2199
2200
  return state, ""
2201
+
2200
2202
  mode_change, state = check_mode_switch(command, state)
2201
2203
  if mode_change:
2202
2204
  return state, 'Mode changed.'
2203
2205
 
2206
+ npc_name = state.npc.name if isinstance(state.npc, NPC) else "__none__"
2207
+ team_name = state.team.name if state.team else "__none__"
2208
+
2209
+ if command_history:
2210
+ relevant_memories = get_relevant_memories(
2211
+ command_history=command_history,
2212
+ npc_name=npc_name,
2213
+ team_name=team_name,
2214
+ path=state.current_path,
2215
+ query=command,
2216
+ max_memories=5,
2217
+ state=state
2218
+ )
2219
+ print('Memory jogged...')
2220
+ print(relevant_memories)
2221
+
2222
+ if relevant_memories:
2223
+ memory_context = "\n".join([
2224
+ f"- {m.get('final_memory', '')}"
2225
+ for m in relevant_memories
2226
+ ])
2227
+ memory_msg = {
2228
+ "role": "system",
2229
+ "content": f"Relevant memories:\n{memory_context}"
2230
+ }
2231
+ if not state.messages or \
2232
+ state.messages[0].get("role") != "system":
2233
+ state.messages.insert(0, memory_msg)
2234
+ else:
2235
+ state.messages[0]["content"] += \
2236
+ f"\n\n{memory_msg['content']}"
2237
+
2204
2238
  original_command_for_embedding = command
2205
2239
  commands = split_by_pipes(command)
2240
+
2206
2241
  stdin_for_next = None
2207
2242
  final_output = None
2208
2243
  current_state = state
@@ -2448,10 +2483,16 @@ def setup_shell() -> Tuple[CommandHistory, Team, Optional[NPC]]:
2448
2483
  team_name_from_ctx = team_ctx.get("name")
2449
2484
  if team_name_from_ctx:
2450
2485
  team.name = team_name_from_ctx
2451
- elif team_dir and os.path.basename(team_dir) != 'npc_team':
2452
- team.name = os.path.basename(team_dir)
2486
+ elif team_dir:
2487
+ normalized_dir = os.path.normpath(team_dir)
2488
+ basename = os.path.basename(normalized_dir)
2489
+ if basename and basename != 'npc_team':
2490
+ team.name = basename
2491
+ else:
2492
+ team.name = "npcsh"
2453
2493
  else:
2454
- team.name = "global_team"
2494
+ team.name = "npcsh"
2495
+
2455
2496
 
2456
2497
  return command_history, team, forenpc_obj
2457
2498
 
@@ -2462,6 +2503,109 @@ from npcpy.memory.memory_processor import memory_approval_ui
2462
2503
  from npcpy.ft.memory_trainer import MemoryTrainer
2463
2504
  from npcpy.llm_funcs import get_facts
2464
2505
 
2506
+ def get_relevant_memories(
2507
+ command_history: CommandHistory,
2508
+ npc_name: str,
2509
+ team_name: str,
2510
+ path: str,
2511
+ query: Optional[str] = None,
2512
+ max_memories: int = 10,
2513
+ state: Optional[ShellState] = None
2514
+ ) -> List[Dict]:
2515
+
2516
+ engine = command_history.engine
2517
+
2518
+ all_memories = command_history.get_memories_for_scope(
2519
+ npc=npc_name,
2520
+ team=team_name,
2521
+ directory_path=path,
2522
+ status='human-approved'
2523
+ )
2524
+
2525
+ if not all_memories:
2526
+ return []
2527
+
2528
+ if len(all_memories) <= max_memories and not query:
2529
+ return all_memories
2530
+
2531
+ if query:
2532
+ query_lower = query.lower()
2533
+ keyword_matches = [
2534
+ m for m in all_memories
2535
+ if query_lower in (m.get('final_memory') or m.get('initial_memory') or '').lower()
2536
+ ]
2537
+
2538
+ if keyword_matches:
2539
+ return keyword_matches[:max_memories]
2540
+
2541
+ if state and state.embedding_model and state.embedding_provider:
2542
+ try:
2543
+ from npcpy.gen.embeddings import get_embeddings
2544
+
2545
+ search_text = query if query else "recent context"
2546
+ query_embedding = get_embeddings(
2547
+ [search_text],
2548
+ state.embedding_model,
2549
+ state.embedding_provider
2550
+ )[0]
2551
+
2552
+ memory_texts = [
2553
+ m.get('final_memory', '') for m in all_memories
2554
+ ]
2555
+ memory_embeddings = get_embeddings(
2556
+ memory_texts,
2557
+ state.embedding_model,
2558
+ state.embedding_provider
2559
+ )
2560
+
2561
+ import numpy as np
2562
+ similarities = []
2563
+ for mem_emb in memory_embeddings:
2564
+ similarity = np.dot(query_embedding, mem_emb) / (
2565
+ np.linalg.norm(query_embedding) *
2566
+ np.linalg.norm(mem_emb)
2567
+ )
2568
+ similarities.append(similarity)
2569
+
2570
+ sorted_indices = np.argsort(similarities)[::-1]
2571
+ return [all_memories[i] for i in sorted_indices[:max_memories]]
2572
+
2573
+ except Exception as e:
2574
+ print(colored(
2575
+ f"RAG search failed, using recent: {e}",
2576
+ "yellow"
2577
+ ))
2578
+
2579
+ return all_memories[-max_memories:]
2580
+
2581
+
2582
+ def search_kg_facts(
2583
+ self,
2584
+ npc: str,
2585
+ team: str,
2586
+ directory_path: str,
2587
+ query: str
2588
+ ) -> List[Dict]:
2589
+
2590
+ kg = load_kg_from_db(
2591
+ self.engine,
2592
+ team,
2593
+ npc,
2594
+ directory_path
2595
+ )
2596
+
2597
+ if not kg or 'facts' not in kg:
2598
+ return []
2599
+
2600
+ query_lower = query.lower()
2601
+ matching_facts = []
2602
+
2603
+ for fact in kg['facts']:
2604
+ statement = fact.get('statement', '').lower()
2605
+ if query_lower in statement:
2606
+ matching_facts.append(fact)
2607
+
2608
+ return matching_facts
2465
2609
 
2466
2610
  def format_memory_context(memory_examples):
2467
2611
  if not memory_examples:
@@ -2548,8 +2692,8 @@ def process_result(
2548
2692
  output: Any,
2549
2693
  command_history: CommandHistory,
2550
2694
  ):
2551
- team_name = result_state.team.name if result_state.team else "__none__"
2552
- npc_name = result_state.npc.name if isinstance(result_state.npc, NPC) else "__none__"
2695
+ team_name = result_state.team.name if result_state.team else "npcsh"
2696
+ npc_name = result_state.npc.name if isinstance(result_state.npc, NPC) else "npcsh"
2553
2697
 
2554
2698
  active_npc = result_state.npc if isinstance(result_state.npc, NPC) else NPC(
2555
2699
  name="default",
@@ -2611,106 +2755,155 @@ def process_result(
2611
2755
  team=team_name,
2612
2756
  )
2613
2757
 
2614
- conversation_turn_text = f"User: {user_input}\nAssistant: {final_output_str}"
2615
- engine = command_history.engine
2758
+ result_state.turn_count += 1
2616
2759
 
2617
- memory_examples = command_history.get_memory_examples_for_context(
2618
- npc=npc_name,
2619
- team=team_name,
2620
- directory_path=result_state.current_path
2621
- )
2622
-
2623
- memory_context = format_memory_context(memory_examples)
2624
-
2625
- approved_facts = []
2626
- try:
2627
- facts = get_facts(
2628
- conversation_turn_text,
2629
- model=active_npc.model,
2630
- provider=active_npc.provider,
2631
- npc=active_npc,
2632
- context=memory_context
2760
+ if result_state.turn_count % 10 == 0:
2761
+ approved_facts = []
2762
+
2763
+ conversation_turn_text = f"User: {user_input}\nAssistant: {final_output_str}"
2764
+ engine = command_history.engine
2765
+
2766
+ memory_examples = command_history.get_memory_examples_for_context(
2767
+ npc=npc_name,
2768
+ team=team_name,
2769
+ directory_path=result_state.current_path
2633
2770
  )
2634
2771
 
2635
- if facts:
2636
- memories_for_approval = []
2637
- for i, fact in enumerate(facts):
2638
- memories_for_approval.append({
2639
- "memory_id": f"temp_{i}",
2640
- "content": fact['statement'],
2641
- "context": f"Type: {fact.get('type', 'unknown')}, Source: {fact.get('source_text', '')}",
2642
- "npc": npc_name,
2643
- "fact_data": fact
2644
- })
2645
-
2646
- approvals = memory_approval_ui(memories_for_approval)
2772
+ memory_context = format_memory_context(memory_examples)
2773
+
2774
+ try:
2775
+ facts = get_facts(
2776
+ conversation_turn_text,
2777
+ model=active_npc.model,
2778
+ provider=active_npc.provider,
2779
+ npc=active_npc,
2780
+ context=memory_context + 'Memories should be fully self contained. They should not use vague pronouns or words like that or this or it. Do not generate more than 1-2 memories at a time.'
2781
+ )
2647
2782
 
2648
- for approval in approvals:
2649
- fact_data = next(m['fact_data'] for m in memories_for_approval
2650
- if m['memory_id'] == approval['memory_id'])
2783
+ if facts:
2784
+ num_memories = len(facts)
2785
+ print(colored(
2786
+ f"\nThere are {num_memories} potential memories. Do you want to review them now?",
2787
+ "cyan"
2788
+ ))
2789
+ review_choice = input("[y/N]: ").strip().lower()
2651
2790
 
2652
- command_history.add_memory_to_database(
2653
- message_id=f"{result_state.conversation_id}_{len(result_state.messages)}",
2654
- conversation_id=result_state.conversation_id,
2655
- npc=npc_name,
2656
- team=team_name,
2657
- directory_path=result_state.current_path,
2658
- initial_memory=fact_data['statement'],
2659
- status=approval['decision'],
2660
- model=active_npc.model,
2661
- provider=active_npc.provider,
2662
- final_memory=approval.get('final_memory')
2663
- )
2791
+ if review_choice == 'y':
2792
+ memories_for_approval = []
2793
+ for i, fact in enumerate(facts):
2794
+ memories_for_approval.append({
2795
+ "memory_id": f"temp_{i}",
2796
+ "content": fact['statement'],
2797
+ "context": f"Type: {fact.get('type', 'unknown')}, Source: {fact.get('source_text', '')}",
2798
+ "npc": npc_name,
2799
+ "fact_data": fact
2800
+ })
2801
+
2802
+ approvals = memory_approval_ui(memories_for_approval)
2803
+
2804
+ for approval in approvals:
2805
+ fact_data = next(
2806
+ m['fact_data'] for m in memories_for_approval
2807
+ if m['memory_id'] == approval['memory_id']
2808
+ )
2809
+
2810
+ command_history.add_memory_to_database(
2811
+ message_id=f"{result_state.conversation_id}_{len(result_state.messages)}",
2812
+ conversation_id=result_state.conversation_id,
2813
+ npc=npc_name,
2814
+ team=team_name,
2815
+ directory_path=result_state.current_path,
2816
+ initial_memory=fact_data['statement'],
2817
+ status=approval['decision'],
2818
+ model=active_npc.model,
2819
+ provider=active_npc.provider,
2820
+ final_memory=approval.get('final_memory')
2821
+ )
2822
+
2823
+ if approval['decision'] in ['human-approved', 'human-edited']:
2824
+ approved_fact = {
2825
+ 'statement': approval.get('final_memory') or fact_data['statement'],
2826
+ 'source_text': fact_data.get('source_text', ''),
2827
+ 'type': fact_data.get('type', 'explicit'),
2828
+ 'generation': 0
2829
+ }
2830
+ approved_facts.append(approved_fact)
2831
+ else:
2832
+ for i, fact in enumerate(facts):
2833
+ command_history.add_memory_to_database(
2834
+ message_id=f"{result_state.conversation_id}_{len(result_state.messages)}",
2835
+ conversation_id=result_state.conversation_id,
2836
+ npc=npc_name,
2837
+ team=team_name,
2838
+ directory_path=result_state.current_path,
2839
+ initial_memory=fact['statement'],
2840
+ status='skipped',
2841
+ model=active_npc.model,
2842
+ provider=active_npc.provider,
2843
+ final_memory=None
2844
+ )
2845
+
2846
+ print(colored(
2847
+ f"Marked {num_memories} memories as skipped.",
2848
+ "yellow"
2849
+ ))
2664
2850
 
2665
- if approval['decision'] in ['human-approved', 'human-edited']:
2666
- approved_fact = {
2667
- 'statement': approval.get('final_memory') or fact_data['statement'],
2668
- 'source_text': fact_data.get('source_text', ''),
2669
- 'type': fact_data.get('type', 'explicit'),
2670
- 'generation': 0
2671
- }
2672
- approved_facts.append(approved_fact)
2673
-
2674
- except Exception as e:
2675
- print(colored(f"Memory generation error: {e}", "yellow"))
2676
-
2677
- if result_state.build_kg and approved_facts:
2678
- try:
2679
- if not should_skip_kg_processing(user_input, final_output_str):
2680
- npc_kg = load_kg_from_db(engine, team_name, npc_name, result_state.current_path)
2681
- evolved_npc_kg, _ = kg_evolve_incremental(
2682
- existing_kg=npc_kg,
2683
- new_facts=approved_facts,
2684
- model=active_npc.model,
2685
- provider=active_npc.provider,
2686
- npc=active_npc,
2687
- get_concepts=True,
2688
- link_concepts_facts=False,
2689
- link_concepts_concepts=False,
2690
- link_facts_facts=False,
2691
- )
2692
- save_kg_to_db(
2693
- engine,
2694
- evolved_npc_kg,
2695
- team_name,
2696
- npc_name,
2697
- result_state.current_path
2698
- )
2699
2851
  except Exception as e:
2700
- print(colored(f"Error during real-time KG evolution: {e}", "red"))
2852
+ print(colored(f"Memory generation error: {e}", "yellow"))
2701
2853
 
2702
- result_state.turn_count += 1
2703
-
2704
- if result_state.turn_count % 10 == 0:
2705
- print(colored("\nChecking for potential team improvements...", "cyan"))
2854
+ if result_state.build_kg and approved_facts:
2855
+ try:
2856
+ if not should_skip_kg_processing(user_input, final_output_str):
2857
+ npc_kg = load_kg_from_db(
2858
+ engine,
2859
+ team_name,
2860
+ npc_name,
2861
+ result_state.current_path
2862
+ )
2863
+ evolved_npc_kg, _ = kg_evolve_incremental(
2864
+ existing_kg=npc_kg,
2865
+ new_facts=approved_facts,
2866
+ model=active_npc.model,
2867
+ provider=active_npc.provider,
2868
+ npc=active_npc,
2869
+ get_concepts=True,
2870
+ link_concepts_facts=False,
2871
+ link_concepts_concepts=False,
2872
+ link_facts_facts=False,
2873
+ )
2874
+ save_kg_to_db(
2875
+ engine,
2876
+ evolved_npc_kg,
2877
+ team_name,
2878
+ npc_name,
2879
+ result_state.current_path
2880
+ )
2881
+ except Exception as e:
2882
+ print(colored(
2883
+ f"Error during real-time KG evolution: {e}",
2884
+ "red"
2885
+ ))
2886
+
2887
+ print(colored(
2888
+ "\nChecking for potential team improvements...",
2889
+ "cyan"
2890
+ ))
2706
2891
  try:
2707
- summary = breathe(messages=result_state.messages[-20:], npc=active_npc)
2892
+ summary = breathe(
2893
+ messages=result_state.messages[-20:],
2894
+ npc=active_npc
2895
+ )
2708
2896
  characterization = summary.get('output')
2709
2897
 
2710
2898
  if characterization and result_state.team:
2711
- team_ctx_path = get_team_ctx_path(result_state.team.team_path)
2899
+ team_ctx_path = get_team_ctx_path(
2900
+ result_state.team.team_path
2901
+ )
2712
2902
  if not team_ctx_path:
2713
- team_ctx_path = os.path.join(result_state.team.team_path, "team.ctx")
2903
+ team_ctx_path = os.path.join(
2904
+ result_state.team.team_path,
2905
+ "team.ctx"
2906
+ )
2714
2907
 
2715
2908
  ctx_data = {}
2716
2909
  if os.path.exists(team_ctx_path):
@@ -2735,11 +2928,20 @@ def process_result(
2735
2928
  suggestion = response.get("response", {}).get("suggestion")
2736
2929
 
2737
2930
  if suggestion:
2738
- new_context = (current_context + " " + suggestion).strip()
2739
- print(colored(f"{npc_name} suggests updating team context:", "yellow"))
2740
- print(f" - OLD: {current_context}\n + NEW: {new_context}")
2931
+ new_context = (
2932
+ current_context + " " + suggestion
2933
+ ).strip()
2934
+ print(colored(
2935
+ f"{npc_name} suggests updating team context:",
2936
+ "yellow"
2937
+ ))
2938
+ print(
2939
+ f" - OLD: {current_context}\n + NEW: {new_context}"
2940
+ )
2741
2941
 
2742
- choice = input("Apply? [y/N/e(dit)]: ").strip().lower()
2942
+ choice = input(
2943
+ "Apply? [y/N/e(dit)]: "
2944
+ ).strip().lower()
2743
2945
 
2744
2946
  if choice == 'y':
2745
2947
  ctx_data['context'] = new_context
@@ -2747,21 +2949,29 @@ def process_result(
2747
2949
  yaml.dump(ctx_data, f)
2748
2950
  print(colored("Team context updated.", "green"))
2749
2951
  elif choice == 'e':
2750
- edited_context = input(f"Edit context [{new_context}]: ").strip()
2952
+ edited_context = input(
2953
+ f"Edit context [{new_context}]: "
2954
+ ).strip()
2751
2955
  if edited_context:
2752
2956
  ctx_data['context'] = edited_context
2753
2957
  else:
2754
2958
  ctx_data['context'] = new_context
2755
2959
  with open(team_ctx_path, 'w') as f:
2756
2960
  yaml.dump(ctx_data, f)
2757
- print(colored("Team context updated with edits.", "green"))
2961
+ print(colored(
2962
+ "Team context updated with edits.",
2963
+ "green"
2964
+ ))
2758
2965
  else:
2759
2966
  print("Suggestion declined.")
2760
2967
  except Exception as e:
2761
2968
  import traceback
2762
- print(colored(f"Could not generate team suggestions: {e}", "yellow"))
2969
+ print(colored(
2970
+ f"Could not generate team suggestions: {e}",
2971
+ "yellow"
2972
+ ))
2763
2973
  traceback.print_exc()
2764
-
2974
+
2765
2975
  initial_state = ShellState(
2766
2976
  conversation_id=start_new_conversation(),
2767
2977
  stream_output=NPCSH_STREAM_OUTPUT,
@@ -396,8 +396,8 @@ def execute_command_corca(command: str, state: ShellState, command_history, sele
396
396
  cprint("Warning: Corca agent has no tools. No MCP server connected.", "yellow", file=sys.stderr)
397
397
 
398
398
  if len(state.messages) > 20:
399
- compressed_state = state.npc.compress_planning_state(messages)
400
- state.messages = [{"role": "system", "content": state.npc.get_system_message() + f' Your current task: {compressed_state}'}]
399
+ compressed_state = state.npc.compress_planning_state(state.messages)
400
+ state.messages = [{"role": "system", "content": state.npc.get_system_prompt() + f' Your current task: {compressed_state}'}]
401
401
  print("Compressed messages during tool execution.")
402
402
 
403
403
  response_dict = get_llm_response_with_handling(
@@ -640,27 +640,28 @@ def _resolve_and_copy_mcp_server_path(
640
640
 
641
641
  cprint("No MCP server script found in any expected location.", "yellow")
642
642
  return None
643
-
644
-
645
643
  def print_corca_welcome_message():
646
644
  turq = "\033[38;2;64;224;208m"
647
645
  chrome = "\033[38;2;211;211;211m"
646
+ orange = "\033[38;2;255;165;0m"
648
647
  reset = "\033[0m"
649
648
 
650
649
  print(
651
650
  f"""
652
- Welcome to {turq}C{chrome}o{turq}r{chrome}c{turq}a{reset}!
653
- {turq} {turq} {turq} {chrome} {chrome}
654
- {turq} ____ {turq} ___ {turq} ____ {chrome} ____ {chrome} __ _
655
- {turq} / __|{turq} / _ \\ {turq}| __\\{chrome} / __| {chrome}/ _` |
656
- {turq} | |__ {turq}| (_) |{turq}| | {chrome}| |__{chrome} | (_| |
657
- {turq} \\____| {turq}\\___/ {turq}| | {chrome}\\____| {chrome}\\__,_|
658
- {turq} {turq} {turq} {chrome} {chrome}
659
- {reset}
660
- An MCP-powered shell for advanced agentic workflows.
651
+ {turq} ██████ ██████ ██████ ██████ ██████{reset}
652
+ {turq}██ ██ ██ ██ ██ ██ ██ ██ ██🦌🦌██{reset}
653
+ {turq}██ ██ ██ ██ ██ ██ ██🦌🦌██{reset}
654
+ {chrome}██ ██ ██ ████████ ██ ████████{reset}
655
+ {chrome}██ ██ ██ ██ ███ ██ ██ ██{reset}
656
+ {chrome}██ ██ ██ ██ ██ ███ ██ ██ ██ ██{reset}
657
+ {orange} ██████ ██████ ██ ███ ███████ ██ ██{reset}
658
+
659
+ {chrome} 🦌 C O R C A 🦌{reset}
660
+
661
+ {turq}MCP-powered shell for agentic workflows{reset}
661
662
  """
662
- )
663
-
663
+ )
664
+
664
665
  def create_corca_state_and_mcp_client(conversation_id, command_history, npc=None, team=None,
665
666
  current_path=None, mcp_server_path_from_request: Optional[str] = None):
666
667
  from npcsh._state import ShellState
@@ -9,5 +9,4 @@ primary_directive: |
9
9
  You must distinguish carefully and when in doubt, opt to ask for further
10
10
  information or clarification with concrete clear options that make it
11
11
  easy for a user to choose.
12
- model: gpt-4o-mini
13
- provider: openai
12
+
@@ -0,0 +1,43 @@
1
+ jinx_name: search_kg
2
+ description: Search knowledge graph for relevant facts
3
+ inputs:
4
+ - query
5
+ steps:
6
+ - name: retrieve_facts
7
+ engine: python
8
+ code: |
9
+ from npcpy.memory.command_history import load_kg_from_db
10
+ import os
11
+
12
+ kg = load_kg_from_db(
13
+ command_history.engine,
14
+ team.name if team else '__none__',
15
+ npc.name if hasattr(npc, 'name') else '__none__',
16
+ os.getcwd()
17
+ )
18
+
19
+ query_lower = '{{ query }}'.lower()
20
+ matching_facts = []
21
+
22
+ if kg and 'facts' in kg:
23
+ for fact in kg['facts']:
24
+ statement = fact.get('statement', '').lower()
25
+ if query_lower in statement:
26
+ matching_facts.append(fact)
27
+
28
+ output = []
29
+ for i, fact in enumerate(matching_facts[:10], 1):
30
+ statement = fact.get('statement', '')
31
+ fact_type = fact.get('type', 'unknown')
32
+ output.append(f"{i}. [{fact_type}] {statement}")
33
+
34
+ output = "\n".join(output) if output else "No facts found"
35
+
36
+ - name: analyze_facts
37
+ engine: natural
38
+ code: |
39
+ Knowledge graph facts for query "{{ query }}":
40
+
41
+ {{ retrieve_facts }}
42
+
43
+ Analyze how these facts relate to the query.
@@ -0,0 +1,36 @@
1
+ jinx_name: search_memories
2
+ description: Search through approved memories for relevant context
3
+ inputs:
4
+ - query
5
+ steps:
6
+ - name: retrieve_memories
7
+ engine: python
8
+ code: |
9
+ from npcsh._state import get_relevant_memories
10
+ import os
11
+
12
+ memories = get_relevant_memories(
13
+ command_history=command_history,
14
+ npc_name=npc.name if hasattr(npc, 'name') else '__none__',
15
+ team_name=team.name if team else '__none__',
16
+ path=os.getcwd(),
17
+ query='{{ query }}',
18
+ max_memories=10,
19
+ state=state
20
+ )
21
+
22
+ output = []
23
+ for i, mem in enumerate(memories, 1):
24
+ content = mem.get('final_memory', mem.get('initial_memory', ''))
25
+ output.append(f"{i}. {content}")
26
+
27
+ output = "\n".join(output) if output else "No memories found"
28
+
29
+ - name: format_results
30
+ engine: natural
31
+ code: |
32
+ Found memories for query "{{ query }}":
33
+
34
+ {{ retrieve_memories }}
35
+
36
+ Summarize the key points from these memories.
@@ -52,15 +52,22 @@ def print_welcome_message():
52
52
 
53
53
  print(
54
54
  """
55
+ ___________________________________________
56
+ ___________________________________________
57
+ ___________________________________________
58
+
55
59
  Welcome to \033[1;94mnpc\033[0m\033[1;38;5;202msh\033[0m!
56
60
  \033[1;94m \033[0m\033[1;38;5;202m _ \\\\
57
61
  \033[1;94m _ __ _ __ ___ \033[0m\033[1;38;5;202m ___ | |___ \\\\
58
- \033[1;94m| '_ \\ | ' \\ / __|\033[0m\033[1;38;5;202m / __/ | |_ _| \\\\
62
+ \033[1;94m| '_ \\ | '_ \\ / __|\033[0m\033[1;38;5;202m / __/ | |_ _| \\\\
59
63
  \033[1;94m| | | || |_) |( |__ \033[0m\033[1;38;5;202m \\_ \\ | | | | //
60
64
  \033[1;94m|_| |_|| .__/ \\___|\033[0m\033[1;38;5;202m |___/ |_| |_| //
61
- \033[1;94m| | \033[0m\033[1;38;5;202m //
62
- \033[1;94m| |
63
- \033[1;94m|_|
65
+ \033[1;94m|🤖| \033[0m\033[1;38;5;202m //
66
+ \033[1;94m|🤖|
67
+ \033[1;94m|🤖|
68
+ ___________________________________________
69
+ ___________________________________________
70
+ ___________________________________________
64
71
 
65
72
  Begin by asking a question, issuing a bash command, or typing '/help' for more information.
66
73
 
@@ -197,7 +204,9 @@ def run_repl(command_history: CommandHistory, initial_state: ShellState):
197
204
  state, output = execute_command(user_input,
198
205
  state,
199
206
  review = True,
200
- router=router)
207
+ router=router,
208
+ command_history=command_history)
209
+
201
210
  process_result(user_input,
202
211
  state,
203
212
  output,
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: npcsh
3
- Version: 1.0.37
3
+ Version: 1.1.2
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
@@ -46,5 +46,7 @@ npcsh/npc_team/jinxs/bash_executer.jinx
46
46
  npcsh/npc_team/jinxs/edit_file.jinx
47
47
  npcsh/npc_team/jinxs/image_generation.jinx
48
48
  npcsh/npc_team/jinxs/internet_search.jinx
49
+ npcsh/npc_team/jinxs/kg_search.jinx
50
+ npcsh/npc_team/jinxs/memory_search.jinx
49
51
  npcsh/npc_team/jinxs/python_executor.jinx
50
52
  npcsh/npc_team/jinxs/screen_cap.jinx
@@ -78,7 +78,7 @@ extra_files = package_files("npcsh/npc_team/")
78
78
 
79
79
  setup(
80
80
  name="npcsh",
81
- version="1.0.37",
81
+ version="1.1.2",
82
82
  packages=find_packages(exclude=["tests*"]),
83
83
  install_requires=base_requirements, # Only install base requirements by default
84
84
  extras_require={
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes