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