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.
- npcsh/_state.py +491 -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.13.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.13.data/data/npcsh/npc_team → npcsh/npc_team/jinxs/lib/core}/edit_file.jinx +3 -3
- {npcsh-1.1.13.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.13.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.15.data/data/npcsh/npc_team/alicanto.npc +23 -0
- npcsh-1.1.15.data/data/npcsh/npc_team/arxiv.jinx +76 -0
- npcsh-1.1.15.data/data/npcsh/npc_team/browser_action.jinx +220 -0
- npcsh-1.1.15.data/data/npcsh/npc_team/browser_screenshot.jinx +40 -0
- {npcsh-1.1.13.data → npcsh-1.1.15.data}/data/npcsh/npc_team/build.jinx +8 -8
- npcsh-1.1.15.data/data/npcsh/npc_team/click.jinx +23 -0
- npcsh-1.1.15.data/data/npcsh/npc_team/close_browser.jinx +14 -0
- npcsh-1.1.15.data/data/npcsh/npc_team/convene.jinx +232 -0
- npcsh-1.1.15.data/data/npcsh/npc_team/corca.npc +31 -0
- npcsh-1.1.15.data/data/npcsh/npc_team/delegate.jinx +184 -0
- {npcsh/npc_team/jinxs/utils → npcsh-1.1.15.data/data/npcsh/npc_team}/edit_file.jinx +3 -3
- npcsh-1.1.15.data/data/npcsh/npc_team/frederic.npc +27 -0
- npcsh-1.1.15.data/data/npcsh/npc_team/guac.npc +22 -0
- npcsh-1.1.15.data/data/npcsh/npc_team/jinxs.jinx +176 -0
- npcsh-1.1.15.data/data/npcsh/npc_team/kadiefa.npc +21 -0
- npcsh-1.1.15.data/data/npcsh/npc_team/key_press.jinx +26 -0
- npcsh-1.1.15.data/data/npcsh/npc_team/launch_app.jinx +37 -0
- {npcsh/npc_team/jinxs/utils → npcsh-1.1.15.data/data/npcsh/npc_team}/load_file.jinx +1 -1
- npcsh-1.1.15.data/data/npcsh/npc_team/nql.jinx +141 -0
- npcsh-1.1.15.data/data/npcsh/npc_team/open_browser.jinx +43 -0
- npcsh-1.1.15.data/data/npcsh/npc_team/paper_search.jinx +101 -0
- npcsh-1.1.15.data/data/npcsh/npc_team/paste.jinx +134 -0
- npcsh-1.1.15.data/data/npcsh/npc_team/plonk.npc +27 -0
- npcsh-1.1.15.data/data/npcsh/npc_team/plonkjr.npc +23 -0
- npcsh-1.1.15.data/data/npcsh/npc_team/screenshot.jinx +23 -0
- {npcsh/npc_team/jinxs/utils → npcsh-1.1.15.data/data/npcsh/npc_team}/search.jinx +2 -1
- npcsh-1.1.15.data/data/npcsh/npc_team/semantic_scholar.jinx +69 -0
- {npcsh-1.1.13.data → npcsh-1.1.15.data}/data/npcsh/npc_team/sh.jinx +2 -8
- npcsh-1.1.15.data/data/npcsh/npc_team/shh.jinx +17 -0
- npcsh-1.1.15.data/data/npcsh/npc_team/sibiji.npc +24 -0
- {npcsh-1.1.13.data → npcsh-1.1.15.data}/data/npcsh/npc_team/sql.jinx +1 -1
- npcsh-1.1.15.data/data/npcsh/npc_team/switch.jinx +62 -0
- npcsh-1.1.15.data/data/npcsh/npc_team/switches.jinx +61 -0
- npcsh-1.1.15.data/data/npcsh/npc_team/sync.jinx +230 -0
- npcsh-1.1.15.data/data/npcsh/npc_team/teamviz.jinx +205 -0
- npcsh-1.1.15.data/data/npcsh/npc_team/type_text.jinx +27 -0
- npcsh-1.1.15.data/data/npcsh/npc_team/verbose.jinx +17 -0
- {npcsh/npc_team/jinxs/utils → npcsh-1.1.15.data/data/npcsh/npc_team}/vixynt.jinx +8 -30
- npcsh-1.1.15.data/data/npcsh/npc_team/wait.jinx +21 -0
- npcsh-1.1.15.data/data/npcsh/npc_team/wander.jinx +152 -0
- {npcsh-1.1.13.dist-info → npcsh-1.1.15.dist-info}/METADATA +399 -58
- npcsh-1.1.15.dist-info/RECORD +170 -0
- npcsh-1.1.15.dist-info/entry_points.txt +19 -0
- npcsh-1.1.15.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.13.data/data/npcsh/npc_team/agent.jinx +0 -17
- npcsh-1.1.13.data/data/npcsh/npc_team/alicanto.jinx +0 -194
- npcsh-1.1.13.data/data/npcsh/npc_team/alicanto.npc +0 -2
- npcsh-1.1.13.data/data/npcsh/npc_team/corca.jinx +0 -249
- npcsh-1.1.13.data/data/npcsh/npc_team/corca.npc +0 -12
- npcsh-1.1.13.data/data/npcsh/npc_team/foreman.npc +0 -7
- npcsh-1.1.13.data/data/npcsh/npc_team/frederic.npc +0 -6
- npcsh-1.1.13.data/data/npcsh/npc_team/guac.jinx +0 -317
- npcsh-1.1.13.data/data/npcsh/npc_team/jinxs.jinx +0 -32
- npcsh-1.1.13.data/data/npcsh/npc_team/kadiefa.npc +0 -3
- npcsh-1.1.13.data/data/npcsh/npc_team/plonk.jinx +0 -214
- npcsh-1.1.13.data/data/npcsh/npc_team/plonk.npc +0 -2
- npcsh-1.1.13.data/data/npcsh/npc_team/plonkjr.npc +0 -2
- npcsh-1.1.13.data/data/npcsh/npc_team/pti.jinx +0 -170
- npcsh-1.1.13.data/data/npcsh/npc_team/sibiji.npc +0 -3
- npcsh-1.1.13.data/data/npcsh/npc_team/wander.jinx +0 -186
- npcsh-1.1.13.dist-info/RECORD +0 -135
- npcsh-1.1.13.dist-info/entry_points.txt +0 -9
- npcsh-1.1.13.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.13.data → npcsh-1.1.15.data}/data/npcsh/npc_team/alicanto.png +0 -0
- {npcsh-1.1.13.data → npcsh-1.1.15.data}/data/npcsh/npc_team/chat.jinx +0 -0
- {npcsh-1.1.13.data → npcsh-1.1.15.data}/data/npcsh/npc_team/cmd.jinx +0 -0
- {npcsh-1.1.13.data → npcsh-1.1.15.data}/data/npcsh/npc_team/compile.jinx +0 -0
- {npcsh-1.1.13.data → npcsh-1.1.15.data}/data/npcsh/npc_team/compress.jinx +0 -0
- {npcsh-1.1.13.data → npcsh-1.1.15.data}/data/npcsh/npc_team/corca.png +0 -0
- {npcsh-1.1.13.data → npcsh-1.1.15.data}/data/npcsh/npc_team/corca_example.png +0 -0
- {npcsh-1.1.13.data → npcsh-1.1.15.data}/data/npcsh/npc_team/frederic4.png +0 -0
- {npcsh-1.1.13.data → npcsh-1.1.15.data}/data/npcsh/npc_team/guac.png +0 -0
- {npcsh-1.1.13.data → npcsh-1.1.15.data}/data/npcsh/npc_team/help.jinx +0 -0
- {npcsh-1.1.13.data → npcsh-1.1.15.data}/data/npcsh/npc_team/init.jinx +0 -0
- {npcsh-1.1.13.data → npcsh-1.1.15.data}/data/npcsh/npc_team/kadiefa.png +0 -0
- {npcsh-1.1.13.data → npcsh-1.1.15.data}/data/npcsh/npc_team/npc-studio.jinx +0 -0
- {npcsh-1.1.13.data → npcsh-1.1.15.data}/data/npcsh/npc_team/npcsh.ctx +0 -0
- {npcsh-1.1.13.data → npcsh-1.1.15.data}/data/npcsh/npc_team/npcsh_sibiji.png +0 -0
- {npcsh-1.1.13.data → npcsh-1.1.15.data}/data/npcsh/npc_team/ots.jinx +0 -0
- {npcsh-1.1.13.data → npcsh-1.1.15.data}/data/npcsh/npc_team/plonk.png +0 -0
- {npcsh-1.1.13.data → npcsh-1.1.15.data}/data/npcsh/npc_team/plonkjr.png +0 -0
- {npcsh-1.1.13.data → npcsh-1.1.15.data}/data/npcsh/npc_team/python.jinx +0 -0
- {npcsh-1.1.13.data → npcsh-1.1.15.data}/data/npcsh/npc_team/roll.jinx +0 -0
- {npcsh-1.1.13.data → npcsh-1.1.15.data}/data/npcsh/npc_team/sample.jinx +0 -0
- {npcsh-1.1.13.data → npcsh-1.1.15.data}/data/npcsh/npc_team/serve.jinx +0 -0
- {npcsh-1.1.13.data → npcsh-1.1.15.data}/data/npcsh/npc_team/set.jinx +0 -0
- {npcsh-1.1.13.data → npcsh-1.1.15.data}/data/npcsh/npc_team/sibiji.png +0 -0
- {npcsh-1.1.13.data → npcsh-1.1.15.data}/data/npcsh/npc_team/sleep.jinx +0 -0
- {npcsh-1.1.13.data → npcsh-1.1.15.data}/data/npcsh/npc_team/spool.jinx +0 -0
- {npcsh-1.1.13.data → npcsh-1.1.15.data}/data/npcsh/npc_team/spool.png +0 -0
- {npcsh-1.1.13.data → npcsh-1.1.15.data}/data/npcsh/npc_team/trigger.jinx +0 -0
- {npcsh-1.1.13.data → npcsh-1.1.15.data}/data/npcsh/npc_team/usage.jinx +0 -0
- {npcsh-1.1.13.data → npcsh-1.1.15.data}/data/npcsh/npc_team/yap.jinx +0 -0
- {npcsh-1.1.13.data → npcsh-1.1.15.data}/data/npcsh/npc_team/yap.png +0 -0
- {npcsh-1.1.13.dist-info → npcsh-1.1.15.dist-info}/WHEEL +0 -0
- {npcsh-1.1.13.dist-info → npcsh-1.1.15.dist-info}/licenses/LICENSE +0 -0
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
jinx_name: open_browser
|
|
2
|
+
description: |
|
|
3
|
+
Open a browser and navigate to a URL. The browser stays open for follow-up commands.
|
|
4
|
+
Use this to start browser automation.
|
|
5
|
+
inputs:
|
|
6
|
+
- url:
|
|
7
|
+
description: "URL to navigate to"
|
|
8
|
+
- browser: "firefox"
|
|
9
|
+
|
|
10
|
+
steps:
|
|
11
|
+
- name: open_browser
|
|
12
|
+
engine: python
|
|
13
|
+
code: |
|
|
14
|
+
from selenium import webdriver
|
|
15
|
+
from selenium.webdriver.firefox.service import Service as FirefoxService
|
|
16
|
+
from selenium.webdriver.chrome.service import Service as ChromeService
|
|
17
|
+
from webdriver_manager.firefox import GeckoDriverManager
|
|
18
|
+
from webdriver_manager.chrome import ChromeDriverManager
|
|
19
|
+
from npcpy.work.browser import set_driver
|
|
20
|
+
import uuid
|
|
21
|
+
|
|
22
|
+
url = context.get('url', '')
|
|
23
|
+
browser_type = context.get('browser', 'firefox').lower()
|
|
24
|
+
|
|
25
|
+
try:
|
|
26
|
+
if browser_type == 'chrome':
|
|
27
|
+
options = webdriver.ChromeOptions()
|
|
28
|
+
options.add_argument('--start-maximized')
|
|
29
|
+
driver = webdriver.Chrome(service=ChromeService(ChromeDriverManager().install()), options=options)
|
|
30
|
+
else:
|
|
31
|
+
options = webdriver.FirefoxOptions()
|
|
32
|
+
driver = webdriver.Firefox(service=FirefoxService(GeckoDriverManager().install()), options=options)
|
|
33
|
+
driver.maximize_window()
|
|
34
|
+
|
|
35
|
+
driver.get(url)
|
|
36
|
+
|
|
37
|
+
session_id = str(uuid.uuid4())[:8]
|
|
38
|
+
set_driver(session_id, driver)
|
|
39
|
+
|
|
40
|
+
output = "Browser opened. Session: {}. Navigated to: {}. Title: {}".format(session_id, url, driver.title)
|
|
41
|
+
except Exception as e:
|
|
42
|
+
import traceback
|
|
43
|
+
output = "Failed to open browser: {}\n{}".format(str(e), traceback.format_exc())
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
jinx_name: paper_search
|
|
2
|
+
description: Search for academic papers across multiple sources (Semantic Scholar, arXiv, local datasets)
|
|
3
|
+
inputs:
|
|
4
|
+
- query: ""
|
|
5
|
+
- limit: 10
|
|
6
|
+
- source: "all"
|
|
7
|
+
steps:
|
|
8
|
+
- name: search_papers
|
|
9
|
+
engine: python
|
|
10
|
+
code: |
|
|
11
|
+
import os
|
|
12
|
+
import time
|
|
13
|
+
import requests
|
|
14
|
+
import urllib.request
|
|
15
|
+
import urllib.parse
|
|
16
|
+
import xml.etree.ElementTree as ET
|
|
17
|
+
|
|
18
|
+
query = context.get('query', '')
|
|
19
|
+
limit = int(context.get('limit', 10))
|
|
20
|
+
source = context.get('source', 'all').lower()
|
|
21
|
+
|
|
22
|
+
if not query:
|
|
23
|
+
context['output'] = """Usage: /paper_search <query> [--limit N] [--source SOURCE]
|
|
24
|
+
|
|
25
|
+
Sources:
|
|
26
|
+
all - Search all available sources (default)
|
|
27
|
+
s2 - Semantic Scholar only (requires S2_API_KEY)
|
|
28
|
+
arxiv - arXiv only
|
|
29
|
+
"""
|
|
30
|
+
exit()
|
|
31
|
+
|
|
32
|
+
all_results = []
|
|
33
|
+
|
|
34
|
+
# Semantic Scholar
|
|
35
|
+
if source in ['all', 's2']:
|
|
36
|
+
api_key = os.environ.get('S2_API_KEY')
|
|
37
|
+
if api_key:
|
|
38
|
+
try:
|
|
39
|
+
url = "https://api.semanticscholar.org/graph/v1/paper/search"
|
|
40
|
+
headers = {"x-api-key": api_key}
|
|
41
|
+
params = {"query": query, "limit": limit, "fields": "title,abstract,authors,year,citationCount,url"}
|
|
42
|
+
response = requests.get(url, headers=headers, params=params, timeout=30)
|
|
43
|
+
response.raise_for_status()
|
|
44
|
+
for paper in response.json().get('data', []):
|
|
45
|
+
all_results.append({
|
|
46
|
+
'source': 'Semantic Scholar',
|
|
47
|
+
'title': paper.get('title', ''),
|
|
48
|
+
'year': paper.get('year'),
|
|
49
|
+
'citations': paper.get('citationCount', 0),
|
|
50
|
+
'authors': [a.get('name', '') for a in paper.get('authors', [])],
|
|
51
|
+
'abstract': paper.get('abstract', '')[:300] if paper.get('abstract') else '',
|
|
52
|
+
'url': paper.get('url', '')
|
|
53
|
+
})
|
|
54
|
+
except Exception as e:
|
|
55
|
+
print(f"S2 error: {e}")
|
|
56
|
+
|
|
57
|
+
# arXiv
|
|
58
|
+
if source in ['all', 'arxiv']:
|
|
59
|
+
try:
|
|
60
|
+
base_url = "http://export.arxiv.org/api/query"
|
|
61
|
+
params = {"search_query": f"all:{query}", "max_results": limit}
|
|
62
|
+
url = f"{base_url}?{urllib.parse.urlencode(params)}"
|
|
63
|
+
with urllib.request.urlopen(url, timeout=30) as response:
|
|
64
|
+
data = response.read().decode('utf-8')
|
|
65
|
+
root = ET.fromstring(data)
|
|
66
|
+
ns = {'atom': 'http://www.w3.org/2005/Atom'}
|
|
67
|
+
for entry in root.findall('atom:entry', ns):
|
|
68
|
+
all_results.append({
|
|
69
|
+
'source': 'arXiv',
|
|
70
|
+
'title': entry.find('atom:title', ns).text.strip().replace('\n', ' '),
|
|
71
|
+
'year': entry.find('atom:published', ns).text[:4],
|
|
72
|
+
'citations': None,
|
|
73
|
+
'authors': [a.find('atom:name', ns).text for a in entry.findall('atom:author', ns)],
|
|
74
|
+
'abstract': entry.find('atom:summary', ns).text.strip()[:300],
|
|
75
|
+
'url': entry.find('atom:id', ns).text
|
|
76
|
+
})
|
|
77
|
+
except Exception as e:
|
|
78
|
+
print(f"arXiv error: {e}")
|
|
79
|
+
|
|
80
|
+
if not all_results:
|
|
81
|
+
context['output'] = f"No papers found for: {query}"
|
|
82
|
+
exit()
|
|
83
|
+
|
|
84
|
+
# Format output
|
|
85
|
+
results = []
|
|
86
|
+
for i, paper in enumerate(all_results[:limit], 1):
|
|
87
|
+
authors = ', '.join(paper['authors'][:3])
|
|
88
|
+
if len(paper['authors']) > 3:
|
|
89
|
+
authors += ' et al.'
|
|
90
|
+
year = paper.get('year', '?')
|
|
91
|
+
citations = f", {paper['citations']} citations" if paper.get('citations') else ""
|
|
92
|
+
|
|
93
|
+
results.append(f"{i}. [{paper['source']}] {paper['title']} ({year}{citations})")
|
|
94
|
+
results.append(f" Authors: {authors}")
|
|
95
|
+
if paper['abstract']:
|
|
96
|
+
results.append(f" Abstract: {paper['abstract']}...")
|
|
97
|
+
results.append(f" URL: {paper['url']}")
|
|
98
|
+
results.append("")
|
|
99
|
+
|
|
100
|
+
context['output'] = f"Found {len(all_results)} papers:\n\n" + "\n".join(results)
|
|
101
|
+
context['papers'] = all_results
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
jinx_name: paste
|
|
2
|
+
description: Grabs content from clipboard (images or text) and saves/displays it. Use this when Ctrl+V paste doesn't work properly.
|
|
3
|
+
inputs:
|
|
4
|
+
- output_path:
|
|
5
|
+
default: ""
|
|
6
|
+
description: "Optional path to save image to. If empty, saves to temp file."
|
|
7
|
+
steps:
|
|
8
|
+
- name: "paste_clipboard"
|
|
9
|
+
engine: "python"
|
|
10
|
+
code: |
|
|
11
|
+
import tempfile
|
|
12
|
+
import os
|
|
13
|
+
from datetime import datetime
|
|
14
|
+
import io
|
|
15
|
+
|
|
16
|
+
output_path = ({{ output_path | default("") | tojson }} or "").strip()
|
|
17
|
+
image_saved = False
|
|
18
|
+
text_content = None
|
|
19
|
+
|
|
20
|
+
# Try PIL/Pillow with ImageGrab first (works on most systems)
|
|
21
|
+
try:
|
|
22
|
+
from PIL import ImageGrab, Image
|
|
23
|
+
|
|
24
|
+
# Try to grab image from clipboard
|
|
25
|
+
img = ImageGrab.grabclipboard()
|
|
26
|
+
|
|
27
|
+
if img is not None:
|
|
28
|
+
if isinstance(img, Image.Image):
|
|
29
|
+
# It's an image
|
|
30
|
+
if output_path:
|
|
31
|
+
save_path = os.path.expanduser(output_path)
|
|
32
|
+
else:
|
|
33
|
+
timestamp = datetime.now().strftime('%Y%m%d_%H%M%S')
|
|
34
|
+
fd, save_path = tempfile.mkstemp(suffix='.png', prefix=f'npcsh_paste_{timestamp}_')
|
|
35
|
+
os.close(fd)
|
|
36
|
+
|
|
37
|
+
img.save(save_path, 'PNG')
|
|
38
|
+
file_size = os.path.getsize(save_path)
|
|
39
|
+
|
|
40
|
+
if file_size > 1024 * 1024:
|
|
41
|
+
size_str = f"{file_size / (1024*1024):.1f} MB"
|
|
42
|
+
elif file_size > 1024:
|
|
43
|
+
size_str = f"{file_size / 1024:.1f} KB"
|
|
44
|
+
else:
|
|
45
|
+
size_str = f"{file_size} bytes"
|
|
46
|
+
|
|
47
|
+
output = f"Image saved to: {save_path} ({size_str}, {img.size[0]}x{img.size[1]})"
|
|
48
|
+
context['pasted_image_path'] = save_path
|
|
49
|
+
image_saved = True
|
|
50
|
+
|
|
51
|
+
elif isinstance(img, list):
|
|
52
|
+
# It's a list of file paths (copied files)
|
|
53
|
+
output = f"Clipboard contains {len(img)} file(s):\n" + "\n".join(img)
|
|
54
|
+
context['pasted_files'] = img
|
|
55
|
+
image_saved = True
|
|
56
|
+
|
|
57
|
+
except ImportError:
|
|
58
|
+
pass
|
|
59
|
+
except Exception as e:
|
|
60
|
+
# ImageGrab failed, try other methods
|
|
61
|
+
pass
|
|
62
|
+
|
|
63
|
+
# If no image, try to get text via tkinter
|
|
64
|
+
if not image_saved:
|
|
65
|
+
try:
|
|
66
|
+
import tkinter as tk
|
|
67
|
+
|
|
68
|
+
root = tk.Tk()
|
|
69
|
+
root.withdraw()
|
|
70
|
+
|
|
71
|
+
try:
|
|
72
|
+
text_content = root.clipboard_get()
|
|
73
|
+
except tk.TclError:
|
|
74
|
+
text_content = None
|
|
75
|
+
|
|
76
|
+
root.destroy()
|
|
77
|
+
|
|
78
|
+
except Exception as e:
|
|
79
|
+
pass
|
|
80
|
+
|
|
81
|
+
# If still nothing, try GTK
|
|
82
|
+
if not image_saved and text_content is None:
|
|
83
|
+
try:
|
|
84
|
+
import gi
|
|
85
|
+
gi.require_version('Gtk', '3.0')
|
|
86
|
+
from gi.repository import Gtk, Gdk, GdkPixbuf
|
|
87
|
+
|
|
88
|
+
clipboard = Gtk.Clipboard.get(Gdk.SELECTION_CLIPBOARD)
|
|
89
|
+
|
|
90
|
+
# Try image first
|
|
91
|
+
pixbuf = clipboard.wait_for_image()
|
|
92
|
+
if pixbuf:
|
|
93
|
+
if output_path:
|
|
94
|
+
save_path = os.path.expanduser(output_path)
|
|
95
|
+
else:
|
|
96
|
+
timestamp = datetime.now().strftime('%Y%m%d_%H%M%S')
|
|
97
|
+
fd, save_path = tempfile.mkstemp(suffix='.png', prefix=f'npcsh_paste_{timestamp}_')
|
|
98
|
+
os.close(fd)
|
|
99
|
+
|
|
100
|
+
pixbuf.savev(save_path, 'png', [], [])
|
|
101
|
+
file_size = os.path.getsize(save_path)
|
|
102
|
+
|
|
103
|
+
if file_size > 1024 * 1024:
|
|
104
|
+
size_str = f"{file_size / (1024*1024):.1f} MB"
|
|
105
|
+
elif file_size > 1024:
|
|
106
|
+
size_str = f"{file_size / 1024:.1f} KB"
|
|
107
|
+
else:
|
|
108
|
+
size_str = f"{file_size} bytes"
|
|
109
|
+
|
|
110
|
+
output = f"Image saved to: {save_path} ({size_str}, {pixbuf.get_width()}x{pixbuf.get_height()})"
|
|
111
|
+
context['pasted_image_path'] = save_path
|
|
112
|
+
image_saved = True
|
|
113
|
+
else:
|
|
114
|
+
# Try text
|
|
115
|
+
text_content = clipboard.wait_for_text()
|
|
116
|
+
|
|
117
|
+
except Exception as e:
|
|
118
|
+
pass
|
|
119
|
+
|
|
120
|
+
# Handle text content
|
|
121
|
+
if not image_saved and text_content:
|
|
122
|
+
line_count = text_content.count('\n') + 1
|
|
123
|
+
char_count = len(text_content)
|
|
124
|
+
context['pasted_text'] = text_content
|
|
125
|
+
|
|
126
|
+
if line_count > 10:
|
|
127
|
+
preview_lines = text_content.split('\n')[:10]
|
|
128
|
+
preview = '\n'.join(preview_lines)
|
|
129
|
+
output = f"Clipboard text ({line_count} lines, {char_count} chars):\n---\n{preview}\n... ({line_count - 10} more lines)\n---"
|
|
130
|
+
else:
|
|
131
|
+
output = f"Clipboard text ({line_count} lines, {char_count} chars):\n---\n{text_content}\n---"
|
|
132
|
+
|
|
133
|
+
elif not image_saved:
|
|
134
|
+
output = "Clipboard is empty or could not access clipboard.\nMake sure you have PIL/Pillow installed: pip install Pillow"
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
name: plonk
|
|
2
|
+
ascii_art: |
|
|
3
|
+
🌲🌲🌲🌲🌲🌲🌲🌲🌲🌲🌲🌲🌲🌲🌲🌲🌲🌲🌲🌲🌲🌲🌲🌲🌲🌲🌲🌲🌲🌲🌲🌲🌲🌲🌲
|
|
4
|
+
██████ ██ ██████ ███ ██ ██ ██
|
|
5
|
+
██ ██ ██ ██ ██ ████ ██ ██ ██
|
|
6
|
+
██████ ██ 🪵 ██ ██ ██ ██ ██ █████
|
|
7
|
+
██ ██ ██ ██ ██ ██ ██ ██ ██
|
|
8
|
+
██ ███████ ██████ ██ ████ ██ ██
|
|
9
|
+
🌲🌲🌲🌲🌲🌲🌲🌲🌲🌲🌲🌲🌲🌲🌲🌲🌲🌲🌲🌲🌲🌲🌲🌲🌲🌲🌲🌲🌲🌲🌲🌲🌲🌲🌲
|
|
10
|
+
colors:
|
|
11
|
+
top: "34,139,34"
|
|
12
|
+
bottom: "139,69,19"
|
|
13
|
+
primary_directive: |
|
|
14
|
+
You are plonk, the browser and GUI automation specialist.
|
|
15
|
+
|
|
16
|
+
Browser tools: open_browser, browser_action, browser_screenshot, close_browser
|
|
17
|
+
|
|
18
|
+
browser_action actions: click, type, type_and_enter, select, wait, scroll, get_text, get_page, get_elements, press_key
|
|
19
|
+
|
|
20
|
+
Use get_elements to discover selectors on the page. Use xpath:// prefix for XPath selectors.
|
|
21
|
+
|
|
22
|
+
Desktop tools: screenshot, click, type_text, key_press, launch_app, wait
|
|
23
|
+
jinxs:
|
|
24
|
+
- lib/browser/*
|
|
25
|
+
- lib/computer_use/*
|
|
26
|
+
- lib/core/sh
|
|
27
|
+
- lib/core/python
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
name: plonkjr
|
|
2
|
+
ascii_art: |
|
|
3
|
+
🌲🌲🌲🌲🌲🌲🌲🌲🌲🌲🌲🌲🌲🌲🌲🌲🌲🌲🌲🌲🌲🌲🌲🌲🌲🌲🌲🌲🌲🌲🌲🌲🌲🌲🌲🌲🌲🌲🌲🌲🌲🌲🌲🌲🌲🌲🌲🌲🌲🌲
|
|
4
|
+
██████ ██ ██████ ███ ██ ██ ██ 🪵 ██ ██████
|
|
5
|
+
██ ██ ██ ██ ██ ████ ██ ██ ██ ██ ██ ██
|
|
6
|
+
██████ ██ ██ ██ ██ ██ ██ █████ 🌲 ██ ██████
|
|
7
|
+
██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
|
|
8
|
+
██ ███████ ██████ ██ ████ ██ ██ █████ ██ ██
|
|
9
|
+
🌲🌲🌲🌲🌲🌲🌲🌲🌲🌲🌲🌲🌲🌲🌲🌲🌲🌲🌲🌲🌲🌲🌲🌲🌲🌲🌲🌲🌲🌲🌲🌲🌲🌲🌲🌲🌲🌲🌲🌲🌲🌲🌲🌲🌲🌲🌲🌲🌲🌲
|
|
10
|
+
colors:
|
|
11
|
+
top: "34,139,34"
|
|
12
|
+
bottom: "139,69,19"
|
|
13
|
+
primary_directive: |
|
|
14
|
+
You are plonkjr, the junior automation assistant. You handle basic computer use tasks:
|
|
15
|
+
taking screenshots, clicking, typing, and pressing keys. You're simpler than plonk -
|
|
16
|
+
you focus on executing the core actions without complex vision-based planning.
|
|
17
|
+
Just do what's asked: screenshot, click, type, key press.
|
|
18
|
+
jinxs:
|
|
19
|
+
- lib/computer_use/screenshot
|
|
20
|
+
- lib/computer_use/click
|
|
21
|
+
- lib/computer_use/type_text
|
|
22
|
+
- lib/computer_use/key_press
|
|
23
|
+
- lib/core/sh
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
jinx_name: screenshot
|
|
2
|
+
description: Capture a screenshot of the current screen
|
|
3
|
+
inputs:
|
|
4
|
+
- output_path: null # Optional path to save screenshot
|
|
5
|
+
|
|
6
|
+
steps:
|
|
7
|
+
- name: capture_screenshot
|
|
8
|
+
engine: python
|
|
9
|
+
code: |
|
|
10
|
+
from npcpy.data.image import capture_screenshot
|
|
11
|
+
|
|
12
|
+
output_path = context.get('output_path')
|
|
13
|
+
messages = context.get('messages', [])
|
|
14
|
+
|
|
15
|
+
result = capture_screenshot(full=True)
|
|
16
|
+
|
|
17
|
+
if result and 'file_path' in result:
|
|
18
|
+
context['output'] = f"Screenshot saved to: {result['file_path']}"
|
|
19
|
+
context['screenshot_path'] = result['file_path']
|
|
20
|
+
else:
|
|
21
|
+
context['output'] = "Failed to capture screenshot"
|
|
22
|
+
|
|
23
|
+
context['messages'] = messages
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
jinx_name: semantic_scholar
|
|
2
|
+
description: Search Semantic Scholar for academic papers. Requires S2_API_KEY env var.
|
|
3
|
+
inputs:
|
|
4
|
+
- query: ""
|
|
5
|
+
- limit: 10
|
|
6
|
+
steps:
|
|
7
|
+
- name: search_s2
|
|
8
|
+
engine: python
|
|
9
|
+
code: |
|
|
10
|
+
import os
|
|
11
|
+
import time
|
|
12
|
+
import requests
|
|
13
|
+
|
|
14
|
+
query = context.get('query', '')
|
|
15
|
+
limit = int(context.get('limit', 10))
|
|
16
|
+
|
|
17
|
+
if not query:
|
|
18
|
+
context['output'] = "Usage: /semantic_scholar <query> [--limit N]"
|
|
19
|
+
exit()
|
|
20
|
+
|
|
21
|
+
api_key = os.environ.get('S2_API_KEY')
|
|
22
|
+
if not api_key:
|
|
23
|
+
context['output'] = "Error: S2_API_KEY environment variable not set. Get one at https://www.semanticscholar.org/product/api"
|
|
24
|
+
exit()
|
|
25
|
+
|
|
26
|
+
url = "https://api.semanticscholar.org/graph/v1/paper/search"
|
|
27
|
+
headers = {"x-api-key": api_key}
|
|
28
|
+
params = {
|
|
29
|
+
"query": query,
|
|
30
|
+
"limit": limit,
|
|
31
|
+
"fields": "title,abstract,authors,year,citationCount,url,tldr"
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
try:
|
|
35
|
+
response = requests.get(url, headers=headers, params=params, timeout=30)
|
|
36
|
+
response.raise_for_status()
|
|
37
|
+
data = response.json().get('data', [])
|
|
38
|
+
|
|
39
|
+
if not data:
|
|
40
|
+
context['output'] = f"No papers found for: {query}"
|
|
41
|
+
exit()
|
|
42
|
+
|
|
43
|
+
results = []
|
|
44
|
+
for i, paper in enumerate(data, 1):
|
|
45
|
+
title = paper.get('title', 'No title')
|
|
46
|
+
year = paper.get('year', '?')
|
|
47
|
+
citations = paper.get('citationCount', 0)
|
|
48
|
+
authors = ', '.join([a.get('name', '') for a in paper.get('authors', [])[:3]])
|
|
49
|
+
if len(paper.get('authors', [])) > 3:
|
|
50
|
+
authors += ' et al.'
|
|
51
|
+
abstract = paper.get('abstract', '')[:200] + '...' if paper.get('abstract') else 'No abstract'
|
|
52
|
+
tldr = paper.get('tldr', {}).get('text', '') if paper.get('tldr') else ''
|
|
53
|
+
url = paper.get('url', '')
|
|
54
|
+
|
|
55
|
+
results.append(f"{i}. {title} ({year})")
|
|
56
|
+
results.append(f" Authors: {authors}")
|
|
57
|
+
results.append(f" Citations: {citations}")
|
|
58
|
+
if tldr:
|
|
59
|
+
results.append(f" TL;DR: {tldr}")
|
|
60
|
+
else:
|
|
61
|
+
results.append(f" Abstract: {abstract}")
|
|
62
|
+
results.append(f" URL: {url}")
|
|
63
|
+
results.append("")
|
|
64
|
+
|
|
65
|
+
context['output'] = f"Found {len(data)} papers:\n\n" + "\n".join(results)
|
|
66
|
+
context['papers'] = data
|
|
67
|
+
|
|
68
|
+
except requests.exceptions.RequestException as e:
|
|
69
|
+
context['output'] = f"Semantic Scholar API error: {e}"
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
jinx_name: sh
|
|
2
|
-
description: Execute bash queries. Should be used to grep for file contents, list directories, explore information to answer user questions more practically.
|
|
2
|
+
description: Execute bash queries. Should be used to grep for file contents, list directories, explore information to answer user questions more practically. NEVER use ls -R on directories that may contain node_modules, .git, or other large dependency folders - this will exceed token limits. Use targeted ls commands instead.
|
|
3
3
|
inputs:
|
|
4
4
|
- bash_command
|
|
5
5
|
steps:
|
|
@@ -9,7 +9,7 @@ steps:
|
|
|
9
9
|
import subprocess
|
|
10
10
|
import os
|
|
11
11
|
|
|
12
|
-
cmd =
|
|
12
|
+
cmd = {{ bash_command | tojson }}
|
|
13
13
|
output = ""
|
|
14
14
|
|
|
15
15
|
process = subprocess.Popen(
|
|
@@ -20,12 +20,6 @@ steps:
|
|
|
20
20
|
)
|
|
21
21
|
stdout, stderr = process.communicate()
|
|
22
22
|
|
|
23
|
-
# Only show debug output if NPCSH_DEBUG is set
|
|
24
|
-
if os.environ.get("NPCSH_DEBUG") == "1":
|
|
25
|
-
import sys
|
|
26
|
-
print(f"[sh] cmd: {cmd}", file=sys.stderr)
|
|
27
|
-
print(f"[sh] stdout: {stdout.decode('utf-8', errors='ignore')[:200]}", file=sys.stderr)
|
|
28
|
-
|
|
29
23
|
if stderr:
|
|
30
24
|
output = f"Error: {stderr.decode('utf-8')}"
|
|
31
25
|
else:
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
jinx_name: "shh"
|
|
2
|
+
description: "Enable silent mode - only shows warnings and errors"
|
|
3
|
+
inputs: []
|
|
4
|
+
steps:
|
|
5
|
+
- name: "set_silent"
|
|
6
|
+
engine: "python"
|
|
7
|
+
code: |
|
|
8
|
+
state = context.get('state')
|
|
9
|
+
output_messages = context.get('messages', [])
|
|
10
|
+
|
|
11
|
+
if state:
|
|
12
|
+
result = state.set_log_level("silent")
|
|
13
|
+
context['output'] = result
|
|
14
|
+
else:
|
|
15
|
+
context['output'] = "Error: state not available"
|
|
16
|
+
|
|
17
|
+
context['messages'] = output_messages
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
name: sibiji
|
|
2
|
+
ascii_art: |
|
|
3
|
+
🕸️🕸️🕸️🕸️🕸️🕸️🕸️🕸️🕸️🕸️🕸️🕸️🕸️🕸️🕸️🕸️🕸️🕸️🕸️🕸️🕸️🕸️🕸️🕸️🕸️🕸️🕸️🕸️🕸️🕸️🕸️🕸️🕸️🕸️🕸️🕸️🕸️🕸️🕸️
|
|
4
|
+
██████ ██ ██████ ██ ██ ██████ ██
|
|
5
|
+
██ ██ ██ ██ ██ ██ ██ ██
|
|
6
|
+
█████ ██ ██████ ██ 🕷️ ██ ██ ██
|
|
7
|
+
██ ██ ██ ██ ██ ██ 🕷️ ██ ██
|
|
8
|
+
██████ ██ ██████ ██ ██ ██████ ██
|
|
9
|
+
🕸️🕸️🕸️🕸️🕸️🕸️🕸️🕸️🕸️🕸️🕸️🕸️🕸️🕸️🕸️🕸️🕸️🕸️🕸️🕸️🕸️🕸️🕸️🕸️🕸️🕸️🕸️🕸️🕸️🕸️🕸️🕸️🕸️🕸️🕸️🕸️🕸️🕸️🕸️
|
|
10
|
+
colors:
|
|
11
|
+
top: "148,0,211"
|
|
12
|
+
bottom: "75,0,130"
|
|
13
|
+
primary_directive: |
|
|
14
|
+
You are sibiji, the orchestrator and general manager of the NPC team.
|
|
15
|
+
Your role is to delegate tasks to appropriate specialist agents based on their expertise.
|
|
16
|
+
|
|
17
|
+
When delegating, match the task to the agent whose primary_directive best fits.
|
|
18
|
+
You have access to the delegate tool to pass tasks to other agents.
|
|
19
|
+
jinxs:
|
|
20
|
+
- lib/orchestration/delegate
|
|
21
|
+
- lib/orchestration/convene
|
|
22
|
+
- lib/core/sh
|
|
23
|
+
- lib/core/python
|
|
24
|
+
- lib/core/search
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
jinx_name: switch
|
|
2
|
+
description: Get or set a switch in the .ctx file
|
|
3
|
+
inputs:
|
|
4
|
+
- name: "" # Switch name
|
|
5
|
+
- value: null # Value to set (omit to get current value)
|
|
6
|
+
- scope: "workspace" # "workspace" or "global"
|
|
7
|
+
|
|
8
|
+
steps:
|
|
9
|
+
- name: manage_switch
|
|
10
|
+
engine: python
|
|
11
|
+
code: |
|
|
12
|
+
import os
|
|
13
|
+
import yaml
|
|
14
|
+
from pathlib import Path
|
|
15
|
+
|
|
16
|
+
switch_name = context.get('name', '')
|
|
17
|
+
switch_value = context.get('value')
|
|
18
|
+
scope = context.get('scope', 'workspace')
|
|
19
|
+
messages = context.get('messages', [])
|
|
20
|
+
|
|
21
|
+
if not switch_name:
|
|
22
|
+
context['output'] = "Usage: /switch <name> [value] [--scope workspace|global]"
|
|
23
|
+
context['messages'] = messages
|
|
24
|
+
exit()
|
|
25
|
+
|
|
26
|
+
# Determine .ctx path based on scope
|
|
27
|
+
if scope == "global":
|
|
28
|
+
ctx_path = Path.home() / ".npcsh" / "npc_team" / "npcsh.ctx"
|
|
29
|
+
else:
|
|
30
|
+
cwd = os.getcwd()
|
|
31
|
+
# Look for npc_team/*.ctx in workspace
|
|
32
|
+
npc_team_dir = Path(cwd) / "npc_team"
|
|
33
|
+
ctx_files = list(npc_team_dir.glob("*.ctx")) if npc_team_dir.exists() else []
|
|
34
|
+
ctx_path = ctx_files[0] if ctx_files else npc_team_dir / "team.ctx"
|
|
35
|
+
|
|
36
|
+
# Load existing ctx
|
|
37
|
+
ctx_data = {}
|
|
38
|
+
if ctx_path.exists():
|
|
39
|
+
try:
|
|
40
|
+
with open(ctx_path) as f:
|
|
41
|
+
ctx_data = yaml.safe_load(f) or {}
|
|
42
|
+
except:
|
|
43
|
+
ctx_data = {}
|
|
44
|
+
|
|
45
|
+
# Ensure switches dict exists
|
|
46
|
+
if 'switches' not in ctx_data:
|
|
47
|
+
ctx_data['switches'] = {}
|
|
48
|
+
|
|
49
|
+
if switch_value is None:
|
|
50
|
+
# Get mode
|
|
51
|
+
current = ctx_data['switches'].get(switch_name, "not set")
|
|
52
|
+
context['output'] = f"{switch_name} ({scope}): {current}"
|
|
53
|
+
else:
|
|
54
|
+
# Set mode
|
|
55
|
+
ctx_path.parent.mkdir(parents=True, exist_ok=True)
|
|
56
|
+
ctx_data['switches'][switch_name] = switch_value
|
|
57
|
+
with open(ctx_path, 'w') as f:
|
|
58
|
+
yaml.dump(ctx_data, f, default_flow_style=False)
|
|
59
|
+
context['output'] = f"Set {switch_name} = {switch_value} ({scope})"
|
|
60
|
+
|
|
61
|
+
context['messages'] = messages
|
|
62
|
+
context['switch_value'] = ctx_data['switches'].get(switch_name)
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
jinx_name: switches
|
|
2
|
+
description: List all switches from .ctx files
|
|
3
|
+
inputs:
|
|
4
|
+
- scope: "all" # "workspace", "global", or "all"
|
|
5
|
+
|
|
6
|
+
steps:
|
|
7
|
+
- name: list_switches
|
|
8
|
+
engine: python
|
|
9
|
+
code: |
|
|
10
|
+
import os
|
|
11
|
+
import yaml
|
|
12
|
+
from pathlib import Path
|
|
13
|
+
|
|
14
|
+
scope = context.get('scope', 'all')
|
|
15
|
+
messages = context.get('messages', [])
|
|
16
|
+
|
|
17
|
+
output_lines = []
|
|
18
|
+
|
|
19
|
+
# Global switches
|
|
20
|
+
if scope in ["global", "all"]:
|
|
21
|
+
global_ctx = Path.home() / ".npcsh" / "npc_team" / "npcsh.ctx"
|
|
22
|
+
if global_ctx.exists():
|
|
23
|
+
try:
|
|
24
|
+
with open(global_ctx) as f:
|
|
25
|
+
data = yaml.safe_load(f) or {}
|
|
26
|
+
switches = data.get('switches', {})
|
|
27
|
+
if switches:
|
|
28
|
+
output_lines.append("Global switches:")
|
|
29
|
+
for k, v in switches.items():
|
|
30
|
+
output_lines.append(f" {k}: {v}")
|
|
31
|
+
else:
|
|
32
|
+
output_lines.append("Global switches: (none)")
|
|
33
|
+
except:
|
|
34
|
+
output_lines.append("Global switches: (none)")
|
|
35
|
+
else:
|
|
36
|
+
output_lines.append("Global switches: (none)")
|
|
37
|
+
|
|
38
|
+
# Workspace switches
|
|
39
|
+
if scope in ["workspace", "all"]:
|
|
40
|
+
cwd = os.getcwd()
|
|
41
|
+
npc_team_dir = Path(cwd) / "npc_team"
|
|
42
|
+
ctx_files = list(npc_team_dir.glob("*.ctx")) if npc_team_dir.exists() else []
|
|
43
|
+
|
|
44
|
+
if ctx_files:
|
|
45
|
+
try:
|
|
46
|
+
with open(ctx_files[0]) as f:
|
|
47
|
+
data = yaml.safe_load(f) or {}
|
|
48
|
+
switches = data.get('switches', {})
|
|
49
|
+
if switches:
|
|
50
|
+
output_lines.append(f"Workspace switches:")
|
|
51
|
+
for k, v in switches.items():
|
|
52
|
+
output_lines.append(f" {k}: {v}")
|
|
53
|
+
else:
|
|
54
|
+
output_lines.append("Workspace switches: (none)")
|
|
55
|
+
except:
|
|
56
|
+
output_lines.append("Workspace switches: (none)")
|
|
57
|
+
else:
|
|
58
|
+
output_lines.append("Workspace switches: (none)")
|
|
59
|
+
|
|
60
|
+
context['output'] = "\n".join(output_lines) if output_lines else "No switches configured."
|
|
61
|
+
context['messages'] = messages
|