npcsh 1.1.14__py3-none-any.whl → 1.1.16__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- npcsh/_state.py +533 -80
- npcsh/mcp_server.py +2 -1
- npcsh/npc.py +84 -32
- npcsh/npc_team/alicanto.npc +22 -1
- npcsh/npc_team/corca.npc +28 -9
- npcsh/npc_team/frederic.npc +25 -4
- npcsh/npc_team/guac.npc +22 -0
- npcsh/npc_team/jinxs/bin/nql.jinx +141 -0
- npcsh/npc_team/jinxs/bin/sync.jinx +230 -0
- {npcsh-1.1.14.data/data/npcsh/npc_team → npcsh/npc_team/jinxs/bin}/vixynt.jinx +8 -30
- npcsh/npc_team/jinxs/bin/wander.jinx +152 -0
- npcsh/npc_team/jinxs/lib/browser/browser_action.jinx +220 -0
- npcsh/npc_team/jinxs/lib/browser/browser_screenshot.jinx +40 -0
- npcsh/npc_team/jinxs/lib/browser/close_browser.jinx +14 -0
- npcsh/npc_team/jinxs/lib/browser/open_browser.jinx +43 -0
- npcsh/npc_team/jinxs/lib/computer_use/click.jinx +23 -0
- npcsh/npc_team/jinxs/lib/computer_use/key_press.jinx +26 -0
- npcsh/npc_team/jinxs/lib/computer_use/launch_app.jinx +37 -0
- npcsh/npc_team/jinxs/lib/computer_use/screenshot.jinx +23 -0
- npcsh/npc_team/jinxs/lib/computer_use/type_text.jinx +27 -0
- npcsh/npc_team/jinxs/lib/computer_use/wait.jinx +21 -0
- {npcsh-1.1.14.data/data/npcsh/npc_team → npcsh/npc_team/jinxs/lib/core}/edit_file.jinx +3 -3
- {npcsh-1.1.14.data/data/npcsh/npc_team → npcsh/npc_team/jinxs/lib/core}/load_file.jinx +1 -1
- npcsh/npc_team/jinxs/lib/core/paste.jinx +134 -0
- {npcsh-1.1.14.data/data/npcsh/npc_team → npcsh/npc_team/jinxs/lib/core}/search.jinx +2 -1
- npcsh/npc_team/jinxs/{code → lib/core}/sh.jinx +2 -8
- npcsh/npc_team/jinxs/{code → lib/core}/sql.jinx +1 -1
- npcsh/npc_team/jinxs/lib/orchestration/convene.jinx +232 -0
- npcsh/npc_team/jinxs/lib/orchestration/delegate.jinx +184 -0
- npcsh/npc_team/jinxs/lib/research/arxiv.jinx +76 -0
- npcsh/npc_team/jinxs/lib/research/paper_search.jinx +101 -0
- npcsh/npc_team/jinxs/lib/research/semantic_scholar.jinx +69 -0
- npcsh/npc_team/jinxs/{utils/core → lib/utils}/build.jinx +8 -8
- npcsh/npc_team/jinxs/lib/utils/jinxs.jinx +176 -0
- npcsh/npc_team/jinxs/lib/utils/shh.jinx +17 -0
- npcsh/npc_team/jinxs/lib/utils/switch.jinx +62 -0
- npcsh/npc_team/jinxs/lib/utils/switches.jinx +61 -0
- npcsh/npc_team/jinxs/lib/utils/teamviz.jinx +205 -0
- npcsh/npc_team/jinxs/lib/utils/verbose.jinx +17 -0
- npcsh/npc_team/kadiefa.npc +19 -1
- npcsh/npc_team/plonk.npc +26 -1
- npcsh/npc_team/plonkjr.npc +22 -1
- npcsh/npc_team/sibiji.npc +23 -2
- npcsh/npcsh.py +153 -39
- npcsh/ui.py +22 -1
- npcsh-1.1.16.data/data/npcsh/npc_team/alicanto.npc +23 -0
- npcsh-1.1.16.data/data/npcsh/npc_team/arxiv.jinx +76 -0
- npcsh-1.1.16.data/data/npcsh/npc_team/browser_action.jinx +220 -0
- npcsh-1.1.16.data/data/npcsh/npc_team/browser_screenshot.jinx +40 -0
- {npcsh-1.1.14.data → npcsh-1.1.16.data}/data/npcsh/npc_team/build.jinx +8 -8
- npcsh-1.1.16.data/data/npcsh/npc_team/click.jinx +23 -0
- npcsh-1.1.16.data/data/npcsh/npc_team/close_browser.jinx +14 -0
- npcsh-1.1.16.data/data/npcsh/npc_team/convene.jinx +232 -0
- npcsh-1.1.16.data/data/npcsh/npc_team/corca.npc +31 -0
- npcsh-1.1.16.data/data/npcsh/npc_team/delegate.jinx +184 -0
- {npcsh/npc_team/jinxs/utils → npcsh-1.1.16.data/data/npcsh/npc_team}/edit_file.jinx +3 -3
- npcsh-1.1.16.data/data/npcsh/npc_team/frederic.npc +27 -0
- npcsh-1.1.16.data/data/npcsh/npc_team/guac.npc +22 -0
- npcsh-1.1.16.data/data/npcsh/npc_team/jinxs.jinx +176 -0
- npcsh-1.1.16.data/data/npcsh/npc_team/kadiefa.npc +21 -0
- npcsh-1.1.16.data/data/npcsh/npc_team/key_press.jinx +26 -0
- npcsh-1.1.16.data/data/npcsh/npc_team/launch_app.jinx +37 -0
- {npcsh/npc_team/jinxs/utils → npcsh-1.1.16.data/data/npcsh/npc_team}/load_file.jinx +1 -1
- npcsh-1.1.16.data/data/npcsh/npc_team/nql.jinx +141 -0
- npcsh-1.1.16.data/data/npcsh/npc_team/open_browser.jinx +43 -0
- npcsh-1.1.16.data/data/npcsh/npc_team/paper_search.jinx +101 -0
- npcsh-1.1.16.data/data/npcsh/npc_team/paste.jinx +134 -0
- npcsh-1.1.16.data/data/npcsh/npc_team/plonk.npc +27 -0
- npcsh-1.1.16.data/data/npcsh/npc_team/plonkjr.npc +23 -0
- npcsh-1.1.16.data/data/npcsh/npc_team/screenshot.jinx +23 -0
- {npcsh/npc_team/jinxs/utils → npcsh-1.1.16.data/data/npcsh/npc_team}/search.jinx +2 -1
- npcsh-1.1.16.data/data/npcsh/npc_team/semantic_scholar.jinx +69 -0
- {npcsh-1.1.14.data → npcsh-1.1.16.data}/data/npcsh/npc_team/sh.jinx +2 -8
- npcsh-1.1.16.data/data/npcsh/npc_team/shh.jinx +17 -0
- npcsh-1.1.16.data/data/npcsh/npc_team/sibiji.npc +24 -0
- {npcsh-1.1.14.data → npcsh-1.1.16.data}/data/npcsh/npc_team/sql.jinx +1 -1
- npcsh-1.1.16.data/data/npcsh/npc_team/switch.jinx +62 -0
- npcsh-1.1.16.data/data/npcsh/npc_team/switches.jinx +61 -0
- npcsh-1.1.16.data/data/npcsh/npc_team/sync.jinx +230 -0
- npcsh-1.1.16.data/data/npcsh/npc_team/teamviz.jinx +205 -0
- npcsh-1.1.16.data/data/npcsh/npc_team/type_text.jinx +27 -0
- npcsh-1.1.16.data/data/npcsh/npc_team/verbose.jinx +17 -0
- {npcsh/npc_team/jinxs/utils → npcsh-1.1.16.data/data/npcsh/npc_team}/vixynt.jinx +8 -30
- npcsh-1.1.16.data/data/npcsh/npc_team/wait.jinx +21 -0
- npcsh-1.1.16.data/data/npcsh/npc_team/wander.jinx +152 -0
- {npcsh-1.1.14.dist-info → npcsh-1.1.16.dist-info}/METADATA +399 -58
- npcsh-1.1.16.dist-info/RECORD +170 -0
- npcsh-1.1.16.dist-info/entry_points.txt +19 -0
- npcsh-1.1.16.dist-info/top_level.txt +2 -0
- project/__init__.py +1 -0
- npcsh/npc_team/foreman.npc +0 -7
- npcsh/npc_team/jinxs/modes/alicanto.jinx +0 -194
- npcsh/npc_team/jinxs/modes/corca.jinx +0 -249
- npcsh/npc_team/jinxs/modes/guac.jinx +0 -317
- npcsh/npc_team/jinxs/modes/plonk.jinx +0 -214
- npcsh/npc_team/jinxs/modes/pti.jinx +0 -170
- npcsh/npc_team/jinxs/modes/wander.jinx +0 -186
- npcsh/npc_team/jinxs/utils/agent.jinx +0 -17
- npcsh/npc_team/jinxs/utils/core/jinxs.jinx +0 -32
- npcsh-1.1.14.data/data/npcsh/npc_team/agent.jinx +0 -17
- npcsh-1.1.14.data/data/npcsh/npc_team/alicanto.jinx +0 -194
- npcsh-1.1.14.data/data/npcsh/npc_team/alicanto.npc +0 -2
- npcsh-1.1.14.data/data/npcsh/npc_team/corca.jinx +0 -249
- npcsh-1.1.14.data/data/npcsh/npc_team/corca.npc +0 -12
- npcsh-1.1.14.data/data/npcsh/npc_team/foreman.npc +0 -7
- npcsh-1.1.14.data/data/npcsh/npc_team/frederic.npc +0 -6
- npcsh-1.1.14.data/data/npcsh/npc_team/guac.jinx +0 -317
- npcsh-1.1.14.data/data/npcsh/npc_team/jinxs.jinx +0 -32
- npcsh-1.1.14.data/data/npcsh/npc_team/kadiefa.npc +0 -3
- npcsh-1.1.14.data/data/npcsh/npc_team/plonk.jinx +0 -214
- npcsh-1.1.14.data/data/npcsh/npc_team/plonk.npc +0 -2
- npcsh-1.1.14.data/data/npcsh/npc_team/plonkjr.npc +0 -2
- npcsh-1.1.14.data/data/npcsh/npc_team/pti.jinx +0 -170
- npcsh-1.1.14.data/data/npcsh/npc_team/sibiji.npc +0 -3
- npcsh-1.1.14.data/data/npcsh/npc_team/wander.jinx +0 -186
- npcsh-1.1.14.dist-info/RECORD +0 -135
- npcsh-1.1.14.dist-info/entry_points.txt +0 -9
- npcsh-1.1.14.dist-info/top_level.txt +0 -1
- /npcsh/npc_team/jinxs/{utils → bin}/roll.jinx +0 -0
- /npcsh/npc_team/jinxs/{utils → bin}/sample.jinx +0 -0
- /npcsh/npc_team/jinxs/{modes → bin}/spool.jinx +0 -0
- /npcsh/npc_team/jinxs/{modes → bin}/yap.jinx +0 -0
- /npcsh/npc_team/jinxs/{utils → lib/computer_use}/trigger.jinx +0 -0
- /npcsh/npc_team/jinxs/{utils → lib/core}/chat.jinx +0 -0
- /npcsh/npc_team/jinxs/{utils → lib/core}/cmd.jinx +0 -0
- /npcsh/npc_team/jinxs/{utils → lib/core}/compress.jinx +0 -0
- /npcsh/npc_team/jinxs/{utils → lib/core}/ots.jinx +0 -0
- /npcsh/npc_team/jinxs/{code → lib/core}/python.jinx +0 -0
- /npcsh/npc_team/jinxs/{utils → lib/core}/sleep.jinx +0 -0
- /npcsh/npc_team/jinxs/{utils/core → lib/utils}/compile.jinx +0 -0
- /npcsh/npc_team/jinxs/{utils/core → lib/utils}/help.jinx +0 -0
- /npcsh/npc_team/jinxs/{utils/core → lib/utils}/init.jinx +0 -0
- /npcsh/npc_team/jinxs/{utils → lib/utils}/serve.jinx +0 -0
- /npcsh/npc_team/jinxs/{utils/core → lib/utils}/set.jinx +0 -0
- /npcsh/npc_team/jinxs/{utils → lib/utils}/usage.jinx +0 -0
- {npcsh-1.1.14.data → npcsh-1.1.16.data}/data/npcsh/npc_team/alicanto.png +0 -0
- {npcsh-1.1.14.data → npcsh-1.1.16.data}/data/npcsh/npc_team/chat.jinx +0 -0
- {npcsh-1.1.14.data → npcsh-1.1.16.data}/data/npcsh/npc_team/cmd.jinx +0 -0
- {npcsh-1.1.14.data → npcsh-1.1.16.data}/data/npcsh/npc_team/compile.jinx +0 -0
- {npcsh-1.1.14.data → npcsh-1.1.16.data}/data/npcsh/npc_team/compress.jinx +0 -0
- {npcsh-1.1.14.data → npcsh-1.1.16.data}/data/npcsh/npc_team/corca.png +0 -0
- {npcsh-1.1.14.data → npcsh-1.1.16.data}/data/npcsh/npc_team/corca_example.png +0 -0
- {npcsh-1.1.14.data → npcsh-1.1.16.data}/data/npcsh/npc_team/frederic4.png +0 -0
- {npcsh-1.1.14.data → npcsh-1.1.16.data}/data/npcsh/npc_team/guac.png +0 -0
- {npcsh-1.1.14.data → npcsh-1.1.16.data}/data/npcsh/npc_team/help.jinx +0 -0
- {npcsh-1.1.14.data → npcsh-1.1.16.data}/data/npcsh/npc_team/init.jinx +0 -0
- {npcsh-1.1.14.data → npcsh-1.1.16.data}/data/npcsh/npc_team/kadiefa.png +0 -0
- {npcsh-1.1.14.data → npcsh-1.1.16.data}/data/npcsh/npc_team/npc-studio.jinx +0 -0
- {npcsh-1.1.14.data → npcsh-1.1.16.data}/data/npcsh/npc_team/npcsh.ctx +0 -0
- {npcsh-1.1.14.data → npcsh-1.1.16.data}/data/npcsh/npc_team/npcsh_sibiji.png +0 -0
- {npcsh-1.1.14.data → npcsh-1.1.16.data}/data/npcsh/npc_team/ots.jinx +0 -0
- {npcsh-1.1.14.data → npcsh-1.1.16.data}/data/npcsh/npc_team/plonk.png +0 -0
- {npcsh-1.1.14.data → npcsh-1.1.16.data}/data/npcsh/npc_team/plonkjr.png +0 -0
- {npcsh-1.1.14.data → npcsh-1.1.16.data}/data/npcsh/npc_team/python.jinx +0 -0
- {npcsh-1.1.14.data → npcsh-1.1.16.data}/data/npcsh/npc_team/roll.jinx +0 -0
- {npcsh-1.1.14.data → npcsh-1.1.16.data}/data/npcsh/npc_team/sample.jinx +0 -0
- {npcsh-1.1.14.data → npcsh-1.1.16.data}/data/npcsh/npc_team/serve.jinx +0 -0
- {npcsh-1.1.14.data → npcsh-1.1.16.data}/data/npcsh/npc_team/set.jinx +0 -0
- {npcsh-1.1.14.data → npcsh-1.1.16.data}/data/npcsh/npc_team/sibiji.png +0 -0
- {npcsh-1.1.14.data → npcsh-1.1.16.data}/data/npcsh/npc_team/sleep.jinx +0 -0
- {npcsh-1.1.14.data → npcsh-1.1.16.data}/data/npcsh/npc_team/spool.jinx +0 -0
- {npcsh-1.1.14.data → npcsh-1.1.16.data}/data/npcsh/npc_team/spool.png +0 -0
- {npcsh-1.1.14.data → npcsh-1.1.16.data}/data/npcsh/npc_team/trigger.jinx +0 -0
- {npcsh-1.1.14.data → npcsh-1.1.16.data}/data/npcsh/npc_team/usage.jinx +0 -0
- {npcsh-1.1.14.data → npcsh-1.1.16.data}/data/npcsh/npc_team/yap.jinx +0 -0
- {npcsh-1.1.14.data → npcsh-1.1.16.data}/data/npcsh/npc_team/yap.png +0 -0
- {npcsh-1.1.14.dist-info → npcsh-1.1.16.dist-info}/WHEEL +0 -0
- {npcsh-1.1.14.dist-info → npcsh-1.1.16.dist-info}/licenses/LICENSE +0 -0
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
jinx_name: arxiv
|
|
2
|
+
description: Search arXiv for preprints and papers
|
|
3
|
+
inputs:
|
|
4
|
+
- query: ""
|
|
5
|
+
- limit: 10
|
|
6
|
+
steps:
|
|
7
|
+
- name: search_arxiv
|
|
8
|
+
engine: python
|
|
9
|
+
code: |
|
|
10
|
+
import urllib.request
|
|
11
|
+
import urllib.parse
|
|
12
|
+
import xml.etree.ElementTree as ET
|
|
13
|
+
|
|
14
|
+
query = context.get('query', '')
|
|
15
|
+
limit = int(context.get('limit', 10))
|
|
16
|
+
|
|
17
|
+
if not query:
|
|
18
|
+
context['output'] = "Usage: /arxiv <query> [--limit N]"
|
|
19
|
+
exit()
|
|
20
|
+
|
|
21
|
+
base_url = "http://export.arxiv.org/api/query"
|
|
22
|
+
params = {
|
|
23
|
+
"search_query": f"all:{query}",
|
|
24
|
+
"start": 0,
|
|
25
|
+
"max_results": limit,
|
|
26
|
+
"sortBy": "relevance",
|
|
27
|
+
"sortOrder": "descending"
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
url = f"{base_url}?{urllib.parse.urlencode(params)}"
|
|
31
|
+
|
|
32
|
+
try:
|
|
33
|
+
with urllib.request.urlopen(url, timeout=30) as response:
|
|
34
|
+
data = response.read().decode('utf-8')
|
|
35
|
+
|
|
36
|
+
root = ET.fromstring(data)
|
|
37
|
+
ns = {'atom': 'http://www.w3.org/2005/Atom'}
|
|
38
|
+
|
|
39
|
+
entries = root.findall('atom:entry', ns)
|
|
40
|
+
|
|
41
|
+
if not entries:
|
|
42
|
+
context['output'] = f"No papers found for: {query}"
|
|
43
|
+
exit()
|
|
44
|
+
|
|
45
|
+
results = []
|
|
46
|
+
papers = []
|
|
47
|
+
for i, entry in enumerate(entries, 1):
|
|
48
|
+
title = entry.find('atom:title', ns).text.strip().replace('\n', ' ')
|
|
49
|
+
summary = entry.find('atom:summary', ns).text.strip()[:300] + '...'
|
|
50
|
+
published = entry.find('atom:published', ns).text[:10]
|
|
51
|
+
authors = [a.find('atom:name', ns).text for a in entry.findall('atom:author', ns)]
|
|
52
|
+
author_str = ', '.join(authors[:3])
|
|
53
|
+
if len(authors) > 3:
|
|
54
|
+
author_str += ' et al.'
|
|
55
|
+
link = entry.find('atom:id', ns).text
|
|
56
|
+
|
|
57
|
+
results.append(f"{i}. {title}")
|
|
58
|
+
results.append(f" Authors: {author_str}")
|
|
59
|
+
results.append(f" Published: {published}")
|
|
60
|
+
results.append(f" Abstract: {summary}")
|
|
61
|
+
results.append(f" URL: {link}")
|
|
62
|
+
results.append("")
|
|
63
|
+
|
|
64
|
+
papers.append({
|
|
65
|
+
'title': title,
|
|
66
|
+
'authors': authors,
|
|
67
|
+
'abstract': entry.find('atom:summary', ns).text.strip(),
|
|
68
|
+
'published': published,
|
|
69
|
+
'url': link
|
|
70
|
+
})
|
|
71
|
+
|
|
72
|
+
context['output'] = f"Found {len(entries)} papers on arXiv:\n\n" + "\n".join(results)
|
|
73
|
+
context['papers'] = papers
|
|
74
|
+
|
|
75
|
+
except Exception as e:
|
|
76
|
+
context['output'] = f"arXiv search error: {e}"
|
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
jinx_name: browser_action
|
|
2
|
+
description: |
|
|
3
|
+
Perform an action in the browser. Actions:
|
|
4
|
+
- click: Click element
|
|
5
|
+
- type: Type text into element (clears first)
|
|
6
|
+
- type_and_enter: Type text and press Enter
|
|
7
|
+
- set_value: Force set value via JS (bypasses date pickers/validation)
|
|
8
|
+
- select: Select dropdown option by visible text
|
|
9
|
+
- wait: Wait for element to appear
|
|
10
|
+
- scroll: Scroll page (up/down/to element)
|
|
11
|
+
- get_text: Get text from element
|
|
12
|
+
- get_page: Get page title, URL, and visible text
|
|
13
|
+
- get_elements: Get interactive elements with their selectors
|
|
14
|
+
- press_key: Press a key (enter, tab, escape, etc)
|
|
15
|
+
Selectors: CSS (#id, .class, input[name="x"]) or xpath://... for XPath
|
|
16
|
+
inputs:
|
|
17
|
+
- action:
|
|
18
|
+
description: "Action: click, type, type_and_enter, set_value, select, wait, scroll, get_text, get_page, get_elements, press_key"
|
|
19
|
+
- selector:
|
|
20
|
+
description: "CSS selector or XPath (prefix xpath: for XPath)"
|
|
21
|
+
default: ""
|
|
22
|
+
- value:
|
|
23
|
+
description: "Value for type/select, or scroll direction, or key name"
|
|
24
|
+
default: ""
|
|
25
|
+
|
|
26
|
+
steps:
|
|
27
|
+
- name: browser_action
|
|
28
|
+
engine: python
|
|
29
|
+
code: |
|
|
30
|
+
from selenium.webdriver.common.by import By
|
|
31
|
+
from selenium.webdriver.support.ui import WebDriverWait, Select
|
|
32
|
+
from selenium.webdriver.support import expected_conditions as EC
|
|
33
|
+
from selenium.webdriver.common.keys import Keys
|
|
34
|
+
from npcpy.work.browser import get_current_driver
|
|
35
|
+
|
|
36
|
+
action = context.get('action', '').lower()
|
|
37
|
+
selector = context.get('selector', '')
|
|
38
|
+
value = context.get('value', '')
|
|
39
|
+
|
|
40
|
+
driver = get_current_driver()
|
|
41
|
+
if not driver:
|
|
42
|
+
output = "No active browser. Use open_browser first."
|
|
43
|
+
exit()
|
|
44
|
+
|
|
45
|
+
def find_element(sel):
|
|
46
|
+
if sel.startswith('xpath:'):
|
|
47
|
+
return driver.find_element(By.XPATH, sel[6:])
|
|
48
|
+
else:
|
|
49
|
+
return driver.find_element(By.CSS_SELECTOR, sel)
|
|
50
|
+
|
|
51
|
+
def wait_for_element(sel, timeout=10):
|
|
52
|
+
if sel.startswith('xpath:'):
|
|
53
|
+
return WebDriverWait(driver, timeout).until(
|
|
54
|
+
EC.presence_of_element_located((By.XPATH, sel[6:]))
|
|
55
|
+
)
|
|
56
|
+
else:
|
|
57
|
+
return WebDriverWait(driver, timeout).until(
|
|
58
|
+
EC.presence_of_element_located((By.CSS_SELECTOR, sel))
|
|
59
|
+
)
|
|
60
|
+
|
|
61
|
+
try:
|
|
62
|
+
if action == 'click':
|
|
63
|
+
elem = wait_for_element(selector)
|
|
64
|
+
elem.click()
|
|
65
|
+
output = "Clicked: {}".format(selector)
|
|
66
|
+
|
|
67
|
+
elif action == 'type':
|
|
68
|
+
elem = wait_for_element(selector)
|
|
69
|
+
elem.click() # Focus first
|
|
70
|
+
elem.clear()
|
|
71
|
+
elem.send_keys(value)
|
|
72
|
+
output = "Typed '{}' into {}".format(value, selector)
|
|
73
|
+
|
|
74
|
+
elif action == 'set_value':
|
|
75
|
+
# Force set value via JS, bypasses validation/calendars
|
|
76
|
+
elem = wait_for_element(selector)
|
|
77
|
+
driver.execute_script("arguments[0].value = arguments[1]; arguments[0].dispatchEvent(new Event('input', {bubbles: true})); arguments[0].dispatchEvent(new Event('change', {bubbles: true}));", elem, value)
|
|
78
|
+
output = "Set value '{}' on {}".format(value, selector)
|
|
79
|
+
|
|
80
|
+
elif action == 'type_and_enter':
|
|
81
|
+
elem = wait_for_element(selector)
|
|
82
|
+
elem.clear()
|
|
83
|
+
elem.send_keys(value)
|
|
84
|
+
elem.send_keys(Keys.RETURN)
|
|
85
|
+
output = "Typed '{}' and pressed Enter".format(value)
|
|
86
|
+
|
|
87
|
+
elif action == 'select':
|
|
88
|
+
elem = wait_for_element(selector)
|
|
89
|
+
select = Select(elem)
|
|
90
|
+
select.select_by_visible_text(value)
|
|
91
|
+
output = "Selected '{}' in {}".format(value, selector)
|
|
92
|
+
|
|
93
|
+
elif action == 'wait':
|
|
94
|
+
timeout = int(value) if value else 10
|
|
95
|
+
wait_for_element(selector, timeout)
|
|
96
|
+
output = "Element found: {}".format(selector)
|
|
97
|
+
|
|
98
|
+
elif action == 'scroll':
|
|
99
|
+
if value == 'down':
|
|
100
|
+
driver.execute_script("window.scrollBy(0, 500)")
|
|
101
|
+
elif value == 'up':
|
|
102
|
+
driver.execute_script("window.scrollBy(0, -500)")
|
|
103
|
+
elif selector:
|
|
104
|
+
elem = find_element(selector)
|
|
105
|
+
driver.execute_script("arguments[0].scrollIntoView();", elem)
|
|
106
|
+
output = "Scrolled {}".format(value or 'to element')
|
|
107
|
+
|
|
108
|
+
elif action == 'get_text':
|
|
109
|
+
elem = wait_for_element(selector)
|
|
110
|
+
output = "Text: {}".format(elem.text)
|
|
111
|
+
|
|
112
|
+
elif action == 'get_page':
|
|
113
|
+
title = driver.title
|
|
114
|
+
url = driver.current_url
|
|
115
|
+
body = driver.find_element(By.TAG_NAME, 'body')
|
|
116
|
+
text = body.text[:3000]
|
|
117
|
+
output = "Page: {} ({})\n\nContent:\n{}".format(title, url, text)
|
|
118
|
+
|
|
119
|
+
elif action == 'get_elements':
|
|
120
|
+
elements = []
|
|
121
|
+
|
|
122
|
+
def is_visible(el):
|
|
123
|
+
try:
|
|
124
|
+
return el.is_displayed() and el.size['width'] > 0
|
|
125
|
+
except:
|
|
126
|
+
return False
|
|
127
|
+
|
|
128
|
+
def safe_selector(tag, el):
|
|
129
|
+
eid = el.get_attribute('id')
|
|
130
|
+
name = el.get_attribute('name')
|
|
131
|
+
if eid and '.' not in eid and ' ' not in eid:
|
|
132
|
+
return '#' + eid
|
|
133
|
+
elif eid:
|
|
134
|
+
return '{}[id="{}"]'.format(tag, eid)
|
|
135
|
+
elif name:
|
|
136
|
+
return '{}[name="{}"]'.format(tag, name)
|
|
137
|
+
return None
|
|
138
|
+
|
|
139
|
+
# Get inputs
|
|
140
|
+
for inp in driver.find_elements(By.CSS_SELECTOR, 'input:not([type="hidden"])'):
|
|
141
|
+
if not is_visible(inp):
|
|
142
|
+
continue
|
|
143
|
+
sel = safe_selector('input', inp)
|
|
144
|
+
if not sel:
|
|
145
|
+
ph = inp.get_attribute('placeholder')
|
|
146
|
+
if ph:
|
|
147
|
+
sel = 'input[placeholder="{}"]'.format(ph)
|
|
148
|
+
else:
|
|
149
|
+
continue
|
|
150
|
+
info = {'tag': 'input', 'type': inp.get_attribute('type') or 'text', 'selector': sel}
|
|
151
|
+
info['placeholder'] = inp.get_attribute('placeholder') or ''
|
|
152
|
+
elements.append(info)
|
|
153
|
+
|
|
154
|
+
# Get buttons
|
|
155
|
+
for btn in driver.find_elements(By.CSS_SELECTOR, 'button, input[type="submit"], input[type="button"]'):
|
|
156
|
+
if not is_visible(btn):
|
|
157
|
+
continue
|
|
158
|
+
sel = safe_selector('button', btn)
|
|
159
|
+
if not sel and btn.text:
|
|
160
|
+
sel = 'xpath://button[contains(text(),"{}")]'.format(btn.text[:30])
|
|
161
|
+
if not sel:
|
|
162
|
+
continue
|
|
163
|
+
info = {'tag': 'button', 'selector': sel, 'text': (btn.text or '')[:50]}
|
|
164
|
+
elements.append(info)
|
|
165
|
+
|
|
166
|
+
# Get select dropdowns
|
|
167
|
+
for s in driver.find_elements(By.TAG_NAME, 'select'):
|
|
168
|
+
if not is_visible(s):
|
|
169
|
+
continue
|
|
170
|
+
sel = safe_selector('select', s)
|
|
171
|
+
if not sel:
|
|
172
|
+
continue
|
|
173
|
+
opts = [o.text for o in s.find_elements(By.TAG_NAME, 'option')[:5]]
|
|
174
|
+
info = {'tag': 'select', 'selector': sel, 'options': opts}
|
|
175
|
+
elements.append(info)
|
|
176
|
+
|
|
177
|
+
# Get links
|
|
178
|
+
for link in driver.find_elements(By.TAG_NAME, 'a')[:30]:
|
|
179
|
+
if not is_visible(link) or not link.text or len(link.text) < 2:
|
|
180
|
+
continue
|
|
181
|
+
sel = safe_selector('a', link)
|
|
182
|
+
if not sel:
|
|
183
|
+
sel = 'xpath://a[contains(text(),"{}")]'.format(link.text[:30])
|
|
184
|
+
info = {'tag': 'a', 'selector': sel, 'text': link.text[:50]}
|
|
185
|
+
elements.append(info)
|
|
186
|
+
|
|
187
|
+
output = "Found {} visible elements:\n".format(len(elements))
|
|
188
|
+
for el in elements[:40]:
|
|
189
|
+
output += "{}: {} ".format(el['tag'], el.get('selector', ''))
|
|
190
|
+
if el.get('text'):
|
|
191
|
+
output += '"{}" '.format(el['text'][:30])
|
|
192
|
+
if el.get('placeholder'):
|
|
193
|
+
output += 'placeholder="{}" '.format(el['placeholder'])
|
|
194
|
+
if el.get('options'):
|
|
195
|
+
output += "opts={} ".format(el['options'][:3])
|
|
196
|
+
output += "\n"
|
|
197
|
+
|
|
198
|
+
elif action == 'press_key':
|
|
199
|
+
key_map = {
|
|
200
|
+
'enter': Keys.RETURN, 'return': Keys.RETURN,
|
|
201
|
+
'tab': Keys.TAB,
|
|
202
|
+
'escape': Keys.ESCAPE, 'esc': Keys.ESCAPE,
|
|
203
|
+
'down': Keys.DOWN, 'up': Keys.UP,
|
|
204
|
+
'left': Keys.LEFT, 'right': Keys.RIGHT,
|
|
205
|
+
'backspace': Keys.BACKSPACE,
|
|
206
|
+
'delete': Keys.DELETE,
|
|
207
|
+
}
|
|
208
|
+
key = key_map.get(value.lower(), value)
|
|
209
|
+
if selector:
|
|
210
|
+
elem = find_element(selector)
|
|
211
|
+
elem.send_keys(key)
|
|
212
|
+
else:
|
|
213
|
+
driver.find_element(By.TAG_NAME, 'body').send_keys(key)
|
|
214
|
+
output = "Pressed key: {}".format(value)
|
|
215
|
+
|
|
216
|
+
else:
|
|
217
|
+
output = "Unknown action: {}".format(action)
|
|
218
|
+
|
|
219
|
+
except Exception as e:
|
|
220
|
+
output = "Browser action failed: {}".format(str(e))
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
jinx_name: browser_screenshot
|
|
2
|
+
description: Take a screenshot of the current browser page.
|
|
3
|
+
inputs:
|
|
4
|
+
- filename:
|
|
5
|
+
description: "Optional filename for screenshot"
|
|
6
|
+
default: ""
|
|
7
|
+
|
|
8
|
+
steps:
|
|
9
|
+
- name: browser_screenshot
|
|
10
|
+
engine: python
|
|
11
|
+
code: |
|
|
12
|
+
import os
|
|
13
|
+
from datetime import datetime
|
|
14
|
+
from npcpy.work.browser import get_current_driver
|
|
15
|
+
|
|
16
|
+
filename = context.get('filename', '')
|
|
17
|
+
|
|
18
|
+
driver = get_current_driver()
|
|
19
|
+
if not driver:
|
|
20
|
+
output = "No active browser. Use open_browser first."
|
|
21
|
+
exit()
|
|
22
|
+
|
|
23
|
+
try:
|
|
24
|
+
screenshots_dir = os.path.expanduser('~/.npcsh/screenshots')
|
|
25
|
+
os.makedirs(screenshots_dir, exist_ok=True)
|
|
26
|
+
|
|
27
|
+
if not filename:
|
|
28
|
+
timestamp = datetime.now().strftime('%Y%m%d_%H%M%S')
|
|
29
|
+
filename = "browser_{}.png".format(timestamp)
|
|
30
|
+
|
|
31
|
+
if not filename.endswith('.png'):
|
|
32
|
+
filename += '.png'
|
|
33
|
+
|
|
34
|
+
filepath = os.path.join(screenshots_dir, filename)
|
|
35
|
+
driver.save_screenshot(filepath)
|
|
36
|
+
|
|
37
|
+
output = "Screenshot saved: {}".format(filepath)
|
|
38
|
+
|
|
39
|
+
except Exception as e:
|
|
40
|
+
output = "Screenshot failed: {}".format(str(e))
|
|
@@ -2,7 +2,7 @@ jinx_name: "build"
|
|
|
2
2
|
description: "Build deployment artifacts for NPC team"
|
|
3
3
|
inputs:
|
|
4
4
|
- target: "flask" # The type of deployment target (e.g., flask, docker, cli, static).
|
|
5
|
-
-
|
|
5
|
+
- outdir: "./build" # The output directory for built artifacts.
|
|
6
6
|
- team: "./npc_team" # The path to the NPC team directory.
|
|
7
7
|
- port: 5337 # The port for flask server builds.
|
|
8
8
|
- cors: "" # Comma-separated CORS origins for flask server builds.
|
|
@@ -28,13 +28,13 @@ steps:
|
|
|
28
28
|
def build_cli_executable(config, **kwargs): return {"output": f"Mock build cli: {config}", "messages": []}
|
|
29
29
|
def build_static_site(config, **kwargs): return {"output": f"Mock build static: {config}", "messages": []}
|
|
30
30
|
|
|
31
|
-
target = context.get('target')
|
|
32
|
-
output_dir = context.get('
|
|
33
|
-
team_path = context.get('team')
|
|
34
|
-
port = context.get('port')
|
|
35
|
-
cors_origins_str = context.get('cors')
|
|
36
|
-
|
|
37
|
-
cors_origins = [origin.strip() for origin in cors_origins_str.split(',')
|
|
31
|
+
target = context.get('target') or 'flask'
|
|
32
|
+
output_dir = context.get('outdir') or './build'
|
|
33
|
+
team_path = context.get('team') or './npc_team'
|
|
34
|
+
port = context.get('port') or 5337
|
|
35
|
+
cors_origins_str = context.get('cors') or ''
|
|
36
|
+
|
|
37
|
+
cors_origins = [origin.strip() for origin in cors_origins_str.split(',') if origin.strip()] or None
|
|
38
38
|
|
|
39
39
|
build_config = {
|
|
40
40
|
'team_path': os.path.abspath(os.path.expanduser(team_path)),
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
jinx_name: click
|
|
2
|
+
description: Click at screen coordinates (0-100 percentage)
|
|
3
|
+
inputs:
|
|
4
|
+
- x: 50 # X coordinate as percentage (0-100)
|
|
5
|
+
- y: 50 # Y coordinate as percentage (0-100)
|
|
6
|
+
|
|
7
|
+
steps:
|
|
8
|
+
- name: perform_click
|
|
9
|
+
engine: python
|
|
10
|
+
code: |
|
|
11
|
+
from npcpy.work.desktop import perform_action
|
|
12
|
+
|
|
13
|
+
x = float(context.get('x', 50))
|
|
14
|
+
y = float(context.get('y', 50))
|
|
15
|
+
messages = context.get('messages', [])
|
|
16
|
+
|
|
17
|
+
try:
|
|
18
|
+
perform_action({'type': 'click', 'x': x, 'y': y})
|
|
19
|
+
context['output'] = f"Clicked at ({x}%, {y}%)"
|
|
20
|
+
except Exception as e:
|
|
21
|
+
context['output'] = f"Click failed: {e}"
|
|
22
|
+
|
|
23
|
+
context['messages'] = messages
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
jinx_name: close_browser
|
|
2
|
+
description: Close the current browser session.
|
|
3
|
+
inputs: []
|
|
4
|
+
|
|
5
|
+
steps:
|
|
6
|
+
- name: close_browser
|
|
7
|
+
engine: python
|
|
8
|
+
code: |
|
|
9
|
+
from npcpy.work.browser import close_current
|
|
10
|
+
|
|
11
|
+
if close_current():
|
|
12
|
+
output = "Browser closed."
|
|
13
|
+
else:
|
|
14
|
+
output = "No active browser session."
|
|
@@ -0,0 +1,232 @@
|
|
|
1
|
+
jinx_name: convene
|
|
2
|
+
description: Run a cycle of discussions between NPCs on a topic. The orchestrator convenes agents to discuss and synthesize.
|
|
3
|
+
inputs:
|
|
4
|
+
- topic: ""
|
|
5
|
+
- npcs: "alicanto,corca,guac"
|
|
6
|
+
- rounds: 3
|
|
7
|
+
- model: null
|
|
8
|
+
- provider: null
|
|
9
|
+
steps:
|
|
10
|
+
- name: convene_discussion
|
|
11
|
+
engine: python
|
|
12
|
+
code: |
|
|
13
|
+
from termcolor import colored
|
|
14
|
+
from npcpy.llm_funcs import get_llm_response
|
|
15
|
+
|
|
16
|
+
topic = context.get('topic', '')
|
|
17
|
+
npcs_str = context.get('npcs', 'alicanto,corca,guac')
|
|
18
|
+
rounds = int(context.get('rounds', 3))
|
|
19
|
+
|
|
20
|
+
npc = context.get('npc')
|
|
21
|
+
team = context.get('team')
|
|
22
|
+
messages = context.get('messages', [])
|
|
23
|
+
|
|
24
|
+
model = context.get('model') or (npc.model if npc else 'gemini-1.5-flash')
|
|
25
|
+
provider = context.get('provider') or (npc.provider if npc else 'gemini')
|
|
26
|
+
|
|
27
|
+
if not topic:
|
|
28
|
+
context['output'] = """Usage: /convene <topic>
|
|
29
|
+
|
|
30
|
+
Options:
|
|
31
|
+
--npcs LIST Comma-separated NPC names (default: alicanto,corca,guac)
|
|
32
|
+
--rounds N Number of discussion rounds (default: 3)
|
|
33
|
+
|
|
34
|
+
Example: /convene "How should we approach the database migration?" --npcs corca,guac,frederic
|
|
35
|
+
"""
|
|
36
|
+
exit()
|
|
37
|
+
|
|
38
|
+
npc_names = [n.strip() for n in npcs_str.split(',')]
|
|
39
|
+
|
|
40
|
+
print(f"""
|
|
41
|
+
██████ ██████ ███ ██ ██ ██ ███████ ███ ██ ███████
|
|
42
|
+
██ ██ ██ ████ ██ ██ ██ ██ ████ ██ ██
|
|
43
|
+
██ ██ ██ ██ ██ ██ ██ ██ █████ ██ ██ ██ █████
|
|
44
|
+
██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
|
|
45
|
+
██████ ██████ ██ ████ ████ ███████ ██ ████ ███████
|
|
46
|
+
|
|
47
|
+
Convening Discussion
|
|
48
|
+
Topic: {topic}
|
|
49
|
+
Participants: {', '.join(npc_names)}
|
|
50
|
+
Rounds: {rounds}
|
|
51
|
+
""")
|
|
52
|
+
|
|
53
|
+
# Get NPC personas
|
|
54
|
+
participants = []
|
|
55
|
+
for name in npc_names:
|
|
56
|
+
if team and hasattr(team, 'npcs') and name in team.npcs:
|
|
57
|
+
target_npc = team.npcs[name]
|
|
58
|
+
persona = getattr(target_npc, 'primary_directive', f'{name} specialist')
|
|
59
|
+
participants.append({'name': name, 'persona': persona, 'npc': target_npc})
|
|
60
|
+
else:
|
|
61
|
+
participants.append({'name': name, 'persona': f'{name} - general assistant', 'npc': None})
|
|
62
|
+
|
|
63
|
+
import random
|
|
64
|
+
|
|
65
|
+
discussion_log = []
|
|
66
|
+
|
|
67
|
+
for round_num in range(1, rounds + 1):
|
|
68
|
+
print(colored(f"\n{'='*60}", "cyan"))
|
|
69
|
+
print(colored(f" ROUND {round_num}/{rounds}", "cyan", attrs=["bold"]))
|
|
70
|
+
print(colored(f"{'='*60}", "cyan"))
|
|
71
|
+
|
|
72
|
+
round_contributions = []
|
|
73
|
+
|
|
74
|
+
for participant in participants:
|
|
75
|
+
name = participant['name']
|
|
76
|
+
persona = participant['persona']
|
|
77
|
+
|
|
78
|
+
# Build context from previous contributions
|
|
79
|
+
prev_context = ""
|
|
80
|
+
if discussion_log:
|
|
81
|
+
prev_context = "\n\nPrevious discussion:\n"
|
|
82
|
+
for entry in discussion_log[-len(participants)*2:]:
|
|
83
|
+
prev_context += f"[{entry['speaker']}]: {entry['contribution'][:200]}...\n"
|
|
84
|
+
|
|
85
|
+
if round_contributions:
|
|
86
|
+
prev_context += "\nThis round so far:\n"
|
|
87
|
+
for entry in round_contributions:
|
|
88
|
+
prev_context += f"[{entry['speaker']}]: {entry['contribution'][:200]}...\n"
|
|
89
|
+
|
|
90
|
+
prompt = f"""You are {name}. {persona}
|
|
91
|
+
|
|
92
|
+
Topic under discussion: "{topic}"
|
|
93
|
+
{prev_context}
|
|
94
|
+
|
|
95
|
+
Provide your perspective on this topic. Be concise but insightful.
|
|
96
|
+
Build on what others have said if applicable.
|
|
97
|
+
If you disagree with something, explain why constructively.
|
|
98
|
+
"""
|
|
99
|
+
|
|
100
|
+
print(colored(f"\n[{name}]:", "yellow", attrs=["bold"]))
|
|
101
|
+
|
|
102
|
+
resp = get_llm_response(
|
|
103
|
+
prompt,
|
|
104
|
+
model=model,
|
|
105
|
+
provider=provider,
|
|
106
|
+
npc=participant.get('npc') or npc,
|
|
107
|
+
temperature=0.7
|
|
108
|
+
)
|
|
109
|
+
|
|
110
|
+
contribution = str(resp.get('response', ''))
|
|
111
|
+
print(contribution)
|
|
112
|
+
|
|
113
|
+
entry = {
|
|
114
|
+
'round': round_num,
|
|
115
|
+
'speaker': name,
|
|
116
|
+
'contribution': contribution
|
|
117
|
+
}
|
|
118
|
+
round_contributions.append(entry)
|
|
119
|
+
discussion_log.append(entry)
|
|
120
|
+
|
|
121
|
+
# Sample a followup from another participant
|
|
122
|
+
other_participants = [p for p in participants if p['name'] != name]
|
|
123
|
+
if other_participants:
|
|
124
|
+
followup_participant = random.choice(other_participants)
|
|
125
|
+
followup_name = followup_participant['name']
|
|
126
|
+
followup_persona = followup_participant['persona']
|
|
127
|
+
|
|
128
|
+
followup_prompt = f"""You are {followup_name}. {followup_persona}
|
|
129
|
+
|
|
130
|
+
Topic: "{topic}"
|
|
131
|
+
|
|
132
|
+
{name} just said: "{contribution[:500]}"
|
|
133
|
+
|
|
134
|
+
Respond briefly to this specific point - agree, disagree, build on it, or ask a clarifying question.
|
|
135
|
+
Keep it to 2-3 sentences.
|
|
136
|
+
"""
|
|
137
|
+
|
|
138
|
+
print(colored(f"\n [{followup_name} responds]:", "cyan"))
|
|
139
|
+
|
|
140
|
+
followup_resp = get_llm_response(
|
|
141
|
+
followup_prompt,
|
|
142
|
+
model=model,
|
|
143
|
+
provider=provider,
|
|
144
|
+
npc=followup_participant.get('npc') or npc,
|
|
145
|
+
temperature=0.7
|
|
146
|
+
)
|
|
147
|
+
|
|
148
|
+
followup_contribution = str(followup_resp.get('response', ''))
|
|
149
|
+
print(f" {followup_contribution}")
|
|
150
|
+
|
|
151
|
+
discussion_log.append({
|
|
152
|
+
'round': round_num,
|
|
153
|
+
'speaker': followup_name,
|
|
154
|
+
'contribution': followup_contribution,
|
|
155
|
+
'type': 'followup'
|
|
156
|
+
})
|
|
157
|
+
|
|
158
|
+
# Probability of original speaker responding back vs someone else
|
|
159
|
+
if random.random() < 0.4:
|
|
160
|
+
# Original speaker responds
|
|
161
|
+
responder = participant
|
|
162
|
+
responder_name = name
|
|
163
|
+
else:
|
|
164
|
+
# Sample from others (could be followup person or someone else)
|
|
165
|
+
responder = random.choice(other_participants)
|
|
166
|
+
responder_name = responder['name']
|
|
167
|
+
|
|
168
|
+
if random.random() < 0.6: # 60% chance of a counter-response
|
|
169
|
+
counter_prompt = f"""You are {responder_name}. {responder['persona']}
|
|
170
|
+
|
|
171
|
+
Topic: "{topic}"
|
|
172
|
+
|
|
173
|
+
{followup_name} responded: "{followup_contribution}"
|
|
174
|
+
|
|
175
|
+
Brief reaction (1-2 sentences). Move the discussion forward.
|
|
176
|
+
"""
|
|
177
|
+
|
|
178
|
+
print(colored(f"\n [{responder_name}]:", "magenta"))
|
|
179
|
+
|
|
180
|
+
counter_resp = get_llm_response(
|
|
181
|
+
counter_prompt,
|
|
182
|
+
model=model,
|
|
183
|
+
provider=provider,
|
|
184
|
+
npc=responder.get('npc') or npc,
|
|
185
|
+
temperature=0.7
|
|
186
|
+
)
|
|
187
|
+
|
|
188
|
+
counter_contribution = str(counter_resp.get('response', ''))
|
|
189
|
+
print(f" {counter_contribution}")
|
|
190
|
+
|
|
191
|
+
discussion_log.append({
|
|
192
|
+
'round': round_num,
|
|
193
|
+
'speaker': responder_name,
|
|
194
|
+
'contribution': counter_contribution,
|
|
195
|
+
'type': 'counter'
|
|
196
|
+
})
|
|
197
|
+
|
|
198
|
+
# Synthesis
|
|
199
|
+
print(colored(f"\n{'='*60}", "green"))
|
|
200
|
+
print(colored(" SYNTHESIS", "green", attrs=["bold"]))
|
|
201
|
+
print(colored(f"{'='*60}", "green"))
|
|
202
|
+
|
|
203
|
+
all_contributions = "\n".join([
|
|
204
|
+
f"[{e['speaker']} - Round {e['round']}]: {e['contribution']}"
|
|
205
|
+
for e in discussion_log
|
|
206
|
+
])
|
|
207
|
+
|
|
208
|
+
synthesis_prompt = f"""As the convener of this discussion on "{topic}", synthesize the key points:
|
|
209
|
+
|
|
210
|
+
Full discussion:
|
|
211
|
+
{all_contributions}
|
|
212
|
+
|
|
213
|
+
Provide:
|
|
214
|
+
1. Key agreements and consensus points
|
|
215
|
+
2. Areas of disagreement or tension
|
|
216
|
+
3. Novel ideas that emerged
|
|
217
|
+
4. Recommended next steps or actions
|
|
218
|
+
"""
|
|
219
|
+
|
|
220
|
+
resp = get_llm_response(synthesis_prompt, model=model, provider=provider, npc=npc, temperature=0.4)
|
|
221
|
+
synthesis = str(resp.get('response', ''))
|
|
222
|
+
print(synthesis)
|
|
223
|
+
|
|
224
|
+
context['output'] = synthesis
|
|
225
|
+
context['messages'] = messages
|
|
226
|
+
context['convene_result'] = {
|
|
227
|
+
'topic': topic,
|
|
228
|
+
'participants': npc_names,
|
|
229
|
+
'rounds': rounds,
|
|
230
|
+
'discussion': discussion_log,
|
|
231
|
+
'synthesis': synthesis
|
|
232
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
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"
|
|
13
|
+
primary_directive: |
|
|
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
|
|
31
|
+
|