npcsh 1.1.17__py3-none-any.whl → 1.1.19__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 (197) hide show
  1. npcsh/_state.py +122 -91
  2. npcsh/alicanto.py +2 -2
  3. npcsh/benchmark/__init__.py +8 -2
  4. npcsh/benchmark/npcsh_agent.py +87 -22
  5. npcsh/benchmark/runner.py +85 -43
  6. npcsh/benchmark/templates/install-npcsh.sh.j2 +35 -0
  7. npcsh/build.py +2 -4
  8. npcsh/completion.py +2 -6
  9. npcsh/config.py +2 -3
  10. npcsh/conversation_viewer.py +389 -0
  11. npcsh/corca.py +0 -1
  12. npcsh/diff_viewer.py +452 -0
  13. npcsh/execution.py +0 -1
  14. npcsh/guac.py +0 -1
  15. npcsh/mcp_helpers.py +2 -3
  16. npcsh/mcp_server.py +5 -10
  17. npcsh/npc.py +10 -11
  18. npcsh/npc_team/jinxs/bin/benchmark.jinx +1 -1
  19. npcsh/npc_team/jinxs/bin/config_tui.jinx +299 -0
  20. npcsh/npc_team/jinxs/bin/memories.jinx +316 -0
  21. npcsh/npc_team/jinxs/bin/setup.jinx +240 -0
  22. npcsh/npc_team/jinxs/bin/sync.jinx +143 -150
  23. npcsh/npc_team/jinxs/bin/team_tui.jinx +327 -0
  24. npcsh/npc_team/jinxs/incognide/add_tab.jinx +1 -1
  25. npcsh/npc_team/jinxs/incognide/close_pane.jinx +1 -1
  26. npcsh/npc_team/jinxs/incognide/close_tab.jinx +1 -1
  27. npcsh/npc_team/jinxs/incognide/confirm.jinx +1 -1
  28. npcsh/npc_team/jinxs/incognide/focus_pane.jinx +1 -1
  29. npcsh/npc_team/jinxs/incognide/list_panes.jinx +1 -1
  30. npcsh/npc_team/jinxs/incognide/navigate.jinx +1 -1
  31. npcsh/npc_team/jinxs/incognide/notify.jinx +1 -1
  32. npcsh/npc_team/jinxs/incognide/open_pane.jinx +1 -1
  33. npcsh/npc_team/jinxs/incognide/read_pane.jinx +1 -1
  34. npcsh/npc_team/jinxs/incognide/run_terminal.jinx +1 -1
  35. npcsh/npc_team/jinxs/incognide/send_message.jinx +1 -1
  36. npcsh/npc_team/jinxs/incognide/split_pane.jinx +1 -1
  37. npcsh/npc_team/jinxs/incognide/switch_npc.jinx +1 -1
  38. npcsh/npc_team/jinxs/incognide/switch_tab.jinx +1 -1
  39. npcsh/npc_team/jinxs/incognide/write_file.jinx +1 -1
  40. npcsh/npc_team/jinxs/incognide/zen_mode.jinx +1 -1
  41. npcsh/npc_team/jinxs/lib/core/search/db_search.jinx +321 -17
  42. npcsh/npc_team/jinxs/lib/core/search/file_search.jinx +312 -67
  43. npcsh/npc_team/jinxs/lib/core/search/kg_search.jinx +366 -44
  44. npcsh/npc_team/jinxs/lib/core/search/mem_review.jinx +73 -0
  45. npcsh/npc_team/jinxs/lib/core/search/mem_search.jinx +328 -20
  46. npcsh/npc_team/jinxs/lib/core/search/web_search.jinx +242 -10
  47. npcsh/npc_team/jinxs/lib/core/sleep.jinx +22 -11
  48. npcsh/npc_team/jinxs/lib/core/sql.jinx +10 -6
  49. npcsh/npc_team/jinxs/lib/research/paper_search.jinx +387 -76
  50. npcsh/npc_team/jinxs/lib/research/semantic_scholar.jinx +372 -55
  51. npcsh/npc_team/jinxs/lib/utils/jinxs.jinx +299 -144
  52. npcsh/npc_team/jinxs/modes/alicanto.jinx +356 -0
  53. npcsh/npc_team/jinxs/modes/arxiv.jinx +720 -0
  54. npcsh/npc_team/jinxs/modes/corca.jinx +430 -0
  55. npcsh/npc_team/jinxs/modes/guac.jinx +542 -0
  56. npcsh/npc_team/jinxs/modes/plonk.jinx +379 -0
  57. npcsh/npc_team/jinxs/modes/pti.jinx +357 -0
  58. npcsh/npc_team/jinxs/modes/reattach.jinx +291 -0
  59. npcsh/npc_team/jinxs/modes/spool.jinx +350 -0
  60. npcsh/npc_team/jinxs/modes/wander.jinx +455 -0
  61. npcsh/npc_team/jinxs/{bin → modes}/yap.jinx +13 -7
  62. npcsh/npcsh.py +7 -4
  63. npcsh/plonk.py +0 -1
  64. npcsh/pti.py +0 -1
  65. npcsh/routes.py +1 -3
  66. npcsh/spool.py +0 -1
  67. npcsh/ui.py +0 -1
  68. npcsh/wander.py +0 -1
  69. npcsh/yap.py +0 -1
  70. {npcsh-1.1.17.data → npcsh-1.1.19.data}/data/npcsh/npc_team/add_tab.jinx +1 -1
  71. npcsh-1.1.19.data/data/npcsh/npc_team/alicanto.jinx +356 -0
  72. npcsh-1.1.19.data/data/npcsh/npc_team/arxiv.jinx +720 -0
  73. {npcsh-1.1.17.data → npcsh-1.1.19.data}/data/npcsh/npc_team/benchmark.jinx +1 -1
  74. {npcsh-1.1.17.data → npcsh-1.1.19.data}/data/npcsh/npc_team/close_pane.jinx +1 -1
  75. {npcsh-1.1.17.data → npcsh-1.1.19.data}/data/npcsh/npc_team/close_tab.jinx +1 -1
  76. npcsh-1.1.19.data/data/npcsh/npc_team/config_tui.jinx +299 -0
  77. {npcsh-1.1.17.data → npcsh-1.1.19.data}/data/npcsh/npc_team/confirm.jinx +1 -1
  78. npcsh-1.1.19.data/data/npcsh/npc_team/corca.jinx +430 -0
  79. npcsh-1.1.19.data/data/npcsh/npc_team/db_search.jinx +348 -0
  80. npcsh-1.1.19.data/data/npcsh/npc_team/file_search.jinx +339 -0
  81. {npcsh-1.1.17.data → npcsh-1.1.19.data}/data/npcsh/npc_team/focus_pane.jinx +1 -1
  82. npcsh-1.1.19.data/data/npcsh/npc_team/guac.jinx +542 -0
  83. npcsh-1.1.19.data/data/npcsh/npc_team/jinxs.jinx +331 -0
  84. npcsh-1.1.19.data/data/npcsh/npc_team/kg_search.jinx +418 -0
  85. {npcsh-1.1.17.data → npcsh-1.1.19.data}/data/npcsh/npc_team/list_panes.jinx +1 -1
  86. npcsh-1.1.19.data/data/npcsh/npc_team/mem_review.jinx +73 -0
  87. npcsh-1.1.19.data/data/npcsh/npc_team/mem_search.jinx +388 -0
  88. npcsh-1.1.19.data/data/npcsh/npc_team/memories.jinx +316 -0
  89. {npcsh-1.1.17.data → npcsh-1.1.19.data}/data/npcsh/npc_team/navigate.jinx +1 -1
  90. {npcsh-1.1.17.data → npcsh-1.1.19.data}/data/npcsh/npc_team/notify.jinx +1 -1
  91. {npcsh-1.1.17.data → npcsh-1.1.19.data}/data/npcsh/npc_team/open_pane.jinx +1 -1
  92. npcsh-1.1.19.data/data/npcsh/npc_team/paper_search.jinx +412 -0
  93. npcsh-1.1.19.data/data/npcsh/npc_team/plonk.jinx +379 -0
  94. npcsh-1.1.19.data/data/npcsh/npc_team/pti.jinx +357 -0
  95. {npcsh-1.1.17.data → npcsh-1.1.19.data}/data/npcsh/npc_team/read_pane.jinx +1 -1
  96. npcsh-1.1.19.data/data/npcsh/npc_team/reattach.jinx +291 -0
  97. {npcsh-1.1.17.data → npcsh-1.1.19.data}/data/npcsh/npc_team/run_terminal.jinx +1 -1
  98. npcsh-1.1.19.data/data/npcsh/npc_team/semantic_scholar.jinx +386 -0
  99. {npcsh-1.1.17.data → npcsh-1.1.19.data}/data/npcsh/npc_team/send_message.jinx +1 -1
  100. npcsh-1.1.19.data/data/npcsh/npc_team/setup.jinx +240 -0
  101. {npcsh-1.1.17.data → npcsh-1.1.19.data}/data/npcsh/npc_team/sleep.jinx +22 -11
  102. {npcsh-1.1.17.data → npcsh-1.1.19.data}/data/npcsh/npc_team/split_pane.jinx +1 -1
  103. npcsh-1.1.19.data/data/npcsh/npc_team/spool.jinx +350 -0
  104. npcsh-1.1.19.data/data/npcsh/npc_team/sql.jinx +20 -0
  105. {npcsh-1.1.17.data → npcsh-1.1.19.data}/data/npcsh/npc_team/switch_npc.jinx +1 -1
  106. {npcsh-1.1.17.data → npcsh-1.1.19.data}/data/npcsh/npc_team/switch_tab.jinx +1 -1
  107. npcsh-1.1.19.data/data/npcsh/npc_team/sync.jinx +223 -0
  108. npcsh-1.1.19.data/data/npcsh/npc_team/team_tui.jinx +327 -0
  109. npcsh-1.1.19.data/data/npcsh/npc_team/wander.jinx +455 -0
  110. npcsh-1.1.19.data/data/npcsh/npc_team/web_search.jinx +283 -0
  111. {npcsh-1.1.17.data → npcsh-1.1.19.data}/data/npcsh/npc_team/write_file.jinx +1 -1
  112. {npcsh-1.1.17.data → npcsh-1.1.19.data}/data/npcsh/npc_team/yap.jinx +13 -7
  113. {npcsh-1.1.17.data → npcsh-1.1.19.data}/data/npcsh/npc_team/zen_mode.jinx +1 -1
  114. {npcsh-1.1.17.dist-info → npcsh-1.1.19.dist-info}/METADATA +110 -14
  115. npcsh-1.1.19.dist-info/RECORD +244 -0
  116. {npcsh-1.1.17.dist-info → npcsh-1.1.19.dist-info}/WHEEL +1 -1
  117. {npcsh-1.1.17.dist-info → npcsh-1.1.19.dist-info}/entry_points.txt +4 -3
  118. npcsh/npc_team/jinxs/bin/spool.jinx +0 -161
  119. npcsh/npc_team/jinxs/bin/wander.jinx +0 -242
  120. npcsh/npc_team/jinxs/lib/research/arxiv.jinx +0 -76
  121. npcsh-1.1.17.data/data/npcsh/npc_team/arxiv.jinx +0 -76
  122. npcsh-1.1.17.data/data/npcsh/npc_team/db_search.jinx +0 -44
  123. npcsh-1.1.17.data/data/npcsh/npc_team/file_search.jinx +0 -94
  124. npcsh-1.1.17.data/data/npcsh/npc_team/jinxs.jinx +0 -176
  125. npcsh-1.1.17.data/data/npcsh/npc_team/kg_search.jinx +0 -96
  126. npcsh-1.1.17.data/data/npcsh/npc_team/mem_search.jinx +0 -80
  127. npcsh-1.1.17.data/data/npcsh/npc_team/paper_search.jinx +0 -101
  128. npcsh-1.1.17.data/data/npcsh/npc_team/semantic_scholar.jinx +0 -69
  129. npcsh-1.1.17.data/data/npcsh/npc_team/spool.jinx +0 -161
  130. npcsh-1.1.17.data/data/npcsh/npc_team/sql.jinx +0 -16
  131. npcsh-1.1.17.data/data/npcsh/npc_team/sync.jinx +0 -230
  132. npcsh-1.1.17.data/data/npcsh/npc_team/wander.jinx +0 -242
  133. npcsh-1.1.17.data/data/npcsh/npc_team/web_search.jinx +0 -51
  134. npcsh-1.1.17.dist-info/RECORD +0 -219
  135. {npcsh-1.1.17.data → npcsh-1.1.19.data}/data/npcsh/npc_team/alicanto.npc +0 -0
  136. {npcsh-1.1.17.data → npcsh-1.1.19.data}/data/npcsh/npc_team/alicanto.png +0 -0
  137. {npcsh-1.1.17.data → npcsh-1.1.19.data}/data/npcsh/npc_team/browser_action.jinx +0 -0
  138. {npcsh-1.1.17.data → npcsh-1.1.19.data}/data/npcsh/npc_team/browser_screenshot.jinx +0 -0
  139. {npcsh-1.1.17.data → npcsh-1.1.19.data}/data/npcsh/npc_team/build.jinx +0 -0
  140. {npcsh-1.1.17.data → npcsh-1.1.19.data}/data/npcsh/npc_team/chat.jinx +0 -0
  141. {npcsh-1.1.17.data → npcsh-1.1.19.data}/data/npcsh/npc_team/click.jinx +0 -0
  142. {npcsh-1.1.17.data → npcsh-1.1.19.data}/data/npcsh/npc_team/close_browser.jinx +0 -0
  143. {npcsh-1.1.17.data → npcsh-1.1.19.data}/data/npcsh/npc_team/cmd.jinx +0 -0
  144. {npcsh-1.1.17.data → npcsh-1.1.19.data}/data/npcsh/npc_team/compile.jinx +0 -0
  145. {npcsh-1.1.17.data → npcsh-1.1.19.data}/data/npcsh/npc_team/compress.jinx +0 -0
  146. {npcsh-1.1.17.data → npcsh-1.1.19.data}/data/npcsh/npc_team/convene.jinx +0 -0
  147. {npcsh-1.1.17.data → npcsh-1.1.19.data}/data/npcsh/npc_team/corca.npc +0 -0
  148. {npcsh-1.1.17.data → npcsh-1.1.19.data}/data/npcsh/npc_team/corca.png +0 -0
  149. {npcsh-1.1.17.data → npcsh-1.1.19.data}/data/npcsh/npc_team/corca_example.png +0 -0
  150. {npcsh-1.1.17.data → npcsh-1.1.19.data}/data/npcsh/npc_team/delegate.jinx +0 -0
  151. {npcsh-1.1.17.data → npcsh-1.1.19.data}/data/npcsh/npc_team/edit_file.jinx +0 -0
  152. {npcsh-1.1.17.data → npcsh-1.1.19.data}/data/npcsh/npc_team/frederic.npc +0 -0
  153. {npcsh-1.1.17.data → npcsh-1.1.19.data}/data/npcsh/npc_team/frederic4.png +0 -0
  154. {npcsh-1.1.17.data → npcsh-1.1.19.data}/data/npcsh/npc_team/guac.npc +0 -0
  155. {npcsh-1.1.17.data → npcsh-1.1.19.data}/data/npcsh/npc_team/guac.png +0 -0
  156. {npcsh-1.1.17.data → npcsh-1.1.19.data}/data/npcsh/npc_team/help.jinx +0 -0
  157. {npcsh-1.1.17.data → npcsh-1.1.19.data}/data/npcsh/npc_team/incognide.jinx +0 -0
  158. {npcsh-1.1.17.data → npcsh-1.1.19.data}/data/npcsh/npc_team/init.jinx +0 -0
  159. {npcsh-1.1.17.data → npcsh-1.1.19.data}/data/npcsh/npc_team/kadiefa.npc +0 -0
  160. {npcsh-1.1.17.data → npcsh-1.1.19.data}/data/npcsh/npc_team/kadiefa.png +0 -0
  161. {npcsh-1.1.17.data → npcsh-1.1.19.data}/data/npcsh/npc_team/key_press.jinx +0 -0
  162. {npcsh-1.1.17.data → npcsh-1.1.19.data}/data/npcsh/npc_team/launch_app.jinx +0 -0
  163. {npcsh-1.1.17.data → npcsh-1.1.19.data}/data/npcsh/npc_team/load_file.jinx +0 -0
  164. {npcsh-1.1.17.data → npcsh-1.1.19.data}/data/npcsh/npc_team/npcsh.ctx +0 -0
  165. {npcsh-1.1.17.data → npcsh-1.1.19.data}/data/npcsh/npc_team/npcsh_sibiji.png +0 -0
  166. {npcsh-1.1.17.data → npcsh-1.1.19.data}/data/npcsh/npc_team/nql.jinx +0 -0
  167. {npcsh-1.1.17.data → npcsh-1.1.19.data}/data/npcsh/npc_team/open_browser.jinx +0 -0
  168. {npcsh-1.1.17.data → npcsh-1.1.19.data}/data/npcsh/npc_team/ots.jinx +0 -0
  169. {npcsh-1.1.17.data → npcsh-1.1.19.data}/data/npcsh/npc_team/paste.jinx +0 -0
  170. {npcsh-1.1.17.data → npcsh-1.1.19.data}/data/npcsh/npc_team/plonk.npc +0 -0
  171. {npcsh-1.1.17.data → npcsh-1.1.19.data}/data/npcsh/npc_team/plonk.png +0 -0
  172. {npcsh-1.1.17.data → npcsh-1.1.19.data}/data/npcsh/npc_team/plonkjr.npc +0 -0
  173. {npcsh-1.1.17.data → npcsh-1.1.19.data}/data/npcsh/npc_team/plonkjr.png +0 -0
  174. {npcsh-1.1.17.data → npcsh-1.1.19.data}/data/npcsh/npc_team/python.jinx +0 -0
  175. {npcsh-1.1.17.data → npcsh-1.1.19.data}/data/npcsh/npc_team/roll.jinx +0 -0
  176. {npcsh-1.1.17.data → npcsh-1.1.19.data}/data/npcsh/npc_team/sample.jinx +0 -0
  177. {npcsh-1.1.17.data → npcsh-1.1.19.data}/data/npcsh/npc_team/screenshot.jinx +0 -0
  178. {npcsh-1.1.17.data → npcsh-1.1.19.data}/data/npcsh/npc_team/search.jinx +0 -0
  179. {npcsh-1.1.17.data → npcsh-1.1.19.data}/data/npcsh/npc_team/serve.jinx +0 -0
  180. {npcsh-1.1.17.data → npcsh-1.1.19.data}/data/npcsh/npc_team/set.jinx +0 -0
  181. {npcsh-1.1.17.data → npcsh-1.1.19.data}/data/npcsh/npc_team/sh.jinx +0 -0
  182. {npcsh-1.1.17.data → npcsh-1.1.19.data}/data/npcsh/npc_team/shh.jinx +0 -0
  183. {npcsh-1.1.17.data → npcsh-1.1.19.data}/data/npcsh/npc_team/sibiji.npc +0 -0
  184. {npcsh-1.1.17.data → npcsh-1.1.19.data}/data/npcsh/npc_team/sibiji.png +0 -0
  185. {npcsh-1.1.17.data → npcsh-1.1.19.data}/data/npcsh/npc_team/spool.png +0 -0
  186. {npcsh-1.1.17.data → npcsh-1.1.19.data}/data/npcsh/npc_team/switch.jinx +0 -0
  187. {npcsh-1.1.17.data → npcsh-1.1.19.data}/data/npcsh/npc_team/switches.jinx +0 -0
  188. {npcsh-1.1.17.data → npcsh-1.1.19.data}/data/npcsh/npc_team/teamviz.jinx +0 -0
  189. {npcsh-1.1.17.data → npcsh-1.1.19.data}/data/npcsh/npc_team/trigger.jinx +0 -0
  190. {npcsh-1.1.17.data → npcsh-1.1.19.data}/data/npcsh/npc_team/type_text.jinx +0 -0
  191. {npcsh-1.1.17.data → npcsh-1.1.19.data}/data/npcsh/npc_team/usage.jinx +0 -0
  192. {npcsh-1.1.17.data → npcsh-1.1.19.data}/data/npcsh/npc_team/verbose.jinx +0 -0
  193. {npcsh-1.1.17.data → npcsh-1.1.19.data}/data/npcsh/npc_team/vixynt.jinx +0 -0
  194. {npcsh-1.1.17.data → npcsh-1.1.19.data}/data/npcsh/npc_team/wait.jinx +0 -0
  195. {npcsh-1.1.17.data → npcsh-1.1.19.data}/data/npcsh/npc_team/yap.png +0 -0
  196. {npcsh-1.1.17.dist-info → npcsh-1.1.19.dist-info}/licenses/LICENSE +0 -0
  197. {npcsh-1.1.17.dist-info → npcsh-1.1.19.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,356 @@
1
+ jinx_name: alicanto
2
+ description: Deep research mode - multi-perspective exploration with gold insights and cliff warnings
3
+ npc: forenpc
4
+ inputs:
5
+ - query: null
6
+ - num_npcs: 5
7
+ - depth: 3
8
+ - model: null
9
+ - provider: null
10
+ - max_steps: 20
11
+ - skip_research: true
12
+ - exploration: 0.3
13
+ - creativity: 0.5
14
+ - format: report
15
+ - browse: false
16
+
17
+ steps:
18
+ - name: alicanto_research
19
+ engine: python
20
+ code: |
21
+ import os
22
+ import sys
23
+ import tty
24
+ import termios
25
+ from termcolor import colored
26
+
27
+ from npcpy.llm_funcs import get_llm_response
28
+ from npcpy.data.web import search_web
29
+ from npcpy.npc_compiler import NPC
30
+
31
+ npc = context.get('npc')
32
+ team = context.get('team')
33
+ messages = context.get('messages', [])
34
+
35
+ # Resolve npc if it's a string (npc name) rather than NPC object
36
+ if isinstance(npc, str) and team:
37
+ npc = team.get(npc) if hasattr(team, 'get') else None
38
+ elif isinstance(npc, str):
39
+ npc = None # Can't use string npc without team to resolve it
40
+
41
+ # ========== TUI Helper Functions ==========
42
+ def get_terminal_size():
43
+ try:
44
+ size = os.get_terminal_size()
45
+ return size.columns, size.lines
46
+ except:
47
+ return 80, 24
48
+
49
+ def research_tui_browser(result):
50
+ """Interactive TUI browser for research results"""
51
+ perspectives = result.get('perspectives', '').split('\n')
52
+ insights = result.get('insights', [])
53
+ gold = result.get('gold', [])
54
+ cliffs = result.get('cliffs', [])
55
+ report = result.get('report', '')
56
+
57
+ # Build tabs
58
+ tabs = ['Gold', 'Cliffs', 'Insights', 'Report']
59
+ current_tab = 0
60
+
61
+ width, height = get_terminal_size()
62
+ selected = 0
63
+ scroll = 0
64
+ list_height = height - 5
65
+
66
+ fd = sys.stdin.fileno()
67
+ old_settings = termios.tcgetattr(fd)
68
+
69
+ try:
70
+ tty.setcbreak(fd)
71
+ sys.stdout.write('\033[?25l')
72
+ sys.stdout.write('\033[2J\033[H')
73
+
74
+ while True:
75
+ width, height = get_terminal_size()
76
+ list_height = height - 5
77
+
78
+ # Get current content
79
+ if current_tab == 0: # Gold
80
+ items = gold if gold else ['No gold insights marked']
81
+ elif current_tab == 1: # Cliffs
82
+ items = cliffs if cliffs else ['No cliff warnings marked']
83
+ elif current_tab == 2: # Insights
84
+ items = [i[:200] for i in insights] if insights else ['No insights yet']
85
+ else: # Report
86
+ items = report.split('\n') if report else ['No report generated']
87
+
88
+ if selected >= len(items):
89
+ selected = max(0, len(items) - 1)
90
+
91
+ if selected < scroll:
92
+ scroll = selected
93
+ elif selected >= scroll + list_height:
94
+ scroll = selected - list_height + 1
95
+
96
+ sys.stdout.write('\033[H')
97
+
98
+ # Tab bar
99
+ tab_bar = " "
100
+ for i, tab in enumerate(tabs):
101
+ if i == current_tab:
102
+ tab_bar += f'\033[43;30;1m {tab} \033[0m '
103
+ else:
104
+ tab_bar += f'\033[90m {tab} \033[0m '
105
+ sys.stdout.write(f'{tab_bar.ljust(width)}\n')
106
+ sys.stdout.write(f'\033[90m{"─" * width}\033[0m\n')
107
+
108
+ # Content
109
+ for i in range(list_height):
110
+ idx = scroll + i
111
+ sys.stdout.write(f'\033[{3+i};1H\033[K')
112
+ if idx >= len(items):
113
+ continue
114
+
115
+ line = str(items[idx])[:width-2]
116
+ if current_tab in [0, 1, 2] and idx == selected:
117
+ sys.stdout.write(f'\033[47;30;1m>{line.ljust(width-2)}\033[0m')
118
+ else:
119
+ # Color gold/cliff markers
120
+ if '[GOLD]' in line:
121
+ sys.stdout.write(f'\033[33m {line}\033[0m')
122
+ elif '[CLIFF]' in line:
123
+ sys.stdout.write(f'\033[31m {line}\033[0m')
124
+ else:
125
+ sys.stdout.write(f' {line}')
126
+
127
+ # Status bar
128
+ sys.stdout.write(f'\033[{height-2};1H\033[K\033[90m{"─" * width}\033[0m')
129
+ counts = f"Gold: {len(gold)} | Cliffs: {len(cliffs)} | Insights: {len(insights)}"
130
+ sys.stdout.write(f'\033[{height-1};1H\033[K {counts}'.ljust(width)[:width])
131
+ sys.stdout.write(f'\033[{height};1H\033[K\033[43;30m h/l:Tabs j/k:Nav Enter:View q:Quit [{selected+1}/{len(items)}] \033[0m')
132
+
133
+ sys.stdout.flush()
134
+
135
+ c = sys.stdin.read(1)
136
+
137
+ if c == '\x1b':
138
+ c2 = sys.stdin.read(1)
139
+ if c2 == '[':
140
+ c3 = sys.stdin.read(1)
141
+ if c3 == 'A' and selected > 0:
142
+ selected -= 1
143
+ elif c3 == 'B' and selected < len(items) - 1:
144
+ selected += 1
145
+ elif c3 == 'C': # Right
146
+ current_tab = (current_tab + 1) % len(tabs)
147
+ selected = 0
148
+ scroll = 0
149
+ elif c3 == 'D': # Left
150
+ current_tab = (current_tab - 1) % len(tabs)
151
+ selected = 0
152
+ scroll = 0
153
+ else:
154
+ return
155
+ continue
156
+
157
+ if c == 'q' or c == '\x03':
158
+ return
159
+ elif c == 'k' and selected > 0:
160
+ selected -= 1
161
+ elif c == 'j' and selected < len(items) - 1:
162
+ selected += 1
163
+ elif c == 'h':
164
+ current_tab = (current_tab - 1) % len(tabs)
165
+ selected = 0
166
+ scroll = 0
167
+ elif c == 'l':
168
+ current_tab = (current_tab + 1) % len(tabs)
169
+ selected = 0
170
+ scroll = 0
171
+ elif c in ('\r', '\n') and current_tab < 3:
172
+ # Show full item
173
+ item = items[selected] if selected < len(items) else ''
174
+ print(f'\033[2J\033[H{item}\n\nPress any key to continue...')
175
+ sys.stdout.flush()
176
+ sys.stdin.read(1)
177
+ sys.stdout.write('\033[2J\033[H')
178
+
179
+ finally:
180
+ termios.tcsetattr(fd, termios.TCSADRAIN, old_settings)
181
+ sys.stdout.write('\033[?25h')
182
+ sys.stdout.write('\033[2J\033[H')
183
+ sys.stdout.flush()
184
+
185
+ query = context.get('query')
186
+ num_npcs = int(context.get('num_npcs', 5))
187
+ depth = int(context.get('depth', 3))
188
+ max_steps = int(context.get('max_steps', 20))
189
+ skip_research = context.get('skip_research', True)
190
+ exploration = float(context.get('exploration', 0.3))
191
+ creativity = float(context.get('creativity', 0.5))
192
+ output_format = context.get('format', 'report')
193
+
194
+ # Handle case where npc might be a string (npc name) or NPC object
195
+ model = context.get('model') or (npc.model if npc and hasattr(npc, 'model') else 'gemini-1.5-pro')
196
+ provider = context.get('provider') or (npc.provider if npc and hasattr(npc, 'provider') else 'gemini')
197
+
198
+ if not query:
199
+ context['output'] = """Usage: /alicanto <research query>
200
+
201
+ Options:
202
+ --num-npcs N Number of research perspectives (default: 5)
203
+ --depth N Research depth (default: 3)
204
+ --max-steps N Maximum research steps (default: 20)
205
+ --exploration F Exploration factor 0-1 (default: 0.3)
206
+ --creativity F Creativity factor 0-1 (default: 0.5)
207
+ --format FORMAT Output: report|summary|full (default: report)
208
+
209
+ Example: /alicanto What are the latest advances in quantum computing?"""
210
+ context['messages'] = messages
211
+ exit()
212
+
213
+ print(f"""
214
+ █████╗ ██╗ ██╗ ██████╗ █████╗ ███╗ ██╗████████╗ ██████╗
215
+ ██╔══██╗██║ ██║██╔════╝██╔══██╗████╗ ██║╚══██╔══╝██╔═══██╗
216
+ ███████║██║ ██║██║ ███████║██╔██╗ ██║ ██║ ██║ ██║
217
+ ██╔══██║██║ ██║██║ ██╔══██║██║╚██╗██║ ██║ ██║ ██║
218
+ ██║ ██║███████╗██║╚██████╗██║ ██║██║ ╚████║ ██║ ╚██████╔╝
219
+ ╚═╝ ╚═╝╚══════╝╚═╝ ╚═════╝╚═╝ ╚═╝╚═╝ ╚═══╝ ╚═╝ ╚═════╝
220
+
221
+ Deep Research Mode
222
+ Query: {query}
223
+ Perspectives: {num_npcs} | Depth: {depth} | Max Steps: {max_steps}
224
+ """)
225
+
226
+ # Generate research perspectives
227
+ perspectives_prompt = f"""Generate {num_npcs} distinct research perspectives for investigating: "{query}"
228
+
229
+ For each perspective, provide:
230
+ 1. Name (a descriptive title)
231
+ 2. Approach (how this perspective would investigate)
232
+ 3. Key questions to explore
233
+
234
+ Return as a numbered list."""
235
+
236
+ print(colored("Generating research perspectives...", "cyan"))
237
+ resp = get_llm_response(
238
+ perspectives_prompt,
239
+ model=model,
240
+ provider=provider,
241
+ npc=npc
242
+ )
243
+ perspectives = str(resp.get('response', ''))
244
+ print(perspectives)
245
+
246
+ # Conduct web research if not skipped
247
+ research_findings = ""
248
+ if not skip_research:
249
+ print(colored("\nConducting web research...", "cyan"))
250
+ try:
251
+ search_results = search_web(query, n_results=5)
252
+ if search_results:
253
+ research_findings = "\n\nWeb Research Findings:\n"
254
+ for i, result in enumerate(search_results[:5], 1):
255
+ title = result.get('title', 'No title')
256
+ snippet = result.get('snippet', result.get('body', ''))[:200]
257
+ research_findings += f"\n{i}. {title}\n {snippet}...\n"
258
+ print(colored(f"Found {len(search_results)} sources", "green"))
259
+ except Exception as e:
260
+ print(colored(f"Web search error: {e}", "yellow"))
261
+
262
+ # Multi-step exploration from each perspective
263
+ all_insights = []
264
+ gold_insights = [] # Key valuable findings
265
+ cliff_warnings = [] # Potential pitfalls or caveats
266
+
267
+ for step in range(min(depth, max_steps)):
268
+ print(colored(f"\n--- Research Depth {step + 1}/{depth} ---", "cyan"))
269
+
270
+ explore_prompt = f"""Research query: "{query}"
271
+
272
+ Perspectives generated:
273
+ {perspectives}
274
+
275
+ {research_findings}
276
+
277
+ Previous insights: {all_insights[-3:] if all_insights else 'None yet'}
278
+
279
+ For depth level {step + 1}:
280
+ 1. Explore deeper implications from each perspective
281
+ 2. Identify GOLD insights (valuable, non-obvious findings) - mark with [GOLD]
282
+ 3. Identify CLIFF warnings (pitfalls, caveats, risks) - mark with [CLIFF]
283
+ 4. Connect insights across perspectives
284
+
285
+ Exploration factor: {exploration} (higher = more diverse exploration)
286
+ Creativity factor: {creativity} (higher = more novel connections)"""
287
+
288
+ resp = get_llm_response(
289
+ explore_prompt,
290
+ model=model,
291
+ provider=provider,
292
+ temperature=creativity,
293
+ npc=npc
294
+ )
295
+
296
+ step_insights = str(resp.get('response', ''))
297
+ print(step_insights)
298
+
299
+ # Extract gold and cliff markers
300
+ if '[GOLD]' in step_insights:
301
+ gold_insights.extend([line.strip() for line in step_insights.split('\n') if '[GOLD]' in line])
302
+ if '[CLIFF]' in step_insights:
303
+ cliff_warnings.extend([line.strip() for line in step_insights.split('\n') if '[CLIFF]' in line])
304
+
305
+ all_insights.append(step_insights)
306
+
307
+ # Generate final synthesis
308
+ print(colored("\n--- Synthesizing Research ---", "cyan"))
309
+
310
+ synthesis_prompt = f"""Synthesize research on: "{query}"
311
+
312
+ All insights gathered:
313
+ {chr(10).join(all_insights)}
314
+
315
+ Gold insights identified:
316
+ {chr(10).join(gold_insights) if gold_insights else 'None explicitly marked'}
317
+
318
+ Cliff warnings identified:
319
+ {chr(10).join(cliff_warnings) if cliff_warnings else 'None explicitly marked'}
320
+
321
+ Generate a {output_format} that:
322
+ 1. Summarizes key findings
323
+ 2. Highlights the most valuable insights (gold)
324
+ 3. Notes important caveats and risks (cliffs)
325
+ 4. Provides actionable conclusions"""
326
+
327
+ resp = get_llm_response(
328
+ synthesis_prompt,
329
+ model=model,
330
+ provider=provider,
331
+ npc=npc
332
+ )
333
+
334
+ final_report = str(resp.get('response', ''))
335
+ print("\n" + "="*60)
336
+ print(colored("ALICANTO RESEARCH REPORT", "green", attrs=['bold']))
337
+ print("="*60)
338
+ print(final_report)
339
+
340
+ alicanto_result = {
341
+ 'query': query,
342
+ 'perspectives': perspectives,
343
+ 'insights': all_insights,
344
+ 'gold': gold_insights,
345
+ 'cliffs': cliff_warnings,
346
+ 'report': final_report
347
+ }
348
+
349
+ context['output'] = final_report
350
+ context['messages'] = messages
351
+ context['alicanto_result'] = alicanto_result
352
+
353
+ # Launch interactive browser automatically
354
+ if gold_insights or cliff_warnings or all_insights:
355
+ print(colored("\nLaunching results browser...", "cyan"))
356
+ research_tui_browser(alicanto_result)