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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (168) hide show
  1. npcsh/_state.py +488 -77
  2. npcsh/mcp_server.py +2 -1
  3. npcsh/npc.py +84 -32
  4. npcsh/npc_team/alicanto.npc +22 -1
  5. npcsh/npc_team/corca.npc +28 -9
  6. npcsh/npc_team/frederic.npc +25 -4
  7. npcsh/npc_team/guac.npc +22 -0
  8. npcsh/npc_team/jinxs/bin/nql.jinx +141 -0
  9. npcsh/npc_team/jinxs/bin/sync.jinx +230 -0
  10. {npcsh-1.1.14.data/data/npcsh/npc_team → npcsh/npc_team/jinxs/bin}/vixynt.jinx +8 -30
  11. npcsh/npc_team/jinxs/bin/wander.jinx +152 -0
  12. npcsh/npc_team/jinxs/lib/browser/browser_action.jinx +220 -0
  13. npcsh/npc_team/jinxs/lib/browser/browser_screenshot.jinx +40 -0
  14. npcsh/npc_team/jinxs/lib/browser/close_browser.jinx +14 -0
  15. npcsh/npc_team/jinxs/lib/browser/open_browser.jinx +43 -0
  16. npcsh/npc_team/jinxs/lib/computer_use/click.jinx +23 -0
  17. npcsh/npc_team/jinxs/lib/computer_use/key_press.jinx +26 -0
  18. npcsh/npc_team/jinxs/lib/computer_use/launch_app.jinx +37 -0
  19. npcsh/npc_team/jinxs/lib/computer_use/screenshot.jinx +23 -0
  20. npcsh/npc_team/jinxs/lib/computer_use/type_text.jinx +27 -0
  21. npcsh/npc_team/jinxs/lib/computer_use/wait.jinx +21 -0
  22. {npcsh-1.1.14.data/data/npcsh/npc_team → npcsh/npc_team/jinxs/lib/core}/edit_file.jinx +3 -3
  23. {npcsh-1.1.14.data/data/npcsh/npc_team → npcsh/npc_team/jinxs/lib/core}/load_file.jinx +1 -1
  24. npcsh/npc_team/jinxs/lib/core/paste.jinx +134 -0
  25. {npcsh-1.1.14.data/data/npcsh/npc_team → npcsh/npc_team/jinxs/lib/core}/search.jinx +2 -1
  26. npcsh/npc_team/jinxs/{code → lib/core}/sh.jinx +2 -8
  27. npcsh/npc_team/jinxs/{code → lib/core}/sql.jinx +1 -1
  28. npcsh/npc_team/jinxs/lib/orchestration/convene.jinx +232 -0
  29. npcsh/npc_team/jinxs/lib/orchestration/delegate.jinx +184 -0
  30. npcsh/npc_team/jinxs/lib/research/arxiv.jinx +76 -0
  31. npcsh/npc_team/jinxs/lib/research/paper_search.jinx +101 -0
  32. npcsh/npc_team/jinxs/lib/research/semantic_scholar.jinx +69 -0
  33. npcsh/npc_team/jinxs/{utils/core → lib/utils}/build.jinx +8 -8
  34. npcsh/npc_team/jinxs/lib/utils/jinxs.jinx +176 -0
  35. npcsh/npc_team/jinxs/lib/utils/shh.jinx +17 -0
  36. npcsh/npc_team/jinxs/lib/utils/switch.jinx +62 -0
  37. npcsh/npc_team/jinxs/lib/utils/switches.jinx +61 -0
  38. npcsh/npc_team/jinxs/lib/utils/teamviz.jinx +205 -0
  39. npcsh/npc_team/jinxs/lib/utils/verbose.jinx +17 -0
  40. npcsh/npc_team/kadiefa.npc +19 -1
  41. npcsh/npc_team/plonk.npc +26 -1
  42. npcsh/npc_team/plonkjr.npc +22 -1
  43. npcsh/npc_team/sibiji.npc +23 -2
  44. npcsh/npcsh.py +153 -39
  45. npcsh/ui.py +22 -1
  46. npcsh-1.1.15.data/data/npcsh/npc_team/alicanto.npc +23 -0
  47. npcsh-1.1.15.data/data/npcsh/npc_team/arxiv.jinx +76 -0
  48. npcsh-1.1.15.data/data/npcsh/npc_team/browser_action.jinx +220 -0
  49. npcsh-1.1.15.data/data/npcsh/npc_team/browser_screenshot.jinx +40 -0
  50. {npcsh-1.1.14.data → npcsh-1.1.15.data}/data/npcsh/npc_team/build.jinx +8 -8
  51. npcsh-1.1.15.data/data/npcsh/npc_team/click.jinx +23 -0
  52. npcsh-1.1.15.data/data/npcsh/npc_team/close_browser.jinx +14 -0
  53. npcsh-1.1.15.data/data/npcsh/npc_team/convene.jinx +232 -0
  54. npcsh-1.1.15.data/data/npcsh/npc_team/corca.npc +31 -0
  55. npcsh-1.1.15.data/data/npcsh/npc_team/delegate.jinx +184 -0
  56. {npcsh/npc_team/jinxs/utils → npcsh-1.1.15.data/data/npcsh/npc_team}/edit_file.jinx +3 -3
  57. npcsh-1.1.15.data/data/npcsh/npc_team/frederic.npc +27 -0
  58. npcsh-1.1.15.data/data/npcsh/npc_team/guac.npc +22 -0
  59. npcsh-1.1.15.data/data/npcsh/npc_team/jinxs.jinx +176 -0
  60. npcsh-1.1.15.data/data/npcsh/npc_team/kadiefa.npc +21 -0
  61. npcsh-1.1.15.data/data/npcsh/npc_team/key_press.jinx +26 -0
  62. npcsh-1.1.15.data/data/npcsh/npc_team/launch_app.jinx +37 -0
  63. {npcsh/npc_team/jinxs/utils → npcsh-1.1.15.data/data/npcsh/npc_team}/load_file.jinx +1 -1
  64. npcsh-1.1.15.data/data/npcsh/npc_team/nql.jinx +141 -0
  65. npcsh-1.1.15.data/data/npcsh/npc_team/open_browser.jinx +43 -0
  66. npcsh-1.1.15.data/data/npcsh/npc_team/paper_search.jinx +101 -0
  67. npcsh-1.1.15.data/data/npcsh/npc_team/paste.jinx +134 -0
  68. npcsh-1.1.15.data/data/npcsh/npc_team/plonk.npc +27 -0
  69. npcsh-1.1.15.data/data/npcsh/npc_team/plonkjr.npc +23 -0
  70. npcsh-1.1.15.data/data/npcsh/npc_team/screenshot.jinx +23 -0
  71. {npcsh/npc_team/jinxs/utils → npcsh-1.1.15.data/data/npcsh/npc_team}/search.jinx +2 -1
  72. npcsh-1.1.15.data/data/npcsh/npc_team/semantic_scholar.jinx +69 -0
  73. {npcsh-1.1.14.data → npcsh-1.1.15.data}/data/npcsh/npc_team/sh.jinx +2 -8
  74. npcsh-1.1.15.data/data/npcsh/npc_team/shh.jinx +17 -0
  75. npcsh-1.1.15.data/data/npcsh/npc_team/sibiji.npc +24 -0
  76. {npcsh-1.1.14.data → npcsh-1.1.15.data}/data/npcsh/npc_team/sql.jinx +1 -1
  77. npcsh-1.1.15.data/data/npcsh/npc_team/switch.jinx +62 -0
  78. npcsh-1.1.15.data/data/npcsh/npc_team/switches.jinx +61 -0
  79. npcsh-1.1.15.data/data/npcsh/npc_team/sync.jinx +230 -0
  80. npcsh-1.1.15.data/data/npcsh/npc_team/teamviz.jinx +205 -0
  81. npcsh-1.1.15.data/data/npcsh/npc_team/type_text.jinx +27 -0
  82. npcsh-1.1.15.data/data/npcsh/npc_team/verbose.jinx +17 -0
  83. {npcsh/npc_team/jinxs/utils → npcsh-1.1.15.data/data/npcsh/npc_team}/vixynt.jinx +8 -30
  84. npcsh-1.1.15.data/data/npcsh/npc_team/wait.jinx +21 -0
  85. npcsh-1.1.15.data/data/npcsh/npc_team/wander.jinx +152 -0
  86. {npcsh-1.1.14.dist-info → npcsh-1.1.15.dist-info}/METADATA +399 -58
  87. npcsh-1.1.15.dist-info/RECORD +170 -0
  88. npcsh-1.1.15.dist-info/entry_points.txt +19 -0
  89. npcsh-1.1.15.dist-info/top_level.txt +2 -0
  90. project/__init__.py +1 -0
  91. npcsh/npc_team/foreman.npc +0 -7
  92. npcsh/npc_team/jinxs/modes/alicanto.jinx +0 -194
  93. npcsh/npc_team/jinxs/modes/corca.jinx +0 -249
  94. npcsh/npc_team/jinxs/modes/guac.jinx +0 -317
  95. npcsh/npc_team/jinxs/modes/plonk.jinx +0 -214
  96. npcsh/npc_team/jinxs/modes/pti.jinx +0 -170
  97. npcsh/npc_team/jinxs/modes/wander.jinx +0 -186
  98. npcsh/npc_team/jinxs/utils/agent.jinx +0 -17
  99. npcsh/npc_team/jinxs/utils/core/jinxs.jinx +0 -32
  100. npcsh-1.1.14.data/data/npcsh/npc_team/agent.jinx +0 -17
  101. npcsh-1.1.14.data/data/npcsh/npc_team/alicanto.jinx +0 -194
  102. npcsh-1.1.14.data/data/npcsh/npc_team/alicanto.npc +0 -2
  103. npcsh-1.1.14.data/data/npcsh/npc_team/corca.jinx +0 -249
  104. npcsh-1.1.14.data/data/npcsh/npc_team/corca.npc +0 -12
  105. npcsh-1.1.14.data/data/npcsh/npc_team/foreman.npc +0 -7
  106. npcsh-1.1.14.data/data/npcsh/npc_team/frederic.npc +0 -6
  107. npcsh-1.1.14.data/data/npcsh/npc_team/guac.jinx +0 -317
  108. npcsh-1.1.14.data/data/npcsh/npc_team/jinxs.jinx +0 -32
  109. npcsh-1.1.14.data/data/npcsh/npc_team/kadiefa.npc +0 -3
  110. npcsh-1.1.14.data/data/npcsh/npc_team/plonk.jinx +0 -214
  111. npcsh-1.1.14.data/data/npcsh/npc_team/plonk.npc +0 -2
  112. npcsh-1.1.14.data/data/npcsh/npc_team/plonkjr.npc +0 -2
  113. npcsh-1.1.14.data/data/npcsh/npc_team/pti.jinx +0 -170
  114. npcsh-1.1.14.data/data/npcsh/npc_team/sibiji.npc +0 -3
  115. npcsh-1.1.14.data/data/npcsh/npc_team/wander.jinx +0 -186
  116. npcsh-1.1.14.dist-info/RECORD +0 -135
  117. npcsh-1.1.14.dist-info/entry_points.txt +0 -9
  118. npcsh-1.1.14.dist-info/top_level.txt +0 -1
  119. /npcsh/npc_team/jinxs/{utils → bin}/roll.jinx +0 -0
  120. /npcsh/npc_team/jinxs/{utils → bin}/sample.jinx +0 -0
  121. /npcsh/npc_team/jinxs/{modes → bin}/spool.jinx +0 -0
  122. /npcsh/npc_team/jinxs/{modes → bin}/yap.jinx +0 -0
  123. /npcsh/npc_team/jinxs/{utils → lib/computer_use}/trigger.jinx +0 -0
  124. /npcsh/npc_team/jinxs/{utils → lib/core}/chat.jinx +0 -0
  125. /npcsh/npc_team/jinxs/{utils → lib/core}/cmd.jinx +0 -0
  126. /npcsh/npc_team/jinxs/{utils → lib/core}/compress.jinx +0 -0
  127. /npcsh/npc_team/jinxs/{utils → lib/core}/ots.jinx +0 -0
  128. /npcsh/npc_team/jinxs/{code → lib/core}/python.jinx +0 -0
  129. /npcsh/npc_team/jinxs/{utils → lib/core}/sleep.jinx +0 -0
  130. /npcsh/npc_team/jinxs/{utils/core → lib/utils}/compile.jinx +0 -0
  131. /npcsh/npc_team/jinxs/{utils/core → lib/utils}/help.jinx +0 -0
  132. /npcsh/npc_team/jinxs/{utils/core → lib/utils}/init.jinx +0 -0
  133. /npcsh/npc_team/jinxs/{utils → lib/utils}/serve.jinx +0 -0
  134. /npcsh/npc_team/jinxs/{utils/core → lib/utils}/set.jinx +0 -0
  135. /npcsh/npc_team/jinxs/{utils → lib/utils}/usage.jinx +0 -0
  136. {npcsh-1.1.14.data → npcsh-1.1.15.data}/data/npcsh/npc_team/alicanto.png +0 -0
  137. {npcsh-1.1.14.data → npcsh-1.1.15.data}/data/npcsh/npc_team/chat.jinx +0 -0
  138. {npcsh-1.1.14.data → npcsh-1.1.15.data}/data/npcsh/npc_team/cmd.jinx +0 -0
  139. {npcsh-1.1.14.data → npcsh-1.1.15.data}/data/npcsh/npc_team/compile.jinx +0 -0
  140. {npcsh-1.1.14.data → npcsh-1.1.15.data}/data/npcsh/npc_team/compress.jinx +0 -0
  141. {npcsh-1.1.14.data → npcsh-1.1.15.data}/data/npcsh/npc_team/corca.png +0 -0
  142. {npcsh-1.1.14.data → npcsh-1.1.15.data}/data/npcsh/npc_team/corca_example.png +0 -0
  143. {npcsh-1.1.14.data → npcsh-1.1.15.data}/data/npcsh/npc_team/frederic4.png +0 -0
  144. {npcsh-1.1.14.data → npcsh-1.1.15.data}/data/npcsh/npc_team/guac.png +0 -0
  145. {npcsh-1.1.14.data → npcsh-1.1.15.data}/data/npcsh/npc_team/help.jinx +0 -0
  146. {npcsh-1.1.14.data → npcsh-1.1.15.data}/data/npcsh/npc_team/init.jinx +0 -0
  147. {npcsh-1.1.14.data → npcsh-1.1.15.data}/data/npcsh/npc_team/kadiefa.png +0 -0
  148. {npcsh-1.1.14.data → npcsh-1.1.15.data}/data/npcsh/npc_team/npc-studio.jinx +0 -0
  149. {npcsh-1.1.14.data → npcsh-1.1.15.data}/data/npcsh/npc_team/npcsh.ctx +0 -0
  150. {npcsh-1.1.14.data → npcsh-1.1.15.data}/data/npcsh/npc_team/npcsh_sibiji.png +0 -0
  151. {npcsh-1.1.14.data → npcsh-1.1.15.data}/data/npcsh/npc_team/ots.jinx +0 -0
  152. {npcsh-1.1.14.data → npcsh-1.1.15.data}/data/npcsh/npc_team/plonk.png +0 -0
  153. {npcsh-1.1.14.data → npcsh-1.1.15.data}/data/npcsh/npc_team/plonkjr.png +0 -0
  154. {npcsh-1.1.14.data → npcsh-1.1.15.data}/data/npcsh/npc_team/python.jinx +0 -0
  155. {npcsh-1.1.14.data → npcsh-1.1.15.data}/data/npcsh/npc_team/roll.jinx +0 -0
  156. {npcsh-1.1.14.data → npcsh-1.1.15.data}/data/npcsh/npc_team/sample.jinx +0 -0
  157. {npcsh-1.1.14.data → npcsh-1.1.15.data}/data/npcsh/npc_team/serve.jinx +0 -0
  158. {npcsh-1.1.14.data → npcsh-1.1.15.data}/data/npcsh/npc_team/set.jinx +0 -0
  159. {npcsh-1.1.14.data → npcsh-1.1.15.data}/data/npcsh/npc_team/sibiji.png +0 -0
  160. {npcsh-1.1.14.data → npcsh-1.1.15.data}/data/npcsh/npc_team/sleep.jinx +0 -0
  161. {npcsh-1.1.14.data → npcsh-1.1.15.data}/data/npcsh/npc_team/spool.jinx +0 -0
  162. {npcsh-1.1.14.data → npcsh-1.1.15.data}/data/npcsh/npc_team/spool.png +0 -0
  163. {npcsh-1.1.14.data → npcsh-1.1.15.data}/data/npcsh/npc_team/trigger.jinx +0 -0
  164. {npcsh-1.1.14.data → npcsh-1.1.15.data}/data/npcsh/npc_team/usage.jinx +0 -0
  165. {npcsh-1.1.14.data → npcsh-1.1.15.data}/data/npcsh/npc_team/yap.jinx +0 -0
  166. {npcsh-1.1.14.data → npcsh-1.1.15.data}/data/npcsh/npc_team/yap.png +0 -0
  167. {npcsh-1.1.14.dist-info → npcsh-1.1.15.dist-info}/WHEEL +0 -0
  168. {npcsh-1.1.14.dist-info → npcsh-1.1.15.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,184 @@
1
+ jinx_name: delegate
2
+ description: Delegate a task to another NPC with review and feedback loop until completion. Choose the NPC whose directive best matches the task.
3
+ inputs:
4
+ - npc_name:
5
+ description: "Name of the NPC to delegate to"
6
+ - task:
7
+ description: "The task or request to delegate to the NPC"
8
+ - max_iterations: "10"
9
+ steps:
10
+ - name: delegate_with_review
11
+ engine: python
12
+ code: |
13
+ from termcolor import colored
14
+ from npcpy.llm_funcs import get_llm_response
15
+
16
+ # Try to get spinner for status updates
17
+ try:
18
+ from npcsh.ui import get_current_spinner
19
+ spinner = get_current_spinner()
20
+ except:
21
+ spinner = None
22
+
23
+ target_name = {{ npc_name | default("") | tojson }}.lower().strip()
24
+ task_request = {{ task | default("") | tojson }}
25
+ max_iters = int({{ max_iterations | default("10") | tojson }} or "10")
26
+
27
+ team_obj = context.get('team') or getattr(npc, 'team', None)
28
+ orchestrator = context.get('npc') or npc
29
+ orchestrator_name = getattr(orchestrator, 'name', 'orchestrator')
30
+
31
+ if not team_obj:
32
+ output = "Error: No team available for delegation"
33
+ exit()
34
+
35
+ if not hasattr(team_obj, 'npcs') or target_name not in team_obj.npcs:
36
+ available = list(team_obj.npcs.keys()) if hasattr(team_obj, 'npcs') else []
37
+ output = "Error: NPC '{}' not found. Available: {}".format(target_name, ', '.join(available))
38
+ exit()
39
+
40
+ target_npc = team_obj.npcs[target_name]
41
+ target_jinxs = dict((k, v) for k, v in target_npc.jinxs_dict.items() if k != 'delegate')
42
+
43
+ sep = '-' * 60
44
+ print(colored("\n" + sep, "cyan"))
45
+ print(colored(" Delegating to @" + target_name, "yellow", attrs=["bold"]))
46
+ task_preview = task_request[:100] + ('...' if len(task_request) > 100 else '')
47
+ print(colored(" Task: " + task_preview, "white", attrs=["dark"]))
48
+ print(colored(sep + "\n", "cyan"))
49
+ print(colored(" [{}] Model: {}".format(target_name, target_npc.model), "white", attrs=["dark"]))
50
+ jinx_list = ', '.join(list(target_jinxs.keys())[:8])
51
+ print(colored(" [{}] Jinxs: {}...".format(target_name, jinx_list), "white", attrs=["dark"]))
52
+
53
+ # Update spinner to show sub-agent
54
+ if spinner:
55
+ spinner.set_message("{} delegated to {}".format(orchestrator_name, target_name))
56
+
57
+ current_task = task_request
58
+ iteration = 0
59
+ final_output = None
60
+ task_complete = False
61
+
62
+ while iteration < max_iters and not task_complete:
63
+ iteration += 1
64
+
65
+ # Update spinner with current iteration
66
+ if spinner:
67
+ spinner.set_message("{} working (iter {}/{})".format(target_name, iteration, max_iters))
68
+
69
+ if iteration > 1:
70
+ print(colored("\n" + sep, "yellow"))
71
+ iter_msg = " Iteration {}/{} - Re-tasking @{}".format(iteration, max_iters, target_name)
72
+ print(colored(iter_msg, "yellow", attrs=["bold"]))
73
+ print(colored(sep + "\n", "yellow"))
74
+
75
+ try:
76
+ result = target_npc.check_llm_command(
77
+ current_task,
78
+ context=context,
79
+ team=team_obj,
80
+ jinxs=target_jinxs,
81
+ stream=False,
82
+ )
83
+
84
+ if isinstance(result, dict):
85
+ delegate_output = result.get('output') or result.get('response') or str(result)
86
+ delegate_messages = result.get('messages', [])
87
+ else:
88
+ delegate_output = str(result)
89
+ delegate_messages = []
90
+
91
+ print(colored("\n" + sep, "cyan"))
92
+ print(colored(" @{} iteration {} complete".format(target_name, iteration), "green"))
93
+ print(colored(sep + "\n", "cyan"))
94
+
95
+ # Update spinner for review phase
96
+ if spinner:
97
+ spinner.set_message("{} reviewing {}'s work".format(orchestrator_name, target_name))
98
+
99
+ # Build review prompt without f-strings to avoid YAML issues
100
+ output_preview = delegate_output[:2000] if delegate_output else 'No output received'
101
+ msg_preview = str(delegate_messages[-5:]) if delegate_messages else 'No messages'
102
+
103
+ review_lines = [
104
+ "You are reviewing work done by @{} on this task:".format(target_name),
105
+ "",
106
+ "ORIGINAL TASK: " + task_request,
107
+ "",
108
+ "ITERATION: {}/{}".format(iteration, max_iters),
109
+ "",
110
+ "SUB-AGENT OUTPUT:",
111
+ output_preview,
112
+ "",
113
+ "RECENT MESSAGES:",
114
+ msg_preview,
115
+ "",
116
+ "Evaluate if the task is complete. Consider:",
117
+ "1. Did the sub-agent accomplish what was asked?",
118
+ "2. Are there obvious errors or incomplete steps?",
119
+ "3. For GUI tasks: Did they fill in all required fields?",
120
+ "",
121
+ "Respond EXACTLY like this:",
122
+ "COMPLETE: YES or NO",
123
+ "FEEDBACK: If NO, what should be done next",
124
+ "SUMMARY: Brief summary of progress"
125
+ ]
126
+ review_prompt = "\n".join(review_lines)
127
+
128
+ review_result = get_llm_response(
129
+ review_prompt,
130
+ model=getattr(orchestrator, 'model', 'gemini-2.5-flash'),
131
+ provider=getattr(orchestrator, 'provider', 'gemini'),
132
+ npc=orchestrator,
133
+ temperature=0.3
134
+ )
135
+
136
+ review_text = str(review_result.get('response', ''))
137
+ is_complete = 'COMPLETE: YES' in review_text.upper()
138
+
139
+ feedback = ""
140
+ if 'FEEDBACK:' in review_text:
141
+ fb_start = review_text.find('FEEDBACK:') + 9
142
+ fb_end = review_text.find('SUMMARY:', fb_start) if 'SUMMARY:' in review_text else len(review_text)
143
+ feedback = review_text[fb_start:fb_end].strip()
144
+
145
+ summary = ""
146
+ if 'SUMMARY:' in review_text:
147
+ summary = review_text[review_text.find('SUMMARY:') + 8:].strip()
148
+
149
+ if is_complete:
150
+ task_complete = True
151
+ print(colored("\n Task completed successfully", "green", attrs=["bold"]))
152
+ if summary:
153
+ print(colored(" Summary: " + summary[:200], "white", attrs=["dark"]))
154
+ final_output = "[{}] Task completed.\n{}".format(target_name, summary)
155
+ else:
156
+ print(colored("\n Task incomplete - providing feedback", "yellow"))
157
+ if feedback:
158
+ print(colored(" Feedback: " + feedback[:200] + "...", "white", attrs=["dark"]))
159
+
160
+ followup_lines = [
161
+ "Continue the previous task. Feedback from orchestrator:",
162
+ "",
163
+ "ORIGINAL TASK: " + task_request,
164
+ "",
165
+ "FEEDBACK: " + feedback,
166
+ "",
167
+ "Continue and complete the task based on this feedback."
168
+ ]
169
+ current_task = "\n".join(followup_lines)
170
+
171
+ if delegate_messages:
172
+ context['messages'] = delegate_messages
173
+
174
+ except Exception as e:
175
+ print(colored(" Error in iteration {}: {}".format(iteration, e), "red"))
176
+ final_output = "Error delegating to {}: {}".format(target_name, str(e))
177
+ break
178
+
179
+ if not task_complete and iteration >= max_iters:
180
+ print(colored("\n Max iterations ({}) reached".format(max_iters), "yellow"))
181
+ status = summary if summary else 'Unknown'
182
+ final_output = "[{}] Task incomplete after {} iterations. Status: {}".format(target_name, max_iters, status)
183
+
184
+ output = final_output or "[{}]: No output received".format(target_name)
@@ -13,9 +13,9 @@ steps:
13
13
  from npcpy.llm_funcs import get_llm_response
14
14
 
15
15
 
16
- file_path = os.path.expanduser("{{ file_path }}")
17
- edit_instructions = "{{ edit_instructions }}"
18
- backup_str = "{{ backup }}"
16
+ file_path = os.path.expanduser({{ file_path | tojson }})
17
+ edit_instructions = {{ edit_instructions | string | tojson }}
18
+ backup_str = {{ backup | default("true") | string | tojson }}
19
19
  create_backup = backup_str.lower() not in ('false', 'no', '0', '')
20
20
 
21
21
 
@@ -0,0 +1,27 @@
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"
13
+ primary_directive: |
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,176 @@
1
+ jinx_name: jinxs
2
+ description: "Show available jinxs organized by folder. Use /jinxs <path> for details on a specific folder."
3
+ inputs:
4
+ - path: "" # Optional path to show details for (e.g., "lib/core", "bin")
5
+ steps:
6
+ - name: list_jinxs
7
+ engine: python
8
+ code: |
9
+ import os
10
+ from pathlib import Path
11
+ import yaml
12
+
13
+ filter_path = context.get('path', '').strip()
14
+
15
+ # Find jinxs directory from team or fallback
16
+ jinxs_dir = None
17
+ if hasattr(npc, 'team') and npc.team:
18
+ if hasattr(npc.team, 'jinxs_dir') and npc.team.jinxs_dir:
19
+ jinxs_dir = Path(npc.team.jinxs_dir)
20
+ elif hasattr(npc.team, 'team_path') and npc.team.team_path:
21
+ candidate = Path(npc.team.team_path) / "jinxs"
22
+ if candidate.exists():
23
+ jinxs_dir = candidate
24
+
25
+ if not jinxs_dir:
26
+ # Fallback to global jinxs
27
+ global_jinxs = Path.home() / ".npcsh" / "npc_team" / "jinxs"
28
+ if global_jinxs.exists():
29
+ jinxs_dir = global_jinxs
30
+
31
+ if not jinxs_dir or not jinxs_dir.exists():
32
+ output = "Error: Could not find jinxs directory"
33
+ exit()
34
+
35
+ def get_jinx_info(jinx_path):
36
+ """Extract name and description from a jinx file."""
37
+ try:
38
+ with open(jinx_path, 'r') as f:
39
+ content = f.read()
40
+ # Parse just the header (before steps:)
41
+ header = content.split('steps:')[0] if 'steps:' in content else content
42
+ data = yaml.safe_load(header)
43
+ name = data.get('jinx_name', jinx_path.stem)
44
+ desc = data.get('description', 'No description')
45
+ return name, desc
46
+ except:
47
+ return jinx_path.stem, 'No description'
48
+
49
+ def get_folder_structure(base_path):
50
+ """Get jinxs organized by folder."""
51
+ structure = {}
52
+ for root, dirs, files in os.walk(base_path):
53
+ # Skip hidden directories
54
+ dirs[:] = [d for d in dirs if not d.startswith('.')]
55
+
56
+ jinx_files = [f for f in files if f.endswith('.jinx')]
57
+ if jinx_files:
58
+ rel_path = Path(root).relative_to(base_path)
59
+ rel_str = str(rel_path) if str(rel_path) != '.' else 'root'
60
+ structure[rel_str] = []
61
+ for jf in sorted(jinx_files):
62
+ jinx_path = Path(root) / jf
63
+ name, desc = get_jinx_info(jinx_path)
64
+ structure[rel_str].append((name, desc, jf))
65
+ return structure
66
+
67
+ output_lines = []
68
+
69
+ if filter_path:
70
+ # Show details for a specific path
71
+ target_path = jinxs_dir / filter_path
72
+ if not target_path.exists():
73
+ # Try to find a matching folder
74
+ matches = []
75
+ for root, dirs, files in os.walk(jinxs_dir):
76
+ rel = Path(root).relative_to(jinxs_dir)
77
+ if filter_path in str(rel) or filter_path in Path(root).name:
78
+ matches.append(rel)
79
+
80
+ if matches:
81
+ output_lines.append(f"No exact match for '{filter_path}'. Did you mean:\n")
82
+ for m in matches[:5]:
83
+ output_lines.append(f" /jinxs {m}\n")
84
+ output = "".join(output_lines)
85
+ exit()
86
+ else:
87
+ output = f"No jinxs found at path: {filter_path}"
88
+ exit()
89
+
90
+ # Get jinxs in this path
91
+ structure = get_folder_structure(target_path)
92
+ if not structure:
93
+ # Check if it's a single folder with jinxs
94
+ jinx_files = list(target_path.glob("*.jinx"))
95
+ if jinx_files:
96
+ output_lines.append(f"Jinxs in {filter_path}:\n\n")
97
+ for jf in sorted(jinx_files):
98
+ name, desc = get_jinx_info(jf)
99
+ output_lines.append(f" /{name}\n")
100
+ output_lines.append(f" {desc}\n\n")
101
+ else:
102
+ output = f"No jinxs found at path: {filter_path}"
103
+ exit()
104
+ else:
105
+ output_lines.append(f"Jinxs in {filter_path}:\n\n")
106
+ for folder, jinxs in sorted(structure.items()):
107
+ if folder != 'root':
108
+ output_lines.append(f" {folder}/\n")
109
+ for name, desc, filename in jinxs:
110
+ prefix = " " if folder != 'root' else " "
111
+ output_lines.append(f"{prefix}/{name} - {desc}\n")
112
+ output_lines.append("\n")
113
+
114
+ else:
115
+ # Show overview organized by folder
116
+ structure = get_folder_structure(jinxs_dir)
117
+
118
+ output_lines.append("Available Jinxs\n")
119
+ output_lines.append("=" * 40 + "\n\n")
120
+
121
+ # Group by top-level folder
122
+ top_level = {}
123
+ for folder, jinxs in structure.items():
124
+ if folder == 'root':
125
+ top = 'root'
126
+ else:
127
+ top = folder.split('/')[0] if '/' in folder else folder
128
+
129
+ if top not in top_level:
130
+ top_level[top] = {'subfolders': {}, 'jinxs': []}
131
+
132
+ if folder == top or folder == 'root':
133
+ top_level[top]['jinxs'].extend(jinxs)
134
+ else:
135
+ subfolder = '/'.join(folder.split('/')[1:])
136
+ if subfolder not in top_level[top]['subfolders']:
137
+ top_level[top]['subfolders'][subfolder] = []
138
+ top_level[top]['subfolders'][subfolder].extend(jinxs)
139
+
140
+ # Display
141
+ folder_order = ['bin', 'lib', 'npc_studio', 'root']
142
+ sorted_folders = sorted(top_level.keys(), key=lambda x: (folder_order.index(x) if x in folder_order else 99, x))
143
+
144
+ for top in sorted_folders:
145
+ data = top_level[top]
146
+
147
+ if top == 'root':
148
+ if data['jinxs']:
149
+ output_lines.append("Root Jinxs:\n")
150
+ for name, desc, _ in data['jinxs']:
151
+ output_lines.append(f" /{name} - {desc}\n")
152
+ output_lines.append("\n")
153
+ else:
154
+ total = len(data['jinxs'])
155
+ for sf_jinxs in data['subfolders'].values():
156
+ total += len(sf_jinxs)
157
+
158
+ output_lines.append(f"{top}/ ({total} jinxs)\n")
159
+
160
+ # Show direct jinxs
161
+ if data['jinxs']:
162
+ for name, desc, _ in data['jinxs'][:3]:
163
+ output_lines.append(f" /{name} - {desc}\n")
164
+ if len(data['jinxs']) > 3:
165
+ output_lines.append(f" ... and {len(data['jinxs']) - 3} more\n")
166
+
167
+ # Show subfolders summary
168
+ if data['subfolders']:
169
+ for subfolder, jinxs in sorted(data['subfolders'].items()):
170
+ output_lines.append(f" {subfolder}/ ({len(jinxs)} jinxs)\n")
171
+
172
+ output_lines.append(f" → /jinxs {top} for details\n\n")
173
+
174
+ output_lines.append("Use /jinxs <path> for details (e.g., /jinxs lib/core)\n")
175
+
176
+ output = "".join(output_lines)
@@ -0,0 +1,21 @@
1
+ name: kadiefa
2
+ ascii_art: |
3
+ 🏔️🏔️🏔️🏔️🏔️🏔️🏔️🏔️🏔️🏔️🏔️🏔️🏔️🏔️🏔️🏔️🏔️🏔️🏔️🏔️🏔️🏔️🏔️🏔️🏔️🏔️🏔️🏔️🏔️🏔️🏔️🏔️🏔️🏔️🏔️🏔️🏔️🏔️🏔️🏔️🏔️🏔️
4
+ ██ ██ █████ ██████ ██ ███████ ███████ █████
5
+ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
6
+ █████ ███████ ██ ██ ██ █████ █████ ███████ 🐆
7
+ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
8
+ ██ ██ ██ ██ ██████ ██ ███████ ██ ██ ██
9
+ 🏔️🏔️🏔️🏔️🏔️🏔️🏔️🏔️🏔️🏔️🏔️🏔️🏔️🏔️🏔️🏔️🏔️🏔️🏔️🏔️🏔️🏔️🏔️🏔️🏔️🏔️🏔️🏔️🏔️🏔️🏔️🏔️🏔️🏔️🏔️🏔️🏔️🏔️🏔️🏔️🏔️🏔️
10
+ colors:
11
+ top: "255,255,255"
12
+ bottom: "173,216,230"
13
+ primary_directive: |
14
+ You are kadiefa, the exploratory snow leopard. You love to find new paths and to explore hidden gems.
15
+ You go into caverns no cat has ventured into before. You climb peaks that others call crazy.
16
+ Your role is to lead users on wandering explorations - deep research journeys that follow threads
17
+ wherever they lead. You help users explore complex research questions and think outside the box.
18
+ Use web search and browsing tools to discover new information.
19
+ jinxs:
20
+ - lib/core/python
21
+ - bin/wander
@@ -0,0 +1,26 @@
1
+ jinx_name: key_press
2
+ description: |
3
+ Press a keyboard key or key combination.
4
+ Valid keys: enter, tab, escape, backspace, delete, space, up, down, left, right,
5
+ home, end, pageup, pagedown, f1-f12, ctrl, alt, shift, command.
6
+ For combinations use + like: ctrl+a, ctrl+c, ctrl+v, alt+tab, ctrl+shift+t
7
+ For regular letters/numbers, use type_text instead.
8
+ inputs:
9
+ - key: "enter"
10
+
11
+ steps:
12
+ - name: perform_key
13
+ engine: python
14
+ code: |
15
+ from npcpy.work.desktop import perform_action
16
+
17
+ key = context.get('key', 'enter')
18
+ messages = context.get('messages', [])
19
+
20
+ try:
21
+ perform_action({'type': 'key', 'keys': key})
22
+ context['output'] = "Pressed key: " + str(key)
23
+ except Exception as e:
24
+ context['output'] = "Key press failed: " + str(e)
25
+
26
+ context['messages'] = messages
@@ -0,0 +1,37 @@
1
+ jinx_name: launch_app
2
+ description: Launch an application on the system
3
+ inputs:
4
+ - command: "" # Command to launch (e.g., "open -a Firefox" on macOS)
5
+
6
+ steps:
7
+ - name: perform_launch
8
+ engine: python
9
+ code: |
10
+ import platform
11
+ from npcpy.work.desktop import perform_action
12
+
13
+ command = context.get('command', '')
14
+ messages = context.get('messages', [])
15
+
16
+ if not command:
17
+ system = platform.system()
18
+ if system == "Darwin":
19
+ examples = "open -a Firefox, open -a TextEdit"
20
+ elif system == "Windows":
21
+ examples = "start firefox, notepad, calc"
22
+ else:
23
+ examples = "firefox &, gedit &"
24
+ context['output'] = f"Usage: /launch_app <command>\nExamples: {examples}"
25
+ context['messages'] = messages
26
+ exit()
27
+
28
+ try:
29
+ result = perform_action({'type': 'bash', 'command': command})
30
+ if result.get('status') == 'success':
31
+ context['output'] = f"Launched: {command}"
32
+ else:
33
+ context['output'] = f"Launch failed: {result.get('message', 'unknown error')}"
34
+ except Exception as e:
35
+ context['output'] = f"Launch failed: {e}"
36
+
37
+ context['messages'] = messages
@@ -10,7 +10,7 @@ steps:
10
10
  from npcpy.data.load import load_file_contents
11
11
 
12
12
  # Expand user path and get absolute path
13
- file_path = os.path.expanduser("{{ file_path }}")
13
+ file_path = os.path.expanduser({{ file_path | tojson }})
14
14
 
15
15
  # Check if file exists
16
16
  if not os.path.exists(file_path):
@@ -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()