npcsh 1.1.13__py3-none-any.whl → 1.1.15__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 +491 -80
- npcsh/mcp_server.py +2 -1
- npcsh/npc.py +84 -32
- npcsh/npc_team/alicanto.npc +22 -1
- npcsh/npc_team/corca.npc +28 -9
- npcsh/npc_team/frederic.npc +25 -4
- npcsh/npc_team/guac.npc +22 -0
- npcsh/npc_team/jinxs/bin/nql.jinx +141 -0
- npcsh/npc_team/jinxs/bin/sync.jinx +230 -0
- {npcsh-1.1.13.data/data/npcsh/npc_team → npcsh/npc_team/jinxs/bin}/vixynt.jinx +8 -30
- npcsh/npc_team/jinxs/bin/wander.jinx +152 -0
- npcsh/npc_team/jinxs/lib/browser/browser_action.jinx +220 -0
- npcsh/npc_team/jinxs/lib/browser/browser_screenshot.jinx +40 -0
- npcsh/npc_team/jinxs/lib/browser/close_browser.jinx +14 -0
- npcsh/npc_team/jinxs/lib/browser/open_browser.jinx +43 -0
- npcsh/npc_team/jinxs/lib/computer_use/click.jinx +23 -0
- npcsh/npc_team/jinxs/lib/computer_use/key_press.jinx +26 -0
- npcsh/npc_team/jinxs/lib/computer_use/launch_app.jinx +37 -0
- npcsh/npc_team/jinxs/lib/computer_use/screenshot.jinx +23 -0
- npcsh/npc_team/jinxs/lib/computer_use/type_text.jinx +27 -0
- npcsh/npc_team/jinxs/lib/computer_use/wait.jinx +21 -0
- {npcsh-1.1.13.data/data/npcsh/npc_team → npcsh/npc_team/jinxs/lib/core}/edit_file.jinx +3 -3
- {npcsh-1.1.13.data/data/npcsh/npc_team → npcsh/npc_team/jinxs/lib/core}/load_file.jinx +1 -1
- npcsh/npc_team/jinxs/lib/core/paste.jinx +134 -0
- {npcsh-1.1.13.data/data/npcsh/npc_team → npcsh/npc_team/jinxs/lib/core}/search.jinx +2 -1
- npcsh/npc_team/jinxs/{code → lib/core}/sh.jinx +2 -8
- npcsh/npc_team/jinxs/{code → lib/core}/sql.jinx +1 -1
- npcsh/npc_team/jinxs/lib/orchestration/convene.jinx +232 -0
- npcsh/npc_team/jinxs/lib/orchestration/delegate.jinx +184 -0
- npcsh/npc_team/jinxs/lib/research/arxiv.jinx +76 -0
- npcsh/npc_team/jinxs/lib/research/paper_search.jinx +101 -0
- npcsh/npc_team/jinxs/lib/research/semantic_scholar.jinx +69 -0
- npcsh/npc_team/jinxs/{utils/core → lib/utils}/build.jinx +8 -8
- npcsh/npc_team/jinxs/lib/utils/jinxs.jinx +176 -0
- npcsh/npc_team/jinxs/lib/utils/shh.jinx +17 -0
- npcsh/npc_team/jinxs/lib/utils/switch.jinx +62 -0
- npcsh/npc_team/jinxs/lib/utils/switches.jinx +61 -0
- npcsh/npc_team/jinxs/lib/utils/teamviz.jinx +205 -0
- npcsh/npc_team/jinxs/lib/utils/verbose.jinx +17 -0
- npcsh/npc_team/kadiefa.npc +19 -1
- npcsh/npc_team/plonk.npc +26 -1
- npcsh/npc_team/plonkjr.npc +22 -1
- npcsh/npc_team/sibiji.npc +23 -2
- npcsh/npcsh.py +153 -39
- npcsh/ui.py +22 -1
- npcsh-1.1.15.data/data/npcsh/npc_team/alicanto.npc +23 -0
- npcsh-1.1.15.data/data/npcsh/npc_team/arxiv.jinx +76 -0
- npcsh-1.1.15.data/data/npcsh/npc_team/browser_action.jinx +220 -0
- npcsh-1.1.15.data/data/npcsh/npc_team/browser_screenshot.jinx +40 -0
- {npcsh-1.1.13.data → npcsh-1.1.15.data}/data/npcsh/npc_team/build.jinx +8 -8
- npcsh-1.1.15.data/data/npcsh/npc_team/click.jinx +23 -0
- npcsh-1.1.15.data/data/npcsh/npc_team/close_browser.jinx +14 -0
- npcsh-1.1.15.data/data/npcsh/npc_team/convene.jinx +232 -0
- npcsh-1.1.15.data/data/npcsh/npc_team/corca.npc +31 -0
- npcsh-1.1.15.data/data/npcsh/npc_team/delegate.jinx +184 -0
- {npcsh/npc_team/jinxs/utils → npcsh-1.1.15.data/data/npcsh/npc_team}/edit_file.jinx +3 -3
- npcsh-1.1.15.data/data/npcsh/npc_team/frederic.npc +27 -0
- npcsh-1.1.15.data/data/npcsh/npc_team/guac.npc +22 -0
- npcsh-1.1.15.data/data/npcsh/npc_team/jinxs.jinx +176 -0
- npcsh-1.1.15.data/data/npcsh/npc_team/kadiefa.npc +21 -0
- npcsh-1.1.15.data/data/npcsh/npc_team/key_press.jinx +26 -0
- npcsh-1.1.15.data/data/npcsh/npc_team/launch_app.jinx +37 -0
- {npcsh/npc_team/jinxs/utils → npcsh-1.1.15.data/data/npcsh/npc_team}/load_file.jinx +1 -1
- npcsh-1.1.15.data/data/npcsh/npc_team/nql.jinx +141 -0
- npcsh-1.1.15.data/data/npcsh/npc_team/open_browser.jinx +43 -0
- npcsh-1.1.15.data/data/npcsh/npc_team/paper_search.jinx +101 -0
- npcsh-1.1.15.data/data/npcsh/npc_team/paste.jinx +134 -0
- npcsh-1.1.15.data/data/npcsh/npc_team/plonk.npc +27 -0
- npcsh-1.1.15.data/data/npcsh/npc_team/plonkjr.npc +23 -0
- npcsh-1.1.15.data/data/npcsh/npc_team/screenshot.jinx +23 -0
- {npcsh/npc_team/jinxs/utils → npcsh-1.1.15.data/data/npcsh/npc_team}/search.jinx +2 -1
- npcsh-1.1.15.data/data/npcsh/npc_team/semantic_scholar.jinx +69 -0
- {npcsh-1.1.13.data → npcsh-1.1.15.data}/data/npcsh/npc_team/sh.jinx +2 -8
- npcsh-1.1.15.data/data/npcsh/npc_team/shh.jinx +17 -0
- npcsh-1.1.15.data/data/npcsh/npc_team/sibiji.npc +24 -0
- {npcsh-1.1.13.data → npcsh-1.1.15.data}/data/npcsh/npc_team/sql.jinx +1 -1
- npcsh-1.1.15.data/data/npcsh/npc_team/switch.jinx +62 -0
- npcsh-1.1.15.data/data/npcsh/npc_team/switches.jinx +61 -0
- npcsh-1.1.15.data/data/npcsh/npc_team/sync.jinx +230 -0
- npcsh-1.1.15.data/data/npcsh/npc_team/teamviz.jinx +205 -0
- npcsh-1.1.15.data/data/npcsh/npc_team/type_text.jinx +27 -0
- npcsh-1.1.15.data/data/npcsh/npc_team/verbose.jinx +17 -0
- {npcsh/npc_team/jinxs/utils → npcsh-1.1.15.data/data/npcsh/npc_team}/vixynt.jinx +8 -30
- npcsh-1.1.15.data/data/npcsh/npc_team/wait.jinx +21 -0
- npcsh-1.1.15.data/data/npcsh/npc_team/wander.jinx +152 -0
- {npcsh-1.1.13.dist-info → npcsh-1.1.15.dist-info}/METADATA +399 -58
- npcsh-1.1.15.dist-info/RECORD +170 -0
- npcsh-1.1.15.dist-info/entry_points.txt +19 -0
- npcsh-1.1.15.dist-info/top_level.txt +2 -0
- project/__init__.py +1 -0
- npcsh/npc_team/foreman.npc +0 -7
- npcsh/npc_team/jinxs/modes/alicanto.jinx +0 -194
- npcsh/npc_team/jinxs/modes/corca.jinx +0 -249
- npcsh/npc_team/jinxs/modes/guac.jinx +0 -317
- npcsh/npc_team/jinxs/modes/plonk.jinx +0 -214
- npcsh/npc_team/jinxs/modes/pti.jinx +0 -170
- npcsh/npc_team/jinxs/modes/wander.jinx +0 -186
- npcsh/npc_team/jinxs/utils/agent.jinx +0 -17
- npcsh/npc_team/jinxs/utils/core/jinxs.jinx +0 -32
- npcsh-1.1.13.data/data/npcsh/npc_team/agent.jinx +0 -17
- npcsh-1.1.13.data/data/npcsh/npc_team/alicanto.jinx +0 -194
- npcsh-1.1.13.data/data/npcsh/npc_team/alicanto.npc +0 -2
- npcsh-1.1.13.data/data/npcsh/npc_team/corca.jinx +0 -249
- npcsh-1.1.13.data/data/npcsh/npc_team/corca.npc +0 -12
- npcsh-1.1.13.data/data/npcsh/npc_team/foreman.npc +0 -7
- npcsh-1.1.13.data/data/npcsh/npc_team/frederic.npc +0 -6
- npcsh-1.1.13.data/data/npcsh/npc_team/guac.jinx +0 -317
- npcsh-1.1.13.data/data/npcsh/npc_team/jinxs.jinx +0 -32
- npcsh-1.1.13.data/data/npcsh/npc_team/kadiefa.npc +0 -3
- npcsh-1.1.13.data/data/npcsh/npc_team/plonk.jinx +0 -214
- npcsh-1.1.13.data/data/npcsh/npc_team/plonk.npc +0 -2
- npcsh-1.1.13.data/data/npcsh/npc_team/plonkjr.npc +0 -2
- npcsh-1.1.13.data/data/npcsh/npc_team/pti.jinx +0 -170
- npcsh-1.1.13.data/data/npcsh/npc_team/sibiji.npc +0 -3
- npcsh-1.1.13.data/data/npcsh/npc_team/wander.jinx +0 -186
- npcsh-1.1.13.dist-info/RECORD +0 -135
- npcsh-1.1.13.dist-info/entry_points.txt +0 -9
- npcsh-1.1.13.dist-info/top_level.txt +0 -1
- /npcsh/npc_team/jinxs/{utils → bin}/roll.jinx +0 -0
- /npcsh/npc_team/jinxs/{utils → bin}/sample.jinx +0 -0
- /npcsh/npc_team/jinxs/{modes → bin}/spool.jinx +0 -0
- /npcsh/npc_team/jinxs/{modes → bin}/yap.jinx +0 -0
- /npcsh/npc_team/jinxs/{utils → lib/computer_use}/trigger.jinx +0 -0
- /npcsh/npc_team/jinxs/{utils → lib/core}/chat.jinx +0 -0
- /npcsh/npc_team/jinxs/{utils → lib/core}/cmd.jinx +0 -0
- /npcsh/npc_team/jinxs/{utils → lib/core}/compress.jinx +0 -0
- /npcsh/npc_team/jinxs/{utils → lib/core}/ots.jinx +0 -0
- /npcsh/npc_team/jinxs/{code → lib/core}/python.jinx +0 -0
- /npcsh/npc_team/jinxs/{utils → lib/core}/sleep.jinx +0 -0
- /npcsh/npc_team/jinxs/{utils/core → lib/utils}/compile.jinx +0 -0
- /npcsh/npc_team/jinxs/{utils/core → lib/utils}/help.jinx +0 -0
- /npcsh/npc_team/jinxs/{utils/core → lib/utils}/init.jinx +0 -0
- /npcsh/npc_team/jinxs/{utils → lib/utils}/serve.jinx +0 -0
- /npcsh/npc_team/jinxs/{utils/core → lib/utils}/set.jinx +0 -0
- /npcsh/npc_team/jinxs/{utils → lib/utils}/usage.jinx +0 -0
- {npcsh-1.1.13.data → npcsh-1.1.15.data}/data/npcsh/npc_team/alicanto.png +0 -0
- {npcsh-1.1.13.data → npcsh-1.1.15.data}/data/npcsh/npc_team/chat.jinx +0 -0
- {npcsh-1.1.13.data → npcsh-1.1.15.data}/data/npcsh/npc_team/cmd.jinx +0 -0
- {npcsh-1.1.13.data → npcsh-1.1.15.data}/data/npcsh/npc_team/compile.jinx +0 -0
- {npcsh-1.1.13.data → npcsh-1.1.15.data}/data/npcsh/npc_team/compress.jinx +0 -0
- {npcsh-1.1.13.data → npcsh-1.1.15.data}/data/npcsh/npc_team/corca.png +0 -0
- {npcsh-1.1.13.data → npcsh-1.1.15.data}/data/npcsh/npc_team/corca_example.png +0 -0
- {npcsh-1.1.13.data → npcsh-1.1.15.data}/data/npcsh/npc_team/frederic4.png +0 -0
- {npcsh-1.1.13.data → npcsh-1.1.15.data}/data/npcsh/npc_team/guac.png +0 -0
- {npcsh-1.1.13.data → npcsh-1.1.15.data}/data/npcsh/npc_team/help.jinx +0 -0
- {npcsh-1.1.13.data → npcsh-1.1.15.data}/data/npcsh/npc_team/init.jinx +0 -0
- {npcsh-1.1.13.data → npcsh-1.1.15.data}/data/npcsh/npc_team/kadiefa.png +0 -0
- {npcsh-1.1.13.data → npcsh-1.1.15.data}/data/npcsh/npc_team/npc-studio.jinx +0 -0
- {npcsh-1.1.13.data → npcsh-1.1.15.data}/data/npcsh/npc_team/npcsh.ctx +0 -0
- {npcsh-1.1.13.data → npcsh-1.1.15.data}/data/npcsh/npc_team/npcsh_sibiji.png +0 -0
- {npcsh-1.1.13.data → npcsh-1.1.15.data}/data/npcsh/npc_team/ots.jinx +0 -0
- {npcsh-1.1.13.data → npcsh-1.1.15.data}/data/npcsh/npc_team/plonk.png +0 -0
- {npcsh-1.1.13.data → npcsh-1.1.15.data}/data/npcsh/npc_team/plonkjr.png +0 -0
- {npcsh-1.1.13.data → npcsh-1.1.15.data}/data/npcsh/npc_team/python.jinx +0 -0
- {npcsh-1.1.13.data → npcsh-1.1.15.data}/data/npcsh/npc_team/roll.jinx +0 -0
- {npcsh-1.1.13.data → npcsh-1.1.15.data}/data/npcsh/npc_team/sample.jinx +0 -0
- {npcsh-1.1.13.data → npcsh-1.1.15.data}/data/npcsh/npc_team/serve.jinx +0 -0
- {npcsh-1.1.13.data → npcsh-1.1.15.data}/data/npcsh/npc_team/set.jinx +0 -0
- {npcsh-1.1.13.data → npcsh-1.1.15.data}/data/npcsh/npc_team/sibiji.png +0 -0
- {npcsh-1.1.13.data → npcsh-1.1.15.data}/data/npcsh/npc_team/sleep.jinx +0 -0
- {npcsh-1.1.13.data → npcsh-1.1.15.data}/data/npcsh/npc_team/spool.jinx +0 -0
- {npcsh-1.1.13.data → npcsh-1.1.15.data}/data/npcsh/npc_team/spool.png +0 -0
- {npcsh-1.1.13.data → npcsh-1.1.15.data}/data/npcsh/npc_team/trigger.jinx +0 -0
- {npcsh-1.1.13.data → npcsh-1.1.15.data}/data/npcsh/npc_team/usage.jinx +0 -0
- {npcsh-1.1.13.data → npcsh-1.1.15.data}/data/npcsh/npc_team/yap.jinx +0 -0
- {npcsh-1.1.13.data → npcsh-1.1.15.data}/data/npcsh/npc_team/yap.png +0 -0
- {npcsh-1.1.13.dist-info → npcsh-1.1.15.dist-info}/WHEEL +0 -0
- {npcsh-1.1.13.dist-info → npcsh-1.1.15.dist-info}/licenses/LICENSE +0 -0
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
jinx_name: teamviz
|
|
2
|
+
description: "Visualize NPC team structure - NPCs, jinxs, and their relationships"
|
|
3
|
+
inputs:
|
|
4
|
+
- team_path: ""
|
|
5
|
+
- save: ""
|
|
6
|
+
|
|
7
|
+
steps:
|
|
8
|
+
- name: visualize_team
|
|
9
|
+
engine: python
|
|
10
|
+
code: |
|
|
11
|
+
import os
|
|
12
|
+
import yaml
|
|
13
|
+
from pathlib import Path
|
|
14
|
+
import matplotlib.pyplot as plt
|
|
15
|
+
import networkx as nx
|
|
16
|
+
from collections import defaultdict
|
|
17
|
+
|
|
18
|
+
team_path = context.get('team_path') or ''
|
|
19
|
+
save_path = context.get('save') or ''
|
|
20
|
+
|
|
21
|
+
# Find team path
|
|
22
|
+
if not team_path:
|
|
23
|
+
if os.path.exists('./npc_team'):
|
|
24
|
+
team_path = './npc_team'
|
|
25
|
+
elif os.path.exists(os.path.expanduser('~/.npcsh/npc_team')):
|
|
26
|
+
team_path = os.path.expanduser('~/.npcsh/npc_team')
|
|
27
|
+
else:
|
|
28
|
+
output = "No npc_team found. Specify team_path."
|
|
29
|
+
exit()
|
|
30
|
+
|
|
31
|
+
team_path = Path(team_path)
|
|
32
|
+
|
|
33
|
+
# Load NPCs
|
|
34
|
+
npcs = {}
|
|
35
|
+
for npc_file in team_path.glob("*.npc"):
|
|
36
|
+
try:
|
|
37
|
+
with open(npc_file, 'r') as f:
|
|
38
|
+
data = yaml.safe_load(f)
|
|
39
|
+
npcs[npc_file.stem] = {
|
|
40
|
+
'jinxs': data.get('jinxs', []),
|
|
41
|
+
'directive': data.get('primary_directive', '')[:100],
|
|
42
|
+
'model': data.get('model', ''),
|
|
43
|
+
}
|
|
44
|
+
except Exception as e:
|
|
45
|
+
print("Error loading {}: {}".format(npc_file, e))
|
|
46
|
+
|
|
47
|
+
# Load team context
|
|
48
|
+
team_name = "NPC Team"
|
|
49
|
+
forenpc = None
|
|
50
|
+
for ctx_file in team_path.glob("*.ctx"):
|
|
51
|
+
try:
|
|
52
|
+
with open(ctx_file, 'r') as f:
|
|
53
|
+
ctx = yaml.safe_load(f)
|
|
54
|
+
team_name = ctx.get('name', team_name)
|
|
55
|
+
forenpc = ctx.get('forenpc')
|
|
56
|
+
except:
|
|
57
|
+
pass
|
|
58
|
+
|
|
59
|
+
# Discover all jinxs
|
|
60
|
+
jinxs_dir = team_path / "jinxs"
|
|
61
|
+
all_jinxs = {}
|
|
62
|
+
if jinxs_dir.exists():
|
|
63
|
+
for jinx_file in jinxs_dir.rglob("*.jinx"):
|
|
64
|
+
rel_path = jinx_file.relative_to(jinxs_dir)
|
|
65
|
+
jinx_name = jinx_file.stem
|
|
66
|
+
# Categorize by location
|
|
67
|
+
if 'bin' in str(rel_path):
|
|
68
|
+
category = 'bin'
|
|
69
|
+
elif 'lib' in str(rel_path):
|
|
70
|
+
parts = str(rel_path).split(os.sep)
|
|
71
|
+
category = parts[1] if len(parts) > 1 else 'lib'
|
|
72
|
+
else:
|
|
73
|
+
category = 'other'
|
|
74
|
+
all_jinxs[jinx_name] = {'path': str(rel_path), 'category': category}
|
|
75
|
+
|
|
76
|
+
# Build graph
|
|
77
|
+
G = nx.DiGraph()
|
|
78
|
+
|
|
79
|
+
# Add team node
|
|
80
|
+
G.add_node(team_name, type='team', size=3000)
|
|
81
|
+
|
|
82
|
+
# Add NPC nodes
|
|
83
|
+
for npc_name, npc_data in npcs.items():
|
|
84
|
+
node_type = 'forenpc' if npc_name == forenpc else 'npc'
|
|
85
|
+
G.add_node(npc_name, type=node_type, size=2000)
|
|
86
|
+
G.add_edge(team_name, npc_name, relation='has_npc')
|
|
87
|
+
|
|
88
|
+
# Track jinx usage
|
|
89
|
+
jinx_users = defaultdict(list)
|
|
90
|
+
npc_jinx_patterns = {}
|
|
91
|
+
|
|
92
|
+
for npc_name, npc_data in npcs.items():
|
|
93
|
+
patterns = npc_data.get('jinxs', [])
|
|
94
|
+
npc_jinx_patterns[npc_name] = patterns
|
|
95
|
+
|
|
96
|
+
# Resolve patterns to actual jinxs
|
|
97
|
+
for pattern in patterns:
|
|
98
|
+
pattern = str(pattern)
|
|
99
|
+
if '*' in pattern:
|
|
100
|
+
# Glob pattern like lib/browser/*
|
|
101
|
+
base = pattern.replace('/*', '').replace('*', '')
|
|
102
|
+
for jname, jdata in all_jinxs.items():
|
|
103
|
+
if base in jdata['path']:
|
|
104
|
+
jinx_users[jname].append(npc_name)
|
|
105
|
+
else:
|
|
106
|
+
# Exact match
|
|
107
|
+
jname = pattern.split('/')[-1]
|
|
108
|
+
if jname in all_jinxs:
|
|
109
|
+
jinx_users[jname].append(npc_name)
|
|
110
|
+
|
|
111
|
+
# Add jinx nodes (only ones that are used)
|
|
112
|
+
jinx_categories = defaultdict(list)
|
|
113
|
+
for jname, users in jinx_users.items():
|
|
114
|
+
if jname in all_jinxs:
|
|
115
|
+
cat = all_jinxs[jname]['category']
|
|
116
|
+
jinx_categories[cat].append(jname)
|
|
117
|
+
|
|
118
|
+
# Size based on usage
|
|
119
|
+
size = 500 + len(users) * 200
|
|
120
|
+
G.add_node(jname, type='jinx', category=cat, size=size, users=len(users))
|
|
121
|
+
|
|
122
|
+
for user in users:
|
|
123
|
+
G.add_edge(user, jname, relation='uses')
|
|
124
|
+
|
|
125
|
+
# Check for delegation relationships
|
|
126
|
+
for npc_name, npc_data in npcs.items():
|
|
127
|
+
directive = npc_data.get('directive', '').lower()
|
|
128
|
+
for other_npc in npcs.keys():
|
|
129
|
+
if other_npc != npc_name and other_npc in directive:
|
|
130
|
+
if 'delegate' in directive:
|
|
131
|
+
G.add_edge(npc_name, other_npc, relation='delegates_to')
|
|
132
|
+
|
|
133
|
+
# Create visualization
|
|
134
|
+
fig, ax = plt.subplots(1, 1, figsize=(16, 12))
|
|
135
|
+
|
|
136
|
+
# Layout
|
|
137
|
+
pos = nx.spring_layout(G, k=2, iterations=50, seed=42)
|
|
138
|
+
|
|
139
|
+
# Color map
|
|
140
|
+
color_map = {
|
|
141
|
+
'team': '#FF6B6B',
|
|
142
|
+
'forenpc': '#4ECDC4',
|
|
143
|
+
'npc': '#45B7D1',
|
|
144
|
+
'jinx': '#96CEB4',
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
# Draw by type
|
|
148
|
+
for node_type in ['team', 'forenpc', 'npc', 'jinx']:
|
|
149
|
+
nodes = [n for n, d in G.nodes(data=True) if d.get('type') == node_type]
|
|
150
|
+
sizes = [G.nodes[n].get('size', 1000) for n in nodes]
|
|
151
|
+
nx.draw_networkx_nodes(G, pos, nodelist=nodes, node_color=color_map[node_type],
|
|
152
|
+
node_size=sizes, alpha=0.9, ax=ax)
|
|
153
|
+
|
|
154
|
+
# Draw edges by type
|
|
155
|
+
delegate_edges = [(u, v) for u, v, d in G.edges(data=True) if d.get('relation') == 'delegates_to']
|
|
156
|
+
use_edges = [(u, v) for u, v, d in G.edges(data=True) if d.get('relation') == 'uses']
|
|
157
|
+
has_edges = [(u, v) for u, v, d in G.edges(data=True) if d.get('relation') == 'has_npc']
|
|
158
|
+
|
|
159
|
+
nx.draw_networkx_edges(G, pos, edgelist=has_edges, edge_color='#999', alpha=0.5, ax=ax)
|
|
160
|
+
nx.draw_networkx_edges(G, pos, edgelist=use_edges, edge_color='#96CEB4', alpha=0.3, ax=ax)
|
|
161
|
+
nx.draw_networkx_edges(G, pos, edgelist=delegate_edges, edge_color='#FF6B6B',
|
|
162
|
+
width=2, style='dashed', ax=ax, arrows=True, arrowsize=20)
|
|
163
|
+
|
|
164
|
+
# Labels
|
|
165
|
+
nx.draw_networkx_labels(G, pos, font_size=8, font_weight='bold', ax=ax)
|
|
166
|
+
|
|
167
|
+
# Legend
|
|
168
|
+
legend_elements = [
|
|
169
|
+
plt.scatter([], [], c=color_map['team'], s=200, label='Team'),
|
|
170
|
+
plt.scatter([], [], c=color_map['forenpc'], s=200, label='ForeNPC'),
|
|
171
|
+
plt.scatter([], [], c=color_map['npc'], s=200, label='NPC'),
|
|
172
|
+
plt.scatter([], [], c=color_map['jinx'], s=200, label='Jinx'),
|
|
173
|
+
]
|
|
174
|
+
ax.legend(handles=legend_elements, loc='upper left')
|
|
175
|
+
|
|
176
|
+
ax.set_title(team_name + " - Team Structure", fontsize=14, fontweight='bold')
|
|
177
|
+
ax.axis('off')
|
|
178
|
+
|
|
179
|
+
plt.tight_layout()
|
|
180
|
+
|
|
181
|
+
if save_path:
|
|
182
|
+
plt.savefig(save_path, dpi=150, bbox_inches='tight')
|
|
183
|
+
plt.close()
|
|
184
|
+
output = "Saved to " + save_path
|
|
185
|
+
else:
|
|
186
|
+
plt.show()
|
|
187
|
+
output = "Displayed team visualization"
|
|
188
|
+
|
|
189
|
+
# Text summary
|
|
190
|
+
summary = "\n\n--- Team Summary ---\n"
|
|
191
|
+
summary += "Team: {}\n".format(team_name)
|
|
192
|
+
summary += "ForeNPC: {}\n".format(forenpc or 'None')
|
|
193
|
+
summary += "NPCs ({}): {}\n".format(len(npcs), ', '.join(sorted(npcs.keys())))
|
|
194
|
+
summary += "Total Jinxs: {}\n".format(len(all_jinxs))
|
|
195
|
+
summary += "\nJinxs by category:\n"
|
|
196
|
+
for cat, jlist in sorted(jinx_categories.items()):
|
|
197
|
+
summary += " {}: {} jinxs\n".format(cat, len(jlist))
|
|
198
|
+
|
|
199
|
+
summary += "\nMost shared jinxs:\n"
|
|
200
|
+
shared = sorted(jinx_users.items(), key=lambda x: len(x[1]), reverse=True)[:10]
|
|
201
|
+
for jname, users in shared:
|
|
202
|
+
if len(users) > 1:
|
|
203
|
+
summary += " {} (used by {}): {}\n".format(jname, len(users), ', '.join(users))
|
|
204
|
+
|
|
205
|
+
output = output + summary
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
jinx_name: "verbose"
|
|
2
|
+
description: "Enable verbose logging - shows debug output from npcpy"
|
|
3
|
+
inputs: []
|
|
4
|
+
steps:
|
|
5
|
+
- name: "set_verbose"
|
|
6
|
+
engine: "python"
|
|
7
|
+
code: |
|
|
8
|
+
state = context.get('state')
|
|
9
|
+
output_messages = context.get('messages', [])
|
|
10
|
+
|
|
11
|
+
if state:
|
|
12
|
+
result = state.set_log_level("verbose")
|
|
13
|
+
context['output'] = result
|
|
14
|
+
else:
|
|
15
|
+
context['output'] = "Error: state not available"
|
|
16
|
+
|
|
17
|
+
context['messages'] = output_messages
|
npcsh/npc_team/kadiefa.npc
CHANGED
|
@@ -1,3 +1,21 @@
|
|
|
1
1
|
name: kadiefa
|
|
2
|
+
ascii_art: |
|
|
3
|
+
🏔️🏔️🏔️🏔️🏔️🏔️🏔️🏔️🏔️🏔️🏔️🏔️🏔️🏔️🏔️🏔️🏔️🏔️🏔️🏔️🏔️🏔️🏔️🏔️🏔️🏔️🏔️🏔️🏔️🏔️🏔️🏔️🏔️🏔️🏔️🏔️🏔️🏔️🏔️🏔️🏔️🏔️
|
|
4
|
+
██ ██ █████ ██████ ██ ███████ ███████ █████
|
|
5
|
+
██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
|
|
6
|
+
█████ ███████ ██ ██ ██ █████ █████ ███████ 🐆
|
|
7
|
+
██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
|
|
8
|
+
██ ██ ██ ██ ██████ ██ ███████ ██ ██ ██
|
|
9
|
+
🏔️🏔️🏔️🏔️🏔️🏔️🏔️🏔️🏔️🏔️🏔️🏔️🏔️🏔️🏔️🏔️🏔️🏔️🏔️🏔️🏔️🏔️🏔️🏔️🏔️🏔️🏔️🏔️🏔️🏔️🏔️🏔️🏔️🏔️🏔️🏔️🏔️🏔️🏔️🏔️🏔️🏔️
|
|
10
|
+
colors:
|
|
11
|
+
top: "255,255,255"
|
|
12
|
+
bottom: "173,216,230"
|
|
2
13
|
primary_directive: |
|
|
3
|
-
|
|
14
|
+
You are kadiefa, the exploratory snow leopard. You love to find new paths and to explore hidden gems.
|
|
15
|
+
You go into caverns no cat has ventured into before. You climb peaks that others call crazy.
|
|
16
|
+
Your role is to lead users on wandering explorations - deep research journeys that follow threads
|
|
17
|
+
wherever they lead. You help users explore complex research questions and think outside the box.
|
|
18
|
+
Use web search and browsing tools to discover new information.
|
|
19
|
+
jinxs:
|
|
20
|
+
- lib/core/python
|
|
21
|
+
- bin/wander
|
npcsh/npc_team/plonk.npc
CHANGED
|
@@ -1,2 +1,27 @@
|
|
|
1
1
|
name: plonk
|
|
2
|
-
|
|
2
|
+
ascii_art: |
|
|
3
|
+
🌲🌲🌲🌲🌲🌲🌲🌲🌲🌲🌲🌲🌲🌲🌲🌲🌲🌲🌲🌲🌲🌲🌲🌲🌲🌲🌲🌲🌲🌲🌲🌲🌲🌲🌲
|
|
4
|
+
██████ ██ ██████ ███ ██ ██ ██
|
|
5
|
+
██ ██ ██ ██ ██ ████ ██ ██ ██
|
|
6
|
+
██████ ██ 🪵 ██ ██ ██ ██ ██ █████
|
|
7
|
+
██ ██ ██ ██ ██ ██ ██ ██ ██
|
|
8
|
+
██ ███████ ██████ ██ ████ ██ ██
|
|
9
|
+
🌲🌲🌲🌲🌲🌲🌲🌲🌲🌲🌲🌲🌲🌲🌲🌲🌲🌲🌲🌲🌲🌲🌲🌲🌲🌲🌲🌲🌲🌲🌲🌲🌲🌲🌲
|
|
10
|
+
colors:
|
|
11
|
+
top: "34,139,34"
|
|
12
|
+
bottom: "139,69,19"
|
|
13
|
+
primary_directive: |
|
|
14
|
+
You are plonk, the browser and GUI automation specialist.
|
|
15
|
+
|
|
16
|
+
Browser tools: open_browser, browser_action, browser_screenshot, close_browser
|
|
17
|
+
|
|
18
|
+
browser_action actions: click, type, type_and_enter, select, wait, scroll, get_text, get_page, get_elements, press_key
|
|
19
|
+
|
|
20
|
+
Use get_elements to discover selectors on the page. Use xpath:// prefix for XPath selectors.
|
|
21
|
+
|
|
22
|
+
Desktop tools: screenshot, click, type_text, key_press, launch_app, wait
|
|
23
|
+
jinxs:
|
|
24
|
+
- lib/browser/*
|
|
25
|
+
- lib/computer_use/*
|
|
26
|
+
- lib/core/sh
|
|
27
|
+
- lib/core/python
|
npcsh/npc_team/plonkjr.npc
CHANGED
|
@@ -1,2 +1,23 @@
|
|
|
1
1
|
name: plonkjr
|
|
2
|
-
|
|
2
|
+
ascii_art: |
|
|
3
|
+
🌲🌲🌲🌲🌲🌲🌲🌲🌲🌲🌲🌲🌲🌲🌲🌲🌲🌲🌲🌲🌲🌲🌲🌲🌲🌲🌲🌲🌲🌲🌲🌲🌲🌲🌲🌲🌲🌲🌲🌲🌲🌲🌲🌲🌲🌲🌲🌲🌲🌲
|
|
4
|
+
██████ ██ ██████ ███ ██ ██ ██ 🪵 ██ ██████
|
|
5
|
+
██ ██ ██ ██ ██ ████ ██ ██ ██ ██ ██ ██
|
|
6
|
+
██████ ██ ██ ██ ██ ██ ██ █████ 🌲 ██ ██████
|
|
7
|
+
██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
|
|
8
|
+
██ ███████ ██████ ██ ████ ██ ██ █████ ██ ██
|
|
9
|
+
🌲🌲🌲🌲🌲🌲🌲🌲🌲🌲🌲🌲🌲🌲🌲🌲🌲🌲🌲🌲🌲🌲🌲🌲🌲🌲🌲🌲🌲🌲🌲🌲🌲🌲🌲🌲🌲🌲🌲🌲🌲🌲🌲🌲🌲🌲🌲🌲🌲🌲
|
|
10
|
+
colors:
|
|
11
|
+
top: "34,139,34"
|
|
12
|
+
bottom: "139,69,19"
|
|
13
|
+
primary_directive: |
|
|
14
|
+
You are plonkjr, the junior automation assistant. You handle basic computer use tasks:
|
|
15
|
+
taking screenshots, clicking, typing, and pressing keys. You're simpler than plonk -
|
|
16
|
+
you focus on executing the core actions without complex vision-based planning.
|
|
17
|
+
Just do what's asked: screenshot, click, type, key press.
|
|
18
|
+
jinxs:
|
|
19
|
+
- lib/computer_use/screenshot
|
|
20
|
+
- lib/computer_use/click
|
|
21
|
+
- lib/computer_use/type_text
|
|
22
|
+
- lib/computer_use/key_press
|
|
23
|
+
- lib/core/sh
|
npcsh/npc_team/sibiji.npc
CHANGED
|
@@ -1,3 +1,24 @@
|
|
|
1
1
|
name: sibiji
|
|
2
|
-
|
|
3
|
-
|
|
2
|
+
ascii_art: |
|
|
3
|
+
🕸️🕸️🕸️🕸️🕸️🕸️🕸️🕸️🕸️🕸️🕸️🕸️🕸️🕸️🕸️🕸️🕸️🕸️🕸️🕸️🕸️🕸️🕸️🕸️🕸️🕸️🕸️🕸️🕸️🕸️🕸️🕸️🕸️🕸️🕸️🕸️🕸️🕸️🕸️
|
|
4
|
+
██████ ██ ██████ ██ ██ ██████ ██
|
|
5
|
+
██ ██ ██ ██ ██ ██ ██ ██
|
|
6
|
+
█████ ██ ██████ ██ 🕷️ ██ ██ ██
|
|
7
|
+
██ ██ ██ ██ ██ ██ 🕷️ ██ ██
|
|
8
|
+
██████ ██ ██████ ██ ██ ██████ ██
|
|
9
|
+
🕸️🕸️🕸️🕸️🕸️🕸️🕸️🕸️🕸️🕸️🕸️🕸️🕸️🕸️🕸️🕸️🕸️🕸️🕸️🕸️🕸️🕸️🕸️🕸️🕸️🕸️🕸️🕸️🕸️🕸️🕸️🕸️🕸️🕸️🕸️🕸️🕸️🕸️🕸️
|
|
10
|
+
colors:
|
|
11
|
+
top: "148,0,211"
|
|
12
|
+
bottom: "75,0,130"
|
|
13
|
+
primary_directive: |
|
|
14
|
+
You are sibiji, the orchestrator and general manager of the NPC team.
|
|
15
|
+
Your role is to delegate tasks to appropriate specialist agents based on their expertise.
|
|
16
|
+
|
|
17
|
+
When delegating, match the task to the agent whose primary_directive best fits.
|
|
18
|
+
You have access to the delegate tool to pass tasks to other agents.
|
|
19
|
+
jinxs:
|
|
20
|
+
- lib/orchestration/delegate
|
|
21
|
+
- lib/orchestration/convene
|
|
22
|
+
- lib/core/sh
|
|
23
|
+
- lib/core/python
|
|
24
|
+
- lib/core/search
|
npcsh/npcsh.py
CHANGED
|
@@ -2,6 +2,10 @@ import os
|
|
|
2
2
|
import sys
|
|
3
3
|
import argparse
|
|
4
4
|
import importlib.metadata
|
|
5
|
+
import warnings
|
|
6
|
+
|
|
7
|
+
# Suppress pydantic serialization warnings from litellm
|
|
8
|
+
warnings.filterwarnings("ignore", message="Pydantic serializer warnings")
|
|
5
9
|
|
|
6
10
|
import platform
|
|
7
11
|
try:
|
|
@@ -71,41 +75,99 @@ def display_usage(state: ShellState):
|
|
|
71
75
|
print(colored("─────────────────────────────\n", "cyan"))
|
|
72
76
|
|
|
73
77
|
|
|
74
|
-
def
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
___________________________________________
|
|
80
|
-
|
|
81
|
-
Welcome to \033[1;94mnpc\033[0m\033[1;38;5;202msh\033[0m!
|
|
82
|
-
\033[1;94m \033[0m\033[1;38;5;202m _ \\\\
|
|
83
|
-
\033[1;94m _ __ _ __ ___ \033[0m\033[1;38;5;202m ___ | |___ \\\\
|
|
84
|
-
\033[1;94m| '_ \\ | '_ \\ / __|\033[0m\033[1;38;5;202m / __/ | |_ _| \\\\
|
|
85
|
-
\033[1;94m| | | || |_) |( |__ \033[0m\033[1;38;5;202m \\_ \\ | | | | //
|
|
86
|
-
\033[1;94m|_| |_|| .__/ \\___|\033[0m\033[1;38;5;202m |___/ |_| |_| //
|
|
87
|
-
\033[1;94m|🤖| \033[0m\033[1;38;5;202m //
|
|
88
|
-
\033[1;94m|🤖|
|
|
89
|
-
\033[1;94m|🤖|
|
|
90
|
-
___________________________________________
|
|
91
|
-
___________________________________________
|
|
92
|
-
___________________________________________
|
|
78
|
+
def print_welcome_art(npc=None):
|
|
79
|
+
"""Print welcome art - from NPC if available, otherwise default npcsh art."""
|
|
80
|
+
BLUE = "\033[1;94m"
|
|
81
|
+
RUST = "\033[1;38;5;202m"
|
|
82
|
+
RESET = "\033[0m"
|
|
93
83
|
|
|
94
|
-
|
|
84
|
+
# If NPC has ascii_art, display it with colors
|
|
85
|
+
if npc and hasattr(npc, 'ascii_art') and npc.ascii_art:
|
|
86
|
+
art = npc.ascii_art
|
|
87
|
+
colors = getattr(npc, 'colors', {}) or {}
|
|
88
|
+
|
|
89
|
+
if colors:
|
|
90
|
+
top = colors.get("top", "255,255,255")
|
|
91
|
+
bottom = colors.get("bottom", "255,255,255")
|
|
92
|
+
lines = art.strip().split("\n")
|
|
93
|
+
mid = len(lines) // 2
|
|
94
|
+
|
|
95
|
+
try:
|
|
96
|
+
tr, tg, tb = map(int, top.split(","))
|
|
97
|
+
br, bg, bb = map(int, bottom.split(","))
|
|
98
|
+
except:
|
|
99
|
+
tr, tg, tb = 255, 255, 255
|
|
100
|
+
br, bg, bb = 255, 255, 255
|
|
101
|
+
|
|
102
|
+
for i, line in enumerate(lines):
|
|
103
|
+
if i < mid:
|
|
104
|
+
print(f"\033[38;2;{tr};{tg};{tb}m{line}\033[0m")
|
|
105
|
+
else:
|
|
106
|
+
print(f"\033[38;2;{br};{bg};{bb}m{line}\033[0m")
|
|
107
|
+
else:
|
|
108
|
+
print(art)
|
|
109
|
+
print()
|
|
110
|
+
return
|
|
111
|
+
|
|
112
|
+
# Default npcsh art
|
|
113
|
+
print(f"""
|
|
114
|
+
{BLUE}___________________________________________{RESET}
|
|
115
|
+
|
|
116
|
+
Welcome to {BLUE}npc{RESET}{RUST}sh{RESET}!
|
|
117
|
+
{BLUE} {RESET}{RUST} _ \\\\{RESET}
|
|
118
|
+
{BLUE} _ __ _ __ ___ {RESET}{RUST} ___ | |___ \\\\{RESET}
|
|
119
|
+
{BLUE}| '_ \\ | '_ \\ / __|{RESET}{RUST} / __/ | |_ _| \\\\{RESET}
|
|
120
|
+
{BLUE}| | | || |_) |( |__ {RESET}{RUST} \\_ \\ | | | | //{RESET}
|
|
121
|
+
{BLUE}|_| |_|| .__/ \\___/{RESET}{RUST} |___/ |_| |_| //{RESET}
|
|
122
|
+
{BLUE}|🤖| {RESET}{RUST} //{RESET}
|
|
123
|
+
{BLUE}|🤖|{RESET}
|
|
124
|
+
{BLUE}|🤖|{RESET}
|
|
125
|
+
{RUST}___________________________________________{RESET}
|
|
95
126
|
|
|
96
|
-
|
|
97
|
-
|
|
127
|
+
Begin by asking a question, issuing a bash command, or typing '/help' for more information.
|
|
128
|
+
""")
|
|
98
129
|
|
|
99
130
|
|
|
100
|
-
def run_repl(command_history: CommandHistory, initial_state: ShellState, router):
|
|
131
|
+
def run_repl(command_history: CommandHistory, initial_state: ShellState, router, launched_agent: str = None, launched_jinx: str = None):
|
|
101
132
|
state = initial_state
|
|
102
|
-
|
|
103
|
-
print_welcome_message()
|
|
104
133
|
|
|
105
|
-
|
|
134
|
+
# Print welcome art - NPC art if launched with an agent, otherwise default
|
|
135
|
+
if not launched_jinx:
|
|
136
|
+
print_welcome_art(state.npc if launched_agent else None)
|
|
137
|
+
|
|
138
|
+
# If launched with a jinx mode, auto-execute that jinx
|
|
139
|
+
if launched_jinx:
|
|
140
|
+
state, output = execute_command(f"/{launched_jinx}", state, router=router, command_history=command_history)
|
|
141
|
+
process_result(f"/{launched_jinx}", state, output, command_history)
|
|
142
|
+
else:
|
|
143
|
+
render_markdown(f'- Using {state.current_mode} mode. Use /agent, /cmd, or /chat to switch to other modes')
|
|
106
144
|
render_markdown(f'- To switch to a different NPC, type /npc <npc_name> or /n <npc_name> to switch to that NPC.')
|
|
107
145
|
render_markdown('\n- Here are the current NPCs available in your team: ' + ', '.join([npc_name for npc_name in state.team.npcs.keys()]))
|
|
108
|
-
|
|
146
|
+
# Show jinxs organized by folder using _source_path from jinx objects
|
|
147
|
+
jinxs_by_folder = {}
|
|
148
|
+
if hasattr(state.team, 'jinxs_dict'):
|
|
149
|
+
for jinx_name, jinx_obj in state.team.jinxs_dict.items():
|
|
150
|
+
folder = 'other'
|
|
151
|
+
if hasattr(jinx_obj, '_source_path') and jinx_obj._source_path:
|
|
152
|
+
parts = jinx_obj._source_path.split(os.sep)
|
|
153
|
+
if 'jinxs' in parts:
|
|
154
|
+
idx = parts.index('jinxs')
|
|
155
|
+
if idx + 1 < len(parts) - 1:
|
|
156
|
+
folder = parts[idx + 1]
|
|
157
|
+
else:
|
|
158
|
+
folder = 'root'
|
|
159
|
+
if folder not in jinxs_by_folder:
|
|
160
|
+
jinxs_by_folder[folder] = []
|
|
161
|
+
jinxs_by_folder[folder].append(jinx_name)
|
|
162
|
+
|
|
163
|
+
if jinxs_by_folder:
|
|
164
|
+
folder_order = ['bin', 'lib', 'npc_studio', 'root', 'other']
|
|
165
|
+
sorted_folders = sorted(jinxs_by_folder.keys(), key=lambda x: (folder_order.index(x) if x in folder_order else 99, x))
|
|
166
|
+
jinx_summary = []
|
|
167
|
+
for folder in sorted_folders:
|
|
168
|
+
count = len(jinxs_by_folder[folder])
|
|
169
|
+
jinx_summary.append(f"{folder}/ ({count})")
|
|
170
|
+
render_markdown('\n- Available Jinxs: ' + ', '.join(jinx_summary) + ' — use `/jinxs` for details')
|
|
109
171
|
|
|
110
172
|
is_windows = platform.system().lower().startswith("win")
|
|
111
173
|
try:
|
|
@@ -208,6 +270,19 @@ def run_repl(command_history: CommandHistory, initial_state: ShellState, router)
|
|
|
208
270
|
usage_str = f"📊 {state.session_input_tokens:,} in / {state.session_output_tokens:,} out"
|
|
209
271
|
if not is_local and state.session_cost_usd > 0:
|
|
210
272
|
usage_str += f" | ${state.session_cost_usd:.4f}"
|
|
273
|
+
# Add elapsed time
|
|
274
|
+
import time
|
|
275
|
+
elapsed = time.time() - state.session_start_time
|
|
276
|
+
if elapsed >= 3600:
|
|
277
|
+
hours = int(elapsed // 3600)
|
|
278
|
+
mins = int((elapsed % 3600) // 60)
|
|
279
|
+
usage_str += f" | {hours}h{mins}m"
|
|
280
|
+
elif elapsed >= 60:
|
|
281
|
+
mins = int(elapsed // 60)
|
|
282
|
+
secs = int(elapsed % 60)
|
|
283
|
+
usage_str += f" | {mins}m{secs}s"
|
|
284
|
+
else:
|
|
285
|
+
usage_str += f" | {int(elapsed)}s"
|
|
211
286
|
token_hint = colored(usage_str, "white", attrs=["dark"])
|
|
212
287
|
else:
|
|
213
288
|
token_hint = ""
|
|
@@ -268,10 +343,8 @@ def run_repl(command_history: CommandHistory, initial_state: ShellState, router)
|
|
|
268
343
|
)
|
|
269
344
|
|
|
270
345
|
except KeyboardInterrupt:
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
exit_shell(state)
|
|
274
|
-
continue
|
|
346
|
+
# Double Ctrl+C exits (handled in _input_with_hint_below)
|
|
347
|
+
exit_shell(state)
|
|
275
348
|
|
|
276
349
|
except EOFError:
|
|
277
350
|
exit_shell(state)
|
|
@@ -282,9 +355,22 @@ def run_repl(command_history: CommandHistory, initial_state: ShellState, router)
|
|
|
282
355
|
raise
|
|
283
356
|
|
|
284
357
|
|
|
285
|
-
def main() -> None:
|
|
358
|
+
def main(npc_name: str = None) -> None:
|
|
359
|
+
"""
|
|
360
|
+
Main entry point for npcsh.
|
|
361
|
+
|
|
362
|
+
Args:
|
|
363
|
+
npc_name: If provided, start with this NPC active. Used by agent-specific
|
|
364
|
+
entry points (guac, plonk, corca, etc.)
|
|
365
|
+
"""
|
|
286
366
|
from npcsh.routes import router
|
|
287
|
-
|
|
367
|
+
|
|
368
|
+
# If no npc_name provided, check how we were invoked
|
|
369
|
+
if npc_name is None:
|
|
370
|
+
invoked_as = os.path.basename(sys.argv[0])
|
|
371
|
+
if invoked_as not in ('npcsh', 'npc'):
|
|
372
|
+
npc_name = invoked_as
|
|
373
|
+
|
|
288
374
|
parser = argparse.ArgumentParser(description="npcsh - An NPC-powered shell.")
|
|
289
375
|
parser.add_argument(
|
|
290
376
|
"-v", "--version", action="version", version=f"npcsh version {VERSION}"
|
|
@@ -292,28 +378,56 @@ def main() -> None:
|
|
|
292
378
|
parser.add_argument(
|
|
293
379
|
"-c", "--command", type=str, help="Execute a single command and exit."
|
|
294
380
|
)
|
|
381
|
+
parser.add_argument(
|
|
382
|
+
"-n", "--npc", type=str, help="Start with a specific NPC active."
|
|
383
|
+
)
|
|
295
384
|
args = parser.parse_args()
|
|
296
385
|
|
|
297
386
|
command_history, team, default_npc = setup_shell()
|
|
298
|
-
|
|
387
|
+
|
|
299
388
|
if team and hasattr(team, 'jinxs_dict'):
|
|
300
389
|
for jinx_name, jinx_obj in team.jinxs_dict.items():
|
|
301
390
|
router.register_jinx(jinx_obj)
|
|
302
391
|
|
|
303
|
-
|
|
304
|
-
|
|
392
|
+
# Determine which NPC to start with
|
|
393
|
+
# Special cases: these are jinxes/modes, not NPCs
|
|
394
|
+
jinx_modes = {"yap", "spool", "wander"}
|
|
395
|
+
target_npc_name = npc_name or args.npc
|
|
396
|
+
|
|
397
|
+
if target_npc_name and target_npc_name.lower() in jinx_modes:
|
|
398
|
+
# It's a jinx mode, use default NPC
|
|
399
|
+
initial_state.npc = default_npc
|
|
400
|
+
elif target_npc_name and team:
|
|
401
|
+
target_npc = team.npcs.get(target_npc_name)
|
|
402
|
+
if target_npc:
|
|
403
|
+
initial_state.npc = target_npc
|
|
404
|
+
else:
|
|
405
|
+
print(f"Warning: NPC '{target_npc_name}' not found. Using default.")
|
|
406
|
+
initial_state.npc = default_npc
|
|
407
|
+
else:
|
|
408
|
+
initial_state.npc = default_npc
|
|
409
|
+
|
|
410
|
+
initial_state.team = team
|
|
305
411
|
if args.command:
|
|
306
412
|
state = initial_state
|
|
307
413
|
state.current_path = os.getcwd()
|
|
308
414
|
final_state, output = execute_command(args.command, state, router=router, command_history=command_history)
|
|
309
415
|
if final_state.stream_output:
|
|
310
|
-
for chunk in output:
|
|
416
|
+
for chunk in output:
|
|
311
417
|
print(str(chunk), end='')
|
|
312
418
|
print()
|
|
313
419
|
elif output is not None:
|
|
314
420
|
print(output)
|
|
315
421
|
else:
|
|
316
|
-
|
|
422
|
+
# Determine if launching an NPC or a jinx mode
|
|
423
|
+
if target_npc_name and target_npc_name.lower() in jinx_modes:
|
|
424
|
+
run_repl(command_history, initial_state, router, launched_jinx=target_npc_name.lower())
|
|
425
|
+
else:
|
|
426
|
+
run_repl(command_history, initial_state, router, launched_agent=npc_name)
|
|
317
427
|
|
|
318
428
|
if __name__ == "__main__":
|
|
319
|
-
|
|
429
|
+
try:
|
|
430
|
+
main()
|
|
431
|
+
except KeyboardInterrupt:
|
|
432
|
+
print() # Clean exit on Ctrl+C without "KeyboardInterrupt" message
|
|
433
|
+
sys.exit(0)
|
npcsh/ui.py
CHANGED
|
@@ -6,6 +6,12 @@ import threading
|
|
|
6
6
|
import time
|
|
7
7
|
from termcolor import colored
|
|
8
8
|
|
|
9
|
+
# Global reference to current active spinner for sub-agent updates
|
|
10
|
+
_current_spinner = None
|
|
11
|
+
|
|
12
|
+
def get_current_spinner():
|
|
13
|
+
"""Get the currently active spinner, if any."""
|
|
14
|
+
return _current_spinner
|
|
9
15
|
|
|
10
16
|
class SpinnerContext:
|
|
11
17
|
"""Context manager for showing a spinner during long operations.
|
|
@@ -46,7 +52,13 @@ class SpinnerContext:
|
|
|
46
52
|
"""Set additional status message."""
|
|
47
53
|
self._status_msg = msg
|
|
48
54
|
|
|
55
|
+
def set_message(self, msg: str):
|
|
56
|
+
"""Update the main spinner message (e.g., when delegating to sub-agent)."""
|
|
57
|
+
self.message = msg
|
|
58
|
+
|
|
49
59
|
def __enter__(self):
|
|
60
|
+
global _current_spinner
|
|
61
|
+
_current_spinner = self
|
|
50
62
|
self._stop = False
|
|
51
63
|
self._interrupted = False
|
|
52
64
|
self._start_time = time.time()
|
|
@@ -58,11 +70,16 @@ class SpinnerContext:
|
|
|
58
70
|
return self
|
|
59
71
|
|
|
60
72
|
def __exit__(self, *args):
|
|
73
|
+
global _current_spinner
|
|
74
|
+
_current_spinner = None
|
|
61
75
|
self._stop = True
|
|
62
76
|
if self._thread:
|
|
63
77
|
self._thread.join(timeout=0.5)
|
|
78
|
+
# Wait for key listener to restore terminal settings
|
|
79
|
+
if self._key_thread:
|
|
80
|
+
self._key_thread.join(timeout=0.5)
|
|
64
81
|
# Clear spinner line
|
|
65
|
-
sys.stdout.write('\r' + ' ' * (len(self.message) +
|
|
82
|
+
sys.stdout.write('\r' + ' ' * (len(self.message) + 60) + '\r')
|
|
66
83
|
sys.stdout.flush()
|
|
67
84
|
# Check if we were interrupted by ESC
|
|
68
85
|
if self._interrupted:
|
|
@@ -74,6 +91,8 @@ class SpinnerContext:
|
|
|
74
91
|
import termios
|
|
75
92
|
import tty
|
|
76
93
|
import select
|
|
94
|
+
import signal
|
|
95
|
+
import os
|
|
77
96
|
|
|
78
97
|
fd = sys.stdin.fileno()
|
|
79
98
|
self._old_settings = termios.tcgetattr(fd)
|
|
@@ -86,6 +105,8 @@ class SpinnerContext:
|
|
|
86
105
|
if ch == '\x1b': # ESC key
|
|
87
106
|
self._interrupted = True
|
|
88
107
|
self._stop = True
|
|
108
|
+
# Send SIGINT to main thread to interrupt blocking calls
|
|
109
|
+
os.kill(os.getpid(), signal.SIGINT)
|
|
89
110
|
break
|
|
90
111
|
finally:
|
|
91
112
|
termios.tcsetattr(fd, termios.TCSADRAIN, self._old_settings)
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
name: alicanto
|
|
2
|
+
ascii_art: |
|
|
3
|
+
✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨
|
|
4
|
+
█████ ██ ██ ██████ █████ ███ ██ ████████ ██████
|
|
5
|
+
██ ██ ██ ██ ██ ██ ██ ████ ██ ██ ██ ██
|
|
6
|
+
███████ ██ ██ ██ ███████ ██ ██ ██ ██ ██ ██ 🦅
|
|
7
|
+
██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
|
|
8
|
+
██ ██ ███████ ██ ██████ ██ ██ ██ ████ ██ ██████
|
|
9
|
+
✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨
|
|
10
|
+
colors:
|
|
11
|
+
top: "255,215,0"
|
|
12
|
+
bottom: "218,165,32"
|
|
13
|
+
primary_directive: |
|
|
14
|
+
You are alicanto, the research and exploration specialist of the NPC team.
|
|
15
|
+
Like the mythical bird, you lead users to discover valuable information.
|
|
16
|
+
Your role is web research, searching, and helping users explore topics.
|
|
17
|
+
Use search tools to find information and present findings clearly.
|
|
18
|
+
jinxs:
|
|
19
|
+
- lib/core/search
|
|
20
|
+
- lib/core/sh
|
|
21
|
+
- lib/core/python
|
|
22
|
+
- lib/core/load_file
|
|
23
|
+
- lib/research/*
|