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.
Files changed (143) hide show
  1. npcsh/_state.py +3508 -0
  2. npcsh/alicanto.py +65 -0
  3. npcsh/build.py +291 -0
  4. npcsh/completion.py +206 -0
  5. npcsh/config.py +163 -0
  6. npcsh/corca.py +50 -0
  7. npcsh/execution.py +185 -0
  8. npcsh/guac.py +46 -0
  9. npcsh/mcp_helpers.py +357 -0
  10. npcsh/mcp_server.py +299 -0
  11. npcsh/npc.py +323 -0
  12. npcsh/npc_team/alicanto.npc +2 -0
  13. npcsh/npc_team/alicanto.png +0 -0
  14. npcsh/npc_team/corca.npc +12 -0
  15. npcsh/npc_team/corca.png +0 -0
  16. npcsh/npc_team/corca_example.png +0 -0
  17. npcsh/npc_team/foreman.npc +7 -0
  18. npcsh/npc_team/frederic.npc +6 -0
  19. npcsh/npc_team/frederic4.png +0 -0
  20. npcsh/npc_team/guac.png +0 -0
  21. npcsh/npc_team/jinxs/code/python.jinx +11 -0
  22. npcsh/npc_team/jinxs/code/sh.jinx +34 -0
  23. npcsh/npc_team/jinxs/code/sql.jinx +16 -0
  24. npcsh/npc_team/jinxs/modes/alicanto.jinx +194 -0
  25. npcsh/npc_team/jinxs/modes/corca.jinx +249 -0
  26. npcsh/npc_team/jinxs/modes/guac.jinx +317 -0
  27. npcsh/npc_team/jinxs/modes/plonk.jinx +214 -0
  28. npcsh/npc_team/jinxs/modes/pti.jinx +170 -0
  29. npcsh/npc_team/jinxs/modes/spool.jinx +161 -0
  30. npcsh/npc_team/jinxs/modes/wander.jinx +186 -0
  31. npcsh/npc_team/jinxs/modes/yap.jinx +262 -0
  32. npcsh/npc_team/jinxs/npc_studio/npc-studio.jinx +77 -0
  33. npcsh/npc_team/jinxs/utils/agent.jinx +17 -0
  34. npcsh/npc_team/jinxs/utils/chat.jinx +44 -0
  35. npcsh/npc_team/jinxs/utils/cmd.jinx +44 -0
  36. npcsh/npc_team/jinxs/utils/compress.jinx +140 -0
  37. npcsh/npc_team/jinxs/utils/core/build.jinx +65 -0
  38. npcsh/npc_team/jinxs/utils/core/compile.jinx +50 -0
  39. npcsh/npc_team/jinxs/utils/core/help.jinx +52 -0
  40. npcsh/npc_team/jinxs/utils/core/init.jinx +41 -0
  41. npcsh/npc_team/jinxs/utils/core/jinxs.jinx +32 -0
  42. npcsh/npc_team/jinxs/utils/core/set.jinx +40 -0
  43. npcsh/npc_team/jinxs/utils/edit_file.jinx +94 -0
  44. npcsh/npc_team/jinxs/utils/load_file.jinx +35 -0
  45. npcsh/npc_team/jinxs/utils/ots.jinx +61 -0
  46. npcsh/npc_team/jinxs/utils/roll.jinx +68 -0
  47. npcsh/npc_team/jinxs/utils/sample.jinx +56 -0
  48. npcsh/npc_team/jinxs/utils/search.jinx +130 -0
  49. npcsh/npc_team/jinxs/utils/serve.jinx +26 -0
  50. npcsh/npc_team/jinxs/utils/sleep.jinx +116 -0
  51. npcsh/npc_team/jinxs/utils/trigger.jinx +61 -0
  52. npcsh/npc_team/jinxs/utils/usage.jinx +33 -0
  53. npcsh/npc_team/jinxs/utils/vixynt.jinx +144 -0
  54. npcsh/npc_team/kadiefa.npc +3 -0
  55. npcsh/npc_team/kadiefa.png +0 -0
  56. npcsh/npc_team/npcsh.ctx +18 -0
  57. npcsh/npc_team/npcsh_sibiji.png +0 -0
  58. npcsh/npc_team/plonk.npc +2 -0
  59. npcsh/npc_team/plonk.png +0 -0
  60. npcsh/npc_team/plonkjr.npc +2 -0
  61. npcsh/npc_team/plonkjr.png +0 -0
  62. npcsh/npc_team/sibiji.npc +3 -0
  63. npcsh/npc_team/sibiji.png +0 -0
  64. npcsh/npc_team/spool.png +0 -0
  65. npcsh/npc_team/yap.png +0 -0
  66. npcsh/npcsh.py +296 -112
  67. npcsh/parsing.py +118 -0
  68. npcsh/plonk.py +54 -0
  69. npcsh/pti.py +54 -0
  70. npcsh/routes.py +139 -0
  71. npcsh/spool.py +48 -0
  72. npcsh/ui.py +199 -0
  73. npcsh/wander.py +62 -0
  74. npcsh/yap.py +50 -0
  75. npcsh-1.1.13.data/data/npcsh/npc_team/agent.jinx +17 -0
  76. npcsh-1.1.13.data/data/npcsh/npc_team/alicanto.jinx +194 -0
  77. npcsh-1.1.13.data/data/npcsh/npc_team/alicanto.npc +2 -0
  78. npcsh-1.1.13.data/data/npcsh/npc_team/alicanto.png +0 -0
  79. npcsh-1.1.13.data/data/npcsh/npc_team/build.jinx +65 -0
  80. npcsh-1.1.13.data/data/npcsh/npc_team/chat.jinx +44 -0
  81. npcsh-1.1.13.data/data/npcsh/npc_team/cmd.jinx +44 -0
  82. npcsh-1.1.13.data/data/npcsh/npc_team/compile.jinx +50 -0
  83. npcsh-1.1.13.data/data/npcsh/npc_team/compress.jinx +140 -0
  84. npcsh-1.1.13.data/data/npcsh/npc_team/corca.jinx +249 -0
  85. npcsh-1.1.13.data/data/npcsh/npc_team/corca.npc +12 -0
  86. npcsh-1.1.13.data/data/npcsh/npc_team/corca.png +0 -0
  87. npcsh-1.1.13.data/data/npcsh/npc_team/corca_example.png +0 -0
  88. npcsh-1.1.13.data/data/npcsh/npc_team/edit_file.jinx +94 -0
  89. npcsh-1.1.13.data/data/npcsh/npc_team/foreman.npc +7 -0
  90. npcsh-1.1.13.data/data/npcsh/npc_team/frederic.npc +6 -0
  91. npcsh-1.1.13.data/data/npcsh/npc_team/frederic4.png +0 -0
  92. npcsh-1.1.13.data/data/npcsh/npc_team/guac.jinx +317 -0
  93. npcsh-1.1.13.data/data/npcsh/npc_team/guac.png +0 -0
  94. npcsh-1.1.13.data/data/npcsh/npc_team/help.jinx +52 -0
  95. npcsh-1.1.13.data/data/npcsh/npc_team/init.jinx +41 -0
  96. npcsh-1.1.13.data/data/npcsh/npc_team/jinxs.jinx +32 -0
  97. npcsh-1.1.13.data/data/npcsh/npc_team/kadiefa.npc +3 -0
  98. npcsh-1.1.13.data/data/npcsh/npc_team/kadiefa.png +0 -0
  99. npcsh-1.1.13.data/data/npcsh/npc_team/load_file.jinx +35 -0
  100. npcsh-1.1.13.data/data/npcsh/npc_team/npc-studio.jinx +77 -0
  101. npcsh-1.1.13.data/data/npcsh/npc_team/npcsh.ctx +18 -0
  102. npcsh-1.1.13.data/data/npcsh/npc_team/npcsh_sibiji.png +0 -0
  103. npcsh-1.1.13.data/data/npcsh/npc_team/ots.jinx +61 -0
  104. npcsh-1.1.13.data/data/npcsh/npc_team/plonk.jinx +214 -0
  105. npcsh-1.1.13.data/data/npcsh/npc_team/plonk.npc +2 -0
  106. npcsh-1.1.13.data/data/npcsh/npc_team/plonk.png +0 -0
  107. npcsh-1.1.13.data/data/npcsh/npc_team/plonkjr.npc +2 -0
  108. npcsh-1.1.13.data/data/npcsh/npc_team/plonkjr.png +0 -0
  109. npcsh-1.1.13.data/data/npcsh/npc_team/pti.jinx +170 -0
  110. npcsh-1.1.13.data/data/npcsh/npc_team/python.jinx +11 -0
  111. npcsh-1.1.13.data/data/npcsh/npc_team/roll.jinx +68 -0
  112. npcsh-1.1.13.data/data/npcsh/npc_team/sample.jinx +56 -0
  113. npcsh-1.1.13.data/data/npcsh/npc_team/search.jinx +130 -0
  114. npcsh-1.1.13.data/data/npcsh/npc_team/serve.jinx +26 -0
  115. npcsh-1.1.13.data/data/npcsh/npc_team/set.jinx +40 -0
  116. npcsh-1.1.13.data/data/npcsh/npc_team/sh.jinx +34 -0
  117. npcsh-1.1.13.data/data/npcsh/npc_team/sibiji.npc +3 -0
  118. npcsh-1.1.13.data/data/npcsh/npc_team/sibiji.png +0 -0
  119. npcsh-1.1.13.data/data/npcsh/npc_team/sleep.jinx +116 -0
  120. npcsh-1.1.13.data/data/npcsh/npc_team/spool.jinx +161 -0
  121. npcsh-1.1.13.data/data/npcsh/npc_team/spool.png +0 -0
  122. npcsh-1.1.13.data/data/npcsh/npc_team/sql.jinx +16 -0
  123. npcsh-1.1.13.data/data/npcsh/npc_team/trigger.jinx +61 -0
  124. npcsh-1.1.13.data/data/npcsh/npc_team/usage.jinx +33 -0
  125. npcsh-1.1.13.data/data/npcsh/npc_team/vixynt.jinx +144 -0
  126. npcsh-1.1.13.data/data/npcsh/npc_team/wander.jinx +186 -0
  127. npcsh-1.1.13.data/data/npcsh/npc_team/yap.jinx +262 -0
  128. npcsh-1.1.13.data/data/npcsh/npc_team/yap.png +0 -0
  129. npcsh-1.1.13.dist-info/METADATA +522 -0
  130. npcsh-1.1.13.dist-info/RECORD +135 -0
  131. {npcsh-0.1.2.dist-info → npcsh-1.1.13.dist-info}/WHEEL +1 -1
  132. npcsh-1.1.13.dist-info/entry_points.txt +9 -0
  133. {npcsh-0.1.2.dist-info → npcsh-1.1.13.dist-info/licenses}/LICENSE +1 -1
  134. npcsh/command_history.py +0 -81
  135. npcsh/helpers.py +0 -36
  136. npcsh/llm_funcs.py +0 -295
  137. npcsh/main.py +0 -5
  138. npcsh/modes.py +0 -343
  139. npcsh/npc_compiler.py +0 -124
  140. npcsh-0.1.2.dist-info/METADATA +0 -99
  141. npcsh-0.1.2.dist-info/RECORD +0 -14
  142. npcsh-0.1.2.dist-info/entry_points.txt +0 -2
  143. {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
@@ -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.
@@ -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.
@@ -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