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