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,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."
@@ -1,122 +0,0 @@
1
- jinx_name: "vixynt"
2
- description: "Generates images from text descriptions or edits existing ones."
3
- inputs:
4
- - prompt
5
- - model: null
6
- - provider: null
7
- - output_name: null
8
- - attachments: null
9
- - n_images: null
10
- - height: null
11
- - width: null
12
- steps:
13
- - name: "generate_or_edit_image"
14
- engine: "python"
15
- code: |
16
- import os
17
- import base64
18
- from io import BytesIO
19
- from datetime import datetime
20
- from PIL import Image
21
- from npcpy.llm_funcs import gen_image
22
-
23
- # Extract inputs from context with proper type conversion
24
- image_prompt = str(context.get('prompt', '')).strip()
25
- output_name = context.get('output_name')
26
- attachments_str = context.get('attachments')
27
-
28
- # Handle integer inputs - they may come as strings or ints
29
- try:
30
- n_images = int(context.get('n_images', 1))
31
- except (ValueError, TypeError):
32
- n_images = 1
33
-
34
- try:
35
- height = int(context.get('height', 1024))
36
- except (ValueError, TypeError):
37
- height = 1024
38
-
39
- try:
40
- width = int(context.get('width', 1024))
41
- except (ValueError, TypeError):
42
- width = 1024
43
-
44
- # Get model and provider from context or environment
45
- model = context.get('model')
46
- provider = context.get('provider')
47
-
48
- # Fallback to environment variables
49
- if not model:
50
- model = os.getenv('NPCSH_IMAGE_GEN_MODEL')
51
- if not provider:
52
- provider = os.getenv('NPCSH_IMAGE_GEN_PROVIDER')
53
-
54
- # Parse attachments
55
- input_images = []
56
- if attachments_str and str(attachments_str).strip():
57
- input_images = [p.strip() for p in str(attachments_str).split(',')]
58
-
59
- output_messages = context.get('messages', [])
60
-
61
- if not image_prompt:
62
- output = "Error: No prompt provided for image generation."
63
- else:
64
- try:
65
- # Generate image(s)
66
- result = gen_image(
67
- prompt=image_prompt,
68
- model=model,
69
- provider=provider,
70
- npc=npc,
71
- height=height,
72
- width=width,
73
- n_images=n_images,
74
- input_images=input_images if input_images else None
75
- )
76
-
77
- # Ensure we have a list of images
78
- if not isinstance(result, list):
79
- images_list = [result] if result is not None else []
80
- else:
81
- images_list = result
82
-
83
- saved_files = []
84
-
85
- for i, image in enumerate(images_list):
86
- if image is None:
87
- continue
88
-
89
- # Determine output filename
90
- if output_name and str(output_name).strip():
91
- base_name, ext = os.path.splitext(os.path.expanduser(str(output_name)))
92
- if not ext:
93
- ext = ".png"
94
- current_output_file = f"{base_name}_{i}{ext}" if len(images_list) > 1 else f"{base_name}{ext}"
95
- else:
96
- os.makedirs(os.path.expanduser("~/.npcsh/images/"), exist_ok=True)
97
- current_output_file = (
98
- os.path.expanduser("~/.npcsh/images/")
99
- + f"image_{datetime.now().strftime('%Y%m%d_%H%M%S')}_{i}.png"
100
- )
101
-
102
- # Save image to file
103
- image.save(current_output_file)
104
- saved_files.append(current_output_file)
105
-
106
- if saved_files:
107
- output = f"Image(s) generated: {', '.join(saved_files)}"
108
- if input_images:
109
- output = f"Image(s) edited: {', '.join(saved_files)}"
110
- context['generated_images'] = saved_files
111
- else:
112
- output = "No images were generated."
113
-
114
- except Exception as e:
115
- import traceback
116
- traceback.print_exc()
117
- output = f"Error {'editing' if input_images else 'generating'} image: {str(e)}"
118
-
119
- context['output'] = output
120
- context['messages'] = output_messages
121
- context['model'] = model
122
- context['provider'] = provider