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,455 +0,0 @@
1
- jinx_name: wander
2
- description: Interactive wandering mode - creative exploration with live TUI dashboard
3
- inputs:
4
- - problem: null
5
- - environment: null
6
- - low_temp: 0.3
7
- - high_temp: 1.5
8
- - model: null
9
- - provider: null
10
-
11
- steps:
12
- - name: wander_interactive
13
- engine: python
14
- code: |
15
- import os
16
- import sys
17
- import tty
18
- import termios
19
- import random
20
- import threading
21
- import time
22
- import textwrap
23
- from datetime import datetime
24
- from termcolor import colored
25
-
26
- from npcpy.llm_funcs import get_llm_response
27
-
28
- npc = context.get('npc')
29
- team = context.get('team')
30
- messages = context.get('messages', [])
31
-
32
- problem = context.get('problem')
33
- environment = context.get('environment')
34
- low_temp = float(context.get('low_temp', 0.3))
35
- high_temp = float(context.get('high_temp', 1.5))
36
-
37
- # Resolve npc if it's a string (npc name) rather than NPC object
38
- if isinstance(npc, str) and team:
39
- npc = team.get(npc) if hasattr(team, 'get') else None
40
- elif isinstance(npc, str):
41
- npc = None
42
-
43
- model = context.get('model') or (npc.model if npc and hasattr(npc, 'model') else None)
44
- provider = context.get('provider') or (npc.provider if npc and hasattr(npc, 'provider') else None)
45
-
46
- if not problem:
47
- context['output'] = """Usage: /wander <problem to explore>
48
-
49
- Interactive TUI Controls:
50
- SPACE - Generate new wandering stream
51
- t - Toggle temperature (focused/creative/wild)
52
- e - Trigger random event
53
- s - Star current insight (save to favorites)
54
- r - Request reflection/synthesis
55
- j/k - Scroll through output
56
- Tab - Switch panels (Environment/Streams/Starred)
57
- q - Quit and show final synthesis
58
-
59
- Example: /wander How might we reimagine urban transportation?"""
60
- context['messages'] = messages
61
- exit()
62
-
63
- # ========== State ==========
64
- class WanderState:
65
- def __init__(self):
66
- self.environment = ""
67
- self.streams = [] # List of {temp, mode, insight, event, starred, timestamp}
68
- self.starred = [] # Starred insights
69
- self.current_temp = low_temp
70
- self.temp_mode = "focused" # focused, creative, wild
71
- self.scroll_offset = 0
72
- self.current_panel = 0 # 0=main, 1=streams, 2=starred
73
- self.status = "Ready"
74
- self.generating = False
75
- self.last_output = ""
76
-
77
- state = WanderState()
78
-
79
- # ========== TUI Helpers ==========
80
- def get_size():
81
- try:
82
- s = os.get_terminal_size()
83
- return s.columns, s.lines
84
- except:
85
- return 80, 24
86
-
87
- def wrap_text(text, width):
88
- lines = []
89
- for line in text.split('\n'):
90
- if len(line) <= width:
91
- lines.append(line)
92
- else:
93
- lines.extend(textwrap.wrap(line, width) or [''])
94
- return lines
95
-
96
- def draw_box(x, y, w, h, title="", color="\033[90m"):
97
- """Draw a box with optional title"""
98
- out = []
99
- # Top border
100
- if title:
101
- title_part = f" {title} "
102
- border = "─" * ((w - len(title_part) - 2) // 2)
103
- top = f"┌{border}{title_part}{border}{'─' * ((w - len(title_part) - 2) % 2)}┐"
104
- else:
105
- top = "┌" + "─" * (w - 2) + "┐"
106
- out.append(f"\033[{y};{x}H{color}{top}\033[0m")
107
- # Sides
108
- for i in range(1, h - 1):
109
- out.append(f"\033[{y+i};{x}H{color}│\033[0m")
110
- out.append(f"\033[{y+i};{x+w-1}H{color}│\033[0m")
111
- # Bottom
112
- out.append(f"\033[{y+h-1};{x}H{color}└{'─' * (w - 2)}┘\033[0m")
113
- return ''.join(out)
114
-
115
- def render_screen():
116
- width, height = get_size()
117
- out = []
118
-
119
- # Clear screen
120
- out.append("\033[2J\033[H")
121
-
122
- # ===== HEADER =====
123
- header = f" WANDER - {problem[:width-20]}... " if len(problem) > width-20 else f" WANDER - {problem} "
124
- temp_color = {"focused": "\033[36m", "creative": "\033[35m", "wild": "\033[31m"}[state.temp_mode]
125
- temp_info = f"{temp_color}[{state.temp_mode} T={state.current_temp:.1f}]\033[0m"
126
- status_color = "\033[33m" if state.generating else "\033[32m"
127
- status_info = f"{status_color}[{state.status}]\033[0m"
128
-
129
- out.append(f"\033[1;1H\033[45;37;1m{header.ljust(width)}\033[0m")
130
- out.append(f"\033[1;{width-35}H{temp_info} {status_info}")
131
-
132
- # ===== LAYOUT =====
133
- # Left panel: Environment + Controls (30% width)
134
- # Right panel: Main output / Streams / Starred (70% width)
135
- left_w = max(25, width // 3)
136
- right_w = width - left_w - 1
137
- panel_h = height - 4
138
-
139
- # ===== LEFT PANEL: Environment =====
140
- out.append(draw_box(1, 3, left_w, panel_h // 2, "Environment", "\033[36m"))
141
- env_lines = wrap_text(state.environment or "Press 'e' to generate...", left_w - 4)
142
- for i, line in enumerate(env_lines[:panel_h // 2 - 3]):
143
- out.append(f"\033[{4+i};3H{line[:left_w-4]}")
144
-
145
- # ===== LEFT PANEL: Controls =====
146
- ctrl_y = 3 + panel_h // 2
147
- out.append(draw_box(1, ctrl_y, left_w, panel_h // 2, "Controls", "\033[33m"))
148
- controls = [
149
- "SPACE - New stream",
150
- f"t - Temp: {state.temp_mode}",
151
- "e - Trigger event",
152
- "s - Star insight",
153
- "r - Reflect/synthesize",
154
- "Tab - Switch panel",
155
- "j/k - Scroll",
156
- "q - Quit",
157
- "",
158
- f"Streams: {len(state.streams)}",
159
- f"Starred: {len(state.starred)}",
160
- ]
161
- for i, ctrl in enumerate(controls[:panel_h // 2 - 3]):
162
- out.append(f"\033[{ctrl_y+1+i};3H\033[90m{ctrl[:left_w-4]}\033[0m")
163
-
164
- # ===== RIGHT PANEL =====
165
- panel_titles = ["Output", "Stream History", "Starred Insights"]
166
- panel_color = ["\033[37m", "\033[36m", "\033[33m"][state.current_panel]
167
- out.append(draw_box(left_w + 1, 3, right_w, panel_h, panel_titles[state.current_panel], panel_color))
168
-
169
- # Panel content
170
- content_w = right_w - 4
171
- content_h = panel_h - 3
172
- content_lines = []
173
-
174
- if state.current_panel == 0: # Main output
175
- if state.last_output:
176
- content_lines = wrap_text(state.last_output, content_w)
177
- else:
178
- content_lines = ["", " Press SPACE to begin wandering...", "",
179
- " The AI will explore your problem from",
180
- " different perspectives and temperatures.", "",
181
- " Star insights you find valuable with 's'.",
182
- " Request synthesis anytime with 'r'."]
183
-
184
- elif state.current_panel == 1: # Stream history
185
- for i, stream in enumerate(reversed(state.streams)):
186
- starred = "★ " if stream.get('starred') else " "
187
- mode_color = {"focused": "\033[36m", "creative": "\033[35m", "wild": "\033[31m"}.get(stream.get('mode', ''), '')
188
- header = f"{starred}{mode_color}Stream {len(state.streams)-i} ({stream.get('mode', '?')}, T={stream.get('temp', 0):.1f})\033[0m"
189
- content_lines.append(header)
190
- preview = stream.get('insight', '')[:100].replace('\n', ' ')
191
- content_lines.append(f" {preview}...")
192
- content_lines.append("")
193
-
194
- elif state.current_panel == 2: # Starred
195
- if not state.starred:
196
- content_lines = ["", " No starred insights yet.", "", " Press 's' after generating a stream", " to star valuable insights."]
197
- else:
198
- for i, item in enumerate(state.starred):
199
- content_lines.append(f"★ {i+1}. [{item.get('mode', '?')}]")
200
- for line in wrap_text(item.get('insight', ''), content_w - 4):
201
- content_lines.append(f" {line}")
202
- content_lines.append("")
203
-
204
- # Apply scroll and render content
205
- visible = content_lines[state.scroll_offset:state.scroll_offset + content_h]
206
- for i, line in enumerate(visible):
207
- # Strip ANSI for length calc but keep for display
208
- out.append(f"\033[{4+i};{left_w+3}H{line[:content_w]}")
209
-
210
- # Scroll indicator
211
- if len(content_lines) > content_h:
212
- scroll_pct = state.scroll_offset / max(1, len(content_lines) - content_h)
213
- indicator_pos = int(scroll_pct * (content_h - 1))
214
- out.append(f"\033[{4+indicator_pos};{width-1}H\033[33m▐\033[0m")
215
-
216
- # ===== FOOTER =====
217
- panel_tabs = ""
218
- for i, name in enumerate(["Output", "Streams", "Starred"]):
219
- if i == state.current_panel:
220
- panel_tabs += f"\033[7m {name} \033[0m "
221
- else:
222
- panel_tabs += f"\033[90m {name} \033[0m "
223
-
224
- out.append(f"\033[{height-1};1H\033[90m{'─' * width}\033[0m")
225
- out.append(f"\033[{height};1H{panel_tabs}")
226
-
227
- sys.stdout.write(''.join(out))
228
- sys.stdout.flush()
229
-
230
- # ========== Actions ==========
231
- def generate_environment():
232
- state.status = "Generating environment..."
233
- state.generating = True
234
- render_screen()
235
-
236
- env_prompt = f"""Create a vivid, metaphorical environment for wandering through while exploring:
237
- "{problem}"
238
-
239
- The environment should:
240
- 1. Have distinct regions that map to aspects of the problem
241
- 2. Include sensory details (sights, sounds, textures)
242
- 3. Feel alive and explorable
243
- 4. Be described in 4-6 evocative sentences
244
-
245
- Respond with only the description."""
246
-
247
- resp = get_llm_response(env_prompt, model=model, provider=provider, temperature=0.8, npc=npc)
248
- state.environment = str(resp.get('response', 'A vast conceptual landscape stretches before you.'))
249
- state.status = "Ready"
250
- state.generating = False
251
-
252
- def generate_stream():
253
- if state.generating:
254
- return
255
-
256
- state.status = f"Wandering ({state.temp_mode})..."
257
- state.generating = True
258
- render_screen()
259
-
260
- # Build context from recent streams
261
- recent = state.streams[-3:] if state.streams else []
262
- recent_context = "\n".join([s.get('insight', '')[:200] for s in recent]) if recent else "Starting fresh"
263
-
264
- wander_prompt = f"""You are wandering through: {state.environment or 'a conceptual landscape'}
265
-
266
- Problem: "{problem}"
267
-
268
- Recent thoughts: {recent_context}
269
-
270
- In this {state.temp_mode} exploration (temperature {state.current_temp}):
271
- - Let associations flow freely
272
- - Notice unexpected connections
273
- - Follow interesting tangents
274
- - Share what emerges
275
-
276
- Respond naturally, 2-4 paragraphs."""
277
-
278
- resp = get_llm_response(wander_prompt, model=model, provider=provider,
279
- temperature=state.current_temp, npc=npc)
280
- insight = str(resp.get('response', ''))
281
-
282
- stream = {
283
- 'temp': state.current_temp,
284
- 'mode': state.temp_mode,
285
- 'insight': insight,
286
- 'event': None,
287
- 'starred': False,
288
- 'timestamp': datetime.now().isoformat()
289
- }
290
- state.streams.append(stream)
291
- state.last_output = insight
292
- state.scroll_offset = 0
293
- state.status = "Ready"
294
- state.generating = False
295
-
296
- def trigger_event():
297
- if state.generating:
298
- return
299
-
300
- state.status = "Event occurring..."
301
- state.generating = True
302
- render_screen()
303
-
304
- event_types = ["encounter", "discovery", "obstacle", "revelation", "memory", "transformation"]
305
- event_type = random.choice(event_types)
306
-
307
- event_prompt = f"""In the environment: {state.environment or 'a conceptual landscape'}
308
- While exploring "{problem}", a {event_type} occurs.
309
-
310
- Describe this {event_type} in 2-3 vivid sentences.
311
- Make it metaphorical and thought-provoking."""
312
-
313
- resp = get_llm_response(event_prompt, model=model, provider=provider, temperature=1.0, npc=npc)
314
- event = str(resp.get('response', ''))
315
-
316
- state.last_output = f"[{event_type.upper()}]\n\n{event}"
317
- state.scroll_offset = 0
318
- state.status = "Ready"
319
- state.generating = False
320
-
321
- def toggle_temp():
322
- modes = ["focused", "creative", "wild"]
323
- temps = [low_temp, (low_temp + high_temp) / 2, high_temp]
324
- idx = modes.index(state.temp_mode)
325
- idx = (idx + 1) % 3
326
- state.temp_mode = modes[idx]
327
- state.current_temp = temps[idx]
328
-
329
- def star_current():
330
- if state.streams and not state.streams[-1].get('starred'):
331
- state.streams[-1]['starred'] = True
332
- state.starred.append(state.streams[-1])
333
- state.status = "★ Starred!"
334
-
335
- def synthesize():
336
- if state.generating or not state.streams:
337
- return
338
-
339
- state.status = "Synthesizing..."
340
- state.generating = True
341
- render_screen()
342
-
343
- all_insights = "\n---\n".join([s.get('insight', '') for s in state.streams])
344
- starred_insights = "\n---\n".join([s.get('insight', '') for s in state.starred]) if state.starred else "None"
345
-
346
- synth_prompt = f"""After wandering through thoughts about "{problem}":
347
-
348
- All explorations:
349
- {all_insights}
350
-
351
- Starred insights:
352
- {starred_insights}
353
-
354
- Synthesize:
355
- 1. Key themes and patterns
356
- 2. Most surprising connections
357
- 3. Questions worth pursuing
358
- 4. Potential next steps
359
-
360
- Be concise but insightful."""
361
-
362
- resp = get_llm_response(synth_prompt, model=model, provider=provider, temperature=0.4, npc=npc)
363
- synthesis = str(resp.get('response', ''))
364
-
365
- state.last_output = "=== SYNTHESIS ===\n\n" + synthesis
366
- state.scroll_offset = 0
367
- state.status = "Ready"
368
- state.generating = False
369
-
370
- # ========== Main Loop ==========
371
- fd = sys.stdin.fileno()
372
- old_settings = termios.tcgetattr(fd)
373
-
374
- try:
375
- tty.setcbreak(fd)
376
- sys.stdout.write('\033[?25l') # Hide cursor
377
- sys.stdout.flush()
378
-
379
- # Generate environment AFTER TUI is set up
380
- if environment:
381
- state.environment = environment
382
- else:
383
- generate_environment()
384
-
385
- render_screen()
386
-
387
- while True:
388
- c = sys.stdin.read(1)
389
-
390
- if c == 'q' or c == '\x03': # q or Ctrl+C
391
- break
392
- elif c == ' ': # Space - new stream
393
- generate_stream()
394
- elif c == 't': # Toggle temperature
395
- toggle_temp()
396
- elif c == 'e': # Trigger event
397
- trigger_event()
398
- elif c == 's': # Star
399
- star_current()
400
- elif c == 'r': # Reflect/synthesize
401
- synthesize()
402
- elif c == '\t': # Tab - switch panel
403
- state.current_panel = (state.current_panel + 1) % 3
404
- state.scroll_offset = 0
405
- elif c == 'j': # Scroll down
406
- state.scroll_offset += 1
407
- elif c == 'k': # Scroll up
408
- state.scroll_offset = max(0, state.scroll_offset - 1)
409
- elif c == '\x1b': # Escape sequence
410
- c2 = sys.stdin.read(1)
411
- if c2 == '[':
412
- c3 = sys.stdin.read(1)
413
- if c3 == 'A': # Up
414
- state.scroll_offset = max(0, state.scroll_offset - 1)
415
- elif c3 == 'B': # Down
416
- state.scroll_offset += 1
417
- elif c3 == 'C': # Right - next panel
418
- state.current_panel = (state.current_panel + 1) % 3
419
- state.scroll_offset = 0
420
- elif c3 == 'D': # Left - prev panel
421
- state.current_panel = (state.current_panel - 1) % 3
422
- state.scroll_offset = 0
423
-
424
- render_screen()
425
-
426
- finally:
427
- termios.tcsetattr(fd, termios.TCSADRAIN, old_settings)
428
- sys.stdout.write('\033[?25h') # Show cursor
429
- sys.stdout.write('\033[2J\033[H') # Clear screen
430
- sys.stdout.flush()
431
-
432
- # Final output
433
- if state.streams:
434
- print(colored("=== WANDER SESSION COMPLETE ===\n", "green"))
435
- print(f"Problem: {problem}")
436
- print(f"Streams: {len(state.streams)}")
437
- print(f"Starred: {len(state.starred)}\n")
438
-
439
- if state.starred:
440
- print(colored("Starred Insights:", "yellow"))
441
- for i, s in enumerate(state.starred):
442
- print(f"\n{i+1}. [{s.get('mode')}] {s.get('insight')[:300]}...")
443
-
444
- print(colored("\n--- Final Synthesis ---", "cyan"))
445
- synthesize()
446
- print(state.last_output)
447
-
448
- context['output'] = state.last_output
449
- context['messages'] = messages
450
- context['wander_result'] = {
451
- 'problem': problem,
452
- 'environment': state.environment,
453
- 'streams': state.streams,
454
- 'starred': state.starred,
455
- }