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.
- npcsh/_state.py +8 -0
- npcsh/benchmark/npcsh_agent.py +47 -16
- npcsh/config.py +1 -0
- npcsh/diff_viewer.py +452 -0
- npcsh/npc_team/jinxs/bin/config_tui.jinx +299 -0
- npcsh/npc_team/jinxs/bin/memories.jinx +316 -0
- npcsh/npc_team/jinxs/bin/setup.jinx +240 -0
- npcsh/npc_team/jinxs/bin/sync.jinx +143 -150
- npcsh/npc_team/jinxs/bin/team_tui.jinx +327 -0
- npcsh/npc_team/jinxs/incognide/add_tab.jinx +1 -1
- npcsh/npc_team/jinxs/incognide/close_pane.jinx +1 -1
- npcsh/npc_team/jinxs/incognide/close_tab.jinx +1 -1
- npcsh/npc_team/jinxs/incognide/confirm.jinx +1 -1
- npcsh/npc_team/jinxs/incognide/focus_pane.jinx +1 -1
- npcsh/npc_team/jinxs/incognide/list_panes.jinx +1 -1
- npcsh/npc_team/jinxs/incognide/navigate.jinx +1 -1
- npcsh/npc_team/jinxs/incognide/notify.jinx +1 -1
- npcsh/npc_team/jinxs/incognide/open_pane.jinx +1 -1
- npcsh/npc_team/jinxs/incognide/read_pane.jinx +1 -1
- npcsh/npc_team/jinxs/incognide/run_terminal.jinx +1 -1
- npcsh/npc_team/jinxs/incognide/send_message.jinx +1 -1
- npcsh/npc_team/jinxs/incognide/split_pane.jinx +1 -1
- npcsh/npc_team/jinxs/incognide/switch_npc.jinx +1 -1
- npcsh/npc_team/jinxs/incognide/switch_tab.jinx +1 -1
- npcsh/npc_team/jinxs/incognide/write_file.jinx +1 -1
- npcsh/npc_team/jinxs/incognide/zen_mode.jinx +1 -1
- npcsh/npc_team/jinxs/modes/guac.jinx +0 -2
- {npcsh-1.1.18.data → npcsh-1.1.19.data}/data/npcsh/npc_team/add_tab.jinx +1 -1
- {npcsh-1.1.18.data → npcsh-1.1.19.data}/data/npcsh/npc_team/close_pane.jinx +1 -1
- {npcsh-1.1.18.data → npcsh-1.1.19.data}/data/npcsh/npc_team/close_tab.jinx +1 -1
- npcsh-1.1.19.data/data/npcsh/npc_team/config_tui.jinx +299 -0
- {npcsh-1.1.18.data → npcsh-1.1.19.data}/data/npcsh/npc_team/confirm.jinx +1 -1
- {npcsh-1.1.18.data → npcsh-1.1.19.data}/data/npcsh/npc_team/focus_pane.jinx +1 -1
- {npcsh-1.1.18.data → npcsh-1.1.19.data}/data/npcsh/npc_team/guac.jinx +0 -2
- {npcsh-1.1.18.data → npcsh-1.1.19.data}/data/npcsh/npc_team/list_panes.jinx +1 -1
- npcsh-1.1.19.data/data/npcsh/npc_team/memories.jinx +316 -0
- {npcsh-1.1.18.data → npcsh-1.1.19.data}/data/npcsh/npc_team/navigate.jinx +1 -1
- {npcsh-1.1.18.data → npcsh-1.1.19.data}/data/npcsh/npc_team/notify.jinx +1 -1
- {npcsh-1.1.18.data → npcsh-1.1.19.data}/data/npcsh/npc_team/open_pane.jinx +1 -1
- {npcsh-1.1.18.data → npcsh-1.1.19.data}/data/npcsh/npc_team/read_pane.jinx +1 -1
- {npcsh-1.1.18.data → npcsh-1.1.19.data}/data/npcsh/npc_team/run_terminal.jinx +1 -1
- {npcsh-1.1.18.data → npcsh-1.1.19.data}/data/npcsh/npc_team/send_message.jinx +1 -1
- npcsh-1.1.19.data/data/npcsh/npc_team/setup.jinx +240 -0
- {npcsh-1.1.18.data → npcsh-1.1.19.data}/data/npcsh/npc_team/split_pane.jinx +1 -1
- {npcsh-1.1.18.data → npcsh-1.1.19.data}/data/npcsh/npc_team/switch_npc.jinx +1 -1
- {npcsh-1.1.18.data → npcsh-1.1.19.data}/data/npcsh/npc_team/switch_tab.jinx +1 -1
- npcsh-1.1.19.data/data/npcsh/npc_team/sync.jinx +223 -0
- npcsh-1.1.19.data/data/npcsh/npc_team/team_tui.jinx +327 -0
- {npcsh-1.1.18.data → npcsh-1.1.19.data}/data/npcsh/npc_team/write_file.jinx +1 -1
- {npcsh-1.1.18.data → npcsh-1.1.19.data}/data/npcsh/npc_team/zen_mode.jinx +1 -1
- {npcsh-1.1.18.dist-info → npcsh-1.1.19.dist-info}/METADATA +21 -14
- {npcsh-1.1.18.dist-info → npcsh-1.1.19.dist-info}/RECORD +138 -129
- {npcsh-1.1.18.dist-info → npcsh-1.1.19.dist-info}/entry_points.txt +4 -0
- npcsh-1.1.18.data/data/npcsh/npc_team/sync.jinx +0 -230
- {npcsh-1.1.18.data → npcsh-1.1.19.data}/data/npcsh/npc_team/alicanto.jinx +0 -0
- {npcsh-1.1.18.data → npcsh-1.1.19.data}/data/npcsh/npc_team/alicanto.npc +0 -0
- {npcsh-1.1.18.data → npcsh-1.1.19.data}/data/npcsh/npc_team/alicanto.png +0 -0
- {npcsh-1.1.18.data → npcsh-1.1.19.data}/data/npcsh/npc_team/arxiv.jinx +0 -0
- {npcsh-1.1.18.data → npcsh-1.1.19.data}/data/npcsh/npc_team/benchmark.jinx +0 -0
- {npcsh-1.1.18.data → npcsh-1.1.19.data}/data/npcsh/npc_team/browser_action.jinx +0 -0
- {npcsh-1.1.18.data → npcsh-1.1.19.data}/data/npcsh/npc_team/browser_screenshot.jinx +0 -0
- {npcsh-1.1.18.data → npcsh-1.1.19.data}/data/npcsh/npc_team/build.jinx +0 -0
- {npcsh-1.1.18.data → npcsh-1.1.19.data}/data/npcsh/npc_team/chat.jinx +0 -0
- {npcsh-1.1.18.data → npcsh-1.1.19.data}/data/npcsh/npc_team/click.jinx +0 -0
- {npcsh-1.1.18.data → npcsh-1.1.19.data}/data/npcsh/npc_team/close_browser.jinx +0 -0
- {npcsh-1.1.18.data → npcsh-1.1.19.data}/data/npcsh/npc_team/cmd.jinx +0 -0
- {npcsh-1.1.18.data → npcsh-1.1.19.data}/data/npcsh/npc_team/compile.jinx +0 -0
- {npcsh-1.1.18.data → npcsh-1.1.19.data}/data/npcsh/npc_team/compress.jinx +0 -0
- {npcsh-1.1.18.data → npcsh-1.1.19.data}/data/npcsh/npc_team/convene.jinx +0 -0
- {npcsh-1.1.18.data → npcsh-1.1.19.data}/data/npcsh/npc_team/corca.jinx +0 -0
- {npcsh-1.1.18.data → npcsh-1.1.19.data}/data/npcsh/npc_team/corca.npc +0 -0
- {npcsh-1.1.18.data → npcsh-1.1.19.data}/data/npcsh/npc_team/corca.png +0 -0
- {npcsh-1.1.18.data → npcsh-1.1.19.data}/data/npcsh/npc_team/corca_example.png +0 -0
- {npcsh-1.1.18.data → npcsh-1.1.19.data}/data/npcsh/npc_team/db_search.jinx +0 -0
- {npcsh-1.1.18.data → npcsh-1.1.19.data}/data/npcsh/npc_team/delegate.jinx +0 -0
- {npcsh-1.1.18.data → npcsh-1.1.19.data}/data/npcsh/npc_team/edit_file.jinx +0 -0
- {npcsh-1.1.18.data → npcsh-1.1.19.data}/data/npcsh/npc_team/file_search.jinx +0 -0
- {npcsh-1.1.18.data → npcsh-1.1.19.data}/data/npcsh/npc_team/frederic.npc +0 -0
- {npcsh-1.1.18.data → npcsh-1.1.19.data}/data/npcsh/npc_team/frederic4.png +0 -0
- {npcsh-1.1.18.data → npcsh-1.1.19.data}/data/npcsh/npc_team/guac.npc +0 -0
- {npcsh-1.1.18.data → npcsh-1.1.19.data}/data/npcsh/npc_team/guac.png +0 -0
- {npcsh-1.1.18.data → npcsh-1.1.19.data}/data/npcsh/npc_team/help.jinx +0 -0
- {npcsh-1.1.18.data → npcsh-1.1.19.data}/data/npcsh/npc_team/incognide.jinx +0 -0
- {npcsh-1.1.18.data → npcsh-1.1.19.data}/data/npcsh/npc_team/init.jinx +0 -0
- {npcsh-1.1.18.data → npcsh-1.1.19.data}/data/npcsh/npc_team/jinxs.jinx +0 -0
- {npcsh-1.1.18.data → npcsh-1.1.19.data}/data/npcsh/npc_team/kadiefa.npc +0 -0
- {npcsh-1.1.18.data → npcsh-1.1.19.data}/data/npcsh/npc_team/kadiefa.png +0 -0
- {npcsh-1.1.18.data → npcsh-1.1.19.data}/data/npcsh/npc_team/key_press.jinx +0 -0
- {npcsh-1.1.18.data → npcsh-1.1.19.data}/data/npcsh/npc_team/kg_search.jinx +0 -0
- {npcsh-1.1.18.data → npcsh-1.1.19.data}/data/npcsh/npc_team/launch_app.jinx +0 -0
- {npcsh-1.1.18.data → npcsh-1.1.19.data}/data/npcsh/npc_team/load_file.jinx +0 -0
- {npcsh-1.1.18.data → npcsh-1.1.19.data}/data/npcsh/npc_team/mem_review.jinx +0 -0
- {npcsh-1.1.18.data → npcsh-1.1.19.data}/data/npcsh/npc_team/mem_search.jinx +0 -0
- {npcsh-1.1.18.data → npcsh-1.1.19.data}/data/npcsh/npc_team/npcsh.ctx +0 -0
- {npcsh-1.1.18.data → npcsh-1.1.19.data}/data/npcsh/npc_team/npcsh_sibiji.png +0 -0
- {npcsh-1.1.18.data → npcsh-1.1.19.data}/data/npcsh/npc_team/nql.jinx +0 -0
- {npcsh-1.1.18.data → npcsh-1.1.19.data}/data/npcsh/npc_team/open_browser.jinx +0 -0
- {npcsh-1.1.18.data → npcsh-1.1.19.data}/data/npcsh/npc_team/ots.jinx +0 -0
- {npcsh-1.1.18.data → npcsh-1.1.19.data}/data/npcsh/npc_team/paper_search.jinx +0 -0
- {npcsh-1.1.18.data → npcsh-1.1.19.data}/data/npcsh/npc_team/paste.jinx +0 -0
- {npcsh-1.1.18.data → npcsh-1.1.19.data}/data/npcsh/npc_team/plonk.jinx +0 -0
- {npcsh-1.1.18.data → npcsh-1.1.19.data}/data/npcsh/npc_team/plonk.npc +0 -0
- {npcsh-1.1.18.data → npcsh-1.1.19.data}/data/npcsh/npc_team/plonk.png +0 -0
- {npcsh-1.1.18.data → npcsh-1.1.19.data}/data/npcsh/npc_team/plonkjr.npc +0 -0
- {npcsh-1.1.18.data → npcsh-1.1.19.data}/data/npcsh/npc_team/plonkjr.png +0 -0
- {npcsh-1.1.18.data → npcsh-1.1.19.data}/data/npcsh/npc_team/pti.jinx +0 -0
- {npcsh-1.1.18.data → npcsh-1.1.19.data}/data/npcsh/npc_team/python.jinx +0 -0
- {npcsh-1.1.18.data → npcsh-1.1.19.data}/data/npcsh/npc_team/reattach.jinx +0 -0
- {npcsh-1.1.18.data → npcsh-1.1.19.data}/data/npcsh/npc_team/roll.jinx +0 -0
- {npcsh-1.1.18.data → npcsh-1.1.19.data}/data/npcsh/npc_team/sample.jinx +0 -0
- {npcsh-1.1.18.data → npcsh-1.1.19.data}/data/npcsh/npc_team/screenshot.jinx +0 -0
- {npcsh-1.1.18.data → npcsh-1.1.19.data}/data/npcsh/npc_team/search.jinx +0 -0
- {npcsh-1.1.18.data → npcsh-1.1.19.data}/data/npcsh/npc_team/semantic_scholar.jinx +0 -0
- {npcsh-1.1.18.data → npcsh-1.1.19.data}/data/npcsh/npc_team/serve.jinx +0 -0
- {npcsh-1.1.18.data → npcsh-1.1.19.data}/data/npcsh/npc_team/set.jinx +0 -0
- {npcsh-1.1.18.data → npcsh-1.1.19.data}/data/npcsh/npc_team/sh.jinx +0 -0
- {npcsh-1.1.18.data → npcsh-1.1.19.data}/data/npcsh/npc_team/shh.jinx +0 -0
- {npcsh-1.1.18.data → npcsh-1.1.19.data}/data/npcsh/npc_team/sibiji.npc +0 -0
- {npcsh-1.1.18.data → npcsh-1.1.19.data}/data/npcsh/npc_team/sibiji.png +0 -0
- {npcsh-1.1.18.data → npcsh-1.1.19.data}/data/npcsh/npc_team/sleep.jinx +0 -0
- {npcsh-1.1.18.data → npcsh-1.1.19.data}/data/npcsh/npc_team/spool.jinx +0 -0
- {npcsh-1.1.18.data → npcsh-1.1.19.data}/data/npcsh/npc_team/spool.png +0 -0
- {npcsh-1.1.18.data → npcsh-1.1.19.data}/data/npcsh/npc_team/sql.jinx +0 -0
- {npcsh-1.1.18.data → npcsh-1.1.19.data}/data/npcsh/npc_team/switch.jinx +0 -0
- {npcsh-1.1.18.data → npcsh-1.1.19.data}/data/npcsh/npc_team/switches.jinx +0 -0
- {npcsh-1.1.18.data → npcsh-1.1.19.data}/data/npcsh/npc_team/teamviz.jinx +0 -0
- {npcsh-1.1.18.data → npcsh-1.1.19.data}/data/npcsh/npc_team/trigger.jinx +0 -0
- {npcsh-1.1.18.data → npcsh-1.1.19.data}/data/npcsh/npc_team/type_text.jinx +0 -0
- {npcsh-1.1.18.data → npcsh-1.1.19.data}/data/npcsh/npc_team/usage.jinx +0 -0
- {npcsh-1.1.18.data → npcsh-1.1.19.data}/data/npcsh/npc_team/verbose.jinx +0 -0
- {npcsh-1.1.18.data → npcsh-1.1.19.data}/data/npcsh/npc_team/vixynt.jinx +0 -0
- {npcsh-1.1.18.data → npcsh-1.1.19.data}/data/npcsh/npc_team/wait.jinx +0 -0
- {npcsh-1.1.18.data → npcsh-1.1.19.data}/data/npcsh/npc_team/wander.jinx +0 -0
- {npcsh-1.1.18.data → npcsh-1.1.19.data}/data/npcsh/npc_team/web_search.jinx +0 -0
- {npcsh-1.1.18.data → npcsh-1.1.19.data}/data/npcsh/npc_team/yap.jinx +0 -0
- {npcsh-1.1.18.data → npcsh-1.1.19.data}/data/npcsh/npc_team/yap.png +0 -0
- {npcsh-1.1.18.dist-info → npcsh-1.1.19.dist-info}/WHEEL +0 -0
- {npcsh-1.1.18.dist-info → npcsh-1.1.19.dist-info}/licenses/LICENSE +0 -0
- {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,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: npcsh
|
|
3
|
-
Version: 1.1.
|
|
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
|
-
#
|
|
109
|
+
# npcsh
|
|
110
110
|
|
|
111
|
-
`npcsh` - a
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
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
|