npcsh 1.1.19__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 (173) hide show
  1. npcsh/_state.py +16 -78
  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 +18 -7
  5. npcsh/npc_team/jinxs/lib/core/search/file_search.jinx +18 -7
  6. npcsh/npc_team/jinxs/lib/core/search/kg_search.jinx +20 -9
  7. npcsh/npc_team/jinxs/lib/core/search/web_search.jinx +53 -15
  8. npcsh/npc_team/jinxs/{bin → lib/utils}/benchmark.jinx +2 -2
  9. npcsh/npc_team/jinxs/lib/utils/jinxs.jinx +393 -317
  10. npcsh/npc_team/jinxs/lib/utils/models.jinx +343 -0
  11. npcsh/npc_team/jinxs/{bin → lib/utils}/setup.jinx +8 -7
  12. npcsh/npc_team/jinxs/modes/alicanto.jinx +1573 -296
  13. npcsh/npc_team/jinxs/modes/arxiv.jinx +6 -6
  14. npcsh/npc_team/jinxs/modes/config_tui.jinx +300 -0
  15. npcsh/npc_team/jinxs/modes/corca.jinx +4 -4
  16. npcsh/npc_team/jinxs/modes/git.jinx +795 -0
  17. npcsh/npc_team/jinxs/modes/guac.jinx +4 -4
  18. npcsh/npc_team/jinxs/modes/kg.jinx +941 -0
  19. npcsh/npc_team/jinxs/modes/memories.jinx +414 -0
  20. npcsh/npc_team/jinxs/modes/nql.jinx +460 -0
  21. npcsh/npc_team/jinxs/modes/papers.jinx +578 -0
  22. npcsh/npc_team/jinxs/modes/plonk.jinx +490 -304
  23. npcsh/npc_team/jinxs/modes/pti.jinx +1 -1
  24. npcsh/npc_team/jinxs/modes/reattach.jinx +4 -4
  25. npcsh/npc_team/jinxs/modes/spool.jinx +4 -4
  26. npcsh/npc_team/jinxs/modes/team.jinx +504 -0
  27. npcsh/npc_team/jinxs/modes/vixynt.jinx +388 -0
  28. npcsh/npc_team/jinxs/modes/wander.jinx +455 -182
  29. npcsh/npc_team/jinxs/modes/yap.jinx +10 -3
  30. npcsh/npcsh.py +112 -47
  31. npcsh/routes.py +12 -3
  32. npcsh/salmon_simulation.py +0 -0
  33. npcsh-1.1.21.data/data/npcsh/npc_team/alicanto.jinx +1633 -0
  34. {npcsh-1.1.19.data → npcsh-1.1.21.data}/data/npcsh/npc_team/arxiv.jinx +6 -6
  35. {npcsh-1.1.19.data → npcsh-1.1.21.data}/data/npcsh/npc_team/benchmark.jinx +2 -2
  36. npcsh-1.1.21.data/data/npcsh/npc_team/compress.jinx +428 -0
  37. npcsh-1.1.21.data/data/npcsh/npc_team/config_tui.jinx +300 -0
  38. {npcsh-1.1.19.data → npcsh-1.1.21.data}/data/npcsh/npc_team/corca.jinx +4 -4
  39. {npcsh-1.1.19.data → npcsh-1.1.21.data}/data/npcsh/npc_team/db_search.jinx +18 -7
  40. {npcsh-1.1.19.data → npcsh-1.1.21.data}/data/npcsh/npc_team/file_search.jinx +18 -7
  41. npcsh-1.1.21.data/data/npcsh/npc_team/git.jinx +795 -0
  42. {npcsh-1.1.19.data → npcsh-1.1.21.data}/data/npcsh/npc_team/guac.jinx +4 -4
  43. npcsh-1.1.21.data/data/npcsh/npc_team/jinxs.jinx +407 -0
  44. npcsh-1.1.21.data/data/npcsh/npc_team/kg.jinx +941 -0
  45. {npcsh-1.1.19.data → npcsh-1.1.21.data}/data/npcsh/npc_team/kg_search.jinx +20 -9
  46. npcsh-1.1.21.data/data/npcsh/npc_team/memories.jinx +414 -0
  47. npcsh-1.1.21.data/data/npcsh/npc_team/models.jinx +343 -0
  48. npcsh-1.1.21.data/data/npcsh/npc_team/nql.jinx +460 -0
  49. npcsh-1.1.21.data/data/npcsh/npc_team/papers.jinx +578 -0
  50. npcsh-1.1.21.data/data/npcsh/npc_team/plonk.jinx +565 -0
  51. {npcsh-1.1.19.data → npcsh-1.1.21.data}/data/npcsh/npc_team/pti.jinx +1 -1
  52. {npcsh-1.1.19.data → npcsh-1.1.21.data}/data/npcsh/npc_team/reattach.jinx +4 -4
  53. {npcsh-1.1.19.data → npcsh-1.1.21.data}/data/npcsh/npc_team/setup.jinx +8 -7
  54. {npcsh-1.1.19.data → npcsh-1.1.21.data}/data/npcsh/npc_team/spool.jinx +4 -4
  55. npcsh-1.1.21.data/data/npcsh/npc_team/team.jinx +504 -0
  56. npcsh-1.1.21.data/data/npcsh/npc_team/vixynt.jinx +388 -0
  57. npcsh-1.1.21.data/data/npcsh/npc_team/wander.jinx +728 -0
  58. {npcsh-1.1.19.data → npcsh-1.1.21.data}/data/npcsh/npc_team/web_search.jinx +53 -15
  59. {npcsh-1.1.19.data → npcsh-1.1.21.data}/data/npcsh/npc_team/yap.jinx +10 -3
  60. {npcsh-1.1.19.dist-info → npcsh-1.1.21.dist-info}/METADATA +2 -2
  61. {npcsh-1.1.19.dist-info → npcsh-1.1.21.dist-info}/RECORD +147 -148
  62. npcsh-1.1.21.dist-info/entry_points.txt +11 -0
  63. npcsh/npc_team/jinxs/bin/config_tui.jinx +0 -299
  64. npcsh/npc_team/jinxs/bin/memories.jinx +0 -316
  65. npcsh/npc_team/jinxs/bin/nql.jinx +0 -141
  66. npcsh/npc_team/jinxs/bin/team_tui.jinx +0 -327
  67. npcsh/npc_team/jinxs/bin/vixynt.jinx +0 -122
  68. npcsh/npc_team/jinxs/lib/core/search/mem_review.jinx +0 -73
  69. npcsh/npc_team/jinxs/lib/core/search/mem_search.jinx +0 -388
  70. npcsh/npc_team/jinxs/lib/research/paper_search.jinx +0 -412
  71. npcsh/npc_team/jinxs/lib/research/semantic_scholar.jinx +0 -386
  72. npcsh/npc_team/plonkjr.npc +0 -23
  73. npcsh-1.1.19.data/data/npcsh/npc_team/alicanto.jinx +0 -356
  74. npcsh-1.1.19.data/data/npcsh/npc_team/compress.jinx +0 -140
  75. npcsh-1.1.19.data/data/npcsh/npc_team/config_tui.jinx +0 -299
  76. npcsh-1.1.19.data/data/npcsh/npc_team/jinxs.jinx +0 -331
  77. npcsh-1.1.19.data/data/npcsh/npc_team/mem_review.jinx +0 -73
  78. npcsh-1.1.19.data/data/npcsh/npc_team/mem_search.jinx +0 -388
  79. npcsh-1.1.19.data/data/npcsh/npc_team/memories.jinx +0 -316
  80. npcsh-1.1.19.data/data/npcsh/npc_team/nql.jinx +0 -141
  81. npcsh-1.1.19.data/data/npcsh/npc_team/paper_search.jinx +0 -412
  82. npcsh-1.1.19.data/data/npcsh/npc_team/plonk.jinx +0 -379
  83. npcsh-1.1.19.data/data/npcsh/npc_team/plonkjr.npc +0 -23
  84. npcsh-1.1.19.data/data/npcsh/npc_team/semantic_scholar.jinx +0 -386
  85. npcsh-1.1.19.data/data/npcsh/npc_team/team_tui.jinx +0 -327
  86. npcsh-1.1.19.data/data/npcsh/npc_team/vixynt.jinx +0 -122
  87. npcsh-1.1.19.data/data/npcsh/npc_team/wander.jinx +0 -455
  88. npcsh-1.1.19.dist-info/entry_points.txt +0 -22
  89. /npcsh/npc_team/jinxs/lib/{orchestration → core}/convene.jinx +0 -0
  90. /npcsh/npc_team/jinxs/lib/{orchestration → core}/delegate.jinx +0 -0
  91. /npcsh/npc_team/jinxs/{bin → lib/core}/sample.jinx +0 -0
  92. /npcsh/npc_team/jinxs/{bin → lib/utils}/sync.jinx +0 -0
  93. /npcsh/npc_team/jinxs/{bin → modes}/roll.jinx +0 -0
  94. {npcsh-1.1.19.data → npcsh-1.1.21.data}/data/npcsh/npc_team/add_tab.jinx +0 -0
  95. {npcsh-1.1.19.data → npcsh-1.1.21.data}/data/npcsh/npc_team/alicanto.npc +0 -0
  96. {npcsh-1.1.19.data → npcsh-1.1.21.data}/data/npcsh/npc_team/alicanto.png +0 -0
  97. {npcsh-1.1.19.data → npcsh-1.1.21.data}/data/npcsh/npc_team/browser_action.jinx +0 -0
  98. {npcsh-1.1.19.data → npcsh-1.1.21.data}/data/npcsh/npc_team/browser_screenshot.jinx +0 -0
  99. {npcsh-1.1.19.data → npcsh-1.1.21.data}/data/npcsh/npc_team/build.jinx +0 -0
  100. {npcsh-1.1.19.data → npcsh-1.1.21.data}/data/npcsh/npc_team/chat.jinx +0 -0
  101. {npcsh-1.1.19.data → npcsh-1.1.21.data}/data/npcsh/npc_team/click.jinx +0 -0
  102. {npcsh-1.1.19.data → npcsh-1.1.21.data}/data/npcsh/npc_team/close_browser.jinx +0 -0
  103. {npcsh-1.1.19.data → npcsh-1.1.21.data}/data/npcsh/npc_team/close_pane.jinx +0 -0
  104. {npcsh-1.1.19.data → npcsh-1.1.21.data}/data/npcsh/npc_team/close_tab.jinx +0 -0
  105. {npcsh-1.1.19.data → npcsh-1.1.21.data}/data/npcsh/npc_team/cmd.jinx +0 -0
  106. {npcsh-1.1.19.data → npcsh-1.1.21.data}/data/npcsh/npc_team/compile.jinx +0 -0
  107. {npcsh-1.1.19.data → npcsh-1.1.21.data}/data/npcsh/npc_team/confirm.jinx +0 -0
  108. {npcsh-1.1.19.data → npcsh-1.1.21.data}/data/npcsh/npc_team/convene.jinx +0 -0
  109. {npcsh-1.1.19.data → npcsh-1.1.21.data}/data/npcsh/npc_team/corca.npc +0 -0
  110. {npcsh-1.1.19.data → npcsh-1.1.21.data}/data/npcsh/npc_team/corca.png +0 -0
  111. {npcsh-1.1.19.data → npcsh-1.1.21.data}/data/npcsh/npc_team/corca_example.png +0 -0
  112. {npcsh-1.1.19.data → npcsh-1.1.21.data}/data/npcsh/npc_team/delegate.jinx +0 -0
  113. {npcsh-1.1.19.data → npcsh-1.1.21.data}/data/npcsh/npc_team/edit_file.jinx +0 -0
  114. {npcsh-1.1.19.data → npcsh-1.1.21.data}/data/npcsh/npc_team/focus_pane.jinx +0 -0
  115. {npcsh-1.1.19.data → npcsh-1.1.21.data}/data/npcsh/npc_team/frederic.npc +0 -0
  116. {npcsh-1.1.19.data → npcsh-1.1.21.data}/data/npcsh/npc_team/frederic4.png +0 -0
  117. {npcsh-1.1.19.data → npcsh-1.1.21.data}/data/npcsh/npc_team/guac.npc +0 -0
  118. {npcsh-1.1.19.data → npcsh-1.1.21.data}/data/npcsh/npc_team/guac.png +0 -0
  119. {npcsh-1.1.19.data → npcsh-1.1.21.data}/data/npcsh/npc_team/help.jinx +0 -0
  120. {npcsh-1.1.19.data → npcsh-1.1.21.data}/data/npcsh/npc_team/incognide.jinx +0 -0
  121. {npcsh-1.1.19.data → npcsh-1.1.21.data}/data/npcsh/npc_team/init.jinx +0 -0
  122. {npcsh-1.1.19.data → npcsh-1.1.21.data}/data/npcsh/npc_team/kadiefa.npc +0 -0
  123. {npcsh-1.1.19.data → npcsh-1.1.21.data}/data/npcsh/npc_team/kadiefa.png +0 -0
  124. {npcsh-1.1.19.data → npcsh-1.1.21.data}/data/npcsh/npc_team/key_press.jinx +0 -0
  125. {npcsh-1.1.19.data → npcsh-1.1.21.data}/data/npcsh/npc_team/launch_app.jinx +0 -0
  126. {npcsh-1.1.19.data → npcsh-1.1.21.data}/data/npcsh/npc_team/list_panes.jinx +0 -0
  127. {npcsh-1.1.19.data → npcsh-1.1.21.data}/data/npcsh/npc_team/load_file.jinx +0 -0
  128. {npcsh-1.1.19.data → npcsh-1.1.21.data}/data/npcsh/npc_team/navigate.jinx +0 -0
  129. {npcsh-1.1.19.data → npcsh-1.1.21.data}/data/npcsh/npc_team/notify.jinx +0 -0
  130. {npcsh-1.1.19.data → npcsh-1.1.21.data}/data/npcsh/npc_team/npcsh.ctx +0 -0
  131. {npcsh-1.1.19.data → npcsh-1.1.21.data}/data/npcsh/npc_team/npcsh_sibiji.png +0 -0
  132. {npcsh-1.1.19.data → npcsh-1.1.21.data}/data/npcsh/npc_team/open_browser.jinx +0 -0
  133. {npcsh-1.1.19.data → npcsh-1.1.21.data}/data/npcsh/npc_team/open_pane.jinx +0 -0
  134. {npcsh-1.1.19.data → npcsh-1.1.21.data}/data/npcsh/npc_team/ots.jinx +0 -0
  135. {npcsh-1.1.19.data → npcsh-1.1.21.data}/data/npcsh/npc_team/paste.jinx +0 -0
  136. {npcsh-1.1.19.data → npcsh-1.1.21.data}/data/npcsh/npc_team/plonk.npc +0 -0
  137. {npcsh-1.1.19.data → npcsh-1.1.21.data}/data/npcsh/npc_team/plonk.png +0 -0
  138. {npcsh-1.1.19.data → npcsh-1.1.21.data}/data/npcsh/npc_team/plonkjr.png +0 -0
  139. {npcsh-1.1.19.data → npcsh-1.1.21.data}/data/npcsh/npc_team/python.jinx +0 -0
  140. {npcsh-1.1.19.data → npcsh-1.1.21.data}/data/npcsh/npc_team/read_pane.jinx +0 -0
  141. {npcsh-1.1.19.data → npcsh-1.1.21.data}/data/npcsh/npc_team/roll.jinx +0 -0
  142. {npcsh-1.1.19.data → npcsh-1.1.21.data}/data/npcsh/npc_team/run_terminal.jinx +0 -0
  143. {npcsh-1.1.19.data → npcsh-1.1.21.data}/data/npcsh/npc_team/sample.jinx +0 -0
  144. {npcsh-1.1.19.data → npcsh-1.1.21.data}/data/npcsh/npc_team/screenshot.jinx +0 -0
  145. {npcsh-1.1.19.data → npcsh-1.1.21.data}/data/npcsh/npc_team/search.jinx +0 -0
  146. {npcsh-1.1.19.data → npcsh-1.1.21.data}/data/npcsh/npc_team/send_message.jinx +0 -0
  147. {npcsh-1.1.19.data → npcsh-1.1.21.data}/data/npcsh/npc_team/serve.jinx +0 -0
  148. {npcsh-1.1.19.data → npcsh-1.1.21.data}/data/npcsh/npc_team/set.jinx +0 -0
  149. {npcsh-1.1.19.data → npcsh-1.1.21.data}/data/npcsh/npc_team/sh.jinx +0 -0
  150. {npcsh-1.1.19.data → npcsh-1.1.21.data}/data/npcsh/npc_team/shh.jinx +0 -0
  151. {npcsh-1.1.19.data → npcsh-1.1.21.data}/data/npcsh/npc_team/sibiji.npc +0 -0
  152. {npcsh-1.1.19.data → npcsh-1.1.21.data}/data/npcsh/npc_team/sibiji.png +0 -0
  153. {npcsh-1.1.19.data → npcsh-1.1.21.data}/data/npcsh/npc_team/sleep.jinx +0 -0
  154. {npcsh-1.1.19.data → npcsh-1.1.21.data}/data/npcsh/npc_team/split_pane.jinx +0 -0
  155. {npcsh-1.1.19.data → npcsh-1.1.21.data}/data/npcsh/npc_team/spool.png +0 -0
  156. {npcsh-1.1.19.data → npcsh-1.1.21.data}/data/npcsh/npc_team/sql.jinx +0 -0
  157. {npcsh-1.1.19.data → npcsh-1.1.21.data}/data/npcsh/npc_team/switch.jinx +0 -0
  158. {npcsh-1.1.19.data → npcsh-1.1.21.data}/data/npcsh/npc_team/switch_npc.jinx +0 -0
  159. {npcsh-1.1.19.data → npcsh-1.1.21.data}/data/npcsh/npc_team/switch_tab.jinx +0 -0
  160. {npcsh-1.1.19.data → npcsh-1.1.21.data}/data/npcsh/npc_team/switches.jinx +0 -0
  161. {npcsh-1.1.19.data → npcsh-1.1.21.data}/data/npcsh/npc_team/sync.jinx +0 -0
  162. {npcsh-1.1.19.data → npcsh-1.1.21.data}/data/npcsh/npc_team/teamviz.jinx +0 -0
  163. {npcsh-1.1.19.data → npcsh-1.1.21.data}/data/npcsh/npc_team/trigger.jinx +0 -0
  164. {npcsh-1.1.19.data → npcsh-1.1.21.data}/data/npcsh/npc_team/type_text.jinx +0 -0
  165. {npcsh-1.1.19.data → npcsh-1.1.21.data}/data/npcsh/npc_team/usage.jinx +0 -0
  166. {npcsh-1.1.19.data → npcsh-1.1.21.data}/data/npcsh/npc_team/verbose.jinx +0 -0
  167. {npcsh-1.1.19.data → npcsh-1.1.21.data}/data/npcsh/npc_team/wait.jinx +0 -0
  168. {npcsh-1.1.19.data → npcsh-1.1.21.data}/data/npcsh/npc_team/write_file.jinx +0 -0
  169. {npcsh-1.1.19.data → npcsh-1.1.21.data}/data/npcsh/npc_team/yap.png +0 -0
  170. {npcsh-1.1.19.data → npcsh-1.1.21.data}/data/npcsh/npc_team/zen_mode.jinx +0 -0
  171. {npcsh-1.1.19.dist-info → npcsh-1.1.21.dist-info}/WHEEL +0 -0
  172. {npcsh-1.1.19.dist-info → npcsh-1.1.21.dist-info}/licenses/LICENSE +0 -0
  173. {npcsh-1.1.19.dist-info → npcsh-1.1.21.dist-info}/top_level.txt +0 -0
@@ -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':
@@ -277,7 +278,7 @@ steps:
277
278
  line = line[:width+15]
278
279
 
279
280
  if idx == selected:
280
- sys.stdout.write(f'\033[47;30;1m>{line}\033[0m')
281
+ sys.stdout.write(f'\033[7;1m>{line}\033[0m')
281
282
  else:
282
283
  sys.stdout.write(f' {line}')
283
284
 
@@ -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