@sulala/agent-os 0.1.5 → 0.1.6
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/data/agents/briefing_agent.json +12 -0
- package/data/agents/dev_agent.json +10 -0
- package/data/agents/manager_agent.json +11 -0
- package/data/agents/media_agent.json +10 -0
- package/data/agents/personal_agent.json +11 -0
- package/data/agents/research_agent.json +10 -0
- package/data/agents/social_media_agent.json +10 -0
- package/data/agents/writer_agent.json +10 -0
- package/data/skills/bluesky/SKILL.md +63 -0
- package/data/skills/bluesky/config.schema.json +17 -0
- package/data/skills/bluesky/scripts/post.sh +48 -0
- package/data/skills/bluesky/scripts/timeline.sh +37 -0
- package/data/skills/date/SKILL.md +53 -0
- package/data/skills/fetch/SKILL.md +42 -0
- package/data/skills/file-search/SKILL.md +122 -0
- package/data/skills/file-stats/SKILL.md +68 -0
- package/data/skills/git/SKILL.md +60 -0
- package/data/skills/gmail/SKILL.md +55 -0
- package/data/skills/gmail/references/send-email.md +54 -0
- package/data/skills/gmail/scripts/send_email.py +94 -0
- package/data/skills/hash/SKILL.md +56 -0
- package/data/skills/jq/SKILL.md +66 -0
- package/data/skills/markdown-to-html/SKILL.md +47 -0
- package/data/skills/memory/SKILL.md +64 -0
- package/data/skills/qr-code/SKILL.md +65 -0
- package/data/skills/rss/SKILL.md +40 -0
- package/data/skills/sulala-portal/SKILL.md +92 -0
- package/data/skills/translate/SKILL.md +52 -0
- package/data/skills/weather/SKILL.md +59 -0
- package/data/skills/web-search/SKILL.md +55 -0
- package/data/skills/web-search/config.schema.json +12 -0
- package/data/skills/web-search/scripts/search.sh +21 -0
- package/data/skills/webhook/SKILL.md +101 -0
- package/data/skills/webhook/config.schema.json +11 -0
- package/data/skills/webhook/scripts/post.sh +13 -0
- package/data/skills/youtube/SKILL.md +91 -0
- package/data/skills/youtube/config.schema.json +11 -0
- package/data/skills/youtube/package.json +8 -0
- package/data/skills/youtube/references/youtube-upload.md +65 -0
- package/data/skills/youtube/requirements.txt +3 -0
- package/data/skills/youtube/scripts/youtube_upload.js +200 -0
- package/data/skills/youtube/scripts/youtube_upload.py +125 -0
- package/data/templates/BOOT.md +11 -0
- package/data/templates/BOOTSTRAP.md +62 -0
- package/data/templates/HEARTBEAT.md +12 -0
- package/data/templates/IDENTITY.dev.md +62 -0
- package/data/templates/IDENTITY.md +60 -0
- package/data/templates/SYSTEM.dev.md +82 -0
- package/data/templates/SYSTEM.md +65 -0
- package/data/templates/TOOLS.dev.md +24 -0
- package/data/templates/TOOLS.md +47 -0
- package/data/templates/USER.dev.md +18 -0
- package/data/templates/USER.md +23 -0
- package/dist/cli.js +22 -13
- package/dist/index.js +14 -5
- package/package.json +3 -2
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
# Send email script
|
|
2
|
+
|
|
3
|
+
The Gmail skill includes a script that sends email using the Gmail API with an OAuth2 access token. Use it when you have a token (e.g. from Sulala Portal) and want to send from the command line or from automation.
|
|
4
|
+
|
|
5
|
+
## Prerequisites
|
|
6
|
+
|
|
7
|
+
- **Access token**: Obtain from Sulala Portal (`POST connections/{connection_id}/use` with your Gmail connection), or from your own OAuth flow. Token must have `https://www.googleapis.com/auth/gmail.send` scope.
|
|
8
|
+
- **Python 3.6+**: No extra pip packages; script uses only the standard library.
|
|
9
|
+
|
|
10
|
+
## Usage
|
|
11
|
+
|
|
12
|
+
From the skill directory (or with paths adjusted):
|
|
13
|
+
|
|
14
|
+
```bash
|
|
15
|
+
python3 scripts/send_email.py \
|
|
16
|
+
--token "YOUR_ACCESS_TOKEN" \
|
|
17
|
+
--to "recipient@example.com" \
|
|
18
|
+
--subject "Subject line" \
|
|
19
|
+
--body "Email body text."
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
With body from a file:
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
python3 scripts/send_email.py \
|
|
26
|
+
--token "YOUR_ACCESS_TOKEN" \
|
|
27
|
+
--to "recipient@example.com" \
|
|
28
|
+
--subject "Subject" \
|
|
29
|
+
--body-file path/to/body.txt
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
## Options
|
|
33
|
+
|
|
34
|
+
| Option | Required | Description |
|
|
35
|
+
|-------------|----------|--------------------------------------|
|
|
36
|
+
| `--token` | Yes | OAuth2 access token (Bearer). |
|
|
37
|
+
| `--to` | Yes | Recipient email address. |
|
|
38
|
+
| `--subject` | Yes | Email subject line. |
|
|
39
|
+
| `--body` | One of | Email body as a string. |
|
|
40
|
+
| `--body-file` | One of | Path to file containing body text. |
|
|
41
|
+
|
|
42
|
+
## Getting the token (Sulala Portal)
|
|
43
|
+
|
|
44
|
+
1. Call the Portal API to list connections: `GET connections` (filter for `provider === "gmail"`).
|
|
45
|
+
2. Call `POST connections/{connection_id}/use` with the Gmail connection’s `connection_id` (e.g. `conn_gmail_...`; use `connection_id` if `id` returns 404).
|
|
46
|
+
3. Use the `accessToken` from the response as `--token`.
|
|
47
|
+
|
|
48
|
+
## Troubleshooting
|
|
49
|
+
|
|
50
|
+
| Problem | Cause | Action |
|
|
51
|
+
|----------------------|--------------------------|---------------------------------|
|
|
52
|
+
| 401 Unauthorized | Token expired or invalid | Refresh or re-issue token. |
|
|
53
|
+
| Recipient required | Missing/invalid To | Ensure `--to` is a valid email. |
|
|
54
|
+
| Invalid To header | Bad To format | Use plain email, no extra text. |
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Send email via Gmail API using an OAuth access token.
|
|
3
|
+
|
|
4
|
+
Use when you have a token from Sulala Portal (connections/:id/use) or another source.
|
|
5
|
+
No local OAuth files required; pass the token on the command line.
|
|
6
|
+
|
|
7
|
+
Usage:
|
|
8
|
+
python3 send_email.py --token TOKEN --to recipient@example.com --subject "Subject" --body "Body text"
|
|
9
|
+
python3 send_email.py --token TOKEN --to recipient@example.com --subject "Subject" --body-file body.txt
|
|
10
|
+
|
|
11
|
+
Requires: Python 3.6+. No extra pip packages (uses urllib and base64 from stdlib).
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
import argparse
|
|
15
|
+
import base64
|
|
16
|
+
import json
|
|
17
|
+
import sys
|
|
18
|
+
import urllib.request
|
|
19
|
+
import urllib.error
|
|
20
|
+
|
|
21
|
+
GMAIL_SEND_URL = "https://gmail.googleapis.com/gmail/v1/users/me/messages/send"
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def base64url_encode(data: bytes) -> str:
|
|
25
|
+
"""Encode bytes to base64url (RFC 4648): no +/ padding, use -_."""
|
|
26
|
+
b64 = base64.b64encode(data).decode("ascii")
|
|
27
|
+
return b64.replace("+", "-").replace("/", "_").rstrip("=")
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def build_rfc2822(to: str, subject: str, body: str) -> str:
|
|
31
|
+
"""Build a minimal RFC 2822 message (CRLF line endings)."""
|
|
32
|
+
lines = [
|
|
33
|
+
f"To: {to}",
|
|
34
|
+
f"Subject: {subject}",
|
|
35
|
+
"Content-Type: text/plain; charset=UTF-8",
|
|
36
|
+
"",
|
|
37
|
+
body,
|
|
38
|
+
]
|
|
39
|
+
return "\r\n".join(lines)
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def send(token: str, to: str, subject: str, body: str) -> None:
|
|
43
|
+
rfc2822 = build_rfc2822(to, subject, body)
|
|
44
|
+
raw = base64url_encode(rfc2822.encode("utf-8"))
|
|
45
|
+
body_json = json.dumps({"raw": raw}).encode("utf-8")
|
|
46
|
+
|
|
47
|
+
req = urllib.request.Request(
|
|
48
|
+
GMAIL_SEND_URL,
|
|
49
|
+
data=body_json,
|
|
50
|
+
headers={
|
|
51
|
+
"Authorization": f"Bearer {token}",
|
|
52
|
+
"Content-Type": "application/json",
|
|
53
|
+
},
|
|
54
|
+
method="POST",
|
|
55
|
+
)
|
|
56
|
+
try:
|
|
57
|
+
with urllib.request.urlopen(req) as resp:
|
|
58
|
+
if resp.status != 200:
|
|
59
|
+
sys.stderr.write(f"Unexpected status: {resp.status}\n")
|
|
60
|
+
sys.exit(1)
|
|
61
|
+
print("Email sent successfully.")
|
|
62
|
+
except urllib.error.HTTPError as e:
|
|
63
|
+
sys.stderr.write(f"Gmail API error: {e.code} {e.reason}\n")
|
|
64
|
+
if e.fp:
|
|
65
|
+
body = e.fp.read().decode("utf-8", errors="replace")
|
|
66
|
+
sys.stderr.write(body)
|
|
67
|
+
sys.stderr.write("\n")
|
|
68
|
+
sys.exit(1)
|
|
69
|
+
except OSError as e:
|
|
70
|
+
sys.stderr.write(f"Request failed: {e}\n")
|
|
71
|
+
sys.exit(1)
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def main() -> None:
|
|
75
|
+
ap = argparse.ArgumentParser(description="Send email via Gmail API with an access token.")
|
|
76
|
+
ap.add_argument("--token", required=True, help="OAuth2 access token (e.g. from Sulala Portal)")
|
|
77
|
+
ap.add_argument("--to", required=True, help="Recipient email address")
|
|
78
|
+
ap.add_argument("--subject", required=True, help="Email subject")
|
|
79
|
+
group = ap.add_mutually_exclusive_group(required=True)
|
|
80
|
+
group.add_argument("--body", help="Email body (plain text)")
|
|
81
|
+
group.add_argument("--body-file", help="Path to file containing email body")
|
|
82
|
+
args = ap.parse_args()
|
|
83
|
+
|
|
84
|
+
if args.body is not None:
|
|
85
|
+
body = args.body
|
|
86
|
+
else:
|
|
87
|
+
with open(args.body_file, "r", encoding="utf-8") as f:
|
|
88
|
+
body = f.read()
|
|
89
|
+
|
|
90
|
+
send(args.token, args.to, args.subject, body)
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
if __name__ == "__main__":
|
|
94
|
+
main()
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: hash
|
|
3
|
+
description: Compute or verify file checksums (SHA-256, MD5). Use when the user asks "checksum of this file", "SHA256 of …", "verify the digest", "hash of file", "MD5 of …".
|
|
4
|
+
metadata:
|
|
5
|
+
clawdbot:
|
|
6
|
+
emoji: "🔐"
|
|
7
|
+
requires:
|
|
8
|
+
bins:
|
|
9
|
+
- sha256sum
|
|
10
|
+
- md5sum
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
# Hash / checksum
|
|
14
|
+
|
|
15
|
+
Use **sha256sum** or **md5sum** via the **exec** tool to compute or verify file digests. Run from the workspace or pass an absolute path.
|
|
16
|
+
|
|
17
|
+
## SHA-256
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
sha256sum path/to/file
|
|
21
|
+
sha256sum ./package.json
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
Output: `hexdigest path/to/file`. Use `-c` to verify a list of digests from stdin or a file.
|
|
25
|
+
|
|
26
|
+
## MD5
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
md5sum path/to/file
|
|
30
|
+
md5sum ./image.png
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
## Verify a checksum
|
|
34
|
+
|
|
35
|
+
User gives expected digest; compute and compare:
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
sha256sum -c - <<< "expected_hex path/to/file"
|
|
39
|
+
# Or: echo "expected_hex file" | sha256sum -c
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
## On macOS
|
|
43
|
+
|
|
44
|
+
macOS uses different names; use one of:
|
|
45
|
+
|
|
46
|
+
```bash
|
|
47
|
+
shasum -a 256 path/to/file
|
|
48
|
+
md5 path/to/file
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
`shasum -a 256` is equivalent to `sha256sum`. Prefer `shasum -a 256` if `sha256sum` is not available.
|
|
52
|
+
|
|
53
|
+
## Tips
|
|
54
|
+
|
|
55
|
+
- Path can be relative (to workspace) or absolute (e.g. `~/Downloads/file.zip`).
|
|
56
|
+
- For "checksum of this file" or "verify this digest", run the command and compare output to what the user provided.
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: jq
|
|
3
|
+
description: Parse, filter, and transform JSON (via jq). Use when the user wants to "parse this JSON", "extract field from this response", "filter JSON", or "get X from this API output".
|
|
4
|
+
metadata:
|
|
5
|
+
clawdbot:
|
|
6
|
+
emoji: "📋"
|
|
7
|
+
requires:
|
|
8
|
+
bins:
|
|
9
|
+
- jq
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
# JSON with jq
|
|
13
|
+
|
|
14
|
+
Use **jq** via the **exec** tool to parse, extract, and filter JSON. Pipe JSON from a file or from stdin (e.g. curl output).
|
|
15
|
+
|
|
16
|
+
## Parse and pretty-print
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
echo '{"a":1,"b":2}' | jq .
|
|
20
|
+
cat response.json | jq .
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
## Extract a field
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
echo '{"name":"Alice","age":30}' | jq -r '.name'
|
|
27
|
+
# Output: Alice
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
```bash
|
|
31
|
+
curl -s "https://api.example.com/data" | jq -r '.items[0].title'
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
## Extract multiple fields
|
|
35
|
+
|
|
36
|
+
```bash
|
|
37
|
+
echo '{"a":1,"b":2,"c":3}' | jq '{a, b}'
|
|
38
|
+
# Output: {"a":1,"b":2}
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
## Array slice and filter
|
|
42
|
+
|
|
43
|
+
```bash
|
|
44
|
+
echo '[1,2,3,4,5]' | jq '.[:3]'
|
|
45
|
+
echo '[{"x":1},{"x":2},{"x":3}]' | jq '.[] | select(.x > 1)'
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
## Keys or length
|
|
49
|
+
|
|
50
|
+
```bash
|
|
51
|
+
echo '{"a":1,"b":2}' | jq 'keys'
|
|
52
|
+
echo '[1,2,3]' | jq 'length'
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
## From a file in the workspace
|
|
56
|
+
|
|
57
|
+
```bash
|
|
58
|
+
jq -r '.config.version' ./package.json
|
|
59
|
+
jq '.data[] | {id, name}' ./data.json
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
## Tips
|
|
63
|
+
|
|
64
|
+
- Use `-r` for raw string output (no quotes).
|
|
65
|
+
- Use `jq -c` for compact one-line output.
|
|
66
|
+
- If jq is not installed, the skill cannot be used; document that `jq` is required (e.g. `apt install jq` / `brew install jq`).
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: markdown-to-html
|
|
3
|
+
description: Convert Markdown to HTML (via pandoc or a simple converter). Use when the user says "convert this markdown to HTML", "render this .md as HTML", "markdown to HTML".
|
|
4
|
+
metadata:
|
|
5
|
+
clawdbot:
|
|
6
|
+
emoji: "📝"
|
|
7
|
+
requires:
|
|
8
|
+
bins:
|
|
9
|
+
- pandoc
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
# Markdown to HTML
|
|
13
|
+
|
|
14
|
+
Use **pandoc** via the **exec** tool to convert Markdown to HTML. Input can be a file path or stdin.
|
|
15
|
+
|
|
16
|
+
## Convert a file
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
pandoc input.md -o output.html
|
|
20
|
+
pandoc input.md -f markdown -t html -o output.html
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
## Stdout (no output file)
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
pandoc input.md -f markdown -t html
|
|
27
|
+
cat input.md | pandoc -f markdown -t html
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
## Standalone HTML (with basic document shell)
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
pandoc input.md -s -o output.html
|
|
34
|
+
# -s = standalone (adds <!DOCTYPE html>, <head>, <body>)
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
## From stdin (e.g. paste or pipe)
|
|
38
|
+
|
|
39
|
+
```bash
|
|
40
|
+
echo "# Hello" | pandoc -f markdown -t html
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
## Tips
|
|
44
|
+
|
|
45
|
+
- Input path can be relative (workspace) or absolute. Output path is relative to exec cwd (workspace) unless absolute.
|
|
46
|
+
- If pandoc is not installed: `brew install pandoc` (macOS), `apt install pandoc` (Linux).
|
|
47
|
+
- For "convert this markdown to HTML", run pandoc on the file or pipe the content to pandoc and return the HTML (or write to a file in the workspace and tell the user the path).
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: memory
|
|
3
|
+
description: Store and retrieve long-term memories about the user and conversations (preferences, profile, important facts).
|
|
4
|
+
tools:
|
|
5
|
+
- id: memory_write
|
|
6
|
+
description: Store a fact in long-term memory. Pass body with agent_id (your current agent id), text (the fact in natural language), optional user_id and tags (e.g. ["profile"]).
|
|
7
|
+
method: POST
|
|
8
|
+
path: /api/memory/write
|
|
9
|
+
- id: memory_search
|
|
10
|
+
description: Search long-term memory. Pass query params q (search string), optional agent_id, user_id, limit (default 10). Use semantic=1 for meaning-based search.
|
|
11
|
+
method: GET
|
|
12
|
+
path: /api/memory/search
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
# Memory Skill
|
|
16
|
+
|
|
17
|
+
Use this skill to **remember** important facts and **recall** them later.
|
|
18
|
+
|
|
19
|
+
## When to use this skill
|
|
20
|
+
|
|
21
|
+
**You MUST call this skill when:**
|
|
22
|
+
|
|
23
|
+
- The user says anything like:
|
|
24
|
+
- "Remember that I …" / "Can you remember that …" / "Please save this about me …"
|
|
25
|
+
- "My [X] is …" (e.g. "My DOB is 25 May 1997", "My name is …", "I live in …")
|
|
26
|
+
- The user asks:
|
|
27
|
+
- "What do I love?" / "Where do I live?" / "What do you remember about me?" / "What did I tell you about X?"
|
|
28
|
+
|
|
29
|
+
**Flow:**
|
|
30
|
+
|
|
31
|
+
- For **new information to store**: call **memory_write** (POST) with a clear `text` fact.
|
|
32
|
+
- For **recall**: call **memory_search** (GET) with `q` set to relevant keywords, then answer from the results.
|
|
33
|
+
|
|
34
|
+
## Write memory (store a fact)
|
|
35
|
+
|
|
36
|
+
- **Method**: `POST`
|
|
37
|
+
- **Path**: `/api/memory/write`
|
|
38
|
+
- **Body (JSON)**:
|
|
39
|
+
- `agent_id` (string, required) — your current agent id (e.g. from the run context)
|
|
40
|
+
- `text` (string, required) — the fact in natural language (e.g. "User's date of birth is 25 May 1997")
|
|
41
|
+
- `user_id` (string, optional)
|
|
42
|
+
- `tags` (array of strings, optional) — e.g. `["profile"]`, `["preference"]`
|
|
43
|
+
|
|
44
|
+
**Example — user says "save my dob is 25 may 1997":**
|
|
45
|
+
|
|
46
|
+
```json
|
|
47
|
+
{
|
|
48
|
+
"body": {
|
|
49
|
+
"agent_id": "personal_agent",
|
|
50
|
+
"text": "User's date of birth is 25 May 1997.",
|
|
51
|
+
"tags": ["profile"]
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
Use the correct `agent_id` for the current agent. Write a clear, standalone sentence in `text`.
|
|
57
|
+
|
|
58
|
+
## Search memory
|
|
59
|
+
|
|
60
|
+
- **Method**: `GET`
|
|
61
|
+
- **Path**: `/api/memory/search`
|
|
62
|
+
- **Query**: `q` (search string), optional `agent_id`, `user_id`, `limit` (default 10). Add `semantic=1` for similarity search when embeddings are available.
|
|
63
|
+
|
|
64
|
+
The server base URL is configured at runtime (e.g. set `AGENT_OS_SKILL_BASE_URL` to `http://127.0.0.1:3010` when running locally).
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: qr-code
|
|
3
|
+
description: Generate a QR code from URL or text (via qrencode or API). Use when the user says "generate a QR code for this URL", "QR code for …", "make a QR code".
|
|
4
|
+
metadata:
|
|
5
|
+
clawdbot:
|
|
6
|
+
emoji: "📱"
|
|
7
|
+
requires:
|
|
8
|
+
bins:
|
|
9
|
+
- qrencode
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
# QR code
|
|
13
|
+
|
|
14
|
+
Generate a QR code from a URL or text. Use **qrencode** via the **exec** tool, or a public API as fallback.
|
|
15
|
+
|
|
16
|
+
## qrencode (terminal / PNG)
|
|
17
|
+
|
|
18
|
+
**Output as PNG file:**
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
qrencode -o qr.png "https://example.com"
|
|
22
|
+
qrencode -o ~/Downloads/qr.png "https://example.com"
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
**Output to stdout (ASCII art in terminal):**
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
qrencode -t ANSIUTF8 "https://example.com"
|
|
29
|
+
qrencode -t UTF8 "Hello world"
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
**Options:**
|
|
33
|
+
|
|
34
|
+
- `-o file` — write PNG to file (path can be in workspace or e.g. `~/Downloads/qr.png`)
|
|
35
|
+
- `-t ANSIUTF8` or `-t UTF8` — print QR as blocks in the terminal (no file)
|
|
36
|
+
- `-s 10` — module size (pixels per module) for PNG
|
|
37
|
+
|
|
38
|
+
## If qrencode is not installed
|
|
39
|
+
|
|
40
|
+
**macOS:** `brew install qrencode`
|
|
41
|
+
**Linux:** `apt install qrencode` or `yum install qrencode`
|
|
42
|
+
|
|
43
|
+
**Fallback (curl to API, no local binary):**
|
|
44
|
+
|
|
45
|
+
```bash
|
|
46
|
+
# Example: API that returns PNG (many exist). Use -o with a path the user can find.
|
|
47
|
+
curl -s "https://api.qrserver.com/v1/create-qr-code/?size=200x200&data=URL_ENCODED_TEXT" -o ~/Downloads/qr.png
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
URL-encode the `data` parameter (e.g. `https%3A%2F%2Fsulala.ai` for `https://sulala.ai`).
|
|
51
|
+
|
|
52
|
+
## Where the file is saved
|
|
53
|
+
|
|
54
|
+
Exec runs with **cwd = agent workspace**: `~/.agent-os/workspaces/<agent_id>/`. So:
|
|
55
|
+
|
|
56
|
+
- **Relative path** (e.g. `-o sulala_ai.png`) → file is at `~/.agent-os/workspaces/<agent_id>/sulala_ai.png`. The user may not know this path.
|
|
57
|
+
- **Absolute path** (e.g. `-o ~/Downloads/sulala_ai.png`) → file is in Downloads and easy to find.
|
|
58
|
+
|
|
59
|
+
**Prefer saving to a user-visible location** when the user will want to open the file: use `-o ~/Downloads/filename.png` (or tell the user the full workspace path: `~/.agent-os/workspaces/<agent_id>/filename.png`).
|
|
60
|
+
|
|
61
|
+
## Tips
|
|
62
|
+
|
|
63
|
+
- For "QR code for this URL", use the URL as the only argument (in quotes). For the API, URL-encode it in the `data=` parameter.
|
|
64
|
+
- Prefer `-o ~/Downloads/qr.png` (or similar) so the user can find the file; if you use a relative path, tell them the full path: `~/.agent-os/workspaces/<agent_id>/filename.png`.
|
|
65
|
+
- For "show me a QR code" without saving, use qrencode `-t ANSIUTF8` (terminal art), or generate PNG to `~/Downloads` and give the path.
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: rss
|
|
3
|
+
description: Fetch RSS or Atom feed content from a URL. Use when the user asks "latest from this blog", "what's new on …", "recent posts from [URL]", "RSS feed", "feed from …".
|
|
4
|
+
metadata:
|
|
5
|
+
clawdbot:
|
|
6
|
+
emoji: "📰"
|
|
7
|
+
requires:
|
|
8
|
+
bins:
|
|
9
|
+
- curl
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
# RSS / Atom feed
|
|
13
|
+
|
|
14
|
+
Fetch a feed URL via **curl**; the response is XML (RSS or Atom). Use the **exec** tool. Optionally trim output with `head -c` so the agent gets a manageable chunk to summarize.
|
|
15
|
+
|
|
16
|
+
## Fetch feed (raw XML)
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
curl -sL "https://example.com/feed.xml"
|
|
20
|
+
curl -sL "https://blog.example.com/rss"
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
## Limit size (feeds can be large)
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
curl -sL "https://example.com/feed.xml" | head -c 50000
|
|
27
|
+
# First 50 KB is often enough for recent items.
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
## With a user-agent (some feeds require it)
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
curl -sL -A "Mozilla/5.0 (compatible; Agent/1.0)" "https://example.com/feed.xml" | head -c 50000
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
## Tips
|
|
37
|
+
|
|
38
|
+
- Feed URLs often end in `/feed`, `/rss`, `/feed.xml`, or `/atom.xml`. If the user gives a site URL, try appending `/feed` or `/rss`.
|
|
39
|
+
- Output is XML; the agent can summarize titles and links from `<title>`, `<link>`, `<item>` (RSS) or `<entry>` (Atom).
|
|
40
|
+
- For "latest posts" or "recent from …", fetch the feed and report the first few items (title + link).
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: sulala-portal
|
|
3
|
+
description: Use your Sulala Portal connections (Gmail, Google Docs, etc.) via the Portal Gateway API. List and use OAuth-connected accounts.
|
|
4
|
+
base_url_env: PORTAL_GATEWAY_URL
|
|
5
|
+
credentials:
|
|
6
|
+
- PORTAL_API_KEY
|
|
7
|
+
auth_scheme: Bearer
|
|
8
|
+
auth_location: header
|
|
9
|
+
auth_param: Authorization
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
# Sulala Portal Connection Skill
|
|
13
|
+
|
|
14
|
+
**For listing connections, Gmail, or any Portal Gateway request: use the tool `sulala-portal_request` only.** Do not use other tools whose names end in `_request` (e.g. weather-geocode_request) for connections or Portal — they call different APIs and will return 404.
|
|
15
|
+
|
|
16
|
+
When the user says "list connection(s)" or "list connections": call **sulala-portal_request** with `{ "method": "GET", "path": "connections" }` immediately. Do not use echo, memory_search, or memory_write for this — only sulala-portal_request returns the actual Portal connections.
|
|
17
|
+
|
|
18
|
+
## How to call
|
|
19
|
+
|
|
20
|
+
Use the generic request tool for this skill:
|
|
21
|
+
|
|
22
|
+
- **Tool id**: `sulala-portal_request`
|
|
23
|
+
- **Method**: `GET` (list) or `POST` (connect, get token, provider actions)
|
|
24
|
+
- **Path**: relative to the gateway base (e.g. `connections`, `connections/{id}/use`). No leading slash.
|
|
25
|
+
- **Query** (optional): object, e.g. `{ "provider": "gmail" }` if the API supports filtering.
|
|
26
|
+
- **Body** (optional): for POST, e.g. `{ "provider": "gmail" }` for connect, or provider-specific payload.
|
|
27
|
+
|
|
28
|
+
Example — list all connections:
|
|
29
|
+
|
|
30
|
+
```json
|
|
31
|
+
{
|
|
32
|
+
"method": "GET",
|
|
33
|
+
"path": "connections"
|
|
34
|
+
}
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
Example — list connections (filter by provider if supported):
|
|
38
|
+
|
|
39
|
+
```json
|
|
40
|
+
{
|
|
41
|
+
"method": "GET",
|
|
42
|
+
"path": "connections",
|
|
43
|
+
"query": { "provider": "gmail" }
|
|
44
|
+
}
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
Example — get access token: use the connection’s `id` (UUID) from the list in the path. If you get 404, try the connection’s `connection_id` (e.g. `conn_gmail_...`) in the path instead.
|
|
48
|
+
|
|
49
|
+
```json
|
|
50
|
+
{
|
|
51
|
+
"method": "POST",
|
|
52
|
+
"path": "connections/{id}/use"
|
|
53
|
+
}
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
## Configuration (runtime)
|
|
57
|
+
|
|
58
|
+
Set these in your environment or in the agent/skill config (e.g. `~/.agent-os/configs/sulala-portal.json` or env vars):
|
|
59
|
+
|
|
60
|
+
- **PORTAL_GATEWAY_URL** — Base URL of the Portal Gateway (e.g. `https://portal.sulala.ai/api/gateway`). No trailing slash.
|
|
61
|
+
- **PORTAL_API_KEY** — Your Portal API key used to authenticate requests.
|
|
62
|
+
|
|
63
|
+
Get **PORTAL_API_KEY** from [portal.sulala.ai](https://portal.sulala.ai) → Account / Developer / Settings → create or copy an API key. Connections (Gmail, Docs, etc.) are managed in the same portal; connect accounts there, then use this skill to list and use them via the API.
|
|
64
|
+
|
|
65
|
+
---
|
|
66
|
+
|
|
67
|
+
## Portal Gateway API reference
|
|
68
|
+
|
|
69
|
+
All paths below are relative to the gateway base (PORTAL_GATEWAY_URL). Use them in the `path` field of sulala-portal_request.
|
|
70
|
+
|
|
71
|
+
### Connections
|
|
72
|
+
|
|
73
|
+
| Method | Path | Description |
|
|
74
|
+
|--------|------|-------------|
|
|
75
|
+
| GET | `connections` | List the tenant's connections. Response: `{ connections: Array<{ id, connection_id, provider, created_at }> }`. |
|
|
76
|
+
| POST | `connect` | Start OAuth for a provider. Body: `{ "provider": string, "redirect_success"?: string }` (e.g. provider `"gmail"`, `"github"`). Response: `{ authUrl, connectionId }`. Redirect user to authUrl. 402 = free plan limit. |
|
|
77
|
+
| POST | `connections/:id/use` | Get an access token for a connection (for agent or backend). Response: `{ connectionId, provider, accessToken, scopes }`. 404 = not found or not owned. **If 404, try `connection_id` from the list in the path instead of `id`.** |
|
|
78
|
+
| DELETE | `connections/:id` | Disconnect and remove the connection. Response: `{ ok: true }`. 404 = not found or access denied. |
|
|
79
|
+
|
|
80
|
+
### Provider-specific actions (same auth)
|
|
81
|
+
|
|
82
|
+
| Method | Path | Body | Description |
|
|
83
|
+
|--------|------|------|-------------|
|
|
84
|
+
| POST | `connections/:id/bsky-create-post` | `{ text, imageBase64?, imageMimeType?, alt? }` | Create a Bluesky post. |
|
|
85
|
+
| POST | `connections/:id/bsky-request` | As required by integrations API | Bluesky XRPC proxy (DPoP-bound). |
|
|
86
|
+
| POST | `connections/:id/youtube-upload` | `{ title, description?, privacyStatus?, video_url }` | Upload a video to YouTube. |
|
|
87
|
+
|
|
88
|
+
All return integration-specific success/error payloads; 404 if connection not found or not owned. After listing, filter the `connections` array by `provider === "gmail"` (or use query if the API supports it).
|
|
89
|
+
|
|
90
|
+
## Base URL
|
|
91
|
+
|
|
92
|
+
The base URL is **not** set in the skill. Configure **PORTAL_GATEWAY_URL** where the agent runs (e.g. `https://portal.sulala.ai/api/gateway`). The loader uses that value at runtime so the skill never contains a hardcoded integration URL.
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: translate
|
|
3
|
+
description: Translate text to another language (e.g. via LibreTranslate). Use when the user says "translate this to [language]", "translate the above to Spanish", "translate to French", "how do you say X in Y".
|
|
4
|
+
metadata:
|
|
5
|
+
clawdbot:
|
|
6
|
+
emoji: "🌐"
|
|
7
|
+
requires:
|
|
8
|
+
bins:
|
|
9
|
+
- curl
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
# Translate
|
|
13
|
+
|
|
14
|
+
Use **curl** to call a translation API. **LibreTranslate** is free and open; public instances exist. No API key for many instances.
|
|
15
|
+
|
|
16
|
+
## LibreTranslate (POST)
|
|
17
|
+
|
|
18
|
+
Most instances use the same API. Replace `https://libretranslate.com` with another instance if needed.
|
|
19
|
+
|
|
20
|
+
**Translate text:**
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
curl -s -X POST "https://libretranslate.com/translate" \
|
|
24
|
+
-H "Content-Type: application/json" \
|
|
25
|
+
-d '{"q":"Hello world","source":"en","target":"es"}'
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
Response: `{"translatedText":"Hola mundo"}`. Use **jq** to extract: `... | jq -r '.translatedText'`.
|
|
29
|
+
|
|
30
|
+
**Parameters:**
|
|
31
|
+
|
|
32
|
+
- `q` — text to translate
|
|
33
|
+
- `source` — source language code (e.g. `en`, `fr`, `auto` for auto-detect)
|
|
34
|
+
- `target` — target language code (e.g. `es`, `de`, `ja`)
|
|
35
|
+
|
|
36
|
+
**Example with jq:**
|
|
37
|
+
|
|
38
|
+
```bash
|
|
39
|
+
curl -s -X POST "https://libretranslate.com/translate" \
|
|
40
|
+
-H "Content-Type: application/json" \
|
|
41
|
+
-d '{"q":"Hello world","source":"en","target":"es"}' | jq -r '.translatedText'
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
## Language codes (common)
|
|
45
|
+
|
|
46
|
+
`en` English · `es` Spanish · `fr` French · `de` German · `it` Italian · `pt` Portuguese · `ja` Japanese · `zh` Chinese · `ko` Korean · `ar` Arabic · `ru` Russian.
|
|
47
|
+
|
|
48
|
+
## Tips
|
|
49
|
+
|
|
50
|
+
- Escape quotes in the JSON body for the shell (e.g. `\"` or use single quotes around the string and escape only inner quotes).
|
|
51
|
+
- If the default instance is rate-limited, try another public LibreTranslate instance or document an optional config for API URL/key.
|
|
52
|
+
- For "translate this to Spanish", set `target` to `es` and put the user's text in `q`; use `source: "auto"` if the source language is unknown.
|