npcsh 1.1.20__py3-none-any.whl → 1.1.21__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 (166) hide show
  1. npcsh/_state.py +5 -71
  2. npcsh/diff_viewer.py +3 -3
  3. npcsh/npc_team/jinxs/lib/core/compress.jinx +373 -85
  4. npcsh/npc_team/jinxs/lib/core/search/db_search.jinx +17 -6
  5. npcsh/npc_team/jinxs/lib/core/search/file_search.jinx +17 -6
  6. npcsh/npc_team/jinxs/lib/core/search/kg_search.jinx +19 -8
  7. npcsh/npc_team/jinxs/lib/core/search/web_search.jinx +52 -14
  8. npcsh/npc_team/jinxs/{bin → lib/utils}/benchmark.jinx +2 -2
  9. npcsh/npc_team/jinxs/{bin → lib/utils}/jinxs.jinx +12 -12
  10. npcsh/npc_team/jinxs/{bin → lib/utils}/models.jinx +7 -7
  11. npcsh/npc_team/jinxs/{bin → lib/utils}/setup.jinx +6 -6
  12. npcsh/npc_team/jinxs/modes/alicanto.jinx +1573 -296
  13. npcsh/npc_team/jinxs/modes/arxiv.jinx +5 -5
  14. npcsh/npc_team/jinxs/modes/config_tui.jinx +300 -0
  15. npcsh/npc_team/jinxs/modes/corca.jinx +3 -3
  16. npcsh/npc_team/jinxs/modes/git.jinx +795 -0
  17. {npcsh-1.1.20.data/data/npcsh/npc_team → npcsh/npc_team/jinxs/modes}/kg.jinx +13 -13
  18. npcsh/npc_team/jinxs/modes/memories.jinx +414 -0
  19. npcsh/npc_team/jinxs/{bin → modes}/nql.jinx +10 -21
  20. npcsh/npc_team/jinxs/modes/papers.jinx +578 -0
  21. npcsh/npc_team/jinxs/modes/plonk.jinx +490 -304
  22. npcsh/npc_team/jinxs/modes/reattach.jinx +3 -3
  23. npcsh/npc_team/jinxs/modes/spool.jinx +3 -3
  24. npcsh/npc_team/jinxs/{bin → modes}/team.jinx +12 -12
  25. npcsh/npc_team/jinxs/modes/vixynt.jinx +388 -0
  26. npcsh/npc_team/jinxs/modes/wander.jinx +454 -181
  27. npcsh/npc_team/jinxs/modes/yap.jinx +10 -3
  28. npcsh/npcsh.py +112 -47
  29. npcsh/routes.py +4 -1
  30. npcsh/salmon_simulation.py +0 -0
  31. npcsh-1.1.21.data/data/npcsh/npc_team/alicanto.jinx +1633 -0
  32. {npcsh-1.1.20.data → npcsh-1.1.21.data}/data/npcsh/npc_team/arxiv.jinx +5 -5
  33. {npcsh-1.1.20.data → npcsh-1.1.21.data}/data/npcsh/npc_team/benchmark.jinx +2 -2
  34. npcsh-1.1.21.data/data/npcsh/npc_team/compress.jinx +428 -0
  35. npcsh-1.1.21.data/data/npcsh/npc_team/config_tui.jinx +300 -0
  36. {npcsh-1.1.20.data → npcsh-1.1.21.data}/data/npcsh/npc_team/corca.jinx +3 -3
  37. {npcsh-1.1.20.data → npcsh-1.1.21.data}/data/npcsh/npc_team/db_search.jinx +17 -6
  38. {npcsh-1.1.20.data → npcsh-1.1.21.data}/data/npcsh/npc_team/file_search.jinx +17 -6
  39. npcsh-1.1.21.data/data/npcsh/npc_team/git.jinx +795 -0
  40. {npcsh-1.1.20.data → npcsh-1.1.21.data}/data/npcsh/npc_team/jinxs.jinx +12 -12
  41. {npcsh/npc_team/jinxs/bin → npcsh-1.1.21.data/data/npcsh/npc_team}/kg.jinx +13 -13
  42. {npcsh-1.1.20.data → npcsh-1.1.21.data}/data/npcsh/npc_team/kg_search.jinx +19 -8
  43. npcsh-1.1.21.data/data/npcsh/npc_team/memories.jinx +414 -0
  44. {npcsh-1.1.20.data → npcsh-1.1.21.data}/data/npcsh/npc_team/models.jinx +7 -7
  45. {npcsh-1.1.20.data → npcsh-1.1.21.data}/data/npcsh/npc_team/nql.jinx +10 -21
  46. npcsh-1.1.21.data/data/npcsh/npc_team/papers.jinx +578 -0
  47. npcsh-1.1.21.data/data/npcsh/npc_team/plonk.jinx +565 -0
  48. {npcsh-1.1.20.data → npcsh-1.1.21.data}/data/npcsh/npc_team/reattach.jinx +3 -3
  49. {npcsh-1.1.20.data → npcsh-1.1.21.data}/data/npcsh/npc_team/setup.jinx +6 -6
  50. {npcsh-1.1.20.data → npcsh-1.1.21.data}/data/npcsh/npc_team/spool.jinx +3 -3
  51. {npcsh-1.1.20.data → npcsh-1.1.21.data}/data/npcsh/npc_team/team.jinx +12 -12
  52. npcsh-1.1.21.data/data/npcsh/npc_team/vixynt.jinx +388 -0
  53. npcsh-1.1.21.data/data/npcsh/npc_team/wander.jinx +728 -0
  54. {npcsh-1.1.20.data → npcsh-1.1.21.data}/data/npcsh/npc_team/web_search.jinx +52 -14
  55. {npcsh-1.1.20.data → npcsh-1.1.21.data}/data/npcsh/npc_team/yap.jinx +10 -3
  56. {npcsh-1.1.20.dist-info → npcsh-1.1.21.dist-info}/METADATA +2 -2
  57. {npcsh-1.1.20.dist-info → npcsh-1.1.21.dist-info}/RECORD +145 -150
  58. npcsh-1.1.21.dist-info/entry_points.txt +11 -0
  59. npcsh/npc_team/jinxs/bin/config_tui.jinx +0 -300
  60. npcsh/npc_team/jinxs/bin/memories.jinx +0 -317
  61. npcsh/npc_team/jinxs/bin/vixynt.jinx +0 -122
  62. npcsh/npc_team/jinxs/lib/core/search/mem_review.jinx +0 -73
  63. npcsh/npc_team/jinxs/lib/core/search/mem_search.jinx +0 -388
  64. npcsh/npc_team/jinxs/lib/research/paper_search.jinx +0 -412
  65. npcsh/npc_team/jinxs/lib/research/semantic_scholar.jinx +0 -386
  66. npcsh/npc_team/plonkjr.npc +0 -23
  67. npcsh-1.1.20.data/data/npcsh/npc_team/alicanto.jinx +0 -356
  68. npcsh-1.1.20.data/data/npcsh/npc_team/compress.jinx +0 -140
  69. npcsh-1.1.20.data/data/npcsh/npc_team/config_tui.jinx +0 -300
  70. npcsh-1.1.20.data/data/npcsh/npc_team/mem_review.jinx +0 -73
  71. npcsh-1.1.20.data/data/npcsh/npc_team/mem_search.jinx +0 -388
  72. npcsh-1.1.20.data/data/npcsh/npc_team/memories.jinx +0 -317
  73. npcsh-1.1.20.data/data/npcsh/npc_team/paper_search.jinx +0 -412
  74. npcsh-1.1.20.data/data/npcsh/npc_team/plonk.jinx +0 -379
  75. npcsh-1.1.20.data/data/npcsh/npc_team/plonkjr.npc +0 -23
  76. npcsh-1.1.20.data/data/npcsh/npc_team/semantic_scholar.jinx +0 -386
  77. npcsh-1.1.20.data/data/npcsh/npc_team/vixynt.jinx +0 -122
  78. npcsh-1.1.20.data/data/npcsh/npc_team/wander.jinx +0 -455
  79. npcsh-1.1.20.dist-info/entry_points.txt +0 -25
  80. /npcsh/npc_team/jinxs/lib/{orchestration → core}/convene.jinx +0 -0
  81. /npcsh/npc_team/jinxs/lib/{orchestration → core}/delegate.jinx +0 -0
  82. /npcsh/npc_team/jinxs/{bin → lib/core}/sample.jinx +0 -0
  83. /npcsh/npc_team/jinxs/{bin → lib/utils}/sync.jinx +0 -0
  84. /npcsh/npc_team/jinxs/{bin → modes}/roll.jinx +0 -0
  85. {npcsh-1.1.20.data → npcsh-1.1.21.data}/data/npcsh/npc_team/add_tab.jinx +0 -0
  86. {npcsh-1.1.20.data → npcsh-1.1.21.data}/data/npcsh/npc_team/alicanto.npc +0 -0
  87. {npcsh-1.1.20.data → npcsh-1.1.21.data}/data/npcsh/npc_team/alicanto.png +0 -0
  88. {npcsh-1.1.20.data → npcsh-1.1.21.data}/data/npcsh/npc_team/browser_action.jinx +0 -0
  89. {npcsh-1.1.20.data → npcsh-1.1.21.data}/data/npcsh/npc_team/browser_screenshot.jinx +0 -0
  90. {npcsh-1.1.20.data → npcsh-1.1.21.data}/data/npcsh/npc_team/build.jinx +0 -0
  91. {npcsh-1.1.20.data → npcsh-1.1.21.data}/data/npcsh/npc_team/chat.jinx +0 -0
  92. {npcsh-1.1.20.data → npcsh-1.1.21.data}/data/npcsh/npc_team/click.jinx +0 -0
  93. {npcsh-1.1.20.data → npcsh-1.1.21.data}/data/npcsh/npc_team/close_browser.jinx +0 -0
  94. {npcsh-1.1.20.data → npcsh-1.1.21.data}/data/npcsh/npc_team/close_pane.jinx +0 -0
  95. {npcsh-1.1.20.data → npcsh-1.1.21.data}/data/npcsh/npc_team/close_tab.jinx +0 -0
  96. {npcsh-1.1.20.data → npcsh-1.1.21.data}/data/npcsh/npc_team/cmd.jinx +0 -0
  97. {npcsh-1.1.20.data → npcsh-1.1.21.data}/data/npcsh/npc_team/compile.jinx +0 -0
  98. {npcsh-1.1.20.data → npcsh-1.1.21.data}/data/npcsh/npc_team/confirm.jinx +0 -0
  99. {npcsh-1.1.20.data → npcsh-1.1.21.data}/data/npcsh/npc_team/convene.jinx +0 -0
  100. {npcsh-1.1.20.data → npcsh-1.1.21.data}/data/npcsh/npc_team/corca.npc +0 -0
  101. {npcsh-1.1.20.data → npcsh-1.1.21.data}/data/npcsh/npc_team/corca.png +0 -0
  102. {npcsh-1.1.20.data → npcsh-1.1.21.data}/data/npcsh/npc_team/corca_example.png +0 -0
  103. {npcsh-1.1.20.data → npcsh-1.1.21.data}/data/npcsh/npc_team/delegate.jinx +0 -0
  104. {npcsh-1.1.20.data → npcsh-1.1.21.data}/data/npcsh/npc_team/edit_file.jinx +0 -0
  105. {npcsh-1.1.20.data → npcsh-1.1.21.data}/data/npcsh/npc_team/focus_pane.jinx +0 -0
  106. {npcsh-1.1.20.data → npcsh-1.1.21.data}/data/npcsh/npc_team/frederic.npc +0 -0
  107. {npcsh-1.1.20.data → npcsh-1.1.21.data}/data/npcsh/npc_team/frederic4.png +0 -0
  108. {npcsh-1.1.20.data → npcsh-1.1.21.data}/data/npcsh/npc_team/guac.jinx +0 -0
  109. {npcsh-1.1.20.data → npcsh-1.1.21.data}/data/npcsh/npc_team/guac.npc +0 -0
  110. {npcsh-1.1.20.data → npcsh-1.1.21.data}/data/npcsh/npc_team/guac.png +0 -0
  111. {npcsh-1.1.20.data → npcsh-1.1.21.data}/data/npcsh/npc_team/help.jinx +0 -0
  112. {npcsh-1.1.20.data → npcsh-1.1.21.data}/data/npcsh/npc_team/incognide.jinx +0 -0
  113. {npcsh-1.1.20.data → npcsh-1.1.21.data}/data/npcsh/npc_team/init.jinx +0 -0
  114. {npcsh-1.1.20.data → npcsh-1.1.21.data}/data/npcsh/npc_team/kadiefa.npc +0 -0
  115. {npcsh-1.1.20.data → npcsh-1.1.21.data}/data/npcsh/npc_team/kadiefa.png +0 -0
  116. {npcsh-1.1.20.data → npcsh-1.1.21.data}/data/npcsh/npc_team/key_press.jinx +0 -0
  117. {npcsh-1.1.20.data → npcsh-1.1.21.data}/data/npcsh/npc_team/launch_app.jinx +0 -0
  118. {npcsh-1.1.20.data → npcsh-1.1.21.data}/data/npcsh/npc_team/list_panes.jinx +0 -0
  119. {npcsh-1.1.20.data → npcsh-1.1.21.data}/data/npcsh/npc_team/load_file.jinx +0 -0
  120. {npcsh-1.1.20.data → npcsh-1.1.21.data}/data/npcsh/npc_team/navigate.jinx +0 -0
  121. {npcsh-1.1.20.data → npcsh-1.1.21.data}/data/npcsh/npc_team/notify.jinx +0 -0
  122. {npcsh-1.1.20.data → npcsh-1.1.21.data}/data/npcsh/npc_team/npcsh.ctx +0 -0
  123. {npcsh-1.1.20.data → npcsh-1.1.21.data}/data/npcsh/npc_team/npcsh_sibiji.png +0 -0
  124. {npcsh-1.1.20.data → npcsh-1.1.21.data}/data/npcsh/npc_team/open_browser.jinx +0 -0
  125. {npcsh-1.1.20.data → npcsh-1.1.21.data}/data/npcsh/npc_team/open_pane.jinx +0 -0
  126. {npcsh-1.1.20.data → npcsh-1.1.21.data}/data/npcsh/npc_team/ots.jinx +0 -0
  127. {npcsh-1.1.20.data → npcsh-1.1.21.data}/data/npcsh/npc_team/paste.jinx +0 -0
  128. {npcsh-1.1.20.data → npcsh-1.1.21.data}/data/npcsh/npc_team/plonk.npc +0 -0
  129. {npcsh-1.1.20.data → npcsh-1.1.21.data}/data/npcsh/npc_team/plonk.png +0 -0
  130. {npcsh-1.1.20.data → npcsh-1.1.21.data}/data/npcsh/npc_team/plonkjr.png +0 -0
  131. {npcsh-1.1.20.data → npcsh-1.1.21.data}/data/npcsh/npc_team/pti.jinx +0 -0
  132. {npcsh-1.1.20.data → npcsh-1.1.21.data}/data/npcsh/npc_team/python.jinx +0 -0
  133. {npcsh-1.1.20.data → npcsh-1.1.21.data}/data/npcsh/npc_team/read_pane.jinx +0 -0
  134. {npcsh-1.1.20.data → npcsh-1.1.21.data}/data/npcsh/npc_team/roll.jinx +0 -0
  135. {npcsh-1.1.20.data → npcsh-1.1.21.data}/data/npcsh/npc_team/run_terminal.jinx +0 -0
  136. {npcsh-1.1.20.data → npcsh-1.1.21.data}/data/npcsh/npc_team/sample.jinx +0 -0
  137. {npcsh-1.1.20.data → npcsh-1.1.21.data}/data/npcsh/npc_team/screenshot.jinx +0 -0
  138. {npcsh-1.1.20.data → npcsh-1.1.21.data}/data/npcsh/npc_team/search.jinx +0 -0
  139. {npcsh-1.1.20.data → npcsh-1.1.21.data}/data/npcsh/npc_team/send_message.jinx +0 -0
  140. {npcsh-1.1.20.data → npcsh-1.1.21.data}/data/npcsh/npc_team/serve.jinx +0 -0
  141. {npcsh-1.1.20.data → npcsh-1.1.21.data}/data/npcsh/npc_team/set.jinx +0 -0
  142. {npcsh-1.1.20.data → npcsh-1.1.21.data}/data/npcsh/npc_team/sh.jinx +0 -0
  143. {npcsh-1.1.20.data → npcsh-1.1.21.data}/data/npcsh/npc_team/shh.jinx +0 -0
  144. {npcsh-1.1.20.data → npcsh-1.1.21.data}/data/npcsh/npc_team/sibiji.npc +0 -0
  145. {npcsh-1.1.20.data → npcsh-1.1.21.data}/data/npcsh/npc_team/sibiji.png +0 -0
  146. {npcsh-1.1.20.data → npcsh-1.1.21.data}/data/npcsh/npc_team/sleep.jinx +0 -0
  147. {npcsh-1.1.20.data → npcsh-1.1.21.data}/data/npcsh/npc_team/split_pane.jinx +0 -0
  148. {npcsh-1.1.20.data → npcsh-1.1.21.data}/data/npcsh/npc_team/spool.png +0 -0
  149. {npcsh-1.1.20.data → npcsh-1.1.21.data}/data/npcsh/npc_team/sql.jinx +0 -0
  150. {npcsh-1.1.20.data → npcsh-1.1.21.data}/data/npcsh/npc_team/switch.jinx +0 -0
  151. {npcsh-1.1.20.data → npcsh-1.1.21.data}/data/npcsh/npc_team/switch_npc.jinx +0 -0
  152. {npcsh-1.1.20.data → npcsh-1.1.21.data}/data/npcsh/npc_team/switch_tab.jinx +0 -0
  153. {npcsh-1.1.20.data → npcsh-1.1.21.data}/data/npcsh/npc_team/switches.jinx +0 -0
  154. {npcsh-1.1.20.data → npcsh-1.1.21.data}/data/npcsh/npc_team/sync.jinx +0 -0
  155. {npcsh-1.1.20.data → npcsh-1.1.21.data}/data/npcsh/npc_team/teamviz.jinx +0 -0
  156. {npcsh-1.1.20.data → npcsh-1.1.21.data}/data/npcsh/npc_team/trigger.jinx +0 -0
  157. {npcsh-1.1.20.data → npcsh-1.1.21.data}/data/npcsh/npc_team/type_text.jinx +0 -0
  158. {npcsh-1.1.20.data → npcsh-1.1.21.data}/data/npcsh/npc_team/usage.jinx +0 -0
  159. {npcsh-1.1.20.data → npcsh-1.1.21.data}/data/npcsh/npc_team/verbose.jinx +0 -0
  160. {npcsh-1.1.20.data → npcsh-1.1.21.data}/data/npcsh/npc_team/wait.jinx +0 -0
  161. {npcsh-1.1.20.data → npcsh-1.1.21.data}/data/npcsh/npc_team/write_file.jinx +0 -0
  162. {npcsh-1.1.20.data → npcsh-1.1.21.data}/data/npcsh/npc_team/yap.png +0 -0
  163. {npcsh-1.1.20.data → npcsh-1.1.21.data}/data/npcsh/npc_team/zen_mode.jinx +0 -0
  164. {npcsh-1.1.20.dist-info → npcsh-1.1.21.dist-info}/WHEEL +0 -0
  165. {npcsh-1.1.20.dist-info → npcsh-1.1.21.dist-info}/licenses/LICENSE +0 -0
  166. {npcsh-1.1.20.dist-info → npcsh-1.1.21.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()
@@ -166,12 +166,12 @@ steps:
166
166
 
167
167
  def do_search(query):
168
168
  ui.search_results = []
169
- q = f"%{query}%"
169
+ pat = f"%{query}%"
170
170
  with engine.connect() as conn:
171
171
  r = conn.execute(text(
172
172
  "SELECT statement, source_text, type, generation, origin "
173
- "FROM kg_facts WHERE statement LIKE :q ORDER BY rowid DESC"
174
- ), {"q": q})
173
+ "FROM kg_facts WHERE statement LIKE :pat ORDER BY rowid DESC"
174
+ ), {"pat": pat})
175
175
  for row in r:
176
176
  ui.search_results.append({
177
177
  'kind': 'fact',
@@ -377,8 +377,8 @@ steps:
377
377
  # ── header ──
378
378
  hdr = " Knowledge Graph "
379
379
  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")
380
+ out.append(wline(1, f"\033[7;1m{pad}\033[0m"))
381
+ out.append(f"\033[1;{max(1,(W - len(hdr))//2)}H\033[7;1m{hdr}\033[0m")
382
382
 
383
383
  # ── tabs ──
384
384
  tb = ""
@@ -439,7 +439,7 @@ steps:
439
439
  foot = " [Tab] Switch [j/k] Select [Enter] Center [Backspace] Back [q] Quit"
440
440
  else:
441
441
  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"))
442
+ out.append(wline(H, f"\033[7m{foot[:W].ljust(W)}\033[0m"))
443
443
 
444
444
  sys.stdout.write(''.join(out))
445
445
  sys.stdout.flush()
@@ -779,10 +779,10 @@ steps:
779
779
  return True
780
780
 
781
781
  def handle_esc():
782
- if select.select([sys.stdin], [], [], 0.05)[0]:
783
- c2 = sys.stdin.read(1)
782
+ if select.select([fd], [], [], 0.05)[0]:
783
+ c2 = os.read(fd, 1).decode('latin-1')
784
784
  if c2 == '[':
785
- c3 = sys.stdin.read(1)
785
+ c3 = os.read(fd, 1).decode('latin-1')
786
786
  if c3 == 'A':
787
787
  nav_up()
788
788
  elif c3 == 'B':
@@ -796,10 +796,10 @@ steps:
796
796
 
797
797
  def handle_search_input(c):
798
798
  if c == '\x1b':
799
- if select.select([sys.stdin], [], [], 0.05)[0]:
800
- c2 = sys.stdin.read(1)
799
+ if select.select([fd], [], [], 0.05)[0]:
800
+ c2 = os.read(fd, 1).decode('latin-1')
801
801
  if c2 == '[':
802
- sys.stdin.read(1)
802
+ os.read(fd, 1).decode('latin-1')
803
803
  else:
804
804
  ui.search_mode = False
805
805
  ui.search_buf = ""
@@ -929,7 +929,7 @@ steps:
929
929
  sys.stdout.flush()
930
930
  render()
931
931
  while True:
932
- c = sys.stdin.read(1)
932
+ c = os.read(fd, 1).decode('latin-1')
933
933
  if not handle(c):
934
934
  break
935
935
  render()
@@ -1,5 +1,6 @@
1
1
  jinx_name: kg_search
2
2
  description: Search the knowledge graph with interactive TUI
3
+ interactive: true
3
4
  inputs:
4
5
  - query: ""
5
6
  - type: "facts"
@@ -241,11 +242,11 @@ steps:
241
242
  # Header
242
243
  if mode == 'list':
243
244
  sort_ind = {'score': '1', 'concept': '2', 'type': '3'}[sort_mode]
244
- q = query or concept or search_type
245
- header = f" KG SEARCH ({len(display_results)} results): '{q}' [sort:{sort_mode}({sort_ind}) filter:{type_filter}] "
245
+ label = query or concept or search_type
246
+ header = f" KG SEARCH ({len(display_results)} results): '{label}' [sort:{sort_mode}({sort_ind}) filter:{type_filter}] "
246
247
  else:
247
248
  header = f" PREVIEW: {display_results[selected]['type']} "
248
- sys.stdout.write(f'\033[44;37;1m{header.ljust(width)}\033[0m\n')
249
+ sys.stdout.write(f'\033[7;1m{header.ljust(width)}\033[0m\n')
249
250
 
250
251
  # Column headers
251
252
  if mode == 'list':
@@ -286,7 +287,7 @@ steps:
286
287
  sel = display_results[selected] if display_results else {}
287
288
  source = sel.get('source', '')
288
289
  sys.stdout.write(f'\033[{height-1};1H\033[K Source: {source}'.ljust(width))
289
- sys.stdout.write(f'\033[{height};1H\033[K\033[44;37m j/k:Nav 1/2/3:Sort c:Concepts e:Explore p:Preview q:Quit [{selected+1}/{len(display_results)}] \033[0m')
290
+ sys.stdout.write(f'\033[{height};1H\033[K\033[7m j/k:Nav 1/2/3:Sort c:Concepts e:Explore p:Preview q:Quit [{selected+1}/{len(display_results)}] \033[0m')
290
291
 
291
292
  else: # preview mode
292
293
  sel = display_results[selected]
@@ -311,16 +312,26 @@ steps:
311
312
 
312
313
  sys.stdout.write(f'\033[{height-2};1H\033[K\033[90m{"─" * width}\033[0m')
313
314
  sys.stdout.write(f'\033[{height-1};1H\033[K [{preview_scroll+1}/{len(all_lines)} lines]')
314
- sys.stdout.write(f'\033[{height};1H\033[K\033[44;37m j/k:Scroll b:Back e:Explore q:Quit \033[0m')
315
+ sys.stdout.write(f'\033[{height};1H\033[K\033[7m j/k:Scroll b:Back e:Explore q:Quit \033[0m')
315
316
 
316
317
  sys.stdout.flush()
317
318
 
318
- c = sys.stdin.read(1)
319
+ c = os.read(fd, 1).decode('latin-1')
319
320
 
320
321
  if c == '\x1b':
321
- c2 = sys.stdin.read(1)
322
+ import select as _sel
323
+ if _sel.select([fd], [], [], 0.05)[0]:
324
+ c2 = os.read(fd, 1).decode('latin-1')
325
+ else:
326
+ if mode == 'preview':
327
+ mode = 'list'
328
+ sys.stdout.write('\033[2J\033[H')
329
+ else:
330
+ context['output'] = "Cancelled."
331
+ break
332
+ continue
322
333
  if c2 == '[':
323
- c3 = sys.stdin.read(1)
334
+ c3 = os.read(fd, 1).decode('latin-1')
324
335
  if c3 == 'A': # Up
325
336
  if mode == 'list' and selected > 0:
326
337
  selected -= 1
@@ -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
@@ -120,8 +120,8 @@ steps:
120
120
  # -- header --
121
121
  hdr = " Models "
122
122
  pad = '=' * W
123
- out.append(wline(1, f"\033[44;37;1m{pad}\033[0m"))
124
- out.append(f"\033[1;{max(1,(W - len(hdr)) // 2)}H\033[44;37;1m{hdr}\033[0m")
123
+ out.append(wline(1, f"\033[7;1m{pad}\033[0m"))
124
+ out.append(f"\033[1;{max(1,(W - len(hdr)) // 2)}H\033[7;1m{hdr}\033[0m")
125
125
 
126
126
  # -- tabs --
127
127
  tb = ""
@@ -205,7 +205,7 @@ steps:
205
205
  # combine status into footer area
206
206
  foot = " [Tab] Filter [j/k] Nav [c] Set Chat [v] Set Vision [e] Set Embed [r] Set Reasoning [d] Refresh [q] Quit"
207
207
  out.append(wline(H - 1, stat_line))
208
- out.append(wline(H, f"\033[44;37m{foot[:W].ljust(W)}\033[0m"))
208
+ out.append(wline(H, f"\033[7m{foot[:W].ljust(W)}\033[0m"))
209
209
 
210
210
  sys.stdout.write(''.join(out))
211
211
  sys.stdout.flush()
@@ -240,10 +240,10 @@ steps:
240
240
  return True
241
241
 
242
242
  def handle_esc():
243
- if select.select([sys.stdin], [], [], 0.05)[0]:
244
- c2 = sys.stdin.read(1)
243
+ if select.select([fd], [], [], 0.05)[0]:
244
+ c2 = os.read(fd, 1).decode('latin-1')
245
245
  if c2 == '[':
246
- c3 = sys.stdin.read(1)
246
+ c3 = os.read(fd, 1).decode('latin-1')
247
247
  if c3 == 'A':
248
248
  nav_up()
249
249
  elif c3 == 'B':
@@ -331,7 +331,7 @@ steps:
331
331
  sys.stdout.flush()
332
332
  render()
333
333
  while True:
334
- c = sys.stdin.read(1)
334
+ c = os.read(fd, 1).decode('latin-1')
335
335
  if not handle(c):
336
336
  break
337
337
  render()