npcsh 1.1.18__py3-none-any.whl → 1.1.19__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 (139) hide show
  1. npcsh/_state.py +8 -0
  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 +299 -0
  6. npcsh/npc_team/jinxs/bin/memories.jinx +316 -0
  7. npcsh/npc_team/jinxs/bin/setup.jinx +240 -0
  8. npcsh/npc_team/jinxs/bin/sync.jinx +143 -150
  9. npcsh/npc_team/jinxs/bin/team_tui.jinx +327 -0
  10. npcsh/npc_team/jinxs/incognide/add_tab.jinx +1 -1
  11. npcsh/npc_team/jinxs/incognide/close_pane.jinx +1 -1
  12. npcsh/npc_team/jinxs/incognide/close_tab.jinx +1 -1
  13. npcsh/npc_team/jinxs/incognide/confirm.jinx +1 -1
  14. npcsh/npc_team/jinxs/incognide/focus_pane.jinx +1 -1
  15. npcsh/npc_team/jinxs/incognide/list_panes.jinx +1 -1
  16. npcsh/npc_team/jinxs/incognide/navigate.jinx +1 -1
  17. npcsh/npc_team/jinxs/incognide/notify.jinx +1 -1
  18. npcsh/npc_team/jinxs/incognide/open_pane.jinx +1 -1
  19. npcsh/npc_team/jinxs/incognide/read_pane.jinx +1 -1
  20. npcsh/npc_team/jinxs/incognide/run_terminal.jinx +1 -1
  21. npcsh/npc_team/jinxs/incognide/send_message.jinx +1 -1
  22. npcsh/npc_team/jinxs/incognide/split_pane.jinx +1 -1
  23. npcsh/npc_team/jinxs/incognide/switch_npc.jinx +1 -1
  24. npcsh/npc_team/jinxs/incognide/switch_tab.jinx +1 -1
  25. npcsh/npc_team/jinxs/incognide/write_file.jinx +1 -1
  26. npcsh/npc_team/jinxs/incognide/zen_mode.jinx +1 -1
  27. npcsh/npc_team/jinxs/modes/guac.jinx +0 -2
  28. {npcsh-1.1.18.data → npcsh-1.1.19.data}/data/npcsh/npc_team/add_tab.jinx +1 -1
  29. {npcsh-1.1.18.data → npcsh-1.1.19.data}/data/npcsh/npc_team/close_pane.jinx +1 -1
  30. {npcsh-1.1.18.data → npcsh-1.1.19.data}/data/npcsh/npc_team/close_tab.jinx +1 -1
  31. npcsh-1.1.19.data/data/npcsh/npc_team/config_tui.jinx +299 -0
  32. {npcsh-1.1.18.data → npcsh-1.1.19.data}/data/npcsh/npc_team/confirm.jinx +1 -1
  33. {npcsh-1.1.18.data → npcsh-1.1.19.data}/data/npcsh/npc_team/focus_pane.jinx +1 -1
  34. {npcsh-1.1.18.data → npcsh-1.1.19.data}/data/npcsh/npc_team/guac.jinx +0 -2
  35. {npcsh-1.1.18.data → npcsh-1.1.19.data}/data/npcsh/npc_team/list_panes.jinx +1 -1
  36. npcsh-1.1.19.data/data/npcsh/npc_team/memories.jinx +316 -0
  37. {npcsh-1.1.18.data → npcsh-1.1.19.data}/data/npcsh/npc_team/navigate.jinx +1 -1
  38. {npcsh-1.1.18.data → npcsh-1.1.19.data}/data/npcsh/npc_team/notify.jinx +1 -1
  39. {npcsh-1.1.18.data → npcsh-1.1.19.data}/data/npcsh/npc_team/open_pane.jinx +1 -1
  40. {npcsh-1.1.18.data → npcsh-1.1.19.data}/data/npcsh/npc_team/read_pane.jinx +1 -1
  41. {npcsh-1.1.18.data → npcsh-1.1.19.data}/data/npcsh/npc_team/run_terminal.jinx +1 -1
  42. {npcsh-1.1.18.data → npcsh-1.1.19.data}/data/npcsh/npc_team/send_message.jinx +1 -1
  43. npcsh-1.1.19.data/data/npcsh/npc_team/setup.jinx +240 -0
  44. {npcsh-1.1.18.data → npcsh-1.1.19.data}/data/npcsh/npc_team/split_pane.jinx +1 -1
  45. {npcsh-1.1.18.data → npcsh-1.1.19.data}/data/npcsh/npc_team/switch_npc.jinx +1 -1
  46. {npcsh-1.1.18.data → npcsh-1.1.19.data}/data/npcsh/npc_team/switch_tab.jinx +1 -1
  47. npcsh-1.1.19.data/data/npcsh/npc_team/sync.jinx +223 -0
  48. npcsh-1.1.19.data/data/npcsh/npc_team/team_tui.jinx +327 -0
  49. {npcsh-1.1.18.data → npcsh-1.1.19.data}/data/npcsh/npc_team/write_file.jinx +1 -1
  50. {npcsh-1.1.18.data → npcsh-1.1.19.data}/data/npcsh/npc_team/zen_mode.jinx +1 -1
  51. {npcsh-1.1.18.dist-info → npcsh-1.1.19.dist-info}/METADATA +21 -14
  52. {npcsh-1.1.18.dist-info → npcsh-1.1.19.dist-info}/RECORD +138 -129
  53. {npcsh-1.1.18.dist-info → npcsh-1.1.19.dist-info}/entry_points.txt +4 -0
  54. npcsh-1.1.18.data/data/npcsh/npc_team/sync.jinx +0 -230
  55. {npcsh-1.1.18.data → npcsh-1.1.19.data}/data/npcsh/npc_team/alicanto.jinx +0 -0
  56. {npcsh-1.1.18.data → npcsh-1.1.19.data}/data/npcsh/npc_team/alicanto.npc +0 -0
  57. {npcsh-1.1.18.data → npcsh-1.1.19.data}/data/npcsh/npc_team/alicanto.png +0 -0
  58. {npcsh-1.1.18.data → npcsh-1.1.19.data}/data/npcsh/npc_team/arxiv.jinx +0 -0
  59. {npcsh-1.1.18.data → npcsh-1.1.19.data}/data/npcsh/npc_team/benchmark.jinx +0 -0
  60. {npcsh-1.1.18.data → npcsh-1.1.19.data}/data/npcsh/npc_team/browser_action.jinx +0 -0
  61. {npcsh-1.1.18.data → npcsh-1.1.19.data}/data/npcsh/npc_team/browser_screenshot.jinx +0 -0
  62. {npcsh-1.1.18.data → npcsh-1.1.19.data}/data/npcsh/npc_team/build.jinx +0 -0
  63. {npcsh-1.1.18.data → npcsh-1.1.19.data}/data/npcsh/npc_team/chat.jinx +0 -0
  64. {npcsh-1.1.18.data → npcsh-1.1.19.data}/data/npcsh/npc_team/click.jinx +0 -0
  65. {npcsh-1.1.18.data → npcsh-1.1.19.data}/data/npcsh/npc_team/close_browser.jinx +0 -0
  66. {npcsh-1.1.18.data → npcsh-1.1.19.data}/data/npcsh/npc_team/cmd.jinx +0 -0
  67. {npcsh-1.1.18.data → npcsh-1.1.19.data}/data/npcsh/npc_team/compile.jinx +0 -0
  68. {npcsh-1.1.18.data → npcsh-1.1.19.data}/data/npcsh/npc_team/compress.jinx +0 -0
  69. {npcsh-1.1.18.data → npcsh-1.1.19.data}/data/npcsh/npc_team/convene.jinx +0 -0
  70. {npcsh-1.1.18.data → npcsh-1.1.19.data}/data/npcsh/npc_team/corca.jinx +0 -0
  71. {npcsh-1.1.18.data → npcsh-1.1.19.data}/data/npcsh/npc_team/corca.npc +0 -0
  72. {npcsh-1.1.18.data → npcsh-1.1.19.data}/data/npcsh/npc_team/corca.png +0 -0
  73. {npcsh-1.1.18.data → npcsh-1.1.19.data}/data/npcsh/npc_team/corca_example.png +0 -0
  74. {npcsh-1.1.18.data → npcsh-1.1.19.data}/data/npcsh/npc_team/db_search.jinx +0 -0
  75. {npcsh-1.1.18.data → npcsh-1.1.19.data}/data/npcsh/npc_team/delegate.jinx +0 -0
  76. {npcsh-1.1.18.data → npcsh-1.1.19.data}/data/npcsh/npc_team/edit_file.jinx +0 -0
  77. {npcsh-1.1.18.data → npcsh-1.1.19.data}/data/npcsh/npc_team/file_search.jinx +0 -0
  78. {npcsh-1.1.18.data → npcsh-1.1.19.data}/data/npcsh/npc_team/frederic.npc +0 -0
  79. {npcsh-1.1.18.data → npcsh-1.1.19.data}/data/npcsh/npc_team/frederic4.png +0 -0
  80. {npcsh-1.1.18.data → npcsh-1.1.19.data}/data/npcsh/npc_team/guac.npc +0 -0
  81. {npcsh-1.1.18.data → npcsh-1.1.19.data}/data/npcsh/npc_team/guac.png +0 -0
  82. {npcsh-1.1.18.data → npcsh-1.1.19.data}/data/npcsh/npc_team/help.jinx +0 -0
  83. {npcsh-1.1.18.data → npcsh-1.1.19.data}/data/npcsh/npc_team/incognide.jinx +0 -0
  84. {npcsh-1.1.18.data → npcsh-1.1.19.data}/data/npcsh/npc_team/init.jinx +0 -0
  85. {npcsh-1.1.18.data → npcsh-1.1.19.data}/data/npcsh/npc_team/jinxs.jinx +0 -0
  86. {npcsh-1.1.18.data → npcsh-1.1.19.data}/data/npcsh/npc_team/kadiefa.npc +0 -0
  87. {npcsh-1.1.18.data → npcsh-1.1.19.data}/data/npcsh/npc_team/kadiefa.png +0 -0
  88. {npcsh-1.1.18.data → npcsh-1.1.19.data}/data/npcsh/npc_team/key_press.jinx +0 -0
  89. {npcsh-1.1.18.data → npcsh-1.1.19.data}/data/npcsh/npc_team/kg_search.jinx +0 -0
  90. {npcsh-1.1.18.data → npcsh-1.1.19.data}/data/npcsh/npc_team/launch_app.jinx +0 -0
  91. {npcsh-1.1.18.data → npcsh-1.1.19.data}/data/npcsh/npc_team/load_file.jinx +0 -0
  92. {npcsh-1.1.18.data → npcsh-1.1.19.data}/data/npcsh/npc_team/mem_review.jinx +0 -0
  93. {npcsh-1.1.18.data → npcsh-1.1.19.data}/data/npcsh/npc_team/mem_search.jinx +0 -0
  94. {npcsh-1.1.18.data → npcsh-1.1.19.data}/data/npcsh/npc_team/npcsh.ctx +0 -0
  95. {npcsh-1.1.18.data → npcsh-1.1.19.data}/data/npcsh/npc_team/npcsh_sibiji.png +0 -0
  96. {npcsh-1.1.18.data → npcsh-1.1.19.data}/data/npcsh/npc_team/nql.jinx +0 -0
  97. {npcsh-1.1.18.data → npcsh-1.1.19.data}/data/npcsh/npc_team/open_browser.jinx +0 -0
  98. {npcsh-1.1.18.data → npcsh-1.1.19.data}/data/npcsh/npc_team/ots.jinx +0 -0
  99. {npcsh-1.1.18.data → npcsh-1.1.19.data}/data/npcsh/npc_team/paper_search.jinx +0 -0
  100. {npcsh-1.1.18.data → npcsh-1.1.19.data}/data/npcsh/npc_team/paste.jinx +0 -0
  101. {npcsh-1.1.18.data → npcsh-1.1.19.data}/data/npcsh/npc_team/plonk.jinx +0 -0
  102. {npcsh-1.1.18.data → npcsh-1.1.19.data}/data/npcsh/npc_team/plonk.npc +0 -0
  103. {npcsh-1.1.18.data → npcsh-1.1.19.data}/data/npcsh/npc_team/plonk.png +0 -0
  104. {npcsh-1.1.18.data → npcsh-1.1.19.data}/data/npcsh/npc_team/plonkjr.npc +0 -0
  105. {npcsh-1.1.18.data → npcsh-1.1.19.data}/data/npcsh/npc_team/plonkjr.png +0 -0
  106. {npcsh-1.1.18.data → npcsh-1.1.19.data}/data/npcsh/npc_team/pti.jinx +0 -0
  107. {npcsh-1.1.18.data → npcsh-1.1.19.data}/data/npcsh/npc_team/python.jinx +0 -0
  108. {npcsh-1.1.18.data → npcsh-1.1.19.data}/data/npcsh/npc_team/reattach.jinx +0 -0
  109. {npcsh-1.1.18.data → npcsh-1.1.19.data}/data/npcsh/npc_team/roll.jinx +0 -0
  110. {npcsh-1.1.18.data → npcsh-1.1.19.data}/data/npcsh/npc_team/sample.jinx +0 -0
  111. {npcsh-1.1.18.data → npcsh-1.1.19.data}/data/npcsh/npc_team/screenshot.jinx +0 -0
  112. {npcsh-1.1.18.data → npcsh-1.1.19.data}/data/npcsh/npc_team/search.jinx +0 -0
  113. {npcsh-1.1.18.data → npcsh-1.1.19.data}/data/npcsh/npc_team/semantic_scholar.jinx +0 -0
  114. {npcsh-1.1.18.data → npcsh-1.1.19.data}/data/npcsh/npc_team/serve.jinx +0 -0
  115. {npcsh-1.1.18.data → npcsh-1.1.19.data}/data/npcsh/npc_team/set.jinx +0 -0
  116. {npcsh-1.1.18.data → npcsh-1.1.19.data}/data/npcsh/npc_team/sh.jinx +0 -0
  117. {npcsh-1.1.18.data → npcsh-1.1.19.data}/data/npcsh/npc_team/shh.jinx +0 -0
  118. {npcsh-1.1.18.data → npcsh-1.1.19.data}/data/npcsh/npc_team/sibiji.npc +0 -0
  119. {npcsh-1.1.18.data → npcsh-1.1.19.data}/data/npcsh/npc_team/sibiji.png +0 -0
  120. {npcsh-1.1.18.data → npcsh-1.1.19.data}/data/npcsh/npc_team/sleep.jinx +0 -0
  121. {npcsh-1.1.18.data → npcsh-1.1.19.data}/data/npcsh/npc_team/spool.jinx +0 -0
  122. {npcsh-1.1.18.data → npcsh-1.1.19.data}/data/npcsh/npc_team/spool.png +0 -0
  123. {npcsh-1.1.18.data → npcsh-1.1.19.data}/data/npcsh/npc_team/sql.jinx +0 -0
  124. {npcsh-1.1.18.data → npcsh-1.1.19.data}/data/npcsh/npc_team/switch.jinx +0 -0
  125. {npcsh-1.1.18.data → npcsh-1.1.19.data}/data/npcsh/npc_team/switches.jinx +0 -0
  126. {npcsh-1.1.18.data → npcsh-1.1.19.data}/data/npcsh/npc_team/teamviz.jinx +0 -0
  127. {npcsh-1.1.18.data → npcsh-1.1.19.data}/data/npcsh/npc_team/trigger.jinx +0 -0
  128. {npcsh-1.1.18.data → npcsh-1.1.19.data}/data/npcsh/npc_team/type_text.jinx +0 -0
  129. {npcsh-1.1.18.data → npcsh-1.1.19.data}/data/npcsh/npc_team/usage.jinx +0 -0
  130. {npcsh-1.1.18.data → npcsh-1.1.19.data}/data/npcsh/npc_team/verbose.jinx +0 -0
  131. {npcsh-1.1.18.data → npcsh-1.1.19.data}/data/npcsh/npc_team/vixynt.jinx +0 -0
  132. {npcsh-1.1.18.data → npcsh-1.1.19.data}/data/npcsh/npc_team/wait.jinx +0 -0
  133. {npcsh-1.1.18.data → npcsh-1.1.19.data}/data/npcsh/npc_team/wander.jinx +0 -0
  134. {npcsh-1.1.18.data → npcsh-1.1.19.data}/data/npcsh/npc_team/web_search.jinx +0 -0
  135. {npcsh-1.1.18.data → npcsh-1.1.19.data}/data/npcsh/npc_team/yap.jinx +0 -0
  136. {npcsh-1.1.18.data → npcsh-1.1.19.data}/data/npcsh/npc_team/yap.png +0 -0
  137. {npcsh-1.1.18.dist-info → npcsh-1.1.19.dist-info}/WHEEL +0 -0
  138. {npcsh-1.1.18.dist-info → npcsh-1.1.19.dist-info}/licenses/LICENSE +0 -0
  139. {npcsh-1.1.18.dist-info → npcsh-1.1.19.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,327 @@
1
+ jinx_name: team_tui
2
+ description: Interactive TUI for managing team context, NPCs, and jinxs
3
+ inputs: []
4
+ steps:
5
+ - name: team_manager
6
+ engine: python
7
+ code: |
8
+ import os
9
+ import sys
10
+ import tty
11
+ import termios
12
+ import select
13
+ import yaml
14
+ from pathlib import Path
15
+
16
+ if not sys.stdin.isatty():
17
+ context['output'] = "Team TUI requires an interactive terminal."
18
+ return
19
+
20
+ team = context.get('team')
21
+ if not team:
22
+ context['output'] = "No team loaded."
23
+ return
24
+
25
+ # ========== State ==========
26
+ class TeamState:
27
+ def __init__(self):
28
+ self.tab = 0 # 0=Team Context, 1=NPCs, 2=Jinxs
29
+ self.tabs = ['Team Context', 'NPCs', 'Jinxs']
30
+ self.selected_idx = 0
31
+ self.scroll_offset = 0
32
+ self.editing = False
33
+ self.edit_field = None
34
+ self.edit_buffer = ""
35
+ self.edit_cursor = 0
36
+ self.status = ""
37
+ self.team_ctx = {}
38
+ self.npcs = []
39
+ self.jinxs = []
40
+
41
+ state = TeamState()
42
+
43
+ # ========== Helpers ==========
44
+ def get_size():
45
+ try:
46
+ s = os.get_terminal_size()
47
+ return s.columns, s.lines
48
+ except:
49
+ return 80, 24
50
+
51
+ def load_team_data():
52
+ """Load team context, NPCs, and jinxs."""
53
+ # Load team.ctx
54
+ if hasattr(team, 'team_ctx'):
55
+ state.team_ctx = team.team_ctx or {}
56
+ else:
57
+ ctx_path = Path(team.team_path) / 'team.ctx'
58
+ if ctx_path.exists():
59
+ with open(ctx_path) as f:
60
+ state.team_ctx = yaml.safe_load(f) or {}
61
+
62
+ # Load NPCs
63
+ if hasattr(team, 'npcs'):
64
+ state.npcs = list(team.npcs.keys())
65
+ else:
66
+ npc_dir = Path(team.team_path)
67
+ state.npcs = [f.stem for f in npc_dir.glob('*.npc')]
68
+
69
+ # Load Jinxs count by folder
70
+ jinxs_dir = Path(team.team_path) / 'jinxs'
71
+ state.jinxs = []
72
+ if jinxs_dir.exists():
73
+ for subdir in sorted(jinxs_dir.iterdir()):
74
+ if subdir.is_dir():
75
+ count = len(list(subdir.glob('*.jinx')))
76
+ state.jinxs.append((subdir.name, count))
77
+
78
+ def save_team_ctx():
79
+ """Save team.ctx file."""
80
+ ctx_path = Path(team.team_path) / 'team.ctx'
81
+ with open(ctx_path, 'w') as f:
82
+ yaml.dump(state.team_ctx, f, default_flow_style=False)
83
+ state.status = "Team context saved!"
84
+
85
+ # ========== Rendering ==========
86
+ def render_screen():
87
+ width, height = get_size()
88
+ out = []
89
+ out.append("\033[2J\033[H")
90
+
91
+ # Header
92
+ team_name = getattr(team, 'name', 'Unknown')
93
+ header = f" Team: {team_name} "
94
+ out.append(f"\033[1;1H\033[44;37;1m{'=' * width}\033[0m")
95
+ out.append(f"\033[1;{(width - len(header)) // 2}H\033[44;37;1m{header}\033[0m")
96
+
97
+ # Tabs
98
+ tab_str = ""
99
+ for i, tab in enumerate(state.tabs):
100
+ if i == state.tab:
101
+ tab_str += f"\033[47;30m [{tab}] \033[0m"
102
+ else:
103
+ tab_str += f" [{tab}] "
104
+ out.append(f"\033[2;2H{tab_str}")
105
+
106
+ # Content area
107
+ out.append(f"\033[3;1H\033[90m{'─' * width}\033[0m")
108
+
109
+ if state.tab == 0:
110
+ render_team_ctx(out, width, height)
111
+ elif state.tab == 1:
112
+ render_npcs(out, width, height)
113
+ elif state.tab == 2:
114
+ render_jinxs(out, width, height)
115
+
116
+ # Status
117
+ if state.status:
118
+ out.append(f"\033[{height-2};2H\033[33m{state.status}\033[0m")
119
+
120
+ # Footer
121
+ if state.editing:
122
+ footer = "[Enter] Save [Esc] Cancel"
123
+ else:
124
+ footer = "[Tab] Switch Tab [j/k] Navigate [e] Edit [s] Save [q] Quit"
125
+ out.append(f"\033[{height};1H\033[90m{footer[:width]}\033[0m")
126
+
127
+ sys.stdout.write(''.join(out))
128
+ sys.stdout.flush()
129
+
130
+ def render_team_ctx(out, width, height):
131
+ """Render team context tab."""
132
+ fields = [
133
+ ('forenpc', 'Forenpc'),
134
+ ('model', 'Model'),
135
+ ('provider', 'Provider'),
136
+ ('context', 'Context'),
137
+ ]
138
+
139
+ row = 5
140
+ for i, (key, label) in enumerate(fields):
141
+ value = state.team_ctx.get(key, '')
142
+ if isinstance(value, str) and len(value) > 50:
143
+ value = value[:50] + '...'
144
+
145
+ if i == state.selected_idx:
146
+ if state.editing:
147
+ out.append(f"\033[{row};4H\033[1m{label}:\033[0m")
148
+ out.append(f"\033[{row+1};6H{state.edit_buffer[:width-10]}\033[7m \033[0m")
149
+ row += 2
150
+ else:
151
+ out.append(f"\033[{row};4H\033[47;30m{label}: {value}\033[0m")
152
+ row += 1
153
+ else:
154
+ if value:
155
+ out.append(f"\033[{row};4H{label}: \033[32m{value}\033[0m")
156
+ else:
157
+ out.append(f"\033[{row};4H{label}: \033[90m(not set)\033[0m")
158
+ row += 1
159
+
160
+ def render_npcs(out, width, height):
161
+ """Render NPCs tab."""
162
+ visible_height = height - 8
163
+ visible = state.npcs[state.scroll_offset:state.scroll_offset + visible_height]
164
+
165
+ row = 5
166
+ for i, npc_name in enumerate(visible):
167
+ idx = i + state.scroll_offset
168
+ npc_obj = team.npcs.get(npc_name) if hasattr(team, 'npcs') else None
169
+
170
+ if npc_obj:
171
+ model_info = f"{npc_obj.model or 'default'}/{npc_obj.provider or 'default'}"
172
+ else:
173
+ model_info = ""
174
+
175
+ if idx == state.selected_idx:
176
+ out.append(f"\033[{row};4H\033[47;30m> {npc_name:<15} {model_info}\033[0m")
177
+ else:
178
+ out.append(f"\033[{row};4H {npc_name:<15} \033[90m{model_info}\033[0m")
179
+ row += 1
180
+
181
+ if not state.npcs:
182
+ out.append(f"\033[5;4H\033[90mNo NPCs found in team.\033[0m")
183
+
184
+ def render_jinxs(out, width, height):
185
+ """Render Jinxs tab."""
186
+ visible_height = height - 8
187
+ visible = state.jinxs[state.scroll_offset:state.scroll_offset + visible_height]
188
+
189
+ row = 5
190
+ for i, (folder, count) in enumerate(visible):
191
+ idx = i + state.scroll_offset
192
+ if idx == state.selected_idx:
193
+ out.append(f"\033[{row};4H\033[47;30m> {folder}/ ({count} jinxs)\033[0m")
194
+ else:
195
+ out.append(f"\033[{row};4H {folder}/ \033[90m({count} jinxs)\033[0m")
196
+ row += 1
197
+
198
+ if not state.jinxs:
199
+ out.append(f"\033[5;4H\033[90mNo jinxs folders found.\033[0m")
200
+
201
+ # ========== Input Handling ==========
202
+ def handle_input(c):
203
+ if state.editing:
204
+ return handle_edit_input(c)
205
+
206
+ if c == 'q':
207
+ return False
208
+
209
+ if c == '\t': # Tab - switch tabs
210
+ state.tab = (state.tab + 1) % len(state.tabs)
211
+ state.selected_idx = 0
212
+ state.scroll_offset = 0
213
+ state.status = ""
214
+
215
+ elif c == '\x1b': # Escape sequence
216
+ if select.select([sys.stdin], [], [], 0.05)[0]:
217
+ c2 = sys.stdin.read(1)
218
+ if c2 == '[':
219
+ c3 = sys.stdin.read(1)
220
+ if c3 == 'A': # Up
221
+ move_up()
222
+ elif c3 == 'B': # Down
223
+ move_down()
224
+
225
+ elif c == 'k':
226
+ move_up()
227
+ elif c == 'j':
228
+ move_down()
229
+ elif c == 'e' or c == '\r' or c == '\n':
230
+ start_edit()
231
+ elif c == 's':
232
+ save_team_ctx()
233
+
234
+ return True
235
+
236
+ def handle_edit_input(c):
237
+ if c == '\x1b': # Escape - cancel
238
+ state.editing = False
239
+ state.edit_buffer = ""
240
+ state.status = "Edit cancelled"
241
+ return True
242
+
243
+ if c == '\r' or c == '\n': # Enter - save
244
+ if state.edit_field:
245
+ state.team_ctx[state.edit_field] = state.edit_buffer
246
+ state.status = f"Updated {state.edit_field}"
247
+ state.editing = False
248
+ state.edit_buffer = ""
249
+ state.edit_field = None
250
+ return True
251
+
252
+ if c == '\x7f' or c == '\x08': # Backspace
253
+ if state.edit_cursor > 0:
254
+ state.edit_buffer = state.edit_buffer[:state.edit_cursor-1] + state.edit_buffer[state.edit_cursor:]
255
+ state.edit_cursor -= 1
256
+
257
+ elif c >= ' ' and c <= '~': # Printable
258
+ state.edit_buffer = state.edit_buffer[:state.edit_cursor] + c + state.edit_buffer[state.edit_cursor:]
259
+ state.edit_cursor += 1
260
+
261
+ return True
262
+
263
+ def move_up():
264
+ state.selected_idx = max(0, state.selected_idx - 1)
265
+ if state.selected_idx < state.scroll_offset:
266
+ state.scroll_offset = state.selected_idx
267
+ state.status = ""
268
+
269
+ def move_down():
270
+ _, height = get_size()
271
+ visible_height = height - 8
272
+
273
+ if state.tab == 0:
274
+ max_idx = 3 # 4 fields in team ctx
275
+ elif state.tab == 1:
276
+ max_idx = len(state.npcs) - 1
277
+ else:
278
+ max_idx = len(state.jinxs) - 1
279
+
280
+ state.selected_idx = min(max_idx, state.selected_idx + 1)
281
+ if state.selected_idx >= state.scroll_offset + visible_height:
282
+ state.scroll_offset = state.selected_idx - visible_height + 1
283
+ state.status = ""
284
+
285
+ def start_edit():
286
+ if state.tab == 0:
287
+ fields = ['forenpc', 'model', 'provider', 'context']
288
+ if state.selected_idx < len(fields):
289
+ state.edit_field = fields[state.selected_idx]
290
+ state.edit_buffer = str(state.team_ctx.get(state.edit_field, ''))
291
+ state.edit_cursor = len(state.edit_buffer)
292
+ state.editing = True
293
+ state.status = "Editing..."
294
+ elif state.tab == 1:
295
+ if state.npcs and state.selected_idx < len(state.npcs):
296
+ npc_name = state.npcs[state.selected_idx]
297
+ state.status = f"Selected NPC: {npc_name} (edit NPC files directly)"
298
+ elif state.tab == 2:
299
+ if state.jinxs and state.selected_idx < len(state.jinxs):
300
+ folder, _ = state.jinxs[state.selected_idx]
301
+ state.status = f"Selected folder: {folder}/"
302
+
303
+ # ========== Main Loop ==========
304
+ load_team_data()
305
+
306
+ fd = sys.stdin.fileno()
307
+ old_settings = termios.tcgetattr(fd)
308
+
309
+ try:
310
+ tty.setcbreak(fd)
311
+ sys.stdout.write('\033[?25l') # Hide cursor
312
+
313
+ render_screen()
314
+
315
+ while True:
316
+ c = sys.stdin.read(1)
317
+ if not handle_input(c):
318
+ break
319
+ render_screen()
320
+
321
+ finally:
322
+ termios.tcsetattr(fd, termios.TCSADRAIN, old_settings)
323
+ sys.stdout.write('\033[?25h') # Show cursor
324
+ sys.stdout.write('\033[2J\033[H') # Clear screen
325
+ sys.stdout.flush()
326
+
327
+ context['output'] = "Team manager closed."
@@ -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.19
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