npcsh 1.1.20__py3-none-any.whl → 1.1.22__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 (186) hide show
  1. npcsh/_state.py +15 -76
  2. npcsh/benchmark/npcsh_agent.py +22 -14
  3. npcsh/benchmark/templates/install-npcsh.sh.j2 +2 -2
  4. npcsh/diff_viewer.py +3 -3
  5. npcsh/mcp_server.py +9 -1
  6. npcsh/npc_team/alicanto.npc +12 -6
  7. npcsh/npc_team/corca.npc +0 -1
  8. npcsh/npc_team/frederic.npc +2 -3
  9. npcsh/npc_team/jinxs/lib/core/compress.jinx +373 -85
  10. npcsh/npc_team/jinxs/lib/core/edit_file.jinx +83 -61
  11. npcsh/npc_team/jinxs/lib/core/search/db_search.jinx +17 -6
  12. npcsh/npc_team/jinxs/lib/core/search/file_search.jinx +17 -6
  13. npcsh/npc_team/jinxs/lib/core/search/web_search.jinx +52 -14
  14. npcsh/npc_team/jinxs/{bin → lib/utils}/benchmark.jinx +2 -2
  15. npcsh/npc_team/jinxs/{bin → lib/utils}/jinxs.jinx +12 -12
  16. npcsh/npc_team/jinxs/{bin → lib/utils}/models.jinx +7 -7
  17. npcsh/npc_team/jinxs/{bin → lib/utils}/setup.jinx +6 -6
  18. npcsh/npc_team/jinxs/modes/alicanto.jinx +1633 -295
  19. npcsh/npc_team/jinxs/modes/arxiv.jinx +5 -5
  20. npcsh/npc_team/jinxs/modes/build.jinx +378 -0
  21. npcsh/npc_team/jinxs/modes/config_tui.jinx +300 -0
  22. npcsh/npc_team/jinxs/modes/convene.jinx +597 -0
  23. npcsh/npc_team/jinxs/modes/corca.jinx +777 -387
  24. npcsh/npc_team/jinxs/modes/git.jinx +795 -0
  25. {npcsh-1.1.20.data/data/npcsh/npc_team → npcsh/npc_team/jinxs/modes}/kg.jinx +82 -15
  26. npcsh/npc_team/jinxs/modes/memories.jinx +414 -0
  27. npcsh/npc_team/jinxs/{bin → modes}/nql.jinx +10 -21
  28. npcsh/npc_team/jinxs/modes/papers.jinx +578 -0
  29. npcsh/npc_team/jinxs/modes/plonk.jinx +503 -308
  30. npcsh/npc_team/jinxs/modes/reattach.jinx +3 -3
  31. npcsh/npc_team/jinxs/modes/spool.jinx +3 -3
  32. npcsh/npc_team/jinxs/{bin → modes}/team.jinx +12 -12
  33. npcsh/npc_team/jinxs/modes/vixynt.jinx +388 -0
  34. npcsh/npc_team/jinxs/modes/wander.jinx +454 -181
  35. npcsh/npc_team/jinxs/modes/yap.jinx +630 -182
  36. npcsh/npc_team/kadiefa.npc +2 -1
  37. npcsh/npc_team/sibiji.npc +3 -3
  38. npcsh/npcsh.py +112 -47
  39. npcsh/routes.py +4 -1
  40. npcsh/salmon_simulation.py +0 -0
  41. npcsh-1.1.22.data/data/npcsh/npc_team/alicanto.jinx +1694 -0
  42. {npcsh-1.1.20.data → npcsh-1.1.22.data}/data/npcsh/npc_team/alicanto.npc +12 -6
  43. {npcsh-1.1.20.data → npcsh-1.1.22.data}/data/npcsh/npc_team/arxiv.jinx +5 -5
  44. {npcsh-1.1.20.data → npcsh-1.1.22.data}/data/npcsh/npc_team/benchmark.jinx +2 -2
  45. npcsh-1.1.22.data/data/npcsh/npc_team/build.jinx +378 -0
  46. npcsh-1.1.22.data/data/npcsh/npc_team/compress.jinx +428 -0
  47. npcsh-1.1.22.data/data/npcsh/npc_team/config_tui.jinx +300 -0
  48. npcsh-1.1.22.data/data/npcsh/npc_team/corca.jinx +820 -0
  49. {npcsh-1.1.20.data → npcsh-1.1.22.data}/data/npcsh/npc_team/corca.npc +0 -1
  50. {npcsh-1.1.20.data → npcsh-1.1.22.data}/data/npcsh/npc_team/db_search.jinx +17 -6
  51. npcsh-1.1.22.data/data/npcsh/npc_team/edit_file.jinx +119 -0
  52. {npcsh-1.1.20.data → npcsh-1.1.22.data}/data/npcsh/npc_team/file_search.jinx +17 -6
  53. {npcsh-1.1.20.data → npcsh-1.1.22.data}/data/npcsh/npc_team/frederic.npc +2 -3
  54. npcsh-1.1.22.data/data/npcsh/npc_team/git.jinx +795 -0
  55. {npcsh-1.1.20.data → npcsh-1.1.22.data}/data/npcsh/npc_team/jinxs.jinx +12 -12
  56. {npcsh-1.1.20.data → npcsh-1.1.22.data}/data/npcsh/npc_team/kadiefa.npc +2 -1
  57. {npcsh/npc_team/jinxs/bin → npcsh-1.1.22.data/data/npcsh/npc_team}/kg.jinx +82 -15
  58. npcsh-1.1.22.data/data/npcsh/npc_team/memories.jinx +414 -0
  59. {npcsh-1.1.20.data → npcsh-1.1.22.data}/data/npcsh/npc_team/models.jinx +7 -7
  60. {npcsh-1.1.20.data → npcsh-1.1.22.data}/data/npcsh/npc_team/nql.jinx +10 -21
  61. npcsh-1.1.22.data/data/npcsh/npc_team/papers.jinx +578 -0
  62. npcsh-1.1.22.data/data/npcsh/npc_team/plonk.jinx +574 -0
  63. {npcsh-1.1.20.data → npcsh-1.1.22.data}/data/npcsh/npc_team/reattach.jinx +3 -3
  64. {npcsh-1.1.20.data → npcsh-1.1.22.data}/data/npcsh/npc_team/setup.jinx +6 -6
  65. {npcsh-1.1.20.data → npcsh-1.1.22.data}/data/npcsh/npc_team/sibiji.npc +3 -3
  66. {npcsh-1.1.20.data → npcsh-1.1.22.data}/data/npcsh/npc_team/spool.jinx +3 -3
  67. {npcsh-1.1.20.data → npcsh-1.1.22.data}/data/npcsh/npc_team/team.jinx +12 -12
  68. npcsh-1.1.22.data/data/npcsh/npc_team/vixynt.jinx +388 -0
  69. npcsh-1.1.22.data/data/npcsh/npc_team/wander.jinx +728 -0
  70. {npcsh-1.1.20.data → npcsh-1.1.22.data}/data/npcsh/npc_team/web_search.jinx +52 -14
  71. npcsh-1.1.22.data/data/npcsh/npc_team/yap.jinx +716 -0
  72. {npcsh-1.1.20.dist-info → npcsh-1.1.22.dist-info}/METADATA +246 -281
  73. npcsh-1.1.22.dist-info/RECORD +240 -0
  74. npcsh-1.1.22.dist-info/entry_points.txt +11 -0
  75. npcsh/npc_team/jinxs/bin/config_tui.jinx +0 -300
  76. npcsh/npc_team/jinxs/bin/memories.jinx +0 -317
  77. npcsh/npc_team/jinxs/bin/vixynt.jinx +0 -122
  78. npcsh/npc_team/jinxs/lib/core/search/kg_search.jinx +0 -418
  79. npcsh/npc_team/jinxs/lib/core/search/mem_review.jinx +0 -73
  80. npcsh/npc_team/jinxs/lib/core/search/mem_search.jinx +0 -388
  81. npcsh/npc_team/jinxs/lib/core/search.jinx +0 -54
  82. npcsh/npc_team/jinxs/lib/research/paper_search.jinx +0 -412
  83. npcsh/npc_team/jinxs/lib/research/semantic_scholar.jinx +0 -386
  84. npcsh/npc_team/jinxs/lib/utils/build.jinx +0 -65
  85. npcsh/npc_team/plonkjr.npc +0 -23
  86. npcsh-1.1.20.data/data/npcsh/npc_team/alicanto.jinx +0 -356
  87. npcsh-1.1.20.data/data/npcsh/npc_team/build.jinx +0 -65
  88. npcsh-1.1.20.data/data/npcsh/npc_team/compress.jinx +0 -140
  89. npcsh-1.1.20.data/data/npcsh/npc_team/config_tui.jinx +0 -300
  90. npcsh-1.1.20.data/data/npcsh/npc_team/corca.jinx +0 -430
  91. npcsh-1.1.20.data/data/npcsh/npc_team/edit_file.jinx +0 -97
  92. npcsh-1.1.20.data/data/npcsh/npc_team/kg_search.jinx +0 -418
  93. npcsh-1.1.20.data/data/npcsh/npc_team/mem_review.jinx +0 -73
  94. npcsh-1.1.20.data/data/npcsh/npc_team/mem_search.jinx +0 -388
  95. npcsh-1.1.20.data/data/npcsh/npc_team/memories.jinx +0 -317
  96. npcsh-1.1.20.data/data/npcsh/npc_team/paper_search.jinx +0 -412
  97. npcsh-1.1.20.data/data/npcsh/npc_team/plonk.jinx +0 -379
  98. npcsh-1.1.20.data/data/npcsh/npc_team/plonkjr.npc +0 -23
  99. npcsh-1.1.20.data/data/npcsh/npc_team/search.jinx +0 -54
  100. npcsh-1.1.20.data/data/npcsh/npc_team/semantic_scholar.jinx +0 -386
  101. npcsh-1.1.20.data/data/npcsh/npc_team/vixynt.jinx +0 -122
  102. npcsh-1.1.20.data/data/npcsh/npc_team/wander.jinx +0 -455
  103. npcsh-1.1.20.data/data/npcsh/npc_team/yap.jinx +0 -268
  104. npcsh-1.1.20.dist-info/RECORD +0 -248
  105. npcsh-1.1.20.dist-info/entry_points.txt +0 -25
  106. /npcsh/npc_team/jinxs/lib/{orchestration → core}/convene.jinx +0 -0
  107. /npcsh/npc_team/jinxs/lib/{orchestration → core}/delegate.jinx +0 -0
  108. /npcsh/npc_team/jinxs/{bin → lib/core}/sample.jinx +0 -0
  109. /npcsh/npc_team/jinxs/lib/{core → utils}/chat.jinx +0 -0
  110. /npcsh/npc_team/jinxs/lib/{core → utils}/cmd.jinx +0 -0
  111. /npcsh/npc_team/jinxs/{bin → lib/utils}/sync.jinx +0 -0
  112. /npcsh/npc_team/jinxs/{bin → modes}/roll.jinx +0 -0
  113. {npcsh-1.1.20.data → npcsh-1.1.22.data}/data/npcsh/npc_team/add_tab.jinx +0 -0
  114. {npcsh-1.1.20.data → npcsh-1.1.22.data}/data/npcsh/npc_team/alicanto.png +0 -0
  115. {npcsh-1.1.20.data → npcsh-1.1.22.data}/data/npcsh/npc_team/browser_action.jinx +0 -0
  116. {npcsh-1.1.20.data → npcsh-1.1.22.data}/data/npcsh/npc_team/browser_screenshot.jinx +0 -0
  117. {npcsh-1.1.20.data → npcsh-1.1.22.data}/data/npcsh/npc_team/chat.jinx +0 -0
  118. {npcsh-1.1.20.data → npcsh-1.1.22.data}/data/npcsh/npc_team/click.jinx +0 -0
  119. {npcsh-1.1.20.data → npcsh-1.1.22.data}/data/npcsh/npc_team/close_browser.jinx +0 -0
  120. {npcsh-1.1.20.data → npcsh-1.1.22.data}/data/npcsh/npc_team/close_pane.jinx +0 -0
  121. {npcsh-1.1.20.data → npcsh-1.1.22.data}/data/npcsh/npc_team/close_tab.jinx +0 -0
  122. {npcsh-1.1.20.data → npcsh-1.1.22.data}/data/npcsh/npc_team/cmd.jinx +0 -0
  123. {npcsh-1.1.20.data → npcsh-1.1.22.data}/data/npcsh/npc_team/compile.jinx +0 -0
  124. {npcsh-1.1.20.data → npcsh-1.1.22.data}/data/npcsh/npc_team/confirm.jinx +0 -0
  125. {npcsh-1.1.20.data → npcsh-1.1.22.data}/data/npcsh/npc_team/convene.jinx +0 -0
  126. {npcsh-1.1.20.data → npcsh-1.1.22.data}/data/npcsh/npc_team/corca.png +0 -0
  127. {npcsh-1.1.20.data → npcsh-1.1.22.data}/data/npcsh/npc_team/corca_example.png +0 -0
  128. {npcsh-1.1.20.data → npcsh-1.1.22.data}/data/npcsh/npc_team/delegate.jinx +0 -0
  129. {npcsh-1.1.20.data → npcsh-1.1.22.data}/data/npcsh/npc_team/focus_pane.jinx +0 -0
  130. {npcsh-1.1.20.data → npcsh-1.1.22.data}/data/npcsh/npc_team/frederic4.png +0 -0
  131. {npcsh-1.1.20.data → npcsh-1.1.22.data}/data/npcsh/npc_team/guac.jinx +0 -0
  132. {npcsh-1.1.20.data → npcsh-1.1.22.data}/data/npcsh/npc_team/guac.npc +0 -0
  133. {npcsh-1.1.20.data → npcsh-1.1.22.data}/data/npcsh/npc_team/guac.png +0 -0
  134. {npcsh-1.1.20.data → npcsh-1.1.22.data}/data/npcsh/npc_team/help.jinx +0 -0
  135. {npcsh-1.1.20.data → npcsh-1.1.22.data}/data/npcsh/npc_team/incognide.jinx +0 -0
  136. {npcsh-1.1.20.data → npcsh-1.1.22.data}/data/npcsh/npc_team/init.jinx +0 -0
  137. {npcsh-1.1.20.data → npcsh-1.1.22.data}/data/npcsh/npc_team/kadiefa.png +0 -0
  138. {npcsh-1.1.20.data → npcsh-1.1.22.data}/data/npcsh/npc_team/key_press.jinx +0 -0
  139. {npcsh-1.1.20.data → npcsh-1.1.22.data}/data/npcsh/npc_team/launch_app.jinx +0 -0
  140. {npcsh-1.1.20.data → npcsh-1.1.22.data}/data/npcsh/npc_team/list_panes.jinx +0 -0
  141. {npcsh-1.1.20.data → npcsh-1.1.22.data}/data/npcsh/npc_team/load_file.jinx +0 -0
  142. {npcsh-1.1.20.data → npcsh-1.1.22.data}/data/npcsh/npc_team/navigate.jinx +0 -0
  143. {npcsh-1.1.20.data → npcsh-1.1.22.data}/data/npcsh/npc_team/notify.jinx +0 -0
  144. {npcsh-1.1.20.data → npcsh-1.1.22.data}/data/npcsh/npc_team/npcsh.ctx +0 -0
  145. {npcsh-1.1.20.data → npcsh-1.1.22.data}/data/npcsh/npc_team/npcsh_sibiji.png +0 -0
  146. {npcsh-1.1.20.data → npcsh-1.1.22.data}/data/npcsh/npc_team/open_browser.jinx +0 -0
  147. {npcsh-1.1.20.data → npcsh-1.1.22.data}/data/npcsh/npc_team/open_pane.jinx +0 -0
  148. {npcsh-1.1.20.data → npcsh-1.1.22.data}/data/npcsh/npc_team/ots.jinx +0 -0
  149. {npcsh-1.1.20.data → npcsh-1.1.22.data}/data/npcsh/npc_team/paste.jinx +0 -0
  150. {npcsh-1.1.20.data → npcsh-1.1.22.data}/data/npcsh/npc_team/plonk.npc +0 -0
  151. {npcsh-1.1.20.data → npcsh-1.1.22.data}/data/npcsh/npc_team/plonk.png +0 -0
  152. {npcsh-1.1.20.data → npcsh-1.1.22.data}/data/npcsh/npc_team/plonkjr.png +0 -0
  153. {npcsh-1.1.20.data → npcsh-1.1.22.data}/data/npcsh/npc_team/pti.jinx +0 -0
  154. {npcsh-1.1.20.data → npcsh-1.1.22.data}/data/npcsh/npc_team/python.jinx +0 -0
  155. {npcsh-1.1.20.data → npcsh-1.1.22.data}/data/npcsh/npc_team/read_pane.jinx +0 -0
  156. {npcsh-1.1.20.data → npcsh-1.1.22.data}/data/npcsh/npc_team/roll.jinx +0 -0
  157. {npcsh-1.1.20.data → npcsh-1.1.22.data}/data/npcsh/npc_team/run_terminal.jinx +0 -0
  158. {npcsh-1.1.20.data → npcsh-1.1.22.data}/data/npcsh/npc_team/sample.jinx +0 -0
  159. {npcsh-1.1.20.data → npcsh-1.1.22.data}/data/npcsh/npc_team/screenshot.jinx +0 -0
  160. {npcsh-1.1.20.data → npcsh-1.1.22.data}/data/npcsh/npc_team/send_message.jinx +0 -0
  161. {npcsh-1.1.20.data → npcsh-1.1.22.data}/data/npcsh/npc_team/serve.jinx +0 -0
  162. {npcsh-1.1.20.data → npcsh-1.1.22.data}/data/npcsh/npc_team/set.jinx +0 -0
  163. {npcsh-1.1.20.data → npcsh-1.1.22.data}/data/npcsh/npc_team/sh.jinx +0 -0
  164. {npcsh-1.1.20.data → npcsh-1.1.22.data}/data/npcsh/npc_team/shh.jinx +0 -0
  165. {npcsh-1.1.20.data → npcsh-1.1.22.data}/data/npcsh/npc_team/sibiji.png +0 -0
  166. {npcsh-1.1.20.data → npcsh-1.1.22.data}/data/npcsh/npc_team/sleep.jinx +0 -0
  167. {npcsh-1.1.20.data → npcsh-1.1.22.data}/data/npcsh/npc_team/split_pane.jinx +0 -0
  168. {npcsh-1.1.20.data → npcsh-1.1.22.data}/data/npcsh/npc_team/spool.png +0 -0
  169. {npcsh-1.1.20.data → npcsh-1.1.22.data}/data/npcsh/npc_team/sql.jinx +0 -0
  170. {npcsh-1.1.20.data → npcsh-1.1.22.data}/data/npcsh/npc_team/switch.jinx +0 -0
  171. {npcsh-1.1.20.data → npcsh-1.1.22.data}/data/npcsh/npc_team/switch_npc.jinx +0 -0
  172. {npcsh-1.1.20.data → npcsh-1.1.22.data}/data/npcsh/npc_team/switch_tab.jinx +0 -0
  173. {npcsh-1.1.20.data → npcsh-1.1.22.data}/data/npcsh/npc_team/switches.jinx +0 -0
  174. {npcsh-1.1.20.data → npcsh-1.1.22.data}/data/npcsh/npc_team/sync.jinx +0 -0
  175. {npcsh-1.1.20.data → npcsh-1.1.22.data}/data/npcsh/npc_team/teamviz.jinx +0 -0
  176. {npcsh-1.1.20.data → npcsh-1.1.22.data}/data/npcsh/npc_team/trigger.jinx +0 -0
  177. {npcsh-1.1.20.data → npcsh-1.1.22.data}/data/npcsh/npc_team/type_text.jinx +0 -0
  178. {npcsh-1.1.20.data → npcsh-1.1.22.data}/data/npcsh/npc_team/usage.jinx +0 -0
  179. {npcsh-1.1.20.data → npcsh-1.1.22.data}/data/npcsh/npc_team/verbose.jinx +0 -0
  180. {npcsh-1.1.20.data → npcsh-1.1.22.data}/data/npcsh/npc_team/wait.jinx +0 -0
  181. {npcsh-1.1.20.data → npcsh-1.1.22.data}/data/npcsh/npc_team/write_file.jinx +0 -0
  182. {npcsh-1.1.20.data → npcsh-1.1.22.data}/data/npcsh/npc_team/yap.png +0 -0
  183. {npcsh-1.1.20.data → npcsh-1.1.22.data}/data/npcsh/npc_team/zen_mode.jinx +0 -0
  184. {npcsh-1.1.20.dist-info → npcsh-1.1.22.dist-info}/WHEEL +0 -0
  185. {npcsh-1.1.20.dist-info → npcsh-1.1.22.dist-info}/licenses/LICENSE +0 -0
  186. {npcsh-1.1.20.dist-info → npcsh-1.1.22.dist-info}/top_level.txt +0 -0
@@ -1,430 +0,0 @@
1
- jinx_name: corca
2
- description: MCP-powered agentic shell - LLM with tool use via MCP servers
3
- inputs:
4
- - mcp_server_path: null
5
- - initial_command: null
6
- - model: null
7
- - provider: null
8
-
9
- steps:
10
- - name: corca_repl
11
- engine: python
12
- code: |
13
- import os
14
- import sys
15
- import tty
16
- import termios
17
- import asyncio
18
- import json
19
- from contextlib import AsyncExitStack
20
- from termcolor import colored
21
-
22
- from npcpy.llm_funcs import get_llm_response
23
- from npcpy.npc_sysenv import render_markdown, get_system_message
24
-
25
- # MCP imports
26
- try:
27
- from mcp import ClientSession, StdioServerParameters
28
- from mcp.client.stdio import stdio_client
29
- MCP_AVAILABLE = True
30
- except ImportError:
31
- MCP_AVAILABLE = False
32
- print(colored("MCP not available. Install with: pip install mcp-client", "yellow"))
33
-
34
- npc = context.get('npc')
35
- team = context.get('team')
36
- messages = context.get('messages', [])
37
- mcp_server_path = context.get('mcp_server_path')
38
- initial_command = context.get('initial_command')
39
-
40
- # Resolve npc if it's a string (npc name) rather than NPC object
41
- if isinstance(npc, str) and team:
42
- npc = team.get(npc) if hasattr(team, 'get') else None
43
- elif isinstance(npc, str):
44
- npc = None
45
-
46
- model = context.get('model') or (npc.model if npc and hasattr(npc, 'model') else None)
47
- provider = context.get('provider') or (npc.provider if npc and hasattr(npc, 'provider') else None)
48
-
49
- # Use shared_context for MCP state
50
- shared_ctx = npc.shared_context if npc and hasattr(npc, 'shared_context') else {}
51
-
52
- # ========== TUI Helper Functions ==========
53
- def get_terminal_size():
54
- try:
55
- size = os.get_terminal_size()
56
- return size.columns, size.lines
57
- except:
58
- return 80, 24
59
-
60
- def tools_tui_browser(tools_llm):
61
- """Interactive TUI browser for MCP tools"""
62
- if not tools_llm:
63
- print(colored("No MCP tools connected.", "yellow"))
64
- return None
65
-
66
- # Build tool info list
67
- tools = []
68
- for t in tools_llm:
69
- func = t.get('function', {})
70
- tools.append({
71
- 'name': func.get('name', 'unknown'),
72
- 'description': func.get('description', '')[:100],
73
- 'params': func.get('parameters', {})
74
- })
75
-
76
- width, height = get_terminal_size()
77
- selected = 0
78
- scroll = 0
79
- list_height = height - 5
80
- mode = 'list'
81
- preview_scroll = 0
82
- preview_lines = []
83
-
84
- fd = sys.stdin.fileno()
85
- old_settings = termios.tcgetattr(fd)
86
-
87
- try:
88
- tty.setcbreak(fd)
89
- sys.stdout.write('\033[?25l')
90
- sys.stdout.write('\033[2J\033[H')
91
-
92
- while True:
93
- width, height = get_terminal_size()
94
- list_height = height - 5
95
-
96
- if mode == 'list':
97
- if selected < scroll:
98
- scroll = selected
99
- elif selected >= scroll + list_height:
100
- scroll = selected - list_height + 1
101
-
102
- sys.stdout.write('\033[H')
103
-
104
- # Header
105
- if mode == 'list':
106
- header = f" CORCA MCP TOOLS ({len(tools)} available) "
107
- else:
108
- header = f" TOOL: {tools[selected]['name']} "
109
- sys.stdout.write(f'\033[46;30;1m{header.ljust(width)}\033[0m\n')
110
-
111
- if mode == 'list':
112
- col_header = f' {"NAME":<25} {"DESCRIPTION":<50}'
113
- sys.stdout.write(f'\033[90m{col_header[:width]}\033[0m\n')
114
- else:
115
- sys.stdout.write(f'\033[90m{"─" * width}\033[0m\n')
116
-
117
- if mode == 'list':
118
- for i in range(list_height):
119
- idx = scroll + i
120
- sys.stdout.write(f'\033[{3+i};1H\033[K')
121
- if idx >= len(tools):
122
- continue
123
-
124
- t = tools[idx]
125
- name = t['name'][:25]
126
- desc = t['description'][:50]
127
-
128
- line = f" {name:<25} {desc}"
129
- line = line[:width-1]
130
-
131
- if idx == selected:
132
- sys.stdout.write(f'\033[7;1m>{line}\033[0m')
133
- else:
134
- sys.stdout.write(f' {line}')
135
-
136
- # Status bar
137
- sys.stdout.write(f'\033[{height-2};1H\033[K\033[90m{"─" * width}\033[0m')
138
- t = tools[selected] if tools else {}
139
- params = t.get('params', {}).get('properties', {})
140
- param_names = list(params.keys())[:5]
141
- sys.stdout.write(f'\033[{height-1};1H\033[K Params: {", ".join(param_names) if param_names else "none"}'.ljust(width)[:width])
142
- sys.stdout.write(f'\033[{height};1H\033[K\033[46;30m j/k:Nav p:Details Enter:Copy q:Quit [{selected+1}/{len(tools)}] \033[0m')
143
-
144
- else: # preview mode
145
- for i in range(list_height):
146
- idx = preview_scroll + i
147
- sys.stdout.write(f'\033[{3+i};1H\033[K')
148
- if idx < len(preview_lines):
149
- sys.stdout.write(preview_lines[idx][:width-1])
150
-
151
- sys.stdout.write(f'\033[{height-2};1H\033[K\033[90m{"─" * width}\033[0m')
152
- sys.stdout.write(f'\033[{height-1};1H\033[K [{preview_scroll+1}/{len(preview_lines)} lines]')
153
- sys.stdout.write(f'\033[{height};1H\033[K\033[46;30m j/k:Scroll b:Back q:Quit \033[0m')
154
-
155
- sys.stdout.flush()
156
-
157
- c = sys.stdin.read(1)
158
-
159
- if c == '\x1b':
160
- c2 = sys.stdin.read(1)
161
- if c2 == '[':
162
- c3 = sys.stdin.read(1)
163
- if c3 == 'A': # Up
164
- if mode == 'list' and selected > 0:
165
- selected -= 1
166
- elif mode == 'preview' and preview_scroll > 0:
167
- preview_scroll -= 1
168
- elif c3 == 'B': # Down
169
- if mode == 'list' and selected < len(tools) - 1:
170
- selected += 1
171
- elif mode == 'preview' and preview_scroll < max(0, len(preview_lines) - list_height):
172
- preview_scroll += 1
173
- else:
174
- if mode == 'preview':
175
- mode = 'list'
176
- sys.stdout.write('\033[2J\033[H')
177
- else:
178
- return None
179
- continue
180
-
181
- if c == 'q' or c == '\x03':
182
- return None
183
- elif c == 'k':
184
- if mode == 'list' and selected > 0:
185
- selected -= 1
186
- elif mode == 'preview' and preview_scroll > 0:
187
- preview_scroll -= 1
188
- elif c == 'j':
189
- if mode == 'list' and selected < len(tools) - 1:
190
- selected += 1
191
- elif mode == 'preview' and preview_scroll < max(0, len(preview_lines) - list_height):
192
- preview_scroll += 1
193
- elif c == 'p' and mode == 'list' and tools:
194
- # Preview tool details
195
- t = tools[selected]
196
- preview_str = f"Tool: {t['name']}\n"
197
- preview_str += f"{'=' * 40}\n\n"
198
- preview_str += f"Description:\n{t['description']}\n\n"
199
- preview_str += f"Parameters:\n"
200
- params = t.get('params', {})
201
- props = params.get('properties', {})
202
- required = params.get('required', [])
203
- for pname, pinfo in props.items():
204
- req = "*" if pname in required else ""
205
- ptype = pinfo.get('type', 'any')
206
- pdesc = pinfo.get('description', '')[:60]
207
- preview_str += f" {pname}{req} ({ptype}): {pdesc}\n"
208
- if not props:
209
- preview_str += " (no parameters)\n"
210
- preview_lines = preview_str.split('\n')
211
- mode = 'preview'
212
- preview_scroll = 0
213
- sys.stdout.write('\033[2J\033[H')
214
- elif c == 'b' and mode == 'preview':
215
- mode = 'list'
216
- sys.stdout.write('\033[2J\033[H')
217
- elif c in ('\r', '\n') and mode == 'list' and tools:
218
- # Return tool name for use
219
- return tools[selected]['name']
220
-
221
- finally:
222
- termios.tcsetattr(fd, termios.TCSADRAIN, old_settings)
223
- sys.stdout.write('\033[?25h')
224
- sys.stdout.write('\033[2J\033[H')
225
- sys.stdout.flush()
226
-
227
- print("""
228
- ██████╗ ██████╗ ██████╗ ██████╗ █████╗
229
- ██╔════╝██╔═══██╗██╔══██╗██╔════╝██╔══██╗
230
- ██║ ██║ ██║██████╔╝██║ ███████║
231
- ██║ ██║ ██║██╔══██╗██║ ██╔══██╗
232
- ╚██████╗╚██████╔╝██║ ██║╚██████╗██║ ██║
233
- ╚═════╝ ╚═════╝ ╚═╝ ╚═╝ ╚═════╝╚═╝ ╚═╝
234
- """)
235
-
236
- npc_name = npc.name if npc else "corca"
237
- print(f"Entering corca mode (NPC: {npc_name}). Type '/cq' to exit.")
238
-
239
- # ========== MCP Connection Setup ==========
240
- async def connect_mcp(server_path):
241
- """Connect to MCP server and return tools"""
242
- if not MCP_AVAILABLE:
243
- return [], {}
244
-
245
- abs_path = os.path.abspath(os.path.expanduser(server_path))
246
- if not os.path.exists(abs_path):
247
- print(colored(f"MCP server not found: {abs_path}", "red"))
248
- return [], {}
249
-
250
- try:
251
- loop = asyncio.get_event_loop()
252
- except RuntimeError:
253
- loop = asyncio.new_event_loop()
254
- asyncio.set_event_loop(loop)
255
-
256
- exit_stack = AsyncExitStack()
257
-
258
- if abs_path.endswith('.py'):
259
- cmd_parts = [sys.executable, abs_path]
260
- else:
261
- cmd_parts = [abs_path]
262
-
263
- server_params = StdioServerParameters(
264
- command=cmd_parts[0],
265
- args=[abs_path],
266
- env=os.environ.copy()
267
- )
268
-
269
- stdio_transport = await exit_stack.enter_async_context(stdio_client(server_params))
270
- session = await exit_stack.enter_async_context(ClientSession(*stdio_transport))
271
- await session.initialize()
272
-
273
- response = await session.list_tools()
274
- tools_llm = []
275
- tool_map = {}
276
-
277
- if response.tools:
278
- for mcp_tool in response.tools:
279
- tool_def = {
280
- "type": "function",
281
- "function": {
282
- "name": mcp_tool.name,
283
- "description": mcp_tool.description or f"MCP tool: {mcp_tool.name}",
284
- "parameters": getattr(mcp_tool, "inputSchema", {"type": "object", "properties": {}})
285
- }
286
- }
287
- tools_llm.append(tool_def)
288
-
289
- # Create sync wrapper for async tool call
290
- def make_tool_func(tool_name, sess, lp):
291
- async def call_tool(**kwargs):
292
- cleaned = {k: (None if v == 'None' else v) for k, v in kwargs.items()}
293
- result = await asyncio.wait_for(sess.call_tool(tool_name, cleaned), timeout=30.0)
294
- return result
295
- def sync_call(**kwargs):
296
- return lp.run_until_complete(call_tool(**kwargs))
297
- return sync_call
298
-
299
- tool_map[mcp_tool.name] = make_tool_func(mcp_tool.name, session, loop)
300
-
301
- # Store in shared context
302
- shared_ctx['mcp_client'] = session
303
- shared_ctx['mcp_tools'] = tools_llm
304
- shared_ctx['mcp_tool_map'] = tool_map
305
- shared_ctx['_mcp_exit_stack'] = exit_stack
306
- shared_ctx['_mcp_loop'] = loop
307
-
308
- print(colored(f"Connected to MCP server. Tools: {', '.join(tool_map.keys())}", "green"))
309
- return tools_llm, tool_map
310
-
311
- # Try to connect if server path provided
312
- tools_llm = shared_ctx.get('mcp_tools', [])
313
- tool_map = shared_ctx.get('mcp_tool_map', {})
314
-
315
- if mcp_server_path and not tools_llm:
316
- try:
317
- loop = asyncio.get_event_loop()
318
- except RuntimeError:
319
- loop = asyncio.new_event_loop()
320
- asyncio.set_event_loop(loop)
321
- tools_llm, tool_map = loop.run_until_complete(connect_mcp(mcp_server_path))
322
-
323
- # Find default MCP server if none provided
324
- if not tools_llm:
325
- default_paths = [
326
- os.path.expanduser("~/.npcsh/npc_team/mcp_server.py"),
327
- os.path.join(team.team_path, "mcp_server.py") if team and hasattr(team, 'team_path') else None,
328
- ]
329
- for path in default_paths:
330
- if path and os.path.exists(path):
331
- try:
332
- loop = asyncio.get_event_loop()
333
- except RuntimeError:
334
- loop = asyncio.new_event_loop()
335
- asyncio.set_event_loop(loop)
336
- tools_llm, tool_map = loop.run_until_complete(connect_mcp(path))
337
- if tools_llm:
338
- break
339
-
340
- # Ensure system message
341
- if not messages or messages[0].get("role") != "system":
342
- sys_msg = get_system_message(npc) if npc else "You are an AI assistant with access to tools."
343
- if tools_llm:
344
- sys_msg += f"\n\nYou have access to these tools: {', '.join(t['function']['name'] for t in tools_llm)}"
345
- messages.insert(0, {"role": "system", "content": sys_msg})
346
-
347
- # Handle initial command if provided (one-shot mode)
348
- if initial_command:
349
- resp = get_llm_response(
350
- initial_command,
351
- model=model,
352
- provider=provider,
353
- messages=messages,
354
- tools=tools_llm if tools_llm else None,
355
- tool_map=tool_map if tool_map else None,
356
- auto_process_tool_calls=True,
357
- npc=npc
358
- )
359
- messages = resp.get('messages', messages)
360
- render_markdown(str(resp.get('response', '')))
361
- context['output'] = resp.get('response', 'Done.')
362
- context['messages'] = messages
363
- # Don't enter REPL for one-shot
364
- exit()
365
-
366
- # REPL loop
367
- while True:
368
- try:
369
- prompt_str = f"{npc_name}:corca> "
370
- user_input = input(prompt_str).strip()
371
-
372
- if not user_input:
373
- continue
374
-
375
- if user_input.lower() == "/cq":
376
- print("Exiting corca mode.")
377
- break
378
-
379
- # Handle /tools to browse available tools with TUI
380
- if user_input.lower() == "/tools":
381
- result = tools_tui_browser(tools_llm)
382
- if result:
383
- print(colored(f"Selected tool: {result}", "cyan"))
384
- print(colored("Use it by describing what you want to do.", "gray"))
385
- continue
386
-
387
- # Handle /connect to connect to new MCP server
388
- if user_input.startswith("/connect "):
389
- new_path = user_input[9:].strip()
390
- try:
391
- loop = asyncio.get_event_loop()
392
- except RuntimeError:
393
- loop = asyncio.new_event_loop()
394
- asyncio.set_event_loop(loop)
395
- tools_llm, tool_map = loop.run_until_complete(connect_mcp(new_path))
396
- continue
397
-
398
- # Get LLM response with tools
399
- resp = get_llm_response(
400
- user_input,
401
- model=model,
402
- provider=provider,
403
- messages=messages,
404
- tools=tools_llm if tools_llm else None,
405
- tool_map=tool_map if tool_map else None,
406
- auto_process_tool_calls=True,
407
- stream=False, # Tool calls don't work well with streaming
408
- npc=npc
409
- )
410
-
411
- messages = resp.get('messages', messages)
412
- response_text = resp.get('response', '')
413
- render_markdown(str(response_text))
414
-
415
- # Track usage
416
- if 'usage' in resp and npc and hasattr(npc, 'shared_context'):
417
- usage = resp['usage']
418
- npc.shared_context['session_input_tokens'] += usage.get('input_tokens', 0)
419
- npc.shared_context['session_output_tokens'] += usage.get('output_tokens', 0)
420
- npc.shared_context['turn_count'] += 1
421
-
422
- except KeyboardInterrupt:
423
- print("\nUse '/cq' to exit or continue.")
424
- continue
425
- except EOFError:
426
- print("\nExiting corca mode.")
427
- break
428
-
429
- context['output'] = "Exited corca mode."
430
- context['messages'] = messages
@@ -1,97 +0,0 @@
1
- jinx_name: edit_file
2
- description: Examines a file, determines what changes are needed, and applies those
3
- changes.
4
- inputs:
5
- - file_path
6
- - edit_instructions
7
- - backup: true
8
- steps:
9
- - name: "edit_file"
10
- engine: "python"
11
- code: |
12
- import os
13
- from npcpy.llm_funcs import get_llm_response
14
-
15
-
16
- file_path = os.path.expanduser({{ file_path | tojson }})
17
- edit_instructions = {{ edit_instructions | string | tojson }}
18
- backup_str = {{ backup | default("true") | string | tojson }}
19
- create_backup = backup_str.lower() not in ('false', 'no', '0', '')
20
-
21
-
22
- with open(file_path, 'r') as f:
23
- original_content = f.read()
24
-
25
-
26
- if create_backup:
27
- backup_path = file_path + ".bak"
28
- with open(backup_path, 'w') as f:
29
- f.write(original_content)
30
-
31
-
32
- prompt = """You are a code editing assistant. Analyze this file and make the requested changes.
33
-
34
- File content:
35
- """ + original_content + """
36
-
37
- Edit instructions: """ + edit_instructions + """
38
-
39
- Return a JSON object with these fields:
40
- 1. "modifications": An array of modification objects, where each object has:
41
- - "type": One of "replace", "insert_after", "insert_before", or "delete"
42
- - "target": For "insert_after" and "insert_before", the text to insert after/before
43
- For "delete", the text to delete
44
- - "original": For "replace", the text to be replaced
45
- - "replacement": For "replace", the text to replace with
46
- - "insertion": For "insert_after" and "insert_before", the text to insert
47
- 2. "explanation": Brief explanation of the changes made
48
-
49
- Example response:
50
- {"modifications": [{"type": "replace", "original": "old code", "replacement": "new code"}], "explanation": "Updated the code"}
51
- """
52
-
53
- response = get_llm_response(prompt, model=npc.model, provider=npc.provider, npc=npc, format="json")
54
-
55
- result = response.get("response", {})
56
- modifications = result.get("modifications", [])
57
- explanation = result.get("explanation", "No explanation provided")
58
-
59
-
60
- updated_content = original_content
61
- changes_applied = 0
62
-
63
- for mod in modifications:
64
- print(mod)
65
- mod_type = mod.get("type")
66
-
67
- if mod_type == "replace":
68
- original = mod.get("original")
69
- replacement = mod.get("replacement")
70
- if original in updated_content:
71
- updated_content = updated_content.replace(original, replacement)
72
- changes_applied += 1
73
-
74
- elif mod_type == "insert_after":
75
- target = mod.get("target")
76
- insertion = mod.get("insertion")
77
- if target in updated_content:
78
- updated_content = updated_content.replace(target, target + insertion)
79
- changes_applied += 1
80
-
81
- elif mod_type == "insert_before":
82
- target = mod.get("target")
83
- insertion = mod.get("insertion")
84
- if target in updated_content:
85
- updated_content = updated_content.replace(target, insertion + target)
86
- changes_applied += 1
87
-
88
- elif mod_type == "delete":
89
- target = mod.get("target")
90
- if target in updated_content:
91
- updated_content = updated_content.replace(target, "")
92
- changes_applied += 1
93
-
94
- with open(file_path, 'w') as f:
95
- f.write(updated_content)
96
-
97
- output = "Applied " + str(changes_applied) + " changes to " + file_path + "\n\n" + explanation