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
@@ -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()
@@ -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
@@ -65,8 +65,8 @@ steps:
65
65
  sys.stdout.flush()
66
66
  time.sleep(0.1)
67
67
  # Flush any pending input
68
- while select.select([sys.stdin], [], [], 0)[0]:
69
- sys.stdin.read(1)
68
+ while select.select([fd], [], [], 0)[0]:
69
+ os.read(fd, 1).decode('latin-1')
70
70
  except:
71
71
  pass
72
72
 
@@ -146,8 +146,8 @@ steps:
146
146
  out.append("\033[2J\033[H")
147
147
 
148
148
  header = f" NQL: {os.path.basename(db_path)} "
149
- out.append(f"\033[1;1H\033[44;37;1m{'=' * width}\033[0m")
150
- out.append(f"\033[1;{(width - len(header)) // 2}H\033[44;37;1m{header}\033[0m")
149
+ out.append(f"\033[1;1H\033[7;1m{'=' * width}\033[0m")
150
+ out.append(f"\033[1;{(width - len(header)) // 2}H\033[7;1m{header}\033[0m")
151
151
 
152
152
  modes = ['Tables', 'Query']
153
153
  tab_str = ""
@@ -264,10 +264,10 @@ steps:
264
264
 
265
265
  def handle_input(c):
266
266
  if c == '\x1b':
267
- if select.select([sys.stdin], [], [], 0.1)[0]:
268
- c2 = sys.stdin.read(1)
267
+ if select.select([fd], [], [], 0.1)[0]:
268
+ c2 = os.read(fd, 1).decode('latin-1')
269
269
  if c2 == '[':
270
- c3 = sys.stdin.read(1)
270
+ c3 = os.read(fd, 1).decode('latin-1')
271
271
  if c3 == 'A':
272
272
  if db_state.mode == 'tables':
273
273
  move_up()
@@ -324,19 +324,8 @@ steps:
324
324
  return True
325
325
 
326
326
  def handle_query_input(c):
327
- if c == 'k':
328
- db_state.row_scroll = max(0, db_state.row_scroll - 1)
329
- return True
330
- if c == 'j':
331
- db_state.row_scroll = min(max(0, len(db_state.rows) - 1), db_state.row_scroll + 1)
332
- return True
333
- if c == 'h':
334
- db_state.col_scroll = max(0, db_state.col_scroll - 1)
335
- return True
336
- if c == 'l':
337
- db_state.col_scroll = min(max(0, len(db_state.columns) - 1), db_state.col_scroll + 1)
338
- return True
339
-
327
+ # In query mode, all printable chars go to the buffer.
328
+ # Use arrow keys (handled in escape sequence block) for result navigation.
340
329
  if c == '\r' or c == '\n':
341
330
  if db_state.query_buffer.strip():
342
331
  run_query(db_state.query_buffer)
@@ -375,7 +364,7 @@ steps:
375
364
  render_screen()
376
365
 
377
366
  while True:
378
- c = sys.stdin.read(1)
367
+ c = os.read(fd, 1).decode('latin-1')
379
368
  if not handle_input(c):
380
369
  break
381
370
  render_screen()