npcsh 1.1.20__py3-none-any.whl → 1.1.22__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (186) hide show
  1. npcsh/_state.py +15 -76
  2. npcsh/benchmark/npcsh_agent.py +22 -14
  3. npcsh/benchmark/templates/install-npcsh.sh.j2 +2 -2
  4. npcsh/diff_viewer.py +3 -3
  5. npcsh/mcp_server.py +9 -1
  6. npcsh/npc_team/alicanto.npc +12 -6
  7. npcsh/npc_team/corca.npc +0 -1
  8. npcsh/npc_team/frederic.npc +2 -3
  9. npcsh/npc_team/jinxs/lib/core/compress.jinx +373 -85
  10. npcsh/npc_team/jinxs/lib/core/edit_file.jinx +83 -61
  11. npcsh/npc_team/jinxs/lib/core/search/db_search.jinx +17 -6
  12. npcsh/npc_team/jinxs/lib/core/search/file_search.jinx +17 -6
  13. npcsh/npc_team/jinxs/lib/core/search/web_search.jinx +52 -14
  14. npcsh/npc_team/jinxs/{bin → lib/utils}/benchmark.jinx +2 -2
  15. npcsh/npc_team/jinxs/{bin → lib/utils}/jinxs.jinx +12 -12
  16. npcsh/npc_team/jinxs/{bin → lib/utils}/models.jinx +7 -7
  17. npcsh/npc_team/jinxs/{bin → lib/utils}/setup.jinx +6 -6
  18. npcsh/npc_team/jinxs/modes/alicanto.jinx +1633 -295
  19. npcsh/npc_team/jinxs/modes/arxiv.jinx +5 -5
  20. npcsh/npc_team/jinxs/modes/build.jinx +378 -0
  21. npcsh/npc_team/jinxs/modes/config_tui.jinx +300 -0
  22. npcsh/npc_team/jinxs/modes/convene.jinx +597 -0
  23. npcsh/npc_team/jinxs/modes/corca.jinx +777 -387
  24. npcsh/npc_team/jinxs/modes/git.jinx +795 -0
  25. {npcsh-1.1.20.data/data/npcsh/npc_team → npcsh/npc_team/jinxs/modes}/kg.jinx +82 -15
  26. npcsh/npc_team/jinxs/modes/memories.jinx +414 -0
  27. npcsh/npc_team/jinxs/{bin → modes}/nql.jinx +10 -21
  28. npcsh/npc_team/jinxs/modes/papers.jinx +578 -0
  29. npcsh/npc_team/jinxs/modes/plonk.jinx +503 -308
  30. npcsh/npc_team/jinxs/modes/reattach.jinx +3 -3
  31. npcsh/npc_team/jinxs/modes/spool.jinx +3 -3
  32. npcsh/npc_team/jinxs/{bin → modes}/team.jinx +12 -12
  33. npcsh/npc_team/jinxs/modes/vixynt.jinx +388 -0
  34. npcsh/npc_team/jinxs/modes/wander.jinx +454 -181
  35. npcsh/npc_team/jinxs/modes/yap.jinx +630 -182
  36. npcsh/npc_team/kadiefa.npc +2 -1
  37. npcsh/npc_team/sibiji.npc +3 -3
  38. npcsh/npcsh.py +112 -47
  39. npcsh/routes.py +4 -1
  40. npcsh/salmon_simulation.py +0 -0
  41. npcsh-1.1.22.data/data/npcsh/npc_team/alicanto.jinx +1694 -0
  42. {npcsh-1.1.20.data → npcsh-1.1.22.data}/data/npcsh/npc_team/alicanto.npc +12 -6
  43. {npcsh-1.1.20.data → npcsh-1.1.22.data}/data/npcsh/npc_team/arxiv.jinx +5 -5
  44. {npcsh-1.1.20.data → npcsh-1.1.22.data}/data/npcsh/npc_team/benchmark.jinx +2 -2
  45. npcsh-1.1.22.data/data/npcsh/npc_team/build.jinx +378 -0
  46. npcsh-1.1.22.data/data/npcsh/npc_team/compress.jinx +428 -0
  47. npcsh-1.1.22.data/data/npcsh/npc_team/config_tui.jinx +300 -0
  48. npcsh-1.1.22.data/data/npcsh/npc_team/corca.jinx +820 -0
  49. {npcsh-1.1.20.data → npcsh-1.1.22.data}/data/npcsh/npc_team/corca.npc +0 -1
  50. {npcsh-1.1.20.data → npcsh-1.1.22.data}/data/npcsh/npc_team/db_search.jinx +17 -6
  51. npcsh-1.1.22.data/data/npcsh/npc_team/edit_file.jinx +119 -0
  52. {npcsh-1.1.20.data → npcsh-1.1.22.data}/data/npcsh/npc_team/file_search.jinx +17 -6
  53. {npcsh-1.1.20.data → npcsh-1.1.22.data}/data/npcsh/npc_team/frederic.npc +2 -3
  54. npcsh-1.1.22.data/data/npcsh/npc_team/git.jinx +795 -0
  55. {npcsh-1.1.20.data → npcsh-1.1.22.data}/data/npcsh/npc_team/jinxs.jinx +12 -12
  56. {npcsh-1.1.20.data → npcsh-1.1.22.data}/data/npcsh/npc_team/kadiefa.npc +2 -1
  57. {npcsh/npc_team/jinxs/bin → npcsh-1.1.22.data/data/npcsh/npc_team}/kg.jinx +82 -15
  58. npcsh-1.1.22.data/data/npcsh/npc_team/memories.jinx +414 -0
  59. {npcsh-1.1.20.data → npcsh-1.1.22.data}/data/npcsh/npc_team/models.jinx +7 -7
  60. {npcsh-1.1.20.data → npcsh-1.1.22.data}/data/npcsh/npc_team/nql.jinx +10 -21
  61. npcsh-1.1.22.data/data/npcsh/npc_team/papers.jinx +578 -0
  62. npcsh-1.1.22.data/data/npcsh/npc_team/plonk.jinx +574 -0
  63. {npcsh-1.1.20.data → npcsh-1.1.22.data}/data/npcsh/npc_team/reattach.jinx +3 -3
  64. {npcsh-1.1.20.data → npcsh-1.1.22.data}/data/npcsh/npc_team/setup.jinx +6 -6
  65. {npcsh-1.1.20.data → npcsh-1.1.22.data}/data/npcsh/npc_team/sibiji.npc +3 -3
  66. {npcsh-1.1.20.data → npcsh-1.1.22.data}/data/npcsh/npc_team/spool.jinx +3 -3
  67. {npcsh-1.1.20.data → npcsh-1.1.22.data}/data/npcsh/npc_team/team.jinx +12 -12
  68. npcsh-1.1.22.data/data/npcsh/npc_team/vixynt.jinx +388 -0
  69. npcsh-1.1.22.data/data/npcsh/npc_team/wander.jinx +728 -0
  70. {npcsh-1.1.20.data → npcsh-1.1.22.data}/data/npcsh/npc_team/web_search.jinx +52 -14
  71. npcsh-1.1.22.data/data/npcsh/npc_team/yap.jinx +716 -0
  72. {npcsh-1.1.20.dist-info → npcsh-1.1.22.dist-info}/METADATA +246 -281
  73. npcsh-1.1.22.dist-info/RECORD +240 -0
  74. npcsh-1.1.22.dist-info/entry_points.txt +11 -0
  75. npcsh/npc_team/jinxs/bin/config_tui.jinx +0 -300
  76. npcsh/npc_team/jinxs/bin/memories.jinx +0 -317
  77. npcsh/npc_team/jinxs/bin/vixynt.jinx +0 -122
  78. npcsh/npc_team/jinxs/lib/core/search/kg_search.jinx +0 -418
  79. npcsh/npc_team/jinxs/lib/core/search/mem_review.jinx +0 -73
  80. npcsh/npc_team/jinxs/lib/core/search/mem_search.jinx +0 -388
  81. npcsh/npc_team/jinxs/lib/core/search.jinx +0 -54
  82. npcsh/npc_team/jinxs/lib/research/paper_search.jinx +0 -412
  83. npcsh/npc_team/jinxs/lib/research/semantic_scholar.jinx +0 -386
  84. npcsh/npc_team/jinxs/lib/utils/build.jinx +0 -65
  85. npcsh/npc_team/plonkjr.npc +0 -23
  86. npcsh-1.1.20.data/data/npcsh/npc_team/alicanto.jinx +0 -356
  87. npcsh-1.1.20.data/data/npcsh/npc_team/build.jinx +0 -65
  88. npcsh-1.1.20.data/data/npcsh/npc_team/compress.jinx +0 -140
  89. npcsh-1.1.20.data/data/npcsh/npc_team/config_tui.jinx +0 -300
  90. npcsh-1.1.20.data/data/npcsh/npc_team/corca.jinx +0 -430
  91. npcsh-1.1.20.data/data/npcsh/npc_team/edit_file.jinx +0 -97
  92. npcsh-1.1.20.data/data/npcsh/npc_team/kg_search.jinx +0 -418
  93. npcsh-1.1.20.data/data/npcsh/npc_team/mem_review.jinx +0 -73
  94. npcsh-1.1.20.data/data/npcsh/npc_team/mem_search.jinx +0 -388
  95. npcsh-1.1.20.data/data/npcsh/npc_team/memories.jinx +0 -317
  96. npcsh-1.1.20.data/data/npcsh/npc_team/paper_search.jinx +0 -412
  97. npcsh-1.1.20.data/data/npcsh/npc_team/plonk.jinx +0 -379
  98. npcsh-1.1.20.data/data/npcsh/npc_team/plonkjr.npc +0 -23
  99. npcsh-1.1.20.data/data/npcsh/npc_team/search.jinx +0 -54
  100. npcsh-1.1.20.data/data/npcsh/npc_team/semantic_scholar.jinx +0 -386
  101. npcsh-1.1.20.data/data/npcsh/npc_team/vixynt.jinx +0 -122
  102. npcsh-1.1.20.data/data/npcsh/npc_team/wander.jinx +0 -455
  103. npcsh-1.1.20.data/data/npcsh/npc_team/yap.jinx +0 -268
  104. npcsh-1.1.20.dist-info/RECORD +0 -248
  105. npcsh-1.1.20.dist-info/entry_points.txt +0 -25
  106. /npcsh/npc_team/jinxs/lib/{orchestration → core}/convene.jinx +0 -0
  107. /npcsh/npc_team/jinxs/lib/{orchestration → core}/delegate.jinx +0 -0
  108. /npcsh/npc_team/jinxs/{bin → lib/core}/sample.jinx +0 -0
  109. /npcsh/npc_team/jinxs/lib/{core → utils}/chat.jinx +0 -0
  110. /npcsh/npc_team/jinxs/lib/{core → utils}/cmd.jinx +0 -0
  111. /npcsh/npc_team/jinxs/{bin → lib/utils}/sync.jinx +0 -0
  112. /npcsh/npc_team/jinxs/{bin → modes}/roll.jinx +0 -0
  113. {npcsh-1.1.20.data → npcsh-1.1.22.data}/data/npcsh/npc_team/add_tab.jinx +0 -0
  114. {npcsh-1.1.20.data → npcsh-1.1.22.data}/data/npcsh/npc_team/alicanto.png +0 -0
  115. {npcsh-1.1.20.data → npcsh-1.1.22.data}/data/npcsh/npc_team/browser_action.jinx +0 -0
  116. {npcsh-1.1.20.data → npcsh-1.1.22.data}/data/npcsh/npc_team/browser_screenshot.jinx +0 -0
  117. {npcsh-1.1.20.data → npcsh-1.1.22.data}/data/npcsh/npc_team/chat.jinx +0 -0
  118. {npcsh-1.1.20.data → npcsh-1.1.22.data}/data/npcsh/npc_team/click.jinx +0 -0
  119. {npcsh-1.1.20.data → npcsh-1.1.22.data}/data/npcsh/npc_team/close_browser.jinx +0 -0
  120. {npcsh-1.1.20.data → npcsh-1.1.22.data}/data/npcsh/npc_team/close_pane.jinx +0 -0
  121. {npcsh-1.1.20.data → npcsh-1.1.22.data}/data/npcsh/npc_team/close_tab.jinx +0 -0
  122. {npcsh-1.1.20.data → npcsh-1.1.22.data}/data/npcsh/npc_team/cmd.jinx +0 -0
  123. {npcsh-1.1.20.data → npcsh-1.1.22.data}/data/npcsh/npc_team/compile.jinx +0 -0
  124. {npcsh-1.1.20.data → npcsh-1.1.22.data}/data/npcsh/npc_team/confirm.jinx +0 -0
  125. {npcsh-1.1.20.data → npcsh-1.1.22.data}/data/npcsh/npc_team/convene.jinx +0 -0
  126. {npcsh-1.1.20.data → npcsh-1.1.22.data}/data/npcsh/npc_team/corca.png +0 -0
  127. {npcsh-1.1.20.data → npcsh-1.1.22.data}/data/npcsh/npc_team/corca_example.png +0 -0
  128. {npcsh-1.1.20.data → npcsh-1.1.22.data}/data/npcsh/npc_team/delegate.jinx +0 -0
  129. {npcsh-1.1.20.data → npcsh-1.1.22.data}/data/npcsh/npc_team/focus_pane.jinx +0 -0
  130. {npcsh-1.1.20.data → npcsh-1.1.22.data}/data/npcsh/npc_team/frederic4.png +0 -0
  131. {npcsh-1.1.20.data → npcsh-1.1.22.data}/data/npcsh/npc_team/guac.jinx +0 -0
  132. {npcsh-1.1.20.data → npcsh-1.1.22.data}/data/npcsh/npc_team/guac.npc +0 -0
  133. {npcsh-1.1.20.data → npcsh-1.1.22.data}/data/npcsh/npc_team/guac.png +0 -0
  134. {npcsh-1.1.20.data → npcsh-1.1.22.data}/data/npcsh/npc_team/help.jinx +0 -0
  135. {npcsh-1.1.20.data → npcsh-1.1.22.data}/data/npcsh/npc_team/incognide.jinx +0 -0
  136. {npcsh-1.1.20.data → npcsh-1.1.22.data}/data/npcsh/npc_team/init.jinx +0 -0
  137. {npcsh-1.1.20.data → npcsh-1.1.22.data}/data/npcsh/npc_team/kadiefa.png +0 -0
  138. {npcsh-1.1.20.data → npcsh-1.1.22.data}/data/npcsh/npc_team/key_press.jinx +0 -0
  139. {npcsh-1.1.20.data → npcsh-1.1.22.data}/data/npcsh/npc_team/launch_app.jinx +0 -0
  140. {npcsh-1.1.20.data → npcsh-1.1.22.data}/data/npcsh/npc_team/list_panes.jinx +0 -0
  141. {npcsh-1.1.20.data → npcsh-1.1.22.data}/data/npcsh/npc_team/load_file.jinx +0 -0
  142. {npcsh-1.1.20.data → npcsh-1.1.22.data}/data/npcsh/npc_team/navigate.jinx +0 -0
  143. {npcsh-1.1.20.data → npcsh-1.1.22.data}/data/npcsh/npc_team/notify.jinx +0 -0
  144. {npcsh-1.1.20.data → npcsh-1.1.22.data}/data/npcsh/npc_team/npcsh.ctx +0 -0
  145. {npcsh-1.1.20.data → npcsh-1.1.22.data}/data/npcsh/npc_team/npcsh_sibiji.png +0 -0
  146. {npcsh-1.1.20.data → npcsh-1.1.22.data}/data/npcsh/npc_team/open_browser.jinx +0 -0
  147. {npcsh-1.1.20.data → npcsh-1.1.22.data}/data/npcsh/npc_team/open_pane.jinx +0 -0
  148. {npcsh-1.1.20.data → npcsh-1.1.22.data}/data/npcsh/npc_team/ots.jinx +0 -0
  149. {npcsh-1.1.20.data → npcsh-1.1.22.data}/data/npcsh/npc_team/paste.jinx +0 -0
  150. {npcsh-1.1.20.data → npcsh-1.1.22.data}/data/npcsh/npc_team/plonk.npc +0 -0
  151. {npcsh-1.1.20.data → npcsh-1.1.22.data}/data/npcsh/npc_team/plonk.png +0 -0
  152. {npcsh-1.1.20.data → npcsh-1.1.22.data}/data/npcsh/npc_team/plonkjr.png +0 -0
  153. {npcsh-1.1.20.data → npcsh-1.1.22.data}/data/npcsh/npc_team/pti.jinx +0 -0
  154. {npcsh-1.1.20.data → npcsh-1.1.22.data}/data/npcsh/npc_team/python.jinx +0 -0
  155. {npcsh-1.1.20.data → npcsh-1.1.22.data}/data/npcsh/npc_team/read_pane.jinx +0 -0
  156. {npcsh-1.1.20.data → npcsh-1.1.22.data}/data/npcsh/npc_team/roll.jinx +0 -0
  157. {npcsh-1.1.20.data → npcsh-1.1.22.data}/data/npcsh/npc_team/run_terminal.jinx +0 -0
  158. {npcsh-1.1.20.data → npcsh-1.1.22.data}/data/npcsh/npc_team/sample.jinx +0 -0
  159. {npcsh-1.1.20.data → npcsh-1.1.22.data}/data/npcsh/npc_team/screenshot.jinx +0 -0
  160. {npcsh-1.1.20.data → npcsh-1.1.22.data}/data/npcsh/npc_team/send_message.jinx +0 -0
  161. {npcsh-1.1.20.data → npcsh-1.1.22.data}/data/npcsh/npc_team/serve.jinx +0 -0
  162. {npcsh-1.1.20.data → npcsh-1.1.22.data}/data/npcsh/npc_team/set.jinx +0 -0
  163. {npcsh-1.1.20.data → npcsh-1.1.22.data}/data/npcsh/npc_team/sh.jinx +0 -0
  164. {npcsh-1.1.20.data → npcsh-1.1.22.data}/data/npcsh/npc_team/shh.jinx +0 -0
  165. {npcsh-1.1.20.data → npcsh-1.1.22.data}/data/npcsh/npc_team/sibiji.png +0 -0
  166. {npcsh-1.1.20.data → npcsh-1.1.22.data}/data/npcsh/npc_team/sleep.jinx +0 -0
  167. {npcsh-1.1.20.data → npcsh-1.1.22.data}/data/npcsh/npc_team/split_pane.jinx +0 -0
  168. {npcsh-1.1.20.data → npcsh-1.1.22.data}/data/npcsh/npc_team/spool.png +0 -0
  169. {npcsh-1.1.20.data → npcsh-1.1.22.data}/data/npcsh/npc_team/sql.jinx +0 -0
  170. {npcsh-1.1.20.data → npcsh-1.1.22.data}/data/npcsh/npc_team/switch.jinx +0 -0
  171. {npcsh-1.1.20.data → npcsh-1.1.22.data}/data/npcsh/npc_team/switch_npc.jinx +0 -0
  172. {npcsh-1.1.20.data → npcsh-1.1.22.data}/data/npcsh/npc_team/switch_tab.jinx +0 -0
  173. {npcsh-1.1.20.data → npcsh-1.1.22.data}/data/npcsh/npc_team/switches.jinx +0 -0
  174. {npcsh-1.1.20.data → npcsh-1.1.22.data}/data/npcsh/npc_team/sync.jinx +0 -0
  175. {npcsh-1.1.20.data → npcsh-1.1.22.data}/data/npcsh/npc_team/teamviz.jinx +0 -0
  176. {npcsh-1.1.20.data → npcsh-1.1.22.data}/data/npcsh/npc_team/trigger.jinx +0 -0
  177. {npcsh-1.1.20.data → npcsh-1.1.22.data}/data/npcsh/npc_team/type_text.jinx +0 -0
  178. {npcsh-1.1.20.data → npcsh-1.1.22.data}/data/npcsh/npc_team/usage.jinx +0 -0
  179. {npcsh-1.1.20.data → npcsh-1.1.22.data}/data/npcsh/npc_team/verbose.jinx +0 -0
  180. {npcsh-1.1.20.data → npcsh-1.1.22.data}/data/npcsh/npc_team/wait.jinx +0 -0
  181. {npcsh-1.1.20.data → npcsh-1.1.22.data}/data/npcsh/npc_team/write_file.jinx +0 -0
  182. {npcsh-1.1.20.data → npcsh-1.1.22.data}/data/npcsh/npc_team/yap.png +0 -0
  183. {npcsh-1.1.20.data → npcsh-1.1.22.data}/data/npcsh/npc_team/zen_mode.jinx +0 -0
  184. {npcsh-1.1.20.dist-info → npcsh-1.1.22.dist-info}/WHEEL +0 -0
  185. {npcsh-1.1.20.dist-info → npcsh-1.1.22.dist-info}/licenses/LICENSE +0 -0
  186. {npcsh-1.1.20.dist-info → npcsh-1.1.22.dist-info}/top_level.txt +0 -0
@@ -1,388 +0,0 @@
1
- jinx_name: mem_search
2
- description: Search memories with interactive TUI and approval workflow
3
- inputs:
4
- - query: ""
5
- - status: "all"
6
- - npc_name: ""
7
- - team_name: ""
8
- - max_results: "20"
9
- - db_path: ""
10
- - text: "false"
11
-
12
- steps:
13
- - name: search_memories
14
- engine: python
15
- code: |
16
- import os
17
- import sys
18
- import tty
19
- import termios
20
- from datetime import datetime
21
- from npcpy.memory.command_history import CommandHistory
22
- from npcpy.memory.memory_processor import get_relevant_memories
23
-
24
- query = context.get('query', '').strip()
25
- text_mode = context.get('text', '').lower() in ('true', '1', 'yes')
26
-
27
- if not query:
28
- lines = [
29
- "Usage: /mem_search <query> [status=all|approved|pending]",
30
- "",
31
- "Options:",
32
- " status - Filter by status (all, approved, pending). Default is all",
33
- " npc_name - Filter by NPC name",
34
- " team_name - Filter by team name",
35
- " max_results - Max results to return (default 20)",
36
- " db_path - Path to history database",
37
- " text - Text-only output, no TUI (true/false)",
38
- "",
39
- "TUI Controls:",
40
- " j/k or arrows - Navigate",
41
- " 1/2/3 - Sort by time/status/npc",
42
- " f - Filter by status (all/approved/pending)",
43
- " p - Preview full memory",
44
- " a - Approve selected memory",
45
- " x - Reject selected memory",
46
- " q/ESC - Quit",
47
- "",
48
- "Examples:",
49
- " /mem_search python",
50
- " /mem_search debugging status=pending",
51
- ]
52
- context['output'] = "\n".join(lines)
53
- else:
54
- status_filter = context.get('status', 'all').lower()
55
- npc_name = context.get('npc_name') or (npc.name if 'npc' in dir() and npc else None)
56
- team_name = context.get('team_name') or None
57
- try:
58
- team_name = team_name or (state.team.name if 'state' in dir() and state and state.team else None)
59
- except:
60
- pass
61
- max_results = int(context.get('max_results') or 20)
62
- db_path = context.get('db_path') or os.path.expanduser("~/npcsh_history.db")
63
- current_path = os.getcwd()
64
-
65
- try:
66
- cmd_history = CommandHistory(db_path)
67
-
68
- if status_filter == 'approved':
69
- state_obj = state if 'state' in dir() else None
70
- memories = get_relevant_memories(
71
- command_history=cmd_history,
72
- npc_name=npc_name or '__none__',
73
- team_name=team_name or '__none__',
74
- path=current_path,
75
- query=query,
76
- max_memories=max_results,
77
- state=state_obj
78
- )
79
- else:
80
- memories = cmd_history.search_memory(
81
- query=query,
82
- npc=npc_name,
83
- team=team_name,
84
- status_filter=status_filter if status_filter != 'all' else None,
85
- limit=max_results
86
- )
87
-
88
- # Normalize to list of dicts
89
- if memories:
90
- normalized = []
91
- for mem in memories:
92
- if isinstance(mem, dict):
93
- normalized.append(mem)
94
- else:
95
- normalized.append({'content': str(mem), 'status': 'unknown', 'timestamp': '', 'npc': ''})
96
- memories = normalized
97
- else:
98
- memories = []
99
-
100
- if not memories:
101
- context['output'] = f"No memories found for '{query}' (status={status_filter})"
102
- elif text_mode:
103
- # Text-only output
104
- lines = [f"Found {len(memories)} memories (status={status_filter}):", ""]
105
- for i, mem in enumerate(memories, 1):
106
- ts = mem.get('timestamp', 'unknown')
107
- content = mem.get('final_memory') or mem.get('initial_memory') or mem.get('content', '')
108
- status = mem.get('status', '')
109
- lines.append(f"{i}. [{ts}] ({status}) {str(content)[:80]}")
110
- context['output'] = "\n".join(lines)
111
- else:
112
- # Interactive TUI mode
113
- def get_terminal_size():
114
- try:
115
- size = os.get_terminal_size()
116
- return size.columns, size.lines
117
- except:
118
- return 80, 24
119
-
120
- def format_ts(ts):
121
- if not ts:
122
- return 'unknown'
123
- try:
124
- if 'T' in str(ts):
125
- dt = datetime.fromisoformat(str(ts).replace('Z', '+00:00'))
126
- else:
127
- dt = datetime.strptime(str(ts)[:19], '%Y-%m-%d %H:%M:%S')
128
- now = datetime.now()
129
- diff = now - dt.replace(tzinfo=None)
130
- if diff.days == 0:
131
- return f"Today {dt.strftime('%H:%M')}"
132
- elif diff.days == 1:
133
- return f"Yesterday {dt.strftime('%H:%M')}"
134
- elif diff.days < 7:
135
- return dt.strftime('%a %H:%M')
136
- else:
137
- return dt.strftime('%b %d')
138
- except:
139
- return str(ts)[:12]
140
-
141
- width, height = get_terminal_size()
142
- selected = 0
143
- scroll = 0
144
- list_height = height - 5
145
- mode = 'list'
146
- preview_scroll = 0
147
- sort_mode = 'time' # time, status, npc
148
- current_filter = status_filter
149
-
150
- def sort_memories(mems, sort_mode):
151
- if sort_mode == 'time':
152
- return sorted(mems, key=lambda x: x.get('timestamp') or '', reverse=True)
153
- elif sort_mode == 'status':
154
- return sorted(mems, key=lambda x: (x.get('status') or '', x.get('timestamp') or ''), reverse=True)
155
- elif sort_mode == 'npc':
156
- return sorted(mems, key=lambda x: (x.get('npc') or '', x.get('timestamp') or ''), reverse=True)
157
- return mems
158
-
159
- def filter_memories(mems, filter_status):
160
- if filter_status == 'all':
161
- return mems
162
- return [m for m in mems if m.get('status') == filter_status]
163
-
164
- display_memories = filter_memories(sort_memories(memories, sort_mode), current_filter)
165
-
166
- fd = sys.stdin.fileno()
167
- old_settings = termios.tcgetattr(fd)
168
-
169
- try:
170
- tty.setcbreak(fd)
171
- sys.stdout.write('\033[?25l')
172
- sys.stdout.write('\033[2J\033[H')
173
-
174
- while True:
175
- width, height = get_terminal_size()
176
- list_height = height - 5
177
-
178
- if mode == 'list':
179
- if selected < scroll:
180
- scroll = selected
181
- elif selected >= scroll + list_height:
182
- scroll = selected - list_height + 1
183
-
184
- sys.stdout.write('\033[H')
185
-
186
- # Header
187
- if mode == 'list':
188
- sort_ind = {'time': '1', 'status': '2', 'npc': '3'}[sort_mode]
189
- header = f" MEM SEARCH ({len(display_memories)} results): '{query}' [sort:{sort_mode}({sort_ind}) filter:{current_filter}] "
190
- else:
191
- header = f" PREVIEW MEMORY "
192
- sys.stdout.write(f'\033[44;37;1m{header.ljust(width)}\033[0m\n')
193
-
194
- # Column headers
195
- if mode == 'list':
196
- col_header = f' {"STATUS":<10} {"TIMESTAMP":<14} {"NPC":<12} {"CONTENT":<40}'
197
- sys.stdout.write(f'\033[90m{col_header[:width]}\033[0m\n')
198
- else:
199
- sys.stdout.write(f'\033[90m{"─" * width}\033[0m\n')
200
-
201
- if mode == 'list':
202
- for i in range(list_height):
203
- idx = scroll + i
204
- sys.stdout.write(f'\033[{3+i};1H\033[K')
205
- if idx >= len(display_memories):
206
- continue
207
-
208
- m = display_memories[idx]
209
- status = (m.get('status') or '?')[:10]
210
- ts = format_ts(m.get('timestamp'))
211
- npc_str = (m.get('npc') or 'default')[:12]
212
- content = (m.get('final_memory') or m.get('initial_memory') or m.get('content', ''))
213
- content = str(content)[:50].replace('\n', ' ')
214
-
215
- # Color by status
216
- if m.get('status') == 'approved':
217
- status_color = '\033[32m' # green
218
- elif m.get('status') == 'pending':
219
- status_color = '\033[33m' # yellow
220
- elif m.get('status') == 'rejected':
221
- status_color = '\033[31m' # red
222
- else:
223
- status_color = '\033[90m' # gray
224
-
225
- line = f" {status_color}{status:<10}\033[0m {ts:<14} {npc_str:<12} {content}"
226
- line = line[:width+15]
227
-
228
- if idx == selected:
229
- sys.stdout.write(f'\033[7;1m>{line}\033[0m')
230
- else:
231
- sys.stdout.write(f' {line}')
232
-
233
- # Status bar
234
- sys.stdout.write(f'\033[{height-2};1H\033[K\033[90m{"─" * width}\033[0m')
235
- sel = display_memories[selected] if display_memories else {}
236
- mem_id = sel.get('id', '') or sel.get('memory_id', '')
237
- sys.stdout.write(f'\033[{height-1};1H\033[K ID: {mem_id}'.ljust(width))
238
- sys.stdout.write(f'\033[{height};1H\033[K\033[44;37m j/k:Nav 1/2/3:Sort f:Filter p:Preview a:Approve x:Reject q:Quit [{selected+1}/{len(display_memories)}] \033[0m')
239
-
240
- else: # preview mode
241
- sel = display_memories[selected]
242
- content = sel.get('final_memory') or sel.get('initial_memory') or sel.get('content', '')
243
- content_lines = str(content).split('\n')
244
-
245
- # Add metadata at top
246
- meta_lines = [
247
- f"Status: {sel.get('status', '')}",
248
- f"Timestamp: {sel.get('timestamp', '')}",
249
- f"NPC: {sel.get('npc', '')}",
250
- f"Team: {sel.get('team', '')}",
251
- f"ID: {sel.get('id', '') or sel.get('memory_id', '')}",
252
- "─" * 40,
253
- ]
254
- all_lines = meta_lines + content_lines
255
-
256
- for i in range(list_height):
257
- idx = preview_scroll + i
258
- sys.stdout.write(f'\033[{3+i};1H\033[K')
259
- if idx < len(all_lines):
260
- sys.stdout.write(all_lines[idx][:width-1])
261
-
262
- sys.stdout.write(f'\033[{height-2};1H\033[K\033[90m{"─" * width}\033[0m')
263
- sys.stdout.write(f'\033[{height-1};1H\033[K [{preview_scroll+1}/{len(all_lines)} lines]')
264
- sys.stdout.write(f'\033[{height};1H\033[K\033[44;37m j/k:Scroll b:Back a:Approve x:Reject q:Quit \033[0m')
265
-
266
- sys.stdout.flush()
267
-
268
- c = sys.stdin.read(1)
269
-
270
- if c == '\x1b':
271
- c2 = sys.stdin.read(1)
272
- if c2 == '[':
273
- c3 = sys.stdin.read(1)
274
- if c3 == 'A': # Up
275
- if mode == 'list' and selected > 0:
276
- selected -= 1
277
- elif mode == 'preview' and preview_scroll > 0:
278
- preview_scroll -= 1
279
- elif c3 == 'B': # Down
280
- if mode == 'list' and selected < len(display_memories) - 1:
281
- selected += 1
282
- elif mode == 'preview':
283
- sel = display_memories[selected]
284
- content = str(sel.get('final_memory') or sel.get('content', ''))
285
- all_lines = content.split('\n')
286
- if preview_scroll < max(0, len(all_lines) + 6 - list_height):
287
- preview_scroll += 1
288
- else:
289
- if mode == 'preview':
290
- mode = 'list'
291
- sys.stdout.write('\033[2J\033[H')
292
- else:
293
- context['output'] = "Cancelled."
294
- break
295
- continue
296
-
297
- if c == 'q' or c == '\x03':
298
- context['output'] = "Cancelled."
299
- break
300
- elif c == 'k':
301
- if mode == 'list' and selected > 0:
302
- selected -= 1
303
- elif mode == 'preview' and preview_scroll > 0:
304
- preview_scroll -= 1
305
- elif c == 'j':
306
- if mode == 'list' and selected < len(display_memories) - 1:
307
- selected += 1
308
- elif mode == 'preview':
309
- sel = display_memories[selected]
310
- content = str(sel.get('final_memory') or sel.get('content', ''))
311
- all_lines = content.split('\n')
312
- if preview_scroll < max(0, len(all_lines) + 6 - list_height):
313
- preview_scroll += 1
314
- elif c == '1':
315
- sort_mode = 'time'
316
- display_memories = filter_memories(sort_memories(memories, sort_mode), current_filter)
317
- selected = 0
318
- scroll = 0
319
- elif c == '2':
320
- sort_mode = 'status'
321
- display_memories = filter_memories(sort_memories(memories, sort_mode), current_filter)
322
- selected = 0
323
- scroll = 0
324
- elif c == '3':
325
- sort_mode = 'npc'
326
- display_memories = filter_memories(sort_memories(memories, sort_mode), current_filter)
327
- selected = 0
328
- scroll = 0
329
- elif c == 'f' and mode == 'list':
330
- # Cycle through filters
331
- if current_filter == 'all':
332
- current_filter = 'pending'
333
- elif current_filter == 'pending':
334
- current_filter = 'approved'
335
- else:
336
- current_filter = 'all'
337
- display_memories = filter_memories(sort_memories(memories, sort_mode), current_filter)
338
- selected = 0
339
- scroll = 0
340
- elif c == 'a' and display_memories:
341
- # Approve memory
342
- sel = display_memories[selected]
343
- mem_id = sel.get('id') or sel.get('memory_id')
344
- if mem_id:
345
- try:
346
- cmd_history.update_memory_status(mem_id, 'approved')
347
- sel['status'] = 'approved'
348
- # Update in original list too
349
- for m in memories:
350
- if (m.get('id') or m.get('memory_id')) == mem_id:
351
- m['status'] = 'approved'
352
- except Exception as e:
353
- pass
354
- elif c == 'x' and display_memories:
355
- # Reject memory
356
- sel = display_memories[selected]
357
- mem_id = sel.get('id') or sel.get('memory_id')
358
- if mem_id:
359
- try:
360
- cmd_history.update_memory_status(mem_id, 'rejected')
361
- sel['status'] = 'rejected'
362
- for m in memories:
363
- if (m.get('id') or m.get('memory_id')) == mem_id:
364
- m['status'] = 'rejected'
365
- except Exception as e:
366
- pass
367
- elif c == 'p' and mode == 'list' and display_memories:
368
- mode = 'preview'
369
- preview_scroll = 0
370
- sys.stdout.write('\033[2J\033[H')
371
- elif c == 'b' and mode == 'preview':
372
- mode = 'list'
373
- sys.stdout.write('\033[2J\033[H')
374
- elif c in ('\r', '\n') and display_memories:
375
- sel = display_memories[selected]
376
- content = sel.get('final_memory') or sel.get('content', '')
377
- context['output'] = f"Selected memory:\n\n{content}"
378
- break
379
-
380
- finally:
381
- termios.tcsetattr(fd, termios.TCSADRAIN, old_settings)
382
- sys.stdout.write('\033[?25h')
383
- sys.stdout.write('\033[2J\033[H')
384
- sys.stdout.flush()
385
-
386
- except Exception as e:
387
- import traceback
388
- context['output'] = "Memory search error: " + str(e) + "\n" + traceback.format_exc()