npcsh 1.1.13__py3-none-any.whl → 1.1.15__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (168) hide show
  1. npcsh/_state.py +491 -80
  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.13.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.13.data/data/npcsh/npc_team → npcsh/npc_team/jinxs/lib/core}/edit_file.jinx +3 -3
  23. {npcsh-1.1.13.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.13.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.13.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.13.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.13.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.13.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.13.data/data/npcsh/npc_team/agent.jinx +0 -17
  101. npcsh-1.1.13.data/data/npcsh/npc_team/alicanto.jinx +0 -194
  102. npcsh-1.1.13.data/data/npcsh/npc_team/alicanto.npc +0 -2
  103. npcsh-1.1.13.data/data/npcsh/npc_team/corca.jinx +0 -249
  104. npcsh-1.1.13.data/data/npcsh/npc_team/corca.npc +0 -12
  105. npcsh-1.1.13.data/data/npcsh/npc_team/foreman.npc +0 -7
  106. npcsh-1.1.13.data/data/npcsh/npc_team/frederic.npc +0 -6
  107. npcsh-1.1.13.data/data/npcsh/npc_team/guac.jinx +0 -317
  108. npcsh-1.1.13.data/data/npcsh/npc_team/jinxs.jinx +0 -32
  109. npcsh-1.1.13.data/data/npcsh/npc_team/kadiefa.npc +0 -3
  110. npcsh-1.1.13.data/data/npcsh/npc_team/plonk.jinx +0 -214
  111. npcsh-1.1.13.data/data/npcsh/npc_team/plonk.npc +0 -2
  112. npcsh-1.1.13.data/data/npcsh/npc_team/plonkjr.npc +0 -2
  113. npcsh-1.1.13.data/data/npcsh/npc_team/pti.jinx +0 -170
  114. npcsh-1.1.13.data/data/npcsh/npc_team/sibiji.npc +0 -3
  115. npcsh-1.1.13.data/data/npcsh/npc_team/wander.jinx +0 -186
  116. npcsh-1.1.13.dist-info/RECORD +0 -135
  117. npcsh-1.1.13.dist-info/entry_points.txt +0 -9
  118. npcsh-1.1.13.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.13.data → npcsh-1.1.15.data}/data/npcsh/npc_team/alicanto.png +0 -0
  137. {npcsh-1.1.13.data → npcsh-1.1.15.data}/data/npcsh/npc_team/chat.jinx +0 -0
  138. {npcsh-1.1.13.data → npcsh-1.1.15.data}/data/npcsh/npc_team/cmd.jinx +0 -0
  139. {npcsh-1.1.13.data → npcsh-1.1.15.data}/data/npcsh/npc_team/compile.jinx +0 -0
  140. {npcsh-1.1.13.data → npcsh-1.1.15.data}/data/npcsh/npc_team/compress.jinx +0 -0
  141. {npcsh-1.1.13.data → npcsh-1.1.15.data}/data/npcsh/npc_team/corca.png +0 -0
  142. {npcsh-1.1.13.data → npcsh-1.1.15.data}/data/npcsh/npc_team/corca_example.png +0 -0
  143. {npcsh-1.1.13.data → npcsh-1.1.15.data}/data/npcsh/npc_team/frederic4.png +0 -0
  144. {npcsh-1.1.13.data → npcsh-1.1.15.data}/data/npcsh/npc_team/guac.png +0 -0
  145. {npcsh-1.1.13.data → npcsh-1.1.15.data}/data/npcsh/npc_team/help.jinx +0 -0
  146. {npcsh-1.1.13.data → npcsh-1.1.15.data}/data/npcsh/npc_team/init.jinx +0 -0
  147. {npcsh-1.1.13.data → npcsh-1.1.15.data}/data/npcsh/npc_team/kadiefa.png +0 -0
  148. {npcsh-1.1.13.data → npcsh-1.1.15.data}/data/npcsh/npc_team/npc-studio.jinx +0 -0
  149. {npcsh-1.1.13.data → npcsh-1.1.15.data}/data/npcsh/npc_team/npcsh.ctx +0 -0
  150. {npcsh-1.1.13.data → npcsh-1.1.15.data}/data/npcsh/npc_team/npcsh_sibiji.png +0 -0
  151. {npcsh-1.1.13.data → npcsh-1.1.15.data}/data/npcsh/npc_team/ots.jinx +0 -0
  152. {npcsh-1.1.13.data → npcsh-1.1.15.data}/data/npcsh/npc_team/plonk.png +0 -0
  153. {npcsh-1.1.13.data → npcsh-1.1.15.data}/data/npcsh/npc_team/plonkjr.png +0 -0
  154. {npcsh-1.1.13.data → npcsh-1.1.15.data}/data/npcsh/npc_team/python.jinx +0 -0
  155. {npcsh-1.1.13.data → npcsh-1.1.15.data}/data/npcsh/npc_team/roll.jinx +0 -0
  156. {npcsh-1.1.13.data → npcsh-1.1.15.data}/data/npcsh/npc_team/sample.jinx +0 -0
  157. {npcsh-1.1.13.data → npcsh-1.1.15.data}/data/npcsh/npc_team/serve.jinx +0 -0
  158. {npcsh-1.1.13.data → npcsh-1.1.15.data}/data/npcsh/npc_team/set.jinx +0 -0
  159. {npcsh-1.1.13.data → npcsh-1.1.15.data}/data/npcsh/npc_team/sibiji.png +0 -0
  160. {npcsh-1.1.13.data → npcsh-1.1.15.data}/data/npcsh/npc_team/sleep.jinx +0 -0
  161. {npcsh-1.1.13.data → npcsh-1.1.15.data}/data/npcsh/npc_team/spool.jinx +0 -0
  162. {npcsh-1.1.13.data → npcsh-1.1.15.data}/data/npcsh/npc_team/spool.png +0 -0
  163. {npcsh-1.1.13.data → npcsh-1.1.15.data}/data/npcsh/npc_team/trigger.jinx +0 -0
  164. {npcsh-1.1.13.data → npcsh-1.1.15.data}/data/npcsh/npc_team/usage.jinx +0 -0
  165. {npcsh-1.1.13.data → npcsh-1.1.15.data}/data/npcsh/npc_team/yap.jinx +0 -0
  166. {npcsh-1.1.13.data → npcsh-1.1.15.data}/data/npcsh/npc_team/yap.png +0 -0
  167. {npcsh-1.1.13.dist-info → npcsh-1.1.15.dist-info}/WHEEL +0 -0
  168. {npcsh-1.1.13.dist-info → npcsh-1.1.15.dist-info}/licenses/LICENSE +0 -0
npcsh/mcp_server.py CHANGED
@@ -30,7 +30,8 @@ from typing import Optional, Dict, Any, List, Union, Callable, get_type_hints
30
30
  from functools import wraps
31
31
  import sys
32
32
 
33
- from npcpy.llm_funcs import generate_group_candidates, abstract, extract_facts, zoom_in, execute_llm_command, gen_image
33
+ from npcpy.llm_funcs import generate_group_candidates, abstract,
34
+ zoom_in, execute_llm_command, gen_image
34
35
  from npcpy.memory.search import search_similar_texts, execute_search_command, execute_rag_command, answer_with_rag, execute_brainblast_command
35
36
  from npcpy.data.load import load_file_contents
36
37
  from npcpy.memory.command_history import CommandHistory
npcsh/npc.py CHANGED
@@ -95,9 +95,20 @@ def main():
95
95
  global_model = args.model
96
96
  global_provider = args.provider
97
97
 
98
+ # Load team early so we can check for jinxs
99
+ try:
100
+ command_history, team, forenpc_obj = setup_shell()
101
+ # Load jinxs into router so they're recognized as commands
102
+ from npcsh._state import initialize_router_with_jinxs
103
+ initialize_router_with_jinxs(team, router)
104
+ except Exception as e:
105
+ print(f"Warning: Could not set up full npcsh environment: {e}", file=sys.stderr)
106
+ team = None
107
+ forenpc_obj = None
108
+
98
109
  is_valid_command = False
99
110
  command_name = None
100
-
111
+
101
112
  if all_args:
102
113
  first_arg = all_args[0]
103
114
  if first_arg.startswith('/'):
@@ -145,15 +156,8 @@ def main():
145
156
  if args.provider is None:
146
157
  args.provider = global_provider
147
158
 
148
- try:
149
- command_history, team, forenpc_obj = setup_shell()
150
- except Exception as e:
151
- print(
152
- f"Warning: Could not set up full npcsh environment: {e}",
153
- file=sys.stderr
154
- )
155
- print("Falling back to basic NPC loading...", file=sys.stderr)
156
- team = None
159
+ # Team already loaded above, just set up NPC
160
+ if not forenpc_obj:
157
161
  forenpc_obj = load_npc_by_name(args.npc, NPCSH_DB_PATH)
158
162
 
159
163
  npc_instance = None
@@ -173,25 +177,6 @@ def main():
173
177
  print(f"Error: Could not load NPC '{args.npc}'", file=sys.stderr)
174
178
  sys.exit(1)
175
179
 
176
- if not is_valid_command and all_args:
177
- first_arg = all_args[0]
178
-
179
- jinx_found = False
180
- if team and first_arg in team.jinxs_dict:
181
- jinx_found = True
182
- elif (
183
- isinstance(npc_instance, NPC)
184
- and hasattr(npc_instance, 'jinxs_dict')
185
- and first_arg in npc_instance.jinxs_dict
186
- ):
187
- jinx_found = True
188
-
189
- if jinx_found:
190
- is_valid_command = True
191
- command_name = '/' + first_arg
192
- all_args = all_args[1:]
193
- unknown_args = all_args
194
-
195
180
  shell_state = initial_state
196
181
  shell_state.npc = npc_instance
197
182
  shell_state.team = team
@@ -245,12 +230,13 @@ def main():
245
230
  )
246
231
 
247
232
  if (
248
- NPCSH_STREAM_OUTPUT
233
+ NPCSH_STREAM_OUTPUT
234
+ and output is not None
249
235
  and not isinstance(output, str)
250
236
  ):
251
237
  print_and_process_stream_with_markdown(
252
- output,
253
- model_for_stream,
238
+ output,
239
+ model_for_stream,
254
240
  provider_for_stream
255
241
  )
256
242
  elif output is not None:
@@ -319,5 +305,71 @@ def main():
319
305
  sys.exit(1)
320
306
 
321
307
 
308
+ def jinx_main():
309
+ """Entry point for bin jinxs called directly from CLI.
310
+
311
+ Parses arguments as key=value pairs and executes the jinx.
312
+ Example: nql show=1 model=daily_summary
313
+ """
314
+ import os
315
+
316
+ # Get jinx name from command name
317
+ jinx_name = os.path.basename(sys.argv[0])
318
+
319
+ # Parse remaining args as key=value pairs
320
+ args = sys.argv[1:]
321
+ jinx_args = []
322
+
323
+ for arg in args:
324
+ if arg in ['-h', '--help']:
325
+ print(f"Usage: {jinx_name} [key=value ...]")
326
+ print(f"\nRun the '{jinx_name}' jinx with specified parameters.")
327
+ print(f"\nExamples:")
328
+ print(f" {jinx_name} show=1")
329
+ print(f" {jinx_name} model=my_model db=~/mydb.db")
330
+ print(f"\nOr use: npc {jinx_name} [key=value ...]")
331
+ sys.exit(0)
332
+ jinx_args.append(arg)
333
+
334
+ # Build command string
335
+ if jinx_args:
336
+ command = f"/{jinx_name} " + " ".join(jinx_args)
337
+ else:
338
+ command = f"/{jinx_name}"
339
+
340
+ try:
341
+ _, team, forenpc_obj = setup_shell()
342
+
343
+ from npcsh._state import initialize_router_with_jinxs
344
+ initialize_router_with_jinxs(team, router)
345
+
346
+ # Update the global initial_state with team/npc context
347
+ initial_state.team = team
348
+ initial_state.npc = forenpc_obj
349
+ if forenpc_obj:
350
+ initial_state.chat_model = forenpc_obj.model or NPCSH_CHAT_MODEL
351
+ initial_state.chat_provider = forenpc_obj.provider or NPCSH_CHAT_PROVIDER
352
+
353
+ _, result = execute_slash_command(
354
+ command,
355
+ stdin_input=None,
356
+ state=initial_state,
357
+ stream=NPCSH_STREAM_OUTPUT,
358
+ router=router
359
+ )
360
+
361
+ if isinstance(result, dict):
362
+ output = result.get("output") or result.get("response")
363
+ if output is not None:
364
+ render_markdown(str(output))
365
+ elif result is not None:
366
+ render_markdown(str(result))
367
+
368
+ except Exception as e:
369
+ print(f"Error executing jinx '{jinx_name}': {e}", file=sys.stderr)
370
+ traceback.print_exc()
371
+ sys.exit(1)
372
+
373
+
322
374
  if __name__ == "__main__":
323
375
  main()
@@ -1,2 +1,23 @@
1
1
  name: alicanto
2
- primary_directive: You are Alicanto the mythical bird. You have been spotted and it is your job to lead users to explore the world.
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/*
npcsh/npc_team/corca.npc CHANGED
@@ -1,12 +1,31 @@
1
1
  name: corca
2
+ ascii_art: |
3
+ ██████ ██████ ██████ ██████ ██████
4
+ ██ ██ ██ ██ ██ ██ ██ ██ ██🦌🦌██
5
+ ██ ██ ██ ██ ██ ██ ██🦌🦌██
6
+ ██ ██ ██ ████████ ██ ████████
7
+ ██ ██ ██ ██ ███ ██ ██ ██
8
+ ██ ██ ██ ██ ██ ███ ██ ██ ██ ██
9
+ ██████ ██████ ██ ███ ██████ ██ ██
10
+ colors:
11
+ top: "64,224,208"
12
+ bottom: "255,165,0"
2
13
  primary_directive: |
3
- You are corca, a distinguished member of the NPC team.
4
- Your expertise is in the area of software development and
5
- you have a knack for thinking through problems carefully.
6
- You favor solutions that prioritize simplicity and clarity and
7
- ought to always consider how some suggestion may increase rather than reduce tech debt
8
- unnecessarily. Now, the key is in this last term, "unnecessarily".
9
- You must distinguish carefully and when in doubt, opt to ask for further
10
- information or clarification with concrete clear options that make it
11
- easy for a user to choose.
14
+ You are corca, the software development specialist of the NPC team.
15
+ Your expertise is in writing, reviewing, and debugging code.
16
+ You think through problems carefully and favor solutions that prioritize simplicity and clarity.
17
+ Always consider how suggestions may increase rather than reduce tech debt unnecessarily.
18
+ When in doubt, ask for clarification with concrete options that make it easy for users to choose.
19
+
20
+ CRITICAL: You MUST ALWAYS use FULL ABSOLUTE PATHS for all file operations.
21
+ - NEVER use relative paths like "apps/api" or "./src"
22
+ - ALWAYS expand paths starting from root like "/Users/username/project/apps/api"
23
+ - When given a task, first determine the absolute path of the working directory using pwd
24
+ - Prefix all file paths with the full absolute path
25
+ jinxs:
26
+ - lib/core/sh
27
+ - lib/core/python
28
+ - lib/core/edit_file
29
+ - lib/core/load_file
30
+ - lib/core/search
12
31
 
@@ -1,6 +1,27 @@
1
1
  name: frederic
2
+ ascii_art: |
3
+ ❄️❄️❄️❄️❄️❄️❄️❄️❄️❄️❄️❄️❄️❄️❄️❄️❄️❄️❄️❄️❄️❄️❄️❄️❄️❄️❄️❄️❄️❄️❄️❄️❄️❄️❄️❄️❄️❄️❄️❄️❄️❄️❄️❄️❄️❄️❄️❄️❄️
4
+ ███████ ██████ ███████ ██████ ███████ ██████ ██ ██████
5
+ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
6
+ █████ ██████ █████ ██ ██ █████ ██████ ██ ██ 🐻‍❄️
7
+ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
8
+ ██ ██ ██ ███████ ██████ ███████ ██ ██ ██ ██████
9
+ ❄️❄️❄️❄️❄️❄️❄️❄️❄️❄️❄️❄️❄️❄️❄️❄️❄️❄️❄️❄️❄️❄️❄️❄️❄️❄️❄️❄️❄️❄️❄️❄️❄️❄️❄️❄️❄️❄️❄️❄️❄️❄️❄️❄️❄️❄️❄️❄️❄️
10
+ colors:
11
+ top: "224,255,255"
12
+ bottom: "173,216,230"
2
13
  primary_directive: |
3
- You are frederic the polar bear. Your job is help users think through problems and
4
- to provide straightforward ways forward on problems. Cut through the ice
5
- to get to what matters and keep things simple. You are to respond in a
6
- witty tone like richard feynman but with the romantic tambor of Frederic Chopin.
14
+ You are frederic the polar bear - a fusion of Richard Feynman and Frederic Chopin.
15
+ You have Feynman's playful curiosity, his ability to explain complex physics simply,
16
+ and his irreverent wit. You also have Chopin's romantic soul, his passion for music,
17
+ and his ability to find beauty in mathematical structures.
18
+ You help users with hard math, physics problems, and music composition.
19
+ Cut through the ice to get to what matters. Make the complex feel simple and beautiful.
20
+ jinxs:
21
+ - lib/core/python
22
+ - lib/core/sql
23
+ - lib/core/sh
24
+ - lib/core/load_file
25
+ - lib/core/search
26
+ - lib/gen/*
27
+ - bin/wander
@@ -0,0 +1,22 @@
1
+ name: guac
2
+ ascii_art: |
3
+ 🟢🟢🟢🟢🟢
4
+ 🟢 🟢
5
+ 🟢
6
+ 🟢
7
+ 🟢
8
+ 🟢 🟢🟢🟢 🟢 🟢 🟢🟢🟢 🟢🟢🟢
9
+ 🟢 🟢 🟢 🟢 ⚫⚫🟢 🟢
10
+ 🟢 🟢 🟢 🟢 ⚫🥑🧅⚫ 🟢
11
+ 🟢 🟢 🟢 🟢 ⚫🥑🍅⚫ 🟢
12
+ 🟢🟢🟢🟢🟢🟢 🟢🟢🟢🟢 ⚫⚫🟢 🟢🟢🟢
13
+ primary_directive: |
14
+ You are guac, the data analysis specialist of the NPC team.
15
+ Your expertise is in loading, analyzing, and visualizing data.
16
+ You work with pandas DataFrames, numpy arrays, and matplotlib plots.
17
+ Help users load data files, run Python code for analysis, and create visualizations.
18
+ jinxs:
19
+ - lib/core/python
20
+ - lib/core/sql
21
+ - lib/core/sh
22
+ - lib/core/load_file
@@ -0,0 +1,141 @@
1
+ jinx_name: nql
2
+ description: "Run NPC-SQL models with AI-powered transformations. Supports cron scheduling."
3
+ inputs:
4
+ - models_dir: "~/.npcsh/npc_team/models"
5
+ - db: "~/npcsh_history.db"
6
+ - model: ""
7
+ - schema: ""
8
+ - show: ""
9
+ - cron: ""
10
+ - install_cron: ""
11
+
12
+ steps:
13
+ - name: run_nql
14
+ engine: python
15
+ code: |
16
+ import os
17
+ import sys
18
+ from pathlib import Path
19
+
20
+ models_dir = context.get('models_dir') or '~/.npcsh/npc_team/models'
21
+ db_path = context.get('db') or '~/npcsh_history.db'
22
+ model_name = context.get('model') or ''
23
+ schema = context.get('schema') or ''
24
+ list_models = context.get('show') or ''
25
+ cron_expr = context.get('cron') or ''
26
+ install_cron = context.get('install_cron') or ''
27
+
28
+ models_dir = os.path.expanduser(models_dir)
29
+ db_path = os.path.expanduser(db_path)
30
+
31
+ # Find NPC team directory
32
+ npc_dir = None
33
+ if os.path.exists('./npc_team'):
34
+ npc_dir = './npc_team'
35
+ elif os.path.exists(os.path.expanduser('~/.npcsh/npc_team')):
36
+ npc_dir = os.path.expanduser('~/.npcsh/npc_team')
37
+
38
+ # Import npcsql
39
+ try:
40
+ from npcpy.sql.npcsql import ModelCompiler, SQLModel
41
+ except ImportError:
42
+ output = "Error: npcpy.sql.npcsql not found. Install npcpy first."
43
+ raise SystemExit(1)
44
+
45
+ # List models mode
46
+ if list_models:
47
+ if not os.path.exists(models_dir):
48
+ output = "No models directory found at " + models_dir
49
+ else:
50
+ models_path = Path(models_dir)
51
+ sql_files = list(models_path.glob("**/*.sql"))
52
+ if not sql_files:
53
+ output = "No .sql models found in " + models_dir
54
+ else:
55
+ lines = ["Available NQL models in " + models_dir + ":", ""]
56
+ for f in sorted(sql_files):
57
+ rel = f.relative_to(models_path)
58
+ # Check for nql functions
59
+ with open(f, 'r') as fh:
60
+ content = fh.read()
61
+ has_nql = "nql." in content
62
+ marker = " [NQL]" if has_nql else ""
63
+ lines.append(" " + str(rel) + marker)
64
+ output = "\n".join(lines)
65
+
66
+ # Install cron mode
67
+ elif install_cron:
68
+ import subprocess
69
+ # Parse cron expression and model
70
+ parts = install_cron.split()
71
+ if len(parts) < 5:
72
+ output = "Error: cron expression must have 5 fields (min hour day month weekday)"
73
+ else:
74
+ cron_time = " ".join(parts[:5])
75
+ cron_model = parts[5] if len(parts) > 5 else ""
76
+
77
+ # Build command
78
+ nql_cmd = "npc nql"
79
+ if cron_model:
80
+ nql_cmd += " model=" + cron_model
81
+ nql_cmd += " models_dir=" + models_dir
82
+ nql_cmd += " db=" + db_path
83
+ if schema:
84
+ nql_cmd += " schema=" + schema
85
+
86
+ cron_line = cron_time + " " + nql_cmd
87
+
88
+ # Get current crontab
89
+ try:
90
+ result = subprocess.run(['crontab', '-l'], capture_output=True, text=True)
91
+ current = result.stdout if result.returncode == 0 else ""
92
+ except:
93
+ current = ""
94
+
95
+ # Check if already installed
96
+ if nql_cmd in current:
97
+ output = "Cron job already exists for: " + nql_cmd
98
+ else:
99
+ # Add new cron line
100
+ new_crontab = current.rstrip() + "\n" + cron_line + "\n"
101
+ proc = subprocess.run(['crontab', '-'], input=new_crontab, text=True, capture_output=True)
102
+ if proc.returncode == 0:
103
+ output = "Installed cron job:\n " + cron_line
104
+ else:
105
+ output = "Failed to install cron: " + proc.stderr
106
+
107
+ # Run models mode
108
+ else:
109
+ if not os.path.exists(models_dir):
110
+ output = "Models directory not found: " + models_dir + "\nCreate it with: mkdir -p " + models_dir
111
+ else:
112
+ try:
113
+ compiler = ModelCompiler(
114
+ models_dir=models_dir,
115
+ target_engine=db_path,
116
+ npc_directory=npc_dir,
117
+ target_schema=schema if schema else None
118
+ )
119
+
120
+ if model_name:
121
+ # Run specific model
122
+ compiler.discover_models()
123
+ if model_name not in compiler.models:
124
+ available = list(compiler.models.keys())
125
+ output = "Model '" + model_name + "' not found. Available: " + str(available)
126
+ else:
127
+ print("Running model: " + model_name)
128
+ df = compiler.execute_model(model_name)
129
+ output = "Model '" + model_name + "' completed. Rows: " + str(len(df))
130
+ else:
131
+ # Run all models in dependency order
132
+ results = compiler.run_all_models()
133
+ lines = ["NQL run complete:", ""]
134
+ for name, df in results.items():
135
+ lines.append(" " + name + ": " + str(len(df)) + " rows")
136
+ output = "\n".join(lines)
137
+
138
+ except Exception as e:
139
+ output = "NQL Error: " + str(e)
140
+ import traceback
141
+ traceback.print_exc()
@@ -0,0 +1,230 @@
1
+ jinx_name: "sync"
2
+ description: "Sync npc_team files from the npcsh repo to ~/.npcsh/npc_team. Detects local modifications before overwriting."
3
+ inputs:
4
+ - force: "" # Use --force or -f to overwrite all files without prompting
5
+ - dry_run: "" # Use --dry-run or -d to preview changes without applying them
6
+ - jinxs: "" # Use --jinxs to sync only .jinx files
7
+ - npcs: "" # Use --npcs to sync only .npc files
8
+ - ctx: "" # Use --ctx to sync only .ctx files
9
+ - images: "" # Use --images to sync only image files (.png, .jpg, .jpeg)
10
+ steps:
11
+ - name: "sync_npc_team"
12
+ engine: "python"
13
+ code: |
14
+ import os
15
+ import hashlib
16
+ import shutil
17
+ from pathlib import Path
18
+ from datetime import datetime
19
+
20
+ force = context.get('force', False)
21
+ dry_run = context.get('dry_run', False)
22
+ sync_jinxs = context.get('jinxs', False)
23
+ sync_npcs = context.get('npcs', False)
24
+ sync_ctx = context.get('ctx', False)
25
+ sync_images = context.get('images', False)
26
+
27
+ # Convert string flags to boolean
28
+ def to_bool(val):
29
+ if isinstance(val, bool):
30
+ return val
31
+ if isinstance(val, str):
32
+ return val.lower() in ('true', '1', 'yes', 'y')
33
+ return bool(val)
34
+
35
+ force = to_bool(force)
36
+ dry_run = to_bool(dry_run)
37
+ sync_jinxs = to_bool(sync_jinxs)
38
+ sync_npcs = to_bool(sync_npcs)
39
+ sync_ctx = to_bool(sync_ctx)
40
+ sync_images = to_bool(sync_images)
41
+
42
+ # If none specified, sync all
43
+ sync_all = not (sync_jinxs or sync_npcs or sync_ctx or sync_images)
44
+
45
+ # Find the repo npc_team directory
46
+ # Check common locations
47
+ possible_repo_paths = [
48
+ Path.home() / "npcww" / "npc-core" / "npcsh" / "npcsh" / "npc_team",
49
+ Path.home() / "npc-core" / "npcsh" / "npcsh" / "npc_team",
50
+ Path.home() / "repos" / "npcsh" / "npcsh" / "npc_team",
51
+ Path.home() / "Projects" / "npcsh" / "npcsh" / "npc_team",
52
+ ]
53
+
54
+ # Also check if we can find it via pip show
55
+ try:
56
+ import subprocess
57
+ result = subprocess.run(['pip', 'show', 'npcsh', '-f'], capture_output=True, text=True)
58
+ if result.returncode == 0:
59
+ for line in result.stdout.split('\n'):
60
+ if line.startswith('Location:'):
61
+ pip_path = Path(line.split(':', 1)[1].strip()) / "npcsh" / "npc_team"
62
+ if pip_path.exists():
63
+ possible_repo_paths.insert(0, pip_path)
64
+ except:
65
+ pass
66
+
67
+ repo_npc_team = None
68
+ for path in possible_repo_paths:
69
+ if path.exists() and path.is_dir():
70
+ repo_npc_team = path
71
+ break
72
+
73
+ local_npc_team = Path.home() / ".npcsh" / "npc_team"
74
+
75
+ if not repo_npc_team:
76
+ context['output'] = "Error: Could not find npcsh repo npc_team directory.\nSearched:\n" + "\n".join(f" - {p}" for p in possible_repo_paths)
77
+ exit()
78
+
79
+ if not local_npc_team.exists():
80
+ context['output'] = f"Error: Local npc_team directory not found at {local_npc_team}"
81
+ exit()
82
+
83
+ def get_file_hash(filepath):
84
+ """Get MD5 hash of file contents."""
85
+ try:
86
+ with open(filepath, 'rb') as f:
87
+ return hashlib.md5(f.read()).hexdigest()
88
+ except:
89
+ return None
90
+
91
+ def get_files_recursive(base_path, extensions=None):
92
+ """Get all files recursively, optionally filtered by extensions."""
93
+ files = []
94
+ for root, dirs, filenames in os.walk(base_path):
95
+ # Skip .git directories
96
+ dirs[:] = [d for d in dirs if d != '.git']
97
+ for filename in filenames:
98
+ if filename.startswith('.'):
99
+ continue
100
+ if extensions and not any(filename.endswith(ext) for ext in extensions):
101
+ continue
102
+ full_path = Path(root) / filename
103
+ rel_path = full_path.relative_to(base_path)
104
+ files.append(rel_path)
105
+ return files
106
+
107
+ # Build list of extensions to sync based on flags
108
+ sync_extensions = []
109
+ if sync_all or sync_npcs:
110
+ sync_extensions.append('.npc')
111
+ if sync_all or sync_ctx:
112
+ sync_extensions.append('.ctx')
113
+ if sync_all or sync_jinxs:
114
+ sync_extensions.append('.jinx')
115
+ if sync_all or sync_images:
116
+ sync_extensions.extend(['.png', '.jpg', '.jpeg'])
117
+
118
+ # Get files from repo
119
+ repo_files = get_files_recursive(repo_npc_team, sync_extensions)
120
+
121
+ output_lines = []
122
+ output_lines.append(f"Syncing from: {repo_npc_team}")
123
+ output_lines.append(f"Syncing to: {local_npc_team}")
124
+
125
+ # Show what's being synced
126
+ sync_types = []
127
+ if sync_all:
128
+ sync_types.append("all")
129
+ else:
130
+ if sync_npcs: sync_types.append("npcs")
131
+ if sync_ctx: sync_types.append("ctx")
132
+ if sync_jinxs: sync_types.append("jinxs")
133
+ if sync_images: sync_types.append("images")
134
+ output_lines.append(f"Syncing: {', '.join(sync_types)}")
135
+
136
+ if dry_run:
137
+ output_lines.append("\n[DRY RUN - No changes will be made]\n")
138
+ output_lines.append("")
139
+
140
+ new_files = []
141
+ updated_files = []
142
+ modified_locally = []
143
+ unchanged_files = []
144
+
145
+ for rel_path in repo_files:
146
+ repo_file = repo_npc_team / rel_path
147
+ local_file = local_npc_team / rel_path
148
+
149
+ if not local_file.exists():
150
+ new_files.append(rel_path)
151
+ else:
152
+ repo_hash = get_file_hash(repo_file)
153
+ local_hash = get_file_hash(local_file)
154
+
155
+ if repo_hash == local_hash:
156
+ unchanged_files.append(rel_path)
157
+ else:
158
+ # Check if local file is newer (possibly modified by user)
159
+ repo_mtime = repo_file.stat().st_mtime
160
+ local_mtime = local_file.stat().st_mtime
161
+
162
+ if local_mtime > repo_mtime:
163
+ modified_locally.append((rel_path, local_mtime, repo_mtime))
164
+ else:
165
+ updated_files.append(rel_path)
166
+
167
+ # Report findings
168
+ if new_files:
169
+ output_lines.append(f"New files to add ({len(new_files)}):")
170
+ for f in new_files:
171
+ output_lines.append(f" + {f}")
172
+ output_lines.append("")
173
+
174
+ if updated_files:
175
+ output_lines.append(f"Files to update ({len(updated_files)}):")
176
+ for f in updated_files:
177
+ output_lines.append(f" ~ {f}")
178
+ output_lines.append("")
179
+
180
+ if modified_locally:
181
+ output_lines.append(f"Locally modified files ({len(modified_locally)}):")
182
+ for f, local_t, repo_t in modified_locally:
183
+ local_dt = datetime.fromtimestamp(local_t).strftime('%Y-%m-%d %H:%M')
184
+ repo_dt = datetime.fromtimestamp(repo_t).strftime('%Y-%m-%d %H:%M')
185
+ output_lines.append(f" ! {f}")
186
+ output_lines.append(f" local: {local_dt} repo: {repo_dt}")
187
+ if not force:
188
+ output_lines.append(" (use --force to overwrite these)")
189
+ output_lines.append("")
190
+
191
+ if unchanged_files:
192
+ output_lines.append(f"Already up to date: {len(unchanged_files)} files")
193
+ output_lines.append("")
194
+
195
+ # Perform sync if not dry run
196
+ if not dry_run:
197
+ synced = 0
198
+ skipped = 0
199
+
200
+ # Sync new files
201
+ for rel_path in new_files:
202
+ src = repo_npc_team / rel_path
203
+ dst = local_npc_team / rel_path
204
+ dst.parent.mkdir(parents=True, exist_ok=True)
205
+ shutil.copy2(src, dst)
206
+ synced += 1
207
+
208
+ # Sync updated files
209
+ for rel_path in updated_files:
210
+ src = repo_npc_team / rel_path
211
+ dst = local_npc_team / rel_path
212
+ dst.parent.mkdir(parents=True, exist_ok=True)
213
+ shutil.copy2(src, dst)
214
+ synced += 1
215
+
216
+ # Handle locally modified files
217
+ for rel_path, _, _ in modified_locally:
218
+ if force:
219
+ src = repo_npc_team / rel_path
220
+ dst = local_npc_team / rel_path
221
+ shutil.copy2(src, dst)
222
+ synced += 1
223
+ else:
224
+ skipped += 1
225
+
226
+ output_lines.append(f"Synced: {synced} files")
227
+ if skipped:
228
+ output_lines.append(f"Skipped: {skipped} locally modified files")
229
+
230
+ context['output'] = "\n".join(output_lines)