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
@@ -104,8 +104,8 @@ steps:
104
104
 
105
105
  # Search filter
106
106
  if ui.search_query:
107
- q = ui.search_query.lower()
108
- items = [j for j in items if q in j['name'].lower() or q in j['description'].lower()]
107
+ srch = ui.search_query.lower()
108
+ items = [j for j in items if srch in j['name'].lower() or srch in j['description'].lower()]
109
109
 
110
110
  ui.filtered = items
111
111
  # Clamp selection
@@ -129,8 +129,8 @@ steps:
129
129
  # ── header ──
130
130
  hdr = " Jinxs "
131
131
  pad = '=' * W
132
- out.append(wline(1, f"\033[44;37;1m{pad}\033[0m"))
133
- out.append(f"\033[1;{max(1, (W - len(hdr)) // 2)}H\033[44;37;1m{hdr}\033[0m")
132
+ out.append(wline(1, f"\033[7;1m{pad}\033[0m"))
133
+ out.append(f"\033[1;{max(1, (W - len(hdr)) // 2)}H\033[7;1m{hdr}\033[0m")
134
134
 
135
135
  # ── tabs ──
136
136
  tb = ""
@@ -184,7 +184,7 @@ steps:
184
184
  foot = " [j/k] Scroll [q/Esc] Back"
185
185
  else:
186
186
  foot = " [Tab] Filter [j/k] Nav [Enter] Detail [/] Search [q] Quit"
187
- out.append(wline(H, f"\033[44;37m{foot[:W].ljust(W)}\033[0m"))
187
+ out.append(wline(H, f"\033[7m{foot[:W].ljust(W)}\033[0m"))
188
188
 
189
189
  sys.stdout.write(''.join(out))
190
190
  sys.stdout.flush()
@@ -296,10 +296,10 @@ steps:
296
296
  return True
297
297
 
298
298
  def handle_esc():
299
- if select.select([sys.stdin], [], [], 0.05)[0]:
300
- c2 = sys.stdin.read(1)
299
+ if select.select([fd], [], [], 0.05)[0]:
300
+ c2 = os.read(fd, 1).decode('latin-1')
301
301
  if c2 == '[':
302
- c3 = sys.stdin.read(1)
302
+ c3 = os.read(fd, 1).decode('latin-1')
303
303
  if c3 == 'A':
304
304
  nav_up()
305
305
  elif c3 == 'B':
@@ -322,10 +322,10 @@ steps:
322
322
  def handle_search(c):
323
323
  if c == '\x1b':
324
324
  # Check if arrow key or bare esc
325
- if select.select([sys.stdin], [], [], 0.05)[0]:
326
- c2 = sys.stdin.read(1)
325
+ if select.select([fd], [], [], 0.05)[0]:
326
+ c2 = os.read(fd, 1).decode('latin-1')
327
327
  if c2 == '[':
328
- sys.stdin.read(1) # consume arrow char
328
+ os.read(fd, 1).decode('latin-1') # consume arrow char
329
329
  else:
330
330
  ui.search_mode = False
331
331
  ui.search_buf = ""
@@ -395,7 +395,7 @@ steps:
395
395
  sys.stdout.flush()
396
396
  render()
397
397
  while True:
398
- c = sys.stdin.read(1)
398
+ c = os.read(fd, 1).decode('latin-1')
399
399
  if not handle(c):
400
400
  break
401
401
  render()
@@ -18,4 +18,5 @@ primary_directive: |
18
18
  Use web search and browsing tools to discover new information.
19
19
  jinxs:
20
20
  - lib/core/python
21
- - bin/wander
21
+ - lib/core/sh
22
+ - lib/core/search/web_search
@@ -1,7 +1,11 @@
1
1
  jinx_name: kg
2
2
  description: Interactive knowledge graph browser - explore facts, concepts, and links
3
3
  interactive: true
4
- inputs: []
4
+ inputs:
5
+ - action: ""
6
+ - dream: false
7
+ - backfill: false
8
+ - ops: ""
5
9
  steps:
6
10
  - name: kg_browser
7
11
  engine: python
@@ -12,7 +16,70 @@ steps:
12
16
  import termios
13
17
  import select
14
18
 
15
- if not sys.stdin.isatty():
19
+ _kg_action = (context.get('action') or '').strip().lower()
20
+
21
+ if _kg_action in ('sleep', 'evolve', 'dream'):
22
+ # Route to KG evolution operations
23
+ import traceback
24
+ from npcpy.memory.command_history import CommandHistory, load_kg_from_db, save_kg_to_db
25
+ from npcpy.memory.knowledge_graph import kg_sleep_process, kg_dream_process, kg_backfill_from_memories
26
+
27
+ _npc = context.get('npc')
28
+ _team = context.get('team')
29
+ _msgs = context.get('messages', [])
30
+ _do_dream = _kg_action == 'dream' or str(context.get('dream', '')).lower() in ('true', '1', 'yes')
31
+ _do_backfill = str(context.get('backfill', '')).lower() in ('true', '1', 'yes')
32
+ _ops_str = context.get('ops', '')
33
+ _ops_config = [op.strip() for op in _ops_str.split(',') if op.strip()] if _ops_str else None
34
+
35
+ _model = (_npc.model if _npc and hasattr(_npc, 'model') else None) or (state.chat_model if state else 'llama3.2')
36
+ _provider = (_npc.provider if _npc and hasattr(_npc, 'provider') else None) or (state.chat_provider if state else 'ollama')
37
+
38
+ _team_name = _team.name if _team else '__none__'
39
+ _npc_name = _npc.name if _npc else '__none__'
40
+ _cur_path = os.getcwd()
41
+
42
+ try:
43
+ _db_path = os.getenv('NPCSH_DB_PATH', os.path.expanduser('~/npcsh_history.db'))
44
+ _ch = CommandHistory(_db_path)
45
+ _eng = _ch.engine
46
+
47
+ _result = ''
48
+ if _do_backfill:
49
+ print('Backfilling from approved memories...')
50
+ _stats = kg_backfill_from_memories(_eng, model=_model, provider=_provider, npc=_npc, get_concepts=True, dry_run=False)
51
+ _result += f"Backfill: +{_stats['facts_after'] - _stats['facts_before']} facts, +{_stats['concepts_after'] - _stats['concepts_before']} concepts\n"
52
+
53
+ _kg = load_kg_from_db(_eng, _team_name, _npc_name, _cur_path)
54
+ if not _kg or not _kg.get('facts'):
55
+ context['output'] = _result + 'Knowledge graph is empty. Use /kg backfill=true or have conversations first.'
56
+ context['messages'] = _msgs
57
+ _ch.close()
58
+ exit()
59
+
60
+ _f0 = len(_kg.get('facts', []))
61
+ _c0 = len(_kg.get('concepts', []))
62
+ _label = 'Sleep'
63
+
64
+ _kg, _ = kg_sleep_process(existing_kg=_kg, model=_model, provider=_provider, npc=_npc, operations_config=_ops_config)
65
+
66
+ if _do_dream:
67
+ _label += ' & Dream'
68
+ _kg, _ = kg_dream_process(existing_kg=_kg, model=_model, provider=_provider, npc=_npc)
69
+
70
+ save_kg_to_db(_eng, _kg, _team_name, _npc_name, _cur_path)
71
+ _f1 = len(_kg.get('facts', []))
72
+ _c1 = len(_kg.get('concepts', []))
73
+ _result += f"{_label} complete. Facts: {_f0} -> {_f1} ({_f1-_f0:+}), Concepts: {_c0} -> {_c1} ({_c1-_c0:+})"
74
+ context['output'] = _result
75
+ context['messages'] = _msgs
76
+ _ch.close()
77
+ except Exception as e:
78
+ traceback.print_exc()
79
+ context['output'] = f'KG evolution error: {e}'
80
+ context['messages'] = _msgs
81
+
82
+ elif not sys.stdin.isatty():
16
83
  context['output'] = "KG browser requires an interactive terminal."
17
84
 
18
85
  else:
@@ -166,12 +233,12 @@ steps:
166
233
 
167
234
  def do_search(query):
168
235
  ui.search_results = []
169
- q = f"%{query}%"
236
+ pat = f"%{query}%"
170
237
  with engine.connect() as conn:
171
238
  r = conn.execute(text(
172
239
  "SELECT statement, source_text, type, generation, origin "
173
- "FROM kg_facts WHERE statement LIKE :q ORDER BY rowid DESC"
174
- ), {"q": q})
240
+ "FROM kg_facts WHERE statement LIKE :pat ORDER BY rowid DESC"
241
+ ), {"pat": pat})
175
242
  for row in r:
176
243
  ui.search_results.append({
177
244
  'kind': 'fact',
@@ -377,8 +444,8 @@ steps:
377
444
  # ── header ──
378
445
  hdr = " Knowledge Graph "
379
446
  pad = '=' * W
380
- out.append(wline(1, f"\033[44;37;1m{pad}\033[0m"))
381
- out.append(f"\033[1;{max(1,(W - len(hdr))//2)}H\033[44;37;1m{hdr}\033[0m")
447
+ out.append(wline(1, f"\033[7;1m{pad}\033[0m"))
448
+ out.append(f"\033[1;{max(1,(W - len(hdr))//2)}H\033[7;1m{hdr}\033[0m")
382
449
 
383
450
  # ── tabs ──
384
451
  tb = ""
@@ -439,7 +506,7 @@ steps:
439
506
  foot = " [Tab] Switch [j/k] Select [Enter] Center [Backspace] Back [q] Quit"
440
507
  else:
441
508
  foot = " [Tab] Switch [j/k] Nav [Enter] Detail [/] Search [g] Gen [q] Quit"
442
- out.append(wline(H, f"\033[44;37m{foot[:W].ljust(W)}\033[0m"))
509
+ out.append(wline(H, f"\033[7m{foot[:W].ljust(W)}\033[0m"))
443
510
 
444
511
  sys.stdout.write(''.join(out))
445
512
  sys.stdout.flush()
@@ -779,10 +846,10 @@ steps:
779
846
  return True
780
847
 
781
848
  def handle_esc():
782
- if select.select([sys.stdin], [], [], 0.05)[0]:
783
- c2 = sys.stdin.read(1)
849
+ if select.select([fd], [], [], 0.05)[0]:
850
+ c2 = os.read(fd, 1).decode('latin-1')
784
851
  if c2 == '[':
785
- c3 = sys.stdin.read(1)
852
+ c3 = os.read(fd, 1).decode('latin-1')
786
853
  if c3 == 'A':
787
854
  nav_up()
788
855
  elif c3 == 'B':
@@ -796,10 +863,10 @@ steps:
796
863
 
797
864
  def handle_search_input(c):
798
865
  if c == '\x1b':
799
- if select.select([sys.stdin], [], [], 0.05)[0]:
800
- c2 = sys.stdin.read(1)
866
+ if select.select([fd], [], [], 0.05)[0]:
867
+ c2 = os.read(fd, 1).decode('latin-1')
801
868
  if c2 == '[':
802
- sys.stdin.read(1)
869
+ os.read(fd, 1).decode('latin-1')
803
870
  else:
804
871
  ui.search_mode = False
805
872
  ui.search_buf = ""
@@ -929,7 +996,7 @@ steps:
929
996
  sys.stdout.flush()
930
997
  render()
931
998
  while True:
932
- c = sys.stdin.read(1)
999
+ c = os.read(fd, 1).decode('latin-1')
933
1000
  if not handle(c):
934
1001
  break
935
1002
  render()
@@ -0,0 +1,414 @@
1
+ jinx_name: memories
2
+ description: Interactive TUI for browsing and managing npcsh memories
3
+ interactive: true
4
+ inputs:
5
+ - scope: ""
6
+ steps:
7
+ - name: memory_browser
8
+ engine: python
9
+ code: |
10
+ import os
11
+ import sys
12
+ import tty
13
+ import termios
14
+ import select
15
+ import time
16
+ from datetime import datetime
17
+
18
+ if not sys.stdin.isatty():
19
+ context['output'] = "Memory browser requires an interactive terminal."
20
+
21
+ else:
22
+ from npcpy.memory.command_history import CommandHistory
23
+ from npcsh.config import NPCSH_DB_PATH
24
+ from sqlalchemy import text
25
+
26
+ db_path = os.path.expanduser(NPCSH_DB_PATH)
27
+ command_history = CommandHistory(db_path)
28
+
29
+ # ========== Discover actual status values from DB ==========
30
+ db_statuses = []
31
+ try:
32
+ with command_history.engine.connect() as conn:
33
+ rows = conn.execute(text("SELECT DISTINCT status FROM memory_lifecycle ORDER BY status"))
34
+ db_statuses = [r[0] for r in rows if r[0]]
35
+ except:
36
+ pass
37
+
38
+ # Build tabs: All + pending first, then rest
39
+ ordered = []
40
+ for s in db_statuses:
41
+ if 'pending' in s.lower():
42
+ ordered.insert(0, s)
43
+ else:
44
+ ordered.append(s)
45
+ tab_names = ['All'] + ordered
46
+ tab_filters = [None] + ordered
47
+
48
+ # ========== State ==========
49
+ class MemState:
50
+ tab = 0
51
+ tabs = tab_names
52
+ tab_filt = tab_filters
53
+ memories = []
54
+ sel = 0
55
+ scroll = 0
56
+ preview = False
57
+ msg = ""
58
+ msg_color = "33"
59
+ npc_filter = None
60
+ team_filter = None
61
+ approved_count = 0
62
+ rejected_count = 0
63
+
64
+ st = MemState()
65
+
66
+ # ========== Helpers ==========
67
+ def get_size():
68
+ try:
69
+ s = os.get_terminal_size()
70
+ return s.columns, s.lines
71
+ except:
72
+ return 80, 24
73
+
74
+ def status_icon(sv):
75
+ if not sv:
76
+ return '\033[90m?\033[0m'
77
+ s = sv.lower()
78
+ if 'approved' in s:
79
+ return '\033[1;32m+\033[0m'
80
+ if 'edited' in s:
81
+ return '\033[1;36m~\033[0m'
82
+ if 'rejected' in s:
83
+ return '\033[1;31m-\033[0m'
84
+ if 'pending' in s:
85
+ return '\033[1;33m*\033[0m'
86
+ return '\033[90m?\033[0m'
87
+
88
+ def status_color(sv):
89
+ if not sv:
90
+ return '0'
91
+ s = sv.lower()
92
+ if 'approved' in s:
93
+ return '32'
94
+ if 'edited' in s:
95
+ return '36'
96
+ if 'rejected' in s:
97
+ return '31'
98
+ if 'pending' in s:
99
+ return '33'
100
+ return '0'
101
+
102
+ def load_memories():
103
+ st.memories = []
104
+ try:
105
+ with command_history.engine.connect() as conn:
106
+ sf = st.tab_filt[st.tab] if st.tab < len(st.tab_filt) else None
107
+ q = "SELECT id, created_at, npc, team, directory_path, initial_memory, final_memory, status FROM memory_lifecycle"
108
+ conds = []
109
+ params = {}
110
+
111
+ if sf:
112
+ conds.append("status = :sf")
113
+ params['sf'] = sf
114
+ if st.npc_filter:
115
+ conds.append("npc = :npc")
116
+ params['npc'] = st.npc_filter
117
+ if st.team_filter:
118
+ conds.append("team = :team")
119
+ params['team'] = st.team_filter
120
+
121
+ if conds:
122
+ q += " WHERE " + " AND ".join(conds)
123
+ q += " ORDER BY created_at DESC LIMIT 200"
124
+
125
+ result = conn.execute(text(q), params)
126
+ for row in result:
127
+ st.memories.append({
128
+ 'id': row[0],
129
+ 'created_at': row[1],
130
+ 'npc': row[2],
131
+ 'team': row[3],
132
+ 'scope': row[4] or '',
133
+ 'original': row[5],
134
+ 'final': row[6],
135
+ 'status': row[7]
136
+ })
137
+ except Exception as e:
138
+ st.msg = "DB Error: " + str(e)
139
+ st.msg_color = "31"
140
+
141
+ def do_update(memory_id, new_status, final_mem=None):
142
+ """Update memory status in DB and reload list."""
143
+ try:
144
+ command_history.update_memory_status(memory_id, new_status, final_mem)
145
+ old_count = len(st.memories)
146
+ old_sel = st.sel
147
+ load_memories()
148
+ new_count = len(st.memories)
149
+
150
+ # Clamp selection
151
+ if st.memories:
152
+ st.sel = min(old_sel, len(st.memories) - 1)
153
+ else:
154
+ st.sel = 0
155
+
156
+ # Fix scroll
157
+ _, height = get_size()
158
+ vis = max(1, height - 7)
159
+ if st.sel < st.scroll:
160
+ st.scroll = st.sel
161
+ elif st.sel >= st.scroll + vis:
162
+ st.scroll = st.sel - vis + 1
163
+
164
+ return True
165
+ except Exception as e:
166
+ st.msg = "UPDATE FAILED: " + str(e)
167
+ st.msg_color = "31"
168
+ return False
169
+
170
+ def format_date(dt_str):
171
+ if not dt_str:
172
+ return " "
173
+ try:
174
+ if isinstance(dt_str, str):
175
+ dt = datetime.fromisoformat(dt_str.replace('Z', '+00:00'))
176
+ else:
177
+ dt = dt_str
178
+ return dt.strftime('%m-%d %H:%M')
179
+ except:
180
+ return str(dt_str)[:10]
181
+
182
+ # ========== Rendering ==========
183
+ def render():
184
+ width, height = get_size()
185
+ out = []
186
+
187
+ # Header with session stats
188
+ stats = ""
189
+ if st.approved_count or st.rejected_count:
190
+ stats = " [+" + str(st.approved_count) + " -" + str(st.rejected_count) + "]"
191
+ header = " MEMORIES (" + str(len(st.memories)) + ")" + stats + " "
192
+ out.append("\033[1;1H\033[K\033[7;1m" + header.ljust(width) + "\033[0m")
193
+
194
+ # Tabs
195
+ tab_str = ""
196
+ for i, tab in enumerate(st.tabs):
197
+ if i == st.tab:
198
+ tab_str += "\033[1;7m [" + tab + "] \033[0m"
199
+ else:
200
+ tab_str += " \033[90m" + tab + "\033[0m "
201
+ out.append("\033[2;1H\033[K " + tab_str)
202
+
203
+ # Separator
204
+ out.append("\033[3;1H\033[K\033[90m" + ("-" * width) + "\033[0m")
205
+
206
+ if st.preview and st.memories:
207
+ render_preview(out, width, height)
208
+ else:
209
+ render_list(out, width, height)
210
+
211
+ # Status bar
212
+ out.append("\033[" + str(height-2) + ";1H\033[K\033[90m" + ("-" * width) + "\033[0m")
213
+ out.append("\033[" + str(height-1) + ";1H\033[K")
214
+ if st.msg:
215
+ out.append(" \033[" + st.msg_color + ";1m" + st.msg[:width-2] + "\033[0m")
216
+
217
+ # Footer
218
+ if st.preview:
219
+ foot = " [Esc] Back [a] Approve [x] Reject [j/k] Prev/Next [q] Quit "
220
+ else:
221
+ foot = " [Tab] Filter [j/k] Nav [Enter] Preview [a] Approve [x] Reject [q] Quit "
222
+ out.append("\033[" + str(height) + ";1H\033[K\033[7m" + foot.ljust(width) + "\033[0m")
223
+
224
+ sys.stdout.write(''.join(out))
225
+ sys.stdout.flush()
226
+
227
+ def render_list(out, width, height):
228
+ vis_h = height - 7
229
+ if vis_h < 1:
230
+ vis_h = 1
231
+
232
+ for i in range(vis_h):
233
+ row = 4 + i
234
+ out.append("\033[" + str(row) + ";1H\033[K")
235
+ idx = st.scroll + i
236
+ if idx >= len(st.memories):
237
+ continue
238
+
239
+ mem = st.memories[idx]
240
+ icon = status_icon(mem['status'])
241
+ date_str = format_date(mem['created_at'])
242
+ npc_str = (mem['npc'] or '-')[:8]
243
+ content = (mem['final'] or mem['original'] or '')[:width-35].replace('\n', ' ')
244
+
245
+ line = icon + " " + date_str + " " + npc_str.ljust(9) + content
246
+
247
+ if idx == st.sel:
248
+ out.append("\033[7m " + line + " \033[0m")
249
+ else:
250
+ out.append(" " + line)
251
+
252
+ # Scroll indicator
253
+ if len(st.memories) > vis_h and vis_h > 0:
254
+ total = max(1, len(st.memories) - vis_h)
255
+ pct = int((st.scroll / total) * 100) if total > 0 else 0
256
+ out.append("\033[4;" + str(width-6) + "H\033[90m[" + str(pct) + "%]\033[0m")
257
+
258
+ if not st.memories:
259
+ out.append("\033[6;4H\033[90mNo memories found for this filter.\033[0m")
260
+ out.append("\033[7;4H\033[90mTry switching tabs with Tab key.\033[0m")
261
+
262
+ def render_preview(out, width, height):
263
+ if not st.memories or st.sel >= len(st.memories):
264
+ return
265
+
266
+ mem = st.memories[st.sel]
267
+
268
+ row = 4
269
+ sc = status_color(mem['status'])
270
+ out.append("\033[" + str(row) + ";1H\033[K\033[1m Memory #" + str(mem['id']) + " \033[" + sc + "m[" + str(mem['status']) + "]\033[0m")
271
+ row += 1
272
+
273
+ out.append("\033[" + str(row) + ";1H\033[K\033[90m Date: " + format_date(mem['created_at']) + " NPC: " + str(mem['npc'] or '-') + " Team: " + str(mem['team'] or '-') + "\033[0m")
274
+ row += 1
275
+ out.append("\033[" + str(row) + ";1H\033[K\033[90m Scope: " + str(mem['scope'] or '-')[:60] + "\033[0m")
276
+ row += 1
277
+ out.append("\033[" + str(row) + ";1H\033[K")
278
+ row += 1
279
+
280
+ out.append("\033[" + str(row) + ";1H\033[K\033[1m Content:\033[0m")
281
+ row += 1
282
+
283
+ content = mem['final'] or mem['original'] or '(empty)'
284
+ for line in content.split('\n')[:height - row - 3]:
285
+ out.append("\033[" + str(row) + ";1H\033[K " + line[:width-5])
286
+ row += 1
287
+
288
+ if mem['final'] and mem['original'] and mem['final'] != mem['original']:
289
+ out.append("\033[" + str(row) + ";1H\033[K")
290
+ row += 1
291
+ out.append("\033[" + str(row) + ";1H\033[K\033[90;1m Original:\033[0m")
292
+ row += 1
293
+ for line in mem['original'].split('\n')[:3]:
294
+ if row >= height - 3:
295
+ break
296
+ out.append("\033[" + str(row) + ";1H\033[K\033[90m " + line[:width-5] + "\033[0m")
297
+ row += 1
298
+
299
+ while row < height - 2:
300
+ out.append("\033[" + str(row) + ";1H\033[K")
301
+ row += 1
302
+
303
+ # ========== Input ==========
304
+ def handle_input(c):
305
+ if c == 'q' or c == '\x03':
306
+ return False
307
+
308
+ if c == '\x1b':
309
+ if select.select([fd], [], [], 0.05)[0]:
310
+ c2 = os.read(fd, 1).decode('latin-1')
311
+ if c2 == '[':
312
+ c3 = os.read(fd, 1).decode('latin-1')
313
+ if c3 == 'A':
314
+ move_up()
315
+ elif c3 == 'B':
316
+ move_down()
317
+ elif c3 == 'Z':
318
+ # Shift+Tab = prev tab
319
+ st.tab = (st.tab - 1) % len(st.tabs)
320
+ st.sel = 0
321
+ st.scroll = 0
322
+ load_memories()
323
+ st.msg = ""
324
+ else:
325
+ if st.preview:
326
+ st.preview = False
327
+ return True
328
+
329
+ if c == '\t':
330
+ st.tab = (st.tab + 1) % len(st.tabs)
331
+ st.sel = 0
332
+ st.scroll = 0
333
+ load_memories()
334
+ st.msg = ""
335
+ st.msg_color = "33"
336
+ elif c == 'k':
337
+ move_up()
338
+ elif c == 'j':
339
+ move_down()
340
+ elif c == '\r' or c == '\n' or c == 'p':
341
+ if st.memories:
342
+ st.preview = not st.preview
343
+ elif c == 'a':
344
+ approve_current()
345
+ elif c == 'x':
346
+ reject_current()
347
+
348
+ return True
349
+
350
+ def move_up():
351
+ st.sel = max(0, st.sel - 1)
352
+ if st.sel < st.scroll:
353
+ st.scroll = st.sel
354
+ st.msg = ""
355
+
356
+ def move_down():
357
+ _, height = get_size()
358
+ vis = max(1, height - 7)
359
+ st.sel = min(max(0, len(st.memories) - 1), st.sel + 1)
360
+ if st.sel >= st.scroll + vis:
361
+ st.scroll = st.sel - vis + 1
362
+ st.msg = ""
363
+
364
+ def approve_current():
365
+ if not st.memories or st.sel >= len(st.memories):
366
+ st.msg = "No memory selected"
367
+ st.msg_color = "33"
368
+ return
369
+ mem = st.memories[st.sel]
370
+ final = mem.get('final') or mem.get('original')
371
+ if do_update(mem['id'], 'human-approved', final):
372
+ st.approved_count += 1
373
+ st.msg = "APPROVED #" + str(mem['id'])
374
+ st.msg_color = "32"
375
+
376
+ def reject_current():
377
+ if not st.memories or st.sel >= len(st.memories):
378
+ st.msg = "No memory selected"
379
+ st.msg_color = "33"
380
+ return
381
+ mem = st.memories[st.sel]
382
+ if do_update(mem['id'], 'human-rejected'):
383
+ st.rejected_count += 1
384
+ st.msg = "REJECTED #" + str(mem['id'])
385
+ st.msg_color = "31"
386
+
387
+ # ========== Main Loop ==========
388
+ load_memories()
389
+
390
+ fd = sys.stdin.fileno()
391
+ old_settings = termios.tcgetattr(fd)
392
+
393
+ try:
394
+ tty.setcbreak(fd)
395
+ sys.stdout.write('\033[?25l')
396
+ sys.stdout.write('\033[2J')
397
+ render()
398
+
399
+ while True:
400
+ c = os.read(fd, 1).decode('latin-1')
401
+ if not handle_input(c):
402
+ break
403
+ render()
404
+
405
+ finally:
406
+ termios.tcsetattr(fd, termios.TCSADRAIN, old_settings)
407
+ sys.stdout.write('\033[?25h')
408
+ sys.stdout.write('\033[2J\033[H')
409
+ sys.stdout.flush()
410
+
411
+ summary = "Memory browser closed."
412
+ if st.approved_count or st.rejected_count:
413
+ summary += " Session: " + str(st.approved_count) + " approved, " + str(st.rejected_count) + " rejected."
414
+ context['output'] = summary