npcsh 1.1.14__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.
Files changed (168) hide show
  1. npcsh/_state.py +488 -77
  2. npcsh/mcp_server.py +2 -1
  3. npcsh/npc.py +84 -32
  4. npcsh/npc_team/alicanto.npc +22 -1
  5. npcsh/npc_team/corca.npc +28 -9
  6. npcsh/npc_team/frederic.npc +25 -4
  7. npcsh/npc_team/guac.npc +22 -0
  8. npcsh/npc_team/jinxs/bin/nql.jinx +141 -0
  9. npcsh/npc_team/jinxs/bin/sync.jinx +230 -0
  10. {npcsh-1.1.14.data/data/npcsh/npc_team → npcsh/npc_team/jinxs/bin}/vixynt.jinx +8 -30
  11. npcsh/npc_team/jinxs/bin/wander.jinx +152 -0
  12. npcsh/npc_team/jinxs/lib/browser/browser_action.jinx +220 -0
  13. npcsh/npc_team/jinxs/lib/browser/browser_screenshot.jinx +40 -0
  14. npcsh/npc_team/jinxs/lib/browser/close_browser.jinx +14 -0
  15. npcsh/npc_team/jinxs/lib/browser/open_browser.jinx +43 -0
  16. npcsh/npc_team/jinxs/lib/computer_use/click.jinx +23 -0
  17. npcsh/npc_team/jinxs/lib/computer_use/key_press.jinx +26 -0
  18. npcsh/npc_team/jinxs/lib/computer_use/launch_app.jinx +37 -0
  19. npcsh/npc_team/jinxs/lib/computer_use/screenshot.jinx +23 -0
  20. npcsh/npc_team/jinxs/lib/computer_use/type_text.jinx +27 -0
  21. npcsh/npc_team/jinxs/lib/computer_use/wait.jinx +21 -0
  22. {npcsh-1.1.14.data/data/npcsh/npc_team → npcsh/npc_team/jinxs/lib/core}/edit_file.jinx +3 -3
  23. {npcsh-1.1.14.data/data/npcsh/npc_team → npcsh/npc_team/jinxs/lib/core}/load_file.jinx +1 -1
  24. npcsh/npc_team/jinxs/lib/core/paste.jinx +134 -0
  25. {npcsh-1.1.14.data/data/npcsh/npc_team → npcsh/npc_team/jinxs/lib/core}/search.jinx +2 -1
  26. npcsh/npc_team/jinxs/{code → lib/core}/sh.jinx +2 -8
  27. npcsh/npc_team/jinxs/{code → lib/core}/sql.jinx +1 -1
  28. npcsh/npc_team/jinxs/lib/orchestration/convene.jinx +232 -0
  29. npcsh/npc_team/jinxs/lib/orchestration/delegate.jinx +184 -0
  30. npcsh/npc_team/jinxs/lib/research/arxiv.jinx +76 -0
  31. npcsh/npc_team/jinxs/lib/research/paper_search.jinx +101 -0
  32. npcsh/npc_team/jinxs/lib/research/semantic_scholar.jinx +69 -0
  33. npcsh/npc_team/jinxs/{utils/core → lib/utils}/build.jinx +8 -8
  34. npcsh/npc_team/jinxs/lib/utils/jinxs.jinx +176 -0
  35. npcsh/npc_team/jinxs/lib/utils/shh.jinx +17 -0
  36. npcsh/npc_team/jinxs/lib/utils/switch.jinx +62 -0
  37. npcsh/npc_team/jinxs/lib/utils/switches.jinx +61 -0
  38. npcsh/npc_team/jinxs/lib/utils/teamviz.jinx +205 -0
  39. npcsh/npc_team/jinxs/lib/utils/verbose.jinx +17 -0
  40. npcsh/npc_team/kadiefa.npc +19 -1
  41. npcsh/npc_team/plonk.npc +26 -1
  42. npcsh/npc_team/plonkjr.npc +22 -1
  43. npcsh/npc_team/sibiji.npc +23 -2
  44. npcsh/npcsh.py +153 -39
  45. npcsh/ui.py +22 -1
  46. npcsh-1.1.15.data/data/npcsh/npc_team/alicanto.npc +23 -0
  47. npcsh-1.1.15.data/data/npcsh/npc_team/arxiv.jinx +76 -0
  48. npcsh-1.1.15.data/data/npcsh/npc_team/browser_action.jinx +220 -0
  49. npcsh-1.1.15.data/data/npcsh/npc_team/browser_screenshot.jinx +40 -0
  50. {npcsh-1.1.14.data → npcsh-1.1.15.data}/data/npcsh/npc_team/build.jinx +8 -8
  51. npcsh-1.1.15.data/data/npcsh/npc_team/click.jinx +23 -0
  52. npcsh-1.1.15.data/data/npcsh/npc_team/close_browser.jinx +14 -0
  53. npcsh-1.1.15.data/data/npcsh/npc_team/convene.jinx +232 -0
  54. npcsh-1.1.15.data/data/npcsh/npc_team/corca.npc +31 -0
  55. npcsh-1.1.15.data/data/npcsh/npc_team/delegate.jinx +184 -0
  56. {npcsh/npc_team/jinxs/utils → npcsh-1.1.15.data/data/npcsh/npc_team}/edit_file.jinx +3 -3
  57. npcsh-1.1.15.data/data/npcsh/npc_team/frederic.npc +27 -0
  58. npcsh-1.1.15.data/data/npcsh/npc_team/guac.npc +22 -0
  59. npcsh-1.1.15.data/data/npcsh/npc_team/jinxs.jinx +176 -0
  60. npcsh-1.1.15.data/data/npcsh/npc_team/kadiefa.npc +21 -0
  61. npcsh-1.1.15.data/data/npcsh/npc_team/key_press.jinx +26 -0
  62. npcsh-1.1.15.data/data/npcsh/npc_team/launch_app.jinx +37 -0
  63. {npcsh/npc_team/jinxs/utils → npcsh-1.1.15.data/data/npcsh/npc_team}/load_file.jinx +1 -1
  64. npcsh-1.1.15.data/data/npcsh/npc_team/nql.jinx +141 -0
  65. npcsh-1.1.15.data/data/npcsh/npc_team/open_browser.jinx +43 -0
  66. npcsh-1.1.15.data/data/npcsh/npc_team/paper_search.jinx +101 -0
  67. npcsh-1.1.15.data/data/npcsh/npc_team/paste.jinx +134 -0
  68. npcsh-1.1.15.data/data/npcsh/npc_team/plonk.npc +27 -0
  69. npcsh-1.1.15.data/data/npcsh/npc_team/plonkjr.npc +23 -0
  70. npcsh-1.1.15.data/data/npcsh/npc_team/screenshot.jinx +23 -0
  71. {npcsh/npc_team/jinxs/utils → npcsh-1.1.15.data/data/npcsh/npc_team}/search.jinx +2 -1
  72. npcsh-1.1.15.data/data/npcsh/npc_team/semantic_scholar.jinx +69 -0
  73. {npcsh-1.1.14.data → npcsh-1.1.15.data}/data/npcsh/npc_team/sh.jinx +2 -8
  74. npcsh-1.1.15.data/data/npcsh/npc_team/shh.jinx +17 -0
  75. npcsh-1.1.15.data/data/npcsh/npc_team/sibiji.npc +24 -0
  76. {npcsh-1.1.14.data → npcsh-1.1.15.data}/data/npcsh/npc_team/sql.jinx +1 -1
  77. npcsh-1.1.15.data/data/npcsh/npc_team/switch.jinx +62 -0
  78. npcsh-1.1.15.data/data/npcsh/npc_team/switches.jinx +61 -0
  79. npcsh-1.1.15.data/data/npcsh/npc_team/sync.jinx +230 -0
  80. npcsh-1.1.15.data/data/npcsh/npc_team/teamviz.jinx +205 -0
  81. npcsh-1.1.15.data/data/npcsh/npc_team/type_text.jinx +27 -0
  82. npcsh-1.1.15.data/data/npcsh/npc_team/verbose.jinx +17 -0
  83. {npcsh/npc_team/jinxs/utils → npcsh-1.1.15.data/data/npcsh/npc_team}/vixynt.jinx +8 -30
  84. npcsh-1.1.15.data/data/npcsh/npc_team/wait.jinx +21 -0
  85. npcsh-1.1.15.data/data/npcsh/npc_team/wander.jinx +152 -0
  86. {npcsh-1.1.14.dist-info → npcsh-1.1.15.dist-info}/METADATA +399 -58
  87. npcsh-1.1.15.dist-info/RECORD +170 -0
  88. npcsh-1.1.15.dist-info/entry_points.txt +19 -0
  89. npcsh-1.1.15.dist-info/top_level.txt +2 -0
  90. project/__init__.py +1 -0
  91. npcsh/npc_team/foreman.npc +0 -7
  92. npcsh/npc_team/jinxs/modes/alicanto.jinx +0 -194
  93. npcsh/npc_team/jinxs/modes/corca.jinx +0 -249
  94. npcsh/npc_team/jinxs/modes/guac.jinx +0 -317
  95. npcsh/npc_team/jinxs/modes/plonk.jinx +0 -214
  96. npcsh/npc_team/jinxs/modes/pti.jinx +0 -170
  97. npcsh/npc_team/jinxs/modes/wander.jinx +0 -186
  98. npcsh/npc_team/jinxs/utils/agent.jinx +0 -17
  99. npcsh/npc_team/jinxs/utils/core/jinxs.jinx +0 -32
  100. npcsh-1.1.14.data/data/npcsh/npc_team/agent.jinx +0 -17
  101. npcsh-1.1.14.data/data/npcsh/npc_team/alicanto.jinx +0 -194
  102. npcsh-1.1.14.data/data/npcsh/npc_team/alicanto.npc +0 -2
  103. npcsh-1.1.14.data/data/npcsh/npc_team/corca.jinx +0 -249
  104. npcsh-1.1.14.data/data/npcsh/npc_team/corca.npc +0 -12
  105. npcsh-1.1.14.data/data/npcsh/npc_team/foreman.npc +0 -7
  106. npcsh-1.1.14.data/data/npcsh/npc_team/frederic.npc +0 -6
  107. npcsh-1.1.14.data/data/npcsh/npc_team/guac.jinx +0 -317
  108. npcsh-1.1.14.data/data/npcsh/npc_team/jinxs.jinx +0 -32
  109. npcsh-1.1.14.data/data/npcsh/npc_team/kadiefa.npc +0 -3
  110. npcsh-1.1.14.data/data/npcsh/npc_team/plonk.jinx +0 -214
  111. npcsh-1.1.14.data/data/npcsh/npc_team/plonk.npc +0 -2
  112. npcsh-1.1.14.data/data/npcsh/npc_team/plonkjr.npc +0 -2
  113. npcsh-1.1.14.data/data/npcsh/npc_team/pti.jinx +0 -170
  114. npcsh-1.1.14.data/data/npcsh/npc_team/sibiji.npc +0 -3
  115. npcsh-1.1.14.data/data/npcsh/npc_team/wander.jinx +0 -186
  116. npcsh-1.1.14.dist-info/RECORD +0 -135
  117. npcsh-1.1.14.dist-info/entry_points.txt +0 -9
  118. npcsh-1.1.14.dist-info/top_level.txt +0 -1
  119. /npcsh/npc_team/jinxs/{utils → bin}/roll.jinx +0 -0
  120. /npcsh/npc_team/jinxs/{utils → bin}/sample.jinx +0 -0
  121. /npcsh/npc_team/jinxs/{modes → bin}/spool.jinx +0 -0
  122. /npcsh/npc_team/jinxs/{modes → bin}/yap.jinx +0 -0
  123. /npcsh/npc_team/jinxs/{utils → lib/computer_use}/trigger.jinx +0 -0
  124. /npcsh/npc_team/jinxs/{utils → lib/core}/chat.jinx +0 -0
  125. /npcsh/npc_team/jinxs/{utils → lib/core}/cmd.jinx +0 -0
  126. /npcsh/npc_team/jinxs/{utils → lib/core}/compress.jinx +0 -0
  127. /npcsh/npc_team/jinxs/{utils → lib/core}/ots.jinx +0 -0
  128. /npcsh/npc_team/jinxs/{code → lib/core}/python.jinx +0 -0
  129. /npcsh/npc_team/jinxs/{utils → lib/core}/sleep.jinx +0 -0
  130. /npcsh/npc_team/jinxs/{utils/core → lib/utils}/compile.jinx +0 -0
  131. /npcsh/npc_team/jinxs/{utils/core → lib/utils}/help.jinx +0 -0
  132. /npcsh/npc_team/jinxs/{utils/core → lib/utils}/init.jinx +0 -0
  133. /npcsh/npc_team/jinxs/{utils → lib/utils}/serve.jinx +0 -0
  134. /npcsh/npc_team/jinxs/{utils/core → lib/utils}/set.jinx +0 -0
  135. /npcsh/npc_team/jinxs/{utils → lib/utils}/usage.jinx +0 -0
  136. {npcsh-1.1.14.data → npcsh-1.1.15.data}/data/npcsh/npc_team/alicanto.png +0 -0
  137. {npcsh-1.1.14.data → npcsh-1.1.15.data}/data/npcsh/npc_team/chat.jinx +0 -0
  138. {npcsh-1.1.14.data → npcsh-1.1.15.data}/data/npcsh/npc_team/cmd.jinx +0 -0
  139. {npcsh-1.1.14.data → npcsh-1.1.15.data}/data/npcsh/npc_team/compile.jinx +0 -0
  140. {npcsh-1.1.14.data → npcsh-1.1.15.data}/data/npcsh/npc_team/compress.jinx +0 -0
  141. {npcsh-1.1.14.data → npcsh-1.1.15.data}/data/npcsh/npc_team/corca.png +0 -0
  142. {npcsh-1.1.14.data → npcsh-1.1.15.data}/data/npcsh/npc_team/corca_example.png +0 -0
  143. {npcsh-1.1.14.data → npcsh-1.1.15.data}/data/npcsh/npc_team/frederic4.png +0 -0
  144. {npcsh-1.1.14.data → npcsh-1.1.15.data}/data/npcsh/npc_team/guac.png +0 -0
  145. {npcsh-1.1.14.data → npcsh-1.1.15.data}/data/npcsh/npc_team/help.jinx +0 -0
  146. {npcsh-1.1.14.data → npcsh-1.1.15.data}/data/npcsh/npc_team/init.jinx +0 -0
  147. {npcsh-1.1.14.data → npcsh-1.1.15.data}/data/npcsh/npc_team/kadiefa.png +0 -0
  148. {npcsh-1.1.14.data → npcsh-1.1.15.data}/data/npcsh/npc_team/npc-studio.jinx +0 -0
  149. {npcsh-1.1.14.data → npcsh-1.1.15.data}/data/npcsh/npc_team/npcsh.ctx +0 -0
  150. {npcsh-1.1.14.data → npcsh-1.1.15.data}/data/npcsh/npc_team/npcsh_sibiji.png +0 -0
  151. {npcsh-1.1.14.data → npcsh-1.1.15.data}/data/npcsh/npc_team/ots.jinx +0 -0
  152. {npcsh-1.1.14.data → npcsh-1.1.15.data}/data/npcsh/npc_team/plonk.png +0 -0
  153. {npcsh-1.1.14.data → npcsh-1.1.15.data}/data/npcsh/npc_team/plonkjr.png +0 -0
  154. {npcsh-1.1.14.data → npcsh-1.1.15.data}/data/npcsh/npc_team/python.jinx +0 -0
  155. {npcsh-1.1.14.data → npcsh-1.1.15.data}/data/npcsh/npc_team/roll.jinx +0 -0
  156. {npcsh-1.1.14.data → npcsh-1.1.15.data}/data/npcsh/npc_team/sample.jinx +0 -0
  157. {npcsh-1.1.14.data → npcsh-1.1.15.data}/data/npcsh/npc_team/serve.jinx +0 -0
  158. {npcsh-1.1.14.data → npcsh-1.1.15.data}/data/npcsh/npc_team/set.jinx +0 -0
  159. {npcsh-1.1.14.data → npcsh-1.1.15.data}/data/npcsh/npc_team/sibiji.png +0 -0
  160. {npcsh-1.1.14.data → npcsh-1.1.15.data}/data/npcsh/npc_team/sleep.jinx +0 -0
  161. {npcsh-1.1.14.data → npcsh-1.1.15.data}/data/npcsh/npc_team/spool.jinx +0 -0
  162. {npcsh-1.1.14.data → npcsh-1.1.15.data}/data/npcsh/npc_team/spool.png +0 -0
  163. {npcsh-1.1.14.data → npcsh-1.1.15.data}/data/npcsh/npc_team/trigger.jinx +0 -0
  164. {npcsh-1.1.14.data → npcsh-1.1.15.data}/data/npcsh/npc_team/usage.jinx +0 -0
  165. {npcsh-1.1.14.data → npcsh-1.1.15.data}/data/npcsh/npc_team/yap.jinx +0 -0
  166. {npcsh-1.1.14.data → npcsh-1.1.15.data}/data/npcsh/npc_team/yap.png +0 -0
  167. {npcsh-1.1.14.dist-info → npcsh-1.1.15.dist-info}/WHEEL +0 -0
  168. {npcsh-1.1.14.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
@@ -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
- You are kadiefa, the exploratory snow leopard. You love to find new paths and to explore hidden gems. You go into caverns no cat has ventured into before. You climb peaks that others call crazy. You are at the height of your power. Your role is to lead the way for users to explore complex research questions and to think outside of the box.
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
- primary_directive: You are the superior automation specialist of the NPC team.
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
@@ -1,2 +1,23 @@
1
1
  name: plonkjr
2
- primary_directive: You are junior automation specialist in the NPC Team.
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
- primary_directive: You are a foundational AI assistant. Your role is to provide support and information. Respond to queries concisely and accurately. Help users with code and other processes.
3
- jinxs: "*"
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 print_welcome_message():
75
- print(
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
- Begin by asking a question, issuing a bash command, or typing '/help' for more information.
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
- render_markdown(f'- Using {state.current_mode} mode. Use /agent, /cmd, or /chat to switch to other modes')
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
- render_markdown('\n- Here are the currently available Jinxs: ' + ', '.join([jinx_name for jinx_name in state.team.jinxs_dict.keys()]))
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
- print("^C")
272
- if input("\nExit? (y/n): ").lower().startswith('y'):
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
- initial_state.npc = default_npc
304
- initial_state.team = team
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
- run_repl(command_history, initial_state, router)
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
- main()
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) + 20) + '\r')
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/*