npcsh 1.1.18__py3-none-any.whl → 1.1.20__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 (165) hide show
  1. npcsh/_state.py +19 -7
  2. npcsh/benchmark/npcsh_agent.py +47 -16
  3. npcsh/config.py +1 -0
  4. npcsh/diff_viewer.py +452 -0
  5. npcsh/npc_team/jinxs/bin/config_tui.jinx +300 -0
  6. npcsh/npc_team/jinxs/bin/jinxs.jinx +407 -0
  7. npcsh/npc_team/jinxs/bin/kg.jinx +941 -0
  8. npcsh/npc_team/jinxs/bin/memories.jinx +317 -0
  9. npcsh/npc_team/jinxs/bin/models.jinx +343 -0
  10. npcsh/npc_team/jinxs/bin/nql.jinx +380 -50
  11. npcsh/npc_team/jinxs/bin/setup.jinx +241 -0
  12. npcsh/npc_team/jinxs/bin/sync.jinx +143 -150
  13. npcsh/npc_team/jinxs/bin/team.jinx +504 -0
  14. npcsh/npc_team/jinxs/incognide/add_tab.jinx +1 -1
  15. npcsh/npc_team/jinxs/incognide/close_pane.jinx +1 -1
  16. npcsh/npc_team/jinxs/incognide/close_tab.jinx +1 -1
  17. npcsh/npc_team/jinxs/incognide/confirm.jinx +1 -1
  18. npcsh/npc_team/jinxs/incognide/focus_pane.jinx +1 -1
  19. npcsh/npc_team/jinxs/incognide/list_panes.jinx +1 -1
  20. npcsh/npc_team/jinxs/incognide/navigate.jinx +1 -1
  21. npcsh/npc_team/jinxs/incognide/notify.jinx +1 -1
  22. npcsh/npc_team/jinxs/incognide/open_pane.jinx +1 -1
  23. npcsh/npc_team/jinxs/incognide/read_pane.jinx +1 -1
  24. npcsh/npc_team/jinxs/incognide/run_terminal.jinx +1 -1
  25. npcsh/npc_team/jinxs/incognide/send_message.jinx +1 -1
  26. npcsh/npc_team/jinxs/incognide/split_pane.jinx +1 -1
  27. npcsh/npc_team/jinxs/incognide/switch_npc.jinx +1 -1
  28. npcsh/npc_team/jinxs/incognide/switch_tab.jinx +1 -1
  29. npcsh/npc_team/jinxs/incognide/write_file.jinx +1 -1
  30. npcsh/npc_team/jinxs/incognide/zen_mode.jinx +1 -1
  31. npcsh/npc_team/jinxs/lib/core/search/db_search.jinx +1 -1
  32. npcsh/npc_team/jinxs/lib/core/search/file_search.jinx +1 -1
  33. npcsh/npc_team/jinxs/lib/core/search/kg_search.jinx +1 -1
  34. npcsh/npc_team/jinxs/lib/core/search/mem_search.jinx +1 -1
  35. npcsh/npc_team/jinxs/lib/core/search/web_search.jinx +1 -1
  36. npcsh/npc_team/jinxs/lib/research/paper_search.jinx +1 -1
  37. npcsh/npc_team/jinxs/lib/research/semantic_scholar.jinx +1 -1
  38. npcsh/npc_team/jinxs/modes/alicanto.jinx +1 -1
  39. npcsh/npc_team/jinxs/modes/arxiv.jinx +1 -1
  40. npcsh/npc_team/jinxs/modes/corca.jinx +1 -1
  41. npcsh/npc_team/jinxs/modes/guac.jinx +4 -6
  42. npcsh/npc_team/jinxs/modes/plonk.jinx +1 -1
  43. npcsh/npc_team/jinxs/modes/pti.jinx +1 -1
  44. npcsh/npc_team/jinxs/modes/reattach.jinx +1 -1
  45. npcsh/npc_team/jinxs/modes/spool.jinx +1 -1
  46. npcsh/npc_team/jinxs/modes/wander.jinx +1 -1
  47. npcsh/routes.py +8 -2
  48. {npcsh-1.1.18.data → npcsh-1.1.20.data}/data/npcsh/npc_team/add_tab.jinx +1 -1
  49. {npcsh-1.1.18.data → npcsh-1.1.20.data}/data/npcsh/npc_team/alicanto.jinx +1 -1
  50. {npcsh-1.1.18.data → npcsh-1.1.20.data}/data/npcsh/npc_team/arxiv.jinx +1 -1
  51. {npcsh-1.1.18.data → npcsh-1.1.20.data}/data/npcsh/npc_team/close_pane.jinx +1 -1
  52. {npcsh-1.1.18.data → npcsh-1.1.20.data}/data/npcsh/npc_team/close_tab.jinx +1 -1
  53. npcsh-1.1.20.data/data/npcsh/npc_team/config_tui.jinx +300 -0
  54. {npcsh-1.1.18.data → npcsh-1.1.20.data}/data/npcsh/npc_team/confirm.jinx +1 -1
  55. {npcsh-1.1.18.data → npcsh-1.1.20.data}/data/npcsh/npc_team/corca.jinx +1 -1
  56. {npcsh-1.1.18.data → npcsh-1.1.20.data}/data/npcsh/npc_team/db_search.jinx +1 -1
  57. {npcsh-1.1.18.data → npcsh-1.1.20.data}/data/npcsh/npc_team/file_search.jinx +1 -1
  58. {npcsh-1.1.18.data → npcsh-1.1.20.data}/data/npcsh/npc_team/focus_pane.jinx +1 -1
  59. {npcsh-1.1.18.data → npcsh-1.1.20.data}/data/npcsh/npc_team/guac.jinx +4 -6
  60. npcsh-1.1.20.data/data/npcsh/npc_team/jinxs.jinx +407 -0
  61. npcsh-1.1.20.data/data/npcsh/npc_team/kg.jinx +941 -0
  62. {npcsh-1.1.18.data → npcsh-1.1.20.data}/data/npcsh/npc_team/kg_search.jinx +1 -1
  63. {npcsh-1.1.18.data → npcsh-1.1.20.data}/data/npcsh/npc_team/list_panes.jinx +1 -1
  64. {npcsh-1.1.18.data → npcsh-1.1.20.data}/data/npcsh/npc_team/mem_search.jinx +1 -1
  65. npcsh-1.1.20.data/data/npcsh/npc_team/memories.jinx +317 -0
  66. npcsh-1.1.20.data/data/npcsh/npc_team/models.jinx +343 -0
  67. {npcsh-1.1.18.data → npcsh-1.1.20.data}/data/npcsh/npc_team/navigate.jinx +1 -1
  68. {npcsh-1.1.18.data → npcsh-1.1.20.data}/data/npcsh/npc_team/notify.jinx +1 -1
  69. npcsh-1.1.20.data/data/npcsh/npc_team/nql.jinx +471 -0
  70. {npcsh-1.1.18.data → npcsh-1.1.20.data}/data/npcsh/npc_team/open_pane.jinx +1 -1
  71. {npcsh-1.1.18.data → npcsh-1.1.20.data}/data/npcsh/npc_team/paper_search.jinx +1 -1
  72. {npcsh-1.1.18.data → npcsh-1.1.20.data}/data/npcsh/npc_team/plonk.jinx +1 -1
  73. {npcsh-1.1.18.data → npcsh-1.1.20.data}/data/npcsh/npc_team/pti.jinx +1 -1
  74. {npcsh-1.1.18.data → npcsh-1.1.20.data}/data/npcsh/npc_team/read_pane.jinx +1 -1
  75. {npcsh-1.1.18.data → npcsh-1.1.20.data}/data/npcsh/npc_team/reattach.jinx +1 -1
  76. {npcsh-1.1.18.data → npcsh-1.1.20.data}/data/npcsh/npc_team/run_terminal.jinx +1 -1
  77. {npcsh-1.1.18.data → npcsh-1.1.20.data}/data/npcsh/npc_team/semantic_scholar.jinx +1 -1
  78. {npcsh-1.1.18.data → npcsh-1.1.20.data}/data/npcsh/npc_team/send_message.jinx +1 -1
  79. npcsh-1.1.20.data/data/npcsh/npc_team/setup.jinx +241 -0
  80. {npcsh-1.1.18.data → npcsh-1.1.20.data}/data/npcsh/npc_team/split_pane.jinx +1 -1
  81. {npcsh-1.1.18.data → npcsh-1.1.20.data}/data/npcsh/npc_team/spool.jinx +1 -1
  82. {npcsh-1.1.18.data → npcsh-1.1.20.data}/data/npcsh/npc_team/switch_npc.jinx +1 -1
  83. {npcsh-1.1.18.data → npcsh-1.1.20.data}/data/npcsh/npc_team/switch_tab.jinx +1 -1
  84. npcsh-1.1.20.data/data/npcsh/npc_team/sync.jinx +223 -0
  85. npcsh-1.1.20.data/data/npcsh/npc_team/team.jinx +504 -0
  86. {npcsh-1.1.18.data → npcsh-1.1.20.data}/data/npcsh/npc_team/wander.jinx +1 -1
  87. {npcsh-1.1.18.data → npcsh-1.1.20.data}/data/npcsh/npc_team/web_search.jinx +1 -1
  88. {npcsh-1.1.18.data → npcsh-1.1.20.data}/data/npcsh/npc_team/write_file.jinx +1 -1
  89. {npcsh-1.1.18.data → npcsh-1.1.20.data}/data/npcsh/npc_team/zen_mode.jinx +1 -1
  90. {npcsh-1.1.18.dist-info → npcsh-1.1.20.dist-info}/METADATA +21 -14
  91. npcsh-1.1.20.dist-info/RECORD +248 -0
  92. {npcsh-1.1.18.dist-info → npcsh-1.1.20.dist-info}/entry_points.txt +7 -0
  93. npcsh/npc_team/jinxs/lib/utils/jinxs.jinx +0 -331
  94. npcsh-1.1.18.data/data/npcsh/npc_team/jinxs.jinx +0 -331
  95. npcsh-1.1.18.data/data/npcsh/npc_team/nql.jinx +0 -141
  96. npcsh-1.1.18.data/data/npcsh/npc_team/sync.jinx +0 -230
  97. npcsh-1.1.18.dist-info/RECORD +0 -235
  98. {npcsh-1.1.18.data → npcsh-1.1.20.data}/data/npcsh/npc_team/alicanto.npc +0 -0
  99. {npcsh-1.1.18.data → npcsh-1.1.20.data}/data/npcsh/npc_team/alicanto.png +0 -0
  100. {npcsh-1.1.18.data → npcsh-1.1.20.data}/data/npcsh/npc_team/benchmark.jinx +0 -0
  101. {npcsh-1.1.18.data → npcsh-1.1.20.data}/data/npcsh/npc_team/browser_action.jinx +0 -0
  102. {npcsh-1.1.18.data → npcsh-1.1.20.data}/data/npcsh/npc_team/browser_screenshot.jinx +0 -0
  103. {npcsh-1.1.18.data → npcsh-1.1.20.data}/data/npcsh/npc_team/build.jinx +0 -0
  104. {npcsh-1.1.18.data → npcsh-1.1.20.data}/data/npcsh/npc_team/chat.jinx +0 -0
  105. {npcsh-1.1.18.data → npcsh-1.1.20.data}/data/npcsh/npc_team/click.jinx +0 -0
  106. {npcsh-1.1.18.data → npcsh-1.1.20.data}/data/npcsh/npc_team/close_browser.jinx +0 -0
  107. {npcsh-1.1.18.data → npcsh-1.1.20.data}/data/npcsh/npc_team/cmd.jinx +0 -0
  108. {npcsh-1.1.18.data → npcsh-1.1.20.data}/data/npcsh/npc_team/compile.jinx +0 -0
  109. {npcsh-1.1.18.data → npcsh-1.1.20.data}/data/npcsh/npc_team/compress.jinx +0 -0
  110. {npcsh-1.1.18.data → npcsh-1.1.20.data}/data/npcsh/npc_team/convene.jinx +0 -0
  111. {npcsh-1.1.18.data → npcsh-1.1.20.data}/data/npcsh/npc_team/corca.npc +0 -0
  112. {npcsh-1.1.18.data → npcsh-1.1.20.data}/data/npcsh/npc_team/corca.png +0 -0
  113. {npcsh-1.1.18.data → npcsh-1.1.20.data}/data/npcsh/npc_team/corca_example.png +0 -0
  114. {npcsh-1.1.18.data → npcsh-1.1.20.data}/data/npcsh/npc_team/delegate.jinx +0 -0
  115. {npcsh-1.1.18.data → npcsh-1.1.20.data}/data/npcsh/npc_team/edit_file.jinx +0 -0
  116. {npcsh-1.1.18.data → npcsh-1.1.20.data}/data/npcsh/npc_team/frederic.npc +0 -0
  117. {npcsh-1.1.18.data → npcsh-1.1.20.data}/data/npcsh/npc_team/frederic4.png +0 -0
  118. {npcsh-1.1.18.data → npcsh-1.1.20.data}/data/npcsh/npc_team/guac.npc +0 -0
  119. {npcsh-1.1.18.data → npcsh-1.1.20.data}/data/npcsh/npc_team/guac.png +0 -0
  120. {npcsh-1.1.18.data → npcsh-1.1.20.data}/data/npcsh/npc_team/help.jinx +0 -0
  121. {npcsh-1.1.18.data → npcsh-1.1.20.data}/data/npcsh/npc_team/incognide.jinx +0 -0
  122. {npcsh-1.1.18.data → npcsh-1.1.20.data}/data/npcsh/npc_team/init.jinx +0 -0
  123. {npcsh-1.1.18.data → npcsh-1.1.20.data}/data/npcsh/npc_team/kadiefa.npc +0 -0
  124. {npcsh-1.1.18.data → npcsh-1.1.20.data}/data/npcsh/npc_team/kadiefa.png +0 -0
  125. {npcsh-1.1.18.data → npcsh-1.1.20.data}/data/npcsh/npc_team/key_press.jinx +0 -0
  126. {npcsh-1.1.18.data → npcsh-1.1.20.data}/data/npcsh/npc_team/launch_app.jinx +0 -0
  127. {npcsh-1.1.18.data → npcsh-1.1.20.data}/data/npcsh/npc_team/load_file.jinx +0 -0
  128. {npcsh-1.1.18.data → npcsh-1.1.20.data}/data/npcsh/npc_team/mem_review.jinx +0 -0
  129. {npcsh-1.1.18.data → npcsh-1.1.20.data}/data/npcsh/npc_team/npcsh.ctx +0 -0
  130. {npcsh-1.1.18.data → npcsh-1.1.20.data}/data/npcsh/npc_team/npcsh_sibiji.png +0 -0
  131. {npcsh-1.1.18.data → npcsh-1.1.20.data}/data/npcsh/npc_team/open_browser.jinx +0 -0
  132. {npcsh-1.1.18.data → npcsh-1.1.20.data}/data/npcsh/npc_team/ots.jinx +0 -0
  133. {npcsh-1.1.18.data → npcsh-1.1.20.data}/data/npcsh/npc_team/paste.jinx +0 -0
  134. {npcsh-1.1.18.data → npcsh-1.1.20.data}/data/npcsh/npc_team/plonk.npc +0 -0
  135. {npcsh-1.1.18.data → npcsh-1.1.20.data}/data/npcsh/npc_team/plonk.png +0 -0
  136. {npcsh-1.1.18.data → npcsh-1.1.20.data}/data/npcsh/npc_team/plonkjr.npc +0 -0
  137. {npcsh-1.1.18.data → npcsh-1.1.20.data}/data/npcsh/npc_team/plonkjr.png +0 -0
  138. {npcsh-1.1.18.data → npcsh-1.1.20.data}/data/npcsh/npc_team/python.jinx +0 -0
  139. {npcsh-1.1.18.data → npcsh-1.1.20.data}/data/npcsh/npc_team/roll.jinx +0 -0
  140. {npcsh-1.1.18.data → npcsh-1.1.20.data}/data/npcsh/npc_team/sample.jinx +0 -0
  141. {npcsh-1.1.18.data → npcsh-1.1.20.data}/data/npcsh/npc_team/screenshot.jinx +0 -0
  142. {npcsh-1.1.18.data → npcsh-1.1.20.data}/data/npcsh/npc_team/search.jinx +0 -0
  143. {npcsh-1.1.18.data → npcsh-1.1.20.data}/data/npcsh/npc_team/serve.jinx +0 -0
  144. {npcsh-1.1.18.data → npcsh-1.1.20.data}/data/npcsh/npc_team/set.jinx +0 -0
  145. {npcsh-1.1.18.data → npcsh-1.1.20.data}/data/npcsh/npc_team/sh.jinx +0 -0
  146. {npcsh-1.1.18.data → npcsh-1.1.20.data}/data/npcsh/npc_team/shh.jinx +0 -0
  147. {npcsh-1.1.18.data → npcsh-1.1.20.data}/data/npcsh/npc_team/sibiji.npc +0 -0
  148. {npcsh-1.1.18.data → npcsh-1.1.20.data}/data/npcsh/npc_team/sibiji.png +0 -0
  149. {npcsh-1.1.18.data → npcsh-1.1.20.data}/data/npcsh/npc_team/sleep.jinx +0 -0
  150. {npcsh-1.1.18.data → npcsh-1.1.20.data}/data/npcsh/npc_team/spool.png +0 -0
  151. {npcsh-1.1.18.data → npcsh-1.1.20.data}/data/npcsh/npc_team/sql.jinx +0 -0
  152. {npcsh-1.1.18.data → npcsh-1.1.20.data}/data/npcsh/npc_team/switch.jinx +0 -0
  153. {npcsh-1.1.18.data → npcsh-1.1.20.data}/data/npcsh/npc_team/switches.jinx +0 -0
  154. {npcsh-1.1.18.data → npcsh-1.1.20.data}/data/npcsh/npc_team/teamviz.jinx +0 -0
  155. {npcsh-1.1.18.data → npcsh-1.1.20.data}/data/npcsh/npc_team/trigger.jinx +0 -0
  156. {npcsh-1.1.18.data → npcsh-1.1.20.data}/data/npcsh/npc_team/type_text.jinx +0 -0
  157. {npcsh-1.1.18.data → npcsh-1.1.20.data}/data/npcsh/npc_team/usage.jinx +0 -0
  158. {npcsh-1.1.18.data → npcsh-1.1.20.data}/data/npcsh/npc_team/verbose.jinx +0 -0
  159. {npcsh-1.1.18.data → npcsh-1.1.20.data}/data/npcsh/npc_team/vixynt.jinx +0 -0
  160. {npcsh-1.1.18.data → npcsh-1.1.20.data}/data/npcsh/npc_team/wait.jinx +0 -0
  161. {npcsh-1.1.18.data → npcsh-1.1.20.data}/data/npcsh/npc_team/yap.jinx +0 -0
  162. {npcsh-1.1.18.data → npcsh-1.1.20.data}/data/npcsh/npc_team/yap.png +0 -0
  163. {npcsh-1.1.18.dist-info → npcsh-1.1.20.dist-info}/WHEEL +0 -0
  164. {npcsh-1.1.18.dist-info → npcsh-1.1.20.dist-info}/licenses/LICENSE +0 -0
  165. {npcsh-1.1.18.dist-info → npcsh-1.1.20.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,504 @@
1
+ jinx_name: team
2
+ description: Interactive TUI for managing team context, NPCs, and jinxs
3
+ interactive: true
4
+ inputs: []
5
+ steps:
6
+ - name: team_manager
7
+ engine: python
8
+ code: |
9
+ import os
10
+ import sys
11
+ import tty
12
+ import termios
13
+ import select
14
+ import yaml
15
+ from pathlib import Path
16
+
17
+ if not sys.stdin.isatty():
18
+ context['output'] = "Team TUI requires an interactive terminal."
19
+
20
+ elif not state or not state.team:
21
+ context['output'] = "No team loaded."
22
+
23
+ else:
24
+ TEAM_DIR = Path(state.team.team_path)
25
+
26
+ # Find the actual .ctx file (first *.ctx found, same as Team class)
27
+ CTX_PATH = None
28
+ for _f in sorted(TEAM_DIR.glob('*.ctx')):
29
+ CTX_PATH = _f
30
+ break
31
+
32
+ class TUIState:
33
+ def __init__(self):
34
+ self.tab = 0
35
+ self.tabs = ['Context', 'NPCs', 'Jinxs']
36
+ self.sel = 0
37
+ self.scroll = 0
38
+ self.editing = False
39
+ self.edit_field = None
40
+ self.edit_buf = ""
41
+ self.edit_cursor = 0
42
+ self.status = ""
43
+ self.team_ctx = {}
44
+ self.npcs = []
45
+ self.npc_data = {}
46
+ self.jinxs = []
47
+ self.jinx_data = {}
48
+ self.detail = False
49
+ self.detail_idx = 0
50
+ self.detail_scroll = 0
51
+
52
+ ui = TUIState()
53
+
54
+ def term_size():
55
+ try:
56
+ s = os.get_terminal_size()
57
+ return s.columns, s.lines
58
+ except:
59
+ return 80, 24
60
+
61
+ def load_data():
62
+ if CTX_PATH and CTX_PATH.exists():
63
+ with open(CTX_PATH) as f:
64
+ ui.team_ctx = yaml.safe_load(f) or {}
65
+ ui.npcs = sorted([f.stem for f in TEAM_DIR.glob('*.npc')])
66
+ ui.npc_data = {}
67
+ for name in ui.npcs:
68
+ try:
69
+ with open(TEAM_DIR / f"{name}.npc") as f:
70
+ ui.npc_data[name] = yaml.safe_load(f) or {}
71
+ except:
72
+ ui.npc_data[name] = {}
73
+ ui.jinxs = []
74
+ ui.jinx_data = {}
75
+ jdir = TEAM_DIR / 'jinxs'
76
+ if jdir.exists():
77
+ for sub in sorted(jdir.iterdir()):
78
+ if sub.is_dir():
79
+ for jf in sorted(sub.glob('*.jinx')):
80
+ ui.jinxs.append((sub.name, jf.stem))
81
+ try:
82
+ with open(jf) as f:
83
+ ui.jinx_data[f"{sub.name}/{jf.stem}"] = yaml.safe_load(f) or {}
84
+ except:
85
+ ui.jinx_data[f"{sub.name}/{jf.stem}"] = {}
86
+
87
+ def ctx_fields():
88
+ return list(ui.team_ctx.keys()) if ui.team_ctx else []
89
+
90
+ def fmt_val(raw, maxw):
91
+ if isinstance(raw, list):
92
+ v = ', '.join(str(x) for x in raw)
93
+ elif isinstance(raw, bool):
94
+ v = 'true' if raw else 'false'
95
+ else:
96
+ v = str(raw) if raw else ''
97
+ if len(v) > maxw:
98
+ v = v[:maxw-3] + '...'
99
+ return v
100
+
101
+ def save_ctx():
102
+ p = CTX_PATH or (TEAM_DIR / 'team.ctx')
103
+ with open(p, 'w') as f:
104
+ yaml.dump(ui.team_ctx, f, default_flow_style=False)
105
+ ui.status = f"Saved {p.name}"
106
+
107
+ def save_npc(name):
108
+ with open(TEAM_DIR / f"{name}.npc", 'w') as f:
109
+ yaml.dump(ui.npc_data[name], f, default_flow_style=False)
110
+ ui.status = f"Saved {name}.npc"
111
+
112
+ def save_jinx(key):
113
+ folder, name = key.split('/', 1)
114
+ p = TEAM_DIR / 'jinxs' / folder / f"{name}.jinx"
115
+ with open(p, 'w') as f:
116
+ yaml.dump(ui.jinx_data[key], f, default_flow_style=False)
117
+ ui.status = f"Saved {key}.jinx"
118
+
119
+ # ── rendering ──────────────────────────────────────────
120
+ def w(row, col, text):
121
+ """Write text at position, clearing the line first."""
122
+ return f"\033[{row};1H\033[K\033[{row};{col}H{text}"
123
+
124
+ def wline(row, text):
125
+ """Write full line at row, clear to EOL."""
126
+ return f"\033[{row};1H\033[K{text}"
127
+
128
+ def render():
129
+ W, H = term_size()
130
+ out = []
131
+
132
+ # Home cursor (no full-screen clear)
133
+ out.append("\033[H")
134
+
135
+ # ── header ──
136
+ team_name = state.team.name or 'team'
137
+ hdr = f" {team_name} "
138
+ pad = '=' * W
139
+ out.append(wline(1, f"\033[44;37;1m{pad}\033[0m"))
140
+ out.append(f"\033[1;{max(1,(W - len(hdr)) // 2)}H\033[44;37;1m{hdr}\033[0m")
141
+
142
+ # ── tabs ──
143
+ tb = ""
144
+ for i, t in enumerate(ui.tabs):
145
+ if i == ui.tab:
146
+ tb += f"\033[7;1m [{t}] \033[0m"
147
+ else:
148
+ tb += f" {t} "
149
+ out.append(wline(2, f" {tb}"))
150
+ out.append(wline(3, f"\033[90m{'─' * W}\033[0m"))
151
+
152
+ # ── body ──
153
+ body_start = 4
154
+ body_end = H - 3 # leave room for separator, status, footer
155
+ body_h = body_end - body_start + 1
156
+
157
+ if ui.detail:
158
+ render_detail(out, W, body_start, body_h)
159
+ elif ui.tab == 0:
160
+ render_ctx(out, W, body_start, body_h)
161
+ elif ui.tab == 1:
162
+ render_npcs(out, W, body_start, body_h)
163
+ else:
164
+ render_jinxs(out, W, body_start, body_h)
165
+
166
+ # clear any leftover body lines
167
+ # (handled per-line in each render_ function)
168
+
169
+ # ── separator ──
170
+ out.append(wline(H - 2, f"\033[90m{'─' * W}\033[0m"))
171
+
172
+ # ── status ──
173
+ if ui.status:
174
+ out.append(wline(H - 1, f" \033[33m{ui.status[:W-2]}\033[0m"))
175
+ else:
176
+ out.append(wline(H - 1, ""))
177
+
178
+ # ── footer ──
179
+ if ui.editing:
180
+ foot = " [Enter] Save [Esc] Cancel [Backspace] Delete"
181
+ elif ui.detail:
182
+ foot = " [j/k] Navigate [e/Enter] Edit [s] Save [q/Esc] Back"
183
+ else:
184
+ foot = " [Tab] Switch [j/k] Nav [Enter] Open [s] Save [q] Quit"
185
+ out.append(wline(H, f"\033[44;37m{foot[:W].ljust(W)}\033[0m"))
186
+
187
+ sys.stdout.write(''.join(out))
188
+ sys.stdout.flush()
189
+
190
+ def render_ctx(out, W, start, body_h):
191
+ fields = ctx_fields()
192
+ for r in range(body_h):
193
+ row = start + r
194
+ i = r
195
+ if i >= len(fields):
196
+ out.append(wline(row, ""))
197
+ continue
198
+ key = fields[i]
199
+ raw = ui.team_ctx.get(key, '')
200
+ val = fmt_val(raw, W - 20)
201
+
202
+ if i == ui.sel:
203
+ if ui.editing:
204
+ out.append(wline(row, f" \033[1m{key}:\033[0m \033[44;37m{ui.edit_buf}\033[0m\033[K"))
205
+ else:
206
+ line = f" {key}: {val}"
207
+ out.append(wline(row, f"\033[7m{line[:W].ljust(W)}\033[0m"))
208
+ else:
209
+ if val:
210
+ out.append(wline(row, f" {key}: \033[32m{val}\033[0m"))
211
+ else:
212
+ out.append(wline(row, f" {key}: \033[90m(empty)\033[0m"))
213
+ if not fields:
214
+ out.append(wline(start, " \033[90mNo context file found.\033[0m"))
215
+
216
+ def render_npcs(out, W, start, body_h):
217
+ vis = ui.npcs[ui.scroll:ui.scroll + body_h]
218
+ for r in range(body_h):
219
+ row = start + r
220
+ i = r + ui.scroll
221
+ if r >= len(vis):
222
+ out.append(wline(row, ""))
223
+ continue
224
+ name = vis[r]
225
+ ndata = ui.npc_data.get(name, {})
226
+ directive = str(ndata.get('primary_directive', ''))[:W-30]
227
+ directive = directive.replace('\n', ' ')
228
+ if i == ui.sel:
229
+ line = f" > {name:<16} {directive}"
230
+ out.append(wline(row, f"\033[7m{line[:W].ljust(W)}\033[0m"))
231
+ else:
232
+ out.append(wline(row, f" {name:<16} \033[90m{directive}\033[0m"))
233
+ if not ui.npcs:
234
+ out.append(wline(start, " \033[90mNo NPCs found.\033[0m"))
235
+
236
+ def render_jinxs(out, W, start, body_h):
237
+ vis = ui.jinxs[ui.scroll:ui.scroll + body_h]
238
+ for r in range(body_h):
239
+ row = start + r
240
+ i = r + ui.scroll
241
+ if r >= len(vis):
242
+ out.append(wline(row, ""))
243
+ continue
244
+ folder, name = vis[r]
245
+ label = f"{folder}/{name}"
246
+ jdata = ui.jinx_data.get(label, {})
247
+ desc = str(jdata.get('description', ''))[:W-30]
248
+ if i == ui.sel:
249
+ line = f" > {label:<24} {desc}"
250
+ out.append(wline(row, f"\033[7m{line[:W].ljust(W)}\033[0m"))
251
+ else:
252
+ out.append(wline(row, f" {label:<24} \033[90m{desc}\033[0m"))
253
+ if not ui.jinxs:
254
+ out.append(wline(start, " \033[90mNo jinxs found.\033[0m"))
255
+
256
+ def render_detail(out, W, start, body_h):
257
+ if ui.tab == 1 and ui.npcs:
258
+ name = ui.npcs[ui.sel]
259
+ data = ui.npc_data.get(name, {})
260
+ title = f"NPC: {name}"
261
+ elif ui.tab == 2 and ui.jinxs:
262
+ folder, name = ui.jinxs[ui.sel]
263
+ key = f"{folder}/{name}"
264
+ data = ui.jinx_data.get(key, {})
265
+ title = f"Jinx: {key}"
266
+ else:
267
+ for r in range(body_h):
268
+ out.append(wline(start + r, ""))
269
+ return
270
+
271
+ out.append(wline(start, f" \033[1m{title}\033[0m"))
272
+ out.append(wline(start + 1, f" \033[90m{'─' * (W - 4)}\033[0m"))
273
+
274
+ fields = list(data.keys()) if data else []
275
+ vis = fields[ui.detail_scroll:ui.detail_scroll + body_h - 2]
276
+ for r in range(body_h - 2):
277
+ row = start + 2 + r
278
+ fi = r + ui.detail_scroll
279
+ if r >= len(vis):
280
+ out.append(wline(row, ""))
281
+ continue
282
+ fkey = vis[r]
283
+ fval = fmt_val(data.get(fkey, ''), W - 25)
284
+ if fi == ui.detail_idx:
285
+ if ui.editing:
286
+ out.append(wline(row, f" \033[1m{fkey}:\033[0m \033[44;37m{ui.edit_buf}\033[0m"))
287
+ else:
288
+ line = f" {fkey}: {fval}"
289
+ out.append(wline(row, f"\033[7m{line[:W].ljust(W)}\033[0m"))
290
+ else:
291
+ out.append(wline(row, f" {fkey}: \033[36m{fval}\033[0m"))
292
+
293
+ # ── input handling ─────────────────────────────────────
294
+ def handle(c):
295
+ if ui.editing:
296
+ return handle_edit(c)
297
+ if c == '\x1b':
298
+ return handle_esc()
299
+ if c == 'q':
300
+ if ui.detail:
301
+ ui.detail = False
302
+ ui.status = ""
303
+ else:
304
+ return False
305
+ elif c == '\t':
306
+ ui.tab = (ui.tab + 1) % 3
307
+ ui.sel = 0
308
+ ui.scroll = 0
309
+ ui.detail = False
310
+ ui.status = ""
311
+ elif c == 'k':
312
+ nav_up()
313
+ elif c == 'j':
314
+ nav_down()
315
+ elif c in ('\r', '\n', 'e'):
316
+ do_enter()
317
+ elif c == 's':
318
+ do_save()
319
+ return True
320
+
321
+ def handle_esc():
322
+ if select.select([sys.stdin], [], [], 0.05)[0]:
323
+ c2 = sys.stdin.read(1)
324
+ if c2 == '[':
325
+ c3 = sys.stdin.read(1)
326
+ if c3 == 'A':
327
+ nav_up()
328
+ elif c3 == 'B':
329
+ nav_down()
330
+ # consume any other escape sequence
331
+ else:
332
+ # bare Esc
333
+ if ui.detail:
334
+ ui.detail = False
335
+ ui.status = ""
336
+ return True
337
+
338
+ def handle_edit(c):
339
+ if c == '\x1b':
340
+ # Check if arrow key or bare esc
341
+ if select.select([sys.stdin], [], [], 0.05)[0]:
342
+ c2 = sys.stdin.read(1)
343
+ if c2 == '[':
344
+ sys.stdin.read(1) # consume arrow char
345
+ else:
346
+ ui.editing = False
347
+ ui.edit_buf = ""
348
+ ui.status = "Cancelled"
349
+ elif c in ('\r', '\n'):
350
+ finish_edit()
351
+ elif c in ('\x7f', '\x08'):
352
+ ui.edit_buf = ui.edit_buf[:-1]
353
+ elif c == '\x15': # Ctrl+U clear line
354
+ ui.edit_buf = ""
355
+ elif 32 <= ord(c) <= 126:
356
+ ui.edit_buf += c
357
+ return True
358
+
359
+ def nav_up():
360
+ if ui.detail:
361
+ ui.detail_idx = max(0, ui.detail_idx - 1)
362
+ if ui.detail_idx < ui.detail_scroll:
363
+ ui.detail_scroll = ui.detail_idx
364
+ else:
365
+ ui.sel = max(0, ui.sel - 1)
366
+ if ui.sel < ui.scroll:
367
+ ui.scroll = ui.sel
368
+ ui.status = ""
369
+
370
+ def nav_down():
371
+ _, H = term_size()
372
+ body_h = H - 6
373
+ if ui.detail:
374
+ if ui.tab == 1 and ui.npcs:
375
+ n = ui.npcs[ui.sel]
376
+ mx = max(0, len(ui.npc_data.get(n, {})) - 1)
377
+ elif ui.tab == 2 and ui.jinxs:
378
+ f, n = ui.jinxs[ui.sel]
379
+ mx = max(0, len(ui.jinx_data.get(f"{f}/{n}", {})) - 1)
380
+ else:
381
+ mx = 0
382
+ ui.detail_idx = min(mx, ui.detail_idx + 1)
383
+ if ui.detail_idx >= ui.detail_scroll + body_h - 2:
384
+ ui.detail_scroll = ui.detail_idx - body_h + 3
385
+ else:
386
+ if ui.tab == 0:
387
+ mx = max(0, len(ctx_fields()) - 1)
388
+ elif ui.tab == 1:
389
+ mx = max(0, len(ui.npcs) - 1)
390
+ else:
391
+ mx = max(0, len(ui.jinxs) - 1)
392
+ ui.sel = min(mx, ui.sel + 1)
393
+ if ui.sel >= ui.scroll + body_h:
394
+ ui.scroll = ui.sel - body_h + 1
395
+ ui.status = ""
396
+
397
+ def do_enter():
398
+ if ui.tab == 0:
399
+ start_ctx_edit()
400
+ elif ui.tab == 1 and ui.npcs:
401
+ if not ui.detail:
402
+ ui.detail = True
403
+ ui.detail_idx = 0
404
+ ui.detail_scroll = 0
405
+ else:
406
+ start_detail_edit()
407
+ elif ui.tab == 2 and ui.jinxs:
408
+ if not ui.detail:
409
+ ui.detail = True
410
+ ui.detail_idx = 0
411
+ ui.detail_scroll = 0
412
+ else:
413
+ start_detail_edit()
414
+
415
+ def start_ctx_edit():
416
+ fields = ctx_fields()
417
+ if ui.sel < len(fields):
418
+ ui.edit_field = fields[ui.sel]
419
+ raw = ui.team_ctx.get(ui.edit_field, '')
420
+ ui.edit_buf = fmt_val(raw, 9999) # full value, no truncation
421
+ ui.editing = True
422
+
423
+ def start_detail_edit():
424
+ if ui.tab == 1:
425
+ name = ui.npcs[ui.sel]
426
+ data = ui.npc_data.get(name, {})
427
+ elif ui.tab == 2:
428
+ f, n = ui.jinxs[ui.sel]
429
+ data = ui.jinx_data.get(f"{f}/{n}", {})
430
+ else:
431
+ return
432
+ fields = list(data.keys())
433
+ if ui.detail_idx < len(fields):
434
+ ui.edit_field = fields[ui.detail_idx]
435
+ raw = data.get(ui.edit_field, '')
436
+ if isinstance(raw, list):
437
+ ui.edit_buf = ', '.join(str(x) for x in raw)
438
+ else:
439
+ ui.edit_buf = str(raw) if raw else ''
440
+ ui.editing = True
441
+
442
+ def finish_edit():
443
+ if ui.tab == 0:
444
+ old = ui.team_ctx.get(ui.edit_field)
445
+ if isinstance(old, list):
446
+ ui.team_ctx[ui.edit_field] = [x.strip() for x in ui.edit_buf.split(',') if x.strip()]
447
+ elif isinstance(old, bool):
448
+ ui.team_ctx[ui.edit_field] = ui.edit_buf.lower() in ('true', '1', 'yes')
449
+ else:
450
+ ui.team_ctx[ui.edit_field] = ui.edit_buf
451
+ elif ui.tab == 1 and ui.detail:
452
+ name = ui.npcs[ui.sel]
453
+ old = ui.npc_data[name].get(ui.edit_field)
454
+ if isinstance(old, list):
455
+ ui.npc_data[name][ui.edit_field] = [x.strip() for x in ui.edit_buf.split(',') if x.strip()]
456
+ elif isinstance(old, bool):
457
+ ui.npc_data[name][ui.edit_field] = ui.edit_buf.lower() in ('true', '1', 'yes')
458
+ else:
459
+ ui.npc_data[name][ui.edit_field] = ui.edit_buf
460
+ elif ui.tab == 2 and ui.detail:
461
+ f, n = ui.jinxs[ui.sel]
462
+ key = f"{f}/{n}"
463
+ old = ui.jinx_data[key].get(ui.edit_field)
464
+ if isinstance(old, list):
465
+ ui.jinx_data[key][ui.edit_field] = [x.strip() for x in ui.edit_buf.split(',') if x.strip()]
466
+ elif isinstance(old, bool):
467
+ ui.jinx_data[key][ui.edit_field] = ui.edit_buf.lower() in ('true', '1', 'yes')
468
+ else:
469
+ ui.jinx_data[key][ui.edit_field] = ui.edit_buf
470
+ ui.editing = False
471
+ ui.edit_buf = ""
472
+ ui.status = f"Set {ui.edit_field}"
473
+
474
+ def do_save():
475
+ if ui.tab == 0:
476
+ save_ctx()
477
+ elif ui.tab == 1 and ui.detail and ui.npcs:
478
+ save_npc(ui.npcs[ui.sel])
479
+ elif ui.tab == 2 and ui.detail and ui.jinxs:
480
+ f, n = ui.jinxs[ui.sel]
481
+ save_jinx(f"{f}/{n}")
482
+
483
+ # ── main loop ──────────────────────────────────────────
484
+ load_data()
485
+ fd = sys.stdin.fileno()
486
+ old_attrs = termios.tcgetattr(fd)
487
+
488
+ try:
489
+ tty.setcbreak(fd)
490
+ sys.stdout.write('\033[?25l') # hide cursor
491
+ sys.stdout.write('\033[2J\033[H') # initial full clear
492
+ sys.stdout.flush()
493
+ render()
494
+ while True:
495
+ c = sys.stdin.read(1)
496
+ if not handle(c):
497
+ break
498
+ render()
499
+ finally:
500
+ termios.tcsetattr(fd, termios.TCSADRAIN, old_attrs)
501
+ sys.stdout.write('\033[?25h\033[2J\033[H')
502
+ sys.stdout.flush()
503
+
504
+ context['output'] = "Team TUI closed."
@@ -217,7 +217,7 @@ steps:
217
217
  panel_tabs = ""
218
218
  for i, name in enumerate(["Output", "Streams", "Starred"]):
219
219
  if i == state.current_panel:
220
- panel_tabs += f"\033[47;30m {name} \033[0m "
220
+ panel_tabs += f"\033[7m {name} \033[0m "
221
221
  else:
222
222
  panel_tabs += f"\033[90m {name} \033[0m "
223
223
 
@@ -153,7 +153,7 @@ steps:
153
153
  line = line[:width-1]
154
154
 
155
155
  if idx == selected:
156
- sys.stdout.write(f'\033[47;30;1m>{line}\033[0m')
156
+ sys.stdout.write(f'\033[7;1m>{line}\033[0m')
157
157
  else:
158
158
  sys.stdout.write(f' {line}')
159
159
 
@@ -1,4 +1,4 @@
1
- jinx_name: studio.write_file
1
+ jinx_name: studio_write_file
2
2
  description: Write content to an editor pane. Updates the file content in the pane.
3
3
  inputs:
4
4
  - paneId: "active"
@@ -1,4 +1,4 @@
1
- jinx_name: studio.zen_mode
1
+ jinx_name: studio_zen_mode
2
2
  description: Toggle zen mode (fullscreen) for a pane.
3
3
  inputs:
4
4
  - paneId: "active"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: npcsh
3
- Version: 1.1.18
3
+ Version: 1.1.20
4
4
  Summary: npcsh is a command-line toolkit for using AI agents in novel ways.
5
5
  Home-page: https://github.com/NPC-Worldwide/npcsh
6
6
  Author: Christopher Agostino
@@ -106,31 +106,38 @@ Dynamic: summary
106
106
  <img src="https://raw.githubusercontent.com/NPC-Worldwide/npcsh/main/npcsh/npcsh.png" alt="npcsh logo" width=600></a>
107
107
  </p>
108
108
 
109
- # NPC Shell
109
+ # npcsh
110
110
 
111
- `npcsh` - a new standard in human-agent interaction. Co-create with agents to build organizations that refine themselves and evolve to your needs.
112
-
113
- The NPC shell is a suite of programs to make use of multi-modal LLMs and agents in novel interactive modes. Based in the command line, use it wherever you work.
114
-
115
- - It is developed to work reliably with small models and performs excellently with the state-of-the-art models from major model providers.
116
- - Fundamentally, the core program of npcsh extends the familiar bash environment with an intelligent layer that lets users seamlessly ask agents questions, run pre-built or custom macros or agents, all without breaking the flow of command-line work.
117
- - Switching between agents is a breeze in `npcsh`, letting you quickly and easily take advantage of a variety of agents (e.g. coding agents versus tool-calling agents versus prompt-based ReACT Flow agents) and personas (e.g. Data scientist, mapmaker with ennui, etc.).
118
- - Project variables and context can be stored in team `.ctx` files. Personas (`.npc`) and Jinja execution templates (`.jinx`) are likewise stored in `yaml` within the global `npcsh` team or your project-specific one, letting you focus on adjusting and engineering context and system prompts iteratively so you can constantly improve your agent team's performance.
111
+ The NPC shell (`npcsh`) makes the most of multi-modal LLMs and agents through a powerful set of simple slash commands and novel interactive modes, all from the comfort of the command line. Build teams of agents and schedule them on jobs, engineer context, and design custom interaction modes and Jinja Execution templates (Jinxs for you and your agents to invoke, all managed scalably for organizations of any size through the NPC data layer.
119
112
 
120
113
  To get started:
114
+ For users who want to mainly use models through APIs (`ollama`, `gemini`, `kimi`, `grok`, `deepseek`, `anthropic`, `openai`, `mistral`, or any others provided by litellm )
121
115
  ```bash
122
- # for users who want to mainly use models through APIs (e.g. , gemini, grok, deepseek, anthropic, openai, mistral, , any others provided by litellm ):
123
116
  pip install 'npcsh[lite]'
124
- # for users who want to use local models (these install diffusers/transformers/torch stack so it is big.):
117
+ ```
118
+ For users who want to use and fine-tune local models (this installs `diffusers`/`transformers`/`torch` stack so it is big):
119
+
120
+ ```bash
125
121
  pip install 'npcsh[local]'
126
- # for users who want to use the voice mode `yap`, see also the OS-specific installation instructions for installing needed system audio libraries
122
+ ```
123
+
124
+
125
+ For users who want to use the voice mode `yap` (see also the OS-specific installation instructions for installing needed system audio libraries)
126
+ ```bash
127
127
  pip install 'npcsh[yap]'
128
128
  ```
129
+
129
130
  Once installed: run
130
131
  ```bash
131
132
  npcsh
132
133
  ```
133
- and you will enter the NPC shell. Additionally, the pip installation includes the following CLI tools available in bash: `npc` cli, `wander`, `spool`, `yap`, and `nql`. Bin jinxs in `npc_team/jinxs/bin/` are automatically registered as CLI commands.
134
+ and you will enter the NPC shell.
135
+
136
+ If you do not have any local models
137
+
138
+
139
+
140
+ Additionally, the pip installation includes the following CLI tools available in bash: `npc` cli, `wander`, `spool`, `yap`, and `nql`. Bin jinxs in `npc_team/jinxs/bin/` are automatically registered as CLI commands.
134
141
 
135
142
 
136
143
  # Usage