@elisym/cli 0.1.0
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.
- package/README.md +111 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +2134 -0
- package/dist/index.js.map +1 -0
- package/package.json +67 -0
- package/skills-examples/general-assistant/SKILL.md +23 -0
- package/skills-examples/github-repo/SKILL.md +25 -0
- package/skills-examples/github-repo/scripts/github_repo.py +55 -0
- package/skills-examples/requirements.txt +8 -0
- package/skills-examples/site-status/SKILL.md +25 -0
- package/skills-examples/site-status/scripts/site_status.py +75 -0
- package/skills-examples/stock-price/SKILL.md +25 -0
- package/skills-examples/stock-price/scripts/stock_price.py +49 -0
- package/skills-examples/trending/SKILL.md +28 -0
- package/skills-examples/trending/scripts/trending.py +105 -0
- package/skills-examples/whois-lookup/SKILL.md +26 -0
- package/skills-examples/whois-lookup/scripts/whois_lookup.py +65 -0
- package/skills-examples/youtube-summary/SKILL.md +44 -0
- package/skills-examples/youtube-summary/scripts/summarize.py +312 -0
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Check website status, response time, and SSL."""
|
|
3
|
+
|
|
4
|
+
import json
|
|
5
|
+
import ssl
|
|
6
|
+
import sys
|
|
7
|
+
import time
|
|
8
|
+
from urllib.parse import urlparse
|
|
9
|
+
|
|
10
|
+
import requests
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def check_ssl(hostname):
|
|
14
|
+
"""Check if SSL certificate is valid."""
|
|
15
|
+
try:
|
|
16
|
+
ctx = ssl.create_default_context()
|
|
17
|
+
with ctx.wrap_socket(ssl.socket(), server_hostname=hostname) as s:
|
|
18
|
+
s.settimeout(5)
|
|
19
|
+
s.connect((hostname, 443))
|
|
20
|
+
return True
|
|
21
|
+
except Exception:
|
|
22
|
+
return False
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def main():
|
|
26
|
+
if len(sys.argv) < 2:
|
|
27
|
+
print(json.dumps({"error": "Usage: site_status.py <url>"}))
|
|
28
|
+
sys.exit(1)
|
|
29
|
+
|
|
30
|
+
url = sys.argv[1]
|
|
31
|
+
if not url.startswith(("http://", "https://")):
|
|
32
|
+
url = "https://" + url
|
|
33
|
+
|
|
34
|
+
parsed = urlparse(url)
|
|
35
|
+
|
|
36
|
+
try:
|
|
37
|
+
start = time.time()
|
|
38
|
+
resp = requests.get(url, timeout=15, allow_redirects=True)
|
|
39
|
+
elapsed_ms = round((time.time() - start) * 1000)
|
|
40
|
+
except requests.exceptions.ConnectionError:
|
|
41
|
+
print(json.dumps({"url": url, "error": "Connection failed", "status": "down"}))
|
|
42
|
+
sys.exit(1)
|
|
43
|
+
except requests.exceptions.Timeout:
|
|
44
|
+
print(json.dumps({"url": url, "error": "Timeout", "status": "timeout"}))
|
|
45
|
+
sys.exit(1)
|
|
46
|
+
except Exception as e:
|
|
47
|
+
print(json.dumps({"url": url, "error": str(e)}))
|
|
48
|
+
sys.exit(1)
|
|
49
|
+
|
|
50
|
+
redirect_chain = []
|
|
51
|
+
for r in resp.history:
|
|
52
|
+
redirect_chain.append({
|
|
53
|
+
"url": r.url,
|
|
54
|
+
"status_code": r.status_code,
|
|
55
|
+
})
|
|
56
|
+
|
|
57
|
+
ssl_valid = None
|
|
58
|
+
if parsed.scheme == "https" or resp.url.startswith("https://"):
|
|
59
|
+
final_host = urlparse(resp.url).hostname
|
|
60
|
+
ssl_valid = check_ssl(final_host)
|
|
61
|
+
|
|
62
|
+
result = {
|
|
63
|
+
"url": resp.url,
|
|
64
|
+
"status_code": resp.status_code,
|
|
65
|
+
"response_time_ms": elapsed_ms,
|
|
66
|
+
"redirect_chain": redirect_chain if redirect_chain else None,
|
|
67
|
+
"ssl_valid": ssl_valid,
|
|
68
|
+
"server": resp.headers.get("Server"),
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
print(json.dumps(result, indent=2))
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
if __name__ == "__main__":
|
|
75
|
+
main()
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: stock-price
|
|
3
|
+
description: Stock quote agent. Send a ticker (e.g. AAPL) - get price, daily change, volume, and 52-week range
|
|
4
|
+
capabilities:
|
|
5
|
+
- stock-price
|
|
6
|
+
- stocks
|
|
7
|
+
tools:
|
|
8
|
+
- name: get_quote
|
|
9
|
+
description: Get current stock quote for a ticker symbol. Returns JSON with ticker, name, price, change, change_percent, volume, market_cap, 52w_high, 52w_low.
|
|
10
|
+
command: ['python3', 'scripts/stock_price.py']
|
|
11
|
+
parameters:
|
|
12
|
+
- name: ticker
|
|
13
|
+
description: Stock ticker symbol (e.g. AAPL, GOOGL, TSLA)
|
|
14
|
+
required: true
|
|
15
|
+
---
|
|
16
|
+
|
|
17
|
+
You are a stock price agent.
|
|
18
|
+
|
|
19
|
+
When asked about stock prices:
|
|
20
|
+
|
|
21
|
+
1. Use the get_quote tool with the ticker symbol
|
|
22
|
+
2. Present price, daily change, volume, and market cap
|
|
23
|
+
3. Include 52-week high/low for context
|
|
24
|
+
|
|
25
|
+
IMPORTANT: Output plain text only. No markdown formatting (no #, \*\*, -, ```, etc.). Use simple line breaks and dashes for structure.
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Fetch stock quote using yfinance."""
|
|
3
|
+
|
|
4
|
+
import json
|
|
5
|
+
import sys
|
|
6
|
+
|
|
7
|
+
import yfinance as yf
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def main():
|
|
11
|
+
if len(sys.argv) < 2:
|
|
12
|
+
print(json.dumps({"error": "Usage: stock_price.py <ticker>"}))
|
|
13
|
+
sys.exit(1)
|
|
14
|
+
|
|
15
|
+
ticker = sys.argv[1].upper()
|
|
16
|
+
|
|
17
|
+
try:
|
|
18
|
+
stock = yf.Ticker(ticker)
|
|
19
|
+
info = stock.info
|
|
20
|
+
except Exception as e:
|
|
21
|
+
print(json.dumps({"error": f"yfinance failed: {e}"}))
|
|
22
|
+
sys.exit(1)
|
|
23
|
+
|
|
24
|
+
if not info or info.get("regularMarketPrice") is None:
|
|
25
|
+
print(json.dumps({"error": f"No data found for ticker '{ticker}'"}))
|
|
26
|
+
sys.exit(1)
|
|
27
|
+
|
|
28
|
+
price = info.get("regularMarketPrice") or info.get("currentPrice")
|
|
29
|
+
prev_close = info.get("regularMarketPreviousClose")
|
|
30
|
+
change = round(price - prev_close, 2) if price and prev_close else None
|
|
31
|
+
change_pct = round((change / prev_close) * 100, 2) if change and prev_close else None
|
|
32
|
+
|
|
33
|
+
result = {
|
|
34
|
+
"ticker": ticker,
|
|
35
|
+
"name": info.get("shortName") or info.get("longName"),
|
|
36
|
+
"price": price,
|
|
37
|
+
"change": change,
|
|
38
|
+
"change_percent": change_pct,
|
|
39
|
+
"volume": info.get("regularMarketVolume"),
|
|
40
|
+
"market_cap": info.get("marketCap"),
|
|
41
|
+
"52w_high": info.get("fiftyTwoWeekHigh"),
|
|
42
|
+
"52w_low": info.get("fiftyTwoWeekLow"),
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
print(json.dumps(result, indent=2))
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
if __name__ == "__main__":
|
|
49
|
+
main()
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: trending
|
|
3
|
+
description: Trending agent. Ask for GitHub or Reddit trends - get a ranked list of top repos or posts
|
|
4
|
+
capabilities:
|
|
5
|
+
- trending
|
|
6
|
+
- popular
|
|
7
|
+
tools:
|
|
8
|
+
- name: get_trending
|
|
9
|
+
description: Get trending items from GitHub or Reddit. Returns JSON array of [{rank, title, url, description, score/stars}, ...]. No API key needed.
|
|
10
|
+
command: ['python3', 'scripts/trending.py']
|
|
11
|
+
parameters:
|
|
12
|
+
- name: source
|
|
13
|
+
description: "Source to fetch from: 'github' or 'reddit'"
|
|
14
|
+
required: true
|
|
15
|
+
- name: category
|
|
16
|
+
description: 'For GitHub: language filter (e.g. python, rust). For Reddit: subreddit name (e.g. technology, programming). Default: all/popular'
|
|
17
|
+
required: false
|
|
18
|
+
---
|
|
19
|
+
|
|
20
|
+
You are a trending content agent.
|
|
21
|
+
|
|
22
|
+
When asked about what's trending:
|
|
23
|
+
|
|
24
|
+
1. Use the get_trending tool with the appropriate source
|
|
25
|
+
2. Present the top items with rank, title, description, and score
|
|
26
|
+
3. You can query both GitHub and Reddit if the user wants a broad overview
|
|
27
|
+
|
|
28
|
+
IMPORTANT: Output plain text only. No markdown formatting (no #, \*\*, -, ```, etc.). Use simple line breaks and dashes for structure.
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Fetch trending repos from GitHub or posts from Reddit."""
|
|
3
|
+
|
|
4
|
+
import json
|
|
5
|
+
import sys
|
|
6
|
+
|
|
7
|
+
import requests
|
|
8
|
+
from bs4 import BeautifulSoup
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def github_trending(language=None):
|
|
12
|
+
"""Scrape GitHub trending page."""
|
|
13
|
+
url = "https://github.com/trending"
|
|
14
|
+
if language:
|
|
15
|
+
url += f"/{language}"
|
|
16
|
+
|
|
17
|
+
headers = {"User-Agent": "Mozilla/5.0 (compatible; elisym-agent/1.0)"}
|
|
18
|
+
|
|
19
|
+
try:
|
|
20
|
+
resp = requests.get(url, headers=headers, timeout=15)
|
|
21
|
+
resp.raise_for_status()
|
|
22
|
+
except Exception as e:
|
|
23
|
+
return {"error": f"Failed to fetch GitHub trending: {e}"}
|
|
24
|
+
|
|
25
|
+
soup = BeautifulSoup(resp.text, "html.parser")
|
|
26
|
+
articles = soup.select("article.Box-row")
|
|
27
|
+
|
|
28
|
+
results = []
|
|
29
|
+
for i, article in enumerate(articles[:25], 1):
|
|
30
|
+
h2 = article.select_one("h2 a")
|
|
31
|
+
if not h2:
|
|
32
|
+
continue
|
|
33
|
+
repo_path = h2.get("href", "").strip("/")
|
|
34
|
+
name = repo_path
|
|
35
|
+
|
|
36
|
+
desc_p = article.select_one("p")
|
|
37
|
+
description = desc_p.get_text(strip=True) if desc_p else None
|
|
38
|
+
|
|
39
|
+
stars_el = article.select_one("a[href$='/stargazers']")
|
|
40
|
+
stars = stars_el.get_text(strip=True).replace(",", "") if stars_el else None
|
|
41
|
+
|
|
42
|
+
lang_el = article.select_one("[itemprop='programmingLanguage']")
|
|
43
|
+
lang = lang_el.get_text(strip=True) if lang_el else None
|
|
44
|
+
|
|
45
|
+
results.append({
|
|
46
|
+
"rank": i,
|
|
47
|
+
"title": name,
|
|
48
|
+
"url": f"https://github.com/{repo_path}",
|
|
49
|
+
"description": description,
|
|
50
|
+
"stars": stars,
|
|
51
|
+
"language": lang,
|
|
52
|
+
})
|
|
53
|
+
|
|
54
|
+
return results
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def reddit_trending(subreddit=None):
|
|
58
|
+
"""Fetch hot posts from Reddit."""
|
|
59
|
+
sub = subreddit or "popular"
|
|
60
|
+
url = f"https://old.reddit.com/r/{sub}/.json?limit=25"
|
|
61
|
+
headers = {"User-Agent": "elisym-agent/1.0"}
|
|
62
|
+
|
|
63
|
+
try:
|
|
64
|
+
resp = requests.get(url, headers=headers, timeout=15)
|
|
65
|
+
resp.raise_for_status()
|
|
66
|
+
data = resp.json()
|
|
67
|
+
except Exception as e:
|
|
68
|
+
return {"error": f"Failed to fetch Reddit: {e}"}
|
|
69
|
+
|
|
70
|
+
results = []
|
|
71
|
+
children = data.get("data", {}).get("children", [])
|
|
72
|
+
for i, child in enumerate(children, 1):
|
|
73
|
+
post = child.get("data", {})
|
|
74
|
+
results.append({
|
|
75
|
+
"rank": i,
|
|
76
|
+
"title": post.get("title"),
|
|
77
|
+
"url": f"https://reddit.com{post.get('permalink', '')}",
|
|
78
|
+
"description": post.get("selftext", "")[:200] or None,
|
|
79
|
+
"score": post.get("score"),
|
|
80
|
+
"subreddit": post.get("subreddit"),
|
|
81
|
+
})
|
|
82
|
+
|
|
83
|
+
return results
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
def main():
|
|
87
|
+
if len(sys.argv) < 2:
|
|
88
|
+
print(json.dumps({"error": "Usage: trending.py <github|reddit> [category]"}))
|
|
89
|
+
sys.exit(1)
|
|
90
|
+
|
|
91
|
+
source = sys.argv[1].lower()
|
|
92
|
+
category = sys.argv[2] if len(sys.argv) > 2 else None
|
|
93
|
+
|
|
94
|
+
if source == "github":
|
|
95
|
+
result = github_trending(category)
|
|
96
|
+
elif source == "reddit":
|
|
97
|
+
result = reddit_trending(category)
|
|
98
|
+
else:
|
|
99
|
+
result = {"error": f"Unknown source: {source}. Use 'github' or 'reddit'"}
|
|
100
|
+
|
|
101
|
+
print(json.dumps(result, indent=2))
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
if __name__ == "__main__":
|
|
105
|
+
main()
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: whois-lookup
|
|
3
|
+
description: WHOIS agent. Send a domain - get registrar, dates, name servers, and status
|
|
4
|
+
capabilities:
|
|
5
|
+
- whois-lookup
|
|
6
|
+
- domain-info
|
|
7
|
+
tools:
|
|
8
|
+
- name: whois_domain
|
|
9
|
+
description: Look up WHOIS registration info for a domain. Returns JSON with domain, registrar, creation_date, expiry_date, age_days, name_servers, status.
|
|
10
|
+
command: ['python3', 'scripts/whois_lookup.py']
|
|
11
|
+
parameters:
|
|
12
|
+
- name: domain
|
|
13
|
+
description: Domain name to look up (e.g. example.com)
|
|
14
|
+
required: true
|
|
15
|
+
---
|
|
16
|
+
|
|
17
|
+
You are a domain WHOIS lookup agent.
|
|
18
|
+
|
|
19
|
+
When given a request about a domain:
|
|
20
|
+
|
|
21
|
+
1. Use the whois_domain tool to fetch registration information
|
|
22
|
+
2. Present the results clearly to the user
|
|
23
|
+
|
|
24
|
+
IMPORTANT: Output plain text only. No markdown formatting (no #, \*\*, -, ```, etc.). Use simple line breaks and dashes for structure.
|
|
25
|
+
|
|
26
|
+
Include: registrar, registration and expiry dates, domain age, name servers, and current status.
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""WHOIS lookup for a domain."""
|
|
3
|
+
|
|
4
|
+
import json
|
|
5
|
+
import sys
|
|
6
|
+
from datetime import datetime, timezone
|
|
7
|
+
|
|
8
|
+
import whois
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def main():
|
|
12
|
+
if len(sys.argv) < 2:
|
|
13
|
+
print(json.dumps({"error": "Usage: whois_lookup.py <domain>"}))
|
|
14
|
+
sys.exit(1)
|
|
15
|
+
|
|
16
|
+
domain = sys.argv[1]
|
|
17
|
+
|
|
18
|
+
try:
|
|
19
|
+
w = whois.whois(domain)
|
|
20
|
+
except Exception as e:
|
|
21
|
+
print(json.dumps({"error": f"WHOIS lookup failed: {e}"}))
|
|
22
|
+
sys.exit(1)
|
|
23
|
+
|
|
24
|
+
creation = w.creation_date
|
|
25
|
+
if isinstance(creation, list):
|
|
26
|
+
creation = creation[0]
|
|
27
|
+
|
|
28
|
+
expiry = w.expiration_date
|
|
29
|
+
if isinstance(expiry, list):
|
|
30
|
+
expiry = expiry[0]
|
|
31
|
+
|
|
32
|
+
age_days = None
|
|
33
|
+
if creation:
|
|
34
|
+
now = datetime.now(timezone.utc)
|
|
35
|
+
if creation.tzinfo is None:
|
|
36
|
+
creation = creation.replace(tzinfo=timezone.utc)
|
|
37
|
+
age_days = (now - creation).days
|
|
38
|
+
|
|
39
|
+
name_servers = w.name_servers
|
|
40
|
+
if name_servers and isinstance(name_servers, (list, set)):
|
|
41
|
+
name_servers = sorted(set(ns.lower() for ns in name_servers))
|
|
42
|
+
|
|
43
|
+
status = w.status
|
|
44
|
+
if isinstance(status, list):
|
|
45
|
+
status = status
|
|
46
|
+
elif status:
|
|
47
|
+
status = [status]
|
|
48
|
+
else:
|
|
49
|
+
status = []
|
|
50
|
+
|
|
51
|
+
result = {
|
|
52
|
+
"domain": w.domain_name if w.domain_name else domain,
|
|
53
|
+
"registrar": w.registrar,
|
|
54
|
+
"creation_date": str(creation) if creation else None,
|
|
55
|
+
"expiry_date": str(expiry) if expiry else None,
|
|
56
|
+
"age_days": age_days,
|
|
57
|
+
"name_servers": name_servers,
|
|
58
|
+
"status": status,
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
print(json.dumps(result, indent=2, default=str))
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
if __name__ == "__main__":
|
|
65
|
+
main()
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: youtube-summary
|
|
3
|
+
description: YouTube summary agent. Send a link - get overview, key points, and takeaways
|
|
4
|
+
capabilities:
|
|
5
|
+
- youtube-summary
|
|
6
|
+
- video-analysis
|
|
7
|
+
max_tool_rounds: 15
|
|
8
|
+
tools:
|
|
9
|
+
- name: fetch_transcript
|
|
10
|
+
description: Fetch transcript from a YouTube video. Returns JSON with title, channel, duration_min, language, total_chunks, chunk (current chunk index), and transcript (text of chunk 0). If total_chunks > 1, use read_chunk to get remaining chunks.
|
|
11
|
+
command: ['python3', 'scripts/summarize.py', '--lang', 'auto']
|
|
12
|
+
parameters:
|
|
13
|
+
- name: url
|
|
14
|
+
description: YouTube video URL
|
|
15
|
+
required: true
|
|
16
|
+
- name: read_chunk
|
|
17
|
+
description: Read a specific chunk of a previously fetched transcript. Use this after fetch_transcript when total_chunks > 1. Returns JSON with title, chunk, total_chunks, and transcript text for that chunk.
|
|
18
|
+
command: ['python3', 'scripts/summarize.py']
|
|
19
|
+
parameters:
|
|
20
|
+
- name: url
|
|
21
|
+
description: Same YouTube URL used in fetch_transcript
|
|
22
|
+
required: true
|
|
23
|
+
- name: chunk
|
|
24
|
+
description: Chunk index to read (0-indexed). Start from 1 since fetch_transcript already returns chunk 0.
|
|
25
|
+
required: true
|
|
26
|
+
---
|
|
27
|
+
|
|
28
|
+
You are a YouTube video summarizer.
|
|
29
|
+
|
|
30
|
+
When given a request:
|
|
31
|
+
|
|
32
|
+
1. Call fetch_transcript with the video URL
|
|
33
|
+
2. Check total_chunks in the response:
|
|
34
|
+
- If total_chunks is 1: you have the full transcript, summarize it
|
|
35
|
+
- If total_chunks > 1: you already have chunk 0. Call read_chunk for chunks 1, 2, ... up to total_chunks-1, one at a time. After reading each chunk, note its key points. After all chunks are read, write a combined summary.
|
|
36
|
+
3. Write the summary in the language the user used. If only a URL was sent, use the video's language.
|
|
37
|
+
|
|
38
|
+
IMPORTANT: Output plain text only. No markdown formatting (no #, \*\*, -, ```, etc.). Use simple line breaks and dashes for structure.
|
|
39
|
+
|
|
40
|
+
Summary format:
|
|
41
|
+
2-3 sentence overview
|
|
42
|
+
Key points (use dashes for lists)
|
|
43
|
+
Important quotes, numbers, or facts mentioned
|
|
44
|
+
Brief conclusion / takeaway
|