npcsh 0.1.2__py3-none-any.whl → 1.1.13__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 +3508 -0
- npcsh/alicanto.py +65 -0
- npcsh/build.py +291 -0
- npcsh/completion.py +206 -0
- npcsh/config.py +163 -0
- npcsh/corca.py +50 -0
- npcsh/execution.py +185 -0
- npcsh/guac.py +46 -0
- npcsh/mcp_helpers.py +357 -0
- npcsh/mcp_server.py +299 -0
- npcsh/npc.py +323 -0
- npcsh/npc_team/alicanto.npc +2 -0
- npcsh/npc_team/alicanto.png +0 -0
- npcsh/npc_team/corca.npc +12 -0
- npcsh/npc_team/corca.png +0 -0
- npcsh/npc_team/corca_example.png +0 -0
- npcsh/npc_team/foreman.npc +7 -0
- npcsh/npc_team/frederic.npc +6 -0
- npcsh/npc_team/frederic4.png +0 -0
- npcsh/npc_team/guac.png +0 -0
- npcsh/npc_team/jinxs/code/python.jinx +11 -0
- npcsh/npc_team/jinxs/code/sh.jinx +34 -0
- npcsh/npc_team/jinxs/code/sql.jinx +16 -0
- npcsh/npc_team/jinxs/modes/alicanto.jinx +194 -0
- npcsh/npc_team/jinxs/modes/corca.jinx +249 -0
- npcsh/npc_team/jinxs/modes/guac.jinx +317 -0
- npcsh/npc_team/jinxs/modes/plonk.jinx +214 -0
- npcsh/npc_team/jinxs/modes/pti.jinx +170 -0
- npcsh/npc_team/jinxs/modes/spool.jinx +161 -0
- npcsh/npc_team/jinxs/modes/wander.jinx +186 -0
- npcsh/npc_team/jinxs/modes/yap.jinx +262 -0
- npcsh/npc_team/jinxs/npc_studio/npc-studio.jinx +77 -0
- npcsh/npc_team/jinxs/utils/agent.jinx +17 -0
- npcsh/npc_team/jinxs/utils/chat.jinx +44 -0
- npcsh/npc_team/jinxs/utils/cmd.jinx +44 -0
- npcsh/npc_team/jinxs/utils/compress.jinx +140 -0
- npcsh/npc_team/jinxs/utils/core/build.jinx +65 -0
- npcsh/npc_team/jinxs/utils/core/compile.jinx +50 -0
- npcsh/npc_team/jinxs/utils/core/help.jinx +52 -0
- npcsh/npc_team/jinxs/utils/core/init.jinx +41 -0
- npcsh/npc_team/jinxs/utils/core/jinxs.jinx +32 -0
- npcsh/npc_team/jinxs/utils/core/set.jinx +40 -0
- npcsh/npc_team/jinxs/utils/edit_file.jinx +94 -0
- npcsh/npc_team/jinxs/utils/load_file.jinx +35 -0
- npcsh/npc_team/jinxs/utils/ots.jinx +61 -0
- npcsh/npc_team/jinxs/utils/roll.jinx +68 -0
- npcsh/npc_team/jinxs/utils/sample.jinx +56 -0
- npcsh/npc_team/jinxs/utils/search.jinx +130 -0
- npcsh/npc_team/jinxs/utils/serve.jinx +26 -0
- npcsh/npc_team/jinxs/utils/sleep.jinx +116 -0
- npcsh/npc_team/jinxs/utils/trigger.jinx +61 -0
- npcsh/npc_team/jinxs/utils/usage.jinx +33 -0
- npcsh/npc_team/jinxs/utils/vixynt.jinx +144 -0
- npcsh/npc_team/kadiefa.npc +3 -0
- npcsh/npc_team/kadiefa.png +0 -0
- npcsh/npc_team/npcsh.ctx +18 -0
- npcsh/npc_team/npcsh_sibiji.png +0 -0
- npcsh/npc_team/plonk.npc +2 -0
- npcsh/npc_team/plonk.png +0 -0
- npcsh/npc_team/plonkjr.npc +2 -0
- npcsh/npc_team/plonkjr.png +0 -0
- npcsh/npc_team/sibiji.npc +3 -0
- npcsh/npc_team/sibiji.png +0 -0
- npcsh/npc_team/spool.png +0 -0
- npcsh/npc_team/yap.png +0 -0
- npcsh/npcsh.py +296 -112
- npcsh/parsing.py +118 -0
- npcsh/plonk.py +54 -0
- npcsh/pti.py +54 -0
- npcsh/routes.py +139 -0
- npcsh/spool.py +48 -0
- npcsh/ui.py +199 -0
- npcsh/wander.py +62 -0
- npcsh/yap.py +50 -0
- npcsh-1.1.13.data/data/npcsh/npc_team/agent.jinx +17 -0
- npcsh-1.1.13.data/data/npcsh/npc_team/alicanto.jinx +194 -0
- npcsh-1.1.13.data/data/npcsh/npc_team/alicanto.npc +2 -0
- npcsh-1.1.13.data/data/npcsh/npc_team/alicanto.png +0 -0
- npcsh-1.1.13.data/data/npcsh/npc_team/build.jinx +65 -0
- npcsh-1.1.13.data/data/npcsh/npc_team/chat.jinx +44 -0
- npcsh-1.1.13.data/data/npcsh/npc_team/cmd.jinx +44 -0
- npcsh-1.1.13.data/data/npcsh/npc_team/compile.jinx +50 -0
- npcsh-1.1.13.data/data/npcsh/npc_team/compress.jinx +140 -0
- npcsh-1.1.13.data/data/npcsh/npc_team/corca.jinx +249 -0
- npcsh-1.1.13.data/data/npcsh/npc_team/corca.npc +12 -0
- npcsh-1.1.13.data/data/npcsh/npc_team/corca.png +0 -0
- npcsh-1.1.13.data/data/npcsh/npc_team/corca_example.png +0 -0
- npcsh-1.1.13.data/data/npcsh/npc_team/edit_file.jinx +94 -0
- npcsh-1.1.13.data/data/npcsh/npc_team/foreman.npc +7 -0
- npcsh-1.1.13.data/data/npcsh/npc_team/frederic.npc +6 -0
- npcsh-1.1.13.data/data/npcsh/npc_team/frederic4.png +0 -0
- npcsh-1.1.13.data/data/npcsh/npc_team/guac.jinx +317 -0
- npcsh-1.1.13.data/data/npcsh/npc_team/guac.png +0 -0
- npcsh-1.1.13.data/data/npcsh/npc_team/help.jinx +52 -0
- npcsh-1.1.13.data/data/npcsh/npc_team/init.jinx +41 -0
- npcsh-1.1.13.data/data/npcsh/npc_team/jinxs.jinx +32 -0
- npcsh-1.1.13.data/data/npcsh/npc_team/kadiefa.npc +3 -0
- npcsh-1.1.13.data/data/npcsh/npc_team/kadiefa.png +0 -0
- npcsh-1.1.13.data/data/npcsh/npc_team/load_file.jinx +35 -0
- npcsh-1.1.13.data/data/npcsh/npc_team/npc-studio.jinx +77 -0
- npcsh-1.1.13.data/data/npcsh/npc_team/npcsh.ctx +18 -0
- npcsh-1.1.13.data/data/npcsh/npc_team/npcsh_sibiji.png +0 -0
- npcsh-1.1.13.data/data/npcsh/npc_team/ots.jinx +61 -0
- npcsh-1.1.13.data/data/npcsh/npc_team/plonk.jinx +214 -0
- npcsh-1.1.13.data/data/npcsh/npc_team/plonk.npc +2 -0
- npcsh-1.1.13.data/data/npcsh/npc_team/plonk.png +0 -0
- npcsh-1.1.13.data/data/npcsh/npc_team/plonkjr.npc +2 -0
- npcsh-1.1.13.data/data/npcsh/npc_team/plonkjr.png +0 -0
- npcsh-1.1.13.data/data/npcsh/npc_team/pti.jinx +170 -0
- npcsh-1.1.13.data/data/npcsh/npc_team/python.jinx +11 -0
- npcsh-1.1.13.data/data/npcsh/npc_team/roll.jinx +68 -0
- npcsh-1.1.13.data/data/npcsh/npc_team/sample.jinx +56 -0
- npcsh-1.1.13.data/data/npcsh/npc_team/search.jinx +130 -0
- npcsh-1.1.13.data/data/npcsh/npc_team/serve.jinx +26 -0
- npcsh-1.1.13.data/data/npcsh/npc_team/set.jinx +40 -0
- npcsh-1.1.13.data/data/npcsh/npc_team/sh.jinx +34 -0
- npcsh-1.1.13.data/data/npcsh/npc_team/sibiji.npc +3 -0
- npcsh-1.1.13.data/data/npcsh/npc_team/sibiji.png +0 -0
- npcsh-1.1.13.data/data/npcsh/npc_team/sleep.jinx +116 -0
- npcsh-1.1.13.data/data/npcsh/npc_team/spool.jinx +161 -0
- npcsh-1.1.13.data/data/npcsh/npc_team/spool.png +0 -0
- npcsh-1.1.13.data/data/npcsh/npc_team/sql.jinx +16 -0
- npcsh-1.1.13.data/data/npcsh/npc_team/trigger.jinx +61 -0
- npcsh-1.1.13.data/data/npcsh/npc_team/usage.jinx +33 -0
- npcsh-1.1.13.data/data/npcsh/npc_team/vixynt.jinx +144 -0
- npcsh-1.1.13.data/data/npcsh/npc_team/wander.jinx +186 -0
- npcsh-1.1.13.data/data/npcsh/npc_team/yap.jinx +262 -0
- npcsh-1.1.13.data/data/npcsh/npc_team/yap.png +0 -0
- npcsh-1.1.13.dist-info/METADATA +522 -0
- npcsh-1.1.13.dist-info/RECORD +135 -0
- {npcsh-0.1.2.dist-info → npcsh-1.1.13.dist-info}/WHEEL +1 -1
- npcsh-1.1.13.dist-info/entry_points.txt +9 -0
- {npcsh-0.1.2.dist-info → npcsh-1.1.13.dist-info/licenses}/LICENSE +1 -1
- npcsh/command_history.py +0 -81
- npcsh/helpers.py +0 -36
- npcsh/llm_funcs.py +0 -295
- npcsh/main.py +0 -5
- npcsh/modes.py +0 -343
- npcsh/npc_compiler.py +0 -124
- npcsh-0.1.2.dist-info/METADATA +0 -99
- npcsh-0.1.2.dist-info/RECORD +0 -14
- npcsh-0.1.2.dist-info/entry_points.txt +0 -2
- {npcsh-0.1.2.dist-info → npcsh-1.1.13.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,317 @@
|
|
|
1
|
+
jinx_name: guac
|
|
2
|
+
description: Python data analysis mode - execute Python code with persistent locals, auto-load files
|
|
3
|
+
inputs:
|
|
4
|
+
- model: null
|
|
5
|
+
- provider: null
|
|
6
|
+
- plots_dir: null
|
|
7
|
+
|
|
8
|
+
steps:
|
|
9
|
+
- name: guac_repl
|
|
10
|
+
engine: python
|
|
11
|
+
code: |
|
|
12
|
+
import os
|
|
13
|
+
import sys
|
|
14
|
+
import io
|
|
15
|
+
import re
|
|
16
|
+
import traceback
|
|
17
|
+
from pathlib import Path
|
|
18
|
+
from datetime import datetime
|
|
19
|
+
from termcolor import colored
|
|
20
|
+
|
|
21
|
+
import numpy as np
|
|
22
|
+
import pandas as pd
|
|
23
|
+
import matplotlib.pyplot as plt
|
|
24
|
+
|
|
25
|
+
from npcpy.llm_funcs import get_llm_response
|
|
26
|
+
from npcpy.npc_sysenv import render_markdown, get_system_message
|
|
27
|
+
|
|
28
|
+
npc = context.get('npc')
|
|
29
|
+
team = context.get('team')
|
|
30
|
+
messages = context.get('messages', [])
|
|
31
|
+
plots_dir = context.get('plots_dir') or os.path.expanduser("~/.npcsh/plots")
|
|
32
|
+
|
|
33
|
+
model = context.get('model') or (npc.model if npc else None)
|
|
34
|
+
provider = context.get('provider') or (npc.provider if npc else None)
|
|
35
|
+
|
|
36
|
+
# Use shared_context for persistent Python locals
|
|
37
|
+
shared_ctx = npc.shared_context if npc and hasattr(npc, 'shared_context') else {}
|
|
38
|
+
if 'locals' not in shared_ctx:
|
|
39
|
+
shared_ctx['locals'] = {}
|
|
40
|
+
|
|
41
|
+
# Initialize locals with useful imports
|
|
42
|
+
guac_locals = shared_ctx['locals']
|
|
43
|
+
guac_locals.update({
|
|
44
|
+
'np': np,
|
|
45
|
+
'pd': pd,
|
|
46
|
+
'plt': plt,
|
|
47
|
+
'Path': Path,
|
|
48
|
+
'os': os,
|
|
49
|
+
})
|
|
50
|
+
|
|
51
|
+
# Also store dataframes reference
|
|
52
|
+
if 'dataframes' not in shared_ctx:
|
|
53
|
+
shared_ctx['dataframes'] = {}
|
|
54
|
+
guac_locals['dataframes'] = shared_ctx['dataframes']
|
|
55
|
+
|
|
56
|
+
os.makedirs(plots_dir, exist_ok=True)
|
|
57
|
+
|
|
58
|
+
print("""
|
|
59
|
+
██████╗ ██╗ ██╗ █████╗ ██████╗
|
|
60
|
+
██╔════╝ ██║ ██║██╔══██╗██╔════╝
|
|
61
|
+
██║ ███╗██║ ██║███████║██║
|
|
62
|
+
██║ ██║██║ ██║██╔══██║██║
|
|
63
|
+
╚██████╔╝╚██████╔╝██║ ██║╚██████╗
|
|
64
|
+
╚═════╝ ╚═════╝ ╚═╝ ╚═╝ ╚═════╝
|
|
65
|
+
""")
|
|
66
|
+
|
|
67
|
+
npc_name = npc.name if npc else "guac"
|
|
68
|
+
print(f"Entering guac mode (NPC: {npc_name}). Type '/gq' to exit.")
|
|
69
|
+
print(" - Type Python code directly to execute")
|
|
70
|
+
print(" - Type natural language to get code suggestions")
|
|
71
|
+
print(" - Drop file paths to auto-load data")
|
|
72
|
+
print(f" - Plots saved to: {plots_dir}")
|
|
73
|
+
|
|
74
|
+
def is_python_code(text):
|
|
75
|
+
"""Check if text looks like Python code"""
|
|
76
|
+
text = text.strip()
|
|
77
|
+
if not text:
|
|
78
|
+
return False
|
|
79
|
+
# Common Python patterns
|
|
80
|
+
if re.match(r'^(import |from |def |class |if |for |while |with |try:|@|#)', text):
|
|
81
|
+
return True
|
|
82
|
+
if re.match(r'^[a-zA-Z_][a-zA-Z0-9_]*\s*=', text):
|
|
83
|
+
return True
|
|
84
|
+
if re.match(r'^[a-zA-Z_][a-zA-Z0-9_]*\s*\(', text):
|
|
85
|
+
return True
|
|
86
|
+
if re.match(r'^[a-zA-Z_][a-zA-Z0-9_]*\s*\[', text):
|
|
87
|
+
return True
|
|
88
|
+
if text.startswith('print(') or text.startswith('plt.'):
|
|
89
|
+
return True
|
|
90
|
+
try:
|
|
91
|
+
compile(text, "<input>", "exec")
|
|
92
|
+
return True
|
|
93
|
+
except SyntaxError:
|
|
94
|
+
return False
|
|
95
|
+
|
|
96
|
+
def execute_python(code_str, locals_dict):
|
|
97
|
+
"""Execute Python code and return output"""
|
|
98
|
+
output_capture = io.StringIO()
|
|
99
|
+
old_stdout, old_stderr = sys.stdout, sys.stderr
|
|
100
|
+
|
|
101
|
+
try:
|
|
102
|
+
sys.stdout = output_capture
|
|
103
|
+
sys.stderr = output_capture
|
|
104
|
+
|
|
105
|
+
# Try as expression first (for REPL-like behavior)
|
|
106
|
+
if '\n' not in code_str.strip():
|
|
107
|
+
try:
|
|
108
|
+
result = eval(compile(code_str, "<input>", "eval"), locals_dict)
|
|
109
|
+
if result is not None:
|
|
110
|
+
print(repr(result))
|
|
111
|
+
return output_capture.getvalue().strip()
|
|
112
|
+
except SyntaxError:
|
|
113
|
+
pass
|
|
114
|
+
|
|
115
|
+
# Execute as statements
|
|
116
|
+
exec(compile(code_str, "<input>", "exec"), locals_dict)
|
|
117
|
+
return output_capture.getvalue().strip()
|
|
118
|
+
|
|
119
|
+
except Exception:
|
|
120
|
+
traceback.print_exc(file=output_capture)
|
|
121
|
+
return output_capture.getvalue().strip()
|
|
122
|
+
finally:
|
|
123
|
+
sys.stdout, sys.stderr = old_stdout, old_stderr
|
|
124
|
+
|
|
125
|
+
def auto_load_file(file_path, locals_dict):
|
|
126
|
+
"""Auto-load a file into locals based on extension"""
|
|
127
|
+
path = Path(file_path).expanduser()
|
|
128
|
+
if not path.exists():
|
|
129
|
+
return None
|
|
130
|
+
|
|
131
|
+
ext = path.suffix.lower()
|
|
132
|
+
var_name = f"data_{datetime.now().strftime('%H%M%S')}"
|
|
133
|
+
|
|
134
|
+
try:
|
|
135
|
+
if ext == '.csv':
|
|
136
|
+
df = pd.read_csv(path)
|
|
137
|
+
locals_dict[var_name] = df
|
|
138
|
+
shared_ctx['dataframes'][var_name] = df
|
|
139
|
+
return f"Loaded CSV as '{var_name}': {len(df)} rows, {len(df.columns)} columns\nColumns: {list(df.columns)}"
|
|
140
|
+
|
|
141
|
+
elif ext in ['.xlsx', '.xls']:
|
|
142
|
+
df = pd.read_excel(path)
|
|
143
|
+
locals_dict[var_name] = df
|
|
144
|
+
shared_ctx['dataframes'][var_name] = df
|
|
145
|
+
return f"Loaded Excel as '{var_name}': {len(df)} rows, {len(df.columns)} columns"
|
|
146
|
+
|
|
147
|
+
elif ext == '.json':
|
|
148
|
+
import json
|
|
149
|
+
with open(path) as f:
|
|
150
|
+
data = json.load(f)
|
|
151
|
+
locals_dict[var_name] = data
|
|
152
|
+
return f"Loaded JSON as '{var_name}': {type(data).__name__}"
|
|
153
|
+
|
|
154
|
+
elif ext in ['.png', '.jpg', '.jpeg', '.gif']:
|
|
155
|
+
from PIL import Image
|
|
156
|
+
img = Image.open(path)
|
|
157
|
+
arr = np.array(img)
|
|
158
|
+
locals_dict[f"{var_name}_img"] = img
|
|
159
|
+
locals_dict[f"{var_name}_arr"] = arr
|
|
160
|
+
return f"Loaded image as '{var_name}_img' and '{var_name}_arr': {img.size}"
|
|
161
|
+
|
|
162
|
+
elif ext == '.npy':
|
|
163
|
+
arr = np.load(path)
|
|
164
|
+
locals_dict[var_name] = arr
|
|
165
|
+
return f"Loaded numpy array as '{var_name}': shape {arr.shape}"
|
|
166
|
+
|
|
167
|
+
elif ext in ['.txt', '.md']:
|
|
168
|
+
with open(path) as f:
|
|
169
|
+
text = f.read()
|
|
170
|
+
locals_dict[var_name] = text
|
|
171
|
+
return f"Loaded text as '{var_name}': {len(text)} chars"
|
|
172
|
+
|
|
173
|
+
else:
|
|
174
|
+
with open(path, 'rb') as f:
|
|
175
|
+
data = f.read()
|
|
176
|
+
locals_dict[var_name] = data
|
|
177
|
+
return f"Loaded binary as '{var_name}': {len(data)} bytes"
|
|
178
|
+
|
|
179
|
+
except Exception as e:
|
|
180
|
+
return f"Error loading {path}: {e}"
|
|
181
|
+
|
|
182
|
+
def save_current_plot():
|
|
183
|
+
"""Save current matplotlib figure if any"""
|
|
184
|
+
if plt.get_fignums():
|
|
185
|
+
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
|
|
186
|
+
plot_path = os.path.join(plots_dir, f"plot_{timestamp}.png")
|
|
187
|
+
plt.savefig(plot_path, dpi=150, bbox_inches='tight')
|
|
188
|
+
plt.close()
|
|
189
|
+
return plot_path
|
|
190
|
+
return None
|
|
191
|
+
|
|
192
|
+
# Ensure system message for LLM help
|
|
193
|
+
if not messages or messages[0].get("role") != "system":
|
|
194
|
+
sys_msg = """You are a Python data analysis assistant. When the user asks questions:
|
|
195
|
+
1. Generate clean, executable Python code
|
|
196
|
+
2. Use pandas, numpy, matplotlib as needed
|
|
197
|
+
3. Reference variables already in the user's session
|
|
198
|
+
4. Keep code concise and focused"""
|
|
199
|
+
messages.insert(0, {"role": "system", "content": sys_msg})
|
|
200
|
+
|
|
201
|
+
# REPL loop
|
|
202
|
+
while True:
|
|
203
|
+
try:
|
|
204
|
+
prompt_str = f"{npc_name}:guac> "
|
|
205
|
+
user_input = input(prompt_str).strip()
|
|
206
|
+
|
|
207
|
+
if not user_input:
|
|
208
|
+
continue
|
|
209
|
+
|
|
210
|
+
if user_input.lower() == "/gq":
|
|
211
|
+
print("Exiting guac mode.")
|
|
212
|
+
break
|
|
213
|
+
|
|
214
|
+
# /vars - show current variables
|
|
215
|
+
if user_input.lower() == "/vars":
|
|
216
|
+
print(colored("Current variables:", "cyan"))
|
|
217
|
+
for k, v in guac_locals.items():
|
|
218
|
+
if not k.startswith('_') and k not in ['np', 'pd', 'plt', 'Path', 'os', 'dataframes']:
|
|
219
|
+
vtype = type(v).__name__
|
|
220
|
+
if isinstance(v, pd.DataFrame):
|
|
221
|
+
print(f" {k}: DataFrame ({len(v)} rows)")
|
|
222
|
+
elif isinstance(v, np.ndarray):
|
|
223
|
+
print(f" {k}: ndarray {v.shape}")
|
|
224
|
+
else:
|
|
225
|
+
print(f" {k}: {vtype}")
|
|
226
|
+
continue
|
|
227
|
+
|
|
228
|
+
# /clear - clear variables
|
|
229
|
+
if user_input.lower() == "/clear":
|
|
230
|
+
keep = {'np', 'pd', 'plt', 'Path', 'os', 'dataframes'}
|
|
231
|
+
guac_locals.clear()
|
|
232
|
+
guac_locals.update({k: v for k, v in [('np', np), ('pd', pd), ('plt', plt), ('Path', Path), ('os', os), ('dataframes', shared_ctx['dataframes'])]})
|
|
233
|
+
print(colored("Variables cleared.", "yellow"))
|
|
234
|
+
continue
|
|
235
|
+
|
|
236
|
+
# Check if it's a file path (drag & drop)
|
|
237
|
+
potential_path = user_input.strip("'\"")
|
|
238
|
+
if os.path.exists(os.path.expanduser(potential_path)):
|
|
239
|
+
result = auto_load_file(potential_path, guac_locals)
|
|
240
|
+
if result:
|
|
241
|
+
print(colored(result, "green"))
|
|
242
|
+
continue
|
|
243
|
+
|
|
244
|
+
# Check if it's Python code
|
|
245
|
+
if is_python_code(user_input):
|
|
246
|
+
output = execute_python(user_input, guac_locals)
|
|
247
|
+
if output:
|
|
248
|
+
print(output)
|
|
249
|
+
|
|
250
|
+
# Save any plots
|
|
251
|
+
plot_path = save_current_plot()
|
|
252
|
+
if plot_path:
|
|
253
|
+
print(colored(f"Plot saved: {plot_path}", "green"))
|
|
254
|
+
continue
|
|
255
|
+
|
|
256
|
+
# Natural language - ask LLM for code
|
|
257
|
+
# Include current variables in context
|
|
258
|
+
var_context = "Current variables:\n"
|
|
259
|
+
for k, v in guac_locals.items():
|
|
260
|
+
if not k.startswith('_') and k not in ['np', 'pd', 'plt', 'Path', 'os', 'dataframes']:
|
|
261
|
+
if isinstance(v, pd.DataFrame):
|
|
262
|
+
var_context += f" {k}: DataFrame with columns {list(v.columns)}\n"
|
|
263
|
+
elif isinstance(v, np.ndarray):
|
|
264
|
+
var_context += f" {k}: ndarray shape {v.shape}\n"
|
|
265
|
+
else:
|
|
266
|
+
var_context += f" {k}: {type(v).__name__}\n"
|
|
267
|
+
|
|
268
|
+
prompt = f"{var_context}\nUser request: {user_input}\n\nGenerate Python code to accomplish this. Return ONLY the code, no explanation."
|
|
269
|
+
|
|
270
|
+
resp = get_llm_response(
|
|
271
|
+
prompt,
|
|
272
|
+
model=model,
|
|
273
|
+
provider=provider,
|
|
274
|
+
messages=messages,
|
|
275
|
+
npc=npc
|
|
276
|
+
)
|
|
277
|
+
|
|
278
|
+
messages = resp.get('messages', messages)
|
|
279
|
+
code_response = str(resp.get('response', ''))
|
|
280
|
+
|
|
281
|
+
# Extract code from response (strip markdown if present)
|
|
282
|
+
code = code_response
|
|
283
|
+
if '```python' in code:
|
|
284
|
+
code = code.split('```python')[1].split('```')[0]
|
|
285
|
+
elif '```' in code:
|
|
286
|
+
code = code.split('```')[1].split('```')[0]
|
|
287
|
+
code = code.strip()
|
|
288
|
+
|
|
289
|
+
if code:
|
|
290
|
+
print(colored("Generated code:", "cyan"))
|
|
291
|
+
print(code)
|
|
292
|
+
confirm = input("Execute? [Y/n/e(dit)]: ").strip().lower()
|
|
293
|
+
|
|
294
|
+
if confirm in ['', 'y', 'yes']:
|
|
295
|
+
output = execute_python(code, guac_locals)
|
|
296
|
+
if output:
|
|
297
|
+
print(output)
|
|
298
|
+
plot_path = save_current_plot()
|
|
299
|
+
if plot_path:
|
|
300
|
+
print(colored(f"Plot saved: {plot_path}", "green"))
|
|
301
|
+
|
|
302
|
+
elif confirm == 'e':
|
|
303
|
+
edited = input("Enter modified code: ").strip()
|
|
304
|
+
if edited:
|
|
305
|
+
output = execute_python(edited, guac_locals)
|
|
306
|
+
if output:
|
|
307
|
+
print(output)
|
|
308
|
+
|
|
309
|
+
except KeyboardInterrupt:
|
|
310
|
+
print("\nUse '/gq' to exit or continue.")
|
|
311
|
+
continue
|
|
312
|
+
except EOFError:
|
|
313
|
+
print("\nExiting guac mode.")
|
|
314
|
+
break
|
|
315
|
+
|
|
316
|
+
context['output'] = "Exited guac mode."
|
|
317
|
+
context['messages'] = messages
|
|
Binary file
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
jinx_name: help
|
|
2
|
+
description: Show help for commands, NPCs, or Jinxs
|
|
3
|
+
inputs:
|
|
4
|
+
- topic: null
|
|
5
|
+
steps:
|
|
6
|
+
- name: show_help
|
|
7
|
+
engine: python
|
|
8
|
+
code: |
|
|
9
|
+
import json
|
|
10
|
+
from npcsh._state import CANONICAL_ARGS, get_argument_help
|
|
11
|
+
|
|
12
|
+
topic = context.get('topic')
|
|
13
|
+
|
|
14
|
+
if not topic:
|
|
15
|
+
output_lines = ["# Available Commands\n\n"]
|
|
16
|
+
|
|
17
|
+
all_jinxs = {}
|
|
18
|
+
if hasattr(npc, 'team') and npc.team and hasattr(npc.team, 'jinxs_dict'):
|
|
19
|
+
all_jinxs.update(npc.team.jinxs_dict)
|
|
20
|
+
if hasattr(npc, 'jinxs_dict') and npc.jinxs_dict:
|
|
21
|
+
all_jinxs.update(npc.jinxs_dict)
|
|
22
|
+
|
|
23
|
+
for cmd in sorted(all_jinxs.keys()):
|
|
24
|
+
jinx_obj = all_jinxs[cmd]
|
|
25
|
+
desc = getattr(jinx_obj, 'description', 'No description')
|
|
26
|
+
output_lines.append(f"/{cmd} - {desc}\n\n")
|
|
27
|
+
|
|
28
|
+
arg_help_map = get_argument_help()
|
|
29
|
+
if arg_help_map:
|
|
30
|
+
output_lines.append("## Common Command-Line Flags\n\n")
|
|
31
|
+
output_lines.append("The shortest unambiguous prefix works.\n")
|
|
32
|
+
|
|
33
|
+
for arg in sorted(CANONICAL_ARGS):
|
|
34
|
+
aliases = arg_help_map.get(arg, [])
|
|
35
|
+
alias_str = f"(-{min(aliases, key=len)})" if aliases else ""
|
|
36
|
+
output_lines.append(f"--{arg:<20} {alias_str}\n")
|
|
37
|
+
|
|
38
|
+
output = "".join(output_lines)
|
|
39
|
+
else:
|
|
40
|
+
jinx_obj = None
|
|
41
|
+
if hasattr(npc, 'team') and npc.team and hasattr(npc.team, 'jinxs_dict'):
|
|
42
|
+
jinx_obj = npc.team.jinxs_dict.get(topic)
|
|
43
|
+
if not jinx_obj and hasattr(npc, 'jinxs_dict'):
|
|
44
|
+
jinx_obj = npc.jinxs_dict.get(topic)
|
|
45
|
+
|
|
46
|
+
if jinx_obj:
|
|
47
|
+
output = f"## Help for Jinx: `/{topic}`\n\n"
|
|
48
|
+
output += f"- **Description**: {jinx_obj.description}\n"
|
|
49
|
+
if hasattr(jinx_obj, 'inputs') and jinx_obj.inputs:
|
|
50
|
+
output += f"- **Inputs**: {json.dumps(jinx_obj.inputs, indent=2)}\n"
|
|
51
|
+
else:
|
|
52
|
+
output = f"No help topic found for `{topic}`."
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
jinx_name: "init"
|
|
2
|
+
description: "Initialize NPC project"
|
|
3
|
+
inputs:
|
|
4
|
+
- directory: "." # The directory where the NPC project should be initialized.
|
|
5
|
+
- templates: "" # Optional templates to use for initialization.
|
|
6
|
+
- context: "" # Optional context for project initialization.
|
|
7
|
+
- model: "" # Optional LLM model to set as default for the project.
|
|
8
|
+
- provider: "" # Optional LLM provider to set as default for the project.
|
|
9
|
+
steps:
|
|
10
|
+
- name: "initialize_project"
|
|
11
|
+
engine: "python"
|
|
12
|
+
code: |
|
|
13
|
+
import os
|
|
14
|
+
import traceback
|
|
15
|
+
from npcpy.npc_compiler import initialize_npc_project
|
|
16
|
+
|
|
17
|
+
directory = context.get('directory')
|
|
18
|
+
templates = context.get('templates')
|
|
19
|
+
context_param = context.get('context') # Renamed to avoid conflict with Jinx context
|
|
20
|
+
model = context.get('model')
|
|
21
|
+
provider = context.get('provider')
|
|
22
|
+
output_messages = context.get('messages', [])
|
|
23
|
+
|
|
24
|
+
output_result = ""
|
|
25
|
+
try:
|
|
26
|
+
initialize_npc_project(
|
|
27
|
+
directory=directory,
|
|
28
|
+
templates=templates,
|
|
29
|
+
context=context_param, # Use the renamed context parameter
|
|
30
|
+
model=model,
|
|
31
|
+
provider=provider
|
|
32
|
+
)
|
|
33
|
+
output_result = f"NPC project initialized in {os.path.abspath(directory)}."
|
|
34
|
+
except NameError:
|
|
35
|
+
output_result = "Init function (initialize_npc_project) not available."
|
|
36
|
+
except Exception as e:
|
|
37
|
+
traceback.print_exc()
|
|
38
|
+
output_result = f"Error initializing project: {e}"
|
|
39
|
+
|
|
40
|
+
context['output'] = output_result
|
|
41
|
+
context['messages'] = output_messages
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
jinx_name: jinxs
|
|
2
|
+
description: Show available jinxs for the current NPC/Team
|
|
3
|
+
inputs: []
|
|
4
|
+
steps:
|
|
5
|
+
- name: list_jinxs
|
|
6
|
+
engine: python
|
|
7
|
+
code: |
|
|
8
|
+
output_lines = ["Available Jinxs:\n"]
|
|
9
|
+
jinxs_listed = set()
|
|
10
|
+
|
|
11
|
+
if hasattr(npc, 'team') and npc.team:
|
|
12
|
+
team = npc.team
|
|
13
|
+
|
|
14
|
+
if hasattr(team, 'jinxs_dict') and team.jinxs_dict:
|
|
15
|
+
output_lines.append(f"\n--- Team Jinxs ---\n")
|
|
16
|
+
for name, jinx_obj in sorted(team.jinxs_dict.items()):
|
|
17
|
+
desc = getattr(jinx_obj, 'description', 'No description available.')
|
|
18
|
+
output_lines.append(f"- /{name}: {desc}\n")
|
|
19
|
+
jinxs_listed.add(name)
|
|
20
|
+
|
|
21
|
+
if hasattr(npc, 'jinxs_dict') and npc.jinxs_dict:
|
|
22
|
+
output_lines.append(f"\n--- NPC Jinxs for {npc.name} ---\n")
|
|
23
|
+
for name, jinx_obj in sorted(npc.jinxs_dict.items()):
|
|
24
|
+
if name not in jinxs_listed:
|
|
25
|
+
desc = getattr(jinx_obj, 'description', 'No description available.')
|
|
26
|
+
output_lines.append(f"- /{name}: {desc}\n")
|
|
27
|
+
jinxs_listed.add(name)
|
|
28
|
+
|
|
29
|
+
if not jinxs_listed:
|
|
30
|
+
output = "No jinxs available for the current context."
|
|
31
|
+
else:
|
|
32
|
+
output = "".join(output_lines)
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
name: kadiefa
|
|
2
|
+
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.
|
|
Binary file
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
jinx_name: load_file
|
|
2
|
+
description: Loads and returns the contents of a file using npcpy's file loaders
|
|
3
|
+
inputs:
|
|
4
|
+
- file_path
|
|
5
|
+
steps:
|
|
6
|
+
- name: "load_file"
|
|
7
|
+
engine: "python"
|
|
8
|
+
code: |
|
|
9
|
+
import os
|
|
10
|
+
from npcpy.data.load import load_file_contents
|
|
11
|
+
|
|
12
|
+
# Expand user path and get absolute path
|
|
13
|
+
file_path = os.path.expanduser("{{ file_path }}")
|
|
14
|
+
|
|
15
|
+
# Check if file exists
|
|
16
|
+
if not os.path.exists(file_path):
|
|
17
|
+
output = f"Error: File not found at {file_path}"
|
|
18
|
+
else:
|
|
19
|
+
try:
|
|
20
|
+
# Load file contents using npcpy's loader
|
|
21
|
+
# Returns chunks by default with chunk_size=250
|
|
22
|
+
chunks = load_file_contents(file_path)
|
|
23
|
+
|
|
24
|
+
# Join chunks back together for full content
|
|
25
|
+
if isinstance(chunks, list):
|
|
26
|
+
if chunks and chunks[0].startswith("Error"):
|
|
27
|
+
output = chunks[0]
|
|
28
|
+
else:
|
|
29
|
+
file_content = "\n".join(chunks)
|
|
30
|
+
output = f"File: {file_path}\n\n{file_content}"
|
|
31
|
+
else:
|
|
32
|
+
output = f"File: {file_path}\n\n{chunks}"
|
|
33
|
+
|
|
34
|
+
except Exception as e:
|
|
35
|
+
output = f"Error reading file {file_path}: {str(e)}"
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
jinx_name: "npc-studio"
|
|
2
|
+
description: "Start npc studio"
|
|
3
|
+
inputs:
|
|
4
|
+
- user_command: ""
|
|
5
|
+
steps:
|
|
6
|
+
- name: "launch_npc_studio"
|
|
7
|
+
engine: "python"
|
|
8
|
+
code: |
|
|
9
|
+
import os
|
|
10
|
+
import subprocess
|
|
11
|
+
import sys
|
|
12
|
+
from pathlib import Path
|
|
13
|
+
import traceback
|
|
14
|
+
|
|
15
|
+
NPC_STUDIO_DIR = Path.home() / ".npcsh" / "npc-studio"
|
|
16
|
+
|
|
17
|
+
user_command = context.get('user_command')
|
|
18
|
+
output_messages = context.get('messages', [])
|
|
19
|
+
output_result = ""
|
|
20
|
+
|
|
21
|
+
try:
|
|
22
|
+
if not NPC_STUDIO_DIR.exists():
|
|
23
|
+
os.makedirs(NPC_STUDIO_DIR.parent, exist_ok=True)
|
|
24
|
+
subprocess.check_call([
|
|
25
|
+
"git", "clone",
|
|
26
|
+
"https://github.com/npc-worldwide/npc-studio.git",
|
|
27
|
+
str(NPC_STUDIO_DIR)
|
|
28
|
+
])
|
|
29
|
+
else:
|
|
30
|
+
subprocess.check_call(
|
|
31
|
+
["git", "pull"],
|
|
32
|
+
cwd=NPC_STUDIO_DIR
|
|
33
|
+
)
|
|
34
|
+
|
|
35
|
+
subprocess.check_call(
|
|
36
|
+
["npm", "install"],
|
|
37
|
+
cwd=NPC_STUDIO_DIR
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
req_file = NPC_STUDIO_DIR / "requirements.txt"
|
|
41
|
+
if req_file.exists():
|
|
42
|
+
subprocess.check_call([
|
|
43
|
+
sys.executable,
|
|
44
|
+
"-m",
|
|
45
|
+
"pip",
|
|
46
|
+
"install",
|
|
47
|
+
"-r",
|
|
48
|
+
str(req_file)
|
|
49
|
+
])
|
|
50
|
+
|
|
51
|
+
backend = subprocess.Popen(
|
|
52
|
+
[sys.executable, "npc_studio_serve.py"],
|
|
53
|
+
cwd=NPC_STUDIO_DIR
|
|
54
|
+
)
|
|
55
|
+
|
|
56
|
+
dev_server = subprocess.Popen(
|
|
57
|
+
["npm", "run", "dev"],
|
|
58
|
+
cwd=NPC_STUDIO_DIR
|
|
59
|
+
)
|
|
60
|
+
|
|
61
|
+
frontend = subprocess.Popen(
|
|
62
|
+
["npm", "start"],
|
|
63
|
+
cwd=NPC_STUDIO_DIR
|
|
64
|
+
)
|
|
65
|
+
|
|
66
|
+
output_result = (
|
|
67
|
+
f"NPC Studio started!\n"
|
|
68
|
+
f"Backend PID={backend.pid}, "
|
|
69
|
+
f"Dev Server PID={dev_server.pid}, "
|
|
70
|
+
f"Frontend PID={frontend.pid}"
|
|
71
|
+
)
|
|
72
|
+
except Exception as e:
|
|
73
|
+
traceback.print_exc()
|
|
74
|
+
output_result = f"Failed to start NPC Studio: {e}"
|
|
75
|
+
|
|
76
|
+
context['output'] = output_result
|
|
77
|
+
context['messages'] = output_messages
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
context: |
|
|
2
|
+
The npcsh NPC team is devoted to providing a safe and helpful
|
|
3
|
+
environment for users where they can work and be as successful as possible.
|
|
4
|
+
npcsh is a command-line tool that makes it easy for users to harness
|
|
5
|
+
the power of LLMs from a command line shell. npcsh is a command line toolkit consisting of several programs.
|
|
6
|
+
databases:
|
|
7
|
+
- ~/npcsh_history.db
|
|
8
|
+
mcp_servers:
|
|
9
|
+
- ~/.npcsh/mcp_server.py
|
|
10
|
+
use_global_jinxs: true
|
|
11
|
+
forenpc: sibiji
|
|
12
|
+
preferences:
|
|
13
|
+
- If you come up with an idea, it is critical that you also provide a way to validate the idea.
|
|
14
|
+
- Never change function names unless requested. keep things idempotent.
|
|
15
|
+
- If plots are requested for python code, prefer to use matplotlib. Do not ever use seaborn.
|
|
16
|
+
- Object oriented programming should be used sparingly and only when practical. Otherwise, opt for functional implementations.
|
|
17
|
+
- Never write unit tests unless explicitly requested.
|
|
18
|
+
- If we want you to write tests, we mean we want you to write example use cases that show how the code works.
|
|
Binary file
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
jinx_name: "ots"
|
|
2
|
+
description: "Take screenshot and analyze with vision model. Usage: /ots <prompt>"
|
|
3
|
+
inputs:
|
|
4
|
+
- prompt
|
|
5
|
+
- image_paths_args: ""
|
|
6
|
+
- vmodel: ""
|
|
7
|
+
- vprovider: ""
|
|
8
|
+
steps:
|
|
9
|
+
- name: "analyze_screenshot_or_image"
|
|
10
|
+
engine: "python"
|
|
11
|
+
code: |
|
|
12
|
+
import os
|
|
13
|
+
import traceback
|
|
14
|
+
from npcpy.llm_funcs import get_llm_response
|
|
15
|
+
from npcpy.data.image import capture_screenshot
|
|
16
|
+
|
|
17
|
+
user_prompt = context.get('prompt') or ""
|
|
18
|
+
image_paths_args_str = context.get('image_paths_args') or ""
|
|
19
|
+
vision_model = context.get('vmodel') or ""
|
|
20
|
+
vision_provider = context.get('vprovider') or ""
|
|
21
|
+
stream_output = context.get('stream') or False
|
|
22
|
+
api_url = context.get('api_url') or ""
|
|
23
|
+
api_key = context.get('api_key') or ""
|
|
24
|
+
output_messages = context.get('messages', [])
|
|
25
|
+
current_npc = context.get('npc')
|
|
26
|
+
|
|
27
|
+
image_paths = []
|
|
28
|
+
if image_paths_args_str.strip():
|
|
29
|
+
for img_path_arg in image_paths_args_str.split(','):
|
|
30
|
+
full_path = os.path.abspath(os.path.expanduser(img_path_arg.strip()))
|
|
31
|
+
if os.path.exists(full_path):
|
|
32
|
+
image_paths.append(full_path)
|
|
33
|
+
|
|
34
|
+
if not image_paths:
|
|
35
|
+
screenshot_info = capture_screenshot(full=False)
|
|
36
|
+
if screenshot_info and "file_path" in screenshot_info:
|
|
37
|
+
image_paths.append(screenshot_info["file_path"])
|
|
38
|
+
print(f"📸 Screenshot captured: {screenshot_info.get('filename', os.path.basename(screenshot_info['file_path']))}")
|
|
39
|
+
|
|
40
|
+
if not vision_model:
|
|
41
|
+
vision_model = getattr(current_npc, 'model', 'gemma3:4b')
|
|
42
|
+
|
|
43
|
+
if not vision_provider:
|
|
44
|
+
vision_provider = getattr(current_npc, 'provider', 'ollama')
|
|
45
|
+
|
|
46
|
+
response_data = get_llm_response(
|
|
47
|
+
prompt=user_prompt,
|
|
48
|
+
model=vision_model,
|
|
49
|
+
provider=vision_provider,
|
|
50
|
+
messages=output_messages,
|
|
51
|
+
images=image_paths,
|
|
52
|
+
stream=stream_output,
|
|
53
|
+
npc=current_npc,
|
|
54
|
+
api_url=api_url or None,
|
|
55
|
+
api_key=api_key or None
|
|
56
|
+
)
|
|
57
|
+
|
|
58
|
+
context['output'] = response_data.get('response', 'No response received')
|
|
59
|
+
context['messages'] = response_data.get('messages', output_messages)
|
|
60
|
+
context['model'] = vision_model
|
|
61
|
+
context['provider'] = vision_provider
|