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,300 +0,0 @@
1
- jinx_name: config_tui
2
- description: Interactive TUI editor for npcsh configuration (~/.npcshrc)
3
- interactive: true
4
- inputs: []
5
- steps:
6
- - name: config_editor
7
- engine: python
8
- code: |
9
- import os
10
- import sys
11
- import tty
12
- import termios
13
- import select
14
- from pathlib import Path
15
-
16
- if not sys.stdin.isatty():
17
- context['output'] = "Config TUI requires an interactive terminal."
18
- return
19
-
20
- # ========== Config Items ==========
21
- CONFIG_ITEMS = [
22
- {'key': 'NPCSH_CHAT_MODEL', 'label': 'Chat Model', 'type': 'text', 'shortcut': 'model'},
23
- {'key': 'NPCSH_CHAT_PROVIDER', 'label': 'Chat Provider', 'type': 'text', 'shortcut': 'provider'},
24
- {'key': 'NPCSH_VISION_MODEL', 'label': 'Vision Model', 'type': 'text'},
25
- {'key': 'NPCSH_VISION_PROVIDER', 'label': 'Vision Provider', 'type': 'text'},
26
- {'key': 'NPCSH_EMBEDDING_MODEL', 'label': 'Embedding Model', 'type': 'text'},
27
- {'key': 'NPCSH_EMBEDDING_PROVIDER', 'label': 'Embedding Provider', 'type': 'text'},
28
- {'key': 'NPCSH_REASONING_MODEL', 'label': 'Reasoning Model', 'type': 'text'},
29
- {'key': 'NPCSH_REASONING_PROVIDER', 'label': 'Reasoning Provider', 'type': 'text'},
30
- {'key': 'NPCSH_DEFAULT_MODE', 'label': 'Default Mode', 'type': 'choice', 'choices': ['agent', 'chat', 'code']},
31
- {'key': 'NPCSH_STREAM_OUTPUT', 'label': 'Stream Output', 'type': 'toggle'},
32
- {'key': 'NPCSH_BUILD_KG', 'label': 'Build Knowledge Graph', 'type': 'toggle'},
33
- {'key': 'NPCSH_SEARCH_PROVIDER', 'label': 'Search Provider', 'type': 'choice', 'choices': ['duckduckgo', 'google', 'bing']},
34
- ]
35
-
36
- # ========== State ==========
37
- class ConfigState:
38
- def __init__(self):
39
- self.selected_idx = 0
40
- self.scroll_offset = 0
41
- self.editing = False
42
- self.edit_buffer = ""
43
- self.edit_cursor = 0
44
- self.values = {}
45
- self.modified = set()
46
- self.status = ""
47
-
48
- state = ConfigState()
49
-
50
- # ========== Helpers ==========
51
- def get_size():
52
- try:
53
- s = os.get_terminal_size()
54
- return s.columns, s.lines
55
- except:
56
- return 80, 24
57
-
58
- def load_values():
59
- """Load current values from environment and npcshrc."""
60
- for item in CONFIG_ITEMS:
61
- key = item['key']
62
- # First try environment
63
- value = os.environ.get(key, '')
64
- if not value:
65
- # Try reading from npcshrc
66
- npcshrc = Path.home() / '.npcshrc'
67
- if npcshrc.exists():
68
- with open(npcshrc) as f:
69
- for line in f:
70
- if line.strip().startswith(f'export {key}='):
71
- value = line.split('=', 1)[1].strip().strip('"').strip("'")
72
- break
73
- state.values[key] = value
74
-
75
- def save_values():
76
- """Save modified values to ~/.npcshrc."""
77
- from npcsh.config import set_npcsh_config_value
78
- for key in state.modified:
79
- set_npcsh_config_value(key, state.values[key])
80
- state.modified.clear()
81
- state.status = "Saved!"
82
-
83
- def format_value(item, value):
84
- """Format value for display."""
85
- if item['type'] == 'toggle':
86
- return '\033[32mON\033[0m' if value in ('1', 'true', 'True', True) else '\033[31mOFF\033[0m'
87
- elif not value:
88
- return '\033[90m(not set)\033[0m'
89
- return value
90
-
91
- # ========== Rendering ==========
92
- def render_screen():
93
- width, height = get_size()
94
- out = []
95
- out.append("\033[2J\033[H")
96
-
97
- # Header
98
- header = " NPCSH Configuration "
99
- out.append(f"\033[1;1H\033[44;37;1m{'=' * width}\033[0m")
100
- out.append(f"\033[1;{(width - len(header)) // 2}H\033[44;37;1m{header}\033[0m")
101
-
102
- # Config items
103
- visible_height = height - 6
104
- visible = CONFIG_ITEMS[state.scroll_offset:state.scroll_offset + visible_height]
105
-
106
- label_width = max(len(item['label']) for item in CONFIG_ITEMS) + 2
107
- value_width = width - label_width - 10
108
-
109
- for i, item in enumerate(visible):
110
- row = 3 + i
111
- idx = i + state.scroll_offset
112
- key = item['key']
113
- value = state.values.get(key, '')
114
- display_value = format_value(item, value)
115
-
116
- # Indicator for modified
117
- mod_indicator = '*' if key in state.modified else ' '
118
-
119
- if idx == state.selected_idx:
120
- if state.editing:
121
- # Show edit mode
122
- out.append(f"\033[{row};2H\033[7m{item['label']:<{label_width}}\033[0m")
123
- # Edit buffer with cursor
124
- cursor_pos = min(state.edit_cursor, len(state.edit_buffer))
125
- before = state.edit_buffer[:cursor_pos]
126
- after = state.edit_buffer[cursor_pos:]
127
- out.append(f"\033[{row};{label_width+4}H{before}\033[7m \033[0m{after}")
128
- else:
129
- out.append(f"\033[{row};2H\033[7m{mod_indicator}{item['label']:<{label_width}} {display_value[:value_width]}\033[0m")
130
- else:
131
- out.append(f"\033[{row};2H{mod_indicator}{item['label']:<{label_width}} {display_value[:value_width]}")
132
-
133
- # Type indicator
134
- type_hint = {'text': '[e]', 'toggle': '[t]', 'choice': '[c]'}.get(item['type'], '')
135
- out.append(f"\033[{row};{width-4}H\033[90m{type_hint}\033[0m")
136
-
137
- # Status line
138
- if state.status:
139
- out.append(f"\033[{height-2};2H\033[33m{state.status}\033[0m")
140
-
141
- # Footer
142
- if state.editing:
143
- footer = "[Enter] Save [Esc] Cancel"
144
- else:
145
- footer = "[j/k] Navigate [e] Edit [t] Toggle [s] Save All [q] Quit"
146
- out.append(f"\033[{height};1H\033[90m{footer[:width]}\033[0m")
147
-
148
- sys.stdout.write(''.join(out))
149
- sys.stdout.flush()
150
-
151
- # ========== Input Handling ==========
152
- def handle_input(c):
153
- if state.editing:
154
- return handle_edit_input(c)
155
-
156
- if c == 'q':
157
- if state.modified:
158
- state.status = "Unsaved changes! Press 's' to save or 'q' again to discard."
159
- return True
160
- return False
161
-
162
- if c == '\x1b': # Escape sequence
163
- if select.select([sys.stdin], [], [], 0.05)[0]:
164
- c2 = sys.stdin.read(1)
165
- if c2 == '[':
166
- c3 = sys.stdin.read(1)
167
- if c3 == 'A': # Up
168
- move_up()
169
- elif c3 == 'B': # Down
170
- move_down()
171
- return True
172
-
173
- if c == 'k':
174
- move_up()
175
- elif c == 'j':
176
- move_down()
177
- elif c == 'e' or c == '\r' or c == '\n':
178
- start_edit()
179
- elif c == 't':
180
- toggle_value()
181
- elif c == 'c':
182
- cycle_choice()
183
- elif c == 's':
184
- save_values()
185
-
186
- return True
187
-
188
- def handle_edit_input(c):
189
- if c == '\x1b': # Escape - cancel edit
190
- state.editing = False
191
- state.edit_buffer = ""
192
- state.status = "Edit cancelled"
193
- return True
194
-
195
- if c == '\r' or c == '\n': # Enter - save edit
196
- key = CONFIG_ITEMS[state.selected_idx]['key']
197
- state.values[key] = state.edit_buffer
198
- state.modified.add(key)
199
- state.editing = False
200
- state.edit_buffer = ""
201
- state.status = f"Changed {key}"
202
- return True
203
-
204
- if c == '\x7f' or c == '\x08': # Backspace
205
- if state.edit_cursor > 0:
206
- state.edit_buffer = state.edit_buffer[:state.edit_cursor-1] + state.edit_buffer[state.edit_cursor:]
207
- state.edit_cursor -= 1
208
- return True
209
-
210
- if c >= ' ' and c <= '~': # Printable
211
- state.edit_buffer = state.edit_buffer[:state.edit_cursor] + c + state.edit_buffer[state.edit_cursor:]
212
- state.edit_cursor += 1
213
- return True
214
-
215
- return True
216
-
217
- def move_up():
218
- state.selected_idx = max(0, state.selected_idx - 1)
219
- if state.selected_idx < state.scroll_offset:
220
- state.scroll_offset = state.selected_idx
221
- state.status = ""
222
-
223
- def move_down():
224
- _, height = get_size()
225
- visible_height = height - 6
226
- state.selected_idx = min(len(CONFIG_ITEMS) - 1, state.selected_idx + 1)
227
- if state.selected_idx >= state.scroll_offset + visible_height:
228
- state.scroll_offset = state.selected_idx - visible_height + 1
229
- state.status = ""
230
-
231
- def start_edit():
232
- item = CONFIG_ITEMS[state.selected_idx]
233
- if item['type'] == 'toggle':
234
- toggle_value()
235
- elif item['type'] == 'choice':
236
- cycle_choice()
237
- else:
238
- key = item['key']
239
- state.edit_buffer = state.values.get(key, '')
240
- state.edit_cursor = len(state.edit_buffer)
241
- state.editing = True
242
- state.status = "Editing... Enter to save, Esc to cancel"
243
-
244
- def toggle_value():
245
- item = CONFIG_ITEMS[state.selected_idx]
246
- if item['type'] != 'toggle':
247
- return
248
- key = item['key']
249
- current = state.values.get(key, '0')
250
- new_value = '0' if current in ('1', 'true', 'True') else '1'
251
- state.values[key] = new_value
252
- state.modified.add(key)
253
- state.status = f"Toggled {item['label']}"
254
-
255
- def cycle_choice():
256
- item = CONFIG_ITEMS[state.selected_idx]
257
- if item['type'] != 'choice':
258
- return
259
- key = item['key']
260
- choices = item.get('choices', [])
261
- if not choices:
262
- return
263
- current = state.values.get(key, '')
264
- try:
265
- idx = choices.index(current)
266
- next_idx = (idx + 1) % len(choices)
267
- except ValueError:
268
- next_idx = 0
269
- state.values[key] = choices[next_idx]
270
- state.modified.add(key)
271
- state.status = f"Changed to {choices[next_idx]}"
272
-
273
- # ========== Main Loop ==========
274
- load_values()
275
-
276
- fd = sys.stdin.fileno()
277
- old_settings = termios.tcgetattr(fd)
278
-
279
- try:
280
- tty.setcbreak(fd)
281
- sys.stdout.write('\033[?25l') # Hide cursor
282
-
283
- render_screen()
284
-
285
- while True:
286
- c = sys.stdin.read(1)
287
- if not handle_input(c):
288
- break
289
- render_screen()
290
-
291
- finally:
292
- termios.tcsetattr(fd, termios.TCSADRAIN, old_settings)
293
- sys.stdout.write('\033[?25h') # Show cursor
294
- sys.stdout.write('\033[2J\033[H') # Clear screen
295
- sys.stdout.flush()
296
-
297
- if state.modified:
298
- context['output'] = f"Exited with unsaved changes: {', '.join(state.modified)}"
299
- else:
300
- context['output'] = "Configuration editor closed."
@@ -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."