@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.
Files changed (56) hide show
  1. package/data/agents/briefing_agent.json +12 -0
  2. package/data/agents/dev_agent.json +10 -0
  3. package/data/agents/manager_agent.json +11 -0
  4. package/data/agents/media_agent.json +10 -0
  5. package/data/agents/personal_agent.json +11 -0
  6. package/data/agents/research_agent.json +10 -0
  7. package/data/agents/social_media_agent.json +10 -0
  8. package/data/agents/writer_agent.json +10 -0
  9. package/data/skills/bluesky/SKILL.md +63 -0
  10. package/data/skills/bluesky/config.schema.json +17 -0
  11. package/data/skills/bluesky/scripts/post.sh +48 -0
  12. package/data/skills/bluesky/scripts/timeline.sh +37 -0
  13. package/data/skills/date/SKILL.md +53 -0
  14. package/data/skills/fetch/SKILL.md +42 -0
  15. package/data/skills/file-search/SKILL.md +122 -0
  16. package/data/skills/file-stats/SKILL.md +68 -0
  17. package/data/skills/git/SKILL.md +60 -0
  18. package/data/skills/gmail/SKILL.md +55 -0
  19. package/data/skills/gmail/references/send-email.md +54 -0
  20. package/data/skills/gmail/scripts/send_email.py +94 -0
  21. package/data/skills/hash/SKILL.md +56 -0
  22. package/data/skills/jq/SKILL.md +66 -0
  23. package/data/skills/markdown-to-html/SKILL.md +47 -0
  24. package/data/skills/memory/SKILL.md +64 -0
  25. package/data/skills/qr-code/SKILL.md +65 -0
  26. package/data/skills/rss/SKILL.md +40 -0
  27. package/data/skills/sulala-portal/SKILL.md +92 -0
  28. package/data/skills/translate/SKILL.md +52 -0
  29. package/data/skills/weather/SKILL.md +59 -0
  30. package/data/skills/web-search/SKILL.md +55 -0
  31. package/data/skills/web-search/config.schema.json +12 -0
  32. package/data/skills/web-search/scripts/search.sh +21 -0
  33. package/data/skills/webhook/SKILL.md +101 -0
  34. package/data/skills/webhook/config.schema.json +11 -0
  35. package/data/skills/webhook/scripts/post.sh +13 -0
  36. package/data/skills/youtube/SKILL.md +91 -0
  37. package/data/skills/youtube/config.schema.json +11 -0
  38. package/data/skills/youtube/package.json +8 -0
  39. package/data/skills/youtube/references/youtube-upload.md +65 -0
  40. package/data/skills/youtube/requirements.txt +3 -0
  41. package/data/skills/youtube/scripts/youtube_upload.js +200 -0
  42. package/data/skills/youtube/scripts/youtube_upload.py +125 -0
  43. package/data/templates/BOOT.md +11 -0
  44. package/data/templates/BOOTSTRAP.md +62 -0
  45. package/data/templates/HEARTBEAT.md +12 -0
  46. package/data/templates/IDENTITY.dev.md +62 -0
  47. package/data/templates/IDENTITY.md +60 -0
  48. package/data/templates/SYSTEM.dev.md +82 -0
  49. package/data/templates/SYSTEM.md +65 -0
  50. package/data/templates/TOOLS.dev.md +24 -0
  51. package/data/templates/TOOLS.md +47 -0
  52. package/data/templates/USER.dev.md +18 -0
  53. package/data/templates/USER.md +23 -0
  54. package/dist/cli.js +22 -13
  55. package/dist/index.js +14 -5
  56. 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.