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
@@ -1,317 +0,0 @@
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
- from datetime import datetime
16
-
17
- if not sys.stdin.isatty():
18
- context['output'] = "Memory browser requires an interactive terminal."
19
- return
20
-
21
- from npcpy.memory.command_history import CommandHistory
22
- from npcsh.config import NPCSH_DB_PATH
23
-
24
- db_path = os.path.expanduser(NPCSH_DB_PATH)
25
- command_history = CommandHistory(db_path)
26
-
27
- # ========== State ==========
28
- class MemoryState:
29
- def __init__(self):
30
- self.tab = 0 # 0=All, 1=Pending, 2=Approved, 3=Rejected
31
- self.tabs = ['All', 'Pending', 'Approved', 'Rejected']
32
- self.memories = []
33
- self.selected_idx = 0
34
- self.scroll_offset = 0
35
- self.preview_mode = False
36
- self.status = ""
37
- self.filters = {
38
- 'npc': None,
39
- 'team': None,
40
- }
41
-
42
- state = MemoryState()
43
-
44
- # ========== Helpers ==========
45
- def get_size():
46
- try:
47
- s = os.get_terminal_size()
48
- return s.columns, s.lines
49
- except:
50
- return 80, 24
51
-
52
- def load_memories():
53
- """Load memories based on current tab filter."""
54
- state.memories = []
55
-
56
- try:
57
- with command_history.engine.connect() as conn:
58
- status_filter = {
59
- 0: None, # All
60
- 1: 'pending',
61
- 2: 'approved',
62
- 3: 'rejected'
63
- }.get(state.tab)
64
-
65
- query = "SELECT id, created_at, npc_name, team_name, scope, original_memory, final_memory, status FROM memories"
66
- conditions = []
67
-
68
- if status_filter:
69
- conditions.append(f"status = '{status_filter}'")
70
- if state.filters['npc']:
71
- conditions.append(f"npc_name = '{state.filters['npc']}'")
72
- if state.filters['team']:
73
- conditions.append(f"team_name = '{state.filters['team']}'")
74
-
75
- if conditions:
76
- query += " WHERE " + " AND ".join(conditions)
77
-
78
- query += " ORDER BY created_at DESC LIMIT 100"
79
-
80
- from sqlalchemy import text
81
- result = conn.execute(text(query))
82
- for row in result:
83
- state.memories.append({
84
- 'id': row[0],
85
- 'created_at': row[1],
86
- 'npc': row[2],
87
- 'team': row[3],
88
- 'scope': row[4],
89
- 'original': row[5],
90
- 'final': row[6],
91
- 'status': row[7]
92
- })
93
- except Exception as e:
94
- state.status = f"Error loading memories: {e}"
95
-
96
- def update_memory_status(memory_id, new_status):
97
- """Update a memory's status."""
98
- try:
99
- command_history.update_memory_status(memory_id, new_status)
100
- state.status = f"Memory {memory_id} marked as {new_status}"
101
- load_memories()
102
- except Exception as e:
103
- state.status = f"Error: {e}"
104
-
105
- def format_date(dt_str):
106
- """Format datetime string for display."""
107
- if not dt_str:
108
- return ""
109
- try:
110
- if isinstance(dt_str, str):
111
- dt = datetime.fromisoformat(dt_str.replace('Z', '+00:00'))
112
- else:
113
- dt = dt_str
114
- return dt.strftime('%m-%d %H:%M')
115
- except:
116
- return str(dt_str)[:10]
117
-
118
- # ========== Rendering ==========
119
- def render_screen():
120
- width, height = get_size()
121
- out = []
122
- out.append("\033[2J\033[H")
123
-
124
- # Header
125
- header = " Memory Browser "
126
- out.append(f"\033[1;1H\033[44;37;1m{'=' * width}\033[0m")
127
- out.append(f"\033[1;{(width - len(header)) // 2}H\033[44;37;1m{header}\033[0m")
128
-
129
- # Tabs
130
- tab_str = ""
131
- for i, tab in enumerate(state.tabs):
132
- count = sum(1 for m in state.memories if state.tab == 0 or True) # Will filter properly
133
- if i == state.tab:
134
- tab_str += f"\033[7m [{tab}] \033[0m"
135
- else:
136
- tab_str += f" [{tab}] "
137
- out.append(f"\033[2;2H{tab_str}")
138
-
139
- # Separator
140
- out.append(f"\033[3;1H\033[90m{'─' * width}\033[0m")
141
-
142
- if state.preview_mode and state.memories:
143
- render_preview(out, width, height)
144
- else:
145
- render_list(out, width, height)
146
-
147
- # Status
148
- if state.status:
149
- out.append(f"\033[{height-2};2H\033[33m{state.status[:width-4]}\033[0m")
150
-
151
- # Footer
152
- if state.preview_mode:
153
- footer = "[Esc] Back [a] Approve [x] Reject [j/k] Prev/Next"
154
- else:
155
- footer = "[Tab] Filter [j/k] Navigate [p] Preview [a] Approve [x] Reject [q] Quit"
156
- out.append(f"\033[{height};1H\033[90m{footer[:width]}\033[0m")
157
-
158
- sys.stdout.write(''.join(out))
159
- sys.stdout.flush()
160
-
161
- def render_list(out, width, height):
162
- """Render memory list."""
163
- visible_height = height - 7
164
- visible = state.memories[state.scroll_offset:state.scroll_offset + visible_height]
165
-
166
- if not state.memories:
167
- out.append(f"\033[5;4H\033[90mNo memories found.\033[0m")
168
- return
169
-
170
- row = 4
171
- for i, mem in enumerate(visible):
172
- idx = i + state.scroll_offset
173
-
174
- # Status indicator
175
- status_icon = {
176
- 'pending': '\033[33m○\033[0m',
177
- 'approved': '\033[32m●\033[0m',
178
- 'rejected': '\033[31m✗\033[0m'
179
- }.get(mem['status'], '?')
180
-
181
- # Format line
182
- date_str = format_date(mem['created_at'])
183
- npc_str = mem['npc'][:8] if mem['npc'] else '-'
184
- content = (mem['final'] or mem['original'] or '')[:width-35]
185
- content = content.replace('\n', ' ')
186
-
187
- if idx == state.selected_idx:
188
- out.append(f"\033[{row};2H\033[7m{status_icon} {date_str} {npc_str:<8} {content}\033[0m")
189
- else:
190
- out.append(f"\033[{row};2H{status_icon} {date_str} \033[90m{npc_str:<8}\033[0m {content}")
191
-
192
- row += 1
193
-
194
- # Scroll indicator
195
- if len(state.memories) > visible_height:
196
- pct = int((state.scroll_offset / max(1, len(state.memories) - visible_height)) * 100)
197
- out.append(f"\033[4;{width-6}H\033[90m[{pct}%]\033[0m")
198
-
199
- def render_preview(out, width, height):
200
- """Render memory preview."""
201
- if not state.memories or state.selected_idx >= len(state.memories):
202
- return
203
-
204
- mem = state.memories[state.selected_idx]
205
-
206
- row = 4
207
- out.append(f"\033[{row};2H\033[1mMemory #{mem['id']}\033[0m")
208
- row += 1
209
-
210
- # Metadata
211
- status_color = {'pending': '33', 'approved': '32', 'rejected': '31'}.get(mem['status'], '0')
212
- out.append(f"\033[{row};2HStatus: \033[{status_color}m{mem['status']}\033[0m")
213
- row += 1
214
- out.append(f"\033[{row};2HDate: {format_date(mem['created_at'])}")
215
- row += 1
216
- out.append(f"\033[{row};2HNPC: {mem['npc'] or '-'} Team: {mem['team'] or '-'} Scope: {mem['scope'] or '-'}")
217
- row += 2
218
-
219
- # Content
220
- out.append(f"\033[{row};2H\033[1mContent:\033[0m")
221
- row += 1
222
-
223
- content = mem['final'] or mem['original'] or '(empty)'
224
- content_lines = content.split('\n')
225
- for line in content_lines[:height-row-3]:
226
- out.append(f"\033[{row};4H{line[:width-6]}")
227
- row += 1
228
-
229
- # ========== Input Handling ==========
230
- def handle_input(c):
231
- if c == 'q':
232
- return False
233
-
234
- if c == '\x1b': # Escape
235
- if select.select([sys.stdin], [], [], 0.05)[0]:
236
- c2 = sys.stdin.read(1)
237
- if c2 == '[':
238
- c3 = sys.stdin.read(1)
239
- if c3 == 'A': # Up
240
- move_up()
241
- elif c3 == 'B': # Down
242
- move_down()
243
- else:
244
- if state.preview_mode:
245
- state.preview_mode = False
246
- return True
247
-
248
- if c == '\t': # Tab - cycle tabs
249
- state.tab = (state.tab + 1) % len(state.tabs)
250
- state.selected_idx = 0
251
- state.scroll_offset = 0
252
- load_memories()
253
- state.status = ""
254
-
255
- elif c == 'k':
256
- move_up()
257
- elif c == 'j':
258
- move_down()
259
- elif c == 'p' or c == '\r' or c == '\n':
260
- if state.memories:
261
- state.preview_mode = not state.preview_mode
262
- elif c == 'a':
263
- approve_current()
264
- elif c == 'x':
265
- reject_current()
266
-
267
- return True
268
-
269
- def move_up():
270
- state.selected_idx = max(0, state.selected_idx - 1)
271
- if state.selected_idx < state.scroll_offset:
272
- state.scroll_offset = state.selected_idx
273
- state.status = ""
274
-
275
- def move_down():
276
- _, height = get_size()
277
- visible_height = height - 7
278
- state.selected_idx = min(len(state.memories) - 1, state.selected_idx + 1)
279
- if state.selected_idx >= state.scroll_offset + visible_height:
280
- state.scroll_offset = state.selected_idx - visible_height + 1
281
- state.status = ""
282
-
283
- def approve_current():
284
- if state.memories and state.selected_idx < len(state.memories):
285
- mem = state.memories[state.selected_idx]
286
- update_memory_status(mem['id'], 'approved')
287
-
288
- def reject_current():
289
- if state.memories and state.selected_idx < len(state.memories):
290
- mem = state.memories[state.selected_idx]
291
- update_memory_status(mem['id'], 'rejected')
292
-
293
- # ========== Main Loop ==========
294
- load_memories()
295
-
296
- fd = sys.stdin.fileno()
297
- old_settings = termios.tcgetattr(fd)
298
-
299
- try:
300
- tty.setcbreak(fd)
301
- sys.stdout.write('\033[?25l') # Hide cursor
302
-
303
- render_screen()
304
-
305
- while True:
306
- c = sys.stdin.read(1)
307
- if not handle_input(c):
308
- break
309
- render_screen()
310
-
311
- finally:
312
- termios.tcsetattr(fd, termios.TCSADRAIN, old_settings)
313
- sys.stdout.write('\033[?25h') # Show cursor
314
- sys.stdout.write('\033[2J\033[H') # Clear screen
315
- sys.stdout.flush()
316
-
317
- context['output'] = "Memory browser closed."